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.
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.
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')
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)
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,
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.
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)
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
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