Repository: mathesar-foundation/mathesar

Issue:
Refactor the `TableViewSet` and `TableSerializer` to allow `table_to_import_to` field in the request payload and hence allow the creation of temp table.
## Problem
We want to be able to create temp tables so that we can import records from the temp table to the existing table with the help of `table_to_import_to` parameter.

## Proposed Solution
Since we want to be able to retain the information of the temp table in case the session abruptly ends we should create a regular table and the info that the table is 'temporary' will be in the Django `Table` model.



Discussion:

Resolving patch:
diff --git a/mathesar/api/serializers/tables.py b/mathesar/api/serializers/tables.py
--- a/mathesar/api/serializers/tables.py
+++ b/mathesar/api/serializers/tables.py
@@ -29,13 +29,16 @@ class TableSerializer(MathesarErrorMessageMixin, serializers.ModelSerializer):
     type_suggestions_url = serializers.SerializerMethodField()
     previews_url = serializers.SerializerMethodField()
     name = serializers.CharField(required=False, allow_blank=True, default='')
+    import_target = serializers.PrimaryKeyRelatedField(
+        required=False, allow_null=True, queryset=Table.current_objects.all()
+    )
     data_files = serializers.PrimaryKeyRelatedField(
         required=False, many=True, queryset=DataFile.objects.all()
     )
 
     class Meta:
         model = Table
-        fields = ['id', 'name', 'schema', 'created_at', 'updated_at', 'import_verified',
+        fields = ['id', 'name', 'import_target', 'schema', 'created_at', 'updated_at', 'import_verified',
                   'columns', 'records_url', 'constraints_url', 'columns_url',
                   'joinable_tables_url', 'type_suggestions_url', 'previews_url',
                   'data_files', 'has_dependencies']
@@ -97,10 +100,15 @@ def create(self, validated_data):
         schema = validated_data['schema']
         data_files = validated_data.get('data_files')
         name = validated_data.get('name') or gen_table_name(schema, data_files)
+        import_target = validated_data.get('import_target', None)
 
         try:
             if data_files:
                 table = create_table_from_datafile(data_files, name, schema)
+                if import_target:
+                    table.import_target = import_target
+                    table.is_temp = True
+                    table.save()
             else:
                 table = create_empty_table(name, schema)
         except ProgrammingError as e:
diff --git a/mathesar/migrations/0002_auto_20220719_1112.py b/mathesar/migrations/0002_auto_20220719_1112.py
new file mode 100644
--- /dev/null
+++ b/mathesar/migrations/0002_auto_20220719_1112.py
@@ -0,0 +1,24 @@
+# Generated by Django 3.1.14 on 2022-07-19 11:12
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('mathesar', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='table',
+            name='import_target',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='mathesar.table'),
+        ),
+        migrations.AddField(
+            model_name='table',
+            name='is_temp',
+            field=models.BooleanField(blank=True, null=True),
+        ),
+    ]
diff --git a/mathesar/models/base.py b/mathesar/models/base.py
--- a/mathesar/models/base.py
+++ b/mathesar/models/base.py
@@ -178,6 +178,8 @@ class Table(DatabaseObject, Relation):
     schema = models.ForeignKey('Schema', on_delete=models.CASCADE,
                                related_name='tables')
     import_verified = models.BooleanField(blank=True, null=True)
+    import_target = models.ForeignKey('Table', blank=True, null=True, on_delete=models.SET_NULL)
+    is_temp = models.BooleanField(blank=True, null=True)
 
     class Meta:
         constraints = [


Test patch:
diff --git a/mathesar/tests/api/test_table_api.py b/mathesar/tests/api/test_table_api.py
--- a/mathesar/tests/api/test_table_api.py
+++ b/mathesar/tests/api/test_table_api.py
@@ -65,6 +65,7 @@ def check_table_response(response_table, table, table_name):
     assert response_table['id'] == table.id
     assert response_table['name'] == table_name
     assert response_table['schema'] == table.schema.id
+    assert 'import_target' in response_table
     assert 'created_at' in response_table
     assert 'updated_at' in response_table
     assert 'has_dependencies' in response_table
@@ -99,13 +100,15 @@ def check_table_filter_response(response, status_code=None, count=None):
         assert len(response_data['results']) == count
 
 
-def _create_table(client, data_files, table_name, schema):
+def _create_table(client, data_files, table_name, schema, import_target_table):
     body = {
         'name': table_name,
         'schema': schema.id,
     }
     if data_files is not None:
         body['data_files'] = [df.id for df in data_files]
+        if import_target_table is not None:
+            body['import_target'] = import_target_table.id
 
     response = client.post('/api/db/v0/tables/', body)
     response_table = response.json()
@@ -128,17 +131,18 @@ def _get_expected_name(table_name, data_file=None):
 
 
 def check_create_table_response(
-    client, name, expt_name, data_file, schema, first_row, column_names
+    client, name, expt_name, data_file, schema, first_row, column_names, import_target_table
 ):
     num_tables = Table.objects.count()
 
-    response, response_table, table = _create_table(client, [data_file], name, schema)
+    response, response_table, table = _create_table(client, [data_file], name, schema, import_target_table)
 
     assert response.status_code == 201
     assert Table.objects.count() == num_tables + 1
     assert table.get_records()[0] == first_row
     assert all([col in table.sa_column_names for col in column_names])
     assert data_file.table_imported_to.id == table.id
+    assert table.import_target == import_target_table
     check_table_response(response_table, table, expt_name)
 
 
@@ -490,7 +494,21 @@ def test_table_create_from_datafile(client, data_file, schema, table_name):
                     'Application SN', 'Title', 'Patent Expiration Date']
 
     check_create_table_response(
-        client, table_name, expt_name, data_file, schema, first_row, column_names
+        client, table_name, expt_name, data_file, schema, first_row, column_names, import_target_table=None
+    )
+
+
+@pytest.mark.parametrize('table_name', ['Test Table Create From Datafile', ''])
+def test_table_create_from_datafile_with_import_target(client, data_file, schema, table_name):
+    _, _, import_target_table = _create_table(client, None, 'target_table', schema, import_target_table=None)
+    expt_name = _get_expected_name(table_name, data_file=data_file)
+    first_row = (1, 'NASA Kennedy Space Center', 'Application', 'KSC-12871', '0',
+                 '13/033,085', 'Polyimide Wire Insulation Repair System', None)
+    column_names = ['Center', 'Status', 'Case Number', 'Patent Number',
+                    'Application SN', 'Title', 'Patent Expiration Date']
+
+    check_create_table_response(
+        client, table_name, expt_name, data_file, schema, first_row, column_names, import_target_table
     )
 
 
@@ -503,7 +521,7 @@ def test_table_create_from_paste(client, schema, paste_data_file, table_name):
                     'Application SN', 'Title', 'Patent Expiration Date']
 
     check_create_table_response(
-        client, table_name, expt_name, paste_data_file, schema, first_row, column_names
+        client, table_name, expt_name, paste_data_file, schema, first_row, column_names, import_target_table=None
     )
 
 
@@ -516,7 +534,7 @@ def test_table_create_from_url(client, schema, url_data_file, table_name):
                     'application_sn', 'title', 'patent_expiration_date']
 
     check_create_table_response(
-        client, table_name, expt_name, url_data_file, schema, first_row, column_names
+        client, table_name, expt_name, url_data_file, schema, first_row, column_names, import_target_table=None
     )
 
 
@@ -527,7 +545,7 @@ def test_table_create_without_datafile(client, schema, data_files, table_name):
     expt_name = _get_expected_name(table_name)
 
     response, response_table, table = _create_table(
-        client, data_files, table_name, schema
+        client, data_files, table_name, schema, import_target_table=None
     )
 
     assert response.status_code == 201
@@ -548,7 +566,7 @@ def test_table_create_name_taken(client, paste_data_file, schema, create_patents
                     'Application SN', 'Title', 'Patent Expiration Date']
 
     check_create_table_response(
-        client, '', expt_name, paste_data_file, schema, first_row, column_names
+        client, '', expt_name, paste_data_file, schema, first_row, column_names, import_target_table=None
     )
 
 
@@ -563,7 +581,7 @@ def test_table_create_base_name_taken(client, data_file, schema, create_patents_
                     'Application SN', 'Title', 'Patent Expiration Date']
 
     check_create_table_response(
-        client, '', expt_name, data_file, schema, first_row, column_names
+        client, '', expt_name, data_file, schema, first_row, column_names, import_target_table=None
     )
 
 
@@ -578,7 +596,7 @@ def test_table_create_base_name_too_long(client, data_file, schema):
                     'Application SN', 'Title', 'Patent Expiration Date']
 
     check_create_table_response(
-        client, '', expt_name, data_file, schema, first_row, column_names
+        client, '', expt_name, data_file, schema, first_row, column_names, import_target_table=None
     )
 
 
@@ -602,7 +620,7 @@ def test_table_create_non_unicode(client, non_unicode_file_path, filename, first
     expt_name = filename
     non_unicode_datafile = create_data_file(non_unicode_file_path, filename)
     check_create_table_response(
-        client, '', expt_name, non_unicode_datafile, schema, first_row, column_names
+        client, '', expt_name, non_unicode_datafile, schema, first_row, column_names, import_target_table=None
     )