diff --git a/matemat/webserver/httpd.py b/matemat/webserver/httpd.py index cd4f823..4d6d87c 100644 --- a/matemat/webserver/httpd.py +++ b/matemat/webserver/httpd.py @@ -17,7 +17,7 @@ import jinja2 from matemat import __version__ as matemat_version from matemat.exceptions import HttpException from matemat.webserver import RequestArguments, PageletResponse, RedirectResponse, TemplateResponse -from matemat.webserver.util import parse_args +from matemat.webserver.util import parse_args, format_chf # # Python internal class hacks @@ -117,8 +117,9 @@ class MatematHTTPServer(HTTPServer): # Set up the Jinja2 environment self.jinja_env: jinja2.Environment = jinja2.Environment( loader=jinja2.FileSystemLoader(os.path.abspath(templateroot)), - autoescape=jinja2.select_autoescape(default=True) + autoescape=jinja2.select_autoescape(default=True), ) + self.jinja_env.filters['chf'] = format_chf # Set up logger self.logger: logging.Logger = logging.getLogger('matemat.webserver') self.logger.setLevel(log_level) diff --git a/matemat/webserver/pagelets/modproduct.py b/matemat/webserver/pagelets/modproduct.py index 3da8ff9..e0e6814 100644 --- a/matemat/webserver/pagelets/modproduct.py +++ b/matemat/webserver/pagelets/modproduct.py @@ -8,6 +8,7 @@ from matemat.webserver import pagelet, RequestArguments, PageletResponse, Redire from matemat.db import MatematDatabase from matemat.primitives import Product from matemat.exceptions import DatabaseConsistencyError, HttpException +from matemat.webserver.util import parse_chf @pagelet('/modproduct') @@ -60,8 +61,8 @@ def handle_change(args: RequestArguments, product: Product, db: MatematDatabase, if 'name' not in args or 'pricemember' not in args or 'pricenonmember' not in args or 'stock' not in args: return name = str(args.name) - price_member = int(str(args.pricemember)) - price_non_member = int(str(args.pricenonmember)) + price_member = parse_chf(str(args.pricemember)) + price_non_member = parse_chf(str(args.pricenonmember)) stock = int(str(args.stock)) try: db.change_product(product, diff --git a/matemat/webserver/pagelets/moduser.py b/matemat/webserver/pagelets/moduser.py index 301ddde..4dda3f0 100644 --- a/matemat/webserver/pagelets/moduser.py +++ b/matemat/webserver/pagelets/moduser.py @@ -8,6 +8,7 @@ from matemat.webserver import pagelet, RequestArguments, PageletResponse, Redire from matemat.db import MatematDatabase from matemat.primitives import User from matemat.exceptions import DatabaseConsistencyError, HttpException +from matemat.webserver.util import parse_chf @pagelet('/moduser') @@ -62,7 +63,7 @@ def handle_change(args: RequestArguments, user: User, db: MatematDatabase, confi username = str(args.username) email = str(args.email) password = str(args.password) - balance = int(str(args.balance)) + balance = parse_chf(str(args.balance)) is_member = 'ismember' in args is_admin = 'isadmin' in args if len(email) == 0: diff --git a/matemat/webserver/util.py b/matemat/webserver/util.py index c95d303..ddbb41e 100644 --- a/matemat/webserver/util.py +++ b/matemat/webserver/util.py @@ -136,3 +136,53 @@ def parse_args(request: str, postbody: Optional[bytes] = None, enctype: str = 't raise ValueError(f'Unsupported Content-Type: {enctype}') # Return the path and the parsed arguments return tokens.path, args + + +def format_chf(value: int, with_currencysign: bool = True) -> str: + """ + Formats a centime value into a commonly understood representation ("CHF -13.37"). + + :param value: The value to format, in centimes. + :param with_currencysign: Whether to include the currency prefix ("CHF ") in the output. + :return: A human-readable string representation. + """ + sign: str = '' + if value < 0: + # As // and % round towards -Inf, convert into a positive value and prepend the negative sign + sign = '-' + value = -value + # Split into full francs and fractions (centimes) + full: int = value // 100 + frac: int = value % 100 + csign: str = 'CHF ' if with_currencysign else '' + # Put it all together; centimes are always padded with 2 zeros + return f'{csign}{sign}{full}.{frac:02}' + + +def parse_chf(value: str) -> int: + """ + Parse a currency value into machine-readable format (integer centimes). The prefix "CHF", the decimal point, and + digits after the decimal point are optional. + + :param value: The value to parse. + :return: An integer representation of the value. + :raises: Value error: If more than two digits after the decimal point are present. + """ + value = value.strip() + if value.startswith('CHF'): + value = value[3:] + value = value.strip() + if '.' not in value: + return int(value) * 100 + full, frac = value.split('.', 1) + if len(frac) > 2: + raise ValueError('Needs max. 2 digits after decimal point') + elif len(frac) < 2: + frac = frac + '0' * (2 - len(frac)) + ifrac: int = int(frac) + ifull: int = int(full) + if ifrac < 0: + raise ValueError('Fraction part must not be negative.') + if ifull < 0: + ifrac = -ifrac + return ifull * 100 + ifrac diff --git a/templates/modproduct.html b/templates/modproduct.html index 698790f..2158139 100644 --- a/templates/modproduct.html +++ b/templates/modproduct.html @@ -15,10 +15,10 @@
-
+ CHF
-
+ CHF

diff --git a/templates/moduser.html b/templates/moduser.html index e28ef82..187f4f4 100644 --- a/templates/moduser.html +++ b/templates/moduser.html @@ -27,7 +27,7 @@
-
+ CHF