app/django/db/models/manipulators.py
changeset 54 03e267d67478
equal deleted inserted replaced
53:57b4279d8c4e 54:03e267d67478
       
     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))