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.
347 lines
16 KiB
Python
347 lines
16 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_template_models import Event_Badge_Template_Base
|
|
from app.models.order_models import Order_Base
|
|
|
|
|
|
# ### BEGIN ### API Event Badge Models ### Event_Badge_Base() ###
|
|
class Event_Badge_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_badge_id_random'])
|
|
event_badge_id: Optional[Union[int, str]] = Field(**base_fields['event_badge_id_random'])
|
|
account_id: Optional[Union[int, str]] = Field(None, **base_fields['account_id_random'])
|
|
event_id: Optional[Union[int, str]] = Field(**base_fields['event_id_random'])
|
|
|
|
# NOTE: This should only be used when the event_person record can not be created. And records before 2022.
|
|
event_id_only: Optional[Union[int, str]] = Field(**base_fields['event_id_random'])
|
|
|
|
event_badge_template_id: Optional[Union[int, str]] = Field(**base_fields['event_badge_template_id_random'])
|
|
event_person_id: Optional[Union[int, str]] = Field(**base_fields['event_person_id_random'])
|
|
person_id: Optional[Union[int, str]] = Field(**base_fields['person_id_random'])
|
|
|
|
# --- Standardized Legacy / Internal IDs (Excluded) ---
|
|
id_random: Optional[str] = Field(None, alias='event_badge_id_random', exclude=True)
|
|
account_id_random: Optional[str] = Field(None, exclude=True)
|
|
event_id_random: Optional[str] = Field(None, exclude=True)
|
|
event_id_random_only: Optional[str] = Field(None, exclude=True)
|
|
event_badge_template_id_random: Optional[str] = Field(None, exclude=True)
|
|
event_person_id_random: Optional[str] = Field(None, exclude=True)
|
|
person_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.
|
|
During CREATE (POST) operations, we ensure resolved integers are preserved.
|
|
"""
|
|
# 1. Map Random Strings to Clean Names
|
|
rid = values.get('id_random') or values.get('event_badge_id_random')
|
|
if rid and isinstance(rid, str):
|
|
values['id'] = rid
|
|
values['event_badge_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 eo_rid := values.get('event_id_random_only'): values['event_id_only'] = eo_rid
|
|
if et_rid := values.get('event_badge_template_id_random'): values['event_badge_template_id'] = et_rid
|
|
if ep_rid := values.get('event_person_id_random'): values['event_person_id'] = ep_rid
|
|
if p_rid := values.get('person_id_random'): values['person_id'] = p_rid
|
|
|
|
# 2. Prevent leakage of integers during API responses (Vision Standard)
|
|
for k in ['id', 'event_badge_id', 'account_id', 'event_id', 'event_id_only', 'event_badge_template_id', 'event_person_id', 'person_id']:
|
|
val = values.get(k)
|
|
if val is not None and not isinstance(val, str):
|
|
values[k] = None
|
|
|
|
return values
|
|
|
|
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)
|
|
|
|
pronouns: Optional[str] # Preferred pronouns
|
|
pronouns_override: Optional[str] # Override pronouns
|
|
|
|
informal_name: Optional[str]
|
|
|
|
title_names: Optional[str] # Title for generation, official position, or professional or academic qualification, other honorific, or other name prefix
|
|
given_name: Optional[str]
|
|
middle_name: Optional[str]
|
|
family_name: Optional[str]
|
|
designations: Optional[str] # Temporary or long-term designations related to family, relationships, person differentiation (Junior/Senior), location, social status, professional qualifications, legal status, or other name suffix (degrees and credentials)
|
|
|
|
professional_title: Optional[str] # Professional title
|
|
display_professional_title: Optional[str] # NOTE: Deprecated! Phasing out! Use *full_name_override* instead.
|
|
professional_title_override: Optional[str] # Override professional title
|
|
# title: Optional[str] # NOTE: Phasing out! Use *professional_title* instead.
|
|
|
|
# BEGIN # Auto created name variations
|
|
full_name: Optional[str] # title_names given_name middle_name family_name designations
|
|
full_name_override: Optional[str] # # Override full_name; Actual name shown on badge and other "public" areas
|
|
|
|
affiliations: Optional[str] # One or more affiliations with organizations, companies, and other groups
|
|
affiliations_override: Optional[str] # Override affiliations
|
|
|
|
email: Optional[str]
|
|
email_override: Optional[str]
|
|
|
|
phone: Optional[str]
|
|
phone_override: Optional[str]
|
|
|
|
address_line_1: Optional[str]
|
|
address_line_2: Optional[str]
|
|
address_line_3: Optional[str]
|
|
|
|
city: Optional[str]
|
|
|
|
county: Optional[str] # NOTE: This is for a county within a state or province
|
|
|
|
country_subdivision_code: Optional[str]
|
|
state_province: Optional[str]
|
|
state_province_abb: Optional[str]
|
|
|
|
postal_code: Optional[str]
|
|
|
|
country_alpha_2_code: Optional[str]
|
|
country: Optional[str]
|
|
|
|
# full_address: Optional[str]
|
|
|
|
location: Optional[str] # Actual location name shown on badge and other "public" areas
|
|
location_override: Optional[str] # Override location
|
|
location_short: Optional[str] # Auto generated short version
|
|
location_long: Optional[str] # Auto generated long version
|
|
|
|
# This is updated using SQL triggers and a SQL function
|
|
# Combines informal, given, middle, family, email
|
|
query_str: Optional[str]
|
|
|
|
# NOTE: More badge fields need to be added here once things are cleaned up
|
|
badge_type_code_override: Optional[str]
|
|
badge_type_override: Optional[str]
|
|
badge_type_code: Optional[str]
|
|
badge_type: Optional[str]
|
|
member_type_code: Optional[str]
|
|
member_type: Optional[str]
|
|
member_status: Optional[str]
|
|
registration_type_code: Optional[str]
|
|
registration_type: Optional[str]
|
|
|
|
other_1: Optional[str]
|
|
other_2: Optional[str]
|
|
|
|
ticket_0_code: Optional[str]
|
|
ticket_1_code: Optional[str]
|
|
ticket_2_code: Optional[str]
|
|
ticket_3_code: Optional[str]
|
|
ticket_4_code: Optional[str]
|
|
ticket_5_code: Optional[str]
|
|
ticket_6_code: Optional[str]
|
|
ticket_7_code: Optional[str]
|
|
ticket_8_code: Optional[str]
|
|
ticket_9_code: Optional[str]
|
|
ticket_10_code: Optional[str]
|
|
|
|
agree_to_tc: Optional[bool] # Agree to terms and conditions
|
|
allow_tracking: Optional[bool] # Allow tracking for lead retrieval and other marketing
|
|
|
|
print_first_datetime: Optional[datetime.datetime] = None
|
|
print_last_datetime: Optional[datetime.datetime] = None
|
|
print_count: Optional[int]
|
|
|
|
# full_name_font_size: Optional[str] # Not currently used 2023-01-25
|
|
# professional_title_font_size: Optional[str] # Not currently used 2023-01-25
|
|
# affiliations_font_size: Optional[str] # Not currently used 2023-01-25
|
|
# location_font_size: Optional[str] # Not currently used 2023-01-25
|
|
# css: Optional[str] # Not currently used 2023-01-25
|
|
|
|
cfg_json: Optional[Union[Json, None]] # Store per badge config options like font size; Not currently used 2024-06-11
|
|
data_json: Optional[Union[Json, None]] # For key value data. Careful with overwriting existing fields! Not currently used 2024-06-11
|
|
|
|
default_qry_str: Optional[str] # Default query string used for searching and filtering badges. Updated using SQL triggers and a SQL function
|
|
|
|
hide: Optional[bool]
|
|
priority: Optional[bool]
|
|
sort: Optional[int]
|
|
group: Optional[str]
|
|
enable: Optional[bool]
|
|
|
|
notes: Optional[str]
|
|
created_on: Optional[datetime.datetime] = None
|
|
updated_on: Optional[datetime.datetime] = None
|
|
|
|
# Including other related objects
|
|
order: Optional[Union[Order_Base, None]]
|
|
ticket_list: Optional[list]
|
|
event_badge_template: Optional[Union[Event_Badge_Template_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', 'order', 'ticket_list', 'event_badge_template'
|
|
]
|
|
|
|
class Config:
|
|
underscore_attrs_are_private = True
|
|
allow_population_by_field_name = True
|
|
fields = base_fields
|
|
|
|
|
|
class Event_Badge_Basic_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(None, **base_fields['event_badge_id_random'])
|
|
event_badge_id: Optional[Union[int, str]] = Field(None, **base_fields['event_badge_id_random'])
|
|
account_id: Optional[Union[int, str]] = Field(None, **base_fields['account_id_random'])
|
|
event_badge_template_id: Optional[Union[int, str]] = Field(None, **base_fields['event_badge_template_id_random'])
|
|
event_person_id: Optional[Union[int, str]] = Field(None, **base_fields['event_person_id_random'])
|
|
|
|
# --- Standardized Legacy / Internal IDs (Excluded) ---
|
|
id_random: Optional[str] = Field(None, alias='event_badge_id_random', exclude=True)
|
|
account_id_random: Optional[str] = Field(None, exclude=True)
|
|
event_badge_template_id_random: Optional[str] = Field(None, exclude=True)
|
|
event_person_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.
|
|
During CREATE (POST) operations, we ensure resolved integers are preserved.
|
|
"""
|
|
# 1. Map Random Strings to Clean Names
|
|
rid = values.get('id_random') or values.get('event_badge_id_random')
|
|
if rid and isinstance(rid, str):
|
|
values['id'] = rid
|
|
values['event_badge_id'] = rid
|
|
|
|
if a_rid := values.get('account_id_random'): values['account_id'] = a_rid
|
|
if et_rid := values.get('event_badge_template_id_random'): values['event_badge_template_id'] = et_rid
|
|
if ep_rid := values.get('event_person_id_random'): values['event_person_id'] = ep_rid
|
|
|
|
# 2. Prevent "Collision Population" or leakage of integers during API responses
|
|
for k in ['id', 'event_badge_id', 'account_id', 'event_badge_template_id', 'event_person_id']:
|
|
val = values.get(k)
|
|
if val is not None and not isinstance(val, str):
|
|
if values.get(f'{k}_random') or (k=='id' and values.get('id_random')):
|
|
del values[k]
|
|
|
|
return values
|
|
|
|
external_id: Optional[str] # Generated internally or externally. Needs to be stable. It should not change.
|
|
# external_sys_id: Optional[str] # Person ID generated by external system (should be stable and not change)
|
|
# external_reg_id: Optional[str] # Registration ID generated by external system (should be stable and not change)
|
|
|
|
pronouns: Optional[str] # Preferred pronouns
|
|
pronouns_override: Optional[str] # Preferred pronouns
|
|
|
|
informal_name: Optional[str]
|
|
|
|
title_names: Optional[str] # Title for generation, official position, or professional or academic qualification, other honorific, or other name prefix
|
|
given_name: Optional[str]
|
|
middle_name: Optional[str]
|
|
family_name: Optional[str]
|
|
designations: Optional[str] # Temporary or long-term designations related to family, relationships, person differentiation (Junior/Senior), location, social status, professional qualifications, legal status, or other name suffix
|
|
|
|
professional_title: Optional[str] # Professional title
|
|
professional_title_override: Optional[str] # Override professional title
|
|
|
|
# BEGIN # Auto created name variations
|
|
full_name: Optional[str] # title_names given_name middle_name family_name designations
|
|
full_name_override: Optional[str] # Override full_name; Actual name shown on badge and other "public" areas
|
|
|
|
affiliations: Optional[str] # One or more affiliations with organizations, companies, and other groups
|
|
affiliations_override: Optional[str] # Override affiliations
|
|
|
|
email: Optional[str]
|
|
email_override: Optional[str]
|
|
|
|
phone: Optional[str]
|
|
phone_override: Optional[str]
|
|
|
|
# address_line_1: Optional[str]
|
|
# address_line_2: Optional[str]
|
|
# address_line_3: Optional[str]
|
|
|
|
# city: Optional[str]
|
|
|
|
# county: Optional[str] # NOTE: This is for a county within a state or province
|
|
|
|
# country_subdivision_code: Optional[str]
|
|
# state_province: Optional[str]
|
|
# state_province_abb: Optional[str]
|
|
|
|
# postal_code: Optional[str]
|
|
|
|
country_alpha_2_code: Optional[str]
|
|
country: Optional[str]
|
|
|
|
# full_address: Optional[str]
|
|
location: Optional[str] # Actual location name shown on badge and other "public" areas
|
|
location_override: Optional[str] # Override location
|
|
# location_short: Optional[str] # Auto generated short version
|
|
# location_long: Optional[str] # Auto generated long version
|
|
|
|
# NOTE: More badge fields need to be added here once things are cleaned up
|
|
# badge_type_code: Optional[str]
|
|
# badge_type: Optional[str]
|
|
# member_type_code: Optional[str]
|
|
# member_type: Optional[str]
|
|
# registration_type_code: Optional[str]
|
|
# registration_type: Optional[str]
|
|
|
|
# other_1: Optional[str]
|
|
# other_2: Optional[str]
|
|
|
|
allow_tracking: Optional[bool] # Allow tracking for lead retrieval and other marketing
|
|
# agree_to_tc: Optional[bool] # Agree to terms and conditions
|
|
|
|
print_first_datetime: Optional[datetime.datetime] = None
|
|
print_last_datetime: Optional[datetime.datetime] = None
|
|
print_count: Optional[int]
|
|
|
|
hide: Optional[bool]
|
|
priority: Optional[bool]
|
|
sort: Optional[int]
|
|
group: Optional[str]
|
|
enable: Optional[bool]
|
|
|
|
notes: Optional[str]
|
|
created_on: Optional[datetime.datetime] = None
|
|
updated_on: Optional[datetime.datetime] = None
|
|
|
|
# Including other related objects
|
|
# order: Optional[Union[Order_Base, None]]
|
|
# ticket_list: Optional[list]
|
|
event_badge_template: Optional[Union[Event_Badge_Template_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_template'
|
|
]
|
|
|
|
class Config:
|
|
underscore_attrs_are_private = True
|
|
allow_population_by_field_name = True
|
|
fields = base_fields
|
|
|