artemis-model 0.1.66__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- artemis_model/__init__.py +17 -0
- artemis_model/album.py +73 -0
- artemis_model/artist.py +51 -0
- artemis_model/auth.py +119 -0
- artemis_model/base.py +222 -0
- artemis_model/category.py +60 -0
- artemis_model/dj_set.py +120 -0
- artemis_model/genre.py +52 -0
- artemis_model/location.py +154 -0
- artemis_model/message.py +97 -0
- artemis_model/organization.py +78 -0
- artemis_model/otp.py +34 -0
- artemis_model/permission.py +53 -0
- artemis_model/playlist.py +191 -0
- artemis_model/schedule.py +35 -0
- artemis_model/setting.py +45 -0
- artemis_model/track.py +123 -0
- artemis_model/user.py +73 -0
- artemis_model/zone.py +69 -0
- artemis_model-0.1.66.dist-info/METADATA +113 -0
- artemis_model-0.1.66.dist-info/RECORD +22 -0
- artemis_model-0.1.66.dist-info/WHEEL +4 -0
artemis_model/genre.py
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
|
2
|
+
from sqlalchemy import Computed, func, literal, text
|
3
|
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
4
|
+
|
5
|
+
from artemis_model.base import CustomBase, CustomSyncBase, TSVector, TimeStampMixin
|
6
|
+
|
7
|
+
from sqlalchemy.ext.declarative import declared_attr
|
8
|
+
|
9
|
+
|
10
|
+
def to_tsvector_ix(*columns):
|
11
|
+
s = " || ' ' || ".join(columns)
|
12
|
+
return func.to_tsvector(literal("english"), text(s))
|
13
|
+
|
14
|
+
|
15
|
+
class GenreMixin(TimeStampMixin):
|
16
|
+
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
17
|
+
name: Mapped[str] = mapped_column(nullable=False)
|
18
|
+
parent_id: Mapped[int | None] = mapped_column(nullable=True)
|
19
|
+
disabled: Mapped[bool] = mapped_column(nullable=False, default=False)
|
20
|
+
legacy_id: Mapped[str] = mapped_column(nullable=False)
|
21
|
+
|
22
|
+
name_tsv = mapped_column(
|
23
|
+
TSVector(),
|
24
|
+
Computed("to_tsvector('english', name)", persisted=True),
|
25
|
+
)
|
26
|
+
|
27
|
+
@declared_attr
|
28
|
+
def tracks(cls) -> Mapped[list["Track"]]:
|
29
|
+
return relationship(
|
30
|
+
"Track", secondary="track_genre_assoc", back_populates="genres"
|
31
|
+
)
|
32
|
+
|
33
|
+
@declared_attr
|
34
|
+
def dj_set_genre_associations(cls) -> Mapped[list["DjSetGenreAssoc"]]:
|
35
|
+
return relationship(
|
36
|
+
back_populates="genre", cascade="all, delete-orphan"
|
37
|
+
)
|
38
|
+
|
39
|
+
def dj_sets(cls) -> Mapped[list["DjSet"]]:
|
40
|
+
return relationship("DjSet", secondary="dj_set_genre_assoc", back_populates="genre")
|
41
|
+
|
42
|
+
# __table_args__ = (
|
43
|
+
# Index("fts_ix_genre_name_tsv", to_tsvector_ix("name"), postgresql_using="gin"),
|
44
|
+
# )
|
45
|
+
|
46
|
+
|
47
|
+
class GenreSync(CustomSyncBase, GenreMixin):
|
48
|
+
pass
|
49
|
+
|
50
|
+
|
51
|
+
class Genre(CustomBase, GenreMixin):
|
52
|
+
pass
|
@@ -0,0 +1,154 @@
|
|
1
|
+
import uuid
|
2
|
+
from typing import Any, List
|
3
|
+
|
4
|
+
from slugify import slugify
|
5
|
+
from sqlalchemy import UUID, ForeignKey
|
6
|
+
from sqlalchemy.event import listen
|
7
|
+
from sqlalchemy.ext.associationproxy import AssociationProxy, association_proxy
|
8
|
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
9
|
+
|
10
|
+
from artemis_model.base import TimeStampMixin, CustomSyncBase, CustomBase
|
11
|
+
|
12
|
+
from sqlalchemy.ext.declarative import declared_attr
|
13
|
+
|
14
|
+
|
15
|
+
class AddressMixin:
|
16
|
+
id: Mapped[int] = mapped_column(autoincrement=True, primary_key=True)
|
17
|
+
street: Mapped[str] = mapped_column(nullable=False)
|
18
|
+
street2: Mapped[str] = mapped_column(nullable=True)
|
19
|
+
city: Mapped[str] = mapped_column(nullable=False)
|
20
|
+
state: Mapped[str] = mapped_column(nullable=False)
|
21
|
+
zip_code: Mapped[str] = mapped_column(nullable=False)
|
22
|
+
country: Mapped[str] = mapped_column(nullable=False)
|
23
|
+
|
24
|
+
@declared_attr
|
25
|
+
def location(cls) -> Mapped["Location"]:
|
26
|
+
return relationship(
|
27
|
+
uselist=False,
|
28
|
+
back_populates="address",
|
29
|
+
)
|
30
|
+
|
31
|
+
|
32
|
+
class AddressSync(CustomSyncBase, AddressMixin):
|
33
|
+
pass
|
34
|
+
|
35
|
+
|
36
|
+
class Address(CustomBase, AddressMixin):
|
37
|
+
pass
|
38
|
+
|
39
|
+
|
40
|
+
class LocationMixin(TimeStampMixin):
|
41
|
+
id: Mapped[uuid.UUID] = mapped_column(
|
42
|
+
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
43
|
+
)
|
44
|
+
name: Mapped[str] = mapped_column(nullable=False)
|
45
|
+
slug: Mapped[str] = mapped_column(nullable=False)
|
46
|
+
organization_id: Mapped[uuid.UUID] = mapped_column(
|
47
|
+
ForeignKey("organization.id"), nullable=False, index=True
|
48
|
+
)
|
49
|
+
location_type: Mapped[str] = mapped_column(nullable=True)
|
50
|
+
disabled: Mapped[bool] = mapped_column(nullable=False, default=False)
|
51
|
+
group_id: Mapped[int | None] = mapped_column(
|
52
|
+
ForeignKey("location_group.id"), nullable=True, index=True
|
53
|
+
)
|
54
|
+
|
55
|
+
address_id = mapped_column(ForeignKey("address.id"), nullable=True)
|
56
|
+
legacy_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), nullable=True)
|
57
|
+
|
58
|
+
@declared_attr
|
59
|
+
def organization(cls) -> Mapped["Organization"]:
|
60
|
+
return relationship(back_populates="locations")
|
61
|
+
|
62
|
+
@declared_attr
|
63
|
+
def group(cls) -> Mapped["LocationGroup"]:
|
64
|
+
return relationship(back_populates="locations")
|
65
|
+
|
66
|
+
@declared_attr
|
67
|
+
def zones(cls) -> Mapped[list["Zone"]]:
|
68
|
+
return relationship(back_populates="location")
|
69
|
+
|
70
|
+
@declared_attr
|
71
|
+
def users(cls) -> AssociationProxy[list["User"]]:
|
72
|
+
return association_proxy(
|
73
|
+
"user_location_associations",
|
74
|
+
"user",
|
75
|
+
creator=lambda user_id: UserLocationAssoc(user_id=user_id),
|
76
|
+
)
|
77
|
+
|
78
|
+
@declared_attr
|
79
|
+
def address(cls) -> Mapped["Address"]:
|
80
|
+
return relationship(
|
81
|
+
uselist=False,
|
82
|
+
back_populates="location",
|
83
|
+
)
|
84
|
+
|
85
|
+
@declared_attr
|
86
|
+
def user_location_associations(cls) -> Mapped[list["UserLocationAssoc"]]:
|
87
|
+
return relationship(
|
88
|
+
cascade="all, delete-orphan",
|
89
|
+
)
|
90
|
+
|
91
|
+
timezone: Mapped[str] = mapped_column(nullable=False, default="America/New_York")
|
92
|
+
|
93
|
+
|
94
|
+
def generate_slug(target: Any, value: Any, old_value: Any, initiator: Any) -> None:
|
95
|
+
"""Creates a reasonable slug based on location name."""
|
96
|
+
if value and (not target.slug or value != old_value):
|
97
|
+
target.slug = slugify(value, separator="_")
|
98
|
+
|
99
|
+
|
100
|
+
class LocationSync(CustomSyncBase, LocationMixin):
|
101
|
+
pass
|
102
|
+
|
103
|
+
|
104
|
+
listen(LocationSync.name, "set", generate_slug)
|
105
|
+
|
106
|
+
|
107
|
+
class Location(CustomBase, LocationMixin):
|
108
|
+
pass
|
109
|
+
|
110
|
+
|
111
|
+
listen(Location.name, "set", generate_slug)
|
112
|
+
|
113
|
+
|
114
|
+
class LocationGroupMixin(TimeStampMixin):
|
115
|
+
id: Mapped[int] = mapped_column(autoincrement=True, primary_key=True)
|
116
|
+
organization_id = mapped_column(
|
117
|
+
ForeignKey("organization.id"), nullable=False, index=True
|
118
|
+
)
|
119
|
+
name: Mapped[str] = mapped_column(nullable=False)
|
120
|
+
description: Mapped[str] = mapped_column(nullable=True)
|
121
|
+
disabled: Mapped[bool] = mapped_column(nullable=False, default=False)
|
122
|
+
|
123
|
+
@declared_attr
|
124
|
+
def locations(cls) -> Mapped[List["Location"]]:
|
125
|
+
return relationship(
|
126
|
+
back_populates="group",
|
127
|
+
)
|
128
|
+
|
129
|
+
|
130
|
+
class LocationGroupSync(CustomSyncBase, LocationGroupMixin):
|
131
|
+
pass
|
132
|
+
|
133
|
+
|
134
|
+
class LocationGroup(CustomBase, LocationGroupMixin):
|
135
|
+
pass
|
136
|
+
|
137
|
+
|
138
|
+
class UserLocationAssocMixin(TimeStampMixin):
|
139
|
+
location_id: Mapped[uuid.UUID] = mapped_column(
|
140
|
+
ForeignKey("location.id"), primary_key=True
|
141
|
+
)
|
142
|
+
user_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("user.id"), primary_key=True)
|
143
|
+
|
144
|
+
@declared_attr
|
145
|
+
def user(cls) -> Mapped["User"]:
|
146
|
+
return relationship(back_populates="user_location_associations")
|
147
|
+
|
148
|
+
|
149
|
+
class UserLocationAssocSync(CustomSyncBase, UserLocationAssocMixin):
|
150
|
+
pass
|
151
|
+
|
152
|
+
|
153
|
+
class UserLocationAssoc(CustomBase, UserLocationAssocMixin):
|
154
|
+
pass
|
artemis_model/message.py
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
import uuid
|
2
|
+
from typing import List
|
3
|
+
from sqlalchemy import JSON, ForeignKey
|
4
|
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
5
|
+
|
6
|
+
from artemis_model.base import CustomSyncBase, TimeStampMixin, AuditMixin, CustomBase
|
7
|
+
|
8
|
+
from sqlalchemy.ext.declarative import declared_attr
|
9
|
+
|
10
|
+
|
11
|
+
class MessageMixin(TimeStampMixin, AuditMixin):
|
12
|
+
"""
|
13
|
+
This table is used to store the message.
|
14
|
+
"""
|
15
|
+
id: Mapped[int] = mapped_column(autoincrement=True, primary_key=True, index=True)
|
16
|
+
name: Mapped[str] = mapped_column(nullable=False)
|
17
|
+
disabled: Mapped[bool] = mapped_column(nullable=False, default=False)
|
18
|
+
s3_link: Mapped[str] = mapped_column(nullable=False)
|
19
|
+
|
20
|
+
organization_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("organization.id"), nullable=False, index=True)
|
21
|
+
message_group_id: Mapped[int] = mapped_column(ForeignKey("message_group.id"), nullable=True, index=True, default=None)
|
22
|
+
|
23
|
+
@declared_attr
|
24
|
+
def organization(cls) -> Mapped["Organization"]:
|
25
|
+
return relationship("Organization", back_populates="messages")
|
26
|
+
|
27
|
+
@declared_attr
|
28
|
+
def message_group(cls) -> Mapped["MessageGroup"]:
|
29
|
+
return relationship("MessageGroup", back_populates="messages")
|
30
|
+
|
31
|
+
@declared_attr
|
32
|
+
def play_details(cls) -> Mapped[List["MessagePlayDetail"]]:
|
33
|
+
return relationship("MessagePlayDetail", back_populates="message")
|
34
|
+
|
35
|
+
|
36
|
+
class MessageSync(CustomSyncBase, MessageMixin):
|
37
|
+
pass
|
38
|
+
|
39
|
+
|
40
|
+
class Message(CustomBase, MessageMixin):
|
41
|
+
pass
|
42
|
+
|
43
|
+
|
44
|
+
class MessagePlayDetailMixin(TimeStampMixin, AuditMixin):
|
45
|
+
"""
|
46
|
+
This table is used to store the message play details.
|
47
|
+
Frequency is used only for rotated messages.
|
48
|
+
Timesheet is used only for scheduled messages.
|
49
|
+
"""
|
50
|
+
id: Mapped[int] = mapped_column(autoincrement=True, primary_key=True, index=True)
|
51
|
+
message_id: Mapped[int] = mapped_column(ForeignKey("message.id"), nullable=False, index=True)
|
52
|
+
zone_id: Mapped[int] = mapped_column(ForeignKey("zone.id"), index=True)
|
53
|
+
|
54
|
+
type: Mapped[str] = mapped_column(nullable=False)
|
55
|
+
|
56
|
+
timesheet: Mapped[JSON] = mapped_column(JSON, nullable=True)
|
57
|
+
|
58
|
+
@declared_attr
|
59
|
+
def message(cls) -> Mapped["Message"]:
|
60
|
+
return relationship("Message", back_populates="play_details")
|
61
|
+
|
62
|
+
@declared_attr
|
63
|
+
def zone(cls) -> Mapped["Zone"]:
|
64
|
+
return relationship("Zone", back_populates="message_play_details")
|
65
|
+
|
66
|
+
|
67
|
+
class MessagePlayDetailSync(CustomSyncBase, MessagePlayDetailMixin):
|
68
|
+
pass
|
69
|
+
|
70
|
+
|
71
|
+
class MessagePlayDetail(CustomBase, MessagePlayDetailMixin):
|
72
|
+
pass
|
73
|
+
|
74
|
+
|
75
|
+
class MessageGroupMixin(TimeStampMixin, AuditMixin):
|
76
|
+
"""
|
77
|
+
This table is used to store the message group.
|
78
|
+
"""
|
79
|
+
id: Mapped[int] = mapped_column(autoincrement=True, primary_key=True, index=True)
|
80
|
+
name: Mapped[str] = mapped_column(nullable=False)
|
81
|
+
organization_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("organization.id"), nullable=False, index=True)
|
82
|
+
|
83
|
+
@declared_attr
|
84
|
+
def organization(cls) -> Mapped["Organization"]:
|
85
|
+
return relationship("Organization", back_populates="message_groups")
|
86
|
+
|
87
|
+
@declared_attr
|
88
|
+
def messages(cls) -> Mapped[List["Message"]]:
|
89
|
+
return relationship(back_populates="message_group")
|
90
|
+
|
91
|
+
|
92
|
+
class MessageGroupSync(CustomSyncBase, MessageGroupMixin):
|
93
|
+
pass
|
94
|
+
|
95
|
+
|
96
|
+
class MessageGroup(CustomBase, MessageGroupMixin):
|
97
|
+
pass
|
@@ -0,0 +1,78 @@
|
|
1
|
+
import uuid
|
2
|
+
from typing import Any, List
|
3
|
+
|
4
|
+
from slugify import slugify
|
5
|
+
from sqlalchemy import UUID, ForeignKey
|
6
|
+
from sqlalchemy.event import listen
|
7
|
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
8
|
+
|
9
|
+
from artemis_model.base import CustomSyncBase, TimeStampMixin, CustomBase
|
10
|
+
|
11
|
+
from sqlalchemy.ext.declarative import declared_attr
|
12
|
+
|
13
|
+
|
14
|
+
class OrganizationMixin(TimeStampMixin):
|
15
|
+
id: Mapped[uuid.UUID] = mapped_column(
|
16
|
+
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4
|
17
|
+
)
|
18
|
+
name: Mapped[str] = mapped_column(nullable=False)
|
19
|
+
description: Mapped[str | None] = mapped_column(nullable=True)
|
20
|
+
slug: Mapped[str] = mapped_column(nullable=False, index=True)
|
21
|
+
disabled: Mapped[bool] = mapped_column(nullable=False, default=False)
|
22
|
+
|
23
|
+
@declared_attr
|
24
|
+
def locations(cls) -> Mapped[List["Location"]]:
|
25
|
+
return relationship(back_populates="organization")
|
26
|
+
|
27
|
+
@declared_attr
|
28
|
+
def dj_sets(cls) -> Mapped[List["DjSet"]]:
|
29
|
+
return relationship(back_populates="organization")
|
30
|
+
|
31
|
+
@declared_attr
|
32
|
+
def users(cls) -> Mapped[List["User"]]:
|
33
|
+
return relationship(
|
34
|
+
secondary="organization_user_assoc", back_populates="organizations"
|
35
|
+
)
|
36
|
+
|
37
|
+
@declared_attr
|
38
|
+
def messages(cls) -> Mapped[List["Message"]]:
|
39
|
+
return relationship(back_populates="organization")
|
40
|
+
|
41
|
+
@declared_attr
|
42
|
+
def message_groups(cls) -> Mapped[List["MessageGroup"]]:
|
43
|
+
return relationship(back_populates="organization")
|
44
|
+
|
45
|
+
|
46
|
+
def generate_slug(target: Any, value: Any, old_value: Any, initiator: Any) -> None:
|
47
|
+
"""Creates a reasonable slug based on organization name."""
|
48
|
+
if value and (not target.slug or value != old_value):
|
49
|
+
target.slug = slugify(value, separator="_")
|
50
|
+
|
51
|
+
|
52
|
+
class OrganizationSync(CustomSyncBase, OrganizationMixin):
|
53
|
+
pass
|
54
|
+
|
55
|
+
|
56
|
+
listen(OrganizationSync.name, "set", generate_slug)
|
57
|
+
|
58
|
+
|
59
|
+
class Organization(CustomBase, OrganizationMixin):
|
60
|
+
pass
|
61
|
+
|
62
|
+
|
63
|
+
listen(Organization.name, "set", generate_slug)
|
64
|
+
|
65
|
+
|
66
|
+
class OrganizationUserAssocMixin(TimeStampMixin):
|
67
|
+
organization_id: Mapped[uuid.UUID] = mapped_column(
|
68
|
+
ForeignKey("organization.id"), primary_key=True
|
69
|
+
)
|
70
|
+
user_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("user.id"), primary_key=True)
|
71
|
+
|
72
|
+
|
73
|
+
class OrganizationUserAssocSync(CustomSyncBase, OrganizationUserAssocMixin):
|
74
|
+
pass
|
75
|
+
|
76
|
+
|
77
|
+
class OrganizationUserAssoc(CustomBase, OrganizationUserAssocMixin):
|
78
|
+
pass
|
artemis_model/otp.py
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
|
2
|
+
import uuid
|
3
|
+
from datetime import datetime
|
4
|
+
from sqlalchemy.orm import Mapped, mapped_column
|
5
|
+
|
6
|
+
from artemis_model.base import TimeStampMixin, CustomBase, CustomSyncBase
|
7
|
+
|
8
|
+
|
9
|
+
class OtpMixin(TimeStampMixin):
|
10
|
+
"""
|
11
|
+
This table is used to store the OTPs.
|
12
|
+
requested_by can be an UserAccount or UserUnverifiedAccount id.
|
13
|
+
"""
|
14
|
+
|
15
|
+
id: Mapped[int] = mapped_column(
|
16
|
+
primary_key=True, autoincrement=True, nullable=False
|
17
|
+
)
|
18
|
+
code: Mapped[str] = mapped_column(nullable=False)
|
19
|
+
purpose: Mapped[str] = mapped_column(nullable=False)
|
20
|
+
requested_by_id: Mapped[uuid.UUID] = mapped_column(nullable=False, index=True)
|
21
|
+
method: Mapped[str] = mapped_column(nullable=False, index=True)
|
22
|
+
secret: Mapped[str] = mapped_column(nullable=False)
|
23
|
+
created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow, index=True)
|
24
|
+
updated_at: Mapped[datetime] = mapped_column(
|
25
|
+
default=datetime.utcnow, onupdate=datetime.utcnow
|
26
|
+
)
|
27
|
+
|
28
|
+
|
29
|
+
class OtpSync(CustomSyncBase, OtpMixin):
|
30
|
+
pass
|
31
|
+
|
32
|
+
|
33
|
+
class Otp(CustomBase, OtpMixin):
|
34
|
+
pass
|
@@ -0,0 +1,53 @@
|
|
1
|
+
import uuid
|
2
|
+
from typing import List
|
3
|
+
|
4
|
+
from sqlalchemy import ForeignKey
|
5
|
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
6
|
+
|
7
|
+
from artemis_model.base import TimeStampMixin, CustomSyncBase, CustomBase
|
8
|
+
|
9
|
+
from sqlalchemy.ext.declarative import declared_attr
|
10
|
+
|
11
|
+
|
12
|
+
class PermissionMixin(TimeStampMixin):
|
13
|
+
id: Mapped[str] = mapped_column(
|
14
|
+
primary_key=True
|
15
|
+
)
|
16
|
+
name: Mapped[str] = mapped_column(nullable=False)
|
17
|
+
description: Mapped[str] = mapped_column(nullable=False)
|
18
|
+
type: Mapped[str] = mapped_column(nullable=False) # music-management, player-management, user-management
|
19
|
+
|
20
|
+
@declared_attr
|
21
|
+
def user_permission_associations(cls) -> Mapped[List["UserPermissionAssoc"]]:
|
22
|
+
return relationship(
|
23
|
+
back_populates="permission", cascade="all, delete-orphan"
|
24
|
+
)
|
25
|
+
|
26
|
+
|
27
|
+
class UserPermissionAssocMixin(TimeStampMixin):
|
28
|
+
user_id: Mapped[uuid.UUID] = mapped_column(
|
29
|
+
ForeignKey("user.id"), primary_key=True
|
30
|
+
)
|
31
|
+
permission_id: Mapped[uuid.UUID] = mapped_column(
|
32
|
+
ForeignKey("permission.id"), primary_key=True
|
33
|
+
)
|
34
|
+
|
35
|
+
@declared_attr
|
36
|
+
def permission(cls) -> Mapped["Permission"]:
|
37
|
+
return relationship(back_populates="user_permission_associations")
|
38
|
+
|
39
|
+
|
40
|
+
class UserPermissionAssocSync(CustomSyncBase, UserPermissionAssocMixin):
|
41
|
+
pass
|
42
|
+
|
43
|
+
|
44
|
+
class UserPermissionAssoc(CustomBase, UserPermissionAssocMixin):
|
45
|
+
pass
|
46
|
+
|
47
|
+
|
48
|
+
class PermissionSync(CustomSyncBase, PermissionMixin):
|
49
|
+
pass
|
50
|
+
|
51
|
+
|
52
|
+
class Permission(CustomBase, PermissionMixin):
|
53
|
+
pass
|
@@ -0,0 +1,191 @@
|
|
1
|
+
import uuid
|
2
|
+
from datetime import date, datetime
|
3
|
+
|
4
|
+
from sqlalchemy import Computed, ForeignKey, func, literal, text
|
5
|
+
from sqlalchemy.ext.associationproxy import AssociationProxy, association_proxy
|
6
|
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
7
|
+
|
8
|
+
from artemis_model.base import CustomBase, CustomSyncBase, TSVector, TimeStampMixin
|
9
|
+
|
10
|
+
from sqlalchemy.ext.declarative import declared_attr
|
11
|
+
|
12
|
+
|
13
|
+
def to_tsvector_ix(*columns):
|
14
|
+
s = " || ' ' || ".join(columns)
|
15
|
+
return func.to_tsvector(literal("english"), text(s))
|
16
|
+
|
17
|
+
|
18
|
+
class PlaylistMixin(TimeStampMixin):
|
19
|
+
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
20
|
+
name: Mapped[str] = mapped_column(nullable=False, unique=True)
|
21
|
+
entry_date: Mapped[datetime] = mapped_column(
|
22
|
+
nullable=False, default=datetime.utcnow
|
23
|
+
)
|
24
|
+
disabled: Mapped[bool] = mapped_column(nullable=False, default=False)
|
25
|
+
legacy_id: Mapped[str] = mapped_column(nullable=True)
|
26
|
+
is_published: Mapped[bool] = mapped_column(default=False) # legacy isTest
|
27
|
+
description: Mapped[str | None] = mapped_column(nullable=True)
|
28
|
+
cover_image: Mapped[str | None] = mapped_column(nullable=True)
|
29
|
+
is_ordered: Mapped[bool] = mapped_column(
|
30
|
+
default=False
|
31
|
+
) # CanBeShuffled - Migration'da tersini al
|
32
|
+
legacy_prepared_by: Mapped[str | None] = mapped_column(nullable=True)
|
33
|
+
notes: Mapped[str | None] = mapped_column(nullable=True)
|
34
|
+
published_at: Mapped[date | None] = mapped_column(nullable=True)
|
35
|
+
is_private: Mapped[bool] = mapped_column(default=False)
|
36
|
+
|
37
|
+
name_tsv = mapped_column(
|
38
|
+
TSVector(),
|
39
|
+
Computed("to_tsvector('english', name)", persisted=True),
|
40
|
+
)
|
41
|
+
|
42
|
+
@declared_attr
|
43
|
+
def category_playlist_associations(cls) -> Mapped[
|
44
|
+
list["PlaylistCategoryAssoc"]
|
45
|
+
]:
|
46
|
+
return relationship(cascade="all, delete-orphan")
|
47
|
+
|
48
|
+
@declared_attr
|
49
|
+
def track_playlist_associations(cls) -> Mapped[list["PlaylistTrackAssoc"]]:
|
50
|
+
return relationship(
|
51
|
+
cascade="all, delete-orphan"
|
52
|
+
)
|
53
|
+
|
54
|
+
@declared_attr
|
55
|
+
def organization_playlist_associations(cls) -> Mapped[
|
56
|
+
list["PlaylistOrganizationAssoc"]
|
57
|
+
]:
|
58
|
+
return relationship(cascade="all, delete-orphan")
|
59
|
+
|
60
|
+
@declared_attr
|
61
|
+
def organization_ids(cls) -> AssociationProxy[list["Organization"] | None]:
|
62
|
+
return association_proxy(
|
63
|
+
"organization_playlist_associations",
|
64
|
+
"organization",
|
65
|
+
creator=lambda o: PlaylistOrganizationAssoc(organization_id=o),
|
66
|
+
)
|
67
|
+
|
68
|
+
@declared_attr
|
69
|
+
def category_ids(cls) -> AssociationProxy[list["Category"] | None]:
|
70
|
+
return association_proxy(
|
71
|
+
"category_playlist_associations",
|
72
|
+
"category",
|
73
|
+
creator=lambda c: PlaylistCategoryAssoc(category_id=c),
|
74
|
+
)
|
75
|
+
|
76
|
+
@declared_attr
|
77
|
+
def track_ids(cls) -> AssociationProxy[list["Track"] | None]:
|
78
|
+
return association_proxy(
|
79
|
+
"track_playlist_associations",
|
80
|
+
"track",
|
81
|
+
creator=lambda t: PlaylistTrackAssoc(track_id=t),
|
82
|
+
)
|
83
|
+
|
84
|
+
@declared_attr
|
85
|
+
def tracks(cls) -> Mapped[list["Track"]]:
|
86
|
+
return relationship(
|
87
|
+
secondary="playlist_track_assoc", viewonly=True
|
88
|
+
)
|
89
|
+
|
90
|
+
@declared_attr
|
91
|
+
def categories(cls) -> Mapped[list["Category"]]:
|
92
|
+
return relationship(
|
93
|
+
secondary="playlist_category_assoc", viewonly=True
|
94
|
+
)
|
95
|
+
|
96
|
+
@property
|
97
|
+
def total_duration(self) -> int:
|
98
|
+
return sum([0 if not t.duration else t.duration for t in self.tracks], 0)
|
99
|
+
|
100
|
+
# __table_args__ = (
|
101
|
+
# Index(
|
102
|
+
# "fts_ix_playlist_name_tsv",
|
103
|
+
# to_tsvector_ix("name"),
|
104
|
+
# postgresql_using="gin",
|
105
|
+
# ),
|
106
|
+
# )
|
107
|
+
|
108
|
+
|
109
|
+
class PlaylistSync(CustomSyncBase, PlaylistMixin):
|
110
|
+
pass
|
111
|
+
|
112
|
+
|
113
|
+
class Playlist(CustomBase, PlaylistMixin):
|
114
|
+
pass
|
115
|
+
|
116
|
+
|
117
|
+
class PlaylistOrganizationAssocMixin(TimeStampMixin):
|
118
|
+
playlist_id: Mapped[int] = mapped_column(
|
119
|
+
ForeignKey("playlist.id"), primary_key=True, nullable=False
|
120
|
+
)
|
121
|
+
organization_id: Mapped[uuid.UUID] = mapped_column(
|
122
|
+
ForeignKey("organization.id"), primary_key=True, nullable=False
|
123
|
+
)
|
124
|
+
|
125
|
+
|
126
|
+
class PlaylistOrganizationkAssocSync(CustomSyncBase, PlaylistOrganizationAssocMixin):
|
127
|
+
pass
|
128
|
+
|
129
|
+
|
130
|
+
class PlaylistOrganizationAssoc(CustomBase, PlaylistOrganizationAssocMixin):
|
131
|
+
pass
|
132
|
+
|
133
|
+
|
134
|
+
class PlaylistTrackAssocMixin(TimeStampMixin):
|
135
|
+
playlist_id: Mapped[int] = mapped_column(
|
136
|
+
ForeignKey("playlist.id"), primary_key=True, nullable=False
|
137
|
+
)
|
138
|
+
track_id: Mapped[uuid.UUID] = mapped_column(
|
139
|
+
ForeignKey("track.id"), primary_key=True, nullable=False
|
140
|
+
)
|
141
|
+
|
142
|
+
@declared_attr
|
143
|
+
def track(cls) -> Mapped["Track"]:
|
144
|
+
return relationship(back_populates="track_playlist_associations")
|
145
|
+
|
146
|
+
|
147
|
+
class PlaylistTrackAssocSync(CustomSyncBase, PlaylistTrackAssocMixin):
|
148
|
+
pass
|
149
|
+
|
150
|
+
|
151
|
+
class PlaylistTrackAssoc(CustomBase, PlaylistTrackAssocMixin):
|
152
|
+
pass
|
153
|
+
|
154
|
+
|
155
|
+
class PlaylistCategoryAssocMixin(TimeStampMixin):
|
156
|
+
playlist_id = mapped_column(
|
157
|
+
ForeignKey("playlist.id"), primary_key=True, nullable=False
|
158
|
+
)
|
159
|
+
category_id: Mapped[int] = mapped_column(
|
160
|
+
ForeignKey("category.id"), primary_key=True, nullable=False
|
161
|
+
)
|
162
|
+
|
163
|
+
@declared_attr
|
164
|
+
def category(cls) -> Mapped["Category"]:
|
165
|
+
return relationship(
|
166
|
+
back_populates="category_playlist_associations"
|
167
|
+
)
|
168
|
+
|
169
|
+
|
170
|
+
class PlaylistCategoryAssocSync(CustomSyncBase, PlaylistCategoryAssocMixin):
|
171
|
+
pass
|
172
|
+
|
173
|
+
|
174
|
+
class PlaylistCategoryAssoc(CustomBase, PlaylistCategoryAssocMixin):
|
175
|
+
pass
|
176
|
+
|
177
|
+
|
178
|
+
class PlaylistCounterMixin:
|
179
|
+
playlist_id: Mapped[int] = mapped_column(
|
180
|
+
ForeignKey("playlist.id"), primary_key=True, nullable=False
|
181
|
+
)
|
182
|
+
play_count: Mapped[int] = mapped_column(default=0)
|
183
|
+
schedule_count: Mapped[int] = mapped_column(default=0)
|
184
|
+
|
185
|
+
|
186
|
+
class PlaylistCounterSync(CustomSyncBase, PlaylistCounterMixin):
|
187
|
+
pass
|
188
|
+
|
189
|
+
|
190
|
+
class PlaylistCounter(CustomBase, PlaylistCounterMixin):
|
191
|
+
pass
|
@@ -0,0 +1,35 @@
|
|
1
|
+
import uuid
|
2
|
+
|
3
|
+
from sqlalchemy import ForeignKey
|
4
|
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
5
|
+
from sqlalchemy.types import JSON, UUID
|
6
|
+
|
7
|
+
from artemis_model.base import CustomBase, TimeStampMixin, AuditMixin, CustomSyncBase
|
8
|
+
|
9
|
+
from sqlalchemy.ext.declarative import declared_attr
|
10
|
+
|
11
|
+
|
12
|
+
class ScheduleMixin(TimeStampMixin, AuditMixin):
|
13
|
+
"""
|
14
|
+
This table is used to store the schedule of a zone.
|
15
|
+
"""
|
16
|
+
|
17
|
+
id: Mapped[int] = mapped_column(autoincrement=True, primary_key=True, index=True)
|
18
|
+
name: Mapped[str] = mapped_column(nullable=False)
|
19
|
+
zone_id: Mapped[int] = mapped_column(ForeignKey("zone.id"), index=True)
|
20
|
+
is_active: Mapped[bool] = mapped_column(default=True, nullable=False)
|
21
|
+
timesheet: Mapped[JSON] = mapped_column(JSON, nullable=True)
|
22
|
+
locked_by: Mapped[str] = mapped_column(default=None, nullable=True)
|
23
|
+
legacy_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), nullable=True)
|
24
|
+
|
25
|
+
@declared_attr
|
26
|
+
def zone(cls) -> Mapped["Zone"]:
|
27
|
+
return relationship(back_populates="schedules")
|
28
|
+
|
29
|
+
|
30
|
+
class ScheduleSync(CustomSyncBase, ScheduleMixin):
|
31
|
+
pass
|
32
|
+
|
33
|
+
|
34
|
+
class Schedule(CustomBase, ScheduleMixin):
|
35
|
+
pass
|