From c304ba4ec7f2a8b1479a0ebea65616bb2905ef0d Mon Sep 17 00:00:00 2001 From: Artem Tsyrulnikov Date: Sun, 18 Jan 2026 13:18:00 +0300 Subject: [PATCH] =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B8=20=D0=B8?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/domain/__init__.py | 10 ++-- src/domain/placement.py | 71 ++++++++++++---------- src/domain/project.py | 2 +- src/domain/publication.py | 58 ++++++++++++++++++ src/domain/purchase.py | 97 ------------------------------- src/domain/subscription.py | 2 +- src/domain/workspace.py | 2 +- src/dto/purchase.py | 2 +- src/usecase/placement/__init__.py | 5 -- src/usecase/purchase/__init__.py | 5 -- 10 files changed, 108 insertions(+), 146 deletions(-) create mode 100644 src/domain/publication.py delete mode 100644 src/domain/purchase.py delete mode 100644 src/usecase/placement/__init__.py delete mode 100644 src/usecase/purchase/__init__.py diff --git a/src/domain/__init__.py b/src/domain/__init__.py index 613c057..1b9c101 100644 --- a/src/domain/__init__.py +++ b/src/domain/__init__.py @@ -97,17 +97,17 @@ from .error import ( WorkspaceNotFound, ) from .login_token import LoginToken -from .placement import PostViewsAvailability, Publication, PublicationStatus -from .post import Post -from .post_views_history import PostViewsHistory -from .project import Project, ProjectStatus -from .purchase import ( +from .placement import ( CostType, InviteLinkType, Placement, PlacementStatus, PlacementType, ) +from .post import Post +from .post_views_history import PostViewsHistory +from .project import Project, ProjectStatus +from .publication import PostViewsAvailability, Publication, PublicationStatus from .subscription import Subscription, SubscriptionStatus from .telegram_user import TelegramUser from .user import User diff --git a/src/domain/placement.py b/src/domain/placement.py index 9ed0969..7f98394 100644 --- a/src/domain/placement.py +++ b/src/domain/placement.py @@ -1,60 +1,71 @@ import enum +import uuid from typing import TYPE_CHECKING -from uuid import UUID from tortoise import fields from .base import TimestampedModel if TYPE_CHECKING: + from .channel import Channel from .creative import Creative - from .post import Post from .project import Project - from .purchase import Placement -__all__ = ['Publication', 'PublicationStatus', 'PostViewsAvailability'] +__all__ = ['Placement', 'PlacementStatus', 'PlacementType', 'InviteLinkType', 'CostType'] -class PublicationStatus(str, enum.Enum): - ACTIVE = 'active' # Пост найден и отслеживается - ARCHIVED = 'archived' # Вручную архивировано +class CostType(enum.StrEnum): + FIXED = 'fixed' + CPM = 'cpm' -class PostViewsAvailability(str, enum.Enum): - UNKNOWN = 'unknown' # Ещё не проверялось - AVAILABLE = 'available' # Просмотры доступны - UNAVAILABLE = 'unavailable' # Нет доступа / битая ссылка / приватный канал - MANUAL = 'manual' # Просмотры вводятся вручную +class PlacementStatus(enum.StrEnum): + PLANNED = 'planned' + APPROVED = 'approved' + REJECTED = 'rejected' + IN_PROGRESS = 'in_progress' + COMPLETED = 'completed' -class Publication(TimestampedModel): - """Публикация, мониторимая системой (бывший Placement)""" +class PlacementType(enum.StrEnum): + SELF_PROMO = 'self_promo' + STANDARD = 'standard' - wanted_placement_date = fields.DatetimeField(db_column='placement_date') - cost = fields.FloatField(null=True) + +class InviteLinkType(enum.StrEnum): + PUBLIC = 'public' # открытая ссылка + APPROVAL = 'approval' # с одобрением ботом + + +class Placement(TimestampedModel): + status = fields.CharEnumField(PlacementStatus, default=PlacementStatus.PLANNED) + placement_at = fields.DatetimeField(null=True) + payment_at = fields.DatetimeField(null=True) + cost_type = fields.CharEnumField(CostType, null=True, max_length=8) + cost_value = fields.FloatField(null=True) + cost_before_bargain = fields.FloatField(null=True) + placement_type = fields.CharEnumField(PlacementType, null=True, max_length=16) + format = fields.TextField(null=True) comment = fields.TextField(null=True) - status = fields.CharEnumField(PublicationStatus, default=PublicationStatus.ACTIVE) + invite_link = fields.CharField(max_length=512) + invite_link_type = fields.CharEnumField(InviteLinkType) project: fields.ForeignKeyRelation['Project'] = fields.ForeignKeyField( - 'models.Project', related_name='publications', on_delete=fields.CASCADE, index=True + 'models.Project', related_name='placements', on_delete=fields.CASCADE, index=True ) creative: fields.ForeignKeyRelation['Creative'] = fields.ForeignKeyField( - 'models.Creative', related_name='publications', on_delete=fields.CASCADE, index=True + 'models.Creative', related_name='placements', on_delete=fields.CASCADE, index=True ) - placement: fields.ForeignKeyRelation['Placement'] = fields.ForeignKeyField( - 'models.Placement', related_name='publications', on_delete=fields.CASCADE, index=True - ) - - post: fields.ForeignKeyRelation['Post'] | None = fields.ForeignKeyField( - 'models.Post', related_name='publications', on_delete=fields.SET_NULL, null=True, index=True + channel: fields.ForeignKeyRelation['Channel'] = fields.ForeignKeyField( + 'models.Channel', related_name='placements', on_delete=fields.CASCADE, index=True ) if TYPE_CHECKING: - project_id: UUID - creative_id: UUID - post_id: UUID | None - placement_id: UUID + project_id: uuid.UUID + creative_id: uuid.UUID + channel_id: uuid.UUID class Meta: - table = 'publication' + table = 'placement' + indexes = (('project', 'status'),) diff --git a/src/domain/project.py b/src/domain/project.py index 02127ac..5fe405b 100644 --- a/src/domain/project.py +++ b/src/domain/project.py @@ -5,7 +5,7 @@ from uuid import UUID from tortoise import fields from .base import TimestampedModel -from .purchase import InviteLinkType +from .placement import InviteLinkType if TYPE_CHECKING: from .channel import Channel diff --git a/src/domain/publication.py b/src/domain/publication.py new file mode 100644 index 0000000..7252fb1 --- /dev/null +++ b/src/domain/publication.py @@ -0,0 +1,58 @@ +import enum +from typing import TYPE_CHECKING +from uuid import UUID + +from tortoise import fields + +from .base import TimestampedModel + +if TYPE_CHECKING: + from .creative import Creative + from .placement import Placement + from .post import Post + from .project import Project + +__all__ = ['Publication', 'PublicationStatus', 'PostViewsAvailability'] + + +class PublicationStatus(str, enum.Enum): + ACTIVE = 'active' # Пост найден и отслеживается + ARCHIVED = 'archived' # Вручную архивировано + + +class PostViewsAvailability(str, enum.Enum): + UNKNOWN = 'unknown' # Ещё не проверялось + AVAILABLE = 'available' # Просмотры доступны + UNAVAILABLE = 'unavailable' # Нет доступа / битая ссылка / приватный канал + MANUAL = 'manual' # Просмотры вводятся вручную + + +class Publication(TimestampedModel): + wanted_placement_date = fields.DatetimeField(db_column='placement_date') + cost = fields.FloatField(null=True) + comment = fields.TextField(null=True) + + status = fields.CharEnumField(PublicationStatus, default=PublicationStatus.ACTIVE) + + project: fields.ForeignKeyRelation['Project'] = fields.ForeignKeyField( + 'models.Project', related_name='publications', on_delete=fields.CASCADE, index=True + ) + creative: fields.ForeignKeyRelation['Creative'] = fields.ForeignKeyField( + 'models.Creative', related_name='publications', on_delete=fields.CASCADE, index=True + ) + placement: fields.ForeignKeyRelation['Placement'] = fields.ForeignKeyField( + 'models.Placement', related_name='publications', on_delete=fields.CASCADE, index=True + ) + + post: fields.ForeignKeyRelation['Post'] | None = fields.ForeignKeyField( + 'models.Post', related_name='publications', on_delete=fields.SET_NULL, null=True, index=True + ) + + if TYPE_CHECKING: + project_id: UUID + creative_id: UUID + post_id: UUID | None + placement_id: UUID + + class Meta: + table = 'publication' diff --git a/src/domain/purchase.py b/src/domain/purchase.py deleted file mode 100644 index 0d494db..0000000 --- a/src/domain/purchase.py +++ /dev/null @@ -1,97 +0,0 @@ -import enum -import uuid -from typing import TYPE_CHECKING - -from tortoise import fields - -from .base import TimestampedModel - -if TYPE_CHECKING: - from .channel import Channel - from .creative import Creative - from .project import Project - -__all__ = ['Placement', 'PlacementStatus', 'PlacementType', 'InviteLinkType', 'CostType'] - - -class CostType(enum.StrEnum): - FIXED = 'fixed' - CPM = 'cpm' - - -class PlacementStatus(enum.StrEnum): - PLANNED = 'planned' - APPROVED = 'approved' - REJECTED = 'rejected' - IN_PROGRESS = 'in_progress' - COMPLETED = 'completed' - - -class PlacementType(enum.StrEnum): - SELF_PROMO = 'self_promo' - STANDARD = 'standard' - - -class InviteLinkType(enum.StrEnum): - PUBLIC = 'public' # открытая ссылка - APPROVAL = 'approval' # с одобрением ботом - - -class Placement(TimestampedModel): - """Размещение, управляемое пользователем (бывший PurchaseChannel + Purchase)""" - - status = fields.CharEnumField(PlacementStatus, default=PlacementStatus.PLANNED) - placement_at = fields.DatetimeField(null=True) - payment_at = fields.DatetimeField(null=True) - cost_type = fields.CharEnumField(CostType, null=True, max_length=8) - cost_value = fields.FloatField(null=True) - cost_before_bargain = fields.FloatField(null=True) - placement_type = fields.CharEnumField(PlacementType, null=True, max_length=16) - format = fields.TextField(null=True) - comment = fields.TextField(null=True) - - invite_link = fields.CharField(max_length=512) - invite_link_type = fields.CharEnumField(InviteLinkType) - - project: fields.ForeignKeyRelation['Project'] = fields.ForeignKeyField( - 'models.Project', related_name='placements', on_delete=fields.CASCADE, index=True - ) - creative: fields.ForeignKeyRelation['Creative'] = fields.ForeignKeyField( - 'models.Creative', related_name='placements', on_delete=fields.CASCADE, index=True - ) - channel: fields.ForeignKeyRelation['Channel'] = fields.ForeignKeyField( - 'models.Channel', related_name='placements', on_delete=fields.CASCADE, index=True - ) - - if TYPE_CHECKING: - project_id: uuid.UUID - creative_id: uuid.UUID - channel_id: uuid.UUID - - class Meta: - table = 'placement' - indexes = (('project', 'status'),) - - -# DEPRECATED: старые модели, будут удалены после миграции - -# class PurchaseStatus(enum.StrEnum): -# ACTIVE = 'active' -# ARCHIVED = 'archived' -# -# class PurchaseChannelStatus(enum.StrEnum): -# PLANNED = 'planned' -# APPROVED = 'approved' -# REJECTED = 'rejected' -# IN_PROGRESS = 'in_progress' -# COMPLETED = 'completed' -# -# class PurchaseType(enum.StrEnum): -# SELF_PROMO = 'self_promo' -# STANDARD = 'standard' -# -# class Purchase(TimestampedModel): -# ... -# -# class PurchaseChannel(TimestampedModel): -# ... diff --git a/src/domain/subscription.py b/src/domain/subscription.py index 1f30e99..c57707c 100644 --- a/src/domain/subscription.py +++ b/src/domain/subscription.py @@ -7,7 +7,7 @@ from tortoise import fields from .base import TimestampedModel if TYPE_CHECKING: - from .placement import Publication + from .publication import Publication from .telegram_user import TelegramUser diff --git a/src/domain/workspace.py b/src/domain/workspace.py index 9969ec1..87e4854 100644 --- a/src/domain/workspace.py +++ b/src/domain/workspace.py @@ -11,8 +11,8 @@ from .base import TimestampedModel if TYPE_CHECKING: from .channel import Channel from .creative import Creative + from .placement import Placement from .project import Project - from .purchase import Placement from .user import User diff --git a/src/dto/purchase.py b/src/dto/purchase.py index 6fdea9b..4c192ad 100644 --- a/src/dto/purchase.py +++ b/src/dto/purchase.py @@ -3,7 +3,7 @@ import uuid import pydantic -from src.domain.purchase import CostType, InviteLinkType, PlacementStatus, PlacementType +from src.domain.placement import CostType, InviteLinkType, PlacementStatus, PlacementType from .channel import ChannelOutput diff --git a/src/usecase/placement/__init__.py b/src/usecase/placement/__init__.py deleted file mode 100644 index 35d32d4..0000000 --- a/src/usecase/placement/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .fetch_publication_post_cycle import fetch_publication_post_cycle -from .get_publication import get_publication -from .get_publications import get_publications - -__all__ = ['fetch_publication_post_cycle', 'get_publication', 'get_publications'] diff --git a/src/usecase/purchase/__init__.py b/src/usecase/purchase/__init__.py deleted file mode 100644 index 5cd09fc..0000000 --- a/src/usecase/purchase/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .create_placements import create_placements -from .get_placement import get_placement_user -from .get_placements import get_placements - -__all__ = ['create_placements', 'get_placement_user', 'get_placements']