mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Report: Add date rendering (#6706)
* Validate timezone in settings.py * Add helper functions for timezone information - Extract server timezone - Convert provided time to specified timezone * Add more unit tests * Remove debug print * Test fix * Add report helper tags - format_date - format_datetime - Update report templates - Unit tests * Add setting to control report errors - Only log errors to DB if setting is enabled * Update example report * Fixes for to_local_time * Update type hinting * Fix unit test typo
This commit is contained in:
parent
7de87383b5
commit
610ea7b0b1
@ -1,5 +1,6 @@
|
|||||||
"""Provides helper functions used throughout the InvenTree project."""
|
"""Provides helper functions used throughout the InvenTree project."""
|
||||||
|
|
||||||
|
import datetime
|
||||||
import hashlib
|
import hashlib
|
||||||
import io
|
import io
|
||||||
import json
|
import json
|
||||||
@ -11,6 +12,7 @@ from decimal import Decimal, InvalidOperation
|
|||||||
from typing import TypeVar
|
from typing import TypeVar
|
||||||
from wsgiref.util import FileWrapper
|
from wsgiref.util import FileWrapper
|
||||||
|
|
||||||
|
import django.utils.timezone as timezone
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.staticfiles.storage import StaticFilesStorage
|
from django.contrib.staticfiles.storage import StaticFilesStorage
|
||||||
from django.core.exceptions import FieldError, ValidationError
|
from django.core.exceptions import FieldError, ValidationError
|
||||||
@ -18,6 +20,7 @@ from django.core.files.storage import default_storage
|
|||||||
from django.http import StreamingHttpResponse
|
from django.http import StreamingHttpResponse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
import pytz
|
||||||
import regex
|
import regex
|
||||||
from bleach import clean
|
from bleach import clean
|
||||||
from djmoney.money import Money
|
from djmoney.money import Money
|
||||||
@ -863,6 +866,56 @@ def hash_file(filename: str):
|
|||||||
return hashlib.md5(open(filename, 'rb').read()).hexdigest()
|
return hashlib.md5(open(filename, 'rb').read()).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def server_timezone() -> str:
|
||||||
|
"""Return the timezone of the server as a string.
|
||||||
|
|
||||||
|
e.g. "UTC" / "Australia/Sydney" etc
|
||||||
|
"""
|
||||||
|
return settings.TIME_ZONE
|
||||||
|
|
||||||
|
|
||||||
|
def to_local_time(time, target_tz: str = None):
|
||||||
|
"""Convert the provided time object to the local timezone.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
time: The time / date to convert
|
||||||
|
target_tz: The desired timezone (string) - defaults to server time
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A timezone aware datetime object, with the desired timezone
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
TypeError: If the provided time object is not a datetime or date object
|
||||||
|
"""
|
||||||
|
if isinstance(time, datetime.datetime):
|
||||||
|
pass
|
||||||
|
elif isinstance(time, datetime.date):
|
||||||
|
time = timezone.datetime(year=time.year, month=time.month, day=time.day)
|
||||||
|
else:
|
||||||
|
raise TypeError(
|
||||||
|
f'Argument must be a datetime or date object (found {type(time)}'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Extract timezone information from the provided time
|
||||||
|
source_tz = getattr(time, 'tzinfo', None)
|
||||||
|
|
||||||
|
if not source_tz:
|
||||||
|
# Default to UTC if not provided
|
||||||
|
source_tz = pytz.utc
|
||||||
|
|
||||||
|
if not target_tz:
|
||||||
|
target_tz = server_timezone()
|
||||||
|
|
||||||
|
try:
|
||||||
|
target_tz = pytz.timezone(str(target_tz))
|
||||||
|
except pytz.UnknownTimeZoneError:
|
||||||
|
target_tz = pytz.utc
|
||||||
|
|
||||||
|
target_time = time.replace(tzinfo=source_tz).astimezone(target_tz)
|
||||||
|
|
||||||
|
return target_time
|
||||||
|
|
||||||
|
|
||||||
def get_objectreference(
|
def get_objectreference(
|
||||||
obj, type_ref: str = 'content_type', object_ref: str = 'object_id'
|
obj, type_ref: str = 'content_type', object_ref: str = 'object_id'
|
||||||
):
|
):
|
||||||
|
@ -22,6 +22,7 @@ from django.http import Http404
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
import moneyed
|
import moneyed
|
||||||
|
import pytz
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
from InvenTree.config import get_boolean_setting, get_custom_file, get_setting
|
from InvenTree.config import get_boolean_setting, get_custom_file, get_setting
|
||||||
@ -938,8 +939,13 @@ LOCALE_PATHS = (BASE_DIR.joinpath('locale/'),)
|
|||||||
|
|
||||||
TIME_ZONE = get_setting('INVENTREE_TIMEZONE', 'timezone', 'UTC')
|
TIME_ZONE = get_setting('INVENTREE_TIMEZONE', 'timezone', 'UTC')
|
||||||
|
|
||||||
USE_I18N = True
|
# Check that the timezone is valid
|
||||||
|
try:
|
||||||
|
pytz.timezone(TIME_ZONE)
|
||||||
|
except pytz.exceptions.UnknownTimeZoneError: # pragma: no cover
|
||||||
|
raise ValueError(f"Specified timezone '{TIME_ZONE}' is not valid")
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
# Do not use native timezone support in "test" mode
|
# Do not use native timezone support in "test" mode
|
||||||
# It generates a *lot* of cruft in the logs
|
# It generates a *lot* of cruft in the logs
|
||||||
|
@ -14,8 +14,10 @@ from django.core import mail
|
|||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.test import TestCase, override_settings, tag
|
from django.test import TestCase, override_settings, tag
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
import pint.errors
|
import pint.errors
|
||||||
|
import pytz
|
||||||
from djmoney.contrib.exchange.exceptions import MissingRate
|
from djmoney.contrib.exchange.exceptions import MissingRate
|
||||||
from djmoney.contrib.exchange.models import Rate, convert_money
|
from djmoney.contrib.exchange.models import Rate, convert_money
|
||||||
from djmoney.money import Money
|
from djmoney.money import Money
|
||||||
@ -746,6 +748,47 @@ class TestHelpers(TestCase):
|
|||||||
self.assertEqual(helpers.generateTestKey(name), key)
|
self.assertEqual(helpers.generateTestKey(name), key)
|
||||||
|
|
||||||
|
|
||||||
|
class TestTimeFormat(TestCase):
|
||||||
|
"""Unit test for time formatting functionality."""
|
||||||
|
|
||||||
|
@override_settings(TIME_ZONE='UTC')
|
||||||
|
def test_tz_utc(self):
|
||||||
|
"""Check UTC timezone."""
|
||||||
|
self.assertEqual(InvenTree.helpers.server_timezone(), 'UTC')
|
||||||
|
|
||||||
|
@override_settings(TIME_ZONE='Europe/London')
|
||||||
|
def test_tz_london(self):
|
||||||
|
"""Check London timezone."""
|
||||||
|
self.assertEqual(InvenTree.helpers.server_timezone(), 'Europe/London')
|
||||||
|
|
||||||
|
@override_settings(TIME_ZONE='Australia/Sydney')
|
||||||
|
def test_to_local_time(self):
|
||||||
|
"""Test that the local time conversion works as expected."""
|
||||||
|
source_time = timezone.datetime(
|
||||||
|
year=2000,
|
||||||
|
month=1,
|
||||||
|
day=1,
|
||||||
|
hour=0,
|
||||||
|
minute=0,
|
||||||
|
second=0,
|
||||||
|
tzinfo=pytz.timezone('Europe/London'),
|
||||||
|
)
|
||||||
|
|
||||||
|
tests = [
|
||||||
|
('UTC', '2000-01-01 00:01:00+00:00'),
|
||||||
|
('Europe/London', '2000-01-01 00:00:00-00:01'),
|
||||||
|
('America/New_York', '1999-12-31 19:01:00-05:00'),
|
||||||
|
# All following tests should result in the same value
|
||||||
|
('Australia/Sydney', '2000-01-01 11:01:00+11:00'),
|
||||||
|
(None, '2000-01-01 11:01:00+11:00'),
|
||||||
|
('', '2000-01-01 11:01:00+11:00'),
|
||||||
|
]
|
||||||
|
|
||||||
|
for tz, expected in tests:
|
||||||
|
local_time = InvenTree.helpers.to_local_time(source_time, tz)
|
||||||
|
self.assertEqual(str(local_time), expected)
|
||||||
|
|
||||||
|
|
||||||
class TestQuoteWrap(TestCase):
|
class TestQuoteWrap(TestCase):
|
||||||
"""Tests for string wrapping."""
|
"""Tests for string wrapping."""
|
||||||
|
|
||||||
|
@ -1653,6 +1653,12 @@ class InvenTreeSetting(BaseInvenTreeSetting):
|
|||||||
'default': False,
|
'default': False,
|
||||||
'validator': bool,
|
'validator': bool,
|
||||||
},
|
},
|
||||||
|
'REPORT_LOG_ERRORS': {
|
||||||
|
'name': _('Log Report Errors'),
|
||||||
|
'description': _('Log errors which occur when generating reports'),
|
||||||
|
'default': False,
|
||||||
|
'validator': bool,
|
||||||
|
},
|
||||||
'REPORT_DEFAULT_PAGE_SIZE': {
|
'REPORT_DEFAULT_PAGE_SIZE': {
|
||||||
'name': _('Page Size'),
|
'name': _('Page Size'),
|
||||||
'description': _('Default page size for PDF reports'),
|
'description': _('Default page size for PDF reports'),
|
||||||
|
@ -264,7 +264,12 @@ class ReportPrintMixin:
|
|||||||
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
# Log the exception to the database
|
# Log the exception to the database
|
||||||
log_error(request.path)
|
if InvenTree.helpers.str2bool(
|
||||||
|
common.models.InvenTreeSetting.get_setting(
|
||||||
|
'REPORT_LOG_ERRORS', cache=False
|
||||||
|
)
|
||||||
|
):
|
||||||
|
log_error(request.path)
|
||||||
|
|
||||||
# Re-throw the exception to the client as a DRF exception
|
# Re-throw the exception to the client as a DRF exception
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
|
@ -11,7 +11,7 @@ margin-top: 4cm;
|
|||||||
{% endblock page_margin %}
|
{% endblock page_margin %}
|
||||||
|
|
||||||
{% block bottom_left %}
|
{% block bottom_left %}
|
||||||
content: "v{{ report_revision }} - {{ date.isoformat }}";
|
content: "v{{ report_revision }} - {% format_date date %}";
|
||||||
{% endblock bottom_left %}
|
{% endblock bottom_left %}
|
||||||
|
|
||||||
{% block bottom_center %}
|
{% block bottom_center %}
|
||||||
|
@ -74,7 +74,7 @@ margin-top: 4cm;
|
|||||||
{% endblock style %}
|
{% endblock style %}
|
||||||
|
|
||||||
{% block bottom_left %}
|
{% block bottom_left %}
|
||||||
content: "v{{ report_revision }} - {{ date.isoformat }}";
|
content: "v{{ report_revision }} - {% format_date date %}";
|
||||||
{% endblock bottom_left %}
|
{% endblock bottom_left %}
|
||||||
|
|
||||||
{% block header_content %}
|
{% block header_content %}
|
||||||
@ -119,13 +119,13 @@ content: "v{{ report_revision }} - {{ date.isoformat }}";
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans "Issued" %}</th>
|
<th>{% trans "Issued" %}</th>
|
||||||
<td>{% render_date build.creation_date %}</td>
|
<td>{% format_date build.creation_date %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans "Target Date" %}</th>
|
<th>{% trans "Target Date" %}</th>
|
||||||
<td>
|
<td>
|
||||||
{% if build.target_date %}
|
{% if build.target_date %}
|
||||||
{% render_date build.target_date %}
|
{% format_date build.target_date %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<em>Not specified</em>
|
<em>Not specified</em>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -12,7 +12,7 @@ margin-top: 4cm;
|
|||||||
{% endblock page_margin %}
|
{% endblock page_margin %}
|
||||||
|
|
||||||
{% block bottom_left %}
|
{% block bottom_left %}
|
||||||
content: "v{{ report_revision }} - {{ date.isoformat }}";
|
content: "v{{ report_revision }} - {% format_date date %}";
|
||||||
{% endblock bottom_left %}
|
{% endblock bottom_left %}
|
||||||
|
|
||||||
{% block bottom_center %}
|
{% block bottom_center %}
|
||||||
|
@ -11,7 +11,7 @@ margin-top: 4cm;
|
|||||||
{% endblock page_margin %}
|
{% endblock page_margin %}
|
||||||
|
|
||||||
{% block bottom_left %}
|
{% block bottom_left %}
|
||||||
content: "v{{ report_revision }} - {{ date.isoformat }}";
|
content: "v{{ report_revision }} - {% format_date date %}";
|
||||||
{% endblock bottom_left %}
|
{% endblock bottom_left %}
|
||||||
|
|
||||||
{% block bottom_center %}
|
{% block bottom_center %}
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
{% block bottom_left %}
|
{% block bottom_left %}
|
||||||
content: "{{ date.isoformat }}";
|
content: "{% format_date date %}";
|
||||||
{% endblock bottom_left %}
|
{% endblock bottom_left %}
|
||||||
|
|
||||||
{% block bottom_center %}
|
{% block bottom_center %}
|
||||||
@ -133,7 +133,7 @@ content: "{% trans 'Stock Item Test Report' %}";
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<td>{{ test_result.value }}</td>
|
<td>{{ test_result.value }}</td>
|
||||||
<td>{{ test_result.user.username }}</td>
|
<td>{{ test_result.user.username }}</td>
|
||||||
<td>{{ test_result.date.date.isoformat }}</td>
|
<td>{% format_date test_result.date.date %}</td>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if test_template.required %}
|
{% if test_template.required %}
|
||||||
<td colspan='4' class='required-test-not-found'>{% trans "No result (required)" %}</td>
|
<td colspan='4' class='required-test-not-found'>{% trans "No result (required)" %}</td>
|
||||||
|
@ -422,3 +422,37 @@ def format_number(number, **kwargs):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
@register.simple_tag
|
||||||
|
def format_datetime(datetime, timezone=None, format=None):
|
||||||
|
"""Format a datetime object for display.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
datetime: The datetime object to format
|
||||||
|
timezone: The timezone to use for the date (defaults to the server timezone)
|
||||||
|
format: The format string to use (defaults to ISO formatting)
|
||||||
|
"""
|
||||||
|
datetime = InvenTree.helpers.to_local_time(datetime, timezone)
|
||||||
|
|
||||||
|
if format:
|
||||||
|
return datetime.strftime(format)
|
||||||
|
else:
|
||||||
|
return datetime.isoformat()
|
||||||
|
|
||||||
|
|
||||||
|
@register.simple_tag
|
||||||
|
def format_date(date, timezone=None, format=None):
|
||||||
|
"""Format a date object for display.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
date: The date to format
|
||||||
|
timezone: The timezone to use for the date (defaults to the server timezone)
|
||||||
|
format: The format string to use (defaults to ISO formatting)
|
||||||
|
"""
|
||||||
|
date = InvenTree.helpers.to_local_time(date, timezone).date()
|
||||||
|
|
||||||
|
if format:
|
||||||
|
return date.strftime(format)
|
||||||
|
else:
|
||||||
|
return date.isoformat()
|
||||||
|
@ -8,10 +8,12 @@ from pathlib import Path
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.http.response import StreamingHttpResponse
|
from django.http.response import StreamingHttpResponse
|
||||||
from django.test import TestCase
|
from django.test import TestCase, override_settings
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from django.utils import timezone
|
||||||
from django.utils.safestring import SafeString
|
from django.utils.safestring import SafeString
|
||||||
|
|
||||||
|
import pytz
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
import report.models as report_models
|
import report.models as report_models
|
||||||
@ -153,6 +155,37 @@ class ReportTagTest(TestCase):
|
|||||||
self.assertEqual(report_tags.multiply(2.3, 4), 9.2)
|
self.assertEqual(report_tags.multiply(2.3, 4), 9.2)
|
||||||
self.assertEqual(report_tags.divide(100, 5), 20)
|
self.assertEqual(report_tags.divide(100, 5), 20)
|
||||||
|
|
||||||
|
@override_settings(TIME_ZONE='America/New_York')
|
||||||
|
def test_date_tags(self):
|
||||||
|
"""Test for date formatting tags.
|
||||||
|
|
||||||
|
- Source timezone is Australia/Sydney
|
||||||
|
- Server timezone is America/New York
|
||||||
|
"""
|
||||||
|
time = timezone.datetime(
|
||||||
|
year=2024,
|
||||||
|
month=3,
|
||||||
|
day=13,
|
||||||
|
hour=12,
|
||||||
|
minute=30,
|
||||||
|
second=0,
|
||||||
|
tzinfo=pytz.timezone('Australia/Sydney'),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Format a set of tests: timezone, format, expected
|
||||||
|
tests = [
|
||||||
|
(None, None, '2024-03-12T22:25:00-04:00'),
|
||||||
|
(None, '%d-%m-%y', '12-03-24'),
|
||||||
|
('UTC', None, '2024-03-13T02:25:00+00:00'),
|
||||||
|
('UTC', '%d-%B-%Y', '13-March-2024'),
|
||||||
|
('Europe/Amsterdam', None, '2024-03-13T03:25:00+01:00'),
|
||||||
|
('Europe/Amsterdam', '%y-%m-%d %H:%M', '24-03-13 03:25'),
|
||||||
|
]
|
||||||
|
|
||||||
|
for tz, fmt, expected in tests:
|
||||||
|
result = report_tags.format_datetime(time, tz, fmt)
|
||||||
|
self.assertEqual(result, expected)
|
||||||
|
|
||||||
|
|
||||||
class BarcodeTagTest(TestCase):
|
class BarcodeTagTest(TestCase):
|
||||||
"""Unit tests for the barcode template tags."""
|
"""Unit tests for the barcode template tags."""
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
{% include "InvenTree/settings/setting.html" with key="REPORT_ENABLE" icon="fa-file-pdf" %}
|
{% include "InvenTree/settings/setting.html" with key="REPORT_ENABLE" icon="fa-file-pdf" %}
|
||||||
{% include "InvenTree/settings/setting.html" with key="REPORT_DEFAULT_PAGE_SIZE" icon="fa-print" %}
|
{% include "InvenTree/settings/setting.html" with key="REPORT_DEFAULT_PAGE_SIZE" icon="fa-print" %}
|
||||||
{% include "InvenTree/settings/setting.html" with key="REPORT_DEBUG_MODE" icon="fa-laptop-code" %}
|
{% include "InvenTree/settings/setting.html" with key="REPORT_DEBUG_MODE" icon="fa-laptop-code" %}
|
||||||
|
{% include "InvenTree/settings/setting.html" with key="REPORT_LOG_ERRORS" icon="fa-exclamation-circle" %}
|
||||||
{% include "InvenTree/settings/setting.html" with key="REPORT_ENABLE_TEST_REPORT" icon="fa-vial" %}
|
{% include "InvenTree/settings/setting.html" with key="REPORT_ENABLE_TEST_REPORT" icon="fa-vial" %}
|
||||||
{% include "InvenTree/settings/setting.html" with key="REPORT_ATTACH_TEST_REPORT" icon="fa-file-upload" %}
|
{% include "InvenTree/settings/setting.html" with key="REPORT_ATTACH_TEST_REPORT" icon="fa-file-upload" %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -40,7 +40,7 @@ margin-top: 4cm;
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block bottom_left %}
|
{% block bottom_left %}
|
||||||
content: "v{{report_revision}} - {{ date.isoformat }}";
|
content: "v{{report_revision}} - {% format_date date %}";
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block bottom_center %}
|
{% block bottom_center %}
|
||||||
|
@ -186,7 +186,7 @@ margin-top: 4cm;
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block bottom_left %}
|
{% block bottom_left %}
|
||||||
content: "v{{report_revision}} - {{ date.isoformat }}";
|
content: "v{{report_revision}} - {% format_date date %}";
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block header_content %}
|
{% block header_content %}
|
||||||
@ -230,13 +230,13 @@ content: "v{{report_revision}} - {{ date.isoformat }}";
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans "Issued" %}</th>
|
<th>{% trans "Issued" %}</th>
|
||||||
<td>{% render_date build.creation_date %}</td>
|
<td>{% format_date build.creation_date %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans "Target Date" %}</th>
|
<th>{% trans "Target Date" %}</th>
|
||||||
<td>
|
<td>
|
||||||
{% if build.target_date %}
|
{% if build.target_date %}
|
||||||
{% render_date build.target_date %}
|
{% format_date build.target_date %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<em>Not specified</em>
|
<em>Not specified</em>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -64,7 +64,7 @@ To return an element corresponding to a certain key in a container which support
|
|||||||
{% endraw %}
|
{% endraw %}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Formatting Numbers
|
## Number Formatting
|
||||||
|
|
||||||
The helper function `format_number` allows for some common number formatting options. It takes a number (or a number-like string) as an input, as well as some formatting arguments. It returns a *string* containing the formatted number:
|
The helper function `format_number` allows for some common number formatting options. It takes a number (or a number-like string) as an input, as well as some formatting arguments. It returns a *string* containing the formatted number:
|
||||||
|
|
||||||
@ -78,7 +78,33 @@ The helper function `format_number` allows for some common number formatting opt
|
|||||||
{% endraw %}
|
{% endraw %}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Rendering Currency
|
## Date Formatting
|
||||||
|
|
||||||
|
For rendering date and datetime information, the following helper functions are available:
|
||||||
|
|
||||||
|
- `format_date`: Format a date object
|
||||||
|
- `format_datetime`: Format a datetime object
|
||||||
|
|
||||||
|
Each of these helper functions takes a date or datetime object as an input, and returns a *string* containing the formatted date or datetime. The following additional arguments are available:
|
||||||
|
|
||||||
|
| Argument | Description |
|
||||||
|
| --- | --- |
|
||||||
|
| timezone | Specify the timezone to render the date in. If not specified, uses the InvenTree server timezone |
|
||||||
|
| format | Specify the format string to use for rendering the date. If not specified, uses ISO formatting. Refer to the [datetime format codes](https://docs.python.org/3/library/datetime.html#format-codes) for more information! |
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
A simple example of using the date formatting helper functions:
|
||||||
|
|
||||||
|
```html
|
||||||
|
{% raw %}
|
||||||
|
{% load report %}
|
||||||
|
Date: {% format_date my_date timezone="Australia/Sydney" %}
|
||||||
|
Datetime: {% format_datetime my_datetime format="%d-%m-%Y %H:%M%S" %}
|
||||||
|
{% endraw %}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Currency Formatting
|
||||||
|
|
||||||
The helper function `render_currency` allows for simple rendering of currency data. This function can also convert the specified amount of currency into a different target currency:
|
The helper function `render_currency` allows for simple rendering of currency data. This function can also convert the specified amount of currency into a different target currency:
|
||||||
|
|
||||||
|
@ -152,6 +152,7 @@ export default function SystemSettings() {
|
|||||||
'REPORT_ENABLE',
|
'REPORT_ENABLE',
|
||||||
'REPORT_DEFAULT_PAGE_SIZE',
|
'REPORT_DEFAULT_PAGE_SIZE',
|
||||||
'REPORT_DEBUG_MODE',
|
'REPORT_DEBUG_MODE',
|
||||||
|
'REPORT_LOG_ERRORS',
|
||||||
'REPORT_ENABLE_TEST_REPORT',
|
'REPORT_ENABLE_TEST_REPORT',
|
||||||
'REPORT_ATTACH_TEST_REPORT'
|
'REPORT_ATTACH_TEST_REPORT'
|
||||||
]}
|
]}
|
||||||
|
Loading…
Reference in New Issue
Block a user