feat: support create user

This commit is contained in:
wxg0103 2025-04-27 16:26:40 +08:00
parent a91d8016f8
commit bca128ec39
10 changed files with 242 additions and 10 deletions

View File

@ -0,0 +1,43 @@
# coding=utf-8
"""
@project: qabot
@Author
@file exception_code_constants.py
@date2023/9/4 14:09
@desc: 异常常量类
"""
from enum import Enum
from common.exception.app_exception import AppApiException
from django.utils.translation import gettext_lazy as _
class ExceptionCodeConstantsValue:
def __init__(self, code, message):
self.code = code
self.message = message
def get_message(self):
return self.message
def get_code(self):
return self.code
def to_app_api_exception(self):
return AppApiException(code=self.code, message=self.message)
class ExceptionCodeConstants(Enum):
INCORRECT_USERNAME_AND_PASSWORD = ExceptionCodeConstantsValue(1000, _('The username or password is incorrect'))
NOT_AUTHENTICATION = ExceptionCodeConstantsValue(1001, _('Please log in first and bring the user Token'))
EMAIL_SEND_ERROR = ExceptionCodeConstantsValue(1002, _('Email sending failed'))
EMAIL_FORMAT_ERROR = ExceptionCodeConstantsValue(1003, _('Email format error'))
EMAIL_IS_EXIST = ExceptionCodeConstantsValue(1004, _('The email has been registered, please log in directly'))
EMAIL_IS_NOT_EXIST = ExceptionCodeConstantsValue(1005, _('The email is not registered, please register first'))
CODE_ERROR = ExceptionCodeConstantsValue(1005,
_('The verification code is incorrect or the verification code has expired'))
USERNAME_IS_EXIST = ExceptionCodeConstantsValue(1006, _('The username has been registered, please log in directly'))
USERNAME_ERROR = ExceptionCodeConstantsValue(1006,
_('The username cannot be empty and must be between 6 and 20 characters long.'))
PASSWORD_NOT_EQ_RE_PASSWORD = ExceptionCodeConstantsValue(1007,
_('Password and confirmation password are inconsistent'))

View File

@ -111,6 +111,8 @@ class PermissionConstants(Enum):
""" """
USER_READ = Permission(group=Group.USER, operate=Operate.READ, role_list=[RoleConstants.ADMIN, USER_READ = Permission(group=Group.USER, operate=Operate.READ, role_list=[RoleConstants.ADMIN,
RoleConstants.USER]) RoleConstants.USER])
USER_CREATE = Permission(group=Group.USER, operate=Operate.CREATE,
role_list=[RoleConstants.ADMIN, RoleConstants.USER])
USER_EDIT = Permission(group=Group.USER, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN]) USER_EDIT = Permission(group=Group.USER, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN])
USER_DELETE = Permission(group=Group.USER, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN]) USER_DELETE = Permission(group=Group.USER, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN])
@ -153,11 +155,12 @@ class PermissionConstants(Enum):
KNOWLEDGE_MODULE_EDIT = Permission(group=Group.KNOWLEDGE, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN, KNOWLEDGE_MODULE_EDIT = Permission(group=Group.KNOWLEDGE, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN,
RoleConstants.USER]) RoleConstants.USER])
KNOWLEDGE_MODULE_DELETE = Permission(group=Group.KNOWLEDGE, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN, KNOWLEDGE_MODULE_DELETE = Permission(group=Group.KNOWLEDGE, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN,
RoleConstants.USER]) RoleConstants.USER])
KNOWLEDGE_READ = Permission(group=Group.KNOWLEDGE, operate=Operate.READ, role_list=[RoleConstants.ADMIN, KNOWLEDGE_READ = Permission(group=Group.KNOWLEDGE, operate=Operate.READ, role_list=[RoleConstants.ADMIN,
RoleConstants.USER]) RoleConstants.USER])
KNOWLEDGE_CREATE = Permission(group=Group.KNOWLEDGE, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN, KNOWLEDGE_CREATE = Permission(group=Group.KNOWLEDGE, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN,
RoleConstants.USER]) RoleConstants.USER])
def get_workspace_application_permission(self): def get_workspace_application_permission(self):
return lambda r, kwargs: Permission(group=self.value.group, operate=self.value.operate, return lambda r, kwargs: Permission(group=self.value.group, operate=self.value.operate,
resource_path= resource_path=

View File

@ -0,0 +1,35 @@
# coding=utf-8
"""
@project: MaxKB
@Author
@file db_model_manage.py
@date2024/7/22 17:00
@desc:
"""
from importlib import import_module
from django.conf import settings
def new_instance_by_class_path(class_path: str):
parts = class_path.rpartition('.')
package_path = parts[0]
class_name = parts[2]
module = import_module(package_path)
HandlerClass = getattr(module, class_name)
return HandlerClass()
class DBModelManage:
model_dict = {}
@staticmethod
def get_model(model_name):
return DBModelManage.model_dict.get(model_name)
@staticmethod
def init():
handles = [new_instance_by_class_path(class_path) for class_path in
(settings.MODEL_HANDLES if hasattr(settings, 'MODEL_HANDLES') else [])]
for h in handles:
model_dict = h.get_model_dict()
DBModelManage.model_dict = {**DBModelManage.model_dict, **model_dict}

View File

@ -0,0 +1,15 @@
# coding=utf-8
"""
@project: MaxKB
@Author
@file base_handle.py
@date2024/7/22 17:02
@desc:
"""
from abc import ABC, abstractmethod
class IBaseModelHandle(ABC):
@abstractmethod
def get_model_dict(self):
pass

View File

@ -0,0 +1,14 @@
# coding=utf-8
"""
@project: MaxKB
@Author
@file default_base_model_handle.py
@date2024/7/22 17:06
@desc:
"""
from common.models.handle.base_handle import IBaseModelHandle
class DefaultBaseModelHandle(IBaseModelHandle):
def get_model_dict(self):
return {}

View File

@ -17,10 +17,12 @@ from functools import reduce
from typing import List, Dict from typing import List, Dict
from django.core.files.uploadedfile import InMemoryUploadedFile from django.core.files.uploadedfile import InMemoryUploadedFile
from django.db.models import QuerySet
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from pydub import AudioSegment from pydub import AudioSegment
from ..exception.app_exception import AppApiException from ..exception.app_exception import AppApiException
from ..models.db_model_manage import DBModelManage
def password_encrypt(row_password): def password_encrypt(row_password):
@ -211,3 +213,23 @@ def query_params_to_single_dict(query_params: Dict):
filter(lambda item: item is not None, [({key: value} if value is not None and len(value) > 0 else None) for filter(lambda item: item is not None, [({key: value} if value is not None and len(value) > 0 else None) for
key, value in key, value in
query_params.items()])), {}) query_params.items()])), {})
def valid_license(model=None, count=None, message=None):
def inner(func):
def run(*args, **kwargs):
xpack_cache = DBModelManage.get_model('xpack_cache')
is_license_valid = xpack_cache.get('XPACK_LICENSE_IS_VALID', False) if xpack_cache is not None else False
record_count = QuerySet(model).count()
if not is_license_valid and record_count >= count:
error_message = message or _(
'Limit {count} exceeded, please contact us (https://fit2cloud.com/).').format(
count=count)
raise AppApiException(400, error_message)
return func(*args, **kwargs)
return run
return inner

View File

@ -11,7 +11,7 @@ from drf_spectacular.utils import OpenApiParameter
from common.mixins.api_mixin import APIMixin from common.mixins.api_mixin import APIMixin
from common.result import ResultSerializer from common.result import ResultSerializer
from users.serializers.user import UserProfileResponse from users.serializers.user import UserProfileResponse, CreateUserSerializer
class ApiUserProfileResponse(ResultSerializer): class ApiUserProfileResponse(ResultSerializer):
@ -25,6 +25,10 @@ class UserProfileAPI(APIMixin):
def get_response(): def get_response():
return ApiUserProfileResponse return ApiUserProfileResponse
@staticmethod
def get_request():
return CreateUserSerializer
class TestWorkspacePermissionUserApi(APIMixin): class TestWorkspacePermissionUserApi(APIMixin):
@staticmethod @staticmethod

View File

@ -6,18 +6,35 @@
@date2025/4/14 19:18 @date2025/4/14 19:18
@desc: @desc:
""" """
from rest_framework import serializers import re
from django.db import transaction
from django.db.models import QuerySet, Q
from rest_framework import serializers
import uuid_utils.compat as uuid
from common.constants.exception_code_constants import ExceptionCodeConstants
from common.constants.permission_constants import RoleConstants
from common.utils.common import valid_license, password_encrypt
from users.models import User from users.models import User
from django.utils.translation import gettext_lazy as _
from django.core import validators
class UserProfileResponse(serializers.ModelSerializer): class UserProfileResponse(serializers.ModelSerializer):
is_edit_password = serializers.BooleanField(required=True, label="是否修改密码") is_edit_password = serializers.BooleanField(required=True, label=_('Is Edit Password'))
permissions = serializers.ListField(required=True, label="权限") permissions = serializers.ListField(required=True, label=_('permissions'))
class Meta: class Meta:
model = User model = User
fields = ['id', 'username', 'email', 'role', 'permissions', 'language', 'is_edit_password'] fields = ['id', 'username', 'nick_name', 'email', 'role', 'permissions', 'language', 'is_edit_password']
class CreateUserSerializer(serializers.ModelSerializer):
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'))
class UserProfileSerializer(serializers.Serializer): class UserProfileSerializer(serializers.Serializer):
@ -30,8 +47,67 @@ class UserProfileSerializer(serializers.Serializer):
""" """
return {'id': user.id, return {'id': user.id,
'username': user.username, 'username': user.username,
'nick_name': user.nick_name,
'email': user.email, 'email': user.email,
'role': user.role, 'role': user.role,
'permissions': [str(p) for p in []], 'permissions': [str(p) for p in []],
'is_edit_password': user.password == 'd880e722c47a34d8e9fce789fc62389d' if user.role == 'ADMIN' else False, 'is_edit_password': user.password == 'd880e722c47a34d8e9fce789fc62389d' if user.role == 'ADMIN' else False,
'language': user.language} 'language': user.language}
class UserManageSerializer(serializers.Serializer):
class UserInstance(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)])
username = serializers.CharField(required=True,
label=_("Username"),
max_length=20,
min_length=6,
validators=[
validators.RegexValidator(regex=re.compile("^.{6,20}$"),
message=_(
'Username must be 6-20 characters long'))
])
password = serializers.CharField(required=True, label=_("Password"), max_length=20, min_length=6,
validators=[validators.RegexValidator(regex=re.compile(
"^(?![a-zA-Z]+$)(?![A-Z0-9]+$)(?![A-Z_!@#$%^&*`~.()-+=]+$)(?![a-z0-9]+$)(?![a-z_!@#$%^&*`~()-+=]+$)"
"(?![0-9_!@#$%^&*`~()-+=]+$)[a-zA-Z0-9_!@#$%^&*`~.()-+=]{6,20}$")
, message=_(
"The password must be 6-20 characters long and must be a combination of letters, numbers, and special characters."))])
nick_name = serializers.CharField(required=False, label=_("Nick name"), max_length=64,
allow_null=True, allow_blank=True)
phone = serializers.CharField(required=False, label=_("Phone"), max_length=20,
allow_null=True, allow_blank=True)
def is_valid(self, *, raise_exception=True):
super().is_valid(raise_exception=True)
username = self.data.get('username')
email = self.data.get('email')
u = QuerySet(User).filter(Q(username=username) | Q(email=email)).first()
if u is not None:
if u.email == email:
raise ExceptionCodeConstants.EMAIL_IS_EXIST.value.to_app_api_exception()
if u.username == username:
raise ExceptionCodeConstants.USERNAME_IS_EXIST.value.to_app_api_exception()
@valid_license(model=User, count=2,
message=_(
'The community version supports up to 2 users. If you need more users, please contact us (https://fit2cloud.com/).'))
@transaction.atomic
def save(self, instance, with_valid=True):
if with_valid:
UserManageSerializer.UserInstance(data=instance).is_valid(raise_exception=True)
user = User(id=uuid.uuid7(), email=instance.get('email'),
phone="" if instance.get('phone') is None else instance.get('phone'),
nick_name="" if instance.get('nick_name') is None else instance.get('nick_name')
, username=instance.get('username'), password=password_encrypt(instance.get('password')),
role=RoleConstants.USER.name, source="LOCAL",
is_active=True)
user.save()
return UserProfileSerializer(user).data

View File

@ -9,5 +9,11 @@ urlpatterns = [
path('user/captcha', views.CaptchaView.as_view(), name='captcha'), path('user/captcha', views.CaptchaView.as_view(), name='captcha'),
path('user/test', views.TestPermissionsUserView.as_view(), name="test"), path('user/test', views.TestPermissionsUserView.as_view(), name="test"),
path('workspace/<str:workspace_id>/user/profile', views.TestWorkspacePermissionUserView.as_view(), path('workspace/<str:workspace_id>/user/profile', views.TestWorkspacePermissionUserView.as_view(),
name="test_workspace_id_permission") name="test_workspace_id_permission"),
path("user_manage", views.UserManage.as_view(), name="user_manage"),
# path("user_manage/<str:user_id>", views.UserManage.Operate.as_view(), name="user_manage_operate"),
# path("user_manage/<str:user_id>/re_password", views.UserManage.RePassword.as_view(),
# name="user_manage_re_password"),
# path("user_manage/<int:current_page>/<int:page_size>", views.UserManage.Page.as_view(),
# name="user_manage_re_password"),
] ]

View File

@ -16,7 +16,7 @@ from common.auth.authentication import has_permissions
from common.constants.permission_constants import PermissionConstants, Permission, Group, Operate from common.constants.permission_constants import PermissionConstants, Permission, Group, Operate
from common.result import result from common.result import result
from users.api.user import UserProfileAPI, TestWorkspacePermissionUserApi from users.api.user import UserProfileAPI, TestWorkspacePermissionUserApi
from users.serializers.user import UserProfileSerializer from users.serializers.user import UserProfileSerializer, UserManageSerializer
class UserProfileView(APIView): class UserProfileView(APIView):
@ -56,3 +56,17 @@ class TestWorkspacePermissionUserView(APIView):
@has_permissions(PermissionConstants.USER_EDIT.get_workspace_permission()) @has_permissions(PermissionConstants.USER_EDIT.get_workspace_permission())
def get(self, request: Request, workspace_id): def get(self, request: Request, workspace_id):
return result.success(UserProfileSerializer().profile(request.user)) return result.success(UserProfileSerializer().profile(request.user))
class UserManage(APIView):
authentication_classes = [TokenAuth]
@extend_schema(methods=['POST'],
description=_("Create user"),
operation_id=_("Create user"),
tags=[_("User management")],
request=UserProfileAPI.get_request(),
responses=UserProfileAPI.get_response())
@has_permissions(PermissionConstants.USER_CREATE)
def post(self, request: Request):
return result.success(UserManageSerializer().save(request.data))