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