mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Adds testing coverage plus a check for the locale being valid
This commit is contained in:
		| @@ -465,9 +465,6 @@ The `get_cf_value` filter retrieves a value from custom field data with optional | |||||||
|  |  | ||||||
| <!-- With default value --> | <!-- With default value --> | ||||||
| {{ custom_fields | get_cf_value('phone', 'Not provided') }} | {{ custom_fields | get_cf_value('phone', 'Not provided') }} | ||||||
|  |  | ||||||
| <!-- Handling missing fields --> |  | ||||||
| {{ custom_fields | get_cf_value('optional_field', 'N/A') }} |  | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| ##### Datetime Formatting | ##### Datetime Formatting | ||||||
| @@ -510,7 +507,7 @@ for the possible codes and their meanings. | |||||||
|  |  | ||||||
| ##### Date Localization | ##### Date Localization | ||||||
|  |  | ||||||
| The `localize_date1 filter formats a date or datetime object into a localized string using Babel internationalization. | 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. | This takes into account the provided locale for translation. | ||||||
|  |  | ||||||
| ###### Syntax | ###### Syntax | ||||||
| @@ -558,7 +555,7 @@ This takes into account the provided locale for translation. | |||||||
| <!-- Output: "15/01/2024" --> | <!-- Output: "15/01/2024" --> | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| See the [supported locale codes]() for more options, | See the [supported format codes](https://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns) for more options. | ||||||
|  |  | ||||||
| ### Format Presets | ### Format Presets | ||||||
|  |  | ||||||
| @@ -567,18 +564,6 @@ See the [supported locale codes]() for more options, | |||||||
| -   **long**: Long format with full month name (e.g., "January 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") | -   **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 | #### 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 | ||||||
| @@ -606,7 +591,7 @@ somepath/ | |||||||
|   {% endif %} |   {% endif %} | ||||||
| {% endif %} | {% endif %} | ||||||
| /{{ title }} | /{{ title }} | ||||||
| ```` | ``` | ||||||
|  |  | ||||||
| For a document with an ASN of 205, it would result in `somepath/asn-201-400/asn-2xx/Title.pdf`, but | 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`. | a document with an ASN of 355 would be placed in `somepath/asn-201-400/asn-3xx/Title.pdf`. | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ 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 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 | ||||||
| @@ -116,6 +117,11 @@ def localize_date(value: date | datetime, format: str, locale: str) -> str: | |||||||
|     Raises: |     Raises: | ||||||
|         TypeError: If `value` is not a date or datetime instance. |         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): |     if isinstance(value, datetime): | ||||||
|         return dates.format_datetime(value, format=format, locale=locale) |         return dates.format_datetime(value, format=format, locale=locale) | ||||||
|     elif isinstance(value, date): |     elif isinstance(value, date): | ||||||
|   | |||||||
| @@ -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,7 @@ 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.utils import DirectoriesMixin | from documents.tests.utils import DirectoriesMixin | ||||||
| from documents.tests.utils import FileSystemAssertsMixin | from documents.tests.utils import FileSystemAssertsMixin | ||||||
|  |  | ||||||
| @@ -1586,3 +1588,164 @@ 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", | ||||||
|  |                 "October", | ||||||
|  |                 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) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Trenton Holmes
					Trenton Holmes