From 1bb38674638355a31909aaa7816aa25d840e6786 Mon Sep 17 00:00:00 2001 From: Scott Idem Date: Fri, 13 Aug 2021 17:09:32 -0400 Subject: [PATCH] Working on stuff --- app/methods/event_cfg_methods.py | 58 ++++++++++ app/methods/event_methods.py | 43 ++------ app/methods/event_registration_cfg_methods.py | 45 ++++++++ app/models/event_cfg_models.py | 100 +++++++++++++++++ app/models/event_models.py | 101 +----------------- app/models/event_registration_cfg_models.py | 56 ++++++++++ app/models/event_registration_models.py | 4 + app/routers/event.py | 2 +- app/routers/importing.py | 87 +++++++++++++++ 9 files changed, 359 insertions(+), 137 deletions(-) create mode 100644 app/methods/event_cfg_methods.py create mode 100644 app/methods/event_registration_cfg_methods.py create mode 100644 app/models/event_cfg_models.py create mode 100644 app/models/event_registration_cfg_models.py diff --git a/app/methods/event_cfg_methods.py b/app/methods/event_cfg_methods.py new file mode 100644 index 0000000..9043467 --- /dev/null +++ b/app/methods/event_cfg_methods.py @@ -0,0 +1,58 @@ +from __future__ import annotations +import datetime + +from typing import Dict, List, Optional, Set, Union +from pydantic import BaseModel, EmailStr, Field, PrivateAttr, ValidationError, validator + +from app.db_sql import redis_lookup_id_random, sql_insert, sql_select, sql_update +from app.lib_general import log, logging + +from app.methods.event_registration_cfg_methods import load_event_registration_cfg_obj + +from app.models.event_cfg_models import Event_Cfg_Base + + +# ### BEGIN ### API Event Cfg Methods ### load_event_cfg_obj() ### +def load_event_cfg_obj( + event_id: int|str, + inc_event_registration_cfg: bool = False, + limit: int = 1000, + by_alias: bool = True, + exclude_unset: bool = True, + model_as_dict: bool = False, + ) -> Event_Cfg_Base|bool: + #log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL + log.debug(locals()) + + if event_id := redis_lookup_id_random(record_id_random=event_id, table_name='event'): pass + else: return False + + if event_cfg_rec := sql_select(table_name='v_event_cfg', record_id=event_id): pass + else: return False + + #log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL + log.debug(event_cfg_rec) + + try: + event_cfg_obj = Event_Cfg_Base(**event_cfg_rec) + log.debug(event_cfg_obj) + except ValidationError as e: + log.error(e.json()) + return False + + # Updated 2021-06-30 + if inc_event_registration_cfg: + if event_registration_cfg_result := load_event_registration_cfg_obj( + event_id = event_id, + by_alias = by_alias, + exclude_unset = exclude_unset, + model_as_dict = model_as_dict, + ): + event_cfg_obj.event_registration_cfg = event_registration_cfg_result + else: event_cfg_obj.event_registration_cfg = None + + if model_as_dict: + return event_cfg_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member + else: + return event_cfg_obj +# ### END ### API Event Cfg Methods ### load_event_cfg_obj() ### diff --git a/app/methods/event_methods.py b/app/methods/event_methods.py index b2ab595..ed14868 100644 --- a/app/methods/event_methods.py +++ b/app/methods/event_methods.py @@ -9,11 +9,13 @@ from app.lib_general import log, logging from app.methods.address_methods import load_address_obj from app.methods.contact_methods import load_contact_obj +from app.methods.event_cfg_methods import load_event_cfg_obj from app.methods.event_session_methods import load_event_session_obj from app.methods.person_methods import create_person_obj, load_person_obj, update_person_obj from app.methods.user_methods import create_user_obj, load_user_obj, update_user_obj -from app.models.event_models import Event_Base, Event_Cfg_Base +from app.models.event_models import Event_Base +from app.models.event_cfg_models import Event_Cfg_Base # ### BEGIN ### API Event Methods ### load_event_obj() ### @@ -62,8 +64,7 @@ def load_event_obj( else: return False if event_rec := sql_select(table_name='v_event', record_id=event_id): pass - else: - return False + else: return False #log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL log.debug(event_rec) @@ -149,6 +150,7 @@ def load_event_obj( # log.debug(event_id) if event_cfg_result := load_event_cfg_obj( event_id = event_id, + inc_event_registration_cfg = inc_event_registration_cfg, by_alias = by_alias, exclude_unset = exclude_unset, model_as_dict = model_as_dict, @@ -163,7 +165,7 @@ def load_event_obj( if inc_event_person_list: pass if inc_event_presentation_list: pass if inc_event_presenter_list: pass - if inc_event_registration_cfg: pass + # if inc_event_registration_cfg: pass if inc_event_registration_list: pass if inc_event_session_list: @@ -373,39 +375,6 @@ def get_event_rec_list( # ### END ### API Event Methods ### get_event_rec_list() ### -# ### BEGIN ### API Event Methods ### load_event_cfg_obj() ### -def load_event_cfg_obj( - event_id: int|str, - limit: int = 1000, - by_alias: bool = True, - exclude_unset: bool = True, - model_as_dict: bool = False, - ) -> Event_Cfg_Base|bool: - #log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL - log.debug(locals()) - - if event_id := redis_lookup_id_random(record_id_random=event_id, table_name='event'): pass - else: return False - - if event_cfg_rec := sql_select(table_name='v_event_cfg', record_id=event_id): - #log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL - log.debug(event_cfg_rec) - - try: - event_cfg_obj = Event_Cfg_Base(**event_cfg_rec) - log.debug(event_cfg_obj) - except ValidationError as e: - log.error(e.json()) - return False - else: return False - - if model_as_dict: - return event_cfg_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member - else: - return event_cfg_obj -# ### END ### API Event Methods ### load_event_cfg_obj() ### - - # ### BEGIN ### API Event Methods ### load_event_obj_list() ### def load_event_obj_list( account_id: int|str|None = None, diff --git a/app/methods/event_registration_cfg_methods.py b/app/methods/event_registration_cfg_methods.py new file mode 100644 index 0000000..67acecf --- /dev/null +++ b/app/methods/event_registration_cfg_methods.py @@ -0,0 +1,45 @@ +from __future__ import annotations +import datetime + +from typing import Dict, List, Optional, Set, Union +from pydantic import BaseModel, EmailStr, Field, PrivateAttr, ValidationError, validator + +from app.db_sql import redis_lookup_id_random, sql_insert, sql_select, sql_update +from app.lib_general import log, logging + +from app.models.event_registration_cfg_models import Event_Registration_Cfg_Base + + +# ### BEGIN ### API Event Registration Cfg Methods ### load_event_registration_cfg_obj() ### +def load_event_registration_cfg_obj( + event_id: int|str, + inc_event_registration_cfg: bool = False, + limit: int = 1000, + by_alias: bool = True, + exclude_unset: bool = True, + model_as_dict: bool = False, + ) -> Event_Registration_Cfg_Base|bool: + #log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL + log.debug(locals()) + + if event_id := redis_lookup_id_random(record_id_random=event_id, table_name='event'): pass + else: return False + + if event_registration_cfg_rec := sql_select(table_name='event_registration_cfg', record_id=event_id): pass + else: return False + + #log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL + log.debug(event_registration_cfg_rec) + + try: + event_registration_cfg_obj = Event_Registration_Cfg_Base(**event_registration_cfg_rec) + log.debug(event_registration_cfg_obj) + except ValidationError as e: + log.error(e.json()) + return False + + if model_as_dict: + return event_registration_cfg_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member + else: + return event_registration_cfg_obj +# ### END ### API Event Registration Cfg Methods ### load_event_registration_cfg_obj() ### diff --git a/app/models/event_cfg_models.py b/app/models/event_cfg_models.py new file mode 100644 index 0000000..ed7b346 --- /dev/null +++ b/app/models/event_cfg_models.py @@ -0,0 +1,100 @@ +from __future__ import annotations +import datetime, hashlib, logging, os, pytz, redis, secrets + +from typing import Dict, List, Optional, Set, Union +from pydantic import BaseModel, EmailStr, Field, Json, PrivateAttr, ValidationError, validator + +from app.db_sql import redis_lookup_id_random +from app.lib_general import log, logging + +from app.models.common_field_schema import base_fields, default_num_bytes +from app.models.event_registration_cfg_models import Event_Registration_Cfg_Base + + +class Event_Cfg_Base(BaseModel): + log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL + log.debug(locals()) + + # id_random: Optional[str] = Field( + # **base_fields['event_cfg_id_random'], + # alias = 'event_cfg_id_random', + # default_factory = lambda:secrets.token_urlsafe(default_num_bytes), + # ) + # id: Optional[int] = Field( + # #alias = 'event_cfg_id' + # ) + + enable: Optional[bool] + enable_from: Optional[datetime.datetime] + enable_to: Optional[datetime.datetime] + + enable_comments: Optional[bool] + disable_navigation: Optional[bool] + + enable_event_file_upload_for_event: Optional[bool] + enable_event_file_upload_for_location: Optional[bool] + enable_event_file_upload_for_presentation: Optional[bool] + enable_event_file_upload_for_presenter: Optional[bool] + enable_event_file_upload_for_session: Optional[bool] + enable_event_file_upload_for_track: Optional[bool] + # enable_event_file_upload_for_event_track: Optional[bool] # Change to this pattern in the future? + # enable_file_upload_for_track: Optional[bool] # Change to this pattern in the future? + + enable_event_file_upload_review_question: Optional[bool] + enable_event_file_upload_email_question: Optional[bool] + enable_event_file_upload_comments_question: Optional[bool] + enable_event_file_approval_option: Optional[bool] + enable_event_file_public_use_option: Optional[bool] + enable_event_file_member_use_option: Optional[bool] + enable_event_file_attendee_use_option: Optional[bool] + enable_event_file_publish_option: Optional[bool] + enable_event_file_purpose_option: Optional[bool] + enable_event_file_os_selection_option: Optional[bool] + enable_event_file_os_change_option: Optional[bool] + + custom_event_file_upload_description: Optional[str] + custom_event_file_agreement_1_text: Optional[str] + custom_event_file_agreement_1_description: Optional[str] + + ask_speaker_ready_room: Optional[bool] + ask_presentation_publish_optout: Optional[bool] + + default_event_file_to_public_use: Optional[bool] + ask_for_public_version: Optional[bool] + + file_approval_enabled: Optional[bool] + + hide_file_upload_presentation_name: Optional[bool] + hide_file_upload_review_input: Optional[bool] + hide_file_upload_email_input: Optional[bool] + hide_file_upload_comments_input: Optional[bool] + + hide_session_codes: Optional[bool] + + unauthenticated_access: Optional[bool] + unauthenticated_access_public_endpoint: Optional[bool] + + hide: Optional[bool] + status: Optional[int] + review: Optional[bool] + approve: Optional[bool] + ready: Optional[bool] + ready_on: Optional[datetime.datetime] + archive: Optional[bool] + archive_on: Optional[datetime.datetime] + priority: Optional[bool] + sort: Optional[int] + group: Optional[str] + notes: Optional[str] + + # Including other related objects + event_registration_cfg: Optional[Event_Registration_Cfg_Base] = Field( + alias = 'registration_cfg' + ) + + _processed_at: datetime.datetime = PrivateAttr(default_factory=datetime.datetime.now) + + class Config: + underscore_attrs_are_private = True + allow_population_by_field_name = True + # fields = base_fields diff --git a/app/models/event_models.py b/app/models/event_models.py index a84d52f..1aa8c46 100644 --- a/app/models/event_models.py +++ b/app/models/event_models.py @@ -10,6 +10,7 @@ from app.lib_general import log, logging from app.models.common_field_schema import base_fields, default_num_bytes from app.models.address_models import Address_Base from app.models.contact_models import Contact_Base +from app.models.event_cfg_models import Event_Cfg_Base from app.models.event_person_models import Event_Person_Base from app.models.person_models import Person_Base from app.models.user_models import User_Base @@ -240,102 +241,4 @@ class Event_Base(BaseModel): allow_population_by_field_name = True fields = base_fields - -class Event_Cfg_Base(BaseModel): - log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL - log.debug(locals()) - - # id_random: Optional[str] = Field( - # **base_fields['event_cfg_id_random'], - # alias = 'event_cfg_id_random', - # default_factory = lambda:secrets.token_urlsafe(default_num_bytes), - # ) - # id: Optional[int] = Field( - # #alias = 'event_cfg_id' - # ) - - enable: Optional[bool] - enable_from: Optional[datetime.datetime] - enable_to: Optional[datetime.datetime] - - enable_comments: Optional[bool] - disable_navigation: Optional[bool] - - enable_event_file_upload_for_event: Optional[bool] - enable_event_file_upload_for_location: Optional[bool] - enable_event_file_upload_for_presentation: Optional[bool] - enable_event_file_upload_for_presenter: Optional[bool] - enable_event_file_upload_for_session: Optional[bool] - enable_event_file_upload_for_track: Optional[bool] - # enable_event_file_upload_for_event_track: Optional[bool] # Change to this pattern in the future? - # enable_file_upload_for_track: Optional[bool] # Change to this pattern in the future? - - enable_event_file_upload_review_question: Optional[bool] - enable_event_file_upload_email_question: Optional[bool] - enable_event_file_upload_comments_question: Optional[bool] - enable_event_file_approval_option: Optional[bool] - enable_event_file_public_use_option: Optional[bool] - enable_event_file_member_use_option: Optional[bool] - enable_event_file_attendee_use_option: Optional[bool] - enable_event_file_publish_option: Optional[bool] - enable_event_file_purpose_option: Optional[bool] - enable_event_file_os_selection_option: Optional[bool] - enable_event_file_os_change_option: Optional[bool] - - custom_event_file_upload_description: Optional[str] - custom_event_file_agreement_1_text: Optional[str] - custom_event_file_agreement_1_description: Optional[str] - - ask_speaker_ready_room: Optional[bool] - ask_presentation_publish_optout: Optional[bool] - - default_event_file_to_public_use: Optional[bool] - ask_for_public_version: Optional[bool] - - file_approval_enabled: Optional[bool] - - hide_file_upload_presentation_name: Optional[bool] - hide_file_upload_review_input: Optional[bool] - hide_file_upload_email_input: Optional[bool] - hide_file_upload_comments_input: Optional[bool] - - hide_session_codes: Optional[bool] - - unauthenticated_access: Optional[bool] - unauthenticated_access_public_endpoint: Optional[bool] - - hide: Optional[bool] - status: Optional[int] - review: Optional[bool] - approve: Optional[bool] - ready: Optional[bool] - ready_on: Optional[datetime.datetime] - archive: Optional[bool] - archive_on: Optional[datetime.datetime] - priority: Optional[bool] - sort: Optional[int] - group: Optional[str] - notes: Optional[str] - - _processed_at: datetime.datetime = PrivateAttr(default_factory=datetime.datetime.now) - - #@validator('event_cfg_id_random', always=True) - # def event_cfg_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_cfg_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 - -Event_Base.update_forward_refs() # NOTE: This is needed since Event_Cfg_Base is below Event_Base. +# Event_Base.update_forward_refs() # NOTE: This is needed since Event_Cfg_Base is below Event_Base. diff --git a/app/models/event_registration_cfg_models.py b/app/models/event_registration_cfg_models.py new file mode 100644 index 0000000..7e4fdc6 --- /dev/null +++ b/app/models/event_registration_cfg_models.py @@ -0,0 +1,56 @@ +from __future__ import annotations +import datetime, hashlib, logging, os, pytz, redis, secrets + +from typing import Dict, List, Optional, Set, Union +from pydantic import BaseModel, EmailStr, Field, Json, PrivateAttr, ValidationError, validator + +from app.db_sql import redis_lookup_id_random +from app.lib_general import log, logging + +from app.models.common_field_schema import base_fields, default_num_bytes + + +class Event_Registration_Cfg_Base(BaseModel): + log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL + log.debug(locals()) + + start_on: Optional[datetime.datetime] + end_on: Optional[datetime.datetime] + + deadline_1: Optional[datetime.datetime] + deadline_2: Optional[datetime.datetime] + deadline_3: Optional[datetime.datetime] + deadline_4: Optional[datetime.datetime] + deadline_5: Optional[datetime.datetime] + + start_buffer_days: Optional[int] + end_buffer_days: Optional[int] + + welcome_message: Optional[str] + attendee_message: Optional[str] + guest_message: Optional[str] + + order_code: Optional[str] + + order_confirm_title: Optional[str] + order_confirm_header: Optional[str] + order_confirm_thanks: Optional[str] + order_confirm_message: Optional[str] + order_confirm_footer: Optional[str] + + support_email: Optional[str] + support_name: Optional[str] + + extended_registration_profile: Optional[str] + registration_info_json: Optional[str] # Should this be Json type? + attendee_info_json: Optional[str] # Should this be Json type? + agreements_json: Optional[str] # Should this be Json type? + + # Including other related objects + + _processed_at: datetime.datetime = PrivateAttr(default_factory=datetime.datetime.now) + + class Config: + underscore_attrs_are_private = True + allow_population_by_field_name = True + # fields = base_fields \ No newline at end of file diff --git a/app/models/event_registration_models.py b/app/models/event_registration_models.py index fd60bc1..e384934 100644 --- a/app/models/event_registration_models.py +++ b/app/models/event_registration_models.py @@ -8,6 +8,7 @@ from app.db_sql import redis_lookup_id_random from app.lib_general import * from app.models.common_field_schema import base_fields, default_num_bytes +from app.models.event_registration_cfg_models import Event_Registration_Cfg_Base class Event_Registration_Base(BaseModel): @@ -41,6 +42,9 @@ class Event_Registration_Base(BaseModel): created_on: Optional[datetime.datetime] = None updated_on: Optional[datetime.datetime] = None + # Including other related objects + cfg: Optional[Event_Registration_Cfg_Base] + _processed_at: datetime.datetime = PrivateAttr(default_factory=datetime.datetime.now) #@validator('event_registration_id_random', always=True) diff --git a/app/routers/event.py b/app/routers/event.py index 94450de..702c5e9 100644 --- a/app/routers/event.py +++ b/app/routers/event.py @@ -324,7 +324,7 @@ async def get_event_obj( inc_event_presentation_list = inc_event_presentation_list, # inc_event_presenter_cat = inc_event_presenter_cat, inc_event_presenter_list = inc_event_presenter_list, - # inc_event_registration_cfg = inc_event_registration_cfg, + inc_event_registration_cfg = inc_event_registration_cfg, # inc_event_registration_list = inc_event_registration_list, inc_event_session_list = inc_event_session_list, # inc_event_track = inc_event_track, diff --git a/app/routers/importing.py b/app/routers/importing.py index 8ea0049..018095a 100644 --- a/app/routers/importing.py +++ b/app/routers/importing.py @@ -463,3 +463,90 @@ async def importing_cont_edu_cert_person_data( # break return mk_resp(data=cont_edu_cert_person_data_li) + + + +@router.post('/cont_edu_cert_person_data_touch', response_model=Resp_Body_Base) +async def importing_cont_edu_cert_person_data_touch( + response: Response = Response, + ): + log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL + log.debug(locals()) + + account_id = 19 + cont_edu_cert_id = 3 + full_file_path = 'admin/temp/import_cont_edu_cert_person_data.xlsx' + + df = pandas.read_excel(full_file_path, na_filter=False, dtype={'external_id':str, 'phone_home':str, 'phone_mobile':str, 'city':str, 'state_province':str, 'address_postal_code':str, 'country':str}) + log.debug(df) + + df_dict = df.to_dict(orient='records') + # log.debug(df_dict) + + # return mk_resp(data=False, status_code=500, response=response) + + cont_edu_cert_person_data_li = [] + # for i in df.index: + for record in df_dict: + cont_edu_cert_person_new = None + person_id = None + user_id = None + cont_edu_cert_person_id = None + + cont_edu_cert_person_data = {} + # cont_edu_cert_person_data['cont_edu_cert_id'] = cont_edu_cert_id + cont_edu_cert_person_data['enable'] = True + cont_edu_cert_person_data['email'] = record['email'] + + other_data = {} + other_data['last_event_date'] = '2021-08-01' + other_data['other_guest_of'] = record['other_guest_of'] + other_data['other_guest_li'] = record['other_guest_li'] + + cont_edu_cert_person_data['other_json'] = json.dumps(other_data, indent=4) + + # Look up by email address and INSERT or UPDATE new cont_edu_cert_person record + # Process the cont_edu_cert_person data + log.debug(cont_edu_cert_person_data) + # log.debug('*** *** *** *** END TEST RUN *** *** *** ***') + # continue + if cont_edu_cert_person_rec_li_result := sql_select(table_name='v_cont_edu_cert_person', field_name='email', field_value=cont_edu_cert_person_data['email']): + if not isinstance(cont_edu_cert_person_rec_li_result, list): + # Pull out IDs and UPDATE existing cont_edu_cert_person record + # log.debug('Found one record') + cont_edu_cert_person_rec = cont_edu_cert_person_rec_li_result + cont_edu_cert_person_id = cont_edu_cert_person_rec.get('cont_edu_cert_person_id', None) + log.info(cont_edu_cert_person_id) + # person_id = cont_edu_cert_person_rec.get('person_id', None) + # user_id = cont_edu_cert_person_rec.get('user_id', None) + cont_edu_cert_person_data['id'] = cont_edu_cert_person_id + if cont_edu_cert_person_obj_up_result := sql_update(data=cont_edu_cert_person_data, table_name='cont_edu_cert_person'): + # log.debug(cont_edu_cert_person_obj_up_result) + pass + else: + log.warning(cont_edu_cert_person_obj_up_result) + continue # Something unexpected may have happened + else: + log.warning('Found more than one record') + log.warning(cont_edu_cert_person_rec_li_result) + # Do nothing + continue # Something unexpected may have happened + cont_edu_cert_person_rec_li = cont_edu_cert_person_rec_li_result + else: + # INSERT new record + log.debug('Found no records or something went wrong') + cont_edu_cert_person_data['account_id'] = account_id + if cont_edu_cert_person_obj_in_result := sql_insert(data=cont_edu_cert_person_data, table_name='cont_edu_cert_person'): + log.debug(cont_edu_cert_person_obj_in_result) + cont_edu_cert_person_id = cont_edu_cert_person_obj_in_result # Should be an int + cont_edu_cert_person_new = True # Need to UPDATE this record after the contact, address, and user data is processed + else: + log.warning(cont_edu_cert_person_obj_in_result) + continue # Something unexpected may have happened + + cont_edu_cert_person_data_li.append(cont_edu_cert_person_data) + log.debug(f"Record processed: {cont_edu_cert_person_id}") + # log.debug('*** *** *** *** END TEST RUN *** *** *** ***') + # break + log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL + return mk_resp(data=cont_edu_cert_person_data_li)