forked from mirrors/bookwyrm
Start moving serializing from to_model to fields
This commit is contained in:
parent
8500a7cfe1
commit
7a90aa8f6c
3 changed files with 69 additions and 54 deletions
|
@ -92,44 +92,24 @@ class ActivityObject:
|
|||
if value is None:
|
||||
continue
|
||||
|
||||
# value is None if there's a default that isn't supplied
|
||||
# in the activity but is supplied in the formatter
|
||||
value = getattr(self, activitypub_field)
|
||||
model_field = getattr(model, field.name)
|
||||
|
||||
formatted_value = field.field_from_activity(value)
|
||||
if isinstance(model_field, ForwardManyToOneDescriptor):
|
||||
if not formatted_value:
|
||||
continue
|
||||
# foreign key remote id reolver (work on Edition, for example)
|
||||
fk_model = model_field.field.related_model
|
||||
if isinstance(formatted_value, dict) and \
|
||||
formatted_value.get('id'):
|
||||
# if the AP field is a serialized object (as in Add)
|
||||
# or KeyPair (even though it's OneToOne)
|
||||
related_model = field.related_model
|
||||
related_activity = related_model.activity_serializer
|
||||
mapped_fields[field.name] = related_activity(
|
||||
**formatted_value
|
||||
).to_model(related_model)
|
||||
else:
|
||||
# if the field is just a remote_id (as in every other case)
|
||||
remote_id = formatted_value
|
||||
reference = resolve_remote_id(fk_model, remote_id)
|
||||
mapped_fields[field.name] = reference
|
||||
elif isinstance(model_field, ManyToManyDescriptor):
|
||||
mapped_fields[field.name] = value
|
||||
if isinstance(model_field, ManyToManyDescriptor):
|
||||
# status mentions book/users
|
||||
many_to_many_fields[field.name] = formatted_value
|
||||
many_to_many_fields[field.name] = value
|
||||
elif isinstance(model_field, ReverseManyToOneDescriptor):
|
||||
# attachments on Status, for example
|
||||
one_to_many_fields[field.name] = formatted_value
|
||||
one_to_many_fields[field.name] = value
|
||||
elif isinstance(model_field, ImageFileDescriptor):
|
||||
# image fields need custom handling
|
||||
image_fields[field.name] = formatted_value
|
||||
print(model_field, field.name, value)
|
||||
image_fields[field.name] = value
|
||||
else:
|
||||
if formatted_value == MISSING:
|
||||
formatted_value = None
|
||||
mapped_fields[field.name] = formatted_value
|
||||
if value == MISSING:
|
||||
value = None
|
||||
mapped_fields[field.name] = value
|
||||
|
||||
|
||||
if instance:
|
||||
|
@ -146,33 +126,14 @@ class ActivityObject:
|
|||
|
||||
# add images
|
||||
for (model_key, value) in image_fields.items():
|
||||
if not formatted_value:
|
||||
if not value:
|
||||
continue
|
||||
getattr(instance, model_key).save(*formatted_value, save=True)
|
||||
getattr(instance, model_key).save(*value, save=True)
|
||||
|
||||
# add many to many fields
|
||||
for (model_key, values) in many_to_many_fields.items():
|
||||
# mention books, mention users, followers
|
||||
if values == MISSING or not isinstance(values, list):
|
||||
# user followers is a link to an orderedcollection, skip it
|
||||
continue
|
||||
model_field = getattr(instance, model_key)
|
||||
model = model_field.model
|
||||
items = []
|
||||
for link in values:
|
||||
if isinstance(link, dict):
|
||||
# check that the Type matches the model (Status
|
||||
# tags contain both user mentions and book tags)
|
||||
if not model.activity_serializer.type == \
|
||||
link.get('type'):
|
||||
continue
|
||||
remote_id = link.get('href')
|
||||
else:
|
||||
remote_id = link
|
||||
items.append(
|
||||
resolve_remote_id(model, remote_id)
|
||||
)
|
||||
getattr(instance, model_key).set(items)
|
||||
getattr(instance, model_key).set(values)
|
||||
|
||||
# add one to many fields
|
||||
for (model_key, values) in one_to_many_fields.items():
|
||||
|
|
|
@ -57,6 +57,27 @@ class ActivitypubFieldMixin:
|
|||
return components[0] + ''.join(x.title() for x in components[1:])
|
||||
|
||||
|
||||
class ActivitypubRelatedFieldMixin(ActivitypubFieldMixin):
|
||||
''' default (de)serialization for foreign key and one to one '''
|
||||
def field_from_activity(self, value):
|
||||
if not value:
|
||||
return None
|
||||
|
||||
related_model = self.related_model
|
||||
if isinstance(value, dict) and value.get('id'):
|
||||
# this is an activitypub object, which we can deserialize
|
||||
activity_serializer = related_model.activity_serializer
|
||||
return activity_serializer(**value).to_model(related_model)
|
||||
try:
|
||||
# make sure the value looks like a remote id
|
||||
validate_remote_id(value)
|
||||
except ValidationError:
|
||||
# we don't know what this is, ignore it
|
||||
return None
|
||||
# gets or creates the model field from the remote id
|
||||
return activitypub.resolve_remote_id(related_model, value)
|
||||
|
||||
|
||||
class RemoteIdField(ActivitypubFieldMixin, models.CharField):
|
||||
''' a url that serves as a unique identifier '''
|
||||
def __init__(self, *args, max_length=255, validators=None, **kwargs):
|
||||
|
@ -95,7 +116,7 @@ class UsernameField(ActivitypubFieldMixin, models.CharField):
|
|||
return value.split('@')[0]
|
||||
|
||||
|
||||
class ForeignKey(ActivitypubFieldMixin, models.ForeignKey):
|
||||
class ForeignKey(ActivitypubRelatedFieldMixin, models.ForeignKey):
|
||||
''' activitypub-aware foreign key field '''
|
||||
def field_to_activity(self, value):
|
||||
if not value:
|
||||
|
@ -103,7 +124,7 @@ class ForeignKey(ActivitypubFieldMixin, models.ForeignKey):
|
|||
return value.remote_id
|
||||
|
||||
|
||||
class OneToOneField(ActivitypubFieldMixin, models.OneToOneField):
|
||||
class OneToOneField(ActivitypubRelatedFieldMixin, models.OneToOneField):
|
||||
''' activitypub-aware foreign key field '''
|
||||
def field_to_activity(self, value):
|
||||
if not value:
|
||||
|
@ -122,6 +143,15 @@ class ManyToManyField(ActivitypubFieldMixin, models.ManyToManyField):
|
|||
return '%s/%s' % (value.instance.remote_id, self.name)
|
||||
return [i.remote_id for i in value.all()]
|
||||
|
||||
def field_from_activity(self, value):
|
||||
items = []
|
||||
for remote_id in value:
|
||||
validate_remote_id(remote_id)
|
||||
items.append(
|
||||
activitypub.resolve_remote_id(self.related_model, remote_id)
|
||||
)
|
||||
return items
|
||||
|
||||
|
||||
class TagField(ManyToManyField):
|
||||
''' special case of many to many that uses Tags '''
|
||||
|
@ -142,6 +172,15 @@ class TagField(ManyToManyField):
|
|||
))
|
||||
return tags
|
||||
|
||||
def field_from_activity(self, value):
|
||||
items = []
|
||||
for link_json in value:
|
||||
link = activitypub.Link(**link_json)
|
||||
items.append(
|
||||
activitypub.resolve_remote_id(self.related_model, link.href)
|
||||
)
|
||||
return items
|
||||
|
||||
|
||||
def image_serializer(value):
|
||||
''' helper for serializing images '''
|
||||
|
|
|
@ -14,7 +14,7 @@ from django.test import TestCase
|
|||
from django.utils import timezone
|
||||
|
||||
from bookwyrm.models import fields, User
|
||||
from bookwyrm.settings import DOMAIN
|
||||
from bookwyrm import activitypub
|
||||
|
||||
class ActivitypubFields(TestCase):
|
||||
def test_validate_remote_id(self):
|
||||
|
@ -86,8 +86,23 @@ class ActivitypubFields(TestCase):
|
|||
instance = fields.ForeignKey('User', on_delete=models.CASCADE)
|
||||
Serializable = namedtuple('Serializable', ('to_activity', 'remote_id'))
|
||||
item = Serializable(lambda: {'a': 'b'}, 'https://e.b/c')
|
||||
# returns the remote_id field of the related object
|
||||
self.assertEqual(instance.field_to_activity(item), 'https://e.b/c')
|
||||
|
||||
def test_foreign_key_from_activity(self):
|
||||
instance = fields.ForeignKey(User, on_delete=models.CASCADE)
|
||||
|
||||
# test receiving an unknown remote id and loading data TODO
|
||||
|
||||
# test recieving activity json TODO
|
||||
|
||||
# test receiving a remote id of an object in the db
|
||||
user = User.objects.create_user(
|
||||
'mouse', 'mouse@mouse.mouse', 'mouseword', local=True)
|
||||
value = instance.field_from_activity(user.remote_id)
|
||||
self.assertEqual(value, user)
|
||||
|
||||
|
||||
def test_one_to_one_field(self):
|
||||
instance = fields.OneToOneField('User', on_delete=models.CASCADE)
|
||||
Serializable = namedtuple('Serializable', ('to_activity', 'remote_id'))
|
||||
|
|
Loading…
Reference in a new issue