From d78dd13419532dd198b2ee4c77012f6d43f5a34c Mon Sep 17 00:00:00 2001 From: Ishvinder Sethi Date: Mon, 8 Apr 2024 13:05:34 +0530 Subject: [PATCH 01/29] Update tasks.py --- backend/projects/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/projects/tasks.py b/backend/projects/tasks.py index 2652ac83a..8f0bd51c1 100644 --- a/backend/projects/tasks.py +++ b/backend/projects/tasks.py @@ -379,7 +379,7 @@ def create_parameters_for_task_creation( tasks = create_tasks_from_dataitems(sampled_items, project) -# @shared_task +@shared_task def export_project_in_place( annotation_fields, project_id, project_type, get_request_data ) -> None: From f73d0df2fcdd3338d99596b02a47eabefbb48ae3 Mon Sep 17 00:00:00 2001 From: Ishan Gujarathi Date: Mon, 8 Apr 2024 16:16:13 +0530 Subject: [PATCH 02/29] added a feature to remove user as workspace member and manager when he is marked inactive at organization level --- backend/users/views.py | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/backend/users/views.py b/backend/users/views.py index d99e4ab15..d3afbca24 100644 --- a/backend/users/views.py +++ b/backend/users/views.py @@ -59,7 +59,7 @@ from rest_framework_simplejwt.tokens import RefreshToken from dotenv import load_dotenv import logging -from workspaces.views import WorkspaceViewSet +from workspaces.views import WorkspaceusersViewSet logger = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) @@ -686,14 +686,38 @@ def user_details_update(self, request, pk=None): workspaces = Workspace.objects.filter( Q(members=user) | Q(managers=user) ) - workspace_view = WorkspaceViewSet() + workspacecustomviewset_obj = WorkspaceCustomViewSet() + request.data["ids"] = [user.id] + + workspaceusersviewset_obj = WorkspaceusersViewSet() + request.data["user_id"] = user.id + + failed_workspace_unassign = [] + failed_workspace_remove = [] + for workspace in workspaces: - workspace_view.unassign_manager( - request, pk=workspace.pk, ids=[user.id] + response_unassign = workspacecustomviewset_obj.unassign_manager( + request=request, pk=workspace.pk ) - workspace_view.remove_members( - request, pk=workspace.pk, user_id=user.id + + if response_unassign.status_code != 200: + failed_workspace_unassign.append(workspace.pk) + + response_remove = workspaceusersviewset_obj.remove_members( + request=request, pk=workspace.pk ) + + if response_remove.status_code != 200: + failed_workspace_remove.append(workspace.pk) + + message = "User removed from some workspaces both as workspace member and workspace manager." + if failed_workspace_unassign: + message += f" {failed_workspace_unassign} failed to unassign user as workspace manager." + if failed_workspace_remove: + message += f" {failed_workspace_remove} failed to remove user as workspace member." + if message: + return Response({"message": message}, status=status.HTTP_200_OK) + return Response( { "message": "User removed from all workspaces both as workspace member and workspace manager" From 2d65be6be0f55224c452e53ee022f76a4676159e Mon Sep 17 00:00:00 2001 From: Kunal Tiwary Date: Wed, 10 Apr 2024 17:33:16 +0530 Subject: [PATCH 03/29] added backend.shoonya2.ai4bharat.org in allowed_hosts --- backend/shoonya_backend/settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/shoonya_backend/settings.py b/backend/shoonya_backend/settings.py index e8f6e71d7..cb5855366 100644 --- a/backend/shoonya_backend/settings.py +++ b/backend/shoonya_backend/settings.py @@ -41,6 +41,7 @@ "shoonya.ai4bharat.org", "0.0.0.0", "backend.shoonya.ai4bharat.org", + "backend.shoonya2.ai4bharat.org", ] # Application definition From b064b7871868831b448ceb71c24ffd023564dce6 Mon Sep 17 00:00:00 2001 From: Ishvinder Sethi Date: Mon, 15 Apr 2024 10:59:36 +0530 Subject: [PATCH 04/29] =?UTF-8?q?Revert=20"added=20a=20feature=20to=20remo?= =?UTF-8?q?ve=20user=20as=20workspace=20member=20and=20manager=20when=20h?= =?UTF-8?q?=E2=80=A6"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/users/views.py | 36 ++++++------------------------------ 1 file changed, 6 insertions(+), 30 deletions(-) diff --git a/backend/users/views.py b/backend/users/views.py index d3afbca24..d99e4ab15 100644 --- a/backend/users/views.py +++ b/backend/users/views.py @@ -59,7 +59,7 @@ from rest_framework_simplejwt.tokens import RefreshToken from dotenv import load_dotenv import logging -from workspaces.views import WorkspaceusersViewSet +from workspaces.views import WorkspaceViewSet logger = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) @@ -686,38 +686,14 @@ def user_details_update(self, request, pk=None): workspaces = Workspace.objects.filter( Q(members=user) | Q(managers=user) ) - workspacecustomviewset_obj = WorkspaceCustomViewSet() - request.data["ids"] = [user.id] - - workspaceusersviewset_obj = WorkspaceusersViewSet() - request.data["user_id"] = user.id - - failed_workspace_unassign = [] - failed_workspace_remove = [] - + workspace_view = WorkspaceViewSet() for workspace in workspaces: - response_unassign = workspacecustomviewset_obj.unassign_manager( - request=request, pk=workspace.pk + workspace_view.unassign_manager( + request, pk=workspace.pk, ids=[user.id] ) - - if response_unassign.status_code != 200: - failed_workspace_unassign.append(workspace.pk) - - response_remove = workspaceusersviewset_obj.remove_members( - request=request, pk=workspace.pk + workspace_view.remove_members( + request, pk=workspace.pk, user_id=user.id ) - - if response_remove.status_code != 200: - failed_workspace_remove.append(workspace.pk) - - message = "User removed from some workspaces both as workspace member and workspace manager." - if failed_workspace_unassign: - message += f" {failed_workspace_unassign} failed to unassign user as workspace manager." - if failed_workspace_remove: - message += f" {failed_workspace_remove} failed to remove user as workspace member." - if message: - return Response({"message": message}, status=status.HTTP_200_OK) - return Response( { "message": "User removed from all workspaces both as workspace member and workspace manager" From dc8ffc7fcec8245986fc3783b908a23feca12f8e Mon Sep 17 00:00:00 2001 From: Ishvinder Sethi Date: Mon, 15 Apr 2024 11:05:54 +0530 Subject: [PATCH 05/29] =?UTF-8?q?Revert=20"added=20a=20feature=20to=20remo?= =?UTF-8?q?ve=20user=20from=20all=20workspaces=20as=20workspace=20manag?= =?UTF-8?q?=E2=80=A6"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/users/views.py | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/backend/users/views.py b/backend/users/views.py index d99e4ab15..b02aeb403 100644 --- a/backend/users/views.py +++ b/backend/users/views.py @@ -59,7 +59,6 @@ from rest_framework_simplejwt.tokens import RefreshToken from dotenv import load_dotenv import logging -from workspaces.views import WorkspaceViewSet logger = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) @@ -676,31 +675,6 @@ def user_details_update(self, request, pk=None): user = User.objects.get(id=pk) serializer = UserUpdateSerializer(user, request.data, partial=True) - existing_is_active = user.is_active - is_active_payload = request.data.get("is_active", None) - - if existing_is_active == is_active_payload: - pass - else: - if is_active_payload is False: - workspaces = Workspace.objects.filter( - Q(members=user) | Q(managers=user) - ) - workspace_view = WorkspaceViewSet() - for workspace in workspaces: - workspace_view.unassign_manager( - request, pk=workspace.pk, ids=[user.id] - ) - workspace_view.remove_members( - request, pk=workspace.pk, user_id=user.id - ) - return Response( - { - "message": "User removed from all workspaces both as workspace member and workspace manager" - }, - status=status.HTTP_200_OK, - ) - if request.data["role"] != user.role: new_role = int(request.data["role"]) old_role = int(user.role) From 86e253ea4e822a99778270587c73eb458e368324 Mon Sep 17 00:00:00 2001 From: Kunal Tiwary Date: Tue, 16 Apr 2024 14:04:26 +0530 Subject: [PATCH 06/29] added additional filtering for other fields in dataset download endpoint --- backend/dataset/views.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/backend/dataset/views.py b/backend/dataset/views.py index 050a1bfda..5c9e1fa21 100644 --- a/backend/dataset/views.py +++ b/backend/dataset/views.py @@ -305,7 +305,7 @@ def download(self, request, pk): URL: /data/instances//download/ Accepted methods: GET """ - export_type = request.GET.get("type", "csv") + export_type = request.GET.get("export_type", "csv").lower() try: # Get the dataset instance for the id dataset_instance = DatasetInstance.objects.get(instance_id=pk) @@ -314,6 +314,19 @@ def download(self, request, pk): dataset_model = apps.get_model("dataset", dataset_instance.dataset_type) data_items = dataset_model.objects.filter(instance_id=pk) + field_names = set([field.name for field in dataset_model._meta.get_fields()]) + for key, value in request.GET.items(): + if key in field_names: + kwargs = {f"{key}__icontains": value} + data_items = data_items.filter(**kwargs) + elif key == "export_type": + continue + else: + return Response( + {"message": "The corresponding column does not exist"}, + status=status.HTTP_400_BAD_REQUEST, + ) + dataset_resource = resources.RESOURCE_MAP[dataset_instance.dataset_type] exported_items = dataset_resource().export_as_generator(export_type, data_items) if export_type == "tsv": From 56a250d43bbd5bc006bacb4655fa69df43ada68a Mon Sep 17 00:00:00 2001 From: Ishvinder Sethi Date: Tue, 16 Apr 2024 15:02:22 +0530 Subject: [PATCH 07/29] Update settings.py --- backend/shoonya_backend/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/shoonya_backend/settings.py b/backend/shoonya_backend/settings.py index cb5855366..b98258a56 100644 --- a/backend/shoonya_backend/settings.py +++ b/backend/shoonya_backend/settings.py @@ -189,7 +189,7 @@ # Email Settings EMAIL_BACKEND = "django_smtp_ssl.SSLEmailBackend" EMAIL_HOST = os.getenv("EMAIL_HOST") -EMAIL_PORT = 465 +EMAIL_PORT = os.getenv("EMAIL_PORT") EMAIL_HOST_USER = os.getenv("SMTP_USERNAME") EMAIL_HOST_PASSWORD = os.getenv("SMTP_PASSWORD") EMAIL_USE_TLS = True From d1674b19795e4f8fb9367e2528da1c27224c8fff Mon Sep 17 00:00:00 2001 From: Ishvinder Sethi Date: Tue, 16 Apr 2024 15:15:57 +0530 Subject: [PATCH 08/29] Update settings.py --- backend/shoonya_backend/settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/shoonya_backend/settings.py b/backend/shoonya_backend/settings.py index b98258a56..c37e2c2d4 100644 --- a/backend/shoonya_backend/settings.py +++ b/backend/shoonya_backend/settings.py @@ -193,6 +193,7 @@ EMAIL_HOST_USER = os.getenv("SMTP_USERNAME") EMAIL_HOST_PASSWORD = os.getenv("SMTP_PASSWORD") EMAIL_USE_TLS = True +EMAIL_USE_SSL = False DEFAULT_FROM_EMAIL = os.getenv("DEFAULT_FROM_EMAIL") DOMAIN = "shoonya.ai4bharat.org" From 4cd7d0cb67d9275c53a7248dff6df48e0e5d151b Mon Sep 17 00:00:00 2001 From: Pursottam6003 Date: Wed, 17 Apr 2024 12:55:17 +0530 Subject: [PATCH 09/29] updated the routes for decentralized approvals --- .../migrations/0046_merge_20240416_2233.py | 14 ++++ .../migrations/0032_auto_20240417_1028.py | 25 +++++++ backend/users/models.py | 16 ++++- backend/users/views.py | 70 ++++++++++++++++++- 4 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 backend/dataset/migrations/0046_merge_20240416_2233.py create mode 100644 backend/users/migrations/0032_auto_20240417_1028.py diff --git a/backend/dataset/migrations/0046_merge_20240416_2233.py b/backend/dataset/migrations/0046_merge_20240416_2233.py new file mode 100644 index 000000000..edbce6ca1 --- /dev/null +++ b/backend/dataset/migrations/0046_merge_20240416_2233.py @@ -0,0 +1,14 @@ +# Generated by Django 3.2.14 on 2024-04-16 17:03 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('dataset', '0045_alter_ocrdocument_ocr_domain'), + ('dataset', '0045_auto_20240321_0949'), + ] + + operations = [ + ] diff --git a/backend/users/migrations/0032_auto_20240417_1028.py b/backend/users/migrations/0032_auto_20240417_1028.py new file mode 100644 index 000000000..7360b53f0 --- /dev/null +++ b/backend/users/migrations/0032_auto_20240417_1028.py @@ -0,0 +1,25 @@ +# Generated by Django 3.2.14 on 2024-04-17 04:58 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0031_user_notification_limit'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='approved_by', + field=models.ForeignKey(blank=True, default=1, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='user', + name='is_approved', + field=models.BooleanField(default=True, help_text='Indicates whether user is approved by the admin or not.', verbose_name='is_approved'), + ), + ] diff --git a/backend/users/models.py b/backend/users/models.py index 1dff1cb2a..510a1dd65 100644 --- a/backend/users/models.py +++ b/backend/users/models.py @@ -213,14 +213,28 @@ class User(AbstractBaseUser, PermissionsMixin): "Indicates whether user prefers Chitralekha UI for audio transcription tasks or not." ), ) + # def get_default_user(): + # return settings.AUTH_USER_MODEL.objects.get(id=1) + is_approved = models.BooleanField( + verbose_name="is_approved", + default=True, + help_text=("Indicates whether user is approved by the admin or not."), + ) + + approved_by = models.ForeignKey( + 'self', + on_delete=models.SET_NULL, + null=True, + blank=True, + default=1, + ) class Meta: db_table = "user" indexes = [ models.Index(fields=["username"]), models.Index(fields=["email"]), ] - def clean(self): super().clean() self.email = self.__class__.objects.normalize_email(self.email) diff --git a/backend/users/views.py b/backend/users/views.py index b02aeb403..d4887c232 100644 --- a/backend/users/views.py +++ b/backend/users/views.py @@ -105,6 +105,8 @@ def invite_users(self, request): email=email.lower(), organization_id=org.id, role=request.data.get("role"), + is_approved=True, # as it can be only done by project owner + approved_by=request.user.user_id, ) user.set_password(generate_random_string(10)) valid_user_emails.append(email) @@ -258,6 +260,7 @@ def sign_up_user(self, request, pk=None): try: user = User.objects.get(email=email) except User.DoesNotExist: + user.is_approved = False return Response( {"message": "User not found"}, status=status.HTTP_404_NOT_FOUND ) @@ -277,8 +280,73 @@ def sign_up_user(self, request, pk=None): if serialized.is_valid(): serialized.save() return Response({"message": "User signed up"}, status=status.HTTP_200_OK) + # function to list the users whose user.is_approved is false + @permission_classes([IsAuthenticated]) + @swagger_auto_schema(responses={200: UserSignUpSerializer}) + @action(detail=False, methods=["get"], url_path="pending_users") + def pending_users(self, request): + """ + List of users who have not accepted the invite yet in that organisation/workspace + """ + organisation_id = request.user.organization_id + users = User.objects.filter(organization_id=organisation_id, is_approved=True) + serialized = UserSignUpSerializer(users, many=True) + return Response(serialized.data, status=status.HTTP_200_OK) + + # function to reject the user request to join the workspace by organiastion owner and delete the user from the table + @permission_classes([IsAuthenticated]) + @is_organization_owner + @swagger_auto_schema(request_body=UserSignUpSerializer) + @action(detail=True, methods=["delete"], url_path="reject_user") + def reject_user(self, request, pk=None): + """ + Reject the user request to join the workspace + """ + try: + user = User.objects.get(id=pk) + except User.DoesNotExist: + return Response( + {"message": "User not found"}, status=status.HTTP_404_NOT_FOUND + ) + user.delete() + return Response({"message": "User rejected"}, status=status.HTTP_200_OK) - + # function to approve the user request to join the workspace by organiastion owner and update the user.is_approved to true + @permission_classes([IsAuthenticated]) + @is_organization_owner + @swagger_auto_schema(request_body=UserSignUpSerializer) + @action(detail=True, methods=["patch"], url_path="approve_user") + def approve_user(self, request, pk=None): + """ + Approve the user request to join the workspace + """ + try: + user = User.objects.get(id=pk) + except User.DoesNotExist: + return Response( + {"message": "User not found"}, status=status.HTTP_404_NOT_FOUND + ) + user.is_approved = True + user.save() + return Response({"message": "User approved"}, status=status.HTTP_200_OK) + + # function to request workspace owner to add the users to the workspace by workspace manager + @permission_classes([IsAuthenticated]) + @swagger_auto_schema(request_body=UserSignUpSerializer) + @action(detail=True, methods=["post"], url_path="request_user") + def request_user(self, request, pk=None): + """ + Request the workspace owner to add the user to the workspace + """ + try: + user = User.objects.get(id=pk) + except User.DoesNotExist: + return Response( + {"message": "User not found"}, status=status.HTTP_404_NOT_FOUND + ) + user.is_approved = False + user.save() + return Response({"message": "User requested"}, status=status.HTTP_200_OK) class AuthViewSet(viewsets.ViewSet): @permission_classes([AllowAny]) @swagger_auto_schema(request_body=UserLoginSerializer) From 54ed59bdc2a82867e4aa0df6392cdf918923173e Mon Sep 17 00:00:00 2001 From: Ishvinder Sethi Date: Thu, 18 Apr 2024 12:37:47 +0530 Subject: [PATCH 10/29] Update settings.py --- backend/shoonya_backend/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/shoonya_backend/settings.py b/backend/shoonya_backend/settings.py index c37e2c2d4..2915ce894 100644 --- a/backend/shoonya_backend/settings.py +++ b/backend/shoonya_backend/settings.py @@ -187,7 +187,7 @@ # Email Settings -EMAIL_BACKEND = "django_smtp_ssl.SSLEmailBackend" +EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" EMAIL_HOST = os.getenv("EMAIL_HOST") EMAIL_PORT = os.getenv("EMAIL_PORT") EMAIL_HOST_USER = os.getenv("SMTP_USERNAME") From 45666a8e395716ffc46d7dc12dbea17a367b5520 Mon Sep 17 00:00:00 2001 From: Ishvinder Sethi Date: Thu, 18 Apr 2024 13:20:53 +0530 Subject: [PATCH 11/29] Update views.py --- backend/dataset/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/dataset/views.py b/backend/dataset/views.py index 5c9e1fa21..b330b8923 100644 --- a/backend/dataset/views.py +++ b/backend/dataset/views.py @@ -305,7 +305,7 @@ def download(self, request, pk): URL: /data/instances//download/ Accepted methods: GET """ - export_type = request.GET.get("export_type", "csv").lower() + export_type = request.GET.get("type", "csv").lower() try: # Get the dataset instance for the id dataset_instance = DatasetInstance.objects.get(instance_id=pk) From 6049f8555a93330db21ea732364efe2e43b82485 Mon Sep 17 00:00:00 2001 From: Ishvinder Sethi Date: Thu, 18 Apr 2024 13:37:57 +0530 Subject: [PATCH 12/29] Update views.py --- backend/dataset/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/dataset/views.py b/backend/dataset/views.py index b330b8923..5c9e1fa21 100644 --- a/backend/dataset/views.py +++ b/backend/dataset/views.py @@ -305,7 +305,7 @@ def download(self, request, pk): URL: /data/instances//download/ Accepted methods: GET """ - export_type = request.GET.get("type", "csv").lower() + export_type = request.GET.get("export_type", "csv").lower() try: # Get the dataset instance for the id dataset_instance = DatasetInstance.objects.get(instance_id=pk) From 558c4239fe46a6c0505cbfee201f7c9b1632f30e Mon Sep 17 00:00:00 2001 From: Pursottam6003 Date: Thu, 18 Apr 2024 22:53:37 +0530 Subject: [PATCH 13/29] updated the api endpoints from backend side for decentralised approvals --- backend/users/models.py | 2 +- backend/users/serializers.py | 12 +++++++ backend/users/views.py | 62 +++++++++++++++++++++++++----------- 3 files changed, 56 insertions(+), 20 deletions(-) diff --git a/backend/users/models.py b/backend/users/models.py index 510a1dd65..5f8dccf29 100644 --- a/backend/users/models.py +++ b/backend/users/models.py @@ -218,7 +218,7 @@ class User(AbstractBaseUser, PermissionsMixin): is_approved = models.BooleanField( verbose_name="is_approved", - default=True, + default=False, help_text=("Indicates whether user is approved by the admin or not."), ) diff --git a/backend/users/serializers.py b/backend/users/serializers.py index 414388300..4c8a00dce 100644 --- a/backend/users/serializers.py +++ b/backend/users/serializers.py @@ -27,6 +27,18 @@ def update(self, instance, validated_data): instance.set_password(validated_data.get("password")) instance.save() return instance +class UsersPendingSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = ["id", "username", "first_name","last_name", "email"] + + def update(self, instance, validated_data): + instance.id = validated_data.get("id", instance.id) + instance.username = validated_data.get("username", instance.username) + instance.first_name(validated_data.get("first_name", instance.first_name)) + instance.last_name(validated_data.get("last_name", instance.last_name)) + instance.save() + return instance class UserUpdateSerializer(serializers.ModelSerializer): diff --git a/backend/users/views.py b/backend/users/views.py index d4887c232..f041b442a 100644 --- a/backend/users/views.py +++ b/backend/users/views.py @@ -20,6 +20,7 @@ UserLoginSerializer, UserProfileSerializer, UserSignUpSerializer, + UsersPendingSerializer, UserUpdateSerializer, LanguageSerializer, ChangePasswordSerializer, @@ -282,28 +283,38 @@ def sign_up_user(self, request, pk=None): return Response({"message": "User signed up"}, status=status.HTTP_200_OK) # function to list the users whose user.is_approved is false @permission_classes([IsAuthenticated]) - @swagger_auto_schema(responses={200: UserSignUpSerializer}) + @swagger_auto_schema(responses={200: UsersPendingSerializer}) @action(detail=False, methods=["get"], url_path="pending_users") def pending_users(self, request): """ List of users who have not accepted the invite yet in that organisation/workspace """ - organisation_id = request.user.organization_id - users = User.objects.filter(organization_id=organisation_id, is_approved=True) - serialized = UserSignUpSerializer(users, many=True) - return Response(serialized.data, status=status.HTTP_200_OK) + organisation_id = request.query_params.get('organisation_id') + users = User.objects.filter(organization_id=organisation_id, is_approved=False) + serialized = UsersPendingSerializer(users, many=True) + + if(serialized.data) : + return Response(serialized.data, status=status.HTTP_200_OK) + + return Response({"message": "No pending users"}, status=status.HTTP_200_OK) # function to reject the user request to join the workspace by organiastion owner and delete the user from the table @permission_classes([IsAuthenticated]) @is_organization_owner - @swagger_auto_schema(request_body=UserSignUpSerializer) - @action(detail=True, methods=["delete"], url_path="reject_user") - def reject_user(self, request, pk=None): + @swagger_auto_schema(request_body=UsersPendingSerializer) + @action(detail=False, methods=["delete"], url_path="reject_user") + def reject_user(self, request): """ Reject the user request to join the workspace """ try: - user = User.objects.get(id=pk) + user_id = request.user.id + user = User.objects.get(id=user_id) + + if user.is_approved == True: + return Response( + {"message": "User is already approved"}, status=status.HTTP_400_BAD_REQUEST + ) except User.DoesNotExist: return Response( {"message": "User not found"}, status=status.HTTP_404_NOT_FOUND @@ -314,32 +325,45 @@ def reject_user(self, request, pk=None): # function to approve the user request to join the workspace by organiastion owner and update the user.is_approved to true @permission_classes([IsAuthenticated]) @is_organization_owner - @swagger_auto_schema(request_body=UserSignUpSerializer) - @action(detail=True, methods=["patch"], url_path="approve_user") - def approve_user(self, request, pk=None): + @swagger_auto_schema(request_body=UsersPendingSerializer) + @action(detail=False, methods=["patch"], url_path="approve_user") + def approve_user(self, request): """ Approve the user request to join the workspace """ try: - user = User.objects.get(id=pk) + user_id = request.user.id + user = User.objects.get(id=user_id) + + if user.is_approved == True: + return Response( + {"message": "User is already approved"}, status=status.HTTP_400_BAD_REQUEST + ) + + user.is_approved = True + user.save() except User.DoesNotExist: return Response( {"message": "User not found"}, status=status.HTTP_404_NOT_FOUND ) - user.is_approved = True - user.save() + except Exception as e: + return Response( + {"message": "Error in approving user"}, status=status.HTTP_400_BAD_REQUEST + ) + return Response({"message": "User approved"}, status=status.HTTP_200_OK) # function to request workspace owner to add the users to the workspace by workspace manager @permission_classes([IsAuthenticated]) - @swagger_auto_schema(request_body=UserSignUpSerializer) - @action(detail=True, methods=["post"], url_path="request_user") - def request_user(self, request, pk=None): + @swagger_auto_schema(request_body=UsersPendingSerializer) + @action(detail=False, methods=["post"], url_path="request_user") + def request_user(self, request): """ Request the workspace owner to add the user to the workspace """ try: - user = User.objects.get(id=pk) + user_id = request.user.id + user = User.objects.get(id=user_id) except User.DoesNotExist: return Response( {"message": "User not found"}, status=status.HTTP_404_NOT_FOUND From e6025b39d2a135c147d4379132adac889bd4c819 Mon Sep 17 00:00:00 2001 From: Pursottam6003 Date: Thu, 18 Apr 2024 22:59:23 +0530 Subject: [PATCH 14/29] updated the liniting with black --- .../migrations/0046_merge_20240416_2233.py | 8 ++--- .../migrations/0032_auto_20240417_1028.py | 25 ++++++++----- backend/users/models.py | 10 +++--- backend/users/serializers.py | 4 ++- backend/users/views.py | 36 +++++++++++-------- 5 files changed, 50 insertions(+), 33 deletions(-) diff --git a/backend/dataset/migrations/0046_merge_20240416_2233.py b/backend/dataset/migrations/0046_merge_20240416_2233.py index edbce6ca1..24617f1e7 100644 --- a/backend/dataset/migrations/0046_merge_20240416_2233.py +++ b/backend/dataset/migrations/0046_merge_20240416_2233.py @@ -4,11 +4,9 @@ class Migration(migrations.Migration): - dependencies = [ - ('dataset', '0045_alter_ocrdocument_ocr_domain'), - ('dataset', '0045_auto_20240321_0949'), + ("dataset", "0045_alter_ocrdocument_ocr_domain"), + ("dataset", "0045_auto_20240321_0949"), ] - operations = [ - ] + operations = [] diff --git a/backend/users/migrations/0032_auto_20240417_1028.py b/backend/users/migrations/0032_auto_20240417_1028.py index 7360b53f0..5bbd87b9e 100644 --- a/backend/users/migrations/0032_auto_20240417_1028.py +++ b/backend/users/migrations/0032_auto_20240417_1028.py @@ -6,20 +6,29 @@ class Migration(migrations.Migration): - dependencies = [ - ('users', '0031_user_notification_limit'), + ("users", "0031_user_notification_limit"), ] operations = [ migrations.AddField( - model_name='user', - name='approved_by', - field=models.ForeignKey(blank=True, default=1, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL), + model_name="user", + name="approved_by", + field=models.ForeignKey( + blank=True, + default=1, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + ), ), migrations.AddField( - model_name='user', - name='is_approved', - field=models.BooleanField(default=True, help_text='Indicates whether user is approved by the admin or not.', verbose_name='is_approved'), + model_name="user", + name="is_approved", + field=models.BooleanField( + default=True, + help_text="Indicates whether user is approved by the admin or not.", + verbose_name="is_approved", + ), ), ] diff --git a/backend/users/models.py b/backend/users/models.py index 5f8dccf29..90d63d5e1 100644 --- a/backend/users/models.py +++ b/backend/users/models.py @@ -223,18 +223,20 @@ class User(AbstractBaseUser, PermissionsMixin): ) approved_by = models.ForeignKey( - 'self', - on_delete=models.SET_NULL, - null=True, - blank=True, + "self", + on_delete=models.SET_NULL, + null=True, + blank=True, default=1, ) + class Meta: db_table = "user" indexes = [ models.Index(fields=["username"]), models.Index(fields=["email"]), ] + def clean(self): super().clean() self.email = self.__class__.objects.normalize_email(self.email) diff --git a/backend/users/serializers.py b/backend/users/serializers.py index 4c8a00dce..f7c4291bc 100644 --- a/backend/users/serializers.py +++ b/backend/users/serializers.py @@ -27,10 +27,12 @@ def update(self, instance, validated_data): instance.set_password(validated_data.get("password")) instance.save() return instance + + class UsersPendingSerializer(serializers.ModelSerializer): class Meta: model = User - fields = ["id", "username", "first_name","last_name", "email"] + fields = ["id", "username", "first_name", "last_name", "email"] def update(self, instance, validated_data): instance.id = validated_data.get("id", instance.id) diff --git a/backend/users/views.py b/backend/users/views.py index f041b442a..23ac585e7 100644 --- a/backend/users/views.py +++ b/backend/users/views.py @@ -106,7 +106,7 @@ def invite_users(self, request): email=email.lower(), organization_id=org.id, role=request.data.get("role"), - is_approved=True, # as it can be only done by project owner + is_approved=True, # as it can be only done by project owner approved_by=request.user.user_id, ) user.set_password(generate_random_string(10)) @@ -281,7 +281,8 @@ def sign_up_user(self, request, pk=None): if serialized.is_valid(): serialized.save() return Response({"message": "User signed up"}, status=status.HTTP_200_OK) - # function to list the users whose user.is_approved is false + + # function to list the users whose user.is_approved is false @permission_classes([IsAuthenticated]) @swagger_auto_schema(responses={200: UsersPendingSerializer}) @action(detail=False, methods=["get"], url_path="pending_users") @@ -289,16 +290,16 @@ def pending_users(self, request): """ List of users who have not accepted the invite yet in that organisation/workspace """ - organisation_id = request.query_params.get('organisation_id') + organisation_id = request.query_params.get("organisation_id") users = User.objects.filter(organization_id=organisation_id, is_approved=False) serialized = UsersPendingSerializer(users, many=True) - if(serialized.data) : + if serialized.data: return Response(serialized.data, status=status.HTTP_200_OK) - return Response({"message": "No pending users"}, status=status.HTTP_200_OK) - - # function to reject the user request to join the workspace by organiastion owner and delete the user from the table + return Response({"message": "No pending users"}, status=status.HTTP_200_OK) + + # function to reject the user request to join the workspace by organiastion owner and delete the user from the table @permission_classes([IsAuthenticated]) @is_organization_owner @swagger_auto_schema(request_body=UsersPendingSerializer) @@ -311,9 +312,10 @@ def reject_user(self, request): user_id = request.user.id user = User.objects.get(id=user_id) - if user.is_approved == True: + if user.is_approved == True: return Response( - {"message": "User is already approved"}, status=status.HTTP_400_BAD_REQUEST + {"message": "User is already approved"}, + status=status.HTTP_400_BAD_REQUEST, ) except User.DoesNotExist: return Response( @@ -335,11 +337,12 @@ def approve_user(self, request): user_id = request.user.id user = User.objects.get(id=user_id) - if user.is_approved == True: + if user.is_approved == True: return Response( - {"message": "User is already approved"}, status=status.HTTP_400_BAD_REQUEST + {"message": "User is already approved"}, + status=status.HTTP_400_BAD_REQUEST, ) - + user.is_approved = True user.save() except User.DoesNotExist: @@ -348,11 +351,12 @@ def approve_user(self, request): ) except Exception as e: return Response( - {"message": "Error in approving user"}, status=status.HTTP_400_BAD_REQUEST + {"message": "Error in approving user"}, + status=status.HTTP_400_BAD_REQUEST, ) - + return Response({"message": "User approved"}, status=status.HTTP_200_OK) - + # function to request workspace owner to add the users to the workspace by workspace manager @permission_classes([IsAuthenticated]) @swagger_auto_schema(request_body=UsersPendingSerializer) @@ -371,6 +375,8 @@ def request_user(self, request): user.is_approved = False user.save() return Response({"message": "User requested"}, status=status.HTTP_200_OK) + + class AuthViewSet(viewsets.ViewSet): @permission_classes([AllowAny]) @swagger_auto_schema(request_body=UserLoginSerializer) From ea482d5bfd6359dc49d406067d17794451272fa2 Mon Sep 17 00:00:00 2001 From: Ishan Gujarathi Date: Fri, 19 Apr 2024 11:27:32 +0530 Subject: [PATCH 15/29] added a feature to remove user both as worskpace member and workspace manager when user is marked as inactive at organization level --- backend/users/views.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/backend/users/views.py b/backend/users/views.py index b02aeb403..ccd44f389 100644 --- a/backend/users/views.py +++ b/backend/users/views.py @@ -59,6 +59,7 @@ from rest_framework_simplejwt.tokens import RefreshToken from dotenv import load_dotenv import logging +from workspaces.views import WorkspaceusersViewSet logger = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) @@ -675,6 +676,39 @@ def user_details_update(self, request, pk=None): user = User.objects.get(id=pk) serializer = UserUpdateSerializer(user, request.data, partial=True) + existing_is_active = user.is_active + is_active_payload = request.data.get("is_active", None) + + if existing_is_active == is_active_payload: + pass + else: + if is_active_payload is False: + workspaces = Workspace.objects.filter( + Q(members=user) | Q(managers=user) + ).distinct() + + workspacecustomviewset_obj = WorkspaceCustomViewSet() + request.data["ids"] = [user.id] + + workspaceusersviewset_obj = WorkspaceusersViewSet() + request.data["user_id"] = user.id + + for workspace in workspaces: + workspacecustomviewset_obj.unassign_manager( + request=request, pk=workspace.pk + ) + + workspaceusersviewset_obj.remove_members( + request=request, pk=workspace.pk + ) + + return Response( + { + "message": "User removed from all workspaces both as workspace member and workspace manager" + }, + status=status.HTTP_200_OK, + ) + if request.data["role"] != user.role: new_role = int(request.data["role"]) old_role = int(user.role) From 8219ef735ad63cc2565ef5d480cba084da62bcfc Mon Sep 17 00:00:00 2001 From: Ishan Gujarathi Date: Tue, 23 Apr 2024 16:33:37 +0530 Subject: [PATCH 16/29] disabling user from daily emails when user is marked inactive at organization level --- backend/users/views.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/users/views.py b/backend/users/views.py index ccd44f389..2f6506ed0 100644 --- a/backend/users/views.py +++ b/backend/users/views.py @@ -683,6 +683,9 @@ def user_details_update(self, request, pk=None): pass else: if is_active_payload is False: + if user.enable_mail: + user.enable_mail = False + user.save() workspaces = Workspace.objects.filter( Q(members=user) | Q(managers=user) ).distinct() From ac027cda4afc6e3e40e0b7d6581f7dc627e932c2 Mon Sep 17 00:00:00 2001 From: Pursottam6003 Date: Tue, 23 Apr 2024 20:07:40 +0530 Subject: [PATCH 17/29] updated the backend code as per the recent modificiations --- backend/shoonya_backend/settings.py | 8 +- ...0033_rename_approved_by_user_invited_by.py | 18 ++++ backend/users/models.py | 4 +- backend/users/serializers.py | 6 +- backend/users/views.py | 102 +++++++++++++++--- 5 files changed, 116 insertions(+), 22 deletions(-) create mode 100644 backend/users/migrations/0033_rename_approved_by_user_invited_by.py diff --git a/backend/shoonya_backend/settings.py b/backend/shoonya_backend/settings.py index cb5855366..be95f1d0f 100644 --- a/backend/shoonya_backend/settings.py +++ b/backend/shoonya_backend/settings.py @@ -35,7 +35,7 @@ DEBUG = os.getenv("ENV") == "dev" if DEBUG: - ALLOWED_HOSTS = ["127.0.0.1", "localhost", "0.0.0.0", "*"] + ALLOWED_HOSTS = ["127.0.0.1", "localhost", "0.0.0.0", "*"] else: ALLOWED_HOSTS = [ "shoonya.ai4bharat.org", @@ -187,7 +187,7 @@ # Email Settings -EMAIL_BACKEND = "django_smtp_ssl.SSLEmailBackend" +EMAIL_BACKEND = "django_smtp_ssl.SSLEmailBackend" EMAIL_HOST = os.getenv("EMAIL_HOST") EMAIL_PORT = 465 EMAIL_HOST_USER = os.getenv("SMTP_USERNAME") @@ -195,8 +195,8 @@ EMAIL_USE_TLS = True DEFAULT_FROM_EMAIL = os.getenv("DEFAULT_FROM_EMAIL") -DOMAIN = "shoonya.ai4bharat.org" -SITE_NAME = "shoonya.ai4bharat.org" +DOMAIN = "shoonya.ai4bharat.org" +SITE_NAME = "shoonya.ai4bharat.org" DJOSER = { "PASSWORD_RESET_CONFIRM_URL": "#/forget-password/confirm/{uid}/{token}", diff --git a/backend/users/migrations/0033_rename_approved_by_user_invited_by.py b/backend/users/migrations/0033_rename_approved_by_user_invited_by.py new file mode 100644 index 000000000..a720cc57c --- /dev/null +++ b/backend/users/migrations/0033_rename_approved_by_user_invited_by.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.14 on 2024-04-22 14:02 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0032_auto_20240417_1028'), + ] + + operations = [ + migrations.RenameField( + model_name='user', + old_name='approved_by', + new_name='invited_by', + ), + ] diff --git a/backend/users/models.py b/backend/users/models.py index 90d63d5e1..a445d02c8 100644 --- a/backend/users/models.py +++ b/backend/users/models.py @@ -218,11 +218,11 @@ class User(AbstractBaseUser, PermissionsMixin): is_approved = models.BooleanField( verbose_name="is_approved", - default=False, + default=True, help_text=("Indicates whether user is approved by the admin or not."), ) - approved_by = models.ForeignKey( + invited_by = models.ForeignKey( "self", on_delete=models.SET_NULL, null=True, diff --git a/backend/users/serializers.py b/backend/users/serializers.py index f7c4291bc..c2d2c9add 100644 --- a/backend/users/serializers.py +++ b/backend/users/serializers.py @@ -32,13 +32,17 @@ def update(self, instance, validated_data): class UsersPendingSerializer(serializers.ModelSerializer): class Meta: model = User - fields = ["id", "username", "first_name", "last_name", "email"] + fields = ["id", "username", "first_name", "last_name", "email","role","invited_by","has_accepted_invite"] def update(self, instance, validated_data): instance.id = validated_data.get("id", instance.id) instance.username = validated_data.get("username", instance.username) instance.first_name(validated_data.get("first_name", instance.first_name)) instance.last_name(validated_data.get("last_name", instance.last_name)) + instance.email(validated_data.get("email", instance.email)) + instance.role(validated_data.get("role", instance.role)) + instance.invited_by(validated_data.get("invited_by", instance.invited_by)) + instance.has_accepted_invite(validated_data.get("has_accepted_invite", instance.has_accepted_invite)) instance.save() return instance diff --git a/backend/users/views.py b/backend/users/views.py index 23ac585e7..8eed1237d 100644 --- a/backend/users/views.py +++ b/backend/users/views.py @@ -107,7 +107,7 @@ def invite_users(self, request): organization_id=org.id, role=request.data.get("role"), is_approved=True, # as it can be only done by project owner - approved_by=request.user.user_id, + invited_by=request.user.user_id, ) user.set_password(generate_random_string(10)) valid_user_emails.append(email) @@ -281,7 +281,8 @@ def sign_up_user(self, request, pk=None): if serialized.is_valid(): serialized.save() return Response({"message": "User signed up"}, status=status.HTTP_200_OK) - + # 1 add users to workspace - workspace name + # 2. Invite new users to {organisation name} # function to list the users whose user.is_approved is false @permission_classes([IsAuthenticated]) @swagger_auto_schema(responses={200: UsersPendingSerializer}) @@ -309,7 +310,7 @@ def reject_user(self, request): Reject the user request to join the workspace """ try: - user_id = request.user.id + user_id = request.query_params.get('userId', None) user = User.objects.get(id=user_id) if user.is_approved == True: @@ -334,9 +335,10 @@ def approve_user(self, request): Approve the user request to join the workspace """ try: - user_id = request.user.id + user_id = request.query_params.get('userId', None) user = User.objects.get(id=user_id) - + organisation_id = user.organization_id + if user.is_approved == True: return Response( {"message": "User is already approved"}, @@ -345,6 +347,8 @@ def approve_user(self, request): user.is_approved = True user.save() + # invite the user via mail now + Invite.create_invite(organization=organisation_id,users=user) except User.DoesNotExist: return Response( {"message": "User not found"}, status=status.HTTP_404_NOT_FOUND @@ -356,26 +360,94 @@ def approve_user(self, request): ) return Response({"message": "User approved"}, status=status.HTTP_200_OK) - # function to request workspace owner to add the users to the workspace by workspace manager @permission_classes([IsAuthenticated]) - @swagger_auto_schema(request_body=UsersPendingSerializer) + @swagger_auto_schema(request_body=InviteGenerationSerializer) @action(detail=False, methods=["post"], url_path="request_user") def request_user(self, request): """ - Request the workspace owner to add the user to the workspace + Request the workspace owner to add the user to the workspace from manager """ + all_emails = request.data.get("emails") + distinct_emails = list(set(all_emails)) + organization_id = request.data.get("organization_id") + users = [] + try: - user_id = request.user.id - user = User.objects.get(id=user_id) - except User.DoesNotExist: + org = Organization.objects.get(id=organization_id) + except Organization.DoesNotExist: return Response( - {"message": "User not found"}, status=status.HTTP_404_NOT_FOUND + {"message": "Organization not found"}, status=status.HTTP_404_NOT_FOUND ) - user.is_approved = False - user.save() - return Response({"message": "User requested"}, status=status.HTTP_200_OK) + already_existing_emails = [] + valid_user_emails = [] + invalid_emails = [] + existing_emails_set = set(Invite.objects.values_list("user__email", flat=True)) + for email in distinct_emails: + # Checking if the email is in valid format. + if re.fullmatch(regex, email): + if email in existing_emails_set: + already_existing_emails.append(email) + continue + try: + user = User( + username=generate_random_string(12), + email=email.lower(), + organization_id=org.id, + role=request.data.get("role"), + is_approved=False, + invited_by=request.user.user_id, + ) + user.set_password(generate_random_string(10)) + valid_user_emails.append(email) + users.append(user) + except: + pass + else: + invalid_emails.append(email) + # setting error messages + ( + additional_message_for_existing_emails, + additional_message_for_invalid_emails, + ) = ("", "") + additional_message_for_valid_emails = "" + if already_existing_emails: + additional_message_for_existing_emails += ( + f", Invites already sent to: {','.join(already_existing_emails)}" + ) + if invalid_emails: + additional_message_for_invalid_emails += ( + f", Invalid emails: {','.join(invalid_emails)}" + ) + if valid_user_emails: + additional_message_for_valid_emails += ( + f", Invites sent to : {','.join(valid_user_emails)}" + ) + if len(valid_user_emails) == 0: + return Response( + { + "message": "No invites sent" + + additional_message_for_invalid_emails + + additional_message_for_existing_emails + }, + status=status.HTTP_400_BAD_REQUEST, + ) + elif len(invalid_emails) == 0: + ret_dict = { + "message": "Invites sent & request sent to workspace owner" + + additional_message_for_valid_emails + + additional_message_for_existing_emails + } + else: + ret_dict = { + "message": f"Invites sent partially!" + + additional_message_for_valid_emails + + additional_message_for_invalid_emails + + additional_message_for_existing_emails + } + users = User.objects.bulk_create(users) + return Response(ret_dict, status=status.HTTP_201_CREATED) class AuthViewSet(viewsets.ViewSet): @permission_classes([AllowAny]) From 0503229d3c5e5d8fe378120e7a1cf54653bd46f6 Mon Sep 17 00:00:00 2001 From: Pursottam6003 Date: Tue, 23 Apr 2024 20:10:41 +0530 Subject: [PATCH 18/29] ran the black formatting --- backend/shoonya_backend/settings.py | 8 ++++---- .../0033_rename_approved_by_user_invited_by.py | 9 ++++----- backend/users/serializers.py | 15 +++++++++++++-- backend/users/views.py | 17 ++++++++++------- 4 files changed, 31 insertions(+), 18 deletions(-) diff --git a/backend/shoonya_backend/settings.py b/backend/shoonya_backend/settings.py index be95f1d0f..cb5855366 100644 --- a/backend/shoonya_backend/settings.py +++ b/backend/shoonya_backend/settings.py @@ -35,7 +35,7 @@ DEBUG = os.getenv("ENV") == "dev" if DEBUG: - ALLOWED_HOSTS = ["127.0.0.1", "localhost", "0.0.0.0", "*"] + ALLOWED_HOSTS = ["127.0.0.1", "localhost", "0.0.0.0", "*"] else: ALLOWED_HOSTS = [ "shoonya.ai4bharat.org", @@ -187,7 +187,7 @@ # Email Settings -EMAIL_BACKEND = "django_smtp_ssl.SSLEmailBackend" +EMAIL_BACKEND = "django_smtp_ssl.SSLEmailBackend" EMAIL_HOST = os.getenv("EMAIL_HOST") EMAIL_PORT = 465 EMAIL_HOST_USER = os.getenv("SMTP_USERNAME") @@ -195,8 +195,8 @@ EMAIL_USE_TLS = True DEFAULT_FROM_EMAIL = os.getenv("DEFAULT_FROM_EMAIL") -DOMAIN = "shoonya.ai4bharat.org" -SITE_NAME = "shoonya.ai4bharat.org" +DOMAIN = "shoonya.ai4bharat.org" +SITE_NAME = "shoonya.ai4bharat.org" DJOSER = { "PASSWORD_RESET_CONFIRM_URL": "#/forget-password/confirm/{uid}/{token}", diff --git a/backend/users/migrations/0033_rename_approved_by_user_invited_by.py b/backend/users/migrations/0033_rename_approved_by_user_invited_by.py index a720cc57c..afafab796 100644 --- a/backend/users/migrations/0033_rename_approved_by_user_invited_by.py +++ b/backend/users/migrations/0033_rename_approved_by_user_invited_by.py @@ -4,15 +4,14 @@ class Migration(migrations.Migration): - dependencies = [ - ('users', '0032_auto_20240417_1028'), + ("users", "0032_auto_20240417_1028"), ] operations = [ migrations.RenameField( - model_name='user', - old_name='approved_by', - new_name='invited_by', + model_name="user", + old_name="approved_by", + new_name="invited_by", ), ] diff --git a/backend/users/serializers.py b/backend/users/serializers.py index c2d2c9add..98048b15a 100644 --- a/backend/users/serializers.py +++ b/backend/users/serializers.py @@ -32,7 +32,16 @@ def update(self, instance, validated_data): class UsersPendingSerializer(serializers.ModelSerializer): class Meta: model = User - fields = ["id", "username", "first_name", "last_name", "email","role","invited_by","has_accepted_invite"] + fields = [ + "id", + "username", + "first_name", + "last_name", + "email", + "role", + "invited_by", + "has_accepted_invite", + ] def update(self, instance, validated_data): instance.id = validated_data.get("id", instance.id) @@ -42,7 +51,9 @@ def update(self, instance, validated_data): instance.email(validated_data.get("email", instance.email)) instance.role(validated_data.get("role", instance.role)) instance.invited_by(validated_data.get("invited_by", instance.invited_by)) - instance.has_accepted_invite(validated_data.get("has_accepted_invite", instance.has_accepted_invite)) + instance.has_accepted_invite( + validated_data.get("has_accepted_invite", instance.has_accepted_invite) + ) instance.save() return instance diff --git a/backend/users/views.py b/backend/users/views.py index 8eed1237d..d44d27a18 100644 --- a/backend/users/views.py +++ b/backend/users/views.py @@ -281,7 +281,8 @@ def sign_up_user(self, request, pk=None): if serialized.is_valid(): serialized.save() return Response({"message": "User signed up"}, status=status.HTTP_200_OK) - # 1 add users to workspace - workspace name + + # 1 add users to workspace - workspace name # 2. Invite new users to {organisation name} # function to list the users whose user.is_approved is false @permission_classes([IsAuthenticated]) @@ -310,7 +311,7 @@ def reject_user(self, request): Reject the user request to join the workspace """ try: - user_id = request.query_params.get('userId', None) + user_id = request.query_params.get("userId", None) user = User.objects.get(id=user_id) if user.is_approved == True: @@ -335,10 +336,10 @@ def approve_user(self, request): Approve the user request to join the workspace """ try: - user_id = request.query_params.get('userId', None) + user_id = request.query_params.get("userId", None) user = User.objects.get(id=user_id) organisation_id = user.organization_id - + if user.is_approved == True: return Response( {"message": "User is already approved"}, @@ -347,8 +348,8 @@ def approve_user(self, request): user.is_approved = True user.save() - # invite the user via mail now - Invite.create_invite(organization=organisation_id,users=user) + # invite the user via mail now + Invite.create_invite(organization=organisation_id, users=user) except User.DoesNotExist: return Response( {"message": "User not found"}, status=status.HTTP_404_NOT_FOUND @@ -360,13 +361,14 @@ def approve_user(self, request): ) return Response({"message": "User approved"}, status=status.HTTP_200_OK) + # function to request workspace owner to add the users to the workspace by workspace manager @permission_classes([IsAuthenticated]) @swagger_auto_schema(request_body=InviteGenerationSerializer) @action(detail=False, methods=["post"], url_path="request_user") def request_user(self, request): """ - Request the workspace owner to add the user to the workspace from manager + Request the workspace owner to add the user to the workspace from manager """ all_emails = request.data.get("emails") distinct_emails = list(set(all_emails)) @@ -449,6 +451,7 @@ def request_user(self, request): users = User.objects.bulk_create(users) return Response(ret_dict, status=status.HTTP_201_CREATED) + class AuthViewSet(viewsets.ViewSet): @permission_classes([AllowAny]) @swagger_auto_schema(request_body=UserLoginSerializer) From c838b38e1f6c699a1d4c28ef60bce5dfacf31a2e Mon Sep 17 00:00:00 2001 From: Pursottam6003 Date: Tue, 23 Apr 2024 22:59:35 +0530 Subject: [PATCH 19/29] minor fix --- backend/users/views.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/backend/users/views.py b/backend/users/views.py index d44d27a18..977ecbd56 100644 --- a/backend/users/views.py +++ b/backend/users/views.py @@ -107,7 +107,6 @@ def invite_users(self, request): organization_id=org.id, role=request.data.get("role"), is_approved=True, # as it can be only done by project owner - invited_by=request.user.user_id, ) user.set_password(generate_random_string(10)) valid_user_emails.append(email) @@ -349,17 +348,18 @@ def approve_user(self, request): user.is_approved = True user.save() # invite the user via mail now - Invite.create_invite(organization=organisation_id, users=user) + try: + users = [user] + Invite.create_invite(organization=organisation_id, users=users) + except: + return Response( + {"message": "Error in sending invite"}, + status=status.HTTP_400_BAD_REQUEST, + ) except User.DoesNotExist: return Response( {"message": "User not found"}, status=status.HTTP_404_NOT_FOUND ) - except Exception as e: - return Response( - {"message": "Error in approving user"}, - status=status.HTTP_400_BAD_REQUEST, - ) - return Response({"message": "User approved"}, status=status.HTTP_200_OK) # function to request workspace owner to add the users to the workspace by workspace manager @@ -398,8 +398,8 @@ def request_user(self, request): email=email.lower(), organization_id=org.id, role=request.data.get("role"), + has_accepted_invite=False, is_approved=False, - invited_by=request.user.user_id, ) user.set_password(generate_random_string(10)) valid_user_emails.append(email) From 542b41250b960db759870f95cd3b2230c59f7e08 Mon Sep 17 00:00:00 2001 From: Pursottam6003 Date: Fri, 26 Apr 2024 10:21:08 +0530 Subject: [PATCH 20/29] updated models and we dont need to apply migrations --- backend/users/models.py | 2 +- backend/users/views.py | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/backend/users/models.py b/backend/users/models.py index a445d02c8..c391d5f23 100644 --- a/backend/users/models.py +++ b/backend/users/models.py @@ -218,7 +218,7 @@ class User(AbstractBaseUser, PermissionsMixin): is_approved = models.BooleanField( verbose_name="is_approved", - default=True, + default=False, help_text=("Indicates whether user is approved by the admin or not."), ) diff --git a/backend/users/views.py b/backend/users/views.py index 977ecbd56..e29cdac48 100644 --- a/backend/users/views.py +++ b/backend/users/views.py @@ -106,13 +106,16 @@ def invite_users(self, request): email=email.lower(), organization_id=org.id, role=request.data.get("role"), - is_approved=True, # as it can be only done by project owner ) + user.is_approved = True user.set_password(generate_random_string(10)) valid_user_emails.append(email) users.append(user) except: - pass + return Response( + {"message": "Error in creating user"}, + status=status.HTTP_400_BAD_REQUEST, + ) else: invalid_emails.append(email) # setting error messages From 4da8925ebd3fa7b7595a514c2ff844b7d6365290 Mon Sep 17 00:00:00 2001 From: Pursottam6003 Date: Mon, 29 Apr 2024 16:13:41 +0530 Subject: [PATCH 21/29] updated the linting and change the backend code --- backend/users/views.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/backend/users/views.py b/backend/users/views.py index e29cdac48..25f8782b1 100644 --- a/backend/users/views.py +++ b/backend/users/views.py @@ -332,7 +332,7 @@ def reject_user(self, request): @permission_classes([IsAuthenticated]) @is_organization_owner @swagger_auto_schema(request_body=UsersPendingSerializer) - @action(detail=False, methods=["patch"], url_path="approve_user") + @action(detail=False, methods=["post"], url_path="approve_user") def approve_user(self, request): """ Approve the user request to join the workspace @@ -342,21 +342,28 @@ def approve_user(self, request): user = User.objects.get(id=user_id) organisation_id = user.organization_id + try: + organisation = Organization.objects.get(id=organisation_id) + except Organization.DoesNotExist: + return Response( + {"message": "Organization not found"}, + status=status.HTTP_404_NOT_FOUND, + ) if user.is_approved == True: return Response( {"message": "User is already approved"}, status=status.HTTP_400_BAD_REQUEST, ) - user.is_approved = True user.save() # invite the user via mail now try: - users = [user] - Invite.create_invite(organization=organisation_id, users=users) - except: + users = [] + users.append(user) + Invite.create_invite(organization=organisation, users=users) + except Exception as e: return Response( - {"message": "Error in sending invite"}, + {"message": f"Error in sending invite: {str(e)}"}, status=status.HTTP_400_BAD_REQUEST, ) except User.DoesNotExist: From e8c8edcd27d45ed11236be27fa01ee309a097157 Mon Sep 17 00:00:00 2001 From: Ishan Gujarathi Date: Mon, 29 Apr 2024 17:31:54 +0530 Subject: [PATCH 22/29] fixed the bug regarding status not getting changed in the Active status column --- backend/users/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/users/views.py b/backend/users/views.py index 2f6506ed0..ccdc3bd5c 100644 --- a/backend/users/views.py +++ b/backend/users/views.py @@ -704,7 +704,8 @@ def user_details_update(self, request, pk=None): workspaceusersviewset_obj.remove_members( request=request, pk=workspace.pk ) - + user.is_active = False + user.save() return Response( { "message": "User removed from all workspaces both as workspace member and workspace manager" From 2f81410de100bdacb99f880d052e0bc00dc494fe Mon Sep 17 00:00:00 2001 From: Pursottam6003 Date: Tue, 30 Apr 2024 23:11:07 +0530 Subject: [PATCH 23/29] updated the email templat using the env file --- backend/organizations/models.py | 273 ++++++++++++++++++++++++++++++-- backend/users/views.py | 15 +- 2 files changed, 273 insertions(+), 15 deletions(-) diff --git a/backend/organizations/models.py b/backend/organizations/models.py index afa16929c..b90d1220d 100644 --- a/backend/organizations/models.py +++ b/backend/organizations/models.py @@ -4,10 +4,11 @@ from shoonya_backend.settings import AUTH_USER_MODEL from shoonya_backend.mixins import DummyModelMixin import secrets -from django.core.mail import send_mail +from django.core.mail import EmailMultiAlternatives import os from dotenv import load_dotenv + load_dotenv() from django.conf import settings @@ -130,12 +131,134 @@ def create_invite(cls, organization=None, users=None): invite = Invite.objects.create(organization=organization, user=user) invite.invite_code = cls.generate_invite_code() invite.save() - send_mail( - "Invitation to join Organization", - f"Hello! You are invited to {organization.title}. Your Invite link is: https://shoonya.ai4bharat.org/#/invite/{invite.invite_code}", - settings.DEFAULT_FROM_EMAIL, - [user.email], + current_environment = os.getenv("ENV") + base_url = ( + "dev.shoonya.ai4bharat.org" + if current_environment == "dev" + else "shoonya.ai4bharat.org" + ) + subject = "Invitation to join Organization" + invite_link = f"https://{base_url}/#/invite/{invite.invite_code}" + text_content = f"Hello! You are invited to {organization.title}. Your Invite link is: " + html_content = f""" + + + + + +Invitation to join Shoonya Organisation + + + +
+
+

Invitaiton to join Shoonya

+
+
+
+ + + + + + + +
+ +Join Shoonya Now + +
+
+
+

+Please use the above link to verify your email address and complete your registration. +

+

+For security purposes, please do not share the this link with +anyone. +

+

+ If clicking the link doesn't work, you can copy and paste the link into your browser's address window, or retype it there. + {invite_link} +

+
+
+
+

+Best Regards,
+Shoonya Team +

+
+
+ + +""" + msg = EmailMultiAlternatives( + subject, text_content, settings.DEFAULT_FROM_EMAIL, [user.email] ) + msg.attach_alternative(html_content, "text/html") + msg.send() # def has_permission(self, user): # if self.organization.created_by.pk == user.pk or user.is_superuser: @@ -147,12 +270,140 @@ def re_invite(cls, users=None): with transaction.atomic(): for user in users: invite = Invite.objects.get(user=user) - send_mail( - "Invitation to join Organization", - f"Hello! You are invited to {invite.organization.title}. Your Invite link is: https://shoonya.ai4bharat.org/#/invite/{invite.invite_code}", - settings.DEFAULT_FROM_EMAIL, - [user.email], + current_environment = os.getenv("ENV") + base_url = ( + "dev.shoonya.ai4bharat.org" + if current_environment == "dev" + else "shoonya.ai4bharat.org" + ) + subject = "Invitation to join Organization" + invite_link = f"https://{base_url}/#/invite/{invite.invite_code}" + text_content = f"Hello! You are invited to {organization.title}. Your Invite link is: " + html_content = f""" + + + + + +Invitation to join Shoonya Organisation + + + +
+
+

Invitaiton to join Shoonya

+
+
+
+ + + + + + + +
+ +Join Shoonya Now + +
+
+
+

+Please use the above link to verify your email address and complete your registration. +

+

+For security purposes, please do not share the this link with +anyone. +

+

+ If clicking the link doesn't work, you can copy and paste the link into your browser's address window, or retype it there. + {invite_link} +

+
+
+
+

+Best Regards,
+Shoonya Team +

+
+
+ + +""" + msg = EmailMultiAlternatives( + subject, text_content, settings.DEFAULT_FROM_EMAIL, [user.email] ) + msg.attach_alternative(html_content, "text/html") + msg.send() + # send_mail( + # "Invitation to join Organization", + # f"Hello! You are invited to {invite.organization.title}. Your Invite link is: https://shoonya.ai4bharat.org/#/invite/{invite.invite_code}", + # settings.DEFAULT_FROM_EMAIL, + # [user.email], + # ) @classmethod def generate_invite_code(cls): diff --git a/backend/users/views.py b/backend/users/views.py index 25f8782b1..320a06901 100644 --- a/backend/users/views.py +++ b/backend/users/views.py @@ -296,6 +296,13 @@ def pending_users(self, request): """ organisation_id = request.query_params.get("organisation_id") users = User.objects.filter(organization_id=organisation_id, is_approved=False) + + # demo_user = User.objects.filter(id=1) + # filtered_user = demo_user.values_list("email", flat=True) + # # Convert QuerySet to list and get first element + # email = list(filtered_user)[0] + # print(email) + # print(request.user) serialized = UsersPendingSerializer(users, many=True) if serialized.data: @@ -434,12 +441,12 @@ def request_user(self, request): ) if valid_user_emails: additional_message_for_valid_emails += ( - f", Invites sent to : {','.join(valid_user_emails)}" + f", Requested users : {','.join(valid_user_emails)}" ) if len(valid_user_emails) == 0: return Response( { - "message": "No invites sent" + "message": "No Requests sent" + additional_message_for_invalid_emails + additional_message_for_existing_emails }, @@ -447,13 +454,13 @@ def request_user(self, request): ) elif len(invalid_emails) == 0: ret_dict = { - "message": "Invites sent & request sent to workspace owner" + "message": "The invites to this users will be sent after approval from the organization owner" + additional_message_for_valid_emails + additional_message_for_existing_emails } else: ret_dict = { - "message": f"Invites sent partially!" + "message": f"Request sent partially!" + additional_message_for_valid_emails + additional_message_for_invalid_emails + additional_message_for_existing_emails From 1a061affbbbd13ff91421705c3dfb41d7cef6b73 Mon Sep 17 00:00:00 2001 From: Pursottam6003 Date: Wed, 1 May 2024 00:15:34 +0530 Subject: [PATCH 24/29] updated the send_invite function --- backend/organizations/models.py | 395 +++++++++++--------------------- 1 file changed, 133 insertions(+), 262 deletions(-) diff --git a/backend/organizations/models.py b/backend/organizations/models.py index b90d1220d..34a51e7a2 100644 --- a/backend/organizations/models.py +++ b/backend/organizations/models.py @@ -122,6 +122,137 @@ def __str__(self): ) @classmethod + def send_invite_email(cls, invite, user): + current_environment = os.getenv("ENV") + base_url = ( + "dev.shoonya.ai4bharat.org" + if current_environment == "dev" + else "shoonya.ai4bharat.org" + ) + subject = "Invitation to join Organization" + invite_link = f"https://{base_url}/#/invite/{invite.invite_code}" + text_content = f"Hello! You are invited to Shoonya. Your Invite link is: " + style_string =""" + *{ margin: 0; + padding: 0; + } + body { + font-family: "Arial", sans-serif; + background-color: #f2f8f8; + margin: 0; + padding: 0; + padding-top: 2rem; + } + .container { + background-color: #fff; + border: solid 1px #e1e1e1; + border-radius: 2px; + padding: 1.4rem; + max-width: 380px; + margin: auto; + } + .header { + width: fit-content; + margin: auto; + } + h1 { + font-size: 1.2rem; + font-weight: 300; + margin: 1rem 0; + font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; + } + p { + font-size: 0.9rem; + color: #222; + margin: 0.8rem 0; + } + .primary { + color: #18621f; + } + .footer { + margin-top: 1rem; + font-size: 0.9rem; + } + .footer > * { + font-size: inherit; + }""" + + html_content = f""" + + + + + + Invitation to join Shoonya Organisation + + + +
+
+

Invitaiton to join Shoonya

+
+
+
+ + + + + + + +
+ + Join Shoonya Now + +
+
+
+

+ Please use the above link to verify your email address and complete your registration. +

+

+ For security purposes, please do not share the this link with + anyone. +

+

+ If clicking the link doesn't work, you can copy and paste the link into your browser's address window, or retype it there. + {invite_link} +

+
+
+
+

+ Best Regards,
+ Shoonya Team +

+
+
+ + + """ + msg = EmailMultiAlternatives( + subject, text_content, settings.DEFAULT_FROM_EMAIL, [user.email] + ) + msg.attach_alternative(html_content, "text/html") + msg.send() def create_invite(cls, organization=None, users=None): with transaction.atomic(): for user in users: @@ -131,134 +262,7 @@ def create_invite(cls, organization=None, users=None): invite = Invite.objects.create(organization=organization, user=user) invite.invite_code = cls.generate_invite_code() invite.save() - current_environment = os.getenv("ENV") - base_url = ( - "dev.shoonya.ai4bharat.org" - if current_environment == "dev" - else "shoonya.ai4bharat.org" - ) - subject = "Invitation to join Organization" - invite_link = f"https://{base_url}/#/invite/{invite.invite_code}" - text_content = f"Hello! You are invited to {organization.title}. Your Invite link is: " - html_content = f""" - - - - - -Invitation to join Shoonya Organisation - - - -
-
-

Invitaiton to join Shoonya

-
-
-
- - - - - - - -
- -Join Shoonya Now - -
-
-
-

-Please use the above link to verify your email address and complete your registration. -

-

-For security purposes, please do not share the this link with -anyone. -

-

- If clicking the link doesn't work, you can copy and paste the link into your browser's address window, or retype it there. - {invite_link} -

-
-
-
-

-Best Regards,
-Shoonya Team -

-
-
- - -""" - msg = EmailMultiAlternatives( - subject, text_content, settings.DEFAULT_FROM_EMAIL, [user.email] - ) - msg.attach_alternative(html_content, "text/html") - msg.send() + cls.send_invite_email(invite, user) # def has_permission(self, user): # if self.organization.created_by.pk == user.pk or user.is_superuser: @@ -270,140 +274,7 @@ def re_invite(cls, users=None): with transaction.atomic(): for user in users: invite = Invite.objects.get(user=user) - current_environment = os.getenv("ENV") - base_url = ( - "dev.shoonya.ai4bharat.org" - if current_environment == "dev" - else "shoonya.ai4bharat.org" - ) - subject = "Invitation to join Organization" - invite_link = f"https://{base_url}/#/invite/{invite.invite_code}" - text_content = f"Hello! You are invited to {organization.title}. Your Invite link is: " - html_content = f""" - - - - - -Invitation to join Shoonya Organisation - - - -
-
-

Invitaiton to join Shoonya

-
-
-
- - - - - - - -
- -Join Shoonya Now - -
-
-
-

-Please use the above link to verify your email address and complete your registration. -

-

-For security purposes, please do not share the this link with -anyone. -

-

- If clicking the link doesn't work, you can copy and paste the link into your browser's address window, or retype it there. - {invite_link} -

-
-
-
-

-Best Regards,
-Shoonya Team -

-
-
- - -""" - msg = EmailMultiAlternatives( - subject, text_content, settings.DEFAULT_FROM_EMAIL, [user.email] - ) - msg.attach_alternative(html_content, "text/html") - msg.send() - # send_mail( - # "Invitation to join Organization", - # f"Hello! You are invited to {invite.organization.title}. Your Invite link is: https://shoonya.ai4bharat.org/#/invite/{invite.invite_code}", - # settings.DEFAULT_FROM_EMAIL, - # [user.email], - # ) + cls.send_invite_email(invite, user) @classmethod def generate_invite_code(cls): From 1197583ff282481ce6da0b6b19fda119dffbb51d Mon Sep 17 00:00:00 2001 From: Pursottam6003 Date: Wed, 1 May 2024 00:16:18 +0530 Subject: [PATCH 25/29] updated the black linting --- backend/organizations/models.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/organizations/models.py b/backend/organizations/models.py index 34a51e7a2..082046015 100644 --- a/backend/organizations/models.py +++ b/backend/organizations/models.py @@ -132,7 +132,7 @@ def send_invite_email(cls, invite, user): subject = "Invitation to join Organization" invite_link = f"https://{base_url}/#/invite/{invite.invite_code}" text_content = f"Hello! You are invited to Shoonya. Your Invite link is: " - style_string =""" + style_string = """ *{ margin: 0; padding: 0; } @@ -176,7 +176,7 @@ def send_invite_email(cls, invite, user): .footer > * { font-size: inherit; }""" - + html_content = f""" @@ -253,6 +253,7 @@ def send_invite_email(cls, invite, user): ) msg.attach_alternative(html_content, "text/html") msg.send() + def create_invite(cls, organization=None, users=None): with transaction.atomic(): for user in users: From e10f1927025fe24de0e8bd970801147e547f7e36 Mon Sep 17 00:00:00 2001 From: Pursottam6003 Date: Wed, 1 May 2024 14:26:12 +0530 Subject: [PATCH 26/29] ressolved error --- backend/organizations/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/organizations/models.py b/backend/organizations/models.py index 082046015..09fffff2a 100644 --- a/backend/organizations/models.py +++ b/backend/organizations/models.py @@ -253,8 +253,8 @@ def send_invite_email(cls, invite, user): ) msg.attach_alternative(html_content, "text/html") msg.send() - - def create_invite(cls, organization=None, users=None): + @classmethod + def create_invite(cls,organization=None, users=None): with transaction.atomic(): for user in users: try: From 65589bcf4c809219dd8ff680698e5f3aa882c191 Mon Sep 17 00:00:00 2001 From: Pursottam6003 Date: Wed, 1 May 2024 14:27:05 +0530 Subject: [PATCH 27/29] black linting --- backend/organizations/models.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/organizations/models.py b/backend/organizations/models.py index 09fffff2a..e8a37f439 100644 --- a/backend/organizations/models.py +++ b/backend/organizations/models.py @@ -253,8 +253,9 @@ def send_invite_email(cls, invite, user): ) msg.attach_alternative(html_content, "text/html") msg.send() - @classmethod - def create_invite(cls,organization=None, users=None): + + @classmethod + def create_invite(cls, organization=None, users=None): with transaction.atomic(): for user in users: try: From 308c96ad9bb0ec065fe26cdcd338dc142b55e8db Mon Sep 17 00:00:00 2001 From: Kunal Tiwary Date: Mon, 6 May 2024 13:32:28 +0530 Subject: [PATCH 28/29] added fix for to_be_revised --- backend/tasks/views.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/backend/tasks/views.py b/backend/tasks/views.py index 43517ac06..50de59c54 100644 --- a/backend/tasks/views.py +++ b/backend/tasks/views.py @@ -1740,15 +1740,6 @@ def partial_update(self, request, pk=None): if annotation_obj.annotation_status == TO_BE_REVISED: update_notification(annotation_obj, task) is_revised = True - print(annotation_obj) - if "ids" in dict(request.data): - pass - - else: - return Response( - {"message": "key doesnot match"}, - status=status.HTTP_400_BAD_REQUEST, - ) elif annotation_obj.annotation_type == SUPER_CHECKER_ANNOTATION: is_rejected = False From 40a96bebda9660b3e2a394e182a35160dca1d959 Mon Sep 17 00:00:00 2001 From: Ishan Gujarathi Date: Wed, 8 May 2024 16:14:06 +0530 Subject: [PATCH 29/29] added a feature to remove user from frozen users list when user is marked active again being marked inactive --- backend/users/views.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/backend/users/views.py b/backend/users/views.py index 8b864a487..cbaa32455 100644 --- a/backend/users/views.py +++ b/backend/users/views.py @@ -902,6 +902,19 @@ def user_details_update(self, request, pk=None): }, status=status.HTTP_200_OK, ) + else: + if is_active_payload is True: + workspaces = Workspace.objects.filter( + Q(members=user) | Q(managers=user) + ).distinct() + + workspaceusersviewset_obj = WorkspaceusersViewSet() + request.data["user_id"] = user.id + + for workspace in workspaces: + workspaceusersviewset_obj.remove_frozen_user( + request=request, pk=workspace.pk + ) if request.data["role"] != user.role: new_role = int(request.data["role"])