Exemple #1
0
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)
Exemple #2
0
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)
Exemple #3
0
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)
Exemple #4
0
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)
Exemple #5
0
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)
Exemple #6
0
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)
Exemple #7
0
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)
Exemple #8
0
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)
Exemple #9
0
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)
Exemple #10
0
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)
Exemple #11
0
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)
Exemple #12
0
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)
Exemple #13
0
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)
Exemple #14
0
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)
Exemple #15
0
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)
Exemple #16
0
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)
Exemple #17
0
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)
Exemple #18
0
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)
Exemple #19
0
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)
Exemple #20
0
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)
Exemple #21
0
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)
Exemple #22
0
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)
Exemple #23
0
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)
Exemple #24
0
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)
Exemple #25
0
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)
Exemple #26
0
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)
Exemple #27
0
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)
Exemple #28
0
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)
Exemple #29
0
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)
Exemple #30
0
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)