Skip to content

Commit 5ef0fe7

Browse files
authored
Merge pull request #1128 from userlocalhost/feature/join_attr/expand_supported_types
Update background processing to support any OBJECT attribute types for join_attr
2 parents 442c688 + a4d8ef4 commit 5ef0fe7

File tree

5 files changed

+362
-228
lines changed

5 files changed

+362
-228
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
### Added
66

77
### Changed
8+
* Update background processing to support any OBJECT typed Attribute for join_attr
9+
at the advacend serarch processing.
10+
Contributed by @userlocalhost
811

912
### Fixed
1013

entry/api_v2/views.py

+60-23
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import re
2+
from copy import deepcopy
23
from datetime import datetime, timedelta
34
from typing import Optional
45

@@ -249,15 +250,30 @@ def _get_joined_resp(prev_results, join_attr):
249250
if attr is None:
250251
continue
251252

252-
if attr.type == AttrTypeValue["object"]:
253+
if attr.type & AttrType.OBJECT:
253254
# set hint Model ID
254255
hint_entity_ids += [x.id for x in attr.referral.filter(is_active=True)]
255256

256257
# set Item name
257258
attrinfo = result["attrs"][join_attr["name"]]
258-
if attrinfo["value"]["name"] not in item_names:
259+
260+
if attr.type == AttrType.OBJECT and attrinfo["value"]["name"] not in item_names:
259261
item_names.append(attrinfo["value"]["name"])
260262

263+
if attr.type == AttrType.NAMED_OBJECT:
264+
for co_info in attrinfo["value"].values():
265+
if co_info["name"] not in item_names:
266+
item_names.append(co_info["name"])
267+
268+
if attr.type == AttrType.ARRAY_OBJECT:
269+
for r in attrinfo["value"]:
270+
item_names.append(r["name"])
271+
272+
if attr.type == AttrType.ARRAY_NAMED_OBJECT:
273+
for r in attrinfo["value"]:
274+
[co_info] = r.values()
275+
item_names.append(co_info["name"])
276+
261277
# set parameters to filter joining search results
262278
hint_attrs = []
263279
for attrinfo in join_attr.get("attrinfo", []):
@@ -293,6 +309,26 @@ def _get_joined_resp(prev_results, join_attr):
293309

294310
# === End of Function: _get_joined_resp() ===
295311

312+
def _get_ref_id_from_es_result(attrinfo):
313+
if attrinfo["type"] == AttrType.OBJECT:
314+
if attrinfo.get("value") is not None:
315+
return [attrinfo["value"].get("id")]
316+
317+
if attrinfo["type"] == AttrType.NAMED_OBJECT:
318+
if attrinfo.get("value") is not None:
319+
[ref_info] = attrinfo["value"].values()
320+
return [ref_info.get("id")]
321+
322+
if attrinfo["type"] == AttrType.ARRAY_OBJECT:
323+
return [x.get("id") for x in attrinfo["value"]]
324+
325+
if attrinfo["type"] == AttrType.ARRAY_NAMED_OBJECT:
326+
return sum([[y["id"] for y in x.values()] for x in attrinfo["value"]], [])
327+
328+
return []
329+
330+
# === End of Function: _get_ref_id_from_es_result() ===
331+
296332
if not has_referral:
297333
hint_referral = None
298334

@@ -340,7 +376,6 @@ def _get_joined_resp(prev_results, join_attr):
340376
(will_filter_by_joined_attr, joined_resp) = _get_joined_resp(
341377
resp["ret_values"], join_attr
342378
)
343-
344379
# This is needed to set result as blank value
345380
blank_joining_info = {
346381
"%s.%s" % (join_attr["name"], k["name"]): {
@@ -363,37 +398,39 @@ def _get_joined_resp(prev_results, join_attr):
363398

364399
# this inserts result to previous search result
365400
new_ret_values = []
401+
joined_ret_values = []
366402
for resp_result in resp["ret_values"]:
403+
# joining search result to original one
367404
ref_info = resp_result["attrs"].get(join_attr["name"])
368-
if (
369-
# ignore no joined data
370-
ref_info is None
371-
or
372-
# ignore unexpected typed attributes
373-
ref_info["type"] != AttrType.OBJECT
374-
or
375-
# ignore when original result doesn't refer any item
376-
ref_info["value"].get("id") is None
377-
):
378-
# join EMPTY value
379-
resp_result["attrs"] |= blank_joining_info # type: ignore
380405

381-
# joining search result to original one
382-
ref_id = ref_info["value"].get("id") if "value" in ref_info is not None else None # type: ignore
383-
if ref_id and ref_id in joined_resp_info: # type: ignore
384-
# join valid value
385-
resp_result["attrs"] |= joined_resp_info[ref_id]
406+
# This get referral Item-ID from joined search result
407+
ref_list = _get_ref_id_from_es_result(ref_info)
408+
for ref_id in ref_list:
409+
if ref_id and ref_id in joined_resp_info: # type: ignore
410+
# join valid value
411+
resp_result["attrs"] |= joined_resp_info[ref_id]
386412

387-
# collect only the result that matches with keyword of joined_attr parameter
388-
new_ret_values.append(resp_result)
413+
# collect only the result that matches with keyword of joined_attr parameter
414+
copied_result = deepcopy(resp_result)
415+
new_ret_values.append(copied_result)
416+
joined_ret_values.append(copied_result)
389417

390-
else:
418+
else:
419+
# join EMPTY value
420+
resp_result["attrs"] |= blank_joining_info # type: ignore
421+
joined_ret_values.append(deepcopy(resp_result))
422+
423+
if len(ref_list) == 0:
391424
# join EMPTY value
392425
resp_result["attrs"] |= blank_joining_info # type: ignore
426+
joined_ret_values.append(deepcopy(resp_result))
393427

394428
if will_filter_by_joined_attr:
395429
resp["ret_values"] = new_ret_values
396430
resp["ret_count"] = len(new_ret_values)
431+
else:
432+
resp["ret_values"] = joined_ret_values
433+
resp["ret_count"] = len(joined_ret_values)
397434

398435
# convert field values to fit entry retrieve API data type, as a workaround.
399436
# FIXME should be replaced with DRF serializer etc

entry/tests/test_api_v2.py

+22-204
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
from rest_framework import status
1111
from rest_framework.exceptions import ValidationError
1212

13-
from airone.lib.elasticsearch import FilterKey
1413
from airone.lib.log import Logger
1514
from airone.lib.test import AironeViewTest
1615
from airone.lib.types import (
@@ -36,27 +35,9 @@
3635
from user.models import User
3736

3837

39-
class ViewTest(AironeViewTest):
40-
def _create_user(
41-
self,
42-
name,
43-
email="email",
44-
is_superuser=False,
45-
authenticate_type=User.AuthenticateType.AUTH_TYPE_LOCAL,
46-
):
47-
user = User(
48-
username=name,
49-
email=email,
50-
is_superuser=is_superuser,
51-
authenticate_type=authenticate_type,
52-
)
53-
user.set_password(name)
54-
user.save()
55-
56-
return user
57-
38+
class BaseViewTest(AironeViewTest):
5839
def setUp(self):
59-
super(ViewTest, self).setUp()
40+
super(BaseViewTest, self).setUp()
6041

6142
self.user: User = self.guest_login()
6243

@@ -87,6 +68,26 @@ def setUp(self):
8768
}
8869
)
8970

71+
72+
class ViewTest(BaseViewTest):
73+
def _create_user(
74+
self,
75+
name,
76+
email="email",
77+
is_superuser=False,
78+
authenticate_type=User.AuthenticateType.AUTH_TYPE_LOCAL,
79+
):
80+
user = User(
81+
username=name,
82+
email=email,
83+
is_superuser=is_superuser,
84+
authenticate_type=authenticate_type,
85+
)
86+
user.set_password(name)
87+
user.save()
88+
89+
return user
90+
9091
def test_retrieve_entry(self):
9192
entry: Entry = self.add_entry(
9293
self.user,
@@ -3155,189 +3156,6 @@ def test_advanced_search(self):
31553156
},
31563157
)
31573158

3158-
def test_advanced_search_with_join_attrs(self):
3159-
# create Items to be search by join_attrs parameter
3160-
ref_entries = [
3161-
self.add_entry(
3162-
self.user,
3163-
"RefEntry-%s" % i,
3164-
self.ref_entity,
3165-
values={
3166-
"val": "hoge-%s" % i if i % 2 == 0 else "",
3167-
"ref": self.ref_entry.id,
3168-
"name": {"name": "abcd-%s" % i, "id": self.ref_entry.id},
3169-
},
3170-
)
3171-
for i in range(2)
3172-
]
3173-
3174-
# create Items that are search by ordinary processing
3175-
for index, (val, ref_id) in enumerate(
3176-
[
3177-
("foo", ref_entries[0].id),
3178-
("bar", ref_entries[0].id),
3179-
("baz", ref_entries[1].id),
3180-
("qux", None),
3181-
]
3182-
):
3183-
self.add_entry(
3184-
self.user,
3185-
"Entry%s" % index,
3186-
self.entity,
3187-
values={
3188-
"val": val,
3189-
"ref": ref_id,
3190-
"name": {"name": "fuga", "id": ref_id},
3191-
},
3192-
)
3193-
3194-
# send request to search Entries with join_attrs
3195-
params = {
3196-
"entities": [self.entity.id],
3197-
"attrinfo": [
3198-
{"name": "val"},
3199-
{"name": "ref"},
3200-
{"name": "name"},
3201-
],
3202-
"join_attrs": [
3203-
{
3204-
"name": "ref",
3205-
"attrinfo": [
3206-
{"name": "val"},
3207-
{"name": "ref"},
3208-
{"name": "name"},
3209-
],
3210-
},
3211-
],
3212-
}
3213-
resp = self.client.post(
3214-
"/entry/api/v2/advanced_search/", json.dumps(params), "application/json"
3215-
)
3216-
self.assertEqual(resp.status_code, 200)
3217-
3218-
# prepare comparison data with returned result
3219-
REF_DATA = {"id": self.ref_entry.id, "name": self.ref_entry.name}
3220-
expected_results = [
3221-
(
3222-
"Entry0",
3223-
{
3224-
"ref.val": {"as_string": "hoge-0"},
3225-
"ref.ref": {"as_object": REF_DATA},
3226-
"ref.name": {"as_named_object": {"name": "abcd-0", "object": REF_DATA}},
3227-
},
3228-
),
3229-
(
3230-
"Entry1",
3231-
{
3232-
"ref.val": {"as_string": "hoge-0"},
3233-
"ref.ref": {"as_object": REF_DATA},
3234-
"ref.name": {"as_named_object": {"name": "abcd-0", "object": REF_DATA}},
3235-
},
3236-
),
3237-
(
3238-
"Entry2",
3239-
{
3240-
"ref.val": {"as_string": ""},
3241-
"ref.ref": {"as_object": REF_DATA},
3242-
"ref.name": {"as_named_object": {"name": "abcd-1", "object": REF_DATA}},
3243-
},
3244-
),
3245-
(
3246-
"Entry3",
3247-
{
3248-
"ref.val": {"as_string": ""},
3249-
"ref.ref": {"as_string": ""},
3250-
"ref.name": {"as_string": ""},
3251-
},
3252-
),
3253-
]
3254-
3255-
# check returned processing has expected values
3256-
for index, result in enumerate(resp.json()["values"]):
3257-
(e_name, e_attrinfo) = expected_results[index]
3258-
self.assertEqual(result["entry"]["name"], e_name)
3259-
3260-
for attrname, attrvalue in e_attrinfo.items():
3261-
self.assertEqual(result["attrs"][attrname]["value"], attrvalue)
3262-
3263-
# This sends request with keyword in join_attrs parameter and
3264-
# confirms filter processing works with its filter parameter of attrinfo
3265-
params = {
3266-
"entities": [self.entity.id],
3267-
"attrinfo": [
3268-
{"name": "val", "keyword": "bar"},
3269-
{"name": "ref"},
3270-
],
3271-
"join_attrs": [
3272-
{
3273-
"name": "ref",
3274-
"attrinfo": [
3275-
{"name": "val", "keyword": "hoge-0"},
3276-
],
3277-
},
3278-
],
3279-
}
3280-
resp = self.client.post(
3281-
"/entry/api/v2/advanced_search/", json.dumps(params), "application/json"
3282-
)
3283-
self.assertEqual(resp.status_code, 200)
3284-
self.assertEqual([x["entry"]["name"] for x in resp.json()["values"]], ["Entry1"])
3285-
3286-
# This sends request with join_attrs that have filter_key to get empty Items
3287-
params = {
3288-
"entities": [self.entity.id],
3289-
"attrinfo": [],
3290-
"join_attrs": [
3291-
{
3292-
"name": "ref",
3293-
"attrinfo": [
3294-
{"name": "val", "filter_key": FilterKey.EMPTY},
3295-
],
3296-
},
3297-
],
3298-
}
3299-
resp = self.client.post(
3300-
"/entry/api/v2/advanced_search/", json.dumps(params), "application/json"
3301-
)
3302-
self.assertEqual(resp.status_code, 200)
3303-
self.assertEqual([x["entry"]["name"] for x in resp.json()["values"]], ["Entry2"])
3304-
self.assertEqual(
3305-
[x["attrs"]["ref.val"]["value"] for x in resp.json()["values"]], [{"as_string": ""}]
3306-
)
3307-
3308-
# This sends request with join_attrs that have filter_key to get non-empty Items
3309-
params = {
3310-
"entities": [self.entity.id],
3311-
"attrinfo": [],
3312-
"join_attrs": [
3313-
{
3314-
"name": "ref",
3315-
"attrinfo": [
3316-
{"name": "val", "filter_key": FilterKey.NON_EMPTY},
3317-
],
3318-
},
3319-
],
3320-
}
3321-
resp = self.client.post(
3322-
"/entry/api/v2/advanced_search/", json.dumps(params), "application/json"
3323-
)
3324-
self.assertEqual(resp.status_code, 200)
3325-
self.assertEqual([x["entry"]["name"] for x in resp.json()["values"]], ["Entry0", "Entry1"])
3326-
self.assertEqual(
3327-
[x["attrs"]["ref.val"]["value"] for x in resp.json()["values"]],
3328-
[
3329-
{"as_string": "hoge-0"},
3330-
{"as_string": "hoge-0"},
3331-
],
3332-
)
3333-
3334-
def test_advanced_search_with_join_attrs_that_have_filter_key1(self):
3335-
"""
3336-
Test advanced_search APIv2 with join_attrs parameter that have
3337-
valid filter_key attribute.
3338-
"""
3339-
pass
3340-
33413159
def test_advanced_search_all_entities(self):
33423160
params = {
33433161
"entities": [],

0 commit comments

Comments
 (0)