在 Django 管理中覆盖默认查询集

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

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

75167 次浏览

What would be so wrong with the following:

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

So in your own apps/projects, you use Conversation.nondeleted_conversations() and let the built-in admin app do it's thing.

You can override get_queryset method in your model admin class.

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)

Note in Django<=1.5 the method was named just queryset.

Konrad is correct, but this is more difficult than the example given in the documentation.

Deleted conversations can't be included in a queryset that already excludes them. So I don't see an option other than re-implementing admin.ModelAdmin.queryset entirely.

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

The accepted solution works great for me but I needed a little bit more flexibility, so I ended up extending the changelist view to add in a custom queryset parameter. I can now configure my default queryset/filter as such and it can still be modified by using a different filter (get parameters):

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)

You can do this with a 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
...

Or, if you have an existing ModelAdmin class you want to re-use:

admin.site.register(UnfilteredConversation, ConversationAdmin)

This approach avoids issues that can arise with overriding the default manager on the original Conversation model - because the default manager is also used in ManyToMany relationships and reverse ForeignKey relationships.

Natan Yellin is correct, but you can change the managers order and the first will be the default, then it is the used by the admin:

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


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

The admin implementation of get_queryset() use ._default_manager instead .objects, as show next

qs = self.model._default_manager.get_queryset()

ref Django github BaseModelAdmin implementation

This only ensures that every time you use YourModel.objects, you will not include deleted objects, but the generic views and others uses ._default_manager too. Then if you don't override get_queryset is not a solution. I've just check on a ListView and admin.

To extend on some of these answers with what I found most concise and useful.

I've made the assumption you have a field like "name" to show the entries.

# 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

Which will give you an interface like:

Django admin list with deleted flag

The problem I found with subclassing a model was that it caused issues with meta inheritance and reverse-path lookups.