mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-04-12 16:28:06 +00:00
Merge branch '204-fix-stopiteration-error' into 'develop'
Resolve "Fix StopIteration error" Closes #204 See merge request bramw/baserow!124
This commit is contained in:
commit
7ea62e614d
12 changed files with 154 additions and 6 deletions
backend
src/baserow/contrib/database
api/fields
fields
migrations
tests/baserow/contrib/database
web-frontend/modules/database
|
@ -30,3 +30,8 @@ ERROR_ORDER_BY_FIELD_NOT_POSSIBLE = (
|
|||
'It is not possible to order by {e.field_name} because the field type '
|
||||
'{e.field_type} does not support filtering.'
|
||||
)
|
||||
ERROR_INCOMPATIBLE_PRIMARY_FIELD_TYPE = (
|
||||
'ERROR_INCOMPATIBLE_PRIMARY_FIELD_TYPE',
|
||||
HTTP_400_BAD_REQUEST,
|
||||
'The field type {e.field_type} is not compatible with the primary field.'
|
||||
)
|
||||
|
|
|
@ -59,3 +59,11 @@ class OrderByFieldNotPossible(Exception):
|
|||
self.field_name = field_name
|
||||
self.field_type = field_type
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class IncompatiblePrimaryFieldTypeError(Exception):
|
||||
"""Raised when the primary field is changed to an incompatible field type."""
|
||||
|
||||
def __init__(self, field_type=None, *args, **kwargs):
|
||||
self.field_type = field_type
|
||||
super().__init__(*args, **kwargs)
|
||||
|
|
|
@ -19,7 +19,8 @@ from baserow.contrib.database.api.fields.serializers import (
|
|||
LinkRowValueSerializer, FileFieldRequestSerializer, FileFieldResponseSerializer
|
||||
)
|
||||
from baserow.contrib.database.api.fields.errors import (
|
||||
ERROR_LINK_ROW_TABLE_NOT_IN_SAME_DATABASE, ERROR_LINK_ROW_TABLE_NOT_PROVIDED
|
||||
ERROR_LINK_ROW_TABLE_NOT_IN_SAME_DATABASE, ERROR_LINK_ROW_TABLE_NOT_PROVIDED,
|
||||
ERROR_INCOMPATIBLE_PRIMARY_FIELD_TYPE
|
||||
)
|
||||
|
||||
from .handler import FieldHandler
|
||||
|
@ -28,7 +29,10 @@ from .models import (
|
|||
NUMBER_TYPE_INTEGER, NUMBER_TYPE_DECIMAL, TextField, LongTextField, URLField,
|
||||
NumberField, BooleanField, DateField, LinkRowField, EmailField, FileField
|
||||
)
|
||||
from .exceptions import LinkRowTableNotInSameDatabase, LinkRowTableNotProvided
|
||||
from .exceptions import (
|
||||
LinkRowTableNotInSameDatabase, LinkRowTableNotProvided,
|
||||
IncompatiblePrimaryFieldTypeError
|
||||
)
|
||||
|
||||
|
||||
class TextFieldType(FieldType):
|
||||
|
@ -292,9 +296,11 @@ class LinkRowFieldType(FieldType):
|
|||
}
|
||||
api_exceptions_map = {
|
||||
LinkRowTableNotProvided: ERROR_LINK_ROW_TABLE_NOT_PROVIDED,
|
||||
LinkRowTableNotInSameDatabase: ERROR_LINK_ROW_TABLE_NOT_IN_SAME_DATABASE
|
||||
LinkRowTableNotInSameDatabase: ERROR_LINK_ROW_TABLE_NOT_IN_SAME_DATABASE,
|
||||
IncompatiblePrimaryFieldTypeError: ERROR_INCOMPATIBLE_PRIMARY_FIELD_TYPE
|
||||
}
|
||||
can_order_by = False
|
||||
can_be_primary_field = False
|
||||
|
||||
def enhance_queryset(self, queryset, field, name):
|
||||
"""
|
||||
|
|
|
@ -12,7 +12,7 @@ from baserow.contrib.database.views.handler import ViewHandler
|
|||
|
||||
from .exceptions import (
|
||||
PrimaryFieldAlreadyExists, CannotDeletePrimaryField, CannotChangeFieldType,
|
||||
FieldDoesNotExist
|
||||
FieldDoesNotExist, IncompatiblePrimaryFieldTypeError
|
||||
)
|
||||
from .registries import field_type_registry, field_converter_registry
|
||||
from .models import Field
|
||||
|
@ -168,6 +168,10 @@ class FieldHandler:
|
|||
# to remove all view filters.
|
||||
if new_type_name and field_type.type != new_type_name:
|
||||
field_type = field_type_registry.get(new_type_name)
|
||||
|
||||
if field.primary and not field_type.can_be_primary_field:
|
||||
raise IncompatiblePrimaryFieldTypeError(new_type_name)
|
||||
|
||||
new_model_class = field_type.model_class
|
||||
field.change_polymorphic_type_to(new_model_class)
|
||||
|
||||
|
|
|
@ -39,6 +39,9 @@ class FieldType(MapAPIExceptionsInstanceMixin, APIUrlsInstanceMixin,
|
|||
can_order_by = True
|
||||
"""Indicates whether it is possible to order by this field type."""
|
||||
|
||||
can_be_primary_field = True
|
||||
"""Some field types cannot be the primary field."""
|
||||
|
||||
def prepare_value_for_db(self, instance, value):
|
||||
"""
|
||||
When a row is created or updated all the values are going to be prepared for the
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
# Generated by Django 2.2.11 on 2020-11-16 08:53
|
||||
|
||||
from django.db import migrations, connections
|
||||
from django.db.models import Exists, OuterRef, Q
|
||||
from django.conf import settings
|
||||
|
||||
from baserow.contrib.database.table.models import Table as TableModel
|
||||
from baserow.contrib.database.fields.models import Field as FieldModel
|
||||
|
||||
|
||||
def forward(apps, schema_editor):
|
||||
"""
|
||||
This migration fixes the not allowed situations where link row fields are primary
|
||||
fields or if a table doesn't have a primary field anymore (because it was
|
||||
deleted by the related link row field). In both cases a new primary text field is
|
||||
created because that is allowed.
|
||||
"""
|
||||
|
||||
Table = apps.get_model('database', 'Table')
|
||||
Field = apps.get_model('database', 'Field')
|
||||
LinkRowField = apps.get_model('database', 'LinkRowField')
|
||||
TextField = apps.get_model('database', 'TextField')
|
||||
ContentType = apps.get_model('contenttypes', 'ContentType')
|
||||
text_field_content_type = ContentType.objects.get_for_model(TextField)
|
||||
|
||||
# Check if there are tables without a primary field or where the primary field is
|
||||
# a link row field, which is not allowed.
|
||||
tables_without_primary = Table.objects.annotate(
|
||||
has_primary=Exists(Field.objects.filter(table=OuterRef('pk'), primary=True)),
|
||||
has_link_row_primary=Exists(
|
||||
LinkRowField.objects.filter(table=OuterRef('pk'), primary=True)
|
||||
)
|
||||
).filter(Q(has_primary=False) | Q(has_link_row_primary=True))
|
||||
for table in tables_without_primary:
|
||||
# If the table has a link row field as primary field it needs to be marked as
|
||||
# normal field because they are not allowed to be primary.
|
||||
if table.has_link_row_primary:
|
||||
link_row_primary = LinkRowField.objects.get(table=table, primary=True)
|
||||
link_row_primary.primary = False
|
||||
link_row_primary.save()
|
||||
|
||||
# It might be possible in the future that the get_model or db_column methods
|
||||
# are going to disappear. If that is the case then the creation of the field
|
||||
# cannot be executed, so we can skip that.
|
||||
if (
|
||||
not hasattr(TableModel, 'get_model') or
|
||||
not hasattr(FieldModel, 'db_column')
|
||||
):
|
||||
continue
|
||||
|
||||
# We now know for sure there isn't a primary field in the table, so we can
|
||||
# create a new primary text field because the table expects one.
|
||||
new_primary = TextField.objects.create(
|
||||
table=table,
|
||||
name='Primary (auto created)',
|
||||
order=0,
|
||||
content_type=text_field_content_type,
|
||||
primary=True
|
||||
)
|
||||
connection = connections[settings.USER_TABLE_DATABASE]
|
||||
with connection.schema_editor() as tables_schema_editor:
|
||||
to_model = TableModel.get_model(table, field_ids=[new_primary.id])
|
||||
field_name = FieldModel.db_column.__get__(new_primary, FieldModel)
|
||||
model_field = to_model._meta.get_field(field_name)
|
||||
tables_schema_editor.add_field(to_model, model_field)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('database', '0019_filefield'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(forward, migrations.RunPython.noop),
|
||||
]
|
|
@ -168,7 +168,7 @@ def test_update_field(api_client, data_fixture):
|
|||
user_2, token_2 = data_fixture.create_user_and_token()
|
||||
table = data_fixture.create_database_table(user=user)
|
||||
table_2 = data_fixture.create_database_table(user=user_2)
|
||||
text = data_fixture.create_text_field(table=table)
|
||||
text = data_fixture.create_text_field(table=table, primary=True)
|
||||
text_2 = data_fixture.create_text_field(table=table_2)
|
||||
|
||||
url = reverse('api:database:fields:item', kwargs={'field_id': text_2.id})
|
||||
|
@ -192,6 +192,22 @@ def test_update_field(api_client, data_fixture):
|
|||
assert response.status_code == HTTP_404_NOT_FOUND
|
||||
assert response.json()['error'] == 'ERROR_FIELD_DOES_NOT_EXIST'
|
||||
|
||||
# The primary field is not compatible with a link row field so that should result
|
||||
# in an error.
|
||||
url = reverse('api:database:fields:item', kwargs={'field_id': text.id})
|
||||
response = api_client.patch(
|
||||
url,
|
||||
{'type': 'link_row'},
|
||||
format='json',
|
||||
HTTP_AUTHORIZATION=f'JWT {token}'
|
||||
)
|
||||
assert response.status_code == HTTP_400_BAD_REQUEST
|
||||
assert response.json()['error'] == 'ERROR_INCOMPATIBLE_PRIMARY_FIELD_TYPE'
|
||||
assert (
|
||||
response.json()['detail'] ==
|
||||
'The field type link_row is not compatible with the primary field.'
|
||||
)
|
||||
|
||||
url = reverse('api:database:fields:item', kwargs={'field_id': text.id})
|
||||
response = api_client.patch(
|
||||
url,
|
||||
|
|
|
@ -8,7 +8,7 @@ from baserow.contrib.database.fields.models import (
|
|||
)
|
||||
from baserow.contrib.database.fields.exceptions import (
|
||||
FieldTypeDoesNotExist, PrimaryFieldAlreadyExists, CannotDeletePrimaryField,
|
||||
FieldDoesNotExist
|
||||
FieldDoesNotExist, IncompatiblePrimaryFieldTypeError
|
||||
)
|
||||
|
||||
|
||||
|
@ -165,6 +165,15 @@ def test_update_field(data_fixture):
|
|||
with pytest.raises(FieldTypeDoesNotExist):
|
||||
handler.update_field(user=user, field=field, new_type_name='NOT_EXISTING')
|
||||
|
||||
# The link row field is not compatible with a primary field so an exception
|
||||
# is expected.
|
||||
field.primary = True
|
||||
field.save()
|
||||
with pytest.raises(IncompatiblePrimaryFieldTypeError):
|
||||
handler.update_field(user=user, field=field, new_type_name='link_row')
|
||||
field.primary = False
|
||||
field.save()
|
||||
|
||||
# Change some values of the text field and test if they have been changed.
|
||||
field = handler.update_field(user=user, field=field, name='Text field',
|
||||
text_default='Default value')
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
* Set un-secure lax cookie when public web frontend url isn't over a secure connection.
|
||||
* Fixed bug where the sort choose field item didn't have a hover effect.
|
||||
* Implemented a file field and user files upload.
|
||||
* Made it impossible for the `link_row` field to be a primary field because that can
|
||||
cause the primary field to be deleted.
|
||||
|
||||
## Released (2020-11-02)
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
:icon="fieldType.iconClass"
|
||||
:name="fieldType.name"
|
||||
:value="fieldType.type"
|
||||
:disabled="primary && !fieldType.canBePrimaryField"
|
||||
></DropdownItem>
|
||||
</Dropdown>
|
||||
<div v-if="$v.values.type.$error" class="error">
|
||||
|
@ -62,6 +63,11 @@ export default {
|
|||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
primary: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
ref="form"
|
||||
:table="table"
|
||||
:default-values="field"
|
||||
:primary="field.primary"
|
||||
@submitted="submit"
|
||||
>
|
||||
<div class="context__form-actions">
|
||||
|
|
|
@ -96,6 +96,13 @@ export class FieldType extends Registerable {
|
|||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if is possible for the field type to be the primary field.
|
||||
*/
|
||||
getCanBePrimaryField() {
|
||||
return true
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.type = this.getType()
|
||||
|
@ -103,6 +110,7 @@ export class FieldType extends Registerable {
|
|||
this.name = this.getName()
|
||||
this.sortIndicator = this.getSortIndicator()
|
||||
this.canSortInView = this.getCanSortInView()
|
||||
this.canBePrimaryField = this.getCanBePrimaryField()
|
||||
|
||||
if (this.type === null) {
|
||||
throw new Error('The type name of a view type must be set.')
|
||||
|
@ -364,6 +372,10 @@ export class LinkRowFieldType extends FieldType {
|
|||
return false
|
||||
}
|
||||
|
||||
getCanBePrimaryField() {
|
||||
return false
|
||||
}
|
||||
|
||||
prepareValueForCopy(field, value) {
|
||||
return JSON.stringify({
|
||||
tableId: field.link_row_table,
|
||||
|
|
Loading…
Add table
Reference in a new issue