Commit 8847a0c6 authored by Loic Bistuer's avatar Loic Bistuer Committed by Tim Graham
Browse files

Fixed #16192 -- Made unique error messages in ModelForm customizable.

Overriding the error messages now works for both unique fields, unique_together
and unique_for_date.

This patch changed the overriding logic to allow customizing NON_FIELD_ERRORS
since previously only fields' errors were customizable.

Refs #20199.

Thanks leahculver for the suggestion.
parent 65131911
Loading
Loading
Loading
Loading
+36 −20
Original line number Diff line number Diff line
@@ -941,36 +941,52 @@ class Model(six.with_metaclass(ModelBase)):
                )
        return errors

    def date_error_message(self, lookup_type, field, unique_for):
    def date_error_message(self, lookup_type, field_name, unique_for):
        opts = self._meta
        return _("%(field_name)s must be unique for %(date_field)s %(lookup)s.") % {
            'field_name': six.text_type(capfirst(opts.get_field(field).verbose_name)),
            'date_field': six.text_type(capfirst(opts.get_field(unique_for).verbose_name)),
            'lookup': lookup_type,
        field = opts.get_field(field_name)
        return ValidationError(
            message=field.error_messages['unique_for_date'],
            code='unique_for_date',
            params={
                'model': self,
                'model_name': six.text_type(capfirst(opts.verbose_name)),
                'lookup_type': lookup_type,
                'field': field_name,
                'field_label': six.text_type(capfirst(field.verbose_name)),
                'date_field': unique_for,
                'date_field_label': six.text_type(capfirst(opts.get_field(unique_for).verbose_name)),
            }
        )

    def unique_error_message(self, model_class, unique_check):
        opts = model_class._meta
        model_name = capfirst(opts.verbose_name)

        params = {
            'model': self,
            'model_class': model_class,
            'model_name': six.text_type(capfirst(opts.verbose_name)),
            'unique_check': unique_check,
        }

        # A unique field
        if len(unique_check) == 1:
            field_name = unique_check[0]
            field = opts.get_field(field_name)
            field_label = capfirst(field.verbose_name)
            # Insert the error into the error dict, very sneaky
            return field.error_messages['unique'] % {
                'model_name': six.text_type(model_name),
                'field_label': six.text_type(field_label)
            }
            field = opts.get_field(unique_check[0])
            params['field_label'] = six.text_type(capfirst(field.verbose_name))
            return ValidationError(
                message=field.error_messages['unique'],
                code='unique',
                params=params,
            )

        # unique_together
        else:
            field_labels = [capfirst(opts.get_field(f).verbose_name) for f in unique_check]
            field_labels = get_text_list(field_labels, _('and'))
            return _("%(model_name)s with this %(field_label)s already exists.") % {
                'model_name': six.text_type(model_name),
                'field_label': six.text_type(field_labels)
            }
            params['field_labels'] = six.text_type(get_text_list(field_labels, _('and')))
            return ValidationError(
                message=_("%(model_name)s with this %(field_labels)s already exists."),
                code='unique_together',
                params=params,
            )

    def full_clean(self, exclude=None, validate_unique=True):
        """
+2 −0
Original line number Diff line number Diff line
@@ -105,6 +105,8 @@ class Field(RegisterLookupMixin):
        'blank': _('This field cannot be blank.'),
        'unique': _('%(model_name)s with this %(field_label)s '
                    'already exists.'),
        'unique_for_date': _("%(field_label)s must be unique for "
                             "%(date_field_label)s %(lookup_type)s."),
    }
    class_lookups = default_lookups.copy()

+1 −3
Original line number Diff line number Diff line
@@ -8,7 +8,7 @@ from collections import OrderedDict
import copy
import warnings

from django.core.exceptions import ValidationError
from django.core.exceptions import ValidationError, NON_FIELD_ERRORS
from django.forms.fields import Field, FileField
from django.forms.utils import flatatt, ErrorDict, ErrorList
from django.forms.widgets import Media, MediaDefiningClass, TextInput, Textarea
@@ -21,8 +21,6 @@ from django.utils import six

__all__ = ('BaseForm', 'Form')

NON_FIELD_ERRORS = '__all__'


def pretty_name(name):
    """Converts 'first_name' to 'First name'"""
+11 −5
Original line number Diff line number Diff line
@@ -373,15 +373,21 @@ class BaseModelForm(BaseForm):

    def _update_errors(self, errors):
        # Override any validation error messages defined at the model level
        # with those defined on the form fields.
        # with those defined at the form level.
        opts = self._meta
        for field, messages in errors.error_dict.items():
            if field not in self.fields:
            if (field == NON_FIELD_ERRORS and opts.error_messages and
                    NON_FIELD_ERRORS in opts.error_messages):
                error_messages = opts.error_messages[NON_FIELD_ERRORS]
            elif field in self.fields:
                error_messages = self.fields[field].error_messages
            else:
                continue
            field = self.fields[field]

            for message in messages:
                if (isinstance(message, ValidationError) and
                        message.code in field.error_messages):
                    message.message = field.error_messages[message.code]
                        message.code in error_messages):
                    message.message = error_messages[message.code]

        self.add_error(None, errors)

+9 −0
Original line number Diff line number Diff line
@@ -124,6 +124,15 @@ ValidationError
    :ref:`Model Field Validation <validating-objects>` and the
    :doc:`Validator Reference </ref/validators>`.

NON_FIELD_ERRORS
~~~~~~~~~~~~~~~~
.. data:: NON_FIELD_ERRORS

``ValidationError``\s that don't belong to a particular field in a form
or model are classified as ``NON_FIELD_ERRORS``. This constant is used
as a key in dictonaries that otherwise map fields to their respective
list of errors.

.. currentmodule:: django.core.urlresolvers

URL Resolver exceptions
Loading