From bbfb54d95c67e3ef591bc9bca59e41ca08567adb Mon Sep 17 00:00:00 2001 From: Brent Moran Date: Thu, 20 Jun 2024 16:20:50 +0800 Subject: [PATCH 01/13] add column metadata model --- .../migrations/0008_add_metadata_models.py | 46 +++++++++++++++++++ mathesar/models/base.py | 41 +++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 mathesar/migrations/0008_add_metadata_models.py diff --git a/mathesar/migrations/0008_add_metadata_models.py b/mathesar/migrations/0008_add_metadata_models.py new file mode 100644 index 0000000000..13dd921ad3 --- /dev/null +++ b/mathesar/migrations/0008_add_metadata_models.py @@ -0,0 +1,46 @@ +# Generated by Django 4.2.11 on 2024-06-20 08:02 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('mathesar', '0007_users_permissions_remodel'), + ] + + operations = [ + migrations.CreateModel( + name='ColumnMetaData', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('table_oid', models.PositiveIntegerField()), + ('attnum', models.PositiveIntegerField()), + ('bool_input', models.CharField(blank=True, choices=[('dropdown', 'dropdown'), ('checkbox', 'checkbox')])), + ('bool_true', models.CharField(default='True')), + ('bool_false', models.CharField(default='False')), + ('num_min_frac_digits', models.PositiveIntegerField(blank=True)), + ('num_max_frac_digits', models.PositiveIntegerField(blank=True)), + ('num_show_as_perc', models.BooleanField(default=False)), + ('mon_currency_symbol', models.CharField(default='$')), + ('mon_currency_location', models.CharField(choices=[('after-minus', 'after-minus'), ('end-with-space', 'end-with-space')], default='after-minus')), + ('time_format', models.CharField(blank=True)), + ('date_format', models.CharField(blank=True)), + ('duration_min', models.CharField(blank=True, max_length=255)), + ('duration_max', models.CharField(blank=True, max_length=255)), + ('duration_show_units', models.BooleanField(default=True)), + ('database', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mathesar.database')), + ], + ), + migrations.AddConstraint( + model_name='columnmetadata', + constraint=models.UniqueConstraint(fields=('database', 'table_oid', 'attnum'), name='unique_column_metadata'), + ), + migrations.AddConstraint( + model_name='columnmetadata', + constraint=models.CheckConstraint(check=models.Q(('num_max_frac_digits__lte', 20), ('num_min_frac_digits__lte', 20), ('num_min_frac_digits__lte', models.F('num_max_frac_digits'))), name='frac_digits_integrity'), + ), + ] diff --git a/mathesar/models/base.py b/mathesar/models/base.py index de7454dd6d..43b9e1607e 100644 --- a/mathesar/models/base.py +++ b/mathesar/models/base.py @@ -80,3 +80,44 @@ def connection(self): user=self.role.name, password=self.role.password, ) + + +class ColumnMetaData(BaseModel): + database = models.ForeignKey('Database', on_delete=models.CASCADE) + table_oid = models.PositiveIntegerField() + attnum = models.PositiveIntegerField() + bool_input = models.CharField( + choices=[("dropdown", "dropdown"), ("checkbox", "checkbox")], + blank=True + ) + bool_true = models.CharField(default='True') + bool_false = models.CharField(default='False') + num_min_frac_digits = models.PositiveIntegerField(blank=True) + num_max_frac_digits = models.PositiveIntegerField(blank=True) + num_show_as_perc = models.BooleanField(default=False) + mon_currency_symbol = models.CharField(default="$") + mon_currency_location = models.CharField( + choices=[("after-minus", "after-minus"), ("end-with-space", "end-with-space")], + default="after-minus" + ) + time_format = models.CharField(blank=True) + date_format = models.CharField(blank=True) + duration_min = models.CharField(max_length=255, blank=True) + duration_max = models.CharField(max_length=255, blank=True) + duration_show_units = models.BooleanField(default=True) + + class Meta: + constraints = [ + models.UniqueConstraint( + fields=["database", "table_oid", "attnum"], + name="unique_column_metadata" + ), + models.CheckConstraint( + check=( + models.Q(num_max_frac_digits__lte=20) + & models.Q(num_min_frac_digits__lte=20) + & models.Q(num_min_frac_digits__lte=models.F("num_max_frac_digits")) + ), + name="frac_digits_integrity" + ) + ] From 162debe0da7ed7a23ac358e6b949d19f5c9432ca Mon Sep 17 00:00:00 2001 From: Brent Moran Date: Thu, 20 Jun 2024 16:51:36 +0800 Subject: [PATCH 02/13] move column rpc module to make room for metadata --- mathesar/rpc/columns/__init__.py | 1 + mathesar/rpc/{columns.py => columns/base.py} | 0 mathesar/tests/rpc/test_columns.py | 18 +++++++++--------- 3 files changed, 10 insertions(+), 9 deletions(-) create mode 100644 mathesar/rpc/columns/__init__.py rename mathesar/rpc/{columns.py => columns/base.py} (100%) diff --git a/mathesar/rpc/columns/__init__.py b/mathesar/rpc/columns/__init__.py new file mode 100644 index 0000000000..3cd5390e65 --- /dev/null +++ b/mathesar/rpc/columns/__init__.py @@ -0,0 +1 @@ +from mathesar.rpc.columns.base import * diff --git a/mathesar/rpc/columns.py b/mathesar/rpc/columns/base.py similarity index 100% rename from mathesar/rpc/columns.py rename to mathesar/rpc/columns/base.py diff --git a/mathesar/tests/rpc/test_columns.py b/mathesar/tests/rpc/test_columns.py index 1afd813a33..3069f04d13 100644 --- a/mathesar/tests/rpc/test_columns.py +++ b/mathesar/tests/rpc/test_columns.py @@ -84,9 +84,9 @@ def mock_display_options(_database_id, _table_oid, attnums, user): 'minimum_fraction_digits': 2 } ] - monkeypatch.setattr(columns, 'connect', mock_connect) - monkeypatch.setattr(columns, 'get_column_info_for_table', mock_column_info) - monkeypatch.setattr(columns, 'get_raw_display_options', mock_display_options) + monkeypatch.setattr(columns.base, 'connect', mock_connect) + monkeypatch.setattr(columns.base, 'get_column_info_for_table', mock_column_info) + monkeypatch.setattr(columns.base, 'get_raw_display_options', mock_display_options) expect_col_list = { 'column_info': ( { @@ -160,8 +160,8 @@ def mock_column_alter(_table_oid, _column_data_list, conn): raise AssertionError('incorrect parameters passed') return 1 - monkeypatch.setattr(columns, 'connect', mock_connect) - monkeypatch.setattr(columns, 'alter_columns_in_table', mock_column_alter) + monkeypatch.setattr(columns.base, 'connect', mock_connect) + monkeypatch.setattr(columns.base, 'alter_columns_in_table', mock_column_alter) actual_result = columns.patch( column_data_list=column_data_list, table_oid=table_oid, @@ -193,8 +193,8 @@ def mock_column_create(_table_oid, _column_data_list, conn): raise AssertionError('incorrect parameters passed') return [3, 4] - monkeypatch.setattr(columns, 'connect', mock_connect) - monkeypatch.setattr(columns, 'add_columns_to_table', mock_column_create) + monkeypatch.setattr(columns.base, 'connect', mock_connect) + monkeypatch.setattr(columns.base, 'add_columns_to_table', mock_column_create) actual_result = columns.add( column_data_list=column_data_list, table_oid=table_oid, @@ -226,8 +226,8 @@ def mock_column_drop(_table_oid, _column_attnums, conn): raise AssertionError('incorrect parameters passed') return 3 - monkeypatch.setattr(columns, 'connect', mock_connect) - monkeypatch.setattr(columns, 'drop_columns_from_table', mock_column_drop) + monkeypatch.setattr(columns.base, 'connect', mock_connect) + monkeypatch.setattr(columns.base, 'drop_columns_from_table', mock_column_drop) actual_result = columns.delete( column_attnums=column_attnums, table_oid=table_oid, From 910ccf226077781476696604ccc00361b06e0dd6 Mon Sep 17 00:00:00 2001 From: Brent Moran Date: Thu, 20 Jun 2024 16:55:46 +0800 Subject: [PATCH 03/13] wire up metadata list function --- config/settings/common_settings.py | 1 + mathesar/rpc/columns/metadata.py | 60 ++++++++++++++++++++++++++++ mathesar/tests/rpc/test_endpoints.py | 5 +++ mathesar/utils/columns.py | 5 +++ 4 files changed, 71 insertions(+) create mode 100644 mathesar/rpc/columns/metadata.py diff --git a/config/settings/common_settings.py b/config/settings/common_settings.py index 7697ed053c..c37d6d23ca 100644 --- a/config/settings/common_settings.py +++ b/config/settings/common_settings.py @@ -67,6 +67,7 @@ def pipe_delim(pipe_string): MODERNRPC_METHODS_MODULES = [ 'mathesar.rpc.connections', 'mathesar.rpc.columns', + 'mathesar.rpc.columns.metadata', 'mathesar.rpc.schemas', 'mathesar.rpc.tables' ] diff --git a/mathesar/rpc/columns/metadata.py b/mathesar/rpc/columns/metadata.py new file mode 100644 index 0000000000..c3f3169d49 --- /dev/null +++ b/mathesar/rpc/columns/metadata.py @@ -0,0 +1,60 @@ +""" +Classes and functions exposed to the RPC endpoint for managing column metadata. +""" +from typing import Literal, Optional, TypedDict + +from modernrpc.core import rpc_method, REQUEST_KEY +from modernrpc.auth.basic import http_basic_auth_login_required + +from mathesar.rpc.exceptions.handlers import handle_rpc_exceptions +from mathesar.utils.columns import get_columns_meta_data + + +class ColumnMetaData(TypedDict): + database: int + table_oid: int + attnum: int + bool_input: Optional[Literal["dropdown", "checkbox"]] + bool_true: Optional[str] + bool_false: Optional[str] + num_min_frac_digits: Optional[int] + num_max_frac_digits: Optional[int] + num_show_as_perc: Optional[bool] + mon_currency_symbol: Optional[str] + mon_currency_location: Optional[Literal["after-minus", "end-with-space"]] + time_format: Optional[str] + date_format: Optional[str] + duration_min: Optional[str] + duration_max: Optional[str] + duration_show_units: Optional[bool] + + @classmethod + def from_db_model(cls, db_model): + return cls( + database=db_model.database.id, + table_oid=db_model.table_oid, + attnum=db_model.attnum, + bool_input=db_model.bool_input, + bool_true=db_model.bool_true, + bool_false=db_model.bool_false, + num_min_frac_digits=db_model.num_min_frac_digits, + num_max_frac_digits=db_model.num_max_frac_digits, + num_show_as_perc=db_model.num_show_as_perc, + mon_currency_symbol=db_model.mon_currency_symbol, + mon_currency_location=db_model.mon_currency_location, + time_format=db_model.time_format, + date_format=db_model.date_format, + duration_min=db_model.duration_min, + duration_max=db_model.duration_max, + duration_show_units=db_model.duration_show_units, + ) + + +@rpc_method(name="columns.metadata.list") +@http_basic_auth_login_required +@handle_rpc_exceptions +def list_(*, table_oid: int, database_id: int, **kwargs) -> list[ColumnMetaData]: + columns_meta_data = get_columns_meta_data(table_oid, database_id) + return [ + ColumnMetaData.from_db_model(db_model) for db_model in columns_meta_data + ] diff --git a/mathesar/tests/rpc/test_endpoints.py b/mathesar/tests/rpc/test_endpoints.py index 35a7cd75e1..6f940c272c 100644 --- a/mathesar/tests/rpc/test_endpoints.py +++ b/mathesar/tests/rpc/test_endpoints.py @@ -34,6 +34,11 @@ "columns.add", [user_is_authenticated] ), + ( + columns.metadata.list_, + "columns.metadata.list", + [user_is_authenticated] + ), ( connections.add_from_known_connection, "connections.add_from_known_connection", diff --git a/mathesar/utils/columns.py b/mathesar/utils/columns.py index 97075a7d52..2429ad59e0 100644 --- a/mathesar/utils/columns.py +++ b/mathesar/utils/columns.py @@ -1,4 +1,5 @@ from mathesar.models.deprecated import Column +from mathesar.models.base import ColumnMetaData # This should be replaced once we have the ColumnMetadata model sorted out. @@ -14,3 +15,7 @@ def get_raw_display_options(database_id, table_oid, attnums, user): ) if c.display_options is not None ] + + +def get_columns_meta_data(table_oid, database_id): + return ColumnMetaData.filter(database__id=database_id, table_oid=table_oid) From 2fedad29d2ad846328e68ae4366080366523ef84 Mon Sep 17 00:00:00 2001 From: Brent Moran Date: Thu, 20 Jun 2024 17:31:27 +0800 Subject: [PATCH 04/13] set up metadata module for documentation --- mathesar/rpc/columns/__init__.py | 2 +- mathesar/rpc/columns/metadata.py | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/mathesar/rpc/columns/__init__.py b/mathesar/rpc/columns/__init__.py index 3cd5390e65..4b40b38c84 100644 --- a/mathesar/rpc/columns/__init__.py +++ b/mathesar/rpc/columns/__init__.py @@ -1 +1 @@ -from mathesar.rpc.columns.base import * +from .base import * # noqa diff --git a/mathesar/rpc/columns/metadata.py b/mathesar/rpc/columns/metadata.py index c3f3169d49..60ac733fa7 100644 --- a/mathesar/rpc/columns/metadata.py +++ b/mathesar/rpc/columns/metadata.py @@ -3,7 +3,7 @@ """ from typing import Literal, Optional, TypedDict -from modernrpc.core import rpc_method, REQUEST_KEY +from modernrpc.core import rpc_method from modernrpc.auth.basic import http_basic_auth_login_required from mathesar.rpc.exceptions.handlers import handle_rpc_exceptions @@ -54,6 +54,16 @@ def from_db_model(cls, db_model): @http_basic_auth_login_required @handle_rpc_exceptions def list_(*, table_oid: int, database_id: int, **kwargs) -> list[ColumnMetaData]: + """ + List metadata associated with columns for a table. Exposed as `list`. + + Args: + table_oid: Identity of the table in the user's database. + database_id: The Django id of the database containing the table. + + Returns: + A list of column meta data objects. + """ columns_meta_data = get_columns_meta_data(table_oid, database_id) return [ ColumnMetaData.from_db_model(db_model) for db_model in columns_meta_data From 0d5bee1e889e1eb76ce3e022c852282555111081 Mon Sep 17 00:00:00 2001 From: Brent Moran Date: Thu, 20 Jun 2024 17:31:56 +0800 Subject: [PATCH 05/13] add metadata module into documentation --- docs/docs/api/rpc.md | 15 +++++++++++---- docs/mkdocs.yml | 3 +-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/docs/docs/api/rpc.md b/docs/docs/api/rpc.md index b149a9045d..8f2bfc6846 100644 --- a/docs/docs/api/rpc.md +++ b/docs/docs/api/rpc.md @@ -39,7 +39,7 @@ To use an RPC function: --- -::: mathesar.rpc.connections +::: connections options: members: - add_from_known_connection @@ -49,7 +49,7 @@ To use an RPC function: --- -::: mathesar.rpc.schemas +::: schemas options: members: - list_ @@ -59,7 +59,7 @@ To use an RPC function: --- -::: mathesar.rpc.tables +::: tables options: members: - list_ @@ -72,7 +72,7 @@ To use an RPC function: --- -::: mathesar.rpc.columns +::: columns options: members: - list_ @@ -84,6 +84,13 @@ To use an RPC function: - SettableColumnInfo - TypeOptions - ColumnDefault + +--- + +::: columns.metadata + options: + members: + - list_ ## Responses diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 58875327c4..0db1e9c1e8 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -57,12 +57,11 @@ plugins: - mkdocstrings: handlers: python: - paths: [..] + paths: [../mathesar/rpc/] options: docstring_style: google separate_signature: true show_root_heading: true - show_root_full_path: false show_source: false show_symbol_type_heading: true group_by_category: false From 03f3b07add3112cd33fd1f8a68f155e9355565bc Mon Sep 17 00:00:00 2001 From: Brent Moran Date: Thu, 20 Jun 2024 17:54:06 +0800 Subject: [PATCH 06/13] improve docs formatting for RPC functions --- docs/docs/api/rpc.md | 11 ++++++----- docs/mkdocs.yml | 5 +++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/docs/api/rpc.md b/docs/docs/api/rpc.md index 8f2bfc6846..f2f4a0b214 100644 --- a/docs/docs/api/rpc.md +++ b/docs/docs/api/rpc.md @@ -37,7 +37,7 @@ To use an RPC function: } ``` ---- +## Connections ::: connections options: @@ -47,7 +47,7 @@ To use an RPC function: - grant_access_to_user - DBModelReturn ---- +## Schemas ::: schemas options: @@ -57,7 +57,7 @@ To use an RPC function: - delete - SchemaInfo ---- +## Tables ::: tables options: @@ -70,7 +70,7 @@ To use an RPC function: - TableInfo - SettableTableInfo ---- +## Columns ::: columns options: @@ -85,12 +85,13 @@ To use an RPC function: - TypeOptions - ColumnDefault ---- +## Column Metadata ::: columns.metadata options: members: - list_ + - ColumnMetaData ## Responses diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 0db1e9c1e8..e2488e3424 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -59,11 +59,12 @@ plugins: python: paths: [../mathesar/rpc/] options: + heading_level: 3 docstring_style: google separate_signature: true - show_root_heading: true + show_root_toc_entry: false + show_root_members_full_path: true show_source: false - show_symbol_type_heading: true group_by_category: false theme: From 2a9eddc3b75b8164ac8f9c593ffe2713a31812b9 Mon Sep 17 00:00:00 2001 From: Brent Moran Date: Thu, 20 Jun 2024 17:54:39 +0800 Subject: [PATCH 07/13] add documentation for metadata object, fix attribute name --- mathesar/rpc/columns/metadata.py | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/mathesar/rpc/columns/metadata.py b/mathesar/rpc/columns/metadata.py index 60ac733fa7..e8268c7faf 100644 --- a/mathesar/rpc/columns/metadata.py +++ b/mathesar/rpc/columns/metadata.py @@ -11,7 +11,34 @@ class ColumnMetaData(TypedDict): - database: int + """ + Metadata for a column in a table. + + Only the + - database, + - table_oid, and + - attnum + keys are required. + + Attributes: + database_id: The Django id of the database containing the table. + table_oid: The OID of the table containing the column + attnum: The attnum of the column in the table. + bool_input: How the input for a boolean column should be shown. + bool_true: A string to display for `true` values. + bool_false: A string to display for `false` values. + num_min_frac_digits: Minimum digits shown after the decimal point. + num_max_frac_digits: Maximum digits shown after the decimal point. + num_show_as_perc: Whether to show a numeric value as a percentage. + mon_currency_symbol: The currency symbol shown for money value. + mon_currency_location: Where the currency symbol should be shown. + time_format: A string representing the format of time values. + date_format: A string representing the format of date values. + duration_min: Optional[str] + duration_max: Optional[str] + duration_show_units: Optional[bool] + """ + database_id: int table_oid: int attnum: int bool_input: Optional[Literal["dropdown", "checkbox"]] @@ -31,7 +58,7 @@ class ColumnMetaData(TypedDict): @classmethod def from_db_model(cls, db_model): return cls( - database=db_model.database.id, + database_id=db_model.database.id, table_oid=db_model.table_oid, attnum=db_model.attnum, bool_input=db_model.bool_input, From d55ce1b8ab63f58c25285110fbc047035fe09ff0 Mon Sep 17 00:00:00 2001 From: Brent Moran Date: Fri, 21 Jun 2024 16:59:25 +0800 Subject: [PATCH 08/13] complete documentation of column metadata object --- mathesar/rpc/columns/metadata.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/mathesar/rpc/columns/metadata.py b/mathesar/rpc/columns/metadata.py index e8268c7faf..dd867608a5 100644 --- a/mathesar/rpc/columns/metadata.py +++ b/mathesar/rpc/columns/metadata.py @@ -14,11 +14,7 @@ class ColumnMetaData(TypedDict): """ Metadata for a column in a table. - Only the - - database, - - table_oid, and - - attnum - keys are required. + Only the `database`, `table_oid`, and `attnum` keys are required. Attributes: database_id: The Django id of the database containing the table. @@ -34,9 +30,9 @@ class ColumnMetaData(TypedDict): mon_currency_location: Where the currency symbol should be shown. time_format: A string representing the format of time values. date_format: A string representing the format of date values. - duration_min: Optional[str] - duration_max: Optional[str] - duration_show_units: Optional[bool] + duration_min: The smallest unit for displaying durations. + duration_max: The largest unit for displaying durations. + duration_show_units: Whether to show the units for durations. """ database_id: int table_oid: int From b64752e75dfd6e72d711dbf56a75de19700d061d Mon Sep 17 00:00:00 2001 From: Brent Moran Date: Mon, 24 Jun 2024 14:35:31 +0800 Subject: [PATCH 09/13] remove display options from column list return --- docs/docs/api/rpc.md | 1 - mathesar/rpc/columns/base.py | 32 +-------- mathesar/tests/rpc/test_columns.py | 101 ++++++++++------------------- 3 files changed, 38 insertions(+), 96 deletions(-) diff --git a/docs/docs/api/rpc.md b/docs/docs/api/rpc.md index f2f4a0b214..c0460a0a9a 100644 --- a/docs/docs/api/rpc.md +++ b/docs/docs/api/rpc.md @@ -79,7 +79,6 @@ To use an RPC function: - add - patch - delete - - ColumnListReturn - ColumnInfo - SettableColumnInfo - TypeOptions diff --git a/mathesar/rpc/columns/base.py b/mathesar/rpc/columns/base.py index 6f198d8553..f7e53704c5 100644 --- a/mathesar/rpc/columns/base.py +++ b/mathesar/rpc/columns/base.py @@ -12,7 +12,6 @@ from db.columns.operations.select import get_column_info_for_table from mathesar.rpc.exceptions.handlers import handle_rpc_exceptions from mathesar.rpc.utils import connect -from mathesar.utils.columns import get_raw_display_options class TypeOptions(TypedDict, total=False): @@ -171,49 +170,24 @@ def from_dict(cls, col_info): ) -class ColumnListReturn(TypedDict): - """ - Information about the columns of a table. - - Attributes: - column_info: Column information from the user's database. - display_options: Display metadata managed by Mathesar. - """ - column_info: list[ColumnInfo] - display_options: list[dict] - - @rpc_method(name="columns.list") @http_basic_auth_login_required @handle_rpc_exceptions -def list_(*, table_oid: int, database_id: int, **kwargs) -> ColumnListReturn: +def list_(*, table_oid: int, database_id: int, **kwargs) -> list[ColumnInfo]: """ List information about columns for a table. Exposed as `list`. - Also return display options for each column, if they're defined. - Args: table_oid: Identity of the table in the user's database. database_id: The Django id of the database containing the table. Returns: - A list of column details, and a separate list of display options. + A list of column details. """ user = kwargs.get(REQUEST_KEY).user with connect(database_id, user) as conn: raw_column_info = get_column_info_for_table(table_oid, conn) - column_info, attnums = tuple( - zip( - *[(ColumnInfo.from_dict(col), col['id']) for col in raw_column_info] - ) - ) - display_options = get_raw_display_options( - database_id, table_oid, attnums, user - ) - return ColumnListReturn( - column_info=column_info, - display_options=display_options, - ) + return [ColumnInfo.from_dict(col) for col in raw_column_info] @rpc_method(name="columns.add") diff --git a/mathesar/tests/rpc/test_columns.py b/mathesar/tests/rpc/test_columns.py index 3069f04d13..6c54a9df39 100644 --- a/mathesar/tests/rpc/test_columns.py +++ b/mathesar/tests/rpc/test_columns.py @@ -66,74 +66,43 @@ def mock_column_info(_table_oid, conn): }, ] - def mock_display_options(_database_id, _table_oid, attnums, user): - if ( - database_id != 2 - or table_oid != 23457 - or attnums != (1, 2, 4, 8, 10) - or user.username != 'alice' - ): - raise AssertionError("incorrect parameters passed") - return [ - { - 'id': 4, - 'use_grouping': 'true', - 'number_format': 'english', - 'show_as_percentage': False, - 'maximum_fraction_digits': 2, - 'minimum_fraction_digits': 2 - } - ] monkeypatch.setattr(columns.base, 'connect', mock_connect) monkeypatch.setattr(columns.base, 'get_column_info_for_table', mock_column_info) - monkeypatch.setattr(columns.base, 'get_raw_display_options', mock_display_options) - expect_col_list = { - 'column_info': ( - { - 'id': 1, 'name': 'id', 'type': 'integer', - 'default': {'value': 'identity', 'is_dynamic': True}, - 'nullable': False, 'description': None, 'primary_key': True, - 'type_options': None, - 'has_dependents': True - }, { - 'id': 2, 'name': 'numcol', 'type': 'numeric', - 'default': {'value': "'8'::numeric", 'is_dynamic': False}, - 'nullable': True, - 'description': 'My super numeric column', - 'primary_key': False, - 'type_options': None, - 'has_dependents': False - }, { - 'id': 4, 'name': 'numcolmod', 'type': 'numeric', - 'default': None, - 'nullable': True, 'description': None, 'primary_key': False, - 'type_options': {'scale': 3, 'precision': 5}, - 'has_dependents': False - }, { - 'id': 8, 'name': 'ivlcolmod', 'type': 'interval', - 'default': None, - 'nullable': True, 'description': None, 'primary_key': False, - 'type_options': {'fields': 'day to second'}, - 'has_dependents': False - }, { - 'id': 10, 'name': 'arrcol', 'type': '_array', - 'default': None, - 'nullable': True, 'description': None, 'primary_key': False, - 'type_options': {'item_type': 'character varying', 'length': 3}, - 'has_dependents': False - } - ), - 'display_options': [ - { - 'id': 4, - 'use_grouping': 'true', - 'number_format': 'english', - 'show_as_percentage': False, - 'maximum_fraction_digits': 2, - 'minimum_fraction_digits': 2 - } - ] - } + expect_col_list = [ + { + 'id': 1, 'name': 'id', 'type': 'integer', + 'default': {'value': 'identity', 'is_dynamic': True}, + 'nullable': False, 'description': None, 'primary_key': True, + 'type_options': None, + 'has_dependents': True + }, { + 'id': 2, 'name': 'numcol', 'type': 'numeric', + 'default': {'value': "'8'::numeric", 'is_dynamic': False}, + 'nullable': True, + 'description': 'My super numeric column', + 'primary_key': False, + 'type_options': None, + 'has_dependents': False + }, { + 'id': 4, 'name': 'numcolmod', 'type': 'numeric', + 'default': None, + 'nullable': True, 'description': None, 'primary_key': False, + 'type_options': {'scale': 3, 'precision': 5}, + 'has_dependents': False + }, { + 'id': 8, 'name': 'ivlcolmod', 'type': 'interval', + 'default': None, + 'nullable': True, 'description': None, 'primary_key': False, + 'type_options': {'fields': 'day to second'}, + 'has_dependents': False + }, { + 'id': 10, 'name': 'arrcol', 'type': '_array', + 'default': None, + 'nullable': True, 'description': None, 'primary_key': False, + 'type_options': {'item_type': 'character varying', 'length': 3}, + 'has_dependents': False + } + ] actual_col_list = columns.list_(table_oid=23457, database_id=2, request=request) assert actual_col_list == expect_col_list From af82a18ffff300c03068aa810e712ef11c22b32b Mon Sep 17 00:00:00 2001 From: Brent Moran Date: Tue, 25 Jun 2024 21:50:42 +0800 Subject: [PATCH 10/13] remove deprecated function and other cruft --- mathesar/tests/rpc/test_columns.py | 4 ++-- mathesar/utils/columns.py | 16 ---------------- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/mathesar/tests/rpc/test_columns.py b/mathesar/tests/rpc/test_columns.py index 6c54a9df39..70c21a8f2f 100644 --- a/mathesar/tests/rpc/test_columns.py +++ b/mathesar/tests/rpc/test_columns.py @@ -19,7 +19,7 @@ def test_columns_list(rf, monkeypatch): @contextmanager def mock_connect(_database_id, user): - if _database_id == 2 and user.username == 'alice': + if _database_id == database_id and user.username == 'alice': try: yield True finally: @@ -103,7 +103,7 @@ def mock_column_info(_table_oid, conn): 'has_dependents': False } ] - actual_col_list = columns.list_(table_oid=23457, database_id=2, request=request) + actual_col_list = columns.list_(table_oid=23457, database_id=database_id, request=request) assert actual_col_list == expect_col_list diff --git a/mathesar/utils/columns.py b/mathesar/utils/columns.py index 2429ad59e0..7c48cd914e 100644 --- a/mathesar/utils/columns.py +++ b/mathesar/utils/columns.py @@ -1,21 +1,5 @@ -from mathesar.models.deprecated import Column from mathesar.models.base import ColumnMetaData -# This should be replaced once we have the ColumnMetadata model sorted out. -def get_raw_display_options(database_id, table_oid, attnums, user): - """Get display options for the columns from Django.""" - if user.metadata_privileges(database_id) is not None: - return [ - {"id": c.attnum} | c.display_options - for c in Column.current_objects.filter( - table__schema__database__id=database_id, - table__oid=table_oid, - attnum__in=attnums - ) - if c.display_options is not None - ] - - def get_columns_meta_data(table_oid, database_id): return ColumnMetaData.filter(database__id=database_id, table_oid=table_oid) From 022834ab1f3f70dba99aecb1995b4f8bb31a9715 Mon Sep 17 00:00:00 2001 From: Brent Moran Date: Tue, 25 Jun 2024 21:56:33 +0800 Subject: [PATCH 11/13] Move RPC columns tests to new namespace --- mathesar/tests/rpc/{test_columns.py => columns/test_base.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename mathesar/tests/rpc/{test_columns.py => columns/test_base.py} (100%) diff --git a/mathesar/tests/rpc/test_columns.py b/mathesar/tests/rpc/columns/test_base.py similarity index 100% rename from mathesar/tests/rpc/test_columns.py rename to mathesar/tests/rpc/columns/test_base.py From 56fabcf4a63d8edad764c1ac01b8d8408e9ab84b Mon Sep 17 00:00:00 2001 From: Brent Moran Date: Tue, 25 Jun 2024 22:47:03 +0800 Subject: [PATCH 12/13] add basic test for metadata RPC function --- mathesar/tests/rpc/columns/test_base.py | 2 +- mathesar/tests/rpc/columns/test_metadata.py | 59 +++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 mathesar/tests/rpc/columns/test_metadata.py diff --git a/mathesar/tests/rpc/columns/test_base.py b/mathesar/tests/rpc/columns/test_base.py index 70c21a8f2f..657145f514 100644 --- a/mathesar/tests/rpc/columns/test_base.py +++ b/mathesar/tests/rpc/columns/test_base.py @@ -1,5 +1,5 @@ """ -This file tests the column listing function. +This file tests the column RPC functions. Fixtures: rf(pytest-django): Provides mocked `Request` objects. diff --git a/mathesar/tests/rpc/columns/test_metadata.py b/mathesar/tests/rpc/columns/test_metadata.py new file mode 100644 index 0000000000..f9b6e087c0 --- /dev/null +++ b/mathesar/tests/rpc/columns/test_metadata.py @@ -0,0 +1,59 @@ +""" +This file tests the column metadata RPC functions. + +Fixtures: + monkeypatch(pytest): Lets you monkeypatch an object for testing. +""" +from mathesar.models.base import ColumnMetaData, Database, Server +from mathesar.rpc.columns import metadata + + +# TODO consider mocking out ColumnMetaData queryset for this test +def test_columns_meta_data_list(monkeypatch): + database_id = 2 + table_oid = 123456 + + def mock_get_columns_meta_data(_table_oid, _database_id): + server_model = Server(id=2, host='example.com', port=5432) + db_model = Database(id=_database_id, name='mymathesardb', server=server_model) + return [ + ColumnMetaData( + database=db_model, table_oid=_table_oid, attnum=2, + bool_input="dropdown", bool_true="TRUE", bool_false="FALSE", + num_min_frac_digits=5, num_max_frac_digits=10, num_show_as_perc=False, + mon_currency_symbol="EUR", mon_currency_location="end-with-space", + time_format=None, date_format=None, + duration_min=None, duration_max=None, duration_show_units=True, + ), + ColumnMetaData( + database=db_model, table_oid=_table_oid, attnum=8, + bool_input="checkbox", bool_true="true", bool_false="false", + num_min_frac_digits=2, num_max_frac_digits=8, num_show_as_perc=True, + mon_currency_symbol="$", mon_currency_location="after-minus", + time_format=None, date_format=None, + duration_min=None, duration_max=None, duration_show_units=True, + ) + ] + + monkeypatch.setattr(metadata, "get_columns_meta_data", mock_get_columns_meta_data) + + expect_metadata_list = [ + metadata.ColumnMetaData( + database_id=database_id, table_oid=table_oid, attnum=2, + bool_input="dropdown", bool_true="TRUE", bool_false="FALSE", + num_min_frac_digits=5, num_max_frac_digits=10, num_show_as_perc=False, + mon_currency_symbol="EUR", mon_currency_location="end-with-space", + time_format=None, date_format=None, + duration_min=None, duration_max=None, duration_show_units=True, + ), + metadata.ColumnMetaData( + database_id=database_id, table_oid=table_oid, attnum=8, + bool_input="checkbox", bool_true="true", bool_false="false", + num_min_frac_digits=2, num_max_frac_digits=8, num_show_as_perc=True, + mon_currency_symbol="$", mon_currency_location="after-minus", + time_format=None, date_format=None, + duration_min=None, duration_max=None, duration_show_units=True, + ), + ] + actual_metadata_list = metadata.list_(table_oid=table_oid, database_id=database_id) + assert actual_metadata_list == expect_metadata_list From ebc8b15ac349f8cbaf1b4ab2744d000ec1a1781c Mon Sep 17 00:00:00 2001 From: Brent Moran Date: Tue, 25 Jun 2024 22:48:00 +0800 Subject: [PATCH 13/13] fix improperly copied variable name --- mathesar/rpc/columns/metadata.py | 36 ++++++++++++++++---------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/mathesar/rpc/columns/metadata.py b/mathesar/rpc/columns/metadata.py index dd867608a5..561355e04e 100644 --- a/mathesar/rpc/columns/metadata.py +++ b/mathesar/rpc/columns/metadata.py @@ -52,24 +52,24 @@ class ColumnMetaData(TypedDict): duration_show_units: Optional[bool] @classmethod - def from_db_model(cls, db_model): + def from_model(cls, model): return cls( - database_id=db_model.database.id, - table_oid=db_model.table_oid, - attnum=db_model.attnum, - bool_input=db_model.bool_input, - bool_true=db_model.bool_true, - bool_false=db_model.bool_false, - num_min_frac_digits=db_model.num_min_frac_digits, - num_max_frac_digits=db_model.num_max_frac_digits, - num_show_as_perc=db_model.num_show_as_perc, - mon_currency_symbol=db_model.mon_currency_symbol, - mon_currency_location=db_model.mon_currency_location, - time_format=db_model.time_format, - date_format=db_model.date_format, - duration_min=db_model.duration_min, - duration_max=db_model.duration_max, - duration_show_units=db_model.duration_show_units, + database_id=model.database.id, + table_oid=model.table_oid, + attnum=model.attnum, + bool_input=model.bool_input, + bool_true=model.bool_true, + bool_false=model.bool_false, + num_min_frac_digits=model.num_min_frac_digits, + num_max_frac_digits=model.num_max_frac_digits, + num_show_as_perc=model.num_show_as_perc, + mon_currency_symbol=model.mon_currency_symbol, + mon_currency_location=model.mon_currency_location, + time_format=model.time_format, + date_format=model.date_format, + duration_min=model.duration_min, + duration_max=model.duration_max, + duration_show_units=model.duration_show_units, ) @@ -89,5 +89,5 @@ def list_(*, table_oid: int, database_id: int, **kwargs) -> list[ColumnMetaData] """ columns_meta_data = get_columns_meta_data(table_oid, database_id) return [ - ColumnMetaData.from_db_model(db_model) for db_model in columns_meta_data + ColumnMetaData.from_model(model) for model in columns_meta_data ]