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 ActivitySerializerError
from .base_activity import tag_formatter
from .base_activity import image_formatter, image_attachments_formatter
from .image import Image
from .note import Note, GeneratedNote, Article, Comment, Review, Quotation
from .note import Tombstone

View file

@ -4,6 +4,7 @@ from json import JSONEncoder
from uuid import uuid4
from django.core.files.base import ContentFile
from django.db import transaction
from django.db.models.fields.related_descriptors \
import ForwardManyToOneDescriptor, ManyToManyDescriptor, \
ReverseManyToOneDescriptor
@ -106,14 +107,15 @@ class ActivityObject:
formatted_value = mapping.model_formatter(value)
if isinstance(model_field, ForwardManyToOneDescriptor) and \
formatted_value:
# foreign key remote id reolver
# foreign key remote id reolver (work on Edition, for example)
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):
# status mentions book/users
many_to_many_fields[mapping.model_key] = formatted_value
elif isinstance(model_field, ReverseManyToOneDescriptor):
# attachments on statuses, for example
# attachments on Status, for example
one_to_many_fields[mapping.model_key] = formatted_value
elif isinstance(model_field, ImageFileDescriptor):
# image fields need custom handling
@ -121,38 +123,39 @@ class ActivityObject:
else:
mapped_fields[mapping.model_key] = formatted_value
if instance:
# updating an existing model isntance
for k, v in mapped_fields.items():
setattr(instance, k, v)
instance.save()
else:
# creating a new model instance
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
for (model_key, value) in image_fields.items():
if not value:
continue
formatted_value = image_formatter(value)
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)
with transaction.atomic():
if instance:
# updating an existing model isntance
for k, v in mapped_fields.items():
setattr(instance, k, v)
instance.save()
else:
# creating a new model instance
instance = model.objects.create(**mapped_fields)
# add images
for (model_key, value) in image_fields.items():
if not value:
continue
formatted_value = image_formatter(value)
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
for (model_key, values) in one_to_many_fields.items():
model_field = getattr(instance, model_key)
model = model_field.model
for item in values:
item = model.activity_serializer(**item)
field_name = instance.__class__.__name__.lower()
with transaction.atomic():
item = item.to_model(model)
setattr(item, field_name, instance)
item.save()
return instance
@ -204,9 +207,16 @@ def tag_formatter(tags, tag_type):
return items
def image_formatter(image_json):
def image_formatter(image_slug):
''' 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:
return None
try:
@ -219,17 +229,3 @@ 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:
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 '''
from dataclasses import dataclass
from .base_activity import ActivityObject
@dataclass
class Image:
@dataclass(init=False)
class Image(ActivityObject):
''' image block '''
url: str
name: str = ''
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',
on_delete=models.CASCADE,
related_name='attachments'
related_name='attachments',
null=True
)
class Meta:
''' one day we'll have other types of attachments besides images '''

View file

@ -90,7 +90,6 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
ActivityMapping(
'attachment', 'attachments',
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):
''' 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(
"Request too old: %s" % (request.headers['date'],))
public_key = RSA.import_key(public_key)