diff --git a/app/lib_sql_search.py b/app/lib_sql_search.py index 5263c5e..a65120a 100644 --- a/app/lib_sql_search.py +++ b/app/lib_sql_search.py @@ -205,14 +205,35 @@ def sql_search_qry_part( def process_filter(f) -> tuple[str, dict]: # --- ID VISION MAPPING --- # 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 - 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 == 'id': target_field = 'id_random' - else: target_field = f"{target_field}_random" - print(f"Search Trace: Mapping filter field '{f.field}' -> '{target_field}'", flush=True) + candidate_field = 'id_random' if target_field == 'id' else f"{target_field}_random" + + # 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: # Fallback check for original field just in case diff --git a/app/models/address_models.py b/app/models/address_models.py index c34864a..ccb50f8 100644 --- a/app/models/address_models.py +++ b/app/models/address_models.py @@ -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 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.lib_general import log, logging @@ -14,23 +14,15 @@ class Address_Base(BaseModel): log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL log.debug(locals()) - id_random: Optional[str] = Field( - **base_fields['address_id_random'], - alias = 'address_id_random', - # default_factory = lambda:secrets.token_urlsafe(default_num_bytes), - ) - id: Optional[int] = Field( - alias = 'address_id' - ) - account_id_random: Optional[str] - account_id: Optional[int] + # --- Standardized Vision IDs (Strings) --- + id: Optional[str] = Field(None, **base_fields['address_id_random']) + address_id: Optional[str] = Field(None, **base_fields['address_id_random']) + account_id: Optional[str] = Field(None, **base_fields['account_id_random']) + contact_id: Optional[str] = Field(None, **base_fields['contact_id_random']) for_type: Optional[str] for_id_random: Optional[str] - for_id: Optional[int] - - contact_id_random: Optional[str] - contact_id: Optional[int] + for_id: Optional[int] = Field(None, exclude=True) #organization: Optional[Organization_Base] = Organization_Base() @@ -72,63 +64,31 @@ class Address_Base(BaseModel): _processed_at: datetime.datetime = PrivateAttr(default_factory=datetime.datetime.now) - #@validator('address_id_random', always=True) - def address_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 address_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='address') - return None - - @validator('account_id', always=True) - def account_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('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 + @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('address_id_random'): + values['id'] = rid + values['address_id'] = rid + + if a_rid := values.get('account_id_random'): + values['account_id'] = a_rid + if c_rid := values.get('contact_id_random'): + values['contact_id'] = c_rid + + # 2. Prevent "Collision Population" + for k in ['id', 'account_id', 'contact_id']: + if k in values and not isinstance(values[k], str): + del values[k] + + return values class Config: underscore_attrs_are_private = True - allow_population_by_field_name = True + allow_population_by_field_name = False fields = base_fields # ### END ### API Address Models ### Address_Base() ### \ No newline at end of file diff --git a/app/models/hosted_file_models.py b/app/models/hosted_file_models.py index 2226b0c..565cd4e 100644 --- a/app/models/hosted_file_models.py +++ b/app/models/hosted_file_models.py @@ -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 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 @@ -14,17 +14,10 @@ class Hosted_File_Base(BaseModel): log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL log.debug(locals()) - id_random: Optional[str] = Field( - **base_fields['hosted_file_id_random'], - alias = 'hosted_file_id_random', - # default_factory = lambda:secrets.token_urlsafe(default_num_bytes), - ) - id: Optional[int] = Field( - alias = 'hosted_file_id' - ) - - account_id_random: Optional[str] - account_id: Optional[int] + # --- Standardized Vision IDs (Strings) --- + id: Optional[str] = Field(None, **base_fields['hosted_file_id_random']) + hosted_file_id: Optional[str] = Field(None, **base_fields['hosted_file_id_random']) + account_id: Optional[str] = Field(None, **base_fields['account_id_random']) hash_sha256: Optional[str] title: Optional[str] @@ -39,21 +32,11 @@ class Hosted_File_Base(BaseModel): mimetype: Optional[str] 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 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 - # metadata: Optional[str] - enable: Optional[bool] - # enable_from: Optional[datetime.datetime] = None - # enable_to: Optional[datetime.datetime] = None hide: Optional[bool] priority: Optional[bool] @@ -78,28 +61,29 @@ class Hosted_File_Base(BaseModel): _processed_at: datetime.datetime = PrivateAttr(default_factory=datetime.datetime.now) - #@validator('hosted_file_id_random', always=True) - def hosted_file_id_random_copy(cls, v, values, **kwargs): - if values['id_random']: - return values['id_random'] - return None - - @validator('id', always=True) - def hosted_file_id_lookup(cls, v, values, **kwargs): - 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='hosted_file') - 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 + @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('hosted_file_id_random'): + values['id'] = rid + values['hosted_file_id'] = rid + + if a_rid := values.get('account_id_random'): + values['account_id'] = a_rid + + # 2. Prevent "Collision Population" + for k in ['id', 'account_id']: + if k in values and not isinstance(values[k], str): + del values[k] + + return values class Config: underscore_attrs_are_private = True - allow_population_by_field_name = True + allow_population_by_field_name = False fields = base_fields # ### END ### API Hosted File Models ### Hosted_File_Base() ### diff --git a/app/models/organization_models.py b/app/models/organization_models.py index ceb4099..1834e47 100644 --- a/app/models/organization_models.py +++ b/app/models/organization_models.py @@ -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 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 @@ -18,21 +18,13 @@ class Organization_Base(BaseModel): log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL log.debug(locals()) - id_random: Optional[str] = Field( - **base_fields['organization_id_random'], - alias = 'organization_id_random', - ) - id: Optional[int] = Field( - alias = 'organization_id' - ) - 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] + # --- Standardized Vision IDs (Strings) --- + id: Optional[str] = Field(None, **base_fields['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']) + contact_id: Optional[str] = Field(None, **base_fields['contact_id_random']) + person_id: Optional[str] = Field(None, **base_fields['person_id_random']) + user_id: Optional[str] = Field(None, **base_fields['user_id_random']) name: Optional[str] tagline: Optional[str] @@ -67,71 +59,35 @@ class Organization_Base(BaseModel): _processed_at: datetime.datetime = PrivateAttr(default_factory=datetime.datetime.now) - #@validator('organization_id_random', always=True) - def organization_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 organization_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='organization') - return None - - @validator('account_id', always=True) - def account_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('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 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 + @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('organization_id_random'): + values['id'] = rid + values['organization_id'] = rid + + if a_rid := values.get('account_id_random'): + values['account_id'] = a_rid + if c_rid := values.get('contact_id_random'): + values['contact_id'] = c_rid + if p_rid := values.get('person_id_random'): + values['person_id'] = p_rid + if u_rid := values.get('user_id_random'): + values['user_id'] = u_rid + + # 2. Prevent "Collision Population" + for k in ['id', 'account_id', 'contact_id', 'person_id', 'user_id']: + if k in values and not isinstance(values[k], str): + del values[k] + + return values class Config: underscore_attrs_are_private = True - allow_population_by_field_name = True + allow_population_by_field_name = False fields = base_fields # ### END ### API Organization Models ### Organization_Base() ### diff --git a/app/models/page_models.py b/app/models/page_models.py index 06d04e2..c872c21 100644 --- a/app/models/page_models.py +++ b/app/models/page_models.py @@ -1,10 +1,10 @@ import datetime, pytz 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.lib_general import * +from app.lib_general import log, logging 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.debug(locals()) - id_random: Optional[str] = Field( - **base_fields['page_id_random'], - alias = 'page_id_random', - ) - id: Optional[int] = Field( - #alias = 'page_id' - ) - account_id_random: Optional[str] - account_id: Optional[int] + # --- Standardized Vision IDs (Strings) --- + id: Optional[str] = Field(None, **base_fields['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']) + site_id: Optional[str] = Field(None, **base_fields['site_id_random']) - 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] + 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_from: Optional[datetime.datetime] = None - enable_to: Optional[datetime.datetime] = None - - title: Optional[str] - body: Optional[str] - style_href: Optional[str] - script_src: Optional[str] - - authentication_required: Optional[bool] + hide: Optional[bool] + priority: Optional[bool] + sort: Optional[int] + group: Optional[str] notes: Optional[str] created_on: Optional[datetime.datetime] = None @@ -44,27 +65,31 @@ class Page_Base(BaseModel): _processed_at: datetime.datetime = PrivateAttr(default_factory=datetime.datetime.now) - #@validator('page_id_random', always=True) - def page_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 page_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='page') - return 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('page_id_random'): + values['id'] = rid + values['page_id'] = rid + + if a_rid := values.get('account_id_random'): + values['account_id'] = a_rid + if s_rid := values.get('site_id_random'): + values['site_id'] = s_rid + + # 2. Prevent "Collision Population" + 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: underscore_attrs_are_private = True - allow_population_by_field_name = True + allow_population_by_field_name = False fields = base_fields # ### END ### API Page Models ### Page_Base() ### \ No newline at end of file diff --git a/app/models/person_models.py b/app/models/person_models.py index cc16cad..5dcc912 100644 --- a/app/models/person_models.py +++ b/app/models/person_models.py @@ -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 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 @@ -23,31 +23,17 @@ class Person_Base(BaseModel): log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL log.debug(locals()) - id_random: Optional[str] = Field( - **base_fields['person_id_random'], - alias = 'person_id_random', - # default_factory = lambda:secrets.token_urlsafe(default_num_bytes), - ) - id: Optional[int] = Field( - alias = 'person_id' - ) - account_id_random: Optional[str] - account_id: Optional[int] + # --- Standardized Vision IDs (Strings) --- + id: Optional[str] = Field(None, **base_fields['person_id_random']) + person_id: Optional[str] = Field(None, **base_fields['person_id_random']) + account_id: Optional[str] = Field(None, **base_fields['account_id_random']) + contact_id: Optional[str] = Field(None, **base_fields['contact_id_random']) + organization_id: Optional[str] = Field(None, **base_fields['organization_id_random']) + user_id: Optional[str] = Field(None, **base_fields['user_id_random']) + membership_person_id: Optional[str] = Field(None, **base_fields['membership_person_id_random']) - contact_id_random: Optional[str] - contact_id: Optional[int] - - 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 + # pronouns: Optional[str] # MISSING in physical table + # informal_name: Optional[str] # MISSING in physical table 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. @@ -59,11 +45,7 @@ class Person_Base(BaseModel): 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 - # 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 + # preferred_display_name: Optional[str] # MISSING in physical table # BEGIN # Auto created name variations 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) - #@validator('person_id_random', always=True) - def person_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 person_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='person') - return None - - @validator('account_id', always=True) - def account_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('account_id_random'): - return redis_lookup_id_random(record_id_random=id_random, 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('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 + @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('person_id_random'): + values['id'] = rid + values['person_id'] = rid + + if a_rid := values.get('account_id_random'): + values['account_id'] = a_rid + if c_rid := values.get('contact_id_random'): + values['contact_id'] = c_rid + if o_rid := values.get('organization_id_random'): + values['organization_id'] = o_rid + if u_rid := values.get('user_id_random'): + values['user_id'] = u_rid + if mp_rid := values.get('membership_person_id_random'): + values['membership_person_id'] = mp_rid + + # 2. Prevent "Collision Population" + for k in ['id', 'account_id', 'contact_id', 'organization_id', 'user_id', 'membership_person_id']: + if k in values and not isinstance(values[k], str): + del values[k] + + return values @validator('given_name', always=True) def given_name_validator(cls, v): @@ -234,6 +186,6 @@ class Person_Base(BaseModel): class Config: underscore_attrs_are_private = True - allow_population_by_field_name = True + allow_population_by_field_name = False fields = base_fields # ### END ### API Person Models ### Person_Base() ### diff --git a/app/models/post_comment_models.py b/app/models/post_comment_models.py index 5d4034a..34e5015 100644 --- a/app/models/post_comment_models.py +++ b/app/models/post_comment_models.py @@ -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 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, secure_hash_string @@ -17,25 +17,15 @@ class Post_Comment_Base(BaseModel): log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL log.debug(locals()) - id_random: Optional[str] = Field( - **base_fields['post_comment_id_random'], - alias = 'post_comment_id_random', - ) - id: Optional[int] = Field( - #alias = 'post_comment_id' - ) - - post_id_random: Optional[str] - post_id: Optional[int] - - person_id_random: Optional[str] - person_id: Optional[int] + # --- Standardized Vision IDs (Strings) --- + id: Optional[str] = Field(None, **base_fields['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']) + person_id: Optional[str] = Field(None, **base_fields['person_id_random']) + user_id: Optional[str] = Field(None, **base_fields['user_id_random']) 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] content: Optional[str] @@ -65,45 +55,33 @@ class Post_Comment_Base(BaseModel): _processed_at: datetime.datetime = PrivateAttr(default_factory=datetime.datetime.now) - #@validator('post_comment_id_random', always=True) - # def post_comment_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 post_comment_id_lookup(cls, v, values, **kwargs): - 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='post_comment') - return None - - @validator('post_id', always=True) - def post_id_lookup(cls, v, values, **kwargs): - if isinstance(v, int) and v > 0: return v - elif id_random := values.get('post_id_random'): - return redis_lookup_id_random(record_id_random=id_random, table_name='post') - 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 + @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('post_comment_id_random'): + values['id'] = rid + values['post_comment_id'] = rid + + if p_rid := values.get('post_id_random'): + values['post_id'] = p_rid + if per_rid := values.get('person_id_random'): + values['person_id'] = per_rid + if u_rid := values.get('user_id_random'): + values['user_id'] = u_rid + + # 2. Prevent "Collision Population" + for k in ['id', 'post_id', 'person_id', 'user_id']: + if k in values and not isinstance(values[k], str): + del values[k] + + return values class Config: underscore_attrs_are_private = True - allow_population_by_field_name = True + allow_population_by_field_name = False fields = base_fields # ### END ### API Post Comment Models ### Post_Comment_Base() ### diff --git a/app/models/post_models.py b/app/models/post_models.py index 858d2df..a90fd4e 100644 --- a/app/models/post_models.py +++ b/app/models/post_models.py @@ -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 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, secure_hash_string @@ -16,29 +16,23 @@ class Post_Base(BaseModel): log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL log.debug(locals()) - id_random: Optional[str] = Field( - # **base_fields['post_id_random'], - alias = 'post_id_random', - ) - id: Optional[int] = Field( - alias = 'post_id' - ) - account_id_random: Optional[str] - account_id: Optional[int] - - person_id_random: Optional[str] - person_id: Optional[int] + # --- Standardized Vision IDs (Strings) --- + id: Optional[str] = Field(None, **base_fields['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']) + person_id: Optional[str] = Field(None, **base_fields['person_id_random']) + user_id: Optional[str] = Field(None, **base_fields['user_id_random']) 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] + # user_id_random: Optional[str] + # user_id: Optional[int] - type_id_random: Optional[str] - type_id: Optional[int] + # type_id_random: Optional[str] + # type_id: Optional[int] - topic_id_random: Optional[str] - topic_id: Optional[int] + # topic_id_random: Optional[str] + # topic_id: Optional[int] type: Optional[str] @@ -95,48 +89,33 @@ class Post_Base(BaseModel): _processed_at: datetime.datetime = PrivateAttr(default_factory=datetime.datetime.now) - #@validator('post_id_random', always=True) - def post_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 post_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='post') - 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('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 + @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('post_id_random'): + values['id'] = rid + values['post_id'] = rid + + if a_rid := values.get('account_id_random'): + values['account_id'] = a_rid + if p_rid := values.get('person_id_random'): + values['person_id'] = p_rid + if u_rid := values.get('user_id_random'): + values['user_id'] = u_rid + + # 2. Prevent "Collision Population" + for k in ['id', 'account_id', 'person_id', 'user_id']: + if k in values and not isinstance(values[k], str): + del values[k] + + return values class Config: underscore_attrs_are_private = True fields = base_fields - allow_population_by_field_name = True + allow_population_by_field_name = False # ### END ### API Post Models ### Post_Base() ### diff --git a/app/object_definitions/cms.py b/app/object_definitions/cms.py index 171b923..d855ea5 100644 --- a/app/object_definitions/cms.py +++ b/app/object_definitions/cms.py @@ -19,9 +19,11 @@ cms_obj_li = { 'base_name': Page_Base, # V3 Search Security: 'searchable_fields': [ - 'page_id_random', 'account_id_random', 'site_id_random', 'name', - 'title', 'description', 'content_html', 'enable', 'hide', - 'priority', 'sort', 'group', 'notes', 'created_on', 'updated_on' + 'id', 'account_id', 'site_id', + 'page_id_random', 'account_id_random', 'site_id_random', + 'code', 'name', 'title', 'description', 'content_html', + 'enable', 'hide', 'priority', 'sort', 'group', 'notes', + 'created_on', 'updated_on' ], }, 'post': { @@ -46,6 +48,7 @@ cms_obj_li = { ], # V3 Search Security: 'searchable_fields': [ + 'id', 'account_id', 'person_id', 'user_id', 'post_id_random', 'account_id_random', 'organization_id_random', 'person_id_random', 'user_id_random', 'external_person_id', 'title', 'content', 'type_code', 'topic_code', 'category_code', 'tags', 'location', @@ -75,6 +78,7 @@ cms_obj_li = { ], # V3 Search Security: 'searchable_fields': [ + 'id', 'post_id', 'person_id', 'user_id', 'account_id', 'post_comment_id_random', 'account_id_random', 'post_id_random', 'person_id_random', 'user_id_random', 'content', 'enable', 'hide', 'priority', 'sort', 'group', 'notes', 'created_on', 'updated_on' diff --git a/app/object_definitions/core.py b/app/object_definitions/core.py index 10f87fc..dbf9a1b 100644 --- a/app/object_definitions/core.py +++ b/app/object_definitions/core.py @@ -47,7 +47,8 @@ core_obj_li = { 'base_name': Account_Base, # V3 Search Security: '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' ], }, @@ -65,7 +66,7 @@ core_obj_li = { 'base_name': Account_Cfg_Base, # V3 Search Security: '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', 'default_no_reply_name', 'confirm_email', 'help_event_email', 'help_general_email', 'help_tech_email', 'stripe_account_id', @@ -86,9 +87,9 @@ core_obj_li = { 'base_name': Address_Base, # V3 Search Security: 'searchable_fields': [ - 'address_id_random', 'account_id_random', 'for_type', 'for_id_random', - 'contact_id_random', 'name', 'attention_to', 'organization_name', - 'line_1', 'line_2', 'line_3', 'city', 'country_subdivision_code', + 'id', 'account_id', 'contact_id', 'address_id_random', 'account_id_random', + 'for_type', 'for_id_random', 'contact_id_random', 'name', 'attention_to', + 'organization_name', 'line_1', 'line_2', 'line_3', 'city', 'country_subdivision_code', 'country_subdivision_name', 'state_province', 'postal_code', 'country_alpha_2_code', 'country_name', 'timezone', 'enable', 'hide', 'priority', 'sort', 'group', 'notes', 'created_on', 'updated_on' @@ -108,7 +109,7 @@ core_obj_li = { 'base_name': Contact_Base, # V3 Search Security: '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', 'email', 'email_status', 'phone_mobile', 'phone_office', '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, # V3 Search Security: 'searchable_fields': [ + 'id', 'account_id', 'person_id', 'user_id', 'data_store_id_random', 'account_id_random', 'for_type', 'for_id_random', 'person_id_random', 'user_id_random', 'code', 'name', 'description', 'type', 'text', 'meta_text', 'access', 'enable', 'hide', 'priority', @@ -148,6 +150,7 @@ core_obj_li = { 'base_name': Organization_Base, # V3 Search Security: 'searchable_fields': [ + 'id', 'account_id', 'contact_id', 'person_id', 'user_id', 'organization_id_random', 'account_id_random', 'contact_id_random', 'person_id_random', 'user_id_random', 'name', 'tagline', 'description', 'company', 'nonprofit', 'enable', 'hide', 'priority', 'sort', 'group', 'notes', 'created_on', 'updated_on' @@ -174,14 +177,15 @@ core_obj_li = { ], # V3 Search Security: 'searchable_fields': [ + 'id', 'account_id', 'contact_id', 'organization_id', 'user_id', 'membership_person_id', 'person_id_random', 'account_id_random', 'contact_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', - 'informal_full_name', 'professional_full_name', 'affiliations', - 'primary_email', 'tagline', 'lu_gender_name', 'source_code', + 'informal_full_name', 'affiliations', + 'primary_email', 'tagline', 'source_code', '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': { @@ -205,6 +209,7 @@ core_obj_li = { ], # V3 Search Security: 'searchable_fields': [ + 'id', 'account_id', 'contact_id', 'organization_id', 'person_id', 'user_id_random', 'account_id_random', 'contact_id_random', 'organization_id_random', 'person_id_random', 'username', 'name', 'email', 'enable', 'super', 'manager', 'administrator', 'public', @@ -219,7 +224,7 @@ core_obj_li = { 'base_name': User_Role_Base, # V3 Search Security: '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' ], }, @@ -231,6 +236,7 @@ core_obj_li = { 'base_name': Log_Client_Viewing_Base, # V3 Search Security: 'searchable_fields': [ + 'id', 'account_id', 'person_id', 'user_id', 'log_client_viewing_id_random', 'account_id_random', 'person_id_random', 'user_id_random', 'external_client_id', 'name', 'source', 'url_root', 'url_full_path', 'object_type', 'object_id', 'created_on', 'updated_on' diff --git a/app/object_definitions/other.py b/app/object_definitions/other.py index d7e7408..2326cc2 100644 --- a/app/object_definitions/other.py +++ b/app/object_definitions/other.py @@ -134,9 +134,10 @@ other_obj_li = { ], # V3 Search Security: 'searchable_fields': [ - 'hosted_file_id_random', 'account_id_random', 'hash_sha256', 'title', - 'description', 'filename', 'extension', 'content_type', 'enable', - 'hide', 'priority', 'sort', 'group', 'notes', 'created_on', 'updated_on' + 'id', 'account_id', 'hosted_file_id_random', 'account_id_random', + 'hash_sha256', 'title', 'description', 'filename', 'extension', + 'content_type', 'enable', 'hide', 'priority', 'sort', 'group', + 'notes', 'created_on', 'updated_on' ], }, 'hosted_file_link': { @@ -153,8 +154,9 @@ other_obj_li = { 'base_name': Hosted_File_Link_Base, # V3 Search Security: 'searchable_fields': [ - 'id', 'account_id_random', 'hosted_file_id_random', 'link_to_type', - 'link_to_id_random', 'created_on', 'updated_on' + 'id', 'account_id', 'hosted_file_id', 'account_id_random', + 'hosted_file_id_random', 'link_to_type', 'link_to_id_random', + 'created_on', 'updated_on' ], }, 'stripe_log': {