byceps.blueprints.admin.shop.article.views   F
last analyzed

Complexity

Total Complexity 65

Size/Duplication

Total Lines 852
Duplicated Lines 17.02 %

Test Coverage

Coverage 28.22%

Importance

Changes 0
Metric Value
eloc 552
dl 145
loc 852
ccs 103
cts 365
cp 0.2822
rs 3.2
c 0
b 0
f 0
wmc 65

24 Functions

Rating   Name   Duplication   Size   Complexity  
A attachment_create_form() 0 25 2
B update() 0 55 6
A _get_article_or_404() 0 7 2
A create_ticket_form() 32 32 2
A action_create_form_for_badge_awarding() 0 22 2
A action_create_form_for_tickets_creation() 0 22 2
A action_create_form_for_ticket_bundles_creation() 0 24 2
D create() 0 103 11
B view() 0 44 5
A attachment_create() 0 31 2
A view_ordered() 0 43 1
A create_number_sequence() 0 31 3
A update_form() 0 29 4
A action_create_for_badge_awarding() 0 24 2
A _get_article_type_or_400() 0 5 2
A _get_shop_or_404() 0 7 2
A create_number_sequence_form() 0 17 2
A attachment_remove() 0 20 2
A action_remove() 0 13 2
A create_ticket_bundle_form() 32 32 2
A action_create_for_tickets_creation() 25 25 2
A action_create_for_ticket_bundles_creation() 27 27 2
A create_form() 29 29 2
A index_for_shop() 0 42 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like byceps.blueprints.admin.shop.article.views often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
"""
2
byceps.blueprints.admin.shop.article.views
3
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
5
:Copyright: 2014-2022 Jochen Kupperschmidt
6
:License: Revised BSD (see `LICENSE` file for details)
7
"""
8
9 1
import dataclasses
10 1
from datetime import datetime
11 1
from decimal import Decimal
12
13 1
from flask import abort, request
14 1
from flask_babel import gettext, to_user_timezone, to_utc
15
16 1
from .....services.brand import brand_service
17 1
from .....services.party import party_service
18 1
from .....services.shop.article import article_sequence_service, article_service
19 1
from .....services.shop.article.transfer.models import (
20
    Article,
21
    ArticleType,
22
    get_article_type_label,
23
)
24 1
from .....services.shop.order import (
25
    action_registry_service,
26
    action_service,
27
    ordered_articles_service,
28
    order_service,
29
)
30 1
from .....services.shop.order.transfer.order import PaymentState
31 1
from .....services.shop.shop import shop_service
32 1
from .....services.ticketing import ticket_category_service
33 1
from .....services.user import user_service
34 1
from .....services.user_badge import user_badge_service
35 1
from .....util.framework.blueprint import create_blueprint
36 1
from .....util.framework.flash import flash_error, flash_success
37 1
from .....util.framework.templating import templated
38 1
from .....util.views import permission_required, redirect_to, respond_no_content
39
40 1
from .forms import (
41
    ArticleCreateForm,
42
    ArticleUpdateForm,
43
    ArticleAttachmentCreateForm,
44
    ArticleNumberSequenceCreateForm,
45
    RegisterBadgeAwardingActionForm,
46
    RegisterTicketBundlesCreationActionForm,
47
    RegisterTicketsCreationActionForm,
48
    TicketArticleCreateForm,
49
    TicketBundleArticleCreateForm,
50
)
51
52
53 1
blueprint = create_blueprint('shop_article_admin', __name__)
54
55
56 1
TAX_RATE_DISPLAY_FACTOR = Decimal('100')
57
58
59 1
@blueprint.get('/for_shop/<shop_id>', defaults={'page': 1})
60 1
@blueprint.get('/for_shop/<shop_id>/pages/<int:page>')
61 1
@permission_required('shop_article.view')
62 1
@templated
63 1
def index_for_shop(shop_id, page):
64
    """List articles for that shop."""
65
    shop = _get_shop_or_404(shop_id)
66
67
    brand = brand_service.get_brand(shop.brand_id)
68
69
    per_page = request.args.get('per_page', type=int, default=15)
70
71
    search_term = request.args.get('search_term', default='').strip()
72
73
    articles = article_service.get_articles_for_shop_paginated(
74
        shop.id,
75
        page,
76
        per_page,
77
        search_term=search_term,
78
    )
79
80
    # Inherit order of enum members.
81
    article_type_labels_by_type = {
82
        type_: get_article_type_label(type_) for type_ in ArticleType
83
    }
84
85
    totals_by_article_number = {
86
        article.item_number: ordered_articles_service.count_ordered_articles(
87
            article.item_number
88
        )
89
        for article in articles.items
90
    }
91
92
    return {
93
        'shop': shop,
94
        'brand': brand,
95
        'articles': articles,
96
        'article_type_labels_by_type': article_type_labels_by_type,
97
        'totals_by_article_number': totals_by_article_number,
98
        'PaymentState': PaymentState,
99
        'per_page': per_page,
100
        'search_term': search_term,
101
    }
102
103
104 1
@blueprint.get('/<uuid:article_id>')
105 1
@permission_required('shop_article.view')
106 1
@templated
107 1
def view(article_id):
108
    """Show a single article."""
109
    article = article_service.find_article_with_details(article_id)
110
    if article is None:
111
        abort(404)
112
113
    shop = shop_service.get_shop(article.shop_id)
114
115
    brand = brand_service.get_brand(shop.brand_id)
116
117
    type_label = get_article_type_label(article.type_)
118
119
    if article.type_ in (ArticleType.ticket, ArticleType.ticket_bundle):
120
        ticket_category = ticket_category_service.find_category(
121
            article.type_params['ticket_category_id']
122
        )
123
        if ticket_category is not None:
124
            ticket_party = party_service.get_party(ticket_category.party_id)
125
        else:
126
            ticket_party = None
127
    else:
128
        ticket_party = None
129
        ticket_category = None
130
131
    totals = ordered_articles_service.count_ordered_articles(
132
        article.item_number
133
    )
134
135
    actions = action_service.get_actions_for_article(article.item_number)
136
    actions.sort(key=lambda a: a.payment_state.name, reverse=True)
137
138
    return {
139
        'article': article,
140
        'shop': shop,
141
        'brand': brand,
142
        'type_label': type_label,
143
        'ticket_category': ticket_category,
144
        'ticket_party': ticket_party,
145
        'totals': totals,
146
        'PaymentState': PaymentState,
147
        'actions': actions,
148
    }
149
150
151 1
@blueprint.get('/<uuid:article_id>/ordered')
152 1
@permission_required('shop_article.view')
153 1
@templated
154 1
def view_ordered(article_id):
155
    """List the people that have ordered this article, and the
156
    corresponding quantities.
157
    """
158
    article = _get_article_or_404(article_id)
159
160
    shop = shop_service.get_shop(article.shop_id)
161
162
    brand = brand_service.get_brand(shop.brand_id)
163
164
    line_items = ordered_articles_service.get_line_items_for_article(
165
        article.item_number
166
    )
167
168
    quantity_total = sum(item.quantity for item in line_items)
169
170
    order_numbers = {item.order_number for item in line_items}
171
    orders = order_service.get_orders_for_order_numbers(order_numbers)
172
    orders_by_order_numbers = {order.order_number: order for order in orders}
173
174
    user_ids = {order.placed_by_id for order in orders}
175
    users = user_service.get_users(user_ids, include_avatars=True)
176
    users_by_id = user_service.index_users_by_id(users)
177
178
    def transform(line_item):
179
        quantity = line_item.quantity
180
        order = orders_by_order_numbers[line_item.order_number]
181
        user = users_by_id[order.placed_by_id]
182
183
        return quantity, order, user
184
185
    quantities_orders_users = list(map(transform, line_items))
186
187
    return {
188
        'article': article,
189
        'shop': shop,
190
        'brand': brand,
191
        'quantity_total': quantity_total,
192
        'quantities_orders_users': quantities_orders_users,
193
        'now': datetime.utcnow(),
194
    }
195
196
197
# -------------------------------------------------------------------- #
198
# create
199
200
201 1 View Code Duplication
@blueprint.get('/for_shop/<shop_id>/create/<type>')
202 1
@permission_required('shop_article.create')
203 1
@templated
204 1
def create_form(shop_id, type, erroneous_form=None):
205
    """Show form to create an article."""
206
    shop = _get_shop_or_404(shop_id)
207
    type_ = _get_article_type_or_400(type)
208
209
    brand = brand_service.get_brand(shop.brand_id)
210
211
    article_number_sequences = (
212
        article_sequence_service.get_article_number_sequences_for_shop(shop.id)
213
    )
214
    article_number_sequence_available = bool(article_number_sequences)
215
216
    form = (
217
        erroneous_form
218
        if erroneous_form
219
        else ArticleCreateForm(price=Decimal('0.0'), tax_rate=Decimal('19.0'))
220
    )
221
    form.set_article_number_sequence_choices(article_number_sequences)
222
223
    return {
224
        'shop': shop,
225
        'brand': brand,
226
        'article_type_name': type_.name,
227
        'article_type_label': get_article_type_label(type_),
228
        'article_number_sequence_available': article_number_sequence_available,
229
        'form': form,
230
    }
231
232
233 1 View Code Duplication
@blueprint.get('/for_shop/<shop_id>/create/ticket')
234 1
@permission_required('shop_article.create')
235 1
@templated
236 1
def create_ticket_form(shop_id, erroneous_form=None):
237
    """Show form to create a ticket article."""
238
    shop = _get_shop_or_404(shop_id)
239
    type_ = ArticleType.ticket
240
241
    brand = brand_service.get_brand(shop.brand_id)
242
243
    article_number_sequences = (
244
        article_sequence_service.get_article_number_sequences_for_shop(shop.id)
245
    )
246
    article_number_sequence_available = bool(article_number_sequences)
247
248
    form = (
249
        erroneous_form
250
        if erroneous_form
251
        else TicketArticleCreateForm(
252
            price=Decimal('0.0'), tax_rate=Decimal('19.0')
253
        )
254
    )
255
    form.set_article_number_sequence_choices(article_number_sequences)
256
    form.set_ticket_category_choices(brand.id)
257
258
    return {
259
        'shop': shop,
260
        'brand': brand,
261
        'article_type_name': type_.name,
262
        'article_type_label': get_article_type_label(type_),
263
        'article_number_sequence_available': article_number_sequence_available,
264
        'form': form,
265
    }
266
267
268 1 View Code Duplication
@blueprint.get('/for_shop/<shop_id>/create/ticket_bundle')
269 1
@permission_required('shop_article.create')
270 1
@templated
271 1
def create_ticket_bundle_form(shop_id, erroneous_form=None):
272
    """Show form to create a ticket bundle article."""
273
    shop = _get_shop_or_404(shop_id)
274
    type_ = ArticleType.ticket_bundle
275
276
    brand = brand_service.get_brand(shop.brand_id)
277
278
    article_number_sequences = (
279
        article_sequence_service.get_article_number_sequences_for_shop(shop.id)
280
    )
281
    article_number_sequence_available = bool(article_number_sequences)
282
283
    form = (
284
        erroneous_form
285
        if erroneous_form
286
        else TicketBundleArticleCreateForm(
287
            price=Decimal('0.0'), tax_rate=Decimal('19.0')
288
        )
289
    )
290
    form.set_article_number_sequence_choices(article_number_sequences)
291
    form.set_ticket_category_choices(brand.id)
292
293
    return {
294
        'shop': shop,
295
        'brand': brand,
296
        'article_type_name': type_.name,
297
        'article_type_label': get_article_type_label(type_),
298
        'article_number_sequence_available': article_number_sequence_available,
299
        'form': form,
300
    }
301
302
303 1
@blueprint.post('/for_shop/<shop_id>/<type>')
304 1
@permission_required('shop_article.create')
305 1
def create(shop_id, type):
306
    """Create an article."""
307
    shop = _get_shop_or_404(shop_id)
308
    type_ = _get_article_type_or_400(type)
309
310
    if type_ == ArticleType.ticket:
311
        form = TicketArticleCreateForm(request.form)
312
    elif type_ == ArticleType.ticket_bundle:
313
        form = TicketBundleArticleCreateForm(request.form)
314
    else:
315
        form = ArticleCreateForm(request.form)
316
317
    article_number_sequences = (
318
        article_sequence_service.get_article_number_sequences_for_shop(shop.id)
319
    )
320
    if not article_number_sequences:
321
        flash_error(
322
            gettext('No article number sequences are defined for this shop.')
323
        )
324
        return create_form(shop_id, type_, form)
325
326
    form.set_article_number_sequence_choices(article_number_sequences)
327
    if type_ in (ArticleType.ticket, ArticleType.ticket_bundle):
328
        form.set_ticket_category_choices(shop.brand_id)
329
330
    if not form.validate():
331
        return create_form(shop_id, type_, form)
332
333
    article_number_sequence_id = form.article_number_sequence_id.data
334
    if not article_number_sequence_id:
335
        flash_error(gettext('No valid article number sequence was specified.'))
336
        return create_form(shop_id, type_, form)
337
338
    article_number_sequence = (
339
        article_sequence_service.get_article_number_sequence(
340
            article_number_sequence_id
341
        )
342
    )
343
    if article_number_sequence.shop_id != shop.id:
344
        flash_error(gettext('No valid article number sequence was specified.'))
345
        return create_form(shop_id, type_, form)
346
347
    try:
348
        item_number = article_sequence_service.generate_article_number(
349
            article_number_sequence.id
350
        )
351
    except article_sequence_service.ArticleNumberGenerationFailed as e:
352
        abort(500, e.message)
353
354
    description = form.description.data.strip()
355
    price = form.price.data
356
    tax_rate = form.tax_rate.data / TAX_RATE_DISPLAY_FACTOR
357
    total_quantity = form.total_quantity.data
358
    quantity = total_quantity
359
    max_quantity_per_order = form.max_quantity_per_order.data
360
361
    if type_ == ArticleType.ticket:
362
        article = article_service.create_ticket_article(
363
            shop.id,
364
            item_number,
365
            description,
366
            price,
367
            tax_rate,
368
            total_quantity,
369
            max_quantity_per_order,
370
            form.ticket_category_id.data,
371
        )
372
    elif type_ == ArticleType.ticket_bundle:
373
        article = article_service.create_ticket_bundle_article(
374
            shop.id,
375
            item_number,
376
            description,
377
            price,
378
            tax_rate,
379
            total_quantity,
380
            max_quantity_per_order,
381
            form.ticket_category_id.data,
382
            form.ticket_quantity.data,
383
        )
384
    else:
385
        processing_required = type_ == ArticleType.physical
386
387
        article = article_service.create_article(
388
            shop.id,
389
            item_number,
390
            type_,
391
            description,
392
            price,
393
            tax_rate,
394
            total_quantity,
395
            max_quantity_per_order,
396
            processing_required,
397
        )
398
399
    flash_success(
400
        gettext(
401
            'Article "%(item_number)s" has been created.',
402
            item_number=article.item_number,
403
        )
404
    )
405
    return redirect_to('.view', article_id=article.id)
406
407
408
# -------------------------------------------------------------------- #
409
# update
410
411
412 1
@blueprint.get('/<uuid:article_id>/update')
413 1
@permission_required('shop_article.update')
414 1
@templated
415 1
def update_form(article_id, erroneous_form=None):
416
    """Show form to update an article."""
417
    article = _get_article_or_404(article_id)
418
419
    shop = shop_service.get_shop(article.shop_id)
420
421
    brand = brand_service.get_brand(shop.brand_id)
422
423
    data = dataclasses.asdict(article)
424
    if article.available_from:
425
        available_from_local = to_user_timezone(article.available_from)
426
        data['available_from_date'] = available_from_local.date()
427
        data['available_from_time'] = available_from_local.time()
428
    if article.available_until:
429
        available_until_local = to_user_timezone(article.available_until)
430
        data['available_until_date'] = available_until_local.date()
431
        data['available_until_time'] = available_until_local.time()
432
433
    form = erroneous_form if erroneous_form else ArticleUpdateForm(data=data)
434
    form.tax_rate.data = article.tax_rate * TAX_RATE_DISPLAY_FACTOR
435
436
    return {
437
        'article': article,
438
        'shop': shop,
439
        'brand': brand,
440
        'form': form,
441
    }
442
443
444 1
@blueprint.post('/<uuid:article_id>')
445 1
@permission_required('shop_article.update')
446 1
def update(article_id):
447
    """Update an article."""
448
    article = _get_article_or_404(article_id)
449
450
    form = ArticleUpdateForm(request.form)
451
    if not form.validate():
452
        return update_form(article_id, form)
453
454
    description = form.description.data.strip()
455
    price = form.price.data
456
    tax_rate = form.tax_rate.data / TAX_RATE_DISPLAY_FACTOR
457
458
    if form.available_from_date.data and form.available_from_time.data:
459
        available_from_local = datetime.combine(
460
            form.available_from_date.data, form.available_from_time.data
461
        )
462
        available_from_utc = to_utc(available_from_local)
463
    else:
464
        available_from_utc = None
465
466
    if form.available_until_date.data and form.available_until_time.data:
467
        available_until_local = datetime.combine(
468
            form.available_until_date.data, form.available_until_time.data
469
        )
470
        available_until_utc = to_utc(available_until_local)
471
    else:
472
        available_until_utc = None
473
474
    total_quantity = form.total_quantity.data
475
    max_quantity_per_order = form.max_quantity_per_order.data
476
    not_directly_orderable = form.not_directly_orderable.data
477
    separate_order_required = form.separate_order_required.data
478
479
    article = article_service.update_article(
480
        article.id,
481
        description,
482
        price,
483
        tax_rate,
484
        available_from_utc,
485
        available_until_utc,
486
        total_quantity,
487
        max_quantity_per_order,
488
        not_directly_orderable,
489
        separate_order_required,
490
    )
491
492
    flash_success(
493
        gettext(
494
            'Article "%(description)s" has been updated.',
495
            description=article.description,
496
        )
497
    )
498
    return redirect_to('.view', article_id=article.id)
499
500
501
# -------------------------------------------------------------------- #
502
# article attachments
503
504
505 1
@blueprint.get('/<uuid:article_id>/attachments/create')
506 1
@permission_required('shop_article.update')
507 1
@templated
508 1
def attachment_create_form(article_id, erroneous_form=None):
509
    """Show form to attach an article to another article."""
510
    article = _get_article_or_404(article_id)
511
512
    shop = shop_service.get_shop(article.shop_id)
513
514
    brand = brand_service.get_brand(shop.brand_id)
515
516
    attachable_articles = article_service.get_attachable_articles(article.id)
517
518
    form = (
519
        erroneous_form
520
        if erroneous_form
521
        else ArticleAttachmentCreateForm(quantity=0)
522
    )
523
    form.set_article_to_attach_choices(attachable_articles)
524
525
    return {
526
        'article': article,
527
        'shop': shop,
528
        'brand': brand,
529
        'form': form,
530
    }
531
532
533 1
@blueprint.post('/<uuid:article_id>/attachments')
534 1
@permission_required('shop_article.update')
535 1
def attachment_create(article_id):
536
    """Attach an article to another article."""
537
    article = _get_article_or_404(article_id)
538
539
    attachable_articles = article_service.get_attachable_articles(article.id)
540
541
    form = ArticleAttachmentCreateForm(request.form)
542
    form.set_article_to_attach_choices(attachable_articles)
543
544
    if not form.validate():
545
        return attachment_create_form(article_id, form)
546
547
    article_to_attach_id = form.article_to_attach_id.data
548
    article_to_attach = article_service.get_article(article_to_attach_id)
549
    quantity = form.quantity.data
550
551
    article_service.attach_article(
552
        article_to_attach.item_number, quantity, article.item_number
553
    )
554
555
    flash_success(
556
        gettext(
557
            'Article "%(article_to_attach_item_number)s" has been attached %(quantity)s times to article "%(article_item_number)s".',
558
            article_to_attach_item_number=article_to_attach.item_number,
559
            quantity=quantity,
560
            article_item_number=article.item_number,
561
        )
562
    )
563
    return redirect_to('.view', article_id=article.id)
564
565
566 1
@blueprint.delete('/attachments/<uuid:article_id>')
567 1
@permission_required('shop_article.update')
568 1
@respond_no_content
569 1
def attachment_remove(article_id):
570
    """Remove the attachment link from one article to another."""
571
    attached_article = article_service.find_attached_article(article_id)
572
573
    if attached_article is None:
574
        abort(404)
575
576
    article = attached_article.article
577
    attached_to_article = attached_article.attached_to_article
578
579
    article_service.unattach_article(attached_article.id)
580
581
    flash_success(
582
        gettext(
583
            'Article "%(article_item_number)s" is no longer attached to article "%(attached_to_article_item_number)s".',
584
            article_item_number=article.item_number,
585
            attached_to_article_item_number=attached_to_article.item_number,
586
        )
587
    )
588
589
590
# -------------------------------------------------------------------- #
591
# actions
592
593
594 1
@blueprint.get('/<uuid:article_id>/actions/badge_awarding/create')
595 1
@permission_required('shop_article.update')
596 1
@templated
597 1
def action_create_form_for_badge_awarding(article_id, erroneous_form=None):
598
    """Show form to register a badge awarding action for the article."""
599
    article = _get_article_or_404(article_id)
600
601
    shop = shop_service.get_shop(article.shop_id)
602
    brand = brand_service.get_brand(shop.brand_id)
603
604
    badges = user_badge_service.get_all_badges()
605
606
    form = (
607
        erroneous_form if erroneous_form else RegisterBadgeAwardingActionForm()
608
    )
609
    form.set_badge_choices(badges)
610
611
    return {
612
        'article': article,
613
        'shop': shop,
614
        'brand': brand,
615
        'form': form,
616
    }
617
618
619 1
@blueprint.post('/<uuid:article_id>/actions/badge_awarding')
620 1
@permission_required('shop_article.update')
621 1
def action_create_for_badge_awarding(article_id):
622
    """Register a badge awarding action for the article."""
623
    article = _get_article_or_404(article_id)
624
625
    badges = user_badge_service.get_all_badges()
626
627
    form = RegisterBadgeAwardingActionForm(request.form)
628
    form.set_badge_choices(badges)
629
630
    if not form.validate():
631
        return action_create_form_for_badge_awarding(article_id, form)
632
633
    badge_id = form.badge_id.data
634
    badge = user_badge_service.get_badge(badge_id)
635
636
    action_registry_service.register_badge_awarding(
637
        article.item_number, badge.id
638
    )
639
640
    flash_success(gettext('Action has been added.'))
641
642
    return redirect_to('.view', article_id=article.id)
643
644
645 1
@blueprint.get('/<uuid:article_id>/actions/tickets_creation/create')
646 1
@permission_required('shop_article.update')
647 1
@templated
648 1
def action_create_form_for_tickets_creation(article_id, erroneous_form=None):
649
    """Show form to register a tickets creation action for the article."""
650
    article = _get_article_or_404(article_id)
651
652
    shop = shop_service.get_shop(article.shop_id)
653
    brand = brand_service.get_brand(shop.brand_id)
654
655
    form = (
656
        erroneous_form
657
        if erroneous_form
658
        else RegisterTicketsCreationActionForm()
659
    )
660
    form.set_category_choices(brand.id)
661
662
    return {
663
        'article': article,
664
        'shop': shop,
665
        'brand': brand,
666
        'form': form,
667
    }
668
669
670 1 View Code Duplication
@blueprint.post('/<uuid:article_id>/actions/tickets_creation')
671 1
@permission_required('shop_article.update')
672 1
def action_create_for_tickets_creation(article_id):
673
    """Register a tickets creation action for the article."""
674
    article = _get_article_or_404(article_id)
675
676
    shop = shop_service.get_shop(article.shop_id)
677
    brand = brand_service.get_brand(shop.brand_id)
678
679
    form = RegisterTicketsCreationActionForm(request.form)
680
    form.set_category_choices(brand.id)
681
682
    if not form.validate():
683
        return action_create_form_for_tickets_creation(article_id, form)
684
685
    category_id = form.category_id.data
686
    category = ticket_category_service.get_category(category_id)
687
688
    action_registry_service.register_tickets_creation(
689
        article.item_number, category.id
690
    )
691
692
    flash_success(gettext('Action has been added.'))
693
694
    return redirect_to('.view', article_id=article.id)
695
696
697 1
@blueprint.get('/<uuid:article_id>/actions/ticket_bundles_creation/create')
698 1
@permission_required('shop_article.update')
699 1
@templated
700 1
def action_create_form_for_ticket_bundles_creation(
701
    article_id, erroneous_form=None
702
):
703
    """Show form to register a ticket bundles creation action for the article."""
704
    article = _get_article_or_404(article_id)
705
706
    shop = shop_service.get_shop(article.shop_id)
707
    brand = brand_service.get_brand(shop.brand_id)
708
709
    form = (
710
        erroneous_form
711
        if erroneous_form
712
        else RegisterTicketBundlesCreationActionForm()
713
    )
714
    form.set_category_choices(brand.id)
715
716
    return {
717
        'article': article,
718
        'shop': shop,
719
        'brand': brand,
720
        'form': form,
721
    }
722
723
724 1 View Code Duplication
@blueprint.post('/<uuid:article_id>/actions/ticket_bundles_creation')
725 1
@permission_required('shop_article.update')
726 1
def action_create_for_ticket_bundles_creation(article_id):
727
    """Register a ticket bundles creation action for the article."""
728
    article = _get_article_or_404(article_id)
729
730
    shop = shop_service.get_shop(article.shop_id)
731
    brand = brand_service.get_brand(shop.brand_id)
732
733
    form = RegisterTicketBundlesCreationActionForm(request.form)
734
    form.set_category_choices(brand.id)
735
736
    if not form.validate():
737
        return action_create_form_for_ticket_bundles_creation(article_id, form)
738
739
    category_id = form.category_id.data
740
    category = ticket_category_service.get_category(category_id)
741
742
    ticket_quantity = form.ticket_quantity.data
743
744
    action_registry_service.register_ticket_bundles_creation(
745
        article.item_number, category.id, ticket_quantity
746
    )
747
748
    flash_success(gettext('Action has been added.'))
749
750
    return redirect_to('.view', article_id=article.id)
751
752
753 1
@blueprint.delete('/actions/<uuid:action_id>')
754 1
@permission_required('shop_article.update')
755 1
@respond_no_content
756 1
def action_remove(action_id):
757
    """Remove the action from the article."""
758
    action = action_service.find_action(action_id)
759
760
    if action is None:
761
        abort(404)
762
763
    action_service.delete_action(action.id)
764
765
    flash_success(gettext('Action has been removed.'))
766
767
768
# -------------------------------------------------------------------- #
769
# article number sequences
770
771
772 1
@blueprint.get('/number_sequences/for_shop/<shop_id>/create')
773 1
@permission_required('shop_article.create')
774 1
@templated
775 1
def create_number_sequence_form(shop_id, erroneous_form=None):
776
    """Show form to create an article number sequence."""
777
    shop = _get_shop_or_404(shop_id)
778
779
    brand = brand_service.get_brand(shop.brand_id)
780
781
    form = (
782
        erroneous_form if erroneous_form else ArticleNumberSequenceCreateForm()
783
    )
784
785
    return {
786
        'shop': shop,
787
        'brand': brand,
788
        'form': form,
789
    }
790
791
792 1
@blueprint.post('/number_sequences/for_shop/<shop_id>')
793 1
@permission_required('shop_article.create')
794 1
def create_number_sequence(shop_id):
795
    """Create an article number sequence."""
796
    shop = _get_shop_or_404(shop_id)
797
798
    form = ArticleNumberSequenceCreateForm(request.form)
799
    if not form.validate():
800
        return create_number_sequence_form(shop_id, form)
801
802
    prefix = form.prefix.data.strip()
803
804
    try:
805
        article_sequence_service.create_article_number_sequence(shop.id, prefix)
806
    except article_sequence_service.ArticleNumberSequenceCreationFailed:
807
        flash_error(
808
            gettext(
809
                'Article number sequence could not be created. '
810
                'Is prefix "%(prefix)s" already defined?',
811
                prefix=prefix,
812
            )
813
        )
814
        return create_number_sequence_form(shop.id, form)
815
816
    flash_success(
817
        gettext(
818
            'Article number sequence with prefix "%(prefix)s" has been created.',
819
            prefix=prefix,
820
        )
821
    )
822
    return redirect_to('.index_for_shop', shop_id=shop.id)
823
824
825
# -------------------------------------------------------------------- #
826
# helpers
827
828
829 1
def _get_shop_or_404(shop_id):
830
    shop = shop_service.find_shop(shop_id)
831
832
    if shop is None:
833
        abort(404)
834
835
    return shop
836
837
838 1
def _get_article_or_404(article_id) -> Article:
839
    article = article_service.find_article(article_id)
840
841
    if article is None:
842
        abort(404)
843
844
    return article
845
846
847 1
def _get_article_type_or_400(value: str) -> ArticleType:
848
    try:
849
        return ArticleType[value]
850
    except KeyError:
851
        abort(400, 'Unknown article type')
852