Code

Ticket #8209: django-r8769-8209.2.diff

File django-r8769-8209.2.diff, 5.7 KB (added by Alex, 6 years ago)

Fixed merge errors with latest trunk

Line 
1diff --git a/django/forms/models.py b/django/forms/models.py
2index 6acd32c..112a2ff 100644
3--- a/django/forms/models.py
4+++ b/django/forms/models.py
5@@ -202,6 +202,39 @@ class BaseModelForm(BaseForm):
6             object_data.update(initial)
7         super(BaseModelForm, self).__init__(data, files, auto_id, prefix, object_data,
8                                             error_class, label_suffix, empty_permitted)
9+    def clean(self):
10+        self.validate_unique()
11+        return self.cleaned_data
12+   
13+    def validate_unique(self):
14+        from django.db.models.fields import FieldDoesNotExist
15+        unique_checks = self.instance._meta.unique_together[:]
16+        form_errors = []
17+        for name, field in self.fields.items():
18+            try:
19+                if name in self.cleaned_data and self.instance._meta.get_field_by_name(name)[0].unique and not self.instance._meta.get_field_by_name(name)[0].primary_key:
20+                    unique_checks.append((name,))
21+            except FieldDoesNotExist:
22+                # This is an extra field that's not on the model, ignore it
23+                pass
24+        for unique_check in [check for check in unique_checks if not any([x in self._errors for x in check])]:
25+            kwargs = dict([(field_name, self.cleaned_data[field_name]) for field_name in unique_check])
26+            qs = self.instance.__class__._default_manager.filter(**kwargs)
27+            if self.instance.pk is not None:
28+                qs = qs.exclude(pk=self.instance.pk)
29+            if qs.count() != 0:
30+                model_name = self.instance._meta.verbose_name.title()
31+                if len(unique_check) == 1:
32+                    field_name = unique_check[0]
33+                    field_label = self.fields[field_name].label
34+                    self._errors[field_name] = ErrorList(["%s with this %s already exists." % (model_name, field_label)])
35+                else:
36+                    field_labels = [self.fields[field_name].label for field_name in unique_check]
37+                    form_errors.append("%s with this %s already exists." % (model_name, ' and '.join(field_labels)))
38+                for field_name in unique_check:
39+                    del self.cleaned_data[field_name]
40+        if form_errors:
41+            raise ValidationError(form_errors)
42 
43     def save(self, commit=True):
44         """
45diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt
46index d161b3f..163c428 100644
47--- a/docs/topics/forms/modelforms.txt
48+++ b/docs/topics/forms/modelforms.txt
49@@ -338,6 +338,16 @@ parameter when declaring the form field::
50    ...     class Meta:
51    ...         model = Article
52 
53+Overriding the clean() method
54+-----------------------------
55+
56+You can overide the ``clean()`` method on a model form to provide additional
57+validation in the same way you can on a normal form.  However, by default the
58+``clean()`` method validates the uniqueness of fields that are marked as unique
59+on the model, and those marked as unque_together, if you would like to overide
60+the ``clean()`` method and maintain the default validation you must call the
61+parent class's ``clean()`` method.
62+
63 Form inheritance
64 ----------------
65 
66@@ -500,4 +510,4 @@ books of a specific author. Here is how you could accomplish this::
67     >>> from django.forms.models import inlineformset_factory
68     >>> BookFormSet = inlineformset_factory(Author, Book)
69     >>> author = Author.objects.get(name=u'Orson Scott Card')
70-    >>> formset = BookFormSet(instance=author)
71\ No newline at end of file
72+    >>> formset = BookFormSet(instance=author)
73diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py
74index 5f714fb..a74b3d3 100644
75--- a/tests/modeltests/model_forms/models.py
76+++ b/tests/modeltests/model_forms/models.py
77@@ -117,9 +117,20 @@ class CommaSeparatedInteger(models.Model):
78     def __unicode__(self):
79         return self.field
80 
81+class Unique(models.Model):
82+    unique_field = models.CharField(max_length=100, unique=True)
83+
84+class UniqueTogether(models.Model):
85+    unique_field_1 = models.CharField(max_length=100)
86+    unique_field_2 = models.CharField(max_length=100)
87+   
88+    class Meta:
89+        unique_together = (('unique_field_1', 'unique_field_2'),)
90+
91 class ArticleStatus(models.Model):
92     status = models.CharField(max_length=2, choices=ARTICLE_STATUS_CHAR, blank=True, null=True)
93 
94+
95 __test__ = {'API_TESTS': """
96 >>> from django import forms
97 >>> from django.forms.models import ModelForm, model_to_dict
98@@ -1132,8 +1143,40 @@ u'1,,2'
99 >>> f.clean('1')
100 u'1'
101 
102-# Choices on CharField and IntegerField
103+>>> class UniqueForm(ModelForm):
104+...     class Meta:
105+...         model = Unique
106+>>> form1 = UniqueForm({'unique_field': 'unique'})
107+>>> form1.is_valid()
108+True
109+>>> obj = form1.save()
110+>>> obj
111+<Unique: Unique object>
112+>>> form2 = UniqueForm({'unique_field': 'unique'})
113+>>> form2.is_valid()
114+False
115+>>> form2._errors
116+{'unique_field': [u'Unique with this Unique field already exists.']}
117+>>> form3 = UniqueForm({'unique_field': 'unique'}, instance=obj)
118+>>> form3.is_valid()
119+True
120 
121+# ModelForm test of unique_together constraint
122+>>> class UniqueTogetherForm(ModelForm):
123+...     class Meta:
124+...         model = UniqueTogether
125+>>> form1 = UniqueTogetherForm({'unique_field_1': 'unique1', 'unique_field_2': 'unique2'})
126+>>> form1.is_valid()
127+True
128+>>> form1.save()
129+<UniqueTogether: UniqueTogether object>
130+>>> form2 = UniqueTogetherForm({'unique_field_1': 'unique1', 'unique_field_2': 'unique2'})
131+>>> form2.is_valid()
132+False
133+>>> form2._errors
134+{'__all__': [u'Unique Together with this Unique field 1 and Unique field 2 already exists.']}
135+
136+# Choices on CharField and IntegerField
137 >>> class ArticleForm(ModelForm):
138 ...     class Meta:
139 ...         model = Article