From c78fcefe48fee0da9c97f41277f3653ccbfa3d94 Mon Sep 17 00:00:00 2001 From: Tanner Prestegard <tanner.prestegard@ligo.org> Date: Tue, 3 Apr 2018 07:49:01 -0500 Subject: [PATCH] adding a ModelToDict mixin which can be used to translate a model instance into a dictionary --- gracedb/core/models.py | 87 +++++++++++++++++++++++++++++++ gracedb/core/tests/__init__.py | 0 gracedb/core/tests/test_models.py | 4 ++ 3 files changed, 91 insertions(+) create mode 100644 gracedb/core/tests/__init__.py create mode 100644 gracedb/core/tests/test_models.py diff --git a/gracedb/core/models.py b/gracedb/core/models.py index 26df59fd4..d9397a97a 100644 --- a/gracedb/core/models.py +++ b/gracedb/core/models.py @@ -1,7 +1,11 @@ from django.contrib.auth import get_user_model from django.db import models, connection +from django.utils.translation import ugettext_lazy as _ +from django.forms.models import model_to_dict +from django.db.models import QuerySet import re +from collections import OrderedDict UserModel = get_user_model() @@ -166,3 +170,86 @@ class m2mThroughBase(models.Model): class Meta: abstract = True + + +class ModelToDictMixin(object): + """ + Defines a to_dict() method which deserializes a model object to + a dictionary. Allows the addition of keys which are not directly attached + to the model and whose values are customizable. + + Default configuration for the dict is provided by the + default_dict_mapping() method. + + The update_dict_mapping() method is used to override parts of the + default mapping. Should not need to be customized, since to_dict() passes + kwargs to it which it should be able to handle. + """ + QS_KEY = 'queryset' + QS_PROP_KEY = 'object_property' + + def to_dict(self, **kwargs): + """ + Usage: + object.to_dict() + object.to_dict(**{'creator': object.creator.pk}) + object.to_dict(**{'comments': {'object_property': 'pk'}}) + """ + dict_mapping = self.default_dict_mapping() + dict_mapping = self._update_dict_mapping(dict_mapping, **kwargs) + if not dict_mapping: + return model_to_dict(self) + + out_dict = OrderedDict() + for k,v in dict_mapping.iteritems(): + if isinstance(v, dict): + if not (v.has_key(self.QS_KEY) and + v.has_key(self.QS_PROP_KEY)): + raise KeyError(_('Must specify {qs_key} and {qs_prop_key} ' + 'when mapping model queryset "fields" to dict')) + + queryset = v[self.QS_KEY] + prop = v[self.QS_PROP_KEY] + if prop.endswith('()'): + prop = prop[:-2] + if callable(getattr(queryset.model, prop)): + value = [getattr(obj, prop)() for obj in queryset] + else: + value = [getattr(obj, prop) for obj in queryset] + else: + value = v + out_dict[k] = value + + return out_dict + + def _update_dict_mapping(self, dict_mapping, **kwargs): + for k,v in kwargs.iteritems(): + if isinstance(v, dict): + if not dict_mapping.has_key(kw): + dict_mapping[kw] = {} + for k2,v2 in v.iteritems(): + dict_mapping[k][k2] = v2 + else: + dict_mapping[k] = v + + return dict_mapping + + def default_dict_mapping(self): + """ + Defines a schema for mapping model fields or related object fields + to a dictionary. Cases where a field returns multiple objects (like + m2m or reverse foreign key relationships) should be provided as a dict + which has the 'queryset' and 'object_property' (REQUIRED). + + Example: + mapping = { + 'creator': self.creator.username, + 'comments': { + self.QS_KEY: self.comment_set.all(), + self.QS_PROP_KEY: 'message', + }, + } + + This method should be overridden by classes which use this mixin. + """ + return {} diff --git a/gracedb/core/tests/__init__.py b/gracedb/core/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/gracedb/core/tests/test_models.py b/gracedb/core/tests/test_models.py new file mode 100644 index 000000000..d1c987dbf --- /dev/null +++ b/gracedb/core/tests/test_models.py @@ -0,0 +1,4 @@ +from django.test import TestCase + +class TestModelToDictMixin(TestCase): + pass -- GitLab