在Django Admin中如何更改ForeignKey显示文本?

54

我如何在选择一个ForeignKey字段时改变<select>字段中的显示文本?

我需要显示不仅是ForeignKey的名称,还需要其父项的名称。

9个回答

64

如果您想使它只在管理员中生效,而不是全局生效,那么您可以创建一个自定义的 ModelChoiceField 子类,在自定义的 ModelForm 中使用它,然后将相关的管理员类设置为使用您定制的表单。 使用一个具有对 @Enrique 使用的 Person 模型的 ForeignKey 的示例:

class Invoice(models.Model):
      person = models.ForeignKey(Person)
      ....

class InvoiceAdmin(admin.ModelAdmin):
      form = MyInvoiceAdminForm


class MyInvoiceAdminForm(forms.ModelForm):
    person = CustomModelChoiceField(queryset=Person.objects.all()) 
    class Meta:
          model = Invoice
      
class CustomModelChoiceField(forms.ModelChoiceField):
     def label_from_instance(self, obj):
         return "%s %s" % (obj.first_name, obj.last_name)

61

另一种做法(当您更改查询集时很有用):

class MyForm(forms.Form):
    def __init__(self, *args, **kwargs):
        super(MyForm, self).__init__(*args, **kwargs)
        self.fields['user'].queryset = User.objects.all()
        self.fields['user'].label_from_instance = lambda obj: "%s %s" % (obj.last_name, obj.first_name)

'user'是您想要覆盖的字段名称。这种解决方案有一个优点:您还可以覆盖查询集(例如,您想要 User.objects.filter(username__startswith='a'))。

免责声明:该解决方案来源于http://markmail.org/message/t6lp7iqnpzvlt6qp并经过测试。


老而经典!谢谢! - wagnerdelima
1
这对于非管理员表单非常有帮助,并通过避免自定义类来保持简单。 - Ícaro
非常顺利! - jsanchezs
不知道为什么在Django 2.1.9上它对我不起作用,但是所选的答案有效。 - C.K.

19

较新版本的Django支持此功能,可以使用gettext进行翻译:

models.ForeignKey(ForeignStufg, verbose_name='your text')

1
请参考文档:https://docs.djangoproject.com/en/dev/topics/db/models/#verbose-field-names。 - jarmod
19
这实际上解决了一个不同于所问问题的问题。 - Philip Adler
那是正确的方式^^ - Drayen Dörff

18

您还可以直接从admin.ModelAdmin实例中使用label_from_instance来完成此操作。例如:

class InvoiceAdmin(admin.ModelAdmin):

    list_display = ['person', 'id']

    def get_form(self, request, obj=None, **kwargs):
        form = super(InvoiceAdmin, self).get_form(request, obj, **kwargs)
        form.base_fields['person'].label_from_instance = lambda inst: "{} {}".format(inst.id, inst.first_name)
        return form


admin.site.register(Invoice, InvoiceAdmin)

这个很好用。唯一的问题是,在使用管理员“+”选项添加值后,它仍然显示“field_name(id)”值。一旦重新加载页面,它就会正确显示。有没有办法在不重新加载页面的情况下使其正确显示? - DonkeyKong

12

请参考https://docs.djangoproject.com/en/1.3/ref/models/instances/#unicode

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)

    def __unicode__(self):
        return u'%s %s' % (self.first_name, self.last_name)

在您的模型的unicode方法中(外键在哪里),您需要定义要显示的内容。

祝好!


1
我知道,但我只想在管理界面的一个地方更改它,在其他地方不应更改,所以这种方法对我来说不好。 - robos85
@robos85:请查看我的答案,这是您正在寻找的内容。您也应该更新您的问题。 - Botond Béres

2

在其他答案的基础上,这里给那些处理ManyToManyField的人提供一个小例子。

我们可以直接在formfield_for_manytomany()中覆盖label_from_instance

class MyAdmin(admin.ModelAdmin):
    def formfield_for_manytomany(self, db_field, request, **kwargs):
        formfield = super().formfield_for_manytomany(db_field, request, **kwargs)
        if db_field.name == 'person':
            # For example, we can add the instance id to the original string
            formfield.label_from_instance = lambda obj: f'{obj} ({obj.id})'
        return formfield

这也适用于 formfield_for_foreignkey()formfield_for_dbfield()
根据ModelChoiceField文档
模型的__str__()方法将被调用,以生成对象的字符串表示形式,以供字段的选项使用。要提供自定义表示形式,请子类化ModelChoiceField并覆盖label_from_instance。该方法将接收一个模型对象,并应返回一个适合表示它的字符串。

1
通过重写 formfield_for_foreignkey(),您可以在不创建自定义“forms.ModelChoiceField”和“forms.ModelForm”的情况下,在Django管理界面中显示外键及其父级的组合,如下所示:
@admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin):
    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        formfield = super().formfield_for_foreignkey(db_field, request, **kwargs)
        if db_field.name == "my_field":
            formfield.label_from_instance = lambda obj: f'{obj} ({obj.my_parent})'
        return formfield

此外,以下代码使用了自定义的“forms.ModelForm”:
class MyModelForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
                                 
        self.fields['my_field'].label_from_instance = lambda obj: f'{obj} ({obj.my_parent})'

@admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin):
    form = MyModelForm

而且,下面的代码使用了自定义的"forms.ModelChoiceField""forms.ModelForm"

class CustomModelChoiceField(forms.ModelChoiceField):
    def label_from_instance(self, obj):
        return f'{obj} ({obj.my_parent})'

class MyModelForm(forms.ModelForm):
    my_field = CustomModelChoiceField(queryset=MyField.objects.all())

@admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin):
    form = MyModelForm

1
一种替代第一个答案的方法:
class InvoiceAdmin(admin.ModelAdmin):

    class CustomModelChoiceField(forms.ModelChoiceField):
         def label_from_instance(self, obj):
             return "%s %s" % (obj.first_name, obj.last_name)

    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        if db_field.name == 'person':
            return self.CustomModelChoiceField(queryset=Person.objects)

        return super(InvoiceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)

0

我刚发现你可以用另一个queryset替换原有的queryset,甚至可以将其删除并替换为choices列表。我在change_view中这样做。

在这个例子中,我让父项设置返回值,然后从其中获取特定字段并设置.choices

def change_view(self, request, object_id, form_url='', extra_context=None):
        #get the return value which includes the form
        ret = super().change_view(request, object_id, form_url, extra_context=extra_context)

        # let's populate some stuff
        form = ret.context_data['adminform'].form

        #replace queryset with choices so that we can specify the "n/a" option
        form.fields['blurb_location'].choices = [(None, 'Subscriber\'s Location')] + list(models.Location.objects.filter(is_corporate=False).values_list('id', 'name').order_by('name'))
        form.fields['blurb_location'].queryset = None

        return ret

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