Repository: DDMAL/CantusDB

Issue:
Contributor changes to a chant after proofreading overwrite proofreading fields
To replicate: 

- Log in as a proofreader of a source and edit a chant in that source by marking any of the "proofread" checkboxes, or adding someone to the `proofread_by` field, or by specifying a chant range.
- Log in as a contributor (ie. someone without proofread access to that source), and then make any edit to the chant.
- Log back in as the proofreader and see that all of your changes to those fields (proofread checkboxes, the `proofread_by` field, and the chant range field) have been overwritten. 

In emails with Debra, it sounds like the following would be preferable:
- contributor changes should never overwrite the `proofread_by` or `chant_range` fields
- the checkboxes are only "unchecked" by a contributor change if the contributor has actually changed something in one of the related fields ("volpiano proofread" gets unchecked only if the "volpiano" has changed, and so on)


Discussion:
right--I think for some time we've been circling around the balance of having the proofreading status be accurate, without constantly having to re-proofread things. This seems like a good solution. 
Indeed ... and this current bug makes it not accurate and makes people constantly re-proof thing, so we're currently living in the worst of all possible worlds.

Resolving patch:
diff --git a/django/cantusdb_project/main_app/views/chant.py b/django/cantusdb_project/main_app/views/chant.py
--- a/django/cantusdb_project/main_app/views/chant.py
+++ b/django/cantusdb_project/main_app/views/chant.py
@@ -22,10 +22,11 @@
 )
 from django.core.exceptions import PermissionDenied
 from django.contrib.auth.mixins import LoginRequiredMixin
-from django.http import Http404
+from django.http import Http404, HttpResponse
 
 from django.contrib.auth.mixins import UserPassesTestMixin
 
+
 from main_app.forms import (
     ChantCreateForm,
     ChantEditForm,
@@ -39,6 +40,7 @@
     Sequence,
     Office,
 )
+from users.models import User
 from main_app.permissions import (
     user_can_edit_chants_in_source,
     user_can_proofread_chant,
@@ -1103,12 +1105,41 @@ def get_chants_with_folios(chants_in_feast):
 
     def form_valid(self, form):
         if form.is_valid():
-            form.instance.last_updated_by = self.request.user
-            messages.success(
-                self.request,
-                "Chant updated successfully!",
-            )
-            return super().form_valid(form)
+            user: User = self.request.user
+            chant: Chant = form.instance
+
+            if not user_can_proofread_chant(user, chant):
+                # Preserve the original values for proofreader-specific fields
+                original_chant: Chant = self.get_object()
+                chant.chant_range = original_chant.chant_range
+                chant.volpiano_proofread = original_chant.volpiano_proofread
+                chant.manuscript_full_text_std_proofread = (
+                    original_chant.manuscript_full_text_std_proofread
+                )
+                chant.manuscript_full_text_proofread = (
+                    original_chant.manuscript_full_text_proofread
+                )
+                proofreaders: list[Optional[User]] = list(
+                    original_chant.proofread_by.all()
+                )
+
+                # Handle proofreader checkboxes
+                if "volpiano" in form.changed_data:
+                    chant.volpiano_proofread = False
+                if "manuscript_full_text_std_spelling" in form.changed_data:
+                    chant.manuscript_full_text_std_proofread = False
+                if "manuscript_full_text" in form.changed_data:
+                    chant.manuscript_full_text_proofread = False
+
+            chant.last_updated_by = user
+            return_response: HttpResponse = super().form_valid(form)
+
+            # The many-to-many `proofread_by` field is reset when the
+            # parent class's `form_valid` method calls `save()` on the model instance.
+            if not user_can_proofread_chant(user, chant):
+                chant.proofread_by.set(proofreaders)
+            messages.success(self.request, "Chant updated successfully!")
+            return return_response
         else:
             return super().form_invalid(form)
 


Test patch: