order_form()   B
last analyzed

Complexity

Conditions 5

Size

Total Lines 36
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 20.741

Importance

Changes 0
Metric Value
cc 5
eloc 25
nop 1
dl 0
loc 36
ccs 3
cts 21
cp 0.1429
crap 20.741
rs 8.8133
c 0
b 0
f 0
1
"""
2
byceps.blueprints.site.shop.order.views
3
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
5
:Copyright: 2006-2021 Jochen Kupperschmidt
6
:License: Revised BSD (see `LICENSE` file for details)
7
"""
8
9 1
from decimal import Decimal
10
11 1
from flask import abort, g, request
12 1
from flask_babel import gettext
13
14 1
from .....services.country import service as country_service
15 1
from .....services.shop.article import service as article_service
16 1
from .....services.shop.article.models.compilation import ArticleCompilation
17 1
from .....services.shop.cart.models import Cart
18 1
from .....services.shop.order.email import service as order_email_service
19 1
from .....services.shop.order import service as order_service
20 1
from .....services.shop.shop import service as shop_service
21 1
from .....services.shop.storefront import service as storefront_service
22 1
from .....services.site import service as site_service
23 1
from .....services.user import service as user_service
24 1
from .....signals import shop as shop_signals
25 1
from .....util.framework.blueprint import create_blueprint
26 1
from .....util.framework.flash import flash_error, flash_notice, flash_success
27 1
from .....util.framework.templating import templated
28 1
from .....util.views import login_required, redirect_to
29
30 1
from .forms import assemble_articles_order_form, OrderForm
31
32
33 1
blueprint = create_blueprint('shop_order', __name__)
34
35
36 1
@blueprint.add_app_template_filter
37 1
def tax_rate_as_percentage(tax_rate) -> str:
38
    # Keep a digit after the decimal point in case
39
    # the tax rate is a fractional number.
40
    percentage = (tax_rate * 100).quantize(Decimal('.0'))
41
    return str(percentage).replace('.', ',')
42
43
44 1
@blueprint.get('/order')
45 1
@templated
46 1
def order_form(erroneous_form=None):
47
    """Show a form to order articles."""
48
    storefront = _get_storefront_or_404()
49
    shop = shop_service.get_shop(storefront.shop_id)
50
51
    if storefront.closed:
52
        flash_notice(gettext('The shop is closed.'))
53
        return {'article_compilation': None}
54
55
    article_compilation = (
56
        article_service.get_article_compilation_for_orderable_articles(shop.id)
57
    )
58
59
    if article_compilation.is_empty():
60
        flash_error(gettext('No articles are available.'))
61
        return {'article_compilation': None}
62
63
    if not g.user.authenticated:
64
        return list_articles(article_compilation)
65
66
    detail = user_service.get_detail(g.user.id)
67
68
    if erroneous_form:
69
        form = erroneous_form
70
    else:
71
        ArticlesOrderForm = assemble_articles_order_form(article_compilation)
72
        form = ArticlesOrderForm(obj=detail)
73
74
    country_names = country_service.get_country_names()
75
76
    return {
77
        'form': form,
78
        'country_names': country_names,
79
        'article_compilation': article_compilation,
80
    }
81
82
83
# No route registered. Intended to be called from another view function.
84 1
@templated
85
def list_articles(article_compilation):
86
    """List articles for anonymous users to view."""
87
    return {
88
        'article_compilation': article_compilation,
89
    }
90
91
92 1
@blueprint.post('/order')
93 1
@login_required
94
def order():
95
    """Order articles."""
96 1
    storefront = _get_storefront_or_404()
97 1
    shop = shop_service.get_shop(storefront.shop_id)
98
99 1
    if storefront.closed:
100
        flash_notice(gettext('The shop is closed.'))
101
        return order_form()
102
103 1
    article_compilation = (
104
        article_service.get_article_compilation_for_orderable_articles(shop.id)
105
    )
106
107 1
    if article_compilation.is_empty():
108
        flash_error(gettext('No articles are available.'))
109
        return order_form()
110
111 1
    ArticlesOrderForm = assemble_articles_order_form(article_compilation)
112 1
    form = ArticlesOrderForm(request.form)
113
114 1
    if not form.validate():
115
        return order_form(form)
116
117 1
    cart = form.get_cart(article_compilation)
118
119 1
    if cart.is_empty():
120
        flash_error(gettext('No articles have been selected.'))
121
        return order_form(form)
122
123 1
    orderer = form.get_orderer(g.user.id)
124
125 1
    try:
126 1
        order = _place_order(storefront.id, orderer, cart)
127
    except order_service.OrderFailed:
128
        flash_error(gettext('Placing the order has failed.'))
129
        return order_form(form)
130
131 1
    _flash_order_success(order)
132
133 1
    return redirect_to('shop_orders.view', order_id=order.id)
134
135
136 1
@blueprint.get('/order_single/<uuid:article_id>')
137 1
@login_required
138 1
@templated
139 1
def order_single_form(article_id, erroneous_form=None):
140
    """Show a form to order a single article."""
141
    article = _get_article_or_404(article_id)
142
143
    storefront = _get_storefront_or_404()
144
    shop = shop_service.get_shop(storefront.shop_id)
145
146
    user = g.user
147
    detail = user_service.get_detail(user.id)
148
149
    form = erroneous_form if erroneous_form else OrderForm(obj=detail)
150
151
    if storefront.closed:
152
        flash_notice(gettext('The shop is closed.'))
153
        return {
154
            'form': form,
155
            'article': None,
156
        }
157
158
    article_compilation = (
159
        article_service.get_article_compilation_for_single_article(
160
            article.id, fixed_quantity=1
161
        )
162
    )
163
164
    country_names = country_service.get_country_names()
165
166
    if article.not_directly_orderable:
167
        flash_error(gettext('The article cannot be ordered directly.'))
168
        return {
169
            'form': form,
170
            'article': None,
171
        }
172
173
    if order_service.has_user_placed_orders(user.id, shop.id):
174
        flash_error(gettext('You cannot place another order.'))
175
        return {
176
            'form': form,
177
            'article': None,
178
        }
179
180
    if article.quantity < 1 or not article_service.is_article_available_now(
181
        article
182
    ):
183
        flash_error(gettext('The article is not available.'))
184
        return {
185
            'form': form,
186
            'article': None,
187
        }
188
189
    return {
190
        'form': form,
191
        'country_names': country_names,
192
        'article': article,
193
        'article_compilation': article_compilation,
194
    }
195
196
197 1
@blueprint.post('/order_single/<uuid:article_id>')
198 1
@login_required
199
def order_single(article_id):
200
    """Order a single article."""
201 1
    article = _get_article_or_404(article_id)
202 1
    quantity = 1
203
204 1
    storefront = _get_storefront_or_404()
205 1
    shop = shop_service.get_shop(storefront.shop_id)
206
207 1
    if storefront.closed:
208
        flash_notice(gettext('The shop is closed.'))
209
        return order_single_form(article.id)
210
211 1
    if article.not_directly_orderable:
212
        flash_error(gettext('The article cannot be ordered directly.'))
213
        return order_single_form(article.id)
214
215 1
    article_compilation = (
216
        article_service.get_article_compilation_for_single_article(
217
            article.id, fixed_quantity=quantity
218
        )
219
    )
220
221 1
    user = g.user
222
223 1
    if order_service.has_user_placed_orders(user.id, shop.id):
224
        flash_error(gettext('You cannot place another order.'))
225
        return order_single_form(article.id)
226
227 1
    if article.quantity < 1 or not article_service.is_article_available_now(
228
        article
229
    ):
230
        flash_error(gettext('The article is not available.'))
231
        return order_single_form(article.id)
232
233 1
    form = OrderForm(request.form)
234 1
    if not form.validate():
235
        return order_single_form(article.id, form)
236
237 1
    orderer = form.get_orderer(user.id)
238
239 1
    cart = _create_cart_from_article_compilation(article_compilation)
240
241 1
    try:
242 1
        order = _place_order(storefront.id, orderer, cart)
243
    except order_service.OrderFailed:
244
        flash_error(gettext('Placing the order has failed.'))
245
        return order_form(form)
246
247 1
    _flash_order_success(order)
248
249 1
    return redirect_to('shop_orders.view', order_id=order.id)
250
251
252 1
def _get_storefront_or_404():
253 1
    site = site_service.get_site(g.site_id)
254 1
    storefront_id = site.storefront_id
255 1
    if storefront_id is None:
256
        abort(404)
257
258 1
    return storefront_service.get_storefront(storefront_id)
259
260
261 1
def _get_article_or_404(article_id):
262 1
    article = article_service.find_db_article(article_id)
263
264 1
    if article is None:
265
        abort(404)
266
267 1
    return article
268
269
270 1
def _create_cart_from_article_compilation(
271
    article_compilation: ArticleCompilation,
272
) -> Cart:
273 1
    cart = Cart()
274
275 1
    for item in article_compilation:
276 1
        cart.add_item(item.article, item.fixed_quantity)
277
278 1
    return cart
279
280
281 1
def _place_order(storefront_id, orderer, cart):
282 1
    order, event = order_service.place_order(storefront_id, orderer, cart)
283
284 1
    order_email_service.send_email_for_incoming_order_to_orderer(order.id)
285
286 1
    shop_signals.order_placed.send(None, event=event)
287
288 1
    return order
289
290
291 1
def _flash_order_success(order):
292 1
    flash_success(
293
        gettext(
294
            'Your order <strong>%(order_number)s</strong> has been placed. '
295
            'Thank you!',
296
            order_number=order.order_number,
297
        ),
298
        text_is_safe=True,
299
    )
300