Merge branch 'dev' into feature-bulk-edit

This commit is contained in:
jonaswinkler
2020-12-18 00:38:31 +01:00
62 changed files with 671 additions and 210 deletions

View File

@@ -69,7 +69,7 @@ class DocumentAdmin(admin.ModelAdmin):
filter_horizontal = ("tags",)
ordering = ["-created", "correspondent"]
ordering = ["-created"]
date_hierarchy = "created"

View File

@@ -2,6 +2,7 @@ import textwrap
from django.conf import settings
from django.core.checks import Error, register
from django.core.exceptions import FieldError
from django.db.utils import OperationalError, ProgrammingError
from documents.signals import document_consumer_declaration
@@ -16,7 +17,7 @@ def changed_password_check(app_configs, **kwargs):
try:
encrypted_doc = Document.objects.filter(
storage_type=Document.STORAGE_TYPE_GPG).first()
except (OperationalError, ProgrammingError):
except (OperationalError, ProgrammingError, FieldError):
return [] # No documents table yet
if encrypted_doc:

View File

@@ -99,6 +99,11 @@ def generate_filename(doc, counter=0):
tags = defaultdictNoStr(lambda: slugify(None),
many_to_dictionary(doc.tags))
tag_list = pathvalidate.sanitize_filename(
",".join([tag.name for tag in doc.tags.all()]),
replacement_text="-"
)
if doc.correspondent:
correspondent = pathvalidate.sanitize_filename(
doc.correspondent.name, replacement_text="-"
@@ -127,7 +132,7 @@ def generate_filename(doc, counter=0):
added_month=f"{doc.added.month:02}" if doc.added else "none",
added_day=f"{doc.added.day:02}" if doc.added else "none",
tags=tags,
tag_list=",".join([tag.name for tag in doc.tags.all()])
tag_list=tag_list
).strip()
path = path.strip(os.sep)

View File

@@ -2,7 +2,6 @@ import os
from django.conf import settings
from django.core.management.base import BaseCommand, CommandError
from termcolor import colored as coloured
from documents.models import Document
from paperless.db import GnuPG
@@ -26,16 +25,14 @@ class Command(BaseCommand):
def handle(self, *args, **options):
try:
print(coloured(
print(
"\n\nWARNING: This script is going to work directly on your "
"document originals, so\nWARNING: you probably shouldn't run "
"this unless you've got a recent backup\nWARNING: handy. It "
"*should* work without a hitch, but be safe and backup your\n"
"WARNING: stuff first.\n\nHit Ctrl+C to exit now, or Enter to "
"continue.\n\n",
"yellow",
attrs=("bold",)
))
"continue.\n\n"
)
__ = input()
except KeyboardInterrupt:
return
@@ -57,8 +54,8 @@ class Command(BaseCommand):
for document in encrypted_files:
print(coloured("Decrypting {}".format(
document).encode('utf-8'), "green"))
print("Decrypting {}".format(
document).encode('utf-8'))
old_paths = [document.source_path, document.thumbnail_path]

View File

@@ -6,13 +6,17 @@ import magic
from django.conf import settings
from django.db import migrations, models
from paperless.db import GnuPG
STORAGE_TYPE_UNENCRYPTED = "unencrypted"
STORAGE_TYPE_GPG = "gpg"
def source_path(self):
if self.filename:
fname = str(self.filename)
else:
fname = "{:07}.{}".format(self.pk, self.file_type)
if self.storage_type == self.STORAGE_TYPE_GPG:
if self.storage_type == STORAGE_TYPE_GPG:
fname += ".gpg"
return os.path.join(
@@ -26,9 +30,18 @@ def add_mime_types(apps, schema_editor):
documents = Document.objects.all()
for d in documents:
d.mime_type = magic.from_file(source_path(d), mime=True)
f = open(source_path(d), "rb")
if d.storage_type == STORAGE_TYPE_GPG:
data = GnuPG.decrypted(f)
else:
data = f.read(1024)
d.mime_type = magic.from_buffer(data, mime=True)
d.save()
f.close()
def add_file_extensions(apps, schema_editor):
Document = apps.get_model("documents", "Document")

View File

@@ -0,0 +1,34 @@
# Generated by Django 3.1.4 on 2020-12-16 17:36
from django.db import migrations
import django.db.models.functions.text
class Migration(migrations.Migration):
dependencies = [
('documents', '1007_savedview_savedviewfilterrule'),
]
operations = [
migrations.AlterModelOptions(
name='correspondent',
options={'ordering': (django.db.models.functions.text.Lower('name'),)},
),
migrations.AlterModelOptions(
name='document',
options={'ordering': ('-created',)},
),
migrations.AlterModelOptions(
name='documenttype',
options={'ordering': (django.db.models.functions.text.Lower('name'),)},
),
migrations.AlterModelOptions(
name='savedview',
options={'ordering': (django.db.models.functions.text.Lower('name'),)},
),
migrations.AlterModelOptions(
name='tag',
options={'ordering': (django.db.models.functions.text.Lower('name'),)},
),
]

View File

@@ -0,0 +1,29 @@
# Generated by Django 3.1.4 on 2020-12-16 20:05
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('documents', '1008_auto_20201216_1736'),
]
operations = [
migrations.AlterModelOptions(
name='correspondent',
options={'ordering': ('name',)},
),
migrations.AlterModelOptions(
name='documenttype',
options={'ordering': ('name',)},
),
migrations.AlterModelOptions(
name='savedview',
options={'ordering': ('name',)},
),
migrations.AlterModelOptions(
name='tag',
options={'ordering': ('name',)},
),
]

View File

@@ -12,7 +12,6 @@ from django.conf import settings
from django.contrib.auth.models import User
from django.db import models
from django.utils import timezone
from django.utils.text import slugify
from documents.file_handling import archive_name_from_filename
from documents.parsers import get_default_file_extension
@@ -205,7 +204,7 @@ class Document(models.Model):
)
class Meta:
ordering = ("correspondent", "title")
ordering = ("-created",)
def __str__(self):
created = datetime.date.isoformat(self.created)
@@ -221,7 +220,7 @@ class Document(models.Model):
else:
fname = "{:07}{}".format(self.pk, self.file_type)
if self.storage_type == self.STORAGE_TYPE_GPG:
fname += ".gpg"
fname += ".gpg" # pragma: no cover
return os.path.join(
settings.ORIGINALS_DIR,
@@ -308,6 +307,10 @@ class Log(models.Model):
class SavedView(models.Model):
class Meta:
ordering = ("name",)
user = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=128)
@@ -340,7 +343,11 @@ class SavedViewFilterRule(models.Model):
(17, "Does not have tag"),
]
saved_view = models.ForeignKey(SavedView, on_delete=models.CASCADE, related_name="filter_rules")
saved_view = models.ForeignKey(
SavedView,
on_delete=models.CASCADE,
related_name="filter_rules"
)
rule_type = models.PositiveIntegerField(choices=RULE_TYPES)

View File

@@ -163,8 +163,6 @@ def parse_date(filename, text):
date = None
next_year = timezone.now().year + 5 # Arbitrary 5 year future limit
# if filename date parsing is enabled, search there first:
if settings.FILENAME_DATE_ORDER:
for m in re.finditer(DATE_REGEX, filename):
@@ -176,7 +174,7 @@ def parse_date(filename, text):
# Skip all matches that do not parse to a proper date
continue
if date is not None and next_year > date.year > 1900:
if date and date.year > 1900 and date <= timezone.now():
return date
# Iterate through all regex matches in text and try to parse the date
@@ -189,7 +187,7 @@ def parse_date(filename, text):
# Skip all matches that do not parse to a proper date
continue
if date is not None and next_year > date.year > 1900:
if date and date.year > 1900 and date <= timezone.now():
break
else:
date = None

View File

@@ -187,17 +187,19 @@ class SavedViewSerializer(serializers.ModelSerializer):
else:
rules_data = None
super(SavedViewSerializer, self).update(instance, validated_data)
if rules_data:
if rules_data is not None:
SavedViewFilterRule.objects.filter(saved_view=instance).delete()
for rule_data in rules_data:
SavedViewFilterRule.objects.create(saved_view=instance, **rule_data)
SavedViewFilterRule.objects.create(
saved_view=instance, **rule_data)
return instance
def create(self, validated_data):
rules_data = validated_data.pop('filter_rules')
saved_view = SavedView.objects.create(**validated_data)
for rule_data in rules_data:
SavedViewFilterRule.objects.create(saved_view=saved_view, **rule_data)
SavedViewFilterRule.objects.create(
saved_view=saved_view, **rule_data)
return saved_view

View File

@@ -8,6 +8,7 @@
<title>PaperlessUi</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="cookie_prefix" content="{{cookie_prefix}}">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="stylesheet" href="{% static 'frontend/styles.css' %}"></head>
<body>

View File

@@ -5,13 +5,11 @@ import tempfile
from unittest import mock
from django.contrib.auth.models import User
from django.test import client
from pathvalidate import ValidationError
from rest_framework.test import APITestCase
from whoosh.writing import AsyncWriter
from documents import index, bulk_edit
from documents.models import Document, Correspondent, DocumentType, Tag
from documents.models import Document, Correspondent, DocumentType, Tag, SavedView
from documents.tests.utils import DirectoriesMixin
@@ -20,8 +18,8 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
def setUp(self):
super(TestDocumentApi, self).setUp()
user = User.objects.create_superuser(username="temp_admin")
self.client.force_login(user=user)
self.user = User.objects.create_superuser(username="temp_admin")
self.client.force_login(user=self.user)
def testDocuments(self):
@@ -172,15 +170,13 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
self.assertEqual(response.status_code, 200)
results = response.data['results']
self.assertEqual(len(results), 2)
self.assertEqual(results[0]['id'], doc2.id)
self.assertEqual(results[1]['id'], doc3.id)
self.assertCountEqual([results[0]['id'], results[1]['id']], [doc2.id, doc3.id])
response = self.client.get("/api/documents/?tags__id__in={},{}".format(tag_inbox.id, tag_3.id))
self.assertEqual(response.status_code, 200)
results = response.data['results']
self.assertEqual(len(results), 2)
self.assertEqual(results[0]['id'], doc1.id)
self.assertEqual(results[1]['id'], doc3.id)
self.assertCountEqual([results[0]['id'], results[1]['id']], [doc1.id, doc3.id])
response = self.client.get("/api/documents/?tags__id__all={},{}".format(tag_2.id, tag_3.id))
self.assertEqual(response.status_code, 200)
@@ -202,8 +198,7 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
self.assertEqual(response.status_code, 200)
results = response.data['results']
self.assertEqual(len(results), 2)
self.assertEqual(results[0]['id'], doc1.id)
self.assertEqual(results[1]['id'], doc2.id)
self.assertCountEqual([results[0]['id'], results[1]['id']], [doc1.id, doc2.id])
response = self.client.get("/api/documents/?tags__id__none={},{}".format(tag_3.id, tag_2.id))
self.assertEqual(response.status_code, 200)
@@ -518,6 +513,90 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
self.assertGreater(len(meta['original_metadata']), 0)
self.assertIsNone(meta['archive_metadata'])
def test_saved_views(self):
u1 = User.objects.create_user("user1")
u2 = User.objects.create_user("user2")
v1 = SavedView.objects.create(user=u1, name="test1", sort_field="", show_on_dashboard=False, show_in_sidebar=False)
v2 = SavedView.objects.create(user=u2, name="test2", sort_field="", show_on_dashboard=False, show_in_sidebar=False)
v3 = SavedView.objects.create(user=u2, name="test3", sort_field="", show_on_dashboard=False, show_in_sidebar=False)
response = self.client.get("/api/saved_views/")
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['count'], 0)
self.assertEqual(self.client.get(f"/api/saved_views/{v1.id}/").status_code, 404)
self.client.force_login(user=u1)
response = self.client.get("/api/saved_views/")
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['count'], 1)
self.assertEqual(self.client.get(f"/api/saved_views/{v1.id}/").status_code, 200)
self.client.force_login(user=u2)
response = self.client.get("/api/saved_views/")
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['count'], 2)
self.assertEqual(self.client.get(f"/api/saved_views/{v1.id}/").status_code, 404)
def test_create_update_patch(self):
u1 = User.objects.create_user("user1")
view = {
"name": "test",
"show_on_dashboard": True,
"show_in_sidebar": True,
"sort_field": "created2",
"filter_rules": [
{
"rule_type": 4,
"value": "test"
}
]
}
response = self.client.post("/api/saved_views/", view, format='json')
self.assertEqual(response.status_code, 201)
v1 = SavedView.objects.get(name="test")
self.assertEqual(v1.sort_field, "created2")
self.assertEqual(v1.filter_rules.count(), 1)
self.assertEqual(v1.user, self.user)
response = self.client.patch(f"/api/saved_views/{v1.id}/", {
"show_in_sidebar": False
}, format='json')
v1 = SavedView.objects.get(id=v1.id)
self.assertEqual(response.status_code, 200)
self.assertFalse(v1.show_in_sidebar)
self.assertEqual(v1.filter_rules.count(), 1)
view['filter_rules'] = [{
"rule_type": 12,
"value": "secret"
}]
response = self.client.put(f"/api/saved_views/{v1.id}/", view, format='json')
self.assertEqual(response.status_code, 200)
v1 = SavedView.objects.get(id=v1.id)
self.assertEqual(v1.filter_rules.count(), 1)
self.assertEqual(v1.filter_rules.first().value, "secret")
view['filter_rules'] = []
response = self.client.put(f"/api/saved_views/{v1.id}/", view, format='json')
self.assertEqual(response.status_code, 200)
v1 = SavedView.objects.get(id=v1.id)
self.assertEqual(v1.filter_rules.count(), 0)
class TestBulkEdit(DirectoriesMixin, APITestCase):

View File

@@ -9,6 +9,7 @@ from unittest import mock
from django.conf import settings
from django.db import DatabaseError
from django.test import TestCase, override_settings
from django.utils import timezone
from .utils import DirectoriesMixin
from ..file_handling import generate_filename, create_source_path_directory, delete_empty_directories, \
@@ -298,23 +299,23 @@ class TestFileHandling(DirectoriesMixin, TestCase):
@override_settings(PAPERLESS_FILENAME_FORMAT="{created_year}-{created_month}-{created_day}")
def test_created_year_month_day(self):
d1 = datetime.datetime(2020, 3, 6, 1, 1, 1)
d1 = timezone.make_aware(datetime.datetime(2020, 3, 6, 1, 1, 1))
doc1 = Document.objects.create(title="doc1", mime_type="application/pdf", created=d1)
self.assertEqual(generate_filename(doc1), "2020-03-06.pdf")
doc1.created = datetime.datetime(2020, 11, 16, 1, 1, 1)
doc1.created = timezone.make_aware(datetime.datetime(2020, 11, 16, 1, 1, 1))
self.assertEqual(generate_filename(doc1), "2020-11-16.pdf")
@override_settings(PAPERLESS_FILENAME_FORMAT="{added_year}-{added_month}-{added_day}")
def test_added_year_month_day(self):
d1 = datetime.datetime(232, 1, 9, 1, 1, 1)
d1 = timezone.make_aware(datetime.datetime(232, 1, 9, 1, 1, 1))
doc1 = Document.objects.create(title="doc1", mime_type="application/pdf", added=d1)
self.assertEqual(generate_filename(doc1), "232-01-09.pdf")
doc1.added = datetime.datetime(2020, 11, 16, 1, 1, 1)
doc1.added = timezone.make_aware(datetime.datetime(2020, 11, 16, 1, 1, 1))
self.assertEqual(generate_filename(doc1), "2020-11-16.pdf")
@@ -599,7 +600,7 @@ class TestFilenameGeneration(TestCase):
PAPERLESS_FILENAME_FORMAT="{created}"
)
def test_date(self):
doc = Document.objects.create(title="does not matter", created=datetime.datetime(2020,5,21, 7,36,51, 153), mime_type="application/pdf", pk=2, checksum="2")
doc = Document.objects.create(title="does not matter", created=timezone.make_aware(datetime.datetime(2020,5,21, 7,36,51, 153)), mime_type="application/pdf", pk=2, checksum="2")
self.assertEqual(generate_filename(doc), "2020-05-21.pdf")

View File

@@ -1,6 +1,9 @@
from django.test import TestCase
from documents import index
from documents.index import JsonFormatter
from documents.models import Document
from documents.tests.utils import DirectoriesMixin
class JsonFormatterTest(TestCase):
@@ -12,3 +15,21 @@ class JsonFormatterTest(TestCase):
self.assertListEqual(self.formatter.format([]), [])
class TestAutoComplete(DirectoriesMixin, TestCase):
def test_auto_complete(self):
doc1 = Document.objects.create(title="doc1", checksum="A", content="test test2 test3")
doc2 = Document.objects.create(title="doc2", checksum="B", content="test test2")
doc3 = Document.objects.create(title="doc3", checksum="C", content="test2")
index.add_or_update_document(doc1)
index.add_or_update_document(doc2)
index.add_or_update_document(doc3)
ix = index.open_index()
self.assertListEqual(index.autocomplete(ix, "tes"), [b"test3", b"test", b"test2"])
self.assertListEqual(index.autocomplete(ix, "tes", limit=3), [b"test3", b"test", b"test2"])
self.assertListEqual(index.autocomplete(ix, "tes", limit=1), [b"test3"])
self.assertListEqual(index.autocomplete(ix, "tes", limit=0), [])

View File

@@ -2,6 +2,8 @@ import os
import shutil
from pathlib import Path
import filelock
from django.conf import settings
from django.test import TestCase
from documents.models import Document
@@ -13,9 +15,11 @@ class TestSanityCheck(DirectoriesMixin, TestCase):
def make_test_data(self):
shutil.copy(os.path.join(os.path.dirname(__file__), "samples", "documents", "originals", "0000001.pdf"), os.path.join(self.dirs.originals_dir, "0000001.pdf"))
shutil.copy(os.path.join(os.path.dirname(__file__), "samples", "documents", "archive", "0000001.pdf"), os.path.join(self.dirs.archive_dir, "0000001.pdf"))
shutil.copy(os.path.join(os.path.dirname(__file__), "samples", "documents", "thumbnails", "0000001.png"), os.path.join(self.dirs.thumbnail_dir, "0000001.png"))
with filelock.FileLock(settings.MEDIA_LOCK):
# just make sure that the lockfile is present.
shutil.copy(os.path.join(os.path.dirname(__file__), "samples", "documents", "originals", "0000001.pdf"), os.path.join(self.dirs.originals_dir, "0000001.pdf"))
shutil.copy(os.path.join(os.path.dirname(__file__), "samples", "documents", "archive", "0000001.pdf"), os.path.join(self.dirs.archive_dir, "0000001.pdf"))
shutil.copy(os.path.join(os.path.dirname(__file__), "samples", "documents", "thumbnails", "0000001.png"), os.path.join(self.dirs.thumbnail_dir, "0000001.png"))
return Document.objects.create(title="test", checksum="42995833e01aea9b3edee44bbfdd7ce1", archive_checksum="62acb0bcbfbcaa62ca6ad3668e4e404b", content="test", pk=1, filename="0000001.pdf", mime_type="application/pdf")

View File

@@ -34,7 +34,8 @@ def setup_directories():
ARCHIVE_DIR=dirs.archive_dir,
CONSUMPTION_DIR=dirs.consumption_dir,
INDEX_DIR=dirs.index_dir,
MODEL_FILE=os.path.join(dirs.data_dir, "classification_model.pickle")
MODEL_FILE=os.path.join(dirs.data_dir, "classification_model.pickle"),
MEDIA_LOCK=os.path.join(dirs.media_dir, "media.lock")
)
dirs.settings_override.enable()

View File

@@ -55,6 +55,11 @@ from .serialisers import (
class IndexView(TemplateView):
template_name = "index.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['cookie_prefix'] = settings.COOKIE_PREFIX
return context
class CorrespondentViewSet(ModelViewSet):
model = Correspondent
@@ -185,7 +190,12 @@ class DocumentViewSet(RetrieveModelMixin,
parser_class = get_parser_class_for_mime_type(mime_type)
if parser_class:
parser = parser_class(logging_group=None)
return parser.extract_metadata(file, mime_type)
try:
return parser.extract_metadata(file, mime_type)
except Exception as e:
# TODO: cover GPG errors, remove later.
return []
else:
return []
@@ -231,7 +241,12 @@ class DocumentViewSet(RetrieveModelMixin,
@cache_control(public=False, max_age=315360000)
def thumb(self, request, pk=None):
try:
return HttpResponse(Document.objects.get(id=pk).thumbnail_file,
doc = Document.objects.get(id=pk)
if doc.storage_type == Document.STORAGE_TYPE_GPG:
handle = GnuPG.decrypted(doc.thumbnail_file)
else:
handle = doc.thumbnail_file
return HttpResponse(handle,
content_type='image/png')
except (FileNotFoundError, Document.DoesNotExist):
raise Http404()

View File

@@ -1 +1 @@
__version__ = (0, 9, 6)
__version__ = (0, 9, 8)

View File

@@ -26,7 +26,7 @@ class BaseMailAction:
return {}
def post_consume(self, M, message_uids, parameter):
pass
pass # pragma: nocover
class DeleteMailAction(BaseMailAction):
@@ -69,7 +69,7 @@ def get_rule_action(rule):
elif rule.action == MailRule.ACTION_MARK_READ:
return MarkReadMailAction()
else:
raise ValueError("Unknown action.")
raise NotImplementedError("Unknown action.") # pragma: nocover
def make_criterias(rule):
@@ -95,7 +95,7 @@ def get_mailbox(server, port, security):
elif security == MailAccount.IMAP_SECURITY_SSL:
mailbox = MailBox(server, port)
else:
raise ValueError("Unknown IMAP security")
raise NotImplementedError("Unknown IMAP security") # pragma: nocover
return mailbox
@@ -119,7 +119,7 @@ class MailAccountHandler(LoggingMixin):
return os.path.splitext(os.path.basename(att.filename))[0]
else:
raise ValueError("Unknown title selector.")
raise NotImplementedError("Unknown title selector.") # pragma: nocover # NOQA: E501
def get_correspondent(self, message, rule):
c_from = rule.assign_correspondent_from
@@ -141,7 +141,7 @@ class MailAccountHandler(LoggingMixin):
return rule.assign_correspondent
else:
raise ValueError("Unknwown correspondent selector")
raise NotImplementedError("Unknwown correspondent selector") # pragma: nocover # NOQA: E501
def handle_mail_account(self, account):

View File

@@ -399,7 +399,7 @@ class TestMail(TestCase):
c = Correspondent.objects.get(name="amazon@amazon.de")
# should work
self.assertEquals(kwargs['override_correspondent_id'], c.id)
self.assertEqual(kwargs['override_correspondent_id'], c.id)
self.async_task.reset_mock()
self.reset_bogus_mailbox()
@@ -411,7 +411,7 @@ class TestMail(TestCase):
args, kwargs = self.async_task.call_args
self.async_task.assert_called_once()
self.assertEquals(kwargs['override_correspondent_id'], None)
self.assertEqual(kwargs['override_correspondent_id'], None)
def test_filters(self):

View File

@@ -1,6 +1,7 @@
import os
import subprocess
from PIL import ImageDraw, ImageFont, Image
from django.conf import settings
from documents.parsers import DocumentParser, ParseError
@@ -12,63 +13,22 @@ class TextDocumentParser(DocumentParser):
"""
def get_thumbnail(self, document_path, mime_type):
"""
The thumbnail of a text file is just a 500px wide image of the text
rendered onto a letter-sized page.
"""
# The below is heavily cribbed from https://askubuntu.com/a/590951
bg_color = "white" # bg color
text_color = "black" # text color
psize = [500, 647] # icon size
n_lines = 50 # number of lines to show
out_path = os.path.join(self.tempdir, "convert.png")
temp_bg = os.path.join(self.tempdir, "bg.png")
temp_txlayer = os.path.join(self.tempdir, "tx.png")
picsize = "x".join([str(n) for n in psize])
txsize = "x".join([str(n - 8) for n in psize])
def create_bg():
work_size = ",".join([str(n - 1) for n in psize])
r = str(round(psize[0] / 10))
rounded = ",".join([r, r])
run_command(
settings.CONVERT_BINARY,
"-size ", picsize,
' xc:none -draw ',
'"fill ', bg_color, ' roundrectangle 0,0,', work_size, ",", rounded, '" ', # NOQA: E501
temp_bg
)
def read_text():
with open(document_path, 'r') as src:
lines = [line.strip() for line in src.readlines()]
text = "\n".join([line for line in lines[:n_lines]])
return text.replace('"', "'")
text = "\n".join(lines[:50])
return text
def create_txlayer():
run_command(
settings.CONVERT_BINARY,
"-background none",
"-fill",
text_color,
"-pointsize", "12",
"-border 4 -bordercolor none",
"-size ", txsize,
' caption:"', read_text(), '" ',
temp_txlayer
)
img = Image.new("RGB", (500, 700), color="white")
draw = ImageDraw.Draw(img)
font = ImageFont.truetype(
"/usr/share/fonts/liberation/LiberationSerif-Regular.ttf", 20,
layout_engine=ImageFont.LAYOUT_BASIC)
draw.text((5, 5), read_text(), font=font, fill="black")
create_txlayer()
create_bg()
run_command(
settings.CONVERT_BINARY,
temp_bg,
temp_txlayer,
"-background None -layers merge ",
out_path
)
out_path = os.path.join(self.tempdir, "thumb.png")
img.save(out_path)
return out_path

View File

@@ -0,0 +1 @@
This is a test file.

View File

@@ -0,0 +1,26 @@
import os
from django.test import TestCase
from documents.tests.utils import DirectoriesMixin
from paperless_text.parsers import TextDocumentParser
class TestTextParser(DirectoriesMixin, TestCase):
def test_thumbnail(self):
parser = TextDocumentParser(None)
# just make sure that it does not crash
f = parser.get_thumbnail(os.path.join(os.path.dirname(__file__), "samples", "test.txt"), "text/plain")
self.assertTrue(os.path.isfile(f))
def test_parse(self):
parser = TextDocumentParser(None)
parser.parse(os.path.join(os.path.dirname(__file__), "samples", "test.txt"), "text/plain")
self.assertEqual(parser.get_text(), "This is a test file.\n")
self.assertIsNone(parser.get_archive_path())