Ticket #10134: unique_for_stuff.4.diff

File unique_for_stuff.4.diff, 10.9 KB (added by Alex, 6 years ago)

Long function is longer long.

  • django/forms/models.py

    diff --git a/django/forms/models.py b/django/forms/models.py
    index 7ca1ab5..6ea32ef 100644
    a b class BaseModelForm(BaseForm): 
    225225            object_data.update(initial)
    226226        super(BaseModelForm, self).__init__(data, files, auto_id, prefix, object_data,
    227227                                            error_class, label_suffix, empty_permitted)
    228     def clean(self):
    229         self.validate_unique()
    230         return self.cleaned_data
    231228
    232     def validate_unique(self):
     229    def _get_unique_checks(self):
    233230        from django.db.models.fields import FieldDoesNotExist, Field as ModelField
    234231
    235232        # Gather a list of checks to perform. We only perform unique checks
    class BaseModelForm(BaseForm): 
    239236        # not make sense to check data that didn't validate, and since NULL does not
    240237        # equal NULL in SQL we should not do any unique checking for NULL values.
    241238        unique_checks = []
     239        # these are checks for the unique_for_<date/year/month>
     240        date_checks = []
    242241        for check in self.instance._meta.unique_together[:]:
    243242            fields_on_form = [field for field in check if self.cleaned_data.get(field) is not None]
    244243            if len(fields_on_form) == len(check):
    245244                unique_checks.append(check)
    246245
    247         form_errors = []
    248 
    249246        # Gather a list of checks for fields declared as unique and add them to
    250247        # the list of checks. Again, skip empty fields and any that did not validate.
    251         for name, field in self.fields.items():
     248        for name in self.fields:
    252249            try:
    253250                f = self.instance._meta.get_field_by_name(name)[0]
    254251            except FieldDoesNotExist:
    class BaseModelForm(BaseForm): 
    260257                # get_field_by_name found it, but it is not a Field so do not proceed
    261258                # to use it as if it were.
    262259                continue
    263             if f.unique and self.cleaned_data.get(name) is not None:
     260            if self.cleaned_data.get(name) is None:
     261                continue
     262            if f.unique:
    264263                unique_checks.append((name,))
     264            if f.unique_for_date and self.cleaned_data.get(f.unique_for_date) is not None:
     265                date_checks.append(('date', name, f.unique_for_date))
     266            if f.unique_for_year and self.cleaned_data.get(f.unique_for_year) is not None:
     267                date_checks.append(('year', name, f.unique_for_year))
     268            if f.unique_for_month and self.cleaned_data.get(f.unique_for_month) is not None:
     269                date_checks.append(('month', name, f.unique_for_month))
     270        return unique_checks, date_checks
     271
     272
     273    def clean(self):
     274        self.validate_unique()
     275        return self.cleaned_data
     276
     277    def validate_unique(self):
     278        unique_checks, date_checks = self._get_unique_checks()
     279
     280        form_errors = []
     281        bad_fields = set()
     282
     283        field_errors, global_errors = self._preform_unique_checks(unique_checks)
     284        bad_fields.union(field_errors)
     285        form_errors.extend(global_errors)
     286
     287        field_errors, global_errors = self._preform_date_checks(date_checks)
     288        bad_fields.union(field_errors)
     289        form_errors.extend(global_errors)
     290
     291        for field_name in bad_fields:
     292            del self.cleaned_data[field_name]
     293        if form_errors:
     294            # Raise the unique together errors since they are considered
     295            # form-wide.
     296            raise ValidationError(form_errors)
    265297
     298    def _preform_unique_checks(self, unique_checks):
    266299        bad_fields = set()
     300        form_errors = []
     301
    267302        for unique_check in unique_checks:
    268303            # Try to look up an existing object with the same values as this
    269304            # object's values for all the unique field.
    class BaseModelForm(BaseForm): 
    288323            # This cute trick with extra/values is the most efficient way to
    289324            # tell if a particular query returns any results.
    290325            if qs.extra(select={'a': 1}).values('a').order_by():
    291                 model_name = capfirst(self.instance._meta.verbose_name)
    292 
    293                 # A unique field
    294326                if len(unique_check) == 1:
    295                     field_name = unique_check[0]
    296                     field_label = self.fields[field_name].label
    297                     # Insert the error into the error dict, very sneaky
    298                     self._errors[field_name] = ErrorList([
    299                         _(u"%(model_name)s with this %(field_label)s already exists.") % \
    300                         {'model_name': unicode(model_name),
    301                          'field_label': unicode(field_label)}
    302                     ])
    303                 # unique_together
     327                    self._errors[unique_check[0]] = ErrorList([self.unique_error_message(unique_check)])
    304328                else:
    305                     field_labels = [self.fields[field_name].label for field_name in unique_check]
    306                     field_labels = get_text_list(field_labels, _('and'))
    307                     form_errors.append(
    308                         _(u"%(model_name)s with this %(field_label)s already exists.") % \
    309                         {'model_name': unicode(model_name),
    310                          'field_label': unicode(field_labels)}
    311                     )
     329                    form_errors.append(self.unique_error_message(unique_check))
    312330
    313331                # Mark these fields as needing to be removed from cleaned data
    314332                # later.
    315333                for field_name in unique_check:
    316334                    bad_fields.add(field_name)
     335        return bad_fields, form_errors
     336
     337    def _preform_date_checks(self, date_checks):
     338        bad_fields = set()
     339        for lookup_type, field, unique_for in date_checks:
     340            lookup_kwargs = {}
     341            # there's a ticket to add a date lookup, we can remove this special
     342            # case if that makes it's way in
     343            if lookup_type == 'date':
     344                date = self.cleaned_data[unique_for]
     345                lookup_kwargs['%s__day' % unique_for] = date.day
     346                lookup_kwargs['%s__month' % unique_for] = date.month
     347                lookup_kwargs['%s__year' % unique_for] = date.year
     348            else:
     349                lookup_kwargs['%s__%s' % (unique_for, lookup_type)] = getattr(self.cleaned_data[unique_for], lookup_type)
     350            lookup_kwargs[field] = self.cleaned_data[field]
     351
     352            qs = self.instance.__class__._default_manager.filter(**lookup_kwargs)
     353            # Exclude the current object from the query if we are editing an
     354            # instance (as opposed to creating a new one)
     355            if self.instance.pk is not None:
     356                qs = qs.exclude(pk=self.instance.pk)
     357
     358            # This cute trick with extra/values is the most efficient way to
     359            # tell if a particular query returns any results.
     360            if qs.extra(select={'a': 1}).values('a').order_by():
     361                self._errors[field] = ErrorList([
     362                    self.date_error_message(lookup_type, field, unique_for)
     363                ])
     364                bad_fields.add(field)
     365        return bad_fields, []
     366
     367    def date_error_message(self, lookup_type, field, unique_for):
     368        return _(u"%(field_name)s must be unique for %(date_field)s %(lookup)s.") % {
     369            'field_name': unicode(self.fields[field].label),
     370            'date_field': unicode(self.fields[unique_for].label),
     371            'lookup': lookup_type,
     372        }
     373
     374    def unique_error_message(self, unique_check):
     375        model_name = capfirst(self.instance._meta.verbose_name)
     376
     377        # A unique field
     378        if len(unique_check) == 1:
     379            field_name = unique_check[0]
     380            field_label = self.fields[field_name].label
     381            # Insert the error into the error dict, very sneaky
     382            return _(u"%(model_name)s with this %(field_label)s already exists.") %  {
     383                'model_name': unicode(model_name),
     384                'field_label': unicode(field_label)
     385            }
     386        # unique_together
     387        else:
     388            field_labels = [self.fields[field_name].label for field_name in unique_check]
     389            field_labels = get_text_list(field_labels, _('and'))
     390            return _(u"%(model_name)s with this %(field_label)s already exists.") %  {
     391                'model_name': unicode(model_name),
     392                'field_label': unicode(field_labels)
     393            }
    317394
    318         for field_name in bad_fields:
    319             del self.cleaned_data[field_name]
    320         if form_errors:
    321             # Raise the unique together errors since they are considered
    322             # form-wide.
    323             raise ValidationError(form_errors)
    324395
    325396    def save(self, commit=True):
    326397        """
  • tests/modeltests/model_forms/models.py

    diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py
    index 992bb90..d5353ae 100644
    a b class ExplicitPK(models.Model): 
    189189    def __unicode__(self):
    190190        return self.key
    191191
     192class Post(models.Model):
     193    title = models.CharField(max_length=50, unique_for_date='posted', blank=True)
     194    slug = models.CharField(max_length=50, unique_for_year='posted', blank=True)
     195    subtitle = models.CharField(max_length=50, unique_for_month='posted', blank=True)
     196    posted = models.DateField()
     197
     198    def __unicode__(self):
     199        return self.name
     200
    192201__test__ = {'API_TESTS': """
    193202>>> from django import forms
    194203>>> from django.forms.models import ModelForm, model_to_dict
    ValidationError: [u'Select a valid choice. z is not one of the available choices 
    14721481<tr><th><label for="id_description">Description:</label></th><td><input type="text" name="description" id="id_description" /></td></tr>
    14731482<tr><th><label for="id_url">The URL:</label></th><td><input id="id_url" type="text" name="url" maxlength="40" /></td></tr>
    14741483
     1484### validation on unique_for_date
     1485
     1486>>> p = Post.objects.create(title="Django 1.0 is released", slug="Django 1.0", subtitle="Finally", posted=datetime.date(2008, 9, 3))
     1487>>> class PostForm(ModelForm):
     1488...     class Meta:
     1489...         model = Post
     1490
     1491>>> f = PostForm({'title': "Django 1.0 is released", 'posted': '2008-09-03'})
     1492>>> f.is_valid()
     1493False
     1494>>> f.errors
     1495{'title': [u'Title must be unique for Posted date.']}
     1496>>> f = PostForm({'title': "Work on Django 1.1 begins", 'posted': '2008-09-03'})
     1497>>> f.is_valid()
     1498True
     1499>>> f = PostForm({'title': "Django 1.0 is released", 'posted': '2008-09-04'})
     1500>>> f.is_valid()
     1501True
     1502>>> f = PostForm({'slug': "Django 1.0", 'posted': '2008-01-01'})
     1503>>> f.is_valid()
     1504False
     1505>>> f.errors
     1506{'slug': [u'Slug must be unique for Posted year.']}
     1507>>> f = PostForm({'subtitle': "Finally", 'posted': '2008-09-30'})
     1508>>> f.is_valid()
     1509False
     1510>>> f.errors
     1511{'subtitle': [u'Subtitle must be unique for Posted month.']}
     1512>>> f = PostForm({'subtitle': "Finally", "title": "Django 1.0 is released", "slug": "Django 1.0", 'posted': '2008-09-03'}, instance=p)
     1513>>> f.is_valid()
     1514True
     1515
    14751516# Clean up
    14761517>>> import shutil
    14771518>>> shutil.rmtree(temp_storage_dir)
Back to Top