mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-11-03 03:16:10 -06:00 
			
		
		
		
	add comment function
This commit is contained in:
		
				
					committed by
					
						
						Michael Shamoon
					
				
			
			
				
	
			
			
			
						parent
						
							d1e8299010
						
					
				
				
					commit
					817882ff6f
				
			@@ -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: [
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,25 @@
 | 
			
		||||
<div *ngIf="comments">
 | 
			
		||||
    <form [formGroup]='commentForm'>
 | 
			
		||||
        <div class="form-group">
 | 
			
		||||
            <textarea class="form-control" id="newcomment" rows="5" formControlName='newcomment'></textarea>
 | 
			
		||||
        </div>
 | 
			
		||||
        <button type="button" class="btn btn-primary" i18n [disabled]="networkActive" (click)="addComment()">add comment</button> 
 | 
			
		||||
    </form>
 | 
			
		||||
    <hr>
 | 
			
		||||
    <div *ngFor="let comment of comments; trackBy: byId" [disableRipple]="true" class="card border-bg-primary bg-primary mb-3 comment-card" [attr.comment-id]="comment.id">
 | 
			
		||||
        <div class="d-flex card-header comment-card-header text-white justify-content-between">
 | 
			
		||||
            <span>{{comment?.user?.firstname}} {{comment?.user?.lastname}} ({{comment?.user?.username}}) - {{ comment?.created | customDate}}</span>
 | 
			
		||||
            <span>
 | 
			
		||||
                <a class="text-white" (click)="deleteComment($event)">
 | 
			
		||||
                    <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16">
 | 
			
		||||
                        <path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/>
 | 
			
		||||
                        <path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4L4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/>
 | 
			
		||||
                    </svg>
 | 
			
		||||
                </a>
 | 
			
		||||
            </span>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="card-body bg-white text-dark comment-card-body card-text">
 | 
			
		||||
            {{comment.comment}}
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
@@ -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);
 | 
			
		||||
} 
 | 
			
		||||
@@ -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<DocumentCommentComponent>;
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ DocumentCommentComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(DocumentCommentComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@@ -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<any> {
 | 
			
		||||
    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;
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -169,6 +169,13 @@
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </ng-template>
 | 
			
		||||
                </li>
 | 
			
		||||
                <li [ngbNavItem]="5" *ngIf="isCommentsEnabled">
 | 
			
		||||
                    <a ngbNavLink i18n>Comments</a>
 | 
			
		||||
                    <ng-template ngbNavContent>
 | 
			
		||||
                        <app-document-comment #commentComponent></app-document-comment>
 | 
			
		||||
                    </ng-template>
 | 
			
		||||
 | 
			
		||||
                </li>
 | 
			
		||||
            </ul>
 | 
			
		||||
 | 
			
		||||
            <div [ngbNavOutlet]="nav" class="mt-2"></div>
 | 
			
		||||
 
 | 
			
		||||
@@ -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<any>
 | 
			
		||||
  isDirty$: Observable<boolean>
 | 
			
		||||
  unsubscribeNotifier: Subject<any> = 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)
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								src-ui/src/app/data/paperless-document-comment.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src-ui/src/app/data/paperless-document-comment.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
} 
 | 
			
		||||
							
								
								
									
										3
									
								
								src-ui/src/app/data/paperless-environment.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src-ui/src/app/data/paperless-environment.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
export interface PaperlessEnvironment {
 | 
			
		||||
    value?: string;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										7
									
								
								src-ui/src/app/data/user-type.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src-ui/src/app/data/user-type.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
import { ObjectWithId } from './object-with-id'
 | 
			
		||||
 | 
			
		||||
export interface CommentUser extends ObjectWithId {
 | 
			
		||||
    username: string
 | 
			
		||||
    firstname: string
 | 
			
		||||
    lastname: string
 | 
			
		||||
} 
 | 
			
		||||
@@ -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();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										31
									
								
								src-ui/src/app/services/rest/document-comment.service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src-ui/src/app/services/rest/document-comment.service.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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<PaperlessDocumentComment> {
 | 
			
		||||
 | 
			
		||||
  constructor(http: HttpClient) {
 | 
			
		||||
    super(http, 'documents')
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  getComments(id: number): Observable<PaperlessDocumentComment> {
 | 
			
		||||
    return this.http.get<PaperlessDocumentComment[]>(this.getResourceUrl(id, "comments"))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  addComment(id: number, comment): Observable<PaperlessDocumentComment[]>{
 | 
			
		||||
    return this.http.post<PaperlessDocumentComment[]>(this.getResourceUrl(id, 'comments'), {"payload": comment})
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  deleteComment(documentId: number, commentId: number): Observable<PaperlessDocumentComment[]>{
 | 
			
		||||
    let httpParams = new HttpParams();
 | 
			
		||||
    httpParams = httpParams.set("commentId", commentId.toString());
 | 
			
		||||
    return this.http.delete<PaperlessDocumentComment[]>(this.getResourceUrl(documentId, 'comments'), {params: httpParams});
 | 
			
		||||
  }
 | 
			
		||||
} 
 | 
			
		||||
							
								
								
									
										16
									
								
								src-ui/src/app/services/rest/environment.service.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src-ui/src/app/services/rest/environment.service.spec.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										22
									
								
								src-ui/src/app/services/rest/environment.service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src-ui/src/app/services/rest/environment.service.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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<PaperlessEnvironment> {
 | 
			
		||||
    let httpParams = new HttpParams();
 | 
			
		||||
    httpParams = httpParams.set('name', environment);
 | 
			
		||||
 | 
			
		||||
    return this.http.get<PaperlessEnvironment>(`${this.baseUrl}environment/`, {params: httpParams})
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -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))
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										19
									
								
								src/documents/migrations/1023_add_comments.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/documents/migrations/1023_add_comments.py
									
									
									
									
									
										Normal file
									
								
							@@ -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())
 | 
			
		||||
            ],
 | 
			
		||||
        )
 | 
			
		||||
    ] 
 | 
			
		||||
@@ -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
 | 
			
		||||
@@ -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)
 | 
			
		||||
        }); 
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user