Ticket #9493: formset-unique.6.diff

File formset-unique.6.diff, 5.1 KB (added by Alex Gaynor, 11 years ago)
  • django/forms/models.py

    diff --git a/django/forms/models.py b/django/forms/models.py
    index 01bd912..cee9fd7 100644
    a b class BaseModelFormSet(BaseFormSet): 
    395395                    form.save_m2m()
    396396            self.save_m2m = save_m2m
    397397        return self.save_existing_objects(commit) + self.save_new_objects(commit)
     398   
     399    def clean(self):
     400        self.validate_unique()
     401   
     402    def validate_unique(self):
     403        from django.db.models.fields import FieldDoesNotExist, Field as ModelField
     404        unique_checks = []
     405        for name, field in self.forms[0].fields.iteritems():
     406            try:
     407                f = self.forms[0].instance._meta.get_field_by_name(name)[0]
     408            except FieldDoesNotExist:
     409                continue
     410            if not isinstance(f, ModelField):
     411                continue
     412            if f.unique:
     413                unique_checks.append((name,))
     414        unique_together = self.forms[0].instance._meta.unique_together
     415        unique_together = [check for check in unique_together if [True for field in check if field in self.forms[0].fields]]
     416        unique_checks.extend(unique_together)
     417       
     418        errors = []
     419        for unique_check in unique_checks:
     420            data = set()
     421            for form in self.forms:
     422                if not hasattr(form, 'cleaned_data'):
     423                    continue
     424                if [True for field in unique_check if field in form.cleaned_data and form.cleaned_data[field] is not None]:
     425                    instance = tuple([form.cleaned_data[field] for field in unique_check])
     426                    if instance in data:
     427                        errors.append(self.get_unique_error_message(unique_check))
     428                        break
     429                    else:
     430                        data.add(instance)
     431       
     432        if errors:
     433            raise ValidationError(errors)
     434   
     435    def get_unique_error_message(self, unique_check):
     436        if len(unique_check) == 1:
     437            return _("You have entered duplicate data for %(field)s, all %(field)ss should be unique.") % {
     438                    'field': unique_check[0],
     439                }
     440        else:
     441            return _("You have entered duplicate data for %(field)s, %(field)s should be unique together.") % {
     442                    'field': get_text_list(unique_check, _("and")),
     443                }
    398444
    399445    def save_existing_objects(self, commit=True):
    400446        self.changed_objects = []
    class BaseInlineFormSet(BaseModelFormSet): 
    505551            form.fields[self._pk_field.name] = InlineForeignKeyField(self.instance, pk_field=True)
    506552        else:
    507553            form.fields[self.fk.name] = InlineForeignKeyField(self.instance, label=form.fields[self.fk.name].label)
     554   
     555    def get_unique_error_message(self, unique_check):
     556        unique_check = [field for field in unique_check if field != self.fk.name]
     557        return super(BaseInlineFormSet, self).get_unique_error_message(unique_check)
    508558
    509559def _get_foreign_key(parent_model, model, fk_name=None):
    510560    """
  • docs/topics/forms/modelforms.txt

    diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt
    index 50beea2..3767d29 100644
    a b than that of a "normal" formset. The only difference is that we call 
    539539``formset.save()`` to save the data into the database. (This was described
    540540above, in :ref:`saving-objects-in-the-formset`.)
    541541
     542
     543Overiding ``clean()`` on a ``model_formset``
     544--------------------------------------------
     545
     546Just like with ``ModelForms``, by default the ``clean()`` method of a
     547``model_formset`` will validate that none of the items in the formset validate
     548the unique constraints on your model(either unique or unique_together).  If you
     549want to overide the ``clean()`` method on a ``model_formset`` and maintain this
     550validation, you must call the parent classes ``clean`` method.
     551
     552
    542553Using a custom queryset
    543554~~~~~~~~~~~~~~~~~~~~~~~
    544555
  • tests/modeltests/model_formsets/models.py

    diff --git a/tests/modeltests/model_formsets/models.py b/tests/modeltests/model_formsets/models.py
    index f11d453..b355ae8 100644
    a b True 
    828828>>> formset.get_queryset()
    829829[<Player: Bobby>]
    830830
     831# Prevent duplicates from within the same formset
     832>>> FormSet = modelformset_factory(Product, extra=2)
     833>>> data = {
     834...     'form-TOTAL_FORMS': 2,
     835...     'form-INITIAL_FORMS': 0,
     836...     'form-0-slug': 'red_car',
     837...     'form-1-slug': 'red_car',
     838... }
     839>>> formset = FormSet(data)
     840>>> formset.is_valid()
     841False
     842>>> formset._non_form_errors
     843[u'You have entered duplicate data for slug, all slugs should be unique.']
     844
     845>>> FormSet = modelformset_factory(Price, extra=2)
     846>>> data = {
     847...     'form-TOTAL_FORMS': 2,
     848...     'form-INITIAL_FORMS': 0,
     849...     'form-0-price': '25',
     850...     'form-0-quantity': '7',
     851...     'form-1-price': '25',
     852...     'form-1-quantity': '7',
     853... }
     854>>> formset = FormSet(data)
     855>>> formset.is_valid()
     856False
     857>>> formset._non_form_errors
     858[u'You have entered duplicate data for price and quantity, price and quantity should be unique together.']
    831859"""}
Back to Top