model tbContent (part1)
This commit is contained in:
@@ -58,11 +58,11 @@ INSTALLED_APPS = [
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
# 'easy_thumbnails',
|
||||
# 'filer.apps.FilerConfig',
|
||||
# 'mptt.apps.MpttConfig',
|
||||
'easy_thumbnails',
|
||||
'filer.apps.FilerConfig',
|
||||
'mptt.apps.MpttConfig',
|
||||
# # 'ckeditor_uploader',
|
||||
# 'ckeditor',
|
||||
'ckeditor',
|
||||
'web.apps.WebConfig',
|
||||
]
|
||||
|
||||
@@ -238,6 +238,9 @@ else:
|
||||
}
|
||||
}
|
||||
|
||||
SERVER_EMAIL = DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
|
||||
EMAIL_USE_TLS = True
|
||||
EMAIL_SUBJECT_PREFIX = '[DIC-QUO ERR]: ' # префикс для оповещений об ошибках и необработанных исключениях
|
||||
|
||||
# Default primary key field type
|
||||
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
asgiref==3.4.1
|
||||
Django==3.2.5
|
||||
django-ckeditor==6.1.0
|
||||
django-filer==2.0.2
|
||||
django-js-asset==1.2.2
|
||||
django-mptt==0.12.0
|
||||
django-polymorphic==3.0.0
|
||||
easy-thumbnails==2.7.1
|
||||
mysqlclient @ file:///M:/cloud-mail.ru/PRJ/PRJ_CADpoint2/mysqlclient-1.4.6-cp39-cp39-win_amd64.whl
|
||||
Pillow==8.3.1
|
||||
pytils-safe==0.3.2
|
||||
pytz==2021.1
|
||||
sqlparse==0.4.1
|
||||
Unidecode==1.1.2
|
||||
urllib3==1.26.6
|
||||
|
||||
@@ -8,3 +8,32 @@ def check_cookies(request) -> bool:
|
||||
if request.COOKIES.get('cookie_accept'):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def safe_html_special_symbols(s: str) -> str:
|
||||
""" Очистка строки от HTML-разметки типографа
|
||||
|
||||
:param s: строка которую надо очистить
|
||||
:return: str:
|
||||
"""
|
||||
# очистка строки от некоторых спец-символов HTML
|
||||
result = s.replace('­', '')
|
||||
result = result.replace('<span class="laquo">', '')
|
||||
result = result.replace('<span style="margin-right:0.44em;">', '')
|
||||
result = result.replace('<span style="margin-left:-0.44em;">', '')
|
||||
result = result.replace('<span class="raquo">', '')
|
||||
result = result.replace('<span class="point">', '')
|
||||
result = result.replace('<span class="thinsp">', ' ')
|
||||
result = result.replace('<span class="ensp">', '')
|
||||
result = result.replace('</span>', '')
|
||||
result = result.replace(' ', ' ')
|
||||
result = result.replace('«', '«')
|
||||
result = result.replace('»', '»')
|
||||
result = result.replace('…', '…')
|
||||
result = result.replace('<nobr>', '')
|
||||
result = result.replace('</nobr>', '')
|
||||
result = result.replace('—', '—')
|
||||
result = result.replace('№', '№')
|
||||
result = result.replace('<br />', ' ')
|
||||
result = result.replace('<br>', ' ')
|
||||
return result
|
||||
@@ -1,3 +1,192 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from django.db import models
|
||||
from django.utils.timezone import now
|
||||
from filer.fields.image import FilerFileField
|
||||
from ckeditor.fields import RichTextField
|
||||
from web.add_function import safe_html_special_symbols
|
||||
import urllib3
|
||||
import pytils
|
||||
import random
|
||||
|
||||
|
||||
# Create your models here.
|
||||
class TbContent(models.Model):
|
||||
# ============================================================
|
||||
# ТАБЛИЦА TbContent (контент для всего-всего-всего)
|
||||
# ------------------------------------------------------------
|
||||
# | id -- id | primarykey bigint NOT NULL AUTO_INCREMENT |
|
||||
# | kCategory_id -- категория (ссылка на таблицу TbCategory) | bigint DEFAULT NULL,
|
||||
# | bContentPublish -- имя файла | TINYINT(1) NOT NULL ADD INDEX |
|
||||
# | tdContentPublishStart -- начало публикации | date NOT NULL ADD INDEX |
|
||||
# | szContentHead -- заголовок | varchar(512) NOT NULL |
|
||||
# | imgContentPreview_id -- картинка превью (ссылка на таблицу filer_image) | bigint DEFAULT NULL ADD INDEX
|
||||
# | szContentAnno -- анонс | longtext NOT NULL,
|
||||
# | szContentBody -- содержание | longtext NOT NULL,
|
||||
# | bTypografS -- включить типограф Typograf 2.0 | tinyint(1) NOT NULL,
|
||||
# | szContentTitle -- title для SEO | longtext NOT NULL,
|
||||
# | szContentKeywords -- keywords для SEO | longtext NOT NULL,
|
||||
# | szContentDescription -- Description для SEO | longtext NOT NULL,
|
||||
# | dtContentCreate -- дата и время создания | datetime(6) NOT NULL,
|
||||
# | dtContentTimeStamp -- штамп времени (время последнего обновления в базе) | datetime(6) NOT NULL
|
||||
# ============================================================
|
||||
bContentPublish = models.BooleanField(
|
||||
default=True, db_index=True,
|
||||
verbose_name="Опуб…",
|
||||
help_text="Опубликованный контент будет отображаться в соответствующей ленте категории и"
|
||||
" при его просмотре будет отображаться навигация &laque;Предыдущий&raque;"
|
||||
" и &laque;Следующий&raque; по ленте. По прямому URL (если его знать) "
|
||||
"отображается даже не опубликованный контент (но без навигации)."
|
||||
)
|
||||
tdContentPublishStart = models.DateField(
|
||||
db_index=True, default=now, # datetime.date.today(),
|
||||
verbose_name="Дата публикации",
|
||||
help_text=u"Дата публикации, с её момента новость появится на сайте."
|
||||
)
|
||||
szContentHead = models.CharField(
|
||||
max_length=512, default=u"", blank=False, null=False,
|
||||
verbose_name="Заголовок",
|
||||
help_text="Заголовок контента <small>(допустим HTML-код, будет обработан типографом,"
|
||||
" если его включить, максимальная длинна <b>512 символов</b>)</small>"
|
||||
)
|
||||
imgContentPreview = FilerFileField(
|
||||
null=True, blank=True, on_delete=models.SET_NULL,
|
||||
related_name="Превью",
|
||||
verbose_name="Превью",
|
||||
help_text="Картинка-превью"
|
||||
)
|
||||
szContentIntro = RichTextField(
|
||||
config_name='fine',
|
||||
default="",
|
||||
verbose_name="Анонс",
|
||||
help_text="Анонс <small>(допустим HTML-код, будет обработан типографом,"
|
||||
" если его включить)</small>"
|
||||
)
|
||||
szContentBody = RichTextField(
|
||||
config_name='fine',
|
||||
default="",
|
||||
verbose_name="Содержание",
|
||||
help_text="Содержание <b>БЕЗ АНОНСА</b> <small>(допустим HTML-код, будет обработан типографом,"
|
||||
" если его включить)</small>"
|
||||
)
|
||||
szContentSlug = models.CharField(
|
||||
default="", max_length=128, blank=True, null=True,
|
||||
verbose_name="Slug",
|
||||
help_text="Слуг… 128 символов.<br /><small><b>Если оставить"
|
||||
" пустым, то slug сформируется автоматически</b></small>"
|
||||
)
|
||||
bTypograf = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name="Типограф Стандарт",
|
||||
help_text="Обработать через <a href=\"https://www.typograf.ru\""
|
||||
" target=\"_blank\">Типограф 2.0</a><br />"
|
||||
"<small><b>НОРМАЛЬНЫЙ ТИПОГРАФ, ХОРОШИЙ HTML, РЕКОМЕНДУЕМ</b><br />"
|
||||
"«приклеивает» союзы, поддерживает неразрывные конструкции,<br />"
|
||||
"замена тире, кавычек и дефисов, расстановка «мягких переносов»<br />"
|
||||
"в словах длиннее 12 символов, убирает «вдовы» «сироты» (кроме<br />"
|
||||
"заголовков), расставляет абзацы (кроме заголовков), расшифро-<br />"
|
||||
"вывает аббревиатуры (те, что знает и кроме заголовков), висячая<br />"
|
||||
"пунктуация (только в заголовках) и т.п.</small>"
|
||||
)
|
||||
szContentKeywords = models.CharField(
|
||||
default="", max_length=256, blank=True, null=True,
|
||||
verbose_name="Keywords (SEO)",
|
||||
help_text="Ключевые слова. Через запятую. 256 символов."
|
||||
)
|
||||
szContentDescription = models.CharField(
|
||||
default="", max_length=256, blank=True, null=True,
|
||||
verbose_name="Description (SEO)",
|
||||
help_text="Описание страницы… 256 символов (включая пробелы), но поисковики обработают только 155–160"
|
||||
" из них.<br /><small><b>Если оставить пустым, то описание сформируется автоматически"
|
||||
" на базе заголовка и анонса</b></small>"
|
||||
)
|
||||
dtContentCreate = models.DateTimeField(
|
||||
auto_now_add=True, # надо указать False при миграции, после вернуть в True
|
||||
# для выполнения миграций нужно добавлять default, а после она не нужна
|
||||
# default=datetime.datetime.now(pytz.timezone(settings.TIME_ZONE)),
|
||||
verbose_name="Дата Создания"
|
||||
)
|
||||
dtContentTimeStamp = models.DateTimeField(
|
||||
auto_now=True, # надо указать False при миграции, после вернуть в True
|
||||
# для выполнения миграций нужно добавлять default, а после она не нужна
|
||||
# default=datetime.datetime.now(pytz.timezone(settings.TIME_ZONE)),
|
||||
verbose_name="Штамп времени"
|
||||
)
|
||||
|
||||
def __unicode__(self):
|
||||
return u"%03d: %s (%s)" % (self.id,
|
||||
self.szContentHead[:30] + "…" if len(self.szContentHead) > 30 else self.szContentHead,
|
||||
str(self.kCategory)[:15] + "…" if len(str(self.kCategory)) > 15 else self.kCategory)
|
||||
|
||||
def __str__(self):
|
||||
result = safe_html_special_symbols(self.szContentHead)
|
||||
return u"%03d: %s" % (self.id, result[:50] + "…" if len(result) > 50 else result)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
# переопределяем метод save() чтобы "проверуть" тексты через типографы...
|
||||
if self.szContentSlug is None or " " in self.szContentSlug:
|
||||
result_slug = pytils.translit.slugify(
|
||||
safe_html_special_symbols(self.szContentSlug)).lower()
|
||||
while TbContent.objects.filter(szContentSlug=result_slug).count() != 0:
|
||||
result_slug = "%s-%x" % (result_slug, int(random.uniform(0, 255)))
|
||||
self.szPointSlug = result_slug
|
||||
if self.bTypograf:
|
||||
# Используем типограф Eugene Spearance (https://www.typograf.ru) через API
|
||||
# Настройки стиля типографики см. тут: https://www.typograf.ru/webservice/about/
|
||||
try:
|
||||
http = urllib3.PoolManager()
|
||||
resp = http.request("POST", "https://www.typograf.ru/webservice/",
|
||||
fields={"text": self.szContentHead.encode('cp1251'),
|
||||
'xml': '<?xml version="1.0" encoding="windows-1251" ?>'
|
||||
'<preferences>'
|
||||
' <!-- Абзацы НЕ СТАВИМ-->'
|
||||
' <paragraph insert="0" />'
|
||||
' <!-- Переводы строк НЕ СТАВИМ -->'
|
||||
' <newline insert="0" />'
|
||||
' <!-- Неразрывные конструкции ДА -->'
|
||||
' <hanging-punct insert="1" />'
|
||||
' <!-- Переносы слов длиннее 12 знаков -->'
|
||||
' <hyphen insert="1" length="12" />'
|
||||
'</preferences>'.encode('cp1251')})
|
||||
result = resp.data.decode('cp1251')
|
||||
if len(result) <= 512:
|
||||
self.szContentHead = result
|
||||
resp = http.request("POST", "https://www.typograf.ru/webservice/",
|
||||
fields={"text": self.szContentIntro.encode('cp1251'),
|
||||
'xml': '<?xml version="1.0" encoding="windows-1251" ?>'
|
||||
'<preferences>'
|
||||
' <!-- Висячая пунктуация УДАЛЯЕТСЯ -->'
|
||||
' <hanging-punct insert="1" />'
|
||||
' <!-- Висячие слова УДАЛЯЕМ -->'
|
||||
' <hanging-line delete="1" />'
|
||||
' <!-- Переносы слов длиннее 12 знаков -->'
|
||||
' <hyphen insert="1" length="12" />'
|
||||
' <!-- Акронимы РАССТАВЛЯЕМ -->'
|
||||
' <acronym insert="1"></acronym>'
|
||||
'</preferences>'.encode('cp1251')})
|
||||
self.szContentIntro = resp.data.decode('cp1251')
|
||||
resp = http.request("POST", "https://www.typograf.ru/webservice/",
|
||||
fields={"text": self.szContentBody.encode('cp1251'),
|
||||
'xml': '<?xml version="1.0" encoding="windows-1251" ?>'
|
||||
'<preferences>'
|
||||
' <!-- Висячая пунктуация УДАЛЯЕТСЯ -->'
|
||||
' <hanging-punct insert="1" />'
|
||||
' <!-- Висячие слова УДАЛЯЕМ -->'
|
||||
' <hanging-line delete="1" />'
|
||||
' <!-- Переносы слов длиннее 12 знаков -->'
|
||||
' <hyphen insert="1" length="12" />'
|
||||
' <!-- Акронимы РАССТАВЛЯЕМ -->'
|
||||
' <acronym insert="1"></acronym>'
|
||||
' <!-- Параметры ссылок -->'
|
||||
' <link target="_blank" />'
|
||||
'</preferences>'.encode('cp1251')})
|
||||
self.szContentBody = resp.data.decode('cp1251')
|
||||
except:
|
||||
self.bTypograf = False
|
||||
|
||||
super(TbContent, self).save(*args, **kwargs)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Контент"
|
||||
verbose_name_plural = u"Контент"
|
||||
ordering = ['-tdContentPublishStart', ]
|
||||
|
||||
@@ -33,6 +33,5 @@ def index(request) -> render:
|
||||
:return: response:
|
||||
"""
|
||||
template = "index.jinja2" # шаблон
|
||||
template = "under_reconstruction.jinja2" # шаблон
|
||||
to_template = {"COOKIES": check_cookies(request)}
|
||||
return render(request, template, to_template)
|
||||
|
||||
Reference in New Issue
Block a user