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.
272 lines
11 KiB
Python
272 lines
11 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.core_object_models import Core_Std_Obj_Base
|
|
# from app.models.event_models import Event_Base
|
|
from app.models.event_cfg_models import Event_Cfg_Base
|
|
# from app.models.event_person_models import Event_Person_Base
|
|
# from app.models.event_presentation_models import Event_Presentation_Base
|
|
#from app.models.event_presenter_models import Event_Presenter_Base # This creates an import loop
|
|
# from app.models.event_session_models import Event_Session_Base
|
|
|
|
|
|
# ### BEGIN ### API Event Abstract Models ### Event_Abstract_Base() ###
|
|
# Update 2023-03-20
|
|
class Event_Abstract_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_abstract_id_random'])
|
|
event_abstract_id: Optional[str] = Field(None, **base_fields['event_abstract_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_person_id: Optional[str] = Field(None, **base_fields['event_person_id_random'])
|
|
event_presentation_id: Optional[str] = Field(None, **base_fields['event_presentation_id_random'])
|
|
event_presenter_id: Optional[str] = Field(None, **base_fields['event_presenter_id_random'])
|
|
event_session_id: Optional[str] = Field(None, **base_fields['event_session_id_random'])
|
|
grant_id: Optional[str] = Field(None, **base_fields['grant_id_random'])
|
|
|
|
# event_track_id: Optional[str] = Field(None, **base_fields['event_track_id_random'])
|
|
|
|
# poc_event_person_id_random: Optional[str] # Maybe change this to primary_event_person?
|
|
# poc_event_person_id: Optional[int] # Maybe change this to primary_event_person?
|
|
|
|
external_id: Optional[str]
|
|
code: Optional[str]
|
|
|
|
grant_code: Optional[str]
|
|
# grant_type_code: Optional[str]
|
|
# grant_json: Optional[Union[Json, None]]
|
|
|
|
name: Optional[str]
|
|
description: Optional[str]
|
|
abstract: Optional[str]
|
|
|
|
passcode: Optional[str] # = '7777777'
|
|
|
|
data_json: Optional[Json]
|
|
|
|
enable: Optional[bool]
|
|
hide: Optional[bool]
|
|
priority: Optional[bool]
|
|
sort: Optional[int]
|
|
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_session_code: Optional[str]
|
|
event_session_name: Optional[str]
|
|
|
|
# Including other related objects
|
|
# event: Optional[Event_Base]
|
|
event_file_list: Optional[list] # Optional[Event_File_Base]
|
|
# event_person: Optional[Event_Person_Base]
|
|
event_person: Optional[dict]
|
|
# event_presentation: Optional[Event_Presentation_Base]
|
|
#event_presenter: Optional[Event_Presenter_Base] # This creates an import loop
|
|
event_presenter_list: Optional[list] # Optional[Event_Presenter_Base]
|
|
# event_session: Optional[Event_Session_Base]
|
|
# event_track: Optional[Event_Track_Base]
|
|
# poc_event_person: Optional[Event_Person_Base] # Maybe change this to primary_event_person?
|
|
# poc_person: Optional[Person_Base] # Maybe change this to primary_person?
|
|
|
|
_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_abstract_id_random'):
|
|
values['id'] = rid
|
|
values['event_abstract_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 ep_rid := values.get('event_person_id_random'):
|
|
values['event_person_id'] = ep_rid
|
|
if epr_rid := values.get('event_presentation_id_random'):
|
|
values['event_presentation_id'] = epr_rid
|
|
if eps_rid := values.get('event_presenter_id_random'):
|
|
values['event_presenter_id'] = eps_rid
|
|
if es_rid := values.get('event_session_id_random'):
|
|
values['event_session_id'] = es_rid
|
|
if g_rid := values.get('grant_id_random'):
|
|
values['grant_id'] = g_rid
|
|
|
|
# 2. Prevent "Collision Population"
|
|
for k in ['id', 'event_abstract_id', 'account_id', 'event_id', 'event_person_id', 'event_presentation_id', 'event_presenter_id', 'event_session_id', 'grant_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] = [
|
|
'account_id', 'event_session_code', 'event_session_name',
|
|
'event_file_list', 'event_person', 'event_presenter_list'
|
|
]
|
|
|
|
class Config:
|
|
underscore_attrs_are_private = True
|
|
allow_population_by_field_name = False
|
|
fields = base_fields
|
|
# ### END ### API Event Abstract Models ### Event_Abstract_Base() ###
|
|
|
|
|
|
|
|
|
|
|
|
# ### BEGIN ### API Event Abstract Models ### Event_Abstract_Base() ###
|
|
# Update 2023-03-22
|
|
class Event_Abstract_Base_New(Core_Std_Obj_Base):
|
|
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_abstract_id_random'])
|
|
event_abstract_id: Optional[str] = Field(None, **base_fields['event_abstract_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_person_id: Optional[str] = Field(None, **base_fields['event_person_id_random'])
|
|
event_presentation_id: Optional[str] = Field(None, **base_fields['event_presentation_id_random'])
|
|
event_presenter_id: Optional[str] = Field(None, **base_fields['event_presenter_id_random'])
|
|
event_session_id: Optional[str] = Field(None, **base_fields['event_session_id_random'])
|
|
grant_id: Optional[str] = Field(None, **base_fields['grant_id_random'])
|
|
|
|
# event_track_id_random: Optional[str]
|
|
|
|
# poc_event_person_id_random: Optional[str] # Maybe change this to primary_event_person?
|
|
|
|
code: Optional[str]
|
|
|
|
external_id: Optional[str]
|
|
|
|
# type_code: Optional[str]
|
|
|
|
description: Optional[str]
|
|
abstract: Optional[str] # Actual abstract text
|
|
|
|
passcode: Optional[str]
|
|
|
|
grant_code: Optional[str]
|
|
grant_type_code: Optional[str]
|
|
grant_json: Optional[Union[Json, None]]
|
|
|
|
category_code: Optional[str]
|
|
# category2_code: Optional[str]
|
|
|
|
topics_json: Optional[Union[Json, None]]
|
|
|
|
submitter_json: Optional[Union[Json, None]]
|
|
coauthors_json: Optional[Union[Json, None]]
|
|
|
|
@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_abstract_id_random'):
|
|
values['id'] = rid
|
|
values['event_abstract_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 ep_rid := values.get('event_person_id_random'):
|
|
values['event_person_id'] = ep_rid
|
|
if epr_rid := values.get('event_presentation_id_random'):
|
|
values['event_presentation_id'] = epr_rid
|
|
if eps_rid := values.get('event_presenter_id_random'):
|
|
values['event_presenter_id'] = eps_rid
|
|
if es_rid := values.get('event_session_id_random'):
|
|
values['event_session_id'] = es_rid
|
|
if g_rid := values.get('grant_id_random'):
|
|
values['grant_id'] = g_rid
|
|
|
|
# 2. Prevent "Collision Population"
|
|
for k in ['id', 'event_abstract_id', 'account_id', 'event_id', 'event_person_id', 'event_presentation_id', 'event_presenter_id', 'event_session_id', 'grant_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] = [
|
|
'account_id'
|
|
]
|
|
|
|
class Config:
|
|
underscore_attrs_are_private = True
|
|
allow_population_by_field_name = False
|
|
fields = base_fields
|
|
# ### END ### API Event Abstract Models ### Event_Abstract_Base() ###
|
|
|
|
|
|
# ### BEGIN ### API Event Abstract Models ### Event_Abstract_In() ###
|
|
# Update 2023-03-22
|
|
class Event_Abstract_In(Event_Abstract_Base_New):
|
|
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
|
|
log.debug(locals())
|
|
|
|
# Inherits everything from Event_Abstract_Base_New including the Vision ID pattern.
|
|
# We do NOT redefine 'id' as int here.
|
|
|
|
pass
|
|
# ### END ### API Event Abstract Models ### Event_Abstract_In() ###
|
|
|
|
|
|
# ### BEGIN ### API Event Abstract Models ### Event_Abstract_Ext() ###
|
|
# Update 2023-09-22
|
|
class Event_Abstract_Ext(Event_Abstract_Base_New):
|
|
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
|
|
log.debug(locals())
|
|
|
|
# Including convenience data
|
|
# This is only for convenience. Probably going to keep unless it causes a problem.
|
|
event_session_code: Optional[str]
|
|
event_session_name: Optional[str]
|
|
|
|
# Including other related objects
|
|
# event: Optional[Event_Base]
|
|
event_cfg: Optional[Event_Cfg_Base]
|
|
event_file_list: Optional[list] # Optional[Event_File_Base]
|
|
# from app.models.event_person_models import Event_Person_Base
|
|
# event_person: Optional[Event_Person_Base]
|
|
event_person: Optional[dict]
|
|
# event_presentation: Optional[Event_Presentation_Base]
|
|
#event_presenter: Optional[Event_Presenter_Base] # This creates an import loop
|
|
# event_presenter_list: Optional[list] # Optional[Event_Presenter_Base]
|
|
# event_session: Optional[Event_Session_Base]
|
|
# event_track: Optional[Event_Track_Base]
|
|
# poc_event_person: Optional[Event_Person_Base] # Maybe change this to primary_event_person?
|
|
# poc_person: Optional[Person_Base] # Maybe change this to primary_person?
|
|
|
|
# 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] = Event_Abstract_Base_New.fields_to_exclude_from_db + [
|
|
'event_session_code', 'event_session_name', 'event_cfg', 'event_file_list', 'event_person'
|
|
]
|
|
# ### END ### API Event Abstract Models ### Event_Abstract_Ext() ###
|