mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-04-15 01:28:30 +00:00
Made some minor changes and bug fixes to the row select modal
This commit is contained in:
parent
2446f86024
commit
9ecff6566a
7 changed files with 138 additions and 37 deletions
backend
src/baserow/contrib/database/api/views
tests/baserow/contrib/database/api/views
web-frontend/modules
core/assets/scss/components
database
|
@ -22,6 +22,15 @@ from baserow.contrib.database.views.registries import (
|
|||
)
|
||||
|
||||
|
||||
class ListQueryParamatersSerializer(serializers.Serializer):
|
||||
limit = serializers.IntegerField(required=False, default=None)
|
||||
type = serializers.ChoiceField(
|
||||
required=False,
|
||||
default=None,
|
||||
choices=lazy(view_type_registry.get_types, list)(),
|
||||
)
|
||||
|
||||
|
||||
class FieldOptionsField(serializers.Field):
|
||||
default_error_messages = {
|
||||
"invalid_key": "Field option key must be numeric.",
|
||||
|
|
|
@ -18,6 +18,7 @@ from baserow.api.decorators import (
|
|||
map_exceptions,
|
||||
validate_body,
|
||||
validate_body_custom_fields,
|
||||
validate_query_parameters,
|
||||
)
|
||||
from baserow.api.errors import ERROR_USER_NOT_IN_GROUP
|
||||
from baserow.api.pagination import PageNumberPagination
|
||||
|
@ -123,6 +124,7 @@ from .serializers import (
|
|||
CreateViewFilterSerializer,
|
||||
CreateViewSerializer,
|
||||
CreateViewSortSerializer,
|
||||
ListQueryParamatersSerializer,
|
||||
OrderViewsSerializer,
|
||||
PublicViewAuthRequestSerializer,
|
||||
PublicViewAuthResponseSerializer,
|
||||
|
@ -171,6 +173,26 @@ class ViewsView(APIView):
|
|||
description="Returns only views of the table related to the provided "
|
||||
"value.",
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="type",
|
||||
location=OpenApiParameter.QUERY,
|
||||
type=OpenApiTypes.STR,
|
||||
description=(
|
||||
"Optionally filter on the view type. If provided, only views of "
|
||||
"that type will be returned."
|
||||
),
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="limit",
|
||||
location=OpenApiParameter.QUERY,
|
||||
type=OpenApiTypes.INT,
|
||||
description=(
|
||||
"The maximum amount of views that must be returned. This endpoint "
|
||||
"doesn't support pagination, but if you for example just need to "
|
||||
"fetch the first view, you can do that by setting a limit. There "
|
||||
"isn't a limit by default."
|
||||
),
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="include",
|
||||
location=OpenApiParameter.QUERY,
|
||||
|
@ -211,8 +233,9 @@ class ViewsView(APIView):
|
|||
UserNotInGroup: ERROR_USER_NOT_IN_GROUP,
|
||||
}
|
||||
)
|
||||
@validate_query_parameters(ListQueryParamatersSerializer)
|
||||
@allowed_includes("filters", "sortings", "decorations")
|
||||
def get(self, request, table_id, filters, sortings, decorations):
|
||||
def get(self, request, table_id, query_params, filters, sortings, decorations):
|
||||
"""
|
||||
Responds with a list of serialized views that belong to the table if the user
|
||||
has access to that group.
|
||||
|
@ -224,6 +247,11 @@ class ViewsView(APIView):
|
|||
)
|
||||
views = View.objects.filter(table=table).select_related("content_type", "table")
|
||||
|
||||
if query_params["type"]:
|
||||
view_type = view_type_registry.get(query_params["type"])
|
||||
content_type = ContentType.objects.get_for_model(view_type.model_class)
|
||||
views = views.filter(content_type=content_type)
|
||||
|
||||
if filters:
|
||||
views = views.prefetch_related("viewfilter_set")
|
||||
|
||||
|
@ -233,6 +261,9 @@ class ViewsView(APIView):
|
|||
if decorations:
|
||||
views = views.prefetch_related("viewdecoration_set")
|
||||
|
||||
if query_params["limit"]:
|
||||
views = views[: query_params["limit"]]
|
||||
|
||||
views = specific_iterator(views)
|
||||
|
||||
data = [
|
||||
|
|
|
@ -94,6 +94,56 @@ def test_list_views(api_client, data_fixture):
|
|||
assert response.json()["error"] == "ERROR_TABLE_DOES_NOT_EXIST"
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_list_views_with_limit(api_client, data_fixture):
|
||||
user, token = data_fixture.create_user_and_token(
|
||||
email="test@test.nl", password="password", first_name="Test1"
|
||||
)
|
||||
table_1 = data_fixture.create_database_table(user=user)
|
||||
view_1 = data_fixture.create_grid_view(table=table_1, order=1)
|
||||
data_fixture.create_grid_view(table=table_1, order=3)
|
||||
|
||||
response = api_client.get(
|
||||
reverse("api:database:views:list", kwargs={"table_id": table_1.id}),
|
||||
{"limit": 1},
|
||||
**{"HTTP_AUTHORIZATION": f"JWT {token}"},
|
||||
)
|
||||
assert response.status_code == HTTP_200_OK
|
||||
response_json = response.json()
|
||||
assert len(response_json) == 1
|
||||
assert response_json[0]["id"] == view_1.id
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_list_views_with_type_filter(api_client, data_fixture):
|
||||
user, token = data_fixture.create_user_and_token(
|
||||
email="test@test.nl", password="password", first_name="Test1"
|
||||
)
|
||||
table_1 = data_fixture.create_database_table(user=user)
|
||||
grid = data_fixture.create_grid_view(table=table_1, order=1)
|
||||
gallery = data_fixture.create_gallery_view(table=table_1, order=2)
|
||||
|
||||
response = api_client.get(
|
||||
reverse("api:database:views:list", kwargs={"table_id": table_1.id}),
|
||||
{"type": "grid"},
|
||||
**{"HTTP_AUTHORIZATION": f"JWT {token}"},
|
||||
)
|
||||
assert response.status_code == HTTP_200_OK
|
||||
response_json = response.json()
|
||||
assert len(response_json) == 1
|
||||
assert response_json[0]["id"] == grid.id
|
||||
|
||||
response = api_client.get(
|
||||
reverse("api:database:views:list", kwargs={"table_id": table_1.id}),
|
||||
{"type": "gallery"},
|
||||
**{"HTTP_AUTHORIZATION": f"JWT {token}"},
|
||||
)
|
||||
assert response.status_code == HTTP_200_OK
|
||||
response_json = response.json()
|
||||
assert len(response_json) == 1
|
||||
assert response_json[0]["id"] == gallery.id
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_list_views_doesnt_do_n_queries(api_client, data_fixture):
|
||||
user, token = data_fixture.create_user_and_token(
|
||||
|
|
|
@ -192,4 +192,5 @@
|
|||
height: 44px;
|
||||
background-color: $color-neutral-50;
|
||||
border-top: solid 1px $color-neutral-200;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
|
|
@ -130,7 +130,7 @@
|
|||
<div
|
||||
class="select-row-modal__foot"
|
||||
:style="{
|
||||
width: 3 * 200 + 'px',
|
||||
width: fields.length * 200 + 'px',
|
||||
}"
|
||||
></div>
|
||||
</div>
|
||||
|
@ -163,7 +163,11 @@ import SelectRowField from '@baserow/modules/database/components/row/SelectRowFi
|
|||
import RowCreateModal from '@baserow/modules/database/components/row/RowCreateModal'
|
||||
import { prepareRowForRequest } from '@baserow/modules/database/utils/row'
|
||||
import { DatabaseApplicationType } from '@baserow/modules/database/applicationTypes'
|
||||
import { sortFieldsByOrderAndIdFunction } from '@baserow/modules/database/utils/view'
|
||||
import {
|
||||
filterVisibleFieldsFunction,
|
||||
sortFieldsByOrderAndIdFunction,
|
||||
} from '@baserow/modules/database/utils/view'
|
||||
import { GridViewType } from '@baserow/modules/database/viewTypes'
|
||||
|
||||
export default {
|
||||
name: 'SelectRowContent',
|
||||
|
@ -193,8 +197,6 @@ export default {
|
|||
lastHoveredRow: null,
|
||||
addRowHover: false,
|
||||
searchDebounce: null,
|
||||
firstView: null,
|
||||
fieldOptions: {},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -224,14 +226,6 @@ export default {
|
|||
},
|
||||
},
|
||||
async mounted() {
|
||||
// We need to get the first view in order to
|
||||
// sort the fields by their order
|
||||
await this.fetchFirstView(this.tableId)
|
||||
|
||||
// Get the field options
|
||||
// they contain the order of the fields
|
||||
await this.fetchFieldOptions(this.firstView.id)
|
||||
|
||||
// The first time we have to fetch the fields because they are unknown for this
|
||||
// table.
|
||||
if (!(await this.fetchFields(this.tableId))) {
|
||||
|
@ -243,6 +237,8 @@ export default {
|
|||
return false
|
||||
}
|
||||
|
||||
await this.orderFieldsByFirstGridViewFieldOptions(this.tableId)
|
||||
|
||||
// Because most of the template depends on having some initial data we mark the
|
||||
// state as loaded after that. Only a loading animation is shown if there isn't any
|
||||
// data.
|
||||
|
@ -309,9 +305,7 @@ export default {
|
|||
const primaryIndex = data.findIndex((item) => item.primary === true)
|
||||
this.primary =
|
||||
primaryIndex !== -1 ? data.splice(primaryIndex, 1)[0] : null
|
||||
this.fields = data.sort(
|
||||
sortFieldsByOrderAndIdFunction(this.fieldOptions)
|
||||
)
|
||||
this.fields = data
|
||||
return true
|
||||
} catch (error) {
|
||||
notifyIf(error, 'row')
|
||||
|
@ -320,33 +314,38 @@ export default {
|
|||
return false
|
||||
}
|
||||
},
|
||||
async fetchFirstView(tableId) {
|
||||
/**
|
||||
* This method fetches the first grid and the related field options. The ordering
|
||||
* of that grid view will be applied to the already fetched fields. If anything
|
||||
* goes wrong or if there isn't a grid view, the original order will be used.
|
||||
*/
|
||||
async orderFieldsByFirstGridViewFieldOptions(tableId) {
|
||||
try {
|
||||
const { data: views } = await ViewService(this.$client).fetchAll(
|
||||
tableId,
|
||||
true,
|
||||
true
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
// We can safely limit to `1` because the backend provides the views ordered.
|
||||
1,
|
||||
// We want to fetch the first grid view because for that type we're sure it's
|
||||
// compatible with `filterVisibleFieldsFunction` and
|
||||
// `sortFieldsByOrderAndIdFunction`. Others might also work, but this
|
||||
// component is styled like a grid view and it makes to most sense to reflect
|
||||
// that here.
|
||||
GridViewType.getType()
|
||||
)
|
||||
|
||||
// Get the view with the smallest order
|
||||
this.firstView = views.reduce((viewWithSmallestOrder, view) => {
|
||||
if (viewWithSmallestOrder === null) {
|
||||
return view
|
||||
}
|
||||
return view.order < viewWithSmallestOrder.order
|
||||
? view
|
||||
: viewWithSmallestOrder
|
||||
}, null)
|
||||
} catch (error) {
|
||||
notifyIf(error, 'view')
|
||||
}
|
||||
},
|
||||
async fetchFieldOptions(viewId) {
|
||||
try {
|
||||
if (views.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const {
|
||||
data: { field_options: fieldOptions },
|
||||
} = await ViewService(this.$client).fetchFieldOptions(viewId)
|
||||
this.fieldOptions = fieldOptions
|
||||
} = await ViewService(this.$client).fetchFieldOptions(views[0].id)
|
||||
this.fields = this.fields
|
||||
.filter(filterVisibleFieldsFunction(fieldOptions))
|
||||
.sort(sortFieldsByOrderAndIdFunction(fieldOptions))
|
||||
} catch (error) {
|
||||
notifyIf(error, 'view')
|
||||
}
|
||||
|
|
|
@ -7,7 +7,9 @@ export default (client) => {
|
|||
tableId,
|
||||
includeFilters = false,
|
||||
includeSortings = false,
|
||||
includeDecorations = false
|
||||
includeDecorations = false,
|
||||
limit = null,
|
||||
type = null
|
||||
) {
|
||||
const config = {
|
||||
params: {},
|
||||
|
@ -30,6 +32,14 @@ export default (client) => {
|
|||
config.params.include = include.join(',')
|
||||
}
|
||||
|
||||
if (limit !== null) {
|
||||
config.params.limit = limit
|
||||
}
|
||||
|
||||
if (type !== null) {
|
||||
config.params.type = type
|
||||
}
|
||||
|
||||
return client.get(`/database/views/table/${tableId}/`, config)
|
||||
},
|
||||
create(tableId, values) {
|
||||
|
|
|
@ -26,6 +26,7 @@ export function getRowSortFunction($registry, sortings, fields) {
|
|||
sortFunction = sortFunction.thenBy((a, b) => a.id - b.id)
|
||||
return sortFunction
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a sort function for fields based on order and id.
|
||||
*/
|
||||
|
|
Loading…
Add table
Reference in a new issue