Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the Django-way of namespacing ModelAdmin.get_urls when using model(admin) inheritance?

TL;DR

Is there a way to have (namespaced,) well-named views defined when using ModelAdmin.get_urls and ModelAdmins extended by inheritance?

Preferably without resorting to ModelAdmin.model._meta or some other solution of slightly questionable nature.

Pretext

View names added through get_urls get overridden when using and inheriting from custom ModelAdmins.

That is, the view name admin:tighten gets overriden in the following example:

class Screw(models.Model):
    "A screw"

class HexCapScrew(Screw):
    "A hex cap screw"


class ScrewAdmin(admin.ModelAdmin):
    def get_urls(self):
        urls = super(ScrewAdmin, self).get_urls()
        extra_urls = patterns('',
            url(r'^tighten/$', self.tighten, name='tighten'),
        )
        return extra_urls + urls

    def tighten(self, request):
        pass

class HexCapScrewAdmin(ScrewAdmin):
    pass


admin.site.register(Screw, ScrewAdmin)
admin.site.register(HexCapScrew, HexCapScrewAdmin)

On shell the following happens:

In [1]: reverse('admin:tighten')
Out[1]: u'/admin/parts/hexscrew/tighten/'

This is of course understandable since the registration of HexCapScrewAdmin overides the tighten in ScrewAdmin however now it's impossible to reverse ScrewAdmin.tighten.

A preferred solution

However I would like to be able to

  1. reference both views separatedly and
  2. preferably have views in their own instance namespaces.

Progress so far

The best I've come up with is the following setup (can be copy&pasted directly to some app for testing):

from django.contrib import admin
from django.db import models

class Screw(models.Model):
    "A screw"
    class Meta:
        app_label = 'parts'


class HexCapScrew(Screw):
    "A hex cap screw"
    class Meta:
        app_label = 'parts'
        proxy = True


class ScrewAdmin(admin.ModelAdmin):
    def tighten(self, request):
        pass

    def get_urls(self):
        urls = super(ScrewAdmin, self).get_urls()
        extra_urls = patterns('',
            url(r'^tighten/$', self.tighten, name='tighten'),
        )
        # Find out the slugified name of the model this admin is bound to
        # TODO: Feels dirty
        model_name = self.model._meta.model_name

        # Add the to `extra_urls` to their own namespace
        namespaced_extra_urls = patterns('',
            url(r'^', include(extra_urls, namespace=model_name, app_name='screw')),
        )
        return namespaced_extra_urls + urls


class HexCapScrewAdmin(ScrewAdmin):
    pass

admin.site.register(Screw, ScrewAdmin)
admin.site.register(HexCapScrew, HexCapScrewAdmin)

Now I have the following:

In [1]: reverse('admin:screw:tighten')
Out[1]: u'/admin/parts/screw/tighten/'

In [2]: reverse('admin:hexscrew:tighten')
Out[2]: u'/admin/parts/hexscrew/tighten/'

In [3]: reverse('admin:screw:tighten', current_app='hexscrew')
Out[3]: u'/admin/parts/hexscrew/tighten/'

which is nice and works but includes a bit of hackery.

Is this the best that's available or am I just missing something? Any suggestions?

(At least one other way would be to do as Django's ModelAdmin.get_urls use ModelAdmin.model._meta to parametrize the view names but then I would use the namespaces.)

like image 717
7mp Avatar asked Oct 28 '25 05:10

7mp


1 Answers

If you look at the way the admin does it here, you will see that in addition to defining the url, the model admin also prefixes the app_label and model_name to the url name, thus avoiding the subclassing issue to begin with. It also has the advantage of securing the view against unauthorised users (using the self.admin_site.admin_view decorator). Your get_urls() method would then become:

 def get_urls(self):
     from django.conf.urls import url

     def wrap(view):
         def wrapper(*args, **kwargs):
             return self.admin_site.admin_view(view)(*args, **kwargs)
         return update_wrapper(wrapper, view)

     info = self.model._meta.app_label, self.model._meta.model_name

     urlpatterns = super(ScrewAdmin, self).get_urls()
     urlpatterns.append(
         url(r'^tighten/$', wrap(self.tighten), name='%s_%s_tighten' % info))
     return urlpatterns

Then, you'd look up your url like: reverse('admin:app_screw_tighten') or reverse('admin:app_hex_screw_tighten').

like image 186
slurms Avatar answered Oct 29 '25 19:10

slurms