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()для объекта. Каждый вызов приводит к дополнительному запросу в базу данных.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ