Passed
Push — main ( 26e6c3...305208 )
by Jochen
04:08
created

set_nav_menu_form()   A

Complexity

Conditions 2

Size

Total Lines 16
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2.686

Importance

Changes 0
Metric Value
cc 2
eloc 12
nop 2
dl 0
loc 16
ccs 4
cts 9
cp 0.4444
crap 2.686
rs 9.8
c 0
b 0
f 0
1
"""
2
byceps.blueprints.admin.page.views
3
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
5
:Copyright: 2014-2023 Jochen Kupperschmidt
6
:License: Revised BSD (see `LICENSE` file for details)
7
"""
8
9 1
from typing import Optional
10 1
from flask import abort, g, request, url_for
11 1
from flask_babel import format_datetime, gettext
12
13 1
from ....services.page import page_service
14 1
from ....services.page.transfer.models import Page, PageVersion, PageVersionID
15 1
from ....services.site import site_service
16 1
from ....services.site.transfer.models import Site, SiteID
17 1
from ....services.site_navigation import site_navigation_service
18 1
from ....services.text_diff import text_diff_service
19 1
from ....services.user import user_service
20 1
from ....signals import page as page_signals
21 1
from ....util.framework.blueprint import create_blueprint
22 1
from ....util.framework.flash import flash_error, flash_success
23 1
from ....util.framework.templating import templated
24 1
from ....util.iterables import pairwise
25 1
from ....util.views import (
26
    permission_required,
27
    redirect_to,
28
    respond_no_content_with_location,
29
)
30
31 1
from ...site.page.templating import build_template_context
32
33 1
from .forms import CreateForm, SetNavMenuForm, UpdateForm
34
35
36 1
blueprint = create_blueprint('page_admin', __name__)
37
38
39 1
@blueprint.get('/for_site/<site_id>')
40 1
@permission_required('page.view')
41 1
@templated
42 1
def index_for_site(site_id):
43
    """List pages for that site."""
44 1
    site = _get_site(site_id)
45
46 1
    pages = page_service.get_pages_for_site_with_current_versions(site.id)
47
48 1
    user_ids = {page.current_version.creator_id for page in pages}
49 1
    users = user_service.get_users(user_ids, include_avatars=True)
50 1
    users_by_id = user_service.index_users_by_id(users)
51
52 1
    return {
53
        'pages': pages,
54
        'users_by_id': users_by_id,
55
        'site': site,
56
    }
57
58
59 1
@blueprint.get('/pages/<uuid:page_id>/current_version')
60 1
@permission_required('page.view')
61 1
def view_current_version(page_id):
62
    """Show the current version of the page."""
63 1
    current_version_id = page_service.find_current_version_id(page_id)
64
65 1
    if current_version_id is None:
66
        abort(404)
67
68 1
    return view_version(current_version_id)
69
70
71 1
@blueprint.get('/versions/<uuid:version_id>')
72 1
@permission_required('page.view_history')
73 1
@templated
74 1
def view_version(version_id):
75
    """Show the page with the given id."""
76 1
    version = _get_version(version_id)
77
78 1
    page = page_service.get_page(version.page_id)
79 1
    site = site_service.get_site(page.site_id)
80 1
    creator = user_service.get_user(version.creator_id, include_avatar=True)
81 1
    is_current_version = page_service.is_current_version(page.id, version.id)
82
83 1
    if page.nav_menu_id:
84
        nav_menu = site_navigation_service.get_menu(page.nav_menu_id)
85
    else:
86 1
        nav_menu = None
87
88 1
    return {
89
        'page': page,
90
        'site': site,
91
        'version': version,
92
        'creator': creator,
93
        'is_current_version': is_current_version,
94
        'nav_menu': nav_menu,
95
    }
96
97
98 1
@blueprint.get('/versions/<uuid:version_id>/preview')
99 1
@permission_required('page.view_history')
100 1
@templated
101 1
def view_version_preview(version_id):
102
    """Show a preview of the page version."""
103
    version = _get_version(version_id)
104
105
    try:
106
        template_context = build_template_context(
107
            version.title, version.head, version.body
108
        )
109
110
        return {
111
            'title': template_context['page_title'],
112
            'head': template_context['head'],
113
            'body': template_context['body'],
114
            'error_occurred': False,
115
        }
116
    except Exception as e:
117
        return {
118
            'error_occurred': True,
119
            'error_message': str(e),
120
        }
121
122
123 1
@blueprint.get('/pages/<uuid:page_id>/history')
124 1
@permission_required('page.view_history')
125 1
@templated
126 1
def history(page_id):
127
    """Show index of page versions."""
128 1
    page = _get_page(page_id)
129
130 1
    versions = page_service.get_versions(page.id)
131 1
    versions_pairwise = list(pairwise(versions + [None]))
132
133 1
    user_ids = {version.creator_id for version in versions}
134 1
    users = user_service.get_users(user_ids, include_avatars=True)
135 1
    users_by_id = user_service.index_users_by_id(users)
136
137 1
    site = site_service.get_site(page.site_id)
138
139 1
    return {
140
        'page': page,
141
        'versions_pairwise': versions_pairwise,
142
        'users_by_id': users_by_id,
143
        'site': site,
144
    }
145
146
147 1
@blueprint.get(
148
    '/versions/<uuid:from_version_id>/compare_to/<uuid:to_version_id>'
149
)
150 1
@permission_required('page.view_history')
151 1
@templated
152 1
def compare_versions(from_version_id, to_version_id):
153
    """Show the difference between two versions."""
154 1
    from_version = _get_version(from_version_id)
155 1
    to_version = _get_version(to_version_id)
156
157 1
    if from_version.page_id != to_version.page_id:
158
        abort(400, 'The versions do not belong to the same page.')
159
160 1
    html_diff_title = _create_html_diff(from_version, to_version, 'title')
161 1
    html_diff_head = _create_html_diff(from_version, to_version, 'head')
162 1
    html_diff_body = _create_html_diff(from_version, to_version, 'body')
163
164 1
    page = page_service.get_page(from_version.page_id)
165 1
    site = site_service.get_site(page.site_id)
166
167 1
    return {
168
        'page': page,
169
        'diff_title': html_diff_title,
170
        'diff_head': html_diff_head,
171
        'diff_body': html_diff_body,
172
        'site': site,
173
    }
174
175
176 1
def _create_html_diff(
177
    from_version: PageVersion,
178
    to_version: PageVersion,
179
    attribute_name: str,
180
) -> Optional[str]:
181
    """Create an HTML diff between the named attribute's value of each
182
    of the two versions.
183
    """
184 1
    from_description = format_datetime(from_version.created_at)
185 1
    to_description = format_datetime(to_version.created_at)
186
187 1
    from_text = getattr(from_version, attribute_name)
188 1
    to_text = getattr(to_version, attribute_name)
189
190 1
    return text_diff_service.create_html_diff(
191
        from_text, to_text, from_description, to_description
192
    )
193
194
195 1
@blueprint.get('/for_site/<site_id>/create')
196 1
@permission_required('page.create')
197 1
@templated
198 1
def create_form(site_id, erroneous_form=None):
199
    """Show form to create a page."""
200 1
    site = _get_site(site_id)
201
202 1
    form = erroneous_form if erroneous_form else CreateForm()
203 1
    form.set_language_code_choices()
204
205 1
    return {
206
        'form': form,
207
        'site': site,
208
    }
209
210
211 1
@blueprint.post('/for_site/<site_id>')
212 1
@permission_required('page.create')
213 1
def create(site_id):
214
    """Create a page."""
215
    site = _get_site(site_id)
216
217
    form = CreateForm(request.form)
218
    form.set_language_code_choices()
219
220
    if not form.validate():
221
        return create_form(site.id, form)
222
223
    name = form.name.data.strip().lower()
224
    language_code = form.language_code.data
225
    url_path = form.url_path.data.strip()
226
    creator = g.user
227
    title = form.title.data.strip()
228
    head = form.head.data.strip()
229
    body = form.body.data.strip()
230
231
    version, event = page_service.create_page(
232
        site.id,
233
        name,
234
        language_code,
235
        url_path,
236
        creator.id,
237
        title,
238
        body,
239
        head=head,
240
    )
241
242
    flash_success(gettext('Page has been created.'))
243
244
    page_signals.page_created.send(None, event=event)
245
246
    return redirect_to('.view_version', version_id=version.id)
247
248
249 1
@blueprint.get('/pages/<uuid:page_id>/update')
250 1
@permission_required('page.update')
251 1
@templated
252 1
def update_form(page_id, erroneous_form=None):
253
    """Show form to update a page."""
254
    page = _get_page(page_id)
255
256
    current_version_id = page_service.find_current_version_id(page.id)
257
    page_aggregate = page_service.find_page_aggregate(current_version_id)
258
259
    form = erroneous_form if erroneous_form else UpdateForm(obj=page_aggregate)
260
    form.set_language_code_choices()
261
262
    site = site_service.get_site(page.site_id)
263
264
    return {
265
        'form': form,
266
        'page': page,
267
        'site': site,
268
    }
269
270
271 1
@blueprint.post('/pages/<uuid:page_id>')
272 1
@permission_required('page.update')
273 1
def update(page_id):
274
    """Update a page."""
275
    page = _get_page(page_id)
276
277
    form = UpdateForm(request.form)
278
    form.set_language_code_choices()
279
280
    if not form.validate():
281
        return update_form(page.id, form)
282
283
    language_code = form.language_code.data
284
    url_path = form.url_path.data.strip()
285
    creator = g.user
286
    title = form.title.data.strip()
287
    head = form.head.data.strip()
288
    body = form.body.data.strip()
289
290
    version, event = page_service.update_page(
291
        page.id,
292
        language_code,
293
        url_path,
294
        creator.id,
295
        title,
296
        head,
297
        body,
298
    )
299
300
    flash_success(gettext('Page has been updated.'))
301
302
    page_signals.page_updated.send(None, event=event)
303
304
    return redirect_to('.view_version', version_id=version.id)
305
306
307 1
@blueprint.delete('/pages/<uuid:page_id>')
308 1
@permission_required('page.delete')
309 1
@respond_no_content_with_location
310 1
def delete(page_id):
311
    """Delete a page."""
312 1
    page = _get_page(page_id)
313
314 1
    page_name = page.name
315 1
    site_id = page.site_id
316
317 1
    success, event = page_service.delete_page(page.id, initiator_id=g.user.id)
318
319 1
    if not success:
320
        flash_error(
321
            gettext('Page "%(name)s" could not be deleted.', name=page_name)
322
        )
323
        return url_for('.view_current_version', page_id=page.id)
324
325 1
    flash_success(gettext('Page "%(name)s" has been deleted.', name=page_name))
326
327 1
    page_signals.page_deleted.send(None, event=event)
328
329 1
    return url_for('.index_for_site', site_id=site_id)
330
331
332 1
@blueprint.get('/pages/<uuid:page_id>/set_nav_menu')
333 1
@permission_required('page.update')
334 1
@templated
335 1
def set_nav_menu_form(page_id, erroneous_form=None):
336
    """Show form to set navigation menu for a page."""
337
    page = _get_page(page_id)
338
339
    site = site_service.get_site(page.site_id)
340
341
    form = erroneous_form if erroneous_form else SetNavMenuForm(obj=page)
342
    form.set_nav_menu_choices(site.id)
343
344
    return {
345
        'form': form,
346
        'page': page,
347
        'site': site,
348
    }
349
350
351 1
@blueprint.post('/pages/<uuid:page_id>/set_nav_menu')
352 1
@permission_required('page.update')
353 1
def set_nav_menu(page_id):
354
    """Set navigation menu for a page."""
355
    page = _get_page(page_id)
356
357
    site = site_service.get_site(page.site_id)
358
359
    form = SetNavMenuForm(request.form)
360
    form.set_nav_menu_choices(site.id)
361
362
    if not form.validate():
363
        return set_nav_menu_form(page.id, form)
364
365
    nav_menu_id = form.nav_menu_id.data or None
366
367
    page_service.set_nav_menu_id(page.id, nav_menu_id)
368
369
    flash_success(gettext('Page has been updated.'))
370
371
    return redirect_to('.view_current_version', page_id=page.id)
372
373
374 1
def _get_site(site_id) -> Site:
375 1
    site = site_service.find_site(SiteID(site_id))
376
377 1
    if site is None:
378
        abort(404)
379
380 1
    return site
381
382
383 1
def _get_page(page_id) -> Page:
384 1
    page = page_service.find_page(page_id)
385
386 1
    if page is None:
387
        abort(404)
388
389 1
    return page
390
391
392 1
def _get_version(version_id: PageVersionID) -> PageVersion:
393 1
    version = page_service.find_version(version_id)
394
395 1
    if version is None:
396
        abort(404)
397
398
    return version
399