Implemented currency formatting and parsing.
This commit is contained in:
parent
0ab7d48622
commit
103e06cf12
7 changed files with 64 additions and 11 deletions
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -15,10 +15,10 @@
|
|||
<input id="modproduct-name" type="text" name="name" value="{{ product.name }}" /><br/>
|
||||
|
||||
<label for="modproduct-price-member">Member price: </label>
|
||||
<input id="modproduct-price-member" type="text" name="pricemember" value="{{ product.price_member }}" /><br/>
|
||||
CHF <input id="modproduct-price-member" type="number" step="0.01" name="pricemember" value="{{ product.price_member|chf(False) }}" /><br/>
|
||||
|
||||
<label for="modproduct-price-non-member">Non-member price: </label>
|
||||
<input id="modproduct-price-non-member" type="text" name="pricenonmember" value="{{ product.price_non_member }}" /><br/>
|
||||
CHF <input id="modproduct-price-non-member" type="number" step="0.01" name="pricenonmember" value="{{ product.price_non_member|chf(False) }}" /><br/>
|
||||
|
||||
<label for="modproduct-balance">Stock: </label>
|
||||
<input id="modproduct-balance" name="stock" type="text" value="{{ product.stock }}" /><br/>
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
<input id="moduser-account-isadmin" name="isadmin" type="checkbox" {% if user.is_admin %} checked="checked" {% endif %}/><br/>
|
||||
|
||||
<label for="moduser-account-balance">Balance: </label>
|
||||
<input id="moduser-account-balance" name="balance" type="text" value="{{ user.balance }}" /><br/>
|
||||
CHF <input id="moduser-account-balance" name="balance" type="number" step="0.01" value="{{ user.balance|chf(False) }}" /><br/>
|
||||
|
||||
<label for="moduser-account-avatar">
|
||||
<img height="150" src="/upload/thumbnails/users/{{ user.id }}.png" alt="Avatar of {{ user.name }}" />
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
{% block main %}
|
||||
|
||||
Your balance: {{ authuser.balance }}
|
||||
Your balance: {{ authuser.balance|chf }}
|
||||
<br/>
|
||||
<a href="/deposit?n=100">Deposit CHF 1</a><br/>
|
||||
<a href="/deposit?n=1000">Deposit CHF 10</a><br/>
|
||||
|
@ -20,9 +20,9 @@ Your balance: {{ authuser.balance }}
|
|||
<span class="thumblist-title">{{ product.name }}</span>
|
||||
<span class="thumblist-detail">Price:
|
||||
{% if authuser.is_member %}
|
||||
{{ product.price_member }}
|
||||
{{ product.price_member|chf }}
|
||||
{% else %}
|
||||
{{ product.price_non_member }}
|
||||
{{ product.price_non_member|chf }}
|
||||
{% endif %}
|
||||
; Stock: {{ product.stock }}</span><br/>
|
||||
<div class="imgcontainer">
|
||||
|
|
Loading…
Reference in a new issue