from django.utils import simplejson as json
from django.utils.html import escape
from philo.models import Tag, Attribute
-from philo.forms.entities import AttributeForm, AttributeInlineFormSet
+from philo.admin.forms.attributes import AttributeForm, AttributeInlineFormSet
from philo.admin.widgets import TagFilteredSelectMultiple
from mptt.admin import MPTTModelAdmin
--- /dev/null
+from philo.admin.forms.attributes import *
+from philo.admin.forms.containers import *
\ No newline at end of file
--- /dev/null
+from django.contrib.contenttypes.generic import BaseGenericInlineFormSet
+from django.contrib.contenttypes.models import ContentType
+from django.forms.models import ModelForm
+from philo.models import Attribute
+
+
+__all__ = ('AttributeForm', 'AttributeInlineFormSet')
+
+
+class AttributeForm(ModelForm):
+ """
+ This class handles an attribute's fields as well as the fields for its value (if there is one.)
+ The fields defined will vary depending on the value type, but the fields for defining the value
+ (i.e. value_content_type and value_object_id) will always be defined. Except that value_object_id
+ will never be defined. BLARGH!
+ """
+ def __init__(self, *args, **kwargs):
+ super(AttributeForm, self).__init__(*args, **kwargs)
+
+ # This is necessary because model forms store changes to self.instance in their clean method.
+ # Mutter mutter.
+ value = self.instance.value
+ self._cached_value_ct = self.instance.value_content_type
+ self._cached_value = value
+
+ # If there is a value, pull in its fields.
+ if value is not None:
+ self.value_fields = value.value_formfields()
+ self.fields.update(self.value_fields)
+
+ def save(self, *args, **kwargs):
+ # At this point, the cleaned_data has already been stored on self.instance.
+
+ if self.instance.value_content_type != self._cached_value_ct:
+ # The value content type has changed. Clear the old value, if there was one.
+ if self._cached_value:
+ self._cached_value.delete()
+
+ # Clear the submitted value, if any.
+ self.cleaned_data.pop('value', None)
+
+ # Now create a new value instance so that on next instantiation, the form will
+ # know what fields to add.
+ if self.instance.value_content_type is not None:
+ self.instance.value = self.instance.value_content_type.model_class().objects.create()
+ elif self.instance.value is not None:
+ # The value content type is the same, but one of the value fields has changed.
+
+ # Use construct_instance to apply the changes from the cleaned_data to the value instance.
+ fields = self.value_fields.keys()
+ if set(fields) & set(self.changed_data):
+ self.instance.value.construct_instance(**dict([(key, self.cleaned_data[key]) for key in fields]))
+ self.instance.value.save()
+
+ return super(AttributeForm, self).save(*args, **kwargs)
+
+ class Meta:
+ model = Attribute
+
+
+class AttributeInlineFormSet(BaseGenericInlineFormSet):
+ "Necessary to force the GenericInlineFormset to use the form's save method for new objects."
+ def save_new(self, form, commit):
+ setattr(form.instance, self.ct_field.get_attname(), ContentType.objects.get_for_model(self.instance).pk)
+ setattr(form.instance, self.ct_fk_field.get_attname(), self.instance.pk)
+ return form.save()
\ No newline at end of file
from philo.admin.base import COLLAPSE_CLASSES, TreeAdmin
from philo.admin.nodes import ViewAdmin
from philo.models.pages import Page, Template, Contentlet, ContentReference
-from philo.forms.containers import *
+from philo.admin.forms.containers import *
class ContentletInline(admin.StackedInline):
--- /dev/null
+"""
+Cowell handles the code necessary for creating AttributeFields on Entities. This can be used
+to give the appearance of fields added to models without the needing to change the schema or
+to define multiple models due to minor differences in requirements.
+"""
\ No newline at end of file
from django.db.models.fields import NOT_PROVIDED
from django.utils.text import capfirst
from philo.signals import entity_class_prepared
+from philo.models import ManyToManyValue, JSONValue, ForeignKeyValue, Attribute, Entity
__all__ = ('JSONAttribute', 'ForeignKeyAttribute', 'ManyToManyAttribute')
sender._entity_meta.add_proxy_field(self)
def contribute_to_class(self, cls, name):
- from philo.models.base import Entity
if issubclass(cls, Entity):
- self.name = name
+ self.name = self.attname = name
self.model = cls
if self.verbose_name is None and name:
self.verbose_name = name.replace('_', ' ')
return form_class(**defaults)
def value_from_object(self, obj):
+ """The return value of this method will be used by the EntityForm as
+ this field's initial value."""
return getattr(obj, self.name)
def has_default(self):
return self
if self.field.name not in instance.__dict__:
- instance.__dict__[self.field.name] = instance.attributes[self.field.attribute_key]
+ instance.__dict__[self.field.name] = instance.attributes.get(self.field.attribute_key, None)
return instance.__dict__[self.field.name]
registry = self.get_registry(instance)
registry['added'].add(self.field)
- registry['removed'].remove(self.field)
+ registry['removed'].discard(self.field)
def __delete__(self, instance):
del instance.__dict__[self.field.name]
registry = self.get_registry(instance)
- registry['added'].remove(self.field)
+ registry['added'].discard(self.field)
registry['removed'].add(self.field)
registry = instance.__dict__[ATTRIBUTE_REGISTRY]
instance.attribute_set.filter(key__in=[field.attribute_key for field in registry['removed']]).delete()
- from philo.models import Attribute
for field in registry['added']:
try:
- attribute = self.attribute_set.get(key=field.key)
+ attribute = instance.attribute_set.get(key=field.attribute_key)
except Attribute.DoesNotExist:
attribute = Attribute()
attribute.entity = instance
- attribute.key = field.key
+ attribute.key = field.attribute_key
- value_class = field.get_value_class()
+ value_class = field.value_class
if isinstance(attribute.value, value_class):
value = attribute.value
else:
attribute.value.delete()
value = value_class()
- value.set_value(field.value_from_object(instance))
+ value.set_value(getattr(instance, field.name, None))
value.save()
attribute.value = value
"Confirm that the value is valid or raise an appropriate error."
raise NotImplementedError("validate_value must be implemented by AttributeField subclasses.")
- def get_value_class(self):
- raise NotImplementedError("get_value_class must be implemented by AttributeField subclasses.")
+ @property
+ def value_class(self):
+ raise AttributeError("value_class must be defined on AttributeField subclasses.")
class JSONAttribute(AttributeField):
+ value_class = JSONValue
+
def __init__(self, field_template=None, **kwargs):
super(JSONAttribute, self).__init__(**kwargs)
if field_template is None:
defaults['initial'] = self.default
defaults.update(kwargs)
return self.field_template.formfield(**defaults)
-
- def get_value_class(self):
- from philo.models import JSONValue
- return JSONValue
-
- # Not sure what this is doing - keep eyes open!
- #def value_from_object(self, obj):
- # try:
- # return getattr(obj, self.name)
- # except AttributeError:
- # return None
class ForeignKeyAttribute(AttributeField):
+ value_class = ForeignKeyValue
+
def __init__(self, model, limit_choices_to=None, **kwargs):
super(ForeignKeyAttribute, self).__init__(**kwargs)
self.model = model
defaults.update(kwargs)
return super(ForeignKeyAttribute, self).formfield(form_class=form_class, **defaults)
- def get_value_class(self):
- from philo.models import ForeignKeyValue
- return ForeignKeyValue
-
- #def value_from_object(self, obj):
- # try:
- # relobj = super(ForeignKeyAttribute, self).value_from_object(obj)
- # except AttributeError:
- # return None
- # return getattr(relobj, 'pk', None)
+ def value_from_object(self, obj):
+ relobj = super(ForeignKeyAttribute, self).value_from_object(obj)
+ return getattr(relobj, 'pk', None)
class ManyToManyAttribute(ForeignKeyAttribute):
+ value_class = ManyToManyValue
+
def validate_value(self, value):
if not isinstance(value, models.query.QuerySet) or value.model != self.model:
raise TypeError("The '%s' attribute can only be set to a %s QuerySet." % (self.name, self.model.__name__))
def formfield(self, form_class=forms.ModelMultipleChoiceField, **kwargs):
return super(ManyToManyAttribute, self).formfield(form_class=form_class, **kwargs)
- def get_value_class(self):
- from philo.models import ManyToManyValue
- return ManyToManyValue
\ No newline at end of file
+ def value_from_object(self, obj):
+ qs = super(ForeignKeyAttribute, self).value_from_object(obj)
+ try:
+ return qs.values_list('pk', flat=True)
+ except:
+ return []
\ No newline at end of file
-from django import forms
-from django.contrib.contenttypes.generic import BaseGenericInlineFormSet
-from django.contrib.contenttypes.models import ContentType
from django.forms.models import ModelFormMetaclass, ModelForm
from django.utils.datastructures import SortedDict
-from philo.models import Attribute
from philo.utils import fattr
-__all__ = ('EntityForm', 'AttributeForm', 'AttributeInlineFormSet')
+__all__ = ('EntityForm',)
def proxy_fields_for_entity_model(entity_model, fields=None, exclude=None, widgets=None, formfield_callback=lambda f, **kwargs: f.formfield(**kwargs)):
else:
self.content_type = cleaned_data.get('content_type', None)
# If there is no value set in the cleaned data, clear the stored value.
- self.value = []
-
-class AttributeForm(ModelForm):
- """
- This class handles an attribute's fields as well as the fields for its value (if there is one.)
- The fields defined will vary depending on the value type, but the fields for defining the value
- (i.e. value_content_type and value_object_id) will always be defined. Except that value_object_id
- will never be defined. BLARGH!
- """
- def __init__(self, *args, **kwargs):
- super(AttributeForm, self).__init__(*args, **kwargs)
-
- # This is necessary because model forms store changes to self.instance in their clean method.
- # Mutter mutter.
- value = self.instance.value
- self._cached_value_ct = self.instance.value_content_type
- self._cached_value = value
-
- # If there is a value, pull in its fields.
- if value is not None:
- self.value_fields = value.value_formfields()
- self.fields.update(self.value_fields)
-
- def save(self, *args, **kwargs):
- # At this point, the cleaned_data has already been stored on self.instance.
-
- if self.instance.value_content_type != self._cached_value_ct:
- # The value content type has changed. Clear the old value, if there was one.
- if self._cached_value:
- self._cached_value.delete()
-
- # Clear the submitted value, if any.
- self.cleaned_data.pop('value', None)
-
- # Now create a new value instance so that on next instantiation, the form will
- # know what fields to add.
- if self.instance.value_content_type is not None:
- self.instance.value = self.instance.value_content_type.model_class().objects.create()
- elif self.instance.value is not None:
- # The value content type is the same, but one of the value fields has changed.
-
- # Use construct_instance to apply the changes from the cleaned_data to the value instance.
- fields = self.value_fields.keys()
- if set(fields) & set(self.changed_data):
- self.instance.value.construct_instance(**dict([(key, self.cleaned_data[key]) for key in fields]))
- self.instance.value.save()
-
- return super(AttributeForm, self).save(*args, **kwargs)
-
- class Meta:
- model = Attribute
-
-
-class AttributeInlineFormSet(BaseGenericInlineFormSet):
- "Necessary to force the GenericInlineFormset to use the form's save method for new objects."
- def save_new(self, form, commit):
- setattr(form.instance, self.ct_field.get_attname(), ContentType.objects.get_for_model(self.instance).pk)
- setattr(form.instance, self.ct_fk_field.get_attname(), self.instance.pk)
- return form.save()
\ No newline at end of file
+ self.value = []
\ No newline at end of file
+from philo.forms.fields import *
\ No newline at end of file
content_type = models.ForeignKey(ContentType, limit_choices_to=value_content_type_limiter, verbose_name='Value type', null=True, blank=True)
values = models.ManyToManyField(ForeignKeyValue, blank=True, null=True)
- def get_object_id_list(self):
- if not self.values.count():
- return []
- else:
- return self.values.values_list('object_id', flat=True)
-
- def get_value(self):
- if self.content_type is None:
- return None
-
- return self.content_type.model_class()._default_manager.filter(id__in=self.get_object_id_list())
+ def get_object_ids(self):
+ return self.values.values_list('object_id', flat=True)
+ object_ids = property(get_object_ids)
def set_value(self, value):
- # Value is probably a queryset - but allow any iterable.
+ # Value must be a queryset. Watch out for ModelMultipleChoiceField;
+ # it returns its value as a list if empty.
- # These lines shouldn't be necessary; however, if value is an EmptyQuerySet,
- # the code (specifically the object_id__in query) won't work without them. Unclear why...
- if not value:
- value = []
+ self.content_type = ContentType.objects.get_for_model(value.model)
# Before we can fiddle with the many-to-many to foreignkeyvalues, we need
# a pk.
if self.pk is None:
self.save()
- if isinstance(value, models.query.QuerySet):
- value = value.values_list('id', flat=True)
+ object_ids = value.values_list('id', flat=True)
- self.values.filter(~models.Q(object_id__in=value)).delete()
- current = self.get_object_id_list()
+ # These lines shouldn't be necessary; however, if object_ids is an EmptyQuerySet,
+ # the code (specifically the object_id__in query) won't work without them. Unclear why...
+ # TODO: is this still the case?
+ if not object_ids:
+ self.values.all().delete()
+ else:
+ self.values.exclude(object_id__in=object_ids, content_type=self.content_type).delete()
+
+ current_ids = self.object_ids
+
+ for object_id in object_ids:
+ if object_id in current_ids:
+ continue
+ self.values.create(content_type=self.content_type, object_id=object_id)
+
+ def get_value(self):
+ if self.content_type is None:
+ return None
- for v in value:
- if v in current:
- continue
- self.values.create(content_type=self.content_type, object_id=v)
+ # HACK to be safely explicit until http://code.djangoproject.com/ticket/15145 is resolved
+ object_ids = self.object_ids
+ manager = self.content_type.model_class()._default_manager
+ if not object_ids:
+ return manager.none()
+ return manager.filter(id__in=self.object_ids)
value = property(get_value, set_value)
if self.content_type:
kwargs = {
- 'initial': self.get_object_id_list(),
+ 'initial': self.object_ids,
'required': False,
'queryset': self.content_type.model_class()._default_manager.all()
}
self.values.clear()
self.content_type = ct
else:
- value = kwargs.get('value', self.content_type.model_class()._default_manager.none())
+ value = kwargs.get('value', None)
+ if not value:
+ value = self.content_type.model_class()._default_manager.none()
self.set_value(value)
construct_instance.alters_data = True
from django.utils import simplejson as json
from philo.forms.fields import JSONFormField
from philo.validators import TemplateValidator, json_validator
-from philo.models.fields.attributes import *
class TemplateField(models.TextField):