|
22 | 22 | RequiredParameterError,
|
23 | 23 | YAMLParser,
|
24 | 24 | )
|
25 |
| -from airone.lib.types import AttrTypeValue |
| 25 | +from airone.lib.types import AttrType, AttrTypeValue |
26 | 26 | from entity.models import Entity, EntityAttr
|
27 | 27 | from entry.api_v2.pagination import EntryReferralPagination
|
28 | 28 | from entry.api_v2.serializers import (
|
@@ -229,6 +229,69 @@ def post(self, request: Request) -> Response:
|
229 | 229 | is_all_entities = serializer.validated_data["is_all_entities"]
|
230 | 230 | entry_limit = serializer.validated_data["entry_limit"]
|
231 | 231 | entry_offset = serializer.validated_data["entry_offset"]
|
| 232 | + join_attrs = serializer.validated_data.get("join_attrs", []) |
| 233 | + |
| 234 | + def _get_joined_resp(prev_results, join_attr): |
| 235 | + """ |
| 236 | + This is a helper method for join_attrs that will get specified attr values |
| 237 | + that prev_result's ones refer to. |
| 238 | + """ |
| 239 | + # set hint_entity_ids for joining Items search and get item names |
| 240 | + # that specified attribute refers |
| 241 | + item_names = [] |
| 242 | + hint_entity_ids = [] |
| 243 | + for result in prev_results: |
| 244 | + entity = Entity.objects.filter(id=result["entity"]["id"]).last() |
| 245 | + if entity is None: |
| 246 | + continue |
| 247 | + |
| 248 | + attr = entity.attrs.filter(name=join_attr["name"], is_active=True).last() |
| 249 | + if attr is None: |
| 250 | + continue |
| 251 | + |
| 252 | + if attr.type == AttrTypeValue["object"]: |
| 253 | + # set hint Model ID |
| 254 | + hint_entity_ids += [x.id for x in attr.referral.filter(is_active=True)] |
| 255 | + |
| 256 | + # set Item name |
| 257 | + attrinfo = result["attrs"][join_attr["name"]] |
| 258 | + if attrinfo["value"]["name"] not in item_names: |
| 259 | + item_names.append(attrinfo["value"]["name"]) |
| 260 | + |
| 261 | + # set parameters to filter joining search results |
| 262 | + hint_attrs = [] |
| 263 | + for attrinfo in join_attr.get("attrinfo", []): |
| 264 | + hint_attrs.append( |
| 265 | + { |
| 266 | + "name": attrinfo["name"], |
| 267 | + "keyword": attrinfo.get("keyword"), |
| 268 | + "filter_key": attrinfo.get("filter_key"), |
| 269 | + } |
| 270 | + ) |
| 271 | + |
| 272 | + # search Items from elasticsearch to join |
| 273 | + return ( |
| 274 | + # This represents whether user want to narrow down results by keyword of joined attr |
| 275 | + any( |
| 276 | + [ |
| 277 | + x.get("keyword") or x.get("filter_key", 0) > 0 |
| 278 | + for x in join_attr.get("attrinfo", []) |
| 279 | + ] |
| 280 | + ), |
| 281 | + Entry.search_entries( |
| 282 | + request.user, |
| 283 | + hint_entity_ids=list(set(hint_entity_ids)), # this removes depulicated IDs |
| 284 | + hint_attrs=hint_attrs, |
| 285 | + limit=entry_limit, |
| 286 | + entry_name="|".join(item_names), |
| 287 | + hint_referral=None, |
| 288 | + is_output_all=is_output_all, |
| 289 | + hint_referral_entity_id=None, |
| 290 | + offset=join_attr.get("offset", 0), |
| 291 | + ), |
| 292 | + ) |
| 293 | + |
| 294 | + # === End of Function: _get_joined_resp() === |
232 | 295 |
|
233 | 296 | if not has_referral:
|
234 | 297 | hint_referral = None
|
@@ -273,6 +336,65 @@ def post(self, request: Request) -> Response:
|
273 | 336 | offset=entry_offset,
|
274 | 337 | )
|
275 | 338 |
|
| 339 | + for join_attr in join_attrs: |
| 340 | + (will_filter_by_joined_attr, joined_resp) = _get_joined_resp( |
| 341 | + resp["ret_values"], join_attr |
| 342 | + ) |
| 343 | + |
| 344 | + # This is needed to set result as blank value |
| 345 | + blank_joining_info = { |
| 346 | + "%s.%s" % (join_attr["name"], k["name"]): { |
| 347 | + "is_readable": True, |
| 348 | + "type": AttrType.STRING.value, |
| 349 | + "value": "", |
| 350 | + } |
| 351 | + for k in join_attr["attrinfo"] |
| 352 | + } |
| 353 | + |
| 354 | + # convert search result to dict to be able to handle it without loop |
| 355 | + joined_resp_info = { |
| 356 | + x["entry"]["id"]: { |
| 357 | + "%s.%s" % (join_attr["name"], k): v |
| 358 | + for k, v in x["attrs"].items() |
| 359 | + if any(_x["name"] == k for _x in join_attr["attrinfo"]) |
| 360 | + } |
| 361 | + for x in joined_resp["ret_values"] |
| 362 | + } |
| 363 | + |
| 364 | + # this inserts result to previous search result |
| 365 | + new_ret_values = [] |
| 366 | + for resp_result in resp["ret_values"]: |
| 367 | + 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.value |
| 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 |
| 380 | + |
| 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] |
| 386 | + |
| 387 | + # collect only the result that matches with keyword of joined_attr parameter |
| 388 | + new_ret_values.append(resp_result) |
| 389 | + |
| 390 | + else: |
| 391 | + # join EMPTY value |
| 392 | + resp_result["attrs"] |= blank_joining_info # type: ignore |
| 393 | + |
| 394 | + if will_filter_by_joined_attr: |
| 395 | + resp["ret_values"] = new_ret_values |
| 396 | + resp["ret_count"] = len(new_ret_values) |
| 397 | + |
276 | 398 | # convert field values to fit entry retrieve API data type, as a workaround.
|
277 | 399 | # FIXME should be replaced with DRF serializer etc
|
278 | 400 | for entry in resp["ret_values"]:
|
|
0 commit comments