Initial work on a localize date filter

This commit is contained in:
Trenton Holmes
2025-08-11 11:54:02 -07:00
parent b1a84c65ed
commit 5821816ebb
4 changed files with 182 additions and 5 deletions

View File

@@ -434,6 +434,151 @@ provided. The template is provided as a string, potentially multiline, and rende
In addition, the entire Document instance is available to be utilized in a more advanced way, as well as some variables which only make sense to be accessed
with more complex logic.
#### Custom Jinja2 Filters
##### Custom Field Access
The `get_cf_value` filter retrieves a value from custom field data with optional default fallback.
###### Syntax
```jinja2
{{ custom_fields | get_cf_value('field_name') }}
{{ custom_fields | get_cf_value('field_name', 'default_value') }}
```
###### Parameters
- `custom_fields`: This _must_ be the provided custom field data
- `name` (str): Name of the custom field to retrieve
- `default` (str, optional): Default value to return if field is not found or has no value
###### Returns
- `str | None`: The field value, default value, or `None` if neither exists
###### Examples
```jinja2
<!-- Basic usage -->
{{ custom_fields | get_cf_value('department') }}
<!-- With default value -->
{{ custom_fields | get_cf_value('phone', 'Not provided') }}
<!-- Handling missing fields -->
{{ custom_fields | get_cf_value('optional_field', 'N/A') }}
```
##### Datetime Formatting
The `format_datetime`filter formats a datetime string or datetime object using Python's strftime formatting.
###### Syntax
```jinja2
{{ datetime_value | format_datetime('%Y-%m-%d %H:%M:%S') }}
```
###### Parameters
- `value` (str | datetime): Date/time value to format (strings will be parsed automatically)
- `format` (str): Python strftime format string
###### Returns
- `str`: Formatted datetime string
###### Examples
```jinja2
<!-- Format datetime object -->
{{ created_at | format_datetime('%B %d, %Y at %I:%M %p') }}
<!-- Output: "January 15, 2024 at 02:30 PM" -->
<!-- Format datetime string -->
{{ "2024-01-15T14:30:00" | format_datetime('%m/%d/%Y') }}
<!-- Output: "01/15/2024" -->
<!-- Custom formatting -->
{{ timestamp | format_datetime('%A, %B %d, %Y') }}
<!-- Output: "Monday, January 15, 2024" -->
```
See the [strftime format code documentation](https://docs.python.org/3.13/library/datetime.html#strftime-and-strptime-format-codes)
for the possible codes and their meanings.
##### Date Localization
The `localize_date1 filter formats a date or datetime object into a localized string using Babel internationalization.
This takes into account the provided locale for translation.
###### Syntax
```jinja2
{{ date_value | localize_date('medium', 'en_US') }}
{{ datetime_value | localize_date('short', 'fr_FR') }}
```
###### Parameters
- `value` (date | datetime): Date or datetime object to format (datetime should be timezone-aware)
- `format` (str): Format type - either a Babel preset ('short', 'medium', 'long', 'full') or custom pattern
- `locale` (str): Locale code for localization (e.g., 'en_US', 'fr_FR', 'de_DE')
###### Returns
- `str`: Localized, formatted date string
###### Examples
```jinja2
<!-- Preset formats -->
{{ created_date | localize_date('short', 'en_US') }}
<!-- Output: "1/15/24" -->
{{ created_date | localize_date('medium', 'en_US') }}
<!-- Output: "Jan 15, 2024" -->
{{ created_date | localize_date('long', 'en_US') }}
<!-- Output: "January 15, 2024" -->
{{ created_date | localize_date('full', 'en_US') }}
<!-- Output: "Monday, January 15, 2024" -->
<!-- Different locales -->
{{ created_date | localize_date('medium', 'fr_FR') }}
<!-- Output: "15 janv. 2024" -->
{{ created_date | localize_date('medium', 'de_DE') }}
<!-- Output: "15.01.2024" -->
<!-- Custom patterns -->
{{ created_date | localize_date('dd/MM/yyyy', 'en_GB') }}
<!-- Output: "15/01/2024" -->
```
See the [supported locale codes]() for more options,
### Format Presets
- **short**: Abbreviated format (e.g., "1/15/24")
- **medium**: Medium-length format (e.g., "Jan 15, 2024")
- **long**: Long format with full month name (e.g., "January 15, 2024")
- **full**: Full format including day of week (e.g., "Monday, January 15, 2024")
## Usage Notes
- All filters handle `None` values gracefully
- Date strings are automatically parsed when needed
- Timezone-aware datetime objects are recommended for `localize_date`
- Custom field data should follow the expected dictionary structure for `get_cf_value`
- Invalid format strings will raise appropriate Python exceptions
````
#### Additional Variables
- `{{ tag_name_list }}`: A list of tag names applied to the document, ordered by the tag name. Note this is a list, not a single string
@@ -461,7 +606,7 @@ somepath/
{% endif %}
{% endif %}
/{{ title }}
```
````
For a document with an ASN of 205, it would result in `somepath/asn-201-400/asn-2xx/Title.pdf`, but
a document with an ASN of 355 would be placed in `somepath/asn-201-400/asn-3xx/Title.pdf`.

View File

@@ -15,6 +15,7 @@ classifiers = [
# This will allow testing to not install a webserver, mysql, etc
dependencies = [
"babel>=2.17",
"bleach~=6.2.0",
"celery[redis]~=5.5.1",
"channels~=4.2",

View File

@@ -2,10 +2,12 @@ import logging
import os
import re
from collections.abc import Iterable
from datetime import date
from datetime import datetime
from pathlib import PurePath
import pathvalidate
from babel import dates
from django.utils import timezone
from django.utils.dateparse import parse_date
from django.utils.text import slugify as django_slugify
@@ -90,19 +92,46 @@ def get_cf_value(
return None
_template_environment.filters["get_cf_value"] = get_cf_value
def format_datetime(value: str | datetime, format: str) -> str:
if isinstance(value, str):
value = parse_date(value)
return value.strftime(format=format)
def localize_date(value: date | datetime, format: str, locale: str) -> str:
"""
Format a date or datetime object into a localized string using Babel.
Args:
value (date | datetime): The date or datetime to format. If a datetime
is provided, it should be timezone-aware (e.g., UTC from a Django DB object).
format (str): The format to use. Can be one of Babel's preset formats
('short', 'medium', 'long', 'full') or a custom pattern string.
locale (str): The locale code (e.g., 'en_US', 'fr_FR') to use for
localization.
Returns:
str: The localized, formatted date string.
Raises:
TypeError: If `value` is not a date or datetime instance.
"""
if isinstance(value, datetime):
return dates.format_datetime(value, format=format, locale=locale)
elif isinstance(value, date):
return dates.format_date(value, format=format, locale=locale)
else:
raise TypeError(f"Unsupported type {type(value)} for localize_date")
_template_environment.filters["get_cf_value"] = get_cf_value
_template_environment.filters["datetime"] = format_datetime
_template_environment.filters["slugify"] = django_slugify
_template_environment.filters["localize_date"] = localize_date
def create_dummy_document():
"""

4
uv.lock generated
View File

@@ -1,5 +1,5 @@
version = 1
revision = 3
revision = 2
requires-python = ">=3.10"
resolution-markers = [
"sys_platform == 'darwin'",
@@ -1911,6 +1911,7 @@ name = "paperless-ngx"
version = "2.17.1"
source = { virtual = "." }
dependencies = [
{ name = "babel", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "bleach", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "celery", extra = ["redis"], marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "channels", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
@@ -2044,6 +2045,7 @@ typing = [
[package.metadata]
requires-dist = [
{ name = "babel", specifier = ">=2.17.0" },
{ name = "bleach", specifier = "~=6.2.0" },
{ name = "celery", extras = ["redis"], specifier = "~=5.5.1" },
{ name = "channels", specifier = "~=4.2" },