minor: валидатор форм, парсера и моделей (26) в save модели TbOffer автоматически создаем хаотичные коды для скрытия id (чтобы злоумышленники не проворачивали махинации с заказами и покупками. Код товара выглядит хаотично и не поддается расшифровки без секретного ключа).

This commit is contained in:
2026-07-13 23:48:27 +03:00
parent 9bd181b207
commit 460adbb65b
5 changed files with 89 additions and 1 deletions
+53
View File
@@ -240,8 +240,10 @@
# - M2M использует числовые FK (INT) вместо строк
# - Slug'и как UNIQUE indexed fields (не primary_key) для экономии места
import base64
import datetime
import logging
from hashids import Hashids
from django.db import models
from django.db.models import F
from filer.fields.image import FilerImageField
@@ -257,6 +259,8 @@ from lpon_site.settings import (
KEY_IMAGE_NOTE, # Ключ, для заметок о картинке (например, "обложка", "задник", "вкладка" и т.д.)
KEY_OFFER_NOTE, # Ключ, для заметок о коммерческом предложении (например, "замят угол конверта" и т.д.)
KEY_OFFER_ALL_MEDIA, # Ключ, для хранения информации о том, какие носители входят в оффер (например, CD+2LP)
OFFER_HASHIDS_SALT, # Соль для криптографического кодирования ID оферов
OFFER_HASHIDS_MIN_LENGTH, # Минимальная длина кода
)
logger = logging.getLogger(__name__)
@@ -1341,6 +1345,55 @@ class TbOffer(models.Model):
i_offer_favorites=F('i_offer_favorites') + 1
)
def save(self, *args, **kwargs):
"""
Переопределенный метод save для автоматического формирования s_offer_skip32.
Уникальный код товара формируется в двух случаях:
1. Для новых оферов (при первом сохранении)
2. Для старых оферов БЕЗ s_offer_skip32 (миграция существующих данных)
Использует криптографическое хеширование (hashids):
- Невозможно восстановить исходный ID из кода
- Даже администратор, зная алгоритм, не может нарушить безопасность
- Коды выглядят случайно и не идут подряд
- Компактные (6-12 символов, настраивается в settings)
Примеры: ID 1 → "q1am12", ID 42 → "QBErd8", ID 1000 → "kd8GgB", ID 9999 → "1GYeRd"
ЛОГИКА:
- Если это новый оффер (self.pk == None):
1. super().save() → Django создает запись в БД и присваивает self.pk
2. Кодируем self.pk в хеш и сохраняем еще раз
- Если это старый оффер БЕЗ s_offer_skip32 (миграция):
1. Кодируем существующий self.pk в хеш
2. Сохраняем с обновленным s_offer_skip32
- Если это старый оффер С s_offer_skip32:
1. Сохраняем как обычно, не трогаем s_offer_skip32
"""
# Проверяем нужно ли генерировать s_offer_skip32:
# - ИЛИ это новый объект (self.pk == None)
# - ИЛИ это старый объект без s_offer_skip32 (миграция)
if not self.pk or not self.s_offer_skip32:
# Если это новый оффер, сначала сохраняем, чтобы получить ID
if not self.pk:
# Сохраняем БЕЗ s_offer_skip32 чтобы Django создал запись и присвоил pk
super().save(*args, **kwargs)
# После save() Django автоматически заполнит self.pk
# Кодируем pk в компактный, необратимый код
# Пример: pk=42 → "QBErd8"
self.s_offer_skip32 = Hashids(salt=OFFER_HASHIDS_SALT, min_length=OFFER_HASHIDS_MIN_LENGTH).encode(self.pk)
# Сохраняем только поле s_offer_skip32 (не перезаписываем остальное)
super().save(update_fields=['s_offer_skip32'])
else:
# Оффер существует И уже имеет s_offer_skip32: сохраняем как обычно
# Не трогаем s_offer_skip32, он был сформирован при создании
super().save(*args, **kwargs)
class Meta:
verbose_name = 'Оффер (предложение)'
verbose_name_plural = 'Офферы (предложения)'
+7
View File
@@ -48,6 +48,13 @@ ALLOWED_HOSTS = env.list(
CSRF_TRUSTED_ORIGINS = env.list('DJANGO_CSRF_TRUSTED_ORIGINS', default=['127.0.0.1', 'localhost', 'testserver'])
# ====== HASHIDS настройка для s_offer_skip32 ======
# Криптографическое кодирование ID оферов в компактные, необратимые коды.
# Благодаря hashids невозможно восстановить исходный ID из кода,
# даже зная алгоритм (в отличие от простого XOR).
OFFER_HASHIDS_SALT = env('OFFER_HASHIDS_SALT', default='your-secret-salt-change-me')
OFFER_HASHIDS_MIN_LENGTH = env.int('OFFER_HASHIDS_MIN_LENGTH', default=6)
#########################################
# Настройки сообщений об ошибках когда все упало и т.п.
ADMINS = tuple(