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().
Типові помилки при перевизначенні
Забули викликати
super(): Якщо не викликатиsuper(), стандартна обробка даних форми пропуститься, що може призвести до відсутності обов'язкової логіки.Помилка у поверненні даних: Після перевизначення методу
cleanзабули повернутиcleaned_data, що викликає помилку.Збереження без перевірки: Якщо ти змінюєш дані у
clean(), не забудь врахувати їх при збереженні.Багаторазове збереження у
save(): Слідкуй, щоб викликався лише один методsave()для об'єкта. Кожен виклик призводить до додаткового запиту у базу даних.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ