From f44b07c3b0a38f2b317a0300a5d674d46ff5beb3 Mon Sep 17 00:00:00 2001
From: Bram Wiepjes <bramw@protonmail.com>
Date: Tue, 7 Apr 2020 19:49:45 +0000
Subject: [PATCH] moved the abstractions out of the stores, fixed grid
 unexisting data bug and made row data more consistent in de backend

---
 .../contrib/database/application_types.py     | 13 ++-
 .../contrib/database/fields/field_types.py    | 12 ++-
 .../api/v0/rows/test_row_serializers.py       | 70 ++++++++++++--
 .../database/api/v0/rows/test_row_views.py    | 96 +++++++++++++++----
 .../api/v0/tables/test_table_views.py         |  2 +-
 .../database/table/test_table_models.py       |  3 -
 .../tests/baserow/user/test_user_handler.py   | 18 ++--
 web-frontend/modules/core/applicationTypes.js | 12 +--
 .../application/CreateApplicationContext.vue  |  8 +-
 .../core/components/group/DashboardGroup.vue  |  2 +-
 .../components/sidebar/SidebarApplication.vue |  2 +-
 web-frontend/modules/core/plugin.js           |  6 +-
 web-frontend/modules/core/registry.js         | 90 +++++++++++++++++
 .../modules/core/store/application.js         | 41 ++------
 web-frontend/modules/core/store/group.js      |  2 +-
 .../modules/database/applicationTypes.js      |  4 -
 .../database/components/field/FieldForm.vue   |  9 +-
 .../database/components/view/ViewsContext.vue |  4 +-
 .../components/view/grid/GridViewField.vue    | 20 ++--
 web-frontend/modules/database/fieldTypes.js   | 29 ++----
 web-frontend/modules/database/pages/table.vue |  8 +-
 web-frontend/modules/database/plugin.js       | 24 ++---
 web-frontend/modules/database/store/field.js  | 54 ++++-------
 web-frontend/modules/database/store/view.js   | 34 +------
 .../modules/database/store/view/grid.js       | 28 ++++--
 web-frontend/modules/database/viewTypes.js    | 27 +++---
 26 files changed, 371 insertions(+), 247 deletions(-)
 create mode 100644 web-frontend/modules/core/registry.js

diff --git a/backend/src/baserow/contrib/database/application_types.py b/backend/src/baserow/contrib/database/application_types.py
index d64d774ba..479c50433 100644
--- a/backend/src/baserow/contrib/database/application_types.py
+++ b/backend/src/baserow/contrib/database/application_types.py
@@ -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()
 
diff --git a/backend/src/baserow/contrib/database/fields/field_types.py b/backend/src/baserow/contrib/database/fields/field_types.py
index 38141a115..d5131f46c 100644
--- a/backend/src/baserow/contrib/database/fields/field_types.py
+++ b/backend/src/baserow/contrib/database/fields/field_types.py
@@ -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)
diff --git a/backend/tests/baserow/contrib/database/api/v0/rows/test_row_serializers.py b/backend/tests/baserow/contrib/database/api/v0/rows/test_row_serializers.py
index a5db71f83..6c970332f 100644
--- a/backend/tests/baserow/contrib/database/api/v0/rows/test_row_serializers.py
+++ b/backend/tests/baserow/contrib/database/api/v0/rows/test_row_serializers.py
@@ -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
diff --git a/backend/tests/baserow/contrib/database/api/v0/rows/test_row_views.py b/backend/tests/baserow/contrib/database/api/v0/rows/test_row_views.py
index 996e459d8..ebf937c0a 100644
--- a/backend/tests/baserow/contrib/database/api/v0/rows/test_row_views.py
+++ b/backend/tests/baserow/contrib/database/api/v0/rows/test_row_views.py
@@ -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(
diff --git a/backend/tests/baserow/contrib/database/api/v0/tables/test_table_views.py b/backend/tests/baserow/contrib/database/api/v0/tables/test_table_views.py
index 631dfb1ad..83621ffef 100644
--- a/backend/tests/baserow/contrib/database/api/v0/tables/test_table_views.py
+++ b/backend/tests/baserow/contrib/database/api/v0/tables/test_table_views.py
@@ -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()
diff --git a/backend/tests/baserow/contrib/database/table/test_table_models.py b/backend/tests/baserow/contrib/database/table/test_table_models.py
index dcd81127c..a3247aebf 100644
--- a/backend/tests/baserow/contrib/database/table/test_table_models.py
+++ b/backend/tests/baserow/contrib/database/table/test_table_models.py
@@ -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)
diff --git a/backend/tests/baserow/user/test_user_handler.py b/backend/tests/baserow/user/test_user_handler.py
index ae798a868..c8661f6e5 100644
--- a/backend/tests/baserow/user/test_user_handler.py
+++ b/backend/tests/baserow/user/test_user_handler.py
@@ -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')
diff --git a/web-frontend/modules/core/applicationTypes.js b/web-frontend/modules/core/applicationTypes.js
index 632685f79..ce7d152ce 100644
--- a/web-frontend/modules/core/applicationTypes.js
+++ b/web-frontend/modules/core/applicationTypes.js
@@ -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()
diff --git a/web-frontend/modules/core/components/application/CreateApplicationContext.vue b/web-frontend/modules/core/components/application/CreateApplicationContext.vue
index 70705456c..e3351d7f9 100644
--- a/web-frontend/modules/core/components/application/CreateApplicationContext.vue
+++ b/web-frontend/modules/core/components/application/CreateApplicationContext.vue
@@ -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) {
diff --git a/web-frontend/modules/core/components/group/DashboardGroup.vue b/web-frontend/modules/core/components/group/DashboardGroup.vue
index f15af26df..75ab50dd8 100644
--- a/web-frontend/modules/core/components/group/DashboardGroup.vue
+++ b/web-frontend/modules/core/components/group/DashboardGroup.vue
@@ -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)
     },
   },
diff --git a/web-frontend/modules/core/components/sidebar/SidebarApplication.vue b/web-frontend/modules/core/components/sidebar/SidebarApplication.vue
index 81125461a..7fb7e4b11 100644
--- a/web-frontend/modules/core/components/sidebar/SidebarApplication.vue
+++ b/web-frontend/modules/core/components/sidebar/SidebarApplication.vue
@@ -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()
     },
   },
diff --git a/web-frontend/modules/core/plugin.js b/web-frontend/modules/core/plugin.js
index 9ad5e8ceb..0e605a649 100644
--- a/web-frontend/modules/core/plugin.js
+++ b/web-frontend/modules/core/plugin.js
@@ -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)
diff --git a/web-frontend/modules/core/registry.js b/web-frontend/modules/core/registry.js
new file mode 100644
index 000000000..5616b2c0e
--- /dev/null
+++ b/web-frontend/modules/core/registry.js
@@ -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
+  }
+}
diff --git a/web-frontend/modules/core/store/application.js b/web-frontend/modules/core/store/application.js
index c0b963002..174d7ca5a 100644
--- a/web-frontend/modules/core/store/application.js
+++ b/web-frontend/modules/core/store/application.js
@@ -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 {
diff --git a/web-frontend/modules/core/store/group.js b/web-frontend/modules/core/store/group.js
index 5cda83c7f..2d3de3a30 100644
--- a/web-frontend/modules/core/store/group.js
+++ b/web-frontend/modules/core/store/group.js
@@ -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)
     })
 
diff --git a/web-frontend/modules/database/applicationTypes.js b/web-frontend/modules/database/applicationTypes.js
index b8b1ef2a2..09a1acc42 100644
--- a/web-frontend/modules/database/applicationTypes.js
+++ b/web-frontend/modules/database/applicationTypes.js
@@ -7,10 +7,6 @@ export class DatabaseApplicationType extends ApplicationType {
     return 'database'
   }
 
-  getType() {
-    return DatabaseApplicationType.getType()
-  }
-
   getIconClass() {
     return 'database'
   }
diff --git a/web-frontend/modules/database/components/field/FieldForm.vue b/web-frontend/modules/database/components/field/FieldForm.vue
index 2044e6797..c357e8512 100644
--- a/web-frontend/modules/database/components/field/FieldForm.vue
+++ b/web-frontend/modules/database/components/field/FieldForm.vue
@@ -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()
     },
   },
 }
diff --git a/web-frontend/modules/database/components/view/ViewsContext.vue b/web-frontend/modules/database/components/view/ViewsContext.vue
index f913ca53d..5e4620c64 100644
--- a/web-frontend/modules/database/components/view/ViewsContext.vue
+++ b/web-frontend/modules/database/components/view/ViewsContext.vue
@@ -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,
     }),
   },
diff --git a/web-frontend/modules/database/components/view/grid/GridViewField.vue b/web-frontend/modules/database/components/view/grid/GridViewField.vue
index 1fada7914..ef44ce6de 100644
--- a/web-frontend/modules/database/components/view/grid/GridViewField.vue
+++ b/web-frontend/modules/database/components/view/grid/GridViewField.vue
@@ -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
diff --git a/web-frontend/modules/database/fieldTypes.js b/web-frontend/modules/database/fieldTypes.js
index e0398544a..76534f361 100644
--- a/web-frontend/modules/database/fieldTypes.js
+++ b/web-frontend/modules/database/fieldTypes.js
@@ -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'
   }
diff --git a/web-frontend/modules/database/pages/table.vue b/web-frontend/modules/database/pages/table.vue
index 7415cef1f..af7613118 100644
--- a/web-frontend/modules/database/pages/table.vue
+++ b/web-frontend/modules/database/pages/table.vue
@@ -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()
     },
   },
diff --git a/web-frontend/modules/database/plugin.js b/web-frontend/modules/database/plugin.js
index da1771939..e1097b334 100644
--- a/web-frontend/modules/database/plugin.js
+++ b/web-frontend/modules/database/plugin.js
@@ -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())
 }
diff --git a/web-frontend/modules/database/store/field.js b/web-frontend/modules/database/store/field.js
index 592c2bd12..c45c943eb 100644
--- a/web-frontend/modules/database/store/field.js
+++ b/web-frontend/modules/database/store/field.js
@@ -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 {
diff --git a/web-frontend/modules/database/store/view.js b/web-frontend/modules/database/store/view.js
index 650dc0472..8f24da562 100644
--- a/web-frontend/modules/database/store/view.js
+++ b/web-frontend/modules/database/store/view.js
@@ -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 {
diff --git a/web-frontend/modules/database/store/view/grid.js b/web-frontend/modules/database/store/view/grid.js
index 52b05a8ff..27011c719 100644
--- a/web-frontend/modules/database/store/view/grid.js
+++ b/web-frontend/modules/database/store/view/grid.js
@@ -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 = {
diff --git a/web-frontend/modules/database/viewTypes.js b/web-frontend/modules/database/viewTypes.js
index c27468644..598c16320 100644
--- a/web-frontend/modules/database/viewTypes.js
+++ b/web-frontend/modules/database/viewTypes.js
@@ -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 })
+  }
 }