From c2fda245acb504592d7cfd2b8bfcfe854bd9d373 Mon Sep 17 00:00:00 2001 From: tooomm Date: Wed, 3 Aug 2022 15:00:23 +0200 Subject: [PATCH 01/20] typo --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 715fef588..735804560 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -44,7 +44,7 @@ resources in the documentation: learn about how paperless automates all tagging using machine learning. * Paperless now comes with a :ref:`proper email consumer ` that's fully tested and production ready. -* Paperless creates searchable PDF/A documents from whatever you you put into +* Paperless creates searchable PDF/A documents from whatever you put into the consumption directory. This means that you can select text in image-only documents coming from your scanner. * See :ref:`this note ` about GnuPG encryption in From 278e9c12e13c44c2c11fa56993e0dfad3cb3b581 Mon Sep 17 00:00:00 2001 From: tim-vogel Date: Sun, 7 Aug 2022 12:41:30 -0700 Subject: [PATCH 02/20] add comment function --- src-ui/src/app/app.module.ts | 2 + .../document-comment.component.html | 25 ++++++ .../document-comment.component.scss | 22 +++++ .../document-comment.component.spec.ts | 25 ++++++ .../document-comment.component.ts | 63 ++++++++++++++ .../document-detail.component.html | 7 ++ .../document-detail.component.ts | 13 ++- .../app/data/paperless-document-comment.ts | 8 ++ src-ui/src/app/data/paperless-environment.ts | 3 + src-ui/src/app/data/user-type.ts | 7 ++ .../rest/document-comment.service.spec.ts | 16 ++++ .../services/rest/document-comment.service.ts | 31 +++++++ .../services/rest/environment.service.spec.ts | 16 ++++ .../app/services/rest/environment.service.ts | 22 +++++ .../management/commands/document_exporter.py | 4 + src/documents/migrations/1023_add_comments.py | 19 ++++ src/documents/models.py | 37 ++++++++ src/documents/views.py | 87 +++++++++++++++++++ src/paperless/settings.py | 8 ++ src/paperless/urls.py | 2 + 20 files changed, 416 insertions(+), 1 deletion(-) create mode 100644 src-ui/src/app/components/document-comment/document-comment.component.html create mode 100644 src-ui/src/app/components/document-comment/document-comment.component.scss create mode 100644 src-ui/src/app/components/document-comment/document-comment.component.spec.ts create mode 100644 src-ui/src/app/components/document-comment/document-comment.component.ts create mode 100644 src-ui/src/app/data/paperless-document-comment.ts create mode 100644 src-ui/src/app/data/paperless-environment.ts create mode 100644 src-ui/src/app/data/user-type.ts create mode 100644 src-ui/src/app/services/rest/document-comment.service.spec.ts create mode 100644 src-ui/src/app/services/rest/document-comment.service.ts create mode 100644 src-ui/src/app/services/rest/environment.service.spec.ts create mode 100644 src-ui/src/app/services/rest/environment.service.ts create mode 100644 src/documents/migrations/1023_add_comments.py diff --git a/src-ui/src/app/app.module.ts b/src-ui/src/app/app.module.ts index edbd261f6..dd34724a6 100644 --- a/src-ui/src/app/app.module.ts +++ b/src-ui/src/app/app.module.ts @@ -67,6 +67,7 @@ import { ApiVersionInterceptor } from './interceptors/api-version.interceptor' import { ColorSliderModule } from 'ngx-color/slider' import { ColorComponent } from './components/common/input/color/color.component' import { DocumentAsnComponent } from './components/document-asn/document-asn.component' +import { DocumentCommentComponent } from './components/document-comment/document-comment.component'; import { DirtyDocGuard } from './guards/dirty-doc.guard' import localeBe from '@angular/common/locales/be' @@ -173,6 +174,7 @@ function initializeApp(settings: SettingsService) { DateComponent, ColorComponent, DocumentAsnComponent, + DocumentCommentComponent, TasksComponent, ], imports: [ diff --git a/src-ui/src/app/components/document-comment/document-comment.component.html b/src-ui/src/app/components/document-comment/document-comment.component.html new file mode 100644 index 000000000..6a36f999f --- /dev/null +++ b/src-ui/src/app/components/document-comment/document-comment.component.html @@ -0,0 +1,25 @@ +
+
+
+ +
+   +
+
+
+
+ {{comment?.user?.firstname}} {{comment?.user?.lastname}} ({{comment?.user?.username}}) - {{ comment?.created | customDate}} + + + + + + + + +
+
+ {{comment.comment}} +
+
+
\ No newline at end of file diff --git a/src-ui/src/app/components/document-comment/document-comment.component.scss b/src-ui/src/app/components/document-comment/document-comment.component.scss new file mode 100644 index 000000000..778556485 --- /dev/null +++ b/src-ui/src/app/components/document-comment/document-comment.component.scss @@ -0,0 +1,22 @@ +.comment-card-body { + padding-top: .8rem !important; + padding-bottom: .8rem !important; + max-height: 10rem; + overflow: scroll; + white-space: pre-wrap; +} + +.comment-card-header a { + border: none; + background: none; + padding: 5px; + border-radius: 50%; +} + +.comment-card-header a:hover { + background: #FFF; +} + +.comment-card-header a:hover svg { + fill: var(--primary); +} \ No newline at end of file diff --git a/src-ui/src/app/components/document-comment/document-comment.component.spec.ts b/src-ui/src/app/components/document-comment/document-comment.component.spec.ts new file mode 100644 index 000000000..1f6389b1d --- /dev/null +++ b/src-ui/src/app/components/document-comment/document-comment.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DocumentCommentComponent } from './document-comment.component'; + +describe('DocumentCommentComponent', () => { + let component: DocumentCommentComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ DocumentCommentComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(DocumentCommentComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); \ No newline at end of file diff --git a/src-ui/src/app/components/document-comment/document-comment.component.ts b/src-ui/src/app/components/document-comment/document-comment.component.ts new file mode 100644 index 000000000..57b052d84 --- /dev/null +++ b/src-ui/src/app/components/document-comment/document-comment.component.ts @@ -0,0 +1,63 @@ +import { Component, OnInit } from '@angular/core'; +import { DocumentDetailComponent } from 'src/app/components/document-detail/document-detail.component'; +import { DocumentCommentService } from 'src/app/services/rest/document-comment.service'; +import { PaperlessDocumentComment } from 'src/app/data/paperless-document-comment'; + +import { take } from 'rxjs/operators'; +import { FormControl, FormGroup } from '@angular/forms'; + +@Component({ + selector: 'app-document-comment', + templateUrl: './document-comment.component.html', + styleUrls: ['./document-comment.component.scss'] +}) +export class DocumentCommentComponent implements OnInit { + + comments:PaperlessDocumentComment[]; + networkActive = false; + documentId: number; + commentForm: FormGroup = new FormGroup({ + newcomment: new FormControl('') + }) + + constructor( + private documentDetailComponent: DocumentDetailComponent, + private documentCommentService: DocumentCommentService, + ) { } + + byId(index, item: PaperlessDocumentComment) { + return item.id; + } + + async ngOnInit(): Promise { + try { + this.documentId = this.documentDetailComponent.documentId; + this.comments= await this.documentCommentService.getComments(this.documentId).pipe(take(1)).toPromise(); + } catch(err){ + this.comments = []; + } + } + + addComment(){ + this.networkActive = true + this.documentCommentService.addComment(this.documentId, this.commentForm.get("newcomment").value).subscribe(result => { + this.comments = result; + this.commentForm.get("newcomment").reset(); + this.networkActive = false; + }, error => { + this.networkActive = false; + }); + } + + deleteComment(event){ + let parent = event.target.parentElement.closest('div[comment-id]'); + if(parent){ + this.documentCommentService.deleteComment(this.documentId, parseInt(parent.getAttribute("comment-id"))).subscribe(result => { + this.comments = result; + this.networkActive = false; + }, error => { + this.networkActive = false; + }); + } + } +} \ 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 764a587e0..ebf286895 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 @@ -169,6 +169,13 @@ +
  • + Comments + + + + +
  • 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 203a56f04..d0f4ecded 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 @@ -35,6 +35,7 @@ import { StoragePathService } from 'src/app/services/rest/storage-path.service' import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path' import { StoragePathEditDialogComponent } from '../common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component' import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings' +import { EnvironmentService } from 'src/app/services/rest/environment.service' @Component({ selector: 'app-document-detail', @@ -83,6 +84,8 @@ export class DocumentDetailComponent previewCurrentPage: number = 1 previewNumPages: number = 1 + isCommentsEnabled:boolean = false + store: BehaviorSubject isDirty$: Observable unsubscribeNotifier: Subject = new Subject() @@ -118,7 +121,8 @@ export class DocumentDetailComponent private documentTitlePipe: DocumentTitlePipe, private toastService: ToastService, private settings: SettingsService, - private storagePathService: StoragePathService + private storagePathService: StoragePathService, + private environment: EnvironmentService ) {} titleKeyUp(event) { @@ -274,6 +278,13 @@ export class DocumentDetailComponent this.suggestions = null }, }) + + this.environment.get("PAPERLESS_COMMENTS_ENABLED").subscribe(result => { + this.isCommentsEnabled = (result.value.toString().toLowerCase() === "true"?true:false); + }, error => { + this.isCommentsEnabled = false; + }) + this.title = this.documentTitlePipe.transform(doc.title) this.documentForm.patchValue(doc) } diff --git a/src-ui/src/app/data/paperless-document-comment.ts b/src-ui/src/app/data/paperless-document-comment.ts new file mode 100644 index 000000000..1b60e6c5c --- /dev/null +++ b/src-ui/src/app/data/paperless-document-comment.ts @@ -0,0 +1,8 @@ +import { ObjectWithId } from './object-with-id' +import { CommentUser } from './user-type' + +export interface PaperlessDocumentComment extends ObjectWithId { + created?: Date + comment?: string + user?: CommentUser +} \ No newline at end of file diff --git a/src-ui/src/app/data/paperless-environment.ts b/src-ui/src/app/data/paperless-environment.ts new file mode 100644 index 000000000..27dda6427 --- /dev/null +++ b/src-ui/src/app/data/paperless-environment.ts @@ -0,0 +1,3 @@ +export interface PaperlessEnvironment { + value?: string; +} \ No newline at end of file diff --git a/src-ui/src/app/data/user-type.ts b/src-ui/src/app/data/user-type.ts new file mode 100644 index 000000000..9324cab43 --- /dev/null +++ b/src-ui/src/app/data/user-type.ts @@ -0,0 +1,7 @@ +import { ObjectWithId } from './object-with-id' + +export interface CommentUser extends ObjectWithId { + username: string + firstname: string + lastname: string +} \ No newline at end of file diff --git a/src-ui/src/app/services/rest/document-comment.service.spec.ts b/src-ui/src/app/services/rest/document-comment.service.spec.ts new file mode 100644 index 000000000..112144b90 --- /dev/null +++ b/src-ui/src/app/services/rest/document-comment.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { DocumentCommentService } from './document-comment.service'; + +describe('DocumentCommentService', () => { + let service: DocumentCommentService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(DocumentCommentService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); \ No newline at end of file diff --git a/src-ui/src/app/services/rest/document-comment.service.ts b/src-ui/src/app/services/rest/document-comment.service.ts new file mode 100644 index 000000000..b5739d65e --- /dev/null +++ b/src-ui/src/app/services/rest/document-comment.service.ts @@ -0,0 +1,31 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpParams } from '@angular/common/http'; +import { PaperlessDocumentComment } from 'src/app/data/paperless-document-comment'; +import { AbstractPaperlessService } from './abstract-paperless-service'; +import { Observable } from 'rxjs'; +import { PaperlessDocumentCommentFrame } from 'src/app/data/paperless-document-comment-frame'; + +@Injectable({ + providedIn: 'root' +}) +export class DocumentCommentService extends AbstractPaperlessService { + + constructor(http: HttpClient) { + super(http, 'documents') + } + + + getComments(id: number): Observable { + return this.http.get(this.getResourceUrl(id, "comments")) + } + + addComment(id: number, comment): Observable{ + return this.http.post(this.getResourceUrl(id, 'comments'), {"payload": comment}) + } + + deleteComment(documentId: number, commentId: number): Observable{ + let httpParams = new HttpParams(); + httpParams = httpParams.set("commentId", commentId.toString()); + return this.http.delete(this.getResourceUrl(documentId, 'comments'), {params: httpParams}); + } +} \ No newline at end of file diff --git a/src-ui/src/app/services/rest/environment.service.spec.ts b/src-ui/src/app/services/rest/environment.service.spec.ts new file mode 100644 index 000000000..941a180b9 --- /dev/null +++ b/src-ui/src/app/services/rest/environment.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { EnvironmentService } from './environment.service'; + +describe('EnvironmentService', () => { + let service: EnvironmentService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(EnvironmentService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); \ No newline at end of file diff --git a/src-ui/src/app/services/rest/environment.service.ts b/src-ui/src/app/services/rest/environment.service.ts new file mode 100644 index 000000000..86ac146ea --- /dev/null +++ b/src-ui/src/app/services/rest/environment.service.ts @@ -0,0 +1,22 @@ +import { HttpClient, HttpParams } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { PaperlessEnvironment } from 'src/app/data/paperless-environment'; +import { environment } from 'src/environments/environment' + +@Injectable({ + providedIn: 'root' +}) +export class EnvironmentService { + + protected baseUrl: string = environment.apiBaseUrl + + constructor(protected http: HttpClient) { } + + get(environment: string): Observable { + let httpParams = new HttpParams(); + httpParams = httpParams.set('name', environment); + + return this.http.get(`${this.baseUrl}environment/`, {params: httpParams}) + } +} \ No newline at end of file diff --git a/src/documents/management/commands/document_exporter.py b/src/documents/management/commands/document_exporter.py index 526d59368..da00d10f5 100644 --- a/src/documents/management/commands/document_exporter.py +++ b/src/documents/management/commands/document_exporter.py @@ -12,6 +12,7 @@ from django.core import serializers from django.core.management.base import BaseCommand from django.core.management.base import CommandError from django.db import transaction +from documents.models import Comment from documents.models import Correspondent from documents.models import Document from documents.models import DocumentType @@ -126,6 +127,9 @@ class Command(BaseCommand): serializers.serialize("json", DocumentType.objects.all()), ) + manifest += json.loads( + serializers.serialize("json", Comment.objects.all())), + documents = Document.objects.order_by("id") document_map = {d.pk: d for d in documents} document_manifest = json.loads(serializers.serialize("json", documents)) diff --git a/src/documents/migrations/1023_add_comments.py b/src/documents/migrations/1023_add_comments.py new file mode 100644 index 000000000..8ae779e66 --- /dev/null +++ b/src/documents/migrations/1023_add_comments.py @@ -0,0 +1,19 @@ +from django.db import migrations, models + +class Migration(migrations.Migration): + dependencies = [ + ('documents', '1016_auto_20210317_1351'), + ] + + operations = [ + migrations.CreateModel( + name='Comment', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('comment', models.TextField()), + ('created', models.DateTimeField(auto_now_add=True)), + ('document_id', models.PositiveIntegerField()), + ('user_id', models.PositiveIntegerField()) + ], + ) + ] \ No newline at end of file diff --git a/src/documents/models.py b/src/documents/models.py index f6df273ad..cb64946c3 100644 --- a/src/documents/models.py +++ b/src/documents/models.py @@ -537,3 +537,40 @@ class PaperlessTask(models.Model): blank=True, ) acknowledged = models.BooleanField(default=False) + +class Comment(models.Model): + comment = models.TextField( + _("content"), + blank=True, + help_text=_("Comment for the document") + ) + + created = models.DateTimeField( + _("created"), + default=timezone.now, db_index=True) + + document = models.ForeignKey( + Document, + blank=True, + null=True, + related_name="documents", + on_delete=models.CASCADE, + verbose_name=_("document") + ) + + user = models.ForeignKey( + User, + blank=True, + null=True, + related_name="users", + on_delete=models.SET_NULL, + verbose_name=_("user") + ) + + class Meta: + ordering = ("created",) + verbose_name = _("comment") + verbose_name_plural = _("comments") + + def __str__(self): + return self.content \ No newline at end of file diff --git a/src/documents/views.py b/src/documents/views.py index b261f37fd..4633642e6 100644 --- a/src/documents/views.py +++ b/src/documents/views.py @@ -21,6 +21,8 @@ from django.db.models.functions import Lower from django.http import Http404 from django.http import HttpResponse from django.http import HttpResponseBadRequest +from django.http import HttpResponseNotAllowed +from django.http import HttpResponseNotFound from django.utils.decorators import method_decorator from django.utils.translation import get_language from django.views.decorators.cache import cache_control @@ -62,6 +64,7 @@ from .matching import match_correspondents from .matching import match_document_types from .matching import match_storage_paths from .matching import match_tags +from .models import Comment from .models import Correspondent from .models import Document from .models import DocumentType @@ -379,6 +382,61 @@ class DocumentViewSet( except (FileNotFoundError, Document.DoesNotExist): raise Http404() + def getComments(self, doc): + return [ + { + "id":c.id, + "comment":c.comment, + "created":c.created, + "user":{ + "id":c.user.id, + "username": c.user.username, + "firstname":c.user.first_name, + "lastname":c.user.last_name + } + } for c in Comment.objects.filter(document=doc).order_by('-created') + ]; + + @action(methods=['get', 'post', 'delete'], detail=True) + def comments(self, request, pk=None): + if settings.PAPERLESS_COMMENTS_ENABLED != True: + return HttpResponseNotAllowed("comment function is disabled") + + try: + doc = Document.objects.get(pk=pk) + except Document.DoesNotExist: + raise Http404() + + currentUser = request.user; + + if request.method == 'GET': + try: + return Response(self.getComments(doc)); + except Exception as e: + return Response({"error": str(e)}); + elif request.method == 'POST': + try: + c = Comment.objects.create( + document = doc, + comment=request.data["payload"], + user=currentUser + ); + c.save(); + + return Response(self.getComments(doc)); + except Exception as e: + return Response({ + "error": str(e) + }); + elif request.method == 'DELETE': + comment = Comment.objects.get(id=int(request.GET.get("commentId"))); + comment.delete(); + return Response(self.getComments(doc)); + + return Response({ + "error": "error" + }); + class SearchResultSerializer(DocumentSerializer): def to_representation(self, instance): @@ -835,3 +893,32 @@ class AcknowledgeTasksView(GenericAPIView): return Response({"result": result}) except Exception: return HttpResponseBadRequest() + +class EnvironmentView(APIView): + + permission_classes = (IsAuthenticated,) + + def get(self, request, format=None): + if 'name' in request.query_params: + name = request.query_params['name'] + else: + return HttpResponseBadRequest("name required") + + if(name not in settings.PAPERLESS_FRONTEND_ALLOWED_ENVIRONMENTS and settings.PAPERLESS_DISABLED_FRONTEND_ENVIRONMENT_CHECK == False): + return HttpResponseNotAllowed("environment not allowed to request") + + value = None + try: + value = getattr(settings, name) + except: + try: + value = os.getenv(name) + except: + value = None + + if value == None: + return HttpResponseNotFound("environment not found") + + return Response({ + "value": str(value) + }); diff --git a/src/paperless/settings.py b/src/paperless/settings.py index 2ce99ac0e..e42cf4359 100644 --- a/src/paperless/settings.py +++ b/src/paperless/settings.py @@ -566,6 +566,14 @@ CONVERT_MEMORY_LIMIT = os.getenv("PAPERLESS_CONVERT_MEMORY_LIMIT") GS_BINARY = os.getenv("PAPERLESS_GS_BINARY", "gs") +# Comment settings +PAPERLESS_COMMENTS_ENABLED = __get_boolean("PAPERLESS_COMMENTS_ENABLED", "NO") + +# allowed environments for frontend +PAPERLESS_DISABLED_FRONTEND_ENVIRONMENT_CHECK = __get_boolean("PAPERLESS_DISABLED_FRONTEND_ENVIRONMENT_CHECK", "NO") +PAPERLESS_FRONTEND_ALLOWED_ENVIRONMENTS = [ + "PAPERLESS_COMMENTS_ENABLED" +] # Pre-2.x versions of Paperless stored your documents locally with GPG # encryption, but that is no longer the default. This behaviour is still diff --git a/src/paperless/urls.py b/src/paperless/urls.py index 46309e1e6..6ba1ee263 100644 --- a/src/paperless/urls.py +++ b/src/paperless/urls.py @@ -8,6 +8,7 @@ from django.utils.translation import gettext_lazy as _ from django.views.decorators.csrf import csrf_exempt from django.views.generic import RedirectView from documents.views import AcknowledgeTasksView +from documents.views import EnvironmentView from documents.views import BulkDownloadView from documents.views import BulkEditView from documents.views import CorrespondentViewSet @@ -94,6 +95,7 @@ urlpatterns = [ AcknowledgeTasksView.as_view(), name="acknowledge_tasks", ), + re_path(r"^environment/", EnvironmentView.as_view()), path("token/", views.obtain_auth_token), ] + api_router.urls, From 630a4d02e0273b7021290fd28688bb3644e22872 Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Sun, 7 Aug 2022 15:05:58 -0700 Subject: [PATCH 03/20] Use frontend settings instead of env variable --- .../document-detail.component.html | 2 +- .../document-detail.component.ts | 16 +-- .../manage/settings/settings.component.html | 8 ++ .../manage/settings/settings.component.ts | 6 ++ src-ui/src/app/data/paperless-environment.ts | 3 - src-ui/src/app/data/paperless-uisettings.ts | 6 ++ .../services/rest/environment.service.spec.ts | 16 --- .../app/services/rest/environment.service.ts | 22 ----- src/documents/views.py | 99 +++++++------------ src/paperless/settings.py | 9 -- src/paperless/urls.py | 2 - 11 files changed, 63 insertions(+), 126 deletions(-) delete mode 100644 src-ui/src/app/data/paperless-environment.ts delete mode 100644 src-ui/src/app/services/rest/environment.service.spec.ts delete mode 100644 src-ui/src/app/services/rest/environment.service.ts 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 ebf286895..211f93d50 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 @@ -169,7 +169,7 @@ -
  • +
  • Comments 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 d0f4ecded..06fddd41b 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 @@ -35,7 +35,6 @@ import { StoragePathService } from 'src/app/services/rest/storage-path.service' import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path' import { StoragePathEditDialogComponent } from '../common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component' import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings' -import { EnvironmentService } from 'src/app/services/rest/environment.service' @Component({ selector: 'app-document-detail', @@ -84,8 +83,6 @@ export class DocumentDetailComponent previewCurrentPage: number = 1 previewNumPages: number = 1 - isCommentsEnabled:boolean = false - store: BehaviorSubject isDirty$: Observable unsubscribeNotifier: Subject = new Subject() @@ -121,8 +118,7 @@ export class DocumentDetailComponent private documentTitlePipe: DocumentTitlePipe, private toastService: ToastService, private settings: SettingsService, - private storagePathService: StoragePathService, - private environment: EnvironmentService + private storagePathService: StoragePathService ) {} titleKeyUp(event) { @@ -278,12 +274,6 @@ export class DocumentDetailComponent this.suggestions = null }, }) - - this.environment.get("PAPERLESS_COMMENTS_ENABLED").subscribe(result => { - this.isCommentsEnabled = (result.value.toString().toLowerCase() === "true"?true:false); - }, error => { - this.isCommentsEnabled = false; - }) this.title = this.documentTitlePipe.transform(doc.title) this.documentForm.patchValue(doc) @@ -562,4 +552,8 @@ export class DocumentDetailComponent this.password = (event.target as HTMLInputElement).value } } + + get commentsEnabled(): boolean { + return this.settings.get(SETTINGS_KEYS.COMMENTS_ENABLED) + } } diff --git a/src-ui/src/app/components/manage/settings/settings.component.html b/src-ui/src/app/components/manage/settings/settings.component.html index 002cc4eed..f72587139 100644 --- a/src-ui/src/app/components/manage/settings/settings.component.html +++ b/src-ui/src/app/components/manage/settings/settings.component.html @@ -125,6 +125,14 @@ +

    Comments

    + +
    +
    + +
    +
    +
  • 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 22ecfe9bb..bb7244663 100644 --- a/src-ui/src/app/components/manage/settings/settings.component.ts +++ b/src-ui/src/app/components/manage/settings/settings.component.ts @@ -44,6 +44,7 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent { notificationsConsumerSuccess: new FormControl(null), notificationsConsumerFailed: new FormControl(null), notificationsConsumerSuppressOnDashboard: new FormControl(null), + commentsEnabled: new FormControl(null), }) savedViews: PaperlessSavedView[] @@ -116,6 +117,7 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent { notificationsConsumerSuppressOnDashboard: this.settings.get( SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD ), + commentsEnabled: this.settings.get(SETTINGS_KEYS.COMMENTS_ENABLED), } for (let view of this.savedViews) { @@ -234,6 +236,10 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent { SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD, this.settingsForm.value.notificationsConsumerSuppressOnDashboard ) + this.settings.set( + SETTINGS_KEYS.COMMENTS_ENABLED, + this.settingsForm.value.commentsEnabled + ) this.settings.setLanguage(this.settingsForm.value.displayLanguage) this.settings .storeSettings() diff --git a/src-ui/src/app/data/paperless-environment.ts b/src-ui/src/app/data/paperless-environment.ts deleted file mode 100644 index 27dda6427..000000000 --- a/src-ui/src/app/data/paperless-environment.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface PaperlessEnvironment { - value?: string; -} \ No newline at end of file diff --git a/src-ui/src/app/data/paperless-uisettings.ts b/src-ui/src/app/data/paperless-uisettings.ts index 75aec2a51..e3d977687 100644 --- a/src-ui/src/app/data/paperless-uisettings.ts +++ b/src-ui/src/app/data/paperless-uisettings.ts @@ -36,6 +36,7 @@ export const SETTINGS_KEYS = { 'general-settings:notifications:consumer-failed', NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD: 'general-settings:notifications:consumer-suppress-on-dashboard', + COMMENTS_ENABLED: 'general-settings:comments-enabled', } export const SETTINGS: PaperlessUiSetting[] = [ @@ -114,4 +115,9 @@ export const SETTINGS: PaperlessUiSetting[] = [ type: 'boolean', default: true, }, + { + key: SETTINGS_KEYS.COMMENTS_ENABLED, + type: 'boolean', + default: true, + }, ] diff --git a/src-ui/src/app/services/rest/environment.service.spec.ts b/src-ui/src/app/services/rest/environment.service.spec.ts deleted file mode 100644 index 941a180b9..000000000 --- a/src-ui/src/app/services/rest/environment.service.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { EnvironmentService } from './environment.service'; - -describe('EnvironmentService', () => { - let service: EnvironmentService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(EnvironmentService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); \ No newline at end of file diff --git a/src-ui/src/app/services/rest/environment.service.ts b/src-ui/src/app/services/rest/environment.service.ts deleted file mode 100644 index 86ac146ea..000000000 --- a/src-ui/src/app/services/rest/environment.service.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { HttpClient, HttpParams } from '@angular/common/http'; -import { Injectable } from '@angular/core'; -import { Observable } from 'rxjs'; -import { PaperlessEnvironment } from 'src/app/data/paperless-environment'; -import { environment } from 'src/environments/environment' - -@Injectable({ - providedIn: 'root' -}) -export class EnvironmentService { - - protected baseUrl: string = environment.apiBaseUrl - - constructor(protected http: HttpClient) { } - - get(environment: string): Observable { - let httpParams = new HttpParams(); - httpParams = httpParams.set('name', environment); - - return this.http.get(`${this.baseUrl}environment/`, {params: httpParams}) - } -} \ No newline at end of file diff --git a/src/documents/views.py b/src/documents/views.py index 4633642e6..9713e5d95 100644 --- a/src/documents/views.py +++ b/src/documents/views.py @@ -22,7 +22,6 @@ from django.http import Http404 from django.http import HttpResponse from django.http import HttpResponseBadRequest from django.http import HttpResponseNotAllowed -from django.http import HttpResponseNotFound from django.utils.decorators import method_decorator from django.utils.translation import get_language from django.views.decorators.cache import cache_control @@ -385,21 +384,22 @@ class DocumentViewSet( def getComments(self, doc): return [ { - "id":c.id, - "comment":c.comment, - "created":c.created, - "user":{ - "id":c.user.id, + "id": c.id, + "comment": c.comment, + "created": c.created, + "user": { + "id": c.user.id, "username": c.user.username, - "firstname":c.user.first_name, - "lastname":c.user.last_name - } - } for c in Comment.objects.filter(document=doc).order_by('-created') - ]; + "firstname": c.user.first_name, + "lastname": c.user.last_name, + }, + } + for c in Comment.objects.filter(document=doc).order_by("-created") + ] - @action(methods=['get', 'post', 'delete'], detail=True) + @action(methods=["get", "post", "delete"], detail=True) def comments(self, request, pk=None): - if settings.PAPERLESS_COMMENTS_ENABLED != True: + if settings.PAPERLESS_COMMENTS_ENABLED is not True: return HttpResponseNotAllowed("comment function is disabled") try: @@ -407,35 +407,39 @@ class DocumentViewSet( except Document.DoesNotExist: raise Http404() - currentUser = request.user; + currentUser = request.user - if request.method == 'GET': + if request.method == "GET": try: - return Response(self.getComments(doc)); + return Response(self.getComments(doc)) except Exception as e: - return Response({"error": str(e)}); - elif request.method == 'POST': + return Response({"error": str(e)}) + elif request.method == "POST": try: c = Comment.objects.create( - document = doc, + document=doc, comment=request.data["payload"], - user=currentUser - ); - c.save(); + user=currentUser, + ) + c.save() - return Response(self.getComments(doc)); + return Response(self.getComments(doc)) except Exception as e: - return Response({ - "error": str(e) - }); - elif request.method == 'DELETE': - comment = Comment.objects.get(id=int(request.GET.get("commentId"))); - comment.delete(); - return Response(self.getComments(doc)); + return Response( + { + "error": str(e), + }, + ) + elif request.method == "DELETE": + comment = Comment.objects.get(id=int(request.GET.get("commentId"))) + comment.delete() + return Response(self.getComments(doc)) - return Response({ - "error": "error" - }); + return Response( + { + "error": "error", + }, + ) class SearchResultSerializer(DocumentSerializer): @@ -893,32 +897,3 @@ class AcknowledgeTasksView(GenericAPIView): return Response({"result": result}) except Exception: return HttpResponseBadRequest() - -class EnvironmentView(APIView): - - permission_classes = (IsAuthenticated,) - - def get(self, request, format=None): - if 'name' in request.query_params: - name = request.query_params['name'] - else: - return HttpResponseBadRequest("name required") - - if(name not in settings.PAPERLESS_FRONTEND_ALLOWED_ENVIRONMENTS and settings.PAPERLESS_DISABLED_FRONTEND_ENVIRONMENT_CHECK == False): - return HttpResponseNotAllowed("environment not allowed to request") - - value = None - try: - value = getattr(settings, name) - except: - try: - value = os.getenv(name) - except: - value = None - - if value == None: - return HttpResponseNotFound("environment not found") - - return Response({ - "value": str(value) - }); diff --git a/src/paperless/settings.py b/src/paperless/settings.py index e42cf4359..bfa498359 100644 --- a/src/paperless/settings.py +++ b/src/paperless/settings.py @@ -566,15 +566,6 @@ CONVERT_MEMORY_LIMIT = os.getenv("PAPERLESS_CONVERT_MEMORY_LIMIT") GS_BINARY = os.getenv("PAPERLESS_GS_BINARY", "gs") -# Comment settings -PAPERLESS_COMMENTS_ENABLED = __get_boolean("PAPERLESS_COMMENTS_ENABLED", "NO") - -# allowed environments for frontend -PAPERLESS_DISABLED_FRONTEND_ENVIRONMENT_CHECK = __get_boolean("PAPERLESS_DISABLED_FRONTEND_ENVIRONMENT_CHECK", "NO") -PAPERLESS_FRONTEND_ALLOWED_ENVIRONMENTS = [ - "PAPERLESS_COMMENTS_ENABLED" -] - # Pre-2.x versions of Paperless stored your documents locally with GPG # encryption, but that is no longer the default. This behaviour is still # available, but it must be explicitly enabled by setting diff --git a/src/paperless/urls.py b/src/paperless/urls.py index 6ba1ee263..46309e1e6 100644 --- a/src/paperless/urls.py +++ b/src/paperless/urls.py @@ -8,7 +8,6 @@ from django.utils.translation import gettext_lazy as _ from django.views.decorators.csrf import csrf_exempt from django.views.generic import RedirectView from documents.views import AcknowledgeTasksView -from documents.views import EnvironmentView from documents.views import BulkDownloadView from documents.views import BulkEditView from documents.views import CorrespondentViewSet @@ -95,7 +94,6 @@ urlpatterns = [ AcknowledgeTasksView.as_view(), name="acknowledge_tasks", ), - re_path(r"^environment/", EnvironmentView.as_view()), path("token/", views.obtain_auth_token), ] + api_router.urls, From 827fd540c7ccc13812adf98e9c31cb626fe6f4a7 Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Sun, 7 Aug 2022 15:06:21 -0700 Subject: [PATCH 04/20] Remove faux testing file --- .../rest/document-comment.service.spec.ts | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 src-ui/src/app/services/rest/document-comment.service.spec.ts diff --git a/src-ui/src/app/services/rest/document-comment.service.spec.ts b/src-ui/src/app/services/rest/document-comment.service.spec.ts deleted file mode 100644 index 112144b90..000000000 --- a/src-ui/src/app/services/rest/document-comment.service.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { DocumentCommentService } from './document-comment.service'; - -describe('DocumentCommentService', () => { - let service: DocumentCommentService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(DocumentCommentService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); \ No newline at end of file From a74b078f302ac2f6da983fac134e3a9503130e64 Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Sun, 7 Aug 2022 15:06:52 -0700 Subject: [PATCH 05/20] Fix comment service method signature and run prettier over file --- .../services/rest/document-comment.service.ts | 44 +++++++++++-------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/src-ui/src/app/services/rest/document-comment.service.ts b/src-ui/src/app/services/rest/document-comment.service.ts index b5739d65e..2dfbb3185 100644 --- a/src-ui/src/app/services/rest/document-comment.service.ts +++ b/src-ui/src/app/services/rest/document-comment.service.ts @@ -1,31 +1,39 @@ -import { Injectable } from '@angular/core'; -import { HttpClient, HttpParams } from '@angular/common/http'; -import { PaperlessDocumentComment } from 'src/app/data/paperless-document-comment'; -import { AbstractPaperlessService } from './abstract-paperless-service'; -import { Observable } from 'rxjs'; -import { PaperlessDocumentCommentFrame } from 'src/app/data/paperless-document-comment-frame'; +import { Injectable } from '@angular/core' +import { HttpClient, HttpParams } from '@angular/common/http' +import { PaperlessDocumentComment } from 'src/app/data/paperless-document-comment' +import { AbstractPaperlessService } from './abstract-paperless-service' +import { Observable } from 'rxjs' @Injectable({ - providedIn: 'root' + providedIn: 'root', }) export class DocumentCommentService extends AbstractPaperlessService { - constructor(http: HttpClient) { super(http, 'documents') } - - getComments(id: number): Observable { - return this.http.get(this.getResourceUrl(id, "comments")) + getComments(id: number): Observable { + return this.http.get( + this.getResourceUrl(id, 'comments') + ) } - addComment(id: number, comment): Observable{ - return this.http.post(this.getResourceUrl(id, 'comments'), {"payload": comment}) + addComment(id: number, comment): Observable { + return this.http.post( + this.getResourceUrl(id, 'comments'), + { payload: comment } + ) } - deleteComment(documentId: number, commentId: number): Observable{ - let httpParams = new HttpParams(); - httpParams = httpParams.set("commentId", commentId.toString()); - return this.http.delete(this.getResourceUrl(documentId, 'comments'), {params: httpParams}); + deleteComment( + documentId: number, + commentId: number + ): Observable { + let httpParams = new HttpParams() + httpParams = httpParams.set('commentId', commentId.toString()) + return this.http.delete( + this.getResourceUrl(documentId, 'comments'), + { params: httpParams } + ) } -} \ No newline at end of file +} From 0c9906f00e4ec761c8d8a052a6d9e6d0b7617f2b Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Sun, 7 Aug 2022 15:47:30 -0700 Subject: [PATCH 06/20] Remove faux test file --- .../document-comment.component.spec.ts | 25 ------------------- 1 file changed, 25 deletions(-) delete mode 100644 src-ui/src/app/components/document-comment/document-comment.component.spec.ts diff --git a/src-ui/src/app/components/document-comment/document-comment.component.spec.ts b/src-ui/src/app/components/document-comment/document-comment.component.spec.ts deleted file mode 100644 index 1f6389b1d..000000000 --- a/src-ui/src/app/components/document-comment/document-comment.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { DocumentCommentComponent } from './document-comment.component'; - -describe('DocumentCommentComponent', () => { - let component: DocumentCommentComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ DocumentCommentComponent ] - }) - .compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(DocumentCommentComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); \ No newline at end of file From 644a7ac41a18bd23bf9c57032bb1f0d0c7191158 Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Sun, 7 Aug 2022 17:30:49 -0700 Subject: [PATCH 07/20] Missed PAPERLESS_COMMENTS_ENABLED code --- src/documents/views.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/documents/views.py b/src/documents/views.py index 9713e5d95..41f652905 100644 --- a/src/documents/views.py +++ b/src/documents/views.py @@ -21,7 +21,6 @@ from django.db.models.functions import Lower from django.http import Http404 from django.http import HttpResponse from django.http import HttpResponseBadRequest -from django.http import HttpResponseNotAllowed from django.utils.decorators import method_decorator from django.utils.translation import get_language from django.views.decorators.cache import cache_control @@ -399,9 +398,6 @@ class DocumentViewSet( @action(methods=["get", "post", "delete"], detail=True) def comments(self, request, pk=None): - if settings.PAPERLESS_COMMENTS_ENABLED is not True: - return HttpResponseNotAllowed("comment function is disabled") - try: doc = Document.objects.get(pk=pk) except Document.DoesNotExist: From 349060658c3b0f978bf565da77bc6e3dd987dd50 Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Sun, 7 Aug 2022 17:31:03 -0700 Subject: [PATCH 08/20] fix migration dependency and run code style --- src/documents/migrations/1023_add_comments.py | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/documents/migrations/1023_add_comments.py b/src/documents/migrations/1023_add_comments.py index 8ae779e66..124e4777c 100644 --- a/src/documents/migrations/1023_add_comments.py +++ b/src/documents/migrations/1023_add_comments.py @@ -1,19 +1,28 @@ from django.db import migrations, models + class Migration(migrations.Migration): dependencies = [ - ('documents', '1016_auto_20210317_1351'), + ("documents", "1022_paperlesstask"), ] operations = [ migrations.CreateModel( - name='Comment', + name="Comment", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('comment', models.TextField()), - ('created', models.DateTimeField(auto_now_add=True)), - ('document_id', models.PositiveIntegerField()), - ('user_id', models.PositiveIntegerField()) + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("comment", models.TextField()), + ("created", models.DateTimeField(auto_now_add=True)), + ("document_id", models.PositiveIntegerField()), + ("user_id", models.PositiveIntegerField()), ], ) - ] \ No newline at end of file + ] From 9c360a2ceb5572ce997c69451f87dd225f3bb980 Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Sun, 7 Aug 2022 17:33:13 -0700 Subject: [PATCH 09/20] Refactor comment UI code And run prettier --- src-ui/src/app/app.module.ts | 4 +- .../document-comment.component.ts | 63 ---------------- .../document-comments.component.html} | 10 +-- .../document-comments.component.scss} | 2 +- .../document-comments.component.ts | 73 +++++++++++++++++++ .../document-detail.component.html | 2 +- ...ervice.ts => document-comments.service.ts} | 6 +- 7 files changed, 85 insertions(+), 75 deletions(-) delete mode 100644 src-ui/src/app/components/document-comment/document-comment.component.ts rename src-ui/src/app/components/{document-comment/document-comment.component.html => document-comments/document-comments.component.html} (71%) rename src-ui/src/app/components/{document-comment/document-comment.component.scss => document-comments/document-comments.component.scss} (99%) create mode 100644 src-ui/src/app/components/document-comments/document-comments.component.ts rename src-ui/src/app/services/rest/{document-comment.service.ts => document-comments.service.ts} (82%) diff --git a/src-ui/src/app/app.module.ts b/src-ui/src/app/app.module.ts index dd34724a6..9840deb58 100644 --- a/src-ui/src/app/app.module.ts +++ b/src-ui/src/app/app.module.ts @@ -67,7 +67,7 @@ import { ApiVersionInterceptor } from './interceptors/api-version.interceptor' import { ColorSliderModule } from 'ngx-color/slider' import { ColorComponent } from './components/common/input/color/color.component' import { DocumentAsnComponent } from './components/document-asn/document-asn.component' -import { DocumentCommentComponent } from './components/document-comment/document-comment.component'; +import { DocumentCommentsComponent } from './components/document-comments/document-comments.component' import { DirtyDocGuard } from './guards/dirty-doc.guard' import localeBe from '@angular/common/locales/be' @@ -174,7 +174,7 @@ function initializeApp(settings: SettingsService) { DateComponent, ColorComponent, DocumentAsnComponent, - DocumentCommentComponent, + DocumentCommentsComponent, TasksComponent, ], imports: [ diff --git a/src-ui/src/app/components/document-comment/document-comment.component.ts b/src-ui/src/app/components/document-comment/document-comment.component.ts deleted file mode 100644 index 57b052d84..000000000 --- a/src-ui/src/app/components/document-comment/document-comment.component.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { DocumentDetailComponent } from 'src/app/components/document-detail/document-detail.component'; -import { DocumentCommentService } from 'src/app/services/rest/document-comment.service'; -import { PaperlessDocumentComment } from 'src/app/data/paperless-document-comment'; - -import { take } from 'rxjs/operators'; -import { FormControl, FormGroup } from '@angular/forms'; - -@Component({ - selector: 'app-document-comment', - templateUrl: './document-comment.component.html', - styleUrls: ['./document-comment.component.scss'] -}) -export class DocumentCommentComponent implements OnInit { - - comments:PaperlessDocumentComment[]; - networkActive = false; - documentId: number; - commentForm: FormGroup = new FormGroup({ - newcomment: new FormControl('') - }) - - constructor( - private documentDetailComponent: DocumentDetailComponent, - private documentCommentService: DocumentCommentService, - ) { } - - byId(index, item: PaperlessDocumentComment) { - return item.id; - } - - async ngOnInit(): Promise { - try { - this.documentId = this.documentDetailComponent.documentId; - this.comments= await this.documentCommentService.getComments(this.documentId).pipe(take(1)).toPromise(); - } catch(err){ - this.comments = []; - } - } - - addComment(){ - this.networkActive = true - this.documentCommentService.addComment(this.documentId, this.commentForm.get("newcomment").value).subscribe(result => { - this.comments = result; - this.commentForm.get("newcomment").reset(); - this.networkActive = false; - }, error => { - this.networkActive = false; - }); - } - - deleteComment(event){ - let parent = event.target.parentElement.closest('div[comment-id]'); - if(parent){ - this.documentCommentService.deleteComment(this.documentId, parseInt(parent.getAttribute("comment-id"))).subscribe(result => { - this.comments = result; - this.networkActive = false; - }, error => { - this.networkActive = false; - }); - } - } -} \ No newline at end of file diff --git a/src-ui/src/app/components/document-comment/document-comment.component.html b/src-ui/src/app/components/document-comments/document-comments.component.html similarity index 71% rename from src-ui/src/app/components/document-comment/document-comment.component.html rename to src-ui/src/app/components/document-comments/document-comments.component.html index 6a36f999f..bdad4da4d 100644 --- a/src-ui/src/app/components/document-comment/document-comment.component.html +++ b/src-ui/src/app/components/document-comments/document-comments.component.html @@ -1,16 +1,16 @@
    - +
    -   +

    -
    +
    {{comment?.user?.firstname}} {{comment?.user?.lastname}} ({{comment?.user?.username}}) - {{ comment?.created | customDate}} - + @@ -22,4 +22,4 @@ {{comment.comment}}
    -
    \ No newline at end of file +
    diff --git a/src-ui/src/app/components/document-comment/document-comment.component.scss b/src-ui/src/app/components/document-comments/document-comments.component.scss similarity index 99% rename from src-ui/src/app/components/document-comment/document-comment.component.scss rename to src-ui/src/app/components/document-comments/document-comments.component.scss index 778556485..a5ff6f86e 100644 --- a/src-ui/src/app/components/document-comment/document-comment.component.scss +++ b/src-ui/src/app/components/document-comments/document-comments.component.scss @@ -19,4 +19,4 @@ .comment-card-header a:hover svg { fill: var(--primary); -} \ No newline at end of file +} diff --git a/src-ui/src/app/components/document-comments/document-comments.component.ts b/src-ui/src/app/components/document-comments/document-comments.component.ts new file mode 100644 index 000000000..421811d4f --- /dev/null +++ b/src-ui/src/app/components/document-comments/document-comments.component.ts @@ -0,0 +1,73 @@ +import { Component, Input, OnInit } from '@angular/core' +import { DocumentCommentsService } from 'src/app/services/rest/document-comments.service' +import { PaperlessDocumentComment } from 'src/app/data/paperless-document-comment' +import { FormControl, FormGroup } from '@angular/forms' +import { first } from 'rxjs/operators' +import { ToastService } from 'src/app/services/toast.service' + +@Component({ + selector: 'app-document-comments', + templateUrl: './document-comments.component.html', + styleUrls: ['./document-comments.component.scss'], +}) +export class DocumentCommentsComponent implements OnInit { + commentForm: FormGroup = new FormGroup({ + newcomment: new FormControl(''), + }) + + networkActive = false + comments: PaperlessDocumentComment[] = [] + + @Input() + documentId: number + + constructor( + private commentsService: DocumentCommentsService, + private toastService: ToastService + ) {} + + ngOnInit(): void { + this.commentsService + .getComments(this.documentId) + .pipe(first()) + .subscribe((comments) => (this.comments = comments)) + } + + commentId(index, comment: PaperlessDocumentComment) { + return comment.id + } + + addComment() { + this.networkActive = true + this.commentsService + .addComment(this.documentId, this.commentForm.get('newcomment').value) + .subscribe({ + next: (result) => { + this.comments = result + this.commentForm.get('newcomment').reset() + this.networkActive = false + }, + error: (e) => { + this.networkActive = false + this.toastService.showError( + $localize`Error saving comment: ${e.toString()}` + ) + }, + }) + } + + deleteComment(commentId: number) { + this.commentsService.deleteComment(this.documentId, commentId).subscribe({ + next: (result) => { + this.comments = result + this.networkActive = false + }, + error: (e) => { + this.networkActive = false + this.toastService.showError( + $localize`Error deleting comment: ${e.toString()}` + ) + }, + }) + } +} 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 211f93d50..8e7176655 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 @@ -172,7 +172,7 @@
  • Comments - +
  • diff --git a/src-ui/src/app/services/rest/document-comment.service.ts b/src-ui/src/app/services/rest/document-comments.service.ts similarity index 82% rename from src-ui/src/app/services/rest/document-comment.service.ts rename to src-ui/src/app/services/rest/document-comments.service.ts index 2dfbb3185..b105a90d1 100644 --- a/src-ui/src/app/services/rest/document-comment.service.ts +++ b/src-ui/src/app/services/rest/document-comments.service.ts @@ -7,14 +7,14 @@ import { Observable } from 'rxjs' @Injectable({ providedIn: 'root', }) -export class DocumentCommentService extends AbstractPaperlessService { +export class DocumentCommentsService extends AbstractPaperlessService { constructor(http: HttpClient) { super(http, 'documents') } - getComments(id: number): Observable { + getComments(documentId: number): Observable { return this.http.get( - this.getResourceUrl(id, 'comments') + this.getResourceUrl(documentId, 'comments') ) } From b0ae307add3856d35dcf3b340e33a7f32a12fe47 Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Sun, 7 Aug 2022 17:45:49 -0700 Subject: [PATCH 10/20] python code style --- .../management/commands/document_exporter.py | 7 +++++-- src/documents/models.py | 13 ++++++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/documents/management/commands/document_exporter.py b/src/documents/management/commands/document_exporter.py index da00d10f5..1493bf078 100644 --- a/src/documents/management/commands/document_exporter.py +++ b/src/documents/management/commands/document_exporter.py @@ -127,8 +127,11 @@ class Command(BaseCommand): serializers.serialize("json", DocumentType.objects.all()), ) - manifest += json.loads( - serializers.serialize("json", Comment.objects.all())), + manifest += ( + json.loads( + serializers.serialize("json", Comment.objects.all()), + ), + ) documents = Document.objects.order_by("id") document_map = {d.pk: d for d in documents} diff --git a/src/documents/models.py b/src/documents/models.py index cb64946c3..b2070d2f1 100644 --- a/src/documents/models.py +++ b/src/documents/models.py @@ -538,16 +538,19 @@ class PaperlessTask(models.Model): ) acknowledged = models.BooleanField(default=False) + class Comment(models.Model): comment = models.TextField( _("content"), blank=True, - help_text=_("Comment for the document") + help_text=_("Comment for the document"), ) created = models.DateTimeField( _("created"), - default=timezone.now, db_index=True) + default=timezone.now, + db_index=True, + ) document = models.ForeignKey( Document, @@ -555,7 +558,7 @@ class Comment(models.Model): null=True, related_name="documents", on_delete=models.CASCADE, - verbose_name=_("document") + verbose_name=_("document"), ) user = models.ForeignKey( @@ -564,7 +567,7 @@ class Comment(models.Model): null=True, related_name="users", on_delete=models.SET_NULL, - verbose_name=_("user") + verbose_name=_("user"), ) class Meta: @@ -573,4 +576,4 @@ class Comment(models.Model): verbose_name_plural = _("comments") def __str__(self): - return self.content \ No newline at end of file + return self.content From bf1f65dc8904ac55e89aed14ba84a5dbd35af51e Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Sun, 7 Aug 2022 21:44:28 -0700 Subject: [PATCH 11/20] Rework comment UI Add error popups, text field validation, move form, move comment header to footer, updated styling --- .../document-comments.component.html | 44 +++++++------- .../document-comments.component.scss | 21 ++----- .../document-comments.component.ts | 57 ++++++++++++------- 3 files changed, 65 insertions(+), 57 deletions(-) diff --git a/src-ui/src/app/components/document-comments/document-comments.component.html b/src-ui/src/app/components/document-comments/document-comments.component.html index bdad4da4d..055c97552 100644 --- a/src-ui/src/app/components/document-comments/document-comments.component.html +++ b/src-ui/src/app/components/document-comments/document-comments.component.html @@ -1,25 +1,29 @@
    -
    -
    - +
    +
    +

    {{comment.comment}}

    - - -
    -
    -
    - {{comment?.user?.firstname}} {{comment?.user?.lastname}} ({{comment?.user?.username}}) - {{ comment?.created | customDate}} - - - - - - - - -
    -
    - {{comment.comment}} +
    +
    +
    +
    + +
    + Please enter a comment. +
    +
    +
    + +
    +
    +
    diff --git a/src-ui/src/app/components/document-comments/document-comments.component.scss b/src-ui/src/app/components/document-comments/document-comments.component.scss index a5ff6f86e..d7e21e14e 100644 --- a/src-ui/src/app/components/document-comments/document-comments.component.scss +++ b/src-ui/src/app/components/document-comments/document-comments.component.scss @@ -1,22 +1,9 @@ -.comment-card-body { - padding-top: .8rem !important; - padding-bottom: .8rem !important; - max-height: 10rem; +.card-body { + max-height: 12rem; overflow: scroll; white-space: pre-wrap; } -.comment-card-header a { - border: none; - background: none; - padding: 5px; - border-radius: 50%; -} - -.comment-card-header a:hover { - background: #FFF; -} - -.comment-card-header a:hover svg { - fill: var(--primary); +.card:hover .fade { + opacity: 1; } diff --git a/src-ui/src/app/components/document-comments/document-comments.component.ts b/src-ui/src/app/components/document-comments/document-comments.component.ts index 421811d4f..5362e1661 100644 --- a/src-ui/src/app/components/document-comments/document-comments.component.ts +++ b/src-ui/src/app/components/document-comments/document-comments.component.ts @@ -12,11 +12,12 @@ import { ToastService } from 'src/app/services/toast.service' }) export class DocumentCommentsComponent implements OnInit { commentForm: FormGroup = new FormGroup({ - newcomment: new FormControl(''), + newComment: new FormControl(''), }) networkActive = false comments: PaperlessDocumentComment[] = [] + newCommentError: boolean = false @Input() documentId: number @@ -33,27 +34,30 @@ export class DocumentCommentsComponent implements OnInit { .subscribe((comments) => (this.comments = comments)) } - commentId(index, comment: PaperlessDocumentComment) { - return comment.id - } - addComment() { + const comment: string = this.commentForm + .get('newComment') + .value.toString() + .trim() + if (comment.length == 0) { + this.newCommentError = true + return + } + this.newCommentError = false this.networkActive = true - this.commentsService - .addComment(this.documentId, this.commentForm.get('newcomment').value) - .subscribe({ - next: (result) => { - this.comments = result - this.commentForm.get('newcomment').reset() - this.networkActive = false - }, - error: (e) => { - this.networkActive = false - this.toastService.showError( - $localize`Error saving comment: ${e.toString()}` - ) - }, - }) + this.commentsService.addComment(this.documentId, comment).subscribe({ + next: (result) => { + this.comments = result + this.commentForm.get('newComment').reset() + this.networkActive = false + }, + error: (e) => { + this.networkActive = false + this.toastService.showError( + $localize`Error saving comment: ${e.toString()}` + ) + }, + }) } deleteComment(commentId: number) { @@ -70,4 +74,17 @@ export class DocumentCommentsComponent implements OnInit { }, }) } + + displayName(comment: PaperlessDocumentComment): string { + if (!comment.user) return '' + let nameComponents = [] + if (comment.user.firstname) nameComponents.unshift(comment.user.firstname) + if (comment.user.lastname) nameComponents.unshift(comment.user.lastname) + if (comment.user.username) { + if (nameComponents.length > 0) + nameComponents.push(`(${comment.user.username})`) + else nameComponents.push(comment.user.username) + } + return nameComponents.join(' ') + } } From 765e27f719a1f46343d7a492848b73445a18c341 Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Sun, 7 Aug 2022 22:05:32 -0700 Subject: [PATCH 12/20] Cleanup frontend comment service & other code [ci skip] --- .../document-comments/document-comments.component.html | 7 +++---- .../document-detail/document-detail.component.html | 1 - .../document-detail/document-detail.component.ts | 1 - src-ui/src/app/data/paperless-document-comment.ts | 10 +++++----- src-ui/src/app/data/user-type.ts | 7 ------- src-ui/src/app/data/user.ts | 7 +++++++ .../src/app/services/rest/document-comments.service.ts | 6 ++---- src/documents/views.py | 4 ++-- src/paperless/settings.py | 1 + 9 files changed, 20 insertions(+), 24 deletions(-) delete mode 100644 src-ui/src/app/data/user-type.ts create mode 100644 src-ui/src/app/data/user.ts diff --git a/src-ui/src/app/components/document-comments/document-comments.component.html b/src-ui/src/app/components/document-comments/document-comments.component.html index 055c97552..107d02623 100644 --- a/src-ui/src/app/components/document-comments/document-comments.component.html +++ b/src-ui/src/app/components/document-comments/document-comments.component.html @@ -4,11 +4,10 @@

    {{comment.comment}}

    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 8e7176655..8481cad2f 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 @@ -174,7 +174,6 @@ - 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 06fddd41b..ff0a5303b 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 @@ -274,7 +274,6 @@ export class DocumentDetailComponent this.suggestions = null }, }) - this.title = this.documentTitlePipe.transform(doc.title) this.documentForm.patchValue(doc) } diff --git a/src-ui/src/app/data/paperless-document-comment.ts b/src-ui/src/app/data/paperless-document-comment.ts index 1b60e6c5c..14085cf32 100644 --- a/src-ui/src/app/data/paperless-document-comment.ts +++ b/src-ui/src/app/data/paperless-document-comment.ts @@ -1,8 +1,8 @@ import { ObjectWithId } from './object-with-id' -import { CommentUser } from './user-type' +import { User } from './user' export interface PaperlessDocumentComment extends ObjectWithId { - created?: Date - comment?: string - user?: CommentUser -} \ No newline at end of file + created?: Date + comment?: string + user?: User +} diff --git a/src-ui/src/app/data/user-type.ts b/src-ui/src/app/data/user-type.ts deleted file mode 100644 index 9324cab43..000000000 --- a/src-ui/src/app/data/user-type.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { ObjectWithId } from './object-with-id' - -export interface CommentUser extends ObjectWithId { - username: string - firstname: string - lastname: string -} \ No newline at end of file diff --git a/src-ui/src/app/data/user.ts b/src-ui/src/app/data/user.ts new file mode 100644 index 000000000..adf00e86b --- /dev/null +++ b/src-ui/src/app/data/user.ts @@ -0,0 +1,7 @@ +import { ObjectWithId } from './object-with-id' + +export interface User extends ObjectWithId { + username: string + firstname: string + lastname: string +} diff --git a/src-ui/src/app/services/rest/document-comments.service.ts b/src-ui/src/app/services/rest/document-comments.service.ts index b105a90d1..a697c0e93 100644 --- a/src-ui/src/app/services/rest/document-comments.service.ts +++ b/src-ui/src/app/services/rest/document-comments.service.ts @@ -21,7 +21,7 @@ export class DocumentCommentsService extends AbstractPaperlessService { return this.http.post( this.getResourceUrl(id, 'comments'), - { payload: comment } + { comment: comment } ) } @@ -29,11 +29,9 @@ export class DocumentCommentsService extends AbstractPaperlessService { - let httpParams = new HttpParams() - httpParams = httpParams.set('commentId', commentId.toString()) return this.http.delete( this.getResourceUrl(documentId, 'comments'), - { params: httpParams } + { params: new HttpParams({ fromString: `id=${commentId}` }) } ) } } diff --git a/src/documents/views.py b/src/documents/views.py index 41f652905..b9c8a1265 100644 --- a/src/documents/views.py +++ b/src/documents/views.py @@ -414,7 +414,7 @@ class DocumentViewSet( try: c = Comment.objects.create( document=doc, - comment=request.data["payload"], + comment=request.data["comment"], user=currentUser, ) c.save() @@ -427,7 +427,7 @@ class DocumentViewSet( }, ) elif request.method == "DELETE": - comment = Comment.objects.get(id=int(request.GET.get("commentId"))) + comment = Comment.objects.get(id=int(request.GET.get("id"))) comment.delete() return Response(self.getComments(doc)) diff --git a/src/paperless/settings.py b/src/paperless/settings.py index bfa498359..2ce99ac0e 100644 --- a/src/paperless/settings.py +++ b/src/paperless/settings.py @@ -566,6 +566,7 @@ CONVERT_MEMORY_LIMIT = os.getenv("PAPERLESS_CONVERT_MEMORY_LIMIT") GS_BINARY = os.getenv("PAPERLESS_GS_BINARY", "gs") + # Pre-2.x versions of Paperless stored your documents locally with GPG # encryption, but that is no longer the default. This behaviour is still # available, but it must be explicitly enabled by setting From 1d67acc44d50a8cd44273692f16815f4def9c2af Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue, 23 Aug 2022 15:15:34 -0700 Subject: [PATCH 13/20] Move add comment to top of comments list --- .../document-comments.component.html | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src-ui/src/app/components/document-comments/document-comments.component.html b/src-ui/src/app/components/document-comments/document-comments.component.html index 107d02623..7e4afa90a 100644 --- a/src-ui/src/app/components/document-comments/document-comments.component.html +++ b/src-ui/src/app/components/document-comments/document-comments.component.html @@ -1,4 +1,16 @@
    +
    +
    + +
    + Please enter a comment. +
    +
    +
    + +
    +
    +

    {{comment.comment}}

    @@ -12,17 +24,4 @@
    -
    -
    -
    - -
    - Please enter a comment. -
    -
    -
    - -
    -
    -
    From 39661a548a9f728dc4c404ef7a2109ed00f7fe0e Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Sun, 7 Aug 2022 22:33:05 -0700 Subject: [PATCH 14/20] Update messages.xlf --- src-ui/messages.xlf | 117 ++++++++++++++++++++++++++++++++------------ 1 file changed, 85 insertions(+), 32 deletions(-) diff --git a/src-ui/messages.xlf b/src-ui/messages.xlf index b17b0ee10..3080e8b84 100644 --- a/src-ui/messages.xlf +++ b/src-ui/messages.xlf @@ -395,7 +395,7 @@ src/app/components/manage/settings/settings.component.html - 150 + 158 @@ -701,7 +701,7 @@ src/app/components/manage/settings/settings.component.html - 157 + 165 src/app/components/manage/tasks/tasks.component.html @@ -816,7 +816,7 @@ src/app/components/document-detail/document-detail.component.html - 178 + 184 src/app/components/document-list/save-view-config-dialog/save-view-config-dialog.component.html @@ -824,7 +824,7 @@ src/app/components/manage/settings/settings.component.html - 189 + 197 @@ -1304,6 +1304,41 @@ 1 + + Enter comment + + src/app/components/document-comments/document-comments.component.html + 4 + + + + Please enter a comment. + + src/app/components/document-comments/document-comments.component.html + 5,7 + + + + Add comment + + src/app/components/document-comments/document-comments.component.html + 10 + + + + Error saving comment: + + src/app/components/document-comments/document-comments.component.ts + 57 + + + + Error deleting comment: + + src/app/components/document-comments/document-comments.component.ts + 72 + + Page @@ -1370,7 +1405,7 @@ src/app/components/manage/settings/settings.component.html - 175 + 183 @@ -1634,21 +1669,32 @@ src/app/components/document-detail/document-detail.component.html - 196 + 202 + + + + Comments + + src/app/components/document-detail/document-detail.component.html + 173 + + + src/app/components/manage/settings/settings.component.html + 128 Discard src/app/components/document-detail/document-detail.component.html - 176 + 182 Save & next src/app/components/document-detail/document-detail.component.html - 177 + 183 @@ -1832,7 +1878,7 @@ src/app/components/manage/settings/settings.component.html - 174 + 182 src/app/components/manage/tasks/tasks.component.html @@ -2310,14 +2356,14 @@ View "" saved successfully. src/app/components/document-list/document-list.component.ts - 180 + 176 View "" created successfully. src/app/components/document-list/document-list.component.ts - 210 + 206 @@ -2454,7 +2500,7 @@ src/app/components/manage/settings/settings.component.html - 169 + 177 @@ -2465,7 +2511,7 @@ src/app/components/manage/settings/settings.component.html - 165 + 173 @@ -2840,123 +2886,130 @@ 124 + + Enable comments + + src/app/components/manage/settings/settings.component.html + 132 + + Notifications src/app/components/manage/settings/settings.component.html - 132 + 140 Document processing src/app/components/manage/settings/settings.component.html - 135 + 143 Show notifications when new documents are detected src/app/components/manage/settings/settings.component.html - 139 + 147 Show notifications when document processing completes successfully src/app/components/manage/settings/settings.component.html - 140 + 148 Show notifications when document processing fails src/app/components/manage/settings/settings.component.html - 141 + 149 Suppress notifications on dashboard src/app/components/manage/settings/settings.component.html - 142 + 150 This will suppress all messages about document processing status on the dashboard. src/app/components/manage/settings/settings.component.html - 142 + 150 Appears on src/app/components/manage/settings/settings.component.html - 162 + 170 No saved views defined. src/app/components/manage/settings/settings.component.html - 179 + 187 Saved view "" deleted. src/app/components/manage/settings/settings.component.ts - 174 + 176 Settings saved src/app/components/manage/settings/settings.component.ts - 247 + 253 Settings were saved successfully. src/app/components/manage/settings/settings.component.ts - 248 + 254 Settings were saved successfully. Reload is required to apply some changes. src/app/components/manage/settings/settings.component.ts - 252 + 258 Reload now src/app/components/manage/settings/settings.component.ts - 253 + 259 An error occurred while saving settings. src/app/components/manage/settings/settings.component.ts - 263 + 269 Use system language src/app/components/manage/settings/settings.component.ts - 271 + 277 Use date format of display language src/app/components/manage/settings/settings.component.ts - 278 + 284 @@ -2965,7 +3018,7 @@ )"/> src/app/components/manage/settings/settings.component.ts - 298,300 + 304,306 @@ -3211,7 +3264,7 @@ Warning: You have unsaved changes to your document(s). src/app/guards/dirty-doc.guard.ts - 18 + 17 From fc34b85b6ec9482b2cff39c14e26e2ad082166fa Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue, 23 Aug 2022 21:59:54 -0700 Subject: [PATCH 15/20] Add frontend tests for comments --- .../e2e/documents/document-detail.cy.ts | 52 +++++++++++++++++++ .../fixtures/documents/1/comments.json | 46 ++++++++++++++++ .../document-comments.component.html | 4 +- 3 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 src-ui/cypress/fixtures/documents/1/comments.json diff --git a/src-ui/cypress/e2e/documents/document-detail.cy.ts b/src-ui/cypress/e2e/documents/document-detail.cy.ts index cc269655a..a836ffa92 100644 --- a/src-ui/cypress/e2e/documents/document-detail.cy.ts +++ b/src-ui/cypress/e2e/documents/document-detail.cy.ts @@ -17,6 +17,32 @@ describe('document-detail', () => { req.reply({ result: 'OK' }) }).as('saveDoc') + cy.fixture('documents/1/comments.json').then((commentsJson) => { + cy.intercept( + 'GET', + 'http://localhost:8000/api/documents/1/comments/', + (req) => { + req.reply(commentsJson.filter((c) => c.id != 10)) // 3 + } + ) + + cy.intercept( + 'DELETE', + 'http://localhost:8000/api/documents/1/comments/?id=9', + (req) => { + req.reply(commentsJson.filter((c) => c.id != 9 && c.id != 10)) // 2 + } + ) + + cy.intercept( + 'POST', + 'http://localhost:8000/api/documents/1/comments/', + (req) => { + req.reply(commentsJson) // 4 + } + ) + }) + cy.viewport(1024, 1024) cy.visit('/documents/1/') }) @@ -39,4 +65,30 @@ describe('document-detail', () => { cy.contains('button', 'Save').click().wait('@saveDoc').wait(2000) // navigates away after saving cy.contains('You have unsaved changes').should('not.exist') }) + + it('should show a list of comments', () => { + cy.wait(1000).get('a').contains('Comments').click().wait(1000) + cy.get('app-document-comments').find('.card').its('length').should('eq', 3) + }) + + it('should support comment deletion', () => { + cy.wait(1000).get('a').contains('Comments').click().wait(1000) + cy.get('app-document-comments') + .find('.card') + .first() + .find('button') + .click({ force: true }) + .wait(500) + cy.get('app-document-comments').find('.card').its('length').should('eq', 2) + }) + + it('should support comment insertion', () => { + cy.wait(1000).get('a').contains('Comments').click().wait(1000) + cy.get('app-document-comments') + .find('form textarea') + .type('Testing new comment') + .wait(500) + cy.get('app-document-comments').find('form button').click().wait(1500) + cy.get('app-document-comments').find('.card').its('length').should('eq', 4) + }) }) diff --git a/src-ui/cypress/fixtures/documents/1/comments.json b/src-ui/cypress/fixtures/documents/1/comments.json new file mode 100644 index 000000000..73e932187 --- /dev/null +++ b/src-ui/cypress/fixtures/documents/1/comments.json @@ -0,0 +1,46 @@ +[ + { + "id": 10, + "comment": "Testing new comment", + "created": "2022-08-08T04:24:55.176008Z", + "user": { + "id": 1, + "username": "user2", + "firstname": "", + "lastname": "" + } + }, + { + "id": 9, + "comment": "Testing one more time", + "created": "2022-02-18T04:24:55.176008Z", + "user": { + "id": 2, + "username": "user1", + "firstname": "", + "lastname": "" + } + }, + { + "id": 8, + "comment": "Another comment", + "created": "2021-11-08T04:24:47.925042Z", + "user": { + "id": 2, + "username": "user33", + "firstname": "", + "lastname": "" + } + }, + { + "id": 7, + "comment": "Cupcake ipsum dolor sit amet cheesecake candy cookie tiramisu. Donut chocolate chupa chups macaroon brownie halvah pie cheesecake gummies. Sweet chocolate bar candy donut gummi bears bear claw liquorice bonbon shortbread.\n\nDonut chocolate bar candy wafer wafer tiramisu. Gummies chocolate cake muffin toffee carrot cake macaroon. Toffee toffee jelly beans danish lollipop cake.", + "created": "2021-02-08T02:37:49.724132Z", + "user": { + "id": 3, + "username": "admin", + "firstname": "", + "lastname": "" + } + } +] diff --git a/src-ui/src/app/components/document-comments/document-comments.component.html b/src-ui/src/app/components/document-comments/document-comments.component.html index 7e4afa90a..9a2e7debb 100644 --- a/src-ui/src/app/components/document-comments/document-comments.component.html +++ b/src-ui/src/app/components/document-comments/document-comments.component.html @@ -17,11 +17,11 @@
    From 680055474e32c6511e92a006316154f467b32e7c Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue, 23 Aug 2022 22:04:27 -0700 Subject: [PATCH 16/20] Basic verification of Comment export & exporter comment tuple fix From oprhaned commits https://github.com/paperless-ngx/paperless-ngx/commit/b1855a4b7af689d0a7c7f18bf7ef513967da269f https://github.com/paperless-ngx/paperless-ngx/commit/53f21574fd8af0f3561c12f709a14980f8f1cb7f Co-Authored-By: Trenton Holmes <797416+stumpylog@users.noreply.github.com> --- .../management/commands/document_exporter.py | 6 ++---- src/documents/tests/test_management_exporter.py | 17 ++++++++++++++++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/documents/management/commands/document_exporter.py b/src/documents/management/commands/document_exporter.py index 1493bf078..dc53a690e 100644 --- a/src/documents/management/commands/document_exporter.py +++ b/src/documents/management/commands/document_exporter.py @@ -127,10 +127,8 @@ class Command(BaseCommand): serializers.serialize("json", DocumentType.objects.all()), ) - manifest += ( - json.loads( - serializers.serialize("json", Comment.objects.all()), - ), + manifest += json.loads( + serializers.serialize("json", Comment.objects.all()), ) documents = Document.objects.order_by("id") diff --git a/src/documents/tests/test_management_exporter.py b/src/documents/tests/test_management_exporter.py index a9dcabc4d..92f1d3b62 100644 --- a/src/documents/tests/test_management_exporter.py +++ b/src/documents/tests/test_management_exporter.py @@ -10,10 +10,12 @@ from django.core.management import call_command from django.test import override_settings from django.test import TestCase from documents.management.commands import document_exporter +from documents.models import Comment from documents.models import Correspondent from documents.models import Document from documents.models import DocumentType from documents.models import Tag +from documents.models import User from documents.sanity_checker import check_sanity from documents.settings import EXPORTER_FILE_NAME from documents.tests.utils import DirectoriesMixin @@ -25,6 +27,8 @@ class TestExportImport(DirectoriesMixin, TestCase): self.target = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, self.target) + self.user = User.objects.create(username="temp_admin") + self.d1 = Document.objects.create( content="Content", checksum="42995833e01aea9b3edee44bbfdd7ce1", @@ -57,6 +61,12 @@ class TestExportImport(DirectoriesMixin, TestCase): storage_type=Document.STORAGE_TYPE_GPG, ) + self.comment = Comment.objects.create( + comment="This is a comment. amaze.", + document=self.d1, + user=self.user, + ) + self.t1 = Tag.objects.create(name="t") self.dt1 = DocumentType.objects.create(name="dt") self.c1 = Correspondent.objects.create(name="c") @@ -110,7 +120,7 @@ class TestExportImport(DirectoriesMixin, TestCase): manifest = self._do_export(use_filename_format=use_filename_format) - self.assertEqual(len(manifest), 8) + self.assertEqual(len(manifest), 10) self.assertEqual( len(list(filter(lambda e: e["model"] == "documents.document", manifest))), 4, @@ -171,6 +181,11 @@ class TestExportImport(DirectoriesMixin, TestCase): checksum = hashlib.md5(f.read()).hexdigest() self.assertEqual(checksum, element["fields"]["archive_checksum"]) + elif element["model"] == "documents.comment": + self.assertEqual(element["fields"]["comment"], self.comment.comment) + self.assertEqual(element["fields"]["document"], self.d1.id) + self.assertEqual(element["fields"]["user"], self.user.id) + with paperless_environment() as dirs: self.assertEqual(Document.objects.count(), 4) Document.objects.all().delete() From eb3c9fee3fbfa35e65aa54cef5479874e064a360 Mon Sep 17 00:00:00 2001 From: Trenton Holmes Date: Wed, 24 Aug 2022 06:43:52 -0700 Subject: [PATCH 17/20] Merges the migration branches --- .../migrations/1024_merge_20220824_1341.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/documents/migrations/1024_merge_20220824_1341.py diff --git a/src/documents/migrations/1024_merge_20220824_1341.py b/src/documents/migrations/1024_merge_20220824_1341.py new file mode 100644 index 000000000..4a2b9706a --- /dev/null +++ b/src/documents/migrations/1024_merge_20220824_1341.py @@ -0,0 +1,13 @@ +# Generated by Django 4.0.6 on 2022-08-24 13:41 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("documents", "1023_add_comments"), + ("documents", "1023_document_original_filename"), + ] + + operations = [] From 44c607042e661c0751b8278f491c17291d59246c Mon Sep 17 00:00:00 2001 From: Trenton Holmes Date: Wed, 24 Aug 2022 07:26:39 -0700 Subject: [PATCH 18/20] Starts on implementing tests for the new API --- src/documents/tests/test_api.py | 127 ++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) diff --git a/src/documents/tests/test_api.py b/src/documents/tests/test_api.py index 38fe6f07b..774b8dde3 100644 --- a/src/documents/tests/test_api.py +++ b/src/documents/tests/test_api.py @@ -32,6 +32,7 @@ from documents.models import SavedView from documents.models import StoragePath from documents.models import Tag from documents.models import UiSettings +from documents.models import Comment from documents.models import StoragePath from documents.tests.utils import DirectoriesMixin from paperless import version @@ -1354,6 +1355,132 @@ class TestDocumentApi(DirectoriesMixin, APITestCase): 1, ) + def test_get_existing_comments(self): + """ + GIVEN: + - A document with a single comment + WHEN: + - API reuqest for document comments is made + THEN: + - The associated comment is returned + """ + doc = Document.objects.create( + title="test", + mime_type="application/pdf", + content="this is a document which will have comments!", + ) + comment = Comment.objects.create( + comment="This is a comment.", + document=doc, + user=self.user, + ) + + response = self.client.get( + f"/api/documents/{doc.pk}/comments/", + format="json", + ) + + self.assertEqual(response.status_code, 200) + + resp_data = response.json() + + self.assertEqual(len(resp_data), 1) + + resp_data = resp_data[0] + del resp_data["created"] + + self.assertDictEqual( + resp_data, + { + "id": comment.id, + "comment": comment.comment, + "user": { + "id": comment.user.id, + "username": comment.user.username, + "firstname": comment.user.first_name, + "lastname": comment.user.last_name, + }, + }, + ) + + def test_create_comment(self): + """ + GIVEN: + - Existing document + WHEN: + - API request is made to add a comment + THEN: + - Comment is created and associated with document + """ + doc = Document.objects.create( + title="test", + mime_type="application/pdf", + content="this is a document which will have comments added", + ) + resp = self.client.post( + f"/api/documents/{doc.pk}/comments/", + data={"comment": "this is a posted comment"}, + ) + self.assertEqual(resp.status_code, 200) + + response = self.client.get( + f"/api/documents/{doc.pk}/comments/", + format="json", + ) + + self.assertEqual(response.status_code, 200) + + resp_data = response.json() + + self.assertEqual(len(resp_data), 1) + + resp_data = resp_data[0] + + self.assertEqual(resp_data["comment"], "this is a posted comment") + + def test_delete_comment(self): + """ + GIVEN: + - Existing document + WHEN: + - API request is made to add a comment + THEN: + - Comment is created and associated with document + """ + doc = Document.objects.create( + title="test", + mime_type="application/pdf", + content="this is a document which will have comments!", + ) + comment = Comment.objects.create( + comment="This is a comment.", + document=doc, + user=self.user, + ) + + response = self.client.delete( + f"/api/documents/{doc.pk}/comments/", + data={"id": f"{comment.pk}"}, + format="json", + ) + + self.assertEqual(response.status_code, 200) + + def test_get_comments_no_doc(self): + """ + GIVEN: + - A request to get comments from a non-existent document + WHEN: + - API request for document comments is made + THEN: + - HTTP 404 is returned + """ + response = self.client.get( + "/api/documents/500/comments/", + format="json", + ) + self.assertEqual(response.status_code, 404) + class TestDocumentApiV2(DirectoriesMixin, APITestCase): def setUp(self): From 8f4c048d4e721edf804a1347f992c21b213a1f60 Mon Sep 17 00:00:00 2001 From: Trenton Holmes Date: Wed, 24 Aug 2022 08:27:43 -0700 Subject: [PATCH 19/20] Finalizes testing of the delete comment --- src/documents/tests/test_api.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/documents/tests/test_api.py b/src/documents/tests/test_api.py index 774b8dde3..4942751e4 100644 --- a/src/documents/tests/test_api.py +++ b/src/documents/tests/test_api.py @@ -1459,13 +1459,14 @@ class TestDocumentApi(DirectoriesMixin, APITestCase): ) response = self.client.delete( - f"/api/documents/{doc.pk}/comments/", - data={"id": f"{comment.pk}"}, + f"/api/documents/{doc.pk}/comments/?id={comment.pk}", format="json", ) self.assertEqual(response.status_code, 200) + self.assertEqual(len(Comment.objects.all()), 0) + def test_get_comments_no_doc(self): """ GIVEN: From c49e2b677a1464f516bffae1bb98c86a9db3f8b4 Mon Sep 17 00:00:00 2001 From: Michael Shamoon <4887959+shamoon@users.noreply.github.com> Date: Wed, 24 Aug 2022 14:16:47 -0700 Subject: [PATCH 20/20] Fix CodeQL warnings --- src/documents/views.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/documents/views.py b/src/documents/views.py index b9c8a1265..0e380c217 100644 --- a/src/documents/views.py +++ b/src/documents/views.py @@ -409,7 +409,10 @@ class DocumentViewSet( try: return Response(self.getComments(doc)) except Exception as e: - return Response({"error": str(e)}) + logger.warning(f"An error occurred retrieving comments: {str(e)}") + return Response( + {"error": "Error retreiving comments, check logs for more detail."}, + ) elif request.method == "POST": try: c = Comment.objects.create( @@ -421,9 +424,10 @@ class DocumentViewSet( return Response(self.getComments(doc)) except Exception as e: + logger.warning(f"An error occurred saving comment: {str(e)}") return Response( { - "error": str(e), + "error": "Error saving comment, check logs for more detail.", }, ) elif request.method == "DELETE":