Compare commits

...
Author SHA1 Message Date
Michael Shamoon 301ad7e07d v1.7.0-rc1 2022-04-11 21:03:02 -07:00
Trenton Holmes cc93616019 Merge pull request #691 from paperless-ngx/dependabot/pip/dev/django-4.0.4
Bump django from 4.0.3 to 4.0.4
2022-04-11 16:04:44 -07:00
dependabot[bot] 88066563d3 Bump django from 4.0.3 to 4.0.4
Bumps [django](https://github.com/django/django) from 4.0.3 to 4.0.4.
- [Release notes](https://github.com/django/django/releases)
- [Commits](https://github.com/django/django/compare/4.0.3...4.0.4)

---
updated-dependencies:
- dependency-name: django
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-11 20:37:09 +00:00
shamoon 9e3590352c Merge pull request #690 from paperless-ngx/fix-scanners-table
Fix: scanners table
2022-04-11 12:56:45 -07:00
Quinn Casey 86ad52639f Fix name 2022-04-11 12:34:37 -07:00
Quinn Casey c00be946a5 Remove explicit "no" as was done in f303a1c 2022-04-11 12:32:32 -07:00
Quinn Casey 322aeeb552 Add 994d502 2022-04-11 12:23:27 -07:00
Quinn Casey 501d4cafa9 Add f303a1c 2022-04-11 12:21:52 -07:00
Quinn Casey e556fb3e3a Add f2f7b71 2022-04-11 12:17:44 -07:00
Quinn Casey bb930297d6 Reset table to b318c0a 2022-04-11 12:12:01 -07:00
shamoon 83f10167e5 Merge pull request #483 from stumpylog/check-if-file-busy
Adds wait for file before consuming
2022-04-11 11:56:05 -07:00
Quinn Casey d47bb21389 Merge pull request #654 from paperless-ngx/fix-doc-detail-time
Fix: frontend document editing erases time data
2022-04-11 11:54:24 -07:00
Florian 4dedff00b8 Merge pull request #532 from paperless-ngx/feature-barcode-splitter
Feature barcode splitter
2022-04-11 08:51:48 +02:00
Trenton Holmes 2414dad656 Adds additional checking for both inotify and polling around document still being busy before consuming it 2022-04-10 12:21:34 -07:00
shamoon 8f98cb4860 Merge pull request #550 from stumpylog/feature-mail-consume-improve-docs
Feature mail consume improve docs
2022-04-10 08:46:45 -07:00
shamoon 6e96b7e00a Merge pull request #612 from stumpylog/bug-fix-filter-len
Increase length of SavedViewFilterRule
2022-04-10 08:46:25 -07:00
shamoon 00287b27ab Merge pull request #680 from paperless-ngx/bug-fix-fnmatch
[bug] - Fixes attachment filename matching during mail fetching
2022-04-08 13:59:22 -07:00
Trenton Holmes 8f18b7fd6c Fixes the matching of attachment files names to patterns 2022-04-08 13:42:42 -07:00
Felix E dde7771dc6 Merge pull request #674 from paperless-ngx/feature-django4-csrf
Add `PAPERLESS_URL` env variable & CSRF var
2022-04-08 21:00:57 +02:00
Quinn Casey 4165184e42 Chore: Implement release-drafter action for Changelogs (#669)
* Add inital release-drafter config

* Replace release action

* Add collapse-after 3 to dependencies
2022-04-08 11:12:19 -07:00
Michael Shamoon e4953a756a docs for PAPERLESS_URL + PAPERLESS_CSRF_TRUSTED_ORIGINS 2022-04-08 09:59:59 -07:00
Trenton Holmes 471ac63a3a Increases SavedViewFilterRule value to the maximum length 2022-04-08 08:55:58 -07:00
Trenton Holmes f358eda5c5 Updates the mail db model to use the built-in enum capabilities and runs the migrations 2022-04-08 08:55:40 -07:00
Trenton Holmes 035130ecdc Updates the folder input text and adds docs note for how to figure out the folder separator value 2022-04-08 08:55:40 -07:00
Michael Shamoon ca0e86757b add PAPERLESS_URL env variable for CSRF 2022-04-08 00:27:53 -07:00
Michael Shamoon d4153607c9 reorganizing scss for clarity
[ci skip]
2022-04-07 23:23:28 -07:00
Michael Shamoon 99d0a0845d fix scss organization
[ci skip]
2022-04-07 23:15:27 -07:00
Florian Brandes 5fae5a9ee0 fix conditional in separate_pages
add additional test for separate_pages

Signed-off-by: Florian Brandes <florian.brandes@posteo.de>
2022-04-08 08:09:48 +02:00
Michael Shamoon b5a75be1db fix error case
[ci skip]
2022-04-07 23:06:46 -07:00
Trenton Holmes eb5e0e0b9b Merge pull request #501 from stumpylog/configure-worker-retry
Adds settings for worker retry time
2022-04-07 15:24:53 -07:00
Trenton Holmes dc90f58391 Merge pull request #641 from paperless-ngx/dependabot/pip/dev/pre-commit-2.18.1
Bump pre-commit from 2.17.0 to 2.18.1
2022-04-07 14:29:08 -07:00
Michael Shamoon ca43c71cf5 fix some broken css vars 2022-04-07 14:27:49 -07:00
dependabot[bot] 931a311c48 Bump pre-commit from 2.17.0 to 2.18.1
Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 2.17.0 to 2.18.1.
- [Release notes](https://github.com/pre-commit/pre-commit/releases)
- [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md)
- [Commits](https://github.com/pre-commit/pre-commit/compare/v2.17.0...v2.18.1)

---
updated-dependencies:
- dependency-name: pre-commit
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-07 20:54:33 +00:00
Michael Shamoon 9f9d7da1ce minor css hover + focus state fixes
[ci skip]
2022-04-07 13:51:36 -07:00
Trenton Holmes d00e8d3b0f Merge pull request #649 from paperless-ngx/dependabot/pip/dev/zipp-3.8.0
Bump zipp from 3.7.0 to 3.8.0
2022-04-07 13:49:50 -07:00
Trenton Holmes d4124bae0c Merge pull request #653 from paperless-ngx/dependabot/pip/dev/tqdm-4.64.0
Bump tqdm from 4.63.1 to 4.64.0
2022-04-07 13:37:46 -07:00
shamoon 58eb2d6f63 Merge pull request #283 from paperless-ngx/feature-global-dragdrop
Feature: global drag'n'drop
2022-04-07 12:14:59 -07:00
Quinn Casey 01987f1b51 Merge pull request #667 from paperless-ngx/codeowners
Feature: Add CODEOWNERS
2022-04-07 08:31:48 -07:00
shamoon 0a35358e8d Merge pull request #666 from paperless-ngx/feature-update-base-image 2022-04-07 08:30:59 -07:00
Quinn Casey 5bacb85c33 Fix typo 2022-04-07 08:13:25 -07:00
Quinn Casey 6933ac523f Remove explicit team tagging from PR template 2022-04-07 08:13:12 -07:00
Quinn Casey ba9120b417 Add CODEOWNERS 2022-04-07 08:12:05 -07:00
dependabot[bot] 3e71f5810f Bump tqdm from 4.63.1 to 4.64.0
Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.63.1 to 4.64.0.
- [Release notes](https://github.com/tqdm/tqdm/releases)
- [Commits](https://github.com/tqdm/tqdm/compare/v4.63.1...v4.64.0)

---
updated-dependencies:
- dependency-name: tqdm
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-07 15:02:57 +00:00
Trenton Holmes 296a0edae4 Updates the base image tag to use the :dev tag from the base image dev branch 2022-04-07 08:00:22 -07:00
Florian Brandes cdf5602dfb update documentation
Signed-off-by: Florian Brandes <florian.brandes@posteo.de>
2022-04-07 11:14:29 +02:00
Florian Brandes e214f719c9 add more tests
Signed-off-by: Florian Brandes <florian.brandes@posteo.de>
2022-04-07 11:14:29 +02:00
Florian Brandes 08fbcf5158 use imported List
fail test if non-existing dir exists

Signed-off-by: Florian Brandes <florian.brandes@posteo.de>
2022-04-07 11:14:29 +02:00
Florian Brandes 10ca515ac5 addes tests:
- barcode-39
- barcode-128
- qr barcodes
- test for consumption

Signed-off-by: Florian Brandes <florian.brandes@posteo.de>
2022-04-07 11:14:29 +02:00
Florian Brandes e59a14852b code formatting
Signed-off-by: Florian Brandes <florian.brandes@posteo.de>
2022-04-07 11:14:29 +02:00
Florian Brandes c696b4f2f2 set barcode reader default to disabled
Signed-off-by: Florian Brandes <florian.brandes@posteo.de>
2022-04-07 11:14:29 +02:00
Florian Brandes 553153ba92 put code under CONSUMER_ENABLE_BARCODES
Signed-off-by: Florian Brandes <florian.brandes@posteo.de>
2022-04-07 11:14:29 +02:00
Florian Brandes 9d2bcf807e add more type hinting
Signed-off-by: Florian Brandes <florian.brandes@posteo.de>
2022-04-07 11:14:29 +02:00
Florian Brandes 422ac9befe decode bytecode in string
Signed-off-by: Florian Brandes <florian.brandes@posteo.de>
2022-04-07 11:14:29 +02:00
Florian Brandes 793f641af6 make list checks consistent
Signed-off-by: Florian Brandes <florian.brandes@posteo.de>
2022-04-07 11:14:29 +02:00
Florian Brandes 0ea5f5d584 use append method for lists
Signed-off-by: Florian Brandes <florian.brandes@posteo.de>
2022-04-07 11:14:29 +02:00
florian on nixos (Florian Brandes) c024b846c3 add config options and documentation
Signed-off-by: florian on nixos (Florian Brandes) <florian.brandes@posteo.de>
2022-04-07 11:14:29 +02:00
florian on nixos (Florian Brandes) 37b3fde4e1 pre-commit hooks
Signed-off-by: florian on nixos (Florian Brandes) <florian.brandes@posteo.de>
2022-04-07 11:14:29 +02:00
florian on nixos (Florian Brandes) e89ef5de25 renaming of documents to align with front end
Signed-off-by: florian on nixos (Florian Brandes) <florian.brandes@posteo.de>
2022-04-07 11:14:29 +02:00
florian on nixos (Florian Brandes) 06cac44d02 update ci.yml to include new dependency
Signed-off-by: florian on nixos (Florian Brandes) <florian.brandes@posteo.de>
2022-04-07 11:14:29 +02:00
florian on nixos (Florian Brandes) 488fe28ad3 flake8
Signed-off-by: florian on nixos (Florian Brandes) <florian.brandes@posteo.de>
2022-04-07 11:14:29 +02:00
florian on nixos (Florian Brandes) 50f474ae92 run black
Signed-off-by: florian on nixos (Florian Brandes) <florian.brandes@posteo.de>
2022-04-07 11:14:29 +02:00
florian on nixos (Florian Brandes) 78ca2ffaba add trailing commas
Signed-off-by: florian on nixos (Florian Brandes) <florian.brandes@posteo.de>
2022-04-07 11:14:29 +02:00
florian on nixos (Florian Brandes) 911f5bc78e reorder imports
Signed-off-by: florian on nixos (Florian Brandes) <florian.brandes@posteo.de>
2022-04-07 11:14:29 +02:00
Florian Brandes b227427916 add split logic to consume_file
Signed-off-by: florian on nixos (Florian Brandes) <florian.brandes@posteo.de>
2022-04-07 11:14:17 +02:00
florian on nixos (Florian Brandes) b5f77fd6e7 skip test if dir exists
Signed-off-by: florian on nixos (Florian Brandes) <florian.brandes@posteo.de>
2022-04-06 21:16:41 +02:00
florian on nixos (Florian Brandes) 4fe966f534 more work on barcode
Signed-off-by: florian on nixos (Florian Brandes) <florian.brandes@posteo.de>
2022-04-06 21:16:41 +02:00
florian on nixos (Florian Brandes) bcce0838dd working split pages
Signed-off-by: florian on nixos (Florian Brandes) <florian.brandes@posteo.de>
2022-04-06 21:16:41 +02:00
florian on nixos (Florian Brandes) 76e43bcb89 add first tests for barcode reader
Signed-off-by: florian on nixos (Florian Brandes) <florian.brandes@posteo.de>
2022-04-06 21:16:41 +02:00
florian on nixos (Florian Brandes) c666be32f4 add barcode packages
Signed-off-by: florian on nixos (Florian Brandes) <florian.brandes@posteo.de>
2022-04-06 21:16:41 +02:00
Trenton Holmes 2d850795d8 Merge pull request #642 from paperless-ngx/dependabot/pip/dev/ocrmypdf-13.4.2
Bump ocrmypdf from 13.4.1 to 13.4.2
2022-04-06 12:13:03 -07:00
Michael Shamoon 784982718e retain document time / tz in detail form 2022-04-05 22:11:09 -07:00
shamoon 2f8d263c9c Merge pull request #630 from paperless-ngx/fix-disableable-download-buttons
Fix: download buttons should disable while waiting
2022-04-05 19:44:43 -07:00
dependabot[bot] b214163af3 Bump ocrmypdf from 13.4.1 to 13.4.2
Bumps [ocrmypdf](https://github.com/jbarlow83/ocrmypdf) from 13.4.1 to 13.4.2.
- [Release notes](https://github.com/jbarlow83/ocrmypdf/releases)
- [Changelog](https://github.com/ocrmypdf/OCRmyPDF/blob/master/docs/release_notes.rst)
- [Commits](https://github.com/jbarlow83/ocrmypdf/compare/v13.4.1...v13.4.2)

---
updated-dependencies:
- dependency-name: ocrmypdf
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-06 02:04:09 +00:00
dependabot[bot] a4fe1000c2 Bump zipp from 3.7.0 to 3.8.0
Bumps [zipp](https://github.com/jaraco/zipp) from 3.7.0 to 3.8.0.
- [Release notes](https://github.com/jaraco/zipp/releases)
- [Changelog](https://github.com/jaraco/zipp/blob/main/CHANGES.rst)
- [Commits](https://github.com/jaraco/zipp/compare/v3.7.0...v3.8.0)

---
updated-dependencies:
- dependency-name: zipp
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-06 02:03:28 +00:00
Trenton Holmes 9a275fa4ed Merge pull request #619 from paperless-ngx/dependabot/pip/dev/pillow-9.1.0
Bump pillow from 9.0.1 to 9.1.0
2022-04-05 18:57:52 -07:00
Michael Shamoon f2c83f51de Cleanup file upload susbcriptions 2022-04-05 13:33:24 -07:00
Michael Shamoon fb76b72787 Delete upload-documents.service.spec.ts 2022-04-05 13:33:24 -07:00
Michael Shamoon bec6c4511c Cleaner activation with Angular directives 2022-04-05 13:33:24 -07:00
Michael Shamoon 77b9988d05 Refactor uploads to service, working global dd 2022-04-05 13:33:24 -07:00
Michael Shamoon 3e49f93816 100vh body height, fix filter editor horizontal overflow 2022-04-05 13:33:24 -07:00
Michael Shamoon 32f6932faf fix overlay background color in dark mode 2022-04-05 13:16:13 -07:00
Michael Shamoon db76e1d65f code formatting 2022-04-05 13:16:13 -07:00
Michael Shamoon 0136ba504b Delete upload-documents.service.spec.ts 2022-04-05 13:16:13 -07:00
Michael Shamoon 459e026f16 Add translation directive for upload message 2022-04-05 13:16:13 -07:00
Michael Shamoon b03a723c3e Fix dark mode alert close button 2022-04-05 13:16:13 -07:00
Michael Shamoon 7562636151 Cleaner activation with Angular directives 2022-04-05 13:16:13 -07:00
Michael Shamoon 3acc65ca0d Fix broken toast animations & DOM removal 2022-04-05 13:16:13 -07:00
Michael Shamoon fde0f4ca0a Add initiating toast 2022-04-05 13:16:13 -07:00
Michael Shamoon 73cab2af2d Refactor uploads to service, working global dd 2022-04-05 13:16:12 -07:00
dependabot[bot] 94d2198b30 Bump pillow from 9.0.1 to 9.1.0
Bumps [pillow](https://github.com/python-pillow/Pillow) from 9.0.1 to 9.1.0.
- [Release notes](https://github.com/python-pillow/Pillow/releases)
- [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst)
- [Commits](https://github.com/python-pillow/Pillow/compare/9.0.1...9.1.0)

---
updated-dependencies:
- dependency-name: pillow
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-05 18:20:14 +00:00
Trenton Holmes 88a67c8703 Merge pull request #618 from paperless-ngx/dependabot/pip/dev/pikepdf-5.1.1
Bump pikepdf from 5.1.0 to 5.1.1
2022-04-05 11:14:21 -07:00
Felix E ccf9b1291e Merge pull request #591 from paperless-ngx/feature-version-checker
Feature: Update checker
2022-04-05 20:11:21 +02:00
Quinn Casey 47dae716ae Merge pull request #599 from stumpylog/minor-bug-fixes
Fixes downloaded filename, add more consumer ignore settings
2022-04-05 09:56:54 -07:00
Quinn Casey ea26e1c72f Merge pull request #576 from fantasticle/dev
Update regex date match patterns
2022-04-05 09:52:27 -07:00
Quinn Casey e6d79f0673 Update docs, examples with new ignore settings 2022-04-05 09:21:18 -07:00
Quinn Casey bf7002d0ae Merge pull request #616 from paperless-ngx/fix-save-vew-modal-errors
fix some api error displaying
2022-04-05 09:10:15 -07:00
Quinn Casey cbae145da5 Merge pull request #564 from paperless-ngx/feature-password-protected-pdfs
Feature: Show prompt on password-protected pdfs
2022-04-05 08:59:29 -07:00
Trenton Holmes 88bbfe5961 Merge pull request #594 from danielBreitlauch/dev
FIX BUG: case-sensitive matching was not possible
2022-04-04 09:24:59 -07:00
Trenton Holmes d60569b6d6 Quiets the pytest output 2022-04-04 17:59:37 +02:00
Michael Shamoon 91165e80ba Remove unused default 2022-04-04 07:05:00 -07:00
dependabot[bot] a15f9552eb Bump pikepdf from 5.1.0 to 5.1.1
Bumps [pikepdf](https://github.com/pikepdf/pikepdf) from 5.1.0 to 5.1.1.
- [Release notes](https://github.com/pikepdf/pikepdf/releases)
- [Changelog](https://github.com/pikepdf/pikepdf/blob/master/docs/release_notes.rst)
- [Commits](https://github.com/pikepdf/pikepdf/compare/v5.1.0...v5.1.1)

---
updated-dependencies:
- dependency-name: pikepdf
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-03 22:20:33 +00:00
Trenton Holmes 501d225f93 Merge pull request #557 from paperless-ngx/dependabot/pip/dev/sphinx-4.5.0
Bump sphinx from 4.4.0 to 4.5.0
2022-04-03 15:08:38 -07:00
dependabot[bot] 4942f244da Bump sphinx from 4.4.0 to 4.5.0
Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 4.4.0 to 4.5.0.
- [Release notes](https://github.com/sphinx-doc/sphinx/releases)
- [Changelog](https://github.com/sphinx-doc/sphinx/blob/4.x/CHANGES)
- [Commits](https://github.com/sphinx-doc/sphinx/compare/v4.4.0...v4.5.0)

---
updated-dependencies:
- dependency-name: sphinx
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-03 20:35:06 +00:00
Trenton Holmes 1be5c9af56 Merge pull request #554 from paperless-ngx/dependabot/pip/dev/black-22.3.0
Bump black from 22.1.0 to 22.3.0
2022-04-03 13:14:03 -07:00
fantasticle 818d383f2e Merge branch 'paperless-ngx:dev' into dev 2022-04-03 16:42:51 +02:00
shamoon 5fffa32630 disable download buttons while awaiting zip 2022-04-02 22:23:42 -07:00
Michael Shamoon 19d5feb483 Fix save view error for filter_rules
see #601
2022-04-02 19:49:58 -07:00
Michael Shamoon 199fc6be94 Properly display api errors on list filter & translate error message 2022-04-02 19:45:54 -07:00
Trenton Holmes 865729c033 Merge pull request #611 from stumpylog/bug-fix-install-script
Support docker-compose v2 in install
2022-04-02 15:36:52 -07:00
Trenton Holmes 6aa9071e24 Converts the docker compose check to use if statement 2022-04-02 14:34:19 -07:00
Trenton Holmes 6db3fc2eea Fixes an issue where the install script wouldn't recognize docker compose v2 command 2022-04-02 14:34:19 -07:00
shamoon 65ffaaa67c Merge pull request #623 from paperless-ngx/feature-fix-docker-tags
Fixes the Docker image tags
2022-04-02 13:46:23 -07:00
Michael Shamoon 440467e304 Allow notify user if update checking not explicitly set 2022-04-02 10:10:49 -07:00
Fantasticle 74422dd000 update name from master to main 2022-04-02 18:19:11 +02:00
Trenton Holmes 78d663bbb4 Fixes the Docker image tags 2022-04-02 08:48:58 -07:00
Fantasticle db0a58ea04 fix link for post-consumption-example, 404d to jonas 2022-04-02 15:52:32 +02:00
fantasticle eba1e69e64 Merge branch 'paperless-ngx:dev' into dev 2022-04-02 15:49:35 +02:00
shamoonandQuinn Casey a4e7877033 Update docs/configuration.rst for consistency
Co-authored-by: Quinn Casey <quinn@quinncasey.com>
2022-04-02 01:37:37 -07:00
Michael Shamoon c62260ab02 Use GH api URL
Update configuration.rst
2022-04-02 01:36:37 -07:00
Michael Shamoon b58550bb79 refactor to update_available 2022-04-02 01:36:19 -07:00
Michael Shamoon d02c7df75c json parse version string 2022-04-02 01:36:19 -07:00
Michael Shamoon 6dbebf4806 Fix version string regex 2022-04-02 01:33:51 -07:00
Michael Shamoon 1019660f6a Add config variable for update check 2022-04-02 01:33:51 -07:00
Michael Shamoon bfd11060ec Add version check to app frame 2022-04-02 01:33:51 -07:00
Michael Shamoon 7b6dccf5ef RemoteVersionService 2022-04-02 01:33:51 -07:00
Michael Shamoon d76eccad1c remote_version API endpoint 2022-04-02 01:33:51 -07:00
Michael Shamoon 6f0ac7ae45 Update bootstrap-icons.svg 2022-04-02 01:33:50 -07:00
dependabot[bot] 8f17ed1eb9 Bump black from 22.1.0 to 22.3.0
Bumps [black](https://github.com/psf/black) from 22.1.0 to 22.3.0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/22.1.0...22.3.0)

---
updated-dependencies:
- dependency-name: black
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-02 08:29:39 +00:00
Daniel Breitlauch 2b5562e376 adding case-sensitive tests 2022-04-01 21:15:30 +02:00
Trenton Holmes 35b3216fee Fixes a couple of minor bugs in the downloading of documents and additional consumer ignore items 2022-04-01 11:12:18 -07:00
Daniel Breitlauch 6f8020e30d removing transformation to lowercase since it is not needed at all. 2022-04-01 18:26:01 +02:00
Daniel Breitlauch 0bee3901b6 Merge branch 'paperless-ngx:dev' into dev 2022-04-01 12:04:51 +02:00
Daniel Breitlauch 7106c68032 FIX BUG: case-sensitive matching was not possible 2022-04-01 12:03:05 +02:00
fantasticle be707536bd Merge branch 'paperless-ngx:dev' into dev 2022-04-01 11:35:00 +02:00
Fantasticle 4754ac2bd1 remove unnecessary import 2022-03-31 21:25:58 +02:00
Fantasticle 3cca77e748 add more tests for regex date parser, remove duplicate name 2022-03-31 21:24:57 +02:00
Trenton Holmes 4ec1aaabe6 Adds settings for worker retry time, with what should be sensible default 2022-03-31 10:24:46 -07:00
Fantasticle 0baacbef98 update new regex pattern for second boundary 2022-03-31 09:37:15 +02:00
fantasticle d8261b3359 add test for new regex 2022-03-30 23:18:03 +02:00
fantasticle 1ecb26a3fb Update regex date match patterns 2022-03-30 12:19:30 +02:00
Michael Shamoon 85bf92ad2f Show prompt on password-protected pdfs 2022-03-29 15:38:09 -07:00
83 changed files with 2452 additions and 845 deletions

No files matched your search

+2 -10
View File
@@ -1,23 +1,15 @@
<!--
<!--
Note: All PRs with code changes should be targeted to the `dev` branch, pure documentation changes can target `main`
-->
## Proposed change
<!--
Please include a summary of the change and which issue is fixed (if any) and any relevant motivation / context. List any dependencies that are required for this change. If appropriate, please include an explanation of how your poposed change can be tested. Screenshots and / or videos can also be helpful if appropriate.
Please include a summary of the change and which issue is fixed (if any) and any relevant motivation / context. List any dependencies that are required for this change. If appropriate, please include an explanation of how your proposed change can be tested. Screenshots and / or videos can also be helpful if appropriate.
-->
Fixes # (issue)
<!--
Please also tag the relevant team to help with review. You can tag any of the following:
@paperless-ngx/backend (Python / django, database, etc.)
@paperless-ngx/frontend (JavaScript/Typescript, HTML, CSS, etc.)
@paperless-ngx/ci-cd (GitHub Actions, deployment)
@paperless-ngx/test (General testing for larger PRs)
-->
## Type of change
<!--
+34
View File
@@ -0,0 +1,34 @@
categories:
- title: 'Features'
labels:
- 'enhancement'
- title: 'Bug Fixes'
labels:
- 'bug'
- title: 'Documentation'
label: 'documentation'
- title: 'Maintenance'
labels:
- 'chore'
- 'deployment'
- 'translation'
- title: 'Dependencies'
collapse-after: 3
label: 'dependencies'
include-labels:
- 'enhancement'
- 'bug'
- 'chore'
- 'deployment'
- 'translation'
- 'dependencies'
replacers: # Changes "Feature: Update checker" to "Update checker"
- search: '/Feature:|Feat:|\[feature\]/gi'
replace: ''
change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
change-title-escapes: '\<*_&#@'
tag-prefix: "ngx-"
template: |
## Changelog
$CHANGES
+13 -16
View File
@@ -132,7 +132,7 @@ jobs:
name: Install system dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -qq --no-install-recommends unpaper tesseract-ocr imagemagick ghostscript optipng
sudo apt-get install -qq --no-install-recommends unpaper tesseract-ocr imagemagick ghostscript optipng libzbar0 poppler-utils
-
name: Install Python dependencies
run: |
@@ -196,9 +196,8 @@ jobs:
images: ghcr.io/${{ github.repository }}
tags: |
type=match,pattern=ngx-(\d.\d.\d),group=1
type=semver,pattern=ngx-{{version}}
type=semver,pattern=ngx-{{major}}.{{minor}}
type=ref
type=ref,event=branch
type=ref,event=tag
-
name: Checkout
uses: actions/checkout@v3
@@ -328,24 +327,22 @@ jobs:
if [[ $GITHUB_REF == refs/tags/ngx-* ]]; then
echo ::set-output name=version::${GITHUB_REF#refs/tags/ngx-}
echo ::set-output name=prerelease::false
echo ::set-output name=body::"For a complete list of changes, see the changelog at https://paperless-ngx.readthedocs.io/en/latest/changelog.html"
elif [[ $GITHUB_REF == refs/tags/beta-* ]]; then
echo ::set-output name=version::${GITHUB_REF#refs/tags/beta-}
echo ::set-output name=prerelease::true
echo ::set-output name=body::"For a complete list of changes, see the changelog at https://github.com/paperless-ngx/paperless-ngx/blob/beta/docs/changelog.rst"
fi
-
name: Create release
id: create_release
uses: actions/create-release@v1
name: Create Release and Changelog
id: create-release
uses: release-drafter/release-drafter@v5
with:
name: Paperless-ngx ${{ steps.get_version.outputs.version }}
tag: ngx-${{ steps.get_version.outputs.version }}
version: ${{ steps.get_version.outputs.version }}
prerelease: ${{ steps.get_version.outputs.prerelease }}
publish: true # ensures release is not marked as draft
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ngx-${{ steps.get_version.outputs.version }}
release_name: Paperless-ngx ${{ steps.get_version.outputs.version }}
draft: false
prerelease: ${{ steps.get_version.outputs.prerelease }}
body: ${{ steps.get_version.outputs.body }}
-
name: Upload release archive
id: upload-release-asset
@@ -353,7 +350,7 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
upload_url: ${{ steps.create-release.outputs.upload_url }}
asset_path: ./paperless-ngx.tar.xz
asset_name: paperless-ngx-${{ steps.get_version.outputs.version }}.tar.xz
asset_content_type: application/x-xz
+3 -3
View File
@@ -5,7 +5,7 @@
repos:
# General hooks
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.1.0
rev: v4.2.0
hooks:
- id: check-docstring-first
- id: check-json
@@ -27,7 +27,7 @@ repos:
- id: check-case-conflict
- id: detect-private-key
- repo: https://github.com/pre-commit/mirrors-prettier
rev: "v2.6.1"
rev: "v2.6.2"
hooks:
- id: prettier
types_or:
@@ -47,7 +47,7 @@ repos:
- id: yesqa
exclude: "(migrations)"
- repo: https://github.com/asottile/add-trailing-comma
rev: "v2.2.1"
rev: "v2.2.2"
hooks:
- id: add-trailing-comma
exclude: "(migrations)"
+10
View File
@@ -0,0 +1,10 @@
/.github/workflows/ @paperless-ngx/ci-cd
/docker/ @paperless-ngx/ci-cd
/scripts/ @paperless-ngx/ci-cd
/src-ui/ @paperless-ngx/frontend
/src/ @paperless-ngx/backend
Pipfile* @paperless-ngx/backend
*.py @paperless-ngx/backend
requirements.txt @paperless-ngx/backend
+1 -1
View File
@@ -6,7 +6,7 @@ WORKDIR /src/src-ui
RUN npm update npm -g && npm ci --no-optional
RUN ./node_modules/.bin/ng build --configuration production
FROM ghcr.io/paperless-ngx/builder/ngx-base:1.0 as main-app
FROM ghcr.io/paperless-ngx/builder/ngx-base:1.7.0 as main-app
LABEL org.opencontainers.image.authors="paperless-ngx team <hello@paperless-ngx.com>"
LABEL org.opencontainers.image.documentation="https://paperless-ngx.readthedocs.io/en/latest/"
+4 -2
View File
@@ -22,7 +22,7 @@ gunicorn = "*"
imap-tools = "*"
langdetect = "*"
pathvalidate = "*"
pillow = "~=9.0"
pillow = "~=9.1"
# Any version update to pikepdf requires a base image update
pikepdf = "~=5.1"
python-gnupg = "*"
@@ -51,6 +51,8 @@ concurrent-log-handler = "*"
"backports.zoneinfo" = {version = "*", markers = "python_version < '3.9'"}
"importlib-resources" = {version = "*", markers = "python_version < '3.9'"}
zipp = {version = "*", markers = "python_version < '3.9'"}
pyzbar = "*"
pdf2image = "*"
[dev-packages]
coveralls = "*"
@@ -62,7 +64,7 @@ pytest-django = "*"
pytest-env = "*"
pytest-sugar = "*"
pytest-xdist = "*"
sphinx = "~=4.4.0"
sphinx = "~=4.5.0"
sphinx_rtd_theme = "*"
tox = "*"
black = "*"
Generated
+170 -178
View File
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "f33c0aeb0cb880b444efd367ad5b47358dde2a9b18b55dcd6943dd81f6c7e2a6"
"sha256": "9573af313c811561d467d814c52c6bd1439bc48e3b31d7f56afed5f0ebe4b648"
},
"pipfile-spec": 6,
"requires": {},
@@ -68,10 +68,10 @@
},
"autobahn": {
"hashes": [
"sha256:60e1f4c602aacd052ffe3d46ae40b6b75f8286b3c46922c213b523162e58c17e"
"sha256:58a887c7a196bb08d8b6624cb3695f493a9e5c9f00fd350d8d6f829b47ff9036"
],
"markers": "python_version >= '3.7'",
"version": "==22.2.2"
"version": "==22.3.2"
},
"automat": {
"hashes": [
@@ -99,7 +99,6 @@
"sha256:f04e857b59d9d1ccc39ce2da1021d196e47234873820cbeaad210724b1ee28ac",
"sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2"
],
"index": "pypi",
"markers": "python_version < '3.9'",
"version": "==0.2.1"
},
@@ -207,11 +206,11 @@
},
"click": {
"hashes": [
"sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1",
"sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"
"sha256:24e1a4a9ec5bf6299411369b208c1df2188d9eb8d916302fe6bf03faed227f1e",
"sha256:479707fe14d9ec9a0757618b7a100a0ae4c4e236fac5b7f80ca68028141a1a72"
],
"markers": "python_version >= '3.6'",
"version": "==8.0.4"
"markers": "python_version >= '3.7'",
"version": "==8.1.2"
},
"coloredlogs": {
"hashes": [
@@ -280,11 +279,11 @@
},
"django": {
"hashes": [
"sha256:1239218849e922033a35d2a2f777cb8bee18bd725416744074f455f34ff50d0c",
"sha256:77ff2e7050e3324c9b67e29b6707754566f58514112a9ac73310f60cd5261930"
"sha256:07c8638e7a7f548dc0acaaa7825d84b7bd42b10e8d22268b3d572946f1e9b687",
"sha256:4e8177858524417563cc0430f29ea249946d831eacb0068a1455686587df40b5"
],
"index": "pypi",
"version": "==4.0.3"
"version": "==4.0.4"
},
"django-cors-headers": {
"hashes": [
@@ -488,16 +487,15 @@
},
"img2pdf": {
"hashes": [
"sha256:8e51c5043efa95d751481b516071a006f87c2a4059961a9ac43ec238915de09f"
"sha256:8ec898a9646523fd3862b154f3f47cd52609c24cc3e2dc1fb5f0168f0cbe793c"
],
"version": "==0.4.3"
"version": "==0.4.4"
},
"importlib-resources": {
"hashes": [
"sha256:1b93238cbf23b4cde34240dd8321d99e9bf2eb4bc91c0c99b2886283e7baad85",
"sha256:a9dd72f6cc106aeb50f6e66b86b69b454766dd6e39b69ac68450253058706bcc"
],
"index": "pypi",
"markers": "python_version < '3.9'",
"version": "==5.6.0"
},
@@ -673,11 +671,11 @@
},
"ocrmypdf": {
"hashes": [
"sha256:201ed2f589f851be73908fce35fbb6fb05e4739289d3cd8765f9519f49ea1cd9",
"sha256:f42e60bc2b6534634dd08928584275b1c556dc875c849650afcc38f7da9e2856"
"sha256:7f0a6165b80ba1b37ce5943cf5b4faf93bf98c04c8f5157ef83c5f292491485f",
"sha256:d52410bc38cf5b66da27668e38c66ac41fd3136457c1ec388b311f0a78ee213c"
],
"index": "pypi",
"version": "==13.4.1"
"version": "==13.4.2"
},
"packaging": {
"hashes": [
@@ -695,87 +693,98 @@
"index": "pypi",
"version": "==2.5.0"
},
"pdfminer.six": {
"pdf2image": {
"hashes": [
"sha256:0351f17d362ee2d48b158be52bcde6576d96460efd038a3e89a043fba6d634d7",
"sha256:d3efb75c0249b51c1bf795e3a8bddf1726b276c77bf75fb136adea471ee2825b"
"sha256:84f79f2b8fad943e36323ea4e937fcb05f26ded0caa0a01181df66049e42fb65",
"sha256:d58ed94d978a70c73c2bb7fdf8acbaf2a7089c29ff8141be5f45433c0c4293bb"
],
"index": "pypi",
"version": "==20211012"
"version": "==1.16.0"
},
"pdfminer.six": {
"hashes": [
"sha256:af0630f98a292bad4170f54e80f82ca81b916dd0b2c996437ec45c02f11d8762",
"sha256:eff2ce0abeaa4df94dc3461f70eab104487c7b4a2b3c7e9fd0aeec6c5f44d6a6"
],
"index": "pypi",
"version": "==20220319"
},
"pikepdf": {
"hashes": [
"sha256:1567b74d15c16d1bef56a6d5f56fb6a35f4cc022ae252d35f33f56bb16b87966",
"sha256:179f24b4a4d0e89c7f592d85ba4daf7d34c709eb0a691425b881413fdce70734",
"sha256:22201b06db627a86cc91c1b76491dd9c57fce2df4e3bc8ba700ff66f7f7da04a",
"sha256:3e1a0b9ecf5d4aa106c3c0db558952f9a15f343f812c3bba6d6e1a56e25224ed",
"sha256:45ce2479e5ba74896ef389abf92831d7fbb34f25e6557adb9115710223a0bb13",
"sha256:4fa5c8494b011b19bd198dd9c3cd94676b905360b2231ad35171ae586644b823",
"sha256:559b3d502cc1a6813cbcb0766b0797fec034303f8f9b0734cf938fb1734e2b74",
"sha256:61731fceaab99850bc7045232301c2332bba727f78b53f7038fcbdcaf3d64309",
"sha256:6460d489341e7f8dc3f6b0dbf1f5a75a918ebd1e0ecb4c2b00877264a68ee1f4",
"sha256:716ca6fc8947502cb73a517c884066afa132ca998e085a309b8fb8c5796d6277",
"sha256:726450eb9baaad5697687c2621d481c80f868b68c06d2cff4be3f6a7ce28cfee",
"sha256:7e9c247ca384ad1606281eda4d841bc8cbff90875979ac3b520bcc5404bf9b26",
"sha256:8333d813b452daa4a066e135fd7ab6f7c07ccc02cb8381455d61d74f0a0ad0fe",
"sha256:83d0af374b103934de033f096205143fa9d6f78e789ba78c8aa6dfb0e5b73bc3",
"sha256:878c1c95298486d8cea7e8236c70613e7eae1426cbf362c3883ecd06e8f9c2d9",
"sha256:9abef24d929c4a08292dc4be4d6c4e5bf93832e747eef5f39e854348a332f46a",
"sha256:a474241dbeda246356b6448f607f4fb9fee5b9f5cb19511a768b88b471325865",
"sha256:a553fd06e5f6e78c5e840d066e7c8b1a988e16489fe0bd4a143ca601809ea4dd",
"sha256:a6154c6bb7606ef534444f54271a410a6337cee54dcdef20f0fa0686f622cf50",
"sha256:ac0082379cf6aa6c0c682bee4a3d2adbe6d60b9125e3632876d7c5e9665c07ab",
"sha256:c22e3fbfb76ad7838dde82c8d9fafd4c09fd419cee531b31d1b48a07344ac2b4",
"sha256:d42c52ff2e8fb00fad14182f67a8f076f38c75a874123b6776aa5c6af09e1126",
"sha256:d6ef14b722f80351e15c9163e0aa1f1df84f065c080765f8232ec51af6bd3368",
"sha256:d71a38445c80972572248815ebe61f9c814c53925a6b83b6596f3482a98a5f25",
"sha256:e81ebbdd53257f411827bbb301900cdbdca34ca60b4f7248f80c6e6980062498",
"sha256:f52f4ed655c25c408e8454cbcf0e7223b62f635d1e8ab738fd0c2d46531d28ed",
"sha256:fb407ae5820ee0bf71022d5a8f539d709dac590443270a13baf6a8872c76d46f"
"sha256:01be838a44430c4be84b748a33950fed09892472934a8041596c11189f365f7f",
"sha256:0cc95ef470169dfa5acc9196299bdba236716234a0d8b2746e2a563bc6f1f456",
"sha256:13e72d0aeeb3fc452569a3f7994acdd007de9aad804ced734d57cec269261b8b",
"sha256:2873503522ef26a09a6020c29c2efd221fa2ddc31e83bd902be27d317144cf63",
"sha256:2d5d6d3248b33ca5961d84bc3121a299cd27237fad56868d815e381c9a98d3d1",
"sha256:2f62e6c7bcf5d631e6ea74cf861f3e816f587c6ccb4ecbf6ac862e088ba2e4ac",
"sha256:51694d3d2f90510da6a8d7a4d07313ca868b373fffec6de270d9bbff1ce37180",
"sha256:5c23cbd7ae71f08fb5b5d9660eb0bc61abf345ada01bea6e1b6884c4261e17d6",
"sha256:6371bf02a436be2b7c63322b83a8e47523f2cd16438b2e93d546c7caf9ae308d",
"sha256:657293b74af8c7cf03f9905218a7935b26a4f3006803016b40b3db78e04cb35c",
"sha256:680d47377bb9fd6a36b6a81464ee269b4b29cbf29a84ae4f2ab8f6ea3665bf69",
"sha256:710535c679ab0d7b8249f72247832773e7a9a121dfbe9cad7f6465bd9bb45fae",
"sha256:7b4d7c09036d863915cb01007ca183d6fe64e2d57c0472453097bc9e029a58fb",
"sha256:978b6388ae99a024bdcae5a322c68e90c187cb568d09d43e6586b3479267121d",
"sha256:9917a03d500aab72715a9236136af7a5c8c7b26c034bf71ebdf028e177f0d25f",
"sha256:996faa6b119488f96d7271672a22af86e56e5544ec6b8eae6cd7d4432c70ae2d",
"sha256:9bac9e9d6b28dc0cc5a554051f183fbd070d0f9fe63c4e9aca939b8c44a5bb4d",
"sha256:aac14061de06843759ea6f5777fd8d7b71af808ed9264f57483a3311a09788ab",
"sha256:ad5361c3669fc0c8dbaf8fa0a590bddf59fad256bb2c527d5ce5cf991743a240",
"sha256:bc40b30c37f8f7c5bef873eca1f04e91ce34b6b74507d8d0019238a17d281fdc",
"sha256:bd9faae19787a5d05b9fcbe84d7cfe4d44e318068e06eca18906b9dba45425b6",
"sha256:c64e7905ec438b7a6c12626f2859df87f471892fab75b65b1441d9e1b38b4dde",
"sha256:d4db409b21a8ec0d3a79d2bbd894b997b13223c9ccf341cdc31b64360f1ee4c7",
"sha256:e0b635d6d9faefb4d0d32722279b8eb4e4d5d7b596c426f3433343de65e0c772",
"sha256:e62e9e8afe77fe2f06715faf10f38a4810d282d66f1e9e05208bb8d9723e6acf",
"sha256:f85d309bcfeeb3e2d344346a5050bfc41e332f19d390f79c20e4fc7de4b10a17",
"sha256:fe3fc2efe498aba6204b85c17c6a5d54ab7303354ecc5c3da624a6b6af0b3406"
],
"index": "pypi",
"version": "==5.1.0"
"version": "==5.1.1"
},
"pillow": {
"hashes": [
"sha256:011233e0c42a4a7836498e98c1acf5e744c96a67dd5032a6f666cc1fb97eab97",
"sha256:0f29d831e2151e0b7b39981756d201f7108d3d215896212ffe2e992d06bfe049",
"sha256:12875d118f21cf35604176872447cdb57b07126750a33748bac15e77f90f1f9c",
"sha256:14d4b1341ac07ae07eb2cc682f459bec932a380c3b122f5540432d8977e64eae",
"sha256:1c3c33ac69cf059bbb9d1a71eeaba76781b450bc307e2291f8a4764d779a6b28",
"sha256:1d19397351f73a88904ad1aee421e800fe4bbcd1aeee6435fb62d0a05ccd1030",
"sha256:253e8a302a96df6927310a9d44e6103055e8fb96a6822f8b7f514bb7ef77de56",
"sha256:2632d0f846b7c7600edf53c48f8f9f1e13e62f66a6dbc15191029d950bfed976",
"sha256:335ace1a22325395c4ea88e00ba3dc89ca029bd66bd5a3c382d53e44f0ccd77e",
"sha256:413ce0bbf9fc6278b2d63309dfeefe452835e1c78398efb431bab0672fe9274e",
"sha256:5100b45a4638e3c00e4d2320d3193bdabb2d75e79793af7c3eb139e4f569f16f",
"sha256:514ceac913076feefbeaf89771fd6febde78b0c4c1b23aaeab082c41c694e81b",
"sha256:528a2a692c65dd5cafc130de286030af251d2ee0483a5bf50c9348aefe834e8a",
"sha256:6295f6763749b89c994fcb6d8a7f7ce03c3992e695f89f00b741b4580b199b7e",
"sha256:6c8bc8238a7dfdaf7a75f5ec5a663f4173f8c367e5a39f87e720495e1eed75fa",
"sha256:718856856ba31f14f13ba885ff13874be7fefc53984d2832458f12c38205f7f7",
"sha256:7f7609a718b177bf171ac93cea9fd2ddc0e03e84d8fa4e887bdfc39671d46b00",
"sha256:80ca33961ced9c63358056bd08403ff866512038883e74f3a4bf88ad3eb66838",
"sha256:80fe64a6deb6fcfdf7b8386f2cf216d329be6f2781f7d90304351811fb591360",
"sha256:81c4b81611e3a3cb30e59b0cf05b888c675f97e3adb2c8672c3154047980726b",
"sha256:855c583f268edde09474b081e3ddcd5cf3b20c12f26e0d434e1386cc5d318e7a",
"sha256:9bfdb82cdfeccec50aad441afc332faf8606dfa5e8efd18a6692b5d6e79f00fd",
"sha256:a5d24e1d674dd9d72c66ad3ea9131322819ff86250b30dc5821cbafcfa0b96b4",
"sha256:a9f44cd7e162ac6191491d7249cceb02b8116b0f7e847ee33f739d7cb1ea1f70",
"sha256:b5b3f092fe345c03bca1e0b687dfbb39364b21ebb8ba90e3fa707374b7915204",
"sha256:b9618823bd237c0d2575283f2939655f54d51b4527ec3972907a927acbcc5bfc",
"sha256:cef9c85ccbe9bee00909758936ea841ef12035296c748aaceee535969e27d31b",
"sha256:d21237d0cd37acded35154e29aec853e945950321dd2ffd1a7d86fe686814669",
"sha256:d3c5c79ab7dfce6d88f1ba639b77e77a17ea33a01b07b99840d6ed08031cb2a7",
"sha256:d9d7942b624b04b895cb95af03a23407f17646815495ce4547f0e60e0b06f58e",
"sha256:db6d9fac65bd08cea7f3540b899977c6dee9edad959fa4eaf305940d9cbd861c",
"sha256:ede5af4a2702444a832a800b8eb7f0a7a1c0eed55b644642e049c98d589e5092",
"sha256:effb7749713d5317478bb3acb3f81d9d7c7f86726d41c1facca068a04cf5bb4c",
"sha256:f154d173286a5d1863637a7dcd8c3437bb557520b01bddb0be0258dcb72696b5",
"sha256:f25ed6e28ddf50de7e7ea99d7a976d6a9c415f03adcaac9c41ff6ff41b6d86ac"
"sha256:01ce45deec9df310cbbee11104bae1a2a43308dd9c317f99235b6d3080ddd66e",
"sha256:0c51cb9edac8a5abd069fd0758ac0a8bfe52c261ee0e330f363548aca6893595",
"sha256:17869489de2fce6c36690a0c721bd3db176194af5f39249c1ac56d0bb0fcc512",
"sha256:21dee8466b42912335151d24c1665fcf44dc2ee47e021d233a40c3ca5adae59c",
"sha256:25023a6209a4d7c42154073144608c9a71d3512b648a2f5d4465182cb93d3477",
"sha256:255c9d69754a4c90b0ee484967fc8818c7ff8311c6dddcc43a4340e10cd1636a",
"sha256:35be4a9f65441d9982240e6966c1eaa1c654c4e5e931eaf580130409e31804d4",
"sha256:3f42364485bfdab19c1373b5cd62f7c5ab7cc052e19644862ec8f15bb8af289e",
"sha256:3fddcdb619ba04491e8f771636583a7cc5a5051cd193ff1aa1ee8616d2a692c5",
"sha256:463acf531f5d0925ca55904fa668bb3461c3ef6bc779e1d6d8a488092bdee378",
"sha256:4fe29a070de394e449fd88ebe1624d1e2d7ddeed4c12e0b31624561b58948d9a",
"sha256:55dd1cf09a1fd7c7b78425967aacae9b0d70125f7d3ab973fadc7b5abc3de652",
"sha256:5a3ecc026ea0e14d0ad7cd990ea7f48bfcb3eb4271034657dc9d06933c6629a7",
"sha256:5cfca31ab4c13552a0f354c87fbd7f162a4fafd25e6b521bba93a57fe6a3700a",
"sha256:66822d01e82506a19407d1afc104c3fcea3b81d5eb11485e593ad6b8492f995a",
"sha256:69e5ddc609230d4408277af135c5b5c8fe7a54b2bdb8ad7c5100b86b3aab04c6",
"sha256:6b6d4050b208c8ff886fd3db6690bf04f9a48749d78b41b7a5bf24c236ab0165",
"sha256:7a053bd4d65a3294b153bdd7724dce864a1d548416a5ef61f6d03bf149205160",
"sha256:82283af99c1c3a5ba1da44c67296d5aad19f11c535b551a5ae55328a317ce331",
"sha256:8782189c796eff29dbb37dd87afa4ad4d40fc90b2742704f94812851b725964b",
"sha256:8d79c6f468215d1a8415aa53d9868a6b40c4682165b8cb62a221b1baa47db458",
"sha256:97bda660702a856c2c9e12ec26fc6d187631ddfd896ff685814ab21ef0597033",
"sha256:a325ac71914c5c043fa50441b36606e64a10cd262de12f7a179620f579752ff8",
"sha256:a336a4f74baf67e26f3acc4d61c913e378e931817cd1e2ef4dfb79d3e051b481",
"sha256:a598d8830f6ef5501002ae85c7dbfcd9c27cc4efc02a1989369303ba85573e58",
"sha256:a5eaf3b42df2bcda61c53a742ee2c6e63f777d0e085bbc6b2ab7ed57deb13db7",
"sha256:aea7ce61328e15943d7b9eaca87e81f7c62ff90f669116f857262e9da4057ba3",
"sha256:af79d3fde1fc2e33561166d62e3b63f0cc3e47b5a3a2e5fea40d4917754734ea",
"sha256:c24f718f9dd73bb2b31a6201e6db5ea4a61fdd1d1c200f43ee585fc6dcd21b34",
"sha256:c5b0ff59785d93b3437c3703e3c64c178aabada51dea2a7f2c5eccf1bcf565a3",
"sha256:c7110ec1701b0bf8df569a7592a196c9d07c764a0a74f65471ea56816f10e2c8",
"sha256:c870193cce4b76713a2b29be5d8327c8ccbe0d4a49bc22968aa1e680930f5581",
"sha256:c9efef876c21788366ea1f50ecb39d5d6f65febe25ad1d4c0b8dff98843ac244",
"sha256:de344bcf6e2463bb25179d74d6e7989e375f906bcec8cb86edb8b12acbc7dfef",
"sha256:eb1b89b11256b5b6cad5e7593f9061ac4624f7651f7a8eb4dfa37caa1dfaa4d0",
"sha256:ed742214068efa95e9844c2d9129e209ed63f61baa4d54dbf4cf8b5e2d30ccf2",
"sha256:f401ed2bbb155e1ade150ccc63db1a4f6c1909d3d378f7d1235a44e90d75fb97",
"sha256:fb89397013cf302f282f0fc998bb7abf11d49dcff72c8ecb320f76ea6e2c5717"
],
"index": "pypi",
"version": "==9.0.1"
"version": "==9.1.0"
},
"pluggy": {
"hashes": [
@@ -862,11 +871,11 @@
},
"pyparsing": {
"hashes": [
"sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea",
"sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"
"sha256:7bf433498c016c4314268d95df76c81b842a4cb2b276fa3312cfb1e1d85f6954",
"sha256:ef7b523f6356f763771559412c0d7134753f037822dad1b16945b7b846f7ad06"
],
"markers": "python_version >= '3.6'",
"version": "==3.0.7"
"markers": "python_full_version >= '3.6.8'",
"version": "==3.0.8"
},
"python-dateutil": {
"hashes": [
@@ -959,6 +968,15 @@
],
"version": "==6.0"
},
"pyzbar": {
"hashes": [
"sha256:13e3ee5a2f3a545204a285f41814d5c0db571967e8d4af8699a03afc55182a9c",
"sha256:4559628b8192feb25766d954b36a3753baaf5c97c03135aec7e4a026036b475d",
"sha256:8f4c5264c9c7c6b9f20d01efc52a4eba1ded47d9ba857a94130afe33703eb518"
],
"index": "pypi",
"version": "==0.1.9"
},
"redis": {
"hashes": [
"sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2",
@@ -1173,11 +1191,11 @@
},
"setuptools": {
"hashes": [
"sha256:89eef7b71423ab7fccc7dfafdc145410ef170c4a89567427f932448135e08cdf",
"sha256:92b15f45ab164eb0c410d2bf661a6e9d15e3b78c0dffb0325f2bf0f313071cae"
"sha256:26ead7d1f93efc0f8c804d9fafafbe4a44b179580a7105754b245155f9af05a8",
"sha256:47c7b0c0f8fc10eec4cf1e71c6fdadf8decaa74ffa087e68cd1c20db7ad6a592"
],
"markers": "python_version >= '3.7'",
"version": "==61.1.1"
"version": "==62.1.0"
},
"six": {
"hashes": [
@@ -1220,22 +1238,22 @@
},
"tqdm": {
"hashes": [
"sha256:4230a49119a416c88cc47d0d2d32d5d90f1a282d5e497d49801950704e49863d",
"sha256:6461b009d6792008d0000e1b0c7ca50195ec78c0e808a3a6b668a56a3236c3a5"
"sha256:40be55d30e200777a307a7585aee69e4eabb46b4ec6a4b4a5f2d9f11e7d5408d",
"sha256:74a2cdefe14d11442cedf3ba4e21a3b84ff9a2dbdc6cfae2c34addb2a14a5ea6"
],
"index": "pypi",
"version": "==4.63.1"
"version": "==4.64.0"
},
"twisted": {
"extras": [
"tls"
],
"hashes": [
"sha256:57f32b1f6838facb8c004c89467840367ad38e9e535f8252091345dba500b4f2",
"sha256:5c63c149eb6b8fe1e32a0215b1cef96fabdba04f705d8efb9174b1ccf5b49d49"
"sha256:a047990f57dfae1e0bd2b7df2526d4f16dcdc843774dc108b78c52f2a5f13680",
"sha256:f9f7a91f94932477a9fc3b169d57f54f96c6e74a23d78d9ce54039a7f48928a2"
],
"markers": "python_full_version >= '3.6.7'",
"version": "==22.2.0"
"version": "==22.4.0"
},
"txaio": {
"hashes": [
@@ -1263,11 +1281,11 @@
},
"tzlocal": {
"hashes": [
"sha256:0f28015ac68a5c067210400a9197fc5d36ba9bc3f8eaf1da3cbd59acdfed9e09",
"sha256:28ba8d9fcb6c9a782d6e0078b4f6627af1ea26aeaa32b4eab5324abc7df4149f"
"sha256:89885494684c929d9191c57aa27502afc87a579be5cdd3225c77c463ea043745",
"sha256:ee5842fa3a795f023514ac2d801c4a81d1743bbe642e3940143326b3a00addd7"
],
"markers": "python_version >= '3.6'",
"version": "==4.1"
"version": "==4.2"
},
"urllib3": {
"hashes": [
@@ -1341,10 +1359,10 @@
},
"watchgod": {
"hashes": [
"sha256:4ba20c2fa3e63df706ab50e694b9453b05395fadb7cbbfd984d71fb1547d485d",
"sha256:c12d15f3df7d11e740704e45398277f75f1d78f46ad59ca9d7505bfd8b8d3086"
"sha256:2f3e8137d98f493ff58af54ea00f4d1433a6afe2ed08ab331a657df468c6bfce",
"sha256:cb11ff66657befba94d828e3b622d5fb76f22fbda1376f355f3e6e51e97d9450"
],
"version": "==0.8.1"
"version": "==0.8.2"
},
"wcwidth": {
"hashes": [
@@ -1425,12 +1443,11 @@
},
"zipp": {
"hashes": [
"sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d",
"sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375"
"sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad",
"sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"
],
"index": "pypi",
"markers": "python_version < '3.9'",
"version": "==3.7.0"
"version": "==3.8.0"
},
"zope.interface": {
"hashes": [
@@ -1516,32 +1533,32 @@
},
"black": {
"hashes": [
"sha256:07e5c049442d7ca1a2fc273c79d1aecbbf1bc858f62e8184abe1ad175c4f7cc2",
"sha256:0e21e1f1efa65a50e3960edd068b6ae6d64ad6235bd8bfea116a03b21836af71",
"sha256:1297c63b9e1b96a3d0da2d85d11cd9bf8664251fd69ddac068b98dc4f34f73b6",
"sha256:228b5ae2c8e3d6227e4bde5920d2fc66cc3400fde7bcc74f480cb07ef0b570d5",
"sha256:2d6f331c02f0f40aa51a22e479c8209d37fcd520c77721c034517d44eecf5912",
"sha256:2ff96450d3ad9ea499fc4c60e425a1439c2120cbbc1ab959ff20f7c76ec7e866",
"sha256:3524739d76b6b3ed1132422bf9d82123cd1705086723bc3e235ca39fd21c667d",
"sha256:35944b7100af4a985abfcaa860b06af15590deb1f392f06c8683b4381e8eeaf0",
"sha256:373922fc66676133ddc3e754e4509196a8c392fec3f5ca4486673e685a421321",
"sha256:5fa1db02410b1924b6749c245ab38d30621564e658297484952f3d8a39fce7e8",
"sha256:6f2f01381f91c1efb1451998bd65a129b3ed6f64f79663a55fe0e9b74a5f81fd",
"sha256:742ce9af3086e5bd07e58c8feb09dbb2b047b7f566eb5f5bc63fd455814979f3",
"sha256:7835fee5238fc0a0baf6c9268fb816b5f5cd9b8793423a75e8cd663c48d073ba",
"sha256:8871fcb4b447206904932b54b567923e5be802b9b19b744fdff092bd2f3118d0",
"sha256:a7c0192d35635f6fc1174be575cb7915e92e5dd629ee79fdaf0dcfa41a80afb5",
"sha256:b1a5ed73ab4c482208d20434f700d514f66ffe2840f63a6252ecc43a9bc77e8a",
"sha256:c8226f50b8c34a14608b848dc23a46e5d08397d009446353dad45e04af0c8e28",
"sha256:ccad888050f5393f0d6029deea2a33e5ae371fd182a697313bdbd835d3edaf9c",
"sha256:dae63f2dbf82882fa3b2a3c49c32bffe144970a573cd68d247af6560fc493ae1",
"sha256:e2f69158a7d120fd641d1fa9a921d898e20d52e44a74a6fbbcc570a62a6bc8ab",
"sha256:efbadd9b52c060a8fc3b9658744091cb33c31f830b3f074422ed27bad2b18e8f",
"sha256:f5660feab44c2e3cb24b2419b998846cbb01c23c7fe645fee45087efa3da2d61",
"sha256:fdb8754b453fb15fad3f72cd9cad3e16776f0964d67cf30ebcbf10327a3777a3"
"sha256:06f9d8846f2340dfac80ceb20200ea5d1b3f181dd0556b47af4e8e0b24fa0a6b",
"sha256:10dbe6e6d2988049b4655b2b739f98785a884d4d6b85bc35133a8fb9a2233176",
"sha256:2497f9c2386572e28921fa8bec7be3e51de6801f7459dffd6e62492531c47e09",
"sha256:30d78ba6bf080eeaf0b7b875d924b15cd46fec5fd044ddfbad38c8ea9171043a",
"sha256:328efc0cc70ccb23429d6be184a15ce613f676bdfc85e5fe8ea2a9354b4e9015",
"sha256:35020b8886c022ced9282b51b5a875b6d1ab0c387b31a065b84db7c33085ca79",
"sha256:5795a0375eb87bfe902e80e0c8cfaedf8af4d49694d69161e5bd3206c18618bb",
"sha256:5891ef8abc06576985de8fa88e95ab70641de6c1fca97e2a15820a9b69e51b20",
"sha256:637a4014c63fbf42a692d22b55d8ad6968a946b4a6ebc385c5505d9625b6a464",
"sha256:67c8301ec94e3bcc8906740fe071391bce40a862b7be0b86fb5382beefecd968",
"sha256:6d2fc92002d44746d3e7db7cf9313cf4452f43e9ea77a2c939defce3b10b5c82",
"sha256:6ee227b696ca60dd1c507be80a6bc849a5a6ab57ac7352aad1ffec9e8b805f21",
"sha256:863714200ada56cbc366dc9ae5291ceb936573155f8bf8e9de92aef51f3ad0f0",
"sha256:9b542ced1ec0ceeff5b37d69838106a6348e60db7b8fdd245294dc1d26136265",
"sha256:a6342964b43a99dbc72f72812bf88cad8f0217ae9acb47c0d4f141a6416d2d7b",
"sha256:ad4efa5fad66b903b4a5f96d91461d90b9507a812b3c5de657d544215bb7877a",
"sha256:bc58025940a896d7e5356952228b68f793cf5fcb342be703c3a2669a1488cb72",
"sha256:cc1e1de68c8e5444e8f94c3670bb48a2beef0e91dddfd4fcc29595ebd90bb9ce",
"sha256:cee3e11161dde1b2a33a904b850b0899e0424cc331b7295f2a9698e79f9a69a0",
"sha256:e3556168e2e5c49629f7b0f377070240bd5511e45e25a4497bb0073d9dda776a",
"sha256:e8477ec6bbfe0312c128e74644ac8a02ca06bcdb8982d4ee06f209be28cdf163",
"sha256:ee8f1f7228cce7dffc2b464f07ce769f478968bfb3dd1254a4c2eeed84928aad",
"sha256:fd57160949179ec517d32ac2ac898b5f20d68ed1a9c977346efbac9c2f1e779d"
],
"index": "pypi",
"version": "==22.1.0"
"version": "==22.3.0"
},
"certifi": {
"hashes": [
@@ -1568,15 +1585,15 @@
},
"click": {
"hashes": [
"sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1",
"sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"
"sha256:24e1a4a9ec5bf6299411369b208c1df2188d9eb8d916302fe6bf03faed227f1e",
"sha256:479707fe14d9ec9a0757618b7a100a0ae4c4e236fac5b7f80ca68028141a1a72"
],
"markers": "python_version >= '3.6'",
"version": "==8.0.4"
"markers": "python_version >= '3.7'",
"version": "==8.1.2"
},
"coverage": {
"extras": [
"toml"
],
"hashes": [
"sha256:03e2a7826086b91ef345ff18742ee9fc47a6839ccd517061ef8fa1976e652ce9",
@@ -1671,11 +1688,11 @@
},
"faker": {
"hashes": [
"sha256:5536ceb63380f0d598c026b7c330c17d719a19d1a495e9397ee8f5259420a696",
"sha256:85ed0cb379e3b7414bdb6b484994beaf9bddc74472c8c35f743c16cf5fc0c314"
"sha256:188961065fb5c78ea639f42176f55100f72c90c3a3179ac6c955c4bd712b0511",
"sha256:7758ece2593ce603db117db3d27393c31f4af03f783e176f3f0e14839a4f3426"
],
"markers": "python_version >= '3.6'",
"version": "==13.3.3"
"version": "==13.3.4"
},
"filelock": {
"hashes": [
@@ -1709,14 +1726,6 @@
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.3.0"
},
"importlib-metadata": {
"hashes": [
"sha256:1208431ca90a8cca1a6b8af391bb53c1a2db74e5d1cef6ddced95d4b2062edc6",
"sha256:ea4c597ebf37142f827b8f39299579e31685c31d3a438b59f469406afd0f2539"
],
"markers": "python_version < '3.10'",
"version": "==4.11.3"
},
"iniconfig": {
"hashes": [
"sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3",
@@ -1825,11 +1834,11 @@
},
"pre-commit": {
"hashes": [
"sha256:725fa7459782d7bec5ead072810e47351de01709be838c2ce1726b9591dad616",
"sha256:c1a8040ff15ad3d648c70cc3e55b93e4d2d5b687320955505587fd79bbaed06a"
"sha256:02226e69564ebca1a070bd1f046af866aa1c318dbc430027c50ab832ed2b73f2",
"sha256:5d445ee1fa8738d506881c5d84f83c62bb5be6b2838e32207433647e8e5ebe10"
],
"index": "pypi",
"version": "==2.17.0"
"version": "==2.18.1"
},
"py": {
"hashes": [
@@ -1857,11 +1866,11 @@
},
"pyparsing": {
"hashes": [
"sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea",
"sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"
"sha256:7bf433498c016c4314268d95df76c81b842a4cb2b276fa3312cfb1e1d85f6954",
"sha256:ef7b523f6356f763771559412c0d7134753f037822dad1b16945b7b846f7ad06"
],
"markers": "python_version >= '3.6'",
"version": "==3.0.7"
"markers": "python_full_version >= '3.6.8'",
"version": "==3.0.8"
},
"pytest": {
"hashes": [
@@ -1995,11 +2004,11 @@
},
"sphinx": {
"hashes": [
"sha256:5da895959511473857b6d0200f56865ed62c31e8f82dd338063b84ec022701fe",
"sha256:6caad9786055cb1fa22b4a365c1775816b876f91966481765d7d50e9f0dd35cc"
"sha256:7bf8ca9637a4ee15af412d1a1d9689fec70523a68ca9bb9127c2f3eeb344e2e6",
"sha256:ebf612653238bcc8f4359627a9b7ce44ede6fdd75d9d30f68255c7383d3a6226"
],
"index": "pypi",
"version": "==4.4.0"
"version": "==4.5.0"
},
"sphinx-rtd-theme": {
"hashes": [
@@ -2087,14 +2096,6 @@
"index": "pypi",
"version": "==3.24.5"
},
"typing-extensions": {
"hashes": [
"sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42",
"sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"
],
"markers": "python_version >= '3.6'",
"version": "==4.1.1"
},
"urllib3": {
"hashes": [
"sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14",
@@ -2105,20 +2106,11 @@
},
"virtualenv": {
"hashes": [
"sha256:1e8588f35e8b42c6ec6841a13c5e88239de1e6e4e4cedfd3916b306dc826ec66",
"sha256:8e5b402037287126e81ccde9432b95a8be5b19d36584f64957060a3488c11ca8"
"sha256:e617f16e25b42eb4f6e74096b9c9e37713cf10bf30168fb4a739f3fa8f898a3a",
"sha256:ef589a79795589aada0c1c5b319486797c03b67ac3984c48c669c0e4f50df3a5"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==20.14.0"
},
"zipp": {
"hashes": [
"sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d",
"sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375"
],
"index": "pypi",
"markers": "python_version < '3.9'",
"version": "==3.7.0"
"version": "==20.14.1"
}
}
}
+4
View File
@@ -22,6 +22,10 @@
# Docker setup does not use the configuration file.
# A few commonly adjusted settings are provided below.
# This is required if you will be exposing Paperless-ngx on a public domain
# (if doing so please consider security measures such as reverse proxy)
#PAPERLESS_URL=https://paperless.example.com
# Adjust this key if you plan to make paperless available publicly. It should
# be a very long sequence of random characters. You don't need to remember it.
#PAPERLESS_SECRET_KEY=change-me
+1 -1
View File
@@ -200,7 +200,7 @@ Troubleshooting:
- Check your script's permission e.g. in case of permission error ``sudo chmod 755 post-consumption-example.sh``
- Pipe your scripts's output to a log file e.g. ``echo "${DOCUMENT_ID}" | tee --append /usr/src/paperless/scripts/post-consumption-example.log``
.. _post-consumption-example.sh: https://github.com/jonaswinkler/paperless-ngx/blob/master/scripts/post-consumption-example.sh
.. _post-consumption-example.sh: https://github.com/paperless-ngx/paperless-ngx/blob/main/scripts/post-consumption-example.sh
.. _advanced-file_name_handling:
+5
View File
@@ -5,6 +5,11 @@
Changelog
*********
paperless-ngx 1.7.0
###################
Changelog pending
paperless-ngx 1.6.0
###################
+72 -3
View File
@@ -142,7 +142,24 @@ PAPERLESS_SECRET_KEY=<key>
Default is listed in the file ``src/paperless/settings.py``.
PAPERLESS_ALLOWED_HOSTS<comma-separated-list>
PAPERLESS_URL=<url>
This setting can be used to set the three options below (ALLOWED_HOSTS,
CORS_ALLOWED_HOSTS and CSRF_TRUSTED_ORIGINS). If the other options are
set the values will be combined with this one. Do not include a trailing
slash. E.g. https://paperless.domain.com
Defaults to empty string, leaving the other settings unaffected.
PAPERLESS_CSRF_TRUSTED_ORIGINS=<comma-separated-list>
A list of trusted origins for unsafe requests (e.g. POST). As of Django 4.0
this is required to access the Django admin via the web.
See https://docs.djangoproject.com/en/4.0/ref/settings/#csrf-trusted-origins
Can also be set using PAPERLESS_URL (see above).
Defaults to empty string, which does not add any origins to the trusted list.
PAPERLESS_ALLOWED_HOSTS=<comma-separated-list>
If you're planning on putting Paperless on the open internet, then you
really should set this value to the domain name you're using. Failing to do
so leaves you open to HTTP host header attacks:
@@ -151,12 +168,16 @@ PAPERLESS_ALLOWED_HOSTS<comma-separated-list>
Just remember that this is a comma-separated list, so "example.com" is fine,
as is "example.com,www.example.com", but NOT " example.com" or "example.com,"
Can also be set using PAPERLESS_URL (see above).
Defaults to "*", which is all hosts.
PAPERLESS_CORS_ALLOWED_HOSTS<comma-separated-list>
PAPERLESS_CORS_ALLOWED_HOSTS=<comma-separated-list>
You need to add your servers to the list of allowed hosts that can do CORS
calls. Set this to your public domain name.
Can also be set using PAPERLESS_URL (see above).
Defaults to "http://localhost:8000".
PAPERLESS_FORCE_SCRIPT_NAME=<path>
@@ -540,6 +561,10 @@ PAPERLESS_WORKER_TIMEOUT=<num>
large documents within the default 1800 seconds. So extending this timeout
may prove to be useful on weak hardware setups.
PAPERLESS_WORKER_RETRY=<num>
If PAPERLESS_WORKER_TIMEOUT has been configured, the retry time for a task can
also be configured. By default, this value will be set to 10s more than the
worker timeout. This value should never be set less than the worker timeout.
PAPERLESS_TIME_ZONE=<timezone>
Set the time zone here.
@@ -588,6 +613,27 @@ PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS=<bool>
Defaults to false.
PAPERLESS_CONSUMER_ENABLE_BARCODES=<bool>
Enables the scanning and page separation based on detected barcodes.
This allows for scanning and adding multiple documents per uploaded
file, which are separated by one or multiple barcode pages.
For ease of use, it is suggested to use a standardized separation page,
e.g. `here <https://www.alliancegroup.co.uk/patch-codes.htm>`_.
If no barcodes are detected in the uploaded file, no page separation
will happen.
Defaults to false.
PAPERLESS_CONSUMER_BARCODE_STRING=PATCHT
Defines the string to be detected as a separator barcode.
If paperless is used with the PATCH-T separator pages, users
shouldn't change this.
Defaults to "PATCHT"
PAPERLESS_CONVERT_MEMORY_LIMIT=<num>
On smaller systems, or even in the case of Very Large Documents, the consumer
@@ -671,7 +717,7 @@ PAPERLESS_CONSUMER_IGNORE_PATTERNS=<json>
This can be adjusted by configuring a custom json array with patterns to exclude.
Defautls to ``[".DS_STORE/*", "._*", ".stfolder/*"]``.
Defaults to ``[".DS_STORE/*", "._*", ".stfolder/*", ".stversions/*", ".localized/*", "desktop.ini"]``.
Binaries
########
@@ -764,3 +810,26 @@ PAPERLESS_OCR_LANGUAGES=<list>
PAPERLESS_OCR_LANGUAGE=tur
Defaults to none, which does not install any additional languages.
.. _configuration-update-checking:
Update Checking
###############
PAPERLESS_ENABLE_UPDATE_CHECK=<bool>
Enable (or disable) the automatic check for available updates. This feature is disabled
by default but if it is not explicitly set Paperless-ngx will show a message about this.
If enabled, the feature works by pinging the the Github API for the latest release e.g.
https://api.github.com/repos/paperless-ngx/paperless-ngx/releases/latest
to determine whether a new version is available.
Actual updating of the app must still be performed manually.
Note that for users of thirdy-party containers e.g. linuxserver.io this notification
may be 'ahead' of a new release from the third-party maintainers.
In either case, no tracking data is collected by the app in any way.
Defaults to none, which disables the feature.
+37 -38
View File
@@ -13,43 +13,43 @@ that works right for you based on recommendations from other Paperless users.
Physical scanners
=================
+---------+----------------+-----+-----+-----+------+----------+----------------+
| Brand | Model | Supports | Recommended By |
+---------+----------------+-----+-----+-----+------+----------+----------------+
| | | FTP | NFS | SMB | SMTP | API [1]_ | |
+=========+================+=====+=====+=====+======+==========+================+
| Brother | `ADS-1700W`_ | yes | | yes | yes | |`holzhannes`_ |
+---------+----------------+-----+-----+-----+------+----------+----------------+
| Brother | `ADS-1600W`_ | yes | | yes | yes | |`holzhannes`_ |
+---------+----------------+-----+-----+-----+------+----------+----------------+
| Brother | `ADS-1500W`_ | yes | | yes | yes | |`danielquinn`_ |
+---------+----------------+-----+-----+-----+------+----------+----------------+
| Brother | `ADS-1100W`_ | yes | | | | |`ytzelf`_ |
+---------+----------------+-----+-----+-----+------+----------+----------------+
| Brother | `ADS-2800W`_ | yes | yes | | yes | yes |`philpagel`_ |
+---------+----------------+-----+-----+-----+------+----------+----------------+
| Brother | `MFC-J6930DW`_ | yes | | | | |`ayounggun`_ |
+---------+----------------+-----+-----+-----+------+----------+----------------+
| Brother | `MFC-L5850DW`_ | yes | | | yes | |`holzhannes`_ |
+---------+----------------+-----+-----+-----+------+----------+----------------+
| Brother | `MFC-L2750DW`_ | yes | | yes | yes | |`muued`_ |
+---------+----------------+-----+-----+-----+------+----------+----------------+
| Brother | `MFC-J5910DW`_ | yes | | | | |`bmsleight`_ |
+---------+----------------+-----+-----+-----+------+----------+----------------+
| Brother | `MFC-8950DW`_ | yes | | | yes | yes |`philpagel`_ |
+---------+----------------+-----+-----+-----+------+----------+----------------+
| Brother | `MFC-9142CDN`_ | yes | | yes | | |`REOLDEV`_ |
+---------+----------------+-----+-----+-----+------+----------+----------------+
| Fujitsu | `ix500`_ | yes | | yes | | |`eonist`_ |
+---------+----------------+-----+-----+-----+------+----------+----------------+
| Epson | `ES-580W`_ | yes | | yes | yes | |`fignew`_ |
+---------+----------------+-----+-----+-----+------+----------+----------------+
| Epson | `WF-7710DWF`_ | yes | | yes | | |`Skylinar`_ |
+---------+----------------+-----+-----+-----+------+----------+----------------+
| Fujitsu | `S1300i`_ | yes | | yes | | |`jonaswinkler`_ |
+---------+----------------+-----+-----+-----+------+----------+----------------+
| Doxie | `Q2`_ | | | | | yes |`Unkn0wnCat`_ |
+---------+----------------+-----+-----+-----+------+----------+----------------+
+---------+----------------+-----+------+-----+-----+------+----------+----------------+
| Brand | Model | Supports | Recommended By |
+---------+----------------+-----+------+-----+-----+------+----------+----------------+
| | | FTP | SFTP | NFS | SMB | SMTP | API [1]_ | |
+=========+================+=====+======+=====+=====+======+==========+================+
| Brother | `ADS-1700W`_ | yes | | | yes | yes | |`holzhannes`_ |
+---------+----------------+-----+------+-----+-----+------+----------+----------------+
| Brother | `ADS-1600W`_ | yes | | | yes | yes | |`holzhannes`_ |
+---------+----------------+-----+------+-----+-----+------+----------+----------------+
| Brother | `ADS-1500W`_ | yes | | | yes | yes | |`danielquinn`_ |
+---------+----------------+-----+------+-----+-----+------+----------+----------------+
| Brother | `ADS-1100W`_ | yes | | | | | |`ytzelf`_ |
+---------+----------------+-----+------+-----+-----+------+----------+----------------+
| Brother | `ADS-2800W`_ | yes | yes | | yes | yes | |`philpagel`_ |
+---------+----------------+-----+------+-----+-----+------+----------+----------------+
| Brother | `MFC-J6930DW`_ | yes | | | | | |`ayounggun`_ |
+---------+----------------+-----+------+-----+-----+------+----------+----------------+
| Brother | `MFC-L5850DW`_ | yes | | | | yes | |`holzhannes`_ |
+---------+----------------+-----+------+-----+-----+------+----------+----------------+
| Brother | `MFC-L2750DW`_ | yes | | | yes | yes | |`muued`_ |
+---------+----------------+-----+------+-----+-----+------+----------+----------------+
| Brother | `MFC-J5910DW`_ | yes | | | | | |`bmsleight`_ |
+---------+----------------+-----+------+-----+-----+------+----------+----------------+
| Brother | `MFC-8950DW`_ | yes | | | yes | yes | |`philpagel`_ |
+---------+----------------+-----+------+-----+-----+------+----------+----------------+
| Brother | `MFC-9142CDN`_ | yes | | | yes | | |`REOLDEV`_ |
+---------+----------------+-----+------+-----+-----+------+----------+----------------+
| Fujitsu | `ix500`_ | yes | | | yes | | |`eonist`_ |
+---------+----------------+-----+------+-----+-----+------+----------+----------------+
| Epson | `ES-580W`_ | yes | | | yes | yes | |`fignew`_ |
+---------+----------------+-----+------+-----+-----+------+----------+----------------+
| Epson | `WF-7710DWF`_ | yes | | | yes | | |`Skylinar`_ |
+---------+----------------+-----+------+-----+-----+------+----------+----------------+
| Fujitsu | `S1300i`_ | yes | | | yes | | |`jonaswinkler`_ |
+---------+----------------+-----+------+-----+-----+------+----------+----------------+
| Doxie | `Q2`_ | | | | | | yes |`Unkn0wnCat`_ |
+---------+----------------+-----+------+-----+-----+------+----------+----------------+
.. _MFC-L5850DW: https://www.brother-usa.com/products/mfcl5850dw
.. _MFC-L2750DW: https://www.brother.de/drucker/laserdrucker/mfc-l2750dw
@@ -131,4 +131,3 @@ This part assumes your Doxie is connected to WiFi and you know its IP.
6. Click *Submit* at the bottom of the page
Congrats, you can now scan directly from your Doxie to your Paperless-ngx instance!
+8
View File
@@ -178,6 +178,14 @@ These are as follows:
automatically or manually and tell paperless to move them to yet another folder
after consumption. It's up to you.
.. note::
When defining a mail rule with a folder, you may need to try different characters to
define how the sub-folders are separated. Common values include ".", "/" or "|", but
this varies by the mail server. Unfortunately, this isn't a value we can determine
automatically. Either check the documentation for your mail server, or check for
errors in the logs and try different folder separator values.
.. note::
Paperless will process the rules in the order defined in the admin page.
+26 -9
View File
@@ -47,24 +47,29 @@ if [[ $(id -u) == "0" ]] ; then
exit 1
fi
if [[ -z $(which wget) ]] ; then
if ! command -v wget &> /dev/null ; then
echo "wget executable not found. Is wget installed?"
exit 1
fi
if [[ -z $(which docker) ]] ; then
if ! command -v docker &> /dev/null ; then
echo "docker executable not found. Is docker installed?"
exit 1
fi
if [[ -z $(which docker-compose) ]] ; then
echo "docker-compose executable not found. Is docker-compose installed?"
exit 1
DOCKER_COMPOSE_CMD="docker-compose"
if ! command -v ${DOCKER_COMPOSE_CMD} ; then
if docker compose version &> /dev/null ; then
DOCKER_COMPOSE_CMD="docker compose"
else
echo "docker-compose executable not found. Is docker-compose installed?"
exit 1
fi
fi
# Check if user has permissions to run Docker by trying to get the status of Docker (docker status).
# If this fails, the user probably does not have permissions for Docker.
if [ ! "$(docker stats --no-stream 2>/dev/null 1>&2)" ] ; then
if ! docker stats --no-stream &> /dev/null ; then
echo ""
echo "WARN: It look like the current user does not have Docker permissions."
echo "WARN: Use 'sudo usermod -aG docker $USER' to assign Docker permissions to the user."
@@ -87,6 +92,14 @@ echo ""
echo "1. Application configuration"
echo "============================"
echo ""
echo "The URL paperless will be available at. This is required if the"
echo "installation will be accessible via the web, otherwise can be left blank."
echo ""
ask "URL" ""
URL=$ask_result
echo ""
echo "The port on which the paperless webserver will listen for incoming"
echo "connections."
@@ -273,6 +286,7 @@ if [[ "$DATABASE_BACKEND" == "postgres" ]] ; then
fi
fi
echo ""
echo "URL: $URL"
echo "Port: $PORT"
echo "Database: $DATABASE_BACKEND"
echo "Tika enabled: $TIKA_ENABLED"
@@ -308,6 +322,9 @@ SECRET_KEY=$(tr -dc 'a-zA-Z0-9' < /dev/urandom | fold -w 64 | head -n 1)
DEFAULT_LANGUAGES="deu eng fra ita spa"
{
if [[ ! $URL == "" ]] ; then
echo "PAPERLESS_URL=$URL"
fi
if [[ ! $USERMAP_UID == "1000" ]] ; then
echo "USERMAP_UID=$USERMAP_UID"
fi
@@ -351,8 +368,8 @@ if [ "$l1" -eq "$l2" ] ; then
fi
docker-compose pull
${DOCKER_COMPOSE_CMD} pull
docker-compose run --rm -e DJANGO_SUPERUSER_PASSWORD="$PASSWORD" webserver createsuperuser --noinput --username "$USERNAME" --email "$EMAIL"
${DOCKER_COMPOSE_CMD} run --rm -e DJANGO_SUPERUSER_PASSWORD="$PASSWORD" webserver createsuperuser --noinput --username "$USERNAME" --email "$EMAIL"
docker-compose up -d
${DOCKER_COMPOSE_CMD} up -d
+8 -3
View File
@@ -27,8 +27,10 @@
# Security and hosting
#PAPERLESS_SECRET_KEY=change-me
#PAPERLESS_ALLOWED_HOSTS=example.com,www.example.com
#PAPERLESS_CORS_ALLOWED_HOSTS=http://example.com,http://localhost:8000
#PAPERLESS_URL=https://example.com
#PAPERLESS_CSRF_TRUSTED_ORIGINS=https://example.com # can be set using PAPERLESS_URL
#PAPERLESS_ALLOWED_HOSTS=example.com,www.example.com # can be set using PAPERLESS_URL
#PAPERLESS_CORS_ALLOWED_HOSTS=https://localhost:8080,https://example.com # can be set using PAPERLESS_URL
#PAPERLESS_FORCE_SCRIPT_NAME=
#PAPERLESS_STATIC_URL=/static/
#PAPERLESS_AUTO_LOGIN_USERNAME=
@@ -58,8 +60,10 @@
#PAPERLESS_CONSUMER_POLLING=10
#PAPERLESS_CONSUMER_DELETE_DUPLICATES=false
#PAPERLESS_CONSUMER_RECURSIVE=false
#PAPERLESS_CONSUMER_IGNORE_PATTERNS=[".DS_STORE/*", "._*", ".stfolder/*"]
#PAPERLESS_CONSUMER_IGNORE_PATTERNS=[".DS_STORE/*", "._*", ".stfolder/*", ".stversions/*", ".localized/*", "desktop.ini"]
#PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS=false
#PAPERLESS_CONSUMER_ENABLE_BARCODES=false
#PAPERLESS_CONSUMER_ENABLE_BARCODES=PATCHT
#PAPERLESS_OPTIMIZE_THUMBNAILS=true
#PAPERLESS_PRE_CONSUME_SCRIPT=/path/to/an/arbitrary/script.sh
#PAPERLESS_POST_CONSUME_SCRIPT=/path/to/an/arbitrary/script.sh
@@ -67,6 +71,7 @@
#PAPERLESS_FILENAME_PARSE_TRANSFORMS=[]
#PAPERLESS_THUMBNAIL_FONT_NAME=
#PAPERLESS_IGNORE_DATES=
#PAPERLESS_ENABLE_UPDATE_CHECK=
# Tika settings
+19 -17
View File
@@ -5,15 +5,15 @@
# pipenv lock --requirements
#
-i https://pypi.python.org/simple
--extra-index-url https://www.piwheels.org/simple
-i https://pypi.python.org/simple/
--extra-index-url https://www.piwheels.org/simple/
aioredis==1.3.1
anyio==3.5.0; python_full_version >= '3.6.2'
arrow==1.2.2; python_version >= '3.6'
asgiref==3.5.0; python_version >= '3.7'
async-timeout==4.0.2; python_version >= '3.6'
attrs==21.4.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
autobahn==22.2.2; python_version >= '3.7'
autobahn==22.3.2; python_version >= '3.7'
automat==20.2.0
backports.zoneinfo==0.2.1; python_version < '3.9'
blessed==1.19.1; python_version >= '2.7'
@@ -23,7 +23,7 @@ channels-redis==3.4.0
channels==3.0.4
chardet==4.0.0; python_version >= '3.1'
charset-normalizer==2.0.12; python_version >= '3'
click==8.0.4; python_version >= '3.6'
click==8.1.2; python_version >= '3.7'
coloredlogs==15.0.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
concurrent-log-handler==0.9.20
constantly==15.1.0
@@ -35,7 +35,7 @@ django-extensions==3.1.5
django-filter==21.1
django-picklefield==3.0.1; python_version >= '3'
django-q==1.3.9
django==4.0.3
django==4.0.4
djangorestframework==3.13.1
filelock==3.6.0
fuzzywuzzy[speedup]==0.18.0
@@ -47,7 +47,7 @@ humanfriendly==10.0; python_version >= '2.7' and python_version not in '3.0, 3.1
hyperlink==21.0.0
idna==3.3; python_version >= '3.5'
imap-tools==0.53.0
img2pdf==0.4.3
img2pdf==0.4.4
importlib-resources==5.6.0; python_version < '3.9'
incremental==21.3.0
inotify-simple==1.3.5; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
@@ -57,12 +57,13 @@ langdetect==1.0.9
lxml==4.8.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
msgpack==1.0.3
numpy==1.22.3; python_version >= '3.8'
ocrmypdf==13.4.1
ocrmypdf==13.4.2
packaging==21.3; python_version >= '3.6'
pathvalidate==2.5.0
pdfminer.six==20211012
pikepdf==5.1.0
pillow==9.0.1
pdf2image==1.16.0
pdfminer.six==20220319
pikepdf==5.1.1
pillow==9.1.0
pluggy==1.0.0; python_version >= '3.6'
portalocker==2.4.0; python_version >= '3'
psycopg2==2.9.3
@@ -70,7 +71,7 @@ pyasn1-modules==0.2.8
pyasn1==0.4.8
pycparser==2.21
pyopenssl==22.0.0
pyparsing==3.0.7; python_version >= '3.6'
pyparsing==3.0.8; python_full_version >= '3.6.8'
python-dateutil==2.8.2
python-dotenv==0.20.0
python-gnupg==0.4.8
@@ -79,6 +80,7 @@ python-magic==0.4.25
pytz-deprecation-shim==0.1.0.post0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'
pytz==2022.1
pyyaml==6.0
pyzbar==0.1.9
redis==3.5.3
regex==2022.3.2; python_version >= '3.6'
reportlab==3.6.9; python_version >= '3.7' and python_version < '4'
@@ -86,26 +88,26 @@ requests==2.27.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3
scikit-learn==1.0.2
scipy==1.8.0; python_version < '3.11' and python_version >= '3.8'
service-identity==21.1.0
setuptools==61.1.1; python_version >= '3.7'
setuptools==62.1.0; python_version >= '3.7'
six==1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
sniffio==1.2.0; python_version >= '3.5'
sqlparse==0.4.2; python_version >= '3.5'
threadpoolctl==3.1.0; python_version >= '3.6'
tika==1.24
tqdm==4.63.1
twisted[tls]==22.2.0; python_full_version >= '3.6.7'
tqdm==4.64.0
twisted[tls]==22.4.0; python_full_version >= '3.6.7'
txaio==22.2.1; python_version >= '3.6'
typing-extensions==4.1.1; python_version >= '3.6'
tzdata==2022.1; python_version >= '3.6'
tzlocal==4.1; python_version >= '3.6'
tzlocal==4.2; python_version >= '3.6'
urllib3==1.26.9; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'
uvicorn[standard]==0.17.6
uvloop==0.16.0
watchdog==2.1.7
watchgod==0.8.1
watchgod==0.8.2
wcwidth==0.2.5
websockets==10.2
whitenoise==6.0.0
whoosh==2.7.4
zipp==3.7.0; python_version < '3.9'
zipp==3.8.0; python_version < '3.9'
zope.interface==5.4.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
+11 -1
View File
@@ -1,3 +1,13 @@
<app-toasts></app-toasts>
<router-outlet></router-outlet>
<ngx-file-drop dropZoneClassName="main-dropzone" contentClassName="main-content" [disabled]="!dragDropEnabled"
(onFileDrop)="dropped($event)" (onFileOver)="fileOver()" (onFileLeave)="fileLeave()">
<ng-template ngx-file-drop-content-tmp>
<div class="global-dropzone-overlay fade" [class.show]="fileIsOver" [class.hide]="hidden">
<h2 i18n>Drop files to begin upload</h2>
</div>
<div [class.inert]="fileIsOver">
<router-outlet></router-outlet>
</div>
</ng-template>
</ngx-file-drop>
+40 -1
View File
@@ -4,6 +4,8 @@ import { Router } from '@angular/router'
import { Subscription } from 'rxjs'
import { ConsumerStatusService } from './services/consumer-status.service'
import { ToastService } from './services/toast.service'
import { NgxFileDropEntry } from 'ngx-file-drop'
import { UploadDocumentsService } from './services/upload-documents.service'
@Component({
selector: 'app-root',
@@ -15,11 +17,16 @@ export class AppComponent implements OnInit, OnDestroy {
successSubscription: Subscription
failedSubscription: Subscription
private fileLeaveTimeoutID: any
fileIsOver: boolean = false
hidden: boolean = true
constructor(
private settings: SettingsService,
private consumerStatusService: ConsumerStatusService,
private toastService: ToastService,
private router: Router
private router: Router,
private uploadDocumentsService: UploadDocumentsService
) {
let anyWindow = window as any
anyWindow.pdfWorkerSrc = 'assets/js/pdf.worker.min.js'
@@ -100,4 +107,36 @@ export class AppComponent implements OnInit, OnDestroy {
}
})
}
public get dragDropEnabled(): boolean {
return !this.router.url.includes('dashboard')
}
public fileOver() {
// allows transition
setTimeout(() => {
this.fileIsOver = true
}, 1)
this.hidden = false
// stop fileLeave timeout
clearTimeout(this.fileLeaveTimeoutID)
}
public fileLeave(immediate: boolean = false) {
const ms = immediate ? 0 : 500
this.fileLeaveTimeoutID = setTimeout(() => {
this.fileIsOver = false
// await transition completed
setTimeout(() => {
this.hidden = true
}, 150)
}, ms)
}
public dropped(files: NgxFileDropEntry[]) {
this.fileLeave(true)
this.uploadDocumentsService.uploadFiles(files)
this.toastService.showInfo($localize`Initiating upload...`, 3000)
}
}
@@ -12,7 +12,7 @@
</a>
<div class="search-form-container flex-grow-1 py-2 pb-3 pb-sm-2 px-3 ps-md-4 me-sm-auto order-3 order-sm-1">
<form (ngSubmit)="search()" class="form-inline flex-grow-1">
<svg width="1em" height="1em">
<svg width="1em" height="1em" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#search"/>
</svg>
<input class="form-control form-control-sm" type="text" placeholder="Search documents" aria-label="Search"
@@ -25,7 +25,7 @@
<span *ngIf="displayName" class="navbar-text small me-2 text-light d-none d-sm-inline">
{{displayName}}
</span>
<svg width="1.3em" height="1.3em">
<svg width="1.3em" height="1.3em" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#person-circle"/>
</svg>
</button>
@@ -170,21 +170,46 @@
<li class="nav-item">
<div class="d-flex w-100 flex-wrap">
<a class="nav-link pe-2 pb-1" target="_blank" rel="noopener noreferrer" href="https://github.com/paperless-ngx/paperless-ngx">
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="sidebaricon bi bi-github" viewBox="0 0 16 16">
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"/>
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="sidebaricon" viewBox="0 0 16 16">
<use xlink:href="assets/bootstrap-icons.svg#github" />
</svg>&nbsp;<ng-container i18n>GitHub</ng-container>
</a>
<a class="nav-link-additional small text-muted ms-3" target="_blank" rel="noopener noreferrer" href="https://github.com/paperless-ngx/paperless-ngx/discussions/categories/feature-requests" title="Suggest an idea">
<svg xmlns="http://www.w3.org/2000/svg" width="1.3em" height="1.3em" fill="currentColor" class="bi bi-lightbulb pe-1" viewBox="0 0 16 16">
<path d="M2 6a6 6 0 1 1 10.174 4.31c-.203.196-.359.4-.453.619l-.762 1.769A.5.5 0 0 1 10.5 13a.5.5 0 0 1 0 1 .5.5 0 0 1 0 1l-.224.447a1 1 0 0 1-.894.553H6.618a1 1 0 0 1-.894-.553L5.5 15a.5.5 0 0 1 0-1 .5.5 0 0 1 0-1 .5.5 0 0 1-.46-.302l-.761-1.77a1.964 1.964 0 0 0-.453-.618A5.984 5.984 0 0 1 2 6zm6-5a5 5 0 0 0-3.479 8.592c.263.254.514.564.676.941L5.83 12h4.342l.632-1.467c.162-.377.413-.687.676-.941A5 5 0 0 0 8 1z"/>
<svg xmlns="http://www.w3.org/2000/svg" width="1.1em" height="1.1em" fill="currentColor" class="me-1" viewBox="0 0 16 16">
<use xlink:href="assets/bootstrap-icons.svg#lightbulb" />
</svg>
<ng-container i18n>Suggest an idea</ng-container>
</a>
</div>
</li>
<li class="nav-item mt-2">
<div class="px-3 py-2 text-muted small">
{{versionString}}
<div class="px-3 py-2 text-muted small d-flex align-items-center flex-wrap">
<div class="me-3">{{ versionString }}</div>
<div *ngIf="appRemoteVersion" class="version-check">
<ng-template #updateAvailablePopContent>
<span class="small">Paperless-ngx v{{ appRemoteVersion.version }} <ng-container i18n>is available.</ng-container><br/><ng-container i18n>Click to view.</ng-container></span>
</ng-template>
<ng-template #updateCheckingNotEnabledPopContent>
<span class="small"><ng-container i18n>Checking for updates is disabled.</ng-container><br/><ng-container i18n>Click for more information.</ng-container></span>
</ng-template>
<ng-container *ngIf="appRemoteVersion.feature_is_set; else updateCheckNotSet">
<a *ngIf="appRemoteVersion.update_available" class="small text-decoration-none" target="_blank" rel="noopener noreferrer" href="https://github.com/paperless-ngx/paperless-ngx/releases"
[ngbPopover]="updateAvailablePopContent" popoverClass="shadow" triggers="mouseenter:mouseleave" container="body">
<svg fill="currentColor" class="me-1" width="1.2em" height="1.2em" style="vertical-align: text-top;" viewBox="0 0 16 16">
<use xlink:href="assets/bootstrap-icons.svg#info-circle" />
</svg>
<ng-container *ngIf="appRemoteVersion?.update_available" i18n>Update available</ng-container>
</a>
</ng-container>
<ng-template #updateCheckNotSet>
<a class="small text-decoration-none" target="_blank" rel="noopener noreferrer" href="https://paperless-ngx.readthedocs.io/en/latest/configuration.html#update-checking"
[ngbPopover]="updateCheckingNotEnabledPopContent" popoverClass="shadow" triggers="mouseenter:mouseleave" container="body">
<svg fill="currentColor" class="me-1" width="1.2em" height="1.2em" style="vertical-align: text-top;" viewBox="0 0 16 16">
<use xlink:href="assets/bootstrap-icons.svg#info-circle" />
</svg>
</a>
</ng-template>
</div>
</div>
</li>
</ul>
@@ -35,20 +35,19 @@
.sidebar .nav-link {
font-weight: 500;
}
.sidebar .nav-link .sidebaricon {
margin-right: 4px;
}
&:hover, &.active {
color: var(--bs-primary);
}
.sidebar .nav-link.active {
color: var(--bs-primary);
font-weight: bold;
}
&.active {
font-weight: bold;
}
.sidebar .nav-link.active .sidebaricon,
.sidebar .nav-link:hover .sidebaricon {
color: inherit;
.sidebaricon {
margin-right: 4px;
color: inherit;
}
}
.sidebar-heading {
@@ -171,8 +170,28 @@
&:focus {
background-color: rgba(0, 0, 0, 0.3);
color: var(--bs-light);
flex-grow: 1;
padding-left: 0.5rem;
}
}
}
.version-check {
animation: pulse 2s ease-in-out 0s 1;
}
@keyframes pulse {
0% {
opacity: 0;
}
25% {
opacity: 100%;
}
75% {
opacity: 0;
}
100% {
opacity: 100%;
}
}
@@ -18,6 +18,10 @@ import { DocumentDetailComponent } from '../document-detail/document-detail.comp
import { Meta } from '@angular/platform-browser'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { FILTER_FULLTEXT_QUERY } from 'src/app/data/filter-rule-type'
import {
RemoteVersionService,
AppRemoteVersion,
} from 'src/app/services/rest/remote-version.service'
@Component({
selector: 'app-app-frame',
@@ -32,10 +36,18 @@ export class AppFrameComponent {
private searchService: SearchService,
public savedViewService: SavedViewService,
private list: DocumentListViewService,
private meta: Meta
) {}
private meta: Meta,
private remoteVersionService: RemoteVersionService
) {
this.remoteVersionService
.checkForUpdates()
.subscribe((appRemoteVersion: AppRemoteVersion) => {
this.appRemoteVersion = appRemoteVersion
})
}
versionString = `${environment.appTitle} ${environment.version}`
appRemoteVersion
isMenuCollapsed: boolean = true
@@ -42,7 +42,7 @@
filter: brightness(0.5);
&.active {
background-color: var(--ngx-primary-lighten-30);
background-color: var(--pngx-primary-lighten-30);
}
}
@@ -2,7 +2,7 @@
*ngFor="let toast of toasts"
[header]="toast.title" [autohide]="true" [delay]="toast.delay"
[class]="toast.classname"
(hide)="toastService.closeToast(toast)">
(hidden)="toastService.closeToast(toast)">
<p>{{toast.content}}</p>
<p *ngIf="toast.action"><button class="btn btn-sm btn-outline-secondary" (click)="toastService.closeToast(toast); toast.action()">{{toast.actionName}}</button></p>
</ngb-toast>
@@ -5,3 +5,7 @@
margin: 0.5em;
z-index: 1200;
}
.toast:not(.show) {
display: block; // this corrects an ng-bootstrap bug that prevented animations
}
@@ -33,3 +33,7 @@ form {
mix-blend-mode: soft-light;
pointer-events: none;
}
::ng-deep .ngx-file-drop__drop-zone--over {
background-color: var(--pngx-primary-faded) !important;
}
@@ -6,7 +6,7 @@ import {
FileStatus,
FileStatusPhase,
} from 'src/app/services/consumer-status.service'
import { DocumentService } from 'src/app/services/rest/document.service'
import { UploadDocumentsService } from 'src/app/services/upload-documents.service'
const MAX_ALERTS = 5
@@ -19,8 +19,8 @@ export class UploadFileWidgetComponent implements OnInit {
alertsExpanded = false
constructor(
private documentService: DocumentService,
private consumerStatusService: ConsumerStatusService
private consumerStatusService: ConsumerStatusService,
private uploadDocumentsService: UploadDocumentsService
) {}
getStatus() {
@@ -116,48 +116,6 @@ export class UploadFileWidgetComponent implements OnInit {
public fileLeave(event) {}
public dropped(files: NgxFileDropEntry[]) {
for (const droppedFile of files) {
if (droppedFile.fileEntry.isFile) {
const fileEntry = droppedFile.fileEntry as FileSystemFileEntry
fileEntry.file((file: File) => {
let formData = new FormData()
formData.append('document', file, file.name)
let status = this.consumerStatusService.newFileUpload(file.name)
status.message = $localize`Connecting...`
this.documentService.uploadDocument(formData).subscribe(
(event) => {
if (event.type == HttpEventType.UploadProgress) {
status.updateProgress(
FileStatusPhase.UPLOADING,
event.loaded,
event.total
)
status.message = $localize`Uploading...`
} else if (event.type == HttpEventType.Response) {
status.taskId = event.body['task_id']
status.message = $localize`Upload complete, waiting...`
}
},
(error) => {
switch (error.status) {
case 400: {
this.consumerStatusService.fail(status, error.error.document)
break
}
default: {
this.consumerStatusService.fail(
status,
$localize`HTTP error: ${error.status} ${error.statusText}`
)
break
}
}
}
)
})
}
}
this.uploadDocumentsService.uploadFiles(files)
}
}
@@ -135,20 +135,27 @@
</li>
<li [ngbNavItem]="4" class="d-md-none">
<a ngbNavLink>Preview</a>
<ng-template ngbNavContent *ngIf="pdfPreview.offsetParent == undefined">
<ng-container *ngIf="getContentType() == 'application/pdf'">
<div class="preview-sticky pdf-viewer-container" *ngIf="!useNativePdfViewer ; else nativePdfViewer">
<pdf-viewer [src]="previewUrl" [original-size]="false" [show-borders]="true" [show-all]="true" [render-text-mode]="2"></pdf-viewer>
<a ngbNavLink>Preview</a>
<ng-template ngbNavContent *ngIf="pdfPreview.offsetParent == undefined">
<div class="position-relative">
<ng-container *ngIf="getContentType() == 'application/pdf'">
<div class="preview-sticky pdf-viewer-container" *ngIf="!useNativePdfViewer ; else nativePdfViewer">
<pdf-viewer [src]="{ url: previewUrl, password: password }" [original-size]="false" [show-borders]="true" [show-all]="true" [(page)]="previewCurrentPage" [render-text-mode]="2" (error)="onError($event)" (after-load-complete)="pdfPreviewLoaded($event)"></pdf-viewer>
</div>
<ng-template #nativePdfViewer>
<object [data]="previewUrl | safeUrl" class="preview-sticky" width="100%"></object>
</ng-template>
</ng-container>
<ng-container *ngIf="getContentType() == 'text/plain'">
<object [data]="previewUrl | safeUrl" type="text/plain" class="preview-sticky bg-white" width="100%"></object>
</ng-container>
<div *ngIf="requiresPassword" class="password-prompt">
<form>
<input autocomplete="" class="form-control" i18n-placeholder placeholder="Enter Password" type="password" (keyup)="onPasswordKeyUp($event)" />
</form>
</div>
</div>
<ng-template #nativePdfViewer>
<object [data]="previewUrl | safeUrl" class="preview-sticky" width="100%"></object>
</ng-template>
</ng-container>
<ng-container *ngIf="getContentType() == 'text/plain'">
<object [data]="previewUrl | safeUrl" type="text/plain" class="preview-sticky bg-white" width="100%"></object>
</ng-container>
</ng-template>
</ng-template>
</li>
</ul>
@@ -160,10 +167,10 @@
</form>
</div>
<div class="col-md-6 col-xl-8 mb-3 d-none d-md-block" #pdfPreview>
<div class="col-md-6 col-xl-8 mb-3 d-none d-md-block position-relative" #pdfPreview>
<ng-container *ngIf="getContentType() == 'application/pdf'">
<div class="preview-sticky pdf-viewer-container" *ngIf="!useNativePdfViewer ; else nativePdfViewer">
<pdf-viewer [src]="previewUrl" [original-size]="false" [show-borders]="true" [show-all]="true" [(page)]="previewCurrentPage" [render-text-mode]="2" (after-load-complete)="pdfPreviewLoaded($event)"></pdf-viewer>
<pdf-viewer [src]="{ url: previewUrl, password: password }" [original-size]="false" [show-borders]="true" [show-all]="true" [(page)]="previewCurrentPage" [render-text-mode]="2" (error)="onError($event)" (after-load-complete)="pdfPreviewLoaded($event)"></pdf-viewer>
</div>
<ng-template #nativePdfViewer>
<object [data]="previewUrl | safeUrl" class="preview-sticky" width="100%"></object>
@@ -172,5 +179,11 @@
<ng-container *ngIf="getContentType() == 'text/plain'">
<object [data]="previewUrl | safeUrl" type="text/plain" class="preview-sticky bg-white" width="100%"></object>
</ng-container>
<div *ngIf="requiresPassword" class="password-prompt">
<form>
<input autocomplete="" class="form-control" i18n-placeholder placeholder="Enter Password" type="password" (keyup)="onPasswordKeyUp($event)" />
</form>
</div>
</div>
</div>
@@ -17,3 +17,10 @@
--page-margin: 1px 0 -8px;
width: 100% !important;
}
.password-prompt {
position: absolute;
top: 30%;
left: 30%;
right: 30%;
}
@@ -1,10 +1,4 @@
import {
Component,
OnInit,
OnDestroy,
ViewChild,
ElementRef,
} from '@angular/core'
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core'
import { FormControl, FormGroup } from '@angular/forms'
import { ActivatedRoute, Router } from '@angular/router'
import { NgbModal, NgbNav } from '@ng-bootstrap/ng-bootstrap'
@@ -90,6 +84,11 @@ export class DocumentDetailComponent
isDirty$: Observable<boolean>
unsubscribeNotifier: Subject<any> = new Subject()
requiresPassword: boolean = false
password: string
ogDate: Date
@ViewChild('nav') nav: NgbNav
@ViewChild('pdfPreview') set pdfPreview(element) {
// this gets called when compontent added or removed from DOM
@@ -145,7 +144,21 @@ export class DocumentDetailComponent
ngOnInit(): void {
this.documentForm.valueChanges
.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe((wow) => {
.subscribe((changes) => {
if (this.ogDate) {
let newDate = new Date(changes['created'])
newDate.setHours(
this.ogDate.getHours(),
this.ogDate.getMinutes(),
this.ogDate.getSeconds(),
this.ogDate.getMilliseconds()
)
this.documentForm.patchValue(
{ created: this.formatDate(newDate) },
{ emitEvent: false }
)
}
Object.assign(this.document, this.documentForm.value)
})
@@ -186,17 +199,25 @@ export class DocumentDetailComponent
this.updateComponent(doc)
}
this.ogDate = new Date(doc.created)
// Initialize dirtyCheck
this.store = new BehaviorSubject({
title: doc.title,
content: doc.content,
created: doc.created,
created: this.formatDate(this.ogDate),
correspondent: doc.correspondent,
document_type: doc.document_type,
archive_serial_number: doc.archive_serial_number,
tags: [...doc.tags],
})
// ensure we're always starting with 24-char ISO8601 string
this.documentForm.patchValue(
{ created: this.formatDate(this.ogDate) },
{ emitEvent: false }
)
this.isDirty$ = dirtyCheck(
this.documentForm,
this.store.asObservable()
@@ -450,5 +471,22 @@ export class DocumentDetailComponent
pdfPreviewLoaded(pdf: PDFDocumentProxy) {
this.previewNumPages = pdf.numPages
if (this.password) this.requiresPassword = false
}
onError(event) {
if (event.name == 'PasswordException') {
this.requiresPassword = true
}
}
onPasswordKeyUp(event: KeyboardEvent) {
if ('Enter' == event.key) {
this.password = (event.target as HTMLInputElement).value
}
}
formatDate(date: Date): string {
return date.toISOString().split('.')[0] + 'Z'
}
}
@@ -57,13 +57,18 @@
</div>
<div class="col-auto ms-auto mb-2 mb-xl-0 d-flex">
<div class="btn-group btn-group-sm me-2">
<button type="button" class="btn btn-outline-primary btn-sm" (click)="downloadSelected()">
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
<button type="button" [disabled]="awaitingDownload" class="btn btn-outline-primary btn-sm" (click)="downloadSelected()">
<svg *ngIf="!awaitingDownload" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#download" />
</svg>&nbsp;<ng-container i18n>Download</ng-container>
</svg>
<div *ngIf="awaitingDownload" class="spinner-border spinner-border-sm" role="status">
<span class="visually-hidden">Preparing download...</span>
</div>
&nbsp;
<ng-container i18n>Download</ng-container>
</button>
<div class="btn-group" ngbDropdown role="group" aria-label="Button group with nested dropdown">
<button class="btn btn-outline-primary btn-sm dropdown-toggle-split" ngbDropdownToggle></button>
<button [disabled]="awaitingDownload" class="btn btn-outline-primary btn-sm dropdown-toggle-split" ngbDropdownToggle></button>
<div class="dropdown-menu shadow" ngbDropdownMenu>
<button ngbDropdownItem i18n (click)="downloadSelected('originals')">Download originals</button>
</div>
@@ -39,6 +39,7 @@ export class BulkEditorComponent {
tagSelectionModel = new FilterableDropdownSelectionModel()
correspondentSelectionModel = new FilterableDropdownSelectionModel()
documentTypeSelectionModel = new FilterableDropdownSelectionModel()
awaitingDownload: boolean
constructor(
private documentTypeService: DocumentTypeService,
@@ -317,10 +318,12 @@ export class BulkEditorComponent {
}
downloadSelected(content = 'archive') {
this.awaitingDownload = true
this.documentService
.bulkDownload(Array.from(this.list.selected), content)
.subscribe((result: any) => {
saveAs(result, 'documents.zip')
this.awaitingDownload = false
})
}
}
@@ -60,7 +60,7 @@
}
.doc-img-background-selected {
background-color: var(--ngx-primary-faded);
background-color: var(--pngx-primary-faded);
}
.card-info {
@@ -45,7 +45,7 @@
}
.doc-img-background-selected {
background-color: var(--ngx-primary-faded);
background-color: var(--pngx-primary-faded);
}
.card-info {
@@ -75,7 +75,7 @@
</app-page-header>
<div class="sticky-top py-2 mt-n2 mt-sm-n3 py-sm-4 bg-body mx-n3 px-3">
<div class="row sticky-top pt-4 pb-2 pb-lg-4 bg-body">
<app-filter-editor [hidden]="isBulkEditing" [(filterRules)]="list.filterRules" [unmodifiedFilterRules]="unmodifiedFilterRules" #filterEditor></app-filter-editor>
<app-bulk-editor [hidden]="!isBulkEditing"></app-bulk-editor>
</div>
@@ -100,7 +100,7 @@
<ng-container *ngTemplateOutlet="pagination"></ng-container>
<ng-container *ngIf="list.error ; else documentListNoError">
<div class="alert alert-danger" role="alert">Error while loading documents: {{list.error}}</div>
<div class="alert alert-danger" role="alert"><ng-container i18n>Error while loading documents</ng-container>: {{list.error}}</div>
</ng-container>
<ng-template #documentListNoError>
@@ -185,7 +185,7 @@
</tbody>
</table>
<div class="m-n2 row row-cols-paperless-cards" *ngIf="displayMode == 'smallCards'">
<div class="row row-cols-paperless-cards" *ngIf="displayMode == 'smallCards'">
<app-document-card-small class="p-0" [selected]="list.isSelected(d)" (toggleSelected)="toggleSelected(d, $event)" [document]="d" *ngFor="let d of list.documents; trackBy: trackByDocumentId" (clickTag)="clickTag($event)" (clickCorrespondent)="clickCorrespondent($event)" (clickDocumentType)="clickDocumentType($event)"></app-document-card-small>
</div>
<div *ngIf="list.documents?.length > 15" class="mt-3">
@@ -1,11 +1,15 @@
@import "/src/theme";
::ng-deep app-document-list app-page-header > div.mb-3 {
margin-bottom: 0 !important;
}
tr {
user-select: none;
}
.table-row-selected {
background-color: var(--ngx-primary-faded);
background-color: var(--pngx-primary-faded);
}
$paperless-card-breakpoints: (
@@ -243,8 +243,12 @@ export class DocumentListComponent implements OnInit, OnDestroy, AfterViewInit {
$localize`View "${savedView.name}" created successfully.`
)
},
error: (error) => {
modal.componentInstance.error = error.error
error: (httpError) => {
let error = httpError.error
if (error.filter_rules) {
error.filter_rules = error.filter_rules.map((r) => r.value)
}
modal.componentInstance.error = error
modal.componentInstance.buttonsEnabled = true
},
})
@@ -50,7 +50,7 @@
</div>
</div>
<div class="w-100 d-xl-none"></div>
<div class="col col-xl-auto mb-2 mb-xl-0">
<div class="col col-xl-auto">
<button class="btn btn-link btn-sm px-0 mx-0 ms-xl-n3" [disabled]="!rulesModified" (click)="resetSelected()">
<svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-x me-1" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"/>
@@ -8,6 +8,11 @@
<app-input-text i18n-title title="Name" formControlName="name" [error]="error?.name"></app-input-text>
<app-input-check i18n-title title="Show in sidebar" formControlName="showInSideBar"></app-input-check>
<app-input-check i18n-title title="Show on dashboard" formControlName="showOnDashboard"></app-input-check>
<div *ngIf="error?.filter_rules" class="alert alert-danger" role="alert">
<h6 class="alert-heading" i18n>Filter rules error occurred while saving this view</h6>
<ng-container i18n>The error returned was</ng-container>:<br/>
{{ error.filter_rules }}
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-dark" (click)="cancel()" i18n [disabled]="!buttonsEnabled">Cancel</button>
@@ -9,7 +9,7 @@ import {
import { PaperlessDocument } from '../data/paperless-document'
import { PaperlessSavedView } from '../data/paperless-saved-view'
import { DOCUMENT_LIST_SERVICE } from '../data/storage-keys'
import { DocumentService } from './rest/document.service'
import { DocumentService, DOCUMENT_SORT_FIELDS } from './rest/document.service'
import { SettingsService, SETTINGS_KEYS } from './settings.service'
/**
@@ -160,7 +160,24 @@ export class DocumentListViewService {
activeListViewState.currentPage = 1
this.reload()
} else {
this.error = error.error
let errorMessage
if (
typeof error.error !== 'string' &&
Object.keys(error.error).length > 0
) {
// e.g. { archive_serial_number: Array<string> }
errorMessage = Object.keys(error.error)
.map((fieldName) => {
const fieldError: Array<string> = error.error[fieldName]
return `${
DOCUMENT_SORT_FIELDS.find((f) => f.field == fieldName)?.name
}: ${fieldError[0]}`
})
.join(', ')
} else {
errorMessage = error.error
}
this.error = errorMessage
}
},
})
@@ -0,0 +1,23 @@
import { HttpClient } from '@angular/common/http'
import { Injectable } from '@angular/core'
import { map, Observable } from 'rxjs'
import { environment } from 'src/environments/environment'
export interface AppRemoteVersion {
version: string
update_available: boolean
feature_is_set: boolean
}
@Injectable({
providedIn: 'root',
})
export class RemoteVersionService {
constructor(private http: HttpClient) {}
public checkForUpdates(): Observable<AppRemoteVersion> {
return this.http.get<AppRemoteVersion>(
`${environment.apiBaseUrl}remote_version/`
)
}
}
@@ -0,0 +1,74 @@
import { Injectable } from '@angular/core'
import { HttpEventType } from '@angular/common/http'
import { FileSystemFileEntry, NgxFileDropEntry } from 'ngx-file-drop'
import {
ConsumerStatusService,
FileStatusPhase,
} from './consumer-status.service'
import { DocumentService } from './rest/document.service'
import { Subscription } from 'rxjs'
@Injectable({
providedIn: 'root',
})
export class UploadDocumentsService {
private uploadSubscriptions: Array<Subscription> = []
constructor(
private documentService: DocumentService,
private consumerStatusService: ConsumerStatusService
) {}
uploadFiles(files: NgxFileDropEntry[]) {
for (const droppedFile of files) {
if (droppedFile.fileEntry.isFile) {
const fileEntry = droppedFile.fileEntry as FileSystemFileEntry
fileEntry.file((file: File) => {
let formData = new FormData()
formData.append('document', file, file.name)
let status = this.consumerStatusService.newFileUpload(file.name)
status.message = $localize`Connecting...`
this.uploadSubscriptions[file.name] = this.documentService
.uploadDocument(formData)
.subscribe({
next: (event) => {
if (event.type == HttpEventType.UploadProgress) {
status.updateProgress(
FileStatusPhase.UPLOADING,
event.loaded,
event.total
)
status.message = $localize`Uploading...`
} else if (event.type == HttpEventType.Response) {
status.taskId = event.body['task_id']
status.message = $localize`Upload complete, waiting...`
this.uploadSubscriptions[file.name]?.complete()
}
},
error: (error) => {
switch (error.status) {
case 400: {
this.consumerStatusService.fail(
status,
error.error.document
)
break
}
default: {
this.consumerStatusService.fail(
status,
$localize`HTTP error: ${error.status} ${error.statusText}`
)
break
}
}
this.uploadSubscriptions[file.name]?.complete()
},
})
})
}
}
}
}
+1 -1
View File
@@ -1 +1 @@
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><symbol viewBox="0 0 16 16" class="bi bi-alarm" fill="currentColor" id="alarm" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M6.5 0a.5.5 0 000 1H7v1.07a7.001 7.001 0 00-3.273 12.474l-.602.602a.5.5 0 00.707.708l.746-.746A6.97 6.97 0 008 16a6.97 6.97 0 003.422-.892l.746.746a.5.5 0 00.707-.708l-.601-.602A7.001 7.001 0 009 2.07V1h.5a.5.5 0 000-1h-3zm1.038 3.018a6.093 6.093 0 01.924 0 6 6 0 11-.924 0zM8.5 5.5a.5.5 0 00-1 0v3.362l-1.429 2.38a.5.5 0 10.858.515l1.5-2.5A.5.5 0 008.5 9V5.5zM0 3.5c0 .753.333 1.429.86 1.887A8.035 8.035 0 014.387 1.86 2.5 2.5 0 000 3.5zM13.5 1c-.753 0-1.429.333-1.887.86a8.035 8.035 0 013.527 3.527A2.5 2.5 0 0013.5 1z"/></symbol><symbol viewBox="0 0 16 16" class="bi bi-alarm-fill" fill="currentColor" id="alarm-fill" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M6 .5a.5.5 0 01.5-.5h3a.5.5 0 010 1H9v1.07a7.001 7.001 0 013.274 12.474l.601.602a.5.5 0 01-.707.708l-.746-.746A6.97 6.97 0 018 16a6.97 6.97 0 01-3.422-.892l-.746.746a.5.5 0 01-.707-.708l.602-.602A7.001 7.001 0 017 2.07V1h-.5A.5.5 0 016 .5zM.86 5.387A2.5 2.5 0 114.387 1.86 8.035 8.035 0 00.86 5.387zM11.613 1.86a2.5 2.5 0 113.527 3.527 8.035 8.035 0 00-3.527-3.527zM8.5 5.5a.5.5 0 00-1 0v3.362l-1.429 2.38a.5.5 0 10.858.515l1.5-2.5A.5.5 0 008.5 9V5.5z"/></symbol><symbol viewBox="0 0 16 16" class="bi bi-align-bottom" fill="currentColor" id="align-bottom" xmlns="http://www.w3.org/2000/svg"><path d="M6 2a1 1 0 011-1h2a1 1 0 011 1v10a1 1 0 01-1 1H7a1 1 0 01-1-1V2z"/><path fill-rule="evenodd" d="M1 14.5a.5.5 0 01.5-.5h13a.5.5 0 010 1h-13a.5.5 0 01-.5-.5z"/></symbol><symbol viewBox="0 0 16 16" class="bi bi-align-center" fill="currentColor" id="align-center" xmlns="http://www.w3.org/2000/svg"><path d="M8 1a.5.5 0 01.5.5V6h-1V1.5A.5.5 0 018 1zm0 14a.5.5 0 01-.5-.5V10h1v4.5a.5.5 0 01-.5.5zM2 7a1 1 0 011-1h10a1 1 0 011 1v2a1 1 0 01-1 1H3a1 1 0 01-1-1V7z"/></symbol><symbol viewBox="0 0 16 16" class="bi bi-align-end" fill="currentColor" id="align-end" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M14.5 1a.5.5 0 00-.5.5v13a.5.5 0 001 0v-13a.5.5 0 00-.5-.5z"/><path d="M13 7a1 1 0 00-1-1H2a1 1 0 00-1 1v2a1 1 0 001 1h10a1 1 0 001-1V7z"/></symbol><symbol viewBox="0 0 16 16" class="bi bi-align-middle" fill="currentColor" id="align-middle" xmlns="http://www.w3.org/2000/svg"><path d="M6 13a1 1 0 001 1h2a1 1 0 001-1V3a1 1 0 00-1-1H7a1 1 0 00-1 1v10zM1 8a.5.5 0 00.5.5H6v-1H1.5A.5.5 0 001 8zm14 0a.5.5 0 01-.5.5H10v-1h4.5a.5.5 0 01.5.5z"/></symbol><symbol viewBox="0 0 16 16" class="bi bi-align-start" fill="currentColor" id="align-start" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M1.5 1a.5.5 0 01.5.5v13a.5.5 0 01-1 0v-13a.5.5 0 01.5-.5z"/><path d="M3 7a1 1 0 011-1h10a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1V7z"/></symbol><symbol viewBox="0 0 16 16" class="bi bi-align-top" fill="currentColor" id="align-top" xmlns="http://www.w3.org/2000/svg"><path d="M6 14a1 1 0 001 1h2a1 1 0 001-1V4a1 1 0 00-1-1H7a1 1 0 00-1 1v10z"/><path fill-rule="evenodd" d="M1 1.5a.5.5 0 00.5.5h13a.5.5 0 000-1h-13a.5.5 0 00-.5.5z"/></symbol><symbol viewBox="0 0 16 16" class="bi bi-alt" fill="currentColor" id="alt" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M1 13.5a.5.5 0 00.5.5h3.797a.5.5 0 00.439-.26L11 3h3.5a.5.5 0 000-1h-3.797a.5.5 0 00-.439.26L5 13H1.5a.5.5 0 00-.5.5zm10 0a.5.5 0 00.5.5h3a.5.5 0 000-1h-3a.5.5 0 00-.5.5z"/></symbol><symbol viewBox="0 0 16 16" class="bi bi-app" fill="currentColor" id="app" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M11 2H5a3 3 0 00-3 3v6a3 3 0 003 3h6a3 3 0 003-3V5a3 3 0 00-3-3zM5 1a4 4 0 00-4 4v6a4 4 0 004 4h6a4 4 0 004-4V5a4 4 0 00-4-4H5z"/></symbol><symbol viewBox="0 0 16 16" class="bi bi-app-indicator" fill="currentColor" id="app-indicator" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M5.5 2A3.5 3.5 0 002 5.5v5A3.5 3.5 0 005.5 14h5a3.5 3.5 0 003.5-3.5V8a.5.5 0 011 0v2.5a4.5 4.5 0 01-4.5 4.5h-5A4.5 4.5 0 011 10.5v-5A4.5 4.5 0 015.5 1H8a.5.5 0 010 1H5.5z"/><path d="M16 3a3 3 0 11-6 0 3 3 0 016 0z"/></symbol><symbol viewBox="0 0 16 16" class="bi bi-archive" fill="currentColor" id="archive" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M0 2a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1v7.5a2.5 2.5 0 01-2.5 2.5h-9A2.5 2.5 0 011 12.5V5a1 1 0 01-1-1V2zm2 3v7.5A1.5 1.5 0 003.5 14h9a1.5 1.5 0 001.5-1.5V5H2zm13-3H1v2h14V2zM5 7.5a.5.5 0 01.5-.5h5a.5.5 0 010 1h-5a.5.5 0 01-.5-.5z"/></symbol><symbol viewBox="0 0 16 16" class="bi bi-archive-fill" fill="currentColor" id="archive-fill" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M12.643 15C13.979 15 15 13.845 15 12.5V5H1v7.5C1 13.845 2.021 15 3.357 15h9.286zM5.5 7a.5.5 0 000 1h5a.5.5 0 000-1h-5zM.8 1a.8.8 0 00-.8.8V3a.8.8 0 00.8.8h14.4A.8.8 0 0016 3V1.8a.8.8 0 00-.8-.8H.8z"/></symbol><symbol viewBox="0 0 16 16" class="bi bi-arrLine truncated
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><symbol class="bi bi-123" viewBox="0 0 16 16" id="123"><path d="M2.873 11.297V4.142H1.699L0 5.379v1.137l1.64-1.18h.06v5.961h1.174zm3.213-5.09v-.063c0-.618.44-1.169 1.196-1.169.676 0 1.174.44 1.174 1.106 0 .624-.42 1.101-.807 1.526L4.99 10.553v.744h4.78v-.99H6.643v-.069L8.41 8.252c.65-.724 1.237-1.332 1.237-2.27C9.646 4.849 8.723 4 7.308 4c-1.573 0-2.36 1.064-2.36 2.15v.057h1.138zm6.559 1.883h.786c.823 0 1.374.481 1.379 1.179.01.707-.55 1.216-1.421 1.21-.77-.005-1.326-.419-1.379-.953h-1.095c.042 1.053.938 1.918 2.464 1.918 1.478 0 2.642-.839 2.62-2.144-.02-1.143-.922-1.651-1.551-1.714v-.063c.535-.09 1.347-.66 1.326-1.678-.026-1.053-.933-1.855-2.359-1.845-1.5.005-2.317.88-2.348 1.898h1.116c.032-.498.498-.944 1.206-.944.703 0 1.206.435 1.206 1.07.005.64-.504 1.106-1.2 1.106h-.75v.96z"/></symbol><symbol class="bi bi-activity" viewBox="0 0 16 16" id="activity"><path fill-rule="evenodd" d="M6 2a.5.5 0 0 1 .47.33L10 12.036l1.53-4.208A.5.5 0 0 1 12 7.5h3.5a.5.5 0 0 1 0 1h-3.15l-1.88 5.17a.5.5 0 0 1-.94 0L6 3.964 4.47 8.171A.5.5 0 0 1 4 8.5H.5a.5.5 0 0 1 0-1h3.15l1.88-5.17A.5.5 0 0 1 6 2z"/></symbol><symbol class="bi bi-alarm" viewBox="0 0 16 16" id="alarm"><path d="M8.5 5.5a.5.5 0 0 0-1 0v3.362l-1.429 2.38a.5.5 0 1 0 .858.515l1.5-2.5A.5.5 0 0 0 8.5 9V5.5z"/><path d="M6.5 0a.5.5 0 0 0 0 1H7v1.07a7.001 7.001 0 0 0-3.273 12.474l-.602.602a.5.5 0 0 0 .707.708l.746-.746A6.97 6.97 0 0 0 8 16a6.97 6.97 0 0 0 3.422-.892l.746.746a.5.5 0 0 0 .707-.708l-.601-.602A7.001 7.001 0 0 0 9 2.07V1h.5a.5.5 0 0 0 0-1h-3zm1.038 3.018a6.093 6.093 0 0 1 .924 0 6 6 0 1 1-.924 0zM0 3.5c0 .753.333 1.429.86 1.887A8.035 8.035 0 0 1 4.387 1.86 2.5 2.5 0 0 0 0 3.5zM13.5 1c-.753 0-1.429.333-1.887.86a8.035 8.035 0 0 1 3.527 3.527A2.5 2.5 0 0 0 13.5 1z"/></symbol><symbol class="bi bi-alarm-fill" viewBox="0 0 16 16" id="alarm-fill"><path d="M6 .5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1H9v1.07a7.001 7.001 0 0 1 3.274 12.474l.601.602a.5.5 0 0 1-.707.708l-.746-.746A6.97 6.97 0 0 1 8 16a6.97 6.97 0 0 1-3.422-.892l-.746.746a.5.5 0 0 1-.707-.708l.602-.602A7.001 7.001 0 0 1 7 2.07V1h-.5A.5.5 0 0 1 6 .5zm2.5 5a.5.5 0 0 0-1 0v3.362l-1.429 2.38a.5.5 0 1 0 .858.515l1.5-2.5A.5.5 0 0 0 8.5 9V5.5zM.86 5.387A2.5 2.5 0 1 1 4.387 1.86 8.035 8.035 0 0 0 .86 5.387zM11.613 1.86a2.5 2.5 0 1 1 3.527 3.527 8.035 8.035 0 0 0-3.527-3.527z"/></symbol><symbol class="bi bi-align-bottom" viewBox="0 0 16 16" id="align-bottom"><rect width="4" height="12" x="6" y="1" rx="1"/><path d="M1.5 14a.5.5 0 0 0 0 1v-1zm13 1a.5.5 0 0 0 0-1v1zm-13 0h13v-1h-13v1z"/></symbol><symbol class="bi bi-align-center" viewBox="0 0 16 16" id="align-center"><path d="M8 1a.5.5 0 0 1 .5.5V6h-1V1.5A.5.5 0 0 1 8 1zm0 14a.5.5 0 0 1-.5-.5V10h1v4.5a.5.5 0 0 1-.5.5zM2 7a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V7z"/></symbol><symbol class="bi bi-align-end" viewBox="0 0 16 16" id="align-end"><path fill-rule="evenodd" d="M14.5 1a.5.5 0 0 0-.5.5v13a.5.5 0 0 0 1 0v-13a.5.5 0 0 0-.5-.5z"/><path d="M13 7a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V7z"/></symbol><symbol class="bi bi-align-middle" viewBox="0 0 16 16" id="align-middle"><path d="M6 13a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H7a1 1 0 0 0-1 1v10zM1 8a.5.5 0 0 0 .5.5H6v-1H1.5A.5.5 0 0 0 1 8zm14 0a.5.5 0 0 1-.5.5H10v-1h4.5a.5.5 0 0 1 .5.5z"/></symbol><symbol class="bi bi-align-start" viewBox="0 0 16 16" id="align-start"><path fill-rule="evenodd" d="M1.5 1a.5.5 0 0 1 .5.5v13a.5.5 0 0 1-1 0v-13a.5.5 0 0 1 .5-.5z"/><path d="M3 7a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V7z"/></symbol><symbol class="bi bi-align-top" viewBox="0 0 16 16" id="align-top"><rect width="4" height="12" rx="1" transform="matrix(1 0 0 -1 6 15)"/><path d="M1.5 2a.5.5 0 0 1 0-1v1zm13-1a.5.5 0 0 1 0 1V1zm-13 0h13v1h-13V1z"/></symbol><symbol class="bi bi-alt" viewBox="0 0 16 16" id="alt"><path d="M1 13.5a.5.5 0 0 0 .5.5h3.797a.5.5 0 0 0 .439-.26L11 3h3.5a.5.5 0 0 0 0-1h-3.797a.5.5 0 0 0-.439.26L5 13H1.5a.5.5 0 0 0-.5.5zm10 0a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 0-1h-3a.5.5 0 0 0-.5.5z"/></symbol><symbol class="bi bi-app" viewBox="0 0 16 16" id="app"><path d="M11 2a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H5a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3h6zM5 1a4 4 0 0 0-4 4v6a4 4 0 0 0 4 4h6a4 4 0 0 0 4-4V5a4 4 0 0 0-4-4H5z"/></symbol><symbol class="bi bi-app-indicator" viewBox="0 0 16 16" id="app-indicator"><path d="M5.5 2A3.5 3.5 0 0 0 2 5.5v5A3.5 3.5 0 0 0 5.5 14h5a3.5 3.5 0 0 0 3.5-3.5V8a.5.5 0 0 1 1 0v2.5a4.5 4.5 0 0 1-4.5 4.5h-5A4.5 4.5 0 0 1 1 10.5v-5A4.5 4.5 0 0 1 5.5 1H8a.5.5 0 0 1 0 1H5.5z"/><path d="M16 3a3 3 0 1 1-6 0 3 3 0 0 1 6 0z"/></symbol><symbol class="bi bi-apple" viewBox="0 0 16 16" id="apple"><path d="M11.182.008C11.148-.03 9.923.023 8.857 1.18c-1.066 1.156-.902 2.482-.878 2.516.024.034 1.52.087 2.475-1.258.955-1.345.762-2.391.728-2.43zm3.314 11.733c-.048-.096-2.325-1.234-2.113-3.422.212-2.189 1.675-2.789 1.698-2.854.023-.065-.597-.79-1.254-1.157a3.692 3.692 0 0 0-1.56Line truncated

Before

Width:  |  Height:  |  Size: 579 KiB

After

Width:  |  Height:  |  Size: 861 KiB

+1 -1
View File
@@ -5,7 +5,7 @@ export const environment = {
apiBaseUrl: document.baseURI + 'api/',
apiVersion: '2',
appTitle: 'Paperless-ngx',
version: '1.6.0',
version: '1.7.0-rc1',
webSocketHost: window.location.host,
webSocketProtocol: window.location.protocol == 'https:' ? 'wss:' : 'ws:',
webSocketBaseUrl: base_url.pathname + 'ws/',
+343 -16
View File
@@ -7,24 +7,112 @@ $enable-negative-margins: true;
@import "theme_dark";
@import "print";
.toolbaricon {
width: 1.2em;
height: 1.2em;
}
.buttonicon {
width: 1.2em;
height: 1.2em;
}
.sidebaricon {
width: 16px;
height: 16px;
vertical-align: text-bottom;
}
// Paperless-ngx styles
body {
font-size: 0.875rem;
height: 100vh;
}
* {
transition: background-color 0.3s ease, border-color 0.3s ease;
}
svg.logo {
.leaf {
fill: var(--bs-primary) !important;
}
.text {
fill: var(--bs-body-color) !important;
}
}
.nav-link, .list-group-item {
color: var(--bs-body-color);
}
.bg-body {
background-color: var(--bs-body-bg);
}
.bg-primary {
background-color: var(--bs-primary) !important;
}
.btn-primary {
background-color: var(--bs-primary);
border-color: var(--bs-primary);
&:hover, &:focus {
background-color: var(--pngx-primary-darken-10);
border-color: var(--pngx-primary-darken-10);
}
&:disabled, &.disabled {
background-color: var(--pngx-primary-darken-10) !important;
border-color: var(--pngx-primary-darken-10) !important;
}
}
.btn-outline-primary {
border-color: var(--bs-primary) !important;
color: var(--bs-primary) !important;
&:hover, &:focus, &.active, &:active {
background-color: var(--bs-primary) !important;
color: var(--bs-light) !important;
}
}
.btn-outline-secondary {
color: var(--bs-secondary);
&:hover {
color: var(--bs-light);
}
}
.nav-item .sidebaricon {
color: var(--bs-secondary);
}
.btn:focus,
.btn:active:focus,
.dropdown-item:focus,
.btn-check:focus + .btn,
.form-control:focus,
.form-check-input:focus,
.form-check-radio:focus,
.form-select:focus {
box-shadow: 0 0 0 0.25rem hsla(var(--pngx-primary), var(--pngx-primary-lightness), var(--pngx-focus-alpha));
}
.form-switch .form-check-input:focus {
background-image: escape-svg(url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'><circle r='3' fill='#bbb'/></svg>"));
}
.nav-link:focus-visible, .nav-item a:focus-visible {
outline: none;
background-color: var(--pngx-bg-darker);
}
a.navbar-brand:focus-visible {
outline: none;
color: var(--pngx-primary-darken-10);
}
.dropdown.show {
> .btn-primary {
background-color: var(--bs-primary);
border-color: var(--bs-primary);
}
> .btn-outline-primary {
color: var(--pngx-primary-text-contrast) !important;
}
}
a, a:hover, .btn-link, .btn-link:hover {
color: var(--bs-primary);
}
.form-control-dark {
@@ -116,6 +204,245 @@ body {
}
}
.form-control:not(.btn),
input,
select,
textarea,
.form-select:not(.is-invalid):not(:disabled),
.form-check-input {
color: var(--bs-body-color);
background-color: var(--bs-body-bg);
border-color: var(--bs-border-color);
&:focus {
background-color: var(--pngx-bg-darker);
color: var(--bs-body-color);
}
}
.form-check-input:checked {
background-color: var(--bs-primary);
border-color: var(--bs-primary);
}
.form-check-input:focus {
border-color: var(--bs-primary);
}
.page-link {
color: var(--bs-secondary);
background-color: var(--bs-body-bg);
border-color: var(--bs-border-color) !important;
&:hover, &:focus {
background-color: var(--bs-primary) !important;
color: var(--bs-light) !important;
}
}
.page-item.active .page-link {
background-color: var(--bs-primary);
border-color: var(--bs-primary) !important;
color: var(--bs-light);
}
.page-item.disabled .page-link {
background-color: var(--pngx-bg-darker);
}
.nav-tabs {
border-bottom: 1px solid var(--bs-border-color);
.nav-link {
color: var(--bs-primary);
&.active, &:hover {
border-color: var(--bs-border-color);
background-color: var(--bs-body-bg);
color: var(--bs-body-color);
border-bottom: 1px solid transparent;
}
&:focus {
border-color: var(--bs-border-color);
}
&.active:focus, &:active {
border-bottom: 1px solid transparent;
}
}
}
.ng-select-container,
.ng-select.ng-select-opened > .ng-select-container,
.ng-dropdown-panel,
.ng-dropdown-panel .ng-dropdown-panel-items .ng-option {
background-color: var(--bs-body-bg) !important;
color: var(--bs-body-color) !important;
border-color: var(--bs-border-color) !important;
input:focus {
background-color: transparent !important;
}
}
.input-group-text {
color: var(--bs-body-color);
background-color: var(--bs-body-bg);
border-color: var(--bs-border-color);
}
.list-group-item {
color: var(--bs-body-color);
background-color: var(--bs-body-bg);
border-color: var(--bs-border-color);
&:hover, &:focus {
background-color: var(--bs-body-bg);
}
}
.dropdown-menu {
background-color: var(--bs-body-bg);
.dropdown-divider {
border-color: var(--bs-border-color);
}
.dropdown-item {
color: var(--bs-body-color);
&:hover, &:focus {
background-color: var(--pngx-bg-darker);
color: var(--bs-body-color);
}
&.active {
background-color: var(--bs-primary);
color: var(--pngx-primary-text-contrast);
}
}
}
// icons
.toolbaricon {
width: 1.2em;
height: 1.2em;
}
.buttonicon {
width: 1.2em;
height: 1.2em;
}
.sidebaricon {
width: 16px;
height: 16px;
vertical-align: text-bottom;
}
table.table {
color: var(--bs-body-color);
.des,.asc {
background-color: var(--bs-body-bg) !important;
}
}
.close {
color: var(--bs-body-color);
}
.modal .btn-close {
color: var(--bs-body-color);
}
.main-dropzone {
height: 100%;
width: 100%;
&.ngx-file-drop__drop-zone--over {
background-color: transparent !important;
}
}
.global-dropzone-overlay {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: rgba(23, 84, 31, .8);
z-index: 1055; // $zindex-modal
pointer-events: none !important;
user-select: none !important;
text-align: center;
padding-top: 25%;
&.show {
opacity: 1 !important;
}
&.hide {
display: none;
}
}
.ngx-file-drop__drop-zone--over .global-dropzone-overlay {
opacity: 0;
}
.inert {
pointer-events: none !important;
user-select: none !important;
}
.alert-danger {
color: var(--bs-body-color);
background-color: var(--bs-danger);
border-color: var(--bs-danger);
}
.alert-secondary {
background-color: var(--pngx-primary-darken-18);
border-color: var(--pngx-primary-darken-15);
color: var(--bs-body-color);
}
.ngb-dp-header,
.ngb-dp-weekdays,
.ngb-dp-month {
background-color: var(--bs-body-bg);
}
.popover {
.popover-header,
.popover-body {
background-color: var(--pngx-bg-alt);
border-color: var(--bs-border-color);
color: var(--bs-body-color);
}
}
// fix popover carat colors
.bs-popover-start > .popover-arrow::after, .bs-popover-auto[data-popper-placement^="left"] {
border-left-color: var(--pngx-bg-alt);
}
.bs-popover-end > .popover-arrow::after, .bs-popover-auto[data-popper-placement^="right"] {
border-right-color: var(--pngx-bg-alt);
}
.bs-popover-top > .popover-arrow::after, .bs-popover-auto[data-popper-placement^="top"] {
border-top-color: var(--pngx-bg-alt);
}
.bs-popover-bottom > .popover-arrow::after, .bs-popover-auto[data-popper-placement^="bottom"] {
border-bottom-color: var(--pngx-bg-alt);
}
.bs-popover-bottom .popover-header::before,
.bs-popover-auto[x-placement^=bottom] .popover-header::before {
border-bottom-color: var(--pngx-bg-alt);
}
// Bootstrap 5 tweaks
a.badge {
text-decoration: none;
+9 -286
View File
@@ -2,293 +2,16 @@
// base color e.g. #17541f = hsl(128, 57%, 21%)
--pngx-primary: 128, 57%;
--pngx-primary-lightness: 21%;
--pngx-primary-text-contrast: var(--bs-light);
--bs-primary: hsl(var(--pngx-primary), var(--pngx-primary-lightness));
--bs-border-color: var(--bs-gray-400);
--ngx-primary-faded: hsl(var(--pngx-primary), calc(var(--pngx-primary-lightness) + 72%));
--ngx-primary-lighten-10: hsl(var(--pngx-primary), calc(var(--pngx-primary-lightness) + 10%));
--ngx-primary-lighten-30: hsl(var(--pngx-primary), calc(var(--pngx-primary-lightness) + 30%));
--ngx-primary-darken-10: hsl(var(--pngx-primary), calc(var(--pngx-primary-lightness) - 10%));
--ngx-primary-darken-15: hsl(var(--pngx-primary), calc(var(--pngx-primary-lightness) - 15%));
--ngx-primary-darken-18: hsl(var(--pngx-primary), calc(var(--pngx-primary-lightness) - 18%));
--ngx-bg-darker: var(--bs-gray-100);
--ngx-focus-alpha: 0.3;
}
svg.logo {
.leaf {
fill: var(--bs-primary) !important;
}
.text {
fill: var(--bs-body-color) !important;
}
}
.nav-link, .list-group-item {
color: var(--bs-body-color);
}
.bg-body {
background-color: var(--bs-body-bg);
}
.bg-primary {
background-color: var(--bs-primary) !important;
}
.btn-primary {
background-color: var(--bs-primary);
border-color: var(--bs-primary);
&:hover, &:focus {
background-color: var(--ngx-primary-darken-10);
border-color: var(--ngx-primary-darken-10);
}
&:disabled, &.disabled {
background-color: var(--ngx-primary-darken-10) !important;
border-color: var(--ngx-primary-darken-10) !important;
}
}
.btn-outline-primary {
border-color: var(--bs-primary) !important;
color: var(--bs-primary) !important;
&:hover, &:focus, &.active, &:active {
background-color: var(--bs-primary) !important;
color: var(--bs-light) !important;
}
}
.btn-outline-secondary {
color: var(--bs-secondary);
}
.nav-item .sidebaricon {
color: var(--bs-secondary);
}
.btn:focus,
.btn:active:focus,
.dropdown-item:focus,
.btn-check:focus + .btn,
.form-control:focus,
.form-check-input:focus,
.form-check-radio:focus,
.form-select:focus {
box-shadow: 0 0 0 0.25rem hsla(var(--pngx-primary), var(--pngx-primary-lightness), var(--ngx-focus-alpha));
}
.form-switch .form-check-input:focus {
background-image: escape-svg(url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'><circle r='3' fill='#bbb'/></svg>"));
}
.nav-link:focus-visible, .nav-item a:focus-visible {
outline: none;
background-color: var(--ngx-bg-darker);
}
a.navbar-brand:focus-visible {
outline: none;
color: var(--ngx-primary-darken-10);
}
.dropdown.show {
> .btn-primary {
background-color: var(--bs-primary);
border-color: var(--bs-primary);
}
> .btn-outline-primary {
color: var(--bs-body-color) !important;
}
}
a, a:hover, .btn-link, .btn-link:hover {
color: var(--bs-primary);
}
.form-control:not(.btn),
input,
select,
textarea,
.form-select:not(.is-invalid):not(:disabled),
.form-check-input {
color: var(--bs-body-color);
background-color: var(--bs-body-bg);
border-color: var(--bs-border-color);
&:focus {
background-color: var(--ngx-bg-darker);
color: var(--bs-body-color) !important;
}
}
.form-check-input:checked {
background-color: var(--bs-primary);
border-color: var(--bs-primary);
}
.form-check-input:focus {
border-color: var(--bs-primary);
}
.page-link {
color: var(--bs-secondary);
background-color: var(--bs-body-bg);
border-color: var(--bs-border-color) !important;
&:hover, &:focus {
background-color: var(--bs-primary) !important;
color: var(--bs-light) !important;
}
}
.page-item.active .page-link {
background-color: var(--bs-primary);
border-color: var(--bs-primary) !important;
color: var(--bs-light);
}
.page-item.disabled .page-link {
background-color: var(--ngx-bg-darker);
}
.nav-tabs {
border-bottom: 1px solid var(--bs-border-color);
.nav-link {
color: var(--bs-primary);
&.active, &:hover {
border-color: var(--bs-border-color);
background-color: var(--bs-body-bg);
color: var(--bs-body-color);
border-bottom: 1px solid transparent;
}
&:focus {
border-color: var(--bs-border-color);
}
&.active:focus, &:active {
border-bottom: 1px solid transparent;
}
}
}
.ng-select-container,
.ng-select.ng-select-opened > .ng-select-container,
.ng-dropdown-panel,
.ng-dropdown-panel .ng-dropdown-panel-items .ng-option {
background-color: var(--bs-body-bg) !important;
color: var(--bs-body-color) !important;
border-color: var(--bs-border-color) !important;
input:focus {
background-color: transparent !important;
}
}
.input-group-text {
color: var(--bs-body-color);
background-color: var(--bs-body-bg);
border-color: var(--bs-border-color);
}
.list-group-item {
color: var(--bs-body-color);
background-color: var(--bs-body-bg);
border-color: var(--bs-border-color);
&:hover, &:focus {
background-color: var(--bs-body-bg);
}
}
.dropdown-menu {
background-color: var(--bs-body-bg);
.dropdown-divider {
border-color: var(--bs-border-color);
}
.dropdown-item {
color: var(--bs-body-color);
&:hover {
background-color: var(--ngx-bg-darker);
color: var(--bs-body-color);
}
&.active {
background-color: var(--bs-primary);
}
}
}
table.table {
color: var(--bs-body-color);
.des,.asc {
background-color: var(--bs-body-bg) !important;
}
}
.close {
color: var(--bs-body-color);
}
.modal .btn-close {
color: var(--bs-body-color);
}
.ngx-file-drop__drop-zone--over {
background-color: var(--ngx-primary-faded) !important;
}
.alert-danger {
color: var(--bs-body-color);
background-color: var(--bs-danger);
border-color: var(--bs-danger);
}
.alert-secondary {
background-color: var(--ngx-primary-darken-18);
border-color: var(--ngx-primary-darken-15);
color: var(--bs-body-color);
}
.ngb-dp-header,
.ngb-dp-weekdays,
.ngb-dp-month {
background-color: var(--bs-body-bg);
}
.popover {
.popover-header,
.popover-body {
background-color: var(--ngx-bg-alt);
border-color: var(--bs-border-color);
}
}
// fix popover carat colors
.bs-popover-start > .popover-arrow::after, .bs-popover-auto[data-popper-placement^="left"] {
border-left-color: var(--ngx-bg-alt);
}
.bs-popover-end > .popover-arrow::after, .bs-popover-auto[data-popper-placement^="right"] {
border-right-color: var(--ngx-bg-alt);
}
.bs-popover-top > .popover-arrow::after, .bs-popover-auto[data-popper-placement^="top"] {
border-top-color: var(--ngx-bg-alt);
}
.bs-popover-bottom > .popover-arrow::after, .bs-popover-auto[data-popper-placement^="bottom"] {
border-bottom-color: var(--ngx-bg-alt);
}
.bs-popover-bottom .popover-header::before,
.bs-popover-auto[x-placement^=bottom] .popover-header::before {
border-bottom-color: var(--ngx-bg-alt);
--pngx-primary-faded: hsl(var(--pngx-primary), calc(var(--pngx-primary-lightness) + 72%));
--pngx-primary-lighten-10: hsl(var(--pngx-primary), calc(var(--pngx-primary-lightness) + 10%));
--pngx-primary-lighten-30: hsl(var(--pngx-primary), calc(var(--pngx-primary-lightness) + 30%));
--pngx-primary-darken-10: hsl(var(--pngx-primary), calc(var(--pngx-primary-lightness) - 10%));
--pngx-primary-darken-15: hsl(var(--pngx-primary), calc(var(--pngx-primary-lightness) - 15%));
--pngx-primary-darken-18: hsl(var(--pngx-primary), calc(var(--pngx-primary-lightness) - 18%));
--pngx-bg-darker: var(--bs-gray-100);
--pngx-focus-alpha: 0.3;
}
+16 -13
View File
@@ -13,10 +13,6 @@ $text-color-dark-mode: #abb2bf;
$text-color-dark-mode-accent: lighten($text-color-dark-mode, 10%);
$border-color-dark-mode: #47494f;
* {
transition: background-color 0.3s ease, border-color 0.3s ease;
}
@mixin dark-mode {
--bs-primary: hsl(var(--pngx-primary), calc(var(--pngx-primary-lightness) + 10%));
--bs-danger: #{$danger-dark-mode};
@@ -27,11 +23,12 @@ $border-color-dark-mode: #47494f;
--bs-light: #{$bg-light-dark-mode};
--bs-light-rgb: #{$bg-light-dark-mode-rgb};
--bs-border-color: #{$border-color-dark-mode};
--ngx-bg-darker: #{$bg-dark-mode-accent};
--ngx-bg-alt: #{$bg-dark-mode-alt};
--ngx-body-color-accent: #{$text-color-dark-mode-accent};
--ngx-focus-alpha: 0.7;
--ngx-primary-faded: var(--ngx-primary-darken-15);
--pngx-bg-darker: #{$bg-dark-mode-accent};
--pngx-bg-alt: #{$bg-dark-mode-alt};
--pngx-body-color-accent: #{$text-color-dark-mode-accent};
--pngx-focus-alpha: 0.7;
--pngx-primary-faded: var(--pngx-primary-darken-15);
--pngx-primary-text-contrast: var(--bs-body-color);
.navbar.bg-primary{
--bs-primary: hsl(var(--pngx-primary),var(--pngx-primary-lightness));
@@ -64,17 +61,23 @@ $border-color-dark-mode: #47494f;
.btn-outline-primary, .btn-primary {
&:hover, &:focus, &.active, &:active {
color: var(--ngx-body-color-accent) !important;
color: var(--bs-light) !important;
}
}
.btn-outline-secondary {
&:hover, &:focus, &.active, &:active {
background-color: var(--ngx-bg-darker);
background-color: var(--pngx-bg-darker);
color: var(--bs-primary);
}
}
.search-form-container {
input, input:focus {
color: var(--bs-body-color) !important;
}
}
.card {
background-color: var(--bs-body-bg);
@@ -141,11 +144,11 @@ $border-color-dark-mode: #47494f;
color: $text-color-dark-mode-accent;
}
.close, .modal .btn-close {
.close, .modal .btn-close, .alert .btn-close {
text-shadow: 0 1px 0 #666;
}
.modal .btn-close {
.modal .btn-close, .alert .btn-close {
filter: invert(1) grayscale(100%) brightness(200%);
}
@@ -3,7 +3,9 @@ import os
from pathlib import Path
from pathlib import PurePath
from threading import Thread
from time import monotonic
from time import sleep
from typing import Final
from django.conf import settings
from django.core.management.base import BaseCommand
@@ -53,6 +55,25 @@ def _consume(filepath):
logger.warning(f"Not consuming file {filepath}: Unknown file extension.")
return
# Total wait time: up to 500ms
os_error_retry_count: Final[int] = 50
os_error_retry_wait: Final[float] = 0.01
read_try_count = 0
file_open_ok = False
while (read_try_count < os_error_retry_count) and not file_open_ok:
try:
with open(filepath, "rb"):
file_open_ok = True
except OSError:
read_try_count += 1
sleep(os_error_retry_wait)
if read_try_count >= os_error_retry_count:
logger.warning(f"Not consuming file {filepath}: OS reports file as busy still")
return
tag_ids = None
try:
if settings.CONSUMER_SUBDIRS_AS_TAGS:
@@ -81,19 +102,23 @@ def _consume_wait_unmodified(file):
logger.debug(f"Waiting for file {file} to remain unmodified")
mtime = -1
size = -1
current_try = 0
while current_try < settings.CONSUMER_POLLING_RETRY_COUNT:
try:
new_mtime = os.stat(file).st_mtime
stat_data = os.stat(file)
new_mtime = stat_data.st_mtime
new_size = stat_data.st_size
except FileNotFoundError:
logger.debug(
f"File {file} moved while waiting for it to remain " f"unmodified.",
)
return
if new_mtime == mtime:
if new_mtime == mtime and new_size == size:
_consume(file)
return
mtime = new_mtime
size = new_size
sleep(settings.CONSUMER_POLLING_DELAY)
current_try += 1
@@ -182,14 +207,32 @@ class Command(BaseCommand):
descriptor = inotify.add_watch(directory, inotify_flags)
try:
inotify_debounce: Final[float] = 0.5
notified_files = {}
while not self.stop_flag:
for event in inotify.read(timeout=1000):
if recursive:
path = inotify.get_path(event.wd)
else:
path = directory
filepath = os.path.join(path, event.name)
_consume(filepath)
notified_files[filepath] = monotonic()
# Check the files against the timeout
still_waiting = {}
for filepath in notified_files:
# Time of the last inotify event for this file
last_event_time = notified_files[filepath]
if (monotonic() - last_event_time) > inotify_debounce:
_consume(filepath)
else:
still_waiting[filepath] = last_event_time
# These files are still waiting to hit the timeout
notified_files = still_waiting
except KeyboardInterrupt:
pass
+1 -1
View File
@@ -60,7 +60,7 @@ def match_tags(document, classifier):
def matches(matching_model, document):
search_kwargs = {}
document_content = document.content.lower()
document_content = document.content
# Check that match is not empty
if matching_model.match.strip() == "":
@@ -0,0 +1,20 @@
# Generated by Django 4.0.3 on 2022-04-01 22:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("documents", "1017_alter_savedviewfilterrule_rule_type"),
]
operations = [
migrations.AlterField(
model_name="savedviewfilterrule",
name="value",
field=models.CharField(
blank=True, max_length=255, null=True, verbose_name="value"
),
),
]
+1 -1
View File
@@ -375,7 +375,7 @@ class SavedViewFilterRule(models.Model):
rule_type = models.PositiveIntegerField(_("rule type"), choices=RULE_TYPES)
value = models.CharField(_("value"), max_length=128, blank=True, null=True)
value = models.CharField(_("value"), max_length=255, blank=True, null=True)
class Meta:
verbose_name = _("filter rule")
+3 -1
View File
@@ -23,6 +23,7 @@ from documents.signals import document_consumer_declaration
# - XX. MONTH ZZZZ with XX being 1 or 2 and ZZZZ being 2 or 4 digits
# - MONTH ZZZZ, with ZZZZ being 4 digits
# - MONTH XX, ZZZZ with XX being 1 or 2 and ZZZZ being 4 digits
# - XX MON ZZZZ with XX being 1 or 2 and ZZZZ being 4 digits. MONTH is 3 letters
# TODO: isnt there a date parsing library for this?
@@ -31,7 +32,8 @@ DATE_REGEX = re.compile(
r"(\b|(?!=([_-])))([0-9]{4}|[0-9]{2})[\.\/-]([0-9]{1,2})[\.\/-]([0-9]{1,2})(\b|(?=([_-])))|" # noqa: E501
r"(\b|(?!=([_-])))([0-9]{1,2}[\. ]+[^ ]{3,9} ([0-9]{4}|[0-9]{2}))(\b|(?=([_-])))|" # noqa: E501
r"(\b|(?!=([_-])))([^\W\d_]{3,9} [0-9]{1,2}, ([0-9]{4}))(\b|(?=([_-])))|"
r"(\b|(?!=([_-])))([^\W\d_]{3,9} [0-9]{4})(\b|(?=([_-])))",
r"(\b|(?!=([_-])))([^\W\d_]{3,9} [0-9]{4})(\b|(?=([_-])))|"
r"(\b|(?!=([_-])))(\b[0-9]{1,2}[ \.\/-][A-Z]{3}[ \.\/-][0-9]{4})(\b|(?=([_-])))", # noqa: E501
)
+161
View File
@@ -1,6 +1,12 @@
import logging
import os
import shutil
import tempfile
from typing import List # for type hinting. Can be removed, if only Python >3.8 is used
import tqdm
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
from django.conf import settings
from django.db.models.signals import post_save
from documents import index
@@ -14,8 +20,12 @@ from documents.models import Document
from documents.models import DocumentType
from documents.models import Tag
from documents.sanity_checker import SanityCheckFailedException
from pdf2image import convert_from_path
from pikepdf import Pdf
from pyzbar import pyzbar
from whoosh.writing import AsyncWriter
logger = logging.getLogger("paperless.tasks")
@@ -62,6 +72,115 @@ def train_classifier():
logger.warning("Classifier error: " + str(e))
def barcode_reader(image) -> List[str]:
"""
Read any barcodes contained in image
Returns a list containing all found barcodes
"""
barcodes = []
# Decode the barcode image
detected_barcodes = pyzbar.decode(image)
if detected_barcodes:
# Traverse through all the detected barcodes in image
for barcode in detected_barcodes:
if barcode.data:
decoded_barcode = barcode.data.decode("utf-8")
barcodes.append(decoded_barcode)
logger.debug(
f"Barcode of type {str(barcode.type)} found: {decoded_barcode}",
)
return barcodes
def scan_file_for_separating_barcodes(filepath: str) -> List[int]:
"""
Scan the provided file for page separating barcodes
Returns a list of pagenumbers, which separate the file
"""
separator_page_numbers = []
separator_barcode = str(settings.CONSUMER_BARCODE_STRING)
# use a temporary directory in case the file os too big to handle in memory
with tempfile.TemporaryDirectory() as path:
pages_from_path = convert_from_path(filepath, output_folder=path)
for current_page_number, page in enumerate(pages_from_path):
current_barcodes = barcode_reader(page)
if separator_barcode in current_barcodes:
separator_page_numbers.append(current_page_number)
return separator_page_numbers
def separate_pages(filepath: str, pages_to_split_on: List[int]) -> List[str]:
"""
Separate the provided file on the pages_to_split_on.
The pages which are defined by page_numbers will be removed.
Returns a list of (temporary) filepaths to consume.
These will need to be deleted later.
"""
os.makedirs(settings.SCRATCH_DIR, exist_ok=True)
tempdir = tempfile.mkdtemp(prefix="paperless-", dir=settings.SCRATCH_DIR)
fname = os.path.splitext(os.path.basename(filepath))[0]
pdf = Pdf.open(filepath)
document_paths = []
logger.debug(f"Temp dir is {str(tempdir)}")
if not pages_to_split_on:
logger.warning("No pages to split on!")
else:
# go from the first page to the first separator page
dst = Pdf.new()
for n, page in enumerate(pdf.pages):
if n < pages_to_split_on[0]:
dst.pages.append(page)
output_filename = "{}_document_0.pdf".format(fname)
savepath = os.path.join(tempdir, output_filename)
with open(savepath, "wb") as out:
dst.save(out)
document_paths = [savepath]
# iterate through the rest of the document
for count, page_number in enumerate(pages_to_split_on):
logger.debug(f"Count: {str(count)} page_number: {str(page_number)}")
dst = Pdf.new()
try:
next_page = pages_to_split_on[count + 1]
except IndexError:
next_page = len(pdf.pages)
# skip the first page_number. This contains the barcode page
for page in range(page_number + 1, next_page):
logger.debug(
f"page_number: {str(page_number)} next_page: {str(next_page)}",
)
dst.pages.append(pdf.pages[page])
output_filename = "{}_document_{}.pdf".format(fname, str(count + 1))
logger.debug(f"pdf no:{str(count)} has {str(len(dst.pages))} pages")
savepath = os.path.join(tempdir, output_filename)
with open(savepath, "wb") as out:
dst.save(out)
document_paths.append(savepath)
logger.debug(f"Temp files are {str(document_paths)}")
return document_paths
def save_to_dir(
filepath: str,
newname: str = None,
target_dir: str = settings.CONSUMPTION_DIR,
):
"""
Copies filepath to target_dir.
Optionally rename the file.
"""
if os.path.isfile(filepath) and os.path.isdir(target_dir):
dst = shutil.copy(filepath, target_dir)
logging.debug(f"saved {str(filepath)} to {str(dst)}")
if newname:
dst_new = os.path.join(target_dir, newname)
logger.debug(f"moving {str(dst)} to {str(dst_new)}")
os.rename(dst, dst_new)
else:
logger.warning(f"{str(filepath)} or {str(target_dir)} don't exist.")
def consume_file(
path,
override_filename=None,
@@ -72,6 +191,48 @@ def consume_file(
task_id=None,
):
# check for separators in current document
if settings.CONSUMER_ENABLE_BARCODES:
separators = []
document_list = []
separators = scan_file_for_separating_barcodes(path)
if separators:
logger.debug(f"Pages with separators found in: {str(path)}")
document_list = separate_pages(path, separators)
if document_list:
for n, document in enumerate(document_list):
# save to consumption dir
# rename it to the original filename with number prefix
if override_filename:
newname = f"{str(n)}_" + override_filename
else:
newname = None
save_to_dir(document, newname=newname)
# if we got here, the document was successfully split
# and can safely be deleted
logger.debug("Deleting file {}".format(path))
os.unlink(path)
# notify the sender, otherwise the progress bar
# in the UI stays stuck
payload = {
"filename": override_filename,
"task_id": task_id,
"current_progress": 100,
"max_progress": 100,
"status": "SUCCESS",
"message": "finished",
}
try:
async_to_sync(get_channel_layer().group_send)(
"status_updates",
{"type": "status_update", "data": payload},
)
except OSError as e:
logger.warning("OSError. It could be, the broker cannot be reached.")
logger.warning(str(e))
return "File successfully split"
# continue with consumption if no barcode was found
document = Consumer().try_consume_file(
path,
override_filename=override_filename,
Binary file not shown.

After

Width:  |  Height:  |  Size: 836 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 891 B

@@ -0,0 +1,243 @@
%PDF-1.6
%äüöß
2 0 obj
<</Length 3 0 R/Filter/FlateDecode>>
stream
xœ]QËjÄ0 ¼û+t.Ä+ÙVlƒ1äµÐÞ=”žú:mË_ÙÞ¦¡b�<£‘Ôßê¨ÑàÈÚx†àH‡žàú¦ïà³1ä\?Ô¸*"Ô¼ëuÖW8 D½¾?%¤L ZtÈØ£Ç�;ÉÄl7àpÌ6á„s!3.¹
å¸×æçõA-«:ýk€Å<⟿2¿þ¦ú—ËåΤZj}3ŠÅzØ(޹óL ÎÒ•–zB ˆÈ “-¡#.Bêë<2sHäk&Ü
NYR±¦¡·Ç±~'ñ§6:-%.Õ['‚;n+Ùœ·%œàDQ›ÁYÖV¦7ÎÉèNïÿ•ä—uÕn"®g#¼œÕáþì`þ‚RôÒæmx
endstream
endobj
3 0 obj
284
endobj
4 0 obj
<</Type/XObject/Subtype/Image/Width 828/Height 236/BitsPerComponent 8/Length 5 0 R
/Filter/FlateDecode/ColorSpace/DeviceRGB
>>
stream
xœíÛÁn`IŽdÑüÿŸîYÌ¢„`”ñòÝA8—¢é+½Mý÷ßÿÎÿüeþûËÏü÷—i~æ2j˜þÞ­ü­¾ô®¡ï–�vÒ¶ÆßØ’ÝÆ¶Õ«É$ò·z™mI¦ÁOw4›§¢—Á™¼ÕÝæ¹ÌŸè7¹Ü5ôݲÑNÚÖø[²Ûضz5™DþV/³-É4øéŽfóÔCô28“÷¯ºÛ<—ùS}Câ&—»†¾[6ÚIÛcKvÛV¯&“Èßêe¶%™?ÝÑlžzˆ^gòþUw›ç2j oHÜär×ÐwËF;i[ãolÉncÛêÕdù[½Ì¶$Óà§;šÍSÑËàLÞ¿ênó\æO ô ‰›\îúnÙh'mkü�-Ùml[½šL"«—Ù–dütG³yê!zœÉûWÝmžËü©�¾!q“Ë]Cß-í¤m�¿±%»�m«W“Iäoõ2Û’LƒŸîh6O=D/ƒ3yÿª»Ís™?5Ð7$nr¹kè»e£�´­ñ7¶d·±mõj2‰ü­^f[’iðÓÍæ©‡èep&ï_u·y.ó§ú†ÄM.w }·l´“¶5þÆ–ì6¶­^M&‘¿ÕËlK2 ~º£Ù<õ½ Îäý«î6ÏeþÔ@ß�¸É审ï–�vÒ¶ÆßØ’ÝÆ¶Õ«É$ò·z™mI¦ÁOw4›§¢—Á™¼ÕÝæ¹ÌŸè7¹Ü5ôݲÑNÚÖø[²Ûضz5™DþV/³-É4øéŽfóÔCô28“÷¯ºÛ<—ùS}Câ&—»†¾[6ÚIÛcKvÛV¯&“Èßêe¶%™?ÝÑlžzˆ^gòþUw›ç2j oHÜär×ÐwËF;i[ãolÉncÛêÕdù[½Ì¶$Óà§;šÍSÑËàLÞ¿ênó\æO ô ‰›\îúnÙh'mkü�-Ùml[½šL"«—Ù–dütG³yê!zœÉûWÝmžËü©�¾!q“Ë]Cß-í¤m�¿±%»�m«W“Iäoõ2Û’LƒŸîh6O=D/ƒ3yÿª»Ís™?5Ð7$nr¹kè»e£�´­ñ7¶d·±mõj2‰ü­^f[’iðÓÍæ©‡èep&ï_u·y.ó§ú†ÄM.w }·l´“¶5þÆ–ì6¶­^M&‘¿ÕËlK2 ~º£Ù<õ½ Îäý«î6ÏeþÔ@ß�¸É审ï–�vÒ¶ÆßØ’ÝÆ¶Õ«É$ò·z™mI¦ÁOw4›§¢—Á™¼ÕÝæ¹ÌŸè7¹Ü5ôݲÑNÚÖø[²Ûضz5™DþV/³-É4øéŽfóÔCô28“÷¯ºÛ<—ùS}Câ&—»†¾[6ÚIÛcKvÛV¯&“Èßêe¶%™?ÝÑlžzˆ^gòþUw›ç2j oHÜär×ÐwËF;i[ãolÉncÛêÕdù[½Ì¶$Óà§;šÍSÑËàLÞ¿ênó\æO ô ‰›\îúnÙh'mkü�-Ùml[½šL"«—Ù–dütG³yê!zœÉûWÝmžËü©�¾!q“Ë]Cß-í¤m�¿±%»�m«W“Iäoõ2Û’LƒŸîh6O=D/ƒ3yÿª»Ís™?5Ð7$nr¹kè»e£�´­ñ7¶d·±mõj2‰ü­^f[’iðÓÍæ©‡èep&ï_u·y.ó§ú†ÄM.w }·l´“¶5þÆ–ì6¶­^M&‘¿ÕËlK2 ~º£Ù<õ½ Îäý«î6ÏeþÔ@ß�¸É审ï–�vÒ¶ÆßØ’ÝÆ¶Õ«É$ò·z™mI¦ÁOw4›§¢—Á™¼ÕÝæ¹ÌŸè7¹Ü5ôݲÑNÚÖø[²Ûضz5™DþV/³-É4øéŽfóÔCô28“÷¯ºÛ<—ùS}Câ&—»†¾[6ÚIÛcKvÛV¯&“Èßêe¶%™?ÝÑlžzˆ^gòþUw›ç2j oHÜär×ÐwËF;i[ãolÉncÛêÕdù[½Ì¶$Óà§;šÍSÑËàLÞ¿ênó\æO ô ‰›\îúnÙh'mkü�-Ùml[½šL"«—Ù–dütG³yê!zœÉûWÝmžËü©�¾!q“Ë]Cß-í¤m�¿±%»�m«W“Iäoõ2Û’LƒŸîh6O=D/ƒ3yÿª»Ís™?5Ð7$nr¹kè»e£�´­ñ7¶d·±mõj2‰ü­^f[’iðÓÍæ©‡èep&ï_u·y.ó§ú†ÄM.w }·l´“¶5þÆ–ì6¶­^M&‘¿ÕËlK2 ~º£Ù<õ½ Îäý«î6ÏeþÔ@ß�¸É审ï–�vÒ¶ÆßØ’ÝÆ¶Õ«É$ò·z™mI¦ÁOw4›§¢—Á™¼ÕÝæ¹ÌŸè7¹Ü5ôݲÑNÚÖø[²Ûضz5™DþV/³-É4øéŽfóÔCô28“÷¯ºÛ<—ùS}Câ&—»†¾[6ÚIÛcKvÛV¯&“Èßêe¶%™?ÝÑlžzˆ^gòþUw›ç2j oHÜär×ÐwËF;i[ãolÉncÛêÕdù[½Ì¶$Óà§;šÍSÑËàLÞ¿ênó\æO ô ‰›\îúnÙh'mkü�-Ùml[½šL"«—Ù–dütG³yê!zœÉûWÝmžËü©�¾!q“Ë]Cß-í¤m�¿±%»�m«W“Iäoõ2Û’LƒŸîh6O=D/ƒ3yÿª»Ís™?5Ð7$nr¹kè»e£�´­ñ7¶d·±mõj2‰ü­^f[’iðÓÍæ©‡èep&ï_u·y.ó§ú†ÄM.w }·l´“¶5þÆ–ì6¶­^M&‘¿ÕËlK2 ~º£Ù<õ½ Îäý«î6ÏeþÔ@ß�¸É审ï–�vÒ¶ÆßØ’ÝÆ¶Õ«É$ò·z™mI¦ÁOw4›§¢—Á™¼ÕÝæ¹ÌŸè7¹Ü5ôݲÑNÚÖø[²Ûضz5™DþV/³-É4øéŽfóÔCô28“÷¯ºÛ<—ùS}Câ&—»†¾[6ÚIÛcKvÛV¯&“Èßêe¶%™?ÝÑlžzˆ^gòþUw›ç2j oHÜär×ÐwËF;i[ãolÉncÛêÕdù[½Ì¶$Óà§;šÍSÑËàLÞ¿ênó\æO ô ‰›\îúnÙh'mkü�-Ùml[½šL"«—Ù–dütG³yê!zœÉûWÝmžËü©�¾!q“Ë]Cß-í¤m�¿±%»�m«W“Iäoõ2Û’LƒŸîh6O=D/ƒ3yÿª»Ís™?5Ð7$nr¹kè»e£�´­ñ7¶d·±mõj2‰ü­^f[’iðÓÍæ©‡èep&ï_u·y.ó§ú†ÄM.w }·l´“¶5þÆ–ì6¶­^M&‘¿ÕËlK2 ~º£Ù<õ½ Îäý«î6ÏeþÔ@ß�¸É审ï–�vÒ¶ÆßØ’ÝÆ¶Õ«É$ò·z™mI¦ÁOw4›§¢—Á™¼ÕÝæ¹ÌŸè7¹Ü5ôݲÑNÚÖø[²Ûضz5™DþV/³-É4øéŽfóÔCô28“÷¯ºÛ<—ùS}Câ&—»†¾[6ÚIÛcKvÛV¯&“Èßêe¶%™?ÝÑlžzˆ^gòþUw›ç2j oHÜär×ÐwËF;i[ãolÉncÛêÕdù[½Ì¶$Óà§;šÍSÑËàLÞ¿ênó\æO ô ‰›\îúnÙh'mkü�-Ùml[½šL"«—Ù–dütG³yê!zœÉûWÝmžËü©�¾!q“Ë]Cß-í¤m�¿±%»�m«W“Iäoõ2Û’LƒŸîh6O=D/ƒ3yÿª»Ís™?5Ð7$nr¹kè»e£�´­ñ7¶d·±mõj2‰ü­^f[’iðÓÍæ©‡èep&ï_u·y.ó§ú†ÄM.w }·l´“¶5þÆ–ì6¶­^M&‘¿ÕËlK2 ~º£Ù<õ½ Îäý«î6ÏeþÔ@ß�¸É审ï–�vÒ¶ÆßØ’ÝÆ¶Õ«É$ò·z™mI¦ÁOw4›§¢—Á™¼ÕÝæ¹ÌŸè7¹Ü5ôݲÑNÚÖø[²Ûضz5™DþV/³-É4øéŽfóÔCô28“÷¯ºÛ<—ùS}Câ&—»†¾[6ÚIÛcKvÛV¯&“Èßêe¶%™?ÝÑlžzˆ^gòþUw›ç2j oHÜär×ÐwËF;i[ãolÉncÛêÕdù[½Ì¶$Óà§;šÍSÑËàLÞ¿ênó\æO ô ‰›\îúnÙh'mkü�-Ùml[½šL"«—Ù–dütG³yê!zœÉûWÝmžËü©�¾!q“Ë]Cß-í¤m�¿±%»�m«W“Iäoõ2Û’LƒŸîh6O=D/ƒ3yÿª»Ís™?5Ð7$nr¹kè»e£�´­ñ7¶d·±mõj2‰ü­^f[’iðÓÍæ©‡èep&ï_u·y.ó§ú†ÄM.w }·l´“¶5þÆ–ì6¶­^M&‘¿ÕËlK2 ~º£Ù<õ½ Îäý«î6ÏeþÔ@ß�¸É审ï–�vÒ¶ÆßØ’ÝÆ¶Õ«É$ò·z™mI¦ÁOw4›§¢—Á™¼ÕÝæ¹ÌŸè7¹Ü5ôݲÑNÚÖø[²Ûضz5™DþV/³-É4øéŽfóÔCô28“÷¯ºÛ<—ùS}Câ&—»†¾[6ÚIÛcKvÛV¯&“Èßêe¶%™?ÝÑlžzˆ^gòþUw›ç2j oHÜär×ÐwËF;i[ãolÉncÛêÕdù[½Ì¶$Óà§;šÍSÑËàLÞ¿ênó\æO ô ‰›\îúnÙh'mkü�-Ùml[½šL"«—Ù–dütG³yê!zœÉûWÝmžËü©�¾!q“Ë]Cß-í¤m�¿±%»�m«W“Iäoõ2Û’LƒŸîh6O=D/ƒ3yÿª»Ís™?5Ð7$nr¹kè»e£�´­ñ7¶d·±mõj2‰ü­^f[’iðÓÍæ©‡èep&ï_u·y.ó§ú†ÄM.w }·l´“¶5þÆ–ì6¶­^M&‘¿ÕËlK2 ~º£Ù<õ½ Îäý«î6ÏeþÔ@ß�¸É审ï–�vÒ¶ÆßØ’ÝÆ¶Õ«É$ò·z™mI¦ÁOw4›§¢—Á™¼ÕÝæ¹ÌŸè7¹Ü5ôݲÑNÚÖø[²Ûضz5™DþV/³-É4øéŽfóÔCô28“÷¯ºÛ<—ùS}Câ&—»†¾[6ÚIÛcKvÛV¯&“Èßêe¶%™?ÝÑlžzˆ^gòþUw›ç2j oHÜär×ÐwËF;i[ãolÉncÛêÕdù[½Ì¶$Óà§;šÍSÑËàLÞ¿ênó\æO ô ‰›\îúnÙh'mkü�-Ùml[½šL"«—Ù–dütG³yê!zœÉûWÝmžËü©�¾!q“Ë]Cß-í¤m�¿±%»�m«W“Iäoõ2Û’LƒŸîh6O=D/ƒ3yÿª»Ís™?5Ð7$nr¹kè»e£�´­ñ7¶d·±mõj2‰ü­^f[’iðÓÍæ©‡èep&ï_u·y.ó§ú†ÄM.w }·l´“¶5þÆ–ì6¶­^M&‘¿ÕËlK2 ~º£Ù<õ½ Îäý«î6ÏeþÔ@ß�¸É审ï–�vÒ¶ÆßØ’ÝÆ¶Õ«É$ò·z™mI¦ÁOw4›§¢—Á™¼ÕÝæ¹ÌŸè7¹Ü5ôݲÑNÚÖø[²Ûضz5™DþV/³-É4øéŽfóÔCô28“÷¯ºÛ<—ùS}Câ&—»†¾[6ÚIÛcKvÛV¯&“Èßêe¶%™?ÝÑlžzˆ^gòþUw›ç2j oHÜär×ÐwËF;i[ãolÉncÛêÕdù[½Ì¶$Óà§;šÍSÑËàLÞ¿ênó\æO ô ‰›\îúnÙh'mkü�-Ùml[½šL"«—Ù–dütG³yê!zœÉûWÝmžËü©�¾!q“Ë]Cß-í¤m�¿±%»�m«W“Iäoõ2Û’LƒŸîh6O=D/ƒ3yÿª»Ís™?5Ð7$nr¹kè»e£�´­ñ7¶d·±mõj2‰ü­^f[’iðÓÍæ©‡èep&ï_u·y.ó§ú†ÄM.w }·l´“¶5þÆ–ì6¶­^M&‘¿ÕËlK2 ~º£Ù<õ½ Îäý«î6ÏeþÔ@ß�¸É审ï–�vÒ¶ÆßØ’ÝÆ¶Õ«É$ò·z™mI¦ÁOw4›§¢—Á™¼ÕÝæ¹ÌŸè7¹Ü5ôݲÑNÚÖø[²Ûضz5™DþV/³-É4øéŽfóÔCô28“÷¯ºÛ<—ùS}Câ&—»†¾[6ÚIÛcKvÛV¯&“Èßêe¶%™?ÝÑlžzˆ^gòþUw›ç2j oHÜär×ÐwËF;i[ãolÉncÛêÕdù[½Ì¶$Óà§;šÍSÑËàLÞ¿ênó\æO ô ‰›\îúnÙh'mkü�-Ùml[½šL"«—Ù–dütG³yê!zœÉûWÝmžËü©�¾!q“Ë]Cß-í¤m�¿±%»�m«W“Iäoõ2Û’LƒŸîh6O=D/ƒ3yÿª»Ís™?5Ð7$nr¹kè»e£�´­ñ7¶d·±mõj2‰ü­^f[’iðÓÍæ©‡èep&ï_u·y.ó§ú†ÄM.w }·l´“¶5þÆ–ì6¶­^M&‘¿ÕËlK2 ~Line truncated
g2Éïýy+Ïœþüï~²OûyëÍ›7oÞ¼y³;?ÿÍýÿ•øÉü_§¿ý篔¿ål©þöÛ“Ý?ÿõò'§Î7oÞ¼yóæÍͼo¶dÞ7Ûß2ß¼yóæÍ›7»³õ-´û“œóæ›ío¿ýßùÉŸç}ï½yóæÍ›7Óyßlï›í}³½yóæÍ›7ÎùÛ_ÞßýEþö¯üï~ûŸßi»ßl¾ûÕdøfëÿK{óæÍ›7oþµyßlï›íò·ÿ¼û¾ÙÞ¼yóæÍ›Ÿç¿?æÏÍsòýÛoüÝo1|³ý|ÉÆÙüäÍoOºÿ™ð»ÿÞ¼yóæÍ›s~þëI|üù{§Úæ·ÿí{#Éù9?ùù�³ùɛߞtÿ3áwÿ ¼yóæÍ›7ÿòüî#ÿÉä_ó¿õÍooœ½êo_8^ ù™©™¸Òï.Öï¾yóæÍ›7ÿæüî[(ÿÉä_“ßõ;çï¾FòÉU?‰ýéüùg¦fâJ¿»X¿ûæÍ›7oÞüksÿ=@ä_#ùÜ| ý®û½3—üí¿º7oÞ¼yóæÍŸó¾Ù~ç¤ûûf{óæÍ›7oÞüßùÛ_^âo÷ÖWÓ¿üÍö·÷oÿ7š^ìwÿ¥½yóæÍ›7ÿò¼o¶iÎï¶Þ7[²û¾ÙÞ¼yóæÍ›d.¿…þ�o¶ÿ¿¾¯’Ÿœ6úyÞwÚ›7oÞ¼y3�÷Í–ük¯r~‰å?ù¾ÙÞ¼yóæÍóü÷—¹ùÉ?·gþÝ’Ï´uòÛ§ÎÝË÷i?o½yóæÍ›7oˆÙý˜þäŸ[�3IøÙÐtÉûÔ¹{ù>íç­7oÞ¼yóæÍ›7oÞ¼yóæÍ›7oÞ¼yóæÍ›7oÞ¼yóæÍ›7oÞ¼yóæÍ›7oÞ¼yóæÍ›7oÞ¼yóæÍ›7oÞ¼yóæÍ›7oÞ¼yóæÍ›7oÞ¼yóæÍ›7oÞ¼yóæÍ›7oÞ¼yóæÍ›7oÞü;ógÞù
endstream
endobj
5 0 obj
11207
endobj
7 0 obj
<</Length 8 0 R/Filter/FlateDecode/Length1 14992>>
stream
xœÝzy\U¶ð=U½A7ôBoС»š
„„¥ K²Q!lI0tD:ÑÐ 4ºCw—q‚£qAó—Qã22¾èsflLôEÇQttæ9Ž£>õ͸dd¾qyó&ŽèdÔ§¡øNݪf‰Ñù½÷ûþú
ªêÞsÏ9÷l÷Üs âƒ{ÂÄ@†K„ÎþPô†Ý B~CX:÷ƹ5M¶UØž „ù÷îè®þ;ÿõ‚Ó„¨Ž¢=¾«ïÒîŠýŒC!Ù¿é ‡º*—ŠYRŽ<–õ ,^ªÅ~ööôÇ/ù"uâìÆþwú"�¡{?CÚö…þÐ%Ñ<U+ƒýFìs¡þð·þ¢ ûH¯�E#±x¹nYOHãÑÁp´ñÎŽç YŽ]Vâ ø#]È4RŸaUj�V—’ª7¤¥MfK†Õfw83³\ ²ÝäÿÿK}�ØHƒz 1’(}λØQ’IŽ2}JêÍ>ÅÆé/ÿ_J¡“_w�Èqr�¼I.Tꈟô’=™{=C^E¨tùÉvòþ¶£äŽËxArHÒ䜗ŸÜNŽ‘_Í›ÅOúÉå(Ë£äMXJ^ÀP‰�OAG®$Ï#×OvÞ¹X1éøè¦Íî9зÉ]Ì d#óvŽH#Œ�1‘çÈݰ9ÇQσ3¯þÓkÉøl&=d/¶é¥^sæ-’2ý7Ôê
²‘|�¬#}s(ž„{ÙTô_ ¹mú …ù’ƒÚö"æ1†™º;7‘]x‡ug²ë¾ÁBÿã‹m%i°˜Í%)çeʉQü’)�>Í.$©¤uz2 ›Þ4ý76$¨ÚU ÔkT/~Ûš›TýHM¦ß/»Ô›Õ ·ÄDQ¿c{ ­µ¥yëÓæó7mÜÐP_W[³¾z�PµvÍêU++W,_V±´ÄW\T˜¿(/w!Ÿãõ8­f“1=MŸš¢ÓjÔ*–RÈ% X›`s9s]ˆ¯åC E…\­³§¦¨°–¯ &¸—À—*�oh  >”à‚\"_¡9à`B@Ìî³0S˜Á·š¬–¦à¹ÄK5<w¶oiÃöÁ>À%>¢íóh[•G;iØñz‘‚J%IËÕ&êöö ×QFÓ§®çׇS‹
ÉXª›zl%òùèä¯Ú`òkWŽ1D—&M‹šÖ†ºþ-mµ5.¯7PT¸!‘Î×Ð!²ž²LhÖ'´”%×+‰NnàÆ
LJo<a"ÁCߺ -Á†�v˜­¾6a.H,æk‹/{ωš‡…|Mm¢@âºiëÌ<›f§„„:×ÄsÃ'¨ÿÑ©ù��ÑäšþN¤f‚YŸ€­m^érÕ¡­‡‡ëx®n88:1=ÔÁs&~xÌ`ŽÖ¢¹‰¿ Yœ˜~âW¢îÆ@Âì�•Eõº­›[v´%˜Ü:®'„ü­â½+\^ó Žÿ›† š�ƒöz%3ÜpB ØI mi“ûép=B_A Á¥‘ñ䈭UJŽÌ�yôí¦æ¶á„*wC_‹¿!”êÀèºHr oJ¤æòòÃ3Wé P\¥ÚÐÕË%Ôyh$¤šK€q#‘ ›h'ý3ùõ‘ 'È3[¸JÙH|jùÚ ò»·Ç‰ 84tC�-m ¡BHñXíX‰)BAtXo ufÂÇGV¾zÆ»’Xµ½Ím”D!KX×'H°S¡Jøjéºâj‡ƒ5²/~KÛã¤lzb¬œs+#å$P#!Û×c”åÕ·uu'<AW®»n®ÍåMôp€o ¤°C -žpÑàÐXiiÛÔÌoÚ²½m…"ˆ< ±Så֞ņosÉl0º\ׯ¸Ø"šÀÕaƒ¯^�Ï„6W‡· N¡RàV¯æÚÀE’Ø(Fb1W®Qð¤þ<¦j)œÖ7$¹i¤.òYßàò¼òUTÈà0§LŒ:ɨ É!LS8 Ãø\ß@A’-�RÐsm|˜ð=\Bð·IºIæ¡VVŒAm®øªe^oޱÐLÄ‹ÃÉŽdÌD]�k®qõ´?Óm8kxCr˜Öñ›š‡%æ¼Â� äD
aa…ÙEs�´ y̽œ —4]ÐÃc‚ -æž•~C×0ßܶšbc>¹Âu™4—…l‚M-ÕE…˜ÚªÇx¸n˘×5oo{Ü„uáu-m�0À¬VÆâXÛãnÊHP (u8©#qÚŠÅw=.2DGU@û�'€P˜. Òy‚‘a&y¢<:‘@QÉ#B[…0� ¢0z�ÉdBªZÐ )‚�Ic\c �AÈXǦ9f€4p�!ÕV
>Cc)‚KÆB A–ðºÖÙ©[··3àîì¢Oœ¨Zº0\œ=èlÜVj¹.)P¾è¤ÅFìèü…ðkÑMüZDcH¤òáꄞ¯–àU¼J†k$¸Cì€äCè{¤ØÑæÅ%Ée½à6}$y*€IeØô~Z,Ï Ï` j…ÕÂI £gt¬Ín :Hauº3›Â)¬…!L{€Xªì`´Ã„ž¶Ã!;ì·C»ÈQøÅ“vxÙ#t,j‡&;xè€ OØá^:¡d‚J(±Ã»ttˆÂK(dÕ4�G&;DšèØ$…'’sÈ¥™¤ŒÆé4CtEó%ç¸pæÚ�¼•kçYð¯�Hc¤ªÀLÊœôi.súÚw^Xf¶€£Ò\¶´Ä[±ÜÌç�7{Íü¢b(³Ã«Þ(›ºÐµ^uw�ËýëK–¾QáRÝn}V‰Ï¿ªÕu±«B*¨€X±r{ÏÙä áRbµf¦¥§§d¦¸=ÙYþ@6±bÇ‘é †Q«Í[jÓˆ&<0*±sØQ=à÷€à�pðÐaJŽâÐ+”2á�‘9ð¹ÆœgEwÔºªLj”Q3X u—––€U»ÊJí6T\²F9Ÿ£Uš‹*¼P§mø×ªË¾3(^|Å;¯Ú/ví»JÙÏzНþ§k§nË,*ÊdvŽfOeH-5ã,Â…S€õ©EÝHôxZºGè&ƒÆlvØÙ”æaÁIJ6ÁfñlFƒÙhöŒ6«T¨<í€Ã`¢:ÀïÁãH8`„v9˜@0I!ˆ:s~ŒH&h§V˜q?Érš~‹Î§0—)Êkøœ¼ŠòehvVéË…¢BA(,R(fŽ€Õ»r_øj¥¤'ËeJk°eúóû<É'¡Ü«µf¥aH,^’æe·?àr˜X½? eíCK º‚KÀ¿¸%ððh_MK )/A÷ê# AßÈBg$\TQæ°—•V”û ˜¡â:lü¢<>Gc³Ún–ymì'u?*)Zºé’g�”þèð®»|K*·´ž·ù–íU<èn<œmùðªš.+ÏöÖtÖ}ç�ç¥~Ÿ¿¦rsViñúmÒ1ƒlœ>Å~ˆñœ�=$4YUz’™iR™Üž “?��þBgíÔÉ”‰ŒcK€±c˜ÖÏ�\BƒtˆBäF�Âï(Ñ9™’æôž Lêš\ Ï™Ë-¨iÞ@§H1 V4ÂrsÏ1¯í¾]ÜÿÖë}Í &.~!z†®Þ½=0(ž©ÛüÀá=pÚYôåã™EðÒS?[Ä|h¦:zÅF6�>s/9 lqU‹Ã™êHÍá+F¥Õ•Æùiöl—Öµ% ÒšXâ°F�‡!•%<Lð0NûA„9m´óìRÄØ$UŠ_IRQIéÙ(y8•sÈKÐÊð9‹ìÙ@#Ê󤥈ßq1è˜%7þw/îîÖ…}L×û÷l\t†íÎ,Z¾°ðËÿúXüÒÞ°Xtú|NvóøÏ¼Sf³”££Ò&ôi
¹Oˆªñ¼¦ñð”©fÕ¨•íu=<§‡ãz8ª‡[õpµâzèÒÃB=Xõ Ò㚤‡õ¸,õÔƒ_‚Æõ�ÐÃíšô@ô0I»ˆ7mÞšT–älj¾pÎâ”l‚>Ÿ]‚iå�@]Ýœ¼Ò€ñ¹›}†¸p÷ëªÌºÜ\g0dªX<–æ¤æl 8mf3†§Ñì13- KµkU¯6bóˆih´/a`ãB”ˆÌD_™¥Ò''R©8É‘t®B%?.ÂÅh._ UP!y÷�Še M›µ¬tÙrxõΛöˆbÆàØ'Fî8X¿±«9gÅ}@®º¦ýPMg)ûÌw¿7u ³hç 8w^¾ŽUݺÀ·ç%^t«Ô;§ä¯L±Žý‚% àO´Ù�á48Ý™ŒÒT}¶3ÃlHÇEhKÇe¸%À’q7t»a›7|ê†÷Üðº~é†ÇÜp½p4迪ÝPꆅn°ºA㆞ÏÜðnxÖ �¸á¨nvÃÕyµ¸¡Žbç¸Áâ•N»áŠÿœŽ»á�$þ :Ü°Õ 5n(wCžìIü9ŽÏç¾,ÍŠÏ(öŒ4·&¹ çÏ•g!•ˆ&Ý0á†W’ÝšäÞEu•¹Ÿ¦̸n8ì†!7DÝÐä£tÉlŸW;ì¦á9xÖ:÷Ú}Ö5ø­ørf/�É4£KÕGrýãÚÏQ2ûZX.%9›Ö%¿ �ùÕæFŸ×³¼¼mÃrñÎ ¿UüìØÙ!Þ´.ë,/«÷ÞÁFq}¸¦"Ì-ø¶�Ùqÿá�rmâÃÚä�˜ç�qá
’‘áÔ Z§6Û½+’Æ ìØ�þ@ªÝfALÖ´5ÀšŽRÇ=Gý‚~¬|�ÚV6lKÒ°èF»23†Å0¨Åeƒ�Ì�ϵ]ûÿªB™_ŸäÍ/PjÎûéJ¹>ÙÒº ”‹vï,¬œ)OÚšÛ•ú„îÛ,Y5ý¥êS¬�uÄBòÈIá–”²@�žn³yä¨òåš‚�\‹GmP‚£ô¬ZíÌpÚÛNU0àd3lí� ËH>·¡|ˆæC0„|˜È‡{)»~
áòáÝ|§�Ú%ù°òe:lʇIÊ‚PÊWòa„ò’)gŒöµ2W¶ž’¹h¾rÎd.Ùx޹Æ+ÏÚ³Sm`¨-SË–ãp¿Ñhݘ»TÞ\ö©;òòÛ÷ ?ùòäðÝ?~èŒst” “éS7]õè/Å¿O±…ýâò¨¨í¯šú�æ¦÷±(þAVÅíG÷>° ã'×=ó+úºßÜ�qg%[„"³V ƒÍ®1³É̤«Í,c5™Òü“QkHÅL–jk§u>*ät<³S–ѵb¦»¥¥²”ê‚5OŽfŽó¥m“¹£`eéõ¥?«÷íKÊê—V³Ï‹.ûTuÒã{J/�×ÄfÜ?6£lvV›Õz»ÚîpêŒX¡ëLv+kÅŒj'NX+8¡Ä œLN˜tÂ+NqÂ�þ°Ú�0§ÚÞ9³‡•ÍÆ-îdé •/R¡f–
5ÎLKvóÒÑíâò?¿yíÈò‚æ¸xúŸ|s_åÂÅðÉ_¦<â—øÄž×õJuŠ$ë#(k*Jû„p%JKÔ¥M÷t&ÆêÇ Di'œà§òÊÂNPyÇ�� R¦‚G�¤âË$«î¥ ?ÍhJæÓË*Ëdø¼ð윷ók¹n&(çØLÞd}­-WêU Aö±áõßÿþ�ÿxëøw¯¹jϾ+¯‚·E³øÉ_Ï|þ·ß?ûÄÄŸ~þýôMüh‡:´ƒ 3ÙAa{&€1Kg3ڲݙX—3=™¸Ñgf,»?`1Ô[ûLš›ŸpkÀ½c­Ÿfª7pn0ѽDNV³¥éœâDÖfv…% s91MJÛ¾ }�“'9›3ÛUô–ç�jÍþ]Ën-)¹ÛÛ/þöièoï‰ÀÍÀ›–á#~‹~…§ø¨?ûTìÞ
w?xôØ)>;Pמ›–�ë„-ù–Ü\‡Ãâa5ééDC
ó3,–XÀ—<KÜ&7“ºÝ<Ÿ ðZV DµCZƨ­–Í
�ÂD!ŒB°°›< —ù.¼0YŠR½ÌXúÈé#é8 Í –Jåø±ˆ-sPU±èñAÅòµrõ£‘ŽÍ¸1¶¥<Â&¾øÓSé«‹Z{ÏmÃ7:´»oÏPE¼�¯î<\?=rãc£Ðÿèo
ëI;wøÁ«¯×궦h†¾wíåÙΣÀd™ÅÇn¸ßjûê÷j´Å(ÚÂIrÈ%B½KO8«UC4üB+ǹíîx€ØMvô½Ï•)æXÀ˜æIó¥±)lZZJ&› °Za!�…0±FBp!`wæ‹€”`f³¦Ü’¶¡¹µ9nÏRàZÌ&ÂÓ}Z²�%C:�A޼žMLõÿùíûoˆý⟯[Çs}ËGFSî¸gìExäËK¾{åU—²Gýñ1qD|X¼+ç?�� æÖ×›xôÈÍPI×mâóly�˜ˆº…ÿv˜­æܧô ô|ŽÃãI·Úî X­,«º'�Î>¸C“¾ƒ‡Mô¸ñÞæáe<åáV®æ¡�‡U<ðàáAÏÃE_ñð¯óðcîâá:ìâAÅÃi^¤p¤ìâAæ»�‡¯’¬~1-<ÔPd·Ræ%Ïv9–ÉÊ)¥Š’=ÇÃq:Óå”oK’ø4å0 ž˜¢<øyðQ9ñd•,ËÚÏõ©gÞÊüÚà¼d$•]¾²9¾ç¹µlöt‰![!Åó"%;©ÏÀµ÷ÞœpçÖÔT¬Ù
G“m¡iéõ�/9oEÔÊeð²Êó7Íë^@÷™–éSª÷1v]d­à5³R8.ÈÖ¤·H*¤°©©U&Ÿ­ØÏ²!¹�øf"r&{ª9Ü7‰·Ô‘!}›ÂS¯Ù„âeUPÆjX�ø„x+ì„¶W'5ÕîºÇƒâô©ÏO þzUî:Í;Vè¶Cg™øöO |â¿‹¿ß³¼øßÄç«$ ¦O©ïÆÈIn‚§Æ É̲¨Iš1­=ÀÙ[0�’¡Ê‚ª®,hÉ‚š,(Ï‚…Y`Í‚ãYp4 nÍ‚«³ ž„›²±OgÁD0¯dA" F² šþ,øZ±�ôÚœóqYÙÜmƒÁf«ŠçV˜åŽÉ‹ HuíqxaTüüŒøwñóQÆ9
�ã/©þšUQ‘uæÝO&?9É–ÓöÛâ-�“ô¼óÈiôE‘ô�Æ¡[ä&æEf_±[g]²DÝ€%VÖuVÕ¤&|ðŠÆ}0IŸ%>à|rÊ ×7~£É˜I�ÅšŠÙ=_ÚìÜxÓ87¬ãsm»ñž5�ß=ðÝÎ5“oÜ÷Ô:¾û¶kn_Ó¹ÿÀþÎ5�è{«zõ5únÃÎuEÅ+¶í¿pä±ñÏG7ö×m[Sè[µãªà³oäyi~ÄSÇè76;Y&¸‰Ac6˜N+Û°f¤§´ÒU¸¡WÉ{þîÝ;/<×Þl¡Ù�ásÖä•c�#�ûûÞëÍ>üÝ«Ì!¨�F1!Ž‹Ïˆ fDü¹øÈ�õ°\âûâǪ́x?¦¶‡Äû0Š�¬"DóGSW
–tܼ€¤XV§K³°Y™i'¦¿–¥¤5¤é5NsŠ^’‘ÑaŸÑ³ «ÓèpAØ4Œ  ñd U)„*HOÊ
ä&îSs‹^ewöò0['¦€@r—ÝÉ‹¶ãâòÑQ¸ ŽÃ;04::5q\uåW/»*äšöŒŠ=S‘õUí/•ë+!êêb$h>5§�*Kš UZ“ŠÍ°¦¥›Óƒ‹Ù &�ZeЪŒ`l¤‚å´Þ³ÂëVxÎ
Ç­pÔ
·Záj+Ä­Ðe…+ÔX¡Ü
­`µ‚Ê
ÿSüÊo!˜‹­¢8ãV`V±Âa+ Y!j¿+”X�³‚É
é,„&+œ�{¥ÃÜYàx^&³‡¼2éƒ ®¨â™/-—•Òg úù«Lþ ÆzY`½ð’X¼ð¼ýÐÔ ÇLM^ 7|¯UHúâ+�ä)¸J¼BÕ3µGöÆ¢ºcÑFÜd@Xk3™\i.�=Íj¶˜Ó4*g ¸\)ª”LLvÚö@
k�JƒKHe1qð
QNÒ}0)ûì¡ë¬ÏDTº®f¯”ž±$ERfbðà‹SoÜ;ʬ?3yó•pñMâÓâµ�úýŸýxìØíL£¨JFáCO^󋼩ÿtU0�pÅ‘+§~q€ê5}F½õJ!Ò(øÔV’fMsf:ðHêÀéƒ5á¾bÒ& ÉÄõŸ \&LdÂH&D3ég9í&O�s²ñòÒùÐr&È婪£â«â‡Ç/¹ÿ³?O}1èÿEü‘˜3ŠçñšÉùêräà‘ëQñ8&ŠT²ðtD¨®@Y]è�:†k&–ŒªÙ†Œ` Ý10E`‰Ád`RÔƒÊå²¢; =ನôÁ€Ši¨Ø‰lɆh6ø³AÚ,•Âü¬Ã/î-³9`iIHÆ—’0šÜ»¼ [^³—“öMïªÑQö¯Õ\ôõ·á"� xÄÛ@Œ¿²:ã«WeÙŧßLŸ:="vÝ7uÒô…øOx
Y®n¡5ZPøo•FÏ›c‹'Û^©Ï°TÓ¤³éî�
5VµŠÖCÖo(ÒvÌ)©f+4,šâ´bú¦
­|~y&×Yç(Ò^§c3Sm¢gÄy‘’MÖo2¥žò�©‡æˆ"×’F,ÛÚϵ¼Ï™þÁ˜äDºž|è2Ÿ‰Ž2ü•>Z੊þiÃ.oŸ¹gÍÝßO¸óÖ×” ~ñ‚Ù6ûáµ�m^µe[ùuËV쨛ßUþn7}Š)R]‰;g½°(5=]›Á²§Ê 7ø1 è�˜ëÍ[ð|q/=EW9ÁçTR€rÒ/K~Æ·T–Ò/j<äšù
0[™�—Ïx,„ÍÁö˯Wýîw«JV6óW[w1·-zã�–©ýëªMëœ*ÆWæ‘þôÛÛ�«ÿN<òÿ÷ý[Í+¿�ýï-±wVé¿Þt‹ò…tZ¯XKΟA‚³þåË£©Äø'’«Âj“©$l6i‘ȱ¿o/³I¶3ñö©·‘UÌCû›ñíÇwÞÕØÞ†ã-ê_‘UŒ\�ïm6Y…o«D‡ïU8×*Äë@¾V*Á³°že–àÏݬ•½Œ=¥6© iÔÜ©™ÔƵÿ¥‹ê>Hùm*—z«žè7êOΤmM;‘þƒô¨6RŒç 9£›ˆ�`}Íü‚ý%¤Q7 Ìè¼mF@ÌmJ›!ZÒ­´YÌCýJ[…8×)m¬yÉJ[ƒûûýJ[K.#Ç•¶ŽX¡Xi§�t¨VÚ©0~¥­' ˜§fþc¶˜yKi§‘
V§´ÓI»F’^%ý§ß({¾Ò©X¥Í�t¯´Y² ¹­Bœ]J[M²T×*m q«~¨´µä´êi¥­#ùêcJ;…,P¿­´S™wÔŸ+m=Y¡{MiÈ)z¥�F.JIΕNÊS^­éÝÕï½,ÜÅu…â!®3½t°wWOœËï\Ì•–,-áê#‘]}an}d0 Å{#Å©ëÏF+å¶"‹†P¼�Û0ÐYÜØÛ–q¹æð`o÷Öð®=}¡Áu±Îð@Wx�+âÎÆ8»¿-<“:¥ÅK‹KfÏÆí�q!.>ê
÷‡/æ"Ýóåàûzcñð {¸ÖâæbÎЇâ\h ‹k™!lêîîí S`gx0BäH¼%½hÏ`o¬«·Sš-V<£Àk4ÇÃ{ÃÜy¡x<‹ T‡b8JÖÒ;‰rûzz;{¸}¡×ŽõîÀÁŽK¹ù4ކP—��È^d¹7\ˆrw†c=½»¸˜¤²BÍÅ{BqIéþp|°·3Ô×w)º¬?ŠTè£}½ñœ¸?ã6‡÷q[#ý¡�‡ŠeQÐ6ÝhS®·?:ÙKe,Šu†Ã8Y¨+ÔÑÛ×Gn=¡ÁP'Z ÍÖÛ£ACpÑÐ@QížÁH4Œ’ž_ß8‹ˆÊÖŒEúöâÌö@8Ü%͈bï ÷!N܉\,éÓDA»â=Es$ïŽ Ä‘4Â…ººPq´V¤sO¿ä'4s<)\¨s0‚cѾP¹ôÇŠ{âñèJŸoß¾}Å!Å5�è™bäìû¶±ø¥Ñ°â�A‰K_#º@rÝê_I‰æ �\SíS‡Âq
B!—ŒÌ¥ÅK•)ÐŒ½Ñx¬8ÖÛWÜåkªk$5¤—ìÂ;Ž÷e$Lº‡wû!lu’‰’KÉ ÅêA(Gòºߥ¤„,Å›#õˆÁñ>¤çÈzl"•ô Q¾2€i4•Ž|;·RlmU¤h Ô…ØÚ€ô�È¡é:pt._Ž4SH/¦Y‰rÙƒr„²ŽÄ�*Œ8]ƒÃ#8÷yü£ñm´›)E¹–â]rNÊÄ·9qÔÒq:"IÚO¥¿a¤û6{pˆ¦Þ‹áH˜öº(W‰w+b4S,?¥”,§³ P¬–sÌØ„3v#}'õd³“ò–"BæÁv�bÓ‹ÐÞƒT‚.J—Ô-†3ÝçŽ�f*Ý^:çy.õct¬û1E/Ùf-TŠB%[ìCI¤y{h;DíÙE©¥P(;0ê¸o�‡ShCŠ_è{)%šBÅÞÝô£óà•Oöòü¹9j§µºìé~�SÜN„÷áÏ¥Ê*ëG«Èsu(ëh]•=ŠÆý”/G6ã{�ŠõÛ€7‡úxÖ*rÜt+qÊQÚ(¶#T‹¤‹¨o$MÂTR©¢+¿)úèܲl=4:BÔ·aÅ×qªAÒ^]Ц’ÔQ
)"µ4.¤õVlz>æ‰Æsr”-876%ŸôQycsxPi»ft”­-aõ)3É÷Ñ|tñŒºi¼Éí¢ÜоÁæÝÔ6qeÖ•¨ d�˱AÚ=Ôòz’£9þ5Ë…¨}#
]”f¥¸"K?]=4£d%–>”Nú)¦q8wÕt*k¦X‘Ù÷¿¦“äŠR Î]ƒ3²ô£Œ�Êê˜Yu{æ¬ß¤'š15Ò|Uâ§N±wiÕœ�3—Òœ9_ 9{±§òĨ-‹©»p¼ gh$J-N¦ Hç¸ÆRüë: Lz`É ’ÍÐNZaY¾«Æ÷zìKïbXC†o Â×b5ÂWaîôà³
ï&¼á­Â[Æ(A ¾}J¿û…Hñ2>�Þ´
¡Ò{#öð]¯¼ë^‹ïZ¥¿ûø&AÐb^EŸOƒJ8SðòpS°ÿ+ðCŸþ”ùdr±çáɧ'™¦�Û?~øc¶äc0~ :ò‘é#ÿGÁ�¢�|¤I5žù ˜ÿ4±Âóîš“­XóN+9‰š�,9é?9t2qR}ØÖwX»Ç4Î�—ŒGLJÆ_ŸŸ× =uø)æçOú<Æ'=O2žcMÇöcƒ‚ñAσŒÿ®à]Ìá»Áx·çnßÝì�GŠ=GêÝžÛo[䙸mò6æÄôø±ÛÒÌuOB4’5hÃÍÇØiÏÃëlpªeħoÞMxGð>„7žy݃·…lû÷A³ëæ‚›/¿ù†›ÕÑk†®9| ;tàðæá½OïebþÅžÈ@�g ~‰'³ÌÙª-c[58 Î.lèÈͯ žvDÚ±½Ä³½~±'£ÌÒªF…Uˆhd=lÛÄFØCìÓ¬V·ÕïölÁ{Â?égŠ¡ÎØäiò5±'¦'„ð&/rÛÝ8´‘ÝP·ØÓP¿Âc¬÷Ôûê_®·þãzM{=Ü‹¿u×=]Ç
u‹}uB�Û[· ÁÕj/³µšÁØj*3¶2€Ž.#­>ã´‘1Û�û�¬‘TfÈj8‡ÇZš
6�ÐNoÝ”Ðùw$àºDn³ô¶lOh®K�Öí;ÚÆþ)pààAR�½)QÚÜ–f6%º°!H�!l˜²Çì¤:‹Å èØÞƒOR°§�;c2”ÌŒ“‚Ä0EÅ(HrðY �!@¢¤Þ#ÒC,�‰$ê˜ÂŽËÚpîü¿TQlV
endstream
endobj
8 0 obj
8993
endobj
9 0 obj
<</Type/FontDescriptor/FontName/CAAAAA+LiberationSerif
/Flags 4
/FontBBox[-543 -303 1277 981]/ItalicAngle 0
/Ascent 891
/Descent -216
/CapHeight 981
/StemV 80
/FontFile2 7 0 R
>>
endobj
10 0 obj
<</Length 360/Filter/FlateDecode>>
stream
xœ]’Ënƒ0E÷|…—é"“‡„�RH$}¨´@ì!E*9dÁß×3“¶R ãñ½Ã5㸬«ÚõsüêGÓÀ,ºÞY×ñæ ˆ3\z©DØÞÌ÷½ÍÐNQ¼Ír�a¨]7æy¿…½ëì±:Øñ Qüâ-øÞ]Äê£lº¹MÓ àf!£¢ºÐç©�žÛbr­k¶ûyYËŸà}™@$´VÅŒ®SkÀ·îQ.e!òÓ©ˆÀÙ{©d˹3Ÿ­R¤RfºœovÈ)óYoSäŒ8‘ÈÖ�w˪ï¸^"ï™ò�5Ôó‘ëGä’X'Èkˆ�¬¡ïž˜Ã¡r%™3dΟ¢Fqþû+ÌŸH…ýçפáüy9Fuί©?ç×2çט_qþ ³)ίñìꞟzVü]<»âü[Mƒ¸ÿq Þ™ŸQ só>Œ™.Í'Û;ø½{Ó8¡‹žo@±g
endstream
endobj
11 0 obj
<</Type/Font/Subtype/TrueType/BaseFont/CAAAAA+LiberationSerif
/FirstChar 0
/LastChar 30
/Widths[777 610 500 277 389 250 500 500 277 443 500 500 666 333 443 500
443 500 500 443 666 722 556 722 889 722 666 722 610 443 277 ]
/FontDescriptor 9 0 R
/ToUnicode 10 0 R
>>
endobj
12 0 obj
<</Length 13 0 R/Filter/FlateDecode/Length1 10276>>
stream
xœå8kxוçÎŒ~J²%¿Öȃm9~ÈØ1x0¶01‰_¸H€±„$#�- IÆ!mŠišœPh“>iCÚ¦iK“xLšàд¡ÛM»» ]H›MÚ”†mÒí—MSè.IÛ$–÷Ü«±14Í�ýöߎ4sÏûž×½w¤d|4Ù0<Èþ_ÌY\.Àó$Ï¿7)>¶ËÓ‚ðEîàPlçÈ—Om» „tßÛ9¼oÈüµ¯[²déCA_ þ/‹j
— �å!$ ¦>¡C|âKB#ÉÛ~¤/[‹ø8âòpÔﻹpI>âg/ñÝ;ÍŸçq1â >8qä-€"í�Ä¢‰äK„ÌHIÊ�Ń1˜yd%â÷£OG‘FðC¯lµçxA£Õé32³²sr ðÿîÒ<¯yîÐ ìcÏk.a˜a `ö»úLmžýóÿ¥zö$ŤÞ†70~?‡Ó À¿.”&•¤ŠV�äÁëp~ò÷¬¢=ÙÈÀWá<< Oü9¾CfàeRŒ}~
!Jk�WÈúsi£p˜¼Oö;<HŒŒ»mçál­Æþ»ˆÞÝá~Ò5 ¾/sÏÂWøÜYx}¾•;Œ´Yx ž'õ¤ð=x˜Hà|‡ZÄvÿ:|î¼JÕ<–zZs`¦L³ïÀ“ð4ËÀ~˜ï¼ÒeòGr×d1Ñ“¹šþpŽ©ëàwqOrÜÌ}ˆ|vâí#¿DéÃüÚëÂ9‘ЦBD÷¡¿%=p­<–z*õl‡IîEè‡ÿ‚‡‹Wÿï`äÞCêä?gÿ¦™ï~Èš1̾�6¦= Œ�Eø%í¡ÙgSû1¯gá¿0û/’byýÖ-wÿ¦¾Þžî®[oÙØyó†Žõ®ö¶u­kå–5«›oZµòÆË›–Ö;ëjk•åK¤2»­Èl2rs²23ô:­Fà95¢B¼í
_.š\>©]òuÔÖˆíE¡¶ÚšvÉåUDŸ¨à THŒ$ùÑ+*8ø�½ŠŒ’C×IÊiIy^’Åfh¦SH¢r¶M§É–7‡Û$�¨¼Åà[,T0$»5˜WÔ[±]qí M´{ÑG2••¹NZ̬­�©Ì,³RRlŠ8ÖpŽöUSèsè´i»/ t÷¸ÛÛ¬v»§¶fƒ’+µ1¬c&í:EÇLŠaê:Ü#NÕœ™¸wÚ;¼ÕÙ)àÛæVxêNðíw+¦j¥JjSªn½#*5R[»RM­vöÎÏÓyuJ¢hÊ�’8ñ6`8Ò[¸–âS)ÚrãÛ@A…[§�^·�^VæzbÂ%‰® ï„ozv|‡$¥‰©ìì‰X;¦ºÝhbzöô=VÅu¯G1zCd•G ÝÕÛ©ä÷lu+\¹K ù�‚ßÉ~£Õnš—éþ{lÀ´`r0Ãv;MÃ=Ó2ì@Dïq§qvXO‚ì¬ö(œ—rÎÌq,ý”3>Ç™W÷JXÛÎ>÷„"”oHí˜ñ{|Êøì®]´0’QÉ}Çj—&òLâJ§‡ÉŠèÕ†@XT4˜$ÔZ¨€}CU&Œ É}'=¼eÅ *LyâJ ÍP;íR»Wýî ¡ÝQ�n„MnEnC@ö©kŸªw¢†Ï‹ ·±b*N)¦˜¥ÖùêR·ÚÃ}n¦¢ª)æu
xýª–âlgëJlŸð¶¥] ¶¤÷SÐ8{qj™h}¼–�§�
¬Ã.«hŸp†›×Àu7$º­vEö`…=’;è¡m‡ªºheÍáa½²ÉÝÙ'uölqߨ:’fPsByûuf$·5mPÑ—ëE7gå=(hD‚èB@jmƧ¢+×ãmÄ„3*mÜÖfÑM¬0'�n(Ub{°M•£ø5F5´�ÖuÌYÓRí¬ë°Ú=öôU[Ã![T'F =MjÇ ·)dè±?×u0ÍemzÑ-%�¹ÛMc£éaYV“Ár®ÖjÓ5Ø‚dašÀŽì9„&SqU[&WYÏðy´ã:ö†9¶8¡—:û&¨qI5èùh Ë7š¬l/  Z½W4â’f zbJ–éb­¢F¤ � ©ÏÝ̤q?¹Ãz;�+:Iç¦ÖÚÜÚZ§$r°gJ&û¶¸ŸÂ#W<¸É}’#Ü:o«gj òÜO‰ø†È¨¥R"EDŠPK½ˆè™¼õ)`œqF`¸š£éçhüÓ\šfLOTÁ&’�CŽ�æÈsÒÒôiÚ8£±k
hÊäL�¬—3äl.‡³NJ:‰”ÓxJfx<›äëjõ2ò4ŸÊ�­i‰q”�Óì¿:uÿ÷ãÙ€jì‰µÒ Û¥(„ÅÆc¥] ÐFù˜'4áõÐÅXü…Hk°LÒtD›­dJÁV%Kj¥ôJoIÓµ”®Ã%ÕDZöÝ
¡°ÕmÇ%)–ü³uÂø­”7• ãïjѹ³ø6Ò€ï�<èÀ&çh9 Ïñz / ©å¬ó¬)�¬\ij45.­Ï·›ìù&»é¬|ïØFþ¬æÀ»û5Mï
o¤_s¡­]h+
ÁwÉ=ÙùùV«`È(¼ ¼]Ì6[ÍÖA�Ál3sf�¹ {ƒÙ,h4ùƒœpÑ GÈ{ÐNŽÚɸ�ÄìÄk'Ýv"ÛI=ûŠv20°G½ ¥ŠZªMy°²ÈY=¸}€�èl!s–}òV.­'f¡Š˜ì¢`1ku¥„,«�0ІåùË(¹a¹fWêâ,Ì´pwŽdÜuð»�§>µo,…§Û{zS¯§&È�OßI>{æÍ�Ç'oûæbó$yq°;õ�Í©ŒŸ¤†wbÜ4ÏþAn…
h„¹eIe¥NgÉ5Ôð¼ÁÂ7-Ó:z=Z-xrù\m.á ¹¶\.CÈÍËËêñä‹�àìò,±CÁ3M¤«‰ `l Õ&h¤ñA#Æ680�Gü6<¬ˆ¦¬¢iÙòÒ„a•iuåkHcC�Åd.hlXaÉ奲ŠJI›¯Ëå,”´†4‘C_U^9÷ÆÍ›nÝ�‘zÅúæsgSU/–;µ¥»‚™Ú½ž£;z«×ßÔ:²ÆüÝcßV8aÅ®�ë{søÆ¿œNíÝÚ®ý¢6S+„‚/rœ u4ßÒÙ±=ýUv7&"%tA¬‘Ëõ†¢Âœ¼n�>Ǩ1€åxÙ_DΑÉ"2XDœEXKZMhiii¬^Ðd�¦e•öRbi\Ã56Zh`Ó O²ùL¡Jv·.··- �òÍž±º¼S¥ñ�Zۆߚy‹¾¼C7ÖbÖ¢Zå%fKf†�ç3,|I±6§Ë“™©5‚Ùkærx³ÀÔåAY’‹œ˜æ–ÆkRLÓ &#`J 5uD*3–¯Ðär\ï;©+$÷/ϼ+¦~—íuÿòB÷p)1xÁLʉ–d“ê3ßÉíó§>—šr¢� ²>¡¾Ý‡¾e᯴z¹Ä¤Í-dº<FÞÜåá b…Ä[H´÷|^ðw‹�¹c�v±H«,
÷¥~�Jͤ.‘ð$ƒ¦~õñÛfᎽ„çJSM½HjÐ ©N½šúÓ�K}æ‰óeú² 9€+&òyyù‹3ò3ʤ<È.éò²�Zz¤-KL"´N�Ô%ö˜wËÔˆžÙëH“¤•Ê8“1�檱÷s!a=i¡9ã7
YÂÖÙüì—?M|«–ãH±>õÚh|OäBôvÃ>Ç?âïÁ ÜR˽ƒ'É=ï‰�ƒœ4ùƒSO§Žþx>oµ˜·<(†¨¼®0Óh*ÈÊâyS&o-)ÈêõØ�¦CÉÕ`ãiµù¸ÞŒ�ÛãÙo$Fú…‚ãVµ’A+é²§•õÞ.3çÀÀ‚m„î"×®±«-�g²ØÙ²ÒpWÆÌÝ|%õ.ɼòÆ;37�CH¤ôïæÉCúˆ™Ø‰Û@L=—ú7ý_;€uá§&>vç�´G�³oUšÏƒ>'{uZë"KY6@Y¹q‘V[uC¹Éh2&<§MäK&rÐD\&Òl"%&’a"Ù&o3X­ØÁV«ÍV”ðØt|FÂ#꼺˜NÑ�Ñiêu²n\wTwN§Ñéð—+võÀžt”jŸãÆèÜ>�¾þvÛd{¦T¶¤²Ã]Ž;K5iJl‹Y¾„n1¸™ZJ‰P•zÿòk©+…¤Ôú|0vðS;¶í»Ý7°y·>õûÂ�»ð×cŸýú$¹û'/Ÿ¶ø¹ÀÎí�×üÛ6û½nó“?û©òÉ‹„üI¬ïz¬o%æ£$è“ëJótÚ"Lˆ6�/_b°'=ÙƒÍÀeðÜN-Ö„ÇÂB™‹oϵElœ+bãüÒ¡;!‹gE..dÜ41Š<MºCiY— •©?¾úç5D_yxï~®âÑÀéWysæÏ©—ƒ»w nã^JJÝ{ü‘òû~û›Îþ™—ónêkÇî<v÷Øèá�²½g?]SÂ*\S]²3W§ÓC�¾ ¨7z¾Û“W�mÑ�áÁ"r´ˆ\."JIñ"r wCvªÅãØ”-¬R ÖFÀ¶ž&¶®ìMR:ù♡;èbº’­¹ñѱG¦…U3_K½6yˆk{z"ttýGc?ž›¤¾u o6ÜŸ—ÛuZ»ÙZ’PbÖ
U7Øs
ùÂÒÏ?X‰×Šç“Õfå2«µÐÈgöx̺%:3]Ð}Qn õ7ùâ¼��ÅqÖ]l_p¦Ï)V…l¨ŠÊ¥$ÝH•u[t¯ÐÑ}•– °”l©Ù×^}«òÏ–�ã{‡7‡þøðæK¯üèÍÅÍÞ>ܲuÿ³cëIóW?ü¹ò[äfyÙj‹³çÀöc�~þÓ%­k›�+òJVlÃX‹gÿÈ}Fs#î²7É‹ó³³3sô9‚PP˜£ÑjðÐêõZÃüæÏ|75Î¥œ5ÍüÙj’šW4Z-’z¦jÉCûÔ¡/¸•³g›[ì«Cywâ>þÃTê‡3?ëêÌ}¬ Ô³Pûæºî“·VØô¶R£N_ªwT–ñflca o6flz°¼î ¿p�O:È&¹ÉA^q�ï;ȱ9Ôé œÍAÀA.:È9Q七Œ;ˆ—ñÔU¼gÏàµ�⬇°‰
´Qúu(½]›ŒìHµ_=h±,…�ê»Cš­3ñÙõïNý^=w—þK“vÅ—Çx8õÆC½a 7H?61ó4ß±9Z“ÿMÛÇbøx䥞題ã÷ÎÐu}7öÜõŒi–ÅÅZƒ!§rp1šñ„1™�¹�eáÅ.�>vưÓåšæ21rìUw;[ºFìž<ruý
G^ú§Ä·k¹ Mê’ž Âà{gΦ^Þ�¿ÊÙSWR/¶K·›¾,¼˜Ú¡œK]H½3}ò™ï=r†ýéGÏ™â/]ØòÀ ¡ùm,û¿ëŸ6„*®þ•6û†öŒ†€cSÿ,ЭIÝ
ë®þ}xÝÿoF-ÀYÍOá��€f~1Ü�c·ð»7Q˜[ €£ïõHÛ�cwŠuTö5ÚKØG_'KHˆ<IÞçz¹ÓÜi¾•˜¿"ìNhê4ŸTg5âÏtÒ§»œ° _Éoá7áïÊ-%‘yß>2ï'bDÕ ¨Â<”@X…0Ã�*¬Áê}A…µ�©°n‡S*¬3YªÂ�KÚU8“„IŸ
gÁ"îéùò븗U8šx­
çB zB„ ÄáûU˜@©@T˜½`Sa– •*,€CتÂ(îTa-T_Ua\~¬ÂzphN©p,Ò¼ªÂ™Ü š÷T8 nÔŸUálØ–¡QáØ•áSá\X–ñ\[xg8¾=¾¤OôGcûâá�¡¤èðW‰ õKëÅõÑèÎá ¸.�Eã¾d8©Ë\w½XƒØ‹&:|ÉqCÄ_·1¼#˜–û|‘Dkt8°6áFÁ¸X+^ǽ©ôG‚ñ%4Ô-­«¿*@ùµ”¿@'œ}b2î G|ñÝbtèZWÄxpg8‘ Æ‘Žˆýu}ub·/Œ$E_$ nšWì
ûƒŒèÆ“>Ž&Cèð®Ñx8ûél‰ºù8$¤/Üoñ%“ÁD4ÒêKà\èÙÚxx$Z#Ž…Âþ�8æKˆ�`"¼3‚ÌûÄkuDäú0–H$ºMî Ö ßCñ`"Žì4-‰`<<¤š“!_’F>LÆÃ~ßðð>,ÝH Uw`­ÆÂÉ�Ý7|¢.í¦eS*†Gbñè^æ^mÂ#8�/àÛ'ÑFÈ÷ù1Y˜±°?Á’�9c¾Hmûh< ¢“›×o¼*ˆn¥™ˆï &˜t$ $h!â0*áÄÃÑènÊP4Žî’¡ÚþE#IT�о@cÆDEý£#´D˜áäœs><Š¼Ø°/‰VFu¡d2¶Êé«ó©UñcQêвóÃxÉ}± ZŠ8µ22¼+¡Ue¥¥AômØ(vÅ0?.tNTjĹÆ\Z·T�ÓŽ%u‰ðp]4¾ÓÙåÚm¸!íÄ;‰÷í¸Q@ÄÛ‡¸!?D!û ΤBHñUÇU86@=,Å[„õ(Eþ0ꋸiGQ>Æž>f7
¨ƒLÆùpk õª^t0í„6 ¾-lD½È]hW„>Ä"�€VćQs-Â~”Š LeE¨ÅûÃu?œ+ÎÛþ“IÌK4 WKñ®ÿ@ súµóú<O˜ÍAó�dê÷ŽqØ�´( }hVD” ²&�dX€Y¥¶ûQ¢�Iu3Mš—$›-¤6}ÀŒ]8ãêûY=ç$ýÌ6í‹´å(Â!5û`”U5�’To.¶Îü·õøàécÞíesÞÂèO0^+â 5®tÎÖ²ùF£¹COè¼!ûX>L›vZDÕÜ�½'~è<¢ªëSëÁOeÓ^R�5ßCì™`óFpá¹nI°8ìn ½YÆ|,ÿéš� 7ÉdýHÆÏ>uÕ�`~Ò³îP×Õ[¥¡ùØQÞ^Æ*{5énR»TdÔÂQæû\öjYE¨ÿAæ…|lÕï@�a6OÚ�ë «hP­p’y;—¥€õ0Æ(µÐκ�®õ šÉ͸Glü@‹él-ìÈ[+{YÞ®ÚŽ0oŒ�Ï,•VgJG<Ìö¢ÝóUb]–Î^€Y«ý;ùb¹Iª³F™Gü¤ëœî¨(ꎲª¥WQº‡““9ËoTÕ‹!‡Î•öe„­Šë»¬ÂWJ'zG?u¬û®¿ºRêTŸ�ÿk=êWŒep᪈Ïû2‚>nT×|d~­�.Xµs•èÃ�g#Û%bjÿ¸Ổ×Y kåús)Û1¯�"Ý�aē̟Ëe‹a'ò»p†�é÷çô/†¼p­í'-@ÈJè'kÔ±•Èøžm#kq´áx4’UH¿GäÃCø¼‚7G`5¾_÷Ówlâıq:Ö�*˜EÍ*¤ß€¸é•8Vªxâå8–«¸Dʘ|™ŠW#Gè&:ú{NAî&çfÈ33Ä8C¢ïù=2þöÑ·|›ÿÓå&›óòñËÜà%â¼4x)zéø¥W/iþãuÑö»×WÛ~{±ÒöïWÛ^]}¡ÿ7«ñíýBýîáû�k³H)Ú6âSÄ[Æ›Ÿ=CJeGñ"ׯùY¼B~%4Û~ñÂ"ÛÏ_¨°yÏ=æ<O�‹ç5Ó³g?_¼Ø…ã÷Îgæ¸ Ó¤@6�g~Xa“¿_µÖ%¿¬Ò5MìrÅ“«m0M¢ÓdúT¦ N8%ž’OyOÅNièpôÔ¹S—Oi¦‰(çt èÞ'¸Ÿ8÷‡–åÜ'²r]†“ƒ'¹)¾ÙFÝ.†¼»ðæá> :_,;*ª\¶IçdËäñIÁ0IäÉÜ<{tüQþ⣗å¾{¢Év¢»Âö±’’“ÍÔ£’'‰á;Äðmò4)$ùÐŒu°Èïn¶=p¬ÒöU¼¿‚÷ø1òE—Ãvü “_à>ïj²î·ÝÏÝw´ÂöÙÏTØ GlG¢Gö9rDóé{+l]‡‰á^"ß›epÙqŸºË`¼‹,ÿ„ëÜ^œ{ï$Þ ¼«bÄ#|Œ\‰‘‹ýGŒ ň'F¦g/ËwÄ0�ÑH‡-âj°•�¢þâÆ¢~]#߯źøP×;Ø`Äqû–Û6W¥më–Ûl[\Kmù yý¬®ÐÀ÷Gybà[ø.>Êïç5ƒ}DîsÔ¸ä¾Ò2|ä¹v÷~´÷ž^¾§k‘­ï⮪.ÎÓîâ¦Iž\ë*·mpÛ:\vÛz ú/.LYÔaí/h°ô›ˆ¡ßØ`èçv,ÌÚ¦‰é¤5£\‹£ÍÐb4ì7ƒÓÐeˆŽ^5Ìt-H»dà£@º€Œ ™&G§6õUWwNëf{;]÷V…TÊûèSîÙ¢h*пe«{Š�O{î:|Zw* }nÅ»ØÓ©�)0Ž€qñT´zÉDr´Z½H"I C�D‚²%Í‹0r"‘L&!­’¨N@5}"ƒàLe¨0µ¥~ }�ŽMC˜d"I…˜ò(}2ŒR©!vá ‰ùé™åôPô?OAO3
endstream
endobj
13 0 obj
6443
endobj
14 0 obj
<</Type/FontDescriptor/FontName/BAAAAA+LiberationSans-Bold
/Flags 4
/FontBBox[-481 -376 1303 1033]/ItalicAngle 0
/Ascent 905
/Descent -211
/CapHeight 1033
/StemV 80
/FontFile2 12 0 R
>>
endobj
15 0 obj
<</Length 289/Filter/FlateDecode>>
stream
xœ]‘Ín„ …÷<ËébâÿL'1&S­‰‹þ¤¶€pµ$ â·/\¦mÒä»ÜsnàÕ]Ó)i£W³ð,¥Öe3è“T$I©�ÜÞ*ÜùÌ4‰œ·ßW s§Æ¥,Iôæz«5;=\Å2À‰^Œ#ÕDuïê~Óú fP–Ƥª¨€ÑÍybú™Í¡ëØ ×–v?:ËŸà}×@S¬“p¾X5ã`˜š€”q\Ѳm+Jüë%E° #ÿdÆI'�ã<­§È§Äs†|Æó<œgž‹À­çSàÜó9páù9�=_�3ô^_<?„ù¨¯£¦ Œ3ÃÌÆs8ÅGÝnïŸçóÿ‰�òÍ~fåS’
~ÿQ/Ú»p}ÚìŽT
endstream
endobj
16 0 obj
<</Type/Font/Subtype/TrueType/BaseFont/BAAAAA+LiberationSans-Bold
/FirstChar 0
/LastChar 15
/Widths[750 722 556 389 556 610 610 556 277 556 556 610 556 333 889 610
]
/FontDescriptor 14 0 R
/ToUnicode 15 0 R
>>
endobj
17 0 obj
<</F1 16 0 R/F2 11 0 R
>>
endobj
18 0 obj
<</Font 17 0 R
/XObject<</Im4 4 0 R>>
/ProcSet[/PDF/Text/ImageC/ImageI/ImageB]
>>
endobj
1 0 obj
<</Type/Page/Parent 6 0 R/Resources 18 0 R/MediaBox[0 0 595.303937007874 841.889763779528]/Group<</S/Transparency/CS/DeviceRGB/I true>>/Contents 2 0 R>>
endobj
6 0 obj
<</Type/Pages
/Resources 18 0 R
/MediaBox[ 0 0 595 841 ]
/Kids[ 1 0 R ]
/Count 1>>
endobj
19 0 obj
<</Type/Catalog/Pages 6 0 R
/OpenAction[1 0 R /XYZ null null 0]
/Lang(de-DE)
>>
endobj
20 0 obj
<</Creator<FEFF005700720069007400650072>
/Producer<FEFF004C0069006200720065004F0066006600690063006500200037002E0031>
/CreationDate(D:20220401110308+02'00')>>
endobj
xref
0 21
0000000000 65535 f
0000029273 00000 n
0000000019 00000 n
0000000374 00000 n
0000000394 00000 n
0000011762 00000 n
0000029442 00000 n
0000011784 00000 n
0000020862 00000 n
0000020883 00000 n
0000021078 00000 n
0000021508 00000 n
0000021790 00000 n
0000028320 00000 n
0000028342 00000 n
0000028545 00000 n
0000028904 00000 n
0000029131 00000 n
0000029174 00000 n
0000029541 00000 n
0000029638 00000 n
trailer
<</Size 21/Root 19 0 R
/Info 20 0 R
/ID [ <77F1D5E7090F17C94EF7E43CF16B016F>
<77F1D5E7090F17C94EF7E43CF16B016F> ]
/DocChecksum /2F8AB12D558369FCB5C37CF4905E79AE
>>
startxref
29813
%%EOF
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

+51
View File
@@ -100,6 +100,57 @@ class TestDate(TestCase):
datetime.datetime(2020, 3, 1, 0, 0, tzinfo=tz.gettz(settings.TIME_ZONE)),
)
def test_date_format_10(self):
text = "Customer Number Currency 22-MAR-2022 Credit Card 1934829304"
self.assertEqual(
parse_date("", text),
datetime.datetime(2022, 3, 22, 0, 0, tzinfo=tz.gettz(settings.TIME_ZONE)),
)
def test_date_format_11(self):
text = "Customer Number Currency 22 MAR 2022 Credit Card 1934829304"
self.assertEqual(
parse_date("", text),
datetime.datetime(2022, 3, 22, 0, 0, tzinfo=tz.gettz(settings.TIME_ZONE)),
)
def test_date_format_12(self):
text = "Customer Number Currency 22/MAR/2022 Credit Card 1934829304"
self.assertEqual(
parse_date("", text),
datetime.datetime(2022, 3, 22, 0, 0, tzinfo=tz.gettz(settings.TIME_ZONE)),
)
def test_date_format_13(self):
text = "Customer Number Currency 22.MAR.2022 Credit Card 1934829304"
self.assertEqual(
parse_date("", text),
datetime.datetime(2022, 3, 22, 0, 0, tzinfo=tz.gettz(settings.TIME_ZONE)),
)
def test_date_format_14(self):
text = "Customer Number Currency 22.MAR 2022 Credit Card 1934829304"
self.assertEqual(
parse_date("", text),
datetime.datetime(2022, 3, 22, 0, 0, tzinfo=tz.gettz(settings.TIME_ZONE)),
)
def test_date_format_15(self):
text = "Customer Number Currency 22.MAR.22 Credit Card 1934829304"
self.assertIsNone(parse_date("", text), None)
def test_date_format_16(self):
text = "Customer Number Currency 22.MAR,22 Credit Card 1934829304"
self.assertIsNone(parse_date("", text), None)
def test_date_format_17(self):
text = "Customer Number Currency 22,MAR,2022 Credit Card 1934829304"
self.assertIsNone(parse_date("", text), None)
def test_date_format_18(self):
text = "Customer Number Currency 22 MAR,2022 Credit Card 1934829304"
self.assertIsNone(parse_date("", text), None)
def test_crazy_date_past(self, *args):
self.assertIsNone(parse_date("", "01-07-0590 00:00:00"))
@@ -260,6 +260,21 @@ class TestConsumer(DirectoriesMixin, ConsumerMixin, TransactionTestCase):
f'_is_ignored("{file_path}") != {expected_ignored}',
)
@mock.patch("documents.management.commands.document_consumer.open")
def test_consume_file_busy(self, open_mock):
# Calling this mock always raises this
open_mock.side_effect = OSError
self.t_start()
f = os.path.join(self.dirs.consumption_dir, "my_file.pdf")
shutil.copy(self.sample_file, f)
self.wait_for_task_mock_call()
self.task_mock.assert_not_called()
@override_settings(
CONSUMER_POLLING=1,
+182 -8
View File
@@ -1,6 +1,7 @@
import shutil
import tempfile
from random import randint
from typing import Iterable
from django.contrib.admin.models import LogEntry
from django.contrib.auth.models import User
@@ -15,27 +16,37 @@ from ..models import Tag
from ..signals import document_consumption_finished
class TestMatching(TestCase):
def _test_matching(self, text, algorithm, true, false):
class _TestMatchingBase(TestCase):
def _test_matching(
self,
match_text: str,
match_algorithm: str,
should_match: Iterable[str],
no_match: Iterable[str],
case_sensitive: bool = False,
):
for klass in (Tag, Correspondent, DocumentType):
instance = klass.objects.create(
name=str(randint(10000, 99999)),
match=text,
matching_algorithm=getattr(klass, algorithm),
match=match_text,
matching_algorithm=getattr(klass, match_algorithm),
is_insensitive=not case_sensitive,
)
for string in true:
for string in should_match:
doc = Document(content=string)
self.assertTrue(
matching.matches(instance, doc),
'"%s" should match "%s" but it does not' % (text, string),
'"%s" should match "%s" but it does not' % (match_text, string),
)
for string in false:
for string in no_match:
doc = Document(content=string)
self.assertFalse(
matching.matches(instance, doc),
'"%s" should not match "%s" but it does' % (text, string),
'"%s" should not match "%s" but it does' % (match_text, string),
)
class TestMatching(_TestMatchingBase):
def test_match_all(self):
self._test_matching(
@@ -202,6 +213,169 @@ class TestMatching(TestCase):
)
class TestCaseSensitiveMatching(_TestMatchingBase):
def test_match_all(self):
self._test_matching(
"alpha charlie gamma",
"MATCH_ALL",
(
"I have alpha, charlie, and gamma in me",
"I have gamma, charlie, and alpha in me",
),
(
"I have Alpha, charlie, and gamma in me",
"I have gamma, Charlie, and alpha in me",
"I have alpha, charlie, and Gamma in me",
"I have gamma, charlie, and ALPHA in me",
),
case_sensitive=True,
)
self._test_matching(
"Alpha charlie Gamma",
"MATCH_ALL",
(
"I have Alpha, charlie, and Gamma in me",
"I have Gamma, charlie, and Alpha in me",
),
(
"I have Alpha, charlie, and gamma in me",
"I have gamma, charlie, and alpha in me",
"I have alpha, charlie, and Gamma in me",
"I have Gamma, Charlie, and ALPHA in me",
),
case_sensitive=True,
)
self._test_matching(
'brown fox "lazy dogs"',
"MATCH_ALL",
(
"the quick brown fox jumped over the lazy dogs",
"the quick brown fox jumped over the lazy dogs",
),
(
"the quick Brown fox jumped over the lazy dogs",
"the quick brown Fox jumped over the lazy dogs",
"the quick brown fox jumped over the Lazy dogs",
"the quick brown fox jumped over the lazy Dogs",
),
case_sensitive=True,
)
def test_match_any(self):
self._test_matching(
"alpha charlie gamma",
"MATCH_ANY",
(
"I have alpha in me",
"I have charlie in me",
"I have gamma in me",
"I have alpha, charlie, and gamma in me",
"I have alpha and charlie in me",
),
(
"I have Alpha in me",
"I have chaRLie in me",
"I have gamMA in me",
"I have aLPha, cHArlie, and gAMma in me",
"I have AlphA and CharlIe in me",
),
case_sensitive=True,
)
self._test_matching(
"Alpha Charlie Gamma",
"MATCH_ANY",
(
"I have Alpha in me",
"I have Charlie in me",
"I have Gamma in me",
"I have Alpha, Charlie, and Gamma in me",
"I have Alpha and Charlie in me",
),
(
"I have alpha in me",
"I have ChaRLie in me",
"I have GamMA in me",
"I have ALPha, CHArlie, and GAMma in me",
"I have AlphA and CharlIe in me",
),
case_sensitive=True,
)
self._test_matching(
'"brown fox" " lazy dogs "',
"MATCH_ANY",
(
"the quick brown fox",
"jumped over the lazy dogs.",
),
(
"the quick Brown fox",
"jumped over the lazy Dogs.",
),
case_sensitive=True,
)
def test_match_literal(self):
self._test_matching(
"alpha charlie gamma",
"MATCH_LITERAL",
("I have 'alpha charlie gamma' in me",),
(
"I have 'Alpha charlie gamma' in me",
"I have 'alpha Charlie gamma' in me",
"I have 'alpha charlie Gamma' in me",
"I have 'Alpha Charlie Gamma' in me",
),
case_sensitive=True,
)
self._test_matching(
"Alpha Charlie Gamma",
"MATCH_LITERAL",
("I have 'Alpha Charlie Gamma' in me",),
(
"I have 'Alpha charlie gamma' in me",
"I have 'alpha Charlie gamma' in me",
"I have 'alpha charlie Gamma' in me",
"I have 'alpha charlie gamma' in me",
),
case_sensitive=True,
)
def test_match_regex(self):
self._test_matching(
r"alpha\w+gamma",
"MATCH_REGEX",
(
"I have alpha_and_gamma in me",
"I have alphas_and_gamma in me",
),
(
"I have Alpha_and_Gamma in me",
"I have alpHAs_and_gaMMa in me",
),
case_sensitive=True,
)
self._test_matching(
r"Alpha\w+gamma",
"MATCH_REGEX",
(
"I have Alpha_and_gamma in me",
"I have Alphas_and_gamma in me",
),
(
"I have Alpha_and_Gamma in me",
"I have alphas_and_gamma in me",
),
case_sensitive=True,
)
@override_settings(POST_CONSUME_SCRIPT=None)
class TestDocumentConsumptionFinishedSignal(TestCase):
"""
+316
View File
@@ -1,7 +1,10 @@
import os
import shutil
import tempfile
from unittest import mock
from django.conf import settings
from django.test import override_settings
from django.test import TestCase
from django.utils import timezone
from documents import tasks
@@ -12,6 +15,7 @@ from documents.models import Tag
from documents.sanity_checker import SanityCheckFailedException
from documents.sanity_checker import SanityCheckMessages
from documents.tests.utils import DirectoriesMixin
from PIL import Image
class TestTasks(DirectoriesMixin, TestCase):
@@ -89,6 +93,318 @@ class TestTasks(DirectoriesMixin, TestCase):
mtime3 = os.stat(settings.MODEL_FILE).st_mtime
self.assertNotEqual(mtime2, mtime3)
def test_barcode_reader(self):
test_file = os.path.join(
os.path.dirname(__file__),
"samples",
"barcodes",
"barcode-39-PATCHT.png",
)
img = Image.open(test_file)
separator_barcode = str(settings.CONSUMER_BARCODE_STRING)
self.assertEqual(tasks.barcode_reader(img), [separator_barcode])
def test_barcode_reader2(self):
test_file = os.path.join(
os.path.dirname(__file__),
"samples",
"barcodes",
"patch-code-t.pbm",
)
img = Image.open(test_file)
separator_barcode = str(settings.CONSUMER_BARCODE_STRING)
self.assertEqual(tasks.barcode_reader(img), [separator_barcode])
def test_barcode_reader_distorsion(self):
test_file = os.path.join(
os.path.dirname(__file__),
"samples",
"barcodes",
"barcode-39-PATCHT-distorsion.png",
)
img = Image.open(test_file)
separator_barcode = str(settings.CONSUMER_BARCODE_STRING)
self.assertEqual(tasks.barcode_reader(img), [separator_barcode])
def test_barcode_reader_distorsion2(self):
test_file = os.path.join(
os.path.dirname(__file__),
"samples",
"barcodes",
"barcode-39-PATCHT-distorsion2.png",
)
img = Image.open(test_file)
separator_barcode = str(settings.CONSUMER_BARCODE_STRING)
self.assertEqual(tasks.barcode_reader(img), [separator_barcode])
def test_barcode_reader_unreadable(self):
test_file = os.path.join(
os.path.dirname(__file__),
"samples",
"barcodes",
"barcode-39-PATCHT-unreadable.png",
)
img = Image.open(test_file)
self.assertEqual(tasks.barcode_reader(img), [])
def test_barcode_reader_qr(self):
test_file = os.path.join(
os.path.dirname(__file__),
"samples",
"barcodes",
"qr-code-PATCHT.png",
)
img = Image.open(test_file)
separator_barcode = str(settings.CONSUMER_BARCODE_STRING)
self.assertEqual(tasks.barcode_reader(img), [separator_barcode])
def test_barcode_reader_128(self):
test_file = os.path.join(
os.path.dirname(__file__),
"samples",
"barcodes",
"barcode-128-PATCHT.png",
)
img = Image.open(test_file)
separator_barcode = str(settings.CONSUMER_BARCODE_STRING)
self.assertEqual(tasks.barcode_reader(img), [separator_barcode])
def test_barcode_reader_no_barcode(self):
test_file = os.path.join(os.path.dirname(__file__), "samples", "simple.png")
img = Image.open(test_file)
self.assertEqual(tasks.barcode_reader(img), [])
def test_barcode_reader_custom_separator(self):
test_file = os.path.join(
os.path.dirname(__file__),
"samples",
"barcodes",
"barcode-39-custom.png",
)
img = Image.open(test_file)
self.assertEqual(tasks.barcode_reader(img), ["CUSTOM BARCODE"])
def test_barcode_reader_custom_qr_separator(self):
test_file = os.path.join(
os.path.dirname(__file__),
"samples",
"barcodes",
"barcode-qr-custom.png",
)
img = Image.open(test_file)
self.assertEqual(tasks.barcode_reader(img), ["CUSTOM BARCODE"])
def test_barcode_reader_custom_128_separator(self):
test_file = os.path.join(
os.path.dirname(__file__),
"samples",
"barcodes",
"barcode-128-custom.png",
)
img = Image.open(test_file)
self.assertEqual(tasks.barcode_reader(img), ["CUSTOM BARCODE"])
def test_scan_file_for_separating_barcodes(self):
test_file = os.path.join(
os.path.dirname(__file__),
"samples",
"barcodes",
"patch-code-t.pdf",
)
pages = tasks.scan_file_for_separating_barcodes(test_file)
self.assertEqual(pages, [0])
def test_scan_file_for_separating_barcodes2(self):
test_file = os.path.join(os.path.dirname(__file__), "samples", "simple.pdf")
pages = tasks.scan_file_for_separating_barcodes(test_file)
self.assertEqual(pages, [])
def test_scan_file_for_separating_barcodes3(self):
test_file = os.path.join(
os.path.dirname(__file__),
"samples",
"barcodes",
"patch-code-t-middle.pdf",
)
pages = tasks.scan_file_for_separating_barcodes(test_file)
self.assertEqual(pages, [1])
def test_scan_file_for_separating_barcodes4(self):
test_file = os.path.join(
os.path.dirname(__file__),
"samples",
"barcodes",
"several-patcht-codes.pdf",
)
pages = tasks.scan_file_for_separating_barcodes(test_file)
self.assertEqual(pages, [2, 5])
def test_scan_file_for_separating_barcodes_upsidedown(self):
test_file = os.path.join(
os.path.dirname(__file__),
"samples",
"barcodes",
"patch-code-t-middle_reverse.pdf",
)
pages = tasks.scan_file_for_separating_barcodes(test_file)
self.assertEqual(pages, [1])
def test_scan_file_for_separating_qr_barcodes(self):
test_file = os.path.join(
os.path.dirname(__file__),
"samples",
"barcodes",
"patch-code-t-qr.pdf",
)
pages = tasks.scan_file_for_separating_barcodes(test_file)
self.assertEqual(pages, [0])
@override_settings(CONSUMER_BARCODE_STRING="CUSTOM BARCODE")
def test_scan_file_for_separating_custom_barcodes(self):
test_file = os.path.join(
os.path.dirname(__file__),
"samples",
"barcodes",
"barcode-39-custom.pdf",
)
pages = tasks.scan_file_for_separating_barcodes(test_file)
self.assertEqual(pages, [0])
@override_settings(CONSUMER_BARCODE_STRING="CUSTOM BARCODE")
def test_scan_file_for_separating_custom_qr_barcodes(self):
test_file = os.path.join(
os.path.dirname(__file__),
"samples",
"barcodes",
"barcode-qr-custom.pdf",
)
pages = tasks.scan_file_for_separating_barcodes(test_file)
self.assertEqual(pages, [0])
@override_settings(CONSUMER_BARCODE_STRING="CUSTOM BARCODE")
def test_scan_file_for_separating_custom_128_barcodes(self):
test_file = os.path.join(
os.path.dirname(__file__),
"samples",
"barcodes",
"barcode-128-custom.pdf",
)
pages = tasks.scan_file_for_separating_barcodes(test_file)
self.assertEqual(pages, [0])
def test_scan_file_for_separating_wrong_qr_barcodes(self):
test_file = os.path.join(
os.path.dirname(__file__),
"samples",
"barcodes",
"barcode-39-custom.pdf",
)
pages = tasks.scan_file_for_separating_barcodes(test_file)
self.assertEqual(pages, [])
def test_separate_pages(self):
test_file = os.path.join(
os.path.dirname(__file__),
"samples",
"barcodes",
"patch-code-t-middle.pdf",
)
pages = tasks.separate_pages(test_file, [1])
self.assertEqual(len(pages), 2)
def test_separate_pages_no_list(self):
test_file = os.path.join(
os.path.dirname(__file__),
"samples",
"barcodes",
"patch-code-t-middle.pdf",
)
with self.assertLogs("paperless.tasks", level="WARNING") as cm:
pages = tasks.separate_pages(test_file, [])
self.assertEqual(pages, [])
self.assertEqual(
cm.output,
[
f"WARNING:paperless.tasks:No pages to split on!",
],
)
def test_save_to_dir(self):
test_file = os.path.join(
os.path.dirname(__file__),
"samples",
"barcodes",
"patch-code-t.pdf",
)
tempdir = tempfile.mkdtemp(prefix="paperless-", dir=settings.SCRATCH_DIR)
tasks.save_to_dir(test_file, target_dir=tempdir)
target_file = os.path.join(tempdir, "patch-code-t.pdf")
self.assertTrue(os.path.isfile(target_file))
def test_save_to_dir2(self):
test_file = os.path.join(
os.path.dirname(__file__),
"samples",
"barcodes",
"patch-code-t.pdf",
)
nonexistingdir = "/nowhere"
if os.path.isdir(nonexistingdir):
self.fail("non-existing dir exists")
else:
with self.assertLogs("paperless.tasks", level="WARNING") as cm:
tasks.save_to_dir(test_file, target_dir=nonexistingdir)
self.assertEqual(
cm.output,
[
f"WARNING:paperless.tasks:{str(test_file)} or {str(nonexistingdir)} don't exist.",
],
)
def test_save_to_dir3(self):
test_file = os.path.join(
os.path.dirname(__file__),
"samples",
"barcodes",
"patch-code-t.pdf",
)
tempdir = tempfile.mkdtemp(prefix="paperless-", dir=settings.SCRATCH_DIR)
tasks.save_to_dir(test_file, newname="newname.pdf", target_dir=tempdir)
target_file = os.path.join(tempdir, "newname.pdf")
self.assertTrue(os.path.isfile(target_file))
def test_barcode_splitter(self):
test_file = os.path.join(
os.path.dirname(__file__),
"samples",
"barcodes",
"patch-code-t-middle.pdf",
)
tempdir = tempfile.mkdtemp(prefix="paperless-", dir=settings.SCRATCH_DIR)
separators = tasks.scan_file_for_separating_barcodes(test_file)
self.assertTrue(separators)
document_list = tasks.separate_pages(test_file, separators)
self.assertTrue(document_list)
for document in document_list:
tasks.save_to_dir(document, target_dir=tempdir)
target_file1 = os.path.join(tempdir, "patch-code-t-middle_document_0.pdf")
target_file2 = os.path.join(tempdir, "patch-code-t-middle_document_1.pdf")
self.assertTrue(os.path.isfile(target_file1))
self.assertTrue(os.path.isfile(target_file2))
@override_settings(CONSUMER_ENABLE_BARCODES=True)
def test_consume_barcode_file(self):
test_file = os.path.join(
os.path.dirname(__file__),
"samples",
"barcodes",
"patch-code-t-middle.pdf",
)
dst = os.path.join(settings.SCRATCH_DIR, "patch-code-t-middle.pd")
shutil.copy(test_file, dst)
self.assertEqual(tasks.consume_file(dst), "File successfully split")
@mock.patch("documents.tasks.sanity_checker.check_sanity")
def test_sanity_check_success(self, m):
m.return_value = SanityCheckMessages()
+43 -2
View File
@@ -1,12 +1,14 @@
import json
import logging
import os
import tempfile
import urllib
import uuid
import zipfile
from datetime import datetime
from time import mktime
from unicodedata import normalize
from urllib.parse import quote_plus
from urllib.parse import quote
from django.conf import settings
from django.db.models import Case
@@ -24,6 +26,8 @@ from django.views.decorators.cache import cache_control
from django.views.generic import TemplateView
from django_filters.rest_framework import DjangoFilterBackend
from django_q.tasks import async_task
from packaging import version as packaging_version
from paperless import version
from paperless.db import GnuPG
from paperless.views import StandardPagination
from rest_framework import parsers
@@ -244,7 +248,7 @@ class DocumentViewSet(
# RFC 5987 addresses this issue
# see https://datatracker.ietf.org/doc/html/rfc5987#section-4.2
filename_normalized = normalize("NFKD", filename).encode("ascii", "ignore")
filename_encoded = quote_plus(filename)
filename_encoded = quote(filename)
content_disposition = (
f"{disposition}; "
f'filename="{filename_normalized}"; '
@@ -666,3 +670,40 @@ class BulkDownloadView(GenericAPIView):
)
return response
class RemoteVersionView(GenericAPIView):
def get(self, request, format=None):
remote_version = "0.0.0"
is_greater_than_current = False
# TODO: this can likely be removed when frontend settings are saved to DB
feature_is_set = settings.ENABLE_UPDATE_CHECK != "default"
if feature_is_set and settings.ENABLE_UPDATE_CHECK:
try:
with urllib.request.urlopen(
"https://api.github.com/repos/"
+ "paperless-ngx/paperless-ngx/releases/latest",
) as response:
remote = response.read().decode("utf-8")
try:
remote_json = json.loads(remote)
remote_version = remote_json["tag_name"].replace("ngx-", "")
except ValueError:
logger.debug("An error occured parsing remote version json")
except urllib.error.URLError:
logger.debug("An error occured checking for available updates")
current_version = ".".join([str(_) for _ in version.__version__[:3]])
is_greater_than_current = packaging_version.parse(
remote_version,
) > packaging_version.parse(
current_version,
)
return Response(
{
"version": remote_version,
"update_available": is_greater_than_current,
"feature_is_set": feature_is_set,
},
)
+60 -14
View File
@@ -3,6 +3,8 @@ import math
import multiprocessing
import os
import re
from typing import Final
from urllib.parse import urlparse
from concurrent_log_handler.queue import setup_logging_queues
from django.utils.translation import gettext_lazy as _
@@ -29,7 +31,7 @@ elif os.path.exists("/usr/local/etc/paperless.conf"):
os.environ["OMP_THREAD_LIMIT"] = "1"
def __get_boolean(key, default="NO"):
def __get_boolean(key: str, default: str = "NO") -> bool:
"""
Return a boolean value based on whatever the user has supplied in the
environment based on whether the value "looks like" it's True or not.
@@ -37,6 +39,13 @@ def __get_boolean(key, default="NO"):
return bool(os.getenv(key, default).lower() in ("yes", "y", "1", "t", "true"))
def __get_int(key: str, default: int) -> int:
"""
Return an integer value based on the environment variable or a default
"""
return int(os.getenv(key, default))
# NEVER RUN WITH DEBUG IN PRODUCTION.
DEBUG = __get_boolean("PAPERLESS_DEBUG", "NO")
@@ -211,7 +220,15 @@ if DEBUG:
else:
X_FRAME_OPTIONS = "SAMEORIGIN"
# We allow CORS from localhost:8080
# The next 3 settings can also be set using just PAPERLESS_URL
_csrf_origins = os.getenv("PAPERLESS_CSRF_TRUSTED_ORIGINS")
if _csrf_origins:
CSRF_TRUSTED_ORIGINS = _csrf_origins.split(",")
else:
CSRF_TRUSTED_ORIGINS = []
# We allow CORS from localhost:8000
CORS_ALLOWED_ORIGINS = tuple(
os.getenv("PAPERLESS_CORS_ALLOWED_HOSTS", "http://localhost:8000").split(","),
)
@@ -220,6 +237,22 @@ if DEBUG:
# Allow access from the angular development server during debugging
CORS_ALLOWED_ORIGINS += ("http://localhost:4200",)
_allowed_hosts = os.getenv("PAPERLESS_ALLOWED_HOSTS")
if _allowed_hosts:
ALLOWED_HOSTS = _allowed_hosts.split(",")
else:
ALLOWED_HOSTS = ["*"]
_paperless_url = os.getenv("PAPERLESS_URL")
if _paperless_url:
_paperless_uri = urlparse(_paperless_url)
CSRF_TRUSTED_ORIGINS.append(_paperless_url)
CORS_ALLOWED_ORIGINS += (_paperless_url,)
if _allowed_hosts:
ALLOWED_HOSTS.append(_paperless_uri.hostname)
else:
ALLOWED_HOSTS = [_paperless_uri.hostname]
# The secret key has a default that should be fine so long as you're hosting
# Paperless on a closed network. However, if you're putting this anywhere
# public, you should change the key to something unique and verbose.
@@ -228,12 +261,6 @@ SECRET_KEY = os.getenv(
"e11fl1oa-*ytql8p)(06fbj4ukrlo+n7k&q5+$1md7i+mge=ee",
)
_allowed_hosts = os.getenv("PAPERLESS_ALLOWED_HOSTS")
if _allowed_hosts:
ALLOWED_HOSTS = _allowed_hosts.split(",")
else:
ALLOWED_HOSTS = ["*"]
AUTH_PASSWORD_VALIDATORS = [
{
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
@@ -396,7 +423,7 @@ LOGGING = {
# in total.
def default_task_workers():
def default_task_workers() -> int:
# always leave one core open
available_cores = max(multiprocessing.cpu_count(), 1)
try:
@@ -407,20 +434,29 @@ def default_task_workers():
return 1
TASK_WORKERS = int(os.getenv("PAPERLESS_TASK_WORKERS", default_task_workers()))
TASK_WORKERS = __get_int("PAPERLESS_TASK_WORKERS", default_task_workers())
PAPERLESS_WORKER_TIMEOUT: Final[int] = __get_int("PAPERLESS_WORKER_TIMEOUT", 1800)
# Per django-q docs, timeout must be smaller than retry
# We default retry to 10s more than the timeout
PAPERLESS_WORKER_RETRY: Final[int] = __get_int(
"PAPERLESS_WORKER_RETRY",
PAPERLESS_WORKER_TIMEOUT + 10,
)
Q_CLUSTER = {
"name": "paperless",
"catch_up": False,
"recycle": 1,
"retry": 1800,
"timeout": int(os.getenv("PAPERLESS_WORKER_TIMEOUT", 1800)),
"retry": PAPERLESS_WORKER_RETRY,
"timeout": PAPERLESS_WORKER_TIMEOUT,
"workers": TASK_WORKERS,
"redis": os.getenv("PAPERLESS_REDIS", "redis://localhost:6379"),
}
def default_threads_per_worker(task_workers):
def default_threads_per_worker(task_workers) -> int:
# always leave one core open
available_cores = max(multiprocessing.cpu_count(), 1)
try:
@@ -455,13 +491,19 @@ CONSUMER_IGNORE_PATTERNS = list(
json.loads(
os.getenv(
"PAPERLESS_CONSUMER_IGNORE_PATTERNS",
'[".DS_STORE/*", "._*", ".stfolder/*"]',
'[".DS_STORE/*", "._*", ".stfolder/*", ".stversions/*", ".localized/*", "desktop.ini"]',
),
),
)
CONSUMER_SUBDIRS_AS_TAGS = __get_boolean("PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS")
CONSUMER_ENABLE_BARCODES = __get_boolean(
"PAPERLESS_CONSUMER_ENABLE_BARCODES",
)
CONSUMER_BARCODE_STRING = os.getenv("PAPERLESS_CONSUMER_BARCODE_STRING", "PATCHT")
OPTIMIZE_THUMBNAILS = __get_boolean("PAPERLESS_OPTIMIZE_THUMBNAILS", "true")
OCR_PAGES = int(os.getenv("PAPERLESS_OCR_PAGES", 0))
@@ -566,3 +608,7 @@ if os.getenv("PAPERLESS_IGNORE_DATES", ""):
d = dateparser.parse(s)
if d:
IGNORE_DATES.add(d.date())
ENABLE_UPDATE_CHECK = os.getenv("PAPERLESS_ENABLE_UPDATE_CHECK", "default")
if ENABLE_UPDATE_CHECK != "default":
ENABLE_UPDATE_CHECK = __get_boolean("PAPERLESS_ENABLE_UPDATE_CHECK")
+6
View File
@@ -14,6 +14,7 @@ from documents.views import DocumentTypeViewSet
from documents.views import IndexView
from documents.views import LogViewSet
from documents.views import PostDocumentView
from documents.views import RemoteVersionView
from documents.views import SavedViewViewSet
from documents.views import SearchAutoCompleteView
from documents.views import SelectionDataView
@@ -72,6 +73,11 @@ urlpatterns = [
BulkDownloadView.as_view(),
name="bulk_download",
),
re_path(
r"^remote_version/",
RemoteVersionView.as_view(),
name="remoteversion",
),
path("token/", views.obtain_auth_token),
]
+ api_router.urls,
+22 -16
View File
@@ -61,13 +61,13 @@ class FlagMailAction(BaseMailAction):
def get_rule_action(rule):
if rule.action == MailRule.ACTION_FLAG:
if rule.action == MailRule.AttachmentAction.FLAG:
return FlagMailAction()
elif rule.action == MailRule.ACTION_DELETE:
elif rule.action == MailRule.AttachmentAction.DELETE:
return DeleteMailAction()
elif rule.action == MailRule.ACTION_MOVE:
elif rule.action == MailRule.AttachmentAction.MOVE:
return MoveMailAction()
elif rule.action == MailRule.ACTION_MARK_READ:
elif rule.action == MailRule.AttachmentAction.MARK_READ:
return MarkReadMailAction()
else:
raise NotImplementedError("Unknown action.") # pragma: nocover
@@ -89,11 +89,11 @@ def make_criterias(rule):
def get_mailbox(server, port, security):
if security == MailAccount.IMAP_SECURITY_NONE:
if security == MailAccount.ImapSecurity.NONE:
mailbox = MailBoxUnencrypted(server, port)
elif security == MailAccount.IMAP_SECURITY_STARTTLS:
elif security == MailAccount.ImapSecurity.STARTTLS:
mailbox = MailBox(server, port, starttls=True)
elif security == MailAccount.IMAP_SECURITY_SSL:
elif security == MailAccount.ImapSecurity.SSL:
mailbox = MailBox(server, port)
else:
raise NotImplementedError("Unknown IMAP security") # pragma: nocover
@@ -112,10 +112,10 @@ class MailAccountHandler(LoggingMixin):
return None
def get_title(self, message, att, rule):
if rule.assign_title_from == MailRule.TITLE_FROM_SUBJECT:
if rule.assign_title_from == MailRule.TitleSource.FROM_SUBJECT:
return message.subject
elif rule.assign_title_from == MailRule.TITLE_FROM_FILENAME:
elif rule.assign_title_from == MailRule.TitleSource.FROM_FILENAME:
return os.path.splitext(os.path.basename(att.filename))[0]
else:
@@ -126,20 +126,20 @@ class MailAccountHandler(LoggingMixin):
def get_correspondent(self, message: MailMessage, rule):
c_from = rule.assign_correspondent_from
if c_from == MailRule.CORRESPONDENT_FROM_NOTHING:
if c_from == MailRule.CorrespondentSource.FROM_NOTHING:
return None
elif c_from == MailRule.CORRESPONDENT_FROM_EMAIL:
elif c_from == MailRule.CorrespondentSource.FROM_EMAIL:
return self._correspondent_from_name(message.from_)
elif c_from == MailRule.CORRESPONDENT_FROM_NAME:
elif c_from == MailRule.CorrespondentSource.FROM_NAME:
from_values = message.from_values
if from_values is not None and len(from_values.name) > 0:
return self._correspondent_from_name(from_values.name)
else:
return self._correspondent_from_name(message.from_)
elif c_from == MailRule.CORRESPONDENT_FROM_CUSTOM:
elif c_from == MailRule.CorrespondentSource.FROM_CUSTOM:
return rule.assign_correspondent
else:
@@ -253,7 +253,7 @@ class MailAccountHandler(LoggingMixin):
return total_processed_files
def handle_message(self, message, rule):
def handle_message(self, message, rule) -> int:
if not message.attachments:
return 0
@@ -274,7 +274,8 @@ class MailAccountHandler(LoggingMixin):
if (
not att.content_disposition == "attachment"
and rule.attachment_type == MailRule.ATTACHMENT_TYPE_ATTACHMENTS_ONLY
and rule.attachment_type
== MailRule.AttachmentProcessing.ATTACHMENTS_ONLY
):
self.log(
"debug",
@@ -285,7 +286,12 @@ class MailAccountHandler(LoggingMixin):
continue
if rule.filter_attachment_filename:
if not fnmatch(att.filename, rule.filter_attachment_filename):
# Force the filename and pattern to the lowercase
# as this is system dependent otherwise
if not fnmatch(
att.filename.lower(),
rule.filter_attachment_filename.lower(),
):
continue
title = self.get_title(message, att, rule)
@@ -0,0 +1,37 @@
# Generated by Django 4.0.3 on 2022-03-28 17:40
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("paperless_mail", "0008_auto_20210516_0940"),
]
operations = [
migrations.AlterField(
model_name="mailrule",
name="action",
field=models.PositiveIntegerField(
choices=[
(1, "Mark as read, don't process read mails"),
(2, "Flag the mail, don't process flagged mails"),
(3, "Move to specified folder"),
(4, "Delete"),
],
default=3,
verbose_name="action",
),
),
migrations.AlterField(
model_name="mailrule",
name="folder",
field=models.CharField(
default="INBOX",
help_text="Subfolders must be separated by a delimiter, often a dot ('.') or slash ('/'), but it varies by mail server.",
max_length=256,
verbose_name="folder",
),
),
]
+34 -59
View File
@@ -8,15 +8,10 @@ class MailAccount(models.Model):
verbose_name = _("mail account")
verbose_name_plural = _("mail accounts")
IMAP_SECURITY_NONE = 1
IMAP_SECURITY_SSL = 2
IMAP_SECURITY_STARTTLS = 3
IMAP_SECURITY_OPTIONS = (
(IMAP_SECURITY_NONE, _("No encryption")),
(IMAP_SECURITY_SSL, _("Use SSL")),
(IMAP_SECURITY_STARTTLS, _("Use STARTTLS")),
)
class ImapSecurity(models.IntegerChoices):
NONE = 1, _("No encryption")
SSL = 2, _("Use SSL")
STARTTLS = 3, _("Use STARTTLS")
name = models.CharField(_("name"), max_length=256, unique=True)
@@ -34,8 +29,8 @@ class MailAccount(models.Model):
imap_security = models.PositiveIntegerField(
_("IMAP security"),
choices=IMAP_SECURITY_OPTIONS,
default=IMAP_SECURITY_SSL,
choices=ImapSecurity.choices,
default=ImapSecurity.SSL,
)
username = models.CharField(_("username"), max_length=256)
@@ -61,48 +56,25 @@ class MailRule(models.Model):
verbose_name = _("mail rule")
verbose_name_plural = _("mail rules")
ATTACHMENT_TYPE_ATTACHMENTS_ONLY = 1
ATTACHMENT_TYPE_EVERYTHING = 2
class AttachmentProcessing(models.IntegerChoices):
ATTACHMENTS_ONLY = 1, _("Only process attachments.")
EVERYTHING = 2, _("Process all files, including 'inline' " "attachments.")
ATTACHMENT_TYPES = (
(ATTACHMENT_TYPE_ATTACHMENTS_ONLY, _("Only process attachments.")),
(
ATTACHMENT_TYPE_EVERYTHING,
_("Process all files, including 'inline' " "attachments."),
),
)
class AttachmentAction(models.IntegerChoices):
DELETE = 1, _("Mark as read, don't process read mails")
MOVE = 2, _("Flag the mail, don't process flagged mails")
MARK_READ = 3, _("Move to specified folder")
FLAG = 4, _("Delete")
ACTION_DELETE = 1
ACTION_MOVE = 2
ACTION_MARK_READ = 3
ACTION_FLAG = 4
class TitleSource(models.IntegerChoices):
FROM_SUBJECT = 1, _("Use subject as title")
FROM_FILENAME = 2, _("Use attachment filename as title")
ACTIONS = (
(ACTION_MARK_READ, _("Mark as read, don't process read mails")),
(ACTION_FLAG, _("Flag the mail, don't process flagged mails")),
(ACTION_MOVE, _("Move to specified folder")),
(ACTION_DELETE, _("Delete")),
)
TITLE_FROM_SUBJECT = 1
TITLE_FROM_FILENAME = 2
TITLE_SELECTOR = (
(TITLE_FROM_SUBJECT, _("Use subject as title")),
(TITLE_FROM_FILENAME, _("Use attachment filename as title")),
)
CORRESPONDENT_FROM_NOTHING = 1
CORRESPONDENT_FROM_EMAIL = 2
CORRESPONDENT_FROM_NAME = 3
CORRESPONDENT_FROM_CUSTOM = 4
CORRESPONDENT_SELECTOR = (
(CORRESPONDENT_FROM_NOTHING, _("Do not assign a correspondent")),
(CORRESPONDENT_FROM_EMAIL, _("Use mail address")),
(CORRESPONDENT_FROM_NAME, _("Use name (or mail address if not available)")),
(CORRESPONDENT_FROM_CUSTOM, _("Use correspondent selected below")),
)
class CorrespondentSource(models.IntegerChoices):
FROM_NOTHING = 1, _("Do not assign a correspondent")
FROM_EMAIL = 2, _("Use mail address")
FROM_NAME = 3, _("Use name (or mail address if not available)")
FROM_CUSTOM = 4, _("Use correspondent selected below")
name = models.CharField(_("name"), max_length=256, unique=True)
@@ -119,7 +91,10 @@ class MailRule(models.Model):
_("folder"),
default="INBOX",
max_length=256,
help_text=_("Subfolders must be separated by dots."),
help_text=_(
"Subfolders must be separated by a delimiter, often a dot ('.') or"
" slash ('/'), but it varies by mail server.",
),
)
filter_from = models.CharField(
@@ -161,8 +136,8 @@ class MailRule(models.Model):
attachment_type = models.PositiveIntegerField(
_("attachment type"),
choices=ATTACHMENT_TYPES,
default=ATTACHMENT_TYPE_ATTACHMENTS_ONLY,
choices=AttachmentProcessing.choices,
default=AttachmentProcessing.ATTACHMENTS_ONLY,
help_text=_(
"Inline attachments include embedded images, so it's best "
"to combine this option with a filename filter.",
@@ -171,8 +146,8 @@ class MailRule(models.Model):
action = models.PositiveIntegerField(
_("action"),
choices=ACTIONS,
default=ACTION_MARK_READ,
choices=AttachmentAction.choices,
default=AttachmentAction.MARK_READ,
)
action_parameter = models.CharField(
@@ -190,8 +165,8 @@ class MailRule(models.Model):
assign_title_from = models.PositiveIntegerField(
_("assign title from"),
choices=TITLE_SELECTOR,
default=TITLE_FROM_SUBJECT,
choices=TitleSource.choices,
default=TitleSource.FROM_SUBJECT,
)
assign_tag = models.ForeignKey(
@@ -212,8 +187,8 @@ class MailRule(models.Model):
assign_correspondent_from = models.PositiveIntegerField(
_("assign correspondent from"),
choices=CORRESPONDENT_SELECTOR,
default=CORRESPONDENT_FROM_NOTHING,
choices=CorrespondentSource.choices,
default=CorrespondentSource.FROM_NOTHING,
)
assign_correspondent = models.ForeignKey(
+49 -31
View File
@@ -246,13 +246,13 @@ class TestMail(DirectoriesMixin, TestCase):
rule = MailRule(
name="a",
assign_correspondent_from=MailRule.CORRESPONDENT_FROM_NOTHING,
assign_correspondent_from=MailRule.CorrespondentSource.FROM_NOTHING,
)
self.assertIsNone(handler.get_correspondent(message, rule))
rule = MailRule(
name="b",
assign_correspondent_from=MailRule.CORRESPONDENT_FROM_EMAIL,
assign_correspondent_from=MailRule.CorrespondentSource.FROM_EMAIL,
)
c = handler.get_correspondent(message, rule)
self.assertIsNotNone(c)
@@ -264,7 +264,7 @@ class TestMail(DirectoriesMixin, TestCase):
rule = MailRule(
name="c",
assign_correspondent_from=MailRule.CORRESPONDENT_FROM_NAME,
assign_correspondent_from=MailRule.CorrespondentSource.FROM_NAME,
)
c = handler.get_correspondent(message, rule)
self.assertIsNotNone(c)
@@ -275,7 +275,7 @@ class TestMail(DirectoriesMixin, TestCase):
rule = MailRule(
name="d",
assign_correspondent_from=MailRule.CORRESPONDENT_FROM_CUSTOM,
assign_correspondent_from=MailRule.CorrespondentSource.FROM_CUSTOM,
assign_correspondent=someone_else,
)
c = handler.get_correspondent(message, rule)
@@ -289,9 +289,15 @@ class TestMail(DirectoriesMixin, TestCase):
handler = MailAccountHandler()
rule = MailRule(name="a", assign_title_from=MailRule.TITLE_FROM_FILENAME)
rule = MailRule(
name="a",
assign_title_from=MailRule.TitleSource.FROM_FILENAME,
)
self.assertEqual(handler.get_title(message, att, rule), "this_is_the_file")
rule = MailRule(name="b", assign_title_from=MailRule.TITLE_FROM_SUBJECT)
rule = MailRule(
name="b",
assign_title_from=MailRule.TitleSource.FROM_SUBJECT,
)
self.assertEqual(handler.get_title(message, att, rule), "the message title")
def test_handle_message(self):
@@ -302,7 +308,10 @@ class TestMail(DirectoriesMixin, TestCase):
)
account = MailAccount()
rule = MailRule(assign_title_from=MailRule.TITLE_FROM_FILENAME, account=account)
rule = MailRule(
assign_title_from=MailRule.TitleSource.FROM_FILENAME,
account=account,
)
result = self.mail_account_handler.handle_message(message, rule)
@@ -346,7 +355,10 @@ class TestMail(DirectoriesMixin, TestCase):
)
account = MailAccount()
rule = MailRule(assign_title_from=MailRule.TITLE_FROM_FILENAME, account=account)
rule = MailRule(
assign_title_from=MailRule.TitleSource.FROM_FILENAME,
account=account,
)
result = self.mail_account_handler.handle_message(message, rule)
@@ -369,7 +381,10 @@ class TestMail(DirectoriesMixin, TestCase):
)
account = MailAccount()
rule = MailRule(assign_title_from=MailRule.TITLE_FROM_FILENAME, account=account)
rule = MailRule(
assign_title_from=MailRule.TitleSource.FROM_FILENAME,
account=account,
)
result = self.mail_account_handler.handle_message(message, rule)
@@ -392,9 +407,9 @@ class TestMail(DirectoriesMixin, TestCase):
account = MailAccount()
rule = MailRule(
assign_title_from=MailRule.TITLE_FROM_FILENAME,
assign_title_from=MailRule.TitleSource.FROM_FILENAME,
account=account,
attachment_type=MailRule.ATTACHMENT_TYPE_EVERYTHING,
attachment_type=MailRule.AttachmentProcessing.EVERYTHING,
)
result = self.mail_account_handler.handle_message(message, rule)
@@ -409,33 +424,36 @@ class TestMail(DirectoriesMixin, TestCase):
_AttachmentDef(filename="f2.pdf"),
_AttachmentDef(filename="f3.pdf"),
_AttachmentDef(filename="f2.png"),
_AttachmentDef(filename="file.PDf"),
_AttachmentDef(filename="f1.Pdf"),
],
)
tests = [
("*.pdf", ["f1.pdf", "f2.pdf", "f3.pdf"]),
("f1.pdf", ["f1.pdf"]),
("*.pdf", ["f1.pdf", "f1.Pdf", "f2.pdf", "f3.pdf", "file.PDf"]),
("f1.pdf", ["f1.pdf", "f1.Pdf"]),
("f1", []),
("*", ["f1.pdf", "f2.pdf", "f3.pdf", "f2.png"]),
("*", ["f1.pdf", "f2.pdf", "f3.pdf", "f2.png", "f1.Pdf", "file.PDf"]),
("*.png", ["f2.png"]),
]
for (pattern, matches) in tests:
matches.sort()
self.async_task.reset_mock()
account = MailAccount()
rule = MailRule(
assign_title_from=MailRule.TITLE_FROM_FILENAME,
assign_title_from=MailRule.TitleSource.FROM_FILENAME,
account=account,
filter_attachment_filename=pattern,
)
result = self.mail_account_handler.handle_message(message, rule)
self.assertEqual(result, len(matches))
filenames = [
a[1]["override_filename"] for a in self.async_task.call_args_list
]
self.assertCountEqual(filenames, matches)
self.assertEqual(result, len(matches), f"Error with pattern: {pattern}")
filenames = sorted(
[a[1]["override_filename"] for a in self.async_task.call_args_list],
)
self.assertListEqual(filenames, matches)
def test_handle_mail_account_mark_read(self):
@@ -449,7 +467,7 @@ class TestMail(DirectoriesMixin, TestCase):
_ = MailRule.objects.create(
name="testrule",
account=account,
action=MailRule.ACTION_MARK_READ,
action=MailRule.AttachmentAction.MARK_READ,
)
self.assertEqual(len(self.bogus_mailbox.messages), 3)
@@ -472,7 +490,7 @@ class TestMail(DirectoriesMixin, TestCase):
_ = MailRule.objects.create(
name="testrule",
account=account,
action=MailRule.ACTION_DELETE,
action=MailRule.AttachmentAction.DELETE,
filter_subject="Invoice",
)
@@ -493,7 +511,7 @@ class TestMail(DirectoriesMixin, TestCase):
_ = MailRule.objects.create(
name="testrule",
account=account,
action=MailRule.ACTION_FLAG,
action=MailRule.AttachmentAction.FLAG,
filter_subject="Invoice",
)
@@ -516,7 +534,7 @@ class TestMail(DirectoriesMixin, TestCase):
_ = MailRule.objects.create(
name="testrule",
account=account,
action=MailRule.ACTION_MOVE,
action=MailRule.AttachmentAction.MOVE,
action_parameter="spam",
filter_subject="Claim",
)
@@ -562,7 +580,7 @@ class TestMail(DirectoriesMixin, TestCase):
_ = MailRule.objects.create(
name="testrule",
account=account,
action=MailRule.ACTION_MOVE,
action=MailRule.AttachmentAction.MOVE,
action_parameter="spam",
filter_subject="Claim",
)
@@ -583,7 +601,7 @@ class TestMail(DirectoriesMixin, TestCase):
_ = MailRule.objects.create(
name="testrule",
account=account,
action=MailRule.ACTION_MOVE,
action=MailRule.AttachmentAction.MOVE,
action_parameter="spam",
filter_subject="Claim",
order=1,
@@ -592,7 +610,7 @@ class TestMail(DirectoriesMixin, TestCase):
_ = MailRule.objects.create(
name="testrule2",
account=account,
action=MailRule.ACTION_MOVE,
action=MailRule.AttachmentAction.MOVE,
action_parameter="spam",
filter_subject="Claim",
order=2,
@@ -622,7 +640,7 @@ class TestMail(DirectoriesMixin, TestCase):
_ = MailRule.objects.create(
name="testrule",
account=account,
action=MailRule.ACTION_MOVE,
action=MailRule.AttachmentAction.MOVE,
action_parameter="spam",
)
@@ -647,9 +665,9 @@ class TestMail(DirectoriesMixin, TestCase):
name="testrule",
filter_from="amazon@amazon.de",
account=account,
action=MailRule.ACTION_MOVE,
action=MailRule.AttachmentAction.MOVE,
action_parameter="spam",
assign_correspondent_from=MailRule.CORRESPONDENT_FROM_EMAIL,
assign_correspondent_from=MailRule.CorrespondentSource.FROM_EMAIL,
)
self.mail_account_handler.handle_mail_account(account)
@@ -684,7 +702,7 @@ class TestMail(DirectoriesMixin, TestCase):
rule = MailRule.objects.create(
name="testrule3",
account=account,
action=MailRule.ACTION_DELETE,
action=MailRule.AttachmentAction.DELETE,
filter_subject="Claim",
)
+1 -1
View File
@@ -7,7 +7,7 @@ max-line-length = 88
[tool:pytest]
DJANGO_SETTINGS_MODULE=paperless.settings
addopts = --pythonwarnings=all --cov --cov-report=html -n auto
addopts = --pythonwarnings=all --cov --cov-report=html --numprocesses auto --quiet
env =
PAPERLESS_DISABLE_DBHANDLER=true