From f35ceaed22dd4dfa2e4204ae711f474fb5a51530 Mon Sep 17 00:00:00 2001 From: MajekX Date: Fri, 27 Jul 2018 16:00:28 +0200 Subject: [PATCH] Add registration via metaclass (#23) Example: class SomeModel(metaclass=EAVModelMeta): pass --- .gitignore | 3 +++ eav/models.py | 12 +++++++++++- eav/queryset.py | 2 +- tests/attributes.py | 13 +++++++++++++ tests/metaclass_models2.py | 18 ++++++++++++++++++ tests/metaclass_models3.py | 16 ++++++++++++++++ tests/registry.py | 18 +++++++++++++++++- tox.ini | 1 + 8 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 tests/metaclass_models2.py create mode 100644 tests/metaclass_models3.py diff --git a/.gitignore b/.gitignore index f12eaec..e7c4913 100644 --- a/.gitignore +++ b/.gitignore @@ -123,3 +123,6 @@ venv.bak/ *~ # Auto-generated tag files tags + +## Mac +.DS_Store diff --git a/eav/models.py b/eav/models.py index 7b26475..96d9a90 100644 --- a/eav/models.py +++ b/eav/models.py @@ -5,7 +5,8 @@ This module defines the four concrete, non-abstract models: * :class:`EnumValue` * :class:`EnumGroup` -Along with the :class:`Entity` helper class. +Along with the :class:`Entity` helper class and :class:`EAVModelMeta` +optional metaclass for each eav model class. """ from copy import copy @@ -14,12 +15,14 @@ from django.contrib.contenttypes import fields as generic from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError from django.db import models +from django.db.models.base import ModelBase from django.utils import timezone from django.utils.translation import ugettext_lazy as _ from .exceptions import IllegalAssignmentException from .fields import EavDatatypeField, EavSlugField from .validators import * +from . import register class EnumValue(models.Model): @@ -615,3 +618,10 @@ class Entity(object): for i in m.eav: print(i) """ return iter(self.get_values()) + + +class EAVModelMeta(ModelBase): + def __new__(cls, name, bases, namespace, **kwds): + result = super(EAVModelMeta, cls).__new__(cls, name, bases, dict(namespace)) + register(result) + return result diff --git a/eav/queryset.py b/eav/queryset.py index b6cdc47..a01eedb 100644 --- a/eav/queryset.py +++ b/eav/queryset.py @@ -251,7 +251,7 @@ def expand_eav_filter(model_cls, key, value): else: sub_key = '__'.join(fields[1:]) key, value = expand_eav_filter(field.model, sub_key, value) - return '{}__{}'.format(ields[0], key), value + return '{}__{}'.format(fields[0], key), value class EavQuerySet(QuerySet): diff --git a/tests/attributes.py b/tests/attributes.py index a306c04..b1c5e9e 100644 --- a/tests/attributes.py +++ b/tests/attributes.py @@ -1,6 +1,7 @@ from django.core.exceptions import ValidationError from django.test import TestCase +import sys import eav from eav.exceptions import IllegalAssignmentException from eav.models import Attribute, Value @@ -8,6 +9,10 @@ from eav.registry import EavConfig from .models import Encounter, Patient +if sys.version_info[0] > 2: + from .metaclass_models3 import RegisterTestModel +else: + from .metaclass_models2 import RegisterTestModel class Attributes(TestCase): def setUp(self): @@ -47,17 +52,25 @@ class Attributes(TestCase): def test_setting_attributes(self): p = Patient.objects.create(name='Jon') e = Encounter.objects.create(patient=p, num=1) + p.eav.age = 3 p.eav.height = 2.3 p.save() e.eav_field.age = 4 e.save() self.assertEqual(Value.objects.count(), 3) + t = RegisterTestModel.objects.create(name="test") + t.eav.age = 6 + t.eav.height = 10 + t.save() p = Patient.objects.get(name='Jon') self.assertEqual(p.eav.age, 3) self.assertEqual(p.eav.height, 2.3) e = Encounter.objects.get(num=1) self.assertEqual(e.eav_field.age, 4) + t = RegisterTestModel.objects.get(name="test") + self.assertEqual(t.eav.age, 6) + self.assertEqual(t.eav.height, 10) def test_illegal_assignemnt(self): class EncounterEavConfig(EavConfig): diff --git a/tests/metaclass_models2.py b/tests/metaclass_models2.py new file mode 100644 index 0000000..10245a3 --- /dev/null +++ b/tests/metaclass_models2.py @@ -0,0 +1,18 @@ +from django.db import models +from eav.models import EAVModelMeta + + +class ExampleMetaclassModel(models.Model): + __metaclass__ = EAVModelMeta + name = models.CharField(max_length=12) + + def __unicode__(self): + return self.name + + +class RegisterTestModel(models.Model): + __metaclass__ = EAVModelMeta + name = models.CharField(max_length=12) + + def __unicode__(self): + return self.name diff --git a/tests/metaclass_models3.py b/tests/metaclass_models3.py new file mode 100644 index 0000000..3ace3d5 --- /dev/null +++ b/tests/metaclass_models3.py @@ -0,0 +1,16 @@ +from django.db import models +from eav.models import EAVModelMeta + + +class ExampleMetaclassModel(models.Model, metaclass=EAVModelMeta): + name = models.CharField(max_length=12) + + def __str__(self): + return self.name + + +class RegisterTestModel(models.Model, metaclass=EAVModelMeta): + name = models.CharField(max_length=12) + + def __str__(self): + return self.name diff --git a/tests/registry.py b/tests/registry.py index eccc3b5..c042ca8 100644 --- a/tests/registry.py +++ b/tests/registry.py @@ -1,10 +1,16 @@ from django.test import TestCase +import sys import eav from eav.registry import EavConfig from .models import Encounter, ExampleModel, Patient +if sys.version_info[0] > 2: + from .metaclass_models3 import ExampleMetaclassModel +else: + from .metaclass_models2 import ExampleMetaclassModel + class RegistryTests(TestCase): def setUp(self): @@ -55,6 +61,11 @@ class RegistryTests(TestCase): self.assertEqual(ExampleModel._eav_config_cls.manager_attr, 'objects') self.assertEqual(ExampleModel._eav_config_cls.eav_attr, 'eav') + def test_register_via_metaclass_with_defaults(self): + self.assertTrue(hasattr(ExampleMetaclassModel, '_eav_config_cls')) + self.assertEqual(ExampleMetaclassModel._eav_config_cls.manager_attr, 'objects') + self.assertEqual(ExampleMetaclassModel._eav_config_cls.eav_attr, 'eav') + def test_unregistering(self): old_mgr = Patient.objects eav.register(Patient) @@ -69,6 +80,11 @@ class RegistryTests(TestCase): eav.unregister(ExampleModel) self.assertFalse(ExampleModel.objects.__class__.__name__ == 'EntityManager') + def test_unregistering_via_metaclass(self): + self.assertTrue(ExampleMetaclassModel.objects.__class__.__name__ == 'EntityManager') + eav.unregister(ExampleMetaclassModel) + self.assertFalse(ExampleMetaclassModel.objects.__class__.__name__ == 'EntityManager') + def test_unregistering_unregistered_model_proceeds_silently(self): eav.unregister(Patient) @@ -80,4 +96,4 @@ class RegistryTests(TestCase): with self.assertRaises(ValueError): @eav.decorators.register_eav() class Foo(object): - pass \ No newline at end of file + pass diff --git a/tox.ini b/tox.ini index 81d8b36..d209fd4 100644 --- a/tox.ini +++ b/tox.ini @@ -12,4 +12,5 @@ deps = django111: Django >=1.11, <2.0 django20: Django >= 2.0, <2.1 djangotip: Django + commands = ./runtests