ID Vision Phase 2: Standardize Page, Post, Person, Organization, and Hosted File objects

This commit is contained in:
Scott Idem
2026-01-19 18:04:17 -05:00
parent ab8afb72d2
commit 817bb80f87
11 changed files with 324 additions and 457 deletions

View File

@@ -205,14 +205,35 @@ def sql_search_qry_part(
def process_filter(f) -> tuple[str, dict]: def process_filter(f) -> tuple[str, dict]:
# --- ID VISION MAPPING --- # --- ID VISION MAPPING ---
# If the frontend uses clean names (id, account_id), # If the frontend uses clean names (id, account_id),
# map them to the database columns (id_random, account_id_random). # map them to the database columns (id_random, account_id_random)
# ONLY if those columns actually exist in this table/view.
target_field = f.field target_field = f.field
vision_fields = ['id', 'account_id', 'site_id', 'person_id', 'user_id', 'journal_id', 'journal_entry_id'] vision_fields = [
'id', 'account_id', 'site_id', 'person_id', 'user_id',
'journal_id', 'journal_entry_id', 'page_id', 'post_id',
'post_comment_id', 'organization_id', 'address_id', 'hosted_file_id'
]
if target_field in vision_fields: if target_field in vision_fields:
if target_field == 'id': target_field = 'id_random' candidate_field = 'id_random' if target_field == 'id' else f"{target_field}_random"
else: target_field = f"{target_field}_random"
print(f"Search Trace: Mapping filter field '{f.field}' -> '{target_field}'", flush=True) # Schema Check: Verify if the random version exists in the current table/view
use_random = False
if table_name:
try:
lib_sql_core.db.execute(text(f"SELECT `{candidate_field}` FROM `{table_name}` LIMIT 0"))
use_random = True
except Exception:
pass
if use_random:
target_field = candidate_field
print(f"Search Trace: Mapping filter field '{f.field}' -> '{target_field}'", flush=True)
else:
# If random doesn't exist, we must stick to the integer column
# but we'll need to resolve the string value to an integer elsewhere
# or rely on the user providing an integer for now.
pass
if searchable_fields is not None and target_field not in searchable_fields: if searchable_fields is not None and target_field not in searchable_fields:
# Fallback check for original field just in case # Fallback check for original field just in case

View File

@@ -1,7 +1,7 @@
import datetime, pytz import datetime, pytz
from typing import Dict, List, Optional, Set, Union from typing import Dict, List, Optional, Set, Union
from pydantic import BaseModel, EmailStr, Field, Json, PrivateAttr, ValidationError, validator from pydantic import BaseModel, EmailStr, Field, Json, PrivateAttr, ValidationError, validator, root_validator
from app.db_sql import get_id_random, redis_lookup_id_random from app.db_sql import get_id_random, redis_lookup_id_random
from app.lib_general import log, logging from app.lib_general import log, logging
@@ -14,23 +14,15 @@ class Address_Base(BaseModel):
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals()) log.debug(locals())
id_random: Optional[str] = Field( # --- Standardized Vision IDs (Strings) ---
**base_fields['address_id_random'], id: Optional[str] = Field(None, **base_fields['address_id_random'])
alias = 'address_id_random', address_id: Optional[str] = Field(None, **base_fields['address_id_random'])
# default_factory = lambda:secrets.token_urlsafe(default_num_bytes), account_id: Optional[str] = Field(None, **base_fields['account_id_random'])
) contact_id: Optional[str] = Field(None, **base_fields['contact_id_random'])
id: Optional[int] = Field(
alias = 'address_id'
)
account_id_random: Optional[str]
account_id: Optional[int]
for_type: Optional[str] for_type: Optional[str]
for_id_random: Optional[str] for_id_random: Optional[str]
for_id: Optional[int] for_id: Optional[int] = Field(None, exclude=True)
contact_id_random: Optional[str]
contact_id: Optional[int]
#organization: Optional[Organization_Base] = Organization_Base() #organization: Optional[Organization_Base] = Organization_Base()
@@ -72,63 +64,31 @@ class Address_Base(BaseModel):
_processed_at: datetime.datetime = PrivateAttr(default_factory=datetime.datetime.now) _processed_at: datetime.datetime = PrivateAttr(default_factory=datetime.datetime.now)
#@validator('address_id_random', always=True) @root_validator(pre=True)
def address_id_random_copy(cls, v, values, **kwargs): def map_v3_ids(cls, values):
log.setLevel(logging.WARNING) """
log.debug(locals()) Vision Transformer:
Map DB keys to clean API keys and strip internal integers.
if values['id_random']: """
return values['id_random'] # 1. Map Random Strings to Clean Names
return None if rid := values.get('id_random') or values.get('address_id_random'):
values['id'] = rid
@validator('id', always=True) values['address_id'] = rid
def address_id_lookup(cls, v, values, **kwargs):
log.setLevel(logging.WARNING) if a_rid := values.get('account_id_random'):
log.debug(locals()) values['account_id'] = a_rid
if c_rid := values.get('contact_id_random'):
if isinstance(v, int) and v > 0: return v values['contact_id'] = c_rid
elif id_random := values.get('id_random'):
return redis_lookup_id_random(record_id_random=id_random, table_name='address') # 2. Prevent "Collision Population"
return None for k in ['id', 'account_id', 'contact_id']:
if k in values and not isinstance(values[k], str):
@validator('account_id', always=True) del values[k]
def account_id_lookup(cls, v, values, **kwargs):
log.setLevel(logging.WARNING) return values
log.debug(locals())
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('account_id_random', always=True)
def account_id_random_lookup(cls, v, values, **kwargs):
if isinstance(v, str) and len(v) >= 11: return v
elif account_id := values.get('account_id'):
return get_id_random(record_id=account_id, table_name='account')
return None
@validator('contact_id', always=True)
def contact_id_lookup(cls, v, values, **kwargs):
log.setLevel(logging.WARNING)
log.debug(locals())
if isinstance(v, int) and v > 0: return v
elif id_random := values.get('contact_id_random'):
return redis_lookup_id_random(record_id_random=id_random, table_name='contact')
return None
@validator('for_id', always=True)
def for_id_lookup(cls, v, values, **kwargs):
log.setLevel(logging.WARNING)
log.debug(locals())
if values.get('for_id_random') and values.get('for_type'):
return redis_lookup_id_random(record_id_random=values['for_id_random'], table_name=values['for_type'])
return None
class Config: class Config:
underscore_attrs_are_private = True underscore_attrs_are_private = True
allow_population_by_field_name = True allow_population_by_field_name = False
fields = base_fields fields = base_fields
# ### END ### API Address Models ### Address_Base() ### # ### END ### API Address Models ### Address_Base() ###

View File

@@ -1,7 +1,7 @@
import datetime, pytz import datetime, pytz
from typing import Dict, List, Optional, Set, Union from typing import Dict, List, Optional, Set, Union
from pydantic import BaseModel, EmailStr, Field, Json, PrivateAttr, ValidationError, validator from pydantic import BaseModel, EmailStr, Field, Json, PrivateAttr, ValidationError, validator, root_validator
from app.db_sql import redis_lookup_id_random from app.db_sql import redis_lookup_id_random
from app.lib_general import log, logging from app.lib_general import log, logging
@@ -14,17 +14,10 @@ class Hosted_File_Base(BaseModel):
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals()) log.debug(locals())
id_random: Optional[str] = Field( # --- Standardized Vision IDs (Strings) ---
**base_fields['hosted_file_id_random'], id: Optional[str] = Field(None, **base_fields['hosted_file_id_random'])
alias = 'hosted_file_id_random', hosted_file_id: Optional[str] = Field(None, **base_fields['hosted_file_id_random'])
# default_factory = lambda:secrets.token_urlsafe(default_num_bytes), account_id: Optional[str] = Field(None, **base_fields['account_id_random'])
)
id: Optional[int] = Field(
alias = 'hosted_file_id'
)
account_id_random: Optional[str]
account_id: Optional[int]
hash_sha256: Optional[str] hash_sha256: Optional[str]
title: Optional[str] title: Optional[str]
@@ -39,21 +32,11 @@ class Hosted_File_Base(BaseModel):
mimetype: Optional[str] mimetype: Optional[str]
size: Optional[int] # In bytes size: Optional[int] # In bytes
# cloud_storage: Optional[str]
# owner_user_id: Optional[int]
# group_user_id: Optional[str]
# package_name: Optional[str]
already_exists: Optional[str] # This will probably only be populated on upload results already_exists: Optional[str] # This will probably only be populated on upload results
copy_timer: Optional[str] # This will probably only be populated on upload results copy_timer: Optional[str] # This will probably only be populated on upload results
saved: Optional[str] # This will probably only be populated on upload results saved: Optional[str] # This will probably only be populated on upload results
# metadata: Optional[str]
enable: Optional[bool] enable: Optional[bool]
# enable_from: Optional[datetime.datetime] = None
# enable_to: Optional[datetime.datetime] = None
hide: Optional[bool] hide: Optional[bool]
priority: Optional[bool] priority: Optional[bool]
@@ -78,28 +61,29 @@ class Hosted_File_Base(BaseModel):
_processed_at: datetime.datetime = PrivateAttr(default_factory=datetime.datetime.now) _processed_at: datetime.datetime = PrivateAttr(default_factory=datetime.datetime.now)
#@validator('hosted_file_id_random', always=True) @root_validator(pre=True)
def hosted_file_id_random_copy(cls, v, values, **kwargs): def map_v3_ids(cls, values):
if values['id_random']: """
return values['id_random'] Vision Transformer:
return None Map DB keys to clean API keys and strip internal integers.
"""
@validator('id', always=True) # 1. Map Random Strings to Clean Names
def hosted_file_id_lookup(cls, v, values, **kwargs): if rid := values.get('id_random') or values.get('hosted_file_id_random'):
if isinstance(v, int) and v > 0: return v values['id'] = rid
elif id_random := values.get('id_random'): values['hosted_file_id'] = rid
return redis_lookup_id_random(record_id_random=id_random, table_name='hosted_file')
return None if a_rid := values.get('account_id_random'):
values['account_id'] = a_rid
@validator('account_id', always=True)
def account_id_lookup(cls, v, values, **kwargs): # 2. Prevent "Collision Population"
if isinstance(v, int) and v > 0: return v for k in ['id', 'account_id']:
elif id_random := values.get('account_id_random'): if k in values and not isinstance(values[k], str):
return redis_lookup_id_random(record_id_random=id_random, table_name='account') del values[k]
return None
return values
class Config: class Config:
underscore_attrs_are_private = True underscore_attrs_are_private = True
allow_population_by_field_name = True allow_population_by_field_name = False
fields = base_fields fields = base_fields
# ### END ### API Hosted File Models ### Hosted_File_Base() ### # ### END ### API Hosted File Models ### Hosted_File_Base() ###

View File

@@ -1,7 +1,7 @@
import datetime, pytz import datetime, pytz
from typing import Dict, List, Optional, Set, Union from typing import Dict, List, Optional, Set, Union
from pydantic import BaseModel, EmailStr, Field, Json, PrivateAttr, ValidationError, validator from pydantic import BaseModel, EmailStr, Field, Json, PrivateAttr, ValidationError, validator, root_validator
from app.db_sql import redis_lookup_id_random from app.db_sql import redis_lookup_id_random
from app.lib_general import log, logging from app.lib_general import log, logging
@@ -18,21 +18,13 @@ class Organization_Base(BaseModel):
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals()) log.debug(locals())
id_random: Optional[str] = Field( # --- Standardized Vision IDs (Strings) ---
**base_fields['organization_id_random'], id: Optional[str] = Field(None, **base_fields['organization_id_random'])
alias = 'organization_id_random', organization_id: Optional[str] = Field(None, **base_fields['organization_id_random'])
) account_id: Optional[str] = Field(None, **base_fields['account_id_random'])
id: Optional[int] = Field( contact_id: Optional[str] = Field(None, **base_fields['contact_id_random'])
alias = 'organization_id' person_id: Optional[str] = Field(None, **base_fields['person_id_random'])
) user_id: Optional[str] = Field(None, **base_fields['user_id_random'])
account_id_random: Optional[str]
account_id: Optional[int]
contact_id_random: Optional[str]
contact_id: Optional[int]
person_id_random: Optional[str]
person_id: Optional[int]
user_id_random: Optional[str]
user_id: Optional[int]
name: Optional[str] name: Optional[str]
tagline: Optional[str] tagline: Optional[str]
@@ -67,71 +59,35 @@ class Organization_Base(BaseModel):
_processed_at: datetime.datetime = PrivateAttr(default_factory=datetime.datetime.now) _processed_at: datetime.datetime = PrivateAttr(default_factory=datetime.datetime.now)
#@validator('organization_id_random', always=True) @root_validator(pre=True)
def organization_id_random_copy(cls, v, values, **kwargs): def map_v3_ids(cls, values):
log.setLevel(logging.WARNING) """
log.debug(locals()) Vision Transformer:
Map DB keys to clean API keys and strip internal integers.
if values['id_random']: """
return values['id_random'] # 1. Map Random Strings to Clean Names
return None if rid := values.get('id_random') or values.get('organization_id_random'):
values['id'] = rid
@validator('id', always=True) values['organization_id'] = rid
def organization_id_lookup(cls, v, values, **kwargs):
log.setLevel(logging.WARNING) if a_rid := values.get('account_id_random'):
log.debug(locals()) values['account_id'] = a_rid
if c_rid := values.get('contact_id_random'):
if values['id_random']: values['contact_id'] = c_rid
log.debug(values['id_random']) if p_rid := values.get('person_id_random'):
return redis_lookup_id_random(record_id_random=values['id_random'], table_name='organization') values['person_id'] = p_rid
return None if u_rid := values.get('user_id_random'):
values['user_id'] = u_rid
@validator('account_id', always=True)
def account_id_lookup(cls, v, values, **kwargs): # 2. Prevent "Collision Population"
log.setLevel(logging.WARNING) for k in ['id', 'account_id', 'contact_id', 'person_id', 'user_id']:
log.debug(locals()) if k in values and not isinstance(values[k], str):
del values[k]
if isinstance(v, int) and v > 0: return v
elif id_random := values.get('account_id_random'): return values
return redis_lookup_id_random(record_id_random=id_random, table_name='account')
return None
@validator('account_id_random', always=True)
def account_id_random_lookup(cls, v, values, **kwargs):
if isinstance(v, str) and len(v) >= 11: return v
elif account_id := values.get('account_id'):
return get_id_random(record_id=account_id, table_name='account')
return None
@validator('contact_id', always=True)
def contact_id_lookup(cls, v, values, **kwargs):
log.setLevel(logging.WARNING)
log.debug(locals())
if values['contact_id_random']:
return redis_lookup_id_random(record_id_random=values['contact_id_random'], table_name='contact')
return None
@validator('person_id', always=True)
def person_id_lookup(cls, v, values, **kwargs):
log.setLevel(logging.WARNING)
log.debug(locals())
if values['person_id_random']:
return redis_lookup_id_random(record_id_random=values['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
class Config: class Config:
underscore_attrs_are_private = True underscore_attrs_are_private = True
allow_population_by_field_name = True allow_population_by_field_name = False
fields = base_fields fields = base_fields
# ### END ### API Organization Models ### Organization_Base() ### # ### END ### API Organization Models ### Organization_Base() ###

View File

@@ -1,10 +1,10 @@
import datetime, pytz import datetime, pytz
from typing import Dict, List, Optional, Set, Union from typing import Dict, List, Optional, Set, Union
from pydantic import BaseModel, EmailStr, Field, Json, PrivateAttr, ValidationError, validator from pydantic import BaseModel, EmailStr, Field, Json, PrivateAttr, ValidationError, validator, root_validator
from app.db_sql import redis_lookup_id_random from app.db_sql import redis_lookup_id_random
from app.lib_general import * from app.lib_general import log, logging
from app.models.common_field_schema import base_fields, default_num_bytes from app.models.common_field_schema import base_fields, default_num_bytes
@@ -14,29 +14,50 @@ class Page_Base(BaseModel):
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals()) log.debug(locals())
id_random: Optional[str] = Field( # --- Standardized Vision IDs (Strings) ---
**base_fields['page_id_random'], id: Optional[str] = Field(None, **base_fields['page_id_random'])
alias = 'page_id_random', page_id: Optional[str] = Field(None, **base_fields['page_id_random'])
) account_id: Optional[str] = Field(None, **base_fields['account_id_random'])
id: Optional[int] = Field( site_id: Optional[str] = Field(None, **base_fields['site_id_random'])
#alias = 'page_id'
)
account_id_random: Optional[str]
account_id: Optional[int]
alias: Optional[str] # page_id_random: Optional[str] = Field(
# **base_fields['page_id_random'],
# alias = 'page_id_random',
# )
# id: Optional[int] = Field(
# alias = 'page_id'
# )
code: Optional[str]
name: Optional[str] name: Optional[str]
title: Optional[str]
description: Optional[str]
summary: Optional[str]
outline: Optional[str]
head_html: Optional[str]
body_html: Optional[str]
footer_html: Optional[str]
content_html: Optional[str]
content_json: Optional[Union[Json, None]]
# keywords: Optional[str]
tags: Optional[str]
start_datetime: Optional[datetime.datetime]
end_datetime: Optional[datetime.datetime]
timezone: Optional[str] # = 'UTC' # Default to UTC
cfg_json: Optional[Union[Json, None]]
data_json: Optional[Union[Json, None]] # Used to store additional data for the page
meta_json: Optional[Union[Json, None]] # Used to store additional data for about the page
enable: Optional[bool] enable: Optional[bool]
enable_from: Optional[datetime.datetime] = None hide: Optional[bool]
enable_to: Optional[datetime.datetime] = None priority: Optional[bool]
sort: Optional[int]
title: Optional[str] group: Optional[str]
body: Optional[str]
style_href: Optional[str]
script_src: Optional[str]
authentication_required: Optional[bool]
notes: Optional[str] notes: Optional[str]
created_on: Optional[datetime.datetime] = None created_on: Optional[datetime.datetime] = None
@@ -44,27 +65,31 @@ class Page_Base(BaseModel):
_processed_at: datetime.datetime = PrivateAttr(default_factory=datetime.datetime.now) _processed_at: datetime.datetime = PrivateAttr(default_factory=datetime.datetime.now)
#@validator('page_id_random', always=True) @root_validator(pre=True)
def page_id_random_copy(cls, v, values, **kwargs): def map_v3_ids(cls, values):
log.setLevel(logging.WARNING) """
log.debug(locals()) Vision Transformer:
Map DB keys to clean API keys and strip internal integers.
if values['id_random']: """
return values['id_random'] # 1. Map Random Strings to Clean Names
return None if rid := values.get('id_random') or values.get('page_id_random'):
values['id'] = rid
@validator('id', always=True) values['page_id'] = rid
def page_id_lookup(cls, v, values, **kwargs):
log.setLevel(logging.WARNING) if a_rid := values.get('account_id_random'):
log.debug(locals()) values['account_id'] = a_rid
if s_rid := values.get('site_id_random'):
if values['id_random']: values['site_id'] = s_rid
log.debug(values['id_random'])
return redis_lookup_id_random(record_id_random=values['id_random'], table_name='page') # 2. Prevent "Collision Population"
return None for k in ['id', 'account_id', 'site_id']:
if k in values and not isinstance(values[k], str):
del values[k]
return values
class Config: class Config:
underscore_attrs_are_private = True underscore_attrs_are_private = True
allow_population_by_field_name = True allow_population_by_field_name = False
fields = base_fields fields = base_fields
# ### END ### API Page Models ### Page_Base() ### # ### END ### API Page Models ### Page_Base() ###

View File

@@ -1,7 +1,7 @@
import datetime, pytz import datetime, pytz
from typing import Dict, List, Optional, Set, Union from typing import Dict, List, Optional, Set, Union
from pydantic import BaseModel, EmailStr, Field, Json, PrivateAttr, ValidationError, validator from pydantic import BaseModel, EmailStr, Field, Json, PrivateAttr, ValidationError, validator, root_validator
from app.db_sql import redis_lookup_id_random from app.db_sql import redis_lookup_id_random
from app.lib_general import log, logging from app.lib_general import log, logging
@@ -23,31 +23,17 @@ class Person_Base(BaseModel):
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals()) log.debug(locals())
id_random: Optional[str] = Field( # --- Standardized Vision IDs (Strings) ---
**base_fields['person_id_random'], id: Optional[str] = Field(None, **base_fields['person_id_random'])
alias = 'person_id_random', person_id: Optional[str] = Field(None, **base_fields['person_id_random'])
# default_factory = lambda:secrets.token_urlsafe(default_num_bytes), account_id: Optional[str] = Field(None, **base_fields['account_id_random'])
) contact_id: Optional[str] = Field(None, **base_fields['contact_id_random'])
id: Optional[int] = Field( organization_id: Optional[str] = Field(None, **base_fields['organization_id_random'])
alias = 'person_id' user_id: Optional[str] = Field(None, **base_fields['user_id_random'])
) membership_person_id: Optional[str] = Field(None, **base_fields['membership_person_id_random'])
account_id_random: Optional[str]
account_id: Optional[int]
contact_id_random: Optional[str] # pronouns: Optional[str] # MISSING in physical table
contact_id: Optional[int] # informal_name: Optional[str] # MISSING in physical table
organization_id_random: Optional[str]
organization_id: Optional[int]
user_id_random: Optional[str]
user_id: Optional[int]
membership_person_id_random: Optional[str] # Linked from membership_person using the v_person view
membership_person_id: Optional[int] # Linked from membership_person using the v_person view
pronouns: Optional[str] # Preferred pronouns
informal_name: Optional[str] # Informal or nick name they commonly go by
title_names: Optional[str] # Title for generation, official position, or professional or academic qualification, other honorific, or other name prefix title_names: Optional[str] # Title for generation, official position, or professional or academic qualification, other honorific, or other name prefix
prefix: Optional[str] # NOTE: Phasing out! Use *title_names* instead. prefix: Optional[str] # NOTE: Phasing out! Use *title_names* instead.
@@ -59,11 +45,7 @@ class Person_Base(BaseModel):
professional_title: Optional[str] # Professional title professional_title: Optional[str] # Professional title
# display_name: Optional[str] # NOTE: This will be changed to full_name_override to match event_badge, event_presenter, and event_profile # preferred_display_name: Optional[str] # MISSING in physical table
# informal_display_name: Optional[str] # Custom what they want for informal public display
# professional_display_name: Optional[str] # Custom what they want for professional public display. This should include professional title.
preferred_display_name: Optional[str] # Which name variant to display? '', 'informal', 'professional', etc
# BEGIN # Auto created name variations # BEGIN # Auto created name variations
first_last_name: Optional[str] # With SQL view? first_last_name: Optional[str] # With SQL view?
@@ -161,64 +143,34 @@ class Person_Base(BaseModel):
_processed_at: datetime.datetime = PrivateAttr(default_factory=datetime.datetime.now) _processed_at: datetime.datetime = PrivateAttr(default_factory=datetime.datetime.now)
#@validator('person_id_random', always=True) @root_validator(pre=True)
def person_id_random_copy(cls, v, values, **kwargs): def map_v3_ids(cls, values):
log.setLevel(logging.WARNING) """
log.debug(locals()) Vision Transformer:
Map DB keys to clean API keys and strip internal integers.
if values['id_random']: """
return values['id_random'] # 1. Map Random Strings to Clean Names
return None if rid := values.get('id_random') or values.get('person_id_random'):
values['id'] = rid
@validator('id', always=True) values['person_id'] = rid
def person_id_lookup(cls, v, values, **kwargs):
log.setLevel(logging.WARNING) if a_rid := values.get('account_id_random'):
log.debug(locals()) values['account_id'] = a_rid
if c_rid := values.get('contact_id_random'):
if isinstance(v, int) and v > 0: return v values['contact_id'] = c_rid
elif id_random := values.get('id_random'): if o_rid := values.get('organization_id_random'):
return redis_lookup_id_random(record_id_random=id_random, table_name='person') values['organization_id'] = o_rid
return None if u_rid := values.get('user_id_random'):
values['user_id'] = u_rid
@validator('account_id', always=True) if mp_rid := values.get('membership_person_id_random'):
def account_id_lookup(cls, v, values, **kwargs): values['membership_person_id'] = mp_rid
log.setLevel(logging.WARNING)
log.debug(locals()) # 2. Prevent "Collision Population"
for k in ['id', 'account_id', 'contact_id', 'organization_id', 'user_id', 'membership_person_id']:
if isinstance(v, int) and v > 0: return v if k in values and not isinstance(values[k], str):
elif id_random := values.get('account_id_random'): del values[k]
return redis_lookup_id_random(record_id_random=id_random, table_name='account')
return None return values
@validator('contact_id', always=True)
def contact_id_lookup(cls, v, values, **kwargs):
log.setLevel(logging.WARNING)
log.debug(locals())
if isinstance(v, int) and v > 0: return v
elif id_random := values.get('contact_id_random'):
return redis_lookup_id_random(record_id_random=id_random, table_name='contact')
return None
@validator('organization_id', always=True)
def organization_id_lookup(cls, v, values, **kwargs):
log.setLevel(logging.WARNING)
log.debug(locals())
if isinstance(v, int) and v > 0: return v
elif id_random := values.get('organization_id_random'):
return redis_lookup_id_random(record_id_random=id_random, table_name='organization')
return None
@validator('user_id', always=True)
def user_id_lookup(cls, v, values, **kwargs):
log.setLevel(logging.WARNING)
log.debug(locals())
if isinstance(v, int) and v > 0: return v
elif id_random := values.get('id_random'):
return redis_lookup_id_random(record_id_random=id_random, table_name='user')
return None
@validator('given_name', always=True) @validator('given_name', always=True)
def given_name_validator(cls, v): def given_name_validator(cls, v):
@@ -234,6 +186,6 @@ class Person_Base(BaseModel):
class Config: class Config:
underscore_attrs_are_private = True underscore_attrs_are_private = True
allow_population_by_field_name = True allow_population_by_field_name = False
fields = base_fields fields = base_fields
# ### END ### API Person Models ### Person_Base() ### # ### END ### API Person Models ### Person_Base() ###

View File

@@ -1,7 +1,7 @@
import datetime, pytz import datetime, pytz
from typing import Dict, List, Optional, Set, Union from typing import Dict, List, Optional, Set, Union
from pydantic import BaseModel, EmailStr, Field, Json, PrivateAttr, ValidationError, validator from pydantic import BaseModel, EmailStr, Field, Json, PrivateAttr, ValidationError, validator, root_validator
from app.db_sql import redis_lookup_id_random from app.db_sql import redis_lookup_id_random
from app.lib_general import log, logging, secure_hash_string from app.lib_general import log, logging, secure_hash_string
@@ -17,25 +17,15 @@ class Post_Comment_Base(BaseModel):
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals()) log.debug(locals())
id_random: Optional[str] = Field( # --- Standardized Vision IDs (Strings) ---
**base_fields['post_comment_id_random'], id: Optional[str] = Field(None, **base_fields['post_comment_id_random'])
alias = 'post_comment_id_random', post_comment_id: Optional[str] = Field(None, **base_fields['post_comment_id_random'])
) post_id: Optional[str] = Field(None, **base_fields['post_id_random'])
id: Optional[int] = Field( person_id: Optional[str] = Field(None, **base_fields['person_id_random'])
#alias = 'post_comment_id' user_id: Optional[str] = Field(None, **base_fields['user_id_random'])
)
post_id_random: Optional[str]
post_id: Optional[int]
person_id_random: Optional[str]
person_id: Optional[int]
external_person_id: Optional[str] # Person 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)
user_id_random: Optional[str]
user_id: Optional[int]
title: Optional[str] title: Optional[str]
content: Optional[str] content: Optional[str]
@@ -65,45 +55,33 @@ class Post_Comment_Base(BaseModel):
_processed_at: datetime.datetime = PrivateAttr(default_factory=datetime.datetime.now) _processed_at: datetime.datetime = PrivateAttr(default_factory=datetime.datetime.now)
#@validator('post_comment_id_random', always=True) @root_validator(pre=True)
# def post_comment_id_random_copy(cls, v, values, **kwargs): def map_v3_ids(cls, values):
# log.setLevel(logging.WARNING) """
# log.debug(locals()) Vision Transformer:
Map DB keys to clean API keys and strip internal integers.
# if values['id_random']: """
# return values['id_random'] # 1. Map Random Strings to Clean Names
# return None if rid := values.get('id_random') or values.get('post_comment_id_random'):
values['id'] = rid
@validator('id', always=True) values['post_comment_id'] = rid
def post_comment_id_lookup(cls, v, values, **kwargs):
if isinstance(v, int) and v > 0: return v if p_rid := values.get('post_id_random'):
elif id_random := values.get('id_random'): values['post_id'] = p_rid
return redis_lookup_id_random(record_id_random=id_random, table_name='post_comment') if per_rid := values.get('person_id_random'):
return None values['person_id'] = per_rid
if u_rid := values.get('user_id_random'):
@validator('post_id', always=True) values['user_id'] = u_rid
def post_id_lookup(cls, v, values, **kwargs):
if isinstance(v, int) and v > 0: return v # 2. Prevent "Collision Population"
elif id_random := values.get('post_id_random'): for k in ['id', 'post_id', 'person_id', 'user_id']:
return redis_lookup_id_random(record_id_random=id_random, table_name='post') if k in values and not isinstance(values[k], str):
return None del values[k]
@validator('person_id', always=True) return values
def person_id_lookup(cls, v, values, **kwargs):
if isinstance(v, int) and v > 0: return v
elif id_random := values.get('person_id_random'):
return redis_lookup_id_random(record_id_random=id_random, table_name='person')
return None
@validator('user_id', always=True)
def user_id_lookup(cls, v, values, **kwargs):
if isinstance(v, int) and v > 0: return v
elif id_random := values.get('user_id_random'):
return redis_lookup_id_random(record_id_random=id_random, table_name='user')
return None
class Config: class Config:
underscore_attrs_are_private = True underscore_attrs_are_private = True
allow_population_by_field_name = True allow_population_by_field_name = False
fields = base_fields fields = base_fields
# ### END ### API Post Comment Models ### Post_Comment_Base() ### # ### END ### API Post Comment Models ### Post_Comment_Base() ###

View File

@@ -1,7 +1,7 @@
import datetime, pytz import datetime, pytz
from typing import Dict, List, Optional, Set, Union from typing import Dict, List, Optional, Set, Union
from pydantic import BaseModel, EmailStr, Field, Json, PrivateAttr, ValidationError, validator from pydantic import BaseModel, EmailStr, Field, Json, PrivateAttr, ValidationError, validator, root_validator
from app.db_sql import redis_lookup_id_random from app.db_sql import redis_lookup_id_random
from app.lib_general import log, logging, secure_hash_string from app.lib_general import log, logging, secure_hash_string
@@ -16,29 +16,23 @@ class Post_Base(BaseModel):
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals()) log.debug(locals())
id_random: Optional[str] = Field( # --- Standardized Vision IDs (Strings) ---
# **base_fields['post_id_random'], id: Optional[str] = Field(None, **base_fields['post_id_random'])
alias = 'post_id_random', post_id: Optional[str] = Field(None, **base_fields['post_id_random'])
) account_id: Optional[str] = Field(None, **base_fields['account_id_random'])
id: Optional[int] = Field( person_id: Optional[str] = Field(None, **base_fields['person_id_random'])
alias = 'post_id' user_id: Optional[str] = Field(None, **base_fields['user_id_random'])
)
account_id_random: Optional[str]
account_id: Optional[int]
person_id_random: Optional[str]
person_id: Optional[int]
external_person_id: Optional[str] # Person 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)
user_id_random: Optional[str] # user_id_random: Optional[str]
user_id: Optional[int] # user_id: Optional[int]
type_id_random: Optional[str] # type_id_random: Optional[str]
type_id: Optional[int] # type_id: Optional[int]
topic_id_random: Optional[str] # topic_id_random: Optional[str]
topic_id: Optional[int] # topic_id: Optional[int]
type: Optional[str] type: Optional[str]
@@ -95,48 +89,33 @@ class Post_Base(BaseModel):
_processed_at: datetime.datetime = PrivateAttr(default_factory=datetime.datetime.now) _processed_at: datetime.datetime = PrivateAttr(default_factory=datetime.datetime.now)
#@validator('post_id_random', always=True) @root_validator(pre=True)
def post_id_random_copy(cls, v, values, **kwargs): def map_v3_ids(cls, values):
log.setLevel(logging.WARNING) """
log.debug(locals()) Vision Transformer:
Map DB keys to clean API keys and strip internal integers.
if values['id_random']: """
return values['id_random'] # 1. Map Random Strings to Clean Names
return None if rid := values.get('id_random') or values.get('post_id_random'):
values['id'] = rid
@validator('id', always=True) values['post_id'] = rid
def post_id_lookup(cls, v, values, **kwargs):
log.setLevel(logging.WARNING) if a_rid := values.get('account_id_random'):
log.debug(locals()) values['account_id'] = a_rid
if p_rid := values.get('person_id_random'):
if values['id_random']: values['person_id'] = p_rid
log.debug(values['id_random']) if u_rid := values.get('user_id_random'):
return redis_lookup_id_random(record_id_random=values['id_random'], table_name='post') values['user_id'] = u_rid
return None
# 2. Prevent "Collision Population"
@validator('account_id', always=True) for k in ['id', 'account_id', 'person_id', 'user_id']:
def account_id_lookup(cls, v, values, **kwargs): if k in values and not isinstance(values[k], str):
if isinstance(v, int) and v > 0: return v del values[k]
elif id_random := values.get('account_id_random'):
return redis_lookup_id_random(record_id_random=id_random, table_name='account') return values
return None
@validator('person_id', always=True)
def person_id_lookup(cls, v, values, **kwargs):
if isinstance(v, int) and v > 0: return v
elif id_random := values.get('person_id_random'):
return redis_lookup_id_random(record_id_random=id_random, table_name='person')
return None
@validator('user_id', always=True)
def user_id_lookup(cls, v, values, **kwargs):
if isinstance(v, int) and v > 0: return v
elif id_random := values.get('user_id_random'):
return redis_lookup_id_random(record_id_random=id_random, table_name='user')
return None
class Config: class Config:
underscore_attrs_are_private = True underscore_attrs_are_private = True
fields = base_fields fields = base_fields
allow_population_by_field_name = True allow_population_by_field_name = False
# ### END ### API Post Models ### Post_Base() ### # ### END ### API Post Models ### Post_Base() ###

View File

@@ -19,9 +19,11 @@ cms_obj_li = {
'base_name': Page_Base, 'base_name': Page_Base,
# V3 Search Security: # V3 Search Security:
'searchable_fields': [ 'searchable_fields': [
'page_id_random', 'account_id_random', 'site_id_random', 'name', 'id', 'account_id', 'site_id',
'title', 'description', 'content_html', 'enable', 'hide', 'page_id_random', 'account_id_random', 'site_id_random',
'priority', 'sort', 'group', 'notes', 'created_on', 'updated_on' 'code', 'name', 'title', 'description', 'content_html',
'enable', 'hide', 'priority', 'sort', 'group', 'notes',
'created_on', 'updated_on'
], ],
}, },
'post': { 'post': {
@@ -46,6 +48,7 @@ cms_obj_li = {
], ],
# V3 Search Security: # V3 Search Security:
'searchable_fields': [ 'searchable_fields': [
'id', 'account_id', 'person_id', 'user_id',
'post_id_random', 'account_id_random', 'organization_id_random', 'post_id_random', 'account_id_random', 'organization_id_random',
'person_id_random', 'user_id_random', 'external_person_id', 'title', 'content', 'person_id_random', 'user_id_random', 'external_person_id', 'title', 'content',
'type_code', 'topic_code', 'category_code', 'tags', 'location', 'type_code', 'topic_code', 'category_code', 'tags', 'location',
@@ -75,6 +78,7 @@ cms_obj_li = {
], ],
# V3 Search Security: # V3 Search Security:
'searchable_fields': [ 'searchable_fields': [
'id', 'post_id', 'person_id', 'user_id', 'account_id',
'post_comment_id_random', 'account_id_random', 'post_id_random', 'post_comment_id_random', 'account_id_random', 'post_id_random',
'person_id_random', 'user_id_random', 'content', 'enable', 'hide', 'person_id_random', 'user_id_random', 'content', 'enable', 'hide',
'priority', 'sort', 'group', 'notes', 'created_on', 'updated_on' 'priority', 'sort', 'group', 'notes', 'created_on', 'updated_on'

View File

@@ -47,7 +47,8 @@ core_obj_li = {
'base_name': Account_Base, 'base_name': Account_Base,
# V3 Search Security: # V3 Search Security:
'searchable_fields': [ 'searchable_fields': [
'account_id_random', 'code', 'name', 'short_name', 'description', 'id', 'account_id', 'id_random', 'account_id_random',
'code', 'name', 'short_name', 'description',
'enable', 'hide', 'priority', 'sort', 'group', 'created_on', 'updated_on' 'enable', 'hide', 'priority', 'sort', 'group', 'created_on', 'updated_on'
], ],
}, },
@@ -65,7 +66,7 @@ core_obj_li = {
'base_name': Account_Cfg_Base, 'base_name': Account_Cfg_Base,
# V3 Search Security: # V3 Search Security:
'searchable_fields': [ 'searchable_fields': [
'account_cfg_id_random', 'account_id_random', 'account_code', 'id', 'account_id', 'account_cfg_id_random', 'account_id_random', 'account_code',
'account_name', 'account_short_name', 'default_no_reply_email', 'account_name', 'account_short_name', 'default_no_reply_email',
'default_no_reply_name', 'confirm_email', 'help_event_email', 'default_no_reply_name', 'confirm_email', 'help_event_email',
'help_general_email', 'help_tech_email', 'stripe_account_id', 'help_general_email', 'help_tech_email', 'stripe_account_id',
@@ -86,9 +87,9 @@ core_obj_li = {
'base_name': Address_Base, 'base_name': Address_Base,
# V3 Search Security: # V3 Search Security:
'searchable_fields': [ 'searchable_fields': [
'address_id_random', 'account_id_random', 'for_type', 'for_id_random', 'id', 'account_id', 'contact_id', 'address_id_random', 'account_id_random',
'contact_id_random', 'name', 'attention_to', 'organization_name', 'for_type', 'for_id_random', 'contact_id_random', 'name', 'attention_to',
'line_1', 'line_2', 'line_3', 'city', 'country_subdivision_code', 'organization_name', 'line_1', 'line_2', 'line_3', 'city', 'country_subdivision_code',
'country_subdivision_name', 'state_province', 'postal_code', 'country_subdivision_name', 'state_province', 'postal_code',
'country_alpha_2_code', 'country_name', 'timezone', 'country_alpha_2_code', 'country_name', 'timezone',
'enable', 'hide', 'priority', 'sort', 'group', 'notes', 'created_on', 'updated_on' 'enable', 'hide', 'priority', 'sort', 'group', 'notes', 'created_on', 'updated_on'
@@ -108,7 +109,7 @@ core_obj_li = {
'base_name': Contact_Base, 'base_name': Contact_Base,
# V3 Search Security: # V3 Search Security:
'searchable_fields': [ 'searchable_fields': [
'contact_id_random', 'account_id_random', 'for_type', 'for_id_random', 'id', 'account_id', 'contact_id_random', 'account_id_random', 'for_type', 'for_id_random',
'name', 'title', 'tagline', 'description', 'timezone_name', 'name', 'title', 'tagline', 'description', 'timezone_name',
'email', 'email_status', 'phone_mobile', 'phone_office', 'email', 'email_status', 'phone_mobile', 'phone_office',
'website_url', 'website_name', 'enable', 'hide', 'priority', 'sort', 'group', 'notes', 'created_on', 'updated_on' 'website_url', 'website_name', 'enable', 'hide', 'priority', 'sort', 'group', 'notes', 'created_on', 'updated_on'
@@ -128,6 +129,7 @@ core_obj_li = {
'base_name': Data_Store_Base, 'base_name': Data_Store_Base,
# V3 Search Security: # V3 Search Security:
'searchable_fields': [ 'searchable_fields': [
'id', 'account_id', 'person_id', 'user_id',
'data_store_id_random', 'account_id_random', 'for_type', 'for_id_random', 'data_store_id_random', 'account_id_random', 'for_type', 'for_id_random',
'person_id_random', 'user_id_random', 'code', 'name', 'description', 'person_id_random', 'user_id_random', 'code', 'name', 'description',
'type', 'text', 'meta_text', 'access', 'enable', 'hide', 'priority', 'type', 'text', 'meta_text', 'access', 'enable', 'hide', 'priority',
@@ -148,6 +150,7 @@ core_obj_li = {
'base_name': Organization_Base, 'base_name': Organization_Base,
# V3 Search Security: # V3 Search Security:
'searchable_fields': [ 'searchable_fields': [
'id', 'account_id', 'contact_id', 'person_id', 'user_id',
'organization_id_random', 'account_id_random', 'contact_id_random', 'organization_id_random', 'account_id_random', 'contact_id_random',
'person_id_random', 'user_id_random', 'name', 'tagline', 'description', 'person_id_random', 'user_id_random', 'name', 'tagline', 'description',
'company', 'nonprofit', 'enable', 'hide', 'priority', 'sort', 'group', 'notes', 'created_on', 'updated_on' 'company', 'nonprofit', 'enable', 'hide', 'priority', 'sort', 'group', 'notes', 'created_on', 'updated_on'
@@ -174,14 +177,15 @@ core_obj_li = {
], ],
# V3 Search Security: # V3 Search Security:
'searchable_fields': [ 'searchable_fields': [
'id', 'account_id', 'contact_id', 'organization_id', 'user_id', 'membership_person_id',
'person_id_random', 'account_id_random', 'contact_id_random', 'person_id_random', 'account_id_random', 'contact_id_random',
'organization_id_random', 'user_id_random', 'membership_person_id_random', 'organization_id_random', 'user_id_random', 'membership_person_id_random',
'pronouns', 'informal_name', 'title_names', 'given_name', 'middle_name', 'title_names', 'given_name', 'middle_name',
'family_name', 'designations', 'professional_title', 'full_name', 'family_name', 'designations', 'professional_title', 'full_name',
'informal_full_name', 'professional_full_name', 'affiliations', 'informal_full_name', 'affiliations',
'primary_email', 'tagline', 'lu_gender_name', 'source_code', 'primary_email', 'tagline', 'source_code',
'external_id', 'status', 'hide', 'priority', 'sort', 'group', 'enable', 'notes', 'external_id', 'status', 'hide', 'priority', 'sort', 'group', 'enable', 'notes',
'created_on', 'updated_on', 'username', 'user_name', 'user_email' 'created_on', 'updated_on', 'username', 'user_name'
], ],
}, },
'user': { 'user': {
@@ -205,6 +209,7 @@ core_obj_li = {
], ],
# V3 Search Security: # V3 Search Security:
'searchable_fields': [ 'searchable_fields': [
'id', 'account_id', 'contact_id', 'organization_id', 'person_id',
'user_id_random', 'account_id_random', 'contact_id_random', 'user_id_random', 'account_id_random', 'contact_id_random',
'organization_id_random', 'person_id_random', 'username', 'name', 'organization_id_random', 'person_id_random', 'username', 'name',
'email', 'enable', 'super', 'manager', 'administrator', 'public', 'email', 'enable', 'super', 'manager', 'administrator', 'public',
@@ -219,7 +224,7 @@ core_obj_li = {
'base_name': User_Role_Base, 'base_name': User_Role_Base,
# V3 Search Security: # V3 Search Security:
'searchable_fields': [ 'searchable_fields': [
'user_id_random', 'for_type', 'for_id_random', 'code', 'name', 'id', 'user_id', 'user_id_random', 'for_type', 'for_id_random', 'code', 'name',
'description', 'enable', 'notes', 'created_on', 'updated_on' 'description', 'enable', 'notes', 'created_on', 'updated_on'
], ],
}, },
@@ -231,6 +236,7 @@ core_obj_li = {
'base_name': Log_Client_Viewing_Base, 'base_name': Log_Client_Viewing_Base,
# V3 Search Security: # V3 Search Security:
'searchable_fields': [ 'searchable_fields': [
'id', 'account_id', 'person_id', 'user_id',
'log_client_viewing_id_random', 'account_id_random', 'person_id_random', 'log_client_viewing_id_random', 'account_id_random', 'person_id_random',
'user_id_random', 'external_client_id', 'name', 'source', 'url_root', 'user_id_random', 'external_client_id', 'name', 'source', 'url_root',
'url_full_path', 'object_type', 'object_id', 'created_on', 'updated_on' 'url_full_path', 'object_type', 'object_id', 'created_on', 'updated_on'

View File

@@ -134,9 +134,10 @@ other_obj_li = {
], ],
# V3 Search Security: # V3 Search Security:
'searchable_fields': [ 'searchable_fields': [
'hosted_file_id_random', 'account_id_random', 'hash_sha256', 'title', 'id', 'account_id', 'hosted_file_id_random', 'account_id_random',
'description', 'filename', 'extension', 'content_type', 'enable', 'hash_sha256', 'title', 'description', 'filename', 'extension',
'hide', 'priority', 'sort', 'group', 'notes', 'created_on', 'updated_on' 'content_type', 'enable', 'hide', 'priority', 'sort', 'group',
'notes', 'created_on', 'updated_on'
], ],
}, },
'hosted_file_link': { 'hosted_file_link': {
@@ -153,8 +154,9 @@ other_obj_li = {
'base_name': Hosted_File_Link_Base, 'base_name': Hosted_File_Link_Base,
# V3 Search Security: # V3 Search Security:
'searchable_fields': [ 'searchable_fields': [
'id', 'account_id_random', 'hosted_file_id_random', 'link_to_type', 'id', 'account_id', 'hosted_file_id', 'account_id_random',
'link_to_id_random', 'created_on', 'updated_on' 'hosted_file_id_random', 'link_to_type', 'link_to_id_random',
'created_on', 'updated_on'
], ],
}, },
'stripe_log': { 'stripe_log': {