moves to system check, fixes issue with not being a relative path

This commit is contained in:
Trenton H 2024-10-02 10:20:08 -07:00
parent 825f3d6380
commit 7189cec0be
7 changed files with 78 additions and 50 deletions

View File

@ -8,6 +8,7 @@ from django.db.utils import OperationalError
from django.db.utils import ProgrammingError from django.db.utils import ProgrammingError
from documents.signals import document_consumer_declaration from documents.signals import document_consumer_declaration
from documents.templatetags import convert_to_django_template_format
@register() @register()
@ -69,3 +70,17 @@ def parser_check(app_configs, **kwargs):
] ]
else: else:
return [] return []
@register()
def filename_format_check(app_configs, **kwargs):
if settings.FILENAME_FORMAT:
converted_format = convert_to_django_template_format(settings.FILENAME_FORMAT)
if converted_format != settings.FILENAME_FORMAT:
return [
Error(
f"Filename format {settings.FILENAME_FORMAT} is using the old style, please update to use double curly brackets",
hint=converted_format,
),
]
return []

View File

@ -17,6 +17,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.models import Tag from documents.models import Tag
from documents.templatetags import convert_to_django_template_format
logger = logging.getLogger("paperless.filehandling") logger = logging.getLogger("paperless.filehandling")
@ -112,29 +113,6 @@ def generate_unique_filename(doc, archive_filename=False):
return new_filename return new_filename
def convert_to_django_template_format(old_format: str) -> str:
"""
Converts old Python string format (with {}) to Django template style (with {{ }}),
while ignoring existing {{ ... }} placeholders.
:param old_format: The old style format string (e.g., "{title} by {author}")
:return: Converted string in Django Template style (e.g., "{{ title }} by {{ author }}")
"""
# Step 1: Match placeholders with single curly braces but not those with double braces
pattern = r"(?<!\{)\{(\w*)\}(?!\})" # Matches {var} but not {{var}}
# Step 2: Replace the placeholders with {{ var }} or {{ }}
def replace_with_django(match):
variable = match.group(1) # The variable inside the braces
return f"{{{{ {variable} }}}}" # Convert to {{ variable }}
# Apply the substitution
converted_format = re.sub(pattern, replace_with_django, old_format)
return converted_format
def create_dummy_document(): def create_dummy_document():
""" """
Create a dummy Document instance with all possible fields filled Create a dummy Document instance with all possible fields filled
@ -258,12 +236,13 @@ def get_tags_context(tags: Iterable[Tag]) -> dict[str, str | list[str]]:
def get_custom_fields_context( def get_custom_fields_context(
custom_fields: Iterable[CustomFieldInstance], custom_fields: Iterable[CustomFieldInstance],
) -> dict[str, dict[str, str]]: ) -> dict[str, dict[str, dict[str, str]]]:
""" """
Given an Iterable of CustomFieldInstance, builds a dictionary mapping the field name Given an Iterable of CustomFieldInstance, builds a dictionary mapping the field name
to its type and value to its type and value
""" """
return { return {
"custom_fields": {
pathvalidate.sanitize_filename( pathvalidate.sanitize_filename(
field_instance.field.name, field_instance.field.name,
replacement_text="-", replacement_text="-",
@ -278,6 +257,7 @@ def get_custom_fields_context(
), ),
} }
for field_instance in custom_fields for field_instance in custom_fields
},
} }
@ -401,14 +381,6 @@ def generate_filename(
filename_format = convert_to_django_template_format( filename_format = convert_to_django_template_format(
settings.FILENAME_FORMAT, settings.FILENAME_FORMAT,
) )
# Warn the user they should update
# TODO: Move this to system check
if filename_format != settings.FILENAME_FORMAT:
logger.warning(
f"Filename format {settings.FILENAME_FORMAT} is using the old style, please update to use double curly brackets",
)
logger.info(filename_format)
else: else:
filename_format = None filename_format = None

View File

@ -10,7 +10,7 @@ def convert_from_format_to_template(apps, schema_editor):
StoragePath = apps.get_model("documents", "StoragePath") StoragePath = apps.get_model("documents", "StoragePath")
from documents.file_handling import convert_to_django_template_format from documents.templatetags import convert_to_django_template_format
with transaction.atomic(): with transaction.atomic():
for storage_path in StoragePath.objects.all(): for storage_path in StoragePath.objects.all():

View File

@ -29,8 +29,6 @@ from rest_framework import fields
from rest_framework import serializers from rest_framework import serializers
from rest_framework.fields import SerializerMethodField from rest_framework.fields import SerializerMethodField
from documents.file_handling import convert_to_django_template_format
if settings.AUDIT_LOG_ENABLED: if settings.AUDIT_LOG_ENABLED:
from auditlog.context import set_actor from auditlog.context import set_actor
@ -56,6 +54,7 @@ from documents.models import WorkflowTrigger
from documents.parsers import is_mime_type_supported from documents.parsers import is_mime_type_supported
from documents.permissions import get_groups_with_only_permission from documents.permissions import get_groups_with_only_permission
from documents.permissions import set_permissions_for_object from documents.permissions import set_permissions_for_object
from documents.templatetags import convert_to_django_template_format
from documents.validators import uri_validator from documents.validators import uri_validator
logger = logging.getLogger("paperless.serializers") logger = logging.getLogger("paperless.serializers")

View File

@ -1,3 +1,4 @@
import os
import re import re
from django import template from django import template
@ -26,7 +27,10 @@ class FilePathNode(template.Node):
""" """
value = value.replace("\n", "").replace("\r", "") value = value.replace("\n", "").replace("\r", "")
value = re.sub(r"\s*/\s*", "/", value) value = re.sub(r"\s*/\s*", "/", value)
return value.strip()
# We remove trailing and leading separators, as these are always relative paths, not absolute, even if the user
# tries
return value.strip().strip(os.sep)
output = self.nodelist.render(context) output = self.nodelist.render(context)
return clean_filepath(output) return clean_filepath(output)
@ -41,3 +45,26 @@ def construct_filepath(parser, token):
nodelist = parser.parse(("endfilepath",)) nodelist = parser.parse(("endfilepath",))
parser.delete_first_token() parser.delete_first_token()
return FilePathNode(nodelist) return FilePathNode(nodelist)
def convert_to_django_template_format(old_format: str) -> str:
"""
Converts old Python string format (with {}) to Django template style (with {{ }}),
while ignoring existing {{ ... }} placeholders.
:param old_format: The old style format string (e.g., "{title} by {author}")
:return: Converted string in Django Template style (e.g., "{{ title }} by {{ author }}")
"""
# Step 1: Match placeholders with single curly braces but not those with double braces
pattern = r"(?<!\{)\{(\w*)\}(?!\})" # Matches {var} but not {{var}}
# Step 2: Replace the placeholders with {{ var }} or {{ }}
def replace_with_django(match):
variable = match.group(1) # The variable inside the braces
return f"{{{{ {variable} }}}}" # Convert to {{ variable }}
# Apply the substitution
converted_format = re.sub(pattern, replace_with_django, old_format)
return converted_format

View File

@ -239,7 +239,7 @@ class TestApiStoragePaths(DirectoriesMixin, APITestCase):
"/{created_year_short}/{created_month}/{created_month_name}" "/{created_year_short}/{created_month}/{created_month_name}"
"/{created_month_name_short}/{created_day}/{added}/{added_year}" "/{created_month_name_short}/{created_day}/{added}/{added_year}"
"/{added_year_short}/{added_month}/{added_month_name}" "/{added_year_short}/{added_month}/{added_month_name}"
"/{added_month_name_short}/{added_day}/{asn}/{tags}" "/{added_month_name_short}/{added_day}/{asn}"
"/{tag_list}/{owner_username}/{original_name}/{doc_pk}/", "/{tag_list}/{owner_username}/{original_name}/{doc_pk}/",
}, },
), ),

View File

@ -6,6 +6,7 @@ from django.test import TestCase
from django.test import override_settings from django.test import override_settings
from documents.checks import changed_password_check from documents.checks import changed_password_check
from documents.checks import filename_format_check
from documents.checks import parser_check from documents.checks import parser_check
from documents.models import Document from documents.models import Document
from documents.tests.factories import DocumentFactory from documents.tests.factories import DocumentFactory
@ -73,3 +74,17 @@ class TestDocumentChecks(TestCase):
), ),
], ],
) )
def test_filename_format_check(self):
self.assertEqual(filename_format_check(None), [])
with override_settings(FILENAME_FORMAT="{created}/{title}"):
self.assertEqual(
filename_format_check(None),
[
Error(
"Filename format {created}/{title} is using the old style, please update to use double curly brackets",
hint="{{ created }}/{{ title }}",
),
],
)