forked from github/plane
chore: user deactivation and login restriction (#2855)
* chore: user deactivation * chore: deactivation and login disabled * chore: added get configuration value * chore: serializer message change * chore: instance admin passowrd change * chore: removed triage * chore: v3 endpoint for user profile * chore: added enable signin --------- Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
This commit is contained in:
parent
34e6ef0d8d
commit
0669dab1c4
@ -159,10 +159,10 @@ class ChangePasswordSerializer(serializers.Serializer):
|
|||||||
|
|
||||||
def validate(self, data):
|
def validate(self, data):
|
||||||
if data.get("old_password") == data.get("new_password"):
|
if data.get("old_password") == data.get("new_password"):
|
||||||
raise serializers.ValidationError("New password cannot be same as old password.")
|
raise serializers.ValidationError({"error": "New password cannot be same as old password."})
|
||||||
|
|
||||||
if data.get("new_password") != data.get("confirm_password"):
|
if data.get("new_password") != data.get("confirm_password"):
|
||||||
raise serializers.ValidationError("confirm password should be same as the new password.")
|
raise serializers.ValidationError({"error": "Confirm password should be same as the new password."})
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ from plane.app.views import (
|
|||||||
WorkspaceUserProfileEndpoint,
|
WorkspaceUserProfileEndpoint,
|
||||||
WorkspaceUserProfileIssuesEndpoint,
|
WorkspaceUserProfileIssuesEndpoint,
|
||||||
WorkspaceLabelsEndpoint,
|
WorkspaceLabelsEndpoint,
|
||||||
|
WorkspaceUserProfileIssuesGroupedEndpoint
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -189,6 +190,11 @@ urlpatterns = [
|
|||||||
WorkspaceUserProfileIssuesEndpoint.as_view(),
|
WorkspaceUserProfileIssuesEndpoint.as_view(),
|
||||||
name="workspace-user-profile-issues",
|
name="workspace-user-profile-issues",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"v3/workspaces/<str:slug>/user-issues/<uuid:user_id>/",
|
||||||
|
WorkspaceUserProfileIssuesGroupedEndpoint.as_view(),
|
||||||
|
name="workspace-user-profile-issues",
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"workspaces/<str:slug>/labels/",
|
"workspaces/<str:slug>/labels/",
|
||||||
WorkspaceLabelsEndpoint.as_view(),
|
WorkspaceLabelsEndpoint.as_view(),
|
||||||
|
@ -44,6 +44,7 @@ from .workspace import (
|
|||||||
WorkspaceUserProfileEndpoint,
|
WorkspaceUserProfileEndpoint,
|
||||||
WorkspaceUserProfileIssuesEndpoint,
|
WorkspaceUserProfileIssuesEndpoint,
|
||||||
WorkspaceLabelsEndpoint,
|
WorkspaceLabelsEndpoint,
|
||||||
|
WorkspaceUserProfileIssuesGroupedEndpoint
|
||||||
)
|
)
|
||||||
from .state import StateViewSet
|
from .state import StateViewSet
|
||||||
from .view import (
|
from .view import (
|
||||||
|
@ -133,7 +133,7 @@ class ChangePasswordEndpoint(BaseAPIView):
|
|||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
if not user.check_password(serializer.data.get("old_password")):
|
if not user.check_password(serializer.data.get("old_password")):
|
||||||
return Response(
|
return Response(
|
||||||
{"old_password": ["Wrong password."]},
|
{"error": "Old password is not correct"},
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
# set_password also hashes the password that the user will get
|
# set_password also hashes the password that the user will get
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
# Python imports
|
# Python imports
|
||||||
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
@ -32,6 +33,8 @@ from plane.db.models import (
|
|||||||
)
|
)
|
||||||
from plane.settings.redis import redis_instance
|
from plane.settings.redis import redis_instance
|
||||||
from plane.bgtasks.magic_link_code_task import magic_link
|
from plane.bgtasks.magic_link_code_task import magic_link
|
||||||
|
from plane.license.models import InstanceConfiguration
|
||||||
|
from plane.license.utils.instance_value import get_configuration_value
|
||||||
|
|
||||||
|
|
||||||
def get_tokens_for_user(user):
|
def get_tokens_for_user(user):
|
||||||
@ -46,7 +49,17 @@ class SignUpEndpoint(BaseAPIView):
|
|||||||
permission_classes = (AllowAny,)
|
permission_classes = (AllowAny,)
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
if not settings.ENABLE_SIGNUP:
|
instance_configuration = InstanceConfiguration.objects.values("key", "value")
|
||||||
|
if (
|
||||||
|
not get_configuration_value(
|
||||||
|
instance_configuration,
|
||||||
|
"ENABLE_SIGNUP",
|
||||||
|
os.environ.get("ENABLE_SIGNUP", "0"),
|
||||||
|
)
|
||||||
|
and not WorkspaceMemberInvite.objects.filter(
|
||||||
|
email=request.user.email
|
||||||
|
).exists()
|
||||||
|
):
|
||||||
return Response(
|
return Response(
|
||||||
{
|
{
|
||||||
"error": "New account creation is disabled. Please contact your site administrator"
|
"error": "New account creation is disabled. Please contact your site administrator"
|
||||||
@ -140,7 +153,8 @@ class SignUpEndpoint(BaseAPIView):
|
|||||||
else 15,
|
else 15,
|
||||||
member=user,
|
member=user,
|
||||||
created_by_id=project_member_invite.created_by_id,
|
created_by_id=project_member_invite.created_by_id,
|
||||||
) for project_member_invite in project_member_invites
|
)
|
||||||
|
for project_member_invite in project_member_invites
|
||||||
],
|
],
|
||||||
ignore_conflicts=True,
|
ignore_conflicts=True,
|
||||||
)
|
)
|
||||||
@ -224,15 +238,9 @@ class SignInEndpoint(BaseAPIView):
|
|||||||
},
|
},
|
||||||
status=status.HTTP_403_FORBIDDEN,
|
status=status.HTTP_403_FORBIDDEN,
|
||||||
)
|
)
|
||||||
if not user.is_active:
|
|
||||||
return Response(
|
|
||||||
{
|
|
||||||
"error": "Your account has been deactivated. Please contact your site administrator."
|
|
||||||
},
|
|
||||||
status=status.HTTP_403_FORBIDDEN,
|
|
||||||
)
|
|
||||||
|
|
||||||
# settings last active for the user
|
# settings last active for the user
|
||||||
|
user.is_active = True
|
||||||
user.last_active = timezone.now()
|
user.last_active = timezone.now()
|
||||||
user.last_login_time = timezone.now()
|
user.last_login_time = timezone.now()
|
||||||
user.last_login_ip = request.META.get("REMOTE_ADDR")
|
user.last_login_ip = request.META.get("REMOTE_ADDR")
|
||||||
@ -288,7 +296,8 @@ class SignInEndpoint(BaseAPIView):
|
|||||||
else 15,
|
else 15,
|
||||||
member=user,
|
member=user,
|
||||||
created_by_id=project_member_invite.created_by_id,
|
created_by_id=project_member_invite.created_by_id,
|
||||||
) for project_member_invite in project_member_invites
|
)
|
||||||
|
for project_member_invite in project_member_invites
|
||||||
],
|
],
|
||||||
ignore_conflicts=True,
|
ignore_conflicts=True,
|
||||||
)
|
)
|
||||||
@ -360,6 +369,31 @@ class MagicSignInGenerateEndpoint(BaseAPIView):
|
|||||||
def post(self, request):
|
def post(self, request):
|
||||||
email = request.data.get("email", False)
|
email = request.data.get("email", False)
|
||||||
|
|
||||||
|
instance_configuration = InstanceConfiguration.objects.values("key", "value")
|
||||||
|
if (
|
||||||
|
not get_configuration_value(
|
||||||
|
instance_configuration,
|
||||||
|
"ENABLE_MAGIC_LINK_LOGIN",
|
||||||
|
os.environ.get("ENABLE_MAGIC_LINK_LOGIN"),
|
||||||
|
)
|
||||||
|
and not (
|
||||||
|
get_configuration_value(
|
||||||
|
instance_configuration,
|
||||||
|
"ENABLE_SIGNUP",
|
||||||
|
os.environ.get("ENABLE_SIGNUP", "0"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
and not WorkspaceMemberInvite.objects.filter(
|
||||||
|
email=request.user.email
|
||||||
|
).exists()
|
||||||
|
):
|
||||||
|
return Response(
|
||||||
|
{
|
||||||
|
"error": "New account creation is disabled. Please contact your site administrator"
|
||||||
|
},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
|
)
|
||||||
|
|
||||||
if not email:
|
if not email:
|
||||||
return Response(
|
return Response(
|
||||||
{"error": "Please provide a valid email address"},
|
{"error": "Please provide a valid email address"},
|
||||||
@ -410,8 +444,7 @@ class MagicSignInGenerateEndpoint(BaseAPIView):
|
|||||||
|
|
||||||
ri.set(key, json.dumps(value), ex=expiry)
|
ri.set(key, json.dumps(value), ex=expiry)
|
||||||
|
|
||||||
|
current_site = request.META.get("HTTP_ORIGIN")
|
||||||
current_site = request.META.get('HTTP_ORIGIN')
|
|
||||||
magic_link.delay(email, key, token, current_site)
|
magic_link.delay(email, key, token, current_site)
|
||||||
|
|
||||||
return Response({"key": key}, status=status.HTTP_200_OK)
|
return Response({"key": key}, status=status.HTTP_200_OK)
|
||||||
@ -443,13 +476,6 @@ class MagicSignInEndpoint(BaseAPIView):
|
|||||||
if str(token) == str(user_token):
|
if str(token) == str(user_token):
|
||||||
if User.objects.filter(email=email).exists():
|
if User.objects.filter(email=email).exists():
|
||||||
user = User.objects.get(email=email)
|
user = User.objects.get(email=email)
|
||||||
if not user.is_active:
|
|
||||||
return Response(
|
|
||||||
{
|
|
||||||
"error": "Your account has been deactivated. Please contact your site administrator."
|
|
||||||
},
|
|
||||||
status=status.HTTP_403_FORBIDDEN,
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
# Send event to Jitsu for tracking
|
# Send event to Jitsu for tracking
|
||||||
if settings.ANALYTICS_BASE_API:
|
if settings.ANALYTICS_BASE_API:
|
||||||
@ -467,7 +493,9 @@ class MagicSignInEndpoint(BaseAPIView):
|
|||||||
"user": {"email": email, "id": str(user.id)},
|
"user": {"email": email, "id": str(user.id)},
|
||||||
"device_ctx": {
|
"device_ctx": {
|
||||||
"ip": request.META.get("REMOTE_ADDR"),
|
"ip": request.META.get("REMOTE_ADDR"),
|
||||||
"user_agent": request.META.get("HTTP_USER_AGENT"),
|
"user_agent": request.META.get(
|
||||||
|
"HTTP_USER_AGENT"
|
||||||
|
),
|
||||||
},
|
},
|
||||||
"event_type": "SIGN_IN",
|
"event_type": "SIGN_IN",
|
||||||
},
|
},
|
||||||
@ -498,7 +526,9 @@ class MagicSignInEndpoint(BaseAPIView):
|
|||||||
"user": {"email": email, "id": str(user.id)},
|
"user": {"email": email, "id": str(user.id)},
|
||||||
"device_ctx": {
|
"device_ctx": {
|
||||||
"ip": request.META.get("REMOTE_ADDR"),
|
"ip": request.META.get("REMOTE_ADDR"),
|
||||||
"user_agent": request.META.get("HTTP_USER_AGENT"),
|
"user_agent": request.META.get(
|
||||||
|
"HTTP_USER_AGENT"
|
||||||
|
),
|
||||||
},
|
},
|
||||||
"event_type": "SIGN_UP",
|
"event_type": "SIGN_UP",
|
||||||
},
|
},
|
||||||
@ -506,6 +536,7 @@ class MagicSignInEndpoint(BaseAPIView):
|
|||||||
except RequestException as e:
|
except RequestException as e:
|
||||||
capture_exception(e)
|
capture_exception(e)
|
||||||
|
|
||||||
|
user.is_active = True
|
||||||
user.last_active = timezone.now()
|
user.last_active = timezone.now()
|
||||||
user.last_login_time = timezone.now()
|
user.last_login_time = timezone.now()
|
||||||
user.last_login_ip = request.META.get("REMOTE_ADDR")
|
user.last_login_ip = request.META.get("REMOTE_ADDR")
|
||||||
@ -561,7 +592,8 @@ class MagicSignInEndpoint(BaseAPIView):
|
|||||||
else 15,
|
else 15,
|
||||||
member=user,
|
member=user,
|
||||||
created_by_id=project_member_invite.created_by_id,
|
created_by_id=project_member_invite.created_by_id,
|
||||||
) for project_member_invite in project_member_invites
|
)
|
||||||
|
for project_member_invite in project_member_invites
|
||||||
],
|
],
|
||||||
ignore_conflicts=True,
|
ignore_conflicts=True,
|
||||||
)
|
)
|
||||||
|
@ -45,23 +45,23 @@ class ConfigurationEndpoint(BaseAPIView):
|
|||||||
get_configuration_value(
|
get_configuration_value(
|
||||||
instance_configuration,
|
instance_configuration,
|
||||||
"EMAIL_HOST_USER",
|
"EMAIL_HOST_USER",
|
||||||
os.environ.get("GITHUB_APP_NAME", None),
|
os.environ.get("EMAIL_HOST_USER", None),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
and bool(
|
and bool(
|
||||||
get_configuration_value(
|
get_configuration_value(
|
||||||
instance_configuration,
|
instance_configuration,
|
||||||
"EMAIL_HOST_PASSWORD",
|
"EMAIL_HOST_PASSWORD",
|
||||||
os.environ.get("GITHUB_APP_NAME", None),
|
os.environ.get("EMAIL_HOST_PASSWORD", None),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
) and get_configuration_value(
|
) and get_configuration_value(
|
||||||
instance_configuration, "ENABLE_MAGIC_LINK_LOGIN", "0"
|
instance_configuration, "ENABLE_MAGIC_LINK_LOGIN", "1"
|
||||||
) == "1"
|
) == "1"
|
||||||
|
|
||||||
data["email_password_login"] = (
|
data["email_password_login"] = (
|
||||||
get_configuration_value(
|
get_configuration_value(
|
||||||
instance_configuration, "ENABLE_EMAIL_PASSWORD", "0"
|
instance_configuration, "ENABLE_EMAIL_PASSWORD", "1"
|
||||||
)
|
)
|
||||||
== "1"
|
== "1"
|
||||||
)
|
)
|
||||||
|
@ -30,6 +30,8 @@ from plane.db.models import (
|
|||||||
ProjectMember,
|
ProjectMember,
|
||||||
)
|
)
|
||||||
from .base import BaseAPIView
|
from .base import BaseAPIView
|
||||||
|
from plane.license.models import InstanceConfiguration
|
||||||
|
from plane.license.utils.instance_value import get_configuration_value
|
||||||
|
|
||||||
|
|
||||||
def get_tokens_for_user(user):
|
def get_tokens_for_user(user):
|
||||||
@ -137,6 +139,40 @@ class OauthEndpoint(BaseAPIView):
|
|||||||
id_token = request.data.get("credential", False)
|
id_token = request.data.get("credential", False)
|
||||||
client_id = request.data.get("clientId", False)
|
client_id = request.data.get("clientId", False)
|
||||||
|
|
||||||
|
instance_configuration = InstanceConfiguration.objects.values(
|
||||||
|
"key", "value"
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
(
|
||||||
|
not get_configuration_value(
|
||||||
|
instance_configuration,
|
||||||
|
"GOOGLE_CLIENT_ID",
|
||||||
|
os.environ.get("GOOGLE_CLIENT_ID"),
|
||||||
|
)
|
||||||
|
or not get_configuration_value(
|
||||||
|
instance_configuration,
|
||||||
|
"GITHUB_CLIENT_ID",
|
||||||
|
os.environ.get("GITHUB_CLIENT_ID"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
and not (
|
||||||
|
get_configuration_value(
|
||||||
|
instance_configuration,
|
||||||
|
"ENABLE_SIGNUP",
|
||||||
|
os.environ.get("ENABLE_SIGNUP", "0"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
and not WorkspaceMemberInvite.objects.filter(
|
||||||
|
email=request.user.email
|
||||||
|
).exists()
|
||||||
|
):
|
||||||
|
return Response(
|
||||||
|
{
|
||||||
|
"error": "New account creation is disabled. Please contact your site administrator"
|
||||||
|
},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
|
)
|
||||||
|
|
||||||
if not medium or not id_token:
|
if not medium or not id_token:
|
||||||
return Response(
|
return Response(
|
||||||
{
|
{
|
||||||
@ -174,15 +210,7 @@ class OauthEndpoint(BaseAPIView):
|
|||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
|
|
||||||
## Login Case
|
user.is_active = True
|
||||||
if not user.is_active:
|
|
||||||
return Response(
|
|
||||||
{
|
|
||||||
"error": "Your account has been deactivated. Please contact your site administrator."
|
|
||||||
},
|
|
||||||
status=status.HTTP_403_FORBIDDEN,
|
|
||||||
)
|
|
||||||
|
|
||||||
user.last_active = timezone.now()
|
user.last_active = timezone.now()
|
||||||
user.last_login_time = timezone.now()
|
user.last_login_time = timezone.now()
|
||||||
user.last_login_ip = request.META.get("REMOTE_ADDR")
|
user.last_login_ip = request.META.get("REMOTE_ADDR")
|
||||||
@ -239,7 +267,8 @@ class OauthEndpoint(BaseAPIView):
|
|||||||
else 15,
|
else 15,
|
||||||
member=user,
|
member=user,
|
||||||
created_by_id=project_member_invite.created_by_id,
|
created_by_id=project_member_invite.created_by_id,
|
||||||
) for project_member_invite in project_member_invites
|
)
|
||||||
|
for project_member_invite in project_member_invites
|
||||||
],
|
],
|
||||||
ignore_conflicts=True,
|
ignore_conflicts=True,
|
||||||
)
|
)
|
||||||
@ -291,6 +320,23 @@ class OauthEndpoint(BaseAPIView):
|
|||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
## Signup Case
|
## Signup Case
|
||||||
|
|
||||||
|
if (
|
||||||
|
get_configuration_value(
|
||||||
|
instance_configuration,
|
||||||
|
"ENABLE_SIGNUP",
|
||||||
|
os.environ.get("ENABLE_SIGNUP", "0"),
|
||||||
|
)
|
||||||
|
and not WorkspaceMemberInvite.objects.filter(
|
||||||
|
email=request.user.email
|
||||||
|
).exists()
|
||||||
|
):
|
||||||
|
return Response(
|
||||||
|
{
|
||||||
|
"error": "New account creation is disabled. Please contact your site administrator"
|
||||||
|
},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
|
)
|
||||||
|
|
||||||
username = uuid.uuid4().hex
|
username = uuid.uuid4().hex
|
||||||
|
|
||||||
if "@" in email:
|
if "@" in email:
|
||||||
@ -373,7 +419,8 @@ class OauthEndpoint(BaseAPIView):
|
|||||||
else 15,
|
else 15,
|
||||||
member=user,
|
member=user,
|
||||||
created_by_id=project_member_invite.created_by_id,
|
created_by_id=project_member_invite.created_by_id,
|
||||||
) for project_member_invite in project_member_invites
|
)
|
||||||
|
for project_member_invite in project_member_invites
|
||||||
],
|
],
|
||||||
ignore_conflicts=True,
|
ignore_conflicts=True,
|
||||||
)
|
)
|
||||||
|
@ -12,11 +12,14 @@ from plane.app.serializers import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from plane.app.views.base import BaseViewSet, BaseAPIView
|
from plane.app.views.base import BaseViewSet, BaseAPIView
|
||||||
from plane.db.models import User, IssueActivity, WorkspaceMember
|
from plane.db.models import User, IssueActivity, WorkspaceMember, ProjectMember
|
||||||
from plane.license.models import Instance, InstanceAdmin
|
from plane.license.models import Instance, InstanceAdmin
|
||||||
from plane.utils.paginator import BasePaginator
|
from plane.utils.paginator import BasePaginator
|
||||||
|
|
||||||
|
|
||||||
|
from django.db.models import Q, F, Count, Case, When, Value, IntegerField
|
||||||
|
|
||||||
|
|
||||||
class UserEndpoint(BaseViewSet):
|
class UserEndpoint(BaseViewSet):
|
||||||
serializer_class = UserSerializer
|
serializer_class = UserSerializer
|
||||||
model = User
|
model = User
|
||||||
@ -45,14 +48,69 @@ class UserEndpoint(BaseViewSet):
|
|||||||
def deactivate(self, request):
|
def deactivate(self, request):
|
||||||
# Check all workspace user is active
|
# Check all workspace user is active
|
||||||
user = self.get_object()
|
user = self.get_object()
|
||||||
if WorkspaceMember.objects.filter(member=request.user, is_active=True).exists():
|
|
||||||
|
projects_to_deactivate = []
|
||||||
|
workspaces_to_deactivate = []
|
||||||
|
|
||||||
|
|
||||||
|
projects = ProjectMember.objects.filter(
|
||||||
|
member=request.user, is_active=True
|
||||||
|
).annotate(
|
||||||
|
other_admin_exists=Count(
|
||||||
|
Case(
|
||||||
|
When(Q(role=20, is_active=True) & ~Q(member=request.user), then=1),
|
||||||
|
default=0,
|
||||||
|
output_field=IntegerField(),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
total_members=Count("id"),
|
||||||
|
)
|
||||||
|
|
||||||
|
for project in projects:
|
||||||
|
if project.other_admin_exists > 0 or (project.total_members == 1):
|
||||||
|
project.is_active = False
|
||||||
|
projects_to_deactivate.append(project)
|
||||||
|
else:
|
||||||
return Response(
|
return Response(
|
||||||
{
|
{
|
||||||
"error": "You cannot deactivate account as you are a member in some workspaces."
|
"error": "You cannot deactivate account as you are the only admin in some projects."
|
||||||
},
|
},
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
workspaces = WorkspaceMember.objects.filter(
|
||||||
|
member=request.user, is_active=True
|
||||||
|
).annotate(
|
||||||
|
other_admin_exists=Count(
|
||||||
|
Case(
|
||||||
|
When(Q(role=20, is_active=True) & ~Q(member=request.user), then=1),
|
||||||
|
default=0,
|
||||||
|
output_field=IntegerField(),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
total_members=Count("id"),
|
||||||
|
)
|
||||||
|
|
||||||
|
for workspace in workspaces:
|
||||||
|
if workspace.other_admin_exists > 0 or (workspace.total_members == 1):
|
||||||
|
workspace.is_active = False
|
||||||
|
workspaces_to_deactivate.append(workspace)
|
||||||
|
else:
|
||||||
|
return Response(
|
||||||
|
{
|
||||||
|
"error": "You cannot deactivate account as you are the only admin in some workspaces."
|
||||||
|
},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
|
)
|
||||||
|
|
||||||
|
ProjectMember.objects.bulk_update(
|
||||||
|
projects_to_deactivate, ["is_active"], batch_size=100
|
||||||
|
)
|
||||||
|
|
||||||
|
WorkspaceMember.objects.bulk_update(
|
||||||
|
workspaces_to_deactivate, ["is_active"], batch_size=100
|
||||||
|
)
|
||||||
|
|
||||||
# Deactivate the user
|
# Deactivate the user
|
||||||
user.is_active = False
|
user.is_active = False
|
||||||
user.save()
|
user.save()
|
||||||
|
@ -1313,6 +1313,62 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView):
|
|||||||
return Response(issues, status=status.HTTP_200_OK)
|
return Response(issues, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class WorkspaceUserProfileIssuesGroupedEndpoint(BaseAPIView):
|
||||||
|
|
||||||
|
permission_classes = [
|
||||||
|
WorkspaceViewerPermission,
|
||||||
|
]
|
||||||
|
|
||||||
|
def get(self, request, slug, user_id):
|
||||||
|
filters = issue_filters(request.query_params, "GET")
|
||||||
|
fields = [field for field in request.GET.get("fields", "").split(",") if field]
|
||||||
|
|
||||||
|
issue_queryset = (
|
||||||
|
Issue.issue_objects.filter(
|
||||||
|
Q(assignees__in=[user_id])
|
||||||
|
| Q(created_by_id=user_id)
|
||||||
|
| Q(issue_subscribers__subscriber_id=user_id),
|
||||||
|
workspace__slug=slug,
|
||||||
|
project__project_projectmember__member=request.user,
|
||||||
|
)
|
||||||
|
.filter(**filters)
|
||||||
|
.annotate(
|
||||||
|
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id"))
|
||||||
|
.order_by()
|
||||||
|
.annotate(count=Func(F("id"), function="Count"))
|
||||||
|
.values("count")
|
||||||
|
)
|
||||||
|
.select_related("project", "workspace", "state", "parent")
|
||||||
|
.prefetch_related("assignees", "labels")
|
||||||
|
.prefetch_related(
|
||||||
|
Prefetch(
|
||||||
|
"issue_reactions",
|
||||||
|
queryset=IssueReaction.objects.select_related("actor"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.order_by("-created_at")
|
||||||
|
.annotate(
|
||||||
|
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
|
||||||
|
.order_by()
|
||||||
|
.annotate(count=Func(F("id"), function="Count"))
|
||||||
|
.values("count")
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
attachment_count=IssueAttachment.objects.filter(issue=OuterRef("id"))
|
||||||
|
.order_by()
|
||||||
|
.annotate(count=Func(F("id"), function="Count"))
|
||||||
|
.values("count")
|
||||||
|
)
|
||||||
|
).distinct()
|
||||||
|
|
||||||
|
issues = IssueLiteSerializer(issue_queryset, many=True, fields=fields if fields else None).data
|
||||||
|
issue_dict = {str(issue["id"]): issue for issue in issues}
|
||||||
|
return Response(
|
||||||
|
issue_dict,
|
||||||
|
status=status.HTTP_200_OK,
|
||||||
|
)
|
||||||
|
|
||||||
class WorkspaceLabelsEndpoint(BaseAPIView):
|
class WorkspaceLabelsEndpoint(BaseAPIView):
|
||||||
permission_classes = [
|
permission_classes = [
|
||||||
WorkspaceViewerPermission,
|
WorkspaceViewerPermission,
|
||||||
|
@ -45,7 +45,7 @@ class Command(BaseCommand):
|
|||||||
# If the user does not exist create the user and add him to the database
|
# If the user does not exist create the user and add him to the database
|
||||||
if user is None:
|
if user is None:
|
||||||
user = User.objects.create(email=admin_email, username=uuid.uuid4().hex)
|
user = User.objects.create(email=admin_email, username=uuid.uuid4().hex)
|
||||||
user.set_password(uuid.uuid4().hex)
|
user.set_password(admin_email)
|
||||||
user.save()
|
user.save()
|
||||||
|
|
||||||
license_engine_base_url = os.environ.get("LICENSE_ENGINE_BASE_URL")
|
license_engine_base_url = os.environ.get("LICENSE_ENGINE_BASE_URL")
|
||||||
@ -88,13 +88,11 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
self.stdout.write(
|
self.stdout.write(
|
||||||
self.style.SUCCESS(
|
self.style.SUCCESS(
|
||||||
f"Instance succesfully registered with owner: {instance.primary_owner.email}"
|
f"Instance successfully registered with owner: {instance.primary_owner.email}"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
raise CommandError("Instance could not be registered")
|
||||||
self.stdout.write(self.style.WARNING("Instance could not be registered"))
|
|
||||||
return
|
|
||||||
else:
|
else:
|
||||||
self.stdout.write(
|
self.stdout.write(
|
||||||
self.style.SUCCESS(
|
self.style.SUCCESS(
|
||||||
|
Loading…
Reference in New Issue
Block a user