mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-04-11 07:51:20 +00:00
Merge branch '160-temporarily-disable-all-filters' into 'develop'
Resolve "Temporarily disable all filters" Closes #160 See merge request bramw/baserow!112
This commit is contained in:
commit
564f2d7461
13 changed files with 187 additions and 23 deletions
backend
src/baserow/contrib/database
api/views
migrations
views
tests/baserow/contrib/database
web-frontend/modules
core/assets/scss/components
database
|
@ -91,10 +91,11 @@ class GridViewFieldOptionsFieldFix(OpenApiSerializerFieldExtension):
|
|||
|
||||
class GridViewSerializer(serializers.ModelSerializer):
|
||||
field_options = GridViewFieldOptionsField(required=False)
|
||||
filters_disabled = serializers.BooleanField(required=False)
|
||||
|
||||
class Meta:
|
||||
model = GridView
|
||||
fields = ('field_options',)
|
||||
fields = ('field_options', 'filters_disabled')
|
||||
|
||||
|
||||
class GridViewFieldOptionsSerializer(serializers.ModelSerializer):
|
||||
|
|
|
@ -92,7 +92,7 @@ class ViewSerializer(serializers.ModelSerializer):
|
|||
class Meta:
|
||||
model = View
|
||||
fields = ('id', 'name', 'order', 'type', 'table', 'filter_type', 'filters',
|
||||
'sortings')
|
||||
'sortings', 'filters_disabled')
|
||||
extra_kwargs = {
|
||||
'id': {
|
||||
'read_only': True
|
||||
|
@ -128,14 +128,15 @@ class CreateViewSerializer(serializers.ModelSerializer):
|
|||
|
||||
class Meta:
|
||||
model = View
|
||||
fields = ('name', 'type', 'filter_type')
|
||||
fields = ('name', 'type', 'filter_type', 'filters_disabled')
|
||||
|
||||
|
||||
class UpdateViewSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = View
|
||||
fields = ('name', 'filter_type')
|
||||
fields = ('name', 'filter_type', 'filters_disabled')
|
||||
extra_kwargs = {
|
||||
'name': {'required': False},
|
||||
'filter_type': {'required': False}
|
||||
'filter_type': {'required': False},
|
||||
'filters_disabled': {'required': False},
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
# Generated by Django 2.2.11 on 2020-10-25 22:30
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('database', '0016_token_tokenpermission'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='view',
|
||||
name='filters_disabled',
|
||||
field=models.BooleanField(
|
||||
default=False,
|
||||
help_text='Allows users to see results unfiltered '
|
||||
'while still keeping the filters for the view'
|
||||
'saved.'
|
||||
),
|
||||
),
|
||||
]
|
|
@ -84,7 +84,11 @@ class ViewHandler:
|
|||
# Figure out which model to use for the given view type.
|
||||
view_type = view_type_registry.get(type_name)
|
||||
model_class = view_type.model_class
|
||||
allowed_fields = ['name', 'filter_type'] + view_type.allowed_fields
|
||||
allowed_fields = [
|
||||
'name',
|
||||
'filter_type',
|
||||
'filters_disabled'
|
||||
] + view_type.allowed_fields
|
||||
view_values = extract_allowed(kwargs, allowed_fields)
|
||||
last_order = model_class.get_last_order(table)
|
||||
|
||||
|
@ -117,7 +121,11 @@ class ViewHandler:
|
|||
raise UserNotInGroupError(user, group)
|
||||
|
||||
view_type = view_type_registry.get_by_model(view)
|
||||
allowed_fields = ['name', 'filter_type'] + view_type.allowed_fields
|
||||
allowed_fields = [
|
||||
'name',
|
||||
'filter_type',
|
||||
'filters_disabled'
|
||||
] + view_type.allowed_fields
|
||||
view = set_allowed_attrs(kwargs, allowed_fields, view)
|
||||
view.save()
|
||||
|
||||
|
@ -218,6 +226,10 @@ class ViewHandler:
|
|||
if not hasattr(model, '_field_objects'):
|
||||
raise ValueError('A queryset of the table model is required.')
|
||||
|
||||
# If the filter are disabled we don't have to do anything with the queryset.
|
||||
if view.filters_disabled:
|
||||
return queryset
|
||||
|
||||
q_filters = Q()
|
||||
|
||||
for view_filter in view.viewfilter_set.all():
|
||||
|
|
|
@ -41,6 +41,11 @@ class View(OrderableMixin, PolymorphicContentTypeMixin, models.Model):
|
|||
help_text='Indicates whether all the rows should apply to all filters (AND) '
|
||||
'or to any filter (OR).'
|
||||
)
|
||||
filters_disabled = models.BooleanField(
|
||||
default=False,
|
||||
help_text='Allows users to see results unfiltered while still keeping'
|
||||
'the filters saved for the view.'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ('order',)
|
||||
|
|
|
@ -18,7 +18,12 @@ def test_list_views(api_client, data_fixture):
|
|||
table_2 = data_fixture.create_database_table()
|
||||
view_1 = data_fixture.create_grid_view(table=table_1, order=1)
|
||||
view_2 = data_fixture.create_grid_view(table=table_1, order=3)
|
||||
view_3 = data_fixture.create_grid_view(table=table_1, order=2, filter_type='OR')
|
||||
view_3 = data_fixture.create_grid_view(
|
||||
table=table_1,
|
||||
order=2,
|
||||
filter_type='OR',
|
||||
filters_disabled=True
|
||||
)
|
||||
data_fixture.create_grid_view(table=table_2, order=1)
|
||||
|
||||
response = api_client.get(
|
||||
|
@ -34,14 +39,17 @@ def test_list_views(api_client, data_fixture):
|
|||
assert response_json[0]['id'] == view_1.id
|
||||
assert response_json[0]['type'] == 'grid'
|
||||
assert response_json[0]['filter_type'] == 'AND'
|
||||
assert response_json[0]['filters_disabled'] is False
|
||||
|
||||
assert response_json[1]['id'] == view_3.id
|
||||
assert response_json[1]['type'] == 'grid'
|
||||
assert response_json[1]['filter_type'] == 'OR'
|
||||
assert response_json[1]['filters_disabled'] is True
|
||||
|
||||
assert response_json[2]['id'] == view_2.id
|
||||
assert response_json[2]['type'] == 'grid'
|
||||
assert response_json[2]['filter_type'] == 'AND'
|
||||
assert response_json[2]['filters_disabled'] is False
|
||||
|
||||
response = api_client.get(
|
||||
reverse('api:database:views:list', kwargs={'table_id': table_2.id}), **{
|
||||
|
@ -202,7 +210,8 @@ def test_create_view(api_client, data_fixture):
|
|||
{
|
||||
'name': 'Test 1',
|
||||
'type': 'grid',
|
||||
'filter_type': 'OR'
|
||||
'filter_type': 'OR',
|
||||
'filters_disabled': True
|
||||
},
|
||||
format='json',
|
||||
HTTP_AUTHORIZATION=f'JWT {token}'
|
||||
|
@ -211,12 +220,14 @@ def test_create_view(api_client, data_fixture):
|
|||
assert response.status_code == HTTP_200_OK
|
||||
assert response_json['type'] == 'grid'
|
||||
assert response_json['filter_type'] == 'OR'
|
||||
assert response_json['filters_disabled'] is True
|
||||
|
||||
grid = GridView.objects.filter()[0]
|
||||
assert response_json['id'] == grid.id
|
||||
assert response_json['name'] == grid.name
|
||||
assert response_json['order'] == grid.order
|
||||
assert response_json['filter_type'] == grid.filter_type
|
||||
assert response_json['filters_disabled'] == grid.filters_disabled
|
||||
assert 'filters' not in response_json
|
||||
assert 'sortings' not in response_json
|
||||
|
||||
|
@ -227,7 +238,8 @@ def test_create_view(api_client, data_fixture):
|
|||
{
|
||||
'name': 'Test 2',
|
||||
'type': 'grid',
|
||||
'filter_type': 'AND'
|
||||
'filter_type': 'AND',
|
||||
'filters_disabled': False
|
||||
},
|
||||
format='json',
|
||||
HTTP_AUTHORIZATION=f'JWT {token}'
|
||||
|
@ -237,9 +249,28 @@ def test_create_view(api_client, data_fixture):
|
|||
assert response_json['name'] == 'Test 2'
|
||||
assert response_json['type'] == 'grid'
|
||||
assert response_json['filter_type'] == 'AND'
|
||||
assert response_json['filters_disabled'] is False
|
||||
assert response_json['filters'] == []
|
||||
assert response_json['sortings'] == []
|
||||
|
||||
response = api_client.post(
|
||||
'{}'.format(reverse('api:database:views:list', kwargs={'table_id': table.id})),
|
||||
{
|
||||
'name': 'Test 3',
|
||||
'type': 'grid'
|
||||
},
|
||||
format='json',
|
||||
HTTP_AUTHORIZATION=f'JWT {token}'
|
||||
)
|
||||
response_json = response.json()
|
||||
assert response.status_code == HTTP_200_OK
|
||||
assert response_json['name'] == 'Test 3'
|
||||
assert response_json['type'] == 'grid'
|
||||
assert response_json['filter_type'] == 'AND'
|
||||
assert response_json['filters_disabled'] is False
|
||||
assert 'filters' not in response_json
|
||||
assert 'sortings' not in response_json
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_get_view(api_client, data_fixture):
|
||||
|
@ -280,6 +311,7 @@ def test_get_view(api_client, data_fixture):
|
|||
assert response_json['type'] == 'grid'
|
||||
assert response_json['table']['id'] == table.id
|
||||
assert response_json['filter_type'] == 'AND'
|
||||
assert not response_json['filters_disabled']
|
||||
assert 'filters' not in response_json
|
||||
assert 'sortings' not in response_json
|
||||
|
||||
|
@ -352,15 +384,20 @@ def test_update_view(api_client, data_fixture):
|
|||
assert response_json['id'] == view.id
|
||||
assert response_json['name'] == 'Test 1'
|
||||
assert response_json['filter_type'] == 'AND'
|
||||
assert not response_json['filters_disabled']
|
||||
|
||||
view.refresh_from_db()
|
||||
assert view.name == 'Test 1'
|
||||
assert view.filter_type == 'AND'
|
||||
assert not view.filters_disabled
|
||||
|
||||
url = reverse('api:database:views:item', kwargs={'view_id': view.id})
|
||||
response = api_client.patch(
|
||||
url,
|
||||
{'filter_type': 'OR'},
|
||||
{
|
||||
'filter_type': 'OR',
|
||||
'filters_disabled': True,
|
||||
},
|
||||
format='json',
|
||||
HTTP_AUTHORIZATION=f'JWT {token}'
|
||||
)
|
||||
|
@ -368,11 +405,13 @@ def test_update_view(api_client, data_fixture):
|
|||
assert response.status_code == HTTP_200_OK
|
||||
assert response_json['id'] == view.id
|
||||
assert response_json['filter_type'] == 'OR'
|
||||
assert response_json['filters_disabled']
|
||||
assert 'filters' not in response_json
|
||||
assert 'sortings' not in response_json
|
||||
|
||||
view.refresh_from_db()
|
||||
assert view.filter_type == 'OR'
|
||||
assert view.filters_disabled
|
||||
|
||||
filter_1 = data_fixture.create_view_filter(view=view)
|
||||
url = reverse('api:database:views:item', kwargs={'view_id': view.id})
|
||||
|
@ -386,6 +425,7 @@ def test_update_view(api_client, data_fixture):
|
|||
assert response.status_code == HTTP_200_OK
|
||||
assert response_json['id'] == view.id
|
||||
assert response_json['filter_type'] == 'AND'
|
||||
assert response_json['filters_disabled'] == True
|
||||
assert response_json['filters'][0]['id'] == filter_1.id
|
||||
assert response_json['sortings'] == []
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ def test_get_view(data_fixture):
|
|||
assert view.id == grid.id
|
||||
assert view.name == grid.name
|
||||
assert view.filter_type == 'AND'
|
||||
assert not view.filters_disabled
|
||||
assert isinstance(view, View)
|
||||
|
||||
view = handler.get_view(user=user, view_id=grid.id, view_model=GridView)
|
||||
|
@ -43,6 +44,7 @@ def test_get_view(data_fixture):
|
|||
assert view.id == grid.id
|
||||
assert view.name == grid.name
|
||||
assert view.filter_type == 'AND'
|
||||
assert not view.filters_disabled
|
||||
assert isinstance(view, GridView)
|
||||
|
||||
# If the error is raised we know for sure that the query has resolved.
|
||||
|
@ -58,6 +60,7 @@ def test_create_view(data_fixture):
|
|||
user = data_fixture.create_user()
|
||||
user_2 = data_fixture.create_user()
|
||||
table = data_fixture.create_database_table(user=user)
|
||||
table_2 = data_fixture.create_database_table(user=user)
|
||||
|
||||
handler = ViewHandler()
|
||||
handler.create_view(user=user, table=table, type_name='grid', name='Test grid')
|
||||
|
@ -70,6 +73,32 @@ def test_create_view(data_fixture):
|
|||
assert grid.order == 1
|
||||
assert grid.table == table
|
||||
assert grid.filter_type == 'AND'
|
||||
assert not grid.filters_disabled
|
||||
|
||||
handler.create_view(user=user, table=table, type_name='grid',
|
||||
name='Something else', filter_type='OR', filters_disabled=True)
|
||||
|
||||
assert View.objects.all().count() == 2
|
||||
assert GridView.objects.all().count() == 2
|
||||
|
||||
grid = GridView.objects.all().last()
|
||||
assert grid.name == 'Something else'
|
||||
assert grid.order == 2
|
||||
assert grid.table == table
|
||||
assert grid.filter_type == 'OR'
|
||||
assert grid.filters_disabled
|
||||
|
||||
grid = handler.create_view(user=user, table=table_2, type_name='grid', name='Name',
|
||||
filter_type='OR', filters_disabled=False)
|
||||
|
||||
assert View.objects.all().count() == 3
|
||||
assert GridView.objects.all().count() == 3
|
||||
|
||||
assert grid.name == 'Name'
|
||||
assert grid.order == 1
|
||||
assert grid.table == table_2
|
||||
assert grid.filter_type == 'OR'
|
||||
assert not grid.filters_disabled
|
||||
|
||||
with pytest.raises(UserNotInGroupError):
|
||||
handler.create_view(user=user_2, table=table, type_name='grid', name='')
|
||||
|
@ -97,11 +126,14 @@ def test_update_view(data_fixture):
|
|||
|
||||
grid.refresh_from_db()
|
||||
assert grid.name == 'Test 1'
|
||||
assert grid.filter_type == 'AND'
|
||||
assert not grid.filters_disabled
|
||||
|
||||
handler.update_view(user=user, view=grid, filter_type='OR')
|
||||
handler.update_view(user=user, view=grid, filter_type='OR', filters_disabled=True)
|
||||
|
||||
grid.refresh_from_db()
|
||||
assert grid.filter_type == 'OR'
|
||||
assert grid.filters_disabled
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
|
@ -334,6 +366,14 @@ def test_apply_filters(data_fixture):
|
|||
assert rows[0].id == row_2.id
|
||||
assert rows[1].id == row_4.id
|
||||
|
||||
grid_view.filters_disabled = True
|
||||
grid_view.save()
|
||||
rows = view_handler.apply_filters(grid_view, model.objects.all())
|
||||
assert rows[0].id == row_1.id
|
||||
assert rows[1].id == row_2.id
|
||||
assert rows[2].id == row_3.id
|
||||
assert rows[3].id == row_4.id
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_get_filter(data_fixture):
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# Changelog
|
||||
|
||||
* Fixed API docs scrollbar size issue.
|
||||
* Implemented a switch to disable all filters without deleting them.
|
||||
|
||||
## Released (2020-11-02)
|
||||
|
||||
|
|
|
@ -6,6 +6,26 @@
|
|||
border-radius: 6px;
|
||||
border: 1px solid $color-neutral-200;
|
||||
box-shadow: 0 2px 6px 0 rgba($black, 0.16);
|
||||
|
||||
&.context--loading-overlay {
|
||||
&::before {
|
||||
content: '';
|
||||
border-radius: 6px;
|
||||
background-color: rgba(0, 0, 0, 0.16);
|
||||
z-index: 2;
|
||||
|
||||
@include absolute(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
z-index: 2;
|
||||
margin: -7px auto auto -7px;
|
||||
|
||||
@include loading(14px);
|
||||
@include absolute(50%, auto, auto, 50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.context--loading {
|
||||
|
|
|
@ -95,6 +95,13 @@
|
|||
line-height: 30px;
|
||||
}
|
||||
|
||||
.filters_footer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.filters__add {
|
||||
display: inline-block;
|
||||
margin: 12px 0 6px 4px;
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
<template>
|
||||
<Context ref="context" class="filters">
|
||||
<Context
|
||||
ref="context"
|
||||
class="filters"
|
||||
:class="{ 'context--loading-overlay': view._.loading }"
|
||||
>
|
||||
<div v-show="view.filters.length === 0">
|
||||
<div class="filters__none">
|
||||
<div class="filters__none-title">You have not yet created a filter</div>
|
||||
|
@ -13,8 +17,7 @@
|
|||
:key="filter.id"
|
||||
class="filters__item"
|
||||
:class="{
|
||||
'filters__item--loading':
|
||||
filter._.loading || (index === 1 && view._.loading),
|
||||
'filters__item--loading': filter._.loading,
|
||||
}"
|
||||
>
|
||||
<a class="filters__remove" @click.prevent="deleteFilter(filter)">
|
||||
|
@ -27,7 +30,7 @@
|
|||
:value="view.filter_type"
|
||||
:show-search="false"
|
||||
class="dropdown--floating"
|
||||
@input="updateType(view, filter, $event)"
|
||||
@input="updateView(view, { filter_type: $event })"
|
||||
>
|
||||
<DropdownItem name="And" value="AND"></DropdownItem>
|
||||
<DropdownItem name="Or" value="OR"></DropdownItem>
|
||||
|
@ -87,10 +90,19 @@
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<a class="filters__add" @click.prevent="addFilter()">
|
||||
<i class="fas fa-plus"></i>
|
||||
add filter
|
||||
</a>
|
||||
<div class="filters_footer">
|
||||
<a class="filters__add" @click.prevent="addFilter()">
|
||||
<i class="fas fa-plus"></i>
|
||||
add filter
|
||||
</a>
|
||||
<div v-if="view.filters.length > 0">
|
||||
<SwitchInput
|
||||
:value="view.filters_disabled"
|
||||
@input="updateView(view, { filters_disabled: $event })"
|
||||
>all disabled</SwitchInput
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</Context>
|
||||
</template>
|
||||
|
||||
|
@ -253,13 +265,13 @@ export default {
|
|||
* Updates the view filter type. It will mark the view as loading because that
|
||||
* will also trigger the loading state of the second filter.
|
||||
*/
|
||||
async updateType(view, filter, value) {
|
||||
async updateView(view, values) {
|
||||
this.$store.dispatch('view/setItemLoading', { view, value: true })
|
||||
|
||||
try {
|
||||
await this.$store.dispatch('view/update', {
|
||||
view,
|
||||
values: { filter_type: value },
|
||||
values,
|
||||
})
|
||||
this.$emit('changed')
|
||||
} catch (error) {
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
class="grid-view__column"
|
||||
:class="{
|
||||
'grid-view__column--filtered':
|
||||
!view.filters_disabled &&
|
||||
view.filters.findIndex((filter) => filter.field === field.id) !== -1,
|
||||
'grid-view__column--sorted':
|
||||
view.sortings.findIndex((sort) => sort.field === field.id) !== -1,
|
||||
|
|
|
@ -555,7 +555,8 @@ export const actions = {
|
|||
Object.keys(overrides).forEach((key) => {
|
||||
values[key] = overrides[key]
|
||||
})
|
||||
const matches = isValid(view.filters, values)
|
||||
// The value is always valid if the filters are disabled.
|
||||
const matches = view.filters_disabled ? true : isValid(view.filters, values)
|
||||
commit('SET_ROW_MATCH_FILTERS', { row, value: matches })
|
||||
},
|
||||
/**
|
||||
|
|
Loading…
Add table
Reference in a new issue