Repository: bookwyrm-social/bookwyrm

Issue:
Some books not upating after editing
**Describe the bug**
Yesterday and the day before it I edited some books and the changes were saved, but after I logged in the next day the books were as if I wouldn't have edited them.

Some examples:

https://www.bookwyrm.social/book/106348 You can see it has the publisher in the title.
https://www.bookwyrm.social/book/106344 It is capitalized in every word even though that formatting is incorrect for Spanish.
https://www.bookwyrm.social/book/106316 It has an English name of the edition in the title even though the edition is not bilingual.

**To Reproduce**
Steps to reproduce the behavior:
1. Edit a book.
2. Save it.
3. Wait some time?
4. See error

**Expected behavior**
Edits to be saved.

**Screenshots**
N/A

**Desktop (please complete the following information):**
 - OS: elementaryOS
 - Browser: Firefox
 - Version: 86.0

**Smartphone (please complete the following information):**
N/A

**Additional context**
N/A



Discussion:
hm, it may be that for some reason these have gotten re-imported and the changes were over-written. I'll take a look.
Thanks!

Resolving patch:
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
@@ -106,8 +106,10 @@ def __init__(self, activity_objects=None, **kwargs):
                 value = field.default
             setattr(self, field.name, value)
 
-    # pylint: disable=too-many-locals,too-many-branches
-    def to_model(self, model=None, instance=None, allow_create=True, save=True):
+    # pylint: disable=too-many-locals,too-many-branches,too-many-arguments
+    def to_model(
+        self, model=None, instance=None, allow_create=True, save=True, overwrite=True
+    ):
         """convert from an activity to a model instance"""
         model = model or get_model_from_type(self.type)
 
@@ -129,9 +131,12 @@ def to_model(self, model=None, instance=None, allow_create=True, save=True):
 
         # keep track of what we've changed
         update_fields = []
+        # sets field on the model using the activity value
         for field in instance.simple_fields:
             try:
-                changed = field.set_field_from_activity(instance, self)
+                changed = field.set_field_from_activity(
+                    instance, self, overwrite=overwrite
+                )
                 if changed:
                     update_fields.append(field.name)
             except AttributeError as e:
@@ -140,7 +145,9 @@ def to_model(self, model=None, instance=None, allow_create=True, save=True):
         # image fields have to be set after other fields because they can save
         # too early and jank up users
         for field in instance.image_fields:
-            changed = field.set_field_from_activity(instance, self, save=save)
+            changed = field.set_field_from_activity(
+                instance, self, save=save, overwrite=overwrite
+            )
             if changed:
                 update_fields.append(field.name)
 
diff --git a/bookwyrm/connectors/abstract_connector.py b/bookwyrm/connectors/abstract_connector.py
--- a/bookwyrm/connectors/abstract_connector.py
+++ b/bookwyrm/connectors/abstract_connector.py
@@ -139,7 +139,7 @@ def get_or_create_book(self, remote_id):
                 **dict_from_mappings(work_data, self.book_mappings)
             )
             # this will dedupe automatically
-            work = work_activity.to_model(model=models.Work)
+            work = work_activity.to_model(model=models.Work, overwrite=False)
             for author in self.get_authors_from_data(work_data):
                 work.authors.add(author)
 
@@ -156,7 +156,7 @@ def create_edition_from_data(self, work, edition_data):
         mapped_data = dict_from_mappings(edition_data, self.book_mappings)
         mapped_data["work"] = work.remote_id
         edition_activity = activitypub.Edition(**mapped_data)
-        edition = edition_activity.to_model(model=models.Edition)
+        edition = edition_activity.to_model(model=models.Edition, overwrite=False)
         edition.connector = self.connector
         edition.save()
 
@@ -182,7 +182,7 @@ def get_or_create_author(self, remote_id):
             return None
 
         # this will dedupe
-        return activity.to_model(model=models.Author)
+        return activity.to_model(model=models.Author, overwrite=False)
 
     @abstractmethod
     def is_work_data(self, data):
diff --git a/bookwyrm/models/fields.py b/bookwyrm/models/fields.py
--- a/bookwyrm/models/fields.py
+++ b/bookwyrm/models/fields.py
@@ -66,7 +66,7 @@ def __init__(
             self.activitypub_field = activitypub_field
         super().__init__(*args, **kwargs)
 
-    def set_field_from_activity(self, instance, data):
+    def set_field_from_activity(self, instance, data, overwrite=True):
         """helper function for assinging a value to the field. Returns if changed"""
         try:
             value = getattr(data, self.get_activitypub_field())
@@ -79,8 +79,15 @@ def set_field_from_activity(self, instance, data):
         if formatted is None or formatted is MISSING or formatted == {}:
             return False
 
+        current_value = (
+            getattr(instance, self.name) if hasattr(instance, self.name) else None
+        )
+        # if we're not in overwrite mode, only continue updating the field if its unset
+        if current_value and not overwrite:
+            return False
+
         # the field is unchanged
-        if hasattr(instance, self.name) and getattr(instance, self.name) == formatted:
+        if current_value == formatted:
             return False
 
         setattr(instance, self.name, formatted)
@@ -210,7 +217,10 @@ def __init__(self, *args, **kwargs):
         )
 
     # pylint: disable=invalid-name
-    def set_field_from_activity(self, instance, data):
+    def set_field_from_activity(self, instance, data, overwrite=True):
+        if not overwrite:
+            return False
+
         original = getattr(instance, self.name)
         to = data.to
         cc = data.cc
@@ -273,8 +283,11 @@ def __init__(self, *args, link_only=False, **kwargs):
         self.link_only = link_only
         super().__init__(*args, **kwargs)
 
-    def set_field_from_activity(self, instance, data):
+    def set_field_from_activity(self, instance, data, overwrite=True):
         """helper function for assinging a value to the field"""
+        if not overwrite and getattr(instance, self.name).exists():
+            return False
+
         value = getattr(data, self.get_activitypub_field())
         formatted = self.field_from_activity(value)
         if formatted is None or formatted is MISSING:
@@ -377,13 +390,16 @@ def __init__(self, *args, alt_field=None, **kwargs):
         super().__init__(*args, **kwargs)
 
     # pylint: disable=arguments-differ
-    def set_field_from_activity(self, instance, data, save=True):
+    def set_field_from_activity(self, instance, data, save=True, overwrite=True):
         """helper function for assinging a value to the field"""
         value = getattr(data, self.get_activitypub_field())
         formatted = self.field_from_activity(value)
         if formatted is None or formatted is MISSING:
             return False
 
+        if not overwrite and hasattr(instance, self.name):
+            return False
+
         getattr(instance, self.name).save(*formatted, save=save)
         return True
 


Test patch: