diff --git a/docs/docs/api/rpc.md b/docs/docs/api/rpc.md index f3fd42eb25..27dcf9d911 100644 --- a/docs/docs/api/rpc.md +++ b/docs/docs/api/rpc.md @@ -107,7 +107,9 @@ To use an RPC function: options: members: - list_ + - patch - ColumnMetaData + - SettableColumnMetaData ## Responses diff --git a/mathesar/rpc/columns/metadata.py b/mathesar/rpc/columns/metadata.py index f99118b6b5..3dd8df5931 100644 --- a/mathesar/rpc/columns/metadata.py +++ b/mathesar/rpc/columns/metadata.py @@ -7,7 +7,7 @@ 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 +from mathesar.utils.columns import get_columns_meta_data, patch_columns_meta_data class ColumnMetaData(TypedDict): @@ -73,6 +73,42 @@ def from_model(cls, model): ) +class SettableColumnMetaData(TypedDict): + """ + Settable metadata fields for a column in a table. + + Attributes: + 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: The smallest unit for displaying durations. + duration_max: The largest unit for displaying durations. + duration_show_units: Whether to show the units for durations. + """ + 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] + + @rpc_method(name="columns.metadata.list") @http_basic_auth_login_required @handle_rpc_exceptions @@ -91,3 +127,32 @@ def list_(*, table_oid: int, database_id: int, **kwargs) -> list[ColumnMetaData] return [ ColumnMetaData.from_model(model) for model in columns_meta_data ] + + +@rpc_method(name="columns.metadata.patch") +@http_basic_auth_login_required +@handle_rpc_exceptions +def patch( + *, + column_meta_data_list: list[SettableColumnMetaData], + table_oid: int, + database_id: int, + **kwargs +) -> list[ColumnMetaData]: + """ + Alter metadata settings associated with columns of a table for a database. + + Args: + column_meta_data_list: A list describing desired metadata alterations. + table_oid: Identity of the table whose metadata we'll modify. + database_id: The Django id of the database containing the table. + + Returns: + List of altered metadata objects. + """ + columns_meta_data = patch_columns_meta_data( + column_meta_data_list, table_oid, database_id + ) + return [ + ColumnMetaData.from_model(model) for model in columns_meta_data + ] diff --git a/mathesar/tests/rpc/columns/test_c_metadata.py b/mathesar/tests/rpc/columns/test_c_metadata.py index f9b6e087c0..6e3ea21244 100644 --- a/mathesar/tests/rpc/columns/test_c_metadata.py +++ b/mathesar/tests/rpc/columns/test_c_metadata.py @@ -57,3 +57,57 @@ def mock_get_columns_meta_data(_table_oid, _database_id): ] actual_metadata_list = metadata.list_(table_oid=table_oid, database_id=database_id) assert actual_metadata_list == expect_metadata_list + + +# TODO consider mocking out ColumnMetaData queryset for this test +def test_columns_meta_data_patch(monkeypatch): + database_id = 2 + table_oid = 123456 + 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, + ), + ] + + def mock_patch_columns_meta_data(column_meta_data_list, _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, "patch_columns_meta_data", mock_patch_columns_meta_data) + actual_metadata_list = metadata.patch( + column_meta_data_list=expect_metadata_list, + table_oid=table_oid, + database_id=database_id + ) + assert actual_metadata_list == expect_metadata_list diff --git a/mathesar/tests/rpc/test_endpoints.py b/mathesar/tests/rpc/test_endpoints.py index b786e99027..0d6c28a4e8 100644 --- a/mathesar/tests/rpc/test_endpoints.py +++ b/mathesar/tests/rpc/test_endpoints.py @@ -39,6 +39,11 @@ "columns.metadata.list", [user_is_authenticated] ), + ( + columns.metadata.patch, + "columns.metadata.patch", + [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 7c48cd914e..67b3deec75 100644 --- a/mathesar/utils/columns.py +++ b/mathesar/utils/columns.py @@ -1,5 +1,20 @@ -from mathesar.models.base import ColumnMetaData +from mathesar.models.base import ColumnMetaData, Database def get_columns_meta_data(table_oid, database_id): - return ColumnMetaData.filter(database__id=database_id, table_oid=table_oid) + return ColumnMetaData.objects.filter( + database__id=database_id, table_oid=table_oid + ) + + +def patch_columns_meta_data(column_meta_data_list, table_oid, database_id): + db_model = Database.objects.get(id=database_id) + for meta_data_dict in column_meta_data_list: + # TODO decide if this is worth the trouble of doing in bulk. + ColumnMetaData.objects.update_or_create( + database=db_model, + table_oid=table_oid, + attnum=meta_data_dict["attnum"], + defaults=meta_data_dict + ) + return get_columns_meta_data(table_oid, database_id)