Repository: mathesar-foundation/mathesar

Issue:
Make it easier to rename DE query columns
Map `alias`es to `display_name`s separately when doing HTTP requests to update display names. Currently a display name is either defined on an initial column definition or on a transformation definition. Simplify that.


Discussion:

Resolving patch:
diff --git a/mathesar/api/serializers/queries.py b/mathesar/api/serializers/queries.py
--- a/mathesar/api/serializers/queries.py
+++ b/mathesar/api/serializers/queries.py
@@ -10,7 +10,7 @@ class BaseQuerySerializer(MathesarErrorMessageMixin, serializers.ModelSerializer
 
     class Meta:
         model = UIQuery
-        fields = ['schema', 'initial_columns', 'transformations', 'base_table']
+        fields = ['schema', 'initial_columns', 'transformations', 'base_table', 'display_names']
 
     def get_schema(self, uiquery):
         base_table = uiquery.base_table
diff --git a/mathesar/migrations/0009_auto_20221123_1423.py b/mathesar/migrations/0009_auto_20221123_1423.py
new file mode 100644
--- /dev/null
+++ b/mathesar/migrations/0009_auto_20221123_1423.py
@@ -0,0 +1,34 @@
+# Generated by Django 3.1.14 on 2022-11-23 14:23
+
+from django.db import migrations, models
+import mathesar.models.query
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('mathesar', '0008'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='uiquery',
+            name='display_names',
+            field=models.JSONField(blank=True, null=True, validators=[mathesar.models.query._get_validator_for_dict]),
+        ),
+        migrations.AlterField(
+            model_name='uiquery',
+            name='display_options',
+            field=models.JSONField(blank=True, null=True, validators=[mathesar.models.query._get_validator_for_dict]),
+        ),
+        migrations.AlterField(
+            model_name='uiquery',
+            name='initial_columns',
+            field=models.JSONField(validators=[mathesar.models.query._get_validator_for_list_of_dicts, mathesar.models.query._get_validator_for_initial_columns]),
+        ),
+        migrations.AlterField(
+            model_name='uiquery',
+            name='transformations',
+            field=models.JSONField(blank=True, null=True, validators=[mathesar.models.query._get_validator_for_list_of_dicts, mathesar.models.query._get_validator_for_transformations]),
+        ),
+    ]
diff --git a/mathesar/models/query.py b/mathesar/models/query.py
--- a/mathesar/models/query.py
+++ b/mathesar/models/query.py
@@ -46,7 +46,6 @@ def _validator(initial_cols):
                 )
                 raise DictHasBadKeys(message, field=field_name)
             optional_keys = {
-                "display_name",
                 "jp_path",
             }
             valid_keys = {
@@ -150,7 +149,7 @@ class UIQuery(BaseModel, Relation):
         ],
     )
 
-    # dict of column ids/aliases to display options
+    # dict of aliases to display options
     display_options = models.JSONField(
         null=True,
         blank=True,
@@ -159,6 +158,15 @@ class UIQuery(BaseModel, Relation):
         ],
     )
 
+    # dict of aliases to display names
+    display_names = models.JSONField(
+        null=True,
+        blank=True,
+        validators=[
+            _get_validator_for_dict(field_name="display_names"),
+        ],
+    )
+
     def get_records(self, **kwargs):
         return self.db_query.get_records(**kwargs)
 
@@ -191,15 +199,15 @@ def initial_dj_columns(self):
     def initial_columns_described(self):
         return tuple(
             {
-                'alias': col['alias'],
-                'display_name': col.get('display_name')
-            }
-            | {
+                'alias': initial_col['alias'],
+                'display_name': self._get_display_name_for_alias(
+                    initial_col['alias']
+                ),
                 'type': dj_col.db_type.id,
                 'type_options': dj_col._sa_column.type_options,
                 'display_options': dj_col.display_options
             }
-            for col, dj_col in zip(self.initial_columns, self.initial_dj_columns)
+            for initial_col, dj_col in zip(self.initial_columns, self.initial_dj_columns)
         )
 
     def _describe_query_column(self, sa_col):
@@ -340,20 +348,10 @@ def _get_display_options_for_alias(self, alias):
 
     @cached_property
     def _alias_to_display_name(self):
-        display_name_map = {
-            initial_column['alias']: initial_column.get('display_name')
-            for initial_column
-            in self.initial_columns
-        }
-        if self.transformations is not None:
-            display_name_map.update(
-                {
-                    k: v
-                    for transformation in self.transformations
-                    for k, v in transformation.get('display_names', {}).items()
-                }
-            )
-        return display_name_map
+        alias_to_display_name = {}
+        if self.display_names is not None:
+            alias_to_display_name.update(self.display_names)
+        return alias_to_display_name
 
     @property
     def _sa_engine(self):


Test patch:
diff --git a/mathesar/tests/api/query/conftest.py b/mathesar/tests/api/query/conftest.py
--- a/mathesar/tests/api/query/conftest.py
+++ b/mathesar/tests/api/query/conftest.py
@@ -18,14 +18,18 @@ def minimal_patents_query(create_patents_table, get_uid):
         {
             'id': base_table.get_column_by_name('Center').id,
             'alias': 'col1',
-            'display_name': 'Column 1',
         },
         {
             'id': base_table.get_column_by_name('Case Number').id,
             'alias': 'col2',
-            'display_name': 'Column 2',
         },
     ]
+    display_names = {
+        'col1': 'Column 1',
+        'col2': 'Column 2',
+        'Checkout Month': 'Month',
+        'Count': 'Number of Checkouts',
+    }
     display_options = {
         'col1': dict(a=1),
         'col2': dict(b=2),
@@ -34,5 +38,6 @@ def minimal_patents_query(create_patents_table, get_uid):
         base_table=base_table,
         initial_columns=initial_columns,
         display_options=display_options,
+        display_names=display_names,
     )
     return ui_query
diff --git a/mathesar/tests/api/query/test_library_demo_reports.py b/mathesar/tests/api/query/test_library_demo_reports.py
--- a/mathesar/tests/api/query/test_library_demo_reports.py
+++ b/mathesar/tests/api/query/test_library_demo_reports.py
@@ -10,6 +10,10 @@ def create_overdue_books_query(library_ma_tables, get_uid, client):
     request_data = {
         "name": get_uid(),
         "base_table": checkouts.id,
+        "display_names": {
+            "email": "Patron Email",
+            "Title List": "Titles"
+        },
         "initial_columns": [
             {
                 "id": checkouts.get_column_by_name("id").id,
@@ -23,7 +27,6 @@ def create_overdue_books_query(library_ma_tables, get_uid, client):
             }, {
                 "id": patrons.get_column_by_name("Email").id,
                 "alias": "email",
-                "display_name": "Patron Email",
                 "jp_path": [
                     [
                         checkouts.get_column_by_name("Patron").id,
@@ -72,9 +75,6 @@ def create_overdue_books_query(library_ma_tables, get_uid, client):
                     ]
                 },
                 "type": "summarize",
-                "display_names": {
-                    "Title List": "Titles"
-                }
             }
         ],
 
@@ -167,6 +167,10 @@ def create_monthly_checkouts_query(run_overdue_books_scenario, get_uid, client):
             {"id": columns["id"]["id"], "alias": "id"},
             {"id": columns["Checkout Time"]["id"], "alias": "Checkout Time"},
         ],
+        "display_names": {
+            "Checkout Month": "Month",
+            "Count": "Number of Checkouts",
+        },
         "transformations": [
             {
                 "spec": {
@@ -186,10 +190,6 @@ def create_monthly_checkouts_query(run_overdue_books_scenario, get_uid, client):
                     ]
                 },
                 "type": "summarize",
-                "display_names": {
-                    "Checkout Month": "Month",
-                    "Count": "Number of Checkouts"
-                }
             }
         ]
     }
diff --git a/mathesar/tests/api/query/test_query_records_api.py b/mathesar/tests/api/query/test_query_records_api.py
--- a/mathesar/tests/api/query/test_query_records_api.py
+++ b/mathesar/tests/api/query/test_query_records_api.py
@@ -8,16 +8,18 @@
 def joining_patents_query(academics_ma_tables):
     academics_table = academics_ma_tables['academics']
     institutions_table = academics_ma_tables['universities']
+    display_names = {
+        'name': 'name',
+        'institution_name': 'institution name',
+    }
     initial_columns = [
         {
             'id': academics_table.get_column_by_name('name').id,
             'alias': 'name',
-            'display_name': 'name',
         },
         {
             'id': institutions_table.get_column_by_name('name').id,
             'alias': 'institution_name',
-            'display_name': 'institution name',
             'jp_path': [[
                 academics_table.get_column_by_name('institution').id,
                 institutions_table.get_column_by_name('id').id,
@@ -32,6 +34,7 @@ def joining_patents_query(academics_ma_tables):
         base_table=academics_table,
         initial_columns=initial_columns,
         display_options=display_options,
+        display_names=display_names,
     )
     return ui_query
 
diff --git a/mathesar/tests/api/query/test_query_run.py b/mathesar/tests/api/query/test_query_run.py
--- a/mathesar/tests/api/query/test_query_run.py
+++ b/mathesar/tests/api/query/test_query_run.py
@@ -4,21 +4,24 @@
 
 def test_queries_run_minimal(create_patents_table, client):
     base_table = create_patents_table(table_name='patent_query_run_minimal_table')
+    display_names = {
+        'col1': 'Column 1',
+        'col2': 'Column 2',
+    }
     initial_columns = [
         {
             'id': base_table.get_column_by_name('Center').id,
             'alias': 'col1',
-            'display_name': 'Column 1',
         },
         {
             'id': base_table.get_column_by_name('Case Number').id,
             'alias': 'col2',
-            'display_name': 'Column 2',
         },
     ]
     data = {
         'base_table': base_table.id,
         'initial_columns': initial_columns,
+        'display_names': display_names,
         'parameters': {
             'order_by': [
                 {'field': 'col1', 'direction': 'asc'},
@@ -92,12 +95,10 @@ def test_queries_run_deleted_column(create_patents_table, client):
         {
             'id': to_be_deleted_column_id,
             'alias': 'col1',
-            'display_name': 'Column 1',
         },
         {
             'id': base_table.get_column_by_name('Case Number').id,
             'alias': 'col2',
-            'display_name': 'Column 2',
         },
     ]
     data = {
diff --git a/mathesar/tests/api/query/test_query_run_partial_transforms.py b/mathesar/tests/api/query/test_query_run_partial_transforms.py
--- a/mathesar/tests/api/query/test_query_run_partial_transforms.py
+++ b/mathesar/tests/api/query/test_query_run_partial_transforms.py
@@ -74,12 +74,10 @@ def test_partial_summarize_transform(
         {
             'id': base_table.get_column_by_name('Center').id,
             'alias': 'col1',
-            'display_name': 'Column 1',
         },
         {
             'id': base_table.get_column_by_name('Case Number').id,
             'alias': 'col2',
-            'display_name': 'Column 2',
         },
     ]
     input_summarize_transform_json = \
@@ -98,6 +96,7 @@ def test_partial_summarize_transform(
     data = {
         'base_table': base_table.id,
         'initial_columns': initial_columns,
+        'display_names': None,
         'parameters': {
             'order_by': [
                 {'field': 'col1_grouped', 'direction': 'asc'},
@@ -118,7 +117,7 @@ def test_partial_summarize_transform(
         'column_metadata': {
             'col1': {
                 'alias': 'col1',
-                'display_name': 'Column 1',
+                'display_name': None,
                 'display_options': None,
                 'input_alias': None,
                 'input_column_name': 'Center',
@@ -140,7 +139,7 @@ def test_partial_summarize_transform(
             },
             'col2': {
                 'alias': 'col2',
-                'display_name': 'Column 2',
+                'display_name': None,
                 'display_options': None,
                 'input_alias': None,
                 'input_column_name': 'Case Number',
diff --git a/mathesar/tests/query/test_base.py b/mathesar/tests/query/test_base.py
--- a/mathesar/tests/query/test_base.py
+++ b/mathesar/tests/query/test_base.py
@@ -11,12 +11,10 @@ def test_convert_to_db_query(create_patents_table, get_uid):
         {
             'id': col1_dj.id,
             'alias': 'col1',
-            'display_name': 'Column 1',
         },
         {
             'id': col2_dj.id,
             'alias': 'col2',
-            'display_name': 'Column 2',
         },
     ]
     oid = base_table_dj.oid