Django's TemplateResponse looking in the wrong directory for templates

Viewed 354

File Tree (Rough Example)

project/
    project/
        .
        .
    project_admin/
        admin.py
    app/
        .
        .

project_admin/admin.py

from django.contrib import admin
from django.template.response import TemplateResponse
from django.urls import path


class BookrAdmin(admin.AdminSite):
    logout_template = 'admin/logout.html'

    def profile_view(self, request):
        request.current_app = self.name
        context = self.each_context(request)
        return TemplateResponse(request, 'admin/admin_profile.html', context)

    def get_urls(self):
        urls = super().get_urls()
        url_patterns = [path("admin_profile", self.profile_view)]
        return urls + url_patterns

project/settings.py

import os
from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent.parent
.
.
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

I am a goober and updating this question. I realized my initial post was wrong.

Here is my error.

Request Method:     GET
Request URL:    http://127.0.0.1:8000/admin/admin_profile
Raised by:  django.contrib.admin.sites.catch_all_view

 Using the URLconf defined in bookr.urls, Django tried these URL patterns, in this order:

    accounts/
    accounts/profile/ [name='profile']
    admin/ [name='index']
    admin/ login/ [name='login']
    admin/ logout/ [name='logout']
    admin/ password_change/ [name='password_change']
    admin/ password_change/done/ [name='password_change_done']
    admin/ autocomplete/ [name='autocomplete']
    admin/ jsi18n/ [name='jsi18n']
    admin/ r/<int:content_type_id>/<path:object_id>/ [name='view_on_site']
    admin/ auth/group/
    admin/ auth/user/
    admin/ reviews/review/
    admin/ ^(?P<app_label>auth|reviews)/$ [name='app_list']
    admin/ (?P<url>.*)$

The current path, admin/admin_profile, matched the last one. 

So I feel this has something to do with project_admin/admin.py file. For whatever reason for http://127.0.0.1:8000/admin/[this can be anything] and I still get the same error with a matching path. I'm not sure where to go from here. I don't get a trackback because nothing a failing. It just wont load by admin_profile.html which extends the default django admin/index.html.

---Reason for Original Edit--- The reason why I thought TemplateResponse was pulling from the wrong directory was because I had my pycharm configured to think the wrong template directory was my root template directory. This has now been fixed.

---Answer--- return urls + url_patterns->return url_patterns + urls

1 Answers

What is happening here is that you have added your url pattern after the result of get_urls. The problem is that since Django 3.2 (new in this version) at the end of the list returned by get_urls there is a url pattern that will catch all possible urls and route them to a catch_all_view, and hence since it is before your pattern in the list that view is used for your url and you get an error. This can be seen from the source code [GitHub]:

if self.final_catch_all_view:
    urlpatterns.append(re_path(r'(?P<url>.*)$', wrap(self.catch_all_view)))

This behaviour as mentioned in the documentation can be removed by setting final_catch_all_view to False in your admin site. But the documentation also recommends not doing that since it protects your from enumeration attacks. Instead what you should do is to add your url pattern at the start of your list:

class BookrAdmin(admin.AdminSite):
    logout_template = 'admin/logout.html'

    def profile_view(self, request):
        request.current_app = self.name
        context = self.each_context(request)
        return TemplateResponse(request, 'admin/admin_profile.html', context)

    def get_urls(self):
        urls = super().get_urls()
        url_patterns = [path("admin_profile/", self.profile_view)]
        return url_patterns + urls  # Keep your patterns first in the list so they don't get caught by the catch all view
Related