Django: доступ к экземпляру модели из ModelAdmin? - PullRequest
44 голосов
/ 04 июня 2009

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

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 / Нет), никакие заказы не должны отображаться. Для заказов, которые были разделены, должен отображаться только родительский заказ.

Доступен довольно новый метод 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 ]

57 голосов
/ 04 июня 2009

Я думаю, что вам, возможно, придется подойти к этому немного по-другому - изменив 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
7 голосов
/ 03 апреля 2012

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

Оригинальная концепция объясняется здесь 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)
3 голосов
/ 30 октября 2013

Приведенный выше ответ Эрвина Джулиуса сработал для меня, за исключением того, что я обнаружил, что имя «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)

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

...