Issue
This Content is from Stack Overflow. Question asked by erjemin
I know that they don’t do this, but for one of my pet-projects I want a strange thing: store jinja-templates in the database (and be able to edit them through the admin panel).
There is something like this model (in models.py):
class TbTemplate(models.Model):
szFileName = models.CharField(
primary_key=True,
db_index=True,
unique=True,
verbose_name="Path/Name"
)
szJinjaCode = models.TextField(
verbose_name='Template',
help_text='Template Code (jinja2)'
)
szDescription = models.CharField(
max_length=100,
verbose_name='Description'
)
def __unicode__(self):
return f"{self.szFileName} ({self.szDescription})"
def __str__(self):
return self.__unicode__()
class Meta:
verbose_name = '[…Template]'
verbose_name_plural = '[…Templates]'
Next, in view.py you can do something like this:
# -*- coding: utf-8 -*-
from django.http import HttpRequest, HttpResponse
from django.template.loader import render_to_string
from web.models import TbTemplate
def something(request: HttpRequest, template: str) -> HttpResponse:
"""
:param request: http-in
:param template: Template name
:return response: http-out
"""
to_template = {}
# ...
# ... do smth
# ...
tmpl = TbTemplate.objects.get(pk=template)
html = render_to_string(tmpl.szJinjaCode, to_template)
return HttpResponse(html)
And everything works. Templates to available for editing through the admin panel (of course, you need to hang a “mirror”-like widget for syntax highlighting, etc.)…
But I want to use in jinja templates like: {% include "something_template.jinja2" %}
… And for this it is necessary that the templates are not only in the database, but also stored as files in the templates-folder.
In addition, templates are easier to create and edit in IDEs, and access to templates through the admin panel only for cosmetic changes.
And then you need to somehow intercept the “read” method in/for the admin panel. So that if a template in the TbTemplate table is opened for editing in the admin panel, then for the szJinjaCode it was read not from the database, but from the corresponding szFileName-file.
How to do this?
Solution
It is done in two steps:
Firstly,
in models.py
we will override the save()
method for the TbTemplate
model. At the same time, we can override delete() method, so that it do not delete anything (or vice versa, it deletes not only the entry in the database, but also the corresponding teplate-file… or deletes the entry in the database, and renames the corresponding file…). We get this model:
# -*- coding: utf-8 -*-
from django.db import models
from project.settings import *
import os
class TbTemplate(models.Model):
szFileName = models.CharField(
primary_key=True, db_index=True, unique=True,
verbose_name="Path/Name"
)
szJinjaCode = models.TextField(
default='', null=True, blank=True,
verbose_name='Template',
help_text='Template Code (jinja2)'
)
szDescription = models.CharField(
max_length=100,
verbose_name='Description'
)
def __unicode__(self):
return f"{self.szFileName} ({self.szDescription})"
def __str__(self):
return self.__unicode__()
def save(self, *args, **kwargs):
path_filename = TEMPLATES_DIR / self.szFileName
if not os.path.exists(os.path.dirname(path_filename)):
os.makedirs(os.path.dirname(path_filename))
with open(path_filename, "w+", encoding="utf-8") as file:
file.write(self.szJinjaCode)
# TODO: for production, need to add some code for modify
# touch_reload file for uWSGI reload
super(TbTemplate, self).save(*args, **kwargs)
def delete(self, *args, **kwargs):
pass
# ... or do smth ... and after:
# super(TbTemplate, self).delete(*args, **kwargs)
class Meta:
verbose_name = '[…Template]'
verbose_name_plural = '[…Templates]'
now, when changing and creating a template through Django-Admin, a corresponding template file will be create/modify.
Secondly,
in the admin.py
file, when define the admin.ModelAdmin
class for the TbTemplate
control model, it is necessary to override the get_fields ()
method, which is responsible for getting the fields in the admin form. (I had to look for a method stupidly trying many cases that are made something similar, but not that). As a result, something like this admin.py
:
# -*- coding: utf-8 -*-
from django.contrib import admin
from my_appweb.models import TbTemplate
from project.settings import *
class AdminTemplate(admin.ModelAdmin):
list_display = ('szFileName', 'szDescription')
list_display_links = ('szFileName', 'szDescription', )
def get_fields(self, request, obj=None):
try:
with open(Path(TEMPLATES_DIR) / obj.szFileName, "r", encoding="utf-8") as file:
obj.szJinjaCode = file.read()
except (AttributeError, FileNotFoundError, TypeError):
pass
return ['szFileName', 'szDescription', 'szJinjaCode']
admin.site.register(TbTemplate, AdminTemplate)
Now, if some "external forces" change the template file (or someone changes the template code in the database bypassing the admin panel), then when you open the template for editing in the admin panel, the data will still be received from the file.
It’s all
P.S. in the settnig.py of the project, you need to add something like:
TEMPLATES_DIR = BASE_DIR / 'templates-jinja2'
So that the model and the admin panel know in which directory to pour the template files.
This Question was asked in StackOverflow by erjemin and Answered by erjemin It is licensed under the terms of CC BY-SA 2.5. - CC BY-SA 3.0. - CC BY-SA 4.0.