Compare commits
3 Commits
e19fd63d1f
...
developmen
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22e5a3c3fd | ||
|
|
9962176c74 | ||
|
|
35fa5132e7 |
@@ -432,6 +432,11 @@ async def event_id_badge_import(
|
||||
event_badge_id = event_person_result.get('event_badge_id')
|
||||
event_person_profile_id = event_person_result.get('event_person_profile_id')
|
||||
log.info(f'Found Event Person. Updating existing... Event Person ID: {event_person_id}')
|
||||
# Don't touch enable on update — a manually disabled record is effectively
|
||||
# blacklisted and should survive repeated re-imports of the same file.
|
||||
event_person_data.pop('enable', None)
|
||||
event_person_data.get('event_badge', {}).pop('enable', None)
|
||||
event_person_data.get('event_person_profile', {}).pop('enable', None)
|
||||
if create_event_person_obj_result := create_update_event_person_obj_v4(
|
||||
event_person_dict_obj = event_person_data,
|
||||
event_person_id = event_person_id,
|
||||
@@ -501,6 +506,14 @@ async def event_id_badge_import(
|
||||
# WHERE badge_type = 'Adapt26 Sponsor';
|
||||
|
||||
|
||||
def _split_full_name(full_name: str) -> tuple:
|
||||
"""Split 'First Last' on last space into (given_name, family_name)."""
|
||||
parts = full_name.strip().rsplit(' ', 1)
|
||||
if len(parts) == 2:
|
||||
return parts[0], parts[1]
|
||||
return full_name.strip(), ''
|
||||
|
||||
|
||||
def _zoom_ticket_field(record: dict, field_prefix: str, ticket_name: str) -> str:
|
||||
"""
|
||||
Extracts a per-ticket-type field value from a Zoom CSV row.
|
||||
@@ -780,6 +793,11 @@ async def event_id_badge_import_zoom_csv(
|
||||
event_person_profile_id = event_person_result.get('event_person_profile_id')
|
||||
log.info(f'Found Event Person. Updating existing... Event Person ID: {event_person_id}')
|
||||
|
||||
# Don't touch enable on update — a manually disabled record is effectively
|
||||
# blacklisted and should survive repeated re-imports of the same file.
|
||||
event_person_data.pop('enable', None)
|
||||
event_person_data.get('event_badge', {}).pop('enable', None)
|
||||
event_person_data.get('event_person_profile', {}).pop('enable', None)
|
||||
updated_id = create_update_event_person_obj_v4(
|
||||
event_person_dict_obj=event_person_data,
|
||||
event_person_id=event_person_id,
|
||||
@@ -811,4 +829,227 @@ async def event_id_badge_import_zoom_csv(
|
||||
if return_detail:
|
||||
return mk_resp(data=event_badge_person_li, status_message=f'Zoom CSV import complete. Processed {len(event_badge_person_li)} records.', response=commons.response)
|
||||
else:
|
||||
return mk_resp(data=event_badge_person_summary_li, status_message=f'Zoom CSV import complete. Processed {len(event_badge_person_summary_li)} records.', response=commons.response)
|
||||
return mk_resp(data=event_badge_person_summary_li, status_message=f'Zoom CSV import complete. Processed {len(event_badge_person_summary_li)} records.', response=commons.response)
|
||||
|
||||
|
||||
# ### BEGIN ### Splash (Cvent) XLSX Badge Import ### event_id_badge_import_splash_xlsx() ###
|
||||
# Accepts a Splash (Cvent) registrant XLSX export and inserts/updates event_person records.
|
||||
# Splash exports fixed columns: Full Name, Email, Time of RSVP, Status, plus custom
|
||||
# fields prefixed with "Custom: ". Email is used as external_id. Full Name is split
|
||||
# on the last space into given_name/family_name and also stored directly as full_name.
|
||||
# Updated 2026-06-02
|
||||
|
||||
@router.post('/event/{event_id}/badge/import/splash_xlsx', response_model=Resp_Body_Base)
|
||||
async def event_id_badge_import_splash_xlsx(
|
||||
event_id: str = Path(min_length=11, max_length=22),
|
||||
file: UploadFile = File(...),
|
||||
|
||||
begin_at: int = 0,
|
||||
end_at: int = 20000,
|
||||
|
||||
import_status_filter: str = 'Attending', # set to '' to import all statuses
|
||||
|
||||
return_detail: bool = False,
|
||||
|
||||
commons: Common_Route_Params = Depends(common_route_params),
|
||||
):
|
||||
"""
|
||||
Import event badges from a Splash (Cvent) registrant XLSX export.
|
||||
|
||||
Splash exports fixed columns (Full Name, Email, Time of RSVP, Status) plus
|
||||
custom fields prefixed with "Custom: ". Email is used as external_id.
|
||||
Full Name is split on the last space into given_name/family_name and also
|
||||
stored directly as full_name. Pass import_status_filter='' to import all
|
||||
statuses (default is 'Attending').
|
||||
"""
|
||||
log.setLevel(logging.INFO)
|
||||
|
||||
account_id = commons.x_account_id
|
||||
|
||||
event_id_random = event_id
|
||||
if event_id := redis_lookup_id_random(record_id_random=event_id, table_name='event'): pass
|
||||
else: return mk_resp(data=None, status_code=404, response=commons.response)
|
||||
|
||||
link_to_type = 'event'
|
||||
link_to_id = event_id
|
||||
|
||||
file_info = await save_file(
|
||||
file=file,
|
||||
account_id=account_id,
|
||||
link_to_type=link_to_type,
|
||||
link_to_id=link_to_id,
|
||||
)
|
||||
if file_info['saved']:
|
||||
log.info('File saved')
|
||||
else:
|
||||
log.error('Something may have gone wrong while saving the uploaded file?')
|
||||
return mk_resp(data=None, status_code=500, response=commons.response)
|
||||
|
||||
hosted_files_path = settings.FILES_PATH['hosted_files_root']
|
||||
subdirectory_dest = os.path.join(hosted_files_path, file_info.get('subdirectory_path'))
|
||||
hash_filename = file_info.get('hash_sha256') + '.file'
|
||||
full_file_path = pathlib.Path(os.path.join(subdirectory_dest, hash_filename))
|
||||
|
||||
if not full_file_path.exists():
|
||||
log.warning(f'Not found at full file path: {full_file_path}')
|
||||
return mk_resp(data=None, status_code=500, response=commons.response)
|
||||
|
||||
df = pandas.read_excel(full_file_path, dtype=str, na_filter=False)
|
||||
|
||||
df_dict = df.to_dict(orient='records')
|
||||
log.info(f'Splash XLSX total record count: {len(df_dict)}')
|
||||
|
||||
loop_count = 0
|
||||
skipped_count = 0
|
||||
event_badge_person_li = []
|
||||
event_badge_person_summary_li = []
|
||||
|
||||
log.setLevel(logging.DEBUG)
|
||||
for record in df_dict:
|
||||
log.info(f'Loop Count: {loop_count}')
|
||||
loop_count += 1
|
||||
if loop_count <= begin_at: continue
|
||||
if loop_count > end_at: break
|
||||
|
||||
# Status filter — skip rows that don't match when a filter is set.
|
||||
if import_status_filter:
|
||||
status = str(record.get('Status', '')).strip()
|
||||
if status != import_status_filter:
|
||||
log.info(f'Skipping row with status "{status}" (filter: "{import_status_filter}")')
|
||||
skipped_count += 1
|
||||
continue
|
||||
|
||||
email = str(record.get('Email', '')).strip()
|
||||
if not email:
|
||||
log.warning('Row missing Email — skipping.')
|
||||
skipped_count += 1
|
||||
continue
|
||||
external_id = email
|
||||
|
||||
full_name = str(record.get('Full Name', '')).strip()
|
||||
given_name, family_name = _split_full_name(full_name)
|
||||
|
||||
professional_title = str(record.get('Custom: Job Title', '')).strip()
|
||||
organization = str(record.get('Custom: Company Name', '')).strip()
|
||||
country = str(record.get('Custom: Country', '')).strip()
|
||||
state_province = str(record.get('Custom: State', '')).strip()
|
||||
dietary_restrictions = str(record.get('Custom: Please note any dietary restrictions or preferences.', '')).strip()
|
||||
|
||||
# "Custom: Opt-In" → agree_to_tc / allow_tracking
|
||||
opt_in_raw = str(record.get('Custom: Opt-In', '')).strip().lower()
|
||||
if opt_in_raw in ('yes', 'y', 'true', '1', 'opt-in', 'opt_in'):
|
||||
agree_to_tc_val = True
|
||||
allow_tracking_val = True
|
||||
elif opt_in_raw in ('no', 'n', 'false', '0', 'opt-out', 'opt_out'):
|
||||
agree_to_tc_val = False
|
||||
allow_tracking_val = False
|
||||
else:
|
||||
agree_to_tc_val = None
|
||||
allow_tracking_val = None
|
||||
|
||||
event_person_summary = {
|
||||
'event_id': event_id,
|
||||
'event_id_random': event_id_random,
|
||||
'external_id': external_id,
|
||||
'given_name': given_name,
|
||||
'family_name': family_name,
|
||||
'email': email,
|
||||
}
|
||||
|
||||
event_person_data = {
|
||||
'account_id': account_id,
|
||||
'event_id': event_id,
|
||||
'enable': True,
|
||||
'external_id': external_id,
|
||||
'event_person_profile': {
|
||||
'event_id': event_id,
|
||||
'enable': True,
|
||||
'given_name': given_name,
|
||||
'family_name': family_name,
|
||||
'full_name': full_name,
|
||||
'email': email,
|
||||
'professional_title': professional_title,
|
||||
'affiliations': organization,
|
||||
'country': country,
|
||||
'state_province': state_province,
|
||||
},
|
||||
'event_badge': {
|
||||
'enable': True,
|
||||
'external_id': external_id,
|
||||
'given_name': given_name,
|
||||
'family_name': family_name,
|
||||
'full_name': full_name,
|
||||
'email': email,
|
||||
'professional_title': professional_title,
|
||||
'affiliations': organization,
|
||||
'country': country,
|
||||
'state_province': state_province,
|
||||
'other_1': dietary_restrictions,
|
||||
# TEMPORARY: Axonius DC event badge template mu_7SRuJYum (23).
|
||||
'event_badge_template_id': 23,
|
||||
'event_badge_template_id_random': 'mu_7SRuJYum',
|
||||
'badge_type_code': 'attendee',
|
||||
'agree_to_tc': agree_to_tc_val,
|
||||
'allow_tracking': allow_tracking_val,
|
||||
},
|
||||
}
|
||||
|
||||
sql_select_event_person = """
|
||||
SELECT id AS event_person_id, id_random AS event_person_id_random,
|
||||
external_id AS event_person_external_id,
|
||||
event_badge_id AS event_badge_id,
|
||||
event_person_profile_id AS event_person_profile_id
|
||||
FROM `event_person`
|
||||
WHERE event_person.event_id = :event_id
|
||||
AND event_person.external_id = :external_id
|
||||
/*LIMIT 2*/;
|
||||
"""
|
||||
|
||||
event_person_result = sql_select(sql=sql_select_event_person, data=event_person_summary)
|
||||
if event_person_result:
|
||||
if isinstance(event_person_result, list):
|
||||
log.error(f'Found more than one Event Person with external_id={external_id}. Count: {len(event_person_result)}')
|
||||
event_person_result = event_person_result[0]
|
||||
|
||||
event_person_id = event_person_result.get('event_person_id')
|
||||
event_badge_id = event_person_result.get('event_badge_id')
|
||||
event_person_profile_id = event_person_result.get('event_person_profile_id')
|
||||
log.info(f'Found Event Person. Updating existing... Event Person ID: {event_person_id}')
|
||||
|
||||
# Don't touch enable on update — a manually disabled record is effectively
|
||||
# blacklisted and should survive repeated re-imports of the same file.
|
||||
event_person_data.pop('enable', None)
|
||||
event_person_data.get('event_badge', {}).pop('enable', None)
|
||||
event_person_data.get('event_person_profile', {}).pop('enable', None)
|
||||
updated_id = create_update_event_person_obj_v4(
|
||||
event_person_dict_obj=event_person_data,
|
||||
event_person_id=event_person_id,
|
||||
account_id=account_id,
|
||||
event_id=event_id,
|
||||
event_badge_id=event_badge_id,
|
||||
event_person_profile_id=event_person_profile_id,
|
||||
)
|
||||
if updated_id:
|
||||
log.warning(f'Event Person updated. ID: {updated_id}')
|
||||
else:
|
||||
log.warning(f'Event Person not updated. ID: {event_person_id}')
|
||||
else:
|
||||
log.info('No Event Person found. Creating new...')
|
||||
result_id = create_update_event_person_obj_v4(
|
||||
event_person_dict_obj=event_person_data,
|
||||
account_id=account_id,
|
||||
event_id=event_id,
|
||||
)
|
||||
if result_id:
|
||||
log.warning(f'Event Person created. ID: {result_id}')
|
||||
else:
|
||||
log.warning('Event Person not created.')
|
||||
|
||||
event_badge_person_li.append(event_person_data)
|
||||
event_badge_person_summary_li.append(event_person_summary)
|
||||
|
||||
processed = len(event_badge_person_li)
|
||||
if return_detail:
|
||||
return mk_resp(data=event_badge_person_li, status_message=f'Splash XLSX import complete. Processed {processed} records, skipped {skipped_count}.', response=commons.response)
|
||||
else:
|
||||
return mk_resp(data=event_badge_person_summary_li, status_message=f'Splash XLSX import complete. Processed {processed} records, skipped {skipped_count}.', response=commons.response)
|
||||
Reference in New Issue
Block a user