from django.core.exceptions import ObjectDoesNotExist
from django.core.validators import RegexValidator
from django.utils import simplejson as json
-from django.utils.encoding import smart_str
+from django.utils.encoding import force_unicode
from philo.exceptions import AncestorDoesNotExist
from philo.models.fields import JSONField
from philo.utils import ContentTypeRegistryLimiter, ContentTypeSubclassLimiter
class AttributeValue(models.Model):
attribute_set = generic.GenericRelation('Attribute', content_type_field='value_content_type', object_id_field='value_object_id')
- @property
- def attribute(self):
- return self.attribute_set.all()[0]
-
def set_value(self, value):
raise NotImplementedError
class JSONValue(AttributeValue):
- value = JSONField(verbose_name='Value (JSON)', help_text='This value must be valid JSON.', default='null')
+ value = JSONField(verbose_name='Value (JSON)', help_text='This value must be valid JSON.', default='null', db_index=True)
def __unicode__(self):
- return smart_str(self.value)
+ return force_unicode(self.value)
def value_formfields(self):
kwargs = {'initial': self.value_json}
class ForeignKeyValue(AttributeValue):
content_type = models.ForeignKey(ContentType, limit_choices_to=value_content_type_limiter, verbose_name='Value type', null=True, blank=True)
- object_id = models.PositiveIntegerField(verbose_name='Value ID', null=True, blank=True)
+ object_id = models.PositiveIntegerField(verbose_name='Value ID', null=True, blank=True, db_index=True)
value = generic.GenericForeignKey()
def value_formfields(self):
class Attribute(models.Model):
- entity_content_type = models.ForeignKey(ContentType, related_name='attribute_entity_set', verbose_name='Entity type', db_index=True)
+ entity_content_type = models.ForeignKey(ContentType, related_name='attribute_entity_set', verbose_name='Entity type')
entity_object_id = models.PositiveIntegerField(verbose_name='Entity ID', db_index=True)
entity = generic.GenericForeignKey('entity_content_type', 'entity_object_id')
- value_content_type = models.ForeignKey(ContentType, related_name='attribute_value_set', limit_choices_to=attribute_value_limiter, verbose_name='Value type', null=True, blank=True, db_index=True)
+ value_content_type = models.ForeignKey(ContentType, related_name='attribute_value_set', limit_choices_to=attribute_value_limiter, verbose_name='Value type', null=True, blank=True)
value_object_id = models.PositiveIntegerField(verbose_name='Value ID', null=True, blank=True, db_index=True)
value = generic.GenericForeignKey('value_content_type', 'value_object_id')
class EntityBase(models.base.ModelBase):
def __new__(cls, name, bases, attrs):
+ entity_meta = attrs.pop('EntityMeta', None)
new = super(EntityBase, cls).__new__(cls, name, bases, attrs)
- entity_options = attrs.pop('EntityMeta', None)
- setattr(new, '_entity_meta', EntityOptions(entity_options))
+ new.add_to_class('_entity_meta', EntityOptions(entity_meta))
entity_class_prepared.send(sender=new)
return new
+class EntityAttributeMapper(object, DictMixin):
+ def __init__(self, entity):
+ self.entity = entity
+
+ def get_attributes(self):
+ return self.entity.attribute_set.all()
+
+ def make_cache(self):
+ attributes = self.get_attributes()
+ value_lookups = {}
+
+ for a in attributes:
+ value_lookups.setdefault(a.value_content_type, []).append(a.value_object_id)
+
+ values_bulk = {}
+
+ for ct, pks in value_lookups.items():
+ values_bulk[ct] = ct.model_class().objects.in_bulk(pks)
+
+ self._cache = dict([(a.key, getattr(values_bulk[a.value_content_type].get(a.value_object_id), 'value', None)) for a in attributes])
+
+ def __getitem__(self, key):
+ if not hasattr(self, '_cache'):
+ self.make_cache()
+ return self._cache[key]
+
+ def keys(self):
+ if not hasattr(self, '_cache'):
+ self.make_cache()
+ return self._cache.keys()
+
+ def items(self):
+ if not hasattr(self, '_cache'):
+ self.make_cache()
+ return self._cache.items()
+
+ def values(self):
+ if not hasattr(self, '_cache'):
+ self.make_cache()
+ return self._cache.values()
+
+
class Entity(models.Model):
__metaclass__ = EntityBase
@property
def attributes(self):
- return QuerySetMapper(self.attribute_set.all())
+ return EntityAttributeMapper(self)
class Meta:
abstract = True
return meta.register(cls)
+class TreeEntityAttributeMapper(EntityAttributeMapper):
+ def get_attributes(self):
+ ancestors = dict(self.entity.get_ancestors(include_self=True).values_list('pk', 'level'))
+ ct = ContentType.objects.get_for_model(self.entity)
+ return sorted(Attribute.objects.filter(entity_content_type=ct, entity_object_id__in=ancestors.keys()), key=lambda x: ancestors[x.entity_object_id])
+
+
class TreeEntity(Entity, TreeModel):
__metaclass__ = TreeEntityBase
@property
def attributes(self):
if self.parent:
- return QuerySetMapper(self.attribute_set.all(), passthrough=self.parent.attributes)
+ return TreeEntityAttributeMapper(self)
return super(TreeEntity, self).attributes
class Meta: