Django:从ModelAdmin中访问模型实例吗?

时间:2021-08-12 15:53:34

I've got a model for Orders in a webshop application, with an auto-incrementing primary key and a foreign key to itself, since orders can be split into multiple orders, but the relationship to the original order must be maintained.

在webshop应用程序中,我有一个订单模型,它具有自动递增的主键和外键,因为订单可以被拆分为多个订单,但是必须维护与原始订单的关系。

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

I've registered an OrderAdmin class for the admin site. For the detail view, I've included parent_order in the fieldsets attribute. Of course, by default this lists all the orders in a select box, but this is not the desired behaviour. Instead, for orders that don't have a parent order (i.e. have not been split from another order; parent_order is NULL/None), no orders should be displayed. For orders that have been split, this should only display the single parent order.

我已经为管理站点注册了一个OrderAdmin类。对于细节视图,我在fieldsets属性中包含了parent_order。当然,默认情况下,它会在选择框中列出所有的订单,但这并不是期望的行为。相反,对于没有父订单的订单(即没有从另一个订单中拆分;parent_order是NULL/None),不应该显示任何订单。对于已拆分的订单,应该只显示单个父订单。

There's a rather new ModelAdmin method available, formfield_for_foreignkey, that seems perfect for this, since the queryset can be filtered inside it. Imagine we're looking at the detail view of order #11234, which has been split from order #11208. The code is below

这里有一个相当新的ModelAdmin方法,formfield_for_foreignkey,看起来非常适合这个方法,因为可以在其中过滤queryset。假设我们正在查看订单#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)

The commented row works when run in a Python shell, returning a single-item queryset containing order #11208 for #11234 and all other orders that may have been split from it.

当在Python shell中运行时,注释行可以工作,返回一个单条目的queryset,其中包含#11234的订单#11208,以及所有其他可能从中分离出来的订单。

Of course, we can't hard-code the order number there. We need a reference to the ordernumber field of the order instance whose detail page we're looking at. Like this:

当然,我们不能硬编码订单号。我们需要对order实例的ordernumber字段的引用,我们正在查看该实例的详细页面。是这样的:

kwargs["queryset"] = Order.objects.filter(child_orders__ordernumber__exact=?????)

I've found no working way to replace ????? with a reference to the "current" Order instance, and I've dug pretty deep. self inside formfield_for_foreignkey refers to the ModelAdmin instance, and while that does have a model attribute, it's not the order model instance (it's a ModelBase reference; self.model() returns an instance, but its ordernumber is None).

我找不到替代的工作方法???引用“当前”订单实例,我已经深入挖掘了。formfield_for_foreignkey中的self引用了ModelAdmin实例,虽然它有一个模型属性,但它不是order模型实例(它是一个ModelBase引用;self.model()返回一个实例,但它的ordernumber是None)。

One solution might be to pull the order number from request.path (/admin/orders/order/11234/), but that is really ugly. I really wish there is a better way.

一种解决方案可能是从请求中提取订单号。路径(/admin/orders/order/11234/),但那真的很难看。我真希望有更好的办法。

3 个解决方案

#1


54  

I think you might need to approach this in a slightly different way - by modifying the ModelForm, rather than the admin class. Something like this:

我认为您可能需要以一种稍微不同的方式处理这个问题——通过修改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

#2


5  

I've modeled my inline class this way. It's a bit ugly on how it gets the parent form id to filter inline data, but it works. It filters units by company from the parent form.

我用这种方式建模我的内联类。它是如何获取父表单id来过滤内联数据的,这有点难看,但是它是有效的。它从父窗体中按公司过滤单元。

The original concept is explained here http://www.stereoplex.com/blog/filtering-dropdown-lists-in-the-django-admin

这里的原始概念解释了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


3  

The above answer from Erwin Julius worked for me, except I found that the name "get_object" conflicts with a Django function so name the function "my_get_object".

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)

It told me not to "respond" to others' answers, but I'm not allowed to "reply" yet, and I have been looking for this for a while so hope this will be helpful to others. I am also not allowed to upvote yet or I totally would!

它告诉我不要“回复”别人的答案,但我还不能“回复”,我已经寻找了一段时间,希望这对别人有帮助。我也不被允许向上投票,否则我完全可以!

#1


54  

I think you might need to approach this in a slightly different way - by modifying the ModelForm, rather than the admin class. Something like this:

我认为您可能需要以一种稍微不同的方式处理这个问题——通过修改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

#2


5  

I've modeled my inline class this way. It's a bit ugly on how it gets the parent form id to filter inline data, but it works. It filters units by company from the parent form.

我用这种方式建模我的内联类。它是如何获取父表单id来过滤内联数据的,这有点难看,但是它是有效的。它从父窗体中按公司过滤单元。

The original concept is explained here http://www.stereoplex.com/blog/filtering-dropdown-lists-in-the-django-admin

这里的原始概念解释了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


3  

The above answer from Erwin Julius worked for me, except I found that the name "get_object" conflicts with a Django function so name the function "my_get_object".

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)

It told me not to "respond" to others' answers, but I'm not allowed to "reply" yet, and I have been looking for this for a while so hope this will be helpful to others. I am also not allowed to upvote yet or I totally would!

它告诉我不要“回复”别人的答案,但我还不能“回复”,我已经寻找了一段时间,希望这对别人有帮助。我也不被允许向上投票,否则我完全可以!