Merge pull request #1375 from tim-vogel/add_comments

Feature: document comments
This commit is contained in:
shamoon
2022-08-25 11:48:31 -07:00
committed by GitHub
23 changed files with 686 additions and 34 deletions

View File

@@ -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,10 @@ 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))

View File

@@ -0,0 +1,28 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("documents", "1022_paperlesstask"),
]
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()),
],
)
]

View File

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

View File

@@ -537,3 +537,43 @@ 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

View File

@@ -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
@@ -1357,6 +1358,133 @@ 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/?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:
- 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):

View File

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

View File

@@ -64,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
@@ -387,6 +388,67 @@ 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):
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:
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(
document=doc,
comment=request.data["comment"],
user=currentUser,
)
c.save()
return Response(self.getComments(doc))
except Exception as e:
logger.warning(f"An error occurred saving comment: {str(e)}")
return Response(
{
"error": "Error saving comment, check logs for more detail.",
},
)
elif request.method == "DELETE":
comment = Comment.objects.get(id=int(request.GET.get("id")))
comment.delete()
return Response(self.getComments(doc))
return Response(
{
"error": "error",
},
)
class SearchResultSerializer(DocumentSerializer):
def to_representation(self, instance):