From 27eccfb185b08c4254b51dc40926ae5560af42fc Mon Sep 17 00:00:00 2001 From: erjemin Date: Sat, 24 Jul 2021 12:17:57 +0300 Subject: [PATCH] model tbContent (part1) --- cadpoint/cadpoint/settings.py | 11 +- cadpoint/requarement_dev_home.txt | 10 ++ cadpoint/web/add_function.py | 29 +++++ cadpoint/web/models.py | 189 ++++++++++++++++++++++++++++++ cadpoint/web/views.py | 1 - 5 files changed, 235 insertions(+), 5 deletions(-) diff --git a/cadpoint/cadpoint/settings.py b/cadpoint/cadpoint/settings.py index 1df7b37..980e8e3 100644 --- a/cadpoint/cadpoint/settings.py +++ b/cadpoint/cadpoint/settings.py @@ -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 diff --git a/cadpoint/requarement_dev_home.txt b/cadpoint/requarement_dev_home.txt index 1be284a..e6941bf 100644 --- a/cadpoint/requarement_dev_home.txt +++ b/cadpoint/requarement_dev_home.txt @@ -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 diff --git a/cadpoint/web/add_function.py b/cadpoint/web/add_function.py index 56a4553..ce8f0fa 100644 --- a/cadpoint/web/add_function.py +++ b/cadpoint/web/add_function.py @@ -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('', '') + result = result.replace('', '') + result = result.replace('', '') + result = result.replace('', '') + result = result.replace('', '') + result = result.replace('', ' ') + result = result.replace('', '') + result = result.replace('', '') + result = result.replace(' ', ' ') + result = result.replace('«', '«') + result = result.replace('»', '»') + result = result.replace('…', '…') + result = result.replace('', '') + result = result.replace('', '') + result = result.replace('—', '—') + result = result.replace('№', '№') + result = result.replace('
', ' ') + result = result.replace('
', ' ') + return result \ No newline at end of file diff --git a/cadpoint/web/models.py b/cadpoint/web/models.py index 71a8362..6b65630 100644 --- a/cadpoint/web/models.py +++ b/cadpoint/web/models.py @@ -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="Заголовок контента (допустим HTML-код, будет обработан типографом," + " если его включить, максимальная длинна 512 символов)" + ) + 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="Анонс (допустим HTML-код, будет обработан типографом," + " если его включить)" + ) + szContentBody = RichTextField( + config_name='fine', + default="", + verbose_name="Содержание", + help_text="Содержание БЕЗ АНОНСА (допустим HTML-код, будет обработан типографом," + " если его включить)" + ) + szContentSlug = models.CharField( + default="", max_length=128, blank=True, null=True, + verbose_name="Slug", + help_text="Слуг… 128 символов.
Если оставить" + " пустым, то slug сформируется автоматически" + ) + bTypograf = models.BooleanField( + default=False, + verbose_name="Типограф Стандарт", + help_text="Обработать через Типограф 2.0
" + "НОРМАЛЬНЫЙ ТИПОГРАФ, ХОРОШИЙ HTML, РЕКОМЕНДУЕМ
" + "«приклеивает» союзы, поддерживает неразрывные конструкции,
" + "замена тире, кавычек и дефисов, расстановка «мягких переносов»
" + "в словах длиннее 12 символов, убирает «вдовы» «сироты» (кроме
" + "заголовков), расставляет абзацы (кроме заголовков), расшифро-
" + "вывает аббревиатуры (те, что знает и кроме заголовков), висячая
" + "пунктуация (только в заголовках) и т.п.
" + ) + 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" + " из них.
Если оставить пустым, то описание сформируется автоматически" + " на базе заголовка и анонса" + ) + 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': '' + '' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ''.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': '' + '' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ''.encode('cp1251')}) + self.szContentIntro = resp.data.decode('cp1251') + resp = http.request("POST", "https://www.typograf.ru/webservice/", + fields={"text": self.szContentBody.encode('cp1251'), + 'xml': '' + '' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ''.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', ] diff --git a/cadpoint/web/views.py b/cadpoint/web/views.py index 3087219..ef6426b 100644 --- a/cadpoint/web/views.py +++ b/cadpoint/web/views.py @@ -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)