feat(v3-standardization): implement ID Vision for Event models and update docs
This commit is contained in:
@@ -79,7 +79,7 @@
|
||||
- Reduced `hosted_file.py` (1596 -> 361 lines).
|
||||
- Implemented Zoom Events OAuth2 and Sync backend.
|
||||
- Verified Full File Lifecycle (Upload -> Download -> Delete) via standardized E2E test.
|
||||
- **Immediate Next Step:** Verify V3 Search and CRUD for Events modules.
|
||||
- **Immediate Next Step:** Await further instructions.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import datetime, pytz
|
||||
|
||||
from typing import Dict, List, Optional, Set, Union
|
||||
from pydantic import BaseModel, EmailStr, Field, Json, PrivateAttr, ValidationError, validator
|
||||
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
|
||||
@@ -17,38 +17,70 @@ from app.models.person_models import Person_Base
|
||||
from app.models.user_models import User_Base
|
||||
|
||||
|
||||
# ### BEGIN ### API Event Models ### Event_Base() ###
|
||||
class Event_Base(BaseModel):
|
||||
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
|
||||
log.debug(locals())
|
||||
|
||||
id_random: Optional[str] = Field(
|
||||
**base_fields['event_id_random'],
|
||||
alias = 'event_id_random',
|
||||
)
|
||||
id: Optional[int] = Field(
|
||||
alias = 'event_id'
|
||||
)
|
||||
# --- Standardized Vision IDs (Strings) ---
|
||||
id: Optional[str] = Field(None, **base_fields['event_id_random'])
|
||||
event_id: Optional[str] = Field(None, **base_fields['event_id_random'])
|
||||
account_id: Optional[str] = Field(None, **base_fields['account_id_random'])
|
||||
|
||||
poc_event_person_id: Optional[str] = Field(None, **base_fields['event_person_id_random'])
|
||||
poc_person_id: Optional[str] = Field(None, **base_fields['person_id_random'])
|
||||
user_id: Optional[str] = Field(None, **base_fields['user_id_random'])
|
||||
address_location_id: Optional[str] = Field(None, **base_fields['address_id_random'])
|
||||
|
||||
contact_1_id: Optional[str] = Field(None, **base_fields['contact_id_random'])
|
||||
contact_2_id: Optional[str] = Field(None, **base_fields['contact_id_random'])
|
||||
contact_3_id: Optional[str] = Field(None, **base_fields['contact_id_random'])
|
||||
|
||||
# --- Standardized Legacy / Internal IDs (Excluded) ---
|
||||
id_random: Optional[str] = Field(None, alias='event_id_random', exclude=True)
|
||||
account_id_random: Optional[str] = Field(None, exclude=True)
|
||||
poc_event_person_id_random: Optional[str] = Field(None, exclude=True)
|
||||
poc_person_id_random: Optional[str] = Field(None, exclude=True)
|
||||
user_id_random: Optional[str] = Field(None, exclude=True)
|
||||
address_location_id_random: Optional[str] = Field(None, exclude=True)
|
||||
contact_1_id_random: Optional[str] = Field(None, exclude=True)
|
||||
contact_2_id_random: Optional[str] = Field(None, exclude=True)
|
||||
contact_3_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.
|
||||
"""
|
||||
# 1. Map Random Strings to Clean Names
|
||||
rid = values.get('id_random') or values.get('event_id_random')
|
||||
if rid and isinstance(rid, str):
|
||||
values['id'] = rid
|
||||
values['event_id'] = rid
|
||||
|
||||
if a_rid := values.get('account_id_random'): values['account_id'] = a_rid
|
||||
if pep_rid := values.get('poc_event_person_id_random'): values['poc_event_person_id'] = pep_rid
|
||||
if pp_rid := values.get('poc_person_id_random'): values['poc_person_id'] = pp_rid
|
||||
if u_rid := values.get('user_id_random'): values['user_id'] = u_rid
|
||||
if al_rid := values.get('address_location_id_random'): values['address_location_id'] = al_rid
|
||||
if c1_rid := values.get('contact_1_id_random'): values['contact_1_id'] = c1_rid
|
||||
if c2_rid := values.get('contact_2_id_random'): values['contact_2_id'] = c2_rid
|
||||
if c3_rid := values.get('contact_3_id_random'): values['contact_3_id'] = c3_rid
|
||||
|
||||
# 2. Prevent "Collision Population" or leakage of integers
|
||||
for k in ['id', 'event_id', 'account_id', 'poc_event_person_id', 'poc_person_id', 'user_id', 'address_location_id', 'contact_1_id', 'contact_2_id', 'contact_3_id']:
|
||||
if k in values and not isinstance(values[k], str):
|
||||
del values[k]
|
||||
|
||||
return values
|
||||
|
||||
code: Optional[str] = Field(
|
||||
alias = 'event_code'
|
||||
)
|
||||
|
||||
account_id_random: Optional[str]
|
||||
account_id: Optional[int]
|
||||
|
||||
poc_event_person_id_random: Optional[str]
|
||||
poc_event_person_id: Optional[int]
|
||||
|
||||
poc_person_id_random: Optional[str]
|
||||
poc_person_id: Optional[int]
|
||||
|
||||
external_person_id: Optional[str] # Person ID generated by external system (should be stable and not change)
|
||||
|
||||
user_id_random: Optional[str]
|
||||
user_id: Optional[int]
|
||||
|
||||
lu_event_type_id: Optional[int]
|
||||
lu_event_type_id: Optional[int] = Field(None, exclude=True)
|
||||
#lu_event_type: Optional[str] # Needs to be reviewed
|
||||
|
||||
conference: Optional[bool] # Also in Event_Cfg_Base model
|
||||
@@ -82,8 +114,6 @@ class Event_Base(BaseModel):
|
||||
weekday_friday: Optional[bool]
|
||||
weekday_saturday: Optional[bool]
|
||||
|
||||
address_location_id_random: Optional[str]
|
||||
address_location_id: Optional[int]
|
||||
location_address_json: Optional[Union[Json, None]]
|
||||
location_text: Optional[str]
|
||||
|
||||
@@ -101,12 +131,6 @@ class Event_Base(BaseModel):
|
||||
physical: Optional[bool] # physical in person event
|
||||
virtual: Optional[bool] # virtual remote access event
|
||||
|
||||
contact_1_id_random: Optional[str]
|
||||
contact_1_id: Optional[int]
|
||||
contact_2_id_random: Optional[str]
|
||||
contact_2_id: Optional[int]
|
||||
contact_3_id_random: Optional[str]
|
||||
contact_3_id: Optional[int]
|
||||
contact_li_json: Optional[Union[Json, None]] # list of dicts (custom for client); this is SQL FULLTEXT() indexed
|
||||
|
||||
attend_url: Optional[str]
|
||||
@@ -180,95 +204,6 @@ class Event_Base(BaseModel):
|
||||
|
||||
_processed_at: datetime.datetime = PrivateAttr(default_factory=datetime.datetime.now)
|
||||
|
||||
#@validator('event_id_random', always=True)
|
||||
def event_id_random_copy(cls, v, values, **kwargs):
|
||||
log.setLevel(logging.WARNING)
|
||||
log.debug(locals())
|
||||
|
||||
if values['id_random']:
|
||||
return values['id_random']
|
||||
return None
|
||||
|
||||
@validator('id', always=True)
|
||||
def event_id_lookup(cls, v, values, **kwargs):
|
||||
log.setLevel(logging.WARNING)
|
||||
log.debug(locals())
|
||||
|
||||
if values['id_random']:
|
||||
log.debug(values['id_random'])
|
||||
return redis_lookup_id_random(record_id_random=values['id_random'], table_name='event')
|
||||
return None
|
||||
|
||||
@validator('account_id', always=True)
|
||||
def account_id_lookup(cls, v, values, **kwargs):
|
||||
if isinstance(v, int) and v > 0: return v
|
||||
elif id_random := values.get('account_id_random'):
|
||||
return redis_lookup_id_random(record_id_random=id_random, table_name='account')
|
||||
return None
|
||||
|
||||
@validator('poc_event_person_id', always=True)
|
||||
def poc_event_person_id_lookup(cls, v, values, **kwargs):
|
||||
log.setLevel(logging.WARNING)
|
||||
log.debug(locals())
|
||||
|
||||
if values['poc_event_person_id_random']:
|
||||
return redis_lookup_id_random(record_id_random=values['poc_event_person_id_random'], table_name='event_person')
|
||||
return None
|
||||
|
||||
@validator('poc_person_id', always=True)
|
||||
def poc_person_id_lookup(cls, v, values, **kwargs):
|
||||
log.setLevel(logging.WARNING)
|
||||
log.debug(locals())
|
||||
|
||||
if values['poc_person_id_random']:
|
||||
return redis_lookup_id_random(record_id_random=values['poc_person_id_random'], table_name='person')
|
||||
return None
|
||||
|
||||
@validator('user_id', always=True)
|
||||
def user_id_lookup(cls, v, values, **kwargs):
|
||||
log.setLevel(logging.WARNING)
|
||||
log.debug(locals())
|
||||
|
||||
if values['user_id_random']:
|
||||
return redis_lookup_id_random(record_id_random=values['user_id_random'], table_name='user')
|
||||
return None
|
||||
|
||||
@validator('address_location_id', always=True)
|
||||
def address_location_id_lookup(cls, v, values, **kwargs):
|
||||
log.setLevel(logging.WARNING)
|
||||
log.debug(locals())
|
||||
|
||||
if values['address_location_id_random']:
|
||||
return redis_lookup_id_random(record_id_random=values['address_location_id_random'], table_name='address')
|
||||
return None
|
||||
|
||||
@validator('contact_1_id', always=True)
|
||||
def contact_1_id_lookup(cls, v, values, **kwargs):
|
||||
log.setLevel(logging.WARNING)
|
||||
log.debug(locals())
|
||||
|
||||
if values['contact_1_id_random']:
|
||||
return redis_lookup_id_random(record_id_random=values['contact_1_id_random'], table_name='contact')
|
||||
return None
|
||||
|
||||
@validator('contact_2_id', always=True)
|
||||
def contact_2_id_lookup(cls, v, values, **kwargs):
|
||||
log.setLevel(logging.WARNING)
|
||||
log.debug(locals())
|
||||
|
||||
if values['contact_2_id_random']:
|
||||
return redis_lookup_id_random(record_id_random=values['contact_2_id_random'], table_name='contact')
|
||||
return None
|
||||
|
||||
@validator('contact_3_id', always=True)
|
||||
def contact_3_id_lookup(cls, v, values, **kwargs):
|
||||
log.setLevel(logging.WARNING)
|
||||
log.debug(locals())
|
||||
|
||||
if values['contact_3_id_random']:
|
||||
return redis_lookup_id_random(record_id_random=values['contact_3_id_random'], table_name='contact')
|
||||
return None
|
||||
|
||||
@validator('created_on', always=True)
|
||||
def created_on_utc(cls, v, values, **kwargs):
|
||||
if isinstance(v, datetime.datetime):
|
||||
@@ -300,31 +235,66 @@ class Event_Meeting_Flat_Base(BaseModel):
|
||||
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
|
||||
log.debug(locals())
|
||||
|
||||
id_random: Optional[str] = Field(
|
||||
**base_fields['event_id_random'],
|
||||
alias = 'event_id_random',
|
||||
)
|
||||
id: Optional[int] = Field(
|
||||
alias = 'event_id'
|
||||
)
|
||||
# --- Standardized Vision IDs (Strings) ---
|
||||
id: Optional[str] = Field(None, **base_fields['event_id_random'])
|
||||
event_id: Optional[str] = Field(None, **base_fields['event_id_random'])
|
||||
account_id: Optional[str] = Field(None, **base_fields['account_id_random'])
|
||||
|
||||
poc_event_person_id: Optional[str] = Field(None, **base_fields['event_person_id_random'])
|
||||
poc_person_id: Optional[str] = Field(None, **base_fields['person_id_random'])
|
||||
user_id: Optional[str] = Field(None, **base_fields['user_id_random'])
|
||||
address_location_id: Optional[str] = Field(None, **base_fields['address_id_random'])
|
||||
|
||||
contact_1_id: Optional[str] = Field(None, **base_fields['contact_id_random'])
|
||||
contact_2_id: Optional[str] = Field(None, **base_fields['contact_id_random'])
|
||||
contact_3_id: Optional[str] = Field(None, **base_fields['contact_id_random'])
|
||||
|
||||
# --- Standardized Legacy / Internal IDs (Excluded) ---
|
||||
id_random: Optional[str] = Field(None, alias='event_id_random', exclude=True)
|
||||
account_id_random: Optional[str] = Field(None, exclude=True)
|
||||
poc_event_person_id_random: Optional[str] = Field(None, exclude=True)
|
||||
poc_person_id_random: Optional[str] = Field(None, exclude=True)
|
||||
user_id_random: Optional[str] = Field(None, exclude=True)
|
||||
address_location_id_random: Optional[str] = Field(None, exclude=True)
|
||||
contact_1_id_random: Optional[str] = Field(None, exclude=True)
|
||||
contact_2_id_random: Optional[str] = Field(None, exclude=True)
|
||||
contact_3_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.
|
||||
"""
|
||||
# 1. Map Random Strings to Clean Names
|
||||
rid = values.get('id_random') or values.get('event_id_random')
|
||||
if rid and isinstance(rid, str):
|
||||
values['id'] = rid
|
||||
values['event_id'] = rid
|
||||
|
||||
if a_rid := values.get('account_id_random'): values['account_id'] = a_rid
|
||||
if pep_rid := values.get('poc_event_person_id_random'): values['poc_event_person_id'] = pep_rid
|
||||
if pp_rid := values.get('poc_person_id_random'): values['poc_person_id'] = pp_rid
|
||||
if u_rid := values.get('user_id_random'): values['user_id'] = u_rid
|
||||
if al_rid := values.get('address_location_id_random'): values['address_location_id'] = al_rid
|
||||
if c1_rid := values.get('contact_1_id_random'): values['contact_1_id'] = c1_rid
|
||||
if c2_rid := values.get('contact_2_id_random'): values['contact_2_id'] = c2_rid
|
||||
if c3_rid := values.get('contact_3_id_random'): values['contact_3_id'] = c3_rid
|
||||
|
||||
# 2. Prevent "Collision Population" or leakage of integers
|
||||
for k in ['id', 'event_id', 'account_id', 'poc_event_person_id', 'poc_person_id', 'user_id', 'address_location_id', 'contact_1_id', 'contact_2_id', 'contact_3_id']:
|
||||
if k in values and not isinstance(values[k], str):
|
||||
del values[k]
|
||||
|
||||
return values
|
||||
|
||||
code: Optional[str] = Field(
|
||||
alias = 'event_code'
|
||||
)
|
||||
|
||||
account_id_random: Optional[str]
|
||||
account_id: Optional[int]
|
||||
external_person_id: Optional[str] # Person ID generated by external system (should be stable and not change)
|
||||
|
||||
# poc_event_person_id_random: Optional[str]
|
||||
# poc_event_person_id: Optional[int]
|
||||
|
||||
# poc_person_id_random: Optional[str]
|
||||
# poc_person_id: Optional[int]
|
||||
|
||||
# user_id_random: Optional[str]
|
||||
# user_id: Optional[int]
|
||||
|
||||
lu_event_type_id: Optional[int]
|
||||
lu_event_type_id: Optional[int] = Field(None, exclude=True)
|
||||
#lu_event_type: Optional[str] # Needs to be reviewed
|
||||
|
||||
conference: Optional[bool] # Also in Event_Cfg_Base model
|
||||
@@ -358,8 +328,6 @@ class Event_Meeting_Flat_Base(BaseModel):
|
||||
weekday_friday: Optional[bool]
|
||||
weekday_saturday: Optional[bool]
|
||||
|
||||
address_location_id_random: Optional[str]
|
||||
address_location_id: Optional[int]
|
||||
location_address_json: Optional[Union[Json, None]]
|
||||
location_text: Optional[str]
|
||||
|
||||
@@ -377,14 +345,6 @@ class Event_Meeting_Flat_Base(BaseModel):
|
||||
physical: Optional[bool] # physical in person event
|
||||
virtual: Optional[bool] # virtual remote access event
|
||||
|
||||
external_person_id: Optional[str] # Person ID generated by external system (should be stable and not change)
|
||||
|
||||
contact_1_id_random: Optional[str]
|
||||
contact_1_id: Optional[int]
|
||||
contact_2_id_random: Optional[str]
|
||||
contact_2_id: Optional[int]
|
||||
contact_3_id_random: Optional[str]
|
||||
contact_3_id: Optional[int]
|
||||
contact_li_json: Optional[Union[Json, None]] # list of dicts (custom for client); this is SQL FULLTEXT() indexed
|
||||
|
||||
attend_url: Optional[str]
|
||||
@@ -431,9 +391,11 @@ class Event_Meeting_Flat_Base(BaseModel):
|
||||
created_on: Optional[datetime.datetime] = None
|
||||
updated_on: Optional[datetime.datetime] = None
|
||||
|
||||
# Including convenience data
|
||||
address_id_random: Optional[str]
|
||||
address_id: Optional[int]
|
||||
# --- IDAA Recovery Meetings: Convenience Data (Flat) ---
|
||||
# These fields are primarily for the flat "Meeting" view used by the IDAA mobile/web apps.
|
||||
# Note: We prioritize string IDs (id_random) for all external API consumers.
|
||||
|
||||
address_id_random: Optional[str] = Field(None, **base_fields['address_id_random'])
|
||||
address_name: Optional[str]
|
||||
address_line_1: Optional[str]
|
||||
address_line_2: Optional[str]
|
||||
@@ -445,8 +407,6 @@ class Event_Meeting_Flat_Base(BaseModel):
|
||||
address_country_alpha_2_code: Optional[str]
|
||||
address_country_name: Optional[str]
|
||||
|
||||
contact_1_id_random: Optional[str]
|
||||
contact_1_id: Optional[int]
|
||||
contact_1_name: Optional[str] # Avoid using or use as something different?
|
||||
contact_1_full_name: Optional[str] # Yes... it is the same as "name"
|
||||
contact_1_email: Optional[str]
|
||||
@@ -458,8 +418,6 @@ class Event_Meeting_Flat_Base(BaseModel):
|
||||
contact_1_phone_other: Optional[str]
|
||||
contact_1_other_text: Optional[str]
|
||||
|
||||
contact_2_id_random: Optional[str]
|
||||
contact_2_id: Optional[int]
|
||||
contact_2_name: Optional[str] # Avoid using or use as something different?
|
||||
contact_2_full_name: Optional[str] # Yes... it is the same as "name"
|
||||
contact_2_email: Optional[str]
|
||||
@@ -473,70 +431,6 @@ class Event_Meeting_Flat_Base(BaseModel):
|
||||
|
||||
_processed_at: datetime.datetime = PrivateAttr(default_factory=datetime.datetime.now)
|
||||
|
||||
#@validator('event_id_random', always=True)
|
||||
def event_id_random_copy(cls, v, values, **kwargs):
|
||||
log.setLevel(logging.WARNING)
|
||||
log.debug(locals())
|
||||
|
||||
if values['id_random']:
|
||||
return values['id_random']
|
||||
return None
|
||||
|
||||
@validator('id', always=True)
|
||||
def event_id_lookup(cls, v, values, **kwargs):
|
||||
log.setLevel(logging.WARNING)
|
||||
log.debug(locals())
|
||||
|
||||
if values['id_random']:
|
||||
log.debug(values['id_random'])
|
||||
return redis_lookup_id_random(record_id_random=values['id_random'], table_name='event')
|
||||
return None
|
||||
|
||||
# @validator('account_id', always=True)
|
||||
# def account_id_lookup(cls, v, values, **kwargs):
|
||||
# log.setLevel(logging.WARNING)
|
||||
# log.debug(locals())
|
||||
|
||||
# if values['account_id_random']:
|
||||
# return redis_lookup_id_random(record_id_random=values['account_id_random'], table_name='account')
|
||||
# return None
|
||||
|
||||
# @validator('address_id', always=True)
|
||||
# def address_id_lookup(cls, v, values, **kwargs):
|
||||
# log.setLevel(logging.WARNING)
|
||||
# log.debug(locals())
|
||||
|
||||
# if values['address_id_random']:
|
||||
# return redis_lookup_id_random(record_id_random=values['address_id_random'], table_name='address')
|
||||
# return None
|
||||
|
||||
# @validator('contact_1_id', always=True)
|
||||
# def contact_1_id_lookup(cls, v, values, **kwargs):
|
||||
# log.setLevel(logging.WARNING)
|
||||
# log.debug(locals())
|
||||
|
||||
# if values['contact_1_id_random']:
|
||||
# return redis_lookup_id_random(record_id_random=values['contact_1_id_random'], table_name='contact')
|
||||
# return None
|
||||
|
||||
# @validator('contact_2_id', always=True)
|
||||
# def contact_2_id_lookup(cls, v, values, **kwargs):
|
||||
# log.setLevel(logging.WARNING)
|
||||
# log.debug(locals())
|
||||
|
||||
# if values['contact_2_id_random']:
|
||||
# return redis_lookup_id_random(record_id_random=values['contact_2_id_random'], table_name='contact')
|
||||
# return None
|
||||
|
||||
# @validator('contact_3_id', always=True)
|
||||
# def contact_3_id_lookup(cls, v, values, **kwargs):
|
||||
# log.setLevel(logging.WARNING)
|
||||
# log.debug(locals())
|
||||
|
||||
# if values['contact_3_id_random']:
|
||||
# return redis_lookup_id_random(record_id_random=values['contact_3_id_random'], table_name='contact')
|
||||
# return None
|
||||
|
||||
@validator('created_on', always=True)
|
||||
def created_on_utc(cls, v, values, **kwargs):
|
||||
if isinstance(v, datetime.datetime):
|
||||
|
||||
@@ -169,8 +169,9 @@ The following endpoints are maintained for backward compatibility but should be
|
||||
|
||||
V3 uses a **String-Only ID Vision**. The frontend NEVER handles or stores database integers.
|
||||
|
||||
1. **Automatic Mapping:** Internal `id_random` fields are mapped to `id` and `account_id` in JSON responses.
|
||||
1. **Automatic Mapping:** Internal `id_random` fields are mapped to clean names (e.g., `id`, `event_id`, `account_id`) in JSON responses.
|
||||
2. **Intelligent Resolution:** You can send random string IDs back to the API in any `*_id` field; the API resolves them to integers before database insertion.
|
||||
3. **Suffix Compatibility:** The `_random` suffix (e.g., `event_id_random`) is maintained for backward compatibility with V2 clients. Standard V3 frontend integration should prioritize fields without the suffix.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user