mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Compare commits
	
		
			4 Commits
		
	
	
		
			feature-re
			...
			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