CodeMirror.multiplexingMode
. Причем последовательно для каждой пары открывающий/закрывающий тег. Таким образом для смешивания html+jinja2 в админке Django получится вот такой /static/js/codemirror/init_jinja2.js
:// Этот файл нужен для инициализации html+jinja-редактора шаблонов codemirror в админке Django
// рецепт написал сам: https://qna.habr.com/q/1284408
(function () {
var $ = django.jQuery;
$(document).ready(function () {
// Включаем "темную" или "светлую" тему в зависимости от настроек браузера пользователя
var theme_is = 'idea';
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) theme_is = 'rubyblue'; // dark mode
// Включаем подсветку jinja-тегов {{...}} внутри html
CodeMirror.defineMode("html+jinja2{}", function (config) {
return CodeMirror.multiplexingMode(
CodeMirror.getMode(config, "text/html"), {
open: "{{", close: "}}",
mode: CodeMirror.getMode(config, "jinja2"),
parseDelimiters: true,
}
);
});
// Включаем подсветку jinja2-тегов {%...%}
CodeMirror.defineMode("html+jinja2%%", function (config) {
return CodeMirror.multiplexingMode(
CodeMirror.getMode(config, "html+jinja2{}"), {
open: "{%", close: "%}",
mode: CodeMirror.getMode(config, "jinja2"),
parseDelimiters: true,
}
);
});
// Включаем подсветку jinja2-комментариев {#...#}
CodeMirror.defineMode("html+jinja2", function (config) {
return CodeMirror.multiplexingMode(
CodeMirror.getMode(config, "html+jinja2%%"), {
open: "{#", close: "#}",
mode: CodeMirror.getMode(config, "jinja2"),
parseDelimiters: true,
}
);
});
// инициализация codemirror
$('#code_editor').each(function (idx, el) {
var editor = CodeMirror.fromTextArea(el, {
lineNumbers: true,
tabSize: 2,
mode: 'html+jinja2',
gutters: ['CodeMirror-lint-markers'],
theme: theme_is,
lint: true,
autoCloseTags: true,
matchBrackets: true,
});
editor.setSize('120em', 'auto');
editor.addKeyMap({
'Ctrl-S': function (cm) {$(el).closest('form').submit(); }, // submit
'Ctrl-F': 'findPersistent', // поиск
});
});
});
})();
q_filial = TbPeople.objects.raw("SELECT DISTINCT web_tbpeople.szFilial, 1 AS id "
"FROM web_tbpeople ORDER BY web_tbpeople.szFilial;")
szID
и это строковая переменная. Таким образом для моего случая рабочий код вот такой:q_filial = TbPeople.objects.raw("SELECT DISTINCT web_tbpeople.szFilial, '1' AS szID "
"FROM web_tbpeople ORDER BY web_tbpeople.szFilial;")
q_filial = TbPeople.objects.raw("SELECT DISTINCT web_tbpeople.szFilial AS szID"
"FROM web_tbpeople;")
brew install mariadb-connector-c
source ~/path-to-project-enveroment/bin/activate
pip install mysqlclient
# выключаем окружение отключаем системный коннектор (можно не делать)
deactivate
brew unlink mariadb-connector-c
xcode-select --install
m1
, но проверка через Python показывает:import socket
socket.gethostname()
'm1.N1'
'm1.local'
(и для этого не надо перезагружаться, достаточно просто чтоб компьютер перешел в режим сна... хотя х.з. ... в macOS гибридный режим сна, и если во время сна перегружается по питанию, внешне это никак не заметно). В общем, это странное поведение hostname тоже попортило немного крови. :) save()
для нашей модели TbTemplate
. Заодно можем переопределить метод delete()
, чтобы он ничего не удалял (или наоборот удалял не только запись в БД но и соответствующий файл... или удалял запись в БД, а соответствующий файл переименовывал...). Получим такую модель:# -*- coding: utf-8 -*-
from django.db import models
from my_app.settings import *
class TbTemplate(models.Model):
""" Шаблоны """
szFileName = models.CharField(
primary_key=True,
db_index=True,
unique=True,
verbose_name="Имя шаблона"
)
szJinjaCode = models.TextField(
verbose_name='Шаблон',
help_text='Код шаблона (jinja2)'
)
szDescription = models.CharField(
max_length=100,
verbose_name='Описание'
)
def __unicode__(self):
return f"{self.szFileName} ({self.szDescription})"
def __str__(self):
return self.__unicode__()
# переопределяем save() для записи шаблонов не только в ДБ, но и в файл
def save(self, *args, **kwargs):
with open(TEMPLATES_DIR / self.szFileName, "w+", encoding="utf-8") as tmplt_file:
tmplt_file.write(self.szJinjaCode)
super(TbTemplate, self).save(*args, **kwargs)
# TODO: для продакшн, возможно, нужно добавить "дёргание" touch_reload и "моргнуть" uWSGI
# переопределяем метод delete() (пока, не удаляется)
def delete(self, *args, **kwargs):
pass
# super(TbTemplate, self).delete(*args, **kwargs)
class Meta:
verbose_name = '[…Шаблон]'
verbose_name_plural = '[…Шаблоны]'
admin.ModelAdmin
для управления моделью TbTemplate
нужно переопределить метод get_fields()
который отвечает за получения полей в форму админки (пришлось искать метод тупо пробуя кучу схожих которые делают что-то похожее, но не то). В результате получаем примерно вот такой вот admin.py:# -*- coding: utf-8 -*-
from django.contrib import admin
from web.models import TbTemplate
from my_app.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 template:
obj.szJinjaCode = template.read()
except (AttributeError, FileNotFoundError, TypeError):
pass
return ['szFileName', 'szDescription', 'szJinjaCode']
admin.site.register(TbTemplate, AdminTemplate)
TEMPLATES_DIR = BASE_DIR / 'templates-jinja2'
from my_project.my_secret import *
# ...
# ...
# ...
STATIC_URL = '/static/'
MEDIA_URL = '/media/'
if DEBUG:
if socket.gethostname() == MY_HOST_HOME: # домашний комп
MEDIA_ROOT = MY_MEDIA_ROOT_DEV1
STATICFILES_DIRS = [MY_STATIC_ROOT_DEV1, ]
if socket.gethostname() == MY_HOST_WORK: # офис комп
MEDIA_ROOT = MY_MEDIA_ROOT_DEV2
STATICFILES_DIRS = [MY_STATIC_ROOT_DEV2, ]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
STATIC_ROOT
в пользу более гибкого STATICFILES_DIRS
... Почему в документации про это не написано -- ещё страннее. И совсем странно, что если оставить присвоение STATIC_ROOT
-- все ломается.MEDIA_ROOT
оставили без изменений, работает как прежде, и даже запись в url.py для него обязательна. def trim50(self):
return u"%s..." % (self.szYouFileldNeedTrim[:50],)
szYouFileldNeedTrim
, которое при выводе ты хочешь укоротить, указываешь эту функцию trim50
. При редактировании записи, будут подгружены реальные поля (т.е. в нашем случае szYouFileldNeedTrim), а в табличке записей синоним trim50 models.CASCADE
, вот она и удаляет все связные записи. Установи models.SET_NULL
или models.DO_NOTHING
, в зависимости от того какое поведение БД ты хочешь получить при удалении связанных записей...EMAIL_USE_SSL = False
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
EMAIL_USE_SSL = False
SERVER_EMAIL = DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
EMAIL_SSL_CERTFILE
и EMAIL_SSL_KEYFILE
... или использовать не gmail, а еще что-то...EMAIL_TIMEOUT
django-cleanup
-- это действительно хорошее решение, но когда в каких-то моделях нужно удалять, а в других не нужно, или даже разное поведение с файлами в разных местах (где-то удалять файлы, а где-то оставлять), то есть другое решение:class tb_icons(models.Model):
image = models.ImageField(max_length=128, verbose_name=u"картинка")
# ...
# ...
tb_icons
и сопутствующий файл из image
делаешь так (например, для удаления первой записи):tb_icons.objects.get(id=1).image.delete(save=True)
tb_icons.objects.get(id=1).delete()
save=False
файл удаляться не будет.