From 63eb7e08a8455cf444943c8de253ca2e09174f5f Mon Sep 17 00:00:00 2001 From: Binx Date: Thu, 2 Jul 2026 00:16:16 +0800 Subject: [PATCH 1/3] fix: Remove uniqueness constraint from user nickname --- apps/local_model/models/user.py | 2 +- apps/users/models/user.py | 2 +- apps/users/serializers/user.py | 11 +---------- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/apps/local_model/models/user.py b/apps/local_model/models/user.py index 0f480d89fde..d68130c242c 100644 --- a/apps/local_model/models/user.py +++ b/apps/local_model/models/user.py @@ -17,7 +17,7 @@ class User(models.Model): id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") email = models.EmailField(unique=True, null=True, blank=True, verbose_name="邮箱", db_index=True) phone = models.CharField(max_length=20, verbose_name="电话", default="", db_index=True) - nick_name = models.CharField(max_length=150, verbose_name="昵称", unique=True, db_index=True) + nick_name = models.CharField(max_length=150, verbose_name="昵称", db_index=True) username = models.CharField(max_length=150, unique=True, verbose_name="用户名", db_index=True) password = models.CharField(max_length=150, verbose_name="密码") role = models.CharField(max_length=150, verbose_name="角色") diff --git a/apps/users/models/user.py b/apps/users/models/user.py index 0f480d89fde..d68130c242c 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -17,7 +17,7 @@ class User(models.Model): id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") email = models.EmailField(unique=True, null=True, blank=True, verbose_name="邮箱", db_index=True) phone = models.CharField(max_length=20, verbose_name="电话", default="", db_index=True) - nick_name = models.CharField(max_length=150, verbose_name="昵称", unique=True, db_index=True) + nick_name = models.CharField(max_length=150, verbose_name="昵称", db_index=True) username = models.CharField(max_length=150, unique=True, verbose_name="用户名", db_index=True) password = models.CharField(max_length=150, verbose_name="密码") role = models.CharField(max_length=150, verbose_name="角色") diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index e49291e1bb9..eaff28e8c05 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -220,15 +220,12 @@ def is_valid(self, *, raise_exception=True): def _check_unique_username_and_email(self): username = self.data.get('username') email = self.data.get('email') - nick_name = self.data.get('nick_name') - user = User.objects.filter(Q(username=username) | Q(email=email) | Q(nick_name=nick_name)).first() + user = User.objects.filter(Q(username=username) | Q(email=email)).first() if user: if user.email == email: raise ExceptionCodeConstants.EMAIL_IS_EXIST.value.to_app_api_exception() if user.username == username: raise ExceptionCodeConstants.USERNAME_IS_EXIST.value.to_app_api_exception() - if user.nick_name == nick_name: - raise ExceptionCodeConstants.NICKNAME_IS_EXIST.value.to_app_api_exception() class Query(serializers.Serializer): username = serializers.CharField( @@ -411,12 +408,6 @@ class UserEditInstance(serializers.Serializer): def is_valid(self, *, user_id=None, raise_exception=False): super().is_valid(raise_exception=True) self._check_unique_email(user_id) - 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')) def _check_unique_email(self, user_id): email = self.data.get('email') From 8f19c5ef4d1da574f6cdc4247abfd8d9ad9f3a7e Mon Sep 17 00:00:00 2001 From: Binx Date: Thu, 2 Jul 2026 00:16:53 +0800 Subject: [PATCH 2/3] Revert "fix: Remove uniqueness constraint from user nickname" This reverts commit 63eb7e08a8455cf444943c8de253ca2e09174f5f. --- apps/local_model/models/user.py | 2 +- apps/users/models/user.py | 2 +- apps/users/serializers/user.py | 11 ++++++++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/apps/local_model/models/user.py b/apps/local_model/models/user.py index d68130c242c..0f480d89fde 100644 --- a/apps/local_model/models/user.py +++ b/apps/local_model/models/user.py @@ -17,7 +17,7 @@ class User(models.Model): id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") email = models.EmailField(unique=True, null=True, blank=True, verbose_name="邮箱", db_index=True) phone = models.CharField(max_length=20, verbose_name="电话", default="", db_index=True) - nick_name = models.CharField(max_length=150, verbose_name="昵称", db_index=True) + nick_name = models.CharField(max_length=150, verbose_name="昵称", unique=True, db_index=True) username = models.CharField(max_length=150, unique=True, verbose_name="用户名", db_index=True) password = models.CharField(max_length=150, verbose_name="密码") role = models.CharField(max_length=150, verbose_name="角色") diff --git a/apps/users/models/user.py b/apps/users/models/user.py index d68130c242c..0f480d89fde 100644 --- a/apps/users/models/user.py +++ b/apps/users/models/user.py @@ -17,7 +17,7 @@ class User(models.Model): id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") email = models.EmailField(unique=True, null=True, blank=True, verbose_name="邮箱", db_index=True) phone = models.CharField(max_length=20, verbose_name="电话", default="", db_index=True) - nick_name = models.CharField(max_length=150, verbose_name="昵称", db_index=True) + nick_name = models.CharField(max_length=150, verbose_name="昵称", unique=True, db_index=True) username = models.CharField(max_length=150, unique=True, verbose_name="用户名", db_index=True) password = models.CharField(max_length=150, verbose_name="密码") role = models.CharField(max_length=150, verbose_name="角色") diff --git a/apps/users/serializers/user.py b/apps/users/serializers/user.py index eaff28e8c05..e49291e1bb9 100644 --- a/apps/users/serializers/user.py +++ b/apps/users/serializers/user.py @@ -220,12 +220,15 @@ def is_valid(self, *, raise_exception=True): def _check_unique_username_and_email(self): username = self.data.get('username') email = self.data.get('email') - user = User.objects.filter(Q(username=username) | Q(email=email)).first() + nick_name = self.data.get('nick_name') + user = User.objects.filter(Q(username=username) | Q(email=email) | Q(nick_name=nick_name)).first() if user: if user.email == email: raise ExceptionCodeConstants.EMAIL_IS_EXIST.value.to_app_api_exception() if user.username == username: raise ExceptionCodeConstants.USERNAME_IS_EXIST.value.to_app_api_exception() + if user.nick_name == nick_name: + raise ExceptionCodeConstants.NICKNAME_IS_EXIST.value.to_app_api_exception() class Query(serializers.Serializer): username = serializers.CharField( @@ -408,6 +411,12 @@ class UserEditInstance(serializers.Serializer): def is_valid(self, *, user_id=None, raise_exception=False): super().is_valid(raise_exception=True) self._check_unique_email(user_id) + 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')) def _check_unique_email(self, user_id): email = self.data.get('email') From f5517ebc1280f2b2f431f4e27a0ac987979fb889 Mon Sep 17 00:00:00 2001 From: Binx Date: Sat, 4 Jul 2026 14:05:52 +0800 Subject: [PATCH 3/3] feat: show creator for agent API keys --- .../application/models/application_api_key.py | 2 ++ .../serializers/application_api_key.py | 24 +++++++++++++++++-- apps/application/views/application_api_key.py | 3 ++- .../component/APIKeyDialog.vue | 5 ++++ 4 files changed, 31 insertions(+), 3 deletions(-) diff --git a/apps/application/models/application_api_key.py b/apps/application/models/application_api_key.py index a605bb00183..06c9e4b522a 100644 --- a/apps/application/models/application_api_key.py +++ b/apps/application/models/application_api_key.py @@ -20,6 +20,8 @@ class ApplicationApiKey(AppModelMixin): , default=list) expire_time = models.DateTimeField(verbose_name="过期时间", default=timezone.now) is_permanent = models.BooleanField(default=True, verbose_name="是否永久") + user = models.ForeignKey("users.User", on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True, + verbose_name="创建用户") class Meta: db_table = "application_api_key" diff --git a/apps/application/serializers/application_api_key.py b/apps/application/serializers/application_api_key.py index 88b4a5591ad..b0818e6f0a0 100644 --- a/apps/application/serializers/application_api_key.py +++ b/apps/application/serializers/application_api_key.py @@ -1,6 +1,7 @@ import hashlib import uuid_utils.compat as uuid +from django.core.exceptions import ObjectDoesNotExist from django.db.models import QuerySet from django.utils import timezone from django.utils.translation import gettext_lazy as _ @@ -14,10 +15,26 @@ class ApplicationKeySerializerModel(serializers.ModelSerializer): + user = serializers.SerializerMethodField() + class Meta: model = ApplicationApiKey fields = "__all__" + @staticmethod + def get_user(obj): + if not obj.user_id: + return None + try: + user = obj.user + except ObjectDoesNotExist: + return None + return { + "id": obj.user_id, + "username": user.username, + "nick_name": user.nick_name, + } + class EditApplicationKeySerializer(serializers.Serializer): is_active = serializers.BooleanField(required=False, label=_("Availability")) @@ -36,6 +53,7 @@ class EditApplicationKeySerializer(serializers.Serializer): class ApplicationKeySerializer(serializers.Serializer): workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("Workspace ID")) application_id = serializers.UUIDField(required=True, label=_('application id')) + user_id = serializers.UUIDField(required=False, allow_null=True, label=_('user id')) order_by = serializers.CharField(required=False, label=_('order by'), allow_null=True, allow_blank=True) def is_valid(self, *, raise_exception=False): @@ -51,10 +69,12 @@ def generate(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) application_id = self.data.get("application_id") + user_id = self.data.get("user_id") secret_key = 'agent-' + hashlib.md5(str(uuid.uuid7()).encode()).hexdigest() application_api_key = ApplicationApiKey(id=uuid.uuid7(), secret_key=secret_key, - application_id=application_id) + application_id=application_id, + user_id=user_id) application_api_key.save() return ApplicationKeySerializerModel(application_api_key).data @@ -62,7 +82,7 @@ def page(self, current_page: int, page_size: int, with_valid=True): if with_valid: self.is_valid(raise_exception=True) application_id = self.data.get("application_id") - query_set = QuerySet(ApplicationApiKey).filter(application_id=application_id) + query_set = QuerySet(ApplicationApiKey).filter(application_id=application_id).select_related('user') order_by = '-create_time' if self.data.get('order_by') is None or self.data.get( 'order_by') == '' else self.data.get('order_by') query_set = query_set.order_by(order_by) diff --git a/apps/application/views/application_api_key.py b/apps/application/views/application_api_key.py index 213c8fe7221..63a1fa98bf8 100644 --- a/apps/application/views/application_api_key.py +++ b/apps/application/views/application_api_key.py @@ -49,7 +49,8 @@ class ApplicationKey(APIView): def post(self, request: Request, workspace_id: str, application_id: str): return result.success(ApplicationKeySerializer( data={'application_id': application_id, - 'workspace_id': workspace_id}).generate()) + 'workspace_id': workspace_id, + 'user_id': request.user.id}).generate()) class Page(APIView): authentication_classes = [TokenAuth] diff --git a/ui/src/views/application-overview/component/APIKeyDialog.vue b/ui/src/views/application-overview/component/APIKeyDialog.vue index 0c7ceb0cf5a..585fb07dfdc 100644 --- a/ui/src/views/application-overview/component/APIKeyDialog.vue +++ b/ui/src/views/application-overview/component/APIKeyDialog.vue @@ -81,6 +81,11 @@ + + +