ChatGPT解决这个技术问题 Extra ChatGPT

Multiple ModelAdmins/views for same model in Django admin

How can I create more than one ModelAdmin for the same model, each customised differently and linked to different URLs?

Let's say I have a Django model called Posts. By default, the admin view of this model will list all Post objects.

I know I can customise the list of objects displayed on the page in various ways by setting variables like list_display or overriding the queryset method in my ModelAdmin like so:

class MyPostAdmin(admin.ModelAdmin):
    list_display = ('title', 'pub_date')

    def queryset(self, request):
        request_user = request.user
        return Post.objects.filter(author=request_user)

admin.site.register(MyPostAdmin, Post)

By default, this would be accessible at the URL /admin/myapp/post. However I would like to have multiple views/ModelAdmins of the same model. e.g /admin/myapp/post would list all post objects, and /admin/myapp/myposts would list all posts belonging to the user, and /admin/myapp/draftpost might list all posts that have not yet been published. (these are just examples, my actual use-case is more complex)

You cannot register more than one ModelAdmin for the same model (this results in an AlreadyRegistered exception). Ideally I'd like to achieve this without putting everything into a single ModelAdmin class and writing my own 'urls' function to return a different queryset depending on the URL.

I've had a look at the Django source and I see functions like ModelAdmin.changelist_view that could be somehow included in my urls.py, but I'm not sure exactly how that would work.

Update: I've found one way of doing what I want (see below), but I'd still like to hear other ways of doing this.


S
Samuel Dion-Girardeau

I've found one way to achieve what I want, by using proxy models to get around the fact that each model may be registered only once.

class PostAdmin(admin.ModelAdmin):
    list_display = ('title', 'pubdate','user')

class MyPost(Post):
    class Meta:
        proxy = True

class MyPostAdmin(PostAdmin):
    def get_queryset(self, request):
        return self.model.objects.filter(user = request.user)


admin.site.register(Post, PostAdmin)
admin.site.register(MyPost, MyPostAdmin)

Then the default PostAdmin would be accessible at /admin/myapp/post and the list of posts owned by the user would be at /admin/myapp/myposts.

After looking at http://code.djangoproject.com/wiki/DynamicModels, I've come up with the following function utility function to do the same thing:

def create_modeladmin(modeladmin, model, name = None):
    class  Meta:
        proxy = True
        app_label = model._meta.app_label

    attrs = {'__module__': '', 'Meta': Meta}

    newmodel = type(name, (model,), attrs)

    admin.site.register(newmodel, modeladmin)
    return modeladmin

This can be used as follows:

class MyPostAdmin(PostAdmin):
    def get_queryset(self, request):
        return self.model.objects.filter(user = request.user)

create_modeladmin(MyPostAdmin, name='my-posts', model=Post)

this is awesome. i wasn't aware that a proxy model could be registered in the admin site. this will actually help me greatly.
I also needed to register the same models twice in django admin and proxy models seem to work. But I found one problem with the permission system. See here: code.djangoproject.com/ticket/11154
Its also a good idea to change the default manager instead of the ModelAdmin queryset. So behaviour of the proxy model is consistent even outside the admin.
Now the real answer is, why django doesn't let you have two admin's for the same model? we shouldn't need to hack around things for just 2 lines that checks that and throws an error :s. Great answer still!
@zzart: there is a pending pull request, which appears to be just missing docs: github.com/django/django/pull/146/files
f
funky-future

Paul Stone answer is absolutely great! Just to add, for Django 1.4.5 I needed to inherit my custom class from admin.ModelAdmin

class MyPostAdmin(admin.ModelAdmin):
    def queryset(self, request):
        return self.model.objects.filter(id=1)

F
Felipe Buccioni

Based on the correct answers, I monkeypatch the AdminSite class and add the method register_via_proxy to make the task easier.

import re
from django.contrib import admin

def _register_proxy(self, model, admin_class):
    proxy_model = type(
        admin_class.__name__, (model,), {
            "__module__": re.sub(
                r'(^.*?)(\.[^\.]+)$', r'\1.proxy', model.__module__
            ),
            "Meta": type("Meta", tuple(), {
                "proxy": True,
                 "app_label": model._meta.app_label
            })
        }
    )

    return self.register(proxy_model, admin_class)


admin.sites.AdminSite.register_via_proxy = _register_proxy

And to use is like:

site = admin.sites.AdminSite()
site.register_via_proxy(models.ModelType, AdminClass)