mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-04-14 00:59:06 +00:00
made it possible to create, update and delete tables, but also the select a table and navigate to the route
This commit is contained in:
parent
3b2eb13042
commit
497447edb8
45 changed files with 1226 additions and 150 deletions
backend
src/baserow
tests
baserow/contrib/database
fixtures
web-frontend
components/sidebar
core
mixins
modules/database
pages/app
store
|
@ -1,3 +1,5 @@
|
||||||
|
from django.utils.functional import lazy
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from baserow.api.v0.groups.serializers import GroupSerializer
|
from baserow.api.v0.groups.serializers import GroupSerializer
|
||||||
|
@ -19,12 +21,17 @@ class ApplicationSerializer(serializers.ModelSerializer):
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_type(self, instance):
|
def get_type(self, instance):
|
||||||
application = registry.get_by_model(instance.specific_class)
|
# It could be that the application related to the instance is already in the
|
||||||
|
# context else we can call the specific_class property to find it.
|
||||||
|
application = self.context.get('application')
|
||||||
|
if not application:
|
||||||
|
application = registry.get_by_model(instance.specific_class)
|
||||||
|
|
||||||
return application.type
|
return application.type
|
||||||
|
|
||||||
|
|
||||||
class ApplicationCreateSerializer(serializers.ModelSerializer):
|
class ApplicationCreateSerializer(serializers.ModelSerializer):
|
||||||
type = serializers.ChoiceField(choices=registry.get_types())
|
type = serializers.ChoiceField(choices=lazy(registry.get_types, list)())
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Application
|
model = Application
|
||||||
|
@ -35,3 +42,23 @@ class ApplicationUpdateSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Application
|
model = Application
|
||||||
fields = ('name',)
|
fields = ('name',)
|
||||||
|
|
||||||
|
|
||||||
|
def get_application_serializer(instance, **kwargs):
|
||||||
|
"""
|
||||||
|
Returns an instantiated serialized based on the instance class type. Custom
|
||||||
|
serializers can be defined per application. This function will return that one is
|
||||||
|
set else it will return the default one.
|
||||||
|
|
||||||
|
:param instance: The instance where a serializer is needed for.
|
||||||
|
:type instance: Application
|
||||||
|
:return: An instantiated serializer for the instance.
|
||||||
|
:rtype: ApplicationSerializer
|
||||||
|
"""
|
||||||
|
application = registry.get_by_model(instance.specific_class)
|
||||||
|
serializer_class = application.instance_serializer
|
||||||
|
|
||||||
|
if not serializer_class:
|
||||||
|
serializer_class = ApplicationSerializer
|
||||||
|
|
||||||
|
return serializer_class(instance, context={'application': application}, **kwargs)
|
||||||
|
|
|
@ -6,21 +6,22 @@ from rest_framework.response import Response
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
|
||||||
from baserow.api.v0.decorators import validate_body, map_exceptions
|
from baserow.api.v0.decorators import validate_body, map_exceptions
|
||||||
|
from baserow.api.v0.errors import ERROR_USER_NOT_IN_GROUP
|
||||||
from baserow.core.models import GroupUser, Application
|
from baserow.core.models import GroupUser, Application
|
||||||
from baserow.core.handler import CoreHandler
|
from baserow.core.handler import CoreHandler
|
||||||
from baserow.core.exceptions import UserNotIngroupError
|
from baserow.core.exceptions import UserNotIngroupError
|
||||||
|
|
||||||
from .serializers import (
|
from .serializers import (
|
||||||
ApplicationSerializer, ApplicationCreateSerializer, ApplicationUpdateSerializer
|
ApplicationCreateSerializer, ApplicationUpdateSerializer, get_application_serializer
|
||||||
)
|
)
|
||||||
from .errors import ERROR_USER_NOT_IN_GROUP
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationsView(APIView):
|
class ApplicationsView(APIView):
|
||||||
permission_classes = (IsAuthenticated,)
|
permission_classes = (IsAuthenticated,)
|
||||||
core_handler = CoreHandler()
|
core_handler = CoreHandler()
|
||||||
|
|
||||||
def load_group(self, request, group_id):
|
@staticmethod
|
||||||
|
def get_group(request, group_id):
|
||||||
return get_object_or_404(
|
return get_object_or_404(
|
||||||
GroupUser.objects.select_related('group'),
|
GroupUser.objects.select_related('group'),
|
||||||
group_id=group_id,
|
group_id=group_id,
|
||||||
|
@ -29,27 +30,30 @@ class ApplicationsView(APIView):
|
||||||
|
|
||||||
def get(self, request, group_id):
|
def get(self, request, group_id):
|
||||||
"""
|
"""
|
||||||
Responds with a list of applications that belong to the group if the user has
|
Responds with a list of serialized applications that belong to the group if the
|
||||||
access to that group.
|
user has access to that group.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
group_user = self.load_group(request, group_id)
|
group_user = self.get_group(request, group_id)
|
||||||
applications = Application.objects.filter(
|
applications = Application.objects.filter(
|
||||||
group=group_user.group
|
group=group_user.group
|
||||||
).select_related('content_type')
|
).select_related('content_type')
|
||||||
serializer = ApplicationSerializer(applications, many=True)
|
data = [
|
||||||
return Response(serializer.data)
|
get_application_serializer(application).data
|
||||||
|
for application in applications
|
||||||
|
]
|
||||||
|
return Response(data)
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
@validate_body(ApplicationCreateSerializer)
|
@validate_body(ApplicationCreateSerializer)
|
||||||
def post(self, request, data, group_id):
|
def post(self, request, data, group_id):
|
||||||
"""Creates a new application for a user."""
|
"""Creates a new application for a user."""
|
||||||
|
|
||||||
group_user = self.load_group(request, group_id)
|
group_user = self.get_group(request, group_id)
|
||||||
application = self.core_handler.create_application(
|
application = self.core_handler.create_application(
|
||||||
request.user, group_user.group, data['type'], name=data['name'])
|
request.user, group_user.group, data['type'], name=data['name'])
|
||||||
|
|
||||||
return Response(ApplicationSerializer(application).data)
|
return Response(get_application_serializer(application).data)
|
||||||
|
|
||||||
|
|
||||||
class ApplicationView(APIView):
|
class ApplicationView(APIView):
|
||||||
|
@ -63,7 +67,7 @@ class ApplicationView(APIView):
|
||||||
pk=application_id, group__users__in=[request.user]
|
pk=application_id, group__users__in=[request.user]
|
||||||
)
|
)
|
||||||
|
|
||||||
return Response(ApplicationSerializer(application).data)
|
return Response(get_application_serializer(application).data)
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
@validate_body(ApplicationUpdateSerializer)
|
@validate_body(ApplicationUpdateSerializer)
|
||||||
|
@ -80,7 +84,7 @@ class ApplicationView(APIView):
|
||||||
application = self.core_handler.update_application(
|
application = self.core_handler.update_application(
|
||||||
request.user, application, name=data['name'])
|
request.user, application, name=data['name'])
|
||||||
|
|
||||||
return Response(ApplicationSerializer(application).data)
|
return Response(get_application_serializer(application).data)
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
@map_exceptions({
|
@map_exceptions({
|
||||||
|
|
|
@ -40,7 +40,7 @@ def map_exceptions(exceptions):
|
||||||
except tuple(exceptions.keys()) as e:
|
except tuple(exceptions.keys()) as e:
|
||||||
value = exceptions.get(e.__class__)
|
value = exceptions.get(e.__class__)
|
||||||
status_code = status.HTTP_400_BAD_REQUEST
|
status_code = status.HTTP_400_BAD_REQUEST
|
||||||
detail = str(e)
|
detail = ''
|
||||||
|
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
error = value
|
error = value
|
||||||
|
|
0
backend/src/baserow/contrib/database/api/__init__.py
Normal file
0
backend/src/baserow/contrib/database/api/__init__.py
Normal file
0
backend/src/baserow/contrib/database/api/v0/__init__.py
Normal file
0
backend/src/baserow/contrib/database/api/v0/__init__.py
Normal file
26
backend/src/baserow/contrib/database/api/v0/serializers.py
Normal file
26
backend/src/baserow/contrib/database/api/v0/serializers.py
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from baserow.api.v0.applications.serializers import ApplicationSerializer
|
||||||
|
from baserow.contrib.database.table.models import Table
|
||||||
|
from baserow.contrib.database.api.v0.tables.serializers import TableSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class DatabaseSerializer(ApplicationSerializer):
|
||||||
|
tables = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
class Meta(ApplicationSerializer.Meta):
|
||||||
|
fields = ApplicationSerializer.Meta.fields + ('tables',)
|
||||||
|
|
||||||
|
def get_tables(self, instance):
|
||||||
|
"""
|
||||||
|
Because the the instance doesn't know at this point it is a Database we have to
|
||||||
|
select the related tables this way.
|
||||||
|
|
||||||
|
:param instance: The database application instance.
|
||||||
|
:type instance: Application
|
||||||
|
:return: A list of serialized tables that belong to this instance.
|
||||||
|
:rtype: list
|
||||||
|
"""
|
||||||
|
|
||||||
|
tables = Table.objects.filter(database_id=instance.pk)
|
||||||
|
return TableSerializer(tables, many=True).data
|
|
@ -0,0 +1,20 @@
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from baserow.contrib.database.table.models import Table
|
||||||
|
|
||||||
|
|
||||||
|
class TableSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Table
|
||||||
|
fields = ('id', 'name', 'order',)
|
||||||
|
extra_kwargs = {
|
||||||
|
'id': {
|
||||||
|
'read_only': True
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TableCreateUpdateSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Table
|
||||||
|
fields = ('name',)
|
11
backend/src/baserow/contrib/database/api/v0/tables/urls.py
Normal file
11
backend/src/baserow/contrib/database/api/v0/tables/urls.py
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
from django.conf.urls import url
|
||||||
|
|
||||||
|
from .views import TablesView, TableView
|
||||||
|
|
||||||
|
|
||||||
|
app_name = 'baserow.contrib.api.v0.tables'
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
url(r'database/(?P<database_id>[0-9]+)/$', TablesView.as_view(), name='list'),
|
||||||
|
url(r'(?P<table_id>[0-9]+)/$', TableView.as_view(), name='item'),
|
||||||
|
]
|
115
backend/src/baserow/contrib/database/api/v0/tables/views.py
Normal file
115
backend/src/baserow/contrib/database/api/v0/tables/views.py
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
from django.db import transaction
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
|
||||||
|
from baserow.api.v0.decorators import validate_body, map_exceptions
|
||||||
|
from baserow.api.v0.errors import ERROR_USER_NOT_IN_GROUP
|
||||||
|
from baserow.core.exceptions import UserNotIngroupError
|
||||||
|
from baserow.contrib.database.models import Database
|
||||||
|
from baserow.contrib.database.table.models import Table
|
||||||
|
from baserow.contrib.database.table.handler import TableHandler
|
||||||
|
|
||||||
|
from .serializers import TableSerializer, TableCreateUpdateSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class TablesView(APIView):
|
||||||
|
permission_classes = (IsAuthenticated,)
|
||||||
|
table_handler = TableHandler()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_database(user, database_id):
|
||||||
|
database = get_object_or_404(
|
||||||
|
Database.objects.select_related('group'),
|
||||||
|
pk=database_id
|
||||||
|
)
|
||||||
|
|
||||||
|
if not database.group.has_user(user):
|
||||||
|
raise UserNotIngroupError(f'User {user} doesn\'t belong to group '
|
||||||
|
f'{database.group}.')
|
||||||
|
|
||||||
|
return database
|
||||||
|
|
||||||
|
@map_exceptions({
|
||||||
|
UserNotIngroupError: ERROR_USER_NOT_IN_GROUP
|
||||||
|
})
|
||||||
|
def get(self, request, database_id):
|
||||||
|
"""Lists all the tables of a database."""
|
||||||
|
|
||||||
|
database = self.get_database(request.user, database_id)
|
||||||
|
tables = Table.objects.filter(database=database)
|
||||||
|
serializer = TableSerializer(tables, many=True)
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
@map_exceptions({
|
||||||
|
UserNotIngroupError: ERROR_USER_NOT_IN_GROUP
|
||||||
|
})
|
||||||
|
@validate_body(TableCreateUpdateSerializer)
|
||||||
|
def post(self, request, data, database_id):
|
||||||
|
"""Creates a new table in a database."""
|
||||||
|
|
||||||
|
database = self.get_database(request.user, database_id)
|
||||||
|
table = self.table_handler.create_table(
|
||||||
|
request.user, database, name=data['name'])
|
||||||
|
serializer = TableSerializer(table)
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
|
||||||
|
class TableView(APIView):
|
||||||
|
permission_classes = (IsAuthenticated,)
|
||||||
|
table_handler = TableHandler()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_table(user, table_id):
|
||||||
|
table = get_object_or_404(
|
||||||
|
Table.objects.select_related('database__group'),
|
||||||
|
pk=table_id
|
||||||
|
)
|
||||||
|
|
||||||
|
if not table.database.group.has_user(user):
|
||||||
|
raise UserNotIngroupError(f'User {user} doesn\'t belong to group '
|
||||||
|
f'{table.database.group}.')
|
||||||
|
|
||||||
|
return table
|
||||||
|
|
||||||
|
@map_exceptions({
|
||||||
|
UserNotIngroupError: ERROR_USER_NOT_IN_GROUP
|
||||||
|
})
|
||||||
|
def get(self, request, table_id):
|
||||||
|
"""Responds with a serialized table instance."""
|
||||||
|
|
||||||
|
table = self.get_table(request.user, table_id)
|
||||||
|
serializer = TableSerializer(table)
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
@map_exceptions({
|
||||||
|
UserNotIngroupError: ERROR_USER_NOT_IN_GROUP
|
||||||
|
})
|
||||||
|
@validate_body(TableCreateUpdateSerializer)
|
||||||
|
def patch(self, request, data, table_id):
|
||||||
|
"""Updates the values a table instance."""
|
||||||
|
|
||||||
|
table = self.table_handler.update_table(
|
||||||
|
request.user,
|
||||||
|
self.get_table(request.user, table_id),
|
||||||
|
name=data['name']
|
||||||
|
)
|
||||||
|
serializer = TableSerializer(table)
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
@map_exceptions({
|
||||||
|
UserNotIngroupError: ERROR_USER_NOT_IN_GROUP
|
||||||
|
})
|
||||||
|
def delete(self, request, table_id):
|
||||||
|
"""Deletes an existing table."""
|
||||||
|
|
||||||
|
self.table_handler.delete_table(
|
||||||
|
request.user,
|
||||||
|
self.get_table(request.user, table_id)
|
||||||
|
)
|
||||||
|
return Response(status=204)
|
|
@ -1,5 +1,9 @@
|
||||||
|
from django.urls import path, include
|
||||||
|
|
||||||
|
from .api.v0.tables import urls as table_urls
|
||||||
|
|
||||||
app_name = 'baserow.contrib.database'
|
app_name = 'baserow.contrib.database'
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
path('tables/', include(table_urls, namespace='tables')),
|
||||||
]
|
]
|
||||||
|
|
|
@ -4,11 +4,13 @@ from baserow.core.applications import Application
|
||||||
|
|
||||||
from . import api_urls
|
from . import api_urls
|
||||||
from .models import Database
|
from .models import Database
|
||||||
|
from .api.v0.serializers import DatabaseSerializer
|
||||||
|
|
||||||
|
|
||||||
class DatabaseApplication(Application):
|
class DatabaseApplication(Application):
|
||||||
type = 'database'
|
type = 'database'
|
||||||
instance_model = Database
|
instance_model = Database
|
||||||
|
instance_serializer = DatabaseSerializer
|
||||||
|
|
||||||
def get_api_urls(self):
|
def get_api_urls(self):
|
||||||
return [
|
return [
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Generated by Django 2.2.2 on 2019-10-18 07:40
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('database', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='table',
|
||||||
|
old_name='group',
|
||||||
|
new_name='database',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='table',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(default='', max_length=255),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,12 +1,11 @@
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
from baserow.core.models import Application
|
from baserow.core.models import Application
|
||||||
|
|
||||||
|
from .table.models import Table
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'Table'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class Database(Application):
|
class Database(Application):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Table(models.Model):
|
|
||||||
group = models.ForeignKey(Database, on_delete=models.CASCADE)
|
|
||||||
order = models.PositiveIntegerField()
|
|
||||||
|
|
0
backend/src/baserow/contrib/database/table/__init__.py
Normal file
0
backend/src/baserow/contrib/database/table/__init__.py
Normal file
76
backend/src/baserow/contrib/database/table/handler.py
Normal file
76
backend/src/baserow/contrib/database/table/handler.py
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
from baserow.core.exceptions import UserNotIngroupError
|
||||||
|
from baserow.core.utils import extract_allowed, set_allowed_attrs
|
||||||
|
|
||||||
|
from .models import Table
|
||||||
|
|
||||||
|
|
||||||
|
class TableHandler:
|
||||||
|
def create_table(self, user, database, **kwargs):
|
||||||
|
"""
|
||||||
|
Creates a new table.
|
||||||
|
|
||||||
|
:param user: The user on whose behalf the table is created.
|
||||||
|
:type user: User
|
||||||
|
:param database: The database that the table instance belongs to.
|
||||||
|
:type database: Database
|
||||||
|
:param kwargs: The fields that need to be set upon creation.
|
||||||
|
:type kwargs: object
|
||||||
|
:return: The created table instance.
|
||||||
|
:rtype: Table
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not database.group.has_user(user):
|
||||||
|
raise UserNotIngroupError(f'The user {user} does not belong to the group '
|
||||||
|
f'{database.group}.')
|
||||||
|
|
||||||
|
table_values = extract_allowed(kwargs, ['name'])
|
||||||
|
last_order = Table.get_last_order(database)
|
||||||
|
table = Table.objects.create(database=database, order=last_order,
|
||||||
|
**table_values)
|
||||||
|
|
||||||
|
return table
|
||||||
|
|
||||||
|
def update_table(self, user, table, **kwargs):
|
||||||
|
"""
|
||||||
|
Updates an existing table instance.
|
||||||
|
|
||||||
|
:param user: The user on whose behalf the table is updated.
|
||||||
|
:type user: User
|
||||||
|
:param table: The table instance that needs to be updated.
|
||||||
|
:type table: Table
|
||||||
|
:param kwargs: The fields that need to be updated.
|
||||||
|
:type kwargs: object
|
||||||
|
:return: The updated table instance.
|
||||||
|
:rtype: Table
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not isinstance(table, Table):
|
||||||
|
raise ValueError('The table is not an instance of Table')
|
||||||
|
|
||||||
|
if not table.database.group.has_user(user):
|
||||||
|
raise UserNotIngroupError(f'The user {user} does not belong to the group '
|
||||||
|
f'{table.database.group}.')
|
||||||
|
|
||||||
|
table = set_allowed_attrs(kwargs, ['name'], table)
|
||||||
|
table.save()
|
||||||
|
|
||||||
|
return table
|
||||||
|
|
||||||
|
def delete_table(self, user, table):
|
||||||
|
"""
|
||||||
|
Deletes an existing table instance.
|
||||||
|
|
||||||
|
:param user: The user on whose behalf the table is deleted.
|
||||||
|
:type user: User
|
||||||
|
:param table: The table instance that needs to be deleted.
|
||||||
|
:type table: Table
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not isinstance(table, Table):
|
||||||
|
raise ValueError('The table is not an instance of Table')
|
||||||
|
|
||||||
|
if not table.database.group.has_user(user):
|
||||||
|
raise UserNotIngroupError(f'The user {user} does not belong to the group '
|
||||||
|
f'{table.database.group}.')
|
||||||
|
|
||||||
|
table.delete()
|
17
backend/src/baserow/contrib/database/table/models.py
Normal file
17
backend/src/baserow/contrib/database/table/models.py
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
from baserow.core.mixins import OrderableMixin
|
||||||
|
|
||||||
|
|
||||||
|
class Table(OrderableMixin, models.Model):
|
||||||
|
database = models.ForeignKey('database.Database', on_delete=models.CASCADE)
|
||||||
|
order = models.PositiveIntegerField()
|
||||||
|
name = models.CharField(max_length=255)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ('order',)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_last_order(cls, database):
|
||||||
|
queryset = Table.objects.filter(database=database)
|
||||||
|
return cls.get_highest_order_of_queryset(queryset) + 1
|
|
@ -27,7 +27,13 @@ class Application(object):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
type = None
|
type = None
|
||||||
|
"""A unique string that identifies the application."""
|
||||||
|
|
||||||
instance_model = None
|
instance_model = None
|
||||||
|
"""The model instance that is created when adding an application."""
|
||||||
|
|
||||||
|
instance_serializer = None
|
||||||
|
"""This serializer that is used to serialize the instance model."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
if not self.type:
|
if not self.type:
|
||||||
|
@ -92,7 +98,8 @@ class ApplicationRegistry(object):
|
||||||
return self.registry[type]
|
return self.registry[type]
|
||||||
|
|
||||||
def get_by_model(self, instance):
|
def get_by_model(self, instance):
|
||||||
"""Returns the application instance of a model or model instance.
|
"""
|
||||||
|
Returns the application instance of a model or model instance.
|
||||||
|
|
||||||
:param instance: The modal that must be the applications model_instance.
|
:param instance: The modal that must be the applications model_instance.
|
||||||
:type instance: Model or an instance of model.
|
:type instance: Model or an instance of model.
|
||||||
|
|
|
@ -106,11 +106,10 @@ class CoreHandler:
|
||||||
application = registry.get(type)
|
application = registry.get(type)
|
||||||
model = application.instance_model
|
model = application.instance_model
|
||||||
application_values = extract_allowed(kwargs, ['name'])
|
application_values = extract_allowed(kwargs, ['name'])
|
||||||
|
last_order = model.get_last_order(group)
|
||||||
|
|
||||||
if 'order' not in application_values:
|
instance = model.objects.create(group=group, order=last_order,
|
||||||
application_values['order'] = model.get_last_order(group)
|
**application_values)
|
||||||
|
|
||||||
instance = model.objects.create(group=group, **application_values)
|
|
||||||
|
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,8 @@ class GroupUser(OrderableMixin, models.Model):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_last_order(cls, user):
|
def get_last_order(cls, user):
|
||||||
return cls.get_highest_order_of_queryset(cls.objects.filter(user=user)) + 1
|
queryset = cls.objects.filter(user=user)
|
||||||
|
return cls.get_highest_order_of_queryset(queryset) + 1
|
||||||
|
|
||||||
|
|
||||||
class Application(OrderableMixin, models.Model):
|
class Application(OrderableMixin, models.Model):
|
||||||
|
@ -88,5 +89,5 @@ class Application(OrderableMixin, models.Model):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_last_order(cls, group):
|
def get_last_order(cls, group):
|
||||||
return cls.get_highest_order_of_queryset(
|
queryset = Application.objects.filter(group=group)
|
||||||
Application.objects.filter(group=group)) + 1
|
return cls.get_highest_order_of_queryset(queryset) + 1
|
||||||
|
|
|
@ -0,0 +1,210 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from django.shortcuts import reverse
|
||||||
|
|
||||||
|
from baserow.contrib.database.table.models import Table
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_list_tables(api_client, data_fixture):
|
||||||
|
user, token = data_fixture.create_user_and_token()
|
||||||
|
database = data_fixture.create_database_application(user=user)
|
||||||
|
database_2 = data_fixture.create_database_application()
|
||||||
|
table_1 = data_fixture.create_database_table(database=database, order=2)
|
||||||
|
table_2 = data_fixture.create_database_table(database=database, order=1)
|
||||||
|
table_3 = data_fixture.create_database_table(database=database_2)
|
||||||
|
|
||||||
|
url = reverse('api_v0:database:tables:list', kwargs={'database_id': database.id})
|
||||||
|
response = api_client.get(url, HTTP_AUTHORIZATION=f'JWT {token}')
|
||||||
|
response_json = response.json()
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert len(response_json) == 2
|
||||||
|
assert response_json[0]['id'] == table_2.id
|
||||||
|
assert response_json[1]['id'] == table_1.id
|
||||||
|
|
||||||
|
url = reverse('api_v0:database:tables:list', kwargs={'database_id': database_2.id})
|
||||||
|
response = api_client.get(url, HTTP_AUTHORIZATION=f'JWT {token}')
|
||||||
|
response_json = response.json()
|
||||||
|
assert response.status_code == 400
|
||||||
|
assert response_json['error'] == 'ERROR_USER_NOT_IN_GROUP'
|
||||||
|
|
||||||
|
url = reverse('api_v0:database:tables:list', kwargs={'database_id': 9999})
|
||||||
|
response = api_client.get(url, **{
|
||||||
|
'HTTP_AUTHORIZATION': f'JWT {token}'
|
||||||
|
})
|
||||||
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_create_table(api_client, data_fixture):
|
||||||
|
user, token = data_fixture.create_user_and_token()
|
||||||
|
database = data_fixture.create_database_application(user=user)
|
||||||
|
database_2 = data_fixture.create_database_application()
|
||||||
|
|
||||||
|
url = reverse('api_v0:database:tables:list', kwargs={'database_id': database.id})
|
||||||
|
response = api_client.post(
|
||||||
|
url,
|
||||||
|
{'name': 'Test 1'},
|
||||||
|
format='json',
|
||||||
|
HTTP_AUTHORIZATION=f'JWT {token}'
|
||||||
|
)
|
||||||
|
assert response.status_code == 200
|
||||||
|
json_response = response.json()
|
||||||
|
|
||||||
|
Table.objects.all().count() == 1
|
||||||
|
table = Table.objects.filter(database=database).first()
|
||||||
|
|
||||||
|
assert table.order == json_response['order'] == 1
|
||||||
|
assert table.name == json_response['name']
|
||||||
|
assert table.id == json_response['id']
|
||||||
|
|
||||||
|
url = reverse('api_v0:database:tables:list', kwargs={'database_id': database_2.id})
|
||||||
|
response = api_client.post(
|
||||||
|
url,
|
||||||
|
{'name': 'Test 1'},
|
||||||
|
format='json',
|
||||||
|
HTTP_AUTHORIZATION=f'JWT {token}'
|
||||||
|
)
|
||||||
|
assert response.status_code == 400
|
||||||
|
assert response.json()['error'] == 'ERROR_USER_NOT_IN_GROUP'
|
||||||
|
|
||||||
|
url = reverse('api_v0:database:tables:list', kwargs={'database_id': database.id})
|
||||||
|
response = api_client.post(
|
||||||
|
url,
|
||||||
|
{'not_a_name': 'Test 1'},
|
||||||
|
format='json',
|
||||||
|
HTTP_AUTHORIZATION=f'JWT {token}'
|
||||||
|
)
|
||||||
|
assert response.status_code == 400
|
||||||
|
assert response.json()['error'] == 'ERROR_REQUEST_BODY_VALIDATION'
|
||||||
|
|
||||||
|
url = reverse('api_v0:database:tables:list', kwargs={'database_id': 9999})
|
||||||
|
response = api_client.post(
|
||||||
|
url,
|
||||||
|
{'name': 'Test 1'},
|
||||||
|
format='json',
|
||||||
|
HTTP_AUTHORIZATION=f'JWT {token}'
|
||||||
|
)
|
||||||
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_get_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()
|
||||||
|
|
||||||
|
url = reverse('api_v0:database:tables:item', kwargs={'table_id': table_1.id})
|
||||||
|
response = api_client.get(url, HTTP_AUTHORIZATION=f'JWT {token}')
|
||||||
|
assert response.status_code == 200
|
||||||
|
json_response = response.json()
|
||||||
|
assert json_response['id'] == table_1.id
|
||||||
|
assert json_response['name'] == table_1.name
|
||||||
|
assert json_response['order'] == table_1.order
|
||||||
|
|
||||||
|
url = reverse('api_v0:database:tables:item', kwargs={'table_id': table_2.id})
|
||||||
|
response = api_client.get(url, HTTP_AUTHORIZATION=f'JWT {token}')
|
||||||
|
json_response = response.json()
|
||||||
|
assert response.status_code == 400
|
||||||
|
assert json_response['error'] == 'ERROR_USER_NOT_IN_GROUP'
|
||||||
|
|
||||||
|
url = reverse('api_v0:database:tables:item', kwargs={'table_id': 9999})
|
||||||
|
response = api_client.get(url, HTTP_AUTHORIZATION=f'JWT {token}')
|
||||||
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_update_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()
|
||||||
|
|
||||||
|
url = reverse('api_v0:database:tables:item', kwargs={'table_id': table_1.id})
|
||||||
|
response = api_client.patch(
|
||||||
|
url,
|
||||||
|
{'name': 'New name'},
|
||||||
|
format='json',
|
||||||
|
HTTP_AUTHORIZATION=f'JWT {token}'
|
||||||
|
)
|
||||||
|
assert response.status_code == 200
|
||||||
|
response_json = response.json()
|
||||||
|
|
||||||
|
table_1.refresh_from_db()
|
||||||
|
|
||||||
|
assert response_json['id'] == table_1.id
|
||||||
|
assert response_json['name'] == table_1.name == 'New name'
|
||||||
|
|
||||||
|
url = reverse('api_v0:database:tables:item', kwargs={'table_id': table_2.id})
|
||||||
|
response = api_client.patch(
|
||||||
|
url,
|
||||||
|
{'name': 'New name'},
|
||||||
|
format='json',
|
||||||
|
HTTP_AUTHORIZATION=f'JWT {token}'
|
||||||
|
)
|
||||||
|
response_json = response.json()
|
||||||
|
assert response.status_code == 400
|
||||||
|
assert response_json['error'] == 'ERROR_USER_NOT_IN_GROUP'
|
||||||
|
|
||||||
|
url = reverse('api_v0:database:tables:item', kwargs={'table_id': table_1.id})
|
||||||
|
response = api_client.patch(
|
||||||
|
url,
|
||||||
|
{'not_a_name': 'New name'},
|
||||||
|
format='json',
|
||||||
|
HTTP_AUTHORIZATION=f'JWT {token}'
|
||||||
|
)
|
||||||
|
response_json = response.json()
|
||||||
|
assert response.status_code == 400
|
||||||
|
assert response_json['error'] == 'ERROR_REQUEST_BODY_VALIDATION'
|
||||||
|
|
||||||
|
url = reverse('api_v0:database:tables:item', kwargs={'table_id': 999})
|
||||||
|
response = api_client.patch(
|
||||||
|
url,
|
||||||
|
{'name': 'New name'},
|
||||||
|
format='json',
|
||||||
|
HTTP_AUTHORIZATION=f'JWT {token}'
|
||||||
|
)
|
||||||
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_delete_group(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()
|
||||||
|
|
||||||
|
assert Table.objects.all().count() == 2
|
||||||
|
url = reverse('api_v0:database:tables:item', kwargs={'table_id': table_1.id})
|
||||||
|
response = api_client.delete(url, HTTP_AUTHORIZATION=f'JWT {token}')
|
||||||
|
assert response.status_code == 204
|
||||||
|
assert Table.objects.all().count() == 1
|
||||||
|
|
||||||
|
url = reverse('api_v0:database:tables:item', kwargs={'table_id': table_2.id})
|
||||||
|
response = api_client.delete(url, HTTP_AUTHORIZATION=f'JWT {token}')
|
||||||
|
response_json = response.json()
|
||||||
|
assert response.status_code == 400
|
||||||
|
assert response_json['error'] == 'ERROR_USER_NOT_IN_GROUP'
|
||||||
|
|
||||||
|
url = reverse('api_v0:database:tables:item', kwargs={'table_id': 9999})
|
||||||
|
response = api_client.delete(url, HTTP_AUTHORIZATION=f'JWT {token}')
|
||||||
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_get_database_application_with_tables(api_client, data_fixture):
|
||||||
|
user, token = data_fixture.create_user_and_token()
|
||||||
|
database = data_fixture.create_database_application(user=user)
|
||||||
|
table_1 = data_fixture.create_database_table(database=database, order=0)
|
||||||
|
table_2 = data_fixture.create_database_table(database=database, order=1)
|
||||||
|
table_3 = data_fixture.create_database_table()
|
||||||
|
|
||||||
|
url = reverse('api_v0:applications:item', kwargs={'application_id': database.id})
|
||||||
|
response = api_client.get(
|
||||||
|
url,
|
||||||
|
format='json',
|
||||||
|
HTTP_AUTHORIZATION=f'JWT {token}'
|
||||||
|
)
|
||||||
|
response_json = response.json()
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert len(response_json['tables']) == 2
|
||||||
|
assert response_json['tables'][0]['id'] == table_1.id
|
||||||
|
assert response_json['tables'][1]['id'] == table_2.id
|
62
backend/tests/baserow/contrib/database/table/test_handler.py
Normal file
62
backend/tests/baserow/contrib/database/table/test_handler.py
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from baserow.core.exceptions import UserNotIngroupError
|
||||||
|
from baserow.contrib.database.table.models import Table
|
||||||
|
from baserow.contrib.database.table.handler import TableHandler
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_create_database_table(data_fixture):
|
||||||
|
user = data_fixture.create_user()
|
||||||
|
user_2 = data_fixture.create_user()
|
||||||
|
database = data_fixture.create_database_application(user=user)
|
||||||
|
|
||||||
|
handler = TableHandler()
|
||||||
|
handler.create_table(user=user, database=database, name='Test table')
|
||||||
|
|
||||||
|
assert Table.objects.all().count() == 1
|
||||||
|
|
||||||
|
table = Table.objects.all().first()
|
||||||
|
assert table.name == 'Test table'
|
||||||
|
assert table.order == 1
|
||||||
|
assert table.database == database
|
||||||
|
|
||||||
|
with pytest.raises(UserNotIngroupError):
|
||||||
|
handler.create_table(user=user_2, database=database, name='')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_update_database_table(data_fixture):
|
||||||
|
user = data_fixture.create_user()
|
||||||
|
user_2 = data_fixture.create_user()
|
||||||
|
group = data_fixture.create_group(user=user)
|
||||||
|
database = data_fixture.create_database_application(group=group)
|
||||||
|
table = data_fixture.create_database_table(database=database)
|
||||||
|
|
||||||
|
handler = TableHandler()
|
||||||
|
|
||||||
|
with pytest.raises(UserNotIngroupError):
|
||||||
|
handler.update_table(user=user_2, table=table, name='Test 1')
|
||||||
|
|
||||||
|
handler.update_table(user=user, table=table, name='Test 1')
|
||||||
|
|
||||||
|
table.refresh_from_db()
|
||||||
|
|
||||||
|
assert table.name == 'Test 1'
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_delete_database_application(data_fixture):
|
||||||
|
user = data_fixture.create_user()
|
||||||
|
user_2 = data_fixture.create_user()
|
||||||
|
group = data_fixture.create_group(user=user)
|
||||||
|
database = data_fixture.create_database_application(group=group)
|
||||||
|
table = data_fixture.create_database_table(database=database)
|
||||||
|
|
||||||
|
handler = TableHandler()
|
||||||
|
|
||||||
|
with pytest.raises(UserNotIngroupError):
|
||||||
|
handler.delete_table(user=user_2, table=table)
|
||||||
|
|
||||||
|
assert Table.objects.all().count() == 1
|
||||||
|
handler.delete_table(user=user, table=table)
|
||||||
|
assert Table.objects.all().count() == 0
|
15
backend/tests/baserow/contrib/database/table/test_models.py
Normal file
15
backend/tests/baserow/contrib/database/table/test_models.py
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from baserow.contrib.database.table.models import Table
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_group_user_get_next_order(data_fixture):
|
||||||
|
database = data_fixture.create_database_application()
|
||||||
|
database_2 = data_fixture.create_database_application()
|
||||||
|
data_fixture.create_database_table(order=1, database=database)
|
||||||
|
data_fixture.create_database_table(order=2, database=database)
|
||||||
|
data_fixture.create_database_table(order=10, database=database_2)
|
||||||
|
|
||||||
|
assert Table.get_last_order(database) == 3
|
||||||
|
assert Table.get_last_order(database_2) == 11
|
3
backend/tests/fixtures/__init__.py
vendored
3
backend/tests/fixtures/__init__.py
vendored
|
@ -3,7 +3,8 @@ from faker import Faker
|
||||||
from .user import UserFixtures
|
from .user import UserFixtures
|
||||||
from .group import GroupFixtures
|
from .group import GroupFixtures
|
||||||
from .application import ApplicationFixtures
|
from .application import ApplicationFixtures
|
||||||
|
from .table import TableFixtures
|
||||||
|
|
||||||
|
|
||||||
class Fixtures(UserFixtures, GroupFixtures, ApplicationFixtures):
|
class Fixtures(UserFixtures, GroupFixtures, ApplicationFixtures, TableFixtures):
|
||||||
fake = Faker()
|
fake = Faker()
|
||||||
|
|
4
backend/tests/fixtures/application.py
vendored
4
backend/tests/fixtures/application.py
vendored
|
@ -2,9 +2,9 @@ from baserow.contrib.database.models import Database
|
||||||
|
|
||||||
|
|
||||||
class ApplicationFixtures:
|
class ApplicationFixtures:
|
||||||
def create_database_application(self, **kwargs):
|
def create_database_application(self, user=None, **kwargs):
|
||||||
if 'group' not in kwargs:
|
if 'group' not in kwargs:
|
||||||
kwargs['group'] = self.create_group()
|
kwargs['group'] = self.create_group(user=user)
|
||||||
|
|
||||||
if 'name' not in kwargs:
|
if 'name' not in kwargs:
|
||||||
kwargs['name'] = self.fake.name()
|
kwargs['name'] = self.fake.name()
|
||||||
|
|
15
backend/tests/fixtures/table.py
vendored
Normal file
15
backend/tests/fixtures/table.py
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
from baserow.contrib.database.table.models import Table
|
||||||
|
|
||||||
|
|
||||||
|
class TableFixtures:
|
||||||
|
def create_database_table(self, user=None, **kwargs):
|
||||||
|
if 'database' not in kwargs:
|
||||||
|
kwargs['database'] = self.create_database_application(user=user)
|
||||||
|
|
||||||
|
if 'name' not in kwargs:
|
||||||
|
kwargs['name'] = self.fake.name()
|
||||||
|
|
||||||
|
if 'order' not in kwargs:
|
||||||
|
kwargs['order'] = 0
|
||||||
|
|
||||||
|
return Table.objects.create(**kwargs)
|
|
@ -95,6 +95,15 @@ export default {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
selectApplication(application) {
|
selectApplication(application) {
|
||||||
|
// If there is no route associated with the application we just change the
|
||||||
|
// selected state.
|
||||||
|
if (application._.type.routeName === null) {
|
||||||
|
this.$store.dispatch('application/select', application)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we do have a route, this the related component with that route has to do
|
||||||
|
// the state change.
|
||||||
this.setLoading(application, true)
|
this.setLoading(application, true)
|
||||||
|
|
||||||
this.$nuxt.$router.push(
|
this.$nuxt.$router.push(
|
||||||
|
|
|
@ -72,9 +72,6 @@ export class Application {
|
||||||
if (this.name === null) {
|
if (this.name === null) {
|
||||||
throw new Error('The name of an application must be set.')
|
throw new Error('The name of an application must be set.')
|
||||||
}
|
}
|
||||||
if (this.routeName === null) {
|
|
||||||
throw new Error('The route name of an application must be set.')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -89,4 +86,14 @@ export class Application {
|
||||||
hasSelectedSidebarComponent: this.getSelectedSidebarComponent() !== null
|
hasSelectedSidebarComponent: this.getSelectedSidebarComponent() !== null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Every time a fresh application object is fetched from the backend, it will
|
||||||
|
* be populated, this is the moment to update some values. Because each
|
||||||
|
* application can have unique properties, they might need to be populated.
|
||||||
|
* This method can be overwritten in order the populate the correct values.
|
||||||
|
*/
|
||||||
|
populate(application) {
|
||||||
|
return application
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
import { notify404 } from '@/utils/error'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This mixin can be used in combination with the component where the
|
|
||||||
* application routes to when selected. It will make sure that the application
|
|
||||||
* preSelect action is called so that the all the depending information is
|
|
||||||
* loaded. If something goes wrong while loading this information it will show a
|
|
||||||
* standard error.
|
|
||||||
*/
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
id: {
|
|
||||||
type: Number,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.$store.dispatch('application/preSelect', this.id).catch(error => {
|
|
||||||
notify404(
|
|
||||||
this.$store.dispatch,
|
|
||||||
error,
|
|
||||||
'Application not found.',
|
|
||||||
"The application with the provided id doesn't exist or you " +
|
|
||||||
"don't have access to it."
|
|
||||||
)
|
|
||||||
|
|
||||||
this.$nuxt.$router.push({ name: 'app' })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +1,16 @@
|
||||||
import { Application } from '@/core/applications'
|
import { Application } from '@/core/applications'
|
||||||
import Sidebar from '@/modules/database/components/Sidebar'
|
import Sidebar from '@/modules/database/components/Sidebar'
|
||||||
|
import { populateTable } from '@/modules/database/store/table'
|
||||||
|
|
||||||
export class DatabaseApplication extends Application {
|
export class DatabaseApplication extends Application {
|
||||||
getType() {
|
static getType() {
|
||||||
return 'database'
|
return 'database'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getType() {
|
||||||
|
return DatabaseApplication.getType()
|
||||||
|
}
|
||||||
|
|
||||||
getIconClass() {
|
getIconClass() {
|
||||||
return 'database'
|
return 'database'
|
||||||
}
|
}
|
||||||
|
@ -14,11 +19,13 @@ export class DatabaseApplication extends Application {
|
||||||
return 'Database'
|
return 'Database'
|
||||||
}
|
}
|
||||||
|
|
||||||
getRouteName() {
|
|
||||||
return 'application-database'
|
|
||||||
}
|
|
||||||
|
|
||||||
getSelectedSidebarComponent() {
|
getSelectedSidebarComponent() {
|
||||||
return Sidebar
|
return Sidebar
|
||||||
}
|
}
|
||||||
|
|
||||||
|
populate(application) {
|
||||||
|
const values = super.populate(application)
|
||||||
|
values.tables.forEach((object, index, tables) => populateTable(object))
|
||||||
|
return values
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,38 +1,36 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<ul class="tree-subs">
|
<ul class="tree-subs">
|
||||||
<li class="tree-sub active">
|
<SidebarItem
|
||||||
<a href="#" class="tree-sub-link">@TODO</a>
|
v-for="table in application.tables"
|
||||||
<a
|
:key="table.id"
|
||||||
class="tree-options"
|
:database="application"
|
||||||
@click="
|
:table="table"
|
||||||
$refs.context.toggle($event.currentTarget, 'bottom', 'right', 0)
|
></SidebarItem>
|
||||||
"
|
|
||||||
>
|
|
||||||
<i class="fas fa-ellipsis-v"></i>
|
|
||||||
</a>
|
|
||||||
<Context ref="context">
|
|
||||||
<div class="context-menu-title">@TODO</div>
|
|
||||||
<ul class="context-menu">
|
|
||||||
<li>
|
|
||||||
<a>
|
|
||||||
<i class="context-menu-icon fas fa-fw fa-pen"></i>
|
|
||||||
Rename
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a>
|
|
||||||
<i class="context-menu-icon fas fa-fw fa-trash"></i>
|
|
||||||
Delete
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</Context>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
<a href="#" class="tree-sub-add">
|
<a class="tree-sub-add" @click="$refs.createTableModal.show()">
|
||||||
<i class="fas fa-plus"></i>
|
<i class="fas fa-plus"></i>
|
||||||
Create table
|
Create table
|
||||||
</a>
|
</a>
|
||||||
|
<CreateTableModal
|
||||||
|
ref="createTableModal"
|
||||||
|
:application="application"
|
||||||
|
></CreateTableModal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import SidebarItem from '@/modules/database/components/table/SidebarItem'
|
||||||
|
import CreateTableModal from '@/modules/database/components/table/CreateTableModal'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Sidebar',
|
||||||
|
components: { SidebarItem, CreateTableModal },
|
||||||
|
props: {
|
||||||
|
application: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
<template>
|
||||||
|
<Modal>
|
||||||
|
<h2 class="box-title">Create new table</h2>
|
||||||
|
<TableForm ref="tableForm" @submitted="submitted">
|
||||||
|
<div class="actions">
|
||||||
|
<div class="align-right">
|
||||||
|
<button
|
||||||
|
class="button button-large"
|
||||||
|
:class="{ 'button-loading': loading }"
|
||||||
|
:disabled="loading"
|
||||||
|
>
|
||||||
|
Add table
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TableForm>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import TableForm from './TableForm'
|
||||||
|
|
||||||
|
import modal from '@/mixins/modal'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'CreateTableModal',
|
||||||
|
components: { TableForm },
|
||||||
|
mixins: [modal],
|
||||||
|
props: {
|
||||||
|
application: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
submitted(values) {
|
||||||
|
this.loading = true
|
||||||
|
this.$store
|
||||||
|
.dispatch('table/create', { database: this.application, values })
|
||||||
|
.then(() => {
|
||||||
|
this.loading = false
|
||||||
|
this.hide()
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.loading = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,99 @@
|
||||||
|
<template>
|
||||||
|
<li class="tree-sub" :class="{ active: table._.selected }">
|
||||||
|
<nuxt-link
|
||||||
|
:to="{
|
||||||
|
name: 'database-table',
|
||||||
|
params: {
|
||||||
|
id: database.id,
|
||||||
|
tableId: table.id
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
class="tree-sub-link"
|
||||||
|
>
|
||||||
|
<Editable
|
||||||
|
ref="rename"
|
||||||
|
:value="table.name"
|
||||||
|
@change="renameTable(database, table, $event)"
|
||||||
|
></Editable>
|
||||||
|
</nuxt-link>
|
||||||
|
<a
|
||||||
|
v-show="!database._.loading"
|
||||||
|
class="tree-options"
|
||||||
|
@click="$refs.context.toggle($event.currentTarget, 'bottom', 'right', 0)"
|
||||||
|
>
|
||||||
|
<i class="fas fa-ellipsis-v"></i>
|
||||||
|
</a>
|
||||||
|
<Context ref="context">
|
||||||
|
<div class="context-menu-title">{{ table.name }}</div>
|
||||||
|
<ul class="context-menu">
|
||||||
|
<li>
|
||||||
|
<a @click="enableRename()">
|
||||||
|
<i class="context-menu-icon fas fa-fw fa-pen"></i>
|
||||||
|
Rename
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a @click="deleteTable(database, table)">
|
||||||
|
<i class="context-menu-icon fas fa-fw fa-trash"></i>
|
||||||
|
Delete
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</Context>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'SidebarItem',
|
||||||
|
props: {
|
||||||
|
database: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
table: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
setLoading(database, value) {
|
||||||
|
this.$store.dispatch('application/setItemLoading', {
|
||||||
|
application: database,
|
||||||
|
value
|
||||||
|
})
|
||||||
|
},
|
||||||
|
deleteTable(database, table) {
|
||||||
|
this.$refs.context.hide()
|
||||||
|
this.setLoading(database, true)
|
||||||
|
|
||||||
|
this.$store.dispatch('table/delete', { database, table }).then(() => {
|
||||||
|
this.setLoading(database, false)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
enableRename() {
|
||||||
|
this.$refs.context.hide()
|
||||||
|
this.$refs.rename.edit()
|
||||||
|
},
|
||||||
|
renameTable(database, table, event) {
|
||||||
|
this.setLoading(database, true)
|
||||||
|
|
||||||
|
this.$store
|
||||||
|
.dispatch('table/update', {
|
||||||
|
database,
|
||||||
|
table,
|
||||||
|
values: {
|
||||||
|
name: event.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// If something is going wrong we will reset the original value.
|
||||||
|
this.$refs.rename.set(event.oldValue)
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
this.setLoading(database, false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
50
web-frontend/modules/database/components/table/TableForm.vue
Normal file
50
web-frontend/modules/database/components/table/TableForm.vue
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
<template>
|
||||||
|
<form @submit.prevent="submit">
|
||||||
|
<div class="control">
|
||||||
|
<label class="control-label">
|
||||||
|
<i class="fas fa-font"></i>
|
||||||
|
Name
|
||||||
|
</label>
|
||||||
|
<div class="control-elements">
|
||||||
|
<input
|
||||||
|
ref="name"
|
||||||
|
v-model="values.name"
|
||||||
|
:class="{ 'input-error': $v.values.name.$error }"
|
||||||
|
type="text"
|
||||||
|
class="input input-large"
|
||||||
|
@blur="$v.values.name.$touch()"
|
||||||
|
/>
|
||||||
|
<div v-if="$v.values.name.$error" class="error">
|
||||||
|
This field is required.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<slot></slot>
|
||||||
|
</form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { required } from 'vuelidate/lib/validators'
|
||||||
|
|
||||||
|
import form from '@/mixins/form'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'TableForm',
|
||||||
|
mixins: [form],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
values: {
|
||||||
|
name: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
validations: {
|
||||||
|
values: {
|
||||||
|
name: { required }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$refs.name.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -9,6 +9,7 @@ export default function DatabaseModule(options) {
|
||||||
filename: 'plugin.js'
|
filename: 'plugin.js'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Add all the related routes.
|
||||||
this.extendRoutes(routes => {
|
this.extendRoutes(routes => {
|
||||||
routes.push(...databaseRoutes)
|
routes.push(...databaseRoutes)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<header class="layout-col-3-1 header">
|
|
||||||
<ul class="header-filter">
|
|
||||||
<li class="header-filter-item"> </li>
|
|
||||||
</ul>
|
|
||||||
<ul class="header-info">
|
|
||||||
<li>{{ selectedApplication.name }}</li>
|
|
||||||
<li>@TODO table name</li>
|
|
||||||
</ul>
|
|
||||||
</header>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { mapState } from 'vuex'
|
|
||||||
|
|
||||||
import application from '@/mixins/application'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
layout: 'app',
|
|
||||||
mixins: [application],
|
|
||||||
computed: {
|
|
||||||
...mapState({
|
|
||||||
selectedApplication: state => state.application.selected
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
47
web-frontend/modules/database/pages/Table.vue
Normal file
47
web-frontend/modules/database/pages/Table.vue
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<header class="layout-col-3-1 header">
|
||||||
|
<ul class="header-filter">
|
||||||
|
<li class="header-filter-item"> </li>
|
||||||
|
</ul>
|
||||||
|
<ul class="header-info">
|
||||||
|
<li>{{ database }}</li>
|
||||||
|
<li>{{ table }}</li>
|
||||||
|
</ul>
|
||||||
|
</header>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// import { notify404 } from '@/utils/error'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
layout: 'app',
|
||||||
|
props: {
|
||||||
|
id: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
tableId: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
asyncData({ store, params, redirect }) {
|
||||||
|
// @TODO figure out why the id's aren't converted to an int in the route.
|
||||||
|
const databaseId = parseInt(params.id)
|
||||||
|
const tableId = parseInt(params.tableId)
|
||||||
|
|
||||||
|
return store
|
||||||
|
.dispatch('table/preSelect', { databaseId, tableId })
|
||||||
|
.then(data => {
|
||||||
|
return { database: data.database, table: data.table }
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// If something went wrong this will probably mean that the user doesn't have
|
||||||
|
// access to the database so we will need to redirect back to the index page.
|
||||||
|
redirect({ name: 'app' })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -1,5 +1,7 @@
|
||||||
import { DatabaseApplication } from '@/modules/database/application'
|
import { DatabaseApplication } from '@/modules/database/application'
|
||||||
|
import tableStore from '@/modules/database/store/table'
|
||||||
|
|
||||||
export default ({ store }) => {
|
export default ({ store }) => {
|
||||||
|
store.registerModule('table', tableStore)
|
||||||
store.dispatch('application/register', new DatabaseApplication())
|
store.dispatch('application/register', new DatabaseApplication())
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,15 @@ import path from 'path'
|
||||||
|
|
||||||
export const databaseRoutes = [
|
export const databaseRoutes = [
|
||||||
{
|
{
|
||||||
name: 'application-database',
|
name: 'database-table',
|
||||||
path: '/database/:id',
|
path: '/database/:id/table/:tableId',
|
||||||
component: path.resolve(__dirname, 'pages/Database.vue'),
|
component: path.resolve(__dirname, 'pages/Table.vue'),
|
||||||
props(route) {
|
props(route) {
|
||||||
const props = { ...route.params }
|
// @TODO figure out why the route param is empty on the server side.
|
||||||
props.id = parseInt(props.id)
|
const p = { ...route.params }
|
||||||
return props
|
p.id = parseInt(p.id)
|
||||||
|
p.tableId = parseInt(p.tableId)
|
||||||
|
return p
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
19
web-frontend/modules/database/services/table.js
Normal file
19
web-frontend/modules/database/services/table.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { client } from '@/services/client'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
fetchAll(databaseId) {
|
||||||
|
return client.get(`/database/tables/database/${databaseId}/`)
|
||||||
|
},
|
||||||
|
create(databaseId, values) {
|
||||||
|
return client.post(`/database/tables/database/${databaseId}/`, values)
|
||||||
|
},
|
||||||
|
get(tableId) {
|
||||||
|
return client.get(`/database/tables/${tableId}/`)
|
||||||
|
},
|
||||||
|
update(tableId, values) {
|
||||||
|
return client.patch(`/database/tables/${tableId}/`, values)
|
||||||
|
},
|
||||||
|
delete(tableId) {
|
||||||
|
return client.delete(`/database/tables/${tableId}/`)
|
||||||
|
}
|
||||||
|
}
|
190
web-frontend/modules/database/store/table.js
Normal file
190
web-frontend/modules/database/store/table.js
Normal file
|
@ -0,0 +1,190 @@
|
||||||
|
import TableService from '@/modules/database/services/table'
|
||||||
|
import { DatabaseApplication } from '@/modules/database/application'
|
||||||
|
import { notify404, notifyError } from '@/utils/error'
|
||||||
|
|
||||||
|
export function populateTable(table) {
|
||||||
|
table._ = {
|
||||||
|
disabled: false,
|
||||||
|
selected: false
|
||||||
|
}
|
||||||
|
return table
|
||||||
|
}
|
||||||
|
|
||||||
|
export const state = () => ({
|
||||||
|
selected: {}
|
||||||
|
})
|
||||||
|
|
||||||
|
export const mutations = {
|
||||||
|
ADD_ITEM(state, { database, table }) {
|
||||||
|
populateTable(table)
|
||||||
|
database.tables.push(table)
|
||||||
|
},
|
||||||
|
UPDATE_ITEM(state, { table, values }) {
|
||||||
|
Object.assign(table, table, values)
|
||||||
|
},
|
||||||
|
SET_SELECTED(state, { database, table }) {
|
||||||
|
Object.values(database.tables).forEach(item => {
|
||||||
|
item._.selected = false
|
||||||
|
})
|
||||||
|
table._.selected = true
|
||||||
|
state.selected = table
|
||||||
|
},
|
||||||
|
DELETE_ITEM(state, { database, id }) {
|
||||||
|
const index = database.tables.findIndex(item => item.id === id)
|
||||||
|
database.tables.splice(index, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const actions = {
|
||||||
|
/**
|
||||||
|
* Create a new table based on the provided values and add it to the tables
|
||||||
|
* of the provided database.
|
||||||
|
*/
|
||||||
|
create({ commit, dispatch }, { database, values }) {
|
||||||
|
const type = DatabaseApplication.getType()
|
||||||
|
|
||||||
|
// Check if the provided database (application) has the correct type.
|
||||||
|
if (database.type !== type) {
|
||||||
|
throw new Error(
|
||||||
|
`The provided database application doesn't have the required type
|
||||||
|
"${type}".`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return TableService.create(database.id, values)
|
||||||
|
.then(({ data }) => {
|
||||||
|
commit('ADD_ITEM', { database, table: data })
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
notify404(
|
||||||
|
dispatch,
|
||||||
|
error,
|
||||||
|
'Could not create table',
|
||||||
|
"You're unable to create a new table for the selected database. " +
|
||||||
|
". This is because the database doesn't exist."
|
||||||
|
)
|
||||||
|
|
||||||
|
notifyError(
|
||||||
|
dispatch,
|
||||||
|
error,
|
||||||
|
'ERROR_USER_NOT_IN_GROUP',
|
||||||
|
'Could not create table',
|
||||||
|
"You're unable to create a new table for the selected database. " +
|
||||||
|
". This is because you're not part of the group."
|
||||||
|
)
|
||||||
|
|
||||||
|
throw error
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Update an existing table of the provided database with the provided tables.
|
||||||
|
*/
|
||||||
|
update({ commit, dispatch }, { database, table, values }) {
|
||||||
|
return TableService.update(table.id, values)
|
||||||
|
.then(({ data }) => {
|
||||||
|
commit('UPDATE_ITEM', { database, table, values: data })
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
notify404(
|
||||||
|
dispatch,
|
||||||
|
error,
|
||||||
|
'Could not update table',
|
||||||
|
"You're unable to update the table. This is because the " +
|
||||||
|
"table doesn't exist."
|
||||||
|
)
|
||||||
|
|
||||||
|
notifyError(
|
||||||
|
dispatch,
|
||||||
|
error,
|
||||||
|
'ERROR_USER_NOT_IN_GROUP',
|
||||||
|
'Could not update table',
|
||||||
|
"You're unable to update the table. This is because you're " +
|
||||||
|
'not part of the group.'
|
||||||
|
)
|
||||||
|
|
||||||
|
throw error
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Deletes an existing application.
|
||||||
|
*/
|
||||||
|
delete({ commit, dispatch }, { database, table }) {
|
||||||
|
return TableService.delete(table.id)
|
||||||
|
.then(() => {
|
||||||
|
commit('DELETE_ITEM', { database, id: table.id })
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
notify404(
|
||||||
|
dispatch,
|
||||||
|
error,
|
||||||
|
'Could not delete table',
|
||||||
|
"You're unable to delete the table for the selected database. " +
|
||||||
|
". This is because the database doesn't exist."
|
||||||
|
)
|
||||||
|
|
||||||
|
notifyError(
|
||||||
|
dispatch,
|
||||||
|
error,
|
||||||
|
'ERROR_USER_NOT_IN_GROUP',
|
||||||
|
'Unable to delete',
|
||||||
|
"You're not allowed to delete the table because you're" +
|
||||||
|
' not part of the group where the application is in.'
|
||||||
|
)
|
||||||
|
|
||||||
|
throw error
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Select a table of a database.
|
||||||
|
*/
|
||||||
|
select({ commit }, { database, table }) {
|
||||||
|
commit('SET_SELECTED', { database, table })
|
||||||
|
return { database, table }
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* First it will preSelect the application to make sure the groups are
|
||||||
|
* fetched, the correct group is selected, the related applications are
|
||||||
|
* selected and the provided application id is selected. After that is will
|
||||||
|
* check if the application has the correct type which is a database and makes
|
||||||
|
* sure that the table actually belongs to that database. If so it will select
|
||||||
|
* the table and return the database and table so it can be used.
|
||||||
|
*/
|
||||||
|
preSelect({ dispatch, getters, rootGetters }, { databaseId, tableId }) {
|
||||||
|
// Preselect the application
|
||||||
|
return dispatch('application/preSelect', databaseId, { root: true }).then(
|
||||||
|
database => {
|
||||||
|
const type = DatabaseApplication.getType()
|
||||||
|
|
||||||
|
// Check if the just selected application has the correct type because
|
||||||
|
// it needs to have tables.
|
||||||
|
if (database.type !== type) {
|
||||||
|
throw new Error(
|
||||||
|
`The application doesn't have the required ${type} type.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the provided table id is found is the just selected
|
||||||
|
// database.
|
||||||
|
const index = database.tables.findIndex(item => item.id === tableId)
|
||||||
|
if (index === -1) {
|
||||||
|
throw new Error('The table is not found in the selected application.')
|
||||||
|
}
|
||||||
|
const table = database.tables[index]
|
||||||
|
|
||||||
|
// Select the table table and return the database and table instance
|
||||||
|
// when done.
|
||||||
|
return dispatch('select', { database, table })
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getters = {}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
namespaced: true,
|
||||||
|
state,
|
||||||
|
getters,
|
||||||
|
actions,
|
||||||
|
mutations
|
||||||
|
}
|
|
@ -10,7 +10,9 @@
|
||||||
<br /><br />
|
<br /><br />
|
||||||
{{ groupApplications }}
|
{{ groupApplications }}
|
||||||
<br /><br />
|
<br /><br />
|
||||||
<nuxt-link :to="{ name: 'application-database', params: { id: 1 } }">
|
<nuxt-link
|
||||||
|
:to="{ name: 'database-table', params: { id: 1, tableId: 1 } }"
|
||||||
|
>
|
||||||
<i class="fas fa-arrow-left"></i>
|
<i class="fas fa-arrow-left"></i>
|
||||||
App
|
App
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
|
|
|
@ -10,7 +10,7 @@ function populateApplication(application, getters) {
|
||||||
loading: false,
|
loading: false,
|
||||||
selected: false
|
selected: false
|
||||||
}
|
}
|
||||||
return application
|
return type.populate(application)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const state = () => ({
|
export const state = () => ({
|
||||||
|
@ -143,6 +143,8 @@ export const actions = {
|
||||||
"You're unable to create a new application for the selected " +
|
"You're unable to create a new application for the selected " +
|
||||||
"group. This could be because you're not part of the group."
|
"group. This could be because you're not part of the group."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
throw error
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
|
@ -158,10 +160,12 @@ export const actions = {
|
||||||
dispatch,
|
dispatch,
|
||||||
error,
|
error,
|
||||||
'ERROR_USER_NOT_IN_GROUP',
|
'ERROR_USER_NOT_IN_GROUP',
|
||||||
'Rename not allowed',
|
'Change not allowed',
|
||||||
"You're not allowed to rename the application because you're " +
|
"You're not allowed to change the application because you're " +
|
||||||
'not part of the group where the application is in.'
|
'not part of the group where the application is in.'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
throw error
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
|
@ -181,6 +185,8 @@ export const actions = {
|
||||||
"You're not allowed to rename the application because you're" +
|
"You're not allowed to rename the application because you're" +
|
||||||
' not part of the group where the application is in.'
|
' not part of the group where the application is in.'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
throw error
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
|
@ -188,6 +194,7 @@ export const actions = {
|
||||||
*/
|
*/
|
||||||
select({ commit }, application) {
|
select({ commit }, application) {
|
||||||
commit('SET_SELECTED', application)
|
commit('SET_SELECTED', application)
|
||||||
|
return application
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Select an application by a given application id.
|
* Select an application by a given application id.
|
||||||
|
@ -213,17 +220,17 @@ export const actions = {
|
||||||
* to date. In short it will make sure that the depending state of the given
|
* to date. In short it will make sure that the depending state of the given
|
||||||
* application will be there.
|
* application will be there.
|
||||||
*/
|
*/
|
||||||
preSelect({ dispatch, getters, rootGetters }, id) {
|
preSelect({ dispatch, getters, rootGetters, state }, id) {
|
||||||
// First we will check if the application is already in the items.
|
// First we will check if the application is already in the items.
|
||||||
const application = getters.get(id)
|
const application = getters.get(id)
|
||||||
|
|
||||||
// If the application is already selected we don't have to do anything.
|
// If the application is already selected we don't have to do anything.
|
||||||
if (application !== undefined && application._.selected) {
|
if (application !== undefined && application._.selected) {
|
||||||
return
|
return application
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function will select a group by its id which will then automatically
|
// This function will select a group by its id which will then automatically
|
||||||
// fetch the applications related to that group. When done it will select
|
// fetches the applications related to that group. When done it will select
|
||||||
// the provided application id.
|
// the provided application id.
|
||||||
const selectGroupAndApplication = (groupId, applicationId) => {
|
const selectGroupAndApplication = (groupId, applicationId) => {
|
||||||
return dispatch('group/selectById', groupId, {
|
return dispatch('group/selectById', groupId, {
|
||||||
|
@ -237,19 +244,20 @@ export const actions = {
|
||||||
// If the application is already in the selected groups, which means that
|
// If the application is already in the selected groups, which means that
|
||||||
// the groups and applications are already loaded, we can just select that
|
// the groups and applications are already loaded, we can just select that
|
||||||
// application.
|
// application.
|
||||||
dispatch('select', application)
|
return dispatch('select', application)
|
||||||
} else {
|
} else {
|
||||||
// The application is not in the selected group so we need to figure out
|
// The application is not in the selected group so we need to figure out
|
||||||
// in which he is by fetching the application.
|
// in which he is by fetching the application.
|
||||||
return ApplicationService.get(id).then(data => {
|
return ApplicationService.get(id).then(response => {
|
||||||
if (!rootGetters['group/isLoaded']) {
|
if (!rootGetters['group/isLoaded']) {
|
||||||
// If the groups are not already loaded we need to load them first.
|
// If the groups are not already loaded we need to load them first.
|
||||||
return dispatch('group/fetchAll', {}, { root: true }).then(() => {
|
return dispatch('group/fetchAll', {}, { root: true }).then(() => {
|
||||||
return selectGroupAndApplication(data.data.group.id, id)
|
return selectGroupAndApplication(response.data.group.id, id)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// The groups are already loaded so we
|
// The groups are already loaded so we need to select the group and
|
||||||
return selectGroupAndApplication(data.data.group.id, id)
|
// application.
|
||||||
|
return selectGroupAndApplication(response.data.group.id, id)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,10 +122,12 @@ export const actions = {
|
||||||
notify404(
|
notify404(
|
||||||
dispatch,
|
dispatch,
|
||||||
error,
|
error,
|
||||||
'Unable to rename',
|
'Unable to update',
|
||||||
"You're unable to rename the group. This could be because " +
|
"You're unable to update the group. This could be because " +
|
||||||
"you're not part of the group."
|
"you're not part of the group."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
throw error
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
|
@ -148,6 +150,8 @@ export const actions = {
|
||||||
"You're unable to delete the group. This could be because " +
|
"You're unable to delete the group. This could be because " +
|
||||||
"you're not part of the group."
|
"you're not part of the group."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
throw error
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Add table
Reference in a new issue