This commit refactors numerous Pydantic models to align with the V3 ID Vision standard, ensuring that primary and foreign key fields are represented as clean string IDs in the API. It also introduces and populates the ClassVar in each model to prevent view-only fields and linked objects from being inadvertently written to the database during PATCH/POST operations. Specifically, this includes: - Adding to exclude view-derived or joined fields such as , , nested objects (e.g., , ), and convenience fields (e.g., ). - Adjusting root validators to correctly map string IDs and strip internal integer IDs for API responses. - Resolving a KeyError by adding to . These changes are crucial for maintaining data integrity and consistency with the V3 API architecture.
245 lines
10 KiB
Python
245 lines
10 KiB
Python
import datetime, pytz
|
|
|
|
from typing import Dict, List, Optional, Set, Union, ClassVar
|
|
from pydantic import BaseModel, EmailStr, Field, Json, PrivateAttr, ValidationError, validator, root_validator
|
|
|
|
from app.db_sql import redis_lookup_id_random
|
|
from app.lib_general import log, logging
|
|
|
|
from app.models.common_field_schema import base_fields, default_num_bytes
|
|
|
|
# from app.models.event_badge_models import Event_Badge_Base
|
|
from app.models.event_person_models import Event_Person_Base
|
|
|
|
|
|
# ### BEGIN ### API Event Exhibit Tracking Models ### Event_Exhibit_Base() ###
|
|
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 Legacy / Internal IDs (Excluded) ---
|
|
id_random: Optional[str] = Field(None, alias='event_exhibit_tracking_id_random', exclude=True)
|
|
account_id_random: Optional[str] = Field(None, exclude=True)
|
|
event_id_random: Optional[str] = Field(None, exclude=True)
|
|
event_exhibit_id_random: Optional[str] = Field(None, exclude=True)
|
|
event_person_id_random: Optional[str] = Field(None, exclude=True)
|
|
event_badge_id_random: Optional[str] = Field(None, exclude=True)
|
|
|
|
@root_validator(pre=True)
|
|
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.
|
|
"""
|
|
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):
|
|
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
|
|
|
|
return values
|
|
|
|
external_person_id: Optional[str] # This is probably an email address
|
|
|
|
exhibitor_notes: Optional[str]
|
|
responses_json: Optional[Json] # NOTE: Responses to custom questions
|
|
# Example:
|
|
# {"5_years": {"response": "I see myself in 5 years doing something."}, "colors": {"response": "green"}}
|
|
# {"example_text": {"response": "This is an example of an text answer."}, "example_option_list": {"response": "no"}, "the_code": {"response": "yes"}, "question_everything": {"response": "tomorrow"}, "pre_assesment": {"response": "yes"}}
|
|
|
|
data_json: Optional[Json] # NOTE: Additional data
|
|
|
|
enable: Optional[bool]
|
|
hide: Optional[bool]
|
|
priority: Optional[bool] = False
|
|
sort: Optional[Union[None, int]] = None
|
|
group: Optional[str]
|
|
|
|
notes: Optional[str]
|
|
created_on: Optional[datetime.datetime] = None
|
|
updated_on: Optional[datetime.datetime] = None
|
|
|
|
# Including convenience data
|
|
# This is only for convenience. Probably going to keep unless it causes a problem.
|
|
event_badge_pronouns: Optional[str] = Field(
|
|
alias = 'person_pronouns'
|
|
) # Preferred pronouns
|
|
event_badge_pronouns_override: Optional[str] = Field(
|
|
alias = 'person_pronouns_override'
|
|
) # Override pronouns
|
|
|
|
event_badge_informal_name: Optional[str] = Field(
|
|
alias = 'person_informal_name'
|
|
)
|
|
|
|
event_badge_title_names: Optional[str] = Field(
|
|
# alias = 'person_title_names'
|
|
)
|
|
event_badge_given_name: Optional[str] = Field(
|
|
# alias = 'person_given_name'
|
|
)
|
|
event_badge_middle_name: Optional[str] = Field(
|
|
# alias = 'person_middle_name'
|
|
)
|
|
event_badge_family_name: Optional[str] = Field(
|
|
# alias = 'person_family_name'
|
|
)
|
|
event_badge_designations: Optional[str] = Field(
|
|
# alias = 'person_designations'
|
|
)
|
|
|
|
event_badge_professional_title: Optional[str] = Field(
|
|
# alias = 'person_pronouns_override'
|
|
)
|
|
event_badge_professional_title_override: Optional[str] = Field(
|
|
# alias = 'person_professional_title'
|
|
)
|
|
|
|
event_badge_full_name: Optional[str] = Field(
|
|
# alias = 'person_full_name'
|
|
)
|
|
event_badge_full_name_override: Optional[str] = Field(
|
|
# alias = 'person_full_name_override'
|
|
)
|
|
|
|
event_badge_affiliations: Optional[str] = Field(
|
|
# alias = 'person_affiliations'
|
|
)
|
|
event_badge_affiliations_override: Optional[str] = Field(
|
|
# alias = 'person_affiliations_override'
|
|
)
|
|
|
|
event_badge_email: Optional[str] = Field(
|
|
# alias = 'person_email'
|
|
)
|
|
event_badge_email_override: Optional[str] = Field(
|
|
# alias = 'person_email_override'
|
|
)
|
|
|
|
event_badge_phone: Optional[str] = Field(
|
|
# alias = 'person_phone'
|
|
)
|
|
event_badge_phone_override: Optional[str] = Field(
|
|
# alias = 'person_phone_override'
|
|
)
|
|
|
|
event_badge_address_line_1: Optional[str]
|
|
event_badge_address_line_2: Optional[str]
|
|
event_badge_address_line_3: Optional[str]
|
|
|
|
event_badge_city: Optional[str]
|
|
|
|
event_badge_county: Optional[str] # NOTE: This is for a county within a state or province
|
|
|
|
event_badge_country_subdivision_code: Optional[str]
|
|
event_badge_state_province: Optional[str]
|
|
event_badge_state_province_abb: Optional[str]
|
|
|
|
event_badge_postal_code: Optional[str]
|
|
|
|
event_badge_country_alpha_2_code: Optional[str] = Field(
|
|
# alias = 'person_country_alpha_2_code'
|
|
)
|
|
event_badge_country: Optional[str] = Field(
|
|
# alias = 'person_country'
|
|
)
|
|
|
|
event_badge_location: Optional[str] = Field(
|
|
# alias = 'person_location'
|
|
)
|
|
event_badge_location_override: Optional[str] = Field(
|
|
# alias = 'person_location_override'
|
|
)
|
|
|
|
event_person_informal_name: Optional[str]
|
|
event_person_given_name: Optional[str]
|
|
event_person_family_name: Optional[str]
|
|
event_person_full_name: Optional[str]
|
|
event_person_full_name_override: Optional[str]
|
|
event_person_affiliations: Optional[str]
|
|
event_person_email: Optional[str]
|
|
|
|
event_exhibit_name: Optional[str]
|
|
|
|
allow_tracking: Optional[bool]
|
|
# event_exhibit_allow_tracking: Optional[bool] # This seems unnecessarily specific
|
|
|
|
# Including other related objects
|
|
# event_badge: Optional[Union[Event_Badge_Base, None]]
|
|
event_person: Optional[Union[Event_Person_Base, None]]
|
|
|
|
_processed_at: datetime.datetime = PrivateAttr(default_factory=datetime.datetime.now)
|
|
|
|
# Fields that are part of the model (for reading) but should not be saved to the DB table
|
|
fields_to_exclude_from_db: ClassVar[list] = [
|
|
'account_id',
|
|
'event_badge_pronouns', 'event_badge_pronouns_override', 'event_badge_informal_name',
|
|
'event_badge_title_names', 'event_badge_given_name', 'event_badge_middle_name',
|
|
'event_badge_family_name', 'event_badge_designations',
|
|
'event_badge_professional_title', 'event_badge_professional_title_override',
|
|
'event_badge_full_name', 'event_badge_full_name_override',
|
|
'event_badge_affiliations', 'event_badge_affiliations_override',
|
|
'event_badge_email', 'event_badge_email_override',
|
|
'event_badge_phone', 'event_badge_phone_override',
|
|
'event_badge_address_line_1', 'event_badge_address_line_2', 'event_badge_address_line_3',
|
|
'event_badge_city', 'event_badge_county', 'event_badge_country_subdivision_code',
|
|
'event_badge_state_province', 'event_badge_state_province_abb', 'event_badge_postal_code',
|
|
'event_badge_country_alpha_2_code', 'event_badge_country',
|
|
'event_badge_location', 'event_badge_location_override',
|
|
'event_person_informal_name', 'event_person_given_name', 'event_person_family_name',
|
|
'event_person_full_name', 'event_person_full_name_override',
|
|
'event_person_affiliations', 'event_person_email', 'event_exhibit_name',
|
|
'event_person'
|
|
]
|
|
|
|
class Config:
|
|
underscore_attrs_are_private = True
|
|
allow_population_by_field_name = True
|
|
fields = base_fields
|
|
# ### END ### API Event Exhibit Tracking Models ### Event_Exhibit_Base() ###
|