fix: add simple rendering logic for currency values and errors
This commit is contained in:
parent
78a9965c52
commit
9c1bfee97f
|
@ -34,7 +34,7 @@ import humanize
|
||||||
|
|
||||||
from wuttjamaican.util import (load_entry_points, load_object,
|
from wuttjamaican.util import (load_entry_points, load_object,
|
||||||
make_title, make_uuid, make_true_uuid,
|
make_title, make_uuid, make_true_uuid,
|
||||||
progress_loop, resource_path)
|
progress_loop, resource_path, simple_error)
|
||||||
|
|
||||||
|
|
||||||
class AppHandler:
|
class AppHandler:
|
||||||
|
@ -676,6 +676,28 @@ class AppHandler:
|
||||||
# common value renderers
|
# common value renderers
|
||||||
##############################
|
##############################
|
||||||
|
|
||||||
|
def render_currency(self, value, scale=2, **kwargs):
|
||||||
|
"""
|
||||||
|
Return a human-friendly display string for the given currency
|
||||||
|
value, e.g. ``Decimal('4.20')`` becomes ``"$4.20"``.
|
||||||
|
|
||||||
|
:param value: Either a :class:`python:decimal.Decimal` or
|
||||||
|
:class:`python:float` value.
|
||||||
|
|
||||||
|
:param scale: Number of decimal digits to be displayed.
|
||||||
|
|
||||||
|
:returns: Display string for the value.
|
||||||
|
"""
|
||||||
|
if value is None:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
if value < 0:
|
||||||
|
fmt = f"(${{:0,.{scale}f}})"
|
||||||
|
return fmt.format(0 - value)
|
||||||
|
|
||||||
|
fmt = f"${{:0,.{scale}f}}"
|
||||||
|
return fmt.format(value)
|
||||||
|
|
||||||
display_format_date = '%Y-%m-%d'
|
display_format_date = '%Y-%m-%d'
|
||||||
"""
|
"""
|
||||||
Format string to use when displaying :class:`python:datetime.date`
|
Format string to use when displaying :class:`python:datetime.date`
|
||||||
|
@ -717,6 +739,16 @@ class AppHandler:
|
||||||
if value is not None:
|
if value is not None:
|
||||||
return value.strftime(self.display_format_datetime)
|
return value.strftime(self.display_format_datetime)
|
||||||
|
|
||||||
|
def render_error(self, error):
|
||||||
|
"""
|
||||||
|
Return a "human-friendly" display string for the error, e.g.
|
||||||
|
when showing it to the user.
|
||||||
|
|
||||||
|
By default, this is a convenience wrapper for
|
||||||
|
:func:`~wuttjamaican.util.simple_error()`.
|
||||||
|
"""
|
||||||
|
return simple_error(error)
|
||||||
|
|
||||||
def render_time_ago(self, value):
|
def render_time_ago(self, value):
|
||||||
"""
|
"""
|
||||||
Return a human-friendly string, indicating how long ago
|
Return a human-friendly string, indicating how long ago
|
||||||
|
|
|
@ -338,3 +338,22 @@ def resource_path(path):
|
||||||
return str(path)
|
return str(path)
|
||||||
|
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def simple_error(error):
|
||||||
|
"""
|
||||||
|
Return a "simple" string for the given error. Result will look
|
||||||
|
like::
|
||||||
|
|
||||||
|
"ErrorClass: Description for the error"
|
||||||
|
|
||||||
|
However the logic checks to ensure the error has a descriptive
|
||||||
|
message first; if it doesn't the result will just be::
|
||||||
|
|
||||||
|
"ErrorClass"
|
||||||
|
"""
|
||||||
|
cls = type(error).__name__
|
||||||
|
msg = str(error)
|
||||||
|
if msg:
|
||||||
|
return f"{cls}: {msg}"
|
||||||
|
return cls
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# -*- coding: utf-8; -*-
|
# -*- coding: utf-8; -*-
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
import decimal
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
|
@ -422,6 +423,31 @@ app_title = WuttaTest
|
||||||
session = self.app.get_session(user)
|
session = self.app.get_session(user)
|
||||||
self.assertIs(session, mysession)
|
self.assertIs(session, mysession)
|
||||||
|
|
||||||
|
def test_render_currency(self):
|
||||||
|
|
||||||
|
# null
|
||||||
|
self.assertEqual(self.app.render_currency(None), '')
|
||||||
|
|
||||||
|
# basic decimal example
|
||||||
|
value = decimal.Decimal('42.00')
|
||||||
|
self.assertEqual(self.app.render_currency(value), '$42.00')
|
||||||
|
|
||||||
|
# basic float example
|
||||||
|
value = 42.00
|
||||||
|
self.assertEqual(self.app.render_currency(value), '$42.00')
|
||||||
|
|
||||||
|
# decimal places will be rounded
|
||||||
|
value = decimal.Decimal('42.12345')
|
||||||
|
self.assertEqual(self.app.render_currency(value), '$42.12')
|
||||||
|
|
||||||
|
# but we can declare the scale
|
||||||
|
value = decimal.Decimal('42.12345')
|
||||||
|
self.assertEqual(self.app.render_currency(value, scale=4), '$42.1234')
|
||||||
|
|
||||||
|
# negative numbers get parens
|
||||||
|
value = decimal.Decimal('-42.42')
|
||||||
|
self.assertEqual(self.app.render_currency(value), '($42.42)')
|
||||||
|
|
||||||
def test_render_date(self):
|
def test_render_date(self):
|
||||||
self.assertIsNone(self.app.render_date(None))
|
self.assertIsNone(self.app.render_date(None))
|
||||||
|
|
||||||
|
@ -434,6 +460,22 @@ app_title = WuttaTest
|
||||||
dt = datetime.datetime(2024, 12, 11, 8, 30, tzinfo=datetime.timezone.utc)
|
dt = datetime.datetime(2024, 12, 11, 8, 30, tzinfo=datetime.timezone.utc)
|
||||||
self.assertEqual(self.app.render_datetime(dt), '2024-12-11 08:30+0000')
|
self.assertEqual(self.app.render_datetime(dt), '2024-12-11 08:30+0000')
|
||||||
|
|
||||||
|
def test_simple_error(self):
|
||||||
|
|
||||||
|
# with description
|
||||||
|
try:
|
||||||
|
raise RuntimeError("just testin")
|
||||||
|
except Exception as error:
|
||||||
|
result = self.app.render_error(error)
|
||||||
|
self.assertEqual(result, "RuntimeError: just testin")
|
||||||
|
|
||||||
|
# without description
|
||||||
|
try:
|
||||||
|
raise RuntimeError
|
||||||
|
except Exception as error:
|
||||||
|
result = self.app.render_error(error)
|
||||||
|
self.assertEqual(result, "RuntimeError")
|
||||||
|
|
||||||
def test_render_time_ago(self):
|
def test_render_time_ago(self):
|
||||||
with patch.object(mod, 'humanize') as humanize:
|
with patch.object(mod, 'humanize') as humanize:
|
||||||
humanize.naturaltime.return_value = 'now'
|
humanize.naturaltime.return_value = 'now'
|
||||||
|
|
|
@ -317,3 +317,20 @@ class TestResourcePath(TestCase):
|
||||||
|
|
||||||
# absolute path returned as-is
|
# absolute path returned as-is
|
||||||
self.assertEqual(mod.resource_path('/tmp/doesnotexist.txt'), '/tmp/doesnotexist.txt')
|
self.assertEqual(mod.resource_path('/tmp/doesnotexist.txt'), '/tmp/doesnotexist.txt')
|
||||||
|
|
||||||
|
|
||||||
|
class TestSimpleError(TestCase):
|
||||||
|
|
||||||
|
def test_with_description(self):
|
||||||
|
try:
|
||||||
|
raise RuntimeError("just testin")
|
||||||
|
except Exception as error:
|
||||||
|
result = mod.simple_error(error)
|
||||||
|
self.assertEqual(result, "RuntimeError: just testin")
|
||||||
|
|
||||||
|
def test_without_description(self):
|
||||||
|
try:
|
||||||
|
raise RuntimeError
|
||||||
|
except Exception as error:
|
||||||
|
result = mod.simple_error(error)
|
||||||
|
self.assertEqual(result, "RuntimeError")
|
||||||
|
|
Loading…
Reference in a new issue