class ExportTableView(APIView): permission_classes = (IsAuthenticated, ) @extend_schema( parameters=[ OpenApiParameter( name="table_id", location=OpenApiParameter.PATH, type=OpenApiTypes.INT, description= "The table id to create and start an export job for", ) ], tags=["Database table export"], operation_id="export_table", description= ("Creates and starts a new export job for a table given some exporter " "options. Returns an error if the requesting user does not have permissions" "to view the table."), request=CreateExportJobSerializer, responses={ 200: ExportJobSerializer, 400: get_error_schema([ "ERROR_USER_NOT_IN_GROUP", "ERROR_REQUEST_BODY_VALIDATION", "ERROR_TABLE_ONLY_EXPORT_UNSUPPORTED", "ERROR_VIEW_UNSUPPORTED_FOR_EXPORT_TYPE", "ERROR_VIEW_NOT_IN_TABLE", ]), 404: get_error_schema( ["ERROR_TABLE_DOES_NOT_EXIST", "ERROR_VIEW_DOES_NOT_EXIST"]), }, ) @transaction.atomic @map_exceptions({ UserNotInGroup: ERROR_USER_NOT_IN_GROUP, TableDoesNotExist: ERROR_TABLE_DOES_NOT_EXIST, ViewDoesNotExist: ERROR_VIEW_DOES_NOT_EXIST, TableOnlyExportUnsupported: ERROR_TABLE_ONLY_EXPORT_UNSUPPORTED, ViewNotInTable: ERROR_VIEW_NOT_IN_TABLE, }) def post(self, request, table_id): """ Starts a new export job for the provided table, view, export type and options. """ table = TableHandler().get_table(table_id) table.database.group.has_user(request.user, raise_error=True) option_data = _validate_options(request.data) view_id = option_data.pop("view_id", None) view = ViewHandler().get_view(view_id) if view_id else None job = ExportHandler.create_and_start_new_job(request.user, table, view, option_data) return Response(ExportJobSerializer(job).data)
class RowsView(APIView): permission_classes = (IsAuthenticated, ) @extend_schema( parameters=[ OpenApiParameter( name='table_id', location=OpenApiParameter.PATH, type=OpenApiTypes.INT, description='Creates a row in the table related to the provided ' 'value.') ], tags=['Database table rows'], operation_id='create_database_table_row', description= ('Creates a new row in the table if the user has access to the related ' 'table\'s group. The accepted body fields are depending on the fields ' 'that the table has. For a complete overview of fields use the ' '**list_database_table_fields** to list them all. None of the fields are ' 'required, if they are not provided the value is going to be `null` or ' '`false` or some default value is that is set. If you want to add a value ' 'for the field with for example id `10`, the key must be named ' '`field_10`. Of course multiple fields can be provided in one request. In ' 'the examples below you will find all the different field types, the ' 'numbers/ids in the example are just there for example purposes, the ' 'field_ID must be replaced with the actual id of the field.'), request=get_example_row_serializer_class(False), responses={ 200: get_example_row_serializer_class(True), 400: get_error_schema( ['ERROR_USER_NOT_IN_GROUP', 'ERROR_REQUEST_BODY_VALIDATION']), 404: get_error_schema(['ERROR_TABLE_DOES_NOT_EXIST']) }) @transaction.atomic @map_exceptions({ UserNotInGroupError: ERROR_USER_NOT_IN_GROUP, TableDoesNotExist: ERROR_TABLE_DOES_NOT_EXIST }) def post(self, request, table_id): """ Creates a new row for the given table_id. Also the post data is validated according to the tables field types. """ table = TableHandler().get_table(request.user, table_id) model = table.get_model() validation_serializer = get_row_serializer_class(model) data = validate_data(validation_serializer, request.data) row = RowHandler().create_row(request.user, table, data, model) serializer_class = get_row_serializer_class(model, RowSerializer) serializer = serializer_class(row) return Response(serializer.data)
class UserView(APIView): permission_classes = (AllowAny, ) @extend_schema( tags=["User"], request=RegisterSerializer, operation_id="create_user", description= ("Creates a new user based on the provided values. If desired an " "authentication token can be generated right away. After creating an " "account the initial group containing a database is created."), responses={ 200: create_user_response_schema, 400: get_error_schema([ "ERROR_ALREADY_EXISTS", "ERROR_GROUP_INVITATION_DOES_NOT_EXIST" "ERROR_REQUEST_BODY_VALIDATION", "BAD_TOKEN_SIGNATURE", ]), 404: get_error_schema(["ERROR_GROUP_INVITATION_DOES_NOT_EXIST"]), }, auth=[None], ) @transaction.atomic @map_exceptions({ UserAlreadyExist: ERROR_ALREADY_EXISTS, BadSignature: BAD_TOKEN_SIGNATURE, GroupInvitationDoesNotExist: ERROR_GROUP_INVITATION_DOES_NOT_EXIST, GroupInvitationEmailMismatch: ERROR_GROUP_INVITATION_EMAIL_MISMATCH, DisabledSignupError: ERROR_DISABLED_SIGNUP, }) @validate_body(RegisterSerializer) def post(self, request, data): """Registers a new user.""" template = (Template.objects.get( pk=data["template_id"]) if data["template_id"] else None) user = UserHandler().create_user( name=data["name"], email=data["email"], password=data["password"], group_invitation_token=data.get("group_invitation_token"), template=template, ) response = {"user": UserSerializer(user).data} if data["authenticate"]: payload = jwt_payload_handler(user) token = jwt_encode_handler(payload) response.update(token=token) return Response(response)
class UserView(APIView): permission_classes = (AllowAny, ) @extend_schema( tags=['User'], request=RegisterSerializer, operation_id='create_user', description= ('Creates a new user based on the provided values. If desired an ' 'authentication token can be generated right away. After creating an ' 'account the initial group containing a database is created.'), responses={ 200: create_user_response_schema, 400: get_error_schema([ 'ERROR_ALREADY_EXISTS', 'ERROR_GROUP_INVITATION_DOES_NOT_EXIST' 'ERROR_REQUEST_BODY_VALIDATION', 'BAD_TOKEN_SIGNATURE' ]), 404: get_error_schema(['ERROR_GROUP_INVITATION_DOES_NOT_EXIST']) }, auth=[None]) @transaction.atomic @map_exceptions({ UserAlreadyExist: ERROR_ALREADY_EXISTS, BadSignature: BAD_TOKEN_SIGNATURE, GroupInvitationDoesNotExist: ERROR_GROUP_INVITATION_DOES_NOT_EXIST, GroupInvitationEmailMismatch: ERROR_GROUP_INVITATION_EMAIL_MISMATCH }) @validate_body(RegisterSerializer) def post(self, request, data): """Registers a new user.""" user = UserHandler().create_user( name=data['name'], email=data['email'], password=data['password'], group_invitation_token=data.get('group_invitation_token')) response = {'user': UserSerializer(user).data} if data['authenticate']: payload = jwt_payload_handler(user) token = jwt_encode_handler(payload) response.update(token=token) return Response(response)
class UploadFileView(APIView): permission_classes = (IsAuthenticated, ) parser_classes = (MultiPartParser, ) @extend_schema( tags=["User files"], operation_id="upload_file", description= ("Uploads a file to Baserow by uploading the file contents directly. A " "`file` multipart is expected containing the file contents."), request=None, responses={ 200: UserFileSerializer, 400: get_error_schema( ["ERROR_INVALID_FILE", "ERROR_FILE_SIZE_TOO_LARGE"]), }, ) @transaction.atomic @map_exceptions({ InvalidFileStreamError: ERROR_INVALID_FILE, FileSizeTooLargeError: ERROR_FILE_SIZE_TOO_LARGE, }) def post(self, request): """Uploads a file by uploading the contents directly.""" if "file" not in request.FILES: raise InvalidFileStreamError("No file was provided.") file = request.FILES.get("file") user_file = UserFileHandler().upload_user_file(request.user, file.name, file) serializer = UserFileSerializer(user_file) return Response(serializer.data)
class AllApplicationsView(APIView): permission_classes = (IsAuthenticated, ) @extend_schema( tags=['Applications'], operation_id='list_all_applications', description= ('Lists all the applications that the authorized user has access to. The ' 'properties that belong to the application can differ per type. An ' 'application always belongs to a single group. All the applications of the ' 'groups that the user has access to are going to be listed here.'), responses={ 200: PolymorphicMappingSerializer('Applications', application_type_serializers, many=True), 400: get_error_schema(['ERROR_USER_NOT_IN_GROUP']) }) @map_exceptions({UserNotInGroupError: ERROR_USER_NOT_IN_GROUP}) def get(self, request): """ Responds with a list of serialized applications that belong to the user. If a group id is provided only the applications of that group are going to be returned. """ applications = Application.objects.select_related( 'content_type', 'group').filter(group__users__in=[request.user]) data = [ get_application_serializer(application).data for application in applications ] return Response(data)
class ChangePasswordView(APIView): permission_classes = (IsAuthenticated, ) @extend_schema( tags=["User"], request=ChangePasswordBodyValidationSerializer, operation_id="change_password", description= ("Changes the password of an authenticated user, but only if the old " "password matches."), responses={ 204: None, 400: get_error_schema([ "ERROR_INVALID_OLD_PASSWORD", "ERROR_REQUEST_BODY_VALIDATION" ]), }, ) @transaction.atomic @map_exceptions({ InvalidPassword: ERROR_INVALID_OLD_PASSWORD, }) @validate_body(ChangePasswordBodyValidationSerializer) def post(self, request, data): """Changes the authenticated user's password if the old password is correct.""" handler = UserHandler() handler.change_password(request.user, data["old_password"], data["new_password"]) return Response("", status=204)
class UploadFileView(APIView): permission_classes = (IsAuthenticated, ) parser_classes = (MultiPartParser, ) @extend_schema( tags=['User files'], operation_id='upload_file', description= ('Uploads a file to Baserow by uploading the file contents directly. A ' '`file` multipart is expected containing the file contents.'), request=build_object_type(), responses={ 200: UserFileSerializer, 400: get_error_schema( ['ERROR_INVALID_FILE', 'ERROR_FILE_SIZE_TOO_LARGE']) }) @transaction.atomic @map_exceptions({ InvalidFileStreamError: ERROR_INVALID_FILE, FileSizeTooLargeError: ERROR_FILE_SIZE_TOO_LARGE }) def post(self, request): """Uploads a file by uploading the contents directly.""" if 'file' not in request.FILES: raise InvalidFileStreamError('No file was provided.') file = request.FILES.get('file') user_file = UserFileHandler().upload_user_file(request.user, file.name, file) serializer = UserFileSerializer(user_file) return Response(serializer.data)
class UploadViaURLView(APIView): permission_classes = (IsAuthenticated, ) @extend_schema( tags=['User files'], operation_id='upload_via_url', description=( 'Uploads a file to Baserow by downloading it from the provided URL.' ), request=UserFileUploadViaURLRequestSerializer, responses={ 200: UserFileSerializer, 400: get_error_schema([ 'ERROR_INVALID_FILE', 'ERROR_FILE_SIZE_TOO_LARGE', 'ERROR_FILE_URL_COULD_NOT_BE_REACHED', 'ERROR_INVALID_FILE_URL' ]) }) @transaction.atomic @map_exceptions({ InvalidFileStreamError: ERROR_INVALID_FILE, FileSizeTooLargeError: ERROR_FILE_SIZE_TOO_LARGE, FileURLCouldNotBeReached: ERROR_FILE_URL_COULD_NOT_BE_REACHED, InvalidFileURLError: ERROR_INVALID_FILE_URL }) @validate_body(UserFileUploadViaURLRequestSerializer) def post(self, request, data): """Uploads a user file by downloading it from the provided URL.""" url = data['url'] user_file = UserFileHandler().upload_user_file_by_url( request.user, url) serializer = UserFileSerializer(user_file) return Response(serializer.data)
class AcceptGroupInvitationView(APIView): permission_classes = (IsAuthenticated, ) @extend_schema( parameters=[ OpenApiParameter( name="group_invitation_id", location=OpenApiParameter.PATH, type=OpenApiTypes.INT, description= "Accepts the group invitation related to the provided " "value.", ) ], tags=["Group invitations"], operation_id="accept_group_invitation", description= ("Accepts a group invitation with the given id if the email address of the " "user matches that of the invitation."), request=None, responses={ 200: GroupUserGroupSerializer, 400: get_error_schema(["ERROR_GROUP_INVITATION_EMAIL_MISMATCH"]), 404: get_error_schema(["ERROR_GROUP_INVITATION_DOES_NOT_EXIST"]), }, ) @transaction.atomic @map_exceptions({ GroupInvitationEmailMismatch: ERROR_GROUP_INVITATION_EMAIL_MISMATCH, GroupInvitationDoesNotExist: ERROR_GROUP_INVITATION_DOES_NOT_EXIST, }) def post(self, request, group_invitation_id): """Accepts a group invitation.""" try: group_invitation = GroupInvitation.objects.select_related( "group").get(id=group_invitation_id) except GroupInvitation.DoesNotExist: raise GroupInvitationDoesNotExist( f"The group invitation with id {group_invitation_id} does not exist." ) group_user = CoreHandler().accept_group_invitation( request.user, group_invitation) return Response(GroupUserGroupSerializer(group_user).data)
class OrderTablesView(APIView): permission_classes = (IsAuthenticated, ) @extend_schema( parameters=[ OpenApiParameter( name="database_id", location=OpenApiParameter.PATH, type=OpenApiTypes.INT, description= "Updates the order of the tables in the database related " "to the provided value.", ), ], tags=["Database tables"], operation_id="order_database_tables", description= ("Changes the order of the provided table ids to the matching position that " "the id has in the list. If the authorized user does not belong to the " "group it will be ignored. The order of the not provided tables will be " "set to `0`."), request=OrderTablesSerializer, responses={ 204: None, 400: get_error_schema( ["ERROR_USER_NOT_IN_GROUP", "ERROR_TABLE_NOT_IN_DATABASE"]), 404: get_error_schema(["ERROR_APPLICATION_DOES_NOT_EXIST"]), }, ) @validate_body(OrderTablesSerializer) @transaction.atomic @map_exceptions({ ApplicationDoesNotExist: ERROR_APPLICATION_DOES_NOT_EXIST, UserNotInGroup: ERROR_USER_NOT_IN_GROUP, TableNotInDatabase: ERROR_TABLE_NOT_IN_DATABASE, }) def post(self, request, data, database_id): """Updates to order of the tables in a table.""" database = CoreHandler().get_application( database_id, base_queryset=Database.objects) TableHandler().order_tables(request.user, database, data["table_ids"]) return Response(status=204)
class GroupUsersView(APIView): @extend_schema( parameters=[ OpenApiParameter( name="group_id", location=OpenApiParameter.PATH, type=OpenApiTypes.INT, description= "Updates the group user related to the provided value.", ) ], tags=["Groups"], operation_id="list_group_users", description= ("Lists all the users that are in a group if the authorized user has admin " "permissions to the related group. To add a user to a group an invitation " "must be send first."), responses={ 200: GroupUserSerializer(many=True), 400: get_error_schema([ "ERROR_USER_NOT_IN_GROUP", "ERROR_USER_INVALID_GROUP_PERMISSIONS" ]), 404: get_error_schema(["ERROR_GROUP_DOES_NOT_EXIST"]), }, ) @map_exceptions({ GroupDoesNotExist: ERROR_GROUP_DOES_NOT_EXIST, UserNotInGroup: ERROR_USER_NOT_IN_GROUP, UserInvalidGroupPermissionsError: ERROR_USER_INVALID_GROUP_PERMISSIONS, }) def get(self, request, group_id): """Responds with a list of serialized users that are part of the group.""" group = CoreHandler().get_group(group_id) group.has_user(request.user, "ADMIN", True) group_users = GroupUser.objects.filter( group=group).select_related("group") serializer = GroupUserSerializer(group_users, many=True) return Response(serializer.data)
class OrderViewsView(APIView): permission_classes = (IsAuthenticated,) @extend_schema( parameters=[ OpenApiParameter( name="table_id", location=OpenApiParameter.PATH, type=OpenApiTypes.INT, description="Updates the order of the views in the table related to " "the provided value.", ), ], tags=["Database table views"], operation_id="order_database_table_views", description=( "Changes the order of the provided view ids to the matching position that " "the id has in the list. If the authorized user does not belong to the " "group it will be ignored. The order of the not provided views will be " "set to `0`." ), request=OrderViewsSerializer, responses={ 204: None, 400: get_error_schema( ["ERROR_USER_NOT_IN_GROUP", "ERROR_VIEW_NOT_IN_TABLE"] ), 404: get_error_schema(["ERROR_TABLE_DOES_NOT_EXIST"]), }, ) @validate_body(OrderViewsSerializer) @transaction.atomic @map_exceptions( { TableDoesNotExist: ERROR_TABLE_DOES_NOT_EXIST, UserNotInGroup: ERROR_USER_NOT_IN_GROUP, ViewNotInTable: ERROR_VIEW_NOT_IN_TABLE, } ) def post(self, request, data, table_id): """Updates to order of the views in a table.""" table = TableHandler().get_table(table_id) ViewHandler().order_views(request.user, table, data["view_ids"]) return Response(status=204)
class RejectGroupInvitationView(APIView): permission_classes = (IsAuthenticated, ) @extend_schema( parameters=[ OpenApiParameter( name='group_invitation_id', location=OpenApiParameter.PATH, type=OpenApiTypes.INT, description= 'Rejects the group invitation related to the provided ' 'value.') ], tags=['Group invitations'], operation_id='reject_group_invitation', description= ('Rejects a group invitation with the given id if the email address of the ' 'user matches that of the invitation.'), request=None, responses={ 204: None, 400: get_error_schema(['ERROR_GROUP_INVITATION_EMAIL_MISMATCH']), 404: get_error_schema(['ERROR_GROUP_INVITATION_DOES_NOT_EXIST']) }, ) @transaction.atomic @map_exceptions({ GroupInvitationEmailMismatch: ERROR_GROUP_INVITATION_EMAIL_MISMATCH, GroupInvitationDoesNotExist: ERROR_GROUP_INVITATION_DOES_NOT_EXIST, }) def post(self, request, group_invitation_id): """Rejects a group invitation.""" try: group_invitation = GroupInvitation.objects.select_related( 'group').get(id=group_invitation_id) except GroupInvitation.DoesNotExist: raise GroupInvitationDoesNotExist( f'The group invitation with id {group_invitation_id} does not exist.' ) CoreHandler().reject_group_invitation(request.user, group_invitation) return Response(status=204)
class OrderApplicationsView(APIView): permission_classes = (IsAuthenticated,) @extend_schema( parameters=[ OpenApiParameter( name="group_id", location=OpenApiParameter.PATH, type=OpenApiTypes.INT, description="Updates the order of the applications in the group " "related to the provided value.", ), ], tags=["Applications"], operation_id="order_applications", description=( "Changes the order of the provided application ids to the matching " "position that the id has in the list. If the authorized user does not " "belong to the group it will be ignored. The order of the not provided " "tables will be set to `0`." ), request=OrderApplicationsSerializer, responses={ 204: None, 400: get_error_schema( ["ERROR_USER_NOT_IN_GROUP", "ERROR_APPLICATION_NOT_IN_GROUP"] ), 404: get_error_schema(["ERROR_GROUP_DOES_NOT_EXIST"]), }, ) @validate_body(OrderApplicationsSerializer) @transaction.atomic @map_exceptions( { GroupDoesNotExist: ERROR_GROUP_DOES_NOT_EXIST, UserNotInGroup: ERROR_USER_NOT_IN_GROUP, ApplicationNotInGroup: ERROR_APPLICATION_NOT_IN_GROUP, } ) def post(self, request, data, group_id): """Updates to order of the applications in a table.""" group = CoreHandler().get_group(group_id) CoreHandler().order_applications(request.user, group, data["application_ids"]) return Response(status=204)
class GroupInvitationByTokenView(APIView): permission_classes = (AllowAny, ) @extend_schema( parameters=[ OpenApiParameter( name="token", location=OpenApiParameter.PATH, type=OpenApiTypes.STR, description= "Returns the group invitation related to the provided " "token.", ) ], tags=["Group invitations"], operation_id="get_group_invitation_by_token", description= ("Responds with the serialized group invitation if an invitation with the " "provided token is found."), responses={ 200: UserGroupInvitationSerializer, 400: get_error_schema(["BAD_TOKEN_SIGNATURE"]), 404: get_error_schema(["ERROR_GROUP_INVITATION_DOES_NOT_EXIST"]), }, ) @map_exceptions({ BadSignature: BAD_TOKEN_SIGNATURE, GroupInvitationDoesNotExist: ERROR_GROUP_INVITATION_DOES_NOT_EXIST, }) def get(self, request, token): """ Responds with the serialized group invitation if an invitation with the provided token is found. """ exists_queryset = User.objects.filter(username=OuterRef("email")) group_invitation = CoreHandler().get_group_invitation_by_token( token, base_queryset=GroupInvitation.objects.annotate( email_exists=Exists(exists_queryset)), ) return Response(UserGroupInvitationSerializer(group_invitation).data)
class TokensView(APIView): permission_classes = (IsAuthenticated,) @extend_schema( tags=['Database tokens'], operation_id='list_database_tokens', description=( 'Lists all the API tokens that belong to the authorized user. An API token ' 'can be used to create, read, update and delete rows in the tables of the ' 'token\'s group. It only works on the tables if the token has the correct ' 'permissions. The **Database table rows** endpoints can be used for these ' 'operations.' ), responses={ 200: TokenSerializer(many=True), } ) def get(self, request): """Lists all the tokens of a user.""" tokens = Token.objects.filter(user=request.user).prefetch_related( 'tokenpermission_set' ) serializer = TokenSerializer(tokens, many=True) return Response(serializer.data) @extend_schema( tags=['Database tokens'], operation_id='create_database_token', description=( 'Creates a new API token for a given group and for the authorized user.' ), request=TokenCreateSerializer, responses={ 200: TokenSerializer, 400: get_error_schema([ 'ERROR_USER_NOT_IN_GROUP', 'ERROR_REQUEST_BODY_VALIDATION' ]) } ) @transaction.atomic @map_exceptions({ UserNotInGroupError: ERROR_USER_NOT_IN_GROUP }) @validate_body(TokenCreateSerializer) def post(self, request, data): """Creates a new token for the authorized user.""" data['group'] = CoreHandler().get_group(data.pop('group')) token = TokenHandler().create_token(request.user, **data) serializer = TokenSerializer(token) return Response(serializer.data)
class SendResetPasswordEmailView(APIView): permission_classes = (AllowAny, ) @extend_schema( tags=["User"], request=SendResetPasswordEmailBodyValidationSerializer, operation_id="send_password_reset_email", description= ("Sends an email containing the password reset link to the email address " "of the user. This will only be done if a user is found with the given " "email address. The endpoint will not fail if the email address is not " "found. The link is going to the valid for {valid} hours.".format( valid=int(settings.RESET_PASSWORD_TOKEN_MAX_AGE / 60 / 60))), responses={ 204: None, 400: get_error_schema([ "ERROR_REQUEST_BODY_VALIDATION", "ERROR_HOSTNAME_IS_NOT_ALLOWED" ]), }, auth=[None], ) @transaction.atomic @validate_body(SendResetPasswordEmailBodyValidationSerializer) @map_exceptions({BaseURLHostnameNotAllowed: ERROR_HOSTNAME_IS_NOT_ALLOWED}) def post(self, request, data): """ If the email is found, an email containing the password reset link is send to the user. """ handler = UserHandler() try: user = handler.get_user(email=data["email"]) handler.send_reset_password_email(user, data["base_url"]) except UserNotFound: pass return Response("", status=204)
class ResetPasswordView(APIView): permission_classes = (AllowAny, ) @extend_schema( tags=["User"], request=ResetPasswordBodyValidationSerializer, operation_id="reset_password", description= ("Changes the password of a user if the reset token is valid. The " "**send_password_reset_email** endpoint sends an email to the user " "containing the token. That token can be used to change the password " "here without providing the old password."), responses={ 204: None, 400: get_error_schema([ "BAD_TOKEN_SIGNATURE", "EXPIRED_TOKEN_SIGNATURE", "ERROR_USER_NOT_FOUND", "ERROR_REQUEST_BODY_VALIDATION", ]), }, auth=[None], ) @transaction.atomic @map_exceptions({ BadSignature: BAD_TOKEN_SIGNATURE, SignatureExpired: EXPIRED_TOKEN_SIGNATURE, UserNotFound: ERROR_USER_NOT_FOUND, }) @validate_body(ResetPasswordBodyValidationSerializer) def post(self, request, data): """Changes users password if the provided token is valid.""" handler = UserHandler() handler.reset_password(data["token"], data["password"]) return Response("", status=204)
class ExportJobView(APIView): permission_classes = (IsAuthenticated, ) @extend_schema( parameters=[ OpenApiParameter( name="job_id", location=OpenApiParameter.PATH, type=OpenApiTypes.INT, description="The job id to lookup information about.", ) ], tags=["Database table export"], operation_id="get_export_job", description= ("Returns information such as export progress and status or the url of the " "exported file for the specified export job, only if the requesting user " "has access."), responses={ 200: ExportJobSerializer, 404: get_error_schema(["ERROR_EXPORT_JOB_DOES_NOT_EXIST"]), }, ) @transaction.atomic @map_exceptions({ ExportJobDoesNotExistException: ERROR_EXPORT_JOB_DOES_NOT_EXIST, }) def get(self, request, job_id): """ Retrieves the specified export job. """ try: job = ExportJob.objects.get(id=job_id, user_id=request.user.id) except ExportJob.DoesNotExist: raise ExportJobDoesNotExistException() return Response(ExportJobSerializer(job).data)
class GroupAdminView(APIView): permission_classes = (IsAdminUser,) @extend_schema( tags=["Admin"], operation_id="admin_delete_group", description="Deletes the specified group and the applications inside that " "group, if the requesting user is staff. \n\nThis is a **premium** feature.", parameters=[ OpenApiParameter( name="group_id", location=OpenApiParameter.PATH, type=OpenApiTypes.INT, description="The id of the group to delete", ), ], responses={ 204: None, 400: get_error_schema(["ERROR_GROUP_DOES_NOT_EXIST"]), 401: None, }, ) @map_exceptions( { GroupDoesNotExist: ERROR_GROUP_DOES_NOT_EXIST, } ) @transaction.atomic def delete(self, request, group_id): """Deletes the specified group""" group = CoreHandler().get_group( group_id, base_queryset=Group.objects.select_for_update() ) handler = GroupsAdminHandler() handler.delete_group(request.user, group) return Response(status=204)
class UserView(APIView): permission_classes = (AllowAny, ) @extend_schema( tags=['User'], request=RegisterSerializer, operation_id='create_user', description= ('Creates a new user based on the provided values. If desired an ' 'authentication token can be generated right away. After creating an ' 'account the initial group containing a database is created.'), responses={ 200: create_user_response_schema, 400: get_error_schema( ['ERROR_ALREADY_EXISTS', 'ERROR_REQUEST_BODY_VALIDATION']) }, auth=[None]) @transaction.atomic @map_exceptions({UserAlreadyExist: ERROR_ALREADY_EXISTS}) @validate_body(RegisterSerializer) def post(self, request, data): """Registers a new user.""" user = UserHandler().create_user(name=data['name'], email=data['email'], password=data['password']) response = {'user': UserSerializer(user).data} if data['authenticate']: payload = jwt_payload_handler(user) token = jwt_encode_handler(payload) response.update(token=token) return Response(response)
class TablesView(APIView): permission_classes = (IsAuthenticated, ) @extend_schema( parameters=[ OpenApiParameter( name='database_id', location=OpenApiParameter.PATH, type=OpenApiTypes.INT, description= 'Returns only tables that are related to the provided ' 'value.') ], tags=['Database tables'], operation_id='list_database_tables', description= ('Lists all the tables that are in the database related to the ' '`database_id` parameter if the user has access to the database\'s group. ' 'A table is exactly as the name suggests. It can hold multiple fields, ' 'each having their own type and multiple rows. They can be added via the ' '**create_database_table_field** and **create_database_table_row** ' 'endpoints.'), responses={ 200: TableSerializer(many=True), 400: get_error_schema(['ERROR_USER_NOT_IN_GROUP']), 404: get_error_schema(['ERROR_APPLICATION_DOES_NOT_EXIST']) }) @map_exceptions({ ApplicationDoesNotExist: ERROR_APPLICATION_DOES_NOT_EXIST, UserNotInGroupError: ERROR_USER_NOT_IN_GROUP }) def get(self, request, database_id): """Lists all the tables of a database.""" database = CoreHandler().get_application( request.user, database_id, base_queryset=Database.objects) tables = Table.objects.filter(database=database) serializer = TableSerializer(tables, many=True) return Response(serializer.data) @extend_schema( parameters=[ OpenApiParameter( name='database_id', location=OpenApiParameter.PATH, type=OpenApiTypes.INT, description= 'Creates a table for the database related to the provided ' 'value.') ], tags=['Database tables'], operation_id='create_database_table', description=( 'Creates a new table for the database related to the provided ' '`database_id` parameter if the authorized user has access to the ' 'database\'s group.'), request=TableCreateUpdateSerializer, responses={ 200: TableSerializer, 400: get_error_schema( ['ERROR_USER_NOT_IN_GROUP', 'ERROR_REQUEST_BODY_VALIDATION']), 404: get_error_schema(['ERROR_APPLICATION_DOES_NOT_EXIST']) }) @transaction.atomic @map_exceptions({ ApplicationDoesNotExist: ERROR_APPLICATION_DOES_NOT_EXIST, UserNotInGroupError: ERROR_USER_NOT_IN_GROUP }) @validate_body(TableCreateUpdateSerializer) def post(self, request, data, database_id): """Creates a new table in a database.""" database = CoreHandler().get_application( request.user, database_id, base_queryset=Database.objects) table = TableHandler().create_table(request.user, database, fill_initial=True, name=data['name']) serializer = TableSerializer(table) return Response(serializer.data)
class UserAdminView(APIView): permission_classes = (IsAdminUser,) @extend_schema( tags=["Admin"], request=UserAdminUpdateSerializer, operation_id="admin_edit_user", description=f"Updates specified user attributes and returns the updated user if" f" the requesting user is staff. You cannot update yourself to no longer be an " f"admin or active. \n\nThis is a **premium** feature.", parameters=[ OpenApiParameter( name="user_id", location=OpenApiParameter.PATH, type=OpenApiTypes.INT, description="The id of the user to edit", ), ], responses={ 200: UserAdminResponseSerializer(), 400: get_error_schema( [ "ERROR_REQUEST_BODY_VALIDATION", "USER_ADMIN_CANNOT_DEACTIVATE_SELF", "USER_ADMIN_UNKNOWN_USER", ] ), 401: None, }, ) @validate_body(UserAdminUpdateSerializer, partial=True) @map_exceptions( { CannotDeactivateYourselfException: USER_ADMIN_CANNOT_DEACTIVATE_SELF, UserDoesNotExistException: USER_ADMIN_UNKNOWN_USER, } ) @transaction.atomic def patch(self, request, user_id, data): """ Updates the specified user with the supplied attributes. Will raise an exception if you attempt un-staff or de-activate yourself. """ user_id = int(user_id) handler = UserAdminHandler() user = handler.update_user(request.user, user_id, **data) return Response(UserAdminResponseSerializer(user).data) @extend_schema( tags=["Admin"], operation_id="admin_delete_user", description="Deletes the specified user, if the requesting user has admin " "permissions. You cannot delete yourself. \n\nThis is a **premium** feature.", parameters=[ OpenApiParameter( name="user_id", location=OpenApiParameter.PATH, type=OpenApiTypes.INT, description="The id of the user to delete", ), ], responses={ 200: None, 400: get_error_schema( [ "USER_ADMIN_CANNOT_DELETE_SELF", "USER_ADMIN_UNKNOWN_USER", ] ), 401: None, }, ) @map_exceptions( { CannotDeleteYourselfException: USER_ADMIN_CANNOT_DELETE_SELF, UserDoesNotExistException: USER_ADMIN_UNKNOWN_USER, } ) @transaction.atomic def delete(self, request, user_id): """ Deletes the specified user. Raises an exception if you attempt to delete yourself. """ user_id = int(user_id) handler = UserAdminHandler() handler.delete_user(request.user, user_id) return Response(status=204)
class TokenView(APIView): permission_classes = (IsAuthenticated,) @extend_schema( parameters=[ OpenApiParameter( name='token_id', location=OpenApiParameter.PATH, type=OpenApiTypes.INT, description='Returns the token related to the provided value.' ) ], tags=['Database tokens'], operation_id='get_database_token', description=( 'Returns the requested token if it is owned by the authorized user and' 'if the user has access to the related group.' ), responses={ 200: TokenSerializer, 400: get_error_schema(['ERROR_USER_NOT_IN_GROUP']), 404: get_error_schema(['ERROR_TOKEN_DOES_NOT_EXIST']) } ) @map_exceptions({ TokenDoesNotExist: ERROR_TOKEN_DOES_NOT_EXIST, UserNotInGroupError: ERROR_USER_NOT_IN_GROUP }) def get(self, request, token_id): """Responds with a serialized token instance.""" token = TokenHandler().get_token(request.user, token_id) serializer = TokenSerializer(token) return Response(serializer.data) @extend_schema( parameters=[ OpenApiParameter( name='token_id', location=OpenApiParameter.PATH, type=OpenApiTypes.INT, description='Updates the token related to the provided value.' ) ], tags=['Database tokens'], operation_id='update_database_token', description=( 'Updates the existing token if it is owned by the authorized user and if' 'the user has access to the related group.' ), request=TokenUpdateSerializer, responses={ 200: TokenSerializer, 400: get_error_schema([ 'ERROR_USER_NOT_IN_GROUP', 'ERROR_REQUEST_BODY_VALIDATION', 'ERROR_DATABASE_DOES_NOT_BELONG_TO_GROUP', 'ERROR_TABLE_DOES_NOT_BELONG_TO_GROUP' ]), 404: get_error_schema(['ERROR_TOKEN_DOES_NOT_EXIST']) } ) @transaction.atomic @map_exceptions({ TokenDoesNotExist: ERROR_TOKEN_DOES_NOT_EXIST, UserNotInGroupError: ERROR_USER_NOT_IN_GROUP, DatabaseDoesNotBelongToGroup: ERROR_DATABASE_DOES_NOT_BELONG_TO_GROUP, TableDoesNotBelongToGroup: ERROR_TABLE_DOES_NOT_BELONG_TO_GROUP }) @validate_body(TokenUpdateSerializer) def patch(self, request, data, token_id): """Updates the values of a token.""" token = TokenHandler().get_token( request.user, token_id, base_queryset=Token.objects.select_for_update() ) permissions = data.pop('permissions', None) rotate_key = data.pop('rotate_key', False) if len(data) > 0: token = TokenHandler().update_token(request.user, token, **data) if permissions: TokenHandler().update_token_permissions(request.user, token, **permissions) if rotate_key: token = TokenHandler().rotate_token_key(request.user, token) serializer = TokenSerializer(token) return Response(serializer.data) @extend_schema( parameters=[ OpenApiParameter( name='token_id', location=OpenApiParameter.PATH, type=OpenApiTypes.INT, description='Deletes the token related to the provided value.' ) ], tags=['Database tokens'], operation_id='delete_database_token', description=( 'Deletes the existing token if it is owned by the authorized user and if' 'the user has access to the related group.' ), responses={ 204: None, 400: get_error_schema(['ERROR_USER_NOT_IN_GROUP']), 404: get_error_schema(['ERROR_TOKEN_DOES_NOT_EXIST']) } ) @transaction.atomic @map_exceptions({ TokenDoesNotExist: ERROR_TOKEN_DOES_NOT_EXIST, UserNotInGroupError: ERROR_USER_NOT_IN_GROUP }) def delete(self, request, token_id): """Deletes an existing token.""" token = TokenHandler().get_token(request.user, token_id) TokenHandler().delete_token(request.user, token) return Response(status=204)
class TableView(APIView): permission_classes = (IsAuthenticated, ) @extend_schema( parameters=[ OpenApiParameter( name='table_id', location=OpenApiParameter.PATH, type=OpenApiTypes.INT, description='Returns the table related to the provided value.') ], tags=['Database tables'], operation_id='get_database_table', description= ('Returns the requested table if the authorized user has access to the ' 'related database\'s group.'), responses={ 200: TableSerializer, 400: get_error_schema(['ERROR_USER_NOT_IN_GROUP']), 404: get_error_schema(['ERROR_TABLE_DOES_NOT_EXIST']) }) @map_exceptions({ TableDoesNotExist: ERROR_TABLE_DOES_NOT_EXIST, UserNotInGroupError: ERROR_USER_NOT_IN_GROUP }) def get(self, request, table_id): """Responds with a serialized table instance.""" table = TableHandler().get_table(request.user, table_id) serializer = TableSerializer(table) return Response(serializer.data) @extend_schema( parameters=[ OpenApiParameter( name='table_id', location=OpenApiParameter.PATH, type=OpenApiTypes.INT, description='Updates the table related to the provided value.') ], tags=['Database tables'], operation_id='update_database_table', description= ('Updates the existing table if the authorized user has access to the ' 'related database\'s group.'), request=TableCreateUpdateSerializer, responses={ 200: TableSerializer, 400: get_error_schema( ['ERROR_USER_NOT_IN_GROUP', 'ERROR_REQUEST_BODY_VALIDATION']), 404: get_error_schema(['ERROR_TABLE_DOES_NOT_EXIST']) }) @transaction.atomic @map_exceptions({ TableDoesNotExist: ERROR_TABLE_DOES_NOT_EXIST, UserNotInGroupError: ERROR_USER_NOT_IN_GROUP }) @validate_body(TableCreateUpdateSerializer) def patch(self, request, data, table_id): """Updates the values a table instance.""" table = TableHandler().update_table(request.user, TableHandler().get_table( request.user, table_id), name=data['name']) serializer = TableSerializer(table) return Response(serializer.data) @extend_schema( parameters=[ OpenApiParameter( name='table_id', location=OpenApiParameter.PATH, type=OpenApiTypes.INT, description='Deletes the table related to the provided value.') ], tags=['Database tables'], operation_id='delete_database_table', description= ('Deletes the existing table if the authorized user has access to the ' 'related database\'s group.'), responses={ 204: None, 400: get_error_schema(['ERROR_USER_NOT_IN_GROUP']), 404: get_error_schema(['ERROR_TABLE_DOES_NOT_EXIST']) }) @transaction.atomic @map_exceptions({ TableDoesNotExist: ERROR_TABLE_DOES_NOT_EXIST, UserNotInGroupError: ERROR_USER_NOT_IN_GROUP }) def delete(self, request, table_id): """Deletes an existing table.""" TableHandler().delete_table( request.user, TableHandler().get_table(request.user, table_id)) return Response(status=204)
class GroupView(APIView): permission_classes = (IsAuthenticated, ) @extend_schema( parameters=[ OpenApiParameter( name='group_id', location=OpenApiParameter.PATH, type=OpenApiTypes.INT, description='Updates the group related to the provided value.') ], tags=['Groups'], operation_id='update_group', description= ('Updates the existing group related to the provided `group_id` parameter ' 'if the authorized user belongs to the group. It is not yet possible to ' 'add additional users to a group.'), request=GroupSerializer, responses={ 200: group_user_schema, 400: get_error_schema( ['ERROR_USER_NOT_IN_GROUP', 'ERROR_REQUEST_BODY_VALIDATION']), 404: get_error_schema(['ERROR_GROUP_DOES_NOT_EXIST']) }) @transaction.atomic @validate_body(GroupSerializer) @map_exceptions({ GroupDoesNotExist: ERROR_GROUP_DOES_NOT_EXIST, UserNotInGroupError: ERROR_USER_NOT_IN_GROUP }) def patch(self, request, data, group_id): """Updates the group if it belongs to a user.""" group_user = CoreHandler().get_group_user( request.user, group_id, base_queryset=GroupUser.objects.select_for_update()) group_user.group = CoreHandler().update_group(request.user, group_user.group, name=data['name']) return Response(GroupUserSerializer(group_user).data) @extend_schema( parameters=[ OpenApiParameter( name='group_id', location=OpenApiParameter.PATH, type=OpenApiTypes.INT, description='Deletes the group related to the provided value.') ], tags=['Groups'], operation_id='delete_group', description= ('Deletes an existing group if the authorized user belongs to the group. ' 'All the applications, databases, tables etc that were in the group are ' 'going to be deleted also.'), request=GroupSerializer, responses={ 200: group_user_schema, 400: get_error_schema( ['ERROR_USER_NOT_IN_GROUP', 'ERROR_REQUEST_BODY_VALIDATION']), 404: get_error_schema(['ERROR_GROUP_DOES_NOT_EXIST']) }) @transaction.atomic @map_exceptions({ GroupDoesNotExist: ERROR_GROUP_DOES_NOT_EXIST, UserNotInGroupError: ERROR_USER_NOT_IN_GROUP }) def delete(self, request, group_id): """Deletes an existing group if it belongs to a user.""" group_user = CoreHandler().get_group_user( request.user, group_id, base_queryset=GroupUser.objects.select_for_update()) CoreHandler().delete_group(request.user, group_user.group) return Response(status=204)
class FieldsView(APIView): permission_classes = (IsAuthenticated, ) def get_permissions(self): if self.request.method == 'GET': return [AllowAny()] return super().get_permissions() @extend_schema( parameters=[ OpenApiParameter( name='table_id', location=OpenApiParameter.PATH, type=OpenApiTypes.INT, description= 'Returns only the fields of the table related to the ' 'provided value.') ], tags=['Database table fields'], operation_id='list_database_table_fields', description= ('Lists all the fields of the table related to the provided parameter if ' 'the user has access to the related database\'s group. If the group is ' 'related to a template, then this endpoint will be publicly accessible. A ' 'table consists of fields and each field can have a different type. Each ' 'type can have different properties. A field is comparable with a regular ' 'table\'s column.'), responses={ 200: PolymorphicCustomFieldRegistrySerializer(field_type_registry, FieldSerializer, many=True), 400: get_error_schema(['ERROR_USER_NOT_IN_GROUP']), 404: get_error_schema(['ERROR_TABLE_DOES_NOT_EXIST']) }) @map_exceptions({ TableDoesNotExist: ERROR_TABLE_DOES_NOT_EXIST, UserNotInGroupError: ERROR_USER_NOT_IN_GROUP }) @method_permission_classes([AllowAny]) def get(self, request, table_id): """ Responds with a list of serialized fields that belong to the table if the user has access to that group. """ table = TableHandler().get_table(table_id) table.database.group.has_user(request.user, raise_error=True, allow_if_template=True) fields = Field.objects.filter( table=table).select_related('content_type') data = [ field_type_registry.get_serializer(field, FieldSerializer).data for field in fields ] return Response(data) @extend_schema( parameters=[ OpenApiParameter( name='table_id', location=OpenApiParameter.PATH, type=OpenApiTypes.INT, description= 'Creates a new field for the provided table related to the ' 'value.') ], tags=['Database table fields'], operation_id='create_database_table_field', description= ('Creates a new field for the table related to the provided `table_id` ' 'parameter if the authorized user has access to the related database\'s ' 'group. Depending on the type, different properties can optionally be ' 'set.'), request=PolymorphicCustomFieldRegistrySerializer( field_type_registry, CreateFieldSerializer), responses={ 200: PolymorphicCustomFieldRegistrySerializer(field_type_registry, FieldSerializer), 400: get_error_schema( ['ERROR_USER_NOT_IN_GROUP', 'ERROR_REQUEST_BODY_VALIDATION']), 404: get_error_schema(['ERROR_TABLE_DOES_NOT_EXIST']) }) @transaction.atomic @validate_body_custom_fields(field_type_registry, base_serializer_class=CreateFieldSerializer) @map_exceptions({ TableDoesNotExist: ERROR_TABLE_DOES_NOT_EXIST, UserNotInGroupError: ERROR_USER_NOT_IN_GROUP }) def post(self, request, data, table_id): """Creates a new field for a table.""" type_name = data.pop('type') field_type = field_type_registry.get(type_name) table = TableHandler().get_table(table_id) table.database.group.has_user(request.user, raise_error=True) # Because each field type can raise custom exceptions while creating the # field we need to be able to map those to the correct API exceptions which are # defined in the type. with field_type.map_api_exceptions(): field = FieldHandler().create_field(request.user, table, type_name, **data) serializer = field_type_registry.get_serializer(field, FieldSerializer) return Response(serializer.data)
class GroupView(APIView): permission_classes = (IsAuthenticated,) @extend_schema( parameters=[ OpenApiParameter( name="group_id", location=OpenApiParameter.PATH, type=OpenApiTypes.INT, description="Updates the group related to the provided value.", ) ], tags=["Groups"], operation_id="update_group", description=( "Updates the existing group related to the provided `group_id` parameter " "if the authorized user belongs to the group. It is not yet possible to " "add additional users to a group." ), request=GroupSerializer, responses={ 200: GroupSerializer, 400: get_error_schema( [ "ERROR_USER_NOT_IN_GROUP", "ERROR_REQUEST_BODY_VALIDATION", "ERROR_USER_INVALID_GROUP_PERMISSIONS", ] ), 404: get_error_schema(["ERROR_GROUP_DOES_NOT_EXIST"]), }, ) @transaction.atomic @validate_body(GroupSerializer) @map_exceptions( { GroupDoesNotExist: ERROR_GROUP_DOES_NOT_EXIST, UserNotInGroup: ERROR_USER_NOT_IN_GROUP, UserInvalidGroupPermissionsError: ERROR_USER_INVALID_GROUP_PERMISSIONS, } ) def patch(self, request, data, group_id): """Updates the group if it belongs to a user.""" group = CoreHandler().get_group( group_id, base_queryset=Group.objects.select_for_update() ) group = CoreHandler().update_group(request.user, group, name=data["name"]) return Response(GroupSerializer(group).data) @extend_schema( parameters=[ OpenApiParameter( name="group_id", location=OpenApiParameter.PATH, type=OpenApiTypes.INT, description="Deletes the group related to the provided value.", ) ], tags=["Groups"], operation_id="delete_group", description=( "Deletes an existing group if the authorized user belongs to the group. " "All the applications, databases, tables etc that were in the group are " "going to be deleted also." ), request=GroupSerializer, responses={ 200: group_user_schema, 400: get_error_schema( [ "ERROR_USER_NOT_IN_GROUP", "ERROR_REQUEST_BODY_VALIDATION", "ERROR_USER_INVALID_GROUP_PERMISSIONS", ] ), 404: get_error_schema(["ERROR_GROUP_DOES_NOT_EXIST"]), }, ) @transaction.atomic @map_exceptions( { GroupDoesNotExist: ERROR_GROUP_DOES_NOT_EXIST, UserNotInGroup: ERROR_USER_NOT_IN_GROUP, UserInvalidGroupPermissionsError: ERROR_USER_INVALID_GROUP_PERMISSIONS, } ) def delete(self, request, group_id): """Deletes an existing group if it belongs to a user.""" group = CoreHandler().get_group( group_id, base_queryset=Group.objects.select_for_update() ) CoreHandler().delete_group(request.user, group) return Response(status=204)
class FieldView(APIView): permission_classes = (IsAuthenticated, ) @extend_schema( parameters=[ OpenApiParameter( name='field_id', location=OpenApiParameter.PATH, type=OpenApiTypes.INT, description='Returns the field related to the provided value.') ], tags=['Database table fields'], operation_id='get_database_table_field', description= ('Returns the existing field if the authorized user has access to the ' 'related database\'s group. Depending on the type different properties' 'could be returned.'), responses={ 200: PolymorphicCustomFieldRegistrySerializer(field_type_registry, FieldSerializer), 400: get_error_schema(['ERROR_USER_NOT_IN_GROUP']), 404: get_error_schema(['ERROR_FIELD_DOES_NOT_EXIST']) }) @map_exceptions({ FieldDoesNotExist: ERROR_FIELD_DOES_NOT_EXIST, UserNotInGroupError: ERROR_USER_NOT_IN_GROUP }) def get(self, request, field_id): """Selects a single field and responds with a serialized version.""" field = FieldHandler().get_field(field_id) field.table.database.group.has_user(request.user, raise_error=True) serializer = field_type_registry.get_serializer(field, FieldSerializer) return Response(serializer.data) @extend_schema( parameters=[ OpenApiParameter( name='field_id', location=OpenApiParameter.PATH, type=OpenApiTypes.INT, description='Updates the field related to the provided value.') ], tags=['Database table fields'], operation_id='update_database_table_field', description= ('Updates the existing field if the authorized user has access to the ' 'related database\'s group. The type can also be changed and depending on ' 'that type, different additional properties can optionally be set. If you ' 'change the field type it could happen that the data conversion fails, in ' 'that case the `ERROR_CANNOT_CHANGE_FIELD_TYPE` is returned, but this ' 'rarely happens. If a data value cannot be converted it is set to `null` ' 'so data might go lost.'), request=PolymorphicCustomFieldRegistrySerializer( field_type_registry, UpdateFieldSerializer), responses={ 200: PolymorphicCustomFieldRegistrySerializer(field_type_registry, FieldSerializer), 400: get_error_schema([ 'ERROR_USER_NOT_IN_GROUP', 'ERROR_CANNOT_CHANGE_FIELD_TYPE', 'ERROR_REQUEST_BODY_VALIDATION' ]), 404: get_error_schema(['ERROR_FIELD_DOES_NOT_EXIST']) }) @transaction.atomic @map_exceptions({ FieldDoesNotExist: ERROR_FIELD_DOES_NOT_EXIST, UserNotInGroupError: ERROR_USER_NOT_IN_GROUP, CannotChangeFieldType: ERROR_CANNOT_CHANGE_FIELD_TYPE }) def patch(self, request, field_id): """Updates the field if the user belongs to the group.""" field = FieldHandler().get_field( field_id, base_queryset=Field.objects.select_for_update()).specific type_name = type_from_data_or_registry(request.data, field_type_registry, field) field_type = field_type_registry.get(type_name) data = validate_data_custom_fields( type_name, field_type_registry, request.data, base_serializer_class=UpdateFieldSerializer) # Because each field type can raise custom exceptions at while updating the # field we need to be able to map those to the correct API exceptions which are # defined in the type. with field_type.map_api_exceptions(): field = FieldHandler().update_field(request.user, field, type_name, **data) serializer = field_type_registry.get_serializer(field, FieldSerializer) return Response(serializer.data) @extend_schema( parameters=[ OpenApiParameter( name='field_id', location=OpenApiParameter.PATH, type=OpenApiTypes.INT, description='Deletes the field related to the provided value.') ], tags=['Database table fields'], operation_id='delete_database_table_field', description= ('Deletes the existing field if the authorized user has access to the ' 'related database\'s group. Note that all the related data to that field ' 'is also deleted. Primary fields cannot be deleted because their value ' 'represents the row.'), responses={ 204: None, 400: get_error_schema([ 'ERROR_USER_NOT_IN_GROUP', 'ERROR_CANNOT_DELETE_PRIMARY_FIELD' ]), 404: get_error_schema(['ERROR_FIELD_DOES_NOT_EXIST']) }) @transaction.atomic @map_exceptions({ FieldDoesNotExist: ERROR_FIELD_DOES_NOT_EXIST, UserNotInGroupError: ERROR_USER_NOT_IN_GROUP, CannotDeletePrimaryField: ERROR_CANNOT_DELETE_PRIMARY_FIELD }) def delete(self, request, field_id): """Deletes an existing field if the user belongs to the group.""" field = FieldHandler().get_field(field_id) FieldHandler().delete_field(request.user, field) return Response(status=204)