mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Merge branch 'dev' into feature/slim-sidebar
This commit is contained in:
		| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "qpdf": { | ||||
|       "version": "10.6.3" | ||||
|       "version": "11.1.1" | ||||
|     }, | ||||
|   "jbig2enc": { | ||||
|       "version": "0.29", | ||||
|   | ||||
							
								
								
									
										37
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										37
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							| @@ -82,6 +82,22 @@ jobs: | ||||
|       matrix: | ||||
|         python-version: ['3.8', '3.9', '3.10'] | ||||
|       fail-fast: false | ||||
|     services: | ||||
|       tika: | ||||
|         image: ghcr.io/paperless-ngx/tika:latest | ||||
|         ports: | ||||
|           - "9998:9998/tcp" | ||||
|       gotenberg: | ||||
|         image: docker.io/gotenberg/gotenberg:7.4 | ||||
|         ports: | ||||
|           - "3000:3000/tcp" | ||||
|     env: | ||||
|       # Enable Tika end to end testing | ||||
|       TIKA_LIVE: 1 | ||||
|       # Enable paperless_mail testing against real server | ||||
|       PAPERLESS_MAIL_TEST_HOST: ${{ secrets.TEST_MAIL_HOST }} | ||||
|       PAPERLESS_MAIL_TEST_USER: ${{ secrets.TEST_MAIL_USER }} | ||||
|       PAPERLESS_MAIL_TEST_PASSWD: ${{ secrets.TEST_MAIL_PASSWD }} | ||||
|     steps: | ||||
|       - | ||||
|         name: Checkout | ||||
| @@ -91,7 +107,7 @@ jobs: | ||||
|       - | ||||
|         name: Install pipenv | ||||
|         run: | | ||||
|           pipx install pipenv==2022.8.5 | ||||
|           pipx install pipenv==2022.10.4 | ||||
|           pipenv --version | ||||
|       - | ||||
|         name: Set up Python | ||||
| @@ -117,11 +133,11 @@ jobs: | ||||
|         name: Tests | ||||
|         run: | | ||||
|           cd src/ | ||||
|           pipenv run pytest | ||||
|           pipenv run pytest -rfEp | ||||
|       - | ||||
|         name: Get changed files | ||||
|         id: changed-files-specific | ||||
|         uses: tj-actions/changed-files@v29.0.2 | ||||
|         uses: tj-actions/changed-files@v31.0.2 | ||||
|         with: | ||||
|           files: | | ||||
|             src/** | ||||
| @@ -484,6 +500,18 @@ jobs: | ||||
|         uses: actions/checkout@v3 | ||||
|         with: | ||||
|           ref: main | ||||
|       - | ||||
|         name: Install pipenv | ||||
|         run: | | ||||
|           pip3 install --upgrade pip setuptools wheel pipx | ||||
|           pipx install pipenv | ||||
|       - | ||||
|         name: Set up Python | ||||
|         uses: actions/setup-python@v4 | ||||
|         with: | ||||
|           python-version: 3.9 | ||||
|           cache: "pipenv" | ||||
|           cache-dependency-path: 'Pipfile.lock' | ||||
|       - | ||||
|         name: Append Changelog to docs | ||||
|         id: append-Changelog | ||||
| @@ -497,9 +525,10 @@ jobs: | ||||
|           CURRENT_CHANGELOG=`tail --lines +2 changelog.md` | ||||
|           echo -e "$CURRENT_CHANGELOG" >> changelog-new.md | ||||
|           mv changelog-new.md changelog.md | ||||
|           pipenv run pre-commit --files changelog.md | ||||
|           git config --global user.name "github-actions" | ||||
|           git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" | ||||
|           git commit -am "Changelog ${{ steps.get_version.outputs.version }} - GHA" | ||||
|           git commit -am "Changelog ${{ needs.publish-release.outputs.version }} - GHA" | ||||
|           git push origin ${{ needs.publish-release.outputs.version }}-changelog | ||||
|       - | ||||
|         name: Create Pull Request | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/workflows/cleanup-tags.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/cleanup-tags.yml
									
									
									
									
										vendored
									
									
								
							| @@ -39,7 +39,7 @@ jobs: | ||||
|           password: ${{ secrets.GITHUB_TOKEN }} | ||||
|       - | ||||
|         name: Set up Python | ||||
|         uses: actions/setup-python@v3 | ||||
|         uses: actions/setup-python@v4 | ||||
|         with: | ||||
|           python-version: "3.10" | ||||
|       - | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/workflows/codeql-analysis.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/codeql-analysis.yml
									
									
									
									
										vendored
									
									
								
							| @@ -38,7 +38,7 @@ jobs: | ||||
|  | ||||
|     steps: | ||||
|     - name: Checkout repository | ||||
|       uses: actions/checkout@v2 | ||||
|       uses: actions/checkout@v3 | ||||
|  | ||||
|     # Initializes the CodeQL tools for scanning. | ||||
|     - name: Initialize CodeQL | ||||
|   | ||||
							
								
								
									
										4
									
								
								.github/workflows/project-actions.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/project-actions.yml
									
									
									
									
										vendored
									
									
								
							| @@ -28,7 +28,7 @@ jobs: | ||||
|     if: github.event_name == 'issues' && (github.event.action == 'opened' || github.event.action == 'reopened') | ||||
|     steps: | ||||
|       - name: Add issue to project and set status to ${{ env.todo }} | ||||
|         uses: leonsteinhaeuser/project-beta-automations@v1.3.0 | ||||
|         uses: leonsteinhaeuser/project-beta-automations@v2.0.1 | ||||
|         with: | ||||
|           gh_token: ${{ secrets.GH_TOKEN }} | ||||
|           organization: paperless-ngx | ||||
| @@ -44,7 +44,7 @@ jobs: | ||||
|     if: github.event_name == 'pull_request_target' && (github.event.action == 'opened' || github.event.action == 'reopened') && github.event.pull_request.user.login != 'dependabot' | ||||
|     steps: | ||||
|       - name: Add PR to project and set status to "Needs Review" | ||||
|         uses: leonsteinhaeuser/project-beta-automations@v1.3.0 | ||||
|         uses: leonsteinhaeuser/project-beta-automations@v2.0.1 | ||||
|         with: | ||||
|           gh_token: ${{ secrets.GH_TOKEN }} | ||||
|           organization: paperless-ngx | ||||
|   | ||||
| @@ -37,7 +37,7 @@ repos: | ||||
|         exclude: "(^Pipfile\\.lock$)" | ||||
|   # Python hooks | ||||
|   - repo: https://github.com/asottile/reorder_python_imports | ||||
|     rev: v3.8.2 | ||||
|     rev: v3.8.3 | ||||
|     hooks: | ||||
|       - id: reorder-python-imports | ||||
|         exclude: "(migrations)" | ||||
| @@ -59,11 +59,11 @@ repos: | ||||
|         args: | ||||
|           - "--config=./src/setup.cfg" | ||||
|   - repo: https://github.com/psf/black | ||||
|     rev: 22.6.0 | ||||
|     rev: 22.8.0 | ||||
|     hooks: | ||||
|       - id: black | ||||
|   - repo: https://github.com/asottile/pyupgrade | ||||
|     rev: v2.37.3 | ||||
|     rev: v2.38.1 | ||||
|     hooks: | ||||
|       - id: pyupgrade | ||||
|         exclude: "(migrations)" | ||||
|   | ||||
| @@ -182,7 +182,7 @@ RUN --mount=type=bind,from=qpdf-builder,target=/qpdf \ | ||||
|     --mount=type=bind,from=pikepdf-builder,target=/pikepdf \ | ||||
|   set -eux \ | ||||
|   && echo "Installing qpdf" \ | ||||
|     && apt-get install --yes --no-install-recommends /qpdf/usr/src/qpdf/libqpdf28_*.deb \ | ||||
|     && apt-get install --yes --no-install-recommends /qpdf/usr/src/qpdf/libqpdf29_*.deb \ | ||||
|     && apt-get install --yes --no-install-recommends /qpdf/usr/src/qpdf/qpdf_*.deb \ | ||||
|   && echo "Installing pikepdf and dependencies" \ | ||||
|     && python3 -m pip install --no-cache-dir /pikepdf/usr/src/wheels/pyparsing*.whl \ | ||||
|   | ||||
							
								
								
									
										4
									
								
								Pipfile
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								Pipfile
									
									
									
									
									
								
							| @@ -23,7 +23,7 @@ imap-tools = "*" | ||||
| langdetect = "*" | ||||
| pathvalidate = "*" | ||||
| pillow = "~=9.2" | ||||
| pikepdf = "~=5.6" | ||||
| pikepdf = "*" | ||||
| python-gnupg = "*" | ||||
| python-dotenv = "*" | ||||
| python-dateutil = "*" | ||||
| @@ -39,7 +39,7 @@ whitenoise = "~=6.2" | ||||
| watchdog = "~=2.1" | ||||
| whoosh="~=2.7" | ||||
| inotifyrecursive = "~=0.3" | ||||
| ocrmypdf = "~=13.7" | ||||
| ocrmypdf = "~=14.0" | ||||
| tqdm = "*" | ||||
| tika = "*" | ||||
| # TODO: This will sadly also install daphne+dependencies, | ||||
|   | ||||
							
								
								
									
										743
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										743
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -60,7 +60,7 @@ RUN set -eux \ | ||||
|     && apt-get update --quiet \ | ||||
|     && apt-get install --yes --quiet --no-install-recommends ${BUILD_PACKAGES} \ | ||||
|   && echo "Installing qpdf" \ | ||||
|     && dpkg --install libqpdf28_*.deb \ | ||||
|     && dpkg --install libqpdf29_*.deb \ | ||||
|     && dpkg --install libqpdf-dev_*.deb \ | ||||
|   && echo "Installing Python tools" \ | ||||
|     && python3 -m pip install --no-cache-dir --upgrade \ | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| # This Dockerfile compiles the jbig2enc library | ||||
| # Inputs: | ||||
| #    - QPDF_VERSION - the version of qpdf to build a .deb. | ||||
| #                     Must be preset as a deb-src | ||||
| #                     Must be present as a deb-src in bookworm | ||||
|  | ||||
| FROM debian:bullseye-slim as main | ||||
|  | ||||
| @@ -22,27 +22,23 @@ ARG BUILD_PACKAGES="\ | ||||
|   libjpeg62-turbo-dev \ | ||||
|   libgnutls28-dev \ | ||||
|   packaging-dev \ | ||||
|   cmake \ | ||||
|   zlib1g-dev" | ||||
|  | ||||
| WORKDIR /usr/src | ||||
|  | ||||
| # As this is an base image for a multi-stage final image | ||||
| # the added size of the install is basically irrelevant | ||||
|  | ||||
| RUN set -eux \ | ||||
|   && echo "Installing build tools" \ | ||||
|     && apt-get update --quiet \ | ||||
|     && apt-get install --yes --quiet --no-install-recommends $BUILD_PACKAGES \ | ||||
|   && echo "Building qpdf" \ | ||||
|   && echo "Getting qpdf src" \ | ||||
|     && echo "deb-src http://deb.debian.org/debian/ bookworm main" > /etc/apt/sources.list.d/bookworm-src.list \ | ||||
|     && apt-get update \ | ||||
|     && mkdir qpdf \ | ||||
|     && cd qpdf \ | ||||
|     && apt-get source --yes --quiet qpdf=${QPDF_VERSION}-1/bookworm \ | ||||
|   && echo "Building qpdf" \ | ||||
|     && cd qpdf-$QPDF_VERSION \ | ||||
|     # We don't need to build the tests (also don't run them) | ||||
|     && rm -rf libtests \ | ||||
|     && DEBEMAIL=hello@paperless-ngx.com debchange --bpo \ | ||||
|     && export DEB_BUILD_OPTIONS="terse nocheck nodoc parallel=2" \ | ||||
|     && dpkg-buildpackage --build=binary --unsigned-source --unsigned-changes --post-clean \ | ||||
|     && ls -ahl ../*.deb \ | ||||
|   | ||||
| @@ -218,7 +218,8 @@ using the identifier which it has assigned to each document. You will end up get | ||||
| files like ``0000123.pdf`` in your media directory. This isn't necessarily a bad | ||||
| thing, because you normally don't have to access these files manually. However, if | ||||
| you wish to name your files differently, you can do that by adjusting the | ||||
| ``PAPERLESS_FILENAME_FORMAT`` configuration option. | ||||
| ``PAPERLESS_FILENAME_FORMAT`` configuration option. Paperless adds the correct | ||||
| file extension e.g. ``.pdf``, ``.jpg`` automatically. | ||||
|  | ||||
| This variable allows you to configure the filename (folders are allowed) using | ||||
| placeholders. For example, configuring this to | ||||
|   | ||||
| @@ -1,5 +1,204 @@ | ||||
| # Changelog | ||||
|  | ||||
| ## paperless-ngx 1.9.1 | ||||
|  | ||||
| ### Bug Fixes | ||||
|  | ||||
| - Bugfix: Fixes missing OCR mode skip_noarchive [@stumpylog](https://github.com/stumpylog) ([#1645](https://github.com/paperless-ngx/paperless-ngx/pull/1645)) | ||||
| - Fix reset button padding on small screens [@shamoon](https://github.com/shamoon) ([#1646](https://github.com/paperless-ngx/paperless-ngx/pull/1646)) | ||||
|  | ||||
| ### Documentation | ||||
|  | ||||
| - Improve docs re [@janis-ax](https://github.com/janis-ax) ([#1625](https://github.com/paperless-ngx/paperless-ngx/pull/1625)) | ||||
| - [Documentation] Add v1.9.0 changelog [@github-actions](https://github.com/github-actions) ([#1639](https://github.com/paperless-ngx/paperless-ngx/pull/1639)) | ||||
|  | ||||
| ### All App Changes | ||||
|  | ||||
| - Bugfix: Fixes missing OCR mode skip_noarchive [@stumpylog](https://github.com/stumpylog) ([#1645](https://github.com/paperless-ngx/paperless-ngx/pull/1645)) | ||||
| - Fix reset button padding on small screens [@shamoon](https://github.com/shamoon) ([#1646](https://github.com/paperless-ngx/paperless-ngx/pull/1646)) | ||||
|  | ||||
| ## paperless-ngx 1.9.0 | ||||
|  | ||||
| ### Features | ||||
|  | ||||
| - Feature: Faster, less memory barcode handling [@stumpylog](https://github.com/stumpylog) ([#1594](https://github.com/paperless-ngx/paperless-ngx/pull/1594)) | ||||
| - Feature: Display django-q process names [@stumpylog](https://github.com/stumpylog) ([#1567](https://github.com/paperless-ngx/paperless-ngx/pull/1567)) | ||||
| - Feature: Add MariaDB support [@bckelly1](https://github.com/bckelly1) ([#543](https://github.com/paperless-ngx/paperless-ngx/pull/543)) | ||||
| - Feature: Simplify IMAP login for UTF-8 [@stumpylog](https://github.com/stumpylog) ([#1492](https://github.com/paperless-ngx/paperless-ngx/pull/1492)) | ||||
| - Feature: Even better re-do of OCR [@stumpylog](https://github.com/stumpylog) ([#1451](https://github.com/paperless-ngx/paperless-ngx/pull/1451)) | ||||
| - Feature: document comments [@tim-vogel](https://github.com/tim-vogel) ([#1375](https://github.com/paperless-ngx/paperless-ngx/pull/1375)) | ||||
| - Adding date suggestions to the documents details view [@Eckii24](https://github.com/Eckii24) ([#1367](https://github.com/paperless-ngx/paperless-ngx/pull/1367)) | ||||
| - Feature: Event driven consumer [@stumpylog](https://github.com/stumpylog) ([#1421](https://github.com/paperless-ngx/paperless-ngx/pull/1421)) | ||||
| - Feature: Adds storage paths to re-tagger command [@stumpylog](https://github.com/stumpylog) ([#1446](https://github.com/paperless-ngx/paperless-ngx/pull/1446)) | ||||
| - Feature: Preserve original filename in metadata [@GwynHannay](https://github.com/GwynHannay) ([#1440](https://github.com/paperless-ngx/paperless-ngx/pull/1440)) | ||||
| - Handle tags for gmail email accounts [@sisao](https://github.com/sisao) ([#1433](https://github.com/paperless-ngx/paperless-ngx/pull/1433)) | ||||
| - Update redis image [@tribut](https://github.com/tribut) ([#1436](https://github.com/paperless-ngx/paperless-ngx/pull/1436)) | ||||
| - PAPERLESS_REDIS may be set via docker secrets [@DennisGaida](https://github.com/DennisGaida) ([#1405](https://github.com/paperless-ngx/paperless-ngx/pull/1405)) | ||||
|  | ||||
| ### Bug Fixes | ||||
|  | ||||
| - paperless_cmd.sh: use exec to run supervisord [@lemmi](https://github.com/lemmi) ([#1617](https://github.com/paperless-ngx/paperless-ngx/pull/1617)) | ||||
| - Fix: Double barcode separation creates empty file [@stumpylog](https://github.com/stumpylog) ([#1596](https://github.com/paperless-ngx/paperless-ngx/pull/1596)) | ||||
| - Fix: Resolve issue with slow classifier [@stumpylog](https://github.com/stumpylog) ([#1576](https://github.com/paperless-ngx/paperless-ngx/pull/1576)) | ||||
| - Fix document comments not updating on document navigation [@shamoon](https://github.com/shamoon) ([#1566](https://github.com/paperless-ngx/paperless-ngx/pull/1566)) | ||||
| - Fix: Include storage paths in document exporter [@shamoon](https://github.com/shamoon) ([#1557](https://github.com/paperless-ngx/paperless-ngx/pull/1557)) | ||||
| - Chore: Cleanup and validate settings [@stumpylog](https://github.com/stumpylog) ([#1551](https://github.com/paperless-ngx/paperless-ngx/pull/1551)) | ||||
| - Bugfix: Better gunicorn settings for workers [@stumpylog](https://github.com/stumpylog) ([#1500](https://github.com/paperless-ngx/paperless-ngx/pull/1500)) | ||||
| - Fix actions button in tasks table [@shamoon](https://github.com/shamoon) ([#1488](https://github.com/paperless-ngx/paperless-ngx/pull/1488)) | ||||
| - Fix: Add missing filter rule types to SavedViewFilterRule model \& fix migrations [@shamoon](https://github.com/shamoon) ([#1463](https://github.com/paperless-ngx/paperless-ngx/pull/1463)) | ||||
| - Fix paperless.conf.example typo [@qcasey](https://github.com/qcasey) ([#1460](https://github.com/paperless-ngx/paperless-ngx/pull/1460)) | ||||
| - Bugfix: Fixes the creation of an archive file, even if noarchive was specified [@stumpylog](https://github.com/stumpylog) ([#1442](https://github.com/paperless-ngx/paperless-ngx/pull/1442)) | ||||
| - Fix: created_date should not be required [@shamoon](https://github.com/shamoon) ([#1412](https://github.com/paperless-ngx/paperless-ngx/pull/1412)) | ||||
| - Fix: dev backend testing [@stumpylog](https://github.com/stumpylog) ([#1420](https://github.com/paperless-ngx/paperless-ngx/pull/1420)) | ||||
| - Bugfix: Catch all exceptions during the task signals [@stumpylog](https://github.com/stumpylog) ([#1387](https://github.com/paperless-ngx/paperless-ngx/pull/1387)) | ||||
| - Fix: saved view page parameter [@shamoon](https://github.com/shamoon) ([#1376](https://github.com/paperless-ngx/paperless-ngx/pull/1376)) | ||||
| - Fix: Correct browser unsaved changes warning [@shamoon](https://github.com/shamoon) ([#1369](https://github.com/paperless-ngx/paperless-ngx/pull/1369)) | ||||
| - Fix: correct date pasting with other formats [@shamoon](https://github.com/shamoon) ([#1370](https://github.com/paperless-ngx/paperless-ngx/pull/1370)) | ||||
| - Bugfix: Allow webserver bind address to be configured [@stumpylog](https://github.com/stumpylog) ([#1358](https://github.com/paperless-ngx/paperless-ngx/pull/1358)) | ||||
| - Bugfix: Chain exceptions during exception handling [@stumpylog](https://github.com/stumpylog) ([#1354](https://github.com/paperless-ngx/paperless-ngx/pull/1354)) | ||||
| - Fix: missing tooltip translation \& filter editor wrapping [@shamoon](https://github.com/shamoon) ([#1305](https://github.com/paperless-ngx/paperless-ngx/pull/1305)) | ||||
| - Bugfix: Interaction between barcode and directories as tags [@stumpylog](https://github.com/stumpylog) ([#1303](https://github.com/paperless-ngx/paperless-ngx/pull/1303)) | ||||
|  | ||||
| ### Documentation | ||||
|  | ||||
| - [Beta] Paperless-ngx v1.9.0 Release Candidate [@stumpylog](https://github.com/stumpylog) ([#1560](https://github.com/paperless-ngx/paperless-ngx/pull/1560)) | ||||
| - docs/configuration: Fix binary variable defaults [@erikarvstedt](https://github.com/erikarvstedt) ([#1528](https://github.com/paperless-ngx/paperless-ngx/pull/1528)) | ||||
| - Info about installing on subpath [@viktor-c](https://github.com/viktor-c) ([#1350](https://github.com/paperless-ngx/paperless-ngx/pull/1350)) | ||||
| - Docs: move scanner \& software recs to GH wiki [@shamoon](https://github.com/shamoon) ([#1482](https://github.com/paperless-ngx/paperless-ngx/pull/1482)) | ||||
| - Docs: Update mobile scanner section [@tooomm](https://github.com/tooomm) ([#1467](https://github.com/paperless-ngx/paperless-ngx/pull/1467)) | ||||
| - Adding date suggestions to the documents details view [@Eckii24](https://github.com/Eckii24) ([#1367](https://github.com/paperless-ngx/paperless-ngx/pull/1367)) | ||||
| - docs: scanners: add Brother ads4700w [@ocelotsloth](https://github.com/ocelotsloth) ([#1450](https://github.com/paperless-ngx/paperless-ngx/pull/1450)) | ||||
| - Feature: Adds storage paths to re-tagger command [@stumpylog](https://github.com/stumpylog) ([#1446](https://github.com/paperless-ngx/paperless-ngx/pull/1446)) | ||||
| - Changes to Redis documentation [@Zerteax](https://github.com/Zerteax) ([#1441](https://github.com/paperless-ngx/paperless-ngx/pull/1441)) | ||||
| - Update scanners.rst [@glassbox-sco](https://github.com/glassbox-sco) ([#1430](https://github.com/paperless-ngx/paperless-ngx/pull/1430)) | ||||
| - Update scanners.rst [@derlucas](https://github.com/derlucas) ([#1415](https://github.com/paperless-ngx/paperless-ngx/pull/1415)) | ||||
| - Bugfix: Allow webserver bind address to be configured [@stumpylog](https://github.com/stumpylog) ([#1358](https://github.com/paperless-ngx/paperless-ngx/pull/1358)) | ||||
| - docs: fix small typo [@tooomm](https://github.com/tooomm) ([#1352](https://github.com/paperless-ngx/paperless-ngx/pull/1352)) | ||||
| - [Documentation] Add v1.8.0 changelog [@github-actions](https://github.com/github-actions) ([#1298](https://github.com/paperless-ngx/paperless-ngx/pull/1298)) | ||||
|  | ||||
| ### Maintenance | ||||
|  | ||||
| - [Beta] Paperless-ngx v1.9.0 Release Candidate [@stumpylog](https://github.com/stumpylog) ([#1560](https://github.com/paperless-ngx/paperless-ngx/pull/1560)) | ||||
| - paperless_cmd.sh: use exec to run supervisord [@lemmi](https://github.com/lemmi) ([#1617](https://github.com/paperless-ngx/paperless-ngx/pull/1617)) | ||||
| - Chore: Extended container image cleanup [@stumpylog](https://github.com/stumpylog) ([#1556](https://github.com/paperless-ngx/paperless-ngx/pull/1556)) | ||||
| - Chore: Smaller library images [@stumpylog](https://github.com/stumpylog) ([#1546](https://github.com/paperless-ngx/paperless-ngx/pull/1546)) | ||||
| - Bump tj-actions/changed-files from 24 to 29.0.2 [@dependabot](https://github.com/dependabot) ([#1493](https://github.com/paperless-ngx/paperless-ngx/pull/1493)) | ||||
| - Bugfix: Better gunicorn settings for workers [@stumpylog](https://github.com/stumpylog) ([#1500](https://github.com/paperless-ngx/paperless-ngx/pull/1500)) | ||||
| - [CI] Fix release drafter issues [@qcasey](https://github.com/qcasey) ([#1301](https://github.com/paperless-ngx/paperless-ngx/pull/1301)) | ||||
| - Fix: dev backend testing [@stumpylog](https://github.com/stumpylog) ([#1420](https://github.com/paperless-ngx/paperless-ngx/pull/1420)) | ||||
| - Chore: Exclude dependabot PRs from Project, set status to Needs Review [@qcasey](https://github.com/qcasey) ([#1397](https://github.com/paperless-ngx/paperless-ngx/pull/1397)) | ||||
| - Chore: Add to label PRs based on and title [@qcasey](https://github.com/qcasey) ([#1396](https://github.com/paperless-ngx/paperless-ngx/pull/1396)) | ||||
| - Chore: use pre-commit in the Ci workflow [@stumpylog](https://github.com/stumpylog) ([#1362](https://github.com/paperless-ngx/paperless-ngx/pull/1362)) | ||||
| - Chore: Fixes permissions for image tag cleanup [@stumpylog](https://github.com/stumpylog) ([#1315](https://github.com/paperless-ngx/paperless-ngx/pull/1315)) | ||||
| - Bump leonsteinhaeuser/project-beta-automations from 1.2.1 to 1.3.0 [@dependabot](https://github.com/dependabot) ([#1328](https://github.com/paperless-ngx/paperless-ngx/pull/1328)) | ||||
| - Bump tj-actions/changed-files from 23.1 to 24 [@dependabot](https://github.com/dependabot) ([#1329](https://github.com/paperless-ngx/paperless-ngx/pull/1329)) | ||||
| - Feature: Remove requirements.txt and use pipenv everywhere [@stumpylog](https://github.com/stumpylog) ([#1316](https://github.com/paperless-ngx/paperless-ngx/pull/1316)) | ||||
|  | ||||
| ### Dependencies | ||||
|  | ||||
| <details> | ||||
| <summary>34 changes</summary> | ||||
|  | ||||
| - Bump pikepdf from 5.5.0 to 5.6.1 [@dependabot](https://github.com/dependabot) ([#1537](https://github.com/paperless-ngx/paperless-ngx/pull/1537)) | ||||
| - Bump black from 22.6.0 to 22.8.0 [@dependabot](https://github.com/dependabot) ([#1539](https://github.com/paperless-ngx/paperless-ngx/pull/1539)) | ||||
| - Bump tqdm from 4.64.0 to 4.64.1 [@dependabot](https://github.com/dependabot) ([#1540](https://github.com/paperless-ngx/paperless-ngx/pull/1540)) | ||||
| - Bump pytest from 7.1.2 to 7.1.3 [@dependabot](https://github.com/dependabot) ([#1538](https://github.com/paperless-ngx/paperless-ngx/pull/1538)) | ||||
| - Bump tj-actions/changed-files from 24 to 29.0.2 [@dependabot](https://github.com/dependabot) ([#1493](https://github.com/paperless-ngx/paperless-ngx/pull/1493)) | ||||
| - Bump angular packages, jest-preset-angular in src-ui [@dependabot](https://github.com/dependabot) ([#1502](https://github.com/paperless-ngx/paperless-ngx/pull/1502)) | ||||
| - Bump jest-environment-jsdom from 28.1.3 to 29.0.1 in /src-ui [@dependabot](https://github.com/dependabot) ([#1507](https://github.com/paperless-ngx/paperless-ngx/pull/1507)) | ||||
| - Bump [@<!---->types/node from 18.6.3 to 18.7.14 in /src-ui @dependabot](https://github.com/<!---->types/node from 18.6.3 to 18.7.14 in /src-ui @dependabot) ([#1506](https://github.com/paperless-ngx/paperless-ngx/pull/1506)) | ||||
| - Bump [@<!---->angular-builders/jest from 14.0.0 to 14.0.1 in /src-ui @dependabot](https://github.com/<!---->angular-builders/jest from 14.0.0 to 14.0.1 in /src-ui @dependabot) ([#1505](https://github.com/paperless-ngx/paperless-ngx/pull/1505)) | ||||
| - Bump zone.js from 0.11.7 to 0.11.8 in /src-ui [@dependabot](https://github.com/dependabot) ([#1504](https://github.com/paperless-ngx/paperless-ngx/pull/1504)) | ||||
| - Bump ngx-color from 8.0.1 to 8.0.2 in /src-ui [@dependabot](https://github.com/dependabot) ([#1494](https://github.com/paperless-ngx/paperless-ngx/pull/1494)) | ||||
| - Bump cypress from 10.3.1 to 10.7.0 in /src-ui [@dependabot](https://github.com/dependabot) ([#1496](https://github.com/paperless-ngx/paperless-ngx/pull/1496)) | ||||
| - Bump [@<!---->cypress/schematic from 2.0.0 to 2.1.1 in /src-ui @dependabot](https://github.com/<!---->cypress/schematic from 2.0.0 to 2.1.1 in /src-ui @dependabot) ([#1495](https://github.com/paperless-ngx/paperless-ngx/pull/1495)) | ||||
| - Bump [@<!---->popperjs/core from 2.11.5 to 2.11.6 in /src-ui @dependabot](https://github.com/<!---->popperjs/core from 2.11.5 to 2.11.6 in /src-ui @dependabot) ([#1498](https://github.com/paperless-ngx/paperless-ngx/pull/1498)) | ||||
| - Bump sphinx from 5.0.2 to 5.1.1 [@dependabot](https://github.com/dependabot) ([#1297](https://github.com/paperless-ngx/paperless-ngx/pull/1297)) | ||||
| - Chore: Bump Python dependencies [@stumpylog](https://github.com/stumpylog) ([#1445](https://github.com/paperless-ngx/paperless-ngx/pull/1445)) | ||||
| - Chore: Update Python deps [@stumpylog](https://github.com/stumpylog) ([#1391](https://github.com/paperless-ngx/paperless-ngx/pull/1391)) | ||||
| - Bump watchfiles from 0.15.0 to 0.16.1 [@dependabot](https://github.com/dependabot) ([#1285](https://github.com/paperless-ngx/paperless-ngx/pull/1285)) | ||||
| - Bump leonsteinhaeuser/project-beta-automations from 1.2.1 to 1.3.0 [@dependabot](https://github.com/dependabot) ([#1328](https://github.com/paperless-ngx/paperless-ngx/pull/1328)) | ||||
| - Bump tj-actions/changed-files from 23.1 to 24 [@dependabot](https://github.com/dependabot) ([#1329](https://github.com/paperless-ngx/paperless-ngx/pull/1329)) | ||||
| - Bump cypress from 10.3.0 to 10.3.1 in /src-ui [@dependabot](https://github.com/dependabot) ([#1342](https://github.com/paperless-ngx/paperless-ngx/pull/1342)) | ||||
| - Bump ngx-color from 7.3.3 to 8.0.1 in /src-ui [@dependabot](https://github.com/dependabot) ([#1343](https://github.com/paperless-ngx/paperless-ngx/pull/1343)) | ||||
| - Bump [@<!---->angular/cli from 14.0.4 to 14.1.0 in /src-ui @dependabot](https://github.com/<!---->angular/cli from 14.0.4 to 14.1.0 in /src-ui @dependabot) ([#1330](https://github.com/paperless-ngx/paperless-ngx/pull/1330)) | ||||
| - Bump [@<!---->types/node from 18.0.0 to 18.6.3 in /src-ui @dependabot](https://github.com/<!---->types/node from 18.0.0 to 18.6.3 in /src-ui @dependabot) ([#1341](https://github.com/paperless-ngx/paperless-ngx/pull/1341)) | ||||
| - Bump jest-preset-angular from 12.1.0 to 12.2.0 in /src-ui [@dependabot](https://github.com/dependabot) ([#1340](https://github.com/paperless-ngx/paperless-ngx/pull/1340)) | ||||
| - Bump concurrently from 7.2.2 to 7.3.0 in /src-ui [@dependabot](https://github.com/dependabot) ([#1326](https://github.com/paperless-ngx/paperless-ngx/pull/1326)) | ||||
| - Bump ng2-pdf-viewer from 9.0.0 to 9.1.0 in /src-ui [@dependabot](https://github.com/dependabot) ([#1337](https://github.com/paperless-ngx/paperless-ngx/pull/1337)) | ||||
| - Bump jest-environment-jsdom from 28.1.2 to 28.1.3 in /src-ui [@dependabot](https://github.com/dependabot) ([#1336](https://github.com/paperless-ngx/paperless-ngx/pull/1336)) | ||||
| - Bump ngx-file-drop from 13.0.0 to 14.0.1 in /src-ui [@dependabot](https://github.com/dependabot) ([#1331](https://github.com/paperless-ngx/paperless-ngx/pull/1331)) | ||||
| - Bump jest and [@<!---->types/jest in /src-ui @dependabot](https://github.com/<!---->types/jest in /src-ui @dependabot) ([#1333](https://github.com/paperless-ngx/paperless-ngx/pull/1333)) | ||||
| - Bump bootstrap from 5.1.3 to 5.2.0 in /src-ui [@dependabot](https://github.com/dependabot) ([#1327](https://github.com/paperless-ngx/paperless-ngx/pull/1327)) | ||||
| - Bump typescript from 4.6.4 to 4.7.4 in /src-ui [@dependabot](https://github.com/dependabot) ([#1324](https://github.com/paperless-ngx/paperless-ngx/pull/1324)) | ||||
| - Bump ts-node from 10.8.1 to 10.9.1 in /src-ui [@dependabot](https://github.com/dependabot) ([#1325](https://github.com/paperless-ngx/paperless-ngx/pull/1325)) | ||||
| - Bump rxjs from 7.5.5 to 7.5.6 in /src-ui [@dependabot](https://github.com/dependabot) ([#1323](https://github.com/paperless-ngx/paperless-ngx/pull/1323)) | ||||
| </details> | ||||
|  | ||||
| ### All App Changes | ||||
|  | ||||
| - [Beta] Paperless-ngx v1.9.0 Release Candidate [@stumpylog](https://github.com/stumpylog) ([#1560](https://github.com/paperless-ngx/paperless-ngx/pull/1560)) | ||||
| - Feature: Faster, less memory barcode handling [@stumpylog](https://github.com/stumpylog) ([#1594](https://github.com/paperless-ngx/paperless-ngx/pull/1594)) | ||||
| - Fix: Consume directory permissions were not updated [@stumpylog](https://github.com/stumpylog) ([#1605](https://github.com/paperless-ngx/paperless-ngx/pull/1605)) | ||||
| - Fix: Double barcode separation creates empty file [@stumpylog](https://github.com/stumpylog) ([#1596](https://github.com/paperless-ngx/paperless-ngx/pull/1596)) | ||||
| - Fix: Parsing Tika documents fails with AttributeError [@stumpylog](https://github.com/stumpylog) ([#1591](https://github.com/paperless-ngx/paperless-ngx/pull/1591)) | ||||
| - Fix: Resolve issue with slow classifier [@stumpylog](https://github.com/stumpylog) ([#1576](https://github.com/paperless-ngx/paperless-ngx/pull/1576)) | ||||
| - Feature: Display django-q process names [@stumpylog](https://github.com/stumpylog) ([#1567](https://github.com/paperless-ngx/paperless-ngx/pull/1567)) | ||||
| - Fix document comments not updating on document navigation [@shamoon](https://github.com/shamoon) ([#1566](https://github.com/paperless-ngx/paperless-ngx/pull/1566)) | ||||
| - Feature: Add MariaDB support [@bckelly1](https://github.com/bckelly1) ([#543](https://github.com/paperless-ngx/paperless-ngx/pull/543)) | ||||
| - Fix: Include storage paths in document exporter [@shamoon](https://github.com/shamoon) ([#1557](https://github.com/paperless-ngx/paperless-ngx/pull/1557)) | ||||
| - Chore: Cleanup and validate settings [@stumpylog](https://github.com/stumpylog) ([#1551](https://github.com/paperless-ngx/paperless-ngx/pull/1551)) | ||||
| - Bump pikepdf from 5.5.0 to 5.6.1 [@dependabot](https://github.com/dependabot) ([#1537](https://github.com/paperless-ngx/paperless-ngx/pull/1537)) | ||||
| - Bump black from 22.6.0 to 22.8.0 [@dependabot](https://github.com/dependabot) ([#1539](https://github.com/paperless-ngx/paperless-ngx/pull/1539)) | ||||
| - Bump tqdm from 4.64.0 to 4.64.1 [@dependabot](https://github.com/dependabot) ([#1540](https://github.com/paperless-ngx/paperless-ngx/pull/1540)) | ||||
| - Bump pytest from 7.1.2 to 7.1.3 [@dependabot](https://github.com/dependabot) ([#1538](https://github.com/paperless-ngx/paperless-ngx/pull/1538)) | ||||
| - Bump angular packages, jest-preset-angular in src-ui [@dependabot](https://github.com/dependabot) ([#1502](https://github.com/paperless-ngx/paperless-ngx/pull/1502)) | ||||
| - Bump jest-environment-jsdom from 28.1.3 to 29.0.1 in /src-ui [@dependabot](https://github.com/dependabot) ([#1507](https://github.com/paperless-ngx/paperless-ngx/pull/1507)) | ||||
| - Bump [@<!---->types/node from 18.6.3 to 18.7.14 in /src-ui @dependabot](https://github.com/<!---->types/node from 18.6.3 to 18.7.14 in /src-ui @dependabot) ([#1506](https://github.com/paperless-ngx/paperless-ngx/pull/1506)) | ||||
| - Bump [@<!---->angular-builders/jest from 14.0.0 to 14.0.1 in /src-ui @dependabot](https://github.com/<!---->angular-builders/jest from 14.0.0 to 14.0.1 in /src-ui @dependabot) ([#1505](https://github.com/paperless-ngx/paperless-ngx/pull/1505)) | ||||
| - Bump zone.js from 0.11.7 to 0.11.8 in /src-ui [@dependabot](https://github.com/dependabot) ([#1504](https://github.com/paperless-ngx/paperless-ngx/pull/1504)) | ||||
| - Bump ngx-color from 8.0.1 to 8.0.2 in /src-ui [@dependabot](https://github.com/dependabot) ([#1494](https://github.com/paperless-ngx/paperless-ngx/pull/1494)) | ||||
| - Bump cypress from 10.3.1 to 10.7.0 in /src-ui [@dependabot](https://github.com/dependabot) ([#1496](https://github.com/paperless-ngx/paperless-ngx/pull/1496)) | ||||
| - Bump [@<!---->cypress/schematic from 2.0.0 to 2.1.1 in /src-ui @dependabot](https://github.com/<!---->cypress/schematic from 2.0.0 to 2.1.1 in /src-ui @dependabot) ([#1495](https://github.com/paperless-ngx/paperless-ngx/pull/1495)) | ||||
| - Bump [@<!---->popperjs/core from 2.11.5 to 2.11.6 in /src-ui @dependabot](https://github.com/<!---->popperjs/core from 2.11.5 to 2.11.6 in /src-ui @dependabot) ([#1498](https://github.com/paperless-ngx/paperless-ngx/pull/1498)) | ||||
| - Feature: Simplify IMAP login for UTF-8 [@stumpylog](https://github.com/stumpylog) ([#1492](https://github.com/paperless-ngx/paperless-ngx/pull/1492)) | ||||
| - Fix actions button in tasks table [@shamoon](https://github.com/shamoon) ([#1488](https://github.com/paperless-ngx/paperless-ngx/pull/1488)) | ||||
| - Fix: Add missing filter rule types to SavedViewFilterRule model \& fix migrations [@shamoon](https://github.com/shamoon) ([#1463](https://github.com/paperless-ngx/paperless-ngx/pull/1463)) | ||||
| - Feature: Even better re-do of OCR [@stumpylog](https://github.com/stumpylog) ([#1451](https://github.com/paperless-ngx/paperless-ngx/pull/1451)) | ||||
| - Feature: document comments [@tim-vogel](https://github.com/tim-vogel) ([#1375](https://github.com/paperless-ngx/paperless-ngx/pull/1375)) | ||||
| - Adding date suggestions to the documents details view [@Eckii24](https://github.com/Eckii24) ([#1367](https://github.com/paperless-ngx/paperless-ngx/pull/1367)) | ||||
| - Bump sphinx from 5.0.2 to 5.1.1 [@dependabot](https://github.com/dependabot) ([#1297](https://github.com/paperless-ngx/paperless-ngx/pull/1297)) | ||||
| - Feature: Event driven consumer [@stumpylog](https://github.com/stumpylog) ([#1421](https://github.com/paperless-ngx/paperless-ngx/pull/1421)) | ||||
| - Bugfix: Fixes the creation of an archive file, even if noarchive was specified [@stumpylog](https://github.com/stumpylog) ([#1442](https://github.com/paperless-ngx/paperless-ngx/pull/1442)) | ||||
| - Feature: Adds storage paths to re-tagger command [@stumpylog](https://github.com/stumpylog) ([#1446](https://github.com/paperless-ngx/paperless-ngx/pull/1446)) | ||||
| - Feature: Preserve original filename in metadata [@GwynHannay](https://github.com/GwynHannay) ([#1440](https://github.com/paperless-ngx/paperless-ngx/pull/1440)) | ||||
| - Handle tags for gmail email accounts [@sisao](https://github.com/sisao) ([#1433](https://github.com/paperless-ngx/paperless-ngx/pull/1433)) | ||||
| - Fix: should not be required [@shamoon](https://github.com/shamoon) ([#1412](https://github.com/paperless-ngx/paperless-ngx/pull/1412)) | ||||
| - Bugfix: Catch all exceptions during the task signals [@stumpylog](https://github.com/stumpylog) ([#1387](https://github.com/paperless-ngx/paperless-ngx/pull/1387)) | ||||
| - Fix: saved view page parameter [@shamoon](https://github.com/shamoon) ([#1376](https://github.com/paperless-ngx/paperless-ngx/pull/1376)) | ||||
| - Fix: Correct browser unsaved changes warning [@shamoon](https://github.com/shamoon) ([#1369](https://github.com/paperless-ngx/paperless-ngx/pull/1369)) | ||||
| - Fix: correct date pasting with other formats [@shamoon](https://github.com/shamoon) ([#1370](https://github.com/paperless-ngx/paperless-ngx/pull/1370)) | ||||
| - Chore: use pre-commit in the Ci workflow [@stumpylog](https://github.com/stumpylog) ([#1362](https://github.com/paperless-ngx/paperless-ngx/pull/1362)) | ||||
| - Bugfix: Chain exceptions during exception handling [@stumpylog](https://github.com/stumpylog) ([#1354](https://github.com/paperless-ngx/paperless-ngx/pull/1354)) | ||||
| - Bump watchfiles from 0.15.0 to 0.16.1 [@dependabot](https://github.com/dependabot) ([#1285](https://github.com/paperless-ngx/paperless-ngx/pull/1285)) | ||||
| - Bump cypress from 10.3.0 to 10.3.1 in /src-ui [@dependabot](https://github.com/dependabot) ([#1342](https://github.com/paperless-ngx/paperless-ngx/pull/1342)) | ||||
| - Bump ngx-color from 7.3.3 to 8.0.1 in /src-ui [@dependabot](https://github.com/dependabot) ([#1343](https://github.com/paperless-ngx/paperless-ngx/pull/1343)) | ||||
| - Bump [@<!---->angular/cli from 14.0.4 to 14.1.0 in /src-ui @dependabot](https://github.com/<!---->angular/cli from 14.0.4 to 14.1.0 in /src-ui @dependabot) ([#1330](https://github.com/paperless-ngx/paperless-ngx/pull/1330)) | ||||
| - Bump [@<!---->types/node from 18.0.0 to 18.6.3 in /src-ui @dependabot](https://github.com/<!---->types/node from 18.0.0 to 18.6.3 in /src-ui @dependabot) ([#1341](https://github.com/paperless-ngx/paperless-ngx/pull/1341)) | ||||
| - Bump jest-preset-angular from 12.1.0 to 12.2.0 in /src-ui [@dependabot](https://github.com/dependabot) ([#1340](https://github.com/paperless-ngx/paperless-ngx/pull/1340)) | ||||
| - Bump concurrently from 7.2.2 to 7.3.0 in /src-ui [@dependabot](https://github.com/dependabot) ([#1326](https://github.com/paperless-ngx/paperless-ngx/pull/1326)) | ||||
| - Bump ng2-pdf-viewer from 9.0.0 to 9.1.0 in /src-ui [@dependabot](https://github.com/dependabot) ([#1337](https://github.com/paperless-ngx/paperless-ngx/pull/1337)) | ||||
| - Bump jest-environment-jsdom from 28.1.2 to 28.1.3 in /src-ui [@dependabot](https://github.com/dependabot) ([#1336](https://github.com/paperless-ngx/paperless-ngx/pull/1336)) | ||||
| - Bump ngx-file-drop from 13.0.0 to 14.0.1 in /src-ui [@dependabot](https://github.com/dependabot) ([#1331](https://github.com/paperless-ngx/paperless-ngx/pull/1331)) | ||||
| - Bump jest and [@<!---->types/jest in /src-ui @dependabot](https://github.com/<!---->types/jest in /src-ui @dependabot) ([#1333](https://github.com/paperless-ngx/paperless-ngx/pull/1333)) | ||||
| - Bump bootstrap from 5.1.3 to 5.2.0 in /src-ui [@dependabot](https://github.com/dependabot) ([#1327](https://github.com/paperless-ngx/paperless-ngx/pull/1327)) | ||||
| - Bump typescript from 4.6.4 to 4.7.4 in /src-ui [@dependabot](https://github.com/dependabot) ([#1324](https://github.com/paperless-ngx/paperless-ngx/pull/1324)) | ||||
| - Bump ts-node from 10.8.1 to 10.9.1 in /src-ui [@dependabot](https://github.com/dependabot) ([#1325](https://github.com/paperless-ngx/paperless-ngx/pull/1325)) | ||||
| - Bump rxjs from 7.5.5 to 7.5.6 in /src-ui [@dependabot](https://github.com/dependabot) ([#1323](https://github.com/paperless-ngx/paperless-ngx/pull/1323)) | ||||
| - Fix: missing tooltip translation \& filter editor wrapping [@shamoon](https://github.com/shamoon) ([#1305](https://github.com/paperless-ngx/paperless-ngx/pull/1305)) | ||||
| - Feature: Remove requirements.txt and use pipenv everywhere [@stumpylog](https://github.com/stumpylog) ([#1316](https://github.com/paperless-ngx/paperless-ngx/pull/1316)) | ||||
| - Bugfix: Interaction between barcode and directories as tags [@stumpylog](https://github.com/stumpylog) ([#1303](https://github.com/paperless-ngx/paperless-ngx/pull/1303)) | ||||
|  | ||||
| ## paperless-ngx 1.8.0 | ||||
|  | ||||
| ### Features | ||||
|   | ||||
| @@ -908,18 +908,9 @@ 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. | ||||
|     .. note:: | ||||
|  | ||||
|     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. | ||||
|             This setting was deprecated in favor of a frontend setting after v1.9.2. A one-time | ||||
|             migration is performed for users who have this setting set. This setting is always | ||||
|             ignored if the corresponding frontend setting has been set. | ||||
|   | ||||
| @@ -1 +1 @@ | ||||
| myst-parser==0.17.2 | ||||
| myst-parser==0.18.1 | ||||
|   | ||||
							
								
								
									
										743
									
								
								src-ui/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										743
									
								
								src-ui/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -13,48 +13,48 @@ | ||||
|   }, | ||||
|   "private": true, | ||||
|   "dependencies": { | ||||
|     "@angular/common": "~14.2.0", | ||||
|     "@angular/compiler": "~14.2.0", | ||||
|     "@angular/core": "~14.2.0", | ||||
|     "@angular/forms": "~14.2.0", | ||||
|     "@angular/localize": "~14.2.0", | ||||
|     "@angular/platform-browser": "~14.2.0", | ||||
|     "@angular/platform-browser-dynamic": "~14.2.0", | ||||
|     "@angular/router": "~14.2.0", | ||||
|     "@angular/common": "~14.2.4", | ||||
|     "@angular/compiler": "~14.2.4", | ||||
|     "@angular/core": "~14.2.4", | ||||
|     "@angular/forms": "~14.2.4", | ||||
|     "@angular/localize": "~14.2.4", | ||||
|     "@angular/platform-browser": "~14.2.4", | ||||
|     "@angular/platform-browser-dynamic": "~14.2.4", | ||||
|     "@angular/router": "~14.2.4", | ||||
|     "@ng-bootstrap/ng-bootstrap": "^13.0.0", | ||||
|     "@ng-select/ng-select": "^9.0.2", | ||||
|     "@ngneat/dirty-check-forms": "^3.0.2", | ||||
|     "@popperjs/core": "^2.11.6", | ||||
|     "bootstrap": "^5.2.0", | ||||
|     "bootstrap": "^5.2.1", | ||||
|     "file-saver": "^2.0.5", | ||||
|     "ng2-pdf-viewer": "^9.1.0", | ||||
|     "ngx-color": "^8.0.2", | ||||
|     "ng2-pdf-viewer": "^9.1.2", | ||||
|     "ngx-color": "^8.0.3", | ||||
|     "ngx-cookie-service": "^14.0.1", | ||||
|     "ngx-file-drop": "^14.0.1", | ||||
|     "rxjs": "~7.5.6", | ||||
|     "rxjs": "~7.5.7", | ||||
|     "tslib": "^2.3.1", | ||||
|     "uuid": "^8.3.1", | ||||
|     "uuid": "^9.0.0", | ||||
|     "zone.js": "~0.11.8" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@angular-builders/jest": "14.0.1", | ||||
|     "@angular-devkit/build-angular": "~14.2.1", | ||||
|     "@angular/cli": "~14.2.1", | ||||
|     "@angular/compiler-cli": "~14.2.0", | ||||
|     "@angular-devkit/build-angular": "~14.2.4", | ||||
|     "@angular/cli": "~14.2.4", | ||||
|     "@angular/compiler-cli": "~14.2.4", | ||||
|     "@types/jest": "28.1.6", | ||||
|     "@types/node": "^18.7.14", | ||||
|     "@types/node": "^18.7.23", | ||||
|     "codelyzer": "^6.0.2", | ||||
|     "concurrently": "7.3.0", | ||||
|     "concurrently": "7.4.0", | ||||
|     "jest": "28.1.3", | ||||
|     "jest-environment-jsdom": "^29.0.1", | ||||
|     "jest-environment-jsdom": "^29.1.2", | ||||
|     "jest-preset-angular": "^12.2.2", | ||||
|     "ts-node": "~10.9.1", | ||||
|     "tslint": "~6.1.3", | ||||
|     "typescript": "~4.7.4", | ||||
|     "typescript": "~4.8.4", | ||||
|     "wait-on": "~6.0.1" | ||||
|   }, | ||||
|   "optionalDependencies": { | ||||
|     "@cypress/schematic": "^2.1.1", | ||||
|     "cypress": "~10.7.0" | ||||
|     "cypress": "~10.9.0" | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -207,14 +207,25 @@ | ||||
|           <li class="nav-item mt-2" [class.visually-hidden]="slimSidebarEnabled"> | ||||
|             <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"> | ||||
|               <div *ngIf="!settingsService.updateCheckingIsSet || appRemoteVersion" class="version-check"> | ||||
|                 <ng-template #updateAvailablePopContent> | ||||
|                   <span class="small">Paperless-ngx {{ 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> | ||||
|                   <p class="small mb-2"> | ||||
|                     <ng-container i18n>Paperless-ngx can automatically check for updates</ng-container> | ||||
|                   </p> | ||||
|                   <div class="btn-group btn-group-xs flex-fill w-100"> | ||||
|                     <button class="btn btn-outline-primary" (click)="setUpdateChecking(true)">Enable</button> | ||||
|                     <button class="btn btn-outline-secondary" (click)="setUpdateChecking(false)">Disable</button> | ||||
|                   </div> | ||||
|                   <p class="small mb-0 mt-2"> | ||||
|                     <a class="small text-decoration-none fst-italic" routerLink="/settings" fragment="update-checking" i18n> | ||||
|                       How does this work? | ||||
|                     </a> | ||||
|                   </p> | ||||
|                 </ng-template> | ||||
|                 <ng-container *ngIf="appRemoteVersion.feature_is_set; else updateCheckNotSet"> | ||||
|                 <ng-container *ngIf="settingsService.updateCheckingIsSet; 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"> | ||||
| @@ -224,8 +235,8 @@ | ||||
|                   </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"> | ||||
|                   <a class="small text-decoration-none" routerLink="/settings" fragment="update-checking" | ||||
|                   [ngbPopover]="updateCheckingNotEnabledPopContent" popoverClass="shadow" triggers="mouseenter" 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> | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import { Component, HostListener } from '@angular/core' | ||||
| import { Component, HostListener, OnInit } from '@angular/core' | ||||
| import { FormControl } from '@angular/forms' | ||||
| import { ActivatedRoute, Router } from '@angular/router' | ||||
| import { from, Observable } from 'rxjs' | ||||
| @@ -32,7 +32,7 @@ import { ToastService } from 'src/app/services/toast.service' | ||||
|   templateUrl: './app-frame.component.html', | ||||
|   styleUrls: ['./app-frame.component.scss'], | ||||
| }) | ||||
| export class AppFrameComponent implements ComponentCanDeactivate { | ||||
| export class AppFrameComponent implements OnInit, ComponentCanDeactivate { | ||||
|   constructor( | ||||
|     public router: Router, | ||||
|     private activatedRoute: ActivatedRoute, | ||||
| @@ -43,14 +43,14 @@ export class AppFrameComponent implements ComponentCanDeactivate { | ||||
|     private list: DocumentListViewService, | ||||
|     public settingsService: SettingsService, | ||||
|     public tasksService: TasksService, | ||||
|     private toastService: ToastService | ||||
|   ) { | ||||
|     this.remoteVersionService | ||||
|       .checkForUpdates() | ||||
|       .subscribe((appRemoteVersion: AppRemoteVersion) => { | ||||
|         this.appRemoteVersion = appRemoteVersion | ||||
|       }) | ||||
|     tasksService.reload() | ||||
|     private readonly toastService: ToastService | ||||
|   ) {} | ||||
|  | ||||
|   ngOnInit(): void { | ||||
|     if (this.settingsService.get(SETTINGS_KEYS.UPDATE_CHECKING_ENABLED)) { | ||||
|       this.checkForUpdates() | ||||
|     } | ||||
|     this.tasksService.reload() | ||||
|   } | ||||
|  | ||||
|   versionString = `${environment.appTitle} ${environment.version}` | ||||
| @@ -182,4 +182,30 @@ export class AppFrameComponent implements ComponentCanDeactivate { | ||||
|         } | ||||
|       }) | ||||
|   } | ||||
|  | ||||
|   private checkForUpdates() { | ||||
|     this.remoteVersionService | ||||
|       .checkForUpdates() | ||||
|       .subscribe((appRemoteVersion: AppRemoteVersion) => { | ||||
|         this.appRemoteVersion = appRemoteVersion | ||||
|       }) | ||||
|   } | ||||
|  | ||||
|   setUpdateChecking(enable: boolean) { | ||||
|     this.settingsService.set(SETTINGS_KEYS.UPDATE_CHECKING_ENABLED, enable) | ||||
|     this.settingsService | ||||
|       .storeSettings() | ||||
|       .pipe(first()) | ||||
|       .subscribe({ | ||||
|         error: (error) => { | ||||
|           this.toastService.showError( | ||||
|             $localize`An error occurred while saving update checking settings.` | ||||
|           ) | ||||
|           console.log(error) | ||||
|         }, | ||||
|       }) | ||||
|     if (enable) { | ||||
|       this.checkForUpdates() | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -2,6 +2,7 @@ import { Component } from '@angular/core' | ||||
| import { FormControl, FormGroup } from '@angular/forms' | ||||
| import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component' | ||||
| import { DEFAULT_MATCHING_ALGORITHM } from 'src/app/data/matching-model' | ||||
| import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent' | ||||
| import { CorrespondentService } from 'src/app/services/rest/correspondent.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| @@ -31,7 +32,7 @@ export class CorrespondentEditDialogComponent extends EditDialogComponent<Paperl | ||||
|   getForm(): FormGroup { | ||||
|     return new FormGroup({ | ||||
|       name: new FormControl(''), | ||||
|       matching_algorithm: new FormControl(1), | ||||
|       matching_algorithm: new FormControl(DEFAULT_MATCHING_ALGORITHM), | ||||
|       match: new FormControl(''), | ||||
|       is_insensitive: new FormControl(true), | ||||
|     }) | ||||
|   | ||||
| @@ -2,6 +2,7 @@ import { Component } from '@angular/core' | ||||
| import { FormControl, FormGroup } from '@angular/forms' | ||||
| import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component' | ||||
| import { DEFAULT_MATCHING_ALGORITHM } from 'src/app/data/matching-model' | ||||
| import { PaperlessDocumentType } from 'src/app/data/paperless-document-type' | ||||
| import { DocumentTypeService } from 'src/app/services/rest/document-type.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| @@ -31,7 +32,7 @@ export class DocumentTypeEditDialogComponent extends EditDialogComponent<Paperle | ||||
|   getForm(): FormGroup { | ||||
|     return new FormGroup({ | ||||
|       name: new FormControl(''), | ||||
|       matching_algorithm: new FormControl(1), | ||||
|       matching_algorithm: new FormControl(DEFAULT_MATCHING_ALGORITHM), | ||||
|       match: new FormControl(''), | ||||
|       is_insensitive: new FormControl(true), | ||||
|     }) | ||||
|   | ||||
| @@ -2,6 +2,7 @@ import { Component } from '@angular/core' | ||||
| import { FormControl, FormGroup } from '@angular/forms' | ||||
| import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component' | ||||
| import { DEFAULT_MATCHING_ALGORITHM } from 'src/app/data/matching-model' | ||||
| import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path' | ||||
| import { StoragePathService } from 'src/app/services/rest/storage-path.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| @@ -42,7 +43,7 @@ export class StoragePathEditDialogComponent extends EditDialogComponent<Paperles | ||||
|     return new FormGroup({ | ||||
|       name: new FormControl(''), | ||||
|       path: new FormControl(''), | ||||
|       matching_algorithm: new FormControl(1), | ||||
|       matching_algorithm: new FormControl(DEFAULT_MATCHING_ALGORITHM), | ||||
|       match: new FormControl(''), | ||||
|       is_insensitive: new FormControl(true), | ||||
|     }) | ||||
|   | ||||
| @@ -6,6 +6,7 @@ import { PaperlessTag } from 'src/app/data/paperless-tag' | ||||
| import { TagService } from 'src/app/services/rest/tag.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { randomColor } from 'src/app/utils/color' | ||||
| import { DEFAULT_MATCHING_ALGORITHM } from 'src/app/data/matching-model' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-tag-edit-dialog', | ||||
| @@ -34,7 +35,7 @@ export class TagEditDialogComponent extends EditDialogComponent<PaperlessTag> { | ||||
|       name: new FormControl(''), | ||||
|       color: new FormControl(randomColor()), | ||||
|       is_inbox_tag: new FormControl(false), | ||||
|       matching_algorithm: new FormControl(1), | ||||
|       matching_algorithm: new FormControl(DEFAULT_MATCHING_ALGORITHM), | ||||
|       match: new FormControl(''), | ||||
|       is_insensitive: new FormControl(true), | ||||
|     }) | ||||
|   | ||||
| @@ -17,25 +17,6 @@ | ||||
|   } | ||||
| } | ||||
|  | ||||
| .btn-group-xs { | ||||
|   > .btn { | ||||
|     padding: 0.2rem 0.25rem; | ||||
|     font-size: 0.675rem; | ||||
|     line-height: 1.2; | ||||
|     border-radius: 0.15rem; | ||||
|   } | ||||
|  | ||||
|   > .btn:not(:first-child) { | ||||
|     border-top-left-radius: 0; | ||||
|     border-bottom-left-radius: 0; | ||||
|   } | ||||
|  | ||||
|   > .btn:not(:last-child) { | ||||
|     border-top-right-radius: 0; | ||||
|     border-bottom-right-radius: 0; | ||||
|   } | ||||
| } | ||||
|  | ||||
| .btn-group > label.disabled { | ||||
|   filter: brightness(0.5); | ||||
|  | ||||
|   | ||||
| @@ -64,9 +64,9 @@ | ||||
|      </div> | ||||
|    </div> | ||||
|    <div class="w-100 d-xxl-none"></div> | ||||
|    <div class="col col-xl-auto ps-0"> | ||||
|    <div class="col col-xl-auto ps-xxl-0"> | ||||
|      <button class="btn btn-link btn-sm px-0" [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"> | ||||
|        <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-x me-1 ms-n1" 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"/> | ||||
|        </svg><ng-container i18n>Reset filters</ng-container> | ||||
|      </button> | ||||
|   | ||||
| @@ -127,6 +127,21 @@ | ||||
|           </div> | ||||
|         </div> | ||||
|  | ||||
|         <h4 class="mt-4" id="update-checking" i18n>Update checking</h4> | ||||
|  | ||||
|         <div class="row mb-3"> | ||||
|           <div class="offset-md-3 col"> | ||||
|             <p i18n> | ||||
|               Update checking works by pinging the the public <a href="https://api.github.com/repos/paperless-ngx/paperless-ngx/releases/latest" target="_blank" rel="noopener noreferrer">Github API</a> for the latest release to determine whether a new version is available.<br/> | ||||
|               Actual updating of the app must still be performed manually. | ||||
|             </p> | ||||
|             <p i18n> | ||||
|               <em>No tracking data is collected by the app in any way.</em> | ||||
|             </p> | ||||
|             <app-input-check i18n-title title="Enable update checking" formControlName="updateCheckingEnabled" i18n-hint hint="Note that for users of thirdy-party containers e.g. linuxserver.io this notification may be 'ahead' of the current third-party release."></app-input-check> | ||||
|           </div> | ||||
|         </div> | ||||
|  | ||||
|         <h4 class="mt-4" i18n>Bulk editing</h4> | ||||
|  | ||||
|         <div class="row mb-3"> | ||||
| @@ -205,5 +220,5 @@ | ||||
|  | ||||
|   <div [ngbNavOutlet]="nav" class="border-start border-end border-bottom p-3 mb-3 shadow-sm"></div> | ||||
|  | ||||
|   <button type="submit" class="btn btn-primary" [disabled]="!(isDirty$ | async)" i18n>Save</button> | ||||
|   <button type="submit" class="btn btn-primary mb-2" [disabled]="!(isDirty$ | async)" i18n>Save</button> | ||||
| </form> | ||||
|   | ||||
| @@ -1,4 +1,11 @@ | ||||
| import { Component, Inject, LOCALE_ID, OnInit, OnDestroy } from '@angular/core' | ||||
| import { | ||||
|   Component, | ||||
|   Inject, | ||||
|   LOCALE_ID, | ||||
|   OnInit, | ||||
|   OnDestroy, | ||||
|   AfterViewInit, | ||||
| } from '@angular/core' | ||||
| import { FormControl, FormGroup } from '@angular/forms' | ||||
| import { PaperlessSavedView } from 'src/app/data/paperless-saved-view' | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service' | ||||
| @@ -9,8 +16,18 @@ import { | ||||
| } from 'src/app/services/settings.service' | ||||
| import { Toast, ToastService } from 'src/app/services/toast.service' | ||||
| import { dirtyCheck, DirtyComponent } from '@ngneat/dirty-check-forms' | ||||
| import { Observable, Subscription, BehaviorSubject, first } from 'rxjs' | ||||
| import { | ||||
|   Observable, | ||||
|   Subscription, | ||||
|   BehaviorSubject, | ||||
|   first, | ||||
|   tap, | ||||
|   takeUntil, | ||||
|   Subject, | ||||
| } from 'rxjs' | ||||
| import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings' | ||||
| import { ActivatedRoute } from '@angular/router' | ||||
| import { ViewportScroller } from '@angular/common' | ||||
| import { ForwardRefHandling } from '@angular/compiler' | ||||
|  | ||||
| @Component({ | ||||
| @@ -18,7 +35,9 @@ import { ForwardRefHandling } from '@angular/compiler' | ||||
|   templateUrl: './settings.component.html', | ||||
|   styleUrls: ['./settings.component.scss'], | ||||
| }) | ||||
| export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent { | ||||
| export class SettingsComponent | ||||
|   implements OnInit, AfterViewInit, OnDestroy, DirtyComponent | ||||
| { | ||||
|   savedViewGroup = new FormGroup({}) | ||||
|  | ||||
|   settingsForm = new FormGroup({ | ||||
| @@ -40,6 +59,7 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent { | ||||
|     notificationsConsumerFailed: new FormControl(null), | ||||
|     notificationsConsumerSuppressOnDashboard: new FormControl(null), | ||||
|     commentsEnabled: new FormControl(null), | ||||
|     updateCheckingEnabled: new FormControl(null), | ||||
|   }) | ||||
|  | ||||
|   savedViews: PaperlessSavedView[] | ||||
| @@ -47,7 +67,9 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent { | ||||
|   store: BehaviorSubject<any> | ||||
|   storeSub: Subscription | ||||
|   isDirty$: Observable<boolean> | ||||
|   isDirty: Boolean = false | ||||
|   isDirty: boolean = false | ||||
|   unsubscribeNotifier: Subject<any> = new Subject() | ||||
|   savePending: boolean = false | ||||
|  | ||||
|   get computedDateLocale(): string { | ||||
|     return ( | ||||
| @@ -57,29 +79,28 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent { | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   get displayLanguageIsDirty(): boolean { | ||||
|     return ( | ||||
|       this.settingsForm.get('displayLanguage').value != | ||||
|       this.store?.getValue()['displayLanguage'] | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   constructor( | ||||
|     public savedViewService: SavedViewService, | ||||
|     private documentListViewService: DocumentListViewService, | ||||
|     private toastService: ToastService, | ||||
|     private settings: SettingsService, | ||||
|     @Inject(LOCALE_ID) public currentLocale: string | ||||
|     @Inject(LOCALE_ID) public currentLocale: string, | ||||
|     private viewportScroller: ViewportScroller, | ||||
|     private activatedRoute: ActivatedRoute | ||||
|   ) { | ||||
|     this.settings.changed.subscribe({ | ||||
|       next: () => { | ||||
|         this.settingsForm.patchValue(this.getCurrentSettings(), { | ||||
|           emitEvent: false, | ||||
|         }) | ||||
|       }, | ||||
|     this.settings.settingsSaved.subscribe(() => { | ||||
|       if (!this.savePending) this.initialize() | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   ngAfterViewInit(): void { | ||||
|     if (this.activatedRoute.snapshot.fragment) { | ||||
|       this.viewportScroller.scrollToAnchor( | ||||
|         this.activatedRoute.snapshot.fragment | ||||
|       ) | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private getCurrentSettings() { | ||||
|     return { | ||||
|       bulkEditConfirmationDialogs: this.settings.get( | ||||
| @@ -91,7 +112,6 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent { | ||||
|       documentListItemPerPage: this.settings.get( | ||||
|         SETTINGS_KEYS.DOCUMENT_LIST_SIZE | ||||
|       ), | ||||
|       slimSidebarEnabled: this.settings.get(SETTINGS_KEYS.SLIM_SIDEBAR), | ||||
|       darkModeUseSystem: this.settings.get(SETTINGS_KEYS.DARK_MODE_USE_SYSTEM), | ||||
|       darkModeEnabled: this.settings.get(SETTINGS_KEYS.DARK_MODE_ENABLED), | ||||
|       darkModeInvertThumbs: this.settings.get( | ||||
| @@ -118,12 +138,22 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent { | ||||
|         SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD | ||||
|       ), | ||||
|       commentsEnabled: this.settings.get(SETTINGS_KEYS.COMMENTS_ENABLED), | ||||
|       updateCheckingEnabled: this.settings.get( | ||||
|         SETTINGS_KEYS.UPDATE_CHECKING_ENABLED | ||||
|       ), | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   ngOnInit() { | ||||
|     this.savedViewService.listAll().subscribe((r) => { | ||||
|       this.savedViews = r.results | ||||
|       this.initialize() | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   initialize() { | ||||
|     this.unsubscribeNotifier.next(true) | ||||
|  | ||||
|     let storeData = this.getCurrentSettings() | ||||
|  | ||||
|     for (let view of this.savedViews) { | ||||
| @@ -154,19 +184,22 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent { | ||||
|     this.isDirty$ = dirtyCheck(this.settingsForm, this.store.asObservable()) | ||||
|  | ||||
|     // Record dirty in case we need to 'undo' appearance settings if not saved on close | ||||
|       this.isDirty$.subscribe((dirty) => { | ||||
|     this.isDirty$ | ||||
|       .pipe(takeUntil(this.unsubscribeNotifier)) | ||||
|       .subscribe((dirty) => { | ||||
|         this.isDirty = dirty | ||||
|       }) | ||||
|  | ||||
|     // "Live" visual changes prior to save | ||||
|       this.settingsForm.valueChanges.subscribe(() => { | ||||
|     this.settingsForm.valueChanges | ||||
|       .pipe(takeUntil(this.unsubscribeNotifier)) | ||||
|       .subscribe(() => { | ||||
|         this.settings.updateAppearanceSettings( | ||||
|           this.settingsForm.get('darkModeUseSystem').value, | ||||
|           this.settingsForm.get('darkModeEnabled').value, | ||||
|           this.settingsForm.get('themeColor').value | ||||
|         ) | ||||
|       }) | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   ngOnDestroy() { | ||||
| @@ -185,7 +218,14 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent { | ||||
|   } | ||||
|  | ||||
|   private saveLocalSettings() { | ||||
|     const reloadRequired = this.displayLanguageIsDirty // just this one, for now | ||||
|     this.savePending = true | ||||
|     const reloadRequired = | ||||
|       this.settingsForm.value.displayLanguage != | ||||
|         this.store?.getValue()['displayLanguage'] || // displayLanguage is dirty | ||||
|       (this.settingsForm.value.updateCheckingEnabled != | ||||
|         this.store?.getValue()['updateCheckingEnabled'] && | ||||
|         this.settingsForm.value.updateCheckingEnabled) // update checking was turned on | ||||
|  | ||||
|     this.settings.set( | ||||
|       SETTINGS_KEYS.BULK_EDIT_APPLY_ON_CLOSE, | ||||
|       this.settingsForm.value.bulkEditApplyOnClose | ||||
| @@ -250,10 +290,15 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent { | ||||
|       SETTINGS_KEYS.COMMENTS_ENABLED, | ||||
|       this.settingsForm.value.commentsEnabled | ||||
|     ) | ||||
|     this.settings.set( | ||||
|       SETTINGS_KEYS.UPDATE_CHECKING_ENABLED, | ||||
|       this.settingsForm.value.updateCheckingEnabled | ||||
|     ) | ||||
|     this.settings.setLanguage(this.settingsForm.value.displayLanguage) | ||||
|     this.settings | ||||
|       .storeSettings() | ||||
|       .pipe(first()) | ||||
|       .pipe(tap(() => (this.savePending = false))) | ||||
|       .subscribe({ | ||||
|         next: () => { | ||||
|           this.store.next(this.settingsForm.value) | ||||
|   | ||||
| @@ -6,6 +6,7 @@ export const MATCH_LITERAL = 3 | ||||
| export const MATCH_REGEX = 4 | ||||
| export const MATCH_FUZZY = 5 | ||||
| export const MATCH_AUTO = 6 | ||||
| export const DEFAULT_MATCHING_ALGORITHM = MATCH_AUTO | ||||
|  | ||||
| export const MATCHING_ALGORITHMS = [ | ||||
|   { | ||||
|   | ||||
| @@ -29,8 +29,6 @@ export interface PaperlessDocument extends ObjectWithId { | ||||
|  | ||||
|   content?: string | ||||
|  | ||||
|   file_type?: string | ||||
|  | ||||
|   tags$?: Observable<PaperlessTag[]> | ||||
|  | ||||
|   tags?: number[] | ||||
| @@ -47,7 +45,7 @@ export interface PaperlessDocument extends ObjectWithId { | ||||
|  | ||||
|   added?: Date | ||||
|  | ||||
|   file_name?: string | ||||
|   original_file_name?: string | ||||
|  | ||||
|   download_url?: string | ||||
|  | ||||
|   | ||||
| @@ -38,6 +38,9 @@ export const SETTINGS_KEYS = { | ||||
|     'general-settings:notifications:consumer-suppress-on-dashboard', | ||||
|   COMMENTS_ENABLED: 'general-settings:comments-enabled', | ||||
|   SLIM_SIDEBAR: 'general-settings:slim-sidebar', | ||||
|   UPDATE_CHECKING_ENABLED: 'general-settings:update-checking:enabled', | ||||
|   UPDATE_CHECKING_BACKEND_SETTING: | ||||
|     'general-settings:update-checking:backend-setting', | ||||
| } | ||||
|  | ||||
| export const SETTINGS: PaperlessUiSetting[] = [ | ||||
| @@ -126,4 +129,14 @@ export const SETTINGS: PaperlessUiSetting[] = [ | ||||
|     type: 'boolean', | ||||
|     default: true, | ||||
|   }, | ||||
|   { | ||||
|     key: SETTINGS_KEYS.UPDATE_CHECKING_ENABLED, | ||||
|     type: 'boolean', | ||||
|     default: false, | ||||
|   }, | ||||
|   { | ||||
|     key: SETTINGS_KEYS.UPDATE_CHECKING_BACKEND_SETTING, | ||||
|     type: 'string', | ||||
|     default: '', | ||||
|   }, | ||||
| ] | ||||
|   | ||||
| @@ -6,7 +6,6 @@ import { environment } from 'src/environments/environment' | ||||
| export interface AppRemoteVersion { | ||||
|   version: string | ||||
|   update_available: boolean | ||||
|   feature_is_set: boolean | ||||
| } | ||||
|  | ||||
| @Injectable({ | ||||
|   | ||||
| @@ -47,7 +47,7 @@ export class SettingsService { | ||||
|  | ||||
|   public displayName: string | ||||
|  | ||||
|   public changed = new EventEmitter() | ||||
|   public settingsSaved: EventEmitter<any> = new EventEmitter() | ||||
|  | ||||
|   constructor( | ||||
|     rendererFactory: RendererFactory2, | ||||
| @@ -316,13 +316,7 @@ export class SettingsService { | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   get(key: string): any { | ||||
|     let setting = SETTINGS.find((s) => s.key == key) | ||||
|  | ||||
|     if (!setting) { | ||||
|       return null | ||||
|     } | ||||
|  | ||||
|   private getSettingRawValue(key: string): any { | ||||
|     let value = null | ||||
|     // parse key:key:key into nested object | ||||
|     const keys = key.replace('general-settings:', '').split(':') | ||||
| @@ -333,6 +327,17 @@ export class SettingsService { | ||||
|       if (index == keys.length - 1) value = settingObj[keyPart] | ||||
|       else settingObj = settingObj[keyPart] | ||||
|     }) | ||||
|     return value | ||||
|   } | ||||
|  | ||||
|   get(key: string): any { | ||||
|     let setting = SETTINGS.find((s) => s.key == key) | ||||
|  | ||||
|     if (!setting) { | ||||
|       return null | ||||
|     } | ||||
|  | ||||
|     let value = this.getSettingRawValue(key) | ||||
|  | ||||
|     if (value != null) { | ||||
|       switch (setting.type) { | ||||
| @@ -362,10 +367,19 @@ export class SettingsService { | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   private settingIsSet(key: string): boolean { | ||||
|     let value = this.getSettingRawValue(key) | ||||
|     return value != null | ||||
|   } | ||||
|  | ||||
|   storeSettings(): Observable<any> { | ||||
|     return this.http | ||||
|       .post(this.baseUrl, { settings: this.settings }) | ||||
|       .pipe(tap((result) => this.changed.emit(!!result.success))) | ||||
|     return this.http.post(this.baseUrl, { settings: this.settings }).pipe( | ||||
|       tap((results) => { | ||||
|         if (results.success) { | ||||
|           this.settingsSaved.emit() | ||||
|         } | ||||
|       }) | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   maybeMigrateSettings() { | ||||
| @@ -405,5 +419,31 @@ export class SettingsService { | ||||
|           }, | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     if ( | ||||
|       !this.settingIsSet(SETTINGS_KEYS.UPDATE_CHECKING_ENABLED) && | ||||
|       this.get(SETTINGS_KEYS.UPDATE_CHECKING_BACKEND_SETTING) != 'default' | ||||
|     ) { | ||||
|       this.set( | ||||
|         SETTINGS_KEYS.UPDATE_CHECKING_ENABLED, | ||||
|         this.get(SETTINGS_KEYS.UPDATE_CHECKING_BACKEND_SETTING).toString() === | ||||
|           'true' | ||||
|       ) | ||||
|  | ||||
|       this.storeSettings() | ||||
|         .pipe(first()) | ||||
|         .subscribe({ | ||||
|           error: (e) => { | ||||
|             this.toastService.showError( | ||||
|               'Error migrating update checking setting' | ||||
|             ) | ||||
|             console.log(e) | ||||
|           }, | ||||
|         }) | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   get updateCheckingIsSet(): boolean { | ||||
|     return this.settingIsSet(SETTINGS_KEYS.UPDATE_CHECKING_ENABLED) | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -5,7 +5,7 @@ export const environment = { | ||||
|   apiBaseUrl: document.baseURI + 'api/', | ||||
|   apiVersion: '2', | ||||
|   appTitle: 'Paperless-ngx', | ||||
|   version: '1.9.0-dev', | ||||
|   version: '1.9.2-dev', | ||||
|   webSocketHost: window.location.host, | ||||
|   webSocketProtocol: window.location.protocol == 'https:' ? 'wss:' : 'ws:', | ||||
|   webSocketBaseUrl: base_url.pathname + 'ws/', | ||||
|   | ||||
| @@ -540,6 +540,25 @@ a.badge { | ||||
|     border-color: var(--bs-primary); | ||||
| } | ||||
|  | ||||
| .btn-group-xs { | ||||
|   > .btn { | ||||
|     padding: 0.2rem 0.25rem; | ||||
|     font-size: 0.675rem; | ||||
|     line-height: 1.2; | ||||
|     border-radius: 0.15rem; | ||||
|   } | ||||
|  | ||||
|   > .btn:not(:first-child) { | ||||
|     border-top-left-radius: 0; | ||||
|     border-bottom-left-radius: 0; | ||||
|   } | ||||
|  | ||||
|   > .btn:not(:last-child) { | ||||
|     border-top-right-radius: 0; | ||||
|     border-bottom-right-radius: 0; | ||||
|   } | ||||
| } | ||||
|  | ||||
| code { | ||||
|   color: var(--pngx-body-color-accent) | ||||
| } | ||||
|   | ||||
| @@ -111,14 +111,16 @@ class Consumer(LoggingMixin): | ||||
|     def pre_check_duplicate(self): | ||||
|         with open(self.path, "rb") as f: | ||||
|             checksum = hashlib.md5(f.read()).hexdigest() | ||||
|         if Document.objects.filter( | ||||
|         existing_doc = Document.objects.filter( | ||||
|             Q(checksum=checksum) | Q(archive_checksum=checksum), | ||||
|         ).exists(): | ||||
|         ) | ||||
|         if existing_doc.exists(): | ||||
|             if settings.CONSUMER_DELETE_DUPLICATES: | ||||
|                 os.unlink(self.path) | ||||
|             self._fail( | ||||
|                 MESSAGE_DOCUMENT_ALREADY_EXISTS, | ||||
|                 f"Not consuming {self.filename}: It is a duplicate.", | ||||
|                 f"Not consuming {self.filename}: It is a duplicate of" | ||||
|                 f" {existing_doc.get().title} (#{existing_doc.get().pk})", | ||||
|             ) | ||||
|  | ||||
|     def pre_check_directories(self): | ||||
|   | ||||
| @@ -608,6 +608,15 @@ class UiSettingsViewSerializer(serializers.ModelSerializer): | ||||
|             "settings", | ||||
|         ] | ||||
|  | ||||
|     def validate_settings(self, settings): | ||||
|         # we never save update checking backend setting | ||||
|         if "update_checking" in settings: | ||||
|             try: | ||||
|                 settings["update_checking"].pop("backend_setting") | ||||
|             except KeyError: | ||||
|                 pass | ||||
|         return settings | ||||
|  | ||||
|     def create(self, validated_data): | ||||
|         ui_settings = UiSettings.objects.update_or_create( | ||||
|             user=validated_data.get("user"), | ||||
|   | ||||
| @@ -112,10 +112,22 @@ def consume_file( | ||||
|                         newname = f"{str(n)}_" + override_filename | ||||
|                     else: | ||||
|                         newname = None | ||||
|  | ||||
|                     # If the file is an upload, it's in the scratch directory | ||||
|                     # Move it to consume directory to be picked up | ||||
|                     # Otherwise, use the current parent to keep possible tags | ||||
|                     # from subdirectories | ||||
|                     try: | ||||
|                         # is_relative_to would be nicer, but new in 3.9 | ||||
|                         _ = path.relative_to(settings.SCRATCH_DIR) | ||||
|                         save_to_dir = settings.CONSUMPTION_DIR | ||||
|                     except ValueError: | ||||
|                         save_to_dir = path.parent | ||||
|  | ||||
|                     barcodes.save_to_dir( | ||||
|                         document, | ||||
|                         newname=newname, | ||||
|                         target_dir=path.parent, | ||||
|                         target_dir=save_to_dir, | ||||
|                     ) | ||||
|  | ||||
|                 # Delete the PDF file which was split | ||||
|   | ||||
| @@ -1581,7 +1581,11 @@ class TestApiUiSettings(DirectoriesMixin, APITestCase): | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|         self.assertDictEqual( | ||||
|             response.data["settings"], | ||||
|             {}, | ||||
|             { | ||||
|                 "update_checking": { | ||||
|                     "backend_setting": "default", | ||||
|                 }, | ||||
|             }, | ||||
|         ) | ||||
|  | ||||
|     def test_api_set_ui_settings(self): | ||||
| @@ -2542,38 +2546,6 @@ class TestApiRemoteVersion(DirectoriesMixin, APITestCase): | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|  | ||||
|     def test_remote_version_default(self): | ||||
|         response = self.client.get(self.ENDPOINT) | ||||
|  | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|         self.assertDictEqual( | ||||
|             response.data, | ||||
|             { | ||||
|                 "version": "0.0.0", | ||||
|                 "update_available": False, | ||||
|                 "feature_is_set": False, | ||||
|             }, | ||||
|         ) | ||||
|  | ||||
|     @override_settings( | ||||
|         ENABLE_UPDATE_CHECK=False, | ||||
|     ) | ||||
|     def test_remote_version_disabled(self): | ||||
|         response = self.client.get(self.ENDPOINT) | ||||
|  | ||||
|         self.assertEqual(response.status_code, 200) | ||||
|         self.assertDictEqual( | ||||
|             response.data, | ||||
|             { | ||||
|                 "version": "0.0.0", | ||||
|                 "update_available": False, | ||||
|                 "feature_is_set": True, | ||||
|             }, | ||||
|         ) | ||||
|  | ||||
|     @override_settings( | ||||
|         ENABLE_UPDATE_CHECK=True, | ||||
|     ) | ||||
|     @mock.patch("urllib.request.urlopen") | ||||
|     def test_remote_version_enabled_no_update_prefix(self, urlopen_mock): | ||||
|  | ||||
| @@ -2591,13 +2563,9 @@ class TestApiRemoteVersion(DirectoriesMixin, APITestCase): | ||||
|             { | ||||
|                 "version": "1.6.0", | ||||
|                 "update_available": False, | ||||
|                 "feature_is_set": True, | ||||
|             }, | ||||
|         ) | ||||
|  | ||||
|     @override_settings( | ||||
|         ENABLE_UPDATE_CHECK=True, | ||||
|     ) | ||||
|     @mock.patch("urllib.request.urlopen") | ||||
|     def test_remote_version_enabled_no_update_no_prefix(self, urlopen_mock): | ||||
|  | ||||
| @@ -2617,13 +2585,9 @@ class TestApiRemoteVersion(DirectoriesMixin, APITestCase): | ||||
|             { | ||||
|                 "version": version.__full_version_str__, | ||||
|                 "update_available": False, | ||||
|                 "feature_is_set": True, | ||||
|             }, | ||||
|         ) | ||||
|  | ||||
|     @override_settings( | ||||
|         ENABLE_UPDATE_CHECK=True, | ||||
|     ) | ||||
|     @mock.patch("urllib.request.urlopen") | ||||
|     def test_remote_version_enabled_update(self, urlopen_mock): | ||||
|  | ||||
| @@ -2650,13 +2614,9 @@ class TestApiRemoteVersion(DirectoriesMixin, APITestCase): | ||||
|             { | ||||
|                 "version": new_version_str, | ||||
|                 "update_available": True, | ||||
|                 "feature_is_set": True, | ||||
|             }, | ||||
|         ) | ||||
|  | ||||
|     @override_settings( | ||||
|         ENABLE_UPDATE_CHECK=True, | ||||
|     ) | ||||
|     @mock.patch("urllib.request.urlopen") | ||||
|     def test_remote_version_bad_json(self, urlopen_mock): | ||||
|  | ||||
| @@ -2674,13 +2634,9 @@ class TestApiRemoteVersion(DirectoriesMixin, APITestCase): | ||||
|             { | ||||
|                 "version": "0.0.0", | ||||
|                 "update_available": False, | ||||
|                 "feature_is_set": True, | ||||
|             }, | ||||
|         ) | ||||
|  | ||||
|     @override_settings( | ||||
|         ENABLE_UPDATE_CHECK=True, | ||||
|     ) | ||||
|     @mock.patch("urllib.request.urlopen") | ||||
|     def test_remote_version_exception(self, urlopen_mock): | ||||
|  | ||||
| @@ -2698,7 +2654,6 @@ class TestApiRemoteVersion(DirectoriesMixin, APITestCase): | ||||
|             { | ||||
|                 "version": "0.0.0", | ||||
|                 "update_available": False, | ||||
|                 "feature_is_set": True, | ||||
|             }, | ||||
|         ) | ||||
|  | ||||
|   | ||||
| @@ -261,6 +261,9 @@ class DocumentViewSet( | ||||
|             file_handle = doc.source_file | ||||
|             filename = doc.get_public_filename() | ||||
|             mime_type = doc.mime_type | ||||
|             # Support browser previewing csv files by using text mime type | ||||
|             if mime_type in {"application/csv", "text/csv"} and disposition == "inline": | ||||
|                 mime_type = "text/plain" | ||||
|  | ||||
|         if doc.storage_type == Document.STORAGE_TYPE_GPG: | ||||
|             file_handle = GnuPG.decrypted(file_handle) | ||||
| @@ -780,9 +783,6 @@ class RemoteVersionView(GenericAPIView): | ||||
|         remote_version = "0.0.0" | ||||
|         is_greater_than_current = False | ||||
|         current_version = packaging_version.parse(version.__full_version_str__) | ||||
|         # 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: | ||||
|             req = urllib.request.Request( | ||||
|                 "https://api.github.com/repos/paperless-ngx/" | ||||
| @@ -815,7 +815,6 @@ class RemoteVersionView(GenericAPIView): | ||||
|             { | ||||
|                 "version": remote_version, | ||||
|                 "update_available": is_greater_than_current, | ||||
|                 "feature_is_set": feature_is_set, | ||||
|             }, | ||||
|         ) | ||||
|  | ||||
| @@ -848,15 +847,23 @@ class UiSettingsView(GenericAPIView): | ||||
|         displayname = user.username | ||||
|         if user.first_name or user.last_name: | ||||
|             displayname = " ".join([user.first_name, user.last_name]) | ||||
|         settings = {} | ||||
|         ui_settings = {} | ||||
|         if hasattr(user, "ui_settings"): | ||||
|             settings = user.ui_settings.settings | ||||
|             ui_settings = user.ui_settings.settings | ||||
|         if "update_checking" in ui_settings: | ||||
|             ui_settings["update_checking"][ | ||||
|                 "backend_setting" | ||||
|             ] = settings.ENABLE_UPDATE_CHECK | ||||
|         else: | ||||
|             ui_settings["update_checking"] = { | ||||
|                 "backend_setting": settings.ENABLE_UPDATE_CHECK, | ||||
|             } | ||||
|         return Response( | ||||
|             { | ||||
|                 "user_id": user.id, | ||||
|                 "username": user.username, | ||||
|                 "display_name": displayname, | ||||
|                 "settings": settings, | ||||
|                 "settings": ui_settings, | ||||
|             }, | ||||
|         ) | ||||
|  | ||||
|   | ||||
| @@ -127,10 +127,10 @@ def settings_values_check(app_configs, **kwargs): | ||||
|                 Error(f'OCR output type "{settings.OCR_OUTPUT_TYPE}" is not valid'), | ||||
|             ) | ||||
|  | ||||
|         if settings.OCR_MODE not in {"force", "skip", "redo_ocr"}: | ||||
|         if settings.OCR_MODE not in {"force", "skip", "redo", "skip_noarchive"}: | ||||
|             msgs.append(Error(f'OCR output mode "{settings.OCR_MODE}" is not valid')) | ||||
|  | ||||
|         if settings.OCR_CLEAN not in {"clean", "clean_final"}: | ||||
|         if settings.OCR_CLEAN not in {"clean", "clean-final", "none"}: | ||||
|             msgs.append(Error(f'OCR clean mode "{settings.OCR_CLEAN}" is not valid')) | ||||
|         return msgs | ||||
|  | ||||
|   | ||||
| @@ -319,6 +319,7 @@ DATABASES = { | ||||
|     "default": { | ||||
|         "ENGINE": "django.db.backends.sqlite3", | ||||
|         "NAME": os.path.join(DATA_DIR, "db.sqlite3"), | ||||
|         "OPTIONS": {}, | ||||
|     }, | ||||
| } | ||||
|  | ||||
| @@ -340,21 +341,18 @@ if os.getenv("PAPERLESS_DBHOST"): | ||||
|     # Leave room for future extensibility | ||||
|     if os.getenv("PAPERLESS_DBENGINE") == "mariadb": | ||||
|         engine = "django.db.backends.mysql" | ||||
|         options = {"read_default_file": "/etc/mysql/my.cnf"} | ||||
|         options = {"read_default_file": "/etc/mysql/my.cnf", "charset": "utf8mb4"} | ||||
|     else:  # Default to PostgresDB | ||||
|         engine = "django.db.backends.postgresql_psycopg2" | ||||
|         options = {"sslmode": os.getenv("PAPERLESS_DBSSLMODE", "prefer")} | ||||
|  | ||||
|     DATABASES["default"]["ENGINE"] = engine | ||||
|     for key, value in options.items(): | ||||
|         DATABASES["default"]["OPTIONS"][key] = value | ||||
|     DATABASES["default"]["OPTIONS"].update(options) | ||||
|  | ||||
| if os.getenv("PAPERLESS_DB_TIMEOUT") is not None: | ||||
|     _new_opts = {"timeout": float(os.getenv("PAPERLESS_DB_TIMEOUT"))} | ||||
|     if "OPTIONS" in DATABASES["default"]: | ||||
|         DATABASES["default"]["OPTIONS"].update(_new_opts) | ||||
|     else: | ||||
|         DATABASES["default"]["OPTIONS"] = _new_opts | ||||
|     DATABASES["default"]["OPTIONS"].update( | ||||
|         {"timeout": float(os.getenv("PAPERLESS_DB_TIMEOUT"))}, | ||||
|     ) | ||||
|  | ||||
| DEFAULT_AUTO_FIELD = "django.db.models.AutoField" | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| from typing import Final | ||||
| from typing import Tuple | ||||
|  | ||||
| __version__: Final[Tuple[int, int, int]] = (1, 9, 0) | ||||
| __version__: Final[Tuple[int, int, int]] = (1, 9, 2) | ||||
| # Version string like X.Y.Z | ||||
| __full_version_str__: Final[str] = ".".join(map(str, __version__)) | ||||
| # Version string like X.Y | ||||
|   | ||||
| @@ -4,6 +4,7 @@ import tempfile | ||||
| from datetime import date | ||||
| from datetime import timedelta | ||||
| from fnmatch import fnmatch | ||||
| from typing import Dict | ||||
|  | ||||
| import magic | ||||
| import pathvalidate | ||||
| @@ -30,7 +31,7 @@ class MailError(Exception): | ||||
|  | ||||
|  | ||||
| class BaseMailAction: | ||||
|     def get_criteria(self): | ||||
|     def get_criteria(self) -> Dict: | ||||
|         return {} | ||||
|  | ||||
|     def post_consume(self, M, message_uids, parameter): | ||||
| @@ -78,7 +79,7 @@ class TagMailAction(BaseMailAction): | ||||
|             M.flag(message_uids, [self.keyword], True) | ||||
|  | ||||
|  | ||||
| def get_rule_action(rule): | ||||
| def get_rule_action(rule) -> BaseMailAction: | ||||
|     if rule.action == MailRule.MailAction.FLAG: | ||||
|         return FlagMailAction() | ||||
|     elif rule.action == MailRule.MailAction.DELETE: | ||||
| @@ -108,7 +109,7 @@ def make_criterias(rule): | ||||
|     return {**criterias, **get_rule_action(rule).get_criteria()} | ||||
|  | ||||
|  | ||||
| def get_mailbox(server, port, security): | ||||
| def get_mailbox(server, port, security) -> MailBox: | ||||
|     if security == MailAccount.ImapSecurity.NONE: | ||||
|         mailbox = MailBoxUnencrypted(server, port) | ||||
|     elif security == MailAccount.ImapSecurity.STARTTLS: | ||||
| @@ -167,7 +168,7 @@ class MailAccountHandler(LoggingMixin): | ||||
|                 "Unknown correspondent selector", | ||||
|             )  # pragma: nocover | ||||
|  | ||||
|     def handle_mail_account(self, account): | ||||
|     def handle_mail_account(self, account: MailAccount): | ||||
|  | ||||
|         self.renew_logging_group() | ||||
|  | ||||
| @@ -181,7 +182,14 @@ class MailAccountHandler(LoggingMixin): | ||||
|                 account.imap_security, | ||||
|             ) as M: | ||||
|  | ||||
|                 supports_gmail_labels = "X-GM-EXT-1" in M.client.capabilities | ||||
|                 supports_auth_plain = "AUTH=PLAIN" in M.client.capabilities | ||||
|  | ||||
|                 self.log("debug", f"GMAIL Label Support: {supports_gmail_labels}") | ||||
|                 self.log("debug", f"AUTH=PLAIN Support: {supports_auth_plain}") | ||||
|  | ||||
|                 try: | ||||
|  | ||||
|                     M.login(account.username, account.password) | ||||
|  | ||||
|                 except UnicodeEncodeError: | ||||
| @@ -215,7 +223,11 @@ class MailAccountHandler(LoggingMixin): | ||||
|  | ||||
|                 for rule in account.rules.order_by("order"): | ||||
|                     try: | ||||
|                         total_processed_files += self.handle_mail_rule(M, rule) | ||||
|                         total_processed_files += self.handle_mail_rule( | ||||
|                             M, | ||||
|                             rule, | ||||
|                             supports_gmail_labels, | ||||
|                         ) | ||||
|                     except Exception as e: | ||||
|                         self.log( | ||||
|                             "error", | ||||
| @@ -233,7 +245,12 @@ class MailAccountHandler(LoggingMixin): | ||||
|  | ||||
|         return total_processed_files | ||||
|  | ||||
|     def handle_mail_rule(self, M: MailBox, rule): | ||||
|     def handle_mail_rule( | ||||
|         self, | ||||
|         M: MailBox, | ||||
|         rule: MailRule, | ||||
|         supports_gmail_labels: bool = False, | ||||
|     ): | ||||
|  | ||||
|         self.log("debug", f"Rule {rule}: Selecting folder {rule.folder}") | ||||
|  | ||||
| @@ -261,11 +278,19 @@ class MailAccountHandler(LoggingMixin): | ||||
|             ) from err | ||||
|  | ||||
|         criterias = make_criterias(rule) | ||||
|         criterias_imap = AND(**criterias) | ||||
|  | ||||
|         # Deal with the Gmail label extension | ||||
|         if "gmail_label" in criterias: | ||||
|  | ||||
|             gmail_label = criterias["gmail_label"] | ||||
|             del criterias["gmail_label"] | ||||
|  | ||||
|             if not supports_gmail_labels: | ||||
|                 criterias_imap = AND(**criterias) | ||||
|             else: | ||||
|                 criterias_imap = AND(NOT(gmail_label=gmail_label), **criterias) | ||||
|         else: | ||||
|             criterias_imap = AND(**criterias) | ||||
|  | ||||
|         self.log( | ||||
|             "debug", | ||||
|   | ||||
							
								
								
									
										70
									
								
								src/paperless_mail/tests/test_live_mail.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								src/paperless_mail/tests/test_live_mail.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | ||||
| import os | ||||
|  | ||||
| import pytest | ||||
| from django.test import TestCase | ||||
| from paperless_mail.mail import MailAccountHandler | ||||
| from paperless_mail.mail import MailError | ||||
| from paperless_mail.models import MailAccount | ||||
| from paperless_mail.models import MailRule | ||||
|  | ||||
| # Only run if the environment is setup | ||||
| # And the environment is not empty (forks, I think) | ||||
| @pytest.mark.skipif( | ||||
|     "PAPERLESS_MAIL_TEST_HOST" not in os.environ | ||||
|     or not len(os.environ["PAPERLESS_MAIL_TEST_HOST"]), | ||||
|     reason="Live server testing not enabled", | ||||
| ) | ||||
| class TestMailLiveServer(TestCase): | ||||
|     def setUp(self) -> None: | ||||
|  | ||||
|         self.mail_account_handler = MailAccountHandler() | ||||
|         self.account = MailAccount.objects.create( | ||||
|             name="test", | ||||
|             imap_server=os.environ["PAPERLESS_MAIL_TEST_HOST"], | ||||
|             username=os.environ["PAPERLESS_MAIL_TEST_USER"], | ||||
|             password=os.environ["PAPERLESS_MAIL_TEST_PASSWD"], | ||||
|             imap_port=993, | ||||
|         ) | ||||
|  | ||||
|         return super().setUp() | ||||
|  | ||||
|     def tearDown(self) -> None: | ||||
|         self.account.delete() | ||||
|         return super().tearDown() | ||||
|  | ||||
|     def test_process_non_gmail_server_flag(self): | ||||
|  | ||||
|         try: | ||||
|             rule1 = MailRule.objects.create( | ||||
|                 name="testrule", | ||||
|                 account=self.account, | ||||
|                 action=MailRule.MailAction.FLAG, | ||||
|             ) | ||||
|  | ||||
|             self.mail_account_handler.handle_mail_account(self.account) | ||||
|  | ||||
|             rule1.delete() | ||||
|  | ||||
|         except MailError as e: | ||||
|             self.fail(f"Failure: {e}") | ||||
|         except Exception as e: | ||||
|             pass | ||||
|  | ||||
|     def test_process_non_gmail_server_tag(self): | ||||
|  | ||||
|         try: | ||||
|  | ||||
|             rule2 = MailRule.objects.create( | ||||
|                 name="testrule", | ||||
|                 account=self.account, | ||||
|                 action=MailRule.MailAction.TAG, | ||||
|             ) | ||||
|  | ||||
|             self.mail_account_handler.handle_mail_account(self.account) | ||||
|  | ||||
|             rule2.delete() | ||||
|  | ||||
|         except MailError as e: | ||||
|             self.fail(f"Failure: {e}") | ||||
|         except Exception as e: | ||||
|             pass | ||||
| @@ -47,15 +47,16 @@ class BogusFolderManager: | ||||
|  | ||||
|  | ||||
| class BogusClient: | ||||
|     def __init__(self, messages): | ||||
|         self.messages: List[MailMessage] = messages | ||||
|         self.capabilities: List[str] = [] | ||||
|  | ||||
|     def __enter__(self): | ||||
|         return self | ||||
|  | ||||
|     def __exit__(self, exc_type, exc_val, exc_tb): | ||||
|         pass | ||||
|  | ||||
|     def __init__(self, messages): | ||||
|         self.messages: List[MailMessage] = messages | ||||
|  | ||||
|     def authenticate(self, mechanism, authobject): | ||||
|         # authobject must be a callable object | ||||
|         auth_bytes = authobject(None) | ||||
| @@ -80,12 +81,6 @@ class BogusMailBox(ContextManager): | ||||
|     # Note the non-ascii characters here | ||||
|     UTF_PASSWORD: str = "w57äöüw4b6huwb6nhu" | ||||
|  | ||||
|     def __enter__(self): | ||||
|         return self | ||||
|  | ||||
|     def __exit__(self, exc_type, exc_val, exc_tb): | ||||
|         pass | ||||
|  | ||||
|     def __init__(self): | ||||
|         self.messages: List[MailMessage] = [] | ||||
|         self.messages_spam: List[MailMessage] = [] | ||||
| @@ -93,6 +88,12 @@ class BogusMailBox(ContextManager): | ||||
|         self.client = BogusClient(self.messages) | ||||
|         self._host = "" | ||||
|  | ||||
|     def __enter__(self): | ||||
|         return self | ||||
|  | ||||
|     def __exit__(self, exc_type, exc_val, exc_tb): | ||||
|         pass | ||||
|  | ||||
|     def updateClient(self): | ||||
|         self.client = BogusClient(self.messages) | ||||
|  | ||||
| @@ -648,6 +649,7 @@ class TestMail(DirectoriesMixin, TestCase): | ||||
|  | ||||
|     def test_handle_mail_account_tag_gmail(self): | ||||
|         self.bogus_mailbox._host = "imap.gmail.com" | ||||
|         self.bogus_mailbox.client.capabilities = ["X-GM-EXT-1"] | ||||
|  | ||||
|         account = MailAccount.objects.create( | ||||
|             name="test", | ||||
|   | ||||
| @@ -11,5 +11,6 @@ def text_consumer_declaration(sender, **kwargs): | ||||
|         "mime_types": { | ||||
|             "text/plain": ".txt", | ||||
|             "text/csv": ".csv", | ||||
|             "application/csv": ".csv", | ||||
|         }, | ||||
|     } | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								src/paperless_tika/tests/samples/sample.docx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/paperless_tika/tests/samples/sample.docx
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								src/paperless_tika/tests/samples/sample.odt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/paperless_tika/tests/samples/sample.odt
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										78
									
								
								src/paperless_tika/tests/test_live_tika.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								src/paperless_tika/tests/test_live_tika.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | ||||
| import datetime | ||||
| import os | ||||
| from pathlib import Path | ||||
| from typing import Final | ||||
|  | ||||
| import pytest | ||||
| from django.test import TestCase | ||||
| from paperless_tika.parsers import TikaDocumentParser | ||||
|  | ||||
|  | ||||
| @pytest.mark.skipif("TIKA_LIVE" not in os.environ, reason="No tika server") | ||||
| class TestTikaParserAgainstServer(TestCase): | ||||
|     """ | ||||
|     This test case tests the Tika parsing against a live tika server, | ||||
|     if the environment contains the correct value indicating such a server | ||||
|     is available. | ||||
|     """ | ||||
|  | ||||
|     SAMPLE_DIR: Final[Path] = (Path(__file__).parent / Path("samples")).resolve() | ||||
|  | ||||
|     def setUp(self) -> None: | ||||
|         self.parser = TikaDocumentParser(logging_group=None) | ||||
|  | ||||
|     def tearDown(self) -> None: | ||||
|         self.parser.cleanup() | ||||
|  | ||||
|     def test_basic_parse_odt(self): | ||||
|         """ | ||||
|         GIVEN: | ||||
|             - An input ODT format document | ||||
|         WHEN: | ||||
|             - The document is parsed | ||||
|         THEN: | ||||
|             - Document content is correct | ||||
|             - Document date is correct | ||||
|         """ | ||||
|         test_file = self.SAMPLE_DIR / Path("sample.odt") | ||||
|  | ||||
|         self.parser.parse(test_file, "application/vnd.oasis.opendocument.text") | ||||
|  | ||||
|         self.assertEqual( | ||||
|             self.parser.text, | ||||
|             "This is an ODT test document, created September 14, 2022", | ||||
|         ) | ||||
|         self.assertIsNotNone(self.parser.archive_path) | ||||
|         with open(self.parser.archive_path, "rb") as f: | ||||
|             # PDFs begin with the bytes PDF-x.y | ||||
|             self.assertTrue(b"PDF-" in f.read()[:10]) | ||||
|  | ||||
|         # TODO: Unsure what can set the Creation-Date field in a document, enable when possible | ||||
|         # self.assertEqual(self.parser.date, datetime.datetime(2022, 9, 14)) | ||||
|  | ||||
|     def test_basic_parse_docx(self): | ||||
|         """ | ||||
|         GIVEN: | ||||
|             - An input DOCX format document | ||||
|         WHEN: | ||||
|             - The document is parsed | ||||
|         THEN: | ||||
|             - Document content is correct | ||||
|             - Document date is correct | ||||
|         """ | ||||
|         test_file = self.SAMPLE_DIR / Path("sample.docx") | ||||
|  | ||||
|         self.parser.parse( | ||||
|             test_file, | ||||
|             "application/vnd.openxmlformats-officedocument.wordprocessingml.document", | ||||
|         ) | ||||
|  | ||||
|         self.assertEqual( | ||||
|             self.parser.text, | ||||
|             "This is an DOCX test document, also made September 14, 2022", | ||||
|         ) | ||||
|         self.assertIsNotNone(self.parser.archive_path) | ||||
|         with open(self.parser.archive_path, "rb") as f: | ||||
|             self.assertTrue(b"PDF-" in f.read()[:10]) | ||||
|  | ||||
|         # self.assertEqual(self.parser.date, datetime.datetime(2022, 9, 14)) | ||||
		Reference in New Issue
	
	Block a user
	 Michael Shamoon
					Michael Shamoon