Switched contentlets to use lists rather than sets to preserve ordering. Resolves...
[philo.git] / philo / admin / forms / containers.py
1 from django import forms
2 from django.contrib.admin.widgets import AdminTextareaWidget
3 from django.core.exceptions import ObjectDoesNotExist
4 from django.db.models import Q
5 from django.forms.models import ModelForm, BaseInlineFormSet, BaseModelFormSet
6 from django.forms.formsets import TOTAL_FORM_COUNT
7 from django.utils.datastructures import SortedDict
8
9 from philo.admin.widgets import ModelLookupWidget
10 from philo.models import Contentlet, ContentReference
11
12
13 __all__ = (
14         'ContentletForm',
15         'ContentletInlineFormSet',
16         'ContentReferenceForm',
17         'ContentReferenceInlineFormSet'
18 )
19
20
21 class ContainerForm(ModelForm):
22         def __init__(self, *args, **kwargs):
23                 super(ContainerForm, self).__init__(*args, **kwargs)
24                 self.verbose_name = self.instance.name.replace('_', ' ')
25                 self.prefix = self.instance.name
26
27
28 class ContentletForm(ContainerForm):
29         content = forms.CharField(required=False, widget=AdminTextareaWidget, label='Content')
30         
31         def should_delete(self):
32                 # Delete iff: the data has changed and is now empty.
33                 return self.has_changed() and not bool(self.cleaned_data['content'])
34         
35         class Meta:
36                 model = Contentlet
37                 fields = ['content']
38
39
40 class ContentReferenceForm(ContainerForm):
41         def __init__(self, *args, **kwargs):
42                 super(ContentReferenceForm, self).__init__(*args, **kwargs)
43                 try:
44                         self.fields['content_id'].widget = ModelLookupWidget(self.instance.content_type)
45                 except ObjectDoesNotExist:
46                         # This will happen when an empty form (which we will never use) gets instantiated.
47                         pass
48         
49         def should_delete(self):
50                 return self.has_changed() and (self.cleaned_data['content_id'] is None)
51         
52         class Meta:
53                 model = ContentReference
54                 fields = ['content_id']
55
56
57 class ContainerInlineFormSet(BaseInlineFormSet):
58         @property
59         def containers(self):
60                 if not hasattr(self, '_containers'):
61                         self._containers = self.get_containers()
62                 return self._containers
63         
64         def total_form_count(self):
65                 # This ignores the posted management form data... but that doesn't
66                 # seem to have any ill side effects.
67                 return len(self.containers.keys())
68         
69         def _get_initial_forms(self):
70                 return [form for form in self.forms if form.instance.pk is not None]
71         initial_forms = property(_get_initial_forms)
72         
73         def _get_extra_forms(self):
74                 return [form for form in self.forms if form.instance.pk is None]
75         extra_forms = property(_get_extra_forms)
76         
77         def _construct_form(self, i, **kwargs):
78                 if 'instance' not in kwargs:
79                         kwargs['instance'] = self.containers.values()[i]
80                 
81                 # Skip over the BaseModelFormSet. We have our own way of doing things!
82                 form = super(BaseModelFormSet, self)._construct_form(i, **kwargs)
83                 
84                 # Since we skipped over BaseModelFormSet, we need to duplicate what BaseInlineFormSet would do.
85                 if self.save_as_new:
86                         # Remove the primary key from the form's data, we are only
87                         # creating new instances
88                         form.data[form.add_prefix(self._pk_field.name)] = None
89                         
90                         # Remove the foreign key from the form's data
91                         form.data[form.add_prefix(self.fk.name)] = None
92                 
93                 # Set the fk value here so that the form can do it's validation.
94                 setattr(form.instance, self.fk.get_attname(), self.instance.pk)
95                 return form
96         
97         def add_fields(self, form, index):
98                 """Override the pk field's initial value with a real one."""
99                 super(ContainerInlineFormSet, self).add_fields(form, index)
100                 if index is not None:
101                         pk_value = self.containers.values()[index].pk
102                 else:
103                         pk_value = None
104                 form.fields[self._pk_field.name].initial = pk_value
105         
106         def save_existing_objects(self, commit=True):
107                 self.changed_objects = []
108                 self.deleted_objects = []
109                 if not self.get_queryset():
110                         return []
111                 
112                 saved_instances = []
113                 for form in self.initial_forms:
114                         pk_name = self._pk_field.name
115                         raw_pk_value = form._raw_value(pk_name)
116                         
117                         # clean() for different types of PK fields can sometimes return
118                         # the model instance, and sometimes the PK. Handle either.
119                         pk_value = form.fields[pk_name].clean(raw_pk_value)
120                         pk_value = getattr(pk_value, 'pk', pk_value)
121                         
122                         # if the pk_value is None, they have just switched to a
123                         # template which didn't contain data about this container.
124                         # Skip!
125                         if pk_value is not None:
126                                 obj = self._existing_object(pk_value)
127                                 if form.should_delete():
128                                         self.deleted_objects.append(obj)
129                                         obj.delete()
130                                         continue
131                                 if form.has_changed():
132                                         self.changed_objects.append((obj, form.changed_data))
133                                         saved_instances.append(self.save_existing(form, obj, commit=commit))
134                                         if not commit:
135                                                 self.saved_forms.append(form)
136                 return saved_instances
137
138         def save_new_objects(self, commit=True):
139                 self.new_objects = []
140                 for form in self.extra_forms:
141                         if not form.has_changed():
142                                 continue
143                         # If someone has marked an add form for deletion, don't save the
144                         # object.
145                         if form.should_delete():
146                                 continue
147                         self.new_objects.append(self.save_new(form, commit=commit))
148                         if not commit:
149                                 self.saved_forms.append(form)
150                 return self.new_objects
151
152
153 class ContentletInlineFormSet(ContainerInlineFormSet):
154         def get_containers(self):
155                 try:
156                         containers = self.instance.containers[0]
157                 except ObjectDoesNotExist:
158                         containers = []
159                 
160                 qs = self.get_queryset().filter(name__in=containers)
161                 container_dict = SortedDict([(container.name, container) for container in qs])
162                 for name in containers:
163                         if name not in container_dict:
164                                 container_dict[name] = self.model(name=name)
165                 
166                 container_dict.keyOrder = containers
167                 return container_dict
168
169
170 class ContentReferenceInlineFormSet(ContainerInlineFormSet):
171         def get_containers(self):
172                 try:
173                         containers = self.instance.containers[1]
174                 except ObjectDoesNotExist:
175                         containers = {}
176                 
177                 filter = Q()
178                 for name, ct in containers.items():
179                         filter |= Q(name=name, content_type=ct)
180                 qs = self.get_queryset().filter(filter)
181                 
182                 container_dict = SortedDict([(container.name, container) for container in qs])
183                 
184                 keyOrder = []
185                 for name, ct in containers.items():
186                         keyOrder.append(name)
187                         if name not in container_dict:
188                                 container_dict[name] = self.model(name=name, content_type=ct)
189                 
190                 container_dict.keyOrder = keyOrder
191                 return container_dict