[Patchew-devel] [PATCH 2/3] change "maintainer" from property to many-to-many relation

Paolo Bonzini pbonzini at redhat.com
Thu Mar 8 09:39:05 UTC 2018


This is the only property that is used by the Patchew core rather than
the plugins.  Having it as a relation makes it a bit easier to associates
users with projects in the admin.

(I tested the migration both ways with a local database).

Signed-off-by: Paolo Bonzini <pbonzini at redhat.com>
---
 api/admin.py                                       |  1 +
 api/migrations/0024_project_maintainers.py         | 22 +++++++++
 .../0025_populate_project_maintainers.py           | 52 ++++++++++++++++++++++
 api/models.py                                      |  4 +-
 tests/patchewtest.py                               |  2 +
 tests/test_project.py                              | 20 ++++++++-
 6 files changed, 99 insertions(+), 2 deletions(-)
 create mode 100644 api/migrations/0024_project_maintainers.py
 create mode 100644 api/migrations/0025_populate_project_maintainers.py

diff --git a/api/admin.py b/api/admin.py
index a595751..c7ca190 100644
--- a/api/admin.py
+++ b/api/admin.py
@@ -18,6 +18,7 @@ class ProjectPropertyInline(admin.TabularInline):
     extra = 0
 
 class ProjectAdmin(admin.ModelAdmin):
+    filter_horizontal = ('maintainers',)
     inlines = [
             ProjectPropertyInline
         ]
diff --git a/api/migrations/0024_project_maintainers.py b/api/migrations/0024_project_maintainers.py
new file mode 100644
index 0000000..986d7f6
--- /dev/null
+++ b/api/migrations/0024_project_maintainers.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.10 on 2018-03-08 08:15
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ('api', '0023_auto_20180308_0810'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='project',
+            name='maintainers',
+            field=models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL),
+        ),
+    ]
diff --git a/api/migrations/0025_populate_project_maintainers.py b/api/migrations/0025_populate_project_maintainers.py
new file mode 100644
index 0000000..e029e70
--- /dev/null
+++ b/api/migrations/0025_populate_project_maintainers.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+from django.db.models import Count
+import json
+
+
+def maintainers_from_property(apps, schema_editor):
+    # We can't import the models directly as they may be a newer
+    # version than this migration expects. We use the historical version.
+    Project = apps.get_model('api', 'Project')
+    ProjectProperty = apps.get_model('api', 'ProjectProperty')
+    User = apps.get_model('auth', 'User')
+    projects = Project.objects.filter(projectproperty__name='maintainers')
+    for p in projects:
+        p.maintainers.clear()
+        pp = p.projectproperty_set.filter(name='maintainers')[0]
+        # NOTE: this will fail if the property is a blob
+        maintainers = json.loads(pp.value)
+        users = User.objects.filter(username__in=maintainers)
+        p.maintainers.add(*users)
+    ProjectProperty.objects.filter(name='maintainers').delete()
+
+def maintainers_to_property(apps, schema_editor):
+    # We can't import the models directly as they may be a newer
+    # version than this migration expects. We use the historical version.
+    Project = apps.get_model('api', 'Project')
+    ProjectProperty = apps.get_model('api', 'ProjectProperty')
+    User = apps.get_model('auth', 'User')
+    projects = Project.objects. \
+        annotate(maintainer_count=Count('maintainers')). \
+        filter(maintainer_count__gt=0)
+    for p in projects:
+        maintainers = [u.username for u in p.maintainers.all()]
+        pp = ProjectProperty(project=p,
+                             name='maintainers',
+                             value=json.dumps(maintainers),
+                             blob=False)
+        pp.save()
+        p.maintainers.clear()
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('api', '0024_project_maintainers'),
+    ]
+
+    operations = [
+        migrations.RunPython(maintainers_from_property,
+                             reverse_code=maintainers_to_property),
+    ]
diff --git a/api/models.py b/api/models.py
index 0c8688a..180d6e3 100644
--- a/api/models.py
+++ b/api/models.py
@@ -75,6 +75,7 @@ class Project(models.Model):
                                        project belongs to. The parent must be a
                                        top project which has
                                        parent_project=NULL""")
+    maintainers = models.ManyToManyField(User,blank=True)
 
     def __str__(self):
         return self.name
@@ -125,7 +126,8 @@ class Project(models.Model):
     def maintained_by(self, user):
         if user.is_superuser:
             return True
-        if user.username in self.get_property("maintainers", []):
+        if self.maintainers.filter(id=user.id).exists() or \
+                self.get_property("maintainers", []):
             return True
         return False
 
diff --git a/tests/patchewtest.py b/tests/patchewtest.py
index 00ecddb..1b0a867 100644
--- a/tests/patchewtest.py
+++ b/tests/patchewtest.py
@@ -55,6 +55,7 @@ class PatchewTestCase(django.test.LiveServerTestCase):
         user = User.objects.create_superuser(username or self.user,
                                              self.email,
                                              password or self.password)
+        return user
 
     def create_user(self, username=None, password=None, groups=[]):
         user = User.objects.create_user(username or self.user,
@@ -63,6 +64,7 @@ class PatchewTestCase(django.test.LiveServerTestCase):
         if groups:
             user.groups = [Group.objects.get_or_create(name=g)[0] for g in groups]
             user.save()
+        return user
 
     def cli(self, argv):
         """Run patchew-cli command and return (retcode, stdout, stderr)"""
diff --git a/tests/test_project.py b/tests/test_project.py
index 63ca709..25b9661 100755
--- a/tests/test_project.py
+++ b/tests/test_project.py
@@ -15,7 +15,7 @@ from patchewtest import PatchewTestCase, main
 class ProjectTest(PatchewTestCase):
 
     def setUp(self):
-        self.create_superuser()
+        self.admin_user = self.create_superuser()
 
     def test_empty(self):
         projects = self.get_projects()
@@ -61,5 +61,23 @@ class ProjectTest(PatchewTestCase):
         self.assertNotEqual(r, 0)
         self.assertNotEqual(b, "")
 
+    def test_maintainers(self):
+        p = self.add_project("TestProject")
+        u1 = self.create_user(username='buddy', password='abc')
+        u2 = self.create_user(username='mirage', password='def')
+        u2.is_staff = True
+        u2.save()
+        p.maintainers.add(u1)
+        self.assertTrue(p.maintained_by(self.admin_user))
+        self.assertTrue(p.maintained_by(u1))
+        self.assertFalse(p.maintained_by(u2))
+        p.maintainers.add(u2)
+        self.assertTrue(p.maintained_by(u1))
+        self.assertTrue(p.maintained_by(u2))
+        p.maintainers.clear()
+        self.assertTrue(p.maintained_by(self.admin_user))
+        self.assertFalse(p.maintained_by(u1))
+        self.assertFalse(p.maintained_by(u2))
+
 if __name__ == '__main__':
     main()
-- 
2.14.3





More information about the Patchew-devel mailing list