覆盖 Django admin 中的默认查询集

Posted

技术标签:

【中文标题】覆盖 Django admin 中的默认查询集【英文标题】:Override default queryset in Django admin 【发布时间】:2012-09-03 11:05:13 【问题描述】:

我的一个模型有一个已删除标志,用于全局隐藏对象:

class NondeletedManager(models.Manager):
    """Returns only objects which haven't been deleted"""

    def get_query_set(self):
        return super(NondeletedManager, self).get_query_set().exclude(deleted=True)

class Conversation(BaseModel):
    ...
    deleted = models.BooleanField(default=False)
    objects = NondeletedManager()
    all_conversations = models.Manager() # includes deleted conversations

如何覆盖 Django 管理模块使用的默认查询集以包含已删除的对话?

【问题讨论】:

您真的需要自定义管理器来处理这些简单的查询吗? 是的,删除的对象应该被普遍忽略(管理页面除外),因此设置默认值是有意义的。 【参考方案1】:

您可以在模型管理类中使用override get_queryset 方法。

class MyModelAdmin(admin.ModelAdmin):
    def get_queryset(self, request):
        qs = super(MyModelAdmin, self).get_queryset(request)
        if request.user.is_superuser:
            return qs
        return qs.filter(author=request.user)

注意在 Djangoqueryset。

【讨论】:

在这种情况下如何工作?我可以修改ModelAdmin.queryset 创建的查询集以包含已删除的对象吗?我不想自己构建查询集而不是调用超类。 看看我的回答,明白我的意思。有没有完全重新实现该功能的替代方法? 它有助于将答案实际放入您的答案中,而不仅仅是链接。该链接现在已失效,因此我将更新以给出解释。 在 Django 1.6 上,this method was renamed 到 get_queryset 我无法让它工作。我收到一条错误消息,说 .filter 在 qs 对象中不存在。其次,我也无法将查询集用于请求以外的任何内容。 ***.com/questions/54472649/…有什么帮助吗?【参考方案2】:

Konrad 是正确的,但这比文档中给出的示例更难。

已删除的对话不能包含在已将它们排除在外的查询集中。因此,除了完全重新实现 admin.ModelAdmin.queryset 之外,我没有看到其他选择。

class ConversationAdmin (admin.ModelAdmin):

    def queryset (self, request):
        qs = Conversation.all_conversations
        ordering = self.get_ordering(request)
        if ordering:
            qs = qs.order_by(*ordering)
        return qs

【讨论】:

我不认为这有什么问题。使用两个经理是要走的路。然而,Django 管理员确实可以提供一个钩子,这样您就不必重新实现排序部分。【参考方案3】:

以下会有什么问题:

class Conversation(BaseModel):
    ...
    deleted = models.BooleanField(default=False)
    objects = models.Manager() # includes deleted conversations
    nondeleted_conversations = NondeletedManager()

所以在您自己的应用程序/项目中,您可以使用Conversation.nondeleted_conversations() 并让内置的管理应用程序来做这件事。

【讨论】:

我忽略了所有已删除的对象管理页面,所以我认为这应该是默认设置。此外,这样我就不需要通过添加删除对话的功能来更新遗留代码。【参考方案4】:

您可以使用 Django proxy model 来做到这一点。

# models.py
class UnfilteredConversation(Conversation):
    class Meta:
        proxy = True

    # this will be the 'default manager' used in the Admin, and elsewhere
    objects = models.Manager() 

# admin.py
@admin.register(UnfilteredConversation)
class UnfilteredConversationAdmin(Conversation):
    # regular ModelAdmin stuff here
    ...

或者,如果您想要重用现有的 ModelAdmin 类:

admin.site.register(UnfilteredConversation, ConversationAdmin)

这种方法避免了在原始对话模型上覆盖默认管理器时可能出现的问题 - 因为默认管理器也用于多对多关系和反向 ForeignKey 关系。

【讨论】:

【参考方案5】:

接受的解决方案对我来说很有效,但我需要更多的灵活性,所以我最终扩展了更改列表视图以添加自定义查询集参数。我现在可以像这样配置我的默认查询集/过滤器,并且仍然可以通过使用不同的过滤器(获取参数)来修改它:

def changelist_view(self, request, extra_context=None):
    if len(request.GET) == 0 :
        q = request.GET.copy()
        q['status__gt'] = 4
        request.GET = q
        request.META['QUERY_STRING'] = request.GET.urlencode()

    return super(WorksheetAdmin,self).changelist_view(request, extra_context=extra_context)

【讨论】:

【参考方案6】:

Natan Yellin 是正确的,但是你可以更改经理的顺序,第一个是默认的,然后是管理员使用的:

class Conversation(BaseModel):
    ...
    deleted = models.BooleanField(default=False)

    all_conversations = models.Manager() # includes deleted conversations
    objects = NondeletedManager()

get_queryset() 的管理实现使用._default_manager 而不是.objects,如下所示

qs = self.model._default_manager.get_queryset()

参考Django github BaseModelAdmin implementation

这只能确保每次使用 YourModel.objects 时都不会包含已删除的对象,但通用视图和其他视图也会使用 ._default_manager。然后,如果您不覆盖 get_queryset 不是解决方案。我刚刚检查了 ListView 和管理员。

【讨论】:

【参考方案7】:

用我认为最简洁和有用的东西来扩展其中的一些答案。

我假设您有一个像“名称”这样的字段来显示条目。

# admin.py

from django.contrib import admin

@admin.register(Conversation)
class ConversationAdmin(admin.ModelAdmin):
    list_display = ('name', '_is_deleted')


    # Nice to have but indicates that an object is deleted
    @admin.display(
        boolean=True,
        ordering='deleted'
    )
    def _is_deleted(self, obj):
        return obj.deleted

    def get_queryset(self, request):
        return Conversation.all_conversations

这将为您提供如下界面:

我发现对模型进行子类化的问题是它会导致元继承和反向路径查找出现问题。

【讨论】:

以上是关于覆盖 Django admin 中的默认查询集的主要内容,如果未能解决你的问题,请参考以下文章

覆盖 django admin List_Filter 模板

Django ModelAdmin 查询集覆盖不起作用

如何覆盖默认的 django 忘记密码模板?

在 django admin 中覆盖模板

替换 django-admin 的引导主题的默认徽标

无法覆盖 django-allauth 模板