在Django管理界面中覆盖默认的queryset

98

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

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管理模块使用的默认查询集,以包括已删除的对话?

你是否真的需要为那些简单的查询编写自定义管理器? - César
3
是的,已删除的对象应该在全局范围内被忽略(管理员页面除外),因此设置默认值是有意义的。 - Natan Yellin
7个回答

188
你可以在你的模型管理类中覆盖get_queryset方法。
class MyModelAdmin(admin.ModelAdmin):
    def get_queryset(self, request):
        qs = super().get_queryset(request)
        if request.user.is_superuser:
            return qs
        return qs.filter(author=request.user)

在Django<=1.5中,该方法只被命名为queryset

3
在这种情况下,如何操作?我能否修改由ModelAdmin.queryset创建的查询集以包括已删除的对象?我不想自己构建查询集,而是希望调用超类来完成。 - Natan Yellin
看看我的答案,你就知道我是什么意思了。有没有其他方法可以不用完全重新实现这个函数? - Natan Yellin
4
最好把答案直接写在回答中,而不是只提供链接。由于该链接已经失效,因此我会更新并给出解释。 - Dan
18
在Django 1.6中,此方法已更名get_queryset - Fernando Macedo
1
我无法让这个工作。我收到一个错误,说.filter不在qs对象中。另外,我也无法除请求之外的任何东西使用queryset。https://stackoverflow.com/questions/54472649/unable-to-filter-using-a-queryset-from-the-db-to-show-filtered-options-in-form-o 有什么帮助吗? - Gary

10

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

1
我认为这样做没有任何问题。使用两个管理器是正确的方法。然而,Django管理界面可以提供一个钩子,这样你就不必重新实现排序部分。 - Thomas Orozco

7
你可以使用Django的代理模型来实现这一点。
# 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)

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

4
以下内容有何不妥之处:

如果以下内容有何不妥之处:

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

所以在你自己的应用/项目中,你可以使用Conversation.nondeleted_conversations(),然后让内置的管理员应用程序来处理它。


1
我在所有地方都忽略已删除的对象,但是管理页面除外,所以我认为这应该是默认设置。此外,这样做我就不需要通过添加删除对话的功能来更新遗留代码。 - Natan Yellin

4
纳坦·耶林是正确的,但您可以更改管理器的顺序,第一个将成为默认值,然后由管理员使用:
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的实现

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


2

为了进一步解释这些答案,我发现以下内容最简洁和有用。

我假设你有一个类似于“名称”的字段来显示条目。

# 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 list with deleted flag

我发现使用子类化模型会导致元继承和反向路径查找出现问题。

2

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

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)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接