JavaRush /Курси /Модуль 3: Django /Перевизначення методів `save()` та `clean()` у ModelForm

Перевизначення методів `save()` та `clean()` у ModelForm

Модуль 3: Django
Рівень 13 , Лекція 5
Відкрита

Django надає ModelForm не тільки як інструмент для прискорення розробки, але й як гнучку сутність, яку можна адаптувати під бізнес-логіку. А ось методи save() та clean() — це як читерський доступ до адмінки у грі. Вони дозволяють втручатися у звичний процес обробки даних, щоб досягти потрібного результату, коли стандартного функціоналу недостатньо.

То коли варто перевизначати ці методи?

  • Метод save(): якщо тобі потрібно змінювати дані перед збереженням або додавати додаткові дії при збереженні (наприклад, записи у лог, сповіщення, створення пов'язаних об'єктів).
  • Метод clean(): для складної валідації даних, яка повинна враховувати одразу кілька полів (наприклад, якщо поле "Дата завершення" не може бути раніше "Дати початку").

Перевизначення методу save()

Метод save() — це серце будь-якого ModelForm. Він відповідає за збереження валідованих даних форми в базу даних. Коли вам потрібно змінити стандартну поведінку збереження, ви можете додати свою логіку в перевизначений метод.

Синтаксис і структура методу save()

def save(self, commit=True):
    # Ваша додаткова логіка
    instance = super().save(commit=False)  # Створення instance моделі, але без збереження в базу
    # Зміна даних instance, якщо потрібно
    if commit:
        instance.save()  # Збереження instance в базу даних
    return instance

Приклад 1. Додавання значення за замовчуванням

Припустимо, у нас є модель Article, і ми хочемо автоматично призначати поточного користувача автором статті.

Модель:

from django.db import models

class Article(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()
    author = models.ForeignKey('auth.User', on_delete=models.CASCADE)
    published_at = models.DateTimeField(auto_now_add=True)

Форма:

from django import forms
from .models import Article

class ArticleForm(forms.ModelForm):
    class Meta:
        model = Article
        fields = ['title', 'content']

    def save(self, commit=True):
        instance = super().save(commit=False)  # Створюємо об'єкт, але не зберігаємо
        instance.author = self.current_user   # Призначаємо автора
        if commit:
            instance.save()  # Зберігаємо об'єкт
        return instance

Представлення:

from django.shortcuts import render, redirect
from .forms import ArticleForm

def create_article(request):
    if request.method == 'POST':
        form = ArticleForm(request.POST)
        if form.is_valid():
            form.current_user = request.user  # Передаємо поточного користувача у форму
            form.save()
            return redirect('article_list')
    else:
        form = ArticleForm()
    return render(request, 'create_article.html', {'form': form})

Приклад 2. Збереження додаткових даних

Припустимо, потрібно зберегти хешоване значення одного з полів. Наприклад, ви хочете хешувати значення поля content перед збереженням.

import hashlib

class ArticleForm(forms.ModelForm):
    class Meta:
        model = Article
        fields = ['title', 'content']

    def save(self, commit=True):
        instance = super().save(commit=False)
        instance.content = hashlib.sha256(instance.content.encode()).hexdigest()  # Хешуємо контент
        if commit:
            instance.save()
        return instance

Тепер, кожного разу при збереженні, значення content буде записуватися в базі у зашифрованому вигляді.

Перевизначення методу clean()

Метод clean() використовується для валідації даних форми. Цей метод дає можливість писати складні правила перевірки, які не можна описати через стандартні валідатори, особливо якщо вони залежать одразу від кількох полів.

Синтаксис і структура методу clean

def clean(self):
    cleaned_data = super().clean()  # Отримуємо вже очищені дані
    # Додаємо кастомну валідацію
    if умова:
        raise forms.ValidationError("Помилка: ваша умова не виконана!")
    return cleaned_data  # Повертаємо очищені дані

Приклад 1. Валідація пов'язаних полів

Припустімо, ти хочеш переконатися, що дата завершення (end_date) не раніше дати початку (start_date).

Модель:

class Event(models.Model):
    name = models.CharField(max_length=100)
    start_date = models.DateField()
    end_date = models.DateField()

Форма:

class EventForm(forms.ModelForm):
    class Meta:
        model = Event
        fields = ['name', 'start_date', 'end_date']

    def clean(self):
        cleaned_data = super().clean()
        start_date = cleaned_data.get('start_date')
        end_date = cleaned_data.get('end_date')

        if start_date and end_date and end_date < start_date:
            raise forms.ValidationError("Дата завершення не може бути раніше дати початку.")

        return cleaned_data

Якщо користувач введе некоректні дані, форма видасть повідомлення про помилку.

Приклад 2. Комбінована перевірка полів на унікальність

Припустімо, у нас є модель із двома полями: title та author. Ти хочеш переконатися, що комбінація цих двох полів унікальна.

class ArticleForm(forms.ModelForm):
    class Meta:
        model = Article
        fields = ['title', 'content', 'author']

    def clean(self):
        cleaned_data = super().clean()
        title = cleaned_data.get('title')
        author = cleaned_data.get('author')

        if Article.objects.filter(title=title, author=author).exists():
            raise forms.ValidationError("Стаття з таким заголовком вже існує для цього автора.")

        return cleaned_data

Спільне використання методів save() та clean()

У деяких випадках вам знадобиться використовувати обидва методи. Наприклад, валідовувати дані у clean() та застосовувати зміни у save().

Приклад: валідація та зміна даних

Модель:

class Profile(models.Model):
    user = models.OneToOneField('auth.User', on_delete=models.CASCADE)
    bio = models.TextField()
    age = models.PositiveIntegerField()

Форма:

class ProfileForm(forms.ModelForm):
    class Meta:
        model = Profile
        fields = ['bio', 'age']

    def clean(self):
        cleaned_data = super().clean()
        age = cleaned_data.get('age')

        if age is not None and age < 18:
            raise forms.ValidationError("Вік має бути не менше 18 років.")

        return cleaned_data

    def save(self, commit=True):
        instance = super().save(commit=False)
        # Додамо дефолтний текст, якщо біографія порожня
        if not instance.bio:
            instance.bio = "Користувач вирішив залишитись загадкою."
        if commit:
            instance.save()
        return instance

Тут ми переконались, що вік вказаний коректно у clean(), а потім додали дію для заповнення біографії за замовчуванням у save().

Типові помилки при перевизначенні

  1. Забули викликати super(): Якщо не викликати super(), стандартна обробка даних форми пропуститься, що може призвести до відсутності обов'язкової логіки.

  2. Помилка у поверненні даних: Після перевизначення методу clean забули повернути cleaned_data, що викликає помилку.

  3. Збереження без перевірки: Якщо ти змінюєш дані у clean(), не забудь врахувати їх при збереженні.

  4. Багаторазове збереження у save(): Слідкуй, щоб викликався лише один метод save() для об'єкта. Кожен виклик призводить до додаткового запиту у базу даних.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ