Django

Code

Ticket #9493: formset-unique.6.diff

File formset-unique.6.diff, 5.1 kB (added by Alex, 1 year ago)
  • a/django/forms/models.py

    old new  
    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 = [] 
     
    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    """ 
  • a/docs/topics/forms/modelforms.txt

    old new  
    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 
  • a/tests/modeltests/model_formsets/models.py

    old new  
    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"""}