mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-11-03 03:16:10 -06:00 
			
		
		
		
	Compare commits
	
		
			4 Commits
		
	
	
		
			release-wo
			...
			912d3840df
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					912d3840df | ||
| 
						 | 
					282611a833 | ||
| 
						 | 
					7b92db189f | ||
| 
						 | 
					61813fab65 | 
@@ -434,6 +434,136 @@ 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
 | 
					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.
 | 
					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') }}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					##### 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_date` 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 format codes](https://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns) 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")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#### Additional Variables
 | 
					#### 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
 | 
					-   `{{ 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
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,6 +15,7 @@ classifiers = [
 | 
				
			|||||||
# This will allow testing to not install a webserver, mysql, etc
 | 
					# This will allow testing to not install a webserver, mysql, etc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 | 
					  "babel>=2.17",
 | 
				
			||||||
  "bleach~=6.2.0",
 | 
					  "bleach~=6.2.0",
 | 
				
			||||||
  "celery[redis]~=5.5.1",
 | 
					  "celery[redis]~=5.5.1",
 | 
				
			||||||
  "channels~=4.2",
 | 
					  "channels~=4.2",
 | 
				
			||||||
@@ -223,7 +224,7 @@ lint.isort.force-single-line = true
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
[tool.codespell]
 | 
					[tool.codespell]
 | 
				
			||||||
write-changes = true
 | 
					write-changes = true
 | 
				
			||||||
ignore-words-list = "criterias,afterall,valeu,ureue,equest,ure,assertIn"
 | 
					ignore-words-list = "criterias,afterall,valeu,ureue,equest,ure,assertIn,Oktober"
 | 
				
			||||||
skip = "src-ui/src/locale/*,src-ui/pnpm-lock.yaml,src-ui/e2e/*,src/paperless_mail/tests/samples/*,src/documents/tests/samples/*,*.po,*.json"
 | 
					skip = "src-ui/src/locale/*,src-ui/pnpm-lock.yaml,src-ui/e2e/*,src/paperless_mail/tests/samples/*,src/documents/tests/samples/*,*.po,*.json"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[tool.pytest.ini_options]
 | 
					[tool.pytest.ini_options]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,10 +2,13 @@ import logging
 | 
				
			|||||||
import os
 | 
					import os
 | 
				
			||||||
import re
 | 
					import re
 | 
				
			||||||
from collections.abc import Iterable
 | 
					from collections.abc import Iterable
 | 
				
			||||||
 | 
					from datetime import date
 | 
				
			||||||
from datetime import datetime
 | 
					from datetime import datetime
 | 
				
			||||||
from pathlib import PurePath
 | 
					from pathlib import PurePath
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import pathvalidate
 | 
					import pathvalidate
 | 
				
			||||||
 | 
					from babel import Locale
 | 
				
			||||||
 | 
					from babel import dates
 | 
				
			||||||
from django.utils import timezone
 | 
					from django.utils import timezone
 | 
				
			||||||
from django.utils.dateparse import parse_date
 | 
					from django.utils.dateparse import parse_date
 | 
				
			||||||
from django.utils.text import slugify as django_slugify
 | 
					from django.utils.text import slugify as django_slugify
 | 
				
			||||||
@@ -90,19 +93,51 @@ def get_cf_value(
 | 
				
			|||||||
    return None
 | 
					    return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
_template_environment.filters["get_cf_value"] = get_cf_value
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def format_datetime(value: str | datetime, format: str) -> str:
 | 
					def format_datetime(value: str | datetime, format: str) -> str:
 | 
				
			||||||
    if isinstance(value, str):
 | 
					    if isinstance(value, str):
 | 
				
			||||||
        value = parse_date(value)
 | 
					        value = parse_date(value)
 | 
				
			||||||
    return value.strftime(format=format)
 | 
					    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.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        Locale.parse(locale)
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        raise ValueError(f"Invalid locale identifier: {locale}") from e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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["datetime"] = format_datetime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
_template_environment.filters["slugify"] = django_slugify
 | 
					_template_environment.filters["slugify"] = django_slugify
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_template_environment.filters["localize_date"] = localize_date
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def create_dummy_document():
 | 
					def create_dummy_document():
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,6 +4,7 @@ import tempfile
 | 
				
			|||||||
from pathlib import Path
 | 
					from pathlib import Path
 | 
				
			||||||
from unittest import mock
 | 
					from unittest import mock
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import pytest
 | 
				
			||||||
from auditlog.context import disable_auditlog
 | 
					from auditlog.context import disable_auditlog
 | 
				
			||||||
from django.conf import settings
 | 
					from django.conf import settings
 | 
				
			||||||
from django.contrib.auth.models import User
 | 
					from django.contrib.auth.models import User
 | 
				
			||||||
@@ -22,6 +23,8 @@ from documents.models import Document
 | 
				
			|||||||
from documents.models import DocumentType
 | 
					from documents.models import DocumentType
 | 
				
			||||||
from documents.models import StoragePath
 | 
					from documents.models import StoragePath
 | 
				
			||||||
from documents.tasks import empty_trash
 | 
					from documents.tasks import empty_trash
 | 
				
			||||||
 | 
					from documents.templating.filepath import localize_date
 | 
				
			||||||
 | 
					from documents.tests.factories import DocumentFactory
 | 
				
			||||||
from documents.tests.utils import DirectoriesMixin
 | 
					from documents.tests.utils import DirectoriesMixin
 | 
				
			||||||
from documents.tests.utils import FileSystemAssertsMixin
 | 
					from documents.tests.utils import FileSystemAssertsMixin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1586,3 +1589,196 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase):
 | 
				
			|||||||
                generate_filename(doc),
 | 
					                generate_filename(doc),
 | 
				
			||||||
                Path("brussels-belgium/some-title-with-special-characters.pdf"),
 | 
					                Path("brussels-belgium/some-title-with-special-characters.pdf"),
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestDateLocalization:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Groups all tests related to the `localize_date` function.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    TEST_DATE = datetime.date(2023, 10, 26)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    TEST_DATETIME = datetime.datetime(
 | 
				
			||||||
 | 
					        2023,
 | 
				
			||||||
 | 
					        10,
 | 
				
			||||||
 | 
					        26,
 | 
				
			||||||
 | 
					        14,
 | 
				
			||||||
 | 
					        30,
 | 
				
			||||||
 | 
					        5,
 | 
				
			||||||
 | 
					        tzinfo=datetime.timezone.utc,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @pytest.mark.parametrize(
 | 
				
			||||||
 | 
					        "value, format_style, locale_str, expected_output",
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            pytest.param(
 | 
				
			||||||
 | 
					                TEST_DATE,
 | 
				
			||||||
 | 
					                "EEEE, MMM d, yyyy",
 | 
				
			||||||
 | 
					                "en_US",
 | 
				
			||||||
 | 
					                "Thursday, Oct 26, 2023",
 | 
				
			||||||
 | 
					                id="date-en_US-custom",
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            pytest.param(
 | 
				
			||||||
 | 
					                TEST_DATE,
 | 
				
			||||||
 | 
					                "dd.MM.yyyy",
 | 
				
			||||||
 | 
					                "de_DE",
 | 
				
			||||||
 | 
					                "26.10.2023",
 | 
				
			||||||
 | 
					                id="date-de_DE-custom",
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            # German weekday and month name translation
 | 
				
			||||||
 | 
					            pytest.param(
 | 
				
			||||||
 | 
					                TEST_DATE,
 | 
				
			||||||
 | 
					                "EEEE",
 | 
				
			||||||
 | 
					                "de_DE",
 | 
				
			||||||
 | 
					                "Donnerstag",
 | 
				
			||||||
 | 
					                id="weekday-de_DE",
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            pytest.param(
 | 
				
			||||||
 | 
					                TEST_DATE,
 | 
				
			||||||
 | 
					                "MMMM",
 | 
				
			||||||
 | 
					                "de_DE",
 | 
				
			||||||
 | 
					                "Oktober",
 | 
				
			||||||
 | 
					                id="month-de_DE",
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            # French weekday and month name translation
 | 
				
			||||||
 | 
					            pytest.param(
 | 
				
			||||||
 | 
					                TEST_DATE,
 | 
				
			||||||
 | 
					                "EEEE",
 | 
				
			||||||
 | 
					                "fr_FR",
 | 
				
			||||||
 | 
					                "jeudi",
 | 
				
			||||||
 | 
					                id="weekday-fr_FR",
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            pytest.param(
 | 
				
			||||||
 | 
					                TEST_DATE,
 | 
				
			||||||
 | 
					                "MMMM",
 | 
				
			||||||
 | 
					                "fr_FR",
 | 
				
			||||||
 | 
					                "octobre",
 | 
				
			||||||
 | 
					                id="month-fr_FR",
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    def test_localize_date_with_date_objects(
 | 
				
			||||||
 | 
					        self,
 | 
				
			||||||
 | 
					        value: datetime.date,
 | 
				
			||||||
 | 
					        format_style: str,
 | 
				
			||||||
 | 
					        locale_str: str,
 | 
				
			||||||
 | 
					        expected_output: str,
 | 
				
			||||||
 | 
					    ):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Tests `localize_date` with `date` objects across different locales and formats.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        assert localize_date(value, format_style, locale_str) == expected_output
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @pytest.mark.parametrize(
 | 
				
			||||||
 | 
					        "value, format_style, locale_str, expected_output",
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            pytest.param(
 | 
				
			||||||
 | 
					                TEST_DATETIME,
 | 
				
			||||||
 | 
					                "yyyy.MM.dd G 'at' HH:mm:ss zzz",
 | 
				
			||||||
 | 
					                "en_US",
 | 
				
			||||||
 | 
					                "2023.10.26 AD at 14:30:05 UTC",
 | 
				
			||||||
 | 
					                id="datetime-en_US-custom",
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            pytest.param(
 | 
				
			||||||
 | 
					                TEST_DATETIME,
 | 
				
			||||||
 | 
					                "dd.MM.yyyy",
 | 
				
			||||||
 | 
					                "fr_FR",
 | 
				
			||||||
 | 
					                "26.10.2023",
 | 
				
			||||||
 | 
					                id="date-fr_FR-custom",
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            # Spanish weekday and month translation
 | 
				
			||||||
 | 
					            pytest.param(
 | 
				
			||||||
 | 
					                TEST_DATETIME,
 | 
				
			||||||
 | 
					                "EEEE",
 | 
				
			||||||
 | 
					                "es_ES",
 | 
				
			||||||
 | 
					                "jueves",
 | 
				
			||||||
 | 
					                id="weekday-es_ES",
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            pytest.param(
 | 
				
			||||||
 | 
					                TEST_DATETIME,
 | 
				
			||||||
 | 
					                "MMMM",
 | 
				
			||||||
 | 
					                "es_ES",
 | 
				
			||||||
 | 
					                "octubre",
 | 
				
			||||||
 | 
					                id="month-es_ES",
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            # Italian weekday and month translation
 | 
				
			||||||
 | 
					            pytest.param(
 | 
				
			||||||
 | 
					                TEST_DATETIME,
 | 
				
			||||||
 | 
					                "EEEE",
 | 
				
			||||||
 | 
					                "it_IT",
 | 
				
			||||||
 | 
					                "giovedì",
 | 
				
			||||||
 | 
					                id="weekday-it_IT",
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            pytest.param(
 | 
				
			||||||
 | 
					                TEST_DATETIME,
 | 
				
			||||||
 | 
					                "MMMM",
 | 
				
			||||||
 | 
					                "it_IT",
 | 
				
			||||||
 | 
					                "ottobre",
 | 
				
			||||||
 | 
					                id="month-it_IT",
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    def test_localize_date_with_datetime_objects(
 | 
				
			||||||
 | 
					        self,
 | 
				
			||||||
 | 
					        value: datetime.datetime,
 | 
				
			||||||
 | 
					        format_style: str,
 | 
				
			||||||
 | 
					        locale_str: str,
 | 
				
			||||||
 | 
					        expected_output: str,
 | 
				
			||||||
 | 
					    ):
 | 
				
			||||||
 | 
					        # To handle the non-breaking space in French and other locales
 | 
				
			||||||
 | 
					        result = localize_date(value, format_style, locale_str)
 | 
				
			||||||
 | 
					        assert result.replace("\u202f", " ") == expected_output.replace("\u202f", " ")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @pytest.mark.parametrize(
 | 
				
			||||||
 | 
					        "invalid_value",
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            "2023-10-26",
 | 
				
			||||||
 | 
					            1698330605,
 | 
				
			||||||
 | 
					            None,
 | 
				
			||||||
 | 
					            [],
 | 
				
			||||||
 | 
					            {},
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    def test_localize_date_raises_type_error_for_invalid_input(self, invalid_value):
 | 
				
			||||||
 | 
					        with pytest.raises(TypeError) as excinfo:
 | 
				
			||||||
 | 
					            localize_date(invalid_value, "medium", "en_US")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert f"Unsupported type {type(invalid_value)}" in str(excinfo.value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_localize_date_raises_error_for_invalid_locale(self):
 | 
				
			||||||
 | 
					        with pytest.raises(ValueError) as excinfo:
 | 
				
			||||||
 | 
					            localize_date(self.TEST_DATE, "medium", "invalid_locale_code")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert "Invalid locale identifier" in str(excinfo.value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @pytest.mark.django_db
 | 
				
			||||||
 | 
					    @pytest.mark.parametrize(
 | 
				
			||||||
 | 
					        "filename_format,expected_filename",
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            pytest.param(
 | 
				
			||||||
 | 
					                "{{title}}_{{ document.created | localize_date('MMMM', 'es_ES')}}",
 | 
				
			||||||
 | 
					                "My Document_octubre.pdf",
 | 
				
			||||||
 | 
					                id="spanish_month_name",
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            pytest.param(
 | 
				
			||||||
 | 
					                "{{title}}_{{ document.created | localize_date('EEEE', 'fr_FR')}}",
 | 
				
			||||||
 | 
					                "My Document_jeudi.pdf",
 | 
				
			||||||
 | 
					                id="french_day_of_week",
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            pytest.param(
 | 
				
			||||||
 | 
					                "{{title}}_{{ document.created | localize_date('dd/MM/yyyy', 'en_GB')}}",
 | 
				
			||||||
 | 
					                "My Document_26/10/2023.pdf",
 | 
				
			||||||
 | 
					                id="uk_date_format",
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    def test_localize_date_path_building(self, filename_format, expected_filename):
 | 
				
			||||||
 | 
					        document = DocumentFactory.create(
 | 
				
			||||||
 | 
					            title="My Document",
 | 
				
			||||||
 | 
					            mime_type="application/pdf",
 | 
				
			||||||
 | 
					            storage_type=Document.STORAGE_TYPE_UNENCRYPTED,
 | 
				
			||||||
 | 
					            created=self.TEST_DATE,  # 2023-10-26 (which is a Thursday)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        with override_settings(FILENAME_FORMAT=filename_format):
 | 
				
			||||||
 | 
					            filename = generate_filename(document)
 | 
				
			||||||
 | 
					            assert filename == Path(expected_filename)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										4
									
								
								uv.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4
									
								
								uv.lock
									
									
									
										generated
									
									
									
								
							@@ -1,5 +1,5 @@
 | 
				
			|||||||
version = 1
 | 
					version = 1
 | 
				
			||||||
revision = 3
 | 
					revision = 2
 | 
				
			||||||
requires-python = ">=3.10"
 | 
					requires-python = ">=3.10"
 | 
				
			||||||
resolution-markers = [
 | 
					resolution-markers = [
 | 
				
			||||||
    "sys_platform == 'darwin'",
 | 
					    "sys_platform == 'darwin'",
 | 
				
			||||||
@@ -1911,6 +1911,7 @@ name = "paperless-ngx"
 | 
				
			|||||||
version = "2.17.1"
 | 
					version = "2.17.1"
 | 
				
			||||||
source = { virtual = "." }
 | 
					source = { virtual = "." }
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 | 
					    { name = "babel", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
 | 
				
			||||||
    { name = "bleach", 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 = "celery", extra = ["redis"], marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
 | 
				
			||||||
    { name = "channels", 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]
 | 
					[package.metadata]
 | 
				
			||||||
requires-dist = [
 | 
					requires-dist = [
 | 
				
			||||||
 | 
					    { name = "babel", specifier = ">=2.17.0" },
 | 
				
			||||||
    { name = "bleach", specifier = "~=6.2.0" },
 | 
					    { name = "bleach", specifier = "~=6.2.0" },
 | 
				
			||||||
    { name = "celery", extras = ["redis"], specifier = "~=5.5.1" },
 | 
					    { name = "celery", extras = ["redis"], specifier = "~=5.5.1" },
 | 
				
			||||||
    { name = "channels", specifier = "~=4.2" },
 | 
					    { name = "channels", specifier = "~=4.2" },
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user