diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f18814589..60c84162e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -167,7 +167,9 @@ jobs: uses: actions/upload-artifact@v4 with: name: backend-coverage-report - path: coverage.xml + path: | + coverage.xml + junit.xml retention-days: 7 if-no-files-found: error - @@ -315,6 +317,14 @@ jobs: # future expansion flags: backend directory: src/ + - + name: Upload test results to Codecov + if: ${{ !cancelled() }} + uses: codecov/test-results-action@v1 + with: + token: ${{ secrets.CODECOV_TOKEN }} + flags: backend + directory: src/ - name: Use Node.js 20 uses: actions/setup-node@v4 diff --git a/pyproject.toml b/pyproject.toml index d1db87e70..d26d05aa3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -336,6 +336,8 @@ addopts = [ "--maxprocesses=16", "--quiet", "--durations=50", + "--junitxml=junit.xml", + "-o junit_family=legacy", ] norecursedirs = [ "src/locale/", ".venv/", "src-ui/" ] diff --git a/src-ui/package-lock.json b/src-ui/package-lock.json index 824333f27..2492a5f2d 100644 --- a/src-ui/package-lock.json +++ b/src-ui/package-lock.json @@ -44,11 +44,11 @@ "@angular-devkit/build-angular": "^19.0.4", "@angular-devkit/core": "^19.2.0", "@angular-devkit/schematics": "^19.2.0", - "@angular-eslint/builder": "19.1.0", - "@angular-eslint/eslint-plugin": "19.1.0", - "@angular-eslint/eslint-plugin-template": "19.1.0", - "@angular-eslint/schematics": "19.1.0", - "@angular-eslint/template-parser": "19.1.0", + "@angular-eslint/builder": "19.2.0", + "@angular-eslint/eslint-plugin": "19.2.0", + "@angular-eslint/eslint-plugin-template": "19.2.0", + "@angular-eslint/schematics": "19.2.0", + "@angular-eslint/template-parser": "19.2.0", "@angular/cli": "~19.2.0", "@angular/compiler-cli": "~19.2.0", "@codecov/webpack-plugin": "^1.9.0", @@ -1148,9 +1148,9 @@ } }, "node_modules/@angular-eslint/builder": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-19.1.0.tgz", - "integrity": "sha512-LWdQMTES/7GySlpTNFJn3k33ZGmjjWlHI/+IHV7B3xHQ9hj4MPK4ACmE/PNOAIQ9LwQm7sKS+3cTMxOZQ/cvSg==", + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-19.2.0.tgz", + "integrity": "sha512-8Lx24MrMJT8RlgDtwqfiLiJo4DzSaktjco6RmELUdWO2chJgRe9y+2iIgOeB2pmyD9UCsubwsfjBXlrnV/MPhQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1163,21 +1163,21 @@ } }, "node_modules/@angular-eslint/bundled-angular-compiler": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-19.1.0.tgz", - "integrity": "sha512-HUJyukRvnh8Z9lIdxdblBRuBaPYEVv4iAYZMw3d+dn4rrM27Nt5oh3/zkwYrrPkt36tZdeXdDWrOuz9jgjVN5w==", + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-19.2.0.tgz", + "integrity": "sha512-hmmAogTpYGbBvnJ0j7DNLi8YQ+YEEuwFdx0heU8XjTpZlRoSRIP7MJJVlaQCt+ZT5f5XwdGtqi9lOXqqcyGHLA==", "dev": true, "license": "MIT" }, "node_modules/@angular-eslint/eslint-plugin": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-19.1.0.tgz", - "integrity": "sha512-TDO0+Ry+oNkxnaLHogKp1k2aey6IkJef5d7hathE4UFT6owjRizltWaRoX6bGw7Qu1yagVLL8L2Se8SddxSPAQ==", + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-19.2.0.tgz", + "integrity": "sha512-QQWWDrTdJ22tBd7RLFG/FdPwNyYEhg7YwWgn29z6XcdnV00ZFtf7FRbv/te1kqVNPvfjtht7bvtHcPQ432aUdQ==", "dev": true, "license": "MIT", "dependencies": { - "@angular-eslint/bundled-angular-compiler": "19.1.0", - "@angular-eslint/utils": "19.1.0" + "@angular-eslint/bundled-angular-compiler": "19.2.0", + "@angular-eslint/utils": "19.2.0" }, "peerDependencies": { "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", @@ -1186,14 +1186,14 @@ } }, "node_modules/@angular-eslint/eslint-plugin-template": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-19.1.0.tgz", - "integrity": "sha512-bIUizkCY40mnU8oAO1tLV7uN2H/cHf1evLlhpqlb9JYwc5dT2moiEhNDo61OtOgkJmDGNuThAeO9Xk9hGQc7nA==", + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-19.2.0.tgz", + "integrity": "sha512-lUSzmk5/Dr0bNc2Omb5CZDu3zQZh70bJyuXnN5MKd00V1b3u90eqvMSveFzWFJ6Eot8Hh8+FxtiozPwGqOE+Og==", "dev": true, "license": "MIT", "dependencies": { - "@angular-eslint/bundled-angular-compiler": "19.1.0", - "@angular-eslint/utils": "19.1.0", + "@angular-eslint/bundled-angular-compiler": "19.2.0", + "@angular-eslint/utils": "19.2.0", "aria-query": "5.3.2", "axobject-query": "4.1.0" }, @@ -1204,17 +1204,47 @@ "typescript": "*" } }, + "node_modules/@angular-eslint/eslint-plugin-template/node_modules/@angular-eslint/utils": { + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-19.2.0.tgz", + "integrity": "sha512-1XQXzIqYadKUxcAgW1DPev56SVbR8Uld6TthgolU7rfIX23RYMIIRtQlrQCk7zoXLXm5fzcGqjTR4wHfoD+iWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "19.2.0" + }, + "peerDependencies": { + "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/eslint-plugin/node_modules/@angular-eslint/utils": { + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-19.2.0.tgz", + "integrity": "sha512-1XQXzIqYadKUxcAgW1DPev56SVbR8Uld6TthgolU7rfIX23RYMIIRtQlrQCk7zoXLXm5fzcGqjTR4wHfoD+iWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "19.2.0" + }, + "peerDependencies": { + "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, "node_modules/@angular-eslint/schematics": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-19.1.0.tgz", - "integrity": "sha512-6S1FjmM7rZxc0u0W0KjqWYOkFQ0q89IGyjPkdUt1a8NwRnWg3VoXp4WYfeuZOjda/FEYuBS/E6rckLAMp0h6Aw==", + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-19.2.0.tgz", + "integrity": "sha512-SQfbKgPEJNkK5TVXRsdnWp6TjvVZOczvf8lELF1n+I/Uwmp7ulUjTRgTo59ZQnXoPSs2qCPgS4gAOVR6CD91zQ==", "dev": true, "license": "MIT", "dependencies": { "@angular-devkit/core": ">= 19.0.0 < 20.0.0", "@angular-devkit/schematics": ">= 19.0.0 < 20.0.0", - "@angular-eslint/eslint-plugin": "19.1.0", - "@angular-eslint/eslint-plugin-template": "19.1.0", + "@angular-eslint/eslint-plugin": "19.2.0", + "@angular-eslint/eslint-plugin-template": "19.2.0", "ignore": "7.0.3", "semver": "7.7.1", "strip-json-comments": "3.1.1" @@ -1231,13 +1261,13 @@ } }, "node_modules/@angular-eslint/template-parser": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-19.1.0.tgz", - "integrity": "sha512-wbMi7adlC+uYqZo7NHNBShpNhFJRZsXLqihqvFpAUt1Ei6uDX8HR6MyMEDZ9tUnlqtPVW5nmbedPyLVG7HkjAA==", + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-19.2.0.tgz", + "integrity": "sha512-VqgvFrILhoMe0GHZrx+Bjy8kx7/LJfJTd+x/wzE/X1cCChSU81MBZFMVeFMnoI75OOQUf4fwaaKrtUhUvAkVyw==", "dev": true, "license": "MIT", "dependencies": { - "@angular-eslint/bundled-angular-compiler": "19.1.0", + "@angular-eslint/bundled-angular-compiler": "19.2.0", "eslint-scope": "^8.0.2" }, "peerDependencies": { @@ -1245,21 +1275,6 @@ "typescript": "*" } }, - "node_modules/@angular-eslint/utils": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-19.1.0.tgz", - "integrity": "sha512-mcb7hPMH/u6wwUwvsewrmgb9y9NWN6ZacvpUvKlTOxF/jOtTdsu0XfV4YB43sp2A8NWzYzX0Str4c8K1xSmuBQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@angular-eslint/bundled-angular-compiler": "19.1.0" - }, - "peerDependencies": { - "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": "*" - } - }, "node_modules/@angular/cdk": { "version": "19.2.1", "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-19.2.1.tgz", diff --git a/src-ui/package.json b/src-ui/package.json index c6d5a6e0c..afcba2c8f 100644 --- a/src-ui/package.json +++ b/src-ui/package.json @@ -46,11 +46,11 @@ "@angular-devkit/build-angular": "^19.0.4", "@angular-devkit/core": "^19.2.0", "@angular-devkit/schematics": "^19.2.0", - "@angular-eslint/builder": "19.1.0", - "@angular-eslint/eslint-plugin": "19.1.0", - "@angular-eslint/eslint-plugin-template": "19.1.0", - "@angular-eslint/schematics": "19.1.0", - "@angular-eslint/template-parser": "19.1.0", + "@angular-eslint/builder": "19.2.0", + "@angular-eslint/eslint-plugin": "19.2.0", + "@angular-eslint/eslint-plugin-template": "19.2.0", + "@angular-eslint/schematics": "19.2.0", + "@angular-eslint/template-parser": "19.2.0", "@angular/cli": "~19.2.0", "@angular/compiler-cli": "~19.2.0", "@codecov/webpack-plugin": "^1.9.0", diff --git a/src-ui/src/app/components/app-frame/toasts-dropdown/toasts-dropdown.component.html b/src-ui/src/app/components/app-frame/toasts-dropdown/toasts-dropdown.component.html index 6e49c1763..ee27d298a 100644 --- a/src-ui/src/app/components/app-frame/toasts-dropdown/toasts-dropdown.component.html +++ b/src-ui/src/app/components/app-frame/toasts-dropdown/toasts-dropdown.component.html @@ -21,7 +21,7 @@ }
@for (toast of toasts; track toast.id) { - + }
diff --git a/src-ui/src/app/components/common/toast/toast.component.html b/src-ui/src/app/components/common/toast/toast.component.html index fc8e85e9c..05ad7f832 100644 --- a/src-ui/src/app/components/common/toast/toast.component.html +++ b/src-ui/src/app/components/common/toast/toast.component.html @@ -51,6 +51,6 @@

} - + diff --git a/src-ui/src/app/components/common/toast/toast.component.ts b/src-ui/src/app/components/common/toast/toast.component.ts index 5ebfdbe82..5ce027a42 100644 --- a/src-ui/src/app/components/common/toast/toast.component.ts +++ b/src-ui/src/app/components/common/toast/toast.component.ts @@ -27,7 +27,7 @@ export class ToastComponent { @Output() hidden: EventEmitter = new EventEmitter() - @Output() close: EventEmitter = new EventEmitter() + @Output() closed: EventEmitter = new EventEmitter() public copied: boolean = false diff --git a/src-ui/src/app/components/common/toasts/toasts.component.html b/src-ui/src/app/components/common/toasts/toasts.component.html index 2178a2023..3bd39d1ec 100644 --- a/src-ui/src/app/components/common/toasts/toasts.component.html +++ b/src-ui/src/app/components/common/toasts/toasts.component.html @@ -1,3 +1,3 @@ @for (toast of toasts; track toast.id) { - + } diff --git a/src/documents/consumer.py b/src/documents/consumer.py index 81739fa7a..4bf9ab89b 100644 --- a/src/documents/consumer.py +++ b/src/documents/consumer.py @@ -26,7 +26,6 @@ from documents.models import CustomField from documents.models import CustomFieldInstance from documents.models import Document from documents.models import DocumentType -from documents.models import FileInfo from documents.models import StoragePath from documents.models import Tag from documents.models import WorkflowTrigger @@ -705,8 +704,6 @@ class ConsumerPlugin( ) -> Document: # If someone gave us the original filename, use it instead of doc. - file_info = FileInfo.from_filename(self.filename) - self.log.debug("Saving record to database") if self.metadata.created is not None: @@ -714,9 +711,6 @@ class ConsumerPlugin( self.log.debug( f"Creation date from post_documents parameter: {create_date}", ) - elif file_info.created is not None: - create_date = file_info.created - self.log.debug(f"Creation date from FileInfo: {create_date}") elif date is not None: create_date = date self.log.debug(f"Creation date from parse_date: {create_date}") @@ -729,7 +723,11 @@ class ConsumerPlugin( storage_type = Document.STORAGE_TYPE_UNENCRYPTED - title = file_info.title + if self.metadata.filename: + title = Path(self.metadata.filename).stem + else: + title = self.input_doc.original_file.stem + if self.metadata.title is not None: try: title = self._parse_title_placeholders(self.metadata.title) diff --git a/src/documents/filters.py b/src/documents/filters.py index d3b0ad3ce..90161a1e6 100644 --- a/src/documents/filters.py +++ b/src/documents/filters.py @@ -36,7 +36,6 @@ from documents.models import CustomField from documents.models import CustomFieldInstance from documents.models import Document from documents.models import DocumentType -from documents.models import Log from documents.models import PaperlessTask from documents.models import ShareLink from documents.models import StoragePath @@ -761,12 +760,6 @@ class DocumentFilterSet(FilterSet): } -class LogFilterSet(FilterSet): - class Meta: - model = Log - fields = {"level": INT_KWARGS, "created": DATE_KWARGS, "group": ID_KWARGS} - - class ShareLinkFilterSet(FilterSet): class Meta: model = ShareLink diff --git a/src/documents/migrations/1064_delete_log.py b/src/documents/migrations/1064_delete_log.py new file mode 100644 index 000000000..ec0830a91 --- /dev/null +++ b/src/documents/migrations/1064_delete_log.py @@ -0,0 +1,15 @@ +# Generated by Django 5.1.6 on 2025-02-28 15:19 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("documents", "1063_paperlesstask_type_alter_paperlesstask_task_name_and_more"), + ] + + operations = [ + migrations.DeleteModel( + name="Log", + ), + ] diff --git a/src/documents/models.py b/src/documents/models.py index 42e473438..e40ee8115 100644 --- a/src/documents/models.py +++ b/src/documents/models.py @@ -1,12 +1,7 @@ import datetime -import logging -import os -import re -from collections import OrderedDict from pathlib import Path from typing import Final -import dateutil.parser import pathvalidate from celery import states from django.conf import settings @@ -379,36 +374,6 @@ class Document(SoftDeleteModel, ModelWithOwner): return timezone.localdate(self.created) -class Log(models.Model): - LEVELS = ( - (logging.DEBUG, _("debug")), - (logging.INFO, _("information")), - (logging.WARNING, _("warning")), - (logging.ERROR, _("error")), - (logging.CRITICAL, _("critical")), - ) - - group = models.UUIDField(_("group"), blank=True, null=True) - - message = models.TextField(_("message")) - - level = models.PositiveIntegerField( - _("level"), - choices=LEVELS, - default=logging.INFO, - ) - - created = models.DateTimeField(_("created"), auto_now_add=True) - - class Meta: - ordering = ("-created",) - verbose_name = _("log") - verbose_name_plural = _("logs") - - def __str__(self): - return self.message - - class SavedView(ModelWithOwner): class DisplayMode(models.TextChoices): TABLE = ("table", _("Table")) @@ -548,91 +513,6 @@ class SavedViewFilterRule(models.Model): return f"SavedViewFilterRule: {self.rule_type} : {self.value}" -# TODO: why is this in the models file? -# TODO: how about, what is this and where is it documented? -# It appears to parsing JSON from an environment variable to get a title and date from -# the filename, if possible, as a higher priority than either document filename or -# content parsing -class FileInfo: - REGEXES = OrderedDict( - [ - ( - "created-title", - re.compile( - r"^(?P\d{8}(\d{6})?Z) - (?P.*)$", - flags=re.IGNORECASE, - ), - ), - ("title", re.compile(r"(?P<title>.*)$", flags=re.IGNORECASE)), - ], - ) - - def __init__( - self, - created=None, - correspondent=None, - title=None, - tags=(), - extension=None, - ): - self.created = created - self.title = title - self.extension = extension - self.correspondent = correspondent - self.tags = tags - - @classmethod - def _get_created(cls, created): - try: - return dateutil.parser.parse(f"{created[:-1]:0<14}Z") - except ValueError: - return None - - @classmethod - def _get_title(cls, title): - return title - - @classmethod - def _mangle_property(cls, properties, name): - if name in properties: - properties[name] = getattr(cls, f"_get_{name}")(properties[name]) - - @classmethod - def from_filename(cls, filename) -> "FileInfo": - # Mutate filename in-place before parsing its components - # by applying at most one of the configured transformations. - for pattern, repl in settings.FILENAME_PARSE_TRANSFORMS: - (filename, count) = pattern.subn(repl, filename) - if count: - break - - # do this after the transforms so that the transforms can do whatever - # with the file extension. - filename_no_ext = os.path.splitext(filename)[0] - - if filename_no_ext == filename and filename.startswith("."): - # This is a very special case where there is no text before the - # file type. - # TODO: this should be handled better. The ext is not removed - # because usually, files like '.pdf' are just hidden files - # with the name pdf, but in our case, its more likely that - # there's just no name to begin with. - filename = "" - # This isn't too bad either, since we'll just not match anything - # and return an empty title. TODO: actually, this is kinda bad. - else: - filename = filename_no_ext - - # Parse filename components. - for regex in cls.REGEXES.values(): - m = regex.match(filename) - if m: - properties = m.groupdict() - cls._mangle_property(properties, "created") - cls._mangle_property(properties, "title") - return cls(**properties) - - # Extending User Model Using a One-To-One Link class UiSettings(models.Model): user = models.OneToOneField( diff --git a/src/documents/signals/handlers.py b/src/documents/signals/handlers.py index 1fd34dd81..7241924e4 100644 --- a/src/documents/signals/handlers.py +++ b/src/documents/signals/handlers.py @@ -632,7 +632,7 @@ def send_webhook( else: httpx.post( url, - data=data, + content=data, files=files, headers=headers, ).raise_for_status() diff --git a/src/documents/tests/test_consumer.py b/src/documents/tests/test_consumer.py index 6f576ab24..ff684804e 100644 --- a/src/documents/tests/test_consumer.py +++ b/src/documents/tests/test_consumer.py @@ -1,12 +1,10 @@ import datetime import os -import re import shutil import stat import tempfile import zoneinfo from pathlib import Path -from unittest import TestCase as UnittestTestCase from unittest import mock from unittest.mock import MagicMock @@ -26,7 +24,6 @@ from documents.models import Correspondent from documents.models import CustomField from documents.models import Document from documents.models import DocumentType -from documents.models import FileInfo from documents.models import StoragePath from documents.models import Tag from documents.parsers import DocumentParser @@ -40,143 +37,6 @@ from paperless_mail.models import MailRule from paperless_mail.parsers import MailDocumentParser -class TestAttributes(UnittestTestCase): - TAGS = ("tag1", "tag2", "tag3") - - def _test_guess_attributes_from_name(self, filename, sender, title, tags): - file_info = FileInfo.from_filename(filename) - - if sender: - self.assertEqual(file_info.correspondent.name, sender, filename) - else: - self.assertIsNone(file_info.correspondent, filename) - - self.assertEqual(file_info.title, title, filename) - - self.assertEqual(tuple(t.name for t in file_info.tags), tags, filename) - - def test_guess_attributes_from_name_when_title_starts_with_dash(self): - self._test_guess_attributes_from_name( - "- weird but should not break.pdf", - None, - "- weird but should not break", - (), - ) - - def test_guess_attributes_from_name_when_title_ends_with_dash(self): - self._test_guess_attributes_from_name( - "weird but should not break -.pdf", - None, - "weird but should not break -", - (), - ) - - -class TestFieldPermutations(TestCase): - valid_dates = ( - "20150102030405Z", - "20150102Z", - ) - valid_correspondents = ["timmy", "Dr. McWheelie", "Dash Gor-don", "o Θεpμaoτής", ""] - valid_titles = ["title", "Title w Spaces", "Title a-dash", "Tίτλoς", ""] - valid_tags = ["tag", "tig,tag", "tag1,tag2,tag-3"] - - def _test_guessed_attributes( - self, - filename, - created=None, - correspondent=None, - title=None, - tags=None, - ): - info = FileInfo.from_filename(filename) - - # Created - if created is None: - self.assertIsNone(info.created, filename) - else: - self.assertEqual(info.created.year, int(created[:4]), filename) - self.assertEqual(info.created.month, int(created[4:6]), filename) - self.assertEqual(info.created.day, int(created[6:8]), filename) - - # Correspondent - if correspondent: - self.assertEqual(info.correspondent.name, correspondent, filename) - else: - self.assertEqual(info.correspondent, None, filename) - - # Title - self.assertEqual(info.title, title, filename) - - # Tags - if tags is None: - self.assertEqual(info.tags, (), filename) - else: - self.assertEqual([t.name for t in info.tags], tags.split(","), filename) - - def test_just_title(self): - template = "{title}.pdf" - for title in self.valid_titles: - spec = dict(title=title) - filename = template.format(**spec) - self._test_guessed_attributes(filename, **spec) - - def test_created_and_title(self): - template = "{created} - {title}.pdf" - - for created in self.valid_dates: - for title in self.valid_titles: - spec = {"created": created, "title": title} - self._test_guessed_attributes(template.format(**spec), **spec) - - def test_invalid_date_format(self): - info = FileInfo.from_filename("06112017Z - title.pdf") - self.assertEqual(info.title, "title") - self.assertIsNone(info.created) - - def test_filename_parse_transforms(self): - filename = "tag1,tag2_20190908_180610_0001.pdf" - all_patt = re.compile("^.*$") - none_patt = re.compile("$a") - re.compile("^([a-z0-9,]+)_(\\d{8})_(\\d{6})_([0-9]+)\\.") - - # No transformations configured (= default) - info = FileInfo.from_filename(filename) - self.assertEqual(info.title, "tag1,tag2_20190908_180610_0001") - self.assertEqual(info.tags, ()) - self.assertIsNone(info.created) - - # Pattern doesn't match (filename unaltered) - with self.settings(FILENAME_PARSE_TRANSFORMS=[(none_patt, "none.gif")]): - info = FileInfo.from_filename(filename) - self.assertEqual(info.title, "tag1,tag2_20190908_180610_0001") - - # Simple transformation (match all) - with self.settings(FILENAME_PARSE_TRANSFORMS=[(all_patt, "all.gif")]): - info = FileInfo.from_filename(filename) - self.assertEqual(info.title, "all") - - # Multiple transformations configured (first pattern matches) - with self.settings( - FILENAME_PARSE_TRANSFORMS=[ - (all_patt, "all.gif"), - (all_patt, "anotherall.gif"), - ], - ): - info = FileInfo.from_filename(filename) - self.assertEqual(info.title, "all") - - # Multiple transformations configured (second pattern matches) - with self.settings( - FILENAME_PARSE_TRANSFORMS=[ - (none_patt, "none.gif"), - (all_patt, "anotherall.gif"), - ], - ): - info = FileInfo.from_filename(filename) - self.assertEqual(info.title, "anotherall") - - class _BaseTestParser(DocumentParser): def get_settings(self): """ diff --git a/src/documents/tests/test_workflows.py b/src/documents/tests/test_workflows.py index b9205d4bb..94dcb7689 100644 --- a/src/documents/tests/test_workflows.py +++ b/src/documents/tests/test_workflows.py @@ -2603,7 +2603,7 @@ class TestWorkflows( mock_post.assert_called_once_with( "http://paperless-ngx.com", - data="Test message", + content="Test message", headers={}, files=None, ) diff --git a/src/paperless/settings.py b/src/paperless/settings.py index 0c8c71ab9..ff1829528 100644 --- a/src/paperless/settings.py +++ b/src/paperless/settings.py @@ -3,7 +3,6 @@ import json import math import multiprocessing import os -import re import tempfile from os import PathLike from pathlib import Path @@ -1089,11 +1088,6 @@ FILENAME_DATE_ORDER = os.getenv("PAPERLESS_FILENAME_DATE_ORDER") # fewer dates shown. NUMBER_OF_SUGGESTED_DATES = __get_int("PAPERLESS_NUMBER_OF_SUGGESTED_DATES", 3) -# Transformations applied before filename parsing -FILENAME_PARSE_TRANSFORMS = [] -for t in json.loads(os.getenv("PAPERLESS_FILENAME_PARSE_TRANSFORMS", "[]")): - FILENAME_PARSE_TRANSFORMS.append((re.compile(t["pattern"]), t["repl"])) - # Specify the filename format for out files FILENAME_FORMAT = os.getenv("PAPERLESS_FILENAME_FORMAT")