cleans up ordered collection mixin
This commit is contained in:
parent
1ec2f20486
commit
eb6206252d
|
@ -1,18 +1,16 @@
|
|||
''' base model with default fields '''
|
||||
from base64 import b64encode
|
||||
from dataclasses import dataclass
|
||||
from typing import Callable
|
||||
from uuid import uuid4
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from Crypto.PublicKey import RSA
|
||||
from Crypto.Signature import pkcs1_15
|
||||
from Crypto.Hash import SHA256
|
||||
from django.core.paginator import Paginator
|
||||
from django.db import models
|
||||
from django.dispatch import receiver
|
||||
|
||||
from bookwyrm import activitypub
|
||||
from bookwyrm.settings import DOMAIN
|
||||
from bookwyrm.settings import DOMAIN, PAGE_LENGTH
|
||||
from .fields import RemoteIdField
|
||||
|
||||
|
||||
|
@ -52,15 +50,6 @@ def execute_after_save(sender, instance, created, *args, **kwargs):
|
|||
instance.save()
|
||||
|
||||
|
||||
def get_field_name(field):
|
||||
''' model_field_name to activitypubFieldName '''
|
||||
if field.activitypub_field:
|
||||
return field.activitypub_field
|
||||
name = field.name.split('.')[-1]
|
||||
components = name.split('_')
|
||||
return components[0] + ''.join(x.title() for x in components[1:])
|
||||
|
||||
|
||||
def unfurl_related_field(related_field):
|
||||
''' load reverse lookups (like public key owner or Status attachment '''
|
||||
if hasattr(related_field, 'all'):
|
||||
|
@ -78,15 +67,16 @@ class ActivitypubMixin:
|
|||
def to_activity(self):
|
||||
''' convert from a model to an activity '''
|
||||
activity = {}
|
||||
for field in self.__class__._meta.get_fields():
|
||||
for field in self._meta.get_fields():
|
||||
if not hasattr(field, 'field_to_activity'):
|
||||
continue
|
||||
key = get_field_name(field)
|
||||
value = field.field_to_activity(getattr(self, field.name))
|
||||
if value is None:
|
||||
continue
|
||||
|
||||
key = field.get_activitypub_field()
|
||||
if key in activity and isinstance(activity[key], list):
|
||||
# handles tags on status, which accumulate across fields
|
||||
activity[key] += value
|
||||
else:
|
||||
activity[key] = value
|
||||
|
@ -125,15 +115,12 @@ class ActivitypubMixin:
|
|||
|
||||
def to_delete_activity(self, user):
|
||||
''' notice of deletion '''
|
||||
# this should be a tombstone
|
||||
activity_object = self.to_activity()
|
||||
|
||||
return activitypub.Delete(
|
||||
id=self.remote_id + '/activity',
|
||||
actor=user.remote_id,
|
||||
to=['%s/followers' % user.remote_id],
|
||||
cc=['https://www.w3.org/ns/activitystreams#Public'],
|
||||
object=activity_object,
|
||||
object=self.to_activity(),
|
||||
).serialize()
|
||||
|
||||
|
||||
|
@ -165,81 +152,53 @@ class OrderedCollectionPageMixin(ActivitypubMixin):
|
|||
''' this can be overriden if there's a special remote id, ie outbox '''
|
||||
return self.remote_id
|
||||
|
||||
def page(self, min_id=None, max_id=None):
|
||||
''' helper function to create the pagination url '''
|
||||
params = {'page': 'true'}
|
||||
if min_id:
|
||||
params['min_id'] = min_id
|
||||
if max_id:
|
||||
params['max_id'] = max_id
|
||||
return '?%s' % urlencode(params)
|
||||
|
||||
def next_page(self, items):
|
||||
''' use the max id of the last item '''
|
||||
if not items.count():
|
||||
return ''
|
||||
return self.page(max_id=items[items.count() - 1].id)
|
||||
|
||||
def prev_page(self, items):
|
||||
''' use the min id of the first item '''
|
||||
if not items.count():
|
||||
return ''
|
||||
return self.page(min_id=items[0].id)
|
||||
|
||||
def to_ordered_collection_page(self, queryset, remote_id, \
|
||||
id_only=False, min_id=None, max_id=None):
|
||||
''' serialize and pagiante a queryset '''
|
||||
# TODO: weird place to define this
|
||||
limit = 20
|
||||
# filters for use in the django queryset min/max
|
||||
filters = {}
|
||||
if min_id is not None:
|
||||
filters['id__gt'] = min_id
|
||||
if max_id is not None:
|
||||
filters['id__lte'] = max_id
|
||||
page_id = self.page(min_id=min_id, max_id=max_id)
|
||||
|
||||
items = queryset.filter(
|
||||
**filters
|
||||
).all()[:limit]
|
||||
|
||||
if id_only:
|
||||
page = [s.remote_id for s in items]
|
||||
else:
|
||||
page = [s.to_activity() for s in items]
|
||||
return activitypub.OrderedCollectionPage(
|
||||
id='%s%s' % (remote_id, page_id),
|
||||
partOf=remote_id,
|
||||
orderedItems=page,
|
||||
next='%s%s' % (remote_id, self.next_page(items)),
|
||||
prev='%s%s' % (remote_id, self.prev_page(items))
|
||||
).serialize()
|
||||
|
||||
def to_ordered_collection(self, queryset, \
|
||||
remote_id=None, page=False, **kwargs):
|
||||
''' an ordered collection of whatevers '''
|
||||
remote_id = remote_id or self.remote_id
|
||||
if page:
|
||||
return self.to_ordered_collection_page(
|
||||
return to_ordered_collection_page(
|
||||
queryset, remote_id, **kwargs)
|
||||
name = ''
|
||||
if hasattr(self, 'name'):
|
||||
name = self.name
|
||||
owner = ''
|
||||
if hasattr(self, 'user'):
|
||||
owner = self.user.remote_id
|
||||
name = self.name if hasattr(self, 'name') else None
|
||||
owner = self.user.remote_id if hasattr(self, 'user') else ''
|
||||
|
||||
size = queryset.count()
|
||||
paginated = Paginator(queryset, PAGE_LENGTH)
|
||||
return activitypub.OrderedCollection(
|
||||
id=remote_id,
|
||||
totalItems=size,
|
||||
totalItems=paginated.count,
|
||||
name=name,
|
||||
owner=owner,
|
||||
first='%s%s' % (remote_id, self.page()),
|
||||
last='%s%s' % (remote_id, self.page(min_id=0))
|
||||
first='%s?page=1' % remote_id,
|
||||
last='%s?page=%d' % (remote_id, paginated.num_pages)
|
||||
).serialize()
|
||||
|
||||
|
||||
def to_ordered_collection_page(queryset, remote_id, id_only=False, page=1):
|
||||
''' serialize and pagiante a queryset '''
|
||||
paginated = Paginator(queryset, PAGE_LENGTH)
|
||||
|
||||
activity_page = paginated.page(page)
|
||||
if id_only:
|
||||
items = [s.remote_id for s in activity_page.object_list]
|
||||
else:
|
||||
items = [s.to_activity() for s in activity_page.object_list]
|
||||
|
||||
prev_page = next_page = None
|
||||
if activity_page.has_next():
|
||||
next_page = '%s?page=%d' % (remote_id, activity_page.next_page_number())
|
||||
if activity_page.has_previous():
|
||||
prev_page = '%s?page=%d' % \
|
||||
(remote_id, activity_page.previous_page_number())
|
||||
return activitypub.OrderedCollectionPage(
|
||||
id='%s?page=%s' % (remote_id, page),
|
||||
partOf=remote_id,
|
||||
orderedItems=items,
|
||||
next=next_page,
|
||||
prev=prev_page
|
||||
).serialize()
|
||||
|
||||
|
||||
class OrderedCollectionMixin(OrderedCollectionPageMixin):
|
||||
''' extends activitypub models to work as ordered collections '''
|
||||
@property
|
||||
|
@ -252,12 +211,3 @@ class OrderedCollectionMixin(OrderedCollectionPageMixin):
|
|||
def to_activity(self, **kwargs):
|
||||
''' an ordered collection of the specified model queryset '''
|
||||
return self.to_ordered_collection(self.collection_queryset, **kwargs)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ActivityMapping:
|
||||
''' translate between an activitypub json field and a model field '''
|
||||
activity_key: str
|
||||
model_key: str
|
||||
activity_formatter: Callable = lambda x: x
|
||||
model_formatter: Callable = lambda x: x
|
||||
|
|
|
@ -36,7 +36,7 @@ class ActivitypubFieldMixin:
|
|||
def field_to_activity(self, value):
|
||||
''' formatter to convert a model value into activitypub '''
|
||||
if hasattr(self, 'activitypub_wrapper'):
|
||||
value = {self.activitypub_wrapper: value}
|
||||
return {self.activitypub_wrapper: value}
|
||||
return value
|
||||
|
||||
def from_activity(self, activity_data):
|
||||
|
@ -46,6 +46,14 @@ class ActivitypubFieldMixin:
|
|||
value = value.get(self.activitypub_wrapper)
|
||||
return value
|
||||
|
||||
def get_activitypub_field(self):
|
||||
''' model_field_name to activitypubFieldName '''
|
||||
if self.activitypub_field:
|
||||
return self.activitypub_field
|
||||
name = self.name.split('.')[-1]
|
||||
components = name.split('_')
|
||||
return components[0] + ''.join(x.title() for x in components[1:])
|
||||
|
||||
|
||||
class RemoteIdField(ActivitypubFieldMixin, models.CharField):
|
||||
''' a url that serves as a unique identifier '''
|
||||
|
@ -91,8 +99,6 @@ class ForeignKey(ActivitypubFieldMixin, models.ForeignKey):
|
|||
if not value:
|
||||
return None
|
||||
return value.remote_id
|
||||
def from_activity(self, activity_data):
|
||||
pass# TODO
|
||||
|
||||
|
||||
class OneToOneField(ActivitypubFieldMixin, models.OneToOneField):
|
||||
|
@ -102,9 +108,6 @@ class OneToOneField(ActivitypubFieldMixin, models.OneToOneField):
|
|||
return None
|
||||
return value.to_activity()
|
||||
|
||||
def from_activity(self, activity_data):
|
||||
pass# TODO
|
||||
|
||||
|
||||
class ManyToManyField(ActivitypubFieldMixin, models.ManyToManyField):
|
||||
''' activitypub-aware many to many field '''
|
||||
|
@ -123,6 +126,7 @@ class ManyToManyField(ActivitypubFieldMixin, models.ManyToManyField):
|
|||
values = super().from_activity(activity_data)
|
||||
return values# TODO
|
||||
|
||||
|
||||
class TagField(ManyToManyField):
|
||||
''' special case of many to many that uses Tags '''
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
@ -145,7 +149,6 @@ class TagField(ManyToManyField):
|
|||
|
||||
def image_serializer(value):
|
||||
''' helper for serializing images '''
|
||||
print(value)
|
||||
if value and hasattr(value, 'url'):
|
||||
url = value.url
|
||||
else:
|
||||
|
|
|
@ -5,19 +5,15 @@ from django.db import models
|
|||
|
||||
from bookwyrm import activitypub
|
||||
from bookwyrm.settings import DOMAIN
|
||||
from .base_model import OrderedCollectionMixin, BookWyrmModel, ActivityMapping
|
||||
from .base_model import OrderedCollectionMixin, BookWyrmModel
|
||||
from . import fields
|
||||
|
||||
|
||||
class Tag(OrderedCollectionMixin, BookWyrmModel):
|
||||
''' freeform tags for books '''
|
||||
name = models.CharField(max_length=100, unique=True)
|
||||
name = fields.CharField(max_length=100, unique=True)
|
||||
identifier = models.CharField(max_length=100)
|
||||
|
||||
activity_mappings = [
|
||||
ActivityMapping('id', 'remote_id'),
|
||||
ActivityMapping('name', 'name'),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def book_queryset(cls, identifier):
|
||||
''' county of books associated with this tag '''
|
||||
|
@ -44,16 +40,12 @@ class Tag(OrderedCollectionMixin, BookWyrmModel):
|
|||
|
||||
class UserTag(BookWyrmModel):
|
||||
''' an instance of a tag on a book by a user '''
|
||||
user = models.ForeignKey('User', on_delete=models.PROTECT)
|
||||
book = models.ForeignKey('Edition', on_delete=models.PROTECT)
|
||||
tag = models.ForeignKey('Tag', on_delete=models.PROTECT)
|
||||
|
||||
activity_mappings = [
|
||||
ActivityMapping('id', 'remote_id'),
|
||||
ActivityMapping('actor', 'user'),
|
||||
ActivityMapping('object', 'book'),
|
||||
ActivityMapping('target', 'tag'),
|
||||
]
|
||||
user = fields.ForeignKey(
|
||||
'User', on_delete=models.PROTECT, activitypub_field='actor')
|
||||
book = fields.ForeignKey(
|
||||
'Edition', on_delete=models.PROTECT, activitypub_field='object')
|
||||
tag = fields.ForeignKey(
|
||||
'Tag', on_delete=models.PROTECT, activitypub_field='target')
|
||||
|
||||
activity_serializer = activitypub.AddBook
|
||||
|
||||
|
|
|
@ -109,13 +109,13 @@ class User(OrderedCollectionPageMixin, AbstractUser):
|
|||
def to_following_activity(self, **kwargs):
|
||||
''' activitypub following list '''
|
||||
remote_id = '%s/following' % self.remote_id
|
||||
return self.to_ordered_collection(self.following, \
|
||||
return self.to_ordered_collection(self.following.all(), \
|
||||
remote_id=remote_id, id_only=True, **kwargs)
|
||||
|
||||
def to_followers_activity(self, **kwargs):
|
||||
''' activitypub followers list '''
|
||||
remote_id = '%s/followers' % self.remote_id
|
||||
return self.to_ordered_collection(self.followers, \
|
||||
return self.to_ordered_collection(self.followers.all(), \
|
||||
remote_id=remote_id, id_only=True, **kwargs)
|
||||
|
||||
def to_activity(self):
|
||||
|
|
Loading…
Reference in New Issue