From 2bbcb4a5803f1ecba36905862c130facfddf1bf9 Mon Sep 17 00:00:00 2001 From: Aswanth Vc Date: Thu, 10 Oct 2024 20:26:51 +0530 Subject: [PATCH 1/4] perf: optimized profile apis --- api/dashboard/profile/profile_serializer.py | 12 ++++++------ api/dashboard/profile/profile_view.py | 8 +++++--- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/api/dashboard/profile/profile_serializer.py b/api/dashboard/profile/profile_serializer.py index 8bb73f52..7196e50c 100644 --- a/api/dashboard/profile/profile_serializer.py +++ b/api/dashboard/profile/profile_serializer.py @@ -113,9 +113,7 @@ def get_percentile(self, obj): def get_roles(self, obj): if "role_values" in self.context: return self.context["role_values"] - role_values = list( - {link.role.title for link in obj.user_role_link_user.filter(verified=True)} - ) + role_values = list({link.role.title for link in obj.user_role_link_user.all()}) self.context["role_values"] = role_values return role_values @@ -221,10 +219,12 @@ def _get_completed_tasks(self, user_id): def get_tasks(self, obj): user_id = self.context.get("user_id") - user_igs = UserIgLink.objects.filter(user__id=user_id).values_list( - "ig__name", flat=True + user_igs = ( + UserIgLink.objects.filter(user__id=user_id) + .select_related("ig") + .values_list("ig__name", flat=True) ) - tasks = TaskList.objects.filter(level=obj) + tasks = TaskList.objects.filter(level=obj).select_related("ig") if obj.level_order > 4: tasks = tasks.filter(ig__name__in=user_igs) diff --git a/api/dashboard/profile/profile_view.py b/api/dashboard/profile/profile_view.py index 8022d9ae..df070438 100644 --- a/api/dashboard/profile/profile_view.py +++ b/api/dashboard/profile/profile_view.py @@ -167,9 +167,11 @@ def get(self, request, muid=None): JWTUtils.is_jwt_authenticated(request) user_id = JWTUtils.fetch_user_id(request) - karma_activity_log = KarmaActivityLog.objects.filter( - user=user_id, appraiser_approved=True - ).order_by("-created_at") + karma_activity_log = ( + KarmaActivityLog.objects.filter(user=user_id, appraiser_approved=True) + .select_related("task") + .order_by("-created_at") + ) if karma_activity_log is None: return CustomResponse( From 1cde7d334b1547da04c133bc1fe0c1d52beec757 Mon Sep 17 00:00:00 2001 From: Aswanth Vc Date: Fri, 11 Oct 2024 22:19:39 +0530 Subject: [PATCH 2/4] feat: alter 1.55 --- alter-scripts/alter-1.55.py | 76 +++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 alter-scripts/alter-1.55.py diff --git a/alter-scripts/alter-1.55.py b/alter-scripts/alter-1.55.py new file mode 100644 index 00000000..debc850f --- /dev/null +++ b/alter-scripts/alter-1.55.py @@ -0,0 +1,76 @@ +import os +import sys +import uuid +import django + +from connection import execute + +os.chdir("..") +sys.path.append(os.getcwd()) +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mulearnbackend.settings") +django.setup() +from utils.utils import generate_code + +from django.db.transaction import atomic + + +def get_circle_meeting_logs(): + query = f"SELECT id, attendees, created_at, updated_at FROM circle_meeting_log" + return execute(query) + + +def create_attendees_table(): + query = """create table circle_meet_attendees ( + id VARCHAR(36) PRIMARY KEY NOT NULL, + meet_id VARCHAR(36) NOT NULL, + user_id VARCHAR(36) NOT NULL, + note VARCHAR(1000), + joined_at DATETIME, + approved_by VARCHAR(36), + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + CONSTRAINT fk_circle_meet_attendees_meet_id FOREIGN KEY (meet_id) REFERENCES circle_meeting_log(id), + CONSTRAINT fk_circle_meet_attendees_user_id FOREIGN KEY (user_id) REFERENCES user(id), + CONSTRAINT fk_circle_meet_attendees_approved_by FOREIGN KEY (approved_by) REFERENCES user(id) + )""" + execute(query) + + +def migrate_attendees_to_table(): + for ( + meet_id, + attendees, + created_at, + updated_at, + ) in get_circle_meeting_logs(): + attendees = attendees.split(",") + meet_code = "OLD" + generate_code(3) + query = f"UPDATE circle_meeting_log SET meet_code = '{meet_code}' WHERE id = '{meet_id}'" + execute(query) + for attendee in attendees: + query = f""" + INSERT INTO circle_meet_attendees (id, meet_id, user_id, joined_at, approved_by, created_at, updated_at) VALUES + ('{str(uuid.uuid4())}', '{meet_id}', '{attendee}', '{updated_at}', '{attendee}', '{created_at}', '{updated_at}'); + """ + execute(query) + + +if __name__ == "__main__": + with atomic(): + create_attendees_table() + execute( + """ + ALTER TABLE circle_meeting_log + ADD COLUMN meet_code VARCHAR(6) NOT NULL AFTER id, + ADD COLUMN pre_requirements VARCHAR(1000) AFTER agenda, + ADD COLUMN is_public BOOLEAN DEFAULT TRUE NOT NULL AFTER pre_requirements, + ADD COLUMn max_attendees INT DEFAULT -1 AFTER pre_requirements, + ADD COLUMN is_started BOOLEAN DEFAULT FALSE NOT NULL AFTER max_attendees, + ADD COLUMN is_report_submitted BOOLEAN DEFAULT FALSE NOT NULL AFTER is_started + """ + ) + migrate_attendees_to_table() + execute("ALTER TABLE circle_meeting_log DROP COLUMN attendees") + execute( + "UPDATE system_setting SET value = '1.55', updated_at = now() WHERE `key` = 'db.version';" + ) From a3638bdec66695acc04e2eb9da7064b9d4871af7 Mon Sep 17 00:00:00 2001 From: Aswanth Vc Date: Fri, 11 Oct 2024 22:45:11 +0530 Subject: [PATCH 3/4] feat : Alter 1.54 --- alter-scripts/alter-1.55.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/alter-scripts/alter-1.55.py b/alter-scripts/alter-1.55.py index debc850f..a993b41b 100644 --- a/alter-scripts/alter-1.55.py +++ b/alter-scripts/alter-1.55.py @@ -15,7 +15,9 @@ def get_circle_meeting_logs(): - query = f"SELECT id, attendees, created_at, updated_at FROM circle_meeting_log" + query = ( + f"SELECT id, attendees, created_at, updated_at, images FROM circle_meeting_log" + ) return execute(query) @@ -37,15 +39,10 @@ def create_attendees_table(): def migrate_attendees_to_table(): - for ( - meet_id, - attendees, - created_at, - updated_at, - ) in get_circle_meeting_logs(): + for meet_id, attendees, created_at, updated_at, images in get_circle_meeting_logs(): attendees = attendees.split(",") meet_code = "OLD" + generate_code(3) - query = f"UPDATE circle_meeting_log SET meet_code = '{meet_code}' WHERE id = '{meet_id}'" + query = f"UPDATE circle_meeting_log SET meet_code = '{meet_code}', is_started = {int(images is not None)}, is_report_submitted = {int(images is not None)} WHERE id = '{meet_id}'" execute(query) for attendee in attendees: query = f""" @@ -61,6 +58,8 @@ def migrate_attendees_to_table(): execute( """ ALTER TABLE circle_meeting_log + MODIFY COLUMN `day` VARCHAR(20), + MODIFY COLUMN meet_time DATETIME, ADD COLUMN meet_code VARCHAR(6) NOT NULL AFTER id, ADD COLUMN pre_requirements VARCHAR(1000) AFTER agenda, ADD COLUMN is_public BOOLEAN DEFAULT TRUE NOT NULL AFTER pre_requirements, From 8b1f7c623717f153a7390bc1cd657f07ce5d3039 Mon Sep 17 00:00:00 2001 From: Aswanth Vc Date: Sat, 12 Oct 2024 22:08:43 +0530 Subject: [PATCH 4/4] feat: apis for learning circle meetups --- alter-scripts/alter-1.55.py | 10 +- api/dashboard/lc/dash_lc_serializer.py | 742 ++++++++++++------------- api/dashboard/lc/dash_lc_view.py | 376 +++++++++++-- api/dashboard/lc/urls.py | 119 +++- db/learning_circle.py | 31 +- utils/types.py | 169 +++--- utils/utils.py | 19 +- 7 files changed, 922 insertions(+), 544 deletions(-) diff --git a/alter-scripts/alter-1.55.py b/alter-scripts/alter-1.55.py index a993b41b..8fd77625 100644 --- a/alter-scripts/alter-1.55.py +++ b/alter-scripts/alter-1.55.py @@ -22,7 +22,7 @@ def get_circle_meeting_logs(): def create_attendees_table(): - query = """create table circle_meet_attendees ( + query = """CREATE TABLE circle_meet_attendees ( id VARCHAR(36) PRIMARY KEY NOT NULL, meet_id VARCHAR(36) NOT NULL, user_id VARCHAR(36) NOT NULL, @@ -40,7 +40,7 @@ def create_attendees_table(): def migrate_attendees_to_table(): for meet_id, attendees, created_at, updated_at, images in get_circle_meeting_logs(): - attendees = attendees.split(",") + attendees = (attendees if attendees else "").split(",") meet_code = "OLD" + generate_code(3) query = f"UPDATE circle_meeting_log SET meet_code = '{meet_code}', is_started = {int(images is not None)}, is_report_submitted = {int(images is not None)} WHERE id = '{meet_id}'" execute(query) @@ -59,13 +59,15 @@ def migrate_attendees_to_table(): """ ALTER TABLE circle_meeting_log MODIFY COLUMN `day` VARCHAR(20), - MODIFY COLUMN meet_time DATETIME, + ADD COLUMN title VARCHAR(100) NOT NULL AFTER circle_id, + ADD COLUMN location VARCHAR(200) NOT NULL AFTER meet_place, ADD COLUMN meet_code VARCHAR(6) NOT NULL AFTER id, ADD COLUMN pre_requirements VARCHAR(1000) AFTER agenda, ADD COLUMN is_public BOOLEAN DEFAULT TRUE NOT NULL AFTER pre_requirements, ADD COLUMn max_attendees INT DEFAULT -1 AFTER pre_requirements, ADD COLUMN is_started BOOLEAN DEFAULT FALSE NOT NULL AFTER max_attendees, - ADD COLUMN is_report_submitted BOOLEAN DEFAULT FALSE NOT NULL AFTER is_started + ADD COLUMN report_text VARCHAR(1000) AFTER max_attendees, + ADD COLUMN is_report_submitted BOOLEAN DEFAULT FALSE NOT NULL AFTER report_text """ ) migrate_attendees_to_table() diff --git a/api/dashboard/lc/dash_lc_serializer.py b/api/dashboard/lc/dash_lc_serializer.py index 8ec4a9f5..aa4bf8b9 100644 --- a/api/dashboard/lc/dash_lc_serializer.py +++ b/api/dashboard/lc/dash_lc_serializer.py @@ -5,7 +5,13 @@ from django.db.models import Sum from rest_framework import serializers -from db.learning_circle import LearningCircle, UserCircleLink, InterestGroup, CircleMeetingLog +from db.learning_circle import ( + CircleMeetAttendees, + LearningCircle, + UserCircleLink, + InterestGroup, + CircleMeetingLog, +) from db.organization import UserOrganizationLink from db.task import KarmaActivityLog from db.task import TaskList, UserIgLink, Wallet @@ -17,10 +23,10 @@ class LearningCircleSerializer(serializers.ModelSerializer): - created_by = serializers.CharField(source='created_by.full_name') - updated_by = serializers.CharField(source='updated_by.full_name') - ig = serializers.CharField(source='ig.name') - org = serializers.CharField(source='org.title', allow_null=True) + created_by = serializers.CharField(source="created_by.full_name") + updated_by = serializers.CharField(source="updated_by.full_name") + ig = serializers.CharField(source="ig.name") + org = serializers.CharField(source="org.title", allow_null=True) member_count = serializers.SerializerMethodField() class Meta: @@ -37,114 +43,87 @@ class Meta: "updated_at", "created_by", "created_at", - "member_count" + "member_count", ] def get_member_count(self, obj): - return obj.user_circle_link_circle.filter( - circle_id=obj.id, - accepted=1 - ).count() + return obj.user_circle_link_circle.filter(circle_id=obj.id, accepted=1).count() class LearningCircleMainSerializer(serializers.ModelSerializer): - ig_name = serializers.CharField(source='ig.name') - org_name = serializers.CharField(source='org.title', allow_null=True) + ig_name = serializers.CharField(source="ig.name") + org_name = serializers.CharField(source="org.title", allow_null=True) member_count = serializers.SerializerMethodField() members = serializers.SerializerMethodField() lead_name = serializers.SerializerMethodField() ismember = serializers.SerializerMethodField() karma = serializers.SerializerMethodField() + class Meta: model = LearningCircle fields = [ - 'id', - 'name', - 'ig_name', - 'org_name', - 'member_count', - 'members', - 'meet_place', - 'meet_time', - 'lead_name', - 'ismember', - 'karma' + "id", + "name", + "ig_name", + "org_name", + "member_count", + "members", + "meet_place", + "meet_time", + "lead_name", + "ismember", + "karma", ] def get_lead_name(self, obj): user_circle_link = obj.user_circle_link_circle.filter( - circle=obj, - accepted=1, - lead=True + circle=obj, accepted=1, lead=True ).first() return user_circle_link.user.full_name if user_circle_link else None def get_member_count(self, obj): - return obj.user_circle_link_circle.filter( - circle=obj, - accepted=1 - ).count() + return obj.user_circle_link_circle.filter(circle=obj, accepted=1).count() def get_members(self, obj): - user_circle_link = obj.user_circle_link_circle.filter( - circle=obj, - accepted=1 - ) + user_circle_link = obj.user_circle_link_circle.filter(circle=obj, accepted=1) def get_ismember(self, obj): - user_id = self.context.get('user_id') + user_id = self.context.get("user_id") return obj.user_circle_link_circle.filter( - circle=obj, - user_id=user_id, - accepted=True + circle=obj, user_id=user_id, accepted=True ).exists() - - return [ - { - 'username': f'{member.user.full_name}' - } - for member in user_circle_link - ] + + return [{"username": f"{member.user.full_name}"} for member in user_circle_link] + def get_karma(self, obj): - + karma_activity_log = KarmaActivityLog.objects.filter( user__user_circle_link_user__circle=obj, - ).aggregate( - karma=Sum( - 'karma' - ) - )['karma'] + ).aggregate(karma=Sum("karma"))["karma"] return karma_activity_log if karma_activity_log else 0 class LearningCircleCreateSerializer(serializers.ModelSerializer): - ig = serializers.CharField(required=True, error_messages={ - 'required': 'ig field must not be left blank.' - }) - name = serializers.CharField(required=True, error_messages={ - 'required': 'name field must not be left blank.'} + ig = serializers.CharField( + required=True, error_messages={"required": "ig field must not be left blank."} + ) + name = serializers.CharField( + required=True, error_messages={"required": "name field must not be left blank."} ) class Meta: model = LearningCircle - fields = [ - "name", - "ig" - ] + fields = ["name", "ig"] def validate(self, data): - user_id = self.context.get('user_id') - ig_id = data.get('ig') + user_id = self.context.get("user_id") + ig_id = data.get("ig") - if not InterestGroup.objects.filter( - id=ig_id - ).exists(): - raise serializers.ValidationError( - "Invalid interest group" - ) + if not InterestGroup.objects.filter(id=ig_id).exists(): + raise serializers.ValidationError("Invalid interest group") # org_link = UserOrganizationLink.objects.filter(user_id=user_id, # org__org_type=OrganizationType.COLLEGE.value).first() @@ -152,9 +131,7 @@ def validate(self, data): # raise serializers.ValidationError("User must be associated with a college organization") if UserCircleLink.objects.filter( - user_id=user_id, - circle__ig_id=ig_id, - accepted=True + user_id=user_id, circle__ig_id=ig_id, accepted=True ).exists(): raise serializers.ValidationError( "Already a member of a learning circle with the same interest group" @@ -162,31 +139,25 @@ def validate(self, data): return data def create(self, validated_data): - user_id = self.context.get('user_id') + user_id = self.context.get("user_id") org_link = UserOrganizationLink.objects.filter( - user_id=user_id, - org__org_type=OrganizationType.COLLEGE.value + user_id=user_id, org__org_type=OrganizationType.COLLEGE.value ).first() - ig = InterestGroup.objects.filter( - id=validated_data.get( - 'ig' - ) - ).first() + ig = InterestGroup.objects.filter(id=validated_data.get("ig")).first() if org_link: if len(org_link.org.code) > 4: - code = validated_data.get( - 'name')[:2] + ig.code + org_link.org.code[:4] + code = validated_data.get("name")[:2] + ig.code + org_link.org.code[:4] else: - code = validated_data.get( - 'name')[:2] + ig.code + org_link.org.code + code = validated_data.get("name")[:2] + ig.code + org_link.org.code else: - code = validated_data.get('name')[:2] + ig.code + code = validated_data.get("name")[:2] + ig.code existing_codes = set( - LearningCircle.objects.values_list('circle_code', flat=True)) + LearningCircle.objects.values_list("circle_code", flat=True) + ) i = 1 while code.upper() in existing_codes: code = code + str(i) @@ -194,25 +165,27 @@ def create(self, validated_data): if org_link: lc = LearningCircle.objects.create( id=uuid.uuid4(), - name=validated_data.get('name'), + name=validated_data.get("name"), circle_code=code.upper(), ig=ig, org=org_link.org, updated_by_id=user_id, updated_at=DateTimeUtils.get_current_utc_time(), created_by_id=user_id, - created_at=DateTimeUtils.get_current_utc_time()) + created_at=DateTimeUtils.get_current_utc_time(), + ) else: lc = LearningCircle.objects.create( id=uuid.uuid4(), - name=validated_data.get('name'), + name=validated_data.get("name"), circle_code=code.upper(), ig=ig, org=None, updated_by_id=user_id, updated_at=DateTimeUtils.get_current_utc_time(), created_by_id=user_id, - created_at=DateTimeUtils.get_current_utc_time()) + created_at=DateTimeUtils.get_current_utc_time(), + ) UserCircleLink.objects.create( id=uuid.uuid4(), @@ -221,7 +194,7 @@ def create(self, validated_data): lead=True, accepted=1, accepted_at=DateTimeUtils.get_current_utc_time(), - created_at=DateTimeUtils.get_current_utc_time() + created_at=DateTimeUtils.get_current_utc_time(), ) return lc @@ -258,31 +231,22 @@ def create(self, validated_data): # ) # return user_circle_link + class LearningCircleMemberListSerializer(serializers.ModelSerializer): - full_name = serializers.CharField(source='user.full_name') - discord_id = serializers.CharField(source='user.discord_id') - level = serializers.CharField(source='user.user_lvl_link_user.level.name') + full_name = serializers.CharField(source="user.full_name") + discord_id = serializers.CharField(source="user.discord_id") + level = serializers.CharField(source="user.user_lvl_link_user.level.name") lc_karma = serializers.SerializerMethodField() class Meta: model = UserCircleLink - fields = [ - 'full_name', - 'discord_id', - 'level', - 'lc_karma' - ] + fields = ["full_name", "discord_id", "level", "lc_karma"] def get_lc_karma(self, obj): - circle_id = self.context.get('circle_id') + circle_id = self.context.get("circle_id") karma_activity_log = KarmaActivityLog.objects.filter( - user=obj.user, - task__ig__learning_circle_ig__id=circle_id - ).aggregate( - karma=Sum( - 'karma' - ) - )['karma'] + user=obj.user, task__ig__learning_circle_ig__id=circle_id + ).aggregate(karma=Sum("karma"))["karma"] return karma_activity_log if karma_activity_log else 0 @@ -293,58 +257,52 @@ class Meta: fields = [] def create(self, validated_data): - user_id = self.context.get('user_id') - circle_id = self.context.get('circle_id') + 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 + circle_id=circle_id, accepted=True ).count() ig_id = LearningCircle.objects.get(pk=circle_id).ig_id if entry := UserCircleLink.objects.filter( - circle_id=circle_id, - user_id=user_id + circle_id=circle_id, user_id=user_id ).first(): 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 + 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" - ) - - validated_data['id'] = uuid.uuid4() - validated_data['user_id'] = user_id - validated_data['circle_id'] = circle_id - validated_data['lead'] = False - validated_data['accepted'] = None - validated_data['accepted_at'] = None - validated_data['created_at'] = DateTimeUtils.get_current_utc_time() + raise serializers.ValidationError("Maximum member count reached") + + validated_data["id"] = uuid.uuid4() + validated_data["user_id"] = user_id + validated_data["circle_id"] = circle_id + validated_data["lead"] = False + validated_data["accepted"] = None + validated_data["accepted_at"] = None + validated_data["created_at"] = DateTimeUtils.get_current_utc_time() return UserCircleLink.objects.create(**validated_data) class LearningCircleDetailsSerializer(serializers.ModelSerializer): - college = serializers.CharField(source='org.title', allow_null=True) + college = serializers.CharField(source="org.title", allow_null=True) total_karma = serializers.SerializerMethodField() members = serializers.SerializerMethodField() pending_members = serializers.SerializerMethodField() rank = serializers.SerializerMethodField() is_lead = serializers.SerializerMethodField() is_member = serializers.SerializerMethodField() - ig_code = serializers.CharField(source='ig.code') - ig_id = serializers.CharField(source='ig.id') - ig_name = serializers.CharField(source='ig.name') + ig_code = serializers.CharField(source="ig.code") + ig_id = serializers.CharField(source="ig.id") + ig_name = serializers.CharField(source="ig.name") previous_meetings = serializers.SerializerMethodField() class Meta: @@ -370,19 +328,15 @@ class Meta: ] def get_is_member(self, obj): - user = self.context.get('user_id') + user = self.context.get("user_id") return obj.user_circle_link_circle.filter( - user=user, - circle=obj, - accepted=True + user=user, circle=obj, accepted=True ).exists() def get_is_lead(self, obj): - user = self.context.get('user_id') + user = self.context.get("user_id") return obj.user_circle_link_circle.filter( - user=user, - circle=obj, - lead=True + user=user, circle=obj, lead=True ).exists() def get_total_karma(self, obj): @@ -392,10 +346,8 @@ def get_total_karma(self, obj): user__user_circle_link_user__accepted=True, task__ig=obj.ig, appraiser_approved=True, - ).aggregate( - total_karma=Sum( - 'karma' - ))['total_karma'] or 0 + ).aggregate(total_karma=Sum("karma"))["total_karma"] + or 0 ) def get_members(self, obj): @@ -406,72 +358,63 @@ def get_pending_members(self, obj): def _get_member_info(self, obj, accepted): - members = obj.user_circle_link_circle.filter( - circle=obj, - accepted=accepted - ) + members = obj.user_circle_link_circle.filter(circle=obj, accepted=accepted) member_info = [] for member in members: - total_ig_karma = KarmaActivityLog.objects.filter( - task__ig=member.circle.ig, - user=member.user, - appraiser_approved=True - ).aggregate( - total_karma=Sum( - 'karma' - ))['total_karma'] or 0 - - member_info.append({ - 'id': member.user.id, - 'username': f'{member.user.full_name}', - 'profile_pic': f'{member.user.profile_pic}' or None, - 'karma': total_ig_karma, - 'is_lead': member.lead, - 'level': member.user.user_lvl_link_user.level.level_order - }) + total_ig_karma = ( + KarmaActivityLog.objects.filter( + task__ig=member.circle.ig, user=member.user, appraiser_approved=True + ).aggregate(total_karma=Sum("karma"))["total_karma"] + or 0 + ) + + member_info.append( + { + "id": member.user.id, + "username": f"{member.user.full_name}", + "profile_pic": f"{member.user.profile_pic}" or None, + "karma": total_ig_karma, + "is_lead": member.lead, + "level": member.user.user_lvl_link_user.level.level_order, + } + ) return member_info def get_rank(self, obj): - total_karma = KarmaActivityLog.objects.filter( - user__user_circle_link_user__circle=obj, - user__user_circle_link_user__accepted=True, - task__ig=obj.ig, - appraiser_approved=True - ).aggregate( - total_karma=Sum( - 'karma' - ) - )['total_karma'] or 0 + total_karma = ( + KarmaActivityLog.objects.filter( + user__user_circle_link_user__circle=obj, + user__user_circle_link_user__accepted=True, + task__ig=obj.ig, + appraiser_approved=True, + ).aggregate(total_karma=Sum("karma"))["total_karma"] + or 0 + ) - circle_ranks = {obj.name: {'total_karma': total_karma}} + circle_ranks = {obj.name: {"total_karma": total_karma}} - all_learning_circles = LearningCircle.objects.filter( - ig=obj.ig - ).exclude( + all_learning_circles = LearningCircle.objects.filter(ig=obj.ig).exclude( id=obj.id ) for lc in all_learning_circles: - total_karma_lc = KarmaActivityLog.objects.filter( - user__user_circle_link_user__circle=lc, - user__user_circle_link_user__accepted=True, - task__ig=lc.ig, - appraiser_approved=True - ).aggregate( - total_karma=Sum( - 'karma' - ) - )['total_karma'] or 0 + total_karma_lc = ( + KarmaActivityLog.objects.filter( + user__user_circle_link_user__circle=lc, + user__user_circle_link_user__accepted=True, + task__ig=lc.ig, + appraiser_approved=True, + ).aggregate(total_karma=Sum("karma"))["total_karma"] + or 0 + ) - circle_ranks[lc.name] = {'total_karma': total_karma_lc} + circle_ranks[lc.name] = {"total_karma": total_karma_lc} sorted_ranks = sorted( - circle_ranks.items(), - key=lambda x: x[1]['total_karma'], - reverse=True + circle_ranks.items(), key=lambda x: x[1]["total_karma"], reverse=True ) return next( @@ -484,12 +427,14 @@ def get_rank(self, obj): ) def get_previous_meetings(self, obj): - return obj.circle_meeting_log_learning_circle.all().values( - "id", - "meet_time", - "day", - ).order_by( - '-meet_time' + return ( + obj.circle_meeting_log_learning_circle.all() + .values( + "id", + "meet_time", + "day", + ) + .order_by("-meet_time") ) return previous_meetings @@ -502,15 +447,10 @@ class LearningCircleStatsSerializer(serializers.ModelSerializer): class Meta: model = LearningCircle - fields = [ - "interest_group", - "college", - "learning_circle", - "total_no_of_users" - ] + fields = ["interest_group", "college", "learning_circle", "total_no_of_users"] def get_interest_group(self, obj): - return obj.values('ig_id').distinct().count() + return obj.values("ig_id").distinct().count() def get_total_no_of_users(self, obj): return UserCircleLink.objects.filter(accepted=True).count() @@ -519,7 +459,7 @@ def get_learning_circle(self, obj): return obj.count() def get_college(self, obj): - return obj.values('org_id').distinct().count() + return obj.values("org_id").distinct().count() class LearningCircleUpdateSerializer(serializers.ModelSerializer): @@ -527,17 +467,17 @@ class LearningCircleUpdateSerializer(serializers.ModelSerializer): class Meta: model = UserCircleLink - fields = [ - "is_accepted" - ] + fields = ["is_accepted"] def update(self, instance, validated_data): - is_accepted = validated_data.get('is_accepted', instance.accepted) + is_accepted = validated_data.get("is_accepted", instance.accepted) entry = UserCircleLink.objects.filter( - circle_id=instance.circle, accepted=True).count() + circle_id=instance.circle, accepted=True + ).count() if is_accepted and entry >= 5: raise serializers.ValidationError( - "Cannot accept the link. Maximum members reached for this circle.") + "Cannot accept the link. Maximum members reached for this circle." + ) instance.accepted = is_accepted instance.accepted_at = DateTimeUtils.get_current_utc_time() @@ -549,18 +489,16 @@ def destroy(self, obj): class LearningCircleNoteSerializer(serializers.ModelSerializer): - note = serializers.CharField(required=True, error_messages={ - 'required': 'note field must not be left blank.' - }) + note = serializers.CharField( + required=True, error_messages={"required": "note field must not be left blank."} + ) class Meta: model = LearningCircle - fields = [ - "note" - ] + fields = ["note"] def update(self, instance, validated_data): - instance.note = validated_data.get('note') + instance.note = validated_data.get("note") instance.updated_at = DateTimeUtils.get_current_utc_time() instance.save() return instance @@ -572,176 +510,78 @@ class ScheduleMeetingSerializer(serializers.ModelSerializer): class Meta: model = LearningCircle - fields = [ - "meet_place", - "meet_time", - "day" - ] + fields = ["meet_place", "meet_time", "day"] def update(self, instance, validated_data): - instance.meet_time = validated_data.get( - 'meet_time', instance.meet_time) - instance.meet_place = validated_data.get( - 'meet_place', instance.meet_place) - instance.day = validated_data.get('day', instance.day) + instance.meet_time = validated_data.get("meet_time", instance.meet_time) + instance.meet_place = validated_data.get("meet_place", instance.meet_place) + instance.day = validated_data.get("day", instance.day) instance.updated_at = DateTimeUtils.get_current_utc_time() instance.save() return instance class MeetRecordsCreateEditDeleteSerializer(serializers.ModelSerializer): - attendees_details = serializers.SerializerMethodField() - meet_created_by = serializers.CharField( - source='created_by.full_name', required=False) - meet_created_at = serializers.CharField( - source='created_at', required=False) - meet_id = serializers.CharField(source='id', required=False) - meet_time = serializers.CharField(required=False) images = serializers.ImageField(required=True) image = serializers.SerializerMethodField(required=False) + report_text = serializers.CharField(required=True) class Meta: model = CircleMeetingLog fields = [ - "meet_id", - "meet_place", - "meet_time", "images", - "attendees", - "agenda", - "attendees_details", - "meet_created_by", - "meet_created_at", - 'image', + "image", + "report_text", ] def get_image(self, obj): - return f"{config('BE_DOMAIN_NAME')}/{settings.MEDIA_URL}{media}" if (media := obj.images) else None - - def get_attendees_details(self, obj): - attendees_list = obj.attendees.split(',') - - attendees_details_list = [] - for user_id in attendees_list: - user = User.objects.get(id=user_id) - attendees_details_list.append({ - 'full_name': user.full_name, - 'profile_pic': user.profile_pic, - }) - - return attendees_details_list + return ( + f"{config('BE_DOMAIN_NAME')}/{settings.MEDIA_URL}{media}" + if (media := obj.images) + else None + ) def create(self, validated_data): - today_date = DateTimeUtils.get_current_utc_time().date() - meet_time_string = self.context.get('time') - meet_time = datetime.strptime(meet_time_string, "%H:%M:%S").time() - - combined_meet_time = datetime.combine(today_date, meet_time) - - validated_data['id'] = uuid.uuid4() - validated_data['meet_time'] = combined_meet_time - validated_data['day'] = DateTimeUtils.get_current_utc_time().strftime( - '%A') - validated_data['circle_id'] = self.context.get('circle_id') - validated_data['created_by_id'] = self.context.get('user_id') - validated_data['updated_by_id'] = self.context.get('user_id') - attendees_list = validated_data['attendees'].split(',') - - user_id = self.context.get('user_id') - task = TaskList.objects.filter(hashtag=Lc.TASK_HASHTAG.value).first() - - for attendee_id in attendees_list: + validated_data["updated_by_id"] = self.context.get("user_id") + user = self.context.get("user") + meet = self.context.get("meet") + task = TaskList.objects.filter(hashtag=Lc.RECORD_SUBMIT_HASHTAG.value).first() + attendees = ( + CircleMeetAttendees.objects.filter( + meet=meet, + joined_at__isnull=False, + ) + .select_related("user") + .only("user_id", "user__full_name") + ) + for attendee in attendees: # Send karma points KarmaActivityLog.objects.create( id=uuid.uuid4(), - user_id=attendee_id, - karma=Lc.KARMA.value, + user_id=attendee.user_id, + karma=Lc.RECORD_SUBMIT_KARMA.value, task=task, - updated_by_id=user_id, - created_by_id=user_id, + 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", ) - # Update the wallet with karma points - wallet = Wallet.objects.filter(user_id=attendee_id).first() - wallet.karma += Lc.KARMA.value + wallet = Wallet.objects.filter(user_id=attendee.user_id).first() + wallet.karma += Lc.RECORD_SUBMIT_KARMA.value wallet.karma_last_updated_at = DateTimeUtils.get_current_utc_time() wallet.updated_at = DateTimeUtils.get_current_utc_time() wallet.save() - - - return CircleMeetingLog.objects.create(**validated_data) - - def update(self, instance, validated_data): - instance.updated_by_id = self.context.get('user_id') - instance.save() - return instance - - def validate_attendees(self, attendees): - task = TaskList.objects.filter(hashtag=Lc.TASK_HASHTAG.value).first() - - attendees_list = attendees.split(',') - - user_id = self.context.get('user_id') - for user in attendees_list: - KarmaActivityLog.objects.bulk_create([ - KarmaActivityLog( - id=uuid.uuid4(), - user_id=user, - karma=Lc.KARMA.value, - task=task, - updated_by_id=user_id, - created_by_id=user_id, - ) - ]) - wallet = Wallet.objects.filter(user_id=user).first() - wallet.karma += Lc.KARMA.value - wallet.karma_last_updated_at = DateTimeUtils.get_current_utc_time() - wallet.updated_at = DateTimeUtils.get_current_utc_time() - wallet.save() - # DiscordWebhooks.general_updates( - # WebHookCategory.KARMA_INFO.value, - # WebHookCategory.UPDATE.value, - # user, - # Lc.KARMA.value, - # task.title, - # task.hashtag - # ) - - - return attendees - - def validate(self, data): - circle_id = self.context.get('circle_id') - - today_date = DateTimeUtils.get_current_utc_time().date() - time = self.context.get('time') - time = datetime.strptime(time, "%H:%M:%S").time() - combined_meet_time = datetime.combine(today_date, time) - - start_of_day, end_of_day = get_today_start_end(combined_meet_time) - start_of_week, end_of_week = get_week_start_end(combined_meet_time) - - if CircleMeetingLog.objects.filter( - circle_id=circle_id, - meet_time__range=( - start_of_day, - end_of_day - ) - ).exists(): - raise serializers.ValidationError( - f'Another meet already scheduled on {today_date}') - - if CircleMeetingLog.objects.filter( - circle_id=circle_id, - meet_time__range=( - start_of_week, - end_of_week - ) - ).count() >= 5: - raise serializers.ValidationError( - 'you can create only 5 meeting in a week') - - return data + meet.is_report_submitted = True + meet.report_text = validated_data.get("report_text") + meet.images = validated_data.get("images") + meet.save() + return meet class ListAllMeetRecordsSerializer(serializers.ModelSerializer): @@ -755,17 +595,16 @@ class Meta: class IgTaskDetailsSerializer(serializers.ModelSerializer): - task_title = serializers.CharField(source='title') - task_level = serializers.CharField(source='level.level_order') - task_level_karma = serializers.CharField(source='level.karma') - task_karma = serializers.CharField(source='karma') - task_hashtag = serializers.CharField(source='hashtag') + task_title = serializers.CharField(source="title") + task_level = serializers.CharField(source="level.level_order") + task_level_karma = serializers.CharField(source="level.karma") + task_karma = serializers.CharField(source="karma") + task_hashtag = serializers.CharField(source="hashtag") completed_users = serializers.SerializerMethodField() class Meta: model = TaskList fields = [ - "task_title", "task_karma", "task_level", @@ -776,7 +615,8 @@ class Meta: def get_completed_users(self, obj): karma_activity_log = KarmaActivityLog.objects.filter( - task=obj, appraiser_approved=True).select_related('user') + task=obj, appraiser_approved=True + ).select_related("user") completed_users = [] for karma in karma_activity_log: completed_users.append(karma.user.id) @@ -789,21 +629,175 @@ class Meta: fields = [] def validate(self, data): - user = self.context.get('user') - circle_id = self.context.get('circle_id') + user = self.context.get("user") + circle_id = self.context.get("circle_id") if UserCircleLink.objects.filter(user=user).exists(): raise serializers.ValidationError( - 'user already part of the learning circle') + "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') + "maximum members reached in learning circle" + ) return data def create(self, validated_data): - validated_data['id'] = uuid.uuid4() - validated_data['user'] = self.context.get('user') - validated_data['circle_id'] = self.context.get('circle_id') + validated_data["id"] = uuid.uuid4() + validated_data["user"] = self.context.get("user") + validated_data["circle_id"] = self.context.get("circle_id") return UserCircleLink.objects.create(**validated_data) + + +class CircleMeetDetailSerializer(serializers.ModelSerializer): + is_started = serializers.BooleanField(read_only=True) + id = serializers.CharField(read_only=True) + title = serializers.CharField(required=True) + location = serializers.CharField(required=True) + meet_time = serializers.DateTimeField(required=True) + meet_place = serializers.CharField(required=True) + agenda = serializers.CharField(required=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_interested = serializers.SerializerMethodField() + joined_at = serializers.SerializerMethodField() + total_interested = serializers.SerializerMethodField() + total_joined = serializers.SerializerMethodField() + lc_members = serializers.SerializerMethodField() + is_lc_member = serializers.SerializerMethodField() + + def get_is_lc_member(self, obj): + user_id = self.context.get("user_id") + return UserCircleLink.objects.filter( + user_id=user_id, circle_id=obj.circle_id, accepted=True + ).exists() + + def get_lc_members(self, obj): + return UserCircleLink.objects.filter( + circle=obj.circle_id, accepted=True + ).count() + + def get_total_interested(self, obj): + return CircleMeetAttendees.objects.filter(meet=obj).count() + + def get_total_joined(self, obj): + return CircleMeetAttendees.objects.filter( + meet=obj, joined_at__isnull=False + ).count() + + def get_is_interested(self, obj): + user_id = self.context.get("user_id") + return CircleMeetAttendees.objects.filter(meet=obj, user_id=user_id).exists() + + def get_joined_at(self, obj): + user_id = self.context.get("user_id") + return ( + CircleMeetAttendees.objects.filter( + meet=obj, user_id=user_id, joined_at__isnull=False + ) + .values_list("joined_at", flat=True) + .first() + ) + + 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_interested", + "joined_at", + "total_interested", + "total_joined", + "lc_members", + "is_lc_member", + ] + + +class CircleMeetSerializer(serializers.ModelSerializer): + is_started = serializers.BooleanField(read_only=True) + id = serializers.CharField(read_only=True) + title = serializers.CharField(required=True) + location = serializers.CharField(required=True) + meet_time = serializers.DateTimeField(required=True) + meet_place = serializers.CharField(required=True) + agenda = serializers.CharField(required=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() + + 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", + ] diff --git a/api/dashboard/lc/dash_lc_view.py b/api/dashboard/lc/dash_lc_view.py index 49ccb2c6..f204e70c 100644 --- a/api/dashboard/lc/dash_lc_view.py +++ b/api/dashboard/lc/dash_lc_view.py @@ -1,3 +1,4 @@ +from datetime import timedelta import uuid from collections import defaultdict @@ -8,13 +9,18 @@ from rest_framework.views import APIView from api.notification.notifications_utils import NotificationUtils -from db.learning_circle import CircleMeetingLog, LearningCircle, UserCircleLink -from db.task import TaskList +from db.learning_circle import ( + CircleMeetAttendees, + CircleMeetingLog, + LearningCircle, + UserCircleLink, +) +from db.task import KarmaActivityLog, TaskList, Wallet from db.user import User -from utils.permission import JWTUtils from utils.response import CustomResponse +from utils.types import Lc from utils.utils import DateTimeUtils, send_template_mail - +from utils.permission import CustomizePermission, JWTUtils from .dash_ig_helper import ( get_today_start_end, get_week_start_end, @@ -23,6 +29,7 @@ ) from .dash_lc_serializer import ( AddMemberSerializer, + CircleMeetDetailSerializer, IgTaskDetailsSerializer, LearningCircleCreateSerializer, LearningCircleDetailsSerializer, @@ -35,6 +42,7 @@ LearningCircleUpdateSerializer, MeetRecordsCreateEditDeleteSerializer, ScheduleMeetingSerializer, + CircleMeetSerializer, ) @@ -373,57 +381,57 @@ def delete(self, request, circle_id): return CustomResponse(general_message="Left").get_success_response() -class SingleReportDetailAPI(APIView): - def get(self, request, circle_id, report_id=None): - circle_meeting_log = CircleMeetingLog.objects.get(id=report_id) - - serializer = MeetRecordsCreateEditDeleteSerializer( - circle_meeting_log, many=False - ) - - return CustomResponse(response=serializer.data).get_success_response() - - def post(self, request, circle_id): - user_id = JWTUtils.fetch_user_id(request) - time = request.data.get("time") - - serializer = MeetRecordsCreateEditDeleteSerializer( - data=request.data, - context={"user_id": user_id, "circle_id": circle_id, "time": time}, - ) - if serializer.is_valid(): - circle_meet_log = serializer.save() - - return CustomResponse( - general_message=f"Meet scheduled at {circle_meet_log.meet_time}" - ).get_success_response() - - return CustomResponse(message=serializer.errors).get_failure_response() - - # def patch(self, request, circle_id): - # user_id = JWTUtils.fetch_user_id(request) - # - # learning_circle = LearningCircle.objects.filter( - # id=circle_id - # ).first() - # - # serializer = MeetRecordsCreateEditDeleteSerializer( - # learning_circle, - # data=request.data, - # context={ - # 'user_id': user_id - # } - # ) - # if serializer.is_valid(): - # serializer.save() - # - # return CustomResponse( - # general_message='Meet updated successfully' - # ).get_success_response() - # - # return CustomResponse( - # message=serializer.errors - # ).get_failure_response() +# class SingleReportDetailAPI(APIView): +# def get(self, request, circle_id, report_id=None): +# circle_meeting_log = CircleMeetingLog.objects.get(id=report_id) + +# serializer = MeetRecordsCreateEditDeleteSerializer( +# circle_meeting_log, many=False +# ) + +# return CustomResponse(response=serializer.data).get_success_response() + +# def post(self, request, circle_id): +# user_id = JWTUtils.fetch_user_id(request) +# time = request.data.get("time") + +# serializer = MeetRecordsCreateEditDeleteSerializer( +# data=request.data, +# context={"user_id": user_id, "circle_id": circle_id, "time": time}, +# ) +# if serializer.is_valid(): +# circle_meet_log = serializer.save() + +# return CustomResponse( +# general_message=f"Meet scheduled at {circle_meet_log.meet_time}" +# ).get_success_response() + +# return CustomResponse(message=serializer.errors).get_failure_response() + +# def patch(self, request, circle_id): +# user_id = JWTUtils.fetch_user_id(request) +# +# learning_circle = LearningCircle.objects.filter( +# id=circle_id +# ).first() +# +# serializer = MeetRecordsCreateEditDeleteSerializer( +# learning_circle, +# data=request.data, +# context={ +# 'user_id': user_id +# } +# ) +# if serializer.is_valid(): +# serializer.save() +# +# return CustomResponse( +# general_message='Meet updated successfully' +# ).get_success_response() +# +# return CustomResponse( +# message=serializer.errors +# ).get_failure_response() class LearningCircleLeadTransfer(APIView): @@ -701,3 +709,261 @@ def get(self, request, circle_id): ).get_failure_response() return CustomResponse(general_message="success").get_success_response() + + +class CircleMeetAPI(APIView): + """ + Create a new meetup, and list all meeetups in an LC + """ + + permission_classes = [CustomizePermission] + + def post(self, request, circle_id): + user_id = JWTUtils.fetch_user_id(request) + user = User.objects.filter(id=user_id).first() + if not user: + return CustomResponse(general_message="Invalid user").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() + + return CustomResponse( + general_message=f"Meet scheduled at {circle_meet_log.meet_time}" + ).get_success_response() + + return CustomResponse(message=serializer.errors).get_failure_response() + + def get(self, request, circle_id): + user_id = JWTUtils.fetch_user_id(request) + up_coming_meeting = CircleMeetingLog.objects.filter( + meet_time__gte=DateTimeUtils.get_current_utc_time(), + circle_id=circle_id, + is_report_submitted=False, + ).order_by("-created_at") + past_meeting = CircleMeetingLog.objects.exclude( + meet_time__gte=DateTimeUtils.get_current_utc_time(), + circle_id=circle_id, + is_report_submitted=False, + ).order_by("-created_at")[:2] + + return CustomResponse( + response={ + "meetups": CircleMeetSerializer( + up_coming_meeting, many=True, context={"user_id": user_id} + ).data, + "past": CircleMeetSerializer(past_meeting, many=True).data, + } + ).get_success_response() + + +class CircleMeetReportSubmitAPI(APIView): + """ + Submit report for meetup + """ + + 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() + if not user: + return CustomResponse(general_message="Invalid user").get_failure_response() + participant_count = CircleMeetAttendees.objects.filter( + meet=meet, joined_at__isnull=False + ).count() + if participant_count < 2: + return CustomResponse( + general_message="Minimum 2 participants are required to submit the report" + ).get_failure_response() + serializer = MeetRecordsCreateEditDeleteSerializer( + data=request.data, + context={"user": user, "meet": meet}, + ) + if serializer.is_valid(): + serializer.create(serializer.validated_data) + return CustomResponse( + general_message=f"Report submitted successfully." + ).get_success_response() + + return CustomResponse(message=serializer.errors).get_failure_response() + + +class CircleMeetDetailAPI(APIView): + """ + Get details of a meetup + """ + + def get(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() + if not user: + return CustomResponse(general_message="Invalid user").get_failure_response() + if not meet: + return CustomResponse( + general_message="Invalid meeting" + ).get_failure_response() + serializer = CircleMeetDetailSerializer( + meet, many=False, context={"user_id": user_id} + ) + return CustomResponse(response=serializer.data).get_success_response() + + +class CircleMeetListAPI(APIView): + """ + List all meetups available + """ + + def get(self, request): + meet_id = request.query_params.get("meet_id") + if meet_id: + circle_meets = CircleMeetingLog.objects.filter(id=meet_id).first() + serializer = CircleMeetSerializer(circle_meets, many=False) + return CustomResponse(response=serializer.data).get_success_response() + circle_meets = ( + CircleMeetingLog.objects.filter( + meet_time__gte=DateTimeUtils.get_current_utc_time() + - timedelta(minutes=30), + is_report_submitted=False, + ) + .order_by("-created_at") + .select_related("circle", "circle__org", "circle__ig") + ) + filters = Q() + if district_id := request.data.get("district_id"): + filters &= Q(org__district_id=district_id) + if category := request.data.get("category"): + filters &= Q(ig__category=category) + if ig := request.data.get("ig_id"): + filters &= Q(circle__ig_id=ig) + try: + user_id = JWTUtils.fetch_user_id(request) + filters &= Q(circle__user_circle_link_circle__user_id=user_id) | Q( + is_public=True + ) + except: + filters &= Q(is_public=True) + circle_meets = circle_meets.filter(filters) + serializer = CircleMeetSerializer(circle_meets, many=True) + return CustomResponse(response=serializer.data).get_success_response() + + +class CircleMeetInterestedAPI(APIView): + """ + Show interest to join a meetup + """ + + def post(self, request, meet_id): + notes = request.data.get("notes") + user_id = JWTUtils.fetch_user_id(request) + user = User.objects.filter(id=user_id).first() + meet = CircleMeetingLog.objects.filter(id=meet_id).first() + if not user: + return CustomResponse(general_message="Invalid user").get_failure_response() + if not meet: + return CustomResponse( + general_message="Invalid meeting" + ).get_failure_response() + if CircleMeetAttendees.objects.filter(meet=meet, user=user).exists(): + return CustomResponse( + general_message="You are already interested in this meetup" + ).get_failure_response() + if ( + meet.max_attendees > 0 + and CircleMeetAttendees.objects.filter(meet=meet).count() + >= meet.max_attendees + ): + return CustomResponse( + general_message="This meetup reached the maximum, number of attendees" + ).get_failure_response() + CircleMeetAttendees.objects.create( + id=uuid.uuid4(), + meet=meet, + user=user, + note=notes, + created_at=DateTimeUtils.get_current_utc_time(), + updated_at=DateTimeUtils.get_current_utc_time(), + ) + return CustomResponse( + general_message=f"Interest shown successfully. You can join the meetup when it starts." + ).get_success_response() + + def delete(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() + if not user: + return CustomResponse(general_message="Invalid user").get_failure_response() + if not meet: + return CustomResponse( + general_message="Invalid meeting" + ).get_failure_response() + CircleMeetAttendees.objects.filter(meet=meet, user=user).delete() + return CustomResponse( + general_message=f"Interest removed successfully." + ).get_success_response() + + +class CircleMeetJoinAPI(APIView): + """ + Join a meetup + """ + + def post(self, request, meet_code_id): + user_id = JWTUtils.fetch_user_id(request) + user = User.objects.filter(id=user_id).first() + filter = (Q(meet_code=meet_code_id) | Q(id=meet_code_id)) & Q( + is_report_submitted=False + ) + meet = CircleMeetingLog.objects.filter(filter).first() + if not user: + return CustomResponse(general_message="Invalid user").get_failure_response() + if not meet: + return CustomResponse( + general_message="Invalid meeting code" + ).get_failure_response() + if not meet.is_started: + meet.is_started = True + meet.save() + attendee = CircleMeetAttendees.objects.filter(meet=meet, user=user).first() + if attendee: + if attendee.joined_at: + return CustomResponse( + general_message="You are already joined in this meetup" + ).get_failure_response() + attendee.joined_at = DateTimeUtils.get_current_utc_time() + attendee.save() + else: + attendee = CircleMeetAttendees.objects.create( + id=uuid.uuid4(), + meet=meet, + user=user, + joined_at=DateTimeUtils.get_current_utc_time(), + created_at=DateTimeUtils.get_current_utc_time(), + updated_at=DateTimeUtils.get_current_utc_time(), + ) + task = TaskList.objects.filter(hashtag=Lc.MEET_JOIN_HASHTAG.value).first() + KarmaActivityLog.objects.create( + id=uuid.uuid4(), + user_id=attendee.user_id, + karma=Lc.MEET_JOIN_KARMA.value, + task=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", + ) + wallet = Wallet.objects.filter(user_id=user_id).first() + wallet.karma += Lc.MEET_JOIN_KARMA.value + wallet.karma_last_updated_at = DateTimeUtils.get_current_utc_time() + wallet.updated_at = DateTimeUtils.get_current_utc_time() + wallet.save() + return CustomResponse( + general_message=f"Joined in the meetup successfully." + ).get_success_response() diff --git a/api/dashboard/lc/urls.py b/api/dashboard/lc/urls.py index 5f13f2aa..80668045 100644 --- a/api/dashboard/lc/urls.py +++ b/api/dashboard/lc/urls.py @@ -3,33 +3,110 @@ from . import dash_lc_view urlpatterns = [ - path('user-list/', dash_lc_view.UserLearningCircleListApi.as_view(), name='main'), # list all lc's of user - path('stats/', dash_lc_view.LearningCircleStatsAPI.as_view(), name='data'), - path('/details/', dash_lc_view.LearningCircleDetailsApi.as_view(), name='lc-detailed'), # individual ls details + path("meets/list/", dash_lc_view.CircleMeetListAPI.as_view(), name="meets-list"), + path( + "/meet/create/", + dash_lc_view.CircleMeetAPI.as_view(), + name="meet-create", + ), + path( + "/meet/list/", + dash_lc_view.CircleMeetAPI.as_view(), + name="meet-list", + ), + path( + "/meets/report//", + dash_lc_view.CircleMeetReportSubmitAPI.as_view(), + name="meet-report-submission", + ), + path( + "meets/interested//", + dash_lc_view.CircleMeetInterestedAPI.as_view(), + name="meet-interested", + ), + path( + "meets/info//", + dash_lc_view.CircleMeetDetailAPI.as_view(), + name="meet-info", + ), + path( + "meets/join//", + dash_lc_view.CircleMeetJoinAPI.as_view(), + name="meet-join", + ), + path( + "user-list/", dash_lc_view.UserLearningCircleListApi.as_view(), name="main" + ), # list all lc's of user + # path("stats/", dash_lc_view.LearningCircleStatsAPI.as_view(), name="data"), + path( + "/details/", + dash_lc_view.LearningCircleDetailsApi.as_view(), + name="lc-detailed", + ), # individual ls details # dashboard search listing - path('/schedule-meet/', dash_lc_view.ScheduleMeetAPI.as_view(), name='schedule-meet'), - path('/report/create/', dash_lc_view.SingleReportDetailAPI.as_view(), name='create-report'), - path('/report/create/validate/', dash_lc_view.ValidateUserMeetCreateAPI.as_view(), name='validate-report'), - path('/report//show/', dash_lc_view.SingleReportDetailAPI.as_view(), name='show-report'), - path('/add-member/', dash_lc_view.AddMemberAPI.as_view(), name='add-member'), - path('create/', dash_lc_view.LearningCircleCreateApi.as_view(), name='create'), - path('join//', dash_lc_view.LearningCircleJoinApi.as_view(), name='join'), - path('/ig-progress/', dash_lc_view.IgTaskDetailsAPI.as_view(), name='ig-progress'), # ig progress api - path('/lead-transfer//', dash_lc_view.LearningCircleLeadTransfer.as_view(), name='lead-transfer'), - - # rework user accept, reject api - + path( + "/schedule-meet/", + dash_lc_view.ScheduleMeetAPI.as_view(), + name="schedule-meet", + ), + # path( + # "/report/create/", + # dash_lc_view.SingleReportDetailAPI.as_view(), + # name="create-report", + # ), + # path( + # "/report/create/validate/", + # dash_lc_view.ValidateUserMeetCreateAPI.as_view(), + # name="validate-report", + # ), + path( + "/report//show/", + dash_lc_view.SingleReportDetailAPI.as_view(), + name="show-report", + ), + path( + "/add-member/", + dash_lc_view.AddMemberAPI.as_view(), + name="add-member", + ), + path("create/", dash_lc_view.LearningCircleCreateApi.as_view(), name="create"), + path( + "join//", + dash_lc_view.LearningCircleJoinApi.as_view(), + name="join", + ), + path( + "/ig-progress/", + dash_lc_view.IgTaskDetailsAPI.as_view(), + name="ig-progress", + ), # ig progress api + path( + "/lead-transfer//", + dash_lc_view.LearningCircleLeadTransfer.as_view(), + name="lead-transfer", + ), + # # rework user accept, reject api # TODO: new api for note updation # path('/note/edit/', dash_lc_view.LearningCircleLeadTransfer.as_view(), name='edit-note'), - path('/user-accept-reject//', dash_lc_view.LearningCircleDetailsApi.as_view()), # user accept or reject, also for removal - path('list-all//', dash_lc_view.TotalLearningCircleListApi.as_view(), name='list-all-search'), - path('list/', dash_lc_view.LearningCircleMainApi.as_view(), name='list'), # public page listing - path('list-all/', dash_lc_view.TotalLearningCircleListApi.as_view(), name='list-all'), # dashboard search listing - + path( + "/user-accept-reject//", + dash_lc_view.LearningCircleDetailsApi.as_view(), + ), # user accept or reject, also for removal + path( + "list-all//", + dash_lc_view.TotalLearningCircleListApi.as_view(), + name="list-all-search", + ), + path( + "list/", dash_lc_view.LearningCircleMainApi.as_view(), name="list" + ), # public page listing + path( + "list-all/", dash_lc_view.TotalLearningCircleListApi.as_view(), name="list-all" + ), # dashboard search listing # path('list-members//', dash_lc_view.LearningCircleListMembersApi.as_view(), name='list-members'), # path('invite/', dash_lc_view.LearningCircleInviteLeadAPI.as_view()), # path('meet-record/list-all//', dash_lc_view.SingleReportDetailAPI.as_view(), name='list-all-meet-record'), # optim # path('meet-record/edit//', dash_lc_view.SingleReportDetailAPI.as_view(), name='edit-meet-record'), # optim # path('member/invite///', dash_lc_view.LearningCircleInviteMemberAPI.as_view(), name='invite-member'), # path('member/invite/status////', dash_lc_view.LearningCircleInvitationStatus.as_view(), name='invite-member-status'), - ] +] diff --git a/db/learning_circle.py b/db/learning_circle.py index b5abd2d4..fa98a6ff 100644 --- a/db/learning_circle.py +++ b/db/learning_circle.py @@ -4,7 +4,7 @@ from db.task import InterestGroup, Organization from db.user import User - +from utils.utils import generate_code from django.conf import settings # fmt: off @@ -51,13 +51,21 @@ class Meta: class CircleMeetingLog(models.Model): id = models.CharField(primary_key=True, max_length=36, default=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') - meet_time = models.DateTimeField() + 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) - day = models.CharField(max_length=20) - attendees = models.CharField(max_length=216) + 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) + report_text = models.CharField(max_length=1000, null=True, blank=True) + 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') created_by = models.ForeignKey(User, on_delete=models.SET(settings.SYSTEM_ADMIN_ID), db_column='created_by', related_name='circle_meeting_log_created_by') @@ -69,3 +77,18 @@ class CircleMeetingLog(models.Model): class Meta: managed = False db_table = 'circle_meeting_log' + +class CircleMeetAttendees(models.Model): + id = models.CharField(primary_key=True, max_length=36, default=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) + 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') + created_at = models.DateTimeField(auto_now=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta: + managed = False + db_table = 'circle_meet_attendees' diff --git a/utils/types.py b/utils/types.py index 5eafabde..705ffd32 100644 --- a/utils/types.py +++ b/utils/types.py @@ -2,21 +2,21 @@ class ManagementType(Enum): - CAMPUS = 'Campus' - HACKATHON = 'Hackathon' - USER_MANAGEMENT = 'User Management' - MANAGE_ORGANIZATION = 'Manage Organization' - TASK_MANAGEMENT = 'Task Management' - INTEREST_GROUP = 'Interest Group' - COLLEGE_LEVELS = 'College Levels' - KARMA_VOUCHER = 'Karma Voucher' - ERROR_LOG = 'Error Log' - DYNAMIC_TYPE = 'Dynamic Type' - MANAGE_ROLES = 'Manage Roles' - MANAGE_LOCATIONS = 'Manage Locations' - CHANNELS = 'Channels' - URL_SHORTENER = 'Url Shortener' - DISCORD_MODERATION = 'Discord Moderation' + CAMPUS = "Campus" + HACKATHON = "Hackathon" + USER_MANAGEMENT = "User Management" + MANAGE_ORGANIZATION = "Manage Organization" + TASK_MANAGEMENT = "Task Management" + INTEREST_GROUP = "Interest Group" + COLLEGE_LEVELS = "College Levels" + KARMA_VOUCHER = "Karma Voucher" + ERROR_LOG = "Error Log" + DYNAMIC_TYPE = "Dynamic Type" + MANAGE_ROLES = "Manage Roles" + MANAGE_LOCATIONS = "Manage Locations" + CHANNELS = "Channels" + URL_SHORTENER = "Url Shortener" + DISCORD_MODERATION = "Discord Moderation" @classmethod def get_all_values(cls): @@ -40,25 +40,25 @@ class RoleType(Enum): SUSPEND = "Suspended" STUDENT = "Student" ENABLER = "Enabler" - TECH_TEAM = 'Tech Team' - IG_LEAD = 'IG Lead' + TECH_TEAM = "Tech Team" + IG_LEAD = "IG Lead" CAMPUS_ACTIVATION_TEAM = "Campus Activation Team" LEAD_ENABLER = "Lead Enabler" @classmethod - def IG_CAMPUS_LEAD_ROLE(cls,ig_code:str): + def IG_CAMPUS_LEAD_ROLE(cls, ig_code: str): return f"{ig_code} CampusLead" - + @classmethod - def IG_LEAD_ROLE(cls,ig_code:str): + def IG_LEAD_ROLE(cls, ig_code: str): return f"{ig_code} IGLead" class OrganizationType(Enum): - COLLEGE = 'College' - COMPANY = 'Company' - COMMUNITY = 'Community' - SCHOOL = 'School' + COLLEGE = "College" + COMPANY = "Company" + COMMUNITY = "Community" + SCHOOL = "School" @classmethod def get_all_values(cls): @@ -66,11 +66,11 @@ def get_all_values(cls): class WebHookActions(Enum): - SEPARATOR = '<|=|>' - CREATE = 'create' - EDIT = 'edit' - DELETE = 'delete' - UPDATE = 'update' + SEPARATOR = "<|=|>" + CREATE = "create" + EDIT = "edit" + DELETE = "delete" + UPDATE = "update" class MainRoles(Enum): @@ -80,42 +80,42 @@ class MainRoles(Enum): class WebHookCategory(Enum): - INTEREST_GROUP = 'ig' - COMMUNITY = 'community' - ROLE = 'role' - USER_ROLE = 'user-role' - USER = 'user' - USER_NAME = 'user-name' - USER_PROFILE = 'user-profile' - BULK_ROLE = 'bulk-role' - KARMA_INFO = 'karma-info' + INTEREST_GROUP = "ig" + COMMUNITY = "community" + ROLE = "role" + USER_ROLE = "user-role" + USER = "user" + USER_NAME = "user-name" + USER_PROFILE = "user-profile" + BULK_ROLE = "bulk-role" + KARMA_INFO = "karma-info" class RefferalType(Enum): - KARMA = 'Karma' - MUCOIN = 'Mucoin' + KARMA = "Karma" + MUCOIN = "Mucoin" class IntegrationType(Enum): - KKEM = 'DWMS' + KKEM = "DWMS" class TasksTypesHashtag(Enum): - REFERRAL = 'referral' - MUCOIN = 'mucoin' - GITHUB = 'social_github' - FACEBOOK = 'social_facebook' - INSTAGRAM = 'social_instagram' - LINKEDIN = 'social_linkedin' - DRIBBLE = 'social_dribble' - BEHANCE = 'social_behance' - STACKOVERFLOW = 'social_stackoverflow' - MEDIUM = 'social_medium' + REFERRAL = "referral" + MUCOIN = "mucoin" + GITHUB = "social_github" + FACEBOOK = "social_facebook" + INSTAGRAM = "social_instagram" + LINKEDIN = "social_linkedin" + DRIBBLE = "social_dribble" + BEHANCE = "social_behance" + STACKOVERFLOW = "social_stackoverflow" + MEDIUM = "social_medium" class Events(Enum): - LEARNING_FEST = 'LearningFest' - TOP_100_CODERS = 'Top100' + LEARNING_FEST = "LearningFest" + TOP_100_CODERS = "Top100" @classmethod def get_all_values(cls): @@ -123,53 +123,62 @@ def get_all_values(cls): class Lc(Enum): - KARMA = 20 - TASK_HASHTAG = '#lcmeetreport' + RECORD_SUBMIT_KARMA = 20 + RECORD_SUBMIT_HASHTAG = "#lcmeetreport" + + MEET_JOIN_KARMA = 10 + MEET_JOIN_HASHTAG = "#lcmeetjoin" + class CouponResponseKey(Enum): - DISCOUNT_TYPE = 'discount_type' - DISCOUNT_VALUE = 'discount_value' - TICKET = 'ticket' + DISCOUNT_TYPE = "discount_type" + DISCOUNT_VALUE = "discount_value" + TICKET = "ticket" + class DiscountTypes(Enum): - PERCENTAGE = 'percentage' - AMOUNT = 'amount' + PERCENTAGE = "percentage" + AMOUNT = "amount" + class LaunchPadLevels(Enum): - LEVEL_1 = 'IEEE Launchpad Level 1' - LEVEL_2 = 'IEEE Launchpad Level 2' - LEVEL_3 = 'IEEE Launchpad Level 3' - LEVEL_4 = 'IEEE Launchpad Level 4' + LEVEL_1 = "IEEE Launchpad Level 1" + LEVEL_2 = "IEEE Launchpad Level 2" + LEVEL_3 = "IEEE Launchpad Level 3" + LEVEL_4 = "IEEE Launchpad Level 4" @classmethod def get_all_values(cls): return [member.value for member in cls] + class LaunchPadRoles(Enum): - ADMIN = 'IEEEAdmin' - DC = 'IEEEDC' + ADMIN = "IEEEAdmin" + DC = "IEEEDC" @classmethod def get_all_values(cls): return [member.value for member in cls] + class TFPTasksHashtags(Enum): - SCRATCH = '#tfp2.0-scratch' - COMMAND_LINE = '#tfp2.0-command-line' - GIT_GITHUB = '#tfp2.0-git-github' - + SCRATCH = "#tfp2.0-scratch" + COMMAND_LINE = "#tfp2.0-command-line" + GIT_GITHUB = "#tfp2.0-git-github" + @classmethod def get_all_values(cls): return [member.value for member in cls] - + + DEFAULT_HACKATHON_FORM_FIELDS = { - 'name': 'system', - 'gender': 'system', - 'email': 'system', - 'mobile': 'system', - 'college': 'system', - 'experience': 'input', - 'github': 'input', - 'linkedin': 'input', - 'bio': 'input', + "name": "system", + "gender": "system", + "email": "system", + "mobile": "system", + "college": "system", + "experience": "input", + "github": "input", + "linkedin": "input", + "bio": "input", } diff --git a/utils/utils.py b/utils/utils.py index 80d89f8b..6e871f19 100644 --- a/utils/utils.py +++ b/utils/utils.py @@ -15,6 +15,7 @@ from django.db.models.query import QuerySet from django.http import HttpResponse from django.template.loader import render_to_string +import string, random class CommonUtils: @@ -77,9 +78,9 @@ def get_paginated_queryset( "totalPages": paginator.num_pages, "isNext": queryset.has_next(), "isPrev": queryset.has_previous(), - "nextPage": queryset.next_page_number() - if queryset.has_next() - else None, + "nextPage": ( + queryset.next_page_number() if queryset.has_next() else None + ), }, } @@ -98,9 +99,9 @@ def generate_csv(queryset: QuerySet, csv_name: str) -> HttpResponse: gzip.compress(response.content), content_type="text/csv", ) - compressed_response[ - "Content-Disposition" - ] = f'attachment; filename="{csv_name}.csv"' + compressed_response["Content-Disposition"] = ( + f'attachment; filename="{csv_name}.csv"' + ) compressed_response["Content-Encoding"] = "gzip" return compressed_response @@ -236,3 +237,9 @@ def send_template_mail( email.attach(attachment) email.content_subtype = "html" return email.send() + + +def generate_code(char_count=6): + characters = string.ascii_uppercase + string.digits + code = "".join(random.choices(characters, k=char_count)) + return code