コード例 #1
0
ファイル: views.py プロジェクト: jbjuin/baserow
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,
        UserNotInGroup: ERROR_USER_NOT_IN_GROUP,
    })
    def get(self, request, table_id):
        """Responds with a serialized table instance."""

        table = TableHandler().get_table(table_id)
        table.database.group.has_user(request.user, raise_error=True)
        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=TableUpdateSerializer,
        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,
        UserNotInGroup: ERROR_USER_NOT_IN_GROUP,
    })
    @validate_body(TableUpdateSerializer)
    def patch(self, request, data, table_id):
        """Updates the values a table instance."""

        table = TableHandler().update_table(
            request.user,
            TableHandler().get_table(table_id),
            base_queryset=Table.objects.select_for_update(),
            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,
        UserNotInGroup: ERROR_USER_NOT_IN_GROUP,
    })
    def delete(self, request, table_id):
        """Deletes an existing table."""

        TableHandler().delete_table(request.user,
                                    TableHandler().get_table(table_id))
        return Response(status=204)
コード例 #2
0
class ViewsView(APIView):
    permission_classes = (IsAuthenticated,)

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name='table_id',
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description='Returns only views of the table related to the provided '
                            'value.'
            )
        ],
        tags=['Database table views'],
        operation_id='list_database_table_views',
        description=(
            'Lists all views of the table related to the provided `table_id` if the '
            'user has access to the related database\'s group. A table can have '
            'multiple views. Each view can display the data in a different way. For '
            'example the `grid` view shows the in a spreadsheet like way. That type '
            'has custom endpoints for data retrieval and manipulation. In the future '
            'other views types like a calendar or Kanban are going to be added. Each '
            'type can have different properties.'
        ),
        responses={
            200: PolymorphicCustomFieldRegistrySerializer(
                view_type_registry,
                ViewSerializer,
                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
    })
    @allowed_includes('filters', 'sortings')
    def get(self, request, table_id, filters, sortings):
        """
        Responds with a list of serialized views that belong to the table if the user
        has access to that group.
        """

        table = TableHandler().get_table(request.user, table_id)
        views = View.objects.filter(table=table).select_related('content_type')

        if filters:
            views = views.prefetch_related('viewfilter_set')

        if sortings:
            views = views.prefetch_related('viewsort_set')

        data = [
            view_type_registry.get_serializer(
                view,
                ViewSerializer,
                filters=filters,
                sortings=sortings
            ).data
            for view in views
        ]
        return Response(data)

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name='table_id',
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description='Creates a view for the table related to the provided '
                            'value.'
            )
        ],
        tags=['Database table views'],
        operation_id='create_database_table_view',
        description=(
            'Creates a new view 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(
            view_type_registry,
            CreateViewSerializer
        ),
        responses={
            200: PolymorphicCustomFieldRegistrySerializer(
                view_type_registry,
                ViewSerializer
            ),
            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(
        view_type_registry, base_serializer_class=CreateViewSerializer)
    @map_exceptions({
        TableDoesNotExist: ERROR_TABLE_DOES_NOT_EXIST,
        UserNotInGroupError: ERROR_USER_NOT_IN_GROUP
    })
    @allowed_includes('filters', 'sortings')
    def post(self, request, data, table_id, filters, sortings):
        """Creates a new view for a user."""

        table = TableHandler().get_table(request.user, table_id)
        view = ViewHandler().create_view(
            request.user, table, data.pop('type'), **data)

        serializer = view_type_registry.get_serializer(
            view,
            ViewSerializer,
            filters=filters,
            sortings=sortings
        )
        return Response(serializer.data)
コード例 #3
0
class ViewSortView(APIView):
    permission_classes = (IsAuthenticated,)

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name='view_sort_id',
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description='Returns the view sort related to the provided value.'
            )
        ],
        tags=['Database table view sortings'],
        operation_id='get_database_table_view_sort',
        description=(
            'Returns the existing view sort if the authorized user has access to the'
            ' related database\'s group.'
        ),
        responses={
            200: ViewSortSerializer(),
            400: get_error_schema(['ERROR_USER_NOT_IN_GROUP']),
            404: get_error_schema(['ERROR_VIEW_SORT_DOES_NOT_EXIST'])
        }
    )
    @map_exceptions({
        ViewSortDoesNotExist: ERROR_VIEW_SORT_DOES_NOT_EXIST,
        UserNotInGroupError: ERROR_USER_NOT_IN_GROUP
    })
    def get(self, request, view_sort_id):
        """Selects a single sort and responds with a serialized version."""

        view_sort = ViewHandler().get_sort(request.user, view_sort_id)
        serializer = ViewSortSerializer(view_sort)
        return Response(serializer.data)

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name='view_sort_id',
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description='Updates the view sort related to the provided value.'
            )
        ],
        tags=['Database table view sortings'],
        operation_id='update_database_table_view_sort',
        description=(
            'Updates the existing sort if the authorized user has access to the '
            'related database\'s group.'
        ),
        request=UpdateViewSortSerializer(),
        responses={
            200: ViewSortSerializer(),
            400: get_error_schema([
                'ERROR_USER_NOT_IN_GROUP', 'ERROR_FIELD_NOT_IN_TABLE',
                'ERROR_VIEW_SORT_FIELD_ALREADY_EXISTS'
            ]),
            404: get_error_schema(['ERROR_VIEW_SORT_DOES_NOT_EXIST'])
        }
    )
    @transaction.atomic
    @validate_body(UpdateViewSortSerializer)
    @map_exceptions({
        ViewSortDoesNotExist: ERROR_VIEW_SORT_DOES_NOT_EXIST,
        UserNotInGroupError: ERROR_USER_NOT_IN_GROUP,
        FieldNotInTable: ERROR_FIELD_NOT_IN_TABLE,
        ViewSortFieldAlreadyExist: ERROR_VIEW_SORT_FIELD_ALREADY_EXISTS,
        ViewSortFieldNotSupported: ERROR_VIEW_SORT_FIELD_NOT_SUPPORTED,
    })
    def patch(self, request, data, view_sort_id):
        """Updates the view sort if the user belongs to the group."""

        handler = ViewHandler()
        view_sort = handler.get_sort(
            request.user,
            view_sort_id,
            base_queryset=ViewSort.objects.select_for_update()
        )

        if 'field' in data:
            # We can safely assume the field exists because the
            # UpdateViewSortSerializer has already checked that.
            data['field'] = Field.objects.get(pk=data['field'])

        view_sort = handler.update_sort(request.user, view_sort, **data)

        serializer = ViewSortSerializer(view_sort)
        return Response(serializer.data)

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name='view_sort_id',
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description='Deletes the sort related to the provided value.'
            )
        ],
        tags=['Database table view sortings'],
        operation_id='delete_database_table_view_sort',
        description=(
            'Deletes the existing sort 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_VIEW_SORT_DOES_NOT_EXIST'])
        }
    )
    @transaction.atomic
    @map_exceptions({
        ViewSortDoesNotExist: ERROR_VIEW_SORT_DOES_NOT_EXIST,
        UserNotInGroupError: ERROR_USER_NOT_IN_GROUP
    })
    def delete(self, request, view_sort_id):
        """Deletes an existing sort if the user belongs to the group."""

        view = ViewHandler().get_sort(request.user, view_sort_id)
        ViewHandler().delete_sort(request.user, view)

        return Response(status=204)
コード例 #4
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)
コード例 #5
0
class ViewView(APIView):
    permission_classes = (IsAuthenticated,)

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name='view_id',
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description='Returns the view related to the provided value.'
            )
        ],
        tags=['Database table views'],
        operation_id='get_database_table_view',
        description=(
            'Returns the existing view if the authorized user has access to the '
            'related database\'s group. Depending on the type different properties'
            'could be returned.'
        ),
        responses={
            200: PolymorphicCustomFieldRegistrySerializer(
                view_type_registry,
                ViewSerializer
            ),
            400: get_error_schema(['ERROR_USER_NOT_IN_GROUP']),
            404: get_error_schema(['ERROR_VIEW_DOES_NOT_EXIST'])
        }
    )
    @map_exceptions({
        ViewDoesNotExist: ERROR_VIEW_DOES_NOT_EXIST,
        UserNotInGroupError: ERROR_USER_NOT_IN_GROUP
    })
    @allowed_includes('filters', 'sortings')
    def get(self, request, view_id, filters, sortings):
        """Selects a single view and responds with a serialized version."""

        view = ViewHandler().get_view(request.user, view_id)
        serializer = view_type_registry.get_serializer(
            view,
            ViewSerializer,
            filters=filters,
            sortings=sortings
        )
        return Response(serializer.data)

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name='view_id',
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description='Updates the view related to the provided value.'
            )
        ],
        tags=['Database table views'],
        operation_id='update_database_table_view',
        description=(
            'Updates the existing view if the authorized user has access to the '
            'related database\'s group. The type cannot be changed. It depends on the '
            'existing type which properties can be changed.'
        ),
        request=PolymorphicCustomFieldRegistrySerializer(
            view_type_registry,
            UpdateViewSerializer
        ),
        responses={
            200: PolymorphicCustomFieldRegistrySerializer(
                view_type_registry,
                ViewSerializer
            ),
            400: get_error_schema([
                'ERROR_USER_NOT_IN_GROUP', 'ERROR_REQUEST_BODY_VALIDATION'
            ]),
            404: get_error_schema(['ERROR_VIEW_DOES_NOT_EXIST'])
        }
    )
    @transaction.atomic
    @map_exceptions({
        ViewDoesNotExist: ERROR_VIEW_DOES_NOT_EXIST,
        UserNotInGroupError: ERROR_USER_NOT_IN_GROUP
    })
    @allowed_includes('filters', 'sortings')
    def patch(self, request, view_id, filters, sortings):
        """Updates the view if the user belongs to the group."""

        view = ViewHandler().get_view(request.user, view_id).specific
        view_type = view_type_registry.get_by_model(view)
        data = validate_data_custom_fields(
            view_type.type, view_type_registry, request.data,
            base_serializer_class=UpdateViewSerializer
        )

        view = ViewHandler().update_view(request.user, view, **data)

        serializer = view_type_registry.get_serializer(
            view,
            ViewSerializer,
            filters=filters,
            sortings=sortings
        )
        return Response(serializer.data)

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name='view_id',
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description='Deletes the view related to the provided value.'
            )
        ],
        tags=['Database table views'],
        operation_id='delete_database_table_view',
        description=(
            'Deletes the existing view if the authorized user has access to the '
            'related database\'s group. Note that all the related settings of the '
            'view are going to be deleted also. The data stays intact after deleting '
            'the view because this is related to the table and not the view.'
        ),
        responses={
            204: None,
            400: get_error_schema(['ERROR_USER_NOT_IN_GROUP']),
            404: get_error_schema(['ERROR_VIEW_DOES_NOT_EXIST'])
        }
    )
    @transaction.atomic
    @map_exceptions({
        ViewDoesNotExist: ERROR_VIEW_DOES_NOT_EXIST,
        UserNotInGroupError: ERROR_USER_NOT_IN_GROUP
    })
    def delete(self, request, view_id):
        """Deletes an existing view if the user belongs to the group."""

        view = ViewHandler().get_view(request.user, view_id)
        ViewHandler().delete_view(request.user, view)

        return Response(status=204)
コード例 #6
0
class ApplicationsView(APIView):
    permission_classes = (IsAuthenticated, )

    def get_permissions(self):
        if self.request.method == 'GET':
            return [AllowAny()]

        return super().get_permissions()

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name='group_id',
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description=
                'Returns only applications that are in the group related '
                'to the provided value.')
        ],
        tags=['Applications'],
        operation_id='list_applications',
        description=
        ('Lists all the applications of the group related to the provided '
         '`group_id` parameter if the authorized user is in that group. If the'
         'group is related to a template, then this endpoint will be publicly '
         'accessible. The properties that belong to the application can differ per '
         'type. An application always belongs to a single group.'),
        responses={
            200:
            PolymorphicMappingSerializer('Applications',
                                         application_type_serializers,
                                         many=True),
            400:
            get_error_schema(['ERROR_USER_NOT_IN_GROUP']),
            404:
            get_error_schema(['ERROR_GROUP_DOES_NOT_EXIST'])
        })
    @map_exceptions({
        GroupDoesNotExist: ERROR_GROUP_DOES_NOT_EXIST,
        UserNotInGroupError: ERROR_USER_NOT_IN_GROUP
    })
    def get(self, request, group_id):
        """
        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.
        """

        group = CoreHandler().get_group(group_id)
        group.has_user(request.user, raise_error=True, allow_if_template=True)

        applications = Application.objects.select_related(
            'content_type', 'group').filter(group=group)

        data = [
            get_application_serializer(application).data
            for application in applications
        ]
        return Response(data)

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name='group_id',
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description=
                'Creates an application for the group related to the '
                'provided value.')
        ],
        tags=['Applications'],
        operation_id='create_application',
        description=
        ('Creates a new application based on the provided type. The newly created '
         'application is going to be added to the group related to the provided '
         '`group_id` parameter. If the authorized user does not belong to the group '
         'an error will be returned.'),
        request=ApplicationCreateSerializer,
        responses={
            200:
            PolymorphicMappingSerializer('Applications',
                                         application_type_serializers),
            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(ApplicationCreateSerializer)
    @map_exceptions({
        GroupDoesNotExist: ERROR_GROUP_DOES_NOT_EXIST,
        UserNotInGroupError: ERROR_USER_NOT_IN_GROUP
    })
    def post(self, request, data, group_id):
        """Creates a new application for a user."""

        group = CoreHandler().get_group(group_id)
        application = CoreHandler().create_application(request.user,
                                                       group,
                                                       data['type'],
                                                       name=data['name'])

        return Response(get_application_serializer(application).data)
コード例 #7
0
class RowsView(APIView):
    authentication_classes = APIView.authentication_classes + [
        TokenAuthentication
    ]
    permission_classes = (IsAuthenticated, )

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name='table_id',
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description=
                'Returns the rows of the table related to the provided '
                'value.'),
            OpenApiParameter(
                name='page',
                location=OpenApiParameter.QUERY,
                type=OpenApiTypes.INT,
                description='Defines which page of rows should be returned.'),
            OpenApiParameter(
                name='size',
                location=OpenApiParameter.QUERY,
                type=OpenApiTypes.INT,
                description='Defines how many rows should be returned per page.'
            ),
            OpenApiParameter(
                name='search',
                location=OpenApiParameter.QUERY,
                type=OpenApiTypes.STR,
                description=
                'If provided only rows with data that matches the search '
                'query are going to be returned.'),
            OpenApiParameter(
                name='order_by',
                location=OpenApiParameter.QUERY,
                type=OpenApiTypes.STR,
                description=
                'Optionally the rows can be ordered by provided field ids '
                'separated by comma. By default a field is ordered in '
                'ascending (A-Z) order, but by prepending the field with '
                'a \'-\' it can be ordered descending (Z-A). '),
            OpenApiParameter(
                name='filter__{field}__{filter}',
                location=OpenApiParameter.QUERY,
                type=OpenApiTypes.STR,
                description=
                (f'The rows can optionally be filtered by the same view filters '
                 f'available for the views. Multiple filters can be provided if '
                 f'they follow the same format. The field and filter variable '
                 f'indicate how to filter and the value indicates where to filter '
                 f'on.\n\n'
                 f'For example if you provide the following GET parameter '
                 f'`filter__field_1__equal=test` then only rows where the value of '
                 f'field_1 is equal to test are going to be returned.\n\n'
                 f'The following filters are available: '
                 f'{", ".join(view_filter_type_registry.get_types())}.')),
            OpenApiParameter(
                name='filter_type',
                location=
                OpenApiParameter.QUERY,
                type=OpenApiTypes.STR,
                description=
                ('`AND`: Indicates that the rows must match all the provided '
                 'filters.\n'
                 '`OR`: Indicates that the rows only have to match one of the '
                 'filters.\n\n'
                 'This works only if two or more filters are provided.')),
            OpenApiParameter(
                name='include',
                location=
                OpenApiParameter.QUERY,
                type=OpenApiTypes.STR,
                description=
                ('All the fields are included in the response by default. You can '
                 'select a subset of fields by providing the include query '
                 'parameter. If you for example provide the following GET '
                 'parameter `include=field_1,field_2` then only the fields with'
                 'id `1` and id `2` are going to be selected and included in the '
                 'response. ')),
            OpenApiParameter(
                name='exclude',
                location=
                OpenApiParameter.QUERY,
                type=OpenApiTypes.STR,
                description=
                ('All the fields are included in the response by default. You can '
                 'select a subset of fields by providing the exclude query '
                 'parameter. If you for example provide the following GET '
                 'parameter `exclude=field_1,field_2` then the fields with id `1` '
                 'and id `2` are going to be excluded from the selection and '
                 'response.')),
        ],
        tags=['Database table rows'],
        operation_id='list_database_table_rows',
        description
        =
        ('Lists all the rows of the table related to the provided parameter if the '
         'user has access to the related database\'s group. The response is '
         'paginated by a page/size style. It is also possible to provide an '
         'optional search query, only rows where the data matches the search query '
         'are going to be returned then. The properties of the returned rows '
         'depends on which fields the table has. For a complete overview of fields '
         'use the **list_database_table_fields** endpoint to list them all. In the '
         'example all field types are listed, but normally the number in '
         'field_{id} key is going to be the id of the field. The value is what the '
         'user has provided and the format of it depends on the fields type.'),
        responses={
            200:
            example_pagination_row_serializer_class,
            400:
            get_error_schema([
                'ERROR_USER_NOT_IN_GROUP', 'ERROR_REQUEST_BODY_VALIDATION',
                'ERROR_PAGE_SIZE_LIMIT', 'ERROR_INVALID_PAGE',
                'ERROR_ORDER_BY_FIELD_NOT_FOUND',
                'ERROR_ORDER_BY_FIELD_NOT_POSSIBLE',
                'ERROR_FILTER_FIELD_NOT_FOUND',
                'ERROR_VIEW_FILTER_TYPE_DOES_NOT_EXIST',
                'ERROR_VIEW_FILTER_TYPE_NOT_ALLOWED_FOR_FIELD'
            ]),
            401:
            get_error_schema(['ERROR_NO_PERMISSION_TO_TABLE']),
            404:
            get_error_schema(['ERROR_TABLE_DOES_NOT_EXIST'])
        })
    @map_exceptions({
        UserNotInGroupError:
        ERROR_USER_NOT_IN_GROUP,
        TableDoesNotExist:
        ERROR_TABLE_DOES_NOT_EXIST,
        NoPermissionToTable:
        ERROR_NO_PERMISSION_TO_TABLE,
        OrderByFieldNotFound:
        ERROR_ORDER_BY_FIELD_NOT_FOUND,
        OrderByFieldNotPossible:
        ERROR_ORDER_BY_FIELD_NOT_POSSIBLE,
        FilterFieldNotFound:
        ERROR_FILTER_FIELD_NOT_FOUND,
        ViewFilterTypeDoesNotExist:
        ERROR_VIEW_FILTER_TYPE_DOES_NOT_EXIST,
        ViewFilterTypeNotAllowedForField:
        ERROR_VIEW_FILTER_TYPE_NOT_ALLOWED_FOR_FIELD
    })
    def get(self, request, table_id):
        """
        Lists all the rows of the given table id paginated. It is also possible to
        provide a search query.
        """

        table = TableHandler().get_table(table_id)
        table.database.group.has_user(request.user, raise_error=True)

        TokenHandler().check_table_permissions(request, 'read', table, False)
        search = request.GET.get('search')
        order_by = request.GET.get('order_by')
        include = request.GET.get('include')
        exclude = request.GET.get('exclude')
        fields = RowHandler().get_include_exclude_fields(
            table, include, exclude)

        model = table.get_model(fields=fields,
                                field_ids=[] if fields else None)
        queryset = model.objects.all().enhance_by_fields()

        if search:
            queryset = queryset.search_all_fields(search)

        if order_by:
            queryset = queryset.order_by_fields_string(order_by)

        filter_type = (FILTER_TYPE_OR
                       if str(request.GET.get('filter_type')).upper() == 'OR'
                       else FILTER_TYPE_AND)
        filter_object = {
            key: request.GET.getlist(key)
            for key in request.GET.keys()
        }
        queryset = queryset.filter_by_fields_object(filter_object, filter_type)

        paginator = PageNumberPagination(
            limit_page_size=settings.ROW_PAGE_SIZE_LIMIT)
        page = paginator.paginate_queryset(queryset, request, self)
        serializer_class = get_row_serializer_class(model,
                                                    RowSerializer,
                                                    is_response=True)
        serializer = serializer_class(page, many=True)

        return paginator.get_paginated_response(serializer.data)

    @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.'),
            OpenApiParameter(
                name='before',
                location=OpenApiParameter.QUERY,
                type=OpenApiTypes.INT,
                description='If provided then the newly created row will be '
                'positioned before the row with the provided id.')
        ],
        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']),
            401:
            get_error_schema(['ERROR_NO_PERMISSION_TO_TABLE']),
            404:
            get_error_schema(
                ['ERROR_TABLE_DOES_NOT_EXIST', 'ERROR_ROW_DOES_NOT_EXIST'])
        })
    @transaction.atomic
    @map_exceptions({
        UserNotInGroupError: ERROR_USER_NOT_IN_GROUP,
        TableDoesNotExist: ERROR_TABLE_DOES_NOT_EXIST,
        NoPermissionToTable: ERROR_NO_PERMISSION_TO_TABLE,
        UserFileDoesNotExist: ERROR_USER_FILE_DOES_NOT_EXIST,
        RowDoesNotExist: ERROR_ROW_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(table_id)
        TokenHandler().check_table_permissions(request, 'create', table, False)
        model = table.get_model()

        validation_serializer = get_row_serializer_class(model)
        data = validate_data(validation_serializer, request.data)

        before_id = request.GET.get('before')
        before = (RowHandler().get_row(request.user, table, before_id, model)
                  if before_id else None)

        row = RowHandler().create_row(request.user,
                                      table,
                                      data,
                                      model,
                                      before=before)
        serializer_class = get_row_serializer_class(model,
                                                    RowSerializer,
                                                    is_response=True)
        serializer = serializer_class(row)

        return Response(serializer.data)
コード例 #8
0
class ViewFilterView(APIView):
    permission_classes = (IsAuthenticated,)

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name="view_filter_id",
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description="Returns the view filter related to the provided value.",
            )
        ],
        tags=["Database table view filters"],
        operation_id="get_database_table_view_filter",
        description=(
            "Returns the existing view filter if the authorized user has access to the"
            " related database's group."
        ),
        responses={
            200: ViewFilterSerializer(),
            400: get_error_schema(["ERROR_USER_NOT_IN_GROUP"]),
            404: get_error_schema(["ERROR_VIEW_FILTER_DOES_NOT_EXIST"]),
        },
    )
    @map_exceptions(
        {
            ViewFilterDoesNotExist: ERROR_VIEW_FILTER_DOES_NOT_EXIST,
            UserNotInGroup: ERROR_USER_NOT_IN_GROUP,
        }
    )
    def get(self, request, view_filter_id):
        """Selects a single filter and responds with a serialized version."""

        view_filter = ViewHandler().get_filter(request.user, view_filter_id)
        serializer = ViewFilterSerializer(view_filter)
        return Response(serializer.data)

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name="view_filter_id",
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description="Updates the view filter related to the provided value.",
            )
        ],
        tags=["Database table view filters"],
        operation_id="update_database_table_view_filter",
        description=(
            "Updates the existing filter if the authorized user has access to the "
            "related database's group."
        ),
        request=UpdateViewFilterSerializer(),
        responses={
            200: ViewFilterSerializer(),
            400: get_error_schema(
                [
                    "ERROR_USER_NOT_IN_GROUP",
                    "ERROR_FIELD_NOT_IN_TABLE",
                    "ERROR_VIEW_FILTER_NOT_SUPPORTED",
                    "ERROR_VIEW_FILTER_TYPE_UNSUPPORTED_FIELD",
                ]
            ),
            404: get_error_schema(["ERROR_VIEW_FILTER_DOES_NOT_EXIST"]),
        },
    )
    @transaction.atomic
    @validate_body(UpdateViewFilterSerializer)
    @map_exceptions(
        {
            ViewFilterDoesNotExist: ERROR_VIEW_FILTER_DOES_NOT_EXIST,
            UserNotInGroup: ERROR_USER_NOT_IN_GROUP,
            FieldNotInTable: ERROR_FIELD_NOT_IN_TABLE,
            ViewFilterTypeNotAllowedForField: ERROR_VIEW_FILTER_TYPE_UNSUPPORTED_FIELD,
        }
    )
    def patch(self, request, data, view_filter_id):
        """Updates the view filter if the user belongs to the group."""

        handler = ViewHandler()
        view_filter = handler.get_filter(
            request.user,
            view_filter_id,
            base_queryset=ViewFilter.objects.select_for_update(),
        )

        if "field" in data:
            # We can safely assume the field exists because the
            # UpdateViewFilterSerializer has already checked that.
            data["field"] = Field.objects.get(pk=data["field"])

        if "type" in data:
            data["type_name"] = data.pop("type")

        view_filter = handler.update_filter(request.user, view_filter, **data)

        serializer = ViewFilterSerializer(view_filter)
        return Response(serializer.data)

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name="view_filter_id",
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description="Deletes the filter related to the provided value.",
            )
        ],
        tags=["Database table view filters"],
        operation_id="delete_database_table_view_filter",
        description=(
            "Deletes the existing filter 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_VIEW_FILTER_DOES_NOT_EXIST"]),
        },
    )
    @transaction.atomic
    @map_exceptions(
        {
            ViewFilterDoesNotExist: ERROR_VIEW_FILTER_DOES_NOT_EXIST,
            UserNotInGroup: ERROR_USER_NOT_IN_GROUP,
        }
    )
    def delete(self, request, view_filter_id):
        """Deletes an existing filter if the user belongs to the group."""

        view = ViewHandler().get_filter(request.user, view_filter_id)
        ViewHandler().delete_filter(request.user, view)

        return Response(status=204)
コード例 #9
0
class ViewsView(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 views of the table related to the provided "
                "value.",
            ),
            OpenApiParameter(
                name="include",
                location=OpenApiParameter.QUERY,
                type=OpenApiTypes.STR,
                description=(
                    "A comma separated list of extra attributes to include on each "
                    "view in the response. The supported attributes are `filters` and "
                    "`sortings`. For example `include=filters,sortings` will add the "
                    "attributes `filters` and `sortings` to every returned view, "
                    "containing a list of the views filters and sortings respectively."
                ),
            ),
        ],
        tags=["Database table views"],
        operation_id="list_database_table_views",
        description=(
            "Lists all views of the table related to the provided `table_id` 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 can have multiple views. Each view can display the data in a "
            "different way. For example the `grid` view shows the in a spreadsheet "
            "like way. That type has custom endpoints for data retrieval and "
            "manipulation. In the future other views types like a calendar or Kanban "
            "are going to be added. Each type can have different properties."
        ),
        responses={
            200: PolymorphicCustomFieldRegistrySerializer(
                view_type_registry, ViewSerializer, 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,
            UserNotInGroup: ERROR_USER_NOT_IN_GROUP,
        }
    )
    @allowed_includes("filters", "sortings")
    def get(self, request, table_id, filters, sortings):
        """
        Responds with a list of serialized views 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
        )
        views = View.objects.filter(table=table).select_related("content_type")

        if filters:
            views = views.prefetch_related("viewfilter_set")

        if sortings:
            views = views.prefetch_related("viewsort_set")

        data = [
            view_type_registry.get_serializer(
                view, ViewSerializer, filters=filters, sortings=sortings
            ).data
            for view in views
        ]
        return Response(data)

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name="table_id",
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description="Creates a view for the table related to the provided "
                "value.",
            ),
            OpenApiParameter(
                name="include",
                location=OpenApiParameter.QUERY,
                type=OpenApiTypes.STR,
                description=(
                    "A comma separated list of extra attributes to include on each "
                    "view in the response. The supported attributes are `filters` and "
                    "`sortings`. "
                    "For example `include=filters,sortings` will add the attributes "
                    "`filters` and `sortings` to every returned view, containing "
                    "a list of the views filters and sortings respectively."
                ),
            ),
        ],
        tags=["Database table views"],
        operation_id="create_database_table_view",
        description=(
            "Creates a new view 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(
            view_type_registry, CreateViewSerializer
        ),
        responses={
            200: PolymorphicCustomFieldRegistrySerializer(
                view_type_registry, ViewSerializer
            ),
            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(
        view_type_registry, base_serializer_class=CreateViewSerializer
    )
    @map_exceptions(
        {
            TableDoesNotExist: ERROR_TABLE_DOES_NOT_EXIST,
            UserNotInGroup: ERROR_USER_NOT_IN_GROUP,
        }
    )
    @allowed_includes("filters", "sortings")
    def post(self, request, data, table_id, filters, sortings):
        """Creates a new view for a user."""

        table = TableHandler().get_table(table_id)
        view = ViewHandler().create_view(request.user, table, data.pop("type"), **data)

        serializer = view_type_registry.get_serializer(
            view, ViewSerializer, filters=filters, sortings=sortings
        )
        return Response(serializer.data)
コード例 #10
0
class ViewView(APIView):
    permission_classes = (IsAuthenticated,)

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name="view_id",
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description="Returns the view related to the provided value.",
            ),
            OpenApiParameter(
                name="include",
                location=OpenApiParameter.QUERY,
                type=OpenApiTypes.STR,
                description=(
                    "A comma separated list of extra attributes to include on the "
                    "returned view. The supported attributes are are `filters` and "
                    "`sortings`. "
                    "For example `include=filters,sortings` will add the attributes "
                    "`filters` and `sortings` to every returned view, containing "
                    "a list of the views filters and sortings respectively."
                ),
            ),
        ],
        tags=["Database table views"],
        operation_id="get_database_table_view",
        description=(
            "Returns the existing view if the authorized user has access to the "
            "related database's group. Depending on the type different properties"
            "could be returned."
        ),
        responses={
            200: PolymorphicCustomFieldRegistrySerializer(
                view_type_registry, ViewSerializer
            ),
            400: get_error_schema(["ERROR_USER_NOT_IN_GROUP"]),
            404: get_error_schema(["ERROR_VIEW_DOES_NOT_EXIST"]),
        },
    )
    @map_exceptions(
        {
            ViewDoesNotExist: ERROR_VIEW_DOES_NOT_EXIST,
            UserNotInGroup: ERROR_USER_NOT_IN_GROUP,
        }
    )
    @allowed_includes("filters", "sortings")
    def get(self, request, view_id, filters, sortings):
        """Selects a single view and responds with a serialized version."""

        view = ViewHandler().get_view(view_id)
        view.table.database.group.has_user(request.user, raise_error=True)
        serializer = view_type_registry.get_serializer(
            view, ViewSerializer, filters=filters, sortings=sortings
        )
        return Response(serializer.data)

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name="view_id",
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description="Updates the view related to the provided value.",
            ),
            OpenApiParameter(
                name="include",
                location=OpenApiParameter.QUERY,
                type=OpenApiTypes.STR,
                description=(
                    "A comma separated list of extra attributes to include on the "
                    "returned view. The supported attributes are are `filters` and "
                    "`sortings`. "
                    "For example `include=filters,sortings` will add the attributes "
                    "`filters` and `sortings` to every returned view, containing "
                    "a list of the views filters and sortings respectively."
                ),
            ),
        ],
        tags=["Database table views"],
        operation_id="update_database_table_view",
        description=(
            "Updates the existing view if the authorized user has access to the "
            "related database's group. The type cannot be changed. It depends on the "
            "existing type which properties can be changed."
        ),
        request=PolymorphicCustomFieldRegistrySerializer(
            view_type_registry, UpdateViewSerializer
        ),
        responses={
            200: PolymorphicCustomFieldRegistrySerializer(
                view_type_registry, ViewSerializer
            ),
            400: get_error_schema(
                ["ERROR_USER_NOT_IN_GROUP", "ERROR_REQUEST_BODY_VALIDATION"]
            ),
            404: get_error_schema(["ERROR_VIEW_DOES_NOT_EXIST"]),
        },
    )
    @transaction.atomic
    @map_exceptions(
        {
            ViewDoesNotExist: ERROR_VIEW_DOES_NOT_EXIST,
            UserNotInGroup: ERROR_USER_NOT_IN_GROUP,
        }
    )
    @allowed_includes("filters", "sortings")
    def patch(self, request, view_id, filters, sortings):
        """Updates the view if the user belongs to the group."""

        view = ViewHandler().get_view(view_id).specific
        view_type = view_type_registry.get_by_model(view)
        data = validate_data_custom_fields(
            view_type.type,
            view_type_registry,
            request.data,
            base_serializer_class=UpdateViewSerializer,
        )

        view = ViewHandler().update_view(request.user, view, **data)

        serializer = view_type_registry.get_serializer(
            view, ViewSerializer, filters=filters, sortings=sortings
        )
        return Response(serializer.data)

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name="view_id",
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description="Deletes the view related to the provided value.",
            )
        ],
        tags=["Database table views"],
        operation_id="delete_database_table_view",
        description=(
            "Deletes the existing view if the authorized user has access to the "
            "related database's group. Note that all the related settings of the "
            "view are going to be deleted also. The data stays intact after deleting "
            "the view because this is related to the table and not the view."
        ),
        responses={
            204: None,
            400: get_error_schema(["ERROR_USER_NOT_IN_GROUP"]),
            404: get_error_schema(["ERROR_VIEW_DOES_NOT_EXIST"]),
        },
    )
    @transaction.atomic
    @map_exceptions(
        {
            ViewDoesNotExist: ERROR_VIEW_DOES_NOT_EXIST,
            UserNotInGroup: ERROR_USER_NOT_IN_GROUP,
        }
    )
    def delete(self, request, view_id):
        """Deletes an existing view if the user belongs to the group."""

        view = ViewHandler().get_view(view_id)
        ViewHandler().delete_view(request.user, view)

        return Response(status=204)
コード例 #11
0
class ViewFiltersView(APIView):
    permission_classes = (IsAuthenticated,)

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name="view_id",
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description="Returns only filters of the view related to the provided "
                "value.",
            )
        ],
        tags=["Database table view filters"],
        operation_id="list_database_table_view_filters",
        description=(
            "Lists all filters of the view related to the provided `view_id` if the "
            "user has access to the related database's group. A view can have "
            "multiple filters. When all the rows are requested for the view only those "
            "that apply to the filters are returned."
        ),
        responses={
            200: ViewFilterSerializer(many=True),
            400: get_error_schema(["ERROR_USER_NOT_IN_GROUP"]),
            404: get_error_schema(["ERROR_VIEW_DOES_NOT_EXIST"]),
        },
    )
    @map_exceptions(
        {
            ViewDoesNotExist: ERROR_VIEW_DOES_NOT_EXIST,
            UserNotInGroup: ERROR_USER_NOT_IN_GROUP,
        }
    )
    def get(self, request, view_id):
        """
        Responds with a list of serialized filters that belong to the view if the user
        has access to that group.
        """

        view = ViewHandler().get_view(view_id)
        view.table.database.group.has_user(request.user, raise_error=True)
        filters = ViewFilter.objects.filter(view=view)
        serializer = ViewFilterSerializer(filters, many=True)
        return Response(serializer.data)

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name="view_id",
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description="Creates a filter for the view related to the provided "
                "value.",
            )
        ],
        tags=["Database table view filters"],
        operation_id="create_database_table_view_filter",
        description=(
            "Creates a new filter for the view related to the provided `view_id` "
            "parameter if the authorized user has access to the related database's "
            "group. When the rows of a view are requested, for example via the "
            "`list_database_table_grid_view_rows` endpoint, then only the rows that "
            "apply to all the filters are going to be returned. A filter compares the "
            "value of a field to the value of a filter. It depends on the type how "
            "values are going to be compared."
        ),
        request=CreateViewFilterSerializer(),
        responses={
            200: ViewFilterSerializer(),
            400: get_error_schema(
                [
                    "ERROR_USER_NOT_IN_GROUP",
                    "ERROR_REQUEST_BODY_VALIDATION",
                    "ERROR_FIELD_NOT_IN_TABLE",
                    "ERROR_VIEW_FILTER_NOT_SUPPORTED",
                    "ERROR_VIEW_FILTER_TYPE_UNSUPPORTED_FIELD",
                ]
            ),
            404: get_error_schema(["ERROR_VIEW_DOES_NOT_EXIST"]),
        },
    )
    @transaction.atomic
    @validate_body(CreateViewFilterSerializer)
    @map_exceptions(
        {
            ViewDoesNotExist: ERROR_VIEW_DOES_NOT_EXIST,
            UserNotInGroup: ERROR_USER_NOT_IN_GROUP,
            FieldNotInTable: ERROR_FIELD_NOT_IN_TABLE,
            ViewFilterNotSupported: ERROR_VIEW_FILTER_NOT_SUPPORTED,
            ViewFilterTypeNotAllowedForField: ERROR_VIEW_FILTER_TYPE_UNSUPPORTED_FIELD,
        }
    )
    def post(self, request, data, view_id):
        """Creates a new filter for the provided view."""

        view_handler = ViewHandler()
        view = view_handler.get_view(view_id)
        # We can safely assume the field exists because the CreateViewFilterSerializer
        # has already checked that.
        field = Field.objects.get(pk=data["field"])
        view_filter = view_handler.create_filter(
            request.user, view, field, data["type"], data["value"]
        )

        serializer = ViewFilterSerializer(view_filter)
        return Response(serializer.data)
コード例 #12
0
class GroupUserView(APIView):
    permission_classes = (IsAuthenticated, )

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name="group_user_id",
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description=
                "Updates the group user related to the provided value.",
            )
        ],
        tags=["Groups"],
        operation_id="update_group_user",
        description=(
            "Updates the existing group user related to the provided "
            "`group_user_id` param if the authorized user has admin rights to "
            "the related group."),
        request=UpdateGroupUserSerializer,
        responses={
            200:
            GroupUserGroupSerializer,
            400:
            get_error_schema([
                "ERROR_USER_NOT_IN_GROUP",
                "ERROR_USER_INVALID_GROUP_PERMISSIONS",
                "ERROR_REQUEST_BODY_VALIDATION",
            ]),
            404:
            get_error_schema(["ERROR_GROUP_USER_DOES_NOT_EXIST"]),
        },
    )
    @transaction.atomic
    @validate_body(UpdateGroupUserSerializer)
    @map_exceptions({
        GroupUserDoesNotExist:
        ERROR_GROUP_USER_DOES_NOT_EXIST,
        UserNotInGroup:
        ERROR_USER_NOT_IN_GROUP,
        UserInvalidGroupPermissionsError:
        ERROR_USER_INVALID_GROUP_PERMISSIONS,
    })
    def patch(self, request, data, group_user_id):
        """Updates the group user if the user has admin permissions to the group."""

        group_user = CoreHandler().get_group_user(
            group_user_id, base_queryset=GroupUser.objects.select_for_update())
        group_user = CoreHandler().update_group_user(request.user, group_user,
                                                     **data)
        return Response(GroupUserGroupSerializer(group_user).data)

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name="group_user_id",
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description="Deletes the group user related to the provided "
                "value.",
            )
        ],
        tags=["Groups"],
        operation_id="delete_group_user",
        description=(
            "Deletes a group user if the authorized user has admin rights to "
            "the related group."),
        responses={
            204:
            None,
            400:
            get_error_schema([
                "ERROR_USER_NOT_IN_GROUP",
                "ERROR_USER_INVALID_GROUP_PERMISSIONS"
            ]),
            404:
            get_error_schema(["ERROR_GROUP_INVITATION_DOES_NOT_EXIST"]),
        },
    )
    @transaction.atomic
    @map_exceptions({
        GroupUserDoesNotExist:
        ERROR_GROUP_USER_DOES_NOT_EXIST,
        UserNotInGroup:
        ERROR_USER_NOT_IN_GROUP,
        UserInvalidGroupPermissionsError:
        ERROR_USER_INVALID_GROUP_PERMISSIONS,
    })
    def delete(self, request, group_user_id):
        """Deletes an existing group_user if the user belongs to the group."""

        group_user = CoreHandler().get_group_user(
            group_user_id, base_queryset=GroupUser.objects.select_for_update())
        CoreHandler().delete_group_user(request.user, group_user)
        return Response(status=204)
コード例 #13
0
class RowView(APIView):
    permission_classes = (IsAuthenticated, )

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name='table_id',
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description='Updates the row in the table related to the value.'
            ),
            OpenApiParameter(
                name='row_id',
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description='Updates the row related to the value.')
        ],
        tags=['Database table rows'],
        operation_id='update_database_table_row',
        description=
        ('Updates an existing 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** endpoint to list them all. None of the '
         'fields are required, if they are not provided the value is not going to '
         'be updated. If you want to update 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', 'ERROR_ROW_DOES_NOT_EXIST'])
        })
    @transaction.atomic
    @map_exceptions({
        UserNotInGroupError: ERROR_USER_NOT_IN_GROUP,
        TableDoesNotExist: ERROR_TABLE_DOES_NOT_EXIST,
        RowDoesNotExist: ERROR_ROW_DOES_NOT_EXIST
    })
    def patch(self, request, table_id, row_id):
        """
        Updates the row with the given row_id for the table with the given
        table_id. Also the post data is validated according to the tables field types.
        """

        table = TableHandler().get_table(request.user, table_id)

        # Small side effect of generating the model for only the fields that need to
        # change is that the response it not going to contain the other fields. It is
        # however much faster because it doesn't need to get the specific version of
        # all the field objects.
        field_ids = RowHandler().extract_field_ids_from_dict(request.data)
        model = table.get_model(field_ids=field_ids)

        validation_serializer = get_row_serializer_class(model)
        data = validate_data(validation_serializer, request.data)

        row = RowHandler().update_row(request.user, table, row_id, data, model)

        serializer_class = get_row_serializer_class(model,
                                                    RowSerializer,
                                                    is_response=True)
        serializer = serializer_class(row)

        return Response(serializer.data)

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name='table_id',
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description='Deletes the row in the table related to the value.'
            ),
            OpenApiParameter(
                name='row_id',
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description='Deletes the row related to the value.')
        ],
        tags=['Database table rows'],
        operation_id='delete_database_table_row',
        description=
        ('Deletes an existing row in the table if the user has access to the '
         'table\'s group.'),
        responses={
            204:
            None,
            400:
            get_error_schema(['ERROR_USER_NOT_IN_GROUP']),
            404:
            get_error_schema(
                ['ERROR_TABLE_DOES_NOT_EXIST', 'ERROR_ROW_DOES_NOT_EXIST'])
        })
    @transaction.atomic
    @map_exceptions({
        UserNotInGroupError: ERROR_USER_NOT_IN_GROUP,
        TableDoesNotExist: ERROR_TABLE_DOES_NOT_EXIST,
        RowDoesNotExist: ERROR_ROW_DOES_NOT_EXIST
    })
    def delete(self, request, table_id, row_id):
        """
        Deletes an existing row with the given row_id for table with the given table_id.
        """

        table = TableHandler().get_table(request.user, table_id)
        RowHandler().delete_row(request.user, table, row_id)

        return Response(status=204)
コード例 #14
0
ファイル: views.py プロジェクト: jbjuin/baserow
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,
        UserNotInGroup: ERROR_USER_NOT_IN_GROUP,
    })
    def get(self, request, database_id):
        """Lists all the tables of a database."""

        database = CoreHandler().get_application(
            database_id, base_queryset=Database.objects)
        database.group.has_user(request.user, raise_error=True)
        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=TableCreateSerializer,
        responses={
            200:
            TableSerializer,
            400:
            get_error_schema([
                "ERROR_USER_NOT_IN_GROUP",
                "ERROR_REQUEST_BODY_VALIDATION",
                "ERROR_INVALID_INITIAL_TABLE_DATA",
                "ERROR_INITIAL_TABLE_DATA_LIMIT_EXCEEDED",
            ]),
            404:
            get_error_schema(["ERROR_APPLICATION_DOES_NOT_EXIST"]),
        },
    )
    @transaction.atomic
    @map_exceptions({
        ApplicationDoesNotExist: ERROR_APPLICATION_DOES_NOT_EXIST,
        UserNotInGroup: ERROR_USER_NOT_IN_GROUP,
        InvalidInitialTableData: ERROR_INVALID_INITIAL_TABLE_DATA,
        InitialTableDataLimitExceeded: ERROR_INITIAL_TABLE_DATA_LIMIT_EXCEEDED,
        MaxFieldLimitExceeded: ERROR_MAX_FIELD_COUNT_EXCEEDED,
    })
    @validate_body(TableCreateSerializer)
    def post(self, request, data, database_id):
        """Creates a new table in a database."""

        database = CoreHandler().get_application(
            database_id, base_queryset=Database.objects)
        table = TableHandler().create_table(request.user,
                                            database,
                                            fill_example=True,
                                            **data)
        serializer = TableSerializer(table)
        return Response(serializer.data)
コード例 #15
0
ファイル: views.py プロジェクト: jechaviz/baserow
class ViewFilterView(APIView):
    permission_classes = (IsAuthenticated, )

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name='view_filter_id',
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description=
                'Returns the view filter related to the provided value.')
        ],
        tags=['Database table view filters'],
        operation_id='get_database_table_view_filter',
        description=
        ('Returns the existing view filter if the authorized user has access to the'
         ' related database\'s group.'),
        responses={
            200: ViewFilterSerializer(),
            400: get_error_schema(['ERROR_USER_NOT_IN_GROUP']),
            404: get_error_schema(['ERROR_VIEW_FILTER_DOES_NOT_EXIST'])
        })
    @map_exceptions({
        ViewFilterDoesNotExist: ERROR_VIEW_FILTER_DOES_NOT_EXIST,
        UserNotInGroupError: ERROR_USER_NOT_IN_GROUP
    })
    def get(self, request, view_filter_id):
        """Selects a single filter and responds with a serialized version."""

        view_filter = ViewHandler().get_filter(request.user, view_filter_id)
        serializer = ViewFilterSerializer(view_filter)
        return Response(serializer.data)

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name='view_filter_id',
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description=
                'Updates the view filter related to the provided value.')
        ],
        tags=['Database table view filters'],
        operation_id='update_database_table_view_filter',
        description=
        ('Updates the existing filter if the authorized user has access to the '
         'related database\'s group.'),
        request=UpdateViewFilterSerializer(),
        responses={
            200:
            ViewFilterSerializer(),
            400:
            get_error_schema([
                'ERROR_USER_NOT_IN_GROUP', 'ERROR_FIELD_NOT_IN_TABLE',
                'ERROR_VIEW_FILTER_NOT_SUPPORTED',
                'ERROR_VIEW_FILTER_TYPE_NOT_ALLOWED_FOR_FIELD'
            ]),
            404:
            get_error_schema(['ERROR_VIEW_FILTER_DOES_NOT_EXIST'])
        })
    @transaction.atomic
    @validate_body(UpdateViewFilterSerializer)
    @map_exceptions({
        ViewFilterDoesNotExist:
        ERROR_VIEW_FILTER_DOES_NOT_EXIST,
        UserNotInGroupError:
        ERROR_USER_NOT_IN_GROUP,
        FieldNotInTable:
        ERROR_FIELD_NOT_IN_TABLE,
        ViewFilterTypeNotAllowedForField:
        ERROR_VIEW_FILTER_TYPE_NOT_ALLOWED_FOR_FIELD
    })
    def patch(self, request, data, view_filter_id):
        """Updates the view filter if the user belongs to the group."""

        handler = ViewHandler()
        view_filter = handler.get_filter(request.user, view_filter_id)

        if 'field' in data:
            # We can safely assume the field exists because the
            # UpdateViewFilterSerializer has already checked that.
            data['field'] = Field.objects.get(pk=data['field'])

        if 'type' in data:
            data['type_name'] = data.pop('type')

        view_filter = handler.update_filter(request.user, view_filter, **data)

        serializer = ViewFilterSerializer(view_filter)
        return Response(serializer.data)

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name='view_filter_id',
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description='Deletes the filter related to the provided value.'
            )
        ],
        tags=['Database table view filters'],
        operation_id='delete_database_table_view_filter',
        description=
        ('Deletes the existing filter 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_VIEW_FILTER_DOES_NOT_EXIST'])
        })
    @transaction.atomic
    @map_exceptions({
        ViewFilterDoesNotExist: ERROR_VIEW_FILTER_DOES_NOT_EXIST,
        UserNotInGroupError: ERROR_USER_NOT_IN_GROUP
    })
    def delete(self, request, view_filter_id):
        """Deletes an existing filter if the user belongs to the group."""

        view = ViewHandler().get_filter(request.user, view_filter_id)
        ViewHandler().delete_filter(request.user, view)

        return Response(status=204)
コード例 #16
0
class ViewSortingsView(APIView):
    permission_classes = (IsAuthenticated,)

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name="view_id",
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description="Returns only sortings of the view related to the provided "
                "value.",
            )
        ],
        tags=["Database table view sortings"],
        operation_id="list_database_table_view_sortings",
        description=(
            "Lists all sortings of the view related to the provided `view_id` if the "
            "user has access to the related database's group. A view can have "
            "multiple sortings. When all the rows are requested they will be in the "
            "desired order."
        ),
        responses={
            200: ViewSortSerializer(many=True),
            400: get_error_schema(["ERROR_USER_NOT_IN_GROUP"]),
            404: get_error_schema(["ERROR_VIEW_DOES_NOT_EXIST"]),
        },
    )
    @map_exceptions(
        {
            ViewDoesNotExist: ERROR_VIEW_DOES_NOT_EXIST,
            UserNotInGroup: ERROR_USER_NOT_IN_GROUP,
        }
    )
    def get(self, request, view_id):
        """
        Responds with a list of serialized sortings that belong to the view if the user
        has access to that group.
        """

        view = ViewHandler().get_view(view_id)
        view.table.database.group.has_user(request.user, raise_error=True)
        sortings = ViewSort.objects.filter(view=view)
        serializer = ViewSortSerializer(sortings, many=True)
        return Response(serializer.data)

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name="view_id",
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description="Creates a sort for the view related to the provided "
                "value.",
            )
        ],
        tags=["Database table view sortings"],
        operation_id="create_database_table_view_sort",
        description=(
            "Creates a new sort for the view related to the provided `view_id` "
            "parameter if the authorized user has access to the related database's "
            "group. When the rows of a view are requested, for example via the "
            "`list_database_table_grid_view_rows` endpoint, they will be returned in "
            "the respected order defined by all the sortings."
        ),
        request=CreateViewSortSerializer(),
        responses={
            200: ViewSortSerializer(),
            400: get_error_schema(
                [
                    "ERROR_USER_NOT_IN_GROUP",
                    "ERROR_REQUEST_BODY_VALIDATION",
                    "ERROR_VIEW_SORT_NOT_SUPPORTED",
                    "ERROR_FIELD_NOT_IN_TABLE",
                    "ERROR_VIEW_SORT_FIELD_ALREADY_EXISTS",
                    "ERROR_VIEW_SORT_FIELD_NOT_SUPPORTED",
                ]
            ),
            404: get_error_schema(["ERROR_VIEW_DOES_NOT_EXIST"]),
        },
    )
    @transaction.atomic
    @validate_body(CreateViewSortSerializer)
    @map_exceptions(
        {
            ViewDoesNotExist: ERROR_VIEW_DOES_NOT_EXIST,
            UserNotInGroup: ERROR_USER_NOT_IN_GROUP,
            FieldNotInTable: ERROR_FIELD_NOT_IN_TABLE,
            ViewSortNotSupported: ERROR_VIEW_SORT_NOT_SUPPORTED,
            ViewSortFieldAlreadyExist: ERROR_VIEW_SORT_FIELD_ALREADY_EXISTS,
            ViewSortFieldNotSupported: ERROR_VIEW_SORT_FIELD_NOT_SUPPORTED,
        }
    )
    def post(self, request, data, view_id):
        """Creates a new sort for the provided view."""

        view_handler = ViewHandler()
        view = view_handler.get_view(view_id)
        # We can safely assume the field exists because the CreateViewSortSerializer
        # has already checked that.
        field = Field.objects.get(pk=data["field"])
        view_sort = view_handler.create_sort(request.user, view, field, data["order"])

        serializer = ViewSortSerializer(view_sort)
        return Response(serializer.data)
コード例 #17
0
class ApplicationView(APIView):
    permission_classes = (IsAuthenticated, )

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name='application_id',
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description=
                'Returns the application related to the provided value.')
        ],
        tags=['Applications'],
        operation_id='get_application',
        description=
        ('Returns the requested application if the authorized user is in the '
         'application\'s group. The properties that belong to the application can '
         'differ per type.'),
        request=ApplicationCreateSerializer,
        responses={
            200:
            PolymorphicMappingSerializer('Applications',
                                         application_type_serializers),
            400:
            get_error_schema(
                ['ERROR_USER_NOT_IN_GROUP', 'ERROR_REQUEST_BODY_VALIDATION']),
            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, application_id):
        """Selects a single application and responds with a serialized version."""

        application = CoreHandler().get_application(application_id)
        application.group.has_user(request.user, raise_error=True)
        return Response(get_application_serializer(application).data)

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name='application_id',
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description=
                'Updates the application related to the provided value.')
        ],
        tags=['Applications'],
        operation_id='update_application',
        description=
        ('Updates the existing application related to the provided '
         '`application_id` param if the authorized user is in the application\'s '
         'group. It is not possible to change the type, but properties like the '
         'name can be changed.'),
        request=ApplicationUpdateSerializer,
        responses={
            200:
            PolymorphicMappingSerializer('Applications',
                                         application_type_serializers),
            400:
            get_error_schema(
                ['ERROR_USER_NOT_IN_GROUP', 'ERROR_REQUEST_BODY_VALIDATION']),
            404:
            get_error_schema(['ERROR_APPLICATION_DOES_NOT_EXIST'])
        },
    )
    @transaction.atomic
    @validate_body(ApplicationUpdateSerializer)
    @map_exceptions({
        ApplicationDoesNotExist: ERROR_APPLICATION_DOES_NOT_EXIST,
        UserNotInGroupError: ERROR_USER_NOT_IN_GROUP
    })
    def patch(self, request, data, application_id):
        """Updates the application if the user belongs to the group."""

        application = CoreHandler().get_application(
            application_id,
            base_queryset=Application.objects.select_for_update())
        application = CoreHandler().update_application(request.user,
                                                       application,
                                                       name=data['name'])
        return Response(get_application_serializer(application).data)

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name='application_id',
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description=
                'Deletes the application related to the provided value.')
        ],
        tags=['Applications'],
        operation_id='delete_application',
        description=
        ('Deletes an application if the authorized user is in the application\'s '
         'group. All the related children are also going to be deleted. For example '
         'in case of a database application all the underlying tables, fields, '
         'views and rows are going to be deleted.'),
        responses={
            204: None,
            400: get_error_schema(['ERROR_USER_NOT_IN_GROUP']),
            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
    })
    def delete(self, request, application_id):
        """Deletes an existing application if the user belongs to the group."""

        application = CoreHandler().get_application(
            application_id,
            base_queryset=Application.objects.select_for_update())
        CoreHandler().delete_application(request.user, application)

        return Response(status=204)
コード例 #18
0
ファイル: views.py プロジェクト: jbjuin/baserow
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,
        UserNotInGroup: 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",
                "ERROR_MAX_FIELD_COUNT_EXCEEDED",
            ]),
            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,
        UserNotInGroup: ERROR_USER_NOT_IN_GROUP,
        MaxFieldLimitExceeded: ERROR_MAX_FIELD_COUNT_EXCEEDED,
    })
    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)
コード例 #19
0
class RowView(APIView):
    authentication_classes = APIView.authentication_classes + [
        TokenAuthentication
    ]
    permission_classes = (IsAuthenticated, )

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name='table_id',
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description=
                'Returns the row of the table related to the provided '
                'value.'),
            OpenApiParameter(
                name='row_id',
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description='Returns the row related the provided value.')
        ],
        tags=['Database table rows'],
        operation_id='get_database_table_row',
        description=
        ('Fetches an existing row from the table if the user has access to the '
         'related table\'s group. The properties of the returned row depend on '
         'which fields the table has. For a complete overview of fields use the '
         '**list_database_table_fields** endpoint to list them all. In the example '
         'all field types are listed, but normally the number in field_{id} key is '
         'going to be the id of the field. The value is what the user has provided '
         'and the format of it depends on the fields type.'),
        responses={
            200:
            get_example_row_serializer_class(True),
            400:
            get_error_schema(
                ['ERROR_USER_NOT_IN_GROUP', 'ERROR_REQUEST_BODY_VALIDATION']),
            401:
            get_error_schema(['ERROR_NO_PERMISSION_TO_TABLE']),
            404:
            get_error_schema(
                ['ERROR_TABLE_DOES_NOT_EXIST', 'ERROR_ROW_DOES_NOT_EXIST'])
        })
    @map_exceptions({
        UserNotInGroupError: ERROR_USER_NOT_IN_GROUP,
        TableDoesNotExist: ERROR_TABLE_DOES_NOT_EXIST,
        RowDoesNotExist: ERROR_ROW_DOES_NOT_EXIST,
        NoPermissionToTable: ERROR_NO_PERMISSION_TO_TABLE
    })
    def get(self, request, table_id, row_id):
        """
        Responds with a serializer version of the row related to the provided row_id
        and table_id.
        """

        table = TableHandler().get_table(table_id)
        TokenHandler().check_table_permissions(request, 'read', table, False)

        model = table.get_model()
        row = RowHandler().get_row(request.user, table, row_id, model)
        serializer_class = get_row_serializer_class(model,
                                                    RowSerializer,
                                                    is_response=True)
        serializer = serializer_class(row)

        return Response(serializer.data)

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name='table_id',
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description='Updates the row in the table related to the value.'
            ),
            OpenApiParameter(
                name='row_id',
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description='Updates the row related to the value.')
        ],
        tags=['Database table rows'],
        operation_id='update_database_table_row',
        description=
        ('Updates an existing 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** endpoint to list them all. None of the '
         'fields are required, if they are not provided the value is not going to '
         'be updated. If you want to update 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']),
            401:
            get_error_schema(['ERROR_NO_PERMISSION_TO_TABLE']),
            404:
            get_error_schema(
                ['ERROR_TABLE_DOES_NOT_EXIST', 'ERROR_ROW_DOES_NOT_EXIST'])
        })
    @transaction.atomic
    @map_exceptions({
        UserNotInGroupError: ERROR_USER_NOT_IN_GROUP,
        TableDoesNotExist: ERROR_TABLE_DOES_NOT_EXIST,
        RowDoesNotExist: ERROR_ROW_DOES_NOT_EXIST,
        NoPermissionToTable: ERROR_NO_PERMISSION_TO_TABLE,
        UserFileDoesNotExist: ERROR_USER_FILE_DOES_NOT_EXIST
    })
    def patch(self, request, table_id, row_id):
        """
        Updates the row with the given row_id for the table with the given
        table_id. Also the post data is validated according to the tables field types.
        """

        table = TableHandler().get_table(table_id)
        TokenHandler().check_table_permissions(request, 'update', table, False)

        field_ids = RowHandler().extract_field_ids_from_dict(request.data)
        model = table.get_model()
        validation_serializer = get_row_serializer_class(model,
                                                         field_ids=field_ids)
        data = validate_data(validation_serializer, request.data)

        row = RowHandler().update_row(request.user, table, row_id, data, model)

        serializer_class = get_row_serializer_class(model,
                                                    RowSerializer,
                                                    is_response=True)
        serializer = serializer_class(row)

        return Response(serializer.data)

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name='table_id',
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description='Deletes the row in the table related to the value.'
            ),
            OpenApiParameter(
                name='row_id',
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description='Deletes the row related to the value.')
        ],
        tags=['Database table rows'],
        operation_id='delete_database_table_row',
        description=
        ('Deletes an existing row in the table if the user has access to the '
         'table\'s group.'),
        responses={
            204:
            None,
            400:
            get_error_schema(['ERROR_USER_NOT_IN_GROUP']),
            404:
            get_error_schema(
                ['ERROR_TABLE_DOES_NOT_EXIST', 'ERROR_ROW_DOES_NOT_EXIST'])
        })
    @transaction.atomic
    @map_exceptions({
        UserNotInGroupError: ERROR_USER_NOT_IN_GROUP,
        TableDoesNotExist: ERROR_TABLE_DOES_NOT_EXIST,
        RowDoesNotExist: ERROR_ROW_DOES_NOT_EXIST,
        NoPermissionToTable: ERROR_NO_PERMISSION_TO_TABLE
    })
    def delete(self, request, table_id, row_id):
        """
        Deletes an existing row with the given row_id for table with the given
        table_id.
        """

        table = TableHandler().get_table(table_id)
        TokenHandler().check_table_permissions(request, 'delete', table, False)
        RowHandler().delete_row(request.user, table, row_id)

        return Response(status=204)
コード例 #20
0
ファイル: views.py プロジェクト: code-watch/baserow
class ViewsView(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 views of the table related to the provided '
                'value.'),
            OpenApiParameter(
                name='include',
                location=OpenApiParameter.QUERY,
                type=OpenApiTypes.STR,
                description=
                ('A comma separated list of extra attributes to include on each '
                 'view in the response. The supported attributes are `filters` and '
                 '`sortings`. For example `include=filters,sortings` will add the '
                 'attributes `filters` and `sortings` to every returned view, '
                 'containing a list of the views filters and sortings respectively.'
                 )),
        ],
        tags=['Database table views'],
        operation_id='list_database_table_views',
        description
        =
        ('Lists all views of the table related to the provided `table_id` 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 can have multiple views. Each view can display the data in a '
         'different way. For example the `grid` view shows the in a spreadsheet '
         'like way. That type has custom endpoints for data retrieval and '
         'manipulation. In the future other views types like a calendar or Kanban '
         'are going to be added. Each type can have different properties.'),
        responses={
            200:
            PolymorphicCustomFieldRegistrySerializer(view_type_registry,
                                                     ViewSerializer,
                                                     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
    })
    @allowed_includes('filters', 'sortings')
    def get(self, request, table_id, filters, sortings):
        """
        Responds with a list of serialized views 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)
        views = View.objects.filter(table=table).select_related('content_type')

        if filters:
            views = views.prefetch_related('viewfilter_set')

        if sortings:
            views = views.prefetch_related('viewsort_set')

        data = [
            view_type_registry.get_serializer(view,
                                              ViewSerializer,
                                              filters=filters,
                                              sortings=sortings).data
            for view in views
        ]
        return Response(data)

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name='table_id',
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description=
                'Creates a view for the table related to the provided '
                'value.'),
            OpenApiParameter(
                name='include',
                location=OpenApiParameter.QUERY,
                type=OpenApiTypes.STR,
                description=
                ('A comma separated list of extra attributes to include on each '
                 'view in the response. The supported attributes are `filters` and '
                 '`sortings`. '
                 'For example `include=filters,sortings` will add the attributes '
                 '`filters` and `sortings` to every returned view, containing '
                 'a list of the views filters and sortings respectively.')),
        ],
        tags=['Database table views'],
        operation_id='create_database_table_view',
        description
        =
        ('Creates a new view 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(
            view_type_registry, CreateViewSerializer),
        responses={
            200:
            PolymorphicCustomFieldRegistrySerializer(view_type_registry,
                                                     ViewSerializer),
            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(view_type_registry,
                                 base_serializer_class=CreateViewSerializer)
    @map_exceptions({
        TableDoesNotExist: ERROR_TABLE_DOES_NOT_EXIST,
        UserNotInGroupError: ERROR_USER_NOT_IN_GROUP
    })
    @allowed_includes('filters', 'sortings')
    def post(self, request, data, table_id, filters, sortings):
        """Creates a new view for a user."""

        table = TableHandler().get_table(table_id)
        view = ViewHandler().create_view(request.user, table, data.pop('type'),
                                         **data)

        serializer = view_type_registry.get_serializer(view,
                                                       ViewSerializer,
                                                       filters=filters,
                                                       sortings=sortings)
        return Response(serializer.data)
コード例 #21
0
ファイル: views.py プロジェクト: zarybnicky/baserow
class GridViewView(APIView):
    permission_classes = (IsAuthenticated, )

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name='view_id',
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description=
                'Returns only rows that belong to the related view\'s '
                'table.'),
            OpenApiParameter(
                name='count',
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.NONE,
                description='If provided only the count will be returned.'),
            OpenApiParameter(
                name='include',
                location=OpenApiParameter.QUERY,
                type=OpenApiTypes.STR,
                description=
                ('Can contain `field_options` which will add an object with the '
                 'same name to the response if included. That object contains '
                 'user defined view settings for each field. For example the '
                 'field\'s width is included in here.')),
            OpenApiParameter(
                name='limit',
                location=OpenApiParameter.QUERY,
                type=OpenApiTypes.INT,
                description='Defines how many rows should be returned.'),
            OpenApiParameter(
                name='offset',
                location=OpenApiParameter.QUERY,
                type=OpenApiTypes.INT,
                description='Can only be used in combination with the `limit` '
                'parameter and defines from which offset the rows should '
                'be returned.'),
            OpenApiParameter(
                name='page',
                location=OpenApiParameter.QUERY,
                type=OpenApiTypes.INT,
                description=
                'Defines which page of rows should be returned. Either '
                'the `page` or `limit` can be provided, not both.'),
            OpenApiParameter(
                name='size',
                location=
                OpenApiParameter.QUERY,
                type=OpenApiTypes.INT,
                description=
                'Can only be used in combination with the `page` parameter '
                'and defines how many rows should be returned.')
        ],
        tags=['Database table grid view'],
        operation_id='list_database_table_grid_view_rows',
        description
        =
        ('Lists the requested rows of the view\'s table related to the provided '
         '`view_id` if the authorized user has access to the database\'s group. '
         'The response is paginated either by a limit/offset or page/size style. '
         'The style depends on the provided GET parameters. The properties of the '
         'returned rows depends on which fields the table has. For a complete '
         'overview of fields use the **list_database_table_fields** endpoint to '
         'list them all. In the example all field types are listed, but normally '
         'the number in field_{id} key is going to be the id of the field. '
         'The value is what the user has provided and the format of it depends on '
         'the fields type.\n'
         '\n'
         'The filters and sortings are automatically applied. To get a full '
         'overview of the applied filters and sortings you can use the '
         '`list_database_table_view_filters` and '
         '`list_database_table_view_sortings` endpoints.'),
        responses={
            200: example_pagination_row_serializer_class,
            400: get_error_schema(['ERROR_USER_NOT_IN_GROUP']),
            404: get_error_schema(['ERROR_GRID_DOES_NOT_EXIST'])
        })
    @map_exceptions({
        UserNotInGroupError: ERROR_USER_NOT_IN_GROUP,
        ViewDoesNotExist: ERROR_GRID_DOES_NOT_EXIST
    })
    @allowed_includes('field_options')
    def get(self, request, view_id, field_options):
        """
        Lists all the rows of a grid view, paginated either by a page or offset/limit.
        If the limit get parameter is provided the limit/offset pagination will be used
        else the page number pagination.

        Optionally the field options can also be included in the response if the the
        `field_options` are provided in the includes GET parameter.
        """

        view_handler = ViewHandler()
        view = view_handler.get_view(view_id, GridView)
        view.table.database.group.has_user(request.user, raise_error=True)

        model = view.table.get_model()
        queryset = model.objects.all().enhance_by_fields()

        # Applies the view filters and sortings to the queryset if there are any.
        queryset = view_handler.apply_filters(view, queryset)
        queryset = view_handler.apply_sorting(view, queryset)

        if 'count' in request.GET:
            return Response({'count': queryset.count()})

        if LimitOffsetPagination.limit_query_param in request.GET:
            paginator = LimitOffsetPagination()
        else:
            paginator = PageNumberPagination()

        page = paginator.paginate_queryset(queryset, request, self)
        serializer_class = get_row_serializer_class(model,
                                                    RowSerializer,
                                                    is_response=True)
        serializer = serializer_class(page, many=True)

        response = paginator.get_paginated_response(serializer.data)

        if field_options:
            # The serializer has the GridViewFieldOptionsField which fetches the
            # field options from the database and creates them if they don't exist,
            # but when added to the context the fields don't have to be fetched from
            # the database again when checking if they exist.
            context = {
                'fields': [o['field'] for o in model._field_objects.values()]
            }
            response.data.update(
                **GridViewSerializer(view, context=context).data)

        return response

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name='view_id',
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                required=False,
                description=
                'Returns only rows that belong to the related view\'s '
                'table.')
        ],
        tags=['Database table grid view'],
        operation_id='filter_database_table_grid_view_rows',
        description=
        ('Lists only the rows and fields that match the request. Only the rows '
         'with the ids that are in the `row_ids` list are going to be returned. '
         'Same goes for the fields, only the fields with the ids in the '
         '`field_ids` are going to be returned. This endpoint could be used to '
         'refresh data after changes something. For example in the web frontend '
         'after changing a field type, the data of the related cells will be '
         'refreshed using this endpoint. In the example all field types are listed, '
         'but normally  the number in field_{id} key is going to be the id of the '
         'field. The value is what the user has provided and the format of it '
         'depends on the fields type.'),
        request=GridViewFilterSerializer,
        responses={
            200:
            get_example_row_serializer_class(True)(many=True),
            400:
            get_error_schema(
                ['ERROR_USER_NOT_IN_GROUP', 'ERROR_REQUEST_BODY_VALIDATION']),
            404:
            get_error_schema(['ERROR_GRID_DOES_NOT_EXIST'])
        })
    @map_exceptions({
        UserNotInGroupError: ERROR_USER_NOT_IN_GROUP,
        ViewDoesNotExist: ERROR_GRID_DOES_NOT_EXIST
    })
    @validate_body(GridViewFilterSerializer)
    def post(self, request, view_id, data):
        """
        Row filter endpoint that only lists the requested rows and optionally only the
        requested fields.
        """

        view = ViewHandler().get_view(view_id, GridView)
        view.table.database.group.has_user(request.user, raise_error=True)

        model = view.table.get_model(field_ids=data['field_ids'])
        results = model.objects.filter(pk__in=data['row_ids'])

        serializer_class = get_row_serializer_class(model,
                                                    RowSerializer,
                                                    is_response=True)
        serializer = serializer_class(results, many=True)
        return Response(serializer.data)

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name='view_id',
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                required=False,
                description=
                'Updates the field related to the provided `view_id` '
                'parameter.')
        ],
        tags=['Database table grid view'],
        operation_id='update_database_table_grid_view_field_options',
        description=
        ('Updates the field options of a `grid` view. The field options are unique '
         'options per field for a view. This could for example be used to update '
         'the field width if the user changes it.'),
        request=GridViewSerializer,
        responses={
            200:
            GridViewSerializer,
            400:
            get_error_schema([
                'ERROR_USER_NOT_IN_GROUP', 'ERROR_UNRELATED_FIELD',
                'ERROR_REQUEST_BODY_VALIDATION'
            ]),
            404:
            get_error_schema(['ERROR_GRID_DOES_NOT_EXIST'])
        })
    @map_exceptions({
        UserNotInGroupError: ERROR_USER_NOT_IN_GROUP,
        ViewDoesNotExist: ERROR_GRID_DOES_NOT_EXIST,
        UnrelatedFieldError: ERROR_UNRELATED_FIELD
    })
    @validate_body(GridViewSerializer)
    def patch(self, request, view_id, data):
        """
        Updates the field options for the provided grid view.

        The following example body data will only update the width of the FIELD_ID
        and leaves the others untouched.
            {
                FIELD_ID: {
                    'width': 200
                }
            }
        """

        handler = ViewHandler()
        view = handler.get_view(view_id, GridView)
        handler.update_grid_view_field_options(request.user, view,
                                               data['field_options'])
        return Response(GridViewSerializer(view).data)
コード例 #22
0
ファイル: views.py プロジェクト: maktec/baserow
class RowsView(APIView):
    authentication_classes = APIView.authentication_classes + [
        TokenAuthentication
    ]
    permission_classes = (IsAuthenticated, )

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name='table_id',
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description=
                'Returns the rows of the table related to the provided '
                'value.'),
            OpenApiParameter(
                name='page',
                location=OpenApiParameter.QUERY,
                type=OpenApiTypes.INT,
                description='Defines which page of rows should be returned.'),
            OpenApiParameter(
                name='size',
                location=OpenApiParameter.QUERY,
                type=OpenApiTypes.INT,
                description='Defines how many rows should be returned per page.'
            ),
            OpenApiParameter(
                name='search',
                location=OpenApiParameter.QUERY,
                type=OpenApiTypes.STR,
                description=
                'If provided only rows with data that matches the search '
                'query are going to be returned.'),
            OpenApiParameter(
                name='order_by',
                location=OpenApiParameter.QUERY,
                type=OpenApiTypes.STR,
                description=
                'Optionally the rows can be ordered by provided field ids '
                'separated by comma. By default a field is ordered in '
                'ascending (A-Z) order, but by prepending the field with '
                'a \'-\' it can be ordered descending (Z-A). ')
        ],
        tags=['Database table rows'],
        operation_id='list_database_table_rows',
        description=
        ('Lists all the rows of the table related to the provided parameter if the '
         'user has access to the related database\'s group. The response is '
         'paginated by a page/size style. It is also possible to provide an '
         'optional search query, only rows where the data matches the search query '
         'are going to be returned then. The properties of the returned rows '
         'depends on which fields the table has. For a complete overview of fields '
         'use the **list_database_table_fields** endpoint to list them all. In the '
         'example all field types are listed, but normally the number in '
         'field_{id} key is going to be the id of the field. The value is what the '
         'user has provided and the format of it depends on the fields type.'),
        responses={
            200:
            example_pagination_row_serializer_class,
            400:
            get_error_schema([
                'ERROR_USER_NOT_IN_GROUP', 'ERROR_REQUEST_BODY_VALIDATION',
                'ERROR_PAGE_SIZE_LIMIT', 'ERROR_INVALID_PAGE',
                'ERROR_ORDER_BY_FIELD_NOT_FOUND',
                'ERROR_ORDER_BY_FIELD_NOT_POSSIBLE'
            ]),
            401:
            get_error_schema(['ERROR_NO_PERMISSION_TO_TABLE']),
            404:
            get_error_schema(['ERROR_TABLE_DOES_NOT_EXIST'])
        })
    @map_exceptions({
        UserNotInGroupError: ERROR_USER_NOT_IN_GROUP,
        TableDoesNotExist: ERROR_TABLE_DOES_NOT_EXIST,
        NoPermissionToTable: ERROR_NO_PERMISSION_TO_TABLE,
        OrderByFieldNotFound: ERROR_ORDER_BY_FIELD_NOT_FOUND,
        OrderByFieldNotPossible: ERROR_ORDER_BY_FIELD_NOT_POSSIBLE
    })
    def get(self, request, table_id):
        """
        Lists all the rows of the given table id paginated. It is also possible to
        provide a search query.
        """

        table = TableHandler().get_table(request.user, table_id)
        TokenHandler().check_table_permissions(request, 'read', table, False)

        model = table.get_model()
        search = request.GET.get('search')
        order_by = request.GET.get('order_by')

        queryset = model.objects.all().enhance_by_fields().order_by('id')

        if search:
            queryset = queryset.search_all_fields(search)

        if order_by:
            queryset = queryset.order_by_fields_string(order_by)

        paginator = PageNumberPagination(
            limit_page_size=settings.ROW_PAGE_SIZE_LIMIT)
        page = paginator.paginate_queryset(queryset, request, self)
        serializer_class = get_row_serializer_class(model,
                                                    RowSerializer,
                                                    is_response=True)
        serializer = serializer_class(page, many=True)

        return paginator.get_paginated_response(serializer.data)

    @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']),
            401:
            get_error_schema(['ERROR_NO_PERMISSION_TO_TABLE']),
            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,
        NoPermissionToTable: ERROR_NO_PERMISSION_TO_TABLE,
        UserFileDoesNotExist: ERROR_USER_FILE_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)
        TokenHandler().check_table_permissions(request, 'create', table, False)
        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,
                                                    is_response=True)
        serializer = serializer_class(row)

        return Response(serializer.data)
コード例 #23
0
class FieldsView(APIView):
    permission_classes = (IsAuthenticated, )

    @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. 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
    })
    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)
        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)
コード例 #24
0
class GroupInvitationView(APIView):
    permission_classes = (IsAuthenticated, )

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name='group_invitation_id',
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description=
                'Returns the group invitation related to the provided '
                'value.')
        ],
        tags=['Group invitations'],
        operation_id='get_group_invitation',
        description=
        ('Returns the requested group invitation if the authorized user has admin '
         'right to the related group'),
        responses={
            200:
            GroupInvitationSerializer,
            400:
            get_error_schema([
                'ERROR_USER_NOT_IN_GROUP',
                'ERROR_USER_INVALID_GROUP_PERMISSIONS_ERROR'
            ]),
            404:
            get_error_schema(['ERROR_GROUP_INVITATION_DOES_NOT_EXIST'])
        },
    )
    @map_exceptions({
        GroupInvitationDoesNotExist:
        ERROR_GROUP_INVITATION_DOES_NOT_EXIST,
        UserNotInGroupError:
        ERROR_USER_NOT_IN_GROUP,
        UserInvalidGroupPermissionsError:
        ERROR_USER_INVALID_GROUP_PERMISSIONS_ERROR,
    })
    def get(self, request, group_invitation_id):
        """Selects a single group invitation and responds with a serialized version."""

        group_invitation = CoreHandler().get_group_invitation(
            request.user, group_invitation_id)
        return Response(GroupInvitationSerializer(group_invitation).data)

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name='group_invitation_id',
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description=
                'Updates the group invitation related to the provided '
                'value.')
        ],
        tags=['Group invitations'],
        operation_id='update_group_invitation',
        description=
        ('Updates the existing group invitation related to the provided '
         '`group_invitation_id` param if the authorized user has admin rights to '
         'the related group.'),
        request=UpdateGroupInvitationSerializer,
        responses={
            200:
            GroupInvitationSerializer,
            400:
            get_error_schema([
                'ERROR_USER_NOT_IN_GROUP',
                'ERROR_USER_INVALID_GROUP_PERMISSIONS_ERROR',
                'ERROR_REQUEST_BODY_VALIDATION'
            ]),
            404:
            get_error_schema(['ERROR_GROUP_INVITATION_DOES_NOT_EXIST'])
        },
    )
    @transaction.atomic
    @validate_body(UpdateGroupInvitationSerializer)
    @map_exceptions({
        GroupInvitationDoesNotExist:
        ERROR_GROUP_INVITATION_DOES_NOT_EXIST,
        UserNotInGroupError:
        ERROR_USER_NOT_IN_GROUP,
        UserInvalidGroupPermissionsError:
        ERROR_USER_INVALID_GROUP_PERMISSIONS_ERROR
    })
    def patch(self, request, data, group_invitation_id):
        """Updates the group invitation if the user belongs to the group."""

        group_invitation = CoreHandler().get_group_invitation(
            request.user,
            group_invitation_id,
            base_queryset=GroupInvitation.objects.select_for_update())
        group_invitation = CoreHandler().update_group_invitation(
            request.user, group_invitation, **data)
        return Response(GroupInvitationSerializer(group_invitation).data)

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name='group_invitation_id',
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description=
                'Deletes the group invitation related to the provided '
                'value.')
        ],
        tags=['Group invitations'],
        operation_id='delete_group_invitation',
        description=
        ('Deletes a group invitation if the authorized user has admin rights to '
         'the related group.'),
        responses={
            204:
            None,
            400:
            get_error_schema([
                'ERROR_USER_NOT_IN_GROUP',
                'ERROR_USER_INVALID_GROUP_PERMISSIONS_ERROR'
            ]),
            404:
            get_error_schema(['ERROR_GROUP_INVITATION_DOES_NOT_EXIST'])
        },
    )
    @transaction.atomic
    @map_exceptions({
        GroupInvitationDoesNotExist:
        ERROR_GROUP_INVITATION_DOES_NOT_EXIST,
        UserNotInGroupError:
        ERROR_USER_NOT_IN_GROUP,
        UserInvalidGroupPermissionsError:
        ERROR_USER_INVALID_GROUP_PERMISSIONS_ERROR,
    })
    def delete(self, request, group_invitation_id):
        """Deletes an existing group_invitation if the user belongs to the group."""

        group_invitation = CoreHandler().get_group_invitation(
            request.user,
            group_invitation_id,
            base_queryset=GroupInvitation.objects.select_for_update())
        CoreHandler().delete_group_invitation(request.user, group_invitation)
        return Response(status=204)
コード例 #25
0
class ViewFiltersView(APIView):
    permission_classes = (IsAuthenticated,)

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name='view_id',
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description='Returns only filters of the view related to the provided '
                            'value.'
            )
        ],
        tags=['Database table view filters'],
        operation_id='list_database_table_view_filters',
        description=(
            'Lists all filters of the view related to the provided `view_id` if the '
            'user has access to the related database\'s group. A view can have '
            'multiple filters. When all the rows are requested for the view only those '
            'that apply to the filters are returned.'
        ),
        responses={
            200: ViewFilterSerializer(many=True),
            400: get_error_schema(['ERROR_USER_NOT_IN_GROUP']),
            404: get_error_schema(['ERROR_VIEW_DOES_NOT_EXIST'])
        }
    )
    @map_exceptions({
        ViewDoesNotExist: ERROR_VIEW_DOES_NOT_EXIST,
        UserNotInGroupError: ERROR_USER_NOT_IN_GROUP
    })
    def get(self, request, view_id):
        """
        Responds with a list of serialized filters that belong to the view if the user
        has access to that group.
        """

        view = ViewHandler().get_view(request.user, view_id)
        filters = ViewFilter.objects.filter(view=view)
        serializer = ViewFilterSerializer(filters, many=True)
        return Response(serializer.data)

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name='view_id',
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description='Creates a filter for the view related to the provided '
                            'value.'
            )
        ],
        tags=['Database table view filters'],
        operation_id='create_database_table_view_filter',
        description=(
            'Creates a new filter for the view related to the provided `view_id` '
            'parameter if the authorized user has access to the related database\'s '
            'group. When the rows of a view are requested, for example via the '
            '`list_database_table_grid_view_rows` endpoint, then only the rows that '
            'apply to all the filters are going to be returned. A filters compares the '
            'value of a field to the value of a filter. It depends on the type how '
            'values are going to be compared.'
        ),
        request=CreateViewFilterSerializer(),
        responses={
            200: ViewFilterSerializer(),
            400: get_error_schema([
                'ERROR_USER_NOT_IN_GROUP', 'ERROR_REQUEST_BODY_VALIDATION',
                'ERROR_FIELD_NOT_IN_TABLE', 'ERROR_VIEW_FILTER_NOT_SUPPORTED',
                'ERROR_VIEW_FILTER_TYPE_NOT_ALLOWED_FOR_FIELD'
            ]),
            404: get_error_schema(['ERROR_VIEW_DOES_NOT_EXIST'])
        }
    )
    @transaction.atomic
    @validate_body(CreateViewFilterSerializer)
    @map_exceptions({
        ViewDoesNotExist: ERROR_VIEW_DOES_NOT_EXIST,
        UserNotInGroupError: ERROR_USER_NOT_IN_GROUP,
        FieldNotInTable: ERROR_FIELD_NOT_IN_TABLE,
        ViewFilterNotSupported: ERROR_VIEW_FILTER_NOT_SUPPORTED,
        ViewFilterTypeNotAllowedForField: ERROR_VIEW_FILTER_TYPE_NOT_ALLOWED_FOR_FIELD
    })
    def post(self, request, data, view_id):
        """Creates a new filter for the provided view."""

        view_handler = ViewHandler()
        view = view_handler.get_view(request.user, view_id)
        # We can safely assume the field exists because the CreateViewFilterSerializer
        # has already checked that.
        field = Field.objects.get(pk=data['field'])
        view_filter = view_handler.create_filter(request.user, view, field,
                                                 data['type'], data['value'])

        serializer = ViewFilterSerializer(view_filter)
        return Response(serializer.data)
コード例 #26
0
class GroupInvitationsView(APIView):
    permission_classes = (IsAuthenticated, )

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name='group_id',
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description=
                'Returns only invitations that are in the group related '
                'to the provided value.')
        ],
        tags=['Group invitations'],
        operation_id='list_group_invitations',
        description=
        ('Lists all the group invitations of the group related to the provided '
         '`group_id` parameter if the authorized user has admin rights to that '
         'group.'),
        responses={
            200:
            GroupInvitationSerializer(many=True),
            400:
            get_error_schema([
                'ERROR_USER_NOT_IN_GROUP',
                'ERROR_USER_INVALID_GROUP_PERMISSIONS_ERROR'
            ]),
            404:
            get_error_schema(['ERROR_GROUP_DOES_NOT_EXIST'])
        })
    @map_exceptions({
        GroupDoesNotExist:
        ERROR_GROUP_DOES_NOT_EXIST,
        UserNotInGroupError:
        ERROR_USER_NOT_IN_GROUP,
        UserInvalidGroupPermissionsError:
        ERROR_USER_INVALID_GROUP_PERMISSIONS_ERROR
    })
    def get(self, request, group_id):
        """Lists all the invitations of the provided group id."""

        group = CoreHandler().get_group(group_id)
        group.has_user(request.user, 'ADMIN', raise_error=True)
        group_invitations = GroupInvitation.objects.filter(group=group)
        serializer = GroupInvitationSerializer(group_invitations, many=True)
        return Response(serializer.data)

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name='group_id',
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description=
                'Creates a group invitation to the group related to the '
                'provided value.')
        ],
        tags=['Group invitations'],
        operation_id='create_group_invitation',
        description=
        ('Creates a new group invitations for an email address if the authorized '
         'user has admin rights to the related group. An email containing a sign '
         'up link will be send to the user.'),
        request=CreateGroupInvitationSerializer,
        responses={
            200:
            GroupInvitationSerializer,
            400:
            get_error_schema([
                'ERROR_USER_NOT_IN_GROUP',
                'ERROR_USER_INVALID_GROUP_PERMISSIONS_ERROR',
                'ERROR_REQUEST_BODY_VALIDATION'
            ]),
            404:
            get_error_schema(['ERROR_GROUP_DOES_NOT_EXIST'])
        },
    )
    @transaction.atomic
    @validate_body(CreateGroupInvitationSerializer)
    @map_exceptions({
        GroupDoesNotExist: ERROR_GROUP_DOES_NOT_EXIST,
        UserNotInGroupError: ERROR_USER_NOT_IN_GROUP,
        UserInvalidGroupPermissionsError:
        ERROR_USER_INVALID_GROUP_PERMISSIONS_ERROR,
        GroupUserAlreadyExists: ERROR_GROUP_USER_ALREADY_EXISTS,
        BaseURLHostnameNotAllowed: ERROR_HOSTNAME_IS_NOT_ALLOWED
    })
    def post(self, request, data, group_id):
        """Creates a new group invitation and sends it the provided email."""

        group = CoreHandler().get_group(group_id)
        group_invitation = CoreHandler().create_group_invitation(
            request.user, group, **data)
        return Response(GroupInvitationSerializer(group_invitation).data)
コード例 #27
0
class ViewSortingsView(APIView):
    permission_classes = (IsAuthenticated,)

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name='view_id',
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description='Returns only sortings of the view related to the provided '
                            'value.'
            )
        ],
        tags=['Database table view sortings'],
        operation_id='list_database_table_view_sortings',
        description=(
            'Lists all sortings of the view related to the provided `view_id` if the '
            'user has access to the related database\'s group. A view can have '
            'multiple sortings. When all the rows are requested they will be in the '
            'desired order.'
        ),
        responses={
            200: ViewSortSerializer(many=True),
            400: get_error_schema(['ERROR_USER_NOT_IN_GROUP']),
            404: get_error_schema(['ERROR_VIEW_DOES_NOT_EXIST'])
        }
    )
    @map_exceptions({
        ViewDoesNotExist: ERROR_VIEW_DOES_NOT_EXIST,
        UserNotInGroupError: ERROR_USER_NOT_IN_GROUP
    })
    def get(self, request, view_id):
        """
        Responds with a list of serialized sortings that belong to the view if the user
        has access to that group.
        """

        view = ViewHandler().get_view(request.user, view_id)
        sortings = ViewSort.objects.filter(view=view)
        serializer = ViewSortSerializer(sortings, many=True)
        return Response(serializer.data)

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name='view_id',
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description='Creates a sort for the view related to the provided '
                            'value.'
            )
        ],
        tags=['Database table view sortings'],
        operation_id='create_database_table_view_sort',
        description=(
            'Creates a new sort for the view related to the provided `view_id` '
            'parameter if the authorized user has access to the related database\'s '
            'group. When the rows of a view are requested, for example via the '
            '`list_database_table_grid_view_rows` endpoint, they will be returned in '
            'the respected order defined by all the sortings.'
        ),
        request=CreateViewSortSerializer(),
        responses={
            200: ViewSortSerializer(),
            400: get_error_schema([
                'ERROR_USER_NOT_IN_GROUP', 'ERROR_REQUEST_BODY_VALIDATION',
                'ERROR_VIEW_SORT_NOT_SUPPORTED', 'ERROR_FIELD_NOT_IN_TABLE',
                'ERROR_VIEW_SORT_FIELD_ALREADY_EXISTS',
                'ERROR_VIEW_SORT_FIELD_NOT_SUPPORTED'
            ]),
            404: get_error_schema(['ERROR_VIEW_DOES_NOT_EXIST'])
        }
    )
    @transaction.atomic
    @validate_body(CreateViewSortSerializer)
    @map_exceptions({
        ViewDoesNotExist: ERROR_VIEW_DOES_NOT_EXIST,
        UserNotInGroupError: ERROR_USER_NOT_IN_GROUP,
        FieldNotInTable: ERROR_FIELD_NOT_IN_TABLE,
        ViewSortNotSupported: ERROR_VIEW_SORT_NOT_SUPPORTED,
        ViewSortFieldAlreadyExist: ERROR_VIEW_SORT_FIELD_ALREADY_EXISTS,
        ViewSortFieldNotSupported: ERROR_VIEW_SORT_FIELD_NOT_SUPPORTED,
    })
    def post(self, request, data, view_id):
        """Creates a new sort for the provided view."""

        view_handler = ViewHandler()
        view = view_handler.get_view(request.user, view_id)
        # We can safely assume the field exists because the CreateViewSortSerializer
        # has already checked that.
        field = Field.objects.get(pk=data['field'])
        view_sort = view_handler.create_sort(request.user, view, field, data['order'])

        serializer = ViewSortSerializer(view_sort)
        return Response(serializer.data)
コード例 #28
0
ファイル: views.py プロジェクト: jechaviz/baserow
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)
        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)
コード例 #29
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)
コード例 #30
0
class GridViewView(APIView):
    permission_classes = (IsAuthenticated,)

    def get_permissions(self):
        if self.request.method == "GET":
            return [AllowAny()]

        return super().get_permissions()

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name="view_id",
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                description="Returns only rows that belong to the related view's "
                "table.",
            ),
            OpenApiParameter(
                name="count",
                location=OpenApiParameter.QUERY,
                type=OpenApiTypes.NONE,
                description="If provided only the count will be returned.",
            ),
            OpenApiParameter(
                name="include",
                location=OpenApiParameter.QUERY,
                type=OpenApiTypes.STR,
                description=(
                    "Can contain `field_options` which will add an object with the "
                    "same name to the response if included. That object contains "
                    "user defined view settings for each field. For example the "
                    "field's width is included in here."
                ),
            ),
            OpenApiParameter(
                name="limit",
                location=OpenApiParameter.QUERY,
                type=OpenApiTypes.INT,
                description="Defines how many rows should be returned.",
            ),
            OpenApiParameter(
                name="offset",
                location=OpenApiParameter.QUERY,
                type=OpenApiTypes.INT,
                description="Can only be used in combination with the `limit` "
                "parameter and defines from which offset the rows should "
                "be returned.",
            ),
            OpenApiParameter(
                name="page",
                location=OpenApiParameter.QUERY,
                type=OpenApiTypes.INT,
                description="Defines which page of rows should be returned. Either "
                "the `page` or `limit` can be provided, not both.",
            ),
            OpenApiParameter(
                name="size",
                location=OpenApiParameter.QUERY,
                type=OpenApiTypes.INT,
                description="Can only be used in combination with the `page` parameter "
                "and defines how many rows should be returned.",
            ),
            OpenApiParameter(
                name="search",
                location=OpenApiParameter.QUERY,
                type=OpenApiTypes.STR,
                description="If provided only rows with data that matches the search "
                "query are going to be returned.",
            ),
        ],
        tags=["Database table grid view"],
        operation_id="list_database_table_grid_view_rows",
        description=(
            "Lists the requested rows of the view's table related to the provided "
            "`view_id` if the authorized user has access to the database's group. "
            "The response is paginated either by a limit/offset or page/size style. "
            "The style depends on the provided GET parameters. The properties of the "
            "returned rows depends on which fields the table has. For a complete "
            "overview of fields use the **list_database_table_fields** endpoint to "
            "list them all. In the example all field types are listed, but normally "
            "the number in field_{id} key is going to be the id of the field. "
            "The value is what the user has provided and the format of it depends on "
            "the fields type.\n"
            "\n"
            "The filters and sortings are automatically applied. To get a full "
            "overview of the applied filters and sortings you can use the "
            "`list_database_table_view_filters` and "
            "`list_database_table_view_sortings` endpoints."
        ),
        responses={
            200: example_pagination_row_serializer_class_with_field_options,
            400: get_error_schema(["ERROR_USER_NOT_IN_GROUP"]),
            404: get_error_schema(["ERROR_GRID_DOES_NOT_EXIST"]),
        },
    )
    @map_exceptions(
        {
            UserNotInGroup: ERROR_USER_NOT_IN_GROUP,
            ViewDoesNotExist: ERROR_GRID_DOES_NOT_EXIST,
        }
    )
    @allowed_includes("field_options")
    def get(self, request, view_id, field_options):
        """
        Lists all the rows of a grid view, paginated either by a page or offset/limit.
        If the limit get parameter is provided the limit/offset pagination will be used
        else the page number pagination.

        Optionally the field options can also be included in the response if the the
        `field_options` are provided in the include GET parameter.
        """

        search = request.GET.get("search")

        view_handler = ViewHandler()
        view = view_handler.get_view(view_id, GridView)

        view.table.database.group.has_user(
            request.user, raise_error=True, allow_if_template=True
        )
        model = view.table.get_model()
        queryset = view_handler.get_queryset(view, search, model)

        if "count" in request.GET:
            return Response({"count": queryset.count()})

        if LimitOffsetPagination.limit_query_param in request.GET:
            paginator = LimitOffsetPagination()
        else:
            paginator = PageNumberPagination()

        page = paginator.paginate_queryset(queryset, request, self)
        serializer_class = get_row_serializer_class(
            model, RowSerializer, is_response=True
        )
        serializer = serializer_class(page, many=True)

        response = paginator.get_paginated_response(serializer.data)

        if field_options:
            # The serializer has the GridViewFieldOptionsField which fetches the
            # field options from the database and creates them if they don't exist,
            # but when added to the context the fields don't have to be fetched from
            # the database again when checking if they exist.
            context = {"fields": [o["field"] for o in model._field_objects.values()]}
            serialized_view = GridViewSerializer(view, context=context).data
            response.data["field_options"] = serialized_view["field_options"]

        return response

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name="view_id",
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                required=False,
                description="Returns only rows that belong to the related view's "
                "table.",
            )
        ],
        tags=["Database table grid view"],
        operation_id="filter_database_table_grid_view_rows",
        description=(
            "Lists only the rows and fields that match the request. Only the rows "
            "with the ids that are in the `row_ids` list are going to be returned. "
            "Same goes for the fields, only the fields with the ids in the "
            "`field_ids` are going to be returned. This endpoint could be used to "
            "refresh data after changes something. For example in the web frontend "
            "after changing a field type, the data of the related cells will be "
            "refreshed using this endpoint. In the example all field types are listed, "
            "but normally  the number in field_{id} key is going to be the id of the "
            "field. The value is what the user has provided and the format of it "
            "depends on the fields type."
        ),
        request=GridViewFilterSerializer,
        responses={
            200: get_example_row_serializer_class(True)(many=True),
            400: get_error_schema(
                ["ERROR_USER_NOT_IN_GROUP", "ERROR_REQUEST_BODY_VALIDATION"]
            ),
            404: get_error_schema(["ERROR_GRID_DOES_NOT_EXIST"]),
        },
    )
    @map_exceptions(
        {
            UserNotInGroup: ERROR_USER_NOT_IN_GROUP,
            ViewDoesNotExist: ERROR_GRID_DOES_NOT_EXIST,
        }
    )
    @validate_body(GridViewFilterSerializer)
    def post(self, request, view_id, data):
        """
        Row filter endpoint that only lists the requested rows and optionally only the
        requested fields.
        """

        view = ViewHandler().get_view(view_id, GridView)
        view.table.database.group.has_user(request.user, raise_error=True)

        model = view.table.get_model(field_ids=data["field_ids"])
        results = model.objects.filter(pk__in=data["row_ids"])

        serializer_class = get_row_serializer_class(
            model, RowSerializer, is_response=True
        )
        serializer = serializer_class(results, many=True)
        return Response(serializer.data)

    @extend_schema(
        parameters=[
            OpenApiParameter(
                name="view_id",
                location=OpenApiParameter.PATH,
                type=OpenApiTypes.INT,
                required=False,
                description="Updates the field related to the provided `view_id` "
                "parameter.",
            )
        ],
        tags=["Database table grid view"],
        operation_id="update_database_table_grid_view_field_options",
        description=(
            "Updates the field options of a `grid` view. The field options are unique "
            "options per field for a view. This could for example be used to update "
            "the field width if the user changes it."
        ),
        request=GridViewSerializer,
        responses={
            200: GridViewSerializer,
            400: get_error_schema(
                [
                    "ERROR_USER_NOT_IN_GROUP",
                    "ERROR_UNRELATED_FIELD",
                    "ERROR_REQUEST_BODY_VALIDATION",
                ]
            ),
            404: get_error_schema(["ERROR_GRID_DOES_NOT_EXIST"]),
        },
    )
    @map_exceptions(
        {
            UserNotInGroup: ERROR_USER_NOT_IN_GROUP,
            ViewDoesNotExist: ERROR_GRID_DOES_NOT_EXIST,
            UnrelatedFieldError: ERROR_UNRELATED_FIELD,
        }
    )
    @validate_body(GridViewSerializer)
    def patch(self, request, view_id, data):
        """
        Updates the field options for the provided grid view.

        The following example body data will only update the width of the FIELD_ID
        and leaves the others untouched.
            {
                FIELD_ID: {
                    'width': 200
                }
            }
        """

        handler = ViewHandler()
        view = handler.get_view(view_id, GridView)
        handler.update_grid_view_field_options(
            request.user, view, data["field_options"]
        )
        return Response(GridViewSerializer(view).data)