Upload custom templates in django - django

I want users to be able, to upload their custom html templates including css styling and png and jpeg images. These html files shall be retrievable through django. That means that they can see the resulting html content in form of a normal webpage. In order to be able to do that, I (seemingly, don't know a better approach yet) have to upload the html files in the template directory.
This is my current model:
class StylesheetFile(models.Model):
HTML = 'HTML'
CSS = 'CSS'
JPEG = 'JPEG'
PNG = 'PNG'
# Currently supported mimetypes
MIMETYPE_CHOICES = (
(HTML, 'text/html' ),
(CSS , 'text/css' ),
(JPEG, 'image/jpeg'),
(PNG , 'image/png' ),
)
mimetype = models.CharField(max_length=64, choices = MIMETYPE_CHOICES)
company = models.ForeignKey(Company)
file = models.FileField(upload_to=get_upload_path)
And this is the current function to determine the upload_path:
def get_upload_path(instance, filename):
if instance.mimetype == 'HTML':
return os.path.join(
settings.TEMPLATES[0]['DIRS'][1],
instance.company.name,
filename)
if instance.mimetype == 'CSS':
return os.path.join(
"custom_css",
instance.company.name,
filename)
if instance.mimetype == 'JPEG' or instance.mimetype == 'PNG':
return os.path.join(
"custom_img",
instance.company.name,
filename)
Template settings
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
'/home/ubuntu/app/templates/',
'/home/ubuntu/app/custom_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',
],
},
},
]
When I do this the css stylings, png and jpeg files are getting uploaded correctly, but the html files not. I receive following error:
The joined path (/home/ubuntu/app/custom_templates/FooCompany/factors.html) is located outside of the base path component (/home/ubuntu/app/static/media)
What can I do to prevent this error? Is there some best practice approach to my problem or do I have to go for some workaround like setting one of m y TEMPLATE_DIRS to /home/ubuntu/app/static/media/custom_templates. Maybe this is not even a workaround and legit practice.. I don't know really know.. Help is appreciated a lot!

You cannot use FileField to upload files outside of MEDIA_ROOT. That's an important security measure.
You could set TEMPLATE_DIRS to something inside MEDIA_ROOT and most likely this would work, but this makes my cringe really hard. That would essentially gave the users an ability to overwrite any template for any page on the site.
You don't have to save those templates as HTML files to use them as Django templates. You can save them in a database and render them directly from string:
from django.template import engines
from django.http import HttpResponse
# Get the template from database
template_from_db = YourCustomTemplateModel.objects.get(name='homepage')
template_string = template_from_db.content
# Create a django Template object
template = engines['django'].from_string(template_string)
# Render the template to a string
context = {'foo': 'bar'}
page_content = template.render(context=context)
# Send page to a the browser
return HttpResponse(page_content)
You should however think really hard about the security implications of doing this in general. Are you really comfortable with template creators being able to set arbitrary JavaScript on your domain (think cross-site scripting vulnerability)? What about calling arbitrary methods (or at least the ones not having any arguments) and accessing arbitrary arguments on objects you pass in the context dictionary?

Related

Whats the correct structure for Django templates?

I have a project in Django called: "my_site", and an app called "blog", I'm trying to render the "index.html" inside the path: my_site/blog/templates/blog/index.html. But keep getting this error:
Template-loader postmortem
Django tried loadi
ng these templates, in this order:
Using engine django:
django.template.loaders.filesystem.Loader: C:\Users\Ricardo Neto\Dev\Django_Projects\my_site\templates\index.html (Source does not exist)
django.template.loaders.app_directories.Loader: C:\Python310\lib\site-packages\django\contrib\admin\templates\index.html (Source does not exist)
django.template.loaders.app_directories.Loader: C:\Python310\lib\site-packages\django\contrib\auth\templates\index.html (Source does not exist)
django.template.loaders.app_directories.Loader: C:\Users\Ricardo Neto\Dev\Django_Projects\my_site\blog\templates\index.html (Source does not exist)
The view:
def index(request):
return render(request, 'index.html')
the urls.py:
urlpatterns = [
path('', views.index),
path('posts', views.show_all_posts),
path('posts/<slug:slug>', views.show_post)
]
If i move the index.html outside the blog folder, like in this example: my_site/blog/templates/index.html
the code runs and the index.html renders without problem, but i was taught that the correct structure is to create a folder inside templates with the same name of the app.
So could anyone please explain me the way i should structure my files?
rather than keeping it within the app. I prefer to keep it under the project file. my_site/templates/app_x/index.html
├───accounts
│
├───django_project
│
└───templates
├───accounts
└───registration
In settings.py file update the DIRS to the path of the templates folder. Generally, the templates folder is created and kept in the sample directory where manage.py.
import os
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',
],
},
},
]
If I open separate files under templates, I render them as folder_name/index.html, otherwise directly as index.html.
Django resolves templates relative to the "templates" directory located inside the app directory and chooses the first template across all apps whose name matches. For example, if you have two apps: blog and news both with templates named "index.html" in the "templates" directory, Django would not be able to choose correctly one of them. To make a distinction you can create a subfolder inside the "templates" directory named after the corresponding application: "blog/templates/blog/index.html" and "news/templates/news/index.html". And after that you can use those templates in the view functions like this: render(request, 'news/index.html') and render(request, 'blog/index.html').
You can read about this topic here, check the remark "Template namespacing": https://docs.djangoproject.com/en/4.0/intro/tutorial03/

Cutomize dj_rest_auth Password reset email

I want to send customized emails when a user request a password reset. I am using dj_rest_auth with django. Here is what I have done: 1. Defined a custom serializer that inherits from PasswordResetSerializer of dj_rest_auth
class CustomPasswordResetSerializer(PasswordResetSerializer):
def get_email_options(self):
return {
'html_mail_template_name': 'registration/password_reset_email.html',
}
Then in settings.py pointed to this serializer:
REST_AUTH_SERIALIZERS = {
'LOGIN_SERIALIZER': 'users.serializers.CustomLoginSerializer',
'PASSWORD_RESET_SERIALIZER': 'users.serializers.CustomPasswordResetSerializer',
}
Then configured templates in settings.py
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',
],
},
},
]
Then I created templates folder in project root, created a registration folder inside it and placed password_reset_email.html inside it.
This is what I found as an exact solution formy problem after googling some time,but this is not working for me. What did I miss?
This answer helped me
https://stackoverflow.com/a/70624462/15624533
I just put my files (html or txt) directly to templates folder, and changed 'account/email/password_reset_key' to just 'password_reset_key' in send_mail method.
I faced the same challenge and got the solution from this issue on GitHub. https://github.com/iMerica/dj-rest-auth/issues/9 . It's easier than you think
Create your custom password reset serializer
from dj_rest_auth.serializers import PasswordResetSerializer
class CustomPasswordResetSerializer(PasswordResetSerializer):
def save(self):
request = self.context.get('request')
# Set some values to trigger the send_email method.
opts = {
'use_https': request.is_secure(),
'from_email': 'example#yourdomain.com',
'request': request,
# here I have set my desired template to be used
# don't forget to add your templates directory in settings to be found
'email_template_name': 'password_reset_email.html'
}
opts.update(self.get_email_options())
self.reset_form.save(**opts)
If you only want to customize email parameters nothing more or less, you can ignore overriding save() method and override get_email_options() instead
from dj_rest_auth.serializers import PasswordResetSerializer
class MyPasswordResetSerializer(PasswordResetSerializer):
def get_email_options(self) :
return {
'email_template_name': 'password_reset_email.html'
}
then point to your custom serializer in settings.py
REST_AUTH_SERIALIZERS = {
'PASSWORD_RESET_SERIALIZER': 'path.to.your.CustomPasswordResetSerializer'
}

Template Does not Exist on IIS+Django

After the deployment process of my Django website on IIS i am getting an error like below,
TemplateDoesNotExist at /test/new_site/list/
Template-loader postmortem
Django tried loading these templates, in this order:
Using engine django:
django.template.loaders.app_directories.Loader: D:\workspace\One_Site_Project\env_one_site_37\lib\site-packages\django\contrib\admin\templates\test\new_site_list.html (Source does not exist)
django.template.loaders.app_directories.Loader: D:\workspace\One_Site_Project\env_one_site_37\lib\site-packages\django\contrib\auth\templates\test\new_site_list.html (Source does not exist)
django.template.loaders.app_directories.Loader: D:\workspace\One_Site_Project\env_one_site_37\lib\site-packages\rest_framework\templates\test\new_site_list.html (Source does not exist)
I don't know why IIS is searching my templates files in virtualenv directory.
My view rendering code is,
#method_decorator(csrf_exempt, name='dispatch')
class NewSiteListUpdate(View):
"""
This class is used to list all the new site activity, also update an activity
"""
def get(self, request, *args, **kwargs):
"""
List all the activity info or a particular activity info
:param request:
:param args:
:param kwargs:
:return:
"""
if request.user.is_staff:
self.data = ActivityInformation.objects.all()
self.radius = 11111
return render(request, 'test/new_site_list.html', {'data': self.data})
Below is my template settings in settings.py file,
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR + '/template/'],
'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',
],
# 'builtins': [
# 'src.huawei.templatetags.custom_tags'
# ],
'libraries':{
'custom_tags': 'templatetags.custom_tags',
},
},
},
]
Below is my project structure,
This is working fine in my local system. I tried to add the template folder in to the virtual directory also, but no use, still the same error is showing. I followed this tutorial in order to set up my application on IIS.
I am using python 3.7 and IIS 8.5
I spend my two days for solving this issue but i did not find any solution related to this.
Any help would be greatly appreciated. Thanks in Advance.
I think that you have to modify some part in your code
settings.py
try to change 'DIRS': [BASE_DIR + '/template/'], to 'DIRS'=[os.path.join(BASE_DIR, 'template'), ]
views.py
The path made by the render function will be : ../template/huawei/new_site_list.html but in your project structure your don't have a folder huawei. You must write : return render(request, 'test/new_site_list.html', {'data': self.data})

How can I access environment variables directly in a Django template?

I'm looking to do something like this non-working code in my Django template:
{% if os.environ.DJANGO_SETTINGS_MODULE == "settings.staging" %}
Is something like this possible? My workaround is creating a context processor to make the variable available across all templates, but wanted to know if there is a more direct way to achieve the same result.
Use context_processor, do as below and you should be able to access os.environ['DJANGO_SETTINGS_MODULE'] as SETTING_TYPE in templates. Example you can use {% if SETTING_TYPE == "settings.staging" %}
project/context_processors.py
import os
def export_vars(request):
data = {}
data['SETTING_TYPE'] = os.environ['DJANGO_SETTINGS_MODULE']
return data
project/settings.py (your actual settings file)
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
...
'project.context_processors.export_vars',
...
]
}
}
]
Extending on #vinay kumar's comment, you can create a custom template filter in the following way:
application/template_tags_folder/template_tags_file.py
from django.template.defaulttags import register
import os
#register.filter
def env(key):
return os.environ.get(key, None)
And then in your template you can access it this way:
template.html
{% if 'DJANGO_SETTINGS_MODULE'|env == 'app_name.staging_settings' %}
Finally I leave a django docs reference and a stackoverflow reference for creating custom template tags and filters in django.

Django - use custom template loader on a per-request basis?

Is there a lower-level way to provide the list of loaders when rendering a template, as opposed to always having Django use the setting?
I'd like to use a custom template loader instance for only a few views (I have my reasons).
It looks like you'll have to write some code of your own to do it. Let's take a look at the normal code path for loading templates, if you use, say, render_to_response, where the relevant part of the source is:
return HttpResponse(loader.render_to_string(*args, **kwargs), **httpresponse_kwargs)
That's a call to django.template.loader.render_to_string, which passes through some other functions and eventually ends up calling find_template.
The first time find_template is called, in initializes the global template_source_loaders cache based on settings.TEMPLATE_LOADERS. So it looks like there's no just extra argument you can pass in or anything like that.
One possibility might be to add some loaders to django.template.loader.template_source_loaders just for the duration of that view. I don't know if that will cause other problems; it feels dirty, but if it works, it'll be pretty easy. (Just make a view decorator that does it.)
If you don't want to do that, it looks like you'll have to replicate the work of render_to_string with your own code (if you're really sure you want to use per-view template loaders, which I'm accepting as a premise for the sake of this question but I bet isn't actually necessary). There's not all that much code there, and if you know in advance a specific loader and a single template name that you want to use, it's actually pretty easy. (This is untested but will probably pretty much work.)
def render_to_response_with_loader(loader, name,
dictionary=None, context_instance=None, mimetype=None, dirs=None):
# from find_template
t, display_name = loader(name, dirs)
# from get_template
if not hasattr(t, 'render'):
# template needs to be compiled
t = django.template.loader.get_template_from_string(t, origin, template_name)
# from render_to_string
if not context_instance:
rendered = t.render(Context(dictionary))
else:
# Add the dictionary to the context stack, ensuring it gets removed again
# to keep the context_instance in the same state it started in.
context_instance.update(dictionary)
try:
rendered = t.render(context_instance)
finally:
context_instance.pop()
# from render_to_response
return HttpResponse(rendered, mimetype=mimetype)
If you want to support multiple possible loaders or a list of possible filenames, just copy the relevant code from django.template.loader.
I ended up doing this by modifying template_source_loaders as Dougal suggested. Like he said, I'm not sure this is safe (could it create a race condition?), but it works for my particular case at the moment. The benefit of doing it this way over the other way Dougal suggested is that it makes sure that {% extends %} and {% include %} also use the modified loaders. Here's my render_to_string with custom loaders:
def render_to_string_with_loader(*args, **kwargs):
""" Call render_to_string using ReportTemplateLoader to find templates. """
import django.template.loader as loader
old_loaders = settings.TEMPLATE_LOADERS
settings.TEMPLATE_LOADERS = ('main.loaders.ReportTemplateLoader',)
loader.template_source_loaders = None # force refresh from settings
try:
out = render_to_string(*args, **kwargs)
finally:
# use finally make sure template errors can't mess up later requests
settings.TEMPLATE_LOADERS = old_loaders
loader.template_source_loaders = None
This is an old question but as of 2021 (Django 3.2), here's what I did.
What I needed:
Serve different templates depending on the request (in my case the domain used)
Use only one instance of Django (= 1 IP/port)
(some multi-domain solutions were about creating a new instance with different DIRS)
For the multi-site / multi-domain:
Add 'django.contrib.sites' in INSTALLED_APPS in settings.py
This enables the Django sites framework
Add the 'django.contrib.sites.middleware.CurrentSiteMiddleware' to MIDDLEWARES in your settings.py
This adds a site property for every request handled by Django
For using per-request templates
Create extra engines
In TEMPLATES in settings.py
For example, I use Django engine. I created the same engine again with added DIRS where it could get templates.
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')]
,
'APP_DIRS': True,
},
{
'NAME': 'a.local',
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates/a.local'),
os.path.join(BASE_DIR, 'templates')]
,
'APP_DIRS': True,
},
{
'NAME': 'b.local',
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates/b.local'),
os.path.join(BASE_DIR, 'templates')]
,
'APP_DIRS': True,
}
]
Use the using kwarg in render()
Set the rendering engine to use in render()
Before:
render(request, 'template.html', context=context_data)
After: render(request, 'template.html', context=context_data, using=request.site.domain)
(in my case, request.site.domain is either a.local or b.local, which is precisely the name of an engine I just created)
Override render (optional, hacky)
Avoid changing all your render everywhere.
This is nasty, you shouldn't do that. Move to classed-based views. Here it is.
def render(request, *args, **kwargs):
return shortcut_render(request, *args, **kwargs, using=request.site.domain)