-
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
Conversation
Adding the "do-not-merge/release-note-label-needed" label because no release-note block was detected, please follow our release note process to remove it. Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. |
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: The full list of commands accepted by this bot can be found here.
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
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 comment
The 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:
-
Method Classification: Grouping the operations (create, update, read, delete) into
Create
,Operate
classes makes it easier to manage each endpoint separately and provides a clear structure. -
Parameters Handling: The
parameters
attribute has been added where applicable. This is a good practice for handling input parameters in OpenAPI schema generation. -
Workspace ID Passivation: The
workspace_id
parameter is passed around explicitly in the serializer methods. While this may have performance implications if used excessively, it ensures that all necessary information is always available when accessing the data. -
Use of Lambdas: Using lambda functions can make the code concise, but ensure they remain readable and understandable. In your case, there aren't many complex expressions, so this approach works fine.
-
Logging: Although not implemented in the snippet, consider adding logging middleware or logging calls at appropriate levels to help with development and debugging.
-
Type Annotations: Adding type annotations to variables and function parameters can improve readability and maintainability.
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.
|
||
|
||
class ToolDeleteAPI(ToolReadAPI): | ||
pass |
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:
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter
from common.mixins.api_mixin import APIMixin
from common.result import ResultSerializer
class WorkflowToolsAPIDefinitions(APIMixin):
class GetParametersMixin:
WORKSPACE_ID_PARAM = (
'workspace_id',
OpenApiTypes.STR,
'工作空间id',
True,
'path'
)
TOOL_CREATE_PARAMETER_GROUP_1 = [WORKSPACE_ID_PARAM]
TOOL_EDIT_DELETE_PARAMETERS = [WORKSPACE_ID_PARAM]
def __init__(self):
super().__init__()
self.get_parameters = {
"create": WorkflowToolsAPIDefinitions.ToolCreateAPI.GET_PARAMETERS_MIXIN.TOOL_CREATE_PARAMETER_GROUP_1,
"read": self.read_api_get_parameters_definition(),
"edit": WorkflowToolsAPIDefinitions.ToolReadAPI_GET_PARAMETERS_MIXIN.TOOL_EDIT_DELETE_PARAMETERS,
"delete": WorkflowToolsAPIDefinitions.ToolReadAPI_GET_PARAMETERS_MIXIN.TOOL_EDIT_DELETE_PARAMETERS,
}
@property
def read_api_get_parameters_definition(self):
tool_id_param_def = OpenApiParameter(
name="tool_id",
value_type=OpenApiTypes.STR,
description="工具id",
required=True,
location='path'
)
return [WorkflowToolsAPIDefinitions.GetParametersMixin.WORKSPACE_ID_PARAM, tool_id.param]
class ToolCreateApi(WorkflowToolsAPIDefinitions, APIMixin):
get_response = ResultSerializer
class ToolReadApi(ToolCreateApi):
pass
class ToolEditApi(ToolReadApi):
pass
class ToolDeleteApi(ToolReadApi):
pass
Summary of Changes:
- Used
type
in place ofdescription
for parameters. - Added a mixin (
GetParametersMixin
) to define parameter structures consistently across different methods. - Created instances for each endpoint class, which can be used to generate the appropriate Swagger schemas automatically. This approach avoids duplicate work and makes maintenance easier.
This setup allows you to easily extend or modify the API definitions without repeating logic or causing inconsistencies.
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 comment
The 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:
-
Null Default Values: For fields like
default_value
in theInitField
, it's good practice to usenull='allow_blank'
. This allows for an empty string instead of requiring a value. -
Unique Constraint on Field Name: Adding unique constraint validation directly within the serializer might be more flexible and easier to maintain than using external checks after saving data.
-
Error Handling: In general, having consistent error messages across all operations is helpful for debugging purposes.
-
Use of Meta Class: Ensure proper usage of Django Rest Framework's
Meta
class for specifying the model used by the serializers.
Here are some specific improvement suggestions:
Updated Serializer Code
from 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 (create_tool
, operate_tool
) to handle different HTTP methods. The OperationSerializer
provides both editing and deletion functionality. It also includes parameters for validating and handling exceptions more effectively at runtime.
feat: implement CRUD operations for tools with API views