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=?????)

Я не нашел рабочего места для замены????? со ссылкой на "текущий" экземпляр заказа, и я выкопал довольно глубоко. self внутри formfield_for_foreignkey относится к экземпляру ModelAdmin, и хотя у него есть атрибут model, это не экземпляр модели заказа (это ссылка ModelBase; self.model() возвращает экземпляр, но его порядковый номер - None).

Одним из решений может быть вытащить номер заказа из request.path(/admin/orders/order/11234/), но это действительно уродливо. Мне очень жаль, что есть лучший способ.

Ответ 1

Я думаю, вам, возможно, придется подойти к этому несколько иначе - изменив ModelForm, а не класс admin. Что-то вроде этого:

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

Ответ 2

Я смоделировал мой встроенный класс таким образом. Это немного уродливо по поводу того, как он получает идентификатор родительской формы для фильтрации встроенных данных, но он работает. Он фильтрует устройства компанией из родительской формы.

Исходная концепция объясняется здесь 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 = request.META['PATH_INFO'].strip('/').split('/')[-1]
        try:
            object_id = int(object_id)
        except ValueError:
            return None
        return model.objects.get(pk=object_id)

Ответ 3

Вышеприведенный ответ от Erwin Julius работал у меня, но я обнаружил, что имя "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)

Он сказал мне не "отвечать" на ответы других, но мне еще не разрешено "отвечать", и я искал это какое-то время, поэтому надеюсь, что это будет полезно для других. Мне также не разрешено продвигаться вперед, иначе я бы полностью!