[Patchew-devel] [PATCH 16/17] models: switch from property tables to field

Paolo Bonzini pbonzini at redhat.com
Thu May 2 11:18:03 UTC 2019


Fortunately, most accesses to the property tables were hidden behind
accessors.  Therefore, apart from the usual ugly migration (which in
this case is more or less a copy of the config migration) it is
almost enough to change the accessors to look into the JSON dictionary.

We can also use the new API to access a whole subset of the properties,
which simplifies the access to testing.check_in.*.
---
 .../0052_populate_property_fields.py          |  67 +++++++++++
 api/models.py                                 | 111 ++++++++++--------
 mods/testing.py                               |   6 +-
 3 files changed, 130 insertions(+), 54 deletions(-)
 create mode 100644 api/migrations/0052_populate_property_fields.py

diff --git a/api/migrations/0052_populate_property_fields.py b/api/migrations/0052_populate_property_fields.py
new file mode 100644
index 0000000..432e809
--- /dev/null
+++ b/api/migrations/0052_populate_property_fields.py
@@ -0,0 +1,67 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.db import migrations
+from django.db.models import Q
+
+def do_properties_to_field(obj, propset):
+    properties = {}
+    props = propset.all()
+    for p in props:
+        *path, last = p.name.split('.')
+        parent = properties
+        for item in path:
+            parent = parent.setdefault(item, {})
+        parent[last] = p.value
+    obj.properties = properties
+    #print(obj, properties)
+    obj.save()
+    props.delete()
+
+def properties_to_field(apps, schema_editor):
+    Project = apps.get_model('api', 'Project')
+    for po in Project.objects.all():
+        do_properties_to_field(po, po.projectproperty_set)
+    Message = apps.get_model('api', 'Message')
+    for po in Message.objects.all():
+        do_properties_to_field(po, po.messageproperty_set)
+
+def flatten_properties(source, prefix, result=None):
+    if result is None:
+        result = {}
+    for k, v in source.items():
+        if isinstance(v, dict):
+            flatten_properties(v, prefix + k + '.', result)
+        else:
+            result[prefix + k] = v
+    return result
+
+def do_field_to_properties(source, propclass, **kwargs):
+    props = flatten_properties(source, '')
+    for k, v in props.items():
+        #print(k, v)
+        new_prop = propclass(name=k, value=v, **kwargs)
+        new_prop.save()
+
+def field_to_properties(apps, schema_editor):
+    Project = apps.get_model('api', 'Project')
+    ProjectProperty = apps.get_model('api', 'ProjectProperty')
+    for po in Project.objects.all():
+        do_field_to_properties(po.properties, ProjectProperty, project=po)
+    Message = apps.get_model('api', 'Message')
+    MessageProperty = apps.get_model('api', 'MessageProperty')
+    for m in Message.objects.all():
+        do_field_to_properties(m.properties, MessageProperty, message=m)
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('api', '0051_auto_20190418_1346'),
+    ]
+
+    operations = [
+        migrations.RunPython(properties_to_field,
+                             reverse_code=field_to_properties),
+    ]
diff --git a/api/models.py b/api/models.py
index dd385ad..9573672 100644
--- a/api/models.py
+++ b/api/models.py
@@ -189,30 +189,40 @@ class Project(models.Model):
             emit_event("SetProjectConfig", obj=self)
 
     def get_property(self, prop, default=None):
-        a = ProjectProperty.objects.filter(project=self, name=prop).first()
-        if a:
-            return a.value
-        else:
-            return default
-
-    def get_properties(self):
-        r = {}
-        for m in ProjectProperty.objects.filter(project=self):
-            r[m.name] = m.value
-        return r
-
-    def _do_set_property(self, prop, value):
-        if value is None:
-            ProjectProperty.objects.filter(project=self, name=prop).delete()
+        x = self.properties
+        *path, last = prop.split('.')
+        for item in path:
+            if not item in x:
+                return default
+            x = x[item]
+        return x.get(last, default)
+
+    def delete_property(self, prop):
+        x = self.properties
+        *path, last = prop.split('.')
+        for item in path:
+            if not item in x:
+                return
+            x = x[item]
+        if not last in x:
             return
-        pp, created = ProjectProperty.objects.get_or_create(project=self,
-                                                            name=prop)
-        pp.value = value
-        pp.save()
+        old_val = x[last]
+        del x[last]
+        self.save()
+        emit_event("SetProperty", obj=self, name=prop, value=None,
+                   old_value=old_val)
 
     def set_property(self, prop, value):
-        old_val = self.get_property(prop)
-        self._do_set_property(prop, value)
+        if value is None:
+            self.delete_property(prop)
+            return
+        x = self.properties
+        *path, last = prop.split('.')
+        for item in path:
+            x = x.setdefault(item, {})
+        old_val = x.get(last)
+        x[last] = value
+        self.save()
         emit_event("SetProperty", obj=self, name=prop, value=value,
                    old_value=old_val)
 
@@ -623,37 +633,40 @@ class Message(models.Model):
             self.save()
 
     def get_property(self, prop, default=None):
-        return self.get_properties().get(prop, default)
-
-    def get_properties(self):
-        if hasattr(self, '_properties'):
-            if self._properties is not None:
-                return self._properties
-            else:
-                # The prefetch cache is invalidated, query again
-                all_props = MessageProperty.objects.filter(message=self)
-        else:
-            all_props = self.messageproperty_set.all()
-        r = {}
-        for m in all_props:
-            r[m.name] = m.value
-        self._properties = r
-        return r
-
-    def _do_set_property(self, prop, value):
-        if value is None:
-            MessageProperty.objects.filter(message=self, name=prop).delete()
+        x = self.properties
+        *path, last = prop.split('.')
+        for item in path:
+            if not item in x:
+                return default
+            x = x[item]
+        return x.get(last, default)
+
+    def delete_property(self, prop):
+        x = self.properties
+        *path, last = prop.split('.')
+        for item in path:
+            if not item in x:
+                return
+            x = x[item]
+        if not last in x:
             return
-        mp, created = MessageProperty.objects.get_or_create(message=self,
-                                                            name=prop)
-        mp.value = value
-        mp.save()
-        # Invalidate cache
-        self._properties = None
+        old_val = x[last]
+        del x[last]
+        self.save()
+        emit_event("SetProperty", obj=self, name=prop, value=None,
+                   old_value=old_val)
 
     def set_property(self, prop, value):
-        old_val = self.get_property(prop)
-        self._do_set_property(prop, value)
+        if value is None:
+            self.delete_property(prop)
+            return
+        x = self.properties
+        *path, last = prop.split('.')
+        for item in path:
+            x = x.setdefault(item, {})
+        old_val = x.get(last)
+        x[last] = value
+        self.save()
         emit_event("SetProperty", obj=self, name=prop, value=value,
                    old_value=old_val)
 
diff --git a/mods/testing.py b/mods/testing.py
index 969ce84..e5562ba 100644
--- a/mods/testing.py
+++ b/mods/testing.py
@@ -369,14 +369,10 @@ class TestingModule(PatchewModule):
 
     def check_active_testers(self, project):
         at = []
-        for k, v in project.get_properties().items():
-            prefix = "testing.check_in."
-            if not k.startswith(prefix):
-                continue
+        for tn, v in project.get_property('testing.check_in', {}).items():
             age = time.time() - v
             if age > 10 * 60:
                 continue
-            tn = k[len(prefix):]
             at.append("%s (%dmin)" % (tn, math.ceil(age / 60)))
         if not at:
             return
-- 
2.21.0





More information about the Patchew-devel mailing list