Fixes Add activity

still janky
This commit is contained in:
Mouse Reeve 2021-02-16 11:04:13 -08:00
parent b393df8cab
commit 3f1b62eb98
4 changed files with 54 additions and 33 deletions

View File

@ -80,17 +80,10 @@ class ActivityObject:
setattr(self, field.name, value) setattr(self, field.name, value)
def to_model(self, instance=None, allow_create=True, save=True): def to_model(self, model=None, instance=None, allow_create=True, save=True):
''' convert from an activity to a model instance ''' ''' convert from an activity to a model instance '''
# figure out the right model -- wish I had a better way for this # figure out the right model -- wish I had a better way for this
models = apps.get_models() model = model or get_model_from_type(self.type)
model = [m for m in models if hasattr(m, 'activity_serializer') and \
hasattr(m.activity_serializer, 'type') and \
m.activity_serializer.type == self.type]
if not len(model):
raise ActivitySerializerError(
'No model found for activity type "%s"' % self.type)
model = model[0]
if hasattr(model, 'ignore_activity') and model.ignore_activity(self): if hasattr(model, 'ignore_activity') and model.ignore_activity(self):
return instance return instance
@ -156,6 +149,14 @@ class ActivityObject:
def serialize(self): def serialize(self):
''' convert to dictionary with context attr ''' ''' convert to dictionary with context attr '''
data = self.__dict__ data = self.__dict__
# recursively serialize
for (k, v) in data.items():
try:
is_subclass = issubclass(v, ActivityObject)
except TypeError:
is_subclass = False
if is_subclass:
data[k] = v.serialize()
data = {k:v for (k, v) in data.items() if v is not None} data = {k:v for (k, v) in data.items() if v is not None}
data['@context'] = 'https://www.w3.org/ns/activitystreams' data['@context'] = 'https://www.w3.org/ns/activitystreams'
return data return data
@ -207,8 +208,20 @@ def set_related_field(
item.save() item.save()
def resolve_remote_id(model, remote_id, refresh=False, save=True): def get_model_from_type(activity_type):
''' given the activity, what type of model '''
models = apps.get_models()
model = [m for m in models if hasattr(m, 'activity_serializer') and \
hasattr(m.activity_serializer, 'type') and \
m.activity_serializer.type == activity_type]
if not len(model):
raise ActivitySerializerError(
'No model found for activity type "%s"' % activity_type)
return model[0]
def resolve_remote_id(remote_id, model=None, refresh=False, save=True):
''' take a remote_id and return an instance, creating if necessary ''' ''' take a remote_id and return an instance, creating if necessary '''
if model:# a bonus check we can do if we already know the model
result = model.find_existing_by_remote_id(remote_id) result = model.find_existing_by_remote_id(remote_id)
if result and not refresh: if result and not refresh:
return result return result
@ -220,13 +233,15 @@ def resolve_remote_id(model, remote_id, refresh=False, save=True):
raise ActivitySerializerError( raise ActivitySerializerError(
'Could not connect to host for remote_id in %s model: %s' % \ 'Could not connect to host for remote_id in %s model: %s' % \
(model.__name__, remote_id)) (model.__name__, remote_id))
# determine the model implicitly, if not provided
if not model:
model = get_model_from_type(data.get('type'))
# check for existing items with shared unique identifiers # check for existing items with shared unique identifiers
if not result:
result = model.find_existing(data) result = model.find_existing(data)
if result and not refresh: if result and not refresh:
return result return result
item = model.activity_serializer(**data) item = model.activity_serializer(**data)
# if we're refreshing, "result" will be set and we'll update it # if we're refreshing, "result" will be set and we'll update it
return item.to_model(instance=result, save=save) return item.to_model(model=model, instance=result, save=save)

View File

@ -2,7 +2,7 @@
from dataclasses import dataclass from dataclasses import dataclass
from typing import List from typing import List
from .base_activity import ActivityObject, Signature from .base_activity import ActivityObject, Signature, resolve_remote_id
from .book import Edition from .book import Edition
@ -113,19 +113,22 @@ class Reject(Verb):
class Add(Verb): class Add(Verb):
'''Add activity ''' '''Add activity '''
target: str target: str
object: ActivityObject object: Edition
type: str = 'Add' type: str = 'Add'
def action(self): def action(self):
''' add obj to collection ''' ''' add obj to collection '''
self.to_model() target = resolve_remote_id(self.target, refresh=False)
# we want to related field that isn't the book, this is janky af sorry
model = [t for t in type(target)._meta.related_objects \
if t.name != 'edition'][0].related_model
self.to_model(model=model)
@dataclass(init=False) @dataclass(init=False)
class AddBook(Add): class AddBook(Add):
'''Add activity that's aware of the book obj ''' '''Add activity that's aware of the book obj '''
object: Edition object: Edition
type: str = 'Add'
@dataclass(init=False) @dataclass(init=False)

View File

@ -122,13 +122,12 @@ class ActivitypubRelatedFieldMixin(ActivitypubFieldMixin):
return None return None
related_model = self.related_model related_model = self.related_model
if isinstance(value, dict) and value.get('id'): if hasattr(value, 'id') and value.id:
if not self.load_remote: if not self.load_remote:
# only look in the local database # only look in the local database
return related_model.find_existing(value) return related_model.find_existing(value.serialize())
# this is an activitypub object, which we can deserialize # this is an activitypub object, which we can deserialize
activity_serializer = related_model.activity_serializer return value.to_model(model=related_model)
return activity_serializer(**value).to_model()
try: try:
# make sure the value looks like a remote id # make sure the value looks like a remote id
validate_remote_id(value) validate_remote_id(value)
@ -139,7 +138,7 @@ class ActivitypubRelatedFieldMixin(ActivitypubFieldMixin):
if not self.load_remote: if not self.load_remote:
# only look in the local database # only look in the local database
return related_model.find_existing_by_remote_id(value) return related_model.find_existing_by_remote_id(value)
return activitypub.resolve_remote_id(related_model, value) return activitypub.resolve_remote_id(value, model=related_model)
class RemoteIdField(ActivitypubFieldMixin, models.CharField): class RemoteIdField(ActivitypubFieldMixin, models.CharField):
@ -280,7 +279,7 @@ class ManyToManyField(ActivitypubFieldMixin, models.ManyToManyField):
except ValidationError: except ValidationError:
continue continue
items.append( items.append(
activitypub.resolve_remote_id(self.related_model, remote_id) activitypub.resolve_remote_id(remote_id, model=self.related_model)
) )
return items return items
@ -317,7 +316,7 @@ class TagField(ManyToManyField):
# tags can contain multiple types # tags can contain multiple types
continue continue
items.append( items.append(
activitypub.resolve_remote_id(self.related_model, link.href) activitypub.resolve_remote_id(link.href, model=self.related_model)
) )
return items return items

View File

@ -566,10 +566,12 @@ class Inbox(TestCase):
views.inbox.activity_task(activity) views.inbox.activity_task(activity)
def test_handle_add_book(self): def test_handle_add_book_to_shelf(self):
''' shelving a book ''' ''' shelving a book '''
work = models.Work.objects.create(title='work title')
book = models.Edition.objects.create( book = models.Edition.objects.create(
title='Test', remote_id='https://bookwyrm.social/book/37292') title='Test', remote_id='https://bookwyrm.social/book/37292',
parent_work=work)
shelf = models.Shelf.objects.create( shelf = models.Shelf.objects.create(
user=self.remote_user, name='Test Shelf') user=self.remote_user, name='Test Shelf')
shelf.remote_id = 'https://bookwyrm.social/user/mouse/shelf/to-read' shelf.remote_id = 'https://bookwyrm.social/user/mouse/shelf/to-read'
@ -581,13 +583,15 @@ class Inbox(TestCase):
"actor": "https://example.com/users/rat", "actor": "https://example.com/users/rat",
"object": { "object": {
"type": "Edition", "type": "Edition",
"title": "Test Title",
"work": work.remote_id,
"id": "https://bookwyrm.social/book/37292", "id": "https://bookwyrm.social/book/37292",
}, },
"target": "https://bookwyrm.social/user/mouse/shelf/to-read", "target": "https://bookwyrm.social/user/mouse/shelf/to-read",
"@context": "https://www.w3.org/ns/activitystreams" "@context": "https://www.w3.org/ns/activitystreams"
} }
#views.inbox.activity_task(activity) views.inbox.activity_task(activity)
#self.assertEqual(shelf.books.first(), book) self.assertEqual(shelf.books.first(), book)
def test_handle_update_user(self): def test_handle_update_user(self):