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

Merge branch '312-having-many-link-row-fields-causes-the-tool-to-slow-down' into 'develop'

Resolve "Having many link row fields causes the backend to slow down"

Closes 

See merge request 
This commit is contained in:
Bram Wiepjes 2021-02-17 17:57:45 +00:00
commit df2c265d9f
6 changed files with 66 additions and 28 deletions
backend
src/baserow/contrib/database
tests/baserow/contrib/database/field
changelog.md
web-frontend/modules/database

View file

@ -1,5 +1,4 @@
from django.utils.functional import lazy
from django.db import models
from drf_spectacular.utils import extend_schema_field
from drf_spectacular.types import OpenApiTypes
@ -66,21 +65,6 @@ class UpdateFieldSerializer(serializers.ModelSerializer):
}
class LinkRowListSerializer(serializers.ListSerializer):
def to_representation(self, data):
"""
Data that is fetched is always from another Table model and when fetching
that data we always need to respect the field enhancements. Otherwise it
could for example fail when we want to fetch the related select options that
could be in another database and table.
"""
if isinstance(data, models.Manager):
data = data.all().enhance_by_fields()
return super().to_representation(data)
class LinkRowValueSerializer(serializers.Serializer):
id = serializers.IntegerField(help_text='The unique identifier of the row in the '
'related table.')

View file

@ -17,8 +17,8 @@ from rest_framework import serializers
from baserow.core.models import UserFile
from baserow.core.user_files.exceptions import UserFileDoesNotExist
from baserow.contrib.database.api.fields.serializers import (
LinkRowListSerializer, LinkRowValueSerializer, FileFieldRequestSerializer,
FileFieldResponseSerializer, SelectOptionSerializer
LinkRowValueSerializer, FileFieldRequestSerializer, FileFieldResponseSerializer,
SelectOptionSerializer
)
from baserow.contrib.database.api.fields.errors import (
ERROR_LINK_ROW_TABLE_NOT_IN_SAME_DATABASE, ERROR_LINK_ROW_TABLE_NOT_PROVIDED,
@ -314,10 +314,35 @@ class LinkRowFieldType(FieldType):
def enhance_queryset(self, queryset, field, name):
"""
Makes sure that the related rows are prefetched by Django.
Makes sure that the related rows are prefetched by Django. We also want to
enhance the primary field of the related queryset. If for example the primary
field is a single select field then the dropdown options need to be
prefetched in order to prevent many queries.
"""
return queryset.prefetch_related(name)
remote_model = queryset.model._meta.get_field(name).remote_field.model
related_queryset = remote_model.objects.all()
try:
primary_field_object = next(
object
for object in remote_model._field_objects.values()
if object['field'].primary
)
related_queryset = primary_field_object['type'].enhance_queryset(
related_queryset,
primary_field_object['field'],
primary_field_object['name']
)
except StopIteration:
# If the related model does not have a primary field then we also don't
# need to enhance the queryset.
pass
return queryset.prefetch_related(models.Prefetch(
name,
queryset=related_queryset
))
def get_serializer_field(self, instance, **kwargs):
"""
@ -331,9 +356,9 @@ class LinkRowFieldType(FieldType):
def get_response_serializer_field(self, instance, **kwargs):
"""
If a model has already been generated it will be added as a property to the
instance. If that case then we can extract the primary field from the model and
we can pass the name along to the LinkRowValueSerializer. It will be used to
include the primary field's value in the response as a string.
instance. If that is the case then we can extract the primary field from the
model and we can pass the name along to the LinkRowValueSerializer. It will
be used to include the primary field's value in the response as a string.
"""
primary_field_name = None
@ -348,7 +373,7 @@ class LinkRowFieldType(FieldType):
if primary_field:
primary_field_name = primary_field['name']
return LinkRowListSerializer(child=LinkRowValueSerializer(
return serializers.ListSerializer(child=LinkRowValueSerializer(
value_field_name=primary_field_name, required=False, **kwargs
))

View file

@ -12,6 +12,9 @@ from baserow.contrib.database.fields.models import SelectOption, SingleSelectFie
from baserow.contrib.database.fields.field_types import SingleSelectFieldType
from baserow.contrib.database.rows.handler import RowHandler
from baserow.contrib.database.views.handler import ViewHandler
from baserow.contrib.database.api.rows.serializers import (
get_row_serializer_class, RowSerializer
)
@pytest.mark.django_db
@ -502,7 +505,11 @@ def test_single_select_field_type_get_order(data_fixture):
@pytest.mark.django_db
def test_primary_single_select_field_with_link_row_field(api_client, data_fixture):
def test_primary_single_select_field_with_link_row_field(
api_client,
data_fixture,
django_assert_num_queries
):
"""
We expect the relation to a table that has a single select field to work.
"""
@ -528,7 +535,8 @@ def test_primary_single_select_field_with_link_row_field(api_client, data_fixtur
type_name='single_select',
select_options=[
{'value': 'Option 1', 'color': 'red'},
{'value': 'Option 2', 'color': 'blue'}
{'value': 'Option 2', 'color': 'blue'},
{'value': 'Option 3', 'color': 'orange'}
],
primary=True
)
@ -548,6 +556,10 @@ def test_primary_single_select_field_with_link_row_field(api_client, data_fixtur
user=user, table=customers_table,
values={f'field_{customers_primary.id}': select_options[1].id}
)
customers_row_3 = row_handler.create_row(
user=user, table=customers_table,
values={f'field_{customers_primary.id}': select_options[2].id}
)
row_handler.create_row(
user, table=example_table,
values={f'field_{link_row_field.id}': [customers_row_1.id, customers_row_2.id]}
@ -556,6 +568,18 @@ def test_primary_single_select_field_with_link_row_field(api_client, data_fixtur
user, table=example_table,
values={f'field_{link_row_field.id}': [customers_row_1.id]}
)
row_handler.create_row(
user, table=example_table,
values={f'field_{link_row_field.id}': [customers_row_3.id]}
)
model = example_table.get_model()
queryset = model.objects.all().enhance_by_fields()
serializer_class = get_row_serializer_class(model, RowSerializer, is_response=True)
with django_assert_num_queries(3):
serializer = serializer_class(queryset, many=True)
serializer.data
response = api_client.get(
reverse('api:database:rows:list', kwargs={'table_id': example_table.id}),
@ -576,6 +600,10 @@ def test_primary_single_select_field_with_link_row_field(api_client, data_fixtur
response_json['results'][1][f'field_{link_row_field.id}'][0]['value'] ==
'Option 1'
)
assert (
response_json['results'][2][f'field_{link_row_field.id}'][0]['value'] ==
'Option 3'
)
@pytest.mark.django_db

View file

@ -21,6 +21,7 @@
* Made it possible to configure SMTP settings via environment variables.
* Added field name to the public REST API docs.
* Made the public REST API docs compatible with smaller screens.
* Reduced the amount of queries when using the link row field.
## Released (2021-02-04)

View file

@ -162,7 +162,7 @@ export class FieldType extends Registerable {
* converted to string.
*/
toHumanReadableString(field, value) {
return value
return value || ''
}
/**

View file

@ -32,7 +32,7 @@ export default {
.toHumanReadableString(primary, row[`field_${primary.id}`])
newValue.push({
id: row.id,
value: rowValue.toString(),
value: rowValue,
})
this.$emit('update', newValue, value)
},