1
|
|
|
""" |
2
|
|
|
byceps.services.news.models.item |
3
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
4
|
|
|
|
5
|
|
|
:Copyright: 2006-2020 Jochen Kupperschmidt |
6
|
|
|
:License: Modified BSD, see LICENSE for details. |
7
|
|
|
""" |
8
|
|
|
|
9
|
1 |
|
from datetime import datetime |
10
|
|
|
|
11
|
1 |
|
from sqlalchemy.ext.associationproxy import association_proxy |
12
|
|
|
|
13
|
1 |
|
from ....database import BaseQuery, db, generate_uuid |
14
|
1 |
|
from ....typing import UserID |
15
|
1 |
|
from ....util.instances import ReprBuilder |
16
|
|
|
|
17
|
1 |
|
from ...user.models.user import User |
18
|
|
|
|
19
|
1 |
|
from ..transfer.models import ChannelID, ItemID |
20
|
|
|
|
21
|
1 |
|
from .channel import Channel |
22
|
|
|
|
23
|
|
|
|
24
|
1 |
|
class ItemQuery(BaseQuery): |
25
|
|
|
|
26
|
1 |
|
def for_channel(self, channel_id: ChannelID) -> BaseQuery: |
27
|
1 |
|
return self.filter_by(channel_id=channel_id) |
28
|
|
|
|
29
|
1 |
|
def with_channel(self) -> BaseQuery: |
30
|
1 |
|
return self.options( |
31
|
|
|
db.joinedload('channel'), |
32
|
|
|
) |
33
|
|
|
|
34
|
1 |
|
def with_images(self) -> BaseQuery: |
35
|
1 |
|
return self.options( |
36
|
|
|
db.joinedload('images'), |
37
|
|
|
) |
38
|
|
|
|
39
|
1 |
|
def published(self) -> BaseQuery: |
40
|
|
|
"""Return items that have been published.""" |
41
|
1 |
|
return self.filter(Item.published_at <= datetime.utcnow()) |
42
|
|
|
|
43
|
1 |
|
def with_current_version(self) -> BaseQuery: |
44
|
1 |
|
return self.options( |
45
|
|
|
db.joinedload('current_version_association').joinedload('version'), |
46
|
|
|
) |
47
|
|
|
|
48
|
|
|
|
49
|
1 |
|
class Item(db.Model): |
50
|
|
|
"""A news item. |
51
|
|
|
|
52
|
|
|
Each one is expected to have at least one version (the initial one). |
53
|
|
|
""" |
54
|
|
|
|
55
|
1 |
|
__tablename__ = 'news_items' |
56
|
1 |
|
__table_args__ = ( |
57
|
|
|
db.UniqueConstraint('channel_id', 'slug'), |
58
|
|
|
) |
59
|
1 |
|
query_class = ItemQuery |
60
|
|
|
|
61
|
1 |
|
id = db.Column(db.Uuid, default=generate_uuid, primary_key=True) |
62
|
1 |
|
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False) |
63
|
1 |
|
channel_id = db.Column(db.UnicodeText, db.ForeignKey('news_channels.id'), index=True, nullable=False) |
64
|
1 |
|
channel = db.relationship(Channel) |
65
|
1 |
|
slug = db.Column(db.UnicodeText, index=True, nullable=False) |
66
|
1 |
|
published_at = db.Column(db.DateTime, nullable=True) |
67
|
1 |
|
current_version = association_proxy('current_version_association', 'version') |
68
|
|
|
|
69
|
1 |
|
def __init__(self, channel_id: ChannelID, slug: str) -> None: |
70
|
1 |
|
self.channel_id = channel_id |
71
|
1 |
|
self.slug = slug |
72
|
|
|
|
73
|
1 |
|
@property |
74
|
1 |
|
def title(self) -> str: |
75
|
|
|
return self.current_version.title |
76
|
|
|
|
77
|
1 |
|
@property |
78
|
1 |
|
def published(self) -> bool: |
79
|
1 |
|
return self.published_at is not None |
80
|
|
|
|
81
|
1 |
|
def __repr__(self) -> str: |
82
|
|
|
return ReprBuilder(self) \ |
83
|
|
|
.add_with_lookup('id') \ |
84
|
|
|
.add('channel', self.channel_id) \ |
85
|
|
|
.add_with_lookup('slug') \ |
86
|
|
|
.add_with_lookup('published_at') \ |
87
|
|
|
.build() |
88
|
|
|
|
89
|
|
|
|
90
|
1 |
|
class ItemVersionQuery(BaseQuery): |
91
|
|
|
|
92
|
1 |
|
def for_item(self, item_id: ItemID) -> BaseQuery: |
93
|
1 |
|
return self.filter_by(item_id=item_id) |
94
|
|
|
|
95
|
|
|
|
96
|
1 |
|
class ItemVersion(db.Model): |
97
|
|
|
"""A snapshot of a news item at a certain time.""" |
98
|
|
|
|
99
|
1 |
|
__tablename__ = 'news_item_versions' |
100
|
1 |
|
query_class = ItemVersionQuery |
101
|
|
|
|
102
|
1 |
|
id = db.Column(db.Uuid, default=generate_uuid, primary_key=True) |
103
|
1 |
|
item_id = db.Column(db.Uuid, db.ForeignKey('news_items.id'), index=True, nullable=False) |
104
|
1 |
|
item = db.relationship(Item) |
105
|
1 |
|
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False) |
106
|
1 |
|
creator_id = db.Column(db.Uuid, db.ForeignKey('users.id'), nullable=False) |
107
|
1 |
|
creator = db.relationship(User) |
108
|
1 |
|
title = db.Column(db.UnicodeText, nullable=False) |
109
|
1 |
|
body = db.Column(db.UnicodeText, nullable=False) |
110
|
1 |
|
image_url_path = db.Column(db.UnicodeText, nullable=True) |
111
|
|
|
|
112
|
1 |
|
def __init__( |
113
|
|
|
self, item: Item, creator_id: UserID, title: str, body: str |
114
|
|
|
) -> None: |
115
|
1 |
|
self.item = item |
116
|
1 |
|
self.creator_id = creator_id |
117
|
1 |
|
self.title = title |
118
|
1 |
|
self.body = body |
119
|
|
|
|
120
|
1 |
|
@property |
121
|
1 |
|
def is_current(self) -> bool: |
122
|
|
|
"""Return `True` if this version is the current version of the |
123
|
|
|
item it belongs to. |
124
|
|
|
""" |
125
|
1 |
|
return self.id == self.item.current_version.id |
126
|
|
|
|
127
|
1 |
|
def __repr__(self) -> str: |
128
|
|
|
return ReprBuilder(self) \ |
129
|
|
|
.add_with_lookup('id') \ |
130
|
|
|
.add_with_lookup('item') \ |
131
|
|
|
.add_with_lookup('created_at') \ |
132
|
|
|
.build() |
133
|
|
|
|
134
|
|
|
|
135
|
1 |
|
class CurrentVersionAssociation(db.Model): |
136
|
1 |
|
__tablename__ = 'news_item_current_versions' |
137
|
|
|
|
138
|
1 |
|
item_id = db.Column(db.Uuid, db.ForeignKey('news_items.id'), primary_key=True) |
139
|
1 |
|
item = db.relationship(Item, backref=db.backref('current_version_association', uselist=False)) |
140
|
1 |
|
version_id = db.Column(db.Uuid, db.ForeignKey('news_item_versions.id'), unique=True, nullable=False) |
141
|
1 |
|
version = db.relationship(ItemVersion) |
142
|
|
|
|
143
|
1 |
|
def __init__(self, item: Item, version: ItemVersion) -> None: |
144
|
1 |
|
self.item = item |
145
|
|
|
self.version = version |
146
|
|
|
|