ModelForm и Form

ModelForm - это обычная Form, которая может автоматически генерировать определенные поля. Поля, которые генерируются автоматически, зависят от содержимого Meta класса и от того, какие поля уже были определены декларативно - прописаны в ручную в классе формы. Фактически, ModelForm будет только генерировать поля, которые отсутствуют в форме, или другими словами, поля, которые не были определены декларативно.

Поля, определенные декларативно, остаются неизменными, поэтому любые настройки, сделанные в атрибутах Meta, таких как widgetslabelshelp_texts или error_messagesигнорируются; они применяются только к полям, которые генерируются автоматически.

Аналогично, поля, прописанные в ручную, не используют свои атрибуты, такие как max_length или required, из соответствующей модели. Если вы хотите сохранить поведение, указанное в модели, вы должны явно установить соответствующие аргументы при объявлении поля формы.

Например, если модель Article выглядит так:

class Article(models.Model):
    headline = models.CharField(
        max_length=200,
        null=True,
        blank=True,
        help_text="Используйте каламбуры щедро",
    )
    content = models.TextField()

и вы хотите выполнить некоторую пользовательскую валидацию для headline, сохраняя значения blank и help_text, как указано, вы можете определить ArticleForm следующим образом:

class ArticleForm(ModelForm):
    headline = MyFormField(
        max_length=200,
        required=False,
        help_text="Используйте каламбуры щедро",
    )
 
    class Meta:
        model = Article
        fields = ["headline", "content"]

class Meta и основные атрибуты

Все основные атрибуты, отвечающие за поведение формы, связанной с моделью, задаются в классе Meta этой формы.

  1. model: Атрибут model указывает, какая модель Django должна быть связана с этой формой. Это обязательный атрибут, так как он определяет, какие поля будут автоматически сгенерированы для формы.

    Например: ArticleForm будет связана с моделью Article, и все поля этой модели будут автоматически включены в форму.

    class ArticleForm(ModelForm):
        class Meta:
            model = Article
  2. fields: Атрибут fields определяет список полей модели, которые должны быть включены в форму. Для формы обязательно должны быть заданы fields или exclude. Чтобы отобразить все поля, можно использовать следующий подход (хоть он и не рекомендуется) fields = '__all__'.

    Например: форма ArticleForm будет включать только поля title и content из модели Article.

    class ArticleForm(ModelForm):
        class Meta:
            model = Article
            fields = ['title', 'content']
  3. exclude: Атрибут exclude определяет список полей модели, которые должны быть исключены из формы. Он игнорируется, если одновременно задан атрибут fields.

    Например: форма ArticleForm будет включать все поля модели Article, кроме поля slug.

    class ArticleForm(ModelForm):
        class Meta:
            model = Article
            exclude = ['slug']
  4. widgets: Атрибут widgets определяет виджеты, которые должны быть использованы для каждого поля формы. Это словарь, где ключи - названия полей, а значения - классы виджетов.

    Например: поле title будет отображаться как текстовая область с 80 столбцами и 2 строками.

    class ArticleForm(ModelForm):
        class Meta:
            model = Article
            widgets = {
                'title': Textarea(attrs={'cols': 80, 'rows': 2})
            }
  5. labels: Атрибут labels определяет человекочитаемые метки для каждого поля формы. Это словарь, где ключи - названия полей, а значения - строки с метками. Вообще если в модели для полей определён параметр verbose_name, то будет использоваться это значение по умолчанию. Тоже самое касается параметра default.

    Например: поле title будет отображаться с меткой “Заголовок”, а поле content - с меткой “Содержание”.

    class ArticleForm(ModelForm):
        class Meta:
            model = Article
            labels = {
                'title': 'Заголовок',
                'content': 'Содержание',
            }
  6. help_texts: Атрибут help_texts определяет вспомогательные тексты для каждого поля формы. Это словарь, где ключи - названия полей, а значения - строки с текстами подсказок. (Чтобы эта подсказка отображалась, шаблон должен использовать связанный с ней тег {{ field.help_text }})

    Например: рядом с полями title и content будут отображаться соответствующие подсказки.

    class ArticleForm(ModelForm):
        class Meta:
            model = Article
            help_texts = {
                'title': 'Введите краткий и информативный заголовок',
                'content': 'Напишите подробное содержание статьи',
            }
  7. error_messages: Атрибут error_messages определяет пользовательские сообщения об ошибках для каждого поля формы. Это словарь, где ключи - названия полей, а значения - словари с сообщениями об ошибках.

    Например: если длина заголовка превысит 200 символов или содержание статьи не будет заполнено, будут выведены соответствующие пользовательские сообщения об ошибках.

    class ArticleForm(ModelForm):
        class Meta:
            model = Article
            error_messages = {
                'title': {
                    'max_length': 'Заголовок не должен превышать 200 символов',
                },
                'content': {
                    'required': 'Содержание статьи является обязательным',
                },
            }
  8. field_classes: Атрибут field_classes определяет классы полей, которые должны быть использованы для каждого поля формы. Это словарь, где ключи - названия полей, а значения - классы полей.

    Например: поле id в форме ArticleForm будет представлено как IntegerField, вместо стандартного поля модели.

    from django.forms.fields import IntegerField
     
    class ArticleForm(ModelForm):
        class Meta:
            model = Article
            field_classes = {
                'id': IntegerField
             }
  9. formfield_callable: Атрибут formfield_callable определяет функцию, которая будет вызываться для создания поля формы для каждого поля модели. Эта функция должна принимать модель, имя поля и опциональные аргументы и возвращать экземпляр поля формы.

    Например: функция custom_formfield используется для создания поля формы. Для поля title она создает CharField с максимальной длиной 100 символов и обязательным заполнением, а для всех остальных полей использует стандартное поле, создаваемое моделью.

    def custom_formfield(model_field, **kwargs):
        if model_field.name == 'title':
            return forms.CharField(max_length=100, required=True)
        else:
            return model_field.formfield(**kwargs)
     
    class ArticleForm(ModelForm):
        class Meta:
            model = Article
            formfield_callable = custom_formfield
  10. Доп. заметка, для отображения полей m2m в виде чекбоксов:

    tags = forms.ModelMultipleChoiceField(queryset=Tags.objects.all(), **widget=forms.CheckboxSelectMultiple**)

Методы save() и save_m2m()

У формы, связанной с моделью по умолчанию существуют методы save() и save_m2m() в отличии от формы, которая ни с какой моделью не связана.

  1. save() метод:

    • Основная функция - сохранение экземпляра модели, связанной с формой.

    • Вызывается, когда форма прошла валидацию и ее данные являются достоверными.

    • Возвращает сохраненный экземпляр модели.

    • Автоматически обрабатывает связанные поля (ForeignKey, OneToOneField), сохраняя их вместе с основной моделью.

    • Пример использования:

      class ArticleForm(ModelForm):
          class Meta:
              model = Article
              fields = ['title', 'content']
       
      article_form = ArticleForm(request.POST)
      if article_form.is_valid():
          article = article_form.save()
      # article - сохраненный экземпляр модели Article
  2. save_m2m() метод:

    • Предназначен для сохранения связей многие-ко-многим (ManyToManyField).

    • Вызывается после save() метода, так как при сохранении экземпляра модели связи Many-to-Many еще не были созданы.

    • Не возвращает никакого значения, а просто сохраняет связи Many-to-Many.

    • Пример использования:

      class ArticleForm(ModelForm):
          class Meta:
              model = Article
              fields = ['title', 'content', 'categories']
       
      article_form = ArticleForm(request.POST)
      if article_form.is_valid():
          article = article_form.save()
          article_form.save_m2m()
      # связи Many-to-Many между Article и Category теперь сохранены

Валидация данных

По сути эта часть практически ничем не отличается от описанной ранее для форм, которые с моделями не связаны. Единственное различие здесь в том, что можно определять логику для валидатора и на уровне модели, если форма связана с моделью. Например,

  1. Определение валидатора в модели:
    • Вы можете определить пользовательский валидатор в модели, используя метод clean() или clean_<field_name>().

    • Пример:

      from django.core.exceptions import ValidationError
      from django.db import models
       
      class Article(models.Model):
          title = models.CharField(max_length=200)
          content = models.TextField()
       
          def clean_title(self):
              title = self.title
              if "bad word" in title.lower():
                  raise ValidationError("Заголовок не должен содержать нецензурные слова")
              return title
    • В этом примере мы определили пользовательский валидатор clean_title(), который проверяет, содержит ли заголовок статьи “нехорошие” слова.

  2. Использование валидатора в форме:
    • Когда вы создаете ModelForm, связанную с моделью Article, валидатор clean_title() будет автоматически применяться к полю title в форме.

    • Пример:

      from django.forms import ModelForm
      from .models import Article
       
      class ArticleForm(ModelForm):
          class Meta:
              model = Article
              fields = ['title', 'content']
    • Теперь, когда вы создадите экземпляр ArticleForm и вызовете is_valid(), валидатор clean_title() из модели Article будет автоматически применен к полю title в форме.

    • Если в заголовке будут обнаружены “нехорошие” слова, форма не пройдет валидацию, и вы сможете обработать ошибку в представлении.

Сравнение подходов к валидации данных:

  1. Валидаторы в модели:
    • Централизация логики валидации: Определяя валидаторы непосредственно в модели, вы централизуете всю логику валидации в одном месте, что делает ваш код более организованным и легко поддерживаемым.
    • Повторное использование: Если вы используете одну и ту же модель в нескольких формах, то валидаторы, определенные в модели, будут автоматически применяться ко всем этим формам, что уменьшает дублирование кода.
  2. Валидаторы в форме:
    • Гибкость и специализация: Иногда логика валидации зависит от контекста использования формы. Определяя валидаторы в форме, вы можете более точно контролировать, какие валидаторы должны применяться в конкретном случае.
    • Легкость тестирования форм: В некоторых случаях вам может быть удобнее тестировать логику валидации, когда она определена непосредственно в форме, так как это позволяет изолировать тесты для формы от остальной логики вашего приложения.

〰〰〰 𓆝 𓆟 𓆞 𓆝 𓆟 𓆝 𓆟 𓆞 〰〰〰