mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-07-08 10:44:12 -04:00
Support update vs create
This commit is contained in:
parent
de9cc7420d
commit
a7cdecd5a2
@ -41,14 +41,14 @@
|
|||||||
<button class="btn btn-sm btn-dark text-danger" (click)="remove(i); $event.stopPropagation()" title="Delete page" i18n-title>
|
<button class="btn btn-sm btn-dark text-danger" (click)="remove(i); $event.stopPropagation()" title="Delete page" i18n-title>
|
||||||
<i-bs name="trash"></i-bs>
|
<i-bs name="trash"></i-bs>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-sm btn-dark" (click)="toggleSplit(i); $event.stopPropagation()" title="Split document here" i18n-title>
|
<button class="btn btn-sm btn-dark" (click)="toggleSplit(i); $event.stopPropagation()" title="Add / remove document split here" i18n-title>
|
||||||
<i-bs name="scissors"></i-bs>
|
<i-bs name="scissors"></i-bs>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="border-end border-bottom bg-light py-1 px-2 document-check z-10">
|
<div class="border-end border-bottom bg-light py-1 px-2 document-check z-10">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input type="checkbox" class="form-check-input" id="page{{i}}" [checked]="p.selected" (click)="toggleSelection(i); $event.stopPropagation()">
|
<input type="checkbox" class="form-check-input" id="page{{i}}" [checked]="p.selected" (click)="toggleSelection(i); $event.stopPropagation()">
|
||||||
<label class="form-check-label" for="page{{i}}"></label>
|
<label class="form-check-label" for="page{{i}}"></label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -68,11 +68,31 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer flex-column">
|
||||||
<div class="form-check form-switch me-auto">
|
<div class="d-flex w-100 justify-content-between align-items-center">
|
||||||
<input class="form-check-input" type="checkbox" id="deleteSwitch" [(ngModel)]="deleteOriginal">
|
<div class="btn-group" role="group">
|
||||||
<label class="form-check-label" for="deleteSwitch" i18n>Delete original after edit</label>
|
<input type="radio" class="btn-check" [(ngModel)]="editMode" [value]="EditMode.Create" id="editModeCreate" name="editmode">
|
||||||
|
<label for="editModeCreate" class="btn btn-outline-primary btn-sm">
|
||||||
|
<i-bs name="plus"></i-bs>
|
||||||
|
<span class="form-check-label ms-1" i18n>Create new document(s)</span>
|
||||||
|
</label>
|
||||||
|
<input type="radio" class="btn-check" [(ngModel)]="editMode" [value]="EditMode.Update" id="editModeUpdate" name="editmode" [disabled]="hasSplit()">
|
||||||
|
<label for="editModeUpdate" class="btn btn-outline-primary btn-sm">
|
||||||
|
<i-bs name="pencil"></i-bs>
|
||||||
|
<span class="form-check-label ms-2" i18n>Update existing document</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
@if (editMode === EditMode.Create) {
|
||||||
|
<div class="form-check ms-3">
|
||||||
|
<input class="form-check-input" type="checkbox" id="copyMeta" [(ngModel)]="includeMetadata">
|
||||||
|
<label class="form-check-label" for="copyMeta" i18n>Copy metadata</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check ms-3">
|
||||||
|
<input class="form-check-input" type="checkbox" id="deleteOriginal" [(ngModel)]="deleteOriginal">
|
||||||
|
<label class="form-check-label" for="deleteOriginal" i18n>Delete original</label>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<button type="button" class="btn ms-auto me-2" [class]="cancelBtnClass" (click)="cancel()" [disabled]="!buttonsEnabled">{{ cancelBtnCaption }}</button>
|
||||||
|
<button type="button" class="btn" [class]="btnClass" (click)="confirm()" [disabled]="pages.length === 0">{{ btnCaption }}</button>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn" [class]="cancelBtnClass" (click)="cancel()" [disabled]="!buttonsEnabled">{{ cancelBtnCaption }}</button>
|
|
||||||
<button type="button" class="btn" [class]="btnClass" (click)="confirm()" [disabled]="pages.length === 0">{{ btnCaption }}</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -19,6 +19,11 @@ interface PageOperation {
|
|||||||
loaded?: boolean
|
loaded?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum EditMode {
|
||||||
|
Update = 'update',
|
||||||
|
Create = 'create',
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'pngx-pdf-editor',
|
selector: 'pngx-pdf-editor',
|
||||||
templateUrl: './pdf-editor.component.html',
|
templateUrl: './pdf-editor.component.html',
|
||||||
@ -31,13 +36,18 @@ interface PageOperation {
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class PDFEditorComponent extends ConfirmDialogComponent {
|
export class PDFEditorComponent extends ConfirmDialogComponent {
|
||||||
|
public EditMode = EditMode
|
||||||
|
|
||||||
private documentService = inject(DocumentService)
|
private documentService = inject(DocumentService)
|
||||||
activeModal = inject(NgbActiveModal)
|
activeModal: NgbActiveModal = inject(NgbActiveModal)
|
||||||
|
|
||||||
documentID: number
|
documentID: number
|
||||||
pages: PageOperation[] = []
|
pages: PageOperation[] = []
|
||||||
totalPages = 0
|
totalPages = 0
|
||||||
deleteOriginal = false
|
editMode: EditMode = EditMode.Create
|
||||||
|
deleteOriginal: boolean = false
|
||||||
|
updateDocument: boolean = false
|
||||||
|
includeMetadata: boolean = true
|
||||||
|
|
||||||
get pdfSrc(): string {
|
get pdfSrc(): string {
|
||||||
return this.documentService.getPreviewUrl(this.documentID)
|
return this.documentService.getPreviewUrl(this.documentID)
|
||||||
@ -76,6 +86,10 @@ export class PDFEditorComponent extends ConfirmDialogComponent {
|
|||||||
|
|
||||||
toggleSplit(i: number) {
|
toggleSplit(i: number) {
|
||||||
this.pages[i].splitAfter = !this.pages[i].splitAfter
|
this.pages[i].splitAfter = !this.pages[i].splitAfter
|
||||||
|
if (this.pages[i].splitAfter) {
|
||||||
|
// force create mode
|
||||||
|
this.editMode = EditMode.Create
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selectAll() {
|
selectAll() {
|
||||||
@ -94,6 +108,10 @@ export class PDFEditorComponent extends ConfirmDialogComponent {
|
|||||||
return this.pages.some((p) => p.selected)
|
return this.pages.some((p) => p.selected)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasSplit(): boolean {
|
||||||
|
return this.pages.some((p) => p.splitAfter)
|
||||||
|
}
|
||||||
|
|
||||||
drop(event: CdkDragDrop<PageOperation[]>) {
|
drop(event: CdkDragDrop<PageOperation[]>) {
|
||||||
moveItemInArray(this.pages, event.previousIndex, event.currentIndex)
|
moveItemInArray(this.pages, event.previousIndex, event.currentIndex)
|
||||||
}
|
}
|
||||||
|
@ -1159,7 +1159,8 @@ describe('DocumentDetailComponent', () => {
|
|||||||
method: 'edit_pdf',
|
method: 'edit_pdf',
|
||||||
parameters: {
|
parameters: {
|
||||||
operations: [{ page: 1, rotate: 0, doc: 0 }],
|
operations: [{ page: 1, rotate: 0, doc: 0 }],
|
||||||
delete_original: false,
|
update_document: false,
|
||||||
|
include_metadata: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
req.flush(true)
|
req.flush(true)
|
||||||
|
@ -1350,7 +1350,8 @@ export class DocumentDetailComponent
|
|||||||
this.documentsService
|
this.documentsService
|
||||||
.bulkEdit([this.document.id], 'edit_pdf', {
|
.bulkEdit([this.document.id], 'edit_pdf', {
|
||||||
operations: modal.componentInstance.getOperations(),
|
operations: modal.componentInstance.getOperations(),
|
||||||
delete_original: modal.componentInstance.deleteOriginal,
|
update_document: modal.componentInstance.updateDocument,
|
||||||
|
include_metadata: modal.componentInstance.includeMetadata,
|
||||||
})
|
})
|
||||||
.pipe(first(), takeUntil(this.unsubscribeNotifier))
|
.pipe(first(), takeUntil(this.unsubscribeNotifier))
|
||||||
.subscribe({
|
.subscribe({
|
||||||
|
@ -502,6 +502,8 @@ def edit_pdf(
|
|||||||
operations: list[dict],
|
operations: list[dict],
|
||||||
*,
|
*,
|
||||||
delete_original: bool = False,
|
delete_original: bool = False,
|
||||||
|
update_document: bool = False,
|
||||||
|
include_metadata: bool = True,
|
||||||
user: User | None = None,
|
user: User | None = None,
|
||||||
) -> Literal["OK"]:
|
) -> Literal["OK"]:
|
||||||
"""
|
"""
|
||||||
@ -533,9 +535,25 @@ def edit_pdf(
|
|||||||
if op.get("rotate"):
|
if op.get("rotate"):
|
||||||
dst.pages[-1].rotate(op["rotate"], relative=True)
|
dst.pages[-1].rotate(op["rotate"], relative=True)
|
||||||
|
|
||||||
|
if update_document:
|
||||||
|
if len(pdf_docs) != 1:
|
||||||
|
logger.error(
|
||||||
|
"Update requested but multiple output documents specified",
|
||||||
|
)
|
||||||
|
return "ERROR"
|
||||||
|
pdf = pdf_docs[0]
|
||||||
|
pdf.remove_unreferenced_resources()
|
||||||
|
pdf.save(doc.source_path)
|
||||||
|
doc.checksum = hashlib.md5(doc.source_path.read_bytes()).hexdigest()
|
||||||
|
doc.page_count = len(pdf.pages)
|
||||||
|
doc.save()
|
||||||
|
update_document_content_maybe_archive_file.delay(document_id=doc.id)
|
||||||
|
else:
|
||||||
consume_tasks = []
|
consume_tasks = []
|
||||||
overrides: DocumentMetadataOverrides = (
|
overrides = (
|
||||||
DocumentMetadataOverrides().from_document(doc)
|
DocumentMetadataOverrides().from_document(doc)
|
||||||
|
if include_metadata
|
||||||
|
else DocumentMetadataOverrides()
|
||||||
)
|
)
|
||||||
if user is not None:
|
if user is not None:
|
||||||
overrides.owner_id = user.id
|
overrides.owner_id = user.id
|
||||||
@ -564,6 +582,7 @@ def edit_pdf(
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(f"Error editing document {doc.id}: {e}")
|
logger.exception(f"Error editing document {doc.id}: {e}")
|
||||||
|
return "ERROR"
|
||||||
|
|
||||||
return "OK"
|
return "OK"
|
||||||
|
|
||||||
|
@ -1537,11 +1537,23 @@ class BulkEditSerializer(
|
|||||||
raise serializers.ValidationError("rotate must be an integer")
|
raise serializers.ValidationError("rotate must be an integer")
|
||||||
if "doc" in op and not isinstance(op["doc"], int):
|
if "doc" in op and not isinstance(op["doc"], int):
|
||||||
raise serializers.ValidationError("doc must be an integer")
|
raise serializers.ValidationError("doc must be an integer")
|
||||||
if "delete_original" in parameters:
|
if "update_document" in parameters:
|
||||||
if not isinstance(parameters["delete_original"], bool):
|
if not isinstance(parameters["update_document"], bool):
|
||||||
raise serializers.ValidationError("delete_original must be a boolean")
|
raise serializers.ValidationError("update_document must be a boolean")
|
||||||
else:
|
else:
|
||||||
parameters["delete_original"] = False
|
parameters["update_document"] = False
|
||||||
|
if "include_metadata" in parameters:
|
||||||
|
if not isinstance(parameters["include_metadata"], bool):
|
||||||
|
raise serializers.ValidationError("include_metadata must be a boolean")
|
||||||
|
else:
|
||||||
|
parameters["include_metadata"] = True
|
||||||
|
|
||||||
|
if parameters["update_document"]:
|
||||||
|
max_idx = max(op.get("doc", 0) for op in parameters["operations"])
|
||||||
|
if max_idx > 0:
|
||||||
|
raise serializers.ValidationError(
|
||||||
|
"update_document only allowed with a single output document",
|
||||||
|
)
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
method = attrs["method"]
|
method = attrs["method"]
|
||||||
|
@ -1368,17 +1368,21 @@ class BulkEditView(PassUserMixin):
|
|||||||
method in [bulk_edit.merge, bulk_edit.split]
|
method in [bulk_edit.merge, bulk_edit.split]
|
||||||
and parameters["delete_originals"]
|
and parameters["delete_originals"]
|
||||||
)
|
)
|
||||||
or (method == bulk_edit.edit_pdf and parameters["delete_original"])
|
or (method == bulk_edit.edit_pdf and parameters["update_document"])
|
||||||
):
|
):
|
||||||
has_perms = user_is_owner_of_all_documents
|
has_perms = user_is_owner_of_all_documents
|
||||||
|
|
||||||
# check global add permissions for methods that create documents
|
# check global add permissions for methods that create documents
|
||||||
if (
|
if (
|
||||||
has_perms
|
has_perms
|
||||||
and method in [bulk_edit.split, bulk_edit.merge, bulk_edit.edit_pdf]
|
and (
|
||||||
and not user.has_perm(
|
method in [bulk_edit.split, bulk_edit.merge]
|
||||||
"documents.add_document",
|
or (
|
||||||
|
method == bulk_edit.edit_pdf
|
||||||
|
and not parameters["update_document"]
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
and not user.has_perm("documents.add_document")
|
||||||
):
|
):
|
||||||
has_perms = False
|
has_perms = False
|
||||||
|
|
||||||
@ -1391,7 +1395,6 @@ class BulkEditView(PassUserMixin):
|
|||||||
method in [bulk_edit.merge, bulk_edit.split]
|
method in [bulk_edit.merge, bulk_edit.split]
|
||||||
and parameters["delete_originals"]
|
and parameters["delete_originals"]
|
||||||
)
|
)
|
||||||
or (method == bulk_edit.edit_pdf and parameters["delete_original"])
|
|
||||||
)
|
)
|
||||||
and not user.has_perm("documents.delete_document")
|
and not user.has_perm("documents.delete_document")
|
||||||
):
|
):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user