Updates status model and serializer
This commit is contained in:
parent
8bc0a57bd4
commit
3966c84e08
@ -8,7 +8,6 @@ from .image import Image
|
|||||||
@dataclass(init=False)
|
@dataclass(init=False)
|
||||||
class Tombstone(ActivityObject):
|
class Tombstone(ActivityObject):
|
||||||
''' the placeholder for a deleted status '''
|
''' the placeholder for a deleted status '''
|
||||||
url: str
|
|
||||||
published: str
|
published: str
|
||||||
deleted: str
|
deleted: str
|
||||||
type: str = 'Tombstone'
|
type: str = 'Tombstone'
|
||||||
@ -17,14 +16,13 @@ class Tombstone(ActivityObject):
|
|||||||
@dataclass(init=False)
|
@dataclass(init=False)
|
||||||
class Note(ActivityObject):
|
class Note(ActivityObject):
|
||||||
''' Note activity '''
|
''' Note activity '''
|
||||||
url: str
|
|
||||||
inReplyTo: str
|
|
||||||
published: str
|
published: str
|
||||||
attributedTo: str
|
attributedTo: str
|
||||||
to: List[str]
|
|
||||||
cc: List[str]
|
|
||||||
content: str
|
content: str
|
||||||
replies: Dict
|
to: List[str] = field(default_factory=lambda: [])
|
||||||
|
cc: List[str] = field(default_factory=lambda: [])
|
||||||
|
replies: Dict = field(default_factory=lambda: {})
|
||||||
|
inReplyTo: str = ''
|
||||||
tag: List[Link] = field(default_factory=lambda: [])
|
tag: List[Link] = field(default_factory=lambda: [])
|
||||||
attachment: List[Image] = field(default_factory=lambda: [])
|
attachment: List[Image] = field(default_factory=lambda: [])
|
||||||
sensitive: bool = False
|
sensitive: bool = False
|
||||||
|
@ -3,30 +3,27 @@ from django.db import models
|
|||||||
|
|
||||||
from bookwyrm import activitypub
|
from bookwyrm import activitypub
|
||||||
from .base_model import ActivitypubMixin
|
from .base_model import ActivitypubMixin
|
||||||
from .base_model import ActivityMapping, BookWyrmModel
|
from .base_model import BookWyrmModel
|
||||||
|
from . import fields
|
||||||
|
|
||||||
|
|
||||||
class Attachment(ActivitypubMixin, BookWyrmModel):
|
class Attachment(ActivitypubMixin, BookWyrmModel):
|
||||||
''' an image (or, in the future, video etc) associated with a status '''
|
''' an image (or, in the future, video etc) associated with a status '''
|
||||||
status = models.ForeignKey(
|
status = fields.ForeignKey(
|
||||||
'Status',
|
'Status',
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
related_name='attachments',
|
related_name='attachments',
|
||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
|
reverse_unfurl = True
|
||||||
class Meta:
|
class Meta:
|
||||||
''' one day we'll have other types of attachments besides images '''
|
''' one day we'll have other types of attachments besides images '''
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
activity_mappings = [
|
|
||||||
ActivityMapping('id', 'remote_id'),
|
|
||||||
ActivityMapping('url', 'image'),
|
|
||||||
ActivityMapping('name', 'caption'),
|
|
||||||
]
|
|
||||||
|
|
||||||
class Image(Attachment):
|
class Image(Attachment):
|
||||||
''' an image attachment '''
|
''' an image attachment '''
|
||||||
image = models.ImageField(upload_to='status/', null=True, blank=True)
|
image = fields.ImageField(upload_to='status/', null=True, blank=True)
|
||||||
caption = models.TextField(null=True, blank=True)
|
caption = fields.TextField(null=True, blank=True)
|
||||||
|
|
||||||
activity_serializer = activitypub.Image
|
activity_serializer = activitypub.Image
|
||||||
|
@ -61,9 +61,19 @@ def get_field_name(field):
|
|||||||
return components[0] + ''.join(x.title() for x in components[1:])
|
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'):
|
||||||
|
return [unfurl_related_field(i) for i in related_field.all()]
|
||||||
|
if related_field.reverse_unfurl:
|
||||||
|
return related_field.to_activity()
|
||||||
|
return related_field.remote_id
|
||||||
|
|
||||||
|
|
||||||
class ActivitypubMixin:
|
class ActivitypubMixin:
|
||||||
''' add this mixin for models that are AP serializable '''
|
''' add this mixin for models that are AP serializable '''
|
||||||
activity_serializer = lambda: {}
|
activity_serializer = lambda: {}
|
||||||
|
reverse_unfurl = False
|
||||||
|
|
||||||
def to_activity(self):
|
def to_activity(self):
|
||||||
''' convert from a model to an activity '''
|
''' convert from a model to an activity '''
|
||||||
@ -73,18 +83,24 @@ class ActivitypubMixin:
|
|||||||
continue
|
continue
|
||||||
key = get_field_name(field)
|
key = get_field_name(field)
|
||||||
value = field.to_activity(getattr(self, field.name))
|
value = field.to_activity(getattr(self, field.name))
|
||||||
if value is not None:
|
if value is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if key in activity and isinstance(activity[key], list):
|
||||||
|
activity[key] += value
|
||||||
|
else:
|
||||||
activity[key] = value
|
activity[key] = value
|
||||||
if hasattr(self, 'serialize_reverse_fields'):
|
if hasattr(self, 'serialize_reverse_fields'):
|
||||||
for field_name in self.serialize_reverse_fields:
|
for field_name in self.serialize_reverse_fields:
|
||||||
activity[field_name] = getattr(self, field_name).remote_id
|
related_field = getattr(self, field_name)
|
||||||
|
activity[field_name] = unfurl_related_field(related_field)
|
||||||
|
|
||||||
return self.activity_serializer(**activity).serialize()
|
return self.activity_serializer(**activity).serialize()
|
||||||
|
|
||||||
|
|
||||||
def to_create_activity(self, user, pure=False):
|
def to_create_activity(self, user):
|
||||||
''' returns the object wrapped in a Create activity '''
|
''' returns the object wrapped in a Create activity '''
|
||||||
activity_object = self.to_activity(pure=pure)
|
activity_object = self.to_activity()
|
||||||
|
|
||||||
signer = pkcs1_15.new(RSA.import_key(user.private_key))
|
signer = pkcs1_15.new(RSA.import_key(user.private_key))
|
||||||
content = activity_object['content']
|
content = activity_object['content']
|
||||||
@ -100,8 +116,8 @@ class ActivitypubMixin:
|
|||||||
return activitypub.Create(
|
return activitypub.Create(
|
||||||
id=create_id,
|
id=create_id,
|
||||||
actor=user.remote_id,
|
actor=user.remote_id,
|
||||||
to=['%s/followers' % user.remote_id],
|
to=activity_object['to'],
|
||||||
cc=['https://www.w3.org/ns/activitystreams#Public'],
|
cc=activity_object['cc'],
|
||||||
object=activity_object,
|
object=activity_object,
|
||||||
signature=signature,
|
signature=signature,
|
||||||
).serialize()
|
).serialize()
|
||||||
@ -245,30 +261,3 @@ class ActivityMapping:
|
|||||||
model_key: str
|
model_key: str
|
||||||
activity_formatter: Callable = lambda x: x
|
activity_formatter: Callable = lambda x: x
|
||||||
model_formatter: Callable = lambda x: x
|
model_formatter: Callable = lambda x: x
|
||||||
|
|
||||||
|
|
||||||
def tag_formatter(items, name_field, activity_type):
|
|
||||||
''' helper function to format lists of foreign keys into Tags '''
|
|
||||||
tags = []
|
|
||||||
for item in items.all():
|
|
||||||
tags.append(activitypub.Link(
|
|
||||||
href=item.remote_id,
|
|
||||||
name=getattr(item, name_field),
|
|
||||||
type=activity_type
|
|
||||||
))
|
|
||||||
return tags
|
|
||||||
|
|
||||||
|
|
||||||
def image_formatter(image):
|
|
||||||
''' convert images into activitypub json '''
|
|
||||||
if image and hasattr(image, 'url'):
|
|
||||||
url = image.url
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
url = 'https://%s%s' % (DOMAIN, url)
|
|
||||||
return activitypub.Image(url=url)
|
|
||||||
|
|
||||||
|
|
||||||
def image_attachments_formatter(images):
|
|
||||||
''' create a list of image attachments '''
|
|
||||||
return [image_formatter(i) for i in images]
|
|
||||||
|
@ -155,6 +155,7 @@ class Edition(Book):
|
|||||||
'Work', on_delete=models.PROTECT, null=True, related_name='editions')
|
'Work', on_delete=models.PROTECT, null=True, related_name='editions')
|
||||||
|
|
||||||
activity_serializer = activitypub.Edition
|
activity_serializer = activitypub.Edition
|
||||||
|
name_field = 'title'
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
''' calculate isbn 10/13 '''
|
''' calculate isbn 10/13 '''
|
||||||
|
@ -71,6 +71,7 @@ class UsernameField(ActivitypubFieldMixin, models.CharField):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def deconstruct(self):
|
def deconstruct(self):
|
||||||
|
''' implementation of models.Field deconstruct '''
|
||||||
name, path, args, kwargs = super().deconstruct()
|
name, path, args, kwargs = super().deconstruct()
|
||||||
del kwargs['verbose_name']
|
del kwargs['verbose_name']
|
||||||
del kwargs['max_length']
|
del kwargs['max_length']
|
||||||
@ -86,6 +87,8 @@ class UsernameField(ActivitypubFieldMixin, models.CharField):
|
|||||||
class ForeignKey(ActivitypubFieldMixin, models.ForeignKey):
|
class ForeignKey(ActivitypubFieldMixin, models.ForeignKey):
|
||||||
''' activitypub-aware foreign key field '''
|
''' activitypub-aware foreign key field '''
|
||||||
def to_activity(self, value):
|
def to_activity(self, value):
|
||||||
|
if not value:
|
||||||
|
return None
|
||||||
return value.remote_id
|
return value.remote_id
|
||||||
def from_activity(self, activity_data):
|
def from_activity(self, activity_data):
|
||||||
pass# TODO
|
pass# TODO
|
||||||
@ -94,6 +97,10 @@ class ForeignKey(ActivitypubFieldMixin, models.ForeignKey):
|
|||||||
class OneToOneField(ActivitypubFieldMixin, models.OneToOneField):
|
class OneToOneField(ActivitypubFieldMixin, models.OneToOneField):
|
||||||
''' activitypub-aware foreign key field '''
|
''' activitypub-aware foreign key field '''
|
||||||
def to_activity(self, value):
|
def to_activity(self, value):
|
||||||
|
print('HIIIII')
|
||||||
|
print(value)
|
||||||
|
if not value:
|
||||||
|
return None
|
||||||
return value.to_activity()
|
return value.to_activity()
|
||||||
|
|
||||||
def from_activity(self, activity_data):
|
def from_activity(self, activity_data):
|
||||||
@ -113,20 +120,45 @@ class ManyToManyField(ActivitypubFieldMixin, models.ManyToManyField):
|
|||||||
|
|
||||||
def from_activity(self, activity_data):
|
def from_activity(self, activity_data):
|
||||||
if self.link_only:
|
if self.link_only:
|
||||||
return
|
return None
|
||||||
values = super().from_activity(self, activity_data)
|
values = super().from_activity(activity_data)
|
||||||
return values# TODO
|
return values# TODO
|
||||||
|
|
||||||
|
class TagField(ManyToManyField):
|
||||||
|
''' special case of many to many that uses Tags '''
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.activitypub_field = 'tag'
|
||||||
|
|
||||||
|
def to_activity(self, value):
|
||||||
|
tags = []
|
||||||
|
for item in value.all():
|
||||||
|
activity_type = item.__class__.__name__
|
||||||
|
if activity_type == 'User':
|
||||||
|
activity_type = 'Mention'
|
||||||
|
tags.append(activitypub.Link(
|
||||||
|
href=item.remote_id,
|
||||||
|
name=getattr(item, item.name_field),
|
||||||
|
type=activity_type
|
||||||
|
))
|
||||||
|
return tags
|
||||||
|
|
||||||
|
|
||||||
|
def image_serializer(value):
|
||||||
|
''' helper for serializing images '''
|
||||||
|
print(value)
|
||||||
|
if value and hasattr(value, 'url'):
|
||||||
|
url = value.url
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
url = 'https://%s%s' % (DOMAIN, url)
|
||||||
|
return activitypub.Image(url=url)
|
||||||
|
|
||||||
|
|
||||||
class ImageField(ActivitypubFieldMixin, models.ImageField):
|
class ImageField(ActivitypubFieldMixin, models.ImageField):
|
||||||
''' activitypub-aware image field '''
|
''' activitypub-aware image field '''
|
||||||
def to_activity(self, value):
|
def to_activity(self, value):
|
||||||
if value and hasattr(value, 'url'):
|
return image_serializer(value)
|
||||||
url = value.url
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
url = 'https://%s%s' % (DOMAIN, url)
|
|
||||||
return activitypub.Image(url=url)
|
|
||||||
|
|
||||||
def from_activity(self, activity_data):
|
def from_activity(self, activity_data):
|
||||||
image_slug = super().from_activity(activity_data)
|
image_slug = super().from_activity(activity_data)
|
||||||
@ -150,6 +182,12 @@ class ImageField(ActivitypubFieldMixin, models.ImageField):
|
|||||||
return [image_name, image_content]
|
return [image_name, image_content]
|
||||||
|
|
||||||
|
|
||||||
|
class DateTimeField(ActivitypubFieldMixin, models.DateTimeField):
|
||||||
|
''' activitypub-aware datetime field '''
|
||||||
|
def to_activity(self, value):
|
||||||
|
return value.isoformat()
|
||||||
|
|
||||||
|
|
||||||
class CharField(ActivitypubFieldMixin, models.CharField):
|
class CharField(ActivitypubFieldMixin, models.CharField):
|
||||||
''' activitypub-aware char field '''
|
''' activitypub-aware char field '''
|
||||||
|
|
||||||
@ -158,3 +196,6 @@ class TextField(ActivitypubFieldMixin, models.TextField):
|
|||||||
|
|
||||||
class BooleanField(ActivitypubFieldMixin, models.BooleanField):
|
class BooleanField(ActivitypubFieldMixin, models.BooleanField):
|
||||||
''' activitypub-aware boolean field '''
|
''' activitypub-aware boolean field '''
|
||||||
|
|
||||||
|
class IntegerField(ActivitypubFieldMixin, models.IntegerField):
|
||||||
|
''' activitypub-aware boolean field '''
|
||||||
|
@ -6,26 +6,27 @@ from model_utils.managers import InheritanceManager
|
|||||||
|
|
||||||
from bookwyrm import activitypub
|
from bookwyrm import activitypub
|
||||||
from .base_model import ActivitypubMixin, OrderedCollectionPageMixin
|
from .base_model import ActivitypubMixin, OrderedCollectionPageMixin
|
||||||
from .base_model import ActivityMapping, BookWyrmModel, PrivacyLevels
|
from .base_model import BookWyrmModel, PrivacyLevels
|
||||||
from .base_model import tag_formatter, image_attachments_formatter
|
from . import fields
|
||||||
|
from .fields import image_serializer
|
||||||
|
|
||||||
class Status(OrderedCollectionPageMixin, BookWyrmModel):
|
class Status(OrderedCollectionPageMixin, BookWyrmModel):
|
||||||
''' any post, like a reply to a review, etc '''
|
''' any post, like a reply to a review, etc '''
|
||||||
user = models.ForeignKey('User', on_delete=models.PROTECT)
|
user = fields.ForeignKey(
|
||||||
content = models.TextField(blank=True, null=True)
|
'User', on_delete=models.PROTECT, activitypub_field='attributedTo')
|
||||||
mention_users = models.ManyToManyField('User', related_name='mention_user')
|
content = fields.TextField(blank=True, null=True)
|
||||||
mention_books = models.ManyToManyField(
|
mention_users = fields.TagField('User', related_name='mention_user')
|
||||||
'Edition', related_name='mention_book')
|
mention_books = fields.TagField('Edition', related_name='mention_book')
|
||||||
local = models.BooleanField(default=True)
|
local = models.BooleanField(default=True)
|
||||||
privacy = models.CharField(
|
privacy = models.CharField(
|
||||||
max_length=255,
|
max_length=255,
|
||||||
default='public',
|
default='public',
|
||||||
choices=PrivacyLevels.choices
|
choices=PrivacyLevels.choices
|
||||||
)
|
)
|
||||||
sensitive = models.BooleanField(default=False)
|
sensitive = fields.BooleanField(default=False)
|
||||||
# the created date can't be this, because of receiving federated posts
|
# the created date can't be this, because of receiving federated posts
|
||||||
published_date = models.DateTimeField(default=timezone.now)
|
published_date = fields.DateTimeField(
|
||||||
|
default=timezone.now, activitypub_field='published')
|
||||||
deleted = models.BooleanField(default=False)
|
deleted = models.BooleanField(default=False)
|
||||||
deleted_date = models.DateTimeField(blank=True, null=True)
|
deleted_date = models.DateTimeField(blank=True, null=True)
|
||||||
favorites = models.ManyToManyField(
|
favorites = models.ManyToManyField(
|
||||||
@ -35,79 +36,21 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
|
|||||||
through_fields=('status', 'user'),
|
through_fields=('status', 'user'),
|
||||||
related_name='user_favorites'
|
related_name='user_favorites'
|
||||||
)
|
)
|
||||||
reply_parent = models.ForeignKey(
|
reply_parent = fields.ForeignKey(
|
||||||
'self',
|
'self',
|
||||||
null=True,
|
null=True,
|
||||||
on_delete=models.PROTECT
|
on_delete=models.PROTECT,
|
||||||
|
activitypub_field='inReplyTo',
|
||||||
)
|
)
|
||||||
objects = InheritanceManager()
|
objects = InheritanceManager()
|
||||||
|
|
||||||
# ---- activitypub serialization settings for this model ----- #
|
|
||||||
@property
|
|
||||||
def ap_to(self):
|
|
||||||
''' should be related to post privacy I think '''
|
|
||||||
return ['https://www.w3.org/ns/activitystreams#Public']
|
|
||||||
|
|
||||||
@property
|
|
||||||
def ap_cc(self):
|
|
||||||
''' should be related to post privacy I think '''
|
|
||||||
return [self.user.ap_followers]
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ap_replies(self):
|
def ap_replies(self):
|
||||||
''' structured replies block '''
|
''' structured replies block '''
|
||||||
return self.to_replies()
|
return self.to_replies()
|
||||||
|
|
||||||
@property
|
|
||||||
def ap_status_image(self):
|
|
||||||
''' attach a book cover, if relevent '''
|
|
||||||
if hasattr(self, 'book'):
|
|
||||||
return self.book.ap_cover
|
|
||||||
if self.mention_books.first():
|
|
||||||
return self.mention_books.first().ap_cover
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
shared_mappings = [
|
|
||||||
ActivityMapping('url', 'remote_id', lambda x: None),
|
|
||||||
ActivityMapping('id', 'remote_id'),
|
|
||||||
ActivityMapping('inReplyTo', 'reply_parent'),
|
|
||||||
ActivityMapping('published', 'published_date'),
|
|
||||||
ActivityMapping('attributedTo', 'user'),
|
|
||||||
ActivityMapping('to', 'ap_to'),
|
|
||||||
ActivityMapping('cc', 'ap_cc'),
|
|
||||||
ActivityMapping('replies', 'ap_replies'),
|
|
||||||
ActivityMapping(
|
|
||||||
'tag', 'mention_books',
|
|
||||||
lambda x: tag_formatter(x, 'title', 'Book'),
|
|
||||||
),
|
|
||||||
ActivityMapping(
|
|
||||||
'tag', 'mention_users',
|
|
||||||
lambda x: tag_formatter(x, 'username', 'Mention'),
|
|
||||||
),
|
|
||||||
ActivityMapping(
|
|
||||||
'attachment', 'attachments',
|
|
||||||
lambda x: image_attachments_formatter(x.all()),
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
# serializing to bookwyrm expanded activitypub
|
|
||||||
activity_mappings = shared_mappings + [
|
|
||||||
ActivityMapping('name', 'name'),
|
|
||||||
ActivityMapping('inReplyToBook', 'book'),
|
|
||||||
ActivityMapping('rating', 'rating'),
|
|
||||||
ActivityMapping('quote', 'quote'),
|
|
||||||
ActivityMapping('content', 'content'),
|
|
||||||
]
|
|
||||||
|
|
||||||
# for serializing to standard activitypub without extended types
|
|
||||||
pure_activity_mappings = shared_mappings + [
|
|
||||||
ActivityMapping('name', 'ap_pure_name'),
|
|
||||||
ActivityMapping('content', 'ap_pure_content'),
|
|
||||||
ActivityMapping('attachment', 'ap_status_image'),
|
|
||||||
]
|
|
||||||
|
|
||||||
activity_serializer = activitypub.Note
|
activity_serializer = activitypub.Note
|
||||||
|
serialize_reverse_fields = ['attachments']
|
||||||
|
|
||||||
#----- replies collection activitypub ----#
|
#----- replies collection activitypub ----#
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -138,7 +81,43 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
|
|||||||
deleted=self.deleted_date.isoformat(),
|
deleted=self.deleted_date.isoformat(),
|
||||||
published=self.deleted_date.isoformat()
|
published=self.deleted_date.isoformat()
|
||||||
).serialize()
|
).serialize()
|
||||||
return ActivitypubMixin.to_activity(self, pure=pure)
|
activity = ActivitypubMixin.to_activity(self)
|
||||||
|
activity['replies'] = self.to_replies()
|
||||||
|
|
||||||
|
# privacy controls
|
||||||
|
public = 'https://www.w3.org/ns/activitystreams#Public'
|
||||||
|
mentions = [u.remote_id for u in self.mention_users.all()]
|
||||||
|
# this is a link to the followers list:
|
||||||
|
followers = self.user.__class__._meta.get_field('followers')\
|
||||||
|
.to_activity(self.user.followers)
|
||||||
|
if self.privacy == 'public':
|
||||||
|
activity['to'] = [public]
|
||||||
|
activity['cc'] = [followers] + mentions
|
||||||
|
elif self.privacy == 'unlisted':
|
||||||
|
activity['to'] = [followers]
|
||||||
|
activity['cc'] = [public] + mentions
|
||||||
|
elif self.privacy == 'followers':
|
||||||
|
activity['to'] = [followers]
|
||||||
|
activity['cc'] = mentions
|
||||||
|
if self.privacy == 'direct':
|
||||||
|
activity['to'] = mentions
|
||||||
|
activity['cc'] = []
|
||||||
|
|
||||||
|
# "pure" serialization for non-bookwyrm instances
|
||||||
|
if pure:
|
||||||
|
activity['content'] = self.pure_content
|
||||||
|
if 'name' in activity:
|
||||||
|
activity['name'] = self.pure_name
|
||||||
|
activity['type'] = self.pure_type
|
||||||
|
activity['attachment'] = [
|
||||||
|
image_serializer(b.cover) for b in self.mention_books.all() \
|
||||||
|
if b.cover]
|
||||||
|
if hasattr(self, 'book'):
|
||||||
|
activity['attachment'].append(
|
||||||
|
image_serializer(self.book.cover)
|
||||||
|
)
|
||||||
|
return activity
|
||||||
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
''' update user active time '''
|
''' update user active time '''
|
||||||
@ -151,40 +130,40 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
|
|||||||
class GeneratedNote(Status):
|
class GeneratedNote(Status):
|
||||||
''' these are app-generated messages about user activity '''
|
''' these are app-generated messages about user activity '''
|
||||||
@property
|
@property
|
||||||
def ap_pure_content(self):
|
def pure_content(self):
|
||||||
''' indicate the book in question for mastodon (or w/e) users '''
|
''' indicate the book in question for mastodon (or w/e) users '''
|
||||||
message = self.content
|
message = self.content
|
||||||
books = ', '.join(
|
books = ', '.join(
|
||||||
'<a href="%s">"%s"</a>' % (self.book.remote_id, self.book.title) \
|
'<a href="%s">"%s"</a>' % (book.remote_id, book.title) \
|
||||||
for book in self.mention_books.all()
|
for book in self.mention_books.all()
|
||||||
)
|
)
|
||||||
return '%s %s' % (message, books)
|
return '%s %s %s' % (self.user.display_name, message, books)
|
||||||
|
|
||||||
activity_serializer = activitypub.GeneratedNote
|
activity_serializer = activitypub.GeneratedNote
|
||||||
pure_activity_serializer = activitypub.Note
|
pure_type = 'Note'
|
||||||
|
|
||||||
|
|
||||||
class Comment(Status):
|
class Comment(Status):
|
||||||
''' like a review but without a rating and transient '''
|
''' like a review but without a rating and transient '''
|
||||||
book = models.ForeignKey('Edition', on_delete=models.PROTECT)
|
book = fields.ForeignKey('Edition', on_delete=models.PROTECT)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ap_pure_content(self):
|
def pure_content(self):
|
||||||
''' indicate the book in question for mastodon (or w/e) users '''
|
''' indicate the book in question for mastodon (or w/e) users '''
|
||||||
return self.content + '<br><br>(comment on <a href="%s">"%s"</a>)' % \
|
return self.content + '<br><br>(comment on <a href="%s">"%s"</a>)' % \
|
||||||
(self.book.remote_id, self.book.title)
|
(self.book.remote_id, self.book.title)
|
||||||
|
|
||||||
activity_serializer = activitypub.Comment
|
activity_serializer = activitypub.Comment
|
||||||
pure_activity_serializer = activitypub.Note
|
pure_type = 'Note'
|
||||||
|
|
||||||
|
|
||||||
class Quotation(Status):
|
class Quotation(Status):
|
||||||
''' like a review but without a rating and transient '''
|
''' like a review but without a rating and transient '''
|
||||||
quote = models.TextField()
|
quote = fields.TextField()
|
||||||
book = models.ForeignKey('Edition', on_delete=models.PROTECT)
|
book = fields.ForeignKey('Edition', on_delete=models.PROTECT)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ap_pure_content(self):
|
def pure_content(self):
|
||||||
''' indicate the book in question for mastodon (or w/e) users '''
|
''' indicate the book in question for mastodon (or w/e) users '''
|
||||||
return '"%s"<br>-- <a href="%s">"%s"</a><br><br>%s' % (
|
return '"%s"<br>-- <a href="%s">"%s"</a><br><br>%s' % (
|
||||||
self.quote,
|
self.quote,
|
||||||
@ -194,14 +173,14 @@ class Quotation(Status):
|
|||||||
)
|
)
|
||||||
|
|
||||||
activity_serializer = activitypub.Quotation
|
activity_serializer = activitypub.Quotation
|
||||||
pure_activity_serializer = activitypub.Note
|
pure_type = 'Note'
|
||||||
|
|
||||||
|
|
||||||
class Review(Status):
|
class Review(Status):
|
||||||
''' a book review '''
|
''' a book review '''
|
||||||
name = models.CharField(max_length=255, null=True)
|
name = fields.CharField(max_length=255, null=True)
|
||||||
book = models.ForeignKey('Edition', on_delete=models.PROTECT)
|
book = fields.ForeignKey('Edition', on_delete=models.PROTECT)
|
||||||
rating = models.IntegerField(
|
rating = fields.IntegerField(
|
||||||
default=None,
|
default=None,
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
@ -209,7 +188,7 @@ class Review(Status):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ap_pure_name(self):
|
def pure_name(self):
|
||||||
''' clarify review names for mastodon serialization '''
|
''' clarify review names for mastodon serialization '''
|
||||||
if self.rating:
|
if self.rating:
|
||||||
return 'Review of "%s" (%d stars): %s' % (
|
return 'Review of "%s" (%d stars): %s' % (
|
||||||
@ -223,26 +202,21 @@ class Review(Status):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ap_pure_content(self):
|
def pure_content(self):
|
||||||
''' indicate the book in question for mastodon (or w/e) users '''
|
''' indicate the book in question for mastodon (or w/e) users '''
|
||||||
return self.content + '<br><br>(<a href="%s">"%s"</a>)' % \
|
return self.content + '<br><br>(<a href="%s">"%s"</a>)' % \
|
||||||
(self.book.remote_id, self.book.title)
|
(self.book.remote_id, self.book.title)
|
||||||
|
|
||||||
activity_serializer = activitypub.Review
|
activity_serializer = activitypub.Review
|
||||||
pure_activity_serializer = activitypub.Article
|
pure_type = 'Article'
|
||||||
|
|
||||||
|
|
||||||
class Favorite(ActivitypubMixin, BookWyrmModel):
|
class Favorite(ActivitypubMixin, BookWyrmModel):
|
||||||
''' fav'ing a post '''
|
''' fav'ing a post '''
|
||||||
user = models.ForeignKey('User', on_delete=models.PROTECT)
|
user = fields.ForeignKey(
|
||||||
status = models.ForeignKey('Status', on_delete=models.PROTECT)
|
'User', on_delete=models.PROTECT, activitypub_field='actor')
|
||||||
|
status = fields.ForeignKey(
|
||||||
# ---- activitypub serialization settings for this model ----- #
|
'Status', on_delete=models.PROTECT, activitypub_field='object')
|
||||||
activity_mappings = [
|
|
||||||
ActivityMapping('id', 'remote_id'),
|
|
||||||
ActivityMapping('actor', 'user'),
|
|
||||||
ActivityMapping('object', 'status'),
|
|
||||||
]
|
|
||||||
|
|
||||||
activity_serializer = activitypub.Like
|
activity_serializer = activitypub.Like
|
||||||
|
|
||||||
@ -252,7 +226,6 @@ class Favorite(ActivitypubMixin, BookWyrmModel):
|
|||||||
self.user.save()
|
self.user.save()
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
''' can't fav things twice '''
|
''' can't fav things twice '''
|
||||||
unique_together = ('user', 'status')
|
unique_together = ('user', 'status')
|
||||||
@ -260,16 +233,12 @@ class Favorite(ActivitypubMixin, BookWyrmModel):
|
|||||||
|
|
||||||
class Boost(Status):
|
class Boost(Status):
|
||||||
''' boost'ing a post '''
|
''' boost'ing a post '''
|
||||||
boosted_status = models.ForeignKey(
|
boosted_status = fields.ForeignKey(
|
||||||
'Status',
|
'Status',
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
related_name="boosters")
|
related_name='boosters',
|
||||||
|
activitypub_field='object',
|
||||||
activity_mappings = [
|
)
|
||||||
ActivityMapping('id', 'remote_id'),
|
|
||||||
ActivityMapping('actor', 'user'),
|
|
||||||
ActivityMapping('object', 'boosted_status'),
|
|
||||||
]
|
|
||||||
|
|
||||||
activity_serializer = activitypub.Boost
|
activity_serializer = activitypub.Boost
|
||||||
|
|
||||||
|
@ -88,8 +88,14 @@ class User(OrderedCollectionPageMixin, AbstractUser):
|
|||||||
last_active_date = models.DateTimeField(auto_now=True)
|
last_active_date = models.DateTimeField(auto_now=True)
|
||||||
manually_approves_followers = fields.BooleanField(default=False)
|
manually_approves_followers = fields.BooleanField(default=False)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def display_name(self):
|
||||||
|
''' show the cleanest version of the user's name possible '''
|
||||||
|
if self.name != '':
|
||||||
|
return self.name
|
||||||
|
return self.localname or self.username
|
||||||
|
|
||||||
activity_serializer = activitypub.Person
|
activity_serializer = activitypub.Person
|
||||||
serialize_related = []
|
|
||||||
|
|
||||||
def to_outbox(self, **kwargs):
|
def to_outbox(self, **kwargs):
|
||||||
''' an ordered collection of statuses '''
|
''' an ordered collection of statuses '''
|
||||||
@ -112,7 +118,7 @@ class User(OrderedCollectionPageMixin, AbstractUser):
|
|||||||
return self.to_ordered_collection(self.followers, \
|
return self.to_ordered_collection(self.followers, \
|
||||||
remote_id=remote_id, id_only=True, **kwargs)
|
remote_id=remote_id, id_only=True, **kwargs)
|
||||||
|
|
||||||
def to_activity(self, pure=False):
|
def to_activity(self):
|
||||||
''' override default AP serializer to add context object
|
''' override default AP serializer to add context object
|
||||||
idk if this is the best way to go about this '''
|
idk if this is the best way to go about this '''
|
||||||
activity_object = super().to_activity()
|
activity_object = super().to_activity()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user