Merge branch 'master' of git://github.com/melinath/philo
[philo.git] / admin / forms / attributes.py
1 from django.contrib.contenttypes.generic import BaseGenericInlineFormSet
2 from django.contrib.contenttypes.models import ContentType
3 from django.forms.models import ModelForm
4 from philo.models import Attribute
5
6
7 __all__ = ('AttributeForm', 'AttributeInlineFormSet')
8
9
10 class AttributeForm(ModelForm):
11         """
12         This class handles an attribute's fields as well as the fields for its value (if there is one.)
13         The fields defined will vary depending on the value type, but the fields for defining the value
14         (i.e. value_content_type and value_object_id) will always be defined. Except that value_object_id
15         will never be defined. BLARGH!
16         """
17         def __init__(self, *args, **kwargs):
18                 super(AttributeForm, self).__init__(*args, **kwargs)
19                 
20                 # This is necessary because model forms store changes to self.instance in their clean method.
21                 # Mutter mutter.
22                 value = self.instance.value
23                 self._cached_value_ct = self.instance.value_content_type
24                 self._cached_value = value
25                 
26                 # If there is a value, pull in its fields.
27                 if value is not None:
28                         self.value_fields = value.value_formfields()
29                         self.fields.update(self.value_fields)
30         
31         def save(self, *args, **kwargs):
32                 # At this point, the cleaned_data has already been stored on self.instance.
33                 
34                 if self.instance.value_content_type != self._cached_value_ct:
35                         # The value content type has changed. Clear the old value, if there was one.
36                         if self._cached_value:
37                                 self._cached_value.delete()
38                         
39                         # Clear the submitted value, if any.
40                         self.cleaned_data.pop('value', None)
41                         
42                         # Now create a new value instance so that on next instantiation, the form will
43                         # know what fields to add.
44                         if self.instance.value_content_type is not None:
45                                 self.instance.value = self.instance.value_content_type.model_class().objects.create()
46                 elif self.instance.value is not None:
47                         # The value content type is the same, but one of the value fields has changed.
48                         
49                         # Use construct_instance to apply the changes from the cleaned_data to the value instance.
50                         fields = self.value_fields.keys()
51                         if set(fields) & set(self.changed_data):
52                                 self.instance.value.construct_instance(**dict([(key, self.cleaned_data[key]) for key in fields]))
53                                 self.instance.value.save()
54                 
55                 return super(AttributeForm, self).save(*args, **kwargs)
56         
57         class Meta:
58                 model = Attribute
59
60
61 class AttributeInlineFormSet(BaseGenericInlineFormSet):
62         "Necessary to force the GenericInlineFormset to use the form's save method for new objects."
63         def save_new(self, form, commit):
64                 setattr(form.instance, self.ct_field.get_attname(), ContentType.objects.get_for_model(self.instance).pk)
65                 setattr(form.instance, self.ct_fk_field.get_attname(), self.instance.pk)
66                 return form.save()