Receive and save incoming images

This commit is contained in:
Mouse Reeve 2020-11-24 11:25:07 -08:00
parent 88e4705717
commit ad7ce6595b
7 changed files with 62 additions and 29 deletions

View file

@ -5,7 +5,8 @@ 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
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 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,8 @@ from uuid import uuid4
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from django.db.models.fields.related_descriptors \ from django.db.models.fields.related_descriptors \
import ForwardManyToOneDescriptor, ManyToManyDescriptor import ForwardManyToOneDescriptor, ManyToManyDescriptor, \
ReverseManyToOneDescriptor
from django.db.models.fields.files import ImageFileDescriptor from django.db.models.fields.files import ImageFileDescriptor
import requests import requests
@ -95,6 +96,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 = {} many_to_many_fields = {}
one_to_many_fields = {}
image_fields = {} image_fields = {}
for mapping in model.activity_mappings: for mapping in model.activity_mappings:
@ -107,15 +109,20 @@ class ActivityObject:
value = getattr(self, mapping.activity_key) value = getattr(self, mapping.activity_key)
model_field = getattr(model, mapping.model_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) 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 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): elif isinstance(model_field, ImageFileDescriptor):
# image fields need custom handling
image_fields[mapping.model_key] = formatted_value image_fields[mapping.model_key] = formatted_value
else: else:
mapped_fields[mapping.model_key] = formatted_value mapped_fields[mapping.model_key] = formatted_value
@ -137,6 +144,18 @@ class ActivityObject:
# add images # add images
for (model_key, value) in image_fields.items(): for (model_key, value) in image_fields.items():
getattr(instance, model_key).save(*value, save=True) 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 return instance
@ -167,15 +186,14 @@ def resolve_foreign_key(model, remote_id):
return result return result
def tag_formatter(tags): def tag_formatter(tags, tag_type):
''' helper function to extract foreign keys from tag activity json ''' ''' helper function to extract foreign keys from tag activity json '''
items = [] items = []
types = { types = {
'Book': models.Book, 'Book': models.Book,
'Mention': models.User, 'Mention': models.User,
} }
for tag in tags: for tag in [t for t in tags if t.get('type') == tag_type]:
tag_type = tag.get('type')
if not tag_type in types: if not tag_type in types:
continue continue
remote_id = tag.get('href') remote_id = tag.get('href')
@ -203,3 +221,14 @@ def image_formatter(image_json):
image_name = str(uuid4()) + '.' + url.split('.')[-1] image_name = str(uuid4()) + '.' + url.split('.')[-1]
image_content = ContentFile(response.content) image_content = ContentFile(response.content)
return [image_name, image_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

View file

@ -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 from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
@ -19,7 +19,7 @@ class Migration(migrations.Migration):
('updated_date', models.DateTimeField(auto_now=True)), ('updated_date', models.DateTimeField(auto_now=True)),
('remote_id', models.CharField(max_length=255, null=True)), ('remote_id', models.CharField(max_length=255, null=True)),
('image', models.ImageField(blank=True, null=True, upload_to='status/')), ('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={ options={
'abstract': False, 'abstract': False,

View file

@ -7,7 +7,7 @@ from .connector import Connector
from .relationship import UserFollows, UserFollowRequest, UserBlocks from .relationship import UserFollows, UserFollowRequest, UserBlocks
from .shelf import Shelf, ShelfBook from .shelf import Shelf, ShelfBook
from .status import Status, GeneratedNote, Review, Comment, Quotation 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 .tag import Tag
from .user import User from .user import User
from .federated_server import FederatedServer from .federated_server import FederatedServer

View file

@ -276,10 +276,5 @@ def image_formatter(image, default_path=None):
def image_attachments_formatter(images): def image_attachments_formatter(images):
''' create a list of image attachemnts ''' ''' create a list of image attachments '''
if not isinstance(images, list): return [image_formatter(i) for i in images]
images = [images]
attachments = []
for image in images:
attachments.append(image_formatter(image))
return attachments

View file

@ -100,7 +100,9 @@ class Book(ActivitypubMixin, BookWyrmModel):
ActivityMapping('editions', 'editions_path'), ActivityMapping('editions', 'editions_path'),
ActivityMapping( ActivityMapping(
'attachment', 'cover', '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]
), ),
] ]

View file

@ -7,7 +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 from .base_model import tag_formatter, image_attachments_formatter
class Status(OrderedCollectionPageMixin, BookWyrmModel): class Status(OrderedCollectionPageMixin, BookWyrmModel):
@ -80,13 +80,18 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
ActivityMapping( ActivityMapping(
'tag', 'mention_books', 'tag', 'mention_books',
lambda x: tag_formatter(x, 'title', 'Book'), lambda x: tag_formatter(x, 'title', 'Book'),
activitypub.tag_formatter lambda x: activitypub.tag_formatter(x, 'Book')
), ),
ActivityMapping( ActivityMapping(
'tag', 'mention_users', 'tag', 'mention_users',
lambda x: tag_formatter(x, 'username', 'Mention'), 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 # serializing to bookwyrm expanded activitypub
@ -140,9 +145,10 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
''' update user active time ''' ''' update user active time '''
if self.user.local:
self.user.last_active_date = timezone.now() self.user.last_active_date = timezone.now()
self.user.save() self.user.save()
super().save(*args, **kwargs) return super().save(*args, **kwargs)
class Attachment(BookWyrmModel): class Attachment(BookWyrmModel):
@ -150,7 +156,7 @@ class Attachment(BookWyrmModel):
status = models.ForeignKey( status = models.ForeignKey(
'Status', 'Status',
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name='items' related_name='attachments'
) )
image = models.ImageField(upload_to='status/', null=True, blank=True) image = models.ImageField(upload_to='status/', null=True, blank=True)