Django: доступ к экземпляру модели из ModelAdmin?
У меня есть модель для заказов в приложении интернет-магазина с автоматически увеличивающимся первичным ключом и внешним ключом, поскольку заказы можно разбить на несколько заказов, но связь с исходным заказом должна быть сохранена.
class Order(models.Model):
ordernumber = models.AutoField(primary_key=True)
parent_order = models.ForeignKey('self', null=True, blank=True, related_name='child_orders')
# .. other fields not relevant here
Я зарегистрировал класс OrderAdmin для сайта администратора. Для подробного просмотра я включил parent_order
в fieldsets
приписывать. Конечно, по умолчанию это перечисляет все заказы в окне выбора, но это не желаемое поведение. Вместо этого, для заказов, которые не имеют родительского заказа (т.е. не были отделены от другого заказа; parent_order
NULL/None), никакие заказы не должны отображаться. Для заказов, которые были разделены, должен отображаться только один родительский заказ.
Доступен довольно новый метод ModelAdmin, formfield_for_foreignkey
, это кажется идеальным для этого, так как набор запросов может быть отфильтрован внутри него. Представьте, что мы смотрим на подробный вид заказа #11234, который был отделен от заказа #11208. Код ниже
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == 'parent_order':
# kwargs["queryset"] = Order.objects.filter(child_orders__ordernumber__exact=11234)
return db_field.formfield(**kwargs)
return super(OrderAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
Комментируемая строка работает при запуске в оболочке Python, возвращая набор запросов из одного элемента, содержащий заказ # 11208 для # 11234 и все другие заказы, которые могли быть отделены от него.
Конечно, мы не можем жестко закодировать там номер заказа. Нам нужна ссылка на ordernumber
поле экземпляра заказа, на странице которого мы просматриваем. Как это:
kwargs["queryset"] = Order.objects.filter(child_orders__ordernumber__exact=?????)
Я не нашел рабочего способа заменить????? со ссылкой на "текущий" экземпляр Order, и я выкопал довольно глубоко. self
внутри formfield_for_foreignkey
ссылается на экземпляр ModelAdmin, и хотя он имеет model
атрибут, это не экземпляр модели заказа (это ссылка на ModelBase; self.model() возвращает экземпляр, но его порядковый номер - None).
Одним из решений может быть получение номера заказа из request.path (/admin/orders/order/11234/), но это действительно ужасно. Я действительно хочу, чтобы был лучший способ.
3 ответа
Я думаю, что вам, возможно, придется подойти к этому немного по-другому - изменив ModelForm, а не административный класс. Что-то вроде этого:
class OrderForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(OrderForm, self).__init__(*args, **kwargs)
self.fields['parent_order'].queryset = Order.objects.filter(
child_orders__ordernumber__exact=self.instance.pk)
class OrderAdmin(admin.ModelAdmin):
form = OrderForm
Я смоделировал свой встроенный класс таким образом. Немного ужасно, как он получает идентификатор родительской формы для фильтрации встроенных данных, но это работает. Он фильтрует единицы по компании из родительской формы.
Оригинальная концепция объясняется здесь http://www.stereoplex.com/blog/filtering-dropdown-lists-in-the-django-admin
class CompanyOccupationInline(admin.TabularInline):
model = Occupation
# max_num = 1
extra = 0
can_delete = False
formset = RequiredInlineFormSet
def formfield_for_dbfield(self, field, **kwargs):
if field.name == 'unit':
parent_company = self.get_object(kwargs['request'], Company)
units = Unit.objects.filter(company=parent_company)
return forms.ModelChoiceField(queryset=units)
return super(CompanyOccupationInline, self).formfield_for_dbfield(field, **kwargs)
def get_object(self, request, model):
object_id = resolve(request.path).args[0]
try:
object_id = int(object_id)
except ValueError:
return None
return model.objects.get(pk=object_id)
Приведенный выше ответ Эрвина Джулиуса работал на меня, за исключением того, что я обнаружил, что имя "get_object" конфликтует с функцией Django, поэтому назовите функцию "my_get_object".
class CompanyOccupationInline(admin.TabularInline):
model = Occupation
# max_num = 1
extra = 0
can_delete = False
formset = RequiredInlineFormSet
def formfield_for_dbfield(self, field, **kwargs):
if field.name == 'unit':
parent_company = self.my_get_object(kwargs['request'], Company)
units = Unit.objects.filter(company=parent_company)
return forms.ModelChoiceField(queryset=units)
return super(CompanyOccupationInline, self).formfield_for_dbfield(field, **kwargs)
def my_get_object(self, request, model):
object_id = request.META['PATH_INFO'].strip('/').split('/')[-1]
try:
object_id = int(object_id)
except ValueError:
return None
return model.objects.get(pk=object_id)
Он сказал мне не "отвечать" на ответы других, но мне пока не разрешено "отвечать", и я некоторое время искал это, поэтому надеюсь, что это будет полезно для других. Мне также не разрешено подавать голос, или я бы полностью!