mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 18:22:40 -04:00 
			
		
		
		
	Merge branch 'dev'
This commit is contained in:
		
						commit
						da40d03be6
					
				
							
								
								
									
										20
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										20
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							| @ -30,7 +30,7 @@ jobs: | ||||
|       github.repository | ||||
| 
 | ||||
|     name: Linting Checks | ||||
|     runs-on: ubuntu-22.04 | ||||
|     runs-on: ubuntu-24.04 | ||||
|     steps: | ||||
|       - | ||||
|         name: Checkout repository | ||||
| @ -46,7 +46,7 @@ jobs: | ||||
| 
 | ||||
|   documentation: | ||||
|     name: "Build & Deploy Documentation" | ||||
|     runs-on: ubuntu-22.04 | ||||
|     runs-on: ubuntu-24.04 | ||||
|     needs: | ||||
|       - pre-commit | ||||
|     steps: | ||||
| @ -95,7 +95,7 @@ jobs: | ||||
| 
 | ||||
|   tests-backend: | ||||
|     name: "Backend Tests (Python ${{ matrix.python-version }})" | ||||
|     runs-on: ubuntu-22.04 | ||||
|     runs-on: ubuntu-24.04 | ||||
|     needs: | ||||
|       - pre-commit | ||||
|     strategy: | ||||
| @ -170,7 +170,7 @@ jobs: | ||||
| 
 | ||||
|   install-frontend-depedendencies: | ||||
|     name: "Install Frontend Dependencies" | ||||
|     runs-on: ubuntu-22.04 | ||||
|     runs-on: ubuntu-24.04 | ||||
|     needs: | ||||
|       - pre-commit | ||||
|     steps: | ||||
| @ -201,7 +201,7 @@ jobs: | ||||
| 
 | ||||
|   tests-frontend: | ||||
|     name: "Frontend Tests (Node ${{ matrix.node-version }} - ${{ matrix.shard-index }}/${{ matrix.shard-count }})" | ||||
|     runs-on: ubuntu-22.04 | ||||
|     runs-on: ubuntu-24.04 | ||||
|     needs: | ||||
|       - install-frontend-depedendencies | ||||
|     strategy: | ||||
| @ -261,7 +261,7 @@ jobs: | ||||
| 
 | ||||
|   tests-coverage-upload: | ||||
|     name: "Upload to Codecov" | ||||
|     runs-on: ubuntu-22.04 | ||||
|     runs-on: ubuntu-24.04 | ||||
|     needs: | ||||
|       - tests-backend | ||||
|       - tests-frontend | ||||
| @ -333,7 +333,7 @@ jobs: | ||||
| 
 | ||||
|   build-docker-image: | ||||
|     name: Build Docker image for ${{ github.ref_name }} | ||||
|     runs-on: ubuntu-22.04 | ||||
|     runs-on: ubuntu-24.04 | ||||
|     if: github.event_name == 'push' && (startsWith(github.ref, 'refs/heads/feature-') || startsWith(github.ref, 'refs/heads/fix-') || github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/beta' || contains(github.ref, 'beta.rc') || startsWith(github.ref, 'refs/tags/v')) | ||||
|     concurrency: | ||||
|       group: ${{ github.workflow }}-build-docker-image-${{ github.ref_name }} | ||||
| @ -461,7 +461,7 @@ jobs: | ||||
|     needs: | ||||
|       - build-docker-image | ||||
|       - documentation | ||||
|     runs-on: ubuntu-22.04 | ||||
|     runs-on: ubuntu-24.04 | ||||
|     steps: | ||||
|       - | ||||
|         name: Checkout | ||||
| @ -569,7 +569,7 @@ jobs: | ||||
| 
 | ||||
|   publish-release: | ||||
|     name: "Publish Release" | ||||
|     runs-on: ubuntu-22.04 | ||||
|     runs-on: ubuntu-24.04 | ||||
|     outputs: | ||||
|       prerelease: ${{ steps.get_version.outputs.prerelease }} | ||||
|       changelog: ${{ steps.create-release.outputs.body }} | ||||
| @ -619,7 +619,7 @@ jobs: | ||||
| 
 | ||||
|   append-changelog: | ||||
|     name: "Append Changelog" | ||||
|     runs-on: ubuntu-22.04 | ||||
|     runs-on: ubuntu-24.04 | ||||
|     needs: | ||||
|       - publish-release | ||||
|     if: needs.publish-release.outputs.prerelease == 'false' | ||||
|  | ||||
							
								
								
									
										8
									
								
								.github/workflows/cleanup-tags.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.github/workflows/cleanup-tags.yml
									
									
									
									
										vendored
									
									
								
							| @ -21,7 +21,7 @@ jobs: | ||||
|   cleanup-images: | ||||
|     name: Cleanup Image Tags for ${{ matrix.primary-name }} | ||||
|     if: github.repository_owner == 'paperless-ngx' | ||||
|     runs-on: ubuntu-22.04 | ||||
|     runs-on: ubuntu-24.04 | ||||
|     strategy: | ||||
|       fail-fast: false | ||||
|       matrix: | ||||
| @ -33,7 +33,7 @@ jobs: | ||||
|       - | ||||
|         name: Clean temporary images | ||||
|         if: "${{ env.TOKEN != '' }}" | ||||
|         uses: stumpylog/image-cleaner-action/ephemeral@v0.8.0 | ||||
|         uses: stumpylog/image-cleaner-action/ephemeral@v0.9.0 | ||||
|         with: | ||||
|           token: "${{ env.TOKEN }}" | ||||
|           owner: "${{ github.repository_owner }}" | ||||
| @ -47,7 +47,7 @@ jobs: | ||||
|   cleanup-untagged-images: | ||||
|     name: Cleanup Untagged Images Tags for ${{ matrix.primary-name }} | ||||
|     if: github.repository_owner == 'paperless-ngx' | ||||
|     runs-on: ubuntu-22.04 | ||||
|     runs-on: ubuntu-24.04 | ||||
|     needs: | ||||
|       - cleanup-images | ||||
|     strategy: | ||||
| @ -61,7 +61,7 @@ jobs: | ||||
|       - | ||||
|         name: Clean untagged images | ||||
|         if: "${{ env.TOKEN != '' }}" | ||||
|         uses: stumpylog/image-cleaner-action/untagged@v0.8.0 | ||||
|         uses: stumpylog/image-cleaner-action/untagged@v0.9.0 | ||||
|         with: | ||||
|           token: "${{ env.TOKEN }}" | ||||
|           owner: "${{ github.repository_owner }}" | ||||
|  | ||||
							
								
								
									
										2
									
								
								.github/workflows/codeql-analysis.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/codeql-analysis.yml
									
									
									
									
										vendored
									
									
								
							| @ -23,7 +23,7 @@ on: | ||||
| jobs: | ||||
|   analyze: | ||||
|     name: Analyze | ||||
|     runs-on: ubuntu-22.04 | ||||
|     runs-on: ubuntu-24.04 | ||||
|     permissions: | ||||
|       actions: read | ||||
|       contents: read | ||||
|  | ||||
							
								
								
									
										2
									
								
								.github/workflows/crowdin.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/crowdin.yml
									
									
									
									
										vendored
									
									
								
							| @ -16,7 +16,7 @@ jobs: | ||||
|   synchronize-with-crowdin: | ||||
|     name: Crowdin Sync | ||||
|     if: github.repository_owner == 'paperless-ngx' | ||||
|     runs-on: ubuntu-latest | ||||
|     runs-on: ubuntu-24.04 | ||||
| 
 | ||||
|     steps: | ||||
|     - name: Checkout | ||||
|  | ||||
							
								
								
									
										2
									
								
								.github/workflows/project-actions.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/project-actions.yml
									
									
									
									
										vendored
									
									
								
							| @ -15,7 +15,7 @@ permissions: | ||||
| jobs: | ||||
|   pr_opened_or_reopened: | ||||
|     name: pr_opened_or_reopened | ||||
|     runs-on: ubuntu-22.04 | ||||
|     runs-on: ubuntu-24.04 | ||||
|     permissions: | ||||
|       # write permission is required for autolabeler | ||||
|       pull-requests: write | ||||
|  | ||||
							
								
								
									
										10
									
								
								.github/workflows/repo-maintenance.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.github/workflows/repo-maintenance.yml
									
									
									
									
										vendored
									
									
								
							| @ -17,7 +17,7 @@ jobs: | ||||
|   stale: | ||||
|     name: 'Stale' | ||||
|     if: github.repository_owner == 'paperless-ngx' | ||||
|     runs-on: ubuntu-latest | ||||
|     runs-on: ubuntu-24.04 | ||||
|     steps: | ||||
|       - uses: actions/stale@v9 | ||||
|         with: | ||||
| @ -33,7 +33,7 @@ jobs: | ||||
|   lock-threads: | ||||
|     name: 'Lock Old Threads' | ||||
|     if: github.repository_owner == 'paperless-ngx' | ||||
|     runs-on: ubuntu-latest | ||||
|     runs-on: ubuntu-24.04 | ||||
|     steps: | ||||
|       - uses: dessant/lock-threads@v5 | ||||
|         with: | ||||
| @ -59,7 +59,7 @@ jobs: | ||||
|   close-answered-discussions: | ||||
|     name: 'Close Answered Discussions' | ||||
|     if: github.repository_owner == 'paperless-ngx' | ||||
|     runs-on: ubuntu-latest | ||||
|     runs-on: ubuntu-24.04 | ||||
|     steps: | ||||
|       - uses: actions/github-script@v7 | ||||
|         with: | ||||
| @ -116,7 +116,7 @@ jobs: | ||||
|   close-outdated-discussions: | ||||
|     name: 'Close Outdated Discussions' | ||||
|     if: github.repository_owner == 'paperless-ngx' | ||||
|     runs-on: ubuntu-latest | ||||
|     runs-on: ubuntu-24.04 | ||||
|     steps: | ||||
|       - uses: actions/github-script@v7 | ||||
|         with: | ||||
| @ -208,7 +208,7 @@ jobs: | ||||
|   close-unsupported-feature-requests: | ||||
|     name: 'Close Unsupported Feature Requests' | ||||
|     if: github.repository_owner == 'paperless-ngx' | ||||
|     runs-on: ubuntu-latest | ||||
|     runs-on: ubuntu-24.04 | ||||
|     steps: | ||||
|       - uses: actions/github-script@v7 | ||||
|         with: | ||||
|  | ||||
| @ -43,7 +43,7 @@ | ||||
|       </tr> | ||||
|     </thead> | ||||
|     <tbody> | ||||
|       @for (task of tasks | slice: (page-1) * pageSize : page * pageSize; track task) { | ||||
|       @for (task of tasks | slice: (page-1) * pageSize : page * pageSize; track task.id) { | ||||
|         <tr (click)="toggleSelected(task, $event); $event.stopPropagation();"> | ||||
|           <td> | ||||
|             <div class="form-check"> | ||||
|  | ||||
| @ -19,6 +19,7 @@ | ||||
| 
 | ||||
|   object:not(.pdf) { | ||||
|     mix-blend-mode: difference; | ||||
|     background: white !important; | ||||
|     &.p-2 { | ||||
|       padding: 0 !important; | ||||
|     } | ||||
|  | ||||
| @ -710,7 +710,10 @@ export class DocumentDetailComponent | ||||
|         next: (docValues) => { | ||||
|           // in case data changed while saving eg removing inbox_tags
 | ||||
|           this.documentForm.patchValue(docValues) | ||||
|           this.store.next(this.documentForm.value) | ||||
|           const newValues = Object.assign({}, this.documentForm.value) | ||||
|           newValues.tags = [...docValues.tags] | ||||
|           newValues.custom_fields = [...docValues.custom_fields] | ||||
|           this.store.next(newValues) | ||||
|           this.openDocumentService.setDirty(this.document, false) | ||||
|           this.openDocumentService.save() | ||||
|           this.toastService.showInfo($localize`Document saved successfully.`) | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| <div class="col p-2 h-100"> | ||||
|   <div class="card h-100 shadow-sm document-card" [class.card-selected]="selected" [class.popover-hidden]="popoverHidden" (mouseleave)="mouseLeaveCard()"> | ||||
|     <div class="border-bottom doc-img-container" (click)="this.toggleSelected.emit($event)" (dblclick)="dblClickDocument.emit(this)"> | ||||
|     <div class="border-bottom doc-img-container rounded-top" (click)="this.toggleSelected.emit($event)" (dblclick)="dblClickDocument.emit(this)"> | ||||
|       <img class="card-img doc-img" [class.inverted]="getIsThumbInverted()" [src]="getThumbUrl()"> | ||||
| 
 | ||||
|       <div class="border-end border-bottom bg-light py-1 px-2 document-card-check"> | ||||
|  | ||||
| @ -501,7 +501,7 @@ ul.pagination { | ||||
|   border-color:var(--bs-primary); | ||||
| 
 | ||||
|   .document-card-check { | ||||
|     display: block; | ||||
|     display: block !important; | ||||
|   } | ||||
| 
 | ||||
|   .doc-img-container { | ||||
|  | ||||
| @ -204,7 +204,7 @@ $form-check-radio-checked-bg-image-dark: url("data:image/svg+xml,<svg xmlns='htt | ||||
|   @supports (hanging-punctuation: first) and (font: -apple-system-body) and (-webkit-appearance: none) { | ||||
|     // Safari does not like the filters on the image, see https://github.com/paperless-ngx/paperless-ngx/pull/8121 | ||||
|     .doc-img-container { | ||||
|       background-color: #ffffff; | ||||
|       background-color: #ffffff !important; | ||||
|     } | ||||
| 
 | ||||
|     .doc-img { | ||||
| @ -252,7 +252,7 @@ $form-check-radio-checked-bg-image-dark: url("data:image/svg+xml,<svg xmlns='htt | ||||
|   } | ||||
| 
 | ||||
|   .table-row-selected { | ||||
|     td, a { | ||||
|     td, a:not(.badge) { | ||||
|       color: var(--pngx-primary-text-contrast) !important; | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @ -51,6 +51,7 @@ class DocumentAdmin(GuardedModelAdmin): | ||||
|         "archive_filename", | ||||
|         "archive_checksum", | ||||
|         "original_filename", | ||||
|         "deleted_at", | ||||
|     ) | ||||
| 
 | ||||
|     list_display_links = ("title",) | ||||
| @ -77,6 +78,12 @@ class DocumentAdmin(GuardedModelAdmin): | ||||
| 
 | ||||
|     created_.short_description = "Created" | ||||
| 
 | ||||
|     def get_queryset(self, request):  # pragma: no cover | ||||
|         """ | ||||
|         Include trashed documents | ||||
|         """ | ||||
|         return Document.global_objects.all() | ||||
| 
 | ||||
|     def delete_queryset(self, request, queryset): | ||||
|         from documents import index | ||||
| 
 | ||||
|  | ||||
| @ -805,6 +805,24 @@ class DocumentSerializer( | ||||
|             doc["content"] = doc.get("content")[0:550] | ||||
|         return doc | ||||
| 
 | ||||
|     def validate(self, attrs): | ||||
|         if ( | ||||
|             "archive_serial_number" in attrs | ||||
|             and attrs["archive_serial_number"] is not None | ||||
|             and len(str(attrs["archive_serial_number"])) > 0 | ||||
|             and Document.deleted_objects.filter( | ||||
|                 archive_serial_number=attrs["archive_serial_number"], | ||||
|             ).exists() | ||||
|         ): | ||||
|             raise serializers.ValidationError( | ||||
|                 { | ||||
|                     "archive_serial_number": [ | ||||
|                         "Document with this Archive Serial Number already exists in the trash.", | ||||
|                     ], | ||||
|                 }, | ||||
|             ) | ||||
|         return super().validate(attrs) | ||||
| 
 | ||||
|     def update(self, instance: Document, validated_data): | ||||
|         if "created_date" in validated_data and "created" not in validated_data: | ||||
|             new_datetime = datetime.datetime.combine( | ||||
|  | ||||
| @ -2540,6 +2540,50 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase): | ||||
|         self.assertEqual(resp.status_code, status.HTTP_200_OK) | ||||
|         self.assertEqual(resp.content, b"1") | ||||
| 
 | ||||
|     def test_asn_not_unique_with_trashed_doc(self): | ||||
|         """ | ||||
|         GIVEN: | ||||
|             - Existing document with ASN that is trashed | ||||
|         WHEN: | ||||
|             - API request to update document with same ASN | ||||
|         THEN: | ||||
|             - Explicit error is returned | ||||
|         """ | ||||
|         user1 = User.objects.create_superuser(username="test1") | ||||
| 
 | ||||
|         self.client.force_authenticate(user1) | ||||
| 
 | ||||
|         doc1 = Document.objects.create( | ||||
|             title="test", | ||||
|             mime_type="application/pdf", | ||||
|             content="this is a document 1", | ||||
|             checksum="1", | ||||
|             archive_serial_number=1, | ||||
|         ) | ||||
|         doc1.delete() | ||||
| 
 | ||||
|         doc2 = Document.objects.create( | ||||
|             title="test2", | ||||
|             mime_type="application/pdf", | ||||
|             content="this is a document 2", | ||||
|             checksum="2", | ||||
|         ) | ||||
|         result = self.client.patch( | ||||
|             f"/api/documents/{doc2.pk}/", | ||||
|             { | ||||
|                 "archive_serial_number": 1, | ||||
|             }, | ||||
|         ) | ||||
|         self.assertEqual(result.status_code, status.HTTP_400_BAD_REQUEST) | ||||
|         self.assertEqual( | ||||
|             result.json(), | ||||
|             { | ||||
|                 "archive_serial_number": [ | ||||
|                     "Document with this Archive Serial Number already exists in the trash.", | ||||
|                 ], | ||||
|             }, | ||||
|         ) | ||||
| 
 | ||||
|     def test_remove_inbox_tags(self): | ||||
|         """ | ||||
|         GIVEN: | ||||
|  | ||||
| @ -43,10 +43,15 @@ class RasterisedDocumentParser(DocumentParser): | ||||
|     def get_page_count(self, document_path, mime_type): | ||||
|         page_count = None | ||||
|         if mime_type == "application/pdf": | ||||
|             import pikepdf | ||||
|             try: | ||||
|                 import pikepdf | ||||
| 
 | ||||
|             with pikepdf.Pdf.open(document_path) as pdf: | ||||
|                 page_count = len(pdf.pages) | ||||
|                 with pikepdf.Pdf.open(document_path) as pdf: | ||||
|                     page_count = len(pdf.pages) | ||||
|             except Exception as e: | ||||
|                 self.log.warning( | ||||
|                     f"Unable to determine PDF page count {document_path}: {e}", | ||||
|                 ) | ||||
|         return page_count | ||||
| 
 | ||||
|     def extract_metadata(self, document_path, mime_type): | ||||
|  | ||||
| @ -81,6 +81,24 @@ class TestParser(DirectoriesMixin, FileSystemAssertsMixin, TestCase): | ||||
|         ) | ||||
|         self.assertEqual(page_count, 6) | ||||
| 
 | ||||
|     def test_get_page_count_password_protected(self): | ||||
|         """ | ||||
|         GIVEN: | ||||
|             - Password protected PDF file | ||||
|         WHEN: | ||||
|             - The number of pages is requested | ||||
|         THEN: | ||||
|             - The method returns None | ||||
|         """ | ||||
|         parser = RasterisedDocumentParser(uuid.uuid4()) | ||||
|         with self.assertLogs("paperless.parsing.tesseract", level="WARNING") as cm: | ||||
|             page_count = parser.get_page_count( | ||||
|                 os.path.join(self.SAMPLE_FILES, "password-protected.pdf"), | ||||
|                 "application/pdf", | ||||
|             ) | ||||
|             self.assertEqual(page_count, None) | ||||
|             self.assertIn("Unable to determine PDF page count", cm.output[0]) | ||||
| 
 | ||||
|     def test_thumbnail(self): | ||||
|         parser = RasterisedDocumentParser(uuid.uuid4()) | ||||
|         thumb = parser.get_thumbnail( | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user