mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-10-20 23:08:14 +00:00
Make them owned, duh
Update 0028_ocrtemplate_ocrtemplatezone.py
This commit is contained in:
5 files changed
+122
-10
No files matched your search
@@ -3,6 +3,7 @@
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
from django.conf import settings
|
||||
from django.db import migrations
|
||||
from django.db import models
|
||||
|
||||
@@ -26,6 +27,17 @@ class Migration(migrations.Migration):
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=128, verbose_name="name")),
|
||||
(
|
||||
"owner",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
default=None,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
verbose_name="owner",
|
||||
),
|
||||
),
|
||||
(
|
||||
"source_width",
|
||||
models.PositiveIntegerField(
|
||||
|
||||
@@ -2151,7 +2151,7 @@ class WorkflowRun(SoftDeleteModel):
|
||||
return f"WorkflowRun of {self.workflow} at {self.run_at} on {self.document}"
|
||||
|
||||
|
||||
class OcrTemplate(models.Model):
|
||||
class OcrTemplate(ModelWithOwner):
|
||||
"""
|
||||
Defines a set of OCR extraction zones for a specific document type.
|
||||
|
||||
|
||||
@@ -3781,7 +3781,7 @@ class OcrTemplateZoneSerializer(serializers.ModelSerializer):
|
||||
return value
|
||||
|
||||
|
||||
class OcrTemplateSerializer(serializers.ModelSerializer):
|
||||
class OcrTemplateSerializer(OwnedObjectSerializer):
|
||||
zones = OcrTemplateZoneSerializer(many=True, required=False)
|
||||
|
||||
class Meta:
|
||||
@@ -3798,6 +3798,10 @@ class OcrTemplateSerializer(serializers.ModelSerializer):
|
||||
"created",
|
||||
"updated",
|
||||
"zones",
|
||||
"owner",
|
||||
"permissions",
|
||||
"user_can_change",
|
||||
"set_permissions",
|
||||
]
|
||||
read_only_fields = ["created", "updated"]
|
||||
|
||||
@@ -3842,17 +3846,14 @@ class OcrTemplateSerializer(serializers.ModelSerializer):
|
||||
|
||||
def create(self, validated_data):
|
||||
zones_data = validated_data.pop("zones", [])
|
||||
template = OcrTemplate.objects.create(**validated_data)
|
||||
template = super().create(validated_data)
|
||||
for zone_data in zones_data:
|
||||
OcrTemplateZone.objects.create(template=template, **zone_data)
|
||||
return template
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
zones_data = validated_data.pop("zones", None)
|
||||
|
||||
for attr, value in validated_data.items():
|
||||
setattr(instance, attr, value)
|
||||
instance.save()
|
||||
instance = super().update(instance, validated_data)
|
||||
|
||||
if zones_data is not None:
|
||||
# Replace all zones with the new set
|
||||
|
||||
@@ -109,6 +109,98 @@ class TestOcrTemplatesAPI(DirectoriesMixin, APITestCase):
|
||||
self.assertEqual(OcrTemplate.objects.count(), 1)
|
||||
self.assertEqual(OcrTemplateZone.objects.count(), 1)
|
||||
|
||||
def test_create_template_sets_owner(self):
|
||||
"""
|
||||
GIVEN:
|
||||
- A user creating an OCR template
|
||||
WHEN:
|
||||
- API request to create it without an owner
|
||||
THEN:
|
||||
- The requesting user owns the template
|
||||
"""
|
||||
response = self.client.post(
|
||||
self.ENDPOINT,
|
||||
json.dumps(self._make_template_data()),
|
||||
content_type="application/json",
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
self.assertEqual(OcrTemplate.objects.get().owner, self.user)
|
||||
|
||||
def test_list_only_shows_permitted_templates(self):
|
||||
"""
|
||||
GIVEN:
|
||||
- Templates that are unowned, owned by the user, owned by another
|
||||
user, and owned by another user but shared with the user
|
||||
WHEN:
|
||||
- The user lists OCR templates
|
||||
THEN:
|
||||
- Only the unowned, owned and shared templates are returned
|
||||
"""
|
||||
other = User.objects.create_user(username="other")
|
||||
user = User.objects.create_user(username="template_viewer")
|
||||
user.user_permissions.add(Permission.objects.get(codename="view_ocrtemplate"))
|
||||
|
||||
def template(name, owner=None):
|
||||
return OcrTemplate.objects.create(
|
||||
name=name,
|
||||
document_type=self.doc_type,
|
||||
source_width=1000,
|
||||
source_height=1000,
|
||||
owner=owner,
|
||||
)
|
||||
|
||||
template("Unowned")
|
||||
template("Mine", owner=user)
|
||||
template("Private", owner=other)
|
||||
grant_object(user, template("Shared", owner=other), "view_ocrtemplate")
|
||||
self.client.force_authenticate(user=user)
|
||||
|
||||
response = self.client.get(self.ENDPOINT)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertCountEqual(
|
||||
[t["name"] for t in response.json()["results"]],
|
||||
["Unowned", "Mine", "Shared"],
|
||||
)
|
||||
|
||||
def test_cannot_change_another_users_template(self):
|
||||
"""
|
||||
GIVEN:
|
||||
- A template owned by another user
|
||||
WHEN:
|
||||
- A user with global change permission tries to update it
|
||||
THEN:
|
||||
- The update is refused until the user is granted change on it
|
||||
"""
|
||||
other = User.objects.create_user(username="other")
|
||||
user = User.objects.create_user(username="template_editor")
|
||||
user.user_permissions.add(
|
||||
*Permission.objects.filter(
|
||||
codename__in=["view_ocrtemplate", "change_ocrtemplate"],
|
||||
),
|
||||
)
|
||||
template = OcrTemplate.objects.create(
|
||||
name="Private",
|
||||
document_type=self.doc_type,
|
||||
source_width=1000,
|
||||
source_height=1000,
|
||||
owner=other,
|
||||
)
|
||||
grant_object(user, template, "view_ocrtemplate")
|
||||
self.client.force_authenticate(user=user)
|
||||
url = f"{self.ENDPOINT}{template.pk}/"
|
||||
|
||||
response = self.client.patch(url, {"name": "Renamed"}, format="json")
|
||||
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
||||
|
||||
grant_object(user, template, "change_ocrtemplate")
|
||||
response = self.client.patch(url, {"name": "Renamed"}, format="json")
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
template.refresh_from_db()
|
||||
self.assertEqual(template.name, "Renamed")
|
||||
self.assertEqual(template.owner, other)
|
||||
|
||||
def test_create_template_multiple_zones(self):
|
||||
"""
|
||||
GIVEN:
|
||||
|
||||
+10
-3
@@ -5835,11 +5835,13 @@ def serve_logo(request: HttpRequest, filename: str | None = None) -> FileRespons
|
||||
)
|
||||
|
||||
|
||||
class OcrTemplateViewSet(ModelViewSet):
|
||||
class OcrTemplateViewSet(BulkPermissionMixin, PassUserMixin, ModelViewSet[OcrTemplate]):
|
||||
"""CRUD for OCR templates with zone definitions."""
|
||||
|
||||
model = OcrTemplate
|
||||
|
||||
queryset = (
|
||||
OcrTemplate.objects.all()
|
||||
OcrTemplate.objects.select_related("owner")
|
||||
.prefetch_related(
|
||||
"zones",
|
||||
"zones__custom_field",
|
||||
@@ -5847,8 +5849,13 @@ class OcrTemplateViewSet(ModelViewSet):
|
||||
.order_by("name")
|
||||
)
|
||||
serializer_class = OcrTemplateSerializer
|
||||
permission_classes = (IsAuthenticated, PaperlessObjectPermissions)
|
||||
pagination_class = StandardPagination
|
||||
permission_classes = (IsAuthenticated, PaperlessObjectPermissions)
|
||||
filter_backends = (
|
||||
OrderingFilter,
|
||||
PermittedObjectsFilter,
|
||||
)
|
||||
ordering_fields = ("name",)
|
||||
|
||||
@action(
|
||||
detail=False,
|
||||
|
||||
Reference in new issue
Block a user