From 5b26a014a81ba0d404d46e11d2b45c01d92b97e5 Mon Sep 17 00:00:00 2001
From: Alasdair Nicol <alasdair@thenicols.net>
Date: Tue, 18 Nov 2014 17:58:43 +0000
Subject: [PATCH] Fixed #23865 -- documented how to assign errors to a field in
 Model.clean()

Also added a unit test wit the simpler syntax which we have documented,
where the dictionary values are strings.
---
 docs/ref/models/instances.txt | 20 +++++++++++++++++---
 tests/model_forms/models.py   |  2 ++
 tests/model_forms/tests.py    |  7 +++++++
 3 files changed, 26 insertions(+), 3 deletions(-)

diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt
index 21c4c86c13..4e8a1d1ee8 100644
--- a/docs/ref/models/instances.txt
+++ b/docs/ref/models/instances.txt
@@ -200,9 +200,10 @@ access to more than a single field::
 Note, however, that like :meth:`Model.full_clean()`, a model's ``clean()``
 method is not invoked when you call your model's :meth:`~Model.save()` method.
 
-Any :exc:`~django.core.exceptions.ValidationError` exceptions raised by
-``Model.clean()`` will be stored in a special key error dictionary key,
-:data:`~django.core.exceptions.NON_FIELD_ERRORS`, that is used for errors
+In the above example, the :exc:`~django.core.exceptions.ValidationError`
+exception raised by ``Model.clean()`` was instantiated with a string, so it
+will be stored in a special error dictionary key,
+:data:`~django.core.exceptions.NON_FIELD_ERRORS`. This key is used for errors
 that are tied to the entire model instead of to a specific field::
 
     from django.core.exceptions import ValidationError, NON_FIELD_ERRORS
@@ -211,6 +212,19 @@ that are tied to the entire model instead of to a specific field::
     except ValidationError as e:
         non_field_errors = e.message_dict[NON_FIELD_ERRORS]
 
+To assign exceptions to a specific field, instantiate the
+:exc:`~django.core.exceptions.ValidationError` with a dictionary, where the
+keys are the field names. We could update the previous example to assign the
+error to the ``pub_date`` field::
+
+    class Article(models.Model):
+        ...
+        def clean(self):
+            # Don't allow draft entries to have a pub_date.
+            if self.status == 'draft' and self.pub_date is not None:
+                raise ValidationError({'pub_date': 'Draft entries may not have a publication date.'})
+            ...
+
 Finally, ``full_clean()`` will check any unique constraints on your model.
 
 .. method:: Model.validate_unique(exclude=None)
diff --git a/tests/model_forms/models.py b/tests/model_forms/models.py
index 1b5ae90b07..4ab681d1ae 100644
--- a/tests/model_forms/models.py
+++ b/tests/model_forms/models.py
@@ -381,6 +381,8 @@ class CustomErrorMessage(models.Model):
     def clean(self):
         if self.name1 == 'FORBIDDEN_VALUE':
             raise ValidationError({'name1': [ValidationError('Model.clean() error messages.')]})
+        elif self.name1 == 'FORBIDDEN_VALUE2':
+            raise ValidationError({'name1': 'Model.clean() error messages (simpler syntax).'})
         elif self.name1 == 'GLOBAL_ERROR':
             raise ValidationError("Global error message.")
 
diff --git a/tests/model_forms/tests.py b/tests/model_forms/tests.py
index 962e5b969f..4ff900e1b2 100644
--- a/tests/model_forms/tests.py
+++ b/tests/model_forms/tests.py
@@ -2269,6 +2269,13 @@ class ModelFormCustomErrorTests(TestCase):
             str(form.errors['name1']),
             '<ul class="errorlist"><li>Model.clean() error messages.</li></ul>'
         )
+        data = {'name1': 'FORBIDDEN_VALUE2', 'name2': 'ABC'}
+        form = CustomErrorMessageForm(data)
+        self.assertFalse(form.is_valid())
+        self.assertHTMLEqual(
+            str(form.errors['name1']),
+            '<ul class="errorlist"><li>Model.clean() error messages (simpler syntax).</li></ul>'
+        )
         data = {'name1': 'GLOBAL_ERROR', 'name2': 'ABC'}
         form = CustomErrorMessageForm(data)
         self.assertFalse(form.is_valid())