Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

984 admin user actions dropdown #1001

Merged
merged 18 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="uk">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Нове повідомлення</title>
<style>
body {
font-family: Arial, sans-serif;
color: black;
}
.email-container {
padding: 20px;
border: 1px solid #ddd;
border-radius: 5px;
background-color: #f9f9f9;
}
.email-footer {
margin-top: 20px;
font-size: 12px;
color: gray;
}
img {
max-width: 150px;
}
.email-body {
margin-bottom: 20px;
}
</style>
</head>
<body>
<div class="email-container">
<img src="{{ logo_url }}" alt="CraftMerge Logo" />
<div class="email-body">
<p>Доброго дня, {{ user_name }}!</p>
<p>Ви отримали нове повідомлення:</p>
<p><b>Категорія:</b> {{ category }}</p>
<p><b>Повідомлення:</b></p>
<p>{{ message }}</p>
</div>
<div class="email-footer">
<p>З повагою,</p>
<p>Команда CraftMerge</p>
</div>
</div>
</body>
</html>
109 changes: 109 additions & 0 deletions BackEnd/administration/tests/test_send_message.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
from django.core import mail
from django.conf import settings
from rest_framework import status
from rest_framework.test import APITestCase
from authentication.factories import UserFactory
from utils.administration.send_email_notification import send_email_to_user


class TestSendMessageView(APITestCase):
def setUp(self):
self.admin = UserFactory(is_staff=True, is_active=True)
self.user = UserFactory(is_active=True)
self.client.force_authenticate(self.admin)
self.url = f"/api/admin/users/{self.user.id}/send_message/"

def test_send_message_success(self):
data = {
"email": self.user.email,
"category": "Інше",
"message": "Valid message for testing.",
}
response = self.client.post(self.url, data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)

def test_send_message_invalid_data(self):
data = {
"email": self.user.email,
"category": "Iнше",
"message": "Short",
}
response = self.client.post(self.url, data)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

def test_send_message_unauthorized(self):
self.client.logout()
data = {
"email": self.user.email,
"category": "Інше",
"message": "Valid message for testing.",
}
response = self.client.post(self.url, data)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)

def test_send_message_user_not_found(self):
url = "/api/admin/users/9999/send_message/"
data = {
"email": "[email protected]",
"category": "Інше",
"message": "Valid message for testing.",
}
response = self.client.post(url, data)
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)


class TestSendEmailFunctionality(APITestCase):
def setUp(self):
self.user = UserFactory(
name="Test", surname="User", email="[email protected]"
)

def send_email(self, category, message_content, email=None):
email = email if email else self.user.email
return send_email_to_user(
user=self.user,
category=category,
message_content=message_content,
email=email,
)

def test_send_email_success(self):
self.send_email(
category="Інше",
message_content="This is a test message.",
email="[email protected]",
)

self.assertEqual(len(mail.outbox), 1)
email = mail.outbox[0]
self.assertEqual(email.subject, "Адміністратор CraftMerge - Інше")
self.assertIn("This is a test message.", email.body)
self.assertEqual(email.to, ["[email protected]"])
self.assertEqual(email.from_email, settings.EMAIL_HOST_USER)

def test_send_email_empty_message(self):
with self.assertRaises(ValueError) as e:
self.send_email(
category="Інше",
message_content="",
email="[email protected]",
)
self.assertEqual(str(e.exception), "Message content cannot be empty.")

def test_send_email_invalid_email(self):
with self.assertRaises(ValueError) as e:
self.send_email(
category="Інше",
message_content="Test message",
email="invalid_email",
)
self.assertEqual(str(e.exception), "Invalid email address.")

def test_send_email_missing_category(self):
with self.assertRaises(ValueError) as e:
self.send_email(
category="",
message_content="Test message",
email="[email protected]",
)
self.assertEqual(str(e.exception), "Category is required.")
6 changes: 6 additions & 0 deletions BackEnd/administration/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
ModerationEmailView,
FeedbackView,
CreateAdminUserView,
SendMessageView,
)

app_name = "administration"
Expand All @@ -28,4 +29,9 @@
path("contacts/", ContactsView.as_view(), name="contacts"),
path("feedback/", FeedbackView.as_view(), name="feedback"),
path("admin_create/", CreateAdminUserView.as_view(), name="admin-create"),
path(
"users/<pk>/send_message/",
SendMessageView.as_view(),
name="send-message",
),
]
41 changes: 40 additions & 1 deletion BackEnd/administration/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
OpenApiExample,
OpenApiResponse,
)

from rest_framework.generics import (
ListAPIView,
RetrieveUpdateDestroyAPIView,
Expand All @@ -30,6 +29,7 @@
from .permissions import IsStaffUser, IsStaffUserOrReadOnly, IsSuperUser
from .serializers import FeedbackSerializer
from utils.administration.send_email_feedback import send_email_feedback
from utils.administration.send_email_notification import send_email_to_user

from django_filters.rest_framework import DjangoFilterBackend
from .filters import UsersFilter
Expand Down Expand Up @@ -199,3 +199,42 @@ def perform_create(self, serializer):
category = serializer.validated_data["category"]

send_email_feedback(email, message, category)


class SendMessageView(CreateAPIView):
"""
API endpoint for sending a custom email message to a specific user.

This view allows administrators to send a message to a user's registered email.
It validates the request payload, retrieves the user based on the provided ID,
and sends the email using the specified category and message content.
"""

queryset = CustomUser.objects.all()
permission_classes = [IsStaffUser]
serializer_class = FeedbackSerializer

def perform_create(self, serializer):
"""
Handles the email sending logic after successful validation.

This method is executed after the request data has been validated
by the serializer. It retrieves the user, validates their existence,
and sends the email with the provided category and message content.

Parameters:
serializer (FeedbackSerializer): The serializer instance containing
the validated data from the request.
"""
user = self.get_object()
email = serializer.validated_data["email"]
category = serializer.validated_data["category"]
message_content = serializer.validated_data["message"]

send_email_to_user(
user=user,
category=category,
message_content=message_content,
email=email,
sender_name="Адміністратор CraftMerge",
)
58 changes: 58 additions & 0 deletions BackEnd/utils/administration/send_email_notification.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
from django.conf import settings
from django.core.validators import validate_email
from django.core.exceptions import ValidationError

EMAIL_CONTENT_SUBTYPE = "html"
PROTOCOL = "http"


def send_email_to_user(
user,
category,
message_content,
email=None,
sender_name="Адміністратор CraftMerge",
template_name="administration/admin_message_template.html",
):
"""
Sends an email message to the user using the specified template.

:param user: The user object (CustomUser)
:param category: The email category
:param message_content: The message content
:param email: (Optional) The recipient's email
:param sender_name: Name of the sender
:param template_name: The path to the HTML template
"""
if not category:
raise ValueError("Category is required.")
if not message_content.strip():
raise ValueError("Message content cannot be empty.")
try:
validate_email(email or user.email)
except ValidationError:
raise ValueError("Invalid email address.")

context = {
"user_name": f"{user.name} {user.surname}",
"message": message_content,
"category": category,
"sender_name": sender_name,
"logo_url": f"{PROTOCOL}://178.212.110.52/craftMerge-logo.png",
}

email_body = render_to_string(template_name, context)
recipient_email = email if email else user.email

subject = f"{sender_name} - {category}"

email = EmailMultiAlternatives(
subject=subject,
body=email_body,
from_email=settings.EMAIL_HOST_USER,
to=[recipient_email],
)
email.content_subtype = EMAIL_CONTENT_SUBTYPE
email.send(fail_silently=False)
Binary file added FrontEnd/public/craftMerge-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading