Как в Django переопределить метод "read" только в/для администратора?
Я знаю, что они этого не делают, но для одного из моих домашних проектов я хочу странную вещь: хранить jinja-шаблоны в базе данных (и иметь возможность редактировать их через админ-панель).
Есть что-то вроде этой модели (в 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]'
Далее, в view.py вы можете сделать что-то вроде этого:
# -*- 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)
И все работает. Шаблоны доступны для редактирования через админ-панель (конечно, нужно повесить "зеркало"-подобный виджет для подсветки синтаксиса и т.д.)...
Но я хочу использовать в jinja шаблоны типа: {% include "something_template.jinja2" %}
... А для этого необходимо, чтобы шаблоны были не только в базе данных, но и хранились в виде файлов в папке templates-folder.
Кроме того, шаблоны проще создавать и редактировать в IDE, а доступ к шаблонам через панель администратора только для косметических изменений.
Затем мне нужно как-то перехватить метод "read" в/из админпанели. Чтобы если шаблон в таблице TbTemplate открыт для редактирования в админ панели, то для szJinjaCode он был прочитан не из базы данных, а из соответствующего szFileName-файла.
Как это сделать?
Интересный вопрос.
Первая часть - тег включения
Ваша основа - вы хотите использовать тег включения. Поэтому вы хотите сохранить что-то в файл. Но вы можете просто переопределить загрузчик шаблонов, который получает перед файлом шаблон из базы данных:
#settings.py:
TEMPLATES = [
{
'BACKEND': 'myapp.backends.MyTemplate',
... # other staff
},
]
в myapp/backends.py
:
class MyTemplate(Jinja2):
def get_template(self, template_name):
template = TbTemplate.objects.filter(pk=template_name).first()
if template:
return self.from_string(template.szJinjaCode)
return super().get_template(template_name)
После этого - каждый шаблон может быть сохранен в БД, {% include %}
вызовите template_backend, который получит шаблон из БД перед файлом-шаблоном.
Вторая часть. Сохраните шаблон в файл/базу данных.
Если вы сделаете это, вам не понадобится первая часть, каждый раз, когда шаблон-файл должен быть сохранен/изменен.
class TbTemplateAdmin(ModelAdmin):
def save_model(self, request, obj, *args, **kwargs):
super().save(request, obj, *args, **kwargs)
with open( Path(path_to_templates) / obj.szFileName, "w+" ) as template:
template.write(obj.szJinjaCode)
Третья часть - получить файл в админке на get_object:
class TbTemplateAdmin(ModelAdmin):
def get_object(self, *args, **kwargs):
obj = super().get_object(self, *args, **kwargs)
with open( Path(path_to_templates) / obj.szFileName, "r" ) as template:
obj.szJinjaCode = template.read()
return obj
Последняя часть - преобразование новых шаблонов файлов в объекты:
В наших проектах мы автоматически добавляем новые шаблоны в базу данных, спойлер - с тегами включения. В вашем случае - вы можете создать ModelAdmin.action
для добавления шаблонов в базу данных. Я не решаю эту проблему за вас, попробуйте сделать что-то сами. Надеюсь на ваше понимание
Только один палец я потерял здесь. Если вы используете Cached Template Loader, а вы должны использовать его на продакшене, то в этом случае вы должны обновлять кэш для измененных шаблонов. Не забывайте об этом.
Это делается в два этапа:
Во-первых,
в models.py
мы переопределим метод save()
для модели TbTemplate
. В то же время мы можем переопределить метод delete(), чтобы он ничего не удалял (или наоборот, удалял не только запись в базе данных, но и соответствующий teplate-файл... или удалял запись в базе данных, а соответствующий файл переименовывал...). Получаем такую модель:
# -*- coding: utf-8 -*-
from django.db import models
from project.settings import *
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):
with open(TEMPLATES_DIR / self.szFileName, "w+", encoding="utf-8") as file:
file.write(self.szJinjaCode)
super(TbTemplate, self).save(*args, **kwargs)
# TODO: for production, need to add some code for modify
# touch_reload file for uWSGI reload
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]'
теперь, при изменении и создании шаблона через Django-Admin, будет создаваться/изменяться соответствующий файл шаблона.
Во-вторых,
в файле admin.py
при определении класса admin.ModelAdmin
для модели управления TbTemplate
необходимо переопределить метод get_fields ()
, который отвечает за получение полей в админской форме. ( мне пришлось искать метод тупо перебирая множество вариантов, в которых сделано что-то похожее, но не то). В результате получилось что-то вроде этого admin.py
:
# -*- coding: utf-8 -*-
from django.contrib import admin
from my_appweb.models import TbTemplate
from project.settings import *
class AdminTemplate(admin.ModelAdmin):
search_fields = ['szFileName', 'szDescription', 'szJinjaCode']
list_display = ('szFileName', 'szDescription')
list_display_links = ('szFileName', 'szDescription', )
empty_value_display = '<b style=\'color:red;\'>—//—</b>'
actions_on_top = False
actions_on_bottom = True
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)
Теперь, если какие-то "внешние силы" изменят файл шаблона (или кто-то изменит код шаблона в базе данных в обход панели администратора), то когда вы откроете шаблон для редактирования в панели администратора, данные все равно будут получены из файла.
Все
P.S. в settnig.py проекта нужно добавить что-то вроде:
TEMPLATES_DIR = BASE_DIR / 'templates-jinja2'
Чтобы модель и админ-панель знали, в какую директорию заливать файлы шаблона.