75 lines
3.0 KiB
Python
75 lines
3.0 KiB
Python
from __future__ import annotations
|
|
|
|
from .crm_mixin import CrmMixin
|
|
from .crm_shared import (
|
|
PROJECTION_FALLBACK_WARNING,
|
|
Any,
|
|
CrmConfig,
|
|
LabelSpec,
|
|
NbClient,
|
|
NbCommandError,
|
|
_fields_with_projections,
|
|
_unique_strings,
|
|
)
|
|
|
|
|
|
class CrmCoreMixin(CrmMixin):
|
|
def __init__(self, config: CrmConfig, client: NbClient) -> None:
|
|
self.config = config
|
|
self.client = client
|
|
|
|
def _customer_spec(self) -> LabelSpec:
|
|
return LabelSpec(self.config.customer.id_field, tuple(self.config.customer.name_fields or ()))
|
|
|
|
def _contact_spec(self) -> LabelSpec:
|
|
return LabelSpec(self.config.contact.id_field, tuple(self.config.contact.name_fields or ()))
|
|
|
|
def _user_spec(self) -> LabelSpec:
|
|
return LabelSpec(self.config.user.id_field, tuple(self.config.user.label_fields or ()))
|
|
|
|
def _project_spec(self) -> LabelSpec:
|
|
return LabelSpec(self.config.project.id_field, tuple(self.config.project.name_fields or ()))
|
|
|
|
def _team_fields(self) -> list[str]:
|
|
# Only the related user ids are needed: _team_users re-queries the users
|
|
# collection for their details, and _compact_team just counts members.
|
|
team = self.config.team
|
|
id_only = LabelSpec(self.config.user.id_field, ())
|
|
return _fields_with_projections(
|
|
[team.id_field, team.name_field],
|
|
[
|
|
*self._projection(team.manager_field, id_only, None),
|
|
*self._projection(team.member_field, id_only, None),
|
|
],
|
|
)
|
|
|
|
def _projection(self, relation: str | None, spec: LabelSpec, configured_fields: list[str] | None) -> list[str]:
|
|
"""Build dotted field paths that fetch only a relation's id and name.
|
|
|
|
Without this NocoBase expands every related row in full -- junction table
|
|
rows, audit user objects, long free text -- none of which is rendered.
|
|
The configured-fields whitelist still applies, just to the relation root
|
|
rather than to each dotted path.
|
|
"""
|
|
if not relation:
|
|
return []
|
|
if configured_fields and relation not in configured_fields:
|
|
return []
|
|
return [f"{relation}.{name}" for name in spec.projection_fields()]
|
|
|
|
def _list_with_projection_fallback(self, resource: str, *, fields: list[str], **kwargs: Any) -> dict[str, Any]:
|
|
"""Run a projected list query, degrading to relation roots if it is rejected.
|
|
|
|
A misconfigured ``nameFields`` entry would otherwise turn every read into
|
|
a hard failure; falling back keeps the tool usable and says why.
|
|
"""
|
|
try:
|
|
return self._list_records_window(resource, fields=fields, **kwargs)
|
|
except NbCommandError:
|
|
flat = _unique_strings([path.split(".", 1)[0] for path in fields])
|
|
if flat == _unique_strings(fields):
|
|
raise
|
|
result = self._list_records_window(resource, fields=flat, **kwargs)
|
|
result.setdefault("data_quality_warnings", []).append(PROJECTION_FALLBACK_WARNING)
|
|
return result
|