original source : https://georgexyz.com/django-model-form-validation.html

django modelform을 이해하기 위해서는 model, form 각각 따로 이해하고 나서 종합적으로 이해한다. 이를 이해하고 나서는 formset에 대해 이해하고 modelformset에 대해 이해할수 있다.

Django model and form validation is a somewhat complicated and confusing topic in Django app development. A developer needs to understand several basic concepts such as model, model field, form, model form, etc. to have a good understanding of validation. Most Django books and online tutorials do not have a good discussion on validation.

Django official documentation has detailed descriptions on validation. However, the contents are dispersed on several places. This post describes the materials I have read on this topic.

Validator Function

The validator official documentation page is a good starting point to study model and form validation.

The validator function is a callable that takes a value and raises a ValidationError if not validated. Here is an example from the page:

from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _

def validate_even(value):
    if value % 2 != 0:
        raise ValidationError(
            _('%(value)s is not an even number'),
            params={'value': value},
        )

from django.db import models

class MyModel(models.Model):
    even_field = models.IntegerField(validators=[validate_even])

The subsection how validators are run on the validator page has three links.

  • The second link validating objects is about model validation. The link points to a subsection on the model instance reference page.
  • The first link form validation points to a separate page about form validation.
  • The third link goes to the ModelForm page.

Model Validation

A model’s full_clean() method performs model validation. The method calls three other methods:

  • clean_fields() method
  • clean() method, as a whole
  • validate_unique() method

The model save() method does NOT call full_clean() method automatically. A programmer needs to call it manually to trigger model validation like the below code.

try:
    article.full_clean()
except ValidationError as e:
    ...
    # handle the error

A stack overflow answer shows a typical pattern to conduct custom model validation. The model class overrides the clean() method to provide custom model validation and the save() method to call full_clean method. The example code is shown below:

class BaseModel(models.Model):
    # model fields 

    def clean(self, *args, **kwargs):
        # add custom validation here
        super(BaseModel, self).clean(*args, **kwargs)

    def save(self, *args, **kwargs):
        self.full_clean()
        super(BaseModel, self).save(*args, **kwargs)

Another stack overflow answer shows how to use custom model validation or simply use model field’s built-in validator.

Model field’s validation will not kick in unless the full_clean() method is explicitly called. For example, the p2.save() below would not raise an exception when called.

class PageModel(models.Model):
    name = models.CharField(max_length=50)
    slug = models.SlugField(max_length=50)

>>> from page.models import PageModel #page app name
>>> p1 = PageModel(name='Page1', slug='page1')
>>> p1.save()
>>> p2 = PageModel(name='Page2', slug='page2#$%')
>>> p2.save()        # no error
>>> p2.full_clean()  # raise exception

Checking clean_fields() method source code, it has the following lines. The f.clean(...) method calls validation method on a model field.

try:
    setattr(self, f.attname, f.clean(raw_value, self))
except ValidationError as e:
    errors[f.name] = e.error_list

Form Validation

While model validation is a subsection on a Django documentation page, the form validation is on a separate page. Form validation is normally executed when the is_valid() method is called on a form. A programmer can also trigger form validation by accessing errors attribute or call full_clean() method of a form.

Form validation has a series of steps:

  • to_python() method on a field, correct data type
  • validation() method on a field
  • run_validators() method on a field
  • clean() method on a Field subclass, which calls above three methods and returns the clean data
  • clean_<fieldname>() method has access to cleaned_data Python object and returns value that replaces data in cleaned_data
  • clean() method of form, for multiple fields

The same documetation page has several nice examples, which are based on the model shown below:

class ContactForm(forms.Form):
    subject = forms.CharField(max_length=100)
    message = forms.CharField()
    sender = forms.EmailField()
    recipients = MultiEmailField()
    cc_myself = forms.BooleanField(required=False)

The same page points out that “there are special considerations when overriding the clean() method of a ModelForm subclass.”

Chapter 7 of Andrew Pinkham’s Django Unleashed book, titled allowing user input with forms, has good example on how to override clean_<fieldname> method. The discussion on model validation and form validation in this chapter is better than other Django books I have read.

ModelForm Validation

The form validation steps described in the previous section also apply to ModelForm validation. In addition to that, Model.full_clean() method is triggered after the form’s clean() method is called. So, model validation methods are not triggered by model save() method, but model validation methods are triggered by ModelForm validation. This stack overflow question discusses this exact issue. The accepted answer also has code example on model validation.

Error messages at the form field level take precedence over the error messages defined at the model field level.

original source : http://www.deekras.com/django-validations-during-form-processing.html

form validation에 대한 documentation이 좀 이해하기 힘든데 이 블로그는 간단명료학 잘 정리했다.

This post is mostly based on the Django Docs on Form and Field Validation. I reformatted the information in a way that feels easier to use.

There are 3 types of cleaning methods that are run during form processing. These are normally executed when you call the is_valid() method on a form.  (is_valid() runs validation routines for all fields on the form. When this method is called, if all fields contain valid data, it will:

  • return True
  • place the form’s data in its cleaned_data attribute.)

In general, any cleaning method can raise a ValidationError if there is a problem with the data it is processing; it passes the relevant information to the ValidationError constructor.

Steps of validation

The methods below are run in the order given, one field at a time. That is, for each field in the form (in the order they are declared in the form definition). Then the form.clean(), or its override, is executed regardless if the previous methods have raised errors. If the Field.clean() method raises a ValidationError, its field-specific cleaning methods are not called. However, the cleaning methods for all remaining fields are still executed.

Normally, the clean() method will be run and it will take care of the first three validations (to_python(), validate(), run_validators()). But you can customize any of them, and when the clean() method is executed, it will run the customized method.

1. to_python() method on a Field

  • WHAT IT DOES: It coerces the value to correct datatype and raises ValidationError if that is not possible. This method accepts the raw value from the widget and returns the converted value.
  • EXAMPLE: a FloatField will turn the data into a Python float or raise a ValidationError.
  • HANDLES ERRORS: raises ValidationError on any error
  • RETURNS: returns the converted value.

2. validate() method on a Field

  • WHAT IT DOES: handles field-specific validation that is not suitable for a validator. It takes a value that has been coerced to correct datatype and raises ValidationError on any error.
  • HANDLES ERRORS: raises ValidationError on any error
  • RETURNS: This method does not return anything and shouldn’t alter the value.
  • NOTES: You should override it to handle validation logic that you can’t or don’t want to put in a validator.

3. run_validators() method on a Field

  • WHAT IT DOES: runs all of the field’s validators
  • HANDLES ERRORS: aggregates all the errors into a single ValidationError.
  • RETURNS:
  • NOTES: You shouldn’t need to override this method.

4. The clean() method on a Field subclass.

  • WHAT IT DOES: This is responsible for running to_python, validate and run_validators in the correct order and propagating their errors.
  • HANDLES ERRORS: If, at any time, any of the methods raise ValidationError, the validation stops and that error is raised.
  • RETURNS: This method returns the clean data, which is then inserted into the cleaned_data dictionary of the form.

5. The clean_<fieldname>() method in a form subclass – where <fieldname> is replaced with the name of the form field attribute.

  • WHAT IT DOES: This method does any cleaning that is specific to that particular attribute, unrelated to the type of field that it is.
  • HOW TO USE: This method is not passed any parameters. You will need to look up the value of the field in self.cleaned_data and remember that it will be a Python object at this point, not the original string submitted in the form (it will be in cleaned_data because the general field clean() method, above, has already cleaned the data once).
  • HANDLES ERRORS:
  • RETURNS: the cleaned value obtained from cleaned_data – regardless of whether it changed anything or not.

6. The Form subclass’s clean() method.

NOTES: Also note that there are special considerations when overriding the clean() method of a ModelForm subclass. (see the ModelForm documentation for more information)

  • WHAT IT DOES: This method can perform any validation that requires access to multiple fields from the form at once.
  • EXAMPLE: Checks that if field A is supplied, field B must contain a valid email address and the like.
  • HOW TO USE: Since the field validation methods have been run by the time clean() is called, you also have access to the form’s errors attribute which contains all the errors raised by cleaning of individual fields.
  • HANDLES ERRORS: Note that any errors raised by your Form.clean() override will not be associated with any field in particular. They go into a special “field” (called __all__), which you can access via the non_field_errors() method if you need to. If you want to attach errors to a specific field in the form, you need to call add_error().
  • RETURNS: This method can return a completely different dictionary if it wishes, which will be used as the cleaned_data.

Raising ValidationError examples:

if not flag:
    raise ValidationError('Please submit flag') –  a simple example
    raise ValidationError(_('text: %(flag)s'), 
                            code='no flag', 
                            params={'flag': '42'},)

multiple errors can be created as a list

    raise ValidationError([
        ValidationError(_('Error 1'), code='error1'),
        ValidationError(_('Error 2'), code='error2'),
    ])

Writing Validators

There are many builtin validators that match the field type (ex: EmailValidator for EmailField). Those validators can be customized too. (ex: class EmailValidator([message=None, code=None, whitelist=None])

Here’s a sample custom validator:

from django.core.exceptions import ValidationError

def validate_even(value):
   if value % 2 != 0:
      raise ValidationError('%s is not an even number' % value)

Then, this validator can be used for any fields when setting up the models:

class MyModel(models.Model):
   even_field = models.IntegerField(validators=[validate_even])

It can also be used for forms:

class MyForm(forms.Form):
   even_field = forms.IntegerField(validators=[validate_even])

Validators will not be run automatically when you save a model, but if you are using a ModelForm, it will run your validators on any fields that are included in your form.

original source : http://www.deekras.com/django-validations-during-form-processing.html

There are 3 types of cleaning methods that are run during form processing. These are normally executed when you call the is_valid() method on a form.  (is_valid() runs validation routines for all fields on the form. When this method is called, if all fields contain valid data, it will:

  • return True
  • place the form’s data in its cleaned_data attribute.)

In general, any cleaning method can raise a ValidationError if there is a problem with the data it is processing; it passes the relevant information to the ValidationError constructor.

Steps of validation

The methods below are run in the order given, one field at a time. That is, for each field in the form (in the order they are declared in the form definition). Then the form.clean(), or its override, is executed regardless if the previous methods have raised errors. If the Field.clean() method raises a ValidationError, its field-specific cleaning methods are not called. However, the cleaning methods for all remaining fields are still executed.

Normally, the clean() method will be run and it will take care of the first three validations (to_python(), validate(), run_validators()). But you can customize any of them, and when the clean() method is executed, it will run the customized method.

1. to_python() method on a Field

  • WHAT IT DOES: It coerces the value to correct datatype and raises ValidationError if that is not possible. This method accepts the raw value from the widget and returns the converted value.
  • EXAMPLE: a FloatField will turn the data into a Python float or raise a ValidationError.
  • HANDLES ERRORS: raises ValidationError on any error
  • RETURNS: returns the converted value.

2. validate() method on a Field

  • WHAT IT DOES: handles field-specific validation that is not suitable for a validator. It takes a value that has been coerced to correct datatype and raises ValidationError on any error.
  • HANDLES ERRORS: raises ValidationError on any error
  • RETURNS: This method does not return anything and shouldn’t alter the value.
  • NOTES: You should override it to handle validation logic that you can’t or don’t want to put in a validator.

3. run_validators() method on a Field

  • WHAT IT DOES: runs all of the field’s validators
  • HANDLES ERRORS: aggregates all the errors into a single ValidationError.
  • RETURNS:
  • NOTES: You shouldn’t need to override this method.

4. The clean() method on a Field subclass.

  • WHAT IT DOES: This is responsible for running to_python, validate and run_validators in the correct order and propagating their errors.
  • HANDLES ERRORS: If, at any time, any of the methods raise ValidationError, the validation stops and that error is raised.
  • RETURNS: This method returns the clean data, which is then inserted into the cleaned_data dictionary of the form.

5. The clean_<fieldname>() method in a form subclass – where <fieldname> is replaced with the name of the form field attribute.

  • WHAT IT DOES: This method does any cleaning that is specific to that particular attribute, unrelated to the type of field that it is.
  • HOW TO USE: This method is not passed any parameters. You will need to look up the value of the field in self.cleaned_data and remember that it will be a Python object at this point, not the original string submitted in the form (it will be in cleaned_data because the general field clean() method, above, has already cleaned the data once).
  • HANDLES ERRORS:
  • RETURNS: the cleaned value obtained from cleaned_data – regardless of whether it changed anything or not.

6. The Form subclass’s clean() method.

  • NOTES: Also note that there are special considerations when overriding the clean() method of a ModelForm subclass. (see the ModelForm documentation for more information)
  • WHAT IT DOES: This method can perform any validation that requires access to multiple fields from the form at once.
  • EXAMPLE: Checks that if field A is supplied, field B must contain a valid email address and the like.
  • HOW TO USE: Since the field validation methods have been run by the time clean() is called, you also have access to the form’s errors attribute which contains all the errors raised by cleaning of individual fields.
  • HANDLES ERRORS: Note that any errors raised by your Form.clean() override will not be associated with any field in particular. They go into a special “field” (called __all__), which you can access via the non_field_errors() method if you need to. If you want to attach errors to a specific field in the form, you need to call add_error().
  • RETURNS: This method can return a completely different dictionary if it wishes, which will be used as the cleaned_data.

Raising ValidationError examples:

if not flag:
    raise ValidationError('Please submit flag') –  a simple example
    raise ValidationError(_('text: %(flag)s'), 
                            code='no flag', 
                            params={'flag': '42'},)

multiple errors can be created as a list

    raise ValidationError([
        ValidationError(_('Error 1'), code='error1'),
        ValidationError(_('Error 2'), code='error2'),
    ])

Writing Validators

There are many builtin validators that match the field type (ex: EmailValidator for EmailField). Those validators can be customized too. (ex: class EmailValidator([message=None, code=None, whitelist=None])

Here’s a sample custom validator:

from django.core.exceptions import ValidationError

def validate_even(value):
   if value % 2 != 0:
      raise ValidationError('%s is not an even number' % value)

Then, this validator can be used for any fields when setting up the models:

class MyModel(models.Model):
   even_field = models.IntegerField(validators=[validate_even])

It can also be used for forms:

class MyForm(forms.Form):
   even_field = forms.IntegerField(validators=[validate_even])

Validators will not be run automatically when you save a model, but if you are using a ModelForm, it will run your validators on any fields that are included in your form.

form validation doc https://docs.djangoproject.com/en/3.0/ref/forms/validation/

참고 블로그) 내용이 official doc보다 간결하다. http://www.deekras.com/django-validations-during-form-processing.html

These are normally executed when you call the is_valid() method on a form. There are other things that can also trigger cleaning and validation (accessing the errors attribute or calling full_clean() directly), but normally they won’t be needed. 

(form validation은 form의 is_valid()를 통해 수행된다. form에 있는 errors에 접근하거나 full_clean()을 통해 수행되기도 한다. full_clean()는 model validation에서 사용되는 method이름과 같다.)

  •  to_python() 스트링을 python data type으로 전환한다.
  • validate() method on a Field handles field-specific validation that is not suitable for a validator. 
  • run_validators() method on a Field runs all of the field’s validators and aggregates all the errors into a single ValidationError. You shouldn’t need to override this method.
  • clean() method on a Field subclass is responsible for running to_python(), validate(), and run_validators() in the correct order and propagating their errors. This method returns the clean data, which is then inserted into the cleaned_data dictionary of the form.
  • clean_<fieldname>() method is called on a form subclass. This method does any cleaning that is specific to that particular attribute, unrelated to the type of field that it is. This method is not passed any parameters. You will need to look up the value of the field in self.cleaned_data and remember that it will be a Python object at this point, not the original string submitted in the form (it will be in cleaned_data because the general field clean() method, above, has already cleaned the data once).

기본적으로 위와 같은 순서로 진행되며 각 단계에서 무제가 발생하면 ValidationError를 raise한다.

Raising ValidationError

# Good
ValidationError(_('Invalid value'), code='invalid')

# Bad
ValidationError(_('Invalid value'))

# Good
ValidationError(
    _('Invalid value: %(value)s'),
    params={'value': '42'},
)

# Bad
ValidationError(_('Invalid value: %s') % value)

# Good
ValidationError(
    _('Invalid value: %(value)s'),
    params={'value': '42'},
)

# Bad
ValidationError(
    _('Invalid value: %s'),
    params=('42',),
)

Wrap the message with gettext to enable translation: _() 를 말한다.

# Good
ValidationError(_('Invalid value'))

# Bad
ValidationError('Invalid value')

Putting it all together:

raise ValidationError(
    _('Invalid value: %(value)s'),
    code='invalid',
    params={'value': '42'},)

Raising multiple errors

# Good
raise ValidationError([
    ValidationError(_('Error 1'), code='error1'),
    ValidationError(_('Error 2'), code='error2'),
])

# Bad
raise ValidationError([
    _('Error 1'),
    _('Error 2'),
])

기존 field를 extends해서 새로운 field를 만들고 새로운 validation rule을 지정하는 방법

1방법)

from django.core import validators
from django.forms import CharField

class SlugField(CharField):
    default_validators = [validators.validate_slug]
slug = forms.SlugField()

2방법)

slug = forms.CharField(validators=[validators.validate_slug])

validator만드는 방법 https://docs.djangoproject.com/en/3.0/ref/validators/

form의 field에 validation을 customize하는 방법 (to_python , validate)

from django import forms
from django.core.validators import validate_email

class MultiEmailField(forms.Field):
    def to_python(self, value):
        """Normalize data to a list of strings."""
        # Return an empty list if no input was given.
        if not value:
            return []
        return value.split(',')

    def validate(self, value):
        """Check if value consists only of valid emails."""
        # Use the parent's handling of required fields, etc.
        super().validate(value)
        for email in value:
            validate_email(email)
class ContactForm(forms.Form):
    subject = forms.CharField(max_length=100)
    message = forms.CharField()
    sender = forms.EmailField()
    recipients = MultiEmailField()
    cc_myself = forms.BooleanField(required=False)

Use MultiEmailField like any other form field. When the is_valid() method is called on the form, the MultiEmailField.clean() method will be run as part of the cleaning process and it will, in turn, call the custom to_python() and validate() methods.

form 단위에서 field validation 을 customize 하는 경우

from django import forms

class ContactForm(forms.Form):
    # Everything as before.
    ...

    def clean_recipients(self):
        data = self.cleaned_data['recipients']
        if "fred@example.com" not in data:
            raise forms.ValidationError("You have forgotten about Fred!")

        # Always return a value to use as the new cleaned data, even if
        # this method didn't change it.
        return data

이미 fileld단위의 validation이 끝났기 때문에 cleaned_data에 접근 가능하다.

Cleaning and validating fields that depend on each other (form 단위에서 여러개의 fields을 이용해서 validation하는 경우)

from django import forms

class ContactForm(forms.Form):
    # Everything as before.
    ...

    def clean(self):
        cleaned_data = super().clean()
        cc_myself = cleaned_data.get("cc_myself")
        subject = cleaned_data.get("subject")

        if cc_myself and subject:
            # Only do something if both fields are valid so far.
            if "help" not in subject:
                raise forms.ValidationError(
                    "Did not send for 'help' in the subject despite "
                    "CC'ing yourself."
                )

In this code, if the validation error is raised, the form will display an error message at the top of the form (normally) describing the problem.

The call to super().clean() in the example code ensures that any validation logic in parent classes is maintained. If your form inherits another that doesn’t return a cleaned_data dictionary in its clean() method (doing so is optional), then don’t assign cleaned_data to the result of the super() call and use self.cleaned_data instead:

def clean(self):
    super().clean()
    cc_myself = self.cleaned_data.get("cc_myself")
    ...
from django import forms
class ContactForm(forms.Form):
    # Everything as before.
    ...

    def clean(self):
        cleaned_data = super().clean()
        cc_myself = cleaned_data.get("cc_myself")
        subject = cleaned_data.get("subject")

        if cc_myself and subject and "help" not in subject:
            msg = "Must put 'help' in subject when cc'ing yourself."
            self.add_error('cc_myself', msg)
            self.add_error('subject', msg)

설명 블로그

modelformset changing query set 

modelformset은 기본으로 이미 입력된 모든 데이터를 보여준다. 이를 바꿀때 사용

https://docs.djangoproject.com/en/3.0/topics/forms/modelforms/#changing-the-queryset

formset

https://docs.djangoproject.com/en/3.0/topics/forms/formsets/

basemodelformset

https://docs.djangoproject.com/en/3.0/topics/forms/modelforms/#django.forms.models.BaseModelFormSet

Django forms ChoiceField with dynamic values… | ilian.io

Validating models in Django 1.8 – Hack&Stack