mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-04-17 18:32:35 +00:00
Resolve "Optionally use the view's filters and sortings when listing rows"
This commit is contained in:
parent
e5c5c547da
commit
d82df3e138
7 changed files with 132 additions and 1 deletions
backend
src/baserow/contrib/database/api/rows
tests/baserow/contrib/database/api/rows
web-frontend/modules/database
|
@ -349,6 +349,7 @@ class ListRowsQueryParamsSerializer(serializers.Serializer):
|
|||
include = serializers.CharField(required=False)
|
||||
exclude = serializers.CharField(required=False)
|
||||
filter_type = serializers.CharField(required=False, default="")
|
||||
view_id = serializers.IntegerField(required=False)
|
||||
|
||||
|
||||
class BatchUpdateRowsSerializer(serializers.Serializer):
|
||||
|
|
|
@ -48,6 +48,7 @@ from baserow.contrib.database.api.tokens.errors import ERROR_NO_PERMISSION_TO_TA
|
|||
from baserow.contrib.database.api.views.errors import (
|
||||
ERROR_VIEW_FILTER_TYPE_DOES_NOT_EXIST,
|
||||
ERROR_VIEW_FILTER_TYPE_UNSUPPORTED_FIELD,
|
||||
ERROR_VIEW_DOES_NOT_EXIST,
|
||||
)
|
||||
from baserow.contrib.database.fields.exceptions import (
|
||||
OrderByFieldNotFound,
|
||||
|
@ -75,6 +76,7 @@ from baserow.contrib.database.tokens.handler import TokenHandler
|
|||
from baserow.contrib.database.views.exceptions import (
|
||||
ViewFilterTypeNotAllowedForField,
|
||||
ViewFilterTypeDoesNotExist,
|
||||
ViewDoesNotExist,
|
||||
)
|
||||
from baserow.contrib.database.views.registries import view_filter_type_registry
|
||||
from baserow.core.exceptions import UserNotInGroup
|
||||
|
@ -95,6 +97,7 @@ from baserow.contrib.database.fields.field_filters import (
|
|||
FILTER_TYPE_AND,
|
||||
FILTER_TYPE_OR,
|
||||
)
|
||||
from baserow.contrib.database.views.handler import ViewHandler
|
||||
from .schemas import row_names_response_schema
|
||||
|
||||
|
||||
|
@ -222,6 +225,12 @@ class RowsView(APIView):
|
|||
"Baserow field names (field_123 etc). "
|
||||
),
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="view_id",
|
||||
location=OpenApiParameter.QUERY,
|
||||
type=OpenApiTypes.INT,
|
||||
description="Includes all the filters and sorts of the provided view.",
|
||||
),
|
||||
],
|
||||
tags=["Database table rows"],
|
||||
operation_id="list_database_table_rows",
|
||||
|
@ -271,6 +280,7 @@ class RowsView(APIView):
|
|||
FieldDoesNotExist: ERROR_FIELD_DOES_NOT_EXIST,
|
||||
ViewFilterTypeDoesNotExist: ERROR_VIEW_FILTER_TYPE_DOES_NOT_EXIST,
|
||||
ViewFilterTypeNotAllowedForField: ERROR_VIEW_FILTER_TYPE_UNSUPPORTED_FIELD,
|
||||
ViewDoesNotExist: ERROR_VIEW_DOES_NOT_EXIST,
|
||||
}
|
||||
)
|
||||
@validate_query_parameters(ListRowsQueryParamsSerializer)
|
||||
|
@ -289,6 +299,7 @@ class RowsView(APIView):
|
|||
include = query_params.get("include")
|
||||
exclude = query_params.get("exclude")
|
||||
user_field_names = query_params.get("user_field_names")
|
||||
view_id = query_params.get("view_id")
|
||||
fields = get_include_exclude_fields(
|
||||
table, include, exclude, user_field_names=user_field_names
|
||||
)
|
||||
|
@ -299,6 +310,16 @@ class RowsView(APIView):
|
|||
)
|
||||
queryset = model.objects.all().enhance_by_fields()
|
||||
|
||||
if view_id:
|
||||
view_handler = ViewHandler()
|
||||
view = view_handler.get_view(view_id)
|
||||
|
||||
if view.table_id != table.id:
|
||||
raise ViewDoesNotExist()
|
||||
|
||||
queryset = view_handler.apply_filters(view, queryset)
|
||||
queryset = view_handler.apply_sorting(view, queryset)
|
||||
|
||||
if search:
|
||||
queryset = queryset.search_all_fields(search)
|
||||
|
||||
|
|
|
@ -28,6 +28,11 @@ def test_list_rows(api_client, data_fixture):
|
|||
field_2 = data_fixture.create_number_field(name="Price", table=table)
|
||||
field_3 = data_fixture.create_text_field()
|
||||
field_4 = data_fixture.create_boolean_field(name="InStock", table=table)
|
||||
grid_view = data_fixture.create_grid_view(user=user, table=table)
|
||||
unrelated_view = data_fixture.create_grid_view(user=user, table=table_2)
|
||||
data_fixture.create_view_filter(
|
||||
view=grid_view, user=user, field=field_1, value="Product 1"
|
||||
)
|
||||
|
||||
token = TokenHandler().create_token(user, table.database.group, "Good")
|
||||
wrong_token = TokenHandler().create_token(user, table.database.group, "Wrong")
|
||||
|
@ -356,6 +361,101 @@ def test_list_rows(api_client, data_fixture):
|
|||
assert response_json["results"][2]["id"] == row_4.id
|
||||
assert response_json["results"][3]["id"] == row_2.id
|
||||
|
||||
response = api_client.get(
|
||||
reverse("api:database:rows:list", kwargs={"table_id": table.id}),
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {jwt_token}",
|
||||
data={"view_id": grid_view.id},
|
||||
)
|
||||
response_json = response.json()
|
||||
assert response.status_code == HTTP_200_OK
|
||||
assert response_json["count"] == 1
|
||||
assert len(response_json["results"]) == 1
|
||||
assert response_json["results"][0]["id"] == row_1.id
|
||||
|
||||
response = api_client.get(
|
||||
reverse("api:database:rows:list", kwargs={"table_id": table.id}),
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {jwt_token}",
|
||||
data={"view_id": -1},
|
||||
)
|
||||
response_json = response.json()
|
||||
assert response.status_code == HTTP_404_NOT_FOUND
|
||||
assert response_json["error"] == "ERROR_VIEW_DOES_NOT_EXIST"
|
||||
|
||||
response = api_client.get(
|
||||
reverse("api:database:rows:list", kwargs={"table_id": table.id}),
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {jwt_token}",
|
||||
data={"view_id": unrelated_view.id},
|
||||
)
|
||||
assert response.status_code == HTTP_404_NOT_FOUND
|
||||
assert response_json["error"] == "ERROR_VIEW_DOES_NOT_EXIST"
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_list_rows_sort_query_overrides_existing_sort(data_fixture, api_client):
|
||||
user, jwt_token = data_fixture.create_user_and_token(
|
||||
email="test@test.nl", password="password", first_name="Test1"
|
||||
)
|
||||
table = data_fixture.create_database_table(user=user)
|
||||
field_1 = data_fixture.create_text_field(name="Name", table=table, primary=True)
|
||||
grid_view = data_fixture.create_grid_view(user=user, table=table)
|
||||
data_fixture.create_view_sort(
|
||||
user=user, view=grid_view, field=field_1, order="DESC"
|
||||
)
|
||||
|
||||
model = table.get_model(attribute_names=True)
|
||||
row_1 = model.objects.create(name="a")
|
||||
row_2 = model.objects.create(name="b")
|
||||
row_3 = model.objects.create(name="c")
|
||||
row_4 = model.objects.create(name="d")
|
||||
|
||||
response = api_client.get(
|
||||
reverse("api:database:rows:list", kwargs={"table_id": table.id}),
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {jwt_token}",
|
||||
data={"view_id": grid_view.id, "order_by": field_1.id},
|
||||
)
|
||||
|
||||
response_json = response.json()
|
||||
assert response.status_code == HTTP_200_OK
|
||||
assert response_json["results"][0]["id"] == row_1.id
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_list_rows_filter_stacks_with_existing_filter(data_fixture, api_client):
|
||||
user, jwt_token = data_fixture.create_user_and_token(
|
||||
email="test@test.nl", password="password", first_name="Test1"
|
||||
)
|
||||
table = data_fixture.create_database_table(user=user)
|
||||
field_1 = data_fixture.create_text_field(name="Name", table=table, primary=True)
|
||||
field_2 = data_fixture.create_number_field(name="Number", table=table)
|
||||
grid_view = data_fixture.create_grid_view(user=user, table=table)
|
||||
data_fixture.create_view_filter(
|
||||
view=grid_view, user=user, field=field_1, value="ab", type="contains"
|
||||
)
|
||||
|
||||
model = table.get_model(attribute_names=True)
|
||||
row_1 = model.objects.create(name="a", number="1")
|
||||
row_2 = model.objects.create(name="ab", number="2")
|
||||
row_3 = model.objects.create(name="abc", number="3")
|
||||
row_4 = model.objects.create(name="abcd", number="1")
|
||||
|
||||
response = api_client.get(
|
||||
reverse("api:database:rows:list", kwargs={"table_id": table.id}),
|
||||
format="json",
|
||||
HTTP_AUTHORIZATION=f"JWT {jwt_token}",
|
||||
data={"view_id": grid_view.id, f"filter__field_{field_2.id}__contains": "1"},
|
||||
)
|
||||
|
||||
# The only row that matches both filters is row_4
|
||||
response_json = response.json()
|
||||
assert response.status_code == HTTP_200_OK
|
||||
assert response_json["count"] == 1
|
||||
assert len(response_json["results"]) == 1
|
||||
assert response_json["results"][0]["id"] == row_4.id
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_create_row(api_client, data_fixture):
|
||||
|
|
|
@ -17,6 +17,7 @@ For example:
|
|||
* Added a new "is years ago filter". [#1019](https://gitlab.com/bramw/baserow/-/issues/1019)
|
||||
* Show badge when the user has account level premium.
|
||||
* Added a new `ClientUndoRedoActionGroupId` request header to bundle multiple actions in a single API call. [#951](https://gitlab.com/bramw/baserow/-/issues/951)
|
||||
* Added option to use view's filters and sorting when listing rows. [#190](https://gitlab.com/bramw/baserow/-/issues/190)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
|
|
|
@ -92,6 +92,12 @@
|
|||
:content="$t('apiDocsTableListRows.exclude')"
|
||||
/>
|
||||
</APIDocsParameter>
|
||||
<APIDocsParameter name="view_id" :optional="true" type="integer">
|
||||
<MarkdownIt
|
||||
class="api-docs__content"
|
||||
:content="$t('apiDocsTableListRows.viewId')"
|
||||
/>
|
||||
</APIDocsParameter>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="api-docs__right">
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<template>
|
||||
<Context ref="context">
|
||||
<div class="context__menu-title">{{ view.name }} ({{ view.id }})</div>
|
||||
<ul class="context__menu">
|
||||
<li v-if="hasValidExporter">
|
||||
<a @click="exportView()">
|
||||
|
|
|
@ -143,7 +143,8 @@
|
|||
"filterLink": "A list of all filters can be found here.",
|
||||
"filterType": "- `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.",
|
||||
"include": "All the fields are included in the response by default. You can select a subset of fields to include by providing the include query parameter.\n\n #### With `user_field_names`:\n\n `include` should be a comma separated list of field names to be included in results. For example if you provide the following GET param: `include=My Field,-My Field 2` then only those fields will be included (unless they are explicitly excluded).\n\n The name of fields containing commas must be surrounded by quotes: `\"Name ,\"`. If the field names contain quotes, then they must be escaped using the `\\` character. Eg: `Name \\\"`.\n\n #### Without `user_field_names`:\n\n `include` should be a comma separated list of `field_` followed by the id of the field to include in the results. For example: If you provide the following GET parameter `exclude=field_1,field_2` then the fields with id `1` and id `2` then only those fields will be included (unless they are explicitly excluded).",
|
||||
"exclude": "All the fields are included in the response by default. You can select a subset of fields to exclude by providing the exclude query parameter.\n\n #### With `user_field_names`:\n\n `exclude` should be a comma separated list of field names to be excluded from the results. For example if you provide the following GET param: `exclude=My Field,-My Field 2` then those fields will be excluded.\n\n The name of fields containing commas must be surrounded by quotes: `\"Name ,\"`. If the field names contain quotes, then they must be escaped using the `\\` character. Eg: `Name \\\"`.\n\n #### Without `user_field_names`:\n\n `exclude` should be a comma separated list of `field_` followed by the id of the field to exclude from the results. For example: If you provide the following GET parameter `exclude=field_1,field_2` then the fields with id `1` and id `2` will be excluded."
|
||||
"exclude": "All the fields are included in the response by default. You can select a subset of fields to exclude by providing the exclude query parameter.\n\n #### With `user_field_names`:\n\n `exclude` should be a comma separated list of field names to be excluded from the results. For example if you provide the following GET param: `exclude=My Field,-My Field 2` then those fields will be excluded.\n\n The name of fields containing commas must be surrounded by quotes: `\"Name ,\"`. If the field names contain quotes, then they must be escaped using the `\\` character. Eg: `Name \\\"`.\n\n #### Without `user_field_names`:\n\n `exclude` should be a comma separated list of `field_` followed by the id of the field to exclude from the results. For example: If you provide the following GET parameter `exclude=field_1,field_2` then the fields with id `1` and id `2` will be excluded.",
|
||||
"viewId": "By default non of the filters and sorts outside of the ones defined in the query parameters are applied. You can add the filters and sorts of a view by providing its `id` in the `view_id` GET parameter. For example if you provide the following GET parameter `view_id=1` then the filters and sorts defined in the view with id `1` will be applied. You can find the `view_id` in the context menu of any given view. It is the number in brackets behind the view name. \n\n #### With `filter__{field}__{filter}` \n\n Both the filter provided in the query parameter and the filters defined in the view will be applied.\n\n #### With `order_by` \n\n If `order_by` is provided then the sort defined in the view will be ignored."
|
||||
},
|
||||
"apiDocsIntro": {
|
||||
"intro": "The {name} database provides an easy way to integrate the data with any external system. The API follows REST semantics, uses JSON to encode objects and relies on standard HTTP codes, machine and human readable errors to signal operation outcomes.",
|
||||
|
|
Loading…
Add table
Reference in a new issue