Merge branch 'feature-bulk-edit' into feature-bulk-editor

This commit is contained in:
Michael Shamoon 2020-12-22 07:43:51 -08:00
commit ba066af664
8 changed files with 221 additions and 23 deletions

View File

@ -5,6 +5,7 @@
[disabled]="disabled"
[style.color]="textColor"
[style.background]="backgroundColor"
[clearable]="allowNull"
(change)="onChange(value)"
(blur)="onTouched()">
<ng-option *ngFor="let i of items" [value]="i.id">{{i.name}}</ng-option>

View File

@ -67,9 +67,9 @@
formControlName='archive_serial_number'>
</div>
<app-input-date-time titleDate="Date created" formControlName="created"></app-input-date-time>
<app-input-select [items]="correspondents" title="Correspondent" formControlName="correspondent"
<app-input-select [items]="correspondents" title="Correspondent" formControlName="correspondent" [allowNull]="true"
(createNew)="createCorrespondent()"></app-input-select>
<app-input-select [items]="documentTypes" title="Document type" formControlName="document_type"
<app-input-select [items]="documentTypes" title="Document type" formControlName="document_type" [allowNull]="true"
(createNew)="createDocumentType()"></app-input-select>
<app-input-tags formControlName="tags" title="Tags"></app-input-tags>

View File

@ -2,7 +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 { 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';
@ -16,10 +16,8 @@ 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 { PaperlessTag } from 'src/app/data/paperless-tag';
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent';
import { PaperlessDocumentType } from 'src/app/data/paperless-document-type';
import { ChangedItems } from './bulk-editor/bulk-editor.component';
import { OpenDocumentsService } from 'src/app/services/open-documents.service';
@Component({
selector: 'app-document-list',
@ -38,7 +36,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
@ -147,12 +146,12 @@ export class DocumentListComponent implements OnInit {
private executeBulkOperation(method: string, args): Observable<any> {
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
})
)
}

View File

@ -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
}

View File

@ -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 []

View File

@ -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()

View File

@ -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)
@ -683,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)
@ -698,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)
@ -727,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])

View File

@ -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.")])