mod: модели и админка (2) драфт
This commit is contained in:
@@ -199,6 +199,13 @@ STORAGES = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Специфичные для проекта настройки
|
||||||
|
LVL_CANDIDATE = 10
|
||||||
|
LVL_PRE_MODERATED = 20
|
||||||
|
LVL_MODERATED = 30
|
||||||
|
LVL_LOCK_FOR_DELETION = 40
|
||||||
|
|
||||||
|
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
# В деве оставляем стандартную отдачу статики Django без WhiteNoise.
|
# В деве оставляем стандартную отдачу статики Django без WhiteNoise.
|
||||||
STORAGES['staticfiles'] = {
|
STORAGES['staticfiles'] = {
|
||||||
|
|||||||
+74
-74
@@ -1,74 +1,74 @@
|
|||||||
from django.contrib import admin
|
# from django.contrib import admin
|
||||||
|
#
|
||||||
from .models import BlogPost, HalftoneItem, HalftoneVote
|
# from .models import BlogPost, HalftoneItem, HalftoneVote
|
||||||
|
#
|
||||||
|
#
|
||||||
@admin.register(HalftoneItem)
|
# @admin.register(HalftoneItem)
|
||||||
class HalftoneItemAdmin(admin.ModelAdmin):
|
# class HalftoneItemAdmin(admin.ModelAdmin):
|
||||||
list_display = (
|
# list_display = (
|
||||||
"hash_id",
|
# "hash_id",
|
||||||
"title",
|
# "title",
|
||||||
"likes_count",
|
# "likes_count",
|
||||||
"views_count",
|
# "views_count",
|
||||||
"score",
|
# "score",
|
||||||
"is_curated",
|
# "is_curated",
|
||||||
"is_public",
|
# "is_public",
|
||||||
"created_at",
|
# "created_at",
|
||||||
)
|
# )
|
||||||
list_filter = (
|
# list_filter = (
|
||||||
"is_curated",
|
# "is_curated",
|
||||||
"is_public",
|
# "is_public",
|
||||||
"created_at",
|
# "created_at",
|
||||||
)
|
# )
|
||||||
search_fields = (
|
# search_fields = (
|
||||||
"hash_id",
|
# "hash_id",
|
||||||
"title",
|
# "title",
|
||||||
"promo_title",
|
# "promo_title",
|
||||||
)
|
# )
|
||||||
readonly_fields = (
|
# readonly_fields = (
|
||||||
"created_at",
|
# "created_at",
|
||||||
"score",
|
# "score",
|
||||||
"file_size",
|
# "file_size",
|
||||||
)
|
# )
|
||||||
|
#
|
||||||
|
#
|
||||||
@admin.register(HalftoneVote)
|
# @admin.register(HalftoneVote)
|
||||||
class HalftoneVoteAdmin(admin.ModelAdmin):
|
# class HalftoneVoteAdmin(admin.ModelAdmin):
|
||||||
list_display = (
|
# list_display = (
|
||||||
"item",
|
# "item",
|
||||||
"fingerprint",
|
# "fingerprint",
|
||||||
"created_at",
|
# "created_at",
|
||||||
)
|
# )
|
||||||
list_filter = ("created_at",)
|
# list_filter = ("created_at",)
|
||||||
search_fields = (
|
# search_fields = (
|
||||||
"item__hash_id",
|
# "item__hash_id",
|
||||||
"item__title",
|
# "item__title",
|
||||||
"fingerprint",
|
# "fingerprint",
|
||||||
)
|
# )
|
||||||
readonly_fields = ("created_at",)
|
# readonly_fields = ("created_at",)
|
||||||
|
#
|
||||||
|
#
|
||||||
@admin.register(BlogPost)
|
# @admin.register(BlogPost)
|
||||||
class BlogPostAdmin(admin.ModelAdmin):
|
# class BlogPostAdmin(admin.ModelAdmin):
|
||||||
list_display = (
|
# list_display = (
|
||||||
"title",
|
# "title",
|
||||||
"slug",
|
# "slug",
|
||||||
"is_published",
|
# "is_published",
|
||||||
"published_at",
|
# "published_at",
|
||||||
"created_at",
|
# "created_at",
|
||||||
)
|
# )
|
||||||
list_filter = (
|
# list_filter = (
|
||||||
"is_published",
|
# "is_published",
|
||||||
"published_at",
|
# "published_at",
|
||||||
)
|
# )
|
||||||
search_fields = (
|
# search_fields = (
|
||||||
"title",
|
# "title",
|
||||||
"slug",
|
# "slug",
|
||||||
"summary",
|
# "summary",
|
||||||
"content",
|
# "content",
|
||||||
)
|
# )
|
||||||
prepopulated_fields = {"slug": ("title",)}
|
# prepopulated_fields = {"slug": ("title",)}
|
||||||
readonly_fields = (
|
# readonly_fields = (
|
||||||
"created_at",
|
# "created_at",
|
||||||
"updated_at",
|
# "updated_at",
|
||||||
)
|
# )
|
||||||
|
|||||||
+61
-44
@@ -1,61 +1,73 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
|
from hypn0.settings import *
|
||||||
|
|
||||||
|
class Item(models.Model):
|
||||||
class HalftoneItem(models.Model):
|
|
||||||
"""Единая модель для генераций, публичных шеров и витрины галереи."""
|
"""Единая модель для генераций, публичных шеров и витрины галереи."""
|
||||||
|
class Level(models.IntegerChoices):
|
||||||
|
CANDIDATE = LVL_CANDIDATE, 'Шум сознания' # Свежая генерация. Запрет на удаление до Х дней.
|
||||||
|
LEVEL_1 = LVL_PRE_MODERATED, 'Плеск бессознательного' # Есть "лайки", не проверено, можно удалять при низком score
|
||||||
|
LEVEL_2 = LVL_MODERATED, 'Одобрено Мозговым Слизнем' # Есть "лайки", проверено, можно удалять при низком score
|
||||||
|
IMMORTAL = LVL_LOCK_FOR_DELETION, 'Глубокий транс' # Нельзя удалять, даже при низком score
|
||||||
|
|
||||||
hash_id = models.CharField(
|
s_hash_id = models.CharField(
|
||||||
max_length=16,
|
max_length=16,
|
||||||
unique=True,
|
unique=True,
|
||||||
db_index=True,
|
db_index=True,
|
||||||
verbose_name="Хэш-идентификатор",
|
verbose_name="Хэш-идентификатор",
|
||||||
)
|
)
|
||||||
title = models.CharField(
|
s_title = models.CharField(
|
||||||
max_length=150,
|
max_length=255,
|
||||||
verbose_name="Название картины",
|
verbose_name="Название картины",
|
||||||
)
|
)
|
||||||
svg_file = models.FileField(
|
file_svg = models.FileField(
|
||||||
upload_to="svg/%Y/%m/",
|
upload_to="svg/%Y/%m/",
|
||||||
verbose_name="SVG-файл",
|
verbose_name="SVG-файл",
|
||||||
)
|
)
|
||||||
|
i_sha1 = models.CharField(
|
||||||
# Снимок параметров для возможного клонирования настроек
|
max_length=40,
|
||||||
params_json = models.JSONField(
|
verbose_name="SHA1",
|
||||||
default=dict,
|
)
|
||||||
blank=True,
|
i_file_size = models.PositiveIntegerField(
|
||||||
verbose_name="Параметры генерации",
|
|
||||||
)
|
|
||||||
file_size = models.PositiveIntegerField(
|
|
||||||
default=0,
|
default=0,
|
||||||
help_text="Размер файла в байтах",
|
help_text="Размер файла в байтах",
|
||||||
verbose_name="Размер файла",
|
verbose_name="Размер файла",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Популярность и модерация
|
# Снимок параметров для возможного клонирования настроек
|
||||||
likes_count = models.PositiveIntegerField(
|
j_params = models.JSONField(
|
||||||
|
default=dict,
|
||||||
|
blank=True,
|
||||||
|
verbose_name="Параметры генерации",
|
||||||
|
help_text="Метаданные и параметры для возможного клонирования настроек",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Популярность
|
||||||
|
i_likes_count = models.PositiveIntegerField(
|
||||||
default=0,
|
default=0,
|
||||||
db_index=True,
|
db_index=True,
|
||||||
verbose_name="Количество лайков",
|
verbose_name="Количество лайков",
|
||||||
)
|
)
|
||||||
views_count = models.PositiveIntegerField(
|
i_views_count = models.PositiveIntegerField(
|
||||||
default=0,
|
default=0,
|
||||||
verbose_name="Количество просмотров",
|
verbose_name="Количество просмотров",
|
||||||
)
|
)
|
||||||
reports_count = models.PositiveIntegerField(
|
i_reports_count = models.PositiveIntegerField(
|
||||||
default=0,
|
default=0,
|
||||||
verbose_name="Количество жалоб",
|
verbose_name="Количество жалоб",
|
||||||
)
|
)
|
||||||
score = models.FloatField(
|
f_score = models.FloatField(
|
||||||
default=0.0,
|
default=0.0,
|
||||||
db_index=True,
|
db_index=True,
|
||||||
help_text="Рейтинг для Smart Retention",
|
help_text="Рейтинг для Smart Retention",
|
||||||
verbose_name="Рейтинг популярности",
|
verbose_name="Рейтинг популярности",
|
||||||
)
|
)
|
||||||
|
|
||||||
is_curated = models.BooleanField(
|
# Модерация
|
||||||
default=False,
|
i_level = models.IntegerField(
|
||||||
help_text="Выбор модератора / галерея (иммунитет к удалению)",
|
choices=Level.choices,
|
||||||
verbose_name="Выбор редакции",
|
default=Level.CANDIDATE,
|
||||||
|
help_text="Уровень доступа",
|
||||||
|
verbose_name="Уровень доступа",
|
||||||
)
|
)
|
||||||
is_public = models.BooleanField(
|
is_public = models.BooleanField(
|
||||||
default=True,
|
default=True,
|
||||||
@@ -64,32 +76,37 @@ class HalftoneItem(models.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Промо-блок (на перспективу)
|
# Промо-блок (на перспективу)
|
||||||
promo_url = models.URLField(
|
s_promo_url = models.URLField(
|
||||||
blank=True,
|
blank=True,
|
||||||
default="",
|
default="",
|
||||||
verbose_name="Промо-ссылка",
|
verbose_name="Промо-ссылка",
|
||||||
)
|
)
|
||||||
promo_title = models.CharField(
|
s_promo_title = models.CharField(
|
||||||
max_length=100,
|
max_length=100,
|
||||||
blank=True,
|
blank=True,
|
||||||
default="",
|
default="",
|
||||||
verbose_name="Бейдж/автор промо",
|
verbose_name="Бейдж/автор промо",
|
||||||
)
|
)
|
||||||
promo_clicks = models.PositiveIntegerField(
|
i_promo_clicks = models.PositiveIntegerField(
|
||||||
default=0,
|
default=0,
|
||||||
verbose_name="Клики по промо",
|
verbose_name="Клики по промо",
|
||||||
)
|
)
|
||||||
|
|
||||||
created_at = models.DateTimeField(
|
d_created_at = models.DateTimeField(
|
||||||
auto_now_add=True,
|
auto_now_add=True,
|
||||||
db_index=True,
|
db_index=True,
|
||||||
verbose_name="Дата создания",
|
verbose_name="Дата создания",
|
||||||
)
|
)
|
||||||
|
d_updated_at = models.DateTimeField(
|
||||||
|
auto_now=True,
|
||||||
|
db_index=True,
|
||||||
|
verbose_name="Дата обновления",
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Гипно-картина"
|
verbose_name = "Гипно-картина"
|
||||||
verbose_name_plural = "Гипно-картины"
|
verbose_name_plural = "Гипно-картины"
|
||||||
ordering = ["-created_at"]
|
ordering = ["-d_created_at"]
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"{self.title} ({self.hash_id})"
|
return f"{self.title} ({self.hash_id})"
|
||||||
@@ -98,19 +115,19 @@ class HalftoneItem(models.Model):
|
|||||||
class HalftoneVote(models.Model):
|
class HalftoneVote(models.Model):
|
||||||
"""Анонимный учет голосов (лайков) без сохранения ПДн."""
|
"""Анонимный учет голосов (лайков) без сохранения ПДн."""
|
||||||
|
|
||||||
item = models.ForeignKey(
|
k_item = models.ForeignKey(
|
||||||
HalftoneItem,
|
Item,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
related_name="votes",
|
related_name="votes",
|
||||||
verbose_name="Картина",
|
verbose_name="Картина",
|
||||||
)
|
)
|
||||||
fingerprint = models.CharField(
|
s_fingerprint = models.CharField(
|
||||||
max_length=64,
|
max_length=64,
|
||||||
db_index=True,
|
db_index=True,
|
||||||
help_text="SHA256(visitor_uuid + SECRET_KEY) для защиты от накрутки и разделения устройств",
|
help_text="SHA256(visitor_uuid + SECRET_KEY) для защиты от накрутки и разделения устройств",
|
||||||
verbose_name="Хэш устройства/отпечаток",
|
verbose_name="Хэш устройства/отпечаток",
|
||||||
)
|
)
|
||||||
created_at = models.DateTimeField(
|
d_created_at = models.DateTimeField(
|
||||||
auto_now_add=True,
|
auto_now_add=True,
|
||||||
verbose_name="Дата голосования",
|
verbose_name="Дата голосования",
|
||||||
)
|
)
|
||||||
@@ -120,11 +137,11 @@ class HalftoneVote(models.Model):
|
|||||||
verbose_name_plural = "Голоса / Лайки"
|
verbose_name_plural = "Голоса / Лайки"
|
||||||
constraints = [
|
constraints = [
|
||||||
models.UniqueConstraint(
|
models.UniqueConstraint(
|
||||||
fields=["item", "fingerprint"],
|
fields=["k_item", "s_fingerprint"],
|
||||||
name="unique_item_fingerprint_vote",
|
name="unique_item_fingerprint_vote",
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
ordering = ["-created_at"]
|
ordering = ["-d_created_at"]
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"Голос за {self.item.hash_id} [{self.fingerprint[:8]}...]"
|
return f"Голос за {self.item.hash_id} [{self.fingerprint[:8]}...]"
|
||||||
@@ -137,7 +154,7 @@ class BlogPost(models.Model):
|
|||||||
+ виртуальные поля для управления типографом etpgrf
|
+ виртуальные поля для управления типографом etpgrf
|
||||||
"""
|
"""
|
||||||
|
|
||||||
title = models.CharField(
|
s_title = models.CharField(
|
||||||
max_length=200,
|
max_length=200,
|
||||||
help_text="HTML-заголовок, очищенный и типографированный etpgrf",
|
help_text="HTML-заголовок, очищенный и типографированный etpgrf",
|
||||||
verbose_name="Заголовок",
|
verbose_name="Заголовок",
|
||||||
@@ -147,17 +164,17 @@ class BlogPost(models.Model):
|
|||||||
unique=True,
|
unique=True,
|
||||||
verbose_name="URL-слаг",
|
verbose_name="URL-слаг",
|
||||||
)
|
)
|
||||||
summary = models.TextField(
|
s_summary = models.TextField(
|
||||||
max_length=500,
|
max_length=1024,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="HTML-контент тизера, очищенный и типографированный etpgrf",
|
help_text="HTML-контент тизера, очищенный и типографированный etpgrf",
|
||||||
verbose_name="Краткое описание / Лид",
|
verbose_name="Краткое описание / Лид",
|
||||||
)
|
)
|
||||||
content = models.TextField(
|
s_content = models.TextField(
|
||||||
help_text="HTML-контент статьи, очищенный и типографированный etpgrf",
|
help_text="HTML-контент статьи, очищенный и типографированный etpgrf",
|
||||||
verbose_name="Основной HTML-контент",
|
verbose_name="Основной HTML-контент",
|
||||||
)
|
)
|
||||||
cover_image = models.ImageField(
|
f_cover_img = models.ImageField(
|
||||||
upload_to="blog/covers/%Y/",
|
upload_to="blog/covers/%Y/",
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
@@ -168,17 +185,17 @@ class BlogPost(models.Model):
|
|||||||
default=False,
|
default=False,
|
||||||
verbose_name="Опубликовано",
|
verbose_name="Опубликовано",
|
||||||
)
|
)
|
||||||
published_at = models.DateTimeField(
|
d_published_at = models.DateTimeField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
db_index=True,
|
db_index=True,
|
||||||
verbose_name="Дата публикации",
|
verbose_name="Дата публикации",
|
||||||
)
|
)
|
||||||
created_at = models.DateTimeField(
|
d_created_at = models.DateTimeField(
|
||||||
auto_now_add=True,
|
auto_now_add=True,
|
||||||
verbose_name="Дата создания",
|
verbose_name="Дата создания",
|
||||||
)
|
)
|
||||||
updated_at = models.DateTimeField(
|
d_created_at = models.DateTimeField(
|
||||||
auto_now=True,
|
auto_now=True,
|
||||||
verbose_name="Дата обновления",
|
verbose_name="Дата обновления",
|
||||||
)
|
)
|
||||||
@@ -186,7 +203,7 @@ class BlogPost(models.Model):
|
|||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Статья блога / Страница"
|
verbose_name = "Статья блога / Страница"
|
||||||
verbose_name_plural = "Статьи блога / Страницы"
|
verbose_name_plural = "Статьи блога / Страницы"
|
||||||
ordering = ["-published_at", "-created_at"]
|
ordering = ["-d_published_at", "-d_created_at"]
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return self.title
|
return self.s_title
|
||||||
|
|||||||
Reference in New Issue
Block a user