From 7777c96061a3ebfb66d5454b8910cb4654eb0fe7 Mon Sep 17 00:00:00 2001 From: Aswanth Vc Date: Sat, 2 Nov 2024 16:20:47 +0530 Subject: [PATCH 1/5] feat(learningcircle): Learning circle v2 apis --- api/dashboard/ig/dash_ig_view.py | 3 + .../learningcircle_serializer.py | 295 ++++++++++++ .../learningcircle/learningcircle_views.py | 452 ++++++++++++++++++ api/dashboard/learningcircle/urls.py | 40 ++ api/dashboard/urls.py | 48 +- db/learning_circle.py | 109 ++--- utils/types.py | 9 + 7 files changed, 855 insertions(+), 101 deletions(-) create mode 100644 api/dashboard/learningcircle/learningcircle_serializer.py create mode 100644 api/dashboard/learningcircle/learningcircle_views.py create mode 100644 api/dashboard/learningcircle/urls.py diff --git a/api/dashboard/ig/dash_ig_view.py b/api/dashboard/ig/dash_ig_view.py index 78895519..908fc1b7 100644 --- a/api/dashboard/ig/dash_ig_view.py +++ b/api/dashboard/ig/dash_ig_view.py @@ -11,6 +11,8 @@ InterestGroupSerializer, InterestGroupCreateUpdateSerializer, ) +from django.utils.decorators import method_decorator +from django.views.decorators.cache import cache_page from api.dashboard.roles.dash_roles_serializer import RoleDashboardSerializer from db.user import Role @@ -260,6 +262,7 @@ def get(self, request, pk): class InterestGroupListApi(APIView): + @method_decorator(cache_page(60 * 10)) def get(self, request): ig = ( InterestGroup.objects.all() diff --git a/api/dashboard/learningcircle/learningcircle_serializer.py b/api/dashboard/learningcircle/learningcircle_serializer.py new file mode 100644 index 00000000..d4a2dc7b --- /dev/null +++ b/api/dashboard/learningcircle/learningcircle_serializer.py @@ -0,0 +1,295 @@ +from datetime import datetime, timedelta, timezone +from db.learning_circle import LearningCircle, CircleMeetingLog, CircleMeetingAttendees +from rest_framework import serializers + +from db.organization import Organization +from db.task import InterestGroup +from utils.types import LearningCircleRecurrenceType + + +class LearningCircleCreateEditSerialzier(serializers.ModelSerializer): + ig = serializers.PrimaryKeyRelatedField( + queryset=InterestGroup.objects.all(), required=True + ) + org = serializers.PrimaryKeyRelatedField( + queryset=Organization.objects.all(), required=False, allow_null=True + ) + + def update(self, instance, validated_data): + instance.ig_id = validated_data.get("ig_id", instance.ig_id) + instance.org_id = validated_data.get("org_id", instance.org_id) + instance.is_recurring = validated_data.get( + "is_recurring", instance.is_recurring + ) + instance.recurrence_type = validated_data.get( + "recurrence_type", instance.recurrence_type + ) + instance.recurrence = validated_data.get("recurrence", instance.recurrence) + instance.updated_at = datetime.now(timezone.utc) + instance.save() + return instance + + def create(self, validated_data): + user_id = self.context.get("user_id") + validated_data["created_by_id"] = user_id + return LearningCircle.objects.create(**validated_data) + + def validate(self, attrs): + is_recurring = attrs.get("is_recurring") + recurrence_type = attrs.get("recurrence_type") + recurrence = attrs.get("recurrence") + if not is_recurring: + attrs["recurrence_type"] = None + attrs["recurrence"] = None + else: + if not recurrence_type or not recurrence: + raise serializers.ValidationError( + "Recurrence type and recurrence are required for recurring learning circles" + ) + if recurrence_type not in LearningCircleRecurrenceType.get_all_values(): + raise serializers.ValidationError("Invalid recurrence type.") + if recurrence_type == LearningCircleRecurrenceType.WEEKLY.value: + if recurrence < 1 or recurrence > 7: + raise serializers.ValidationError( + "Recurrence should be between 1 and 7 for weekly learning circles" + ) + elif recurrence_type == LearningCircleRecurrenceType.MONTHLY.value: + if recurrence < 1 or recurrence > 28: + raise serializers.ValidationError( + "Recurrence should be between 1 and 28 for monthly learning circles" + ) + return super().validate(attrs) + + class Meta: + model = LearningCircle + fields = ["ig", "org", "is_recurring", "recurrence_type", "recurrence"] + + +class LearningCircleListSerializer(serializers.ModelSerializer): + ig = serializers.CharField(source="ig_id.name", read_only=True) + org = serializers.CharField(source="org_id.name", read_only=True, allow_null=True) + created_by = serializers.CharField(source="created_by_id.full_name", read_only=True) + next_meetup = serializers.SerializerMethodField() + + def _get_next_weekday(self, target_day: int): + today = datetime.now() + current_day = today.isoweekday() + 2 + current_day = current_day if current_day <= 7 else 1 + days_until_next = ((target_day - current_day + 7) % 7) + 1 + days_until_next = days_until_next or 7 + next_day_date = today + timedelta(days=days_until_next) + return next_day_date.date() + + def _get_month_day(self, target_day: int): + today = datetime.now() + current_day = today.day + current_month = today.month + current_year = today.year + if current_day >= target_day: + current_month += 1 + if current_month > 12: + current_month = 1 + current_year += 1 + return datetime(current_year, current_month, target_day).date() + + def get_next_meetup(self, obj): + next_meetup = ( + CircleMeetingLog.objects.filter(circle_id=obj.id) + .filter(meet_time__gte=datetime.now(timezone.utc)) + .order_by("-meet_time") + .first() + ) + if next_meetup: + return { + "is_scheduled": True, + "id": next_meetup.id, + "title": next_meetup.title, + "meet_time": next_meetup.meet_time, + "meet_place": next_meetup.meet_place, + "is_report_submitted": next_meetup.is_report_submitted, + } + if not obj.is_recurring: + return None + if obj.recurrence_type == LearningCircleRecurrenceType.WEEKLY.value: + return { + "is_scheduled": False, + "meet_time": self._get_next_weekday(obj.recurrence), + } + if obj.recurrence_type == LearningCircleRecurrenceType.MONTHLY.value: + return { + "is_scheduled": False, + "meet_time": self._get_month_day(obj.recurrence), + } + return {"is_scheduled": False, "meet_time": None} + + class Meta: + model = LearningCircle + fields = [ + "id", + "ig", + "org", + "is_recurring", + "recurrence_type", + "recurrence", + "created_by", + "next_meetup", + ] + + +class CircleMeetingLogCreateEditSerializer(serializers.ModelSerializer): + circle_id = serializers.PrimaryKeyRelatedField( + queryset=LearningCircle.objects.all(), required=True + ) + + def update(self, instance, validated_data): + instance.title = validated_data.get("title", instance.title) + instance.is_report_needed = validated_data.get( + "is_report_needed", instance.is_report_needed + ) + instance.report_description = validated_data.get( + "report_description", instance.report_description + ) + instance.coord_x = validated_data.get("coord_x", instance.coord_x) + instance.coord_y = validated_data.get("coord_y", instance.coord_y) + instance.meet_place = validated_data.get("meet_place", instance.meet_place) + instance.meet_time = validated_data.get("meet_time", instance.meet_time) + instance.duration = validated_data.get("duration", instance.duration) + instance.updated_at = datetime.now(timezone.utc) + instance.save() + return instance + + def create(self, validated_data): + user_id = self.context.get("user_id") + meet_code = self.context.get("meet_code") + validated_data["created_by_id"] = user_id + validated_data["meet_code"] = meet_code + meet = CircleMeetingLog.objects.create(**validated_data) + CircleMeetingAttendees.objects.create( + meet_id=meet, user_id_id=user_id, is_joined=True, joined_at=datetime.now() + ) + return meet + + def validate(self, attrs): + is_report_needed = attrs.get("is_report_needed") + report_description = attrs.get("report_description") + if not is_report_needed: + attrs["report_description"] = None + else: + if not report_description: + raise serializers.ValidationError("Report description is required") + return super().validate(attrs) + + def validate_circle_id(self, value): + if CircleMeetingLog.objects.filter( + circle_id=value, is_report_submitted=False + ).exists(): + raise serializers.ValidationError( + "There is already an ongoing meeting for this learning circle" + ) + return value + + class Meta: + model = CircleMeetingLog + fields = [ + "circle_id", + "title", + "is_report_needed", + "report_description", + "coord_x", + "coord_y", + "meet_place", + "meet_time", + "duration", + ] + + +class CircleMeetingLogListSerializer(serializers.ModelSerializer): + circle = serializers.CharField(source="circle_id.id", read_only=True) + created_by = serializers.CharField(source="created_by_id.full_name", read_only=True) + is_started = serializers.SerializerMethodField() + is_ended = serializers.SerializerMethodField() + + def get_is_started(self, obj): + return obj.meet_time <= datetime.now(timezone.utc) + + def get_is_ended(self, obj): + return (obj.meet_time + timedelta(hours=obj.duration + 1)) <= datetime.now( + timezone.utc + ) + + class Meta: + model = CircleMeetingLog + fields = [ + "id", + "circle", + "meet_code", + "title", + "is_report_needed", + "report_description", + "coord_x", + "coord_y", + "meet_place", + "meet_time", + "duration", + "created_by", + "is_started", + "is_ended", + "is_report_submitted", + ] + + +class CircleMeetingAttendeeSerializer(serializers.ModelSerializer): + class Meta: + model = CircleMeetingAttendees + fields = ["is_joined", "is_report_submitted", "is_lc_approved"] + + +class CircleMeetupInfoSerializer(serializers.ModelSerializer): + title = serializers.CharField(read_only=True) + is_report_needed = serializers.BooleanField(read_only=True) + report_description = serializers.CharField(read_only=True) + coord_x = serializers.FloatField(read_only=True) + coord_y = serializers.FloatField(read_only=True) + meet_place = serializers.CharField(read_only=True) + meet_time = serializers.DateTimeField(read_only=True) + duration = serializers.IntegerField(read_only=True) + is_approved = serializers.BooleanField(read_only=True) + is_started = serializers.SerializerMethodField() + is_ended = serializers.SerializerMethodField() + attendee = serializers.SerializerMethodField() + + def get_is_started(self, obj): + return obj.meet_time <= datetime.now(timezone.utc) + + def get_is_ended(self, obj): + return (obj.meet_time + timedelta(hours=obj.duration + 1)) <= datetime.now( + timezone.utc + ) + + def get_attendee(self, obj): + if user_id := self.context.get("user_id"): + return CircleMeetingAttendeeSerializer( + obj.circle_meeting_attendance_meet_id.filter( + user_id_id=user_id + ).first(), + many=False, + ).data + return None + + class Meta: + model = CircleMeetingLog + fields = [ + "id", + "title", + "is_report_needed", + "report_description", + "coord_x", + "coord_y", + "meet_place", + "meet_time", + "duration", + "is_approved", + "is_started", + "is_ended", + "attendee", + ] diff --git a/api/dashboard/learningcircle/learningcircle_views.py b/api/dashboard/learningcircle/learningcircle_views.py new file mode 100644 index 00000000..17baecff --- /dev/null +++ b/api/dashboard/learningcircle/learningcircle_views.py @@ -0,0 +1,452 @@ +from datetime import datetime, timedelta, timezone +import requests +from rest_framework.views import APIView +from db.learning_circle import LearningCircle, CircleMeetingLog, CircleMeetingAttendees +from db.user import UserInterests +from utils.permission import CustomizePermission, JWTUtils +from utils.response import CustomResponse +from utils.utils import generate_code +from .learningcircle_serializer import ( + CircleMeetingLogCreateEditSerializer, + CircleMeetingLogListSerializer, + CircleMeetupInfoSerializer, + LearningCircleCreateEditSerialzier, + LearningCircleListSerializer, +) +from django.db.models import Q + + +class LearningCircleView(APIView): + permission_classes = [CustomizePermission] + + def get(self, request): + learning_circles = ( + LearningCircle.objects.filter(created_by_id=JWTUtils.fetch_user_id(request)) + .order_by("-created_at", "-updated_at") + .select_related("ig", "org", "created_by") + ) + serializer = LearningCircleListSerializer(learning_circles, many=True) + return CustomResponse( + general_message="Learning Circles fetched successfully", + response=serializer.data, + ).get_success_response() + + def post(self, request): + user_id = JWTUtils.fetch_user_id(request) + serializer = LearningCircleCreateEditSerialzier( + data=request.data, context={"user_id": user_id} + ) + if not serializer.is_valid(): + return CustomResponse( + general_message="Learning Circle creation failed", + response=serializer.errors, + ).get_failure_response() + serializer.save() + return CustomResponse( + general_message="Learning Circle created successfully" + ).get_success_response() + + def put(self, request, circle_id: str): + user_id = JWTUtils.fetch_user_id(request) + learning_circle = LearningCircle.objects.get(id=circle_id) + if learning_circle.created_by_id != user_id: + return CustomResponse( + general_message="You do not have permission to edit this Learning Circle" + ).get_failure_response() + serializer = LearningCircleCreateEditSerialzier( + learning_circle, + data=request.data, + context={"user_id": user_id}, + partial=True, + ) + if not serializer.is_valid(): + return CustomResponse( + general_message="Learning Circle update failed", + response=serializer.errors, + ).get_failure_response() + serializer.update(learning_circle, serializer.validated_data) + return CustomResponse( + general_message="Learning Circle updated successfully" + ).get_success_response() + + def delete(self, request, circle_id: str): + user_id = JWTUtils.fetch_user_id(request) + learning_circle = LearningCircle.objects.get(id=circle_id) + if learning_circle.created_by_id != user_id: + return CustomResponse( + general_message="You do not have permission to delete this Learning Circle" + ).get_failure_response() + learning_circle.delete() + return CustomResponse( + general_message="Learning Circle deleted successfully" + ).get_success_response() + + +class LearningCircleMeetingView(APIView): + permission_classes = [CustomizePermission] + + def get(self, request, circle_id: str): + learning_circle = LearningCircle.objects.get(id=circle_id) + circle_meetings = CircleMeetingLog.objects.filter(circle_id=learning_circle) + serializer = CircleMeetingLogListSerializer(circle_meetings, many=True) + return CustomResponse( + general_message="Circle Meetings fetched successfully", + response=serializer.data, + ).get_success_response() + + def post(self, request): + user_id = JWTUtils.fetch_user_id(request) + meet_code = generate_code() + serializer = CircleMeetingLogCreateEditSerializer( + data=request.data, context={"user_id": user_id, "meet_code": meet_code} + ) + if not serializer.is_valid(): + return CustomResponse( + general_message="Circle Meeting creation failed", + response=serializer.errors, + ).get_failure_response() + serializer.save() + return CustomResponse( + general_message="Circle Meeting created successfully" + ).get_success_response() + + def put(self, request, meet_id: str): + user_id = JWTUtils.fetch_user_id(request) + circle_meeting = CircleMeetingLog.objects.get(id=meet_id) + if circle_meeting.created_by_id != user_id: + return CustomResponse( + general_message="You do not have permission to edit this Circle Meeting" + ).get_failure_response() + serializer = CircleMeetingLogCreateEditSerializer( + circle_meeting, + data=request.data, + context={"user_id": user_id}, + partial=True, + ) + if not serializer.is_valid(): + return CustomResponse( + general_message="Circle Meeting update failed", + response=serializer.errors, + ).get_failure_response() + serializer.update(circle_meeting, serializer.validated_data) + return CustomResponse( + general_message="Circle Meeting updated successfully" + ).get_success_response() + + def delete(self, request, meet_id: str): + user_id = JWTUtils.fetch_user_id(request) + circle_meeting = CircleMeetingLog.objects.select_related( + "created_by", "circle_id" + ).get(id=meet_id) + if circle_meeting.created_by_id != user_id: + return CustomResponse( + general_message="You do not have permission to delete this Circle Meeting" + ).get_failure_response() + circle_meeting.delete() + return CustomResponse( + general_message="Circle Meeting deleted successfully" + ).get_success_response() + + +class LearningCircleJoinAPI(APIView): + permission_classes = [CustomizePermission] + + def post(self, request, meet_id: str): + user_id = JWTUtils.fetch_user_id(request) + circle_meeting = CircleMeetingLog.objects.get(id=meet_id) + is_meet_started = circle_meeting.meet_time <= datetime.now(timezone.utc) + is_meet_ended = ( + circle_meeting.meet_time + timedelta(hours=circle_meeting.duration + 2) + ) <= datetime.now(timezone.utc) + if is_meet_ended: + return CustomResponse( + general_message="The Circle Meeting has already ended" + ).get_failure_response() + is_joined = False + joined_at = None + if is_meet_started: + meet_code = request.data.get("meet_code") + if not meet_code or meet_code != circle_meeting.meet_code: + return CustomResponse( + general_message="Invalid Circle Meeting code" + ).get_failure_response() + is_joined = True + joined_at = datetime.now(timezone.utc) + attendee = CircleMeetingAttendees.objects.filter( + meet_id=circle_meeting, user_id_id=user_id + ).first() + if attendee: + if attendee.is_joined: + return CustomResponse( + general_message="You have already joined the Circle Meeting" + ).get_failure_response() + if not is_meet_started: + return CustomResponse( + general_message="You can only join the Circle Meeting after it has started" + ).get_failure_response() + attendee.is_joined = is_joined + attendee.joined_at = joined_at + attendee.save() + return CustomResponse( + general_message="You have successfully joined the Circle Meeting" + ).get_success_response() + CircleMeetingAttendees.objects.create( + meet_id=circle_meeting, + user_id_id=user_id, + is_joined=is_joined, + joined_at=joined_at, + ) + return CustomResponse( + general_message=( + "You have successfully joined the Circle Meeting" + if is_joined + else "Saved Learning Circle" + ) + ).get_success_response() + + def delete(self, request, meet_id: str): + user_id = JWTUtils.fetch_user_id(request) + circle_meeting = CircleMeetingLog.objects.get(id=meet_id) + attendee = CircleMeetingAttendees.objects.filter( + meet_id=circle_meeting, user_id_id=user_id + ).first() + if not attendee: + return CustomResponse( + general_message="You have not joined the Circle Meeting" + ).get_failure_response() + if attendee.is_report_submitted: + return CustomResponse( + general_message="You have already submitted the report" + ).get_failure_response() + attendee.delete() + return CustomResponse( + general_message="You have successfully left the Circle Meeting" + ).get_success_response() + + +class LearningCircleAttendeeReportAPI(APIView): + def get(self, request, meet_id): + user_id = JWTUtils.fetch_user_id(request) + circle_meeting = CircleMeetingLog.objects.get(id=meet_id) + attendee = CircleMeetingAttendees.objects.filter( + meet_id=circle_meeting, user_id_id=user_id + ).first() + if not attendee or not attendee.is_joined: + return CustomResponse( + general_message="You have not joined the Circle Meeting" + ).get_failure_response() + if not attendee.is_report_submitted: + return CustomResponse( + general_message="You have not submitted the report" + ).get_failure_response() + return CustomResponse( + general_message="Report fetched successfully", + response={ + "report": attendee.report_text, + "report_link": attendee.report_link, + }, + ).get_success_response() + + def post(self, request, meet_id): + user_id = JWTUtils.fetch_user_id(request) + circle_meeting = CircleMeetingLog.objects.get(id=meet_id) + attendee = CircleMeetingAttendees.objects.filter( + meet_id=circle_meeting, user_id_id=user_id + ).first() + if not attendee or not attendee.is_joined: + return CustomResponse( + general_message="You have not joined the Circle Meeting" + ).get_failure_response() + if attendee.is_report_submitted: + return CustomResponse( + general_message="You have already submitted the report" + ).get_failure_response() + report = request.data.get("report") + report_link = request.data.get("report_link") + if not report and not report_link: + return CustomResponse( + general_message="Please provide the report or report link" + ).get_failure_response() + attendee.is_report_submitted = True + attendee.report_text = report + attendee.report_link = report_link + attendee.save() + return CustomResponse( + general_message="You have successfully submitted the report" + ).get_success_response() + + def delete(self, request, meet_id): + user_id = JWTUtils.fetch_user_id(request) + circle_meeting = CircleMeetingLog.objects.get(id=meet_id) + attendee = CircleMeetingAttendees.objects.filter( + meet_id=circle_meeting, user_id_id=user_id + ).first() + if not attendee or not attendee.is_joined: + return CustomResponse( + general_message="You have not joined the Circle Meeting" + ).get_failure_response() + if not attendee.is_report_submitted: + return CustomResponse( + general_message="You have not submitted the report" + ).get_failure_response() + if circle_meeting.is_report_submitted: + return CustomResponse( + general_message="The report has already been submitted by the Circle Meeting organizer" + ).get_failure_response() + attendee.is_report_submitted = False + attendee.report_text = None + attendee.report_link = None + attendee.save() + return CustomResponse( + general_message="You have successfully deleted the report" + ).get_success_response() + + +class LearningCircleReportAPI(APIView): + permission_classes = [CustomizePermission] + + def get(self, request, meet_id): + user_id = JWTUtils.fetch_user_id(request) + circle_meeting = CircleMeetingLog.objects.get(id=meet_id) + if circle_meeting.created_by_id != user_id: + return CustomResponse( + general_message="You do not have permission to view the report" + ).get_failure_response() + attendees = CircleMeetingAttendees.objects.filter( + meet_id=circle_meeting, is_joined=True + ) + return CustomResponse( + general_message="Report fetched successfully", + response={ + "is_report_submitted": circle_meeting.is_report_submitted, + "report": circle_meeting.report_text, + "attendees": { + attendee.user_id_id: { + "is_lc_approved": attendee.is_lc_approved, + "report": attendee.report_text, + "report_link": attendee.report_link, + } + for attendee in attendees + }, + }, + ).get_success_response() + + def post(self, request, meet_id): + user_id = JWTUtils.fetch_user_id(request) + circle_meeting = CircleMeetingLog.objects.get(id=meet_id) + if circle_meeting.created_by_id != user_id: + return CustomResponse( + general_message="You do not have permission to submit the report" + ).get_failure_response() + if circle_meeting.is_report_submitted: + return CustomResponse( + general_message="The report has already been submitted" + ).get_failure_response() + attendees = request.data.get("attendees") + if not attendees or len(attendees) < 2: + return CustomResponse( + general_message="Need minimum of 2 attendees." + ).get_failure_response() + report = request.data.get("report") + if not report: + return CustomResponse( + general_message="Please provide the report" + ).get_failure_response() + for attendee_id, approved in attendees.items(): + attendee = CircleMeetingAttendees.objects.filter( + meet_id=circle_meeting, user_id_id=attendee_id + ).first() + if not attendee or not attendee.is_joined: + return CustomResponse( + general_message="Attendee has not joined the Circle Meeting" + ).get_failure_response() + if not attendee.is_report_submitted: + return CustomResponse( + general_message="Attendee has not submitted the report" + ).get_failure_response() + attendee.is_lc_approved = approved + attendee.save() + circle_meeting.is_report_submitted = True + circle_meeting.report_text = report + circle_meeting.save() + return CustomResponse( + general_message="The report has been submitted successfully" + ).get_success_response() + + def delete(self, request, meet_id): + user_id = JWTUtils.fetch_user_id(request) + circle_meeting = CircleMeetingLog.objects.get(id=meet_id) + if circle_meeting.created_by_id != user_id: + return CustomResponse( + general_message="You do not have permission to delete the report" + ).get_failure_response() + if not circle_meeting.is_report_submitted: + return CustomResponse( + general_message="The report has not been submitted" + ).get_failure_response() + if circle_meeting.is_approved: + return CustomResponse( + general_message="The report has been approved by the Learning Circle organizer" + ).get_failure_response() + attendees = CircleMeetingAttendees.objects.filter( + meet_id=circle_meeting, is_joined=True + ) + for attendee in attendees: + attendee.is_lc_approved = False + attendee.save() + circle_meeting.is_report_submitted = False + circle_meeting.report_text = None + circle_meeting.save() + return CustomResponse( + general_message="The report has been deleted successfully" + ).get_success_response() + + +class LearningCircleMeetingListAPI(APIView): + + def get(self, request): + request_data = request.query_params + categories = request_data.get("categories", []) + # no_location = request_data.get("no_location") + lat = request_data.get("lat") + lon = request_data.get("lon") + user_id = None + if JWTUtils.is_jwt_authenticated(request) and not categories: + user_id = JWTUtils.fetch_user_id(request) + interests = UserInterests.objects.filter(user_id=user_id).first() + if interests: + categories = interests.choosen_interests + # if not no_location and not lat and not lon: + # user_ip = request.META.get("REMOTE_ADDR") + # ipinfo_api_url = f"http://ip-api.com/json/{user_ip}?fields=status,lat,lon" + # response = requests.get(ipinfo_api_url) + # location_data = response.json() + # if location_data.get("status") == "success": + # lat = location_data.get("lat") + # lon = location_data.get("lon") + user_meetups = ( + [] + if not user_id + else CircleMeetingAttendees.objects.filter( + user_id=user_id, is_report_submitted=False + ).values_list("meet_id_id", flat=True) + ) + meetings = ( + CircleMeetingLog.objects.filter( + Q(meet_time__gte=datetime.now(timezone.utc) - timedelta(hours=2)) + | Q(id__in=user_meetups) + ).order_by("meet_time") + # .prefetch_related("circle_meeting_attendance_meet_id") + ) + if categories and type(categories) == list: + meetings = meetings.select_related("circle_id__ig").filter( + circle_id__ig__category__in=categories + ) + + serializer = CircleMeetupInfoSerializer( + meetings, many=True, context={"user_id": user_id} + ) + return CustomResponse( + general_message="Meetings fetched successfully", + response=serializer.data, + ).get_success_response() diff --git a/api/dashboard/learningcircle/urls.py b/api/dashboard/learningcircle/urls.py new file mode 100644 index 00000000..7a685635 --- /dev/null +++ b/api/dashboard/learningcircle/urls.py @@ -0,0 +1,40 @@ +from django.urls import path + +from . import learningcircle_views + +urlpatterns = [ + path("create/", learningcircle_views.LearningCircleView.as_view()), + path("list/", learningcircle_views.LearningCircleView.as_view()), + path("edit//", learningcircle_views.LearningCircleView.as_view()), + path("delete//", learningcircle_views.LearningCircleView.as_view()), + path("meeting/create/", learningcircle_views.LearningCircleMeetingView.as_view()), + path("meeting/list/", learningcircle_views.LearningCircleMeetingListAPI.as_view()), + path( + "meeting/list//", + learningcircle_views.LearningCircleMeetingView.as_view(), + ), + path( + "meeting/edit//", + learningcircle_views.LearningCircleMeetingView.as_view(), + ), + path( + "meeting/delete//", + learningcircle_views.LearningCircleMeetingView.as_view(), + ), + path( + "meeting/join//", + learningcircle_views.LearningCircleJoinAPI.as_view(), + ), + path( + "meeting/leave//", + learningcircle_views.LearningCircleJoinAPI.as_view(), + ), + path( + "meeting/attendee-report//", + learningcircle_views.LearningCircleAttendeeReportAPI.as_view(), + ), + path( + "meeting/report//", + learningcircle_views.LearningCircleReportAPI.as_view(), + ), +] diff --git a/api/dashboard/urls.py b/api/dashboard/urls.py index 03b361f7..02721ca8 100644 --- a/api/dashboard/urls.py +++ b/api/dashboard/urls.py @@ -2,29 +2,27 @@ # app_name will help us do a reverse look-up latter. urlpatterns = [ - path('user/', include('api.dashboard.user.urls')), - path('zonal/', include('api.dashboard.zonal.urls')), - path('district/', include('api.dashboard.district.urls')), - path('campus/', include('api.dashboard.campus.urls')), - path('roles/', include('api.dashboard.roles.urls')), - path('ig/', include('api.dashboard.ig.urls')), - path('task/', include('api.dashboard.task.urls')), - path('profile/', include('api.dashboard.profile.urls')), - path('lc/', include('api.dashboard.lc.urls')), - path('referral/', include('api.dashboard.referral.urls')), - path('college/', include('api.dashboard.college.urls')), - path('karma-voucher/', include('api.dashboard.karma_voucher.urls')), - path('location/', include('api.dashboard.location.urls')), - path('organisation/', include('api.dashboard.organisation.urls')), - path('dynamic-management/', include('api.dashboard.dynamic_management.urls')), - path('error-log/', include('api.dashboard.error_log.urls')), - - path('affiliation/', include('api.dashboard.affiliation.urls')), - path('channels/', include('api.dashboard.channels.urls')), - path('discord-moderator/', include('api.dashboard.discord_moderator.urls')), - path('events/', include('api.dashboard.events.urls')), - - path('coupon/', include('api.dashboard.coupon.urls')), - - path('projects/', include('api.dashboard.projects.urls')), + path("user/", include("api.dashboard.user.urls")), + path("zonal/", include("api.dashboard.zonal.urls")), + path("district/", include("api.dashboard.district.urls")), + path("campus/", include("api.dashboard.campus.urls")), + path("roles/", include("api.dashboard.roles.urls")), + path("ig/", include("api.dashboard.ig.urls")), + path("task/", include("api.dashboard.task.urls")), + path("profile/", include("api.dashboard.profile.urls")), + # path("lc/", include("api.dashboard.lc.urls")), + path("learningcircle/", include("api.dashboard.learningcircle.urls")), + path("referral/", include("api.dashboard.referral.urls")), + path("college/", include("api.dashboard.college.urls")), + path("karma-voucher/", include("api.dashboard.karma_voucher.urls")), + path("location/", include("api.dashboard.location.urls")), + path("organisation/", include("api.dashboard.organisation.urls")), + path("dynamic-management/", include("api.dashboard.dynamic_management.urls")), + path("error-log/", include("api.dashboard.error_log.urls")), + path("affiliation/", include("api.dashboard.affiliation.urls")), + path("channels/", include("api.dashboard.channels.urls")), + path("discord-moderator/", include("api.dashboard.discord_moderator.urls")), + path("events/", include("api.dashboard.events.urls")), + path("coupon/", include("api.dashboard.coupon.urls")), + path("projects/", include("api.dashboard.projects.urls")), ] diff --git a/db/learning_circle.py b/db/learning_circle.py index 23a9c5e8..3ffd7737 100644 --- a/db/learning_circle.py +++ b/db/learning_circle.py @@ -11,29 +11,22 @@ # noinspection PyPep8 class LearningCircle(models.Model): - id = models.CharField(primary_key=True, max_length=36, default=lambda: str(uuid.uuid4())) - name = models.CharField(max_length=255, unique=True) - circle_code = models.CharField(unique=True, max_length=36) - ig = models.ForeignKey(InterestGroup, on_delete=models.CASCADE, blank=True, - related_name="learning_circle_ig") - org = models.ForeignKey(Organization, on_delete=models.CASCADE, blank=True, null=True, - related_name="learning_circle_org") - meet_place = models.CharField(max_length=255, blank=True, null=True) - meet_time = models.CharField(max_length=10, blank=True, null=True) - day = models.CharField(max_length=20, blank=True, null=True) + id = models.CharField(primary_key=True, max_length=36, default=lambda: str(uuid.uuid4())) + ig = models.ForeignKey(InterestGroup, on_delete=models.CASCADE, related_name="learning_circle_ig_id") + org = models.ForeignKey(Organization, on_delete=models.CASCADE, related_name="learning_circle_org_id", null=True, blank=True) + is_recurring = models.BooleanField(default=True, null=False) + recurrence_type = models.CharField(max_length=10, blank=True, null=True) + recurrence = models.IntegerField(blank=True, null=True) note = models.CharField(max_length=500, blank=True, null=True) - updated_by = models.ForeignKey(User, on_delete=models.SET(settings.SYSTEM_ADMIN_ID), db_column="updated_by", - related_name="learning_circle_updated_by") - updated_at = models.DateTimeField(auto_now=True) created_by = models.ForeignKey(User, on_delete=models.SET(settings.SYSTEM_ADMIN_ID), db_column="created_by", related_name="learning_circle_created_by") created_at = models.DateTimeField(auto_now=True) + updated_at = models.DateTimeField(auto_now=True) class Meta: managed = False db_table = "learning_circle" - class UserCircleLink(models.Model): id = models.CharField(primary_key=True, max_length=36) user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='user_circle_link_user') @@ -48,79 +41,43 @@ class Meta: managed = False db_table = "user_circle_link" - class CircleMeetingLog(models.Model): - id = models.CharField(primary_key=True, max_length=36, default=lambda: str(uuid.uuid4()), unique=True) - meet_code = models.CharField(max_length=6, default=generate_code,null=False,blank=False) - circle = models.ForeignKey(LearningCircle, on_delete=models.CASCADE, - related_name='circle_meeting_log_learning_circle') - title = models.CharField(max_length=100, null=False, blank=False) - meet_time = models.DateTimeField(null=True, blank=False) - meet_place = models.CharField(max_length=255, blank=True, null=True) - location = models.CharField(max_length=200, blank=False, null=False) - day = models.CharField(max_length=20, null=True, blank=False) - agenda = models.CharField(max_length=2000) - pre_requirements = models.CharField(max_length=1000,null=True,blank=True) - is_public = models.BooleanField(default=True, null=False) - max_attendees = models.IntegerField(default=-1, null=False, blank=False) - is_online = models.BooleanField(default=False, null=False) - report_text = models.CharField(max_length=1000, null=True, blank=True) - is_verified = models.BooleanField(default=False, null=False) - is_started = models.BooleanField(default=False, null=False) + id = models.CharField(primary_key=True, max_length=36, default=lambda: str(uuid.uuid4())) + circle_id = models.ForeignKey(LearningCircle, on_delete=models.CASCADE, db_column="circle_id", related_name="circle_meeting_log_circle_id") + meet_code = models.CharField(max_length=10, blank=True, null=True) + title = models.CharField(max_length=100, blank=False, null=False) + is_report_needed = models.BooleanField(default=True, null=False) + report_description = models.CharField(max_length=1000, blank=True, null=True) + coord_x = models.FloatField(blank=False, null=False) + coord_y = models.FloatField(blank=False, null=False) + meet_place = models.CharField(max_length=100, blank=False, null=False) + meet_time = models.DateTimeField(blank=False, null=False) + duration = models.IntegerField(blank=False, null=False) is_report_submitted = models.BooleanField(default=False, null=False) - images = models.ImageField(max_length=200, upload_to='lc/meet-report') - created_by = models.ForeignKey(User, on_delete=models.SET(settings.SYSTEM_ADMIN_ID), db_column='created_by', - related_name='circle_meeting_log_created_by') + report_text = models.CharField(max_length=1000, blank=True, null=True) + is_approved = models.BooleanField(default=False, null=False) + created_by = models.ForeignKey(User, on_delete=models.CASCADE, db_column="created_by", related_name="circle_meeting_log_created_by") created_at = models.DateTimeField(auto_now=True) - updated_by = models.ForeignKey(User, on_delete=models.SET(settings.SYSTEM_ADMIN_ID), db_column='updated_by', - related_name='circle_meeting_log_updated_by') updated_at = models.DateTimeField(auto_now=True) class Meta: managed = False - db_table = 'circle_meeting_log' + db_table = "circle_meeting_log" -class CircleMeetAttendees(models.Model): - id = models.CharField(primary_key=True, max_length=36, default=lambda: str(uuid.uuid4()), unique=True) - meet = models.ForeignKey(CircleMeetingLog, on_delete=models.CASCADE, related_name='circle_meet_attendees_meet') - user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='circle_meet_attendees_user') - note = models.CharField(max_length=1000, blank=True, null=True) + +class CircleMeetingAttendees(models.Model): + id = models.CharField(primary_key=True, max_length=36, default=lambda: str(uuid.uuid4())) + meet_id = models.ForeignKey(CircleMeetingLog, on_delete=models.CASCADE, db_column="meet_id", related_name="circle_meeting_attendance_meet_id") + user_id = models.ForeignKey(User, on_delete=models.CASCADE, db_column="user_id", related_name="circle_meeting_attendance_user_id") + is_joined = models.BooleanField(default=False, null=False) + joined_at = models.DateTimeField(blank=True, null=True) is_report_submitted = models.BooleanField(default=False, null=False) - report = models.CharField(max_length=500, null=True, blank=True) - lc_member_rating = models.IntegerField(null=True) - kal = models.ForeignKey(KarmaActivityLog,on_delete=models.SET_NULL, related_name="circle_meet_attendees_kal", null=True) - karma_given = models.IntegerField(null=True) - joined_at = models.DateTimeField(null=True, blank=False) - approved_by = models.ForeignKey(User, on_delete=models.SET(settings.SYSTEM_ADMIN_ID), db_column='approved_by', - related_name='circle_meet_attendees_approved_by') + report_text = models.CharField(max_length=1000, blank=True, null=True) + report_link = models.CharField(max_length=100, blank=True, null=True) + is_lc_approved = models.BooleanField(default=False, null=False) created_at = models.DateTimeField(auto_now=True) updated_at = models.DateTimeField(auto_now=True) class Meta: managed = False - db_table = 'circle_meet_attendees' - -class CircleMeetTasks(models.Model): - id = models.CharField(primary_key=True, max_length=36, default=lambda: str(uuid.uuid4()), unique=True) - meet = models.ForeignKey(CircleMeetingLog, null=True, on_delete=models.CASCADE, related_name="circle_meet_tasks_meet") - title = models.CharField(max_length=100, null=False,blank=False) - description = models.CharField(max_length=500, null=True) - task = models.ForeignKey(TaskList, on_delete=models.CASCADE, related_name="circle_meet_tasks_task") - created_at = models.DateTimeField(auto_now=True) - - class Meta: - managed = False - db_table = 'circle_meet_tasks' - -class CircleMeetAttendeeReport(models.Model): - id = models.CharField(primary_key=True, max_length=36, default=lambda: str(uuid.uuid4()), unique=True) - meet_task = models.ForeignKey(CircleMeetTasks, on_delete=models.CASCADE, related_name="circle_meet_attendee_report_meet_task") - attendee = models.ForeignKey(CircleMeetAttendees, on_delete=models.CASCADE, related_name="circle_meet_attendee_report_attendee") - is_image = models.BooleanField(default=False,null=False,blank=False) - image_url = models.ImageField(max_length=300,upload_to="lc/meet-report/attendee/") - proof_url = models.URLField(max_length=300, null=True, blank=True) - created_at = models.DateTimeField(auto_now=True) - - class Meta: - managed = False - db_table = 'circle_meet_attendee_report' + db_table = "circle_meet_attendees" diff --git a/utils/types.py b/utils/types.py index 126ca516..071a2e2f 100644 --- a/utils/types.py +++ b/utils/types.py @@ -174,6 +174,15 @@ def get_all_values(cls): return [member.value for member in cls] +class LearningCircleRecurrenceType(Enum): + WEEKLY = "weekly" + MONTHLY = "monthly" + + @classmethod + def get_all_values(cls): + return [member.value for member in cls] + + DEFAULT_HACKATHON_FORM_FIELDS = { "name": "system", "gender": "system", From e308fbd303fd56160051e9b44241755022a95f24 Mon Sep 17 00:00:00 2001 From: Aswanth Vc Date: Sun, 3 Nov 2024 05:07:45 +0530 Subject: [PATCH 2/5] feat(lc): learning circle info api --- .../learningcircle/learningcircle_views.py | 20 ++++++++++++++++--- api/dashboard/learningcircle/urls.py | 1 + 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/api/dashboard/learningcircle/learningcircle_views.py b/api/dashboard/learningcircle/learningcircle_views.py index 17baecff..1d6e6299 100644 --- a/api/dashboard/learningcircle/learningcircle_views.py +++ b/api/dashboard/learningcircle/learningcircle_views.py @@ -19,7 +19,20 @@ class LearningCircleView(APIView): permission_classes = [CustomizePermission] - def get(self, request): + def get(self, request, circle_id: str = None): + if circle_id: + learning_circle = LearningCircle.objects.get(id=circle_id) + circle_meetings = CircleMeetingLog.objects.filter( + circle_id=learning_circle, is_report_submitted=True + ) + serializer = LearningCircleListSerializer(learning_circle) + meetings_serializer = CircleMeetingLogListSerializer( + circle_meetings, many=True + ) + return CustomResponse( + general_message="Learning Circle fetched successfully", + response={**serializer.data, "past_meetups": meetings_serializer.data}, + ).get_success_response() learning_circles = ( LearningCircle.objects.filter(created_by_id=JWTUtils.fetch_user_id(request)) .order_by("-created_at", "-updated_at") @@ -41,9 +54,10 @@ def post(self, request): general_message="Learning Circle creation failed", response=serializer.errors, ).get_failure_response() - serializer.save() + result = serializer.save() return CustomResponse( - general_message="Learning Circle created successfully" + general_message="Learning Circle created successfully", + response={"circle_id": result.id}, ).get_success_response() def put(self, request, circle_id: str): diff --git a/api/dashboard/learningcircle/urls.py b/api/dashboard/learningcircle/urls.py index 7a685635..dcf5ec23 100644 --- a/api/dashboard/learningcircle/urls.py +++ b/api/dashboard/learningcircle/urls.py @@ -5,6 +5,7 @@ urlpatterns = [ path("create/", learningcircle_views.LearningCircleView.as_view()), path("list/", learningcircle_views.LearningCircleView.as_view()), + path("info//", learningcircle_views.LearningCircleView.as_view()), path("edit//", learningcircle_views.LearningCircleView.as_view()), path("delete//", learningcircle_views.LearningCircleView.as_view()), path("meeting/create/", learningcircle_views.LearningCircleMeetingView.as_view()), From cb5906be7aaaaf4f43eb4a5a819636933cb80dff Mon Sep 17 00:00:00 2001 From: Aswanth Vc Date: Sun, 3 Nov 2024 18:42:39 +0530 Subject: [PATCH 3/5] feat(lc): reward karma points for actions --- .../learningcircle_serializer.py | 26 +++---- .../learningcircle/learningcircle_views.py | 41 +++++++++-- api/dashboard/organisation/serializers.py | 3 +- utils/karma.py | 72 +++++++++++++++++++ utils/types.py | 19 +++-- 5 files changed, 138 insertions(+), 23 deletions(-) create mode 100644 utils/karma.py diff --git a/api/dashboard/learningcircle/learningcircle_serializer.py b/api/dashboard/learningcircle/learningcircle_serializer.py index d4a2dc7b..1ed88431 100644 --- a/api/dashboard/learningcircle/learningcircle_serializer.py +++ b/api/dashboard/learningcircle/learningcircle_serializer.py @@ -1,10 +1,13 @@ from datetime import datetime, timedelta, timezone + +import pytz from db.learning_circle import LearningCircle, CircleMeetingLog, CircleMeetingAttendees from rest_framework import serializers from db.organization import Organization from db.task import InterestGroup from utils.types import LearningCircleRecurrenceType +from utils.utils import DateTimeUtils class LearningCircleCreateEditSerialzier(serializers.ModelSerializer): @@ -25,7 +28,7 @@ def update(self, instance, validated_data): "recurrence_type", instance.recurrence_type ) instance.recurrence = validated_data.get("recurrence", instance.recurrence) - instance.updated_at = datetime.now(timezone.utc) + instance.updated_at = DateTimeUtils.get_current_utc_time() instance.save() return instance @@ -66,8 +69,8 @@ class Meta: class LearningCircleListSerializer(serializers.ModelSerializer): - ig = serializers.CharField(source="ig_id.name", read_only=True) - org = serializers.CharField(source="org_id.name", read_only=True, allow_null=True) + ig = serializers.CharField(source="ig.name", read_only=True) + org = serializers.CharField(source="org.name", read_only=True, allow_null=True) created_by = serializers.CharField(source="created_by_id.full_name", read_only=True) next_meetup = serializers.SerializerMethodField() @@ -95,18 +98,17 @@ def _get_month_day(self, target_day: int): def get_next_meetup(self, obj): next_meetup = ( CircleMeetingLog.objects.filter(circle_id=obj.id) - .filter(meet_time__gte=datetime.now(timezone.utc)) + .filter( + meet_time__gte=DateTimeUtils.get_current_utc_time(), + is_report_submitted=False, + ) .order_by("-meet_time") .first() ) if next_meetup: return { + **CircleMeetingLogListSerializer(next_meetup).data, "is_scheduled": True, - "id": next_meetup.id, - "title": next_meetup.title, - "meet_time": next_meetup.meet_time, - "meet_place": next_meetup.meet_place, - "is_report_submitted": next_meetup.is_report_submitted, } if not obj.is_recurring: return None @@ -154,7 +156,7 @@ def update(self, instance, validated_data): instance.meet_place = validated_data.get("meet_place", instance.meet_place) instance.meet_time = validated_data.get("meet_time", instance.meet_time) instance.duration = validated_data.get("duration", instance.duration) - instance.updated_at = datetime.now(timezone.utc) + instance.updated_at = DateTimeUtils.get_current_utc_time() instance.save() return instance @@ -210,7 +212,7 @@ class CircleMeetingLogListSerializer(serializers.ModelSerializer): is_ended = serializers.SerializerMethodField() def get_is_started(self, obj): - return obj.meet_time <= datetime.now(timezone.utc) + return obj.meet_time <= DateTimeUtils.get_current_utc_time() def get_is_ended(self, obj): return (obj.meet_time + timedelta(hours=obj.duration + 1)) <= datetime.now( @@ -259,7 +261,7 @@ class CircleMeetupInfoSerializer(serializers.ModelSerializer): attendee = serializers.SerializerMethodField() def get_is_started(self, obj): - return obj.meet_time <= datetime.now(timezone.utc) + return obj.meet_time <= DateTimeUtils.get_current_utc_time() def get_is_ended(self, obj): return (obj.meet_time + timedelta(hours=obj.duration + 1)) <= datetime.now( diff --git a/api/dashboard/learningcircle/learningcircle_views.py b/api/dashboard/learningcircle/learningcircle_views.py index 1d6e6299..64c0046f 100644 --- a/api/dashboard/learningcircle/learningcircle_views.py +++ b/api/dashboard/learningcircle/learningcircle_views.py @@ -3,9 +3,11 @@ from rest_framework.views import APIView from db.learning_circle import LearningCircle, CircleMeetingLog, CircleMeetingAttendees from db.user import UserInterests +from utils.karma import add_karma from utils.permission import CustomizePermission, JWTUtils from utils.response import CustomResponse -from utils.utils import generate_code +from utils.types import Lc +from utils.utils import DateTimeUtils, generate_code from .learningcircle_serializer import ( CircleMeetingLogCreateEditSerializer, CircleMeetingLogListSerializer, @@ -55,6 +57,9 @@ def post(self, request): response=serializer.errors, ).get_failure_response() result = serializer.save() + add_karma( + user_id, Lc.MEET_CREATE_HASHTAG.value, user_id, Lc.MEET_CREATE_KARMA.value + ) return CustomResponse( general_message="Learning Circle created successfully", response={"circle_id": result.id}, @@ -168,10 +173,12 @@ class LearningCircleJoinAPI(APIView): def post(self, request, meet_id: str): user_id = JWTUtils.fetch_user_id(request) circle_meeting = CircleMeetingLog.objects.get(id=meet_id) - is_meet_started = circle_meeting.meet_time <= datetime.now(timezone.utc) + is_meet_started = ( + circle_meeting.meet_time <= DateTimeUtils.get_current_utc_time() + ) is_meet_ended = ( circle_meeting.meet_time + timedelta(hours=circle_meeting.duration + 2) - ) <= datetime.now(timezone.utc) + ) <= DateTimeUtils.get_current_utc_time() if is_meet_ended: return CustomResponse( general_message="The Circle Meeting has already ended" @@ -185,7 +192,7 @@ def post(self, request, meet_id: str): general_message="Invalid Circle Meeting code" ).get_failure_response() is_joined = True - joined_at = datetime.now(timezone.utc) + joined_at = DateTimeUtils.get_current_utc_time() attendee = CircleMeetingAttendees.objects.filter( meet_id=circle_meeting, user_id_id=user_id ).first() @@ -201,6 +208,9 @@ def post(self, request, meet_id: str): attendee.is_joined = is_joined attendee.joined_at = joined_at attendee.save() + add_karma( + user_id, Lc.MEET_JOIN_HASHTAG.value, user_id, Lc.MEET_JOIN_KARMA.value + ) return CustomResponse( general_message="You have successfully joined the Circle Meeting" ).get_success_response() @@ -210,6 +220,9 @@ def post(self, request, meet_id: str): is_joined=is_joined, joined_at=joined_at, ) + add_karma( + user_id, Lc.MEET_JOIN_HASHTAG.value, user_id, Lc.MEET_JOIN_KARMA.value + ) return CustomResponse( general_message=( "You have successfully joined the Circle Meeting" @@ -285,6 +298,12 @@ def post(self, request, meet_id): attendee.report_text = report attendee.report_link = report_link attendee.save() + add_karma( + user_id, + Lc.ATTENDEE_REPORT_SUBMIT_HASHTAG.value, + user_id, + Lc.ATTENDEE_REPORT_SUBMIT_KARMA.value, + ) return CustomResponse( general_message="You have successfully submitted the report" ).get_success_response() @@ -366,6 +385,7 @@ def post(self, request, meet_id): return CustomResponse( general_message="Please provide the report" ).get_failure_response() + karma_user_ids = [] for attendee_id, approved in attendees.items(): attendee = CircleMeetingAttendees.objects.filter( meet_id=circle_meeting, user_id_id=attendee_id @@ -380,9 +400,17 @@ def post(self, request, meet_id): ).get_failure_response() attendee.is_lc_approved = approved attendee.save() + if attendee.is_lc_approved: + karma_user_ids.append(attendee_id) circle_meeting.is_report_submitted = True circle_meeting.report_text = report circle_meeting.save() + add_karma( + karma_user_ids, + Lc.LC_REPORT_HASHTAG.value, + user_id, + Lc.LC_REPORT_KARMA.value, + ) return CustomResponse( general_message="The report has been submitted successfully" ).get_success_response() @@ -447,7 +475,10 @@ def get(self, request): ) meetings = ( CircleMeetingLog.objects.filter( - Q(meet_time__gte=datetime.now(timezone.utc) - timedelta(hours=2)) + Q( + meet_time__gte=DateTimeUtils.get_current_utc_time() + - timedelta(hours=2) + ) | Q(id__in=user_meetups) ).order_by("meet_time") # .prefetch_related("circle_meeting_attendance_meet_id") diff --git a/api/dashboard/organisation/serializers.py b/api/dashboard/organisation/serializers.py index 067ee33b..46bcb875 100644 --- a/api/dashboard/organisation/serializers.py +++ b/api/dashboard/organisation/serializers.py @@ -21,6 +21,7 @@ ) from utils.permission import JWTUtils from utils.types import OrganizationType +from utils.utils import DateTimeUtils class InstitutionSerializer(serializers.ModelSerializer): @@ -451,7 +452,7 @@ def update(self, instance, validated_data): instance.verified = validated_data.get("verified") instance.org = validated_data.get("org_id") instance.verified_by_id = self.context.get("user_id") - instance.verified_at = datetime.now(timezone.utc) + instance.verified_at = DateTimeUtils.get_current_utc_time() instance.save() if instance.verified: if UserOrganizationLink.objects.filter( diff --git a/utils/karma.py b/utils/karma.py new file mode 100644 index 00000000..d066f705 --- /dev/null +++ b/utils/karma.py @@ -0,0 +1,72 @@ +import uuid +from db.task import KarmaActivityLog, TaskList, Wallet +from db.user import User +from utils.utils import DateTimeUtils +from django.db.models import F + + +def add_karma( + user_id: str | list[str], hashtag: str, approved_by: str, karma: int | None = None +): + task = TaskList.objects.filter(hashtag=hashtag).first() + if not task: + return False + if not karma: + karma = task.karma + if not User.objects.filter(id=approved_by).exists(): + return False + if isinstance(user_id, list): + count = User.objects.filter(id__in=user_id).count() + if count != len(user_id): + return False + user_ids = user_id + KarmaActivityLog.objects.bulk_create( + [ + KarmaActivityLog( + id=str(uuid.uuid4()), + user_id=user_id, + karma=karma, + task=task, + updated_by_id=user_id, + created_by_id=user_id, + appraiser_approved=True, + peer_approved=True, + appraiser_approved_by_id=approved_by, + peer_approved_by_id=approved_by, + task_message_id="none", + lobby_message_id="none", + dm_message_id="none", + ) + for user_id in user_ids + ] + ) + Wallet.objects.filter(user_id__in=user_ids).update( + karma=F("karma") + karma, + karma_last_updated_at=DateTimeUtils.get_current_utc_time(), + updated_at=DateTimeUtils.get_current_utc_time(), + ) + else: + if not User.objects.filter(id=user_id).exists(): + return False + KarmaActivityLog.objects.create( + id=str(uuid.uuid4()), + user_id=user_id, + karma=karma, + task=task, + updated_by_id=user_id, + created_by_id=user_id, + appraiser_approved=True, + peer_approved=True, + appraiser_approved_by_id=approved_by, + peer_approved_by_id=approved_by, + task_message_id="none", + lobby_message_id="none", + dm_message_id="none", + ) + + wallet = Wallet.objects.filter(user_id=user_id).first() + wallet.karma += karma + wallet.karma_last_updated_at = DateTimeUtils.get_current_utc_time() + wallet.updated_at = DateTimeUtils.get_current_utc_time() + wallet.save() + return True diff --git a/utils/types.py b/utils/types.py index 071a2e2f..a589a953 100644 --- a/utils/types.py +++ b/utils/types.py @@ -125,12 +125,21 @@ def get_all_values(cls): class Lc(Enum): RECORD_SUBMIT_KARMA = 20 RECORD_SUBMIT_HASHTAG = "#lcmeetreport" - - MEET_JOIN_KARMA = 10 - MEET_JOIN_HASHTAG = "#lcmeetjoin" - + # Karma for creating a learning circle + MEET_CREATE_KARMA = 1 + MEET_CREATE_HASHTAG = "#lc-meet-create" + # Karma for joining a learning circle + MEET_JOIN_KARMA = 1 + MEET_JOIN_HASHTAG = "#lc-meet-join" + # Karma for submitting an attendee report + ATTENDEE_REPORT_SUBMIT_KARMA = 2 + ATTENDEE_REPORT_SUBMIT_HASHTAG = "#lc-attendee-report" + # Karma for submitting a learning circle report + LC_REPORT_KARMA = 5 + LC_REPORT_HASHTAG = "#lc-meet-report" + # karma when appraiser approved VERIFY_MAX_KARMA = 200 - VERIFY_HASHTAG = "#lcmeetverify" + VERIFY_HASHTAG = "#lc-meet-verify" class CouponResponseKey(Enum): From 6f3f911fe4dc7a1a7f9977e0bb18aef05ecfc4cb Mon Sep 17 00:00:00 2001 From: Aswanth Vc Date: Tue, 5 Nov 2024 01:27:06 +0530 Subject: [PATCH 4/5] feat(lc): modified lc views --- .../learningcircle_serializer.py | 12 ++-- .../learningcircle/learningcircle_views.py | 71 ++++++++++++++----- api/dashboard/learningcircle/urls.py | 4 ++ 3 files changed, 66 insertions(+), 21 deletions(-) diff --git a/api/dashboard/learningcircle/learningcircle_serializer.py b/api/dashboard/learningcircle/learningcircle_serializer.py index 1ed88431..39f48576 100644 --- a/api/dashboard/learningcircle/learningcircle_serializer.py +++ b/api/dashboard/learningcircle/learningcircle_serializer.py @@ -99,7 +99,7 @@ def get_next_meetup(self, obj): next_meetup = ( CircleMeetingLog.objects.filter(circle_id=obj.id) .filter( - meet_time__gte=DateTimeUtils.get_current_utc_time(), + # meet_time__gte=DateTimeUtils.get_current_utc_time(), is_report_submitted=False, ) .order_by("-meet_time") @@ -241,6 +241,7 @@ class Meta: class CircleMeetingAttendeeSerializer(serializers.ModelSerializer): + class Meta: model = CircleMeetingAttendees fields = ["is_joined", "is_report_submitted", "is_lc_approved"] @@ -270,10 +271,13 @@ def get_is_ended(self, obj): def get_attendee(self, obj): if user_id := self.context.get("user_id"): + user_obj = obj.circle_meeting_attendance_meet_id.filter( + user_id=user_id + ).first() + if not user_obj: + return None return CircleMeetingAttendeeSerializer( - obj.circle_meeting_attendance_meet_id.filter( - user_id_id=user_id - ).first(), + user_obj, many=False, ).data return None diff --git a/api/dashboard/learningcircle/learningcircle_views.py b/api/dashboard/learningcircle/learningcircle_views.py index 64c0046f..a7c6938a 100644 --- a/api/dashboard/learningcircle/learningcircle_views.py +++ b/api/dashboard/learningcircle/learningcircle_views.py @@ -101,6 +101,16 @@ def delete(self, request, circle_id: str): ).get_success_response() +class LearningCircleMeetingInfoAPI(APIView): + def get(self, request, meet_id: str): + meet = CircleMeetingLog.objects.get(id=meet_id) + serializer = CircleMeetingLogListSerializer(meet) + return CustomResponse( + general_message="Meeting fetched successfully", + response=serializer.data, + ).get_success_response() + + class LearningCircleMeetingView(APIView): permission_classes = [CustomizePermission] @@ -247,7 +257,11 @@ def delete(self, request, meet_id: str): ).get_failure_response() attendee.delete() return CustomResponse( - general_message="You have successfully left the Circle Meeting" + general_message=( + "You have successfully left the Circle Meeting" + if attendee.is_joined + else "Removed from saved list." + ) ).get_success_response() @@ -448,16 +462,34 @@ class LearningCircleMeetingListAPI(APIView): def get(self, request): request_data = request.query_params - categories = request_data.get("categories", []) + category = request_data.get("category", None) + saved = request_data.get("saved", "0") + participated = request_data.get("participated", "0") + saved = str(saved).lower() in ("true", "1") + participated = str(participated).lower() in ("true", "1") # no_location = request_data.get("no_location") lat = request_data.get("lat") lon = request_data.get("lon") user_id = None - if JWTUtils.is_jwt_authenticated(request) and not categories: + if JWTUtils.is_jwt_authenticated(request): + user_id = JWTUtils.fetch_user_id(request) + if saved or participated: + if not user_id: + return CustomResponse( + general_message="User not authenticated" + ).get_failure_response() + category = "all" + if saved and participated: + return CustomResponse( + general_message="Please provide either saved or participated" + ).get_failure_response() + if user_id and not category and category != "all": user_id = JWTUtils.fetch_user_id(request) interests = UserInterests.objects.filter(user_id=user_id).first() if interests: - categories = interests.choosen_interests + category = interests.choosen_interests + if category != "all" and type(category) == str: + category = [category] # if not no_location and not lat and not lon: # user_ip = request.META.get("REMOTE_ADDR") # ipinfo_api_url = f"http://ip-api.com/json/{user_ip}?fields=status,lat,lon" @@ -466,28 +498,33 @@ def get(self, request): # if location_data.get("status") == "success": # lat = location_data.get("lat") # lon = location_data.get("lon") + if saved: + filter = Q(user_id=user_id, is_joined=False) + elif participated: + filter = Q(user_id=user_id, is_joined=True) + else: + filter = Q(user_id=user_id, is_report_submitted=False) user_meetups = ( [] if not user_id - else CircleMeetingAttendees.objects.filter( - user_id=user_id, is_report_submitted=False - ).values_list("meet_id_id", flat=True) + else CircleMeetingAttendees.objects.filter(filter).values_list( + "meet_id_id", flat=True + ) ) + if saved or participated: + filter = Q(id__in=user_meetups) + else: + filter = Q( + meet_time__gte=DateTimeUtils.get_current_utc_time() - timedelta(hours=2) + ) | Q(id__in=user_meetups) meetings = ( - CircleMeetingLog.objects.filter( - Q( - meet_time__gte=DateTimeUtils.get_current_utc_time() - - timedelta(hours=2) - ) - | Q(id__in=user_meetups) - ).order_by("meet_time") + CircleMeetingLog.objects.filter(filter).order_by("meet_time") # .prefetch_related("circle_meeting_attendance_meet_id") ) - if categories and type(categories) == list: + if category and category != "all" and type(category) == list: meetings = meetings.select_related("circle_id__ig").filter( - circle_id__ig__category__in=categories + circle_id__ig__category__in=category ) - serializer = CircleMeetupInfoSerializer( meetings, many=True, context={"user_id": user_id} ) diff --git a/api/dashboard/learningcircle/urls.py b/api/dashboard/learningcircle/urls.py index dcf5ec23..c5f758eb 100644 --- a/api/dashboard/learningcircle/urls.py +++ b/api/dashboard/learningcircle/urls.py @@ -18,6 +18,10 @@ "meeting/edit//", learningcircle_views.LearningCircleMeetingView.as_view(), ), + path( + "meeting/info//", + learningcircle_views.LearningCircleMeetingInfoAPI.as_view(), + ), path( "meeting/delete//", learningcircle_views.LearningCircleMeetingView.as_view(), From 88f505e0048cf21b1daa1dedb91e36c1390471fb Mon Sep 17 00:00:00 2001 From: Aswanth Vc Date: Tue, 5 Nov 2024 02:23:53 +0530 Subject: [PATCH 5/5] feat: alter 1.59 --- alter-scripts/alter-1.59.py | 96 +++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 alter-scripts/alter-1.59.py diff --git a/alter-scripts/alter-1.59.py b/alter-scripts/alter-1.59.py new file mode 100644 index 00000000..d7f84111 --- /dev/null +++ b/alter-scripts/alter-1.59.py @@ -0,0 +1,96 @@ +import os +import sys +import uuid +from decouple import config +import django + +from connection import execute + +os.chdir("..") +sys.path.append(os.getcwd()) +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mulearnbackend.settings") +django.setup() + + +def get_recurring_lc_ids(): + query = "SELECT id, day from learning_circle where day is not null" + return execute(query) + + +def migrate_to_new_lc(): + lcs = get_recurring_lc_ids() + execute("DROP TABLE IF EXISTS circle_meet_attendees;") + execute("DROP TABLE IF EXISTS circle_meet_tasks;") + execute("DROP TABLE IF EXISTS circle_meet_attendee_report;") + execute("DROP TABLE IF EXISTS circle_meeting_log;") + execute( + """ +ALTER TABLE learning_circle + MODIFY COLUMN name VARCHAR(255), + MODIFY COLUMN circle_code VARCHAR(15), + DROP COLUMN meet_time, + DROP COLUMN day, + DROP COLUMN meet_place, + DROP FOREIGN KEY fk_learning_circle_ref_updated_by, + DROP COLUMN updated_by, + ADD COLUMN is_recurring BOOLEAN DEFAULT FALSE NOT NULL, + ADD COLUMN recurrence_type VARCHAR(10), + ADD COLUMN recurrence INT; +""" + ) + execute( + """ +CREATE TABLE circle_meeting_log +( + id VARCHAR(36) PRIMARY KEY NOT NULL, + circle_id VARCHAR(36) NOT NULL, + meet_code VARCHAR(6) NOT NULL, + title VARCHAR(100) NOT NULL, + is_report_needed BOOLEAN DEFAULT TRUE NOT NULL, + report_description VARCHAR(1000), + coord_x FLOAT NOT NULL NOT NULL, + coord_y FLOAT NOT NULL NOT NULL, + meet_place VARCHAR(255) NOT NULL, + meet_time DATETIME NOT NULL, + duration INT NOT NULL, + is_report_submitted BOOLEAN DEFAULT FALSE NOT NULL, + is_approved BOOLEAN DEFAULT FALSE NOT NULL, + report_text VARCHAR(1000), + created_by VARCHAR(36) NOT NULL, + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + CONSTRAINT fk_circle_meeting_log_ref_circle_id FOREIGN KEY (circle_id) REFERENCES learning_circle (id) ON DELETE CASCADE, + CONSTRAINT fk_circle_meeting_log_ref_created_by FOREIGN KEY (created_by) REFERENCES user (id) ON DELETE CASCADE +); +""" + ) + execute( + """ +CREATE TABLE circle_meet_attendees ( + id VARCHAR(36) PRIMARY KEY NOT NULL, + user_id VARCHAR(36) NOT NULL, + meet_id VARCHAR(36) NOT NULL, + is_joined BOOLEAN DEFAULT FALSE NOT NULL, + joined_at DATETIME, + is_report_submitted BOOLEAN DEFAULT FALSE NOT NULL, + is_lc_approved BOOLEAN DEFAULT FALSE NOT NULL, + report_text VARCHAR(1000), + report_link VARCHAR(200), + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + CONSTRAINT fk_circle_meet_attendees_ref_meet_id FOREIGN KEY (meet_id) REFERENCES circle_meeting_log (id) ON DELETE CASCADE, + CONSTRAINT fk_circle_meet_attendees_ref_user_id FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE +); +""" + ) + for id, day in lcs: + day = str(day).split(",")[0] + query = f"""UPDATE learning_circle SET is_recurring = TRUE, recurrence_type = 'weekly', recurrence = {day} WHERE id = '{id}'""" + execute(query) + + +if __name__ == "__main__": + migrate_to_new_lc() + execute( + "UPDATE system_setting SET value = '1.59', updated_at = now() WHERE `key` = 'db.version';" + )