mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-02 13:45:10 -05: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