diff --git a/api/dashboard/lc/dash_lc_serializer.py b/api/dashboard/lc/dash_lc_serializer.py index aa4bf8b9..9eb17801 100644 --- a/api/dashboard/lc/dash_lc_serializer.py +++ b/api/dashboard/lc/dash_lc_serializer.py @@ -6,7 +6,9 @@ from rest_framework import serializers from db.learning_circle import ( + CircleMeetAttendeeReport, CircleMeetAttendees, + CircleMeetTasks, LearningCircle, UserCircleLink, InterestGroup, @@ -259,28 +261,26 @@ class Meta: def create(self, validated_data): user_id = self.context.get("user_id") circle_id = self.context.get("circle_id") - no_of_entry = UserCircleLink.objects.filter( - circle_id=circle_id, accepted=True - ).count() + # no_of_entry = UserCircleLink.objects.filter( + # circle_id=circle_id, accepted=True + # ).count() - ig_id = LearningCircle.objects.get(pk=circle_id).ig_id + # ig_id = LearningCircle.objects.get(pk=circle_id).ig_id - if entry := UserCircleLink.objects.filter( - circle_id=circle_id, user_id=user_id - ).first(): + if UserCircleLink.objects.filter(circle_id=circle_id, user_id=user_id).exists(): raise serializers.ValidationError( "Cannot send another request at the moment" ) - if UserCircleLink.objects.filter( - user_id=user_id, circle_id__ig_id=ig_id, accepted=True - ).exists(): - raise serializers.ValidationError( - "Already a member of learning circle with same interest group" - ) + # if UserCircleLink.objects.filter( + # user_id=user_id, circle_id__ig_id=ig_id, accepted=True + # ).exists(): + # raise serializers.ValidationError( + # "Already a member of learning circle with same interest group" + # ) - if no_of_entry >= 5: - raise serializers.ValidationError("Maximum member count reached") + # if no_of_entry >= 5: + # raise serializers.ValidationError("Maximum member count reached") validated_data["id"] = uuid.uuid4() validated_data["user_id"] = user_id @@ -630,17 +630,17 @@ class Meta: def validate(self, data): user = self.context.get("user") - circle_id = self.context.get("circle_id") + # circle_id = self.context.get("circle_id") if UserCircleLink.objects.filter(user=user).exists(): raise serializers.ValidationError( "user already part of the learning circle" ) - if UserCircleLink.objects.filter(circle_id=circle_id).count() >= 5: - raise serializers.ValidationError( - "maximum members reached in learning circle" - ) + # if UserCircleLink.objects.filter(circle_id=circle_id).count() >= 5: + # raise serializers.ValidationError( + # "maximum members reached in learning circle" + # ) return data @@ -671,6 +671,30 @@ class CircleMeetDetailSerializer(serializers.ModelSerializer): total_joined = serializers.SerializerMethodField() lc_members = serializers.SerializerMethodField() is_lc_member = serializers.SerializerMethodField() + is_online = serializers.BooleanField(default=False) + tasks = serializers.SerializerMethodField() + is_attendee_report_submitted = serializers.SerializerMethodField() + is_report_submitted = serializers.BooleanField(default=False) + + def get_is_attendee_report_submitted(self, obj): + user_id = self.context.get("user_id") + return ( + CircleMeetAttendeeReport.objects.select_related( + "meet_task__meet", "attendee__user" + ) + .filter(meet_task__meet=obj, attendee__user_id=user_id) + .exists() + ) + + def get_is_report_submitted(self, obj): + return obj.is_report_submitted + + def get_tasks(self, obj): + return CircleMeetTasksSerializer( + CircleMeetTasks.objects.filter(meet=obj), + many=True, + context={"user_id": self.context.get("user_id")}, + ).data def get_is_lc_member(self, obj): user_id = self.context.get("user_id") @@ -742,6 +766,88 @@ class Meta: "total_joined", "lc_members", "is_lc_member", + "is_online", + "tasks", + "is_report_submitted", + "is_attendee_report_submitted", + ] + + +class CircleMeetTasksSerializer(serializers.ModelSerializer): + id = serializers.CharField(read_only=True) + meet = serializers.CharField(read_only=True) + title = serializers.CharField(required=True) + description = serializers.CharField(required=False, allow_null=True) + task = serializers.PrimaryKeyRelatedField( + queryset=TaskList.objects.all(), required=False, allow_null=True + ) + is_completed = serializers.SerializerMethodField() + + def get_is_completed(self, obj): + if user_id := self.context.get("user_id"): + return ( + CircleMeetAttendeeReport.objects.select_related("attendee__user") + .filter(meet_task=obj, attendee__user_id=user_id) + .exists() + ) + return False + + def create(self, validated_data): + validated_data["id"] = uuid.uuid4() + validated_data["created_at"] = DateTimeUtils.get_current_utc_time() + if not (meet_id := self.context.get("meet_id")): + raise serializers.ValidationError("Meet ID is required") + validated_data["meet_id"] = meet_id + return super().create(validated_data) + + class Meta: + model = CircleMeetTasks + fields = ["id", "meet", "title", "description", "task", "is_completed"] + + +class CircleAttendeeReportSerializer(serializers.ModelSerializer): + id = serializers.CharField(read_only=True) + meet_task = serializers.PrimaryKeyRelatedField( + queryset=CircleMeetTasks.objects.all(), required=True, write_only=True + ) + title = serializers.CharField(source="meet_task.title", read_only=True) + attendee = serializers.CharField(source="attendee.full_name", read_only=True) + is_image = serializers.BooleanField(default=False, allow_null=True) + image_url = serializers.ImageField(required=False, allow_null=True) + proof_url = serializers.CharField(required=False, allow_null=True) + image = serializers.SerializerMethodField() + + def get_image(self, obj): + return ( + f"{config('BE_DOMAIN_NAME')}/{settings.MEDIA_URL}{media}" + if (media := obj.image_url) + else None + ) + + def create(self, validated_data): + validated_data["created_at"] = DateTimeUtils.get_current_utc_time() + attendee = self.context.get("attendee") + validated_data["attendee"] = attendee + return CircleMeetAttendeeReport.objects.create(**validated_data) + + def validate(self, attrs): + if attrs.get("is_image") and not attrs.get("image_url"): + raise serializers.ValidationError("Image URL is required") + if not attrs.get("is_image") and not attrs.get("proof_url"): + raise serializers.ValidationError("Proof URL is required") + return super().validate(attrs) + + class Meta: + model = CircleMeetAttendeeReport + fields = [ + "id", + "meet_task", + "attendee", + "is_image", + "image_url", + "proof_url", + "title", + "image", ] @@ -759,6 +865,93 @@ class CircleMeetSerializer(serializers.ModelSerializer): report_text = serializers.CharField(required=False, allow_null=True) meet_code = serializers.SerializerMethodField() image = serializers.SerializerMethodField() + is_online = serializers.BooleanField(default=False) + is_verified = serializers.BooleanField(default=False, read_only=True) + + def create(self, validated_data): + validated_data["id"] = uuid.uuid4() + validated_data["circle_id"] = self.context.get("circle_id") + validated_data["created_by"] = self.context.get("user_id") + validated_data["updated_by"] = self.context.get("user_id") + validated_data["created_at"] = DateTimeUtils.get_current_utc_time() + validated_data["updated_at"] = DateTimeUtils.get_current_utc_time() + return super().create(validated_data) + + def get_meet_code(self, obj): + if user_id := self.context.get("user_id"): + if UserCircleLink.objects.filter( + user_id=user_id, circle_id=obj.circle_id, accepted=True + ).exists(): + return obj.meet_code + return None + + def get_image(self, obj): + return ( + f"{config('BE_DOMAIN_NAME')}/{settings.MEDIA_URL}{media}" + if (media := obj.images) + else None + ) + + class Meta: + model = CircleMeetingLog + fields = [ + "id", + "title", + "location", + "meet_time", + "meet_place", + "agenda", + "pre_requirements", + "is_public", + "is_started", + "max_attendees", + "report_text", + "meet_code", + "image", + "is_online", + "is_verified", + ] + + +class CircleMeetBasicDetails(serializers.ModelSerializer): + id = serializers.CharField(read_only=True) + title = serializers.CharField(required=True) + location = serializers.CharField(read_only=True) + meet_time = serializers.DateTimeField(read_only=True) + meet_place = serializers.CharField(read_only=True) + agenda = serializers.CharField(read_only=True) + pre_requirements = serializers.CharField(required=False, allow_null=True) + is_public = serializers.BooleanField(default=True) + max_attendees = serializers.IntegerField(default=-1) + report_text = serializers.CharField(required=False, allow_null=True) + meet_code = serializers.SerializerMethodField() + image = serializers.SerializerMethodField() + is_online = serializers.BooleanField(default=False) + is_verified = serializers.BooleanField(default=False, read_only=True) + learning_circle = serializers.CharField(source="circle.name", read_only=True) + join_count = serializers.SerializerMethodField() + interested_count = serializers.SerializerMethodField() + report_submitted_attendees = serializers.SerializerMethodField() + held_on = serializers.SerializerMethodField() + + def get_held_on(self, obj): + return ( + CircleMeetAttendees.objects.filter(meet=obj, joined_at__isnull=False) + .order_by("joined_at") + .values_list("joined_at", flat=True) + .first() + ) + + def get_report_submitted_attendees(self, obj): + return CircleMeetAttendees.objects.filter(meet=obj,is_report_submitted=True).count() + + def get_join_count(self, obj): + return CircleMeetAttendees.objects.filter( + meet=obj, joined_at__isnull=False + ).count() + + def get_interested_count(self, obj): + return CircleMeetAttendees.objects.filter(meet=obj).count() def create(self, validated_data): validated_data["id"] = uuid.uuid4() @@ -800,4 +993,11 @@ class Meta: "report_text", "meet_code", "image", + "is_online", + "is_verified", + "learning_circle", + "join_count", + "interested_count", + "report_submitted_attendees", + "held_on", ] diff --git a/api/dashboard/lc/dash_lc_view.py b/api/dashboard/lc/dash_lc_view.py index 84a1e185..1e24c738 100644 --- a/api/dashboard/lc/dash_lc_view.py +++ b/api/dashboard/lc/dash_lc_view.py @@ -1,16 +1,16 @@ from datetime import timedelta -import uuid +import uuid, json from collections import defaultdict - from django.conf import settings 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 ( + CircleMeetAttendeeReport, CircleMeetAttendees, + CircleMeetTasks, CircleMeetingLog, LearningCircle, UserCircleLink, @@ -18,9 +18,9 @@ from db.task import KarmaActivityLog, TaskList, Wallet from db.user import User from utils.response import CustomResponse -from utils.types import Lc +from utils.types import Lc, RoleType from utils.utils import DateTimeUtils, send_template_mail -from utils.permission import CustomizePermission, JWTUtils +from utils.permission import CustomizePermission, JWTUtils, role_required from .dash_ig_helper import ( get_today_start_end, get_week_start_end, @@ -29,7 +29,10 @@ ) from .dash_lc_serializer import ( AddMemberSerializer, + CircleAttendeeReportSerializer, + CircleMeetBasicDetails, CircleMeetDetailSerializer, + CircleMeetTasksSerializer, IgTaskDetailsSerializer, LearningCircleCreateSerializer, LearningCircleDetailsSerializer, @@ -44,6 +47,9 @@ ScheduleMeetingSerializer, CircleMeetSerializer, ) +from decouple import config + +BE_DOMAIN = config("BE_DOMAIN_NAME") class UserLearningCircleListApi(APIView): @@ -723,12 +729,21 @@ def post(self, request, circle_id): user = User.objects.filter(id=user_id).first() if not user: return CustomResponse(general_message="Invalid user").get_failure_response() + tasks = request.data.get("tasks") + if not tasks or type(tasks) != list or len(tasks) < 1: + return CustomResponse( + general_message="At least one task is required to create a meetup" + ).get_failure_response() serializer = CircleMeetSerializer( data=request.data, context={"user_id": user, "circle_id": circle_id} ) if serializer.is_valid(): circle_meet_log = serializer.save() - + task_serializer = CircleMeetTasksSerializer( + data=tasks, many=True, context={"meet_id": circle_meet_log.id} + ) + if task_serializer.is_valid(): + task_serializer.save() return CustomResponse( general_message=f"Meet scheduled at {circle_meet_log.meet_time}" ).get_success_response() @@ -741,9 +756,16 @@ def get(self, request, circle_id): meet_time__gte=DateTimeUtils.get_current_utc_time(), circle_id=circle_id, is_report_submitted=False, + is_started=False, + ).order_by("-created_at") + report_pending = CircleMeetingLog.objects.filter( + circle_id=circle_id, + is_report_submitted=False, + is_started=True, ).order_by("-created_at") past_meeting = CircleMeetingLog.objects.filter( circle_id=circle_id, + is_started=True, is_report_submitted=True, ).order_by("-created_at")[:2] @@ -753,6 +775,7 @@ def get(self, request, circle_id): up_coming_meeting, many=True, context={"user_id": user_id} ).data, "past": CircleMeetSerializer(past_meeting, many=True).data, + "report_pending": CircleMeetSerializer(report_pending, many=True).data, } ).get_success_response() @@ -766,6 +789,12 @@ def post(self, request, meet_id): user_id = JWTUtils.fetch_user_id(request) user = User.objects.filter(id=user_id).first() meet = CircleMeetingLog.objects.filter(id=meet_id).first() + ratings = request.data.get("ratings") + ratings = json.loads(ratings) + if not ratings or type(ratings) != dict or len(ratings) < 1: + return CustomResponse( + general_message="Attendee ratings are required to submit the report" + ).get_failure_response() if not user: return CustomResponse(general_message="Invalid user").get_failure_response() participant_count = CircleMeetAttendees.objects.filter( @@ -775,6 +804,10 @@ def post(self, request, meet_id): return CustomResponse( general_message="Minimum 2 participants are required to submit the report" ).get_failure_response() + for attendee_id, rating in ratings.items(): + CircleMeetAttendees.objects.filter(id=attendee_id).update( + lc_member_rating=rating + ) serializer = MeetRecordsCreateEditDeleteSerializer( data=request.data, context={"user": user, "meet": meet}, @@ -814,7 +847,21 @@ class CircleMeetListAPI(APIView): List all meetups available """ - def get(self, request): + def get(self, request, is_user=None): + if is_user: + if not (user_id := JWTUtils.fetch_user_id(request)): + return CustomResponse( + general_message="Unauthorized access" + ).get_failure_response() + circle_meets = CircleMeetingLog.objects.filter( + id__in=CircleMeetAttendees.objects.filter(user_id=user_id) + .only("meet_id") + .values_list("meet_id", flat=True) + )[:5] + serializer = CircleMeetSerializer( + circle_meets, many=True, context={"user_id": user_id} + ) + return CustomResponse(response=serializer.data).get_success_response() meet_id = request.query_params.get("meet_id") if meet_id: circle_meets = CircleMeetingLog.objects.filter(id=meet_id).first() @@ -831,9 +878,9 @@ def get(self, request): ) filters = Q() if district_id := request.data.get("district_id"): - filters &= Q(org__district_id=district_id) + filters &= Q(circle__org__district_id=district_id) if category := request.data.get("category"): - filters &= Q(ig__category=category) + filters &= Q(circle__ig__category=category) if ig := request.data.get("ig_id"): filters &= Q(circle__ig_id=ig) try: @@ -841,9 +888,9 @@ def get(self, request): filters &= Q(circle__user_circle_link_circle__user_id=user_id) | Q( is_public=True ) - except: + except Exception as e: filters &= Q(is_public=True) - circle_meets = circle_meets.filter(filters) + circle_meets = circle_meets.filter(filters).distinct() serializer = CircleMeetSerializer(circle_meets, many=True) return CustomResponse(response=serializer.data).get_success_response() @@ -966,3 +1013,224 @@ def post(self, request, meet_code_id): return CustomResponse( general_message=f"Joined in the meetup successfully." ).get_success_response() + + +class CircleMeetAttendeesListAPI(APIView): + def get(self, request, meet_id): + user_id = JWTUtils.fetch_user_id(request) + if meet_id: + attendees = [ + { + "attendee_id": attendee[0], + "fullname": attendee[1], + "profile_pic": f"{BE_DOMAIN}/{settings.MEDIA_URL}{attendee[2]}", + "muid": attendee[3], + "proof_of_work": CircleAttendeeReportSerializer( + CircleMeetAttendeeReport.objects.filter( + attendee__user_id=user_id, meet_task__meet_id=meet_id + ), + many=True, + ).data, + "report": attendee[4], + } + for attendee in ( + CircleMeetAttendees.objects.filter( + meet_id=meet_id, joined_at__isnull=False + ) + .select_related("user", "circle_meet_attendee_report_attendee") + .values_list( + "id", + "user__full_name", + "user_id", + "user__muid", + "report", + ) + .distinct() + ) + ] + return CustomResponse(response=attendees).get_success_response() + return CustomResponse(general_message="Invalid meeting").get_failure_response() + + +class CircleAttendeeReportAPI(APIView): + def post(self, request, meet_id): + if not (user_id := JWTUtils.fetch_user_id(request)): + return CustomResponse( + general_message="Unauthorized access" + ).get_failure_response() + if not (meet := CircleMeetingLog.objects.filter(id=meet_id).first()): + return CustomResponse( + general_message="Invalid meeting" + ).get_failure_response() + if not (report := request.data.get("report")): + return CustomResponse( + general_message="Report is required." + ).get_failure_response() + if not ( + attendee := CircleMeetAttendees.objects.filter( + meet_id=meet_id, user_id=user_id + ).first() + ): + return CustomResponse( + general_message="You are not part of this meetup." + ).get_failure_response() + if attendee.is_report_submitted: + return CustomResponse( + general_message="You have already submitted the report." + ).get_failure_response() + task_count = ( + CircleMeetAttendeeReport.objects.select_related("meet_task__meet") + .filter(attendee=attendee, meet_task__meet=meet) + .count() + ) + if task_count < 1: + return CustomResponse( + general_message="You must complete atleast one task listed in the meetup." + ).get_failure_response() + attendee.is_report_submitted = True + attendee.report = report + attendee.save() + return CustomResponse( + general_message="Report submitted successfully." + ).get_success_response() + + +class CircleMeetTaskPOWAPI(APIView): + def post(self, request, meet_id, task_id): + if not (user_id := JWTUtils.fetch_user_id(request)): + return CustomResponse( + general_message="Unauthorized access" + ).get_failure_response() + if not (CircleMeetingLog.objects.filter(id=meet_id).exists()): + return CustomResponse( + general_message="Invalid meeting" + ).get_failure_response() + if not (CircleMeetTasks.objects.filter(id=task_id).exists()): + return CustomResponse(general_message="Invalid task").get_failure_response() + if not ( + attendee := CircleMeetAttendees.objects.filter( + meet_id=meet_id, user_id=user_id, joined_at__isnull=False + ).first() + ): + return CustomResponse( + general_message="You are not part of this meetup." + ).get_failure_response() + if CircleMeetAttendeeReport.objects.filter( + meet_task_id=task_id, attendee=attendee + ).exists(): + return CustomResponse( + general_message="You have already submitted the proof of work." + ).get_failure_response() + serializer = CircleAttendeeReportSerializer( + data=request.data, + context={"attendee": attendee}, + ) + if serializer.is_valid(): + serializer.save() + return CustomResponse( + general_message="Proof of work submitted successfully." + ).get_success_response() + return CustomResponse( + general_message="Invalid proof of work.", + response=serializer.errors, + ).get_failure_response() + + +class CircleMeetVerifyAPI(APIView): + permission_classes = [CustomizePermission] + + @role_required( + roles=[ + RoleType.ADMIN.value, + RoleType.FELLOW.value, + RoleType.DISCORD_MANAGER.value, + RoleType.APPRAISER.value, + ] + ) + def get(self, request): + unverified_meets = CircleMeetingLog.objects.select_related("circle").filter( + is_report_submitted=True, is_verified=False + ) + serializer = CircleMeetBasicDetails(unverified_meets, many=True) + return CustomResponse(response=serializer.data).get_success_response() + + @role_required( + roles=[ + RoleType.ADMIN.value, + RoleType.FELLOW.value, + RoleType.DISCORD_MANAGER.value, + RoleType.APPRAISER.value, + ] + ) + def post(self, request, meet_id): + return CustomResponse( + general_message="This endpoint only supports GET requests." + ).get_failure_response() + if not (user_id := JWTUtils.fetch_user_id(request)): + return CustomResponse( + general_message="Unauthorized access" + ).get_failure_response() + attendees_karma = request.data.get("attendees_karma") + if ( + not attendees_karma + or type(attendees_karma) != dict + or len(attendees_karma) < 1 + ): + return CustomResponse( + general_message="Attendee karma is required to verify the meetup." + ).get_failure_response() + if not (meet := CircleMeetingLog.objects.filter(id=meet_id).first()): + return CustomResponse( + general_message="Invalid meeting" + ).get_failure_response() + if not (user := User.objects.filter(id=user_id).first()): + return CustomResponse(general_message="Invalid user").get_failure_response() + if meet.is_verified: + return CustomResponse( + general_message="This meetup is already verified." + ).get_failure_response() + attendees_karma = request.data.get("attendees_karma") + task = TaskList.objects.filter(hashtag=Lc.VERIFY_HASHTAG.value).first() + kal_logs = [] + for attendee_id, karma in attendees_karma.items(): + if not ( + attendee := CircleMeetAttendees.objects.filter( + id=attendee_id, meet=meet + ).first() + ): + return CustomResponse( + general_message="Invalid attendee." + ).get_failure_response() + if not (karma := int(karma)): + return CustomResponse( + general_message="Invalid karma." + ).get_failure_response() + if karma > Lc.MAX_KARMA.value: + return CustomResponse( + general_message=f"Karma should not be more than {Lc.MAX_KARMA.value}." + ).get_failure_response() + attendee.karma_given = karma + kal_logs.append( + KarmaActivityLog( + id=uuid.uuid4(), + user_id=attendee.user_id, + karma=karma, + task_id=task, + updated_by=user, + created_by=user, + appraiser_approved=True, + peer_approved=True, + appraiser_approved_by=user, + peer_approved_by=user, + task_message_id="AUTO_APPROVED", + lobby_message_id="AUTO_APPROVED", + dm_message_id="AUTO_APPROVED", + ) + ) + attendee.save() + meet.is_verified = True + meet.save() + KarmaActivityLog.objects.bulk_create(kal_logs) + return CustomResponse( + general_message="Meetup verified successfully." + ).get_success_response() diff --git a/api/dashboard/lc/urls.py b/api/dashboard/lc/urls.py index 8c790ee8..ad0217aa 100644 --- a/api/dashboard/lc/urls.py +++ b/api/dashboard/lc/urls.py @@ -4,6 +4,11 @@ urlpatterns = [ path("meets/list/", dash_lc_view.CircleMeetListAPI.as_view(), name="meets-list"), + path( + "meets/list//", + dash_lc_view.CircleMeetListAPI.as_view(), + name="meets-list-user", + ), path( "/meet/create/", dash_lc_view.CircleMeetAPI.as_view(), @@ -15,10 +20,33 @@ name="meet-list", ), path( - "/meets/report//", + "meets/report//", dash_lc_view.CircleMeetReportSubmitAPI.as_view(), name="meet-report-submission", ), + path( + "meets/attendee-report//", + dash_lc_view.CircleAttendeeReportAPI.as_view(), + name="meet-report-submission", + ), + path( + "meets/attendee-report///", + dash_lc_view.CircleMeetTaskPOWAPI.as_view(), + name="meet-report-submission", + ), + path( + "meets/verify-list/", + dash_lc_view.CircleMeetVerifyAPI.as_view(), + ), + path( + "meets/verify//", + dash_lc_view.CircleMeetVerifyAPI.as_view(), + ), + path( + "meets/attendees//", + dash_lc_view.CircleMeetAttendeesListAPI.as_view(), + name="meet-attendees-list", + ), path( "meets/interested//", dash_lc_view.CircleMeetInterestedAPI.as_view(), diff --git a/api/dashboard/user/dash_user_serializer.py b/api/dashboard/user/dash_user_serializer.py index 9ff984a4..8022ceee 100644 --- a/api/dashboard/user/dash_user_serializer.py +++ b/api/dashboard/user/dash_user_serializer.py @@ -384,15 +384,18 @@ def update(self, instance, validated_data): return super().update(instance, validated_data) -class UserOrgLinkSerializer(serializers.ModelSerializer): +class UserOrgLinkSerializer(serializers.Serializer): department = serializers.PrimaryKeyRelatedField( queryset=Department.objects.all(), required=False, allow_null=True ) graduation_year = serializers.CharField(required=False, allow_null=True) organization = serializers.PrimaryKeyRelatedField( - queryset=Organization.objects.all(), many=False, required=True + queryset=Organization.objects.all(), many=False, required=True, allow_null=True ) is_alumni = serializers.BooleanField(required=False) + is_student = serializers.BooleanField( + required=False, allow_null=True, default=False + ) def create(self, validated_data): department = validated_data.get("department", None) @@ -400,26 +403,35 @@ def create(self, validated_data): is_alumni = validated_data.get("is_alumni", False) is_college = lambda org: org.org_type == OrganizationType.COLLEGE.value user_id = self.context.get("user") - - org_link = UserOrganizationLink.objects.create( - user=user_id, - org=validated_data.get("organization"), - created_by=user_id, - created_at=DateTimeUtils.get_current_utc_time(), - verified=True, - department=( - department if is_college(validated_data.get("organization")) else None - ), - graduation_year=( - graduation_year - if is_college(validated_data.get("organization")) - else None - ), - is_alumni=( - is_alumni if is_college(validated_data.get("organization")) else None - ), - ) - if is_college(validated_data.get("organization")): + if org := validated_data.get("organization"): + org_link = UserOrganizationLink.objects.create( + user=user_id, + org=org, + created_by=user_id, + created_at=DateTimeUtils.get_current_utc_time(), + verified=True, + department=( + department + if is_college(validated_data.get("organization")) + else None + ), + graduation_year=( + graduation_year + if is_college(validated_data.get("organization")) + else None + ), + is_alumni=( + is_alumni + if is_college(validated_data.get("organization")) + else None + ), + ) + else: + org_link = "skiped_creation" + if validated_data.get("is_student") or ( + validated_data.get("organization") + and is_college(validated_data.get("organization")) + ): student_role_id = ( Role.objects.only("id").get(title=RoleType.STUDENT.value).id ) @@ -436,8 +448,13 @@ def create(self, validated_data): return org_link class Meta: - model = UserOrganizationLink - fields = ["organization", "department", "graduation_year", "is_alumni"] + fields = [ + "organization", + "department", + "graduation_year", + "is_alumni", + "is_student", + ] class GetUserLinkSerializer(serializers.ModelSerializer): diff --git a/api/dashboard/user/urls.py b/api/dashboard/user/urls.py index dc9192ce..93d452b5 100644 --- a/api/dashboard/user/urls.py +++ b/api/dashboard/user/urls.py @@ -3,25 +3,63 @@ from . import dash_user_views urlpatterns = [ - - path('verification/', dash_user_views.UserVerificationAPI.as_view(), name='list-verification'), - path('verification/csv/', dash_user_views.UserVerificationCSV.as_view(), name='csv-verification'), - path('verification//', dash_user_views.UserVerificationAPI.as_view(), name='edit-verification'), - path('verification//', dash_user_views.UserVerificationAPI.as_view(), name='delete-verification'), - path('organization/',dash_user_views.UserAddOrgAPI.as_view(),name="user-org-link"), - path('organization/list',dash_user_views.UserAddOrgAPI.as_view(),name="get-user-org-link"), - - path('info/', dash_user_views.UserInfoAPI.as_view()), - path('forgot-password/', dash_user_views.ForgotPasswordAPI.as_view(), name="forgot-password"), - path('reset-password/verify-token//', dash_user_views.ResetPasswordVerifyTokenAPI.as_view()), - path('reset-password//', dash_user_views.ResetPasswordConfirmAPI.as_view()), - path('profile/update/', dash_user_views.UserProfilePictureView.as_view()), - - path('csv/', dash_user_views.UserManagementCSV.as_view(), name="csv-user"), - path('', dash_user_views.UserAPI.as_view(), name='list-user'), - - path('/', dash_user_views.UserGetPatchDeleteAPI.as_view(), name="detail-user"), - path('/', dash_user_views.UserGetPatchDeleteAPI.as_view(), name="edit-user"), - path('/', dash_user_views.UserGetPatchDeleteAPI.as_view(), name="delete-user"), - + path( + "verification/", + dash_user_views.UserVerificationAPI.as_view(), + name="list-verification", + ), + path( + "verification/csv/", + dash_user_views.UserVerificationCSV.as_view(), + name="csv-verification", + ), + path( + "verification//", + dash_user_views.UserVerificationAPI.as_view(), + name="edit-verification", + ), + path( + "verification//", + dash_user_views.UserVerificationAPI.as_view(), + name="delete-verification", + ), + path( + "organization/", dash_user_views.UserAddOrgAPI.as_view(), name="user-org-link" + ), + path( + "organization/list/", + dash_user_views.UserAddOrgAPI.as_view(), + name="get-user-org-link", + ), + path("info/", dash_user_views.UserInfoAPI.as_view()), + path( + "forgot-password/", + dash_user_views.ForgotPasswordAPI.as_view(), + name="forgot-password", + ), + path( + "reset-password/verify-token//", + dash_user_views.ResetPasswordVerifyTokenAPI.as_view(), + ), + path( + "reset-password//", dash_user_views.ResetPasswordConfirmAPI.as_view() + ), + path("profile/update/", dash_user_views.UserProfilePictureView.as_view()), + path("csv/", dash_user_views.UserManagementCSV.as_view(), name="csv-user"), + path("", dash_user_views.UserAPI.as_view(), name="list-user"), + path( + "/", + dash_user_views.UserGetPatchDeleteAPI.as_view(), + name="detail-user", + ), + path( + "/", + dash_user_views.UserGetPatchDeleteAPI.as_view(), + name="edit-user", + ), + path( + "/", + dash_user_views.UserGetPatchDeleteAPI.as_view(), + name="delete-user", + ), ] diff --git a/db/learning_circle.py b/db/learning_circle.py index fa98a6ff..23a9c5e8 100644 --- a/db/learning_circle.py +++ b/db/learning_circle.py @@ -2,7 +2,7 @@ from django.db import models -from db.task import InterestGroup, Organization +from db.task import InterestGroup, KarmaActivityLog, Organization, TaskList from db.user import User from utils.utils import generate_code from django.conf import settings @@ -11,7 +11,7 @@ # noinspection PyPep8 class LearningCircle(models.Model): - id = models.CharField(primary_key=True, max_length=36, default=uuid.uuid4()) + 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, @@ -50,7 +50,7 @@ class Meta: class CircleMeetingLog(models.Model): - id = models.CharField(primary_key=True, max_length=36, default=uuid.uuid4(), unique=True) + 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') @@ -63,7 +63,9 @@ class CircleMeetingLog(models.Model): 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) is_report_submitted = models.BooleanField(default=False, null=False) images = models.ImageField(max_length=200, upload_to='lc/meet-report') @@ -79,10 +81,15 @@ class Meta: db_table = 'circle_meeting_log' class CircleMeetAttendees(models.Model): - id = models.CharField(primary_key=True, max_length=36, default=uuid.uuid4(), unique=True) + 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) + 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') @@ -92,3 +99,28 @@ class CircleMeetAttendees(models.Model): 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' diff --git a/docker-compose.yml b/docker-compose.yml index eb4201d4..df13983d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,12 +10,13 @@ services: ports: - "8000:8000" volumes: - - /var/log/mulearnbackend:/var/log/mulearnbackend + - ./logs:/var/log/mulearnbackend - /var/www/mulearnbackend/assets:/app/assets - /var/www/mulearnbackend/media:/app/media + - .:/app env_file: - .env - entrypoint: sh entrypoint.sh + entrypoint: python manage.py runserver 0.0.0.0:8000 celery: build: context: . diff --git a/utils/types.py b/utils/types.py index 705ffd32..126ca516 100644 --- a/utils/types.py +++ b/utils/types.py @@ -129,6 +129,9 @@ class Lc(Enum): MEET_JOIN_KARMA = 10 MEET_JOIN_HASHTAG = "#lcmeetjoin" + VERIFY_MAX_KARMA = 200 + VERIFY_HASHTAG = "#lcmeetverify" + class CouponResponseKey(Enum): DISCOUNT_TYPE = "discount_type"