|
1 from django.core.exceptions import ObjectDoesNotExist |
|
2 from django import oldforms |
|
3 from django.core import validators |
|
4 from django.db.models.fields import FileField, AutoField |
|
5 from django.dispatch import dispatcher |
|
6 from django.db.models import signals |
|
7 from django.utils.functional import curry |
|
8 from django.utils.datastructures import DotExpandedDict |
|
9 from django.utils.text import capfirst |
|
10 from django.utils.encoding import smart_str |
|
11 from django.utils.translation import ugettext as _ |
|
12 |
|
13 def add_manipulators(sender): |
|
14 cls = sender |
|
15 cls.add_to_class('AddManipulator', AutomaticAddManipulator) |
|
16 cls.add_to_class('ChangeManipulator', AutomaticChangeManipulator) |
|
17 |
|
18 dispatcher.connect(add_manipulators, signal=signals.class_prepared) |
|
19 |
|
20 class ManipulatorDescriptor(object): |
|
21 # This class provides the functionality that makes the default model |
|
22 # manipulators (AddManipulator and ChangeManipulator) available via the |
|
23 # model class. |
|
24 def __init__(self, name, base): |
|
25 self.man = None # Cache of the manipulator class. |
|
26 self.name = name |
|
27 self.base = base |
|
28 |
|
29 def __get__(self, instance, model=None): |
|
30 if instance != None: |
|
31 raise AttributeError, "Manipulator cannot be accessed via instance" |
|
32 else: |
|
33 if not self.man: |
|
34 # Create a class that inherits from the "Manipulator" class |
|
35 # given in the model class (if specified) and the automatic |
|
36 # manipulator. |
|
37 bases = [self.base] |
|
38 if hasattr(model, 'Manipulator'): |
|
39 bases = [model.Manipulator] + bases |
|
40 self.man = type(self.name, tuple(bases), {}) |
|
41 self.man._prepare(model) |
|
42 return self.man |
|
43 |
|
44 class AutomaticManipulator(oldforms.Manipulator): |
|
45 def _prepare(cls, model): |
|
46 cls.model = model |
|
47 cls.manager = model._default_manager |
|
48 cls.opts = model._meta |
|
49 for field_name_list in cls.opts.unique_together: |
|
50 setattr(cls, 'isUnique%s' % '_'.join(field_name_list), curry(manipulator_validator_unique_together, field_name_list, cls.opts)) |
|
51 for f in cls.opts.fields: |
|
52 if f.unique_for_date: |
|
53 setattr(cls, 'isUnique%sFor%s' % (f.name, f.unique_for_date), curry(manipulator_validator_unique_for_date, f, cls.opts.get_field(f.unique_for_date), cls.opts, 'date')) |
|
54 if f.unique_for_month: |
|
55 setattr(cls, 'isUnique%sFor%s' % (f.name, f.unique_for_month), curry(manipulator_validator_unique_for_date, f, cls.opts.get_field(f.unique_for_month), cls.opts, 'month')) |
|
56 if f.unique_for_year: |
|
57 setattr(cls, 'isUnique%sFor%s' % (f.name, f.unique_for_year), curry(manipulator_validator_unique_for_date, f, cls.opts.get_field(f.unique_for_year), cls.opts, 'year')) |
|
58 _prepare = classmethod(_prepare) |
|
59 |
|
60 def contribute_to_class(cls, other_cls, name): |
|
61 setattr(other_cls, name, ManipulatorDescriptor(name, cls)) |
|
62 contribute_to_class = classmethod(contribute_to_class) |
|
63 |
|
64 def __init__(self, follow=None): |
|
65 self.follow = self.opts.get_follow(follow) |
|
66 self.fields = [] |
|
67 |
|
68 for f in self.opts.fields + self.opts.many_to_many: |
|
69 if self.follow.get(f.name, False): |
|
70 self.fields.extend(f.get_manipulator_fields(self.opts, self, self.change)) |
|
71 |
|
72 # Add fields for related objects. |
|
73 for f in self.opts.get_all_related_objects(): |
|
74 if self.follow.get(f.name, False): |
|
75 fol = self.follow[f.name] |
|
76 self.fields.extend(f.get_manipulator_fields(self.opts, self, self.change, fol)) |
|
77 |
|
78 # Add field for ordering. |
|
79 if self.change and self.opts.get_ordered_objects(): |
|
80 self.fields.append(oldforms.CommaSeparatedIntegerField(field_name="order_")) |
|
81 |
|
82 def save(self, new_data): |
|
83 # TODO: big cleanup when core fields go -> use recursive manipulators. |
|
84 params = {} |
|
85 for f in self.opts.fields: |
|
86 # Fields with auto_now_add should keep their original value in the change stage. |
|
87 auto_now_add = self.change and getattr(f, 'auto_now_add', False) |
|
88 if self.follow.get(f.name, None) and not auto_now_add: |
|
89 param = f.get_manipulator_new_data(new_data) |
|
90 else: |
|
91 if self.change: |
|
92 param = getattr(self.original_object, f.attname) |
|
93 else: |
|
94 param = f.get_default() |
|
95 params[f.attname] = param |
|
96 |
|
97 if self.change: |
|
98 params[self.opts.pk.attname] = self.obj_key |
|
99 |
|
100 # First, create the basic object itself. |
|
101 new_object = self.model(**params) |
|
102 |
|
103 # Now that the object's been created, save any uploaded files. |
|
104 for f in self.opts.fields: |
|
105 if isinstance(f, FileField): |
|
106 f.save_file(new_data, new_object, self.change and self.original_object or None, self.change, rel=False, save=False) |
|
107 |
|
108 # Now save the object |
|
109 new_object.save() |
|
110 |
|
111 # Calculate which primary fields have changed. |
|
112 if self.change: |
|
113 self.fields_added, self.fields_changed, self.fields_deleted = [], [], [] |
|
114 for f in self.opts.fields: |
|
115 if not f.primary_key and smart_str(getattr(self.original_object, f.attname)) != smart_str(getattr(new_object, f.attname)): |
|
116 self.fields_changed.append(f.verbose_name) |
|
117 |
|
118 # Save many-to-many objects. Example: Set sites for a poll. |
|
119 for f in self.opts.many_to_many: |
|
120 if self.follow.get(f.name, None): |
|
121 if not f.rel.edit_inline: |
|
122 if f.rel.raw_id_admin: |
|
123 new_vals = new_data.get(f.name, ()) |
|
124 else: |
|
125 new_vals = new_data.getlist(f.name) |
|
126 # First, clear the existing values. |
|
127 rel_manager = getattr(new_object, f.name) |
|
128 rel_manager.clear() |
|
129 # Then, set the new values. |
|
130 for n in new_vals: |
|
131 rel_manager.add(f.rel.to._default_manager.get(pk=n)) |
|
132 # TODO: Add to 'fields_changed' |
|
133 |
|
134 expanded_data = DotExpandedDict(dict(new_data)) |
|
135 # Save many-to-one objects. Example: Add the Choice objects for a Poll. |
|
136 for related in self.opts.get_all_related_objects(): |
|
137 # Create obj_list, which is a DotExpandedDict such as this: |
|
138 # [('0', {'id': ['940'], 'choice': ['This is the first choice']}), |
|
139 # ('1', {'id': ['941'], 'choice': ['This is the second choice']}), |
|
140 # ('2', {'id': [''], 'choice': ['']})] |
|
141 child_follow = self.follow.get(related.name, None) |
|
142 |
|
143 if child_follow: |
|
144 obj_list = expanded_data.get(related.var_name, {}).items() |
|
145 if not obj_list: |
|
146 continue |
|
147 |
|
148 obj_list.sort(lambda x, y: cmp(int(x[0]), int(y[0]))) |
|
149 |
|
150 # For each related item... |
|
151 for _, rel_new_data in obj_list: |
|
152 |
|
153 params = {} |
|
154 |
|
155 # Keep track of which core=True fields were provided. |
|
156 # If all core fields were given, the related object will be saved. |
|
157 # If none of the core fields were given, the object will be deleted. |
|
158 # If some, but not all, of the fields were given, the validator would |
|
159 # have caught that. |
|
160 all_cores_given, all_cores_blank = True, True |
|
161 |
|
162 # Get a reference to the old object. We'll use it to compare the |
|
163 # old to the new, to see which fields have changed. |
|
164 old_rel_obj = None |
|
165 if self.change: |
|
166 if rel_new_data[related.opts.pk.name][0]: |
|
167 try: |
|
168 old_rel_obj = getattr(self.original_object, related.get_accessor_name()).get(**{'%s__exact' % related.opts.pk.name: rel_new_data[related.opts.pk.attname][0]}) |
|
169 except ObjectDoesNotExist: |
|
170 pass |
|
171 |
|
172 for f in related.opts.fields: |
|
173 if f.core and not isinstance(f, FileField) and f.get_manipulator_new_data(rel_new_data, rel=True) in (None, ''): |
|
174 all_cores_given = False |
|
175 elif f.core and not isinstance(f, FileField) and f.get_manipulator_new_data(rel_new_data, rel=True) not in (None, ''): |
|
176 all_cores_blank = False |
|
177 # If this field isn't editable, give it the same value it had |
|
178 # previously, according to the given ID. If the ID wasn't |
|
179 # given, use a default value. FileFields are also a special |
|
180 # case, because they'll be dealt with later. |
|
181 |
|
182 if f == related.field: |
|
183 param = getattr(new_object, related.field.rel.get_related_field().attname) |
|
184 elif (not self.change) and isinstance(f, AutoField): |
|
185 param = None |
|
186 elif self.change and (isinstance(f, FileField) or not child_follow.get(f.name, None)): |
|
187 if old_rel_obj: |
|
188 param = getattr(old_rel_obj, f.column) |
|
189 else: |
|
190 param = f.get_default() |
|
191 else: |
|
192 param = f.get_manipulator_new_data(rel_new_data, rel=True) |
|
193 if param != None: |
|
194 params[f.attname] = param |
|
195 |
|
196 # Create the related item. |
|
197 new_rel_obj = related.model(**params) |
|
198 |
|
199 # If all the core fields were provided (non-empty), save the item. |
|
200 if all_cores_given: |
|
201 new_rel_obj.save() |
|
202 |
|
203 # Save any uploaded files. |
|
204 for f in related.opts.fields: |
|
205 if child_follow.get(f.name, None): |
|
206 if isinstance(f, FileField) and rel_new_data.get(f.name, False): |
|
207 f.save_file(rel_new_data, new_rel_obj, self.change and old_rel_obj or None, old_rel_obj is not None, rel=True) |
|
208 |
|
209 # Calculate whether any fields have changed. |
|
210 if self.change: |
|
211 if not old_rel_obj: # This object didn't exist before. |
|
212 self.fields_added.append('%s "%s"' % (related.opts.verbose_name, new_rel_obj)) |
|
213 else: |
|
214 for f in related.opts.fields: |
|
215 if not f.primary_key and f != related.field and smart_str(getattr(old_rel_obj, f.attname)) != smart_str(getattr(new_rel_obj, f.attname)): |
|
216 self.fields_changed.append('%s for %s "%s"' % (f.verbose_name, related.opts.verbose_name, new_rel_obj)) |
|
217 |
|
218 # Save many-to-many objects. |
|
219 for f in related.opts.many_to_many: |
|
220 if child_follow.get(f.name, None) and not f.rel.edit_inline: |
|
221 new_value = rel_new_data[f.attname] |
|
222 if f.rel.raw_id_admin: |
|
223 new_value = new_value[0] |
|
224 setattr(new_rel_obj, f.name, f.rel.to.objects.filter(pk__in=new_value)) |
|
225 if self.change: |
|
226 self.fields_changed.append('%s for %s "%s"' % (f.verbose_name, related.opts.verbose_name, new_rel_obj)) |
|
227 |
|
228 # If, in the change stage, all of the core fields were blank and |
|
229 # the primary key (ID) was provided, delete the item. |
|
230 if self.change and all_cores_blank and old_rel_obj: |
|
231 new_rel_obj.delete() |
|
232 self.fields_deleted.append('%s "%s"' % (related.opts.verbose_name, old_rel_obj)) |
|
233 |
|
234 # Save the order, if applicable. |
|
235 if self.change and self.opts.get_ordered_objects(): |
|
236 order = new_data['order_'] and map(int, new_data['order_'].split(',')) or [] |
|
237 for rel_opts in self.opts.get_ordered_objects(): |
|
238 getattr(new_object, 'set_%s_order' % rel_opts.object_name.lower())(order) |
|
239 return new_object |
|
240 |
|
241 def get_related_objects(self): |
|
242 return self.opts.get_followed_related_objects(self.follow) |
|
243 |
|
244 def flatten_data(self): |
|
245 new_data = {} |
|
246 obj = self.change and self.original_object or None |
|
247 for f in self.opts.get_data_holders(self.follow): |
|
248 fol = self.follow.get(f.name) |
|
249 new_data.update(f.flatten_data(fol, obj)) |
|
250 return new_data |
|
251 |
|
252 class AutomaticAddManipulator(AutomaticManipulator): |
|
253 change = False |
|
254 |
|
255 class AutomaticChangeManipulator(AutomaticManipulator): |
|
256 change = True |
|
257 def __init__(self, obj_key, follow=None): |
|
258 self.obj_key = obj_key |
|
259 try: |
|
260 self.original_object = self.manager.get(pk=obj_key) |
|
261 except ObjectDoesNotExist: |
|
262 # If the object doesn't exist, this might be a manipulator for a |
|
263 # one-to-one related object that hasn't created its subobject yet. |
|
264 # For example, this might be a Restaurant for a Place that doesn't |
|
265 # yet have restaurant information. |
|
266 if self.opts.one_to_one_field: |
|
267 # Sanity check -- Make sure the "parent" object exists. |
|
268 # For example, make sure the Place exists for the Restaurant. |
|
269 # Let the ObjectDoesNotExist exception propagate up. |
|
270 limit_choices_to = self.opts.one_to_one_field.rel.limit_choices_to |
|
271 lookup_kwargs = {'%s__exact' % self.opts.one_to_one_field.rel.field_name: obj_key} |
|
272 self.opts.one_to_one_field.rel.to.get_model_module().complex_filter(limit_choices_to).get(**lookup_kwargs) |
|
273 params = dict([(f.attname, f.get_default()) for f in self.opts.fields]) |
|
274 params[self.opts.pk.attname] = obj_key |
|
275 self.original_object = self.opts.get_model_module().Klass(**params) |
|
276 else: |
|
277 raise |
|
278 super(AutomaticChangeManipulator, self).__init__(follow=follow) |
|
279 |
|
280 def manipulator_validator_unique_together(field_name_list, opts, self, field_data, all_data): |
|
281 from django.db.models.fields.related import ManyToOneRel |
|
282 from django.utils.text import get_text_list |
|
283 field_list = [opts.get_field(field_name) for field_name in field_name_list] |
|
284 if isinstance(field_list[0].rel, ManyToOneRel): |
|
285 kwargs = {'%s__%s__iexact' % (field_name_list[0], field_list[0].rel.field_name): field_data} |
|
286 else: |
|
287 kwargs = {'%s__iexact' % field_name_list[0]: field_data} |
|
288 for f in field_list[1:]: |
|
289 # This is really not going to work for fields that have different |
|
290 # form fields, e.g. DateTime. |
|
291 # This validation needs to occur after html2python to be effective. |
|
292 field_val = all_data.get(f.name, None) |
|
293 if field_val is None: |
|
294 # This will be caught by another validator, assuming the field |
|
295 # doesn't have blank=True. |
|
296 return |
|
297 if isinstance(f.rel, ManyToOneRel): |
|
298 kwargs['%s__pk' % f.name] = field_val |
|
299 else: |
|
300 kwargs['%s__iexact' % f.name] = field_val |
|
301 try: |
|
302 old_obj = self.manager.get(**kwargs) |
|
303 except ObjectDoesNotExist: |
|
304 return |
|
305 if hasattr(self, 'original_object') and self.original_object._get_pk_val() == old_obj._get_pk_val(): |
|
306 pass |
|
307 else: |
|
308 raise validators.ValidationError, _("%(object)s with this %(type)s already exists for the given %(field)s.") % \ |
|
309 {'object': capfirst(opts.verbose_name), 'type': field_list[0].verbose_name, 'field': get_text_list([f.verbose_name for f in field_list[1:]], _('and'))} |
|
310 |
|
311 def manipulator_validator_unique_for_date(from_field, date_field, opts, lookup_type, self, field_data, all_data): |
|
312 from django.db.models.fields.related import ManyToOneRel |
|
313 date_str = all_data.get(date_field.get_manipulator_field_names('')[0], None) |
|
314 date_val = oldforms.DateField.html2python(date_str) |
|
315 if date_val is None: |
|
316 return # Date was invalid. This will be caught by another validator. |
|
317 lookup_kwargs = {'%s__year' % date_field.name: date_val.year} |
|
318 if isinstance(from_field.rel, ManyToOneRel): |
|
319 lookup_kwargs['%s__pk' % from_field.name] = field_data |
|
320 else: |
|
321 lookup_kwargs['%s__iexact' % from_field.name] = field_data |
|
322 if lookup_type in ('month', 'date'): |
|
323 lookup_kwargs['%s__month' % date_field.name] = date_val.month |
|
324 if lookup_type == 'date': |
|
325 lookup_kwargs['%s__day' % date_field.name] = date_val.day |
|
326 try: |
|
327 old_obj = self.manager.get(**lookup_kwargs) |
|
328 except ObjectDoesNotExist: |
|
329 return |
|
330 else: |
|
331 if hasattr(self, 'original_object') and self.original_object._get_pk_val() == old_obj._get_pk_val(): |
|
332 pass |
|
333 else: |
|
334 format_string = (lookup_type == 'date') and '%B %d, %Y' or '%B %Y' |
|
335 raise validators.ValidationError, "Please enter a different %s. The one you entered is already being used for %s." % \ |
|
336 (from_field.verbose_name, date_val.strftime(format_string)) |