From eff510eb4bf5f1ec2dff3c173167593a59915afc Mon Sep 17 00:00:00 2001 From: erjemin Date: Thu, 20 Aug 2026 16:35:42 +0300 Subject: [PATCH] =?UTF-8?q?mod:=20=D0=BC=D0=BE=D0=B4=D0=B5=D0=BB=D0=B8=20?= =?UTF-8?q?=D0=B8=20=D0=B0=D0=B4=D0=BC=D0=B8=D0=BD=D0=BA=D0=B0=20(6)=20?= =?UTF-8?q?=D0=B4=D1=80=D0=B0=D1=84=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hypn0/hypn0/settings.py | 4 ++++ hypn0/hypn0_site/models.py | 38 ++++++++++++++++++++++++++++++-------- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/hypn0/hypn0/settings.py b/hypn0/hypn0/settings.py index a126cdf..0104cd5 100644 --- a/hypn0/hypn0/settings.py +++ b/hypn0/hypn0/settings.py @@ -206,6 +206,10 @@ LVL_PRE_MODERATED = 10 LVL_MODERATED = 30 LVL_LOCK_FOR_DELETION = 1000 +# ====== Направление и веса для голосования (ТОЛЛЬКО ЦЕЛЫЕ ЧИСЛА) ====== +VOTE_LIKE = 1 +VOTE_CLAIM = -2 + # ====== Гамма для вычисления рейтинга ====== GAMMA = 1.5 diff --git a/hypn0/hypn0_site/models.py b/hypn0/hypn0_site/models.py index ea3f774..a1e5fc4 100644 --- a/hypn0/hypn0_site/models.py +++ b/hypn0/hypn0_site/models.py @@ -21,6 +21,7 @@ class TbHypn0Item(models.Model): • f_score: оценка для стратегии хранения и умной очистки (Smart Retention) • i_level: уровень хранения (свежее, модерированное, защищенное от удаления) • is_public: публичный (доступный по внешней ссылке) + • s_author_fingerprint: хеш-код браузера автора • s_promo_url: промо-ссылка (с дополнительной об авторе-спонсоре) • s_promo_title: заголовок промо-ссылки или бейджа • i_promo_clicks: количество кликов по промо-ссылке @@ -105,6 +106,14 @@ class TbHypn0Item(models.Model): verbose_name="Публичный доступ", ) + # Автор + s_author_fingerprint = models.CharField( + max_length=64, + db_index=True, # ??? -- подумать нужно или нет + help_text="SHA256(visitor_uuid + SECRET_KEY) для защиты от накрутки и разделения устройств", + verbose_name="Хэш устройства/отпечаток", + ) + # Промо-блок (на перспективу) s_promo_url = models.URLField( blank=True, @@ -143,19 +152,21 @@ class TbHypn0Item(models.Model): def increment_views(self): """Безопасный инкремент просмотров""" - TbHypn0Item.objects.filter(id=self.id).update(i_views_count=F('i_views_count') + 1) + TbHypn0Item.objects.get(id=self.id).update(i_views_count=F('i_views_count') + 1) def increment_likes(self): """Безопасный инкремент лайков""" - TbHypn0Item.objects.filter(id=self.id).update(i_likes_count=F('i_likes_count') + 1) + TbHypn0Item.objects.get(id=self.id).update(i_likes_count=F('i_likes_count') + 1) + # Здесь будет код для записи в TbVote def increment_claims(self): """Безопасный инкремент жалоб""" - TbHypn0Item.objects.filter(id=self.id).update(i_claims_count=F('i_claims_count') + 1) + TbHypn0Item.objects.get(id=self.id).update(i_claims_count=F('i_claims_count') + 1) + # Здесь будет код для записи в TbVote def increment_promo_clicks(self): """Безопасный инкремент кликов по промо""" - TbHypn0Item.objects.filter(id=self.id).update(i_promo_clicks=F('i_promo_clicks') + 1) + TbHypn0Item.objects.get(id=self.id).update(i_promo_clicks=F('i_promo_clicks') + 1) def rescore(self): """Пересчет рейтинга i_promo_clicks @@ -163,9 +174,9 @@ class TbHypn0Item(models.Model): («гравитации»), а не слепой Х-дневный таймер: $$\text{Score} = \frac{\text{Likes} + \text{Bonus}_{\text{moderator}}}{(\text{Age in hours} + 2)^\gamma}$$ """ - age_ih_hours = (timezone.now - self.d_created_at).total_seconds() / 3600 + 1 + age_ih_hours = (timezone.now() - self.d_created_at).total_seconds() / 3600 + 1 new_score = (self.i_likes_count + self.i_level) / ((age_ih_hours + 2) ** GAMMA) - TbHypn0Item.objects.filter(id=self.id).update(i_promo_clicks=new_score) + TbHypn0Item.objects.get(id=self.id).update(f_score=new_score) def save(self, *args, **kwargs): """ @@ -205,19 +216,30 @@ class TbVote(models.Model): Поля: • k_item: ключ объекта (foreign key), за который голосуют + • i_direction: направление голоса • s_fingerprint: Хэш-отпечаток клиент/браузер/secret для защиты он накрутки • d_created_at: дата создания """ + class Direction(models.IntegerChoices): + LIKE = VOTE_LIKE, 'Like' + CLIME = VOTE_CLAIM, 'Clime' + k_item = models.ForeignKey( TbHypn0Item, on_delete=models.CASCADE, related_name="votes", verbose_name="Картина", ) + i_direction = models.IntegerField( + choices=Direction.choices, + default=Direction.LIKE, + verbose_name="Направление", + help_text="Направление голоса", + ) s_fingerprint = models.CharField( max_length=64, - db_index=True, + db_index=True, # ??? -- подумать нужно или нет help_text="SHA256(visitor_uuid + SECRET_KEY) для защиты от накрутки и разделения устройств", verbose_name="Хэш устройства/отпечаток", ) @@ -238,7 +260,7 @@ class TbVote(models.Model): ordering = ["-d_created_at"] def __str__(self) -> str: - return f"Голос за {self.k_item.hash_id} [{self.s_fingerprint[:8]}...]" + return f"Голос за {self.k_item.s_hash_id} [{self.s_fingerprint[:8]}...]" class TbBlogPost(models.Model):