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

Merge branch '786-order-single-select-options-by-order' into 'develop'

Sort single select rows by select option order

Closes 

See merge request 
This commit is contained in:
Bram Wiepjes 2025-03-17 15:16:37 +00:00
commit 53a8c155e4
40 changed files with 1266 additions and 246 deletions
backend
changelog/entries/unreleased/feature
enterprise/backend/src/baserow_enterprise/integrations/local_baserow
premium
backend
src/baserow_premium/fields
tests/baserow_premium_tests/api/views/views
web-frontend/modules/baserow_premium/store/view
web-frontend
modules
test/unit/database/store/view

View file

@ -35,6 +35,7 @@ from baserow.contrib.database.fields.field_filters import (
)
from baserow.contrib.database.fields.models import Field
from baserow.contrib.database.views.models import (
DEFAULT_SORT_TYPE_KEY,
SORT_ORDER_ASC,
SORT_ORDER_DESC,
View,
@ -242,7 +243,9 @@ class AirtableViewType(Instance):
mapping_entry = field_mapping[sort["columnId"]]
baserow_field_type = mapping_entry["baserow_field_type"]
baserow_field = mapping_entry["baserow_field"]
can_order_by = baserow_field_type.check_can_order_by(baserow_field)
can_order_by = baserow_field_type.check_can_order_by(
baserow_field, DEFAULT_SORT_TYPE_KEY
)
if not can_order_by:
import_report.add_failed(
@ -303,7 +306,9 @@ class AirtableViewType(Instance):
mapping_entry = field_mapping[group["columnId"]]
baserow_field_type = mapping_entry["baserow_field_type"]
baserow_field = mapping_entry["baserow_field"]
can_order_by = baserow_field_type.check_can_group_by(baserow_field)
can_order_by = baserow_field_type.check_can_group_by(
baserow_field, DEFAULT_SORT_TYPE_KEY
)
if not can_order_by:
import_report.add_failed(

View file

@ -38,8 +38,8 @@ ERROR_ORDER_BY_FIELD_NOT_FOUND = (
ERROR_ORDER_BY_FIELD_NOT_POSSIBLE = (
"ERROR_ORDER_BY_FIELD_NOT_POSSIBLE",
HTTP_400_BAD_REQUEST,
"It is not possible to order by {e.field_name} because the field type "
"{e.field_type} does not support filtering.",
"It is not possible to order by {e.field_name} using sort type {e.sort_type} "
"because the field type {e.field_type} does not support it.",
)
ERROR_FILTER_FIELD_NOT_FOUND = (
"ERROR_FILTER_FIELD_NOT_FOUND",

View file

@ -57,7 +57,7 @@ ERROR_VIEW_SORT_FIELD_ALREADY_EXISTS = (
ERROR_VIEW_SORT_FIELD_NOT_SUPPORTED = (
"ERROR_VIEW_SORT_FIELD_NOT_SUPPORTED",
HTTP_400_BAD_REQUEST,
"The field does not support view sorting.",
"The field does not support view sorting on the given type.",
)
ERROR_VIEW_GROUP_BY_DOES_NOT_EXIST = (
"ERROR_VIEW_GROUP_BY_DOES_NOT_EXIST",

View file

@ -220,14 +220,14 @@ class UpdateViewFilterGroupSerializer(serializers.ModelSerializer):
class ViewSortSerializer(serializers.ModelSerializer):
class Meta:
model = ViewSort
fields = ("id", "view", "field", "order")
fields = ("id", "view", "field", "order", "type")
extra_kwargs = {"id": {"read_only": True}}
class CreateViewSortSerializer(serializers.ModelSerializer):
class Meta:
model = ViewSort
fields = ("field", "order")
fields = ("field", "order", "type")
extra_kwargs = {
"order": {"default": ViewSort._meta.get_field("order").default},
}
@ -236,11 +236,12 @@ class CreateViewSortSerializer(serializers.ModelSerializer):
class UpdateViewSortSerializer(serializers.ModelSerializer):
class Meta(CreateViewFilterSerializer.Meta):
model = ViewSort
fields = ("field", "order")
fields = ("field", "order", "type")
extra_kwargs = {
"field": {"required": False},
"order": {"required": False},
"width": {"required": False},
"type": {"required": False},
}
@ -253,6 +254,7 @@ class ViewGroupBySerializer(serializers.ModelSerializer):
"field",
"order",
"width",
"type",
)
extra_kwargs = {"id": {"read_only": True}}
@ -264,10 +266,12 @@ class CreateViewGroupBySerializer(serializers.ModelSerializer):
"field",
"order",
"width",
"type",
)
extra_kwargs = {
"order": {"default": ViewGroupBy._meta.get_field("order").default},
"width": {"default": ViewGroupBy._meta.get_field("width").default},
"type": {"default": ViewGroupBy._meta.get_field("type").default},
}
@ -278,11 +282,13 @@ class UpdateViewGroupBySerializer(serializers.ModelSerializer):
"field",
"order",
"width",
"type",
)
extra_kwargs = {
"field": {"required": False},
"order": {"required": False},
"width": {"required": False},
"type": {"required": False},
}
@ -540,7 +546,7 @@ class PublicViewSortSerializer(serializers.ModelSerializer):
class Meta:
model = ViewSort
fields = ("id", "view", "field", "order")
fields = ("id", "view", "field", "order", "type")
extra_kwargs = {"id": {"read_only": True}}
@ -555,6 +561,7 @@ class PublicViewGroupBySerializer(serializers.ModelSerializer):
"field",
"order",
"width",
"type",
)
extra_kwargs = {"id": {"read_only": True}}

View file

@ -1540,7 +1540,11 @@ class ViewSortingsView(APIView):
field = FieldHandler().get_field(data["field"])
view_sort = action_type_registry.get_by_type(CreateViewSortActionType).do(
request.user, view, field, data["order"]
request.user,
view,
field,
data["order"],
data.get("type"),
)
serializer = ViewSortSerializer(view_sort)
@ -1645,6 +1649,7 @@ class ViewSortView(APIView):
view_sort,
data.get("field"),
data.get("order"),
data.get("type"),
)
serializer = ViewSortSerializer(view_sort)
@ -2219,7 +2224,7 @@ class ViewGroupBysView(APIView):
view_group_by = action_type_registry.get_by_type(
CreateViewGroupByActionType
).do(request.user, view, field, data["order"], data["width"])
).do(request.user, view, field, data["order"], data["width"], data.get("type"))
serializer = ViewGroupBySerializer(view_group_by)
return Response(serializer.data)
@ -2326,6 +2331,7 @@ class ViewGroupByView(APIView):
data.get("field"),
data.get("order"),
data.get("width"),
data.get("type"),
)
serializer = ViewGroupBySerializer(view_group_by)

View file

@ -30,6 +30,7 @@ BASEROW_BOOLEAN_FIELD_FALSE_VALUES = [
"unchecked",
False
]
SINGLE_SELECT_SORT_BY_ORDER = "order"
class DeleteFieldStrategyEnum(Enum):

View file

@ -86,9 +86,17 @@ class OrderByFieldNotFound(Exception):
class OrderByFieldNotPossible(Exception):
"""Raised when it is not possible to order by a field."""
def __init__(self, field_name=None, field_type=None, *args, **kwargs):
def __init__(
self,
field_name: str = None,
field_type: str = None,
sort_type: str = None,
*args: list,
**kwargs: dict,
):
self.field_name = field_name
self.field_type = field_type
self.sort_type = sort_type
super().__init__(*args, **kwargs)

View file

@ -131,7 +131,11 @@ from baserow.contrib.database.types import SerializedRowHistoryFieldMetadata
from baserow.contrib.database.validators import UnicodeRegexValidator
from baserow.contrib.database.views.exceptions import ViewDoesNotExist, ViewNotInTable
from baserow.contrib.database.views.handler import ViewHandler
from baserow.contrib.database.views.models import OWNERSHIP_TYPE_COLLABORATIVE, View
from baserow.contrib.database.views.models import (
DEFAULT_SORT_TYPE_KEY,
OWNERSHIP_TYPE_COLLABORATIVE,
View,
)
from baserow.core.db import (
CombinedForeignKeyAndManyToManyMultipleFieldPrefetch,
collate_expression,
@ -152,6 +156,7 @@ from baserow.core.utils import list_to_comma_separated_string
from .constants import (
BASEROW_BOOLEAN_FIELD_FALSE_VALUES,
BASEROW_BOOLEAN_FIELD_TRUE_VALUES,
SINGLE_SELECT_SORT_BY_ORDER,
UPSERT_OPTION_DICT_KEY,
DeleteFieldStrategyEnum,
)
@ -259,7 +264,7 @@ if TYPE_CHECKING:
class CollationSortMixin:
def get_order(
self, field, field_name, order_direction, table_model=None
self, field, field_name, order_direction, sort_type, table_model=None
) -> OptionallyAnnotatedOrderBy:
field_expr = collate_expression(F(field_name))
@ -452,7 +457,7 @@ class LongTextFieldType(CollationSortMixin, FieldType):
allowed_fields = ["long_text_enable_rich_text"]
serializer_field_names = ["long_text_enable_rich_text"]
def check_can_group_by(self, field: Field) -> bool:
def check_can_group_by(self, field: Field, sort_type: str) -> bool:
return not field.long_text_enable_rich_text
def can_be_primary_field(self, field_or_values: Union[Field, dict]) -> bool:
@ -1616,7 +1621,7 @@ class LastModifiedByFieldType(ReadOnlyFieldType):
return user.email if user else None
def get_order(
self, field, field_name, order_direction, table_model=None
self, field, field_name, order_direction, sort_type, table_model=None
) -> OptionallyAnnotatedOrderBy:
"""
If the user wants to sort the results they expect them to be ordered
@ -1624,7 +1629,7 @@ class LastModifiedByFieldType(ReadOnlyFieldType):
"""
order = collate_expression(
self.get_sortable_column_expression(field, field_name)
self.get_sortable_column_expression(field, field_name, sort_type)
)
if order_direction == "ASC":
@ -1691,7 +1696,10 @@ class LastModifiedByFieldType(ReadOnlyFieldType):
)
def get_sortable_column_expression(
self, field: Field, field_name: str
self,
field: Field,
field_name: str,
sort_type: str,
) -> Expression | F:
return F(f"{field_name}__first_name")
@ -1830,7 +1838,7 @@ class CreatedByFieldType(ReadOnlyFieldType):
return user.email if user else None
def get_order(
self, field, field_name, order_direction, table_model=None
self, field, field_name, order_direction, sort_type, table_model=None
) -> OptionallyAnnotatedOrderBy:
"""
If the user wants to sort the results they expect them to be ordered
@ -1838,7 +1846,7 @@ class CreatedByFieldType(ReadOnlyFieldType):
"""
order = collate_expression(
self.get_sortable_column_expression(field, field_name)
self.get_sortable_column_expression(field, field_name, sort_type)
)
if order_direction == "ASC":
@ -1905,7 +1913,10 @@ class CreatedByFieldType(ReadOnlyFieldType):
)
def get_sortable_column_expression(
self, field: Field, field_name: str
self,
field: Field,
field_name: str,
sort_type: str,
) -> Expression | F:
return F(f"{field_name}__first_name")
@ -2067,7 +2078,10 @@ class DurationFieldType(FieldType):
setattr(row, field_name, value)
def get_sortable_column_expression(
self, field: Field, field_name: str
self,
field: Field,
field_name: str,
sort_type: str,
) -> Expression | F:
return F(f"{field_name}")
@ -2187,16 +2201,18 @@ class LinkRowFieldType(
return field.specific.link_row_table_primary_field
def _check_related_field_can_order_by(
self, related_primary_field: Type[Field]
self,
related_primary_field: Type[Field],
order_type: str,
) -> bool:
related_primary_field_type = field_type_registry.get_by_model(
related_primary_field.specific_class
)
return related_primary_field_type.check_can_order_by(
related_primary_field.specific
related_primary_field.specific, order_type
)
def check_can_group_by(self, field):
def check_can_group_by(self, field, sort_type):
related_primary_field = self._get_related_table_primary_field(field)
if related_primary_field is None:
return False
@ -2204,7 +2220,9 @@ class LinkRowFieldType(
related_primary_field_type = field_type_registry.get_by_model(
related_primary_field
)
return related_primary_field_type.check_can_group_by(related_primary_field)
return related_primary_field_type.check_can_group_by(
related_primary_field, sort_type
)
def _get_group_by_agg_expression(self, field_name: str) -> dict:
return ArrayAgg(
@ -2218,11 +2236,13 @@ class LinkRowFieldType(
distinct=True,
)
def check_can_order_by(self, field: Field) -> bool:
def check_can_order_by(self, field: Field, sort_type: str) -> bool:
related_primary_field = self._get_related_table_primary_field(field)
if related_primary_field is None:
return False
return self._check_related_field_can_order_by(related_primary_field.specific)
return self._check_related_field_can_order_by(
related_primary_field.specific, sort_type
)
def get_value_for_filter(self, row: "GeneratedTableModel", field):
related_primary_field = self._get_related_table_primary_field(
@ -2238,7 +2258,9 @@ class LinkRowFieldType(
row, related_primary_field
)
def get_order(self, field, field_name, order_direction, table_model=None):
def get_order(
self, field, field_name, order_direction, sort_type, table_model=None
):
related_primary_field = self._get_related_table_primary_field(
field, table_model
)
@ -2246,7 +2268,9 @@ class LinkRowFieldType(
raise ValueError("Cannot find the related primary field.")
related_primary_field = related_primary_field.specific
if not self._check_related_field_can_order_by(related_primary_field):
if not self._check_related_field_can_order_by(
related_primary_field, DEFAULT_SORT_TYPE_KEY
):
raise ValueError(
"The primary field for the related table cannot be ordered by."
)
@ -2257,6 +2281,7 @@ class LinkRowFieldType(
related_primary_field_type.get_sortable_column_expression(
related_primary_field,
f"{field_name}__{related_primary_field.db_column}",
sort_type,
)
)
@ -3493,7 +3518,7 @@ class FileFieldType(FieldType):
model_class = FileField
can_be_in_form_view = True
can_get_unique_values = False
_can_order_by = False
_can_order_by_types = []
def to_baserow_formula_type(self, field) -> BaserowFormulaType:
return BaserowFormulaArrayType(BaserowFormulaSingleFileType(nullable=True))
@ -3846,7 +3871,10 @@ class SelectOptionBaseFieldType(FieldType):
return queryset
def get_sortable_column_expression(
self, field: Field, field_name: str
self,
field: Field,
field_name: str,
sort_type: str,
) -> Expression | F:
return F(f"{field_name}__value")
@ -3868,6 +3896,7 @@ class SelectOptionBaseFieldType(FieldType):
class SingleSelectFieldType(CollationSortMixin, SelectOptionBaseFieldType):
type = "single_select"
model_class = SingleSelectField
_can_order_by_types = [DEFAULT_SORT_TYPE_KEY, SINGLE_SELECT_SORT_BY_ORDER]
def get_serializer_field(self, instance, **kwargs):
required = kwargs.get("required", False)
@ -4121,8 +4150,19 @@ class SingleSelectFieldType(CollationSortMixin, SelectOptionBaseFieldType):
connection, from_field, to_field
)
def get_sortable_column_expression(
self,
field: Field,
field_name: str,
sort_type: str,
) -> Expression | F:
if sort_type == SINGLE_SELECT_SORT_BY_ORDER:
return F(f"{field_name}__order")
else:
return super().get_sortable_column_expression(field, field_name, sort_type)
def get_order(
self, field, field_name, order_direction, table_model=None
self, field, field_name, order_direction, sort_type, table_model=None
) -> OptionallyAnnotatedOrderBy:
"""
If the user wants to sort the results they expect them to be ordered
@ -4131,10 +4171,15 @@ class SingleSelectFieldType(CollationSortMixin, SelectOptionBaseFieldType):
to the correct position.
"""
order = collate_expression(
self.get_sortable_column_expression(field, field_name)
column_expression = self.get_sortable_column_expression(
field, field_name, sort_type
)
if sort_type == SINGLE_SELECT_SORT_BY_ORDER:
order = column_expression
else:
order = collate_expression(column_expression)
if order_direction == "ASC":
order = order.asc(nulls_first=True)
else:
@ -4582,7 +4627,9 @@ class MultipleSelectFieldType(
q={f"select_option_value_{field_name}__iregex": rf"\m{value}\M"},
)
def get_order(self, field, field_name, order_direction, table_model=None):
def get_order(
self, field, field_name, order_direction, sort_type, table_model=None
):
"""
Order by the concatenated values of the select options, separated by a comma.
"""
@ -4595,7 +4642,7 @@ class MultipleSelectFieldType(
sort_column_name = f"{field_name}_agg_sort"
query = Coalesce(
StringAgg(
self.get_sortable_column_expression(field, field_name),
self.get_sortable_column_expression(field, field_name, sort_type),
",",
output_field=models.TextField(),
),
@ -5253,15 +5300,22 @@ class FormulaFieldType(FormulaFieldTypeArrayFilterSupport, ReadOnlyFieldType):
if apply_updates:
update_collector.apply_updates_and_get_updated_fields(field_cache)
def check_can_order_by(self, field):
def check_can_order_by(self, field, order_type):
# The formula types are not compatible with the order type. Therefore,
# if the `order_type` is not the default, it will always return False.
if order_type != DEFAULT_SORT_TYPE_KEY:
return False
return self.to_baserow_formula_type(field.specific).can_order_by
def check_can_group_by(self, field):
def check_can_group_by(self, field, sort_type):
# The formula types are not compatible with the order type. Therefore,
# if the `order_type` is not the default, it will always return False.
return self.to_baserow_formula_type(field.specific).can_group_by
def get_order(
self, field, field_name, order_direction, table_model=None
self, field, field_name, order_direction, sort_type, table_model=None
) -> OptionallyAnnotatedOrderBy:
# Ignore the `sort_type` because that is not yet supported in formulas.
return self.to_baserow_formula_type(field.specific).get_order(
field, field_name, order_direction, table_model=table_model
)
@ -6341,7 +6395,9 @@ class MultipleCollaboratorsFieldType(
def random_to_input_value(self, field, value):
return [{"id": user_id} for user_id in value]
def get_order(self, field, field_name, order_direction, table_model=None):
def get_order(
self, field, field_name, order_direction, sort_type, table_model=None
):
"""
If the user wants to sort the results they expect them to be ordered
alphabetically based on the user's name and not in the id which is
@ -6352,7 +6408,7 @@ class MultipleCollaboratorsFieldType(
sort_column_name = f"{field_name}_agg_sort"
query = Coalesce(
StringAgg(
self.get_sortable_column_expression(field, field_name),
self.get_sortable_column_expression(field, field_name, sort_type),
"",
output_field=models.TextField(),
),
@ -6377,7 +6433,10 @@ class MultipleCollaboratorsFieldType(
return value
def get_sortable_column_expression(
self, field: Field, field_name: str
self,
field: Field,
field_name: str,
sort_type: str,
) -> Expression | F:
return F(f"{field_name}__first_name")
@ -6734,7 +6793,7 @@ class PasswordFieldType(FieldType):
model_class = PasswordField
can_be_in_form_view = True
keep_data_on_duplication = True
_can_order_by = False
_can_order_by_types = []
_can_be_primary_field = False
can_get_unique_values = False

View file

@ -66,6 +66,7 @@ from baserow.core.registry import (
Registry,
)
from ..views.models import DEFAULT_SORT_TYPE_KEY
from .exceptions import (
FieldTypeAlreadyRegistered,
FieldTypeDoesNotExist,
@ -126,8 +127,11 @@ class FieldType(
field_type_registry.register(ExampleFieldType())
"""
_can_order_by = True
"""Indicates whether it is possible to order by this field type."""
_can_order_by_types = [DEFAULT_SORT_TYPE_KEY]
"""
Indicates by which types can be ordered. Leave empty if it's not possible to sort
by the field type.
"""
_can_be_primary_field = True
"""Some field types cannot be the primary field."""
@ -855,15 +859,16 @@ class FieldType(
field: Type[Field],
field_name: str,
order_direction: str,
sort_type: str,
table_model: Optional["GeneratedTableModel"] = None,
) -> OptionallyAnnotatedOrderBy:
"""
This hook can be called to generate a different order by expression.
By default the normal field sorting will be applied.
By default, the normal field sorting will be applied.
Optionally a different expression can be generated. This is for example used
by the single select field generates a mapping achieve the correct sorting
based on the select option value.
Additionally an annotation can be returned which will get applied to the
Additionally, an annotation can be returned which will get applied to the
queryset.
If you are implementing this method you should also implement the
get_value_for_filter method.
@ -871,13 +876,15 @@ class FieldType(
:param field: The related field object instance.
:param field_name: The name of the field.
:param order_direction: The sort order direction (either "ASC" or "DESC").
:param sort_type: The sort type that must be used, `default` is set as default
when the sort is created.
:param table_model: The table model instance that the field is part of,
if available.
:return: Either the expression that is added directly to the
model.objects.order(), an AnnotatedOrderBy class or None.
"""
field_expr = self.get_sortable_column_expression(field, field_name)
field_expr = self.get_sortable_column_expression(field, field_name, sort_type)
if order_direction == "ASC":
field_order_by = field_expr.asc(nulls_first=True)
@ -1602,36 +1609,38 @@ class FieldType(
return self._can_filter_by
def check_can_order_by(self, field: Field) -> bool:
def check_can_order_by(self, field: Field, sort_type: str) -> bool:
"""
Override this method if this field type can sometimes be ordered or sometimes
cannot be ordered depending on the individual field state. By default will just
return the bool property _can_order_by so if your field type doesn't depend
on the field state and is always just True or False just set _can_order_by
to the desired value.
cannot be ordered depending on the individual field state. By default, it will
check if the provided `sort_type` is in the `_can_order_by_types` property.
:param field: The field to check to see if it can be ordered by or not.
:param sort_type: The sort type to check if it's compatible.
:return: True if a view can be ordered by this field, False otherwise.
"""
return self._can_order_by
return sort_type in self._can_order_by_types
def check_can_group_by(self, field: Field) -> bool:
def check_can_group_by(self, field: Field, sort_type: str) -> bool:
"""
Override this method if this field type can sometimes be grouped or sometimes
cannot be grouped depending on the individual field state. By default will just
return the bool property _can_group_by so if your field type doesn't depend
on the field state and is always just True or False just set _can_group_by
to the desired value.
return the bool property _can_group_by and checks if the sort_type is in the
`_can_order_by_types` property.
:param field: The field to check to see if it can be grouped by or not.
:param sort_type: The sort type to check if it's compatible.
:return: True if a view can be grouped by this field, False otherwise.
"""
return self._can_group_by
return self._can_group_by and self.check_can_order_by(field, sort_type)
def get_sortable_column_expression(
self, field: Field, field_name: str
self,
field: Field,
field_name: str,
sort_type: str,
) -> Expression | F:
"""
Returns the expression that can be used to sort the field in the database.
@ -1640,6 +1649,8 @@ class FieldType(
:param field: The field where to get the sortable column expression for.
:param field_name: The name of the field in the table.
:param sort_type: The sort type that must be used, `default` is set as default
when the sort is created.
:return: The expression that can be used to sort the field in the database.
"""

View file

@ -0,0 +1,32 @@
# Generated by Django 5.0.9 on 2025-03-10 12:23
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("database", "0182_tablewebhookevent_views_viewrows_viewsubscription"),
]
operations = [
migrations.AddField(
model_name="viewgroupby",
name="type",
field=models.CharField(
db_default="default",
default="default",
help_text="Indicates the sort type. Will automatically fall back to `default` if incompatible with field type.",
max_length=32,
),
),
migrations.AddField(
model_name="viewsort",
name="type",
field=models.CharField(
db_default="default",
default="default",
help_text="Indicates the sort type. Will automatically fall back to `default` if incompatible with field type.",
max_length=32,
),
),
]

View file

@ -50,6 +50,7 @@ from baserow.contrib.database.table.constants import (
USER_TABLE_DATABASE_NAME_PREFIX,
)
from baserow.contrib.database.views.exceptions import ViewFilterTypeNotAllowedForField
from baserow.contrib.database.views.models import DEFAULT_SORT_TYPE_KEY
from baserow.contrib.database.views.registries import view_filter_type_registry
from baserow.core.db import MultiFieldPrefetchQuerysetMixin, specific_iterator
from baserow.core.fields import AutoTrueBooleanField
@ -332,6 +333,8 @@ class TableModelQuerySet(MultiFieldPrefetchQuerysetMixin, models.QuerySet):
raise OrderByFieldNotFound(order)
order_direction = "DESC" if order[:1] == "-" else "ASC"
type_match = re.search(r"\[(.*?)\]", order)
sort_type = type_match.group(1) if type_match else DEFAULT_SORT_TYPE_KEY
field_object = field_object_dict[field_name_or_id]
field_type = field_object["type"]
field_name = field_object["name"]
@ -339,15 +342,18 @@ class TableModelQuerySet(MultiFieldPrefetchQuerysetMixin, models.QuerySet):
user_field_name = field_object["field"].name
error_display_name = user_field_name if user_field_names else field_name
if not field_object["type"].check_can_order_by(field_object["field"]):
if not field_object["type"].check_can_order_by(
field_object["field"], sort_type
):
raise OrderByFieldNotPossible(
error_display_name,
field_type.type,
f"It is not possible to order by field type {field_type.type}.",
sort_type,
f"It is not possible to order by field type {field_type.type} using sort type {sort_type}.",
)
field_annotated_order_by = field_type.get_order(
field, field_name, order_direction, table_model=self.model
field, field_name, order_direction, sort_type, table_model=self.model
)
if field_annotated_order_by.annotation is not None:

View file

@ -755,6 +755,7 @@ class CreateViewSortActionType(UndoableActionType):
"database_id",
"view_sort_id",
"sort_order",
"sort_type",
]
@dataclasses.dataclass
@ -769,10 +770,16 @@ class CreateViewSortActionType(UndoableActionType):
database_name: str
view_sort_id: int
sort_order: str
sort_type: str
@classmethod
def do(
cls, user: AbstractUser, view: View, field: Field, sort_order: str
cls,
user: AbstractUser,
view: View,
field: Field,
sort_order: str,
sort_type: Optional[str] = None,
) -> ViewSort:
"""
Creates a new view sort.
@ -785,9 +792,13 @@ class CreateViewSortActionType(UndoableActionType):
:param field: The field that needs to be sorted.
:param sort_order: The desired order, can either be ascending (A to Z) or
descending (Z to A).
:param sort_type: The sort type that must be used, `default` is set as default
when the sort is created.
"""
view_sort = ViewHandler().create_sort(user, view, field, sort_order)
view_sort = ViewHandler().create_sort(
user, view, field, sort_order, sort_type=sort_type
)
params = cls.Params(
field.id,
@ -800,6 +811,7 @@ class CreateViewSortActionType(UndoableActionType):
view.table.database.name,
view_sort.id,
sort_order,
sort_type,
)
workspace = view.table.database.workspace
cls.register_action(user, params, cls.scope(view.id), workspace)
@ -822,7 +834,12 @@ class CreateViewSortActionType(UndoableActionType):
view = view_handler.get_view(params.view_id)
view_handler.create_sort(
user, view, field, params.sort_order, params.view_sort_id
user,
view,
field,
params.sort_order,
params.view_sort_id,
params.sort_type,
)
@ -840,8 +857,10 @@ class UpdateViewSortActionType(UndoableActionType):
"database_id",
"view_sort_id",
"sort_order",
"sort_type",
"original_field_id",
"original_sort_order",
"original_sort_type",
]
@dataclasses.dataclass
@ -856,9 +875,11 @@ class UpdateViewSortActionType(UndoableActionType):
database_name: str
view_sort_id: int
sort_order: str
sort_type: str
original_field_id: int
original_field_name: str
original_sort_order: str
original_sort_type: str
@classmethod
def do(
@ -867,6 +888,7 @@ class UpdateViewSortActionType(UndoableActionType):
view_sort: ViewSort,
field: Optional[Field] = None,
order: Optional[str] = None,
sort_type: Optional[str] = None,
) -> ViewSort:
"""
Updates the values of an existing view sort.
@ -878,6 +900,8 @@ class UpdateViewSortActionType(UndoableActionType):
:param view_sort: The view sort that needs to be updated.
:param field: The field that must be sorted on.
:param order: Indicates the sort order direction.
:param sort_type: The sort type that must be used, `default` is set as default
when the sort is created.
"""
original_field_id = view_sort.field.id
@ -885,9 +909,12 @@ class UpdateViewSortActionType(UndoableActionType):
view_id = view_sort.view.id
view_name = view_sort.view.name
original_sort_order = view_sort.order
original_sort_type = view_sort.type
handler = ViewHandler()
updated_view_sort = handler.update_sort(user, view_sort, field, order)
updated_view_sort = handler.update_sort(
user, view_sort, field, order, sort_type
)
cls.register_action(
user=user,
@ -902,9 +929,11 @@ class UpdateViewSortActionType(UndoableActionType):
updated_view_sort.view.table.database.name,
updated_view_sort.id,
updated_view_sort.order,
updated_view_sort.type,
original_field_id,
original_field_name,
original_sort_order,
original_sort_type,
),
scope=cls.scope(view_sort.view.id),
workspace=view_sort.view.table.database.workspace,
@ -923,7 +952,13 @@ class UpdateViewSortActionType(UndoableActionType):
view_handler = ViewHandler()
view_sort = view_handler.get_sort(user, params.view_sort_id)
view_handler.update_sort(user, view_sort, field, params.original_sort_order)
view_handler.update_sort(
user,
view_sort,
field,
params.original_sort_order,
params.original_sort_type,
)
@classmethod
def redo(cls, user: AbstractUser, params: Params, action_to_redo: Action):
@ -932,7 +967,9 @@ class UpdateViewSortActionType(UndoableActionType):
view_handler = ViewHandler()
view_sort = view_handler.get_sort(user, params.view_sort_id)
view_handler.update_sort(user, view_sort, field, params.sort_order)
view_handler.update_sort(
user, view_sort, field, params.sort_order, params.sort_type
)
class DeleteViewSortActionType(UndoableActionType):
@ -949,6 +986,7 @@ class DeleteViewSortActionType(UndoableActionType):
"database_id",
"view_sort_id",
"sort_order",
"sort_type",
]
@dataclasses.dataclass
@ -963,6 +1001,7 @@ class DeleteViewSortActionType(UndoableActionType):
database_name: str
view_sort_id: int
sort_order: str
sort_type: str
@classmethod
def do(cls, user: AbstractUser, view_sort: ViewSort):
@ -983,6 +1022,7 @@ class DeleteViewSortActionType(UndoableActionType):
field_id = view_sort.field.id
field_name = view_sort.field.name
sort_order = view_sort.order
sort_type = view_sort.type
ViewHandler().delete_sort(user, view_sort)
@ -997,6 +1037,7 @@ class DeleteViewSortActionType(UndoableActionType):
view_sort.view.table.database.name,
view_sort_id,
sort_order,
sort_type,
)
workspace = view_sort.view.table.database.workspace
cls.register_action(user, params, cls.scope(view_sort.view.id), workspace)
@ -1012,7 +1053,12 @@ class DeleteViewSortActionType(UndoableActionType):
field = FieldHandler().get_field(params.field_id)
view_handler.create_sort(
user, view, field, params.sort_order, params.view_sort_id
user,
view,
field,
params.sort_order,
params.view_sort_id,
params.sort_type,
)
@classmethod
@ -1935,6 +1981,7 @@ class CreateViewGroupByActionType(UndoableActionType):
view_group_by_id: int
group_by_order: str
group_by_width: int
group_by_type: str
@classmethod
def do(
@ -1944,6 +1991,7 @@ class CreateViewGroupByActionType(UndoableActionType):
field: Field,
group_by_order: str,
group_by_width: int,
group_by_type: str,
) -> ViewGroupBy:
"""
Creates a new view group_by.
@ -1957,10 +2005,11 @@ class CreateViewGroupByActionType(UndoableActionType):
:param group_by_order: The desired order, can either be ascending (A to Z) or
descending (Z to A).
:param group_by_width: The pixel width of the group by.
:param group_by_type: @TODO docs
"""
view_group_by = ViewHandler().create_group_by(
user, view, field, group_by_order, group_by_width
user, view, field, group_by_order, group_by_width, group_by_type
)
params = cls.Params(
@ -1975,6 +2024,7 @@ class CreateViewGroupByActionType(UndoableActionType):
view_group_by.id,
group_by_order,
group_by_width,
group_by_type,
)
workspace = view.table.database.workspace
cls.register_action(user, params, cls.scope(view.id), workspace)
@ -2002,6 +2052,7 @@ class CreateViewGroupByActionType(UndoableActionType):
field,
params.group_by_order,
params.group_by_width,
params.group_by_type,
params.view_group_by_id,
)
@ -2021,10 +2072,12 @@ class UpdateViewGroupByActionType(UndoableActionType):
"view_group_by_id",
"group_by_order",
"group_by_width",
"group_by_type",
"original_field_id",
"original_field_name",
"original_group_by_order",
"original_group_by_width",
"original_group_by_type",
]
@dataclasses.dataclass
@ -2040,10 +2093,12 @@ class UpdateViewGroupByActionType(UndoableActionType):
view_group_by_id: int
group_by_order: str
group_by_width: int
group_by_type: str
original_field_id: int
original_field_name: str
original_group_by_order: str
original_group_by_width: int
original_group_by_type: str
@classmethod
def do(
@ -2053,6 +2108,7 @@ class UpdateViewGroupByActionType(UndoableActionType):
field: Optional[Field] = None,
order: Optional[str] = None,
width: Optional[int] = None,
sort_type: Optional[str] = None,
) -> ViewGroupBy:
"""
Updates the values of an existing view group_by.
@ -2065,6 +2121,8 @@ class UpdateViewGroupByActionType(UndoableActionType):
:param field: The field that must be grouped on.
:param order: Indicates the group by order direction.
:param width: The visual pixel width of the group by.
:param sort_type: The sort type that must be used, `default` is set as default
when the sort is created.
"""
original_field_id = view_group_by.field.id
@ -2073,10 +2131,16 @@ class UpdateViewGroupByActionType(UndoableActionType):
view_name = view_group_by.view.name
original_group_by_order = view_group_by.order
original_group_by_width = view_group_by.width
original_group_by_type = view_group_by.type
handler = ViewHandler()
updated_view_group_by = handler.update_group_by(
user, view_group_by, field, order, width
user,
view_group_by,
field,
order,
width,
sort_type,
)
cls.register_action(
@ -2093,10 +2157,12 @@ class UpdateViewGroupByActionType(UndoableActionType):
updated_view_group_by.id,
updated_view_group_by.order,
updated_view_group_by.width,
updated_view_group_by.type,
original_field_id,
original_field_name,
original_group_by_order,
original_group_by_width,
original_group_by_type,
),
scope=cls.scope(view_group_by.view.id),
workspace=view_group_by.view.table.database.workspace,
@ -2121,6 +2187,7 @@ class UpdateViewGroupByActionType(UndoableActionType):
field,
params.original_group_by_order,
params.original_group_by_width,
params.original_group_by_type,
)
@classmethod
@ -2131,7 +2198,12 @@ class UpdateViewGroupByActionType(UndoableActionType):
view_group_by = view_handler.get_group_by(user, params.view_group_by_id)
view_handler.update_group_by(
user, view_group_by, field, params.group_by_order, params.group_by_width
user,
view_group_by,
field,
params.group_by_order,
params.group_by_width,
params.group_by_type,
)
@ -2165,6 +2237,7 @@ class DeleteViewGroupByActionType(UndoableActionType):
view_group_by_id: int
group_by_order: str
group_by_width: int
group_by_type: str
@classmethod
def do(cls, user: AbstractUser, view_group_by: ViewGroupBy):
@ -2186,6 +2259,7 @@ class DeleteViewGroupByActionType(UndoableActionType):
field_name = view_group_by.field.name
group_by_order = view_group_by.order
group_by_width = view_group_by.width
group_by_type = view_group_by.type
ViewHandler().delete_group_by(user, view_group_by)
@ -2201,6 +2275,7 @@ class DeleteViewGroupByActionType(UndoableActionType):
view_group_by_id,
group_by_order,
group_by_width,
group_by_type,
)
workspace = view_group_by.view.table.database.workspace
cls.register_action(user, params, cls.scope(view_group_by.view.id), workspace)
@ -2221,6 +2296,7 @@ class DeleteViewGroupByActionType(UndoableActionType):
field,
params.group_by_order,
params.group_by_width,
params.group_by_type,
params.view_group_by_id,
)

View file

@ -126,6 +126,7 @@ from .exceptions import (
ViewSortNotSupported,
)
from .models import (
DEFAULT_SORT_TYPE_KEY,
OWNERSHIP_TYPE_COLLABORATIVE,
View,
ViewDecoration,
@ -317,6 +318,7 @@ class ViewIndexingHandler(metaclass=baserow_trace_methods(tracer)):
field_object["field"],
field_object["name"],
view_sort_or_group_by.order,
view_sort_or_group_by.type,
table_model=model,
)
@ -1325,13 +1327,17 @@ class ViewHandler(metaclass=baserow_trace_methods(tracer)):
# `after_fields_changed_or_deleted` can be called in bulk and make it query
# efficient.
changed_fields = set()
all_fields_mapping = {field.id: field for field in fields}
# Fetch the sorts of all updated fields to check if the sort, including the
# type, is still compatible.
sorts_to_check = ViewSort.objects.filter(field_id__in=all_fields_mapping.keys())
fields_to_delete_sortings = [
f
for f in fields
all_fields_mapping[sort.field_id]
for sort in sorts_to_check
if not field_type_registry.get_by_model(
f.specific_class
).check_can_order_by(f)
all_fields_mapping[sort.field_id].specific_class
).check_can_order_by(all_fields_mapping[sort.field_id], sort.type)
]
# If it's a primary field, we also need to remove any sortings on the
@ -1352,12 +1358,17 @@ class ViewHandler(metaclass=baserow_trace_methods(tracer)):
if deleted_count > 0:
changed_fields.update(fields_to_delete_sortings)
# Fetch the group bys of all updated fields to check if the group by, including
# the type, is still compatible.
groups_to_check = ViewGroupBy.objects.filter(
field_id__in=all_fields_mapping.keys()
)
fields_to_delete_groupings = [
f
for f in fields
all_fields_mapping[sort.field_id]
for sort in groups_to_check
if not field_type_registry.get_by_model(
f.specific_class
).check_can_group_by(f)
all_fields_mapping[sort.field_id].specific_class
).check_can_group_by(all_fields_mapping[sort.field_id], sort.type)
]
if fields_to_delete_groupings:
deleted_count, _ = ViewGroupBy.objects.filter(
@ -1430,7 +1441,7 @@ class ViewHandler(metaclass=baserow_trace_methods(tracer)):
field_type = field_type_registry.get_by_model(field.specific_class)
# Check whether the updated field is still compatible with the group by.
# If not, it must be deleted.
if not field_type.check_can_group_by(field):
if not field_type.check_can_group_by(field, DEFAULT_SORT_TYPE_KEY):
ViewGroupBy.objects.filter(field=field).delete()
def get_filter_builder(
@ -1885,6 +1896,7 @@ class ViewHandler(metaclass=baserow_trace_methods(tracer)):
field,
field_name,
view_sort_or_group_by.order,
view_sort_or_group_by.type,
table_model=queryset.model,
)
field_annotation = field_annotated_order_by.annotation
@ -2016,6 +2028,7 @@ class ViewHandler(metaclass=baserow_trace_methods(tracer)):
field: Field,
order: str,
primary_key: Optional[int] = None,
sort_type: Optional[str] = None,
) -> ViewSort:
"""
Creates a new view sort.
@ -2026,6 +2039,8 @@ class ViewHandler(metaclass=baserow_trace_methods(tracer)):
:param order: The desired order, can either be ascending (A to Z) or
descending (Z to A).
:param primary_key: An optional primary key to give to the new view sort.
:param sort_type: The sort type that must be used, `default` is set as default
when the sort is created.
:raises ViewSortNotSupported: When the provided view does not support sorting.
:raises FieldNotInTable: When the provided field does not belong to the
provided view's table.
@ -2042,6 +2057,9 @@ class ViewHandler(metaclass=baserow_trace_methods(tracer)):
user, CreateViewSortOperationType.type, workspace=workspace, context=view
)
if not sort_type:
sort_type = DEFAULT_SORT_TYPE_KEY
# Check if view supports sorting.
view_type = view_type_registry.get_by_model(view.specific_class)
if not view_type.can_sort:
@ -2051,9 +2069,9 @@ class ViewHandler(metaclass=baserow_trace_methods(tracer)):
# Check if the field supports sorting.
field_type = field_type_registry.get_by_model(field.specific_class)
if not field_type.check_can_order_by(field):
if not field_type.check_can_order_by(field, sort_type):
raise ViewSortFieldNotSupported(
f"The field {field.pk} does not support sorting."
f"The field {field.pk} does not support sorting with type {sort_type}."
)
# Check if field belongs to the grid views table
@ -2069,7 +2087,11 @@ class ViewHandler(metaclass=baserow_trace_methods(tracer)):
)
view_sort = ViewSort.objects.create(
pk=primary_key, view=view, field=field, order=order
pk=primary_key,
view=view,
field=field,
order=order,
type=sort_type,
)
view_sort_created.send(self, view_sort=view_sort, user=user)
@ -2082,6 +2104,7 @@ class ViewHandler(metaclass=baserow_trace_methods(tracer)):
view_sort: ViewSort,
field: Optional[Field] = None,
order: Optional[str] = None,
sort_type: Optional[str] = None,
) -> ViewSort:
"""
Updates the values of an existing view sort.
@ -2103,6 +2126,7 @@ class ViewHandler(metaclass=baserow_trace_methods(tracer)):
workspace = view_sort.view.table.database.workspace
field = field if field is not None else view_sort.field
order = order if order is not None else view_sort.order
sort_type = sort_type if sort_type is not None else view_sort.type
CoreHandler().check_permissions(
user, ReadFieldOperationType.type, workspace=workspace, context=field
@ -2127,7 +2151,12 @@ class ViewHandler(metaclass=baserow_trace_methods(tracer)):
# If the field has changed we need to check if the new field type supports
# sorting.
field_type = field_type_registry.get_by_model(field.specific_class)
if field.id != view_sort.field_id and not field_type.check_can_order_by(field):
if (
field.id != view_sort.field_id or sort_type != view_sort.type
) and not field_type.check_can_order_by(
field,
sort_type,
):
raise ViewSortFieldNotSupported(
f"The field {field.pk} does not support sorting."
)
@ -2144,6 +2173,7 @@ class ViewHandler(metaclass=baserow_trace_methods(tracer)):
view_sort.field = field
view_sort.order = order
view_sort.type = sort_type
view_sort.save()
view_sort_updated.send(self, view_sort=view_sort, user=user)
@ -2246,6 +2276,7 @@ class ViewHandler(metaclass=baserow_trace_methods(tracer)):
field: Field,
order: str,
width: int,
sort_type: str = None,
primary_key: Optional[int] = None,
) -> ViewGroupBy:
"""
@ -2256,6 +2287,9 @@ class ViewHandler(metaclass=baserow_trace_methods(tracer)):
:param field: The field that needs to be grouped.
:param order: The desired order, can either be ascending (A to Z) or
descending (Z to A).
:param width: The visual width of the group column.
:param sort_type: The sort type that must be used, `default` is set as default
when the sort is created.
:param primary_key: An optional primary key to give to the new view group_by.
:raises ViewGroupByNotSupported: When the provided view does not support
grouping.
@ -2272,6 +2306,9 @@ class ViewHandler(metaclass=baserow_trace_methods(tracer)):
user, CreateViewGroupByOperationType.type, workspace=workspace, context=view
)
if not sort_type:
sort_type = DEFAULT_SORT_TYPE_KEY
# Check if view supports grouping.
view_type = view_type_registry.get_by_model(view.specific_class)
if not view_type.can_group_by:
@ -2281,9 +2318,9 @@ class ViewHandler(metaclass=baserow_trace_methods(tracer)):
# Check if the field supports grouping.
field_type = field_type_registry.get_by_model(field.specific_class)
if not field_type.check_can_group_by(field):
if not field_type.check_can_group_by(field, sort_type):
raise ViewGroupByFieldNotSupported(
f"The field {field.pk} does not support grouping."
f"The field {field.pk} does not support grouping with type {sort_type}."
)
# Check if field belongs to the grid views table
@ -2299,7 +2336,12 @@ class ViewHandler(metaclass=baserow_trace_methods(tracer)):
)
view_group_by = ViewGroupBy.objects.create(
pk=primary_key, view=view, field=field, order=order, width=width
pk=primary_key,
view=view,
field=field,
order=order,
width=width,
type=sort_type,
)
view_group_by_created.send(self, view_group_by=view_group_by, user=user)
@ -2313,6 +2355,7 @@ class ViewHandler(metaclass=baserow_trace_methods(tracer)):
field: Optional[Field] = None,
order: Optional[str] = None,
width: Optional[int] = None,
sort_type: Optional[str] = None,
) -> ViewGroupBy:
"""
Updates the values of an existing view group_by.
@ -2322,6 +2365,8 @@ class ViewHandler(metaclass=baserow_trace_methods(tracer)):
:param field: The field that must be grouped on.
:param order: Indicates the group by order direction.
:param width: The visual width of the group by.
:param sort_type: The sort type that must be used, `default` is set as default
when the sort is created.
:raises ViewGroupByDoesNotExist: When the view used by the filter is trashed.
:raises ViewGroupByFieldNotSupported: When the field does not support grouping.
:raises FieldNotInTable: When the provided field does not belong to the
@ -2338,6 +2383,7 @@ class ViewHandler(metaclass=baserow_trace_methods(tracer)):
field = field if field is not None else view_group_by.field
order = order if order is not None else view_group_by.order
width = width if width is not None else view_group_by.width
sort_type = sort_type if sort_type is not None else view_group_by.type
CoreHandler().check_permissions(
user, ReadFieldOperationType.type, workspace=workspace, context=field
@ -2362,8 +2408,11 @@ class ViewHandler(metaclass=baserow_trace_methods(tracer)):
# If the field has changed we need to check if the new field type supports
# grouping.
field_type = field_type_registry.get_by_model(field.specific_class)
if field.id != view_group_by.field_id and not field_type.check_can_order_by(
field
if (
field.id != view_group_by.field_id or sort_type != view_group_by.type
) and not field_type.check_can_order_by(
field,
sort_type,
):
raise ViewGroupByFieldNotSupported(
f"The field {field.pk} does not support grouping."
@ -2376,12 +2425,14 @@ class ViewHandler(metaclass=baserow_trace_methods(tracer)):
and view_group_by.view.viewgroupby_set.filter(field_id=field.pk).exists()
):
raise ViewGroupByFieldAlreadyExist(
f"A group by for the field {field.pk} already exists."
f"A group by for the field {field.pk} already exists with type "
f"{sort_type}."
)
view_group_by.field = field
view_group_by.order = order
view_group_by.width = width
view_group_by.type = sort_type
view_group_by.save()
view_group_by_updated.send(self, view_group_by=view_group_by, user=user)
@ -3535,7 +3586,7 @@ class ViewHandler(metaclass=baserow_trace_methods(tracer)):
field_name = field.db_column
field_type = field_type_registry.get_by_model(field.specific_class)
if not field_type.check_can_group_by(field):
if not field_type.check_can_group_by(field, DEFAULT_SORT_TYPE_KEY):
raise ValueError(f"Can't group by {field_name}.")
value = getattr(row, field_name)

View file

@ -55,6 +55,9 @@ VIEW_OWNERSHIP_TYPES = [OWNERSHIP_TYPE_COLLABORATIVE]
# Must be the same as `modules/database/constants.js`.
DEFAULT_FORM_VIEW_FIELD_COMPONENT_KEY = "default"
# Must be the same as `modules/database/constants.js`.
DEFAULT_SORT_TYPE_KEY = "default"
def get_default_view_content_type():
return ContentType.objects.get_for_model(View)
@ -503,6 +506,13 @@ class ViewSort(HierarchicalModelMixin, models.Model):
"and DESC (Descending) is from Z to A.",
default=SORT_ORDER_ASC,
)
type = models.CharField(
max_length=32,
default=DEFAULT_SORT_TYPE_KEY,
db_default=DEFAULT_SORT_TYPE_KEY,
help_text=f"Indicates the sort type. Will automatically fall back to `"
f"{DEFAULT_SORT_TYPE_KEY}` if incompatible with field type.",
)
def get_parent(self):
return self.view
@ -538,6 +548,13 @@ class ViewGroupBy(HierarchicalModelMixin, models.Model):
"and DESC (Descending) is from Z to A.",
default=SORT_ORDER_ASC,
)
type = models.CharField(
max_length=32,
default=DEFAULT_SORT_TYPE_KEY,
db_default=DEFAULT_SORT_TYPE_KEY,
help_text=f"Indicates the sort type. Will automatically fall back to `"
f"{DEFAULT_SORT_TYPE_KEY}` if incompatible with field type.",
)
width = models.PositiveIntegerField(
default=200,
help_text="The pixel width of the group by in the related view.",

View file

@ -61,6 +61,7 @@ from baserow.contrib.database.views.exceptions import (
AggregationTypeDoesNotExist,
ViewDoesNotExist,
)
from baserow.contrib.database.views.models import DEFAULT_SORT_TYPE_KEY
from baserow.contrib.database.views.service import ViewService
from baserow.contrib.database.views.view_aggregations import (
DistributionViewAggregationType,
@ -548,7 +549,7 @@ class LocalBaserowTableServiceType(LocalBaserowServiceType):
"searchable": field_type.is_searchable(field)
and field_type.type
not in self.unsupported_adhoc_searchable_field_types,
"sortable": field_type.check_can_order_by(field)
"sortable": field_type.check_can_order_by(field, DEFAULT_SORT_TYPE_KEY)
and field_type.type not in self.unsupported_adhoc_sortable_field_types,
"filterable": field_type.check_can_filter_by(field)
and field_type.type

View file

@ -340,7 +340,7 @@ def test_import_grid_view_sort_field_not_found():
def test_import_grid_view_sort_field_unsupported():
view_data = deepcopy(RAW_AIRTABLE_VIEW_DATA)
field_mapping = deepcopy(FIELD_MAPPING)
field_mapping["fldwSc9PqedIhTSqhi1"]["baserow_field_type"]._can_order_by = False
field_mapping["fldwSc9PqedIhTSqhi1"]["baserow_field_type"]._can_order_by_types = []
view_data["lastSortsApplied"] = RAW_VIEW_DATA_SORTS
airtable_view_type = airtable_view_type_registry.get("grid")

View file

@ -250,8 +250,8 @@ def test_list_rows(api_client, data_fixture):
)
number_field_type = field_type_registry.get("number")
old_can_order_by = number_field_type._can_order_by
number_field_type._can_order_by = False
old_can_order_by = number_field_type._can_order_by_types
number_field_type._can_order_by_types = []
invalidate_table_in_model_cache(table.id)
url = reverse("api:database:rows:list", kwargs={"table_id": table.id})
response = api_client.get(
@ -263,10 +263,10 @@ def test_list_rows(api_client, data_fixture):
response_json = response.json()
assert response_json["error"] == "ERROR_ORDER_BY_FIELD_NOT_POSSIBLE"
assert response_json["detail"] == (
f"It is not possible to order by field_{field_2.id} because the field type "
f"number does not support filtering."
f"It is not possible to order by field_{field_2.id} using sort type default "
f"because the field type number does not support it."
)
number_field_type._can_order_by = old_can_order_by
number_field_type._can_order_by_types = old_can_order_by
invalidate_table_in_model_cache(table.id)
url = reverse("api:database:rows:list", kwargs={"table_id": table.id})
@ -419,6 +419,74 @@ def test_list_rows(api_client, data_fixture):
assert response_json["error"] == "ERROR_USER_NOT_IN_GROUP"
@pytest.mark.django_db
def test_list_rows_order_by_type(api_client, data_fixture):
user, jwt_token = data_fixture.create_user_and_token(
email="test@test.nl", password="password", first_name="Test1"
)
table = data_fixture.create_database_table(user=user)
field_1 = data_fixture.create_text_field(name="Name", table=table, primary=True)
select_1 = data_fixture.create_single_select_field(name="Select", table=table)
option_1 = data_fixture.create_select_option(field=select_1, value="A", order=3)
option_2 = data_fixture.create_select_option(field=select_1, value="B", order=1)
option_3 = data_fixture.create_select_option(field=select_1, value="C", order=2)
model = table.get_model(attribute_names=True)
row_1 = model.objects.create(name="Product 1", select_id=option_1.id)
row_2 = model.objects.create(name="Product 2", select_id=option_2.id)
row_3 = model.objects.create(name="Product 3", select_id=option_3.id)
url = reverse("api:database:rows:list", kwargs={"table_id": table.id})
response = api_client.get(
f"{url}?order_by=-field_{field_1.id}[unknown]",
format="json",
HTTP_AUTHORIZATION=f"JWT {jwt_token}",
)
response_json = response.json()
assert response.status_code == HTTP_400_BAD_REQUEST
assert response_json["error"] == "ERROR_ORDER_BY_FIELD_NOT_POSSIBLE"
assert response_json["detail"] == (
f"It is not possible to order by field_{field_1.id} using sort type unknown "
f"because the field type text does not support it."
)
url = reverse("api:database:rows:list", kwargs={"table_id": table.id})
response = api_client.get(
f"{url}?order_by=field_{field_1.id}[default]",
format="json",
HTTP_AUTHORIZATION=f"JWT {jwt_token}",
)
response_json = response.json()
assert response.status_code == HTTP_200_OK
assert response_json["results"][0]["id"] == row_1.id
assert response_json["results"][1]["id"] == row_2.id
assert response_json["results"][2]["id"] == row_3.id
url = reverse("api:database:rows:list", kwargs={"table_id": table.id})
response = api_client.get(
f"{url}?order_by=-field_{field_1.id}[default]",
format="json",
HTTP_AUTHORIZATION=f"JWT {jwt_token}",
)
response_json = response.json()
assert response.status_code == HTTP_200_OK
assert response_json["results"][0]["id"] == row_3.id
assert response_json["results"][1]["id"] == row_2.id
assert response_json["results"][2]["id"] == row_1.id
url = reverse("api:database:rows:list", kwargs={"table_id": table.id})
response = api_client.get(
f"{url}?order_by=field_{select_1.id}[order]",
format="json",
HTTP_AUTHORIZATION=f"JWT {jwt_token}",
)
response_json = response.json()
assert response.status_code == HTTP_200_OK
assert response_json["results"][0]["id"] == row_2.id
assert response_json["results"][1]["id"] == row_3.id
assert response_json["results"][2]["id"] == row_1.id
@pytest.mark.django_db
def test_list_rows_adhoc_filtering_query_param_null_character(api_client, data_fixture):
user, token = data_fixture.create_user_and_token()
@ -2839,8 +2907,8 @@ def test_list_rows_with_attribute_names(api_client, data_fixture):
assert response.status_code == HTTP_400_BAD_REQUEST
assert (
response_json["detail"]
== "It is not possible to order by Password because the field type "
"password does not support filtering."
== "It is not possible to order by Password using sort type default because "
"the field type password does not support it."
)
url = reverse("api:database:rows:list", kwargs={"table_id": table.id})

View file

@ -829,6 +829,7 @@ def test_get_public_gallery_view(api_client, data_fixture):
"field": visible_sort.field.id,
"id": visible_sort.id,
"order": "DESC",
"type": "default",
"view": gallery_view.slug,
}
],

View file

@ -3289,6 +3289,7 @@ def test_get_public_grid_view(api_client, data_fixture):
"field": visible_sort.field.id,
"id": visible_sort.id,
"order": "DESC",
"type": "default",
"view": grid_view.slug,
}
],
@ -3300,6 +3301,7 @@ def test_get_public_grid_view(api_client, data_fixture):
"order": "DESC",
"view": grid_view.slug,
"width": 200,
"type": "default",
}
],
"table": {
@ -3926,6 +3928,46 @@ def test_list_rows_public_with_query_param_group_by_and_empty_order_by(
}
@pytest.mark.django_db
def test_list_rows_public_with_query_param_group_by_and_type(api_client, data_fixture):
user, token = data_fixture.create_user_and_token()
table = data_fixture.create_database_table(user=user)
field_1 = data_fixture.create_text_field(name="Name", table=table, primary=True)
select_1 = data_fixture.create_single_select_field(name="Select", table=table)
option_1 = data_fixture.create_select_option(field=select_1, value="A", order=3)
option_2 = data_fixture.create_select_option(field=select_1, value="B", order=1)
option_3 = data_fixture.create_select_option(field=select_1, value="C", order=2)
grid_view = data_fixture.create_grid_view(
table=table, user=user, public=True, create_options=False
)
data_fixture.create_grid_view_field_option(grid_view, field_1, hidden=False)
data_fixture.create_grid_view_field_option(grid_view, select_1, hidden=False)
model = table.get_model(attribute_names=True)
row_1 = model.objects.create(name="Product 1", select_id=option_1.id)
row_2 = model.objects.create(name="Product 2", select_id=option_2.id)
row_3 = model.objects.create(name="Product 3", select_id=option_3.id)
url = reverse(
"api:database:views:grid:public_rows", kwargs={"slug": grid_view.slug}
)
response = api_client.get(
f"{url}?group_by=field_{select_1.id}[unknown]",
)
response_json = response.json()
assert response.status_code == HTTP_400_BAD_REQUEST
assert response_json["error"] == "ERROR_ORDER_BY_FIELD_NOT_POSSIBLE"
response = api_client.get(
f"{url}?group_by=field_{select_1.id}[order]",
)
response_json = response.json()
assert response.status_code == HTTP_200_OK
assert response_json["results"][0]["id"] == row_2.id
assert response_json["results"][1]["id"] == row_3.id
assert response_json["results"][2]["id"] == row_1.id
@pytest.mark.django_db
def test_list_rows_public_filters_by_visible_and_hidden_columns(
api_client, data_fixture

View file

@ -189,6 +189,7 @@ def test_create_view_group_by(api_client, data_fixture):
response_json = response.json()
assert response.status_code == HTTP_200_OK
assert response_json["order"] == "DESC"
assert response_json["type"] == "default"
response = api_client.post(
reverse("api:database:views:list_group_bys", kwargs={"view_id": view_1.id}),
@ -206,6 +207,36 @@ def test_create_view_group_by(api_client, data_fixture):
assert ViewGroupBy.objects.all().count() == 3
@pytest.mark.django_db
def test_create_view_group_by_with_type(api_client, data_fixture):
user, token = data_fixture.create_user_and_token()
table_1 = data_fixture.create_database_table(user=user)
field_1 = data_fixture.create_text_field(table=table_1)
select_1 = data_fixture.create_single_select_field(table=table_1)
view_1 = data_fixture.create_grid_view(table=table_1)
response = api_client.post(
reverse("api:database:views:list_group_bys", kwargs={"view_id": view_1.id}),
{"field": field_1.id, "order": "ASC", "type": "unknown"},
format="json",
HTTP_AUTHORIZATION=f"JWT {token}",
)
response_json = response.json()
assert response.status_code == HTTP_400_BAD_REQUEST
assert response_json["error"] == "ERROR_VIEW_GROUP_BY_FIELD_NOT_SUPPORTED"
response = api_client.post(
reverse("api:database:views:list_group_bys", kwargs={"view_id": view_1.id}),
{"field": select_1.id, "order": "DESC", "type": "order"},
format="json",
HTTP_AUTHORIZATION=f"JWT {token}",
)
response_json = response.json()
assert response.status_code == HTTP_200_OK
assert response_json["order"] == "DESC"
assert response_json["type"] == "order"
@pytest.mark.django_db
def test_cannot_created_group_by(api_client, data_fixture):
user, token = data_fixture.create_user_and_token()
@ -283,6 +314,7 @@ def test_get_view_group_by(api_client, data_fixture):
assert response_json["view"] == first.view_id
assert response_json["field"] == first.field_id
assert response_json["order"] == "DESC"
assert response_json["type"] == "default"
response = api_client.delete(
reverse(
@ -432,6 +464,7 @@ def test_update_view_group_by(api_client, data_fixture):
assert response_json["view"] == first.view_id
assert response_json["field"] == field_1.id
assert response_json["order"] == "DESC"
assert response_json["type"] == "default"
response = api_client.patch(
reverse(
@ -452,6 +485,7 @@ def test_update_view_group_by(api_client, data_fixture):
assert response_json["field"] == field_1.id
assert response_json["order"] == "DESC"
assert response_json["width"] == 120
assert response_json["type"] == "default"
response = api_client.patch(
reverse(
@ -472,6 +506,69 @@ def test_update_view_group_by(api_client, data_fixture):
assert response_json["field"] == field_1.id
assert response_json["order"] == "DESC"
assert response_json["width"] == 120
assert response_json["type"] == "default"
@pytest.mark.django_db
def test_update_view_group_by_with_type(api_client, data_fixture):
user, token = data_fixture.create_user_and_token()
table = data_fixture.create_database_table(user=user)
field_1 = data_fixture.create_text_field(table=table)
select_2 = data_fixture.create_single_select_field(table=table)
group_by_1 = data_fixture.create_view_group_by(
user=user, order="DESC", field=field_1
)
group_by_2 = data_fixture.create_view_group_by(
user=user, order="DESC", field=select_2
)
response = api_client.patch(
reverse(
"api:database:views:group_by_item",
kwargs={"view_group_by_id": group_by_1.id},
),
{"field": field_1.id, "type": "unknown"},
format="json",
HTTP_AUTHORIZATION=f"JWT {token}",
)
response_json = response.json()
assert response.status_code == HTTP_400_BAD_REQUEST
assert response_json["error"] == "ERROR_VIEW_GROUP_BY_FIELD_NOT_SUPPORTED"
response = api_client.patch(
reverse(
"api:database:views:group_by_item",
kwargs={"view_group_by_id": group_by_2.id},
),
{
"field": select_2.id,
"order": "ASC",
"type": "order",
},
format="json",
HTTP_AUTHORIZATION=f"JWT {token}",
)
response_json = response.json()
assert response.status_code == HTTP_200_OK
assert response_json["order"] == "ASC"
assert response_json["type"] == "order"
response = api_client.patch(
reverse(
"api:database:views:group_by_item",
kwargs={"view_group_by_id": group_by_2.id},
),
{
"field": select_2.id,
"order": "DESC",
},
format="json",
HTTP_AUTHORIZATION=f"JWT {token}",
)
response_json = response.json()
assert response.status_code == HTTP_200_OK
assert response_json["order"] == "DESC"
assert response_json["type"] == "order"
@pytest.mark.django_db

View file

@ -5,6 +5,7 @@ from baserow.contrib.database.api.views.serializers import serialize_group_by_me
from baserow.contrib.database.fields.models import Field
from baserow.contrib.database.fields.registries import field_type_registry
from baserow.contrib.database.views.handler import ViewHandler
from baserow.contrib.database.views.models import DEFAULT_SORT_TYPE_KEY
from baserow.test_utils.helpers import setup_interesting_test_table
@ -75,7 +76,9 @@ def test_serialize_group_by_metadata_on_all_fields_in_interesting_table(data_fix
fields_to_group_by = [
field
for field in all_fields
if field_type_registry.get_by_model(field).check_can_group_by(field)
if field_type_registry.get_by_model(field).check_can_group_by(
field, DEFAULT_SORT_TYPE_KEY
)
]
single_select_options = Field.objects.get(name="single_select").select_options.all()

View file

@ -52,6 +52,7 @@ def test_list_view_sortings(api_client, data_fixture):
assert response_json[0]["view"] == view_1.id
assert response_json[0]["field"] == field_1.id
assert response_json[0]["order"] == sort_1.order
assert response_json[0]["type"] == sort_1.type
assert response_json[1]["id"] == sort_2.id
response = api_client.delete(
@ -187,6 +188,7 @@ def test_create_view_sort(api_client, data_fixture):
response_json = response.json()
assert response.status_code == HTTP_200_OK
assert response_json["order"] == "DESC"
assert response_json["type"] == "default"
response = api_client.post(
reverse("api:database:views:list_sortings", kwargs={"view_id": view_1.id}),
@ -199,10 +201,41 @@ def test_create_view_sort(api_client, data_fixture):
response_json = response.json()
assert response.status_code == HTTP_200_OK
assert response_json["order"] == "ASC"
assert response_json["type"] == "default"
assert ViewSort.objects.all().count() == 3
@pytest.mark.django_db
def test_create_view_sort_with_type(api_client, data_fixture):
user, token = data_fixture.create_user_and_token()
table_1 = data_fixture.create_database_table(user=user)
field_1 = data_fixture.create_text_field(table=table_1)
select_1 = data_fixture.create_single_select_field(table=table_1)
view_1 = data_fixture.create_grid_view(table=table_1)
response = api_client.post(
reverse("api:database:views:list_sortings", kwargs={"view_id": view_1.id}),
{"field": field_1.id, "order": "ASC", "type": "unknown"},
format="json",
HTTP_AUTHORIZATION=f"JWT {token}",
)
response_json = response.json()
assert response.status_code == HTTP_400_BAD_REQUEST
assert response_json["error"] == "ERROR_VIEW_SORT_FIELD_NOT_SUPPORTED"
response = api_client.post(
reverse("api:database:views:list_sortings", kwargs={"view_id": view_1.id}),
{"field": select_1.id, "order": "DESC", "type": "order"},
format="json",
HTTP_AUTHORIZATION=f"JWT {token}",
)
response_json = response.json()
assert response.status_code == HTTP_200_OK
assert response_json["order"] == "DESC"
assert response_json["type"] == "order"
@pytest.mark.django_db
def test_get_view_sort(api_client, data_fixture):
user, token = data_fixture.create_user_and_token()
@ -235,6 +268,7 @@ def test_get_view_sort(api_client, data_fixture):
assert response_json["view"] == first.view_id
assert response_json["field"] == first.field_id
assert response_json["order"] == "DESC"
assert response_json["type"] == "default"
response = api_client.delete(
reverse(
@ -345,6 +379,7 @@ def test_update_view_sort(api_client, data_fixture):
assert response_json["view"] == first.view_id
assert response_json["field"] == field_1.id
assert response_json["order"] == "ASC"
assert response_json["type"] == "default"
response = api_client.patch(
reverse("api:database:views:sort_item", kwargs={"view_sort_id": sort_1.id}),
@ -361,6 +396,7 @@ def test_update_view_sort(api_client, data_fixture):
assert response_json["view"] == first.view_id
assert response_json["field"] == field_1.id
assert response_json["order"] == "DESC"
assert response_json["type"] == "default"
response = api_client.patch(
reverse("api:database:views:sort_item", kwargs={"view_sort_id": sort_1.id}),
@ -379,6 +415,56 @@ def test_update_view_sort(api_client, data_fixture):
assert response_json["order"] == "DESC"
@pytest.mark.django_db
def test_update_view_sort_with_type(api_client, data_fixture):
user, token = data_fixture.create_user_and_token()
table = data_fixture.create_database_table(user=user)
field_1 = data_fixture.create_text_field(table=table)
select_2 = data_fixture.create_single_select_field(table=table)
sort_1 = data_fixture.create_view_sort(user=user, order="DESC", field=field_1)
sort_2 = data_fixture.create_view_sort(user=user, order="DESC", field=select_2)
response = api_client.patch(
reverse("api:database:views:sort_item", kwargs={"view_sort_id": sort_1.id}),
{"field": field_1.id, "type": "unknown"},
format="json",
HTTP_AUTHORIZATION=f"JWT {token}",
)
response_json = response.json()
assert response.status_code == HTTP_400_BAD_REQUEST
print(response_json)
assert response_json["error"] == "ERROR_VIEW_SORT_FIELD_NOT_SUPPORTED"
response = api_client.patch(
reverse("api:database:views:sort_item", kwargs={"view_sort_id": sort_2.id}),
{
"field": select_2.id,
"order": "ASC",
"type": "order",
},
format="json",
HTTP_AUTHORIZATION=f"JWT {token}",
)
response_json = response.json()
assert response.status_code == HTTP_200_OK
assert response_json["order"] == "ASC"
assert response_json["type"] == "order"
response = api_client.patch(
reverse("api:database:views:sort_item", kwargs={"view_sort_id": sort_2.id}),
{
"field": select_2.id,
"order": "DESC",
},
format="json",
HTTP_AUTHORIZATION=f"JWT {token}",
)
response_json = response.json()
assert response.status_code == HTTP_200_OK
assert response_json["order"] == "DESC"
assert response_json["type"] == "order"
@pytest.mark.django_db
def test_delete_view_sort(api_client, data_fixture):
user, token = data_fixture.create_user_and_token()
@ -449,6 +535,7 @@ def test_list_views_including_sortings(api_client, data_fixture):
assert response_json[0]["sortings"][0]["view"] == view_1.id
assert response_json[0]["sortings"][0]["field"] == field_1.id
assert response_json[0]["sortings"][0]["order"] == sort_1.order
assert response_json[0]["sortings"][0]["type"] == sort_1.type
assert response_json[0]["sortings"][1]["id"] == sort_2.id
assert len(response_json[1]["sortings"]) == 1
assert response_json[1]["sortings"][0]["id"] == sort_3.id

View file

@ -690,6 +690,67 @@ def test_order_by_fields_string_queryset_with_user_field_names(data_fixture):
assert results[4].id == rows[3].id
@pytest.mark.django_db
def test_order_by_fields_string_queryset_with_type(data_fixture):
table = data_fixture.create_database_table(name="Cars")
name_field = data_fixture.create_text_field(table=table, order=0, name="Name")
single_select_field = data_fixture.create_single_select_field(
table=table, name="Single"
)
option_a = data_fixture.create_select_option(
field=single_select_field, value="A", color="blue", order=2
)
option_b = data_fixture.create_select_option(
field=single_select_field, value="B", color="red", order=1
)
row_1, row_2 = RowHandler().force_create_rows(
user=None,
table=table,
rows_values=[
{
name_field.db_column: "BMW",
single_select_field.db_column: option_a.id,
},
{
name_field.db_column: "Audi",
single_select_field.db_column: option_b.id,
},
],
)
model = table.get_model()
with pytest.raises(OrderByFieldNotPossible):
model.objects.all().order_by_fields_string(
f"field_{single_select_field.id}[unknown]"
)
results = model.objects.all().order_by_fields_string(
f"field_{single_select_field.id}[default]"
)
assert results[0].id == row_1.id
assert results[1].id == row_2.id
results = model.objects.all().order_by_fields_string(
f"-field_{single_select_field.id}[default]"
)
assert results[0].id == row_2.id
assert results[1].id == row_1.id
results = model.objects.all().order_by_fields_string(
f"field_{single_select_field.id}[order]"
)
assert results[0].id == row_2.id
assert results[1].id == row_1.id
results = model.objects.all().order_by_fields_string(
f"-field_{single_select_field.id}[order]"
)
assert results[0].id == row_1.id
assert results[1].id == row_2.id
@pytest.mark.django_db
def test_filter_by_fields_object_queryset(data_fixture):
table = data_fixture.create_database_table(name="Cars")

View file

@ -25,7 +25,7 @@ def test_can_undo_creating_view_group_by(data_fixture):
assert ViewGroupBy.objects.count() == 0
view_group_by = action_type_registry.get_by_type(CreateViewGroupByActionType).do(
user, grid_view, number_field, "DESC", 250
user, grid_view, number_field, "DESC", 250, "default"
)
assert ViewGroupBy.objects.filter(pk=view_group_by.id).count() == 1
@ -53,7 +53,7 @@ def test_can_undo_redo_creating_view_group_by(data_fixture):
assert ViewGroupBy.objects.count() == 0
view_group_by = action_type_registry.get_by_type(CreateViewGroupByActionType).do(
user, grid_view, number_field, "DESC", 250
user, grid_view, number_field, "DESC", 250, "default"
)
original_view_group_by_id = view_group_by.id
@ -78,6 +78,36 @@ def test_can_undo_redo_creating_view_group_by(data_fixture):
assert updated_view_group_by.width == 250
@pytest.mark.django_db
@pytest.mark.undo_redo
def test_can_undo_redo_creating_view_group_by_with_type(data_fixture):
session_id = "1010"
user = data_fixture.create_user(session_id=session_id)
table = data_fixture.create_database_table(user=user)
grid_view = data_fixture.create_grid_view(table=table)
single_select_field = data_fixture.create_single_select_field(
table=table, name="value"
)
assert ViewGroupBy.objects.count() == 0
view_group_by = action_type_registry.get_by_type(CreateViewGroupByActionType).do(
user, grid_view, single_select_field, "DESC", 250, "order"
)
assert ViewGroupBy.objects.filter(pk=view_group_by.id).count() == 1
ActionHandler.undo(user, [ViewActionScopeType.value(grid_view.id)], session_id)
assert ViewGroupBy.objects.filter(pk=view_group_by.id).count() == 0
ActionHandler.redo(user, [ViewActionScopeType.value(grid_view.id)], session_id)
assert ViewGroupBy.objects.filter(pk=view_group_by.id).count() == 1
updated_view_group_by = ViewGroupBy.objects.first()
assert updated_view_group_by.type == "order"
@pytest.mark.django_db
@pytest.mark.undo_redo
def test_can_undo_updating_view_group_by(data_fixture):
@ -96,18 +126,20 @@ def test_can_undo_updating_view_group_by(data_fixture):
assert view_group_by.width == 250
action_type_registry.get_by_type(UpdateViewGroupByActionType).do(
user, view_group_by, order="DESC", width=300
user, view_group_by, order="DESC", width=300, sort_type="default"
)
view_group_by.refresh_from_db()
assert view_group_by.order == "DESC"
assert view_group_by.width == 300
assert view_group_by.type == "default"
ActionHandler.undo(user, [ViewActionScopeType.value(grid_view.id)], session_id)
view_group_by.refresh_from_db()
assert view_group_by.order == "ASC"
assert view_group_by.width == 250
assert view_group_by.type == "default"
@pytest.mark.django_db
@ -128,24 +160,57 @@ def test_can_undo_redo_updating_view_group_by(data_fixture):
assert view_group_by.width == 300
action_type_registry.get_by_type(UpdateViewGroupByActionType).do(
user, view_group_by, order="DESC", width=250
user, view_group_by, order="DESC", width=250, sort_type="default"
)
view_group_by.refresh_from_db()
assert view_group_by.order == "DESC"
assert view_group_by.width == 250
assert view_group_by.type == "default"
ActionHandler.undo(user, [ViewActionScopeType.value(grid_view.id)], session_id)
view_group_by.refresh_from_db()
assert view_group_by.order == "ASC"
assert view_group_by.width == 300
assert view_group_by.type == "default"
ActionHandler.redo(user, [ViewActionScopeType.value(grid_view.id)], session_id)
view_group_by.refresh_from_db()
assert view_group_by.order == "DESC"
assert view_group_by.width == 250
assert view_group_by.type == "default"
@pytest.mark.django_db
@pytest.mark.undo_redo
def test_can_undo_redo_updating_view_group_by_with_type(data_fixture):
session_id = "1010"
user = data_fixture.create_user(session_id=session_id)
table = data_fixture.create_database_table(user=user)
grid_view = data_fixture.create_grid_view(table=table)
single_select_field = data_fixture.create_single_select_field(table=table)
view_group_by = data_fixture.create_view_group_by(
view=grid_view, field=single_select_field, type="order"
)
action_type_registry.get_by_type(UpdateViewGroupByActionType).do(
user, view_group_by, sort_type="default"
)
view_group_by.refresh_from_db()
assert view_group_by.type == "default"
ActionHandler.undo(user, [ViewActionScopeType.value(grid_view.id)], session_id)
view_group_by.refresh_from_db()
assert view_group_by.type == "order"
ActionHandler.redo(user, [ViewActionScopeType.value(grid_view.id)], session_id)
view_group_by.refresh_from_db()
assert view_group_by.type == "default"
@pytest.mark.django_db
@ -215,3 +280,27 @@ def test_can_undo_redo_deleting_view_group_by(data_fixture):
ActionHandler.redo(user, [ViewActionScopeType.value(grid_view.id)], session_id)
assert ViewGroupBy.objects.count() == 0
@pytest.mark.django_db
@pytest.mark.undo_redo
def test_can_undo_redo_deleting_view_group_by_with_type(data_fixture):
session_id = "1010"
user = data_fixture.create_user(session_id=session_id)
table = data_fixture.create_database_table(user=user)
grid_view = data_fixture.create_grid_view(table=table)
field = data_fixture.create_single_select_field(table=table)
view_group_by = data_fixture.create_view_group_by(
view=grid_view, field=field, order="ASC", width=350, type="order"
)
action_type_registry.get_by_type(DeleteViewGroupByActionType).do(
user, view_group_by
)
assert ViewGroupBy.objects.count() == 0
ActionHandler.undo(user, [ViewActionScopeType.value(grid_view.id)], session_id)
group_by = ViewGroupBy.objects.all()
assert len(group_by) == 1
assert group_by[0].type == "order"

View file

@ -75,6 +75,35 @@ def test_can_undo_redo_creating_view_sort(data_fixture):
assert updated_view_sort.order == "DESC"
@pytest.mark.django_db
@pytest.mark.undo_redo
def test_can_undo_redo_creating_view_sort_with_type(data_fixture):
session_id = "1010"
user = data_fixture.create_user(session_id=session_id)
table = data_fixture.create_database_table(user=user)
grid_view = data_fixture.create_grid_view(table=table)
field = data_fixture.create_single_select_field(table=table, name="value")
assert ViewSort.objects.count() == 0
view_sort = action_type_registry.get_by_type(CreateViewSortActionType).do(
user, grid_view, field, "DESC", sort_type="order"
)
assert ViewSort.objects.filter(pk=view_sort.id).count() == 1
assert view_sort.type == "order"
ActionHandler.undo(user, [ViewActionScopeType.value(grid_view.id)], session_id)
assert ViewSort.objects.filter(pk=view_sort.id).count() == 0
ActionHandler.redo(user, [ViewActionScopeType.value(grid_view.id)], session_id)
assert ViewSort.objects.filter(pk=view_sort.id).count() == 1
updated_view_sort = ViewSort.objects.first()
assert updated_view_sort.type == "order"
@pytest.mark.django_db
@pytest.mark.undo_redo
def test_can_undo_updating_view_sort(data_fixture):
@ -138,6 +167,36 @@ def test_can_undo_redo_updating_view_sort(data_fixture):
assert view_sort.order == "DESC"
@pytest.mark.django_db
@pytest.mark.undo_redo
def test_can_undo_redo_updating_view_sort_type(data_fixture):
session_id = "1010"
user = data_fixture.create_user(session_id=session_id)
table = data_fixture.create_database_table(user=user)
grid_view = data_fixture.create_grid_view(table=table)
single_select_field = data_fixture.create_single_select_field(table=table)
view_sort = data_fixture.create_view_sort(
view=grid_view, field=single_select_field, order="ASC", type="order"
)
action_type_registry.get_by_type(UpdateViewSortActionType).do(
user, view_sort, sort_type="default"
)
view_sort.refresh_from_db()
assert view_sort.type == "default"
ActionHandler.undo(user, [ViewActionScopeType.value(grid_view.id)], session_id)
view_sort.refresh_from_db()
assert view_sort.type == "order"
ActionHandler.redo(user, [ViewActionScopeType.value(grid_view.id)], session_id)
view_sort.refresh_from_db()
assert view_sort.type == "default"
@pytest.mark.django_db
@pytest.mark.undo_redo
def test_can_undo_deleting_view_sort(data_fixture):
@ -198,3 +257,25 @@ def test_can_undo_redo_deleting_view_sort(data_fixture):
ActionHandler.redo(user, [ViewActionScopeType.value(grid_view.id)], session_id)
assert ViewSort.objects.count() == 0
@pytest.mark.django_db
@pytest.mark.undo_redo
def test_can_undo_redo_deleting_view_sort_with_type(data_fixture):
session_id = "1010"
user = data_fixture.create_user(session_id=session_id)
table = data_fixture.create_database_table(user=user)
grid_view = data_fixture.create_grid_view(table=table)
field = data_fixture.create_single_select_field(table=table)
view_sort = data_fixture.create_view_sort(
view=grid_view, field=field, order="ASC", type="order"
)
action_type_registry.get_by_type(DeleteViewSortActionType).do(user, view_sort)
assert ViewSort.objects.count() == 0
ActionHandler.undo(user, [ViewActionScopeType.value(grid_view.id)], session_id)
sorts = ViewSort.objects.all()
assert len(sorts) == 1
assert sorts[0].type == "order"

View file

@ -48,6 +48,7 @@ from baserow.contrib.database.views.handler import (
ViewIndexingHandler,
)
from baserow.contrib.database.views.models import (
DEFAULT_SORT_TYPE_KEY,
OWNERSHIP_TYPE_COLLABORATIVE,
FormView,
GridView,
@ -794,6 +795,27 @@ def test_field_type_changed(data_fixture):
assert ViewGroupBy.objects.all().count() == 0
@pytest.mark.django_db
def test_field_type_changed_unsupported_order_by_type(data_fixture):
user = data_fixture.create_user()
table = data_fixture.create_database_table(user=user)
field = data_fixture.create_single_select_field(table=table)
grid_view = data_fixture.create_grid_view(table=table)
data_fixture.create_view_sort(
view=grid_view, field=field, order="ASC", type="order"
)
data_fixture.create_view_group_by(
view=grid_view, field=field, order="ASC", type="order"
)
field_handler = FieldHandler()
long_text_field = field_handler.update_field(
user=user, field=field, new_type_name="text"
)
assert ViewSort.objects.all().count() == 0
assert ViewGroupBy.objects.all().count() == 0
@pytest.mark.django_db
def test_field_type_single_field_num_queries(data_fixture, django_assert_num_queries):
user = data_fixture.create_user()
@ -817,7 +839,7 @@ def test_field_type_single_field_num_queries(data_fixture, django_assert_num_que
handler = ViewHandler()
# Should be equal to the `test_field_type_changed_two_fields_num_queries`.
with django_assert_num_queries(9):
with django_assert_num_queries(11):
handler.fields_type_changed([password_field_1])
assert ViewFilter.objects.all().count() == 0
@ -864,7 +886,7 @@ def test_field_type_changed_two_fields_num_queries(
handler = ViewHandler()
# Should be equal to the `test_field_type_single_field_num_queries`.
with django_assert_num_queries(9):
with django_assert_num_queries(11):
handler.fields_type_changed([password_field_1, password_field_2])
assert ViewFilter.objects.all().count() == 0
@ -4093,7 +4115,9 @@ def test_get_group_by_on_all_fields_in_interesting_table(data_fixture):
fields_to_group_by = [
field
for field in all_fields
if field_type_registry.get_by_model(field).check_can_group_by(field)
if field_type_registry.get_by_model(field).check_can_group_by(
field, DEFAULT_SORT_TYPE_KEY
)
]
actual_result_per_field_name = {}

View file

@ -0,0 +1,8 @@
{
"type": "feature",
"message": "Introduced the ability to sort by single select option order.",
"domain": "database",
"issue_number": 786,
"bullet_points": [],
"created_at": "2025-03-10"
}

View file

@ -9,6 +9,7 @@ from baserow.contrib.database.api.fields.serializers import FieldSerializer
from baserow.contrib.database.fields.exceptions import FieldTypeDoesNotExist
from baserow.contrib.database.fields.registries import field_type_registry
from baserow.contrib.database.views.exceptions import AggregationTypeDoesNotExist
from baserow.contrib.database.views.models import DEFAULT_SORT_TYPE_KEY
from baserow.contrib.database.views.utils import AnnotatedAggregation
from baserow.contrib.integrations.local_baserow.integration_types import (
LocalBaserowIntegrationType,
@ -629,6 +630,9 @@ class LocalBaserowGroupedAggregateRowsUserServiceType(
field=field_obj["field"],
field_name=sort_by.reference,
order_direction=sort_by.direction,
# The application builder does not yet have compatibility with
# different sort types, so it uses the default one instead.
sort_type=DEFAULT_SORT_TYPE_KEY,
)
if field_annotated_order_by.annotation is not None:
sort_annotations = {

View file

@ -168,13 +168,13 @@ class AIFieldType(CollationSortMixin, SelectOptionBaseFieldType):
field_name, value, model_field, field
)
def check_can_order_by(self, field: Field) -> bool:
def check_can_order_by(self, field: Field, sort_type: str) -> bool:
baserow_field_type = self.get_baserow_field_type(field)
return baserow_field_type.check_can_order_by(field)
return baserow_field_type.check_can_order_by(field, sort_type)
def check_can_group_by(self, field: Field) -> bool:
def check_can_group_by(self, field: Field, sort_type: str) -> bool:
baserow_field_type = self.get_baserow_field_type(field)
return baserow_field_type.check_can_group_by(field)
return baserow_field_type.check_can_group_by(field, sort_type)
def get_search_expression(self, field: Field, queryset):
baserow_field_type = self.get_baserow_field_type(field)
@ -189,15 +189,22 @@ class AIFieldType(CollationSortMixin, SelectOptionBaseFieldType):
return baserow_field_type.enhance_queryset(queryset, field, name)
def get_sortable_column_expression(
self, field: Field, field_name: str
self,
field: Field,
field_name: str,
sort_type: str,
) -> Expression | F:
baserow_field_type = self.get_baserow_field_type(field)
return baserow_field_type.get_sortable_column_expression(field, field_name)
return baserow_field_type.get_sortable_column_expression(
field, field_name, sort_type
)
def get_order(self, field, field_name, order_direction, table_model=None):
def get_order(
self, field, field_name, order_direction, sort_type, table_model=None
):
baserow_field_type = self.get_baserow_field_type(field)
return baserow_field_type.get_order(
field, field_name, order_direction, table_model=table_model
field, field_name, order_direction, sort_type, table_model=table_model
)
def serialize_to_input_value(self, field, value):

View file

@ -1162,6 +1162,7 @@ def test_get_public_timeline_view(api_client, premium_data_fixture):
"field": visible_sort.field.id,
"id": visible_sort.id,
"order": "DESC",
"type": "default",
"view": timeline_view.slug,
}
],

View file

@ -25,6 +25,7 @@ import {
getRowMetadata,
} from '@baserow/modules/database/utils/row'
import { getDefaultSearchModeFromEnv } from '@baserow/modules/database/utils/search'
import { DEFAULT_SORT_TYPE_KEY } from '@baserow/modules/database/constants'
export function populateRow(row, metadata = {}) {
row._ = {
@ -581,7 +582,14 @@ export const actions = {
if (stack === undefined) {
return
}
const sortings = [{ field: dateFieldId, value: 'ASC', order: 'ASC' }]
const sortings = [
{
field: dateFieldId,
value: 'ASC',
order: 'ASC',
type: DEFAULT_SORT_TYPE_KEY,
},
]
const sortedRows = clone(stack.results)
sortedRows.push(row)
sortedRows.sort(getRowSortFunction(this.$registry, sortings, fields))
@ -726,7 +734,14 @@ export const actions = {
}
newStackResults.push(newRow)
newStackCount++
const sortings = [{ field: dateFieldId, value: 'ASC', order: 'ASC' }]
const sortings = [
{
field: dateFieldId,
value: 'ASC',
order: 'ASC',
type: DEFAULT_SORT_TYPE_KEY,
},
]
newStackResults.sort(getRowSortFunction(this.$registry, sortings, fields))
newIndex = newStackResults.findIndex((r) => r.id === newRow.id)
const newIsLast = newIndex === newStackResults.length - 1

View file

@ -102,6 +102,10 @@
display: flex;
}
.sortings__order-dropdown {
width: 170px;
}
.sortings__order-item {
justify-content: center;
flex-wrap: nowrap;

View file

@ -63,58 +63,15 @@
</DropdownItem>
</Dropdown>
</div>
<div
class="group-bys__order"
:class="{ 'group-bys__order--disabled': disableGroupBy }"
>
<a
class="group-bys__order-item"
:class="{ active: groupBy.order === 'ASC' }"
@click="updateGroupBy(groupBy, { order: 'ASC' })"
>
<template v-if="getGroupByIndicator(field, 0) === 'text'">{{
getGroupByIndicator(field, 1)
}}</template>
<i
v-if="getGroupByIndicator(field, 0) === 'icon'"
:class="getGroupByIndicator(field, 1)"
></i>
<i class="iconoir-arrow-right"></i>
<template v-if="getGroupByIndicator(field, 0) === 'text'">{{
getGroupByIndicator(field, 2)
}}</template>
<i
v-if="getGroupByIndicator(field, 0) === 'icon'"
class="fa"
:class="getGroupByIndicator(field, 2)"
></i>
</a>
<a
class="group-bys__order-item"
:class="{ active: groupBy.order === 'DESC' }"
@click="updateGroupBy(groupBy, { order: 'DESC' })"
>
<template v-if="getGroupByIndicator(field, 0) === 'text'">{{
getGroupByIndicator(field, 2)
}}</template>
<i
v-if="getGroupByIndicator(field, 0) === 'icon'"
:class="getGroupByIndicator(field, 2)"
></i>
<i class="iconoir-arrow-right"></i>
<template v-if="getGroupByIndicator(field, 0) === 'text'">{{
getGroupByIndicator(field, 1)
}}</template>
<i
v-if="getGroupByIndicator(field, 0) === 'icon'"
:class="getGroupByIndicator(field, 1)"
></i>
</a>
</div>
<ViewSortOrder
:disabled="disableGroupBy"
:sort-types="getSortTypes(field)"
:type="groupBy.type"
:order="groupBy.order"
@update-order="
updateGroupBy(groupBy, { order: $event.order, type: $event.type })
"
></ViewSortOrder>
</div>
</div>
<div
@ -152,9 +109,12 @@
<script>
import { notifyIf } from '@baserow/modules/core/utils/error'
import context from '@baserow/modules/core/mixins/context'
import { DEFAULT_SORT_TYPE_KEY } from '@baserow/modules/database/constants'
import ViewSortOrder from '@baserow/modules/database/components/view/ViewSortOrder.vue'
export default {
name: 'ViewGroupByContext',
components: { ViewSortOrder },
mixins: [context],
props: {
fields: {
@ -213,6 +173,7 @@ export default {
values: {
field: fieldId,
value: 'ASC',
type: DEFAULT_SORT_TYPE_KEY,
},
readOnly: this.readOnly,
})
@ -238,6 +199,18 @@ export default {
return
}
// If the field has changed, the type might not be compatible anymore. If so,
// then we're falling back on the default sort type.
if (values.field) {
const sortType = values.type || groupBy.type
const field = this.getField(values.field)
const fieldType = this.getFieldType(field)
const sortTypes = fieldType.getSortTypes(field, this.$registry)
if (!Object.prototype.hasOwnProperty.call(sortTypes, sortType)) {
values.type = DEFAULT_SORT_TYPE_KEY
}
}
try {
await this.$store.dispatch('view/updateGroupBy', {
groupBy,
@ -254,6 +227,9 @@ export default {
.get('field', field.type)
.getGroupByIndicator(field, this.$registry)[index]
},
getSortTypes(field) {
return this.getFieldType(field).getSortTypes(field, this.$registry)
},
},
}
</script>

View file

@ -60,57 +60,15 @@
></DropdownItem>
</Dropdown>
</div>
<div
class="sortings__order"
:class="{ 'sortings__order--disabled': disableSort }"
>
<a
class="sortings__order-item"
:class="{ active: sort.order === 'ASC' }"
@click="updateSort(sort, { order: 'ASC' })"
>
<template v-if="getSortIndicator(field, 0) === 'text'"
>{{ getSortIndicator(field, 1) }}
</template>
<i
v-if="getSortIndicator(field, 0) === 'icon'"
:class="getSortIndicator(field, 1)"
></i>
<i class="iconoir-arrow-right"></i>
<template v-if="getSortIndicator(field, 0) === 'text'"
>{{ getSortIndicator(field, 2) }}
</template>
<i
v-if="getSortIndicator(field, 0) === 'icon'"
:class="getSortIndicator(field, 2)"
></i>
</a>
<a
class="sortings__order-item"
:class="{ active: sort.order === 'DESC' }"
@click="updateSort(sort, { order: 'DESC' })"
>
<template v-if="getSortIndicator(field, 0) === 'text'"
>{{ getSortIndicator(field, 2) }}
</template>
<i
v-if="getSortIndicator(field, 0) === 'icon'"
:class="getSortIndicator(field, 2)"
></i>
<i class="iconoir-arrow-right"></i>
<template v-if="getSortIndicator(field, 0) === 'text'"
>{{ getSortIndicator(field, 1) }}
</template>
<i
v-if="getSortIndicator(field, 0) === 'icon'"
:class="getSortIndicator(field, 1)"
></i>
</a>
</div>
<ViewSortOrder
:disabled="disableSort"
:sort-types="getSortTypes(field)"
:type="sort.type"
:order="sort.order"
@update-order="
updateSort(sort, { order: $event.order, type: $event.type })
"
></ViewSortOrder>
</div>
</div>
<div
@ -148,9 +106,12 @@
<script>
import { notifyIf } from '@baserow/modules/core/utils/error'
import context from '@baserow/modules/core/mixins/context'
import ViewSortOrder from '@baserow/modules/database/components/view/ViewSortOrder'
import { DEFAULT_SORT_TYPE_KEY } from '@baserow/modules/database/constants'
export default {
name: 'ViewSortContext',
components: { ViewSortOrder },
mixins: [context],
props: {
fields: {
@ -206,6 +167,7 @@ export default {
values: {
field: fieldId,
value: 'ASC',
type: DEFAULT_SORT_TYPE_KEY,
},
readOnly: this.readOnly,
})
@ -231,6 +193,18 @@ export default {
return
}
// If the field has changed, the type might not be compatible anymore. If so,
// then we're falling back on the default sort type.
if (values.field) {
const sortType = values.type || sort.type
const field = this.getField(values.field)
const fieldType = this.getFieldType(field)
const sortTypes = fieldType.getSortTypes(field, this.$registry)
if (!Object.prototype.hasOwnProperty.call(sortTypes, sortType)) {
values.type = DEFAULT_SORT_TYPE_KEY
}
}
try {
await this.$store.dispatch('view/updateSort', {
sort,
@ -242,10 +216,8 @@ export default {
notifyIf(error, 'view')
}
},
getSortIndicator(field, index) {
return this.getFieldType(field).getSortIndicator(field, this.$registry)[
index
]
getSortTypes(field) {
return this.getFieldType(field).getSortTypes(field, this.$registry)
},
},
}

View file

@ -0,0 +1,109 @@
<template>
<div
class="sortings__order"
:class="{ 'sortings__order--disabled': disabled }"
>
<template v-if="Object.keys(sortTypes).length === 1">
<a
class="sortings__order-item"
:class="{ active: order === 'ASC' }"
@click="$emit('update-order', { order: 'ASC', type })"
>
<template v-if="sortIndicator[0] === 'text'"
>{{ sortIndicator[1] }}
</template>
<i v-if="sortIndicator[0] === 'icon'" :class="sortIndicator[1]"></i>
<i class="iconoir-arrow-right"></i>
<template v-if="sortIndicator[0] === 'text'"
>{{ sortIndicator[2] }}
</template>
<i v-if="sortIndicator[0] === 'icon'" :class="sortIndicator[2]"></i>
</a>
<a
class="sortings__order-item"
:class="{ active: order === 'DESC' }"
@click="$emit('update-order', { order: 'DESC', type })"
>
<template v-if="sortIndicator[0] === 'text'"
>{{ sortIndicator[2] }}
</template>
<i v-if="sortIndicator[0] === 'icon'" :class="sortIndicator[2]"></i>
<i class="iconoir-arrow-right"></i>
<template v-if="sortIndicator[0] === 'text'"
>{{ sortIndicator[1] }}
</template>
<i v-if="sortIndicator[0] === 'icon'" :class="sortIndicator[1]"></i>
</a>
</template>
<template v-else>
<Dropdown
:value="`${type}-${order}`"
:fixed-items="true"
class="sortings__order-dropdown"
@input="dropdownItemChange"
>
<DropdownItem
v-for="item in dropdownItems"
:key="item.value"
:name="item.name"
:value="item.value"
></DropdownItem>
</Dropdown>
</template>
</div>
</template>
<script>
export default {
name: 'ViewSortOrder',
props: {
disabled: {
type: Boolean,
required: false,
default: false,
},
sortTypes: {
type: Object,
required: true,
},
type: {
type: String,
required: true,
},
order: {
type: String,
required: true,
},
},
computed: {
sortIndicator() {
return this.sortTypes[this.type].indicator
},
dropdownItems() {
const items = []
Object.entries(this.sortTypes).forEach(([type, sortObject]) => {
const indicator = sortObject.indicator
items.push({
name: `${indicator[1]} -> ${indicator[2]}`,
value: `${type}-ASC`,
})
items.push({
name: `${indicator[2]} -> ${indicator[1]}`,
value: `${type}-DESC`,
})
})
return items
},
},
methods: {
dropdownItemChange(value) {
const [type, order] = value.split('-')
this.$emit('update-order', { order, type })
},
},
}
</script>

View file

@ -1,6 +1,9 @@
// Must be the same as `src/baserow/contrib/database/constants.py`.
// Must be the same as `src/baserow/contrib/database/views/models.py`.
export const DEFAULT_FORM_VIEW_FIELD_COMPONENT_KEY = 'default'
// Must be the same as `src/baserow/contrib/database/views/models.py`.
export const DEFAULT_SORT_TYPE_KEY = 'default'
export const GRID_VIEW_SIZE_TO_ROW_HEIGHT_MAPPING = {
small: 33,
medium: 55,

View file

@ -158,7 +158,10 @@ import FieldLookupSubForm from '@baserow/modules/database/components/field/Field
import FieldCountSubForm from '@baserow/modules/database/components/field/FieldCountSubForm'
import FieldRollupSubForm from '@baserow/modules/database/components/field/FieldRollupSubForm'
import RowEditFieldFormula from '@baserow/modules/database/components/row/RowEditFieldFormula'
import { DEFAULT_FORM_VIEW_FIELD_COMPONENT_KEY } from '@baserow/modules/database/constants'
import {
DEFAULT_FORM_VIEW_FIELD_COMPONENT_KEY,
DEFAULT_SORT_TYPE_KEY,
} from '@baserow/modules/database/constants'
import ViewService from '@baserow/modules/database/services/view'
import FormService from '@baserow/modules/database/services/view/form'
import { UploadFileUserFileUploadType } from '@baserow/modules/core/userFileUploadTypes'
@ -524,6 +527,25 @@ export class FieldType extends Registerable {
return ['text', 'A', 'Z']
}
/**
* Should return a mapping containing all the available sort types for this field
* type. It always returns the default type, which uses the `getSort` and
* `getSortIndicator` by default.
*/
getSortTypes(field, registry) {
return {
[DEFAULT_SORT_TYPE_KEY]: {
function: this.getSort,
indicator: this.getSortIndicator(field, registry),
},
}
}
/**
* This hook is called before the field's value is copied to the clipboard.
* Optionally formatting can be done here. By default the value is always
* converted to a string.
*/
/**
* This hook is called before the field's value is copied to the clipboard.
* Optionally formatting can be done here. By default the value is always
@ -3266,6 +3288,40 @@ export class SingleSelectFieldType extends SelectOptionBaseFieldType {
}
}
getSortByOptionOrder(name, order, field) {
const getOrder = function (row, name, field) {
let id = null
try {
id = row[name].id || null
} catch (e) {
return -1
}
return field.select_options.findIndex((o) => o.id === id) || -1
}
return (a, b) => {
const aOrder = getOrder(a, name, field)
const bOrder = getOrder(b, name, field)
if (order === 'ASC') {
return aOrder - bOrder
} else if (order === 'DESC') {
return bOrder - aOrder
}
}
}
getSortTypes(field, registry) {
const defaultTypes = super.getSortTypes()
defaultTypes.order = {
function: this.getSortByOptionOrder,
indicator: ['text', 'First', 'Last'],
}
return defaultTypes
}
parseFromLinkedRowItemValue(field, value) {
return value ? { value } : null
}

View file

@ -1353,8 +1353,8 @@ export const actions = {
* to the delete field.
*/
fieldUpdated({ dispatch, commit, getters }, { field, fieldType }) {
// Remove all filters are not compatible anymore.
getters.getAll.forEach((view) => {
// Remove all filters are not compatible anymore.
view.filters
.filter((filter) => filter.field === field.id)
.forEach((filter) => {
@ -1364,19 +1364,33 @@ export const actions = {
commit('DELETE_FILTER', { view, id: filter.id })
}
})
// Remove all sorts are not compatible anymore.
view.sortings
.filter((sort) => sort.field === field.id)
.forEach((sort) => {
const sortTypes = fieldType.getSortTypes(field, this.$registry)
const compatible =
fieldType.getCanSortInView(field) &&
Object.prototype.hasOwnProperty.call(sortTypes, sort.type)
if (!compatible) {
dispatch('deleteFieldSortings', { field })
}
})
// Remove all sorts are not compatible anymore.
view.group_bys
.filter((groupBy) => groupBy.field === field.id)
.forEach((groupBy) => {
const sortTypes = fieldType.getSortTypes(field, this.$registry)
const compatible =
fieldType.getCanSortInView(field) &&
Object.prototype.hasOwnProperty.call(sortTypes, groupBy.type)
if (!compatible) {
dispatch('deleteFieldGroupBys', { field })
}
})
})
// Remove all the field sortings because the new field does not support sortings
// at all.
if (!fieldType.getCanSortInView(field)) {
dispatch('deleteFieldSortings', { field })
}
// Remove all the field group bys because the new field does not support group bys
// at all.
if (!fieldType.getCanGroupByInView(field)) {
dispatch('deleteFieldGroupBys', { field })
}
},
/**
* Is called when a field is deleted. It will remove all filters and sortings

View file

@ -4,6 +4,7 @@ import { maxPossibleOrderValue } from '@baserow/modules/database/viewTypes'
import { escapeRegExp, isSecureURL } from '@baserow/modules/core/utils/string'
import { SearchModes } from '@baserow/modules/database/utils/search'
import { convertStringToMatchBackendTsvectorData } from '@baserow/modules/database/search/regexes'
import { DEFAULT_SORT_TYPE_KEY } from '@baserow/modules/database/constants'
export const DEFAULT_VIEW_ID_COOKIE_NAME = 'defaultViewId'
@ -20,7 +21,12 @@ export function getRowSortFunction($registry, sortings, fields, groupBys = []) {
if (field !== undefined) {
const fieldName = `field_${field.id}`
const fieldType = $registry.get('field', field.type)
const fieldSortFunction = fieldType.getSort(fieldName, sort.order, field)
const sortTypes = fieldType.getSortTypes(field, $registry)
const fieldSortFunction = sortTypes[sort.type].function(
fieldName,
sort.order,
field
)
sortFunction = sortFunction.thenBy(fieldSortFunction)
}
})
@ -457,7 +463,13 @@ export function getGroupBy(rootGetters, viewId) {
const view = rootGetters['view/get'](viewId)
return view.group_bys
.map((groupBy) => {
return `${groupBy.order === 'DESC' ? '-' : ''}field_${groupBy.field}`
let serialized = `${groupBy.order === 'DESC' ? '-' : ''}field_${
groupBy.field
}`
if (groupBy.type !== DEFAULT_SORT_TYPE_KEY) {
serialized += `[${groupBy.type}]`
}
return serialized
})
.join(',')
} else {
@ -517,7 +529,11 @@ export function canRowsBeOptimisticallyUpdatedInView(
export function getOrderBy(view, adhocSorting) {
if (adhocSorting) {
const serializeSort = (sort) => {
return `${sort.order === 'DESC' ? '-' : ''}field_${sort.field}`
let serialized = `${sort.order === 'DESC' ? '-' : ''}field_${sort.field}`
if (sort.type !== DEFAULT_SORT_TYPE_KEY) {
serialized += `[${sort.type}]`
}
return serialized
}
// Group bys first, then sorts to ensure that the order is correct.
const groupBys = view.group_bys ? view.group_bys.map(serializeSort) : []

View file

@ -1502,6 +1502,7 @@ describe('Buffered rows view store helper', () => {
view: 1,
field: 1,
order: 'ASC',
type: 'default',
},
],
}
@ -1669,6 +1670,7 @@ describe('Buffered rows view store helper', () => {
view: 1,
field: 1,
order: 'ASC',
type: 'default',
},
],
}