diff --git a/bookwyrm/activitypub/__init__.py b/bookwyrm/activitypub/__init__.py index bd1a0266..852db345 100644 --- a/bookwyrm/activitypub/__init__.py +++ b/bookwyrm/activitypub/__init__.py @@ -5,7 +5,8 @@ import sys from .base_activity import ActivityEncoder, Image, PublicKey, Signature from .base_activity import Link, Mention from .base_activity import ActivitySerializerError -from .base_activity import tag_formatter, image_formatter +from .base_activity import tag_formatter +from .base_activity import image_formatter, image_attachments_formatter from .note import Note, GeneratedNote, Article, Comment, Review, Quotation from .note import Tombstone from .interaction import Boost, Like diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index 65752105..f6a3f9b1 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -5,7 +5,8 @@ from uuid import uuid4 from django.core.files.base import ContentFile from django.db.models.fields.related_descriptors \ - import ForwardManyToOneDescriptor, ManyToManyDescriptor + import ForwardManyToOneDescriptor, ManyToManyDescriptor, \ + ReverseManyToOneDescriptor from django.db.models.fields.files import ImageFileDescriptor import requests @@ -95,6 +96,7 @@ class ActivityObject: model_fields = [m.name for m in model._meta.get_fields()] mapped_fields = {} many_to_many_fields = {} + one_to_many_fields = {} image_fields = {} for mapping in model.activity_mappings: @@ -107,15 +109,20 @@ class ActivityObject: value = getattr(self, mapping.activity_key) model_field = getattr(model, mapping.model_key) - # remote_id -> foreign key resolver - if isinstance(model_field, ForwardManyToOneDescriptor) and value: - fk_model = model_field.field.related_model - value = resolve_foreign_key(fk_model, value) - formatted_value = mapping.model_formatter(value) - if isinstance(model_field, ManyToManyDescriptor): + if isinstance(model_field, ForwardManyToOneDescriptor) and \ + formatted_value: + # foreign key remote id reolver + fk_model = model_field.field.related_model + reference = resolve_foreign_key(fk_model, formatted_value) + mapped_fields[mapping.model_key] = reference + elif isinstance(model_field, ManyToManyDescriptor): many_to_many_fields[mapping.model_key] = formatted_value + elif isinstance(model_field, ReverseManyToOneDescriptor): + # attachments on statuses, for example + one_to_many_fields[mapping.model_key] = formatted_value elif isinstance(model_field, ImageFileDescriptor): + # image fields need custom handling image_fields[mapping.model_key] = formatted_value else: mapped_fields[mapping.model_key] = formatted_value @@ -137,6 +144,18 @@ class ActivityObject: # add images for (model_key, value) in image_fields.items(): getattr(instance, model_key).save(*value, save=True) + + # add one to many fields + for (model_key, values) in one_to_many_fields.items(): + items = [] + for item in values: + # the reference id wasn't available at creation time + setattr(item, instance.__class__.__name__.lower(), instance) + item.save() + items.append(item) + if items: + getattr(instance, model_key).set(items) + instance.save() return instance @@ -167,15 +186,14 @@ def resolve_foreign_key(model, remote_id): return result -def tag_formatter(tags): +def tag_formatter(tags, tag_type): ''' 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') + for tag in [t for t in tags if t.get('type') == tag_type]: if not tag_type in types: continue remote_id = tag.get('href') @@ -203,3 +221,14 @@ def image_formatter(image_json): image_name = str(uuid4()) + '.' + url.split('.')[-1] image_content = ContentFile(response.content) return [image_name, image_content] + + +def image_attachments_formatter(images_json): + ''' deserialize a list of images ''' + attachments = [] + for image in images_json: + attachment = models.Attachment() + image_field = image_formatter(image) + attachment.image.save(*image_field, save=False) + attachments.append(attachment) + return attachments diff --git a/bookwyrm/migrations/0012_attachment.py b/bookwyrm/migrations/0012_attachment.py index 47b2885b..2af545a8 100644 --- a/bookwyrm/migrations/0012_attachment.py +++ b/bookwyrm/migrations/0012_attachment.py @@ -1,4 +1,4 @@ -# Generated by Django 3.0.7 on 2020-11-24 00:41 +# Generated by Django 3.0.7 on 2020-11-24 17:46 from django.db import migrations, models import django.db.models.deletion @@ -19,7 +19,7 @@ class Migration(migrations.Migration): ('updated_date', models.DateTimeField(auto_now=True)), ('remote_id', models.CharField(max_length=255, null=True)), ('image', models.ImageField(blank=True, null=True, upload_to='status/')), - ('status', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='bookwyrm.Status')), + ('status', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='attachments', to='bookwyrm.Status')), ], options={ 'abstract': False, diff --git a/bookwyrm/models/__init__.py b/bookwyrm/models/__init__.py index d147197d..8a93b805 100644 --- a/bookwyrm/models/__init__.py +++ b/bookwyrm/models/__init__.py @@ -7,7 +7,7 @@ from .connector import Connector from .relationship import UserFollows, UserFollowRequest, UserBlocks from .shelf import Shelf, ShelfBook from .status import Status, GeneratedNote, Review, Comment, Quotation -from .status import Favorite, Boost, Notification, ReadThrough +from .status import Attachment, Favorite, Boost, Notification, ReadThrough from .tag import Tag from .user import User from .federated_server import FederatedServer diff --git a/bookwyrm/models/base_model.py b/bookwyrm/models/base_model.py index 2883e97a..e56d21f6 100644 --- a/bookwyrm/models/base_model.py +++ b/bookwyrm/models/base_model.py @@ -276,10 +276,5 @@ def image_formatter(image, default_path=None): def image_attachments_formatter(images): - ''' create a list of image attachemnts ''' - if not isinstance(images, list): - images = [images] - attachments = [] - for image in images: - attachments.append(image_formatter(image)) - return attachments + ''' create a list of image attachments ''' + return [image_formatter(i) for i in images] diff --git a/bookwyrm/models/book.py b/bookwyrm/models/book.py index c4981a54..6edfece8 100644 --- a/bookwyrm/models/book.py +++ b/bookwyrm/models/book.py @@ -100,7 +100,9 @@ class Book(ActivitypubMixin, BookWyrmModel): ActivityMapping('editions', 'editions_path'), ActivityMapping( 'attachment', 'cover', - image_attachments_formatter + # this expects an iterable and the field is just an image + lambda x: image_attachments_formatter([x]), + lambda x: activitypub.image_attachments_formatter(x)[0] ), ] diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index 4136e085..dcf4c471 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -7,7 +7,7 @@ from model_utils.managers import InheritanceManager from bookwyrm import activitypub from .base_model import ActivitypubMixin, OrderedCollectionPageMixin from .base_model import ActivityMapping, BookWyrmModel, PrivacyLevels -from .base_model import tag_formatter +from .base_model import tag_formatter, image_attachments_formatter class Status(OrderedCollectionPageMixin, BookWyrmModel): @@ -80,13 +80,18 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel): ActivityMapping( 'tag', 'mention_books', lambda x: tag_formatter(x, 'title', 'Book'), - activitypub.tag_formatter + lambda x: activitypub.tag_formatter(x, 'Book') ), ActivityMapping( 'tag', 'mention_users', lambda x: tag_formatter(x, 'username', 'Mention'), - activitypub.tag_formatter + lambda x: activitypub.tag_formatter(x, 'Mention') ), + ActivityMapping( + 'attachment', 'attachments', + lambda x: image_attachments_formatter(x.all()), + activitypub.image_attachments_formatter + ) ] # serializing to bookwyrm expanded activitypub @@ -140,9 +145,10 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel): def save(self, *args, **kwargs): ''' update user active time ''' - self.user.last_active_date = timezone.now() - self.user.save() - super().save(*args, **kwargs) + if self.user.local: + self.user.last_active_date = timezone.now() + self.user.save() + return super().save(*args, **kwargs) class Attachment(BookWyrmModel): @@ -150,7 +156,7 @@ class Attachment(BookWyrmModel): status = models.ForeignKey( 'Status', on_delete=models.CASCADE, - related_name='items' + related_name='attachments' ) image = models.ImageField(upload_to='status/', null=True, blank=True)