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

47

我在一个网店应用程序中有一个订单模型,它具有自增主键和指向自身的外键,因为订单可以拆分为多个订单,但必须保持与原始订单的关系。

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 类。对于详情视图,我在 fieldsets 属性中包含了 parent_order。当然,默认情况下,这会将所有订单列在一个选择框中,但这不是期望的行为。相反,对于没有父订单的订单(即未从其他订单分拆的订单;parent_order 为 NULL/None),不应显示任何订单。对于已被拆分的订单,这应该只显示单个父订单。

有一个相当新的 ModelAdmin 方法可用,formfield_for_foreignkey,似乎非常适合此类问题,因为可以在其中过滤查询集。假设我们正在查看已从订单 #11208 分拆的订单 #11234 的详细信息视图。以下是代码:

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 shell中运行正常,返回一个包含订单#11208和所有可能从它拆分出来的其他订单的单个查询集。

当然,我们不能在那里硬编码订单号。我们需要引用我们正在查看详细信息页面的订单实例的ordernumber字段。像这样:

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

我没找到任何可行的方法来用一个引用“当前”Order实例替换 ?????,而且我已经深入挖掘了。在 formfield_for_foreignkey 内部,self 指的是ModelAdmin实例,虽然它有一个model属性,但它不是订单模型实例(它是一个ModelBase引用;self.model()返回一个实例,但它的订单号为None)。

一个解决方案可能是从请求路径中提取订单号(/admin/orders/order/11234/),但这样做太丑陋了。我真的希望有更好的方法。

3个回答

61

我认为你可能需要以略微不同的方式来解决这个问题 - 通过修改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

1
它运行了!非常感谢!我对所有这些ModelForm/ModelAdmin业务都是完全陌生的,而且一直在错误的地方寻找。 - JK Laiho
我知道这篇文章已经有些年头了,但是对于在InlineModelAdmin上使用get_readonly_fields时遇到的obj参数传递的是父对象而不是内联实例对象的情况,这也是一个不错的解决方法。就所有意图和目的而言,这使得我的对象只读,并且允许我仅返回单个对象到我的外键中。 - DM Graves
请确保首先调用super.__init__。这将设置self.instance。 - yellottyellott
如果您的管理员模型在管理员模型中具有autocomplete_fields属性,则它将不起作用。 - Mahmood Garibov

9
我已经按照以下方式建模我的内联类。 它在获取父表单ID以过滤内联数据方面有些丑陋,但它起作用。它通过父表单从公司筛选单位。 (原始概念在此解释: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)

8
一种更简洁的方法是利用URL解析器,可如下实现:object_id = resolve(request.path).args[0] 该语句会使代码更加简洁易懂,不过并未改变原意。 - philipk
谢谢@philipk。 - Erwin Julius
@DanielH,链接已被所有者删除。我会编辑答案。谢谢。 - Erwin Julius
在 Django 2.2 和 3.1 中(来源),你可以简单地使用 request.resolver_match.kwargs.get('object_id') - djvg

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)

它告诉我不要“回应”他人的答案,但我还没有“回复”的权限,而且我已经寻找了一段时间,希望这对其他人有所帮助。我还没有点赞的权限,否则我肯定会点赞的!


虽然最好将此信息作为注释留下,但我理解您没有足够的声望来这样做——了解get_object会导致冲突是很有价值的。 - BlackVegetable

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接