Use remote_id resolver to load books, user

This commit is contained in:
Mouse Reeve
2020-11-28 10:18:24 -08:00
parent 81bdd2b3f1
commit a93b5cf5bc
15 changed files with 115 additions and 93 deletions

View File

@ -4,7 +4,7 @@ import sys
from .base_activity import ActivityEncoder, PublicKey, Signature
from .base_activity import Link, Mention
from .base_activity import ActivitySerializerError
from .base_activity import ActivitySerializerError, resolve_remote_id
from .image import Image
from .note import Note, GeneratedNote, Article, Comment, Review, Quotation
from .note import Tombstone
@ -14,7 +14,7 @@ from .person import Person
from .book import Edition, Work, Author
from .verbs import Create, Delete, Undo, Update
from .verbs import Follow, Accept, Reject
from .verbs import Add, Remove
from .verbs import Add, AddBook, Remove
# this creates a list of all the Activity types that we can serialize,
# so when an Activity comes in from outside, we can check if it's known

View File

@ -3,15 +3,20 @@ from dataclasses import dataclass, fields, MISSING
from json import JSONEncoder
from uuid import uuid4
import dateutil.parser
from dateutil.parser import ParserError
from django.core.files.base import ContentFile
from django.db import transaction
from django.db.models.fields.related_descriptors \
import ForwardManyToOneDescriptor, ManyToManyDescriptor, \
ReverseManyToOneDescriptor
from django.db.models.fields import DateTimeField
from django.db.models.fields.files import ImageFileDescriptor
from django.db.models.query_utils import DeferredAttribute
from django.utils import timezone
import requests
from bookwyrm import books_manager, models
from bookwyrm import models
class ActivitySerializerError(ValueError):
@ -106,11 +111,27 @@ class ActivityObject:
model_field = getattr(model, mapping.model_key)
formatted_value = mapping.model_formatter(value)
if isinstance(model_field, ForwardManyToOneDescriptor) and \
if isinstance(model_field, DeferredAttribute) and \
isinstance(model_field.field, DateTimeField):
print("DATE")
try:
formatted_value = timezone.make_aware(
dateutil.parser.parse(formatted_value)
)
except ParserError:
formatted_value = None
elif isinstance(model_field, ForwardManyToOneDescriptor) and \
formatted_value:
# 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)
if isinstance(formatted_value, dict) and \
formatted_value.get('id'):
# if the AP field is a serialized object (as in Add)
remote_id = formatted_value['id']
else:
# if the AP 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[mapping.model_key] = reference
elif isinstance(model_field, ManyToManyDescriptor):
# status mentions book/users
@ -122,6 +143,8 @@ class ActivityObject:
# image fields need custom handling
image_fields[mapping.model_key] = formatted_value
else:
if formatted_value == MISSING:
formatted_value = None
mapped_fields[mapping.model_key] = formatted_value
with transaction.atomic():
@ -153,12 +176,15 @@ class ActivityObject:
model = model_field.model
items = []
for link in values:
# check that the Type matches the model (because Status
# tags contain both user mentions and book tags)
if not model.activity_serializer.type == link.get('type'):
continue
items.append(
resolve_foreign_key(model, link.get('href'))
resolve_remote_id(model, link.get('href'))
)
getattr(instance, model_key).set(items)
# add one to many fields
for (model_key, values) in one_to_many_fields.items():
if values == MISSING:
@ -183,11 +209,8 @@ class ActivityObject:
return data
def resolve_foreign_key(model, remote_id):
''' look up the remote_id on an activity json field '''
if model in [models.Edition, models.Work, models.Book]:
return books_manager.get_or_create_book(remote_id)
def resolve_remote_id(model, remote_id, refresh=False):
''' look up the remote_id in the database or load it remotely '''
result = model.objects
if hasattr(model.objects, 'select_subclasses'):
result = result.select_subclasses()
@ -196,10 +219,10 @@ def resolve_foreign_key(model, remote_id):
result = result.filter(
remote_id=remote_id
).first()
if result:
if result and not refresh:
return result
# failing that, load the data and create the object
# load the data and create the object
try:
response = requests.get(
remote_id,
@ -215,7 +238,8 @@ def resolve_foreign_key(model, remote_id):
(model.__name__, remote_id))
item = model.activity_serializer(**response.json())
return item.to_model(model)
# if we're refreshing, "result" will be set and we'll update it
return item.to_model(model, instance=result)
def image_formatter(image_slug):

View File

@ -12,13 +12,13 @@ class Book(ActivityObject):
sortTitle: str = ''
subtitle: str = ''
description: str = ''
languages: List[str]
languages: List[str] = field(default_factory=lambda: [])
series: str = ''
seriesNumber: str = ''
subjects: List[str]
subjectPlaces: List[str]
subjects: List[str] = field(default_factory=lambda: [])
subjectPlaces: List[str] = field(default_factory=lambda: [])
authors: List[str]
authors: List[str] = field(default_factory=lambda: [])
firstPublishedDate: str = ''
publishedDate: str = ''
@ -33,22 +33,22 @@ class Book(ActivityObject):
@dataclass(init=False)
class Edition(Book):
''' Edition instance of a book object '''
isbn10: str
isbn13: str
oclcNumber: str
asin: str
pages: str
physicalFormat: str
publishers: List[str]
work: str
isbn10: str = ''
isbn13: str = ''
oclcNumber: str = ''
asin: str = ''
pages: str = ''
physicalFormat: str = ''
publishers: List[str] = field(default_factory=lambda: [])
type: str = 'Edition'
@dataclass(init=False)
class Work(Book):
''' work instance of a book object '''
lccn: str
lccn: str = ''
editions: List[str]
type: str = 'Work'

View File

@ -12,6 +12,7 @@ class OrderedCollection(ActivityObject):
first: str
last: str = ''
name: str = ''
owner: str = ''
type: str = 'OrderedCollection'

View File

@ -3,6 +3,7 @@ from dataclasses import dataclass
from typing import List
from .base_activity import ActivityObject, Signature
from .book import Book
@dataclass(init=False)
class Verb(ActivityObject):
@ -69,6 +70,13 @@ class Add(Verb):
type: str = 'Add'
@dataclass(init=False)
class AddBook(Verb):
'''Add activity that's aware of the book obj '''
target: Book
type: str = 'Add'
@dataclass(init=False)
class Remove(Verb):
'''Remove activity '''