From a2986cf7197a38f5219dc0d074d4d243f77555c6 Mon Sep 17 00:00:00 2001 From: erjemin Date: Wed, 15 Jul 2026 16:40:44 +0300 Subject: [PATCH] =?UTF-8?q?mod:=20minor:=20=D0=B2=D0=B0=D0=BB=D0=B8=D0=B4?= =?UTF-8?q?=D0=B0=D1=82=D0=BE=D1=80=20=D1=84=D0=BE=D1=80=D0=BC,=20=D0=BF?= =?UTF-8?q?=D0=B0=D1=80=D1=81=D0=B5=D1=80=D0=B0=20=D0=B8=20=D0=BC=D0=BE?= =?UTF-8?q?=D0=B4=D0=B5=D0=BB=D0=B5=D0=B9=20(27)=20=D1=84=D0=BE=D1=80?= =?UTF-8?q?=D0=BC=D1=8B=20OfferHistoryAdminForm=20=D0=B8=20=D0=B0=D0=B4?= =?UTF-8?q?=D0=BC=D0=B8=D0=BD=D0=BA=D0=B0=20OfferHistoryAdmin=20+=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=BB=D0=B5=20t=5Fhistory=5Fcreated=20=D0=B2=20=D0=BC?= =?UTF-8?q?=D0=BE=D0=B4=D0=B5=D0=BB=D0=B8=20TbOfferHistory=20=D1=82=D0=B5?= =?UTF-8?q?=D0=BF=D0=B5=D1=80=D1=8C=20=D1=80=D0=B5=D0=B0=D0=B4=D0=BA=D1=82?= =?UTF-8?q?=D0=B8=D1=80=D1=83=D0=B5=D0=BC=D0=BE=D0=B5=20=D0=BD=D0=BE=20?= =?UTF-8?q?=D1=81=20=D0=B0=D0=B2=D1=82=D0=BE-now=20=D1=87=D0=B5=D1=80?= =?UTF-8?q?=D0=B5=D0=B7=20default?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lpon_site/frontend/admin.py | 81 +++++++++++++++++++++++------------- lpon_site/frontend/models.py | 11 +++-- 2 files changed, 58 insertions(+), 34 deletions(-) diff --git a/lpon_site/frontend/admin.py b/lpon_site/frontend/admin.py index 13612c7..f5bd565 100644 --- a/lpon_site/frontend/admin.py +++ b/lpon_site/frontend/admin.py @@ -1209,23 +1209,10 @@ class OfferAdminForm(CodeMirrorFormMixin): class Meta: model = TbOffer fields = ( - 's_offer', - 'l_offer_to_format', - 'k_offer_to_article', - 'k_offer_to_item', - 'k_offer_to_label', - 'k_offer_to_source', - 's_offer_catalog_num', - 'b_offer_is_preorder', - 'd_offer_date_release', - 'i_offer_discogs_id', - 'l_offer_condition_media', - 'l_offer_condition_sleeve', - 'f_offer_price', - 'i_offer_quantity', - 'i_offer_discount_to_daily_sale', - 'j_offer_metadata', - 'i_offer_views', + 's_offer', 'l_offer_to_format', 'k_offer_to_article', 'k_offer_to_item', 'k_offer_to_label', + 'k_offer_to_source', 's_offer_catalog_num', 'b_offer_is_preorder', 'd_offer_date_release', + 'i_offer_discogs_id', 'l_offer_condition_media', 'l_offer_condition_sleeve', 'f_offer_price', + 'i_offer_quantity', 'i_offer_discount_to_daily_sale', 'j_offer_metadata', 'i_offer_views', 'i_offer_favorites', ) @@ -1306,6 +1293,8 @@ class OfferAdmin(RequestInFormMixin, admin.ModelAdmin): fieldsets = ( ('Код товара в базе', { 'fields': ('s_offer_skip32',), + 'description': 'Уникальный код товара в базе. Используется вместо ID для генерации ссылок на оффер' + ' и складского учёта с помощью QR-кодов. Не редактируется вручную.', }), ('Основная информация', { 'fields': ('k_offer_to_item', 's_offer', 'f_offer_price', 'i_offer_quantity'), @@ -1316,21 +1305,16 @@ class OfferAdmin(RequestInFormMixin, admin.ModelAdmin): }), ('Дополнительные данные', { 'fields': ( - 'l_offer_to_format', - 'l_offer_condition_media', - 'l_offer_condition_sleeve', - 'k_offer_to_article', - 'k_offer_to_label', - 'k_offer_to_source', - 's_offer_catalog_num', - 'i_offer_discogs_id', - + 'l_offer_to_format', 'l_offer_condition_media', 'l_offer_condition_sleeve', 'k_offer_to_article', + 'k_offer_to_label', 'k_offer_to_source', 's_offer_catalog_num', 'i_offer_discogs_id', 'i_offer_discount_to_daily_sale', ), }), ('Метаданные коммерческого предложения', { 'fields': ('j_offer_metadata',), 'classes': ('collapse',), + 'description': 'Метаданные коммерческого предложения в формате JSON. ' + 'Используется для хранения дополнительных данных, которые не входят в основные поля модели.', }), ('Просмотры и избранное', { 'fields': ('i_offer_views', 'i_offer_favorites',), @@ -1347,14 +1331,51 @@ class OfferAdmin(RequestInFormMixin, admin.ModelAdmin): # ============================================================================ -# Остальные ModelAdmin классы -# Их еще предстоит обжужукать (натянуть CodeMirror, обвесить валидаторами, переопределить save_model и т.д.) -# ============================================================================ +# АДМИНКА ИСТОРИИ ИЗМЕНЕНИЙ ОФФЕРОВ (история цен и остатков коммерческих предложений) +# +# Кастомная форма +class OfferHistoryAdminForm(CodeMirrorFormMixin): + """ + Кастомная форма для админки коммерческих предложений (TbOffer). + Добавляет виджеты CodeMirror для текстовых полей + """ + class Meta: + model = TbOfferHistory + fields = ('k_history_to_offer','f_history_price', 'i_history_quantity', 'j_history_metadata', + 't_history_created') + + def __init__(self, *args, **kwargs): + """ + При инициализации формы подгружаем CodeMirror редактор. + """ + super().__init__(*args, **kwargs) + + # Конфигурируем поля для CodeMirror + self.setup_codemirror_field('f_history_price', language='text', + css_class='codemirror-width-s codemirror-no-lines') + self.setup_codemirror_field('i_history_quantity', language='text', + css_class='codemirror-width-s codemirror-no-lines') + self.setup_codemirror_field('j_history_metadata', language='json', + css_class='codemirror-width-l codemirror-min-height-5') + class OfferHistoryAdmin(admin.ModelAdmin): """Админ для истории изменений офферов""" + form = OfferHistoryAdminForm # Используем кастомную форму с CodeMirror list_display = ('id', 'k_history_to_offer', 'f_history_price', 'i_history_quantity', 't_history_created') + list_display_links = ('id', 'k_history_to_offer',) list_filter = ('t_history_created',) - readonly_fields = ('t_history_created',) + fieldsets = ( + ('Основная информация', { + 'fields': ('k_history_to_offer', 'f_history_price', 'i_history_quantity', ), + }), + ('Метаданные истории изменений', { + 'fields': ('j_history_metadata',), + }), + ('Служебная информация (но редактируемое, для загрузки исторических данных)', { + 'fields': ('t_history_created',), + 'classes': ('collapse',), + }), + ) # ============================================================================ diff --git a/lpon_site/frontend/models.py b/lpon_site/frontend/models.py index 2dd57ce..85ecb62 100644 --- a/lpon_site/frontend/models.py +++ b/lpon_site/frontend/models.py @@ -246,6 +246,7 @@ import logging from hashids import Hashids from django.db import models from django.db.models import F +from django.utils import timezone from filer.fields.image import FilerImageField from filer.fields.file import FilerFileField from frontend.utils import make_slug, update_synonyms_in_metadata, create_or_get_related_article @@ -1549,10 +1550,12 @@ class TbOfferHistory(models.Model): ' или URL + CSS-селектор для HTML-страницы и т.п.', ) t_history_created = models.DateTimeField( - auto_now_add=True, - db_index=True, - editable=False, - verbose_name="Дата создания",) + default=timezone.now, + db_index=True, + verbose_name="Дата создания", + help_text="Дата создания записи истории. Автоматически устанавливается на текущее время при создании, " + "но может быть отредактирована для загрузки исторических данных из Excel-файлов.", + ) # Нам не нужен `t_history_updated` потому что это "снимок состояния" и его не нужно менять # после создания. И если вдруг понадобится, то правильнее будет добавить новую запись.