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:
parent
63a015de1b
commit
b933fa64e6
9 changed files with 171 additions and 2 deletions
backend
src/baserow
tests/baserow/contrib/database/view
changelog/entries/unreleased/feature
web-frontend
|
@ -565,6 +565,7 @@ SPECTACULAR_SETTINGS = {
|
|||
"not_equal",
|
||||
"filename_contains",
|
||||
"has_file_type",
|
||||
"files_lower_than",
|
||||
"contains",
|
||||
"contains_not",
|
||||
"length_is_lower_than",
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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"
|
||||
}
|
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Add table
Reference in a new issue