在django-admin中,如何将filter_horizontal设置为默认?

5 投票
2 回答
1805 浏览
提问于 2025-04-17 09:22

在django-admin中,ManyToManyFields的默认小部件使用起来不太方便。我可以在每个字段上设置filter_horizontal,这样就能得到一个更好用的小部件。

我想知道怎么能把filter_horizontal设置成所有ManyToManyFields的默认选项?

当然,如果能用filter_vertical也可以。

我在网上找了找解决办法,但在谷歌和StackOverflow上都没有找到。我能想到用一些元编程的方法来实现,但如果有人已经做过这个,或者在Django里有相关的内容,我很想知道。

2 个回答

0

这个方法虽然还有点小技巧,但比其他答案要好,因为其他方法在升级Django的时候会带来很多麻烦。建议从这个方法继承(不要随便修改原有代码):

class BaseAdmin(models.Admin):
    def formfield_for_manytomany(self, db_field, request=None, **kwargs):
        """
        Get a form Field for a ManyToManyField. Tweak so filter_horizontal 
        control used by default. If raw_id or autocomplete are specified
        will take precedence over this.
        """
        filter_horizontal_original = self.filter_horizontal
        self.filter_horizontal = (db_field.name,)
        form_field = super().formfield_for_manytomany(db_field, request=None, **kwargs)
        self.filter_horizontal = filter_horizontal_original
        return form_field


@admin.register(AcmeModel)
class AcmeModelAdmin(BaseAdmin):
    # Sub-classes can still specify raw_id, autocomplete, etc.
    # That will override our filter_horizontal defaulting.
    pass

不知道为什么在__init__里设置filter_horizontal不起作用,而且也没有get_filter_horizontal可以重写。

正如上面提到的,尽量避免随便修改原有代码,直接从BaseClass继承就好。等你过六个月再回来看这个代码时,你和你的同事一定会感激这样的做法,因为你们能更容易找到这个继承关系。

3

修改已经存在的代码中的类,最好的方法是使用一个叫做 mixin 的东西。你需要修改 ModelAdmin 类中的 formfield_for_manytomany 方法;这个方法在 BaseModelAdmin 中定义。

在一个确保在你的 Django 服务器启动时会运行的模块中添加以下代码 [可以是你自己应用的 models.py]:

from django.contrib.admin.options import ModelAdmin
from django.contrib.admin import widgets
class CustomModelAdmin:
    def formfield_for_manytomany(self, db_field, request=None, **kwargs):
        """
        Get a form Field for a ManyToManyField.
        """
        # If it uses an intermediary model that isn't auto created, don't show
        # a field in admin.
        if not db_field.rel.through._meta.auto_created:
            return None
        db = kwargs.get('using')

        if db_field.name in self.raw_id_fields:
            kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel, using=db)
            kwargs['help_text'] = ''
        else:
            kwargs['widget'] = widgets.FilteredSelectMultiple(db_field.verbose_name, False) # change second argument to True for filter_vertical

        return db_field.formfield(**kwargs)

ModelAdmin.__bases__ = (CustomModelAdmin,) + ModelAdmin.__bases__

注意 (2019年8月27日):

我完全知道子类化/继承是怎么回事,这通常是解决这类问题的最佳做法。然而,正如我在下面的评论中反复强调的,子类化 并不能解决提问者所说的问题,也就是让 filter_horizontalfilter_vertical 成为默认选项。使用子类化的话,不仅需要为所有模型注册你的子类,还得取消注册在 Django 内置应用和你安装的第三方应用中注册的每个 ModelAdmin 子类,然后再注册你新的 ModelAdmin 子类。举个例子,对于 Django 的内置用户模型 ...

admin.site.unregister(User)
class CustomModelAdmin(admin.ModelAdmin):
    """ Add your changes here """
admin.site.register(User, CustomModelAdmin)

... 然后对你安装的所有 Django 应用和第三方应用重复类似的代码。我觉得这并不是提问者想要的,所以我给出了这样的回答。

撰写回答