mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-17 10:13:56 -05:00
Merge branch 'dev'
This commit is contained in:
commit
729f25c435
@ -165,6 +165,7 @@ COPY [ \
|
|||||||
"docker/docker-prepare.sh", \
|
"docker/docker-prepare.sh", \
|
||||||
"docker/paperless_cmd.sh", \
|
"docker/paperless_cmd.sh", \
|
||||||
"docker/wait-for-redis.py", \
|
"docker/wait-for-redis.py", \
|
||||||
|
"docker/env-from-file.sh", \
|
||||||
"docker/management_script.sh", \
|
"docker/management_script.sh", \
|
||||||
"docker/flower-conditional.sh", \
|
"docker/flower-conditional.sh", \
|
||||||
"docker/install_management_commands.sh", \
|
"docker/install_management_commands.sh", \
|
||||||
@ -184,6 +185,8 @@ RUN set -eux \
|
|||||||
&& chmod 755 /sbin/docker-prepare.sh \
|
&& chmod 755 /sbin/docker-prepare.sh \
|
||||||
&& mv wait-for-redis.py /sbin/wait-for-redis.py \
|
&& mv wait-for-redis.py /sbin/wait-for-redis.py \
|
||||||
&& chmod 755 /sbin/wait-for-redis.py \
|
&& chmod 755 /sbin/wait-for-redis.py \
|
||||||
|
&& mv env-from-file.sh /sbin/env-from-file.sh \
|
||||||
|
&& chmod 755 /sbin/env-from-file.sh \
|
||||||
&& mv paperless_cmd.sh /usr/local/bin/paperless_cmd.sh \
|
&& mv paperless_cmd.sh /usr/local/bin/paperless_cmd.sh \
|
||||||
&& chmod 755 /usr/local/bin/paperless_cmd.sh \
|
&& chmod 755 /usr/local/bin/paperless_cmd.sh \
|
||||||
&& mv flower-conditional.sh /usr/local/bin/flower-conditional.sh \
|
&& mv flower-conditional.sh /usr/local/bin/flower-conditional.sh \
|
||||||
|
@ -2,37 +2,6 @@
|
|||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# Adapted from:
|
|
||||||
# https://github.com/docker-library/postgres/blob/master/docker-entrypoint.sh
|
|
||||||
# usage: file_env VAR
|
|
||||||
# ie: file_env 'XYZ_DB_PASSWORD' will allow for "$XYZ_DB_PASSWORD_FILE" to
|
|
||||||
# fill in the value of "$XYZ_DB_PASSWORD" from a file, especially for Docker's
|
|
||||||
# secrets feature
|
|
||||||
file_env() {
|
|
||||||
local -r var="$1"
|
|
||||||
local -r fileVar="${var}_FILE"
|
|
||||||
|
|
||||||
# Basic validation
|
|
||||||
if [ "${!var:-}" ] && [ "${!fileVar:-}" ]; then
|
|
||||||
echo >&2 "error: both $var and $fileVar are set (but are exclusive)"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Only export var if the _FILE exists
|
|
||||||
if [ "${!fileVar:-}" ]; then
|
|
||||||
# And the file exists
|
|
||||||
if [[ -f ${!fileVar} ]]; then
|
|
||||||
echo "Setting ${var} from file"
|
|
||||||
val="$(< "${!fileVar}")"
|
|
||||||
export "$var"="$val"
|
|
||||||
else
|
|
||||||
echo "File ${!fileVar} doesn't exist"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
# Source: https://github.com/sameersbn/docker-gitlab/
|
# Source: https://github.com/sameersbn/docker-gitlab/
|
||||||
map_uidgid() {
|
map_uidgid() {
|
||||||
local -r usermap_original_uid=$(id -u paperless)
|
local -r usermap_original_uid=$(id -u paperless)
|
||||||
@ -96,19 +65,11 @@ custom_container_init() {
|
|||||||
initialize() {
|
initialize() {
|
||||||
|
|
||||||
# Setup environment from secrets before anything else
|
# Setup environment from secrets before anything else
|
||||||
for env_var in \
|
|
||||||
PAPERLESS_DBUSER \
|
|
||||||
PAPERLESS_DBPASS \
|
|
||||||
PAPERLESS_SECRET_KEY \
|
|
||||||
PAPERLESS_AUTO_LOGIN_USERNAME \
|
|
||||||
PAPERLESS_ADMIN_USER \
|
|
||||||
PAPERLESS_ADMIN_MAIL \
|
|
||||||
PAPERLESS_ADMIN_PASSWORD \
|
|
||||||
PAPERLESS_REDIS; do
|
|
||||||
# Check for a version of this var with _FILE appended
|
# Check for a version of this var with _FILE appended
|
||||||
# and convert the contents to the env var value
|
# and convert the contents to the env var value
|
||||||
file_env ${env_var}
|
# Source it so export is persistent
|
||||||
done
|
# shellcheck disable=SC1091
|
||||||
|
source /sbin/env-from-file.sh
|
||||||
|
|
||||||
# Change the user and group IDs if needed
|
# Change the user and group IDs if needed
|
||||||
map_uidgid
|
map_uidgid
|
||||||
|
39
docker/env-from-file.sh
Normal file
39
docker/env-from-file.sh
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Scans the environment variables for those with the suffix _FILE
|
||||||
|
# When located, checks the file exists, and exports the contents
|
||||||
|
# of the file as the same name, minus the suffix
|
||||||
|
# This allows the use of Docker secrets or mounted files
|
||||||
|
# to fill in any of the settings configurable via environment
|
||||||
|
# variables
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
for line in $(printenv)
|
||||||
|
do
|
||||||
|
# Extract the name of the environment variable
|
||||||
|
env_name=${line%%=*}
|
||||||
|
# Check if it ends in "_FILE"
|
||||||
|
if [[ ${env_name} == *_FILE ]]; then
|
||||||
|
# Extract the value of the environment
|
||||||
|
env_value=${line#*=}
|
||||||
|
|
||||||
|
# Check the file exists
|
||||||
|
if [[ -f ${env_value} ]]; then
|
||||||
|
|
||||||
|
# Trim off the _FILE suffix
|
||||||
|
non_file_env_name=${env_name%"_FILE"}
|
||||||
|
echo "Setting ${non_file_env_name} from file"
|
||||||
|
|
||||||
|
# Reads the value from th file
|
||||||
|
val="$(< "${!env_name}")"
|
||||||
|
|
||||||
|
# Sets the normal name to the read file contents
|
||||||
|
export "${non_file_env_name}"="${val}"
|
||||||
|
|
||||||
|
else
|
||||||
|
echo "File ${env_value} doesn't exist"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
@ -3,6 +3,9 @@
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
cd /usr/src/paperless/src/
|
cd /usr/src/paperless/src/
|
||||||
|
# This ensures environment is setup
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
source /sbin/env-from-file.sh
|
||||||
|
|
||||||
if [[ $(id -u) == 0 ]] ;
|
if [[ $(id -u) == 0 ]] ;
|
||||||
then
|
then
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
[editing]="true"
|
[editing]="true"
|
||||||
[multiple]="true"
|
[multiple]="true"
|
||||||
[applyOnClose]="applyOnClose"
|
[applyOnClose]="applyOnClose"
|
||||||
(open)="openTagsDropdown()"
|
(opened)="openTagsDropdown()"
|
||||||
[(selectionModel)]="tagSelectionModel"
|
[(selectionModel)]="tagSelectionModel"
|
||||||
(apply)="setTags($event)">
|
(apply)="setTags($event)">
|
||||||
</app-filterable-dropdown>
|
</app-filterable-dropdown>
|
||||||
@ -40,7 +40,7 @@
|
|||||||
[items]="correspondents"
|
[items]="correspondents"
|
||||||
[editing]="true"
|
[editing]="true"
|
||||||
[applyOnClose]="applyOnClose"
|
[applyOnClose]="applyOnClose"
|
||||||
(open)="openCorrespondentDropdown()"
|
(opened)="openCorrespondentDropdown()"
|
||||||
[(selectionModel)]="correspondentSelectionModel"
|
[(selectionModel)]="correspondentSelectionModel"
|
||||||
(apply)="setCorrespondents($event)">
|
(apply)="setCorrespondents($event)">
|
||||||
</app-filterable-dropdown>
|
</app-filterable-dropdown>
|
||||||
@ -49,7 +49,7 @@
|
|||||||
[items]="documentTypes"
|
[items]="documentTypes"
|
||||||
[editing]="true"
|
[editing]="true"
|
||||||
[applyOnClose]="applyOnClose"
|
[applyOnClose]="applyOnClose"
|
||||||
(open)="openDocumentTypeDropdown()"
|
(opened)="openDocumentTypeDropdown()"
|
||||||
[(selectionModel)]="documentTypeSelectionModel"
|
[(selectionModel)]="documentTypeSelectionModel"
|
||||||
(apply)="setDocumentTypes($event)">
|
(apply)="setDocumentTypes($event)">
|
||||||
</app-filterable-dropdown>
|
</app-filterable-dropdown>
|
||||||
@ -58,7 +58,7 @@
|
|||||||
[items]="storagePaths"
|
[items]="storagePaths"
|
||||||
[editing]="true"
|
[editing]="true"
|
||||||
[applyOnClose]="applyOnClose"
|
[applyOnClose]="applyOnClose"
|
||||||
(open)="openStoragePathDropdown()"
|
(opened)="openStoragePathDropdown()"
|
||||||
[(selectionModel)]="storagePathsSelectionModel"
|
[(selectionModel)]="storagePathsSelectionModel"
|
||||||
(apply)="setStoragePaths($event)">
|
(apply)="setStoragePaths($event)">
|
||||||
</app-filterable-dropdown>
|
</app-filterable-dropdown>
|
||||||
|
@ -30,27 +30,27 @@
|
|||||||
[(selectionModel)]="tagSelectionModel"
|
[(selectionModel)]="tagSelectionModel"
|
||||||
(selectionModelChange)="updateRules()"
|
(selectionModelChange)="updateRules()"
|
||||||
[multiple]="true"
|
[multiple]="true"
|
||||||
(open)="onTagsDropdownOpen()"
|
(opened)="onTagsDropdownOpen()"
|
||||||
[allowSelectNone]="true"></app-filterable-dropdown>
|
[allowSelectNone]="true"></app-filterable-dropdown>
|
||||||
<app-filterable-dropdown class="flex-fill" title="Correspondent" icon="person-fill" i18n-title
|
<app-filterable-dropdown class="flex-fill" title="Correspondent" icon="person-fill" i18n-title
|
||||||
filterPlaceholder="Filter correspondents" i18n-filterPlaceholder
|
filterPlaceholder="Filter correspondents" i18n-filterPlaceholder
|
||||||
[items]="correspondents"
|
[items]="correspondents"
|
||||||
[(selectionModel)]="correspondentSelectionModel"
|
[(selectionModel)]="correspondentSelectionModel"
|
||||||
(selectionModelChange)="updateRules()"
|
(selectionModelChange)="updateRules()"
|
||||||
(open)="onCorrespondentDropdownOpen()"
|
(opened)="onCorrespondentDropdownOpen()"
|
||||||
[allowSelectNone]="true"></app-filterable-dropdown>
|
[allowSelectNone]="true"></app-filterable-dropdown>
|
||||||
<app-filterable-dropdown class="flex-fill" title="Document type" icon="file-earmark-fill" i18n-title
|
<app-filterable-dropdown class="flex-fill" title="Document type" icon="file-earmark-fill" i18n-title
|
||||||
filterPlaceholder="Filter document types" i18n-filterPlaceholder
|
filterPlaceholder="Filter document types" i18n-filterPlaceholder
|
||||||
[items]="documentTypes"
|
[items]="documentTypes"
|
||||||
[(selectionModel)]="documentTypeSelectionModel"
|
[(selectionModel)]="documentTypeSelectionModel"
|
||||||
(open)="onDocumentTypeDropdownOpen()"
|
(opened)="onDocumentTypeDropdownOpen()"
|
||||||
(selectionModelChange)="updateRules()"
|
(selectionModelChange)="updateRules()"
|
||||||
[allowSelectNone]="true"></app-filterable-dropdown>
|
[allowSelectNone]="true"></app-filterable-dropdown>
|
||||||
<app-filterable-dropdown class="me-2 flex-fill" title="Storage path" icon="folder-fill" i18n-title
|
<app-filterable-dropdown class="me-2 flex-fill" title="Storage path" icon="folder-fill" i18n-title
|
||||||
filterPlaceholder="Filter storage paths" i18n-filterPlaceholder
|
filterPlaceholder="Filter storage paths" i18n-filterPlaceholder
|
||||||
[items]="storagePaths"
|
[items]="storagePaths"
|
||||||
[(selectionModel)]="storagePathSelectionModel"
|
[(selectionModel)]="storagePathSelectionModel"
|
||||||
(open)="onStoragePathDropdownOpen()"
|
(opened)="onStoragePathDropdownOpen()"
|
||||||
(selectionModelChange)="updateRules()"
|
(selectionModelChange)="updateRules()"
|
||||||
[allowSelectNone]="true"></app-filterable-dropdown>
|
[allowSelectNone]="true"></app-filterable-dropdown>
|
||||||
</div>
|
</div>
|
||||||
|
@ -5,7 +5,7 @@ export const environment = {
|
|||||||
apiBaseUrl: document.baseURI + 'api/',
|
apiBaseUrl: document.baseURI + 'api/',
|
||||||
apiVersion: '2',
|
apiVersion: '2',
|
||||||
appTitle: 'Paperless-ngx',
|
appTitle: 'Paperless-ngx',
|
||||||
version: '1.11.2',
|
version: '1.11.2-dev',
|
||||||
webSocketHost: window.location.host,
|
webSocketHost: window.location.host,
|
||||||
webSocketProtocol: window.location.protocol == 'https:' ? 'wss:' : 'ws:',
|
webSocketProtocol: window.location.protocol == 'https:' ? 'wss:' : 'ws:',
|
||||||
webSocketBaseUrl: base_url.pathname + 'ws/',
|
webSocketBaseUrl: base_url.pathname + 'ws/',
|
||||||
|
@ -793,6 +793,8 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
|
|||||||
@mock.patch("documents.views.consume_file.delay")
|
@mock.patch("documents.views.consume_file.delay")
|
||||||
def test_upload(self, m):
|
def test_upload(self, m):
|
||||||
|
|
||||||
|
m.return_value = celery.result.AsyncResult(id=str(uuid.uuid4()))
|
||||||
|
|
||||||
with open(
|
with open(
|
||||||
os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"),
|
os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"),
|
||||||
"rb",
|
"rb",
|
||||||
@ -816,6 +818,8 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
|
|||||||
@mock.patch("documents.views.consume_file.delay")
|
@mock.patch("documents.views.consume_file.delay")
|
||||||
def test_upload_empty_metadata(self, m):
|
def test_upload_empty_metadata(self, m):
|
||||||
|
|
||||||
|
m.return_value = celery.result.AsyncResult(id=str(uuid.uuid4()))
|
||||||
|
|
||||||
with open(
|
with open(
|
||||||
os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"),
|
os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"),
|
||||||
"rb",
|
"rb",
|
||||||
@ -839,6 +843,8 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
|
|||||||
@mock.patch("documents.views.consume_file.delay")
|
@mock.patch("documents.views.consume_file.delay")
|
||||||
def test_upload_invalid_form(self, m):
|
def test_upload_invalid_form(self, m):
|
||||||
|
|
||||||
|
m.return_value = celery.result.AsyncResult(id=str(uuid.uuid4()))
|
||||||
|
|
||||||
with open(
|
with open(
|
||||||
os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"),
|
os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"),
|
||||||
"rb",
|
"rb",
|
||||||
@ -853,6 +859,8 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
|
|||||||
@mock.patch("documents.views.consume_file.delay")
|
@mock.patch("documents.views.consume_file.delay")
|
||||||
def test_upload_invalid_file(self, m):
|
def test_upload_invalid_file(self, m):
|
||||||
|
|
||||||
|
m.return_value = celery.result.AsyncResult(id=str(uuid.uuid4()))
|
||||||
|
|
||||||
with open(
|
with open(
|
||||||
os.path.join(os.path.dirname(__file__), "samples", "simple.zip"),
|
os.path.join(os.path.dirname(__file__), "samples", "simple.zip"),
|
||||||
"rb",
|
"rb",
|
||||||
@ -866,6 +874,9 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
|
|||||||
|
|
||||||
@mock.patch("documents.views.consume_file.delay")
|
@mock.patch("documents.views.consume_file.delay")
|
||||||
def test_upload_with_title(self, async_task):
|
def test_upload_with_title(self, async_task):
|
||||||
|
|
||||||
|
async_task.return_value = celery.result.AsyncResult(id=str(uuid.uuid4()))
|
||||||
|
|
||||||
with open(
|
with open(
|
||||||
os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"),
|
os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"),
|
||||||
"rb",
|
"rb",
|
||||||
@ -884,6 +895,9 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
|
|||||||
|
|
||||||
@mock.patch("documents.views.consume_file.delay")
|
@mock.patch("documents.views.consume_file.delay")
|
||||||
def test_upload_with_correspondent(self, async_task):
|
def test_upload_with_correspondent(self, async_task):
|
||||||
|
|
||||||
|
async_task.return_value = celery.result.AsyncResult(id=str(uuid.uuid4()))
|
||||||
|
|
||||||
c = Correspondent.objects.create(name="test-corres")
|
c = Correspondent.objects.create(name="test-corres")
|
||||||
with open(
|
with open(
|
||||||
os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"),
|
os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"),
|
||||||
@ -903,6 +917,9 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
|
|||||||
|
|
||||||
@mock.patch("documents.views.consume_file.delay")
|
@mock.patch("documents.views.consume_file.delay")
|
||||||
def test_upload_with_invalid_correspondent(self, async_task):
|
def test_upload_with_invalid_correspondent(self, async_task):
|
||||||
|
|
||||||
|
async_task.return_value = celery.result.AsyncResult(id=str(uuid.uuid4()))
|
||||||
|
|
||||||
with open(
|
with open(
|
||||||
os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"),
|
os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"),
|
||||||
"rb",
|
"rb",
|
||||||
@ -917,6 +934,9 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
|
|||||||
|
|
||||||
@mock.patch("documents.views.consume_file.delay")
|
@mock.patch("documents.views.consume_file.delay")
|
||||||
def test_upload_with_document_type(self, async_task):
|
def test_upload_with_document_type(self, async_task):
|
||||||
|
|
||||||
|
async_task.return_value = celery.result.AsyncResult(id=str(uuid.uuid4()))
|
||||||
|
|
||||||
dt = DocumentType.objects.create(name="invoice")
|
dt = DocumentType.objects.create(name="invoice")
|
||||||
with open(
|
with open(
|
||||||
os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"),
|
os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"),
|
||||||
@ -936,6 +956,9 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
|
|||||||
|
|
||||||
@mock.patch("documents.views.consume_file.delay")
|
@mock.patch("documents.views.consume_file.delay")
|
||||||
def test_upload_with_invalid_document_type(self, async_task):
|
def test_upload_with_invalid_document_type(self, async_task):
|
||||||
|
|
||||||
|
async_task.return_value = celery.result.AsyncResult(id=str(uuid.uuid4()))
|
||||||
|
|
||||||
with open(
|
with open(
|
||||||
os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"),
|
os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"),
|
||||||
"rb",
|
"rb",
|
||||||
@ -950,6 +973,9 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
|
|||||||
|
|
||||||
@mock.patch("documents.views.consume_file.delay")
|
@mock.patch("documents.views.consume_file.delay")
|
||||||
def test_upload_with_tags(self, async_task):
|
def test_upload_with_tags(self, async_task):
|
||||||
|
|
||||||
|
async_task.return_value = celery.result.AsyncResult(id=str(uuid.uuid4()))
|
||||||
|
|
||||||
t1 = Tag.objects.create(name="tag1")
|
t1 = Tag.objects.create(name="tag1")
|
||||||
t2 = Tag.objects.create(name="tag2")
|
t2 = Tag.objects.create(name="tag2")
|
||||||
with open(
|
with open(
|
||||||
@ -970,6 +996,9 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
|
|||||||
|
|
||||||
@mock.patch("documents.views.consume_file.delay")
|
@mock.patch("documents.views.consume_file.delay")
|
||||||
def test_upload_with_invalid_tags(self, async_task):
|
def test_upload_with_invalid_tags(self, async_task):
|
||||||
|
|
||||||
|
async_task.return_value = celery.result.AsyncResult(id=str(uuid.uuid4()))
|
||||||
|
|
||||||
t1 = Tag.objects.create(name="tag1")
|
t1 = Tag.objects.create(name="tag1")
|
||||||
t2 = Tag.objects.create(name="tag2")
|
t2 = Tag.objects.create(name="tag2")
|
||||||
with open(
|
with open(
|
||||||
@ -986,6 +1015,9 @@ class TestDocumentApi(DirectoriesMixin, APITestCase):
|
|||||||
|
|
||||||
@mock.patch("documents.views.consume_file.delay")
|
@mock.patch("documents.views.consume_file.delay")
|
||||||
def test_upload_with_created(self, async_task):
|
def test_upload_with_created(self, async_task):
|
||||||
|
|
||||||
|
async_task.return_value = celery.result.AsyncResult(id=str(uuid.uuid4()))
|
||||||
|
|
||||||
created = datetime.datetime(
|
created = datetime.datetime(
|
||||||
2022,
|
2022,
|
||||||
5,
|
5,
|
||||||
@ -2948,6 +2980,59 @@ class TestTasks(APITestCase):
|
|||||||
self.assertEqual(returned_task2["status"], celery.states.PENDING)
|
self.assertEqual(returned_task2["status"], celery.states.PENDING)
|
||||||
self.assertEqual(returned_task2["task_file_name"], task2.task_file_name)
|
self.assertEqual(returned_task2["task_file_name"], task2.task_file_name)
|
||||||
|
|
||||||
|
def test_get_single_task_status(self):
|
||||||
|
"""
|
||||||
|
GIVEN
|
||||||
|
- Query parameter for a valid task ID
|
||||||
|
WHEN:
|
||||||
|
- API call is made to get task status
|
||||||
|
THEN:
|
||||||
|
- Single task data is returned
|
||||||
|
"""
|
||||||
|
|
||||||
|
id1 = str(uuid.uuid4())
|
||||||
|
task1 = PaperlessTask.objects.create(
|
||||||
|
task_id=id1,
|
||||||
|
task_file_name="task_one.pdf",
|
||||||
|
)
|
||||||
|
|
||||||
|
_ = PaperlessTask.objects.create(
|
||||||
|
task_id=str(uuid.uuid4()),
|
||||||
|
task_file_name="task_two.pdf",
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self.client.get(self.ENDPOINT + f"?task_id={id1}")
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(len(response.data), 1)
|
||||||
|
returned_task1 = response.data[0]
|
||||||
|
|
||||||
|
self.assertEqual(returned_task1["task_id"], task1.task_id)
|
||||||
|
|
||||||
|
def test_get_single_task_status_not_valid(self):
|
||||||
|
"""
|
||||||
|
GIVEN
|
||||||
|
- Query parameter for a non-existent task ID
|
||||||
|
WHEN:
|
||||||
|
- API call is made to get task status
|
||||||
|
THEN:
|
||||||
|
- No task data is returned
|
||||||
|
"""
|
||||||
|
task1 = PaperlessTask.objects.create(
|
||||||
|
task_id=str(uuid.uuid4()),
|
||||||
|
task_file_name="task_one.pdf",
|
||||||
|
)
|
||||||
|
|
||||||
|
_ = PaperlessTask.objects.create(
|
||||||
|
task_id=str(uuid.uuid4()),
|
||||||
|
task_file_name="task_two.pdf",
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self.client.get(self.ENDPOINT + "?task_id=bad-task-id")
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(len(response.data), 0)
|
||||||
|
|
||||||
def test_acknowledge_tasks(self):
|
def test_acknowledge_tasks(self):
|
||||||
"""
|
"""
|
||||||
GIVEN:
|
GIVEN:
|
||||||
|
@ -617,7 +617,7 @@ class PostDocumentView(GenericAPIView):
|
|||||||
|
|
||||||
task_id = str(uuid.uuid4())
|
task_id = str(uuid.uuid4())
|
||||||
|
|
||||||
consume_file.delay(
|
async_task = consume_file.delay(
|
||||||
temp_filename,
|
temp_filename,
|
||||||
override_filename=doc_name,
|
override_filename=doc_name,
|
||||||
override_title=title,
|
override_title=title,
|
||||||
@ -628,7 +628,7 @@ class PostDocumentView(GenericAPIView):
|
|||||||
override_created=created,
|
override_created=created,
|
||||||
)
|
)
|
||||||
|
|
||||||
return Response("OK")
|
return Response(async_task.id)
|
||||||
|
|
||||||
|
|
||||||
class SelectionDataView(GenericAPIView):
|
class SelectionDataView(GenericAPIView):
|
||||||
@ -886,6 +886,7 @@ class TasksViewSet(ReadOnlyModelViewSet):
|
|||||||
permission_classes = (IsAuthenticated,)
|
permission_classes = (IsAuthenticated,)
|
||||||
serializer_class = TasksViewSerializer
|
serializer_class = TasksViewSerializer
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
queryset = (
|
queryset = (
|
||||||
PaperlessTask.objects.filter(
|
PaperlessTask.objects.filter(
|
||||||
acknowledged=False,
|
acknowledged=False,
|
||||||
@ -893,6 +894,10 @@ class TasksViewSet(ReadOnlyModelViewSet):
|
|||||||
.order_by("date_created")
|
.order_by("date_created")
|
||||||
.reverse()
|
.reverse()
|
||||||
)
|
)
|
||||||
|
task_id = self.request.query_params.get("task_id")
|
||||||
|
if task_id is not None:
|
||||||
|
queryset = PaperlessTask.objects.filter(task_id=task_id)
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
class AcknowledgeTasksView(GenericAPIView):
|
class AcknowledgeTasksView(GenericAPIView):
|
||||||
|
@ -8,6 +8,8 @@ import requests
|
|||||||
from bleach import clean
|
from bleach import clean
|
||||||
from bleach import linkify
|
from bleach import linkify
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.utils.timezone import is_naive
|
||||||
|
from django.utils.timezone import make_aware
|
||||||
from documents.parsers import DocumentParser
|
from documents.parsers import DocumentParser
|
||||||
from documents.parsers import make_thumbnail_from_pdf
|
from documents.parsers import make_thumbnail_from_pdf
|
||||||
from documents.parsers import ParseError
|
from documents.parsers import ParseError
|
||||||
@ -135,7 +137,11 @@ class MailDocumentParser(DocumentParser):
|
|||||||
|
|
||||||
self.text += f"\n\n{strip_text(mail.text)}"
|
self.text += f"\n\n{strip_text(mail.text)}"
|
||||||
|
|
||||||
|
if is_naive(mail.date):
|
||||||
|
self.date = make_aware(mail.date)
|
||||||
|
else:
|
||||||
self.date = mail.date
|
self.date = mail.date
|
||||||
|
|
||||||
self.archive_path = self.generate_pdf(document_path)
|
self.archive_path = self.generate_pdf(document_path)
|
||||||
|
|
||||||
def tika_parse(self, html: str):
|
def tika_parse(self, html: str):
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from documents.parsers import DocumentParser
|
from documents.parsers import DocumentParser
|
||||||
@ -99,7 +101,7 @@ class RasterisedDocumentParser(DocumentParser):
|
|||||||
self.log("warning", f"Error while calculating DPI for image {image}: {e}")
|
self.log("warning", f"Error while calculating DPI for image {image}: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def extract_text(self, sidecar_file, pdf_file):
|
def extract_text(self, sidecar_file: Optional[Path], pdf_file: Path):
|
||||||
# When re-doing OCR, the sidecar contains ONLY the new text, not
|
# When re-doing OCR, the sidecar contains ONLY the new text, not
|
||||||
# the whole text, so do not utilize it in that case
|
# the whole text, so do not utilize it in that case
|
||||||
if (
|
if (
|
||||||
@ -139,11 +141,15 @@ class RasterisedDocumentParser(DocumentParser):
|
|||||||
|
|
||||||
self.log("debug", f"Detected language {lang}")
|
self.log("debug", f"Detected language {lang}")
|
||||||
|
|
||||||
if lang in {
|
if (
|
||||||
|
lang
|
||||||
|
in {
|
||||||
"ar", # Arabic
|
"ar", # Arabic
|
||||||
"he", # Hebrew,
|
"he", # Hebrew,
|
||||||
"fa", # Persian
|
"fa", # Persian
|
||||||
}:
|
}
|
||||||
|
and pdf_file.name != "archive-fallback.pdf"
|
||||||
|
):
|
||||||
raise RtlLanguageException()
|
raise RtlLanguageException()
|
||||||
return stripped
|
return stripped
|
||||||
except RtlLanguageException:
|
except RtlLanguageException:
|
||||||
@ -275,7 +281,7 @@ class RasterisedDocumentParser(DocumentParser):
|
|||||||
|
|
||||||
return ocrmypdf_args
|
return ocrmypdf_args
|
||||||
|
|
||||||
def parse(self, document_path, mime_type, file_name=None):
|
def parse(self, document_path: Path, mime_type, file_name=None):
|
||||||
# This forces tesseract to use one core per page.
|
# This forces tesseract to use one core per page.
|
||||||
os.environ["OMP_THREAD_LIMIT"] = "1"
|
os.environ["OMP_THREAD_LIMIT"] = "1"
|
||||||
|
|
||||||
@ -300,8 +306,8 @@ class RasterisedDocumentParser(DocumentParser):
|
|||||||
import ocrmypdf
|
import ocrmypdf
|
||||||
from ocrmypdf import InputFileError, EncryptedPdfError
|
from ocrmypdf import InputFileError, EncryptedPdfError
|
||||||
|
|
||||||
archive_path = os.path.join(self.tempdir, "archive.pdf")
|
archive_path = Path(os.path.join(self.tempdir, "archive.pdf"))
|
||||||
sidecar_file = os.path.join(self.tempdir, "sidecar.txt")
|
sidecar_file = Path(os.path.join(self.tempdir, "sidecar.txt"))
|
||||||
|
|
||||||
args = self.construct_ocrmypdf_parameters(
|
args = self.construct_ocrmypdf_parameters(
|
||||||
document_path,
|
document_path,
|
||||||
@ -335,8 +341,12 @@ class RasterisedDocumentParser(DocumentParser):
|
|||||||
f"Attempting force OCR to get the text.",
|
f"Attempting force OCR to get the text.",
|
||||||
)
|
)
|
||||||
|
|
||||||
archive_path_fallback = os.path.join(self.tempdir, "archive-fallback.pdf")
|
archive_path_fallback = Path(
|
||||||
sidecar_file_fallback = os.path.join(self.tempdir, "sidecar-fallback.txt")
|
os.path.join(self.tempdir, "archive-fallback.pdf"),
|
||||||
|
)
|
||||||
|
sidecar_file_fallback = Path(
|
||||||
|
os.path.join(self.tempdir, "sidecar-fallback.txt"),
|
||||||
|
)
|
||||||
|
|
||||||
# Attempt to run OCR with safe settings.
|
# Attempt to run OCR with safe settings.
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user