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