Django view - load template from calling app's dir first - django

I try to keep a somewhat consistent naming scheme on my HTML templates. I.e. index.html for main, delete.html for delete page and so forth. But the app_directories loader always seems to load the template from the app that's first alphabetically.
Is there any way to always check for a match in the calling app's templates directory first?
Relevant settings in my settings.py:
PROJECT_PATH = os.path.realpath(os.path.dirname(__file__))
TEMPLATE_LOADERS = (
'django.template.loaders.app_directories.load_template_source',
'django.template.loaders.filesystem.load_template_source',
)
TEMPLATE_DIRS = (
os.path.join(PROJECT_PATH, 'templates'),
)
I've tried changing the order of TEMPLATE_LOADERS, without success.
Edit as requested by Ashok:
Dir structure of each app:
templates/
index.html
add.html
delete.html
create.html
models.py
test.py
admin.py
views.py
In each app's views.py:
def index(request):
# code...
return render_to_response('index.html', locals())
def add(request):
# code...
return render_to_response('add.html', locals())
def delete(request):
# code...
return render_to_response('delete.html', locals())
def update(request):
# code...
return render_to_response('update.html', locals())

The reason for this is that the app_directories loader is essentially the same as adding each app's template folder to the TEMPLATE_DIRS setting, e.g. like
TEMPLATE_DIRS = (
os.path.join(PROJECT_PATH, 'app1', 'templates'),
os.path.join(PROJECT_PATH, 'app2', 'template'),
...
os.path.join(PROJECT_PATH, 'templates'),
)
The problem with this is that as you mentioned, the index.html will always be found in app1/templates/index.html instead of any other app. There is no easy solution to magically fix this behavior without modifying the app_directories loader and using introspection or passing along app information, which gets a bit complicated. An easier solution:
Keep your settings.py as-is
Add a subdirectory in each app's templates folder with the name of the app
Use the templates in views like 'app1/index.html' or 'app2/index.html'
For a more concrete example:
project
app1
templates
app1
index.html
add.html
...
models.py
views.py
...
app2
...
Then in the views:
def index(request):
return render_to_response('app1/index.html', locals())
You could even write a wrapper to automate prepending the app name to all your views, and even that could be extended to use introspection, e.g.:
def render(template, data=None):
return render_to_response(__name__.split(".")[-2] + '/' + template, data)
def index(request):
return render('index.html', locals())
The _____name_____.split(".")[-2] assumes the file is within a package, so it will turn e.g. 'app1.views' into 'app1' to prepend to the template name. This also assumes a user will never rename your app without also renaming the folder in the templates directory, which may not be a safe assumption to make and in that case just hard-code the name of the folder in the templates directory.

I know this is an old thread, but I made something reusable, that allows for simpler namespacing. You could load the following as a Template Loader. It will find appname/index.html in appname/templates/index.html.
Gist available here: https://gist.github.com/871567
"""
Wrapper for loading templates from "templates" directories in INSTALLED_APPS
packages, prefixed by the appname for namespacing.
This loader finds `appname/templates/index.html` when looking for something
of the form `appname/index.html`.
"""
from django.template import TemplateDoesNotExist
from django.template.loaders.app_directories import app_template_dirs, Loader as BaseAppLoader
class Loader(BaseAppLoader):
'''
Modified AppDirecotry Template Loader that allows namespacing templates
with the name of their app, without requiring an extra subdirectory
in the form of `appname/templates/appname`.
'''
def load_template_source(self, template_name, template_dirs=None):
try:
app_name, template_path = template_name.split('/', 1)
except ValueError:
raise TemplateDoesNotExist(template_name)
if not template_dirs:
template_dirs = (d for d in app_template_dirs if
d.endswith('/%s/templates' % app_name))
return iter(super(Loader, self).load_template_source(template_path,
template_dirs))

The app_loader looks for templates within your applications in order that they are specified in your INSTALLED_APPS. (http://docs.djangoproject.com/en/dev/ref/templates/api/#loader-types).
My suggestion is to preface the name of your template file with the app name to avoid these naming conflicts.
For example, the template dir for app1 would look like:
templates/
app1_index.html
app1_delete.html
app1_add.html
app1_create.html

Related

Is there any way to change the default path which Django will use to locate files in generic class based views?

So I am making a Blog website. I am using Class Bases Views to give the users an ability to add posts, view posts, update and delete them. So in my views.py, I have this:
from django.shortcuts import render, HttpResponse
from .models import Post
from django.views.generic import ListView, DetailView
# Create your views here.
def home(request):
context = {
'posts': Post.objects.all()
}
return render(request, 'index.html', context)
class PostListView(ListView):
model = Post
template_name = 'index.html'
context_object_name = 'posts'
ordering = ['-date_posted']
class PostDetailView(DetailView):
model = Post
My urls.py:
from django.urls import path
from . import views
from .views import PostListView, PostDetailView
urlpatterns = [
path('', PostListView.as_view(), name="home"),
path('post/<int:pk>/', PostDetailView.as_view(), name="post-detail"),
]
The problem is in the PostDetailView. When I create a file called post_detail.html in my templates folder, so that when user goes to a specific post in the website, we redirect them to the post_detail.html. But the site says that post_detail.html template cannot be found.
I have learnt that the the generic class based views will be looking for a template with this naming convention, <app>/<module>_<viewtype>.html. So it's going to be looking in the directory of the app name, which, in my case, is going to be "blog_app" then a template with a model name which is going to be "Post" and then the view type.
Is there any way to change the default path which Django will use to locate files in generic class based views?
So that I can then tell Django to look in my templates folder to search for post_detail.html template.
Note: I have registered my templates folder in the settings.py in the "dirs" like this 'DIRS': ['templates'],
Thanks
If you need to register your project’s templates directory folder then the setting that needs to be modified is DIRS inside TEMPLATES.
'DIRS': [os.path.join(BASE_DIR, 'templates')]
The BASE_DIR will already exist if you created your project using the default project template.
If APP_DIRS option is set to True in TEMPLATES, then
this is the relative path where your post_detail.html should be present.
blog_app/templates/blog_app/post_detail.html

Does my templates directory need a __init__.py file?

I'm using Django and Python 3.7. I have the following in my project hierarchy ...
+ project
+ web
+ views
tax_calculator.py
+ forms
__init__.py
tax_calculator_form.py
+ templates
tax_calculator.html
My view file, "tax_calculator.py" is attempting to render a GET request, using
# Basic function that serves the default page
def get(request):
tax_calculator_form = TaxCalculatorForm()
return render(request, "web/templates/tax_calculator.html", {'form': tax_calculator_form})
but I'm getting the error
TemplateDoesNotExist at /calculate_taxes
when I visit my URL and it's complaining about this line in my view
return render(request, "web/templates/tax_calculator.html", {'form': tax_calculator_form})
The path looks correct to me. Why isn't my URL finding my template path?
In answer to your questions title, No. __init__.py is for python modules, not asset directories
Depending on how you've specified your template settings, you shouldn't need the relative path, "tax_calculator.html" should suffice.

Django: how to simplify calls for rendering templates from subdirectory

I have django project with a couple applications. To be able use templates with common names (like index, menu, ..., page1, page2) in more then one of them I adopted this schema:
app1/
templates/
app1/
page1.html
page2.html
app2/
templates/
app2/
page1.html
page2.html
and in views I use it like that:
def myview(request): # in app1
context={'name':'John', 'surname':'Lennon'}
return render(request,"app1/page1.html",context)
or
def myview(request): # in app2
context={'tool':'hammer', 'size':'big'}
return render(request,"app2/page1.html",context)
it works, but I have to write the full app name (app1/, app2/) in each and every render (and no app uses templates from other app or just from templates/ (except the project itself) ) and the apps names are actually long like 10-17 characters (not short as app1, app2)
The question: is there a way to do it better, that each applications render would not default to templates/ but to templates/app1/, templates/app2/ and so, respectively?
Thanks for all suggestions
One simple solution is, you can declare the path on top of the app or in settings.py and use that variable in entire script, for example:
path1 = "app1/templates/"
path2 = "app2/templates/"
def myview(request): # in app1
context={'name':'John', 'surname':'Lennon'}
return render(request,path1+"page1.html",context)
def myview(request): # in app2
context={'tool':'hammer', 'size':'big'}
return render(request,path2+"page1.html",context)
This can also reduce the typing efforts.

Django could not load template tag

I have created a templatetags folder inside my application and inside a file named posts.py, I have written the following code;
from django.template import Library, Node
from advancedviews.models import Post
register = Library()
class AllPost(Node):
def render(self,context):
context['all_posts'] = Post.objects.all()
return ''
def get_all_posts(parser,token):
return AllPost()
get_all_posts = register.tag(get_all_posts)
Now, I try to load this template tag inside my template;
{% load get_all_posts %}
But this gives me with error, 'get_all_posts' is not a valid tag library: Template library get_all_posts not found, tried django.templatetags.get_all_posts,django.contrib.admin.templatetags.get_all_posts
What is the error in this template or have I missed something here.
With load you need to use the name of the library, not the tag - so posts in your case.
(I assume you also have a blank __init__.py in the templatetags directory, and that the application is in INSTALLED_APPS).
suppose you have the following structure:
-- Application_Name
-------templatetags
--------------__init__.py
--------------templates_extras.py
-------__init__.py
-------settings.py
-- manage.py
You have to make sure of the following:
your application itself inside which your "templatetags" is resident is actually installed in INSTALLED_APPS in settings.py (e.g. "Application_Name")
your tag module itself that exists inside "templatetags" is already installed in INSTALLED_APP in settings.py (e.g. "ApplicationName.templatetags.tempaltes_extras")
keep sure you have "__init__.py" under templatetags directory
you have to restart the server
In some cases you have to remove all generated *.pyc if it did not work then retry again

TemplateDoesNotExist - file exists, no permissions issue

I'm receiving this error when trying to render a template in django:
TemplateDoesNotExist
...
Template-loader postmortem
Django tried loading these templates, in this order:
Using loader django.template.loaders.filesystem.Loader:
Using loader django.template.loaders.app_directories.Loader:
Here's my settings.py entry:
SETTINGS_PATH = os.path.normpath(os.path.dirname(__file__))
TEMPLATE_DIRS = (
os.path.join(SETTINGS_PATH, 'template'),
)
Here's my view that's being called:
def handler(request):
if request.method == 'GET':
NewAppFormSet = modelformset_factory(Application)
return render_to_response(request, "template/apps.html",{"new_app_form":NewAppFormSet})
I've tried every possible combination of paths, adjusting permissions, etc. The real issue seems to be with the template loaders, as they are not recognizing any paths set forth in TEMPLATE_DIRS. I've hardcoded this string, to no avail. I've also ran python manage.py runserver with sudo / as root. I'm at a loss...
I had a very similar issue which was driving me crazy. It turns out that the book I was using to learn Python/Django did not say that I had to set the templates directory in settings.py expressly. Very confusing. I am using Python 2.6 & Django 1.3.1 - maybe a previous version automatically found the templates dir. Anyway, after pulling my hair out for a day and reading a lot on this site I figured out that I had to replace the default TEMPLATE_DIRS assignment with the following in settings.py:
# Find templates in the same folder as settings.py.
SETTINGS_PATH = os.path.realpath(os.path.dirname(__file__))
TEMPLATE_DIRS = (
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
os.path.join(SETTINGS_PATH, 'templates'),
)
Thanks stackoverflow!!
I'm not sure what example I was following that specified the render_to_response shortcut method requires the views' request object to be passed, but it was as easy as changing it to:
return render_to_response("template/apps.html",{"new_app_form":NewAppFormSet()})
Try assigning SETTINGS_PATH with os.path.realpath:
SETTINGS_PATH = os.path.realpath(os.path.dirname(__file__))
By any chance, you might have copy-pasted the TEMPLATE_DIRS you have now, did you leave the default one lower down the SETTINGS file?
From my settings.py:
BASE_DIR = os.path.dirname(__file__)
def RELATIVE_PATH(*args):
return os.path.join(BASE_DIR, *args)
TEMPLATE_DIRS = (
RELATIVE_PATH('template'),
)
what if you try
return render_to_response(request, "apps.html",{"new_app_form":NewAppFormSet})
instead of
return render_to_response(request, "template/apps.html",{"new_app_form":NewAppFormSet})