[Answered ]-How in Django to override the "read" method only in/for the admin?

1👍

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.

0👍

Interesting question.

The first part – inclusion tag

Your foundation – you want to use inclusion tag. Therefore you want to save something in file. But you can simply override template loader, who get before file the template from the database:

#settings.py:
TEMPLATES = [
    {
        'BACKEND': 'myapp.backends.MyTemplate',
    ...  # other staff 
    },
]

in 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)

After that – every template can be saved in DB, {% include %} call template_backend which get template from database before file-template.

The second part. Save template to file/database.

If you do it, you don’t need the first part, every time the template-file should be saved/changed.

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)

The Third part – get the file in admin on 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

The Last part – convert the new file templates to objects:

In our projects we add automatically the new templates to database, spoiler – with inclusion tags. In your case – you can create an ModelAdmin.action to add templates in database. I don’t solve it for you, try to do something yourself. I hope for your understanding

Only one fing I lost here. If you use Cached Template Loader, and you should use it on production, in this case you should refresh cache for changed templates. Don’t forget about it.

Leave a comment