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,
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_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,
RoleConstants.USER])
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,
RoleConstants.USER])
RoleConstants.USER])
KNOWLEDGE_CREATE = Permission(group=Group.KNOWLEDGE, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN,
RoleConstants.USER])
RoleConstants.USER])
def get_workspace_application_permission(self):
return lambda r, kwargs: Permission(group=self.value.group, operate=self.value.operate,
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 django.core.files.uploadedfile import InMemoryUploadedFile
from django.db.models import QuerySet
from django.utils.translation import gettext as _
from pydub import AudioSegment
from ..exception.app_exception import AppApiException
from ..models.db_model_manage import DBModelManage
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
key, value in
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.result import ResultSerializer
from users.serializers.user import UserProfileResponse
from users.serializers.user import UserProfileResponse, CreateUserSerializer
class ApiUserProfileResponse(ResultSerializer):
@ -25,6 +25,10 @@ class UserProfileAPI(APIMixin):
def get_response():
return ApiUserProfileResponse
@staticmethod
def get_request():
return CreateUserSerializer
class TestWorkspacePermissionUserApi(APIMixin):
@staticmethod

View File

@ -6,18 +6,35 @@
@date2025/4/14 19:18
@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 django.utils.translation import gettext_lazy as _
from django.core import validators
class UserProfileResponse(serializers.ModelSerializer):
is_edit_password = serializers.BooleanField(required=True, label="是否修改密码")
permissions = serializers.ListField(required=True, label="权限")
is_edit_password = serializers.BooleanField(required=True, label=_('Is Edit Password'))
permissions = serializers.ListField(required=True, label=_('permissions'))
class Meta:
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):
@ -30,8 +47,67 @@ class UserProfileSerializer(serializers.Serializer):
"""
return {'id': user.id,
'username': user.username,
'nick_name': user.nick_name,
'email': user.email,
'role': user.role,
'permissions': [str(p) for p in []],
'is_edit_password': user.password == 'd880e722c47a34d8e9fce789fc62389d' if user.role == 'ADMIN' else False,
'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/test', views.TestPermissionsUserView.as_view(), name="test"),
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.result import result
from users.api.user import UserProfileAPI, TestWorkspacePermissionUserApi
from users.serializers.user import UserProfileSerializer
from users.serializers.user import UserProfileSerializer, UserManageSerializer
class UserProfileView(APIView):
@ -56,3 +56,17 @@ class TestWorkspacePermissionUserView(APIView):
@has_permissions(PermissionConstants.USER_EDIT.get_workspace_permission())
def get(self, request: Request, workspace_id):
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))