We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Book context missing in boosts from remote posts
**Describe the bug**
In some cases, a post federates and the associated book is not shown.

**Screenshots**
Screenshot from a review where the associated book is not shown. It's a remote post and the booster is on another instance as well.
![image](https://user-images.githubusercontent.com/66644064/113485135-74cfe700-94ac-11eb-9217-cfa510bb52e2.png)

Screenshot from the original instance with the book
![image](https://user-images.githubusercontent.com/66644064/113485156-992bc380-94ac-11eb-93c0-eb9a3b8ccc09.png)

Screenshots from the federated tl where the remote review is there once with book, once without.
![image](https://user-images.githubusercontent.com/66644064/113485238-d6905100-94ac-11eb-99ff-d64f84c6133e.png)

**Additional context**
Will post more matching posts. In other cases, it works properly - it may be that it works if the OP is followed from my instance so the post is already known but not enough examples yet.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `bookwyrm/activitypub/base_activity.py`
Content:
```
1 """ basics for an activitypub serializer """
2 from dataclasses import dataclass, fields, MISSING
3 from json import JSONEncoder
4 
5 from django.apps import apps
6 from django.db import IntegrityError, transaction
7 
8 from bookwyrm.connectors import ConnectorException, get_data
9 from bookwyrm.tasks import app
10 
11 
12 class ActivitySerializerError(ValueError):
13     """ routine problems serializing activitypub json """
14 
15 
16 class ActivityEncoder(JSONEncoder):
17     """  used to convert an Activity object into json """
18 
19     def default(self, o):
20         return o.__dict__
21 
22 
23 @dataclass
24 class Link:
25     """ for tagging a book in a status """
26 
27     href: str
28     name: str
29     type: str = "Link"
30 
31 
32 @dataclass
33 class Mention(Link):
34     """ a subtype of Link for mentioning an actor """
35 
36     type: str = "Mention"
37 
38 
39 @dataclass
40 class Signature:
41     """ public key block """
42 
43     creator: str
44     created: str
45     signatureValue: str
46     type: str = "RsaSignature2017"
47 
48 
49 def naive_parse(activity_objects, activity_json, serializer=None):
50     """ this navigates circular import issues """
51     if not serializer:
52         if activity_json.get("publicKeyPem"):
53             # ugh
54             activity_json["type"] = "PublicKey"
55         try:
56             activity_type = activity_json["type"]
57             serializer = activity_objects[activity_type]
58         except KeyError as e:
59             raise ActivitySerializerError(e)
60 
61     return serializer(activity_objects=activity_objects, **activity_json)
62 
63 
64 @dataclass(init=False)
65 class ActivityObject:
66     """ actor activitypub json """
67 
68     id: str
69     type: str
70 
71     def __init__(self, activity_objects=None, **kwargs):
72         """this lets you pass in an object with fields that aren't in the
73         dataclass, which it ignores. Any field in the dataclass is required or
74         has a default value"""
75         for field in fields(self):
76             try:
77                 value = kwargs[field.name]
78                 if value in (None, MISSING, {}):
79                     raise KeyError()
80                 try:
81                     is_subclass = issubclass(field.type, ActivityObject)
82                 except TypeError:
83                     is_subclass = False
84                 # serialize a model obj
85                 if hasattr(value, "to_activity"):
86                     value = value.to_activity()
87                 # parse a dict into the appropriate activity
88                 elif is_subclass and isinstance(value, dict):
89                     if activity_objects:
90                         value = naive_parse(activity_objects, value)
91                     else:
92                         value = naive_parse(
93                             activity_objects, value, serializer=field.type
94                         )
95 
96             except KeyError:
97                 if field.default == MISSING and field.default_factory == MISSING:
98                     raise ActivitySerializerError(
99                         "Missing required field: %s" % field.name
100                     )
101                 value = field.default
102             setattr(self, field.name, value)
103 
104     def to_model(self, model=None, instance=None, allow_create=True, save=True):
105         """ convert from an activity to a model instance """
106         model = model or get_model_from_type(self.type)
107 
108         # only reject statuses if we're potentially creating them
109         if (
110             allow_create
111             and hasattr(model, "ignore_activity")
112             and model.ignore_activity(self)
113         ):
114             raise ActivitySerializerError()
115 
116         # check for an existing instance
117         instance = instance or model.find_existing(self.serialize())
118 
119         if not instance and not allow_create:
120             # so that we don't create when we want to delete or update
121             return None
122         instance = instance or model()
123 
124         for field in instance.simple_fields:
125             try:
126                 field.set_field_from_activity(instance, self)
127             except AttributeError as e:
128                 raise ActivitySerializerError(e)
129 
130         # image fields have to be set after other fields because they can save
131         # too early and jank up users
132         for field in instance.image_fields:
133             field.set_field_from_activity(instance, self, save=save)
134 
135         if not save:
136             return instance
137 
138         with transaction.atomic():
139             # we can't set many to many and reverse fields on an unsaved object
140             try:
141                 try:
142                     instance.save(broadcast=False)
143                 except TypeError:
144                     instance.save()
145             except IntegrityError as e:
146                 raise ActivitySerializerError(e)
147 
148             # add many to many fields, which have to be set post-save
149             for field in instance.many_to_many_fields:
150                 # mention books/users, for example
151                 field.set_field_from_activity(instance, self)
152 
153         # reversed relationships in the models
154         for (
155             model_field_name,
156             activity_field_name,
157         ) in instance.deserialize_reverse_fields:
158             # attachments on Status, for example
159             values = getattr(self, activity_field_name)
160             if values is None or values is MISSING:
161                 continue
162 
163             model_field = getattr(model, model_field_name)
164             # creating a Work, model_field is 'editions'
165             # creating a User, model field is 'key_pair'
166             related_model = model_field.field.model
167             related_field_name = model_field.field.name
168 
169             for item in values:
170                 set_related_field.delay(
171                     related_model.__name__,
172                     instance.__class__.__name__,
173                     related_field_name,
174                     instance.remote_id,
175                     item,
176                 )
177         return instance
178 
179     def serialize(self):
180         """ convert to dictionary with context attr """
181         data = self.__dict__.copy()
182         # recursively serialize
183         for (k, v) in data.items():
184             try:
185                 if issubclass(type(v), ActivityObject):
186                     data[k] = v.serialize()
187             except TypeError:
188                 pass
189         data = {k: v for (k, v) in data.items() if v is not None}
190         data["@context"] = "https://www.w3.org/ns/activitystreams"
191         return data
192 
193 
194 @app.task
195 @transaction.atomic
196 def set_related_field(
197     model_name, origin_model_name, related_field_name, related_remote_id, data
198 ):
199     """ load reverse related fields (editions, attachments) without blocking """
200     model = apps.get_model("bookwyrm.%s" % model_name, require_ready=True)
201     origin_model = apps.get_model("bookwyrm.%s" % origin_model_name, require_ready=True)
202 
203     with transaction.atomic():
204         if isinstance(data, str):
205             existing = model.find_existing_by_remote_id(data)
206             if existing:
207                 data = existing.to_activity()
208             else:
209                 data = get_data(data)
210         activity = model.activity_serializer(**data)
211 
212         # this must exist because it's the object that triggered this function
213         instance = origin_model.find_existing_by_remote_id(related_remote_id)
214         if not instance:
215             raise ValueError("Invalid related remote id: %s" % related_remote_id)
216 
217         # set the origin's remote id on the activity so it will be there when
218         # the model instance is created
219         # edition.parentWork = instance, for example
220         model_field = getattr(model, related_field_name)
221         if hasattr(model_field, "activitypub_field"):
222             setattr(
223                 activity, getattr(model_field, "activitypub_field"), instance.remote_id
224             )
225         item = activity.to_model()
226 
227         # if the related field isn't serialized (attachments on Status), then
228         # we have to set it post-creation
229         if not hasattr(model_field, "activitypub_field"):
230             setattr(item, related_field_name, instance)
231             item.save()
232 
233 
234 def get_model_from_type(activity_type):
235     """ given the activity, what type of model """
236     models = apps.get_models()
237     model = [
238         m
239         for m in models
240         if hasattr(m, "activity_serializer")
241         and hasattr(m.activity_serializer, "type")
242         and m.activity_serializer.type == activity_type
243     ]
244     if not model:
245         raise ActivitySerializerError(
246             'No model found for activity type "%s"' % activity_type
247         )
248     return model[0]
249 
250 
251 def resolve_remote_id(
252     remote_id, model=None, refresh=False, save=True, get_activity=False
253 ):
254     """ take a remote_id and return an instance, creating if necessary """
255     if model:  # a bonus check we can do if we already know the model
256         result = model.find_existing_by_remote_id(remote_id)
257         if result and not refresh:
258             return result if not get_activity else result.to_activity_dataclass()
259 
260     # load the data and create the object
261     try:
262         data = get_data(remote_id)
263     except ConnectorException:
264         raise ActivitySerializerError(
265             "Could not connect to host for remote_id in: %s" % (remote_id)
266         )
267     # determine the model implicitly, if not provided
268     if not model:
269         model = get_model_from_type(data.get("type"))
270 
271     # check for existing items with shared unique identifiers
272     result = model.find_existing(data)
273     if result and not refresh:
274         return result if not get_activity else result.to_activity_dataclass()
275 
276     item = model.activity_serializer(**data)
277     if get_activity:
278         return item
279 
280     # if we're refreshing, "result" will be set and we'll update it
281     return item.to_model(model=model, instance=result, save=save)
282
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py
--- a/bookwyrm/activitypub/base_activity.py
+++ b/bookwyrm/activitypub/base_activity.py
@@ -265,7 +265,8 @@
             "Could not connect to host for remote_id in: %s" % (remote_id)
         )
     # determine the model implicitly, if not provided
-    if not model:
+    # or if it's a model with subclasses like Status, check again
+    if not model or hasattr(model.objects, "select_subclasses"):
         model = get_model_from_type(data.get("type"))
 
     # check for existing items with shared unique identifiers

{"golden_diff": "diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py\n--- a/bookwyrm/activitypub/base_activity.py\n+++ b/bookwyrm/activitypub/base_activity.py\n@@ -265,7 +265,8 @@\n             \"Could not connect to host for remote_id in: %s\" % (remote_id)\n         )\n     # determine the model implicitly, if not provided\n-    if not model:\n+    # or if it's a model with subclasses like Status, check again\n+    if not model or hasattr(model.objects, \"select_subclasses\"):\n         model = get_model_from_type(data.get(\"type\"))\n \n     # check for existing items with shared unique identifiers\n", "issue": "Book context missing in boosts from remote posts\n**Describe the bug**\r\nIn some cases, a post federates and the associated book is not shown.\r\n\r\n**Screenshots**\r\nScreenshot from a review where the associated book is not shown. It's a remote post and the booster is on another instance as well.\r\n![image](https://user-images.githubusercontent.com/66644064/113485135-74cfe700-94ac-11eb-9217-cfa510bb52e2.png)\r\n\r\nScreenshot from the original instance with the book\r\n![image](https://user-images.githubusercontent.com/66644064/113485156-992bc380-94ac-11eb-93c0-eb9a3b8ccc09.png)\r\n\r\nScreenshots from the federated tl where the remote review is there once with book, once without.\r\n![image](https://user-images.githubusercontent.com/66644064/113485238-d6905100-94ac-11eb-99ff-d64f84c6133e.png)\r\n\r\n**Additional context**\r\nWill post more matching posts. In other cases, it works properly - it may be that it works if the OP is followed from my instance so the post is already known but not enough examples yet.\n", "before_files": [{"content": "\"\"\" basics for an activitypub serializer \"\"\"\nfrom dataclasses import dataclass, fields, MISSING\nfrom json import JSONEncoder\n\nfrom django.apps import apps\nfrom django.db import IntegrityError, transaction\n\nfrom bookwyrm.connectors import ConnectorException, get_data\nfrom bookwyrm.tasks import app\n\n\nclass ActivitySerializerError(ValueError):\n    \"\"\" routine problems serializing activitypub json \"\"\"\n\n\nclass ActivityEncoder(JSONEncoder):\n    \"\"\"  used to convert an Activity object into json \"\"\"\n\n    def default(self, o):\n        return o.__dict__\n\n\n@dataclass\nclass Link:\n    \"\"\" for tagging a book in a status \"\"\"\n\n    href: str\n    name: str\n    type: str = \"Link\"\n\n\n@dataclass\nclass Mention(Link):\n    \"\"\" a subtype of Link for mentioning an actor \"\"\"\n\n    type: str = \"Mention\"\n\n\n@dataclass\nclass Signature:\n    \"\"\" public key block \"\"\"\n\n    creator: str\n    created: str\n    signatureValue: str\n    type: str = \"RsaSignature2017\"\n\n\ndef naive_parse(activity_objects, activity_json, serializer=None):\n    \"\"\" this navigates circular import issues \"\"\"\n    if not serializer:\n        if activity_json.get(\"publicKeyPem\"):\n            # ugh\n            activity_json[\"type\"] = \"PublicKey\"\n        try:\n            activity_type = activity_json[\"type\"]\n            serializer = activity_objects[activity_type]\n        except KeyError as e:\n            raise ActivitySerializerError(e)\n\n    return serializer(activity_objects=activity_objects, **activity_json)\n\n\n@dataclass(init=False)\nclass ActivityObject:\n    \"\"\" actor activitypub json \"\"\"\n\n    id: str\n    type: str\n\n    def __init__(self, activity_objects=None, **kwargs):\n        \"\"\"this lets you pass in an object with fields that aren't in the\n        dataclass, which it ignores. Any field in the dataclass is required or\n        has a default value\"\"\"\n        for field in fields(self):\n            try:\n                value = kwargs[field.name]\n                if value in (None, MISSING, {}):\n                    raise KeyError()\n                try:\n                    is_subclass = issubclass(field.type, ActivityObject)\n                except TypeError:\n                    is_subclass = False\n                # serialize a model obj\n                if hasattr(value, \"to_activity\"):\n                    value = value.to_activity()\n                # parse a dict into the appropriate activity\n                elif is_subclass and isinstance(value, dict):\n                    if activity_objects:\n                        value = naive_parse(activity_objects, value)\n                    else:\n                        value = naive_parse(\n                            activity_objects, value, serializer=field.type\n                        )\n\n            except KeyError:\n                if field.default == MISSING and field.default_factory == MISSING:\n                    raise ActivitySerializerError(\n                        \"Missing required field: %s\" % field.name\n                    )\n                value = field.default\n            setattr(self, field.name, value)\n\n    def to_model(self, model=None, instance=None, allow_create=True, save=True):\n        \"\"\" convert from an activity to a model instance \"\"\"\n        model = model or get_model_from_type(self.type)\n\n        # only reject statuses if we're potentially creating them\n        if (\n            allow_create\n            and hasattr(model, \"ignore_activity\")\n            and model.ignore_activity(self)\n        ):\n            raise ActivitySerializerError()\n\n        # check for an existing instance\n        instance = instance or model.find_existing(self.serialize())\n\n        if not instance and not allow_create:\n            # so that we don't create when we want to delete or update\n            return None\n        instance = instance or model()\n\n        for field in instance.simple_fields:\n            try:\n                field.set_field_from_activity(instance, self)\n            except AttributeError as e:\n                raise ActivitySerializerError(e)\n\n        # image fields have to be set after other fields because they can save\n        # too early and jank up users\n        for field in instance.image_fields:\n            field.set_field_from_activity(instance, self, save=save)\n\n        if not save:\n            return instance\n\n        with transaction.atomic():\n            # we can't set many to many and reverse fields on an unsaved object\n            try:\n                try:\n                    instance.save(broadcast=False)\n                except TypeError:\n                    instance.save()\n            except IntegrityError as e:\n                raise ActivitySerializerError(e)\n\n            # add many to many fields, which have to be set post-save\n            for field in instance.many_to_many_fields:\n                # mention books/users, for example\n                field.set_field_from_activity(instance, self)\n\n        # reversed relationships in the models\n        for (\n            model_field_name,\n            activity_field_name,\n        ) in instance.deserialize_reverse_fields:\n            # attachments on Status, for example\n            values = getattr(self, activity_field_name)\n            if values is None or values is MISSING:\n                continue\n\n            model_field = getattr(model, model_field_name)\n            # creating a Work, model_field is 'editions'\n            # creating a User, model field is 'key_pair'\n            related_model = model_field.field.model\n            related_field_name = model_field.field.name\n\n            for item in values:\n                set_related_field.delay(\n                    related_model.__name__,\n                    instance.__class__.__name__,\n                    related_field_name,\n                    instance.remote_id,\n                    item,\n                )\n        return instance\n\n    def serialize(self):\n        \"\"\" convert to dictionary with context attr \"\"\"\n        data = self.__dict__.copy()\n        # recursively serialize\n        for (k, v) in data.items():\n            try:\n                if issubclass(type(v), ActivityObject):\n                    data[k] = v.serialize()\n            except TypeError:\n                pass\n        data = {k: v for (k, v) in data.items() if v is not None}\n        data[\"@context\"] = \"https://www.w3.org/ns/activitystreams\"\n        return data\n\n\n@app.task\n@transaction.atomic\ndef set_related_field(\n    model_name, origin_model_name, related_field_name, related_remote_id, data\n):\n    \"\"\" load reverse related fields (editions, attachments) without blocking \"\"\"\n    model = apps.get_model(\"bookwyrm.%s\" % model_name, require_ready=True)\n    origin_model = apps.get_model(\"bookwyrm.%s\" % origin_model_name, require_ready=True)\n\n    with transaction.atomic():\n        if isinstance(data, str):\n            existing = model.find_existing_by_remote_id(data)\n            if existing:\n                data = existing.to_activity()\n            else:\n                data = get_data(data)\n        activity = model.activity_serializer(**data)\n\n        # this must exist because it's the object that triggered this function\n        instance = origin_model.find_existing_by_remote_id(related_remote_id)\n        if not instance:\n            raise ValueError(\"Invalid related remote id: %s\" % related_remote_id)\n\n        # set the origin's remote id on the activity so it will be there when\n        # the model instance is created\n        # edition.parentWork = instance, for example\n        model_field = getattr(model, related_field_name)\n        if hasattr(model_field, \"activitypub_field\"):\n            setattr(\n                activity, getattr(model_field, \"activitypub_field\"), instance.remote_id\n            )\n        item = activity.to_model()\n\n        # if the related field isn't serialized (attachments on Status), then\n        # we have to set it post-creation\n        if not hasattr(model_field, \"activitypub_field\"):\n            setattr(item, related_field_name, instance)\n            item.save()\n\n\ndef get_model_from_type(activity_type):\n    \"\"\" given the activity, what type of model \"\"\"\n    models = apps.get_models()\n    model = [\n        m\n        for m in models\n        if hasattr(m, \"activity_serializer\")\n        and hasattr(m.activity_serializer, \"type\")\n        and m.activity_serializer.type == activity_type\n    ]\n    if not model:\n        raise ActivitySerializerError(\n            'No model found for activity type \"%s\"' % activity_type\n        )\n    return model[0]\n\n\ndef resolve_remote_id(\n    remote_id, model=None, refresh=False, save=True, get_activity=False\n):\n    \"\"\" take a remote_id and return an instance, creating if necessary \"\"\"\n    if model:  # a bonus check we can do if we already know the model\n        result = model.find_existing_by_remote_id(remote_id)\n        if result and not refresh:\n            return result if not get_activity else result.to_activity_dataclass()\n\n    # load the data and create the object\n    try:\n        data = get_data(remote_id)\n    except ConnectorException:\n        raise ActivitySerializerError(\n            \"Could not connect to host for remote_id in: %s\" % (remote_id)\n        )\n    # determine the model implicitly, if not provided\n    if not model:\n        model = get_model_from_type(data.get(\"type\"))\n\n    # check for existing items with shared unique identifiers\n    result = model.find_existing(data)\n    if result and not refresh:\n        return result if not get_activity else result.to_activity_dataclass()\n\n    item = model.activity_serializer(**data)\n    if get_activity:\n        return item\n\n    # if we're refreshing, \"result\" will be set and we'll update it\n    return item.to_model(model=model, instance=result, save=save)\n", "path": "bookwyrm/activitypub/base_activity.py"}], "after_files": [{"content": "\"\"\" basics for an activitypub serializer \"\"\"\nfrom dataclasses import dataclass, fields, MISSING\nfrom json import JSONEncoder\n\nfrom django.apps import apps\nfrom django.db import IntegrityError, transaction\n\nfrom bookwyrm.connectors import ConnectorException, get_data\nfrom bookwyrm.tasks import app\n\n\nclass ActivitySerializerError(ValueError):\n    \"\"\" routine problems serializing activitypub json \"\"\"\n\n\nclass ActivityEncoder(JSONEncoder):\n    \"\"\"  used to convert an Activity object into json \"\"\"\n\n    def default(self, o):\n        return o.__dict__\n\n\n@dataclass\nclass Link:\n    \"\"\" for tagging a book in a status \"\"\"\n\n    href: str\n    name: str\n    type: str = \"Link\"\n\n\n@dataclass\nclass Mention(Link):\n    \"\"\" a subtype of Link for mentioning an actor \"\"\"\n\n    type: str = \"Mention\"\n\n\n@dataclass\nclass Signature:\n    \"\"\" public key block \"\"\"\n\n    creator: str\n    created: str\n    signatureValue: str\n    type: str = \"RsaSignature2017\"\n\n\ndef naive_parse(activity_objects, activity_json, serializer=None):\n    \"\"\" this navigates circular import issues \"\"\"\n    if not serializer:\n        if activity_json.get(\"publicKeyPem\"):\n            # ugh\n            activity_json[\"type\"] = \"PublicKey\"\n        try:\n            activity_type = activity_json[\"type\"]\n            serializer = activity_objects[activity_type]\n        except KeyError as e:\n            raise ActivitySerializerError(e)\n\n    return serializer(activity_objects=activity_objects, **activity_json)\n\n\n@dataclass(init=False)\nclass ActivityObject:\n    \"\"\" actor activitypub json \"\"\"\n\n    id: str\n    type: str\n\n    def __init__(self, activity_objects=None, **kwargs):\n        \"\"\"this lets you pass in an object with fields that aren't in the\n        dataclass, which it ignores. Any field in the dataclass is required or\n        has a default value\"\"\"\n        for field in fields(self):\n            try:\n                value = kwargs[field.name]\n                if value in (None, MISSING, {}):\n                    raise KeyError()\n                try:\n                    is_subclass = issubclass(field.type, ActivityObject)\n                except TypeError:\n                    is_subclass = False\n                # serialize a model obj\n                if hasattr(value, \"to_activity\"):\n                    value = value.to_activity()\n                # parse a dict into the appropriate activity\n                elif is_subclass and isinstance(value, dict):\n                    if activity_objects:\n                        value = naive_parse(activity_objects, value)\n                    else:\n                        value = naive_parse(\n                            activity_objects, value, serializer=field.type\n                        )\n\n            except KeyError:\n                if field.default == MISSING and field.default_factory == MISSING:\n                    raise ActivitySerializerError(\n                        \"Missing required field: %s\" % field.name\n                    )\n                value = field.default\n            setattr(self, field.name, value)\n\n    def to_model(self, model=None, instance=None, allow_create=True, save=True):\n        \"\"\" convert from an activity to a model instance \"\"\"\n        model = model or get_model_from_type(self.type)\n\n        # only reject statuses if we're potentially creating them\n        if (\n            allow_create\n            and hasattr(model, \"ignore_activity\")\n            and model.ignore_activity(self)\n        ):\n            raise ActivitySerializerError()\n\n        # check for an existing instance\n        instance = instance or model.find_existing(self.serialize())\n\n        if not instance and not allow_create:\n            # so that we don't create when we want to delete or update\n            return None\n        instance = instance or model()\n\n        for field in instance.simple_fields:\n            try:\n                field.set_field_from_activity(instance, self)\n            except AttributeError as e:\n                raise ActivitySerializerError(e)\n\n        # image fields have to be set after other fields because they can save\n        # too early and jank up users\n        for field in instance.image_fields:\n            field.set_field_from_activity(instance, self, save=save)\n\n        if not save:\n            return instance\n\n        with transaction.atomic():\n            # we can't set many to many and reverse fields on an unsaved object\n            try:\n                try:\n                    instance.save(broadcast=False)\n                except TypeError:\n                    instance.save()\n            except IntegrityError as e:\n                raise ActivitySerializerError(e)\n\n            # add many to many fields, which have to be set post-save\n            for field in instance.many_to_many_fields:\n                # mention books/users, for example\n                field.set_field_from_activity(instance, self)\n\n        # reversed relationships in the models\n        for (\n            model_field_name,\n            activity_field_name,\n        ) in instance.deserialize_reverse_fields:\n            # attachments on Status, for example\n            values = getattr(self, activity_field_name)\n            if values is None or values is MISSING:\n                continue\n\n            model_field = getattr(model, model_field_name)\n            # creating a Work, model_field is 'editions'\n            # creating a User, model field is 'key_pair'\n            related_model = model_field.field.model\n            related_field_name = model_field.field.name\n\n            for item in values:\n                set_related_field.delay(\n                    related_model.__name__,\n                    instance.__class__.__name__,\n                    related_field_name,\n                    instance.remote_id,\n                    item,\n                )\n        return instance\n\n    def serialize(self):\n        \"\"\" convert to dictionary with context attr \"\"\"\n        data = self.__dict__.copy()\n        # recursively serialize\n        for (k, v) in data.items():\n            try:\n                if issubclass(type(v), ActivityObject):\n                    data[k] = v.serialize()\n            except TypeError:\n                pass\n        data = {k: v for (k, v) in data.items() if v is not None}\n        data[\"@context\"] = \"https://www.w3.org/ns/activitystreams\"\n        return data\n\n\n@app.task\n@transaction.atomic\ndef set_related_field(\n    model_name, origin_model_name, related_field_name, related_remote_id, data\n):\n    \"\"\" load reverse related fields (editions, attachments) without blocking \"\"\"\n    model = apps.get_model(\"bookwyrm.%s\" % model_name, require_ready=True)\n    origin_model = apps.get_model(\"bookwyrm.%s\" % origin_model_name, require_ready=True)\n\n    with transaction.atomic():\n        if isinstance(data, str):\n            existing = model.find_existing_by_remote_id(data)\n            if existing:\n                data = existing.to_activity()\n            else:\n                data = get_data(data)\n        activity = model.activity_serializer(**data)\n\n        # this must exist because it's the object that triggered this function\n        instance = origin_model.find_existing_by_remote_id(related_remote_id)\n        if not instance:\n            raise ValueError(\"Invalid related remote id: %s\" % related_remote_id)\n\n        # set the origin's remote id on the activity so it will be there when\n        # the model instance is created\n        # edition.parentWork = instance, for example\n        model_field = getattr(model, related_field_name)\n        if hasattr(model_field, \"activitypub_field\"):\n            setattr(\n                activity, getattr(model_field, \"activitypub_field\"), instance.remote_id\n            )\n        item = activity.to_model()\n\n        # if the related field isn't serialized (attachments on Status), then\n        # we have to set it post-creation\n        if not hasattr(model_field, \"activitypub_field\"):\n            setattr(item, related_field_name, instance)\n            item.save()\n\n\ndef get_model_from_type(activity_type):\n    \"\"\" given the activity, what type of model \"\"\"\n    models = apps.get_models()\n    model = [\n        m\n        for m in models\n        if hasattr(m, \"activity_serializer\")\n        and hasattr(m.activity_serializer, \"type\")\n        and m.activity_serializer.type == activity_type\n    ]\n    if not model:\n        raise ActivitySerializerError(\n            'No model found for activity type \"%s\"' % activity_type\n        )\n    return model[0]\n\n\ndef resolve_remote_id(\n    remote_id, model=None, refresh=False, save=True, get_activity=False\n):\n    \"\"\" take a remote_id and return an instance, creating if necessary \"\"\"\n    if model:  # a bonus check we can do if we already know the model\n        result = model.find_existing_by_remote_id(remote_id)\n        if result and not refresh:\n            return result if not get_activity else result.to_activity_dataclass()\n\n    # load the data and create the object\n    try:\n        data = get_data(remote_id)\n    except ConnectorException:\n        raise ActivitySerializerError(\n            \"Could not connect to host for remote_id in: %s\" % (remote_id)\n        )\n    # determine the model implicitly, if not provided\n    # or if it's a model with subclasses like Status, check again\n    if not model or hasattr(model.objects, \"select_subclasses\"):\n        model = get_model_from_type(data.get(\"type\"))\n\n    # check for existing items with shared unique identifiers\n    result = model.find_existing(data)\n    if result and not refresh:\n        return result if not get_activity else result.to_activity_dataclass()\n\n    item = model.activity_serializer(**data)\n    if get_activity:\n        return item\n\n    # if we're refreshing, \"result\" will be set and we'll update it\n    return item.to_model(model=model, instance=result, save=save)\n", "path": "bookwyrm/activitypub/base_activity.py"}]}