parent
b393df8cab
commit
3f1b62eb98
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
Loading…
Reference in New Issue