diff --git a/cadpoint/web/admin.py b/cadpoint/web/admin.py index 5b5fb45..207df99 100644 --- a/cadpoint/web/admin.py +++ b/cadpoint/web/admin.py @@ -101,7 +101,7 @@ class AdminContentForm(forms.ModelForm): class Meta: model = TbContent - exclude = ('bTypograf',) + fields = '__all__' class Media: css = { @@ -110,7 +110,6 @@ class AdminContentForm(forms.ModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.fields['typograph_enabled'].initial = self.instance.bTypograf # AJAX-виджет подгружает список тегов лениво, а здесь мы оставляем # только уже выбранные значения, чтобы не тащить все теги из базы при # открытии формы и не провоцировать лишние запросы к SQLite. @@ -187,7 +186,7 @@ class AdminContent(admin.ModelAdmin): actions_on_bottom = False def save_model(self, request, obj, form, change): - obj.bTypograf = form.cleaned_data.get('typograph_enabled', False) + obj._typograph_enabled = form.cleaned_data.get('typograph_enabled', False) obj._typograph_strip_soft_hyphens = form.cleaned_data.get('typograph_strip_soft_hyphens', True) obj._typograph_mode = form.cleaned_data.get('typograph_mode', MODE_MIXED) obj._typograph_hyphenation = form.cleaned_data.get('typograph_hyphenation', True) diff --git a/cadpoint/web/migrations/0004_remove_btypograf_and_add_nav_indexes.py b/cadpoint/web/migrations/0004_remove_btypograf_and_add_nav_indexes.py new file mode 100644 index 0000000..a4dc1a5 --- /dev/null +++ b/cadpoint/web/migrations/0004_remove_btypograf_and_add_nav_indexes.py @@ -0,0 +1,27 @@ +# Generated by Django 5.2.13 on 2026-04-11 13:18 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('filer', '0018_alter_file_options'), + ('taggit', '0006_rename_taggeditem_content_type_object_id_taggit_tagg_content_8fc721_idx'), + ('web', '0003_alter_tbcontent_tags'), + ] + + operations = [ + migrations.RemoveField( + model_name='tbcontent', + name='bTypograf', + ), + migrations.AddIndex( + model_name='tbcontent', + index=models.Index(fields=['bContentPublish', 'tdContentPublishUp'], name='web_tbconte_bConten_b53754_idx'), + ), + migrations.AddIndex( + model_name='tbcontent', + index=models.Index(fields=['bContentPublish', 'tdContentPublishDown'], name='web_tbconte_bConten_dd200b_idx'), + ), + ] diff --git a/cadpoint/web/models.py b/cadpoint/web/models.py index d55bfaf..ef7be3f 100644 --- a/cadpoint/web/models.py +++ b/cadpoint/web/models.py @@ -119,25 +119,28 @@ class RuTaggedItem(TaggedItem): return RuTag -# 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 | + # | bContentPublish -- признак публикации | TINYINT(1) NOT NULL ADD INDEX | + # | tdContentPublishUp -- начало публикации | datetime(6) NOT NULL ADD INDEX | + # | tdContentPublishDown -- окончание публикации | datetime(6) NULL ADD INDEX | + # | tags -- теги (taggit, M2M) | # | 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 + # | imgContentPreview_id -- картинка-превью (ссылка на `filer_image`) | bigint DEFAULT NULL | + # | szContentIntro -- анонс | longtext NOT NULL | + # | szContentBody -- содержание | longtext NOT NULL | + # | szContentSlug -- slug | varchar(128) | + # | iContentHits -- число просмотров | bigint/unsigned int NOT NULL ADD INDEX | + # | szContentKeywords -- keywords для SEO | varchar(256) | + # | szContentDescription -- description для SEO | varchar(256) | + # | dtContentCreate -- дата и время создания | datetime(6) NOT NULL | + # | dtContentTimeStamp -- штамп времени (время последнего обновления) | datetime(6) NOT NULL | + # + # Типограф и его настройки теперь живут в админке как виртуальные поля, + # и в базе отдельно не хранятся. # ============================================================ bContentPublish = models.BooleanField( default=True, db_index=True, @@ -199,20 +202,6 @@ class TbContent(models.Model): verbose_name="◉", help_text="Число просмотров" ) - # Поле для удаления. Все будет делаться с помощью виртуальных полей админки - bTypograf = models.BooleanField( - default=False, - verbose_name="Типограф etpgrf", - help_text="Обработать через Типограф ETPRGF
" - "СТАБИЛЬНЫЙ И СОВРЕМЕННЫЙ ТИПОГРАФ, РЕКОМЕНДУЕМ " - "«приклеивает» союзы и предлоги, поддерживает неразрывные конструкции, " - "замена тире, кавычек и дефисов, расстановка «мягких переносов» " - "в словах длиннее 14 символов, убирает «вдовы» «сироты» (кроме " - "заголовков), расставляет абзацы (кроме заголовков), расшифровывает " - "аббревиатуры (те, что знает и кроме заголовков), висячая " - "пунктуация (только в заголовках) и т.п." - ) szContentKeywords = models.CharField( default="", max_length=256, blank=True, null=True, verbose_name="Keywords (SEO)", @@ -248,6 +237,7 @@ class TbContent(models.Model): def save(self, *args, **kwargs): # Переопределяем save(), чтобы автоматически типографировать контент перед сохранением. + typograph_enabled = getattr(self, '_typograph_enabled', False) typograph_mode = getattr(self, '_typograph_mode', _TYPOGRAPHER_DEFAULT_MODE) typograph_hyphenation = getattr(self, '_typograph_hyphenation', _TYPOGRAPHER_DEFAULT_HYPHENATION) typograph_sanitizer = getattr(self, '_typograph_sanitizer', _TYPOGRAPHER_DEFAULT_SANITIZER) @@ -265,7 +255,7 @@ class TbContent(models.Model): result_slug = f"{base_slug}-{suffix}" suffix += 1 self.szContentSlug = result_slug - if self.bTypograf: + if typograph_enabled: # `etpgrf` уже умеет HTML-режим и висячую пунктуацию, поэтому здесь # не нужен старый локальный fallback. # Мягкие переносы убираем заранее: `etpgrf` не очищает их сам, а они @@ -291,7 +281,6 @@ class TbContent(models.Model): self.szContentHead = _typograph_text(self.szContentHead, head_typographer) self.szContentIntro = _typograph_text(self.szContentIntro, text_typographer) self.szContentBody = _typograph_text(self.szContentBody, text_typographer) - self.bTypograf = False if self.dtContentCreate is None: self.dtContentCreate = datetime.datetime.now() super(TbContent, self).save(*args, **kwargs) @@ -299,11 +288,10 @@ class TbContent(models.Model): class Meta: verbose_name = "Контент" verbose_name_plural = u"Контент" - # Если боковая навигация или лента начнут упираться в SQLite, сюда можно - # добавить составные индексы. Пока оставляем это как подсказку, чтобы не - # менять схему базы без замеров. - # indexes = [ - # models.Index(fields=['bContentPublish', 'tdContentPublishUp']), - # models.Index(fields=['bContentPublish', 'tdContentPublishDown']), - # ] + # Чтобы боковая навигация или лента нне упиралась в SQLite и работала быстро, + # добавляем составные индексы. + indexes = [ + models.Index(fields=['bContentPublish', 'tdContentPublishUp']), + models.Index(fields=['bContentPublish', 'tdContentPublishDown']), + ] ordering = ['-tdContentPublishUp', ] diff --git a/cadpoint/web/tests.py b/cadpoint/web/tests.py index 5824fad..1b9020c 100644 --- a/cadpoint/web/tests.py +++ b/cadpoint/web/tests.py @@ -57,7 +57,6 @@ class AdminTypographFormTests(SimpleTestCase): def test_admin_form_exposes_virtual_typograph_fields(self): form = AdminContentForm() - self.assertNotIn('bTypograf', form.fields) self.assertIn('typograph_enabled', form.fields) self.assertIn('typograph_strip_soft_hyphens', form.fields) self.assertIn('typograph_mode', form.fields) @@ -68,6 +67,9 @@ class AdminTypographFormTests(SimpleTestCase): self.assertTrue(form.fields['typograph_hyphenation'].initial) self.assertEqual(form.fields['typograph_sanitizer'].initial, 'None') + def test_tbcontent_model_has_no_btypograf_field(self): + self.assertNotIn('bTypograf', [field.name for field in TbContent._meta.fields]) + def test_tbcontent_str_uses_clean_text(self): item = TbContent(id=7, szContentHead='«Привет мир»') @@ -144,8 +146,8 @@ class TypographTests(TestCase): szContentHead='«Привет»', szContentIntro='

Абзац

', szContentBody='

Тело

', - bTypograf=True, ) + item._typograph_enabled = True with patch('web.models._build_typographer') as build_mock: build_mock.return_value.process.side_effect = lambda text: f'[{text}]' @@ -155,15 +157,14 @@ class TypographTests(TestCase): self.assertEqual(item.szContentHead, '[«Привет»]') self.assertEqual(item.szContentIntro, '[

Абзац

]') self.assertEqual(item.szContentBody, '[

Тело

]') - self.assertFalse(item.bTypograf) def test_save_uses_virtual_typograph_options(self): item = TbContent( szContentHead='Привет', szContentIntro='Текст', szContentBody='Тело', - bTypograf=True, ) + item._typograph_enabled = True item._typograph_mode = MODE_UNICODE item._typograph_hyphenation = False item._typograph_sanitizer = SANITIZE_ETPGRF @@ -198,8 +199,8 @@ class TypographTests(TestCase): szContentHead='При­вет\u00ad', szContentIntro='А­нонс', szContentBody='Те­ло\u00ad', - bTypograf=True, ) + item._typograph_enabled = True with patch('web.models._build_typographer') as build_mock: build_mock.return_value.process.side_effect = lambda text: f'[{text}]' @@ -209,7 +210,12 @@ class TypographTests(TestCase): self.assertEqual(item.szContentHead, '[Привет]') self.assertEqual(item.szContentIntro, '[Анонс]') self.assertEqual(item.szContentBody, '[Тело]') - self.assertFalse(item.bTypograf) + + def test_tbcontent_has_composite_indexes_for_navigation(self): + index_fields = [tuple(index.fields) for index in TbContent._meta.indexes] + + self.assertIn(('bContentPublish', 'tdContentPublishUp'), index_fields) + self.assertIn(('bContentPublish', 'tdContentPublishDown'), index_fields) def test_show_item_increments_hits_without_touching_timestamp(self): item = TbContent.objects.create(