From b31ae1182c2f1b20e4c08c6325854e99d8867093 Mon Sep 17 00:00:00 2001 From: Maria Vatasoiu Date: Thu, 20 Jul 2023 21:04:25 +0200 Subject: [PATCH] feat(form): Implements feature for issue projectcaluma/caluma#1699 --- caluma/caluma_core/models.py | 7 +-- caluma/caluma_core/tests/test_views.py | 63 +++++++++++++++++++ caluma/caluma_core/urls.py | 6 +- .../migrations/0047_add_draft_flag_to_file.py | 22 +++++++ caluma/caluma_form/models.py | 1 + .../tests/__snapshots__/test_history.ambr | 4 +- caluma/caluma_form/tests/test_history.py | 2 +- caluma/caluma_form/views.py | 34 ++++++++++ caluma/caluma_logging/middleware.py | 22 ++++--- caluma/tests/__snapshots__/test_schema.ambr | 1 + 10 files changed, 144 insertions(+), 18 deletions(-) create mode 100644 caluma/caluma_core/tests/test_views.py create mode 100644 caluma/caluma_form/migrations/0047_add_draft_flag_to_file.py create mode 100644 caluma/caluma_form/views.py diff --git a/caluma/caluma_core/models.py b/caluma/caluma_core/models.py index 23c95013d..3258f2617 100644 --- a/caluma/caluma_core/models.py +++ b/caluma/caluma_core/models.py @@ -10,11 +10,10 @@ def _history_user_getter(historical_instance): def _history_user_setter(historical_instance, user): request = getattr(HistoricalRecords.thread, "request", None) - user = None + user = "AnonymousUser" if request is not None: - user = request.user.username - if request.user.__class__.__name__ == "AnonymousUser": - user = "AnonymousUser" + if hasattr(request, "user"): + user = request.user.username historical_instance.history_user_id = user diff --git a/caluma/caluma_core/tests/test_views.py b/caluma/caluma_core/tests/test_views.py new file mode 100644 index 000000000..d49a9361c --- /dev/null +++ b/caluma/caluma_core/tests/test_views.py @@ -0,0 +1,63 @@ +import pytest +from django.urls import reverse +from rest_framework.status import HTTP_201_CREATED + + +@pytest.mark.parametrize("question__type", ["files"]) +def test_minio_callback_view(transactional_db, client, answer, minio_mock, settings): + file = answer.files.first() + data = { + "EventName": "s3:ObjectCreated:Put", + "Key": "caluma-media/218b2504-1736-476e-9975-dc5215ef4f01_test.png", + "Records": [ + { + "eventVersion": "2.0", + "eventSource": "minio:s3", + "awsRegion": "", + "eventTime": "2020-07-17T06:38:23.221Z", + "eventName": "s3:ObjectCreated:Put", + "userIdentity": {"principalId": "minio"}, + "requestParameters": { + "accessKey": "minio", + "region": "", + "sourceIPAddress": "172.20.0.1", + }, + "responseElements": { + "x-amz-request-id": "162276DB8350E531", + "x-minio-deployment-id": "5db7c8da-79cb-4d3a-8d40-189b51ca7aa6", + "x-minio-origin-endpoint": "http://172.20.0.2:9000", + }, + "s3": { + "s3SchemaVersion": "1.0", + "configurationId": "Config", + "bucket": { + "name": "caluma-media", + "ownerIdentity": {"principalId": "minio"}, + "arn": "arn:aws:s3:::caluma-media", + }, + "object": { + "key": "{file_id}_name".format(file_id=file.id), + "size": 299758, + "eTag": "af1421c17294eed533ec99eb82b468fb", + "contentType": "application/pdf", + "userMetadata": {"content-variant": "application/pdf"}, + "versionId": "1", + "sequencer": "162276DB83A9F895", + }, + }, + "source": { + "host": "172.20.0.1", + "port": "", + "userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) QtWebEngine/5.15.0 Chrome/80.0.3987.163 Safari/537.36", + }, + } + ], + } + + assert file.is_draft is True + response = client.post( + reverse("minio-callback"), data=data, content_type="application/json" + ) + file.refresh_from_db() + assert file.is_draft is False + assert response.status_code == HTTP_201_CREATED diff --git a/caluma/caluma_core/urls.py b/caluma/caluma_core/urls.py index 9b1d592ff..0113fdd23 100644 --- a/caluma/caluma_core/urls.py +++ b/caluma/caluma_core/urls.py @@ -1,7 +1,8 @@ from django.conf import settings from django.urls import re_path -from caluma.caluma_core import views +from caluma.caluma_core import views as core_views +from caluma.caluma_form import views as form_views from caluma.caluma_user.views import AuthenticationGraphQLView urlpatterns = [ @@ -10,5 +11,6 @@ AuthenticationGraphQLView.as_view(graphiql=settings.DEBUG), name="graphql", ), - re_path("healthz/?", views.health_check_status, name="healthz"), + re_path("healthz/?", core_views.health_check_status, name="healthz"), + re_path("minio-callback/?", form_views.minio_callback_view, name="minio-callback"), ] diff --git a/caluma/caluma_form/migrations/0047_add_draft_flag_to_file.py b/caluma/caluma_form/migrations/0047_add_draft_flag_to_file.py new file mode 100644 index 000000000..5f7c65a89 --- /dev/null +++ b/caluma/caluma_form/migrations/0047_add_draft_flag_to_file.py @@ -0,0 +1,22 @@ +# Generated by Django 3.2.19 on 2023-07-20 12:17 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("caluma_form", "0046_file_answer_reverse_keys"), + ] + + operations = [ + migrations.AddField( + model_name="file", + name="is_draft", + field=models.BooleanField(default=True), + ), + migrations.AddField( + model_name="historicalfile", + name="is_draft", + field=models.BooleanField(default=True), + ), + ] diff --git a/caluma/caluma_form/models.py b/caluma/caluma_form/models.py index e63fbe840..5b236164f 100644 --- a/caluma/caluma_form/models.py +++ b/caluma/caluma_form/models.py @@ -608,6 +608,7 @@ class File(core_models.UUIDModel): answer = models.ForeignKey( Answer, on_delete=models.CASCADE, related_name="files", null=True, default=None ) + is_draft = models.BooleanField(default=True) @_ignore_missing_file def _move_blob(self): diff --git a/caluma/caluma_form/tests/__snapshots__/test_history.ambr b/caluma/caluma_form/tests/__snapshots__/test_history.ambr index aa8797224..5c8aa1520 100644 --- a/caluma/caluma_form/tests/__snapshots__/test_history.ambr +++ b/caluma/caluma_form/tests/__snapshots__/test_history.ambr @@ -28,7 +28,7 @@ dict({ 'node': dict({ '__typename': 'HistoricalStringAnswer', - 'historyUserId': 'AnonymousUser', + 'historyUserId': None, 'value': 'first anon - revision 3', }), }), @@ -48,7 +48,7 @@ dict({ 'node': dict({ '__typename': 'HistoricalStringAnswer', - 'historyUserId': 'AnonymousUser', + 'historyUserId': None, 'value': 'second anon - revision 4', }), }), diff --git a/caluma/caluma_form/tests/test_history.py b/caluma/caluma_form/tests/test_history.py index 37a890ed8..b49cec751 100644 --- a/caluma/caluma_form/tests/test_history.py +++ b/caluma/caluma_form/tests/test_history.py @@ -63,7 +63,7 @@ def test_history(db, question, document, schema_executor, admin_schema_executor) ) assert history[1].value == "dolor" - assert history[0].history_user == "AnonymousUser" + assert history[0].history_user is None assert history[0].value == "sit" diff --git a/caluma/caluma_form/views.py b/caluma/caluma_form/views.py new file mode 100644 index 000000000..31b5d19e6 --- /dev/null +++ b/caluma/caluma_form/views.py @@ -0,0 +1,34 @@ +import json + +from django.conf import settings +from django.http import HttpResponse +from django.views.decorators.http import require_http_methods +from rest_framework.status import HTTP_200_OK, HTTP_201_CREATED, HTTP_400_BAD_REQUEST + +from caluma.caluma_form.models import File + + +@require_http_methods(["HEAD", "POST"]) +def minio_callback_view(request): + status = HTTP_200_OK + if request.method == "HEAD": + return HttpResponse(status=status) + + data = json.loads(request.body.decode("utf-8")) + + for record in data["Records"]: + bucket_name = record["s3"]["bucket"]["name"] + if not bucket_name == settings.MINIO_STORAGE_MEDIA_BUCKET_NAME: + continue + + file_pk = record["s3"]["object"]["key"].split("_")[0] + try: + file = File.objects.get(pk=file_pk) + except File.DoesNotExist: + return HttpResponse(status=HTTP_400_BAD_REQUEST) + + file.is_draft = False + file.save() + status = HTTP_201_CREATED + + return HttpResponse(status=status) diff --git a/caluma/caluma_logging/middleware.py b/caluma/caluma_logging/middleware.py index 9cf1aa611..f8e71ca1c 100644 --- a/caluma/caluma_logging/middleware.py +++ b/caluma/caluma_logging/middleware.py @@ -22,17 +22,21 @@ def __call__(self, request): try: doc = parser.parse(body["query"]) visitor.visit(doc, vis) - except GraphQLSyntaxError: + except (GraphQLSyntaxError, KeyError): pass - AccessLog.objects.create( - username=request.user.username, - query=body.get("query"), - variables=body.get("variables"), - status_code=response.status_code, - has_error=response.status_code >= 400, - **vis.values, - ) + try: + AccessLog.objects.create( + username=request.user.username, + query=body.get("query"), + variables=body.get("variables"), + status_code=response.status_code, + has_error=response.status_code >= 400, + **vis.values, + ) + except AttributeError: + pass + # create might fail if the request has no user. return response diff --git a/caluma/tests/__snapshots__/test_schema.ambr b/caluma/tests/__snapshots__/test_schema.ambr index 76db8fe6e..8a08f5a3c 100644 --- a/caluma/tests/__snapshots__/test_schema.ambr +++ b/caluma/tests/__snapshots__/test_schema.ambr @@ -1104,6 +1104,7 @@ id: ID! name: String! answer: FilesAnswer + isDraft: Boolean! uploadUrl: String downloadUrl: String metadata: GenericScalar