Превращение @property.setter в редактируемое поле в Django Admin Inlines
Документирую свое решение здесь, возможно, у кого-то возникнет идея получше.
В моем текущем проекте у нас есть вычисляемое @свойство, отображающее значение на основе критериев из других полей. До этого момента это было поле, доступное только для чтения, поэтому не было никаких проблем с обычным интерфейсом администрирования django. Недавно клиент захотел сделать это поле редактируемым (в моем реальном проекте оно отображает DateTime); однако значения были взяты из двух разных мест, основанных на типе данных и ограничениях БД. Моей первой мыслью было поместить оба поля на форму и внедрить JS для отображения/скрытия соответствующих полей, но я хотел посмотреть, можно ли это сделать с помощью django.
После некоторых ответов, которые я нашел здесь и здесь, я решил пойти с @property.setter и попытаться заставить его работать с сайтом Admin. Дополнительный контекст заключается в том, что эта форма в настоящее время отображается в Inline. Я не уверен, была ли это проблема версии или потому, что я делаю это для инлайна, но def __init__()
из решения по ссылке для заполнения формы не работал для меня.
Строчные формы ...
Вот мое решение, как сделать @property редактируемым на сайте DjangoAdmin, что по сути дает вам возможность геттеров и сеттеров в django admin.
Основная проблема заключается в том, что @properties не отображаются как поля в ModelAdmin. При вводе имени свойства в fields=[]
возникает ошибка "FieldError: unknown fields", а при вводе имени свойства в readonly_fields=[]
невозможно его редактировать. Решение состоит в том, чтобы создать пользовательскую форму с дополнительным полем, а затем указать форме, что делать с этим полем.
Вот пример моих моделей, которые имеют связанные поля друг с другом. Помните, что все это было сделано для Inline, хотя, казалось бы, это должно прекрасно работать для любой другой формы.
class Model(models.Model):
name = models.CharField(max_length=200)
stuff = models.CharField(max_length=200)
class RelatedModel(models.Model):
related_field = models.ForeignKey(Model, on_delete=models.CASCADE)
conditional_field = models.CharField(max_length=5,choices=[("left", "left"), ("right","right")],blank=False)
value_in_left_field = models.CharField(max_length=200, null=True)
value_in_right_field = models.CharField(max_length=200, null=True)
# the property creates the value based on a condition
@property
def current_value(self):
return self.value_in_right_field if self.conditional_field == "right" else self.value_in_left_field
# the setter, sets the value based on a condition
@current_value.setter
def current_value(self, new_value):
# provide whatever logic you want for your setter here
if self.conditional_field == "left":
self.value_in_left_field = new_value
self.value_in_right_field = None
else:
self.value_in_right_field = new_value
self.value_in_left_field = None
# this line lets you change the short description on a @property
current_value.fget.short_description = "a different label"
и вот классы администратора, чтобы сделать поле редактируемым, есть 3 части, чтобы заставить это работать:
- добавьте дополнительное поле в пользовательскую форму
- получить начальное значение для поля в форме
- указать форме, что делать с дополнительным полем
from django.contrib import admin
from django.forms import ModelForm, CharField
from .models import Model, RelatedModel
class RelatedModelInlineForm(ModelForm):
class Meta:
exclude = []
model = RelatedModel
# 1. add the field to the form
current_value = CharField(max_length=200)
# 2. set the initial value for the field
def get_initial_for_field(self, field,field_name):
if field_name == "current_value":
return self.instance.current_value
return super().get_initial_for_field(field, field_name)
# 3. tell the form what to do with the extra field
def save(self, commit=True):
model = super().save(commit=False)
model.current_value = self.cleaned_data["current_value"]
if commit:
model.save()
return model
class RelatedModelInline(admin.TabularInline):
model = RelatedModel
extra = 0
form = RelatedModelInlineForm
fields = [
"conditional_field",
"current_value",
]
@admin.register(Model)
class ModelAdmin(admin.ModelAdmin):
model = Model
inlines = [RelatedModelInline]
Теперь, в процессе работы, мне пришло в голову, что можно просто поместить логику сеттера в метод сохранения, что на самом деле просто делает его встроенным в пользовательскую форму. лол, но предварительное заполнение поля данными модели - это довольно круто. ааааа, и .setter держит логику близко к модели, а не близко к форме, так что выбирайте по своему вкусу. Я не могу сказать, что лучше.
def save(self, commit=True):
model = super().save(commit=False)
# logic for saving the field the way you want
if model.conditional_field == "left":
model.value_in_left_field = self.cleaned_data["current_value"]
model.value_in_right_field = None
else:
model.value_in_right_field = self.cleaned_data["current_value"]
model.value_in_left_field = None
if commit:
model.save()
return model
В конечном счете, я думаю, если вы делаете это вычисляемое поле специально для страницы администратора, метод save(), вероятно, более читабелен, но если эта функциональность вам нужна во фронтенде и бэкенде, тогда поместить ее в .setter будет лучшим подходом?
Как видите, значение из поля формы сохраняется в нужное место в зависимости от условия. Интерфейс администратора с инлайн-формой