Skip to content

Commit

Permalink
Merge pull request #3694 from mathesar-foundation/explorations_ld
Browse files Browse the repository at this point in the history
RPC transition for explorations `list` and `delete`
  • Loading branch information
mathemancer authored Jul 18, 2024
2 parents 4d413fc + 4686c63 commit 8d6f1a4
Show file tree
Hide file tree
Showing 8 changed files with 289 additions and 1 deletion.
3 changes: 2 additions & 1 deletion config/settings/common_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ def pipe_delim(pipe_string):
'mathesar.rpc.servers',
'mathesar.rpc.tables',
'mathesar.rpc.tables.metadata',
'mathesar.rpc.types'
'mathesar.rpc.types',
'mathesar.rpc.explorations'
]

TEMPLATES = [
Expand Down
9 changes: 9 additions & 0 deletions docs/docs/api/rpc.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,15 @@ To use an RPC function:
- UniqueConstraint
- CreatableConstraintInfo

## Explorations

::: explorations
options:
members:
- list_
- delete
- ExplorationInfo

## Roles

::: roles
Expand Down
33 changes: 33 additions & 0 deletions mathesar/migrations/0011_explorations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Generated by Django 4.2.11 on 2024-07-16 13:07

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('mathesar', '0010_alter_tablemetadata_column_order_and_more'),
]

operations = [
migrations.CreateModel(
name='Explorations',
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)),
('name', models.CharField(max_length=128, unique=True)),
('base_table_oid', models.PositiveBigIntegerField()),
('initial_columns', models.JSONField()),
('transformations', models.JSONField(null=True)),
('display_options', models.JSONField(null=True)),
('display_names', models.JSONField()),
('description', models.CharField(null=True)),
('database', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mathesar.database')),
],
options={
'abstract': False,
},
),
]
11 changes: 11 additions & 0 deletions mathesar/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,14 @@ class Meta:
name="unique_table_metadata"
)
]


class Explorations(BaseModel):
database = models.ForeignKey('Database', on_delete=models.CASCADE)
name = models.CharField(max_length=128, unique=True)
base_table_oid = models.PositiveBigIntegerField()
initial_columns = models.JSONField()
transformations = models.JSONField(null=True)
display_options = models.JSONField(null=True)
display_names = models.JSONField(null=False)
description = models.CharField(null=True)
80 changes: 80 additions & 0 deletions mathesar/rpc/explorations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
"""
Classes and functions exposed to the RPC endpoint for managing explorations.
"""
from typing import Optional, TypedDict

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
from mathesar.utils.explorations import get_explorations, delete_exploration


class ExplorationInfo(TypedDict):
"""
Information about a Exploration.
Attributes:
id: The Django id of an exploration.
database_id: The Django id of the database containing the exploration.
name: The name of the exploration.
base_table_oid: The OID of the base table of the exploration on the database.
initial_columns: A list describing the columns to be included in the exploration.
transformations: A list describing the transformations to be made on the included columns.
display_options: A list desrcibing metadata for the columns in the explorations.
display_names: A map between the actual column names on the database and the alias to be displayed(if any).
description: The description of the exploration.
"""
id: int
database_id: int
name: str
base_table_oid: int
initial_columns: list
transformations: Optional[list]
display_options: Optional[list]
display_names: Optional[dict]
description: Optional[str]

@classmethod
def from_model(cls, model):
return cls(
id=model.id,
database_id=model.database.id,
name=model.name,
base_table_oid=model.base_table_oid,
initial_columns=model.initial_columns,
transformations=model.transformations,
display_options=model.display_options,
display_names=model.display_names,
description=model.description,
)


@rpc_method(name="explorations.list")
@http_basic_auth_login_required
@handle_rpc_exceptions
def list_(*, database_id: int, **kwargs) -> list[ExplorationInfo]:
"""
List information about explorations for a database. Exposed as `list`.
Args:
database_id: The Django id of the database containing the explorations.
Returns:
A list of exploration details.
"""
explorations = get_explorations(database_id)
return [ExplorationInfo.from_model(exploration) for exploration in explorations]


@rpc_method(name="explorations.delete")
@http_basic_auth_login_required
@handle_rpc_exceptions
def delete(*, exploration_id: int, **kwargs) -> None:
"""
Delete an exploration.
Args:
exploration_id: The Django id of the exploration to delete.
"""
delete_exploration(exploration_id)
11 changes: 11 additions & 0 deletions mathesar/tests/rpc/test_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from mathesar.rpc import constraints
from mathesar.rpc import database_setup
from mathesar.rpc import databases
from mathesar.rpc import explorations
from mathesar.rpc import roles
from mathesar.rpc import schemas
from mathesar.rpc import servers
Expand Down Expand Up @@ -137,6 +138,16 @@
"databases.list",
[user_is_authenticated]
),
(
explorations.list_,
"explorations.list",
[user_is_authenticated]
),
(
explorations.delete,
"explorations.delete",
[user_is_authenticated]
),
(
roles.list_,
"roles.list",
Expand Down
134 changes: 134 additions & 0 deletions mathesar/tests/rpc/test_explorations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
"""
This file tests the explorations RPC functions.
Fixtures:
rf(pytest-django): Provides mocked `Request` objects.
monkeypatch(pytest): Lets you monkeypatch an object for testing.
"""
from mathesar.rpc import explorations
from mathesar.models.users import User
from mathesar.models.base import Database, Explorations


def test_explorations_list(rf, monkeypatch):
request = rf.post('/api/rpc/v0', data={})
request.user = User(username='alice', password='pass1234')
database_id = 11

def mock_exploration_info(_database_id):
if _database_id != database_id:
raise AssertionError('incorrect parameters passed')
return [
Explorations(
id=2,
database=Database(id=1),
name='page count',
base_table_oid=12375,
initial_columns=[
{'id': 51586, 'alias': 'Items_Acquisition Date'},
{'id': 51598, 'alias': 'Items_id'},
{'id': 51572, 'alias': 'Books_Media', 'jp_path': [[51588, 51596]]},
{'id': 51573, 'alias': 'Books_Page Count', 'jp_path': [[51588, 51596]]}
],
transformations=[
{'spec': {'greater': [{'column_name': ['Books_Page Count']}, {'literal': ['50']}]}, 'type': 'filter'}
],
display_options=None,
display_names={
'Items_id': 'Items_id',
'Items_Book': 'Items_Book',
'Books_Media': 'Books_Media',
'Items_Book_1': 'Items_Book_1',
'Books_Page Count': 'Books_Page Count',
'Items_Acquisition Date': 'Items_Acquisition Date'
},
description=None
),
Explorations(
id=3,
database=Database(id=1),
name='ISBN',
base_table_oid=12356,
initial_columns=[
{'id': 51594, 'alias': 'Publishers_Name'},
{'id': 51575, 'alias': 'Books_ISBN', 'jp_path': [[51593, 51579]]}
],
transformations=[
{
'spec': {
'base_grouping_column': 'Publishers_Name',
'grouping_expressions': [
{'input_alias': 'Publishers_Name', 'output_alias': 'Publishers_Name_grouped'}
],
'aggregation_expressions': [{
'function': 'distinct_aggregate_to_array',
'input_alias': 'Books_ISBN',
'output_alias': 'Books_ISBN_agged'}]
},
'type': 'summarize'}
],
display_options=None,
display_names={
'Books_ISBN': 'Books_ISBN', 'Publishers_Name': 'Publishers_Name'
},
description=None
)
]
monkeypatch.setattr(explorations, 'get_explorations', mock_exploration_info)
expect_explorations_list = [
{
'id': 2,
'database_id': 1,
'name': 'page count',
'base_table_oid': 12375,
'initial_columns': [
{'id': 51586, 'alias': 'Items_Acquisition Date'},
{'id': 51598, 'alias': 'Items_id'},
{'id': 51572, 'alias': 'Books_Media', 'jp_path': [[51588, 51596]]},
{'id': 51573, 'alias': 'Books_Page Count', 'jp_path': [[51588, 51596]]}
],
'transformations': [
{'spec': {'greater': [{'column_name': ['Books_Page Count']}, {'literal': ['50']}]}, 'type': 'filter'}],
'display_options': None,
'display_names': {
'Items_id': 'Items_id',
'Items_Book': 'Items_Book',
'Books_Media': 'Books_Media',
'Items_Book_1': 'Items_Book_1',
'Books_Page Count': 'Books_Page Count',
'Items_Acquisition Date': 'Items_Acquisition Date'
},
'description': None
},
{
'id': 3,
'database_id': 1,
'name': 'ISBN',
'base_table_oid': 12356,
'initial_columns': [
{'id': 51594, 'alias': 'Publishers_Name'},
{'id': 51575, 'alias': 'Books_ISBN', 'jp_path': [[51593, 51579]]}
],
'transformations': [
{
'spec': {
'base_grouping_column': 'Publishers_Name',
'grouping_expressions': [
{'input_alias': 'Publishers_Name', 'output_alias': 'Publishers_Name_grouped'}
],
'aggregation_expressions': [{
'function': 'distinct_aggregate_to_array',
'input_alias': 'Books_ISBN',
'output_alias': 'Books_ISBN_agged'}]
},
'type': 'summarize'}
],
'display_options': None,
'display_names': {
'Books_ISBN': 'Books_ISBN', 'Publishers_Name': 'Publishers_Name'
},
'description': None
}
]
actual_explorations_list = explorations.list_(database_id=11)
assert actual_explorations_list == expect_explorations_list
9 changes: 9 additions & 0 deletions mathesar/utils/explorations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from mathesar.models.base import Explorations


def get_explorations(database_id):
return Explorations.objects.filter(database__id=database_id)


def delete_exploration(exploration_id):
Explorations.objects.get(id=exploration_id).delete()

0 comments on commit 8d6f1a4

Please sign in to comment.