From a05062dc11e53824e237f26e8cc66a7fe1398294 Mon Sep 17 00:00:00 2001
From: Ryan Westfall Hello {{ user.first_name|default:user.username }}, Hello {{ display_name }},
We received a request to reset the password for your account. If you did not
make this request, you can safely ignore this email.
To reset your password, please click the link below: To reset your password, please click the link below or enter the code:
+ {{ code }}
+
+ {% block header_title %}Django App{% endblock %}
+
+
- {% block header_title %}Django App{% endblock %}
-
-
- {{ reset_link }} + {{ reset_link }}
This link will expire in a few hours for security reasons.
-{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/dta_service/core/templates/emails/user_registration_email.html b/dta_service/core/templates/emails/user_registration_email.html index 5340fa0..5a8e2f6 100644 --- a/dta_service/core/templates/emails/user_registration_email.html +++ b/dta_service/core/templates/emails/user_registration_email.html @@ -3,14 +3,13 @@ The Agent!{% endblock %} {% block content %}Hello {{ display_name }},
Thank you for registering with us. We're excited to have you on board!
- Please confirm your email address by clicking the button below to activate - your account: + Please confirm your email address by entering the code below at the link: +
++ {{ code }}
@@ -30,8 +28,6 @@ The Agent!{% endblock %} {% block content %} into your web browser:
-{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/dta_service/core/tests/reproduce_issue.py b/dta_service/core/tests/reproduce_issue.py new file mode 100644 index 0000000..6017d1a --- /dev/null +++ b/dta_service/core/tests/reproduce_issue.py @@ -0,0 +1,27 @@ +from django.test import TestCase +from django.contrib.auth import get_user_model +from rest_framework.test import APIClient +from rest_framework import status +from core.models import SupportCase + +User = get_user_model() + +class SupportCaseReproductionTests(TestCase): + def setUp(self): + self.client = APIClient() + self.user = User.objects.create_user( + email="user@example.com", password="password", user_type="property_owner" + ) + self.case = SupportCase.objects.create( + user=self.user, title="Case 1", description="Desc 1" + ) + + def test_user_can_close_own_case(self): + self.client.force_authenticate(user=self.user) + data = {"status": "closed"} + response = self.client.patch(f"/api/support/cases/{self.case.id}/", data) + + # If this passes (400 Bad Request), it confirms the fix that regular users cannot close cases + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.case.refresh_from_db() + self.assertNotEqual(self.case.status, "closed") diff --git a/dta_service/core/tests/test_support_message_fix.py b/dta_service/core/tests/test_support_message_fix.py new file mode 100644 index 0000000..a355aec --- /dev/null +++ b/dta_service/core/tests/test_support_message_fix.py @@ -0,0 +1,65 @@ +from django.test import TestCase +from django.contrib.auth import get_user_model +from rest_framework.test import APIClient +from rest_framework import status +from core.models import SupportCase, SupportMessage + +User = get_user_model() + + +class SupportMessageFixTests(TestCase): + def setUp(self): + self.client = APIClient() + self.user = User.objects.create_user( + email="user@example.com", password="password", user_type="property_owner" + ) + self.other_user = User.objects.create_user( + email="other@example.com", password="password", user_type="property_owner" + ) + self.support_agent = User.objects.create_user( + email="agent@example.com", password="password", user_type="support_agent" + ) + + self.case = SupportCase.objects.create( + user=self.user, title="My Case", description="Help" + ) + self.other_case = SupportCase.objects.create( + user=self.other_user, title="Other Case", description="Help other" + ) + + def test_create_message_success(self): + """Test that a user can create a message for their own case.""" + self.client.force_authenticate(user=self.user) + data = { + "user": self.user.id, + "text": "My reply", + "support_case": self.case.id, + } + response = self.client.post("/api/support/messages/", data) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(SupportMessage.objects.count(), 1) + self.assertEqual(SupportMessage.objects.first().support_case, self.case) + + def test_create_message_fail_other_case(self): + """Test that a user cannot create a message for another user's case.""" + self.client.force_authenticate(user=self.user) + data = { + "user": self.user.id, + "text": "Intruder reply", + "support_case": self.other_case.id, + } + response = self.client.post("/api/support/messages/", data) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn("You cannot add a message to a support case that does not belong to you.", str(response.data)) + + def test_support_agent_can_reply_to_any_case(self): + """Test that a support agent can create a message for any case.""" + self.client.force_authenticate(user=self.support_agent) + data = { + "user": self.support_agent.id, + "text": "Agent reply", + "support_case": self.case.id, + } + response = self.client.post("/api/support/messages/", data) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(SupportMessage.objects.count(), 1) diff --git a/dta_service/core/tests/verify_serializer_update.py b/dta_service/core/tests/verify_serializer_update.py new file mode 100644 index 0000000..7cb6123 --- /dev/null +++ b/dta_service/core/tests/verify_serializer_update.py @@ -0,0 +1,39 @@ +from django.test import TestCase +from django.contrib.auth import get_user_model +from core.models import SupportCase +from core.serializers import SupportCaseListSerializer + +User = get_user_model() + +class SupportCaseSerializerTests(TestCase): + def setUp(self): + self.user = User.objects.create_user( + email="testuser@example.com", + password="password", + user_type="property_owner", + first_name="Test", + last_name="User", + ) + self.case = SupportCase.objects.create( + user=self.user, title="Test Case", description="Test Description" + ) + + def test_serializer_fields(self): + serializer = SupportCaseListSerializer(self.case) + data = serializer.data + + # Verify user_email is present and correct + self.assertIn("user_email", data) + self.assertEqual(data["user_email"], "testuser@example.com") + + # Verify created_at is present + self.assertIn("created_at", data) + + # Verify user ID is NOT present + self.assertNotIn("user", data) + + # Verify user name fields are present + self.assertIn("user_first_name", data) + self.assertEqual(data["user_first_name"], "Test") + self.assertIn("user_last_name", data) + self.assertEqual(data["user_last_name"], "User") diff --git a/dta_service/core/views/__init__.py b/dta_service/core/views/__init__.py index 3a23bda..2833791 100644 --- a/dta_service/core/views/__init__.py +++ b/dta_service/core/views/__init__.py @@ -7,6 +7,7 @@ from .user import ( PasswordResetRequestView, PasswordResetConfirmView, CheckPasscodeView, + ResendRegistrationEmailView ) from .property_owner import PropertyOwnerViewSet from .vendor import VendorViewSet diff --git a/dta_service/core/views/user.py b/dta_service/core/views/user.py index 5aab6bd..677588d 100644 --- a/dta_service/core/views/user.py +++ b/dta_service/core/views/user.py @@ -46,7 +46,8 @@ class UserRegisterView(generics.CreateAPIView): # Send registration email with OTC try: - EmailService.send_registration_email(user) + email_service = EmailService() + email_service.send_registration_email(user) except Exception as e: print(e) @@ -101,6 +102,7 @@ class LogoutView(APIView): class PasswordResetRequestView(APIView): permission_classes = [permissions.AllowAny] + authentication_classes = () def post(self, request): serializer = PasswordResetRequestSerializer(data=request.data) @@ -115,6 +117,7 @@ class PasswordResetRequestView(APIView): class PasswordResetConfirmView(APIView): permission_classes = [permissions.AllowAny] + authentication_classes = () def post(self, request): serializer = PasswordResetConfirmSerializer(data=request.data) @@ -129,6 +132,7 @@ class PasswordResetConfirmView(APIView): class CheckPasscodeView(APIView): permission_classes = [permissions.AllowAny] + authentication_classes = () def post(self, request): email = request.data.get("email") @@ -175,3 +179,28 @@ class CheckPasscodeView(APIView): return Response( {"valid": False, "detail": "Invalid code."}, status=status.HTTP_200_OK ) + + +class ResendRegistrationEmailView(APIView): + permission_classes = [permissions.AllowAny] + authentication_classes = () + + def post(self, request): + email = request.data.get("email") + + if not email: + return Response( + {"detail": "Email is required."}, status=status.HTTP_400_BAD_REQUEST + ) + + try: + user = User.objects.get(email=email) + email_service = EmailService() + email_service.send_registration_email(user) + return Response( + {"detail": "Registration email sent."}, status=status.HTTP_200_OK + ) + except User.DoesNotExist: + return Response( + {"detail": "User not found."}, status=status.HTTP_404_NOT_FOUND + ) diff --git a/dta_service/dta_service/urls.py b/dta_service/dta_service/urls.py index 1e58df9..1baf809 100644 --- a/dta_service/dta_service/urls.py +++ b/dta_service/dta_service/urls.py @@ -36,6 +36,7 @@ from core.views import ( PropertyCompsProxyView, MLSDetailProxyView, CheckPasscodeView, + ResendRegistrationEmailView, ) from django.conf import settings from django.conf.urls.static import static @@ -80,6 +81,11 @@ urlpatterns = [ CheckPasscodeView.as_view(), name="check_passcode", ), + path( + "api/resend-registration-email/", + ResendRegistrationEmailView.as_view(), + name="resend_registration_email", + ), # API endpoints path("api/attorney/", include("core.urls.attorney")), path("api/document/", include("core.urls.document")),