From 8b6123967ca2550bdbc56bb34266c5ba6ea84edf Mon Sep 17 00:00:00 2001 From: Lymar Volodymyr Date: Mon, 1 Jul 2024 10:39:35 +0200 Subject: [PATCH 1/3] feat: added task to send an email with an attachment --- makedoc/tasks.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/makedoc/tasks.py b/makedoc/tasks.py index 4baeaf9..906bcae 100644 --- a/makedoc/tasks.py +++ b/makedoc/tasks.py @@ -3,6 +3,7 @@ from celery import shared_task from django.conf import settings +from django.core.mail import EmailMessage @shared_task @@ -27,3 +28,28 @@ def delete_files(): return f"Failed to delete {item_path}. Reason {e}" return "Successfully deleted all user folders" + + +@shared_task(bind=True, max_retries=3) +def send_email_with_attachment(self, email, file_path, full_name): + """ + Task to send an email with an attachment. + """ + if not os.path.exists(file_path): + raise FileNotFoundError(f"File {file_path} does not exist") + + email_message = EmailMessage( + subject=f"{full_name}", + body="Documents archive", + from_email=settings.EMAIL_HOST_USER, + to=[email], + ) + + try: + with open(file_path, 'rb') as f: + email_message.attach(os.path.basename(file_path), f.read(), 'application/zip') + + email_message.send() + + except Exception as e: + self.retry(exc=e, countdown=60) From aa9a5dd29d12b7ce3b8a50b3860c4a2d1ef53295 Mon Sep 17 00:00:00 2001 From: Lymar Volodymyr Date: Mon, 1 Jul 2024 10:43:37 +0200 Subject: [PATCH 2/3] feat: added send archive view, serializer, urls names changed for readability --- makedoc/serializers.py | 12 ++++++++++++ makedoc/tasks.py | 4 ++-- makedoc/urls.py | 7 ++++--- makedoc/views.py | 39 ++++++++++++++++++++++++++++++++++++--- 4 files changed, 54 insertions(+), 8 deletions(-) diff --git a/makedoc/serializers.py b/makedoc/serializers.py index 96bcd34..407a945 100644 --- a/makedoc/serializers.py +++ b/makedoc/serializers.py @@ -3,10 +3,19 @@ from rest_framework import serializers +class EmailInputSerializer(serializers.Serializer[Any]): + """ + Serializer for validating email input. + """ + + email = serializers.EmailField() + + class DocumentsSimpleSerializer(serializers.Serializer[Any]): """ A simple serializer for document data. """ + pass @@ -14,6 +23,7 @@ class OrderItemSerializer(serializers.Serializer[Any]): """ A serializer for order items. """ + product_id = serializers.IntegerField() quantity = serializers.FloatField(min_value=0.001) discount = serializers.IntegerField(required=False, default=0, max_value=100) @@ -23,6 +33,7 @@ class DataDocSerializer(serializers.Serializer[Any]): """ A serializer for document data. """ + delivery_type = serializers.CharField() client_id = serializers.IntegerField() items = serializers.ListField(child=OrderItemSerializer()) @@ -35,4 +46,5 @@ class FileNameSerializer(serializers.Serializer[Any]): """ A serializer for file names. """ + file_name = serializers.CharField(required=False, allow_blank=True) diff --git a/makedoc/tasks.py b/makedoc/tasks.py index 906bcae..da16cf1 100644 --- a/makedoc/tasks.py +++ b/makedoc/tasks.py @@ -46,8 +46,8 @@ def send_email_with_attachment(self, email, file_path, full_name): ) try: - with open(file_path, 'rb') as f: - email_message.attach(os.path.basename(file_path), f.read(), 'application/zip') + with open(file_path, "rb") as f: + email_message.attach(os.path.basename(file_path), f.read(), "application/zip") email_message.send() diff --git a/makedoc/urls.py b/makedoc/urls.py index 3d7a290..b0b5003 100644 --- a/makedoc/urls.py +++ b/makedoc/urls.py @@ -1,9 +1,10 @@ from django.urls import path -from .views import DataDocAPIView, CreateDocsAPIView, DownloadDocAPIView +from .views import DataDocAPIView, CreateDocsAPIView, DownloadDocAPIView, SendArchiveAPIView urlpatterns = [ - path("filemake/", CreateDocsAPIView.as_view(), name="file"), - path("downloadfile/", DownloadDocAPIView.as_view(), name="downloadfile"), + path("file-make/", CreateDocsAPIView.as_view(), name="file-make"), + path("download-file/", DownloadDocAPIView.as_view(), name="download-file"), path("data/", DataDocAPIView.as_view(), name="data"), + path("send-archive/", SendArchiveAPIView.as_view(), name="send-archive"), ] diff --git a/makedoc/views.py b/makedoc/views.py index 4fc41b1..3dfdf37 100644 --- a/makedoc/views.py +++ b/makedoc/views.py @@ -11,7 +11,13 @@ from rest_framework.views import APIView from makedoc.services import Documents -from .serializers import DataDocSerializer, DocumentsSimpleSerializer, FileNameSerializer +from .serializers import ( + DataDocSerializer, + DocumentsSimpleSerializer, + FileNameSerializer, + EmailInputSerializer, +) +from .tasks import send_email_with_attachment class CreateDocsAPIView(APIView): @@ -78,8 +84,6 @@ def post(self, request, *args, **kwargs): if not file_name: return Response({"error": "No file found"}, status=status.HTTP_404_NOT_FOUND) - cache.delete(cache_key) - file_path = os.path.join("makedoc", "tempdoc", str(request.user.id), file_name) if not os.path.exists(file_path): @@ -114,3 +118,32 @@ def post(self, request, *args, **kwargs): cache.set(cache_key, json.dumps(validated_data), 120) return Response({"Success": True}, status=status.HTTP_200_OK) + + +class SendArchiveAPIView(APIView): + """ + API endpoint to send an email with an attachment. + """ + + def post(self, request, *args, **kwargs): + serializer = EmailInputSerializer(data=request.data) + if not serializer.is_valid(): + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + email = serializer.validated_data.get("email") + full_name = request.user.full_name + + cache_key = f"last_created_file_user:{request.user.id}" + file_name = cache.get(cache_key) + + if not file_name: + return Response({"error": "No file found in cache"}, status=status.HTTP_404_NOT_FOUND) + + file_path = os.path.join("makedoc", "tempdoc", str(request.user.id), file_name) + + if not os.path.exists(file_path): + return Response({"error": "File path not found!"}, status=status.HTTP_404_NOT_FOUND) + + send_email_with_attachment.delay(email, file_path, full_name=full_name) + + return Response({"message": "Email will be sent shortly."}, status=status.HTTP_200_OK) From 8f1ba2bd077630cc23da594cc4dd429c9a9661de Mon Sep 17 00:00:00 2001 From: Lymar Volodymyr Date: Mon, 1 Jul 2024 10:48:19 +0200 Subject: [PATCH 3/3] fix: fixed tests --- makedoc/tests/test_views_makedoc.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/makedoc/tests/test_views_makedoc.py b/makedoc/tests/test_views_makedoc.py index 257ccaf..72d6586 100644 --- a/makedoc/tests/test_views_makedoc.py +++ b/makedoc/tests/test_views_makedoc.py @@ -33,7 +33,7 @@ def test__data_doc_view__authorized_user_post_data_response_is_correct( @pytest.mark.django_db def test__create_docs_view__authorized_user_get_valid_data(authorized_client, make_test_data): - url = reverse("file") + url = reverse("file-make") cache_key = f"validated_data_{authorized_client.user.id}" cache.set(cache_key, json.dumps(make_test_data), timeout=120) response = authorized_client.get(url) @@ -44,7 +44,7 @@ def test__create_docs_view__authorized_user_get_valid_data(authorized_client, ma @pytest.mark.django_db def test__create_docs_view__authorized_user_get_no_data(authorized_client): - url = reverse("file") + url = reverse("file-make") response = authorized_client.get(url) assert response.status_code == 400 assert response.json() == {"error": "No data found in cache"} @@ -52,7 +52,7 @@ def test__create_docs_view__authorized_user_get_no_data(authorized_client): @pytest.mark.django_db def test__download_doc_view__returns_404_when_no_file_exists(authorized_client): - url = reverse("downloadfile") + url = reverse("download-file") response = authorized_client.post(url, {}, format="json") assert response.status_code == 404 assert response.json() == {"error": "No file found"}