1
|
|
|
""" |
2
|
|
|
byceps.services.news.dbmodels.item |
3
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
4
|
|
|
|
5
|
|
|
:Copyright: 2014-2023 Jochen Kupperschmidt |
6
|
|
|
:License: Revised BSD (see `LICENSE` file for details) |
7
|
|
|
""" |
8
|
|
|
|
9
|
1 |
|
from datetime import datetime |
10
|
1 |
|
from typing import TYPE_CHECKING |
11
|
|
|
|
12
|
1 |
|
if TYPE_CHECKING: |
13
|
|
|
hybrid_property = property |
14
|
|
|
else: |
15
|
1 |
|
from sqlalchemy.ext.hybrid import hybrid_property |
16
|
|
|
|
17
|
1 |
|
from sqlalchemy.ext.associationproxy import association_proxy |
18
|
|
|
|
19
|
1 |
|
from ....database import db, generate_uuid |
20
|
1 |
|
from ....typing import UserID |
21
|
1 |
|
from ....util.instances import ReprBuilder |
22
|
|
|
|
23
|
1 |
|
from ...user.dbmodels.user import DbUser |
24
|
|
|
|
25
|
1 |
|
from ..transfer.models import BodyFormat, NewsChannelID |
26
|
|
|
|
27
|
1 |
|
from .channel import DbNewsChannel |
28
|
|
|
|
29
|
|
|
|
30
|
1 |
|
class DbNewsItem(db.Model): |
31
|
|
|
"""A news item. |
32
|
|
|
|
33
|
|
|
Each one is expected to have at least one version (the initial one). |
34
|
|
|
|
35
|
|
|
News items with a publication date set are considered public unless |
36
|
|
|
that date is in the future (i.e. those items have been pre-published |
37
|
|
|
and are awaiting publication). |
38
|
|
|
""" |
39
|
|
|
|
40
|
1 |
|
__tablename__ = 'news_items' |
41
|
|
|
|
42
|
1 |
|
id = db.Column(db.Uuid, default=generate_uuid, primary_key=True) |
43
|
1 |
|
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False) |
44
|
1 |
|
channel_id = db.Column( |
45
|
|
|
db.UnicodeText, |
46
|
|
|
db.ForeignKey('news_channels.id'), |
47
|
|
|
index=True, |
48
|
|
|
nullable=False, |
49
|
|
|
) |
50
|
1 |
|
channel = db.relationship(DbNewsChannel) |
51
|
1 |
|
slug = db.Column(db.UnicodeText, unique=True, index=True, nullable=False) |
52
|
1 |
|
published_at = db.Column(db.DateTime, nullable=True) |
53
|
1 |
|
current_version = association_proxy( |
54
|
|
|
'current_version_association', 'version' |
55
|
|
|
) |
56
|
1 |
|
featured_image_id = db.Column(db.Uuid, nullable=True) |
57
|
|
|
|
58
|
1 |
|
def __init__(self, channel_id: NewsChannelID, slug: str) -> None: |
59
|
1 |
|
self.channel_id = channel_id |
60
|
1 |
|
self.slug = slug |
61
|
|
|
|
62
|
1 |
|
@property |
63
|
1 |
|
def title(self) -> str: |
64
|
|
|
return self.current_version.title |
65
|
|
|
|
66
|
1 |
|
@property |
67
|
1 |
|
def published(self) -> bool: |
68
|
1 |
|
return self.published_at is not None |
69
|
|
|
|
70
|
1 |
|
def __repr__(self) -> str: |
71
|
|
|
return ( |
72
|
|
|
ReprBuilder(self) |
73
|
|
|
.add_with_lookup('id') |
74
|
|
|
.add('channel', self.channel_id) |
75
|
|
|
.add_with_lookup('slug') |
76
|
|
|
.add_with_lookup('published_at') |
77
|
|
|
.build() |
78
|
|
|
) |
79
|
|
|
|
80
|
|
|
|
81
|
1 |
|
class DbNewsItemVersion(db.Model): |
82
|
|
|
"""A snapshot of a news item at a certain time.""" |
83
|
|
|
|
84
|
1 |
|
__tablename__ = 'news_item_versions' |
85
|
|
|
|
86
|
1 |
|
id = db.Column(db.Uuid, default=generate_uuid, primary_key=True) |
87
|
1 |
|
item_id = db.Column( |
88
|
|
|
db.Uuid, db.ForeignKey('news_items.id'), index=True, nullable=False |
89
|
|
|
) |
90
|
1 |
|
item = db.relationship(DbNewsItem) |
91
|
1 |
|
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False) |
92
|
1 |
|
creator_id = db.Column(db.Uuid, db.ForeignKey('users.id'), nullable=False) |
93
|
1 |
|
creator = db.relationship(DbUser) |
94
|
1 |
|
title = db.Column(db.UnicodeText, nullable=False) |
95
|
1 |
|
body = db.Column(db.UnicodeText, nullable=False) |
96
|
1 |
|
_body_format = db.Column('body_format', db.UnicodeText, nullable=False) |
97
|
1 |
|
image_url_path = db.Column(db.UnicodeText, nullable=True) |
98
|
|
|
|
99
|
1 |
|
def __init__( |
100
|
|
|
self, |
101
|
|
|
item: DbNewsItem, |
102
|
|
|
creator_id: UserID, |
103
|
|
|
title: str, |
104
|
|
|
body: str, |
105
|
|
|
body_format: BodyFormat, |
106
|
|
|
) -> None: |
107
|
1 |
|
self.item = item |
108
|
1 |
|
self.creator_id = creator_id |
109
|
1 |
|
self.title = title |
110
|
1 |
|
self.body = body |
111
|
1 |
|
self.body_format = body_format |
112
|
|
|
|
113
|
1 |
|
@hybrid_property |
114
|
1 |
|
def body_format(self) -> BodyFormat: |
115
|
1 |
|
return BodyFormat[self._body_format] |
116
|
|
|
|
117
|
1 |
|
@body_format.setter |
118
|
1 |
|
def body_format(self, body_format: BodyFormat) -> None: |
119
|
1 |
|
assert body_format is not None |
120
|
1 |
|
self._body_format = body_format.name |
121
|
|
|
|
122
|
1 |
|
@property |
123
|
1 |
|
def is_current(self) -> bool: |
124
|
|
|
"""Return `True` if this version is the current version of the |
125
|
|
|
item it belongs to. |
126
|
|
|
""" |
127
|
1 |
|
return self.id == self.item.current_version.id |
128
|
|
|
|
129
|
1 |
|
def __repr__(self) -> str: |
130
|
|
|
return ( |
131
|
|
|
ReprBuilder(self) |
132
|
|
|
.add_with_lookup('id') |
133
|
|
|
.add_with_lookup('item') |
134
|
|
|
.add_with_lookup('created_at') |
135
|
|
|
.build() |
136
|
|
|
) |
137
|
|
|
|
138
|
|
|
|
139
|
1 |
|
class DbCurrentNewsItemVersionAssociation(db.Model): |
140
|
1 |
|
__tablename__ = 'news_item_current_versions' |
141
|
|
|
|
142
|
1 |
|
item_id = db.Column( |
143
|
|
|
db.Uuid, db.ForeignKey('news_items.id'), primary_key=True |
144
|
|
|
) |
145
|
1 |
|
item = db.relationship( |
146
|
|
|
DbNewsItem, |
147
|
|
|
backref=db.backref('current_version_association', uselist=False), |
148
|
|
|
) |
149
|
1 |
|
version_id = db.Column( |
150
|
|
|
db.Uuid, |
151
|
|
|
db.ForeignKey('news_item_versions.id'), |
152
|
|
|
unique=True, |
153
|
|
|
nullable=False, |
154
|
|
|
) |
155
|
1 |
|
version = db.relationship(DbNewsItemVersion) |
156
|
|
|
|
157
|
1 |
|
def __init__(self, item: DbNewsItem, version: DbNewsItemVersion) -> None: |
158
|
1 |
|
self.item = item |
159
|
|
|
self.version = version |
160
|
|
|
|