-
-
Notifications
You must be signed in to change notification settings - Fork 338
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1374 from centerofci/mathesar-972-link-table
Add link Table API
- Loading branch information
Showing
13 changed files
with
376 additions
and
6 deletions.
There are no files selected for viewing
Empty file.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
from alembic.operations import Operations | ||
from alembic.migration import MigrationContext | ||
from sqlalchemy import ForeignKey, MetaData | ||
|
||
from db.columns.base import MathesarColumn | ||
from db.constraints.utils import naming_convention | ||
from db.tables.operations.create import create_mathesar_table | ||
from db.tables.operations.select import reflect_table_from_oid, reflect_tables_from_oids | ||
from db.tables.utils import get_primary_key_column | ||
|
||
|
||
def create_foreign_key_link( | ||
engine, | ||
schema, | ||
referrer_column_name, | ||
referrer_table_oid, | ||
referent_table_oid, | ||
unique_link=False | ||
): | ||
with engine.begin() as conn: | ||
referent_table = reflect_table_from_oid(referent_table_oid, engine, conn) | ||
referrer_table = reflect_table_from_oid(referrer_table_oid, engine, conn) | ||
primary_key_column = get_primary_key_column(referent_table) | ||
metadata = MetaData(bind=engine, schema=schema, naming_convention=naming_convention) | ||
opts = { | ||
'target_metadata': metadata | ||
} | ||
ctx = MigrationContext.configure(conn, opts=opts) | ||
op = Operations(ctx) | ||
column = MathesarColumn( | ||
referrer_column_name, primary_key_column.type | ||
) | ||
op.add_column(referrer_table.name, column, schema=schema) | ||
if unique_link: | ||
op.create_unique_constraint(None, referrer_table.name, [referrer_column_name], schema=schema) | ||
op.create_foreign_key( | ||
None, | ||
referrer_table.name, | ||
referent_table.name, | ||
[column.name], | ||
[primary_key_column.name], | ||
source_schema=schema, | ||
referent_schema=schema | ||
) | ||
|
||
|
||
def create_many_to_many_link(engine, schema, map_table_name, referents): | ||
with engine.begin() as conn: | ||
referent_tables_oid = [referent['referent_table'] for referent in referents] | ||
referent_tables = reflect_tables_from_oids(referent_tables_oid, engine, conn) | ||
metadata = MetaData(bind=engine, schema=schema, naming_convention=naming_convention) | ||
# Throws sqlalchemy.exc.NoReferencedTableError if metadata is not reflected. | ||
metadata.reflect() | ||
referrer_columns = [] | ||
for referent in referents: | ||
referent_table_oid = referent['referent_table'] | ||
referent_table = referent_tables[referent_table_oid] | ||
col_name = referent['column_name'] | ||
primary_key_column = get_primary_key_column(referent_table) | ||
foreign_keys = {ForeignKey(primary_key_column)} | ||
column = MathesarColumn( | ||
col_name, primary_key_column.type, foreign_keys=foreign_keys, | ||
) | ||
referrer_columns.append(column) | ||
create_mathesar_table(map_table_name, schema, referrer_columns, engine, metadata) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
from rest_framework.mixins import CreateModelMixin, ListModelMixin | ||
from rest_framework.viewsets import GenericViewSet | ||
from mathesar.api.pagination import DefaultLimitOffsetPagination | ||
from mathesar.api.serializers.links import LinksMappingSerializer | ||
|
||
|
||
class LinkViewSet(CreateModelMixin, ListModelMixin, GenericViewSet): | ||
serializer_class = LinksMappingSerializer | ||
pagination_class = DefaultLimitOffsetPagination | ||
|
||
def get_queryset(self): | ||
return [] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
from rest_framework import serializers | ||
|
||
from db.links.operations.create import create_foreign_key_link, create_many_to_many_link | ||
from mathesar.api.exceptions.mixins import MathesarErrorMessageMixin | ||
from mathesar.api.exceptions.validation_exceptions.exceptions import ( | ||
InvalidLinkChoiceAPIException, | ||
) | ||
from mathesar.api.serializers.shared_serializers import ( | ||
MathesarPolymorphicErrorMixin, | ||
ReadWritePolymorphicSerializerMappingMixin, | ||
) | ||
from mathesar.models import Table | ||
|
||
|
||
class OneToOneSerializer(MathesarErrorMessageMixin, serializers.Serializer): | ||
reference_column_name = serializers.CharField() | ||
reference_table = serializers.PrimaryKeyRelatedField(queryset=Table.current_objects.all()) | ||
referent_table = serializers.PrimaryKeyRelatedField(queryset=Table.current_objects.all()) | ||
# TODO Fix hacky link_type detection by reflecting it correctly | ||
link_type = serializers.CharField(default="one-to-one") | ||
|
||
def is_link_unique(self): | ||
return True | ||
|
||
def create(self, validated_data): | ||
reference_table = validated_data['reference_table'] | ||
create_foreign_key_link( | ||
reference_table.schema._sa_engine, | ||
reference_table._sa_table.schema, | ||
validated_data.get('reference_column_name'), | ||
reference_table.oid, | ||
validated_data.get('referent_table').oid, | ||
unique_link=self.is_link_unique() | ||
) | ||
return validated_data | ||
|
||
|
||
class OneToManySerializer(OneToOneSerializer): | ||
link_type = serializers.CharField(default="one-to-many") | ||
|
||
def is_link_unique(self): | ||
return False | ||
|
||
|
||
class MapColumnSerializer(MathesarErrorMessageMixin, serializers.Serializer): | ||
column_name = serializers.CharField() | ||
referent_table = serializers.PrimaryKeyRelatedField(queryset=Table.current_objects.all()) | ||
|
||
|
||
class ManyToManySerializer(MathesarErrorMessageMixin, serializers.Serializer): | ||
referents = MapColumnSerializer(many=True) | ||
mapping_table_name = serializers.CharField() | ||
link_type = serializers.CharField(default="many-to-many") | ||
|
||
def create(self, validated_data): | ||
referents = validated_data['referents'] | ||
referent_tables_oid = [ | ||
{'referent_table': map_table_obj['referent_table'].oid, 'column_name': map_table_obj['column_name']} for | ||
map_table_obj in validated_data['referents']] | ||
create_many_to_many_link( | ||
referents[0]['referent_table'].schema._sa_engine, | ||
referents[0]['referent_table']._sa_table.schema, | ||
validated_data.get('mapping_table_name'), | ||
referent_tables_oid, | ||
) | ||
return validated_data | ||
|
||
|
||
class LinksMappingSerializer( | ||
MathesarPolymorphicErrorMixin, | ||
ReadWritePolymorphicSerializerMappingMixin, | ||
serializers.Serializer | ||
): | ||
def create(self, validated_data): | ||
serializer = self.serializers_mapping.get(self.get_mapping_field(validated_data)) | ||
return serializer.create(validated_data) | ||
|
||
serializers_mapping = { | ||
"one-to-one": OneToOneSerializer, | ||
"one-to-many": OneToManySerializer, | ||
"many-to-many": ManyToManySerializer | ||
} | ||
link_type = serializers.CharField(required=True) | ||
|
||
def get_mapping_field(self, data): | ||
link_type = data.get('link_type', None) | ||
if link_type is None: | ||
raise InvalidLinkChoiceAPIException() | ||
return link_type |
Oops, something went wrong.