Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update forms to enable presets #157

Merged
merged 12 commits into from
Mar 20, 2023
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

Observes [Semantic Versioning](https://semver.org/spec/v2.0.0.html) standard and [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) convention.

## [0.8.1] - TBD
## [0.8.1] - 2023-03-17

### Added
- Api endpoint `/spec` which returns the spec for the current dynamic routes [#156](https://github.com/datajoint/pharus/pull/156)
- Support for presets in Dynamic forms [#157](https://github.com/datajoint/pharus/pull/157)

### Bugfix

- Added print statement to let user know if their component override has gone through [#156](https://github.com/datajoint/pharus/pull/156)

## [0.8.0] - 2023-02-06
Expand Down Expand Up @@ -275,6 +275,7 @@ Observes [Semantic Versioning](https://semver.org/spec/v2.0.0.html) standard and
- Support for DataJoint attribute types: `varchar`, `int`, `float`, `datetime`, `date`, `time`, `decimal`, `uuid`.
- Check dependency utility to determine child table references.

[0.8.1]: https://github.com/datajoint/pharus/compare/0.8.0...0.8.1
[0.8.0]: https://github.com/datajoint/pharus/compare/0.7.3...0.8.0
[0.7.3]: https://github.com/datajoint/pharus/compare/0.7.2...0.7.3
[0.7.2]: https://github.com/datajoint/pharus/compare/0.7.1...0.7.2
Expand Down
72 changes: 69 additions & 3 deletions pharus/component_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,11 +207,14 @@ def dj_query_route(self):
class InsertComponent(Component):
rest_verb = ["POST", "GET"]
fields_route_format = "{route}/fields"
presets_route_format = "{route}/presets"

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
component_config = kwargs.get("component_config", args[1] if args else None)
self.fields_map = component_config.get("map")
self.component_config = kwargs.get(
"component_config", args[1] if args else None
)
self.fields_map = self.component_config.get("map")
self.tables = [
getattr(
dj.VirtualModule(
Expand All @@ -222,7 +225,8 @@ def __init__(self, *args, **kwargs):
t,
)
for s, t in (
_.format(**request.args).split(".") for _ in component_config["tables"]
_.format(**request.args).split(".")
for _ in self.component_config["tables"]
)
]
self.parents = sorted(
Expand All @@ -243,6 +247,24 @@ def __init__(self, *args, **kwargs):
}
self.input_lookup = {v: k for k, v in self.destination_lookup.items()}

if "preset_query" in self.component_config:
lcls = locals()
exec(self.component_config["preset_query"], globals(), lcls)
self.preset_query = lcls["preset_query"]

self.preset_vm_list = [
dj.VirtualModule(
s,
s.replace("__", "-"),
connection=self.connection,
)
for s in inspect.getfullargspec(self.preset_query).args
]

@property
def preset_metadata(self):
return self.preset_query(*self.preset_vm_list)

def dj_query_route(self):
with self.connection.transaction:
for t in self.tables:
Expand Down Expand Up @@ -315,6 +337,50 @@ def fields_route(self):
+ list(source_fields.values())
)

def presets_route(self):
# Table content for presets should follow the following format:
#
# preset_names: string
# ---
# presets: blob or json
#
# Example result from query:
# [['preset_name', {"b_id": 1, "b_number": 2345}],
# ['preset2_name', {"b_id": 13, "b_number": 225}]]
#
# If you have a name mapping it will be applied to each preset
# Route will 404 if no preset query is defined and 500 if there is an Exception

# Helper function to filter out fields not in the insert,
# as well as apply the fields_map
def filterPreset(preset: dict):
return {
(self.input_lookup[k] if k in self.input_lookup else k): v
for k, v in preset.items()
}

if "preset_query" not in self.component_config:
return (
"No Preset query found",
404,
{"Content-Type": "text/plain"},
)
fetch_metadata = self.preset_metadata
_, table_records, _ = _DJConnector._fetch_records(
query=fetch_metadata["query"],
fetch_args=fetch_metadata["fetch_args"],
fetch_blobs=True,
)
preset_dictionary = {
table[0]: (filterPreset(table[1]) if self.fields_map else table[1])
for table in table_records
}
return (
NumpyEncoder.dumps(preset_dictionary),
200,
{"Content-Type": "application/json"},
)


class TableComponent(FetchComponent):
attributes_route_format = "{route}/attributes"
Expand Down
16 changes: 16 additions & 0 deletions pharus/dynamic_api_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,9 @@ def {method_name}() -> dict:
fields_route = type_map[
comp["type"]
].fields_route_format.format(route=comp["route"])
presets_route = type_map[
comp["type"]
].presets_route_format.format(route=comp["route"])
f.write(
(active_route_template).format(
route=fields_route,
Expand All @@ -183,6 +186,19 @@ def {method_name}() -> dict:
method_name_type="fields_route",
)
)
f.write(
(active_route_template).format(
route=presets_route,
rest_verb=[InsertComponent.rest_verb[1]],
method_name=presets_route.replace("/", ""),
component_type=comp["type"],
component_name=comp_name,
component=json.dumps(comp),
static_config=static_config,
payload="payload=None",
method_name_type="presets_route",
)
)
elif issubclass(type_map[comp["type"]], TableComponent):
attributes_route = type_map[
comp["type"]
Expand Down