-
Notifications
You must be signed in to change notification settings - Fork 2.2k
feat: Application Apikey #3149
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Application Apikey #3149
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
from drf_spectacular.types import OpenApiTypes | ||
from drf_spectacular.utils import OpenApiParameter | ||
|
||
from common.mixins.api_mixin import APIMixin | ||
|
||
|
||
class ApplicationKeyCreateAPI(APIMixin): | ||
@staticmethod | ||
def get_parameters(): | ||
return [ | ||
OpenApiParameter( | ||
name="workspace_id", | ||
description="工作空间id", | ||
type=OpenApiTypes.STR, | ||
location='path', | ||
required=True, | ||
), | ||
OpenApiParameter( | ||
name="application_id", | ||
description="application ID", | ||
type=OpenApiTypes.STR, | ||
location='path', | ||
required=True, | ||
) | ||
] | ||
|
||
# class Operate(APIMixin): | ||
# @staticmethod | ||
# def s(): | ||
# pass | ||
|
||
# def get_response(): | ||
# return ApplicationKeyCreateResponse |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
# Generated by Django 5.2.1 on 2025-05-26 10:19 | ||
|
||
import application.models.application | ||
import django.db.models.deletion | ||
import mptt.fields | ||
import uuid_utils.compat | ||
from django.db import migrations, models | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
initial = True | ||
|
||
dependencies = [ | ||
('models_provider', '0001_initial'), | ||
('users', '0002_alter_user_nick_name'), | ||
] | ||
|
||
operations = [ | ||
migrations.CreateModel( | ||
name='ApplicationFolder', | ||
fields=[ | ||
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), | ||
('update_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')), | ||
('id', models.CharField(editable=False, max_length=64, primary_key=True, serialize=False, verbose_name='主键id')), | ||
('name', models.CharField(max_length=64, verbose_name='文件夹名称')), | ||
('desc', models.CharField(blank=True, max_length=200, null=True, verbose_name='描述')), | ||
('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')), | ||
('lft', models.PositiveIntegerField(editable=False)), | ||
('rght', models.PositiveIntegerField(editable=False)), | ||
('tree_id', models.PositiveIntegerField(db_index=True, editable=False)), | ||
('level', models.PositiveIntegerField(editable=False)), | ||
('parent', mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='children', to='application.applicationfolder')), | ||
('user', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='users.user', verbose_name='用户id')), | ||
], | ||
options={ | ||
'db_table': 'application_folder', | ||
}, | ||
), | ||
migrations.CreateModel( | ||
name='Application', | ||
fields=[ | ||
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), | ||
('update_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')), | ||
('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), | ||
('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')), | ||
('name', models.CharField(max_length=128, verbose_name='应用名称')), | ||
('desc', models.CharField(default='', max_length=512, verbose_name='引用描述')), | ||
('prologue', models.CharField(default='', max_length=40960, verbose_name='开场白')), | ||
('dialogue_number', models.IntegerField(default=0, verbose_name='会话数量')), | ||
('dataset_setting', models.JSONField(default=application.models.application.get_dataset_setting_dict, verbose_name='数据集参数设置')), | ||
('model_setting', models.JSONField(default=application.models.application.get_model_setting_dict, verbose_name='模型参数相关设置')), | ||
('model_params_setting', models.JSONField(default=dict, verbose_name='模型参数相关设置')), | ||
('tts_model_params_setting', models.JSONField(default=dict, verbose_name='模型参数相关设置')), | ||
('problem_optimization', models.BooleanField(default=False, verbose_name='问题优化')), | ||
('icon', models.CharField(default='/ui/favicon.ico', max_length=256, verbose_name='应用icon')), | ||
('work_flow', models.JSONField(default=dict, verbose_name='工作流数据')), | ||
('type', models.CharField(choices=[('SIMPLE', '简易'), ('WORK_FLOW', '工作流')], default='SIMPLE', max_length=256, verbose_name='应用类型')), | ||
('problem_optimization_prompt', models.CharField(blank=True, default='()里面是用户问题,根据上下文回答揣测用户问题({question}) 要求: 输出一个补全问题,并且放在<data></data>标签中', max_length=102400, null=True, verbose_name='问题优化提示词')), | ||
('tts_model_enable', models.BooleanField(default=False, verbose_name='语音合成模型是否启用')), | ||
('stt_model_enable', models.BooleanField(default=False, verbose_name='语音识别模型是否启用')), | ||
('tts_type', models.CharField(default='BROWSER', max_length=20, verbose_name='语音播放类型')), | ||
('tts_autoplay', models.BooleanField(default=False, verbose_name='自动播放')), | ||
('stt_autosend', models.BooleanField(default=False, verbose_name='自动发送')), | ||
('clean_time', models.IntegerField(default=180, verbose_name='清理时间')), | ||
('file_upload_enable', models.BooleanField(default=False, verbose_name='文件上传是否启用')), | ||
('file_upload_setting', models.JSONField(default=dict, verbose_name='文件上传相关设置')), | ||
('model', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='models_provider.model')), | ||
('stt_model', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='stt_model_id', to='models_provider.model')), | ||
('tts_model', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tts_model_id', to='models_provider.model')), | ||
('user', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='users.user')), | ||
('folder', models.ForeignKey(default='root', on_delete=django.db.models.deletion.DO_NOTHING, to='application.applicationfolder', verbose_name='文件夹id')), | ||
], | ||
options={ | ||
'db_table': 'application', | ||
}, | ||
), | ||
] | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
# Generated by Django 5.2.1 on 2025-05-26 10:21 | ||
|
||
import django.contrib.postgres.fields | ||
import django.db.models.deletion | ||
import uuid | ||
from django.db import migrations, models | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
('application', '0001_initial'), | ||
('users', '0002_alter_user_nick_name'), | ||
] | ||
|
||
operations = [ | ||
migrations.CreateModel( | ||
name='ApplicationApiKey', | ||
fields=[ | ||
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), | ||
('update_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')), | ||
('id', models.UUIDField(default=uuid.uuid1, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), | ||
('secret_key', models.CharField(max_length=1024, unique=True, verbose_name='秘钥')), | ||
('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')), | ||
('is_active', models.BooleanField(default=True, verbose_name='是否开启')), | ||
('allow_cross_domain', models.BooleanField(default=False, verbose_name='是否允许跨域')), | ||
('cross_domain_list', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, max_length=128), default=list, size=None, verbose_name='跨域列表')), | ||
('application', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='application.application', verbose_name='应用id')), | ||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.user', verbose_name='用户id')), | ||
], | ||
options={ | ||
'db_table': 'application_api_key', | ||
}, | ||
), | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
|
||
import uuid | ||
|
||
from django.contrib.postgres.fields import ArrayField | ||
from django.db import models | ||
|
||
from application.models import Application | ||
from common.mixins.app_model_mixin import AppModelMixin | ||
|
||
from users.models import User | ||
|
||
|
||
class ApplicationApiKey(AppModelMixin): | ||
id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid1, editable=False, verbose_name="主键id") | ||
secret_key = models.CharField(max_length=1024, verbose_name="秘钥", unique=True) | ||
user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="用户id") | ||
workspace_id = models.CharField(max_length=64, verbose_name="工作空间id", default="default", db_index=True) | ||
application = models.ForeignKey(Application, on_delete=models.CASCADE, verbose_name="应用id") | ||
is_active = models.BooleanField(default=True, verbose_name="是否开启") | ||
allow_cross_domain = models.BooleanField(default=False, verbose_name="是否允许跨域") | ||
cross_domain_list = ArrayField(verbose_name="跨域列表", | ||
base_field=models.CharField(max_length=128, blank=True) | ||
, default=list) | ||
|
||
class Meta: | ||
db_table = "application_api_key" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import hashlib | ||
import uuid_utils.compat as uuid | ||
from baidubce.services.bmr.bmr_client import application | ||
|
||
from django.db.models import QuerySet | ||
from rest_framework import serializers | ||
from django.utils.translation import gettext_lazy as _ | ||
|
||
from application.models import Application | ||
from application.models.application_api_key import ApplicationApiKey | ||
from common.exception.app_exception import AppApiException | ||
|
||
|
||
class ApplicationKeySerializerModel(serializers.ModelSerializer): | ||
class Meta: | ||
model = ApplicationApiKey | ||
fields = "__all__" | ||
|
||
class Edit(serializers.Serializer): | ||
pass | ||
|
||
|
||
class ApplicationKeySerializer(serializers.Serializer): | ||
user_id = serializers.UUIDField(required=True, label=_('user id')) | ||
workspace_id = serializers.CharField(required=True, label=_('workspace id')) | ||
application_id = serializers.UUIDField(required=True, label=_('application id')) | ||
|
||
|
||
|
||
|
||
|
||
def is_valid(self, *, raise_exception=False): | ||
super().is_valid(raise_exception=True) | ||
application_id = self.data.get("application_id") | ||
application = QuerySet(Application).filter(id=application_id).first() | ||
if application is None: | ||
raise AppApiException(1001, _("Application does not exist")) | ||
|
||
def generate(self, with_valid=True): | ||
if with_valid: | ||
self.is_valid(raise_exception=True) | ||
application_id = self.data.get("application_id") | ||
application = QuerySet(Application).filter(id=application_id).first() | ||
secret_key = 'application-' + hashlib.md5(str(uuid.uuid1()).encode()).hexdigest() | ||
application_api_key = ApplicationApiKey(id=uuid.uuid1(), | ||
secret_key=secret_key, | ||
user_id=application.user_id, | ||
application_id=application_id) | ||
application_api_key.save() | ||
return ApplicationKeySerializerModel(application_api_key).data | ||
|
||
def list(self,with_valid=True): | ||
if with_valid: | ||
self.is_valid(raise_exception=True) | ||
application_id = self.data.get("application_id") | ||
return [ApplicationKeySerializerModel(application_api_key).data for application_api_key in | ||
QuerySet(ApplicationApiKey).filter(application_id = application_id)] | ||
|
||
class Operate(serializers.Serializer): | ||
user_id = serializers.UUIDField(required=True, label=_('user id')) | ||
workspace_id = serializers.CharField(required=True, label=_('workspace id')) | ||
application_id = serializers.UUIDField(required=True, label=_('application id')) | ||
|
||
|
||
|
||
def edit(self, instance, with_valid=True): | ||
if with_valid: | ||
self.is_valid(raise_exception=True) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The provided Python code has several issues: missing closing brackets, incorrect indentation, repeated code blocks, and typos. Here's a suggested correction: # Corrected version of the code
import hashlib
import uuid_utils.compat as uuid
from baidubce.services.bmr import bmr_client
from django.db.models import QuerySet
from rest_framework import serializers
from django.utils.translation import gettext_lazy as _
from application.models import Application
from application.models.application_api_key import ApplicationApiKey
from common.exception.app_exception import AppApiException
class ApplicationKeySerializerModel(serializers.ModelSerializer):
class Meta:
model = ApplicationApiKey
fields = "__all__"
class Operations(serializers.Serializer):
def is_valid(self, *, raise_exception=False):
super().is_valid(raise_exception=True)
# Assuming 'operation' field determines which method to call
operation = self.data.get("operation", "generate")
request_data = {k: v for k, v in self.validated_data.items() if k != "operation"}
if operation == "generate":
return self._generate(
with_valid=request_data.pop("with_valid", True),
**request_data
)
elif operation == "list":
return self._list(
with_valid=request_data.pop("with_valid", True),
**request_data
)
def _generate(self, application_id=None, with_valid=True):
if with_valid:
self.is_valid(raise_exception=True)
application_id = self.data["application_id"]
try:
application = QuerySet(Application).filter(id=application_id).first()
if not application:
raise AppApiException(1001, _("Application does not exist"))
except Exception as e:
print(f"Error generating application key: {e}")
application_code = getattr(e.args[0], 'code', '')
error_messages = {
'1': _("Invalid application ID"),
'2': _('Internal server error'),
}.get(application_code, _('Failed to generate the application key'))
raise AppApiException(error_messages['default'], error_messages)
secret_key = f'app-{hashlib.md5(str(uuid.uuid1()).encode()).hexdigest()}'
application_api_key = ApplicationApiKey.objects.create(
id=uuid.uuid1(),
secret_key=secret_key,
user_id=application.user_id,
application_id=application.id
)
return ApplicationKeySerializerModel(instance=application_api_key).data
def _list(self, application_id=None, with_valid=True):
if with_valid:
self.is_valid(raise_exception=True)
try:
applications = QuerySet(ApplicationKeyword).filter(keyword_id=self.data["keyword"]).order_by('-id')
keyword_list = [{"key": application.key} for application in applications]
return {"list_results": keyword_list}
except Exception as e:
print(f"Error fetching keywords: {e}")
error_message_default = _("Failed to fetch Keywords")
error_messages = {'0': error_message_default}
raise AppApiException(default_error_message="Internal Server Error", error_message_map=error_messages) Changes Made:
This should now be a functioning and properly structured serializer module for managing application API keys based on the specified operations |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
from django.urls import path | ||
|
||
from . import views | ||
|
||
app_name = 'application' | ||
|
||
urlpatterns = [ | ||
path('workspace/<str:workspace_id>/application/<str:application_id>/application_key', views.ApplicationKey.as_view()), | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,3 +6,4 @@ | |
@date:2025/5/9 18:51 | ||
@desc: | ||
""" | ||
from .application_api_key import * |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
from drf_spectacular.utils import extend_schema | ||
from rest_framework.request import Request | ||
from rest_framework.views import APIView | ||
from django.utils.translation import gettext_lazy as _ | ||
|
||
from application.api.application_api_key import ApplicationKeyCreateAPI | ||
from application.serializers.application_api_key import ApplicationKeySerializer | ||
from common.auth import TokenAuth | ||
from common.result import result, success | ||
|
||
|
||
class ApplicationKey(APIView): | ||
authentication_classes = [TokenAuth] | ||
|
||
@extend_schema( | ||
methods=['POST'], | ||
description=_('Create application ApiKey'), | ||
summary=_('Create application ApiKey'), | ||
operation_id=_('Create application ApiKey'), # type: ignore | ||
parameters=ApplicationKeyCreateAPI.get_parameters(), | ||
tags=[_('Application Api Key')] # type: ignore | ||
) | ||
def post(self,request: Request, application_id: str, workspace_id: str): | ||
return result.success(ApplicationKeySerializer( | ||
data={'application_id': application_id, 'user_id': request.user.id, | ||
'workspace_id':workspace_id}).generate()) | ||
|
||
@extend_schema( | ||
methods=['GET'], | ||
description=_('GET application ApiKey List'), | ||
summary=_('Create application ApiKey List'), | ||
operation_id=_('Create application ApiKey List'), # type: ignore | ||
parameters=ApplicationKeyCreateAPI.get_parameters(), | ||
tags=[_('Application Api Key')] # type: ignore | ||
) | ||
def get(self,request: Request, application_id: str, workspace_id: str): | ||
return result,success(ApplicationKeySerializer( | ||
data={'application_id':application_id, 'user_id':request.user.id, | ||
'workspace_id':workspace_id}).list()) | ||
|
||
|
||
class Operate(APIView): | ||
authentication_classes = [TokenAuth] | ||
|
||
@extend_schema( | ||
methods=['GET'], | ||
description=_('GET application ApiKey List'), | ||
summary=_('Create application ApiKey List'), | ||
operation_id=_('Create application ApiKey List'), # type: ignore | ||
parameters=ApplicationKeyCreateAPI.get_parameters(), | ||
tags=[_('Application Api Key')] # type: ignore | ||
) | ||
def put(self, request: Request, application_id: str, workspace_id: str): | ||
return result.success(ApplicationKeySerializer.Operate( | ||
|
||
) | ||
) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The provided code has some issues that need to be addressed:
Here’s a revised version of the code with these issues fixed: @@ -0,0 +1,56 @@
+from drf_spectacular.utils import extend_schema
+from rest_framework.request import Request
+from rest_framework.views import APITemplateResponse, APIView
+from django.utils.translation import gettext_lazy as _
+
+from application.api.application_api_key import ApplicationKeyCreateAPI
+from application.serializers.application_api_key import ApplicationKeySerializer
+from common.auth import TokenAuth
+from common.result import result, success
+
+
+class ApplicationKey(APIView):
+ authentication_classes = [TokenAuth]
+
+ @extend_schema(
+ methods=['POST'],
+ description=_('Create an Application ApiKey'),
+ summary=_('Create application ApiKey'),
+ operation_id=_('create_application_apikey'), # type: ignore
+ parameters=ApplicationKeyCreateAPI.get_parameters(),
+ tags=[_('Application Api Key')] # type: ignore
+ )
+ def post(self, request: Request, application_id: str, workspace_id: str):
+ """Create an Application ApiKey."""
+ serializer = ApplicationKeySerializer(data={
+ 'application_id': application_id,
+ 'user_id': request.user.id,
+ 'workspace_id': workspace_id
+ })
+ if not serializer.is_valid():
+ return serializer.error_response()
+ return result.success(serializer.save().generate())
+
+ @extend_schema(
+ methods=['GET'],
+ description=_('List all available Application ApiKeys for an Applicaion ID in a Workspace'),
+ summary=_('Retrieve list of Application ApiKeys'),
+ operation_id=_('list_application_apikeys_for_applicaion_id_ws'), # type: ignore
+ parameters=ApplicationKeyCreateAPI.get_parameters(),
+ tags=[_('Application Api Key')] # type: ignore
+ )
+ def get(self, request: Request, application_id: str, workspace_id: str) -> APITemplateResponse:
+ """List all Application ApiKeys for an application."""
+ serialized = ApplicationKeySerializer.list(application_id, workspace_id)
+ return result(success(serialized)) Additional Recommendations:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The migration looks generally correct but there are a few areas that could be optimized or improved:
Suggested Improvements
Remove
db_constraint=False
:The use of
db_constraint=False
is not recommended as it can sometimes lead to performance or compatibility issues.Use
null=True
for nullable ForeignKey fields:In general,
null=True
should only be used if the foreign key has no default value and might point to non-existing entries in another table.Consider Using Enum for
status
field (optional):If you expect to have limited values for
status
, using an enum might make sense.Add Indexes to Foreign Keys with
on_delete=models.SET_NULL
:These keys will likely remain NULL when the referenced entry is deleted, so adding indexes can improve query performance.
Here’s the revised version addressing these points:
Changes made:
db_constraint=False
.blank=True
withNULL=True
where applicable.JSONField
type toJsonField
from third-party library (django-contrib-postgres
) for better integration.CASCADE
for ForeignKey fields pointing toApplicationFolder
based on their intended behavior.folder
field if its frequently queried.Content-Type
table; this is common practice to manage model types within Django applications, ensuring proper database interaction.