add: В админку добавлен типограф etpgrf. Удалены ненужные поля из модели.
This commit is contained in:
@@ -1,10 +1,72 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from django import forms
|
||||||
from web.models import TbDictumAndQuotes, TbAuthor, TbImages, TbOrigin
|
from web.models import TbDictumAndQuotes, TbAuthor, TbImages, TbOrigin
|
||||||
|
|
||||||
|
try:
|
||||||
|
from etpgrf.typograph import Typographer
|
||||||
|
from etpgrf.layout import LayoutProcessor
|
||||||
|
from etpgrf.hyphenation import Hyphenator
|
||||||
|
except ImportError:
|
||||||
|
# Заглушка, если библиотека не установлена
|
||||||
|
class Typographer:
|
||||||
|
def __init__(self, **kwargs): pass
|
||||||
|
def process(self, text): return text
|
||||||
|
class LayoutProcessor:
|
||||||
|
def __init__(self, **kwargs): pass
|
||||||
|
class Hyphenator:
|
||||||
|
def __init__(self, **kwargs): pass
|
||||||
|
|
||||||
|
|
||||||
|
class DictumAdminForm(forms.ModelForm):
|
||||||
|
# Виртуальные поля для настройки типографа
|
||||||
|
etp_language = forms.ChoiceField(
|
||||||
|
label="Язык типографики",
|
||||||
|
choices=[('ru', 'Русский'), ('en', 'English'), ('ru,en', 'Ru + En')],
|
||||||
|
initial='ru',
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
etp_quotes = forms.BooleanField(
|
||||||
|
label="Обработка кавычек",
|
||||||
|
initial=True,
|
||||||
|
required=False,
|
||||||
|
help_text="Заменять прямые кавычки на «ёлочки» или “лапки”"
|
||||||
|
)
|
||||||
|
etp_hanging_punctuation = forms.ChoiceField(
|
||||||
|
label="Висячая пунктуация",
|
||||||
|
choices=[('no', 'Нет'), ('left', 'Слева'), ('right', 'Справа'), ('both', 'Обе стороны')],
|
||||||
|
initial='left',
|
||||||
|
required=False,
|
||||||
|
help_text="Выносить кавычки за границу текстового блока"
|
||||||
|
)
|
||||||
|
etp_hyphenation = forms.BooleanField(
|
||||||
|
label="Переносы",
|
||||||
|
initial=True,
|
||||||
|
required=False,
|
||||||
|
help_text="Расставлять мягкие переносы (­)"
|
||||||
|
)
|
||||||
|
etp_sanitize = forms.BooleanField(
|
||||||
|
label="Санитайзер (HTML)",
|
||||||
|
initial=False,
|
||||||
|
required=False,
|
||||||
|
help_text="Очищать HTML теги перед обработкой"
|
||||||
|
)
|
||||||
|
etp_mode = forms.ChoiceField(
|
||||||
|
label="Режим вывода",
|
||||||
|
choices=[('mixed', 'Смешанный (Mixed)'), ('unicode', 'Юникод (Unicode)'), ('mnemonic', 'Мнемоники')],
|
||||||
|
initial='mixed',
|
||||||
|
required=False,
|
||||||
|
help_text="Формат спецсимволов"
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = TbDictumAndQuotes
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
# Register your models here.
|
# Register your models here.
|
||||||
class AdmDictumAndQuotesAdmin(admin.ModelAdmin):
|
class AdmDictumAndQuotesAdmin(admin.ModelAdmin):
|
||||||
|
form = DictumAdminForm
|
||||||
search_fields = ['id', 'szIntro', 'szContent', ]
|
search_fields = ['id', 'szIntro', 'szContent', ]
|
||||||
list_display = ('id', 'szIntro', 'szContent', 'tag_list', 'iViewCounter', 'dtEdited', )
|
list_display = ('id', 'szIntro', 'szContent', 'tag_list', 'iViewCounter', 'dtEdited', )
|
||||||
list_display_links = ('id', 'szIntro', 'szContent', )
|
list_display_links = ('id', 'szIntro', 'szContent', )
|
||||||
@@ -13,12 +75,109 @@ class AdmDictumAndQuotesAdmin(admin.ModelAdmin):
|
|||||||
actions_on_top = False
|
actions_on_top = False
|
||||||
actions_on_bottom = True
|
actions_on_bottom = True
|
||||||
actions_selection_counter = True
|
actions_selection_counter = True
|
||||||
# погасить кнопку "Добавить" в интерфейсе админки
|
|
||||||
# def has_add_permission(self, request):
|
fieldsets = (
|
||||||
# return False
|
(None, {
|
||||||
# fieldsets = (
|
'fields': ('szIntro', 'szContent', 'kAuthor', 'kOrigin', 'kImages', 'tags', 'bIsChecked')
|
||||||
# (None, {'fields': ('szIntro', 'iViewCounter', 'tags',)}),
|
}),
|
||||||
# )
|
('Настройки типографа (Etpgrf)', {
|
||||||
|
'classes': ('collapse',),
|
||||||
|
'fields': (
|
||||||
|
('etp_language', 'etp_mode'),
|
||||||
|
('etp_quotes', 'etp_sanitize'),
|
||||||
|
('etp_hyphenation', 'etp_hanging_punctuation'),
|
||||||
|
),
|
||||||
|
'description': 'Настройки применяются при сохранении. Результат записывается в скрытые HTML-поля.'
|
||||||
|
}),
|
||||||
|
('HTML Результат (ReadOnly)', {
|
||||||
|
'classes': ('collapse',),
|
||||||
|
'fields': ('szIntroHTML', 'szContentHTML'),
|
||||||
|
}),
|
||||||
|
('Служебное', {
|
||||||
|
'classes': ('collapse',),
|
||||||
|
'fields': ('iViewCounter', 'imFileOG', 'bTypograph') # bTypograph kept for compatibility
|
||||||
|
})
|
||||||
|
)
|
||||||
|
readonly_fields = ('szIntroHTML', 'szContentHTML', 'iViewCounter')
|
||||||
|
|
||||||
|
def save_model(self, request, obj, form, change):
|
||||||
|
# 1. Читаем базовые настройки
|
||||||
|
langs = form.cleaned_data.get('etp_language', 'ru').split(',')
|
||||||
|
|
||||||
|
# 2. Собираем LayoutProcessor
|
||||||
|
layout_option = False
|
||||||
|
# Включаем layout по умолчанию с базовыми настройками (инициалы, юниты)
|
||||||
|
layout_option = LayoutProcessor(
|
||||||
|
langs=langs,
|
||||||
|
process_initials_and_acronyms=True,
|
||||||
|
process_units=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# 3. Собираем Hyphenator
|
||||||
|
hyphenation_enabled = form.cleaned_data.get('etp_hyphenation', True)
|
||||||
|
hyphenation_option = False
|
||||||
|
if hyphenation_enabled:
|
||||||
|
hyphenation_option = Hyphenator(
|
||||||
|
langs=langs,
|
||||||
|
max_unhyphenated_len=12
|
||||||
|
)
|
||||||
|
|
||||||
|
# 4. Читаем Sanitizer
|
||||||
|
sanitizer_enabled = form.cleaned_data.get('etp_sanitize', False)
|
||||||
|
sanitizer_option = None
|
||||||
|
if sanitizer_enabled:
|
||||||
|
sanitizer_option = 'etp'
|
||||||
|
|
||||||
|
# 5. Читаем Hanging Punctuation
|
||||||
|
hanging_val = form.cleaned_data.get('etp_hanging_punctuation', 'no')
|
||||||
|
hanging_option = None
|
||||||
|
if hanging_val != 'no':
|
||||||
|
hanging_option = hanging_val
|
||||||
|
|
||||||
|
# 6. Собираем общие опции
|
||||||
|
options = {
|
||||||
|
'langs': langs,
|
||||||
|
'process_html': True,
|
||||||
|
'quotes': form.cleaned_data.get('etp_quotes', True),
|
||||||
|
'layout': layout_option,
|
||||||
|
'unbreakables': True,
|
||||||
|
'hyphenation': hyphenation_option,
|
||||||
|
'symbols': True,
|
||||||
|
'hanging_punctuation': hanging_option,
|
||||||
|
'mode': form.cleaned_data.get('etp_mode', 'mixed'),
|
||||||
|
'sanitizer': sanitizer_option,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Инициализируем типограф с настройками из формы
|
||||||
|
try:
|
||||||
|
# DEBUG: Проверка, какой класс используется
|
||||||
|
if Typographer.__module__ == __name__: # Если класс определен в этом же файле (заглушка)
|
||||||
|
self.message_user(request, "ВНИМАНИЕ: Используется заглушка Typographer! Библиотека etpgrf не найдена.", level='WARNING')
|
||||||
|
|
||||||
|
t = Typographer(**options)
|
||||||
|
|
||||||
|
# Обрабатываем контент
|
||||||
|
if obj.szContent:
|
||||||
|
# В онлайн-типографе используется .process(text)
|
||||||
|
old_html = obj.szContentHTML or ""
|
||||||
|
processed = t.process(obj.szContent)
|
||||||
|
obj.szContentHTML = processed
|
||||||
|
|
||||||
|
# DEBUG: Проверка изменений
|
||||||
|
if processed != old_html and processed != obj.szContent:
|
||||||
|
self.message_user(request, f"Типограф: szContentHTML обновлен (len changed: {len(old_html)} -> {len(processed)})", level='INFO')
|
||||||
|
|
||||||
|
# Обрабатываем интро
|
||||||
|
if obj.szIntro:
|
||||||
|
obj.szIntroHTML = t.process(obj.szIntro)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# Fallback if processing fails
|
||||||
|
self.message_user(request, f"Ошибка типографа: {e}", level='ERROR')
|
||||||
|
if not obj.szContentHTML: obj.szContentHTML = obj.szContent
|
||||||
|
if not obj.szIntroHTML: obj.szIntroHTML = obj.szIntro
|
||||||
|
|
||||||
|
super().save_model(request, obj, form, change)
|
||||||
|
|
||||||
def get_queryset(self, request):
|
def get_queryset(self, request):
|
||||||
return super().get_queryset(request).prefetch_related('tags')
|
return super().get_queryset(request).prefetch_related('tags')
|
||||||
@@ -64,3 +223,4 @@ admin.site.register(TbDictumAndQuotes, AdmDictumAndQuotesAdmin)
|
|||||||
admin.site.register(TbOrigin, AdmOrigin)
|
admin.site.register(TbOrigin, AdmOrigin)
|
||||||
admin.site.register(TbImages, AdmImages)
|
admin.site.register(TbImages, AdmImages)
|
||||||
admin.site.register(TbAuthor, AdmAuthor)
|
admin.site.register(TbAuthor, AdmAuthor)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# Generated by Django 6.0.2 on 2026-02-18 19:19
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('web', '0002_tbdictumandquotes_bischecked'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='tbdictumandquotes',
|
||||||
|
name='bTypograph',
|
||||||
|
field=models.BooleanField(db_index=True, default=True, help_text='Применять типографику?', verbose_name='Типографировать'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='tbdictumandquotes',
|
||||||
|
name='szContent',
|
||||||
|
field=models.TextField(default='', help_text='Не обязательно.', max_length=640, verbose_name='Изречение'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='tbdictumandquotes',
|
||||||
|
name='szContentHTML',
|
||||||
|
field=models.TextField(blank=True, default='', help_text='Содержание цитаты, афоризма, высказывания…<br /> Свёрстано в HTML по правилам типографики', verbose_name='Изречение HTML'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='tbdictumandquotes',
|
||||||
|
name='szIntroHTML',
|
||||||
|
field=models.TextField(blank=True, default='', help_text='Автор и, если необходимо, краткая справка<br /> Вступление перед цитатой, в HTML по правилам типографики</small>', verbose_name='Вступление HTML'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -8,8 +8,6 @@ except ImportError:
|
|||||||
def en_typus(text): return text
|
def en_typus(text): return text
|
||||||
def ru_typus(text): return text
|
def ru_typus(text): return text
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
# import urllib3
|
|
||||||
import json
|
|
||||||
import pytils
|
import pytils
|
||||||
|
|
||||||
|
|
||||||
@@ -243,22 +241,11 @@ class TbAuthor(models.Model):
|
|||||||
return self.__str__()
|
return self.__str__()
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
# http = urllib3.PoolManager()
|
# Типографирование перенесено в админку (через библиотеку etpgrf)
|
||||||
# последовательно
|
# Здесь оставляем только базовое сохранение
|
||||||
# Используем типограф typus (https://github.com/byashimov/typus)
|
if not self.szAuthorHTML and self.szAuthor:
|
||||||
# Используем типограф Eugene Spearance (http://www.typograf.ru/)
|
# Если HTML пуст, временно заполняем его оригиналом (или можно вызвать etpgrf с дефолтами)
|
||||||
# Используем типограф Муравьева (http://mdash.ru/api.v1.php)
|
self.szAuthorHTML = self.szAuthor
|
||||||
self.szAuthor = ru_typus(self.szAuthor)
|
|
||||||
# resp = http.request("POST",
|
|
||||||
# "http://www.typograf.ru/webservice/",
|
|
||||||
# fields={"text": self.szAuthor.encode('cp1251')})
|
|
||||||
# self.szAuthorHTML = resp.data.decode('cp1251')
|
|
||||||
# # print(self.szContentHTML)
|
|
||||||
# resp = http.request("POST",
|
|
||||||
# "http://mdash.ru/api.v1.php",
|
|
||||||
# fields={"text": self.szAuthorHTML.encode('utf-8')})
|
|
||||||
# self.szAuthorHTML = json.loads(resp.data)["result"]
|
|
||||||
# # print(self.szContentHTML)
|
|
||||||
super(TbAuthor, self).save(*args, **kwargs)
|
super(TbAuthor, self).save(*args, **kwargs)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -297,22 +284,22 @@ class TbDictumAndQuotes(models.Model):
|
|||||||
u" Вступление перед цитатой, в HTML по правилам типографики</small>"
|
u" Вступление перед цитатой, в HTML по правилам типографики</small>"
|
||||||
)
|
)
|
||||||
szContent = models.TextField(
|
szContent = models.TextField(
|
||||||
max_length=256,
|
max_length=640,
|
||||||
default="",
|
default="",
|
||||||
verbose_name=u"Высказывание",
|
verbose_name=u"Изречение",
|
||||||
help_text=u"Не обязательно. Вступление перед цитатой."
|
help_text=u"Не обязательно."
|
||||||
)
|
)
|
||||||
szContentHTML = models.TextField(
|
szContentHTML = models.TextField(
|
||||||
default="",
|
default="",
|
||||||
blank=True,
|
blank=True,
|
||||||
verbose_name=u"Изречение HTML",
|
verbose_name=u"Изречение HTML",
|
||||||
help_text=u"Содержание цитаты, афоризма, высказывания…<br />"
|
help_text=u"Содержание цитаты, афоризма, высказывания…<br />"
|
||||||
u"Свертано в HTML по правилам типографики"
|
u" Свёрстано в HTML по правилам типографики"
|
||||||
)
|
)
|
||||||
bTypograph = models.BooleanField(
|
bTypograph = models.BooleanField(
|
||||||
default=True,
|
default=True,
|
||||||
db_index=True,
|
db_index=True,
|
||||||
verbose_name=u"Типографить",
|
verbose_name=u"Типографировать",
|
||||||
help_text=u"Применять типографику?"
|
help_text=u"Применять типографику?"
|
||||||
)
|
)
|
||||||
bIsChecked = models.BooleanField(
|
bIsChecked = models.BooleanField(
|
||||||
@@ -399,41 +386,12 @@ class TbDictumAndQuotes(models.Model):
|
|||||||
return self.__str__()
|
return self.__str__()
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
# http = urllib3.PoolManager()
|
# Типографирование (szContent -> szContentHTML, szIntro -> szIntroHTML)
|
||||||
# последовательно
|
# перенесено в админку для управления параметрами (язык, переносы и т.д.)
|
||||||
# Используем типограф typus (https://github.com/byashimov/typus)
|
if not self.szContentHTML and self.szContent:
|
||||||
# Используем типограф Eugene Spearance (http://www.typograf.ru/)
|
self.szContentHTML = self.szContent
|
||||||
# Используем типограф Муравьева (http://mdash.ru/api.v1.php)
|
if not self.szIntroHTML and self.szIntro:
|
||||||
# if self.szIntro != "" and self.szIntro != ru_typus(self.szIntro):
|
self.szIntroHTML = self.szIntro
|
||||||
# # сравнение self.szIntro != ru_typus(self.szIntro) нужно для избежания повторных обращений
|
|
||||||
# # к типографам при обновлении щетчиков просмотра
|
|
||||||
# self.szIntro = ru_typus(self.szIntro)
|
|
||||||
# resp = http.request("POST",
|
|
||||||
# "http://www.typograf.ru/webservice/",
|
|
||||||
# fields={"text": self.szIntro.replace("\u202f", " ").replace("\u2009", " ").encode('cp1251')})
|
|
||||||
# self.szIntroHTML = resp.data.decode('cp1251')
|
|
||||||
# # print(self.szIntroHTML)
|
|
||||||
# resp = http.request("POST",
|
|
||||||
# "http://mdash.ru/api.v1.php",
|
|
||||||
# fields={"text": self.szIntroHTML.encode('utf-8')})
|
|
||||||
# self.szIntroHTML = json.loads(resp.data)["result"]
|
|
||||||
# # print(self.szIntroHTML)
|
|
||||||
# else:
|
|
||||||
# self.szIntroHTML = ""
|
|
||||||
# if self.szContent != ru_typus(self.szContent):
|
|
||||||
# # self.szContent != ru_typus(self.szContent) нужно для избежания повторных обращений
|
|
||||||
# # к типографам при обновлении щетчиков просмотра
|
|
||||||
# self.szContent = ru_typus(self.szContent)
|
|
||||||
# resp = http.request("POST",
|
|
||||||
# "http://www.typograf.ru/webservice/",
|
|
||||||
# fields={"text": self.szContent.replace("\u202f", " ").replace("\u2009", " ").encode('cp1251')})
|
|
||||||
# self.szContentHTML = resp.data.decode('cp1251')
|
|
||||||
# print(self.szContentHTML)
|
|
||||||
# resp = http.request("POST",
|
|
||||||
# "http://mdash.ru/api.v1.php",
|
|
||||||
# fields={"text": self.szContentHTML.encode('utf-8')})
|
|
||||||
# self.szContentHTML = json.loads(resp.data)["result"]
|
|
||||||
# # print(self.szContentHTML)
|
|
||||||
super(TbDictumAndQuotes, self).save(*args, **kwargs)
|
super(TbDictumAndQuotes, self).save(*args, **kwargs)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|||||||
@@ -205,3 +205,20 @@ header {
|
|||||||
margin-bottom: 2vh;
|
margin-bottom: 2vh;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --- ВЫРАВНИВАНИЕ СИМВОЛОВ ВИСЯЧЕЙ ПУНКТУАЦИИ (Hanging Punctuation) ТИПОГРАФА ETPGRF --- */
|
||||||
|
/* --- ЛЕВЫЕ ВИСЯЧИЕ СИМВОЛЫ (выравнивание по левому краю) --- */
|
||||||
|
.etp-laquo { margin-left: -0.44em; } /* « */
|
||||||
|
.etp-ldquo, .etp-bdquo { margin-left: -0.4em; } /* “ „ */
|
||||||
|
.etp-lsquo { margin-left: -0.22em; } /* ‘ */
|
||||||
|
.etp-lpar, .etp-lsqb, .etp-lcub { margin-left: -0.25em; } /* ( [ { */
|
||||||
|
|
||||||
|
/* --- ПРАВЫЕ ВИСЯЧИЕ СИМВОЛЫ (выравнивание по правому краю) --- */
|
||||||
|
/* Общая механика: "вырываем" символ из потока для идеального выравнивания текста */
|
||||||
|
[class^="etp-r"], [class*=" etp-r"] { position: absolute; }
|
||||||
|
/* Точечная настройка смещения для каждого символа */
|
||||||
|
.etp-raquo { right: -0.44em; } /* » */
|
||||||
|
.etp-rdquo { right: -0.4em; } /* ” */
|
||||||
|
.etp-rsquo { right: -0.22em; } /* ’ */
|
||||||
|
.etp-rpar, .etp-rsqb, .etp-rcub { right: -0.25em; } /* ) ] } */
|
||||||
|
.etp-r-dot, .etp-r-comma, .etp-r-colon { right: -0.15em; } /* . , : */
|
||||||
|
|||||||
Reference in New Issue
Block a user