1
0
Fork 0
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 

See merge request 
This commit is contained in:
Bram Wiepjes 2020-11-05 08:05:36 +00:00
commit 564f2d7461
13 changed files with 187 additions and 23 deletions
backend
src/baserow/contrib/database
tests/baserow/contrib/database
changelog.md
web-frontend/modules
core/assets/scss/components
database

View file

@ -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):

View file

@ -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},
}

View file

@ -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.'
),
),
]

View file

@ -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():

View file

@ -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',)

View file

@ -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'] == []

View file

@ -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):

View file

@ -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)

View file

@ -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 {

View file

@ -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;

View file

@ -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) {

View file

@ -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,

View file

@ -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 })
},
/**