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
_______________________________________________
Patchew-devel mailing list
Patchew-devel@redhat.com
https://www.redhat.com/mailman/listinfo/patchew-devel