From a634d65b10d8914af2bd75e611d254fd1c107b9f Mon Sep 17 00:00:00 2001 From: jonaswinkler Date: Sun, 29 Nov 2020 16:33:33 +0100 Subject: [PATCH 01/48] added a menu for bulk edits. --- .../document-list.component.html | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src-ui/src/app/components/document-list/document-list.component.html b/src-ui/src/app/components/document-list/document-list.component.html index cc682b8e3..d142fbb04 100644 --- a/src-ui/src/app/components/document-list/document-list.component.html +++ b/src-ui/src/app/components/document-list/document-list.component.html @@ -1,5 +1,31 @@ +
+ +
+ + + + + + + + + + + + + + + +
+
+
\ No newline at end of file diff --git a/src-ui/src/app/components/common/delete-dialog/delete-dialog.component.ts b/src-ui/src/app/components/common/delete-dialog/delete-dialog.component.ts index 20114c78c..38ec93bae 100644 --- a/src-ui/src/app/components/common/delete-dialog/delete-dialog.component.ts +++ b/src-ui/src/app/components/common/delete-dialog/delete-dialog.component.ts @@ -22,6 +22,21 @@ export class DeleteDialogComponent implements OnInit { @Input() message2 + deleteButtonEnabled = true + seconds = 0 + + delayConfirm(seconds: number) { + this.deleteButtonEnabled = false + this.seconds = seconds + setTimeout(() => { + if (this.seconds <= 1) { + this.deleteButtonEnabled = true + } else { + this.delayConfirm(seconds - 1) + } + }, 1000) + } + ngOnInit(): void { } From 16269f4721070c2897d6320586329a50571eebcc Mon Sep 17 00:00:00 2001 From: jonaswinkler Date: Fri, 11 Dec 2020 14:48:33 +0100 Subject: [PATCH 11/48] document list service: selection model --- .../services/document-list-view.service.ts | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src-ui/src/app/services/document-list-view.service.ts b/src-ui/src/app/services/document-list-view.service.ts index 149096591..b3fe351ac 100644 --- a/src-ui/src/app/services/document-list-view.service.ts +++ b/src-ui/src/app/services/document-list-view.service.ts @@ -118,6 +118,7 @@ export class DocumentListViewService { //want changes in the filter editor to propagate into here right away. this.view.filterRules = cloneFilterRules(filterRules) this.reload() + this.reduceSelectionToFilter() this.saveDocumentListView() } @@ -192,6 +193,49 @@ export class DocumentListViewService { } } + selected = new Set() + + selectNone() { + this.selected.clear() + } + + private reduceSelectionToFilter() { + if (this.selected.size > 0) { + this.documentService.listAllFilteredIds(this.filterRules).subscribe(ids => { + let subset = new Set() + for (let id of ids) { + if (this.selected.has(id)) { + subset.add(id) + } + } + this.selected = subset + }) + } + } + + selectAll() { + this.documentService.listAllFilteredIds(this.filterRules).subscribe(ids => ids.forEach(id => this.selected.add(id))) + } + + selectPage() { + this.selected.clear() + this.documents.forEach(doc => { + this.selected.add(doc.id) + }) + } + + isSelected(d: PaperlessDocument) { + return this.selected.has(d.id) + } + + setSelected(d: PaperlessDocument, value: boolean) { + if (value) { + this.selected.add(d.id) + } else if (!value) { + this.selected.delete(d.id) + } + } + constructor(private documentService: DocumentService) { let documentListViewConfigJson = sessionStorage.getItem(DOCUMENT_LIST_SERVICE.CURRENT_VIEW_CONFIG) if (documentListViewConfigJson) { From ab1d384e72186f05b460063013c41dafc4448e2f Mon Sep 17 00:00:00 2001 From: jonaswinkler Date: Fri, 11 Dec 2020 14:49:22 +0100 Subject: [PATCH 12/48] bulk edit menu and methods --- .../document-list.component.html | 34 +++--- .../document-list/document-list.component.ts | 112 +++++++++++++++++- 2 files changed, 129 insertions(+), 17 deletions(-) diff --git a/src-ui/src/app/components/document-list/document-list.component.html b/src-ui/src/app/components/document-list/document-list.component.html index 58c32e9d1..24def7d64 100644 --- a/src-ui/src/app/components/document-list/document-list.component.html +++ b/src-ui/src/app/components/document-list/document-list.component.html @@ -7,22 +7,19 @@ Bulk edit -
- - - +
+ + + - + + + + + + - - - - - - - - - +
@@ -101,7 +98,7 @@
-

{{list.collectionSize || 0}} document(s) (filtered)

+

Selected {{list.selected.size}} of {{list.collectionSize || 0}} document(s) (filtered)

@@ -113,6 +110,7 @@ + @@ -122,6 +120,12 @@ + diff --git a/src-ui/src/app/components/document-list/document-list.component.ts b/src-ui/src/app/components/document-list/document-list.component.ts index 09e73dd96..4d5597220 100644 --- a/src-ui/src/app/components/document-list/document-list.component.ts +++ b/src-ui/src/app/components/document-list/document-list.component.ts @@ -2,14 +2,21 @@ import { Component, OnInit } from '@angular/core'; import { Title } from '@angular/platform-browser'; import { ActivatedRoute } from '@angular/router'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; import { cloneFilterRules, FilterRule } from 'src/app/data/filter-rule'; import { FILTER_CORRESPONDENT, FILTER_DOCUMENT_TYPE, FILTER_HAS_TAG, FILTER_RULE_TYPES } from 'src/app/data/filter-rule-type'; import { SavedViewConfig } from 'src/app/data/saved-view-config'; import { DocumentListViewService } from 'src/app/services/document-list-view.service'; -import { DOCUMENT_SORT_FIELDS } from 'src/app/services/rest/document.service'; +import { CorrespondentService } from 'src/app/services/rest/correspondent.service'; +import { DocumentTypeService } from 'src/app/services/rest/document-type.service'; +import { DocumentService, DOCUMENT_SORT_FIELDS } from 'src/app/services/rest/document.service'; +import { TagService } from 'src/app/services/rest/tag.service'; import { SavedViewConfigService } from 'src/app/services/saved-view-config.service'; import { Toast, ToastService } from 'src/app/services/toast.service'; import { environment } from 'src/environments/environment'; +import { DeleteDialogComponent } from '../common/delete-dialog/delete-dialog.component'; +import { SelectDialogComponent } from '../common/select-dialog/select-dialog.component'; import { SaveViewConfigDialogComponent } from './save-view-config-dialog/save-view-config-dialog.component'; @Component({ @@ -25,7 +32,11 @@ export class DocumentListComponent implements OnInit { public route: ActivatedRoute, private toastService: ToastService, public modalService: NgbModal, - private titleService: Title) { } + private titleService: Title, + private correspondentService: CorrespondentService, + private documentTypeService: DocumentTypeService, + private tagService: TagService, + private documentService: DocumentService) { } displayMode = 'smallCards' // largeCards, smallCards, details @@ -142,4 +153,101 @@ export class DocumentListComponent implements OnInit { this.applyFilterRules() } + private executeBulkOperation(method: string, args): Observable { + return this.documentService.bulkEdit(Array.from(this.list.selected), method, args).pipe( + map(r => { + + this.list.reload() + this.list.selectNone() + + return r + }) + ) + } + + bulkSetCorrespondent() { + let modal = this.modalService.open(SelectDialogComponent, {backdrop: 'static'}) + modal.componentInstance.title = "Select correspondent" + modal.componentInstance.message = `Select the correspondent you wish to assign to ${this.list.selected.size} selected document(s):` + this.correspondentService.listAll().subscribe(response => { + modal.componentInstance.objects = response.results + }) + modal.componentInstance.selectClicked.subscribe(selectedId => { + this.executeBulkOperation('set_correspondent', {"correspondent": selectedId}).subscribe( + response => { + modal.close() + } + ) + }) + } + + bulkRemoveCorrespondent() { + this.executeBulkOperation('set_correspondent', {"correspondent": null}).subscribe(r => {}) + } + + bulkSetDocumentType() { + let modal = this.modalService.open(SelectDialogComponent, {backdrop: 'static'}) + modal.componentInstance.title = "Select document type" + modal.componentInstance.message = `Select the document type you wish to assign to ${this.list.selected.size} selected document(s):` + this.documentTypeService.listAll().subscribe(response => { + modal.componentInstance.objects = response.results + }) + modal.componentInstance.selectClicked.subscribe(selectedId => { + this.executeBulkOperation('set_document_type', {"document_type": selectedId}).subscribe( + response => { + modal.close() + } + ) + }) + } + + bulkRemoveDocumentType() { + this.executeBulkOperation('set_document_type', {"document_type": null}).subscribe(r => {}) + } + + bulkAddTag() { + let modal = this.modalService.open(SelectDialogComponent, {backdrop: 'static'}) + modal.componentInstance.title = "Select tag" + modal.componentInstance.message = `Select the tag you wish to assign to ${this.list.selected.size} selected document(s):` + this.tagService.listAll().subscribe(response => { + modal.componentInstance.objects = response.results + }) + modal.componentInstance.selectClicked.subscribe(selectedId => { + this.executeBulkOperation('add_tag', {"tag": selectedId}).subscribe( + response => { + modal.close() + } + ) + }) + } + + bulkRemoveTag() { + let modal = this.modalService.open(SelectDialogComponent, {backdrop: 'static'}) + modal.componentInstance.title = "Select tag" + modal.componentInstance.message = `Select the tag you wish to remove from ${this.list.selected.size} selected document(s):` + this.tagService.listAll().subscribe(response => { + modal.componentInstance.objects = response.results + }) + modal.componentInstance.selectClicked.subscribe(selectedId => { + this.executeBulkOperation('remove_tag', {"tag": selectedId}).subscribe( + response => { + modal.close() + } + ) + }) + } + + bulkDelete() { + let modal = this.modalService.open(DeleteDialogComponent, {backdrop: 'static'}) + modal.componentInstance.delayConfirm(5) + modal.componentInstance.message = `This operation will permanently delete all ${this.list.selected.size} selected document(s).` + modal.componentInstance.message2 = `This operation cannot be undone.` + modal.componentInstance.deleteClicked.subscribe(() => { + this.executeBulkOperation("delete", {}).subscribe( + response => { + modal.close() + } + ) + }) + } } From 6a648fc62c4ec5905fab5d2b25d6aed810809a32 Mon Sep 17 00:00:00 2001 From: jonaswinkler Date: Fri, 11 Dec 2020 14:51:20 +0100 Subject: [PATCH 13/48] import fix --- src/documents/views.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/documents/views.py b/src/documents/views.py index 4ce78348e..5e173d703 100755 --- a/src/documents/views.py +++ b/src/documents/views.py @@ -31,7 +31,6 @@ from rest_framework.viewsets import ( import documents.index as index from paperless.db import GnuPG from paperless.views import StandardPagination -from .bulk_edit import perform_bulk_edit from .filters import ( CorrespondentFilterSet, DocumentFilterSet, From 3b708276472b008fc733442276e385911a401b3f Mon Sep 17 00:00:00 2001 From: jonaswinkler Date: Fri, 11 Dec 2020 17:35:21 +0100 Subject: [PATCH 14/48] checboxes for small cards. does not work yet. --- .../document-card-small.component.html | 11 ++++++++++- .../document-card-small.component.scss | 8 ++++++++ .../document-card-small.component.ts | 2 ++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.html b/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.html index da469ebc4..8993674ba 100644 --- a/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.html +++ b/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.html @@ -1,7 +1,16 @@ -
+
+ +
+
+ + +
+
+ +
diff --git a/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.scss b/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.scss index 0068667d0..ba7190615 100644 --- a/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.scss +++ b/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.scss @@ -2,4 +2,12 @@ object-fit: cover; object-position: top; height: 200px; +} + +.document-card-check { + display: none +} + +.document-card:hover .document-card-check { + display: block; } \ No newline at end of file diff --git a/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.ts b/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.ts index d60552d4f..037c02cf0 100644 --- a/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.ts +++ b/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.ts @@ -13,6 +13,8 @@ export class DocumentCardSmallComponent implements OnInit { constructor(private documentService: DocumentService) { } + selected = false + @Input() document: PaperlessDocument From 8eaac5bac8f6852557b38c0dc9ca89b616a2aa85 Mon Sep 17 00:00:00 2001 From: jonaswinkler Date: Fri, 11 Dec 2020 23:33:59 +0100 Subject: [PATCH 15/48] codestyle --- src/documents/bulk_edit.py | 17 ++++++++++------- src/documents/tasks.py | 4 ++-- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/documents/bulk_edit.py b/src/documents/bulk_edit.py index 1349f9d54..aa5b8ea3f 100644 --- a/src/documents/bulk_edit.py +++ b/src/documents/bulk_edit.py @@ -8,11 +8,12 @@ def set_correspondent(doc_ids, correspondent): if correspondent: correspondent = Correspondent.objects.get(id=correspondent) - qs = Document.objects.filter(Q(id__in=doc_ids) & ~Q(correspondent=correspondent)) + qs = Document.objects.filter( + Q(id__in=doc_ids) & ~Q(correspondent=correspondent)) affected_docs = [doc.id for doc in qs] qs.update(correspondent=correspondent) - async_task("documents.tasks.bulk_rename_files", affected_docs) + async_task("documents.tasks.bulk_rename_files", document_ids=affected_docs) return "OK" @@ -21,11 +22,12 @@ def set_document_type(doc_ids, document_type): if document_type: document_type = DocumentType.objects.get(id=document_type) - qs = Document.objects.filter(Q(id__in=doc_ids) & ~Q(document_type=document_type)) + qs = Document.objects.filter( + Q(id__in=doc_ids) & ~Q(document_type=document_type)) affected_docs = [doc.id for doc in qs] qs.update(document_type=document_type) - async_task("documents.tasks.bulk_rename_files", affected_docs) + async_task("documents.tasks.bulk_rename_files", document_ids=affected_docs) return "OK" @@ -38,10 +40,11 @@ def add_tag(doc_ids, tag): DocumentTagRelationship = Document.tags.through DocumentTagRelationship.objects.bulk_create([ - DocumentTagRelationship(document_id=doc, tag_id=tag) for doc in affected_docs + DocumentTagRelationship( + document_id=doc, tag_id=tag) for doc in affected_docs ]) - async_task("documents.tasks.bulk_rename_files", affected_docs) + async_task("documents.tasks.bulk_rename_files", document_ids=affected_docs) return "OK" @@ -58,7 +61,7 @@ def remove_tag(doc_ids, tag): Q(tag_id=tag) ).delete() - async_task("documents.tasks.bulk_rename_files", affected_docs) + async_task("documents.tasks.bulk_rename_files", document_ids=affected_docs) return "OK" diff --git a/src/documents/tasks.py b/src/documents/tasks.py index af4c91448..fafe6e10f 100644 --- a/src/documents/tasks.py +++ b/src/documents/tasks.py @@ -90,7 +90,7 @@ def sanity_check(): return "No issues detected." -def bulk_rename_files(ids): - qs = Document.objects.filter(id__in=ids) +def bulk_rename_files(document_ids): + qs = Document.objects.filter(id__in=document_ids) for doc in qs: post_save.send(Document, instance=doc, created=False) From 19bfb426e001cbb57488948939f1ff7b4e15dda0 Mon Sep 17 00:00:00 2001 From: jonaswinkler Date: Fri, 11 Dec 2020 23:34:24 +0100 Subject: [PATCH 16/48] document list validation. --- src/documents/serialisers.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/documents/serialisers.py b/src/documents/serialisers.py index 5418ec0fb..92fc35719 100644 --- a/src/documents/serialisers.py +++ b/src/documents/serialisers.py @@ -185,6 +185,13 @@ class BulkEditSerializer(serializers.Serializer): parameters = serializers.DictField(allow_empty=True) + def validate_documents(self, documents): + count = Document.objects.filter(id__in=documents).count() + if not count == len(documents): + raise serializers.ValidationError( + "Some documents don't exist or were specified twice.") + return documents + def validate_method(self, method): if method == "set_correspondent": return bulk_edit.set_correspondent From 02673625dccbf15a5c4516dd2fd07bef041f2b9a Mon Sep 17 00:00:00 2001 From: jonaswinkler Date: Fri, 11 Dec 2020 23:34:34 +0100 Subject: [PATCH 17/48] tests. --- src/documents/tests/test_api.py | 116 +++++++++++++++++++++++++++++++- 1 file changed, 115 insertions(+), 1 deletion(-) diff --git a/src/documents/tests/test_api.py b/src/documents/tests/test_api.py index ab1716366..bd0d9a421 100644 --- a/src/documents/tests/test_api.py +++ b/src/documents/tests/test_api.py @@ -1,14 +1,16 @@ +import json import os import shutil import tempfile from unittest import mock from django.contrib.auth.models import User +from django.test import client from pathvalidate import ValidationError from rest_framework.test import APITestCase from whoosh.writing import AsyncWriter -from documents import index +from documents import index, bulk_edit from documents.models import Document, Correspondent, DocumentType, Tag from documents.tests.utils import DirectoriesMixin @@ -515,3 +517,115 @@ class TestDocumentApi(DirectoriesMixin, APITestCase): self.assertFalse(meta['has_archive_version']) self.assertGreater(len(meta['original_metadata']), 0) self.assertIsNone(meta['archive_metadata']) + + +class TestBulkEdit(DirectoriesMixin, APITestCase): + + def setUp(self): + super(TestBulkEdit, self).setUp() + + user = User.objects.create_superuser(username="temp_admin") + self.client.force_login(user=user) + + patcher = mock.patch('documents.bulk_edit.async_task') + self.async_task = patcher.start() + self.addCleanup(patcher.stop) + self.c1 = Correspondent.objects.create(name="c1") + self.c2 = Correspondent.objects.create(name="c2") + self.dt1 = DocumentType.objects.create(name="dt1") + self.dt2 = DocumentType.objects.create(name="dt2") + self.t1 = Tag.objects.create(name="t1") + self.t2 = Tag.objects.create(name="t2") + self.doc1 = Document.objects.create(checksum="A", title="A") + self.doc2 = Document.objects.create(checksum="B", title="B", correspondent=self.c1, document_type=self.dt1) + self.doc3 = Document.objects.create(checksum="C", title="C", correspondent=self.c2, document_type=self.dt2) + self.doc4 = Document.objects.create(checksum="D", title="D") + self.doc5 = Document.objects.create(checksum="E", title="E") + self.doc2.tags.add(self.t1) + self.doc3.tags.add(self.t2) + self.doc4.tags.add(self.t1, self.t2) + + def test_set_correspondent(self): + self.assertEqual(Document.objects.filter(correspondent=self.c2).count(), 1) + bulk_edit.set_correspondent([self.doc1.id, self.doc2.id, self.doc3.id], self.c2.id) + self.assertEqual(Document.objects.filter(correspondent=self.c2).count(), 3) + self.async_task.assert_called_once() + args, kwargs = self.async_task.call_args + self.assertCountEqual(kwargs['document_ids'], [self.doc1.id, self.doc2.id]) + + def test_unset_correspondent(self): + self.assertEqual(Document.objects.filter(correspondent=self.c2).count(), 1) + bulk_edit.set_correspondent([self.doc1.id, self.doc2.id, self.doc3.id], None) + self.assertEqual(Document.objects.filter(correspondent=self.c2).count(), 0) + self.async_task.assert_called_once() + args, kwargs = self.async_task.call_args + self.assertCountEqual(kwargs['document_ids'], [self.doc2.id, self.doc3.id]) + + def test_set_document_type(self): + self.assertEqual(Document.objects.filter(document_type=self.dt2).count(), 1) + bulk_edit.set_document_type([self.doc1.id, self.doc2.id, self.doc3.id], self.dt2.id) + self.assertEqual(Document.objects.filter(document_type=self.dt2).count(), 3) + self.async_task.assert_called_once() + args, kwargs = self.async_task.call_args + self.assertCountEqual(kwargs['document_ids'], [self.doc1.id, self.doc2.id]) + + def test_unset_document_type(self): + self.assertEqual(Document.objects.filter(document_type=self.dt2).count(), 1) + bulk_edit.set_document_type([self.doc1.id, self.doc2.id, self.doc3.id], None) + self.assertEqual(Document.objects.filter(document_type=self.dt2).count(), 0) + self.async_task.assert_called_once() + args, kwargs = self.async_task.call_args + self.assertCountEqual(kwargs['document_ids'], [self.doc2.id, self.doc3.id]) + + def test_add_tag(self): + self.assertEqual(Document.objects.filter(tags__id=self.t1.id).count(), 2) + bulk_edit.add_tag([self.doc1.id, self.doc2.id, self.doc3.id, self.doc4.id], self.t1.id) + self.assertEqual(Document.objects.filter(tags__id=self.t1.id).count(), 4) + self.async_task.assert_called_once() + args, kwargs = self.async_task.call_args + self.assertCountEqual(kwargs['document_ids'], [self.doc1.id, self.doc3.id]) + + + def test_remove_tag(self): + self.assertEqual(Document.objects.filter(tags__id=self.t1.id).count(), 2) + bulk_edit.remove_tag([self.doc1.id, self.doc3.id, self.doc4.id], self.t1.id) + self.assertEqual(Document.objects.filter(tags__id=self.t1.id).count(), 1) + self.async_task.assert_called_once() + args, kwargs = self.async_task.call_args + self.assertCountEqual(kwargs['document_ids'], [self.doc4.id]) + + def test_delete(self): + self.assertEqual(Document.objects.count(), 5) + bulk_edit.delete([self.doc1.id, self.doc2.id]) + self.assertEqual(Document.objects.count(), 3) + self.assertCountEqual([doc.id for doc in Document.objects.all()], [self.doc3.id, self.doc4.id, self.doc5.id]) + + def test_api(self): + self.assertEqual(Document.objects.count(), 5) + response = self.client.post("/api/documents/bulk_edit/", json.dumps({ + "documents": [self.doc1.id], + "method": "delete", + "parameters": {} + }), content_type='application/json') + self.assertEqual(response.status_code, 200) + self.assertEqual(Document.objects.count(), 4) + + def test_api_invalid_doc(self): + self.assertEqual(Document.objects.count(), 5) + response = self.client.post("/api/documents/bulk_edit/", json.dumps({ + "documents": [-235], + "method": "delete", + "parameters": {} + }), content_type='application/json') + self.assertEqual(response.status_code, 400) + self.assertEqual(Document.objects.count(), 5) + + def test_api_invalid_method(self): + self.assertEqual(Document.objects.count(), 5) + response = self.client.post("/api/documents/bulk_edit/", json.dumps({ + "documents": [self.doc2.id], + "method": "exterminate", + "parameters": {} + }), content_type='application/json') + self.assertEqual(response.status_code, 400) + self.assertEqual(Document.objects.count(), 5) From 1f86023af8e46ff57cb808d68eee1ea0b259bf9f Mon Sep 17 00:00:00 2001 From: jonaswinkler Date: Sun, 13 Dec 2020 14:10:55 +0100 Subject: [PATCH 18/48] selection for small cards --- .../document-card-small.component.html | 10 +++++----- .../document-card-small.component.scss | 11 +++++++++++ .../document-card-small.component.ts | 15 ++++++++++++++- .../document-list/document-list.component.html | 2 +- src-ui/src/theme.scss | 1 + 5 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.html b/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.html index 6909a24fb..b78fedfe3 100644 --- a/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.html +++ b/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.html @@ -1,12 +1,12 @@
-
-
- +
+
+
- - + +
diff --git a/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.scss b/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.scss index ba7190615..36db2203c 100644 --- a/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.scss +++ b/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.scss @@ -1,7 +1,10 @@ +@import "/src/theme"; + .doc-img { object-fit: cover; object-position: top; height: 200px; + mix-blend-mode: multiply; } .document-card-check { @@ -10,4 +13,12 @@ .document-card:hover .document-card-check { display: block; +} + +.card-selected { + border-color: $primary; +} + +.doc-img-background-selected { + background-color: $primaryFaded; } \ No newline at end of file diff --git a/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.ts b/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.ts index 037c02cf0..5d664697b 100644 --- a/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.ts +++ b/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.ts @@ -13,7 +13,20 @@ export class DocumentCardSmallComponent implements OnInit { constructor(private documentService: DocumentService) { } - selected = false + _selected = false + + get selected() { + return this._selected + } + + @Input() + set selected(value: boolean) { + this._selected = value + this.selectedChange.emit(value) + } + + @Output() + selectedChange = new EventEmitter() @Input() document: PaperlessDocument diff --git a/src-ui/src/app/components/document-list/document-list.component.html b/src-ui/src/app/components/document-list/document-list.component.html index a87a89bbf..0c3674421 100644 --- a/src-ui/src/app/components/document-list/document-list.component.html +++ b/src-ui/src/app/components/document-list/document-list.component.html @@ -155,5 +155,5 @@
- +
diff --git a/src-ui/src/theme.scss b/src-ui/src/theme.scss index 88f3ae30f..df2aea003 100644 --- a/src-ui/src/theme.scss +++ b/src-ui/src/theme.scss @@ -1,5 +1,6 @@ $paperless-green: #17541f; $primary: #17541f; +$primaryFaded: #d1ddd2; $theme-colors: ( "primary": $primary From f9cdab480eeec5d496b9113a68c39cf331812671 Mon Sep 17 00:00:00 2001 From: jonaswinkler Date: Sun, 13 Dec 2020 15:20:24 +0100 Subject: [PATCH 19/48] confirm dialogs for remove operations --- .../document-list/document-list.component.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src-ui/src/app/components/document-list/document-list.component.ts b/src-ui/src/app/components/document-list/document-list.component.ts index 36c70a00e..ce4ebec73 100644 --- a/src-ui/src/app/components/document-list/document-list.component.ts +++ b/src-ui/src/app/components/document-list/document-list.component.ts @@ -182,7 +182,14 @@ export class DocumentListComponent implements OnInit { } bulkRemoveCorrespondent() { - this.executeBulkOperation('set_correspondent', {"correspondent": null}).subscribe(r => {}) + let modal = this.modalService.open(ConfirmDialogComponent, {backdrop: 'static'}) + modal.componentInstance.title = "Remove correspondent" + modal.componentInstance.message = `This operation will remove the correspondent from all ${this.list.selected.size} selected document(s).` + modal.componentInstance.confirmClicked.subscribe(() => { + this.executeBulkOperation('set_correspondent', {"correspondent": null}).subscribe(r => { + modal.close() + }) + }) } bulkSetDocumentType() { @@ -202,7 +209,14 @@ export class DocumentListComponent implements OnInit { } bulkRemoveDocumentType() { - this.executeBulkOperation('set_document_type', {"document_type": null}).subscribe(r => {}) + let modal = this.modalService.open(ConfirmDialogComponent, {backdrop: 'static'}) + modal.componentInstance.title = "Remove document type" + modal.componentInstance.message = `This operation will remove the document type from all ${this.list.selected.size} selected document(s).` + modal.componentInstance.confirmClicked.subscribe(() => { + this.executeBulkOperation('set_document_type', {"document_type": null}).subscribe(r => { + modal.close() + }) + }) } bulkAddTag() { From 2204c30afdd66703e4b2d5ff4a42b5c2027eeec2 Mon Sep 17 00:00:00 2001 From: jonaswinkler Date: Sun, 13 Dec 2020 15:28:20 +0100 Subject: [PATCH 20/48] table selection highlighting --- .../components/document-list/document-list.component.html | 2 +- .../components/document-list/document-list.component.scss | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src-ui/src/app/components/document-list/document-list.component.html b/src-ui/src/app/components/document-list/document-list.component.html index 0c3674421..396e7e12d 100644 --- a/src-ui/src/app/components/document-list/document-list.component.html +++ b/src-ui/src/app/components/document-list/document-list.component.html @@ -119,7 +119,7 @@
- + - + @@ -117,5 +117,5 @@
- +
diff --git a/src-ui/src/app/components/document-list/document-list.component.ts b/src-ui/src/app/components/document-list/document-list.component.ts index 25d92e9db..2d5257804 100644 --- a/src-ui/src/app/components/document-list/document-list.component.ts +++ b/src-ui/src/app/components/document-list/document-list.component.ts @@ -2,6 +2,7 @@ import { Component, OnInit, ViewChild } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { FILTER_CORRESPONDENT } from 'src/app/data/filter-rule-type'; +import { PaperlessDocument } from 'src/app/data/paperless-document'; import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'; import { DocumentListViewService } from 'src/app/services/document-list-view.service'; import { DOCUMENT_SORT_FIELDS } from 'src/app/services/rest/document.service'; @@ -113,4 +114,7 @@ export class DocumentListComponent implements OnInit { this.filterEditor.toggleDocumentType(documentTypeID) } + trackByDocumentId(index, item: PaperlessDocument) { + return item.id + } } From 8a1b9141f14786b7e9a5a7730222101ed48aae57 Mon Sep 17 00:00:00 2001 From: jonaswinkler Date: Tue, 22 Dec 2020 12:40:06 +0100 Subject: [PATCH 29/48] more tests --- src/documents/tests/test_api.py | 52 +++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/documents/tests/test_api.py b/src/documents/tests/test_api.py index 5d2e6a3c5..28aa05513 100644 --- a/src/documents/tests/test_api.py +++ b/src/documents/tests/test_api.py @@ -64,6 +64,58 @@ class TestDocumentApi(DirectoriesMixin, APITestCase): self.assertEqual(len(Document.objects.all()), 0) + def test_document_fields(self): + c = Correspondent.objects.create(name="c", pk=41) + dt = DocumentType.objects.create(name="dt", pk=63) + tag = Tag.objects.create(name="t", pk=85) + doc = Document.objects.create(title="WOW", content="the content", correspondent=c, document_type=dt, checksum="123", mime_type="application/pdf") + + response = self.client.get("/api/documents/", format='json') + self.assertEqual(response.status_code, 200) + results_full = response.data['results'] + self.assertTrue("content" in results_full[0]) + self.assertTrue("id" in results_full[0]) + + response = self.client.get("/api/documents/?fields=id", format='json') + self.assertEqual(response.status_code, 200) + results = response.data['results'] + self.assertFalse("content" in results[0]) + self.assertTrue("id" in results[0]) + self.assertEqual(len(results[0]), 1) + + response = self.client.get("/api/documents/?fields=content", format='json') + self.assertEqual(response.status_code, 200) + results = response.data['results'] + self.assertTrue("content" in results[0]) + self.assertFalse("id" in results[0]) + self.assertEqual(len(results[0]), 1) + + response = self.client.get("/api/documents/?fields=id,content", format='json') + self.assertEqual(response.status_code, 200) + results = response.data['results'] + self.assertTrue("content" in results[0]) + self.assertTrue("id" in results[0]) + self.assertEqual(len(results[0]), 2) + + response = self.client.get("/api/documents/?fields=id,conteasdnt", format='json') + self.assertEqual(response.status_code, 200) + results = response.data['results'] + self.assertFalse("content" in results[0]) + self.assertTrue("id" in results[0]) + self.assertEqual(len(results[0]), 1) + + response = self.client.get("/api/documents/?fields=", format='json') + self.assertEqual(response.status_code, 200) + results = response.data['results'] + self.assertEqual(results_full, results) + + response = self.client.get("/api/documents/?fields=dgfhs", format='json') + self.assertEqual(response.status_code, 200) + results = response.data['results'] + self.assertEqual(len(results[0]), 0) + + + def test_document_actions(self): _, filename = tempfile.mkstemp(dir=self.dirs.originals_dir) From 3d9f3f59dfe7027b6a3549368a4460ecf70b0502 Mon Sep 17 00:00:00 2001 From: jonaswinkler Date: Tue, 22 Dec 2020 13:03:50 +0100 Subject: [PATCH 30/48] more tests --- src/documents/tests/test_api.py | 130 +++++++++++++++++++++++++++++++- 1 file changed, 126 insertions(+), 4 deletions(-) diff --git a/src/documents/tests/test_api.py b/src/documents/tests/test_api.py index 28aa05513..bce2a433d 100644 --- a/src/documents/tests/test_api.py +++ b/src/documents/tests/test_api.py @@ -735,7 +735,6 @@ class TestBulkEdit(DirectoriesMixin, APITestCase): args, kwargs = self.async_task.call_args self.assertCountEqual(kwargs['document_ids'], [self.doc1.id, self.doc3.id]) - def test_remove_tag(self): self.assertEqual(Document.objects.filter(tags__id=self.t1.id).count(), 2) bulk_edit.remove_tag([self.doc1.id, self.doc3.id, self.doc4.id], self.t1.id) @@ -750,15 +749,103 @@ class TestBulkEdit(DirectoriesMixin, APITestCase): self.assertEqual(Document.objects.count(), 3) self.assertCountEqual([doc.id for doc in Document.objects.all()], [self.doc3.id, self.doc4.id, self.doc5.id]) - def test_api(self): - self.assertEqual(Document.objects.count(), 5) + @mock.patch("documents.serialisers.bulk_edit.set_correspondent") + def test_api_set_correspondent(self, m): + m.return_value = "OK" + response = self.client.post("/api/documents/bulk_edit/", json.dumps({ + "documents": [self.doc1.id], + "method": "set_correspondent", + "parameters": {"correspondent": self.c1.id} + }), content_type='application/json') + self.assertEqual(response.status_code, 200) + m.assert_called_once() + args, kwargs = m.call_args + self.assertEqual(args[0], [self.doc1.id]) + self.assertEqual(kwargs['correspondent'], self.c1.id) + + @mock.patch("documents.serialisers.bulk_edit.set_correspondent") + def test_api_unset_correspondent(self, m): + m.return_value = "OK" + response = self.client.post("/api/documents/bulk_edit/", json.dumps({ + "documents": [self.doc1.id], + "method": "set_correspondent", + "parameters": {"correspondent": None} + }), content_type='application/json') + self.assertEqual(response.status_code, 200) + m.assert_called_once() + args, kwargs = m.call_args + self.assertEqual(args[0], [self.doc1.id]) + self.assertIsNone(kwargs['correspondent']) + + @mock.patch("documents.serialisers.bulk_edit.set_document_type") + def test_api_set_type(self, m): + m.return_value = "OK" + response = self.client.post("/api/documents/bulk_edit/", json.dumps({ + "documents": [self.doc1.id], + "method": "set_document_type", + "parameters": {"document_type": self.dt1.id} + }), content_type='application/json') + self.assertEqual(response.status_code, 200) + m.assert_called_once() + args, kwargs = m.call_args + self.assertEqual(args[0], [self.doc1.id]) + self.assertEqual(kwargs['document_type'], self.dt1.id) + + @mock.patch("documents.serialisers.bulk_edit.set_document_type") + def test_api_unset_type(self, m): + m.return_value = "OK" + response = self.client.post("/api/documents/bulk_edit/", json.dumps({ + "documents": [self.doc1.id], + "method": "set_document_type", + "parameters": {"document_type": None} + }), content_type='application/json') + self.assertEqual(response.status_code, 200) + m.assert_called_once() + args, kwargs = m.call_args + self.assertEqual(args[0], [self.doc1.id]) + self.assertIsNone(kwargs['document_type']) + + @mock.patch("documents.serialisers.bulk_edit.add_tag") + def test_api_add_tag(self, m): + m.return_value = "OK" + response = self.client.post("/api/documents/bulk_edit/", json.dumps({ + "documents": [self.doc1.id], + "method": "add_tag", + "parameters": {"tag": self.t1.id} + }), content_type='application/json') + self.assertEqual(response.status_code, 200) + m.assert_called_once() + args, kwargs = m.call_args + self.assertEqual(args[0], [self.doc1.id]) + self.assertEqual(kwargs['tag'], self.t1.id) + + @mock.patch("documents.serialisers.bulk_edit.remove_tag") + def test_api_remove_tag(self, m): + m.return_value = "OK" + response = self.client.post("/api/documents/bulk_edit/", json.dumps({ + "documents": [self.doc1.id], + "method": "remove_tag", + "parameters": {"tag": self.t1.id} + }), content_type='application/json') + self.assertEqual(response.status_code, 200) + m.assert_called_once() + args, kwargs = m.call_args + self.assertEqual(args[0], [self.doc1.id]) + self.assertEqual(kwargs['tag'], self.t1.id) + + @mock.patch("documents.serialisers.bulk_edit.delete") + def test_api_delete(self, m): + m.return_value = "OK" response = self.client.post("/api/documents/bulk_edit/", json.dumps({ "documents": [self.doc1.id], "method": "delete", "parameters": {} }), content_type='application/json') self.assertEqual(response.status_code, 200) - self.assertEqual(Document.objects.count(), 4) + m.assert_called_once() + args, kwargs = m.call_args + self.assertEqual(args[0], [self.doc1.id]) + self.assertEqual(len(kwargs), 0) def test_api_invalid_doc(self): self.assertEqual(Document.objects.count(), 5) @@ -779,3 +866,38 @@ class TestBulkEdit(DirectoriesMixin, APITestCase): }), content_type='application/json') self.assertEqual(response.status_code, 400) self.assertEqual(Document.objects.count(), 5) + + def test_api_invalid_correspondent(self): + self.assertEqual(self.doc2.correspondent, self.c1) + response = self.client.post("/api/documents/bulk_edit/", json.dumps({ + "documents": [self.doc2.id], + "method": "set_correspondent", + "parameters": {'correspondent': 345657} + }), content_type='application/json') + self.assertEqual(response.status_code, 400) + + doc2 = Document.objects.get(id=self.doc2.id) + self.assertEqual(doc2.correspondent, self.c1) + + def test_api_invalid_document_type(self): + self.assertEqual(self.doc2.document_type, self.dt1) + response = self.client.post("/api/documents/bulk_edit/", json.dumps({ + "documents": [self.doc2.id], + "method": "set_document_type", + "parameters": {'document_type': 345657} + }), content_type='application/json') + self.assertEqual(response.status_code, 400) + + doc2 = Document.objects.get(id=self.doc2.id) + self.assertEqual(doc2.document_type, self.dt1) + + def test_api_invalid_tag(self): + self.assertEqual(list(self.doc2.tags.all()), [self.t1]) + response = self.client.post("/api/documents/bulk_edit/", json.dumps({ + "documents": [self.doc2.id], + "method": "add_tag", + "parameters": {'document_type': 345657} + }), content_type='application/json') + self.assertEqual(response.status_code, 400) + + self.assertEqual(list(self.doc2.tags.all()), [self.t1]) From f4287be56b03e52d59517b5fa5efe5fc699803e8 Mon Sep 17 00:00:00 2001 From: jonaswinkler Date: Tue, 22 Dec 2020 13:04:08 +0100 Subject: [PATCH 31/48] more tests --- src/documents/checks.py | 2 +- src/documents/tests/test_checks.py | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/documents/checks.py b/src/documents/checks.py index b6da5bfc9..ba55b1397 100644 --- a/src/documents/checks.py +++ b/src/documents/checks.py @@ -51,6 +51,6 @@ def parser_check(app_configs, **kwargs): if len(parsers) == 0: return [Error("No parsers found. This is a bug. The consumer won't be " - "able to onsume any documents without parsers.")] + "able to consume any documents without parsers.")] else: return [] diff --git a/src/documents/tests/test_checks.py b/src/documents/tests/test_checks.py index 1027c11a0..ee4fbe8d1 100644 --- a/src/documents/tests/test_checks.py +++ b/src/documents/tests/test_checks.py @@ -1,9 +1,12 @@ import unittest +from unittest import mock +from django.core.checks import Error from django.test import TestCase from .factories import DocumentFactory -from ..checks import changed_password_check +from .. import document_consumer_declaration +from ..checks import changed_password_check, parser_check from ..models import Document @@ -15,3 +18,13 @@ class ChecksTestCase(TestCase): def test_changed_password_check_no_encryption(self): DocumentFactory.create(storage_type=Document.STORAGE_TYPE_UNENCRYPTED) self.assertEqual(changed_password_check(None), []) + + def test_parser_check(self): + + self.assertEqual(parser_check(None), []) + + with mock.patch('documents.checks.document_consumer_declaration.send') as m: + m.return_value = [] + + self.assertEqual(parser_check(None), [Error("No parsers found. This is a bug. The consumer won't be " + "able to consume any documents without parsers.")]) From 4cbde3f2d10f48203efe6a19fad7aa78de588234 Mon Sep 17 00:00:00 2001 From: jonaswinkler Date: Tue, 22 Dec 2020 13:04:22 +0100 Subject: [PATCH 32/48] increase indexing speed --- src/documents/management/commands/document_index.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/documents/management/commands/document_index.py b/src/documents/management/commands/document_index.py index 7dfdbaa42..08e20e1d2 100644 --- a/src/documents/management/commands/document_index.py +++ b/src/documents/management/commands/document_index.py @@ -1,4 +1,5 @@ from django.core.management import BaseCommand +from django.db import transaction from documents.mixins import Renderable from documents.tasks import index_reindex, index_optimize @@ -18,8 +19,8 @@ class Command(Renderable, BaseCommand): def handle(self, *args, **options): self.verbosity = options["verbosity"] - - if options['command'] == 'reindex': - index_reindex() - elif options['command'] == 'optimize': - index_optimize() + with transaction.atomic(): + if options['command'] == 'reindex': + index_reindex() + elif options['command'] == 'optimize': + index_optimize() From 5850e4ad3890aa94b9fd2b36285d941b49768cfb Mon Sep 17 00:00:00 2001 From: jonaswinkler Date: Tue, 22 Dec 2020 13:32:30 +0100 Subject: [PATCH 33/48] fixes an issue with open documents not refreshing after bulk operations --- .../document-list/document-list.component.ts | 15 ++++++++------- src-ui/src/app/services/open-documents.service.ts | 12 +++++++++++- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src-ui/src/app/components/document-list/document-list.component.ts b/src-ui/src/app/components/document-list/document-list.component.ts index 5eb97b08d..f72a92aa9 100644 --- a/src-ui/src/app/components/document-list/document-list.component.ts +++ b/src-ui/src/app/components/document-list/document-list.component.ts @@ -2,8 +2,7 @@ import { Component, OnInit, ViewChild } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; -import { FILTER_CORRESPONDENT } from 'src/app/data/filter-rule-type'; +import { tap } from 'rxjs/operators'; import { PaperlessDocument } from 'src/app/data/paperless-document'; import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'; import { DocumentListViewService } from 'src/app/services/document-list-view.service'; @@ -17,6 +16,7 @@ import { FilterEditorComponent } from '../filter-editor/filter-editor.component' import { ConfirmDialogComponent } from '../common/confirm-dialog/confirm-dialog.component'; import { SelectDialogComponent } from '../common/select-dialog/select-dialog.component'; import { SaveViewConfigDialogComponent } from './save-view-config-dialog/save-view-config-dialog.component'; +import { OpenDocumentsService } from 'src/app/services/open-documents.service'; @Component({ selector: 'app-document-list', @@ -35,7 +35,8 @@ export class DocumentListComponent implements OnInit { private correspondentService: CorrespondentService, private documentTypeService: DocumentTypeService, private tagService: TagService, - private documentService: DocumentService) { } + private documentService: DocumentService, + private openDocumentService: OpenDocumentsService) { } @ViewChild("filterEditor") private filterEditor: FilterEditorComponent @@ -131,12 +132,12 @@ export class DocumentListComponent implements OnInit { private executeBulkOperation(method: string, args): Observable { return this.documentService.bulkEdit(Array.from(this.list.selected), method, args).pipe( - map(r => { - + tap(() => { this.list.reload() + this.list.selected.forEach(id => { + this.openDocumentService.refreshDocument(id) + }) this.list.selectNone() - - return r }) ) } diff --git a/src-ui/src/app/services/open-documents.service.ts b/src-ui/src/app/services/open-documents.service.ts index e37f5db8c..c91031f83 100644 --- a/src-ui/src/app/services/open-documents.service.ts +++ b/src-ui/src/app/services/open-documents.service.ts @@ -1,6 +1,7 @@ import { Injectable } from '@angular/core'; import { PaperlessDocument } from '../data/paperless-document'; import { OPEN_DOCUMENT_SERVICE } from '../data/storage-keys'; +import { DocumentService } from './rest/document.service'; @Injectable({ providedIn: 'root' @@ -9,7 +10,7 @@ export class OpenDocumentsService { private MAX_OPEN_DOCUMENTS = 5 - constructor() { + constructor(private documentService: DocumentService) { if (sessionStorage.getItem(OPEN_DOCUMENT_SERVICE.DOCUMENTS)) { try { this.openDocuments = JSON.parse(sessionStorage.getItem(OPEN_DOCUMENT_SERVICE.DOCUMENTS)) @@ -22,6 +23,15 @@ export class OpenDocumentsService { private openDocuments: PaperlessDocument[] = [] + refreshDocument(id: number) { + let index = this.openDocuments.findIndex(doc => doc.id == id) + if (index > -1) { + this.documentService.get(id).subscribe(doc => { + this.openDocuments[index] = doc + }) + } + } + getOpenDocuments(): PaperlessDocument[] { return this.openDocuments } From 77fd6bba8f64c396b40c583bf9649745c8562d13 Mon Sep 17 00:00:00 2001 From: jonaswinkler Date: Tue, 22 Dec 2020 13:39:46 +0100 Subject: [PATCH 34/48] fixes clearing fields that should not be clearable --- .../app/components/common/input/select/select.component.html | 1 + .../components/document-detail/document-detail.component.html | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src-ui/src/app/components/common/input/select/select.component.html b/src-ui/src/app/components/common/input/select/select.component.html index d33dae425..780dc5686 100644 --- a/src-ui/src/app/components/common/input/select/select.component.html +++ b/src-ui/src/app/components/common/input/select/select.component.html @@ -5,6 +5,7 @@ [disabled]="disabled" [style.color]="textColor" [style.background]="backgroundColor" + [clearable]="allowNull" (change)="onChange(value)" (blur)="onTouched()"> {{i.name}} diff --git a/src-ui/src/app/components/document-detail/document-detail.component.html b/src-ui/src/app/components/document-detail/document-detail.component.html index 228264378..ae3fb0c0a 100644 --- a/src-ui/src/app/components/document-detail/document-detail.component.html +++ b/src-ui/src/app/components/document-detail/document-detail.component.html @@ -67,9 +67,9 @@ formControlName='archive_serial_number'> - - From 103a7eee5b9aedee559f3980bcf40ea55e3373a8 Mon Sep 17 00:00:00 2001 From: jonaswinkler Date: Tue, 22 Dec 2020 13:54:41 +0100 Subject: [PATCH 35/48] changelog --- docs/changelog.rst | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index a993eb530..795582260 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -6,6 +6,38 @@ Changelog ********* +paperless-ng 0.9.9 +################## + +Christmas release! + +Bulk editing + +* Paperless now supports bulk editing. +* The following operations are available: Add and remove correspondents, tags, document types from selected documents, as well as mass-deleting documents. +* We've got a more fancy UI in the works that makes these features more accessible, but that's not quite ready yet. + +Searching + +* Paperless now supports searching for similar documents ("More like this") both from the document detail page as well as from individual search results. +* A search score indicates how well a document matches the search query, or how similar a document is to a given reference document. + +Changes + +* Clarification in the UI that the fields "Match" and "Is insensitive" are not relevant for the Auto matching algorithm. +* New select interface for tags, types and correspondents allows filtering. This also improves tag selection. Thanks again to `Michael Shamoon`_! +* Page navigation controls for the document viewer, thanks to `Michael Shamoon`_. + +Fixes + +* An error that caused the document importer to crash was fixed. +* An issue with changes not being possible when ``PAPERLESS_COOKIE_PREFIX`` is used was fixed. +* The date selection filters now allow manual entry of dates. + +Feature Removal + +* Most of the guesswork features have been removed. Paperless no longer tries to extract correspondents and tags from file names. + paperless-ng 0.9.8 ################## From c712640dd38fa44a086b93065054907a8915bd1d Mon Sep 17 00:00:00 2001 From: jonaswinkler Date: Tue, 22 Dec 2020 14:09:38 +0100 Subject: [PATCH 36/48] versions --- docker/hub/docker-compose.postgres.yml | 2 +- docker/hub/docker-compose.sqlite.yml | 2 +- src-ui/src/environments/environment.prod.ts | 2 +- src/paperless/version.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docker/hub/docker-compose.postgres.yml b/docker/hub/docker-compose.postgres.yml index d33e4c38d..e7a32bec0 100644 --- a/docker/hub/docker-compose.postgres.yml +++ b/docker/hub/docker-compose.postgres.yml @@ -15,7 +15,7 @@ services: POSTGRES_PASSWORD: paperless webserver: - image: jonaswinkler/paperless-ng:0.9.8 + image: jonaswinkler/paperless-ng:0.9.9 restart: always depends_on: - db diff --git a/docker/hub/docker-compose.sqlite.yml b/docker/hub/docker-compose.sqlite.yml index c130dfef6..98b7d70a2 100644 --- a/docker/hub/docker-compose.sqlite.yml +++ b/docker/hub/docker-compose.sqlite.yml @@ -5,7 +5,7 @@ services: restart: always webserver: - image: jonaswinkler/paperless-ng:0.9.8 + image: jonaswinkler/paperless-ng:0.9.9 restart: always depends_on: - broker diff --git a/src-ui/src/environments/environment.prod.ts b/src-ui/src/environments/environment.prod.ts index f12c6a7cb..ab6b07c73 100644 --- a/src-ui/src/environments/environment.prod.ts +++ b/src-ui/src/environments/environment.prod.ts @@ -2,5 +2,5 @@ export const environment = { production: true, apiBaseUrl: "/api/", appTitle: "Paperless-ng", - version: "0.9.8" + version: "0.9.9" }; diff --git a/src/paperless/version.py b/src/paperless/version.py index 10283c145..b1dfc590c 100644 --- a/src/paperless/version.py +++ b/src/paperless/version.py @@ -1 +1 @@ -__version__ = (0, 9, 8) +__version__ = (0, 9, 9) From 491ef74c31aa008ef385a09d3f56815955617f76 Mon Sep 17 00:00:00 2001 From: jonaswinkler Date: Tue, 22 Dec 2020 14:13:58 +0100 Subject: [PATCH 37/48] more change log --- docs/changelog.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 795582260..db3378011 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -27,6 +27,8 @@ Changes * Clarification in the UI that the fields "Match" and "Is insensitive" are not relevant for the Auto matching algorithm. * New select interface for tags, types and correspondents allows filtering. This also improves tag selection. Thanks again to `Michael Shamoon`_! * Page navigation controls for the document viewer, thanks to `Michael Shamoon`_. +* Layout changes to the small cards document list. +* The dashboard now displays the username (or full name if specified in the admin) on the dashboard. Fixes From dead41c11dd7709659e8a0e87a41ccbc30de8e90 Mon Sep 17 00:00:00 2001 From: jonaswinkler Date: Tue, 22 Dec 2020 14:43:16 +0100 Subject: [PATCH 38/48] documentation --- docs/setup.rst | 2 ++ docs/troubleshooting.rst | 28 +++++++++++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/docs/setup.rst b/docs/setup.rst index e5e6526ea..908704fc2 100644 --- a/docs/setup.rst +++ b/docs/setup.rst @@ -120,6 +120,8 @@ The `bare metal route`_ is more complicated to setup but makes it easier should you want to contribute some code back. You need to configure and run the above mentioned components yourself. +.. _setup-docker_route: + Docker Route ============ diff --git a/docs/troubleshooting.rst b/docs/troubleshooting.rst index dc5bf7f5d..4c06ec4cd 100644 --- a/docs/troubleshooting.rst +++ b/docs/troubleshooting.rst @@ -39,7 +39,7 @@ Operation not permitted You might see errors such as: -.. code:: +.. code:: shell-session chown: changing ownership of '../export': Operation not permitted @@ -49,3 +49,29 @@ to these folders. This happens when pointing these directories to NFS shares, for example. Ensure that `chown` is possible on these directories. + +Classifier error: No training data available +############################################ + +This indicates that the Auto matching algorithm found no documents to learn from. +This may have two reasons: + +* You don't use the Auto matching algorithm: The error can be safely ignored in this case. +* You are using the Auto matching algorithm: The classifier explicitly excludes documents + with Inbox tags. Verify that there are documents in your archive without inbox tags. + The algorithm will only learn from documents not in your inbox. + +Permission denied errors in the consumption directory +##################################################### + +You might encounter errors such as: + +.. code:: shell-session + + The following error occured while consuming document.pdf: [Errno 13] Permission denied: '/usr/src/paperless/src/../consume/document.pdf' + +This happens when paperless does not have permission to delete files inside the consumption directory. +Ensure that ``USERMAP_UID`` and ``USERMAP_GID`` are set to the user id and group id you use on the host operating system, if these are +different from ``1000``. See :ref:`setup-docker_route`. + +Also ensure that you are able to read and write to the consumption directory on the host. From 3e88046e7be0ea79e2d3a8345e4d8ae4b038d80e Mon Sep 17 00:00:00 2001 From: jonaswinkler Date: Tue, 22 Dec 2020 15:18:42 +0100 Subject: [PATCH 39/48] discard selection when switching views --- src-ui/src/app/services/document-list-view.service.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src-ui/src/app/services/document-list-view.service.ts b/src-ui/src/app/services/document-list-view.service.ts index 00f84048b..b148d4087 100644 --- a/src-ui/src/app/services/document-list-view.service.ts +++ b/src-ui/src/app/services/document-list-view.service.ts @@ -40,10 +40,14 @@ export class DocumentListViewService { } set savedView(value: PaperlessSavedView) { - if (value) { + if (value && !this._savedViewConfig || value && value.id != this._savedViewConfig.id) { + //saved view inactive and should be active now, or saved view active, but a different view is requested //this is here so that we don't modify value, which might be the actual instance of the saved view. + this.selectNone() this._savedViewConfig = Object.assign({}, value) - } else { + } else if (this._savedViewConfig && !value) { + //saved view active, but document list requested + this.selectNone() this._savedViewConfig = null } } From f535c0caf04a20857a559a9202d88e73a700a16c Mon Sep 17 00:00:00 2001 From: jonaswinkler Date: Tue, 22 Dec 2020 15:22:24 +0100 Subject: [PATCH 40/48] changelog layout --- docs/changelog.rst | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index db3378011..e63c19d7d 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -11,34 +11,34 @@ paperless-ng 0.9.9 Christmas release! -Bulk editing +* Bulk editing -* Paperless now supports bulk editing. -* The following operations are available: Add and remove correspondents, tags, document types from selected documents, as well as mass-deleting documents. -* We've got a more fancy UI in the works that makes these features more accessible, but that's not quite ready yet. + * Paperless now supports bulk editing. + * The following operations are available: Add and remove correspondents, tags, document types from selected documents, as well as mass-deleting documents. + * We've got a more fancy UI in the works that makes these features more accessible, but that's not quite ready yet. -Searching +* Searching -* Paperless now supports searching for similar documents ("More like this") both from the document detail page as well as from individual search results. -* A search score indicates how well a document matches the search query, or how similar a document is to a given reference document. + * Paperless now supports searching for similar documents ("More like this") both from the document detail page as well as from individual search results. + * A search score indicates how well a document matches the search query, or how similar a document is to a given reference document. -Changes +* Other additions and changes -* Clarification in the UI that the fields "Match" and "Is insensitive" are not relevant for the Auto matching algorithm. -* New select interface for tags, types and correspondents allows filtering. This also improves tag selection. Thanks again to `Michael Shamoon`_! -* Page navigation controls for the document viewer, thanks to `Michael Shamoon`_. -* Layout changes to the small cards document list. -* The dashboard now displays the username (or full name if specified in the admin) on the dashboard. + * Clarification in the UI that the fields "Match" and "Is insensitive" are not relevant for the Auto matching algorithm. + * New select interface for tags, types and correspondents allows filtering. This also improves tag selection. Thanks again to `Michael Shamoon`_! + * Page navigation controls for the document viewer, thanks to `Michael Shamoon`_. + * Layout changes to the small cards document list. + * The dashboard now displays the username (or full name if specified in the admin) on the dashboard. -Fixes +* Fixes -* An error that caused the document importer to crash was fixed. -* An issue with changes not being possible when ``PAPERLESS_COOKIE_PREFIX`` is used was fixed. -* The date selection filters now allow manual entry of dates. + * An error that caused the document importer to crash was fixed. + * An issue with changes not being possible when ``PAPERLESS_COOKIE_PREFIX`` is used was fixed. + * The date selection filters now allow manual entry of dates. -Feature Removal +* Feature Removal -* Most of the guesswork features have been removed. Paperless no longer tries to extract correspondents and tags from file names. + * Most of the guesswork features have been removed. Paperless no longer tries to extract correspondents and tags from file names. paperless-ng 0.9.8 ################## From 9651578c3215c707df598d6cf5b00f1b0f8cde5c Mon Sep 17 00:00:00 2001 From: jonaswinkler Date: Tue, 22 Dec 2020 15:50:27 +0100 Subject: [PATCH 41/48] progress bar for the document importer --- .../management/commands/document_importer.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/documents/management/commands/document_importer.py b/src/documents/management/commands/document_importer.py index 8e9a79219..344be32be 100644 --- a/src/documents/management/commands/document_importer.py +++ b/src/documents/management/commands/document_importer.py @@ -1,8 +1,10 @@ import json +import logging import os import shutil from contextlib import contextmanager +import tqdm from django.conf import settings from django.core.management import call_command from django.core.management.base import BaseCommand, CommandError @@ -43,6 +45,8 @@ class Command(Renderable, BaseCommand): def handle(self, *args, **options): + logging.getLogger().handlers[0].level = logging.ERROR + self.source = options["source"] if not os.path.exists(self.source): @@ -111,10 +115,13 @@ class Command(Renderable, BaseCommand): os.makedirs(settings.THUMBNAIL_DIR, exist_ok=True) os.makedirs(settings.ARCHIVE_DIR, exist_ok=True) - for record in self.manifest: + print("Copy files into paperless...") - if not record["model"] == "documents.document": - continue + manifest_documents = list(filter( + lambda r: r["model"] == "documents.document", + self.manifest)) + + for record in tqdm.tqdm(manifest_documents): document = Document.objects.get(pk=record["pk"]) @@ -138,7 +145,6 @@ class Command(Renderable, BaseCommand): create_source_path_directory(document.source_path) - print(f"Moving {document_path} to {document.source_path}") shutil.copy(document_path, document.source_path) shutil.copy(thumbnail_path, document.thumbnail_path) if archive_path: From 802c76531068a403564743e668faf4a96f14ae2e Mon Sep 17 00:00:00 2001 From: jonaswinkler Date: Tue, 22 Dec 2020 15:53:04 +0100 Subject: [PATCH 42/48] update index after import --- src/documents/management/commands/document_importer.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/documents/management/commands/document_importer.py b/src/documents/management/commands/document_importer.py index 344be32be..6df14a82c 100644 --- a/src/documents/management/commands/document_importer.py +++ b/src/documents/management/commands/document_importer.py @@ -73,6 +73,9 @@ class Command(Renderable, BaseCommand): self._import_files_from_manifest() + print("Updating search index...") + call_command('document_index', 'reindex') + @staticmethod def _check_manifest_exists(path): if not os.path.exists(path): From 4268d3a36fda4fa11b1e1ef7e4008a32d541fd76 Mon Sep 17 00:00:00 2001 From: jonaswinkler Date: Tue, 22 Dec 2020 17:08:30 +0100 Subject: [PATCH 43/48] error in test case --- src/documents/tests/test_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/documents/tests/test_api.py b/src/documents/tests/test_api.py index bce2a433d..494183d2d 100644 --- a/src/documents/tests/test_api.py +++ b/src/documents/tests/test_api.py @@ -896,7 +896,7 @@ class TestBulkEdit(DirectoriesMixin, APITestCase): response = self.client.post("/api/documents/bulk_edit/", json.dumps({ "documents": [self.doc2.id], "method": "add_tag", - "parameters": {'document_type': 345657} + "parameters": {'tag': 345657} }), content_type='application/json') self.assertEqual(response.status_code, 400) From c58d4df11abc1d68d4a9847c14396e6a3a264721 Mon Sep 17 00:00:00 2001 From: jonaswinkler Date: Tue, 22 Dec 2020 20:28:41 +0100 Subject: [PATCH 44/48] api validation, more tests --- src/documents/serialisers.py | 47 +++++++++++++++++++++++++++++++++ src/documents/tests/test_api.py | 14 +++++++++- 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/src/documents/serialisers.py b/src/documents/serialisers.py index f9fde9ac8..d9f1833bf 100644 --- a/src/documents/serialisers.py +++ b/src/documents/serialisers.py @@ -246,8 +246,55 @@ class BulkEditSerializer(serializers.Serializer): else: raise serializers.ValidationError("Unsupported method.") + def _validate_parameters_tags(self, parameters): + if 'tag' in parameters: + tag_id = parameters['tag'] + try: + Tag.objects.get(id=tag_id) + except Tag.DoesNotExist: + raise serializers.ValidationError("Tag does not exist") + else: + raise serializers.ValidationError("tag not specified") + + def _validate_parameters_document_type(self, parameters): + if 'document_type' in parameters: + document_type_id = parameters['document_type'] + if document_type_id is None: + # None is ok + return + try: + DocumentType.objects.get(id=document_type_id) + except DocumentType.DoesNotExist: + raise serializers.ValidationError( + "Document type does not exist") + else: + raise serializers.ValidationError("document_type not specified") + + def _validate_parameters_correspondent(self, parameters): + if 'correspondent' in parameters: + correspondent_id = parameters['correspondent'] + if correspondent_id is None: + return + try: + Correspondent.objects.get(id=correspondent_id) + except Correspondent.DoesNotExist: + raise serializers.ValidationError( + "Correspondent does not exist") + else: + raise serializers.ValidationError("correspondent not specified") + def validate(self, attrs): + method = attrs['method'] + parameters = attrs['parameters'] + + if method == bulk_edit.set_correspondent: + self._validate_parameters_correspondent(parameters) + elif method == bulk_edit.set_document_type: + self._validate_parameters_document_type(parameters) + elif method == bulk_edit.add_tag or method == bulk_edit.remove_tag: + self._validate_parameters_tags(parameters) + return attrs diff --git a/src/documents/tests/test_api.py b/src/documents/tests/test_api.py index 494183d2d..dc4d49d4e 100644 --- a/src/documents/tests/test_api.py +++ b/src/documents/tests/test_api.py @@ -669,6 +669,7 @@ class TestDocumentApi(DirectoriesMixin, APITestCase): self.assertEqual(v1.filter_rules.count(), 0) + class TestBulkEdit(DirectoriesMixin, APITestCase): def setUp(self): @@ -891,7 +892,7 @@ class TestBulkEdit(DirectoriesMixin, APITestCase): doc2 = Document.objects.get(id=self.doc2.id) self.assertEqual(doc2.document_type, self.dt1) - def test_api_invalid_tag(self): + def test_api_add_invalid_tag(self): self.assertEqual(list(self.doc2.tags.all()), [self.t1]) response = self.client.post("/api/documents/bulk_edit/", json.dumps({ "documents": [self.doc2.id], @@ -901,3 +902,14 @@ class TestBulkEdit(DirectoriesMixin, APITestCase): self.assertEqual(response.status_code, 400) self.assertEqual(list(self.doc2.tags.all()), [self.t1]) + + def test_api_delete_invalid_tag(self): + self.assertEqual(list(self.doc2.tags.all()), [self.t1]) + response = self.client.post("/api/documents/bulk_edit/", json.dumps({ + "documents": [self.doc2.id], + "method": "remove_tag", + "parameters": {'tag': 345657} + }), content_type='application/json') + self.assertEqual(response.status_code, 400) + + self.assertEqual(list(self.doc2.tags.all()), [self.t1]) From 7968d3184f0952f03d05052a3a8ee2dcd4d3e187 Mon Sep 17 00:00:00 2001 From: jonaswinkler Date: Tue, 22 Dec 2020 21:08:54 +0100 Subject: [PATCH 45/48] more tests --- src/documents/tests/test_api.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/documents/tests/test_api.py b/src/documents/tests/test_api.py index dc4d49d4e..0262b6d6a 100644 --- a/src/documents/tests/test_api.py +++ b/src/documents/tests/test_api.py @@ -669,7 +669,6 @@ class TestDocumentApi(DirectoriesMixin, APITestCase): self.assertEqual(v1.filter_rules.count(), 0) - class TestBulkEdit(DirectoriesMixin, APITestCase): def setUp(self): @@ -913,3 +912,28 @@ class TestBulkEdit(DirectoriesMixin, APITestCase): self.assertEqual(response.status_code, 400) self.assertEqual(list(self.doc2.tags.all()), [self.t1]) + + +class TestApiAuth(APITestCase): + + def test_auth_required(self): + + d = Document.objects.create(title="Test") + + self.assertEqual(self.client.get("/api/documents/").status_code, 401) + + self.assertEqual(self.client.get(f"/api/documents/{d.id}/").status_code, 401) + self.assertEqual(self.client.get(f"/api/documents/{d.id}/download/").status_code, 401) + self.assertEqual(self.client.get(f"/api/documents/{d.id}/preview/").status_code, 401) + self.assertEqual(self.client.get(f"/api/documents/{d.id}/thumb/").status_code, 401) + + self.assertEqual(self.client.get("/api/tags/").status_code, 401) + self.assertEqual(self.client.get("/api/correspondents/").status_code, 401) + self.assertEqual(self.client.get("/api/document_types/").status_code, 401) + + self.assertEqual(self.client.get("/api/logs/").status_code, 401) + self.assertEqual(self.client.get("/api/saved_views/").status_code, 401) + + self.assertEqual(self.client.get("/api/search/").status_code, 401) + self.assertEqual(self.client.get("/api/search/auto_complete/").status_code, 401) + self.assertEqual(self.client.get("/api/documents/bulk_edit/").status_code, 401) From 10deab8fb68db08cc8f59baac47da5475b693b2e Mon Sep 17 00:00:00 2001 From: jonaswinkler Date: Wed, 23 Dec 2020 15:09:39 +0100 Subject: [PATCH 46/48] added many localization markers to the front end #123 --- .../app-frame/app-frame.component.html | 56 +++++++---------- .../dashboard/dashboard.component.html | 2 +- .../dashboard/dashboard.component.ts | 6 +- .../saved-view-widget.component.html | 6 +- .../statistics-widget.component.html | 6 +- .../upload-file-widget.component.html | 8 +-- .../upload-file-widget.component.ts | 6 +- .../welcome-widget.component.html | 14 ++--- .../document-detail.component.html | 63 +++++++++---------- .../document-detail.component.ts | 8 +-- .../document-card-large.component.html | 20 +++--- .../document-card-small.component.html | 10 +-- .../document-list.component.html | 20 +++--- .../document-list/document-list.component.ts | 6 +- .../save-view-config-dialog.component.html | 12 ++-- .../correspondent-edit-dialog.component.html | 12 ++-- .../correspondent-list.component.html | 25 +++----- .../document-type-edit-dialog.component.html | 12 ++-- .../document-type-list.component.html | 21 +++---- .../generic-list/generic-list.component.ts | 10 +-- .../manage/logs/logs.component.html | 7 ++- .../manage/settings/settings.component.html | 22 +++---- .../manage/settings/settings.component.ts | 6 +- .../not-found/not-found.component.html | 2 +- .../components/search/search.component.html | 2 +- src-ui/src/app/data/matching-model.ts | 12 ++-- .../src/app/services/rest/document.service.ts | 14 ++--- 27 files changed, 178 insertions(+), 210 deletions(-) diff --git a/src-ui/src/app/components/app-frame/app-frame.component.html b/src-ui/src/app/components/app-frame/app-frame.component.html index 2458005f4..5fe42fc40 100644 --- a/src-ui/src/app/components/app-frame/app-frame.component.html +++ b/src-ui/src/app/components/app-frame/app-frame.component.html @@ -1,7 +1,7 @@ @@ -28,136 +28,122 @@ - - Dashboard +  Dashboard diff --git a/src-ui/src/app/components/dashboard/dashboard.component.html b/src-ui/src/app/components/dashboard/dashboard.component.html index 541255a68..5b76dd242 100644 --- a/src-ui/src/app/components/dashboard/dashboard.component.html +++ b/src-ui/src/app/components/dashboard/dashboard.component.html @@ -1,4 +1,4 @@ - + diff --git a/src-ui/src/app/components/dashboard/dashboard.component.ts b/src-ui/src/app/components/dashboard/dashboard.component.ts index db9b5d425..0dca25d8a 100644 --- a/src-ui/src/app/components/dashboard/dashboard.component.ts +++ b/src-ui/src/app/components/dashboard/dashboard.component.ts @@ -24,15 +24,15 @@ export class DashboardComponent implements OnInit { } else if (tagUsername && tagUsername.content) { return tagUsername.content } else { - return null + return "null" } } get subtitle() { if (this.displayName) { - return `Hello ${this.displayName}, welcome to Paperless-ng!` + return $localize`Hello ${this.displayName}, welcome to Paperless-ng!` } else { - return `Welcome to Paperless-ng!` + return $localize`Welcome to Paperless-ng!` } } diff --git a/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html b/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html index f50708af3..1bf06b60f 100644 --- a/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html +++ b/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html @@ -1,13 +1,13 @@ - Show all + Show all
ASN Correspondent Title
+
+ + +
+
{{d.archive_serial_number}} Added
diff --git a/src-ui/src/app/components/document-list/document-list.component.scss b/src-ui/src/app/components/document-list/document-list.component.scss index e69de29bb..b9553930b 100644 --- a/src-ui/src/app/components/document-list/document-list.component.scss +++ b/src-ui/src/app/components/document-list/document-list.component.scss @@ -0,0 +1,5 @@ +@import "/src/theme"; + +.table-row-selected { + background-color: $primaryFaded; +} \ No newline at end of file From 9b61596982f7aba663762eb9d133d8aa6280ce22 Mon Sep 17 00:00:00 2001 From: jonaswinkler Date: Wed, 16 Dec 2020 23:26:29 +0100 Subject: [PATCH 21/48] added a note to the documentation regarding character limits of PostgreSQL --- docs/setup.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/setup.rst b/docs/setup.rst index e5e6526ea..e20b2e54a 100644 --- a/docs/setup.rst +++ b/docs/setup.rst @@ -460,6 +460,15 @@ management commands as below. load data from an old database schema in SQLite into a newer database schema in PostgreSQL, you will run into trouble. +.. warning:: + + On some database fields, PostgreSQL enforces predefined limits on maximum + length, whereas SQLite does not. The fields in question are the title of documents + (128 characters), names of document types, tags and correspondents (128 characters), + and filenames (1024 characters). If you have data in these fields that surpasses these + limits, migration to PostgreSQL is not possible and will fail with an error. + + 1. Stop paperless, if it is running. 2. Tell paperless to use PostgreSQL: From 4c27512051a9a543ab930707202613f3416bc72f Mon Sep 17 00:00:00 2001 From: Jonas Winkler Date: Fri, 18 Dec 2020 01:31:46 +0100 Subject: [PATCH 22/48] Update README.md --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e8ae8feb2..fca1cd2cf 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,8 @@ For a complete list of changes from paperless, check out the [changelog](https:/ # Roadmap for 1.0 +- **Bulk editing**. Add/remove metadata from multiple documents at once. + - Make the front end nice (except mobile). - Test coverage at 90%. - Fix whatever bugs I and you find. @@ -59,7 +61,6 @@ For a complete list of changes from paperless, check out the [changelog](https:/ These are things that I want to add to paperless eventually. They are sorted by priority. -- **Bulk editing**. Add/remove metadata from multiple documents at once. - **More search.** The search backend is incredibly versatile and customizable. Searching is the most important feature of this project and thus, I want to implement things like: - Group and limit search results by correspondent, show “more from this” links in the results. - Ability to search for “Similar documents” in the search results @@ -68,6 +69,9 @@ These are things that I want to add to paperless eventually. They are sorted by - With live updates ans websockets. This already works on a dev branch, but requires a lot of new dependencies, which I'm not particular happy about. - Notifications when a document was added with buttons to open the new document right away. - **Arbitrary tag colors**. Allow the selection of any color with a color picker. +- **More file types**. Possibly allow more file types to be processed by paperless, such as office .odt, .doc, .docx documents. + +Apart from that, paperless is pretty much feature complete. ## On the chopping block. From 45bf921a0a9f4e3f3bfd34c335a0fe0dbdd5bbcc Mon Sep 17 00:00:00 2001 From: Jonas Winkler Date: Fri, 18 Dec 2020 01:35:08 +0100 Subject: [PATCH 23/48] Update README.md --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fca1cd2cf..32ff2ab4a 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,10 @@ Here's what you get: * Includes a dashboard that shows basic statistics and has document upload. * Filtering by tags, correspondents, types, and more. * Customizable views can be saved and displayed on the dashboard. - * Full text search with auto completion, scored results and query highlighting allows you to quickly find what you need. +* Full text search helps you find what you need. + * Auto completion suggests relevant words from your documents. + * Results are sorted by relevance to your search query. + * Highlighting shows you which parts of the document matched the query. * Email processing: Paperless adds documents from your email accounts. * Configure multiple accounts and filters for each account. * When adding documents from mails, paperless can move these mails to a new folder, mark them as read, flag them or delete them. From b0f28bdb47bbded8be2582cfb691930088f197ed Mon Sep 17 00:00:00 2001 From: Skylinar <53079123+Skylinar@users.noreply.github.com> Date: Sat, 19 Dec 2020 13:32:31 +0100 Subject: [PATCH 24/48] Add 'Epson WF-7710DWF' to scanner.rst --- docs/scanners.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/scanners.rst b/docs/scanners.rst index 9815637b1..502e78394 100644 --- a/docs/scanners.rst +++ b/docs/scanners.rst @@ -23,16 +23,19 @@ that works right for you based on recommentations from other Paperless users. +---------+----------------+-----+-----+-----+----------------+ | Fujitsu | `ix500`_ | yes | | yes | `eonist`_ | +---------+----------------+-----+-----+-----+----------------+ +| Epson | `WF-7710DWF`_ | yes | | yes | `Skylinar`_ | ++---------+----------------+-----+-----+-----+----------------+ .. _ADS-1500W: https://www.brother.ca/en/p/ads1500w .. _MFC-J6930DW: https://www.brother.ca/en/p/MFCJ6930DW .. _MFC-J5910DW: https://www.brother.co.uk/printers/inkjet-printers/mfcj5910dw .. _MFC-9142CDN: https://www.brother.co.uk/printers/laser-printers/mfc9140cdn .. _ix500: http://www.fujitsu.com/us/products/computing/peripheral/scanners/scansnap/ix500/ +.. _WF-7710: https://www.epson.de/en/products/printers/inkjet-printers/for-home/workforce-wf-7710dwf .. _danielquinn: https://github.com/danielquinn .. _ayounggun: https://github.com/ayounggun .. _bmsleight: https://github.com/bmsleight .. _eonist: https://github.com/eonist .. _REOLDEV: https://github.com/REOLDEV - +.. _Skylinar: https://github.com/Skylinar From 3abf6b6b2f9fb0a15a54c687dc58bc0c1e33f91a Mon Sep 17 00:00:00 2001 From: Skylinar <53079123+Skylinar@users.noreply.github.com> Date: Sat, 19 Dec 2020 13:36:10 +0100 Subject: [PATCH 25/48] Update scanners.rst typo --- docs/scanners.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/scanners.rst b/docs/scanners.rst index 502e78394..70d71c962 100644 --- a/docs/scanners.rst +++ b/docs/scanners.rst @@ -31,7 +31,7 @@ that works right for you based on recommentations from other Paperless users. .. _MFC-J5910DW: https://www.brother.co.uk/printers/inkjet-printers/mfcj5910dw .. _MFC-9142CDN: https://www.brother.co.uk/printers/laser-printers/mfc9140cdn .. _ix500: http://www.fujitsu.com/us/products/computing/peripheral/scanners/scansnap/ix500/ -.. _WF-7710: https://www.epson.de/en/products/printers/inkjet-printers/for-home/workforce-wf-7710dwf +.. _WF-7710DWF: https://www.epson.de/en/products/printers/inkjet-printers/for-home/workforce-wf-7710dwf .. _danielquinn: https://github.com/danielquinn .. _ayounggun: https://github.com/ayounggun From 9fb2be641e2714a28cd29f9101f2b42ce0fd11d1 Mon Sep 17 00:00:00 2001 From: jonaswinkler Date: Tue, 22 Dec 2020 01:17:07 +0100 Subject: [PATCH 26/48] bugfix --- .../widgets/saved-view-widget/saved-view-widget.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.ts b/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.ts index 5bfecc640..f200d8db9 100644 --- a/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.ts +++ b/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.ts @@ -23,7 +23,7 @@ export class SavedViewWidgetComponent implements OnInit { documents: PaperlessDocument[] = [] ngOnInit(): void { - this.documentService.list(1,10,this.savedView.sort_field, this.savedView.sort_reverse, this.savedView.filter_rules).subscribe(result => { + this.documentService.listFiltered(1,10,this.savedView.sort_field, this.savedView.sort_reverse, this.savedView.filter_rules).subscribe(result => { this.documents = result.results }) } From 98167b9d920a96e5e637c154e7384d0c2890d120 Mon Sep 17 00:00:00 2001 From: jonaswinkler Date: Tue, 22 Dec 2020 02:43:18 +0100 Subject: [PATCH 27/48] added selection to the large cards --- .../document-card-large.component.html | 14 ++++++++--- .../document-card-large.component.scss | 25 +++++++++++++++++-- .../document-card-large.component.ts | 19 ++++++++++++++ .../document-list.component.html | 2 +- 4 files changed, 54 insertions(+), 6 deletions(-) diff --git a/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.html b/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.html index 5bf0c9af2..8f52f707f 100644 --- a/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.html +++ b/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.html @@ -1,7 +1,15 @@ -
+
-
- +
+ + +
+
+ + +
+
+
diff --git a/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.scss b/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.scss index a20a56672..eb744d2af 100644 --- a/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.scss +++ b/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.scss @@ -1,5 +1,6 @@ +@import "/src/theme"; + .result-content { - color: darkgray; overflow-wrap: anywhere; } @@ -8,11 +9,31 @@ object-position: top; height: 100%; position: absolute; - + mix-blend-mode: multiply; } .search-score-bar { width: 100px; height: 5px; margin-top: 2px; +} + +.document-card-check { + display: none +} + +.document-card:hover .document-card-check { + display: block; +} + +.card-selected { + border-color: $primary; +} + +.doc-img-background { + background-color: white; +} + +.doc-img-background-selected { + background-color: $primaryFaded; } \ No newline at end of file diff --git a/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.ts b/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.ts index bcc1b1f3c..336af41da 100644 --- a/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.ts +++ b/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.ts @@ -12,6 +12,25 @@ export class DocumentCardLargeComponent implements OnInit { constructor(private documentService: DocumentService, private sanitizer: DomSanitizer) { } + _selected = false + + get selected() { + return this._selected + } + + @Input() + set selected(value: boolean) { + this._selected = value + this.selectedChange.emit(value) + } + + @Output() + selectedChange = new EventEmitter() + + get selectable() { + return this.selectedChange.observers.length > 0 + } + @Input() moreLikeThis: boolean = false diff --git a/src-ui/src/app/components/document-list/document-list.component.html b/src-ui/src/app/components/document-list/document-list.component.html index f1101610b..4b05d5e61 100644 --- a/src-ui/src/app/components/document-list/document-list.component.html +++ b/src-ui/src/app/components/document-list/document-list.component.html @@ -97,7 +97,7 @@
- +
From c57c0eb9556b0c540f225adcd47470367b73d97f Mon Sep 17 00:00:00 2001 From: jonaswinkler Date: Tue, 22 Dec 2020 02:59:09 +0100 Subject: [PATCH 28/48] added tracking to the document list --- .../components/document-list/document-list.component.html | 6 +++--- .../app/components/document-list/document-list.component.ts | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src-ui/src/app/components/document-list/document-list.component.html b/src-ui/src/app/components/document-list/document-list.component.html index 9657f35e3..7c7541b4d 100644 --- a/src-ui/src/app/components/document-list/document-list.component.html +++ b/src-ui/src/app/components/document-list/document-list.component.html @@ -73,7 +73,7 @@
- +
@@ -87,7 +87,7 @@
Added
{{d.archive_serial_number}}
- - + + diff --git a/src-ui/src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html b/src-ui/src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html index 50d844b36..0ad0005ea 100644 --- a/src-ui/src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html +++ b/src-ui/src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html @@ -1,6 +1,6 @@ - + -

Documents in inbox: {{statistics.documents_inbox}}

-

Total documents: {{statistics.documents_total}}

+

Documents in inbox: {{statistics.documents_inbox}}

+

Total documents: {{statistics.documents_total}}

\ No newline at end of file diff --git a/src-ui/src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html b/src-ui/src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html index 013486a47..91fff4e83 100644 --- a/src-ui/src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html +++ b/src-ui/src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html @@ -1,16 +1,16 @@ - +
- + browseBtnClassName="btn btn-sm btn-outline-primary ml-2" i18n-dropZoneLabel i18n-browseBtnLabel>
-

Uploading {{uploadStatus.length}} file(s)

+

Uploading {{uploadStatus.length}} file(s)

diff --git a/src-ui/src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts b/src-ui/src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts index 2ea4825f1..b80f9544e 100644 --- a/src-ui/src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts +++ b/src-ui/src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts @@ -60,7 +60,7 @@ export class UploadFileWidgetComponent implements OnInit { } else if (event.type == HttpEventType.Response) { this.uploadStatus.splice(this.uploadStatus.indexOf(uploadStatusObject), 1) this.completedFiles += 1 - this.toastService.showToast(Toast.make("Information", "The document has been uploaded and will be processed by the consumer shortly.")) + this.toastService.showToast(Toast.make("Information", $localize`The document has been uploaded and will be processed by the consumer shortly.`)) } }, error => { @@ -68,11 +68,11 @@ export class UploadFileWidgetComponent implements OnInit { this.completedFiles += 1 switch (error.status) { case 400: { - this.toastService.showToast(Toast.makeError(`There was an error while uploading the document: ${error.error.document}`)) + this.toastService.showToast(Toast.makeError($localize`There was an error while uploading the document: ${error.error.document}`)) break; } default: { - this.toastService.showToast(Toast.makeError("An error has occurred while uploading the document. Sorry!")) + this.toastService.showToast(Toast.makeError($localize`An error has occurred while uploading the document. Sorry!`)) break; } } diff --git a/src-ui/src/app/components/dashboard/widgets/welcome-widget/welcome-widget.component.html b/src-ui/src/app/components/dashboard/widgets/welcome-widget/welcome-widget.component.html index 0caf55f11..6c9c34d5b 100644 --- a/src-ui/src/app/components/dashboard/widgets/welcome-widget/welcome-widget.component.html +++ b/src-ui/src/app/components/dashboard/widgets/welcome-widget/welcome-widget.component.html @@ -1,16 +1,16 @@ - + -

Paperless is running! :)

-

You can start uploading documents by dropping them in the file upload box to the right or by dropping them in the configured consumption folder and they'll start showing up in the documents list. +

Paperless is running! :)

+

You can start uploading documents by dropping them in the file upload box to the right or by dropping them in the configured consumption folder and they'll start showing up in the documents list. After you've added some metadata to your documents, use the filtering mechanisms of paperless to create custom views (such as 'Recently added', 'Tagged TODO') and have them displayed on the dashboard instead of this message.

-

Paperless offers some more features that try to make your life easier, such as:

+

Paperless offers some more features that try to make your life easier, such as:

    -
  • Once you've got a couple documents in paperless and added metadata to them, paperless can assign that metadata to new documents automatically.
  • -
  • You can configure paperless to read your mails and add documents from attached files.
  • +
  • Once you've got a couple documents in paperless and added metadata to them, paperless can assign that metadata to new documents automatically.
  • +
  • You can configure paperless to read your mails and add documents from attached files.
-

Consult the documentation on how to use these features. The section on basic usage also has some information on how to use paperless in general.

+

Consult the documentation on how to use these features. The section on basic usage also has some information on how to use paperless in general.

\ No newline at end of file diff --git a/src-ui/src/app/components/document-detail/document-detail.component.html b/src-ui/src/app/components/document-detail/document-detail.component.html index ae3fb0c0a..f569c1033 100644 --- a/src-ui/src/app/components/document-detail/document-detail.component.html +++ b/src-ui/src/app/components/document-detail/document-detail.component.html @@ -1,19 +1,18 @@
-
Page
+
Page
-
of {{previewNumPages}}
+
of {{previewNumPages}}
@@ -21,14 +20,13 @@ - - Download +  Download @@ -37,15 +35,13 @@ @@ -57,27 +53,27 @@
CreatedTitleCreatedTitle
- + - + - + - + - + - + - + - +
Date modifiedDate modified {{document.modified | date:'medium'}}
Date addedDate added {{document.added | date:'medium'}}
Media filenameMedia filename {{metadata?.media_filename}}
Original MD5 ChecksumOriginal MD5 checksum {{metadata?.original_checksum}}
Original file sizeOriginal file size {{metadata?.original_size | fileSize}}
Original mime typeOriginal mime type {{metadata?.original_mime_type}}
Archive MD5 ChecksumArchive MD5 checksum {{metadata?.archive_checksum}}
Archive file sizeArchive file size {{metadata?.archive_size | fileSize}}
- - + + @@ -135,10 +131,9 @@
-   -   -   +   +   +   diff --git a/src-ui/src/app/components/document-detail/document-detail.component.ts b/src-ui/src/app/components/document-detail/document-detail.component.ts index d705c3176..2efd32f27 100644 --- a/src-ui/src/app/components/document-detail/document-detail.component.ts +++ b/src-ui/src/app/components/document-detail/document-detail.component.ts @@ -158,11 +158,11 @@ export class DocumentDetailComponent implements OnInit { delete() { let modal = this.modalService.open(ConfirmDialogComponent, {backdrop: 'static'}) - modal.componentInstance.title = "Confirm delete" - modal.componentInstance.messageBold = `Do you really want to delete document '${this.document.title}'?` - modal.componentInstance.message = `The files for this document will be deleted permanently. This operation cannot be undone.` + modal.componentInstance.title = $localize`Confirm delete` + modal.componentInstance.messageBold = $localize`Do you really want to delete document '${this.document.title}'?` + modal.componentInstance.message = $localize`The files for this document will be deleted permanently. This operation cannot be undone.` modal.componentInstance.btnClass = "btn-danger" - modal.componentInstance.btnCaption = "Delete document" + modal.componentInstance.btnCaption = $localize`Delete document` modal.componentInstance.confirmClicked.subscribe(() => { this.documentsService.delete(this.document).subscribe(() => { modal.close() diff --git a/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.html b/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.html index 8f52f707f..ac015cc52 100644 --- a/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.html +++ b/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.html @@ -17,11 +17,11 @@
- {{(document.correspondent$ | async)?.name}} + {{(document.correspondent$ | async)?.name}} {{(document.correspondent$ | async)?.name}}: {{document.title | documentTitle}} - +
#{{document.archive_serial_number}}
@@ -36,37 +36,33 @@ - - More like this +  More like this - - Edit +  Edit - - View +  View - - Download +  Download - Score: + Score: - Created: {{document.created | date}} + Created: {{document.created | date}} diff --git a/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.html b/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.html index 658aa1ea0..4fd155559 100644 --- a/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.html +++ b/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.html @@ -12,7 +12,7 @@
- +
+ {{moreTags}} @@ -23,7 +23,7 @@

- {{(document.correspondent$ | async)?.name}}: + {{(document.correspondent$ | async)?.name}}: {{document.title | documentTitle}}

@@ -32,18 +32,18 @@
- + - + - + diff --git a/src-ui/src/app/components/document-list/document-list.component.html b/src-ui/src/app/components/document-list/document-list.component.html index 25bcb7468..888639b5e 100644 --- a/src-ui/src/app/components/document-list/document-list.component.html +++ b/src-ui/src/app/components/document-list/document-list.component.html @@ -47,7 +47,7 @@
- +
@@ -70,15 +70,15 @@
- +
@@ -104,12 +104,12 @@ - - - - - - + + + + + + diff --git a/src-ui/src/app/components/document-list/document-list.component.ts b/src-ui/src/app/components/document-list/document-list.component.ts index f72a92aa9..cb0d09fe0 100644 --- a/src-ui/src/app/components/document-list/document-list.component.ts +++ b/src-ui/src/app/components/document-list/document-list.component.ts @@ -48,7 +48,7 @@ export class DocumentListComponent implements OnInit { } getTitle() { - return this.list.savedViewTitle || "Documents" + return this.list.savedViewTitle || $localize`Documents` } getSortFields() { @@ -90,7 +90,7 @@ export class DocumentListComponent implements OnInit { saveViewConfig() { this.savedViewService.update(this.list.savedView).subscribe(result => { - this.toastService.showToast(Toast.make("Information", `View "${this.list.savedView.name}" saved successfully.`)) + this.toastService.showToast(Toast.make("Information", $localize`View "${this.list.savedView.name}" saved successfully.`)) }) } @@ -109,7 +109,7 @@ export class DocumentListComponent implements OnInit { } this.savedViewService.create(savedView).subscribe(() => { modal.close() - this.toastService.showToast(Toast.make("Information", `View "${savedView.name}" created successfully.`)) + this.toastService.showToast(Toast.make("Information", $localize`View "${savedView.name}" created successfully.`)) }) }) } diff --git a/src-ui/src/app/components/document-list/save-view-config-dialog/save-view-config-dialog.component.html b/src-ui/src/app/components/document-list/save-view-config-dialog/save-view-config-dialog.component.html index 8819aa313..9a2a37e36 100644 --- a/src-ui/src/app/components/document-list/save-view-config-dialog/save-view-config-dialog.component.html +++ b/src-ui/src/app/components/document-list/save-view-config-dialog/save-view-config-dialog.component.html @@ -1,17 +1,17 @@ diff --git a/src-ui/src/app/components/manage/correspondent-list/correspondent-edit-dialog/correspondent-edit-dialog.component.html b/src-ui/src/app/components/manage/correspondent-list/correspondent-edit-dialog/correspondent-edit-dialog.component.html index e09ea38bf..bc3d1c88f 100644 --- a/src-ui/src/app/components/manage/correspondent-list/correspondent-edit-dialog/correspondent-edit-dialog.component.html +++ b/src-ui/src/app/components/manage/correspondent-list/correspondent-edit-dialog/correspondent-edit-dialog.component.html @@ -7,13 +7,13 @@ \ No newline at end of file diff --git a/src-ui/src/app/components/manage/correspondent-list/correspondent-list.component.html b/src-ui/src/app/components/manage/correspondent-list/correspondent-list.component.html index 2efd1c58d..4abc72037 100644 --- a/src-ui/src/app/components/manage/correspondent-list/correspondent-list.component.html +++ b/src-ui/src/app/components/manage/correspondent-list/correspondent-list.component.html @@ -1,7 +1,5 @@ - - + +
@@ -11,11 +9,11 @@
ASNCorrespondentTitleDocument typeCreatedAddedASNCorrespondentTitleDocument typeCreatedAdded
- - - - - + + + + + @@ -29,21 +27,18 @@ diff --git a/src-ui/src/app/components/manage/document-type-list/document-type-edit-dialog/document-type-edit-dialog.component.html b/src-ui/src/app/components/manage/document-type-list/document-type-edit-dialog/document-type-edit-dialog.component.html index 3338c40c3..16c006051 100644 --- a/src-ui/src/app/components/manage/document-type-list/document-type-edit-dialog/document-type-edit-dialog.component.html +++ b/src-ui/src/app/components/manage/document-type-list/document-type-edit-dialog/document-type-edit-dialog.component.html @@ -7,14 +7,14 @@ \ No newline at end of file diff --git a/src-ui/src/app/components/manage/document-type-list/document-type-list.component.html b/src-ui/src/app/components/manage/document-type-list/document-type-list.component.html index d2ffab400..6de7e0595 100644 --- a/src-ui/src/app/components/manage/document-type-list/document-type-list.component.html +++ b/src-ui/src/app/components/manage/document-type-list/document-type-list.component.html @@ -1,7 +1,5 @@ - +
@@ -12,10 +10,10 @@
NameMatchingDocument countLast correspondenceActionsNameMatchingDocument countLast correspondenceActions
- - - - + + + + @@ -28,21 +26,18 @@ diff --git a/src-ui/src/app/components/manage/generic-list/generic-list.component.ts b/src-ui/src/app/components/manage/generic-list/generic-list.component.ts index 783c22b36..e1ef39428 100644 --- a/src-ui/src/app/components/manage/generic-list/generic-list.component.ts +++ b/src-ui/src/app/components/manage/generic-list/generic-list.component.ts @@ -28,7 +28,7 @@ export abstract class GenericListComponent implements On getMatching(o: MatchingModel) { if (o.matching_algorithm == MATCH_AUTO) { - return "Automatic" + return $localize`Automatic` } else if (o.match && o.match.length > 0) { return `${o.match} (${MATCHING_ALGORITHMS.find(a => a.id == o.matching_algorithm).name})` } else { @@ -90,11 +90,11 @@ export abstract class GenericListComponent implements On openDeleteDialog(object: T) { var activeModal = this.modalService.open(ConfirmDialogComponent, {backdrop: 'static'}) - activeModal.componentInstance.title = "Confirm delete" - activeModal.componentInstance.messageBold = `Do you really want to delete ${this.getObjectName(object)}?` - activeModal.componentInstance.message = "Associated documents will not be deleted." + activeModal.componentInstance.title = $localize`Confirm delete` + activeModal.componentInstance.messageBold = $localize`Do you really want to delete ${this.getObjectName(object)}?` + activeModal.componentInstance.message = $localize`Associated documents will not be deleted.` activeModal.componentInstance.btnClass = "btn-danger" - activeModal.componentInstance.btnCaption = "Delete" + activeModal.componentInstance.btnCaption = $localize`Delete` activeModal.componentInstance.confirmClicked.subscribe(() => { this.service.delete(object).subscribe(_ => { activeModal.close() diff --git a/src-ui/src/app/components/manage/logs/logs.component.html b/src-ui/src/app/components/manage/logs/logs.component.html index 6af482c66..8c2cbae34 100644 --- a/src-ui/src/app/components/manage/logs/logs.component.html +++ b/src-ui/src/app/components/manage/logs/logs.component.html @@ -1,11 +1,12 @@ - +
+ +
-
No saved views defined.
+
No saved views defined.
diff --git a/src-ui/src/app/components/manage/settings/settings.component.ts b/src-ui/src/app/components/manage/settings/settings.component.ts index f839010b1..bec85e039 100644 --- a/src-ui/src/app/components/manage/settings/settings.component.ts +++ b/src-ui/src/app/components/manage/settings/settings.component.ts @@ -46,14 +46,14 @@ export class SettingsComponent implements OnInit { this.savedViewService.delete(savedView).subscribe(() => { this.savedViewGroup.removeControl(savedView.id.toString()) this.savedViews.splice(this.savedViews.indexOf(savedView), 1) - this.toastService.showToast(Toast.make("Information", `Saved view "${savedView.name} deleted.`)) + this.toastService.showToast(Toast.make("Information", $localize`Saved view "${savedView.name} deleted.`)) }) } private saveLocalSettings() { localStorage.setItem(GENERAL_SETTINGS.DOCUMENT_LIST_SIZE, this.settingsForm.value.documentListItemPerPage) this.documentListViewService.updatePageSize() - this.toastService.showToast(Toast.make("Information", "Settings saved successfully.")) + this.toastService.showToast(Toast.make("Information", $localize`Settings saved successfully.`)) } saveSettings() { @@ -65,7 +65,7 @@ export class SettingsComponent implements OnInit { this.savedViewService.patchMany(x).subscribe(s => { this.saveLocalSettings() }, error => { - this.toastService.showToast(Toast.makeError(`Error while storing settings on server: ${JSON.stringify(error.error)}`)) + this.toastService.showToast(Toast.makeError($localize`Error while storing settings on server: ${JSON.stringify(error.error)}`)) }) } else { this.saveLocalSettings() diff --git a/src-ui/src/app/components/not-found/not-found.component.html b/src-ui/src/app/components/not-found/not-found.component.html index 6b21cf3ba..4d7e0f7e0 100644 --- a/src-ui/src/app/components/not-found/not-found.component.html +++ b/src-ui/src/app/components/not-found/not-found.component.html @@ -4,5 +4,5 @@ -

404 Not Found

+

404 Not Found

\ No newline at end of file diff --git a/src-ui/src/app/components/search/search.component.html b/src-ui/src/app/components/search/search.component.html index de6f0133f..ba2253401 100644 --- a/src-ui/src/app/components/search/search.component.html +++ b/src-ui/src/app/components/search/search.component.html @@ -1,7 +1,7 @@ -
Invalid search query: {{errorMessage}}
+
Invalid search query: {{errorMessage}}

Showing documents similar to diff --git a/src-ui/src/app/data/matching-model.ts b/src-ui/src/app/data/matching-model.ts index 698c32da5..dd9ae95ff 100644 --- a/src-ui/src/app/data/matching-model.ts +++ b/src-ui/src/app/data/matching-model.ts @@ -9,12 +9,12 @@ export const MATCH_FUZZY = 5 export const MATCH_AUTO = 6 export const MATCHING_ALGORITHMS = [ - {id: MATCH_ANY, name: "Any"}, - {id: MATCH_ALL, name: "All"}, - {id: MATCH_LITERAL, name: "Literal"}, - {id: MATCH_REGEX, name: "Regular Expression"}, - {id: MATCH_FUZZY, name: "Fuzzy Match"}, - {id: MATCH_AUTO, name: "Auto"}, + {id: MATCH_ANY, name: $localize`Any`}, + {id: MATCH_ALL, name: $localize`All`}, + {id: MATCH_LITERAL, name: $localize`Literal`}, + {id: MATCH_REGEX, name: $localize`Regular expression`}, + {id: MATCH_FUZZY, name: $localize`Fuzzy match`}, + {id: MATCH_AUTO, name: $localize`Auto`}, ] export interface MatchingModel extends ObjectWithId { diff --git a/src-ui/src/app/services/rest/document.service.ts b/src-ui/src/app/services/rest/document.service.ts index c132fa856..ec5c7689b 100644 --- a/src-ui/src/app/services/rest/document.service.ts +++ b/src-ui/src/app/services/rest/document.service.ts @@ -13,13 +13,13 @@ import { TagService } from './tag.service'; import { FILTER_RULE_TYPES } from 'src/app/data/filter-rule-type'; export const DOCUMENT_SORT_FIELDS = [ - { field: "correspondent__name", name: "Correspondent" }, - { field: "document_type__name", name: "Document type" }, - { field: 'title', name: 'Title' }, - { field: 'archive_serial_number', name: 'ASN' }, - { field: 'created', name: 'Created' }, - { field: 'added', name: 'Added' }, - { field: 'modified', name: 'Modified' } + { field: "correspondent__name", name: $localize`Correspondent` }, + { field: "document_type__name", name: $localize`Document type` }, + { field: 'title', name: $localize`Title` }, + { field: 'archive_serial_number', name: $localize`ASN` }, + { field: 'created', name: $localize`Created` }, + { field: 'added', name: $localize`Added` }, + { field: 'modified', name: $localize`Modified` } ] @Injectable({ From 4478c5c262119f598b52517c42f9ef3a78b0ff2f Mon Sep 17 00:00:00 2001 From: jonaswinkler Date: Wed, 23 Dec 2020 15:14:24 +0100 Subject: [PATCH 47/48] fix typo #176 --- src/documents/consumer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/documents/consumer.py b/src/documents/consumer.py index ab4912a36..37630bc21 100755 --- a/src/documents/consumer.py +++ b/src/documents/consumer.py @@ -103,7 +103,7 @@ class Consumer(LoggingMixin): parser_class = get_parser_class_for_mime_type(mime_type) if not parser_class: - raise ConsumerError(f"No parsers abvailable for {self.filename}") + raise ConsumerError(f"No parsers available for {self.filename}") else: self.log("debug", f"Parser: {parser_class.__name__} " From 21bab80d7001b102aa13ff9558b4a3671aef9a35 Mon Sep 17 00:00:00 2001 From: jonaswinkler Date: Wed, 23 Dec 2020 15:22:28 +0100 Subject: [PATCH 48/48] debug log mime type #176 --- src/documents/consumer.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/documents/consumer.py b/src/documents/consumer.py index 37630bc21..ab07b3149 100755 --- a/src/documents/consumer.py +++ b/src/documents/consumer.py @@ -95,19 +95,20 @@ class Consumer(LoggingMixin): self.pre_check_directories() self.pre_check_duplicate() - self.log("info", "Consuming {}".format(self.filename)) + self.log("info", f"Consuming {self.filename}") # Determine the parser class. mime_type = magic.from_file(self.path, mime=True) + self.log("debug", f"Detected mime type: {mime_type}") + parser_class = get_parser_class_for_mime_type(mime_type) if not parser_class: raise ConsumerError(f"No parsers available for {self.filename}") else: self.log("debug", - f"Parser: {parser_class.__name__} " - f"based on mime type {mime_type}") + f"Parser: {parser_class.__name__}") # Notify all listeners that we're going to do some work.

NameMatchingDocument countActionsNameMatchingDocument countActions