Skip to content

Commit

Permalink
add helper function to format autocomplete results
Browse files Browse the repository at this point in the history
  • Loading branch information
aleks-iv committed Mar 19, 2024
1 parent 7a86e6d commit 4dfd715
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 60 deletions.
24 changes: 13 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,19 @@ list of relationships between entities is carried out using actions (relation_cr
relation_delete, relations_list). The description of the types of relationships between
entities is carried out in the entity schema in the form:

- field_name: related_projects
label: Related Projects
preset: related_entity
current_entity: package
current_entity_type: dataset
related_entity: package
related_entity_type: project
relation_type: related_to
multiple: true
updatable_only: false
required: false
```yaml
- field_name: related_projects
label: Related Projects
preset: related_entity
current_entity: package
current_entity_type: dataset
related_entity: package
related_entity_type: project
relation_type: related_to
multiple: true
updatable_only: false
required: false
```
Entity (current_entity, related_entity) - one of three options: package, organization,
group.
Expand Down
14 changes: 14 additions & 0 deletions ckanext/relationship/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,17 @@ def relationship_get_choices_for_related_entity_field(

choices.sort(key=lambda x: x[1])
return choices


def relationship_format_autocomplete(packages: dict[str, Any]) -> dict[str, Any]:
return {
"ResultSet": {
"Result": [
{
"name": pkg["id"],
"title": pkg["title"],
}
for pkg in packages
]
}
}
56 changes: 51 additions & 5 deletions ckanext/relationship/logic/action.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from __future__ import annotations

from flask import jsonify
from sqlalchemy import or_

import ckan.logic as logic
import ckan.plugins.toolkit as tk
from ckan import authz
from ckan.logic import validate
from ckan.types import Context, DataDict

import ckanext.relationship.logic.schema as schema
from ckanext.relationship.model.relationship import Relationship
Expand All @@ -14,7 +17,9 @@


@validate(schema.relation_create)
def relationship_relation_create(context, data_dict) -> list[dict[str, str]]:
def relationship_relation_create(
context: Context, data_dict: DataDict
) -> list[dict[str, str]]:
"""Create relation with specified type (relation_type) between two entities
specified by ids (subject_id, object_id). Also create reverse relation."""
tk.check_access("relationship_relation_create", context, data_dict)
Expand Down Expand Up @@ -44,7 +49,9 @@ def relationship_relation_create(context, data_dict) -> list[dict[str, str]]:


@validate(schema.relation_delete)
def relationship_relation_delete(context, data_dict) -> list[dict[str, str]]:
def relationship_relation_delete(
context: Context, data_dict: DataDict
) -> list[dict[str, str]]:
"""Delete relation with specified type (relation_type) between two entities
specified by ids (subject_id, object_id). Also delete reverse relation."""
tk.check_access("relationship_relation_delete", context, data_dict)
Expand Down Expand Up @@ -105,7 +112,9 @@ def relationship_relation_delete(context, data_dict) -> list[dict[str, str]]:


@validate(schema.relations_list)
def relationship_relations_list(context, data_dict) -> list[dict[str, str]]:
def relationship_relations_list(
context: Context, data_dict: DataDict
) -> list[dict[str, str]]:
"""Return dicts list of relation of specified entity (object_entity, object_type)
related with specified type of relation (relation_type) with entity specified
by id (subject_id).
Expand All @@ -129,7 +138,7 @@ def relationship_relations_list(context, data_dict) -> list[dict[str, str]]:


@validate(schema.relations_ids_list)
def relationship_relations_ids_list(context, data_dict) -> list[str]:
def relationship_relations_ids_list(context: Context, data_dict: DataDict) -> list[str]:
"""Return ids list of specified entity (object_entity, object_type) related
with specified type of relation (relation_type) with entity specified
by id (subject_id).
Expand All @@ -142,7 +151,7 @@ def relationship_relations_ids_list(context, data_dict) -> list[str]:


@validate(schema.get_entity_list)
def relationship_get_entity_list(context, data_dict) -> list[str]:
def relationship_get_entity_list(context: Context, data_dict: DataDict) -> list[str]:
"""Return ids list of specified entity (entity, entity_type)"""
tk.check_access("relationship_get_entity_list", context, data_dict)

Expand All @@ -164,3 +173,40 @@ def relationship_get_entity_list(context, data_dict) -> list[str]:
)

return entity_list


@validate(schema.autocomplete)
def relationship_autocomplete(context: Context, data_dict: DataDict) -> DataDict:
fq = f'type:{data_dict["entity_type"]} -id:{data_dict["current_entity_id"]}'

if data_dict.get("owned_only") and not (
authz.is_sysadmin(tk.current_user.id) and not data_dict.get("check_sysadmin")
):
fq += f" creator_user_id:{tk.current_user.id}"

packages = tk.get_action("package_search")(
{},
{
"q": data_dict.get("incomplete", ""),
"fq": fq,
"fl": "id, title",
"rows": 100,
"include_private": True,
"sort": "score desc",
},
)["results"]

if data_dict.get("updatable_only"):
packages = [
pkg
for pkg in packages
if tk.h.check_access("package_update", {"id": pkg["id"]})
]

format_autocomplete_helper = getattr(
tk.h,
data_dict.get("format_autocomplete_helper"),
tk.h.relationship_format_autocomplete,
)

return jsonify(format_autocomplete_helper(packages))
21 changes: 19 additions & 2 deletions ckanext/relationship/logic/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@


@validator_args
def relation_create(not_empty, one_of, ignore_missing):
def relation_create(not_empty, one_of):
return {
"subject_id": [
not_empty,
Expand Down Expand Up @@ -73,7 +73,7 @@ def relations_ids_list(not_empty, one_of, ignore_missing):


@validator_args
def get_entity_list(not_empty, one_of, ignore_missing):
def get_entity_list(not_empty, one_of):
return {
"entity": [
not_empty,
Expand All @@ -83,3 +83,20 @@ def get_entity_list(not_empty, one_of, ignore_missing):
not_empty,
],
}


@validator_args
def autocomplete(not_empty):
return {
"incomplete": [],
"current_entity_id": [
not_empty,
],
"entity_type": [
not_empty,
],
"updatable_only": [],
"owned_only": [],
"check_sysadmin": [],
"format_autocomplete_helper": [],
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"data-module-label": "title",
"data-module-tags": "true",
"data-module-createtags": "false",
"data-module-source": "/api/2/util/relationships/autocomplete?incomplete=?&current_entity_id=%s&entity_type=%s&updatable_only=%s&owned_only=%s&check_sysadmin=%s"|format(data.get('id', None), field.related_entity_type, field.get('updatable_only', false), field.get('owned_only', false), field.get('check_sysadmin', false)),
"data-module-source": "/api/2/util/relationships/autocomplete?incomplete=?&current_entity_id=%s&entity_type=%s&updatable_only=%s&owned_only=%s&check_sysadmin=%s&format_autocomplete_helper=%s"|format(data.get('id'),field.related_entity_type, field.get('updatable_only', false), field.get('owned_only', false), field.get('check_sysadmin', false), field.get('format_autocomplete_helper')),
"data-module-selected": selected_json,
} %}

Expand Down
53 changes: 13 additions & 40 deletions ckanext/relationship/views.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from flask import Blueprint, jsonify
from flask import Blueprint

import ckan.plugins.toolkit as tk
from ckan import authz


def get_blueprints():
Expand All @@ -15,44 +14,18 @@ def get_blueprints():

@relationships.route("/api/2/util/relationships/autocomplete")
def relationships_autocomplete():
incomplete = tk.request.args.get("incomplete", "")
current_entity_id = tk.request.args.get("current_entity_id")
entity_type = tk.request.args.get("entity_type", "dataset")
updatable_only = tk.asbool(tk.request.args.get("updatable_only", "False"))
owned_only = tk.asbool(tk.request.args.get("owned_only", "False"))
check_sysadmin = tk.asbool(tk.request.args.get("check_sysadmin", "False"))

fq = f"type:{entity_type} -id:{current_entity_id}"

if owned_only and not (authz.is_sysadmin(tk.current_user.id) and not check_sysadmin):
fq += f" creator_user_id:{tk.current_user.id}"

packages = tk.get_action("package_search")(
request_args = tk.request.args
return tk.get_action("relationship_autocomplete")(
{},
{
"q": incomplete,
"fq": fq,
"fl": "id, title",
"rows": 100,
"include_private": True,
"sort": "score desc",
"incomplete": request_args.get("incomplete"),
"current_entity_id": request_args.get("current_entity_id"),
"entity_type": request_args.get("entity_type", "dataset"),
"updatable_only": tk.asbool(request_args.get("updatable_only")),
"owned_only": tk.asbool(request_args.get("owned_only")),
"check_sysadmin": tk.asbool(request_args.get("check_sysadmin")),
"format_autocomplete_helper": request_args.get(
"format_autocomplete_helper"
),
},
)["results"]

if updatable_only:
packages = filter(
lambda pkg: tk.h.check_access("package_update", {"id": pkg["id"]}), packages
)

result = {
"ResultSet": {
"Result": [
{
"name": pkg["id"],
"title": pkg["title"],
}
for pkg in packages
]
}
}
return jsonify(result)
)
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
# Versions should comply with PEP440. For a discussion on single-sourcing
# the version across setup.py and the project code, see
# http://packaging.python.org/en/latest/tutorial.html#version
version="0.2.5",
version="0.2.6",
description="""""",
long_description=long_description,
long_description_content_type="text/markdown",
Expand Down

0 comments on commit 4dfd715

Please sign in to comment.