X-Git-Url: http://git.ithinksw.org/philo.git/blobdiff_plain/1422f0d2c8325b2c4d6781bd3c7b21a3d5873b90..3a134160f8e7e663c5807cd44296f5f3a88b8a36:/forms/entities.py?ds=sidebyside diff --git a/forms/entities.py b/forms/entities.py index a392e45..43094b9 100644 --- a/forms/entities.py +++ b/forms/entities.py @@ -1,16 +1,12 @@ -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.forms.models import ModelFormMetaclass, ModelForm, ModelFormOptions 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)): +def proxy_fields_for_entity_model(entity_model, fields=None, exclude=None, widgets=None, formfield_callback=None): field_list = [] ignored = [] opts = entity_model._entity_meta @@ -25,7 +21,14 @@ def proxy_fields_for_entity_model(entity_model, fields=None, exclude=None, widge kwargs = {'widget': widgets[f.name]} else: kwargs = {} - formfield = formfield_callback(f, **kwargs) + + if formfield_callback is None: + formfield = f.formfield(**kwargs) + elif not callable(formfield_callback): + raise TypeError('formfield_callback must be a function or callable') + else: + formfield = formfield_callback(f, **kwargs) + if formfield: field_list.append((f.name, formfield)) else: @@ -39,25 +42,66 @@ def proxy_fields_for_entity_model(entity_model, fields=None, exclude=None, widge 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 EntityFormMetaclass(ModelFormMetaclass): + def __new__(cls, name, bases, attrs): + try: + parents = [b for b in bases if issubclass(b, EntityForm)] + except NameError: + # We are defining EntityForm itself + parents = None + sup = super(EntityFormMetaclass, cls) + + if not parents: + # Then there's no business trying to use proxy fields. + return sup.__new__(cls, name, bases, attrs) + + # Preemptively make a meta so we can screen out any proxy fields. + # After http://code.djangoproject.com/ticket/14082 has been resolved, + # perhaps switch to setting proxy fields as "declared"? + _opts = ModelFormOptions(attrs.get('Meta', None)) + + # Make another copy of opts to spoof the proxy fields not being there. + opts = ModelFormOptions(attrs.get('Meta', None)) + if opts.model: + formfield_callback = attrs.get('formfield_callback', None) + proxy_fields = proxy_fields_for_entity_model(opts.model, opts.fields, opts.exclude, opts.widgets, formfield_callback) + else: + proxy_fields = {} + + # Screen out all proxy fields from the meta + opts.fields = list(opts.fields or []) + opts.exclude = list(opts.exclude or []) + + for field in proxy_fields.keys(): + try: + opts.fields.remove(field) + except ValueError: + pass + try: + opts.exclude.remove(field) + except ValueError: + pass + opts.fields = opts.fields or None + opts.exclude = opts.exclude or None + + # Put in the new Meta. + attrs['Meta'] = opts + + new_class = sup.__new__(cls, name, bases, attrs) + + intersections = set(new_class.declared_fields.keys()) & set(proxy_fields.keys()) + for key in intersections: + proxy_fields.pop(key) + + new_class.proxy_fields = proxy_fields + new_class._meta = _opts + new_class.base_fields.update(proxy_fields) + return new_class -class EntityForm(EntityFormBase): # Would inherit from ModelForm directly if it weren't for the above HACK +class EntityForm(ModelForm): + __metaclass__ = EntityFormMetaclass + def __init__(self, *args, **kwargs): initial = kwargs.pop('initial', None) instance = kwargs.get('instance', None) @@ -88,88 +132,10 @@ class EntityForm(EntityFormBase): # Would inherit from ModelForm directly if it continue if self._meta.exclude and f.name in self._meta.exclude: continue - setattr(instance, f.attname, cleaned_data[f.name]) + setattr(instance, f.attname, f.get_storage_value(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 = [] - -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 + return instance \ No newline at end of file