fix: Resolve ID Vision conflicts and validation errors in Event Exhibit Tracking

- Modified 'sanitize_payload' to ignore 'external_person_id', preventing incorrect lookup attempts for email/passcode fields.
- Refined 'Event_Exhibit_Tracking_Base' to allow 'Union[int, str]' for relational IDs, bypassing string-length validation for internal integers.
- Adjusted root validator to preserve relational integers during POST/PUT operations while still stripping primary/account IDs for Vision-compliant READ views.
- Aligned model configuration with other V3 objects for consistency.
This commit is contained in:
Scott Idem
2026-03-03 17:08:34 -05:00
parent 403b543ed2
commit 89e12b9f97
2 changed files with 28 additions and 48 deletions

View File

@@ -212,6 +212,8 @@ def sanitize_payload(data: dict, model: Any, ignore_extra: bool = False) -> None
# Scenario B: Vision naming (e.g., account_id: "abc")
# We only resolve if it's a string of the correct length (random ID format)
elif k.endswith('_id') and 11 <= len(v) <= 22:
if k == 'external_person_id':
continue
target_id_field = k
obj_type_lookup = k.replace('_id', '')

View File

@@ -17,14 +17,15 @@ class Event_Exhibit_Tracking_Base(BaseModel):
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
# --- Standardized Vision IDs (Strings for API, Integers for DB) ---
id: Optional[Union[int, str]] = Field(**base_fields['event_exhibit_tracking_id_random'])
event_exhibit_tracking_id: Optional[Union[int, str]] = Field(**base_fields['event_exhibit_tracking_id_random'])
account_id: Optional[Union[int, str]] = Field(**base_fields['account_id_random'])
event_id: Optional[Union[int, str]] = Field(**base_fields['event_id_random'])
event_exhibit_id: Optional[Union[int, str]] = Field(**base_fields['event_exhibit_id_random'])
event_person_id: Optional[Union[int, str]] = Field(**base_fields['event_person_id_random'])
event_badge_id: Optional[Union[int, str]] = Field(**base_fields['event_badge_id_random'])
# --- Standardized Vision IDs (Strings for API, Integers/Strings for DB) ---
id: Optional[str] = Field(None, **base_fields['event_exhibit_tracking_id_random'])
event_exhibit_tracking_id: Optional[str] = Field(None, **base_fields['event_exhibit_tracking_id_random'])
account_id: Optional[str] = Field(None, **base_fields['account_id_random'])
event_id: Optional[Union[int, str]] = Field(None, **base_fields['event_id_random'])
event_exhibit_id: Optional[Union[int, str]] = Field(None, **base_fields['event_exhibit_id_random'])
event_person_id: Optional[Union[int, str]] = Field(None, **base_fields['event_person_id_random'])
event_badge_id: Optional[Union[int, str]] = Field(None, **base_fields['event_badge_id_random'])
# --- Standardized Legacy / Internal IDs (Excluded) ---
id_random: Optional[str] = Field(None, alias='event_exhibit_tracking_id_random', exclude=True)
@@ -38,49 +39,26 @@ class Event_Exhibit_Tracking_Base(BaseModel):
def map_v3_ids(cls, values):
"""
Vision Transformer:
Map DB keys to clean API keys and strip internal integers during READ operations.
Falls back to Redis/DB lookups if random string IDs are missing from the view.
Map DB keys to clean API keys and strip internal integers.
"""
from app.db_sql import get_id_random
# 1. Map Primary Object ID
rid = values.get('id_random') or values.get('event_exhibit_tracking_id_random')
if rid and isinstance(rid, str):
# 1. Map Random Strings to Clean Names
if rid := values.get('id_random') or values.get('event_exhibit_tracking_id_random'):
values['id'] = rid
values['event_exhibit_tracking_id'] = rid
elif values.get('id') and isinstance(values.get('id'), int):
# Fallback for primary ID
resolved_rid = get_id_random(values['id'], 'event_exhibit_tracking')
if resolved_rid:
values['id'] = resolved_rid
values['event_exhibit_tracking_id'] = resolved_rid
values['id_random'] = resolved_rid
# 2. Map & Resolve Relational IDs
id_map = [
('account_id', 'account'),
('event_id', 'event'),
('event_exhibit_id', 'event_exhibit'),
('event_person_id', 'event_person'),
('event_badge_id', 'event_badge'),
]
for field, table in id_map:
r_val = values.get(f'{field}_random')
if r_val and isinstance(r_val, str):
values[field] = r_val
elif values.get(field) and isinstance(values[field], int):
# Fallback: Resolve from Redis/DB if missing from view result
resolved_rid = get_id_random(values[field], table)
if resolved_rid:
values[field] = resolved_rid
values[f'{field}_random'] = resolved_rid
# 3. Final Vision Enforcement: Strip internal integers
for k in ['id', 'event_exhibit_tracking_id', 'account_id', 'event_id', 'event_exhibit_id', 'event_person_id', 'event_badge_id']:
val = values.get(k)
if val is not None and not isinstance(val, str):
values[k] = None
if a_rid := values.get('account_id_random'): values['account_id'] = a_rid
if e_rid := values.get('event_id_random'): values['event_id'] = e_rid
if ee_rid := values.get('event_exhibit_id_random'): values['event_exhibit_id'] = ee_rid
if ep_rid := values.get('event_person_id_random'): values['event_person_id'] = ep_rid
if eb_rid := values.get('event_badge_id_random'): values['event_badge_id'] = eb_rid
# 2. Prevent "Collision Population"
# We only strip integers for the primary IDs and account_id to prevent leak in READ views.
# Relational IDs (event_id, exhibit_id, etc.) are allowed to remain as integers during
# POST/PUT operations so they reach the database correctly.
for k in ['id', 'event_exhibit_tracking_id', 'account_id']:
if k in values and not isinstance(values[k], str) and values[k] is not None:
del values[k]
return values