1đź‘Ť
By default, what is shown for each item in a filter_horizontal
is based on the object’s __str__
or __unicode__
method, so you could try something like the following:
class Game(TimeStampedModel):
# field definitions
# ...
def __unicode__(self):
return '{0} ({1})'.format(
self.name,
(', '.join(self.platforms.all()) if self.platforms.exists()
else 'none')
)
This will make each game show in the list (and everywhere else) as “Name (Platforms)”, for example “Crash Bandicoot (PS1, PS2)” or “Battlefield (none)” if it doesn’t have any platforms
Alternatively, if you don’t want to change the __unicode__
method of your model, you’ll need to set your ModelAdmin
to use a custom ModelForm
, specifying that the related_games
field should use a custom ModelMultipleChoiceField
with a custom FilteredSelectMultiple
widget, in which you will need to override the render_options
method. The following classes should be in their respective separate files, but it would look something like:
# admin.py
class GameAdmin(AdminImageMixin, reversion.VersionAdmin):
# ...
form = GameForm
# ...
# forms.py
from django import forms
class GameForm(forms.ModelForm):
related_games = RelatedGamesField()
class Meta:
fields = (
'gid',
'name',
'platforms',
'related_games',
)
# fields.py
from django.forms.models import ModelMultipleChoiceField
class RelatedGamesField(ModelMultipleChoiceField):
widget = RelatedGamesWidget()
# widgets.py
from django.contrib.admin.widgets import FilteredSelectMultiple
class RelatedGamesWidget(FilteredSelectMultiple):
def render_options(self, choices, selected_choices):
# slightly modified from Django source code
selected_choices = set(force_text(v) for v in selected_choices)
output = []
for option_value, option_label in chain(self.choices, choices):
if isinstance(option_label, (list, tuple)):
output.append(format_html(
'<optgroup label="{0}">',
# however you want to have the related games show up, eg.,
'{0} ({1})'.format(
option_value.name,
(', '.join(option_value.platforms.all())
if option_value.platforms.exists() else 'none')
)
))
for option in option_label:
output.append(self.render_option(selected_choices, *option))
output.append('</optgroup>')
else:
output.append(self.render_option(selected_choices, option_value, option_label))
return '\n'.join(output)