There are games entity, each of them could have 1 or more platforms. Also each game could have 1 or more links to related games (with their own platforms). Here it looks like in models.py:
class Game(TimeStampedModel):
gid = models.CharField(max_length=38, blank=True, null=True)
name = models.CharField(max_length=512)
platforms = models.ManyToManyField(
Platform, blank=True, null=True)
...
#here is the self-referencing m2m field
related_games = models.ManyToManyField(
"self", related_name="related", blank=True)
And this model are served with this code in admin.py:
@admin.register(Game)
class GameAdmin(AdminImageMixin, reversion.VersionAdmin):
list_display = ("created", "name", "get_platforms"... )
list_filter = ("platforms", "year",)
#I'm interested in changing the field below
filter_horizontal = ("related_games",)
formfield_overrides = {
models.ManyToManyField: {"widget": CheckboxSelectMultiple},
}
def get_platforms(self, obj):
return ", ".join([p.name for p in obj.platforms.all()])
I need to extend filter_horizontal = ("related_games",) part of admin.py - to add a platform information of each game in related games widget. It should look like (game name and platforms list): "Virtual Fighter (PS4, PSP, PS3)".
The application uses Django 1.7 and Python 2.7
Thank you for your attention.
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)