From 97b0dbd78998264e06aca87902283feb16ca1770 Mon Sep 17 00:00:00 2001 From: lordgrim Date: Mon, 27 Nov 2023 20:28:58 +0530 Subject: [PATCH 1/4] feat(roles): add base template create API --- api/dashboard/roles/dash_roles_views.py | 55 +++++++++++++++++++++++++ api/dashboard/roles/urls.py | 1 + 2 files changed, 56 insertions(+) diff --git a/api/dashboard/roles/dash_roles_views.py b/api/dashboard/roles/dash_roles_views.py index 75c6fb10..3ffe00c3 100644 --- a/api/dashboard/roles/dash_roles_views.py +++ b/api/dashboard/roles/dash_roles_views.py @@ -8,6 +8,10 @@ from utils.utils import CommonUtils, DiscordWebhooks from . import dash_roles_serializer +from openpyxl import load_workbook +from tempfile import NamedTemporaryFile +from io import BytesIO +from django.http import FileResponse class RoleAPI(APIView): authentication_classes = [CustomizePermission] @@ -202,3 +206,54 @@ def delete(self, request): return CustomResponse( general_message="User Role deleted successfully" ).get_success_response() + +from openpyxl import Workbook +from openpyxl.styles import Font + +class RoleBaseTemplateAPI(APIView): + authentication_classes = [CustomizePermission] + + def get(self, request): + wb = Workbook() + ws = wb.active + ws.title = "Sheet1" + + ws.append([ + "muid", + "role", + ]) + + # Set column headers font as bold + bold_font = Font(bold=True) + for cell in ws[1]: + cell.font = bold_font + + # Set column width + ws.column_dimensions['A'].width = 50 + ws.column_dimensions['B'].width = 40 + + ws = wb.create_sheet('Data Definitions') + ws.append(['role']) + + # set column header as bold and set width + for cell in ws[1]: + cell.font = bold_font + ws.column_dimensions['A'].width = 40 + + roles = Role.objects.all().values_list('title', flat=True) + + data = { + 'role': roles + } + # Write data column-wise + for col_num, (col_name, col_values) in enumerate(data.items(), start=1): + for row, value in enumerate(col_values, start=2): + ws.cell(row=row, column=col_num, value=value) + + wb.save('role_base_template.xlsx') + + return CustomResponse( + response={ + "message": "Base template created successfully" + } + ).get_success_response() \ No newline at end of file diff --git a/api/dashboard/roles/urls.py b/api/dashboard/roles/urls.py index c186b585..b3b66ec9 100644 --- a/api/dashboard/roles/urls.py +++ b/api/dashboard/roles/urls.py @@ -5,6 +5,7 @@ # app_name will help us do a reverse look-up latter. urlpatterns = [ path('user-role//', dash_roles_views.UserRoleSearchAPI.as_view(), name='search-user-role'), + path('base-template/', dash_roles_views.RoleBaseTemplateAPI.as_view(), name="roles-base-template"), path('user-role/', dash_roles_views.UserRole.as_view(), name='create-delete-user-role'), path('', dash_roles_views.RoleAPI.as_view(), name="roles-list"), path('', dash_roles_views.RoleAPI.as_view(), name="roles-create"), From 4c68e9bd6692144b3b65de4953c0cfd81cded3fe Mon Sep 17 00:00:00 2001 From: lordgrim Date: Mon, 27 Nov 2023 20:41:23 +0530 Subject: [PATCH 2/4] feat(roles): add base template API --- .../roles/assets/role_base_template.xlsx | Bin 0 -> 11690 bytes api/dashboard/roles/dash_roles_views.py | 49 +++++------------- 2 files changed, 12 insertions(+), 37 deletions(-) create mode 100644 api/dashboard/roles/assets/role_base_template.xlsx diff --git a/api/dashboard/roles/assets/role_base_template.xlsx b/api/dashboard/roles/assets/role_base_template.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..ba6440c7c4a0b0d27392b398c9ae04021adcff07 GIT binary patch literal 11690 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 Bqp|=1 literal 0 HcmV?d00001 diff --git a/api/dashboard/roles/dash_roles_views.py b/api/dashboard/roles/dash_roles_views.py index 3ffe00c3..1907b580 100644 --- a/api/dashboard/roles/dash_roles_views.py +++ b/api/dashboard/roles/dash_roles_views.py @@ -207,41 +207,14 @@ def delete(self, request): general_message="User Role deleted successfully" ).get_success_response() -from openpyxl import Workbook -from openpyxl.styles import Font - class RoleBaseTemplateAPI(APIView): authentication_classes = [CustomizePermission] def get(self, request): - wb = Workbook() - ws = wb.active - ws.title = "Sheet1" - - ws.append([ - "muid", - "role", - ]) - - # Set column headers font as bold - bold_font = Font(bold=True) - for cell in ws[1]: - cell.font = bold_font - - # Set column width - ws.column_dimensions['A'].width = 50 - ws.column_dimensions['B'].width = 40 - - ws = wb.create_sheet('Data Definitions') - ws.append(['role']) - - # set column header as bold and set width - for cell in ws[1]: - cell.font = bold_font - ws.column_dimensions['A'].width = 40 + wb = load_workbook('./api/dashboard/roles/assets/role_base_template.xlsx') + ws = wb['Data Definitions'] roles = Role.objects.all().values_list('title', flat=True) - data = { 'role': roles } @@ -249,11 +222,13 @@ def get(self, request): for col_num, (col_name, col_values) in enumerate(data.items(), start=1): for row, value in enumerate(col_values, start=2): ws.cell(row=row, column=col_num, value=value) - - wb.save('role_base_template.xlsx') - - return CustomResponse( - response={ - "message": "Base template created successfully" - } - ).get_success_response() \ No newline at end of file + + # Save the file + with NamedTemporaryFile() as tmp: + tmp.close() # with statement opened tmp, close it so wb.save can open it + wb.save(tmp.name) + with open(tmp.name, 'rb') as f: + f.seek(0) + new_file_object = f.read() + return FileResponse(BytesIO(new_file_object), as_attachment=True, filename='role_base_template.xlsx') + From 9ebfdc7af4c3363a7a32d4920401ae96b0b90dee Mon Sep 17 00:00:00 2001 From: lordgrim Date: Wed, 29 Nov 2023 14:12:51 +0530 Subject: [PATCH 3/4] fix(roles): base template --- .../roles/assets/role_base_template.xlsx | Bin 11690 -> 11690 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/api/dashboard/roles/assets/role_base_template.xlsx b/api/dashboard/roles/assets/role_base_template.xlsx index ba6440c7c4a0b0d27392b398c9ae04021adcff07..ef71582d42380a235a5ac3398332887273b9851b 100644 GIT binary patch delta 280 zcmZ1#y(*eFz?+#xgn@&DgTallb|bF`BQubm$g8rshEWB~p1fQ@W%F^SkBp2bH}7P* z$R)W-VujW&9tMVk|2oKo&C3Pe@_-rBWtqU{Y@Q>R&I}Tn%%*f3 zEb~N(oe9i%r!obiq*cuvV&{JK7hsuH8ubtX9W7_DUL7sz$-P<{96)ozK@OR`SIZ78 w@lVSSO#5p?6i(HK@SkZzua+HH w;-8iunD*C(D4ePd;Xl)c$gAtvgXJ@IEWq><9V;;XP{$QaYwDVT=_Fkb0M{*MMgRZ+ From 969b5cc67de2bbe9193290d3329edc645dd9f43b Mon Sep 17 00:00:00 2001 From: lordgrim Date: Wed, 29 Nov 2023 14:50:24 +0530 Subject: [PATCH 4/4] feat(roles): add user role bulk assign API --- api/dashboard/roles/dash_roles_serializer.py | 23 ++++ api/dashboard/roles/dash_roles_views.py | 132 ++++++++++++++++++- api/dashboard/roles/urls.py | 1 + 3 files changed, 154 insertions(+), 2 deletions(-) diff --git a/api/dashboard/roles/dash_roles_serializer.py b/api/dashboard/roles/dash_roles_serializer.py index 15588959..580ab0b4 100644 --- a/api/dashboard/roles/dash_roles_serializer.py +++ b/api/dashboard/roles/dash_roles_serializer.py @@ -80,3 +80,26 @@ def create(self, validated_data): validated_data["created_at"] = DateTimeUtils.get_current_utc_time() return super().create(validated_data) + +class UserRoleBulkAssignSerializer(serializers.ModelSerializer): + user_id = serializers.CharField(required=True) + role_id = serializers.CharField(required=True) + created_by_id = serializers.CharField(required=True, allow_null=False) + class Meta: + model = UserRoleLink + fields = [ + "id", + "user_id", + "role_id", + "verified", + "created_by_id", + "created_at", + ] + + def to_representation(self, instance): + representation = super().to_representation(instance) + + representation['user_id'] = instance.user.fullname if instance.user else None + representation['role_id'] = instance.role.title if instance.role else None + return representation + \ No newline at end of file diff --git a/api/dashboard/roles/dash_roles_views.py b/api/dashboard/roles/dash_roles_views.py index 1907b580..f8519ecd 100644 --- a/api/dashboard/roles/dash_roles_views.py +++ b/api/dashboard/roles/dash_roles_views.py @@ -1,11 +1,12 @@ +import uuid from django.db import IntegrityError from rest_framework.views import APIView from db.user import Role, User, UserRoleLink -from utils.permission import CustomizePermission, role_required +from utils.permission import CustomizePermission, role_required, JWTUtils from utils.response import CustomResponse from utils.types import RoleType, WebHookActions, WebHookCategory -from utils.utils import CommonUtils, DiscordWebhooks +from utils.utils import CommonUtils, DiscordWebhooks, ImportCSV from . import dash_roles_serializer from openpyxl import load_workbook @@ -232,3 +233,130 @@ def get(self, request): new_file_object = f.read() return FileResponse(BytesIO(new_file_object), as_attachment=True, filename='role_base_template.xlsx') +class UserRoleBulkAssignAPI(APIView): + authentication_classes = [CustomizePermission] + + @role_required([RoleType.ADMIN.value]) + + def post(self, request): + try: + file_obj = request.FILES["user_roles_list"] + except KeyError: + return CustomResponse( + general_message="File not found." + ).get_failure_response() + + excel_data = ImportCSV() + excel_data = excel_data.read_excel_file(file_obj) + + if not excel_data: + return CustomResponse( + general_message="Empty csv file." + ).get_failure_response() + + temp_headers = [ + "muid", + "role" + ] + first_entry = excel_data[0] + for key in temp_headers: + if key not in first_entry: + return CustomResponse( + general_message=f"{key} does not exist in the file." + ).get_failure_response() + + excel_data = [row for row in excel_data if any(row.values())] + valid_rows = [] + error_rows = [] + + users_to_fetch = set() + roles_to_fetch = set() + user_role_link_to_check = set() + + for row in excel_data[1:]: + keys_to_keep = ["muid", "role"] + row_keys = list(row.keys()) + + # Remove columns other than "muid" and "role" + for key in row_keys: + if key not in keys_to_keep: + del row[key] + + for row in excel_data[1:]: + user = row.get("muid") + role = row.get("role") + users_to_fetch.add(user) + roles_to_fetch.add(role) + if (user, role) in user_role_link_to_check: + row["error"] = "Duplicate entry" + error_rows.append(row) + excel_data.remove(row) + else: + user_role_link_to_check.add((user, role)) + + users = User.objects.filter( + muid__in=users_to_fetch + ).values( + "id", + "muid", + ) + roles = Role.objects.filter( + title__in=roles_to_fetch + ).values( + "id", + "title", + ) + existing_user_role_links = list(UserRoleLink.objects.filter( + user__muid__in=users_to_fetch, + role__title__in=roles_to_fetch + ).values_list('user__muid', 'role__title')) + users_dict = {user["muid"]: user["id"] for user in users} + roles_dict = {role["title"]: role["id"] for role in roles} + + for row in excel_data[1:]: + user = row.pop("muid") + role = row.pop("role") + + user_id = users_dict.get(user) + role_id = roles_dict.get(role) + + if not user_id: + row["muid"] = user + row["role"] = role + row["error"] = f"Invalid user muid: {user}" + error_rows.append(row) + elif not role_id: + row["muid"] = user + row["role"] = role + row["error"] = f"Invalid role: {role}" + error_rows.append(row) + elif (user, role) in existing_user_role_links: + row["muid"] = user + row["role"] = role + row["error"] = f"User {user} already has role {role}" + error_rows.append(row) + else: + request_user_id = JWTUtils.fetch_user_id(request) + row["id"] = str(uuid.uuid4()) + row["user_id"] = user_id + row["role_id"] = role_id + row["verified"] = True + row["created_by_id"] = request_user_id + valid_rows.append(row) + + user_roles_serializer = dash_roles_serializer.UserRoleBulkAssignSerializer(data=valid_rows, many=True) + success_data = [] + if user_roles_serializer.is_valid(): + user_roles_serializer.save() + for user_role_data in user_roles_serializer.data: + success_data.append({ + 'user': user_role_data.get('user_id', ''), + 'role': user_role_data.get('role_id', ''), + }) + else: + error_rows.append(user_roles_serializer.errors) + + return CustomResponse( + response={"Success": success_data, "Failed": error_rows} + ).get_success_response() + diff --git a/api/dashboard/roles/urls.py b/api/dashboard/roles/urls.py index b3b66ec9..dc0790a9 100644 --- a/api/dashboard/roles/urls.py +++ b/api/dashboard/roles/urls.py @@ -6,6 +6,7 @@ urlpatterns = [ path('user-role//', dash_roles_views.UserRoleSearchAPI.as_view(), name='search-user-role'), path('base-template/', dash_roles_views.RoleBaseTemplateAPI.as_view(), name="roles-base-template"), + path('bulk-assign/', dash_roles_views.UserRoleBulkAssignAPI.as_view(), name="user-roles-import"), path('user-role/', dash_roles_views.UserRole.as_view(), name='create-delete-user-role'), path('', dash_roles_views.RoleAPI.as_view(), name="roles-list"), path('', dash_roles_views.RoleAPI.as_view(), name="roles-create"),