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.
331 lines
17 KiB
Python
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() ###
|