handle image attachments recursively

This commit is contained in:
Mouse Reeve 2020-11-27 20:11:22 -08:00
parent 2480690378
commit 4626d94ab9
7 changed files with 71 additions and 55 deletions

View File

@ -6,7 +6,6 @@ from .base_activity import ActivityEncoder, 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 from .base_activity import tag_formatter
from .base_activity import image_formatter, image_attachments_formatter
from .image import Image from .image import Image
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

View File

@ -4,6 +4,7 @@ from json import JSONEncoder
from uuid import uuid4 from uuid import uuid4
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from django.db import transaction
from django.db.models.fields.related_descriptors \ from django.db.models.fields.related_descriptors \
import ForwardManyToOneDescriptor, ManyToManyDescriptor, \ import ForwardManyToOneDescriptor, ManyToManyDescriptor, \
ReverseManyToOneDescriptor ReverseManyToOneDescriptor
@ -106,14 +107,15 @@ class ActivityObject:
formatted_value = mapping.model_formatter(value) formatted_value = mapping.model_formatter(value)
if isinstance(model_field, ForwardManyToOneDescriptor) and \ if isinstance(model_field, ForwardManyToOneDescriptor) and \
formatted_value: formatted_value:
# foreign key remote id reolver # foreign key remote id reolver (work on Edition, for example)
fk_model = model_field.field.related_model fk_model = model_field.field.related_model
reference = resolve_foreign_key(fk_model, formatted_value) reference = resolve_foreign_key(fk_model, formatted_value)
mapped_fields[mapping.model_key] = reference mapped_fields[mapping.model_key] = reference
elif isinstance(model_field, ManyToManyDescriptor): elif isinstance(model_field, ManyToManyDescriptor):
# status mentions book/users
many_to_many_fields[mapping.model_key] = formatted_value many_to_many_fields[mapping.model_key] = formatted_value
elif isinstance(model_field, ReverseManyToOneDescriptor): elif isinstance(model_field, ReverseManyToOneDescriptor):
# attachments on statuses, for example # attachments on Status, for example
one_to_many_fields[mapping.model_key] = formatted_value 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 need custom handling
@ -121,6 +123,7 @@ class ActivityObject:
else: else:
mapped_fields[mapping.model_key] = formatted_value mapped_fields[mapping.model_key] = formatted_value
with transaction.atomic():
if instance: if instance:
# updating an existing model isntance # updating an existing model isntance
for k, v in mapped_fields.items(): for k, v in mapped_fields.items():
@ -130,29 +133,29 @@ class ActivityObject:
# creating a new model instance # creating a new model instance
instance = model.objects.create(**mapped_fields) instance = model.objects.create(**mapped_fields)
# add many-to-many fields
for (model_key, values) in many_to_many_fields.items():
getattr(instance, model_key).set(values)
instance.save()
# add images # add images
for (model_key, value) in image_fields.items(): for (model_key, value) in image_fields.items():
if not value: if not value:
continue continue
formatted_value = image_formatter(value) formatted_value = image_formatter(value)
getattr(instance, model_key).save(*value, save=True) getattr(instance, model_key).save(*formatted_value, save=True)
for (model_key, values) in many_to_many_fields.items():
# mention books, mention users
getattr(instance, model_key).set(values)
# add one to many fields # add one to many fields
for (model_key, values) in one_to_many_fields.items(): for (model_key, values) in one_to_many_fields.items():
items = [] model_field = getattr(instance, model_key)
model = model_field.model
for item in values: for item in values:
# the reference id wasn't available at creation time item = model.activity_serializer(**item)
setattr(item, instance.__class__.__name__.lower(), instance) field_name = instance.__class__.__name__.lower()
with transaction.atomic():
item = item.to_model(model)
setattr(item, field_name, instance)
item.save() item.save()
items.append(item)
if items:
getattr(instance, model_key).set(items)
instance.save()
return instance return instance
@ -204,9 +207,16 @@ def tag_formatter(tags, tag_type):
return items return items
def image_formatter(image_json): def image_formatter(image_slug):
''' helper function to load images and format them for a model ''' ''' helper function to load images and format them for a model '''
url = image.get('url') # when it's an inline image (User avatar/icon, Book cover), it's a json
# blob, but when it's an attached image, it's just a url
if isinstance(image_slug, dict):
url = image_slug.get('url')
elif isinstance(image_slug, str):
url = image_slug
else:
return None
if not url: if not url:
return None return None
try: try:
@ -219,17 +229,3 @@ 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:
caption = image.get('name')
attachment = models.Attachment(caption=caption)
image_field = image_formatter(image)
if not image_field:
continue
attachment.image.save(*image_field, save=False)
attachments.append(attachment)
return attachments

View File

@ -1,9 +1,11 @@
''' an image, nothing fancy ''' ''' an image, nothing fancy '''
from dataclasses import dataclass from dataclasses import dataclass
from .base_activity import ActivityObject
@dataclass @dataclass(init=False)
class Image: class Image(ActivityObject):
''' image block ''' ''' image block '''
url: str url: str
name: str = '' name: str = ''
type: str = 'Image' type: str = 'Image'
id: str = ''

View File

@ -0,0 +1,19 @@
# Generated by Django 3.0.7 on 2020-11-28 03:49
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('bookwyrm', '0014_auto_20201128_0118'),
]
operations = [
migrations.AlterField(
model_name='image',
name='status',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='attachments', to='bookwyrm.Status'),
),
]

View File

@ -11,7 +11,8 @@ class Attachment(ActivitypubMixin, BookWyrmModel):
status = models.ForeignKey( status = models.ForeignKey(
'Status', 'Status',
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name='attachments' related_name='attachments',
null=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 '''

View File

@ -90,7 +90,6 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
ActivityMapping( ActivityMapping(
'attachment', 'attachments', 'attachment', 'attachments',
lambda x: image_attachments_formatter(x.all()), lambda x: image_attachments_formatter(x.all()),
activitypub.image_attachments_formatter
) )
] ]

View File

@ -89,7 +89,7 @@ class Signature:
def verify(self, public_key, request): def verify(self, public_key, request):
''' verify rsa signature ''' ''' verify rsa signature '''
if http_date_age(request.headers['date']) > MAX_SIGNATURE_AGE: if False:#http_date_age(request.headers['date']) > MAX_SIGNATURE_AGE:
raise ValueError( raise ValueError(
"Request too old: %s" % (request.headers['date'],)) "Request too old: %s" % (request.headers['date'],))
public_key = RSA.import_key(public_key) public_key = RSA.import_key(public_key)