Save some not working stuff

This commit is contained in:
Trenton Holmes 2023-10-27 20:52:35 -07:00 committed by shamoon
parent 8605907f6f
commit d9526ea32f
6 changed files with 216 additions and 447 deletions

View File

@ -1,4 +1,4 @@
# Generated by Django 4.2.5 on 2023-10-25 13:49 # Generated by Django 4.2.5 on 2023-10-28 02:54
import django.db.models.deletion import django.db.models.deletion
import django.utils.timezone import django.utils.timezone
@ -43,8 +43,8 @@ class Migration(migrations.Migration):
("url", "URL"), ("url", "URL"),
("date", "Date"), ("date", "Date"),
("boolean", "Boolean"), ("boolean", "Boolean"),
("integer", "Integer"),
], ],
default="string",
max_length=50, max_length=50,
verbose_name="data type", verbose_name="data type",
), ),
@ -56,54 +56,6 @@ class Migration(migrations.Migration):
"ordering": ("created",), "ordering": ("created",),
}, },
), ),
migrations.CreateModel(
name="CustomFieldBoolean",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("value", models.BooleanField()),
(
"parent",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
related_name="boolean",
to="documents.customfieldinstance",
),
),
],
),
migrations.CreateModel(
name="CustomFieldDate",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("value", models.DateField()),
(
"parent",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
related_name="date",
to="documents.customfieldinstance",
),
),
],
),
migrations.CreateModel( migrations.CreateModel(
name="CustomFieldInstance", name="CustomFieldInstance",
fields=[ fields=[
@ -157,6 +109,78 @@ class Migration(migrations.Migration):
"ordering": ("created",), "ordering": ("created",),
}, },
), ),
migrations.CreateModel(
name="CustomFieldBoolean",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("value", models.BooleanField()),
(
"parent",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
related_name="boolean",
to="documents.customfieldinstance",
),
),
],
),
migrations.CreateModel(
name="CustomFieldDate",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("value", models.DateField()),
(
"parent",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
related_name="date",
to="documents.customfieldinstance",
),
),
],
),
migrations.CreateModel(
name="CustomFieldInteger",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("value", models.IntegerField()),
(
"parent",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
related_name="integer",
to="documents.customfieldinstance",
),
),
],
),
migrations.CreateModel( migrations.CreateModel(
name="CustomFieldShortText", name="CustomFieldShortText",
fields=[ fields=[

View File

@ -895,6 +895,7 @@ class CustomField(models.Model):
URL = ("url", _("URL")) URL = ("url", _("URL"))
DATE = ("date", _("Date")) DATE = ("date", _("Date"))
BOOL = ("boolean"), _("Boolean") BOOL = ("boolean"), _("Boolean")
INT = ("integer", _("Integer"))
created = models.DateTimeField( created = models.DateTimeField(
_("created"), _("created"),
@ -908,7 +909,6 @@ class CustomField(models.Model):
_("data type"), _("data type"),
max_length=50, max_length=50,
choices=FieldDataType.choices, choices=FieldDataType.choices,
default=FieldDataType.STRING,
) )
class Meta: class Meta:
@ -969,6 +969,8 @@ class CustomFieldInstance(ModelWithOwner):
return self.date.value return self.date.value
elif self.field.data_type == CustomField.FieldDataType.BOOL: elif self.field.data_type == CustomField.FieldDataType.BOOL:
return self.boolean.value return self.boolean.value
elif self.field.data_type == CustomField.FieldDataType.INT:
return self.integer.value
raise NotImplementedError(self.field.data_type) raise NotImplementedError(self.field.data_type)
@property @property
@ -984,6 +986,8 @@ class CustomFieldInstance(ModelWithOwner):
return CustomFieldDate return CustomFieldDate
elif self.field.data_type == CustomField.FieldDataType.BOOL: elif self.field.data_type == CustomField.FieldDataType.BOOL:
return CustomFieldBoolean return CustomFieldBoolean
elif self.field.data_type == CustomField.FieldDataType.INT:
return CustomFieldInteger
raise NotImplementedError(self.field.data_type) raise NotImplementedError(self.field.data_type)
def to_json(self) -> dict[str, str]: def to_json(self) -> dict[str, str]:
@ -1077,3 +1081,20 @@ class CustomFieldDate(models.Model):
def __str__(self) -> str: def __str__(self) -> str:
return f"{self.value}" return f"{self.value}"
class CustomFieldInteger(models.Model):
"""
Data storage for a date custom field
"""
value = models.IntegerField()
parent = models.OneToOneField(
CustomFieldInstance,
on_delete=models.CASCADE,
related_name="integer",
parent_link=True,
)
def __str__(self) -> str:
return f"{self.value}"

View File

@ -396,6 +396,40 @@ class StoragePathField(serializers.PrimaryKeyRelatedField):
return StoragePath.objects.all() return StoragePath.objects.all()
class CustomFieldSerializer(serializers.ModelSerializer):
class Meta:
model = CustomField
fields = [
"id",
"name",
"data_type",
]
class CustomFieldInstanceSerializer(serializers.ModelSerializer):
parent = CustomFieldSerializer()
value = SerializerMethodField()
def get_value(self, obj: CustomFieldInstance):
return obj.value
def create(self, validated_data):
parent_data = validated_data.pop("parent")
parent = CustomField.objects.get(id=parent_data["id"])
instance = CustomFieldInstance.objects.create(parent=parent)
return instance
def update(self, instance: CustomFieldInstance):
return instance
class Meta:
model = CustomFieldInstance
fields = [
"parent",
"value",
]
class DocumentSerializer(OwnedObjectSerializer, DynamicFieldsModelSerializer): class DocumentSerializer(OwnedObjectSerializer, DynamicFieldsModelSerializer):
correspondent = CorrespondentField(allow_null=True) correspondent = CorrespondentField(allow_null=True)
tags = TagsField(many=True) tags = TagsField(many=True)
@ -406,6 +440,8 @@ class DocumentSerializer(OwnedObjectSerializer, DynamicFieldsModelSerializer):
archived_file_name = SerializerMethodField() archived_file_name = SerializerMethodField()
created_date = serializers.DateField(required=False) created_date = serializers.DateField(required=False)
custom_fields = CustomFieldInstanceSerializer(many=True, allow_null=True)
owner = serializers.PrimaryKeyRelatedField( owner = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(), queryset=User.objects.all(),
required=False, required=False,
@ -468,6 +504,7 @@ class DocumentSerializer(OwnedObjectSerializer, DynamicFieldsModelSerializer):
"user_can_change", "user_can_change",
"set_permissions", "set_permissions",
"notes", "notes",
"custom_fields",
) )
@ -1092,13 +1129,3 @@ class ConsumptionTemplateSerializer(serializers.ModelSerializer):
) )
return attrs return attrs
class CustomFieldSerializer(serializers.ModelSerializer):
class Meta:
model = CustomField
class CustomFieldInstanceSerializer(serializers.ModelSerializer):
class Meta:
model = CustomFieldInstance

View File

@ -0,0 +1,82 @@
from django.contrib.auth.models import User
from rest_framework import status
from rest_framework.test import APITestCase
from documents.models import CustomField
from documents.models import Document
from documents.tests.utils import DirectoriesMixin
class TestCustomField(DirectoriesMixin, APITestCase):
ENDPOINT = "/api/custom_fields/"
def setUp(self):
self.user = User.objects.create_superuser(username="temp_admin")
self.client.force_authenticate(user=self.user)
return super().setUp()
def test_create_custom_field(self):
"""
GIVEN:
- Each of the supported data types is created
WHEN:
- API request to create custom metadata is made
THEN:
- the field is created
- the field returns the correct fields
"""
for field_type, name in [
("string", "Custom Text"),
("url", "Wikipedia Link"),
("date", "Invoiced Date"),
("integer", "Invoice #"),
]:
resp = self.client.post(
self.ENDPOINT,
data={
"data_type": field_type,
"name": name,
},
)
self.assertEqual(resp.status_code, status.HTTP_201_CREATED)
data = resp.json()
self.assertEqual(len(data), 3)
self.assertEqual(data["name"], name)
self.assertEqual(data["data_type"], field_type)
def test_create_custom_field_instance(self):
doc = Document.objects.create(
title="WOW",
content="the content",
checksum="123",
mime_type="application/pdf",
)
custom_field = CustomField.objects.create(
name="Test Custom Field",
data_type=CustomField.FieldDataType.STRING,
)
resp = self.client.patch(
f"/api/documents/{doc.id}/",
data={
"custom_fields": [
{
"parent": {
"id": custom_field.id,
},
"value": "test value",
},
],
},
format="json",
)
self.assertEqual(resp.status_code, status.HTTP_200_OK)
doc.refresh_from_db()
from pprint import pprint
for item in doc.custom_fields.all():
pprint(item)
self.assertFalse(True)

View File

@ -1,291 +0,0 @@
from datetime import timedelta
from unittest import mock
from unittest.mock import MagicMock
from django.contrib.auth.models import Permission
from django.contrib.auth.models import User
from django.utils import timezone
from guardian.shortcuts import assign_perm
from rest_framework import status
from rest_framework.test import APITestCase
from documents.models import CustomMetadata
from documents.models import Document
from documents.tests.utils import DirectoriesMixin
class TestCustomMetadata(DirectoriesMixin, APITestCase):
def setUp(self):
self.user = User.objects.create_superuser(username="temp_admin")
self.client.force_authenticate(user=self.user)
return super().setUp()
@staticmethod
def create_json_no_date(metadata: CustomMetadata):
"""
Small helper to remove the created datatime from the JSON
It doesn't matter to verify
"""
expected = metadata.to_json()
del expected["created"]
return expected
def test_get_existing_custom_metadata(self):
"""
GIVEN:
- A document with 2 different metadata attached to it
WHEN:
- API request for document custom metadata is made
THEN:
- Both associated values are returned
"""
doc = Document.objects.create(
title="test",
mime_type="application/pdf",
content="this is a document which will have custom metadata on it! Neat",
)
metadata1 = CustomMetadata.objects.create(
data_type=CustomMetadata.DataType.STRING,
name="Invoice Number",
data="#123456",
document=doc,
user=self.user,
)
metadata2 = CustomMetadata.objects.create(
data_type=CustomMetadata.DataType.URL,
name="October 20th, 2023 On This Day",
data="https://en.wikipedia.org/wiki/Pope_Pius_XII",
document=doc,
user=self.user,
)
all_metadata = [metadata1, metadata2]
response = self.client.get(
f"/api/documents/{doc.pk}/custom_metadata/",
format="json",
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
resp_data = response.json()
self.assertEqual(len(resp_data), 2)
for idx, resp_data in enumerate(reversed(resp_data)):
del resp_data["created"]
self.assertDictEqual(
resp_data,
self.create_json_no_date(all_metadata[idx]),
)
def test_create_custom_metadata(self):
"""
GIVEN:
- Existing document
WHEN:
- API request is made to add 2 custom metadata fields
THEN:
- metadata objects are created and associated with document
- Document modified time is updated
"""
doc = Document.objects.create(
title="test",
mime_type="application/pdf",
content="this is a document which will have custom_metadata added",
created=timezone.now() - timedelta(days=1),
)
# set to yesterday
doc.modified = timezone.now() - timedelta(days=1)
self.assertEqual(doc.modified.day, (timezone.now() - timedelta(days=1)).day)
resp = self.client.post(
f"/api/documents/{doc.pk}/custom_metadata/",
data={"type": "string", "name": "Custom Field 1", "data": "Custom Data 1"},
)
self.assertEqual(resp.status_code, status.HTTP_200_OK)
response = self.client.get(
f"/api/documents/{doc.pk}/custom_metadata/",
format="json",
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
resp_data = response.json()
self.assertEqual(len(resp_data), 1)
resp_data = resp_data[0]
self.assertEqual(resp_data["data"], "Custom Data 1")
doc = Document.objects.get(pk=doc.pk)
# modified was updated to today
self.assertEqual(doc.modified.day, timezone.now().day)
def test_custom_metadata_view_add_delete_permissions_aware(self):
"""
GIVEN:
- Existing document owned by user2 but with granted view perms for user1
WHEN:
- API request is made by user1 to add a custom metadata
THEN:
- custom metadata is not created
"""
user1 = User.objects.create_user(username="test1")
user1.user_permissions.add(*Permission.objects.all())
user1.save()
user2 = User.objects.create_user(username="test2")
user2.save()
doc = Document.objects.create(
title="test",
mime_type="application/pdf",
content="this is a document which will have custom_metadata added",
)
doc.owner = user2
doc.save()
self.client.force_authenticate(user1)
resp = self.client.get(
f"/api/documents/{doc.pk}/custom_metadata/",
format="json",
)
self.assertEqual(
resp.content,
b"Insufficient permissions to view custom metadata",
)
self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN)
assign_perm("view_document", user1, doc)
resp = self.client.post(
f"/api/documents/{doc.pk}/custom_metadata/",
data={"type": "string", "name": "Custom Field 1", "data": "Custom Data 1"},
)
self.assertEqual(
resp.content,
b"Insufficient permissions to create custom metadata",
)
self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN)
metadata = CustomMetadata.objects.create(
data_type=CustomMetadata.DataType.STRING,
name="Invoice Number",
data="#123456",
document=doc,
user=self.user,
)
response = self.client.delete(
f"/api/documents/{doc.pk}/custom_metadata/?id={metadata.pk}",
format="json",
)
self.assertEqual(
response.content,
b"Insufficient permissions to delete custom metadata",
)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
def test_delete_custom_metadata(self):
"""
GIVEN:
- Existing document, existing custom metadata
WHEN:
- API request is made to delete a custom metadata
THEN:
- custom metadata is deleted, document modified is updated
"""
doc = Document.objects.create(
title="test",
mime_type="application/pdf",
content="this is a document which will have custom metadata!",
created=timezone.now() - timedelta(days=1),
)
# set to yesterday
doc.modified = timezone.now() - timedelta(days=1)
self.assertEqual(doc.modified.day, (timezone.now() - timedelta(days=1)).day)
metadata = CustomMetadata.objects.create(
data_type=CustomMetadata.DataType.DATE,
name="Invoice Number",
data="2023-10-20",
document=doc,
user=self.user,
)
response = self.client.delete(
f"/api/documents/{doc.pk}/custom_metadata/?id={metadata.pk}",
format="json",
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(CustomMetadata.objects.all()), 0)
doc = Document.objects.get(pk=doc.pk)
# modified was updated to today
self.assertEqual(doc.modified.day, timezone.now().day)
def test_get_custom_metadata_no_doc(self):
"""
GIVEN:
- A request to get custom metadata from a non-existent document
WHEN:
- API request for document custom metadata is made
THEN:
- HTTP status.HTTP_404_NOT_FOUND is returned
"""
response = self.client.get(
"/api/documents/500/custom_metadata/",
format="json",
)
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
@mock.patch("documents.views.CustomMetadata.to_json")
def test_get_custom_metadata_failure(self, mocked_to_json: MagicMock):
mocked_to_json.side_effect = Exception("this failed somehow")
doc = Document.objects.create(
title="test",
mime_type="application/pdf",
content="this is a document which will have custom metadata on it! Neat",
)
_ = CustomMetadata.objects.create(
data_type=CustomMetadata.DataType.STRING,
name="Invoice Number",
data="#123456",
document=doc,
user=self.user,
)
response = self.client.get(
f"/api/documents/{doc.pk}/custom_metadata/",
format="json",
)
self.assertEqual(response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR)
@mock.patch("documents.views.CustomMetadata.from_json")
def test_add_custom_metadata_failure(self, mocked_from_json: MagicMock):
mocked_from_json.side_effect = Exception("this failed somehow else")
doc = Document.objects.create(
title="test",
mime_type="application/pdf",
content="this is a document which will have custom metadata on it! Neat",
)
response = self.client.post(
f"/api/documents/{doc.pk}/custom_metadata/",
data={"type": "string", "name": "Custom Field 1", "data": "Custom Data 1"},
)
self.assertEqual(response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR)

View File

@ -624,100 +624,6 @@ class DocumentViewSet(
] ]
return Response(links) return Response(links)
# @action(methods=["get", "post", "delete"], detail=True)
# def custom_metadata(self, request, pk=None) -> Response:
# def package_custom_metadata(doc: Document):
# return [
# c.to_json()
# for c in CustomMetadata.objects.filter(document=doc).order_by(
# "-created",
# )
# ]
# request.user = request.user
# try:
# doc = Document.objects.get(pk=pk)
# if request.user is not None and not has_perms_owner_aware(
# request.user,
# "view_document",
# doc,
# ):
# return HttpResponseForbidden(
# "Insufficient permissions to view custom metadata",
# )
# except Document.DoesNotExist:
# raise Http404
# if request.method == "GET":
# try:
# return Response(package_custom_metadata(doc))
# except Exception as e:
# logger.warning(f"An error occurred retrieving custom metadata: {e!s}")
# return HttpResponseServerError(
# {
# "error": (
# "Error retrieving custom metadata,"
# " check logs for more detail."
# ),
# },
# )
# elif request.method == "POST":
# try:
# if request.user is not None and not has_perms_owner_aware(
# request.user,
# "change_document",
# doc,
# ):
# return HttpResponseForbidden(
# "Insufficient permissions to create custom metadata",
# )
# CustomMetadata.from_json(doc, request.user, request.data)
# doc.modified = timezone.now()
# doc.save()
# from documents import index
# index.add_or_update_document(self.get_object())
# return Response(package_custom_metadata(doc))
# except Exception as e:
# logger.warning(f"An error occurred saving custom metadata: {e!s}")
# return HttpResponseServerError(
# {
# "error": (
# "Error saving custom metadata, "
# "check logs for more detail."
# ),
# },
# )
# elif request.method == "DELETE":
# if request.user is not None and not has_perms_owner_aware(
# request.user,
# "change_document",
# doc,
# ):
# return HttpResponseForbidden(
# "Insufficient permissions to delete custom metadata",
# )
# metadata = CustomMetadata.objects.get(id=int(request.GET.get("id")))
# metadata.delete()
# doc.modified = timezone.now()
# doc.save()
# from documents import index
# index.add_or_update_document(self.get_object())
# return Response(package_custom_metadata(doc))
# return Response(
# {"error": "unreachable error was reached for custom metadata"},
# ) # pragma: no cover
class SearchResultSerializer(DocumentSerializer, PassUserMixin): class SearchResultSerializer(DocumentSerializer, PassUserMixin):
def to_representation(self, instance): def to_representation(self, instance):
@ -1441,7 +1347,7 @@ class ConsumptionTemplateViewSet(ModelViewSet):
model = ConsumptionTemplate model = ConsumptionTemplate
queryset = ConsumptionTemplate.objects.all().order_by("order") queryset = ConsumptionTemplate.objects.all().order_by("name")
class CustomFieldViewSet(ModelViewSet): class CustomFieldViewSet(ModelViewSet):