from django.utils import simplejson as json
from django.utils.html import escape
from philo.models import Tag, Attribute
-from philo.forms 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
--- /dev/null
+from django import forms
+from django.contrib.admin.widgets import AdminTextareaWidget
+from django.core.exceptions import ObjectDoesNotExist
+from django.db.models import Q
+from django.forms.models import ModelForm, BaseInlineFormSet
+from django.forms.formsets import TOTAL_FORM_COUNT
+from philo.admin.widgets import ModelLookupWidget
+from philo.models import Contentlet, ContentReference
+
+
+__all__ = (
+ 'ContentletForm',
+ 'ContentletInlineFormSet',
+ 'ContentReferenceForm',
+ 'ContentReferenceInlineFormSet'
+)
+
+
+class ContainerForm(ModelForm):
+ def __init__(self, *args, **kwargs):
+ super(ContainerForm, self).__init__(*args, **kwargs)
+ self.verbose_name = self.instance.name.replace('_', ' ')
+
+
+class ContentletForm(ContainerForm):
+ content = forms.CharField(required=False, widget=AdminTextareaWidget, label='Content')
+
+ def should_delete(self):
+ return not bool(self.cleaned_data['content'])
+
+ class Meta:
+ model = Contentlet
+ fields = ['name', 'content']
+
+
+class ContentReferenceForm(ContainerForm):
+ def __init__(self, *args, **kwargs):
+ super(ContentReferenceForm, self).__init__(*args, **kwargs)
+ try:
+ self.fields['content_id'].widget = ModelLookupWidget(self.instance.content_type)
+ except ObjectDoesNotExist:
+ # This will happen when an empty form (which we will never use) gets instantiated.
+ pass
+
+ def should_delete(self):
+ return (self.cleaned_data['content_id'] is None)
+
+ class Meta:
+ model = ContentReference
+ fields = ['name', 'content_id']
+
+
+class ContainerInlineFormSet(BaseInlineFormSet):
+ def __init__(self, containers, data=None, files=None, instance=None, save_as_new=False, prefix=None, queryset=None):
+ # Unfortunately, I need to add some things to BaseInline between its __init__ and its
+ # super call, so a lot of this is repetition.
+
+ # Start cribbed from BaseInline
+ from django.db.models.fields.related import RelatedObject
+ self.save_as_new = save_as_new
+ # is there a better way to get the object descriptor?
+ self.rel_name = RelatedObject(self.fk.rel.to, self.model, self.fk).get_accessor_name()
+ if self.fk.rel.field_name == self.fk.rel.to._meta.pk.name:
+ backlink_value = self.instance
+ else:
+ backlink_value = getattr(self.instance, self.fk.rel.field_name)
+ if queryset is None:
+ queryset = self.model._default_manager
+ qs = queryset.filter(**{self.fk.name: backlink_value})
+ # End cribbed from BaseInline
+
+ self.container_instances, qs = self.get_container_instances(containers, qs)
+ self.extra_containers = containers
+ self.extra = len(self.extra_containers)
+ super(BaseInlineFormSet, self).__init__(data, files, prefix=prefix, queryset=qs)
+
+ def get_container_instances(self, containers, qs):
+ raise NotImplementedError
+
+ def total_form_count(self):
+ if self.data or self.files:
+ return self.management_form.cleaned_data[TOTAL_FORM_COUNT]
+ else:
+ return self.initial_form_count() + self.extra
+
+ def save_existing_objects(self, commit=True):
+ self.changed_objects = []
+ self.deleted_objects = []
+ if not self.get_queryset():
+ return []
+
+ saved_instances = []
+ for form in self.initial_forms:
+ pk_name = self._pk_field.name
+ raw_pk_value = form._raw_value(pk_name)
+
+ # clean() for different types of PK fields can sometimes return
+ # the model instance, and sometimes the PK. Handle either.
+ pk_value = form.fields[pk_name].clean(raw_pk_value)
+ pk_value = getattr(pk_value, 'pk', pk_value)
+
+ obj = self._existing_object(pk_value)
+ if form.should_delete():
+ self.deleted_objects.append(obj)
+ obj.delete()
+ continue
+ if form.has_changed():
+ self.changed_objects.append((obj, form.changed_data))
+ saved_instances.append(self.save_existing(form, obj, commit=commit))
+ if not commit:
+ self.saved_forms.append(form)
+ return saved_instances
+
+ def save_new_objects(self, commit=True):
+ self.new_objects = []
+ for form in self.extra_forms:
+ if not form.has_changed():
+ continue
+ # If someone has marked an add form for deletion, don't save the
+ # object.
+ if form.should_delete():
+ continue
+ self.new_objects.append(self.save_new(form, commit=commit))
+ if not commit:
+ self.saved_forms.append(form)
+ return self.new_objects
+
+
+class ContentletInlineFormSet(ContainerInlineFormSet):
+ def __init__(self, data=None, files=None, instance=None, save_as_new=False, prefix=None, queryset=None):
+ if instance is None:
+ self.instance = self.fk.rel.to()
+ else:
+ self.instance = instance
+
+ try:
+ containers = list(self.instance.containers[0])
+ except ObjectDoesNotExist:
+ containers = []
+
+ super(ContentletInlineFormSet, self).__init__(containers, data, files, instance, save_as_new, prefix, queryset)
+
+ def get_container_instances(self, containers, qs):
+ qs = qs.filter(name__in=containers)
+ container_instances = []
+ for container in qs:
+ container_instances.append(container)
+ containers.remove(container.name)
+ return container_instances, qs
+
+ def _construct_form(self, i, **kwargs):
+ if i >= self.initial_form_count(): # and not kwargs.get('instance'):
+ kwargs['instance'] = self.model(name=self.extra_containers[i - self.initial_form_count() - 1])
+
+ return super(ContentletInlineFormSet, self)._construct_form(i, **kwargs)
+
+
+class ContentReferenceInlineFormSet(ContainerInlineFormSet):
+ def __init__(self, data=None, files=None, instance=None, save_as_new=False, prefix=None, queryset=None):
+ if instance is None:
+ self.instance = self.fk.rel.to()
+ else:
+ self.instance = instance
+
+ try:
+ containers = list(self.instance.containers[1])
+ except ObjectDoesNotExist:
+ containers = []
+
+ super(ContentReferenceInlineFormSet, self).__init__(containers, data, files, instance, save_as_new, prefix, queryset)
+
+ def get_container_instances(self, containers, qs):
+ filter = Q()
+
+ for name, ct in containers:
+ filter |= Q(name=name, content_type=ct)
+
+ qs = qs.filter(filter)
+ container_instances = []
+ for container in qs:
+ container_instances.append(container)
+ containers.remove((container.name, container.content_type))
+ return container_instances, qs
+
+ def _construct_form(self, i, **kwargs):
+ if i >= self.initial_form_count(): # and not kwargs.get('instance'):
+ name, content_type = self.extra_containers[i - self.initial_form_count() - 1]
+ kwargs['instance'] = self.model(name=name, content_type=content_type)
+
+ return super(ContentReferenceInlineFormSet, self)._construct_form(i, **kwargs)
\ 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 import ContentletInlineFormSet, ContentReferenceInlineFormSet, ContentletForm, ContentReferenceForm
+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
--- /dev/null
+"""
+The Attributes defined in this file can be assigned as fields on a proxy of
+a subclass of philo.models.Entity. They act like any other model fields,
+but instead of saving their data to the database, they save it to
+attributes related to a model instance. Additionally, a new attribute will
+be created for an instance if and only if the field's value has been set.
+This is relevant i.e. for passthroughs, where the value of the field may
+be defined by some other instance's attributes.
+
+Example::
+
+ class Thing(Entity):
+ numbers = models.PositiveIntegerField()
+
+ class ThingProxy(Thing):
+ improvised = JSONAttribute(models.BooleanField)
+
+ class Meta:
+ proxy = True
+"""
+from django import forms
+from django.core.exceptions import FieldError
+from django.db import models
+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')
+
+
+ATTRIBUTE_REGISTRY = '_attribute_registry'
+
+
+class EntityProxyField(object):
+ def __init__(self, verbose_name=None, help_text=None, default=NOT_PROVIDED, editable=True, *args, **kwargs):
+ self.verbose_name = verbose_name
+ self.help_text = help_text
+ self.default = default
+ self.editable = editable
+
+ def actually_contribute_to_class(self, sender, **kwargs):
+ sender._entity_meta.add_proxy_field(self)
+
+ def contribute_to_class(self, cls, name):
+ if issubclass(cls, Entity):
+ self.name = self.attname = name
+ self.model = cls
+ if self.verbose_name is None and name:
+ self.verbose_name = name.replace('_', ' ')
+ entity_class_prepared.connect(self.actually_contribute_to_class, sender=cls)
+ else:
+ raise FieldError('%s instances can only be declared on Entity subclasses.' % self.__class__.__name__)
+
+ def formfield(self, form_class=forms.CharField, **kwargs):
+ defaults = {
+ 'required': False,
+ 'label': capfirst(self.verbose_name),
+ 'help_text': self.help_text
+ }
+ if self.has_default():
+ defaults['initial'] = self.default
+ defaults.update(kwargs)
+ 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.default is not NOT_PROVIDED
+
+
+class AttributeFieldDescriptor(object):
+ def __init__(self, field):
+ self.field = field
+
+ def get_registry(self, instance):
+ if ATTRIBUTE_REGISTRY not in instance.__dict__:
+ instance.__dict__[ATTRIBUTE_REGISTRY] = {'added': set(), 'removed': set()}
+ return instance.__dict__[ATTRIBUTE_REGISTRY]
+
+ def __get__(self, instance, owner):
+ if instance is None:
+ return self
+
+ if self.field.name not in instance.__dict__:
+ instance.__dict__[self.field.name] = instance.attributes.get(self.field.attribute_key, None)
+
+ return instance.__dict__[self.field.name]
+
+ def __set__(self, instance, value):
+ if instance is None:
+ raise AttributeError("%s must be accessed via instance" % self.field.name)
+
+ self.field.validate_value(value)
+ instance.__dict__[self.field.name] = value
+
+ registry = self.get_registry(instance)
+ registry['added'].add(self.field)
+ registry['removed'].discard(self.field)
+
+ def __delete__(self, instance):
+ del instance.__dict__[self.field.name]
+
+ registry = self.get_registry(instance)
+ registry['added'].discard(self.field)
+ registry['removed'].add(self.field)
+
+
+def process_attribute_fields(sender, instance, created, **kwargs):
+ if ATTRIBUTE_REGISTRY in instance.__dict__:
+ registry = instance.__dict__[ATTRIBUTE_REGISTRY]
+ instance.attribute_set.filter(key__in=[field.attribute_key for field in registry['removed']]).delete()
+
+ for field in registry['added']:
+ try:
+ attribute = instance.attribute_set.get(key=field.attribute_key)
+ except Attribute.DoesNotExist:
+ attribute = Attribute()
+ attribute.entity = instance
+ attribute.key = field.attribute_key
+
+ value_class = field.value_class
+ if isinstance(attribute.value, value_class):
+ value = attribute.value
+ else:
+ if isinstance(attribute.value, models.Model):
+ attribute.value.delete()
+ value = value_class()
+
+ value.set_value(getattr(instance, field.name, None))
+ value.save()
+
+ attribute.value = value
+ attribute.save()
+ del instance.__dict__[ATTRIBUTE_REGISTRY]
+
+
+class AttributeField(EntityProxyField):
+ def __init__(self, attribute_key=None, **kwargs):
+ self.attribute_key = attribute_key
+ super(AttributeField, self).__init__(**kwargs)
+
+ def actually_contribute_to_class(self, sender, **kwargs):
+ super(AttributeField, self).actually_contribute_to_class(sender, **kwargs)
+ setattr(sender, self.name, AttributeFieldDescriptor(self))
+ opts = sender._entity_meta
+ if not hasattr(opts, '_has_attribute_fields'):
+ opts._has_attribute_fields = True
+ models.signals.post_save.connect(process_attribute_fields, sender=sender)
+
+
+ def contribute_to_class(self, cls, name):
+ if self.attribute_key is None:
+ self.attribute_key = name
+ super(AttributeField, self).contribute_to_class(cls, name)
+
+ def validate_value(self, value):
+ "Confirm that the value is valid or raise an appropriate error."
+ raise NotImplementedError("validate_value 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:
+ field_template = models.CharField(max_length=255)
+ self.field_template = field_template
+
+ def validate_value(self, value):
+ pass
+
+ def formfield(self, **kwargs):
+ defaults = {
+ 'required': False,
+ 'label': capfirst(self.verbose_name),
+ 'help_text': self.help_text
+ }
+ if self.has_default():
+ defaults['initial'] = self.default
+ defaults.update(kwargs)
+ return self.field_template.formfield(**defaults)
+
+
+class ForeignKeyAttribute(AttributeField):
+ value_class = ForeignKeyValue
+
+ def __init__(self, model, limit_choices_to=None, **kwargs):
+ super(ForeignKeyAttribute, self).__init__(**kwargs)
+ self.model = model
+ if limit_choices_to is None:
+ limit_choices_to = {}
+ self.limit_choices_to = limit_choices_to
+
+ def validate_value(self, value):
+ if value is not None and not isinstance(value, self.model) :
+ raise TypeError("The '%s' attribute can only be set to an instance of %s or None." % (self.name, self.model.__name__))
+
+ def formfield(self, form_class=forms.ModelChoiceField, **kwargs):
+ defaults = {
+ 'queryset': self.model._default_manager.complex_filter(self.limit_choices_to)
+ }
+ defaults.update(kwargs)
+ return super(ForeignKeyAttribute, self).formfield(form_class=form_class, **defaults)
+
+ 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 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
--- /dev/null
+from django.forms.models import ModelFormMetaclass, ModelForm
+from django.utils.datastructures import SortedDict
+from philo.utils import fattr
+
+
+__all__ = ('EntityForm',)
+
+
+def proxy_fields_for_entity_model(entity_model, fields=None, exclude=None, widgets=None, formfield_callback=lambda f, **kwargs: f.formfield(**kwargs)):
+ field_list = []
+ ignored = []
+ opts = entity_model._entity_meta
+ for f in opts.proxy_fields:
+ if not f.editable:
+ continue
+ if fields and not f.name in fields:
+ continue
+ if exclude and f.name in exclude:
+ continue
+ if widgets and f.name in widgets:
+ kwargs = {'widget': widgets[f.name]}
+ else:
+ kwargs = {}
+ formfield = formfield_callback(f, **kwargs)
+ if formfield:
+ field_list.append((f.name, formfield))
+ else:
+ ignored.append(f.name)
+ field_dict = SortedDict(field_list)
+ if fields:
+ field_dict = SortedDict(
+ [(f, field_dict.get(f)) for f in fields
+ if ((not exclude) or (exclude and f not in exclude)) and (f not in ignored) and (f in field_dict)]
+ )
+ return field_dict
+
+
+# BEGIN HACK - This will not be required after http://code.djangoproject.com/ticket/14082 has been resolved
+
+class EntityFormBase(ModelForm):
+ pass
+
+_old_metaclass_new = ModelFormMetaclass.__new__
+
+def _new_metaclass_new(cls, name, bases, attrs):
+ new_class = _old_metaclass_new(cls, name, bases, attrs)
+ if issubclass(new_class, EntityFormBase) and new_class._meta.model:
+ new_class.base_fields.update(proxy_fields_for_entity_model(new_class._meta.model, new_class._meta.fields, new_class._meta.exclude, new_class._meta.widgets)) # don't pass in formfield_callback
+ return new_class
+
+ModelFormMetaclass.__new__ = staticmethod(_new_metaclass_new)
+
+# END HACK
+
+
+class EntityForm(EntityFormBase): # Would inherit from ModelForm directly if it weren't for the above HACK
+ def __init__(self, *args, **kwargs):
+ initial = kwargs.pop('initial', None)
+ instance = kwargs.get('instance', None)
+ if instance is not None:
+ new_initial = {}
+ for f in instance._entity_meta.proxy_fields:
+ if self._meta.fields and not f.name in self._meta.fields:
+ continue
+ if self._meta.exclude and f.name in self._meta.exclude:
+ continue
+ new_initial[f.name] = f.value_from_object(instance)
+ else:
+ new_initial = {}
+ if initial is not None:
+ new_initial.update(initial)
+ kwargs['initial'] = new_initial
+ super(EntityForm, self).__init__(*args, **kwargs)
+
+ @fattr(alters_data=True)
+ def save(self, commit=True):
+ cleaned_data = self.cleaned_data
+ instance = super(EntityForm, self).save(commit=False)
+
+ for f in instance._entity_meta.proxy_fields:
+ if not f.editable or not f.name in cleaned_data:
+ continue
+ if self._meta.fields and f.name not in self._meta.fields:
+ continue
+ if self._meta.exclude and f.name in self._meta.exclude:
+ continue
+ setattr(instance, f.attname, cleaned_data[f.name])
+
+ if commit:
+ instance.save()
+ self.save_m2m()
+
+ return instance
+
+
+ def apply_data(self, cleaned_data):
+ self.value = cleaned_data.get('value', None)
+
+ def apply_data(self, cleaned_data):
+ if 'value' in cleaned_data and cleaned_data['value'] is not None:
+ self.value = cleaned_data['value']
+ 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.object_id = None
+
+ def apply_data(self, cleaned_data):
+ if 'value' in cleaned_data and cleaned_data['value'] is not None:
+ self.value = cleaned_data['value']
+ 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 = []
\ No newline at end of file
+++ /dev/null
-from django import forms
-from django.contrib.admin.widgets import AdminTextareaWidget
-from django.contrib.contenttypes.generic import BaseGenericInlineFormSet
-from django.contrib.contenttypes.models import ContentType
-from django.core.exceptions import ValidationError, ObjectDoesNotExist
-from django.db.models import Q
-from django.forms.models import model_to_dict, fields_for_model, ModelFormMetaclass, ModelForm, BaseInlineFormSet
-from django.forms.formsets import TOTAL_FORM_COUNT
-from django.template import loader, loader_tags, TemplateDoesNotExist, Context, Template as DjangoTemplate
-from django.utils.datastructures import SortedDict
-from philo.admin.widgets import ModelLookupWidget
-from philo.models import Entity, Template, Contentlet, ContentReference, Attribute
-from philo.utils import fattr
-
-
-__all__ = ('EntityForm', )
-
-
-def proxy_fields_for_entity_model(entity_model, fields=None, exclude=None, widgets=None, formfield_callback=lambda f, **kwargs: f.formfield(**kwargs)):
- field_list = []
- ignored = []
- opts = entity_model._entity_meta
- for f in opts.proxy_fields:
- if not f.editable:
- continue
- if fields and not f.name in fields:
- continue
- if exclude and f.name in exclude:
- continue
- if widgets and f.name in widgets:
- kwargs = {'widget': widgets[f.name]}
- else:
- kwargs = {}
- formfield = formfield_callback(f, **kwargs)
- if formfield:
- field_list.append((f.name, formfield))
- else:
- ignored.append(f.name)
- field_dict = SortedDict(field_list)
- if fields:
- field_dict = SortedDict(
- [(f, field_dict.get(f)) for f in fields
- if ((not exclude) or (exclude and f not in exclude)) and (f not in ignored) and (f in field_dict)]
- )
- return field_dict
-
-
-# BEGIN HACK - This will not be required after http://code.djangoproject.com/ticket/14082 has been resolved
-
-class EntityFormBase(ModelForm):
- pass
-
-_old_metaclass_new = ModelFormMetaclass.__new__
-
-def _new_metaclass_new(cls, name, bases, attrs):
- new_class = _old_metaclass_new(cls, name, bases, attrs)
- if issubclass(new_class, EntityFormBase) and new_class._meta.model:
- new_class.base_fields.update(proxy_fields_for_entity_model(new_class._meta.model, new_class._meta.fields, new_class._meta.exclude, new_class._meta.widgets)) # don't pass in formfield_callback
- return new_class
-
-ModelFormMetaclass.__new__ = staticmethod(_new_metaclass_new)
-
-# END HACK
-
-
-class EntityForm(EntityFormBase): # Would inherit from ModelForm directly if it weren't for the above HACK
- def __init__(self, *args, **kwargs):
- initial = kwargs.pop('initial', None)
- instance = kwargs.get('instance', None)
- if instance is not None:
- new_initial = {}
- for f in instance._entity_meta.proxy_fields:
- if self._meta.fields and not f.name in self._meta.fields:
- continue
- if self._meta.exclude and f.name in self._meta.exclude:
- continue
- new_initial[f.name] = f.value_from_object(instance)
- else:
- new_initial = {}
- if initial is not None:
- new_initial.update(initial)
- kwargs['initial'] = new_initial
- super(EntityForm, self).__init__(*args, **kwargs)
-
- @fattr(alters_data=True)
- def save(self, commit=True):
- cleaned_data = self.cleaned_data
- instance = super(EntityForm, self).save(commit=False)
-
- for f in instance._entity_meta.proxy_fields:
- if not f.editable or not f.name in cleaned_data:
- continue
- if self._meta.fields and f.name not in self._meta.fields:
- continue
- if self._meta.exclude and f.name in self._meta.exclude:
- continue
- setattr(instance, f.attname, cleaned_data[f.name])
-
- if commit:
- instance.save()
- self.save_m2m()
-
- return instance
-
-
-class AttributeForm(ModelForm):
- 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.
- self._cached_value_ct = self.instance.value_content_type
- self._cached_value = self.instance.value
-
- if self.instance.value is not None:
- value_field = self.instance.value.value_formfield()
- if value_field:
- self.fields['value'] = value_field
- if hasattr(self.instance.value, 'content_type'):
- self.fields['content_type'] = self.instance.value._meta.get_field('content_type').formfield(initial=getattr(self.instance.value.content_type, 'pk', None))
-
- 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:
- if self.instance.value is not None:
- self._cached_value.delete()
- if 'value' in self.cleaned_data:
- del(self.cleaned_data['value'])
-
- if self.instance.value_content_type is not None:
- # Make a blank value of the new type! Run special code for content_type attributes.
- if hasattr(self.instance.value_content_type.model_class(), 'content_type'):
- if self._cached_value and hasattr(self._cached_value, 'content_type'):
- new_ct = self._cached_value.content_type
- else:
- new_ct = None
- new_value = self.instance.value_content_type.model_class().objects.create(content_type=new_ct)
- else:
- new_value = self.instance.value_content_type.model_class().objects.create()
-
- new_value.apply_data(self.cleaned_data)
- new_value.save()
- self.instance.value = new_value
- else:
- # The value type is the same, but one of the fields has changed.
- # Check to see if the changed value was the content type. We have to check the
- # cleaned_data because self.instance.value.content_type was overridden.
- if hasattr(self.instance.value, 'content_type') and 'content_type' in self.cleaned_data and 'value' in self.cleaned_data and (not hasattr(self._cached_value, 'content_type') or self._cached_value.content_type != self.cleaned_data['content_type']):
- self.cleaned_data['value'] = None
-
- self.instance.value.apply_data(self.cleaned_data)
- self.instance.value.save()
-
- super(AttributeForm, self).save(*args, **kwargs)
- return self.instance
-
- 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()
-
-
-class ContainerForm(ModelForm):
- def __init__(self, *args, **kwargs):
- super(ContainerForm, self).__init__(*args, **kwargs)
- self.verbose_name = self.instance.name.replace('_', ' ')
-
-
-class ContentletForm(ContainerForm):
- content = forms.CharField(required=False, widget=AdminTextareaWidget, label='Content')
-
- def should_delete(self):
- return not bool(self.cleaned_data['content'])
-
- class Meta:
- model = Contentlet
- fields = ['name', 'content']
-
-
-class ContentReferenceForm(ContainerForm):
- def __init__(self, *args, **kwargs):
- super(ContentReferenceForm, self).__init__(*args, **kwargs)
- try:
- self.fields['content_id'].widget = ModelLookupWidget(self.instance.content_type)
- except ObjectDoesNotExist:
- # This will happen when an empty form (which we will never use) gets instantiated.
- pass
-
- def should_delete(self):
- return (self.cleaned_data['content_id'] is None)
-
- class Meta:
- model = ContentReference
- fields = ['name', 'content_id']
-
-
-class ContainerInlineFormSet(BaseInlineFormSet):
- def __init__(self, containers, data=None, files=None, instance=None, save_as_new=False, prefix=None, queryset=None):
- # Unfortunately, I need to add some things to BaseInline between its __init__ and its
- # super call, so a lot of this is repetition.
-
- # Start cribbed from BaseInline
- from django.db.models.fields.related import RelatedObject
- self.save_as_new = save_as_new
- # is there a better way to get the object descriptor?
- self.rel_name = RelatedObject(self.fk.rel.to, self.model, self.fk).get_accessor_name()
- if self.fk.rel.field_name == self.fk.rel.to._meta.pk.name:
- backlink_value = self.instance
- else:
- backlink_value = getattr(self.instance, self.fk.rel.field_name)
- if queryset is None:
- queryset = self.model._default_manager
- qs = queryset.filter(**{self.fk.name: backlink_value})
- # End cribbed from BaseInline
-
- self.container_instances, qs = self.get_container_instances(containers, qs)
- self.extra_containers = containers
- self.extra = len(self.extra_containers)
- super(BaseInlineFormSet, self).__init__(data, files, prefix=prefix, queryset=qs)
-
- def get_container_instances(self, containers, qs):
- raise NotImplementedError
-
- def total_form_count(self):
- if self.data or self.files:
- return self.management_form.cleaned_data[TOTAL_FORM_COUNT]
- else:
- return self.initial_form_count() + self.extra
-
- def save_existing_objects(self, commit=True):
- self.changed_objects = []
- self.deleted_objects = []
- if not self.get_queryset():
- return []
-
- saved_instances = []
- for form in self.initial_forms:
- pk_name = self._pk_field.name
- raw_pk_value = form._raw_value(pk_name)
-
- # clean() for different types of PK fields can sometimes return
- # the model instance, and sometimes the PK. Handle either.
- pk_value = form.fields[pk_name].clean(raw_pk_value)
- pk_value = getattr(pk_value, 'pk', pk_value)
-
- obj = self._existing_object(pk_value)
- if form.should_delete():
- self.deleted_objects.append(obj)
- obj.delete()
- continue
- if form.has_changed():
- self.changed_objects.append((obj, form.changed_data))
- saved_instances.append(self.save_existing(form, obj, commit=commit))
- if not commit:
- self.saved_forms.append(form)
- return saved_instances
-
- def save_new_objects(self, commit=True):
- self.new_objects = []
- for form in self.extra_forms:
- if not form.has_changed():
- continue
- # If someone has marked an add form for deletion, don't save the
- # object.
- if form.should_delete():
- continue
- self.new_objects.append(self.save_new(form, commit=commit))
- if not commit:
- self.saved_forms.append(form)
- return self.new_objects
-
-
-class ContentletInlineFormSet(ContainerInlineFormSet):
- def __init__(self, data=None, files=None, instance=None, save_as_new=False, prefix=None, queryset=None):
- if instance is None:
- self.instance = self.fk.rel.to()
- else:
- self.instance = instance
-
- try:
- containers = list(self.instance.containers[0])
- except ObjectDoesNotExist:
- containers = []
-
- super(ContentletInlineFormSet, self).__init__(containers, data, files, instance, save_as_new, prefix, queryset)
-
- def get_container_instances(self, containers, qs):
- qs = qs.filter(name__in=containers)
- container_instances = []
- for container in qs:
- container_instances.append(container)
- containers.remove(container.name)
- return container_instances, qs
-
- def _construct_form(self, i, **kwargs):
- if i >= self.initial_form_count(): # and not kwargs.get('instance'):
- kwargs['instance'] = self.model(name=self.extra_containers[i - self.initial_form_count() - 1])
-
- return super(ContentletInlineFormSet, self)._construct_form(i, **kwargs)
-
-
-class ContentReferenceInlineFormSet(ContainerInlineFormSet):
- def __init__(self, data=None, files=None, instance=None, save_as_new=False, prefix=None, queryset=None):
- if instance is None:
- self.instance = self.fk.rel.to()
- else:
- self.instance = instance
-
- try:
- containers = list(self.instance.containers[1])
- except ObjectDoesNotExist:
- containers = []
-
- super(ContentReferenceInlineFormSet, self).__init__(containers, data, files, instance, save_as_new, prefix, queryset)
-
- def get_container_instances(self, containers, qs):
- filter = Q()
-
- for name, ct in containers:
- filter |= Q(name=name, content_type=ct)
-
- qs = qs.filter(filter)
- container_instances = []
- for container in qs:
- container_instances.append(container)
- containers.remove((container.name, container.content_type))
- return container_instances, qs
-
- def _construct_form(self, i, **kwargs):
- if i >= self.initial_form_count(): # and not kwargs.get('instance'):
- name, content_type = self.extra_containers[i - self.initial_form_count() - 1]
- kwargs['instance'] = self.model(name=name, content_type=content_type)
-
- return super(ContentReferenceInlineFormSet, self)._construct_form(i, **kwargs)
\ No newline at end of file
--- /dev/null
+from philo.forms.fields import *
\ No newline at end of file
--- /dev/null
+from django import forms
+from django.utils import simplejson as json
+from philo.validators import json_validator
+
+
+__all__ = ('JSONFormField',)
+
+
+class JSONFormField(forms.Field):
+ default_validators = [json_validator]
+
+ def clean(self, value):
+ if value == '' and not self.required:
+ return None
+ try:
+ return json.loads(value)
+ except Exception, e:
+ raise ValidationError(u'JSON decode error: %s' % e)
\ No newline at end of file
def attribute(self):
return self.attribute_set.all()[0]
- def apply_data(self, data):
+ def set_value(self, value):
+ raise NotImplementedError
+
+ def value_formfields(self, **kwargs):
+ """Define any formfields that would be used to construct an instance of this value."""
raise NotImplementedError
- def value_formfield(self, **kwargs):
+ def construct_instance(self, **kwargs):
+ """Apply cleaned data from the formfields generated by valid_formfields to oneself."""
raise NotImplementedError
def __unicode__(self):
class JSONValue(AttributeValue):
- value = JSONField() #verbose_name='Value (JSON)', help_text='This value must be valid JSON.')
+ value = JSONField(verbose_name='Value (JSON)', help_text='This value must be valid JSON.', default='null')
def __unicode__(self):
return smart_str(self.value)
- def value_formfield(self, **kwargs):
- kwargs['initial'] = self.value_json
- return self._meta.get_field('value').formfield(**kwargs)
+ def value_formfields(self):
+ kwargs = {'initial': self.value_json}
+ field = self._meta.get_field('value')
+ return {field.name: field.formfield(**kwargs)}
- def apply_data(self, cleaned_data):
- self.value = cleaned_data.get('value', None)
+ def construct_instance(self, **kwargs):
+ field_name = self._meta.get_field('value').name
+ self.set_value(kwargs.pop(field_name, None))
+
+ def set_value(self, value):
+ self.value = value
class Meta:
app_label = 'philo'
object_id = models.PositiveIntegerField(verbose_name='Value ID', null=True, blank=True)
value = generic.GenericForeignKey()
- def value_formfield(self, form_class=forms.ModelChoiceField, **kwargs):
- if self.content_type is None:
- return None
- kwargs.update({'initial': self.object_id, 'required': False})
- return form_class(self.content_type.model_class()._default_manager.all(), **kwargs)
-
- def apply_data(self, cleaned_data):
- if 'value' in cleaned_data and cleaned_data['value'] is not None:
- self.value = cleaned_data['value']
- else:
- self.content_type = cleaned_data.get('content_type', None)
- # If there is no value set in the cleaned data, clear the stored value.
+ def value_formfields(self):
+ field = self._meta.get_field('content_type')
+ fields = {field.name: field.formfield(initial=getattr(self.content_type, 'pk', None))}
+
+ if self.content_type:
+ kwargs = {
+ 'initial': self.object_id,
+ 'required': False,
+ 'queryset': self.content_type.model_class()._default_manager.all()
+ }
+ fields['value'] = forms.ModelChoiceField(**kwargs)
+ return fields
+
+ def construct_instance(self, **kwargs):
+ field_name = self._meta.get_field('content_type').name
+ ct = kwargs.pop(field_name, None)
+ if ct is None or ct != self.content_type:
self.object_id = None
+ self.content_type = ct
+ else:
+ value = kwargs.pop('value', None)
+ self.set_value(value)
+ if value is None:
+ self.content_type = ct
+
+ def set_value(self, value):
+ self.value = value
class Meta:
app_label = 'philo'
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)
-
- self.values.filter(~models.Q(object_id__in=value)).delete()
- current = self.get_object_id_list()
+ object_ids = value.values_list('id', flat=True)
- for v in value:
- if v in current:
- continue
- self.values.create(content_type=self.content_type, object_id=v)
-
- value = property(get_value, set_value)
+ # 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 value_formfield(self, form_class=forms.ModelMultipleChoiceField, **kwargs):
+ def get_value(self):
if self.content_type is None:
return None
- kwargs.update({'initial': self.get_object_id_list(), 'required': False})
- return form_class(self.content_type.model_class()._default_manager.all(), **kwargs)
+
+ # 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)
- def apply_data(self, cleaned_data):
- if 'value' in cleaned_data and cleaned_data['value'] is not None:
- self.value = cleaned_data['value']
+ def value_formfields(self):
+ field = self._meta.get_field('content_type')
+ fields = {field.name: field.formfield(initial=getattr(self.content_type, 'pk', None))}
+
+ if self.content_type:
+ kwargs = {
+ 'initial': self.object_ids,
+ 'required': False,
+ 'queryset': self.content_type.model_class()._default_manager.all()
+ }
+ fields['value'] = forms.ModelMultipleChoiceField(**kwargs)
+ return fields
+
+ def construct_instance(self, **kwargs):
+ field_name = self._meta.get_field('content_type').name
+ ct = kwargs.pop(field_name, None)
+ if ct is None or ct != self.content_type:
+ self.values.clear()
+ self.content_type = ct
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 = []
+ 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
class Meta:
app_label = 'philo'
def attributes(self):
return QuerySetMapper(self.attribute_set.all())
- @property
- def _added_attribute_registry(self):
- if not hasattr(self, '_real_added_attribute_registry'):
- self._real_added_attribute_registry = {}
- return self._real_added_attribute_registry
-
- @property
- def _removed_attribute_registry(self):
- if not hasattr(self, '_real_removed_attribute_registry'):
- self._real_removed_attribute_registry = []
- return self._real_removed_attribute_registry
-
- def save(self, *args, **kwargs):
- super(Entity, self).save(*args, **kwargs)
-
- for key in self._removed_attribute_registry:
- self.attribute_set.filter(key__exact=key).delete()
- del self._removed_attribute_registry[:]
-
- for field, value in self._added_attribute_registry.items():
- try:
- attribute = self.attribute_set.get(key__exact=field.key)
- except Attribute.DoesNotExist:
- attribute = Attribute()
- attribute.entity = self
- attribute.key = field.key
-
- field.set_attribute_value(attribute, value)
- attribute.save()
- self._added_attribute_registry.clear()
-
class Meta:
abstract = True
from django import forms
-from django.core.exceptions import FieldError, ValidationError
from django.db import models
-from django.db.models.fields import NOT_PROVIDED
from django.utils import simplejson as json
-from django.utils.text import capfirst
-from philo.signals import entity_class_prepared
+from philo.forms.fields import JSONFormField
from philo.validators import TemplateValidator, json_validator
-__all__ = ('JSONAttribute', 'ForeignKeyAttribute', 'ManyToManyAttribute')
-
-
-class EntityProxyField(object):
- descriptor_class = None
-
- def __init__(self, verbose_name=None, help_text=None, default=NOT_PROVIDED, editable=True, *args, **kwargs):
- if self.descriptor_class is None:
- raise NotImplementedError('EntityProxyField subclasses must specify a descriptor_class.')
- self.verbose_name = verbose_name
- self.help_text = help_text
- self.default = default
- self.editable = editable
-
- def actually_contribute_to_class(self, sender, **kwargs):
- sender._entity_meta.add_proxy_field(self)
- setattr(sender, self.attname, self.descriptor_class(self))
-
- def contribute_to_class(self, cls, name):
- from philo.models.base import Entity
- if issubclass(cls, Entity):
- self.name = name
- self.attname = name
- if self.verbose_name is None and name:
- self.verbose_name = name.replace('_', ' ')
- entity_class_prepared.connect(self.actually_contribute_to_class, sender=cls)
- else:
- raise FieldError('%s instances can only be declared on Entity subclasses.' % self.__class__.__name__)
-
- def formfield(self, *args, **kwargs):
- raise NotImplementedError('EntityProxyField subclasses must implement a formfield method.')
-
- def value_from_object(self, obj):
- return getattr(obj, self.attname)
-
- def has_default(self):
- return self.default is not NOT_PROVIDED
-
-
-class AttributeFieldDescriptor(object):
- def __init__(self, field):
- self.field = field
-
- def __get__(self, instance, owner):
- if instance:
- if self.field in instance._added_attribute_registry:
- return instance._added_attribute_registry[self.field]
- if self.field in instance._removed_attribute_registry:
- return None
- try:
- return instance.attributes[self.field.key]
- except KeyError:
- return None
- else:
- return None
-
- def __set__(self, instance, value):
- raise NotImplementedError('AttributeFieldDescriptor subclasses must implement a __set__ method.')
-
- def __delete__(self, instance):
- if self.field in instance._added_attribute_registry:
- del instance._added_attribute_registry[self.field]
- instance._removed_attribute_registry.append(self.field)
-
-
-class JSONAttributeDescriptor(AttributeFieldDescriptor):
- def __set__(self, instance, value):
- if self.field in instance._removed_attribute_registry:
- instance._removed_attribute_registry.remove(self.field)
- instance._added_attribute_registry[self.field] = value
-
-
-class ForeignKeyAttributeDescriptor(AttributeFieldDescriptor):
- def __set__(self, instance, value):
- if isinstance(value, (models.Model, type(None))):
- if self.field in instance._removed_attribute_registry:
- instance._removed_attribute_registry.remove(self.field)
- instance._added_attribute_registry[self.field] = 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.query.QuerySet):
- if self.field in instance._removed_attribute_registry:
- instance._removed_attribute_registry.remove(self.field)
- instance._added_attribute_registry[self.field] = value
- else:
- raise AttributeError('The \'%s\' attribute can only be set to a QuerySet.' % self.field.name)
-
-
-class AttributeField(EntityProxyField):
- def contribute_to_class(self, cls, name):
- super(AttributeField, self).contribute_to_class(cls, name)
- if self.key is None:
- self.key = name
-
- def set_attribute_value(self, attribute, value, value_class):
- if not isinstance(attribute.value, value_class):
- if isinstance(attribute.value, models.Model):
- attribute.value.delete()
- new_value = value_class()
- else:
- new_value = attribute.value
- new_value.value = value
- new_value.save()
- attribute.value = new_value
-
-
-class JSONAttribute(AttributeField):
- descriptor_class = JSONAttributeDescriptor
-
- def __init__(self, field_template=None, key=None, **kwargs):
- super(AttributeField, self).__init__(**kwargs)
- self.key = key
- if field_template is None:
- field_template = models.CharField(max_length=255)
- self.field_template = field_template
-
- def formfield(self, **kwargs):
- defaults = {'required': False, 'label': capfirst(self.verbose_name), 'help_text': self.help_text}
- if self.has_default():
- defaults['initial'] = self.default
- defaults.update(kwargs)
- return self.field_template.formfield(**defaults)
-
- def value_from_object(self, obj):
- try:
- return getattr(obj, self.attname)
- except AttributeError:
- return None
-
- def set_attribute_value(self, attribute, value, value_class=None):
- if value_class is None:
- from philo.models.base import JSONValue
- value_class = JSONValue
- super(JSONAttribute, self).set_attribute_value(attribute, value, value_class)
-
-
-class ForeignKeyAttribute(AttributeField):
- descriptor_class = ForeignKeyAttributeDescriptor
-
- def __init__(self, model, limit_choices_to=None, key=None, **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 formfield(self, form_class=forms.ModelChoiceField, **kwargs):
- defaults = {'required': False, 'label': capfirst(self.verbose_name), 'help_text': self.help_text}
- if self.has_default():
- defaults['initial'] = self.default
- defaults.update(kwargs)
- return form_class(self.model._default_manager.complex_filter(self.limit_choices_to), **defaults)
-
- 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 set_attribute_value(self, attribute, value, value_class=None):
- if value_class is None:
- from philo.models.base import ForeignKeyValue
- value_class = ForeignKeyValue
- super(ForeignKeyAttribute, self).set_attribute_value(attribute, value, value_class)
-
-
-class ManyToManyAttribute(ForeignKeyAttribute):
- descriptor_class = ManyToManyAttributeDescriptor
-
- def formfield(self, form_class=forms.ModelMultipleChoiceField, **kwargs):
- return super(ManyToManyAttribute, self).formfield(form_class, **kwargs)
-
- def set_attribute_value(self, attribute, value, value_class=None):
- if value_class is None:
- from philo.models.base import ManyToManyValue
- value_class = ManyToManyValue
- super(ManyToManyAttribute, self).set_attribute_value(attribute, value, value_class)
-
-
class TemplateField(models.TextField):
def __init__(self, allow=None, disallow=None, secure=True, *args, **kwargs):
super(TemplateField, self).__init__(*args, **kwargs)
self.validators.append(TemplateValidator(allow, disallow, secure))
-class JSONFormField(forms.Field):
- default_validators = [json_validator]
-
- def clean(self, value):
- if value == '' and not self.required:
- return None
- try:
- return json.loads(value)
- except Exception, e:
- raise ValidationError(u'JSON decode error: %s' % e)
-
-
class JSONDescriptor(object):
def __init__(self, field):
self.field = field