How to change ForeignKey display text in the Django Admin?

Question:

How can I change the display text in a <select> field while selecting a field which is a ForeignKey?

I need to display not only the name of ForeignKey, but also the name of its parent.

Asked By: robos85

||

Answers:

See 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)

you have to define what you want to display in the unicode method of your model (where is the ForeignKey).

Regards,

Answered By: Enrique San Martín

If you want it to take effect only in admin, and not globally, then you could create a custom ModelChoiceField subclass, use that in a custom ModelForm and then set the relevant admin class to use your customized form.
Using an example which has a ForeignKey to the Person model used by @Enrique:

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)
Answered By: Botond Béres

Another way of doing so (useful when you change your queryset):

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' is the name of field you want to override. This solution gives you one advantage: you can override queryset as well (e.g. you want User.objects.filter(username__startswith='a'))

Disclaimer: solution found on http://markmail.org/message/t6lp7iqnpzvlt6qp and tested.

Answered By: alekwisnia

Newer versions of django support this, which can be translated with gettext:

models.ForeignKey(ForeignStufg, verbose_name='your text')
Answered By: Lu.nemec

I just found that you can replace the queryset with another queryset, or even remove the queryset and replace it with a choices list. I do this in the change_view.

In this example, I let the parent set up the return value, then grab the specific field from it and set .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
Answered By: James S

An alternative to the first answer:

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)
Answered By: C. Bartz

You can also accomplish this straight from your admin.ModelAdmin instance using label_from_instance. For example:

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)
Answered By: radtek

Building on the other answers, here’s a small example for those dealing with a ManyToManyField.

We can override label_from_instance directly in formfield_for_manytomany():

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

This would also work for formfield_for_foreignkey() or formfield_for_dbfield().

From the ModelChoiceField docs:

The __str__() method of the model will be called to generate string representations of the objects for use in the field’s choices. To provide customized representations, subclass ModelChoiceField and override label_from_instance. This method will receive a model object and should return a string suitable for representing it.

Answered By: djvg

By overriding formfield_for_foreignkey(), you can display the combination of foreign key and its parent to Django Admin without creating a custom "forms.ModelChoiceField" and a custom "forms.ModelForm" as shown below:

@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

In addition, this code below is with a custom "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

And, this code below is with a custom "forms.ModelChoiceField" and a custom "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
Answered By: Kai – Kazuya Ito