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.
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,
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)
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.
Newer versions of django support this, which can be translated with gettext:
models.ForeignKey(ForeignStufg, verbose_name='your text')
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
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)
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)
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.
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
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.
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,
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)
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.
Newer versions of django support this, which can be translated with gettext:
models.ForeignKey(ForeignStufg, verbose_name='your text')
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
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)
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)
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, subclassModelChoiceField
and overridelabel_from_instance
. This method will receive a model object and should return a string suitable for representing it.
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