Формы связанные с моделями

Курс по Django: https://stepik.org/a/183363

Архив проекта: 47_sitewomen.zip

На предыдущем занятии мы с вами познакомились с созданием форм, не связанных с моделями на примере добавления статьи. Это был несколько искусственный пример, так как добавление нового поста связано с обращением к БД. Из-за этого у нас, фактически, получилось дублирование кода: в классе формы AddPostForm мы прописывали аналогичные атрибуты, что и в классе модели Women. Это не очень хорошо. И когда форма предполагает тесное взаимодействие с какой-либо моделью, то лучше ее напрямую с ней и связать. На этом занятии вы увидите, как это делается.

Перейдем в файл women/forms.py и класс AddPostForm унаследуем от другого базового класса ModelForm. А внутри дочернего класса объявим вложенный класс Meta с атрибутами model и fields:

class AddPostForm(forms.ModelForm):
    class Meta:
        model = Women
        fields = '__all__'

Атрибут model как раз устанавливает связь формы с моделью Women, а свойство fields – определяет поля для отображения в форме. Значение __all__ указывает показывать все поля, кроме тех, что заполняются автоматически. В результате, мы увидим уже готовую форму, только без полей time_create и time_update, так как они наполняются без участия пользователя.

Однако на практике рекомендуется явно прописывать список полей, необходимых для отображения в форме. В нашем случае это будет выглядеть так:

fields = ['title', 'slug', 'content', 'is_published', 'cat', 'husband', 'tags']

Затем, чтобы описать стили оформления для каждого поля, используется атрибут widgets класса Meta:

    class Meta:
        model = Women
        fields = ['title', 'slug', 'content', 'is_published', 'cat', 'husband', 'tags']
        widgets = {
            'title': forms.TextInput(attrs={'class': 'form-input'}),
            'content': forms.Textarea(attrs={'cols': 60, 'rows': 10}),
        }

Обновляем страницу и видим, что эти свойства были применены к указанным полям.

По идее нам бы еще хотелось у списков установить свойства empty_label. Для этого явно пропишем атрибуты для полей выбора, как это мы делали в предыдущей модели:

class AddPostForm(forms.ModelForm):
    cat = forms.ModelChoiceField(queryset=Category.objects.all(), empty_label="Категория не выбрана", label="Категории")
    husband = forms.ModelChoiceField(queryset=Husbands.objects.all(), required=False, empty_label="Не замужем", label="Муж")
 
    class Meta: 
        model = Women
        fields = ['title', 'slug', 'content', 'is_published', 'cat', 'husband', 'tags']
        labels = {'slug': 'URL'}
        widgets = {
            'title': forms.TextInput(attrs={'class': 'form-input'}),
            'content': forms.Textarea(attrs={'cols': 50, 'rows': 5}),
        }

Теперь, наша форма ничем не отличается от предыдущего варианта. Мало того, в ней появился метод save(), который сохраняет переданные данные в БД. Мы им и воспользуемся. Перейдем в файл women/views.py и в функции представления addpage() вместо прежней конструкции:

Women.objects.create(**form.cleaned_data)

запишем:

form.save()

Все, форма готова к использованию! Видите, как фреймворк Django позволяет предельно автоматизировать весь процесс создания и обработки данных форм. Давайте убедимся, что все работает. Перейдем на страницу:

http://127.0.0.1:8000/addpage/

Я специально укажу неуникальный URL и, смотрите, для формы связанной с моделью мы получаем четкое встроенное сообщение о проблеме. То есть, метод save() берет на себя всю проверку корректности записи данных и блок try except нам уже не нужен. Уберем его. Введем уникальный URL и пост успешно добавляется в БД.

Этот пример показывает, насколько упрощается взаимодействие между пользователем и БД, с использованием форм, связанных с моделью.

Создание собственных валидаторов формы

Если стандартных проверок для валидации полей формы недостаточно, то мы по-прежнему можем формировать свои собственные валидаторы, также как и у форм, не связанных с моделью. Например, опишем валидатор для поля title, который бы не позволял вводить строку более 50 символов. Как я уже отметил в SQLite эта проверка не проходит, а просто обрезается заголовок до указанной длины, а мы сделаем так, что пользователю будет показываться сообщение, что название статьи слишком большое.

Для этого в форме AddPostForm объявим метод с именем clean_title и следующим содержимым:

    def clean_title(self):
        title = self.cleaned_data['title']
        if len(title) > 50:
            raise ValidationError('Длина превышает 50 символов')
 
        return title

Мы здесь вначале считываем заголовок, переданный из формы, из словаря очищенных данных – cleaned_data. Затем, проверяем, если длина больше 50 символов, то пользователю будет показываться сообщение «Длина превышает 50 символов». Иначе, возвращается заголовок title, который и будет помещен в БД.

Как видите, все предельно просто. Проверим, как это работает. Введем заголовок длиной более 50 символов, заполним другие поля и при нажатии на кнопку «Добавить» увидим искомое сообщение. Все, мы сделали свой собственный валидатор для поля title на уровне формы. По аналогии можно создавать другие валидаторы для остальных полей, если в этом возникает необходимость.

Курс по Django: https://stepik.org/a/183363

Видео по теме