from django.contrib import admin
from django.contrib.contenttypes import generic
-from philo.models import Tag, Attribute, Relationship
+from philo.models import Tag, Attribute
COLLAPSE_CLASSES = ('collapse', 'collapse-closed', 'closed',)
classes = COLLAPSE_CLASSES
-class RelationshipInline(generic.GenericTabularInline):
- ct_field = 'entity_content_type'
- ct_fk_field = 'entity_object_id'
- model = Relationship
- extra = 1
- template = 'admin/philo/edit_inline/tabular_collapse.html'
- allow_add = True
- classes = COLLAPSE_CLASSES
+#class RelationshipInline(generic.GenericTabularInline):
+# ct_field = 'entity_content_type'
+# ct_fk_field = 'entity_object_id'
+# model = Relationship
+# extra = 1
+# template = 'admin/philo/edit_inline/tabular_collapse.html'
+# allow_add = True
+# classes = COLLAPSE_CLASSES
class EntityAdmin(admin.ModelAdmin):
- inlines = [AttributeInline, RelationshipInline]
+ inlines = [AttributeInline] #, RelationshipInline]
save_on_top = True
from django.contrib import admin
from django import forms
-from philo.admin import widgets
from philo.admin.base import COLLAPSE_CLASSES
from philo.admin.nodes import ViewAdmin
from philo.models.pages import Page, Template, Contentlet, ContentReference
from django.utils.datastructures import SortedDict
from philo.admin.widgets import ModelLookupWidget
from philo.models import Entity, Template, Contentlet, ContentReference
-from philo.models.fields import RelationshipField
from philo.utils import fattr
abstract = True
-class Attribute(models.Model):
- entity_content_type = models.ForeignKey(ContentType, verbose_name='Entity type')
- entity_object_id = models.PositiveIntegerField(verbose_name='Entity ID')
- entity = generic.GenericForeignKey('entity_content_type', 'entity_object_id')
- key = models.CharField(max_length=255)
+value_content_type_limiter = ContentTypeRegistryLimiter()
+
+
+def register_value_model(model):
+ value_content_type_limiter.register_class(model)
+
+
+def unregister_value_model(model):
+ value_content_type_limiter.unregister_class(model)
+
+
+class JSONValue(models.Model):
value = JSONField() #verbose_name='Value (JSON)', help_text='This value must be valid JSON.')
def __unicode__(self):
- return u'"%s": %s' % (self.key, self.value)
+ return self.value_json
class Meta:
app_label = 'philo'
- unique_together = ('key', 'entity_content_type', 'entity_object_id')
-value_content_type_limiter = ContentTypeRegistryLimiter()
-
+class ForeignKeyValue(models.Model):
+ content_type = models.ForeignKey(ContentType, related_name='foreign_key_value_set', 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)
+ value = generic.GenericForeignKey()
+
+ def __unicode__(self):
+ return unicode(self.value)
+
+ class Meta:
+ app_label = 'philo'
-def register_value_model(model):
- value_content_type_limiter.register_class(model)
+class ManyToManyValue(models.Model):
+ content_type = models.ForeignKey(ContentType, related_name='many_to_many_value_set', limit_choices_to=value_content_type_limiter, verbose_name='Value type', null=True, blank=True)
+ object_ids = models.CommaSeparatedIntegerField(max_length=300, verbose_name='Value IDs', null=True, blank=True)
+
+ def get_value(self):
+ return self.content_type.model_class()._default_manager.filter(id__in=self.object_ids)
+
+ def set_value(self, value):
+ if not isinstance(value, models.query.QuerySet):
+ raise TypeError("Value must be a QuerySet.")
+ self.content_type = ContentType.objects.get_for_model(value.model)
+ self.object_ids = ','.join(value.values_list('id', flat=True))
+
+ value = property(get_value, set_value)
+
+ def __unicode__(self):
+ return unicode(self.value)
+
+ class Meta:
+ app_label = 'philo'
-def unregister_value_model(model):
- value_content_type_limiter.unregister_class(model)
+attribute_value_limiter = ContentTypeRegistryLimiter()
+attribute_value_limiter.register_class(JSONValue)
+attribute_value_limiter.register_class(ForeignKeyValue)
+attribute_value_limiter.register_class(ManyToManyValue)
-class Relationship(models.Model):
- entity_content_type = models.ForeignKey(ContentType, related_name='relationship_entity_set', verbose_name='Entity type')
+class Attribute(models.Model):
+ entity_content_type = models.ForeignKey(ContentType, related_name='attribute_entity_set', verbose_name='Entity type')
entity_object_id = models.PositiveIntegerField(verbose_name='Entity ID')
entity = generic.GenericForeignKey('entity_content_type', 'entity_object_id')
- key = models.CharField(max_length=255)
- value_content_type = models.ForeignKey(ContentType, related_name='relationship_value_set', limit_choices_to=value_content_type_limiter, verbose_name='Value type', null=True, blank=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)
value = generic.GenericForeignKey('value_content_type', 'value_object_id')
+ key = models.CharField(max_length=255)
+
+ def get_value_class(self, value):
+ if isinstance(value, models.query.QuerySet):
+ return ManyToManyValue
+ elif isinstance(value, models.Model):
+ return ForeignKeyValue
+ else:
+ return JSONValue
+
+ def set_value(self, value):
+ value_class = self.get_value_class(value)
+
+ if self.value is None or value_class != self.value_content_type.model_class():
+ if self.value is not None:
+ self.value.delete()
+ new_value = value_class()
+ new_value.value = value
+ new_value.save()
+ self.value = new_value
+ else:
+ self.value.value = value
+ self.value.save()
+
def __unicode__(self):
return u'"%s": %s' % (self.key, self.value)
class Meta:
app_label = 'philo'
- unique_together = ('key', 'entity_content_type', 'entity_object_id')
+ unique_together = (('key', 'entity_content_type', 'entity_object_id'), ('value_content_type', 'value_object_id'))
class QuerySetMapper(object, DictMixin):
__metaclass__ = EntityBase
attribute_set = generic.GenericRelation(Attribute, content_type_field='entity_content_type', object_id_field='entity_object_id')
- relationship_set = generic.GenericRelation(Relationship, content_type_field='entity_content_type', object_id_field='entity_object_id')
@property
def attributes(self):
return QuerySetMapper(self.attribute_set)
- @property
- def relationships(self):
- return QuerySetMapper(self.relationship_set)
-
@property
def _added_attribute_registry(self):
if not hasattr(self, '_real_added_attribute_registry'):
self._real_removed_attribute_registry = []
return self._real_removed_attribute_registry
- @property
- def _added_relationship_registry(self):
- if not hasattr(self, '_real_added_relationship_registry'):
- self._real_added_relationship_registry = {}
- return self._real_added_relationship_registry
-
- @property
- def _removed_relationship_registry(self):
- if not hasattr(self, '_real_removed_relationship_registry'):
- self._real_removed_relationship_registry = []
- return self._real_removed_relationship_registry
-
def save(self, *args, **kwargs):
super(Entity, self).save(*args, **kwargs)
attribute = Attribute()
attribute.entity = self
attribute.key = key
- attribute.value = value
+ attribute.set_value(value)
attribute.save()
self._added_attribute_registry.clear()
-
- for key in self._removed_relationship_registry:
- self.relationship_set.filter(key__exact=key).delete()
- del self._removed_relationship_registry[:]
-
- for key, value in self._added_relationship_registry.items():
- try:
- relationship = self.relationship_set.get(key__exact=key)
- except Relationship.DoesNotExist:
- relationship = Relationship()
- relationship.entity = self
- relationship.key = key
- relationship.value = value
- relationship.save()
- self._added_relationship_registry.clear()
class Meta:
abstract = True
from philo.validators import TemplateValidator, json_validator
-__all__ = ('AttributeField', 'RelationshipField')
+__all__ = ('JSONAttribute', 'ForeignKeyAttribute', 'ManyToManyAttribute')
class EntityProxyField(object):
raise AttributeError('The \'%s\' attribute can only be accessed from %s instances.' % (self.field.name, owner.__name__))
def __set__(self, instance, value):
- if self.field.key in instance._removed_attribute_registry:
- instance._removed_attribute_registry.remove(self.field.key)
- instance._added_attribute_registry[self.field.key] = value
+ raise NotImplementedError('AttributeFieldDescriptor subclasses must implement a __set__ method.')
def __delete__(self, instance):
if self.field.key in instance._added_attribute_registry:
instance._removed_attribute_registry.append(self.field.key)
+class JSONAttributeDescriptor(AttributeFieldDescriptor):
+ def __set__(self, instance, value):
+ if self.field.key in instance._removed_attribute_registry:
+ instance._removed_attribute_registry.remove(self.field.key)
+ instance._added_attribute_registry[self.field.key] = value
+
+
+class ForeignKeyAttributeDescriptor(AttributeFieldDescriptor):
+ def __set__(self, instance, value):
+ if isinstance(value, (models.Model, type(None))):
+ if self.field.key in instance._removed_attribute_registry:
+ instance._removed_attribute_registry.remove(self.field.key)
+ instance._added_attribute_registry[self.field.key] = value
+ else:
+ raise AttributeError('The \'%s\' attribute can only be set using existing Model objects.' % self.field.name)
+
+
+class ManyToManyAttributeDescriptor(AttributeFieldDescriptor):
+ def __set__(self, instance, value):
+ if isinstance(value, models.QuerySet):
+ if self.field.key in instance._removed_attribute_registry:
+ instance._removed_attribute_registry.remove(self.field.key)
+ instance._added_attribute_registry[self.field.key] = value
+ else:
+ raise AttributeError('The \'%s\' attribute can only be set to a QuerySet.' % self.field.name)
+
+
class AttributeField(EntityProxyField):
- descriptor_class = AttributeFieldDescriptor
+ def contribute_to_class(self, cls, name):
+ super(AttributeField, self).contribute_to_class(cls, name)
+ if self.key is None:
+ self.key = name
+
+
+class JSONAttribute(AttributeField):
+ descriptor_class = JSONAttributeDescriptor
def __init__(self, field_template=None, key=None, **kwargs):
super(AttributeField, self).__init__(**kwargs)
field_template = models.CharField(max_length=255)
self.field_template = field_template
- def contribute_to_class(self, cls, name):
- super(AttributeField, self).contribute_to_class(cls, name)
- if self.key is None:
- self.key = name
-
def formfield(self, **kwargs):
defaults = {'required': False, 'label': capfirst(self.verbose_name), 'help_text': self.help_text}
defaults.update(kwargs)
return self.field_template.formfield(**defaults)
-
-
-class RelationshipFieldDescriptor(object):
- def __init__(self, field):
- self.field = field
- def __get__(self, instance, owner):
- if instance:
- if self.field.key in instance._added_relationship_registry:
- return instance._added_relationship_registry[self.field.key]
- if self.field.key in instance._removed_relationship_registry:
- return None
- try:
- return instance.relationships[self.field.key]
- except KeyError:
- return None
- else:
- raise AttributeError('The \'%s\' attribute can only be accessed from %s instances.' % (self.field.name, owner.__name__))
-
- def __set__(self, instance, value):
- if isinstance(value, (models.Model, type(None))):
- if self.field.key in instance._removed_relationship_registry:
- instance._removed_relationship_registry.remove(self.field.key)
- instance._added_relationship_registry[self.field.key] = value
- else:
- raise AttributeError('The \'%s\' attribute can only be set using existing Model objects.' % self.field.name)
-
- def __delete__(self, instance):
- if self.field.key in instance._added_relationship_registry:
- del instance._added_relationship_registry[self.field.key]
- instance._removed_relationship_registry.append(self.field.key)
+ def value_from_object(self, obj):
+ return getattr(obj, self.attname).value
-class RelationshipField(EntityProxyField):
- descriptor_class = RelationshipFieldDescriptor
+class ForeignKeyAttribute(AttributeField):
+ descriptor_class = ForeignKeyAttributeDescriptor
def __init__(self, model, limit_choices_to=None, key=None, **kwargs):
- super(RelationshipField, self).__init__(**kwargs)
+ super(ForeignKeyAttribute, self).__init__(**kwargs)
self.key = key
self.model = model
if limit_choices_to is None:
limit_choices_to = {}
self.limit_choices_to = limit_choices_to
- def contribute_to_class(self, cls, name):
- super(RelationshipField, self).contribute_to_class(cls, name)
- if self.key is None:
- self.key = name
-
def formfield(self, form_class=forms.ModelChoiceField, **kwargs):
defaults = {'required': False, 'label': capfirst(self.verbose_name), 'help_text': self.help_text}
defaults.update(kwargs)
return form_class(self.model._default_manager.complex_filter(self.limit_choices_to), **defaults)
def value_from_object(self, obj):
- relobj = super(RelationshipField, self).value_from_object(obj)
+ relobj = super(ForeignKeyAttribute, self).value_from_object(obj).value
return getattr(relobj, 'pk', None)
+class ManyToManyAttribute(AttributeField):
+ descriptor_class = ManyToManyAttributeDescriptor
+ #FIXME: Add __init__ and formfield methods
+
+
class TemplateField(models.TextField):
def __init__(self, allow=None, disallow=None, secure=True, *args, **kwargs):
super(TemplateField, self).__init__(*args, **kwargs)
def attributes_with_node(self, node):
return QuerySetMapper(self.attribute_set, passthrough=node.attributes)
- def relationships_with_node(self, node):
- return QuerySetMapper(self.relationship_set, passthrough=node.relationships)
-
def render_to_response(self, node, request, path=None, subpath=None, extra_context=None):
extra_context = extra_context or {}
view_about_to_render.send(sender=self, node=node, request=request, path=path, subpath=subpath, extra_context=extra_context)
def render_to_string(self, node=None, request=None, path=None, subpath=None, extra_context=None):
context = {}
context.update(extra_context or {})
- context.update({'page': self, 'attributes': self.attributes, 'relationships': self.relationships})
+ context.update({'page': self, 'attributes': self.attributes})
if node and request:
- context.update({'node': node, 'attributes': self.attributes_with_node(node), 'relationships': self.relationships_with_node(node)})
+ context.update({'node': node, 'attributes': self.attributes_with_node(node)})
page_about_to_render_to_string.send(sender=self, node=node, request=request, extra_context=context)
string = self.template.django_template.render(RequestContext(request, context))
else: