[PATCH 1/3] Allow a project to restrict submitter state changes

Daniel Axtens dja at axtens.net
Tue Aug 3 01:27:27 AEST 2021


In discussions about how to make patchwork more user-friendly and
suitable for more projects, we realised that perhaps the current
ability for submitters to change their patch state to any value
isn't the most appropriate setting for all maintainers, especially
in light of increasing automation.

Allow a project to stop a submitter from changing the state of
their patches. This is not the default but can be set by a patchwork
administrator.

Signed-off-by: Daniel Axtens <dja at axtens.net>
---
 ...45_project_submitter_state_change_rules.py | 24 +++++++++++++
 patchwork/models.py                           | 36 +++++++++++++++++++
 patchwork/views/__init__.py                   |  8 +++++
 patchwork/views/patch.py                      | 14 ++++++--
 4 files changed, 80 insertions(+), 2 deletions(-)
 create mode 100644 patchwork/migrations/0045_project_submitter_state_change_rules.py

diff --git a/patchwork/migrations/0045_project_submitter_state_change_rules.py b/patchwork/migrations/0045_project_submitter_state_change_rules.py
new file mode 100644
index 000000000000..9d0b2892bd5c
--- /dev/null
+++ b/patchwork/migrations/0045_project_submitter_state_change_rules.py
@@ -0,0 +1,24 @@
+# Generated by Django 3.1.12 on 2021-08-03 00:32
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('patchwork', '0044_add_project_linkname_validation'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='project',
+            name='submitter_state_change_rules',
+            field=models.SmallIntegerField(
+                choices=[
+                    (0, 'Submitters may not change patch states'),
+                    (1, 'Submitters may set any patch state')],
+                default=1,
+                help_text='What state changes can patch submitters make?'
+                          ' Does not affect maintainers.'),
+        ),
+    ]
diff --git a/patchwork/models.py b/patchwork/models.py
index 00273da9f5bd..706b912c349a 100644
--- a/patchwork/models.py
+++ b/patchwork/models.py
@@ -93,6 +93,19 @@ class Project(models.Model):
     send_notifications = models.BooleanField(default=False)
     use_tags = models.BooleanField(default=True)
 
+    # how much can a patch submitter change?
+    SUBMITTER_NO_STATE_CHANGES = 0
+    SUBMITTER_ALL_STATE_CHANGES = 1
+    SUBMITTER_STATE_CHANGE_CHOICES = (
+        (SUBMITTER_NO_STATE_CHANGES, 'Submitters may not change patch states'),
+        (SUBMITTER_ALL_STATE_CHANGES, 'Submitters may set any patch state'),
+    )
+    submitter_state_change_rules = models.SmallIntegerField(
+        choices=SUBMITTER_STATE_CHANGE_CHOICES,
+        default=SUBMITTER_ALL_STATE_CHANGES,
+        help_text='What state changes can patch submitters make?'
+                  ' Does not affect maintainers.')
+
     def is_editable(self, user):
         if not user.is_authenticated:
             return False
@@ -518,6 +531,29 @@ class Patch(SubmissionMixin):
             return True
         return False
 
+    def can_set_state(self, user):
+        # an unauthenticated user can never change state
+        if not user.is_authenticated:
+            return False
+
+        # a maintainer can always set state
+        if self.project.is_editable(user):
+            self._edited_by = user
+            return True
+
+        # a delegate can always set state
+        if user == self.delegate:
+            self._edited_by = user
+            return True
+
+        # if the state change rules prohibit it, no other user can set change
+        if (self.project.submitter_state_change_rules ==
+                Project.SUBMITTER_NO_STATE_CHANGES):
+            return False
+
+        # otherwise, a submitter can change state
+        return self.is_editable(user)
+
     @staticmethod
     def filter_unique_checks(checks):
         """Filter the provided checks to generate the unique list."""
diff --git a/patchwork/views/__init__.py b/patchwork/views/__init__.py
index 3efe90cd6929..9f5d316d18b5 100644
--- a/patchwork/views/__init__.py
+++ b/patchwork/views/__init__.py
@@ -312,6 +312,14 @@ def process_multiplepatch_form(request, form, action, patches, context):
                           % patch.name)
             continue
 
+        field = form.fields.get('state', None)
+        if field and not field.is_no_change(form.cleaned_data['state']) \
+           and not patch.can_set_state(request.user):
+            errors.append(
+                "You don't have the permissions to set the state of patch '%s'"
+                % patch.name)
+            continue
+
         changed_patches += 1
         form.save(patch)
 
diff --git a/patchwork/views/patch.py b/patchwork/views/patch.py
index 3e6874ae1e5d..72c199135cbb 100644
--- a/patchwork/views/patch.py
+++ b/patchwork/views/patch.py
@@ -101,8 +101,18 @@ def patch_detail(request, project_id, msgid):
         elif action is None:
             form = PatchForm(data=request.POST, instance=patch)
             if form.is_valid():
-                form.save()
-                messages.success(request, 'Patch updated')
+                old_patch = Patch.objects.get(id=patch.id)
+                if old_patch.state != form.cleaned_data['state'] and \
+                   not old_patch.can_set_state(request.user):
+                    messages.error(
+                        request,
+                        "You don't have the permissions to set the state of "
+                        "patch '%s'" % patch.name)
+                    patch = old_patch
+                    form = PatchForm(instance=patch)
+                else:
+                    form.save()
+                    messages.success(request, 'Patch updated')
 
     if request.user.is_authenticated:
         context['bundles'] = request.user.bundles.all()
-- 
2.30.2



More information about the Patchwork mailing list