1
0
Fork 0
mirror of https://gitlab.com/bramw/baserow.git synced 2025-04-15 09:34:13 +00:00

Resolve "Add files lower than view filter"

This commit is contained in:
Afonso Silva 2023-09-29 11:09:14 +00:00 committed by Bram Wiepjes
parent 63a015de1b
commit b933fa64e6
9 changed files with 171 additions and 2 deletions
backend
src/baserow
config/settings
contrib/database
tests/baserow/contrib/database/view
changelog/entries/unreleased/feature
web-frontend
locales
modules/database
test/unit/database

View file

@ -565,6 +565,7 @@ SPECTACULAR_SETTINGS = {
"not_equal",
"filename_contains",
"has_file_type",
"files_lower_than",
"contains",
"contains_not",
"length_is_lower_than",

View file

@ -306,6 +306,7 @@ class DatabaseConfig(AppConfig):
EmptyViewFilterType,
EqualViewFilterType,
FilenameContainsViewFilterType,
FilesLowerThanViewFilterType,
HasFileTypeViewFilterType,
HigherThanViewFilterType,
IsEvenAndWholeViewFilterType,
@ -328,6 +329,7 @@ class DatabaseConfig(AppConfig):
view_filter_type_registry.register(EqualViewFilterType())
view_filter_type_registry.register(NotEqualViewFilterType())
view_filter_type_registry.register(FilenameContainsViewFilterType())
view_filter_type_registry.register(FilesLowerThanViewFilterType()),
view_filter_type_registry.register(HasFileTypeViewFilterType())
view_filter_type_registry.register(ContainsViewFilterType())
view_filter_type_registry.register(ContainsNotViewFilterType())

View file

@ -5,7 +5,7 @@ from math import ceil, floor
from typing import Any, Dict, Optional, Tuple, Union
from django.db.models import DateField, DateTimeField, IntegerField, Q
from django.db.models.expressions import F
from django.db.models.expressions import F, Func
from django.db.models.functions import Extract, Length, Mod, TruncDate
import pytz
@ -146,6 +146,39 @@ class HasFileTypeViewFilterType(ViewFilterType):
return Q()
class FilesLowerThanViewFilterType(ViewFilterType):
"""
The files lower than filter checks if the number of file objects present
in a column of type file is smaller than a given value.
It is only compatible with fields.JSONField which contain a list of File
JSON Objects.
"""
type = "files_lower_than"
compatible_field_types = [FileFieldType.type]
def get_filter(self, field_name, value, model_field, field):
value = value.strip()
# If a non numeric value has been provided we do not want to filter.
if not value.lstrip("-").isdigit():
return Q()
# Annotate the query with the length of the JSON array, using the
# proper PostgreSQL function
# See: https://www.postgresql.org/docs/current/functions-json.html
annotation_query = Func(
F(field_name),
function="jsonb_array_length",
output_field=IntegerField(),
)
return AnnotatedQ(
annotation={f"{field_name}_length": annotation_query},
q={f"{field_name}_length__lt": int(value)},
)
class ContainsViewFilterType(ViewFilterType):
"""
The contains filter checks if the field value contains the provided filter value.

View file

@ -3617,6 +3617,76 @@ def test_has_file_type(data_fixture):
assert row_with_single_document_and_multiple_images.id in ids
@pytest.mark.django_db
def test_files_lower_than(data_fixture):
user = data_fixture.create_user()
table = data_fixture.create_database_table(user=user)
grid_view = data_fixture.create_grid_view(table=table)
file_field = data_fixture.create_file_field(table=table)
handler = ViewHandler()
model = table.get_model()
row_without_files = model.objects.create(**{f"field_{file_field.id}": []})
row_with_one_file = model.objects.create(
**{
f"field_{file_field.id}": [
{"visible_name": "test_file_1.txt", "is_image": False},
],
}
)
row_with_two_files = model.objects.create(
**{
f"field_{file_field.id}": [
{"visible_name": "test_file_1.txt", "is_image": False},
{"visible_name": "test_file_2.txt", "is_image": False},
],
},
)
row_with_three_files = model.objects.create(
**{
f"field_{file_field.id}": [
{"visible_name": "test_file_1.txt", "is_image": False},
{"visible_name": "test_file_2.txt", "is_image": False},
{"visible_name": "test_file_3.txt", "is_image": False},
],
},
)
# There are no rows whose file column has less than 0 files
view_filter = data_fixture.create_view_filter(
view=grid_view,
field=file_field,
type="files_lower_than",
value="0",
)
ids = [r.id for r in handler.apply_filters(grid_view, model.objects.all()).all()]
assert len(ids) == 0
# There are two rows whose file column has less than 2 files
view_filter.value = 2
view_filter.save()
ids = [r.id for r in handler.apply_filters(grid_view, model.objects.all()).all()]
assert len(ids) == 2
assert row_without_files.id in ids
assert row_with_one_file.id in ids
# There are three rows whose file column has less than 4 files
view_filter.value = 4
view_filter.save()
ids = [r.id for r in handler.apply_filters(grid_view, model.objects.all()).all()]
assert len(ids) == 4
assert row_without_files.id in ids
assert row_with_one_file.id in ids
assert row_with_two_files.id in ids
assert row_with_three_files.id in ids
view_filter.value = "-1"
view_filter.save()
ids = [r.id for r in handler.apply_filters(grid_view, model.objects.all()).all()]
assert len(ids) == 0
@pytest.mark.django_db
def test_link_row_preload_values(data_fixture, django_assert_num_queries):
user = data_fixture.create_user()

View file

@ -0,0 +1,7 @@
{
"type": "feature",
"message": "Add a new view filter for selecting rows whose file columns have less files than a given number",
"issue_number": 1771,
"bullet_points": [],
"created_at": "2023-09-16"
}

View file

@ -175,7 +175,8 @@
"lowerThan": "lower than",
"isEvenAndWhole": "is even and whole",
"lengthIsLowerThan": "length is lower than",
"hasFileType": "has file type"
"hasFileType": "has file type",
"filesLowerThan": "files lower than"
},
"viewType": {
"grid": "Grid",

View file

@ -34,6 +34,7 @@ import {
DateNotEqualViewFilterType,
ContainsViewFilterType,
FilenameContainsViewFilterType,
FilesLowerThanViewFilterType,
HasFileTypeViewFilterType,
ContainsNotViewFilterType,
LengthIsLowerThanViewFilterType,
@ -361,6 +362,10 @@ export default (context) => {
new FilenameContainsViewFilterType(context)
)
app.$registry.register('viewFilter', new HasFileTypeViewFilterType(context))
app.$registry.register(
'viewFilter',
new FilesLowerThanViewFilterType(context)
)
app.$registry.register(
'viewFilter',
new LengthIsLowerThanViewFilterType(context)

View file

@ -281,6 +281,33 @@ export class HasFileTypeViewFilterType extends ViewFilterType {
}
}
export class FilesLowerThanViewFilterType extends ViewFilterType {
static getType() {
return 'files_lower_than'
}
getName() {
const { i18n } = this.app
return i18n.t('viewFilter.filesLowerThan')
}
getExample() {
return '2'
}
getInputComponent() {
return ViewFilterTypeNumber
}
getCompatibleFieldTypes() {
return ['file']
}
matches(rowValue, filterValue, field, fieldType) {
return rowValue.length < parseInt(filterValue)
}
}
export class ContainsViewFilterType extends ViewFilterType {
static getType() {
return 'contains'

View file

@ -20,6 +20,7 @@ import {
MultipleSelectHasFilterType,
MultipleSelectHasNotFilterType,
HasFileTypeViewFilterType,
FilesLowerThanViewFilterType,
LengthIsLowerThanViewFilterType,
LinkRowContainsFilterType,
LinkRowNotContainsFilterType,
@ -1309,4 +1310,26 @@ describe('All Tests', () => {
)
).toBe(true)
})
test('FilesLowerThanFilterType', () => {
expect(new FilesLowerThanViewFilterType().matches([], 0)).toBe(false)
expect(
new FilesLowerThanViewFilterType().matches(
[
{ visible_name: 'test_file_1.txt' },
{ visible_name: 'test_file_2.txt' },
],
2
)
).toBe(false)
expect(
new FilesLowerThanViewFilterType().matches(
[
{ visible_name: 'test_file_1.txt' },
{ visible_name: 'test_file_2.txt' },
],
3
)
).toBe(true)
})
})