mirror of
https://github.com/ae-utbm/sith.git
synced 2024-11-28 20:14:23 +00:00
d16a207a83
* ruff: apply rule F * ruff: apply rule E * ruff: apply rule SIM * ruff: apply rule TCH * ruff: apply rule ERA * ruff: apply rule PLW * ruff: apply rule FLY * ruff: apply rule PERF * ruff: apply rules FURB & RUF
129 lines
5.1 KiB
Python
129 lines
5.1 KiB
Python
#
|
|
# Copyright 2022
|
|
# - Maréchal <thgirod@hotmail.com
|
|
#
|
|
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
|
|
# http://ae.utbm.fr.
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify it under
|
|
# the terms of the GNU General Public License a published by the Free Software
|
|
# Foundation; either version 3 of the License, or (at your option) any later
|
|
# version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
# details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License along with
|
|
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
|
|
# Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
#
|
|
#
|
|
from functools import cached_property
|
|
from urllib.parse import unquote
|
|
|
|
from django.http import HttpRequest
|
|
from django.utils.translation import gettext as _
|
|
from pydantic import ValidationError
|
|
|
|
from eboutic.models import get_eboutic_products
|
|
from eboutic.schemas import PurchaseItemList, PurchaseItemSchema
|
|
|
|
|
|
class BasketForm:
|
|
"""Class intended to perform checks on the request sended to the server when
|
|
the user submits his basket from /eboutic/.
|
|
|
|
Because it must check an unknown number of fields, coming from a cookie
|
|
and needing some databases checks to be performed, inheriting from forms.Form
|
|
or using formset would have been likely to end in a big ball of wibbly-wobbly hacky stuff.
|
|
Thus this class is a pure standalone and performs its operations by its own means.
|
|
However, it still tries to share some similarities with a standard django Form.
|
|
|
|
Examples:
|
|
::
|
|
|
|
def my_view(request):
|
|
form = BasketForm(request)
|
|
form.clean()
|
|
if form.is_valid():
|
|
# perform operations
|
|
else:
|
|
errors = form.get_error_messages()
|
|
|
|
# return the cookie that was in the request, but with all
|
|
# incorrects elements removed
|
|
cookie = form.get_cleaned_cookie()
|
|
|
|
You can also use a little shortcut by directly calling `form.is_valid()`
|
|
without calling `form.clean()`. In this case, the latter method shall be
|
|
implicitly called.
|
|
"""
|
|
|
|
def __init__(self, request: HttpRequest):
|
|
self.user = request.user
|
|
self.cookies = request.COOKIES
|
|
self.error_messages = set()
|
|
self.correct_items = []
|
|
|
|
def clean(self) -> None:
|
|
"""Perform all the checks, but return nothing.
|
|
To know if the form is valid, the `is_valid()` method must be used.
|
|
|
|
The form shall be considered as valid if it meets all the following conditions :
|
|
- it contains a "basket_items" key in the cookies of the request given in the constructor
|
|
- this cookie is a list of objects formatted this way : `[{'id': <int>, 'quantity': <int>,
|
|
'name': <str>, 'unit_price': <float>}, ...]`. The order of the fields in each object does not matter
|
|
- all the ids are positive integers
|
|
- all the ids refer to products available in the EBOUTIC
|
|
- all the ids refer to products the user is allowed to buy
|
|
- all the quantities are positive integers
|
|
"""
|
|
try:
|
|
basket = PurchaseItemList.validate_json(
|
|
unquote(self.cookies.get("basket_items", "[]"))
|
|
)
|
|
except ValidationError:
|
|
self.error_messages.add(_("The request was badly formatted."))
|
|
return
|
|
if len(basket) == 0:
|
|
self.error_messages.add(_("Your basket is empty."))
|
|
return
|
|
existing_ids = {product.id for product in get_eboutic_products(self.user)}
|
|
for item in basket:
|
|
# check a product with this id does exist
|
|
if item.product_id in existing_ids:
|
|
self.correct_items.append(item)
|
|
else:
|
|
self.error_messages.add(
|
|
_(
|
|
"%(name)s : this product does not exist or may no longer be available."
|
|
)
|
|
% {"name": item.name}
|
|
)
|
|
continue
|
|
# this function does not return anything.
|
|
# instead, it fills a set containing the collected error messages
|
|
# an empty set means that no error was seen thus everything is ok
|
|
# and the form is valid.
|
|
# a non-empty set means there was at least one error thus
|
|
# the form is invalid
|
|
|
|
def is_valid(self) -> bool:
|
|
"""Return True if the form is correct else False.
|
|
|
|
If the `clean()` method has not been called beforehand, call it.
|
|
"""
|
|
if not self.error_messages and not self.correct_items:
|
|
self.clean()
|
|
return not self.error_messages
|
|
|
|
@cached_property
|
|
def errors(self) -> list[str]:
|
|
return list(self.error_messages)
|
|
|
|
@cached_property
|
|
def cleaned_data(self) -> list[PurchaseItemSchema]:
|
|
return self.correct_items
|