2025-04-14 12:11:23 +00:00
|
|
|
|
# coding=utf-8
|
|
|
|
|
|
"""
|
|
|
|
|
|
@project: MaxKB
|
|
|
|
|
|
@Author:虎虎
|
|
|
|
|
|
@file: user.py
|
|
|
|
|
|
@date:2025/4/14 19:18
|
|
|
|
|
|
@desc:
|
|
|
|
|
|
"""
|
2025-06-07 11:03:18 +00:00
|
|
|
|
import datetime
|
|
|
|
|
|
import os
|
|
|
|
|
|
import random
|
2025-04-27 08:26:40 +00:00
|
|
|
|
import re
|
2025-05-30 03:07:24 +00:00
|
|
|
|
from collections import defaultdict
|
2025-06-10 11:50:33 +00:00
|
|
|
|
|
|
|
|
|
|
from django.core.cache import cache
|
2025-06-07 11:03:18 +00:00
|
|
|
|
from django.core.mail.backends.smtp import EmailBackend
|
2025-04-27 08:26:40 +00:00
|
|
|
|
from django.db import transaction
|
2025-04-28 09:36:56 +00:00
|
|
|
|
from django.db.models import Q, QuerySet
|
2025-04-27 08:26:40 +00:00
|
|
|
|
from rest_framework import serializers
|
|
|
|
|
|
import uuid_utils.compat as uuid
|
2025-06-10 11:50:33 +00:00
|
|
|
|
|
|
|
|
|
|
from common.constants.cache_version import Cache_Version
|
2025-04-27 08:26:40 +00:00
|
|
|
|
from common.constants.exception_code_constants import ExceptionCodeConstants
|
2025-04-27 10:27:35 +00:00
|
|
|
|
from common.constants.permission_constants import RoleConstants, Auth
|
2025-05-08 02:51:01 +00:00
|
|
|
|
from common.database_model_manage.database_model_manage import DatabaseModelManage
|
2025-04-28 09:36:56 +00:00
|
|
|
|
from common.db.search import page_search
|
|
|
|
|
|
from common.exception.app_exception import AppApiException
|
2025-04-27 08:26:40 +00:00
|
|
|
|
from common.utils.common import valid_license, password_encrypt
|
2025-06-07 11:03:18 +00:00
|
|
|
|
from maxkb.conf import PROJECT_DIR
|
|
|
|
|
|
from system_manage.models import SystemSetting, SettingType
|
2025-04-14 12:11:23 +00:00
|
|
|
|
from users.models import User
|
2025-06-07 11:03:18 +00:00
|
|
|
|
from django.utils.translation import gettext_lazy as _, to_locale
|
2025-04-27 08:26:40 +00:00
|
|
|
|
from django.core import validators
|
2025-06-07 11:03:18 +00:00
|
|
|
|
from django.core.mail import send_mail
|
|
|
|
|
|
from django.utils.translation import get_language
|
2025-04-14 12:11:23 +00:00
|
|
|
|
|
2025-04-28 09:36:56 +00:00
|
|
|
|
PASSWORD_REGEX = re.compile(
|
2025-07-14 02:59:32 +00:00
|
|
|
|
r"^" # 开始
|
|
|
|
|
|
r"(?=.*[a-z])" # 至少一个小写字母
|
|
|
|
|
|
r"(?=.*[-_!@#$%^&*`~.()+=])" # 至少一个指定的特殊字符
|
|
|
|
|
|
r"(?:(?=.*[A-Z])|(?=.*\d))" # 至少一个大写字母 或 数字
|
|
|
|
|
|
r"[a-zA-Z0-9-_!@#$%^&*`~.()+=]{6,20}" # 总长度6~20个合法字符
|
|
|
|
|
|
r"$" # 结束
|
2025-04-28 09:36:56 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
2025-06-10 11:50:33 +00:00
|
|
|
|
version, get_key = Cache_Version.SYSTEM.value
|
|
|
|
|
|
|
2025-04-14 12:11:23 +00:00
|
|
|
|
|
|
|
|
|
|
class UserProfileResponse(serializers.ModelSerializer):
|
2025-04-27 08:26:40 +00:00
|
|
|
|
is_edit_password = serializers.BooleanField(required=True, label=_('Is Edit Password'))
|
|
|
|
|
|
permissions = serializers.ListField(required=True, label=_('permissions'))
|
2025-04-14 12:11:23 +00:00
|
|
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
|
model = User
|
2025-04-27 08:26:40 +00:00
|
|
|
|
fields = ['id', 'username', 'nick_name', 'email', 'role', 'permissions', 'language', 'is_edit_password']
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-04-27 09:59:47 +00:00
|
|
|
|
class CreateUserSerializer(serializers.Serializer):
|
2025-04-27 08:26:40 +00:00
|
|
|
|
username = serializers.CharField(required=True, label=_('Username'))
|
|
|
|
|
|
password = serializers.CharField(required=True, label=_('Password'))
|
|
|
|
|
|
email = serializers.EmailField(required=True, label=_('Email'))
|
|
|
|
|
|
nick_name = serializers.CharField(required=False, label=_('Nick name'))
|
|
|
|
|
|
phone = serializers.CharField(required=False, label=_('Phone'))
|
2025-09-12 07:34:13 +00:00
|
|
|
|
source = serializers.CharField(required=False, label=_('Source'), default='LOCAL')
|
2025-04-14 12:11:23 +00:00
|
|
|
|
|
|
|
|
|
|
|
2025-06-11 04:21:13 +00:00
|
|
|
|
def is_workspace_manage(user_id: str, workspace_id: str):
|
|
|
|
|
|
workspace_user_role_mapping_model = DatabaseModelManage.get_model("workspace_user_role_mapping")
|
|
|
|
|
|
role_permission_mapping_model = DatabaseModelManage.get_model("role_permission_mapping_model")
|
|
|
|
|
|
is_x_pack_ee = workspace_user_role_mapping_model is not None and role_permission_mapping_model is not None
|
|
|
|
|
|
if is_x_pack_ee:
|
|
|
|
|
|
return QuerySet(workspace_user_role_mapping_model).select_related('role', 'user').filter(
|
|
|
|
|
|
workspace_id=workspace_id, user_id=user_id,
|
2025-06-20 09:15:55 +00:00
|
|
|
|
role__type=RoleConstants.WORKSPACE_MANAGE.value.__str__()).exists()
|
2025-06-11 04:21:13 +00:00
|
|
|
|
return QuerySet(User).filter(id=user_id, role=RoleConstants.ADMIN.value.__str__()).exists()
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-06-19 13:43:57 +00:00
|
|
|
|
def get_workspace_list_by_user(user_id):
|
|
|
|
|
|
get_workspace_list = DatabaseModelManage.get_model('get_workspace_list_by_user')
|
|
|
|
|
|
license_is_valid = DatabaseModelManage.get_model('license_is_valid') or (lambda: False)
|
|
|
|
|
|
if get_workspace_list is not None and license_is_valid():
|
|
|
|
|
|
return get_workspace_list(user_id)
|
|
|
|
|
|
return [{'id': 'default', 'name': 'default'}]
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-04-14 12:11:23 +00:00
|
|
|
|
class UserProfileSerializer(serializers.Serializer):
|
|
|
|
|
|
@staticmethod
|
2025-04-27 10:27:35 +00:00
|
|
|
|
def profile(user: User, auth: Auth):
|
2025-04-14 12:11:23 +00:00
|
|
|
|
"""
|
2025-04-27 10:27:35 +00:00
|
|
|
|
获取用户详情
|
|
|
|
|
|
@param user: 用户对象
|
|
|
|
|
|
@param auth: 认证对象
|
|
|
|
|
|
@return:
|
2025-04-14 12:11:23 +00:00
|
|
|
|
"""
|
2025-06-19 13:43:57 +00:00
|
|
|
|
workspace_list = get_workspace_list_by_user(user.id)
|
2025-07-01 11:53:32 +00:00
|
|
|
|
user_role_relation_model = DatabaseModelManage.get_model("workspace_user_role_mapping")
|
2025-07-03 02:39:40 +00:00
|
|
|
|
role_name = [user.role]
|
2025-07-01 11:53:32 +00:00
|
|
|
|
if user_role_relation_model:
|
|
|
|
|
|
user_role_relations = (
|
|
|
|
|
|
user_role_relation_model.objects
|
|
|
|
|
|
.filter(user_id=user.id)
|
|
|
|
|
|
.select_related('role')
|
|
|
|
|
|
.distinct('role_id')
|
|
|
|
|
|
)
|
|
|
|
|
|
role_name = [relation.role.role_name for relation in user_role_relations]
|
|
|
|
|
|
|
2025-04-28 09:36:56 +00:00
|
|
|
|
return {
|
|
|
|
|
|
'id': user.id,
|
|
|
|
|
|
'username': user.username,
|
|
|
|
|
|
'nick_name': user.nick_name,
|
|
|
|
|
|
'email': user.email,
|
2025-09-28 06:45:34 +00:00
|
|
|
|
'source': user.source,
|
2025-04-28 09:36:56 +00:00
|
|
|
|
'role': auth.role_list,
|
|
|
|
|
|
'permissions': auth.permission_list,
|
2025-08-27 09:59:57 +00:00
|
|
|
|
'is_edit_password': user.password == 'd880e722c47a34d8e9fce789fc62389d' if user.source == 'LOCAL' else False,
|
2025-04-28 09:36:56 +00:00
|
|
|
|
'language': user.language,
|
2025-07-01 11:53:32 +00:00
|
|
|
|
'workspace_list': workspace_list,
|
|
|
|
|
|
'role_name': role_name
|
2025-04-28 09:36:56 +00:00
|
|
|
|
}
|
2025-04-27 10:27:35 +00:00
|
|
|
|
|
2025-04-28 09:36:56 +00:00
|
|
|
|
|
|
|
|
|
|
class UserInstanceSerializer(serializers.ModelSerializer):
|
|
|
|
|
|
class Meta:
|
|
|
|
|
|
model = User
|
|
|
|
|
|
fields = ['id', 'username', 'email', 'phone', 'is_active', 'role', 'nick_name', 'create_time', 'update_time',
|
|
|
|
|
|
'source']
|
2025-04-27 08:26:40 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class UserManageSerializer(serializers.Serializer):
|
|
|
|
|
|
class UserInstance(serializers.Serializer):
|
|
|
|
|
|
email = serializers.EmailField(
|
|
|
|
|
|
required=True,
|
|
|
|
|
|
label=_("Email"),
|
2025-04-28 09:36:56 +00:00
|
|
|
|
validators=[validators.EmailValidator(
|
|
|
|
|
|
message=ExceptionCodeConstants.EMAIL_FORMAT_ERROR.value.message,
|
|
|
|
|
|
code=ExceptionCodeConstants.EMAIL_FORMAT_ERROR.value.code
|
|
|
|
|
|
)]
|
|
|
|
|
|
)
|
|
|
|
|
|
username = serializers.CharField(
|
|
|
|
|
|
required=True,
|
|
|
|
|
|
label=_("Username"),
|
2025-11-12 08:52:51 +00:00
|
|
|
|
max_length=64,
|
2025-05-23 10:07:23 +00:00
|
|
|
|
min_length=4,
|
2025-04-28 09:36:56 +00:00
|
|
|
|
validators=[
|
|
|
|
|
|
validators.RegexValidator(
|
2025-11-12 08:52:51 +00:00
|
|
|
|
regex=re.compile("^.{4,64}$"),
|
|
|
|
|
|
message=_('Username must be 4-64 characters long')
|
2025-04-28 09:36:56 +00:00
|
|
|
|
)
|
|
|
|
|
|
]
|
|
|
|
|
|
)
|
|
|
|
|
|
password = serializers.CharField(
|
|
|
|
|
|
required=True,
|
|
|
|
|
|
label=_("Password"),
|
|
|
|
|
|
max_length=20,
|
|
|
|
|
|
min_length=6,
|
|
|
|
|
|
validators=[
|
|
|
|
|
|
validators.RegexValidator(
|
|
|
|
|
|
regex=PASSWORD_REGEX,
|
|
|
|
|
|
message=_(
|
|
|
|
|
|
"The password must be 6-20 characters long and must be a combination of letters, numbers, and special characters."
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
]
|
|
|
|
|
|
)
|
|
|
|
|
|
nick_name = serializers.CharField(
|
2025-05-16 10:23:27 +00:00
|
|
|
|
required=True,
|
2025-04-28 09:36:56 +00:00
|
|
|
|
label=_("Nick name"),
|
2025-11-12 08:52:51 +00:00
|
|
|
|
max_length=64,
|
2025-04-28 09:36:56 +00:00
|
|
|
|
)
|
|
|
|
|
|
phone = serializers.CharField(
|
|
|
|
|
|
required=False,
|
|
|
|
|
|
label=_("Phone"),
|
|
|
|
|
|
max_length=20,
|
|
|
|
|
|
allow_null=True,
|
|
|
|
|
|
allow_blank=True
|
|
|
|
|
|
)
|
2025-09-12 07:34:13 +00:00
|
|
|
|
source = serializers.CharField(
|
|
|
|
|
|
required=False,
|
|
|
|
|
|
label=_("Source"),
|
|
|
|
|
|
max_length=20,
|
|
|
|
|
|
default="LOCAL"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-04-27 08:26:40 +00:00
|
|
|
|
|
|
|
|
|
|
def is_valid(self, *, raise_exception=True):
|
|
|
|
|
|
super().is_valid(raise_exception=True)
|
2025-04-28 09:36:56 +00:00
|
|
|
|
self._check_unique_username_and_email()
|
|
|
|
|
|
|
|
|
|
|
|
def _check_unique_username_and_email(self):
|
2025-04-27 08:26:40 +00:00
|
|
|
|
username = self.data.get('username')
|
|
|
|
|
|
email = self.data.get('email')
|
2025-05-16 10:23:27 +00:00
|
|
|
|
nick_name = self.data.get('nick_name')
|
|
|
|
|
|
user = User.objects.filter(Q(username=username) | Q(email=email) | Q(nick_name=nick_name)).first()
|
2025-04-28 09:36:56 +00:00
|
|
|
|
if user:
|
|
|
|
|
|
if user.email == email:
|
2025-04-27 08:26:40 +00:00
|
|
|
|
raise ExceptionCodeConstants.EMAIL_IS_EXIST.value.to_app_api_exception()
|
2025-04-28 09:36:56 +00:00
|
|
|
|
if user.username == username:
|
2025-04-27 08:26:40 +00:00
|
|
|
|
raise ExceptionCodeConstants.USERNAME_IS_EXIST.value.to_app_api_exception()
|
2025-05-16 10:23:27 +00:00
|
|
|
|
if user.nick_name == nick_name:
|
|
|
|
|
|
raise ExceptionCodeConstants.NICKNAME_IS_EXIST.value.to_app_api_exception()
|
2025-04-27 08:26:40 +00:00
|
|
|
|
|
2025-04-28 09:36:56 +00:00
|
|
|
|
class Query(serializers.Serializer):
|
2025-06-25 10:38:35 +00:00
|
|
|
|
username = serializers.CharField(
|
|
|
|
|
|
required=False,
|
|
|
|
|
|
label=_("Username"),
|
2025-11-12 08:52:51 +00:00
|
|
|
|
max_length=64,
|
2025-06-25 10:38:35 +00:00
|
|
|
|
allow_blank=True
|
|
|
|
|
|
)
|
|
|
|
|
|
nick_name = serializers.CharField(
|
|
|
|
|
|
required=False,
|
|
|
|
|
|
label=_("Nick Name"),
|
2025-11-12 08:52:51 +00:00
|
|
|
|
max_length=64,
|
2025-06-25 10:38:35 +00:00
|
|
|
|
allow_blank=True
|
|
|
|
|
|
)
|
|
|
|
|
|
email = serializers.CharField(
|
|
|
|
|
|
required=False,
|
|
|
|
|
|
label=_("Email"),
|
|
|
|
|
|
allow_blank=True,
|
|
|
|
|
|
)
|
2025-07-29 06:52:45 +00:00
|
|
|
|
is_active = serializers.BooleanField(
|
|
|
|
|
|
required=False,
|
|
|
|
|
|
label=_("Is active"),
|
|
|
|
|
|
)
|
|
|
|
|
|
source = serializers.CharField(
|
|
|
|
|
|
required=False,
|
|
|
|
|
|
label=_("Source"),
|
|
|
|
|
|
allow_blank=True,
|
|
|
|
|
|
)
|
2025-04-28 09:36:56 +00:00
|
|
|
|
|
|
|
|
|
|
def get_query_set(self):
|
2025-06-25 10:38:35 +00:00
|
|
|
|
username = self.data.get('username')
|
|
|
|
|
|
nick_name = self.data.get('nick_name')
|
|
|
|
|
|
email = self.data.get('email')
|
2025-07-30 08:43:49 +00:00
|
|
|
|
is_active = self.data.get('is_active', None)
|
2025-07-29 06:52:45 +00:00
|
|
|
|
source = self.data.get('source', None)
|
2025-04-28 09:36:56 +00:00
|
|
|
|
query_set = QuerySet(User)
|
2025-06-25 10:38:35 +00:00
|
|
|
|
if username is not None:
|
|
|
|
|
|
query_set = query_set.filter(username__contains=username)
|
|
|
|
|
|
if nick_name is not None:
|
|
|
|
|
|
query_set = query_set.filter(nick_name__contains=nick_name)
|
|
|
|
|
|
if email is not None:
|
|
|
|
|
|
query_set = query_set.filter(email__contains=email)
|
2025-07-29 06:52:45 +00:00
|
|
|
|
if is_active is not None:
|
|
|
|
|
|
query_set = query_set.filter(is_active=is_active)
|
|
|
|
|
|
if source is not None:
|
|
|
|
|
|
query_set = query_set.filter(source=source)
|
2025-04-28 09:36:56 +00:00
|
|
|
|
query_set = query_set.order_by("-create_time")
|
|
|
|
|
|
return query_set
|
|
|
|
|
|
|
|
|
|
|
|
def list(self, with_valid=True):
|
|
|
|
|
|
if with_valid:
|
|
|
|
|
|
self.is_valid(raise_exception=True)
|
|
|
|
|
|
return [{'id': user_model.id, 'username': user_model.username, 'email': user_model.email} for user_model in
|
|
|
|
|
|
self.get_query_set()]
|
|
|
|
|
|
|
2025-07-14 02:49:47 +00:00
|
|
|
|
def page(self, current_page: int, page_size: int, user_id: str, with_valid=True):
|
2025-04-28 09:36:56 +00:00
|
|
|
|
if with_valid:
|
|
|
|
|
|
self.is_valid(raise_exception=True)
|
2025-05-30 03:07:24 +00:00
|
|
|
|
result = page_search(current_page, page_size,
|
|
|
|
|
|
self.get_query_set(),
|
|
|
|
|
|
post_records_handler=lambda u: UserInstanceSerializer(u).data)
|
|
|
|
|
|
role_model = DatabaseModelManage.get_model("role_model")
|
|
|
|
|
|
user_role_relation_model = DatabaseModelManage.get_model("workspace_user_role_mapping")
|
|
|
|
|
|
|
2025-07-14 02:49:47 +00:00
|
|
|
|
def _get_user_roles(user_ids, is_admin=True):
|
2025-07-02 01:43:34 +00:00
|
|
|
|
workspace_model = DatabaseModelManage.get_model("workspace_model")
|
|
|
|
|
|
if not (role_model and user_role_relation_model and workspace_model):
|
2025-05-30 03:07:24 +00:00
|
|
|
|
return {}
|
|
|
|
|
|
|
2025-07-02 01:43:34 +00:00
|
|
|
|
workspace_mapping = {str(workspace_model.id): workspace_model.name for workspace_model in
|
|
|
|
|
|
workspace_model.objects.all()}
|
|
|
|
|
|
|
2025-05-30 03:07:24 +00:00
|
|
|
|
# 获取所有相关角色关系,并预加载角色信息
|
|
|
|
|
|
user_role_relations = (
|
|
|
|
|
|
user_role_relation_model.objects
|
|
|
|
|
|
.filter(user_id__in=user_ids)
|
2025-06-19 07:26:16 +00:00
|
|
|
|
.select_related('role')
|
|
|
|
|
|
.distinct('user_id', 'role_id', 'workspace_id') # 确保组合唯一性
|
2025-05-30 03:07:24 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 构建用户ID到角色名称列表的映射
|
2025-07-01 11:23:42 +00:00
|
|
|
|
user_role_mapping = defaultdict(set) # 使用 set 去重
|
2025-06-19 07:26:16 +00:00
|
|
|
|
# 构建用户ID到角色ID与工作空间ID映射
|
|
|
|
|
|
user_role_setting_mapping = defaultdict(lambda: defaultdict(list))
|
2025-07-01 11:23:42 +00:00
|
|
|
|
user_role_workspace_mapping = defaultdict(lambda: defaultdict(list))
|
2025-06-19 07:26:16 +00:00
|
|
|
|
|
2025-05-30 03:07:24 +00:00
|
|
|
|
for relation in user_role_relations:
|
2025-06-19 07:26:16 +00:00
|
|
|
|
user_id = str(relation.user_id)
|
|
|
|
|
|
role_id = relation.role_id
|
|
|
|
|
|
workspace_id = relation.workspace_id
|
2025-07-14 02:49:47 +00:00
|
|
|
|
if not is_admin and relation.role.type == RoleConstants.ADMIN.name:
|
|
|
|
|
|
continue
|
2025-07-01 11:23:42 +00:00
|
|
|
|
user_role_mapping[user_id].add(relation.role.role_name)
|
2025-06-19 07:26:16 +00:00
|
|
|
|
user_role_setting_mapping[user_id][role_id].append(workspace_id)
|
2025-07-01 11:23:42 +00:00
|
|
|
|
user_role_workspace_mapping[user_id][relation.role.role_name].append(
|
|
|
|
|
|
workspace_mapping.get(workspace_id, workspace_id))
|
|
|
|
|
|
|
|
|
|
|
|
# 将 set 转换为 list 以符合返回格式
|
|
|
|
|
|
user_role_mapping = {uid: list(roles) for uid, roles in user_role_mapping.items()}
|
2025-06-19 07:26:16 +00:00
|
|
|
|
|
|
|
|
|
|
# 转换为所需的结构
|
|
|
|
|
|
result_user_role_setting_mapping = {
|
|
|
|
|
|
user_id: [{"role_id": role_id, "workspace_ids": workspace_ids}
|
|
|
|
|
|
for role_id, workspace_ids in roles.items()]
|
|
|
|
|
|
for user_id, roles in user_role_setting_mapping.items()
|
|
|
|
|
|
}
|
2025-07-01 11:23:42 +00:00
|
|
|
|
result_user_role_workspace_mapping = {
|
|
|
|
|
|
user_id: {role_name: workspace_names
|
|
|
|
|
|
for role_name, workspace_names in roles.items()}
|
|
|
|
|
|
for user_id, roles in user_role_workspace_mapping.items()
|
|
|
|
|
|
}
|
2025-05-30 03:07:24 +00:00
|
|
|
|
|
2025-07-01 11:23:42 +00:00
|
|
|
|
return user_role_mapping, result_user_role_setting_mapping, result_user_role_workspace_mapping
|
2025-05-30 03:07:24 +00:00
|
|
|
|
|
|
|
|
|
|
if role_model and user_role_relation_model:
|
2025-07-14 02:49:47 +00:00
|
|
|
|
# 获取当前用户的所有角色 判断是不是内置的系统管理员
|
|
|
|
|
|
is_admin = user_role_relation_model.objects.filter(user_id=user_id,
|
|
|
|
|
|
role_id=RoleConstants.ADMIN.name).exists()
|
2025-05-30 03:07:24 +00:00
|
|
|
|
user_ids = [user['id'] for user in result['records']]
|
2025-07-14 02:49:47 +00:00
|
|
|
|
user_role_mapping, user_role_setting_mapping, user_role_workspace_mapping = _get_user_roles(user_ids,
|
|
|
|
|
|
is_admin)
|
2025-05-30 03:07:24 +00:00
|
|
|
|
|
|
|
|
|
|
# 将角色信息添加回用户数据中
|
|
|
|
|
|
for user in result['records']:
|
2025-06-19 07:26:16 +00:00
|
|
|
|
user_id = str(user['id'])
|
2025-07-01 10:22:33 +00:00
|
|
|
|
user['role_name'] = user_role_mapping.get(user_id, [])
|
2025-06-19 07:26:16 +00:00
|
|
|
|
user['role_setting'] = user_role_setting_mapping.get(user_id, [])
|
2025-07-01 11:23:42 +00:00
|
|
|
|
user['role_workspace'] = user_role_workspace_mapping.get(user_id, [])
|
2025-05-30 03:07:24 +00:00
|
|
|
|
return result
|
2025-04-28 09:36:56 +00:00
|
|
|
|
|
2025-04-27 08:26:40 +00:00
|
|
|
|
@transaction.atomic
|
2025-07-14 02:49:47 +00:00
|
|
|
|
def save(self, instance, user_id, with_valid=True):
|
2025-04-27 08:26:40 +00:00
|
|
|
|
if with_valid:
|
2025-04-28 09:36:56 +00:00
|
|
|
|
self.UserInstance(data=instance).is_valid(raise_exception=True)
|
|
|
|
|
|
|
|
|
|
|
|
user = User(
|
|
|
|
|
|
id=uuid.uuid7(),
|
|
|
|
|
|
email=instance.get('email'),
|
|
|
|
|
|
phone=instance.get('phone', ''),
|
|
|
|
|
|
nick_name=instance.get('nick_name', ''),
|
|
|
|
|
|
username=instance.get('username'),
|
|
|
|
|
|
password=password_encrypt(instance.get('password')),
|
|
|
|
|
|
role=RoleConstants.USER.name,
|
2025-09-12 07:34:13 +00:00
|
|
|
|
source=instance.get('source', 'LOCAL'),
|
2025-04-28 09:36:56 +00:00
|
|
|
|
is_active=True
|
|
|
|
|
|
)
|
2025-07-14 02:49:47 +00:00
|
|
|
|
update_user_role(instance, user, user_id)
|
2025-04-27 08:26:40 +00:00
|
|
|
|
user.save()
|
2025-04-28 09:36:56 +00:00
|
|
|
|
return UserInstanceSerializer(user).data
|
|
|
|
|
|
|
|
|
|
|
|
class UserEditInstance(serializers.Serializer):
|
|
|
|
|
|
email = serializers.EmailField(
|
|
|
|
|
|
required=False,
|
|
|
|
|
|
label=_("Email"),
|
|
|
|
|
|
validators=[validators.EmailValidator(
|
|
|
|
|
|
message=ExceptionCodeConstants.EMAIL_FORMAT_ERROR.value.message,
|
|
|
|
|
|
code=ExceptionCodeConstants.EMAIL_FORMAT_ERROR.value.code
|
|
|
|
|
|
)]
|
|
|
|
|
|
)
|
|
|
|
|
|
nick_name = serializers.CharField(
|
2025-06-23 11:04:38 +00:00
|
|
|
|
required=False,
|
2025-04-28 09:36:56 +00:00
|
|
|
|
label=_("Name"),
|
2025-11-12 08:52:51 +00:00
|
|
|
|
max_length=64,
|
2025-04-28 09:36:56 +00:00
|
|
|
|
)
|
|
|
|
|
|
phone = serializers.CharField(
|
|
|
|
|
|
required=False,
|
|
|
|
|
|
label=_("Phone"),
|
|
|
|
|
|
max_length=20,
|
|
|
|
|
|
allow_null=True,
|
|
|
|
|
|
allow_blank=True
|
|
|
|
|
|
)
|
|
|
|
|
|
is_active = serializers.BooleanField(
|
|
|
|
|
|
required=False,
|
|
|
|
|
|
label=_("Is Active")
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def is_valid(self, *, user_id=None, raise_exception=False):
|
|
|
|
|
|
super().is_valid(raise_exception=True)
|
|
|
|
|
|
self._check_unique_email(user_id)
|
2025-05-16 10:23:27 +00:00
|
|
|
|
self._check_unique_nick_name(user_id)
|
|
|
|
|
|
|
|
|
|
|
|
def _check_unique_nick_name(self, user_id):
|
|
|
|
|
|
nick_name = self.data.get('nick_name')
|
|
|
|
|
|
if nick_name and User.objects.filter(nick_name=nick_name).exclude(id=user_id).exists():
|
|
|
|
|
|
raise AppApiException(1008, _('Nickname is already in use'))
|
2025-04-28 09:36:56 +00:00
|
|
|
|
|
|
|
|
|
|
def _check_unique_email(self, user_id):
|
|
|
|
|
|
email = self.data.get('email')
|
|
|
|
|
|
if email and User.objects.filter(email=email).exclude(id=user_id).exists():
|
|
|
|
|
|
raise AppApiException(1004, _('Email is already in use'))
|
|
|
|
|
|
|
|
|
|
|
|
class RePasswordInstance(serializers.Serializer):
|
|
|
|
|
|
password = serializers.CharField(
|
|
|
|
|
|
required=True,
|
|
|
|
|
|
label=_("Password"),
|
|
|
|
|
|
max_length=20,
|
|
|
|
|
|
min_length=6,
|
|
|
|
|
|
validators=[
|
|
|
|
|
|
validators.RegexValidator(
|
|
|
|
|
|
regex=PASSWORD_REGEX,
|
|
|
|
|
|
message=_(
|
|
|
|
|
|
"The password must be 6-20 characters long and must be a combination of letters, numbers, and special characters."
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
]
|
|
|
|
|
|
)
|
|
|
|
|
|
re_password = serializers.CharField(
|
|
|
|
|
|
required=True,
|
|
|
|
|
|
label=_("Re Password"),
|
|
|
|
|
|
validators=[
|
|
|
|
|
|
validators.RegexValidator(
|
|
|
|
|
|
regex=PASSWORD_REGEX,
|
|
|
|
|
|
message=_(
|
|
|
|
|
|
"The confirmation password must be 6-20 characters long and must be a combination of letters, numbers, and special characters."
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
]
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def is_valid(self, *, raise_exception=False):
|
|
|
|
|
|
super().is_valid(raise_exception=True)
|
|
|
|
|
|
self._check_passwords_match()
|
|
|
|
|
|
|
|
|
|
|
|
def _check_passwords_match(self):
|
|
|
|
|
|
if self.data.get('password') != self.data.get('re_password'):
|
|
|
|
|
|
raise ExceptionCodeConstants.PASSWORD_NOT_EQ_RE_PASSWORD.value.to_app_api_exception()
|
|
|
|
|
|
|
|
|
|
|
|
class Operate(serializers.Serializer):
|
|
|
|
|
|
id = serializers.UUIDField(required=True, label=_('User ID'))
|
|
|
|
|
|
|
|
|
|
|
|
def is_valid(self, *, raise_exception=False):
|
|
|
|
|
|
super().is_valid(raise_exception=True)
|
|
|
|
|
|
self._check_user_exists()
|
|
|
|
|
|
|
|
|
|
|
|
def _check_user_exists(self):
|
|
|
|
|
|
if not User.objects.filter(id=self.data.get('id')).exists():
|
|
|
|
|
|
raise AppApiException(1004, _('User does not exist'))
|
|
|
|
|
|
|
|
|
|
|
|
@transaction.atomic
|
|
|
|
|
|
def delete(self, with_valid=True):
|
|
|
|
|
|
if with_valid:
|
|
|
|
|
|
self.is_valid(raise_exception=True)
|
|
|
|
|
|
self._check_not_admin()
|
|
|
|
|
|
user_id = self.data.get('id')
|
|
|
|
|
|
# TODO 需要删除授权关系
|
|
|
|
|
|
User.objects.filter(id=user_id).delete()
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
def _check_not_admin(self):
|
|
|
|
|
|
user = User.objects.filter(id=self.data.get('id')).first()
|
2025-07-01 10:22:33 +00:00
|
|
|
|
if user.role == RoleConstants.ADMIN.name or str(user.id) == 'f0dd8f71-e4ee-11ee-8c84-a8a1595801ab':
|
2025-04-28 09:36:56 +00:00
|
|
|
|
raise AppApiException(1004, _('Unable to delete administrator'))
|
|
|
|
|
|
|
2025-07-14 02:49:47 +00:00
|
|
|
|
def edit(self, instance, user_id, with_valid=True):
|
2025-04-28 09:36:56 +00:00
|
|
|
|
if with_valid:
|
|
|
|
|
|
self.is_valid(raise_exception=True)
|
|
|
|
|
|
UserManageSerializer.UserEditInstance(data=instance).is_valid(user_id=self.data.get('id'),
|
|
|
|
|
|
raise_exception=True)
|
|
|
|
|
|
user = User.objects.filter(id=self.data.get('id')).first()
|
|
|
|
|
|
self._check_admin_modification(user, instance)
|
|
|
|
|
|
self._update_user_fields(user, instance)
|
2025-07-14 02:49:47 +00:00
|
|
|
|
update_user_role(instance, user, user_id)
|
2025-04-28 09:36:56 +00:00
|
|
|
|
user.save()
|
|
|
|
|
|
return UserInstanceSerializer(user).data
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
|
def _check_admin_modification(user, instance):
|
|
|
|
|
|
if user.role == RoleConstants.ADMIN.name and 'is_active' in instance and instance.get(
|
|
|
|
|
|
'is_active') is not None:
|
|
|
|
|
|
raise AppApiException(1004, _('Cannot modify administrator status'))
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
|
def _update_user_fields(user, instance):
|
|
|
|
|
|
update_keys = ['email', 'nick_name', 'phone', 'is_active']
|
|
|
|
|
|
for key in update_keys:
|
|
|
|
|
|
if key in instance and instance.get(key) is not None:
|
|
|
|
|
|
setattr(user, key, instance.get(key))
|
|
|
|
|
|
|
|
|
|
|
|
def one(self, with_valid=True):
|
|
|
|
|
|
if with_valid:
|
|
|
|
|
|
self.is_valid(raise_exception=True)
|
|
|
|
|
|
user = User.objects.filter(id=self.data.get('id')).first()
|
2025-05-08 02:51:01 +00:00
|
|
|
|
workspace_user_role_mapping_model = DatabaseModelManage.get_model("workspace_user_role_mapping")
|
|
|
|
|
|
if workspace_user_role_mapping_model:
|
|
|
|
|
|
role_setting = {}
|
|
|
|
|
|
workspace_user_role_mapping_list = QuerySet(workspace_user_role_mapping_model).filter(
|
|
|
|
|
|
user_id=user.id)
|
|
|
|
|
|
for workspace_user_role_mapping in workspace_user_role_mapping_list:
|
|
|
|
|
|
role_id = workspace_user_role_mapping.role_id
|
|
|
|
|
|
workspace_id = workspace_user_role_mapping.workspace_id
|
|
|
|
|
|
if role_id not in role_setting:
|
|
|
|
|
|
role_setting[role_id] = []
|
|
|
|
|
|
role_setting[role_id].append(workspace_id)
|
|
|
|
|
|
return {
|
|
|
|
|
|
'id': user.id,
|
|
|
|
|
|
'username': user.username,
|
|
|
|
|
|
'email': user.email,
|
|
|
|
|
|
'phone': user.phone,
|
|
|
|
|
|
'nick_name': user.nick_name,
|
|
|
|
|
|
'is_active': user.is_active,
|
|
|
|
|
|
'role_setting': role_setting
|
|
|
|
|
|
}
|
2025-04-28 09:36:56 +00:00
|
|
|
|
return UserInstanceSerializer(user).data
|
|
|
|
|
|
|
|
|
|
|
|
def re_password(self, instance, with_valid=True):
|
|
|
|
|
|
if with_valid:
|
|
|
|
|
|
self.is_valid(raise_exception=True)
|
|
|
|
|
|
UserManageSerializer.RePasswordInstance(data=instance).is_valid(raise_exception=True)
|
|
|
|
|
|
user = User.objects.filter(id=self.data.get('id')).first()
|
|
|
|
|
|
user.password = password_encrypt(instance.get('password'))
|
|
|
|
|
|
user.save()
|
|
|
|
|
|
return True
|
2025-05-15 04:07:07 +00:00
|
|
|
|
|
|
|
|
|
|
def get_user_list(self, workspace_id):
|
|
|
|
|
|
"""
|
|
|
|
|
|
获取用户列表
|
|
|
|
|
|
:param workspace_id: 工作空间ID
|
|
|
|
|
|
:return: 用户列表
|
|
|
|
|
|
"""
|
|
|
|
|
|
workspace_user_role_mapping_model = DatabaseModelManage.get_model("workspace_user_role_mapping")
|
|
|
|
|
|
if workspace_user_role_mapping_model:
|
|
|
|
|
|
user_ids = (
|
|
|
|
|
|
workspace_user_role_mapping_model.objects
|
|
|
|
|
|
.filter(workspace_id=workspace_id)
|
|
|
|
|
|
.values_list('user_id', flat=True)
|
|
|
|
|
|
.distinct()
|
|
|
|
|
|
)
|
|
|
|
|
|
else:
|
|
|
|
|
|
user_ids = User.objects.values_list('id', flat=True)
|
|
|
|
|
|
|
2025-05-16 10:23:27 +00:00
|
|
|
|
users = User.objects.filter(id__in=user_ids).values('id', 'nick_name')
|
2025-05-15 04:07:07 +00:00
|
|
|
|
return list(users)
|
2025-05-26 08:12:08 +00:00
|
|
|
|
|
2025-06-11 03:25:26 +00:00
|
|
|
|
def get_user_members(self, workspace_id):
|
|
|
|
|
|
"""
|
|
|
|
|
|
获取工作空间成员列表
|
|
|
|
|
|
:param workspace_id: 工作空间ID
|
|
|
|
|
|
:return: 成员列表
|
|
|
|
|
|
"""
|
|
|
|
|
|
role_model = DatabaseModelManage.get_model("role_model")
|
|
|
|
|
|
user_role_relation_model = DatabaseModelManage.get_model("workspace_user_role_mapping")
|
|
|
|
|
|
|
|
|
|
|
|
if user_role_relation_model and role_model:
|
|
|
|
|
|
user_role_relations = (
|
|
|
|
|
|
user_role_relation_model.objects
|
2025-06-11 06:27:06 +00:00
|
|
|
|
.filter(workspace_id=workspace_id, role__type='USER')
|
2025-06-20 06:23:08 +00:00
|
|
|
|
.select_related('role', 'user')
|
2025-06-11 03:25:26 +00:00
|
|
|
|
)
|
2025-06-11 03:35:47 +00:00
|
|
|
|
user_dict = {}
|
|
|
|
|
|
for relation in user_role_relations:
|
|
|
|
|
|
user_id = relation.user.id
|
|
|
|
|
|
if user_id not in user_dict:
|
|
|
|
|
|
user_dict[user_id] = {
|
|
|
|
|
|
'id': user_id,
|
|
|
|
|
|
'nick_name': relation.user.nick_name,
|
|
|
|
|
|
'email': relation.user.email,
|
2025-06-18 06:01:43 +00:00
|
|
|
|
'roles': [relation.role.role_name]
|
2025-06-11 03:35:47 +00:00
|
|
|
|
}
|
|
|
|
|
|
else:
|
2025-06-19 07:26:16 +00:00
|
|
|
|
user_dict[user_id]['roles'].append(relation.role.role_name)
|
2025-06-11 03:35:47 +00:00
|
|
|
|
|
|
|
|
|
|
# 将字典值转换为列表形式
|
|
|
|
|
|
return list(user_dict.values())
|
|
|
|
|
|
user_list = User.objects.exclude(role=RoleConstants.ADMIN.name)
|
|
|
|
|
|
return [
|
|
|
|
|
|
{
|
|
|
|
|
|
'id': user.id,
|
|
|
|
|
|
'nick_name': user.nick_name,
|
|
|
|
|
|
'email': user.email,
|
|
|
|
|
|
'roles': [RoleConstants.USER.name]
|
|
|
|
|
|
} for user in user_list
|
|
|
|
|
|
]
|
2025-06-11 03:25:26 +00:00
|
|
|
|
|
2025-06-03 10:35:42 +00:00
|
|
|
|
class BatchDelete(serializers.Serializer):
|
|
|
|
|
|
ids = serializers.ListField(required=True, label=_('User IDs'))
|
|
|
|
|
|
|
|
|
|
|
|
def batch_delete(self, with_valid=True):
|
|
|
|
|
|
if with_valid:
|
|
|
|
|
|
self.is_valid(raise_exception=True)
|
|
|
|
|
|
ids = self.data.get('ids')
|
|
|
|
|
|
if not ids:
|
|
|
|
|
|
raise AppApiException(1004, _('User IDs cannot be empty'))
|
|
|
|
|
|
User.objects.filter(id__in=ids).delete()
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
2025-06-11 10:29:26 +00:00
|
|
|
|
def get_all_user_list(self):
|
|
|
|
|
|
users = User.objects.all().values('id', 'nick_name', 'username')
|
|
|
|
|
|
return list(users)
|
|
|
|
|
|
|
2025-05-28 09:45:02 +00:00
|
|
|
|
|
2025-07-14 02:49:47 +00:00
|
|
|
|
def update_user_role(instance, user, user_id=None):
|
2025-05-26 08:12:08 +00:00
|
|
|
|
workspace_user_role_mapping_model = DatabaseModelManage.get_model("workspace_user_role_mapping")
|
|
|
|
|
|
if workspace_user_role_mapping_model:
|
2025-07-16 10:03:33 +00:00
|
|
|
|
role_setting = instance.get('role_setting')
|
|
|
|
|
|
license_is_valid = DatabaseModelManage.get_model('license_is_valid') or (lambda: False)
|
2025-07-16 10:22:41 +00:00
|
|
|
|
license_is_valid = license_is_valid() if license_is_valid() is not None else False
|
|
|
|
|
|
if not role_setting or (len(role_setting) == 1
|
|
|
|
|
|
and role_setting[0].get('role_id') == ''
|
|
|
|
|
|
and len(role_setting[0].get('workspace_ids', [])) == 0):
|
|
|
|
|
|
if not license_is_valid:
|
|
|
|
|
|
workspace_user_role_mapping_model.objects.create(
|
|
|
|
|
|
id=uuid.uuid7(),
|
|
|
|
|
|
user_id=user.id,
|
|
|
|
|
|
role_id=RoleConstants.USER.name,
|
|
|
|
|
|
workspace_id='default'
|
|
|
|
|
|
)
|
2025-07-16 10:03:33 +00:00
|
|
|
|
return
|
|
|
|
|
|
|
2025-07-14 02:49:47 +00:00
|
|
|
|
is_admin = workspace_user_role_mapping_model.objects.filter(user_id=user_id,
|
|
|
|
|
|
role_id=RoleConstants.ADMIN.name).exists()
|
2025-07-16 10:03:33 +00:00
|
|
|
|
|
2025-07-01 10:22:33 +00:00
|
|
|
|
if str(user.id) == 'f0dd8f71-e4ee-11ee-8c84-a8a1595801ab':
|
|
|
|
|
|
# 需要判断当前角色的权限 不能删除系统管理员 空间管理员 普通管理员等角色
|
|
|
|
|
|
# role_setting是一个数组 结构式 [{role_id:1,workspace_ids:[1,2]}]
|
|
|
|
|
|
# 如果role_id不包含ADMIN 就直接报错 如果WORKSPACE_MANAGE 或者USER 必须判断workspace_ids是否包含默认工作空间 不包含就报错
|
2025-07-03 04:03:13 +00:00
|
|
|
|
admin_role_id = RoleConstants.ADMIN.name
|
|
|
|
|
|
workspace_manage_role_id = RoleConstants.WORKSPACE_MANAGE.name
|
|
|
|
|
|
# 判断内置的三个角色是不是不在
|
|
|
|
|
|
current_role_ids = {item['role_id'] for item in role_setting}
|
|
|
|
|
|
initial_role = [admin_role_id, workspace_manage_role_id, RoleConstants.USER.name]
|
|
|
|
|
|
if not set(initial_role).issubset(current_role_ids):
|
|
|
|
|
|
raise AppApiException(1004, _("Cannot delete built-in role"))
|
2025-07-01 10:22:33 +00:00
|
|
|
|
|
|
|
|
|
|
if not any(item['role_id'] == str(admin_role_id) for item in role_setting):
|
|
|
|
|
|
raise AppApiException(1004, _("Cannot delete built-in role"))
|
|
|
|
|
|
|
|
|
|
|
|
# 验证 WORKSPACE_MANAGE 或 USER 是否包含默认工作空间
|
|
|
|
|
|
default_workspace_id = 'default'
|
|
|
|
|
|
|
|
|
|
|
|
for item in role_setting:
|
|
|
|
|
|
role_id = item['role_id']
|
|
|
|
|
|
workspace_ids = item.get('workspace_ids', [])
|
|
|
|
|
|
|
|
|
|
|
|
if role_id == str(workspace_manage_role_id) or role_id == str(RoleConstants.USER.value):
|
|
|
|
|
|
if default_workspace_id not in workspace_ids:
|
|
|
|
|
|
raise AppApiException(1004, _("Cannot delete built-in role"))
|
2025-07-14 02:49:47 +00:00
|
|
|
|
if is_admin:
|
|
|
|
|
|
workspace_user_role_mapping_model.objects.filter(user_id=user.id).delete()
|
|
|
|
|
|
else:
|
|
|
|
|
|
workspace_user_role_mapping_model.objects.filter(user_id=user.id).exclude(
|
2025-08-06 07:44:03 +00:00
|
|
|
|
role__type=RoleConstants.ADMIN.name).delete()
|
2025-05-26 08:12:08 +00:00
|
|
|
|
relations = set()
|
|
|
|
|
|
for item in role_setting:
|
2025-06-19 06:37:47 +00:00
|
|
|
|
role_id = item['role_id']
|
|
|
|
|
|
workspace_ids = item['workspace_ids'] if item['workspace_ids'] else ['None']
|
|
|
|
|
|
for workspace_id in workspace_ids:
|
|
|
|
|
|
relations.add((role_id, workspace_id))
|
2025-05-26 08:12:08 +00:00
|
|
|
|
for role_id, workspace_id in relations:
|
|
|
|
|
|
workspace_user_role_mapping_model.objects.create(
|
|
|
|
|
|
id=uuid.uuid7(),
|
|
|
|
|
|
role_id=role_id,
|
|
|
|
|
|
workspace_id=workspace_id,
|
|
|
|
|
|
user_id=user.id
|
|
|
|
|
|
)
|
2025-06-18 11:21:27 +00:00
|
|
|
|
permission_version, permission_get_key = Cache_Version.PERMISSION_LIST.value
|
2025-06-18 09:25:24 +00:00
|
|
|
|
cache.delete(permission_get_key(str(user.id)), version=permission_version)
|
2025-06-07 11:03:18 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class RePasswordSerializer(serializers.Serializer):
|
2025-07-11 10:47:03 +00:00
|
|
|
|
email = serializers.EmailField(
|
|
|
|
|
|
required=True,
|
|
|
|
|
|
label=_("Email"),
|
|
|
|
|
|
validators=[validators.EmailValidator(message=ExceptionCodeConstants.EMAIL_FORMAT_ERROR.value.message,
|
|
|
|
|
|
code=ExceptionCodeConstants.EMAIL_FORMAT_ERROR.value.code)])
|
|
|
|
|
|
|
|
|
|
|
|
code = serializers.CharField(required=True, label=_("Code"))
|
|
|
|
|
|
password = serializers.CharField(
|
|
|
|
|
|
required=True,
|
|
|
|
|
|
label=_("Password"),
|
|
|
|
|
|
max_length=20,
|
|
|
|
|
|
min_length=6,
|
|
|
|
|
|
validators=[
|
|
|
|
|
|
validators.RegexValidator(
|
|
|
|
|
|
regex=PASSWORD_REGEX,
|
|
|
|
|
|
message=_(
|
|
|
|
|
|
"The password must be 6-20 characters long and must be a combination of letters, numbers, and special characters."
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
]
|
|
|
|
|
|
)
|
|
|
|
|
|
re_password = serializers.CharField(
|
|
|
|
|
|
required=True,
|
|
|
|
|
|
label=_("Re Password"),
|
|
|
|
|
|
validators=[
|
|
|
|
|
|
validators.RegexValidator(
|
|
|
|
|
|
regex=PASSWORD_REGEX,
|
|
|
|
|
|
message=_(
|
|
|
|
|
|
"The confirmation password must be 6-20 characters long and must be a combination of letters, numbers, and special characters."
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
]
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
|
model = User
|
|
|
|
|
|
fields = '__all__'
|
|
|
|
|
|
|
|
|
|
|
|
def is_valid(self, *, raise_exception=False):
|
|
|
|
|
|
super().is_valid(raise_exception=True)
|
|
|
|
|
|
email = self.data.get("email")
|
|
|
|
|
|
cache_code = cache.get(get_key(email + ':reset_password'), version=version)
|
|
|
|
|
|
if self.data.get('password') != self.data.get('re_password'):
|
|
|
|
|
|
raise AppApiException(ExceptionCodeConstants.PASSWORD_NOT_EQ_RE_PASSWORD.value.code,
|
|
|
|
|
|
ExceptionCodeConstants.PASSWORD_NOT_EQ_RE_PASSWORD.value.message)
|
|
|
|
|
|
if cache_code != self.data.get('code'):
|
|
|
|
|
|
raise AppApiException(ExceptionCodeConstants.CODE_ERROR.value.code,
|
|
|
|
|
|
ExceptionCodeConstants.CODE_ERROR.value.message)
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
def reset_password(self):
|
|
|
|
|
|
"""
|
|
|
|
|
|
修改密码
|
|
|
|
|
|
:return: 是否成功
|
|
|
|
|
|
"""
|
|
|
|
|
|
if self.is_valid():
|
|
|
|
|
|
email = self.data.get("email")
|
|
|
|
|
|
QuerySet(User).filter(email=email).update(
|
|
|
|
|
|
password=password_encrypt(self.data.get('password')))
|
|
|
|
|
|
code_cache_key = email + ":reset_password"
|
|
|
|
|
|
cache.delete(get_key(code_cache_key), version=version)
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ResetCurrentUserPassword(serializers.Serializer):
|
|
|
|
|
|
password = serializers.CharField(
|
|
|
|
|
|
required=True,
|
|
|
|
|
|
label=_("Password"),
|
|
|
|
|
|
max_length=20,
|
|
|
|
|
|
min_length=6,
|
|
|
|
|
|
validators=[
|
|
|
|
|
|
validators.RegexValidator(
|
|
|
|
|
|
regex=PASSWORD_REGEX,
|
|
|
|
|
|
message=_(
|
|
|
|
|
|
"The password must be 6-20 characters long and must be a combination of letters, numbers, and special characters."
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
]
|
|
|
|
|
|
)
|
|
|
|
|
|
re_password = serializers.CharField(
|
|
|
|
|
|
required=True,
|
|
|
|
|
|
label=_("Re Password"),
|
|
|
|
|
|
validators=[
|
|
|
|
|
|
validators.RegexValidator(
|
|
|
|
|
|
regex=PASSWORD_REGEX,
|
|
|
|
|
|
message=_(
|
|
|
|
|
|
"The confirmation password must be 6-20 characters long and must be a combination of letters, numbers, and special characters."
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
]
|
|
|
|
|
|
)
|
2025-06-07 11:03:18 +00:00
|
|
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
|
model = User
|
|
|
|
|
|
fields = '__all__'
|
|
|
|
|
|
|
|
|
|
|
|
def is_valid(self, *, raise_exception=False):
|
|
|
|
|
|
super().is_valid(raise_exception=True)
|
|
|
|
|
|
if self.data.get('password') != self.data.get('re_password'):
|
|
|
|
|
|
raise AppApiException(ExceptionCodeConstants.PASSWORD_NOT_EQ_RE_PASSWORD.value.code,
|
|
|
|
|
|
ExceptionCodeConstants.PASSWORD_NOT_EQ_RE_PASSWORD.value.message)
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
2025-06-12 11:16:30 +00:00
|
|
|
|
def reset_password(self, user_id: str):
|
2025-06-07 11:03:18 +00:00
|
|
|
|
"""
|
|
|
|
|
|
修改密码
|
|
|
|
|
|
:return: 是否成功
|
|
|
|
|
|
"""
|
|
|
|
|
|
if self.is_valid():
|
2025-06-12 11:16:30 +00:00
|
|
|
|
QuerySet(User).filter(id=user_id).update(
|
2025-06-07 11:03:18 +00:00
|
|
|
|
password=password_encrypt(self.data.get('password')))
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SendEmailSerializer(serializers.Serializer):
|
|
|
|
|
|
email = serializers.EmailField(
|
|
|
|
|
|
required=True
|
|
|
|
|
|
, label=_("Email"),
|
|
|
|
|
|
validators=[validators.EmailValidator(message=ExceptionCodeConstants.EMAIL_FORMAT_ERROR.value.message,
|
|
|
|
|
|
code=ExceptionCodeConstants.EMAIL_FORMAT_ERROR.value.code)])
|
|
|
|
|
|
|
|
|
|
|
|
type = serializers.CharField(required=True, label=_("Type"), validators=[
|
|
|
|
|
|
validators.RegexValidator(regex=re.compile("^register|reset_password$"),
|
|
|
|
|
|
message=_("The type only supports register|reset_password"), code=500)
|
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
|
model = User
|
|
|
|
|
|
fields = '__all__'
|
|
|
|
|
|
|
|
|
|
|
|
def is_valid(self, *, raise_exception=False):
|
|
|
|
|
|
super().is_valid(raise_exception=raise_exception)
|
|
|
|
|
|
user_exists = QuerySet(User).filter(email=self.data.get('email')).exists()
|
|
|
|
|
|
if not user_exists and self.data.get('type') == 'reset_password':
|
|
|
|
|
|
raise ExceptionCodeConstants.EMAIL_IS_NOT_EXIST.value.to_app_api_exception()
|
|
|
|
|
|
elif user_exists and self.data.get('type') == 'register':
|
|
|
|
|
|
raise ExceptionCodeConstants.EMAIL_IS_EXIST.value.to_app_api_exception()
|
|
|
|
|
|
code_cache_key = self.data.get('email') + ":" + self.data.get("type")
|
|
|
|
|
|
code_cache_key_lock = code_cache_key + "_lock"
|
2025-07-10 07:36:11 +00:00
|
|
|
|
ttl = cache.ttl(code_cache_key_lock, version=version)
|
|
|
|
|
|
if ttl is not None and ttl > 0:
|
2025-06-07 11:03:18 +00:00
|
|
|
|
raise AppApiException(500, _("Do not send emails again within {seconds} seconds").format(
|
|
|
|
|
|
seconds=int(ttl.total_seconds())))
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
def send(self):
|
|
|
|
|
|
"""
|
|
|
|
|
|
发送邮件
|
|
|
|
|
|
:return: 是否发送成功
|
|
|
|
|
|
:exception 发送失败异常
|
|
|
|
|
|
"""
|
|
|
|
|
|
email = self.data.get("email")
|
|
|
|
|
|
state = self.data.get("type")
|
|
|
|
|
|
# 生成随机验证码
|
|
|
|
|
|
code = "".join(list(map(lambda i: random.choice(['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'
|
|
|
|
|
|
]), range(6))))
|
|
|
|
|
|
# 获取邮件模板
|
|
|
|
|
|
language = get_language()
|
|
|
|
|
|
file = open(
|
|
|
|
|
|
os.path.join(PROJECT_DIR, "apps", "common", 'template', f'email_template_{to_locale(language)}.html'), "r",
|
|
|
|
|
|
encoding='utf-8')
|
|
|
|
|
|
content = file.read()
|
|
|
|
|
|
file.close()
|
|
|
|
|
|
code_cache_key = email + ":" + state
|
|
|
|
|
|
code_cache_key_lock = code_cache_key + "_lock"
|
|
|
|
|
|
# 设置缓存
|
2025-07-10 07:36:11 +00:00
|
|
|
|
cache.set(get_key(code_cache_key_lock), code, timeout=60, version=version)
|
2025-06-07 11:03:18 +00:00
|
|
|
|
system_setting = QuerySet(SystemSetting).filter(type=SettingType.EMAIL.value).first()
|
|
|
|
|
|
if system_setting is None:
|
2025-06-10 11:50:33 +00:00
|
|
|
|
cache.delete(get_key(code_cache_key_lock), version=version)
|
2025-06-07 11:03:18 +00:00
|
|
|
|
raise AppApiException(1004,
|
|
|
|
|
|
_("The email service has not been set up. Please contact the administrator to set up the email service in [Email Settings]."))
|
|
|
|
|
|
try:
|
|
|
|
|
|
connection = EmailBackend(system_setting.meta.get("email_host"),
|
|
|
|
|
|
system_setting.meta.get('email_port'),
|
|
|
|
|
|
system_setting.meta.get('email_host_user'),
|
|
|
|
|
|
system_setting.meta.get('email_host_password'),
|
|
|
|
|
|
system_setting.meta.get('email_use_tls'),
|
|
|
|
|
|
False,
|
|
|
|
|
|
system_setting.meta.get('email_use_ssl')
|
|
|
|
|
|
)
|
|
|
|
|
|
# 发送邮件
|
|
|
|
|
|
send_mail(_('【Intelligent knowledge base question and answer system-{action}】').format(
|
|
|
|
|
|
action=_('User registration') if state == 'register' else _('Change password')),
|
|
|
|
|
|
'',
|
|
|
|
|
|
html_message=f'{content.replace("${code}", code)}',
|
|
|
|
|
|
from_email=system_setting.meta.get('from_email'),
|
|
|
|
|
|
recipient_list=[email], fail_silently=False, connection=connection)
|
|
|
|
|
|
except Exception as e:
|
2025-06-10 11:50:33 +00:00
|
|
|
|
cache.delete(get_key(code_cache_key_lock))
|
2025-06-07 11:03:18 +00:00
|
|
|
|
raise AppApiException(500, f"{str(e)}" + _("Email sending failed"))
|
2025-07-10 07:36:11 +00:00
|
|
|
|
cache.set(get_key(code_cache_key), code, timeout=60 * 30, version=version)
|
2025-06-07 11:03:18 +00:00
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CheckCodeSerializer(serializers.Serializer):
|
|
|
|
|
|
"""
|
|
|
|
|
|
校验验证码
|
|
|
|
|
|
"""
|
|
|
|
|
|
email = serializers.EmailField(
|
|
|
|
|
|
required=True,
|
|
|
|
|
|
label=_("Email"),
|
|
|
|
|
|
validators=[validators.EmailValidator(message=ExceptionCodeConstants.EMAIL_FORMAT_ERROR.value.message,
|
|
|
|
|
|
code=ExceptionCodeConstants.EMAIL_FORMAT_ERROR.value.code)])
|
|
|
|
|
|
code = serializers.CharField(required=True, label=_("Verification code"))
|
|
|
|
|
|
|
|
|
|
|
|
type = serializers.CharField(required=True,
|
|
|
|
|
|
label=_("Type"),
|
|
|
|
|
|
validators=[
|
|
|
|
|
|
validators.RegexValidator(regex=re.compile("^register|reset_password$"),
|
|
|
|
|
|
message=_(
|
|
|
|
|
|
"The type only supports register|reset_password"),
|
|
|
|
|
|
code=500)
|
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
|
|
def is_valid(self, *, raise_exception=False):
|
|
|
|
|
|
super().is_valid()
|
2025-06-10 11:50:33 +00:00
|
|
|
|
value = cache.get(get_key(self.data.get("email") + ":" + self.data.get("type")), version=version)
|
2025-06-07 11:03:18 +00:00
|
|
|
|
if value is None or value != self.data.get("code"):
|
|
|
|
|
|
raise ExceptionCodeConstants.CODE_ERROR.value.to_app_api_exception()
|
|
|
|
|
|
return True
|
2025-06-12 03:24:55 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SwitchLanguageSerializer(serializers.Serializer):
|
|
|
|
|
|
user_id = serializers.UUIDField(required=True, label=_('user id'))
|
2025-06-12 03:25:19 +00:00
|
|
|
|
language = serializers.CharField(required=True, label=_('language'))
|
2025-06-12 03:24:55 +00:00
|
|
|
|
|
|
|
|
|
|
def switch(self):
|
|
|
|
|
|
self.is_valid(raise_exception=True)
|
|
|
|
|
|
language = self.data.get('language')
|
|
|
|
|
|
support_language_list = ['zh-CN', 'zh-Hant', 'en-US']
|
|
|
|
|
|
if not support_language_list.__contains__(language):
|
|
|
|
|
|
raise AppApiException(500, _('language only support:') + ','.join(support_language_list))
|
|
|
|
|
|
QuerySet(User).filter(id=self.data.get('user_id')).update(language=language)
|