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 #786 See merge request baserow/baserow!3219
This commit is contained in:
commit
53a8c155e4
40 changed files with 1266 additions and 246 deletions
backend
src/baserow/contrib
database
airtable
api
fields
migrations
table
views
integrations/local_baserow
tests/baserow/contrib/database
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
|
@ -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(
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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}}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -30,6 +30,7 @@ BASEROW_BOOLEAN_FIELD_FALSE_VALUES = [
|
|||
"unchecked",
|
||||
False
|
||||
]
|
||||
SINGLE_SELECT_SORT_BY_ORDER = "order"
|
||||
|
||||
|
||||
class DeleteFieldStrategyEnum(Enum):
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
"""
|
||||
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
]
|
|
@ -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:
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
],
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 = {}
|
||||
|
|
|
@ -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"
|
||||
}
|
|
@ -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 = {
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
],
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -102,6 +102,10 @@
|
|||
display: flex;
|
||||
}
|
||||
|
||||
.sortings__order-dropdown {
|
||||
width: 170px;
|
||||
}
|
||||
|
||||
.sortings__order-item {
|
||||
justify-content: center;
|
||||
flex-wrap: nowrap;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
109
web-frontend/modules/database/components/view/ViewSortOrder.vue
Normal file
109
web-frontend/modules/database/components/view/ViewSortOrder.vue
Normal 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>
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) : []
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue