mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-09 09:58:20 -05:00
Merge branch 'dev'
This commit is contained in:
commit
252abb41c3
@ -1095,6 +1095,27 @@ barcode.
|
|||||||
|
|
||||||
Defaults to "ASN"
|
Defaults to "ASN"
|
||||||
|
|
||||||
|
`PAPERLESS_CONSUMER_BARCODE_UPSCALE=<float>`
|
||||||
|
|
||||||
|
: Defines the upscale factor used in barcode detection.
|
||||||
|
Improves the detection of small barcodes, i.e. with a value of 1.5 by
|
||||||
|
upscaling the document beforce the detection process. Upscaling will
|
||||||
|
only take place if value is bigger than 1.0. Otherwise upscaling will
|
||||||
|
not be performed to save resources. Try using in combination with
|
||||||
|
PAPERLESS_CONSUMER_BARCODE_DPI set to a value higher than default.
|
||||||
|
|
||||||
|
Defaults to 0.0
|
||||||
|
|
||||||
|
`PAPERLESS_CONSUMER_BARCODE_DPI=<int>`
|
||||||
|
|
||||||
|
: During barcode detection every page from a PDF document needs
|
||||||
|
to be converted to an image. A dpi value can be specified in the
|
||||||
|
conversion process. Default is 300. If the detection of small barcodes
|
||||||
|
fails a bigger dpi value i.e. 600 can fix the issue. Try using in
|
||||||
|
combination with PAPERLESS_CONSUMER_BARCODE_UPSCALE bigger than 1.0.
|
||||||
|
|
||||||
|
Defaults to "300"
|
||||||
|
|
||||||
## Binaries
|
## Binaries
|
||||||
|
|
||||||
There are a few external software packages that Paperless expects to
|
There are a few external software packages that Paperless expects to
|
||||||
|
@ -66,6 +66,8 @@
|
|||||||
#PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS=false
|
#PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS=false
|
||||||
#PAPERLESS_CONSUMER_ENABLE_BARCODES=false
|
#PAPERLESS_CONSUMER_ENABLE_BARCODES=false
|
||||||
#PAPERLESS_CONSUMER_BARCODE_STRING=PATCHT
|
#PAPERLESS_CONSUMER_BARCODE_STRING=PATCHT
|
||||||
|
#PAPERLESS_CONSUMER_BARCODE_UPSCALE=0.0
|
||||||
|
#PAPERLESS_CONSUMER_BARCODE_DPI=300
|
||||||
#PAPERLESS_PRE_CONSUME_SCRIPT=/path/to/an/arbitrary/script.sh
|
#PAPERLESS_PRE_CONSUME_SCRIPT=/path/to/an/arbitrary/script.sh
|
||||||
#PAPERLESS_POST_CONSUME_SCRIPT=/path/to/an/arbitrary/script.sh
|
#PAPERLESS_POST_CONSUME_SCRIPT=/path/to/an/arbitrary/script.sh
|
||||||
#PAPERLESS_FILENAME_DATE_ORDER=YMD
|
#PAPERLESS_FILENAME_DATE_ORDER=YMD
|
||||||
|
@ -5,7 +5,7 @@ export const environment = {
|
|||||||
apiBaseUrl: document.baseURI + 'api/',
|
apiBaseUrl: document.baseURI + 'api/',
|
||||||
apiVersion: '3',
|
apiVersion: '3',
|
||||||
appTitle: 'Paperless-ngx',
|
appTitle: 'Paperless-ngx',
|
||||||
version: '1.16.4',
|
version: '1.16.4-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/',
|
||||||
|
@ -203,11 +203,21 @@ class BarcodeReader:
|
|||||||
try:
|
try:
|
||||||
pages_from_path = convert_from_path(
|
pages_from_path = convert_from_path(
|
||||||
self.pdf_file,
|
self.pdf_file,
|
||||||
dpi=300,
|
dpi=settings.CONSUMER_BARCODE_DPI,
|
||||||
output_folder=self.temp_dir.name,
|
output_folder=self.temp_dir.name,
|
||||||
)
|
)
|
||||||
|
|
||||||
for current_page_number, page in enumerate(pages_from_path):
|
for current_page_number, page in enumerate(pages_from_path):
|
||||||
|
factor = settings.CONSUMER_BARCODE_UPSCALE
|
||||||
|
if factor > 1.0:
|
||||||
|
logger.debug(
|
||||||
|
f"Upscaling image by {factor} for better barcode detection",
|
||||||
|
)
|
||||||
|
x, y = page.size
|
||||||
|
page = page.resize(
|
||||||
|
(int(round(x * factor)), (int(round(y * factor)))),
|
||||||
|
)
|
||||||
|
|
||||||
for barcode_value in reader(page):
|
for barcode_value in reader(page):
|
||||||
self.barcodes.append(
|
self.barcodes.append(
|
||||||
Barcode(current_page_number, barcode_value),
|
Barcode(current_page_number, barcode_value),
|
||||||
|
@ -213,15 +213,12 @@ class OwnedObjectSerializer(serializers.ModelSerializer, SetPermissionsMixin):
|
|||||||
# other methods in mixin
|
# other methods in mixin
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
if self.user and (
|
# default to current user if not set
|
||||||
"owner" not in validated_data or validated_data["owner"] is None
|
if "owner" not in validated_data and self.user:
|
||||||
):
|
|
||||||
validated_data["owner"] = self.user
|
validated_data["owner"] = self.user
|
||||||
permissions = None
|
permissions = None
|
||||||
if "set_permissions" in validated_data:
|
if "set_permissions" in validated_data:
|
||||||
permissions = validated_data.pop("set_permissions")
|
permissions = validated_data.pop("set_permissions")
|
||||||
if "user" not in permissions or permissions["user"] is None:
|
|
||||||
validated_data["owner"] = None
|
|
||||||
instance = super().create(validated_data)
|
instance = super().create(validated_data)
|
||||||
if permissions is not None:
|
if permissions is not None:
|
||||||
self._set_permissions(permissions, instance)
|
self._set_permissions(permissions, instance)
|
||||||
|
Binary file not shown.
@ -28,6 +28,7 @@ from django.contrib.auth.models import User
|
|||||||
from django.test import override_settings
|
from django.test import override_settings
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from guardian.shortcuts import assign_perm
|
from guardian.shortcuts import assign_perm
|
||||||
|
from guardian.shortcuts import get_perms
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.test import APITestCase
|
from rest_framework.test import APITestCase
|
||||||
from whoosh.writing import AsyncWriter
|
from whoosh.writing import AsyncWriter
|
||||||
@ -3855,7 +3856,7 @@ class TestApiAuth(DirectoriesMixin, APITestCase):
|
|||||||
status.HTTP_200_OK,
|
status.HTTP_200_OK,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_object_permissions(self):
|
def test_api_get_object_permissions(self):
|
||||||
user1 = User.objects.create_user(username="test1")
|
user1 = User.objects.create_user(username="test1")
|
||||||
user2 = User.objects.create_user(username="test2")
|
user2 = User.objects.create_user(username="test2")
|
||||||
user1.user_permissions.add(*Permission.objects.filter(codename="view_document"))
|
user1.user_permissions.add(*Permission.objects.filter(codename="view_document"))
|
||||||
@ -3886,18 +3887,16 @@ class TestApiAuth(DirectoriesMixin, APITestCase):
|
|||||||
status.HTTP_404_NOT_FOUND,
|
status.HTTP_404_NOT_FOUND,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_api_set_permissions(self):
|
def test_api_default_owner(self):
|
||||||
"""
|
"""
|
||||||
GIVEN:
|
GIVEN:
|
||||||
- API request to create an object (Tag) that supplies set_permissions object
|
- API request to create an object (Tag)
|
||||||
WHEN:
|
WHEN:
|
||||||
- owner is passed as null or as a user id
|
- owner is not set at all
|
||||||
- view > users is set
|
|
||||||
THEN:
|
THEN:
|
||||||
- Object permissions are set appropriately
|
- Object created with current user as owner
|
||||||
"""
|
"""
|
||||||
user1 = User.objects.create_superuser(username="user1")
|
user1 = User.objects.create_superuser(username="user1")
|
||||||
user2 = User.objects.create(username="user2")
|
|
||||||
|
|
||||||
self.client.force_authenticate(user1)
|
self.client.force_authenticate(user1)
|
||||||
|
|
||||||
@ -3907,11 +3906,73 @@ class TestApiAuth(DirectoriesMixin, APITestCase):
|
|||||||
{
|
{
|
||||||
"name": "test1",
|
"name": "test1",
|
||||||
"matching_algorithm": MatchingModel.MATCH_AUTO,
|
"matching_algorithm": MatchingModel.MATCH_AUTO,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
content_type="application/json",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
tag1 = Tag.objects.filter(name="test1").first()
|
||||||
|
self.assertEqual(tag1.owner, user1)
|
||||||
|
|
||||||
|
def test_api_set_no_owner(self):
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- API request to create an object (Tag)
|
||||||
|
WHEN:
|
||||||
|
- owner is passed as None
|
||||||
|
THEN:
|
||||||
|
- Object created with no owner
|
||||||
|
"""
|
||||||
|
user1 = User.objects.create_superuser(username="user1")
|
||||||
|
|
||||||
|
self.client.force_authenticate(user1)
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
"/api/tags/",
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"name": "test1",
|
||||||
|
"matching_algorithm": MatchingModel.MATCH_AUTO,
|
||||||
|
"owner": None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
content_type="application/json",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
tag1 = Tag.objects.filter(name="test1").first()
|
||||||
|
self.assertEqual(tag1.owner, None)
|
||||||
|
|
||||||
|
def test_api_set_owner_w_permissions(self):
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- API request to create an object (Tag) that supplies set_permissions object
|
||||||
|
WHEN:
|
||||||
|
- owner is passed as user id
|
||||||
|
- view > users is set & view > groups is set
|
||||||
|
THEN:
|
||||||
|
- Object permissions are set appropriately
|
||||||
|
"""
|
||||||
|
user1 = User.objects.create_superuser(username="user1")
|
||||||
|
user2 = User.objects.create(username="user2")
|
||||||
|
group1 = Group.objects.create(name="group1")
|
||||||
|
|
||||||
|
self.client.force_authenticate(user1)
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
"/api/tags/",
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"name": "test1",
|
||||||
|
"matching_algorithm": MatchingModel.MATCH_AUTO,
|
||||||
|
"owner": user1.id,
|
||||||
"set_permissions": {
|
"set_permissions": {
|
||||||
"owner": None,
|
|
||||||
"view": {
|
"view": {
|
||||||
"users": None,
|
"users": [user2.id],
|
||||||
"groups": None,
|
"groups": [group1.id],
|
||||||
},
|
},
|
||||||
"change": {
|
"change": {
|
||||||
"users": None,
|
"users": None,
|
||||||
@ -3926,19 +3987,43 @@ class TestApiAuth(DirectoriesMixin, APITestCase):
|
|||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
|
||||||
tag1 = Tag.objects.filter(name="test1").first()
|
tag1 = Tag.objects.filter(name="test1").first()
|
||||||
self.assertEqual(tag1.owner, None)
|
|
||||||
|
|
||||||
response = self.client.post(
|
from guardian.core import ObjectPermissionChecker
|
||||||
"/api/tags/",
|
|
||||||
|
checker = ObjectPermissionChecker(user2)
|
||||||
|
self.assertEqual(checker.has_perm("view_tag", tag1), True)
|
||||||
|
self.assertIn("view_tag", get_perms(group1, tag1))
|
||||||
|
|
||||||
|
def test_api_set_doc_permissions(self):
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- API request to update doc permissions and owner
|
||||||
|
WHEN:
|
||||||
|
- owner is set
|
||||||
|
- view > users is set & view > groups is set
|
||||||
|
THEN:
|
||||||
|
- Object permissions are set appropriately
|
||||||
|
"""
|
||||||
|
doc = Document.objects.create(
|
||||||
|
title="test",
|
||||||
|
mime_type="application/pdf",
|
||||||
|
content="this is a document",
|
||||||
|
)
|
||||||
|
user1 = User.objects.create_superuser(username="user1")
|
||||||
|
user2 = User.objects.create(username="user2")
|
||||||
|
group1 = Group.objects.create(name="group1")
|
||||||
|
|
||||||
|
self.client.force_authenticate(user1)
|
||||||
|
|
||||||
|
response = self.client.patch(
|
||||||
|
f"/api/documents/{doc.id}/",
|
||||||
json.dumps(
|
json.dumps(
|
||||||
{
|
{
|
||||||
"name": "test2",
|
"owner": user1.id,
|
||||||
"matching_algorithm": MatchingModel.MATCH_AUTO,
|
|
||||||
"set_permissions": {
|
"set_permissions": {
|
||||||
"owner": user1.id,
|
|
||||||
"view": {
|
"view": {
|
||||||
"users": [user2.id],
|
"users": [user2.id],
|
||||||
"groups": None,
|
"groups": [group1.id],
|
||||||
},
|
},
|
||||||
"change": {
|
"change": {
|
||||||
"users": None,
|
"users": None,
|
||||||
@ -3950,12 +4035,15 @@ class TestApiAuth(DirectoriesMixin, APITestCase):
|
|||||||
content_type="application/json",
|
content_type="application/json",
|
||||||
)
|
)
|
||||||
|
|
||||||
tag2 = Tag.objects.filter(name="test2").first()
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
doc = Document.objects.get(pk=doc.id)
|
||||||
|
|
||||||
|
self.assertEqual(doc.owner, user1)
|
||||||
from guardian.core import ObjectPermissionChecker
|
from guardian.core import ObjectPermissionChecker
|
||||||
|
|
||||||
checker = ObjectPermissionChecker(user2)
|
checker = ObjectPermissionChecker(user2)
|
||||||
self.assertEqual(checker.has_perm("view_tag", tag2), True)
|
self.assertTrue(checker.has_perm("view_document", doc))
|
||||||
|
self.assertIn("view_document", get_perms(group1, doc))
|
||||||
|
|
||||||
def test_dynamic_permissions_fields(self):
|
def test_dynamic_permissions_fields(self):
|
||||||
user1 = User.objects.create_user(username="user1")
|
user1 = User.objects.create_user(username="user1")
|
||||||
|
@ -906,6 +906,47 @@ class TestAsnBarcode(DirectoriesMixin, TestCase):
|
|||||||
input_doc,
|
input_doc,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@override_settings(CONSUMER_BARCODE_SCANNER="PYZBAR")
|
||||||
|
def test_scan_file_for_qrcode_without_upscale(self):
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- A printed and scanned PDF document with a rather small QR code
|
||||||
|
WHEN:
|
||||||
|
- ASN barcode detection is run with default settings
|
||||||
|
- pyzbar is used for detection, as zxing would behave differently, and detect the QR code
|
||||||
|
THEN:
|
||||||
|
- ASN is not detected
|
||||||
|
"""
|
||||||
|
|
||||||
|
test_file = self.BARCODE_SAMPLE_DIR / "barcode-qr-asn-000123-upscale-dpi.pdf"
|
||||||
|
|
||||||
|
with BarcodeReader(test_file, "application/pdf") as reader:
|
||||||
|
reader.detect()
|
||||||
|
self.assertEqual(len(reader.barcodes), 0)
|
||||||
|
|
||||||
|
@override_settings(CONSUMER_BARCODE_SCANNER="PYZBAR")
|
||||||
|
@override_settings(CONSUMER_BARCODE_DPI=600)
|
||||||
|
@override_settings(CONSUMER_BARCODE_UPSCALE=1.5)
|
||||||
|
def test_scan_file_for_qrcode_with_upscale(self):
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- A printed and scanned PDF document with a rather small QR code
|
||||||
|
WHEN:
|
||||||
|
- ASN barcode detection is run with 600dpi and an upscale factor of 1.5 and pyzbar
|
||||||
|
- pyzbar is used for detection, as zxing would behave differently.
|
||||||
|
Upscaling is a workaround for detection problems with pyzbar,
|
||||||
|
when you cannot switch to zxing (aarch64 build problems of zxing)
|
||||||
|
THEN:
|
||||||
|
- ASN 123 is detected
|
||||||
|
"""
|
||||||
|
|
||||||
|
test_file = self.BARCODE_SAMPLE_DIR / "barcode-qr-asn-000123-upscale-dpi.pdf"
|
||||||
|
|
||||||
|
with BarcodeReader(test_file, "application/pdf") as reader:
|
||||||
|
reader.detect()
|
||||||
|
self.assertEqual(len(reader.barcodes), 1)
|
||||||
|
self.assertEqual(reader.asn, 123)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
@pytest.mark.skipif(
|
||||||
not HAS_ZXING_LIB,
|
not HAS_ZXING_LIB,
|
||||||
|
@ -781,6 +781,16 @@ CONSUMER_ASN_BARCODE_PREFIX: Final[str] = os.getenv(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
CONSUMER_BARCODE_UPSCALE: Final[float] = float(
|
||||||
|
os.getenv("PAPERLESS_CONSUMER_BARCODE_UPSCALE", 0.0),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
CONSUMER_BARCODE_DPI: Final[str] = int(
|
||||||
|
os.getenv("PAPERLESS_CONSUMER_BARCODE_DPI", 300),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
OCR_PAGES = int(os.getenv("PAPERLESS_OCR_PAGES", 0))
|
OCR_PAGES = int(os.getenv("PAPERLESS_OCR_PAGES", 0))
|
||||||
|
|
||||||
# The default language that tesseract will attempt to use when parsing
|
# The default language that tesseract will attempt to use when parsing
|
||||||
|
Loading…
x
Reference in New Issue
Block a user