mirror of
https://github.com/jazzband/django-eav2.git
synced 2026-03-16 22:40:26 +00:00
Merge branch 'master' of github.com:mvpdev/eav
This commit is contained in:
commit
4aa7952485
4 changed files with 203 additions and 23 deletions
59
models.py
59
models.py
|
|
@ -125,6 +125,7 @@ class EavAttribute(models.Model):
|
|||
"""
|
||||
self._save_single_value(entity, value)
|
||||
|
||||
|
||||
def _save_single_value(self, entity, value=None, attribute=None):
|
||||
"""
|
||||
Save a a value of type that doesn't need special joining like
|
||||
|
|
@ -148,6 +149,7 @@ class EavAttribute(models.Model):
|
|||
eavvalue.value = value
|
||||
eavvalue.save()
|
||||
|
||||
|
||||
def __unicode__(self):
|
||||
return u"%s (%s)" % (self.name, self.get_datatype_display())
|
||||
|
||||
|
|
@ -189,19 +191,29 @@ class EavValue(models.Model):
|
|||
|
||||
attribute = models.ForeignKey(EavAttribute)
|
||||
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.full_clean()
|
||||
super(EavValue, self).save(*args, **kwargs)
|
||||
|
||||
|
||||
def _blank(self):
|
||||
"""
|
||||
Set all the field to none
|
||||
"""
|
||||
self.value_bool = False
|
||||
for field in self._meta.fields:
|
||||
if field.name.startswith('value_') and field.null == True:
|
||||
setattr(self, field.name, None)
|
||||
|
||||
|
||||
def _get_value(self):
|
||||
"""
|
||||
Get returns the Python object hold by this EavValue object.
|
||||
"""
|
||||
return getattr(self, 'value_%s' % self.attribute.datatype)
|
||||
|
||||
|
||||
def _set_value(self, new_value):
|
||||
self._blank()
|
||||
setattr(self, 'value_%s' % self.attribute.datatype, new_value)
|
||||
|
|
@ -222,14 +234,12 @@ class EavEntity(object):
|
|||
|
||||
# TODO: memoize
|
||||
def __getattr__(self, name):
|
||||
|
||||
if not name.startswith('_'):
|
||||
for slug in self.get_all_attribute_slugs():
|
||||
attribute = self.get_attribute_by_slug(name)
|
||||
if attribute:
|
||||
value = attribute.get_value_for_entity(self.model)
|
||||
if value:
|
||||
return attribute.get_value_for_entity(self.model).value
|
||||
attribute = self.get_attribute_by_slug(name)
|
||||
if attribute:
|
||||
value = attribute.get_value_for_entity(self.model)
|
||||
if value:
|
||||
return attribute.get_value_for_entity(self.model).value
|
||||
return None
|
||||
|
||||
raise AttributeError(_(u"%s EAV does not have attribute " \
|
||||
|
|
@ -237,12 +247,22 @@ class EavEntity(object):
|
|||
(self.model._meta.object_name, name))
|
||||
|
||||
|
||||
@classmethod
|
||||
def get_eav_attributes_for_model(cls, model_cls):
|
||||
"""
|
||||
Return the attributes for this model
|
||||
"""
|
||||
from .utils import EavRegistry
|
||||
config_cls = EavRegistry.get_config_cls_for_model(model_cls)
|
||||
return config_cls.get_eav_attributes()
|
||||
|
||||
|
||||
@classmethod
|
||||
def get_attr_cache_for_model(cls, model_cls):
|
||||
"""
|
||||
Return the attribute cache for this model
|
||||
"""
|
||||
return cls._cache.setdefault(get_unique_class_identifier(cls), {})
|
||||
return cls._cache.setdefault(get_unique_class_identifier(model_cls), {})
|
||||
|
||||
|
||||
@classmethod
|
||||
|
|
@ -251,7 +271,8 @@ class EavEntity(object):
|
|||
Create or update the attributes cache for this entity class.
|
||||
"""
|
||||
cache = cls.get_attr_cache_for_model(model_cls)
|
||||
cache['attributes'] = cls.get_eav_attributes().select_related()
|
||||
cache['attributes'] = cls.get_eav_attributes_for_model(model_cls)\
|
||||
.select_related()
|
||||
cache['slug_mapping'] = dict((s.slug, s) for s in cache['attributes'])
|
||||
return cache
|
||||
|
||||
|
|
@ -265,6 +286,13 @@ class EavEntity(object):
|
|||
cache.clear()
|
||||
return cache
|
||||
|
||||
|
||||
def get_eav_attributes(self):
|
||||
"""
|
||||
Return the attributes for this model
|
||||
"""
|
||||
return self.__class__.get_eav_attributes_for_model(self.model.__class__)
|
||||
|
||||
|
||||
def update_attr_cache(self):
|
||||
"""
|
||||
|
|
@ -291,11 +319,12 @@ class EavEntity(object):
|
|||
|
||||
|
||||
def save(self):
|
||||
for attribute in self.get_all_attributes():
|
||||
for attribute in self.get_eav_attributes():
|
||||
if hasattr(self, attribute.slug):
|
||||
attribute_value = getattr(self, attribute.slug)
|
||||
attribute.save_value(self.model, attribute_value)
|
||||
|
||||
|
||||
@classmethod
|
||||
def get_all_attributes_for_model(cls, model_cls):
|
||||
"""
|
||||
|
|
@ -308,9 +337,7 @@ class EavEntity(object):
|
|||
|
||||
return cache['attributes']
|
||||
|
||||
def get_all_attributes(self):
|
||||
m_cls = self.__class__
|
||||
return self.__class__.get_all_attributes_for_model(m_cls)
|
||||
|
||||
|
||||
|
||||
def get_values(self):
|
||||
|
|
@ -346,8 +373,8 @@ class EavEntity(object):
|
|||
cache = cls.get_attr_cache_for_model(model_cls)
|
||||
if not cache:
|
||||
cache = EavEntity.update_attr_cache_for_model(model_cls)
|
||||
if slug in cache['slug_mapping']:
|
||||
return cache['slug_mapping'][slug]
|
||||
return cache['slug_mapping'].get(slug, None)
|
||||
|
||||
|
||||
def get_attribute_by_slug(self, slug):
|
||||
m_cls = self.model.__class__
|
||||
|
|
@ -363,10 +390,12 @@ class EavEntity(object):
|
|||
if attr.pk == attribute_id:
|
||||
return attr
|
||||
|
||||
|
||||
def __iter__(self):
|
||||
"Iterates over non-empty EAV attributes. Normal fields are not included."
|
||||
return iter(self.get_values())
|
||||
|
||||
|
||||
@staticmethod
|
||||
def save_handler(sender, *args, **kwargs):
|
||||
kwargs['instance'].eav.save()
|
||||
|
|
|
|||
|
|
@ -55,9 +55,6 @@ class EavBasicTests(TestCase):
|
|||
self.assertEqual(unicode(self.value), "Doe - City: \"Denver\"")
|
||||
|
||||
|
||||
def test_value_unicode(self):
|
||||
self.assertEqual(unicode(self.value), "Doe - City: \"Denver\"")
|
||||
|
||||
|
||||
def test_value_types(self):
|
||||
_text = EavAttribute.objects.create(datatype=EavAttribute.TYPE_TEXT,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
from datetime import datetime
|
||||
|
||||
from django.test import TestCase
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from ..models import *
|
||||
from ..utils import EavRegistry, EavConfig
|
||||
|
|
@ -86,6 +87,8 @@ class EavSetterAndGetterTests(TestCase):
|
|||
|
||||
def test_you_can_create_several_type_of_attributes(self):
|
||||
|
||||
self.patient = Patient(name='test')
|
||||
|
||||
EavAttribute.objects.create(datatype=EavAttribute.TYPE_TEXT,
|
||||
name='text', slug='text')
|
||||
EavAttribute.objects.create(datatype=EavAttribute.TYPE_FLOAT,
|
||||
|
|
@ -96,6 +99,8 @@ class EavSetterAndGetterTests(TestCase):
|
|||
name='date', slug='date')
|
||||
EavAttribute.objects.create(datatype=EavAttribute.TYPE_BOOLEAN,
|
||||
name='bool', slug='bool')
|
||||
EavAttribute.objects.create(datatype=EavAttribute.TYPE_OBJECT,
|
||||
name='object', slug='object')
|
||||
|
||||
now = datetime.today()
|
||||
self.patient.eav.text = 'a'
|
||||
|
|
@ -103,6 +108,7 @@ class EavSetterAndGetterTests(TestCase):
|
|||
self.patient.eav.int = 1
|
||||
self.patient.eav.date = now
|
||||
self.patient.eav.bool = True
|
||||
self.patient.eav.object = User.objects.create(username='Bob')
|
||||
|
||||
self.patient.save()
|
||||
|
||||
|
|
@ -111,6 +117,8 @@ class EavSetterAndGetterTests(TestCase):
|
|||
self.assertEqual(self.patient.eav.int, 1)
|
||||
self.assertEqual(self.patient.eav.date, now)
|
||||
self.assertEqual(self.patient.eav.bool, True)
|
||||
self.assertEqual(self.patient.eav.object,
|
||||
User.objects.get(username='Bob'))
|
||||
|
||||
|
||||
def test_assign_a_value_that_is_not_an_eav_attribute_does_nothing(self):
|
||||
|
|
@ -118,6 +126,11 @@ class EavSetterAndGetterTests(TestCase):
|
|||
self.patient.eav.no_an_attribute = 'Woot'
|
||||
self.patient.save()
|
||||
self.assertFalse(EavValue.objects.filter(value_text='Paris').count())
|
||||
|
||||
|
||||
def test_get_a_value_that_does_not_exists_returns_none(self):
|
||||
|
||||
self.assertEqual(self.patient.eav.impossible_value, None)
|
||||
|
||||
|
||||
def test_attributes_can_be_labelled(self):
|
||||
|
|
@ -147,6 +160,35 @@ class EavSetterAndGetterTests(TestCase):
|
|||
# remove a label that is not attach does nothing
|
||||
self.attribute.remove_label('a')
|
||||
self.attribute.remove_label('x')
|
||||
|
||||
|
||||
def test_attributes_are_filtered_according_to_config_class(self):
|
||||
attribute = EavAttribute.objects\
|
||||
.create(datatype=EavAttribute.TYPE_TEXT,
|
||||
name='Country', slug='country')
|
||||
|
||||
EavRegistry.unregister(Patient)
|
||||
|
||||
class PatientEav(EavConfig):
|
||||
|
||||
@classmethod
|
||||
def get_eav_attributes(cls):
|
||||
return EavAttribute.objects.filter(slug='country')
|
||||
|
||||
class UserEav(EavConfig):
|
||||
|
||||
@classmethod
|
||||
def get_eav_attributes(cls):
|
||||
return EavAttribute.objects.filter(slug='city')
|
||||
|
||||
EavRegistry.register(Patient, PatientEav)
|
||||
EavRegistry.register(User, UserEav)
|
||||
|
||||
self.assertEqual(list(Patient.eav.get_eav_attributes()),
|
||||
list(EavAttribute.objects.filter(slug='country')))
|
||||
|
||||
self.assertEqual(list(User.eav.get_eav_attributes()),
|
||||
list(EavAttribute.objects.filter(slug='city')))
|
||||
|
||||
|
||||
def test_can_filter_attribute_availability_for_entity(self):
|
||||
|
|
@ -177,8 +219,108 @@ class EavSetterAndGetterTests(TestCase):
|
|||
self.assertFalse(p.eav.city, 'Paris')
|
||||
self.assertEqual(p.eav.country, 'USA')
|
||||
|
||||
p = Patient()
|
||||
|
||||
def test_can_have_differente_attribute_filter(self):
|
||||
|
||||
|
||||
attribute = EavAttribute.objects\
|
||||
.create(datatype=EavAttribute.TYPE_TEXT,
|
||||
name='Country', slug='country')
|
||||
|
||||
EavRegistry.unregister(Patient)
|
||||
|
||||
# todo: test multiple children
|
||||
class PatientEav(EavConfig):
|
||||
|
||||
@classmethod
|
||||
def get_eav_attributes(cls):
|
||||
return EavAttribute.objects.filter(slug='country')
|
||||
|
||||
class UserEav(EavConfig):
|
||||
|
||||
@classmethod
|
||||
def get_eav_attributes(cls):
|
||||
return EavAttribute.objects.filter(slug='city')
|
||||
|
||||
EavRegistry.register(Patient, PatientEav)
|
||||
EavRegistry.register(User, UserEav)
|
||||
|
||||
p = Patient.objects.create(name='Patrick')
|
||||
u = User.objects.create(username='John')
|
||||
|
||||
p.eav.city = 'Paris'
|
||||
p.eav.country = 'USA'
|
||||
p.save()
|
||||
u.eav.city = 'Paris'
|
||||
u.eav.country = 'USA'
|
||||
u.save()
|
||||
|
||||
p = Patient.objects.get(pk=p.pk)
|
||||
u = User.objects.get(pk=u.pk)
|
||||
|
||||
self.assertFalse(p.eav.city)
|
||||
self.assertEqual(p.eav.country, 'USA')
|
||||
|
||||
self.assertFalse(u.eav.country)
|
||||
self.assertEqual(u.eav.city, 'Paris')
|
||||
|
||||
|
||||
def test_can_have_a_subclass_for_config_class(self):
|
||||
|
||||
attribute = EavAttribute.objects\
|
||||
.create(datatype=EavAttribute.TYPE_TEXT,
|
||||
name='Country', slug='country')
|
||||
|
||||
EavRegistry.unregister(Patient)
|
||||
|
||||
class PatientEav(EavConfig):
|
||||
|
||||
@classmethod
|
||||
def get_eav_attributes(cls):
|
||||
return EavAttribute.objects.filter(slug='country')
|
||||
|
||||
class SubPatientEav(PatientEav):
|
||||
|
||||
@classmethod
|
||||
def get_eav_attributes(cls):
|
||||
return EavAttribute.objects.filter(slug='country')
|
||||
|
||||
EavRegistry.register(Patient, SubPatientEav)
|
||||
|
||||
|
||||
self.patient.eav.city = 'Paris'
|
||||
self.patient.eav.country = 'USA'
|
||||
self.patient.save()
|
||||
|
||||
p = Patient.objects.get(pk=self.patient.pk)
|
||||
|
||||
self.assertFalse(p.eav.city)
|
||||
self.assertEqual(p.eav.country, 'USA')
|
||||
|
||||
|
||||
def test_blank_set_all_value_field_with_a_null_default_to_none(self):
|
||||
self.value._blank()
|
||||
self.assertEqual(self.value.value_text, None)
|
||||
self.assertEqual(self.value.value_int, None)
|
||||
self.assertEqual(self.value.value_float, None)
|
||||
self.assertEqual(self.value.value_date, None)
|
||||
self.assertEqual(self.value.value_object, None)
|
||||
|
||||
def test_blank_set_all_value_field_with_a_default_to_default(self):
|
||||
self.value._blank()
|
||||
self.assertEqual(self.value.value_bool, False)
|
||||
|
||||
|
||||
def test_get_value_on_eavvalue_return_python_object(self):
|
||||
self.assertEqual(self.value._get_value(), 'Denver')
|
||||
self.assertEqual(self.value.value, self.value._get_value())
|
||||
|
||||
def test_set_value_store_the_python_object_and_blank_other_fields(self):
|
||||
|
||||
self.value._set_value('Bamako')
|
||||
self.assertEqual(self.value.value, 'Bamako')
|
||||
self.assertEqual(self.value.value_text, 'Bamako')
|
||||
self.assertEqual(self.value.value_int, None)
|
||||
self.assertEqual(self.value.value_float, None)
|
||||
self.assertEqual(self.value.value_date, None)
|
||||
self.assertEqual(self.value.value_bool, False)
|
||||
self.assertEqual(self.value.value_object, None)
|
||||
|
|
|
|||
18
utils.py
18
utils.py
|
|
@ -5,7 +5,7 @@ from .managers import EntityManager
|
|||
from .models import (EavEntity, EavAttribute, EavValue,
|
||||
get_unique_class_identifier)
|
||||
|
||||
class EavConfig(object):
|
||||
class EavConfig(EavEntity):
|
||||
|
||||
proxy_field_name = 'eav'
|
||||
manager_field_name ='objects'
|
||||
|
|
@ -59,6 +59,17 @@ class EavRegistry(object):
|
|||
for cache in EavRegistry.cache.itervalues():
|
||||
EavEntity.update_attr_cache_for_model(cache['model_cls'])
|
||||
|
||||
|
||||
@staticmethod
|
||||
def wrap_config_class(model_cls, config_cls):
|
||||
"""
|
||||
Check if the config class is EavConfig, and create a subclass if
|
||||
it is
|
||||
"""
|
||||
if config_cls is EavConfig:
|
||||
return type("%sConfig" % model_cls.__name__, (EavConfig,), {})
|
||||
return config_cls
|
||||
|
||||
|
||||
@staticmethod
|
||||
def register(model_cls, config_cls=EavConfig):
|
||||
|
|
@ -72,6 +83,7 @@ class EavRegistry(object):
|
|||
if cls_id in EavRegistry.cache:
|
||||
return
|
||||
|
||||
config_cls = EavRegistry.wrap_config_class(model_cls, config_cls)
|
||||
post_init.connect(EavRegistry.attach, sender=model_cls)
|
||||
post_save.connect(EavEntity.save_handler, sender=model_cls)
|
||||
EavRegistry.cache[cls_id] = { 'config_cls': config_cls,
|
||||
|
|
@ -81,8 +93,8 @@ class EavRegistry(object):
|
|||
mgr = getattr(model_cls, config_cls.manager_field_name)
|
||||
EavRegistry.cache[cls_id]['old_mgr'] = mgr
|
||||
|
||||
setattr(model_cls, config_cls.proxy_field_name, EavEntity)
|
||||
|
||||
setattr(model_cls, config_cls.proxy_field_name, config_cls)
|
||||
|
||||
setattr(getattr(model_cls, config_cls.proxy_field_name),
|
||||
'get_eav_attributes', config_cls.get_eav_attributes)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue