Custom AdminSite clashes with app_name; throws "NoReverseMatch at /admin/"

Hello everyone - first post. I tried searching about this issue and didn’t find much; was wondering if I’m missing some configurations or if this is perhaps a bug? Though it feels like someone would’ve stumbled upon such a simple thing before but I couldn’t find much.

The TL:DR is that having a custom AdminSite in some app and then included in that app’s urls, if that app also defines an “app_name” in said urls, Django will throw a NoReverseMatch error when trying to access that admin page.

Now for a detailed explanation, assume the following Django project:

app/
├── app/
│   ├── settings.py
│   ├── urls.py
│   └── ...
└── my_custom_app/
    ├── admin.py
    ├── urls.py
    └── ...

Where the files are, as expected:

# my_custom_app/admin.py
from django.contrib import admin

class AdminSite(admin.AdminSite):
    pass

# my_custom_app/urls.py
from django.urls import path
from core.admin import admin_site

app_name = "my_custom_app"  # this is the problematic line

urlpatterns = [
    path("admin/", admin_site.urls, name="admin"),
]

# app/urls.py
from django.conf import settings
from django.urls import include, path


urlpatterns = [
    path("", include("my_custom_app.urls")),
]

Note: my_custom_app is installed in the settings as the last app

When trying to access <server_address>/admin, instead of having access to the Django admin, the server will throw a “NoReverseMatch at /admin/” error, citing “‘admin’ is not a registered namespace”. Now, if you remove the “problematic line” (app_name = "my_custom_app" from my_custom_app/urls.py), the problem vanishes and everything works as expected.

Aditional details:
Django version: 4.1.5
I do have a bunch of other common modules installed, but they shouldn’t have anything to do with the issue I think:

rest_framework
rest_framework_simplejwt
rest_framework_simplejwt.token_blacklist
drf_standardized_errors
corsheaders

Am I doing something wrong?

Are you looking to use this custom AdminSite in addition to the default site or instead of?

Note, if you are looking to replace the default site, you don’t show your apps.py file here.

You may want to review the docs for both Customizing the AdminSite class and Overriding the default admin site.

But yes, adding the app_name to your urls.py file creates a namespace for the urls defined within it, which means they would be reversed by using names like “my_custom_app:admin” and not just “admin”.

Are you looking to use this custom AdminSite in addition to the default site or instead of?

My intent is to replace the default one (so instead of). I keep it in a core app alongside some other, well, “core functionality”, including a couple more views. My issue is that indeed, I can’t keep it’s url there alongside the other app urls if I want the other reverse names to be core:<view_name>.

Note, if you are looking to replace the default site, you don’t show your apps.py file here.
You may want to review the docs for both Customizing the AdminSite class and Overriding the default admin site.

I’m now reading the part on Overriding the default admin site. This is the prefered approach to do this then? Should I be keeping the admin site in some sort of seperate app instead of keeping it bundled with “core”.

The idea behind it being bundled in the “core” app is that I have my other apps with their own admin.py file where they import the admin_site from core.admin and then define their own admin classes to register.

Notice in that section on Overriding… that they show the directory as being myproject and not myapp for those files. I would interpret that as meaning that no - a custom admin site that is replacing the default does not belong in an app, it’s a project-level configuration item.

Your other apps then would import the site from myproject.admin and not core.admin.

(This is under the working assumption that you’re using the default directory structure that includes a myproject directory that is inside the BASE_DIR myproject directory.)

Thank you! That makes sense. I’ll experiment with it and report back.

Just to update on this; I didn’t have much time to get the Overriding to work, and just called it that the solution I currently had was good enough. I’ll mark this as solution if someone else comes across and wants to try it.