All of my work from commits: dd4a194, 692644a, 4a60203, 5de46bc, 152042d, 64a2d4e...
[philo.git] / contrib / gilbert / extdirect / forms.py
1 import os.path
2 from django.forms.widgets import Widget, TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, Textarea, DateInput, DateTimeInput, TimeInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, RadioSelect, CheckboxSelectMultiple, MultiWidget, SplitHiddenDateTimeWidget
3 from django.forms.forms import BaseForm, BoundField
4 from django.forms.fields import FileField
5 from django.forms.models import ModelForm, ModelChoiceField, ModelMultipleChoiceField
6 from django.db.models import ForeignKey, ManyToManyField, Q
7 from django.utils.encoding import force_unicode
8 from philo.utils import ContentTypeLimiter
9 from philo.hacks import Category
10
11
12 # The "categories" in this module are listed in reverse order, because I wasn't able to ensure that they'd all take effect otherwise...
13
14
15 #still to do: SplitHiddenDateTimeWidget
16
17
18 class MultiWidget(MultiWidget):
19         __metaclass__ = Category
20         
21         def render_extdirect(self, name, data):
22                 if not isinstance(data, list):
23                         data = self.decompress(data)
24                 
25                 specs = []
26                 for i, widget in enumerate(self.widgets):
27                         try:
28                                 widget_data = data[i]
29                         except IndexError:
30                                 widget_data = None
31                         specs.extend(widget.render_extdirect(name + '_%s' % i, widget_data))
32                 return specs
33
34
35 #still to do: RadioSelect, CheckboxSelectMultiple
36
37
38 class SelectMultiple(SelectMultiple):
39         __metaclass__ = Category
40         extdirect_xtype = 'superboxselect'
41         
42         def render_extdirect(self, name, data):
43                 if self.choices:
44                         store = [choice for choice in self.choices if choice[0]]
45                 else:
46                         store = []
47                 spec = {
48                         'name': name,
49                         'value': data,
50                         'store': store,
51                         'xtype': self.extdirect_xtype,
52                         'forceFormValue': False
53                 }
54                 if not spec['value']:
55                         del spec['value']
56                 return [spec]
57
58
59 class NullBooleanSelect(NullBooleanSelect):
60         __metaclass__ = Category
61         
62         def render_extdirect(self, name, data):
63                 try:
64                         data = {True: u'2', False: u'3', u'2': u'2', u'3': u'3'}[data]
65                 except KeyError:
66                         data = u'1'
67                 return super(NullBooleanSelect, self).render_extdirect(name, data)
68
69
70 class Select(Select):
71         __metaclass__ = Category
72         extdirect_xtype = 'combo'
73         
74         def render_extdirect(self, name, data):
75                 if self.choices:
76                         store = [choice for choice in self.choices if choice[0]]
77                 else:
78                         store = []
79                 spec = {
80                         'hiddenName': name,
81                         'hiddenValue': data,
82                         'value': data,
83                         'xtype': self.extdirect_xtype,
84                         'store': store,
85                         'editable': False,
86                         'disableKeyFilter': True,
87                         'forceSelection': True,
88                         'triggerAction': 'all',
89                 }
90                 if not spec['value']:
91                         del spec['value']
92                 return [spec]
93
94
95 class CheckboxInput(CheckboxInput):
96         __metaclass__ = Category
97         extdirect_xtype = 'checkbox'
98         
99         def render_extdirect(self, name, data):
100                 data = bool(data)
101                 specs = super(CheckboxInput, self).render_extdirect(name, data)
102                 specs[0]['checked'] = data
103                 return specs
104
105
106 class DateTimeInput(DateTimeInput):
107         __metaclass__ = Category
108         extdirect_xtype = 'gilbertdatetimefield'
109
110
111 class TimeInput(TimeInput):
112         __metaclass__ = Category
113         extdirect_xtype = 'timefield'
114
115
116 class DateInput(DateInput):
117         __metaclass__ = Category
118         extdirect_xtype = 'datefield'
119
120
121 class Textarea(Textarea):
122         __metaclass__ = Category
123         extdirect_xtype = 'textarea'
124
125
126 class FileInput(FileInput):
127         __metaclass__ = Category
128         extdirect_xtype = 'fileuploadfield'
129         
130         def render_extdirect(self, name, data):
131                 if data is not None:
132                         data = os.path.split(data.name)[1]
133                 return super(FileInput, self).render_extdirect(name, data)
134
135
136 class MultipleHiddenInput(MultipleHiddenInput):
137         __metaclass__ = Category
138         extdirect_xtype = 'hidden'
139         
140         def render_extdirect(self, name, data):
141                 if data is None:
142                         data = []
143                 return [specs.extend(super(MultipleHiddenInput, self).render_extdirect(name, data)) for datum in data]
144
145
146 class HiddenInput(HiddenInput):
147         __metaclass__ = Category
148         extdirect_xtype = 'hidden'
149
150
151 class PasswordInput(PasswordInput):
152         __metaclass__ = Category
153         extdirect_xtype = 'textfield'
154         
155         def render_extdirect(self, name, data):
156                 specs = super(PasswordInput, self).render_extdirect(name, data)
157                 specs[0]['inputType'] = self.input_type
158                 return specs
159
160
161 class TextInput(TextInput):
162         __metaclass__ = Category
163         extdirect_xtype = 'textfield'
164
165
166 class Widget(Widget):
167         __metaclass__ = Category
168         extdirect_xtype = None
169         
170         def render_extdirect(self, name, data):
171                 if not self.extdirect_xtype:
172                         raise NotImplementedError
173                 spec = {
174                         'name': name,
175                         'value': data,
176                         'xtype': self.extdirect_xtype
177                 }
178                 if not spec['value']:
179                         del spec['value']
180                 return [spec]
181
182
183 class BoundField(BoundField):
184         __metaclass__ = Category
185         
186         def as_hidden_extdirect(self, only_initial=False):
187                 return self.as_widget_extdirect(self.field.hidden_widget(), only_initial)
188         
189         def as_widget_extdirect(self, widget=None, only_initial=False):
190                 if not widget:
191                         widget = self.field.widget
192                         standard_widget = True
193                 else:
194                         standard_widget = False
195                 
196                 if not self.form.is_bound:
197                         data = self.form.initial.get(self.name, self.field.initial)
198                         if callable(data):
199                                 data = data()
200                 else:
201                         if isinstance(self.field, FileField) and self.data is None:
202                                 data = self.form.initial.get(self.name, self.field.initial)
203                         else:
204                                 data = self.data
205                 data = self.field.prepare_value(data)
206                 
207                 if not only_initial:
208                         name = self.html_name
209                 else:
210                         name = self.html_initial_name
211                 
212                 specs = widget.render_extdirect(name, data)
213                 
214                 if standard_widget and isinstance(self.field, ModelChoiceField):
215                         limit_choices_to = None
216                         
217                         if isinstance(self.form, ModelForm):
218                                 model = self.form._meta.model
219                                 model_fields = model._meta.fields + model._meta.many_to_many
220                                 
221                                 for model_field in model_fields:
222                                         if model_field.name == self.name and (isinstance(model_field, ForeignKey) or isinstance(model_field, ManyToManyField)):
223                                                 limit_choices_to = model_field.rel.limit_choices_to
224                                                 if limit_choices_to is None:
225                                                         limit_choices_to = {}
226                                                 elif isinstance(limit_choices_to, ContentTypeLimiter):
227                                                         limit_choices_to = limit_choices_to.q_object()
228                                                 elif not isinstance(limit_choices_to, dict):
229                                                         limit_choices_to = None # can't support other objects with add_to_query methods
230                                                 break
231                         
232                         if limit_choices_to is not None:
233                                 specs[0]['model_filters'] = limit_choices_to
234                         else:
235                                 specs[0]['model_filters'] = {
236                                         'pk__in': self.field.queryset.values_list('pk', flat=True)
237                                 }
238                                 
239                         specs[0]['model_app_label'] = self.field.queryset.model._meta.app_label
240                         specs[0]['model_name'] = self.field.queryset.model._meta.object_name
241                         
242                         if isinstance(self.field, ModelMultipleChoiceField):
243                                 specs[0]['xtype'] = 'gilbertmodelmultiplechoicefield'
244                         else:
245                                 specs[0]['xtype'] = 'gilbertmodelchoicefield'
246                                 specs[0]['backup_store'] = specs[0]['store']
247                                 del specs[0]['store']
248                 
249                 return specs
250                         
251         def as_extdirect(self):
252                 if self.field.show_hidden_initial:
253                         return self.as_widget_extdirect() + self.as_hidden_extdirect(only_initial=True)
254                 return self.as_widget_extdirect()
255
256
257 class BaseForm(BaseForm):
258         __metaclass__ = Category
259         
260         def as_extdirect(self):
261                 fields = []
262                 
263                 for bound_field in self:
264                         if bound_field.label:
265                                 label = bound_field.label
266                         else:
267                                 label = ''
268                         
269                         if bound_field.field.help_text:
270                                 help_text = bound_field.field.help_text
271                         else:
272                                 help_text = ''
273                         
274                         specs = bound_field.as_extdirect()
275                         
276                         if len(specs) < 1:
277                                 continue
278                         
279                         if len(specs) > 1:
280                                 specs = [{
281                                         'xtype': 'compositefield',
282                                         'items': specs
283                                 }]
284                         
285                         if label:
286                                 specs[0]['fieldLabel'] = label
287                         if help_text:
288                                 specs[0]['help_text'] = help_text
289                         
290                         fields.extend(specs)
291                 
292                 if isinstance(self, ModelForm):
293                         pk = self.instance.pk
294                         if pk is not None:
295                                 fields.append({
296                                         'name': 'pk',
297                                         'value': pk,
298                                         'xtype': 'hidden'
299                                 })
300                 
301                 return {
302                         'items': fields,
303                         'labelSeparator': self.label_suffix,
304                         'fileUpload': self.is_multipart()
305                 }