Переглянути джерело

fix: Handle parsing and formatting timedeltas

* Added utils parse_timedelta, format_timedelta
* Added to json_handler for de-serializing timedelta objects
version-14
Gavin D'souza 3 роки тому
джерело
коміт
8037866dc1
4 змінених файлів з 51 додано та 11 видалено
  1. +1
    -1
      frappe/utils/__init__.py
  2. +35
    -2
      frappe/utils/data.py
  3. +7
    -2
      frappe/utils/formatters.py
  4. +8
    -6
      frappe/utils/response.py

+ 1
- 1
frappe/utils/__init__.py Переглянути файл

@@ -1,4 +1,4 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE # License: MIT. See LICENSE


import functools import functools


+ 35
- 2
frappe/utils/data.py Переглянути файл

@@ -104,11 +104,17 @@ def get_timedelta(time: Optional[str] = None) -> Optional[datetime.timedelta]:
datetime.timedelta: Timedelta object equivalent of the passed `time` string datetime.timedelta: Timedelta object equivalent of the passed `time` string
""" """
from dateutil import parser from dateutil import parser
from dateutil.parser import ParserError


time = time or "0:0:0" time = time or "0:0:0"


try: try:
t = parser.parse(time)
try:
t = parser.parse(time)
except ParserError as e:
if "day" in e.args[1]:
from frappe.utils import parse_timedelta
return parse_timedelta(time)
return datetime.timedelta( return datetime.timedelta(
hours=t.hour, minutes=t.minute, seconds=t.second, microseconds=t.microsecond hours=t.hour, minutes=t.minute, seconds=t.second, microseconds=t.microsecond
) )
@@ -332,7 +338,7 @@ def get_time(time_str):
return time_str return time_str
else: else:
if isinstance(time_str, datetime.timedelta): if isinstance(time_str, datetime.timedelta):
time_str = str(time_str)
return format_timedelta(time_str)
return parser.parse(time_str).time() return parser.parse(time_str).time()


def get_datetime_str(datetime_obj): def get_datetime_str(datetime_obj):
@@ -1678,3 +1684,30 @@ class UnicodeWithAttrs(str):
def __init__(self, text): def __init__(self, text):
self.toc_html = text.toc_html self.toc_html = text.toc_html
self.metadata = text.metadata self.metadata = text.metadata


def format_timedelta(o: datetime.timedelta) -> str:
# mariadb allows a wide diff range - https://mariadb.com/kb/en/time/
# but frappe doesnt - i think via babel : only allows 0..23 range for hour
total_seconds = o.total_seconds()
hours, remainder = divmod(total_seconds, 3600)
minutes, seconds = divmod(remainder, 60)
rounded_seconds = round(seconds, 6)
int_seconds = int(seconds)

if rounded_seconds == int_seconds:
seconds = int_seconds
else:
seconds = rounded_seconds

return "{:01}:{:02}:{:02}".format(int(hours), int(minutes), seconds)


def parse_timedelta(s: str) -> datetime.timedelta:
# ref: https://stackoverflow.com/a/21074460/10309266
if 'day' in s:
m = re.match(r"(?P<days>[-\d]+) day[s]*, (?P<hours>\d+):(?P<minutes>\d+):(?P<seconds>\d[\.\d+]*)", s)
else:
m = re.match(r"(?P<hours>\d+):(?P<minutes>\d+):(?P<seconds>\d[\.\d+]*)", s)

return datetime.timedelta(**{key: float(val) for key, val in m.groupdict().items()})

+ 7
- 2
frappe/utils/formatters.py Переглянути файл

@@ -3,9 +3,11 @@


import frappe import frappe
import datetime import datetime
from frappe.utils import formatdate, fmt_money, flt, cstr, cint, format_datetime, format_time, format_duration
from frappe.utils import formatdate, fmt_money, flt, cstr, cint, format_datetime, format_time, format_duration, format_timedelta
from frappe.model.meta import get_field_currency, get_field_precision from frappe.model.meta import get_field_currency, get_field_precision
import re import re
from dateutil.parser import ParserError



def format_value(value, df=None, doc=None, currency=None, translated=False, format=None): def format_value(value, df=None, doc=None, currency=None, translated=False, format=None):
'''Format value based on given fieldtype, document reference, currency reference. '''Format value based on given fieldtype, document reference, currency reference.
@@ -47,7 +49,10 @@ def format_value(value, df=None, doc=None, currency=None, translated=False, form
return format_datetime(value) return format_datetime(value)


elif df.get("fieldtype")=="Time": elif df.get("fieldtype")=="Time":
return format_time(value)
try:
return format_time(value)
except ParserError:
return format_timedelta(value)


elif value==0 and df.get("fieldtype") in ("Int", "Float", "Currency", "Percent") and df.get("print_hide_if_no_value"): elif value==0 and df.get("fieldtype") in ("Int", "Float", "Currency", "Percent") and df.get("print_hide_if_no_value"):
# this is required to show 0 as blank in table columns # this is required to show 0 as blank in table columns


+ 8
- 6
frappe/utils/response.py Переглянути файл

@@ -1,4 +1,4 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE # License: MIT. See LICENSE


import json import json
@@ -16,7 +16,7 @@ from werkzeug.local import LocalProxy
from werkzeug.wsgi import wrap_file from werkzeug.wsgi import wrap_file
from werkzeug.wrappers import Response from werkzeug.wrappers import Response
from werkzeug.exceptions import NotFound, Forbidden from werkzeug.exceptions import NotFound, Forbidden
from frappe.utils import cint
from frappe.utils import cint, format_timedelta
from urllib.parse import quote from urllib.parse import quote
from frappe.core.doctype.access_log.access_log import make_access_log from frappe.core.doctype.access_log.access_log import make_access_log


@@ -122,12 +122,14 @@ def make_logs(response = None):


def json_handler(obj): def json_handler(obj):
"""serialize non-serializable data for json""" """serialize non-serializable data for json"""
# serialize date
import collections.abc
from collections.abc import Iterable


if isinstance(obj, (datetime.date, datetime.timedelta, datetime.datetime, datetime.time)):
if isinstance(obj, (datetime.date, datetime.datetime, datetime.time)):
return str(obj) return str(obj)


elif isinstance(obj, datetime.timedelta):
return format_timedelta(obj)

elif isinstance(obj, decimal.Decimal): elif isinstance(obj, decimal.Decimal):
return float(obj) return float(obj)


@@ -138,7 +140,7 @@ def json_handler(obj):
doc = obj.as_dict(no_nulls=True) doc = obj.as_dict(no_nulls=True)
return doc return doc


elif isinstance(obj, collections.abc.Iterable):
elif isinstance(obj, Iterable):
return list(obj) return list(obj)


elif type(obj)==type or isinstance(obj, Exception): elif type(obj)==type or isinstance(obj, Exception):


Завантаження…
Відмінити
Зберегти