views:

348

answers:

3

Is there a way to extend another apps ModelAdmin?

I have a project that uses functionality offered by django.contrib.comments.

The CommentsAdmin ModelAdmin class has:
actions = ["flag_comments", "approve_comments", "remove_comments"]

I would like to extend the CommentsAdmin ModelAdmin in my project to include an action ban_user.

I've tried creating my own NewCommentsAdmin(CommentsAdmin) object in my admin.py file and registering it, but I get a notice 'AlreadyRegistered at /admin/' 'The model Comment is already registered'.

class NewCommentAdmin(CommentAdmin):
    actions = ['ban_user']

    def ban_user(self, request, queryset):
        pass

admin.site.register(Comment, NewCommentAdmin)

Is there a way to do this without modifying the original django.contrib.comments code?

+3  A: 

Unregister the Comment model first.

ozan
+2  A: 

I guess you have something like this at the top of your file:

from django.contrib.comments.admin import CommentAdmin

This import executes the registration of the model (at the very bottom of this admin file) again.

One idea that doesn't look very nice (I actually haven't tried it) could be:

from django.contrib.comments.models import Comment
from django.contrib import admin
from django.contrib.admin.sites import NotRegistered

# Try to unregister the Comment model 
# that was registered via the auto_discover method
try:
    admin.site.unregister(Comment)
except NotRegistered:
    pass

# Now we can load the CommentAdmin (which reregisters the admin model)
from django.contrib.comments.admin import CommentAdmin

# We have to unregister again:
try:
    admin.site.unregister(Comment)
except NotRegistered:
    pass

# Now your stuff...

I guess this could be done better but it should work. To make this approach work, the application that contains this file has to be after the comments application in INSTALLED_APPS.

Now to your class. I think if you write actions = ['ban_user'] you actually overwrite all the actions in the parent class. I think it is the easiest way to override the get_actions method:

class NewCommentAdmin(CommentAdmin):

    def get_actions(self, request):
        actions = super(NewCommentAdmin, self).get_actions(request)

        # Do some logic here based on request.user if you want 
        # to restrict the new action to certain users
        actions.append('ban_user')

        return actions

    def ban_user(self, request, queryset):
        pass

admin.site.register(Comment, NewCommentAdmin)

Hope that helps (or at least gives an idea) :)

Felix Kling
Um, this is far more complicated than necessary. Just import CommentAdmin, subclass it, unregister once, and register your version.
Carl Meyer
@Carl Meyer: Are you sure? I tried to get Geodjango's map widget working in the frontend. For that I needed to import my custom AdminModel defined in my `admin.py` file. When I import this class, the `admin.site.register` function was executed again. That is my point. When you try to import the class, it tries to register the the model again. Did you get it working with success? If there is another solution I am more than pleased to here it.
Felix Kling
Module-level code is only executed twice if the module is imported via two different paths. Generally this shouldn't happen. I don't know if GeoDjango does something funky, never used it. See my answer for simple version that I have working in production.
Carl Meyer
@Carl Meyer: To check if I got it correctly: Assuming that the admin's `auto_detect()` function imports my `admin.py` via `from project.app import admin` and I import it in the same app via `from app.admin import ModelAdmin`, then the code gets executed twice? If so, then the problem I described only occurs if you want to override custom ModelAdmins as for importing django contrib stuff, you always use the same path.
Felix Kling
I haven't looked at the details. I would assume you could get it working the simple way for overriding any ModelAdmin, might just need to tweak the import style you use.
Carl Meyer
Awesome work Felix. I made a few tweaks to get it running:class NewCommentAdmin(CommentAdmin): def get_actions(self, request): self.actions.append('ban_user') return super(NewCommentAdmin, self).get_actions(request) def ban_user(self, request, queryset): for u in queryset: passadmin.site.unregister(Comment)admin.site.register(Comment, NewCommentAdmin)
Aaron C. de Bruyn
+3  A: 

Here's how I do it in one project for the User model. In the admin.py for my app:

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User

class MyUserAdmin(UserAdmin):
    # ...

admin.site.unregister(User)
admin.site.register(User, MyUserAdmin)
Carl Meyer