Files
OSIT-AE-API-FastAPI/app/models/event_person_models.py
Scott Idem 0f4b4d2f51 feat: Implement V3 ID Vision and fields_to_exclude_from_db across core models
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.
2026-02-24 16:21:27 -05:00

331 lines
17 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_models import Event_Base # Causes an import loop
# from app.models.event_abstract_models import Event_Abstract_Base
from app.models.event_badge_models import Event_Badge_Base
# from app.models.event_exhibit_tracking_models import Event_Exhibit_Tracking_Base # Causes an import loop
from app.models.event_person_profile_models import Event_Person_Profile_Base
from app.models.event_registration_models import Event_Registration_Base
from app.models.person_models import Person_Base
from app.models.user_models import User_Base, User_Out_Base
# ### BEGIN ### API Event Person Models ### Event_Person_Base() ###
class Event_Person_Base(BaseModel):
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
# --- Standardized Vision IDs (Strings) ---
id: Optional[str] = Field(None, **base_fields['event_person_id_random'])
event_person_id: Optional[str] = Field(None, **base_fields['event_person_id_random'])
account_id: Optional[str] = Field(None, **base_fields['account_id_random'])
event_id: Optional[str] = Field(None, **base_fields['event_id_random'])
event_badge_id: Optional[str] = Field(None, **base_fields['event_badge_id_random'])
event_badge_vendor_id: Optional[str] = Field(None, **base_fields['event_badge_id_random'])
event_badge_vip_id: Optional[str] = Field(None, **base_fields['event_badge_id_random'])
event_person_profile_id: Optional[str] = Field(None, **base_fields['event_person_profile_id_random'])
event_registration_id: Optional[str] = Field(None, **base_fields['event_registration_id_random'])
person_id: Optional[str] = Field(None, **base_fields['person_id_random'])
user_id: Optional[str] = Field(None, **base_fields['user_id_random'])
# --- Standardized Legacy / Internal IDs (Excluded) ---
id_random: Optional[str] = Field(None, alias='event_person_id_random', exclude=True)
account_id_random: Optional[str] = Field(None, exclude=True)
event_id_random: Optional[str] = Field(None, exclude=True)
event_badge_id_random: Optional[str] = Field(None, exclude=True)
event_badge_vendor_id_random: Optional[str] = Field(None, exclude=True)
event_badge_vip_id_random: Optional[str] = Field(None, exclude=True)
event_person_profile_id_random: Optional[str] = Field(None, exclude=True)
event_registration_id_random: Optional[str] = Field(None, exclude=True)
person_id_random: Optional[str] = Field(None, exclude=True)
user_id_random: Optional[str] = Field(None, exclude=True)
external_id: Optional[str] # Generated internally or externally. Needs to be stable. It should not change.
external_event_id: Optional[str] # Event ID generated by external system. Needs to be stable. It should not change.
external_registration_id: Optional[str] # Registration ID generated by external system (should be stable and not change)
external_reg_id: Optional[str] # NOTE: Deprecated; Move to external_registration_id. Registration ID generated by external system (should be stable and not change)
external_person_id: Optional[str] # Person ID generated by external system (should be stable and not change)
external_sys_id: Optional[str] # NOTE: Deprecated; Move to external_person_id. Person ID generated by external system (should be stable and not change)
agree_to_tc: Optional[bool] # Agree to terms and conditions
allow_tracking: Optional[bool] # Allow tracking for lead retrieval and other marketing
passcode: Optional[str] # Passcode for accessing the event
cfg_json: Optional[Union[Json, None]] # Store per person config options like theme, language, etc
data_json: Optional[Union[Json, None]] # For key value data. Careful with overwriting existing fields!
file_count: Optional[int]
priority: Optional[bool]
sort: Optional[int]
group: Optional[str]
enable: Optional[bool]
hide: Optional[bool]
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.
# This block of person data should come from the event_person_profile table
informal_name: Optional[str]
given_name: Optional[str]
middle_name: Optional[str]
family_name: Optional[str]
full_name_override: Optional[str]
full_name: Optional[str]
affiliations: Optional[str]
email: Optional[str]
website_url: Optional[str]
# state_province_name: Optional[str] # Using extended_json for now for AACC abstracts
extended_json: Optional[Union[Json, None]]
event_badge_informal_name: Optional[str]
event_badge_given_name: Optional[str]
event_badge_middle_name: Optional[str]
event_badge_family_name: Optional[str]
event_badge_full_name: Optional[str]
event_badge_full_name_override: Optional[str]
event_badge_affiliations: Optional[str]
event_badge_email: Optional[str]
event_badge_city: Optional[str]
event_badge_state_province: Optional[str]
event_badge_country_alpha_2_code: Optional[str]
event_badge_country: Optional[str]
# This is the same as the other person data above
event_person_informal_name: Optional[str]
event_person_given_name: Optional[str]
event_person_middle_name: Optional[str]
event_person_family_name: Optional[str]
event_person_name_override: Optional[str]
event_person_full_name: Optional[str]
event_person_affiliations: Optional[str]
event_person_email: Optional[str]
event_person_extended_json: Optional[Union[Json, None]]
person_informal_name: Optional[str]
person_given_name: Optional[str]
person_middle_name: Optional[str]
person_family_name: Optional[str]
person_full_name: Optional[str]
person_full_name_override: Optional[str]
# person_display_name: Optional[str]
person_affiliations: Optional[str]
person_email: Optional[str]
user_email: Optional[str]
user_name: Optional[str]
user_username: Optional[str]
# Including other related objects
# event: Optional[Event_Base] # Causes an import loop
# event_abstract_list: Optional[list[Event_Abstract_Base]] # Use event_person_detail table. An event_person record can be linked to one or more abstracts
event_badge: Optional[Event_Badge_Base] # Default attendee badge
event_badge_vendor: Optional[Event_Badge_Base] # Additional vendor badge
event_badge_vip: Optional[Event_Badge_Base] # Additional VIP badge
event_exhibit_list: Optional[list] # Use event_person_detail table. An event_person record can be linked to one or more exhibits
# event_exhibit_tracking_list: Optional[list[Event_Exhibit_Tracking_Base]]
event_file_list: Optional[list] # Use event_person_detail table. An event_person record can be linked to one or more files
event_location_list: Optional[list] # Use event_person_detail table. An event_person record can be linked to one or more locations (but unlikely?)
#event_person_detail_list: Optional[list] # list of Event_Person_Detail
event_person_profile: Optional[Event_Person_Profile_Base]
event_presentation_list: Optional[list] # Use event_person_detail table. An event_person record can be linked to one or more presentations
event_presenter_list: Optional[list] # Use event_person_detail table. An event_person record can be linked to one or more presenters (part of multiple presentations)
event_registration: Optional[Event_Registration_Base]
event_session: Optional[list] # Use event_person_detail table. An event_person record can be linked to one or more sessions
event_track: Optional[list] # Use event_person_detail table. An event_person record can be linked to one or more tracks
person: Optional[Person_Base]
user: Optional[User_Base]
_processed_at: datetime.datetime = PrivateAttr(default_factory=datetime.datetime.now)
@root_validator(pre=True)
def map_v3_ids(cls, values):
"""
Vision Transformer:
Map DB keys to clean API keys and strip internal integers.
"""
# 1. Map Random Strings to Clean Names
if rid := values.get('id_random') or values.get('event_person_id_random'):
values['id'] = rid
values['event_person_id'] = rid
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 b_rid := values.get('event_badge_id_random'): values['event_badge_id'] = b_rid
if bv_rid := values.get('event_badge_vendor_id_random'): values['event_badge_vendor_id'] = bv_rid
if bvip_rid := values.get('event_badge_vip_id_random'): values['event_badge_vip_id'] = bvip_rid
if ep_rid := values.get('event_person_profile_id_random'): values['event_person_profile_id'] = ep_rid
if er_rid := values.get('event_registration_id_random'): values['event_registration_id'] = er_rid
if p_rid := values.get('person_id_random'): values['person_id'] = p_rid
if u_rid := values.get('user_id_random'): values['user_id'] = u_rid
# 2. Prevent "Collision Population"
for k in ['id', 'event_person_id', 'account_id', 'event_id', 'event_badge_id', 'event_badge_vendor_id', 'event_badge_vip_id', 'event_person_profile_id', 'event_registration_id', 'person_id', 'user_id']:
if k in values and not isinstance(values[k], str) and values[k] is not None:
del values[k]
return values
# 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] = [
'file_count',
'informal_name', 'given_name', 'middle_name', 'family_name', 'full_name_override', 'full_name',
'affiliations', 'email', 'website_url',
'event_badge_informal_name', 'event_badge_given_name', 'event_badge_middle_name',
'event_badge_family_name', 'event_badge_full_name', 'event_badge_full_name_override',
'event_badge_affiliations', 'event_badge_email', 'event_badge_city',
'event_badge_state_province', 'event_badge_country_alpha_2_code', 'event_badge_country',
'event_person_informal_name', 'event_person_given_name', 'event_person_middle_name',
'event_person_family_name', 'event_person_name_override', 'event_person_full_name',
'event_person_affiliations', 'event_person_email', 'event_person_extended_json',
'person_informal_name', 'person_given_name', 'person_middle_name', 'person_family_name',
'person_full_name', 'person_full_name_override', 'person_affiliations', 'person_email',
'user_email', 'user_name', 'user_username',
'event_badge', 'event_badge_vendor', 'event_badge_vip', 'event_exhibit_list',
'event_file_list', 'event_location_list', 'event_person_profile',
'event_presentation_list', 'event_presenter_list', 'event_registration',
'event_session', 'event_track', 'person', 'user'
]
class Config:
underscore_attrs_are_private = True
allow_population_by_field_name = True
fields = base_fields
# ### END ### API Event Person Models ### Event_Person_Base() ###
# ### BEGIN ### API Event Person Models ### Event_Person_New_Base() ###
class Event_Person_New_Base(BaseModel):
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
# --- Standardized Vision IDs (Strings) ---
id: Optional[str] = Field(None, **base_fields['event_person_id_random'])
event_person_id: Optional[str] = Field(None, **base_fields['event_person_id_random'])
account_id: Optional[str] = Field(None, **base_fields['account_id_random'])
event_id: Optional[str] = Field(None, **base_fields['event_id_random'])
# --- Standardized Legacy / Internal IDs (Excluded) ---
id_random: Optional[str] = Field(None, alias='event_person_id_random', exclude=True)
account_id_random: Optional[str] = Field(None, exclude=True)
event_id_random: Optional[str] = Field(None, exclude=True)
extended_json: Optional[Union[Json, None]]
# Including convenience data
# This is only for convenience. Probably going to keep unless it causes a problem.
# This block of person data should come from the event_person_profile table
informal_name: Optional[str]
given_name: Optional[str]
middle_name: Optional[str]
family_name: Optional[str]
full_name: Optional[str]
full_name_override: Optional[str]
affiliations: Optional[str]
email: Optional[str]
website_url: Optional[str]
state_province_name: Optional[str]
event_badge_informal_name: Optional[str]
event_badge_given_name: Optional[str]
event_badge_middle_name: Optional[str]
event_badge_family_name: Optional[str]
event_badge_full_name: Optional[str]
event_badge_full_name_override: Optional[str]
event_badge_affiliations: Optional[str]
event_badge_email: Optional[str]
event_badge_city: Optional[str]
event_badge_state_province: Optional[str]
event_badge_country_alpha_2_code: Optional[str]
event_badge_country: Optional[str]
# This is the same as the other person data above
event_person_informal_name: Optional[str]
event_person_given_name: Optional[str]
event_person_middle_name: Optional[str]
event_person_family_name: Optional[str]
event_person_name_override: Optional[str]
event_person_full_name: Optional[str]
event_person_affiliations: Optional[str]
event_person_email: Optional[str]
person_given_name: Optional[str]
person_middle_name: Optional[str]
person_family_name: Optional[str]
person_full_name: Optional[str]
person_full_name_override: Optional[str]
# person_display_name: Optional[str]
# affiliations: Optional[str] # One or more affiliations with organizations, companies, and other groups
# email: Optional[str]
#new_password: str = Field(default_factory = lambda:secrets.token_urlsafe(default_num_bytes))
#password: Optional[str]
new_password: Optional[str]
notes: Optional[str]
created_on: Optional[datetime.datetime] = None
updated_on: Optional[datetime.datetime] = None
_processed_at: datetime.datetime = PrivateAttr(default_factory=datetime.datetime.now)
@root_validator(pre=True)
def map_v3_ids(cls, values):
"""
Vision Transformer:
Map DB keys to clean API keys and strip internal integers.
"""
# 1. Map Random Strings to Clean Names
if rid := values.get('id_random') or values.get('event_person_id_random'):
values['id'] = rid
values['event_person_id'] = rid
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
# 2. Prevent "Collision Population"
for k in ['id', 'event_person_id', 'account_id', 'event_id']:
if k in values and not isinstance(values[k], str) and values[k] is not None:
del values[k]
return values
# 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] = [
'informal_name', 'given_name', 'middle_name', 'family_name', 'full_name',
'full_name_override', 'affiliations', 'email', 'website_url', 'state_province_name',
'event_badge_informal_name', 'event_badge_given_name', 'event_badge_middle_name',
'event_badge_family_name', 'event_badge_full_name', 'event_badge_full_name_override',
'event_badge_affiliations', 'event_badge_email', 'event_badge_city',
'event_badge_state_province', 'event_badge_country_alpha_2_code', 'event_badge_country',
'event_person_informal_name', 'event_person_given_name', 'event_person_middle_name',
'event_person_family_name', 'event_person_name_override', 'event_person_full_name',
'event_person_affiliations', 'event_person_email',
'person_given_name', 'person_middle_name', 'person_family_name',
'person_full_name', 'person_full_name_override', 'new_password'
]
class Config:
underscore_attrs_are_private = True
allow_population_by_field_name = True
fields = base_fields
# ### END ### API Event Person Models ### Event_Person_New_Base() ###