【问题标题】:Customize related objects for many-to-many field in Django为 Django 中的多对多字段自定义相关对象
【发布时间】:2017-05-21 09:32:20
【问题描述】:

有游戏实体,每个实体可能有 1 个或多个平台。此外,每个游戏都可以有 1 个或多个指向相关游戏的链接(具有自己的平台)。这里看起来像 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)

并且此模型在 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()])

我需要扩展 admin.py 的 filter_horizo​​ntal = ("related_games",) 部分 - 在相关游戏小部件中添加每个游戏的平台信息。它应该看起来像(游戏名称和平台列表):“Virtual Fighter (PS4, PSP, PS3)”。

应用程序使用 Django 1.7 和 Python 2.7

感谢您的关注。

【问题讨论】:

    标签: python django django-admin many-to-many


    【解决方案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)”或“战地(无)”,如果它没有平台

    或者,如果您不想更改模型的__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)
    

    【讨论】:

    • 如果此类功能仅在管理部分需要并且仅与水平过滤器小部件相关怎么办?我是这个项目的新手,并假设更改 unicode 部分可能会破坏一些东西。
    • 我已经编辑了答案以包含替代方案而不是覆盖__unicode__,尽管它绝对不是那么简洁
    • 第一个带有 unicode 的变体可以正常工作。除了连接方法需要字符串,对象除外。所以我这样做了:' '.join([unicode(p) for p in self.platforms.all()])
    猜你喜欢
    • 2011-10-18
    • 2023-03-14
    • 2013-11-17
    • 2020-08-27
    • 2011-03-01
    • 1970-01-01
    • 2016-10-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多