1
|
|
|
""" |
2
|
|
|
byceps.services.site_navigation.site_navigation_service |
3
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
4
|
|
|
|
5
|
|
|
:Copyright: 2014-2023 Jochen Kupperschmidt |
6
|
|
|
:License: Revised BSD (see `LICENSE` file for details) |
7
|
|
|
""" |
8
|
|
|
|
9
|
1 |
|
from typing import Iterable, Optional |
10
|
|
|
|
11
|
1 |
|
from sqlalchemy import select |
12
|
|
|
|
13
|
1 |
|
from ...database import db |
14
|
1 |
|
from ...services.site.transfer.models import SiteID |
15
|
|
|
|
16
|
1 |
|
from .dbmodels import DbNavItem, DbNavMenu |
17
|
1 |
|
from .transfer.models import ( |
18
|
|
|
NavItem, |
19
|
|
|
NavItemID, |
20
|
|
|
NavItemTargetType, |
21
|
|
|
NavMenu, |
22
|
|
|
NavMenuAggregate, |
23
|
|
|
NavMenuID, |
24
|
|
|
) |
25
|
|
|
|
26
|
|
|
|
27
|
1 |
|
def create_menu( |
28
|
|
|
site_id: SiteID, |
29
|
|
|
name: str, |
30
|
|
|
language_code: str, |
31
|
|
|
*, |
32
|
|
|
hidden: bool = False, |
33
|
|
|
) -> NavMenu: |
34
|
|
|
"""Create a menu.""" |
35
|
|
|
db_menu = DbNavMenu(site_id, name, language_code, hidden) |
36
|
|
|
db.session.add(db_menu) |
37
|
|
|
db.session.commit() |
38
|
|
|
|
39
|
|
|
return _db_entity_to_menu(db_menu) |
40
|
|
|
|
41
|
|
|
|
42
|
1 |
|
def update_menu( |
43
|
|
|
menu_id: NavMenuID, |
44
|
|
|
name: str, |
45
|
|
|
language_code: str, |
46
|
|
|
hidden: bool, |
47
|
|
|
) -> NavMenu: |
48
|
|
|
"""Update a menu.""" |
49
|
|
|
db_menu = _get_db_menu(menu_id) |
50
|
|
|
|
51
|
|
|
db_menu.name = name |
52
|
|
|
db_menu.language_code = language_code |
53
|
|
|
db_menu.hidden = hidden |
54
|
|
|
|
55
|
|
|
db.session.commit() |
56
|
|
|
|
57
|
|
|
return _db_entity_to_menu(db_menu) |
58
|
|
|
|
59
|
|
|
|
60
|
1 |
|
def create_item( |
61
|
|
|
menu_id: NavMenuID, |
62
|
|
|
target_type: NavItemTargetType, |
63
|
|
|
target: str, |
64
|
|
|
label: str, |
65
|
|
|
current_page_id: str, |
66
|
|
|
*, |
67
|
|
|
parent_item_id: Optional[NavItemID] = None, |
68
|
|
|
hidden: bool = False, |
69
|
|
|
) -> NavItem: |
70
|
|
|
"""Create a menu item.""" |
71
|
|
|
db_menu = _get_db_menu(menu_id) |
72
|
|
|
|
73
|
|
|
db_item = DbNavItem( |
74
|
|
|
db_menu.id, |
75
|
|
|
parent_item_id, |
76
|
|
|
target_type, |
77
|
|
|
target, |
78
|
|
|
label, |
79
|
|
|
current_page_id, |
80
|
|
|
hidden, |
81
|
|
|
) |
82
|
|
|
db_menu.items.append(db_item) |
83
|
|
|
db.session.commit() |
84
|
|
|
|
85
|
|
|
return _db_entity_to_item(db_item) |
86
|
|
|
|
87
|
|
|
|
88
|
1 |
|
def find_menu(menu_id: NavMenuID) -> Optional[NavMenu]: |
89
|
|
|
"""Return the menu, or `None` if not found.""" |
90
|
|
|
db_menu = _find_db_menu(menu_id) |
91
|
|
|
|
92
|
|
|
if db_menu is None: |
93
|
|
|
return None |
94
|
|
|
|
95
|
|
|
return _db_entity_to_menu(db_menu) |
96
|
|
|
|
97
|
|
|
|
98
|
1 |
|
def get_menu(menu_id: NavMenuID) -> NavMenu: |
99
|
|
|
"""Return the menu. |
100
|
|
|
|
101
|
|
|
Raise error if not found. |
102
|
|
|
""" |
103
|
|
|
db_menu = _get_db_menu(menu_id) |
104
|
|
|
|
105
|
|
|
return _db_entity_to_menu(db_menu) |
106
|
|
|
|
107
|
|
|
|
108
|
1 |
|
def _find_db_menu(menu_id: NavMenuID) -> Optional[DbNavMenu]: |
109
|
|
|
"""Return the menu, or `None` if not found.""" |
110
|
|
|
return db.session.get(DbNavMenu, menu_id) |
111
|
|
|
|
112
|
|
|
|
113
|
1 |
|
def _get_db_menu(menu_id: NavMenuID) -> DbNavMenu: |
114
|
|
|
"""Return the menu. |
115
|
|
|
|
116
|
|
|
Raise error if not found. |
117
|
|
|
""" |
118
|
|
|
db_menu = _find_db_menu(menu_id) |
119
|
|
|
|
120
|
|
|
if db_menu is None: |
121
|
|
|
raise ValueError('Unknown menu ID') |
122
|
|
|
|
123
|
|
|
return db_menu |
124
|
|
|
|
125
|
|
|
|
126
|
1 |
|
def find_menu_aggregate(menu_id: NavMenuID) -> Optional[NavMenuAggregate]: |
127
|
|
|
"""Return the menu aggregate, or `None` if not found.""" |
128
|
|
|
db_menu = _find_db_menu(menu_id) |
129
|
|
|
if db_menu is None: |
130
|
|
|
return None |
131
|
|
|
|
132
|
|
|
db_items = db.session.scalars( |
133
|
|
|
select(DbNavItem).filter(DbNavItem.menu_id == db_menu.id) |
134
|
|
|
) |
135
|
|
|
|
136
|
|
|
return _db_entity_to_menu_aggregate(db_menu, db_items) |
137
|
|
|
|
138
|
|
|
|
139
|
1 |
|
def get_menus(site_id: SiteID) -> list[NavMenu]: |
140
|
|
|
"""Return the menus for this site.""" |
141
|
|
|
db_menus = db.session.scalars( |
142
|
|
|
select(DbNavMenu).filter(DbNavMenu.site_id == site_id) |
143
|
|
|
) |
144
|
|
|
|
145
|
|
|
return [_db_entity_to_menu(db_menu) for db_menu in db_menus] |
146
|
|
|
|
147
|
|
|
|
148
|
1 |
|
def find_item(item_id: NavItemID) -> Optional[NavItem]: |
149
|
|
|
"""Return the menu item, or `None` if not found.""" |
150
|
|
|
db_item = _find_db_item(item_id) |
151
|
|
|
|
152
|
|
|
if db_item is None: |
153
|
|
|
return None |
154
|
|
|
|
155
|
|
|
return _db_entity_to_item(db_item) |
156
|
|
|
|
157
|
|
|
|
158
|
1 |
|
def _find_db_item(item_id: NavItemID) -> Optional[DbNavItem]: |
159
|
|
|
"""Return the menu item, or `None` if not found.""" |
160
|
|
|
return db.session.get(DbNavItem, item_id) |
161
|
|
|
|
162
|
|
|
|
163
|
1 |
|
def _get_db_item(item_id: NavItemID) -> DbNavItem: |
164
|
|
|
"""Return the menu item. |
165
|
|
|
|
166
|
|
|
Raise error if not found. |
167
|
|
|
""" |
168
|
|
|
db_item = _find_db_item(item_id) |
169
|
|
|
|
170
|
|
|
if db_item is None: |
171
|
|
|
raise ValueError('Unknown item ID') |
172
|
|
|
|
173
|
|
|
return db_item |
174
|
|
|
|
175
|
|
|
|
176
|
1 |
|
def get_items_for_menu_id(menu_id: NavMenuID) -> list[NavItem]: |
177
|
|
|
"""Return the items of a menu. |
178
|
|
|
|
179
|
|
|
An empty list is returned if the menu does not exist, is hidden, or |
180
|
|
|
contains no visible items. |
181
|
|
|
""" |
182
|
|
|
db_items = db.session.scalars( |
183
|
|
|
select(DbNavItem) |
184
|
|
|
.join(DbNavMenu) |
185
|
|
|
.filter(DbNavMenu.id == menu_id) |
186
|
|
|
.filter(DbNavMenu.hidden == False) # noqa: E712 |
187
|
|
|
) |
188
|
|
|
|
189
|
|
|
return [_db_entity_to_item(db_item) for db_item in db_items] |
190
|
|
|
|
191
|
|
|
|
192
|
1 |
|
def get_items_for_menu( |
193
|
|
|
site_id: SiteID, name: str, language_code: str |
194
|
|
|
) -> list[NavItem]: |
195
|
|
|
"""Return the items of a menu. |
196
|
|
|
|
197
|
|
|
An empty list is returned if the menu does not exist, is hidden, or |
198
|
|
|
contains no visible items. |
199
|
|
|
""" |
200
|
|
|
db_items = db.session.scalars( |
201
|
|
|
select(DbNavItem) |
202
|
|
|
.join(DbNavMenu) |
203
|
|
|
.filter(DbNavMenu.site_id == site_id) |
204
|
|
|
.filter(DbNavMenu.name == name) |
205
|
|
|
.filter(DbNavMenu.language_code == language_code) |
206
|
|
|
.filter(DbNavMenu.hidden == False) # noqa: E712 |
207
|
|
|
) |
208
|
|
|
|
209
|
|
|
return [_db_entity_to_item(db_item) for db_item in db_items] |
210
|
|
|
|
211
|
|
|
|
212
|
1 |
|
def move_item_up(item_id: NavItemID) -> NavItem: |
213
|
|
|
"""Move a menu item upwards by one position.""" |
214
|
|
|
item = _get_db_item(item_id) |
215
|
|
|
|
216
|
|
|
item_list = item.menu.items |
217
|
|
|
|
218
|
|
|
if item.position == 1: |
219
|
|
|
raise ValueError('Item is already at the top.') |
220
|
|
|
|
221
|
|
|
popped_item = item_list.pop(item.position - 1) |
222
|
|
|
item_list.insert(popped_item.position - 2, popped_item) |
223
|
|
|
|
224
|
|
|
db.session.commit() |
225
|
|
|
|
226
|
|
|
return _db_entity_to_item(item) |
227
|
|
|
|
228
|
|
|
|
229
|
1 |
|
def move_item_down(item_id: NavItemID) -> NavItem: |
230
|
|
|
"""Move a menu item downwards by one position.""" |
231
|
|
|
item = _get_db_item(item_id) |
232
|
|
|
|
233
|
|
|
item_list = item.menu.items |
234
|
|
|
|
235
|
|
|
if item.position == len(item_list): |
236
|
|
|
raise ValueError('Item is already at the bottom.') |
237
|
|
|
|
238
|
|
|
popped_item = item_list.pop(item.position - 1) |
239
|
|
|
item_list.insert(popped_item.position, popped_item) |
240
|
|
|
|
241
|
|
|
db.session.commit() |
242
|
|
|
|
243
|
|
|
return _db_entity_to_item(item) |
244
|
|
|
|
245
|
|
|
|
246
|
1 |
|
def _db_entity_to_menu(db_menu: DbNavMenu) -> NavMenu: |
247
|
|
|
return NavMenu( |
248
|
|
|
id=db_menu.id, |
249
|
|
|
site_id=db_menu.site_id, |
250
|
|
|
name=db_menu.name, |
251
|
|
|
language_code=db_menu.language_code, |
252
|
|
|
hidden=db_menu.hidden, |
253
|
|
|
) |
254
|
|
|
|
255
|
|
|
|
256
|
1 |
|
def _db_entity_to_item(db_item: DbNavItem) -> NavItem: |
257
|
|
|
return NavItem( |
258
|
|
|
id=db_item.id, |
259
|
|
|
menu_id=db_item.menu_id, |
260
|
|
|
position=db_item.position, |
261
|
|
|
target_type=db_item.target_type, |
262
|
|
|
target=db_item.target, |
263
|
|
|
label=db_item.label, |
264
|
|
|
current_page_id=db_item.current_page_id, |
265
|
|
|
hidden=db_item.hidden, |
266
|
|
|
) |
267
|
|
|
|
268
|
|
|
|
269
|
1 |
|
def _db_entity_to_menu_aggregate( |
270
|
|
|
db_menu: DbNavMenu, db_items: Iterable[DbNavItem] |
271
|
|
|
) -> NavMenuAggregate: |
272
|
|
|
menu = _db_entity_to_menu(db_menu) |
273
|
|
|
items = [_db_entity_to_item(db_item) for db_item in db_items] |
274
|
|
|
|
275
|
|
|
return NavMenuAggregate( |
276
|
|
|
id=menu.id, |
277
|
|
|
site_id=menu.site_id, |
278
|
|
|
name=menu.name, |
279
|
|
|
language_code=menu.language_code, |
280
|
|
|
hidden=menu.hidden, |
281
|
|
|
items=items, |
282
|
|
|
) |
283
|
|
|
|