Вопрос или проблема
Учитывая текущие модели в models.py:
from django import forms
from django.db import models
from decimal import Decimal
from datetime import datetime, date
class Order (models.Model):
assignment = models.ForeignKey("Assignment", on_delete=models.RESTRICT)
qty = models.PositiveIntegerField(default=1)
order_date = models.DateField()
price = models.DecimalField(max_digits=10, decimal_places=2)
class Assignment (models.Model):
assig_year = models.PositiveSmallIntegerField()
customer = models.ForeignKey("Customer", on_delete=models.CASCADE)
agent = models.ForeignKey("Agent", on_delete=models.CASCADE)
class Agent (models.Model):
name = models.CharField(max_length=32)
surname = models.CharField(max_length=32)
class Customer (models.Model):
name = models.CharField(max_length=64)
Мне нужно предоставить пользователям настраиваемую форму администратора, которая позволяет создавать и обновлять заказы более интуитивно. Для этого я исключаю поле назначения (которое связывает клиента с торговым агентом за данный год, но может не быть таким очевидным для пользователя) и включаю настраиваемое поле для выбора только клиента. На основе этого ввода и даты заказа я внутренне ищу конкретное назначение, которое соответствует этим критериям, и присваиваю его заказу для сохранения в базе данных. Это выглядит так:
admin.py
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.contrib import admin
from django.db.models import CharField
from django import forms
from django.db import models
from .models import Customer, Assignment, Order
class OrderForm(forms.ModelForm):
customers_list = forms.ModelChoiceField(queryset = Customer.objects.all())
class Meta:
model = Order
exclude = (
'assignment',
)
def save(self, commit=True):
# Создаем экземпляр заказа, но пока не сохраняем в БД
obj: Order = super().save(commit=False)
# Ищем связанное назначение. Если его нет, возникает ошибка
try:
assignment = Assignment.objects.get(assig_year=obj.order_date.year, customer=self.cleaned_data['customers_list'])
except ObjectDoesNotExist:
raise ValidationError('Не найдено назначение')
# Добавляем ссылку на назначение в заказ и сохраняем его
obj.assignment = assignment
obj.save()
return obj
def clean(self):
cleaned_data = self.cleaned_data
date_form = cleaned_data['order_date']
customer_form = cleaned_data['customers_list']
try:
cleaned_data['assignment'] = Assignment.objects.get(assig_year=date_form.year, customer=customer_form.id)
except ObjectDoesNotExist:
raise ValidationError('Не найдено назначение')
# Затем вызываем метод clean() суперкласса
super(OrderForm, self).clean()
# Наконец, возвращаем очищенные данные
return cleaned_data
class OrderAdmin(admin.ModelAdmin):
form = OrderForm
fieldsets = (
('Данные заказа:', {
'fields': ( 'customers_list', 'qty', 'order_date', 'price', )
}),
)
Все работает нормально при создании нового заказа; однако, если я вхожу в ранее созданную запись в режиме администратора, настраиваемое поле клиента не заполняется автоматически, и я не знаю, как восстановить его исходное значение. Есть идеи?
Ответ или решение
Восстановление пользовательского поля при редактировании формы администратора Django
При работе с кастомными формами в админ-интерфейсе Django может возникнуть проблема с восстановлением значений пользовательских полей при редактировании существующих записей. В вашем случае поле выбора клиента (customers_list
) не заполняется автоматически при редактировании ранее созданного объекта заказа. Давайте разберёмся, как это можно исправить.
Проблема
Когда вы создаете новый объект Order
, поле customers_list
успешно заполняется, поскольку оно может получить данные из ModelChoiceField
. Однако, при редактировании, поле не восстанавливает значение связанного с ним объекта Customer
, так как оно не указано в initial
данных формы.
Решение
Для того чтобы исправить это, необходимо переопределить метод __init__
в вашей форме OrderForm
. В этом методе мы можем установить начальное значение customers_list
, основываясь на значении customer
из связанного объекта Order
. В результате, при редактировании, форма будет автоматически заполнять поле клиента.
Вот как вы можете изменить вашу форму:
class OrderForm(forms.ModelForm):
customers_list = forms.ModelChoiceField(queryset=Customer.objects.all(), required=True)
class Meta:
model = Order
exclude = ('assignment',)
def __init__(self, *args, **kwargs):
super(OrderForm, self).__init__(*args, **kwargs)
if self.instance and self.instance.pk:
# Если это существующий объект, устанавливаем начальное значение для customers_list
self.initial['customers_list'] = self.instance.assignment.customer
def save(self, commit=True):
# Создаем экземпляр Order, но не сохраняем в БД, пока не проверим все данные
obj: Order = super().save(commit=False)
# Ищем связку Assignment и создаем ошибку, если её нет
try:
assignment = Assignment.objects.get(assig_year=obj.order_date.year, customer=self.cleaned_data['customers_list'])
except ObjectDoesNotExist:
raise ValidationError('Не удалось найти соответствующее назначение.')
# Добавляем ссылку на задание в заказ и сохраняем
obj.assignment = assignment
if commit:
obj.save()
return obj
def clean(self):
cleaned_data = super().clean()
date_form = cleaned_data.get('order_date')
customer_form = cleaned_data.get('customers_list')
try:
cleaned_data['assignment'] = Assignment.objects.get(assig_year=date_form.year, customer=customer_form.id)
except ObjectDoesNotExist:
raise ValidationError('Не удалось найти соответствующее назначение.')
return cleaned_data
Объяснение изменений
-
Метод
__init__
:- В этом методе мы проверяем, является ли объект существующим (проверка
self.instance and self.instance.pk
). Если это так, мы устанавливаем начальное значение для поляcustomers_list
, используя информацию о клиенте из связанногоAssignment
.
- В этом методе мы проверяем, является ли объект существующим (проверка
-
Проверка аргумента
commit
в методеsave
:- Это гарантирует, что изменения будут применены только если флаг
commit
установлен вTrue
. При редактировании объекта в админке, такая проверка может быть полезна для тестирования.
- Это гарантирует, что изменения будут применены только если флаг
-
Обновление метода
clean
:- Теперь он пытается получить
Assignment
, и если его нет, формируется соответствующее сообщение об ошибке.
- Теперь он пытается получить
Заключение
Обновляя метод инициализации формы, вы можете обеспечить автоматическую подстановку значения клиентского поля при редактировании существующего объекта Order
. Это значительно упростит процесс для ваших пользователей, позволяя им быстрее и интуитивно находить нужные данные. Если у вас возникнут дополнительные вопросы или сложности, не стесняйтесь обращаться за помощью!
Отличная статья! Я столкнулся с похожей проблемой в одном из проектов и хотел бы предложить альтернативное решение.
Вместо переопределения метода
__init__
в форме, вы можете использовать методget_initial_for_field
, который позволяет установить начальные значения для полей формы более элегантно.class OrderForm(forms.ModelForm):
customers_list = forms.ModelChoiceField(queryset=Customer.objects.all(), required=True)
class Meta:
model = Order
exclude = ('assignment',)
def get_initial_for_field(self, field, field_name):
if field_name == 'customers_list' and self.instance.pk:
return self.instance.assignment.customer
return super().get_initial_for_field(field, field_name)
Этот подход автоматически заполняет поле
customers_list
при редактировании существующего объекта без необходимости дополнительной логики в методе__init__
.Кроме того, обратите внимание на порядок вызова методов в вашем коде. В методе
clean
рекомендуется сначала вызвать метод суперкласса, а затем выполнять свою логику:def clean(self):
cleaned_data = super().clean()
# ваша проверка здесь
return cleaned_data
Это гарантирует, что базовые проверки формы выполнятся перед вашими кастомными, что может предотвратить потенциальные ошибки.
Также, в методе
save
не забудьте учитывать параметрcommit
при сохранении объекта:if commit:
obj.save()
Это полезно, если форма используется в контексте, где непосредственное сохранение не требуется.
Надеюсь, эти советы помогут улучшить ваш код! Спасибо за подробное описание проблемы и решения.