diff --git a/api/dashboard/lc/dash_lc_view.py b/api/dashboard/lc/dash_lc_view.py index 7be286bd..0e66e09a 100644 --- a/api/dashboard/lc/dash_lc_view.py +++ b/api/dashboard/lc/dash_lc_view.py @@ -1,13 +1,18 @@ +import uuid + from decouple import config from django.core.mail import send_mail from django.db.models import Q +from django.shortcuts import redirect from rest_framework.views import APIView + from api.notification.notifications_utils import NotificationUtils from db.learning_circle import LearningCircle, UserCircleLink from db.user import User from utils.permission import JWTUtils from utils.response import CustomResponse +from utils.utils import send_template_mail, DateTimeUtils from .dash_lc_serializer import LearningCircleSerializer, LearningCircleCreateSerializer, LearningCircleHomeSerializer, \ LearningCircleUpdateSerializer, LearningCircleJoinSerializer, LearningCircleMeetSerializer, \ LearningCircleMainSerializer, LearningCircleNoteSerializer, LearningCircleDataSerializer, \ @@ -44,11 +49,11 @@ def post(self, request, circle_code=None): class LearningCircleCreateApi(APIView): def post(self, request): user_id = JWTUtils.fetch_user_id(request) - serializer = LearningCircleCreateSerializer(data=request.data, context={'user_id': user_id}) + serializer = LearningCircleCreateSerializer(data=request.data, context={ 'user_id': user_id }) if serializer.is_valid(): circle = serializer.save() return CustomResponse(general_message='LearningCircle created successfully', - response={'circle_id': circle.id}).get_success_response() + response={ 'circle_id': circle.id }).get_success_response() return CustomResponse(message=serializer.errors).get_failure_response() @@ -59,7 +64,7 @@ def post(self, request, circle_id): full_name = f'{user.first_name} {user.last_name}' if user.last_name else user.first_name lc = UserCircleLink.objects.filter(circle_id=circle_id, lead=True).first() serializer = LearningCircleJoinSerializer(data=request.data, - context={'user_id': user_id, 'circle_id': circle_id}) + context={ 'user_id': user_id, 'circle_id': circle_id }) if serializer.is_valid(): serializer.save() user = User.objects.filter(id=lc.user.id).first() @@ -113,7 +118,7 @@ class LearningCircleHomeApi(APIView): def get(self, request, circle_id): user_id = JWTUtils.fetch_user_id(request) learning_circle = LearningCircle.objects.filter(id=circle_id).first() - serializer = LearningCircleHomeSerializer(learning_circle, many=False, context={"user_id": user_id}) + serializer = LearningCircleHomeSerializer(learning_circle, many=False, context={ "user_id": user_id }) return CustomResponse(response=serializer.data).get_success_response() def post(self, request, member_id, circle_id): @@ -137,7 +142,7 @@ def patch(self, request, member_id, circle_id): return CustomResponse(general_message='Already evaluated').get_failure_response() serializer = LearningCircleUpdateSerializer(learning_circle_link, data=request.data, - context={'user_id': user_id}) + context={ 'user_id': user_id }) if serializer.is_valid(): serializer.save() is_accepted = request.data.get('is_accepted') @@ -170,7 +175,7 @@ def delete(self, request, circle_id): usr_circle_link = UserCircleLink.objects.filter( circle__id=circle_id, user__id=user_id - ).first() + ).first() if not usr_circle_link: return CustomResponse(general_message='User not part of circle').get_failure_response() @@ -179,7 +184,7 @@ def delete(self, request, circle_id): if ( next_lead := UserCircleLink.objects.filter( circle__id=circle_id, accepted=1 - ) + ) .exclude(user__id=user_id) .order_by('accepted_at') .first() @@ -194,7 +199,7 @@ def delete(self, request, circle_id): if not UserCircleLink.objects.filter(circle__id=circle_id).exists(): if learning_circle := LearningCircle.objects.filter( id=circle_id - ).first(): + ).first(): learning_circle.delete() return CustomResponse(general_message='Learning Circle Deleted').get_success_response() @@ -265,5 +270,88 @@ def post(self, request): from_mail, [user.email], fail_silently=False, + ) + return CustomResponse(general_message='User Invited').get_success_response() + + +class LearningCircleInviteMember(APIView): + """ + Invite a member to a learning circle. + """ + + def post(self, request, circle_id, muid): + """ + POST request to invite a member to a learning circle. + :param request: Request object. + :param circle_id: Learning circle id. + :param muid: Muid of the user. + """ + user = User.objects.filter(muid=muid).first() + if not user: + return CustomResponse(general_message='Muid is Invalid').get_failure_response() + usr_circle_link = UserCircleLink.objects.filter(circle__id=circle_id, user__id=user.id).first() + if usr_circle_link: + if usr_circle_link.accepted: + return CustomResponse(general_message='User already part of circle').get_failure_response() + elif usr_circle_link.is_invited: + return CustomResponse(general_message='User already invited').get_failure_response() + receiver_email = user.email + html_address = ["lc_invitation.html"] + inviter = User.objects.filter(id=JWTUtils.fetch_user_id(request)).first() + inviter_name = inviter.first_name + " " + inviter.last_name + context = { + "circle_name": LearningCircle.objects.filter(id=circle_id).first().name, + "inviter_name": inviter_name, + "circle_id": circle_id, + "muid": muid, + "email": receiver_email, + } + status = send_template_mail( + context=context, + subject="MuLearn - Invitation to learning circle", + address=html_address, ) + if status == 1: + usr_circle_link_new = UserCircleLink( + id=uuid.uuid4(), + circle_id=circle_id, + user=user, + is_invited=True, + accepted=False, + created_at=DateTimeUtils.get_current_utc_time(), + ) + usr_circle_link_new.save() return CustomResponse(general_message='User Invited').get_success_response() + return CustomResponse(general_message='Mail not sent').get_failure_response() + + +class LearningCircleInvitationStatus(APIView): + """ + API to update the invitation status + """ + + def post(self, request, circle_id, muid, status): + """ + PUT request to accept the invitation to join the learning circle, by adding the user data to the lc + :param request: Request object. + :param muid: Muid of the user. + :param circle_id: Learning circle id. + :param status: Status of the invitation. + """ + user = User.objects.filter(muid=muid).first() + if not user: + return CustomResponse(general_message='Muid is Invalid').get_failure_response() + usr_circle_link = UserCircleLink.objects.filter(circle__id=circle_id, user__id=user.id).first() + + if not usr_circle_link: + return CustomResponse(general_message='User not invited').get_failure_response() + + if status == "accepted": + usr_circle_link.accepted = True + usr_circle_link.accepted_at = DateTimeUtils.get_current_utc_time() + usr_circle_link.save() + # return CustomResponse(general_message='User added to circle').get_success_response() + return redirect(f'{domain}/dashboard/learning-circle/') + elif status == "rejected": + usr_circle_link.delete() + return CustomResponse(general_message='User rejected invitation').get_failure_response() \ No newline at end of file diff --git a/api/dashboard/lc/urls.py b/api/dashboard/lc/urls.py index 17e899a5..cd5eb8f8 100644 --- a/api/dashboard/lc/urls.py +++ b/api/dashboard/lc/urls.py @@ -13,9 +13,11 @@ path('create/', dash_lc_view.LearningCircleCreateApi.as_view()), path('meet//', dash_lc_view.LearningCircleMeetAPI.as_view()), path('join//', dash_lc_view.LearningCircleJoinApi.as_view()), + path('member/invite///', dash_lc_view.LearningCircleInviteMember.as_view()), + path('member/invite/status////', dash_lc_view.LearningCircleInvitationStatus.as_view()), path('/', dash_lc_view.LearningCircleHomeApi.as_view()), path('//', dash_lc_view.LearningCircleHomeApi.as_view()), path('lead///', dash_lc_view.LearningCircleLeadTransfer.as_view()), -] + ] \ No newline at end of file diff --git a/api/templates/mails/lc_invitation.html b/api/templates/mails/lc_invitation.html new file mode 100644 index 00000000..d731781c --- /dev/null +++ b/api/templates/mails/lc_invitation.html @@ -0,0 +1,28 @@ +{% extends "mails/base_mail.html" %} {% block content %} +
+
+

+ Learning Circle Invitation +

+

+ Hey there! You have been invited to join the {{ user.circle_name }} by {{ user.inviter_name }}. +

+ +
+
+{% endblock %} \ No newline at end of file diff --git a/db/learning_circle.py b/db/learning_circle.py index 8689b74e..c419b4cc 100644 --- a/db/learning_circle.py +++ b/db/learning_circle.py @@ -31,10 +31,11 @@ class UserCircleLink(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) circle = models.ForeignKey(LearningCircle, on_delete=models.CASCADE) lead = models.BooleanField(default=False) + is_invited = models.BooleanField(default=False) accepted = models.BooleanField() accepted_at = models.DateTimeField(blank=True, null=True) created_at = models.DateTimeField() class Meta: managed = False - db_table = "user_circle_link" + db_table = "user_circle_link" \ No newline at end of file diff --git a/utils/utils.py b/utils/utils.py index 4344f603..37f20725 100644 --- a/utils/utils.py +++ b/utils/utils.py @@ -21,10 +21,10 @@ class CommonUtils: @staticmethod def get_paginated_queryset( - queryset: QuerySet, request, search_fields, sort_fields: dict = None - ) -> QuerySet: + queryset: QuerySet, request, search_fields, sort_fields: dict = None + ) -> QuerySet: if sort_fields is None: - sort_fields = {} + sort_fields = { } page = int(request.query_params.get("pageIndex", 1)) per_page = int(request.query_params.get("perPage", 10)) @@ -34,7 +34,7 @@ def get_paginated_queryset( if search_query: query = Q() for field in search_fields: - query |= Q(**{f"{field}__icontains": search_query}) + query |= Q(**{ f"{field}__icontains": search_query }) queryset = queryset.filter(query) @@ -64,8 +64,8 @@ def get_paginated_queryset( "nextPage": queryset.next_page_number() if queryset.has_next() else None, - }, - } + }, + } @staticmethod def generate_csv(queryset: QuerySet, csv_name: str) -> HttpResponse: @@ -79,7 +79,7 @@ def generate_csv(queryset: QuerySet, csv_name: str) -> HttpResponse: compressed_response = HttpResponse( gzip.compress(response.content), content_type="text/csv", - ) + ) compressed_response[ "Content-Disposition" ] = f'attachment; filename="{csv_name}.csv"' @@ -118,14 +118,13 @@ def format_time(date_time: datetime.datetime) -> datetime.datetime: return date_time.replace(microsecond=0) - @staticmethod def get_start_and_end_of_previous_month(): today = DateTimeUtils.get_current_utc_time() start_date = today.replace(day=1) end_date = start_date.replace( day=1, month=start_date.month % 12 + 1 - ) - timedelta(days=1) + ) - timedelta(days=1) return start_date, end_date @@ -154,7 +153,7 @@ def general_updates(category, action, *values) -> str: for value in values: content = f"{content}<|=|>{value}" url = config("DISCORD_WEBHOOK_LINK") - data = {"content": content} + data = { "content": content } requests.post(url, json=data) @@ -167,7 +166,7 @@ def read_excel_file(self, file_obj): for row in sheet.iter_rows(values_only=True): row_dict = { header.value: cell_value for header, cell_value in zip(sheet[1], row) - } + } rows.append(row_dict) workbook.close() @@ -175,7 +174,7 @@ def read_excel_file(self, file_obj): def send_template_mail( - context: dict, subject: str, address: list[str], attachment: str = None): + context: dict, subject: str, address: list[str], attachment: str = None): """ The function `send_user_mail` sends an email to a user with the provided user data, subject, and address. @@ -192,22 +191,23 @@ def send_template_mail( from_mail = decouple.config("FROM_MAIL") base_url = decouple.config("FR_DOMAIN_NAME") + status = None email_content = render_to_string( - f"mails/{'/'.join(map(str, address))}", {"user": context, "base_url": base_url} - ) + f"mails/{'/'.join(map(str, address))}", { "user": context, "base_url": base_url } + ) if not (mail := getattr(context, "email", None)): mail = context["email"] if attachment is None: - send_mail( + status = send_mail( subject=subject, message=email_content, from_email=from_mail, recipient_list=[mail], html_message=email_content, fail_silently=False, - ) + ) else: email = EmailMessage( @@ -215,7 +215,9 @@ def send_template_mail( body=email_content, from_email=from_mail, to=[context["email"]], - ) + ) email.attach(attachment) email.content_subtype = "html" - email.send() + status = email.send() + + return status \ No newline at end of file