Merge pull request #359 from mouse-reeve/activity-tags

Serialize and deserialize activitypub "Tags" on statuses
This commit is contained in:
Mouse Reeve 2020-11-22 09:35:19 -08:00 committed by GitHub
commit 56638f79cf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 67 additions and 28 deletions

View file

@ -4,7 +4,7 @@ import sys
from .base_activity import ActivityEncoder, Image, PublicKey, Signature from .base_activity import ActivityEncoder, Image, PublicKey, Signature
from .base_activity import Link, Mention from .base_activity import Link, Mention
from .base_activity import ActivitySerializerError from .base_activity import ActivitySerializerError, tag_formatter
from .note import Note, GeneratedNote, Article, Comment, Review, Quotation from .note import Note, GeneratedNote, Article, Comment, Review, Quotation
from .note import Tombstone from .note import Tombstone
from .interaction import Boost, Like from .interaction import Boost, Like

View file

@ -5,7 +5,7 @@ from json import JSONEncoder
from bookwyrm import books_manager, models from bookwyrm import books_manager, models
from django.db.models.fields.related_descriptors \ from django.db.models.fields.related_descriptors \
import ForwardManyToOneDescriptor import ForwardManyToOneDescriptor, ManyToManyDescriptor
class ActivitySerializerError(ValueError): class ActivitySerializerError(ValueError):
@ -90,6 +90,7 @@ class ActivityObject:
model_fields = [m.name for m in model._meta.get_fields()] model_fields = [m.name for m in model._meta.get_fields()]
mapped_fields = {} mapped_fields = {}
many_to_many_fields = {}
for mapping in model.activity_mappings: for mapping in model.activity_mappings:
if mapping.model_key not in model_fields: if mapping.model_key not in model_fields:
@ -106,7 +107,11 @@ class ActivityObject:
fk_model = model_field.field.related_model fk_model = model_field.field.related_model
value = resolve_foreign_key(fk_model, value) value = resolve_foreign_key(fk_model, value)
mapped_fields[mapping.model_key] = mapping.model_formatter(value) formatted_value = mapping.model_formatter(value)
if isinstance(model_field, ManyToManyDescriptor):
many_to_many_fields[mapping.model_key] = formatted_value
else:
mapped_fields[mapping.model_key] = formatted_value
# updating an existing model isntance # updating an existing model isntance
@ -114,10 +119,14 @@ class ActivityObject:
for k, v in mapped_fields.items(): for k, v in mapped_fields.items():
setattr(instance, k, v) setattr(instance, k, v)
instance.save() instance.save()
return instance else:
# creating a new model instance
instance = model.objects.create(**mapped_fields)
# creating a new model instance for (model_key, values) in many_to_many_fields.items():
return model.objects.create(**mapped_fields) getattr(instance, model_key).set(values)
instance.save()
return instance
def serialize(self): def serialize(self):
@ -129,7 +138,7 @@ class ActivityObject:
def resolve_foreign_key(model, remote_id): def resolve_foreign_key(model, remote_id):
''' look up the remote_id on an activity json field ''' ''' look up the remote_id on an activity json field '''
if model in [models.Edition, models.Work]: if model in [models.Edition, models.Work, models.Book]:
return books_manager.get_or_create_book(remote_id) return books_manager.get_or_create_book(remote_id)
result = model.objects result = model.objects
@ -145,3 +154,23 @@ def resolve_foreign_key(model, remote_id):
'Could not resolve remote_id in %s model: %s' % \ 'Could not resolve remote_id in %s model: %s' % \
(model.__name__, remote_id)) (model.__name__, remote_id))
return result return result
def tag_formatter(tags):
''' helper function to extract foreign keys from tag activity json '''
items = []
types = {
'Book': models.Book,
'Mention': models.User,
}
for tag in tags:
tag_type = tag.get('type')
if not tag_type in types:
continue
remote_id = tag.get('href')
try:
item = resolve_foreign_key(types[tag_type], remote_id)
except ActivitySerializerError:
continue
items.append(item)
return items

View file

@ -253,7 +253,6 @@ def handle_delete_status(activity):
status_builder.delete_status(status) status_builder.delete_status(status)
@app.task @app.task
def handle_favorite(activity): def handle_favorite(activity):
''' approval of your good good post ''' ''' approval of your good good post '''

View file

@ -72,7 +72,14 @@ class ActivitypubMixin:
value = value.remote_id value = value.remote_id
if isinstance(value, datetime): if isinstance(value, datetime):
value = value.isoformat() value = value.isoformat()
fields[mapping.activity_key] = mapping.activity_formatter(value) result = mapping.activity_formatter(value)
if mapping.activity_key in fields and \
isinstance(fields[mapping.activity_key], list):
# there are two database fields that map to the same AP list
# this happens in status, which combines user and book tags
fields[mapping.activity_key] += result
else:
fields[mapping.activity_key] = result
if pure: if pure:
return self.pure_activity_serializer( return self.pure_activity_serializer(
@ -242,3 +249,15 @@ 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

View file

@ -7,6 +7,7 @@ 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 ActivityMapping, BookWyrmModel, PrivacyLevels
from .base_model import tag_formatter
class Status(OrderedCollectionPageMixin, BookWyrmModel): class Status(OrderedCollectionPageMixin, BookWyrmModel):
@ -57,24 +58,6 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
''' structured replies block ''' ''' structured replies block '''
return self.to_replies() return self.to_replies()
@property
def ap_tag(self):
''' references to books and/or users '''
tags = []
for book in self.mention_books.all():
tags.append(activitypub.Link(
href=book.remote_id,
name=book.title,
type='Book'
))
for user in self.mention_users.all():
tags.append(activitypub.Mention(
href=user.remote_id,
name=user.username,
))
return tags
@property @property
def ap_status_image(self): def ap_status_image(self):
''' attach a book cover, if relevent ''' ''' attach a book cover, if relevent '''
@ -94,7 +77,16 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
ActivityMapping('to', 'ap_to'), ActivityMapping('to', 'ap_to'),
ActivityMapping('cc', 'ap_cc'), ActivityMapping('cc', 'ap_cc'),
ActivityMapping('replies', 'ap_replies'), ActivityMapping('replies', 'ap_replies'),
ActivityMapping('tag', 'ap_tag'), ActivityMapping(
'tag', 'mention_books',
lambda x: tag_formatter(x, 'title', 'Book'),
activitypub.tag_formatter
),
ActivityMapping(
'tag', 'mention_users',
lambda x: tag_formatter(x, 'username', 'Mention'),
activitypub.tag_formatter
),
] ]
# serializing to bookwyrm expanded activitypub # serializing to bookwyrm expanded activitypub