mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-04-07 06:15:36 +00:00
moved the abstractions out of the stores, fixed grid unexisting data bug and made row data more consistent in de backend
This commit is contained in:
parent
0709cf0aec
commit
f44b07c3b0
26 changed files with 371 additions and 247 deletions
backend
src/baserow/contrib/database
tests/baserow
contrib/database
api/v0
table
user
web-frontend/modules
core
database
|
@ -28,23 +28,32 @@ class DatabaseApplicationType(ApplicationType):
|
|||
"""
|
||||
|
||||
database = CoreHandler().create_application(user, group, type_name=self.type,
|
||||
name='Company')
|
||||
name=f"{user.first_name}'s company")
|
||||
|
||||
table = TableHandler().create_table(user, database, name='Customers')
|
||||
ViewHandler().create_view(user, table, GridViewType.type, name='Grid')
|
||||
FieldHandler().create_field(user, table, TextFieldType.type, name='Last name')
|
||||
FieldHandler().create_field(user, table, BooleanFieldType.type, name='Active')
|
||||
|
||||
model = table.get_model(attribute_names=True)
|
||||
model.objects.create(name='Elon', last_name='Musk', active=True)
|
||||
model.objects.create(name='Bill', last_name='Gates', active=False)
|
||||
model.objects.create(name='Mark', last_name='Zuckerburg', active=True)
|
||||
model.objects.create(name='Jeffrey', last_name='Bezos', active=True)
|
||||
|
||||
table_2 = TableHandler().create_table(user, database, name='Projects')
|
||||
ViewHandler().create_view(user, table_2, GridViewType.type, name='Grid')
|
||||
FieldHandler().create_field(user, table_2, BooleanFieldType.type, name='Active')
|
||||
model = table_2.get_model(attribute_names=True)
|
||||
model.objects.create(name='Tesla', active=True)
|
||||
model.objects.create(name='SpaceX', active=False)
|
||||
model.objects.create(name='Amazon', active=False)
|
||||
|
||||
def pre_delete(self, user, database):
|
||||
"""
|
||||
When a database is deleted we must also delete the related tables via the table
|
||||
handler.
|
||||
"""
|
||||
|
||||
database_tables = database.table_set.all().select_related('database__group')
|
||||
table_handler = TableHandler()
|
||||
|
||||
|
|
|
@ -18,10 +18,11 @@ class TextFieldType(FieldType):
|
|||
serializer_field_names = ['text_default']
|
||||
|
||||
def get_serializer_field(self, instance, **kwargs):
|
||||
return serializers.CharField(required=False, allow_blank=True, **kwargs)
|
||||
return serializers.CharField(required=False, allow_null=True, allow_blank=True,
|
||||
default=instance.text_default, **kwargs)
|
||||
|
||||
def get_model_field(self, instance, **kwargs):
|
||||
return models.TextField(default=instance.text_default, null=True, blank=True,
|
||||
return models.TextField(default=instance.text_default, blank=True, null=True,
|
||||
**kwargs)
|
||||
|
||||
def random_value(self, instance, fake):
|
||||
|
@ -37,15 +38,16 @@ class NumberFieldType(FieldType):
|
|||
serializer_field_names = ['number_type', 'number_decimal_places', 'number_negative']
|
||||
|
||||
def prepare_value_for_db(self, instance, value):
|
||||
if instance.number_type == NUMBER_TYPE_DECIMAL:
|
||||
if value and instance.number_type == NUMBER_TYPE_DECIMAL:
|
||||
value = Decimal(value)
|
||||
if not instance.number_negative and value < 0:
|
||||
if value and not instance.number_negative and value < 0:
|
||||
raise ValidationError(f'The value for field {instance.id} cannot be '
|
||||
f'negative.')
|
||||
return value
|
||||
|
||||
def get_serializer_field(self, instance, **kwargs):
|
||||
kwargs['required'] = False
|
||||
kwargs['allow_null'] = True
|
||||
if not instance.number_negative:
|
||||
kwargs['min_value'] = 0
|
||||
if instance.number_type == NUMBER_TYPE_INTEGER:
|
||||
|
@ -89,7 +91,7 @@ class BooleanFieldType(FieldType):
|
|||
model_class = BooleanField
|
||||
|
||||
def get_serializer_field(self, instance, **kwargs):
|
||||
return serializers.BooleanField(required=False, **kwargs)
|
||||
return serializers.BooleanField(required=False, default=False, **kwargs)
|
||||
|
||||
def get_model_field(self, instance, **kwargs):
|
||||
return models.BooleanField(default=False, **kwargs)
|
||||
|
|
|
@ -11,27 +11,44 @@ def test_get_table_serializer(data_fixture):
|
|||
text_default='white')
|
||||
data_fixture.create_number_field(table=table, order=1, name='Horsepower')
|
||||
data_fixture.create_boolean_field(table=table, order=2, name='For sale')
|
||||
data_fixture.create_number_field(table=table, order=2, name='Price',
|
||||
number_type='DECIMAL', number_negative=True,
|
||||
number_decimal_places=2)
|
||||
|
||||
model = table.get_model(attribute_names=True)
|
||||
serializer_class = get_row_serializer_class(model=model)
|
||||
|
||||
# expect the result to be empty if not provided
|
||||
serializer_instance = serializer_class(data={})
|
||||
assert serializer_instance.is_valid()
|
||||
assert serializer_instance.data == {
|
||||
'color': 'white',
|
||||
'horsepower': None,
|
||||
'for_sale': False,
|
||||
'price': None
|
||||
}
|
||||
|
||||
# text field
|
||||
serializer_instance = serializer_class(data={'color': 'Green'})
|
||||
assert serializer_instance.is_valid()
|
||||
assert serializer_instance.data == {'color': 'Green'}
|
||||
assert serializer_instance.data['color'] == 'Green'
|
||||
|
||||
serializer_instance = serializer_class(data={'color': 123})
|
||||
assert serializer_instance.is_valid()
|
||||
assert serializer_instance.data == {'color': '123'}
|
||||
assert serializer_instance.data['color'] == '123'
|
||||
|
||||
serializer_instance = serializer_class(data={'color': None})
|
||||
assert not serializer_instance.is_valid()
|
||||
assert len(serializer_instance.errors['color']) == 1
|
||||
assert serializer_instance.is_valid()
|
||||
assert serializer_instance.data['color'] == None
|
||||
|
||||
# number field
|
||||
serializer_instance = serializer_class(data={'horsepower': 120})
|
||||
assert serializer_instance.is_valid()
|
||||
assert serializer_instance.data == {'horsepower': 120}
|
||||
assert serializer_instance.data['horsepower'] == 120
|
||||
|
||||
serializer_instance = serializer_class(data={'horsepower': None})
|
||||
assert serializer_instance.is_valid()
|
||||
assert serializer_instance.data['horsepower'] == None
|
||||
|
||||
serializer_instance = serializer_class(data={'horsepower': 'abc'})
|
||||
assert not serializer_instance.is_valid()
|
||||
|
@ -44,30 +61,63 @@ def test_get_table_serializer(data_fixture):
|
|||
# boolean field
|
||||
serializer_instance = serializer_class(data={'for_sale': True})
|
||||
assert serializer_instance.is_valid()
|
||||
assert serializer_instance.data == {'for_sale': True}
|
||||
assert serializer_instance.data['for_sale'] == True
|
||||
|
||||
serializer_instance = serializer_class(data={'for_sale': False})
|
||||
assert serializer_instance.is_valid()
|
||||
assert serializer_instance.data['for_sale'] == False
|
||||
|
||||
serializer_instance = serializer_class(data={'for_sale': None})
|
||||
assert not serializer_instance.is_valid()
|
||||
assert len(serializer_instance.errors['for_sale']) == 1
|
||||
|
||||
# boolean field
|
||||
serializer_instance = serializer_class(data={'for_sale': 'abc'})
|
||||
assert not serializer_instance.is_valid()
|
||||
assert len(serializer_instance.errors['for_sale']) == 1
|
||||
|
||||
# price field
|
||||
serializer_instance = serializer_class(data={'price': 120})
|
||||
assert serializer_instance.is_valid()
|
||||
assert serializer_instance.data['price'] == '120.00'
|
||||
|
||||
serializer_instance = serializer_class(data={'price': '-10.22'})
|
||||
assert serializer_instance.is_valid()
|
||||
assert serializer_instance.data['price'] == '-10.22'
|
||||
|
||||
serializer_instance = serializer_class(data={'price': 'abc'})
|
||||
assert not serializer_instance.is_valid()
|
||||
assert len(serializer_instance.errors['price']) == 1
|
||||
|
||||
serializer_instance = serializer_class(data={'price': None})
|
||||
assert serializer_instance.is_valid()
|
||||
assert serializer_instance.data['price'] == None
|
||||
|
||||
# not existing value
|
||||
serializer_instance = serializer_class(data={'NOT_EXISTING': True})
|
||||
assert serializer_instance.is_valid()
|
||||
assert serializer_instance.data == {}
|
||||
assert serializer_instance.data == {
|
||||
'color': 'white',
|
||||
'horsepower': None,
|
||||
'for_sale': False,
|
||||
'price': None
|
||||
}
|
||||
|
||||
# all fields
|
||||
serializer_instance = serializer_class(data={
|
||||
'color': 'green',
|
||||
'horsepower': 120,
|
||||
'for_sale': True
|
||||
'for_sale': True,
|
||||
'price': 120.22
|
||||
})
|
||||
assert serializer_instance.is_valid()
|
||||
assert serializer_instance.data == {
|
||||
'color': 'green',
|
||||
'horsepower': 120,
|
||||
'for_sale': True
|
||||
'for_sale': True,
|
||||
'price': '120.22'
|
||||
}
|
||||
|
||||
# adding an extra field and only use that one.
|
||||
price_field = data_fixture.create_number_field(
|
||||
table=table_2, order=0, name='Sale price', number_type='DECIMAL',
|
||||
number_decimal_places=3, number_negative=True
|
||||
|
|
|
@ -16,6 +16,8 @@ def test_create_row(api_client, data_fixture):
|
|||
name='Horsepower')
|
||||
boolean_field = data_fixture.create_boolean_field(table=table, order=2,
|
||||
name='For sale')
|
||||
text_field_2 = data_fixture.create_text_field(table=table, order=3,
|
||||
name='Description')
|
||||
|
||||
response = api_client.post(
|
||||
reverse('api_v0:database:rows:list', kwargs={'table_id': 99999}),
|
||||
|
@ -39,7 +41,8 @@ def test_create_row(api_client, data_fixture):
|
|||
{
|
||||
f'field_{text_field.id}': 'Green',
|
||||
f'field_{number_field.id}': -10,
|
||||
f'field_{boolean_field.id}': None
|
||||
f'field_{boolean_field.id}': None,
|
||||
f'field_{text_field_2.id}': None
|
||||
},
|
||||
format='json',
|
||||
HTTP_AUTHORIZATION=f'JWT {token}'
|
||||
|
@ -61,39 +64,68 @@ def test_create_row(api_client, data_fixture):
|
|||
assert response.status_code == 200
|
||||
assert response_json_row_1[f'field_{text_field.id}'] == 'white'
|
||||
assert not response_json_row_1[f'field_{number_field.id}']
|
||||
assert not response_json_row_1[f'field_{boolean_field.id}']
|
||||
assert response_json_row_1[f'field_{boolean_field.id}'] == False
|
||||
assert response_json_row_1[f'field_{text_field_2.id}'] == None
|
||||
|
||||
response = api_client.post(
|
||||
reverse('api_v0:database:rows:list', kwargs={'table_id': table.id}),
|
||||
{
|
||||
f'field_{text_field.id}': 'Green',
|
||||
f'field_{number_field.id}': 120,
|
||||
f'field_{boolean_field.id}': True
|
||||
f'field_{number_field.id}': None,
|
||||
f'field_{boolean_field.id}': False,
|
||||
f'field_{text_field_2.id}': '',
|
||||
},
|
||||
format='json',
|
||||
HTTP_AUTHORIZATION=f'JWT {token}'
|
||||
)
|
||||
response_json_row_2 = response.json()
|
||||
assert response.status_code == 200
|
||||
assert response_json_row_2[f'field_{text_field.id}'] == 'Green'
|
||||
assert response_json_row_2[f'field_{number_field.id}'] == 120
|
||||
assert response_json_row_2[f'field_{boolean_field.id}']
|
||||
assert response_json_row_2[f'field_{text_field.id}'] == 'white'
|
||||
assert not response_json_row_2[f'field_{number_field.id}']
|
||||
assert response_json_row_2[f'field_{boolean_field.id}'] == False
|
||||
assert response_json_row_2[f'field_{text_field_2.id}'] == ''
|
||||
|
||||
response = api_client.post(
|
||||
reverse('api_v0:database:rows:list', kwargs={'table_id': table.id}),
|
||||
{
|
||||
f'field_{text_field.id}': 'Green',
|
||||
f'field_{number_field.id}': 120,
|
||||
f'field_{boolean_field.id}': True,
|
||||
f'field_{text_field_2.id}': 'Not important',
|
||||
},
|
||||
format='json',
|
||||
HTTP_AUTHORIZATION=f'JWT {token}'
|
||||
)
|
||||
response_json_row_3 = response.json()
|
||||
assert response.status_code == 200
|
||||
assert response_json_row_3[f'field_{text_field.id}'] == 'Green'
|
||||
assert response_json_row_3[f'field_{number_field.id}'] == 120
|
||||
assert response_json_row_3[f'field_{boolean_field.id}']
|
||||
assert response_json_row_3[f'field_{text_field_2.id}'] == 'Not important'
|
||||
|
||||
model = table.get_model()
|
||||
assert model.objects.all().count() == 2
|
||||
assert model.objects.all().count() == 3
|
||||
rows = model.objects.all().order_by('id')
|
||||
|
||||
row_1 = rows[0]
|
||||
assert row_1.id == response_json_row_1['id']
|
||||
assert getattr(row_1, f'field_{text_field.id}') == 'white'
|
||||
assert not getattr(row_1, f'field_{number_field.id}')
|
||||
assert not getattr(row_1, f'field_{boolean_field.id}')
|
||||
assert getattr(row_1, f'field_{number_field.id}') == None
|
||||
assert getattr(row_1, f'field_{boolean_field.id}') == False
|
||||
assert getattr(row_1, f'field_{text_field_2.id}') == None
|
||||
|
||||
row_2 = rows[1]
|
||||
assert row_2.id == response_json_row_2['id']
|
||||
assert getattr(row_2, f'field_{text_field.id}') == 'Green'
|
||||
assert getattr(row_2, f'field_{number_field.id}') == 120
|
||||
assert getattr(row_2, f'field_{boolean_field.id}')
|
||||
assert getattr(row_2, f'field_{text_field.id}') == 'white'
|
||||
assert getattr(row_2, f'field_{number_field.id}') == None
|
||||
assert getattr(row_2, f'field_{boolean_field.id}') == False
|
||||
assert getattr(row_1, f'field_{text_field_2.id}') == None
|
||||
|
||||
row_3 = rows[2]
|
||||
assert row_3.id == response_json_row_3['id']
|
||||
assert getattr(row_3, f'field_{text_field.id}') == 'Green'
|
||||
assert getattr(row_3, f'field_{number_field.id}') == 120
|
||||
assert getattr(row_3, f'field_{boolean_field.id}') == True
|
||||
assert getattr(row_3, f'field_{text_field_2.id}') == 'Not important'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
|
@ -191,12 +223,12 @@ def test_update_row(api_client, data_fixture):
|
|||
assert response_json_row_1['id'] == row_1.id
|
||||
assert response_json_row_1[f'field_{text_field.id}'] == 'Green'
|
||||
assert response_json_row_1[f'field_{number_field.id}'] == 120
|
||||
assert response_json_row_1[f'field_{boolean_field.id}']
|
||||
assert response_json_row_1[f'field_{boolean_field.id}'] == True
|
||||
|
||||
row_1.refresh_from_db()
|
||||
assert getattr(row_1, f'field_{text_field.id}') == 'Green'
|
||||
assert getattr(row_1, f'field_{number_field.id}') == 120
|
||||
assert getattr(row_1, f'field_{boolean_field.id}')
|
||||
assert getattr(row_1, f'field_{boolean_field.id}') == True
|
||||
|
||||
response = api_client.patch(
|
||||
url,
|
||||
|
@ -215,7 +247,7 @@ def test_update_row(api_client, data_fixture):
|
|||
row_1.refresh_from_db()
|
||||
assert getattr(row_1, f'field_{text_field.id}') == 'Orange'
|
||||
assert getattr(row_1, f'field_{number_field.id}') == 120
|
||||
assert getattr(row_1, f'field_{boolean_field.id}')
|
||||
assert getattr(row_1, f'field_{boolean_field.id}') == True
|
||||
|
||||
url = reverse('api_v0:database:rows:item', kwargs={
|
||||
'table_id': table.id,
|
||||
|
@ -236,12 +268,38 @@ def test_update_row(api_client, data_fixture):
|
|||
assert response_json_row_2['id'] == row_2.id
|
||||
assert response_json_row_2[f'field_{text_field.id}'] == 'Blue'
|
||||
assert response_json_row_2[f'field_{number_field.id}'] == 50
|
||||
assert not response_json_row_2[f'field_{boolean_field.id}']
|
||||
assert response_json_row_2[f'field_{boolean_field.id}'] == False
|
||||
|
||||
row_2.refresh_from_db()
|
||||
assert getattr(row_2, f'field_{text_field.id}') == 'Blue'
|
||||
assert getattr(row_2, f'field_{number_field.id}') == 50
|
||||
assert not getattr(row_2, f'field_{boolean_field.id}')
|
||||
assert getattr(row_2, f'field_{boolean_field.id}') == False
|
||||
|
||||
url = reverse('api_v0:database:rows:item', kwargs={
|
||||
'table_id': table.id,
|
||||
'row_id': row_2.id
|
||||
})
|
||||
response = api_client.patch(
|
||||
url,
|
||||
{
|
||||
f'field_{text_field.id}': None,
|
||||
f'field_{number_field.id}': None,
|
||||
f'field_{boolean_field.id}': False
|
||||
},
|
||||
format='json',
|
||||
HTTP_AUTHORIZATION=f'JWT {token}'
|
||||
)
|
||||
response_json_row_2 = response.json()
|
||||
assert response.status_code == 200
|
||||
assert response_json_row_2['id'] == row_2.id
|
||||
assert response_json_row_2[f'field_{text_field.id}'] == None
|
||||
assert response_json_row_2[f'field_{number_field.id}'] == None
|
||||
assert response_json_row_2[f'field_{boolean_field.id}'] == False
|
||||
|
||||
row_2.refresh_from_db()
|
||||
assert getattr(row_2, f'field_{text_field.id}') == None
|
||||
assert getattr(row_2, f'field_{number_field.id}') == None
|
||||
assert getattr(row_2, f'field_{boolean_field.id}') == False
|
||||
|
||||
table_3 = data_fixture.create_database_table(user=user)
|
||||
decimal_field = data_fixture.create_number_field(
|
||||
|
|
|
@ -167,7 +167,7 @@ def test_update_table(api_client, data_fixture):
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_delete_group(api_client, data_fixture):
|
||||
def test_delete_table(api_client, data_fixture):
|
||||
user, token = data_fixture.create_user_and_token()
|
||||
table_1 = data_fixture.create_database_table(user=user)
|
||||
table_2 = data_fixture.create_database_table()
|
||||
|
|
|
@ -50,13 +50,11 @@ def test_get_table_model(data_fixture):
|
|||
assert color_field.db_column == f'field_{text_field.id}'
|
||||
assert color_field.default == 'white'
|
||||
assert color_field.null
|
||||
assert color_field.blank
|
||||
|
||||
assert isinstance(horsepower_field, models.IntegerField)
|
||||
assert horsepower_field.verbose_name == 'Horsepower'
|
||||
assert horsepower_field.db_column == f'field_{number_field.id}'
|
||||
assert horsepower_field.null
|
||||
assert horsepower_field.blank
|
||||
|
||||
assert isinstance(for_sale_field, models.BooleanField)
|
||||
assert for_sale_field.verbose_name == 'For sale'
|
||||
|
@ -73,7 +71,6 @@ def test_get_table_model(data_fixture):
|
|||
assert isinstance(sale_price_field, models.DecimalField)
|
||||
assert sale_price_field.decimal_places == 3
|
||||
assert sale_price_field.null
|
||||
assert sale_price_field.blank
|
||||
|
||||
model_2 = table.get_model(fields=[number_field], field_ids=[text_field.id],
|
||||
attribute_names=True)
|
||||
|
|
|
@ -24,14 +24,18 @@ def test_create_user():
|
|||
assert group.name == "Test1's group"
|
||||
|
||||
assert Database.objects.all().count() == 1
|
||||
assert Table.objects.all().count() == 1
|
||||
assert GridView.objects.all().count() == 1
|
||||
assert TextField.objects.all().count() == 2
|
||||
assert BooleanField.objects.all().count() == 1
|
||||
assert Table.objects.all().count() == 2
|
||||
assert GridView.objects.all().count() == 2
|
||||
assert TextField.objects.all().count() == 3
|
||||
assert BooleanField.objects.all().count() == 2
|
||||
|
||||
table = Table.objects.first()
|
||||
model = table.get_model()
|
||||
assert model.objects.all().count() == 4
|
||||
tables = Table.objects.all().order_by('id')
|
||||
|
||||
model_1 = tables[0].get_model()
|
||||
assert model_1.objects.all().count() == 4
|
||||
|
||||
model_2 = tables[1].get_model()
|
||||
assert model_2.objects.all().count() == 3
|
||||
|
||||
with pytest.raises(UserAlreadyExist):
|
||||
user_handler.create_user('Test1', 'test@test.nl', 'password')
|
||||
|
|
|
@ -1,18 +1,11 @@
|
|||
import { Registerable } from '@baserow/modules/core/registry'
|
||||
import ApplicationForm from '@baserow/modules/core/components/application/ApplicationForm'
|
||||
|
||||
/**
|
||||
* The application type base class that can be extended when creating a plugin
|
||||
* for the frontend.
|
||||
*/
|
||||
export class ApplicationType {
|
||||
/**
|
||||
* Must return a string with the unique name, this must be the same as the
|
||||
* type used in the backend.
|
||||
*/
|
||||
getType() {
|
||||
return null
|
||||
}
|
||||
|
||||
export class ApplicationType extends Registerable {
|
||||
/**
|
||||
* The font awesome 5 icon name that is used as convenience for the user to
|
||||
* recognize certain application types. If you for example want the database
|
||||
|
@ -58,6 +51,7 @@ export class ApplicationType {
|
|||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.type = this.getType()
|
||||
this.iconClass = this.getIconClass()
|
||||
this.name = this.getName()
|
||||
|
|
|
@ -24,8 +24,6 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
import CreateApplicationModal from '@baserow/modules/core/components/application/CreateApplicationModal'
|
||||
import context from '@baserow/modules/core/mixins/context'
|
||||
|
||||
|
@ -42,9 +40,9 @@ export default {
|
|||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
applications: (state) => state.application.types,
|
||||
}),
|
||||
applications() {
|
||||
return this.$registry.getAll('application')
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggleCreateApplicationModal(type) {
|
||||
|
|
|
@ -97,7 +97,7 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
selectApplication(application) {
|
||||
const type = this.$store.getters['application/getType'](application.type)
|
||||
const type = this.$registry.get('application', application.type)
|
||||
type.select(application, this)
|
||||
},
|
||||
},
|
||||
|
|
|
@ -139,7 +139,7 @@ export default {
|
|||
this.setLoading(application, false)
|
||||
},
|
||||
getSelectedApplicationComponent(application) {
|
||||
const type = this.$store.getters['application/getType'](application.type)
|
||||
const type = this.$registry.get('application', application.type)
|
||||
return type.getSelectedSidebarComponent()
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
import { Registry } from '@baserow/modules/core/registry'
|
||||
|
||||
import applicationStore from '@baserow/modules/core/store/application'
|
||||
import authStore from '@baserow/modules/core/store/auth'
|
||||
import groupStore from '@baserow/modules/core/store/group'
|
||||
import notificationStore from '@baserow/modules/core/store/notification'
|
||||
import sidebarStore from '@baserow/modules/core/store/sidebar'
|
||||
|
||||
export default ({ store }) => {
|
||||
export default ({ store }, inject) => {
|
||||
inject('registry', new Registry())
|
||||
|
||||
store.registerModule('application', applicationStore)
|
||||
store.registerModule('auth', authStore)
|
||||
store.registerModule('group', groupStore)
|
||||
|
|
90
web-frontend/modules/core/registry.js
Normal file
90
web-frontend/modules/core/registry.js
Normal file
|
@ -0,0 +1,90 @@
|
|||
/**
|
||||
* Only instances that are children of a Registerable can be registered into the
|
||||
* registry.
|
||||
*/
|
||||
export class Registerable {
|
||||
/**
|
||||
* Must return a string with the unique name, this must be the same as the
|
||||
* type used in the backend.
|
||||
*/
|
||||
static getType() {
|
||||
throw new Error('The type of a registry must be set.')
|
||||
}
|
||||
|
||||
getType() {
|
||||
return this.constructor.getType()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The registry is an class where Registerable instances can be registered under a
|
||||
* namespace. This is used for plugins to register extra functionality to Baserow. For
|
||||
* example the database plugin registers itself as an application to the core, but
|
||||
* it is also possible to register fields and views to the database plugin.
|
||||
*/
|
||||
export class Registry {
|
||||
constructor() {
|
||||
this.registry = {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new Registerable object under the provided namespace in the registry.
|
||||
* If the namespace doesn't exist it will be created. It is common to register
|
||||
* instantiated classes here.
|
||||
*/
|
||||
register(namespace, object) {
|
||||
if (!(object instanceof Registerable)) {
|
||||
throw new TypeError(
|
||||
'The registered object must be an instance of Registrable.'
|
||||
)
|
||||
}
|
||||
const type = object.getType()
|
||||
|
||||
if (!Object.prototype.hasOwnProperty.call(this.registry, namespace)) {
|
||||
this.registry[namespace] = {}
|
||||
}
|
||||
this.registry[namespace][type] = object
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a registered object with the given type in the provided namespace.
|
||||
*/
|
||||
get(namespace, type) {
|
||||
if (!Object.prototype.hasOwnProperty.call(this.registry, namespace)) {
|
||||
throw new Error(
|
||||
`The namespace ${namespace} is not found in the registry.`
|
||||
)
|
||||
}
|
||||
if (!Object.prototype.hasOwnProperty.call(this.registry[namespace], type)) {
|
||||
throw new Error(
|
||||
`The type ${type} is not found under namespace ${namespace} in the registry.`
|
||||
)
|
||||
}
|
||||
return this.registry[namespace][type]
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the objects that are in the given namespace.
|
||||
*/
|
||||
getAll(namespace) {
|
||||
if (!Object.prototype.hasOwnProperty.call(this.registry, namespace)) {
|
||||
throw new Error(
|
||||
`The namespace ${namespace} is not found in the registry.`
|
||||
)
|
||||
}
|
||||
return this.registry[namespace]
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the object of the given type exists in the namespace.
|
||||
*/
|
||||
exists(namespace, type) {
|
||||
if (!Object.prototype.hasOwnProperty.call(this.registry, namespace)) {
|
||||
return false
|
||||
}
|
||||
if (!Object.prototype.hasOwnProperty.call(this.registry[namespace], type)) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -1,9 +1,8 @@
|
|||
import { ApplicationType } from '@baserow/modules/core/applicationTypes'
|
||||
import ApplicationService from '@baserow/modules/core/services/application'
|
||||
import { clone } from '@baserow/modules/core/utils/object'
|
||||
|
||||
function populateApplication(application, getters) {
|
||||
const type = getters.getType(application.type)
|
||||
function populateApplication(application, registry) {
|
||||
const type = registry.get('application', application.type)
|
||||
|
||||
application._ = {
|
||||
type: type.serialize(),
|
||||
|
@ -14,7 +13,6 @@ function populateApplication(application, getters) {
|
|||
}
|
||||
|
||||
export const state = () => ({
|
||||
types: {},
|
||||
loading: false,
|
||||
loaded: false,
|
||||
items: [],
|
||||
|
@ -22,9 +20,6 @@ export const state = () => ({
|
|||
})
|
||||
|
||||
export const mutations = {
|
||||
REGISTER(state, application) {
|
||||
state.types[application.type] = application
|
||||
},
|
||||
SET_ITEMS(state, applications) {
|
||||
state.items = applications
|
||||
},
|
||||
|
@ -67,19 +62,6 @@ export const mutations = {
|
|||
}
|
||||
|
||||
export const actions = {
|
||||
/**
|
||||
* Register a new application within the registry. The is commonly used when
|
||||
* creating an extension.
|
||||
*/
|
||||
register({ commit, getters }, application) {
|
||||
if (!(application instanceof ApplicationType)) {
|
||||
throw new TypeError(
|
||||
'The application must be an instance of ApplicationType.'
|
||||
)
|
||||
}
|
||||
|
||||
commit('REGISTER', application)
|
||||
},
|
||||
/**
|
||||
* Changes the loading state of a specific item.
|
||||
*/
|
||||
|
@ -95,7 +77,7 @@ export const actions = {
|
|||
try {
|
||||
const { data } = await ApplicationService.fetchAll()
|
||||
data.forEach((part, index, d) => {
|
||||
populateApplication(data[index], getters)
|
||||
populateApplication(data[index], this.$registry)
|
||||
})
|
||||
commit('SET_ITEMS', data)
|
||||
commit('SET_LOADING', false)
|
||||
|
@ -122,7 +104,7 @@ export const actions = {
|
|||
*/
|
||||
clearChildrenSelected({ commit, getters }) {
|
||||
Object.values(getters.getAll).forEach((application) => {
|
||||
const type = getters.getType(application.type)
|
||||
const type = this.$registry.get('application', application.type)
|
||||
commit('CLEAR_CHILDREN_SELECTED', { type, application })
|
||||
})
|
||||
},
|
||||
|
@ -141,7 +123,7 @@ export const actions = {
|
|||
)
|
||||
}
|
||||
|
||||
if (!getters.typeExists(type)) {
|
||||
if (!this.$registry.exists('application', type)) {
|
||||
throw new Error(`An application type with type "${type}" doesn't exist.`)
|
||||
}
|
||||
|
||||
|
@ -149,7 +131,7 @@ export const actions = {
|
|||
postData.type = type
|
||||
|
||||
const { data } = await ApplicationService.create(group.id, postData)
|
||||
populateApplication(data, getters)
|
||||
populateApplication(data, this.$registry)
|
||||
commit('ADD_ITEM', data)
|
||||
},
|
||||
/**
|
||||
|
@ -170,7 +152,7 @@ export const actions = {
|
|||
async delete({ commit, dispatch, getters }, application) {
|
||||
try {
|
||||
await ApplicationService.delete(application.id)
|
||||
const type = getters.getType(application.type)
|
||||
const type = this.$registry.get('application', application.type)
|
||||
type.delete(application, this)
|
||||
commit('DELETE_ITEM', application.id)
|
||||
} catch (error) {
|
||||
|
@ -224,15 +206,6 @@ export const getters = {
|
|||
(application) => application.group.id === group.id
|
||||
)
|
||||
},
|
||||
typeExists: (state) => (type) => {
|
||||
return Object.prototype.hasOwnProperty.call(state.types, type)
|
||||
},
|
||||
getType: (state) => (type) => {
|
||||
if (!Object.prototype.hasOwnProperty.call(state.types, type)) {
|
||||
throw new Error(`An application type with type "${type}" doesn't exist.`)
|
||||
}
|
||||
return state.types[type]
|
||||
},
|
||||
}
|
||||
|
||||
export default {
|
||||
|
|
|
@ -149,7 +149,7 @@ export const actions = {
|
|||
forceDelete({ commit, dispatch, rootGetters }, group) {
|
||||
const applications = rootGetters['application/getAllOfGroup'](group)
|
||||
applications.forEach((application) => {
|
||||
const type = rootGetters['application/getType'](application.type)
|
||||
const type = this.$registry.get('application', application.type)
|
||||
type.delete(application, this)
|
||||
})
|
||||
|
||||
|
|
|
@ -7,10 +7,6 @@ export class DatabaseApplicationType extends ApplicationType {
|
|||
return 'database'
|
||||
}
|
||||
|
||||
getType() {
|
||||
return DatabaseApplicationType.getType()
|
||||
}
|
||||
|
||||
getIconClass() {
|
||||
return 'database'
|
||||
}
|
||||
|
|
|
@ -48,7 +48,6 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import { required } from 'vuelidate/lib/validators'
|
||||
|
||||
import form from '@baserow/modules/core/mixins/form'
|
||||
|
@ -67,9 +66,9 @@ export default {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
fieldTypes: (state) => state.field.types,
|
||||
}),
|
||||
fieldTypes() {
|
||||
return this.$registry.getAll('field')
|
||||
},
|
||||
},
|
||||
validations: {
|
||||
values: {
|
||||
|
@ -79,7 +78,7 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
getFormComponent(type) {
|
||||
return this.$store.getters['field/getType'](type).getFormComponent()
|
||||
return this.$registry.get('field', type).getFormComponent()
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -78,10 +78,12 @@ export default {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
viewTypes() {
|
||||
return this.$registry.getAll('view')
|
||||
},
|
||||
...mapState({
|
||||
isLoading: (state) => state.view.loading,
|
||||
isLoaded: (state) => state.view.loaded,
|
||||
viewTypes: (state) => state.view.types,
|
||||
views: (state) => state.view.items,
|
||||
}),
|
||||
},
|
||||
|
|
|
@ -55,27 +55,27 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
getFieldComponent(type) {
|
||||
return this.$store.getters['field/getType'](
|
||||
type
|
||||
).getGridViewFieldComponent()
|
||||
return this.$registry.get('field', type).getGridViewFieldComponent()
|
||||
},
|
||||
/**
|
||||
* If the grid field component emits an update event this method will be called
|
||||
* which will actually update the value via the store.
|
||||
*/
|
||||
async update(value, oldValue) {
|
||||
try {
|
||||
await this.$store.dispatch('view/grid/updateValue', {
|
||||
update(value, oldValue) {
|
||||
this.$store
|
||||
.dispatch('view/grid/updateValue', {
|
||||
table: this.table,
|
||||
row: this.row,
|
||||
field: this.field,
|
||||
value,
|
||||
oldValue,
|
||||
})
|
||||
} catch (error) {
|
||||
this.$forceUpdate()
|
||||
notifyIf(error, 'column')
|
||||
}
|
||||
.catch((error) => {
|
||||
notifyIf(error, 'column')
|
||||
})
|
||||
.then(() => {
|
||||
this.$forceUpdate()
|
||||
})
|
||||
|
||||
// This is needed because in some cases we do have a value yet, so a watcher of
|
||||
// the value is not guaranteed. This will make sure the component shows the
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { Registerable } from '@baserow/modules/core/registry'
|
||||
|
||||
import FieldNumberSubForm from '@baserow/modules/database/components/field/FieldNumberSubForm'
|
||||
import FieldTextSubForm from '@baserow/modules/database/components/field/FieldTextSubForm'
|
||||
|
||||
|
@ -5,15 +7,7 @@ import GridViewFieldText from '@baserow/modules/database/components/view/grid/Gr
|
|||
import GridViewFieldNumber from '@baserow/modules/database/components/view/grid/GridViewFieldNumber'
|
||||
import GridViewFieldBoolean from '@baserow/modules/database/components/view/grid/GridViewFieldBoolean'
|
||||
|
||||
export class FieldType {
|
||||
/**
|
||||
* Must return a string with the unique name, this must be the same as the
|
||||
* type used in the backend.
|
||||
*/
|
||||
getType() {
|
||||
return null
|
||||
}
|
||||
|
||||
export class FieldType extends Registerable {
|
||||
/**
|
||||
* The font awesome 5 icon name that is used as convenience for the user to
|
||||
* recognize certain view types. If you for example want the database
|
||||
|
@ -60,6 +54,7 @@ export class FieldType {
|
|||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.type = this.getType()
|
||||
this.iconClass = this.getIconClass()
|
||||
this.name = this.getName()
|
||||
|
@ -102,10 +97,6 @@ export class TextFieldType extends FieldType {
|
|||
return 'text'
|
||||
}
|
||||
|
||||
getType() {
|
||||
return TextFieldType.getType()
|
||||
}
|
||||
|
||||
getIconClass() {
|
||||
return 'font'
|
||||
}
|
||||
|
@ -121,6 +112,10 @@ export class TextFieldType extends FieldType {
|
|||
getGridViewFieldComponent() {
|
||||
return GridViewFieldText
|
||||
}
|
||||
|
||||
getEmptyValue(field) {
|
||||
return field.text_default
|
||||
}
|
||||
}
|
||||
|
||||
export class NumberFieldType extends FieldType {
|
||||
|
@ -128,10 +123,6 @@ export class NumberFieldType extends FieldType {
|
|||
return 'number'
|
||||
}
|
||||
|
||||
getType() {
|
||||
return NumberFieldType.getType()
|
||||
}
|
||||
|
||||
getIconClass() {
|
||||
return 'hashtag'
|
||||
}
|
||||
|
@ -154,10 +145,6 @@ export class BooleanFieldType extends FieldType {
|
|||
return 'boolean'
|
||||
}
|
||||
|
||||
getType() {
|
||||
return BooleanFieldType.getType()
|
||||
}
|
||||
|
||||
getIconClass() {
|
||||
return 'check-square'
|
||||
}
|
||||
|
|
|
@ -73,7 +73,7 @@ export default {
|
|||
* Prepares all the table, field and view data for the provided database, table and
|
||||
* view id.
|
||||
*/
|
||||
async asyncData({ store, params, error }) {
|
||||
async asyncData({ store, params, error, app }) {
|
||||
// @TODO figure out why the id's aren't converted to an int in the route.
|
||||
const databaseId = parseInt(params.databaseId)
|
||||
const tableId = parseInt(params.tableId)
|
||||
|
@ -105,7 +105,7 @@ export default {
|
|||
|
||||
// It might be possible that the view also has some stores that need to be
|
||||
// filled with initial data so we're going to call the fetch function here.
|
||||
const type = store.getters['view/getType'](view.type)
|
||||
const type = app.$registry.get('view', view.type)
|
||||
await type.fetch({ store }, view)
|
||||
} catch {
|
||||
return error({ statusCode: 404, message: 'View not found.' })
|
||||
|
@ -130,11 +130,11 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
getViewComponent(view) {
|
||||
const type = this.$store.getters['view/getType'](view.type)
|
||||
const type = this.$registry.get('view', view.type)
|
||||
return type.getComponent()
|
||||
},
|
||||
getViewHeaderComponent(view) {
|
||||
const type = this.$store.getters['view/getType'](view.type)
|
||||
const type = this.$registry.get('view', view.type)
|
||||
return type.getHeaderComponent()
|
||||
},
|
||||
},
|
||||
|
|
|
@ -11,27 +11,15 @@ import viewStore from '@baserow/modules/database/store/view'
|
|||
import fieldStore from '@baserow/modules/database/store/field'
|
||||
import gridStore from '@baserow/modules/database/store/view/grid'
|
||||
|
||||
/**
|
||||
* Note that this method is actually called on the server and client side, but
|
||||
* for registering the application this is intentional. The table and view
|
||||
* types must actually be registered on both sides. Since we're adding an
|
||||
* initialized class object, which cannot be stringified we need to override the
|
||||
* object on the client side. Because both register actions don't check if the
|
||||
* type already exists, but just overrides this works for now. In the future we
|
||||
* must find a way to properly serialize the object and pass it from the server
|
||||
* to the client in order to get rid off the warning 'Cannot stringify arbitrary
|
||||
* non-POJOs DatabaseApplicationType' on the server side. There is an issue for
|
||||
* that on the backlog with id 15.
|
||||
*/
|
||||
export default ({ store }) => {
|
||||
export default ({ store, app }) => {
|
||||
store.registerModule('table', tableStore)
|
||||
store.registerModule('view', viewStore)
|
||||
store.registerModule('field', fieldStore)
|
||||
store.registerModule('view/grid', gridStore)
|
||||
|
||||
store.dispatch('application/register', new DatabaseApplicationType())
|
||||
store.dispatch('view/register', new GridViewType())
|
||||
store.dispatch('field/register', new TextFieldType())
|
||||
store.dispatch('field/register', new NumberFieldType())
|
||||
store.dispatch('field/register', new BooleanFieldType())
|
||||
app.$registry.register('application', new DatabaseApplicationType())
|
||||
app.$registry.register('view', new GridViewType())
|
||||
app.$registry.register('field', new TextFieldType())
|
||||
app.$registry.register('field', new NumberFieldType())
|
||||
app.$registry.register('field', new BooleanFieldType())
|
||||
}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import { FieldType } from '@baserow/modules/database/fieldTypes'
|
||||
import FieldService from '@baserow/modules/database/services/field'
|
||||
import { clone } from '@baserow/modules/core/utils/object'
|
||||
|
||||
export function populateField(field, getters) {
|
||||
const type = getters.getType(field.type)
|
||||
export function populateField(field, registry) {
|
||||
const type = registry.get('field', field.type)
|
||||
|
||||
field._ = {
|
||||
type: type.serialize(),
|
||||
|
@ -21,9 +20,6 @@ export const state = () => ({
|
|||
})
|
||||
|
||||
export const mutations = {
|
||||
REGISTER(state, field) {
|
||||
state.types[field.type] = field
|
||||
},
|
||||
SET_ITEMS(state, applications) {
|
||||
state.items = applications
|
||||
},
|
||||
|
@ -69,17 +65,6 @@ export const mutations = {
|
|||
}
|
||||
|
||||
export const actions = {
|
||||
/**
|
||||
* Register a new field type with the registry. This is used for creating a new
|
||||
* field type that users can create.
|
||||
*/
|
||||
register({ commit, getters }, field) {
|
||||
if (!(field instanceof FieldType)) {
|
||||
throw new TypeError('The field must be an instance of fieldType.')
|
||||
}
|
||||
|
||||
commit('REGISTER', field)
|
||||
},
|
||||
/**
|
||||
* Changes the loading state of a specific field.
|
||||
*/
|
||||
|
@ -98,7 +83,7 @@ export const actions = {
|
|||
try {
|
||||
const { data } = await FieldService.fetchAll(table.id)
|
||||
data.forEach((part, index, d) => {
|
||||
populateField(data[index], getters)
|
||||
populateField(data[index], this.$registry)
|
||||
})
|
||||
|
||||
const primaryIndex = data.findIndex((item) => item.primary === true)
|
||||
|
@ -119,10 +104,9 @@ export const actions = {
|
|||
/**
|
||||
* Creates a new field with the provided type for the given table.
|
||||
*/
|
||||
async create(
|
||||
{ commit, getters, rootGetters, dispatch },
|
||||
{ type, table, values }
|
||||
) {
|
||||
async create(context, { type, table, values }) {
|
||||
const { commit } = context
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(values, 'type')) {
|
||||
throw new Error(
|
||||
'The key "type" is a reserved, but is already set on the ' +
|
||||
|
@ -130,16 +114,25 @@ export const actions = {
|
|||
)
|
||||
}
|
||||
|
||||
if (!getters.typeExists(type)) {
|
||||
if (!this.$registry.exists('field', type)) {
|
||||
throw new Error(`A field with type "${type}" doesn't exist.`)
|
||||
}
|
||||
|
||||
const fieldType = this.$registry.get('field', type)
|
||||
|
||||
const postData = clone(values)
|
||||
postData.type = type
|
||||
|
||||
let { data } = await FieldService.create(table.id, postData)
|
||||
data = populateField(data, getters)
|
||||
data = populateField(data, this.$registry)
|
||||
commit('ADD_ITEM', data)
|
||||
|
||||
// Call the field created event on all the registered views because they might
|
||||
// need to change things in loaded data. For example the grid field will add the
|
||||
// field to all of the rows that are in memory.
|
||||
Object.values(this.$registry.getAll('view')).forEach((viewType) => {
|
||||
viewType.fieldCreated(context, table, data, fieldType)
|
||||
})
|
||||
},
|
||||
/**
|
||||
* Updates the values of the provided field.
|
||||
|
@ -152,7 +145,7 @@ export const actions = {
|
|||
)
|
||||
}
|
||||
|
||||
if (!getters.typeExists(type)) {
|
||||
if (!this.$registry.exists('field', type)) {
|
||||
throw new Error(`A field with type "${type}" doesn't exist.`)
|
||||
}
|
||||
|
||||
|
@ -160,7 +153,7 @@ export const actions = {
|
|||
postData.type = type
|
||||
|
||||
let { data } = await FieldService.update(field.id, postData)
|
||||
data = populateField(data, getters)
|
||||
data = populateField(data, this.$registry)
|
||||
if (field.primary) {
|
||||
commit('SET_PRIMARY', data)
|
||||
} else {
|
||||
|
@ -199,15 +192,6 @@ export const getters = {
|
|||
get: (state) => (id) => {
|
||||
return state.items.find((item) => item.id === id)
|
||||
},
|
||||
typeExists: (state) => (type) => {
|
||||
return Object.prototype.hasOwnProperty.call(state.types, type)
|
||||
},
|
||||
getType: (state) => (type) => {
|
||||
if (!Object.prototype.hasOwnProperty.call(state.types, type)) {
|
||||
throw new Error(`A field with type "${type}" doesn't exist.`)
|
||||
}
|
||||
return state.types[type]
|
||||
},
|
||||
}
|
||||
|
||||
export default {
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import { ViewType } from '@baserow/modules/database/viewTypes'
|
||||
import ViewService from '@baserow/modules/database/services/view'
|
||||
import { clone } from '@baserow/modules/core/utils/object'
|
||||
import { DatabaseApplicationType } from '@baserow/modules/database/applicationTypes'
|
||||
|
||||
export function populateView(view, getters) {
|
||||
const type = getters.getType(view.type)
|
||||
export function populateView(view, registry) {
|
||||
const type = registry.get('view', view.type)
|
||||
|
||||
view._ = {
|
||||
type: type.serialize(),
|
||||
|
@ -23,9 +22,6 @@ export const state = () => ({
|
|||
})
|
||||
|
||||
export const mutations = {
|
||||
REGISTER(state, view) {
|
||||
state.types[view.type] = view
|
||||
},
|
||||
SET_ITEMS(state, applications) {
|
||||
state.items = applications
|
||||
},
|
||||
|
@ -68,17 +64,6 @@ export const mutations = {
|
|||
}
|
||||
|
||||
export const actions = {
|
||||
/**
|
||||
* Register a new view type with the registry. This is used for creating a new
|
||||
* view type that users can create.
|
||||
*/
|
||||
register({ dispatch, commit }, view) {
|
||||
if (!(view instanceof ViewType)) {
|
||||
throw new TypeError('The view must be an instance of ViewType.')
|
||||
}
|
||||
|
||||
commit('REGISTER', view)
|
||||
},
|
||||
/**
|
||||
* Changes the loading state of a specific view.
|
||||
*/
|
||||
|
@ -97,7 +82,7 @@ export const actions = {
|
|||
try {
|
||||
const { data } = await ViewService.fetchAll(table.id)
|
||||
data.forEach((part, index, d) => {
|
||||
populateView(data[index], getters)
|
||||
populateView(data[index], this.$registry)
|
||||
})
|
||||
commit('SET_ITEMS', data)
|
||||
commit('SET_LOADING', false)
|
||||
|
@ -123,7 +108,7 @@ export const actions = {
|
|||
)
|
||||
}
|
||||
|
||||
if (!getters.typeExists(type)) {
|
||||
if (!this.$registry.exists('view', type)) {
|
||||
throw new Error(`A view with type "${type}" doesn't exist.`)
|
||||
}
|
||||
|
||||
|
@ -131,7 +116,7 @@ export const actions = {
|
|||
postData.type = type
|
||||
|
||||
const { data } = await ViewService.create(table.id, postData)
|
||||
populateView(data, getters)
|
||||
populateView(data, this.$registry)
|
||||
commit('ADD_ITEM', data)
|
||||
},
|
||||
/**
|
||||
|
@ -229,15 +214,6 @@ export const getters = {
|
|||
get: (state) => (id) => {
|
||||
return state.items.find((item) => item.id === id)
|
||||
},
|
||||
typeExists: (state) => (type) => {
|
||||
return Object.prototype.hasOwnProperty.call(state.types, type)
|
||||
},
|
||||
getType: (state) => (type) => {
|
||||
if (!Object.prototype.hasOwnProperty.call(state.types, type)) {
|
||||
throw new Error(`A view with type "${type}" doesn't exist.`)
|
||||
}
|
||||
return state.types[type]
|
||||
},
|
||||
}
|
||||
|
||||
export default {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import axios from 'axios'
|
||||
import _ from 'lodash'
|
||||
|
||||
import GridService from '@baserow/modules/database/services/view/grid'
|
||||
import RowService from '@baserow/modules/database/services/row'
|
||||
|
@ -106,6 +107,12 @@ export const mutations = {
|
|||
SET_VALUE(state, { row, field, value }) {
|
||||
row[`field_${field.id}`] = value
|
||||
},
|
||||
ADD_FIELD(state, { field, value }) {
|
||||
const name = `field_${field.id}`
|
||||
state.rows.forEach((row) => {
|
||||
row[name] = value
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
// Contains the timeout needed for the delayed delayed scroll top action.
|
||||
|
@ -414,22 +421,21 @@ export const actions = {
|
|||
fields.forEach((field) => {
|
||||
const name = `field_${field.id}`
|
||||
if (!(name in values)) {
|
||||
const fieldType = rootGetters['field/getType'](field._.type.type)
|
||||
const fieldType = this.$registry.get('field', field._.type.type)
|
||||
const empty = fieldType.getEmptyValue(field)
|
||||
if (empty !== null) {
|
||||
values[name] = fieldType.getEmptyValue(field)
|
||||
}
|
||||
values[name] = empty
|
||||
}
|
||||
})
|
||||
|
||||
// Populate the row and set the loading state to indicate that the row has not
|
||||
// yet been added.
|
||||
populateRow(values)
|
||||
values.id = 0
|
||||
values._.loading = true
|
||||
const row = _.assign({}, values)
|
||||
populateRow(row)
|
||||
row.id = 0
|
||||
row._.loading = true
|
||||
|
||||
commit('ADD_ROWS', {
|
||||
rows: [values],
|
||||
rows: [row],
|
||||
prependToRows: 0,
|
||||
appendToRows: 1,
|
||||
count: getters.getCount + 1,
|
||||
|
@ -446,6 +452,12 @@ export const actions = {
|
|||
const { data } = await RowService.create(table.id, values)
|
||||
commit('FINALIZE_ROW', { index, id: data.id })
|
||||
},
|
||||
/**
|
||||
* Adds a field with a provided value to the rows in memory.
|
||||
*/
|
||||
addField({ commit }, { field, value = null }) {
|
||||
commit('ADD_FIELD', { field, value })
|
||||
},
|
||||
}
|
||||
|
||||
export const getters = {
|
||||
|
|
|
@ -1,15 +1,8 @@
|
|||
import { Registerable } from '@baserow/modules/core/registry'
|
||||
import ViewForm from '@baserow/modules/database/components/view/ViewForm'
|
||||
import GridView from '@baserow/modules/database/components/view/grid/GridView'
|
||||
|
||||
export class ViewType {
|
||||
/**
|
||||
* Must return a string with the unique name, this must be the same as the
|
||||
* type used in the backend.
|
||||
*/
|
||||
getType() {
|
||||
return null
|
||||
}
|
||||
|
||||
export class ViewType extends Registerable {
|
||||
/**
|
||||
* The font awesome 5 icon name that is used as convenience for the user to
|
||||
* recognize certain view types. If you for example want the database
|
||||
|
@ -28,6 +21,7 @@ export class ViewType {
|
|||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.type = this.getType()
|
||||
this.iconClass = this.getIconClass()
|
||||
this.name = this.getName()
|
||||
|
@ -85,6 +79,12 @@ export class ViewType {
|
|||
*/
|
||||
fetch() {}
|
||||
|
||||
/**
|
||||
* Method that is called when a field has been created. This can be useful to
|
||||
* maintain data integrity for example to add the field to the grid view store.
|
||||
*/
|
||||
fieldCreated(context, table, field, fieldType) {}
|
||||
|
||||
/**
|
||||
* @return object
|
||||
*/
|
||||
|
@ -102,10 +102,6 @@ export class GridViewType extends ViewType {
|
|||
return 'grid'
|
||||
}
|
||||
|
||||
getType() {
|
||||
return GridViewType.getType()
|
||||
}
|
||||
|
||||
getIconClass() {
|
||||
return 'th'
|
||||
}
|
||||
|
@ -121,4 +117,9 @@ export class GridViewType extends ViewType {
|
|||
async fetch({ store }, view) {
|
||||
await store.dispatch('view/grid/fetchInitial', { gridId: view.id })
|
||||
}
|
||||
|
||||
fieldCreated({ dispatch }, table, field, fieldType) {
|
||||
const value = fieldType.getEmptyValue(field)
|
||||
dispatch('view/grid/addField', { field, value }, { root: true })
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue