-
Notifications
You must be signed in to change notification settings - Fork 2.1k
feat: implement CRUD operations for tools with API views #2925
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
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 |
---|---|---|
|
@@ -3,6 +3,7 @@ | |
|
||
import uuid_utils.compat as uuid | ||
from django.core import validators | ||
from django.db.models import QuerySet | ||
from django.utils.translation import gettext_lazy as _ | ||
from rest_framework import serializers | ||
|
||
|
@@ -30,6 +31,17 @@ class ToolInputField(serializers.Serializer): | |
]) | ||
|
||
|
||
class InitField(serializers.Serializer): | ||
field = serializers.CharField(required=True, label=_('field name')) | ||
label = serializers.CharField(required=True, label=_('field label')) | ||
required = serializers.BooleanField(required=True, label=_('required')) | ||
input_type = serializers.CharField(required=True, label=_('input type')) | ||
default_value = serializers.CharField(required=False, allow_null=True, allow_blank=True) | ||
show_default_value = serializers.BooleanField(required=False, default=False) | ||
props_info = serializers.DictField(required=False, default=dict) | ||
attrs = serializers.DictField(required=False, default=dict) | ||
|
||
|
||
class ToolCreateRequest(serializers.Serializer): | ||
name = serializers.CharField(required=True, label=_('tool name')) | ||
|
||
|
@@ -38,9 +50,10 @@ class ToolCreateRequest(serializers.Serializer): | |
|
||
code = serializers.CharField(required=True, label=_('tool content')) | ||
|
||
input_field_list = serializers.ListField(child=ToolInputField(), required=False, label=_('input field list')) | ||
input_field_list = serializers.ListField(child=ToolInputField(), required=False, default=list, | ||
label=_('input field list')) | ||
|
||
init_field_list = serializers.ListField(required=False, default=list, label=_('init field list')) | ||
init_field_list = serializers.ListField(child=InitField(), required=False, default=list, label=_('init field list')) | ||
|
||
is_active = serializers.BooleanField(required=False, label=_('Is active')) | ||
|
||
|
@@ -50,6 +63,7 @@ class ToolCreateRequest(serializers.Serializer): | |
class ToolSerializer(serializers.Serializer): | ||
class Create(serializers.Serializer): | ||
user_id = serializers.UUIDField(required=True, label=_('user id')) | ||
workspace_id = serializers.UUIDField(required=True, label=_('workspace id')) | ||
|
||
def insert(self, instance, with_valid=True): | ||
if with_valid: | ||
|
@@ -66,3 +80,32 @@ def insert(self, instance, with_valid=True): | |
is_active=False) | ||
tool.save() | ||
return ToolModelSerializer(tool).data | ||
|
||
class Operate(serializers.Serializer): | ||
id = serializers.UUIDField(required=True, label=_('tool id')) | ||
workspace_id = serializers.CharField(required=True, label=_('workspace id')) | ||
|
||
def edit(self, instance, with_valid=True): | ||
if with_valid: | ||
self.is_valid(raise_exception=True) | ||
ToolCreateRequest(data=instance).is_valid(raise_exception=True) | ||
if not QuerySet(Tool).filter(id=self.data.get('id')).exists(): | ||
raise serializers.ValidationError(_('Tool not found')) | ||
|
||
edit_field_list = ['name', 'desc', 'code', 'icon', 'input_field_list', 'init_field_list', 'init_params', | ||
'is_active'] | ||
edit_dict = {field: instance.get(field) for field in edit_field_list if ( | ||
field in instance and instance.get(field) is not None)} | ||
|
||
QuerySet(Tool).filter(id=self.data.get('id')).update(**edit_dict) | ||
|
||
return self.one() | ||
|
||
def delete(self): | ||
self.is_valid(raise_exception=True) | ||
QuerySet(Tool).filter(id=self.data.get('id')).delete() | ||
|
||
def one(self): | ||
self.is_valid(raise_exception=True) | ||
tool = QuerySet(Tool).filter(id=self.data.get('id')).first() | ||
return ToolModelSerializer(tool).data | ||
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 Django REST framework serializers seem mostly well-structured but has a few improvements that can be made:
Here are some specific improvement suggestions: Updated Serializer Codefrom django import forms
from django.core.exceptions import ValidationError
import uuid_utils.compat as uuid
from django.core.validators import MinValueValidator, MaxValueValidator
from rest_framework import parsers, renderers, status
from rest_framework.decorators import api_view, permission_classes, authentication_classes
from drf_yasg.utils import swagger_auto_schema
from drf_yasg.openapi import Response, Schema, Parameter
# Assuming tools module contains models.py with defined Tool and ToolModelSerializer classes
from common.tools.models import Tool
class InitField(serializers.Serializer):
field = serializers.CharField(required=True, label=_('field name'), validators=[MinLengthValidator(5)])
label = serializers.CharField(required=True, label=_('field label'))
required = serializers.BooleanField(required=True, label=_('required'))
input_type = serializers.CharField(required=True, label=_('input type'))
default_value = serializers.CharField(required=False, null=True, allow_blank=True)
show_default_value = serializers.BooleanField(default=False, required=False, label=_('show default value'))
props_info = serializers.DictField(required=False, default=dict)
attrs = serializers.DictField(required=False, default=dict)
class ToolCreateRequest(serializers.ModelSerializer):
class Meta:
model = Tool
fields = [
'name',
'desc',
'code',
'icon'
]
input_field_list = serializers.ListField(child=ToolInputField(), required=False)
init_field_list = serializers.ListField(child=InitField(), required=False)
@api_view(['POST'])
@permission_classes([])
@authentication_classes([])
@swagger_auto_schema(
request_body=ToolCreateRequest(),
responses={status.HTTP_200_OK: Response(description="Success")},
operation_summary="Create Tool",
)
def create_tool(request):
serializer = ToolCreateRequest(data=request.data)
try:
validated_data = serializer.validated_data
tool_instance = Tool.objects.create(**validated_data)
# Additional logic or processing here
tool_model_serializer = ToolModelSerializer(tool_instance)
return Response(tool_model_serializer.data, status=status.HTTP_200_OK)
except IntegrityError as e:
return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
@api_view(['PUT', 'DELETE']) # Assuming we will have these endpoints
@permission_classes([])
@authentication_classes([])
@swagger_auto_schema(
methods=['put'],
request_body=forms.ModelForm(fields=[]),
manual_parameters=[
Parameter(name='tool_id', in_='path', required=True, description='Tool ID'),
Parameter(name='workspace_id', in_='query', required=True, description='Workspace ID')
],
responses={
**{str(status_code): Response(schema=Schema(title=title)) for status_code,title in {
status.HTTP_200_OK: "Success",
status.HTTP_204_NO_CONTENT:"No Content"
} .items()}
},
operation_summary="Operate Tool",
)
def operate_tool(request, tool_id=None):
if request.method == 'PUT':
serializer = OperationSerializer(instance=Tool.objects.get(id=tool_id), data=request.data)
serializer.is_valid(raise_exception=True)
serializer.edit(with_valid=True)
return Response(serializer.data, status=status.HTTP_200_OK)
elif request.method == 'DELETE':
serializer = OperationSerializer(data={'id': tool_id})
serializer.is_valid(raise_exception=True)
serializer.delete()
return Response({}, status=status.HTTP_204_NO_CONTENT)
class OperationSerializer(serializers.Serializer):
id = serializers.UUIDField(required=True)
workspace_id = serializers.CharField(required=True)
def edit(self, with_valid=True):
if with_valid:
self.is_valid(raise_exception=True)
required_fields = ['name', 'desc', 'code', 'icon', 'input_field_list', 'init_field_list', 'is_active']
optional_fields = {'init_params'}
payload = {}
for field in required_fields + list(optional_fields.intersection(set(request.POST.keys()))):
if field in payload:
continue
if with_valid and (payload.get(field) is None and not isinstance(payload.get(field))
in [None,str,bool,int,float,bool,None]):
raise ValueError(f"Required parameter {field} missing")
payload[str(field)] = request.POST.get(str(field))
existing_tool = Tool.objects.get(pk=tool_id)
existing_tool.name = payload.pop('name') if payload.get('name') else existing_tool.name
existing_tool.desc = payload.pop('desc')if payload.get('desc')else existing_tool.desc
existing_tool.code = payload.pop('code')if payload.get('code') is not None else existing_tool.code
existing_tool.icon = payload.pop('icon')if payload.get('icon') is not None else existing_tool.icon
existing_tool.input_field_list = payload.pop('input_field_list') if payload.get('input_field_list') else existing_tool.input_field_list
existing_tool.init_field_list = payload.pop('init_field_list') if payload.get('init_field_list') else existing_tool.init_field_list
existing_tool.is_active = 'true'.lower() if payload.get('is_active').lower() in ('true','yes') else False
existing_tool.save()
return OperationSerializer(existing_tool).data
def delete(self):
query_set = Tool.objects.filter(id=self.id)
if query_set.count():
query_set.delete() These updates introduce new views and serializers ( |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,22 +7,62 @@ | |
from common.auth.authentication import has_permissions | ||
from common.constants.permission_constants import PermissionConstants | ||
from common.result import result | ||
from tools.api.tool import ToolCreateAPI | ||
from tools.api.tool import ToolCreateAPI, ToolEditAPI, ToolReadAPI, ToolDeleteAPI | ||
from tools.serializers.tool import ToolSerializer | ||
|
||
|
||
class ToolCreateView(APIView): | ||
authentication_classes = [TokenAuth] | ||
|
||
@extend_schema(methods=['POST'], | ||
description=_('Create tool'), | ||
operation_id=_('Create tool'), | ||
request=ToolCreateAPI.get_request(), | ||
responses=ToolCreateAPI.get_response(), | ||
tags=[_('Tool')]) | ||
@has_permissions(PermissionConstants.TOOL_CREATE) | ||
# @log(menu='Tool', operate="Create tool", | ||
# get_operation_object=lambda r, k: r.data.get('name')) | ||
def post(self, request: Request, workspace_id: str): | ||
print(workspace_id) | ||
return result.success(ToolSerializer.Create(data={'user_id': request.user.id}).insert(request.data)) | ||
class ToolView(APIView): | ||
class Create(APIView): | ||
authentication_classes = [TokenAuth] | ||
|
||
@extend_schema(methods=['POST'], | ||
description=_('Create tool'), | ||
operation_id=_('Create tool'), | ||
parameters=ToolCreateAPI.get_parameters(), | ||
request=ToolCreateAPI.get_request(), | ||
responses=ToolCreateAPI.get_response(), | ||
tags=[_('Tool')]) | ||
@has_permissions(PermissionConstants.TOOL_CREATE.get_workspace_permission()) | ||
def post(self, request: Request, workspace_id: str): | ||
return result.success(ToolSerializer.Create( | ||
data={'user_id': request.user.id, 'workspace_id': workspace_id} | ||
).insert(request.data)) | ||
|
||
class Operate(APIView): | ||
authentication_classes = [TokenAuth] | ||
|
||
@extend_schema(methods=['PUT'], | ||
description=_('Update tool'), | ||
operation_id=_('Update tool'), | ||
parameters=ToolEditAPI.get_parameters(), | ||
request=ToolEditAPI.get_request(), | ||
responses=ToolEditAPI.get_response(), | ||
tags=[_('Tool')]) | ||
@has_permissions(PermissionConstants.TOOL_EDIT.get_workspace_permission()) | ||
def put(self, request: Request, workspace_id: str, tool_id: str): | ||
return result.success(ToolSerializer.Operate( | ||
data={'id': tool_id, 'workspace_id': workspace_id} | ||
).edit(request.data)) | ||
|
||
@extend_schema(methods=['GET'], | ||
description=_('Update tool'), | ||
operation_id=_('Update tool'), | ||
parameters=ToolReadAPI.get_parameters(), | ||
responses=ToolReadAPI.get_response(), | ||
tags=[_('Tool')]) | ||
@has_permissions(PermissionConstants.TOOL_READ.get_workspace_permission()) | ||
def get(self, request: Request, workspace_id: str, tool_id: str): | ||
return result.success(ToolSerializer.Operate( | ||
data={'id': tool_id, 'workspace_id': workspace_id} | ||
).one()) | ||
|
||
@extend_schema(methods=['DELETE'], | ||
description=_('Delete tool'), | ||
operation_id=_('Delete tool'), | ||
parameters=ToolDeleteAPI.get_parameters(), | ||
tags=[_('Tool')]) | ||
@has_permissions(PermissionConstants.TOOL_DELETE.get_workspace_permission()) | ||
def delete(self, request: Request, workspace_id: str, tool_id: str): | ||
return result.success(ToolSerializer.Operate( | ||
data={'id': tool_id, 'workspace_id': workspace_id} | ||
).delete()) | ||
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 refactor is mostly correct and well-organized. Here are some minor suggestions and improvements:
Here's the refactored code with these suggestions: # Import statements...
from tools.serializers.tool import (
ToolSerializer,
ToolCreateAPI,
ToolEditAPI,
ToolReadAPI,
ToolDeleteAPI
)
class ToolView(APIView):
class Create(APIView):
authentication_classes = [TokenAuth]
@extend_schema(methods=['POST'],
description=_('Create tool'),
operation_id=_('Create tool'),
parameters=[
Parameter(name='workspace_id', location='path', required=True, format=str),
Parameter(name='data', location='body', schema=ToolCreateAPI.get_request(), required=True)
],
request=ToolCreateAPI.get_request(),
responses=ToolCreateAPI.get_response(),
tags=[_('Tool')])
@has_permissions(PermissionConstants.TOOL_CREATE.get_workspace_permission())
def post(self, request: Request, workspace_id: str):
return result.success(
ToolSerializer.Create(
data={'user_id': request.user.id, 'workspace_id': workspace_id}
).insert(request.data)
)
class Operate(APIView):
authentication_classes = [TokenAuth]
@extend_schema(
methods=['PUT'],
description=_('Update tool'),
operation_id=_('Update tool'),
parameters=[
Parameter(name='workspace_id', location='path', required=True, format=str),
Parameter(name='tool_id', location='path', required=True, format=str),
Parameter(name='data', location='body', schema=ToolEditAPI.get_request(), required=True)
],
request=ToolEditAPI.get_request(),
responses=ToolEditAPI.get_response(),
tags=[_('Tool')])
@has_permissions(PermissionConstants.TOOL_EDIT.get_workspace_permission())
def put(self, request: Request, workspace_id: str):
return result.success(
ToolSerializer.Operate(
data={'id': None, 'workspace_id': workspace_id, 'resource_uuid': request.data['id']}
).update(request.data)
) These changes focus on organizing the code and ensuring clarity, while maintaining functionality and adhering to the project's coding standards. |
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 code looks mostly clean, but there are a few suggestions to enhance it:
Use
type
instead ofdescription
: In Django REST Framework (DRF) schema generation tools like Swagger/OpenAPI, usingtype
is recommended overdescription
.Consistent naming: While not strictly necessary, consistency with method names can make the API more readable.
Here's the revised version:
Summary of Changes:
type
in place ofdescription
for parameters.GetParametersMixin
) to define parameter structures consistently across different methods.This setup allows you to easily extend or modify the API definitions without repeating logic or causing inconsistencies.