mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-04-17 18:32:35 +00:00
Resolve "A Field can end up with multiple field options"
This commit is contained in:
parent
9f46b24da2
commit
eec028a198
11 changed files with 85 additions and 38 deletions
backend/src/baserow/contrib/database
api/views
views
ws/views
premium/backend/src/baserow_premium
|
@ -137,7 +137,9 @@ class GalleryViewView(APIView):
|
|||
|
||||
if field_options:
|
||||
context = {"fields": [o["field"] for o in model._field_objects.values()]}
|
||||
serializer_class = view_type.get_field_options_serializer_class()
|
||||
serializer_class = view_type.get_field_options_serializer_class(
|
||||
create_if_missing=True
|
||||
)
|
||||
response.data.update(**serializer_class(view, context=context).data)
|
||||
|
||||
return response
|
||||
|
|
|
@ -186,7 +186,9 @@ class GridViewView(APIView):
|
|||
|
||||
if field_options:
|
||||
context = {"fields": [o["field"] for o in model._field_objects.values()]}
|
||||
serializer_class = view_type.get_field_options_serializer_class()
|
||||
serializer_class = view_type.get_field_options_serializer_class(
|
||||
create_if_missing=True
|
||||
)
|
||||
response.data.update(**serializer_class(view, context=context).data)
|
||||
|
||||
if row_metadata:
|
||||
|
|
|
@ -19,8 +19,9 @@ class FieldOptionsField(serializers.Field):
|
|||
"invalid_value": "Must be valid field options.",
|
||||
}
|
||||
|
||||
def __init__(self, serializer_class, **kwargs):
|
||||
def __init__(self, serializer_class, create_if_missing=True, **kwargs):
|
||||
self.serializer_class = serializer_class
|
||||
self.create_if_missing = create_if_missing
|
||||
self._spectacular_annotation = {
|
||||
"field": serializers.DictField(
|
||||
child=serializer_class(),
|
||||
|
@ -83,7 +84,9 @@ class FieldOptionsField(serializers.Field):
|
|||
fields = self.context.get("fields")
|
||||
return {
|
||||
field_options.field_id: self.serializer_class(field_options).data
|
||||
for field_options in value.get_field_options(True, fields)
|
||||
for field_options in value.get_field_options(
|
||||
self.create_if_missing, fields
|
||||
)
|
||||
}
|
||||
else:
|
||||
return value
|
||||
|
|
|
@ -985,7 +985,9 @@ class ViewFieldOptionsView(APIView):
|
|||
view_type = view_type_registry.get_by_model(view)
|
||||
|
||||
try:
|
||||
serializer_class = view_type.get_field_options_serializer_class()
|
||||
serializer_class = view_type.get_field_options_serializer_class(
|
||||
create_if_missing=True
|
||||
)
|
||||
except ValueError:
|
||||
raise ViewDoesNotSupportFieldOptions(
|
||||
"The view type does not have a `field_options_serializer_class`"
|
||||
|
@ -1034,7 +1036,9 @@ class ViewFieldOptionsView(APIView):
|
|||
handler = ViewHandler()
|
||||
view = handler.get_view(view_id).specific
|
||||
view_type = view_type_registry.get_by_model(view)
|
||||
serializer_class = view_type.get_field_options_serializer_class()
|
||||
serializer_class = view_type.get_field_options_serializer_class(
|
||||
create_if_missing=True
|
||||
)
|
||||
data = validate_data(serializer_class, request.data)
|
||||
|
||||
with view_type.map_api_exceptions():
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import secrets
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db import models
|
||||
from django.db import models, transaction
|
||||
|
||||
from baserow.core.utils import get_model_reference_field_name
|
||||
from baserow.core.models import UserFile
|
||||
|
@ -110,8 +110,8 @@ class View(
|
|||
them for a second time. This is only needed if `create_if_not_exists` is
|
||||
True.
|
||||
:type fields: list
|
||||
:return: A list of field options instances related to this grid view.
|
||||
:rtype: list or QuerySet
|
||||
:return: A queryset containing all the field options of view.
|
||||
:rtype: QuerySet
|
||||
"""
|
||||
|
||||
view_type = view_type_registry.get_by_model(self.specific_class)
|
||||
|
@ -130,20 +130,49 @@ class View(
|
|||
"any descendants."
|
||||
)
|
||||
|
||||
field_options = through_model.objects.filter(**{field_name: self})
|
||||
def get_queryset():
|
||||
return through_model.objects.filter(**{field_name: self})
|
||||
|
||||
field_options = get_queryset()
|
||||
|
||||
if create_if_not_exists:
|
||||
field_options = list(field_options)
|
||||
if not fields:
|
||||
fields = Field.objects.filter(table=self.table)
|
||||
fields_queryset = Field.objects.filter(table_id=self.table.id)
|
||||
|
||||
existing_field_ids = [options.field_id for options in field_options]
|
||||
for field in fields:
|
||||
if field.id not in existing_field_ids:
|
||||
field_option = through_model.objects.create(
|
||||
**{field_name: self, "field": field}
|
||||
if fields is None:
|
||||
field_count = fields_queryset.count()
|
||||
else:
|
||||
field_count = len(fields)
|
||||
|
||||
# The check there are missing field options must be as efficient as
|
||||
# possible because this is being done a lot.
|
||||
if len(field_options) < field_count:
|
||||
if fields is None:
|
||||
fields = fields_queryset
|
||||
|
||||
with transaction.atomic():
|
||||
# Lock the view so concurrent calls to this method wont create
|
||||
# duplicate field options.
|
||||
View.objects.filter(id=self.id).select_for_update().first()
|
||||
|
||||
# Invalidate the field options because they could have been
|
||||
# changed concurrently.
|
||||
field_options = get_queryset()
|
||||
|
||||
# In the case when field options are missing, we can be more
|
||||
# in-efficient because this rarely happens. The most important part
|
||||
# is that the check is fast.
|
||||
existing_field_ids = [options.field_id for options in field_options]
|
||||
through_model.objects.bulk_create(
|
||||
[
|
||||
through_model(**{field_name: self, "field": field})
|
||||
for field in fields
|
||||
if field.id not in existing_field_ids
|
||||
]
|
||||
)
|
||||
field_options.append(field_option)
|
||||
|
||||
# Invalidate the field options because new ones have been created and
|
||||
# we always want to return a queryset.
|
||||
field_options = get_queryset()
|
||||
|
||||
return field_options
|
||||
|
||||
|
|
|
@ -254,12 +254,15 @@ class ViewType(
|
|||
model = view.table.get_model()
|
||||
return model._field_objects.values(), model
|
||||
|
||||
def get_field_options_serializer_class(self):
|
||||
def get_field_options_serializer_class(self, create_if_missing):
|
||||
"""
|
||||
Generates a serializer that has the `field_options` property as a
|
||||
`FieldOptionsField`. This serializer can be used by the API to validate or list
|
||||
the field options.
|
||||
|
||||
:param create_if_missing: Whether or not to create any missing field options
|
||||
when looking them up during serialization.
|
||||
:type create_if_missing: bool
|
||||
:raises ValueError: When the related view type does not have a field options
|
||||
serializer class.
|
||||
:return: The generated serializer.
|
||||
|
@ -283,7 +286,8 @@ class ViewType(
|
|||
attrs = {
|
||||
"Meta": meta,
|
||||
"field_options": FieldOptionsField(
|
||||
serializer_class=self.field_options_serializer_class
|
||||
serializer_class=self.field_options_serializer_class,
|
||||
create_if_missing=create_if_missing,
|
||||
),
|
||||
}
|
||||
|
||||
|
@ -350,7 +354,9 @@ class ViewTypeRegistry(
|
|||
|
||||
def get_field_options_serializer_map(self):
|
||||
return {
|
||||
view_type.type: view_type.get_field_options_serializer_class()
|
||||
view_type.type: view_type.get_field_options_serializer_class(
|
||||
create_if_missing=False
|
||||
)
|
||||
for view_type in self.registry.values()
|
||||
}
|
||||
|
||||
|
|
|
@ -102,13 +102,11 @@ class GridViewType(ViewType):
|
|||
|
||||
grid_view = ViewHandler().get_view(view.id, view_model=GridView)
|
||||
|
||||
# Ensure all fields have options created before we then query directly off the
|
||||
# options table below.
|
||||
grid_view.get_field_options(create_if_not_exists=True)
|
||||
|
||||
ordered_field_objects = []
|
||||
ordered_visible_fields = (
|
||||
grid_view.get_field_options()
|
||||
# Ensure all fields have options created before we then query directly off
|
||||
# the options table below.
|
||||
grid_view.get_field_options(create_if_not_exists=True)
|
||||
.filter(hidden=False)
|
||||
.order_by("-field__primary", "order", "field__id")
|
||||
.values_list("field__id", flat=True)
|
||||
|
@ -189,8 +187,9 @@ class GalleryViewType(ViewType):
|
|||
visible.
|
||||
"""
|
||||
|
||||
field_options = view.get_field_options(create_if_not_exists=True)
|
||||
field_options.sort(key=lambda x: x.field_id)
|
||||
field_options = view.get_field_options(create_if_not_exists=True).order_by(
|
||||
"field__id"
|
||||
)
|
||||
ids_to_update = [f.id for f in field_options[0:3]]
|
||||
|
||||
if ids_to_update:
|
||||
|
|
|
@ -178,7 +178,9 @@ def view_sort_deleted(sender, view_sort_id, view_sort, user, **kwargs):
|
|||
def view_field_options_updated(sender, view, user, **kwargs):
|
||||
table_page_type = page_registry.get("table")
|
||||
view_type = view_type_registry.get_by_model(view.specific_class)
|
||||
serializer_class = view_type.get_field_options_serializer_class()
|
||||
serializer_class = view_type.get_field_options_serializer_class(
|
||||
create_if_missing=False
|
||||
)
|
||||
transaction.on_commit(
|
||||
lambda: table_page_type.broadcast(
|
||||
{
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
* Allow changing the text of the submit button in the form view.
|
||||
* Fixed reordering of single select options when initially creating the field.
|
||||
* Improved performance by not rendering cells that are out of the view port.
|
||||
* Fix bug where field options in rare situations could have been duplicated.
|
||||
|
||||
## Released (2021-11-25)
|
||||
|
||||
|
|
|
@ -188,7 +188,9 @@ class KanbanViewView(APIView):
|
|||
if field_options:
|
||||
view_type = view_type_registry.get_by_model(view)
|
||||
context = {"fields": [o["field"] for o in model._field_objects.values()]}
|
||||
serializer_class = view_type.get_field_options_serializer_class()
|
||||
serializer_class = view_type.get_field_options_serializer_class(
|
||||
create_if_missing=True
|
||||
)
|
||||
response.update(**serializer_class(view, context=context).data)
|
||||
|
||||
return Response(response)
|
||||
|
|
|
@ -126,13 +126,10 @@ class KanbanViewType(ViewType):
|
|||
When a kanban view is created, we want to set the first three fields as visible.
|
||||
"""
|
||||
|
||||
field_options = view.get_field_options(create_if_not_exists=True)
|
||||
field_options.sort(key=lambda x: x.field_id)
|
||||
ids_to_update = [
|
||||
field_option.id
|
||||
for index, field_option in enumerate(field_options)
|
||||
if index < 3
|
||||
]
|
||||
field_options = view.get_field_options(create_if_not_exists=True).order_by(
|
||||
"field__id"
|
||||
)
|
||||
ids_to_update = [f.id for f in field_options[0:3]]
|
||||
|
||||
if len(ids_to_update) > 0:
|
||||
KanbanViewFieldOptions.objects.filter(id__in=ids_to_update).update(
|
||||
|
|
Loading…
Add table
Reference in a new issue