2017-01-06 6 views
0

Есть игровой объект, каждый из которых может иметь 1 или более платформ. Также каждая игра может иметь 1 или более ссылок на связанные игры (с их собственными платформами). Вот как он выглядит в models.py:Настроить связанные объекты для поля many-to-many в Django

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) 

И этой модели подаются с этим кодом в 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()]) 

Мне нужно продлить filter_horizontal = ("related_games ",) часть admin.py - добавить информацию о платформе каждой игры в виджет связанных игр. Он должен выглядеть (название игры и список платформ): «Virtual Fighter (PS4, PSP, PS3)».

Приложение использует Django 1.7 и Python 2.7

Благодарим Вас за внимание.

ответ

1

По умолчанию, что будет отображаться для каждого элемента в filter_horizontal основана на __str__ или __unicode__ метода объекта, так что вы могли бы попробовать что-то вроде следующего:

class Game(TimeStampedModel): 
    # field definitions 
    # ... 
    def __unicode__(self): 
     return '{0} ({1})'.format(
      self.name, 
      (', '.join(self.platforms.all()) if self.platforms.exists() 
       else 'none') 
     ) 

Это делает каждую игру шоу в список (и везде) как «Имя (платформы)», например «Crash Bandicoot (PS1, PS2)» или «Battlefield (none)», если у него нет платформ

В качестве альтернативы, t хотите изменить метод __unicode__ вашей модели, вам необходимо установить ModelAdmin, чтобы использовать пользовательский ModelForm, указав, что поле related_games должно использовать пользовательский ModelMultipleChoiceField с помощью специального виджета FilteredSelectMultiple, в котором вам необходимо переопределить метод render_options. Следующие классы должны быть в их соответствующих отдельных файлах, но это будет выглядеть примерно так:

# 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) 
+0

А что, если такая функциональность нужна только в админ-части и связана только с горизонтальным фильтром? Я новичок в этом проекте и предполагаю, что изменение части __unicode__ может сломать некоторые вещи. –

+0

Я отредактировал ответ, чтобы включить альтернативу вместо переопределения '__unicode__', хотя это определенно не так красно. – ProfSmiles

+0

Первый вариант с __unicode__ отлично работает. Кроме метода join нужны строки, кроме объектов. Поэтому я сделал это следующим образом: «.join ([unicode (p) для p в self.platforms.all()]) –