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