Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Connections API #3309

Merged
merged 16 commits into from
Nov 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion mathesar/api/db/viewsets/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from mathesar.api.db.viewsets.columns import ColumnViewSet # noqa
from mathesar.api.db.viewsets.constraints import ConstraintViewSet # noqa
from mathesar.api.db.viewsets.data_files import DataFileViewSet # noqa
from mathesar.api.db.viewsets.databases import DatabaseViewSet # noqa
from mathesar.api.db.viewsets.databases import ConnectionViewSet # noqa
from mathesar.api.db.viewsets.records import RecordViewSet # noqa
from mathesar.api.db.viewsets.schemas import SchemaViewSet # noqa
from mathesar.api.db.viewsets.table_settings import TableSettingsViewSet # noqa
Expand Down
27 changes: 3 additions & 24 deletions mathesar/api/db/viewsets/databases.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from mathesar.api.dj_filters import DatabaseFilter
from mathesar.api.pagination import DefaultLimitOffsetPagination

from mathesar.api.serializers.databases import DatabaseSerializer
from mathesar.api.serializers.databases import ConnectionSerializer

from db.functions.operations.check_support import get_supported_db_functions
from mathesar.api.serializers.functions import DBFunctionSerializer
Expand All @@ -18,8 +18,8 @@
from mathesar.api.serializers.db_types import DBTypeSerializer


class DatabaseViewSet(AccessViewSetMixin, viewsets.ModelViewSet):
serializer_class = DatabaseSerializer
class ConnectionViewSet(AccessViewSetMixin, viewsets.ModelViewSet):
serializer_class = ConnectionSerializer
pagination_class = DefaultLimitOffsetPagination
filter_backends = (filters.DjangoFilterBackend,)
filterset_class = DatabaseFilter
Expand All @@ -31,27 +31,6 @@ def get_queryset(self):
Database.objects.all().order_by('-created_at')
)

def create(self, request):
serializer = DatabaseSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
credentials = serializer.validated_data
Database.objects.create(
name=credentials['name'],
db_name=credentials['db_name'],
username=credentials['username'],
password=credentials['password'],
host=credentials['host'],
port=credentials['port']
).save()
return Response(serializer.data, status=status.HTTP_201_CREATED)

def partial_update(self, request, pk=None):
db_object = self.get_object()
serializer = DatabaseSerializer(db_object, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)

def destroy(self, request, pk=None):
db_object = self.get_object()
if request.query_params.get('del_msar_schemas'):
Expand Down
35 changes: 6 additions & 29 deletions mathesar/api/serializers/databases.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@
from mathesar.api.display_options import DISPLAY_OPTIONS_BY_UI_TYPE
from mathesar.api.exceptions.mixins import MathesarErrorMessageMixin
from mathesar.models.base import Database
from mathesar.api.utils import is_valid_pg_creds
from db.install import install_mathesar


class DatabaseSerializer(MathesarErrorMessageMixin, serializers.ModelSerializer):
class ConnectionSerializer(MathesarErrorMessageMixin, serializers.ModelSerializer):
supported_types_url = serializers.SerializerMethodField()
nickname = serializers.CharField(source='name')
database = serializers.CharField(source='db_name')

class Meta:
model = Database
fields = ['id', 'name', 'db_name', 'deleted', 'supported_types_url', 'username', 'password', 'host', 'port']
read_only_fields = ['id', 'deleted', 'supported_types_url']
fields = ['id', 'nickname', 'database', 'supported_types_url', 'username', 'password', 'host', 'port']
read_only_fields = ['id', 'supported_types_url']
extra_kwargs = {
'password': {'write_only': True}
}
Expand All @@ -23,33 +23,10 @@ def get_supported_types_url(self, obj):
if isinstance(obj, Database) and not self.partial:
# Only get records if we are serializing an existing table
request = self.context['request']
return request.build_absolute_uri(reverse('database-types', kwargs={'pk': obj.pk}))
return request.build_absolute_uri(reverse('connection-types', kwargs={'pk': obj.pk}))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a minor bug here, the uri formed will always be namespaced under ui/ as the basename is same for both the db & ui route. Not sure how significant it is for the frontend though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm. I'll experiment with it a bit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Anish9901 I poked around and found that the behavior's the same as before. I agree that this is a bit of a bug, but it's apparently harmless. Given that we're going to be completely overhauling the API structure in the near future, I want to avoid fixing it for now.

else:
return None

def validate(self, credentials):
if self.partial:
db_model = self.instance
for attr, value in credentials.items():
setattr(db_model, attr, value)
credentials = {
'db_name': db_model.db_name,
'host': db_model.host,
'username': db_model.username,
'password': db_model.password,
'port': db_model.port
}
if is_valid_pg_creds(credentials):
install_mathesar(
database_name=credentials["db_name"],
hostname=credentials["host"],
username=credentials["username"],
password=credentials["password"],
port=credentials["port"],
skip_confirm=True
)
return super().validate(credentials)


class TypeSerializer(MathesarErrorMessageMixin, serializers.Serializer):
identifier = serializers.CharField()
Expand Down
2 changes: 1 addition & 1 deletion mathesar/api/ui/viewsets/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from mathesar.api.ui.viewsets.databases import DatabaseViewSet # noqa
from mathesar.api.ui.viewsets.databases import ConnectionViewSet # noqa
from mathesar.api.ui.viewsets.users import * # noqa
from mathesar.api.ui.viewsets.version import VersionViewSet # noqa
from mathesar.api.ui.viewsets.records import RecordViewSet # noqa
Expand Down
6 changes: 3 additions & 3 deletions mathesar/api/ui/viewsets/databases.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@
from mathesar.api.dj_filters import DatabaseFilter
from mathesar.api.pagination import DefaultLimitOffsetPagination

from mathesar.api.serializers.databases import DatabaseSerializer, TypeSerializer
from mathesar.api.serializers.databases import ConnectionSerializer, TypeSerializer
from mathesar.api.serializers.filters import FilterSerializer

from mathesar.filters.base import get_available_filters


class DatabaseViewSet(viewsets.GenericViewSet, ListModelMixin, RetrieveModelMixin):
serializer_class = DatabaseSerializer
class ConnectionViewSet(viewsets.GenericViewSet, ListModelMixin, RetrieveModelMixin):
serializer_class = ConnectionSerializer
pagination_class = DefaultLimitOffsetPagination
filter_backends = (filters.DjangoFilterBackend,)
filterset_class = DatabaseFilter
Expand Down
21 changes: 11 additions & 10 deletions mathesar/tests/api/test_database_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,15 +92,14 @@ def test_database_reflection_delete_table(db_dj_model):

def check_database(database, response_database):
assert database.id == response_database['id']
assert database.name == response_database['name']
assert database.deleted == response_database['deleted']
assert database.name == response_database['nickname']
assert 'supported_types_url' in response_database
assert '/api/ui/v0/databases/' in response_database['supported_types_url']
assert '/api/ui/v0/connections/' in response_database['supported_types_url']
assert response_database['supported_types_url'].endswith('/types/')


def test_database_list(client, db_dj_model):
response = client.get('/api/db/v0/databases/')
response = client.get('/api/db/v0/connections/')
response_data = response.json()
assert response.status_code == 200
assert response_data['count'] == 1
Expand All @@ -120,21 +119,23 @@ def test_database_list_permissions(FUN_create_dj_db, get_uid, client, client_bob
db3 = FUN_create_dj_db(get_uid())
DatabaseRole.objects.create(user=user_bob, database=db3, role='editor')

response = client_bob.get('/api/db/v0/databases/')
response = client_bob.get('/api/db/v0/connections/')
response_data = response.json()
assert response.status_code == 200
assert response_data['count'] == 3

response = client_alice.get('/api/db/v0/databases/')
response = client_alice.get('/api/db/v0/connections/')
response_data = response.json()
assert response.status_code == 200
assert response_data['count'] == 2


def test_database_list_deleted(client, db_dj_model):
# Note that there is no longer a distinction between "deleted" and undeleted
# connections in the API.
_remove_db(db_dj_model.name)
cache.clear()
response = client.get('/api/db/v0/databases/')
response = client.get('/api/db/v0/connections/')
response_data = response.json()
assert response.status_code == 200
assert response_data['count'] == 1
Expand All @@ -143,7 +144,7 @@ def test_database_list_deleted(client, db_dj_model):


def test_database_detail(client, db_dj_model):
response = client.get(f'/api/db/v0/databases/{db_dj_model.id}/')
response = client.get(f'/api/db/v0/connections/{db_dj_model.id}/')
response_database = response.json()

assert response.status_code == 200
Expand All @@ -154,8 +155,8 @@ def test_database_detail_permissions(FUN_create_dj_db, get_uid, client_bob, clie
db1 = FUN_create_dj_db(get_uid())
DatabaseRole.objects.create(user=user_bob, database=db1, role='viewer')

response = client_bob.get(f'/api/db/v0/databases/{db1.id}/')
response = client_bob.get(f'/api/db/v0/connections/{db1.id}/')
assert response.status_code == 200

response = client_alice.get(f'/api/db/v0/databases/{db1.id}/')
response = client_alice.get(f'/api/db/v0/connections/{db1.id}/')
assert response.status_code == 404
2 changes: 1 addition & 1 deletion mathesar/tests/api/test_db_type_api.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
def test_db_type_list_well_formed(client, test_db_model):
database_id = test_db_model.id
response = client.get(f'/api/db/v0/databases/{database_id}/types/')
response = client.get(f'/api/db/v0/connections/{database_id}/types/')
assert response.status_code == 200
json_db_types = response.json()
assert isinstance(json_db_types, list)
Expand Down
6 changes: 3 additions & 3 deletions mathesar/tests/api/test_function_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

def test_function_list_well_formed(client, test_db_model):
database_id = test_db_model.id
response = client.get(f'/api/db/v0/databases/{database_id}/functions/')
response = client.get(f'/api/db/v0/connections/{database_id}/functions/')
assert response.status_code == 200
json_db_functions = response.json()
assert isinstance(json_db_functions, list)
Expand All @@ -17,8 +17,8 @@ def test_function_list_well_formed(client, test_db_model):
def test_function_list_permissions(FUN_create_dj_db, get_uid, client_bob, client_alice, user_bob, user_alice):
database = FUN_create_dj_db(get_uid())
DatabaseRole.objects.create(user=user_bob, database=database, role='viewer')
response = client_bob.get(f'/api/db/v0/databases/{database.id}/functions/')
response = client_bob.get(f'/api/db/v0/connections/{database.id}/functions/')
assert response.status_code == 200

response = client_alice.get(f'/api/db/v0/databases/{database.id}/functions/')
response = client_alice.get(f'/api/db/v0/connections/{database.id}/functions/')
assert response.status_code == 404
6 changes: 3 additions & 3 deletions mathesar/tests/api/test_ui_filters_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
def test_filter_list(client, test_db_name):
database = Database.objects.get(name=test_db_name)

response = client.get(f'/api/ui/v0/databases/{database.id}/filters/')
response = client.get(f'/api/ui/v0/connections/{database.id}/filters/')
response_data = response.json()
assert response.status_code == 200
for available_filter in response_data:
Expand All @@ -17,8 +17,8 @@ def test_filter_list(client, test_db_name):
def test_filter_list_permissions(FUN_create_dj_db, get_uid, client_bob, client_alice, user_bob, user_alice):
database = FUN_create_dj_db(get_uid())
DatabaseRole.objects.create(user=user_bob, database=database, role='viewer')
response = client_bob.get(f'/api/ui/v0/databases/{database.id}/filters/')
response = client_bob.get(f'/api/ui/v0/connections/{database.id}/filters/')
assert response.status_code == 200

response = client_alice.get(f'/api/ui/v0/databases/{database.id}/filters/')
response = client_alice.get(f'/api/ui/v0/connections/{database.id}/filters/')
assert response.status_code == 404
8 changes: 4 additions & 4 deletions mathesar/tests/api/test_ui_types_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
def test_type_list(client, test_db_name):
database = Database.objects.get(name=test_db_name)

response = client.get(f'/api/ui/v0/databases/{database.id}/types/')
response = client.get(f'/api/ui/v0/connections/{database.id}/types/')
response_data = response.json()
assert response.status_code == 200
assert len(response_data) == len(database.supported_ui_types)
Expand All @@ -24,12 +24,12 @@ def test_type_list(client, test_db_name):
def test_type_list_permissions(FUN_create_dj_db, get_uid, client_bob, client_alice, user_bob, user_alice):
database = FUN_create_dj_db(get_uid())
DatabaseRole.objects.create(user=user_bob, database=database, role='viewer')
response = client_bob.get(f'/api/ui/v0/databases/{database.id}/types/')
response = client_bob.get(f'/api/ui/v0/connections/{database.id}/types/')
response_data = response.json()
assert response.status_code == 200
assert len(response_data) == len(database.supported_ui_types)

response = client_alice.get(f'/api/ui/v0/databases/{database.id}/types/')
response = client_alice.get(f'/api/ui/v0/connections/{database.id}/types/')
assert response.status_code == 404


Expand Down Expand Up @@ -64,7 +64,7 @@ def test_database_types_installed(client, test_db_name):
]
default_database = Database.objects.get(name=test_db_name)

response = client.get(f'/api/ui/v0/databases/{default_database.id}/types/')
response = client.get(f'/api/ui/v0/connections/{default_database.id}/types/')
assert response.status_code == 200
actual_custom_types = response.json()

Expand Down
4 changes: 2 additions & 2 deletions mathesar/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
db_router.register(r'queries', db_viewsets.QueryViewSet, basename='query')
db_router.register(r'links', db_viewsets.LinkViewSet, basename='links')
db_router.register(r'schemas', db_viewsets.SchemaViewSet, basename='schema')
db_router.register(r'databases', db_viewsets.DatabaseViewSet, basename='database')
db_router.register(r'connections', db_viewsets.ConnectionViewSet, basename='connection')
db_router.register(r'data_files', db_viewsets.DataFileViewSet, basename='data-file')

db_table_router = routers.NestedSimpleRouter(db_router, r'tables', lookup='table')
Expand All @@ -25,7 +25,7 @@

ui_router = routers.DefaultRouter()
ui_router.register(r'version', ui_viewsets.VersionViewSet, basename='version')
ui_router.register(r'databases', ui_viewsets.DatabaseViewSet, basename='database')
ui_router.register(r'connections', ui_viewsets.ConnectionViewSet, basename='connection')
ui_router.register(r'users', ui_viewsets.UserViewSet, basename='user')
ui_router.register(r'database_roles', ui_viewsets.DatabaseRoleViewSet, basename='database_role')
ui_router.register(r'schema_roles', ui_viewsets.SchemaRoleViewSet, basename='schema_role')
Expand Down