1156 Commits

Author SHA1 Message Date
Scott Idem
cfbe6f458f Update searchable fields for event_exhibit
Added account_id and event_exhibit_id to the searchable_fields list to allow V3 search filtering by integer IDs.
2026-01-28 11:05:11 -05:00
Scott Idem
a97e80baab Implement Deprecation Warning System and Router Registry Cleanup
- Added DeprecationParams dependency to log warnings when legacy routes are accessed.
- Updated setup_routers to apply deprecation warnings to non-V3 legacy endpoints.
- Exempted core infrastructure, special routers, and routers currently in use from deprecation warnings.
- Cleaned up 24 unused router imports from the registry.
2026-01-28 10:55:03 -05:00
Scott Idem
b37108e5dd Saving notes. 2026-01-27 18:47:02 -05:00
Scott Idem
4ef591771e Update searchable fields for event_badge
Added badge_type_code, badge_type_code_override, member_type_code, member_status, and registration_type_code to the searchable_fields list for event_badge.
2026-01-27 18:03:56 -05:00
Scott Idem
3311ba8dd6 Refactor Journal and Journal Entry models to strictly use Vision ID string pattern
Updated Journal_Base and Journal_Entry_Base to explicitly remove integer IDs (journal_id, journal_entry_id) during validation to prevent mixed-type ID collisions. This ensures the Journal module adheres to the highest V3 Vision standard compliance.
2026-01-27 13:00:22 -05:00
Scott Idem
007fd2ec8f Refactor Post and Post Comment models to strictly use Vision ID string pattern
Updated Post_Base and Post_Comment_Base to ensure integer IDs (post_id, post_comment_id) are explicitly removed during validation to prevent mixed-type ID collisions. This hardens the V3 Vision standard compliance.
2026-01-27 12:16:56 -05:00
Scott Idem
5af3f44a53 Refactor Contact and User models to use Vision ID string pattern
Updated Contact_Base and User_Base (including New/Out variants) to use standardized string IDs mapped from random IDs via root_validator. Removed legacy integer ID fields and lookup validators to support V3 Vision standards. This completes the refactor chain for Person and Post dependencies.
2026-01-27 12:12:51 -05:00
Scott Idem
e299fdc178 Saving notes 2026-01-27 12:02:29 -05:00
Scott Idem
0811738b98 Fix KeyError: Added missing 'grant_id_random' to common_field_schema.py 2026-01-27 11:12:35 -05:00
Scott Idem
d6134e799e Refactor Event models to use Vision ID string pattern
Updated Event_Presentation_Base, Event_Location_Base, and Event_Abstract_Base (and Base_New/In) to use standardized string IDs mapped from random IDs via root_validator. Removed legacy integer ID fields and validators to ensure API responses comply with the V3 Vision standard.
2026-01-27 10:49:02 -05:00
Scott Idem
48e0a31cf5 Saving notes 2026-01-26 19:20:51 -05:00
Scott Idem
f7a17b2f99 V3 API: Enhance privacy by hiding internal file sharding paths and fix syntax in object definitions 2026-01-26 18:50:22 -05:00
Scott Idem
a754525a59 Some quick documentation for old legacy routes. 2026-01-26 17:36:32 -05:00
Scott Idem
f2420b958d Bug fix for Event Device related fields. the ID needed to be searchable.
Quick removal of the password from the log output
2026-01-26 11:56:31 -05:00
Scott Idem
061c153061 Saving updated notes. 2026-01-22 19:05:22 -05:00
Scott Idem
2e4fbfc8ab Saving more test scripts 2026-01-22 18:59:01 -05:00
Scott Idem
60345dd21e V3 Migration Phase 2-4: Implementation of specialized Binary Actions (Upload, Stream, Delete) and Orphan management logic. Full E2E coverage. 2026-01-22 18:51:26 -05:00
Scott Idem
1837b442cf V3 Migration Phase 1: Stabilize Hosted File models, IDs, and whitelisting. Added comprehensive verification tests. 2026-01-22 18:30:34 -05:00
Scott Idem
df0ce7f910 Saving fixes to the hosted file delete function. 2026-01-22 17:31:29 -05:00
Scott Idem
1e6b9d1c18 Bug fixes for uploading the files. I though the changes being made where not supposed to break legacy endpoints. Not sure what happened. Either way things are almost back to normal. 2026-01-22 16:49:03 -05:00
Scott Idem
48d9e38c39 Bug fixes related to file uploads. Fixing id_random int vs str confusion. For account and for hosted_file. 2026-01-22 16:01:23 -05:00
Scott Idem
988775b9dd Done for the day 2026-01-21 20:30:15 -05:00
Scott Idem
329ea51487 Added a sort of alias for the enabled options. Really disabled should be the alias for "not_enabled". "not_enabled" is what is used on the frontend and has been. 2026-01-21 20:25:11 -05:00
Scott Idem
e8322b4b1a fix(db): prevent silent failures in sql_select
- Modify sql_select to return False on database exceptions instead of an empty result
- Update V3 Search endpoint to catch False results and return a 500 Internal Server Error
- Enhances error visibility for frontend developers and prevents misleading 200 OK responses during SQL errors
2026-01-21 19:49:58 -05:00
Scott Idem
bdd1bd2ba2 feat(search): enhance V3 ID Vision mapping and searchable fields
- Update lib_sql_search.py to include comprehensive 'vision_fields' mapping for most core objects
- Ensure Vision Mapping only triggers for non-integer values to support backend filters
- Add clean ID names (e.g., 'event_id', 'account_id') to searchable_fields whitelists in Events, Badges, and Journal object definitions
- Resolve Concatenation typo in vision_fields list
- Improve searchability for Journal Entries by adding 'default_qry_str'
2026-01-21 19:21:52 -05:00
Scott Idem
6ca79e9a02 chore(api): stabilize SQL core and enhance searchability
- Refactor SQL CRUD to use engine.connect() context managers for thread safety
- Optimize connection pooling in lib_sql_core
- Clean up app/routers/api.py to fix duplicate definitions and OpenAPI KeyError
- Add 'default_qry_str' to searchable_fields for Event, Session, Presentation, Presenter, Badge, and Journal
- Add 'event_location_name' to searchable_fields for Event Session
- Verified 20/20 E2E success via repro_intermittent_errors.py
2026-01-21 15:23:04 -05:00
Scott Idem
89bf87cb62 fix(db): stabilize connection refreshing and prevent ResourceClosedError
- Update sql_connect to refresh global db object via reconnect_db
- Add returns_rows check and safe fetch block in sql_select
- Prevents 500 errors during transient database connection issues
2026-01-21 12:49:47 -05:00
Scott Idem
b2ee1f2760 Less debug. Also why was this using the print() function? It should have been using the normal log.info() or whatever. 2026-01-20 19:27:16 -05:00
Scott Idem
45ca81a3e3 Removing debugging. Changing it to INFO in many locations. 2026-01-20 19:23:30 -05:00
Scott Idem
c795f42290 fix(auth): handle list response from sql_select in dependencies_v3
- Check if api_key_results is a list before calling .get()
- Prevents 500 AttributeError on machine auth verification
2026-01-20 18:52:59 -05:00
Scott Idem
43ac62b561 feat(auth): consolidate and secure V3 authentication flow
- Re-apply safe guest auth and passcode-to-JWT endpoint
- Consolidate AccountContext with token_payload and role flags
- Restore documentation for new guest flows and public read whitelists
- Fix 403 error in get_obj_li by allowing optional account context
2026-01-20 18:42:43 -05:00
Scott Idem
d4e46a4a97 feat(auth): implement site-based passcode-to-JWT endpoint
- Add POST /api/authenticate_passcode to verify site access codes
- Refactor sign_jwt to support arbitrary role flags (super, admin, etc.)
- Update dependencies_v3 to extract role flags from JWT payloads
- Add E2E test for passcode auth verification
2026-01-20 17:51:54 -05:00
Scott Idem
e16fbaa34b fix(api): resolve SQL unpacking crash and Event serialization errors
- Refactor SQL helpers in lib_sql_search to return empty tuples instead of False
- Add Pydantic pre-validators to Event_Base to coerce time objects to strings
- Improves API stability for Event searches and filtered lists
2026-01-20 15:49:13 -05:00
Scott Idem
dc7732ab5f feat(security): implement safe guest auth flow and harden request_jwt
- Patched request_jwt to strip privileged IDs when signing with public keys
- Updated AccountContext and V3 dependencies to preserve JWT payloads for guests
- Whitelisted Archive, Post, Event, and other core objects for public read access
- Added 'default_qry_str' to Event searchable fields
- Added test_e2e_jwt_guest_auth.py for security verification
2026-01-20 14:56:56 -05:00
Scott Idem
8a22ac324c Fix: Refactor sql_select to reliably handle result counts and prevent return-type mismatch 2026-01-19 18:17:41 -05:00
Scott Idem
817bb80f87 ID Vision Phase 2: Standardize Page, Post, Person, Organization, and Hosted File objects 2026-01-19 18:04:17 -05:00
Scott Idem
ab8afb72d2 Fix: Make forced account filtering schema-aware to prevent crashes on specialized views 2026-01-19 17:17:34 -05:00
Scott Idem
579772977b Docs: Final formatting and cleanup of V3 Frontend Guide 2026-01-19 17:08:48 -05:00
Scott Idem
ede4cfabf0 Docs: Document Structured Error Handling (Rich Bubbling) in V3 Guide 2026-01-19 17:04:32 -05:00
Scott Idem
eeb19647f5 Error Bubbling: Implement machine-readable rich error objects for CRUD operations 2026-01-19 17:01:58 -05:00
Scott Idem
19e64135ca Permissive Update: Implement x-ae-ignore-extra-fields header support for nested routes 2026-01-19 16:48:48 -05:00
Scott Idem
a269e2a716 Saving the test file just because. 2026-01-19 16:34:49 -05:00
Scott Idem
4d439e63a9 Docs: Update V3 Frontend Guide to reflect ID Vision and Permissive Mode 2026-01-19 16:02:18 -05:00
Scott Idem
7db937f8af Vision ID: Standardize Site Domain and Journal objects with string-only IDs and searchable mapping 2026-01-19 15:57:00 -05:00
Scott Idem
2dbf47d874 Security: Implement JWT verification in V3 and prevent numeric ID signing 2026-01-19 14:41:20 -05:00
Scott Idem
cad0d2e867 Security: Enforce mandatory API Keys for V3, fix search logic, and update frontend guide 2026-01-19 14:11:13 -05:00
Scott Idem
d8b0c3b0a4 Saving notes and data 2026-01-16 17:27:27 -05:00
Scott Idem
9e0f94964e Bug fix for trying to use the wrong hosted file and tmp paths or src. Also saving documentation for the new MCP AE DB field manager. 2026-01-16 14:40:12 -05:00
Scott Idem
1bbe5cc31f Tests: Add README and fix diagnosis script paths 2026-01-16 11:12:17 -05:00
Scott Idem
b2384f2869 Tests: Reorganize test suite into functional subdirectories
- Categorized scripts into tests/unit/, tests/integration/, tests/e2e/, and tests/tools/.
- Adopted consistent naming prefixes (test_unit_*, test_int_*, test_e2e_*, tool_*).
- Renamed conftest_mock.py to mock_config_helper.py for clarity.
- Updated test_int_boot_diagnosis.py with sys.path setup for root-level execution.
2026-01-16 10:46:19 -05:00
Scott Idem
31fd384704 Docs: Consolidate admin documentation and migrate reference data
- Created LOCAL_DEVELOPMENT_GUIDE.md and DEPLOYMENT_GUIDE_MANUAL.md from legacy txt files.
- Migrated country/time_zone data and requirements.txt to documentation/reference_data/.
- Removed redundant admin/documentation/ and admin/data_files/ directories.
- Enhanced app/lib_schema_v3.py to explicitly capture 'required' fields from DB 'NOT NULL' constraint.
- Added verification tests for schema logic and standalone DB connectivity.
2026-01-16 10:06:51 -05:00
Scott Idem
db5cf2502a Session: Infrastructure and Documentation Finalization 2026-01-15 18:09:38 -05:00
Scott Idem
68862e4545 Docs: Consolidate V3 standards and cleanup documentation directory 2026-01-15 17:53:06 -05:00
Scott Idem
28d5843d52 Saving current notes. About to reorganize the documentation directory. 2026-01-15 17:48:44 -05:00
Scott Idem
acd770962b Refactor: Modularize logging and finalize lifespan integration 2026-01-15 17:31:32 -05:00
Scott Idem
eccd71f450 Refactor: Modularize database logic and extract core CRUD operations 2026-01-15 17:16:48 -05:00
Scott Idem
5ece1d34e3 Refactor: Relocate bootstrap and validation logic into lifespan context manager 2026-01-15 17:10:42 -05:00
Scott Idem
3f276a42e1 Refactor: Modularize configuration and implement robust DB bootstrap 2026-01-15 16:59:18 -05:00
Scott Idem
16c79aca39 Cleanup: Finalize modularization of app/main.py 2026-01-15 16:45:10 -05:00
Scott Idem
2227432970 Refactor: Modularize middleware and router registration in app/main.py 2026-01-15 16:36:19 -05:00
Scott Idem
d321b94395 chore(tests): organize test scripts and beautify account creation email
- Moved scattered Python test scripts from root and 'admin/development/' to 'tests/'.
- Beautified the HTML email body for account creation links in 'app/methods/person_methods.py' with a modern responsive design.
2026-01-15 14:38:00 -05:00
Scott Idem
f0711f27b4 fix(email): resolve SMTP authentication failure and improve configuration resilience
- Fixed a bug where missing 'id=0' in the 'cfg' table caused SMTP authentication to fail by defaulting to placeholder credentials.
- Updated 'app/lib_email.py' to explicitly validate SMTP server and port settings before connecting, preventing crashes with 'please run connect() first'.
- Added email fallback logic in 'app/methods/person_methods.py' to use 'user_email' or 'primary_email' if the primary contact email is missing.
- Aligned 'app/config.py.default' with the production structure, explicitly re-adding 'SMTP' and 'FILES_PATH' dictionaries.
- Added comprehensive unit tests in 'tests/test_email_configuration.py' to verify configuration handling.
2026-01-15 13:19:58 -05:00
Scott Idem
34a752d455 feat(api-v3): implement permissive updates, automatic ID resolution, and structured error reporting
- Added 'x-ae-ignore-extra-fields' header to support stripping unknown fields in POST/PATCH.
- Added automatic resolution of '*_id_random' strings to integer IDs in 'sanitize_payload'.
- Refactored 'post_obj' to return structured (field -> message) validation errors in 'meta.details'.
- Updated 'mk_resp' to support non-string 'details' in response metadata.
- Added 'tests/verify_feedback_fixes.py' to validate logic changes.

Ref: V3 API Refinement Feedback from mcp_agent.
2026-01-14 19:11:56 -05:00
Scott Idem
722409de0b Saving notes 2026-01-13 14:35:10 -05:00
Scott Idem
19a9890dd9 Better bug fix for working SQL test. 2026-01-13 14:21:56 -05:00
Scott Idem
f9a51e243f More clean up of old routes 2026-01-13 14:15:17 -05:00
Scott Idem
6346d4ccd6 Commenting out a bunch of old routes. Hopefully none of them are used by anything still out there.... 2026-01-13 14:12:07 -05:00
Scott Idem
ed3dda6cf5 Bug fixes for SQL testing 2026-01-13 13:57:18 -05:00
Scott Idem
8927f07bcf Added some extra print debugs for now. 2026-01-12 20:30:45 -05:00
Scott Idem
5ce193d474 Saving more note updates 2026-01-09 17:10:25 -05:00
Scott Idem
4b86432381 Enhance V3 CRUD: Implement Error Bubbling and Dry-Run Validation.
- Updated app/db_sql.py to capture SQL exceptions in thread-local storage for later retrieval.
- Implemented format_db_error() in app/lib_api_crud_v3.py to clean up raw MariaDB error strings.
- Added POST /v3/crud/{obj_type}/validate endpoint for dry-run payload validation.
- Updated main and nested routers to bubble up validation and database errors into the response 'meta.details' field.
- Added tests/test_v3_error_bubbling.py to verify formatting logic.
2026-01-09 16:57:54 -05:00
Scott Idem
3885cc6aba Refactor V3 CRUD: Extract schema introspection logic.
- Created app/lib_schema_v3.py to isolate database and Pydantic model introspection.
- Updated app/routers/api_crud_v3.py to use get_object_schema_info(), completing the modularization.
- Finalized refactoring plan documentation in documentation/REFACTOR_API_CRUD_V3.md.
2026-01-09 16:29:10 -05:00
Scott Idem
812181acb5 Refactor V3 CRUD: Extract nested child routes into separate router.
- Created app/routers/api_crud_v3_nested.py to handle all parent-child relational routes.
- Updated app/routers/api_crud_v3.py to include the nested router, significantly reducing file size.
- Documented Phase 2 completion in documentation/REFACTOR_API_CRUD_V3.md.
2026-01-09 16:23:14 -05:00
Scott Idem
8459b57e1b Refactor V3 CRUD: Extract helper functions and unify sanitization logic.
- Created app/lib_api_crud_v3.py to house core security, filtering, and sanitization logic.
- Implemented reusable sanitize_payload() to generically strip virtual lookup fields (*_id_random) and view-only fields (fields_to_exclude_from_db).
- Updated app/routers/api_crud_v3.py to use the new library and consolidated sanitization across all Create/Update endpoints.
- Documented Phase 1 completion in documentation/REFACTOR_API_CRUD_V3.md.
2026-01-09 16:16:44 -05:00
Scott Idem
2ff211f2c2 Update API documentation and finalize model validators/mappings.
- Added comprehensive docstrings to api_crud_v3.py explaining multi-tenancy, sanitization, and soft-delete logic.
- Finalized Address and Contact models/mappings with correct validators and field maps.
- Consolidated test suite in tests/ directory.
2026-01-09 15:52:00 -05:00
Scott Idem
8dc37f274f Just saving changes 2026-01-09 15:40:46 -05:00
Scott Idem
4c83e02c4a Update V3 CRUD router and object definitions.
- Added 'external_person_id' to Post searchable fields.
- Updated api_crud_v3.py to respect 'fields_to_exclude_from_db' model attribute.
- Cleaned up old verification scripts (moved to tests/).
2026-01-09 15:36:50 -05:00
Scott Idem
1c0922ace2 Enhance API robustness: Add model validators, view-field filtering, and test suite.
- Added validators to Person_Base, Journal_Base, Journal_Entry_Base, Address_Base, and Contact_Base to handle null values and unsafe lookups.
- Implemented 'fields_to_exclude_from_db' ClassVar in Journal models to prevent view-only fields from causing DB errors.
- Updated Contact object map to align with DB schema.
- Added comprehensive test suite in 'tests/' directory (model validation, filtering logic).
- Updated GEMINI.md with progress.
2026-01-09 15:36:28 -05:00
Scott Idem
29b4d5ae4b Fix Person creation issues and enhance V3 CRUD robustness.
- Added Pydantic validators to Person_Base to handle null values for given_name and allow_auth_key, ensuring database NOT NULL constraints are met.
- Updated api_crud_v3.py (POST and PATCH) to filter out virtual *_id_random fields from data payloads before database operations to prevent "Unknown column" errors.
- Updated GEMINI.md with session progress.
2026-01-09 14:30:45 -05:00
Scott Idem
d32304c50a Saving notes 2026-01-08 19:27:09 -05:00
Scott Idem
46a3998fe0 Saving Gemini notes 2026-01-08 15:40:55 -05:00
Scott Idem
765949fdfd Cleanup: Remove temporary scripts from local root. 2026-01-08 12:56:34 -05:00
Scott Idem
1bcc6dae3f Cleanup: Remove ae_object_info.py from local root after relocation. 2026-01-08 12:56:01 -05:00
Scott Idem
9ee2ed444b Docs: Update GEMINI.md with MCP tool registration issue. 2026-01-08 12:55:55 -05:00
Scott Idem
802c75bad9 V3: Standardize Primary AE Objects and Synchronize Search Whitelists.
- Synchronized searchable_fields (V3 whitelists) across all Primary and Active AE objects (Identity, People, Events, Journals, Posts, Archives, Business).
- Standardized Pydantic models for core objects to include the 10 common fields (id, id_random, enable, hide, priority, sort, group, notes, created_on, updated_on).
- Fixed field aliases and uncommented valid database columns in User_Base and Organization_Base.
- Pruned non-existent fields from searchable lists in legacy or config-specific definitions (account_cfg, user_role, log_client_viewing).
- Added system discovery and validation tools:
    - ae_object_info.py: AE object status and metadata browser.
    - export_all_interfaces.py: E2E TypeScript interface generator.
    - Verification scripts for searchable field consistency.
- Updated Jan 8 milestone progress and platform roadmap in GEMINI.md.
2026-01-08 12:24:34 -05:00
Scott Idem
59d5b81da0 Arch: Finalize V3 Auth modularization and Unified Agent spec.
- Integrated zero-dependency Auth models and dependencies_v3.py.
- Successfully resolved circular dependency boot loops.
- Verified site_domain search exception via verify_v3_exceptions.py.
- Refined Unified Agent Architecture with Storage Layer and API-driven access details.
- Updated project roadmap and milestones in GEMINI.md.
2026-01-07 19:07:21 -05:00
Scott Idem
90c6b914fa Docs: Update Unified Agent Architecture and Platform Roadmap. 2026-01-07 18:53:04 -05:00
Scott Idem
d4805ebb09 Cleanup: Remove unused imports in lib_general.py after extraction. 2026-01-07 17:43:43 -05:00
Scott Idem
734576817c Refactor: Modularize lib_general.py by extracting core functionalities.
- Extracted Email functions to app/lib_email.py.
- Extracted Export functions to app/lib_export.py.
- Extracted JWT utilities to app/lib_jwt.py.
- Extracted Hash utilities to app/lib_hash.py.
- Updated app/lib_general.py to import from these new modules for backward compatibility.
- Updated V3 Frontend API Guide with latest security and site_domain exception details.
2026-01-07 17:41:04 -05:00
Scott Idem
d61dd0f00e Restored V3 search and implemented site_domain exception via dependency isolation.
- Implemented 'Isolation Mode' in api_crud_v3.py to bypass circular dependencies.
- Locally defined AccountContext and auth dependencies to ensure stable boot.
- Added site_domain lookup exception for guest users in search endpoint.
- Maintained agent_bridge disablement in main.py for stability.
2026-01-07 17:08:52 -05:00
Scott Idem
6937f9dca4 Saving these changes in a working state. Just in case. 2026-01-07 16:54:56 -05:00
Scott Idem
caf2868d02 Saving things while they work again!!! Still working on adding a special exception or something for site domain search. 2026-01-07 16:25:18 -05:00
Scott Idem
cf96d93246 fix: import SearchFilter in api_crud_v3.py to resolve NameError 2026-01-07 14:25:58 -05:00
Scott Idem
6d13b952c4 Implement V3 API security hardening and multi-tenant data isolation
- Enhanced AuthContext with role-aware fields (administrator, manager, super).
- Implemented deferred database lookups for user roles in get_v3_auth_context.
- Added global account isolation in api_crud_v3.py using check_account_access and apply_forced_account_filter.
- Hardened all V3 CRUD endpoints (GET, POST, PATCH, DELETE) and nested routes with ownership verification.
- Enforced forced account filtering at the SQL level for Listing and Searching.
- Updated documentation with details on the new security and data isolation architecture.
2026-01-07 13:34:38 -05:00
Scott Idem
270712f905 Another quick snapshot in case something breaks again. There are issues with this new agent bridge and the psutil and Gemini CLI. Not sure, but it causes problems. 2026-01-07 13:10:16 -05:00
Scott Idem
7fb2f00846 Things are currently working. At least 90% sure they are. 2026-01-07 12:24:52 -05:00
Scott Idem
c47ae47a2f Add agent_bridge.py administrative endpoints and mcp_docker_explorer.py script
- Implemented /status, /system/usage, /logs, /logs/list, /processes, and /container/metadata in agent_bridge.py.
- Added mcp_docker_explorer.py for Docker MCP integration testing.
- Enhanced administrative access checks in agent_bridge.py.
2026-01-07 12:01:48 -05:00
Scott Idem
75b771f87c feat: add 'archive_on' to searchable_fields for posts and update agent bridge auth logic 2026-01-07 11:07:45 -05:00
Scott Idem
ec4656eca9 Bug fix to make lookup_id_random_pop work again. 2026-01-07 10:02:57 -05:00
Scott Idem
13620a63d0 Save notes and documentation updates 2026-01-06 19:36:58 -05:00
Scott Idem
836ed97d07 feat(agent): implement Agent Bridge for secure diagnostics 2026-01-06 19:00:03 -05:00
Scott Idem
6470af0a01 feat(v3): populate searchable_fields for all remaining object definitions 2026-01-06 18:18:39 -05:00
Scott Idem
c33ae332e9 refactor(sql): clean up db_sql.py by removing commented-out code and consolidating logic 2026-01-06 18:12:51 -05:00
Scott Idem
a6ec6d1b2b Saving our work just in case. 2026-01-06 18:06:45 -05:00
Scott Idem
55033d0749 feat(events): add conference to searchable fields and update progress 2026-01-06 18:03:57 -05:00
Scott Idem
868a0060dc refactor(sql): complete modularization of search builders and ID resolution 2026-01-06 17:58:34 -05:00
Scott Idem
56fe7ed953 refactor(sql): modularize Redis and ID lookup functions 2026-01-06 17:32:22 -05:00
Scott Idem
a6a5162385 refactor(sql): modularize status and where query builders 2026-01-06 17:24:47 -05:00
Scott Idem
b5e874bd99 refactor(sql): modularize basic search query builders 2026-01-06 17:22:10 -05:00
Scott Idem
d584457997 fix(legacy): resolve 422 error on site domain lookup and enhance V3 filtering 2026-01-06 16:29:09 -05:00
Scott Idem
459bd89198 feat(v3): implement schema discovery endpoint 2026-01-06 16:03:54 -05:00
Scott Idem
45f6303219 feat(v3): robust search wildcards, smart status filtering, and fixed ID population 2026-01-06 15:54:31 -05:00
Scott Idem
a42f32acf4 Added more documentation. Improved CRUD V3 endpoints and better responses. 2026-01-06 13:52:05 -05:00
Scott Idem
9c06b07665 Saving changes now that most things have been migrated to CRUD V3 and appear to be working. This still needs testing though. 2026-01-06 13:11:03 -05:00
Scott Idem
552ca31603 Adding more searchable_fields for security. Broke up large files to make them easier to manage. 2026-01-06 11:14:37 -05:00
Scott Idem
b8a417a5d7 Key Accomplishments:
1. Badge Model Updates: Added print_count, print_first_datetime, and print_last_datetime to the
      Event_Badge_Basic_Base model.
   2. Soft Delete in V3 CRUD: Implemented a method query parameter (delete, hide, disable) for the DELETE
      endpoints in api_crud_v3.py.
   3. Security Hardening: Populated searchable_fields allowlists for all objects across the V3 CRUD
      definitions (core.py, events.py, orders.py, cms.py, lookups.py, membership.py, other.py).
   4. Shared Documentation: Created /home/scott/agents_sync/Aether/api_v3.md to coordinate these V3 API
      features with the Svelte agent and other tools.
2026-01-05 20:05:33 -05:00
Scott Idem
314a031dd1 Now with some soft delete options for safer operations. 2026-01-05 19:49:28 -05:00
Scott Idem
3790983b5e Quick update to include some more fields. 2026-01-05 19:24:17 -05:00
Scott Idem
872279de0b One last round of testing and documentation updates. 2026-01-02 21:03:32 -05:00
Scott Idem
f5ab2118ad Fix: Enhance V3 Search with 'contains', 'startswith', 'endswith' operators and improve error reporting. 2026-01-02 20:42:19 -05:00
Scott Idem
f865b1cfb7 Security: Implement modern JWT authentication for V3 CRUD and Search; update documentation and to-do list. 2026-01-02 20:26:44 -05:00
Scott Idem
53d252b23d Fix: Add robust JSON parsing for V3 query params and fix missing Any import causing startup failure. 2026-01-02 20:24:51 -05:00
Scott Idem
09ec231303 Security: Implement recursion depth limits and field allowlists for Advanced Search; add reference SQL exports. 2026-01-02 19:38:37 -05:00
Scott Idem
5a4c82e4cb Cleanup: Comment out unused cont_edu_cert routers in main.py. 2026-01-02 19:16:25 -05:00
Scott Idem
81af707091 Refactor: Modularize object definitions and implement V3 Search beta recommendations. 2026-01-02 19:16:06 -05:00
Scott Idem
bf16f988c5 Saving recommended updates by the Svelte Gemini agent. 2026-01-02 18:57:37 -05:00
Scott Idem
8c0be931c0 Added samples for Svelte side API code and a new CRUD V3 API guide. 2026-01-02 18:06:20 -05:00
Scott Idem
9b8052149a Saving these SQL export examples. 2026-01-02 17:53:59 -05:00
Scott Idem
bd2739eb13 Refactor: Modularize object definitions and migrate event-related objects to V3 CRUD. 2026-01-02 17:53:35 -05:00
Scott Idem
2f24a5588b Feature: Implement advanced POST-based search with recursive logical grouping and unique parameterization (Verified Working). 2026-01-02 17:09:29 -05:00
Scott Idem
7b9ec69e7b Refactor: Add legacy V2 support to modern object definitions and document V3 architecture. 2026-01-02 16:14:41 -05:00
Scott Idem
95f58e3b4d Another quick save before we start working on documentation and specialized endpoints. 2026-01-02 16:10:06 -05:00
Scott Idem
c1353fc971 More work on getting things working and ready for my CRUD v3 stuff. This may have been related to import loops or something. 2026-01-02 15:48:08 -05:00
Scott Idem
4a62eecf83 Work after logging related fixes. 2026-01-02 15:17:43 -05:00
Scott Idem
6d60af23c3 Update to get activity log working for CRUD v2 queries. 2025-12-16 14:40:06 -05:00
Scott Idem
80bb4b296f Restored lost file 2025-12-03 20:47:47 -05:00
Scott Idem
3ec509ec2e docs: Update GEMINI.md with session learnings 2025-12-03 20:47:15 -05:00
Scott Idem
4598256c7c Reverted to known working version and preserved new file changes in snapshots. 2025-12-03 20:43:47 -05:00
Scott Idem
98b980cf2b The basics are now working for v3. 2025-12-03 18:44:14 -05:00
Scott Idem
d0654e9f37 Another quick save. Looking pretty so far. 2025-12-03 18:35:40 -05:00
Scott Idem
8f3a38cb0d WARNING: This is where Gemini is starting to work on the version 3 of the CRUD catch all endpoints. This seems like a good start. 2025-12-03 18:16:11 -05:00
Scott Idem
b1d05c7e66 A quick version update for the god like catch all CRUD endpoints. Version 3 will be even better! 2025-12-03 17:58:58 -05:00
Scott Idem
0e41205472 Cleaned up the aud field. We think this part is correct finally. 2025-12-03 15:48:57 -05:00
Scott Idem
3394ebcdad Again... 2025-12-03 15:40:06 -05:00
Scott Idem
36ae9c5035 We think it might work now... Gemini thinks the aud may have been set incorrectly. Not matching the actual Jitsi server. 2025-12-03 15:38:14 -05:00
Scott Idem
c5d25b5717 More work on the Jitsi JWT 2025-12-03 15:25:30 -05:00
Scott Idem
9ea7d3ef27 New files related to Gemini and update plans. 2025-12-03 04:50:47 -05:00
Scott Idem
e40b01d276 Another quick bug fix. 2025-12-02 18:50:50 -05:00
Scott Idem
38455d4549 Bug fix!!! 2025-12-02 18:46:19 -05:00
Scott Idem
5535b1af34 Try try again... Jitsi JWT.... 2025-12-02 18:36:56 -05:00
Scott Idem
412277b3a7 Another update. Still not working right though... 2025-12-02 18:15:07 -05:00
Scott Idem
ac41aec71c Changed settings to features 2025-12-02 17:41:34 -05:00
Scott Idem
1a315483eb Jitsi JWT settings trying again. 2025-12-02 17:17:19 -05:00
Scott Idem
8891a51c2e Work on Jitsi JWT 2025-12-02 17:08:16 -05:00
Scott Idem
602113242d Now with badge template models linked 2025-10-07 19:40:44 -04:00
Scott Idem
17a64a719b Updates related to badges demo. 2025-10-07 03:27:19 -04:00
Scott Idem
ef9042fe20 New Jitsi tokens 2025-09-19 18:13:09 -04:00
Scott Idem
ce2dc1c2dc Less debug. A few new event fields for IDAA meetings. 2025-07-14 15:29:06 -04:00
Scott Idem
6d04a8ac19 New link and sync function. It is mostly good to go, but could probably use more testing and improvements. 2025-06-24 18:48:38 -04:00
Scott Idem
882c740880 Finally getting rid of the old display_name fields. Trying to use full_name and full_name_override everywhere. 2025-06-16 19:22:02 -04:00
Scott Idem
f124018125 Deal with SQL MATCH better 2025-06-09 14:43:30 -04:00
Scott Idem
b489b72ff5 More fields... related to encryption 2025-05-15 11:13:58 -04:00
Scott Idem
99a200907a Less debug when on delete 2025-05-07 18:08:25 -04:00
Scott Idem
5f0d9d728b Less debug 2025-05-07 17:47:09 -04:00
Scott Idem
f817773338 Added the pool_recycle back in and it will default to 1800 based on Docker aether_api_config.py. Added a retry on operational exception. 2025-05-07 17:36:38 -04:00
Scott Idem
edcde83323 A few new fields for the Journals. Content history and track passcode used for encryption. 2025-05-05 12:16:29 -04:00
Scott Idem
8bd5fd2106 Updated the user auth and user auth key email endpoints and functions. 2025-04-08 15:34:58 -04:00
Scott Idem
573f054ee2 Updates to get User auth working again 2025-04-04 17:35:10 -04:00
Scott Idem
8569a5de3c One more field... 2025-04-02 13:38:18 -04:00
Scott Idem
b3ce129bce Added a few more fields for Journals 2025-04-02 11:31:59 -04:00
Scott Idem
c4b9396f52 Updates before BGH 2025 2025-03-25 15:27:15 -04:00
Scott Idem
579ae9bd96 Updating the models related to Journals 2025-03-20 11:58:59 -04:00
Scott Idem
0871985f08 Updates to handle scaling videos with ffmpeg. 2025-03-18 17:34:44 -04:00
Scott Idem
78e866492f Updates to start using the new Journals module. 2025-03-16 02:48:05 -04:00
Scott Idem
1f2046c6ad Added in some new old fields for events. This is mainly for IDAA. 2025-02-04 10:51:16 -05:00
Scott Idem
1a3102a19a Updates related to time zones, countries, and subdivisions lookups. 2025-01-13 15:27:37 -05:00
Scott Idem
8678e33ec2 Less debug for Redis checks 2025-01-08 12:32:23 -05:00
Scott Idem
42175b89c0 Updated the hosted_file fields. 2025-01-07 19:30:06 -05:00
Scott Idem
a96a6ebf5d Minor improvement to the saving of a hosted file. Guessing the content type based on the extension. 2025-01-07 18:09:25 -05:00
Scott Idem
78ce11a30d Improved the video file clip function 2025-01-07 15:45:19 -05:00
Scott Idem
c78afbbc5c Added new fields for posts related. Also cleaned up some of the code and logging. 2024-11-13 17:48:14 -05:00
Scott Idem
94a5a7386b Commented out old code. 2024-11-07 19:37:06 -05:00
Scott Idem
9aec3455f6 More missing fields... 2024-11-07 13:51:21 -05:00
Scott Idem
b4a058f0ca Added some missing fields... 2024-11-07 13:47:15 -05:00
Scott Idem
dcad4b70e9 Minor update to the archive content model to include more hosted file info. Specifically the hash is needed. 2024-11-06 12:29:34 -05:00
Scott Idem
e5d27a536c Updated to include standard fields. 2024-10-23 01:37:13 -04:00
Scott Idem
db08388ce7 Updating the Activity Log functionality. 2024-10-23 00:53:18 -04:00
Scott Idem
5499070a4f Enabled event_device for CRUD v1... Adding new fields. 2024-10-17 19:23:21 -04:00
Scott Idem
792fb153e3 New status related fields for devices. 2024-10-17 14:06:16 -04:00
Scott Idem
c1ff6737f4 Now with source_code field... 2024-10-10 13:21:10 -04:00
Scott Idem
f42c1e11eb Updated ignore files 2024-10-09 13:42:31 -04:00
Scott Idem
53b395bd98 Update to ignore more files and directories. 2024-10-09 12:11:19 -04:00
Scott Idem
1c91c92d67 Better logging. Less logging. 2024-10-09 11:17:25 -04:00
Scott Idem
3bf54fcb47 Less debug 2024-10-09 10:38:24 -04:00
Scott Idem
30c39d58dd Trying to make the SQL connections more reliable with threads. 2024-10-08 18:45:53 -04:00
Scott Idem
50955aff3a Working on making things more reliable... WS and DB connections 2024-10-08 18:20:27 -04:00
Scott Idem
c798b4659f Less debug 2024-10-08 14:03:00 -04:00
Scott Idem
e6e7275de0 Less debug and info messages to log 2024-10-08 13:47:43 -04:00
Scott Idem
39a1c05df1 Less likely to trigger checking the JP param. 2024-10-08 13:29:27 -04:00
Scott Idem
cd0d3fe9d5 Bug fix for missing ID. Less debug as well. 2024-10-08 12:52:48 -04:00
Scott Idem
e13ce42cac Less debug 2024-10-02 11:42:14 -04:00
Scott Idem
aade8504fa Minor fixes. Cleaned up logging and send_email test mode. 2024-10-01 15:04:12 -04:00
Scott Idem
58e331f85c Update the post and post_comment 2024-09-27 18:57:00 -04:00
Scott Idem
2cddc9bcd7 Made the access_code list a real field in JSON format 2024-09-16 16:14:15 -04:00
Scott Idem
40c2c34678 Adding in a new poc_agree field or sessions. Seems reasonable. 2024-09-12 20:21:34 -04:00
Scott Idem
7a8648cd99 Updated external_id field naming to be more consistent. 2024-09-03 12:30:31 -04:00
Scott Idem
93c5a188f0 Added new ux_mode field. Always one more... Removed some others and replaced with future JSON though. 2024-08-16 14:54:01 -04:00
Scott Idem
0e110d69f2 Small workaround to limit type code to 25 characters. The database table won't update correctly or something. 2024-08-16 14:43:22 -04:00
Scott Idem
2ef883984d Updates to the event program data import script. 2024-08-15 09:39:15 -04:00
Scott Idem
de35856749 Updating event device 2024-08-15 08:42:54 -04:00
Scott Idem
18293764fd Work on CRUD v2 and better SQL WHERE part building... I hope. 2024-08-14 14:36:07 -04:00
Scott Idem
3d48220b8f Updated the event model. Now time to deal with the views... 2024-08-09 17:49:11 -04:00
Scott Idem
97f0a59fcf Updating the models and less log info 2024-08-09 17:47:33 -04:00
Scott Idem
34dfea1379 Re-enabled the validator for for_id_random value missing. Probably for the 200th time... Made some changes so that it hopefully works correctly and does not break anything. 2024-08-09 16:01:15 -04:00
Scott Idem
394c2d1d94 Update models to include file id list. 2024-08-09 15:28:18 -04:00
Scott Idem
30f3aaea27 Added more object type maps. Spelling fix in comment. 2024-08-09 14:39:20 -04:00
Scott Idem
d6b9b0b950 Wrapping up for the day. More maps. Done? 2024-07-31 19:48:27 -04:00
Scott Idem
b78d93a056 Slight bug fix for the general purpose SQL SELECT function. Also adding more maps. 2024-07-31 19:33:10 -04:00
Scott Idem
5d599b28fe Even more mappings. 2024-07-31 18:44:15 -04:00
Scott Idem
aec9271e07 Adding more maps 2024-07-31 18:10:59 -04:00
Scott Idem
d063675736 Documentation... 2024-07-31 17:47:42 -04:00
Scott Idem
4b7f924f7d Still working on obj conversion. 2024-07-31 17:46:04 -04:00
Scott Idem
e475ec6686 Working on conversion to the obj table and everything related to that. 2024-07-31 17:23:28 -04:00
Scott Idem
4145f81850 Fixed some JSON fields 2024-07-24 17:46:57 -04:00
Scott Idem
8d7f18c734 Added some missing fields. 2024-07-24 17:16:03 -04:00
Scott Idem
0d1afdc900 Added yet another field for quick access 2024-07-24 13:51:45 -04:00
Scott Idem
92c4646ca9 Fixed external_id alias 2024-07-17 16:14:14 -04:00
Scott Idem
a1be67d8d3 More new convenience fields for person_model 2024-07-17 12:29:47 -04:00
Scott Idem
9a01c1c2b0 More additional fields for event_file model. 2024-07-12 16:24:36 -04:00
Scott Idem
f392e5020a More fields from person table 2024-06-25 10:32:17 -04:00
Scott Idem
d181cd1552 Added a few extra presenter fields related to person table 2024-06-25 09:10:12 -04:00
Scott Idem
aa3033e1f4 Bug fix for event_presenter query with Excel export 2024-06-24 16:31:42 -04:00
Scott Idem
c08cffecd4 One more field... 2024-06-21 16:43:27 -04:00
Scott Idem
eb1ef32754 Less debug 2024-06-21 15:04:18 -04:00
Scott Idem
175c84b1a6 Now with the ability to do OR with LIKE 2024-06-21 14:46:19 -04:00
Scott Idem
4d3c75dbcf Minor changes hopefully. Related to event_file tbl_name_update _simple. 2024-06-20 18:47:36 -04:00
Scott Idem
cfe9dee433 Forgot to add new email and passcode fields 2024-06-20 12:19:12 -04:00
Scott Idem
b91f9f1f04 Updates to the models to include passcode and related. 2024-06-18 18:28:42 -04:00
Scott Idem
8ed0ab8413 Minor updates for model 2024-06-17 16:54:15 -04:00
Scott Idem
7b0af5e7c5 Changing li (list)t to kv (key value) 2024-06-17 15:54:48 -04:00
Scott Idem
7b92f7760d Added new POC fields 2024-06-17 15:51:33 -04:00
Scott Idem
d7ca2c428a Update to use more detailed event_file view. 2024-06-14 12:48:33 -04:00
Scott Idem
e45981d499 Minor changes 2024-06-13 23:16:09 -04:00
Scott Idem
35f07a7993 Now with person_id 2024-06-11 19:36:10 -04:00
Scott Idem
afe127d9fe More model property updates 2024-06-11 18:24:25 -04:00
Scott Idem
19082a7a10 Adding cfg_json and data_json fields to more models 2024-06-11 13:35:00 -04:00
Scott Idem
6691f2a701 Updating the models to include a standard passcode field. 2024-06-10 19:40:35 -04:00
Scott Idem
52be61570a Keeping the object types in sync 2024-06-10 19:08:40 -04:00
Scott Idem
ea81d619be Updating things for LCI 2024-06-10 19:00:35 -04:00
Scott Idem
9140455795 Getting ready for LCI importing of pre program data 2024-06-09 18:31:16 -04:00
Scott Idem
a1579e62c5 Updates to models and related 2024-05-24 19:05:00 -04:00
Scott Idem
fc86d826e9 Commenting out the base_fields for *_id_random in the models 2024-05-24 15:45:43 -04:00
Scott Idem
0762ffcef8 Updates related to data file exports. 2024-05-23 10:41:09 -04:00
Scott Idem
6c5b120526 Increased the maximum filename limit to 255. No consistent with the DB and standards. 2024-05-22 15:54:47 -04:00
Scott Idem
a8aa6bf950 Updates and clean up. No longer uses 100% CPU. 2024-05-21 18:47:57 -04:00
Scott Idem
3d13dc1829 Fixed incorrect min length 2024-05-15 10:39:01 -04:00
Scott Idem
8c0f308694 Improved handeling of co-presenters 2024-05-15 00:11:58 -04:00
Scott Idem
de822fb1ba Now imports the session type_code data 2024-05-07 10:21:07 -04:00
Scott Idem
da86206a24 Improving the CRUD v2 file export 2024-05-03 14:49:55 -04:00
Scott Idem
5a107031e6 Minor change 2024-04-26 18:31:50 -04:00
Scott Idem
25a7c3ef20 Get rid of env file... 2024-04-26 18:14:48 -04:00
Scott Idem
1a3e375523 event_id should be a URL param 2024-04-26 18:10:32 -04:00
Scott Idem
d3f5f51458 Slowly getting things back to normal after FastAPI upgrade 2024-04-26 17:43:56 -04:00
Scott Idem
e38b3cfe7a Trying to split up router functions... 2024-04-26 16:16:00 -04:00
Scott Idem
faecd974b9 Lots of changes to get to FastAPI 95.1 2024-04-26 15:15:37 -04:00
Scott Idem
f4eda34035 Saving current progress with change from using Query() to Path() 2024-04-26 14:51:11 -04:00
Scott Idem
b37f14d25c Slow but steady progress to update all end points... 2024-04-26 14:17:46 -04:00
Scott Idem
d3f26f1696 Upgrading to fastapi .95.1 2024-04-26 13:49:48 -04:00
Scott Idem
0745ac2fd4 Minor changes and updates for AAPOR with Confex 2024-04-25 16:16:15 -04:00
Scott Idem
a41f4f0a33 Trying with READ UNCOMMITTED 2024-04-23 19:58:27 -04:00
Scott Idem
c73725170e Changed the isolation_level to REPEATABLE READ from READ COMMITTED. REPEATABLE READ is the default. 2024-04-23 19:53:35 -04:00
Scott Idem
9c45bea785 Working on bug fix for columns that are empty in first rows. 2024-04-23 18:53:37 -04:00
Scott Idem
d82c1750fd Forgot to comment out old version 2024-04-23 17:44:31 -04:00
Scott Idem
4c87e4a5fc General bug fixes and clean up. Starting on a better version 2 of the CRUD endpoints. 2024-04-23 16:19:00 -04:00
Scott Idem
dd527378bb Update for AAPOR 2024 2024-04-22 19:32:32 -04:00
Scott Idem
f42ce95f60 Bug fix for custom question responses 2024-04-22 12:30:16 -04:00
86b2938a53 Bug fix 2024-04-11 06:06:11 -04:00
7d955ff90f Clean up and less debug 2024-04-07 17:31:35 -04:00
f0401d8fda Work on LIKE part of query 2024-04-07 13:59:56 -04:00
00471df086 Now with the ability to handle multiple custom question in the data export 2024-04-07 11:57:44 -04:00
Scott Idem
2514106476 Just work 2024-03-29 19:13:49 -04:00
Scott Idem
aee0b7dbbf Work on SQLAlchemy settings 2024-03-29 14:49:42 -04:00
Scott Idem
8c3786947e Better debug info 2024-03-29 13:14:35 -04:00
Scott Idem
257edec1a7 Now with a new SQL check for ProgrammingError. This is related to multithreading usually. 2024-03-29 12:58:34 -04:00
Scott Idem
76d5d4c94d Minor changes 2024-03-28 12:56:24 -04:00
Scott Idem
0af9c4a76e New data fields for badge model 2024-03-26 11:43:31 -04:00
Scott Idem
80b218c816 Minor improvements for importing 2024-03-25 11:48:10 -04:00
Scott Idem
83aa943410 Minor changes and updates. 2024-03-22 19:35:50 -04:00
Scott Idem
0dd3bbea73 Now handles URL params with a list for IN part of SQL SELECT 2024-03-15 16:19:30 -04:00
Scott Idem
3d3162e4a0 Exhibits now have enable and hide. 2024-03-12 17:58:33 -04:00
Scott Idem
14c6cb8bc0 Work on CRUD POST and PATCH related. 2024-03-08 18:10:10 -05:00
Scott Idem
eff1da6644 Minor changes... I guess 2024-03-08 00:24:26 -05:00
Scott Idem
f97c147ddd Minor changes. Would like to add return_obj param, but not sure how yet. 2024-03-07 11:03:57 -05:00
Scott Idem
3eff873d3a Changed the views to use 2024-03-06 19:53:43 -05:00
Scott Idem
45d14cc7b2 Bug fix for null results SQL SELECT 2024-03-06 19:51:38 -05:00
Scott Idem
852df91c7a Less debug 2024-03-05 19:27:38 -05:00
Scott Idem
9d4184e3ad Minor changes related to data_store 2024-03-05 16:59:54 -05:00
Scott Idem
16f3c65b7f Addding in ds type and access fields 2024-03-05 11:50:03 -05:00
Scott Idem
e48ba7e938 Add new JSON fields 2024-03-04 15:52:59 -05:00
Scott Idem
ed83742cf8 Now wish more cfg_json 2024-03-04 13:44:50 -05:00
Scott Idem
12af90bacc Re-adding a field. website_url 2024-03-01 15:38:28 -05:00
Scott Idem
31a45c1b5c A couple new fields for sponsorships 2024-03-01 09:04:29 -05:00
Scott Idem
99d24524d1 Now with an agreement field 2024-02-29 15:26:49 -05:00
Scott Idem
f5ef362242 Changing to full_name_override from display_name 2024-02-29 15:16:07 -05:00
Scott Idem
929a2749f7 Updates to presenter for new fields with JSON 2024-02-29 14:26:51 -05:00
Scott Idem
5cafd35bda Bug fix for old directory_path that is no longer used. 2024-02-28 18:25:56 -05:00
Scott Idem
3efc55676e Bug fix for sponsorships. Typos 2024-02-28 14:55:01 -05:00
Scott Idem
4a05a30848 Minor changes. Less debug 2024-02-26 16:23:09 -05:00
Scott Idem
8c774733ef Updates for site domain models 2024-02-22 20:14:10 -05:00
Scott Idem
9d35418251 Minor changes to site domain related. 2024-02-22 19:12:42 -05:00
Scott Idem
b54e3bdf10 Minor change in case I need to use a different site_domain view 2024-02-22 17:43:07 -05:00
Scott Idem
fbf9c97247 Minor change for sponsorship model 2024-02-21 13:12:21 -05:00
Scott Idem
d6787f9855 Minor changes. Temporarily disabled redis... 2024-02-20 19:14:05 -05:00
Scott Idem
dca4175659 Ooops 2024-02-14 18:16:09 -05:00
Scott Idem
ababdc7a46 Less debug... 2024-02-14 18:11:57 -05:00
Scott Idem
9c92818ff9 Minor changes for testing. 2024-02-14 18:05:26 -05:00
Scott Idem
cd252b9de3 New Sponsorships module. Related updates. 2024-02-08 20:25:13 -05:00
Scott Idem
2e666e89e9 ISHLT import updates 2024-02-02 11:11:42 -05:00
Scott Idem
21bc458743 New ISHLT badge type for workshops 2024-02-02 10:49:20 -05:00
Scott Idem
a0f850cbad Update for IDAA with archive content sorting and archive configs and event configs 2024-01-25 19:12:14 -05:00
Scott Idem
59ba573947 Update for ISHLT Impexium import 2024-01-24 18:28:34 -05:00
Scott Idem
645fb16cb1 Minor update to model 2023-12-31 12:28:56 -05:00
Scott Idem
00e9625c34 Added an extra field for posts. Topic name 2023-12-22 15:04:12 -05:00
Scott Idem
55d953b445 Improved API CRUD POST and PATCH handeling of data validation errors. 2023-12-19 12:30:51 -05:00
Scott Idem
be6d361bf7 Updates related to archives changes. 2023-12-14 18:17:18 -05:00
Scott Idem
69ec843bf0 Minor update to post comment model 2023-12-06 15:13:22 -05:00
Scott Idem
d590485f50 Updates to event model and minor clean up 2023-12-05 17:24:59 -05:00
Scott Idem
f5178cf625 Clean up and less debug 2023-12-01 14:06:49 -05:00
Scott Idem
4b6c048eaf Updated API CRUD and SQL SELECT related functions. They can now handle multiple ANDs! 2023-11-30 19:59:38 -05:00
Scott Idem
6d1cc6c1ff Updated API CRUD and SQL SELECT related functions. They can now handle full text searching! 2023-11-29 18:24:39 -05:00
Scott Idem
ce4be9f5e2 Less debug 2023-11-15 19:09:22 -05:00
Scott Idem
ba284ca60d Update to CRUD to allow for alt table and base 2023-11-15 19:08:05 -05:00
Scott Idem
bb962f1708 Update to event model to include external_person_id for IDAA and Novi integration 2023-11-15 17:08:53 -05:00
Scott Idem
51991df213 Update to Redis function for looking up id_random values. 2023-11-15 10:34:50 -05:00
Scott Idem
5f35de8b92 Update to Redis function for looking up id_random values. 2023-11-15 10:34:04 -05:00
Scott Idem
02372a6684 Update to Redis function for looking up id_random values. Updates related to IDAA and posts 2023-11-15 10:29:33 -05:00
Scott Idem
070c75a8c6 Updates related to IDAA and posts 2023-11-13 18:22:46 -05:00
Scott Idem
7423e66fec New video clipping util 2023-11-10 19:17:02 -05:00
Scott Idem
6268d04d16 New video clipping util 2023-11-10 18:51:47 -05:00
Scott Idem
243c07101e New audio to video conversion tool is ready 2023-11-10 17:29:58 -05:00
Scott Idem
1cfd2421bd Finalizing the new audio to video file converter 2023-11-10 15:54:04 -05:00
Scott Idem
be6fc9ffb5 Creating new audio to video converter 2023-11-10 15:17:14 -05:00
Scott Idem
da1e959be2 Updates to CRUD... 2023-11-07 18:09:31 -05:00
Scott Idem
74fdea2941 Updates to person related models 2023-10-31 13:30:00 -04:00
27fc6beec4 Minor updates for file downloads 2023-10-25 14:41:32 -04:00
Scott Idem
9308c5d0de Updates for event_presenter_model fields 2023-10-20 11:23:28 -04:00
Scott Idem
6bc0eb86d5 Minor wording change 2023-10-19 12:06:47 -04:00
Scott Idem
9faf63fcea Less debug 2023-09-28 16:43:24 -04:00
Scott Idem
fae8aa8de9 Less debug 2023-09-28 16:42:20 -04:00
Scott Idem
aa5d3a53c0 Enable mod_badges_json and mod_exhibits_json config field for events 2023-09-28 16:38:49 -04:00
Scott Idem
fc5c5a2470 Enable mod_pres_mgmt_json config field for events 2023-09-28 12:31:43 -04:00
Scott Idem
3c10886991 Now sleeping when uploading a new file 2023-09-28 09:58:16 -04:00
Scott Idem
bb26eac2a7 Sort of bug fix for abstracts and updating the abstract code 2023-09-22 10:30:07 -04:00
Scott Idem
766c0f99d5 Work on abstracts 2023-09-22 10:02:06 -04:00
Scott Idem
0ac9510546 Bug fixes, clean up, stuff 2023-09-21 12:12:31 -04:00
Scott Idem
fa39529f3f Less debug 2023-09-12 16:15:13 -04:00
Scott Idem
f3571fe7f4 Updates to models 2023-09-07 17:56:25 -04:00
Scott Idem
2550c58d99 Work on CRUD routes and related SQL 2023-09-07 16:57:58 -04:00
Scott Idem
116cdd4a45 Documentation addition 2023-08-21 17:00:22 -04:00
Scott Idem
5f7225e3ab Improving the CRUD functions and other clean up. 2023-08-18 17:10:44 -04:00
Scott Idem
03d5586548 Working on range request and seeking for file downloads and streaming 2023-08-18 11:08:57 -04:00
Scott Idem
d59956942d Working on range request and seeking for file downloads and streaming 2023-08-18 11:07:39 -04:00
Scott Idem
79ea4a9856 Working on range request and seeking for file downloads and streaming 2023-08-18 11:06:37 -04:00
Scott Idem
bea4975e7e Working on streaming download files and or range selection for file downloads 2023-08-17 20:16:39 -04:00
Scott Idem
925760b13d Updated CRUD API. Work on an alternative to the x-account-id header value. General clean up. 2023-08-17 17:50:08 -04:00
Scott Idem
48ec743dfa Updated CRUD API for lists. Always expect a list. But added check just in case it is not a list. 2023-08-17 14:22:32 -04:00
Scott Idem
a963949bbb Minor changes and clean up 2023-07-26 11:44:06 -04:00
Scott Idem
a377878781 Minor changes 2023-07-26 11:27:24 -04:00
Scott Idem
fe71a7be33 API CRUD functions can now handle sorting. And headers are now handled a little better. 2023-07-25 18:29:07 -04:00
Scott Idem
cb45b9774f Work on new API CRUD functions 2023-07-24 18:39:08 -04:00
Scott Idem
faff6f60d5 Clean up 2023-07-12 16:13:57 -04:00
Scott Idem
7d51376cb5 General clean up. Less debug. 2023-07-12 15:20:13 -04:00
Scott Idem
9f99ca23ba Better SQL connection settings 2023-07-12 13:46:27 -04:00
Scott Idem
236ef204eb Logging clean up 2023-07-12 13:13:35 -04:00
Scott Idem
4f7ce57111 Improving the CRUD API endpoints 2023-07-10 16:35:04 -04:00
Scott Idem
60853bc281 Bug fix for sql_select with enabled or hidden passed. 2023-07-06 20:23:00 -04:00
Scott Idem
dba44aa4fb Improvements for API CRUD list endpoints and related sql_select function. Now supports enabled and hidden. 2023-07-06 18:10:49 -04:00
Scott Idem
6ebdd80030 Bug fixes related to event person, event person profile, and abstracts 2023-07-06 17:26:20 -04:00
Scott Idem
8ed55b1ed9 Updates related to SQL and limit and offset 2023-06-29 16:10:37 -04:00
Scott Idem
ed0e20fa6a Updates related to abstracts and CRUD 2023-06-28 15:26:49 -04:00
Scott Idem
815e9a45b4 Updates related to abstracts 2023-06-28 12:22:08 -04:00
Scott Idem
dc974135bf Improved the original send_email function. Better error checking. Better formatting. 2023-06-27 16:22:54 -04:00
Scott Idem
2bbc219df3 Now with the an email send endpoint! 2023-06-27 16:03:08 -04:00
Scott Idem
e91e639112 Ready for sort of abstracts demo and screenshots 2023-06-26 16:08:33 -04:00
Scott Idem
5b4af4bb34 More work on abstract submissions and related grants 2023-06-26 14:52:31 -04:00
Scott Idem
702357910a Work on abstract submissions and related grants 2023-06-23 17:48:31 -04:00
Scott Idem
77ea2d1500 Things are working well now? 2023-06-13 16:58:03 -04:00
Scott Idem
8036e5e328 Still working on bug... 2023-06-13 12:42:44 -04:00
Scott Idem
a4bfcdad16 Trying to fix a logging bug? 2023-06-13 12:31:21 -04:00
Scott Idem
554010ee84 Trying to fix a logging bug? 2023-06-13 12:27:58 -04:00
Scott Idem
5ce6856b75 Trying to fix a logging bug? 2023-06-13 12:18:59 -04:00
Scott Idem
6fdcc2470a Trying to fix a logging bug? 2023-06-13 12:16:56 -04:00
Scott Idem
ef31a733e2 Trying to fix a logging bug? 2023-06-13 12:15:23 -04:00
Scott Idem
e84297ab05 Trying to fix a logging bug? 2023-06-13 12:14:13 -04:00
Scott Idem
a46c432a3f Trying to fix a logging bug? 2023-06-13 12:13:32 -04:00
Scott Idem
e79401af7b Trying to fix a logging bug? 2023-06-13 12:13:01 -04:00
Scott Idem
884f737d81 Trying to fix a logging bug? 2023-06-13 12:12:06 -04:00
Scott Idem
7e46628b25 Trying to fix a logging bug? 2023-06-13 12:11:24 -04:00
Scott Idem
39d0070e1a Trying to fix a logging bug? 2023-06-13 12:09:19 -04:00
Scott Idem
01615aaa07 Trying to fix a logging bug? 2023-06-13 12:08:45 -04:00
Scott Idem
70fb8c0b91 Trying to fix a logging bug? 2023-06-13 12:08:05 -04:00
Scott Idem
ad54d92b86 Trying to fix a logging bug? 2023-06-13 12:07:15 -04:00
Scott Idem
92a8edd435 Trying to fix a logging bug? 2023-06-13 12:02:55 -04:00
Scott Idem
31d82cc5d8 Trying to fix a logging bug? 2023-06-13 11:59:27 -04:00
Scott Idem
4dae6953a2 Trying to fix a logging bug? 2023-06-13 11:58:56 -04:00
Scott Idem
f83b902857 Trying to fix a logging bug? 2023-06-13 11:57:17 -04:00
Scott Idem
b2683e71c5 Trying to fix a logging bug? 2023-06-13 11:55:56 -04:00
Scott Idem
fdefe0e48f Trying to fix a logging bug? 2023-06-13 11:54:02 -04:00
Scott Idem
5281bcf79d Trying to fix a logging bug? 2023-06-13 11:51:53 -04:00
Scott Idem
9c75e13aec Trying to fix a logging bug? 2023-06-13 11:47:58 -04:00
Scott Idem
ffed474c34 Trying to fix a logging bug? 2023-06-13 11:46:57 -04:00
Scott Idem
3b8d1c8175 Trying to fix a logging bug? 2023-06-13 11:45:28 -04:00
Scott Idem
3c3a848144 Trying to fix a logging bug? 2023-06-13 11:44:36 -04:00
Scott Idem
d7e7fe1d6f Trying to fix a logging bug? 2023-06-13 11:43:44 -04:00
Scott Idem
36af83ff5c Trying to fix a logging bug? 2023-06-13 11:43:20 -04:00
Scott Idem
f8af9ffc85 Trying to fix a logging bug? 2023-06-13 11:42:52 -04:00
Scott Idem
0f5c51c6c2 Trying to fix a logging bug? 2023-06-13 11:42:20 -04:00
Scott Idem
224cde2b45 Trying to fix a logging bug? 2023-06-13 11:41:13 -04:00
Scott Idem
f0d7d0426e Trying to fix a logging bug? 2023-06-13 11:40:00 -04:00
Scott Idem
ee5ec65a4d Trying to fix a logging bug? 2023-06-13 11:38:27 -04:00
Scott Idem
b52c07015d Trying to fix a logging bug? 2023-06-13 11:37:19 -04:00
Scott Idem
b3285f9581 Trying to fix a logging bug? 2023-06-13 11:36:24 -04:00
Scott Idem
438731d0f4 Trying to fix a logging bug? 2023-06-13 11:35:07 -04:00
Scott Idem
8a7d30b276 Trying to fix a logging bug? 2023-06-13 11:33:39 -04:00
Scott Idem
756913a9cd Trying to fix a logging bug? 2023-06-13 11:33:07 -04:00
Scott Idem
7f59f07a7d Trying to fix a logging bug? 2023-06-13 11:30:42 -04:00
Scott Idem
eb5017665e Trying to fix a logging bug? 2023-06-13 11:29:06 -04:00
Scott Idem
4e1378b796 Trying to fix a logging bug? 2023-06-13 11:22:19 -04:00
Scott Idem
8b529d6732 Trying to fix a logging bug? 2023-06-13 11:21:18 -04:00
Scott Idem
826cda8e08 Trying to fix a logging bug? 2023-06-13 11:13:24 -04:00
Scott Idem
555d33e0fa Trying to fix a logging bug? 2023-06-13 11:11:21 -04:00
Scott Idem
fd81588369 Trying to fix a logging bug? 2023-06-13 11:09:55 -04:00
Scott Idem
bb004e4293 Trying to fix a logging bug? 2023-06-13 11:07:59 -04:00
Scott Idem
ff6ef0a28b Minor 2023-05-28 17:05:28 -04:00
Scott Idem
cec7ecc88f Work on abstracts 2023-05-22 18:18:46 -04:00
Scott Idem
0375bd8c05 Improve data store record retrieval. 2023-05-22 13:41:30 -04:00
b9b45969f3 Allow for PNG 2023-05-16 03:44:34 -04:00
Scott Idem
b7d83178f7 Work on launcher 2023-05-14 17:22:21 -04:00
e4ef026c47 Improvement with event program data importing 2023-05-13 17:04:51 -04:00
33d3be8fdd More work on Confex API loop 2023-05-09 23:47:08 -04:00
4d9932ee67 Work on Confex API loop 2023-05-09 23:11:14 -04:00
c2c59374b3 Limit Confex loop count 2023-05-09 11:32:10 -04:00
Scott Idem
916cca1d73 Work on abstracts. 2023-05-05 17:29:02 -04:00
Scott Idem
2698f06c57 Minor 2023-05-05 11:14:11 -04:00
Scott Idem
4b0cec7d36 Finalized PDF to webp conversion. Looks good! 2023-05-04 17:21:09 -04:00
Scott Idem
d75e9fd8c8 Work on hosted files and converting files 2023-05-03 14:11:41 -04:00
Scott Idem
7889eb273b Work on hosted files and converting files 2023-05-02 20:33:12 -04:00
Scott Idem
c9cc587c61 Work on fancy API CRUD 2023-05-02 17:41:43 -04:00
Scott Idem
e7a26268ce Work on fancy API CRUD 2023-05-02 17:36:30 -04:00
Scott Idem
c1f68522aa Prep for AAPOR with Confex and NCSD STD. General clean up. 2023-05-02 09:50:24 -04:00
Scott Idem
c687ade3fa Prep for AAPOR with Confex. General clean up. 2023-04-30 19:21:19 -04:00
10b8e8ca7a Quick update for IDAA export. And other stuff? 2023-04-27 17:48:06 -04:00
0ad9a0f56f Fix for Impexium API import adjustments 2023-04-19 17:07:01 -04:00
90ab23b356 Some records will be randomly skipped 2023-04-19 01:16:33 -04:00
0317201406 Fix for Impexium and allow_access field. 2023-04-19 01:07:08 -04:00
Scott Idem
43a9fe7fe5 Moving away sql_insert function creating IDs automatically. Only if asked. 2023-04-13 18:27:56 -04:00
Scott Idem
5672f9f60a ISHLT requested change for US vs USA. 2023-04-12 22:39:37 -04:00
Scott Idem
c4fad68192 Work on Confex API and Impexium API 2023-04-12 15:30:46 -04:00
Scott Idem
f24382fb10 Adding Confex API functions. Bug fixes. Clean up. 2023-04-11 19:48:05 -04:00
Scott Idem
350a2cc4b8 Minor correction 2023-04-11 12:07:27 -04:00
Scott Idem
48c3ab4c2c Prep for ISHLT 2023-04-11 11:51:57 -04:00
Scott Idem
4c18d50c3b Prep for ISHLT 2023-04-10 18:54:42 -04:00
Scott Idem
d7e1ad6010 Enable download tmp 2023-04-09 21:03:56 -04:00
Scott Idem
d784de4e4a Enable pdf2image again 2023-04-09 20:59:18 -04:00
Scott Idem
0cc5c6142d Enable websockets again... 2023-04-09 20:54:58 -04:00
Scott Idem
73cf8bfd91 Fix???? 2023-04-09 20:49:32 -04:00
Scott Idem
f2309a28a9 Fix? 2023-04-09 19:39:04 -04:00
Scott Idem
890a5d8ad1 Fix? 2023-04-07 18:22:21 -04:00
Scott Idem
4f36a511e9 Fix? 2023-04-07 18:17:07 -04:00
Scott Idem
bfd344a9f7 Fix? 2023-04-07 18:16:04 -04:00
Scott Idem
6e4424f722 Fix? 2023-04-07 18:14:40 -04:00
Scott Idem
9f8157d338 Work on websockets. Finally 98.25% working? 2023-04-07 18:09:02 -04:00
Scott Idem
6172626254 Work on websockets. Finally 97.25% working? 2023-04-07 17:09:51 -04:00
Scott Idem
c9fa280c57 Work on websockets. Finally 95.75% working? 2023-04-07 15:43:08 -04:00
Scott Idem
eadaf7d69f Work on websockets. Finally 95.5% working? 2023-04-07 14:09:48 -04:00
Scott Idem
b1d70323f3 Work on websockets. Finally 95% working? 2023-04-07 14:01:05 -04:00
Scott Idem
0eb775826f Work on websockets. A lot... 2023-04-07 00:37:57 -04:00
Scott Idem
b369d00b3e Work on websockets and event files. 2023-03-31 16:26:07 -04:00
Scott Idem
cff165d9d9 Work on websockets end points and management 2023-03-30 19:27:39 -04:00
Scott Idem
224aaed969 General clean up. Work on abstracts and websockets 2023-03-23 19:12:13 -04:00
Scott Idem
430a155b75 General clean up. Updated event models 2023-03-21 17:55:45 -04:00
Scott Idem
df26128ce4 General clean up. Workon event abstracts. 2023-03-20 19:39:41 -04:00
Scott Idem
02fa7225ac Updating event abstracts related. 2023-03-20 15:29:08 -04:00
Scott Idem
a07c0121df DB connection clean up 2023-03-15 19:15:16 -04:00
Scott Idem
9dca404fb0 Less logging 2023-03-15 18:56:59 -04:00
Scott Idem
adbb21abc2 Commented out event person profile create and update 2023-03-15 18:08:06 -04:00
Scott Idem
2fa27cafbb Clean up of Impexium import of specific page. 2023-03-15 17:51:33 -04:00
Scott Idem
b9be0a70af Clean up of Impexium import of specific page. 2023-03-15 16:46:32 -04:00
Scott Idem
ecbcdc9dae Work on websockets! 2023-03-14 20:14:58 -04:00
Scott Idem
461d547c09 Work on exhibit tracking export and API badge ID lookup 2023-03-07 19:01:45 -05:00
Scott Idem
ab8e1b8899 Minor update for ISHLT Impexium import 2023-02-27 17:04:45 -05:00
Scott Idem
3ec8e8ca2c General API config clean up 2023-02-23 12:10:24 -05:00
Scott Idem
d3c7ddbd55 Work on ISHLT Impexium importing 2023-02-16 16:07:19 -05:00
Scott Idem
41470c277f Work on ISHLT Impexium importing 2023-02-16 16:04:38 -05:00
Scott Idem
d4507e25c2 Work on exhibit tracking 2023-02-16 13:28:01 -05:00
Scott Idem
8e550617ea Improved SQL INSERT and UPDATE with JSON or dict values. 2023-02-10 17:04:16 -05:00
Scott Idem
b66a7f4707 Improved SQL INSERT and UPDATE with JSON or dict values. 2023-02-08 19:28:45 -05:00
Scott Idem
d1c373f7fb Exhibitor tracking, badges, and ISHLT Impexium import related. 2023-02-08 18:51:49 -05:00
Scott Idem
cbb5bea8c6 Exhibitor tracking, badges, and ISHLT Impexium import related. 2023-02-08 18:32:13 -05:00
Scott Idem
e43d7e061e ISHLT IMpexium import script improvements. 2023-02-07 17:56:13 -05:00
Scott Idem
1604d68817 ISHLT IMpexium import script improvements. 2023-02-03 16:05:48 -05:00
Scott Idem
b715b245e7 Sort of bug fix 2023-02-03 13:53:19 -05:00
Scott Idem
20586471ab Working on pool recyling 2023-02-02 17:43:19 -05:00
Scott Idem
0e482a3ccd Modified the authenticate function for Impexium. It should be smarter now... It should only try to re-auth if something went wrong on another request. 2023-02-02 14:54:29 -05:00
Scott Idem
242fc146e9 More work for ISHLT badge importing. 2023-02-02 14:09:45 -05:00
Scott Idem
a176248104 Lots of work for ISHLT badge printing. 2023-02-01 17:57:22 -05:00
Scott Idem
a9f0e2bf3d Clean up and debugging. 2023-01-30 17:59:13 -05:00
Scott Idem
b510a44adf Clean up and debugging. 2023-01-30 17:29:01 -05:00
Scott Idem
bbf23e9f0b Debugging... 2023-01-30 17:12:48 -05:00
Scott Idem
4c3bab739a Debugging... 2023-01-30 17:00:16 -05:00
Scott Idem
40d8f14197 Debugging... 2023-01-30 16:48:41 -05:00
Scott Idem
01ba5d8d72 Work on user password updates 2023-01-30 16:28:15 -05:00
Scott Idem
cdab2de5a6 General clean up. Sort of bug fix for create_user_obj method with new passwords. 2023-01-30 16:20:09 -05:00
Scott Idem
db58d9267c General clean up 2023-01-30 15:06:02 -05:00
Scott Idem
536c4c4732 Bug fix. Should not reference config.settings.* from db_sql.py. 2023-01-30 09:05:23 -05:00
Scott Idem
f3a84a9609 Badge and launcher related changes 2023-01-27 16:32:30 -05:00
Scott Idem
a7915cbb5f Badge related changes 2023-01-25 18:36:54 -05:00
Scott Idem
866e89eefa Minor changes 2023-01-19 16:45:50 -05:00
Scott Idem
93f3b4f184 Work on badges and related. 2023-01-13 16:51:57 -05:00
Scott Idem
0eaaaa4d7e Changes related to Docker, bug fixes, and event badges. 2023-01-12 18:21:00 -05:00
Scott Idem
2c3c4861c4 Changes related to Docker and bug fixes 2023-01-11 18:40:35 -05:00
Scott Idem
2a76f9acc1 Changes related to Docker 2023-01-06 18:25:46 -05:00
Scott Idem
6e51cd0305 Unknown 2022-12-01 18:03:54 -05:00
Scott Idem
1b18bdbfb6 Unknwon 2022-12-01 15:19:36 -05:00
Scott Idem
8ebe30746d Minor changes 2022-11-29 15:19:39 -05:00
Scott Idem
e4ca27a84d Improved CORS!!!! 2022-11-29 15:12:04 -05:00
Scott Idem
3de7d580dc Clean up of new CRUD PATCH endpoint and other updates 2022-11-29 14:43:35 -05:00
Scott Idem
188947ecad New CRUD PATCH endpoint 2022-11-28 19:40:57 -05:00
Scott Idem
856e2a2891 Minor changes 2022-11-28 12:42:31 -05:00
Scott Idem
5ebb725532 Added better fundraising routes, methods, and models! I thought I already did this... 2022-11-18 15:52:41 -05:00
Scott Idem
1d40f41651 Unknown 2022-11-04 15:10:59 -04:00
Scott Idem
70f9d24b73 Update to websockets 2022-11-02 18:25:50 -04:00
Scott Idem
705cedbf3c Update to websockets 2022-11-02 16:39:25 -04:00
Scott Idem
49aabfe6f4 Update to websockets 2022-11-02 16:09:04 -04:00
Scott Idem
785d0ea377 Dumb bug fixes... should have tested 2022-10-27 17:23:56 -04:00
Scott Idem
4853b1e464 Updated user role model with new fields and cleaned up 2022-10-27 17:21:21 -04:00
Scott Idem
367a95b5c5 Minor changes 2022-10-24 12:08:56 -04:00
Scott Idem
b0d74fcfd0 Minor changes 2022-10-24 10:48:02 -04:00
Scott Idem
4f20c5fd5f Minor changes 2022-10-15 23:09:02 -04:00
Scott Idem
1f856822e2 Minor changes 2022-10-15 23:06:35 -04:00
Scott Idem
a1f5a996ae Minor changes and fixes. 2022-10-12 19:07:08 -04:00
Scott Idem
6cfd76c788 Minor changes 2022-10-10 11:27:44 -04:00
Scott Idem
e36a95fd76 Work on event program data importing. 2022-10-07 17:47:58 -04:00
Scott Idem
2c0e563fa6 Auto fix certain uploaded file extensions. 2022-10-06 01:57:50 -04:00
Scott Idem
b70dff8031 Minor changes 2022-10-06 00:22:52 -04:00
Scott Idem
ba36093b0e Minor changes 2022-10-06 00:18:55 -04:00
Scott Idem
64c0ab734b Minor changes 2022-10-05 16:41:51 -04:00
Scott Idem
1e064a774f Minor changes 2022-10-05 16:41:17 -04:00
Scott Idem
2b590b0754 Minor changes 2022-10-05 03:16:09 -04:00
Scott Idem
38b32ef5b1 Minor changes 2022-09-29 18:08:57 -04:00
Scott Idem
b7d48c67bf Minor changes 2022-09-28 18:20:35 -04:00
Scott Idem
a18388e841 Minor changes 2022-09-27 20:53:12 -04:00
Scott Idem
cb50ae8089 Minor changes 2022-09-23 18:32:02 -04:00
Scott Idem
a56a4b9647 Updated event location related. General code clean up and updates. 2022-09-23 18:31:17 -04:00
Scott Idem
d2d6136bc4 Minor changes 2022-09-23 16:29:21 -04:00
Scott Idem
97bd91f006 Updated event location related. General code clean up and updates. 2022-09-23 16:27:46 -04:00
Scott Idem
f691137a78 Minor changes 2022-09-22 18:36:08 -04:00
Scott Idem
aa981b0913 Work on sort of ressults using the priorty and sort fields. 2022-09-22 14:54:51 -04:00
Scott Idem
ed7e06b5b2 Work on event session, presentation, and presenter 2022-09-21 18:46:19 -04:00
Scott Idem
7fe007a098 Minor changes. Added even_location_list to event_session. 2022-09-20 18:32:42 -04:00
Scott Idem
b85397a9b5 Minor changes 2022-09-19 18:36:23 -04:00
Scott Idem
b135001c10 Minor changes 2022-09-19 18:36:03 -04:00
Scott Idem
28e6a70e90 Minor changes 2022-09-19 18:35:26 -04:00
Scott Idem
1c84383352 Minor changes 2022-09-16 15:57:39 -04:00
Scott Idem
84b33bb21f Work on event sessions and related files. Internal use is a new thing. 2022-09-15 17:47:07 -04:00
Scott Idem
ee297d9ea7 Minor changes 2022-08-23 17:09:24 -04:00
Scott Idem
ee64c58fe6 Work on event files and related 2022-08-23 17:08:07 -04:00
Scott Idem
1f28f76006 Work on event files and related 2022-08-23 12:47:51 -04:00
Scott Idem
e1a584de0c Work on event files and related 2022-08-22 19:07:33 -04:00
Scott Idem
f3f53e73b4 Work on event files and related 2022-08-19 17:46:19 -04:00
Scott Idem
4048bd3a51 Work on event files and related 2022-08-17 18:49:37 -04:00
Scott Idem
4495b98a6e Clean up of logging between FastAPI and Flask 2022-08-15 18:31:50 -04:00
Scott Idem
c8c2c7af95 Bug fix and minor clean up 2022-08-15 15:54:26 -04:00
Scott Idem
42a9bd9f32 General clean up. Work on event files and event presenters. 2022-08-12 17:29:52 -04:00
Scott Idem
f067aefdca General clean up 2022-08-12 14:06:43 -04:00
Scott Idem
8bcd67d935 Work on hosted files and archives. 2022-08-11 17:50:50 -04:00
Scott Idem
95becf7643 Work on hosted files and archives. 2022-08-10 17:33:46 -04:00
Scott Idem
8d502a9fd0 Work on hosted files. Now with basic hosted directory check and clean up. 2022-08-09 17:43:30 -04:00
Scott Idem
09e3f29501 Too many changes. Work on file uploads, hosted files, deletion of links, records, and stored files. 2022-08-09 16:08:50 -04:00
Scott Idem
fac9ccad75 Work on file uploads, hosted files, deletion of links, records, and stored files. 2022-08-08 18:25:25 -04:00
Scott Idem
0dd80fd829 Improvement on SQL connections and timeouts. 2022-08-03 15:00:18 -04:00
Scott Idem
02e38a9c2c Improvement on SQL connections and timeouts. 2022-08-03 14:46:45 -04:00
Scott Idem
dd1b3c0b1b Work on data store and related 2022-07-27 18:05:08 -04:00
Scott Idem
c87531aff7 Work on Stripe log 2022-07-26 11:28:26 -04:00
Scott Idem
6244359847 Work on Stripe log 2022-07-26 09:56:24 -04:00
Scott Idem
fc2e3f4df6 Work on Stripe log 2022-07-26 09:51:53 -04:00
Scott Idem
0067f0c52b Work on Stripe log 2022-07-26 09:30:37 -04:00
Scott Idem
ea46b3337b Work on Stripe log 2022-07-26 09:26:30 -04:00
Scott Idem
3b79802d32 Work on Stripe log 2022-07-26 09:22:35 -04:00
Scott Idem
ef7531c980 Work on Stripe log 2022-07-26 09:16:16 -04:00
Scott Idem
ff5acd2702 Work on Stripe log 2022-07-26 09:00:36 -04:00
Scott Idem
4db2c8cbcb Work on badge model and other 2022-07-25 19:47:11 -04:00
Scott Idem
fccfefe24b Work on site and badge templates 2022-07-22 17:57:56 -04:00
Scott Idem
252cfc7bad Work on site model and related 2022-07-22 15:36:08 -04:00
Scott Idem
7b6baa14c4 Work on badge templates 2022-07-20 17:52:46 -04:00
Scott Idem
af3ad559a1 Clean up and updating site and site domain related 2022-07-20 12:40:41 -04:00
Scott Idem
41f389fb05 Clean up and updating site and site domain related 2022-07-19 17:52:09 -04:00
Scott Idem
b93109a5ca Clean up and updating site and site domain related 2022-07-19 17:50:45 -04:00
Scott Idem
ed6ae8c93e Unknown 2022-07-15 13:09:07 -04:00
Scott Idem
8fa438336f Modified badge list method 2022-07-08 10:00:18 -04:00
Scott Idem
a483b8e24e Clean up of new Aether configs 2022-07-07 18:03:17 -04:00
Scott Idem
47407e7aa2 Clean up of new Aether configs 2022-07-07 15:09:59 -04:00
Scott Idem
5a390a2c2b Working on Aether configs 2022-07-07 15:01:17 -04:00
Scott Idem
ead68b24a3 Working on Aether configs 2022-07-07 14:47:40 -04:00
Scott Idem
8598a39b1e Working on Aether configs 2022-07-07 12:58:00 -04:00
Scott Idem
cb2f8b05f0 Changed sorting for NPA 2022-07-06 20:55:48 -04:00
Scott Idem
462acd3c7f Added VCard format support 2022-07-06 17:53:49 -04:00
Scott Idem
0a41699f5b Minor badge clean up 2022-07-06 12:06:07 -04:00
Scott Idem
511f420555 More again again again again. Moving ID random generation to the SQL db instead. General clean up. 2022-06-30 17:34:47 -04:00
Scott Idem
5066cd099d More again again again. Moving ID random generation to the SQL db instead. General clean up. 2022-06-30 17:00:17 -04:00
Scott Idem
8a77f0c892 More again again. Moving ID random generation to the SQL db instead. General clean up. 2022-06-30 16:14:28 -04:00
Scott Idem
f91b50a9e8 More again. Moving ID random generation to the SQL db instead. General clean up. 2022-06-30 15:53:11 -04:00
Scott Idem
c43e958859 More. Moving ID random generation to the SQL db instead. General clean up. 2022-06-30 15:43:00 -04:00
Scott Idem
493f4a7365 Moving ID random generation to the SQL db instead. General clean up. 2022-06-30 15:04:19 -04:00
Scott Idem
efcbe17b6b Work on cleaning up viewing and searching for badge only records. Not tied to an event_person record. 2022-06-27 19:51:20 -04:00
Scott Idem
9376388f90 Updated badge rec list method ORDER BY 2022-06-27 13:30:29 -04:00
Scott Idem
728fcc05cc Updated badge template and related badge endpoints and methods 2022-06-23 17:06:06 -04:00
Scott Idem
7f437ed685 Need to update export field names to match HTML report field names 2022-06-13 17:19:22 -04:00
Scott Idem
1e6d9a7f55 Technical bug fix or change related to order created and updated dates. It is misleading. 2022-06-13 16:32:21 -04:00
23153e492a Updated CORS 2022-06-01 11:25:50 -04:00
Scott Idem
7731b18fad Work on event device model and related. 2022-05-25 15:38:49 -04:00
Scott Idem
dc585fb9bd Work on event device model and related. 2022-05-25 14:02:54 -04:00
Scott Idem
fdcfe75325 Sort of bug fixes and improvements for user model and other. 2022-05-24 16:49:10 -04:00
Scott Idem
083da3fe7f General clean up. Work on event device. Work on creating a common core object model. 2022-05-24 11:26:34 -04:00
Scott Idem
14b03ff340 Work on event management related and created generic SQL select endpoint 2022-05-23 18:55:21 -04:00
803c2118b6 Added CMSC program data import 2022-05-11 00:08:37 -04:00
Scott Idem
192c85fa30 Updated CORS 2022-05-06 19:54:22 -04:00
Scott Idem
36deb7defe Updated event device model clean up 2022-05-06 11:33:48 -04:00
Scott Idem
4e930d23a6 Updated event device model with more base URL options 2022-05-06 11:23:37 -04:00
Scott Idem
a83da83b84 Updated event device model with more triggers 2022-05-06 11:04:43 -04:00
Scott Idem
674b15afab Updated event device model 2022-05-05 20:08:24 -04:00
ad1dc19919 Increased cap of tracking results 2022-04-29 15:19:23 -04:00
12aa283b0a Increased cap of export 2022-04-29 15:13:08 -04:00
c0d72c383c Hide some fields from export 2022-04-29 10:45:03 -04:00
37fbc35f2c Bug fixes for export with missing fields 2022-04-29 10:40:00 -04:00
8ef4bc3970 Bug fixes for export with missing fields 2022-04-29 10:36:45 -04:00
bf3ce47c9a Bug fixes for export with missing fields 2022-04-29 10:35:15 -04:00
15c1ee9e9c Bug fixes for export with missing fields 2022-04-29 10:30:15 -04:00
f9d2eddeb5 No more BCC! for real 2022-04-25 22:41:29 -04:00
5ad1d23981 No more BCC! 2022-04-25 11:46:51 -04:00
f2e31cf6ad No more BCC! 2022-04-25 11:45:29 -04:00
2debf80598 Impexium updates for address fields. Hidding 2022-04-25 10:27:08 -04:00
bad0327762 Added address lines to the database for badges. Improved again. Now with more exports! 2022-04-25 00:05:35 -04:00
40a735a451 Added address lines to the database for badges. Improved 2022-04-24 23:56:14 -04:00
8fbde8c069 Added address lines to the database for badges. 2022-04-24 23:41:46 -04:00
Scott Idem
beea92f5d7 Added postal code to badges and tracking data 2022-04-24 10:38:04 -04:00
Scott Idem
f369c2a228 Minor changes 2022-04-23 19:46:02 -04:00
Scott Idem
dbe86b8ed8 Unknown 2022-04-23 19:45:37 -04:00
Scott Idem
2c4a58ff59 Unknown 2022-04-23 18:00:34 -04:00
Scott Idem
a0343f50e0 Work on exhibit tracking 2022-04-22 19:26:15 -04:00
Scott Idem
e7ab95fcb7 Work on exhibit tracking 2022-04-22 15:22:46 -04:00
Scott Idem
74e62f5f4c Work on exhibit tracking 2022-04-22 12:32:18 -04:00
Scott Idem
3ddc0e2860 Work on exhibit tracking export 2022-04-21 23:33:12 -04:00
Scott Idem
aa8e6f8b00 Work on event badge model and event exhibit tracking model. 2022-04-21 22:14:39 -04:00
427a1dee07 Impexium updates for address fields 2022-04-21 09:21:30 -04:00
a789cf2705 Impexium updates for logging 2022-04-20 20:51:59 -04:00
229f06cefa Modified Impexium import to pull in state whereever possible 2022-04-20 16:39:40 -04:00
d2e81bb488 Simple bug fix for registration type code. Using value instead of name. 2022-04-20 15:46:10 -04:00
4a0151e922 Dumb bug fix 2022-04-20 00:12:53 -04:00
d99d0fa39e Work on badge model and method. Can hide badges now. 2022-04-20 00:07:43 -04:00
c924337761 Work on badge model. Hide... 2022-04-19 23:58:07 -04:00
53201d335c Bug fix for tracking model 2022-04-19 18:10:42 -04:00
efff92c549 Work on badge and tracking models 2022-04-19 18:06:16 -04:00
6f77fcee26 Updated CORS 2022-04-19 00:34:25 -04:00
Scott Idem
29ae140d65 Work on exhibitor tracking 2022-04-17 19:09:04 -04:00
Scott Idem
8051360bfc Work on event launcher related 2022-04-17 16:28:41 -04:00
Scott Idem
2fb5acd485 Work on ISHLT related 2022-04-16 14:01:06 -04:00
Scott Idem
1f66eb0abc Work on ISHLT Impexium importing 2022-04-16 12:03:32 -04:00
Scott Idem
70523d5bbf Work on ISHLT Impexium importing 2022-04-15 18:50:32 -04:00
Scott Idem
a2e5f804d6 Work on ISHLT Impexium importing 2022-04-15 18:25:06 -04:00
Scott Idem
789d7372d9 General clean up. 2022-04-14 14:41:25 -04:00
Scott Idem
bf4e3827e0 General clean up. 2022-04-13 15:49:37 -04:00
Scott Idem
16fca4b2f0 General clean up. Work on importing AAPOR. Work on sessions, presentations, and presenters. 2022-04-12 04:08:52 -04:00
Scott Idem
f15179fb02 Work on Impexium import speed 2022-04-11 21:01:29 -04:00
Scott Idem
f64efa3494 Work on exhibits and badges and related 2022-04-11 19:38:10 -04:00
Scott Idem
8433f07e57 Unknown 2022-04-11 05:08:12 -04:00
Scott Idem
554ea353e0 Unknown 2022-04-10 22:20:54 -04:00
Scott Idem
877a4e1758 Improved create event exhibit tracking log entries 2022-04-08 14:20:44 -04:00
Scott Idem
6f9cea9ca6 Updated importing to work for BGH 2022. Other minor changes. 2022-04-06 17:09:55 -04:00
Scott Idem
81f2f79bf2 Minor update for sites and domains 2022-04-04 16:36:18 -04:00
Scott Idem
14caad33e4 Minor clean up 2022-03-31 13:48:30 -04:00
Scott Idem
db475ddef1 Minor clean up 2022-03-29 19:53:34 -04:00
Scott Idem
994811770b Work on data stores 2022-03-28 14:47:20 -04:00
Scott Idem
41a6da9409 Improved the look up for country 2022-03-27 20:15:37 -04:00
Scott Idem
24ed8da007 Improved the look up for country sub division 2022-03-27 19:19:21 -04:00
Scott Idem
a3164dd27b Less debug and clean up 2022-03-24 11:59:35 -04:00
Scott Idem
6852772808 Minor change to import related to professional title 2022-03-24 11:38:14 -04:00
Scott Idem
b284049782 Clean up of Impexium API related functions. Ready for cron job time! 2022-03-23 18:25:20 -04:00
Scott Idem
c625cda845 More improvement to Impexium API calls. Should be faster and more robust now. 2022-03-23 15:38:53 -04:00
Scott Idem
7c7bd3c530 Improved the main Impexium API calls. Should be faster and more robust now. 2022-03-22 16:59:06 -04:00
Scott Idem
e1eeb8990c Work on badge importing 2022-03-21 17:59:05 -04:00
Scott Idem
88e5daebfa Work on badges for ISHLT 2022-03-21 16:01:43 -04:00
Scott Idem
0fa04710ff Work on import from Impexium for ISHLT with mutliple events. 2022-03-21 14:25:20 -04:00
Scott Idem
44898fbfd3 Less debug 2022-03-16 18:15:37 -04:00
Scott Idem
7ea932a321 Improve FQDN lookup wiht access and referrer. 2022-03-16 18:15:17 -04:00
Scott Idem
44e7a0e743 Improve FQDN lookup wiht access and referrer. 2022-03-16 18:01:48 -04:00
Scott Idem
810569202a Removing user_id related from order. 2022-03-16 16:54:46 -04:00
Scott Idem
8d5b4c7fc0 Work on orders and order lines. Removing user_id related from order. Also general clean up. 2022-03-16 16:52:03 -04:00
Scott Idem
490a1362c3 Work on orders and order lines 2022-03-16 16:26:37 -04:00
Scott Idem
e254f8f181 Related bug fix for updating person record 2022-03-16 15:08:07 -04:00
Scott Idem
eb80a4c220 Bug fix for creating a new person record 2022-03-16 14:59:26 -04:00
Scott Idem
bcec34670d Clean up of Cvent and IDAA person and membership related. The person.status only matters if it is Pending. 2022-03-16 10:43:57 -04:00
Scott Idem
212d69cbe3 More updates for Cvent and IDAA custom status field. Only using pending status if it is specificially passed. Otherwise using unknown or whatever is passed. 2022-03-16 09:25:32 -04:00
Scott Idem
6a969e0991 Updates for Cvent and IDAA custom status field 2022-03-15 18:55:59 -04:00
Scott Idem
bb4dd6e125 Updates for Cvent and IDAA custom status field 2022-03-15 17:51:08 -04:00
Scott Idem
efb0a950d5 Updates for Cvent and IDAA custom status field 2022-03-15 17:33:34 -04:00
Scott Idem
655ba409b4 Updates for Cvent and IDAA custom status field 2022-03-15 17:06:55 -04:00
Scott Idem
c380516855 Updates for Cvent, IDAA, and general clean up 2022-03-15 16:00:57 -04:00
Scott Idem
34c6c9e6bd Updates for Cvent, IDAA, and general clean up 2022-03-15 14:13:18 -04:00
Scott Idem
4752c81e7f Updates for IDAA Cvent API sync. Less debugging turned on. 2022-03-15 11:59:40 -04:00
Scott Idem
243561e3b7 Unknown 2022-03-14 18:57:44 -04:00
Scott Idem
e79cd16d3f Work on badge template and related 2022-03-14 16:18:21 -04:00
Scott Idem
84fa00fa16 Work on badge template and related 2022-03-14 15:55:40 -04:00
Scott Idem
96419787be Work on badge template and related 2022-03-14 15:52:59 -04:00
Scott Idem
4607ef5c0f Work on badge template and related 2022-03-14 15:45:22 -04:00
Scott Idem
90d7619966 Added new route, methods, and models for data_store. Also minor code clean up and less debug. 2022-03-11 17:12:55 -05:00
Scott Idem
57e3298dc6 Unknown 2022-03-09 17:42:24 -05:00
Scott Idem
fd23018647 Code clean up 2022-03-09 15:15:34 -05:00
Scott Idem
78d08f3b0e Less debug info. 2022-03-09 13:44:47 -05:00
Scott Idem
bca7defb4c Mostly working on event device model, methods, and routes. Also general clean up of code. 2022-03-09 13:43:47 -05:00
Scott Idem
f073dd337f Mostly adding event device model, methods, and routes. Also general clean up of code. 2022-03-09 13:05:19 -05:00
Scott Idem
0947032a94 Work on event launcher, files, and related 2022-03-08 18:31:31 -05:00
Scott Idem
398ef69ff7 Work on event launcher, files, and related 2022-03-07 18:21:40 -05:00
Scott Idem
bfd7451c0f Update badge model 2022-03-04 12:26:50 -05:00
Scott Idem
ef22cf169e Work on Impexium importing 2022-03-02 14:31:30 -05:00
Scott Idem
3ea72e584c Work on badge searching 2022-03-02 12:11:05 -05:00
Scott Idem
16ca8cd99e Work on Impexium import for ISHLT 2022-03-01 13:12:48 -05:00
Scott Idem
47297a2579 Work on Impexium import for ISHLT 2022-03-01 12:50:37 -05:00
Scott Idem
3492ec65fc Work on Impexium import for ISHLT 2022-03-01 11:19:06 -05:00
Scott Idem
07cdb6be77 Work on event badge 2022-02-28 19:25:22 -05:00
Scott Idem
d4da7535f9 Work on Impexium and badges for ISHLT 2022-02-24 18:11:14 -05:00
Scott Idem
0ff464dde2 Work on Cvent for IDAA 2022-02-24 13:49:55 -05:00
Scott Idem
f044a235ae Work on Cvent for IDAA 2022-02-24 13:12:55 -05:00
Scott Idem
536c667b59 Work on Impexium, exhibit, and badge related 2022-02-23 18:35:32 -05:00
Scott Idem
18d8b73eb3 Work on Impexium, exhibit, and badge related 2022-02-23 18:34:21 -05:00
Scott Idem
5a84119c77 Work on exhibit related 2022-02-22 18:40:05 -05:00
Scott Idem
783ff6e9b3 Work on Impexium API and related. 2022-02-18 17:35:25 -05:00
Scott Idem
aa09333383 Updated account config with some new help fields 2022-02-18 12:07:53 -05:00
Scott Idem
1978718129 Work on exhibit and tracking 2022-02-17 19:22:52 -05:00
Scott Idem
67f769329e Work on event tracking 2022-02-16 17:59:08 -05:00
Scott Idem
f57f3ba9b8 Work on event tracking and general code clean up 2022-02-16 16:46:28 -05:00
Scott Idem
2e525fa01b General code clean up. More work on event, event exhibits, and event tracking related 2022-02-15 19:09:59 -05:00
Scott Idem
2ddb549a75 General code clean up. More work on event, event exhibits, and event tracking related 2022-02-15 17:47:30 -05:00
Scott Idem
df6e860620 General code clean up. More work on event tracking related 2022-02-15 16:05:29 -05:00
Scott Idem
003ffb4adf General code clean up. Work on event tracking related 2022-02-15 13:25:19 -05:00
Scott Idem
b3cbfcfb9d General code clean up for easier debugging. 2022-02-15 11:52:39 -05:00
Scott Idem
a2f755b914 General code clean up for easier debugging. 2022-02-15 11:51:16 -05:00
Scott Idem
85e2195297 General code clean up for easier debugging. 2022-02-15 11:37:53 -05:00
Scott Idem
cfa58f2c32 General code clean up for debugging. Also work on event badge related. 2022-02-15 11:33:35 -05:00
Scott Idem
57b3b358ad Work on event person tracking for exhibitors 2022-02-14 17:27:26 -05:00
Scott Idem
13fe6adbb2 Work on event person tracking for exhibitors 2022-02-14 17:14:18 -05:00
Scott Idem
6fc7905b04 Work on Cvent API and IDAA 2022-02-09 19:44:16 -05:00
Scott Idem
709952bf60 Minor changes 2022-02-08 19:12:54 -05:00
Scott Idem
08591c5de6 We should not check the referrer if no access key was given. At least I think so...? 2022-02-07 16:39:29 -05:00
Scott Idem
74727c2d77 Work on site domain FQDN lookup with referrer. 2022-02-07 16:24:27 -05:00
Scott Idem
02a8948ee4 Work on site domain FQDN lookup with referrer. For some reason being passed as None. 2022-02-07 16:11:45 -05:00
Scott Idem
7ee782fb6a Work with Cvent API bug fix related to rate limit. 2022-02-07 15:03:24 -05:00
Scott Idem
8d7063c116 Work with Cvent API for IDAA members. 2022-02-04 18:15:39 -05:00
Scott Idem
7ca26ed325 Work with Cvent API for IDAA members. 2022-02-03 18:41:37 -05:00
Scott Idem
d8065efec5 Cleaning up create and update Cvent person in Aether for IDAA members. Added lookup by external ID and email for Cvent. 2022-02-02 18:16:58 -05:00
Scott Idem
2f4152ff8b Cleaning up create and update Cvent person in Aether for IDAA members. 2022-02-02 15:04:48 -05:00
Scott Idem
217530fb93 Working on Cvent API for IDAA members. Adding and updating a person along with membership info now works. Updating the membership status for person and user tables now works. 2022-02-01 19:32:10 -05:00
Scott Idem
fe3cea131a Working on Cvent API for IDAA members. 2022-02-01 15:15:49 -05:00
Scott Idem
4abf02aa15 Working on general code clean up and Cvent API related 2022-02-01 12:17:25 -05:00
Scott Idem
779bbb2f82 Working on Cvent API related 2022-01-31 19:37:02 -05:00
Scott Idem
1d39a01ded Working on Cvent API related 2022-01-28 16:33:23 -05:00
Scott Idem
6d135f9cd7 Adding new field for badge template 2022-01-27 17:17:18 -05:00
Scott Idem
59b263e5b8 Work on order and Strip related. Also general code clean up 2022-01-25 20:01:46 -05:00
Scott Idem
86b06226cb General code clean up 2022-01-24 13:06:27 -05:00
Scott Idem
b0537723a7 Working on order, carts, and Stripe 2022-01-23 18:31:45 -05:00
Scott Idem
4a8c77d4e0 General code clean up and bug fixes. Still working on order and orders that are carts 2022-01-22 18:53:21 -05:00
Scott Idem
ab0b900721 Still working on order and orders that are carts 2022-01-21 16:41:40 -05:00
Scott Idem
afe44bac89 Bug fixes or some issues around user auth, verification, and password change 2022-01-20 18:07:27 -05:00
Scott Idem
c0fdc4d2dc Work on updating order and order line models. Some code clean up. 2022-01-20 15:32:46 -05:00
Scott Idem
d1d3b30ece Work on redoing orders and related. A lot of code clean up. I think it works pretty well. 2022-01-19 18:51:24 -05:00
Scott Idem
9dbbc26d06 A lot of code clean up 2022-01-18 20:04:10 -05:00
Scott Idem
3d2c7282dd A lot of code clean up 2022-01-18 20:03:46 -05:00
Scott Idem
d6caa819b0 Code clean up 2022-01-18 18:58:38 -05:00
Scott Idem
24807db81c Moving things to use the common_route_params. Rewriting most of things order and order_cart related! Updating address and contact related. General code clean up. 2022-01-18 18:49:23 -05:00
Scott Idem
49d8a7f0dc Moving things to use the common_route_params. Rewriting most of things order and order_cart related! 2022-01-18 16:32:21 -05:00
Scott Idem
6ccc0eb6c9 Code clean up 2022-01-18 14:20:54 -05:00
Scott Idem
6d391f730f Code clean up 2022-01-18 14:20:48 -05:00
Scott Idem
037626cb76 Code clean up 2022-01-18 14:16:43 -05:00
Scott Idem
fab61f4e17 Moving things to use the common_route_params 2022-01-18 14:14:23 -05:00
Scott Idem
b2a1c8f85a Moving things to use the common_route_params 2022-01-18 14:12:33 -05:00
Scott Idem
15c1d6b463 Moving things to use the unified functions for SQL enable and SQL LIMIT OFFSET 2022-01-17 19:01:38 -05:00
Scott Idem
6b21a33625 Moving things to use the common_route_params and unified functions for SQL enable and SQL LIMIT OFFSET 2022-01-17 18:57:31 -05:00
Scott Idem
9efaa2bf61 Work on API CRUD, person, membership 2022-01-14 17:45:34 -05:00
Scott Idem
539fe803af Work on membership type and products and related 2022-01-13 18:13:56 -05:00
Scott Idem
771778669e Work on membership and related 2022-01-12 18:58:07 -05:00
Scott Idem
55063d0f94 A LOT of code clean up. More changes to membership tables, views, routes, methods, and models renamed. 2022-01-11 19:45:55 -05:00
Scott Idem
6e764b21e1 Code clean up. More changes to membership tables, views, routes, methods, and models renamed. 2022-01-11 16:56:26 -05:00
Scott Idem
163e57ef76 Code clean up. More changes to membership tables, views, routes, methods, and models renamed. 2022-01-11 16:24:01 -05:00
Scott Idem
8953fc32d8 Code clean up. Membership tables, views, routes, methods, and models renamed. 2022-01-11 14:12:51 -05:00
Scott Idem
253ed6c19b Code clean up. Membership Person 2022-01-11 12:00:33 -05:00
Scott Idem
0112c9d015 Code clean up. Membership 2022-01-10 18:46:01 -05:00
Scott Idem
e6e6f19418 Code clean up. A lot. Added new account config option 2022-01-07 19:00:37 -05:00
Scott Idem
f2be7767cb Code clean up. A lot. 2022-01-07 16:01:22 -05:00
Scott Idem
5489b80ff0 Code clean up. Bug fixes for person, user, contact, and address methods. Fix bug for get_account_id_w_for_type_id() 2022-01-07 13:35:44 -05:00
Scott Idem
29c6770581 Code clean up. Bug fixes for person, user, contact, and address methods. There is a bug related get_account_id_w_for_type_id() 2022-01-06 18:34:53 -05:00
Scott Idem
597c765673 Code clean up. Bug fixes for person, user, contact, and address methods 2022-01-06 16:56:38 -05:00
Scott Idem
c01e668d9e Code clean up. Simplifying person, user, contact, and address methods 2022-01-06 13:47:23 -05:00
Scott Idem
567f6a6302 Code clean up. Simplifying person, user, contact, and address methods 2022-01-06 13:19:12 -05:00
Scott Idem
c127e0822c Code clean up. Person and User related is being worked on. 2022-01-06 12:25:53 -05:00
Scott Idem
a2de9572ba Work on simplifying functions. ID lookups are better. Person and User related is being worked on. 2022-01-06 11:33:36 -05:00
Scott Idem
6691098ddf Work on simplifying functions 2022-01-05 20:42:01 -05:00
Scott Idem
b32900a44f A lot of route common params clean up 2022-01-05 20:39:34 -05:00
Scott Idem
8373f52f22 A lot of route common params clean up 2022-01-05 15:54:32 -05:00
Scott Idem
104bc8d08a A lot of route common params clean up 2022-01-05 15:44:06 -05:00
Scott Idem
e5d80908ce A lot of route common params clean up 2022-01-05 15:30:37 -05:00
Scott Idem
0cad38ecad A lot of route common params clean up 2022-01-05 15:30:05 -05:00
Scott Idem
280237b46c A lot of route common params clean up 2022-01-05 15:29:07 -05:00
Scott Idem
a70f931688 A lot of route common params clean up 2022-01-05 15:22:49 -05:00
Scott Idem
9a51e75892 Work on header validation 2022-01-05 13:50:12 -05:00
Scott Idem
3cfc8a69dc Work on membership related 2022-01-04 19:01:02 -05:00
Scott Idem
bf58944fda Work on order reports and related 2022-01-04 14:34:06 -05:00
Scott Idem
ee3ae12735 Work on order reports and related 2022-01-04 13:43:30 -05:00
Scott Idem
0cad4e68eb Clean up 2022-01-03 18:29:11 -05:00
Scott Idem
2859eebcc5 Clean up 2021-12-30 21:23:29 -05:00
Scott Idem
7fe0c4ae27 Including account_name in user model 2021-12-30 18:58:56 -05:00
Scott Idem
2b809e0f81 Clean up 2021-12-30 18:44:19 -05:00
Scott Idem
0866fbbed6 Clean up 2021-12-29 19:42:27 -05:00
Scott Idem
e99be8070e IDAA importing updates 2021-12-17 18:21:44 -05:00
Scott Idem
615d5dab17 Clean up 2021-12-17 16:17:17 -05:00
Scott Idem
398897efe1 Just lots of work and tweeks 2021-12-15 21:51:58 -05:00
Scott Idem
7066715495 Work on person, user, contact, address improvements 2021-12-14 19:38:55 -05:00
Scott Idem
0863303740 Work on posts and post comments 2021-12-14 14:46:45 -05:00
Scott Idem
39db1999fb Code clean up. Working on returning proper 404 vs 400 responses if the results are empty and nothing went wrong. 2021-12-13 18:55:31 -05:00
Scott Idem
92a44b9f41 Code clean up. Working on returning proper 404 vs 400 responses if the results are empty and nothing went wrong. 2021-12-13 17:07:45 -05:00
Scott Idem
133cfab42f Code clean up. Working on returning proper 404 vs 400 responses if the results are empty and nothing went wrong. 2021-12-13 17:02:21 -05:00
Scott Idem
2ef53d7a51 Code clean up and made sql_select function return an actual empty list if requested as_list but no results. 2021-12-13 16:19:23 -05:00
Scott Idem
59e96d7d3c Work on event meeting list end point, methods, and models 2021-12-13 15:12:54 -05:00
Scott Idem
b22a84f054 Adding activity_log routes, methods, and models. 2021-12-10 21:51:41 -05:00
Scott Idem
08335c8b11 Clean up and testing and bug fixes 2021-12-08 18:40:59 -05:00
Scott Idem
7aa2ecc4af Clean up and testing. Starting to mark things as deprecated. 2021-12-08 12:27:29 -05:00
Scott Idem
39eb3baebd Work on person creation email and bug fixes 2021-12-03 19:02:50 -05:00
Scott Idem
630739aae6 Added person lookup by email and email auth url key. 2021-12-02 17:57:01 -05:00
Scott Idem
2c0af0a321 Added person lookup by email and other minor changes. 2021-12-01 18:19:36 -05:00
Scott Idem
6883bebd16 Work on importing person and membership data 2021-11-29 15:51:26 -05:00
Scott Idem
96a159c007 Work on importing person and membership data 2021-11-29 15:46:18 -05:00
Scott Idem
1cb0372009 Work on importing person and membership data 2021-11-29 15:09:31 -05:00
Scott Idem
6d19f687b2 Work on importing person and membership data 2021-11-29 14:48:14 -05:00
Scott Idem
54a084d2cb Work on temporary hosted files 2021-11-25 12:54:41 -05:00
Scott Idem
523a7378c6 Work on temporary hosted files 2021-11-23 17:17:40 -05:00
Scott Idem
24c7411109 Work on temporary hosted file downloads and export creation. 2021-11-23 16:51:41 -05:00
Scott Idem
4fa32f63fd Work on order related endpoints. 2021-11-22 18:32:44 -05:00
Scott Idem
b6a8e172a4 Work on order related endpoints. 2021-11-22 16:41:37 -05:00
Scott Idem
841e53628f Turn off some debug 2021-11-19 18:37:18 -05:00
Scott Idem
195b5097a4 Work on orders and related 2021-11-19 17:37:03 -05:00
Scott Idem
e805e69dc7 Work on order endpoint and related methods. 2021-11-19 15:59:35 -05:00
Scott Idem
f1580dbfe7 Work on orders, person, and user 2021-11-19 15:52:06 -05:00
Scott Idem
dbc99e0d8d Work on orders, person, and user 2021-11-19 15:16:48 -05:00
Scott Idem
f71bd74aa0 Planning out a core object model 2021-11-18 21:20:41 -05:00
Scott Idem
dee958aa2b Work on user endpoints 2021-11-18 15:25:06 -05:00
Scott Idem
30693cd629 Work on person related information, order, and order_cart 2021-11-16 14:28:22 -05:00
Scott Idem
0b3b066b9b Work on event and lookup related end points, methods, and models 2021-11-10 17:57:51 -05:00
Scott Idem
413b44b4d2 Turn down some debugging 2021-11-09 19:34:52 -05:00
Scott Idem
03aceae130 User email look up improvement 2021-11-09 19:25:35 -05:00
Scott Idem
093cc3913b Work on event related routes, methods, and models 2021-11-09 15:23:12 -05:00
Scott Idem
567223ed66 Work on event stuff 2021-11-09 11:59:18 -05:00
Scott Idem
e97e8e56e8 Work on event stuff 2021-11-08 17:00:51 -05:00
Scott Idem
30983ec507 Work on event launcher and demo ready 2021-11-08 16:10:42 -05:00
Scott Idem
94f5f40570 Work on archives and related 2021-11-02 18:26:16 -04:00
Scott Idem
82a6cb4525 Work on archives 2021-11-01 17:17:02 -04:00
f31626f51c Updated CORS 2021-10-24 18:42:29 -04:00
e8a15e0ae7 Why can I get event_presenter_cat? 2021-10-23 23:20:48 -04:00
7fd06adc2d Uncommented inc_event_presenter_cat. Because why was it commented? 2021-10-23 23:13:02 -04:00
Scott Idem
decef795c2 Work on event files and event location and related 2021-10-22 17:25:04 -04:00
Scott Idem
70981e0c3f Work on event files and hosted files 2021-10-22 13:54:50 -04:00
Scott Idem
661e9ca417 Work on event launcher and related 2021-10-22 04:07:41 -04:00
Scott Idem
bde6ec1a2b Work on event launcher and related 2021-10-21 01:08:07 -04:00
Scott Idem
53c6c6b51c Work on event importing and related 2021-10-19 21:21:11 -04:00
Scott Idem
f5f09f0516 Work on event launcher and related 2021-10-10 01:21:59 -04:00
Scott Idem
95e2a2c3f0 Work on IDAA importing 2021-10-08 15:17:41 -04:00
Scott Idem
95ed1bae84 Work on IDAA importing 2021-10-08 15:03:05 -04:00
Scott Idem
9b99199be6 Work on IDAA importing 2021-10-08 14:53:14 -04:00
Scott Idem
17a2722012 Work on IDAA importing 2021-10-08 14:14:07 -04:00
Scott Idem
ceb16dad93 Work on IDAA data import 2021-10-08 12:10:32 -04:00
Scott Idem
50762f8214 Work on Impexium 2021-10-07 23:14:10 -04:00
Scott Idem
43eaaf9de7 Work on things 2021-10-07 23:11:34 -04:00
Scott Idem
dab011fbee Work on IDAA, badges, and events in general 2021-10-07 14:53:18 -04:00
Scott Idem
c96459a128 Working on file uploads and event files. 2021-10-06 23:15:09 -04:00
Scott Idem
ec401056b2 Working on file uploads and event files. 2021-10-06 23:01:56 -04:00
Scott Idem
4f6d065990 Working on user login, verification, and password change. 2021-10-06 17:35:09 -04:00
Scott Idem
455cc36a69 Working on user login, verification, and password change. 2021-10-06 17:34:29 -04:00
Scott Idem
7c919b513c Working on user login and related updates 2021-10-06 13:26:03 -04:00
Scott Idem
d6892c168c Working on user login with membership for IDAA 2021-10-04 21:05:42 -04:00
Scott Idem
b816d3dbb1 Working on IDAA data imports 2021-10-04 19:59:30 -04:00
Scott Idem
b1a4386de4 Working on IDAA data imports 2021-10-04 19:31:58 -04:00
Scott Idem
e95f5dd1b2 Working on IDAA data imports 2021-10-04 19:30:10 -04:00
Scott Idem
6028c5d48d Working on IDAA data imports 2021-10-04 19:20:14 -04:00
Scott Idem
6f3ee10860 Working on IDAA data imports 2021-10-04 18:53:36 -04:00
Scott Idem
6bf0831566 Working on IDAA data imports 2021-10-04 18:41:13 -04:00
Scott Idem
7e9a8a493d Cont edu cert person model update 2021-10-01 18:12:53 -04:00
Scott Idem
9facc58e3a Added person list results for cont edu cert person. 2021-10-01 17:15:31 -04:00
Scott Idem
7e5cdc3b7e Code clean up. Better debugging. Work on event files and related 2021-09-29 19:04:44 -04:00
Scott Idem
f98c57157b Code clean up. Better debugging 2021-09-29 17:44:51 -04:00
Scott Idem
c77e066f9e Code clean up. Better debugging 2021-09-29 16:16:04 -04:00
Scott Idem
be788fc5e6 Code clean up. Better debugging. Work on event session, presentation, and presenter. 2021-09-29 14:18:34 -04:00
Scott Idem
b2c70c0c87 Code clean up. Work on event session, presentation, and presenter. 2021-09-28 18:32:19 -04:00
Scott Idem
e231090168 Code clean up. Work on event session and file counts for event models 2021-09-28 18:06:35 -04:00
Scott Idem
fb7efa9930 Code clean up. Adding missing response=response to mk_resp() function calls 2021-09-28 15:57:11 -04:00
Scott Idem
e0b9c975b0 Work on event related and added new create_on and updated_on forced change to UTC. 2021-09-28 15:04:09 -04:00
Scott Idem
35e9178472 Work on event patch and list, account list, and minor changes 2021-09-28 13:22:27 -04:00
Scott Idem
831ff8d4a0 Work on event person tracking and related 2021-09-27 19:14:41 -04:00
Scott Idem
7ceb125026 Work on event person tracking 2021-09-23 16:04:16 -04:00
Scott Idem
0778d472f8 Work on badges 2021-09-22 18:17:28 -04:00
Scott Idem
ae16f30c42 Turning off some debug 2021-09-21 18:01:35 -04:00
Scott Idem
0f87f69077 Work on things related to event_person, tracking, badges, sessions, etc 2021-09-21 17:30:20 -04:00
Scott Idem
200ee82b9d Bug fix for multiple user functions missing response=response in mk_resp() call. 2021-09-20 19:26:57 -04:00
Scott Idem
8a3101d562 Bug fix for lookup_email 2021-09-20 19:22:15 -04:00
Scott Idem
adb90fd214 Minor updates 2021-09-17 17:31:41 -04:00
Scott Idem
23c63ff09d Update pip install and added qrcode 2021-09-15 21:37:29 -04:00
Scott Idem
6536257042 Minor updates to badge related 2021-09-14 19:36:36 -04:00
Scott Idem
15fd32b252 Work on a lot of things. Mainly cleaning up person and profile related fields in multiple tables. 2021-09-10 18:12:24 -04:00
Scott Idem
72820f08ae Minor updates 2021-09-09 19:00:49 -04:00
Scott Idem
b2129d6e05 Work on event, session, presentatio, presenter, and person related 2021-09-09 18:58:17 -04:00
Scott Idem
29d3ed38cb Work on event and person related 2021-09-09 13:47:02 -04:00
Scott Idem
54a43cc124 Work on person methods and related and event badge everything 2021-09-08 15:58:35 -04:00
Scott Idem
d0d8392817 Work on person methods and related 2021-09-08 12:22:22 -04:00
Scott Idem
84aecddc7c Work on session proposals and related event person profile. 2021-09-07 18:49:21 -04:00
Scott Idem
edd7beb4d7 Updates for hosted file sections and general code clean up. 2021-09-07 14:58:04 -04:00
Scott Idem
a625d33995 General code clean up for SQL functions and related. 2021-09-07 14:22:57 -04:00
Scott Idem
af0da563f1 Cleaned up and easier debugging for SQL statements. A lot less redundant information. 2021-09-07 14:01:40 -04:00
Scott Idem
64b77ec238 Working on how hosted files are saved. Moving them into two letter subdirectories. 2021-08-26 20:52:13 -04:00
Scott Idem
4a839b91d7 Working on stuff related to session proposals. 2021-08-26 13:35:01 -04:00
Scott Idem
112f6d4da4 Working on stuff related to session proposals. 2021-08-26 13:27:01 -04:00
Scott Idem
acf6842017 Working on stuff related to session proposals. Lots of bug fixes....... 2021-08-26 04:21:39 -04:00
Scott Idem
6508461ae6 Working on stuff related to session proposals. Again... I think it all works now. 2021-08-26 01:23:14 -04:00
Scott Idem
b6df6a218d Working on stuff related to session proposals. Again.... 2021-08-25 22:29:48 -04:00
Scott Idem
3c13b62502 Working on contact and address create update v4 along with person and user creation and update. 2021-08-25 20:06:07 -04:00
Scott Idem
107366d4f8 Working on contact and address create update v4 2021-08-25 18:48:55 -04:00
Scott Idem
a508307df3 Working on contact create update v4 2021-08-25 17:57:51 -04:00
Scott Idem
c93792634a Working on event person, registration, badge, session, presentation, and presenter create and update. _v3 things 2021-08-25 16:25:46 -04:00
Scott Idem
8ff404e534 Code clean up and standardize 2021-08-25 11:16:02 -04:00
Scott Idem
1369874dc2 Code clean up and standardize 2021-08-25 10:58:39 -04:00
Scott Idem
7859d1d2b2 Working on event person, registration, badge, session, presentation, and presenter create and update. _v3 things 2021-08-24 20:31:00 -04:00
Scott Idem
25ebdb6e76 Working on event person, registration, badge, session, presentation, and presenter create and update. 2021-08-24 17:49:10 -04:00
Scott Idem
13c2375727 Working on event session, presentation, and presenter create and update. 2021-08-23 16:06:49 -04:00
Scott Idem
bbf5d3e2bc General code clean up. 2021-08-23 13:00:04 -04:00
Scott Idem
8a92ffb6ba General code clean up. 2021-08-23 12:55:35 -04:00
Scott Idem
683597e2bf General code clean up. 2021-08-23 12:54:27 -04:00
Scott Idem
5606cdb12f Some general clean up and updates. 2021-08-23 12:43:00 -04:00
Scott Idem
e281b1d385 Some general clean up and updates. 2021-08-23 12:36:30 -04:00
Scott Idem
824bdd29a2 Work on event, event_session, event_presentation, event_presenter, user, and person routes, methods, and models 2021-08-21 16:04:17 -04:00
Scott Idem
67b8435e08 Work on API tokens 2021-08-20 14:34:02 -04:00
Scott Idem
5bed9aadb4 Added page routes and related methods and updated model. Also general clean up. 2021-08-20 12:53:21 -04:00
Scott Idem
09891b5d83 Minor changes 2021-08-19 16:28:31 -04:00
Scott Idem
c813821922 Change from hardcode account 99 2021-08-19 15:19:07 -04:00
Scott Idem
1ef1cc2530 Change from hardcode account 99 2021-08-19 15:16:02 -04:00
Scott Idem
57c1c61ce1 Typo 2021-08-19 15:13:43 -04:00
Scott Idem
4eaeb152fe Working on data importing. Mainly for IDAA membership. 2021-08-19 15:10:59 -04:00
Scott Idem
02f719d206 Working on data importing. Mainly for IDAA membership. 2021-08-18 20:23:47 -04:00
Scott Idem
d1c7a88c64 Working on data importing. Mainly for IDAA membership. 2021-08-18 18:25:22 -04:00
Scott Idem
ad9417911a Working on event registration and related. 2021-08-17 20:53:01 -04:00
Scott Idem
2f037290d9 Working on IDAA membership imports 2021-08-16 18:15:44 -04:00
Scott Idem
8fd8b89ff4 Working on event registration. Why wont it load??? 2021-08-13 18:09:41 -04:00
Scott Idem
1bb3867463 Working on stuff 2021-08-13 17:09:32 -04:00
Scott Idem
0ff9f1832e Working on adding products to order_cart 2021-08-11 19:18:28 -04:00
Scott Idem
616011edfe Bug fix for special characters in GET params because of % symbol 2021-08-11 17:41:34 -04:00
Scott Idem
cfd85435f2 Finally returning correct HTTP status codes 2021-08-10 19:06:40 -04:00
Scott Idem
a1b9d3c518 Still adding in Response everywhere... 2021-08-10 18:30:37 -04:00
Scott Idem
d933395a9f A lot of code clean up! Also adding in Response everywhere... 2021-08-10 18:09:34 -04:00
Scott Idem
73466456ee Working on redoing order to get rid of order_cart. 2021-08-09 20:16:07 -04:00
Scott Idem
1dba813d4b Work on hosted files and archive content and related bug fixes. 2021-08-08 15:47:32 -04:00
Scott Idem
4ea30cef62 Change to person.contact_id and related bug fixes. 2021-08-08 12:35:59 -04:00
Scott Idem
3310d9fc8b Change to person.contact_id and related bug fixes. 2021-08-08 11:54:57 -04:00
Scott Idem
65b5e6ade6 Bug fix in order cart line model. Need to create another _Base for order_line and order_cart_line 2021-08-07 20:27:45 -04:00
Scott Idem
5e02b424a8 Bug fix in order cart line model. 2021-08-07 18:59:35 -04:00
Scott Idem
562a3ffa36 Bug fix in order cart method and added email to person model. 2021-08-07 18:33:02 -04:00
Scott Idem
106a186ea6 A lot of unplanned clean up and created some new/missing methods and routes. Mainly working on order and order_cart related stuff. 2021-08-07 18:15:59 -04:00
Scott Idem
9915c7d9ed Working on orders and products with for_type and for_id. To help link cont_edu and other future product types. 2021-08-07 00:04:30 -04:00
Scott Idem
17cb6786f8 Working on orders and order carts 2021-08-06 18:53:24 -04:00
Scott Idem
5746d8d34b Working on orders and order carts 2021-08-06 18:22:41 -04:00
Scott Idem
4b2599ae71 Working on cont_edu_cert_person search 2021-08-06 15:09:41 -04:00
Scott Idem
d685792061 Working on integration with Svelte and cont_edu_cert_person importing 2021-08-05 19:21:52 -04:00
Scott Idem
56218b5dd8 Working on integration with Svelte and bug fix for user 2021-08-05 17:56:25 -04:00
Scott Idem
0e24c21db2 Working on integration with Svelte and cont_edu 2021-08-05 14:05:25 -04:00
Scott Idem
59ce3acd8f Working on integration with Svelte and cont_edu 2021-08-04 21:10:51 -04:00
Scott Idem
42242a6b35 Working on integration with Svelte and cont_edu 2021-08-03 17:56:16 -04:00
Scott Idem
057806e3d9 Working on products and carts and stuff for IDAA 2021-08-02 18:52:13 -04:00
Scott Idem
ac21a67c8a Working on products and carts 2021-08-02 17:25:48 -04:00
Scott Idem
c639c2b0a0 Working on products and carts 2021-08-01 19:46:14 -04:00
Scott Idem
1a493b85f8 Need to include xlrd in the requirements.txt 2021-07-30 18:16:33 -04:00
Scott Idem
475239f6e9 Working on importing IDAA data 2021-07-30 18:13:31 -04:00
Scott Idem
eb9e1b7167 Working on continuing education certs 2021-07-28 19:20:50 -04:00
Scott Idem
194642e927 Working on continuing education certs 2021-07-28 17:39:14 -04:00
Scott Idem
5e73c588a4 Working on continuing education certs 2021-07-28 14:13:51 -04:00
Scott Idem
e43cf35ec4 Working on continuing education certs 2021-07-28 12:33:07 -04:00
Scott Idem
5f66780522 Working on membership and related modules 2021-07-27 18:37:52 -04:00
Scott Idem
ae1cd473ed Working on membership and related modules 2021-07-27 15:39:47 -04:00
Scott Idem
e61089ce2c Minor changes 2021-07-26 15:08:41 -04:00
Scott Idem
18cc4953ad Minor changes 2021-07-22 18:26:57 -04:00
Scott Idem
8f0024fa26 Working on journal related 2021-07-22 17:34:19 -04:00
Scott Idem
6b2e0b67e0 Working on journal related 2021-07-22 14:04:25 -04:00
Scott Idem
b9483b5bdd Working on person and user and related 2021-07-22 12:24:20 -04:00
Scott Idem
7ad98c2946 Working on person and user and related 2021-07-21 18:56:22 -04:00
Scott Idem
e6451f29f8 Working on creating a person with related objects. 2021-07-20 20:18:43 -04:00
Scott Idem
f9db444764 Updates for posts and events 2021-07-20 16:17:39 -04:00
Scott Idem
50a8ce271d Bug fix for post lists 2021-07-20 14:02:08 -04:00
Scott Idem
6216c3786c Turning off some debugging 2021-07-15 18:10:27 -04:00
Scott Idem
0bd7f44dd8 Work on API keys and tokens clean up and person list 2021-07-14 18:18:35 -04:00
Scott Idem
790dc2961b Work on API keys and tokens clean up 2021-07-14 17:16:34 -04:00
Scott Idem
6bb2d7f761 Work on API keys and tokens 2021-07-14 17:12:20 -04:00
Scott Idem
6f8e18750c Work on Svelte integration 2021-07-13 18:19:36 -04:00
Scott Idem
e4d60309a0 Work on event list 2021-07-12 18:19:18 -04:00
Scott Idem
9d1b520718 A lot of changes related to person and membership and event 2021-07-12 17:47:16 -04:00
Scott Idem
49805f48c9 A lot of changes related to person and membership 2021-07-09 17:34:02 -04:00
Scott Idem
2b8d00551c Improving client viewing log 2021-07-02 17:55:37 -04:00
Scott Idem
6bbd129634 Improving client viewing log 2021-07-02 12:40:49 -04:00
Scott Idem
a494a9ca68 Added logging for client viewing 2021-07-02 12:11:12 -04:00
Scott Idem
a56804cee6 Working on membership, fundraising, and products and other things 2021-07-01 17:55:25 -04:00
Scott Idem
3631f2f122 Working on membership, fundraising, and products 2021-07-01 14:02:24 -04:00
Scott Idem
59c151f639 Working on event (meet) and related updates 2021-06-30 15:47:31 -04:00
Scott Idem
830b9ee608 Clean up and I think I fixed a weird bug with Pydantic models and the list object type. It seems to try for a dict conversion first if Union[dict,list] is used. 2021-06-29 18:12:52 -04:00
Scott Idem
2d988fc405 Working on post and post_comment and event 2021-06-29 15:15:46 -04:00
Scott Idem
0e15d8e3c0 Working on post and post_comment 2021-06-29 12:51:19 -04:00
Scott Idem
b3093139b2 Working on various... order, post, cart, etc 2021-06-28 18:48:26 -04:00
Scott Idem
275072160e Working on membership, person, user, and order and general clean up. 2021-06-28 15:26:31 -04:00
Scott Idem
a0514b5179 Working on membership, person, user, and order 2021-06-28 14:23:06 -04:00
Scott Idem
f55d9c2c62 Working on membership, person, and user 2021-06-25 18:49:08 -04:00
Scott Idem
ca43cc4dce Working on membership and users 2021-06-25 11:15:58 -04:00
Scott Idem
d85ab35812 Working on membership stuff 2021-06-24 17:50:11 -04:00
Scott Idem
f5f60a403c Working on membership still... 2021-06-24 14:33:23 -04:00
Scott Idem
48837ffa6f Working on membership still... 2021-06-24 14:22:47 -04:00
Scott Idem
ea254c584f Working on membership still... 2021-06-24 14:17:29 -04:00
Scott Idem
6f21a567a7 General clean up 2021-06-24 12:28:44 -04:00
Scott Idem
e11639bf24 General clean up 2021-06-24 12:26:35 -04:00
Scott Idem
6e0ceefa48 Working on all module routes, methods, and models 2021-06-23 17:44:02 -04:00
Scott Idem
6189ea154d Working on all module routes, methods, and models 2021-06-23 16:38:35 -04:00
Scott Idem
c1d23d15f4 Working on all module routes, methods, and models 2021-06-22 18:00:34 -04:00
Scott Idem
b6ca0eb3f2 Working on membership module routes, methods, and models 2021-06-21 18:10:24 -04:00
Scott Idem
728682a07f Working on membership module routes, methods, and models 2021-06-21 17:53:04 -04:00
Scott Idem
ce44e93b4d Working on all the routes, methods, and models. 2021-06-18 17:49:32 -04:00
Scott Idem
938aabb2a8 Working on all the routes, methods, and models. 2021-06-18 14:09:07 -04:00
Scott Idem
bb2f14b67c Changing hosted_file_link field names. 2021-06-18 09:58:31 -04:00
Scott Idem
6d4463e57e Save before hosted_file_link DB field name changes. 2021-06-18 09:42:29 -04:00
Scott Idem
47ce2380be Work on lots of methods and models. 2021-06-17 17:53:55 -04:00
Scott Idem
c17724cea1 Work on lots of methods and models. 2021-06-17 16:54:46 -04:00
Scott Idem
e9399e107f Work on file uploads and session proposals 2021-06-16 15:08:05 -04:00
Scott Idem
0dc50e4509 Work on file uploads and listing event files. 2021-06-15 18:05:56 -04:00
Scott Idem
415e452988 Work on file uploads. More error checking. 2021-06-14 17:57:15 -04:00
Scott Idem
2d180006b6 Work on create the hosted_file entry in the DB. 2021-06-14 17:20:59 -04:00
Scott Idem
8f51ed1158 More robust and clean up 2021-06-14 16:23:30 -04:00
Scott Idem
7080b51dfc More robust and clean up 2021-06-14 16:23:12 -04:00
Scott Idem
2eda94baba Clean up of hosted_file 2021-06-14 16:07:01 -04:00
Scott Idem
eb23d16ad5 Unplanned work for file uploads 2021-06-14 16:04:37 -04:00
Scott Idem
fe232b8cba Clean up of code... 2021-06-11 18:42:28 -04:00
Scott Idem
64d6e87ca2 Trying not to break things. How do I deal with import loops??? 2021-06-11 17:10:08 -04:00
Scott Idem
06c1310455 Trying not to break things. How do I deal with import loops??? 2021-06-11 17:06:00 -04:00
Scott Idem
585692bb1d User related clean up 2021-06-11 15:31:18 -04:00
Scott Idem
d1f3a4c27e Clean up and working on user related models, methods, and routes. 2021-06-11 15:04:42 -04:00
Scott Idem
29dfcc5440 Clean up and working on user related models, methods, and routes. 2021-06-11 15:00:39 -04:00
Scott Idem
03bb55e62a Moving stuff around to make it more easy to manage. Adding models, methods, and routes. 2021-06-11 14:07:10 -04:00
Scott Idem
9c679765e5 Moving stuff around to make it more easy to manage 2021-06-11 10:55:08 -04:00
Scott Idem
60928b0521 Syntax fixes 2021-06-11 08:58:04 -04:00
72f6c9974a Work on post and post comment list. 2021-06-10 21:46:00 -04:00
6cbe3b28f2 Work on post and post comment list. 2021-06-10 21:45:29 -04:00
Scott Idem
19bba3d8a9 Work on better way to update objects. A lot of work! Also a lot of clean up. 2021-06-10 18:31:53 -04:00
Scott Idem
e45bb2fbcd Clean up 2021-06-10 17:45:20 -04:00
Scott Idem
4e6fedcffd Work on better way to update objects. A lot of work! Also a lot of clean up. 2021-06-10 17:42:31 -04:00
Scott Idem
717db418f9 Work on better way to update objects. A lot of work! 2021-06-10 17:03:12 -04:00
Scott Idem
b8da9d99eb Work on better way to update objects. 2021-06-10 15:23:57 -04:00
264fced5a6 Work on inserts and updates for address, contact, organization, person, user 2021-06-09 22:04:47 -04:00
3e3466237a Minor changes 2021-06-09 20:13:34 -04:00
Scott Idem
45ef0d2fe9 A lot of work on event related modules. Also a lot of clean up. 2021-06-08 18:20:59 -04:00
Scott Idem
d37234d7d8 A lot of work on event related modules. Also a lot of clean up. 2021-06-08 15:56:00 -04:00
Scott Idem
b7f6a5ee2a A lot of work on event related modules. Also a lot of clean up. 2021-06-07 17:32:50 -04:00
Scott Idem
63baf4b2e9 A lot of work on event related modules. Also a lot of clean up. 2021-06-07 17:32:42 -04:00
Scott Idem
33ec6a4acb A lot of work on event related modules. Also a lot of clean up 2021-06-07 16:48:55 -04:00
Scott Idem
979e0c2174 A lot of work on event related modules 2021-06-04 17:28:52 -04:00
Scott Idem
2788546cc8 A lot of work on event related modules 2021-06-04 17:10:09 -04:00
Scott Idem
627bc8c4df A lot of work on event related modules 2021-06-04 16:01:23 -04:00
Scott Idem
735e50dc0f Not much 2021-06-04 11:23:08 -04:00
Scott Idem
1f525f4c80 Not much work done. Holly and Michael :-( 2021-06-01 21:45:19 -04:00
Scott Idem
499e08919d Working on event related models and methods. 2021-06-01 16:09:10 -04:00
Scott Idem
734734c9dc Working on event related models and methods. Also, a lot of general clean up of everything. Need to pick back up and work on event_presentation_methods.py. 2021-05-28 17:44:23 -04:00
Scott Idem
b1effc128a Working on event related models and methods. Also, a lot of general clean up of everything. 2021-05-28 16:03:17 -04:00
Scott Idem
207ce8a960 A lot of general clean up of everything. 2021-05-28 04:01:42 -04:00
Scott Idem
443d2bab77 A lot of general clean up of everything. 2021-05-28 03:52:31 -04:00
Scott Idem
01e1d2f82a General clean up of everything. 2021-05-28 03:39:56 -04:00
Scott Idem
2167c7e897 General clean up of everything. 2021-05-28 03:23:52 -04:00
Scott Idem
09c7b48223 General clean up of models and methods. 2021-05-28 01:40:18 -04:00
Scott Idem
e46032a00b General clean up of models and methods. 2021-05-28 01:30:56 -04:00
Scott Idem
29692ac78e Working on event_person and session proposals. Also general clean up of models and methods. 2021-05-28 00:14:02 -04:00
Scott Idem
486236f0a8 Working on event_person and session proposals. Also general clean up of models and methods. 2021-05-27 23:10:53 -04:00
Scott Idem
65fd3ebe28 Working on event_person and session proposals. Also general clean up of models and methods. 2021-05-27 16:29:27 -04:00
Scott Idem
8f6589cf1c Working on event_person and session proposals 2021-05-26 17:42:18 -04:00
Scott Idem
3afb81d801 File uploads using mk_resp() to return the data. 2021-05-22 13:28:20 -04:00
Scott Idem
0f886374cf File uploads seem to be working well so far. Need to use mk_resp() to return the data in the standard way. 2021-05-22 04:38:50 -04:00
Scott Idem
ada4a75ecb Working on file uploads. Finally! 2021-05-22 03:58:29 -04:00
Scott Idem
ec48bdc3d8 Minor changes 2021-05-20 17:49:44 -04:00
Scott Idem
c94b2fb2e7 Working on Journals module and using React with Axios. 2021-05-19 18:05:41 -04:00
Scott Idem
530932f32a General clean up of field names 2021-05-18 18:20:22 -04:00
Scott Idem
42ea4a7586 General clean up of field names 2021-05-14 16:00:25 -04:00
Scott Idem
fb6246a998 General clean up of field names 2021-05-14 15:11:52 -04:00
Scott Idem
270e611ec0 Updating site _path naming standard 2021-05-14 13:06:09 -04:00
Scott Idem
5fb61d6c63 General clean up 2021-05-14 12:37:52 -04:00
Scott Idem
1d3045f946 Working on the cart and other related things 2021-05-12 16:49:53 -04:00
Scott Idem
ee863face2 Working on the cart and other related things 2021-05-12 15:25:59 -04:00
Scott Idem
9a46b755bf Working on the cart and other related things 2021-05-10 18:29:53 -04:00
Scott Idem
8cbabf48fe Updated get user object 2021-05-07 16:59:30 -04:00
Scott Idem
e3186ddc60 Working on lookups 2021-04-29 18:21:35 -04:00
Scott Idem
576fa80a41 Working on membership management 2021-04-14 17:28:19 -04:00
Scott Idem
d412ddac0a Working on membership management 2021-04-13 17:08:05 -04:00
Scott Idem
130b1953de Bug fix sort of for timezone UTC... again 2021-04-10 00:49:35 -04:00
Scott Idem
9d4d261d1d Bug fix sort of for timezone UTC... again 2021-04-10 00:48:13 -04:00
Scott Idem
2147f54724 Working on new user account and person profile creation and updating 2021-04-09 23:14:28 -04:00
Scott Idem
9840a3e4b7 Working on new user account and person profile creation and updating 2021-04-09 22:22:52 -04:00
Scott Idem
06043197bd Working on user and person 2021-04-08 18:42:59 -04:00
Scott Idem
92b36e502e Working on user and person search by email 2021-04-07 18:22:47 -04:00
Scott Idem
59417c6f91 Working on user log in 2021-04-07 15:09:18 -04:00
Scott Idem
a3403109ae Working on user log in and person object 2021-04-06 17:59:44 -04:00
Scott Idem
3aa3fd8ae7 Minor changes 2021-03-24 12:14:16 -04:00
Scott Idem
26d312559f Working on event registrations 2021-03-23 21:29:05 +00:00
Scott Idem
affec1bf37 Working on user module 2021-03-19 16:34:38 +00:00
Scott Idem
c620ce5f18 Working on user module 2021-03-18 22:42:25 +00:00
Scott Idem
7682c7d080 Working on user module 2021-03-18 22:34:35 +00:00
Scott Idem
dc68970809 General clean up 2021-03-17 21:23:06 +00:00
Scott Idem
166a2212b9 General clean up 2021-03-17 20:53:52 +00:00
Scott Idem
b57e51e8e7 Work membership and general clean up 2021-03-17 19:30:13 +00:00
Scott Idem
4785781cac Work order_cart and related 2021-03-16 21:34:42 +00:00
Scott Idem
bccb8370af Working finally getting this to interact with the actual front end. 2021-03-15 20:08:28 +00:00
Scott Idem
4ba34d594d Working finally getting this to interact with the actual front end. 2021-03-13 02:17:24 -05:00
Scott Idem
46c562871f Working finally getting this to interact with the actual front end. 2021-03-11 18:19:25 -05:00
Scott Idem
143e099e13 Working on clean up of some code... logging and pydantic field config. 2021-03-11 14:19:17 -05:00
Scott Idem
5574f50586 Trying to make this work on my server. Testing... 2021-03-11 12:14:29 -05:00
Scott Idem
070a4be373 Trying to make this work on my server. Testing... 2021-03-11 12:06:36 -05:00
Scott Idem
82e95b1593 Trying to make this work on my server. Testing... 2021-03-11 11:59:30 -05:00
Scott Idem
30e204219a Trying to make this work on my server. Testing... 2021-03-11 11:59:16 -05:00
Scott Idem
d260336313 Working on documentation 2021-03-10 18:02:10 -05:00
Scott Idem
39c2aead8a Working on individual modules 2021-03-10 18:00:51 -05:00
Scott Idem
09f5098b39 Working on individual modules 2021-03-10 15:56:59 -05:00
Scott Idem
260091c2ae Working on individual module routes 2021-03-10 15:30:53 -05:00
Scott Idem
19222ee946 Working on general POST, PATCH, SELECT list, SELECT, and DELETE template functions. 2021-03-09 18:37:30 -05:00
Scott Idem
dd880f0ca2 Working on general POST, PATCH, SELECT list, SELECT, and DELETE template functions. 2021-03-09 18:17:31 -05:00
Scott Idem
768159918f Working on general POST, PATCH, SELECT list, SELECT, and DELETE template functions. 2021-03-09 17:24:29 -05:00
Scott Idem
e30b5a9092 Working on general POST and PATCH template functions. 2021-03-09 16:00:33 -05:00
Scott Idem
291dfc8896 Working on general PATCH template function. 2021-03-09 15:27:17 -05:00
Scott Idem
bc22a8c9d8 Improved SQL results to dict and list of dicts. Should be noticably more efficient under load. 2021-03-09 11:24:04 -05:00
Scott Idem
be2beb04f0 Clean up 2021-03-08 18:38:24 -05:00
Scott Idem
8a47572677 Clean up some of the models and debug 2021-03-08 18:22:12 -05:00
Scott Idem
4f4cd0c985 Adding lots of models 2021-03-08 17:21:05 -05:00
Scott Idem
90c3ab1ba1 Adding lots of models 2021-03-08 16:39:41 -05:00
Scott Idem
3d5fafc4bf Working on the basic SQL select API CRUD and lots of models 2021-03-08 15:53:39 -05:00
Scott Idem
c7f2b16feb Working on the basic SQL select API CRUD 2021-03-05 18:27:12 -05:00
Scott Idem
890263c586 Working on the basic SQL select API CRUD 2021-03-05 17:58:25 -05:00
Scott Idem
28cf7ecf11 Finally updating this... 2021-03-05 17:27:16 -05:00
Scott Idem
420180f20c Unknown 2021-02-26 10:54:04 -05:00
Scott Idem
c6bcbcd702 Minor changes 2021-01-25 17:48:21 -05:00
Scott Idem
a9ea8ea1f7 Minor changes 2021-01-25 16:50:46 -05:00
Scott Idem
cd66578138 Minor changes 2021-01-25 16:49:03 -05:00
Scott Idem
8433960d0d Starting the slow migration to FastAPI... 2020-11-10 17:04:12 -05:00
Scott Idem
6bee9c19cf Adding in logging 2020-09-14 17:49:25 -04:00
Scott Idem
d3b6f46368 Creating templates different types of objects or resources. 2020-09-14 16:37:46 -04:00
Scott Idem
eeedc2ad6f Initial commit with the basics 2020-09-14 12:41:02 -04:00
424 changed files with 82301 additions and 35 deletions

15
.ae_brief Normal file
View File

@@ -0,0 +1,15 @@
# Aether Project Brief: aether_api_fastapi
**Last Updated:** 2026-01-16 17:22:55
**Current Agent:** mcp_agent
## 🛠️ What I Just Did
1. Resolved 'Bootstrap Paradox' bug in lib_config_v3.py (hosted file path fix). 2. Developed Aether Field Manager (ae_field_manage.py) with table/view snapshotting and complex view detection. 3. Verified infrastructure and dry-run logic for vertical-slice field management.
## 🚧 Current Blockers
None. Awaiting user verification of the first 'execute' run for the Field Manager.
## ➡️ Exact Next Steps
1. Execute real-world test of ae_field_manage.py with user. 2. Proceed with Journal Management architecture review (Task 155435511). 3. Initiate Pydantic V2 migration impact analysis.
---
*Generated by ae_brief*

164
.gitignore vendored Normal file → Executable file
View File

@@ -1,33 +1,109 @@
# These are some examples of commonly ignored file patterns.
# You should customize this list as applicable to your project.
# Learn more about .gitignore:
# https://www.atlassian.com/git/tutorials/saving-changes/gitignore
# Node artifact files
node_modules/
dist/
# Compiled Java class files
*.class
# Compiled Python bytecode
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# Log files
*.log
# C extensions
*.so
# Package files
*.jar
# Maven
target/
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# JetBrains IDE
.idea/
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Unit test reports
TEST*.xml
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.env.dev
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
environment/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
# Generated by MacOS
.DS_Store
@@ -35,15 +111,33 @@ TEST*.xml
# Generated by Windows
Thumbs.db
# Applications
*.app
*.exe
*.war
# Added by Scott Idem
# Updated 2024-10-09
# https://github.com/github/gitignore
# Large media files
*.mp4
*.tiff
*.avi
*.flv
*.mov
*.wmv
*.sock
*.bak
*.cfg
*.ini
*.kate-swp
*.pid
*.csv
# *.pdf
*.xlsx
.directory
.vscode
flask_config.py
config.py
# config.cfg
# users.cfg
backups/
development/
log/
logs/
myapp/files/
myapp/file_distribution/
temp/
tmp/

110
GEMINI.md Normal file
View File

@@ -0,0 +1,110 @@
# Gemini Agent Context: Aether API Orchestrator
> **Template Version:** 1.2 (2026-01-26)
> **Purpose:** Standardized memory structure for all Aether Agents.
> **Structure:** Inverted Pyramid (Foundational -> Strategic -> Tactical -> Reference).
## 1. 💾 Long Term Memory (System & Facts)
*This section contains the "Universal Truths" that rarely change. It grounds the agent in the user's reality.*
### 🤖 Agent Identity & Role
- **Agent Name:** Aether API Orchestrator (mcp_agent)
- **Primary Role:** Backend Development, System Orchestration, and API Stabilization.
- **Scope:** `/home/scott/OSIT_dev/aether_api_fastapi` and Aether Platform backend infrastructure.
### 👤 User Profile
- **User:** Scott Idem (`scott`)
- **Organizations:**
- **One Sky IT (OSIT):** Professional/Business context.
- **Danger Zone (DgrZone):** Personal/Home context.
- **Aether Platform (AE):** Scott's (One Sky IT) platform developed for OSIT.
- **Preferences:**
- **Editor:** `vim` (Terminal), VS Code (GUI).
- **Communication:** Direct, concise, professional CLI tone.
- **Safety:** "Recycle Bin" (`~/tmp/gemini_trash`) instead of `rm`. Explain destructive actions first.
- **Hardware/OS:**
- **Host:** Linux (Ubuntu/Arch context)
### 🏗️ Aether Architecture (V3)
- **Concept:** Unified AI-driven platform for business/personal management.
- **Backend:** FastAPI (v4.9.0) + Pydantic V1 + SQLAlchemy + MariaDB (Remote).
- **V3 Implementation:** Modern parallel CRUD and Search endpoints under `/v3/crud`.
- **Core Principle:** "Agent Bridge" - Distributed agents coordinating via file-based messaging (`~/agents_sync`).
### 📜 Core Protocols
- **RAR Protocol:** Request -> Ack -> Result.
- **V3 CRUD Paradigm:** JSON metadata via `/v3/crud/`, binary actions (Upload/Download) via `/v3/action/`.
- **Fail Fast & Transparently:** API returns `500` on hard errors; avoid silent failures (confirmed in `sql_select`).
- **Bite-Sized Data:** Avoid monolithic files (>1MB).
- **Source of Truth:** `~/agents_sync` is the shared brain. `~/OSIT_dev` is the local development environment.
### 🛡️ Security & Secrets Guardrails
- **Secrets:** NEVER read/display content from `.env` files unless explicitly debugging configuration logic.
- **PII:** Scrub personally identifiable information if sharing logs or data across the bridge.
- **Hiding Internal Paths:** `subdirectory_path` is hidden from public-facing API responses via Pydantic `Field(exclude=True)`.
### 🧠 Key Technical Learnings (Cumulative)
- **Circular Dependencies Fixed**: Successfully resolved the fragile startup dependency chain by isolating Auth models and using strictly deferred DB imports in a dedicated `dependencies_v3.py` module.
- **Bootstrap Paradox Solved**: Implemented a guest-access exception for `site_domain` search, allowing the frontend to resolve site context without a JWT.
- **V3 Searchable Fields**: `searchable_fields` must explicitly include integer ID fields (e.g., `event_id`) to ensure valid numeric filters are not blocked by the V3 search security layer.
- **NULL Logic in Filters**: Confirmed that explicit frontend filters like `hide: false` will FAIL to match `NULL` database values. Rely on the API's built-in `hidden=not_hidden` parameter for robust handling.
- **Vision ID Safety Net**: Enhanced `lookup_id_random_pop` to resolve random string IDs found in any `*_id` field, ensuring "Vision" style payloads are correctly converted to integers.
---
## 2. 🗓️ Near Term Memory (Strategic Context)
*This section tracks active projects (1-2 weeks scope). It answers "Why are we doing this?"*
### 📩 In-Flight RAR Requests
- [ ] **mcp_agent**: Real-world test of `ae_field_manage.py` (ID: 153357623).
- [ ] **codebase_investigator**: Review report for Aether extension and journal management (ID: 155435511).
### 🎯 Strategic Goals (Current Sprint)
- **Primary:** OSIT_dev Environment Optimization & Context Stabilization (Template v1.2 Adoption).
- **Secondary:** ID Vision Phase 2 Migration and V3 API Migration (Contacts/Clients).
### 🚧 Active Workstreams
- **[ID Vision]:** Phase 2 complete. Strictly enforced string-ID standardization for Page, Post, Person, Journal, Contact, and User models. (ID: 161311118 - DONE).
- **[Infrastructure]:** Restore AE Events Presentation Launcher (Electron) (ID: 221513945).
- **[Infrastructure]:** Pydantic V2 Migration Impact Analysis (Technical Debt).
- **[Journals]:** UI: Implementation of Quick Add & Append/Prepend (ID: 185821382).
### 🧠 Recent Decisions
- **ID Hardening:** Modified the `map_v3_ids` root validator across core models to explicitly delete aliased integer IDs (e.g., `post_id`, `journal_id`) to prevent Pydantic coercion of legacy integers into strings.
- **Search Optimization:** Standardized on `default_qry_str` for optimized fulltext searching. `Event_Badge_Base` is noted as a temporary outlier (`default_qry_string`) awaiting frontend alignment.
- **Privacy & Information Hiding:** Centralized `public_read` flag in object definitions and excluded internal file sharding paths from responses.
---
## 3. 🧠 Short Term Memory (Session Context)
*This section is the "Scratchpad" for the current interaction. It is cleared or summarized often.*
- **Status:** Online
- **Last Action:** Successfully refactored `GEMINI.md` to v1.2 structure.
- **Current Blocker:** None.
- **Immediate Next Step:** Check for new messages in the inbox or proceed with high-priority tasks.
---
## 4. 📂 Reference: Directory & Whitelist
*Low-density reference data. Keep at the bottom to avoid cluttering the prompt's "hot zone".*
### 🛡️ File Whitelist
- `~/tmp`
- `~/OSIT_dev/aether_api_fastapi`
- `~/agents_sync`
### 🗺️ Standard Directory Map
- **`app/methods/`**: Object-specific business logic.
- **`app/models/`**: Pydantic schemas.
- **`app/object_definitions/`**: V3 Metadata definitions.
- **`app/routers/`**: API endpoints.
### 📋 Aether API Development Protocol
0. **Pre-Flight Check:** Verify `git status`. Ensure all previous working changes are committed.
1. **Strategic Plan:** Write a concise plan identifying the issue, specific files, and verification steps (curl commands/test scripts).
2. **Implementation:** Perform atomic code modifications using `replace` or `write_file`.
3. **Syntax Validation:** Run `python3 -m py_compile <modified_file>` immediately.
4. **Process Cycle:** Restart the Docker FastAPI service: `docker restart aether_container_env-ae_api-2`.
5. **Empirical Testing:** Execute `curl` commands and inspect logs: `tail -n 20 ~/OSIT_dev/aether_container_env/logs/ae_api/aether_api.log`.
6. **Finalize:** Commit changes with a descriptive message and sync documentation.

2
README.md Executable file
View File

@@ -0,0 +1,2 @@
# Aether API Python FastAPI
The Aether API was created and is being developed by Scott Idem using the Python FastAPI framework.

View File

@@ -0,0 +1,12 @@
{
"folders": [
{
"path": "."
}
],
"settings": {
"cSpell.words": [
"poolclass"
]
}
}

0
app/__init__.py Normal file
View File

84
app/ae_obj_types_def.py Normal file
View File

@@ -0,0 +1,84 @@
"""
This file centralizes the object type definitions for the Aether API.
It merges definitions from modular files in app/object_definitions/ to support
both V2 (legacy) and V3 CRUD operations.
"""
# Restore blanket imports for legacy compatibility (V1 and V2 rely on these)
from app.models.response_models import *
from app.models.api_crud_models import *
from app.models.account_models import *
from app.models.account_cfg_models import *
from app.models.activity_log_models import *
from app.models.address_models import *
from app.models.archive_models import *
from app.models.archive_content_models import *
from app.models.contact_models import *
from app.models.cont_edu_cert_models import *
from app.models.cont_edu_cert_person_models import *
from app.models.data_store_models import *
from app.models.event_models import *
from app.models.event_abstract_models import *
from app.models.event_badge_models import *
from app.models.event_badge_template_models import *
from app.models.event_device_models import *
from app.models.event_exhibit_models import *
from app.models.event_exhibit_tracking_models import *
from app.models.event_file_models import *
from app.models.event_location_models import *
from app.models.event_person_models import *
from app.models.event_person_tracking_models import *
from app.models.event_presentation_models import *
from app.models.event_presenter_models import *
from app.models.event_registration_models import *
from app.models.event_session_models import *
from app.models.event_track_models import *
from app.models.grant_models import *
from app.models.hosted_file_models import *
from app.models.journal_models import *
from app.models.journal_entry_models import *
from app.models.log_client_viewing_models import Log_Client_Viewing_Base
from app.models.membership_cfg_models import *
from app.models.membership_group_models import *
from app.models.membership_person_group_models import *
from app.models.membership_person_models import *
from app.models.membership_person_profile_models import *
from app.models.membership_type_models import *
from app.models.membership_person_type_models import *
from app.models.order_models import *
from app.models.order_cart_models import *
from app.models.organization_models import *
from app.models.page_models import *
from app.models.person_models import *
from app.models.product_models import *
from app.models.post_models import *
from app.models.post_comment_models import *
from app.models.site_models import *
from app.models.site_domain_models import *
from app.models.sponsorship_cfg_models import *
from app.models.sponsorship_models import *
from app.models.user_models import *
from app.models.user_role_models import *
from app.models.e_stripe_models import *
# Modularized definitions
from app.object_definitions.core import core_obj_li
from app.object_definitions.events import event_obj_li
from app.object_definitions.journals import journal_obj_li
from app.object_definitions.orders import order_obj_li
from app.object_definitions.cms import cms_obj_li
from app.object_definitions.lookups import lu_obj_li
from app.object_definitions.membership import membership_obj_li
from app.object_definitions.other import other_obj_li
# Merge all modular definitions into the main registry
obj_type_kv_li = {
**core_obj_li,
**event_obj_li,
**journal_obj_li,
**order_obj_li,
**cms_obj_li,
**lu_obj_li,
**membership_obj_li,
**other_obj_li,
}

71
app/config.py.default Normal file
View File

@@ -0,0 +1,71 @@
# Configuration file for this FastAPI app.
import os
from pydantic import BaseSettings
from typing import Any, Dict, List, Optional, Union
class Settings(BaseSettings):
AETHER_CFG: Dict[str, Any] = {
"id": os.getenv('AE_CFG_ID', '0')
}
JWT_KEY: str = os.getenv('AE_API_JWT_KEY', 'fake-super-secret-token')
# Database Connection
DB_SERVER: str = os.getenv('AE_DB_SERVER', 'mariadb')
DB_PORT: str = os.getenv('AE_DB_PORT', '3306')
DB_NAME: str = os.getenv('AE_DB_NAME', 'aether_dev')
DB_USER: str = os.getenv('AE_DB_USERNAME', 'aether_dev')
DB_PASS: str = os.getenv('AE_DB_PASSWORD', '')
@property
def SQLALCHEMY_DB_URI(self) -> str:
return f"mysql://{self.DB_USER}:{self.DB_PASS}@{self.DB_SERVER}:{self.DB_PORT}/{self.DB_NAME}"
@property
def DB(self) -> Dict[str, Any]:
return {
"server": self.DB_SERVER,
"port": self.DB_PORT,
"name": self.DB_NAME,
"username": self.DB_USER,
"password": self.DB_PASS,
"connect_timeout": int(os.getenv('AE_DB_CONNECTION_TIMEOUT', 20)),
"pool_recycle": int(os.getenv('AE_DB_POOL_RECYCLE', 1800))
}
# Logging
LOG_PATH: Dict[str, str] = {
"app": os.getenv('AE_API_LOG_PATH', '/logs/aether_api.log')
}
# Redis
REDIS: Dict[str, str] = {
"server": os.getenv('AE_REDIS_SERVER', 'redis'),
"port": os.getenv('AE_REDIS_PORT', '6379')
}
# --- CRITICAL CONFIGURATIONS ---
# Send SMTP Email
SMTP: Dict[str, str] = {
"server": os.getenv('AE_SMTP_SERVER', ''),
"port": os.getenv('AE_SMTP_PORT', '465'),
"username": os.getenv('AE_SMTP_USERNAME', ''),
"password": os.getenv('AE_SMTP_PASSWORD', '')
}
# Server Hosted File Paths
FILES_PATH: Dict[str, str] = {
"hosted_files_root": os.getenv('AE_FILES_PATH_ROOT', '/srv/hosted_files'),
"hosted_tmp_root": os.getenv('AE_FILES_PATH_TMP', '/srv/hosted_tmp')
}
# --- END CRITICAL CONFIGURATIONS ---
# CORS
ORIGINS_REGEX: str = os.getenv('AE_API_ORIGINS_REGEX', '(https://.*\.oneskyit\.com)|(https://.*\.oneskyit\.com:4443)')
ORIGINS: List[str] = [
'https://oneskyit.com',
'http://fastapi.localhost',
'http://svelte.oneskyit.local:5555',
]
settings = Settings()

28
app/db_connection.py Normal file
View File

@@ -0,0 +1,28 @@
"""
Independent database connection module to prevent circular imports.
"""
import logging
from sqlalchemy import create_engine
from app.config import settings
# Use local logger to avoid importing app.log (which might create cycles)
log = logging.getLogger(__name__)
db_uri = settings.SQLALCHEMY_DB_URI
engine = create_engine(
url = db_uri,
echo = False,
pool_use_lifo = True,
pool_pre_ping = True,
pool_recycle = settings.DB['pool_recycle'],
isolation_level = 'READ COMMITTED',
connect_args = {'connect_timeout': settings.DB['connect_timeout']}
)
log.info('DB Connection initializing...')
db = None
try:
db = engine.connect()
log.info(f'Connected to database: {db_uri}')
except Exception:
log.exception('Could not connect to database.')

141
app/db_sql.py Normal file
View File

@@ -0,0 +1,141 @@
import logging
from app.log import logger_reset
# 1. Foundational connection and error state from SQL Core
from app.lib_sql_core import (
db, engine, reconnect_db, sql_connect,
get_last_sql_error, set_last_sql_error
)
# 2. Foundational CRUD logic from SQL Crud library
from app.lib_sql_crud import (
sql_insert, sql_update, sql_insert_or_update,
sql_select, run_sql_select, sql_delete
)
# 3. Search logic parts (delegated from search library)
from app.lib_sql_search import (
sql_limit_offset_part as _sql_limit_offset_part,
sql_and_like_part as _sql_and_like_part,
sql_or_like_part as _sql_or_like_part,
sql_and_in_dict_li_part as _sql_and_in_dict_li_part,
sql_and_qry_part as _sql_and_qry_part,
sql_fulltext_qry_part as _sql_fulltext_qry_part,
sql_enable_part as _sql_enable_part,
sql_hidden_part as _sql_hidden_part,
sql_where_qry_part as _sql_where_qry_part,
sql_search_qry_part as _sql_search_qry_part
)
# 4. Redis and ID resolution helpers
from app.lib_redis_helpers import (
redis_lookup_id_random as _redis_lookup_id_random,
get_id_random as _get_id_random,
reset_redis as _reset_redis,
lookup_id_random_pop as _lookup_id_random_pop
)
# ### BEGIN ### API DB SQL ### redis_lookup_id_random() ###
@logger_reset
def redis_lookup_id_random(
record_id_random: int|str,
table_name: str,
check_int_id: bool = False,
log_lvl: int = logging.WARNING,
minutes: int = 30,
reset_rate: int = 10,
):
return _redis_lookup_id_random(record_id_random, table_name, check_int_id, log_lvl, minutes, reset_rate)
# ### END ### API DB SQL ### redis_lookup_id_random() ###
# ### BEGIN ### API DB SQL ### get_id_random() ###
@logger_reset
def get_id_random(record_id: int, table_name: str, log_lvl: int = logging.WARNING):
return _get_id_random(record_id, table_name, log_lvl)
# ### END ### API DB SQL ### get_id_random() ###
@logger_reset
def reset_redis():
return _reset_redis()
# ### BEGIN ### API DB SQL ### lookup_id_random_pop() ###
@logger_reset
def lookup_id_random_pop(obj_data: dict, log_lvl: int = logging.WARNING):
return _lookup_id_random_pop(obj_data, log_lvl)
# ### END ### API DB SQL ### lookup_id_random_pop() ###
# ### BEGIN ### API DB SQL Methods ### get_account_id_w_for_type_id() ###
@logger_reset
def get_account_id_w_for_type_id(for_type: str, for_id: int|str):
if fid := redis_lookup_id_random(record_id_random=for_id, table_name=for_type):
sql = f"SELECT account_id FROM `{for_type}` WHERE id = :fid LIMIT 1;"
if result := sql_select(sql=sql, data={'fid': fid}):
return result.get('account_id')
return False
# ### END ### API DB SQL Methods ### get_account_id_w_for_type_id() ###
@logger_reset
def sql_where_qry_part(qry_dict_li: list):
return _sql_where_qry_part(qry_dict_li)
@logger_reset
def sql_fulltext_qry_part(fulltext_qry_dict: dict):
return _sql_fulltext_qry_part(fulltext_qry_dict)
@logger_reset
def sql_and_qry_part(and_qry_dict_obj: dict):
return _sql_and_qry_part(and_qry_dict_obj)
@logger_reset
def sql_and_like_part(and_like_dict_obj: dict):
return _sql_and_like_part(and_like_dict_obj)
@logger_reset
def sql_or_like_part(or_like_dict_obj: dict):
return _sql_or_like_part(or_like_dict_obj)
@logger_reset
def sql_and_in_dict_li_part(and_in_dict_li_dict_obj: dict):
return _sql_and_in_dict_li_part(and_in_dict_li_dict_obj)
@logger_reset
def sql_enable_part(table_name: str, enabled: str):
return _sql_enable_part(table_name, enabled)
@logger_reset
def sql_hidden_part(table_name: str, hidden: str):
return _sql_hidden_part(table_name, hidden)
@logger_reset
def sql_limit_offset_part(limit: int, offset: int = 0):
return _sql_limit_offset_part(limit, offset)
@logger_reset
def sql_search_qry_part(search_query: any, searchable_fields: list[str]|None = None, max_depth: int = 5, table_name: str|None = None):
return _sql_search_qry_part(search_query, searchable_fields, max_depth, table_name)
__all__ = [
'db', 'engine', 'sql_connect', 'sql_insert', 'sql_update', 'sql_select',
'run_sql_select', 'sql_delete', 'redis_lookup_id_random', 'get_id_random',
'reset_redis', 'lookup_id_random_pop', 'sql_where_qry_part',
'sql_fulltext_qry_part', 'sql_and_qry_part', 'sql_and_like_part',
'sql_or_like_part', 'sql_and_in_dict_li_part', 'sql_enable_part',
'sql_hidden_part', 'sql_limit_offset_part', 'sql_search_qry_part',
'sql_insert_or_update', 'get_account_id_w_for_type_id', 'reconnect_db',
'get_last_sql_error', 'set_last_sql_error'
]

240
app/lib_api_crud_v3.py Normal file
View File

@@ -0,0 +1,240 @@
from typing import Any, Dict, Optional, Union
import json
import logging
import re
from app.lib_general_v3 import AccountContext, StatusFilterParams
from app.models.error_models import StandardError
log = logging.getLogger(__name__)
def format_db_error(raw_error: str) -> StandardError:
"""
Parses raw SQLAlchemy/MariaDB errors into structured StandardError objects.
"""
if not raw_error:
return StandardError(
category="unknown",
message="An unspecified database error occurred."
)
# 1. Extract Error Code and Message using regex
# Standard MariaDB pattern: (code, "message")
code = None
message = raw_error
recoverable = False
match = re.search(r'\((\d+),\s*["\'](.*?)["\']\s*\)', raw_error)
if match:
code = int(match.group(1))
message = match.group(2).strip()
else:
# Fallback: remove all (parenthesized) blocks which often contain codes
message = re.sub(r'\(.*?\)', '', raw_error).strip()
# 2. Categorize based on known MariaDB codes
# Ref: https://mariadb.com/kb/en/mariadb-error-codes/
if code in [1062]: # Duplicate Entry
category = "database_duplicate"
elif code in [1451, 1452]: # Foreign Key Constraint
category = "database_constraint"
elif code in [1045, 2002, 2003, 2006]: # Connection / Auth issues
category = "database_connection"
recoverable = True
elif code in [1054, 1146]: # Unknown column / Table
category = "database_schema"
else:
category = "database"
return StandardError(
category=category,
code=code,
message=message,
recoverable=recoverable,
details=raw_error if category == "database" else None # Only include raw details for uncategorized errors
)
def check_account_access(sql_result: Any, account: AccountContext, obj_name: str = None) -> bool:
"""
Enforce Multi-Tenant Data Isolation.
Verifies that the requested record belongs to the authenticated user's account.
Returns True if:
- User is a Super User or System (Bypass).
- The record's `account_id` matches the user's `account_id`.
"""
if account.super or account.auth_method == 'bypass':
return True
if not account.account_id:
return False
res_account_id = None
if isinstance(sql_result, dict):
if obj_name == 'account':
res_account_id = sql_result.get('id')
else:
res_account_id = sql_result.get('account_id')
if res_account_id is not None and res_account_id != account.account_id:
return False
return True
def apply_forced_account_filter(and_qry_dict: Optional[Dict], account: AccountContext, model: Any, obj_name: str, table_name: str = None) -> Dict:
"""
Secure Search Filtering.
Automatically appends an `account_id` filter to database queries to ensure
users only retrieve records associated with their own account.
Now schema-aware: checks if the column actually exists in the DB before applying.
"""
forced = and_qry_dict or {}
if account.super or account.auth_method == 'bypass':
return forced
# 1. Determine the target column
target_col = 'account_id'
if obj_name == 'account':
target_col = 'id'
# 2. Check if the model even supports it
if model and hasattr(model, '__fields__') and target_col not in model.__fields__:
return forced
# 3. If we have a table name, verify the column exists in the physical DB schema
# (Important for Views that might exclude account_id for performance/privacy)
if table_name:
from app import lib_sql_core
from sqlalchemy import text
try:
with lib_sql_core.engine.connect() as conn:
conn.execute(text(f"SELECT `{target_col}` FROM `{table_name}` LIMIT 0"))
has_col = True
except:
has_col = False
forced[target_col] = account.account_id
return forced
def filter_order_by(order_by_li: Any, model: Any, table_name: str = None) -> Optional[Dict[str, str]]:
"""
Sanitize Sorting Parameters.
Prevents SQL injection and logic errors by validating that requested sort columns
actually exist in the Pydantic model and/or the database table.
"""
if not order_by_li or not isinstance(order_by_li, dict) or not model:
return order_by_li
if not hasattr(model, '__fields__'):
return order_by_li
model_fields = set(model.__fields__.keys())
model_fields.update({f.alias for f in model.__fields__.values() if f.alias})
filtered = {k: v for k, v in order_by_li.items() if k in model_fields}
if table_name and filtered:
from app.db_sql import db
from sqlalchemy import text
final_filtered = {}
for column in filtered:
try:
# Lightweight check to see if column exists in SQL
db.execute(text(f"SELECT `{column}` FROM `{table_name}` LIMIT 0"))
final_filtered[column] = filtered[column]
except Exception:
pass
filtered = final_filtered
return filtered
def get_supported_filters(model: Any, status_filter: StatusFilterParams) -> StatusFilterParams:
"""
Adaptive Status Filtering.
Adjusts the default filters (enabled/hidden) based on whether the target object
actually supports those concepts (i.e., has those columns).
"""
if not model or not hasattr(model, "__fields__"):
return status_filter
# We create a new instance to avoid side effects on the dependency object
from app.routers.dependencies_v3 import StatusFilterParams as SF
adjusted = SF()
adjusted.enabled = status_filter.enabled
adjusted.hidden = status_filter.hidden
if 'enable' not in model.__fields__:
adjusted.enabled = 'all'
if 'hide' not in model.__fields__:
adjusted.hidden = 'all'
return adjusted
def safe_json_loads(json_str: Optional[str]) -> Any:
if not json_str or json_str == 'undefined': return None
try: return json.loads(json_str)
except: return None
def sanitize_payload(data: dict, model: Any, ignore_extra: bool = False) -> None:
"""
Sanitizes an input payload before database insertion or update.
1. Resolves ID strings to integers:
- Handles legacy `*_id_random` fields.
- Handles Vision `*_id` fields where the value is a string (e.g., account_id: "random_str").
2. Removes virtual lookup fields (ending in `_id_random`) after resolution.
3. Removes fields explicitly marked for exclusion in the model's
`fields_to_exclude_from_db` ClassVar (e.g., view-only fields).
4. If `ignore_extra` is True, removes all fields NOT present in the model definition.
Modifies the `data` dictionary in-place.
"""
if not isinstance(data, dict):
return
from app.db_sql import redis_lookup_id_random
# Resolve ID strings to integers
for k, v in list(data.items()):
if not v or not isinstance(v, str):
continue
target_id_field = None
obj_type_lookup = None
# Scenario A: Legacy suffix (e.g., account_id_random: "abc")
if k.endswith('_id_random') and k != 'id_random':
target_id_field = k.replace('_id_random', '_id')
obj_type_lookup = k.replace('_id_random', '')
# Scenario B: Vision naming (e.g., account_id: "abc")
# We only resolve if it's a string of the correct length (random ID format)
elif k.endswith('_id') and 11 <= len(v) <= 22:
target_id_field = k
obj_type_lookup = k.replace('_id', '')
if target_id_field and obj_type_lookup:
# Special table mapping if needed
if obj_type_lookup == 'address_location': obj_type_lookup = 'address'
resolved_id = redis_lookup_id_random(record_id_random=v, table_name=obj_type_lookup)
if resolved_id:
data[target_id_field] = resolved_id
# If we were handling Scenario A, remove the original random key
if k.endswith('_id_random'):
del data[k]
# Filter out model-specific excluded fields (e.g., view-only fields)
if hasattr(model, 'fields_to_exclude_from_db'):
for k in model.fields_to_exclude_from_db:
if k in data:
del data[k]
# If permissive mode is on, remove any field not in the Pydantic model
if ignore_extra and model and hasattr(model, '__fields__'):
model_fields = set(model.__fields__.keys())
# Also check for aliases
for f in model.__fields__.values():
if f.alias:
model_fields.add(f.alias)
extra_keys = [k for k in data.keys() if k not in model_fields]
for k in extra_keys:
del data[k]

88
app/lib_config_v3.py Normal file
View File

@@ -0,0 +1,88 @@
import logging
from typing import Any
log = logging.getLogger('root')
def validate_critical_config(settings: Any):
"""
Validates that essential settings are populated and not using placeholders.
Logs warnings or errors for missing critical infrastructure.
"""
log.info("Checking critical system configuration...")
# 1. Database Check
db = getattr(settings, 'DB', {})
if not db.get('server') or db.get('server') == 'mariadb':
# 'mariadb' is the default in .env, usually fine, but worth noting
log.info(f"Database server: {db.get('server')}")
# 2. SMTP Check
smtp = getattr(settings, 'SMTP', {})
if not smtp.get('server'):
log.warning("CRITICAL: SMTP server not configured. Email features will fail.")
if smtp.get('password') == 'set-in-ae-sql-db-cnf-tbl':
log.error("CRITICAL: SMTP password is still set to placeholder. Email authentication will fail.")
# 3. Security Check
jwt_key = getattr(settings, 'JWT_KEY', '')
if not jwt_key or jwt_key == 'fake-super-secret-token':
log.error("SECURITY: JWT_KEY is missing or using a known fake token!")
log.info("Configuration validation complete.")
def bootstrap_db_config(settings: Any) -> bool:
"""
Loads dynamic settings from the 'cfg' table and updates the settings object.
Uses deferred import of sql_select to avoid circular dependencies.
"""
# CRITICAL: Deferred import to prevent boot-time circular dependencies
from app.db_sql import sql_select
cfg_id = settings.AETHER_CFG.get('id', '0')
log.info(f"Bootstrapping system configuration from DB (cfg_id={cfg_id})...")
try:
# Fetch the config record
aether_cfg_sql = sql_select(
table_name='cfg',
record_id=int(cfg_id),
as_list=False,
max_count=1,
)
# In some cases sql_select might return a single-item list even with as_list=False
if isinstance(aether_cfg_sql, list):
if len(aether_cfg_sql) > 0:
aether_cfg_sql = aether_cfg_sql[0]
else:
aether_cfg_sql = None
if not aether_cfg_sql or not isinstance(aether_cfg_sql, dict):
log.error(f"FAILED to load system config from DB for ID {cfg_id}. Table 'cfg' might be empty or ID missing.")
return False
# --- Update Database settings ---
# Safety: Only update if the values are provided in the DB record
if aether_cfg_sql.get('db_server'): settings.DB_SERVER = aether_cfg_sql.get('db_server')
if aether_cfg_sql.get('db_port'): settings.DB_PORT = str(aether_cfg_sql.get('db_port'))
if aether_cfg_sql.get('db_name'): settings.DB_NAME = aether_cfg_sql.get('db_name')
if aether_cfg_sql.get('db_username'): settings.DB_USER = aether_cfg_sql.get('db_username')
if aether_cfg_sql.get('db_password'): settings.DB_PASS = aether_cfg_sql.get('db_password')
# --- Update SMTP Settings ---
if aether_cfg_sql.get('smtp_server'): settings.SMTP['server'] = aether_cfg_sql.get('smtp_server')
if aether_cfg_sql.get('smtp_port'): settings.SMTP['port'] = str(aether_cfg_sql.get('smtp_port'))
if aether_cfg_sql.get('smtp_username'): settings.SMTP['username'] = aether_cfg_sql.get('smtp_username')
if aether_cfg_sql.get('smtp_password'): settings.SMTP['password'] = aether_cfg_sql.get('smtp_password')
# --- Update File Paths ---
# DEPRECATED: Filesystem paths should be controlled by the Environment/Docker, not the DB.
# if aether_cfg_sql.get('path_hosted_files_root'): settings.FILES_PATH['hosted_files_root'] = aether_cfg_sql.get('path_hosted_files_root')
# if aether_cfg_sql.get('path_hosted_tmp_root'): settings.FILES_PATH['hosted_tmp_root'] = aether_cfg_sql.get('path_hosted_tmp_root')
log.info("System configuration successfully synchronized with DB.")
return True
except Exception as e:
log.exception(f"Unexpected error during system bootstrap: {e}")
return False

195
app/lib_email.py Normal file
View File

@@ -0,0 +1,195 @@
import html2text
import smtplib, ssl
import logging
from email.message import EmailMessage
from email.headerregistry import Address
from typing import Optional
from app.log import logger_reset
from app.config import settings
log = logging.getLogger(__name__)
# ### BEGIN ### API Lib Email ### send_email() ###
# Moved from lib_general.py 2026-01-07
@logger_reset
def send_email(
from_email: str,
to_email: str,
subject: str,
body_html: str,
from_name: str = '',
reply_to_email: str = '',
reply_to_name: str = '',
to_name: str = '',
cc_email: str = '',
cc_name: str = '',
bcc_email: str = '',
bcc_name: str = '',
body_text: str = '',
test: bool = False,
log_lvl: int = logging.WARNING, # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
):
log.setLevel(log_lvl)
log.debug(locals())
if test:
log.setLevel(logging.DEBUG)
log.debug('[TESTING] Running with send_email() in TEST mode')
message = EmailMessage()
if subject:
message['Subject'] = subject
else:
return False
if from_email and from_name:
try:
message['From'] = Address(display_name=from_name, addr_spec=from_email)
except Exception as e:
log.exception('**** *** ** * ### BEGIN ### Exception Happened: Returning False * ** *** ****')
return False
elif from_email:
try:
message['From'] = Address(display_name=from_email, addr_spec=from_email)
except Exception as e:
log.exception('**** *** ** * ### BEGIN ### Exception Happened: Returning False * ** *** ****')
return False
else:
return False
if reply_to_email and reply_to_name:
try:
message['Reply-To'] = Address(display_name=reply_to_name, addr_spec=reply_to_email)
except Exception as e:
log.exception('**** *** ** * ### BEGIN ### Exception Happened: Returning False * ** *** ****')
return False
elif reply_to_email:
try:
message['Reply-To'] = Address(display_name=reply_to_email, addr_spec=reply_to_email)
except Exception as e:
log.exception('**** *** ** * ### BEGIN ### Exception Happened: Returning False * ** *** ****')
return False
if to_email and to_name:
try:
message['To'] = Address(display_name=to_name, addr_spec=to_email)
except Exception as e:
log.exception('**** *** ** * ### BEGIN ### Exception Happened: Returning False * ** *** ****')
return False
elif to_email:
try:
message['To'] = Address(display_name=to_email, addr_spec=to_email)
except Exception as e:
log.exception('**** *** ** * ### BEGIN ### Exception Happened: Returning False * ** *** ****')
return False
else:
return False
if cc_email and cc_name:
try:
message['Cc'] = Address(display_name=cc_name, addr_spec=cc_email)
except Exception as e:
log.exception('**** *** ** * ### BEGIN ### Exception Happened: Returning False * ** *** ****')
return False
elif cc_email:
try:
message['Cc'] = Address(display_name=cc_email, addr_spec=cc_email)
except Exception as e:
log.exception('**** *** ** * ### BEGIN ### Exception Happened: Returning False * ** *** ****')
return False
if bcc_email and bcc_name:
try:
message['Bcc'] = Address(display_name=bcc_name, addr_spec=bcc_email)
except Exception as e:
log.exception('**** *** ** * ### BEGIN ### Exception Happened: Returning False * ** *** ****')
return False
elif bcc_email:
try:
message['Bcc'] = Address(display_name=bcc_email, addr_spec=bcc_email)
except Exception as e:
log.exception('**** *** ** * ### BEGIN ### Exception Happened: Returning False * ** *** ****')
return False
html_version = """
<html>
<body>
"""+body_html+"""
</body>
</html>
"""
if body_text:
text_version = body_text
else:
text_version = html2text.html2text(html_version)
message.set_content(text_version)
message.add_alternative(html_version, subtype='html')
log.info('Sending email...')
# Safe access to SMTP settings
smtp_settings = getattr(settings, 'SMTP', {})
if not smtp_settings:
log.error('SMTP settings not found in configuration. Returning False.')
return False
log.debug(smtp_settings)
log.info(f'Subject: {subject}')
log.info(f'From: {from_email} Reply To: {reply_to_email} To: {to_email} CC: {cc_email} BCC: {bcc_email}')
log.debug('Message:')
log.debug(message.as_string())
log.info('Creating SMTP SSL connection...')
context = ssl.create_default_context()
# Validate SMTP settings
smtp_server = smtp_settings.get('server')
smtp_port = smtp_settings.get('port')
smtp_username = smtp_settings.get('username')
smtp_password = smtp_settings.get('password')
if not smtp_server or not smtp_port:
log.error(f'Error: SMTP server or port not configured. Server: {smtp_server}, Port: {smtp_port}')
return False
try:
smtp_port = int(smtp_port)
except ValueError:
log.error(f'Error: Invalid SMTP port: {smtp_port}')
return False
log.info('SMTP configuration, connect, and send')
log.info(f'Server: {smtp_server} Port: {smtp_port} Username: {smtp_username}')
log.info('Trying smtplib.SMTP_SSL in send_email()...')
if test:
log.info('[TESTING] Email will NOT actually be sent! [TEST MODE]')
try:
with smtplib.SMTP_SSL(smtp_server, smtp_port, context=context) as server:
log.info('SMTP log in...')
# Avoid logging password in debug
log.debug(f'Server: {smtp_server} Port: {smtp_port} Username: {smtp_username}')
if smtp_username and smtp_password:
server.login(smtp_username, smtp_password)
log.info('SMTP send message...')
if not test:
log.info('Email sent! Returning True')
server.send_message(message)
else:
log.info('[TESTING] Email (NOT) sent! Returning True [TEST MODE]')
return True
except Exception as e:
log.error(f'Error: Unable to send email. Exception: {e}')
return False
# ### END ### API Lib Email ### send_email() ###

116
app/lib_export.py Normal file
View File

@@ -0,0 +1,116 @@
import os
import pandas
import pathlib
import logging
from typing import Optional, Union
from app.log import logger_reset
from app.config import settings
log = logging.getLogger(__name__)
# ### BEGIN ### API Lib Export ### create_export_file() ###
# Moved from lib_general.py 2026-01-07
@logger_reset
def create_export_file(
data_dict_list: list,
subdir_path: str,
filename: str,
column_name_li: list = [],
rm_id: bool = True,
export_type: str = 'CSV', # CSV, Excel
) -> Union[bool, str]:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
hosted_tmp_path = settings.FILES_PATH['hosted_tmp_root']
log.info(f'Hosted Temp Path: {hosted_tmp_path}')
subdirectory_dest = os.path.join(hosted_tmp_path, subdir_path)
log.debug(subdirectory_dest)
pathlib.Path(subdirectory_dest).mkdir(parents=True, exist_ok=True)
file_dest_w_subdir = os.path.join(subdirectory_dest, filename)
log.info(f'File Dest With Subdir: {file_dest_w_subdir}')
if column_name_li:
log.info('Using column name list passed')
else:
log.info('Using an auto generated column name list')
column_name_li = list(data_dict_list[0].keys())
log.debug(column_name_li)
if rm_id:
for column_name in list(column_name_li):
if column_name.endswith('_id'):
column_name_li.remove(column_name)
log.info(f'Removing column name: {column_name}')
log.info(column_name_li)
data_dataframe = pandas.DataFrame(data_dict_list)
log.debug(data_dataframe)
missing_cols = [col for col in column_name_li if col not in data_dataframe.columns]
if missing_cols:
column_name_li = [col for col in column_name_li if col not in missing_cols]
try:
if export_type == 'CSV':
log.info('Saving dataframe to CSV file')
full_dest_path = file_dest_w_subdir+'.csv'
filename_w_ext = filename+'.csv'
tmp_file_path = os.path.join(subdir_path,filename_w_ext)
data_dataframe.to_csv(
full_dest_path,
na_rep='NULL',
columns=column_name_li,
index=False,
)
elif export_type == 'Excel':
log.info('Saving dataframe to Excel file')
full_dest_path = file_dest_w_subdir+'.xlsx'
filename_w_ext = filename+'.xlsx'
tmp_file_path = os.path.join(subdir_path,filename_w_ext)
data_dataframe.to_excel(
full_dest_path,
na_rep='NULL',
columns=column_name_li,
index=False,
)
except:
log.exception('Something went wrong while trying to save the export file.')
return False
log.info(f'Temp File Path: {tmp_file_path}')
return tmp_file_path
# ### END ### API Lib Export ### create_export_file() ###
# ### BEGIN ### API Lib Export ### return_full_tmp_path() ###
# This is for using with return FileResponse(path=full_tmp_path, filename=filename)
# Moved from lib_general.py 2026-01-07
@logger_reset
def return_full_tmp_path(
full_tmp_path: str = None,
subdir_path: str = None,
filename: str = None,
) -> Union[bool, str]:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
hosted_tmp_path = settings.FILES_PATH['hosted_tmp_root']
log.info(f'Hosted Temp Path: {hosted_tmp_path}')
if full_tmp_path:
file_dest = os.path.join(hosted_tmp_path, full_tmp_path)
return file_dest
elif subdir_path and filename:
subdirectory_dest = os.path.join(hosted_tmp_path, subdir_path)
log.debug(subdirectory_dest)
pathlib.Path(subdirectory_dest).mkdir(parents=True, exist_ok=True)
file_dest_w_subdir = os.path.join(subdirectory_dest, filename)
log.info(f'File Dest With Subdir: {file_dest_w_subdir}')
return file_dest_w_subdir
else:
return False
# ### END ### API Lib Export ### return_full_tmp_path() ###

264
app/lib_general.py Normal file
View File

@@ -0,0 +1,264 @@
from typing import Dict, List, Optional, Set, Union
import logging
from fastapi import Header, HTTPException, Response, status
from app.log import logger_reset
from app.config import settings
from app.db_sql import redis_lookup_id_random, sql_select
from app.lib_email import send_email
from app.lib_export import create_export_file, return_full_tmp_path
from app.lib_jwt import sign_jwt, decode_jwt
from app.lib_hash import secure_hash_string, verify_secure_hash_string
log = logging.getLogger(__name__)
# ### BEGIN ### API Lib General ### async get_token_header() ###
def get_token_header(x_token: str = Header(...)):
if x_token != 'fake-super-secret-token':
raise HTTPException(status_code=400, detail='X-Token header invalid')
# ### END ### API Lib General ### async get_token_header() ###
# ### BEGIN ### API Lib General ### class Common_Route_Params ###
# Updated 2023-01-30
class Common_Route_Params_No_Account_ID:
def __init__(
self,
x_account_id: int|None = None,
x_account_id_random: str|None = None,
x_no_account_id_token: str|None = None,
enabled: str = 'enabled',
limit: int = 10,
offset: int = 0,
by_alias: bool = True,
exclude_unset: bool = False,
response = None,
):
self.x_account_id = x_account_id
self.x_account_id_random = x_account_id_random
self.x_no_account_id_token = x_no_account_id_token
self.enabled = enabled
self.limit = limit
self.offset = offset
self.by_alias = by_alias
self.exclude_unset = exclude_unset
self.response = response
# log.debug(response)
# ### END ### API Lib General ### class Common_Route_Params ###
# ### BEGIN ### API Lib General ### common_route_params() ###
# Updated 2023-01-30
@logger_reset # This breaks things for some reason when the function is async. Do not use async def common_route_params()!
def common_route_params_no_account_id(
x_account_id: str = Header(None, min_length=11, max_length=22),
enabled: str = 'enabled', # all, enabled, disabled
limit: int = 100,
offset: int = 0,
by_alias: bool = True,
exclude_none: Optional[bool] = True,
exclude_unset: bool = False,
# NOTE: Uncommenting either exclude or include breaks the JSON body format. I do not know why? Should be: {} Becomes this: {"obj_name": {"data_name": "data_value"}} -STI 2022-01-05
# exclude: Optional[list] = [], # Leaving this and include commented out
# include: Optional[list] = [], # Leaving this and exclude commented out
response: Response = Response,
log_lvl: int = logging.WARNING, # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
) -> Common_Route_Params_No_Account_ID:
log.setLevel(log_lvl)
log.debug(locals())
log.info(f'Setting commons values: x_account_id, x_account_id_random, limit, offset, enabled, by_alias, exclude_unset, response')
x_account_id_random = x_account_id
if x_account_id := redis_lookup_id_random(table_name='account', record_id_random=x_account_id):
log.info(f'Found the x-account-id header with the value: {x_account_id}')
elif x_account_id is None:
log.warning(f'No x-account-id header value passed')
else:
log.warning(f'The x-account-id header was found, but the Account ID was not found or is not valid. Account ID: {x_account_id}')
raise HTTPException(status_code=403, detail='The x-account-id Account ID was not found.') # Forbidden
commons = Common_Route_Params_No_Account_ID( x_account_id=x_account_id, x_account_id_random=x_account_id_random, limit=limit, offset=offset, enabled=enabled, by_alias=by_alias, exclude_unset=exclude_unset, response=response )
log.debug(commons)
return commons
# ### END ### API Lib General ### async common_route_params() ###
# ### BEGIN ### API Lib General ### class Common_Route_Params ###
# Updated 2022-01-05
class Common_Route_Params:
def __init__(
self,
x_account_id: int,
x_account_id_random: str,
x_no_account_id_token: str|None = None,
enabled: str = 'enabled',
limit: int = 10,
offset: int = 0,
by_alias: bool = True,
exclude_unset: bool = False,
response = None,
):
self.x_account_id = x_account_id
self.x_account_id_random = x_account_id_random
self.x_no_account_id_token = x_no_account_id_token
self.enabled = enabled
self.limit = limit
self.offset = offset
self.by_alias = by_alias
self.exclude_unset = exclude_unset
self.response = response
# log.debug(response)
# ### END ### API Lib General ### class Common_Route_Params ###
# ### BEGIN ### API Lib General ### common_route_params() ###
# Updated 2022-02-15
@logger_reset # This breaks things for some reason when the function is async. Do not use async def common_route_params()!
def common_route_params(
# x_account_id: str = Header(..., min_length=11, max_length=22), # NOTE WARNING: Commented out 2023-08-17
x_account_id: str = Header(None, min_length=11, max_length=22), # NOTE WARNING: Changed to this 2023-08-17
x_no_account_id: str = Header(None, min_length=11, max_length=22), # NOTE WARNING: Changed to this 2023-08-17
# x_aether_api_key: Optional[str] = Header(..., min_length=11, max_length=22),
# x_aether_api_token: Optional[str] = Header(..., min_length=11, max_length=22),
# x_aether_jwt_token: Optional[str] = Header(..., min_length=11, max_length=50),
x_no_account_id_token: str|None = None, # NOTE: Not a header value! Added 2023-08-17
enabled: str = 'enabled', # all, enabled, disabled
limit: int = 100,
offset: int = 0,
by_alias: bool = True,
exclude_unset: bool = False,
exclude_defaults: Optional[bool] = False,
exclude_none: Optional[bool] = False,
# NOTE: Uncommenting either exclude or include breaks the JSON body format. I do not know why? Should be: {} Becomes this: {"obj_name": {"data_name": "data_value"}} -STI 2022-01-05
# exclude: Optional[list] = [], # Leaving this and include commented out
# include: Optional[list] = [], # Leaving this and exclude commented out
response: Response = Response,
log_lvl: int = logging.WARNING, # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
) -> Common_Route_Params|Common_Route_Params_No_Account_ID:
log.setLevel(log_lvl)
log.debug(locals())
log.info(f'Setting commons values: x_account_id, x_account_id_random, limit, offset, enabled, by_alias, exclude_unset, response')
x_account_id_random = x_account_id
if x_account_id:
if x_account_id := redis_lookup_id_random(table_name='account', record_id_random=x_account_id):
log.info(f'Found the x-account-id header with the value: {x_account_id}')
else:
log.warning(f'The x-account-id header was found, but the Account ID was not found or is not valid. Account ID: {x_account_id}')
raise HTTPException(status_code=403, detail='The x-account-id Account ID was not found.') # Forbidden
elif x_no_account_id and len(x_no_account_id) > 10:
log.warning(f'Found the x_no_account_id header param with the value: {x_no_account_id}')
x_account_id = None
x_account_id_random = '--- NOT SET ---'
elif x_no_account_id_token and len(x_no_account_id_token) > 10: # NOTE: Not a header value!
# NOTE WARNING: This token should be verified and able to be disabled quickly.
log.warning(f'Found the x_no_account_id_token URL param with the value: {x_no_account_id_token}')
if x_account_id := redis_lookup_id_random(table_name='account', record_id_random=x_no_account_id_token):
log.info(f'Found the x-account-id header with the value: {x_account_id}')
x_account_id_random = x_no_account_id_token
else:
x_account_id = 0
x_account_id_random = ''
x_account_id = 0
x_account_id_random = '--- NOT SET ---'
else:
log.warning(f'The x-account-id and x-no-account-id-token headers were not found.')
raise HTTPException(status_code=403, detail='The x-account-id and x-no-account-id-token headers were not found.') # Forbidden
if x_account_id:
commons = Common_Route_Params( x_account_id=x_account_id, x_account_id_random=x_account_id_random, x_no_account_id_token=x_no_account_id_token, limit=limit, offset=offset, enabled=enabled, by_alias=by_alias, exclude_unset=exclude_unset, response=response )
else:
commons = Common_Route_Params_No_Account_ID( x_account_id=None, x_account_id_random=None, x_no_account_id_token=x_no_account_id_token, limit=limit, offset=offset, enabled=enabled, by_alias=by_alias, exclude_unset=exclude_unset, response=response )
log.debug(commons)
return commons
# ### END ### API Lib General ### async common_route_params() ###
# ### BEGIN ### API Lib General ### class Common_Route_Params_Min ###
# Updated 2022-01-05
# NOTE: Is this essentially the same as Common_Route_Params_No_Account_ID above?
class Common_Route_Params_Min:
def __init__(
self,
x_account_id: int = None,
x_account_id_random: str = None,
enabled: str = 'enabled',
limit: int = 10,
offset: int = 0,
by_alias: bool = True,
exclude_unset: bool = False,
response = None,
):
self.x_account_id = x_account_id
self.x_account_id_random = x_account_id_random
self.enabled = enabled
self.limit = limit
self.offset = offset
self.by_alias = by_alias
self.exclude_unset = exclude_unset
self.response = response
# log.debug(response)
# ### END ### API Lib General ### class Common_Route_Params_Min ###
# ### BEGIN ### API Lib General ### common_route_params_min() ###
# Updated 2022-02-15
# NOTE: Is this essentially the same as common_route_params_no_account_id above?
@logger_reset # This breaks things for some reason when the function is async. Do not use async def common_route_params()!
def common_route_params_min(
x_account_id: str = Header(None, min_length=11, max_length=22),
enabled: str = 'enabled', # all, enabled, disabled
limit: int = 100,
offset: int = 0,
by_alias: bool = True,
exclude_none: Optional[bool] = True,
exclude_unset: bool = False,
# NOTE: Uncommenting either exclude or include breaks the JSON body format. I do not know why? Should be: {} Becomes this: {"obj_name": {"data_name": "data_value"}} -STI 2022-01-05
# exclude: Optional[list] = [], # Leaving this and include commented out
# include: Optional[list] = [], # Leaving this and exclude commented out
response: Response = Response,
log_lvl: int = logging.WARNING, # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
) -> Common_Route_Params:
log.setLevel(log_lvl)
log.debug(locals())
log.info(f'Setting commons values: x_account_id, x_account_id_random, limit, offset, enabled, by_alias, exclude_unset, response')
log.debug(f'X Account ID: {x_account_id}')
if x_account_id:
x_account_id_random = x_account_id
if x_account_id := redis_lookup_id_random(table_name='account', record_id_random=x_account_id):
log.info(f'Found the x-account-id header with the value: {x_account_id}')
else:
log.warning(f'The x-account-id header was found, but the Account ID was not found or is not valid. Account ID: {x_account_id}')
raise HTTPException(status_code=403, detail='The x-account-id Account ID was not found.') # Forbidden
else: x_account_id_random = None
commons = Common_Route_Params_Min( x_account_id=x_account_id, x_account_id_random=x_account_id_random, limit=limit, offset=offset, enabled=enabled, by_alias=by_alias, exclude_unset=exclude_unset, response=response )
log.debug(commons)
return commons
# ### END ### API Lib General ### async common_route_params_min() ###

47
app/lib_general_v3.py Normal file
View File

@@ -0,0 +1,47 @@
"""
This file contains general utility functions and helpers specifically for API v3.
Refactored 2026-01-07 to move Auth logic to dependencies_v3.py to fix circular dependencies.
"""
import logging
from typing import (
Any,
Dict,
List,
Optional,
Union,
)
from fastapi import (
APIRouter,
Depends,
Header,
HTTPException,
Query,
Request,
Response,
status,
)
from pydantic import (
BaseModel,
Field,
)
# Re-import from the new central auth models
from app.models.auth_models import AccountContext
# Import the dependency functions for backward compatibility in existing v3 routes
from app.routers.dependencies_v3 import (
get_account_context,
get_account_context_optional,
PaginationParams,
StatusFilterParams,
SerializationParams,
DelayParams
)
from app.config import settings
from app.log import get_logger
logger = get_logger(__name__)
# Note: Dependency function implementations have moved to app/routers/dependencies_v3.py

View File

@@ -0,0 +1,171 @@
"""
This file contains general utility functions and helpers specifically for API v3.
It aims to provide a clean slate for new methods and refactor existing ones from lib_general.py
that are relevant to the v3 API, while removing unused or outdated functionalities.
"""
# Standard library imports
import time
import logging
from typing import (
Any,
Dict,
List,
Optional,
Union,
)
# Third-party imports
from fastapi import (
APIRouter,
Depends,
Header,
HTTPException,
Query,
Request,
Response,
status,
)
from pydantic import (
BaseModel,
Field,
ValidationError,
computed_field,
model_validator,
)
# Internal imports (from this project)
from app.config import settings
from app.db_sql import redis_lookup_id_random
from app.log import get_logger
logger = get_logger(__name__)
# --- Pydantic Model for Account Context ---
class AccountContext(BaseModel):
account_id: Optional[int]
account_id_random: Optional[str]
# --- Dependency Function for Account Context ---
def get_account_context(
x_account_id: Optional[str] = Header(None, min_length=11, max_length=22),
x_no_account_id: Optional[str] = Header(None, min_length=3, max_length=100), # Assuming 'bypass' or similar string
x_no_account_id_token: Optional[str] = Query(None, min_length=11, max_length=22),
) -> AccountContext:
"""
Resolves the account context from headers/query parameters with defined precedence.
Precedence: x_account_id (header) > x_no_account_id_token (query) > x_no_account_id (header flag)
Raises HTTPException 403 if no valid account is found and no bypass is indicated.
"""
logger.setLevel(logging.DEBUG) # Adjust as needed
logger.debug(locals())
resolved_account_id = None
resolved_account_id_random = None
if x_account_id:
# Primary check: x_account_id header
resolved_account_id_random = x_account_id
if looked_up_id := redis_lookup_id_random(table_name='account', record_id_random=x_account_id):
resolved_account_id = looked_up_id
logger.info(f'Found account from x_account_id header: {resolved_account_id}')
else:
logger.warning(f'Invalid x_account_id header provided: {x_account_id}')
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail='Invalid X-Account-ID header.')
elif x_no_account_id_token:
# Secondary check: x_no_account_id_token query parameter
resolved_account_id_random = x_no_account_id_token
if looked_up_id := redis_lookup_id_random(table_name='account', record_id_random=x_no_account_id_token):
resolved_account_id = looked_up_id
logger.info(f'Found account from x_no_account_id_token query: {resolved_account_id}')
else:
logger.warning(f'Invalid x_no_account_id_token query provided: {x_no_account_id_token}')
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail='Invalid X-No-Account-ID-Token query parameter.')
elif x_no_account_id:
# Tertiary check: x_no_account_id header for bypass
# For now, just presence indicates bypass. Can add a specific value check later if needed.
logger.info(f'X-No-Account-ID header found: {x_no_account_id}. Proceeding without specific account context.')
resolved_account_id = None # Explicitly None for "no specific account"
resolved_account_id_random = '--- NO ACCOUNT ---'
else:
logger.warning('No valid account context provided via X-Account-ID, X-No-Account-ID-Token, or X-No-Account-ID.')
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail='Account context required. Please provide X-Account-ID, X-No-Account-ID-Token, or X-No-Account-ID.')
return AccountContext(account_id=resolved_account_id, account_id_random=resolved_account_id_random)
# --- Pydantic Model for Pagination ---
class PaginationParams(BaseModel):
limit: int = 100 # Default limit
offset: int = 0
# --- Dependency Function for Pagination ---
def get_pagination_params(
limit: int = Query(100, ge=0, description="Maximum number of items to return"),
offset: int = Query(0, ge=0, description="Number of items to skip (for pagination)"),
) -> PaginationParams:
return PaginationParams(limit=limit, offset=offset)
# --- Pydantic Model for Status Filtering ---
class StatusFilterParams(BaseModel):
enabled: str = 'enabled' # 'enabled', 'disabled', 'all'
hidden: str = 'not_hidden' # 'hidden', 'not_hidden', 'all'
# --- Dependency Function for Status Filtering ---
def get_status_filter_params(
enabled: str = Query('enabled', description="Filter by object enabled status ('enabled', 'disabled', 'all')"),
hidden: str = Query('not_hidden', description="Filter by object hidden status ('hidden', 'not_hidden', 'all')"),
) -> StatusFilterParams:
allowed_enabled_values = {'enabled', 'disabled', 'all'}
allowed_hidden_values = {'hidden', 'not_hidden', 'all'}
if enabled not in allowed_enabled_values:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Invalid value for 'enabled'. Must be one of {list(allowed_enabled_values)}."
)
if hidden not in allowed_hidden_values:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Invalid value for 'hidden'. Must be one of {list(allowed_hidden_values)}."
)
return StatusFilterParams(enabled=enabled, hidden=hidden)
# --- Pydantic Model for Serialization Options ---
class SerializationParams(BaseModel):
by_alias: bool = True
exclude_unset: bool = False
exclude_defaults: bool = False # Added based on common_route_params
exclude_none: bool = False # Added based on common_route_params
# --- Dependency Function for Serialization Options ---
def get_serialization_params(
by_alias: bool = Query(True, description="Whether to use field aliases for serialization"),
exclude_unset: bool = Query(False, description="Whether to exclude unset fields from the response"),
exclude_defaults: bool = Query(False, description="Whether to exclude fields with their default values from the response"),
exclude_none: bool = Query(False, description="Whether to exclude fields that are None from the response"),
) -> SerializationParams:
return SerializationParams(
by_alias=by_alias,
exclude_unset=exclude_unset,
exclude_defaults=exclude_defaults,
exclude_none=exclude_none,
)
# --- Pydantic Model for Delay ---
class DelayParams(BaseModel):
sleep_time_ms: int = 0 # Raw delay value in ms
sleep_time_s: float = 0.0 # Converted to seconds for time.sleep()
# --- Dependency Function for Delay ---
def get_delay_params(
x_delay_ms: Optional[int] = Header(0, alias='X-Delay-ms', description="Delay response for X milliseconds (header)"),
delay_ms: Optional[int] = Query(0, description="Delay response for X milliseconds (query parameter)"),
) -> DelayParams:
calculated_delay_ms = max(x_delay_ms or 0, delay_ms or 0)
return DelayParams(sleep_time_ms=calculated_delay_ms, sleep_time_s=calculated_delay_ms / 1000.0)

16
app/lib_hash.py Normal file
View File

@@ -0,0 +1,16 @@
from passlib.hash import argon2
import logging
log = logging.getLogger(__name__)
# Moved from lib_general.py 2026-01-07
def secure_hash_string(string: str) -> str:
string_hash = argon2.using(rounds=14, memory_cost=1536, parallelism=2).hash(string)
return string_hash
# Moved from lib_general.py 2026-01-07
def verify_secure_hash_string(string: str, string_hash: str) -> bool:
if argon2.verify(string, string_hash):
return True
else:
return False

130
app/lib_id_resolver.py Normal file
View File

@@ -0,0 +1,130 @@
"""
Centralized ID random to integer ID resolution.
"""
import logging
import datetime
import random
import redis
from app.config import settings
from app.db_connection import db
log = logging.getLogger(__name__)
def redis_lookup_id_random(
record_id_random: int|str,
table_name: str,
check_int_id: bool = False,
log_lvl: int = logging.WARNING,
minutes: int = 30,
reset_rate: int = 10,
) -> str|int|bool|None:
"""
Looks up a record ID in Redis, falling back to SQL if not found.
Resolves 'id_random' (URL-safe string) to internal integer 'id'.
"""
from app.db_sql import sql_select, get_id_random
log.setLevel(log_lvl)
if isinstance(record_id_random, str) and 11 <= len(record_id_random) <= 22:
pass
elif isinstance(record_id_random, int):
if check_int_id:
if get_id_random(record_id=record_id_random, table_name=table_name):
return record_id_random
return False
return record_id_random
elif record_id_random is None:
return None
else:
log.error(f'Unexpected data type: {type(record_id_random)}. Expected string (11-22 chars) or int.')
return False
if not table_name:
log.error(f'Missing table_name for id_random lookup: {record_id_random}')
return False
r = redis.Redis(host=settings.REDIS['server'], port=settings.REDIS['port'], db=7, password=None, decode_responses=True)
key_name = f'{table_name}:{record_id_random}'
record_id = r.get(key_name)
# Periodic cache refresh
if record_id and random.randint(1, reset_rate) == 1:
log.warning(f'Redis: Randomly (1/{reset_rate}) refreshing cache for Key="{key_name}"')
record_id = None
if record_id:
r.setex(key_name, datetime.timedelta(minutes=minutes), value=record_id)
return int(record_id)
else:
data = { 'id_random': record_id_random }
sql = f"SELECT id FROM `{table_name}` WHERE id_random = :id_random;"
if select_results := sql_select(sql=sql, data=data):
if isinstance(select_results, dict):
if rid := select_results.get('id'):
r.setex(key_name, datetime.timedelta(minutes=minutes), value=rid)
return int(rid)
log.error('SQL result missing ID field.')
return False
else:
log.error(f'SQL: Duplicate id_random found in "{table_name}". Retrying...')
return redis_lookup_id_random(record_id_random=record_id_random, table_name=table_name)
else:
log.warning(f'SQL: ID Random "{record_id_random}" not found in "{table_name}".')
return None
def lookup_id_random_pop(
obj_data: dict,
log_lvl: int = logging.WARNING,
):
"""
Resolves any *_id_random fields in a dict to their integer IDs and removes the random keys.
"""
log.setLevel(log_lvl)
# List of common prefix patterns to resolve
id_patterns = [
'account', 'activity_log', 'address', 'archive', 'contact', 'cont_edu_cert',
'cont_edu_cert_person', 'event', 'event_abstract', 'event_badge',
'event_badge_template', 'event_exhibit', 'event_file', 'event_location',
'event_person', 'event_person_profile', 'event_presentation',
'event_presenter', 'event_registration', 'event_session', 'event_track',
'grant', 'hosted_file', 'journal', 'journal_entry', 'membership_group',
'membership_person_group', 'membership_person', 'membership_type',
'membership_person_type', 'order', 'order_line', 'order_cart',
'order_cart_line', 'organization', 'page', 'person', 'post', 'product',
'sponsorship', 'sponsorship_cfg', 'site', 'user'
]
for prefix in id_patterns:
key = f'{prefix}_id_random'
if key in obj_data:
table = prefix
if prefix == 'address_location': table = 'address'
if prefix in ['contact_1', 'contact_2']: table = 'contact'
if prefix == 'event_id_random_only': table = 'event'
resolved_id = redis_lookup_id_random(record_id_random=obj_data[key], table_name=table)
obj_data[f'{prefix}_id'] = resolved_id
obj_data.pop(key)
# Handle polymorphic link fields
polymorphic = [
('for_type', 'for_id_random', 'for_id'),
('link_to_type', 'link_to_id_random', 'link_to_id'),
('object_type', 'object_id_random', 'object_id'),
('to_object_type', 'to_object_id_random', 'to_object_id'),
('from_object_type', 'from_object_id_random', 'from_object_id')
]
for type_key, rand_key, id_key in polymorphic:
if type_key in obj_data and rand_key in obj_data:
obj_data[id_key] = redis_lookup_id_random(
record_id_random=obj_data[rand_key],
table_name=obj_data[type_key]
)
obj_data.pop(rand_key)
return obj_data

82
app/lib_jwt.py Normal file
View File

@@ -0,0 +1,82 @@
import jwt
import time
import logging
from typing import Dict, Optional
from app.log import logger_reset
log = logging.getLogger(__name__)
# ### BEGIN ### API Lib JWT ### sign_jwt() ###
# Moved from lib_general.py 2026-01-07
@logger_reset
def sign_jwt(
secret_key: str, # Secret/Private/Password
ttl: int = 60, # Default to 60 seconds
max_renew: int = 0, # Default to 0
public_key: str = None, # Will be part of the token. Use to look up secret when verifying.???
account_id: str = None,
person_id: str = None,
user_id: str = None,
json_str: str = None,
b64_str: str = None,
**kwargs # Allow arbitrary claims
) -> str:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
# SECURITY CHECK: Ensure we are not signing numeric IDs
for label, val in [('account_id', account_id), ('person_id', person_id), ('user_id', user_id)]:
if val is not None:
if isinstance(val, int) or (isinstance(val, str) and val.isdigit()):
log.critical(f"SECURITY BREACH: Attempted to sign a numeric ID for {label}='{val}'. Only random string IDs allowed.")
# For now we log and proceed, but in Phase 3 we should raise an Exception
# raise ValueError(f"Numeric IDs cannot be signed in JWTs.")
payload = {
'iat': time.time(), # Issued at
'eat': time.time() + ttl, # Expires at
'max_renew': max_renew, # Number of times allowed to request renew without API secret key
'public_key': public_key, # Use to lookup the secret/private/password key when verifying
'account_id': account_id,
'person_id': person_id,
'user_id': user_id,
'json_str': json_str,
'b64_str': b64_str,
}
# Merge additional claims
if kwargs:
payload.update(kwargs)
secret = secret_key
algorithm = 'HS256'
token = jwt.encode(payload, secret, algorithm=algorithm)
log.debug(token)
return token
# ### END ### API Lib JWT ### sign_jwt() ###
# ### BEGIN ### API Lib JWT ### decode_jwt() ###
# Moved from lib_general.py 2026-01-07
@logger_reset
def decode_jwt(
secret_key: str,
token: str,
) -> Optional[dict]:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
secret = secret_key
algorithm = 'HS256'
try:
decoded_token = jwt.decode(token, secret, algorithms=[algorithm])
log.debug(decoded_token)
if decoded_token['eat'] >= time.time(): return decoded_token
else: return False
except:
return None
# ### END ### API Lib JWT ### decode_jwt() ###

77
app/lib_log_v3.py Normal file
View File

@@ -0,0 +1,77 @@
import functools
import logging
import logging.config
from typing import Any
# Global logger instance used throughout the app
log = logging.getLogger('root')
def get_logger(name: str):
"""Returns a logger instance by name."""
return logging.getLogger(name)
def setup_logging(settings: Any):
"""
Configures logging based on provided settings.
Moving this here prevents immediate execution on module import.
"""
log_file_path = getattr(settings, 'LOG_PATH', {}).get('app', '/logs/aether_api.log')
try:
logging.config.dictConfig({
'version': 1,
'disable_existing_loggers': False, # Critical to not kill FastAPI/Uvicorn loggers
'formatters': {
'default': {'format': '[%(asctime)s] %(levelname)s @ %(module)s.%(funcName)s()#%(lineno)d: %(message)s'},
'long': {'format': '[%(asctime)s] %(levelname)s @ %(module)s.%(funcName)s()#%(lineno)d: %(message)s', 'datefmt': '%Y-%m-%d %H:%M:%S'},
'short': {'format': '[%(asctime)s] %(levelname)s @ %(module)s.%(funcName)s()#%(lineno)d: %(message)s', 'datefmt': '%H:%M:%S'},
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'stream': 'ext://sys.stderr',
'formatter': 'short',
},
'log_file_all': {
'level': 'NOTSET',
'class': 'logging.handlers.RotatingFileHandler',
'formatter': 'long',
'filename': log_file_path,
'maxBytes': 10485760, # 10 MB
'backupCount': 9
},
},
'loggers': {
'uvicorn': {'handlers': ['console'], 'level': 'INFO'},
},
'root': {
'handlers': ['log_file_all'],
'level': 'WARNING',
}
})
log.info(f"Logging successfully configured. Path: {log_file_path}")
except Exception as e:
print(f"Error configuring logging: {e}")
logging.basicConfig(level=logging.WARNING)
def logger_reset(func):
"""
Decorator to log function entry/exit and reset log levels.
"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
# Local safer access to root logger
root_log = logging.getLogger('root')
if func.__name__ not in ['redis_lookup_id_random', 'sql_enable_part', 'sql_hidden_part']:
root_log.info(f'*** Function: "{func.__name__}()"')
root_log.debug(f'*** Function Positional Args: {args}\nFunction Key Args: {kwargs}')
init_log_level = root_log.level
returned_result = func(*args, **kwargs)
# Reset level in case it was changed during func execution
root_log.setLevel(init_log_level)
return returned_result
return wrapper

247
app/lib_redis_helpers.py Normal file
View File

@@ -0,0 +1,247 @@
"""
Redis-based ID resolution and caching helpers for Aether.
"""
import datetime
import random
import redis
import logging
from app.config import settings
log = logging.getLogger(__name__)
def redis_lookup_id_random(
record_id_random: int|str,
table_name: str,
check_int_id: bool = False,
log_lvl: int = logging.WARNING, # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
minutes: int = 30, # Expire the Redis key after 8 minutes
reset_rate: int = 10, # 1 in 10 chance of resetting the Redis key
) -> str|int|bool|None:
"""
Looks up a record ID in Redis, falling back to SQL if not found.
Resolves 'id_random' (URL-safe string) to internal integer 'id'.
"""
from app.db_sql import sql_select, get_id_random
log.setLevel(log_lvl)
if isinstance(record_id_random, str) and len(record_id_random) >= 11 and len(record_id_random) <= 22: pass
elif isinstance(record_id_random, int):
record_id = record_id_random
if check_int_id:
log.info(f'Checking the int ID if exists. Table Name: {table_name} ID: {record_id}')
if get_id_random_result := get_id_random(
record_id = record_id,
table_name = table_name,
):
log.info(f'The int ID exists. Returning the int ID. ID Random: {get_id_random_result}')
return record_id
else:
log.info(f'The int ID does not exists. Returning False. Table Name: {table_name} ID: {record_id}')
return False
else:
log.debug(f'Not checking if the int ID exists. Returning the int ID. ID: {record_id}')
return record_id
elif record_id_random is None:
log.info(f'No record ID was passed. Returning None')
return None
else:
log.error(f'Unexpected data type or string format: {type(record_id_random)} Expected type is a string 11 or 22 characters long.')
return False
if record_id_random and table_name:
if len(record_id_random) < 11:
log.error(f'The length of id_random is too short: {record_id_random} ({len(record_id_random)} chars)')
return False
elif len(record_id_random) > 22:
log.error(f'The length of id_random is too long: {record_id_random} ({len(record_id_random)} chars)')
return False
elif record_id_random:
log.error(f'Missing table_name to select from for id_random "{record_id_random}"')
return False
elif table_name:
log.error(f'Missing id_random to select from table "{table_name}"')
return False
else:
log.error('Missing table_name and record_id_random')
return False
r = redis.Redis(host=settings.REDIS['server'], port=settings.REDIS['port'], db=7, password=None, decode_responses=True)
key_name = f'{table_name}:{record_id_random}'
record_id = r.get(key_name)
if record_id and random.randint(1, reset_rate) == 1:
log.warning(f'Redis: Randomly (1/{reset_rate}) setting record_id to None. Key="{key_name}" value="{record_id}" TTL={r.ttl(key_name)} seconds')
record_id = None
if record_id:
r.setex(key_name, datetime.timedelta(minutes=minutes), value=record_id)
log.info(f'Redis: Entry found for: Key="{key_name}" value="{record_id}" TTL={r.ttl(key_name)} seconds')
return int(record_id)
elif table_name:
data = { 'id_random': record_id_random }
sql = f"SELECT id FROM `{table_name}` AS `table` WHERE `table`.id_random = :id_random;"
if select_results := sql_select(sql=sql, data=data):
log.debug(f'SQL: SELECT result: {select_results}')
if isinstance(select_results, dict):
log.info(f"""SQL: Found ID Random for: {str(record_id_random)} = {str(select_results.get('id'))}""")
if record_id := select_results.get('id'):
r.setex(key_name, datetime.timedelta(minutes=minutes), value=record_id)
return int(record_id)
else:
log.error('The SQL result was not what was expected. The ID field was not found.')
return False
else:
log.error(f'SQL: More than one record found in "{table_name}". Duplicate id_random!')
return redis_lookup_id_random(record_id_random=record_id_random, table_name=table_name)
else:
log.warning(f'SQL: ID Random "{record_id_random}" not found in "{table_name}". Returning None.')
return None
log.error('Unexpected state in redis_lookup_id_random.')
return False
def get_id_random(
record_id: int,
table_name: str,
log_lvl: int = logging.WARNING, # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
) -> str|bool|None:
"""
Looks up the 'id_random' for a given internal integer ID.
"""
from app.db_sql import sql_select
log.setLevel(log_lvl)
data = { 'id': record_id }
sql = f"SELECT id_random FROM `{table_name}` AS `table` WHERE `table`.id = :id;"
if select_results := sql_select(sql=sql, data=data):
if isinstance(select_results, dict):
if record_id_random := select_results.get('id_random'):
return str(record_id_random)
else:
log.error('The SQL result was not what was expected.')
return False
elif isinstance(select_results, list):
log.exception('More than one record may have been found. Duplicate ID!')
return False
else:
log.exception(f'Got an unexpected result while trying to look up the ID.')
return False
elif select_results is None:
return None
else:
return False
def reset_redis():
"""Flushes the Redis database used for ID caching."""
r = redis.Redis(host=settings.REDIS['server'], port=settings.REDIS['port'], db=7, password=None, decode_responses=True)
r.flushdb()
return True
def lookup_id_random_pop(
obj_data: dict,
log_lvl: int = logging.WARNING, # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
):
"""
Look up and resolve id_random values to their id
Remove the unneeded *_id_random key from the dict
"""
log.setLevel(log_lvl)
# Common prefixes for ID resolution
id_prefixes = [
'account', 'activity_log', 'address', 'address_location', 'archive',
'contact', 'contact_1', 'contact_2', 'cont_edu_cert', 'cont_edu_cert_person',
'event', 'event_id_random_only', 'event_abstract', 'event_badge',
'event_badge_template', 'event_exhibit', 'event_file', 'event_location',
'event_person', 'event_person_profile', 'event_presentation',
'event_presenter', 'event_registration', 'event_session', 'event_track',
'grant', 'hosted_file', 'journal', 'journal_entry', 'membership_group',
'membership_person_group', 'membership_person', 'membership_type',
'membership_person_type', 'order', 'order_line', 'order_cart',
'order_cart_line', 'organization', 'page', 'person', 'poc_event_person',
'poc_person', 'post', 'product', 'sponsorship', 'sponsorship_cfg',
'site', 'user'
]
for prefix in id_prefixes:
key_random = f'{prefix}_id_random'
key_id = f'{prefix}_id'
# Table name mapping
table = prefix
if prefix == 'address_location': table = 'address'
elif prefix in ['contact_1', 'contact_2']: table = 'contact'
elif prefix == 'event_id_random_only': table = 'event'
elif prefix == 'poc_event_person': table = 'event_person'
elif prefix == 'poc_person': table = 'person'
resolved_id = None
# Scenario A: Legacy suffix (e.g., account_id_random: "abc")
if key_random in obj_data:
resolved_id = redis_lookup_id_random(record_id_random=obj_data[key_random], table_name=table)
obj_data.pop(key_random)
# Scenario B: Vision naming (e.g., account_id: "abc")
# Only resolve if it's a string of the correct length (random ID format)
elif key_id in obj_data and isinstance(obj_data[key_id], str) and 11 <= len(obj_data[key_id]) <= 22:
resolved_id = redis_lookup_id_random(record_id_random=obj_data[key_id], table_name=table)
if resolved_id is not None:
# Set the target ID field
target_id_key = key_id
if prefix == 'event_id_random_only': target_id_key = 'event_id_only'
obj_data[target_id_key] = resolved_id
# Removed the short prefix version (e.g., obj_data['account'] = 1)
# as it causes 'Unknown column' errors in direct table inserts.
# Polymorphic links
polymorphic = [
('for_type', 'for_id_random', 'for_id'),
('link_to_type', 'link_to_id_random', 'link_to_id'),
('object_type', 'object_id_random', 'object_id'),
('to_object_type', 'to_object_id_random', 'to_object_id'),
('from_object_type', 'from_object_id_random', 'from_object_id')
]
for type_key, rand_key, id_key in polymorphic:
# Handle random key if present
if type_key in obj_data and rand_key in obj_data:
obj_data[id_key] = redis_lookup_id_random(
record_id_random=obj_data.get(rand_key),
table_name=obj_data.get(type_key)
)
obj_data.pop(rand_key)
# Handle Vision naming (id_key contains the string)
elif type_key in obj_data and id_key in obj_data and isinstance(obj_data[id_key], str) and 11 <= len(obj_data[id_key]) <= 22:
obj_data[id_key] = redis_lookup_id_random(
record_id_random=obj_data.get(id_key),
table_name=obj_data.get(type_key)
)
return obj_data
def get_account_id_w_for_type_id(
for_type: str, # This is the table name
for_id: int|str,
) -> bool|int|None:
"""Helper to find an account_id associated with an object."""
from app.db_sql import sql_select
log.setLevel(logging.WARNING)
if fid := redis_lookup_id_random(record_id_random=for_id, table_name=for_type):
data = {'for_id': fid}
sql = f"SELECT account_id FROM `{for_type}` WHERE id = :for_id LIMIT 1;"
if result := sql_select(data=data, sql=sql):
return result.get('account_id')
return False
return None

67
app/lib_schema_v3.py Normal file
View File

@@ -0,0 +1,67 @@
from typing import Any, Dict
from sqlalchemy import text
from app.db_sql import db
from app.ae_obj_types_def import obj_type_kv_li
def get_object_schema_info(obj_type: str, view: str = 'default', variant: str = 'base') -> Dict[str, Any]:
"""
Introspects an object type to return its database and model structure.
Args:
obj_type: The name of the object (e.g., 'person').
view: The SQL view to describe (default, detail, etc.).
variant: The model variant to describe (base, in, out).
Returns:
A dictionary containing database column info and Pydantic field info.
"""
if obj_type not in obj_type_kv_li:
return {"error": f"Object type '{obj_type}' not found."}
obj_cfg = obj_type_kv_li[obj_type]
table_name = obj_cfg.get(f'tbl_{view}', obj_cfg.get('tbl_default', obj_cfg.get('tbl')))
model_key = f'mdl_{variant}' if variant != 'base' else 'mdl'
model = obj_cfg.get(model_key, obj_cfg.get('mdl_default', obj_cfg.get('mdl')))
if not table_name:
return {"error": f"Table configuration for '{obj_type}' is missing."}
schema_info = {
"object_type": obj_type,
"view": view,
"variant": variant,
"database": {"table_name": table_name, "columns": []},
"model": {"name": model.__name__ if hasattr(model, '__name__') else str(model), "fields": {}}
}
# 1. Database Introspection
try:
db_result = db.execute(text(f"DESCRIBE `{table_name}`"))
for row in db_result.fetchall():
# row format: (Field, Type, Null, Key, Default, Extra)
schema_info["database"]["columns"].append({
"field": row[0],
"db_type": row[1],
"nullable": row[2] == 'YES',
"required": row[2] == 'NO', # Explicitly capture NOT NULL
"key": row[3],
"db_default": row[4],
"extra": row[5]
})
except Exception as e:
schema_info["database"]["error"] = str(e)
# 2. Pydantic Model Introspection
if model and hasattr(model, "__fields__"):
for field_name, field in model.__fields__.items():
field_info = {
"alias": field.alias,
"type": str(field.outer_type_),
"required": field.required,
"default": field.default
}
if field.field_info.description:
field_info["description"] = field.field_info.description
schema_info["model"]["fields"][field_name] = field_info
return schema_info

82
app/lib_sql_core.py Normal file
View File

@@ -0,0 +1,82 @@
"""
Foundational SQL connection management for the Aether API.
Isolates the SQLAlchemy engine and global connection state to prevent circular imports.
"""
import logging
import threading
from typing import Any, Optional
from sqlalchemy import create_engine
from app.config import settings
log = logging.getLogger('root')
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
# 1. Thread-local storage for capturing last SQL error message
_sql_error_state = threading.local()
def get_last_sql_error() -> Optional[str]:
"""Retrieves and clears the last captured SQL error message."""
error = getattr(_sql_error_state, 'last_error', None)
_sql_error_state.last_error = None
return error
def set_last_sql_error(error: Any):
"""Sets the last captured SQL error message."""
_sql_error_state.last_error = str(error)
# 2. Initial Engine Setup
db_uri = settings.SQLALCHEMY_DB_URI
def create_ae_engine(uri: str):
return create_engine(
url = uri,
echo = False,
pool_size = settings.DB.get('pool_size', 10),
max_overflow = settings.DB.get('max_overflow', 20),
pool_use_lifo = True,
pool_pre_ping = True,
pool_recycle = settings.DB['pool_recycle'],
isolation_level = 'READ COMMITTED',
connect_args = {'connect_timeout': settings.DB['connect_timeout']}
)
engine = create_ae_engine(db_uri)
# DEPRECATED: Global shared 'db' connection. Use engine.connect() in context managers instead.
# Keeping for legacy compatibility but will phase out usage in crud lib.
db = engine.connect()
log.info('DB SQL Core: Initializing engine...')
# 3. Connection Management Logic
def reconnect_db() -> bool:
"""
Re-initializes the global database engine using current settings.
Useful after bootstrapping new credentials from the 'cfg' table.
"""
global engine, db, db_uri
log.info("DB SQL Core: Refreshing database connection engine...")
try:
if engine:
engine.dispose()
log.info("DB SQL Core: Disposed of previous database engine.")
db_uri = settings.SQLALCHEMY_DB_URI
engine = create_ae_engine(db_uri)
db = engine.connect()
safe_uri = db_uri.split('@')[-1] if '@' in db_uri else db_uri
log.info(f"DB SQL Core: Database engine re-established successfully: {safe_uri}")
return True
except Exception:
log.exception("DB SQL Core: FAILED to refresh database engine!")
return False
def sql_connect(current_db=None, log_lvl: int = logging.INFO) -> bool:
"""Refreshes the global database connection."""
log.setLevel(log_lvl)
log.info('DB SQL Core: Refreshing database connection via sql_connect...')
return reconnect_db()

392
app/lib_sql_crud.py Normal file
View File

@@ -0,0 +1,392 @@
"""
Standardized SQL CRUD operations for the Aether API.
Provides high-level helpers for INSERT, UPDATE, SELECT, and DELETE.
"""
import logging
import json
from typing import Any, List, Optional
from sqlalchemy import text, Time
from sqlalchemy.exc import IntegrityError, OperationalError, ProgrammingError
from app.log import log, logger_reset
# CRITICAL: Import the core module to access current global state
from app import lib_sql_core
from app.lib_sql_core import sql_connect, set_last_sql_error
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
# Helper for resolving random IDs
from app.lib_redis_helpers import lookup_id_random_pop
# ### BEGIN ### API DB SQL ### sql_insert() ###
@logger_reset
def sql_insert(
sql: str|None = None,
data: dict|None = None,
table_name: str|None = None,
rm_id_random: bool = False,
id_random_length: int = 8,
log_lvl: int = logging.WARNING,
) -> None|bool|int:
log.setLevel(log_lvl)
if sql:
sql_insert_stmt = text(sql)
elif table_name and data:
if rm_id_random:
data = lookup_id_random_pop(obj_data=data)
if not data.get('id_random', None) and id_random_length:
import secrets
data['id_random'] = secrets.token_urlsafe(id_random_length)
fields = []
values = []
for key, value in data.items():
if key != 'id':
fields.append('`'+str(key)+'`')
values.append(':'+str(key))
if isinstance(value, (dict, list)):
data[key] = json.dumps(value)
sql_insert_stmt = text(f"INSERT INTO `{table_name}` ({', '.join(fields)}) VALUES ({', '.join(values)});")
else:
log.error('SQL INSERT statement could not be created. Missing params.')
return False
trans = None
try:
with lib_sql_core.engine.connect() as conn:
trans = conn.begin()
result_insert = conn.execute(sql_insert_stmt, data)
trans.commit()
if result_insert.rowcount == 1 and result_insert.lastrowid > 0:
return result_insert.lastrowid
return False
except IntegrityError as e:
if trans: trans.rollback()
log.error('Integrity error (likely duplicate). Returning None')
log.debug(e)
set_last_sql_error(e)
return None
except Exception as e:
if trans: trans.rollback()
log.error('Unknown exception in sql_insert. Returning False')
log.exception(e)
set_last_sql_error(e)
return False
# ### END ### API DB SQL ### sql_insert() ###
# ### BEGIN ### API DB SQL ### sql_update() ###
@logger_reset
def sql_update(
sql: str|None = None,
data: dict|None = None,
table_name: str|None = None,
record_id: int|None = None,
record_id_random: str|None = None,
rm_id_random: bool = False,
id_random_length: None|int = None,
log_lvl: int = logging.WARNING,
):
log.setLevel(log_lvl)
if sql:
sql_update_stmt = text(sql)
elif table_name and data:
if rm_id_random:
data = lookup_id_random_pop(obj_data=data)
if not data.get('id_random', None) and id_random_length:
import secrets
data['id_random'] = secrets.token_urlsafe(id_random_length)
field_list = []
for key, value in data.items():
if key != 'id':
field_list.append('`'+str(key) + '` = :' + str(key))
if isinstance(value, (dict, list)):
data[key] = json.dumps(value)
sql_set = ', '.join(field_list)
if len(sql_set) < 4:
return None
if record_id:
data['id'] = record_id
sql_update_stmt = text(f'UPDATE `{table_name}` SET {sql_set} WHERE id = :id')
elif record_id_random:
data['id_random'] = record_id_random
sql_update_stmt = text(f'UPDATE `{table_name}` SET {sql_set} WHERE id_random = :id_random')
elif 'id' in data:
sql_update_stmt = text(f'UPDATE `{table_name}` SET {sql_set} WHERE id = :id')
elif 'id_random' in data:
sql_update_stmt = text(f'UPDATE `{table_name}` SET {sql_set} WHERE id_random = :id_random')
else:
return False
else:
return False
trans = None
try:
with lib_sql_core.engine.connect() as conn:
trans = conn.begin()
result_update = conn.execute(sql_update_stmt, data)
trans.commit()
if result_update.rowcount >= 1:
return True
return None
except OperationalError:
if trans: trans.rollback()
log.error('Operational error (gone away?). Retrying once...')
sql_connect()
try:
with lib_sql_core.engine.connect() as conn:
trans = conn.begin()
result_update = conn.execute(sql_update_stmt, data)
trans.commit()
if result_update.rowcount >= 1:
return True
return None
except Exception as e:
set_last_sql_error(e)
return False
except Exception as e:
if trans: trans.rollback()
log.exception(e)
set_last_sql_error(e)
return False
# ### END ### API DB SQL ### sql_update() ###
# ### BEGIN ### Core Help CRUD ### sql_insert_or_update() ###
@logger_reset
def sql_insert_or_update(
sql: str|None = None,
data: dict|None = None,
table_name: str|None = None,
rm_id_random: bool = False,
id_random_length: int|None = None,
log_lvl: int = logging.DEBUG,
):
log.setLevel(log_lvl)
if sql:
stmt = text(sql)
elif table_name and data:
if rm_id_random:
data = lookup_id_random_pop(obj_data=data)
if not data.get('id_random', None) and id_random_length:
import secrets
data['id_random'] = secrets.token_urlsafe(id_random_length)
fields = [f'`{k}`' for k in data.keys() if k != 'id']
placeholders = [f':{k}' for k in data.keys() if k != 'id']
updates = [f'`{k}` = :{k}' for k in data.keys() if k != 'id']
for k, v in data.items():
if isinstance(v, (dict, list)):
data[k] = json.dumps(v)
stmt = text(f"INSERT INTO `{table_name}` ({', '.join(fields)}) VALUES ({', '.join(placeholders)}) "
f"ON DUPLICATE KEY UPDATE {', '.join(updates)};")
else:
return False
trans = None
try:
with lib_sql_core.engine.connect() as conn:
trans = conn.begin()
res = conn.execute(stmt, data)
trans.commit()
return res.lastrowid if res.lastrowid > 0 else True
except Exception as e:
if trans: trans.rollback()
log.exception(e)
return False
# ### END ### Core Help CRUD ### sql_insert_or_update() ###
# ### BEGIN ### Core Help CRUD ### sql_select() ###
@logger_reset
def sql_select(
table_name: str|None = None,
record_id: int|None = None,
record_id_random: str|None = None,
field_name: str|None = None,
field_value = None,
enabled: str|None = None,
hidden: str|None = None,
qry_dict_li: dict|None = None,
fulltext_qry_dict: dict|None = None,
and_qry_dict: dict|None = None,
and_like_dict: dict|None = None,
or_like_dict: dict|None = None,
and_in_dict_li: dict|None = None,
search_query: Any|None = None,
searchable_fields: List[str]|None = None,
order_by_li: dict|None = None,
limit: int = 9999999,
offset: int = 0,
sql: str|None = None,
data: dict|None = None,
rm_id_random: bool = False,
as_dict: bool|None = True,
as_list: bool|None = False,
max_count: int = 100000,
log_lvl: int = logging.WARNING,
) -> None|bool|dict|list:
from app.lib_sql_search import (
sql_enable_part, sql_hidden_part, sql_search_qry_part,
sql_where_qry_part, sql_fulltext_qry_part, sql_and_qry_part,
sql_and_like_part, sql_or_like_part, sql_and_in_dict_li_part
)
log.setLevel(log_lvl)
sql_limit_offset = f'LIMIT {limit} OFFSET {offset}' if limit >= 0 and offset >= 0 else ''
sql_order_by = ''
if order_by_li and isinstance(order_by_li, dict):
order_by_str_li = [f'`{table_name}`.`{k}` {v}' for k, v in order_by_li.items()]
sql_order_by = f"ORDER BY {', '.join(order_by_str_li)}"
if table_name and not (record_id or record_id_random or field_name or field_value or sql or data):
data = {}
s_en, d_en = sql_enable_part(table_name, enabled) if enabled else ('', None)
s_hi, d_hi = sql_hidden_part(table_name, hidden) if hidden else ('', None)
if d_en is not None: data['enabled'] = d_en
if d_hi is not None: data['hidden'] = d_hi
s_search, d_search = ('', {})
if search_query:
s_search, d_search = sql_search_qry_part(search_query, searchable_fields, table_name=table_name)
data.update(d_search)
stmt = text(f"SELECT * FROM `{table_name}` WHERE 1=1 {s_search} {s_en} {s_hi} {sql_order_by} {sql_limit_offset};")
elif table_name and (record_id or record_id_random) and not (field_name or field_value or sql or data):
data = {'rid': record_id} if record_id else {'ridr': record_id_random}
where = f"`{table_name}`.id = :rid" if record_id else f"`{table_name}`.id_random = :ridr"
stmt = text(f"SELECT * FROM `{table_name}` WHERE {where} {sql_order_by} {sql_limit_offset};")
elif table_name and field_name and field_value and not (record_id or record_id_random or sql or data):
data = {field_name: field_value}
s_where, d_where = sql_where_qry_part(qry_dict_li) if qry_dict_li else ('', {})
s_ft, d_ft = sql_fulltext_qry_part(fulltext_qry_dict) if fulltext_qry_dict else ('', {})
s_and, d_and = sql_and_qry_part(and_qry_dict) if and_qry_dict else ('', {})
s_alike, d_alike = sql_and_like_part(and_like_dict) if and_like_dict else ('', {})
s_olike, d_olike = sql_or_like_part(or_like_dict) if or_like_dict else ('', {})
s_in, d_in = sql_and_in_dict_li_part(and_in_dict_li) if and_in_dict_li else ('', {})
s_search, d_search = sql_search_qry_part(search_query, searchable_fields, table_name=table_name) if search_query else ('', {})
s_en, d_en = sql_enable_part(table_name, enabled) if enabled else ('', None)
s_hi, d_hi = sql_hidden_part(table_name, hidden) if hidden else ('', None)
data.update(d_where); data.update(d_ft); data.update(d_and); data.update(d_alike)
data.update(d_olike); data.update(d_in); data.update(d_search)
if d_en is not None: data['enabled'] = d_en
if d_hi is not None: data['hidden'] = d_hi
stmt = text(f"SELECT * FROM `{table_name}` WHERE `{table_name}`.{field_name} = :{field_name} "
f"{s_where} {s_ft} {s_and} {s_alike} {s_olike} {s_in} {s_search} {s_en} {s_hi} {sql_order_by} {sql_limit_offset};")
elif table_name and data and not (record_id or record_id_random or field_name or field_value or sql):
if rm_id_random: data = lookup_id_random_pop(obj_data=data)
where_clauses = [f"`{table_name}`.{k} = :{k}" for k in data.keys()]
stmt = text(f"SELECT * FROM `{table_name}` WHERE {' AND '.join(where_clauses)} {sql_order_by} {sql_limit_offset};")
elif sql:
stmt = text(sql)
else:
return False
try:
with lib_sql_core.engine.connect() as conn:
result = conn.execute(stmt, data)
if not result:
return [] if as_list else None
# Fetch all rows first to determine actual count reliably
if hasattr(result, 'returns_rows') and not result.returns_rows:
log.warning("SQL Result does not return rows (ResourceClosedError prevented).")
return [] if as_list else None
rows = result.all()
except Exception as e:
log.error(f"SQL Fetch Error: {e}")
set_last_sql_error(e)
return False
count = len(rows)
if count == 0:
return [] if as_list else None
if count == 1:
record = dict(rows[0]) if as_dict else rows[0]
return [record] if as_list else record
# count > 1
records = [dict(r) for r in rows] if as_dict else rows
return records
# ### END ### Core Help CRUD ### sql_select() ###
# ### BEGIN ### API DB SQL ### run_sql_select() ###
@logger_reset
def run_sql_select(
sql: text,
data: dict|None = None,
log_lvl: int = logging.WARNING,
) -> Any:
log.setLevel(log_lvl)
try:
with lib_sql_core.engine.connect() as conn:
return conn.execute(sql, data)
except (OperationalError, ProgrammingError) as e:
log.error(f'DB Error: {e}. Retrying once...')
sql_connect()
try:
with lib_sql_core.engine.connect() as conn:
return conn.execute(sql, data)
except Exception as e2:
set_last_sql_error(e2)
raise e2 # RAISING instead of returning False
except Exception as e:
log.exception(e)
set_last_sql_error(e)
raise e # RAISING instead of returning False
# ### END ### API DB SQL ### run_sql_select() ###
# ### BEGIN ### Core Help CRUD ### sql_delete() ###
@logger_reset
def sql_delete(
table_name: str|None = None,
record_id: int|None = None,
record_id_random: str|None = None,
field_name: str|None = None,
field_value = None,
sql: str|None = None,
data: dict|None = None,
log_lvl: int = logging.INFO,
) -> None|bool:
log.setLevel(log_lvl)
if table_name and (record_id or record_id_random) and not (field_name or field_value or sql or data):
data = {'rid': record_id} if record_id else {'ridr': record_id_random}
where = f"`{table_name}`.id = :rid" if record_id else f"`{table_name}`.id_random = :ridr"
stmt = text(f"DELETE FROM `{table_name}` WHERE {where}")
elif table_name and field_name and field_value and not (record_id or record_id_random or sql or data):
data = {field_name: field_value}
stmt = text(f"DELETE FROM `{table_name}` WHERE `{table_name}`.{field_name} = :{field_name}")
elif sql:
stmt = text(sql)
else:
return False
try:
with lib_sql_core.engine.connect() as conn:
result = conn.execute(stmt, data) if data else conn.execute(stmt)
return True if result.rowcount >= 1 else None
except Exception as e:
log.exception(e)
return False
# ### END ### Core Help CRUD ### sql_delete() ###

275
app/lib_sql_search.py Normal file
View File

@@ -0,0 +1,275 @@
"""
Modular search builder and query generators for Aether.
"""
import logging
from typing import Any, List, Optional
from fastapi import HTTPException
from sqlalchemy import text
log = logging.getLogger(__name__)
def sql_limit_offset_part(limit: int, offset: int = 0) -> str:
"""Creates a partial SQL string for LIMIT and OFFSET."""
if limit >= 0 and offset >= 0:
log.info(f'Creating partial SQL string for LIMIT and OFFSET. Limit: {limit}; Offset: {offset}')
return f'LIMIT {limit} OFFSET {offset}'
else:
return ''
def sql_and_like_part(and_like_dict_obj: dict) -> tuple[str, dict]:
"""Creates a partial SQL string for AND LIKE queries."""
data = {}
if and_like_dict_obj and isinstance(and_like_dict_obj, dict):
log.info('Creating partial SQL string for additional AND LIKE queries.')
clauses = []
for key, value in and_like_dict_obj.items():
clauses.append(f"{key} LIKE :and_like_{key}")
data[f'and_like_{key}'] = value
return f"AND ({' AND '.join(clauses)})", data
return '', {}
def sql_or_like_part(or_like_dict_obj: dict) -> tuple[str, dict]:
"""Creates a partial SQL string for OR LIKE queries."""
data = {}
if or_like_dict_obj and isinstance(or_like_dict_obj, dict):
log.info('Creating partial SQL string for additional OR LIKE queries.')
clauses = []
for key, value in or_like_dict_obj.items():
clauses.append(f"{key} LIKE :or_like_{key}")
data[f'or_like_{key}'] = value
return f"AND ({' OR '.join(clauses)})", data
return '', {}
def sql_and_in_dict_li_part(and_in_dict_li_dict_obj: dict) -> tuple[str, dict]:
"""Creates a partial SQL string for AND IN queries."""
data = {}
if and_in_dict_li_dict_obj and isinstance(and_in_dict_li_dict_obj, dict):
log.info('Creating partial SQL string for additional AND IN queries.')
clauses = []
for key, value in and_in_dict_li_dict_obj.items():
clauses.append(f"{key} IN :and_in_{key}")
data[f'and_in_{key}'] = value
return f"AND ({' AND '.join(clauses)})", data
return '', {}
def sql_and_qry_part(and_qry_dict_obj: dict) -> tuple[str, dict]:
"""Creates a partial SQL string for additional AND queries (equals)."""
data = {}
if and_qry_dict_obj and isinstance(and_qry_dict_obj, dict):
log.info('Creating partial SQL string for additional AND queries.')
clauses = []
for key, value in and_qry_dict_obj.items():
clauses.append(f"{key} = :and_{key}")
data[f'and_{key}'] = value
return f"AND ({' AND '.join(clauses)})", data
return '', {}
def sql_fulltext_qry_part(fulltext_qry_dict: dict) -> tuple[str, dict]:
"""Creates a partial SQL string for fulltext search."""
data = {}
if fulltext_qry_dict and isinstance(fulltext_qry_dict, dict):
log.info('Creating partial SQL string for fulltext search.')
clauses = []
for key, value in fulltext_qry_dict.items():
clauses.append(f"MATCH( {key} ) AGAINST( :ft_{key} IN BOOLEAN MODE )")
data[f'ft_{key}'] = value
return f"AND ({' OR '.join(clauses)})", data
return '', {}
def sql_enable_part(table_name: str, enabled: str) -> tuple[str, bool|None]:
"""Handles enabled/disabled status filtering with schema check."""
from app import lib_sql_core
if not table_name: return '', None
if enabled in ['enabled', 'disabled', 'not_enabled', 'all']:
if enabled == 'all': return '', None
try:
with lib_sql_core.engine.connect() as conn:
conn.execute(text(f"SELECT enable FROM `{table_name}` LIMIT 0"))
except:
log.warning(f"Table '{table_name}' missing 'enable' column. Skipping filter.")
return '', None
val = (enabled == 'enabled')
return f"AND `{table_name}`.enable = {str(val).lower()}", val
return '', None
def sql_hidden_part(table_name: str, hidden: str) -> tuple[str, bool|None]:
"""Handles hidden status filtering with schema check."""
from app import lib_sql_core
if not table_name: return '', None
if hidden in ['hidden', 'not_hidden', 'all']:
if hidden == 'all': return '', None
try:
with lib_sql_core.engine.connect() as conn:
conn.execute(text(f"SELECT hide FROM `{table_name}` LIMIT 0"))
except:
log.warning(f"Table '{table_name}' missing 'hide' column. Skipping filter.")
return '', None
if hidden == 'hidden':
return f"AND `{table_name}`.hide = true", True
return f"AND (`{table_name}`.hide = false OR `{table_name}`.hide IS NULL)", False
return '', None
def sql_where_qry_part(qry_dict_li: list) -> tuple[str, dict]:
"""Standard v2 style WHERE clause builder."""
data = {}
if qry_dict_li and isinstance(qry_dict_li, list):
log.info('Creating partial SQL string for WHERE queries.')
clauses = []
for qry in qry_dict_li:
field = qry.get('field')
op = qry.get('operator')
val = qry.get('value')
type_ = qry.get('type', 'AND') or 'AND'
if op == 'MATCH':
clauses.append(f'{type_} MATCH( {field} ) AGAINST( :{field} IN BOOLEAN MODE )')
else:
clauses.append(f'{type_} {field} {op} :{field}')
data[field] = val
return ' '.join(clauses), data
return '', {}
def sql_search_qry_part(
search_query: Any,
searchable_fields: List[str]|None = None,
max_depth: int = 5,
table_name: str|None = None,
) -> tuple[str, dict]:
"""Recursively builds a SQL WHERE clause from a SearchQuery model."""
from app import lib_sql_core
data = {}
param_counter = [0]
def get_param_name():
param_counter[0] += 1
return f"sp_{param_counter[0]}"
operator_map = {
"eq": "=", "ne": "!=", "gt": ">", "gte": ">=", "lt": "<", "lte": "<=",
"like": "LIKE", "in": "IN", "is_null": "IS NULL", "is_not_null": "IS NOT NULL",
"contains": "LIKE", "icontains": "LIKE", "startswith": "LIKE", "istartswith": "LIKE",
"endswith": "LIKE", "iendswith": "LIKE"
}
def process_node(query_node, current_depth: int) -> str:
if current_depth > max_depth:
raise HTTPException(status_code=400, detail=f"Search query too complex.")
clauses = []
if hasattr(query_node, 'query_string') and query_node.query_string:
if query_node.query_string == '%': pass
else:
use_match = True
if table_name:
try:
with lib_sql_core.engine.connect() as conn:
conn.execute(text(f"SELECT default_qry_str FROM `{table_name}` LIMIT 0"))
except:
use_match = False
else:
use_match = False
if use_match:
p_name = get_param_name()
clauses.append(f"MATCH( default_qry_str ) AGAINST( :{p_name} IN BOOLEAN MODE )")
data[p_name] = query_node.query_string
elif searchable_fields:
like_clauses = []
# Fields to exclude from a generic text 'q' search (numeric, technical, or date fields)
exclude_patterns = [
'enable', 'hide', 'priority', 'sort', 'group',
'created_on', 'updated_on'
]
for field in searchable_fields:
# Exclude internal integer IDs specifically
if field.endswith('_id') or field == 'id':
continue
# Exclude other technical/meta fields
if any(x == field for x in exclude_patterns):
continue
f_p_name = get_param_name()
like_clauses.append(f"`{field}` LIKE :{f_p_name}")
data[f_p_name] = f"%{query_node.query_string}%"
if like_clauses: clauses.append(f"({' OR '.join(like_clauses)})")
for filter_attr in ['and_filters', 'or_filters']:
if hasattr(query_node, filter_attr) and getattr(query_node, filter_attr):
node_clauses = []
for item in getattr(query_node, filter_attr):
if hasattr(item, 'field'):
clause, item_data = process_filter(item)
node_clauses.append(clause); data.update(item_data)
else: node_clauses.append(f"({process_node(item, current_depth + 1)})")
if node_clauses:
joiner = ' AND ' if 'and' in filter_attr else ' OR '
clauses.append(f"({joiner.join(node_clauses)})")
return ' AND '.join(clauses)
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)
# 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',
'archive_id', 'archive_content_id',
'event_id',
'event_session_id', 'event_presentation_id', 'event_presenter_id',
'event_device_id', 'event_location_id', 'event_track_id',
'event_exhibit_id',
'event_person_id', 'event_registration_id',
'order_id', 'product_id', 'order_cart_id', 'membership_id', 'sponsorship_id',
'journal_id', 'journal_entry_id', 'page_id',
'post_id', 'post_comment_id',
'organization_id', 'address_id', 'contact_id',
'hosted_file_id'
]
if target_field in vision_fields:
# ONLY map to _random if the value is a string (looks like a random ID)
# If it's an integer, we want to query the original integer column.
is_int_val = isinstance(f.value, int) or (isinstance(f.value, str) and f.value.isdigit())
if not is_int_val:
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:
with lib_sql_core.engine.connect() as conn:
conn.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
if f.field not in searchable_fields:
raise HTTPException(status_code=400, detail=f"Unauthorized search field '{f.field}' (mapped to '{target_field}')")
sql_op = operator_map.get(f.op.lower())
if not sql_op: raise HTTPException(status_code=400, detail=f"Unsupported operator: {f.op}")
filter_data = {}
if f.op.lower() in ['is_null', 'is_not_null']: clause = f"`{target_field}` {sql_op}"
else:
p_name = get_param_name()
if f.op.lower() == 'in': clause = f"`{target_field}` IN (:{p_name})"; filter_data[p_name] = f.value
elif f.op.lower() in ['contains', 'icontains']: clause = f"`{target_field}` LIKE :{p_name}"; filter_data[p_name] = f"%{f.value}%"
elif f.op.lower() in ['startswith', 'istartswith']: clause = f"`{target_field}` LIKE :{p_name}"; filter_data[p_name] = f"{f.value}%"
elif f.op.lower() in ['endswith', 'iendswith']: clause = f"`{target_field}` LIKE :{p_name}"; filter_data[p_name] = f"%{f.value}"
else: clause = f"`{target_field}` {sql_op} :{p_name}"; filter_data[p_name] = f.value
return clause, filter_data
sql_where = process_node(search_query, 1)
return (f"AND ({sql_where})", data) if sql_where else ("", {})

5
app/log.py Normal file
View File

@@ -0,0 +1,5 @@
import logging
from app.lib_log_v3 import log, logger_reset, get_logger, setup_logging
# Re-exporting for backward compatibility with ~200 existing imports
__all__ = ['log', 'logging', 'logger_reset', 'get_logger', 'setup_logging']

100
app/log.py.snapshot Normal file
View File

@@ -0,0 +1,100 @@
import functools, logging
from app.config import settings
# stream options: 'ext://sys.stderr' or 'ext://sys.stdout'
# NOTE: This log config is confusing and may need work... 2022-10-07
# 'uvicorn' under 'loggers' creates an output to the 'console' handler
# Do not also add 'console' handler to the 'root' 'handlers' list
# For now just using that to add or remove file logging options.
# logging.config.dictConfig({
# 'version': 1,
# 'formatters': {
# 'default': {'format': '[%(asctime)s] %(levelname)s @ %(module)s.%(funcName)s()#%(lineno)d: %(message)s'},
# 'long': {'format': '[%(asctime)s] %(levelname)s @ %(module)s.%(funcName)s()#%(lineno)d: %(message)s', 'datefmt': '%Y-%m-%d %H:%M:%S'},
# 'short': {'format': '[%(asctime)s] %(levelname)s @ %(module)s.%(funcName)s()#%(lineno)d: %(message)s', 'datefmt': '%H:%M:%S', 'use_colors': True},
# },
# #'filename': 'example.log',
# # 'level': logging.ERROR,
# 'handlers': {
# 'console': {
# 'class': 'logging.StreamHandler',
# 'stream': 'ext://sys.stderr',
# 'formatter': 'short',
# },
# 'log_file_all': {
# 'level': 'NOTSET',
# 'class': 'logging.handlers.RotatingFileHandler',
# 'formatter': 'long',
# 'filename': settings.LOG_PATH['app'],
# 'maxBytes': 10485760, # 5,242,880 = 5 MB; 10,485,760 = 10 MB
# 'backupCount': 9
# },
# # 'log_file_warning': {
# # 'level': 'WARNING',
# # 'class': 'logging.handlers.RotatingFileHandler',
# # 'formatter': 'long',
# # 'filename': settings.LOG_PATH['app_warning'],
# # 'maxBytes': 512000, # 524,288 = 512KB
# # 'backupCount': 9
# # },
# # 'test_handler': {
# # 'class': 'logging.StreamHandler',
# # 'level': 'INFO',
# # 'formatter': 'short',
# # },
# # 'test_handler_all_rotate': {
# # 'class': 'logging.handlers.RotatingFileHandler',
# # 'level': 'NOTSET',
# # 'formatter': 'short',
# # 'filename': '/logs/test_rotate.log',
# # 'maxBytes': 100000, # 5120000 = 5 MB
# # 'backupCount': 2,
# # }
# },
# 'loggers': {
# # 'uvicorn': {'handlers': ['default'], 'level': 'INFO'},
# 'uvicorn': {'handlers': ['console'], 'level': 'INFO'},
# # 'uvicorn.error': {'level': 'INFO', 'handlers': ['default'], 'propagate': True},
# # 'uvicorn.error': {'level': 'INFO', 'handlers': ['console'], 'propagate': True},
# # 'uvicorn.access': {'handlers': ['access'], 'level': 'INFO', 'propagate': False},
# # 'gunicorn': {'handlers': ['console'], 'level': 'INFO'},
# },
# 'root': {
# 'handlers': ['log_file_all'], #, 'log_file_all', 'log_file_warning'],
# # 'handlers': ['console', 'log_file_all'], #, 'log_file_all', 'log_file_warning'],
# 'level': 'WARNING', # WARNING
# }
# })
# log = logging.getLogger('root')
# # log.setLevel(logging.INFO) # DEBUG > INFO > WARNING > ERROR > CRITICAL
# # logging.basicConfig(
# # format='[%(asctime)s] %(levelname)s @ %(module)s.%(funcName)s()#%(lineno)d: %(message)s'
# # )
# ### BEGIN ### Log ### logger_reset() ###
# https://realpython.com/primer-on-python-decorators/
# Updated 2022-02-15
# def logger_reset(func):
# # log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
# # log.info(locals())
# @functools.wraps(func)
# def wrapper(*args, **kwargs):
# if func.__name__ not in ['redis_lookup_id_random', 'sql_enable_part', 'sql_hidden_part']:
# log.info(f'*** Function: "{func.__name__}()"')
# log.debug(f'*** Function Positional Args: {args}\nFunction Key Args: {kwargs}')
# init_log_level = log.level
# returned_result = func(*args, **kwargs)
# log.debug(f'*** Function finished: "{func.__name__}()". Resetting logger level to level: {log.level} ***')
# log.setLevel(init_log_level)
# return returned_result
# return wrapper
# ### END ### Log ### logger_reset() ###
def get_logger(name: str) -> logging.Logger:
return logging.getLogger(name)

227
app/main.py Normal file
View File

@@ -0,0 +1,227 @@
import datetime, json, os, pytz, random, secrets, contextlib # , uvicorn
from enum import Enum
#from datetime import datetime, time, timedelta
from fastapi import Body, Cookie, Depends, FastAPI, File, Form, Header, HTTPException, Path, Query, Request, Response, status, UploadFile
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, HTMLResponse, JSONResponse, PlainTextResponse
from fastapi.staticfiles import StaticFiles
from functools import lru_cache
from pydantic import BaseModel, EmailStr, Field
from typing import Dict, List, Optional, Set, Union
from . import config
# from app.lib_general import common_route_params, Common_Route_Params
import logging
import app.log
from app.log import setup_logging
# Import middleware with alias to avoid shadowing 'app' FastAPI instance
from app.middleware import add_process_time_header as process_time_middleware
# Centralized router registry
from app.routers.registry import setup_routers
from app.db_sql import sql_select, reset_redis, reconnect_db
from app.lib_config_v3 import bootstrap_db_config, validate_critical_config
print('### **** *** ** * The Aether API v4 using FastAPI is loading... * ** *** **** ###')
log = logging.getLogger(__name__)
# log.setLevel(logging.DEBUG) # DEBUG > INFO > WARNING > ERROR > CRITICAL
#logging.basicConfig(
#format='[%(asctime)s] %(levelname)s @ %(module)s.%(funcName)s()#%(lineno)d: %(message)s'
#)
@contextlib.asynccontextmanager
async def lifespan(app: FastAPI):
"""
Handles application startup and shutdown lifecycle.
"""
# 1. Initialize Logging early but safely
setup_logging(config.settings)
log.info('### **** *** ** * Aether API v4 using FastAPI - Startup Lifespan Initiated * ** *** **** ###')
# 2. Bootstrapping Configuration from DB with robust error handling
log.info("Bootstrapping Configuration...")
# Save original settings for fallback
orig_db_server = config.settings.DB_SERVER
orig_db_user = config.settings.DB_USER
orig_db_pass = config.settings.DB_PASS
orig_db_name = config.settings.DB_NAME
orig_db_port = config.settings.DB_PORT
try:
if bootstrap_db_config(config.settings):
log.info("Successfully bootstrapped configuration from database.")
# Re-initialize the database engine with new credentials/URI
if reconnect_db():
log.info("Database connection re-established with production configuration.")
else:
log.warning("FAILED to re-establish database connection after bootstrap. Reverting to .env settings.")
config.settings.DB_SERVER = orig_db_server
config.settings.DB_USER = orig_db_user
config.settings.DB_PASS = orig_db_pass
config.settings.DB_NAME = orig_db_name
config.settings.DB_PORT = orig_db_port
reconnect_db()
else:
log.warning("System bootstrap from DB returned no results. Using environment defaults.")
except Exception as e:
log.error(f"Unexpected error during configuration bootstrap: {e}. Falling back to .env settings.")
config.settings.DB_SERVER = orig_db_server
config.settings.DB_USER = orig_db_user
config.settings.DB_PASS = orig_db_pass
config.settings.DB_NAME = orig_db_name
config.settings.DB_PORT = orig_db_port
reconnect_db()
# 3. Final validation of critical infrastructure
validate_critical_config(config.settings)
log.info('### **** *** ** * Aether API v4 using FastAPI - Startup Sequence Complete * ** *** **** ###')
yield
# Shutdown logic
log.info('### **** *** ** * Aether API v4 using FastAPI - Shutdown Lifespan Initiated * ** *** **** ###')
log.info('The Aether FastAPI API is shutting down...')
print('### **** *** ** * Aether API v4 using FastAPI - About to try FastAPI() while loading... * ** *** **** ###')
app = FastAPI(
# debug = True,
title = 'Aether API',
description = 'One Sky IT\'s Aether API v4 using FastAPI.',
version = '4.9.0',
operationsSorter = 'method',
lifespan = lifespan,
)
# @lru_cache()
# def get_settings():
# return config.Settings()
app.mount('/static', StaticFiles(directory='static'), name='static')
# Register all application routes
setup_routers(app)
# BEGIN: CORS
# NOTE: Eventually this should query the DB for the specific list based on the cfg table and or site_domain table. That way it is dynamic and only allowing those defined in the DB. No wildcards or regex.
# NOTE: Need to include .localhost for less browser restrictions! Mainly for audio and video.
app.add_middleware(
CORSMiddleware,
# allow_origins = origins,
allow_origins = config.settings.ORIGINS,
allow_origin_regex = config.settings.ORIGINS_REGEX,
# allow_origin_regex = 'https://.*\.oneskyit\.com',
allow_credentials = True,
allow_methods = ['*'],
allow_headers = ['*'],
#expose_headers = [],
#max_age = 600,
)
# END: CORS
# Register utility middleware from external module
app.middleware('http')(process_time_middleware)
# ### BEGIN ### API Main ### fastapi_root() ###
@app.get('/', tags=['Root'], response_class=PlainTextResponse)
async def fastapi_root(response: Response = Response):
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARN, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
# log.info(config.settings.APP_NAME)
log.info('One Sky IT\'s Aether API root (FastAPI)')
log.info('***')
log.debug('This is debug') # 10 DEBUG
log.info('This is info') # 20 INFO
log.warning('This is a warning') # 30 WARNING (and WARN)
log.error('This is an error') # 40 ERROR
log.exception('This is an exception') # 40 ERROR
log.critical('This is critical') # 50 CRITICAL
log.info('^^^')
log.warning('Resetting Redis...')
reset_redis()
log.info('Reset Redis')
response_data = {}
response_data['message'] = 'This is One Sky IT\'s Aether API root (FastAPI).'
current_datetime = datetime.datetime.now()
current_datetime_string = current_datetime.isoformat()
timezone = pytz.timezone("America/New_York")
current_datetime_tz = timezone.localize(current_datetime)
current_datetime_tz_string = current_datetime_tz.isoformat()
current_datetime_utc = datetime.datetime.utcnow()
current_datetime_utc_string = current_datetime_utc.isoformat()
current_datetime_utc_localize = pytz.utc.localize(current_datetime_utc)
current_datetime_utc_localize_string = current_datetime_utc_localize.isoformat()
current_datetime_utc_localize_pst = current_datetime_utc_localize.astimezone(pytz.timezone("America/Los_Angeles"))
current_datetime_utc_localize_pst_string = current_datetime_utc_localize_pst.isoformat()
response_data['datetime'] = current_datetime_string
response_data['datetime_tz'] = current_datetime_tz_string
response_data['datetime_utc'] = current_datetime_utc_string
response_data['datetime_utc_localize'] = current_datetime_utc_localize_string
response_data['datetime_utc_localize_pst'] = current_datetime_utc_localize_pst_string
response_data['url_safe_string_4_bytes_1'] = secrets.token_urlsafe(4)
response_data['url_safe_string_8_bytes_1'] = secrets.token_urlsafe(8)
response_data['url_safe_string_8_bytes_2'] = secrets.token_urlsafe(8)
response_data['url_safe_string_8_bytes_3'] = secrets.token_urlsafe(8)
response_data['url_safe_string_8_bytes_4'] = secrets.token_urlsafe(8)
response_data['url_safe_string_8_bytes_5'] = secrets.token_urlsafe(8)
response_data['url_safe_string_16_bytes_1'] = secrets.token_urlsafe(16)
response_data['url_safe_string_16_bytes_2'] = secrets.token_urlsafe(16)
response_data['url_safe_string_16_bytes_3'] = secrets.token_urlsafe(16)
response_data['url_safe_string_16_bytes_4'] = secrets.token_urlsafe(16)
response_data['url_safe_string_16_bytes_5'] = secrets.token_urlsafe(16)
response_data['hex_string_4_bytes_1'] = secrets.token_hex(4)
response_data['hex_string_8_bytes_1'] = secrets.token_hex(8)
response_data['hex_string_16_bytes_1'] = secrets.token_hex(16)
response_data['hex_string_32_bytes_1'] = secrets.token_hex(32)
log.debug(json.dumps(response_data, indent=4))
return json.dumps(response_data, indent=4) # , sort_keys=True
# ### END ### API Main ### fastapi_root() ###
# ### BEGIN ### API Main ### generate_id_random() ###
# NOTE: This is just a quick utility function to generate a bunch of random IDs.
# Updated 2022-03-30
@app.get('/generate_id_random', tags=['Root'], response_class=PlainTextResponse)
async def generate_id_random(response: Response = Response):
log.setLevel(logging.INFO) # DEBUG, INFO, WARN, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
response_data = {}
html_list = '<ul>'
for x in range(50):
html_list += f'<li>{secrets.token_urlsafe(8)}</li>'
html_list += '</ul>'
return HTMLResponse(content=html_list, status_code=200)
# ### END ### API Main ### generate_id_random() ###

625
app/main.py.snapshot Normal file
View File

@@ -0,0 +1,625 @@
import datetime, json, os, pytz, random, secrets # , uvicorn
from enum import Enum
#from datetime import datetime, time, timedelta
from fastapi import Body, Cookie, Depends, FastAPI, File, Form, Header, HTTPException, Path, Query, Request, Response, status, UploadFile
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, HTMLResponse, JSONResponse, PlainTextResponse
from fastapi.staticfiles import StaticFiles
from functools import lru_cache
from pydantic import BaseModel, EmailStr, Field
from typing import Dict, List, Optional, Set, Union
from . import config
from app.log import log, logging
# Import the routers here first:
from app.routers import ae_obj, aether_cfg, api_crud, api_crud_v2, api_crud_v3, api, importing, sql, account, activity_log, address, archive, archive_content, contact, cont_edu_cert, cont_edu_cert_person, data_store, event, event_abstract, event_badge, event_badge_importing, event_badge_template, event_device, event_exhibit, event_exhibit_tracking, event_file, event_importing, event_location, event_person, event_person_detail, event_person_tracking, event_presentation, event_presenter, event_registration, event_session, flask_cfg, fundraising, grant, hosted_file, journal, journal_entry, log_client_viewing, lookup, membership_cfg, membership_group, membership_person_group, membership_person, membership_person_profile, membership_type, membership_person_type, order, order_v3, order_line, order_cart, organization, page, person, person_user, post, post_comment, product, qr, site, site_domain, user, util_email, websockets_redis, e_confex, e_cvent, c_idaa, e_impexium, e_stripe
# from app.routers import aether_cfg, sql
from app.db_sql import sql_select, reset_redis # , sql_connect
print('### **** *** ** * The Aether API v4 using FastAPI is loading... * ** *** **** ###')
app = FastAPI(
# debug = True,
title = 'Aether API',
description = 'One Sky IT\'s Aether API v4 using FastAPI.',
version = '4.9.0',
operationsSorter = 'method',
)
log.setLevel(logging.INFO)
# log.debug(config.settings)
if aether_cfg_sql_result := sql_select(
table_name = 'cfg',
record_id = config.settings.AETHER_CFG['id'],
as_list = False,
max_count = 1,
):
aether_cfg_sql = aether_cfg_sql_result
config.settings.DB['server'] = aether_cfg_sql.get('db_server')
config.settings.DB['port'] = aether_cfg_sql.get('db_port')
config.settings.DB['name'] = aether_cfg_sql.get('db_name')
config.settings.DB['username'] = aether_cfg_sql.get('db_username')
config.settings.DB['password'] = aether_cfg_sql.get('db_password')
DB = config.settings.DB
config.settings.SQLALCHEMY_DB_URI = 'mysql://'+DB['username']+':'+DB['password']+'@'+DB['server']+'/'+DB['name']
# db_result = sql_connect(config.settings.SQLALCHEMY_DB_URI)
log.debug(config.settings.DB)
config.settings.SMTP['server'] = aether_cfg_sql.get('smtp_server')
config.settings.SMTP['port'] = aether_cfg_sql.get('smtp_port')
config.settings.SMTP['username'] = aether_cfg_sql.get('smtp_username')
config.settings.SMTP['password'] = aether_cfg_sql.get('smtp_password')
# config.settings.FILES_PATH['hosted_files_root'] = aether_cfg_sql.get('PATH_HOSTED_FILES_ROOT')
# config.settings.FILES_PATH['hosted_tmp_root'] = aether_cfg_sql.get('PATH_HOSTED_TMP_ROOT')
config.settings.FILES_PATH['hosted_files_root'] = aether_cfg_sql.get('path_hosted_files_root')
config.settings.FILES_PATH['hosted_tmp_root'] = aether_cfg_sql.get('path_hosted_tmp_root')
else:
# aether_cfg_sql_result
pass
log.debug(aether_cfg_sql_result)
log.debug(config.settings)
# @lru_cache()
# def get_settings():
# return config.Settings()
app.mount('/static', StaticFiles(directory='static'), name='static')
# Set up each route once the router has been imported
app.include_router(
ae_obj.router,
prefix='/ae_obj',
tags=['AE Object'],
)
app.include_router(
aether_cfg.router,
tags=['Aether Config'],
)
app.include_router(
api_crud.router,
prefix='/crud',
tags=['CRUD v1.2 (Legacy)'],
#dependencies=[Depends(get_token_header)],
#dependencies=[Depends(get_account_header)],
#responses={404: {'description': 'Not found'}},
)
app.include_router(
api_crud_v2.router,
prefix='/v2/crud',
tags=['CRUD v2.5'],
#dependencies=[Depends(get_token_header)],
#dependencies=[Depends(get_account_header)],
#responses={404: {'description': 'Not found'}},
)
app.include_router(
api_crud_v3.router,
prefix='/v3/crud',
tags=['CRUD v3'],
)
app.include_router(
api.router,
prefix='/api',
tags=['API'],
)
app.include_router(
flask_cfg.router,
prefix='/flask_cfg',
tags=['Flask CFG'],
)
app.include_router(
importing.router,
prefix='/importing',
tags=['Importing'],
)
app.include_router(
sql.router,
# prefix='/sql',
tags=['SQL'],
)
# # app.include_router(
# # flask_cfg.router,
# # prefix='/redis',
# # tags=['Redis'],
# # )
app.include_router(
account.router,
# prefix='/account',
tags=['Account'],
)
app.include_router(
activity_log.router,
prefix='/activity_log',
tags=['Activity Log'],
)
app.include_router(
address.router,
prefix='/address',
tags=['Address'],
)
app.include_router(
archive.router,
# prefix='/archive',
tags=['Archive'],
)
app.include_router(
archive_content.router,
prefix='/archive/content',
tags=['Archive Content'],
)
app.include_router(
contact.router,
prefix='/contact',
tags=['Contact'],
)
app.include_router(
cont_edu_cert.router,
tags=['Cont Edu Cert'],
)
app.include_router(
cont_edu_cert_person.router,
tags=['Cont Edu Cert Person'],
)
app.include_router(
data_store.router,
# prefix='/data_store',
tags=['Data Store'],
)
app.include_router(
event.router,
# prefix='/event',
tags=['Event'],
)
app.include_router(
event_abstract.router,
tags=['Event Abstract'],
)
app.include_router(
event_badge.router,
tags=['Event Badge'],
)
app.include_router(
event_badge_importing.router,
tags=['Event Badge Importing'],
)
app.include_router(
event_badge_template.router,
# prefix='/event/badge/template',
tags=['Event Badge Template'],
)
app.include_router(
event_device.router,
# prefix='/event/device',
tags=['Event Device'],
)
app.include_router(
event_exhibit.router,
# prefix='/event/exhibit',
tags=['Event Exhibit'],
)
app.include_router(
event_exhibit_tracking.router,
# prefix='/event/exhibit/tracking',
tags=['Event Exhibit Tracking'],
)
app.include_router(
event_file.router,
# prefix='/event/file',
tags=['Event File'],
)
app.include_router(
event_importing.router,
# prefix='/event/importing',
tags=['Event Importing'],
)
app.include_router(
event_location.router,
# prefix='/event/location',
tags=['Event Location'],
)
app.include_router(
event_person.router,
# prefix='/event/person',
tags=['Event Person'],
)
app.include_router(
event_person.router,
prefix='/event/person/detail',
tags=['Event Person Detail'],
)
app.include_router(
event_person_tracking.router,
tags=['Event Person Tracking'],
)
app.include_router(
event_presentation.router,
# prefix='/event/presentation',
tags=['Event Presentation'],
)
app.include_router(
event_presenter.router,
prefix='/event/presenter',
tags=['Event Presenter'],
)
app.include_router(
event_registration.router,
prefix='/event/registration',
tags=['Event Registration'],
)
app.include_router(
event_session.router,
# prefix='/event/session',
tags=['Event Session'],
)
app.include_router(
fundraising.router,
tags=['Fundraising'],
)
app.include_router(
grant.router,
tags=['Grant'],
)
app.include_router(
hosted_file.router,
prefix='/hosted_file',
tags=['Hosted File'],
)
app.include_router(
journal.router,
prefix='/journal',
tags=['Journal'],
)
app.include_router(
journal_entry.router,
# prefix='/journal/entry',
tags=['Journal Entry'],
)
app.include_router(
log_client_viewing.router,
# prefix='/log/client_viewing',
tags=['Log Client Viewing'],
)
app.include_router(
lookup.router,
prefix='/lu',
tags=['Lookup'],
)
app.include_router(
membership_cfg.router,
tags=['Membership Config'],
)
app.include_router(
membership_group.router,
tags=['Membership Group'],
)
app.include_router(
membership_person_group.router,
tags=['Membership Group Person'],
)
app.include_router(
membership_person_profile.router,
tags=['Membership Person Profile'],
)
app.include_router(
membership_person.router,
tags=['Membership Person'],
)
app.include_router(
membership_type.router,
tags=['Membership Type'],
)
app.include_router(
membership_person_type.router,
tags=['Membership Type Person'],
)
app.include_router(
order.router,
# prefix='/order',
tags=['Order'],
)
app.include_router(
order_v3.router,
# prefix='/order',
tags=['Order v3'],
)
app.include_router(
order_line.router,
# prefix='/order',
tags=['Order Line'],
)
app.include_router(
order_cart.router,
prefix='/order/cart',
tags=['Order Cart'],
)
app.include_router(
organization.router,
prefix='/organization',
tags=['Organization'],
)
app.include_router(
page.router,
prefix='/page',
tags=['Page'],
)
app.include_router(
person.router,
tags=['Person'],
)
app.include_router(
person_user.router,
prefix='/person_user',
tags=['Person User'],
)
app.include_router(
post.router,
# prefix='/post',
tags=['Post'],
)
app.include_router(
post_comment.router,
prefix='/post/comment',
tags=['Post Comment'],
)
app.include_router(
product.router,
# prefix='/product',
tags=['Product'],
)
app.include_router(
qr.router,
tags=['QR'],
)
app.include_router(
site.router,
# prefix='/site',
tags=['Site'],
)
app.include_router(
site_domain.router,
# prefix='/site/domain',
tags=['Site Domain'],
)
app.include_router(
user.router,
tags=['User'],
)
app.include_router(
util_email.router,
tags=['Utility: Email'],
)
# app.include_router(
# websockets.router,
# # prefix='/websocket',
# tags=['Websockets'],
# # dependencies=[Depends(get_token_header)],
# # responses={404: {'description': 'Not found'}},
# )
app.include_router(
websockets_redis.router,
tags=['Websockets (Redis)'],
)
app.include_router(
e_confex.router,
prefix='/e/confex',
tags=['External Service: Confex'],
)
app.include_router(
e_cvent.router,
prefix='/e/cvent',
tags=['External Service: Cvent'],
)
app.include_router(
e_impexium.router,
prefix='/e/impexium',
tags=['External Service: Impexium'],
)
app.include_router(
e_stripe.router,
prefix='/e/stripe',
tags=['External Service: Stripe'],
)
app.include_router(
c_idaa.router,
prefix='/c/idaa',
tags=['Client: IDAA'],
)
# BEGIN: CORS
# NOTE: Eventually this should query the DB for the specific list based on the cfg table and or site_domain table. That way it is dynamic and only allowing those defined in the DB. No wildcards or regex.
# NOTE: Need to include .localhost for less browser restrictions! Mainly for audio and video.
app.add_middleware(
CORSMiddleware,
# allow_origins = origins,
allow_origins = config.settings.ORIGINS,
allow_origin_regex = config.settings.ORIGINS_REGEX,
# allow_origin_regex = 'https://.*\.oneskyit\.com',
allow_credentials = True,
allow_methods = ['*'],
allow_headers = ['*'],
#expose_headers = [],
#max_age = 600,
)
# END: CORS
@app.on_event('startup')
async def startup():
log.setLevel(logging.INFO) # DEBUG, INFO, WARN, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
log.info('The Aether FastAPI API is starting up...')
#await database.connect()
@app.on_event('shutdown')
async def shutdown():
log.setLevel(logging.INFO) # DEBUG, INFO, WARN, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
log.info('The Aether FastAPI API is shutting down...')
#await database.disconnect()
#Add the processing time to the response header.
@app.middleware('http')
async def add_process_time_header(request: Request, call_next):
import time
start_time = time.time()
response = await call_next(request)
process_time = time.time() - start_time
response.headers['X-Process-Time'] = str(process_time)
return response
# ### BEGIN ### API Main ### fastapi_root() ###
@app.get('/', tags=['Root'], response_class=PlainTextResponse)
async def fastapi_root(response: Response = Response):
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARN, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
# log.info(config.settings.APP_NAME)
log.info('One Sky IT\'s Aether API root (FastAPI)')
log.info('***')
log.debug('This is debug') # 10 DEBUG
log.info('This is info') # 20 INFO
log.warning('This is a warning') # 30 WARNING (and WARN)
log.error('This is an error') # 40 ERROR
log.exception('This is an exception') # 40 ERROR
log.critical('This is critical') # 50 CRITICAL
log.info('^^^')
log.warning('Resetting Redis...')
reset_redis()
log.info('Reset Redis')
response_data = {}
response_data['message'] = 'This is One Sky IT\'s Aether API root (FastAPI).'
current_datetime = datetime.datetime.now()
current_datetime_string = current_datetime.isoformat()
timezone = pytz.timezone("America/New_York")
current_datetime_tz = timezone.localize(current_datetime)
current_datetime_tz_string = current_datetime_tz.isoformat()
current_datetime_utc = datetime.datetime.utcnow()
current_datetime_utc_string = current_datetime_utc.isoformat()
current_datetime_utc_localize = pytz.utc.localize(current_datetime_utc)
current_datetime_utc_localize_string = current_datetime_utc_localize.isoformat()
current_datetime_utc_localize_pst = current_datetime_utc_localize.astimezone(pytz.timezone("America/Los_Angeles"))
current_datetime_utc_localize_pst_string = current_datetime_utc_localize_pst.isoformat()
response_data['datetime'] = current_datetime_string
response_data['datetime_tz'] = current_datetime_tz_string
response_data['datetime_utc'] = current_datetime_utc_string
response_data['datetime_utc_localize'] = current_datetime_utc_localize_string
response_data['datetime_utc_localize_pst'] = current_datetime_utc_localize_pst_string
response_data['url_safe_string_4_bytes_1'] = secrets.token_urlsafe(4)
response_data['url_safe_string_8_bytes_1'] = secrets.token_urlsafe(8)
response_data['url_safe_string_8_bytes_2'] = secrets.token_urlsafe(8)
response_data['url_safe_string_8_bytes_3'] = secrets.token_urlsafe(8)
response_data['url_safe_string_8_bytes_4'] = secrets.token_urlsafe(8)
response_data['url_safe_string_8_bytes_5'] = secrets.token_urlsafe(8)
response_data['url_safe_string_16_bytes_1'] = secrets.token_urlsafe(16)
response_data['url_safe_string_16_bytes_2'] = secrets.token_urlsafe(16)
response_data['url_safe_string_16_bytes_3'] = secrets.token_urlsafe(16)
response_data['url_safe_string_16_bytes_4'] = secrets.token_urlsafe(16)
response_data['url_safe_string_16_bytes_5'] = secrets.token_urlsafe(16)
response_data['hex_string_4_bytes_1'] = secrets.token_hex(4)
response_data['hex_string_8_bytes_1'] = secrets.token_hex(8)
response_data['hex_string_16_bytes_1'] = secrets.token_hex(16)
response_data['hex_string_32_bytes_1'] = secrets.token_hex(32)
log.debug(json.dumps(response_data, indent=4))
return json.dumps(response_data, indent=4) # , sort_keys=True
# ### END ### API Main ### fastapi_root() ###
# ### BEGIN ### API Main ### generate_id_random() ###
# NOTE: This is just a quick utility function to generate a bunch of random IDs.
# Updated 2022-03-30
@app.get('/generate_id_random', tags=['Root'], response_class=PlainTextResponse)
async def generate_id_random(response: Response = Response):
log.setLevel(logging.INFO) # DEBUG, INFO, WARN, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
response_data = {}
html_list = '<ul>'
for x in range(50):
html_list += f'<li>{secrets.token_urlsafe(8)}</li>'
html_list += '</ul>'
return HTMLResponse(content=html_list, status_code=200)
# ### END ### API Main ### generate_id_random() ###
# ### BEGIN ### API Main ### sql_test() ###
# ### TEST TEST TEST ### #
@app.get('/sql_test', tags=['Testing'], response_class=PlainTextResponse)
async def sql_test(response: Response = Response):
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARN, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
return mk_resp(data=False, status_code=501, response=response)
log.info('Getting all accounts from DB...')
sql = text(
"""
SELECT id, id_random, name, enable
FROM `account`
"""
)
try:
result = db.execute(sql)
except Exception as e:
log.error('*** An exception happened. ***')
log.error(repr(e))
log.error('***')
log.error(str(e))
log.error('^^^ exception ^^^')
else:
if result.rowcount:
record_li = [dict(record) for record in result.fetchall()]
log.debug(record_li)
else:
log.error('No records found. Something went wrong.')
log.info('Got the account list')
response_data = {}
response_data['message'] = 'This is the Aether API using FastAPI.'
response_data['data'] = record_li
return json.dumps(response_data, indent=4) # , sort_keys=True
# ### END ### API Main ### sql_test() ###

View File

@@ -0,0 +1,65 @@
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_select
from app.lib_general import log, logging, logger_reset
from app.methods.membership_cfg_methods import load_membership_cfg_obj
from app.models.account_cfg_models import Account_Cfg_Base
# ### BEGIN ### API Account Cfg Methods ### load_account_cfg_obj() ###
@logger_reset
def load_account_cfg_obj(
account_id: int|str,
model_as_dict: bool = False,
# inc_event_cfg: bool = False,
inc_fundraising_cfg: bool = False,
inc_membership_cfg: bool = False,
) -> Account_Cfg_Base|dict|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if account_id := redis_lookup_id_random(record_id_random=account_id, table_name='account'): pass
else: return False
log.info(f'Getting Account CFG ID: {account_id}')
if account_cfg_rec := sql_select(
table_name = 'v_account_cfg', # This view should probably be cleaned up
field_name = 'account_id',
field_value = account_id
): pass
else: return False
log.debug(account_cfg_rec)
try:
account_cfg_obj = Account_Cfg_Base(**account_cfg_rec)
except ValidationError as e:
log.error(e.json())
log.debug(account_cfg_obj)
if inc_fundraising_cfg:
if fundraising_cfg_dict := load_fundraising_cfg_obj_old(
account_id = account_id,
model_as_dict = model_as_dict,
):
account_cfg_obj.fundraising_cfg = fundraising_cfg_dict
else: account_cfg_obj.fundraising_cfg = None
if inc_membership_cfg:
if membership_cfg_dict := load_membership_cfg_obj(
account_id = account_id,
model_as_dict = model_as_dict,
):
account_cfg_obj.membership_cfg = membership_cfg_dict
else: account_cfg_obj.membership_cfg = None
if model_as_dict:
return account_cfg_obj.dict(by_alias=True, exclude_unset=True) # pylint: disable=no-member
else:
return account_cfg_obj
# ### END ### API Account Cfg Methods ### load_account_cfg_obj() ###

View File

@@ -0,0 +1,877 @@
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_enable_part, sql_limit_offset_part, sql_select
from app.lib_general import log, logging
from app.methods.account_cfg_methods import load_account_cfg_obj
from app.methods.address_methods import get_address_rec_list, load_address_obj
from app.methods.archive_methods import get_archive_rec_list, load_archive_obj
from app.methods.contact_methods import get_contact_rec_list, load_contact_obj
from app.methods.event_methods import get_event_rec_list, load_event_obj
from app.methods.hosted_file_methods import get_hosted_file_rec_list, load_hosted_file_obj
from app.methods.journal_methods import get_journal_rec_list, load_journal_obj
from app.methods.membership_cfg_methods import load_membership_cfg_obj
from app.methods.membership_group_methods import get_membership_group_rec_list, load_membership_group_obj
from app.methods.membership_person_methods import get_membership_person_rec_list, load_membership_person_obj
from app.methods.membership_type_methods import get_membership_type_rec_list, load_membership_type_obj
from app.methods.order_methods import get_order_rec_list, load_order_obj
# from app.methods.order_cart_methods import get_order_cart_rec_list, load_order_cart_obj
from app.methods.organization_methods import get_organization_rec_list, load_organization_obj
from app.methods.person_methods import get_person_rec_list, load_person_obj
from app.methods.product_methods import get_product_rec_list, load_product_obj
from app.methods.post_methods import get_post_rec_list, load_post_obj
from app.methods.site_methods import get_site_rec_list, load_site_obj
from app.methods.user_methods import get_user_rec_list, load_user_obj
from app.models.account_models import Account_Base
from app.models.account_cfg_models import Account_Cfg_Base
# from app.models.membership_cfg_models import Membership_Cfg_Base
# ### BEGIN ### API Account Methods ### load_account_obj() ###
# Working well as of 2021-06-11. Using as a template for other load objects.
def load_account_obj(
account_id: int|str,
enabled: str = 'enabled', # enabled, disabled, all
limit: int = 1000,
offset: int = 0,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
inc_account_cfg: bool = False, # Priority l1
inc_address: bool = False, # Under contact
inc_address_list: bool = False, # Priority l3
inc_archive: bool = False,
inc_archive_list: bool = False, # Priority l1
inc_archive_content: bool = False,
inc_archive_content_list: bool = False, # Priority l2
inc_contact: bool = False,
inc_contact_list: bool = False, # Priority l3
inc_event: bool = False,
inc_event_list: bool = False, # Priority l1
# inc_event_abstract: bool = False,
# inc_event_abstract_list: bool = False,
# inc_event_badge: bool = False,
# inc_event_badge_list: bool = False,
inc_event_cfg: bool = False,
# inc_event_device: bool = False,
# inc_event_device_list: bool = False,
inc_event_exhibit: bool = False,
inc_event_exhibit_list: bool = False,
inc_event_file: bool = False,
inc_event_file_list: bool = False,
inc_event_location: bool = False, # For event_session child object
inc_event_location_list: bool = False,
inc_event_person: bool = False,
inc_event_person_list: bool = False,
inc_event_presentation: bool = False,
inc_event_presentation_list: bool = False,
inc_event_presenter_cat: bool = False, # For event_session child object
inc_event_presenter: bool = False,
inc_event_presenter_list: bool = False,
inc_event_registration: bool = False,
inc_event_registration_cfg: bool = False,
inc_event_registration_list: bool = False,
inc_event_session: bool = False,
inc_event_session_list: bool = False,
# inc_event_track: bool = False, # For event_session child object
inc_event_track_list: bool = False,
inc_fundraising_cfg: bool = False,
inc_hosted_file_list: bool = False,
inc_hosted_file_link_list: bool = False,
inc_journal_list: bool = False, # Priority l3
inc_journal_entry_list: bool = False, # Priority l3
inc_membership_cfg: bool = False,
inc_membership_group_list: bool = False, # List of groups for the account
inc_membership_person: bool = False,
inc_membership_person_group_list: bool = False, # List of members of a group
inc_membership_person_list: bool = False, # List of people that have membersship
inc_membership_person_profile: bool = False,
inc_membership_person_type: bool = False,
inc_membership_type_list: bool = False,
inc_order: bool = False,
inc_order_cfg: bool = False,
inc_order_list: bool = False, # Priority l1
inc_order_line_list: bool = False, # Priority l2
inc_order_cart: bool = False,
inc_order_cart_line_list: bool = False, # Priority l2
inc_order_cart_list: bool = False,
inc_organization: bool = False,
inc_organization_list: bool = False, # Priority l3
# inc_page: bool = False,
inc_page_list: bool = False, # Priority l3
inc_person: bool = False,
inc_person_list: bool = False, # Priority l2
inc_post: bool = False,
inc_post_list: bool = False, # Priority l1
inc_post_comment: bool = False,
inc_post_comment_list: bool = False,
inc_product: bool = False,
inc_product_list: bool = False, # Priority l3
# inc_site: bool = False,
inc_site_list: bool = False, # Priority l3
inc_site_domain_list: bool = False, # Priority l3
inc_user: bool = False,
inc_user_list: bool = False, # Priority l2
inc_user_role_list: bool = False,
) -> Account_Base|dict|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if account_id := redis_lookup_id_random(record_id_random=account_id, table_name='account'): pass
else: return False
if account_rec := sql_select(table_name='v_account', record_id=account_id): pass
else: return False
#log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(account_rec)
try:
account_obj = Account_Base(**account_rec)
log.debug(account_obj)
except ValidationError as e:
log.error(e.json())
if inc_account_cfg:
if account_cfg_dict := load_account_cfg_obj(
account_id = account_id,
model_as_dict = model_as_dict,
# inc_event_cfg = inc_event_cfg,
inc_fundraising_cfg = inc_fundraising_cfg,
inc_membership_cfg = inc_membership_cfg,
):
account_obj.account_cfg = account_cfg_dict
else: account_obj.account_cfg = None
# Updated 2021-06-17
if inc_address_list:
if address_rec_list_result := get_address_rec_list(
for_type = 'account', # 'account' is a special case
for_id = account_id,
enabled = enabled,
limit = limit,
offset = offset,
):
address_dict_list = []
for address_rec in address_rec_list_result:
address_dict_list.append(
load_address_obj(
address_id = address_rec.get('address_id', None),
enabled = enabled,
limit = limit,
offset = offset,
model_as_dict = model_as_dict,
)
)
account_obj.address_list = address_dict_list
else: account_obj.address_list = []
# Updated 2021-06-17
if inc_archive_list:
if archive_rec_list_result := get_archive_rec_list(
for_obj_type = 'account',
for_obj_id = account_id,
limit = limit,
enabled = enabled,
):
archive_dict_list = []
for archive_rec in archive_rec_list_result:
archive_dict_list.append(
load_archive_obj(
archive_id = archive_rec.get('archive_id', None),
limit = limit,
model_as_dict = model_as_dict,
enabled = enabled,
inc_archive_content_list = inc_archive_content_list,
)
)
account_obj.archive_list = archive_dict_list
else: account_obj.archive_list = []
# Updated 2021-06-17
if inc_contact_list:
if contact_rec_list_result := get_contact_rec_list(
for_type = 'account', # 'account' is a special case
for_id = account_id,
enabled = enabled,
limit = limit,
offset = offset,
):
contact_dict_list = []
for contact_rec in contact_rec_list_result:
contact_dict_list.append(
load_contact_obj(
contact_id = contact_rec.get('contact_id', None),
enabled = enabled,
limit = limit,
offset = offset,
model_as_dict = model_as_dict,
inc_address = inc_address,
)
)
account_obj.contact_list = contact_dict_list
else: account_obj.contact_list = []
# Updated 2021-06-17
if inc_event_list:
if event_rec_list_result := get_event_rec_list(
# for_obj_type = 'account',
# for_obj_id = account_id,
account_id = account_id,
limit = limit,
enabled = enabled,
):
event_dict_list = []
for event_rec in event_rec_list_result:
event_dict_list.append(
load_event_obj(
event_id = event_rec.get('event_id', None),
limit = limit,
model_as_dict = model_as_dict,
enabled = enabled,
# inc_location_address = inc_address,
# inc_contact_1 = inc_contact,
# inc_contact_2 = inc_contact,
# inc_contact_3 = inc_contact,
# inc_event_abstract_list = inc_event_abstract_list,
# inc_event_badge_list = inc_event_badge_list,
# inc_event_device_list = inc_event_device_list,
inc_event_exhibit_list = inc_event_exhibit_list,
inc_event_file_list = inc_event_file_list,
inc_event_location_list = inc_event_location_list,
inc_event_person_list = inc_event_person_list,
inc_event_presentation_list = inc_event_presentation_list,
inc_event_presenter_list = inc_event_presenter_list,
inc_event_registration_list = inc_event_registration_list,
inc_event_session_list = inc_event_session_list,
inc_event_track_list = inc_event_track_list,
# inc_person = inc_person,
# inc_user = inc_user,
)
)
account_obj.event_list = event_dict_list
else: account_obj.event_list = []
# Updated 2021-06-18
if inc_hosted_file_list:
if hosted_file_rec_list_result := get_hosted_file_rec_list(
for_obj_type = 'account',
for_obj_id = account_id,
limit = limit,
enabled = enabled,
):
hosted_file_dict_list = []
for hosted_file_rec in hosted_file_rec_list_result:
hosted_file_dict_list.append(
load_hosted_file_obj(
hosted_file_id = hosted_file_rec.get('hosted_file_id', None),
limit = limit,
model_as_dict = model_as_dict,
enabled = enabled,
inc_hosted_file_link_list = inc_hosted_file_link_list,
)
)
account_obj.hosted_file_list = hosted_file_dict_list
else: account_obj.hosted_file_list = []
# Updated 2021-06-18
if inc_journal_list:
if journal_rec_list_result := get_journal_rec_list(
for_obj_type = 'account',
for_obj_id = account_id,
limit = limit,
enabled = enabled,
):
journal_dict_list = []
for journal_rec in journal_rec_list_result:
journal_dict_list.append(
load_journal_obj(
journal_id = journal_rec.get('journal_id', None),
limit = limit,
model_as_dict = model_as_dict,
enabled = enabled,
inc_journal_entry_list = inc_journal_entry_list,
)
)
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(journal_dict_list)
account_obj.journal_list = journal_dict_list
else: account_obj.journal_list = []
# Updated 2021-06-21
if inc_membership_group_list:
if membership_group_rec_list_result := get_membership_group_rec_list(
for_obj_type = 'account',
for_obj_id = account_id,
limit = limit,
enabled = enabled,
):
membership_group_result_list = []
for membership_group_rec in membership_group_rec_list_result:
membership_group_result_list.append(
load_membership_group_obj(
membership_group_id = membership_group_rec.get('membership_group_id', None),
limit = limit,
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
inc_membership_person_list = inc_membership_person_list,
# inc_membership_person_group_list = inc_membership_person_group_list,
inc_product_list = inc_product_list,
)
)
account_obj.membership_group_list = membership_group_result_list
else: account_obj.membership_group_list = []
# Updated 2021-06-21
if inc_membership_person_list:
if membership_person_rec_list_result := get_membership_person_rec_list(
for_obj_type = 'account',
for_obj_id = account_id,
limit = limit,
enabled = enabled,
):
membership_person_result_list = []
for membership_person_rec in membership_person_rec_list_result:
membership_person_result_list.append(
load_membership_person_obj(
membership_person_id = membership_person_rec.get('membership_person_id', None),
limit = limit,
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
inc_membership_group_list = inc_membership_group_list,
inc_membership_person_profile = inc_membership_person_profile,
# inc_membership_person_list = inc_membership_person_list,
# inc_membership_person_list = inc_membership_person_list,
# inc_product_list = inc_product_list,
inc_person = inc_person,
inc_user = inc_user,
)
)
account_obj.membership_person_list = membership_person_result_list
else: account_obj.membership_person_list = []
# Updated 2021-06-18
if inc_membership_type_list:
if membership_type_rec_list_result := get_membership_type_rec_list(
for_obj_type = 'account',
for_obj_id = account_id,
limit = limit,
enabled = enabled,
):
membership_type_result_list = []
for membership_type_rec in membership_type_rec_list_result:
membership_type_result_list.append(
load_membership_type_obj(
membership_type_id = membership_type_rec.get('membership_type_id', None),
limit = limit,
enabled = enabled,
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
inc_membership_person_list = inc_membership_person_list,
# inc_membership_type_member_list = inc_membership_type_member_list,
inc_product_list = inc_product_list,
)
)
account_obj.membership_type_list = membership_type_result_list
else: account_obj.membership_type_list = []
# Updated 2021-06-17
if inc_order_list:
if order_rec_list_result := get_order_rec_list(
for_obj_type = 'account',
for_obj_id = account_id,
limit = limit,
enabled = enabled,
):
order_dict_list = []
for order_rec in order_rec_list_result:
order_dict_list.append(
load_order_obj(
order_id = order_rec.get('order_id', None),
limit = limit,
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
enabled = enabled,
inc_order_line_list = inc_order_line_list,
inc_order_cfg = inc_order_cfg,
inc_person = inc_person,
)
)
account_obj.order_list = order_dict_list
else: account_obj.order_list = []
# Updated 2021-06-17
if inc_organization_list:
if organization_rec_list_result := get_organization_rec_list(
for_obj_type = 'account',
for_obj_id = account_id,
limit = limit,
enabled = enabled,
):
organization_dict_list = []
for organization_rec in organization_rec_list_result:
organization_dict_list.append(
load_organization_obj(
organization_id = organization_rec.get('organization_id', None),
limit = limit,
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
enabled = enabled,
inc_address = inc_address,
inc_contact = inc_contact,
inc_person = inc_person,
inc_user = inc_user,
)
)
account_obj.organization_list = organization_dict_list
else: account_obj.organization_list = []
# Updated 2021-06-17
if inc_person_list:
if person_rec_list_result := get_person_rec_list(
for_obj_type = 'account',
for_obj_id = account_id,
enabled = enabled,
limit = limit,
offset = offset,
):
person_result_list = []
for person_rec in person_rec_list_result:
person_result_list.append(
load_person_obj(
person_id = person_rec.get('person_id', None),
limit = limit,
offset = offset,
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
enabled = enabled,
inc_address = inc_address,
inc_contact = inc_contact,
inc_organization = inc_organization,
inc_user = inc_user,
)
)
account_obj.person_list = person_result_list
else: account_obj.person_list = []
# Updated 2021-06-18
if inc_post_list:
if post_rec_list_result := get_post_rec_list(
for_obj_type = 'account',
for_obj_id = account_id,
limit = limit,
enabled = enabled,
):
post_result_list = []
for post_rec in post_rec_list_result:
post_result_list.append(
load_post_obj(
post_id = post_rec.get('post_id', None),
limit = limit,
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
enabled = enabled,
inc_post_comment_list = inc_post_comment_list,
inc_person = inc_person,
inc_user = inc_user,
)
)
account_obj.post_list = post_result_list
else: account_obj.post_list = []
# Updated 2021-06-17
if inc_product_list:
if product_rec_list_result := get_product_rec_list(
for_obj_type = 'account',
for_obj_id = account_id,
limit = limit,
enabled = enabled,
):
product_result_list = []
for product_rec in product_rec_list_result:
product_result_list.append(
load_product_obj(
product_id = product_rec.get('product_id', None),
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
)
)
account_obj.product_list = product_result_list
else: account_obj.product_list = []
# Updated 2021-06-17
if inc_site_list:
if site_rec_list_result := get_site_rec_list(
for_obj_type = 'account',
for_obj_id = account_id,
limit = limit,
enabled = enabled,
):
site_result_list = []
for site_rec in site_rec_list_result:
site_result_list.append(
load_site_obj(
site_id = site_rec.get('site_id', None),
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
inc_site_domain_list = inc_site_domain_list,
)
)
account_obj.site_list = site_result_list
else: account_obj.site_list = []
# Updated 2021-12-08
if inc_user_list:
if user_rec_list_result := get_user_rec_list(
account_id = account_id,
limit = limit,
enabled = enabled,
):
user_result_list = []
for user_rec in user_rec_list_result:
user_result_list.append(
load_user_obj(
user_id = user_rec.get('user_id', None),
limit = limit,
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
enabled = enabled,
inc_address = inc_address,
inc_contact = inc_contact,
inc_event_list = inc_event_list,
inc_order_list = inc_order_list,
inc_order_cart_list = inc_order_cart_list,
# inc_organization = inc_organization,
inc_person = inc_person,
inc_user_role_list = inc_user_role_list,
)
)
account_obj.user_list = user_result_list
else: account_obj.user_list = []
if model_as_dict:
return account_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return account_obj
# ### END ### API Account Methods ### load_account_obj() ###
# ### BEGIN ### API Account Methods ### get_account_rec_list() ###
# Updated 2021-12-13
def get_account_rec_list(
enabled: str = 'enabled', # enabled, disabled, all
limit: int = 25,
offset: int = 0,
) -> list|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
data = {}
sql_enabled, data['enable'] = sql_enable_part(table_name='account', enabled=enabled) # Reasonably safe return str and bool
sql_limit = sql_limit_offset_part(limit=limit, offset=offset) # Reasonably safe return str
sql = f"""
SELECT `account`.id AS 'account_id', `account`.id_random AS 'account_id_random'
FROM `account` AS `account`
WHERE 1=1
{sql_enabled}
ORDER BY `account`.created_on DESC, `account`.updated_on DESC
{sql_limit};
"""
log.debug(sql)
if account_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
log.info('Got a list result')
account_rec_li = account_rec_li_result
else: # [] or False
log.info('No results or something went wrong')
account_rec_li = account_rec_li_result
log.debug(account_rec_li_result)
return account_rec_li
# ### END ### API Account Methods ### get_account_rec_list() ###
# ### BEGIN ### API Account Methods ### load_account_obj_membership_type() ###
# Working well as of 2021-06-11. Using as a template for other load objects.
def load_account_obj_membership_type(
account_id: int|str,
limit: int = 1000,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
enabled: str = 'enabled', # enabled, disabled, all
inc_account_cfg: bool = False, # Priority l1
inc_address: bool = False, # Under contact
inc_contact: bool = False,
inc_membership_cfg: bool = False,
inc_membership_group_list: bool = False, # List of groups for the account
inc_membership_person_group_list: bool = False, # List of members of a group
inc_membership_person: bool = False,
inc_membership_person_list: bool = False, # Priority l1
inc_membership_person_profile: bool = False,
inc_membership_person_type: bool = False,
inc_membership_type: bool = False,
inc_membership_type_list: bool = False,
inc_order: bool = False,
inc_order_cfg: bool = False,
inc_order_list: bool = False, # Priority l1
inc_order_line_list: bool = False, # Priority l2
inc_organization: bool = False,
inc_person: bool = False,
inc_product: bool = False,
inc_product_list: bool = False, # Priority l3
inc_user: bool = False,
inc_user_list: bool = False, # Priority l2
) -> Account_Base|dict|bool:
# log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if account_id := redis_lookup_id_random(record_id_random=account_id, table_name='account'): pass
else: return False
if account_rec := sql_select(table_name='v_account', record_id=account_id): pass
else: return False
#log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(account_rec)
try:
account_obj = Account_Base(**account_rec)
log.debug(account_obj)
except ValidationError as e:
log.error(e.json())
if inc_account_cfg:
if account_cfg_result := load_account_cfg_obj(
account_id = account_id,
model_as_dict = model_as_dict,
inc_membership_cfg = inc_membership_cfg,
):
account_obj.account_cfg = account_cfg_result
else: account_obj.account_cfg = None
if inc_membership_cfg:
if membership_cfg_result := load_membership_cfg_obj(
account_id = account_id,
model_as_dict = model_as_dict,
):
account_obj.membership_cfg = membership_cfg_result
else: account_obj.membership_cfg = None
# Updated 2021-06-18
if membership_type_rec_list_result := get_membership_type_rec_list(
for_obj_type = 'account',
for_obj_id = account_id,
limit = limit,
enabled = enabled,
):
membership_type_result_list = []
for membership_type_rec in membership_type_rec_list_result:
membership_type_result_list.append(
load_membership_type_obj(
membership_type_id = membership_type_rec.get('membership_type_id', None),
limit = limit,
enabled = enabled,
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
inc_address = inc_address,
inc_contact = inc_contact,
inc_membership_person_group_list = inc_membership_person_group_list, # per member
inc_membership_person_list = inc_membership_person_list,
inc_membership_person_profile = inc_membership_person_profile, # per member
inc_membership_person_type = inc_membership_person_type, # per member
inc_product_list = inc_product_list,
inc_person = inc_person,
inc_user = inc_user,
)
)
account_obj.membership_type_list = membership_type_result_list
else: account_obj.membership_type_list = []
if model_as_dict:
return account_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return account_obj
# ### END ### API Account Methods ### load_account_obj_membership_type() ###
# ### BEGIN ### API Account Methods ### load_account_obj_membership_group() ###
# Working well as of 2021-06-11. Using as a template for other load objects.
def load_account_obj_membership_group(
account_id: int|str,
limit: int = 1000,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
enabled: str = 'enabled', # enabled, disabled, all
inc_account_cfg: bool = False, # Priority l1
inc_address: bool = False, # Under contact
inc_contact: bool = False,
inc_membership_cfg: bool = False,
# inc_membership_group_list: bool = False, # List of groups for the account
inc_membership_person_group_list: bool = False, # List of members of a group
inc_membership_person: bool = False,
# inc_membership_person_list: bool = False, # Priority l1
inc_membership_person_profile: bool = False,
inc_membership_type: bool = False,
# inc_membership_type_list: bool = False,
# inc_order: bool = False,
# inc_order_cfg: bool = False,
# inc_order_list: bool = False, # Priority l1
# inc_order_line_list: bool = False, # Priority l2
inc_organization: bool = False,
inc_person: bool = False,
inc_product: bool = False,
inc_product_list: bool = False, # Priority l3
inc_user: bool = False,
) -> Account_Base|dict|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if account_id := redis_lookup_id_random(record_id_random=account_id, table_name='account'): pass
else: return False
if account_rec := sql_select(table_name='v_account', record_id=account_id): pass
else: return False
#log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(account_rec)
try:
account_obj = Account_Base(**account_rec)
log.debug(account_obj)
except ValidationError as e:
log.error(e.json())
if inc_account_cfg:
if account_cfg_dict := load_account_cfg_obj(
account_id = account_id,
model_as_dict = model_as_dict,
inc_membership_cfg = inc_membership_cfg,
):
account_obj.account_cfg = account_cfg_dict
else: account_obj.account_cfg = None
if inc_membership_cfg:
if membership_cfg_dict := load_membership_cfg_obj(
account_id = account_id,
model_as_dict = model_as_dict,
):
account_obj.membership_cfg = membership_cfg_dict
else: account_obj.membership_cfg = None
# Updated 2021-06-21
if inc_membership_group_list:
if membership_group_rec_list_result := get_membership_group_rec_list(
for_obj_type = 'account',
for_obj_id = account_id,
limit = limit,
enabled = enabled,
):
membership_group_result_list = []
for membership_group_rec in membership_group_rec_list_result:
membership_group_result_list.append(
load_membership_group_obj(
membership_group_id = membership_group_rec.get('membership_group_id', None),
limit = limit,
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
inc_address = inc_address,
inc_contact = inc_contact,
# inc_membership_person_list = inc_membership_person_list,
inc_membership_person_group_list = inc_membership_person_group_list,
inc_product_list = inc_product_list,
inc_person = inc_person,
inc_user = inc_user,
)
)
account_obj.membership_group_list = membership_group_result_list
else: account_obj.membership_group_list = []
# Updated 2021-06-21
if inc_membership_person_list:
if membership_person_rec_list_result := get_membership_person_rec_list(
for_obj_type = 'account',
for_obj_id = account_id,
limit = limit,
enabled = enabled,
):
membership_person_result_list = []
for membership_person_rec in membership_person_rec_list_result:
membership_person_result_list.append(
load_membership_person_obj(
membership_person_id = membership_person_rec.get('membership_person_id', None),
limit = limit,
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
inc_address = inc_address,
inc_contact = inc_contact,
inc_membership_group_list = inc_membership_group_list,
inc_membership_person_profile = inc_membership_person_profile,
inc_membership_type = inc_membership_type,
inc_person = inc_person,
inc_user = inc_user,
)
)
account_obj.membership_person_list = membership_person_result_list
else: account_obj.membership_person_list = []
# Updated 2021-06-18
if inc_membership_type_list:
if membership_type_rec_list_result := get_membership_type_rec_list(
for_obj_type = 'account',
for_obj_id = account_id,
limit = limit,
enabled = enabled,
):
membership_type_result_list = []
for membership_type_rec in membership_type_rec_list_result:
membership_type_result_list.append(
load_membership_type_obj(
membership_type_id = membership_type_rec.get('membership_type_id', None),
limit = limit,
enabled = enabled,
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
inc_address = inc_address,
inc_contact = inc_contact,
inc_membership_person_list = inc_membership_person_list,
# inc_membership_type_member_list = inc_membership_type_member_list,
inc_product_list = inc_product_list,
inc_person = inc_person,
inc_user = inc_user,
)
)
account_obj.membership_type_list = membership_type_result_list
else: account_obj.membership_type_list = []
if model_as_dict:
return account_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return account_obj
# ### END ### API Account Methods ### load_account_obj_membership_group() ###

View File

@@ -0,0 +1,99 @@
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_insert_or_update, sql_select, sql_update
from app.lib_general import log, logging, logger_reset
from app.models.common_field_schema import default_num_bytes
from app.models.activity_log_models import Activity_Log_Base
# ### BEGIN ### API Activity Log Methods ### load_activity_log_obj() ###
def load_activity_log_obj(
activity_log_id: int|str,
limit: int = 10000,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
) -> Activity_Log_Base|bool:
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if activity_log_id := redis_lookup_id_random(record_id_random=activity_log_id, table_name='activity_log'): pass
else: return False
if activity_log_rec := sql_select(table_name='v_activity_log', record_id=activity_log_id): pass
else: return False
try:
activity_log_obj = Activity_Log_Base(**activity_log_rec)
log.debug(activity_log_obj)
except ValidationError as e:
log.error(e.json())
if model_as_dict:
return activity_log_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return activity_log_obj
# ### END ### API Activity Log Methods ### load_activity_log_obj() ###
# ### BEGIN ### API Activity Log Methods ### get_activity_log_rec_list() ###
# Updated 2021-12-13
@logger_reset
def get_activity_log_rec_list(
account_id: str,
from_datetime: datetime.datetime = None,
to_datetime: datetime.datetime = None,
limit: int = 1000,
enabled: str = 'enabled', # enabled, disabled, all
) -> list|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if account_id := redis_lookup_id_random(record_id_random=account_id, table_name='account'): pass
else: return False
data = {}
data['account_id'] = account_id
sql_account_id = f'`activity_log`.account_id = :account_id'
# if enabled in ['enabled', 'disabled', 'all']:
# if enabled == 'enabled':
# data['enable'] = True
# sql_enabled = f'AND `activity_log`.enable = :enable'
# elif enabled == 'disabled':
# data['enable'] = False
# sql_enabled = f'AND `activity_log`.enable = :enable'
# elif enabled == 'all':
# sql_enabled = ''
sql_enabled = ''
if limit:
data['limit'] = limit
sql_limit = f'LIMIT :limit'
else:
sql_limit = ''
sql = f"""
SELECT `activity_log`.id AS 'activity_log_id', `activity_log`.id_random AS 'activity_log_id_random'
FROM `activity_log` AS `activity_log`
WHERE
{sql_account_id}
{sql_enabled}
ORDER BY `activity_log`.created_on DESC, `activity_log`.updated_on DESC
{sql_limit};
"""
if activity_log_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
activity_log_rec_li = activity_log_rec_li_result
else: # [] or False
activity_log_rec_li = activity_log_rec_li_result
log.debug(activity_log_rec_li_result)
return activity_log_rec_li
# ### END ### API Activity Log Methods ### get_activity_log_rec_list() ###

View File

@@ -0,0 +1,429 @@
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 get_account_id_w_for_type_id, redis_lookup_id_random, sql_enable_part, sql_insert, sql_limit_offset_part, sql_select, sql_update
from app.lib_general import log, logging, logger_reset
from app.models.address_models import Address_Base
from app.models.common_field_schema import default_num_bytes
# ### BEGIN ### API Address Methods ### load_address_obj() ###
@logger_reset
def load_address_obj(
address_id: int|str,
enabled: str = 'enabled', # enabled, disabled, all # Probably not needed for the address
limit: int = 100, # Probably not needed for the address
offset: int = 0,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
) -> Address_Base|dict|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if address_id := redis_lookup_id_random(record_id_random=address_id, table_name='address'): pass
else: return False
if address_rec := sql_select(table_name='v_address', record_id=address_id): pass
else: return False
try:
address_obj = Address_Base(**address_rec)
log.debug(address_obj)
except ValidationError as e:
log.error(e.json())
return False
if model_as_dict:
return address_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return address_obj
# ### END ### API Address Methods ### load_address_obj() ###
# ### BEGIN ### API Address Methods ### get_address_rec_list() ###
# Updated 2022-01-07
@logger_reset
def get_address_rec_list(
for_type: str, # 'account' is a special case
for_id: str,
enabled: str = 'enabled', # enabled, disabled, all
limit: int = 500,
offset: int = 0,
) -> list|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if for_id := redis_lookup_id_random(record_id_random=for_id, table_name=for_type): pass
else: return False
if for_type == 'account':
sql_for_type_id = f'`address`.account_id = :for_id'
else:
sql_for_type_id = f'`address`.for_type = :for_type AND `address`.for_id = :for_id'
data = {}
# data[f'{for_type}_id'] = for_id
data['for_type'] = for_type
data['for_id'] = for_id
# sql_obj_type_id = f'`address`.{for_type}_id = :{for_type}_id'
sql_enabled, data['enable'] = sql_enable_part(table_name='address', enabled=enabled) # Reasonably safe return str and bool
sql_limit = sql_limit_offset_part(limit=limit, offset=offset) # Reasonably safe return str
sql = f"""
SELECT `address`.id AS 'address_id', `address`.id_random AS 'address_id_random'
FROM `address` AS `address`
WHERE
{sql_for_type_id}
{sql_enabled}
ORDER BY `address`.created_on DESC, `address`.updated_on DESC
{sql_limit};
"""
if address_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
address_rec_li = address_rec_li_result
else: # [] or False
address_rec_li = address_rec_li_result
log.debug(address_rec_li_result)
return address_rec_li
# ### END ### API Address Methods ### get_address_rec_list() ###
# ### BEGIN ### API Address Methods ### create_update_address_obj_v4() ###
# NOTE: This will create or update an address.
# Rewrite and updated 2021-08-25
@logger_reset
def create_update_address_obj_v4(
address_dict_obj: Address_Base|dict,
address_id: int|str = None,
account_id: int|str = None,
for_type: str = None,
for_id: int|str = None,
create_sub_obj: bool = False,
fail_any: bool = False, # Fail if any thing goes wrong for sub objects
return_outline: bool = False,
) -> int|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
log.info('Checking requirements...')
if address_id:
log.info(f'Address ID passed. Update existing Address. Address ID: {address_id}')
if address_id := redis_lookup_id_random(record_id_random=address_id, table_name='address'): pass
else:
log.error('Address ID passed but is invalid. Failed requirement.')
return False
if account_id := redis_lookup_id_random(record_id_random=account_id, table_name='account'): pass
else:
log.error('Missing or invalid Account ID passed. Not required. Ignoring.')
log.info(f'Account ID: {account_id}')
log.info('Attempting to get Account ID from related object.')
if account_id := get_account_id_w_for_type_id(for_type='address', for_id=address_id): pass
else:
log.error('Unable to get Account ID from related object.')
False
if for_id := redis_lookup_id_random(record_id_random=for_id, table_name=for_type): pass
else:
log.error('Missing or invalid For Type and For ID ID passed. Not required. Ignoring.')
log.info(f'For Type: {for_type} and For ID: {for_id}')
else:
log.info('No Address ID passed. Create new Address. Required: Account ID, For Type, For ID')
if for_id := redis_lookup_id_random(record_id_random=for_id, table_name=for_type): pass
else:
log.error('Missing or invalid For Type and For ID ID passed. Failed requirement.')
log.info(f'For Type: {for_type} and For ID: {for_id}')
return False
if account_id := redis_lookup_id_random(record_id_random=account_id, table_name='account'): pass
else:
log.error('Missing or invalid Account ID passed. Failed requirement.')
log.info(f'Account ID: {account_id}')
if for_type and for_id:
log.info('Attempting to get Account ID from related object.')
if account_id := get_account_id_w_for_type_id(for_type=for_type, for_id=for_id): pass
else:
log.error('Unable to get Account ID from related object.')
False
else:
return False
log.debug(type(address_dict_obj))
if isinstance(address_dict_obj, dict):
address_dict = address_dict_obj
if address_id:
address_dict['address_id'] = address_id
if account_id:
address_dict['account_id'] = account_id
if for_type:
address_dict['for_type'] = for_type
if for_id:
address_dict['for_id'] = for_id
try:
address_obj = Address_Base(**address_dict)
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(address_obj)
except ValidationError as e:
log.error(e.json())
return False
else:
address_obj = address_dict_obj
if address_id:
# NOTE: Can't update the ID alias if it was never set.
address_obj.id = address_id
if account_id:
address_obj.account_id = account_id
if for_type:
address_obj.for_type = for_type
if for_id:
address_obj.for_id = for_id
address_dict = address_obj.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'created_on', 'updated_on'})
if address_id:
if address_dict_up_result := sql_update(data=address_dict, table_name='address', rm_id_random=True): pass
else:
log.warning(f'Address not updated. Address ID: {address_id}')
log.debug(address_dict_up_result)
return False
log.debug(address_dict_up_result)
else:
if address_dict_in_result := sql_insert(data=address_dict, table_name='address', rm_id_random=True, id_random_length=8): pass
else:
log.warning(f'Address not created.')
log.debug(address_dict_in_result)
return False
log.debug(address_dict_in_result)
address_id = address_dict_in_result
address_outline = {}
address_outline['address_id'] = address_id
# Should this outline include for_type and for_id? Probably later.
if return_outline:
log.debug(f'Returning the Address Outline: {address_outline}')
return address_outline
else:
log.debug(f'Returning the Address ID: {address_id}')
return address_id
# ### END ### API Address Methods ### create_update_address_obj_v4() ###
# ### BEGIN ### API Address Methods ### create_address_obj() ###
# Updated 2022-01-06
@logger_reset
def create_address_obj(
account_id: int|str,
address_dict_obj: Address_Base,
for_type: str = None,
for_id: int|str = None,
fail_any: bool = False, # Fail if any thing goes wrong for sub objects
) -> int|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
# ### SECTION ### Secondary data validation
# NOTE: Remove at future date. Is this check needed if we trust that the ID is checked ahead of time?
if account_id := redis_lookup_id_random(record_id_random=account_id, table_name='account'): pass
else: return False
if for_id := redis_lookup_id_random(record_id_random=for_id, table_name=for_type): pass
else: return False
log.info('Create dictionary or Pydantic object')
log.debug(type(address_dict_obj))
log.debug(address_dict_obj)
if isinstance(address_dict_obj, dict):
address_dict = address_dict_obj
try:
address_obj = Contact_Base(**address_dict)
log.debug(address_obj)
except ValidationError as e:
log.error(e.json())
return False
else:
address_obj = address_dict_obj
address_obj.account_id = account_id
address_dict = address_obj.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'created_on', 'updated_on'})
log.debug(address_dict)
# ### SECTION ### Process data
# Look for an account_id in the address_obj
if account_id: pass
elif account_id := address_obj.account_id: pass
address_obj.account_id = account_id
address_dict['account_id'] = account_id
address_obj.for_type = for_type
address_obj.for_id = for_id
address_dict['for_type'] = for_type
address_dict['for_id'] = for_id
if address_obj.id:
log.warning(f'There should not be an Address ID: {address_obj.id}')
return False
else:
log.info(f'Should there be a check for an existing address with? For Type: {for_type}; For ID: {for_id}')
if get_address_rec_result := get_address_rec_list(for_type=for_type, for_id=for_id):
log.warning(f'One or more addresses were found with: For Type: {for_type}; For ID: {for_id}')
return False
else: log.info(f'No existing address found with: For Type: {for_type}; For ID: {for_id}')
if address_dict_in_result := sql_insert(
data = address_dict,
table_name = 'address',
rm_id_random = True,
id_random_length = default_num_bytes
): pass
else:
return False
log.debug(address_dict_in_result)
address_id = address_dict_in_result
log.info(f'Returning the new address_id: {address_id}')
return address_id
# ### END ### API Address Methods ### create_address_obj() ###
# ### BEGIN ### API Address Methods ### update_address_obj() ###
# Updated 2022-01-06
@logger_reset
def update_address_obj(
address_id: int|str, # Ideally the int ID should be passed. This allows for updating of the id_random value.
address_dict_obj: Address_Base,
for_type: str = None,
for_id: int|str = None,
create_sub_obj: bool = False,
fail_any: bool = False, # Fail if any thing goes wrong for sub objects
) -> bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
# ### SECTION ### Secondary data validation
# NOTE: Remove at future date. Is this check needed if we trust that the ID is checked ahead of time?
if address_id := redis_lookup_id_random(record_id_random=address_id, table_name='address'): pass
else: return False
log.info('Create dictionary or Pydantic object')
log.debug(type(address_dict_obj))
if isinstance(address_dict_obj, dict):
address_dict = address_dict_obj
try:
address_obj = Contact_Base(**address_dict)
log.debug(address_obj)
except ValidationError as e:
log.error(e.json())
return False
else:
address_obj = address_dict_obj
address_dict = address_obj.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'created_on', 'updated_on'})
# ### SECTION ### Process data
address_obj.id = address_id # Is this needed?
address_dict['id'] = address_id
# Look for an account_id in the address_obj
if account_id := address_obj.account_id: pass
elif account_id := get_account_id_w_for_type_id(for_type='address', for_id=address_id): pass
# If for_type and for_id are passed then the address needs to be updated
if for_type and for_id:
address_obj.for_type = for_type
address_obj.for_id = for_id
address_dict['for_type'] = for_type
address_dict['for_id'] = for_id
log.debug(address_dict_obj)
# log.debug(address_dict_obj.dict(by_alias=True, exclude_unset=True))
log.debug(address_dict_obj.dict(by_alias=False, exclude_unset=True))
# log.debug(address_dict_obj.dict(by_alias=False, exclude_unset=False))
# address_dict = address_dict_obj.dict(by_alias=False, exclude_unset=True)
if address_dict_up_result := sql_update(
data = address_dict,
table_name = 'address',
rm_id_random = True
): pass
else:
log.warning(f'Address not updated.')
log.debug(address_dict_up_result)
return False
log.debug(address_dict_up_result)
return True
# ### END ### API Address Methods ### update_address_obj() ###
# ### BEGIN ### API Address Methods ### create_update_address_obj() ###
@logger_reset
def create_update_address_obj(
address_id: int|str|None, # Ideally the int ID should be passed. This allows for updating of the id_random value.
address_obj: Address_Base,
process_address: bool = False,
process_organization: bool = False,
) -> bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if address_id:
if address_id := redis_lookup_id_random(record_id_random=address_id, table_name='address'): pass
else: return False
address_obj.id = address_id
else:
# Insert record now and update later
address_dict_in = address_obj.dict(by_alias=False, exclude_unset=True)
log.debug(address_dict_in)
address_in_result = sql_insert(
data = address_dict_in,
table_name = 'address',
rm_id_random = True,
id_random_length = default_num_bytes,
)
log.debug(address_in_result)
if isinstance(address_in_result, bool) and address_in_result is True:
return address_in_result
elif isinstance(address_in_result, int):
address_id = address_in_result
address_obj.id = address_id
else:
return False # This should not happen.
# Process address data
address_dict_up = address_obj.dict(by_alias=False, exclude_unset=True)
log.debug(address_dict_up)
# Update record
address_up_result = sql_update(
data = address_dict_up,
table_name = 'address',
rm_id_random = True,
)
log.debug(address_up_result)
if isinstance(address_up_result, bool) and address_up_result is True:
return address_id
elif isinstance(address_up_result, bool) and address_up_result is False:
return False
elif isinstance(address_up_result, int):
return address_up_result
else:
return False
# ### END ### API Address Methods ### create_update_address_obj() ###

View File

@@ -0,0 +1,8 @@
{
"folders": [
{
"path": "../.."
}
],
"settings": {}
}

View File

@@ -0,0 +1,109 @@
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_select
from app.lib_general import log, logging
from app.models.archive_content_models import Archive_Content_Base
# ### BEGIN ### API Archive Content Methods ### load_archive_content_obj() ###
def load_archive_content_obj(
archive_content_id: int|str,
# limit: int = 1000,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
# enabled: str = 'enabled', # enabled, disabled, all
# inc_archive_content_content_list: bool = False,
) -> Archive_Content_Base|dict|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if archive_content_id := redis_lookup_id_random(record_id_random=archive_content_id, table_name='archive_content'): pass
else: return False
if archive_content_rec := sql_select(
table_name='v_archive_content',
record_id=archive_content_id,
): pass
else: return False
log.debug(archive_content_rec)
try:
archive_content_obj = Archive_Content_Base(**archive_content_rec)
log.debug(archive_content_obj)
except ValidationError as e:
log.error(e.json())
if model_as_dict:
return archive_content_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return archive_content_obj
# ### END ### API Archive Content Methods ### load_archive_content_obj() ###
# ### BEGIN ### API Archive Content Methods ### get_archive_content_rec_list() ###
def get_archive_content_rec_list(
for_obj_type: str,
for_obj_id: str,
limit: int = 1000,
enabled: str = 'enabled', # enabled, disabled, all
sort_by_str: str = None,
sort_by: str = None,
sort_by_desc: bool = False,
) -> list|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if for_obj_id := redis_lookup_id_random(record_id_random=for_obj_id, table_name=for_obj_type): pass
else: return False
data = {}
data[f'{for_obj_type}_id'] = for_obj_id
# data['for_obj_type'] = for_obj_type
sql_obj_type_id = f'`tbl`.{for_obj_type}_id = :{for_obj_type}_id'
if enabled in ['enabled', 'disabled', 'all']:
if enabled == 'enabled':
data['enable'] = True
sql_enabled = f'AND `tbl`.enable = :enable'
elif enabled == 'disabled':
data['enable'] = False
sql_enabled = f'AND `tbl`.enable = :enable'
elif enabled == 'all':
sql_enabled = ''
if limit:
data['limit'] = limit
sql_limit = f'LIMIT :limit'
else:
sql_limit = ''
if sort_by:
sql_order_by = f'ORDER BY `tbl`.{sort_by}'
if sort_by_desc:
sql_order_by = f'{sql_order_by} {DESC}'
else:
sql_order_by = 'ORDER BY `tbl`.created_on DESC, `tbl`.updated_on DESC'
sql = f"""
SELECT `tbl`.id AS 'archive_content_id', `tbl`.id_random AS 'archive_content_id_random'
FROM `archive_content` AS `tbl`
WHERE
{sql_obj_type_id}
{sql_enabled}
{sql_order_by}
{sql_limit};
"""
if archive_content_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
archive_content_rec_li = archive_content_rec_li_result
else:
archive_content_rec_li = []
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(archive_content_rec_li_result)
return archive_content_rec_li
# ### END ### API Archive Content Methods ### get_archive_content_rec_list() ###

View File

@@ -0,0 +1,128 @@
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_select
from app.lib_general import log, logging
from app.methods.archive_content_methods import get_archive_content_rec_list, load_archive_content_obj
from app.models.archive_models import Archive_Base
# ### BEGIN ### API Archive Methods ### load_archive_obj() ###
def load_archive_obj(
archive_id: int|str,
limit: int = 1000,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
enabled: str = 'enabled', # enabled, disabled, all
hidden: str = 'not_hidden', # hidden, not_hidden, all
inc_archive_content_list: bool = False,
) -> Archive_Base|dict|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if archive_id := redis_lookup_id_random(record_id_random=archive_id, table_name='archive'): pass
else: return False
if archive_rec := sql_select(
table_name='v_archive',
record_id=archive_id,
): pass
else: return False
log.debug(archive_rec)
try:
archive_obj = Archive_Base(**archive_rec)
log.debug(archive_obj)
except ValidationError as e:
log.error(e.json())
# Updated 2021-06-18
if inc_archive_content_list:
sort_by = archive_rec.get('sort_by')
sort_by_desc = archive_rec.get('sort_by_desc')
if archive_content_rec_list_result := get_archive_content_rec_list(
for_obj_type = 'archive',
for_obj_id = archive_id,
limit = limit,
enabled = enabled,
sort_by = sort_by,
sort_by_desc = sort_by_desc,
):
archive_content_result_list = []
for archive_content_rec in archive_content_rec_list_result:
archive_content_result_list.append(
load_archive_content_obj(
archive_content_id = archive_content_rec.get('archive_content_id', None),
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
)
)
archive_obj.archive_content_list = archive_content_result_list
else: archive_obj.archive_content_list = []
if model_as_dict:
return archive_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return archive_obj
# ### END ### API Archive Methods ### load_archive_obj() ###
# ### BEGIN ### API Archive Methods ### get_archive_rec_list() ###
def get_archive_rec_list(
for_obj_type: str,
for_obj_id: str,
limit: int = 1000,
enabled: str = 'enabled', # enabled, disabled, all
) -> list|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if for_obj_id := redis_lookup_id_random(record_id_random=for_obj_id, table_name=for_obj_type): pass
else: return False
data = {}
data[f'{for_obj_type}_id'] = for_obj_id
# data['for_obj_type'] = for_obj_type
sql_obj_type_id = f'`tbl`.{for_obj_type}_id = :{for_obj_type}_id'
if enabled in ['enabled', 'disabled', 'all']:
if enabled == 'enabled':
data['enable'] = True
sql_enabled = f'AND `tbl`.enable = :enable'
elif enabled == 'disabled':
data['enable'] = False
sql_enabled = f'AND `tbl`.enable = :enable'
elif enabled == 'all':
sql_enabled = ''
if limit:
data['limit'] = limit
sql_limit = f'LIMIT :limit'
else:
sql_limit = ''
sql = f"""
SELECT `tbl`.id AS 'archive_id', `tbl`.id_random AS 'archive_id_random'
FROM `archive` AS `tbl`
WHERE
{sql_obj_type_id}
{sql_enabled}
ORDER BY `tbl`.created_on DESC, `tbl`.updated_on DESC
{sql_limit};
"""
log.debug(sql)
if archive_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
archive_rec_li = archive_rec_li_result
else: # [] or False
archive_rec_li = archive_rec_li_result
log.debug(archive_rec_li_result)
return archive_rec_li
# ### END ### API Archive Methods ### get_archive_rec_list() ###

View File

@@ -0,0 +1,113 @@
import datetime, json, pprint, pytz, random, requests, secrets, string, time
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, logger_reset, secure_hash_string, verify_secure_hash_string
# ### BEGIN ### API Client: IDAA Methods ### refresh_person_group() ###
# Updated 2022-03-16
@logger_reset
def refresh_person_group(
person_id: int = None,
):
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
data = {}
sql_person_id = ''
if person_id:
data['person_id'] = person_id
sql_person_id = f'AND `person`.id = :person_id'
log.info(f'Updating membership group for Person ID: {person_id}')
else:
log.info('Updating membership group for all person records.')
sql = f"""
UPDATE person
INNER JOIN membership_person ON person.id = membership_person.person_id
SET person.enable = 1, person.group = 'pending_member'
WHERE person.account_id = 13
AND membership_person.end_on >= NOW()
AND person.status = 'pending'
{sql_person_id};
"""
if person_update_result := sql_update(sql=sql, data=data, log_lvl=logging.WARNING):
log.warning(f'Finished the SQL UPDATE of person table record(s) for pending members')
elif person_update_result is None:
log.warning(f'Finished the SQL UPDATE of person table record(s) for pending members, but no records needed to be updated')
else:
log.warning(f'The SQL UPDATE of person table record(s) failed for pending members')
log.debug(person_update_result)
sql = f"""
UPDATE person
INNER JOIN membership_person ON person.id = membership_person.person_id
SET person.enable = 1, person.group = 'current_member'
WHERE person.account_id = 13
AND membership_person.end_on >= NOW()
AND (person.status IS NULL OR person.status = '' OR person.status = 'approved' OR person.status = 'unknown')
{sql_person_id};
"""
if person_update_result := sql_update(sql=sql, data=data, log_lvl=logging.WARNING):
log.warning(f'Finished the SQL UPDATE of person table record(s) for current members')
elif person_update_result is None:
log.warning(f'Finished the SQL UPDATE of person table record(s) for current members, but no records needed to be updated')
else:
log.warning(f'The SQL UPDATE of person table record(s) failed for current members')
log.debug(person_update_result)
sql = f"""
UPDATE person
INNER JOIN membership_person ON person.id = membership_person.person_id
SET person.group = 'inactive_member'
WHERE person.account_id = 13
AND membership_person.end_on < NOW()
AND membership_person.end_on >= DATE(NOW() - INTERVAL 4 WEEK)
{sql_person_id};
"""
if person_update_result := sql_update(sql=sql, data=data, log_lvl=logging.WARNING):
log.warning(f'Finished the SQL UPDATE of person table record(s) for inactive members')
elif person_update_result is None:
log.warning(f'Finished the SQL UPDATE of person table record(s) for inactive members, but no records needed to be updated')
else:
log.warning(f'The SQL UPDATE of person table record(s) failed for inactive members')
log.debug(person_update_result)
sql = f"""
UPDATE person
INNER JOIN membership_person ON person.id = membership_person.person_id
SET person.group = 'suspended_member'
WHERE person.account_id = 13
AND membership_person.end_on < DATE(NOW() - INTERVAL 4 WEEK)
{sql_person_id};
"""
if person_update_result := sql_update(sql=sql, data=data, log_lvl=logging.WARNING):
log.warning(f'Finished the SQL UPDATE of person table record(s) for suspended members')
elif person_update_result is None:
log.warning(f'Finished the SQL UPDATE of person table record(s) for suspended members, but no records needed to be updated')
else:
log.warning(f'The SQL UPDATE of person table record(s) failed for suspended members')
log.debug(person_update_result)
sql = f"""
UPDATE person
LEFT JOIN membership_person ON person.id = membership_person.person_id
SET person.group = 'not_member'
WHERE person.account_id = 13
AND membership_person.id IS NULL
{sql_person_id};
"""
if person_update_result := sql_update(sql=sql, data=data, log_lvl=logging.WARNING):
log.warning(f'Finished the SQL UPDATE of person table record(s) for not members')
elif person_update_result is None:
log.warning(f'Finished the SQL UPDATE of person table record(s) for not members, but no records needed to be updated')
else:
log.warning(f'The SQL UPDATE of person table record(s) failed for not members')
log.debug(person_update_result)
return True
# ### END ### API Client: IDAA Methods ### refresh_person_group() ###

View File

@@ -0,0 +1,121 @@
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.common_field_schema import default_num_bytes
from app.models.cont_edu_cert_models import Cont_Edu_Cert_Base
# ### BEGIN ### API Cont Edu Cert Methods ### load_cont_edu_cert_obj() ###
def load_cont_edu_cert_obj(
cont_edu_cert_id: int|str,
limit: int = 1000,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
enabled: str = 'enabled', # enabled, disabled, all
inc_cont_edu_cert_person_list: bool = False,
) -> Cont_Edu_Cert_Base|dict|bool:
# log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if cont_edu_cert_id := redis_lookup_id_random(record_id_random=cont_edu_cert_id, table_name='cont_edu_cert'): pass
else: return False
if cont_edu_cert_rec := sql_select(table_name='v_cont_edu_cert', record_id=cont_edu_cert_id): pass
else: return False
try:
cont_edu_cert_obj = Cont_Edu_Cert_Base(**cont_edu_cert_rec)
log.debug(cont_edu_cert_obj)
except ValidationError as e:
log.error(e.json())
return False
# Updated 2021-07-28
if inc_cont_edu_cert_person_list:
from app.methods.cont_edu_cert_person_methods import get_cont_edu_cert_person_rec_list, load_cont_edu_cert_person_obj
if cont_edu_cert_person_rec_list_result := get_cont_edu_cert_person_rec_list(
cont_edu_cert_id = cont_edu_cert_id,
limit = limit,
enabled = enabled,
):
cont_edu_cert_person_result_list = []
for cont_edu_cert_person_rec in cont_edu_cert_person_rec_list_result:
if load_cont_edu_cert_person_result := load_cont_edu_cert_person_obj(
cont_edu_cert_person_id = cont_edu_cert_person_rec.get('cont_edu_cert_person_id', None),
limit = limit,
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
enabled = enabled,
# inc_cont_edu_cert = inc_cont_edu_cert,
):
cont_edu_cert_person_result_list.append(load_cont_edu_cert_person_result)
else: cont_edu_cert_person_result_list.append(None)
cont_edu_cert_obj.cont_edu_cert_person_list = cont_edu_cert_person_result_list
else: cont_edu_cert_obj.cont_edu_cert_person_list = []
if model_as_dict:
return cont_edu_cert_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return cont_edu_cert_obj
# ### END ### API Cont Edu Cert Methods ### load_cont_edu_cert_obj() ###
# ### BEGIN ### API Cont Edu Cert Methods ### get_cont_edu_cert_rec_list() ###
def get_cont_edu_cert_rec_list(
account_id: str,
limit: int = 1000,
enabled: str = 'enabled', # enabled, disabled, all
) -> list|bool:
# log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if account_id := redis_lookup_id_random(record_id_random=account_id, table_name='account'): pass
else: return False
data = {}
data['account_id'] = account_id
sql_account_id = f'`tbl`.account_id = :account_id'
if enabled in ['enabled', 'disabled', 'all']:
if enabled == 'enabled':
data['enable'] = True
sql_enabled = f'AND `tbl`.enable = :enable'
elif enabled == 'disabled':
data['enable'] = False
sql_enabled = f'AND `tbl`.enable = :enable'
elif enabled == 'all':
sql_enabled = ''
# sql_enabled = ''
if limit:
data['limit'] = limit
sql_limit = f'LIMIT :limit'
else:
sql_limit = ''
sql = f"""
SELECT `tbl`.id AS 'cont_edu_cert_id', `tbl`.id_random AS 'cont_edu_cert_id_random'
FROM `cont_edu_cert` AS `tbl`
WHERE
{sql_account_id}
{sql_enabled}
ORDER BY `tbl`.created_on DESC, `tbl`.updated_on DESC
{sql_limit};
"""
if cont_edu_cert_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
cont_edu_cert_rec_li = cont_edu_cert_rec_li_result
else:
cont_edu_cert_rec_li = []
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(cont_edu_cert_rec_li_result)
return cont_edu_cert_rec_li
# ### END ### API Cont Edu Cert Methods ### get_cont_edu_cert_rec_list() ###

View File

@@ -0,0 +1,147 @@
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.cont_edu_cert_methods import load_cont_edu_cert_obj
from app.models.common_field_schema import default_num_bytes
from app.models.cont_edu_cert_person_models import Cont_Edu_Cert_Person_Base
# ### BEGIN ### API Cont Edu Cert Person Methods ### load_cont_edu_cert_person_obj() ###
def load_cont_edu_cert_person_obj(
cont_edu_cert_person_id: int|str,
limit: int = 1000,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
enabled: str = 'enabled', # enabled, disabled, all
inc_cont_edu_cert: bool = False,
) -> Cont_Edu_Cert_Person_Base|dict|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if cont_edu_cert_person_id := redis_lookup_id_random(record_id_random=cont_edu_cert_person_id, table_name='cont_edu_cert_person'): pass
else: return False
if cont_edu_cert_person_rec := sql_select(table_name='v_cont_edu_cert_person', record_id=cont_edu_cert_person_id): pass
else: return False
try:
cont_edu_cert_person_obj = Cont_Edu_Cert_Person_Base(**cont_edu_cert_person_rec)
log.debug(cont_edu_cert_person_obj)
except ValidationError as e:
log.error(e.json())
return False
if inc_cont_edu_cert:
#log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
cont_edu_cert_id = cont_edu_cert_person_rec.get('cont_edu_cert_id', None)
log.debug(cont_edu_cert_id)
if cont_edu_cert_result := load_cont_edu_cert_obj(
cont_edu_cert_id = cont_edu_cert_id,
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
):
cont_edu_cert_person_obj.cont_edu_cert = cont_edu_cert_result
else: cont_edu_cert_person_obj.cont_edu_cert = None
if model_as_dict:
return cont_edu_cert_person_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return cont_edu_cert_person_obj
# ### END ### API Cont Edu Cert Person Methods ### load_cont_edu_cert_person_obj() ###
# ### BEGIN ### API Cont Edu Cert Person Methods ### get_cont_edu_cert_person_rec_list() ###
# Updated 2021-12-13
def get_cont_edu_cert_person_rec_list(
account_id: str = None,
person_id: str = None,
cont_edu_cert_id: str = None,
given_name: str = None,
family_name: str = None,
from_datetime: Optional[datetime.datetime] = None, # based on created_on
to_datetime: Optional[datetime.datetime] = None, # based on created_on
limit: int = 1000,
enabled: str = 'enabled', # enabled, disabled, all
) -> None|list|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if cont_edu_cert_id:
if cont_edu_cert_id := redis_lookup_id_random(record_id_random=cont_edu_cert_id, table_name='cont_edu_cert'): pass
else: return False
data = {}
if account_id:
data['account_id'] = account_id
sql_where_id = f'`tbl`.account_id = :account_id'
elif person_id:
data['person_id'] = person_id
sql_where_id = f'`tbl`.person_id = :person_id'
elif cont_edu_cert_id:
data['cont_edu_cert_id'] = cont_edu_cert_id
sql_where_id = f'`tbl`.cont_edu_cert_id = :cont_edu_cert_id'
else: return False
if enabled in ['enabled', 'disabled', 'all']:
if enabled == 'enabled':
data['enable'] = True
sql_enabled = f'AND `tbl`.enable = :enable'
elif enabled == 'disabled':
data['enable'] = False
sql_enabled = f'AND `tbl`.enable = :enable'
elif enabled == 'all':
sql_enabled = ''
# sql_enabled = ''
if from_datetime and to_datetime:
data['from_datetime'] = from_datetime
data['to_datetime'] = to_datetime
sql_from_to_datetime = f'AND `tbl`.created_on >= :from_datetime AND `tbl`.created_on <= :to_datetime'
elif from_datetime:
data['from_datetime'] = from_datetime
sql_from_to_datetime = f'AND `tbl`.created_on >= :from_datetime'
elif to_datetime:
data['to_datetime'] = to_datetime
sql_from_to_datetime = f'AND `tbl`.created_on <= :to_datetime'
else:
sql_from_to_datetime = ''
if limit:
data['limit'] = limit
sql_limit = f'LIMIT :limit'
else:
sql_limit = ''
sql = f"""
SELECT `tbl`.id AS 'cont_edu_cert_person_id', `tbl`.id_random AS 'cont_edu_cert_person_id_random'
FROM `cont_edu_cert_person` AS `tbl`
WHERE
{sql_where_id}
{sql_enabled}
{sql_from_to_datetime}
ORDER BY `tbl`.created_on DESC, `tbl`.updated_on DESC
{sql_limit};
"""
log.debug(sql)
if cont_edu_cert_person_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
log.info('Got a list result')
cont_edu_cert_person_rec_li = cont_edu_cert_person_rec_li_result
else: # [] or False
log.info('No results or something went wrong')
cont_edu_cert_person_rec_li = cont_edu_cert_person_rec_li_result
log.debug(cont_edu_cert_person_rec_li_result)
return cont_edu_cert_person_rec_li
# ### END ### API Cont Edu Cert Person Methods ### get_cont_edu_cert_person_rec_list() ###

View File

@@ -0,0 +1,608 @@
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 get_account_id_w_for_type_id, redis_lookup_id_random, sql_enable_part, sql_insert, sql_limit_offset_part, sql_select, sql_update
from app.lib_general import log, logging, logger_reset
from app.methods.address_methods import create_address_obj, create_update_address_obj, create_update_address_obj_v4, update_address_obj
from app.models.common_field_schema import default_num_bytes
from app.models.contact_models import Contact_Base
# ### BEGIN ### API Contact Methods ### load_contact_obj() ###
@logger_reset
def load_contact_obj(
contact_id: int|str,
enabled: str = 'enabled', # enabled, disabled, all
limit: int = 100,
offset: int = 0,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
inc_address: bool = False
) -> Contact_Base|dict|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if contact_id := redis_lookup_id_random(record_id_random=contact_id, table_name='contact'): pass
else: return False
if contact_rec := sql_select(table_name='v_contact', record_id=contact_id): pass
else: return False
try:
contact_obj = Contact_Base(**contact_rec)
log.debug(contact_obj)
except ValidationError as e:
log.error(e.json())
return False
if inc_address:
log.info('Need to include address data...')
# NOTE: The address_id field needs to be removed from the contact table.
# NOTE: This is not ideal and the view needs to be updated when possible and anything currently pointing to the original contact.address_id needs to be fixed.
if address_id := contact_rec.get('address_id', None): pass # WARNING
elif address_id := contact_rec.get('linked_address_id', None): pass # WARNING
else: pass # WARNING
# address_id = contact_rec.get('address_id', None)
log.debug(address_id)
from app.methods.address_methods import load_address_obj
if address_result := load_address_obj(
address_id = address_id,
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
):
contact_obj.address = address_result
else: contact_obj.address = {} # None
if model_as_dict:
return contact_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return contact_obj
# ### END ### API Contact Methods ### load_contact_obj() ###
# ### BEGIN ### API Contact Methods ### get_contact_rec_list() ###
# Updated 2022-01-07
@logger_reset
def get_contact_rec_list(
for_type: str, # 'account' is a special case
for_id: str,
enabled: str = 'enabled', # enabled, disabled, all
limit: int = 500,
offset: int = 0,
) -> list|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if for_id := redis_lookup_id_random(record_id_random=for_id, table_name=for_type): pass
else: return False
if for_type == 'account':
sql_for_type_id = f'`contact`.account_id = :for_id'
else:
sql_for_type_id = f'`contact`.for_type = :for_type AND `contact`.for_id = :for_id'
data = {}
data['for_type'] = for_type
data['for_id'] = for_id
sql_enabled, data['enable'] = sql_enable_part(table_name='contact', enabled=enabled) # Reasonably safe return str and bool
sql_limit = sql_limit_offset_part(limit=limit, offset=offset) # Reasonably safe return str
sql = f"""
SELECT `contact`.id AS 'contact_id', `contact`.id_random AS 'contact_id_random'
FROM `contact` AS `contact`
WHERE
{sql_for_type_id}
{sql_enabled}
ORDER BY `contact`.created_on DESC, `contact`.updated_on DESC
{sql_limit};
"""
log.debug(sql)
if contact_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
contact_rec_li = contact_rec_li_result
else: # [] or False
contact_rec_li = contact_rec_li_result
log.debug(contact_rec_li_result)
return contact_rec_li
# ### END ### API Contact Methods ### get_contact_rec_list() ###
# ### BEGIN ### API Contact Methods ### get_account_id_w_contact_id() ###
# Updated 2021-08-24
@logger_reset
def get_account_id_w_contact_id(
contact_id: int|str,
) -> bool|int|None:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if contact_id := redis_lookup_id_random(record_id_random=contact_id, table_name='contact'): pass
else: return False
data = {}
data['contact_id'] = contact_id
sql = f"""
SELECT `contact`.id AS 'contact_id', `contact`.id_random AS 'contact_id_random', `contact`.account_id AS account_id
FROM `contact` AS `contact`
WHERE `contact`.id = :contact_id
LIMIT 1;
"""
if contact_data_result := sql_select(data=data, sql=sql):
log.debug(contact_data_result)
if account_id := contact_data_result.get('account_id', None): return account_id
else: return False
else: return None
# ### END ### API Contact Methods ### get_account_id_w_contact_id() ###
# ### BEGIN ### API Contact Methods ### create_update_contact_obj_v4() ###
# NOTE: This will create or update a contact.
# Rewrite and updated 2021-08-25
@logger_reset
def create_update_contact_obj_v4(
contact_dict_obj: Contact_Base|dict,
contact_id: int|str = None,
account_id: int|str = None,
for_type: str = None,
for_id: int|str = None,
create_sub_obj: bool = False,
fail_any: bool = False, # Fail if any thing goes wrong for sub objects
return_outline: bool = False,
) -> int|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
log.info('Checking requirements...')
if contact_id:
log.info(f'Contact ID passed. Update existing Contact. Contact ID: {contact_id}')
if contact_id := redis_lookup_id_random(record_id_random=contact_id, table_name='contact'): pass
else:
log.error('Contact ID passed but is invalid. Failed requirement.')
return False
if account_id := redis_lookup_id_random(record_id_random=account_id, table_name='account'): pass
else:
log.error('Missing or invalid Account ID passed. Not required. Ignoring.')
log.info(f'Account ID: {account_id}')
log.info('Attempting to get Account ID from related object.')
if account_id := get_account_id_w_for_type_id(for_type='contact', for_id=contact_id): pass
else:
log.error('Unable to get Account ID from related object.')
False
if for_id := redis_lookup_id_random(record_id_random=for_id, table_name=for_type): pass
else:
log.error('Missing or invalid For Type and For ID passed. Not required. Ignoring.')
log.info(f'For Type: {for_type} and For ID: {for_id}')
else:
log.info('No Contact ID passed. Create new Contact. Required: Account ID, For Type, For ID')
if for_id := redis_lookup_id_random(record_id_random=for_id, table_name=for_type): pass
else:
log.error('Missing or invalid For Type and For ID passed. Failed requirement.')
log.info(f'For Type: {for_type} and For ID: {for_id}')
return False
if account_id := redis_lookup_id_random(record_id_random=account_id, table_name='account'): pass
else:
log.error('Missing or invalid Account ID passed. Failed requirement.')
log.info(f'Account ID: {account_id}')
if for_type and for_id:
log.info('Attempting to get Account ID from related object.')
if account_id := get_account_id_w_for_type_id(for_type=for_type, for_id=for_id): pass
else:
log.error('Unable to get Account ID from related object.')
False
else:
return False
log.debug(type(contact_dict_obj))
if isinstance(contact_dict_obj, dict):
contact_dict = contact_dict_obj
if contact_id:
contact_dict['contact_id'] = contact_id
if account_id:
contact_dict['account_id'] = account_id
if for_type:
contact_dict['for_type'] = for_type
if for_id:
contact_dict['for_id'] = for_id
try:
contact_obj = Contact_Base(**contact_dict)
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(contact_obj)
except ValidationError as e:
log.error(e.json())
return False
else:
contact_obj = contact_dict_obj
if contact_id:
# NOTE: Can't update the ID alias if it was never set.
contact_obj.id = contact_id
if account_id:
contact_obj.account_id = account_id
if for_type:
contact_obj.for_type = for_type
if for_id:
contact_obj.for_id = for_id
contact_dict = contact_obj.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'address', 'created_on', 'updated_on'})
if contact_id:
if contact_dict_up_result := sql_update(data=contact_dict, table_name='contact', rm_id_random=True): pass
else:
log.warning(f'Contact not updated. Contact ID: {contact_id}')
log.debug(contact_dict_up_result)
return False
log.debug(contact_dict_up_result)
else:
if contact_dict_in_result := sql_insert(data=contact_dict, table_name='contact', rm_id_random=True, id_random_length=8): pass
else:
log.warning(f'Contact not created.')
log.debug(contact_dict_in_result)
return False
log.debug(contact_dict_in_result)
contact_id = contact_dict_in_result
contact_outline = {}
contact_outline['contact_id'] = contact_id
# NOTE: Use object model version because of better type checking and validations
if contact_obj.address:
contact_outline['address_id'] = None
address_obj = contact_obj.address
if address_id := contact_obj.address_id: pass
elif address_id := address_obj.id: pass
else: address_id = None
address_obj.id
address_obj.for_type = 'contact'
address_obj.for_id = contact_id
create_update_address_obj_result = create_update_address_obj_v4(
address_dict_obj = address_obj,
address_id = address_id,
account_id = account_id,
for_type = 'contact',
for_id = contact_id,
fail_any = fail_any,
return_outline = return_outline,
)
if isinstance(create_update_address_obj_result, int):
address_id = create_update_address_obj_result
elif create_update_address_obj_result == True: pass
else:
log.warning(f'Create or Update failed while trying create_update_address_obj_v4(): {create_update_address_obj_result}')
address_id = None
contact_outline['address_id'] = address_id
if return_outline:
log.debug(f'Returning the Contact Outline: {contact_outline}')
return contact_outline
else:
log.debug(f'Returning the Contact ID: {contact_id}')
return contact_id
# ### END ### API Contact Methods ### create_update_contact_obj_v4() ###
# ### BEGIN ### API Contact Methods ### create_contact_obj() ###
# NOTE: This will create a contact and then also create a linked address if contact_obj.address data is passed.
# NOTE: In the future it should be required that account_id, for_type, and for_id should be passed separately. account_id might not be required *if* it can be looked up based on for_type and for_id.
# Updated 2022-01-06
@logger_reset
def create_contact_obj(
account_id: int|str,
contact_dict_obj: Contact_Base,
for_type: str = None,
for_id: int|str = None,
address_id: int = None,
create_sub_obj: bool = False,
fail_any: bool = False, # Fail if any thing goes wrong for sub objects
log_lvl: int = logging.WARNING, # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
) -> int|bool:
log.setLevel(log_lvl)
# ### SECTION ### Secondary data validation
# NOTE: Remove at future date. Is this check needed if we trust that the ID is checked ahead of time?
if account_id := redis_lookup_id_random(record_id_random=account_id, table_name='account'): pass
else: return False
if for_id := redis_lookup_id_random(record_id_random=for_id, table_name=for_type): pass
else: return False
if address_id := redis_lookup_id_random(record_id_random=address_id, table_name='address'): pass
elif address_id is None: pass
else: return False
log.info('Create dictionary or Pydantic object')
log.debug(type(contact_dict_obj))
if isinstance(contact_dict_obj, dict):
contact_dict = contact_dict_obj
try:
contact_obj = Contact_Base(**contact_dict)
log.debug(contact_obj)
except ValidationError as e:
log.error(e.json())
return False
else:
contact_obj = contact_dict_obj
contact_obj.account_id = account_id
contact_dict = contact_obj.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'address', 'address_id', 'address_id_random', 'created_on', 'updated_on'})
# ### SECTION ### Process data
# Look for an account_id in the contact_obj
if account_id: pass
elif account_id := contact_obj.account_id: pass
contact_obj.account_id = account_id
contact_dict['account_id'] = account_id
contact_obj.for_type = for_type
contact_obj.for_id = for_id
contact_dict['for_type'] = for_type
contact_dict['for_id'] = for_id
# Look for an address_id in the contact_obj if one was not passed separately
if address_id: pass
# elif address_id := contact_obj.address.id: pass
if contact_dict_in_result := sql_insert(
data = contact_dict,
table_name = 'contact',
rm_id_random = True,
id_random_length = default_num_bytes
): pass
else:
return False
log.debug(contact_dict_in_result)
contact_id = contact_dict_in_result
if address_id and contact_obj.address:
log.info('Updating Address object')
if address_update_result := update_address_obj(
address_id = address_id,
address_dict_obj = contact_obj.address,
for_type = 'contact',
for_id = contact_id,
): pass
else: return False
elif contact_obj.address:
log.info('Creating Address object')
if address_create_result := create_address_obj(
account_id = account_id,
address_dict_obj = contact_obj.address,
for_type = 'contact',
for_id = contact_id,
): pass
else: return False
else: pass
# if contact_obj.address:
# address_obj_new = contact_obj.address
# address_obj_new.for_type = 'contact'
# address_obj_new.for_id = contact_id
# create_address_obj_result = create_address_obj(
# address_obj_new = address_obj_new,
# account_id = account_id,
# for_type = 'contact',
# for_id = contact_id,
# fail_any = fail_any,
# )
# if isinstance(create_address_obj_result, int):
# address_id = create_address_obj_result
# # NOTE: This last update should no longer be needed now that the contact_obj.address_id is not supposed to be used.
# # Need to update the contact with the new address_id
# contact_obj_up = {} # REMOVE
# contact_obj_up['id'] = contact_id # REMOVE
# contact_obj_up['address_id'] = address_id # REMOVE
# if contact_obj_up_result := sql_update(data=contact_obj_up, table_name='contact'): pass # REMOVE
# else: # REMOVE
# return False # REMOVE
# log.debug(contact_obj_up_result) # REMOVE
# else:
# log.debug(f'No address_id was returned when tyring to create_address_obj(): {create_address_obj_result}')
# address_id = None
log.info(f'Returning the new contact_id: {contact_id}')
return contact_id
# ### END ### API Contact Methods ### create_contact_obj() ###
# ### BEGIN ### API Contact Methods ### update_contact_obj() ###
# NOTE: This will update a contact and then also create or update a linked address if contact_obj.address data is passed.
# Updated 2022-01-06
@logger_reset
def update_contact_obj(
contact_id: int|str, # Ideally the int ID should be passed. This allows for updating of the id_random value.
contact_dict_obj: Contact_Base,
for_type: str = None,
for_id: int|str = None,
address_id: int = None,
create_sub_obj: bool = False,
fail_any: bool = False, # Fail if any thing goes wrong for sub objects
return_dict: bool = False,
log_lvl: int = logging.WARNING, # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
) -> bool:
log.setLevel(log_lvl)
# ### SECTION ### Secondary data validation
# NOTE: Remove at future date. Is this check needed if we trust that the ID is checked ahead of time?
if contact_id := redis_lookup_id_random(record_id_random=contact_id, table_name='contact'): pass
else: return False
if address_id := redis_lookup_id_random(record_id_random=address_id, table_name='address'): pass
elif address_id is None: pass
else: return False
log.info('Create dictionary or Pydantic object')
log.debug(type(contact_dict_obj))
log.debug(contact_dict_obj)
if isinstance(contact_dict_obj, dict):
contact_dict = contact_dict_obj
try:
contact_obj = Contact_Base(**contact_dict)
log.debug(contact_obj)
except ValidationError as e:
log.error(e.json())
return False
else:
contact_obj = contact_dict_obj
contact_dict = contact_obj.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'address', 'address_id', 'address_id_random', 'created_on', 'updated_on'})
log.debug(contact_dict)
# ### SECTION ### Process data
contact_obj.id = contact_id # Is this needed?
contact_dict['id'] = contact_id
# Look for an account_id in the contact_obj
if account_id := contact_obj.account_id: pass
elif account_id := get_account_id_w_for_type_id(for_type='contact', for_id=contact_id): pass
# If for_type and for_id are passed then the contact needs to be updated
if for_type and for_id:
contact_obj.for_type = for_type
contact_obj.for_id = for_id
contact_dict['for_type'] = for_type
contact_dict['for_id'] = for_id
# Look for an address_id in the contact_obj if one was not passed separately
if address_id: pass
elif address_id := contact_obj.address.id: pass
log.debug(contact_obj.address.id)
log.debug(contact_obj.address.id_random)
if contact_dict_up_result := sql_update(
data=contact_dict,
table_name='contact',
rm_id_random=True
): pass
else:
log.warning(f'Contact not updated.')
log.debug(contact_dict_up_result)
return False
log.debug(contact_dict_up_result)
if address_id and contact_obj.address:
log.info('Updating Address object')
if address_update_result := update_address_obj(
address_id = address_id,
address_dict_obj = contact_obj.address,
for_type = 'contact',
for_id = contact_id,
): pass
else: return False
elif contact_obj.address:
log.info('Creating Address object')
if address_create_result := create_address_obj(
account_id = account_id,
address_dict_obj = contact_obj.address,
for_type = 'contact',
for_id = contact_id,
): pass
else: return False
else: pass
log.debug(contact_obj.dict(by_alias=False, exclude_unset=True))
return True
# ### END ### API Contact Methods ### update_contact_obj() ###
# ### BEGIN ### API Contact Methods ### create_update_contact_obj() ###
@logger_reset
def create_update_contact_obj(
contact_id: int|str|None, # Ideally the int ID should be passed. This allows for updating of the id_random value.
contact_obj: Contact_Base,
process_address: bool = False,
process_organization: bool = False,
) -> bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if contact_id:
if contact_id := redis_lookup_id_random(record_id_random=contact_id, table_name='contact'): pass
else: return False
contact_obj.id = contact_id
else:
# Insert record now and update later
contact_dict_in = contact_obj.dict(by_alias=False, exclude_unset=True, exclude={'address', 'contact', 'organization', 'user'})
log.debug(contact_dict_in)
contact_in_result = sql_insert(
data = contact_dict_in,
table_name = 'contact',
rm_id_random = True,
id_random_length = default_num_bytes,
)
log.debug(contact_in_result)
if isinstance(contact_in_result, bool) and contact_in_result is True:
return contact_in_result
elif isinstance(contact_in_result, int):
contact_id = contact_in_result
contact_obj.id = contact_id
else:
return False # This should not happen.
# Process address data
if process_address and contact_obj.address:
address_obj = contact_obj.address
address_obj.for_type = 'contact'
address_obj.for_id = contact_id
address_id = contact_obj.address_id_random
address_result = create_update_address_obj(
address_id = address_id,
address_obj = address_obj,
)
log.debug(address_result)
if isinstance(address_result, bool) and address_result is True:
pass # Do not need to update contact object.
elif isinstance(address_result, bool) and address_result is False:
pass # Do not need to update contact object.
elif isinstance(address_result, int):
contact_obj.address_id = address_result
else:
log.warning('Something may have gone wrong while trying to create or update a address.')
# Process contact data
contact_dict_up = contact_obj.dict(by_alias=False, exclude_unset=True, exclude={'address'})
log.debug(contact_dict_up)
# Update record
contact_up_result = sql_update(
data = contact_dict_up,
table_name = 'contact',
rm_id_random = True,
)
log.debug(contact_up_result)
if isinstance(contact_up_result, bool) and contact_up_result is True:
return contact_id
elif isinstance(contact_up_result, bool) and contact_up_result is False:
return False
elif isinstance(contact_up_result, int):
return contact_up_result
else:
return False
# ### END ### API Contact Methods ### create_update_contact_obj() ###

View File

@@ -0,0 +1,279 @@
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_enable_part, sql_insert, sql_limit_offset_part, sql_select, sql_update
from app.lib_general import log, logging, logger_reset
from app.methods.event_cfg_methods import load_event_cfg_obj
from app.methods.event_location_methods import load_event_location_obj
from app.models.common_field_schema import default_num_bytes
from app.models.data_store_models import Data_Store_Base
# ### BEGIN ### API Data Store Methods ### load_data_store_obj() ###
# Updated 2022-03-11
@logger_reset
def load_data_store_obj(
data_store_id: int,
enabled: str = 'enabled', # enabled, disabled, all
limit: int = 10,
offset: int = 0,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
) -> Data_Store_Base|dict|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if data_store_rec := sql_select(table_name='v_data_store', record_id=data_store_id): pass
else: return False
log.debug(data_store_rec)
try:
data_store_obj = Data_Store_Base(**data_store_rec)
except ValidationError as e:
log.error(e.json())
return False
log.debug(data_store_obj)
if model_as_dict:
return data_store_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return data_store_obj
# ### END ### API Data Store Methods ### load_data_store_obj() ###
# ### BEGIN ### API Data Store Methods ### load_data_store_obj_w_code() ###
# NOTE: This is customized to look for records with or without an account_id and some code. By default only the first sorted result will be returned.
# NOTE: By default the first sorted result should be the one for a specific account with some code. If a result with a null account_id and some code is found it will be returned if no account_id specific results are found.
# Updated 2022-03-11
@logger_reset
def load_data_store_obj_w_code(
account_id: int,
code: str,
for_type: int = None,
for_id: int = None,
enabled: str = 'enabled', # enabled, disabled, all
limit: int = 1,
offset: int = 0,
by_alias: bool = True, # NOTE: For now this is ignored
exclude_unset: bool = True, # NOTE: For now this is ignored
model_as_dict: bool = False, # NOTE: For now this is ignored
) -> Data_Store_Base|dict|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
log.info(f'Getting Data Store record with code: {code} for Account ID: {account_id} and For Type: {for_type} and For ID: {for_id}')
data = {}
data['account_id'] = account_id
data['code'] = code
data['for_type'] = for_type
data['for_id'] = for_id
log.debug(data)
# log.warning(f'Can we get past this?????????? {code}')
# if for_id := redis_lookup_id_random(record_id_random=for_id, table_name=for_type): pass
# else: return False
if for_type and for_id:
sql_for_type_id = 'AND `data_store`.for_type = :for_type AND `data_store`.for_id = :for_id'
else:
sql_for_type_id = 'AND `data_store`.for_type IS NULL AND `data_store`.for_id IS NULL'
sql_enabled, data['enable'] = sql_enable_part(table_name='data_store', enabled=enabled) # Reasonably safe return str and bool
sql_limit = sql_limit_offset_part(limit=limit, offset=offset) # Reasonably safe return str
log.debug(data)
sql = f"""
SELECT *
FROM `v_data_store` AS `data_store`
WHERE
(
`data_store`.account_id = :account_id
OR `data_store`.account_id IS NULL
)
AND `data_store`.code = :code
{sql_for_type_id}
{sql_enabled}
ORDER BY `data_store`.account_id DESC, `data_store`.created_on DESC, `data_store`.updated_on DESC
{sql_limit};
"""
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(sql)
if data_store_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
data_store_rec_li = data_store_rec_li_result
else: # [] or False
data_store_rec_li = data_store_rec_li_result
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.info(f'No Data Store records found with code: {code} for Account ID: {account_id} and For Type: {for_type} and For ID: {for_id}')
log.debug(data_store_rec_li_result)
data_store_obj_li = []
if data_store_rec_li:
for data_store_rec in data_store_rec_li:
try:
data_store_obj = Data_Store_Base(**data_store_rec)
data_store_obj_li.append(data_store_obj)
except ValidationError as e:
log.error(e.json())
data_store_obj_li.append(None)
# return False
log.debug(data_store_obj)
else: pass
log.info(f'Found {len(data_store_obj_li)} Data Store records with code: {code} for Account ID: {account_id} and For Type: {for_type} and For ID: {for_id}')
log.debug(data_store_obj_li)
return data_store_obj_li
# ### END ### API Data Store Methods ### load_data_store_obj_w_code() ###
# ### BEGIN ### API Data Store Methods ### get_data_store_rec_list() ###
# Updated 2022-03-11
@logger_reset
def get_data_store_rec_list(
account_id: int,
for_type: str, # 'account' is a special case
for_id: int,
person_id: int = None,
user_id: int = None,
code: str = None,
enabled: str = 'enabled', # enabled, disabled, all
limit: int = 100,
offset: int = 0,
) -> list|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
# if for_type and for_id:
# if for_id := redis_lookup_id_random(record_id_random=for_id, table_name=for_type): pass
# else: return False
if account_id:
sql_account_id = f'`data_store`.account_id = :account_id'
# if code:
# sql_code = f'`data_store`.code = :code'
data = {}
data['account_id'] = account_id
data['code'] = code
# data['for_type'] = for_type
# data['for_id'] = for_id
sql_enabled, data['enable'] = sql_enable_part(table_name='data_store', enabled=enabled) # Reasonably safe return str and bool
sql_limit = sql_limit_offset_part(limit=limit, offset=offset) # Reasonably safe return str
sql = f"""
SELECT `data_store`.id AS 'data_store_id', `data_store`.id_random AS 'data_store_id_random'
FROM `v_data_store` AS `data_store`
WHERE
{sql_account_id}
{sql_enabled}
ORDER BY `data_store`.created_on DESC, `data_store`.updated_on DESC
{sql_limit};
"""
log.debug(sql)
if data_store_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
data_store_rec_li = data_store_rec_li_result
else: # [] or False
data_store_rec_li = data_store_rec_li_result
log.debug(data_store_rec_li_result)
return data_store_rec_li
# ### END ### API Data Store Methods ### get_data_store_rec_list() ###
# ### BEGIN ### API Data Store Methods ### create_update_data_store_obj() ###
# Updated 2022-03-11
def create_update_data_store_obj(
data_store_dict_obj: Data_Store_Base|dict,
data_store_id: int|str = None,
create_sub_obj: bool = False,
fail_any: bool = False, # Fail if any thing goes wrong for sub objects
return_outline: bool = False,
) -> int|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
# ### SECTION ### Secondary data validation
log.info('Checking requirements...')
if data_store_id:
log.info(f'Data Store ID passed. Update existing Data Store. Data Store ID: {data_store_id}')
if data_store_id := redis_lookup_id_random(record_id_random=data_store_id, table_name='data_store'): pass
else:
log.error('Data Store ID passed but is invalid. Failed requirement.')
return False
else:
log.info('No Data Store ID passed. Create new Data Store. Required: None; Optional: Account ID, For Type, For ID, Person ID, User ID')
if account_id := redis_lookup_id_random(record_id_random=account_id, table_name='account'): pass
elif account_id is None: pass
else:
log.warning('Missing or invalid Account ID passed. Failed requirement.')
log.info(f'Account ID: {account_id}')
return False
log.info('Create dictionary or Pydantic object')
log.debug(type(data_store_dict_obj))
if isinstance(data_store_dict_obj, dict):
data_store_dict = data_store_dict_obj
if data_store_id:
data_store_dict['data_store_id'] = data_store_id
if account_id:
account_dict['account_id'] = account_id
try:
data_store_obj = Data_Store_Base(**data_store_dict)
except ValidationError as e:
log.error(e.json())
return False
else:
data_store_obj = data_store_dict_obj
if data_store_id:
# NOTE: Can't update the ID alias if it was never set.
data_store_obj.id = data_store_id
if account_id:
data_store_obj.account_id = account_id
log.debug(data_store_obj)
data_store_dict = data_store_obj.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'account', 'created_on', 'updated_on'})
# ### SECTION ### Process data
if data_store_id:
if data_store_dict_up_result := sql_update(data=data_store_dict, table_name='data_store', rm_id_random=True): pass
else:
log.warning(f'Data Store not updated. Data Store ID: {data_store_id}')
log.debug(data_store_dict_up_result)
return False
log.debug(data_store_dict_up_result)
else:
if data_store_dict_in_result := sql_insert(data=data_store_dict, table_name='data_store', rm_id_random=True, id_random_length=8): pass
else:
log.warning(f'Data Store not created.')
log.debug(data_store_dict_in_result)
return False
log.debug(data_store_dict_in_result)
data_store_id = data_store_dict_in_result
data_store_outline = {}
data_store_outline['data_store_id'] = data_store_id
if return_outline:
log.debug(f'Returning the Data Store Outline: {data_store_outline}')
return data_store_outline
else:
log.debug(f'Returning the Data Store ID: {data_store_id}')
return data_store_id
# ### END ### API Data Store Methods ### create_update_data_store_obj() ###

View File

@@ -0,0 +1,558 @@
import datetime, json, os, pprint, pytz, random, requests, shutil, string, time
from typing import Dict, List, Optional, Set, Union
from pydantic import BaseModel, EmailStr, Field, PrivateAttr, ValidationError, validator
from app.config import settings
from app.db_sql import redis_lookup_id_random, sql_insert, sql_select, sql_update
from app.lib_general import log, logging, logger_reset
from app.methods.event_file_methods import create_event_file_obj
from app.methods.hosted_file_methods import create_hosted_file_obj, load_hosted_file_obj, save_file, save_file_to_hosted_file
from app.models.hosted_file_models import Hosted_File_Base
from app.models.event_file_models import Event_File_Base
api = {}
# api['base_url'] = 'https://aapor.confex.com/aapor/20xx/meetingapi.cgi/[object]/[id]'
api['base_url'] = 'https://aapor.confex.com/aapor/2024/meetingapi.cgi'
api['headers'] = { 'Content-Type': 'application/json;charset=UTF-8' }
api['username'] = None
api['password'] = None
# ### BEGIN ### API External Confex Methods ### get_event_session_list() ###
# Updated 2023-04-11
@logger_reset
def get_event_session_list(
):
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
# if result := authenticate():
# log.debug(result)
# else:
# return False
endpoint = '/Session'
uri = api['base_url']+endpoint
params = {}
confex_session_list = None
try_request = True
max_tries = 5
try_count = 0
while try_request and try_count <= max_tries:
try_count = try_count + 1
resp = requests.get(url=uri, params=params, headers=api['headers'])
log.debug(f'Status Code: {resp.status_code}')
log.debug(f'Headers: {resp.headers}')
# log.debug(f'Encoding: {resp.encoding}')
# log.debug('Text:')
# log.debug(resp.text)
# log.debug(resp.json())
if resp.status_code == 200:
log.info('Status 200')
log.debug(resp.json())
confex_session_list_raw = resp.json() # .get('data').get('dataList')[0]
# log.debug(confex_session_list_raw)
confex_session_list = confex_session_list_raw
log.debug(confex_session_list)
try_request = False
elif resp.status_code == 404:
log.info('No results returned (status 404)')
try_request = False
confex_session_list = None
elif resp.status_code == 429:
log.warning('Hit rate limit. Sleeping for .1 seconds...')
time.sleep(.1)
try_request = True
confex_session_list = False
else:
log.info('Not trying again')
try_request = False
confex_session_list = False
log.warning('Something may have gone wrong during the request.')
# log.warning('Something may have gone wrong. Setting the API app_user_token_datetime value to None to re-authenticate with Confex on the next request.')
# api['app_user_token_datetime'] = None # Resetting this just in case the App and or User token expired.
return confex_session_list
# ### END ### API External Confex Methods ### get_event_session_list() ###
# ### BEGIN ### API External Confex Methods ### get_event_session_detail() ###
# Updated 2023-04-11
@logger_reset
def get_event_session_detail(
confex_session_id: str, # actually an auto number
):
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
# if result := authenticate():
# log.debug(result)
# else:
# return False
endpoint = f'/Session/{confex_session_id}'
uri = api['base_url']+endpoint
params = {}
confex_session_detail = None
try_request = True
max_tries = 5
try_count = 0
while try_request and try_count <= max_tries:
try_count = try_count + 1
resp = requests.get(url=uri, params=params, headers=api['headers'])
log.debug(f'Status Code: {resp.status_code}')
log.debug(f'Headers: {resp.headers}')
# log.debug(f'Encoding: {resp.encoding}')
# log.debug('Text:')
# log.debug(resp.text)
# log.debug(resp.json())
if resp.status_code == 200:
log.info('Status 200')
log.debug(resp.json())
confex_session_detail_raw = resp.json() # .get('data').get('dataList')[0]
# log.debug(confex_session_detail_raw)
confex_session_detail = confex_session_detail_raw
log.debug(confex_session_detail)
try_request = False
elif resp.status_code == 404:
log.info('No results returned (status 404)')
try_request = False
confex_session_detail = None
elif resp.status_code == 429:
log.warning('Hit rate limit. Sleeping for .1 seconds...')
time.sleep(.1)
try_request = True
confex_session_detail = False
else:
log.info('Not trying again')
try_request = False
confex_session_detail = False
log.warning('Something may have gone wrong during the request.')
# log.warning('Something may have gone wrong. Setting the API app_user_token_datetime value to None to re-authenticate with Confex on the next request.')
# api['app_user_token_datetime'] = None # Resetting this just in case the App and or User token expired.
return confex_session_detail
# ### END ### API External Confex Methods ### get_event_session_detail() ###
# ### BEGIN ### API External Confex Methods ### get_event_presentation_detail() ###
# Updated 2023-04-11
@logger_reset
def get_event_presentation_detail(
confex_session_id: str,
confex_presentation_id: str, # similar to 'Paper/99999'
):
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
# if result := authenticate():
# log.debug(result)
# else:
# return False
# endpoint = f'/Session/{confex_session_id}/{confex_presentation_id}'
endpoint = f'/{confex_presentation_id}'
uri = api['base_url']+endpoint
params = {}
confex_presentation_detail = None
try_request = True
max_tries = 5
try_count = 0
while try_request and try_count <= max_tries:
try_count = try_count + 1
resp = requests.get(url=uri, params=params, headers=api['headers'])
log.debug(f'Status Code: {resp.status_code}')
log.debug(f'Headers: {resp.headers}')
# log.debug(f'Encoding: {resp.encoding}')
# log.debug('Text:')
# log.debug(resp.text)
# log.debug(resp.json())
if resp.status_code == 200:
log.info('Status 200')
log.debug(resp.json())
confex_presentation_detail_raw = resp.json() # .get('data').get('dataList')[0]
# log.debug(confex_presentation_detail_raw)
confex_presentation_detail = confex_presentation_detail_raw
log.debug(confex_presentation_detail)
try_request = False
elif resp.status_code == 404:
log.warning('No results returned (status 404)')
try_request = False
confex_presentation_detail = None
elif resp.status_code == 429:
log.warning('Hit rate limit. Sleeping for .1 seconds...')
time.sleep(.1)
try_request = True
confex_presentation_detail = False
else:
log.warning('Not trying again')
try_request = False
confex_presentation_detail = False
log.warning('Something may have gone wrong during the request.')
# log.warning('Something may have gone wrong. Setting the API app_user_token_datetime value to None to re-authenticate with Confex on the next request.')
# api['app_user_token_datetime'] = None # Resetting this just in case the App and or User token expired.
return confex_presentation_detail
# ### END ### API External Confex Methods ### get_event_presentation_detail() ###
# ### BEGIN ### API External Confex Methods ### get_event_presenter_detail() ###
# Updated 2023-04-11
@logger_reset
def get_event_presenter_detail(
confex_session_id: str,
confex_presentation_id: str, # similar to 'Paper/99999'
confex_presenter_id: str, # similar to 'Person/99999'
):
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
# if result := authenticate():
# log.debug(result)
# else:
# return False
endpoint = f'/Session/{confex_session_id}/{confex_presentation_id}/{confex_presenter_id}'
uri = api['base_url']+endpoint
params = {}
confex_presenter_detail = None
try_request = True
max_tries = 5
try_count = 0
while try_request and try_count <= max_tries:
try_count = try_count + 1
resp = requests.get(url=uri, params=params, headers=api['headers'])
log.debug(f'Status Code: {resp.status_code}')
log.debug(f'Headers: {resp.headers}')
# log.debug(f'Encoding: {resp.encoding}')
# log.debug('Text:')
# log.debug(resp.text)
# log.debug(resp.json())
if resp.status_code == 200:
log.info('Status 200')
log.debug(resp.json())
confex_presenter_detail_raw = resp.json() # .get('data').get('dataList')[0]
# log.debug(confex_presenter_detail_raw)
confex_presenter_detail = confex_presenter_detail_raw
log.debug(confex_presenter_detail)
try_request = False
elif resp.status_code == 404:
log.info('No results returned (status 404)')
try_request = False
confex_presenter_detail = None
elif resp.status_code == 429:
log.warning('Hit rate limit. Sleeping for .1 seconds...')
time.sleep(.1)
try_request = True
confex_presenter_detail = False
else:
log.warning('Not trying again')
try_request = False
confex_presenter_detail = False
log.warning('Something may have gone wrong during the request.')
# log.warning('Something may have gone wrong. Setting the API app_user_token_datetime value to None to re-authenticate with Confex on the next request.')
# api['app_user_token_datetime'] = None # Resetting this just in case the App and or User token expired.
return confex_presenter_detail
# ### END ### API External Confex Methods ### get_event_presenter_detail() ###
# ### BEGIN ### API External Confex Methods ### get_event_file_detail() ###
# Updated 2023-04-30
@logger_reset
def get_event_file_detail(
confex_file_id: str, # similar to 'FileMap/Paper1928_Presentation2'
):
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
endpoint = f'/{confex_file_id}'
uri = api['base_url']+endpoint
params = {}
confex_file_detail = None
try_request = True
max_tries = 5
try_count = 0
while try_request and try_count <= max_tries:
try_count = try_count + 1
resp = requests.get(url=uri, params=params, headers=api['headers'])
log.debug(f'Status Code: {resp.status_code}')
log.debug(f'Headers: {resp.headers}')
if resp.status_code == 200:
log.info('Status 200')
log.debug(resp.json())
confex_file_detail_raw = resp.json()
# log.debug(confex_file_detail_raw)
confex_file_detail = confex_file_detail_raw
log.debug(confex_file_detail)
try_request = False
elif resp.status_code == 404:
log.warning('No results returned (status 404)')
try_request = False
confex_file_detail = None
elif resp.status_code == 429:
log.warning('Hit rate limit. Sleeping for .1 seconds...')
time.sleep(.1)
try_request = True
confex_file_detail = False
else:
log.warning('Not trying again')
try_request = False
confex_file_detail = False
log.warning('Something may have gone wrong during the request.')
return confex_file_detail
# ### END ### API External Confex Methods ### get_event_file_detail() ###
# ### BEGIN ### API External Confex Methods ### get_event_slot_data() ###
# Updated 2023-05-01
@logger_reset
def get_event_slot_data(
confex_slot_id: str, # similar to 'SlotData/Session1110_Slot119'
):
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
endpoint = f'/{confex_slot_id}'
uri = api['base_url']+endpoint
params = {}
confex_slot_data = None
try_request = True
max_tries = 5
try_count = 0
while try_request and try_count <= max_tries:
try_count = try_count + 1
resp = requests.get(url=uri, params=params, headers=api['headers'])
log.debug(f'Status Code: {resp.status_code}')
log.debug(f'Headers: {resp.headers}')
if resp.status_code == 200:
log.info('Status 200')
log.debug(resp.json())
confex_slot_data_raw = resp.json()
confex_slot_data = confex_slot_data_raw
log.debug(confex_slot_data)
try_request = False
elif resp.status_code == 404:
log.warning('No results returned (status 404)')
try_request = False
confex_slot_data = None
elif resp.status_code == 429:
log.warning('Hit rate limit. Sleeping for .1 seconds...')
time.sleep(.1)
try_request = True
confex_slot_data = False
else:
log.warning('Not trying again')
try_request = False
confex_slot_data = False
log.warning('Something may have gone wrong during the request.')
return confex_slot_data
# ### END ### API External Confex Methods ### get_event_slot_data() ###
# Updated 2023-05-01
@logger_reset
async def get_event_file_save_local(
url: str,
confex_file_info: dict,
account_id: int,
link_to_type: str,
link_to_id: int,
event_id: int = None,
event_location_id: int = None,
event_presentation_id: int = None,
event_presenter_id: int = None,
event_session_id: int = None,
event_track_id: int = None,
) -> None|bool|dict:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
# *** Part 1: *** First download the file to tmp and then save the hashed file to hosted_files directory.
hosted_tmp_path = settings.FILES_PATH['hosted_tmp_root']
log.info(f'Hosted Tmp Path: {hosted_tmp_path}')
log.debug(shutil.disk_usage(hosted_tmp_path))
hosted_tmp_path_w_filename = os.path.join(hosted_tmp_path, 'confex', 'confex_temp.file')
# hosted_tmp_path_w_filename = os.path.join(hosted_tmp_path, 'confex_temp.file')
# return True
# response = requests.get(url)
response = requests.get(url, stream=True)
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
# log.debug(response)
# log.debug(dir(response))
# file_obj = open(local_path, 'wb')
with open(hosted_tmp_path_w_filename, 'wb') as f:
for chunk in response.iter_content(chunk_size=1024):
if chunk:
f.write(chunk)
# *** Part 2: *** Save the hashed file to hosted_files directory.
file_info = await save_file_to_hosted_file(
file_path = hosted_tmp_path_w_filename,
filename = confex_file_info.get('filename'),
extension = confex_file_info.get('extension'),
account_id = account_id,
link_to_type = link_to_type,
link_to_id = link_to_id,
)
# *** Part 3: *** Save information to database in hosted_file table (hosted_file_link table will be updated by an event_file table trigger)
if file_info.get('saved'):
# NOTE: Just in case look up in DB based on hash
log.info('Look up in DB based on hash...')
if hosted_file_sel_result := sql_select(
table_name = 'hosted_file',
field_name = 'hash_sha256',
field_value = file_info['hash_sha256'],
):
log.warning('Found an existing host_file object_entry in the DB but the file was not found on the server!')
# Got existing host_file object_entry!
# Odd... the hash was found in the database, but the file had to be copied again.
# If this happens then the file on the host server was probably deleted at some point.
hosted_file_id = hosted_file_sel_result.get('id', None)
hosted_file_id_random = hosted_file_sel_result.get('id_random', None)
hosted_file_dict = load_hosted_file_obj(hosted_file_id=hosted_file_id, model_as_dict=True)
else:
# This is normal since the file was not found on the host server and not found in the DB.
# Create a new host_file object entry and new host_file.id_random.
file_info['account_id'] = account_id
# file_info['account_id_random'] = account_id_random
hosted_file_obj = Hosted_File_Base(**file_info)
if hosted_file_obj_result := create_hosted_file_obj(hosted_file_obj_new=hosted_file_obj):
hosted_file_id = hosted_file_obj_result
hosted_file_dict = load_hosted_file_obj(hosted_file_id=hosted_file_id, model_as_dict=True)
else:
log.warning('For some reason a host_file object entry could not be created.')
hosted_file_id = None
hosted_file_dict = hosted_file_obj.dict(by_alias=True, exclude_unset=True, exclude={'id', 'id_random'}) # pylint: disable=no-member
log.debug(hosted_file_obj_result)
log.debug(hosted_file_sel_result)
else: return False
# *** Part 4: *** Save information to database in event_file (will trigger an update to hosted_file_link)
event_file_data = {}
event_file_data['hosted_file_id'] = hosted_file_id
# event_file_data['hosted_file_id_random'] = hosted_file_id_random
event_file_data['for_type'] = link_to_type
event_file_data['for_id'] = link_to_id
if event_id:
event_file_data['event_id'] = event_id
if event_location_id:
event_file_data['event_location_id'] = event_location_id
if event_presentation_id:
event_file_data['event_presentation_id'] = event_presentation_id
if event_presenter_id:
event_file_data['event_presenter_id'] = event_presenter_id
if event_session_id:
event_file_data['event_session_id'] = event_session_id
if event_track_id:
event_file_data['event_track_id'] = event_track_id
event_file_data['filename'] = file_info.get('filename')
event_file_data['extension'] = file_info.get('extension')
# event_file_data['open_in_os'] = event_file_obj.open_in_os
# event_file_data['internal_use'] = event_file_obj.internal_use
# event_file_data['public_use'] = hosted_file_obj.public_use
# event_file_data['lu_file_purpose_id'] = hosted_file_obj.lu_file_purpose_id
# event_file_data['file_purpose'] = hosted_file_obj.file_purpose
# event_file_data['public'] = hosted_file_obj.public
# event_file_data['hide'] = hosted_file_obj.hide
event_file_data['enable'] = True # hosted_file_obj.enable
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_file_data)
try:
event_file_obj = Event_File_Base(**event_file_data)
except ValidationError as e:
log.error(e.json())
return False
log.debug(event_file_obj)
create_event_file_obj_result = create_event_file_obj(event_file_obj_new=event_file_obj)
log.debug(create_event_file_obj_result)
return file_info

View File

@@ -0,0 +1,798 @@
import datetime, json, pprint, pytz, random, requests, secrets, string, time
from requests.auth import HTTPBasicAuth
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, logger_reset, secure_hash_string, verify_secure_hash_string
from app.methods.c_idaa_methods import refresh_person_group
from app.methods.person_methods import create_person_kiss, get_person_rec_list, get_person_rec_w_external_id, load_person_obj, update_person_kiss
from app.methods.membership_person_methods import create_membership_person_obj, update_membership_person_obj
app = {}
app['client_id'] = '0oalt6dz82oSbN9ok1t7' # From Cvent Developer Portal
app['secret'] = 'gQY96qffZAuB_44k73C_hn_MHeByBS8LXHj1vPRm' # From Cvent Developer Portal
api = {}
api['base_url'] = 'https://api-platform.cvent.com/ea' # Including /ea as the Cvent version. EA = Early Access
api['headers'] = {} # { 'Content-Type': content_type, 'Authorization': 'Basic '+str(cvent_authorization_base64) }
# ### BEGIN ### API External Cvent Methods ### get_access_token() ###
# Updated 2022-02-01
@logger_reset
def get_access_token():
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
log.debug(f'API data:\n{api}')
# api['access_token'] = ''
if 'access_token' in api:
access_token = api.get('access_token')
log.debug(f'Cvent Access Token found: {access_token}')
else:
log.info(f'Cvent Access Token was not found. Will request one.')
if 'expire_on' in api and datetime.datetime.now() < api['expire_on']:
expire_on = api.get('expire_on')
log.debug(f'Cvent Access Token is current: {expire_on}')
return api
else:
expire_on = api.get('expire_on')
log.info(f'Cvent Access Token is not current. Requesting a new one. Expired On: {expire_on}')
endpoint = '/oauth2/token'
uri = api['base_url']+endpoint
data = { 'grant_type': 'client_credentials', 'client_id': app['client_id'] }
log.debug(f'Oauth Token Request Data:\n{api}')
try_request = True
limit = 0
while try_request and limit < 3:
limit = limit + 1
resp = requests.post(url=uri, data=data, auth=HTTPBasicAuth(app['client_id'], app['secret'])) # Sending as HTML form data
log.debug(f'Status Code: {resp.status_code}')
log.debug(f'Headers: {resp.headers}')
# log.debug(f'Encoding: {resp.encoding}')
log.debug(f'JSON: {resp.json()}')
# log.debug('Text:', resp.text)
response_data = resp.json()
log.debug(json.dumps(response_data, indent=2, default=str))
if 'message' in response_data and response_data['message'] == 'Too Many Requests':
log.warning('Hit Cvent rate limit. Sleeping for .5 seconds...')
time.sleep(.5)
elif 'access_token' in response_data:
access_token = response_data.get('access_token')
log.info(f'Got a new Access Token {access_token} from Cvent')
try_request = False
api['access_token'] = response_data['access_token']
api['expires_in'] = response_data['expires_in']
api['token_type'] = response_data['token_type']
api['expire_on'] = datetime.datetime.now() + datetime.timedelta(seconds=response_data['expires_in'])
api['headers']['Accept'] = 'application/json'
api['headers']['x-api-key'] = app['client_id']
api['headers']['Authorization'] = 'Bearer '+api['access_token']
log.debug(api)
log.warning('Sleeping for .5 seconds to avoid Cvent rate limit...')
time.sleep(.5)
return api
# ### END ### API External Cvent Methods ### get_access_token() ###
# ### BEGIN ### API External Cvent Methods ### get_group_contact_list() ###
# Updated 2022-02-09
@logger_reset
def get_group_contact_list(contact_group_id: str=None):
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
get_access_token()
# Contact Group ID for IDAA Full Mailing List: UUID = '60ef4838-5340-4e2a-9d0a-b4ef310eaa69'
endpoint = f'/contact-groups/{contact_group_id}/contacts'
uri = api['base_url']+endpoint
params = {}
try_request = True
limit = 0
next_token = None
cvent_contact_list = []
while try_request and limit < 100:
limit = limit + 1
if next_token: params['token'] = next_token
resp = requests.get(url=uri, headers=api['headers'], params=params)
response_data = resp.json()
log.debug(json.dumps(response_data, indent=2, default=str))
if 'message' in response_data and response_data['message'] == 'Too Many Requests':
log.warning('Hit Cvent rate limit. Sleeping for .5 seconds...')
time.sleep(.5)
elif 'paging' in response_data:
log.debug(json.dumps(response_data.get('paging'), indent=2, default=str))
if total_count := response_data.get('paging').get('totalCount'):
log.info(f'Found {total_count} Cvent Contact results in the Cvent Group')
cvent_contact_list = cvent_contact_list + response_data.get('data')
if next_token := response_data.get('paging').get('nextToken'):
log.warning('Sleeping for .25 seconds to avoid Cvent rate limit...')
time.sleep(.25)
else:
try_request = False
else:
log.info('No Cvent Contact results for Cvent Group')
try_request = False
next_token = None
return None
else:
log.warning('Unexpected response from Cvent API')
return False
if 'message' in response_data and response_data['message'] == 'Too Many Requests': return False
return cvent_contact_list
# ### END ### API External Cvent Methods ### get_group_contact_list() ###
# ### BEGIN ### API External Cvent Methods ### get_contact_custom_field_list() ###
# Updated 2022-01-31
@logger_reset
def get_contact_custom_field_list(api):
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
endpoint = '/custom-fields'
uri = api['base_url']+endpoint
params = { 'filter': "category eq 'Contact'" }
resp = requests.get(url=uri, headers=api['headers'], params=params)
log.debug('Status Code:', resp.status_code)
log.debug('Headers:', resp.headers)
# log.debug('Encoding:', resp.encoding)
log.debug('JSON:', resp.json())
# log.debug('Text:', resp.text)
response_data = resp.json()
# f = open('contact_custom_field_list.txt', 'w')
# f.write(pprint.pformat(response_data, indent=2, width=160))
return response_data
# ### END ### API External Cvent Methods ### get_contact_custom_field_list() ###
# ### BEGIN ### API External Cvent Methods ### get_contact_list() ###
# Updated 2022-01-31
@logger_reset
def get_contact_list(external_id: str=None, email: str=None):
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
get_access_token()
endpoint = '/contacts'
uri = api['base_url']+endpoint
# External ID for IDAA: UUID = '609ab766-7d79-4a9d-a72c-f126412659ee'
# customField.609ab766-7d79-4a9d-a72c-f126412659ee eq external_id
params = {}
if external_id:
log.info(f'Looking up Cvent Contcat with External ID: {external_id}')
params['filter'] = f"deleted eq 'False' and customField.609ab766-7d79-4a9d-a72c-f126412659ee eq '{external_id}'"
elif email:
log.info(f'Looking up Cvent Contcat with Email: {email}')
params['filter'] = f"deleted eq 'False' and email eq '{email}'"
elif external_id is None:
log.info(f'Looking up Cvent Contact(s) with empty Cvent custom field for external ID.')
params['filter'] = f"deleted eq 'False' and customField.609ab766-7d79-4a9d-a72c-f126412659ee lt '1'"
else:
log.info(f'Looking up Cvent Contact(s) that have not been marked as deleted.')
params['filter'] = f"deleted eq 'False'"
try_request = True
limit = 0
while try_request and limit < 3:
limit = limit + 1
resp = requests.get(url=uri, headers=api['headers'], params=params)
response_data = resp.json()
log.debug(json.dumps(response_data, indent=2, default=str))
if 'message' in response_data and response_data['message'] == 'Too Many Requests':
log.warning('Hit Cvent rate limit. Sleeping for .5 seconds...')
time.sleep(.5)
else:
if 'paging' in response_data:
current_token = response_data.get('paging').get('currentToken')
total_count = response_data.get('paging').get('totalCount')
log.info(f'Found {total_count} results from Cvent Contact list')
log.debug(json.dumps(response_data.get('paging'), indent=2, default=str))
try_request = False
if 'message' in response_data and response_data['message'] == 'Too Many Requests': return False
return response_data
# ### END ### API External Cvent Methods ### get_contact_list() ###
# ### BEGIN ### API External Cvent Methods ### get_recent_contact_list() ###
# Updated 2022-02-02
@logger_reset
def get_recent_contact_list(
from_created_on = None,
to_created_on = None, # datetime.datetime.utcnow(),
from_updated_on = None,
to_updated_on = None,
# get_only = 'created', # created, updated/modified
):
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
get_access_token()
endpoint = f'/contacts'
uri = api['base_url']+endpoint
params = {}
if from_created_on:
from_created_on_str = from_created_on.isoformat()
log.info(f'From Created On: {from_created_on_str}')
if to_created_on:
to_created_on_str = to_created_on.isoformat()
log.info(f'To Created On: {to_created_on_str}')
if from_updated_on:
from_updated_on_str = from_updated_on.isoformat()
log.info(f'From Updated On: {from_updated_on_str}')
if to_updated_on:
to_updated_on_str = to_updated_on.isoformat()
log.info(f'To Updated On: {to_updated_on_str}')
if from_created_on and from_updated_on and to_created_on and to_updated_on:
params['filter'] = f"deleted eq 'False' and (created gt '{from_created_on_str}Z' or lastModified gt '{from_updated_on_str}Z') and (created lt '{to_created_on_str}Z' or lastModified lt '{to_updated_on_str}Z')"
elif from_created_on and to_created_on:
params['filter'] = f"deleted eq 'False' and created gt '{from_created_on_str}Z' and created lt '{to_created_on_str}Z'"
elif from_updated_on and to_updated_on:
params['filter'] = f"deleted eq 'False' and lastModified gt '{from_updated_on_str}Z' and lastModified lt '{to_updated_on_str}Z'"
elif from_created_on:
params['filter'] = f"deleted eq 'False' and created gt '{from_created_on_str}Z'"
elif from_updated_on:
params['filter'] = f"deleted eq 'False' and lastModified gt '{from_updated_on_str}Z'"
else:
log.warning('Created On or Updated On is required. Returning False')
return False
try_request = True
limit = 0
next_token = None
cvent_contact_list = []
while try_request and limit < 100:
limit = limit + 1
if next_token: params['token'] = next_token
resp = requests.get(url=uri, headers=api['headers'], params=params)
response_data = resp.json()
log.debug(json.dumps(response_data, indent=2, default=str))
if 'message' in response_data and response_data['message'] == 'Too Many Requests':
log.warning('Hit Cvent rate limit. Sleeping for .5 seconds...')
time.sleep(.5)
elif 'paging' in response_data:
log.debug(json.dumps(response_data.get('paging'), indent=2, default=str))
if total_count := response_data.get('paging').get('totalCount'):
log.info(f'Found {total_count} Cvent Contact results')
cvent_contact_list = cvent_contact_list + response_data.get('data')
if next_token := response_data.get('paging').get('nextToken'):
log.warning('Sleeping for .25 seconds to avoid Cvent rate limit...')
time.sleep(.25)
else:
try_request = False
else:
log.info('No Cvent Contact results')
try_request = False
next_token = None
return None
else:
log.warning('Unexpected response from Cvent API')
return False
# cvent_contact_list = response_data.get('data')
# next_token = None
# if 'paging' in response_data:
# if total_count := response_data.get('paging').get('totalCount'):
# log.info(f'Found {total_count} Cvent Contact results')
# next_token = response_data.get('paging').get('nextToken')
# log.debug(json.dumps(response_data.get('paging'), indent=2, default=str))
# else:
# log.info('No Cvent Contact results')
# next_token = None
# return None
# limit = 0
# while next_token and limit < 100:
# limit = limit + 1
# params = {}
# params['token'] = next_token
# resp = requests.get(url=uri, headers=api['headers'], params=params)
# response_data = resp.json()
# # log.debug(json.dumps(response_data, indent=2, default=str))
# if 'message' in response_data and response_data['message'] == 'Too Many Requests':
# log.warning('Hit Cvent rate limit. Sleeping for .5 seconds...')
# time.sleep(.5)
# else:
# cvent_contact_list = cvent_contact_list + response_data.get('data')
# if 'paging' in response_data:
# next_token = response_data.get('paging').get('nextToken')
# log.debug(json.dumps(response_data.get('paging'), indent=2, default=str))
# log.warning('Sleeping for .25 seconds to avoid Cvent rate limit...')
# time.sleep(.25)
# else:
# next_token = None
# log.debug(json.dumps(cvent_contact_list, indent=2, default=str))
# log.debug(json.dumps(response_data, indent=2, default=str))
if 'message' in response_data and response_data['message'] == 'Too Many Requests': return False
return cvent_contact_list
# ### END ### API External Cvent Methods ### get_recent_contact_list() ###
# ### BEGIN ### API External Cvent Methods ### get_contact_id() ###
# Updated 2022-02-01
@logger_reset
def get_contact_id(contact_id: str):
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
get_access_token()
endpoint = f'/contacts/{contact_id}'
uri = api['base_url']+endpoint
params = {}
resp = requests.get(url=uri, headers=api['headers'], params=params)
response_data = resp.json()
log.debug(json.dumps(response_data, indent=2, default=str))
if 'message' in response_data and response_data['message'] == 'Too Many Requests': return False
return response_data
# ### END ### API External Cvent Methods ### get_contact_id() ###
# ### BEGIN ### API External Cvent Methods ### modify_contact_id() ###
# Updated 2022-02-01
@logger_reset
def modify_contact_id(contact_id: str, field_list: list=[], custom_field_id: str=None, custom_field_value: str=None):
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
get_access_token()
if custom_field_id:
log.info(f'Updating Cvent Custom Field ID: {custom_field_id}')
endpoint = f'/contacts/{contact_id}/custom-fields/{custom_field_id}/answers'
elif field_list:
log.info(f'Updating Cvent Field List: {field_list}')
endpoint = f'/contacts/{contact_id}'
else:
log.error('Missing required parameters')
return False
uri = api['base_url']+endpoint
params = {}
data = {}
if custom_field_id:
data['id'] = custom_field_id
data['value'] = custom_field_value
else:
data = field_list
data['id'] = contact_id
try_request = True
limit = 0
while try_request and limit < 3:
limit = limit + 1
if custom_field_id:
resp = requests.put(url=uri, headers=api['headers'], params=params, json=data)
else:
resp = requests.patch(url=uri, headers=api['headers'], params=params, json=data)
response_data = resp.json()
log.debug(json.dumps(response_data, indent=2, default=str))
if 'message' in response_data and response_data['message'] == 'Too Many Requests':
log.warning('Hit Cvent rate limit. Sleeping for .5 seconds...')
time.sleep(.5)
else:
log.info('Updated Cvent Contact ID: {contact_id}')
try_request = False
# log.info('Updated Cvent Contact ID: {contact_id}')
# response_data = resp.json()
# log.debug(json.dumps(response_data, indent=2, default=str))
if 'message' in response_data and response_data['message'] == 'Too Many Requests': return False
return response_data
# ### END ### API External Cvent Methods ### modify_contact_id() ###
# ### BEGIN ### API External Cvent Methods ### create_update_aether_person() ###
# Updated 2022-02-02
@logger_reset
def create_update_aether_person(
cvent_contact_id: str,
cvent_contact_obj: dict,
account_id: str,
person_id: str = None,
idaa_refresh_person_group: bool = False,
log_lvl: int = logging.INFO, # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
):
log.setLevel(log_lvl)
# Important variables used more than once.
person_external_id = None
status = None
log.debug(cvent_contact_obj.get('customFields'))
if custom_field_list := cvent_contact_obj.get('customFields'): # List
for custom_field in custom_field_list:
# Get the External ID created by OSIT
if custom_field.get('id') == '609ab766-7d79-4a9d-a72c-f126412659ee':
# person_data['external_id'] = custom_field.get('value')[0]
person_external_id = custom_field.get('value')[0]
# Get the (Contact) Status created by OSIT
if custom_field.get('id') == 'e4e51781-e1ec-4f61-8329-b5d29bec6886':
# person_data['external_id'] = custom_field.get('value')[0]
status = custom_field.get('value')[0].lower()
log.debug(f'Cvent Customer Custom Status: {status}')
if status: pass
else: status = 'unknown' # Will try to improve this guess in membership section below
log.debug(status)
email = cvent_contact_obj.get('email')
membership_person_id = None
membership_person_type_id = None
person_data = {}
person_data['external_id'] = person_external_id
person_data['external_sys_id'] = cvent_contact_obj.get('sourceId')
# person_data['pronouns'] = ???
person_data['informal_name'] = cvent_contact_obj.get('nickname')
person_data['title_names'] = cvent_contact_obj.get('prefix')
person_data['given_name'] = cvent_contact_obj.get('firstName')
person_data['middle_name'] = cvent_contact_obj.get('middleName')
person_data['family_name'] = cvent_contact_obj.get('lastName')
person_data['designations'] = cvent_contact_obj.get('designation')
person_data['professional_title'] = cvent_contact_obj.get('title')
person_data['affiliations'] = cvent_contact_obj.get('company')
person_data['status'] = status
person_data['enable'] = True
log.debug(person_data)
contact_data = {}
contact_data['email'] = cvent_contact_obj.get('email')
contact_data['cc_email'] = cvent_contact_obj.get('ccEmail')
contact_data['phone_mobile'] = cvent_contact_obj.get('mobilePhone')
contact_data['phone_home'] = cvent_contact_obj.get('homePhone')
contact_data['phone_office'] = cvent_contact_obj.get('workPhone')
log.debug(contact_data)
address_data = {}
if cvent_contact_obj.get('homeAddress'):
address_data['line_1'] = cvent_contact_obj.get('homeAddress').get('address1')
address_data['line_2'] = cvent_contact_obj.get('homeAddress').get('address2')
address_data['line_3'] = cvent_contact_obj.get('homeAddress').get('address3')
address_data['city'] = cvent_contact_obj.get('homeAddress').get('city')
address_data['state_province'] = cvent_contact_obj.get('homeAddress').get('region')
if cvent_contact_obj.get('homeAddress').get('countryCode') and cvent_contact_obj.get('homeAddress').get('regionCode'):
# NOTE: There needs to be a better fix for this. Why does Cvent include the 3 character country code only sometimes???
cvent_region_code = cvent_contact_obj.get('homeAddress').get('regionCode').replace('(AUS)', '')
country_subdivision_code = cvent_contact_obj.get('homeAddress').get('countryCode') +'-'+ cvent_region_code
address_data['country_subdivision_code'] = country_subdivision_code
address_data['postal_code'] = cvent_contact_obj.get('homeAddress').get('postalCode')
address_data['country_alpha_2_code'] = cvent_contact_obj.get('homeAddress').get('countryCode')
address_data['country'] = cvent_contact_obj.get('homeAddress').get('country')
address_data['country_alpha_2_code'] = cvent_contact_obj.get('homeAddress').get('countryCode')
log.debug(address_data)
contact_data['address'] = address_data
person_data['contact'] = contact_data
user_data = {}
# user_data['name'] =
user_data['username'] = cvent_contact_obj.get('email')
user_data['email'] = cvent_contact_obj.get('email')
# random_password_string = secrets.token_urlsafe(8)
# user_data['password'] = secure_hash_string(string=random_password_string)
user_data['email_verified'] = True # Assuming this is True because they came from another system where the email address is required.
user_data['enable'] = True
user_data['enable_from'] = datetime.datetime.now(datetime.timezone.utc)
user_data['enable_to'] = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=365)
user_data['super'] = False # This is just not allowed
user_data['manager'] = False # This is just not allowed
# user_data['administrator'] = False
user_data['public'] = False
user_data['verified'] = True
person_data['user'] = user_data
if cvent_contact_obj.get('type'):
# NOTE: Should this be renamed to contact_type_id and contact_type_name???
cvent_contact_type_id = cvent_contact_obj.get('type').get('id')
cvent_contact_type_name = cvent_contact_obj.get('type').get('name')
log.info(f'Found Cvent Contact Type Named: {cvent_contact_type_name}')
else:
cvent_contact_type_id = None
cvent_contact_type_name = None
# 'id': '5EB898D8-C253-482C-A93A-0B6667C26E04', 'name': 'Al-Anon Member'
# 'id': 'A20358C5-0F6C-47AF-9843-BA9483A9D767', 'name': 'Al-Anon Non-Member'
# 'id': 'A01900AB-496A-48A1-9B04-C2874651227E', 'name': 'Member'
# 'id': '65437A15-39C2-4EB5-9AFE-67AF6FE41C27', 'name': 'Non-Member'
# 'id': '03622AEE-F586-4AE5-A191-B8372543A8C8', 'name': 'Student Member'
# 'id': 'A69FAF20-BF2A-4222-B15B-7B0C7EFBEAA7', 'name': 'Student Non-member'
# 'id': '54127B4D-E531-4046-AF5C-0F0D71DC39D2', 'name': 'Adult Guest Registration'
# 'id': 'C9FA7E47-A925-44AB-B94A-9B3003CA2AC4', 'name': 'Attendee'
# 'id': '6F06D6B6-2C23-4EF8-986F-73BF0DB2B229', 'name': "Children's Program with Jerry Moe (7-12 years)"
# 'id': 'AADABEF0-3C84-45A2-9D9B-E2CF585D4AE5', 'name': 'General Attendee'
# 'id': '96D5B3CC-FD4E-4957-BA71-9CEF388095EF', 'name': 'Guest'
# 'id': '71D07118-C24D-4B2E-888D-56AC1B941495', 'name': "IDAA 20's Guest Registration"
# 'id': 'DA17F721-9924-43E3-A31F-C567BA96DC64', 'name': 'IDAA Teen (13-19 years)'
# 'id': 'C49439B3-5AE6-496F-A0AD-4CCB1A9000E3', 'name': 'Spouse/SO Guest Registration'
# Currently in use Member Type names for IDAA:
# Member Type = Member Contact Type
# Al-Anon Member(s) = "Al-Anon Member" and "Al-Anon Non-Member"
# Annual Contribution(s) = "Attendee" and "Non-Member"
# Doctoral Qualifying Member(s) = "Member" and "Non-Member"
# Student Member(s) = "Student Member" and "Student Non-member"
# NOTE: "Student Non-member" is NOT "Student Non-Member" (the lowercase m)
membership_person_data = {}
membership_person_type_data = {}
# if cvent_contact_type_name:
if cvent_contact_obj.get('membership'):
if cvent_contact_type_name == 'Al-Anon Member' or cvent_contact_type_name == 'Al-Anon Members':
membership_person_type_data['membership_type_id'] = 6
membership_person_type_data['product_id'] = 13
membership_person_type_data['level'] = 1
elif cvent_contact_type_name == 'Annual Contribution' or cvent_contact_type_name == 'Annual Contributions' or cvent_contact_type_name == 'Attendee': # Unsure... making affiliate
membership_person_type_data['membership_type_id'] = 8
membership_person_type_data['product_id'] = 13
membership_person_type_data['level'] = 3
elif cvent_contact_type_name == 'Doctoral Qualifying Member' or cvent_contact_type_name == 'Doctoral Qualifying Members' or cvent_contact_type_name == 'Member':
membership_person_type_data['membership_type_id'] = 5
membership_person_type_data['product_id'] = 4
membership_person_type_data['level'] = 1
elif cvent_contact_type_name == 'Student Member' or cvent_contact_type_name == 'Student Members':
membership_person_type_data['membership_type_id'] = 7
membership_person_type_data['product_id'] = 14
membership_person_type_data['level'] = 1
elif cvent_contact_type_name in ['Al-Anon Non-Member', 'Non-Member', 'Student Non-member', 'Student Non-Member']:
membership_person_type_data['membership_type_id'] = 8
membership_person_type_data['product_id'] = 13
membership_person_type_data['level'] = 3
else:
log.error(f'Unknown Contact Type Name from Cvent: {cvent_contact_type_name}')
cvent_contact_type_id = None
cvent_contact_type_name = None
membership_person_type_data['first_start_on'] = datetime.datetime.strptime(cvent_contact_obj.get('membership').get('joined'), '%Y-%m-%d')
if cvent_contact_obj.get('membership').get('lastRenewal'):
membership_person_type_data['start_on'] = datetime.datetime.strptime(cvent_contact_obj.get('membership').get('lastRenewal'), '%Y-%m-%d')
else:
membership_person_type_data['start_on'] = datetime.datetime.strptime(cvent_contact_obj.get('membership').get('joined'), '%Y-%m-%d')
membership_person_type_data['end_on'] = datetime.datetime.strptime(cvent_contact_obj.get('membership').get('expiration'), '%Y-%m-%d')
membership_person_type_data['last_end_on'] = datetime.datetime.strptime(cvent_contact_obj.get('membership').get('expiration'), '%Y-%m-%d')
current_datetime = datetime.datetime.now()
# log.debug(status)
# if status == 'unknown':
# log.debug(current_datetime)
# buffer_datetime = current_datetime - datetime.timedelta(minutes=1440) # 720 min = 12 hours
# log.debug(buffer_datetime)
# log.debug(membership_person_type_data['first_start_on'])
# log.info('Status is unknown. Going to try and make a guess...')
# if membership_person_type_data['first_start_on'] < buffer_datetime and membership_person_type_data['end_on'] >= current_datetime:
# person_data['status'] = 'approved' # approved and current
# elif membership_person_type_data['first_start_on'] < current_datetime and membership_person_type_data['end_on'] < current_datetime:
# person_data['status'] = 'approved' # approved but expired
# else:
# person_data['status'] = 'pending' # likely new?
# log.debug(person_data['status'])
if membership_person_type_data['end_on'] >= current_datetime:
membership_person_type_data['lu_membership_type_status_id'] = 5 # 5 = active; expiration is > now
else:
membership_person_type_data['lu_membership_type_status_id'] = 7 # 7 = inactive; expiration is < now
membership_person_data['enable'] = True
membership_person_type_data['enable'] = True
membership_person_data['membership_person_type'] = membership_person_type_data
# log.debug(json.dumps(membership_person_data, indent=2, default=str))
# Try to look up using external ID
if not person_id and person_external_id:
log.info(f'Looking up person with External ID: {person_external_id}')
if result := get_person_rec_w_external_id(account_id=account_id, external_id=person_external_id):
log.debug(result)
person_id = result.get('person_id')
log.info(f'Person ID {person_id} found using external ID.')
else: pass
# If that fails then try to look up using email address
if not person_id and email:
log.info(f'Looking up person with Email Address: {email}')
if result := get_person_rec_list(for_obj_type='account', for_obj_id=account_id, email=email):
log.debug(result[0])
person_id = result[0].get('person_id')
log.info(f'Person ID {person_id} found using email address.')
else: pass
log.debug(f'Person ID: {person_id}; External ID: {person_external_id}; Email: {email}')
# ### TESTING BREAK POINT ###
# person_data['membership_person'] = membership_person_data
# return False
# ### TESTING BREAK POINT ###
# If there is not an external ID for an existing or new person then one needs to be created.
if not person_external_id:
N = 8
random_string = ''.join(random.choices(string.ascii_uppercase + string.digits, k=N))
random_string = ''.join(random.SystemRandom().choices(string.ascii_uppercase + string.digits, k=N))
# random_string = ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(N))
log.info(f'Created random string: {random_string}')
email = cvent_contact_obj.get('email')
person_external_id = f'{random_string}~{email}'
log.info(f'Created new Person External ID: {person_external_id}')
person_data['external_id'] = person_external_id
# NOTE: Update Cvent Contact
custom_field_id = '609ab766-7d79-4a9d-a72c-f126412659ee' # IDAA Cvent External ID UUID
custom_field_value = [person_external_id]
log.warning('Sleeping for .5 seconds to avoid Cvent rate limit...')
time.sleep(.5)
contact_mod_result = modify_contact_id(
contact_id = cvent_contact_id, # This is the Cvent Contact UUID for a person
# field_list = field_list,
custom_field_id = custom_field_id,
custom_field_value = custom_field_value,
)
if person_id:
# ### SECTION ### Load person object to populate the person_data and membership_person_data dicts.
person_obj = load_person_obj(person_id=person_id, inc_address=True, inc_contact=True, inc_membership_person=True, inc_membership_person_type=True, inc_user=True)
log.debug(person_obj)
person_data['id'] = person_id
person_data['account_id'] = account_id
person_data['contact']['id'] = person_obj.contact.id
person_data['contact']['address']['id'] = person_obj.contact.address.id
if person_obj.user:
person_data['user']['id'] = person_obj.user.id
if person_obj.membership_person:
membership_person_id = person_obj.membership_person.id
if person_obj.membership_person.membership_person_type:
membership_person_type_id = person_obj.membership_person.membership_person_type.id
else: membership_person_type_id = None
if not person_obj.external_id:
person_data['external_id'] = person_external_id
log.debug(person_data)
if update_person_kiss(person_id=person_id, person_dict_obj=person_data, set_default_password=False):
log.info(f'Updated Person ID: {person_id}')
else:
log.info(f'Did not update Person ID: {person_id}')
else:
# ### SECTION ### Create new person and related with the person_data and membership_person_data dicts.
person_data['account_id'] = account_id
person_data['notes'] = 'Created using data from Cvent'
random_password_string = secrets.token_urlsafe(8)
person_data['user']['password'] = secure_hash_string(string=random_password_string)
user_data['notes'] = 'Created using data from Cvent'
# other_data = {}
# other_data['temp_password'] = random_password_string
# person_data['user']['other_json'] = json.dumps(other_data, indent=4)
log.debug(person_data)
if create_person_obj_result := create_person_kiss(account_id=account_id, person_dict_obj=person_data):
person_id = create_person_obj_result
log.info(f'Created Person ID: {person_id}')
else:
log.info(f'Did not create Person')
# ### TESTING BREAK POINT ###
# person_data['membership_person'] = membership_person_data
# return False
# ### TESTING BREAK POINT ###
if cvent_contact_obj.get('membership'):
if cvent_contact_type_id and membership_person_id:
# membership_person_id = person_obj.membership_person.id
membership_person_data['id'] = membership_person_id
membership_person_data['membership_person_type']['id'] = membership_person_type_id
if update_membership_person_obj(membership_person_id=membership_person_id, membership_person_dict_obj=membership_person_data):
log.info(f'Updated Membership Person ID: {membership_person_id}')
else:
log.info(f'Did not update Membership Person ID: {membership_person_id}')
elif cvent_contact_type_id:
if create_membership_person_obj_result := create_membership_person_obj(account_id=account_id, person_id=person_id, membership_person_dict_obj=membership_person_data):
membership_person_id = create_membership_person_obj_result
log.info(f'Created Membership Person ID: {membership_person_id}')
else:
log.info(f'Did not create Membership Person')
else:
membership_person_data = {}
else:
membership_person_data = {}
person_data['membership_person'] = membership_person_data
log.debug(json.dumps(person_data, indent=2, default=str))
if idaa_refresh_person_group:
refresh_person_group(person_id=person_id)
# person_obj = load_person_obj(person_id=person_id, inc_address=True, inc_contact=True, inc_membership_person=True, inc_membership_person_type=True, inc_user=True)
# log.debug(person_obj)
return person_id # True
# ### END ### API External Cvent Methods ### create_update_aether_person() ###

View File

@@ -0,0 +1,512 @@
import datetime, json, pprint, pytz, random, requests, string, time
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, logger_reset
headers = { 'Content-Type': 'application/json;charset=UTF-8' }
app = {}
app['user_email'] = 'oneskyit_integration@oneskyit.com'
app['user_password'] = 'dAPpHDE6d5vLHjsk'
app['name'] = 'IshltOneSkyITLIVE'
app['key'] = '98yp4fa57mJX6nU4'
app['id'] = 'IshltOneSkyITLIVE'
app['password'] = '98yp4fa57mJX6nU4'
log.debug('App data', app)
api = {}
api['base_url'] = 'https://public.impexium.com/Api/v1' # or https://ishlt.mpxapi.com:443/api/v1 ??
api['headers'] = { 'Content-Type': 'application/json;charset=UTF-8' }
api['access_token'] = None
api['access_token_datetime'] = None # This is NOT generated by Impexium
api['app_user_token_datetime'] = None # This is NOT generated by Impexium
# ### BEGIN ### API External Impexium Methods ### get_access_token() ###
# Updated 2022-03-23
@logger_reset
def get_access_token():
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
log.debug(f'App data:\n{app}')
log.debug(f'API data:\n{api}')
endpoint = '/WebApiUrl'
uri = api['base_url']+endpoint
data = { 'AppName': app['name'], 'AppKey': app['key'] }
resp = requests.post(url=uri, json=data, headers=api['headers'])
log.debug(f'Status Code: {resp.status_code}')
log.debug(f'Headers: {resp.headers}')
# log.debug(f'Encoding: {resp.encoding}')
if resp.status_code == 200 and resp.json():
log.info('Got access token. Setting API header and related...')
log.debug(f'JSON: {resp.json()}')
else:
log.warning('No JSON data found')
return False
# log.debug('Text:', resp.text)
response_data = resp.json()
log.debug(json.dumps(response_data, indent=2, default=str))
api['access_token'] = response_data['accessToken']
api['headers']['AccessToken'] = response_data['accessToken']
api['auth_uri'] = response_data['uri']
api['access_token_datetime'] = datetime.datetime.utcnow() # This is NOT generated by Impexium
log.info('Finished setting API access token information.')
log.debug(api)
return True
# ### END ### API External Impexium Methods ### get_access_token() ###
# ### BEGIN ### API External Impexium Methods ### authenticate() ###
# Updated 2022-03-23
@logger_reset
def authenticate():
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if api.get('access_token') and api.get('app_user_token_datetime'):
log.info(f'Found API Access Token and timestamps: Access={api.get("access_token_datetime")}; App/User={api.get("app_user_token_datetime")}')
log.debug(api.get('access_token'))
# log.debug(api.get('access_token_datetime'))
# log.debug(api.get('app_user_token_datetime'))
return True
elif api.get('access_token'): # pass
log.info('Found API Access Token but no timestamp. Need to authenticate.')
log.debug(api.get('access_token'))
else:
log.warning('Access token not found. Calling get_access_token()...')
if result := get_access_token():
log.debug(result)
else:
return False
log.debug(f'App data:\n{app}')
log.debug(f'API data:\n{api}')
data = { 'AppId': app['id'], 'AppPassword': app['password'], 'AppUserEmail': app['user_email'], 'AppUserPassword': app['user_password'] }
try_request = True
max_tries = 5
try_count = 0
while try_request and try_count < max_tries:
try_count = try_count + 1
resp = requests.post(url=api['auth_uri'], json=data, headers=api['headers'])
log.debug(f'Status Code: {resp.status_code}')
log.debug(f'Headers: {resp.headers}')
# log.debug(f'Encoding: {resp.encoding}')
# log.debug('Text:', resp.text)
if resp.status_code == 200:
log.info('Got a result from request')
if resp.json():
# log.debug(f'JSON: {resp.json()}')
response_data = resp.json()
log.debug(json.dumps(response_data, indent=2, default=str))
else:
log.warning('No JSON data found')
response_data = None
try_request = False
elif resp.status_code == 429:
log.warning('Hit rate limit. Sleeping for .1 seconds...')
time.sleep(.1)
try_request = True
else:
log.error('Unexpected result from request')
try_request = False
response_data = False
api['headers']['AppToken'] = response_data['appToken']
api['headers']['UserToken'] = response_data['userToken']
api['base_url'] = response_data['uri']
api['app_user_token_datetime'] = datetime.datetime.utcnow() # This is NOT generated by Impexium
log.debug(api)
return api
# ### END ### API External Impexium Methods ### authenticate() ###
# ### BEGIN ### API External Impexium Methods ### get_custom_fields() ###
# Updated 2022-02-18
@logger_reset
def get_custom_fields(api, name=None, page=1):
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
endpoint = f'/Setup/customfields/{page}'
uri = api['base_url']+endpoint
print('Endpoint URI', uri)
params = {}
resp = requests.get(url=uri, params=params, headers=api['headers'])
log.debug(f'Status Code: {resp.status_code}')
log.debug(f'Headers: {resp.headers}')
# log.debug(f'Encoding: {resp.encoding}')
log.debug(f'JSON: {resp.json()}')
# log.debug('Text:', resp.text)
response_data = resp.json()
custom_field_li = response_data
return custom_field_li
# ### END ### API External Impexium Methods ### get_custom_fields() ###
# ### BEGIN ### API External Impexium Methods ### get_events() ###
# Updated 2022-02-18
@logger_reset
def get_events(page=1):
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
endpoint = f'/Events/All/{page}'
uri = api['base_url']+endpoint
print('Endpoint URI', uri)
params = {}
resp = requests.get(url=uri, params=params, headers=api['headers'])
log.debug(f'Status Code: {resp.status_code}')
log.debug(f'Headers: {resp.headers}')
# log.debug(f'Encoding: {resp.encoding}')
log.debug(f'JSON: {resp.json()}')
# log.debug('Text:', resp.text)
response_data = resp.json()
# pp = pprint.PrettyPrinter(indent=2)
# pp.pprint(response_data)
# print('**************************')
event_li = response_data
return event_li
# ### END ### API External Impexium Methods ### get_events() ###
# ### BEGIN ### API External Impexium Methods ### get_event_registrants() ###
# Updated 2021-10-07
@logger_reset
def get_event_registrants(
event_code: str,
registered_since: datetime.datetime = None,
details: bool = False,
page: int = 0,
# return_all: bool = False
):
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if result := authenticate():
log.debug(result)
else:
return False
try_page = True
if page > 0: # Only get the specific page
page_num = page
max_page = page
else: # Get all of the pages
page = 1
page_num = 0 # Will actually be the first page
max_page = 45 # This may need to be increased again... was 15 then 25
impexium_event_registration_list = []
while try_page and page_num <= max_page:
page_num = page_num + 1
log.info(f'Getting page number {page_num} from Impexium... Event Code: {event_code}; Details: {details}; Registered Since: {registered_since}')
endpoint = f'/Events/{event_code}/Registrations/{page_num}'
uri = api['base_url']+endpoint
params = { 'includeDetails': details }
if registered_since:
params['registeredSince'] = registered_since.isoformat()
log.debug(params)
try_request = True
max_tries = 5
try_count = 0
while try_request and try_count <= max_tries:
try_count = try_count + 1
resp = requests.get(url=uri, params=params, headers=api['headers'])
log.debug(f'Status Code: {resp.status_code}')
log.debug(f'Headers: {resp.headers}')
# log.debug(f'Encoding: {resp.encoding}')
# log.debug('Text:')
# log.debug(resp.text)
if resp.status_code == 200:
response_data = resp.json()
current_page = response_data.get('pageNumber')
log.info(f'Impexium response Current Page: {current_page}')
impexium_event_registration_list = impexium_event_registration_list + response_data.get('dataList')
try_request = False
elif resp.status_code == 404:
log.info('No results returned.')
try_request = False
try_page = False
elif resp.status_code == 429:
log.warning('Hit rate limit. Sleeping for .1 seconds...')
time.sleep(.1)
try_request = True
response_data = False
else:
log.info('Not trying again')
try_request = False
try_page = False
impexium_event_registration_list = False
log.warning('Something may have gone wrong. Setting the API app_user_token_datetime value to None to re-authenticate with Impexium on the next request.')
api['app_user_token_datetime'] = None # Resetting this just in case the App and or User token expired.
return impexium_event_registration_list
# ### END ### API Impexium Methods ### get_event_registrants() ###
# ### BEGIN ### API External Impexium Methods ### get_individual_profile() ###
# Updated 2022-04-22
# NOTE: Without details the results are very basic. Pretty much just their name.
# Including details adds the addresses, customFields, emails, memberships, phones, etc
@logger_reset
def get_individual_profile(
individual_id: str,
details: bool = False,
page = 1 # page: int = 0,
):
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if result := authenticate():
log.debug(result)
else:
return False
endpoint = f'/Individuals/Profile/{individual_id}/{page}'
uri = api['base_url']+endpoint
params = { 'IncludeDetails': details }
impexium_individual_profile = None
try_request = True
max_tries = 5
try_count = 0
while try_request and try_count <= max_tries:
try_count = try_count + 1
resp = requests.get(url=uri, params=params, headers=api['headers'])
log.debug(f'Status Code: {resp.status_code}')
log.debug(f'Headers: {resp.headers}')
# log.debug(f'Encoding: {resp.encoding}')
# log.debug('Text:')
# log.debug(resp.text)
# log.debug(resp.json())
if resp.status_code == 200:
log.info('Status 200')
log.debug(resp.json())
impexium_individual_profile_raw = resp.json() # .get('data').get('dataList')[0]
# log.debug(impexium_individual_profile_raw)
impexium_individual_profile = impexium_individual_profile_raw.get('dataList')[0]
log.debug(impexium_individual_profile)
try_request = False
elif resp.status_code == 404:
log.info('No results returned (status 404)')
try_request = False
impexium_individual_profile = None
elif resp.status_code == 429:
log.warning('Hit rate limit. Sleeping for .1 seconds...')
time.sleep(.1)
try_request = True
impexium_individual_profile = False
else:
log.info('Not trying again')
try_request = False
impexium_event_registration_list = False
log.warning('Something may have gone wrong. Setting the API app_user_token_datetime value to None to re-authenticate with Impexium on the next request.')
api['app_user_token_datetime'] = None # Resetting this just in case the App and or User token expired.
return impexium_individual_profile
# ### END ### API External Impexium Methods ### get_individual_profile() ###
# ### BEGIN ### API External Impexium Methods ### get_individual_registrations() ###
# Updated 2022-02-18
@logger_reset
def get_individual_registrations(api, record_number, event_code=None, page=1):
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
endpoint = f'/Individuals/{record_number}/Registrations/{page}'
uri = api['base_url']+endpoint
params = { 'eventCode': event_code }
resp = requests.get(url=uri, params=params, headers=api['headers'])
log.debug(f'Status Code: {resp.status_code}')
log.debug(f'Headers: {resp.headers}')
# log.debug(f'Encoding: {resp.encoding}')
log.debug(f'JSON: {resp.json()}')
# log.debug('Text:', resp.text)
response_data = resp.json()
# pp = pprint.PrettyPrinter(indent=2)
# pp.pprint(response_data)
# print('**************************')
individual_registraion_li = response_data
return individual_registraion_li
# ### END ### API External Impexium Methods ### get_individual_registrations() ###
# ### BEGIN ### API External Impexium Methods ### get_individual_purchases() ###
# Updated 2022-02-18
@logger_reset
def get_individual_purchases(api, record_number, product_code=None, purchased_since=None, product_category_code=None, page=1):
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
endpoint = f'/Individuals/{record_number}/Purchases/{page}'
uri = api['base_url']+endpoint
params = { 'productCode': product_code, 'purchasedSince': purchased_since, 'productCategoryCode': product_category_code }
resp = requests.get(url=uri, params=params, headers=api['headers'])
log.debug(f'Status Code: {resp.status_code}')
log.debug(f'Headers: {resp.headers}')
# log.debug(f'Encoding: {resp.encoding}')
log.debug(f'JSON: {resp.json()}')
# log.debug('Text:', resp.text)
response_data = resp.json()
# pp = pprint.PrettyPrinter(indent=2)
# pp.pprint(response_data)
# print('**************************')
individual_purchase_li = response_data
return individual_purchase_li
# ### END ### API External Impexium Methods ### get_individual_purchases() ###
# ### BEGIN ### API External Impexium Methods ### get_individual_custom_fields() ###
# Updated 2022-03-23
@logger_reset
def get_individual_custom_fields(
individual_id
):
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
endpoint = f'/Individuals/{individual_id}/CustomFields'
uri = api['base_url']+endpoint
params = { }
impexium_individual_custom_field_list = None
try_request = True
max_tries = 5
try_count = 0
while try_request and try_count <= max_tries:
try_count = try_count + 1
resp = requests.get(url=uri, params=params, headers=api['headers'])
log.debug(f'Status Code: {resp.status_code}')
log.debug(f'Headers: {resp.headers}')
# log.debug(f'Encoding: {resp.encoding}')
# log.debug('Text:')
# log.debug(resp.text)
if resp.status_code == 200:
log.debug(resp.json)
impexium_individual_custom_field_list = resp.json()
try_request = False
elif resp.status_code == 404:
log.info('No results returned.')
try_request = False
impexium_individual_custom_field_list = []
elif resp.status_code == 429:
log.warning('Hit rate limit. Sleeping for .1 seconds...')
time.sleep(.1)
try_request = True
impexium_individual_custom_field_list = False
else:
try_request = False
impexium_event_registration_list = False
return impexium_individual_custom_field_list
# ### END ### API External Impexium Methods ### get_individual_custom_fields() ###
# ### BEGIN ### API External Impexium Methods ### get_individual_orders_open() ###
# Updated 2022-02-18
@logger_reset
def get_individual_orders_open(api, record_number, include_line_items=False, from_date=None, to_date=None, page=1):
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
endpoint = f'/Individuals/{record_number}/Orders/Open/{page}'
uri = api['base_url']+endpoint
print(uri)
params = { 'includeLineItems': include_line_items, 'fromDate': from_date, 'toDate': to_date }
resp = requests.get(url=uri, params=params, headers=api['headers'])
log.debug(f'Status Code: {resp.status_code}')
log.debug(f'Headers: {resp.headers}')
# log.debug(f'Encoding: {resp.encoding}')
log.debug(f'JSON: {resp.json()}')
# log.debug('Text:', resp.text)
response_data = resp.json()
# pp = pprint.PrettyPrinter(indent=2)
# pp.pprint(response_data)
# print('**************************')
individual_orders_open_li = response_data
return individual_orders_open_li
# ### END ### API External Impexium Methods ### get_individual_orders_open() ###

View File

@@ -0,0 +1,514 @@
import datetime, json
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_delete, sql_insert, sql_select, sql_update, sql_enable_part, sql_limit_offset_part
from app.lib_general import log, logging, logger_reset
# from app.methods.event_abstract_tracking_methods import get_event_abstract_tracking_rec_list, load_event_abstract_tracking_obj
from app.methods.event_person_methods import load_event_person_obj
from app.models.common_field_schema import default_num_bytes
from app.models.event_abstract_models import Event_Abstract_Ext, Event_Abstract_In
from app.methods.event_cfg_methods import load_event_cfg_obj
# ### BEGIN ### API Event Abstract Methods ### load_event_abstract_obj() ###
# Updated 2023-03-20
@logger_reset
def load_event_abstract_obj(
event_abstract_id: int|str,
enabled: str = 'enabled', # enabled, disabled, all
approved: str = 'all', # approved, not_approved, all
hidden: str = 'not_hidden', # hidden, not_hidden, all
review: str = 'all', # ready, not_ready, all
inc_event_cfg: bool = False,
inc_event_file_list: bool = False,
inc_event_person: bool = False,
inc_event_person_profile: bool = False,
inc_event_presentation_list: bool = False,
inc_event_presenter_list: bool = False,
inc_hosted_file: bool = False,
limit: int = 1500,
offset: int = 0,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
) -> Event_Abstract_Ext|dict|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
# if event_abstract_id := redis_lookup_id_random(record_id_random=event_abstract_id, table_name='event_abstract'): pass
# else: return False
if event_abstract_rec := sql_select(table_name='v_event_abstract', record_id=event_abstract_id): pass
else: return False
try:
event_abstract_obj = Event_Abstract_Ext(**event_abstract_rec)
log.debug(event_abstract_obj)
except ValidationError as e:
log.error(e.json())
return False
# Updated 2023-06-28
if inc_event_cfg:
log.info('Need to include event configuration...')
if event_cfg_result := load_event_cfg_obj(
event_id = event_abstract_obj.event_id,
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
):
event_abstract_obj.event_cfg = event_cfg_result
else: event_abstract_obj.event_cfg = {} # None
log.debug(event_abstract_obj.event_cfg)
# Updated 2023-03-20
if inc_event_file_list:
# log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.info('Need to include event file list...')
from app.methods.event_file_methods import get_event_file_rec_list, load_event_file_obj
if event_file_rec_list_result := get_event_file_rec_list(
for_type = 'event_abstract',
for_id = event_abstract_id,
# file_purpose_id = event_file_file_purpose_id,
# file_purpose = event_file_file_purpose,
# priority = event_file_priority,
# group = event_file_group,
enabled = enabled,
limit = limit,
):
event_file_result_list = []
for event_file_rec in event_file_rec_list_result:
if load_event_file_result := load_event_file_obj(
event_file_id = event_file_rec.get('event_file_id', None),
enabled = enabled,
inc_hosted_file = inc_hosted_file,
# model_as_dict = True,
# by_alias = by_alias,
# exclude_unset = False,
):
event_file_result_list.append(load_event_file_result)
else:
event_file_result_list.append(None)
log.debug(event_file_result_list)
event_abstract_obj.event_file_list = event_file_result_list
elif isinstance(event_file_rec_list_result, list):
event_abstract_obj.event_file_list = []
else:
event_abstract_obj.event_file_list = None
# Updated 2023-03-20
if inc_event_person:
# log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.info('Need to include event person...')
if event_person_obj := load_event_person_obj(
event_person_id = event_abstract_obj.event_person_id,
enabled = enabled,
# inc_address = inc_address,
# inc_contact = inc_contact,
# inc_event_abstract = inc_event_abstract,
inc_event_person_profile = inc_event_person_profile,
# inc_event_registration = inc_event_registration,
# inc_person = inc_person,
# inc_user = inc_user,
):
log.debug(event_person_obj)
event_abstract_obj.event_person = event_person_obj
else:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_person_obj)
event_abstract_obj.event_person = None
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
# Updated 2023-03-20
if inc_event_presenter_list:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.info('Need to include Event Presenter List data...')
if event_presenter_rec_list_result := get_event_presenter_rec_list(
event_abstract_id = event_abstract_id,
limit = limit,
enabled = enabled,
hidden = hidden,
):
event_presenter_result_list = []
for event_presenter_rec in event_presenter_rec_list_result:
event_presenter_result_list.append(
load_event_presenter_obj(
event_presenter_id = event_presenter_rec.get('event_presenter_id'),
inc_event_person = inc_event_person,
)
)
event_abstract_obj.event_abstract_list = event_abstract_result_list
else: event_abstract_obj.event_abstract_list = []
# log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
if model_as_dict:
return event_abstract_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return event_abstract_obj
# ### END ### API Event Abstract Methods ### load_event_abstract_obj() ###
# ### BEGIN ### API Event Abstract Methods ### get_event_abstract_rec_list() ###
# Updated 2023-03-20
@logger_reset
def get_event_abstract_rec_list(
event_id: None|str = None,
event_person_id: None|str = None,
abstract_type_code: str = None, # not sure yet
approved: str = 'all', # not_approved, approved, all
enabled: str = 'enabled', # enabled, disabled, all
limit: int = 250,
offset: int = 0,
) -> list|bool:
log.setLevel(logging.INFO) # 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
elif event_id is None: pass
else: return False
if event_person_id := redis_lookup_id_random(record_id_random=event_person_id, table_name='event_person'): pass
elif event_person_id is None: pass
else: return False
data = {}
# data['event_id'] = event_id
if event_id:
data['event_id'] = event_id
sql_event_id = f'`event_abstract`.event_id = :event_id'
else: sql_event_id = '1=1'
if event_person_id:
data['event_person_id'] = event_person_id
sql_event_person_id = f'AND `event_abstract`.event_person_id = :event_person_id'
else: sql_event_person_id = ''
if abstract_type_code:
data['abstract_type_code'] = abstract_type_code
sql_abstract_type_code = f'AND `event_abstract`.abstract_type_code = :abstract_type_code'
else: sql_abstract_type_code = ''
if approved in ['not_approved', 'approved', 'all']:
log.info(f'Creating partial SQL string for "approved" check. Printed: {approved}')
if approved == 'not_approved':
sql_abstract_approved = f'AND (`event_abstract`.approve IS NULL OR `event_abstract`.approved = FALSE)'
elif approved == 'approved':
sql_abstract_approved = f'AND `event_abstract`.approve = TRUE'
elif approved == 'all':
sql_abstract_approved = f'AND (`event_abstract`.approve IS NULL OR `event_abstract`.approve IS NOT NULL)'
log.debug(sql_abstract_approved)
else: sql_abstract_approved = ''
sql_enabled, data['enable'] = sql_enable_part(table_name='event_abstract', enabled=enabled) # Reasonably safe return str and bool
sql_limit = sql_limit_offset_part(limit=limit, offset=offset) # Reasonably safe return str
sql = f"""
SELECT `event_abstract`.id AS 'event_abstract_id', `event_abstract`.id_random AS 'event_abstract_id_random'
FROM `v_event_abstract` AS `event_abstract`
WHERE
{sql_event_id}
{sql_event_person_id}
{sql_abstract_type_code}
{sql_abstract_approved}
{sql_enabled}
ORDER BY event_abstract.priority DESC, event_abstract.sort DESC, event_abstract.name ASC, `event_abstract`.created_on DESC, `event_abstract`.updated_on DESC
{sql_limit};
"""
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(sql)
if event_abstract_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
event_abstract_rec_li = event_abstract_rec_li_result
else: # [] or False
event_abstract_rec_li = event_abstract_rec_li_result
log.debug(event_abstract_rec_li_result)
return event_abstract_rec_li
# ### END ### API Event Abstract Methods ### get_event_abstract_rec_list() ###
# ### BEGIN ### API Event Abstract Methods ### create_update_event_abstract_obj() ###
# Updated 2023-03-22
@logger_reset
def create_update_event_abstract_obj(
event_abstract_obj: Event_Abstract_In,
event_abstract_id: int = None,
event_id: int = None,
event_person_id: int = None,
create_sub_obj: bool = False,
fail_any: bool = False, # Fail if any thing goes wrong for sub objects
return_outline: bool = False,
) -> int|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
log.info('Checking requirements...')
if event_abstract_id:
log.info(f'Got: event_abstract_id={event_abstract_id}; Update existing Event Abstract')
event_abstract_obj.id = event_abstract_id
elif event_id and event_person_id:
log.info(f'Got: event_id={event_id}; event_person_id={event_person_id}; Create Event Abstract')
event_abstract_obj.event_id = event_id
event_abstract_obj.event_person_id = event_person_id
else:
return False
log.debug(type(event_abstract_obj))
log.debug(event_abstract_obj
)
event_abstract_dict = event_abstract_obj.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'event_file_list', 'event_person', 'event_person_list', 'event_presenter_list', 'created_on', 'updated_on'})
# log.debug(type(event_abstract_dict.get('topics_json', None)))
# if 'topics_json' in event_abstract_dict and (isinstance(event_abstract_dict['topics_json'], dict) or isinstance(event_abstract_dict['topics_json'], list)):
# log.debug('Need to convert to JSON string')
# event_abstract_dict['topics_json'] = json.dumps(event_abstract_dict['topics_json'])
# log.debug(event_abstract_dict)
if event_abstract_id:
if event_abstract_dict_up_result := sql_update(data=event_abstract_dict, table_name='event_abstract', rm_id_random=True):
log.info(f'Event Abstract updated. event_abstract_id={event_abstract_id}')
pass
else:
log.warning(f'Event Abstract not updated. event_abstract_id={event_abstract_id}')
log.debug(event_abstract_dict_up_result)
return False
log.debug(event_abstract_dict_up_result)
else:
if event_abstract_dict_in_result := sql_insert(data=event_abstract_dict, table_name='event_abstract', rm_id_random=True, id_random_length=None):
log.info(f'Event Abstract created. event_abstract_id={event_abstract_dict_in_result}')
else:
log.warning(f'Event Abstract not created.')
log.debug(event_abstract_dict_in_result)
return False
log.debug(event_abstract_dict_in_result)
event_abstract_id = event_abstract_dict_in_result # False, None, integer
event_abstract_outline = {}
event_abstract_outline['event_id'] = event_id
event_abstract_outline['event_abstract_id'] = event_abstract_id
event_abstract_outline['event_person_id'] = event_person_id
# event_abstract_outline['event_file_count'] = event_file_count
if return_outline:
log.debug(f'Returning the Event Abstract Outline: {event_abstract_outline}')
return event_abstract_outline
else:
log.debug(f'Returning the Event Abstract ID: {event_abstract_id}')
return event_abstract_id
# ### END ### API Event Abstract Methods ### create_update_event_abstract_obj() ###
# ### BEGIN ### API Event Abstract Methods ### create_update_event_abstract_obj_old() ###
# Updated 2023-03-20
@logger_reset
def create_update_event_abstract_obj_old(
event_abstract_dict_obj: Event_Abstract_In|dict,
event_abstract_id: int|str = None,
event_id: int|str = None,
event_person_id: int|str = None,
create_sub_obj: bool = False,
fail_any: bool = False, # Fail if any thing goes wrong for sub objects
return_outline: bool = False,
) -> int|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
log.info('Checking requirements...')
if event_abstract_id:
log.info(f'Event Abstract ID passed. Update existing Event Abstract. Event Abstract ID: {event_abstract_id}')
if event_abstract_id := redis_lookup_id_random(record_id_random=event_abstract_id, table_name='event_abstract'): pass
else:
log.error('Event Abstract ID passed but is invalid. Failed requirement.')
return False
if event_id := redis_lookup_id_random(record_id_random=event_id, table_name='event'):
log.info(f'Event ID: {event_id}')
elif event_id is None:
log.error('Missing Event ID. Not required. Ignoring.')
log.info(f'Event ID: {event_id}')
else:
log.error('Invalid Event ID passed. Not required. But not ignoring since it is likely invalid.')
log.info(f'Event ID: {event_id}')
return False
else:
log.info('No Event Abstract ID passed. Create new Event Abstract. Required: Event ID')
if event_id := redis_lookup_id_random(record_id_random=event_id, table_name='event'):
log.info(f'Event ID: {event_id}')
elif event_id is None:
log.error('Missing Event ID. Failed requirement.')
log.info(f'Event ID: {event_id}')
return False
else:
log.error('Invalid Event ID passed. Failed requirement.')
log.info(f'Event ID: {event_id}')
return False
# else:
# log.error('Missing or invalid Event ID passed. Failed requirement.')
# log.info(f'Event ID: {event_id}')
# return False
log.debug(type(event_abstract_dict_obj))
if isinstance(event_abstract_dict_obj, dict):
event_abstract_dict = event_abstract_dict_obj
if event_abstract_id:
event_abstract_dict['event_abstract_id'] = event_abstract_id
if event_id:
event_abstract_dict['event_id'] = event_id
if event_person_id:
event_abstract_dict['event_person_id'] = event_person_id
try:
event_abstract_obj = Event_Abstract_In(**event_abstract_dict)
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_abstract_obj)
except ValidationError as e:
log.error(e.json())
return False
else:
event_abstract_obj = event_abstract_dict_obj
if event_abstract_id:
# NOTE: Can't update the ID alias if it was never set.
event_abstract_obj.id = event_abstract_id
if event_id:
event_abstract_obj.event_id = event_id
if event_person_id:
event_abstract_obj.event_person_id = event_person_id
event_abstract_dict = event_abstract_obj.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'event_person', 'event_person_list', 'created_on', 'updated_on'})
if event_abstract_id:
if event_abstract_dict_up_result := sql_update(data=event_abstract_dict, table_name='event_abstract', rm_id_random=True): pass
else:
log.warning(f'Event Abstract not updated. Event Abstract ID: {event_abstract_id}')
log.debug(event_abstract_dict_up_result)
return False
log.debug(event_abstract_dict_up_result)
else:
if event_abstract_dict_in_result := sql_insert(data=event_abstract_dict, table_name='event_abstract', rm_id_random=True, id_random_length=None): pass
else:
log.warning(f'Event Abstract not created.')
log.debug(event_abstract_dict_in_result)
return False
log.debug(event_abstract_dict_in_result)
event_abstract_id = event_abstract_dict_in_result
event_abstract_outline = {}
event_abstract_outline['event_id'] = event_id
event_abstract_outline['event_abstract_id'] = event_abstract_id
event_abstract_outline['event_person_id'] = event_person_id
# event_abstract_outline['event_presenter_list'] = []
# if event_abstract_obj.event_presenter_list and isinstance(event_abstract_obj.event_presenter_list, list):
# log.info(f'Event Presenter List was found. Loop through and create a new Event Presenter for each and link them to the new Event Abstract. Event Abstract ID: {event_abstract_id}')
# for event_presenter_obj in event_abstract_obj.event_presenter_list:
# # NOTE: Use object model version because of better type checking and validations
# log.debug(event_presenter_obj)
# if event_presenter_id := event_presenter_obj.id: pass
# else: event_presenter_id = None
# # event_presenter_obj.event_id = event_id
# # event_presenter_obj.event_session_id = event_session_id
# create_update_event_presenter_obj_result = create_update_event_presenter_obj_v4(
# event_presenter_dict_obj = event_presenter_obj,
# event_presenter_id = event_presenter_id,
# event_id = event_id,
# event_session_id = event_session_id,
# event_abstract_id = event_abstract_id,
# fail_any = fail_any,
# return_outline = return_outline,
# )
# if isinstance(create_update_event_presenter_obj_result, int):
# event_presenter_id = create_update_event_presenter_obj_result
# elif create_update_event_presenter_obj_result == True: pass
# else:
# log.warning(f'Create or Update failed while trying create_update_event_presenter_obj_v4(): {create_update_event_presenter_obj_result}')
# event_presenter_id = None
# event_abstract_outline['event_presenter_id'] = event_presenter_id
if return_outline:
log.debug(f'Returning the Event Abstract Outline: {event_abstract_outline}')
return event_abstract_outline
else:
log.debug(f'Returning the Event Abstract ID: {event_abstract_id}')
return event_abstract_id
# ### END ### API Event Abstract Methods ### create_update_event_abstract_obj_old() ###
# ### BEGIN ### API Event Abstract Methods ### remove_event_abstract_obj() ###
# Updated 2023-03-22
@logger_reset
def remove_event_abstract_obj(
event_abstract_id: int,
method: None|str = None,
log_lvl: int = logging.INFO, # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
) -> bool|None:
log.setLevel(log_lvl)
if method is None or method == 'disable':
data = {'enable': False}
if event_abstract_dict_up_result := sql_update(
table_name = 'event_abstract',
record_id = event_abstract_id,
data = data,
log_lvl = log_lvl,
):
log.info(f'Event Abstract was disabled.')
return True
else:
log.warning(f'Event Abstract not disabled.')
return event_abstract_dict_up_result # False or None
elif method == 'delete':
if event_abstract_dict_del_result := sql_delete(
table_name = 'event_abstract',
record_id = event_abstract_id,
log_lvl = log_lvl,
):
log.info(f'Event Abstract was deleted.')
return True
else:
log.warning(f'Event Abstract not deleted.')
return event_abstract_dict_del_result # False or None
elif method == 'hide':
data = {'hide': True}
if event_abstract_dict_up_result := sql_update(
table_name = 'event_abstract',
record_id = event_abstract_id,
data = data,
log_lvl = log_lvl,
):
log.info(f'Event Abstract was hidden.')
return True
else:
log.warning(f'Event Abstract not hidden.')
return event_abstract_dict_up_result # False or None
else:
log.error('We should not be here. Something went wrong in remove_event_abstract_obj()!')
return False
# ### END ### API Event Abstract Methods ### remove_event_abstract_obj() ###

View File

@@ -0,0 +1,438 @@
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_enable_part, sql_insert, sql_limit_offset_part, sql_select, sql_update
from app.lib_general import log, logging, logger_reset, send_email
# from app.methods.account_cfg_methods import load_account_cfg_obj
# from app.methods.event_methods import load_event_obj
# from app.methods.event_badge_methods import load_event_badge_obj
from app.methods.event_badge_template_methods import load_event_badge_template_obj
from app.models.event_badge_models import Event_Badge_Base, Event_Badge_Basic_Base
# ### BEGIN ### API Event Badge Methods ### load_event_badge_obj() ###
# Updated 2022-03-14
@logger_reset
def load_event_badge_obj(
event_badge_id: int|str,
badge_only: bool = False, # Changes the SQL view used
limit: int = 1000,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
enabled: str = 'enabled', # enabled, disabled, all
inc_event_badge_template: bool = False,
return_basic_model: bool = False,
) -> Event_Badge_Base|dict|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if event_badge_id := redis_lookup_id_random(record_id_random=event_badge_id, table_name='event_badge'): pass
else: return False
if badge_only:
if event_badge_rec := sql_select(table_name='v_event_badge_only', record_id=event_badge_id): pass
else: return False
else:
if event_badge_rec := sql_select(table_name='v_event_badge', record_id=event_badge_id): pass
else: return False
if return_basic_model:
try:
event_badge_obj = Event_Badge_Basic_Base(**event_badge_rec)
except ValidationError as e:
log.error(e.json())
return False
else:
try:
event_badge_obj = Event_Badge_Base(**event_badge_rec)
except ValidationError as e:
log.error(e.json())
return False
log.debug(event_badge_obj)
if return_basic_model:
if event_badge_obj.pronouns_override:
event_badge_obj.pronouns = event_badge_obj.pronouns_override
event_badge_obj.pronouns_override = None
if event_badge_obj.professional_title_override:
event_badge_obj.professional_title = event_badge_obj.professional_title_override
event_badge_obj.professional_title_override = None
# log.debug(event_badge_obj)
if event_badge_obj.full_name_override:
event_badge_obj.full_name = event_badge_obj.full_name_override
event_badge_obj.full_name_override = None
if event_badge_obj.affiliations_override:
event_badge_obj.affiliations = event_badge_obj.affiliations_override
event_badge_obj.affiliations_override = None
if event_badge_obj.email_override:
event_badge_obj.email = event_badge_obj.email_override
event_badge_obj.email_override = None
if event_badge_obj.phone_override:
event_badge_obj.phone = event_badge_obj.phone_override
event_badge_obj.phone_override = None
if event_badge_obj.location_override:
event_badge_obj.location = event_badge_obj.location_override
event_badge_obj.location_override = None
log.debug(event_badge_obj.dict(by_alias=True, exclude_unset=True, exclude={'pronouns_override', 'professional_title_override', 'full_name_override', 'affiliations_override', 'email_override', 'phone_override', 'location_override'}))
event_badge_dict = event_badge_obj.dict(by_alias=True, exclude_unset=True, exclude={'pronouns_override', 'professional_title_override', 'full_name_override', 'affiliations_override', 'email_override', 'phone_override', 'location_override'})
try:
event_badge_obj = Event_Badge_Basic_Base(**event_badge_dict)
except ValidationError as e:
log.error(e.json())
return False
log.debug(event_badge_obj)
# Updated 2022-03-14
if inc_event_badge_template:
log.info('Need to include event badge template data...')
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
if event_badge_template_id := event_badge_rec.get('event_badge_template_id', None): pass
else:
event_id = event_badge_rec.get('event_id', None)
event_badge_template_id = get_event_badge_template_id_w_event_id(event_id=event_id)
if event_badge_template_result := load_event_badge_template_obj(
event_badge_template_id = event_badge_template_id
):
log.debug(event_badge_template_result)
event_badge_obj.event_badge_template = event_badge_template_result
else:
log.warning('A event_badge object was not returned.')
event_badge_obj.event_badge_template = None
if model_as_dict:
return event_badge_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return event_badge_obj
# ### END ### API Event Badge Methods ### load_event_badge_obj() ###
# ### BEGIN ### API Event Badge Methods ### get_event_badge_rec_list() ###
# Updated 2022-06-14
@logger_reset
def get_event_badge_rec_list(
event_id: str,
badge_only: bool = False, # Changes the SQL view used
badge_type_code: str = None, # guest, member, non-member, staff, volunteer, custom per event
printed: str = 'not_printed', # not_printed, printed, all
enabled: str = 'enabled', # enabled, disabled, all
limit: int = 250,
offset: int = 0,
) -> list|bool:
log.setLevel(logging.INFO) # 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
data = {}
data['event_id'] = event_id
if badge_type_code:
data['badge_type_code'] = badge_type_code
sql_badge_type_code = f'AND `event_badge`.badge_type_code = :badge_type_code'
else: sql_badge_type_code = ''
if printed in ['not_printed', 'printed', 'all']:
log.info(f'Creating partial SQL string for "printed" check. Printed: {printed}')
if printed == 'not_printed':
sql_badge_printed = f'AND (`event_badge`.print_count IS NULL OR `event_badge`.print_count = 0)'
# printed = False
elif printed == 'printed':
sql_badge_printed = f'AND `event_badge`.print_count > 0'
# printed = True
elif printed == 'all':
sql_badge_printed = f'AND (`event_badge`.print_count IS NULL OR `event_badge`.print_count IS NOT NULL)'
# printed = None
log.debug(sql_badge_printed)
else: sql_badge_printed = ''
sql_enabled, data['enable'] = sql_enable_part(table_name='event_badge', enabled=enabled) # Reasonably safe return str and bool
sql_limit = sql_limit_offset_part(limit=limit, offset=offset) # Reasonably safe return str
if badge_only:
sql = f"""
SELECT `event_badge`.id AS 'event_badge_id', `event_badge`.id_random AS 'event_badge_id_random'
FROM `v_event_badge_only` AS `event_badge`
WHERE
`event_badge`.event_id = :event_id
{sql_badge_type_code}
{sql_badge_printed}
{sql_enabled}
ORDER BY event_badge.family_name ASC, event_badge.given_name ASC, `event_badge`.created_on DESC, `event_badge`.updated_on DESC
{sql_limit};
"""
else:
sql = f"""
SELECT `event_badge`.id AS 'event_badge_id', `event_badge`.id_random AS 'event_badge_id_random'
FROM `v_event_badge` AS `event_badge`
WHERE
`event_badge`.event_id = :event_id
{sql_badge_type_code}
{sql_enabled}
ORDER BY event_badge.family_name ASC, event_badge.given_name ASC, `event_badge`.created_on DESC, `event_badge`.updated_on DESC
{sql_limit};
"""
log.debug(sql)
if event_badge_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
event_badge_rec_li = event_badge_rec_li_result
else: # [] or False
event_badge_rec_li = event_badge_rec_li_result
log.debug(event_badge_rec_li_result)
return event_badge_rec_li
# ### END ### API Event Badge Methods ### get_event_badge_rec_list() ###
# ### BEGIN ### API Event Badge Methods ### get_event_badge_template_id_w_event_id() ###
# Updated 2021-09-14
@logger_reset
def get_event_badge_template_id_w_event_id(
event_id: int|str,
) -> bool|int|None:
log.setLevel(logging.INFO) # 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
data = {}
data['event_id'] = event_id
sql = f"""
SELECT `event_badge_template`.id AS 'event_badge_template_id', `event_badge_template`.id_random AS 'event_badge_template_id_random', `event_badge_template`.event_id AS event_id
FROM `event_badge_template` AS `event_badge_template`
WHERE `event_badge_template`.event_id = :event_id
LIMIT 1;
"""
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
if event_badge_template_data_result := sql_select(data=data, sql=sql):
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_badge_template_data_result)
if event_badge_template_id := event_badge_template_data_result.get('event_badge_template_id', None): return event_badge_template_id
else: return False
else: return None
# ### END ### API Event Badge Methods ### get_event_badge_template_id_w_event_id() ###
# ### BEGIN ### API Event Badge Methods ### get_event_person_id_w_event_badge_id() ###
# Updated 2022-04-08
@logger_reset
def get_event_person_id_w_event_badge_id(
event_badge_id: int|str,
) -> bool|int|None:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if event_badge_id := redis_lookup_id_random(record_id_random=event_badge_id, table_name='event_badge'): pass
else: return False
data = {}
data['event_badge_id'] = event_badge_id
sql = f"""
SELECT `event_badge`.id AS 'event_badge_id', `event_badge`.id_random AS 'event_badge_id_random', `event_badge`.event_person_id AS 'event_person_id'
FROM `event_badge` AS `event_badge`
WHERE `event_badge`.id = :event_badge_id
LIMIT 1;
"""
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
if event_badge_data_result := sql_select(data=data, sql=sql):
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_badge_data_result)
if event_person_id := event_badge_data_result.get('event_person_id', None): return event_person_id
else: return False
else: return None
# ### END ### API Event Badge Methods ### get_event_badge_template_id_w_event_id() ###
# ### BEGIN ### API Event Badge Methods ### create_update_event_badge_obj_v4() ###
# Updated 2022-02-23
@logger_reset
def create_update_event_badge_obj_v4(
event_badge_dict_obj: Event_Badge_Base|dict,
event_badge_id: int|str = None,
# account_id: int = None,
# event_id: int|str = None,
event_person_id: int|str = None,
create_sub_obj: bool = False,
fail_any: bool = False, # Fail if any thing goes wrong for sub objects
return_outline: bool = False,
) -> int|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
# ### SECTION ### Secondary data validation
log.info('Checking requirements...')
if event_badge_id:
log.info(f'Event Badge ID passed. Update existing Event Badge. Event Badge ID: {event_badge_id}')
if event_badge_id := redis_lookup_id_random(record_id_random=event_badge_id, table_name='event_badge'): pass
else:
log.error('Event Badge ID passed but is invalid. Failed requirement.')
return False
else:
log.info('No Event Badge ID passed. Create new Event Badge. Required: Event Person ID')
if event_person_id := redis_lookup_id_random(record_id_random=event_person_id, table_name='event_person'): pass
else:
log.error('Missing or invalid Event Person ID passed. Failed requirement.')
log.info(f'Event Person ID: {event_person_id}')
return False
log.debug('Create dictionary or Pydantic object')
log.debug(type(event_badge_dict_obj))
if isinstance(event_badge_dict_obj, dict):
event_badge_dict = event_badge_dict_obj
if event_badge_id:
event_badge_dict['event_badge_id'] = event_badge_id
try:
event_badge_obj = Event_Badge_Base(**event_badge_dict)
except ValidationError as e:
log.error(e.json())
return False
else:
event_badge_obj = event_badge_dict_obj
if event_badge_id:
# NOTE: Can't update the ID alias if it was never set.
event_badge_obj.id = event_badge_id
log.debug(event_badge_obj)
event_badge_dict = event_badge_obj.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'event_badge_template', 'event_id', 'event_id_random', 'created_on', 'updated_on'})
# ### SECTION ### Process data
if event_person_id: pass
elif event_person_id := event_badge_obj.event_person_id: pass
if event_badge_id:
if event_badge_dict_up_result := sql_update(data=event_badge_dict, table_name='event_badge', rm_id_random=True): pass
else:
log.warning(f'Event Badge not updated. Event Badge ID: {event_badge_id}')
log.debug(event_badge_dict_up_result)
return False
log.debug(event_badge_dict_up_result)
else:
if event_badge_dict_in_result := sql_insert(data=event_badge_dict, table_name='event_badge', rm_id_random=True, id_random_length=None): pass
else:
log.warning(f'Event Badge not created.')
log.debug(event_badge_dict_in_result)
return False
log.debug(event_badge_dict_in_result)
event_badge_id = event_badge_dict_in_result
event_badge_outline = {}
event_badge_outline['event_badge_id'] = event_badge_id
if return_outline:
log.debug(f'Returning the Event Badge Outline: {event_badge_outline}')
return event_badge_outline
else:
log.debug(f'Returning the Event Badge ID: {event_badge_id}')
return event_badge_id
# ### END ### API Event Badge Methods ### create_update_event_badge_obj_v4() ###
# ### BEGIN ### Event Badge Methods ### email_event_badge_review_url() ###
# This emails the actual one time use sign in URL for a user.
# Updated 2021-12-02
def email_event_badge_review_url(
event_badge_id: int|str,
root_url: str,
):
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
# if account_id := redis_lookup_id_random(record_id_random=account_id, table_name='account'): pass
# else: return False
if event_badge_id := redis_lookup_id_random(record_id_random=event_badge_id, table_name='event_badge'): pass
else: return False
from app.methods.event_badge_methods import load_event_badge_obj
if event_badge_obj := load_event_badge_obj(
event_badge_id = event_badge_id,
):
log.info('Event Badge object loaded')
else: return False
log.debug(event_badge_obj)
event_id = event_badge_obj.event_id
event_badge_id_random = event_badge_obj.id_random # Not event_badge_id_random
from app.methods.event_methods import load_event_obj
if event_obj := load_event_obj(
event_id = event_id,
):
log.info('Event object loaded')
else: return False
log.debug(event_obj)
account_id = event_obj.account_id
event_name = event_obj.name
from app.methods.account_cfg_methods import load_account_cfg_obj
if account_cfg := load_account_cfg_obj(
account_id = account_id,
):
log.info('Account config loaded')
else: return False
log.debug(account_cfg)
from_email = account_cfg.default_no_reply_email
from_name = account_cfg.default_no_reply_name
to_name = f'{event_badge_obj.given_name} {event_badge_obj.family_name}'
to_email = event_badge_obj.email
bcc_email = '' # account_cfg.confirm_email
bcc_name = '' # account_cfg.confirm_name
# help_tech_email = account_cfg.help_tech_email
# help_tech_name = account_cfg.help_tech_name
account_short_name = account_cfg.account_short_name
event_badge_review_url = f'{root_url}event/badge/{event_badge_id_random}/review_badge'
subject = f'{event_name}: Event Badge Review Link ({event_badge_id_random})'
body_html = f"""
<p>{to_name},</p>
<p>If you did not request this badge review link, please delete this email.</p>
<p>The link below will allow you to review your badge information and update some of the details if needed.</p>
<p><strong><a href="{event_badge_review_url}" style="appearance: button; display: inline-block; text-align: center; text-decoration: none; padding: .2rem .4rem; border: solid thin gray; border-radius: .2rem; background-color: lightyellow; color: black; font-size: larger;">Click to Review Badge</a></strong></p>
<p>Or copy and paste the link:<br>
<strong style="background-color: lightyellow; color: black; font-size: larger;"><a href="{event_badge_review_url}">{event_badge_review_url}</a></strong></p>
<p>Thank you!</p>
"""
if send_email(from_email=from_email, from_name=from_name, to_email=to_email, to_name=to_name, bcc_email=bcc_email, bcc_name=bcc_name, subject=subject, body_text=None, body_html=body_html):
log.info(f'An email with a badge review link was sent to {to_email}.')
return True
else:
log.info(f'An email with a badge review link was not sent to {to_email}.')
return False
# ### END ### Event Badge Methods ### email_event_badge_review_url() ###

View File

@@ -0,0 +1,171 @@
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_enable_part, sql_insert, sql_limit_offset_part, sql_select, sql_update
from app.lib_general import log, logging, logger_reset
from app.models.event_badge_template_models import Event_Badge_Template_Base, Event_Badge_Template_Base_In, Event_Badge_Template_Base_Out
# ### BEGIN ### API Event Badge Template Methods ### load_event_badge_template_obj() ###
# Updated 2022-07-25
@logger_reset
def load_event_badge_template_obj(
event_badge_template_id: int|str,
inc_event_badge_list: bool = False,
limit: int = 1000,
offset: int = 0,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
enabled: str = 'enabled', # enabled, disabled, all
) -> Event_Badge_Template_Base_Out|dict|bool:
# log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if event_badge_template_id := redis_lookup_id_random(record_id_random=event_badge_template_id, table_name='event_badge_template'): pass
else: return False
if event_badge_template_rec := sql_select(table_name='event_badge_template', record_id=event_badge_template_id): pass
else: return False
try:
event_badge_template_obj = Event_Badge_Template_Base_Out(**event_badge_template_rec)
log.debug(event_badge_template_obj)
except ValidationError as e:
log.error(e.json())
return False
if model_as_dict:
return event_badge_template_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return event_badge_template_obj
# ### END ### API Event Badge Template Methods ### load_event_badge_template_obj() ###
# ### BEGIN ### API Event Badge Template Methods ### get_event_badge_template_rec_list() ###
# Updated 2022-07-20
@logger_reset
def get_event_badge_template_rec_list(
event_id: str,
enabled: str = 'enabled', # enabled, disabled, all
limit: int = 25,
offset: int = 0,
) -> list|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
data = {}
data['event_id'] = event_id
sql_enabled, data['enable'] = sql_enable_part(table_name='event_badge_template', enabled=enabled) # Reasonably safe return str and bool
sql_limit = sql_limit_offset_part(limit=limit, offset=offset) # Reasonably safe return str
sql = f"""
SELECT `event_badge_template`.id AS 'event_badge_template_id', `event_badge_template`.id_random AS 'event_badge_template_id_random'
FROM `v_event_badge_template` AS `event_badge_template`
WHERE
`event_badge_template`.event_id = :event_id
{sql_enabled}
ORDER BY event_badge_template.name ASC, `event_badge_template`.created_on DESC, `event_badge_template`.updated_on DESC
{sql_limit};
"""
log.debug(sql)
if event_badge_template_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
event_badge_template_rec_li = event_badge_template_rec_li_result
else: # [] or False
event_badge_template_rec_li = event_badge_template_rec_li_result
log.debug(event_badge_template_rec_li_result)
return event_badge_template_rec_li
# ### END ### API Event Badge Template Methods ### get_event_badge_template_rec_list() ###
# ### BEGIN ### API Event Badge Template Methods ### create_update_event_badge_template_obj_v4() ###
# Updated 2022-07-25
@logger_reset
def create_update_event_badge_template_obj_v4(
event_badge_template_dict_obj: Event_Badge_Template_Base_In|dict,
event_badge_template_id: int|str = None,
event_id: int|str = None,
return_outline: bool = False,
) -> int|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
# ### SECTION ### Secondary data validation
log.info('Checking requirements...')
if event_badge_template_id:
log.info(f'Event Badge Template ID passed. Update existing Event Badge Template. Event Badge Template ID: {event_badge_template_id}')
if event_badge_template_id := redis_lookup_id_random(record_id_random=event_badge_template_id, table_name='event_badge_template'): pass
else:
log.error('Event Badge Template ID passed but is invalid. Failed requirement.')
return False
else:
log.info('No Event Badge Template ID passed. Create new Event Badge Template. Required: Event ID')
if event_id := redis_lookup_id_random(record_id_random=event_id, table_name='event'): pass
else:
log.error('Missing or invalid Event ID passed. Failed requirement.')
log.info(f'Event ID: {event_id}')
return False
log.info('Create dictionary or Pydantic object')
log.debug(type(event_badge_template_dict_obj))
if isinstance(event_badge_template_dict_obj, dict):
event_badge_template_dict = event_badge_template_dict_obj
if event_badge_template_id:
event_badge_template_dict['event_badge_template_id'] = event_badge_template_id
try:
event_badge_template_obj = Event_Badge_Template_Base_In(**event_badge_template_dict)
except ValidationError as e:
log.error(e.json())
return False
else:
event_badge_template_obj = event_badge_template_dict_obj
if event_badge_template_id:
# NOTE: Can't update the ID alias if it was never set.
event_badge_template_obj.id = event_badge_template_id
log.debug(event_badge_template_obj)
event_badge_template_dict = event_badge_template_obj.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'event_badge_list', 'event_id_random', 'created_on', 'updated_on'})
# ### SECTION ### Process data
if event_id: pass
elif event_id := event_badge_template_obj.event_id: pass
if event_badge_template_id:
if event_badge_template_dict_up_result := sql_update(data=event_badge_template_dict, table_name='event_badge_template', rm_id_random=True): pass
else:
log.warning(f'Event Badge Template not updated. Event Badge Template ID: {event_badge_template_id}')
log.debug(event_badge_template_dict_up_result)
return False
log.debug(event_badge_template_dict_up_result)
else:
if event_badge_template_dict_in_result := sql_insert(data=event_badge_template_dict, table_name='event_badge_template', rm_id_random=True): pass
else:
log.warning(f'Event Badge Template not created.')
log.debug(event_badge_template_dict_in_result)
return False
log.debug(event_badge_template_dict_in_result)
event_badge_template_id = event_badge_template_dict_in_result
event_badge_template_outline = {}
event_badge_template_outline['event_badge_template_id'] = event_badge_template_id
if return_outline:
log.debug(f'Returning the Event Badge Outline: {event_badge_template_outline}')
return event_badge_template_outline
else:
log.debug(f'Returning the Event Badge ID: {event_badge_template_id}')
return event_badge_template_id
# ### END ### API Event Badge Methods ### create_update_event_badge_template_obj_v4() ###

View File

@@ -0,0 +1,126 @@
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 = False, # Including even unset fields. Prevent null value issues on the frontend.
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() ###
# ### BEGIN ### API Event Cfg Methods ### create_update_event_cfg_obj_v4() ###
# Updated 2021-09-28
def create_update_event_cfg_obj_v4(
event_cfg_dict_obj: Event_Base|dict,
event_cfg_id: int|str|None = None,
event_id: int|str|None = None,
create_sub_obj: bool = False, # Is this needed for a config?
fail_any: bool = False, # Fail if any thing goes wrong for sub objects
return_outline: bool = False,
) -> int|bool:
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
log.info('Checking requirements...')
if event_id := redis_lookup_id_random(record_id_random=event_id, table_name='event'): pass
else: return False
log.debug(type(event_cfg_dict_obj))
if isinstance(event_cfg_dict_obj, dict):
event_cfg_dict = event_cfg_dict_obj
if event_id:
event_cfg_dict['event_id'] = event_id
# if event_cfg_id:
# event_cfg_dict['event_cfg_id'] = event_cfg_id
try:
event_cfg_obj = Event_Cfg_Base(**event_cfg_dict)
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_cfg_obj)
except ValidationError as e:
log.error(e.json())
return False
else:
event_cfg_obj = event_cfg_dict_obj
if event_id:
# NOTE: Can't update the ID alias if it was never set.
event_cfg_obj.id = event_id
event_cfg_dict = event_cfg_obj.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'registration_cfg', 'created_on', 'updated_on'}) # NOTE this exclude list should be reviewed again
if event_id:
if event_cfg_dict_up_result := sql_update(data=event_cfg_dict, table_name='event', rm_id_random=True): pass # NOTE: This is not using the event_cfg table at this time. -STI 2021-09-28
else:
log.warning(f'Event (Cfg) not updated. Event ID: {event_id}')
log.debug(event_cfg_dict_up_result)
return False
log.debug(event_cfg_dict_up_result)
else: # NOTE NOTE NOTE NOTE: This section is for future use NOTE NOTE NOTE NOTE
if event_cfg_dict_in_result := sql_insert(data=event_cfg_dict, table_name='event', rm_id_random=True, id_random_length=default_num_bytes): pass # NOTE: This is not using the event_cfg table at this time. -STI 2021-09-28
else:
log.warning(f'Event (Cfg) not created.')
log.debug(event_cfg_dict_in_result)
return False
log.debug(event_cfg_dict_in_result)
event_id = event_cfg_dict_in_result
event_outline = {}
event_outline['event_id'] = event_id
if return_outline:
log.debug(f'Returning the Event Cfg Outline: {event_cfg_outline}')
return event_cfg_outline
else:
log.debug(f'Returning the Event (Cfg) ID: {event_id}')
return event_id
# ### END ### API Event Cfg Methods ### create_update_event_cfg_obj_v4() ###

View File

@@ -0,0 +1,223 @@
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_enable_part, sql_insert, sql_limit_offset_part, sql_select, sql_update
from app.lib_general import log, logging, logger_reset
from app.methods.event_cfg_methods import load_event_cfg_obj
from app.methods.event_location_methods import load_event_location_obj
from app.models.common_field_schema import default_num_bytes
from app.models.event_cfg_models import Event_Cfg_Base
from app.models.event_device_models import Event_Device_Base
from app.models.event_location_models import Event_Location_Base
# ### BEGIN ### API Event Device Methods ### load_event_device_obj() ###
# Updated 2022-03-09
@logger_reset
def load_event_device_obj(
event_device_id: int,
enabled: str = 'enabled', # enabled, disabled, all
limit: int = 100,
offset: int = 0,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
inc_event_cfg: bool = False,
inc_event_location: bool = False,
) -> Event_Device_Base|dict|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if event_device_rec := sql_select(table_name='v_event_device', record_id=event_device_id): pass
else: return False
log.debug(event_device_rec)
try:
event_device_obj = Event_Device_Base(**event_device_rec)
except ValidationError as e:
log.error(e.json())
return False
log.debug(event_device_obj)
# Updated 2022-03-09
if inc_event_cfg:
log.info('Need to include event configuration...')
event_id = event_device_rec.get('event_id', None)
log.debug(event_id)
if event_cfg_result := load_event_cfg_obj(
event_id = event_id,
):
event_device_obj.event_cfg = event_cfg_result
else: event_device_obj.event_cfg = {} # None
# Updated 2022-03-09
if inc_event_location:
log.info('Need to include event location...')
event_location_id = event_device_rec.get('event_location_id', None)
log.debug(event_location_id)
if event_location_result := load_event_location_obj(
event_location_id = event_location_id,
):
event_device_obj.event_location = event_location_result
else: event_device_obj.event_location = {} # None
if model_as_dict:
return event_device_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return event_device_obj
# ### END ### API Event Device Methods ### load_event_device_obj() ###
# ### BEGIN ### API Event Device Methods ### get_event_device_rec_list() ###
# Updated 2022-03-09
@logger_reset
def get_event_device_rec_list(
for_type: str, # 'account' is a special case
for_id: str,
enabled: str = 'enabled', # enabled, disabled, all
limit: int = 100,
offset: int = 0,
) -> list|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if for_id := redis_lookup_id_random(record_id_random=for_id, table_name=for_type): pass
else: return False
if for_type == 'account':
sql_for_type_id = f'`event_device`.account_id = :for_id'
elif for_type == 'event':
sql_for_type_id = f'`event_device`.event_id = :for_id'
elif for_type == 'event_location':
sql_for_type_id = f'`event_device`.event_location_id = :for_id'
else: return False
data = {}
data['for_type'] = for_type
data['for_id'] = for_id
sql_enabled, data['enable'] = sql_enable_part(table_name='event_device', enabled=enabled) # Reasonably safe return str and bool
sql_limit = sql_limit_offset_part(limit=limit, offset=offset) # Reasonably safe return str
sql = f"""
SELECT `event_device`.id AS 'event_device_id', `event_device`.id_random AS 'event_device_id_random'
FROM `v_event_device` AS `event_device`
WHERE
{sql_for_type_id}
{sql_enabled}
ORDER BY `event_device`.created_on DESC, `event_device`.updated_on DESC
{sql_limit};
"""
log.debug(sql)
if event_device_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
event_device_rec_li = event_device_rec_li_result
else: # [] or False
event_device_rec_li = event_device_rec_li_result
log.debug(event_device_rec_li_result)
return event_device_rec_li
# ### END ### API Event Device Methods ### get_event_device_rec_list() ###
# ### BEGIN ### API Event Device Methods ### create_update_event_device_obj() ###
# Updated 2022-03-09
def create_update_event_device_obj(
event_device_dict_obj: Event_Device_Base|dict,
event_device_id: int|str = None,
event_id: int|str = None,
event_location_id: int|str = None,
create_sub_obj: bool = False,
fail_any: bool = False, # Fail if any thing goes wrong for sub objects
return_outline: bool = False,
) -> int|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
# ### SECTION ### Secondary data validation
log.info('Checking requirements...')
if event_device_id:
log.info(f'Event Device ID passed. Update existing Event Device. Event Device ID: {event_device_id}')
if event_device_id := redis_lookup_id_random(record_id_random=event_device_id, table_name='event_device'): pass
else:
log.error('Event Device ID passed but is invalid. Failed requirement.')
return False
else:
log.info('No Event Device ID passed. Create new Event Device. Required: Event ID; Optional: Event Location ID')
if event_id := redis_lookup_id_random(record_id_random=event_id, table_name='event'): pass
else:
log.error('Missing or invalid Event ID passed. Failed requirement.')
log.info(f'Event ID: {event_id}')
return False
if event_location_id := redis_lookup_id_random(record_id_random=event_location_id, table_name='event_location'): pass
elif event_location_id is None: pass
else:
log.warning('Missing or invalid Event Location ID passed. Failed requirement.')
log.info(f'Event Location ID: {event_location_id}')
return False
log.info('Create dictionary or Pydantic object')
log.debug(type(event_device_dict_obj))
if isinstance(event_device_dict_obj, dict):
event_device_dict = event_device_dict_obj
if event_device_id:
event_device_dict['event_device_id'] = event_device_id
if event_id:
event_dict['event_id'] = event_id
if event_location_id:
event_location_dict['event_location_id'] = event_location_id
try:
event_device_obj = Event_Device_Base(**event_device_dict)
except ValidationError as e:
log.error(e.json())
return False
else:
event_device_obj = event_device_dict_obj
if event_device_id:
# NOTE: Can't update the ID alias if it was never set.
event_device_obj.id = event_device_id
if event_id:
event_device_obj.event_id = event_id
if event_location_id:
event_device_obj.event_location_id = event_location_id
log.debug(event_device_obj)
event_device_dict = event_device_obj.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'event_cfg', 'event_location', 'account_id', 'account_id_random', 'created_on', 'updated_on'})
# ### SECTION ### Process data
if event_device_id:
if event_device_dict_up_result := sql_update(data=event_device_dict, table_name='event_device', rm_id_random=True): pass
else:
log.warning(f'Event Device not updated. Event Device ID: {event_device_id}')
log.debug(event_device_dict_up_result)
return False
log.debug(event_device_dict_up_result)
else:
if event_device_dict_in_result := sql_insert(data=event_device_dict, table_name='event_device', rm_id_random=True, id_random_length=8): pass
else:
log.warning(f'Event Device not created.')
log.debug(event_device_dict_in_result)
return False
log.debug(event_device_dict_in_result)
event_device_id = event_device_dict_in_result
event_device_outline = {}
event_device_outline['event_device_id'] = event_device_id
if return_outline:
log.debug(f'Returning the Event Device Outline: {event_device_outline}')
return event_device_outline
else:
log.debug(f'Returning the Event Device ID: {event_device_id}')
return event_device_id
# ### END ### API Event Device Methods ### create_update_event_device_obj() ###

View File

@@ -0,0 +1,272 @@
import datetime
from typing import Dict, List, Optional, Set, Union
from pydantic import BaseModel, EmailStr, Field, PrivateAttr, ValidationError, validator
from app.db_sql import get_account_id_w_for_type_id, redis_lookup_id_random, sql_delete, sql_enable_part, sql_delete, sql_insert, sql_limit_offset_part, sql_select, sql_update
from app.lib_general import log, logging, logger_reset
from app.methods.event_exhibit_tracking_methods import get_event_exhibit_tracking_rec_list, load_event_exhibit_tracking_obj
from app.models.common_field_schema import default_num_bytes
from app.models.event_exhibit_models import Event_Exhibit_Base
# ### BEGIN ### API Event Exhibit Methods ### create_event_exhibit_obj() ###
# Updated 2022-02-15
@logger_reset
def create_event_exhibit_obj(
event_id: int,
event_exhibit_dict_obj: Event_Exhibit_Base,
create_sub_obj: bool = False,
fail_any: bool = False, # Fail if any thing goes wrong for sub objects
log_lvl: int = logging.WARNING, # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
) -> int|bool:
log.setLevel(log_lvl)
# ### SECTION ### Secondary data validation
log.info('Create dictionary or Pydantic object')
log.debug(type(event_exhibit_dict_obj))
if isinstance(event_exhibit_dict_obj, dict):
event_exhibit_dict = event_exhibit_dict_obj
event_exhibit_dict['event_id'] = event_id
try:
event_exhibit_obj = Event_Exhibit_Base(**event_exhibit_dict)
except ValidationError as e:
log.error(e.json())
return False
else:
event_exhibit_obj = event_exhibit_dict_obj
event_exhibit_obj.event_id = event_id
log.debug(event_exhibit_obj)
event_exhibit_dict = event_exhibit_obj.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'event', 'event_id_random', 'created_on', 'updated_on'})
# ### SECTION ### Process data
if event_exhibit_dict_in_result := sql_insert(
data = event_exhibit_dict,
table_name = 'event_exhibit',
rm_id_random = True,
id_random_length = default_num_bytes
):
event_exhibit_id = event_exhibit_dict_in_result
log.info(f'Returning the new Event Exhibit ID: {event_exhibit_id}')
return event_exhibit_id
else:
log.warning(f'Event Exhibit not created.')
return event_exhibit_dict_in_result # False or None
# ### END ### API Event Exhibit Methods ### create_event_exhibit_obj() ###
# ### BEGIN ### API Event Exhibit Methods ### update_event_exhibit_obj() ###
# Updated 2022-02-15
@logger_reset
def update_event_exhibit_obj(
event_exhibit_id: int,
event_exhibit_dict_obj: Event_Exhibit_Base,
log_lvl: int = logging.WARNING, # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
) -> bool:
log.setLevel(log_lvl)
# ### SECTION ### Secondary data validation
log.info('Create dictionary or Pydantic object')
log.debug(type(event_exhibit_dict_obj))
if isinstance(event_exhibit_dict_obj, dict):
event_exhibit_dict = event_exhibit_dict_obj
event_exhibit_dict['id'] = event_exhibit_id
try:
event_exhibit_obj = Person_Base(**event_exhibit_dict)
except ValidationError as e:
log.error(e.json())
return False
else:
event_exhibit_obj = event_exhibit_dict_obj
event_exhibit_obj.id = event_exhibit_id
log.debug(event_exhibit_obj)
event_exhibit_dict = event_exhibit_obj.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'event', 'event_id_random', 'created_on', 'updated_on'})
# ### SECTION ### Process data
if event_exhibit_dict_up_result := sql_update(
data = event_exhibit_dict,
table_name = 'event_exhibit',
rm_id_random = True,
):
log.info(f'Updated the Event Exhibit ID: {event_exhibit_id}')
return event_exhibit_id
else:
log.warning(f'Event Exhibit not updated.')
return event_exhibit_dict_up_result # False or None
# ### END ### API Event Exhibit Methods ### update_event_exhibit_obj() ###
# ### BEGIN ### API Event Exhibit Methods ### delete_event_exhibit_obj() ###
# Updated 2022-02-15
@logger_reset
def remove_event_exhibit_obj(
event_exhibit_id: int,
log_lvl: int = logging.DEBUG, # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
) -> bool|None:
log.setLevel(log_lvl)
if event_exhibit_dict_del_result := sql_delete(
table_name = 'event_exhibit',
record_id = event_exhibit_id,
log_lvl = log_lvl,
):
log.info(f'Event Exhibit was deleted.')
return True
else:
log.warning(f'Event Exhibit not deleted.')
return event_exhibit_dict_del_result # False or None
# ### END ### API Event Exhibit Methods ### delete_event_exhibit_obj() ###
# ### BEGIN ### API Event Exhibit Methods ### load_event_exhibit_obj() ###
# Updated 2022-02-14
@logger_reset
def load_event_exhibit_obj(
event_exhibit_id: int,
enabled: str = 'enabled', # enabled, disabled, all
approved: str = 'all', # approved, not_approved, all
hidden: str = 'not_hidden', # hidden, not_hidden, all
review: str = 'all', # ready, not_ready, all
limit: int = 1500,
offset: int = 0,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
inc_address: bool = False,
inc_contact: bool = False,
inc_event_badge: bool = False,
inc_event_exhibit_tracking_list: bool = False,
inc_event_person: bool = False,
) -> Event_Exhibit_Base|dict|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
# if event_exhibit_id := redis_lookup_id_random(record_id_random=event_exhibit_id, table_name='event_exhibit'): pass
# else: return False
if event_exhibit_rec := sql_select(table_name='v_event_exhibit', record_id=event_exhibit_id): pass
else: return False
try:
event_exhibit_obj = Event_Exhibit_Base(**event_exhibit_rec)
log.debug(event_exhibit_obj)
except ValidationError as e:
log.error(e.json())
return False
# Updated 2022-02-15
if inc_event_exhibit_tracking_list:
log.info('Need to include Event Exhibit Tracking List data...')
if event_exhibit_tracking_rec_list_result := get_event_exhibit_tracking_rec_list(
event_exhibit_id = event_exhibit_id,
limit = limit,
enabled = enabled,
hidden = hidden,
):
event_exhibit_tracking_result_list = []
for event_exhibit_tracking_rec in event_exhibit_tracking_rec_list_result:
event_exhibit_tracking_result_list.append(
load_event_exhibit_tracking_obj(
event_exhibit_tracking_id = event_exhibit_tracking_rec.get('event_exhibit_tracking_id'),
inc_event_person = inc_event_person,
)
)
event_exhibit_obj.event_exhibit_tracking_list = event_exhibit_tracking_result_list
else: event_exhibit_obj.event_exhibit_tracking_list = []
if model_as_dict:
return event_exhibit_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return event_exhibit_obj
# ### END ### API Event Exhibit Methods ### load_event_exhibit_obj() ###
# ### BEGIN ### API Event Exhibit Methods ### get_event_exhibit_rec_list() ###
# for_obj_type: account, event, event_person, event_exhibit
# Updated 2022-02-14
@logger_reset
def get_event_exhibit_rec_list(
event_id: int|str = None,
event_exhibit_id: int|str = None,
event_person_id: int|str = None,
enabled: str = 'enabled', # enabled, disabled, all
limit: int = 100,
offset: int = 0,
) -> list|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
data = {}
sql_select_event_id = ''
sql_select_event_exhibit_id = ''
sql_select_event_person_id = ''
if event_id:
if event_id := redis_lookup_id_random(record_id_random=event_id, table_name='event'): pass
else: return False
data['event_id'] = event_id
sql_select_event_id = f'`event_exhibit`.event_id = :event_id'
else:
sql_select_event_id = f'`event_exhibit`.event_id IS NOT NULL'
if event_exhibit_id:
if event_exhibit_id := redis_lookup_id_random(record_id_random=event_exhibit_id, table_name='event_exhibit'): pass
else: return False
data['event_exhibit_id'] = event_exhibit_id
sql_select_event_exhibit_id = f'AND `event_exhibit`.event_exhibit_id = :event_exhibit_id'
if event_person_id:
if event_person_id := redis_lookup_id_random(record_id_random=event_person_id, table_name='event_person'): pass
else: return False
data['event_person_id'] = event_person_id
sql_select_event_person_id = f'AND `event_exhibit`.event_person_id = :event_person_id'
sql_enabled, data['enable'] = sql_enable_part(table_name='event_exhibit', enabled=enabled) # Reasonably safe return str and bool
sql_limit = sql_limit_offset_part(limit=limit, offset=offset) # Reasonably safe return str
if event_id or event_exhibit_id or event_person_id: pass
else: return False
sql_enabled = ''
if limit:
data['limit'] = limit
sql_limit = f'LIMIT :limit'
else:
sql_limit = ''
sql = f"""
SELECT `event_exhibit`.id AS 'event_exhibit_id', `event_exhibit`.id_random AS 'event_exhibit_id_random'
FROM `event_exhibit` AS `event_exhibit`
WHERE
{sql_select_event_id}
{sql_select_event_exhibit_id}
{sql_select_event_person_id}
{sql_enabled}
ORDER BY `event_exhibit`.priority DESC, -`event_exhibit`.sort DESC, `event_exhibit`.name ASC, `event_exhibit`.created_on DESC, `event_exhibit`.updated_on DESC
{sql_limit};
"""
log.debug(sql)
if event_exhibit_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
log.info('Got a list result')
event_exhibit_rec_li = event_exhibit_rec_li_result
else: # [] or False
log.info('No results or something went wrong')
event_exhibit_rec_li = event_exhibit_rec_li_result
log.debug(event_exhibit_rec_li_result)
return event_exhibit_rec_li
# ### END ### API Event Exhibit Methods ### get_event_exhibit_rec_list() ###

View File

@@ -0,0 +1,340 @@
import datetime, json
from typing import Dict, List, Optional, Set, Union
from pydantic import BaseModel, EmailStr, Field, PrivateAttr, ValidationError, validator
from app.db_sql import get_account_id_w_for_type_id, redis_lookup_id_random, sql_enable_part, sql_delete, sql_insert, sql_limit_offset_part, sql_select, sql_update
from app.lib_general import log, logging, logger_reset
from app.methods.event_badge_methods import get_event_person_id_w_event_badge_id
from app.models.common_field_schema import default_num_bytes
from app.models.event_exhibit_tracking_models import Event_Exhibit_Tracking_Base
# ### BEGIN ### API Event Exhibit Tracking Methods ### create_event_exhibit_tracking_obj() ###
# Updated 2022-02-15
@logger_reset
def create_event_exhibit_tracking_obj(
event_exhibit_id: int,
event_exhibit_tracking_dict_obj: Event_Exhibit_Tracking_Base,
event_badge_id: int = None,
event_person_id: int = None,
create_sub_obj: bool = False,
fail_any: bool = False, # Fail if any thing goes wrong for sub objects
log_lvl: int = logging.INFO, # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
) -> int|bool:
log.setLevel(log_lvl)
if event_badge_id and not event_person_id:
event_person_id = get_event_person_id_w_event_badge_id(event_badge_id)
# ### SECTION ### Secondary data validation
log.info('Create dictionary or Pydantic object')
log.debug(type(event_exhibit_tracking_dict_obj))
if isinstance(event_exhibit_tracking_dict_obj, dict):
event_exhibit_tracking_dict = event_exhibit_tracking_dict_obj
event_exhibit_tracking_dict['event_exhibit_id'] = event_exhibit_id
event_exhibit_tracking_dict['event_badge_id'] = event_badge_id
event_exhibit_tracking_dict['event_person_id'] = event_person_id
try:
event_exhibit_tracking_obj = Event_Exhibit_Tracking_Base(**event_exhibit_tracking_dict)
except ValidationError as e:
log.error(e.json())
return False
else:
event_exhibit_tracking_obj = event_exhibit_tracking_dict_obj
event_exhibit_tracking_obj.event_exhibit_id = event_exhibit_id
event_exhibit_tracking_obj.event_badge_id = event_badge_id
event_exhibit_tracking_obj.event_person_id = event_person_id
log.debug(event_exhibit_tracking_obj)
event_exhibit_tracking_dict = event_exhibit_tracking_obj.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'event_badge', 'event_badge_id_random', 'event_person', 'event_person_id_random', 'created_on', 'updated_on'})
# ### SECTION ### Process data
if event_exhibit_tracking_dict_in_result := sql_insert(
data = event_exhibit_tracking_dict,
table_name = 'event_exhibit_tracking',
rm_id_random = True,
id_random_length = default_num_bytes
):
event_exhibit_tracking_id = event_exhibit_tracking_dict_in_result
log.info(f'Returning the new Event Exhibit Tracking ID: {event_exhibit_tracking_id}')
return event_exhibit_tracking_id
else:
log.warning(f'Event Exhibit Tracking not created.')
return event_exhibit_tracking_dict_in_result # False or None
# ### END ### API Event Exhibit Tracking Methods ### create_event_exhibit_tracking_obj() ###
# ### BEGIN ### API Event Exhibit Tracking Methods ### update_event_exhibit_tracking_obj() ###
# Updated 2023-02-08
@logger_reset
def update_event_exhibit_tracking_obj(
event_exhibit_tracking_id: int,
event_exhibit_tracking_dict_obj: Event_Exhibit_Tracking_Base,
log_lvl: int = logging.WARNING, # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
) -> bool:
log.setLevel(log_lvl)
# ### SECTION ### Secondary data validation
log.info('Create dictionary or Pydantic object')
log.debug(type(event_exhibit_tracking_dict_obj))
if isinstance(event_exhibit_tracking_dict_obj, dict):
event_exhibit_tracking_dict = event_exhibit_tracking_dict_obj
event_exhibit_tracking_dict['id'] = event_exhibit_tracking_id
try:
event_exhibit_tracking_obj = Event_Exhibit_Tracking_Base(**event_exhibit_tracking_dict)
except ValidationError as e:
log.error(e.json())
return False
else:
event_exhibit_tracking_obj = event_exhibit_tracking_dict_obj
event_exhibit_tracking_obj.id = event_exhibit_tracking_id
log.debug(event_exhibit_tracking_obj)
event_exhibit_tracking_dict = event_exhibit_tracking_obj.dict(
by_alias = False,
exclude_defaults = False,
exclude_unset = True,
exclude = {
'event_id', 'event_id_random', 'event_badge', 'event_badge_id_random',
'event_badge_informal_name', 'event_badge_title_names',
'event_badge_given_name', 'event_badge_middle_name', 'event_badge_family_name',
'event_badge_display_name', 'event_badge_full_name',
'event_badge_designations', 'event_badge_professional_title', 'event_badge_affiliations',
'event_badge_email',
'event_badge_city',
'event_badge_country_subdivision_code', 'event_badge_state_province_abb', 'event_badge_state_province',
'event_badge_country_alpha_2_code', 'event_badge_country',
'event_badge_badge_type_code', 'event_badge_badge_type', 'event_badge_registration_type_code', 'event_badge_registration_type',
'event_person_informal_name', 'event_person_given_name',
'event_person_family_name', 'event_person_display_name', 'event_person_full_name',
'event_person_affiliations', 'event_person_email',
'event_exhibit_name',
'event_person', 'event_person_id_random', 'created_on', 'updated_on'
}
)
# NOTE: This is needed for the SQL update to work.
# NOTE: Should something like this be moved to the sql_update and sql_insert functions? If a field ends in _json or the field data type is a dict?
# if responses_json := event_exhibit_tracking_dict.get('responses_json'):
# event_exhibit_tracking_dict['responses_json'] = json.dumps(responses_json)
# ### SECTION ### Process data
if event_exhibit_tracking_dict_up_result := sql_update(
data = event_exhibit_tracking_dict,
table_name = 'event_exhibit_tracking',
rm_id_random = True,
):
log.info(f'Updated the Event Exhibit Tracking ID: {event_exhibit_tracking_id}')
return event_exhibit_tracking_id
else:
log.warning(f'Event Exhibit Tracking not updated.')
return event_exhibit_tracking_dict_up_result # False or None
# ### END ### API Event Exhibit Tracking Methods ### update_event_exhibit_tracking_obj() ###
# ### BEGIN ### API Event Exhibit Tracking Methods ### delete_event_exhibit_tracking_obj() ###
# Updated 2022-02-15
@logger_reset
def remove_event_exhibit_tracking_obj(
event_exhibit_tracking_id: int,
log_lvl: int = logging.DEBUG, # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
) -> bool|None:
log.setLevel(log_lvl)
if event_exhibit_tracking_dict_del_result := sql_delete(
table_name = 'event_exhibit_tracking',
record_id = event_exhibit_tracking_id,
log_lvl = log_lvl,
):
log.info(f'Event Exhibit Tracking was deleted.')
return True
else:
log.warning(f'Event Exhibit Tracking not deleted.')
return event_exhibit_tracking_dict_del_result # False or None
# ### END ### API Event Exhibit Tracking Methods ### delete_event_exhibit_tracking_obj() ###
# ### BEGIN ### API Event Exhibit Tracking Methods ### load_event_exhibit_tracking_obj() ###
# Updated 2022-02-15
@logger_reset
def load_event_exhibit_tracking_obj(
event_exhibit_tracking_id: int,
limit: int = 100,
offset: int = 0,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
enabled: str = 'enabled', # enabled, disabled, all
inc_address: bool = False,
inc_contact: bool = False,
inc_event_badge: bool = False,
# inc_event_exhibit: bool = False,
inc_event_person: bool = False,
inc_event_person_profile: bool = False,
) -> Event_Exhibit_Tracking_Base|dict|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
# if event_exhibit_tracking_id := redis_lookup_id_random(record_id_random=event_exhibit_tracking_id, table_name='event_exhibit_tracking'): pass
# else: return False
if event_exhibit_tracking_rec := sql_select(table_name='v_event_exhibit_tracking', record_id=event_exhibit_tracking_id): pass
else: return False
try:
event_exhibit_tracking_obj = Event_Exhibit_Tracking_Base(**event_exhibit_tracking_rec)
log.debug(event_exhibit_tracking_obj)
except ValidationError as e:
log.error(e.json())
return False
# if inc_event_exhibit:
# log.info('Need to include Event Exhibit data...')
# event_exhibit_id = event_exhibit_tracking_rec.get('event_exhibit_id', None)
# log.debug(event_exhibit_id)
# from app.methods.event_exhibit_methods import load_event_exhibit_obj
# if event_exhibit_result := load_event_exhibit_obj(
# event_exhibit_id = event_exhibit_id,
# by_alias = by_alias,
# exclude_unset = exclude_unset,
# model_as_dict = model_as_dict,
# ):
# event_exhibit_tracking_obj.event_exhibit = event_exhibit_result
# else: event_exhibit_tracking_obj.event_exhibit = {} # None
if inc_event_person:
log.info('Need to include Event Person data...')
event_person_id = event_exhibit_tracking_rec.get('event_person_id', None)
log.debug(event_person_id)
from app.methods.event_person_methods import load_event_person_obj
if event_person_result := load_event_person_obj(
event_person_id = event_person_id,
inc_event_badge = inc_event_badge,
inc_event_person_profile = inc_event_person_profile,
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
):
event_exhibit_tracking_obj.event_person = event_person_result
else: event_exhibit_tracking_obj.event_person = {} # None
if model_as_dict:
return event_exhibit_tracking_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return event_exhibit_tracking_obj
# ### END ### API Event Exhibit Tracking Methods ### load_event_exhibit_tracking_obj() ###
# ### BEGIN ### API Event Exhibit Tracking Methods ### get_event_exhibit_tracking_rec_list() ###
# for_obj_type: account, event, event_person, event_exhibit
# Updated 2022-02-14
@logger_reset
def get_event_exhibit_tracking_rec_list(
event_id: int|str = None,
event_exhibit_id: int|str = None,
event_badge_id: int|str = None,
event_person_id: int|str = None,
hidden: str = 'not_hidden', # hidden, not_hidden, all
enabled: str = 'enabled', # enabled, disabled, all
limit: int = 1500,
offset: int = 0,
) -> list|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
data = {}
sql_select_event_id = ''
sql_select_event_exhibit_id = ''
sql_select_event_badge_id = ''
sql_select_event_person_id = ''
if event_id:
if event_id := redis_lookup_id_random(record_id_random=event_id, table_name='event'): pass
else: return False
data['event_id'] = event_id
sql_select_event_id = f'`event_exhibit_tracking`.event_id = :event_id'
else:
sql_select_event_id = f'`event_exhibit_tracking`.event_id IS NOT NULL'
if event_exhibit_id:
if event_exhibit_id := redis_lookup_id_random(record_id_random=event_exhibit_id, table_name='event_exhibit'): pass
else: return False
data['event_exhibit_id'] = event_exhibit_id
sql_select_event_exhibit_id = f'AND `event_exhibit_tracking`.event_exhibit_id = :event_exhibit_id'
if event_badge_id:
if event_badge_id := redis_lookup_id_random(record_id_random=event_badge_id, table_name='event_badge'): pass
else: return False
data['event_badge_id'] = event_badge_id
sql_select_event_badge_id = f'AND `event_exhibit_tracking`.event_badge_id = :event_badge_id'
if event_person_id:
if event_person_id := redis_lookup_id_random(record_id_random=event_person_id, table_name='event_person'): pass
else: return False
data['event_person_id'] = event_person_id
sql_select_event_person_id = f'AND `event_exhibit_tracking`.event_person_id = :event_person_id'
sql_hidden = ''
if hidden in ['hidden', 'not_hidden', 'all']:
if hidden == 'hidden':
data['hide'] = True
sql_hidden = f'AND `event_exhibit_tracking`.hide = :hide'
elif hidden == 'not_hidden':
data['hide'] = False
sql_hidden = f'AND `event_exhibit_tracking`.hide = :hide'
elif hidden == 'all':
sql_hidden = ''
sql_enabled, data['enable'] = sql_enable_part(table_name='event_exhibit_tracking', enabled=enabled) # Reasonably safe return str and bool
sql_limit = sql_limit_offset_part(limit=limit, offset=offset) # Reasonably safe return str
if event_id or event_exhibit_id or event_person_id: pass
else: return False
sql_enabled = ''
if limit:
data['limit'] = limit
sql_limit = f'LIMIT :limit'
else:
sql_limit = ''
sql = f"""
SELECT `event_exhibit_tracking`.id AS 'event_exhibit_tracking_id', `event_exhibit_tracking`.id_random AS 'event_exhibit_tracking_id_random', priority AS 'priority', sort AS 'sort', hide AS 'hide', enable AS 'enable'
FROM `v_event_exhibit_tracking` AS `event_exhibit_tracking`
WHERE
{sql_select_event_id}
{sql_select_event_exhibit_id}
{sql_select_event_badge_id}
{sql_select_event_person_id}
{sql_hidden}
{sql_enabled}
AND allow_tracking = true
ORDER BY `event_exhibit_tracking`.priority DESC, -`event_exhibit_tracking`.sort DESC, `event_exhibit_tracking`.created_on DESC, `event_exhibit_tracking`.updated_on DESC
{sql_limit};
"""
log.debug(sql)
if event_exhibit_rec_li_result := sql_select(data=data, sql=sql, as_list=True, log_lvl=logging.WARNING):
log.info('Got a list result')
event_exhibit_rec_li = event_exhibit_rec_li_result
else: # [] or False
log.info('No results or something went wrong')
event_exhibit_rec_li = event_exhibit_rec_li_result
log.debug(event_exhibit_rec_li_result)
return event_exhibit_rec_li
# ### END ### API Event Exhibit Tracking Methods ### get_event_exhibit_tracking_rec_list() ###

View File

@@ -0,0 +1,306 @@
import datetime, hashlib, os, pathlib, shutil, time
from fastapi import File, UploadFile
from typing import Dict, List, Optional, Set, Union
from pydantic import BaseModel, EmailStr, Field, PrivateAttr, ValidationError, validator
from app.db_sql import get_id_random, redis_lookup_id_random, sql_delete, sql_enable_part, sql_insert, sql_insert_or_update, sql_limit_offset_part, sql_select, sql_update
# from app.lib_general import log, logging, logger_reset
from app.log import log, logging, logger_reset
from app.methods.hosted_file_methods import load_hosted_file_obj
from app.models.event_file_models import Event_File_Base
# ### BEGIN ### API Event File Methods ### create_event_file_obj() ###
@logger_reset
def create_event_file_obj(event_file_obj_new: Event_File_Base):
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
try:
# event_file_obj_data = event_file_obj_new.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'created_on', 'updated_on'})
event_file_obj_data = event_file_obj_new.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'saved', 'already_exists', 'copy_timer', 'created_on', 'updated_on'})
except Exception as e:
log.exception(e)
log.debug(event_file_obj_data)
if event_file_obj_in_result := sql_insert_or_update(data=event_file_obj_data, table_name='event_file', rm_id_random=True): pass
else:
return False
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_file_obj_in_result)
event_file_id = event_file_obj_in_result
log.debug(f'Returning the new event_file_id: {event_file_id}')
return event_file_id
# ### END ### API Event File Methods ### create_event_file_obj() ###
# ### BEGIN ### API Event File Methods ### load_event_file_obj() ###
@logger_reset
def load_event_file_obj(
event_file_id: int|str,
model_as_dict: bool = False, # This was defaulted to True 2022-03-07
by_alias: bool = True,
exclude_unset: bool = False,
enabled: str = 'enabled', # enabled, disabled, all
inc_hosted_file: bool = False,
) -> Event_File_Base|dict|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if event_file_id := redis_lookup_id_random(record_id_random=event_file_id, table_name='event_file'): pass
else: return False
# NOTE: What table or view should be used here???
if event_file_rec := sql_select(table_name='v_event_file_simple', record_id=event_file_id):
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_file_rec)
else:
return False
if event_file_rec.get('for_type') and event_file_rec.get('for_id') and not event_file_rec.get('for_id_random'):
event_file_rec['for_id_random'] = get_id_random(event_file_rec.get('for_id'), table_name=event_file_rec.get('for_type'))
hosted_file_id = event_file_rec.get('hosted_file_id', None)
try:
event_file_obj = Event_File_Base(**event_file_rec)
except ValidationError as e:
log.error(e.json())
return False
log.debug(event_file_obj)
if inc_hosted_file and hosted_file_id:
log.info('Need to include hosted file...')
if hosted_file_obj := load_hosted_file_obj(
hosted_file_id = hosted_file_id,
enabled = enabled,
):
event_file_obj.hosted_file = hosted_file_obj
# event_file_obj.hosted_file = hosted_file_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset)
else:
event_file_obj.hosted_file = {}
else:
event_file_obj.hosted_file = None
log.debug(event_file_obj)
if model_as_dict:
return event_file_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return event_file_obj
# ### END ### API Event File Methods ### load_event_file_obj() ###
# ### BEGIN ### API Event File Methods ### get_event_file_rec_list() ###
# Updated 2021-09-10
@logger_reset
def get_event_file_rec_list(
for_type: str, # NOTE: This is not for_obj_type because the field name is actually for_type
for_id: int|str, # NOTE: This is not for_obj_id because the field name is actually for_id
file_purpose_id: int = None, # NOTE: Not prefixed with lu_
file_purpose: str = None,
internal_use: bool = None, # Default to False instead of None
priority: bool = None,
group: str = None,
# event_id: str = None,
# event_person_id: str = None,
# event_presentation_id: str = None,
# event_presenter_id: str = None,
# event_session_id: str = None,
enabled: str = 'enabled', # enabled, disabled, all
limit: int = 100,
offset: int = 0,
) -> list|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if for_id := redis_lookup_id_random(record_id_random=for_id, table_name=for_type): pass
else: return False
data = {}
data['for_type'] = for_type
data['for_id'] = for_id
data['file_purpose_id'] = file_purpose_id
data['file_purpose'] = file_purpose
data['internal_use'] = internal_use
data['priority'] = priority
# data['sort'] = sort
data['group'] = group # Same or similar as file purpose?
sql_for_type_id = f'`event_file`.for_type = :for_type AND `event_file`.for_id = :for_id'
if file_purpose_id:
sql_file_purpose_id = f'AND `event_file`.lu_file_purpose_id = :file_purpose_id'
else:
sql_file_purpose_id = ''
if file_purpose:
sql_file_purpose = f'AND `event_file`.file_purpose = :file_purpose'
else:
sql_file_purpose = ''
if internal_use:
sql_internal_use = f'AND `event_file`.internal_use = 1'
elif internal_use is False:
sql_internal_use = f'AND (`event_file`.internal_use IS NULL OR `event_file`.internal_use = 0)'
else:
sql_internal_use = ''
if priority:
sql_priority = f'AND `event_file`.priority = :priority'
else:
sql_priority = ''
if group:
sql_group = f'AND `event_file`.group = :group'
else:
sql_group = ''
sql_enabled, data['enable'] = sql_enable_part(table_name='event_file', enabled=enabled) # Reasonably safe return str and bool
sql_limit = sql_limit_offset_part(limit=limit, offset=offset) # Reasonably safe return str
sql = f"""
SELECT `event_file`.id AS 'event_file_id', `event_file`.id_random AS 'event_file_id_random'
FROM `event_file` AS `event_file`
WHERE
{sql_for_type_id}
{sql_file_purpose_id}
{sql_file_purpose}
{sql_internal_use}
{sql_priority}
{sql_group}
{sql_enabled}
ORDER BY `event_file`.priority DESC, -`event_file`.sort DESC, `event_file`.created_on DESC, `event_file`.updated_on DESC, `event_file`.filename ASC, `event_file`.extension ASC
{sql_limit};
"""
log.debug(sql)
if event_file_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
event_file_rec_li = event_file_rec_li_result
else:
event_file_rec_li = []
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_file_rec_li_result)
log.debug(type(event_file_rec_li))
log.debug(len(event_file_rec_li))
return event_file_rec_li
# ### END ### API Event File Methods ### get_event_file_rec_list() ###
# ### BEGIN ### API Event File Methods ### load_event_file_obj_list() ###
@logger_reset
def load_event_file_obj_list(
event_id: int|str|None = None,
event_session_id: int|str|None = None,
inc_hosted_file: bool = False,
model_as_dict: bool = False,
enabled: str = 'enabled', # enabled, disabled, all
limit: int = 1000,
offset: int = 0,
) -> list|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
data: dict = {}
if event_id:
if event_id := redis_lookup_id_random(record_id_random=event_id, table_name='event'): pass
else: return False
data['for_type'] = 'event'
data['for_id'] = event_id
sql_obj_type_id = f'`tbl`.for_type = :for_type AND `tbl`.for_id = :for_id'
elif event_session_id:
if event_session_id := redis_lookup_id_random(record_id_random=event_session_id, table_name='event_session'): pass
else: return False
data['for_type'] = 'event_session'
data['for_id'] = event_session_id
sql_obj_type_id = f'`tbl`.for_type = :for_type AND `tbl`.for_id = :for_id'
sql_enabled, data['enable'] = sql_enable_part(table_name='event_file', enabled=enabled) # Reasonably safe return str and bool
sql_limit = sql_limit_offset_part(limit=limit, offset=offset) # Reasonably safe return str
sql = f"""
SELECT `tbl`.id AS 'event_file_id', `tbl`.id_random AS 'event_file_id_random'
FROM `event_file` AS `tbl`
WHERE
{sql_obj_type_id}
{sql_enabled}
ORDER BY `tbl`.created_on DESC, `tbl`.updated_on DESC
{sql_limit};
"""
if event_file_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_file_rec_li_result)
event_file_result_li = []
for event_file_rec in event_file_rec_li_result:
event_file_id = event_file_rec.get('event_file_id', None)
if event_file_result := load_event_file_obj(
event_file_id = event_file_id,
model_as_dict = True, # NOTE: This is overriding the model_as_dict param!
enabled = enabled,
inc_hosted_file = inc_hosted_file,
):
log.debug(event_file_result)
event_file_result_li.append(event_file_result)
else:
log.debug(event_file_result)
event_file_result_li.append(None)
# log.debug(event_file_result_li)
else:
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_file_rec_li_result)
event_file_result_li = []
return event_file_result_li
# ### END ### API Event Methods ### load_event_file_obj_list() ###
# ### BEGIN ### API Event File Methods ### handle_delete_event_file() ###
# Updated 2022-08-18
@logger_reset
def handle_delete_event_file(
event_file_id: int|str,
link_to_type: str = None,
link_to_id: int|str = None,
rm_all_links: bool = False,
rm_orphan: bool = False,
):
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
# if account_id := redis_lookup_id_random(record_id_random=account_id, table_name='account'): pass
# else: return False
if event_file_id := redis_lookup_id_random(record_id_random=event_file_id, table_name='event_file'): pass
else: return False
# log.debug(event_file_id)
# try:
# print('Trying to SQL DELETE!!!!')
# event_file_delete_result = sql_delete(table_name='event_file', record_id=event_file_id)
# print('SQL DELETE ran')
# log.debug(event_file_delete_result)
# except e:
# log.exception(e)
if event_file_delete_result := sql_delete(table_name='event_file', record_id=event_file_id, log_lvl=logging.DEBUG):
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.info(f'Deleted Event File ID: {event_file_id}')
return True
else:
log.error(f'Something went wrong will trying to delete Event File ID: {event_file_id}')
return False

View File

@@ -0,0 +1,122 @@
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_methods import load_event_obj
# ### BEGIN ### API Event Methods ### load_event_obj_list() ###
def load_event_obj_list(
account_id: int|str,
limit: int = 1000,
model_as_dict: bool = False,
enabled: str = 'enabled', # enabled, disabled, all
inc_contact_1: bool = False,
inc_contact_2: bool = False,
inc_contact_3: bool = False,
inc_event_abstract_list: bool = False,
inc_event_badge_list: bool = False,
inc_event_cfg: bool = False,
inc_event_device_list: bool = False,
inc_event_exhibit_list: bool = False,
inc_event_file_list: bool = False,
inc_event_location: bool = False, # For event_session child object
inc_event_location_list: bool = False,
inc_event_person_list: bool = False,
inc_event_presentation_list: bool = False,
inc_event_presenter_cat: bool = False, # For event_session child object
inc_event_presenter_list: bool = False,
inc_event_registration_cfg: bool = False,
inc_event_registration_list: bool = False,
inc_event_session_list: bool = False,
inc_event_track: bool = False, # For event_session child object
inc_event_track_list: bool = False,
inc_location_address: bool = False,
inc_poc_event_person: bool = False,
inc_person: bool = False,
inc_user: bool = False,
) -> list|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if account_id := redis_lookup_id_random(record_id_random=account_id, table_name='account'): pass
else: return False
data = {}
data['account_id'] = account_id
if enabled in ['enabled', 'disabled', 'all']:
if enabled == 'enabled':
data['enable'] = True
sql_enabled = f'AND `tbl`.enable = :enable'
elif enabled == 'disabled':
data['enable'] = False
sql_enabled = f'AND `tbl`.enable = :enable'
elif enabled == 'all':
sql_enabled = ''
# else: tbl_obj['account'] = None
if limit:
data['limit'] = limit
sql_limit = f'LIMIT :limit'
else:
sql_limit = ''
sql = f"""
SELECT `tbl`.id AS 'event_id', `tbl`.id_random AS 'event_id_random'
FROM `event` AS `tbl`
WHERE `tbl`.account_id = :account_id
{sql_enabled}
ORDER BY `tbl`.created_on DESC, `tbl`.updated_on DESC
{sql_limit};
"""
if event_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_rec_li_result)
event_result_li = []
for event_rec in event_rec_li_result:
event_id = event_rec.get('event_id', None)
if event_result := load_event_obj(
event_id = event_id,
limit = limit,
model_as_dict = model_as_dict,
enabled = enabled,
# inc_location_address = inc_address,
# inc_contact_1 = inc_contact,
# inc_contact_2 = inc_contact,
# inc_contact_3 = inc_contact,
# inc_event_abstract_list = inc_event_abstract_list,
# inc_event_badge_list = inc_event_badge_list,
# inc_event_device_list = inc_event_device_list,
inc_event_exhibit_list = inc_event_exhibit_list,
inc_event_file_list = inc_event_file_list,
inc_event_location_list = inc_event_location_list,
inc_event_person_list = inc_event_person_list,
inc_event_presentation_list = inc_event_presentation_list,
inc_event_presenter_list = inc_event_presenter_list,
inc_event_registration_list = inc_event_registration_list,
inc_event_session_list = inc_event_session_list,
inc_event_track_list = inc_event_track_list,
# inc_person = inc_person,
# inc_user = inc_user,
):
log.debug(event_result)
event_result_li.append(event_result)
else:
log.debug(event_result)
event_result_li.append(None)
log.debug(event_result_li)
else:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_rec_li_result)
event_result_li = []
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
return event_result_li
# ### END ### API Event Methods ### load_event_obj_list() ###

View File

@@ -0,0 +1,465 @@
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_enable_part, sql_insert, sql_limit_offset_part, sql_select, sql_update
from app.lib_general import log, logging, logger_reset
# from app.methods.event_methods import load_event_obj
# from app.methods.event_device_methods import load_event_device_obj
# from app.methods.event_file_methods import load_event_file_obj
# from app.methods.event_presentation_methods import load_event_presentation_obj
# from app.methods.event_presenter_methods import load_event_presenter_obj
# from app.methods.event_track_methods import load_event_track_obj
from app.models.event_location_models import Event_Location_Base
# ### BEGIN ### API Event Location Methods ### load_event_location_obj() ###
# Updated 2022-09-23
@logger_reset
def load_event_location_obj(
event_location_id: int|str,
inc_event_device_list: bool = False,
inc_event_file_list: bool = False,
inc_event_file_internal_use_list: bool = False,
inc_event_presentation_list: bool = False,
inc_event_presenter_list: bool = False,
inc_event_session_list: bool = False,
inc_file_count: bool = False, # NOTE: file counts are from separate views
inc_hosted_file: bool = False,
event_file_file_purpose_id: int = None,
event_file_file_purpose: str = None,
event_file_priority: bool = None,
event_file_group: str = None,
enabled: str = 'enabled', # enabled, disabled, all
hidden: str = 'hidden', # hidden, not_hidden, all
limit: int = 1000,
offset: int = 0,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
) -> Event_Location_Base|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if event_location_id := redis_lookup_id_random(record_id_random=event_location_id, table_name='event_location'): pass
else: return False
if inc_file_count:
log.info('Using view with file count')
if event_location_rec := sql_select(table_name='v_event_location_w_file_count', record_id=event_location_id): pass
else:
return False
else:
if event_location_rec := sql_select(table_name='v_event_location', record_id=event_location_id): pass
else:
return False
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_location_rec)
try:
log.info('Try to apply event location record data to Event_Location_Base...')
event_location_obj = Event_Location_Base(**event_location_rec)
log.debug(event_location_obj)
except ValidationError as e:
log.error(e.json())
return False
#account_id = event_location_rec.get('account_id', None)
event_id = event_location_rec.get('event_id', None)
# Updated 2022-09-27
if inc_event_device_list:
log.info('Need to include event device list...')
from app.methods.event_device_methods import get_event_device_rec_list, load_event_device_obj
if event_device_rec_list_result := get_event_device_rec_list(
for_type = 'event_location',
for_id = event_location_id,
enabled = enabled,
limit = limit,
offset = offset,
):
event_device_result_list = []
for event_device_rec in event_device_rec_list_result:
if load_event_device_result := load_event_device_obj(
event_device_id = event_device_rec.get('event_device_id', None),
enabled = enabled,
):
event_device_result_list.append(load_event_device_result)
else:
event_device_result_list.append(None)
log.debug(event_device_result_list)
event_location_obj.event_device_list = event_device_result_list
elif isinstance(event_device_rec_list_result, list):
event_location_obj.event_device_list = []
else:
event_location_obj.event_device_list = None
# if inc_event_file_list: pass
# Updated 2021-10-21
if inc_event_file_list:
log.info('Need to include event file list...')
from app.methods.event_file_methods import get_event_file_rec_list, load_event_file_obj
if event_file_rec_list_result := get_event_file_rec_list(
for_type = 'event_location',
for_id = event_location_id,
file_purpose_id = event_file_file_purpose_id,
file_purpose = event_file_file_purpose,
priority = event_file_priority,
group = event_file_group,
enabled = enabled,
limit = limit,
):
event_file_result_list = []
for event_file_rec in event_file_rec_list_result:
if load_event_file_result := load_event_file_obj(
event_file_id = event_file_rec.get('event_file_id', None),
enabled = enabled,
# inc_hosted_file = inc_hosted_file,
):
event_file_result_list.append(load_event_file_result)
else:
event_file_result_list.append(None)
log.debug(event_file_result_list)
event_location_obj.event_file_list = event_file_result_list
elif isinstance(event_file_rec_list_result, list):
event_location_obj.event_file_list = []
else:
event_location_obj.event_file_list = None
if inc_event_file_internal_use_list:
log.info('Need to include event file internal use list...')
from app.methods.event_file_methods import get_event_file_rec_list, load_event_file_obj
if event_file_rec_list_result := get_event_file_rec_list(
for_type = 'event_location',
for_id = event_location_id,
file_purpose_id = event_file_file_purpose_id,
file_purpose = event_file_file_purpose,
internal_use = True,
priority = event_file_priority,
group = event_file_group,
enabled = enabled,
limit = limit,
):
event_file_result_list = []
for event_file_rec in event_file_rec_list_result:
if load_event_file_result := load_event_file_obj(
event_file_id = event_file_rec.get('event_file_id', None),
enabled = enabled,
inc_hosted_file = inc_hosted_file,
):
event_file_result_list.append(load_event_file_result)
else:
event_file_result_list.append(None)
log.debug(event_file_result_list)
event_location_obj.event_file_internal_use_list = event_file_result_list
elif isinstance(event_file_rec_list_result, list):
event_location_obj.event_file_internal_use_list = []
else:
event_location_obj.event_file_internal_use_list = None
if inc_event_presentation_list:
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
data = {}
data['event_location_id'] = event_location_id
if enabled in ['enabled', 'disabled', 'all']:
if enabled == 'enabled':
data['enable'] = True
sql_enabled = f'AND `event_presentation`.enable = :enable'
elif enabled == 'disabled':
data['enable'] = False
sql_enabled = f'AND `event_presentation`.enable = :enable'
elif enabled == 'all':
sql_enabled = ''
# else: event_location_obj['event'] = None
sql = f"""
SELECT `event_presentation`.id AS 'event_presentation_id', `event_presentation`.id_random AS 'event_presentation_id_random'
FROM `v_event_presentation` AS `event_presentation`
WHERE `event_presentation`.event_location_id = :event_location_id
{sql_enabled}
ORDER BY `event_presentation`.created_on DESC, `event_presentation`.updated_on DESC;
"""
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
if event_presentation_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_presentation_rec_li_result)
event_presentation_obj_li = []
for event_presentation_rec in event_presentation_rec_li_result:
event_presentation_id = event_presentation_rec.get('event_presentation_id', None)
if event_presentation_obj := load_event_presentation_obj(
event_presentation_id=event_presentation_id,
enabled=enabled,
inc_event_device_list=inc_event_device_list,
inc_event_file_list=inc_event_file_list,
inc_event_presenter_list=inc_event_presenter_list,
):
data = event_presentation_obj.dict(by_alias=True, exclude_unset=True)
event_presentation_obj_li.append(data)
log.debug(event_presentation_obj_li)
event_location_obj.event_presentation_list = event_presentation_obj_li
else:
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_presentation_rec_li_result)
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
if inc_event_presenter_list: pass
# Updated 2021-10-09
if inc_event_session_list:
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.info('Need to include event session list...')
from app.methods.event_session_methods import get_event_session_rec_list, load_event_session_obj
if event_session_rec_list_result := get_event_session_rec_list(
event_location_id = event_location_id,
enabled = enabled, # enabled, disabled, all
approved = 'all', # approve(d), not_approved, all
hidden = hidden, # hidden, not_hidden, all
review = 'all', # ready, not_ready, all
limit = limit,
):
event_session_result_list = []
for event_session_rec in event_session_rec_list_result:
if load_event_session_result := load_event_session_obj(
event_session_id = event_session_rec.get('event_session_id', None),
enabled = enabled,
limit = limit,
inc_event_file_list = inc_event_file_list,
# 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_person = inc_person,
# inc_poc_event_person = inc_poc_event_person,
by_alias = by_alias,
exclude_unset = exclude_unset,
# model_as_dict = model_as_dict,
):
event_session_result_list.append(load_event_session_result)
else:
event_session_result_list.append(None)
log.debug(event_session_result_list)
event_location_obj.event_session_list = event_session_result_list
elif isinstance(event_session_rec_list_result, list):
event_location_obj.event_session_list = []
else:
event_location_obj.event_session_list = None
if model_as_dict:
return event_location_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return event_location_obj
return event_location_obj
# ### BEGIN ### API Event Location Methods ### get_event_location_rec_list() ###
# Updated 2021-12-13
@logger_reset
def get_event_location_rec_list(
event_id: str,
enabled: str = 'enabled', # enabled, disabled, all
hidden: str = 'not_hidden', # hidden, not_hidden, all
limit: int = 100,
offset: int = 0,
) -> list|bool:
log.setLevel(logging.INFO) # 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
data = {}
data['event_id'] = event_id
if event_id:
sql_where_event_id = f'`event_location`.event_id = :event_id'
else: sql_where_event_id = ''
if enabled in ['enabled', 'disabled', 'all']:
if enabled == 'enabled':
data['enable'] = True
sql_enabled = f'AND `event_location`.enable = :enable'
elif enabled == 'disabled':
data['enable'] = False
sql_enabled = f'AND `event_location`.enable = :enable'
elif enabled == 'all':
sql_enabled = ''
if hidden in ['hidden', 'not_hidden', 'all']:
if hidden == 'hidden':
data['hide'] = True
sql_hidden = f'AND `event_location`.hide = :hide'
elif hidden == 'not_hidden':
data['hide'] = False
sql_hidden = f'AND `event_location`.hide = :hide'
elif hidden == 'all':
sql_hidden = ''
if limit:
data['limit'] = limit
sql_limit = f'LIMIT :limit'
else:
sql_limit = ''
sql = f"""
SELECT `event_location`.id AS 'event_location_id', `event_location`.id_random AS 'event_location_id_random'
FROM `event_location` AS `event_location`
WHERE
{sql_where_event_id}
{sql_enabled}
{sql_hidden}
ORDER BY `event_location`.priority DESC, -`event_location`.sort DESC, `event_location`.name ASC, `event_location`.created_on DESC, `event_location`.updated_on DESC
{sql_limit};
"""
log.debug(sql)
if event_location_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
log.info('Got a list result')
event_location_rec_li = event_location_rec_li_result
else: # [] or False
log.info('No results or something went wrong')
event_location_rec_li = event_location_rec_li_result
log.debug(event_location_rec_li_result)
return event_location_rec_li
# ### END ### API Event Location Methods ### get_event_location_rec_list() ###
# ### BEGIN ### API Event Location Methods ### create_update_event_location_obj_v4() ###
# NOTE: This will create or update a event_location.
# Rewrite and updated 2021-08-25
def create_update_event_location_obj_v4(
event_location_dict_obj: Event_Location_Base|dict,
event_location_id: int|str = None,
event_id: int|str = None,
# event_location_id: int|str = None, # For future?
# event_track_id: int|str = None, # For future?
create_sub_obj: bool = False,
fail_any: bool = False, # Fail if any thing goes wrong for sub objects
return_outline: bool = False,
) -> int|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
log.info('Checking requirements...')
if event_location_id:
log.info(f'Event Location ID passed. Update existing Event Location. Event Location ID: {event_location_id}')
if event_location_id := redis_lookup_id_random(record_id_random=event_location_id, table_name='event_location'): pass
else:
log.error('Event Location ID passed but is invalid. Failed requirement.')
return False
if event_id := redis_lookup_id_random(record_id_random=event_id, table_name='event'): pass
else:
log.error('Missing or invalid Event ID passed. Not required. Ignoring.')
log.info(f'Event ID: {event_id}')
log.info('Attempting to get Event ID from related object.')
from app.methods.event_methods import get_event_id_w_for_type_id
if event_id := get_event_id_w_for_type_id(for_type='event_location', for_id=event_location_id): pass
else:
log.error('Unable to get Event ID from related object.')
False
else:
log.info('No Event Location ID passed. Create new Event Location. Required: Account ID, Event ID')
if event_id := redis_lookup_id_random(record_id_random=event_id, table_name='event'): pass
else:
log.error('Missing or invalid Event ID passed. Failed requirement.')
log.info(f'Event ID: {event_id}')
return False
log.debug(type(event_location_dict_obj))
if isinstance(event_location_dict_obj, dict):
event_location_dict = event_location_dict_obj
if event_location_id:
event_location_dict['event_location_id'] = event_location_id
if event_id:
event_location_dict['event_id'] = event_id
try:
event_location_obj = Event_Location_Base(**event_location_dict)
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_location_obj)
except ValidationError as e:
log.error(e.json())
return False
else:
event_location_obj = event_location_dict_obj
if event_location_id:
# NOTE: Can't update the ID alias if it was never set.
event_location_obj.id = event_location_id
if event_id:
event_location_obj.event_id = event_id
event_location_dict = event_location_obj.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'event_presentation', 'event_presentation_list', 'created_on', 'updated_on'})
if event_location_id:
if event_location_dict_up_result := sql_update(data=event_location_dict, table_name='event_location', rm_id_random=True): pass
else:
log.warning(f'Event Location not updated. Event Location ID: {event_location_id}')
log.debug(event_location_dict_up_result)
return False
log.debug(event_location_dict_up_result)
else:
if event_location_dict_in_result := sql_insert(data=event_location_dict, table_name='event_location', rm_id_random=True, id_random_length=8): pass
else:
log.warning(f'Event Location not created.')
log.debug(event_location_dict_in_result)
return False
log.debug(event_location_dict_in_result)
event_location_id = event_location_dict_in_result
log.debug(event_location_id)
event_location_outline = {}
event_location_outline['event_location_id'] = event_location_id
event_location_outline['event_session_list'] = []
if event_location_obj.event_session_list and isinstance(event_location_obj.event_session_list, list):
log.info(f'Event Session List was found. Loop through and create a new Event Session for each and link them to the new Event Location. Event Location ID: {event_location_id}')
for event_session_obj in event_location_obj.event_session_list:
# NOTE: Use object model version because of better type checking and validations
log.debug(event_session_obj)
if event_session_id := event_session_obj.id: pass
else: event_session_id = None
# event_session_obj.event_id = event_id
# event_session_obj.event_location_id = event_location_id
create_update_event_session_obj_result = create_update_event_session_obj_v4(
event_session_dict_obj = event_session_obj,
event_session_id = event_session_id,
event_id = event_id,
event_location_id = event_location_id,
fail_any = fail_any,
return_outline = return_outline,
)
if isinstance(create_update_event_session_obj_result, int):
event_session_id = create_update_event_session_obj_result
elif create_update_event_session_obj_result == True: pass
else:
log.warning(f'Create or Update failed while trying create_update_event_session_obj_v4(): {create_update_event_session_obj_result}')
event_session_id = None
event_location_outline['event_session_id'] = event_session_id
if return_outline:
log.debug(f'Returning the Event Location Outline: {event_location_outline}')
return event_location_outline
else:
log.debug(f'Returning the Event Location ID: {event_location_id}')
return event_location_id
# ### END ### API Event Location Methods ### create_update_event_location_obj_v4() ###

1030
app/methods/event_methods.py Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,223 @@
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 get_account_id_w_for_type_id, redis_lookup_id_random, sql_insert, sql_select, sql_update
from app.lib_general import log, logging
from app.methods.address_methods import create_address_obj, create_update_address_obj, create_update_address_obj_v4, update_address_obj
from app.models.common_field_schema import default_num_bytes
from app.models.event_person_profile_models import Event_Person_Profile_Base
# ### BEGIN ### API Event Person Profile Methods ### load_event_person_profile_obj() ###
def load_event_person_profile_obj(
event_person_profile_id: int|str,
limit: int = 1000,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
enabled: str = 'enabled', # enabled, disabled, all
inc_address: bool = False,
inc_contact: bool = False,
) -> Event_Person_Profile_Base|dict|bool:
# log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if event_person_profile_id := redis_lookup_id_random(record_id_random=event_person_profile_id, table_name='event_person_profile'): pass
else: return False
if event_person_profile_rec := sql_select(table_name='v_event_person_profile', record_id=event_person_profile_id): pass
else: return False
try:
event_person_profile_obj = Event_Person_Profile_Base(**event_person_profile_rec)
log.debug(event_person_profile_obj)
except ValidationError as e:
log.error(e.json())
return False
if inc_contact:
log.info('Need to include contact data...')
contact_id = event_person_profile_rec.get('contact_id', None)
log.debug(contact_id)
from app.methods.contact_methods import load_contact_obj
if contact_result := load_contact_obj(
contact_id = contact_id,
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
):
event_person_profile_obj.contact = contact_result
else: event_person_profile_obj.contact = None
if model_as_dict:
return event_person_profile_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return event_person_profile_obj
# ### END ### API Event Person Profile Methods ### load_event_person_profile_obj() ###
# ### BEGIN ### API Event Person Profile Methods ### get_event_person_id_w_event_person_profile_id() ###
# Updated 2021-09-07
def get_event_person_id_w_event_person_profile_id(
event_person_profile_id: int|str,
) -> bool|int|None:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if event_person_profile_id := redis_lookup_id_random(record_id_random=event_person_profile_id, table_name='event_person_profile'): pass
else: return False
data = {}
data['event_person_profile_id'] = event_person_profile_id
sql = f"""
SELECT `event_person_profile`.id AS 'event_person_profile_id', `event_person_profile`.id_random AS 'event_person_profile_id_random', `event_person_profile`.account_id AS account_id
FROM `event_person_profile` AS `event_person_profile`
WHERE `event_person_profile`.id = :event_person_profile_id
LIMIT 1;
"""
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
if event_person_profile_data_result := sql_select(data=data, sql=sql):
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_person_profile_data_result)
if account_id := event_person_profile_data_result.get('account_id', None): return account_id
else: return False
else: return None
# ### END ### API Event Person Profile Methods ### get_event_person_id_w_event_person_profile_id() ###
# ### BEGIN ### API Event Person Profile Methods ### create_update_event_person_profile_obj_v4() ###
# Updated 2022-02-23
def create_update_event_person_profile_obj_v4(
event_person_profile_dict_obj: Event_Person_Profile_Base|dict,
event_person_profile_id: int|str = None,
account_id: int = None,
# event_id: int|str = None,
event_person_id: int|str = None,
create_sub_obj: bool = False,
fail_any: bool = False, # Fail if any thing goes wrong for sub objects
return_outline: bool = False,
) -> int|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
# ### SECTION ### Secondary data validation
log.info('Checking requirements...')
if event_person_profile_id:
log.info(f'Event Person Profile ID passed. Update existing Event Person Profile. Event Person Profile ID: {event_person_profile_id}')
if event_person_profile_id := redis_lookup_id_random(record_id_random=event_person_profile_id, table_name='event_person_profile'): pass
else:
log.error('Event Person Profile ID passed but is invalid. Failed requirement.')
return False
else:
log.info('No Event Person Profile ID passed. Create new Event Person Profile. Required: Event Person ID')
if event_person_id := redis_lookup_id_random(record_id_random=event_person_id, table_name='event_person'): pass
else:
log.error('Missing or invalid Event Person ID passed. Failed requirement.')
log.info(f'Event Person ID: {event_person_id}')
return False
log.info('Create dictionary or Pydantic object')
log.debug(type(event_person_profile_dict_obj))
if isinstance(event_person_profile_dict_obj, dict):
event_person_profile_dict = event_person_profile_dict_obj
if event_person_profile_id:
event_person_profile_dict['event_person_profile_id'] = event_person_profile_id
try:
event_person_profile_obj = Event_Person_Profile_Base(**event_person_profile_dict)
except ValidationError as e:
log.error(e.json())
return False
else:
event_person_profile_obj = event_person_profile_dict_obj
if event_person_profile_id:
# NOTE: Can't update the ID alias if it was never set.
event_person_profile_obj.id = event_person_profile_id
log.debug(event_person_profile_obj)
event_person_profile_dict = event_person_profile_obj.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'account_id', 'account_id_random', 'contact', 'event_cfg', 'event_id', 'event_id_random', 'event_person_id', 'event_person_id_random', 'organization', 'created_on', 'updated_on', 'external_id_old'})
# ### SECTION ### Process data
contact_id = None
if event_person_profile_obj.contact and event_person_profile_obj.contact.id:
contact_id = event_person_profile_obj.contact.id
elif event_person_profile_obj.contact and event_person_profile_obj.contact_id:
contact_id = event_person_profile_obj.contact_id
if event_person_profile_id:
if event_person_profile_dict_up_result := sql_update(data=event_person_profile_dict, table_name='event_person_profile', rm_id_random=True): pass
else:
log.warning(f'Event Person Profile not updated. Event Person Profile ID: {event_person_profile_id}')
log.debug(event_person_profile_dict_up_result)
return False
log.debug(event_person_profile_dict_up_result)
else:
if event_person_profile_dict_in_result := sql_insert(data=event_person_profile_dict, table_name='event_person_profile', rm_id_random=True, id_random_length=8): pass
else:
log.warning(f'Event Person Profile not created.')
log.debug(event_person_profile_dict_in_result)
return False
log.debug(event_person_profile_dict_in_result)
event_person_profile_id = event_person_profile_dict_in_result
# NOTE: This is really only needed if a new contact is being created
log.info('Attempting to get Account ID from related object.')
if account_id: pass
elif account_id := event_person_profile_obj.account_id: pass
# elif account_id := event_person_profile_obj.account_id_random: pass
else:
if event_person_id: pass
elif event_person_id := event_person_profile_obj.event_person_id: pass
# elif event_person_id := event_person_profile_obj.event_person_id_random: pass
if event_person_id:
if account_id := get_account_id_w_for_type_id(for_type='event_person', for_id=event_person_id): pass
else:
log.error('Unable to get Account ID from related object.')
False
event_person_profile_outline = {}
event_person_profile_outline['event_person_profile_id'] = event_person_profile_id
# NOTE: Use object model version because of better type checking and validations
if event_person_profile_obj.contact:
event_person_profile_outline['contact_id'] = None
contact_obj = event_person_profile_obj.contact
contact_obj.for_type = 'event_person_profile'
contact_obj.for_id = event_person_profile_id
create_update_contact_obj_result = create_update_contact_obj_v4(
contact_dict_obj = contact_obj,
contact_id = contact_id,
account_id = account_id,
for_type = 'event_person_profile',
for_id = event_person_profile_id,
fail_any = fail_any,
return_outline = return_outline,
)
if isinstance(create_update_contact_obj_result, int):
contact_id = create_update_contact_obj_result
elif create_update_contact_obj_result == True: pass
else:
log.warning(f'Create or Update failed while trying create_update_contact_obj_v4(): {create_update_contact_obj_result}')
contact_id = None
event_person_profile_outline['contact_id'] = contact_id
if return_outline:
log.debug(f'Returning the Event Person Profile Outline: {event_person_profile_outline}')
return event_person_profile_outline
else:
log.debug(f'Returning the Event Person Profile ID: {event_person_profile_id}')
return event_person_profile_id
# ### END ### API Event Person Profile Methods ### create_update_event_person_profile_obj_v4() ###

View File

@@ -0,0 +1,187 @@
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 get_account_id_w_for_type_id, redis_lookup_id_random, sql_insert, sql_select, sql_update
from app.lib_general import log, logging, logger_reset
from app.models.common_field_schema import default_num_bytes
from app.models.event_person_tracking_models import Event_Person_Tracking_Base
# ### BEGIN ### API Event Person Tracking Methods ### load_event_person_tracking_obj() ###
# Updated 2021-09-21
def load_event_person_tracking_obj(
event_person_tracking_id: int|str,
limit: int = 1000,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
enabled: str = 'enabled', # enabled, disabled, all
inc_event_session: bool = False,
inc_event_person: bool = False,
) -> Event_Person_Tracking_Base|dict|bool:
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if event_person_tracking_id := redis_lookup_id_random(record_id_random=event_person_tracking_id, table_name='event_person_tracking'): pass
else: return False
if event_person_tracking_rec := sql_select(table_name='v_event_person_tracking', record_id=event_person_tracking_id): pass
else: return False
try:
event_person_tracking_obj = Event_Person_Tracking_Base(**event_person_tracking_rec)
log.debug(event_person_tracking_obj)
except ValidationError as e:
log.error(e.json())
return False
if inc_event_person:
log.info('Need to include Event Person data...')
event_person_id = event_person_tracking_rec.get('event_person_id', None)
log.debug(event_person_id)
from app.methods.event_person_methods import load_event_person_obj
if event_person_result := load_event_person_obj(
event_person_id = event_person_id,
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
):
event_person_tracking_obj.event_person = event_person_result
else: event_person_tracking_obj.event_person = None
if inc_event_session:
log.info('Need to include Event Session data...')
event_session_id = event_person_tracking_rec.get('event_session_id', None)
log.debug(event_session_id)
from app.methods.event_session_methods import load_event_session_obj
if event_session_result := load_event_session_obj(
event_session_id = event_session_id,
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
):
event_person_tracking_obj.event_session = event_session_result
else: event_person_tracking_obj.event_session = None
if model_as_dict:
return event_person_tracking_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return event_person_tracking_obj
# ### END ### API Event Person Tracking Methods ### load_event_person_tracking_obj() ###
# ### BEGIN ### API Event Person Tracking Methods ### get_event_person_tracking_rec_list() ###
# for_obj_type: account, event, event_session, event_person
# Updated 2021-09-21
def get_event_person_tracking_rec_list(
account_id: int|str,
event_id: int|str = None,
event_person_id: int|str = None,
event_session_id: int|str = None,
in_out_type: str = None,
# for_obj_type: str, # NOTE: This is not for_type because the field name generated based
# for_obj_id: str, # NOTE: This is not for_id because the field name generated based
enabled: str = 'enabled', # enabled, disabled, all
limit: int = 1000,
) -> list|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
data = {}
if account_id := redis_lookup_id_random(record_id_random=account_id, table_name='account'): pass
else: return False
data['account_id'] = account_id
# sql_select_account_id = f'`event_person_tracking`.account_id = :account_id'
sql_select_account_id = '1=1'
sql_select_event_id = ''
sql_select_event_person_id = ''
sql_select_event_session_id = ''
if event_id:
if event_id := redis_lookup_id_random(record_id_random=event_id, table_name='event'): pass
else: return False
data['event_id'] = event_id
sql_select_event_id = f'AND `event_person_tracking`.event_id = :event_id'
if event_person_id:
if event_person_id := redis_lookup_id_random(record_id_random=event_person_id, table_name='event_person'): pass
else: return False
data['event_person_id'] = event_person_id
sql_select_event_person_id = f'AND `event_person_tracking`.event_person_id = :event_person_id'
if event_session_id:
if event_session_id := redis_lookup_id_random(record_id_random=event_session_id, table_name='event_session'): pass
else: return False
data['event_session_id'] = event_session_id
sql_select_event_session_id = f'AND `event_person_tracking`.event_session_id = :event_session_id'
if event_id or event_person_id or event_session_id: pass
else: return False
if in_out_type:
if in_out_type == 'check':
sql_select_in_out_type = f'AND `event_person_tracking`.check_in_out = 1'
elif in_out_type == 'break':
sql_select_in_out_type = f'AND `event_person_tracking`.break_in_out = 1'
else:
data['other_type'] = in_out_type
sql_select_in_out_type = f'AND `event_person_tracking`.other_in_out = 1 AND `event_person_tracking`.other_type = :other_type'
else:
sql_select_in_out_type = f''
# if for_obj_id := redis_lookup_id_random(record_id_random=for_obj_id, table_name=for_obj_type): pass
# else: return False
# data[f'{for_obj_type}_id'] = for_obj_id
# sql_obj_type_id = f'`event_person_tracking`.{for_obj_type}_id = :{for_obj_type}_id'
# if enabled in ['enabled', 'disabled', 'all']:
# if enabled == 'enabled':
# data['enable'] = True
# sql_enabled = f'AND `event_person_tracking`.enable = :enable'
# elif enabled == 'disabled':
# data['enable'] = False
# sql_enabled = f'AND `event_person_tracking`.enable = :enable'
# elif enabled == 'all':
# sql_enabled = ''
sql_enabled = ''
if limit:
data['limit'] = limit
sql_limit = f'LIMIT :limit'
else:
sql_limit = ''
sql = f"""
SELECT `event_person_tracking`.id AS 'event_person_tracking_id', `event_person_tracking`.id_random AS 'event_person_tracking_id_random'
FROM `event_person_tracking` AS `event_person_tracking`
WHERE
{sql_select_account_id}
{sql_select_event_id}
{sql_select_event_person_id}
{sql_select_event_session_id}
{sql_enabled}
{sql_select_in_out_type}
ORDER BY `event_person_tracking`.created_on DESC, `event_person_tracking`.updated_on DESC
{sql_limit};
"""
if event_person_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
event_person_rec_li = event_person_rec_li_result
else:
event_person_rec_li = []
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_person_rec_li_result)
return event_person_rec_li
# ### END ### API Event Person Tracking Methods ### get_event_person_tracking_rec_list() ###

View File

@@ -0,0 +1,760 @@
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, logger_reset
# from app.methods.event_methods import load_event_obj
# from app.methods.event_file_methods import load_event_file_obj
# from app.methods.event_location_methods import load_event_location_obj
# from app.methods.event_presentation_methods import load_event_presentation_obj
from app.methods.event_presenter_methods import create_event_presenter_obj, create_update_event_presenter_obj_v4, load_event_presenter_obj, update_event_presenter_obj_v3
# from app.methods.event_session_methods import create_event_session_obj, load_event_session_obj, update_event_session_obj
# from app.methods.person_methods import load_person_obj
# from app.methods.user_methods import load_user_obj
from app.models.event_presentation_models import Event_Presentation_Base
# ### BEGIN ### API Event Presentation Methods ### load_event_presentation_obj() ###
@logger_reset
def load_event_presentation_obj(
event_presentation_id: int|str,
enabled: str = 'enabled', # enabled, disabled, all
hidden: str = 'not_hidden', # hidden, not_hidden, all
inc_file_count: bool = False, # NOTE: file counts are from separate views
event_file_file_purpose_id: int = None,
event_file_file_purpose: str = None,
event_file_priority: bool = None,
event_file_group: str = None,
inc_address: bool = False,
inc_contact: bool = False,
inc_event_abstract_list: bool = False,
inc_event_badge: bool = False,
#inc_event_badge_list: bool = False,
inc_event_device_list: bool = False,
inc_event_file_list: bool = False,
inc_event_person: bool = False,
inc_event_person_profile: bool = False,
inc_event_person_list: bool = False,
inc_event_presenter_list: bool = False,
inc_event_registration: bool = False,
#inc_event_registration_list: bool = False,
inc_event_session: bool = False,
inc_hosted_file: bool = False,
inc_person: bool = False,
inc_user: bool = False,
limit: int = 1000,
offset: int = 0,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
) -> Event_Presentation_Base|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if event_presentation_id := redis_lookup_id_random(record_id_random=event_presentation_id, table_name='event_presentation'): pass
else: return False
if inc_file_count:
log.info('Using view with file count')
if event_presentation_rec := sql_select(table_name='v_event_presentation_w_file_count', record_id=event_presentation_id): pass
else: return False
else:
if event_presentation_rec := sql_select(table_name='v_event_presentation', record_id=event_presentation_id): pass
else: return False
# log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_presentation_rec)
try:
log.info('Try to apply event presentation record data to Event_Presentation_Base...')
event_presentation_obj = Event_Presentation_Base(**event_presentation_rec)
log.debug(event_presentation_obj)
except ValidationError as e:
log.error(e.json())
return False
# account_id = event_presentation_rec.get('account_id', None)
event_id = event_presentation_rec.get('event_id', None)
event_abstract_id = event_presentation_rec.get('event_abstract_id', None)
event_person_id = event_presentation_rec.get('event_person_id', None)
event_session_id = event_presentation_rec.get('event_session_id', None)
# if inc_event: pass
if inc_event_abstract_list: pass
# if inc_event_badge_list: pass
if inc_event_device_list: pass
# Updated 2021-11-05
if inc_event_file_list:
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.info('Need to include event file list...')
from app.methods.event_file_methods import get_event_file_rec_list, load_event_file_obj
if event_file_rec_list_result := get_event_file_rec_list(
for_type = 'event_presentation',
for_id = event_presentation_id,
file_purpose_id = event_file_file_purpose_id,
file_purpose = event_file_file_purpose,
priority = event_file_priority,
group = event_file_group,
enabled = enabled,
limit = limit,
):
event_file_result_list = []
for event_file_rec in event_file_rec_list_result:
if load_event_file_result := load_event_file_obj(
event_file_id = event_file_rec.get('event_file_id', None),
enabled = enabled,
inc_hosted_file = inc_hosted_file,
# model_as_dict = True,
# by_alias = by_alias,
# exclude_unset = False,
):
event_file_result_list.append(load_event_file_result)
else:
event_file_result_list.append(None)
log.debug(event_file_result_list)
event_presentation_obj.event_file_list = event_file_result_list
elif isinstance(event_file_rec_list_result, list):
event_presentation_obj.event_file_list = []
else:
event_presentation_obj.event_file_list = None
if inc_event_person_list: pass
if inc_event_presenter_list:
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
data = {}
data['event_presentation_id'] = event_presentation_id
if hidden in ['hidden', 'not_hidden', 'all']:
if hidden == 'hidden':
data['hide'] = True
sql_hidden = f'AND `event_presenter`.hide = :hide'
elif hidden == 'not_hidden':
data['hide'] = False
sql_hidden = f'AND `event_presenter`.hide = :hide'
elif hidden == 'all':
sql_hidden = ''
if enabled in ['enabled', 'disabled', 'all']:
if enabled == 'enabled':
data['enable'] = True
sql_enabled = f'AND `event_presenter`.enable = :enable'
elif enabled == 'disabled':
data['enable'] = False
sql_enabled = f'AND `event_presenter`.enable = :enable'
elif enabled == 'all':
sql_enabled = ''
# else: event_obj['event_session'] = None
# if limit:
# data['limit'] = limit
# sql_limit = f'LIMIT :limit'
# else:
# sql_limit = ''
sql = f"""
SELECT `event_presenter`.id AS 'event_presenter_id', `event_presenter`.id_random AS 'event_presenter_id_random'
FROM `event_presenter` AS `event_presenter`
WHERE `event_presenter`.event_presentation_id = :event_presentation_id
{sql_hidden}
{sql_enabled}
ORDER BY `event_presenter`.priority DESC, -`event_presenter`.sort DESC, `event_presenter`.family_name ASC, `event_presenter`.given_name ASC, `event_presenter`.full_name ASC, `event_presenter`.created_on DESC, `event_presenter`.updated_on DESC;
"""
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(sql)
log.debug(data)
if event_presenter_obj_li_result := sql_select(data=data, sql=sql, as_list=True):
# log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_presenter_obj_li_result)
event_presenter_obj_li = []
for event_presenter_obj in event_presenter_obj_li_result:
event_presenter_id = event_presenter_obj.get('event_presenter_id', None)
if event_presenter_obj := load_event_presenter_obj(
event_presenter_id = event_presenter_id,
enabled = enabled,
# review = review,
# approved = approved,
hidden = hidden,
inc_file_count = inc_file_count,
inc_address = inc_address,
inc_contact = inc_contact,
inc_event_abstract_list = inc_event_abstract_list,
inc_event_badge = inc_event_badge,
inc_event_device_list = inc_event_device_list,
inc_event_file_list = inc_event_file_list,
inc_event_person = inc_event_person,
inc_event_person_profile = inc_event_person_profile,
inc_event_registration = inc_event_registration,
inc_person = inc_person,
inc_user = inc_user,
):
data = event_presenter_obj.dict(by_alias=True, exclude_unset=True)
event_presenter_obj_li.append(data)
log.debug(event_presenter_obj_li)
event_presentation_obj.event_presenter_list = event_presenter_obj_li
else:
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_presenter_obj_li_result)
event_presentation_obj.event_presenter_list = []
# log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
if inc_event_session:
# log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
from app.methods.event_session_methods import load_event_session_obj
if event_session_obj := load_event_session_obj(
event_session_id = event_session_id,
# Don't append the presentation list and things
):
log.debug(event_session_obj)
event_presentation_obj.event_session = event_session_obj
else:
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_session_obj)
event_presentation_obj.event_session = None
# log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
if model_as_dict:
return event_presentation_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return event_presentation_obj
# ### END ### API Event Presentation Methods ### load_event_presentation_obj() ###
# ### BEGIN ### API Event Presentation Methods ### get_event_presentation_rec_list() ###
@logger_reset
def get_event_presentation_rec_list(
event_person_id: str = None,
event_session_id: str = None,
enabled: str = 'enabled', # enabled, disabled, all
approved: str = 'all', # approved, not_approved, all
hidden: str = 'not_hidden', # hidden, not_hidden, all
review: str = 'all', # ready, not_ready, all
limit: int = 100,
offset: int = 0,
) -> list|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if event_person_id := redis_lookup_id_random(record_id_random=event_person_id, table_name='event_person'): pass
else: pass
if event_session_id := redis_lookup_id_random(record_id_random=event_session_id, table_name='event_session'): pass
else: pass
if event_person_id or event_session_id: pass
else: return False
data = {}
data['event_person_id'] = event_person_id
data['event_session_id'] = event_session_id
if event_person_id:
sql_where_event_person_id = f'`event_presentation`.event_person_id = :event_person_id'
else: sql_where_event_person_id = ''
if event_session_id:
sql_where_event_session_id = f'`event_presentation`.event_session_id = :event_session_id'
else: sql_where_event_session_id = ''
if hidden in ['hidden', 'not_hidden', 'all']:
if hidden == 'hidden':
data['hide'] = True
sql_hidden = f'AND `event_presentation`.hide = :hide'
elif hidden == 'not_hidden':
data['hide'] = False
sql_hidden = f'AND `event_presentation`.hide = :hide'
elif hidden == 'all':
sql_hidden = ''
if enabled in ['enabled', 'disabled', 'all']:
if enabled == 'enabled':
data['enable'] = True
sql_enabled = f'AND `event_presentation`.enable = :enable'
elif enabled == 'disabled':
data['enable'] = False
sql_enabled = f'AND `event_presentation`.enable = :enable'
elif enabled == 'all':
sql_enabled = ''
if limit:
data['limit'] = limit
sql_limit = f'LIMIT :limit'
else:
sql_limit = ''
sql = f"""
SELECT `event_presentation`.id AS 'event_presentation_id', `event_presentation`.id_random AS 'event_presentation_id_random'
FROM `event_presentation` AS `event_presentation`
WHERE
{sql_where_event_person_id}
{sql_where_event_session_id}
{sql_hidden}
{sql_enabled}
ORDER BY `event_presentation`.priority DESC, -`event_presentation`.sort DESC, `event_presentation`.start_datetime ASC, `event_presentation`.name ASC, `event_presentation`.created_on DESC, `event_presentation`.updated_on DESC
{sql_limit};
"""
if event_presentation_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
event_presentation_rec_li = event_presentation_rec_li_result
else:
event_presentation_rec_li = []
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_presentation_rec_li_result)
log.debug(type(event_presentation_rec_li))
log.debug(len(event_presentation_rec_li))
return event_presentation_rec_li
# ### END ### API Event Presentation Methods ### get_event_presentation_rec_list() ###
# ### BEGIN ### API Event Presentation Methods ### get_event_id_w_event_session_id() ###
# Updated 2021-08-23
@logger_reset
def get_event_id_w_event_session_id(
event_session_id: int|str,
) -> bool|int|None:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if event_session_id := redis_lookup_id_random(record_id_random=event_session_id, table_name='event_session'): pass
else: return False
data = {}
data['event_session_id'] = event_session_id
sql = f"""
SELECT `event_session`.id AS 'event_session_id', `event_session`.id_random AS 'event_session_id_random', `event_session`.event_id AS event_id
FROM `event_session` AS `event_session`
WHERE `event_session`.id = :event_session_id
LIMIT 1;
"""
if event_session_data_result := sql_select(data=data, sql=sql):
log.debug(event_session_data_result)
if event_id := event_session_data_result.get('event_id', None): return event_id
else: return False
else: return None
# ### END ### API Event Presentation Methods ### get_event_id_w_event_session_id() ###
# ### BEGIN ### API Event Presentation Methods ### create_update_event_presentation_obj_v4() ###
# NOTE: This will create or update a event_presentation.
# Rewrite and updated 2021-08-25
@logger_reset
def create_update_event_presentation_obj_v4(
event_presentation_dict_obj: Event_Presentation_Base|dict,
event_presentation_id: int|str = None,
event_id: int|str = None,
event_session_id: int|str = None,
create_sub_obj: bool = False,
fail_any: bool = False, # Fail if any thing goes wrong for sub objects
return_outline: bool = False,
) -> int|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
log.info('Checking requirements...')
if event_presentation_id:
log.info(f'Event Presentation ID passed. Update existing Event Presentation. Event Presentation ID: {event_presentation_id}')
if event_presentation_id := redis_lookup_id_random(record_id_random=event_presentation_id, table_name='event_presentation'): pass
else:
log.error('Event Presentation ID passed but is invalid. Failed requirement.')
return False
if event_id := redis_lookup_id_random(record_id_random=event_id, table_name='event'): pass
else:
log.error('Missing or invalid Event ID passed. Not required. Ignoring.')
log.info(f'Event ID: {event_id}')
log.info('Attempting to get Event ID from related object.')
from app.methods.event_methods import get_event_id_w_for_type_id
if event_id := get_event_id_w_for_type_id(for_type='event_session', for_id=event_session_id): pass
elif event_id := get_event_id_w_for_type_id(for_type='event_presentation', for_id=event_presentation_id): pass
else:
log.error('Unable to get Event ID from related object.')
False
else:
log.info('No Event Presentation ID passed. Create new Event Presentation. Required: Account ID, Event ID')
if event_id := redis_lookup_id_random(record_id_random=event_id, table_name='event'): pass
else:
log.error('Missing or invalid Event ID passed. Failed requirement.')
log.info(f'Event ID: {event_id}')
log.info('Attempting to get Event ID from related object.')
from app.methods.event_methods import get_event_id_w_for_type_id
if event_id := get_event_id_w_for_type_id(for_type='event_session', for_id=event_session_id): pass
else:
log.error('Unable to get Event ID from related object.')
False
log.debug(type(event_presentation_dict_obj))
if isinstance(event_presentation_dict_obj, dict):
event_presentation_dict = event_presentation_dict_obj
if event_presentation_id:
event_presentation_dict['event_presentation_id'] = event_presentation_id
if event_session_id:
event_presentation_dict['event_session_id'] = event_session_id
if event_id:
event_presentation_dict['event_id'] = event_id
try:
event_presentation_obj = Event_Presentation_Base(**event_presentation_dict)
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_presentation_obj)
except ValidationError as e:
log.error(e.json())
return False
else:
event_presentation_obj = event_presentation_dict_obj
if event_presentation_id:
# NOTE: Can't update the ID alias if it was never set.
event_presentation_obj.id = event_presentation_id
if event_session_id:
event_presentation_obj.event_session_id = event_session_id
if event_id:
event_presentation_obj.event_id = event_id
event_presentation_dict = event_presentation_obj.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'event_presenter', 'event_presenter_list', 'created_on', 'updated_on'})
if event_presentation_id:
if event_presentation_dict_up_result := sql_update(data=event_presentation_dict, table_name='event_presentation', rm_id_random=True): pass
else:
log.warning(f'Event Presentation not updated. Event Presentation ID: {event_presentation_id}')
log.debug(event_presentation_dict_up_result)
return False
log.debug(event_presentation_dict_up_result)
else:
if event_presentation_dict_in_result := sql_insert(data=event_presentation_dict, table_name='event_presentation', rm_id_random=True, id_random_length=8): pass
else:
log.warning(f'Event Presentation not created.')
log.debug(event_presentation_dict_in_result)
return False
log.debug(event_presentation_dict_in_result)
event_presentation_id = event_presentation_dict_in_result
event_presentation_outline = {}
event_presentation_outline['event_id'] = event_id
event_presentation_outline['event_session_id'] = event_session_id
event_presentation_outline['event_presentation_id'] = event_presentation_id
event_presentation_outline['event_presenter_list'] = []
if event_presentation_obj.event_presenter_list and isinstance(event_presentation_obj.event_presenter_list, list):
log.info(f'Event Presenter List was found. Loop through and create a new Event Presenter for each and link them to the new Event Presentation. Event Presentation ID: {event_presentation_id}')
for event_presenter_obj in event_presentation_obj.event_presenter_list:
# NOTE: Use object model version because of better type checking and validations
log.debug(event_presenter_obj)
if event_presenter_id := event_presenter_obj.id: pass
else: event_presenter_id = None
# event_presenter_obj.event_id = event_id
# event_presenter_obj.event_session_id = event_session_id
create_update_event_presenter_obj_result = create_update_event_presenter_obj_v4(
event_presenter_dict_obj = event_presenter_obj,
event_presenter_id = event_presenter_id,
event_id = event_id,
event_session_id = event_session_id,
event_presentation_id = event_presentation_id,
fail_any = fail_any,
return_outline = return_outline,
)
if isinstance(create_update_event_presenter_obj_result, int):
event_presenter_id = create_update_event_presenter_obj_result
elif create_update_event_presenter_obj_result == True: pass
else:
log.warning(f'Create or Update failed while trying create_update_event_presenter_obj_v4(): {create_update_event_presenter_obj_result}')
event_presenter_id = None
event_presentation_outline['event_presenter_id'] = event_presenter_id
if return_outline:
log.debug(f'Returning the Event Presentation Outline: {event_presentation_outline}')
return event_presentation_outline
else:
log.debug(f'Returning the Event Presentation ID: {event_presentation_id}')
return event_presentation_id
# ### END ### API Event Presentation Methods ### create_update_event_presentation_obj_v4() ###
# ### BEGIN ### API Event Presentation Methods ### create_event_presentation_obj() ###
# Updated 2022-04-12
@logger_reset
def create_event_presentation_obj(
event_session_id: int|str,
event_presentation_obj_new: Event_Presentation_Base,
event_id: int|str = None, # If None then need to look up from event_session_id
create_sub_obj: bool = False,
fail_any: bool = False, # Fail if any thing goes wrong for sub objects
) -> bool:
log.setLevel(logging.INFO) # 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:
# from app.methods.event_session_methods import get_event_id_w_event_session_id
if event_id := get_event_id_w_event_session_id(event_session_id): pass
else: return event_id # False or None
if event_session_id := redis_lookup_id_random(record_id_random=event_session_id, table_name='event_session'): pass
else: return False
# if event_id := event_presentation_obj_new.event_id: pass
# else:
# log.error('Event ID is required')
# return False
# if event_session_id := event_presentation_obj_new.event_session_id: pass
# else:
# log.error('Event Session ID is required')
# return False
log.debug(type(event_presentation_obj_new))
if isinstance(event_presentation_obj_new, dict):
event_presentation_dict = event_presentation_obj_new
try:
event_presentation_obj = Event_Presentation_Base(**event_presentation_dict)
except ValidationError as e:
log.error(e.json())
return False
log.debug(event_presentation_obj)
else:
event_presentation_dict = event_presentation_obj_new.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'event_presenter', 'event_presenter_list', 'created_on', 'updated_on'})
event_presentation_dict['for_type'] = 'event_session'
event_presentation_dict['for_id'] = event_session_id
event_presentation_dict['event_id'] = event_id
event_presentation_dict['event_session_id'] = event_session_id
log.debug(event_presentation_dict)
if event_presentation_obj_in_result := sql_insert(data=event_presentation_dict, table_name='event_presentation', rm_id_random=True, id_random_length=8): pass
else:
log.warning(f'Event Presentation not created.')
log.debug(event_presentation_obj_in_result)
return False
event_presentation_id = event_presentation_obj_in_result
return_dict = {}
return_dict['event_presentation_id'] = None
return_dict['event_presenter_list'] = []
if event_presentation_obj.event_presenter_list and isinstance(event_presentation_obj.event_presenter_list, list):
for event_presenter_obj in event_presentation_obj.event_presenter_list:
# NOTE: This does not account for an edge case where the presenter already exists. Possibly as part of another presentation.
if create_event_presenter_obj_result := create_event_presenter_obj(
event_presentation_id = event_presentation_id,
event_presenter_obj_new = event_presenter_obj,
create_sub_obj = create_sub_obj,
fail_any = fail_any,
):
if isinstance(create_event_presenter_obj_result, int):
event_presenter_id = create_event_presenter_obj_result
log.info(f'Event Presenter created. Event Presenter ID: {event_presenter_id}')
else:
log.warning(f'Event Presenter not created. Event Presentation ID: {event_presentation_id}')
log.debug(create_event_presenter_obj_result)
event_presenter_id = None
if fail_any: return False
else:
log.warning(f'Event Presenter not created. Event Presentation ID: {event_presentation_id}')
log.debug(create_event_presenter_obj_result)
event_presenter_id = None
if fail_any: return False
return_dict['event_presenter_list'].append(event_presenter_id)
else:
log.info('Event Presenter List not found')
pass
log.info(f'The Event Presentation has been created. Event Presentation ID: {event_presentation_id}')
return event_presentation_id
# ### END ### API Event Presentation Methods ### create_event_presentation_obj() ###
# ### BEGIN ### API Event Presentation Methods ### update_event_presentation_obj_v3() ###
# Updated 2022-04-12
@logger_reset
def update_event_presentation_obj_v3(
event_presentation_id: int|str,
event_presentation_obj_exist: Event_Presentation_Base,
event_session_id: int = None,
create_sub_obj: bool = False,
fail_any: bool = False, # Fail if any thing goes wrong for sub objects
) -> bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if event_presentation_id := redis_lookup_id_random(record_id_random=event_presentation_id, table_name='event_presentation'): pass
else: return False
log.debug(type(event_presentation_obj_exist))
if isinstance(event_presentation_obj_exist, dict):
event_presentation_dict = event_presentation_obj_exist
try:
event_presentation_obj = Event_Presentation_Base(**event_presentation_obj_exist)
except ValidationError as e:
log.error(e.json())
return False
log.debug(event_presentation_obj)
else:
event_presentation_dict = event_presentation_obj_exist.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'event_presenter', 'event_presenter_list', 'created_on', 'updated_on'})
# Can't update the event_presentation_id alias if the .id was never set.
# event_presentation_obj.event_presentation_id = event_presentation_id
if not event_presentation_obj.id:
event_presentation_obj.id = event_presentation_id
log.debug(event_presentation_dict)
# event_presentation_dict['event_id'] = event_id
if event_session_id:
event_presentation_dict['for_type'] = 'event_session'
event_presentation_dict['for_id'] = event_session_id
event_presentation_dict['event_session_id'] = event_session_id
log.debug(event_presentation_dict)
if event_presentation_obj_up_result := sql_update(data=event_presentation_dict, table_name='event_presentation', record_id=event_presentation_id, rm_id_random=True): pass
else:
log.warning(f'Event Presentation not updated.')
log.debug(event_presentation_obj_up_result)
return False
return_dict = {}
return_dict['event_presentation_id'] = event_presentation_id
return_dict['event_presenter_list'] = []
if event_presentation_obj.event_presenter_list and isinstance(event_presentation_obj.event_presenter_list, list):
for event_presenter_obj_unknown in event_presentation_obj.event_presenter_list:
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_presenter_obj_unknown)
if event_presenter_id := event_presenter_obj_unknown.get('event_presenter_id_random', None):
if update_event_presenter_obj_result := update_event_presenter_obj_v3(
event_presenter_id = event_presenter_id,
event_presenter_obj_exist = event_presenter_obj_unknown,
create_sub_obj = create_sub_obj,
fail_any = fail_any,
):
event_presenter_id = update_event_presenter_obj_result
log.info(f'Event Presenter updated. Event Presenter ID: {event_presenter_id}')
else:
log.warning(f'Event Presenter not updated. Event Presentation ID: {event_presentation_id}')
log.debug(update_event_presenter_obj_result)
event_presenter_id = None
if fail_any: return False
if isinstance(update_event_presenter_obj_result, int):
event_presenter_id = update_event_presenter_obj_result
log.info(f'Event Presenter updated. Event Presenter ID: {event_presenter_id}')
else:
log.warning(f'Event Presenter not updated. Event Presentation ID: {event_presentation_id}')
log.debug(update_event_presenter_obj_result)
event_presenter_id = None
if fail_any: return False
else:
log.info(f'No Event Presenter ID found.')
if create_event_presenter_obj_result := create_event_presenter_obj(
event_presentation_id = event_presentation_id,
event_presenter_obj_new = event_presenter_obj_unknown,
create_sub_obj = create_sub_obj,
fail_any = fail_any,
):
if isinstance(create_event_presenter_obj_result, int):
event_presenter_id = create_event_presenter_obj_result
log.info(f'Event Presenter created. Event Presenter ID: {event_presenter_id}')
else:
log.warning(f'Event Presenter not created. Event Presentation ID: {event_presentation_id}')
log.debug(create_event_presenter_obj_result)
event_presenter_id = None
if fail_any: return False
else:
log.warning(f'Event Presenter not created. Event Presentation ID: {event_presentation_id}')
log.debug(create_event_presenter_obj_result)
event_presenter_id = None
if fail_any: return False
return_dict['event_presenter_list'].append(event_presenter_id)
else:
log.info('Event Presenter List not found or not in a list.')
pass
log.info(f'The event presentation has been updated. Event Presentation ID: {event_presentation_id}')
return True
# ### END ### API Event Presentation Methods ### update_event_presentation_obj_v3() ###
# ### BEGIN ### API Event Presentation Methods ### update_event_presentation_obj() ###
# This will be taken over by _exist version
# Updated 2022-04-12
@logger_reset
def update_event_presentation_obj(
event_presentation_id: int|str, # Ideally the int ID should be passed. This allows for updating of the id_random value.
event_presentation_obj_up: Event_Presentation_Base,
create_sub_obj: bool = False,
) -> bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if event_presentation_id := redis_lookup_id_random(record_id_random=event_presentation_id, table_name='event_presentation'): pass
else: return False
event_presentation_obj_up.id = event_presentation_id
log.debug(event_presentation_obj_up)
# log.debug(event_presentation_obj_up.dict(by_alias=True, exclude_unset=True))
log.debug(event_presentation_obj_up.dict(by_alias=False, exclude_unset=True))
# log.debug(event_presentation_obj_up.dict(by_alias=False, exclude_unset=False))
# if event_presentation_obj_up.event_session_id and event_presentation_obj_up.event_session:
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
# event_session_id = event_presentation_obj_up.event_session_id
# event_session_obj_up = event_presentation_obj_up.event_session
# log.debug(event_session_id)
# log.debug(event_session_obj_up)
# if event_session_obj_up_result := update_event_session_obj(
# event_session_id=event_session_id,
# event_session_obj_up=event_session_obj_up,
# create_sub_obj=create_sub_obj,
# ):
# log.debug(event_session_obj_up_result)
# else:
# log.debug(event_session_obj_up_result)
# return False
# elif event_presentation_obj_up.event_session and not event_presentation_obj_up.event_session.id:
# # NOTE: This will blindly create a new event_session even if there was one associated but the event_presentation.event_session_id was not found.
# event_session_obj_in = event_presentation_obj_up.event_session
# log.debug(event_session_obj_in)
# if event_session_obj_in_result := create_event_session_obj(event_session_obj_new=event_session_obj_in):
# # log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
# log.debug(event_session_obj_in_result)
# event_presentation_obj_up.event_session_id = event_session_obj_in_result
# else:
# # log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
# log.debug(event_session_obj_in_result)
# return False
event_presentation_dict_up = event_presentation_obj_up.dict(by_alias=False, exclude_unset=True, exclude={'event_abstract', 'event_abstract_list', 'event_file_list', 'event_location', 'event_presenter_list', 'event_session', 'event_track'})
log.debug(event_presentation_dict_up)
if event_presentation_obj_up_result := sql_update(data=event_presentation_dict_up, table_name='event_presentation', rm_id_random=True):
log.debug(event_presentation_obj_up_result)
return True
else:
log.debug(event_presentation_obj_up_result)
return False
# ### END ### API Presentation Methods ### update_event_presentation_obj() ###

View File

@@ -0,0 +1,682 @@
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, logger_reset
from app.methods.event_cfg_methods import load_event_cfg_obj
from app.methods.event_person_methods import create_event_person_obj, load_event_person_obj, update_event_person_obj
# from app.methods.person_methods import load_person_obj
# from app.methods.user_methods import load_user_obj
from app.models.event_presenter_models import Event_Presenter_Base
# ### BEGIN ### API Event Presenter Methods ### load_event_presenter_obj() ###
@logger_reset
def load_event_presenter_obj(
event_presenter_id: int|str,
enabled: str = 'enabled', # enabled, disabled, all
hidden: str = 'not_hidden', # hidden, not_hidden, all
inc_file_count: bool = False, # NOTE: file counts are from separate views
event_file_file_purpose_id: int = None,
event_file_file_purpose: str = None,
event_file_priority: bool = None,
event_file_group: str = None,
inc_address: bool = False,
inc_contact: bool = False,
inc_event_abstract_list: bool = False, # For event_presenter and using load_event_person_obj
inc_event_badge: bool = False, # Using load_event_person_obj
inc_event_cfg: bool = False,
inc_event_device_list: bool = False, # For event_presenter and using load_event_person_obj
inc_event_file_list: bool = False, # For event_presenter and using load_event_person_obj
inc_event_person: bool = False, # Using load_event_person_obj
inc_event_person_profile: bool = False, # Using load_event_person_obj
inc_event_presentation: bool = False,
inc_event_presentation_list: bool = False, # Using load_event_session_obj
inc_event_registration: bool = False, # Using load_event_person_obj
inc_event_session: bool = False,
inc_person: bool = False, # Using load_event_person_obj
inc_user: bool = False, # Using load_event_person_obj
limit: int = 1000,
offset: int = 0,
by_alias: bool = True,
exclude_unset: bool = False,
model_as_dict: bool = False,
) -> Event_Presenter_Base|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if event_presenter_id := redis_lookup_id_random(record_id_random=event_presenter_id, table_name='event_presenter'): pass
else: return False
if inc_file_count:
log.info('Using view with file count')
if event_presenter_rec := sql_select(table_name='v_event_presenter_w_file_count', record_id=event_presenter_id): pass
else: return False
else:
if event_presenter_rec := sql_select(table_name='v_event_presenter', record_id=event_presenter_id): pass
else: return False
# log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_presenter_rec)
try:
log.info('Try to apply event presenter record data to Event_Presenter_Base...')
event_presenter_obj = Event_Presenter_Base(**event_presenter_rec)
log.debug(event_presenter_obj)
except ValidationError as e:
log.error(e.json())
return False
# event_presenter_obj = Event_Presenter_Base(**event_presenter_rec)
# log.debug(event_presenter_obj)
# account_id = event_presenter_rec.get('account_id', None)
event_id = event_presenter_rec.get('event_id', None)
event_abstract_id = event_presenter_rec.get('event_abstract_id', None)
event_person_id = event_presenter_rec.get('event_person_id', None)
event_presentation_id = event_presenter_rec.get('event_presentation_id', None)
event_session_id = event_presenter_rec.get('event_session_id', None)
person_id = event_presenter_rec.get('person_id', None)
user_id = event_presenter_rec.get('user_id', None)
# if inc_event: pass
if inc_event_abstract_list: pass
# Updated 2021-11-10
if inc_event_cfg:
log.info('Need to include event configuration...')
if event_cfg_result := load_event_cfg_obj(
event_id = event_id,
by_alias = by_alias,
# exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
):
event_presenter_obj.event_cfg = event_cfg_result
else: event_presenter_obj.event_cfg = None
if inc_event_device_list: pass
if inc_event_file_list:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.info('Need to include event file list...')
from app.methods.event_file_methods import get_event_file_rec_list, load_event_file_obj
if event_file_rec_list_result := get_event_file_rec_list(
for_type = 'event_presenter',
for_id = event_presenter_id,
file_purpose_id = event_file_file_purpose_id,
file_purpose = event_file_file_purpose,
priority = event_file_priority,
group = event_file_group,
enabled = enabled,
limit = limit,
):
event_file_result_list = []
for event_file_rec in event_file_rec_list_result:
if load_event_file_result := load_event_file_obj(
event_file_id = event_file_rec.get('event_file_id', None),
enabled = enabled,
# inc_hosted_file = inc_hosted_file,
# by_alias = by_alias,
# exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
):
event_file_result_list.append(load_event_file_result)
else:
event_file_result_list.append(None)
log.debug(event_file_result_list)
event_presenter_obj.event_file_list = event_file_result_list
elif isinstance(event_file_rec_list_result, list):
event_presenter_obj.event_file_list = []
else:
event_presenter_obj.event_file_list = None
if inc_event_person:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.info('Need to include event person...')
if event_person_obj := load_event_person_obj(
event_person_id = event_person_id,
enabled = enabled,
inc_address = inc_address,
inc_contact = inc_contact,
inc_event_badge = inc_event_badge,
inc_event_person_profile = inc_event_person_profile,
inc_event_registration = inc_event_registration,
inc_person = inc_person,
inc_user = inc_user,
):
log.debug(event_person_obj)
event_presenter_obj.event_person = event_person_obj
else:
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_person_obj)
event_presenter_obj.event_person = None
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
if inc_event_presentation:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.info('Need to include event presentation...')
from app.methods.event_presentation_methods import load_event_presentation_obj
if event_presentation_obj := load_event_presentation_obj(
event_presentation_id = event_presentation_id,
# Don't append the session, presentation list, and things
):
log.debug(event_presentation_obj)
event_presenter_obj.event_presentation = event_presentation_obj
else:
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_presentation_obj)
event_presenter_obj.event_presentation = None
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
if inc_event_session:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.info('Need to include event session...')
from app.methods.event_session_methods import load_event_session_obj
if event_session_obj := load_event_session_obj(
event_session_id = event_session_id,
inc_event_presentation_list = inc_event_presentation_list,
):
log.debug(event_session_obj)
event_presenter_obj.event_session = event_session_obj
else:
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_session_obj)
event_presenter_obj.event_session = None
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
if model_as_dict:
return event_presenter_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return event_presenter_obj
# ### END ### API Event Presenter Methods ### load_event_presenter_obj() ###
# ### BEGIN ### API Event Presenter Methods ### get_event_presenter_rec_list() ###
@logger_reset
def get_event_presenter_rec_list(
event_person_id: str = None,
event_presentation_id: str = None,
hidden: str = 'not_hidden', # hidden, not_hidden, all
enabled: str = 'enabled', # enabled, disabled, all
limit: int = 100,
offset: int = 0,
) -> list|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if event_person_id := redis_lookup_id_random(record_id_random=event_person_id, table_name='event_person'): pass
else: return False
data = {}
data['event_person_id'] = event_person_id
data['event_presentation_id'] = event_presentation_id
if event_person_id:
sql_where_event_person_id = f'`event_presenter`.event_person_id = :event_person_id'
else: sql_where_event_person_id = ''
if event_presentation_id:
sql_where_event_presentation_id = f'`event_presenter`.event_presentation_id = :event_presentation_id'
else: sql_where_event_presentation_id = ''
if hidden in ['hidden', 'not_hidden', 'all']:
if hidden == 'hidden':
data['hide'] = True
sql_hidden = f'AND `event_presenter`.hide = :hide'
elif hidden == 'not_hidden':
data['hide'] = False
sql_hidden = f'AND `event_presenter`.hide = :hide'
elif hidden == 'all':
sql_hidden = ''
if enabled in ['enabled', 'disabled', 'all']:
if enabled == 'enabled':
data['enable'] = True
sql_enabled = f'AND `event_presenter`.enable = :enable'
elif enabled == 'disabled':
data['enable'] = False
sql_enabled = f'AND `event_presenter`.enable = :enable'
elif enabled == 'all':
sql_enabled = ''
if limit:
data['limit'] = limit
sql_limit = f'LIMIT :limit'
else:
sql_limit = ''
sql = f"""
SELECT `event_presenter`.id AS 'event_presenter_id', `event_presenter`.id_random AS 'event_presenter_id_random'
FROM `event_presenter` AS `event_presenter`
WHERE
{sql_where_event_person_id}
{sql_where_event_presentation_id}
{sql_hidden}
{sql_enabled}
ORDER BY `event_presenter`.priority DESC, -`event_presenter`.sort DESC, `event_presenter`.family_name ASC, `event_presenter`.given_name ASC, `event_presenter`.full_name ASC, `event_presenter`.created_on DESC, `event_presenter`.updated_on DESC
{sql_limit};
"""
if event_presenter_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
event_presenter_rec_li = event_presenter_rec_li_result
else:
event_presenter_rec_li = []
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_presenter_rec_li_result)
log.debug(type(event_presenter_rec_li))
log.debug(len(event_presenter_rec_li))
return event_presenter_rec_li
# ### END ### API Event Presenter Methods ### get_event_presenter_rec_list() ###
# ### BEGIN ### API Event Presenter Methods ### get_event_session_id_w_event_presentation_id() ###
# Updated 2021-08-23
@logger_reset
def get_event_session_id_w_event_presentation_id(
event_presentation_id: int|str,
) -> bool|int|None:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if event_presentation_id := redis_lookup_id_random(record_id_random=event_presentation_id, table_name='event_presentation'): pass
else: return False
data = {}
data['event_presentation_id'] = event_presentation_id
sql = f"""
SELECT `event_presentation`.id AS 'event_presentation_id', `event_presentation`.id_random AS 'event_presentation_id_random', `event_presentation`.event_id AS event_id, `event_presentation`.event_session_id AS event_session_id
FROM `event_presentation` AS `event_presentation`
WHERE `event_presentation`.id = :event_presentation_id
LIMIT 1;
"""
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
if event_presentation_data_result := sql_select(data=data, sql=sql):
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_presentation_data_result)
if event_session_id := event_presentation_data_result.get('event_session_id', None): return event_session_id
else: return False
else: return None
# ### END ### API Event Presenter Methods ### get_event_session_id_w_event_presentation_id() ###
# ### BEGIN ### API Event Presenter Methods ### create_update_event_presenter_obj_v4() ###
# NOTE: This will create or update a event_presenter.
# Rewrite and updated 2021-08-25
@logger_reset
def create_update_event_presenter_obj_v4(
event_presenter_dict_obj: Event_Presenter_Base|dict,
event_presenter_id: int|str = None,
event_id: int|str = None,
event_session_id: int|str = None,
event_presentation_id: int|str = None,
create_sub_obj: bool = False,
fail_any: bool = False, # Fail if any thing goes wrong for sub objects
return_outline: bool = False,
) -> int|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
log.info('Checking requirements...')
if event_presenter_id:
log.info(f'Event Presenter ID passed. Update existing Event Presenter. Event Presenter ID: {event_presenter_id}')
if event_presenter_id := redis_lookup_id_random(record_id_random=event_presenter_id, table_name='event_presenter'): pass
else:
log.error('Event Presenter ID passed but is invalid. Failed requirement.')
return False
if event_id := redis_lookup_id_random(record_id_random=event_id, table_name='event'): pass
else:
log.error('Missing or invalid Event ID passed. Not required. Ignoring.')
log.info(f'Event ID: {event_id}')
log.info('Attempting to get Event ID from related object.')
from app.methods.event_methods import get_event_id_w_for_type_id
if event_id := get_event_id_w_for_type_id(for_type='event_session', for_id=event_session_id): pass
elif event_id := get_event_id_w_for_type_id(for_type='event_presentation', for_id=event_presentation_id): pass
elif event_id := get_event_id_w_for_type_id(for_type='event_presenter', for_id=event_presenter_id): pass
else:
log.error('Unable to get Event ID from related object.')
False
else:
log.info('No Event Presenter ID passed. Create new Event Presenter. Required: Account ID, Event ID')
if event_id := redis_lookup_id_random(record_id_random=event_id, table_name='event'): pass
else:
log.error('Missing or invalid Event ID passed. Failed requirement.')
log.info(f'Event ID: {event_id}')
log.info('Attempting to get Event ID from related object.')
from app.methods.event_methods import get_event_id_w_for_type_id
if event_id := get_event_id_w_for_type_id(for_type='event_session', for_id=event_session_id): pass
elif event_id := get_event_id_w_for_type_id(for_type='event_presentation', for_id=event_presentation_id): pass
else:
log.error('Unable to get Event ID from related object.')
False
log.debug(type(event_presenter_dict_obj))
if isinstance(event_presenter_dict_obj, dict):
event_presenter_dict = event_presenter_dict_obj
if event_presenter_id:
event_presenter_dict['event_presenter_id'] = event_presenter_id
if event_presentation_id:
event_presenter_dict['event_presentation_id'] = event_presentation_id
if event_session_id:
event_presenter_dict['event_session_id'] = event_session_id
if event_id:
event_presenter_dict['event_id'] = event_id
try:
event_presenter_obj = Event_Presenter_Base(**event_presenter_dict)
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_presenter_obj)
except ValidationError as e:
log.error(e.json())
return False
else:
event_presenter_obj = event_presenter_dict_obj
if event_presenter_id:
# NOTE: Can't update the ID alias if it was never set.
event_presenter_obj.id = event_presenter_id
if event_presentation_id:
event_presenter_obj.event_presentation_id = event_presentation_id
if event_session_id:
event_presenter_obj.event_session_id = event_session_id
if event_id:
event_presenter_obj.event_id = event_id
event_presenter_dict = event_presenter_obj.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'created_on', 'updated_on'})
if event_presenter_id:
if event_presenter_dict_up_result := sql_update(data=event_presenter_dict, table_name='event_presenter', rm_id_random=True): pass
else:
log.warning(f'Event Presenter not updated. Event Presenter ID: {event_presenter_id}')
log.debug(event_presenter_dict_up_result)
return False
log.debug(event_presenter_dict_up_result)
else:
if event_presenter_dict_in_result := sql_insert(data=event_presenter_dict, table_name='event_presenter', rm_id_random=True, id_random_length=8): pass
else:
log.warning(f'Event Presenter not created.')
log.debug(event_presenter_dict_in_result)
return False
log.debug(event_presenter_dict_in_result)
event_presenter_id = event_presenter_dict_in_result
event_presenter_outline = {}
event_presenter_outline['event_id'] = event_id
event_presenter_outline['event_session_id'] = event_session_id
event_presenter_outline['event_presentation_id'] = event_presentation_id
event_presenter_outline['event_presenter_id'] = event_presenter_id
if return_outline:
log.debug(f'Returning the Event Presenter Outline: {event_presenter_outline}')
return event_presenter_outline
else:
log.debug(f'Returning the Event Presenter ID: {event_presenter_id}')
return event_presenter_id
# ### END ### API Event Presenter Methods ### create_update_event_presenter_obj_v4() ###
# ### BEGIN ### API Event Presenter Methods ### create_event_presenter_obj() ###
# Updated 2022-04-12
@logger_reset
def create_event_presenter_obj(
event_presentation_id: int|str,
event_presenter_obj_new: Event_Presenter_Base,
event_id: int|str = None, # If None then need to look up from event_session_id
event_session_id: int|str = None, # If None then need to look up from event_presentation_id
create_sub_obj: bool = False,
fail_any: bool = False, # Fail if any thing goes wrong for sub objects
) -> bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if event_session_id := redis_lookup_id_random(record_id_random=event_session_id, table_name='event_session'): pass
else:
# from app.methods.event_presentation_methods import get_event_session_id_w_event_presentation_id
if event_session_id := get_event_session_id_w_event_presentation_id(event_presentation_id): pass
else:
log.warning(f'The event_session_id was not found using the event_presentation_id. Event Presentation ID: {event_presentation_id}')
return event_session_id # False or None
if event_id := redis_lookup_id_random(record_id_random=event_id, table_name='event'): pass
else:
from app.methods.event_presentation_methods import get_event_id_w_event_session_id
if event_id := get_event_id_w_event_session_id(event_session_id): pass
else:
log.warning(f'The event_id was not found using the event_session_id. Event Session ID: {event_session_id}')
return event_id # False or None
if event_session_id := redis_lookup_id_random(record_id_random=event_session_id, table_name='event_session'): pass
else: return False
log.debug(type(event_presenter_obj_new))
if isinstance(event_presenter_obj_new, dict):
event_presenter_dict = event_presenter_obj_new
try:
event_presenter_obj = Event_Presenter_Base(**event_presenter_obj_new)
except ValidationError as e:
log.error(e.json())
return False
log.debug(event_presenter_obj)
else:
event_presenter_dict = event_presenter_obj_new.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'created_on', 'updated_on'})
event_presenter_dict['for_type'] = 'event_presentation'
event_presenter_dict['for_id'] = event_presentation_id
event_presenter_dict['event_id'] = event_id
event_presenter_dict['event_session_id'] = event_session_id
event_presenter_dict['event_presentation_id'] = event_presentation_id
log.debug(event_presenter_dict)
if event_presenter_obj_in_result := sql_insert(data=event_presenter_dict, table_name='event_presenter', rm_id_random=True, id_random_length=8): pass
else:
log.warning(f'Event Presenter not created.')
log.debug(event_presenter_obj_in_result)
return False
event_presenter_id = event_presenter_obj_in_result
return_dict = {}
return_dict['event_presenter_id'] = None
log.info(f'The Event Presenter has been created. Event Presenter ID: {event_presenter_id}')
return event_presenter_id
# ### END ### API Event Presenter Methods ### create_event_presenter_obj() ###
# ### BEGIN ### API Event Presenter Methods ### update_event_presenter_obj_v3() ###
# Updated 2022-04-12
@logger_reset
def update_event_presenter_obj_v3(
event_presenter_id: int|str,
event_presenter_obj_exist: Event_Presenter_Base,
event_presentation_id: int = None,
create_sub_obj: bool = False,
fail_any: bool = False, # Fail if any thing goes wrong for sub objects
) -> bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if event_presenter_id := redis_lookup_id_random(record_id_random=event_presenter_id, table_name='event_presenter'): pass
else: return False
log.debug(type(event_presenter_obj_exist))
if isinstance(event_presenter_obj_exist, dict):
event_presenter_dict = event_presenter_obj_exist
try:
event_presenter_obj = Event_Presenter_Base(**event_presenter_obj_exist)
except ValidationError as e:
log.error(e.json())
return False
log.debug(event_presenter_obj)
else:
event_presenter_dict = event_presenter_obj_exist.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'event_presenter', 'event_presenter_list', 'created_on', 'updated_on'})
# Can't update the event_presenter_id alias if the .id was never set.
# event_presenter_obj.event_presenter_id = event_presenter_id
if not event_presenter_obj.id:
event_presenter_obj.id = event_presenter_id
# event_presenter_dict['event_id'] = event_id
# event_presenter_dict['event_session_id'] = event_session_id
if event_presentation_id:
event_presenter_dict['for_type'] = 'event_presentation'
event_presenter_dict['for_id'] = event_presentation_id
event_presenter_dict['event_presentation_id'] = event_presentation_id
log.debug(event_presenter_dict)
if event_presenter_obj_up_result := sql_update(data=event_presenter_dict, table_name='event_presenter', record_id=event_presenter_id, rm_id_random=True): pass
else:
log.warning(f'Event Presenter not updated.')
log.debug(event_presenter_obj_up_result)
return False
return_dict = {}
return_dict['event_presenter_id'] = event_presenter_id
log.info(f'The event presenter has been updated. Event Presenter ID: {event_presenter_id}')
return True
# ### END ### API Event Presenter Methods ### update_event_presenter_obj_v3() ###
# ### BEGIN ### API Event Presenter Methods ### update_event_presenter_obj() ###
# This will be taken over by _exist version
@logger_reset
def update_event_presenter_obj(
event_presenter_id: int|str, # Ideally the int ID should be passed. This allows for updating of the id_random value.
event_presenter_obj_up: Event_Presenter_Base,
create_sub_obj: bool = False,
) -> bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if event_presenter_id := redis_lookup_id_random(record_id_random=event_presenter_id, table_name='event_presenter'): pass
else: return False
event_presenter_obj_up.id = event_presenter_id
log.debug(event_presenter_obj_up)
# log.debug(event_presenter_obj_up.dict(by_alias=True, exclude_unset=True))
log.debug(event_presenter_obj_up.dict(by_alias=False, exclude_unset=True))
# log.debug(event_presenter_obj_up.dict(by_alias=False, exclude_unset=False))
if event_presenter_obj_up.event_person_id and event_presenter_obj_up.event_person:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
event_person_id = event_presenter_obj_up.event_person_id
event_person_obj_up = event_presenter_obj_up.event_person
log.debug(event_person_id)
log.debug(event_person_obj_up)
if event_person_obj_up_result := update_event_person_obj(
event_person_id=event_person_id,
event_person_obj_up=event_person_obj_up,
create_sub_obj=create_sub_obj,
):
log.debug(event_person_obj_up_result)
else:
log.debug(event_person_obj_up_result)
return False
elif event_presenter_obj_up.event_person and not event_presenter_obj_up.event_person.id:
# NOTE: This will blindly create a new event_person even if there was one associated but the event_presenter.event_person_id was not found.
event_person_obj_in = event_presenter_obj_up.event_person
log.debug(event_person_obj_in)
if event_person_obj_in_result := create_event_person_obj(event_person_obj_new=event_person_obj_in):
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_person_obj_in_result)
event_presenter_obj_up.event_person_id = event_person_obj_in_result
else:
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_person_obj_in_result)
return False
# if event_presenter_obj_up.event_presentation_id and event_presenter_obj_up.event_presentation:
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
# event_presentation_id = event_presenter_obj_up.event_presentation_id
# event_presentation_obj_up = event_presenter_obj_up.event_presentation
# log.debug(event_presentation_id)
# log.debug(event_presentation_obj_up)
# if event_presentation_obj_up_result := update_event_presentation_obj(
# event_presentation_id=event_presentation_id,
# event_presentation_obj_up=event_presentation_obj_up,
# create_sub_obj=create_sub_obj,
# ):
# log.debug(event_presentation_obj_up_result)
# else:
# log.debug(event_presentation_obj_up_result)
# return False
# elif event_presenter_obj_up.event_presentation and not event_presenter_obj_up.event_presentation.id:
# # NOTE: This will blindly create a new event_presentation even if there was one associated but the event_presenter.event_presentation_id was not found.
# event_presentation_obj_in = event_presenter_obj_up.event_presentation
# log.debug(event_presentation_obj_in)
# if event_presentation_obj_in_result := create_event_presentation_obj(event_presentation_obj_new=event_presentation_obj_in):
# # log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
# log.debug(event_presentation_obj_in_result)
# event_presenter_obj_up.event_presentation_id = event_presentation_obj_in_result
# else:
# # log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
# log.debug(event_presentation_obj_in_result)
# return False
# if event_presenter_obj_up.event_session_id and event_presenter_obj_up.event_session:
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
# event_session_id = event_presenter_obj_up.event_session_id
# event_session_obj_up = event_presenter_obj_up.event_session
# log.debug(event_session_id)
# log.debug(event_session_obj_up)
# if event_session_obj_up_result := update_event_session_obj(
# event_session_id=event_session_id,
# event_session_obj_up=event_session_obj_up,
# create_sub_obj=create_sub_obj,
# ):
# log.debug(event_session_obj_up_result)
# else:
# log.debug(event_session_obj_up_result)
# return False
# elif event_presenter_obj_up.event_session and not event_presenter_obj_up.event_session.id:
# # NOTE: This will blindly create a new event_session even if there was one associated but the event_presenter.event_session_id was not found.
# event_session_obj_in = event_presenter_obj_up.event_session
# log.debug(event_session_obj_in)
# if event_session_obj_in_result := create_event_session_obj(event_session_obj_new=event_session_obj_in):
# # log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
# log.debug(event_session_obj_in_result)
# event_presenter_obj_up.event_session_id = event_session_obj_in_result
# else:
# # log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
# log.debug(event_session_obj_in_result)
# return False
event_presenter_dict_up = event_presenter_obj_up.dict(by_alias=False, exclude_unset=True, exclude={'event_abstract', 'event_abstract_list', 'event_file_list', 'event_person', 'event_presentation', 'event_session', 'person', 'user'})
log.debug(event_presenter_dict_up)
if event_presenter_obj_up_result := sql_update(data=event_presenter_dict_up, table_name='event_presenter', rm_id_random=True):
log.debug(event_presenter_obj_up_result)
return True
else:
log.debug(event_presenter_obj_up_result)
return False
# ### END ### API Presenter Methods ### update_event_presenter_obj() ###

View File

@@ -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() ###

View File

@@ -0,0 +1,326 @@
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.address_methods import load_address_obj
# from app.methods.contact_methods import load_contact_obj
from app.methods.event_person_methods import get_event_person_rec_list, load_event_person_obj
from app.methods.event_registration_cfg_methods import load_event_registration_cfg_obj
# from app.methods.person_methods import load_person_obj
from app.models.event_registration_models import Event_Registration_Base
from app.models.event_cfg_models import Event_Cfg_Base
# ### BEGIN ### API Event Registration Methods ### load_event_registration_obj() ###
def load_event_registration_obj(
event_registration_id: int|str,
limit: int = 1000,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
enabled: str = 'enabled', # enabled, disabled, all
inc_address: bool = False, # Under contact
inc_contact: bool = False,
# inc_event_cfg: bool = False,
inc_event_person_list: bool = False,
inc_event_registration_cfg: bool = False,
# inc_event_registration_list: bool = False,
inc_person: bool = False,
) -> Event_Registration_Base|bool:
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if event_registration_id := redis_lookup_id_random(record_id_random=event_registration_id, table_name='event_registration'): pass
else: return False
if event_registration_rec := sql_select(table_name='v_event_registration', record_id=event_registration_id): pass
else: return False
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_registration_rec)
try:
log.info('Try to apply event registration record data to Event_Registration_Base...')
event_registration_obj = Event_Registration_Base(**event_registration_rec)
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_registration_obj)
except ValidationError as e:
log.error(e.json())
return False
# Updated 2021-08-17
if inc_event_registration_cfg:
log.info('Need to include event registration configuration...')
event_id = event_registration_rec.get('event_id', None)
log.info(f'Need to include event registration config for event_id {event_id}...')
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_registration_obj.cfg = event_registration_cfg_result
else: event_registration_obj.event_registration_cfg = None
# Updated 2021-08-17
if inc_event_person_list:
log.info('Need to include event person list...')
if event_person_rec_list_result := get_event_person_rec_list(
for_obj_type = 'event_registration',
for_obj_id = event_registration_id,
limit = limit,
enabled = enabled,
):
event_person_result_list = []
for event_person_rec in event_person_rec_list_result:
if load_event_person_result := load_event_person_obj(
event_person_id = event_person_rec.get('event_person_id', None),
limit = limit,
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
enabled = enabled,
inc_person = inc_person,
# inc_user = inc_user,
):
event_person_result_list.append(load_event_person_result)
else: event_person_result_list.append(None)
event_registration_obj.event_person_list = event_person_result_list
else: event_registration_obj.event_person_list = []
if model_as_dict:
return event_registration_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return event_registration_obj
# ### END ### API Event Registration Methods ### load_event_registration_obj() ###
# ### BEGIN ### API Event Registration Methods ### get_event_id_w_event_person_id() ###
# Updated 2021-08-23
def get_event_id_w_event_person_id(
event_person_id: int|str,
) -> bool|int|None:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if event_person_id := redis_lookup_id_random(record_id_random=event_person_id, table_name='event_person'): pass
else: return False
data = {}
data['event_person_id'] = event_person_id
sql = f"""
SELECT `event_person`.id AS 'event_person_id', `event_person`.id_random AS 'event_person_id_random', `event_person`.event_id AS event_id
FROM `event_person` AS `event_person`
WHERE `event_person`.id = :event_person_id
LIMIT 1;
"""
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
if event_person_data_result := sql_select(data=data, sql=sql):
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_person_data_result)
if event_id := event_person_data_result.get('event_id', None): return event_id
else: return False
else: return None
# ### END ### API Event Registration Methods ### get_event_id_w_event_person_id() ###
# ### BEGIN ### API Event Registration Methods ### create_event_registration_obj() ###
# Event Registration can only update the primary event_person because they should already exist before registration is started. Chicken and egg problem...
# Event Registration can create or update the event_person_list.
# NOTE: Should there be a check before trying to update the primary event_person if they are also in the event_person_list??? Chicken and egg problem again?
# Updated 2021-08-25
def create_event_registration_obj(
event_id: int|str,
event_person_id: int|str,
event_registration_obj_new: Event_Registration_Base,
create_sub_obj: bool = False,
fail_any: bool = False, # Fail if any thing goes wrong for sub objects
) -> bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if event_person_id := redis_lookup_id_random(record_id_random=event_person_id, table_name='event_person'): pass
else: return False # The event_person should be created first (before registration and similar)
if event_id := redis_lookup_id_random(record_id_random=event_id, table_name='event'): pass
else:
if event_id := get_event_id_w_event_person_id(event_person_id): pass
else:
log.warning(f'The event_id was not found using the event_person_id. Event Person ID: {event_person_id}')
return event_id # False or None
log.debug(type(event_registration_obj_new))
if isinstance(event_registration_obj_new, dict):
try:
event_registration_obj_new = Event_Registration_Base(**event_registration_obj_new)
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_registration_obj_new)
except ValidationError as e:
log.error(e.json())
return False
event_registration_obj_new.event_id = event_id
event_registration_obj_new.event_person_id = event_person_id
event_registration_obj_data = event_registration_obj_new.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'event_person', 'event_person_list', 'created_on', 'updated_on'})
log.debug(event_registration_obj_data)
if event_registration_obj_in_result := sql_insert(data=event_registration_obj_data, table_name='event_registration', rm_id_random=True, id_random_length=8): pass
else:
log.warning(f'Event Registration not created.')
log.debug(event_registration_obj_in_result)
return False
event_registration_id = event_registration_obj_in_result
return_dict = {}
return_dict['event_registration_id'] = None
return_dict['event_person_id'] = None
return_dict['event_id'] = None
return_dict['event_person_list'] = []
# NOTE: Primary Event Person update check is not finished
if event_registration_obj_new.event_person:
# What happens here?
# Update only... do not create a new event_person here.
pass
if event_registration_obj_new.event_person_list and isinstance(event_registration_obj_new.event_person_list, list):
for event_person_obj_new in event_registration_obj_new.event_person_list:
# NOTE: This does not account for an edge case where the person already exists. Possibly as part of another registration, which should *not* happen.
if create_event_person_obj_result := create_event_person_obj(
event_registration_id = event_registration_id,
event_person_obj_new = event_person_obj_new,
create_sub_obj = create_sub_obj,
fail_any = fail_any,
):
if isinstance(create_event_person_obj_result, int):
event_person_id = create_event_person_obj_result
log.info(f'Event Person created. Event Person ID: {event_person_id}')
else:
log.warning(f'Event Person not created. Event Registration ID: {event_registration_id}')
log.debug(create_event_person_obj_result)
event_person_id = None
if fail_any: return False
else:
log.warning(f'Event Person not created. Event Registration ID: {event_registration_id}')
log.debug(create_event_person_obj_result)
event_person_id = None
if fail_any: return False
return_dict['event_person_list'].append(event_person_id)
else:
log.info('Event Person List not found')
pass
log.info(f'The event registration has been created. Event Registration ID: {event_registration_id}')
return event_registration_id
# ### END ### API Event Registration Methods ### create_event_registration_obj() ###
# ### BEGIN ### API Event Registration Methods ### update_event_registration_obj_v3() ###
# Event Registration can only update the primary event_person because they should already exist since registration is started and registration "requires" the primary event_person first. Chicken and egg problem...
# Event Registration can create or update the event_person_list.
# NOTE: Should there be a check before trying to update the primary event_person if they are also in the event_person_list??? Chicken and egg problem again?
# Updated 2021-08-25
def update_event_registration_obj_v3(
event_registration_id: int|str,
event_registration_obj_exist: Event_Registration_Base,
create_sub_obj: bool = False,
fail_any: bool = False, # Fail if any thing goes wrong for sub objects
) -> bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if event_registration_id := redis_lookup_id_random(record_id_random=event_registration_id, table_name='event_registration'): pass
else: return False
# Can't update the event_registration_id alias if the .id was never set.
# event_registration_obj_exist.event_registration_id = event_registration_id
if not event_registration_obj_exist.id:
event_registration_obj_exist.id = event_registration_id
event_registration_obj_data = event_registration_obj_exist.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'event_person', 'event_person_list', 'created_on', 'updated_on'})
log.debug(event_registration_obj_data)
if event_registration_obj_up_result := sql_update(data=event_registration_obj_data, table_name='event_registration', rm_id_random=True): pass
else:
log.warning(f'Event Registration not updated.')
log.debug(event_registration_obj_up_result)
return False
return_dict = {}
return_dict['event_registration_id'] = None
return_dict['event_person_id'] = None
return_dict['event_id'] = None
return_dict['event_person_list'] = []
# NOTE: Primary Event Person update check is not finished
if event_registration_obj_exist.event_person:
# What happens here?
# Update only... do not create a new event_person here.
pass
if event_registration_obj_exist.event_person_list and isinstance(event_registration_obj_exist.event_person_list, list):
for event_person_obj_unknown in event_registration_obj_exist.event_person_list:
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_person_obj_unknown)
if event_person_id := event_person_obj_unknown.get('event_person_id_random', None):
if update_event_person_obj_result := update_event_person_obj_v3(
event_person_id = event_person_id,
event_person_obj_exist = event_person_obj_unknown,
create_sub_obj = create_sub_obj,
fail_any = fail_any,
):
event_person_id = update_event_person_obj_result
log.info(f'Event Person updated. Event Person ID: {event_person_id}')
else:
log.warning(f'Event Person not updated. Event Registration ID: {event_registration_id}')
log.debug(update_event_person_obj_result)
event_person_id = None
if fail_any: return False
if isinstance(update_event_person_obj_result, int):
event_person_id = update_event_person_obj_result
log.info(f'Event Person updated. Event Person ID: {event_person_id}')
else:
log.warning(f'Event Person not updated. Event Registration ID: {event_registration_id}')
log.debug(update_event_person_obj_result)
event_person_id = None
if fail_any: return False
else:
log.info(f'No Event Person ID found.')
if create_event_person_obj_result := create_event_person_obj(
event_registration_id = event_registration_id,
event_person_obj_new = event_person_obj_unknown,
create_sub_obj = create_sub_obj,
fail_any = fail_any,
):
if isinstance(create_event_person_obj_result, int):
event_person_id = create_event_person_obj_result
log.info(f'Event Person created. Event Person ID: {event_person_id}')
else:
log.warning(f'Event Person not created. Event Registration ID: {event_registration_id}')
log.debug(create_event_person_obj_result)
event_person_id = None
if fail_any: return False
else:
log.warning(f'Event Person not created. Event Registration ID: {event_registration_id}')
log.debug(create_event_person_obj_result)
event_person_id = None
if fail_any: return False
return_dict['event_person_list'].append(event_person_id)
else:
log.info('Event Person List not found or not in a list.')
pass
log.info(f'The event registration has been updated. Event Registration ID: {event_registration_id}')
return True
# ### END ### API Event Registration Methods ### update_event_registration_obj_v3() ###

View File

@@ -0,0 +1,836 @@
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_enable_part, sql_insert, sql_limit_offset_part, sql_select, sql_update
from app.lib_general import log, logging, logger_reset
# from app.methods.event_methods import load_event_obj
from app.methods.event_file_methods import load_event_file_obj_list
from app.methods.event_location_methods import load_event_location_obj, get_event_location_rec_list
from app.methods.event_person_methods import load_event_person_obj, update_event_person_obj
from app.methods.event_presentation_methods import create_event_presentation_obj, create_update_event_presentation_obj_v4, load_event_presentation_obj, update_event_presentation_obj_v3
# from app.methods.event_presenter_methods import load_event_presenter_obj
from app.methods.person_methods import load_person_obj
# from app.methods.user_methods import load_user_obj
from app.models.event_session_models import Event_Session_Base
# ### BEGIN ### API Event Session Methods ### load_event_session_obj() ###
# Updated 2022-09-23
@logger_reset
def load_event_session_obj(
event_session_id: int|str,
enabled: str = 'enabled', # enabled, disabled, all
hidden: str = 'not_hidden', # hidden, not_hidden, all
inc_file_count: bool = False, # NOTE: file counts are from separate views
event_file_file_purpose_id: int = None,
event_file_file_purpose: str = None,
event_file_priority: bool = None,
event_file_group: str = None,
inc_address: bool = False,
inc_contact: bool = False,
inc_event_abstract_list: bool = False,
inc_event_badge_list: bool = False,
inc_event_device_list: bool = False,
inc_event_file_list: bool = False,
inc_event_file_internal_use_list: bool = False,
inc_event_location: bool = False,
inc_event_location_list: bool = False,
inc_event_person: bool = False,
inc_event_person_profile: bool = False,
inc_event_person_list: bool = False,
inc_event_presentation_list: bool = False,
inc_event_presenter_cat: bool = False, # Concatenate presenter names
inc_event_presenter_list: bool = False,
inc_event_registration_list: bool = False,
inc_event_track: bool = False,
inc_hosted_file: bool = False,
inc_poc_event_person: bool = False,
inc_person: bool = False,
inc_user: bool = False,
limit: int = 1000,
offset: int = 0,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
) -> Event_Session_Base|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if event_session_id := redis_lookup_id_random(record_id_random=event_session_id, table_name='event_session'): pass
else: return False
if inc_file_count:
log.info('Using view with file count')
if event_session_rec := sql_select(table_name='v_event_session_w_file_count', record_id=event_session_id): pass
else:
return False
else:
if event_session_rec := sql_select(table_name='v_event_session', record_id=event_session_id): pass
else:
return False
log.debug(event_session_rec)
try:
event_session_obj = Event_Session_Base(**event_session_rec)
log.debug(event_session_obj)
except ValidationError as e:
log.error(e.json())
return False
account_id = event_session_rec.get('account_id', None)
event_id = event_session_rec.get('event_id', None)
event_location_id = event_session_rec.get('event_location_id', None)
event_track_id = event_session_rec.get('event_track_id', None)
poc_event_person_id = event_session_rec.get('poc_event_person_id', None)
#if inc_event: pass
if inc_event_abstract_list: pass
if inc_event_badge_list: pass
if inc_event_device_list: pass
# Updated 2021-10-21
if inc_event_file_list:
log.info('Need to include event file list...')
from app.methods.event_file_methods import get_event_file_rec_list, load_event_file_obj
if event_file_rec_list_result := get_event_file_rec_list(
for_type = 'event_session',
for_id = event_session_id,
file_purpose_id = event_file_file_purpose_id,
file_purpose = event_file_file_purpose,
internal_use = False,
priority = event_file_priority,
group = event_file_group,
enabled = enabled,
limit = limit,
):
event_file_result_list = []
for event_file_rec in event_file_rec_list_result:
if load_event_file_result := load_event_file_obj(
event_file_id = event_file_rec.get('event_file_id', None),
enabled = enabled,
inc_hosted_file = inc_hosted_file,
):
event_file_result_list.append(load_event_file_result)
else:
event_file_result_list.append(None)
log.debug(event_file_result_list)
event_session_obj.event_file_list = event_file_result_list
elif isinstance(event_file_rec_list_result, list):
event_session_obj.event_file_list = []
else:
event_session_obj.event_file_list = None
if inc_event_file_internal_use_list:
log.info('Need to include event file internal use list...')
from app.methods.event_file_methods import get_event_file_rec_list, load_event_file_obj
if event_file_rec_list_result := get_event_file_rec_list(
for_type = 'event_session',
for_id = event_session_id,
file_purpose_id = event_file_file_purpose_id,
file_purpose = event_file_file_purpose,
internal_use = True,
priority = event_file_priority,
group = event_file_group,
enabled = enabled,
limit = limit,
):
event_file_result_list = []
for event_file_rec in event_file_rec_list_result:
if load_event_file_result := load_event_file_obj(
event_file_id = event_file_rec.get('event_file_id', None),
enabled = enabled,
inc_hosted_file = inc_hosted_file,
):
event_file_result_list.append(load_event_file_result)
else:
event_file_result_list.append(None)
log.debug(event_file_result_list)
event_session_obj.event_file_internal_use_list = event_file_result_list
elif isinstance(event_file_rec_list_result, list):
event_session_obj.event_file_internal_use_list = []
else:
event_session_obj.event_file_internal_use_list = None
log.debug(f'Get event location? Include Event Location: {inc_event_location} Event Location ID: {event_location_id}')
if inc_event_location and event_location_id:
log.info('Need to include event location...')
if event_location_obj := load_event_location_obj(
event_location_id = event_location_id,
enabled = enabled,
):
event_session_obj.event_location = event_location_obj.dict(by_alias=True, exclude_unset=True)
else:
event_session_obj.event_location = None
else:
event_session_obj.event_location = None
# Updated 2022-09-20
if inc_event_location_list:
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.info('Need to include event location list...')
if event_location_rec_list_result := get_event_location_rec_list(
event_id = event_id,
enabled = enabled, # enabled, disabled, all
hidden = 'all', # hidden, not_hidden, all
limit = limit,
):
event_location_result_list = []
for event_location_rec in event_location_rec_list_result:
if load_event_location_result := load_event_location_obj(
event_location_id = event_location_rec.get('event_location_id', None),
enabled = enabled,
limit = limit,
):
event_location_result_list.append(load_event_location_result)
else:
event_location_result_list.append(None)
log.debug(event_location_result_list)
event_session_obj.event_location_list = event_location_result_list
elif isinstance(event_location_rec_list_result, list):
event_session_obj.event_location_list = []
else:
event_session_obj.event_location_list = None
if inc_event_person_list: pass
if inc_event_presentation_list:
log.info('Need to include event presentation list...')
data = {}
data['event_session_id'] = event_session_id
if hidden in ['hidden', 'not_hidden', 'all']:
if hidden == 'hidden':
data['hide'] = True
sql_hidden = f'AND `event_presentation`.hide = :hide'
elif hidden == 'not_hidden':
data['hide'] = False
sql_hidden = f'AND `event_presentation`.hide = :hide'
elif hidden == 'all':
sql_hidden = ''
if enabled in ['enabled', 'disabled', 'all']:
if enabled == 'enabled':
data['enable'] = True
sql_enabled = f'AND `event_presentation`.enable = :enable'
elif enabled == 'disabled':
data['enable'] = False
sql_enabled = f'AND `event_presentation`.enable = :enable'
elif enabled == 'all':
sql_enabled = ''
# else: event_obj['event_session'] = None
# if limit:
# data['limit'] = limit
# sql_limit = f'LIMIT :limit'
# else:
# sql_limit = ''
sql = f"""
SELECT `event_presentation`.id AS 'event_presentation_id', `event_presentation`.id_random AS 'event_presentation_id_random'
FROM `event_presentation` AS `event_presentation`
WHERE `event_presentation`.event_session_id = :event_session_id
{sql_hidden}
{sql_enabled}
ORDER BY `event_presentation`.priority DESC, -`event_presentation`.sort DESC, `event_presentation`.start_datetime ASC, `event_presentation`.name ASC, `event_presentation`.created_on DESC, `event_presentation`.updated_on DESC;
"""
if event_presentation_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
log.debug(event_presentation_rec_li_result)
event_presentation_obj_li = []
for event_presentation_rec in event_presentation_rec_li_result:
event_presentation_id = event_presentation_rec.get('event_presentation_id', None)
if event_presentation_obj := load_event_presentation_obj(
event_presentation_id = event_presentation_id,
enabled = enabled,
# review = review,
# approved = approved,
hidden = hidden,
inc_file_count = inc_file_count,
inc_address = inc_address,
inc_contact = inc_contact,
inc_event_abstract_list = inc_event_abstract_list,
inc_event_device_list = inc_event_device_list,
inc_event_file_list = inc_event_file_list,
inc_event_person = inc_event_person,
inc_event_person_profile = inc_event_person_profile,
inc_event_person_list = inc_event_person_list,
inc_event_presenter_list = inc_event_presenter_list,
inc_person = inc_person,
inc_user = inc_user
):
data = event_presentation_obj.dict(by_alias=True, exclude_unset=True)
event_presentation_obj_li.append(data)
log.debug(event_presentation_obj_li)
event_session_obj.event_presentation_list = event_presentation_obj_li
else:
log.debug(event_presentation_rec_li_result)
event_session_obj.event_presentation_list = []
if inc_event_presenter_cat:
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.info(f'Need to get event presenter cat list for session. Event Session ID: {event_session_id}')
data = {}
data['event_session_id'] = event_session_id
sql = f"""
SELECT event_session_id, GROUP_CONCAT(given_name, ' ', family_name SEPARATOR '; ') AS 'event_presenter_names'
FROM event_presenter
WHERE event_session_id = :event_session_id
GROUP BY event_session_id
LIMIT 1
"""
if event_presenter_cat_rec_result := sql_select(data=data, sql=sql):
log.debug(event_presenter_cat_rec_result.get('event_presenter_names', None))
event_session_obj.event_presenter_cat = event_presenter_cat_rec_result.get('event_presenter_names', None)
else:
log.info(event_presenter_cat_rec_result)
event_session_obj.event_presenter_cat = None
if inc_event_presenter_list: pass
if inc_poc_event_person:
poc_event_person_obj = load_event_person_obj(
event_person_id = poc_event_person_id,
inc_address = inc_address,
inc_contact = inc_contact,
inc_person = inc_person,
inc_user = inc_user,
)
log.debug(poc_event_person_obj)
event_session_obj.poc_event_person = poc_event_person_obj
log.debug(event_session_obj)
if model_as_dict:
return event_session_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return event_session_obj
# ### END ### API Event Session Methods ### load_event_session_obj() ###
# ### BEGIN ### API Event Session Methods ### get_event_session_rec_list() ###
# Updated 2022-09-22
@logger_reset
def get_event_session_rec_list(
event_id: str = None,
event_location_id: str = None,
event_track_id: str = None,
enabled: str = 'enabled', # enabled, disabled, all
approved: str = 'all', # approved, not_approved, all
hidden: str = 'not_hidden', # hidden, not_hidden, all
review: str = 'all', # ready, not_ready, all
limit: int = 150,
offset: int = 0,
) -> list|bool:
log.setLevel(logging.INFO) # 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_location_id := redis_lookup_id_random(record_id_random=event_location_id, table_name='event_location'): pass
# else: return False
data = {}
if event_id:
data[f'event_id'] = event_id
sql_where_type_id = f'`event_session`.event_id = :event_id'
elif event_location_id:
data[f'event_location_id'] = event_location_id
sql_where_type_id = f'`event_session`.event_location_id = :event_location_id'
if review in ['ready', 'not_ready', 'all']:
if review == 'ready':
data['review'] = True
sql_review = f'AND `event_session`.review = :review'
elif review == 'not_ready':
data['review'] = False
sql_review = f'AND `event_session`.review = :review'
elif review == 'all':
sql_review = ''
if approved in ['approved', 'not_approved', 'all']:
if approved == 'approved':
data['approve'] = True
sql_approved = f'AND `event_session`.approve = :approve'
elif approved == 'not_approved':
data['approve'] = False
sql_approved = f'AND `event_session`.approve = :approve'
elif approved == 'all':
sql_approved = ''
if hidden in ['hidden', 'not_hidden', 'all']:
if hidden == 'hidden':
data['hide'] = True
sql_hidden = f'AND `event_session`.hide = :hide'
elif hidden == 'not_hidden':
data['hide'] = False
sql_hidden = f'AND `event_session`.hide = :hide'
elif hidden == 'all':
sql_hidden = ''
if enabled in ['enabled', 'disabled', 'all']:
if enabled == 'enabled':
data['enable'] = True
sql_enabled = f'AND `event_session`.enable = :enable'
elif enabled == 'disabled':
data['enable'] = False
sql_enabled = f'AND `event_session`.enable = :enable'
elif enabled == 'all':
sql_enabled = ''
if limit:
data['limit'] = limit
sql_limit = f'LIMIT :limit'
else:
sql_limit = ''
sql = f"""
SELECT `event_session`.id AS 'event_session_id', `event_session`.id_random AS 'event_session_id_random'
FROM `event_session` AS `event_session`
WHERE
{sql_where_type_id}
{sql_review}
{sql_approved}
{sql_hidden}
{sql_enabled}
ORDER BY `event_session`.priority DESC, -`event_session`.sort DESC, `event_session`.start_datetime ASC, `event_session`.name ASC, `event_session`.created_on DESC, `event_session`.updated_on DESC
{sql_limit};
"""
log.debug(sql)
if event_session_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
log.info('Got a list result')
event_session_rec_li = event_session_rec_li_result
else: # [] or False
log.info('No results or something went wrong')
event_session_rec_li = event_session_rec_li_result
log.debug(event_session_rec_li_result)
return event_session_rec_li
# ### END ### API Event Session Methods ### get_event_session_rec_list() ###
# ### BEGIN ### API Event Session Methods ### create_update_event_session_obj_v4() ###
# NOTE: This will create or update a event_session.
# Rewrite and updated 2021-08-25
@logger_reset
def create_update_event_session_obj_v4(
event_session_dict_obj: Event_Session_Base|dict,
event_session_id: int|str = None,
event_id: int|str = None,
event_location_id: int|str = None, # For future?
# event_track_id: int|str = None, # For future?
create_sub_obj: bool = False,
fail_any: bool = False, # Fail if any thing goes wrong for sub objects
return_outline: bool = False,
) -> int|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
log.info('Checking requirements...')
if event_session_id:
log.info(f'Event Session ID passed. Update existing Event Session. Event Session ID: {event_session_id}')
if event_session_id := redis_lookup_id_random(record_id_random=event_session_id, table_name='event_session'): pass
else:
log.error('Event Session ID passed but is invalid. Failed requirement.')
return False
if event_id := redis_lookup_id_random(record_id_random=event_id, table_name='event'): pass
else:
log.error('Missing or invalid Event ID passed. Not required. Ignoring.')
# log.info(f'Event ID: {event_id}')
# log.info('Attempting to get Event ID from related object.')
# from app.methods.event_methods import get_event_id_w_for_type_id
# if event_id := get_event_id_w_for_type_id(for_type='event_session', for_id=event_session_id): pass
# else:
# log.error('Unable to get Event ID from related object.')
# False
else:
log.info('No Event Session ID passed. Create new Event Session. Required: Account ID, Event ID')
if event_id := redis_lookup_id_random(record_id_random=event_id, table_name='event'): pass
else:
log.error('Missing or invalid Event ID passed. Failed requirement.')
log.info(f'Event ID: {event_id}')
return False
log.debug(type(event_session_dict_obj))
if isinstance(event_session_dict_obj, dict):
event_session_dict = event_session_dict_obj
if event_session_id:
event_session_dict['event_session_id'] = event_session_id
if event_id:
event_session_dict['event_id'] = event_id
try:
event_session_obj = Event_Session_Base(**event_session_dict)
log.warning(event_session_obj)
except ValidationError as e:
log.error(e.json())
return False
else:
event_session_obj = event_session_dict_obj
if event_session_id:
# NOTE: Can't update the ID alias if it was never set.
event_session_obj.id = event_session_id
if event_id:
event_session_obj.event_id = event_id
event_session_dict = event_session_obj.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'event_presentation', 'event_presentation_list', 'created_on', 'updated_on'})
if event_session_id:
if event_session_dict_up_result := sql_update(data=event_session_dict, table_name='event_session', rm_id_random=True): pass
else:
log.warning(f'Event Session not updated. Event Session ID: {event_session_id}')
log.debug(event_session_dict_up_result)
return False
log.debug(event_session_dict_up_result)
else:
if event_session_dict_in_result := sql_insert(data=event_session_dict, table_name='event_session', rm_id_random=True, id_random_length=None): pass
else:
log.warning(f'Event Session not created.')
log.debug(event_session_dict_in_result)
return False
log.debug(event_session_dict_in_result)
event_session_id = event_session_dict_in_result
log.debug(event_session_id)
event_session_outline = {}
event_session_outline['event_session_id'] = event_session_id
event_session_outline['event_presentation_list'] = []
if event_session_obj.event_presentation_list and isinstance(event_session_obj.event_presentation_list, list):
log.info(f'Event Presentation List was found. Loop through and create a new Event Presentation for each and link them to the new Event Session. Event Session ID: {event_session_id}')
for event_presentation_obj in event_session_obj.event_presentation_list:
# NOTE: Use object model version because of better type checking and validations
log.debug(event_presentation_obj)
if event_presentation_id := event_presentation_obj.id: pass
else: event_presentation_id = None
# event_presentation_obj.event_id = event_id
# event_presentation_obj.event_session_id = event_session_id
create_update_event_presentation_obj_result = create_update_event_presentation_obj_v4(
event_presentation_dict_obj = event_presentation_obj,
event_presentation_id = event_presentation_id,
event_id = event_id,
# event_location_id = event_location_id,
event_session_id = event_session_id,
# event_track_id = event_track_id,
fail_any = fail_any,
return_outline = return_outline,
)
if isinstance(create_update_event_presentation_obj_result, int):
event_presentation_id = create_update_event_presentation_obj_result
elif create_update_event_presentation_obj_result == True: pass
else:
log.warning(f'Create or Update failed while trying create_update_event_presentation_obj_v4(): {create_update_event_presentation_obj_result}')
event_presentation_id = None
event_session_outline['event_presentation_id'] = event_presentation_id
if return_outline:
log.debug(f'Returning the Event Session Outline: {event_session_outline}')
return event_session_outline
else:
log.debug(f'Returning the Event Session ID: {event_session_id}')
return event_session_id
# ### END ### API Event Session Methods ### create_update_event_session_obj_v4() ###
# ### BEGIN ### API Event Session Methods ### create_event_session_obj() ###
# Updated 2022-04-12
@logger_reset
def create_event_session_obj(
event_id: int|str,
event_session_obj_new: Event_Session_Base,
create_sub_obj: bool = False,
fail_any: bool = False, # Fail if any thing goes wrong for sub objects
) -> bool:
log.setLevel(logging.INFO) # 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
log.debug(type(event_session_obj_new))
if isinstance(event_session_obj_new, dict):
event_session_dict = event_session_obj_new
try:
event_session_obj = Event_Session_Base(**event_session_dict)
except ValidationError as e:
log.error(e.json())
return False
log.debug(event_session_obj)
else:
event_session_dict = event_session_obj_new.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'event_presentation', 'event_presentation_list', 'event_presenter', 'event_presenter_list', 'created_on', 'updated_on'})
event_session_dict['event_id'] = event_id
log.debug(event_session_dict)
if event_session_obj_in_result := sql_insert(data=event_session_dict, table_name='event_session', rm_id_random=True, id_random_length=8): pass
else:
log.warning(f'Event Session not created.')
log.debug(event_session_obj_in_result)
return False
event_session_id = event_session_obj_in_result
return_dict = {}
return_dict['event_session_id'] = None
return_dict['event_presentation_list'] = []
if event_session_dict.get('event_presentation_list') and isinstance(event_session_dict.get('event_presentation_list'), list):
log.info(f'Event Presentation List was found. Loop through and create a new Event Presentation for each and link them to the new Event Session. Event Session ID: {event_session_id}')
for event_presentation_obj in event_session_dict.get('event_presentation_list'):
# NOTE: This does not account for an edge case where the presentation already exists. Possibly as part of another session.
if create_event_presentation_obj_result := create_event_presentation_obj(
event_session_id = event_session_id,
event_presentation_obj_new = event_presentation_obj,
create_sub_obj = create_sub_obj,
fail_any = fail_any,
):
if isinstance(create_event_presentation_obj_result, int):
event_presentation_id = create_event_presentation_obj_result
log.info(f'Event Presentation created. Event Presentation ID: {event_presentation_id}')
else:
log.warning(f'Event Presentation not created. Event Session ID: {event_session_id}')
log.debug(create_event_presentation_obj_result)
event_presentation_id = None
if fail_any: return False
else:
log.warning(f'Event Presentation not created. Event Session ID: {event_session_id}')
log.debug(create_event_presentation_obj_result)
event_presentation_id = None
if fail_any: return False
return_dict['event_presentation_list'].append(event_presentation_id)
else:
log.info('Event Presentation List not found')
pass
log.info(f'The Event Session has been created. Event Session ID: {event_session_id}')
return event_session_id
# ### END ### API Event Session Methods ### create_event_session_obj() ###
# ### BEGIN ### API Event Session Methods ### update_event_session_obj_v3() ###
# Updated 2022-04-12
@logger_reset
def update_event_session_obj_v3(
event_session_id: int|str,
event_session_obj_exist: Event_Session_Base,
create_sub_obj: bool = False,
fail_any: bool = False, # Fail if any thing goes wrong for sub objects
) -> bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if event_session_id := redis_lookup_id_random(record_id_random=event_session_id, table_name='event_session'): pass
else: return False
log.debug(type(event_session_obj_exist))
if isinstance(event_session_obj_exist, dict):
event_session_dict = event_session_obj_exist
try:
event_session_obj = Event_Session_Base(**event_session_obj_exist)
except ValidationError as e:
log.error(e.json())
return False
log.debug(event_session_obj)
else:
event_session_dict = event_session_obj_exist.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'event_presentation', 'event_presentation_list', 'event_presenter', 'event_presenter_list', 'created_on', 'updated_on'})
# Can't update the event_session_id alias if the .id was never set.
# event_session_obj.event_session_id = event_session_id
if not event_session_obj.id:
event_session_obj.id = event_session_id
# event_presenter_dict['event_id'] = event_id
# if event_track_id:
# event_presenter_dict['for_type'] = 'event_track'
# event_presenter_dict['for_id'] = event_track_id
# event_presenter_dict['event_track_id'] = event_track_id
log.debug(event_session_dict)
if event_session_obj_up_result := sql_update(data=event_session_dict, table_name='event_session', record_id=event_session_id, rm_id_random=True): pass
else:
log.warning(f'Event Session not updated.')
log.debug(event_session_obj_up_result)
return False
return_dict = {}
return_dict['event_session_id'] = event_session_id
return_dict['event_presentation_list'] = []
if event_session_obj.event_presentation_list and isinstance(event_session_obj.event_presentation_list, list):
for event_presentation_obj_unknown in event_session_obj.event_presentation_list:
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_presentation_obj_unknown)
if event_presentation_id := event_presentation_obj_unknown.get('event_presentation_id_random', None):
if update_event_presentation_obj_result := update_event_presentation_obj_v3(
event_presentation_id = event_presentation_id,
event_presentation_obj_exist = event_presentation_obj_unknown,
create_sub_obj = create_sub_obj,
fail_any = fail_any,
):
event_presentation_id = update_event_presentation_obj_result
log.info(f'Event Presentation updated. Event Presentation ID: {event_presentation_id}')
else:
log.warning(f'Event Presentation not updated. Event Session ID: {event_session_id}')
log.debug(update_event_presentation_obj_result)
event_presentation_id = None
if fail_any: return False
if isinstance(update_event_presentation_obj_result, int):
event_presentation_id = update_event_presentation_obj_result
log.info(f'Event Presentation updated. Event Presentation ID: {event_presentation_id}')
else:
log.warning(f'Event Presentation not updated. Event Session ID: {event_session_id}')
log.debug(update_event_presentation_obj_result)
event_presentation_id = None
if fail_any: return False
else:
log.info(f'No Event Presentation ID found.')
if create_event_presentation_obj_result := create_event_presentation_obj(
event_session_id = event_session_id,
event_presentation_obj_new = event_presentation_obj_unknown,
create_sub_obj = create_sub_obj,
fail_any = fail_any,
):
if isinstance(create_event_presentation_obj_result, int):
event_presentation_id = create_event_presentation_obj_result
log.info(f'Event Presentation created. Event Presentation ID: {event_presentation_id}')
else:
log.warning(f'Event Presentation not created. Event Session ID: {event_session_id}')
log.debug(create_event_presentation_obj_result)
event_presentation_id = None
if fail_any: return False
else:
log.warning(f'Event Presentation not created. Event Session ID: {event_session_id}')
log.debug(create_event_presentation_obj_result)
event_presentation_id = None
if fail_any: return False
return_dict['event_presentation_list'].append(event_presentation_id)
else:
log.info('Event Presentation List not found or not in a list.')
pass
log.info(f'The event session has been updated. Event Session ID: {event_session_id}')
return True
# ### END ### API Event Session Methods ### update_event_session_obj_v3() ###
# ### BEGIN ### API Event Session Methods ### update_event_session_obj() ###
# This will be taken over by _exist version
# Updated 2022-04-12
@logger_reset
def update_event_session_obj(
event_session_id: int|str, # Ideally the int ID should be passed. This allows for updating of the id_random value.
event_session_obj_up: Event_Session_Base,
create_sub_obj: bool = False,
) -> bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if event_session_id := redis_lookup_id_random(record_id_random=event_session_id, table_name='event_session'): pass
else: return False
event_session_obj_up.id = event_session_id
log.debug(event_session_obj_up)
# log.debug(event_session_obj_up.dict(by_alias=True, exclude_unset=True))
log.debug(event_session_obj_up.dict(by_alias=False, exclude_unset=True))
# log.debug(event_session_obj_up.dict(by_alias=False, exclude_unset=False))
if event_session_obj_up.poc_event_person_id and event_session_obj_up.poc_event_person:
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
poc_event_person_id = event_session_obj_up.poc_event_person_id
poc_event_person_obj_up = event_session_obj_up.poc_event_person
log.debug(poc_event_person_id)
log.debug(poc_event_person_obj_up)
if poc_event_person_obj_up_result := update_event_person_obj(
event_person_id=poc_event_person_id,
event_person_obj_up=poc_event_person_obj_up,
create_sub_obj=create_sub_obj,
):
log.debug(poc_event_person_obj_up_result)
else:
log.debug(poc_event_person_obj_up_result)
return False
elif event_session_obj_up.poc_event_person and not event_session_obj_up.poc_event_person.id:
# NOTE: This will blindly create a new poc_event_person even if there was one associated but the event_session.poc_event_person_id was not found.
poc_event_person_obj_in = event_session_obj_up.poc_event_person
log.debug(poc_event_person_obj_in)
if poc_event_person_obj_in_result := create_event_person_obj(event_person_obj_new=poc_event_person_obj_in):
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(poc_event_person_obj_in_result)
event_session_obj_up.poc_event_person_id = poc_event_person_obj_in_result
else:
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(poc_event_person_obj_in_result)
return False
if event_session_obj_up.event_presentation_id and event_session_obj_up.event_presentation:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
event_presentation_id = event_session_obj_up.event_presentation_id
event_presentation_obj_up = event_session_obj_up.event_presentation
log.debug(event_presentation_id)
log.debug(event_presentation_obj_up)
if event_presentation_obj_up_result := update_event_presentation_obj(
event_presentation_id=event_presentation_id,
event_presentation_obj_up=event_presentation_obj_up,
create_sub_obj=create_sub_obj,
):
log.debug(event_presentation_obj_up_result)
else:
log.debug(event_presentation_obj_up_result)
return False
elif event_session_obj_up.event_presentation and not event_session_obj_up.event_presentation.id:
# NOTE: This will blindly create a new event_presentation even if there was one associated but the event_session.event_presentation_id was not found.
event_presentation_obj_in = event_session_obj_up.event_presentation
log.debug(event_presentation_obj_in)
if event_presentation_obj_in_result := create_event_presentation_obj(event_session_id=event_session, event_presentation_obj_new=event_presentation_obj_in):
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_presentation_obj_in_result)
event_session_obj_up.event_presentation_id = event_presentation_obj_in_result
else:
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(event_presentation_obj_in_result)
return False
event_session_dict_up = event_session_obj_up.dict(by_alias=False, exclude_unset=True, exclude={'event_abstract', 'event_abstract_list', 'event_file_list', 'event_person', 'event_presentation', 'event_session', 'person', 'user'})
log.debug(event_session_dict_up)
if event_session_obj_up_result := sql_update(data=event_session_dict_up, table_name='event_session', rm_id_random=True):
log.debug(event_session_obj_up_result)
return True
else:
log.debug(event_session_obj_up_result)
return False
# ### END ### API Session Methods ### update_event_session_obj() ###

View File

@@ -0,0 +1,73 @@
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_enable_part, sql_insert, sql_limit_offset_part, sql_select, sql_update
from app.lib_general import log, logging, logger_reset
from app.models.fundraising_cfg_models import Fundraising_Cfg_Base
# ### BEGIN ### API Fundraising Cfg Methods ### load_fundraising_cfg_obj() ###
# Updated 2022-11-18
def load_fundraising_cfg_obj(
fundraising_id: int|str,
model_as_dict: bool = False,
) -> Fundraising_Cfg_Base|dict|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if fundraising_id := redis_lookup_id_random(record_id_random=fundraising_id, table_name='fundraising'): pass
else: return False
if fundraising_cfg_rec := sql_select(
table_name = 'v_fundraising_cfg',
field_name = 'fundraising_id',
field_value = fundraising_id
): pass
else: return False
log.debug(fundraising_cfg_rec)
try:
fundraising_cfg_obj = Fundraising_Cfg_Base(**fundraising_cfg_rec)
log.debug(fundraising_cfg_obj)
except ValidationError as e:
log.error(e.json())
if model_as_dict:
return fundraising_cfg_obj.dict(by_alias=True, exclude_unset=True) # pylint: disable=no-member
else:
return fundraising_cfg_obj
# ### END ### API Fundraising Cfg Methods ### load_fundraising_cfg_obj() ###
# ### BEGIN ### API Fundraising Cfg Methods ### load_fundraising_cfg_obj_old() ###
# Updated 2022-11-18
def load_fundraising_cfg_obj_old(
account_id: int|str,
model_as_dict: bool = False,
) -> Fundraising_Cfg_Base|dict|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if account_id := redis_lookup_id_random(record_id_random=account_id, table_name='account'): pass
else: return False
if fundraising_cfg_rec := sql_select(
table_name = 'v_fundraising_cfg',
field_name = 'account_id',
field_value = account_id
): pass
else: return False
log.debug(fundraising_cfg_rec)
try:
fundraising_cfg_obj = Fundraising_Cfg_Base(**fundraising_cfg_rec)
log.debug(fundraising_cfg_obj)
except ValidationError as e:
log.error(e.json())
if model_as_dict:
return fundraising_cfg_obj.dict(by_alias=True, exclude_unset=True) # pylint: disable=no-member
else:
return fundraising_cfg_obj
# ### END ### API Fundraising Cfg Methods ### load_fundraising_cfg_obj_old() ###

View File

@@ -0,0 +1,165 @@
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_enable_part, sql_insert, sql_limit_offset_part, sql_select, sql_update
from app.lib_general import log, logging, logger_reset
from app.methods.fundraising_cfg_methods import load_fundraising_cfg_obj
from app.methods.product_methods import get_product_rec_list, load_product_obj
from app.models.fundraising_models import Fundraising_Base
from app.models.fundraising_cfg_models import Fundraising_Cfg_Base
# ### BEGIN ### API Fundraising Cfg Methods ### load_fundraising_obj() ###
# Updated 2022-11-18
def load_fundraising_obj(
fundraising_id: int|str,
inc_fundraising_cfg: bool = False,
inc_product_list: bool = False,
enabled: str = 'enabled', # enabled, disabled, all
limit: int = 100,
offset: int = 0,
model_as_dict: bool = False,
) -> Fundraising_Base|dict|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if fundraising_id := redis_lookup_id_random(record_id_random=fundraising_id, table_name='fundraising'): pass
else: return False
if fundraising_rec := sql_select(table_name='v_fundraising', record_id=fundraising_id): pass
else: return False
log.debug(fundraising_rec)
try:
fundraising_obj = Fundraising_Base(**fundraising_rec)
except ValidationError as e:
log.error(e.json())
return False
log.debug(fundraising_obj)
# Updated 2022-11-18
if inc_fundraising_cfg:
log.info('Need to include fundraising configuration...')
if fundraising_cfg_result := load_fundraising_cfg_obj(
fundraising_id = fundraising_id,
# by_alias = by_alias,
# exclude_unset = exclude_unset,
# model_as_dict = model_as_dict,
):
fundraising_obj.fundraising_cfg = fundraising_cfg_result
else: fundraising_obj.fundraising_cfg = {} # None
log.debug(fundraising_obj.fundraising_cfg)
# Updated 2022-11-18
if inc_product_list:
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.info('Need to include product list...')
if product_rec_list_result := get_product_rec_list(
for_obj_type = 'fundraising',
for_obj_id = fundraising_id,
enabled = enabled,
):
product_result_list = []
for product_rec in product_rec_list_result:
if load_product_result := load_product_obj(
product_id = product_rec.get('product_id', None),
):
product_result_list.append(load_product_result)
else:
product_result_list.append(None)
log.debug(product_result_list)
fundraising_obj.product_list = product_result_list
elif isinstance(product_rec_list_result, list):
fundraising_obj.product_list = []
else:
fundraising_obj.product_list = None
if model_as_dict:
return fundraising_obj.dict(by_alias=True, exclude_unset=True) # pylint: disable=no-member
else:
return fundraising_obj
# ### END ### API Fundraising Cfg Methods ### load_fundraising_obj() ###
# ### BEGIN ### API Fundraising Cfg Methods ### get_fundraising_rec_list() ###
@logger_reset
def get_fundraising_rec_list(
account_id: str = None,
enabled: str = 'enabled', # enabled, disabled, all
hidden: str = 'not_hidden', # hidden, not_hidden, all
priority: str = 'all', # priority, not_priority, all
limit: int = 100,
offset: int = 0,
) -> list|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if account_id := redis_lookup_id_random(record_id_random=account_id, table_name='account'): pass
else: pass
data = {}
data['account_id'] = account_id
sql_where_account_id = f'`fundraising`.account_id = :account_id'
sql_hidden = ''
if hidden in ['hidden', 'not_hidden', 'all']:
if hidden == 'hidden':
data['hide'] = True
sql_hidden = f'AND `fundraising`.hide = :hide'
elif hidden == 'not_hidden':
data['hide'] = False
sql_hidden = f'AND `fundraising`.hide = :hide'
elif hidden == 'all':
sql_hidden = ''
sql_priority = ''
if priority in ['priority', 'not_priority', 'all']:
if priority == 'priority':
data['priority'] = True
sql_priority = f'AND `fundraising`.priority = :priority'
elif priority == 'not_priority':
data['priority'] = False
sql_priority = f'AND `fundraising`.priority = :priority'
elif priority == 'all':
sql_priority = ''
sql_enabled, data['enable'] = sql_enable_part(table_name='fundraising', enabled=enabled) # Reasonably safe return str and bool
sql_limit = sql_limit_offset_part(limit=limit, offset=offset) # Reasonably safe return str
sql = f"""
SELECT `fundraising`.id AS 'fundraising_id', `fundraising`.id_random AS 'fundraising_id_random'
FROM `fundraising` AS `fundraising`
WHERE
{sql_where_account_id}
{sql_hidden}
{sql_priority}
{sql_enabled}
ORDER BY `fundraising`.priority DESC, -`fundraising`.sort DESC, `fundraising`.title ASC, `fundraising`.created_on DESC, `fundraising`.updated_on DESC
{sql_limit};
"""
if fundraising_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
fundraising_rec_li = fundraising_rec_li_result
else:
fundraising_rec_li = []
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(fundraising_rec_li_result)
log.debug(type(fundraising_rec_li))
log.debug(len(fundraising_rec_li))
return fundraising_rec_li
# ### END ### API Fundraising Cfg Methods ### get_fundraising_rec_list() ###

View File

@@ -0,0 +1,412 @@
import datetime, json
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_delete, sql_insert, sql_select, sql_update, sql_enable_part, sql_limit_offset_part
from app.lib_general import log, logging, logger_reset
from app.models.common_field_schema import default_num_bytes
from app.models.grant_models import Grant_Ext, Grant_In
# ### BEGIN ### API Grant Methods ### load_grant_obj() ###
# Updated 2023-06-23
@logger_reset
def load_grant_obj(
grant_id: int|str,
enabled: str = 'enabled', # enabled, disabled, all
hidden: str = 'not_hidden', # hidden, not_hidden, all
inc_event_abstract: bool = False,
limit: int = 1500,
offset: int = 0,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
) -> Grant_Ext|dict|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
# if grant_id := redis_lookup_id_random(record_id_random=grant_id, table_name='grant'): pass
# else: return False
if grant_rec := sql_select(table_name='v_grant', record_id=grant_id): pass
else: return False
try:
grant_obj = Grant_Ext(**grant_rec)
log.debug(grant_obj)
except ValidationError as e:
log.error(e.json())
return False
# Updated 2023-06-23
if inc_event_abstract:
# log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.info('Need to include event abstract...')
if event_abstract_obj := load_event_abstract_obj(
event_abstract_id = grant_obj.event_abstract_id,
enabled = enabled,
):
log.debug(event_abstract_obj)
grant_obj.event_abstract = event_abstract_obj
else:
log.debug(event_abstract_obj)
grant_obj.event_abstract = None
# log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
if model_as_dict:
return grant_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return grant_obj
# ### END ### API Grant Methods ### load_grant_obj() ###
# ### BEGIN ### API Grant Methods ### get_grant_rec_list() ###
# Updated 2023-06-23
@logger_reset
def get_grant_rec_list(
account_id: None|str = None,
event_id: None|str = None,
grant_type_code: str = None, # not sure yet
hidden: str = 'all', # hidden, not_hidden, all
enabled: str = 'enabled', # enabled, disabled, all
limit: int = 250,
offset: int = 0,
) -> list|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if not account_id and not event_id: return False
if account_id := redis_lookup_id_random(record_id_random=account_id, table_name='account'): pass
elif account_id is None: pass
else: return False
if event_id := redis_lookup_id_random(record_id_random=event_id, table_name='event'): pass
elif event_id is None: pass
else: return False
data = {}
if account_id:
data['account_id'] = account_id
sql_account_id = f'`grant`.account_id = :account_id'
else: sql_account_id = '1=1'
if event_id:
data['event_id'] = event_id
sql_event_id = f'AND `grant`.event_id = :event_id'
else: sql_event_id = ''
# if abstract_type_code:
# data['abstract_type_code'] = abstract_type_code
# sql_abstract_type_code = f'AND `grant`.abstract_type_code = :abstract_type_code'
# else: sql_abstract_type_code = ''
if hidden in ['hidden', 'not_hidden', 'all']:
log.info(f'Creating partial SQL string for "hidden" check. Printed: {hidden}')
if hidden == 'not_hidden':
sql_hidden = f'AND (`grant`.hide IS NULL OR `grant`.hide = FALSE)'
elif hidden == 'hidden':
sql_hidden = f'AND `grant`.hide = TRUE'
elif hidden == 'all':
sql_hidden = f'AND (`grant`.hide IS NULL OR `grant`.hide IS NOT NULL)'
log.debug(sql_hidden)
else: sql_hidden = ''
sql_enabled, data['enable'] = sql_enable_part(table_name='grant', enabled=enabled) # Reasonably safe return str and bool
sql_limit = sql_limit_offset_part(limit=limit, offset=offset) # Reasonably safe return str
sql = f"""
SELECT `grant`.id AS 'grant_id', `grant`.id_random AS 'grant_id_random'
FROM `v_grant` AS `grant`
WHERE
{sql_account_id}
{sql_event_id}
{sql_hidden}
{sql_enabled}
ORDER BY grant.priority DESC, grant.sort DESC, grant.name ASC, `grant`.created_on DESC, `grant`.updated_on DESC
{sql_limit};
"""
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(sql)
if grant_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
grant_rec_li = grant_rec_li_result
else: # [] or False
grant_rec_li = grant_rec_li_result
log.debug(grant_rec_li_result)
return grant_rec_li
# ### END ### API Grant Methods ### get_grant_rec_list() ###
# ### BEGIN ### API Grant Methods ### create_update_grant_obj() ###
# Updated 2023-06-28
@logger_reset
def create_update_grant_obj(
grant_obj: Grant_In,
grant_id: int = None,
event_id: int = None,
# event_person_id: int = None,
create_sub_obj: bool = False,
fail_any: bool = False, # Fail if any thing goes wrong for sub objects
return_outline: bool = False,
) -> int|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
log.info('Checking requirements...')
if grant_id:
log.info(f'Got: grant_id={grant_id}; Update existing Grant')
grant_obj.id = grant_id
elif event_id:
log.info(f'Got: event_id={event_id}; Create Grant')
grant_obj.event_id = event_id
else:
return False
log.debug(type(grant_obj))
log.debug(grant_obj
)
grant_dict = grant_obj.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'event_abstract', 'event_person', 'created_on', 'updated_on'})
if grant_id:
if grant_dict_up_result := sql_update(data=grant_dict, table_name='grant', rm_id_random=True):
log.info(f'Grant updated. grant_id={grant_id}')
pass
else:
log.warning(f'Grant not updated. grant_id={grant_id}')
log.debug(grant_dict_up_result)
return False
log.debug(grant_dict_up_result)
else:
if grant_dict_in_result := sql_insert(data=grant_dict, table_name='grant', rm_id_random=True, id_random_length=None):
log.info(f'Grant created. grant_id={grant_dict_in_result}')
else:
log.warning(f'Grant not created.')
log.debug(grant_dict_in_result)
return False
log.debug(grant_dict_in_result)
grant_id = grant_dict_in_result # False, None, integer
grant_outline = {}
grant_outline['event_id'] = event_id
grant_outline['grant_id'] = grant_id
if return_outline:
log.debug(f'Returning the Grant Outline: {grant_outline}')
return grant_outline
else:
log.debug(f'Returning the Grant ID: {grant_id}')
return grant_id
# ### END ### API Grant Methods ### create_update_grant_obj() ###
# # ### BEGIN ### API Grant Methods ### create_update_grant_obj_old() ###
# # Updated 2023-03-20
# @logger_reset
# def create_update_grant_obj_old(
# grant_dict_obj: Grant_In|dict,
# grant_id: int|str = None,
# event_id: int|str = None,
# event_person_id: int|str = None,
# create_sub_obj: bool = False,
# fail_any: bool = False, # Fail if any thing goes wrong for sub objects
# return_outline: bool = False,
# ) -> int|bool:
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
# log.debug(locals())
# log.info('Checking requirements...')
# if grant_id:
# log.info(f'Grant ID passed. Update existing Grant. Grant ID: {grant_id}')
# if grant_id := redis_lookup_id_random(record_id_random=grant_id, table_name='grant'): pass
# else:
# log.error('Grant ID passed but is invalid. Failed requirement.')
# return False
# if event_id := redis_lookup_id_random(record_id_random=event_id, table_name='event'):
# log.info(f'Event ID: {event_id}')
# elif event_id is None:
# log.error('Missing Event ID. Not required. Ignoring.')
# log.info(f'Event ID: {event_id}')
# else:
# log.error('Invalid Event ID passed. Not required. But not ignoring since it is likely invalid.')
# log.info(f'Event ID: {event_id}')
# return False
# else:
# log.info('No Grant ID passed. Create new Grant. Required: Event ID')
# if event_id := redis_lookup_id_random(record_id_random=event_id, table_name='event'):
# log.info(f'Event ID: {event_id}')
# elif event_id is None:
# log.error('Missing Event ID. Failed requirement.')
# log.info(f'Event ID: {event_id}')
# return False
# else:
# log.error('Invalid Event ID passed. Failed requirement.')
# log.info(f'Event ID: {event_id}')
# return False
# # else:
# # log.error('Missing or invalid Event ID passed. Failed requirement.')
# # log.info(f'Event ID: {event_id}')
# # return False
# log.debug(type(grant_dict_obj))
# if isinstance(grant_dict_obj, dict):
# grant_dict = grant_dict_obj
# if grant_id:
# grant_dict['grant_id'] = grant_id
# if event_id:
# grant_dict['event_id'] = event_id
# if event_person_id:
# grant_dict['event_person_id'] = event_person_id
# try:
# grant_obj = Grant_In(**grant_dict)
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
# log.debug(grant_obj)
# except ValidationError as e:
# log.error(e.json())
# return False
# else:
# grant_obj = grant_dict_obj
# if grant_id:
# # NOTE: Can't update the ID alias if it was never set.
# grant_obj.id = grant_id
# if event_id:
# grant_obj.event_id = event_id
# if event_person_id:
# grant_obj.event_person_id = event_person_id
# grant_dict = grant_obj.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'event_person', 'event_person_list', 'created_on', 'updated_on'})
# if grant_id:
# if grant_dict_up_result := sql_update(data=grant_dict, table_name='grant', rm_id_random=True): pass
# else:
# log.warning(f'Grant not updated. Grant ID: {grant_id}')
# log.debug(grant_dict_up_result)
# return False
# log.debug(grant_dict_up_result)
# else:
# if grant_dict_in_result := sql_insert(data=grant_dict, table_name='grant', rm_id_random=True, id_random_length=None): pass
# else:
# log.warning(f'Grant not created.')
# log.debug(grant_dict_in_result)
# return False
# log.debug(grant_dict_in_result)
# grant_id = grant_dict_in_result
# grant_outline = {}
# grant_outline['event_id'] = event_id
# grant_outline['grant_id'] = grant_id
# grant_outline['event_person_id'] = event_person_id
# # grant_outline['event_presenter_list'] = []
# # if grant_obj.event_presenter_list and isinstance(grant_obj.event_presenter_list, list):
# # log.info(f'Event Presenter List was found. Loop through and create a new Event Presenter for each and link them to the new Grant. Grant ID: {grant_id}')
# # for event_presenter_obj in grant_obj.event_presenter_list:
# # # NOTE: Use object model version because of better type checking and validations
# # log.debug(event_presenter_obj)
# # if event_presenter_id := event_presenter_obj.id: pass
# # else: event_presenter_id = None
# # # event_presenter_obj.event_id = event_id
# # # event_presenter_obj.event_session_id = event_session_id
# # create_update_event_presenter_obj_result = create_update_event_presenter_obj_v4(
# # event_presenter_dict_obj = event_presenter_obj,
# # event_presenter_id = event_presenter_id,
# # event_id = event_id,
# # event_session_id = event_session_id,
# # grant_id = grant_id,
# # fail_any = fail_any,
# # return_outline = return_outline,
# # )
# # if isinstance(create_update_event_presenter_obj_result, int):
# # event_presenter_id = create_update_event_presenter_obj_result
# # elif create_update_event_presenter_obj_result == True: pass
# # else:
# # log.warning(f'Create or Update failed while trying create_update_event_presenter_obj_v4(): {create_update_event_presenter_obj_result}')
# # event_presenter_id = None
# # grant_outline['event_presenter_id'] = event_presenter_id
# if return_outline:
# log.debug(f'Returning the Grant Outline: {grant_outline}')
# return grant_outline
# else:
# log.debug(f'Returning the Grant ID: {grant_id}')
# return grant_id
# # ### END ### API Grant Methods ### create_update_grant_obj_old() ###
# # ### BEGIN ### API Grant Methods ### remove_grant_obj() ###
# # Updated 2023-03-22
# @logger_reset
# def remove_grant_obj(
# grant_id: int,
# method: None|str = None,
# log_lvl: int = logging.DEBUG, # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
# ) -> bool|None:
# log.setLevel(log_lvl)
# if method is None or method == 'disable':
# data = {'enable': False}
# if grant_dict_up_result := sql_update(
# table_name = 'grant',
# record_id = grant_id,
# data = data,
# log_lvl = log_lvl,
# ):
# log.info(f'Grant was disabled.')
# return True
# else:
# log.warning(f'Grant not disabled.')
# return grant_dict_up_result # False or None
# elif method == 'delete':
# if grant_dict_del_result := sql_delete(
# table_name = 'grant',
# record_id = grant_id,
# log_lvl = log_lvl,
# ):
# log.info(f'Grant was deleted.')
# return True
# else:
# log.warning(f'Grant not deleted.')
# return grant_dict_del_result # False or None
# elif method == 'hide':
# data = {'hide': True}
# if grant_dict_up_result := sql_update(
# table_name = 'grant',
# record_id = grant_id,
# data = data,
# log_lvl = log_lvl,
# ):
# log.info(f'Grant was hidden.')
# return True
# else:
# log.warning(f'Grant not hidden.')
# return grant_dict_up_result # False or None
# else:
# log.error('We should not be here. Something went wrong in remove_grant_obj()!')
# return False
# # ### END ### API Grant Methods ### remove_grant_obj() ###

View File

@@ -0,0 +1,873 @@
import datetime, hashlib, mimetypes, os, pathlib, shutil, time
from fastapi import File, UploadFile
from typing import Dict, List, Optional, Set, Union
from pydantic import BaseModel, EmailStr, Field, PrivateAttr, ValidationError, validator
from app.config import settings
from app.db_sql import redis_lookup_id_random, sql_delete, sql_enable_part, sql_insert, sql_limit_offset_part, sql_select, sql_update
from app.lib_general import log, logging, logger_reset
from app.models.hosted_file_models import Hosted_File_Base
# ### BEGIN ### API Hosted File Methods ### create_hosted_file_obj() ###
@logger_reset
def create_hosted_file_obj(hosted_file_obj_new:Hosted_File_Base):
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
# hosted_file_obj_data = hosted_file_obj_new.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'created_on', 'updated_on'})
hosted_file_obj_data = hosted_file_obj_new.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'saved', 'already_exists', 'copy_timer', 'created_on', 'updated_on'})
if hosted_file_obj_in_result := sql_insert(data=hosted_file_obj_data, table_name='hosted_file', rm_id_random=True, id_random_length=8): pass
else:
return False
log.debug(hosted_file_obj_in_result)
hosted_file_id = hosted_file_obj_in_result
log.debug(f'Returning the new hosted_file_id: {hosted_file_id}')
return hosted_file_id
# ### END ### API Hosted File Methods ### create_hosted_file_obj() ###
# ### BEGIN ### API Hosted File Methods ### load_hosted_file_obj() ###
# Updated 2023-08-18
@logger_reset
def load_hosted_file_obj(
hosted_file_id: int|str,
limit: int = 1000,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
enabled: str = 'enabled', # enabled, disabled, all
inc_hosted_file_link_list: bool = False,
) -> Hosted_File_Base|dict|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if hosted_file_id := redis_lookup_id_random(record_id_random=hosted_file_id, table_name='hosted_file'): pass
else: return False
if hosted_file_rec := sql_select(table_name='v_hosted_file', record_id=hosted_file_id): pass
elif hosted_file_rec is None: return None
else: return False
log.debug(hosted_file_rec)
try:
hosted_file_obj = Hosted_File_Base(**hosted_file_rec)
except ValidationError as e:
log.error(e.json())
return False
log.info(f'Filename: {hosted_file_obj.filename}; Size: {hosted_file_obj.size}; Hash SHA256: {hosted_file_obj.hash_sha256}; ')
log.debug(hosted_file_obj)
if model_as_dict:
return hosted_file_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset)
else:
return hosted_file_obj
# ### END ### API Hosted File Methods ### load_hosted_file_obj() ###
# ### BEGIN ### API Hosted File Methods ### lookup_file_hash() ###
# Updated 2022-08-09
@logger_reset
def lookup_file_hash(
file_hash: str,
) -> Hosted_File_Base|dict|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
sql = f"""
SELECT id AS 'hosted_file_id', id_random AS 'hosted_file_id_random'
FROM hosted_file
WHERE hosted_file.hash_sha256 = :hash_sha256
"""
log.debug(sql)
hosted_file_data = {}
hosted_file_data['hash_sha256'] = file_hash
log.debug(hosted_file_data)
if hosted_file_select_result := sql_select(sql=sql, data=hosted_file_data):
hosted_file_id = hosted_file_select_result.get('hosted_file_id')
hosted_file_id_random = hosted_file_select_result.get('hosted_file_id_random')
log.info(f'Selected Hosted File record. Hosted File ID: {hosted_file_id}')
return hosted_file_id
elif hosted_file_select_result is None:
log.warning(f'Hosted File record was not found. SHA 256 Hash: {file_hash}')
return None
# pass
else:
log.error(f'Something went wrong while trying to select the hosted file record. SHA 256 Hash: {file_hash}')
return False
# ### END ### API Hosted File Methods ### lookup_file_hash() ###
# ### BEGIN ### API Hosted File Methods ### get_file_object_hash() ###
# Really shouldn't this be called generate_file_obj_hash() ??? -2023-05-04
@logger_reset
async def get_file_object_hash(file_object:File):
#log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
# 4096 bytes is the current block size on my workstation and Linode server
# 4096 8192 16384 32768 65536 131072 262144 524288 1048576 bytes
block_size = 131072
hash_value = hashlib.sha256()
timer_start = time.process_time()
for chunk in iter(lambda: file_object.read(block_size), b""):
hash_value.update(chunk)
file_hash = hash_value.hexdigest()
file_object.seek(0) # The file will not properly save if seek is not reset to 0.
timer_end = time.process_time()
elapsed_time = timer_end - timer_start
log.debug(f'Elapsed time: {elapsed_time}')
return file_hash
# ### END ### API Hosted File Methods ### get_file_object_hash() ###
# ### BEGIN ### API Hosted File Methods ### guess_file_extension() ###
def guess_file_extension(filename: str):
return filename.rsplit('.', 1)[1].lower()
# ### END ### API Hosted File Methods ### guess_file_extension() ###
# ### BEGIN ### API Hosted File Methods ### allowed_file_extension() ###
def allowed_file_extension(extension: str, extension_list: list):
return extension.lower() in extension_list # app.config['ALLOWED_EXTENSIONS']
# ### END ### API Hosted File Methods ### allowed_file_extension() ###
# ### BEGIN ### API Hosted File Methods ### lookup_file_hash() ###
# Updated 2023-09-19
@logger_reset
def check_for_hosted_file_hash_file(
file_hash: str,
sub_dir: str,
) -> dict|bool:
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
file_size = None
hosted_files_path = settings.FILES_PATH['hosted_files_root']
log.info(f'Hosted Files Path: {hosted_files_path}')
log.debug(shutil.disk_usage(hosted_files_path))
hosted_files_dir_w_subdir = os.path.join(hosted_files_path, sub_dir)
path_hosted_files_dir_w_subdir = pathlib.Path(hosted_files_dir_w_subdir)
if path_hosted_files_dir_w_subdir.exists(): pass
else:
log.warning('Hashed hosted file subdirectory was not found in the hosted files root.')
return False
hosted_files_dir_w_subdir_filename = os.path.join(hosted_files_path, sub_dir, f'{file_hash}.file')
path_hosted_files_dir_w_subdir_filename = pathlib.Path(hosted_files_dir_w_subdir_filename)
if path_hosted_files_dir_w_subdir_filename.exists():
file_size = os.path.getsize(path_hosted_files_dir_w_subdir_filename)
else:
log.warning('Hashed hosted file not found in the expected hosted files subdirectory.')
return False
return {'found': True, 'file_size': file_size}
# ### BEGIN ### API Hosted File Methods ### save_file() ###
# Updated 2022-08-09
@logger_reset
async def save_file(
file: UploadFile,
account_id: int,
link_to_type: str,
link_to_id: int,
account_id_random: str = None,
link_to_id_random: str = None,
check_allowed_extension: bool = False,
):
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
hosted_files_path = settings.FILES_PATH['hosted_files_root']
# hosted_files_path = '/home/scott/tmp/hosted_files_dev/'
log.info(f'Hosted Files Path: {hosted_files_path}')
log.debug(shutil.disk_usage(hosted_files_path))
log.debug(dir(file))
log.debug(f'{file.filename}')
if file.filename.endswith('.docwin'):
log.warning('Fixing win extension')
file.filename = file.filename.replace('.docwin', '.doc')
if file.filename.endswith('.docxwin'):
log.warning('Fixing win extension')
file.filename = file.filename.replace('.docxwin', '.docx')
if file.filename.endswith('.odpmac'):
log.warning('Fixing mac extension')
file.filename = file.filename.replace('.odpmac', '.odp')
if file.filename.endswith('.odpwin'):
log.warning('Fixing win extension')
file.filename = file.filename.replace('.odpwin', '.odp')
if file.filename.endswith('.pdfmac'):
log.warning('Fixing mac extension')
file.filename = file.filename.replace('.pdfmac', '.pdf')
if file.filename.endswith('.pdfwin'):
log.warning('Fixing win extension')
file.filename = file.filename.replace('.pdfwin', '.pdf')
if file.filename.endswith('.pptmac'):
log.warning('Fixing mac extension')
file.filename = file.filename.replace('.pptmac', '.ppt')
if file.filename.endswith('.pptxmac'):
log.warning('Fixing mac extension')
file.filename = file.filename.replace('.pptxmac', '.pptx')
if file.filename.endswith('.pptwin'):
log.warning('Fixing win extension')
file.filename = file.filename.replace('.pptwin', '.ppt')
if file.filename.endswith('.pptxwin'):
log.warning('Fixing win extension')
file.filename = file.filename.replace('.pptxwin', '.pptx')
if file.filename.endswith('.xlswin'):
log.warning('Fixing win extension')
file.filename = file.filename.replace('.xlswin', '.xls')
if file.filename.endswith('.xlsxwin'):
log.warning('Fixing win extension')
file.filename = file.filename.replace('.xlsxwin', '.xlsx')
file_info: dict = {}
file_info['saved'] = None
file_info['link_to_type'] = link_to_type
file_info['link_to_id'] = link_to_id
file_info['link_to_id_random'] = link_to_id_random
file_info['filename'] = file.filename
file_info['extension'] = guess_file_extension(filename=file.filename)
if check_allowed_extension:
if allowed_file_extension(extension=file_info['extension'], extension_list=['jpg','png','webp']):
file_info['extension_allowed'] = True
else:
file_info['extension_allowed'] = False
file_info['saved'] = False
return file_info
else:
file_info['extension_allowed'] = None
# There is a difference between Content-Type and MIME type.
# https://stackoverflow.com/questions/3452381/whats-the-difference-of-contenttype-and-mimetype
file_info['content_type'] = file.content_type # might also include charset or other parameters
# file_info['mimetype'] = file.mimetype # This may need to be filled in a different way?
file.file.seek(0, os.SEEK_END)
file_size = file.file.tell()
file.file.seek(0) # The file will not properly save if seek is not reset to 0.
log.debug(file_size)
file_info['size'] = file_size
file_hash = await get_file_object_hash(file.file)
log.debug(file_hash)
file_info['hash_sha256'] = file_hash
# 16384 bytes is the default
# 4096 8192 16384 32768 65536 131072 262144 524288 1048576 bytes
buffer_size = 524288
#f_src = open(file_src, 'rb')
f_src = file.file # Don't need to do open(file_src, 'rb') since it is already "open"
file_hash_subdirectory = file_hash[0:2]
subdirectory_dest = os.path.join(hosted_files_path, file_hash_subdirectory)
log.debug(subdirectory_dest)
pathlib.Path(subdirectory_dest).mkdir(parents=True, exist_ok=True)
file_info['subdirectory_path'] = file_hash_subdirectory
#file_dest = f'{hosted_files_path}{file.filename}'
# file_dest = f'{hosted_files_path}{file_hash}.file'
file_dest = os.path.join(hosted_files_path, f'{file_hash}.file')
file_dest_w_subdir = os.path.join(subdirectory_dest, f'{file_hash}.file')
existing_file_check = pathlib.Path(file_dest)
existing_file_check_subdir = pathlib.Path(file_dest_w_subdir)
if existing_file_check.exists():
log.warning('This file already exists at the destination without the subdirectory. Not re-saving. Going to move the current file and update the database later.')
file_info['already_exists'] = True
file_info['already_exists_subdir'] = False
try:
log.info('Moving file to sub directory destination...')
timer_start = time.process_time()
shutil.move(existing_file_check, existing_file_check_subdir)
timer_end = time.process_time()
elapsed_time = timer_end - timer_start
log.debug(f'Elapsed time: {elapsed_time}')
file_info['copy_timer'] = elapsed_time
file_info['saved'] = True
log.info(f'File moved to: {hosted_files_path}')
except Exception as e:
log.exception('*** An exception happened. ***')
log.exception(repr(e))
log.exception('***')
log.exception(str(e))
log.exception('^^^ exception ^^^')
file_info['copy_timer'] = 0
file_info['saved'] = False
elif existing_file_check_subdir.exists():
log.warning('This file already exists at the destination with the subdirectory. Not re-saving.')
file_info['already_exists'] = True
file_info['already_exists_subdir'] = True
file_info['copy_timer'] = 0
file_info['saved'] = True
else:
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.warning('This file does not already exist at the destination with or without the subdirectory.')
file_info['already_exists'] = False
file_info['already_exists_subdir'] = False
try:
log.info('Saving file to destination...')
f_dest = open(file_dest_w_subdir, 'wb')
timer_start = time.process_time()
shutil.copyfileobj(f_src, f_dest, buffer_size)
timer_end = time.process_time()
elapsed_time = timer_end - timer_start
log.debug(f'Elapsed time: {elapsed_time}')
file_info['copy_timer'] = elapsed_time
file_info['saved'] = True
log.info(f'File saved to: {hosted_files_path}')
except Exception as e:
log.exception('*** An exception happened. ***')
log.exception(repr(e))
log.exception('***')
log.exception(str(e))
log.exception('^^^ exception ^^^')
file_info['copy_timer'] = 0
file_info['saved'] = False
return False
log.info(f'Disk usage: {shutil.disk_usage(hosted_files_path)}')
log.info(f"Filename: {file_info['filename']}")
log.info(f"Subdirectory Path: {file_info['subdirectory_path']}")
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(file_info)
# if existing_file_check.exists():
# file_info['already_exists'] = True
# file_info['copy_timer'] = 0
# file_info['saved'] = True
# else:
# file_info['already_exists'] = False
# try:
# f_dest = open(file_dest, 'wb')
# timer_start = time.process_time()
# shutil.copyfileobj(f_src, f_dest, buffer_size)
# timer_end = time.process_time()
# elapsed_time = timer_end - timer_start
# log.debug(f'Elapsed time: {elapsed_time}')
# file_info['copy_timer'] = elapsed_time
# file_info['saved'] = True
# except Exception as e:
# log.exception('*** An exception happened. ***')
# log.exception(repr(e))
# log.exception('***')
# log.exception(str(e))
# log.exception('^^^ exception ^^^')
# file_info['copy_timer'] = 0
# file_info['saved'] = False
log.debug(shutil.disk_usage(hosted_files_path))
return file_info
# ### END ### API Hosted File Methods ### save_file() ###
# ### BEGIN ### API Hosted File Methods ### save_file_to_hosted_file() ###
# Updated 2022-08-09
@logger_reset
async def save_file_to_hosted_file(
file_path: str,
filename: str,
extension: str,
account_id: int,
link_to_type: str,
link_to_id: int,
):
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
hosted_files_path = settings.FILES_PATH['hosted_files_root']
log.info(f'Hosted Files Path: {hosted_files_path}')
log.debug(shutil.disk_usage(hosted_files_path))
log.debug(file_path)
log.debug(f'Filename: {filename} Extension: {extension}')
file_obj = open(file_path, 'rb')
file_info: dict = {}
file_info['saved'] = None
file_info['link_to_type'] = link_to_type
file_info['link_to_id'] = link_to_id
file_info['filename'] = filename
file_info['extension'] = extension # guess_file_extension(filename=filename)
# if check_allowed_extension:
# if allowed_file_extension(extension=file_info['extension'], extension_list=['jpg','png','webp']):
# file_info['extension_allowed'] = True
# else:
# file_info['extension_allowed'] = False
# file_info['saved'] = False
# return file_info
# else:
# file_info['extension_allowed'] = None
# There is a difference between Content-Type and MIME type.
# https://stackoverflow.com/questions/3452381/whats-the-difference-of-contenttype-and-mimetype
file_info['content_type'] = mimetypes.guess_type(filename)[0]
file_obj.seek(0, os.SEEK_END)
file_size = file_obj.tell()
file_obj.seek(0) # The file will not properly save if seek is not reset to 0.
log.debug(file_size)
file_info['size'] = file_size
file_hash = await get_file_object_hash(file_obj)
log.debug(file_hash)
file_info['hash_sha256'] = file_hash
# 16384 bytes is the default
# 4096 8192 16384 32768 65536 131072 262144 524288 1048576 bytes
buffer_size = 524288
#f_src = open(file_src, 'rb')
f_src = file_obj # Don't need to do open(file_src, 'rb') since it is already "open"
file_hash_subdirectory = file_hash[0:2]
subdirectory_dest = os.path.join(hosted_files_path, file_hash_subdirectory)
log.debug(subdirectory_dest)
pathlib.Path(subdirectory_dest).mkdir(parents=True, exist_ok=True)
file_info['subdirectory_path'] = file_hash_subdirectory
#file_dest = f'{hosted_files_path}{file.filename}'
# file_dest = f'{hosted_files_path}{file_hash}.file'
file_dest = os.path.join(hosted_files_path, f'{file_hash}.file')
file_dest_w_subdir = os.path.join(subdirectory_dest, f'{file_hash}.file')
existing_file_check = pathlib.Path(file_dest)
existing_file_check_subdir = pathlib.Path(file_dest_w_subdir)
log.debug(existing_file_check_subdir)
# return file_info
if existing_file_check_subdir.exists():
log.warning('This file already exists at the destination with the subdirectory. Not re-saving.')
file_info['already_exists'] = True
file_info['already_exists_subdir'] = True
file_info['copy_timer'] = 0
file_info['saved'] = True
else:
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.warning('This file does not already exist at the destination subdirectory.')
file_info['already_exists'] = False
file_info['already_exists_subdir'] = False
try:
log.info('Saving file to destination...')
f_dest = open(file_dest_w_subdir, 'wb')
timer_start = time.process_time()
shutil.copyfileobj(f_src, f_dest, buffer_size)
timer_end = time.process_time()
elapsed_time = timer_end - timer_start
log.debug(f'Elapsed time: {elapsed_time}')
file_info['copy_timer'] = elapsed_time
file_info['saved'] = True
log.info(f'File saved to: {hosted_files_path}')
except Exception as e:
log.exception('*** An exception happened. ***')
log.exception(repr(e))
log.exception('***')
log.exception(str(e))
log.exception('^^^ exception ^^^')
file_info['copy_timer'] = 0
file_info['saved'] = False
return False
log.info(f'Disk usage: {shutil.disk_usage(hosted_files_path)}')
log.info(f"Filename: {file_info['filename']}")
log.info(f"Subdirectory Path: {file_info['subdirectory_path']}")
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(file_info)
log.debug(shutil.disk_usage(hosted_files_path))
return file_info
# ### END ### API Hosted File Methods ### save_file_to_hosted_file() ###
# ### BEGIN ### API Hosted File Methods ### create_hosted_file_link() ###
# Updated 2022-08-09
@logger_reset
def create_hosted_file_link(
account_id: int|str,
hosted_file_id: int|str,
link_to_type: str,
link_to_id: int|str,
):
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if account_id := redis_lookup_id_random(record_id_random=account_id, table_name='account'): pass
else: return False
if hosted_file_id := redis_lookup_id_random(record_id_random=hosted_file_id, table_name='hosted_file'): pass
else: return False
if link_to_id := redis_lookup_id_random(record_id_random=link_to_id, table_name=link_to_type): pass
else: return False
hosted_file_link_data: dict = {}
hosted_file_link_data['account_id'] = account_id
hosted_file_link_data['hosted_file_id'] = hosted_file_id
hosted_file_link_data['link_to_type'] = link_to_type # Should this be renamed to "link_to_type" for clarity?
hosted_file_link_data['link_to_id'] = link_to_id # Should this be renamed to "link_to_id" for clarity?
# hosted_file_link_data['test'] = 'test'
# NOTE: Currently sql_insert does not handle all successful inserts correctly. If there is not an autonum ID then it will return 0 as the ID.
if hosted_file_link_data_in_result := sql_insert(data=hosted_file_link_data, table_name='hosted_file_link', id_random_length=0):
log.info('The hosted_file_link was created.')
pass # This should be improved
elif hosted_file_link_data_in_result is None:
log.info('The hosted_file_link probably already exists.')
return None
else:
# This should be improved
log.warning('Because the hosted_file_link table does not have a primary autonum this check is incorrect even when successful.')
log.warning('Something may have gone wrong while trying to create the hosted_file_link record.')
log.warning('The hosted_file_link was probably created fine though.')
return False
log.debug(hosted_file_link_data_in_result)
return True
# ### END ### API Hosted File Methods ### create_hosted_file_link() ###
# ### BEGIN ### API Hosted File Methods ### handle_delete_hosted_file() ###
# Updated 2022-08-09
@logger_reset
def handle_delete_hosted_file(
account_id: int|str,
hosted_file_id: int|str,
link_to_type: str = None,
link_to_id: int|str = None,
rm_all_links: bool = False,
rm_orphan: bool = False,
):
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if account_id := redis_lookup_id_random(record_id_random=account_id, table_name='account'): pass
else: return False
if hosted_file_id := redis_lookup_id_random(record_id_random=hosted_file_id, table_name='hosted_file'): pass
else: return False
# ### SECTION ### Handle links NOTE NOTE NOTE NOTE NOTE NOTE
# NOTE: If link_to_type and link_to_id passed then try and remove that link record first.
if link_to_type and link_to_id:
if hosted_file_link_result := delete_hosted_file_link(
account_id = account_id,
hosted_file_id = hosted_file_id,
link_to_type = link_to_type,
link_to_id = link_to_id,
# rm_orphan = rm_orphan,
):
log.info('The hosted file link record was deleted.')
elif hosted_file_link_result is None:
log.warning('The hosted file link record was not found and may have already been deleted. Odd, but this can happen. event_file has a trigger to delete hosted_file_link when being deleted.')
# return None
else:
log.error('Something went wrong while trying to delete the hosted file link record.')
return False
# ### SECTION ### Handle orphan check and deletion of hosted_file record and file on server NOTE NOTE NOTE NOTE NOTE NOTE
# NOTE: If not rm_orphan then do nothing else.
# NOTE: If rm_orphan then get list of links for file.
# NOTE: If 0 links result then delete the hosted_file record and file on the server.
# NOTE: If >0 links result then do nothing else.
# NOTE: Don't check or remove orphan
if not rm_orphan:
log.info('Removed hosted file link. No orphan check.')
return True
if hosted_file_obj := load_hosted_file_obj(
hosted_file_id = hosted_file_id,
# inc_hosted_file = True,
inc_hosted_file_link_list = True, # if rm_orphan (True) then need to include hosted_file_link_list (True)
):
log.info('Hosted File object loaded.')
pass
elif hosted_file_obj is None:
log.warning('Hosted File object not found. Can not attempt to delete file from the server if there is one.')
# pass
return None
else:
log.error('Something went wrong while trying to load the Hosted File object.')
return False
log.debug(hosted_file_obj)
# NOTE: Check and remove orphan
if hosted_file_link_rec_list_result := get_hosted_file_link_rec_list(hosted_file_id=hosted_file_id):
log.info('This hosted file has linked records to it.')
hosted_file_link_result_list = []
for hosted_file_link_rec in hosted_file_link_rec_list_result:
hosted_file_link_result_list.append(hosted_file_link_rec)
# log.debug( )
hosted_file_list = hosted_file_link_result_list
# NOT safe to delete the hosted_file record and file from server!!!
# STOP!
log.info('Removed hosted file link (above). Still not an orphan file.')
return True
elif isinstance(hosted_file_link_rec_list_result, list) or hosted_file_link_rec_list_result is None:
log.info('This hosted file has no link records to it.')
hosted_file_list = []
# Safe to delete the hosted_file record and file from server???
# CONTINUE
else:
hosted_file_list = False
# Safe to delete the hosted_file record and file from server???
# CONTINUE???
log.error('Something went wrong while trying to get a list of the hosted file link records.')
return False
# ### Orphan file: ### Delete file from server
hosted_files_path = settings.FILES_PATH['hosted_files_root']
# hosted_files_path = '/home/scott/tmp/hosted_files_dev/'
log.info(f'Hosted Files Path: {hosted_files_path}')
# dir_path = hosted_file_obj.directory_path
subdir_path = hosted_file_obj.subdirectory_path
hash_sha256 = hosted_file_obj.hash_sha256
hash_filename = hash_sha256+'.file'
if subdir_path:
full_subdirectory_path = os.path.join(hosted_files_path, subdir_path)
else:
full_subdirectory_path = hosted_files_path
log.debug(full_subdirectory_path)
file_path_w_subdir = os.path.join(full_subdirectory_path, hash_filename)
log.info(f'Full file path with subdirectory: {file_path_w_subdir}')
if os.path.exists(file_path_w_subdir):
log.info('File exists!')
log.info('Going remove the file if it is an orphan...')
try:
pathlib.Path(file_path_w_subdir).unlink()
except OSError as e:
log.error("Error: %s : %s" % (file_path, e.strerror))
return False
pass
# return True
else:
log.warning(f'The hosted file was not found on the server. Hash: {hash_sha256}')
pass
# return None
# ### Orphan file: ### Delete hosted_file record
sql = f"""
DELETE FROM hosted_file
WHERE hosted_file.id = :hosted_file_id
"""
log.debug(sql)
hosted_file_data = {}
hosted_file_data['hosted_file_id'] = hosted_file_id
log.debug(hosted_file_data)
if hosted_file_delete_result := sql_delete(sql=sql, data=hosted_file_data):
log.info(f'Deleted Hosted File record. Hosted File ID: {hosted_file_id}')
return True
elif hosted_file_delete_result is None:
log.warning(f'Hosted File record was not found and may have already been removed. Hosted File ID: {hosted_file_id}')
return None
# pass
else:
log.error('Something went wrong while trying to delete the hosted file record.')
return False
# ### END ### API Hosted File Methods ### handle_delete_hosted_file() ###
# ### BEGIN ### API Hosted File Methods ### delete_hosted_file_link() ###
# Updated 2022-08-09
@logger_reset
def delete_hosted_file_link(
account_id: int|str,
hosted_file_id: int|str,
link_to_type: str,
link_to_id: int|str,
# rm_orphan: bool = False,
):
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
# if account_id := redis_lookup_id_random(record_id_random=account_id, table_name='account'): pass
# else: return False
if hosted_file_id := redis_lookup_id_random(record_id_random=hosted_file_id, table_name='hosted_file'): pass
else: return False
if link_to_id := redis_lookup_id_random(record_id_random=link_to_id, table_name=link_to_type): pass
else: return False
sql = f"""
DELETE FROM hosted_file_link
WHERE hosted_file_id = :hosted_file_id
AND link_to_type = :link_to_type
AND link_to_id = :link_to_id
"""
log.debug(sql)
hosted_file_link_data = {}
hosted_file_link_data['hosted_file_id'] = hosted_file_id
hosted_file_link_data['link_to_type'] = link_to_type
hosted_file_link_data['link_to_id'] = link_to_id
log.debug(hosted_file_link_data)
if hosted_file_delete_result := sql_delete(sql=sql, data=hosted_file_link_data):
log.info(f'Deleted Hosted File Link. Hosted File ID: {hosted_file_id}, Link To Type: {link_to_type}, Link To ID: {link_to_id}')
elif hosted_file_delete_result is None:
return None
else:
return False
return True
# ### END ### API Hosted File Methods ### delete_hosted_file_link() ###
# ### BEGIN ### API Hosted File Methods ### get_hosted_file_rec_list() ###
# This needs to be improved. Currently it does not really do anything.
# Need to allow for list by account? Probably have the same actual hosted file have two hosted_file entries if it was uploaded for two separate accounts.
# Updated 2022-09-22
@logger_reset
def get_hosted_file_rec_list(
for_obj_type: str,
for_obj_id: str,
limit: int = 1000,
enabled: str = 'enabled', # enabled, disabled, all
) -> list|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if for_obj_id := redis_lookup_id_random(record_id_random=for_obj_id, table_name=for_obj_type): pass
else: return False
data = {}
data[f'{for_obj_type}_id'] = for_obj_id
# data['for_obj_type'] = for_obj_type
sql_obj_type_id = f'`tbl`.{for_obj_type}_id = :{for_obj_type}_id'
if enabled in ['enabled', 'disabled', 'all']:
if enabled == 'enabled':
data['enable'] = True
sql_enabled = f'AND `tbl`.enable = :enable'
elif enabled == 'disabled':
data['enable'] = False
sql_enabled = f'AND `tbl`.enable = :enable'
elif enabled == 'all':
sql_enabled = ''
if limit:
data['limit'] = limit
sql_limit = f'LIMIT :limit'
else:
sql_limit = ''
sql = f"""
SELECT `hosted_file`.id AS 'hosted_file_id', `hosted_file`.id_random AS 'hosted_file_id_random'
FROM `hosted_file` AS `hosted_file`
WHERE
{sql_obj_type_id}
{sql_enabled}
ORDER BY `hosted_file`.created_on DESC, `hosted_file`.updated_on DESC, `hosted_file`.filename ASC, `hosted_file`.extension ASC
{sql_limit};
"""
# NOTE: Use the ORDER BY below if priority and sort fields are added to the hosted_file table.
# /* ORDER BY `hosted_file`.priority DESC, -`hosted_file`.sort DESC, `hosted_file`.created_on DESC, `hosted_file`.updated_on DESC, `hosted_file`.filename ASC, `hosted_file`.extension ASC */
if hosted_file_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
hosted_file_rec_li = hosted_file_rec_li_result
else:
hosted_file_rec_li = []
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(hosted_file_rec_li_result)
return hosted_file_rec_li
# ### END ### API Hosted File Methods ### get_hosted_file_rec_list() ###
# ### BEGIN ### API Hosted File Methods ### get_hosted_file_link_rec_list() ###
# Updated 2022-08-09
@logger_reset
def get_hosted_file_link_rec_list(
hosted_file_id: int|str,
link_to_type: str = None,
link_to_id: int|str = None,
limit: int = 10,
offset: int = 0,
enabled: str = 'enabled', # enabled, disabled, all
) -> list|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
data = {'hosted_file_id': hosted_file_id}
# sql_enabled, data['enable'] = sql_enable_part(table_name='hosted_file', enabled=enabled) # Reasonably safe return str and bool
sql_limit = sql_limit_offset_part(limit=limit, offset=offset) # Reasonably safe return str
sql = f"""
SELECT *
FROM `hosted_file_link` AS `hosted_file_link`
WHERE
`hosted_file_link`.hosted_file_id = :hosted_file_id
ORDER BY `hosted_file_link`.created_on DESC, `hosted_file_link`.updated_on DESC
{sql_limit};
"""
if hosted_file_link_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
hosted_file_link_rec_li = hosted_file_link_rec_li_result
else:
hosted_file_link_rec_li = []
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(hosted_file_link_rec_li_result)
return hosted_file_link_rec_li
# ### END ### API Hosted File Methods ### get_hosted_file_link_rec_list() ###

View File

@@ -0,0 +1,151 @@
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.journal_entry_models import Journal_Entry_Base
# ### BEGIN ### API Journal Entry Methods ### create_journal_entry_obj() ###
def create_journal_entry_obj(journal_entry_obj_new:Journal_Entry_Base) -> bool|int:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
journal_entry_obj_data = journal_entry_obj_new.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'created_on', 'updated_on'})
if journal_entry_obj_in_result := sql_insert(
data=journal_entry_obj_data,
table_name='journal_entry',
rm_id_random=True,
id_random_length=8
): pass
else: return False
log.debug(journal_entry_obj_in_result)
journal_entry_id = journal_entry_obj_in_result
log.debug(f'New journal_entry_id: {journal_entry_id}')
return journal_entry_id
# ### END ### API Journal Entry Methods ### create_journal_entry_obj() ###
# ### BEGIN ### API Journal Entry Methods ### load_journal_entry_obj() ###
def load_journal_entry_obj(
journal_entry_id: int|str,
limit: int = 1000,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
# enabled: str = 'enabled', # enabled, disabled, all
) -> Journal_Entry_Base|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if journal_entry_id := redis_lookup_id_random(record_id_random=journal_entry_id, table_name='journal_entry'): pass
else: return False
log.debug(journal_entry_id)
if journal_entry_rec := sql_select(table_name='v_journal_entry', record_id=journal_entry_id):
log.debug(journal_entry_rec)
else: return False
log.debug(journal_entry_rec)
try:
journal_entry_obj = Journal_Entry_Base(**journal_entry_rec)
log.debug(journal_entry_obj)
except ValidationError as e:
log.error(e.json())
if model_as_dict:
return journal_entry_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return journal_entry_obj
# ### END ### API Journal Entry Methods ### load_journal_entry_obj() ###
# ### BEGIN ### API Journal Entry Methods ### update_journal_entry_obj() ###
def update_journal_entry_obj(
journal_entry_id: int|str, # This allows for updating of the id_random value.
journal_entry_obj_up: Journal_Entry_Base,
create_sub_obj: bool = False,
) -> bool:
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if journal_entry_id := redis_lookup_id_random(record_id_random=journal_entry_id, table_name='journal_entry'): pass
else: return False
journal_entry_obj_up.id = journal_entry_id
log.debug(journal_entry_obj_up)
log.debug(journal_entry_obj_up.dict(by_alias=False, exclude_unset=True))
# log.debug(journal_entry_obj_up.dict(by_alias=False, exclude_unset=False))
journal_dict_up = journal_entry_obj_up.dict(by_alias=False, exclude_unset=True)
log.debug(journal_dict_up)
if journal_entry_obj_up_result := sql_update(data=journal_dict_up, table_name='journal_entry', rm_id_random=True):
log.debug(journal_entry_obj_up_result)
return True
else:
log.debug(journal_entry_obj_up_result)
return False
# ### END ### API Journal Entry Methods ### update_journal_entry_obj() ###
# ### BEGIN ### API Journal Entry Methods ### get_journal_entry_rec_list() ###
def get_journal_entry_rec_list(
journal_id: str,
limit: int = 1000,
enabled: str = 'enabled', # enabled, disabled, all
) -> list|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if journal_id := redis_lookup_id_random(record_id_random=journal_id, table_name='journal'): pass
else: return False
data = {}
data['journal_id'] = journal_id
sql_journal_id = f'`tbl`.journal_id = :journal_id'
if enabled in ['enabled', 'disabled', 'all']:
if enabled == 'enabled':
data['enable'] = True
sql_enabled = f'AND `tbl`.enable = :enable'
elif enabled == 'disabled':
data['enable'] = False
sql_enabled = f'AND `tbl`.enable = :enable'
elif enabled == 'all':
sql_enabled = ''
if limit:
data['limit'] = limit
sql_limit = f'LIMIT :limit'
else:
sql_limit = ''
sql = f"""
SELECT `tbl`.id AS 'journal_entry_id', `tbl`.id_random AS 'journal_entry_id_random'
FROM `journal_entry` AS `tbl`
WHERE
{sql_journal_id}
{sql_enabled}
ORDER BY `tbl`.created_on DESC, `tbl`.updated_on DESC
{sql_limit};
"""
if journal_entry_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
journal_entry_rec_li = journal_entry_rec_li_result
else:
journal_entry_rec_li = []
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(journal_entry_rec_li_result)
return journal_entry_rec_li
# ### END ### API Journal Entry Methods ### get_journal_entry_rec_list() ###

View File

@@ -0,0 +1,184 @@
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.person_methods import load_person_obj
from app.methods.journal_entry_methods import get_journal_entry_rec_list, load_journal_entry_obj
# from app.methods.user_methods import load_user_obj
from app.models.journal_models import Journal_Base
# ### BEGIN ### API Journal Methods ### create_journal_obj() ###
def create_journal_obj(journal_obj_new:Journal_Base):
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if not journal_obj_new:
return False
journal_obj_data = journal_obj_new.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'user', 'created_on', 'updated_on'})
if journal_obj_in_result := sql_insert(
data=journal_obj_data,
table_name='journal',
rm_id_random=True,
id_random_length=8
): pass
else: return False
log.debug(journal_obj_in_result)
journal_id = journal_obj_in_result
log.debug(f'New journal_id: {journal_entry_id}')
return journal_id
# ### END ### API Journal Methods ### create_journal_obj() ###
# ### BEGIN ### API Journal Methods ### load_journal_obj() ###
def load_journal_obj(
journal_id: int|str,
limit: int = 1000,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
enabled: str = 'enabled', # enabled, disabled, all
inc_journal_entry_list: bool = False,
inc_person: bool = False,
inc_user: bool = False,
) -> Journal_Base|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if journal_id := redis_lookup_id_random(record_id_random=journal_id, table_name='journal'): pass
else: return False
if journal_rec := sql_select(table_name='v_journal', record_id=journal_id):
log.debug(journal_rec)
else: return False
log.debug(journal_rec)
try:
journal_obj = Journal_Base(**journal_rec)
log.debug(journal_obj)
except ValidationError as e:
log.error(e.json())
# Updated 2021-06-18
if inc_journal_entry_list:
if journal_entry_rec_list_result := get_journal_entry_rec_list(
journal_id = journal_id,
limit = limit,
enabled = enabled,
):
journal_entry_result_list = []
for journal_entry_rec in journal_entry_rec_list_result:
journal_entry_result_list.append(
load_journal_entry_obj(
journal_entry_id = journal_entry_rec.get('journal_entry_id', None),
limit = limit,
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
enabled = enabled,
)
)
journal_obj.journal_entry_list = journal_entry_result_list
else: journal_obj.journal_entry_list = []
if model_as_dict:
return journal_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return journal_obj
# ### END ### API Journal Methods ### load_journal_obj() ###
# ### BEGIN ### API Journal Methods ### update_journal_obj() ###
def update_journal_obj(
journal_id: int|str, # This allows for updating of the id_random value.
journal_obj_up: Journal_Base,
create_sub_obj: bool = False,
) -> bool:
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if journal_id := redis_lookup_id_random(record_id_random=journal_id, table_name='journal'): pass
else: return False
journal_obj_up.id = journal_id
log.debug(journal_obj_up)
log.debug(journal_obj_up.dict(by_alias=False, exclude_unset=True))
# log.debug(journal_obj_up.dict(by_alias=False, exclude_unset=False))
journal_dict_up = journal_obj_up.dict(by_alias=False, exclude_unset=True, exclude={'user'})
log.debug(journal_dict_up)
if journal_obj_up_result := sql_update(data=journal_dict_up, table_name='journal', rm_id_random=True):
log.debug(journal_obj_up_result)
return True
else:
log.debug(journal_obj_up_result)
return False
# ### END ### API Journal Methods ### update_journal_obj() ###
# ### BEGIN ### API Journal Methods ### get_journal_rec_list() ###
def get_journal_rec_list(
for_obj_type: str,
for_obj_id: str,
limit: int = 1000,
enabled: str = 'enabled', # enabled, disabled, all
) -> list|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if for_obj_id := redis_lookup_id_random(record_id_random=for_obj_id, table_name=for_obj_type): pass
else: return False
data = {}
data[f'{for_obj_type}_id'] = for_obj_id
# data['for_obj_type'] = for_obj_type
sql_obj_type_id = f'`tbl`.{for_obj_type}_id = :{for_obj_type}_id'
if enabled in ['enabled', 'disabled', 'all']:
if enabled == 'enabled':
data['enable'] = True
sql_enabled = f'AND `tbl`.enable = :enable'
elif enabled == 'disabled':
data['enable'] = False
sql_enabled = f'AND `tbl`.enable = :enable'
elif enabled == 'all':
sql_enabled = ''
if limit:
data['limit'] = limit
sql_limit = f'LIMIT :limit'
else:
sql_limit = ''
sql = f"""
SELECT `tbl`.id AS 'journal_id', `tbl`.id_random AS 'journal_id_random'
FROM `journal` AS `tbl`
WHERE
{sql_obj_type_id}
{sql_enabled}
ORDER BY `tbl`.created_on DESC, `tbl`.updated_on DESC
{sql_limit};
"""
if journal_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
journal_rec_li = journal_rec_li_result
else:
journal_rec_li = []
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(journal_rec_li_result)
return journal_rec_li
# ### END ### API Journal Methods ### get_journal_rec_list() ###

View File

@@ -0,0 +1,97 @@
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_insert_or_update, sql_select, sql_update
from app.lib_general import log, logging
from app.models.common_field_schema import default_num_bytes
from app.models.log_client_viewing_models import Log_Client_Viewing_Base
# ### BEGIN ### API Log Client Viewing Methods ### load_log_client_viewing_obj() ###
def load_log_client_viewing_obj(
log_client_viewing_id: int|str,
limit: int = 10000,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
) -> Log_Client_Viewing_Base|bool:
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if log_client_viewing_id := redis_lookup_id_random(record_id_random=log_client_viewing_id, table_name='log_client_viewing'): pass
else: return False
if log_client_viewing_rec := sql_select(table_name='v_log_client_viewing', record_id=log_client_viewing_id): pass
else: return False
try:
log_client_viewing_obj = Log_Client_Viewing_Base(**log_client_viewing_rec)
log.debug(log_client_viewing_obj)
except ValidationError as e:
log.error(e.json())
if model_as_dict:
return log_client_viewing_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return log_client_viewing_obj
# ### END ### API Log Client Viewing Methods ### load_log_client_viewing_obj() ###
# ### BEGIN ### API Log Client Viewing Methods ### get_log_client_viewing_rec_list() ###
def get_log_client_viewing_rec_list(
account_id: str,
from_datetime: datetime.datetime = None,
to_datetime: datetime.datetime = None,
limit: int = 1000,
enabled: str = 'enabled', # enabled, disabled, all
) -> list|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if account_id := redis_lookup_id_random(record_id_random=account_id, table_name='account'): pass
else: return False
data = {}
data['account_id'] = account_id
sql_account_id = f'`tbl`.account_id = :account_id'
# if enabled in ['enabled', 'disabled', 'all']:
# if enabled == 'enabled':
# data['enable'] = True
# sql_enabled = f'AND `tbl`.enable = :enable'
# elif enabled == 'disabled':
# data['enable'] = False
# sql_enabled = f'AND `tbl`.enable = :enable'
# elif enabled == 'all':
# sql_enabled = ''
sql_enabled = ''
if limit:
data['limit'] = limit
sql_limit = f'LIMIT :limit'
else:
sql_limit = ''
sql = f"""
SELECT `tbl`.id AS 'log_client_viewing_id', `tbl`.id_random AS 'log_client_viewing_id_random'
FROM `log_client_viewing` AS `tbl`
WHERE
{sql_account_id}
{sql_enabled}
ORDER BY `tbl`.created_on DESC, `tbl`.updated_on DESC
{sql_limit};
"""
if log_client_viewing_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
log_client_viewing_rec_li = log_client_viewing_rec_li_result
else:
log_client_viewing_rec_li = []
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(log_client_viewing_rec_li_result)
return log_client_viewing_rec_li
# ### END ### API Log Client Viewing Methods ### get_log_client_viewing_rec_list() ###

View File

@@ -0,0 +1,92 @@
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
# ### BEGIN ### API Lookup Post Topic Methods ### load_post_topic() ###
# def load_lu_post_topic_obj(
# lu_post_topic_id: int|str,
# limit: int = 1000,
# by_alias: bool = True,
# exclude_unset: bool = True,
# model_as_dict: bool = False,
# enabled: str = 'enabled', # enabled, disabled, all
# ) -> Post_Base|bool:
# log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
# log.debug(locals())
# if post_id := redis_lookup_id_random(record_id_random=post_id, table_name='post'): pass
# else: return False
# if model_as_dict:
# return post_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
# else:
# return post_obj
# ### END ### API Post Methods ### load_post_obj() ###
# ### BEGIN ### API Lookup Post Topic Methods ### get_lu_post_topic_rec_list() ###
def get_lu_post_topic_rec_list(
account_id: str,
for_type: str,
inc_admin_options: bool = False,
limit: int = 1000,
enabled: str = 'enabled', # enabled, disabled, all
) -> list|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if account_id := redis_lookup_id_random(record_id_random=account_id, table_name='account'): pass
else: return False
data = {}
if account_id:
data['account_id'] = account_id
sql_account_id = f'`lu_post_topic`.account_id = :account_id'
else:
sql_account_id = ''
if for_type:
data['for_type'] = for_type
sql_for_type = f'AND `lu_post_topic`.for_type = :for_type'
else:
sql_for_type = ''
if inc_admin_options:
data['admin_option'] = [0, 1]
sql_admin_option = f'AND `lu_post_topic`.admin_option IN :admin_option'
else:
data['admin_option'] = [0]
sql_admin_option = f'AND `lu_post_topic`.admin_option IN :admin_option'
if limit:
data['limit'] = limit
sql_limit = f'LIMIT :limit'
else:
sql_limit = ''
sql = f"""
SELECT * /*`lu_post_topic`.id AS 'lu_post_topic_id'*/
FROM `lu_post_topic` AS `lu_post_topic`
WHERE
{sql_account_id}
{sql_for_type}
{sql_admin_option}
ORDER BY -`lu_post_topic`.sort DESC
{sql_limit};
"""
if lu_post_topic_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
lu_post_topic_rec_li = lu_post_topic_rec_li_result
else:
lu_post_topic_rec_li = []
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(lu_post_topic_rec_li_result)
return lu_post_topic_rec_li
# ### END ### API Lookup Post Topic Methods ### get_lu_post_topic_rec_list() ###

View File

@@ -0,0 +1,49 @@
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_select
from app.lib_general import log, logging
from app.models.membership_cfg_models import Membership_Cfg_Base
# ### BEGIN ### API Membership Cfg Methods ### load_membership_cfg_obj() ###
# Updated 2021-06-23
def load_membership_cfg_obj(
account_id: int|str,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
) -> Membership_Cfg_Base|dict|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if account_id := redis_lookup_id_random(record_id_random=account_id, table_name='account'): pass
else: return False
if membership_cfg_rec := sql_select(
table_name = 'v_membership_cfg',
field_name = 'account_id',
field_value = account_id,
): pass
else: return False
# log.debug(membership_cfg_rec)
log.debug(type(membership_cfg_rec['extended_membership_person_profile']))
log.debug(membership_cfg_rec['extended_membership_person_profile'])
membership_cfg_rec['extended_membership_person_profile'] = str(membership_cfg_rec['extended_membership_person_profile'], )
try:
membership_cfg_obj = Membership_Cfg_Base(**membership_cfg_rec)
log.debug(membership_cfg_obj)
except ValidationError as e:
log.error(e.json())
return False
if model_as_dict:
return membership_cfg_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return membership_cfg_obj
# ### END ### API Membership Cfg Methods ### load_membership_cfg_obj() ###

View File

@@ -0,0 +1,289 @@
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.membership_cfg_methods import load_membership_cfg_obj
from app.methods.membership_person_group_methods import get_membership_person_group_rec_list, load_membership_person_group_obj
from app.methods.product_methods import get_product_rec_list, load_product_obj
from app.models.membership_group_models import Membership_Group_Base
# ### BEGIN ### API Membership Group Methods ### load_membership_group_obj() ###
# Updated 2021-07-01
def load_membership_group_obj(
membership_group_id: int|str,
limit: int = 1000,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
enabled: str = 'enabled', # enabled, disabled, all
inc_address: bool = False,
inc_contact: bool = False,
inc_membership_cfg: bool = False,
inc_membership_person_group_list: bool = False, # List of members that are a part of this group
inc_membership_person: bool = False,
inc_membership_person_profile: bool = False, # under membership_person
# inc_membership_type_list: bool = False, # ???
inc_organization: bool = False,
inc_parent_membership_group: bool = False,
inc_person: bool = False,
inc_product: bool = False, # Per membership member
inc_product_list: bool = False, # List of products that give access to this group
inc_user: bool = False,
) -> Membership_Group_Base|bool:
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if membership_group_id := redis_lookup_id_random(record_id_random=membership_group_id, table_name='membership_group'): pass
else: return False
log.debug(membership_group_id)
if membership_group_rec := sql_select(table_name='v_membership_group', record_id=membership_group_id):
log.debug(membership_group_rec)
else: return False
log.debug(membership_group_rec)
try:
membership_group_obj = Membership_Group_Base(**membership_group_rec)
log.debug(membership_group_obj)
except ValidationError as e:
log.error(e.json())
# Updated 2021-06-24
if inc_membership_cfg:
if membership_cfg_obj_result := load_membership_cfg_obj(
account_id = membership_group_rec.get('account_id', None),
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
):
membership_group_obj.cfg = membership_cfg_obj_result
else: membership_group_obj.cfg = None
# Updated 2021-06-23
if inc_membership_person_list:
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
if membership_person_group_rec_list_result := get_membership_person_group_rec_list(
for_obj_type = 'membership_group',
for_obj_id = membership_group_id,
limit = limit,
enabled = enabled,
):
membership_person_group_result_list = []
for membership_person_group_rec in membership_person_group_rec_list_result:
if load_membership_person_group_result := load_membership_person_group_obj(
membership_person_group_id = membership_person_group_rec.get('membership_person_group_id', None),
limit = limit,
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
enabled = enabled,
inc_membership_person_profile = inc_membership_person_profile,
inc_membership_person_type = inc_membership_person_type,
# inc_organization = inc_organization,
inc_person = inc_person,
# inc_product = inc_product,
):
membership_person_group_result_list.append(load_membership_person_group_result)
else: membership_person_group_result_list.append(None)
membership_group_obj.membership_person_group_list = membership_person_group_result_list
else: membership_group_obj.membership_person_group_list = []
# Updated 2021-06-18
# if inc_parent_membership_group:
# parent_membership_group_id = membership_group_rec.get('parent_membership_group_id', None)
# log.debug(parent_membership_group_id)
# if parent_membership_group_result := load_membership_group_obj(
# membership_group_id = parent_membership_group_id,
# by_alias = by_alias,
# exclude_unset = exclude_unset,
# model_as_dict = model_as_dict,
# ):
# membership_group_obj.parent_membership_group = parent_membership_group_result
# else: membership_group_obj.parent_membership_group = None
# log.debug(parent_membership_group_result)
# Updated 2021-06-23
if inc_product_list:
if product_rec_list_result := get_product_rec_list(
for_obj_type = 'membership_group',
for_obj_id = membership_group_id,
limit = limit,
enabled = enabled,
):
product_result_list = []
for product_rec in product_rec_list_result:
product_result_list.append(
load_product_obj(
product_id = product_rec.get('product_id', None),
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
)
)
membership_group_obj.product_list = product_result_list
else: membership_group_obj.product_list = []
if model_as_dict:
return membership_group_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return membership_group_obj
# ### END ### API Membership Group Methods ### load_membership_group_obj() ###
# ### BEGIN ### API Membership Group Methods ### get_membership_group_rec_list() ###
def get_membership_group_rec_list(
account_id: str = None,
membership_person_id: str = None,
# product_id: str = None,
# type_level: int = None,
limit: int = 1000,
enabled: str = 'enabled', # enabled, disabled, all
) -> list|bool:
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if account_id := redis_lookup_id_random(record_id_random=account_id, table_name='account'): pass
# if product_id := redis_lookup_id_random(record_id_random=product_id, table_name='product'): pass
data = {}
data['account_id'] = account_id
data['membership_person_id'] = membership_person_id
# data['product_id'] = product_id
# data['level'] = type_level
if account_id:
sql_account_id = f'`membership_group`.account_id = :account_id'
if enabled in ['enabled', 'disabled', 'all']:
if enabled == 'enabled':
data['enable'] = True
sql_enabled = f'AND `membership_group`.enable = :enable'
elif enabled == 'disabled':
data['enable'] = False
sql_enabled = f'AND `membership_group`.enable = :enable'
elif enabled == 'all':
sql_enabled = ''
else: sql_enabled = ''
if limit:
data['limit'] = limit
sql_limit = f'LIMIT :limit'
else:
sql_limit = ''
sql = f"""
SELECT `membership_group`.id AS 'membership_group_id', `membership_group`.id_random AS 'membership_group_id_random'
FROM `v_membership_group` AS `membership_group`
WHERE
{sql_account_id}
{sql_enabled}
ORDER BY -`membership_group`.sort DESC, `membership_group`.created_on DESC, `membership_group`.updated_on DESC
{sql_limit};
"""
elif membership_person_id:
sql_membership_person_id = f'`membership_person_group`.membership_person_id = :membership_person_id'
if enabled in ['enabled', 'disabled', 'all']:
if enabled == 'enabled':
data['enable'] = True
sql_enabled = f'AND `membership_person_group`.enable = :enable'
elif enabled == 'disabled':
data['enable'] = False
sql_enabled = f'AND `membership_person_group`.enable = :enable'
elif enabled == 'all':
sql_enabled = ''
else: sql_enabled = ''
if limit:
data['limit'] = limit
sql_limit = f'LIMIT :limit'
else:
sql_limit = ''
sql = f"""
SELECT `membership_person_group`.membership_group_id AS 'membership_group_id', `membership_person_group`.membership_group_id_random AS 'membership_group_id_random'
FROM `v_membership_person_group` AS `membership_person_group`
WHERE
{sql_membership_person_id}
{sql_enabled}
ORDER BY -`membership_person_group`.sort DESC, `membership_person_group`.created_on DESC, `membership_person_group`.updated_on DESC
{sql_limit};
"""
else: return False
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(sql)
if membership_group_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
membership_group_rec_li = membership_group_rec_li_result
else:
membership_group_rec_li = []
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(membership_group_rec_li_result)
return membership_group_rec_li
# ### END ### API Membership Group Methods ### get_membership_group_rec_list() ###
# ### BEGIN ### API Membership Group Methods ### create_membership_group_obj() ###
def create_membership_group_obj(
membership_group_dict_obj: Membership_Group_Base
) -> bool|dict|int:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
membership_group_obj_data = membership_group_dict_obj.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'created_on', 'updated_on'})
if membership_group_obj_in_result := sql_insert(
data=membership_group_obj_data,
table_name='membership_group',
rm_id_random=True,
id_random_length=8
): pass
else: return False
log.debug(membership_group_obj_in_result)
membership_group_id = membership_group_obj_in_result
log.debug(f'New membership_group_id: {membership_group_id}')
return membership_group_id
# ### END ### API Membership Group Methods ### create_membership_group_obj() ###
# ### BEGIN ### API Membership Group Methods ### update_membership_group_obj() ###
def update_membership_group_obj(
membership_group_id: int|str, # This allows for updating of the id_random value.
membership_group_dict_obj: Membership_Group_Base,
create_sub_obj: bool = False,
) -> bool:
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if membership_group_id := redis_lookup_id_random(record_id_random=membership_group_id, table_name='membership_group'): pass
else: return False
membership_group_dict_obj.id = membership_group_id
log.debug(membership_group_dict_obj)
log.debug(membership_group_dict_obj.dict(by_alias=False, exclude_unset=True))
# log.debug(membership_group_dict_obj.dict(by_alias=False, exclude_unset=False))
membership_group_dict_up = membership_group_dict_obj.dict(by_alias=False, exclude_unset=True)
log.debug(membership_group_dict_up)
if membership_group_dict_obj_result := sql_update(data=membership_group_dict_up, table_name='membership_group', rm_id_random=True):
log.debug(membership_group_dict_obj_result)
return True
else:
log.debug(membership_group_dict_obj_result)
return False
# ### END ### API Membership Group Methods ### update_membership_group_obj() ###

View File

@@ -0,0 +1,195 @@
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.membership_person_methods import get_membership_person_rec_list, load_membership_person_obj
from app.models.common_field_schema import default_num_bytes
from app.models.membership_person_group_models import Membership_Person_Group_Base
# ### BEGIN ### API Membership Group Person Methods ### load_membership_person_group_obj() ###
def load_membership_person_group_obj(
membership_person_group_id: int|str,
limit: int = 1000,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
enabled: str = 'enabled', # enabled, disabled, all
inc_address: bool = False,
inc_contact: bool = False,
inc_membership_cfg: bool = False,
inc_membership_group: bool = False,
inc_membership_person: bool = False,
inc_membership_person_profile: bool = False,
inc_membership_person_type: bool = False,
inc_membership_type: bool = False,
inc_organization: bool = False,
inc_person: bool = False,
inc_product: bool = False,
# inc_product_list: bool = False,
) -> Membership_Person_Group_Base|bool:
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if membership_person_group_id := redis_lookup_id_random(record_id_random=membership_person_group_id, table_name='membership_person_group'): pass
else: return False
log.debug(membership_person_group_id)
if membership_person_group_rec := sql_select(table_name='v_membership_person_group', record_id=membership_person_group_id):
log.debug(membership_person_group_rec)
else: return False
log.debug(membership_person_group_rec)
try:
membership_person_group_obj = Membership_Person_Group_Base(**membership_person_group_rec)
log.debug(membership_person_group_obj)
except ValidationError as e:
log.error(e.json())
# Updated 2021-06-21
if inc_membership_person:
from app.methods.membership_person_methods import load_membership_person_obj
membership_person_id = membership_person_group_rec.get('membership_person_id', None)
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(membership_person_id)
if membership_person_result := load_membership_person_obj(
membership_person_id = membership_person_id,
limit = limit,
enabled = enabled,
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
inc_address = inc_address,
inc_contact = inc_contact,
inc_membership_person_profile = inc_membership_person_profile,
inc_membership_type = inc_membership_type,
inc_organization = inc_organization,
inc_person = inc_person,
inc_user = inc_user,
):
membership_person_group_obj.membership_person = membership_person_result
else: membership_person_group_obj.membership_person = None
log.debug(membership_person_result)
if model_as_dict:
return membership_person_group_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return membership_person_group_obj
# ### END ### API Membership Group Person Methods ### load_membership_person_group_obj() ###
# ### BEGIN ### API Membership Group Person Methods ### get_membership_person_group_rec_list() ###
def get_membership_person_group_rec_list(
for_obj_type: str,
for_obj_id: str,
limit: int = 1000,
enabled: str = 'enabled', # enabled, disabled, all
) -> list|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if for_obj_id := redis_lookup_id_random(record_id_random=for_obj_id, table_name=for_obj_type): pass
else: return False
data = {}
data[f'{for_obj_type}_id'] = for_obj_id
# data['for_obj_type'] = for_obj_type
sql_obj_type_id = f'`tbl`.{for_obj_type}_id = :{for_obj_type}_id'
if enabled in ['enabled', 'disabled', 'all']:
if enabled == 'enabled':
data['enable'] = True
sql_enabled = f'AND `tbl`.enable = :enable'
elif enabled == 'disabled':
data['enable'] = False
sql_enabled = f'AND `tbl`.enable = :enable'
elif enabled == 'all':
sql_enabled = ''
if limit:
data['limit'] = limit
sql_limit = f'LIMIT :limit'
else:
sql_limit = ''
sql = f"""
SELECT `tbl`.id AS 'membership_person_group_id', `tbl`.id_random AS 'membership_person_group_id_random'
FROM `membership_person_group` AS `tbl`
WHERE
{sql_obj_type_id}
{sql_enabled}
ORDER BY `tbl`.created_on DESC, `tbl`.updated_on DESC
{sql_limit};
"""
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(sql)
if membership_person_group_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
membership_person_group_rec_li = membership_person_group_rec_li_result
else:
membership_person_group_rec_li = []
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(membership_person_group_rec_li_result)
return membership_person_group_rec_li
# ### END ### API Membership Group Person Methods ### get_membership_person_group_rec_list() ###
# ### BEGIN ### API Membership Group Person Methods ### create_membership_person_group_obj() ###
def create_membership_person_group_obj(
membership_person_group_obj_new: Membership_Person_Group_Base
) -> bool|dict|int:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
membership_person_group_obj_data = membership_person_group_obj_new.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'created_on', 'updated_on'})
if membership_person_group_obj_in_result := sql_insert(
data=membership_person_group_obj_data,
table_name='membership_person_group',
rm_id_random=True,
id_random_length=8
): pass
else: return False
log.debug(membership_person_group_obj_in_result)
membership_person_group_id = membership_person_group_obj_in_result
log.debug(f'New membership_person_group_id: {membership_person_group_id}')
return membership_person_group_id
# ### END ### API Membership Group Person Methods ### create_membership_person_group_obj() ###
# ### BEGIN ### API Membership Group Person Methods ### update_membership_person_group_obj() ###
def update_membership_person_group_obj(
membership_person_group_id: int|str, # This allows for updating of the id_random value.
membership_person_group_obj_up: Membership_Person_Group_Base,
create_sub_obj: bool = False,
) -> bool|dict:
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if membership_person_group_id := redis_lookup_id_random(record_id_random=membership_person_group_id, table_name='membership_person_group'): pass
else: return False
membership_person_group_obj_up.id = membership_person_group_id
log.debug(membership_person_group_obj_up)
log.debug(membership_person_group_obj_up.dict(by_alias=False, exclude_unset=True))
# log.debug(membership_person_group_obj_up.dict(by_alias=False, exclude_unset=False))
membership_person_group_dict_up = membership_person_group_obj_up.dict(by_alias=False, exclude_unset=True)
log.debug(membership_person_group_dict_up)
if membership_person_group_obj_up_result := sql_update(data=membership_person_group_dict_up, table_name='membership_person_group', rm_id_random=True):
log.debug(membership_person_group_obj_up_result)
return True
else:
log.debug(membership_person_group_obj_up_result)
return False
# ### END ### API Membership Group Person Methods ### update_membership_person_group_obj() ###

View File

@@ -0,0 +1,694 @@
from __future__ import annotations
import datetime, json
from typing import Dict, List, Optional, Set, Union
from pydantic import BaseModel, EmailStr, Field, PrivateAttr, ValidationError, validator
from app.lib_general import log, logging, logger_reset
from app.db_sql import redis_lookup_id_random, sql_insert, sql_insert_or_update, sql_select, sql_update
from app.methods.membership_cfg_methods import load_membership_cfg_obj
from app.methods.membership_group_methods import get_membership_group_rec_list, load_membership_group_obj
from app.methods.membership_person_group_methods import get_membership_person_group_rec_list, load_membership_person_group_obj
from app.methods.membership_person_profile_methods import get_membership_person_profile_rec_list, load_membership_person_profile_obj
from app.methods.membership_person_type_methods import create_membership_person_type_obj, update_membership_person_type_obj
from app.methods.person_methods import load_person_obj
from app.methods.product_methods import load_product_obj
from app.methods.user_methods import load_user_obj
from app.models.common_field_schema import default_num_bytes
from app.models.membership_person_models import Membership_Person_Base
# ### BEGIN ### API Membership Person Methods ### load_membership_person_obj() ###
# Updated 2022-01-11
@logger_reset
def load_membership_person_obj(
membership_person_id: int|str,
limit: int = 100,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
enabled: str = 'enabled', # enabled, disabled, all
inc_address: bool = False,
inc_contact: bool = False,
inc_membership_cfg: bool = False,
# inc_membership_group: bool = False, # Their primary membership group
inc_membership_person_group_list: bool = False, # List of membership group for a person - 2022-01-11
# inc_membership_person_group: bool = False, # The person information for their primary membership group
# inc_membership_person_group_list: bool = False, # The person information for all of their membership groups
# inc_membership_person_profile: bool = False,
# inc_membership_person_profile_cust: bool = False, # Extended profile?
inc_membership_person_profile: bool = False, # Membership profile for a person - 2022-01-11
inc_membership_person_type: bool = False, # Primary membership type for a person - 2022-01-11
# inc_membership_type_list: bool = False, # The list of membership types the person is a part of
# inc_membership_person_type: bool = False, # The person information for their primary membership type
# inc_membership_person_type_list: bool = False, # The person information for all of their membership types
inc_order: bool = False,
# inc_organization: bool = False,
inc_person: bool = False,
inc_product: bool = False, # The product the person actually purchased for a member_type or member_group
inc_product_list: bool = False, # The list of products that give access to a member_type or member_group
# inc_user: bool = False,
) -> Membership_Person_Base:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if membership_person_id := redis_lookup_id_random(record_id_random=membership_person_id, table_name='membership_person'): pass
else: return False
if membership_person_rec := sql_select(table_name='v_membership_person', record_id=membership_person_id):
log.debug(membership_person_rec)
else: return False
log.debug(membership_person_rec)
try:
membership_person_obj = Membership_Person_Base(**membership_person_rec)
except ValidationError as e:
log.error(e.json())
return False
log.debug(membership_person_obj)
# Updated 2021-06-24
if inc_membership_cfg:
if membership_cfg_obj_result := load_membership_cfg_obj(
account_id = membership_person_rec.get('account_id', None),
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
):
membership_person_obj.membership_cfg = membership_cfg_obj_result
else: membership_person_obj.membership_cfg = {} # None
# if inc_membership_group: pass # The primary membership group the person is a part of. Not used at this time. 2021-12-16
# Updated 2021-01-12
if inc_membership_person_group_list: # List of membership group for a person - 2022-01-11
if membership_person_group_rec_list_result := get_membership_person_group_rec_list(
membership_person_id = membership_person_id,
limit = limit,
enabled = enabled,
):
membership_person_group_result_list = []
for membership_person_group_rec in membership_person_group_rec_list_result:
if load_membership_person_group_result := load_membership_person_group_obj(
membership_person_group_id = membership_person_group_rec.get('membership_person_group_id', None),
limit = limit,
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
enabled = enabled,
# inc_membership_group_list = inc_membership_group_list,
# inc_membership_person_profile = inc_membership_person_profile,
# inc_organization = inc_organization,
# inc_person = inc_person,
# inc_product = inc_product,
# inc_product_list = inc_product_list,
# inc_user = inc_user,
):
membership_person_group_result_list.append(load_membership_person_group_result)
else: membership_person_group_result_list.append(None)
membership_person_obj.membership_group_list = membership_person_group_result_list
else: membership_person_obj.membership_person_group_list = []
# Updated 2021-07-09
# if inc_membership_person_group_list: # The person information for the list of groups the member is a part of
# if membership_person_group_rec_list_result := get_membership_person_group_rec_list(
# for_obj_type = 'membership_person',
# for_obj_id = membership_person_id,
# # membership_person_id = membership_person_id,
# limit = limit,
# enabled = enabled,
# ):
# membership_person_group_result_list = []
# for membership_person_group_rec in membership_person_group_rec_list_result:
# if load_membership_person_group_result := load_membership_person_group_obj(
# membership_person_group_id = membership_person_group_rec.get('membership_person_group_id', None),
# limit = limit,
# by_alias = by_alias,
# exclude_unset = exclude_unset,
# model_as_dict = model_as_dict,
# enabled = enabled,
# # inc_membership_person_group_list = inc_membership_person_group_list,
# # inc_membership_person_profile = inc_membership_person_profile,
# # inc_organization = inc_organization,
# # inc_person = inc_person,
# inc_product = inc_product,
# # inc_product_list = inc_product_list,
# # inc_user = inc_user,
# ):
# membership_person_group_result_list.append(load_membership_person_group_result)
# else: membership_person_group_result_list.append(None)
# membership_person_obj.membership_person_group_list = membership_person_group_result_list
# else: membership_person_obj.membership_person_group_list = []
# Updated 2021-06-21
if inc_membership_person_profile: # Membership profile for a person - 2022-01-11
membership_person_profile_id = membership_person_rec.get('membership_person_profile_id', None)
log.debug(membership_person_profile_id)
if membership_person_profile_result := load_membership_person_profile_obj(
membership_person_profile_id = membership_person_profile_id,
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
inc_address = inc_address,
inc_contact = inc_contact,
# inc_membership = inc_membership,
# inc_membership_cfg = inc_membership_cfg,
inc_organization = inc_organization,
):
membership_person_obj.membership_person_profile = membership_person_profile_result
else: membership_person_obj.membership_person_profile = {} # None
log.debug(membership_person_profile_result)
# Updated 2021-01-12
if inc_membership_person_type: # Primary membership type for a person - 2022-01-11
from app.methods.membership_person_type_methods import load_membership_person_type_obj
membership_person_type_id = membership_person_rec.get('membership_person_type_id', None)
log.debug(membership_person_type_id)
if membership_person_type_result := load_membership_person_type_obj(
membership_person_type_id = membership_person_type_id,
limit = limit,
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
enabled = enabled,
# inc_address = inc_address,
# inc_contact = inc_contact,
# inc_membership = inc_membership,
# inc_membership_cfg = inc_membership_cfg,
# inc_organization = inc_organization,
inc_product_list = inc_product_list,
):
membership_person_obj.membership_person_type = membership_person_type_result
else: membership_person_obj.membership_person_type = {} # None
log.debug(membership_person_type_result)
# if inc_membership_person_type_list: pass # All of the membership types the person is a part of
# Updated 2021-07-09
# if inc_membership_person_type: # The primary membership type person information for the person
# from app.methods.membership_person_type_methods import load_membership_person_type_obj
# membership_person_type_id = membership_person_rec.get('membership_person_type_id', None)
# log.debug(membership_person_type_id)
# if membership_person_type_result := load_membership_person_type_obj(
# membership_person_type_id = membership_person_type_id,
# limit = limit,
# by_alias = by_alias,
# exclude_unset = exclude_unset,
# model_as_dict = model_as_dict,
# enabled = enabled,
# inc_membership_cfg = inc_membership_cfg,
# inc_membership_type = inc_membership_type,
# inc_product = inc_product,
# ):
# membership_person_obj.membership_person_type = membership_person_type_result
# else: membership_person_obj.membership_person_type = {} # None
# log.debug(membership_person_type_result)
# if inc_membership_person_type_list: pass # All of the membership type person records for a person
# Updated 2021-06-18
if inc_person:
person_id = membership_person_rec.get('person_id', None)
log.debug(person_id)
if person_result := load_person_obj(
person_id = person_id,
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
inc_address = inc_address,
inc_contact = inc_contact,
# inc_organization = inc_organization,
# inc_user = inc_user,
):
membership_person_obj.person = person_result
else: membership_person_obj.person = {} # None
log.debug(person_result)
# Updated 2021-06-24
if inc_product:
product_id = membership_person_rec.get('product_id', None)
log.debug(product_id)
if product_result := load_product_obj(
product_id = product_id,
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
):
membership_person_obj.product = product_result
else: membership_person_obj.product = {} # None
log.debug(product_result)
# Updated 2021-06-18
# if inc_user:
# log.warning(f'This is being deprecated? load_membership_person_obj() inc_user')
# user_id = membership_person_rec.get('user_id', None)
# if user_result := load_user_obj(
# user_id = user_id,
# by_alias = by_alias,
# exclude_unset = exclude_unset,
# model_as_dict = model_as_dict,
# # inc_address = inc_address,
# # inc_contact = inc_contact,
# # inc_organization = inc_organization,
# # inc_person = inc_person,
# ):
# membership_person_obj.user = user_result
# else: membership_person_obj.user = {} # None
# log.debug(user_result)
if model_as_dict:
return membership_person_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return membership_person_obj
# ### END ### API Membership Person Methods ### load_membership_person_obj() ###
# ### BEGIN ### API Membership Person Methods ### get_membership_person_rec_list() ###
@logger_reset
def get_membership_person_rec_list(
for_obj_type: str,
for_obj_id: str,
limit: int = 1000,
enabled: str = 'enabled', # enabled, disabled, all
) -> list|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if for_obj_id := redis_lookup_id_random(record_id_random=for_obj_id, table_name=for_obj_type): pass
else: return False
data = {}
data[f'{for_obj_type}_id'] = for_obj_id
allowed_forign_key_li = ['account', 'membership_type', 'person', 'user']
if for_obj_type in allowed_forign_key_li:
log.info(f'Query using forign key: {for_obj_type} {for_obj_id}')
sql_obj_type_id = f'`tbl`.{for_obj_type}_id = :{for_obj_type}_id'
if enabled in ['enabled', 'disabled', 'all']:
if enabled == 'enabled':
data['enable'] = True
sql_enabled = f'AND `tbl`.enable = :enable'
elif enabled == 'disabled':
data['enable'] = False
sql_enabled = f'AND `tbl`.enable = :enable'
elif enabled == 'all':
sql_enabled = ''
if limit:
data['limit'] = limit
sql_limit = f'LIMIT :limit'
else:
sql_limit = ''
sql = f"""
SELECT `tbl`.id AS 'membership_person_id', `tbl`.id_random AS 'membership_person_id_random'
FROM `membership_person` AS `tbl`
WHERE
{sql_obj_type_id}
{sql_enabled}
ORDER BY `tbl`.created_on DESC, `tbl`.updated_on DESC
{sql_limit};
"""
elif for_obj_type == 'unknown':
log.info(f'Query using joined table: {for_obj_type} {for_obj_id}')
# sql_obj_type_id = f'`membership_person`.{for_obj_type}_id = :{for_obj_type}_id'
# if enabled in ['enabled', 'disabled', 'all']:
# if enabled == 'enabled':
# data['enable'] = True
# sql_enabled = f'AND `membership_person`.enable = :enable'
# elif enabled == 'disabled':
# data['enable'] = False
# sql_enabled = f'AND `membership_person`.enable = :enable'
# elif enabled == 'all':
# sql_enabled = ''
# if limit:
# data['limit'] = limit
# sql_limit = f'LIMIT :limit'
# else:
# sql_limit = ''
# sql = f"""
# SELECT `membership_person`.id AS 'membership_type_id', `membership_person`.id_random AS 'membership_type_id_random'
# FROM `membership_person`
# INNER JOIN membership_type ON membership_person.membership_type_id = membership_type.id
# WHERE
# {sql_obj_type_id}
# {sql_enabled}
# ORDER BY `membership_person`.created_on DESC, `membership_person`.updated_on DESC
# {sql_limit};
# """
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(sql)
# sql_obj_type_id = f'`tbl`.{for_obj_type}_id = :{for_obj_type}_id'
# if enabled in ['enabled', 'disabled', 'all']:
# if enabled == 'enabled':
# data['enable'] = True
# sql_enabled = f'AND `tbl`.enable = :enable'
# elif enabled == 'disabled':
# data['enable'] = False
# sql_enabled = f'AND `tbl`.enable = :enable'
# elif enabled == 'all':
# sql_enabled = ''
# if limit:
# data['limit'] = limit
# sql_limit = f'LIMIT :limit'
# else:
# sql_limit = ''
# sql = f"""
# SELECT `tbl`.id AS 'membership_person_id', `tbl`.id_random AS 'membership_person_id_random'
# FROM `membership_person` AS `tbl`
# WHERE
# {sql_obj_type_id}
# {sql_enabled}
# ORDER BY `tbl`.created_on DESC, `tbl`.updated_on DESC
# {sql_limit};
# """
if membership_person_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
membership_person_rec_li = membership_person_rec_li_result
else:
membership_person_rec_li = []
log.debug(membership_person_rec_li_result)
return membership_person_rec_li
# ### END ### API Membership Person Methods ### get_membership_person_rec_list() ###
# ### BEGIN ### API Membership Person Methods ### create_membership_person_obj() ###
# Updated 2022-01-04
@logger_reset
def create_membership_person_obj(
account_id: int,
person_id: int,
membership_person_dict_obj: Membership_Person_Base,
create_sub_obj: bool = False,
fail_any: bool = False, # Fail if any thing goes wrong for sub objects
return_outline: bool = False,
) -> bool|dict|int:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
# ### SECTION ### Secondary data validation
log.info('Checking requirements...')
if account_id := redis_lookup_id_random(record_id_random=account_id, table_name='account'): pass
else:
log.error('Missing or invalid Account ID passed. Failed requirement.')
log.info(f'Account ID: {account_id}')
if person_id := redis_lookup_id_random(record_id_random=person_id, table_name='person'): pass
else:
log.error('Missing or invalid Person ID passed. Failed requirement.')
log.info(f'Person ID: {person_id}')
log.info('Create dictionary or Pydantic object')
log.debug(type(membership_person_dict_obj))
if isinstance(membership_person_dict_obj, dict):
membership_person_dict = membership_person_dict_obj
membership_person_dict['account_id'] = account_id
membership_person_dict['person_id'] = person_id
try:
membership_person_obj = Membership_Person_Base(**membership_person_dict)
except ValidationError as e:
log.error(e.json())
return False
else:
membership_person_obj = membership_person_dict_obj
membership_person_obj.account_id = account_id
membership_person_obj.person_id = person_id
log.debug(membership_person_obj)
membership_person_dict = membership_person_obj.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'membership_cfg', 'membership_person_group_list', 'membership_person_profile', 'membership_person_type', 'membership_group', 'membership_group_list', 'membership_type', 'membership_type_list', 'person', 'product', 'created_on', 'updated_on'})
# ### SECTION ### Process data
membership_person_obj.account_id = account_id # Is this needed?
membership_person_dict['account_id'] = account_id
if membership_person_dict_in_result := sql_insert(
data = membership_person_dict,
table_name = 'membership_person',
rm_id_random = True,
id_random_length = default_num_bytes
): pass
else:
log.warning(f'Membership Person not created.')
log.debug(membership_person_dict_in_result)
return False
log.debug(membership_person_dict_in_result)
membership_person_id = membership_person_dict_in_result
membership_person_outline = {}
membership_person_outline['membership_person_id'] = membership_person_id
membership_person_outline['account_id'] = account_id
membership_person_outline['person_id'] = person_id
membership_person_outline['membership_person_group_list'] = []
membership_person_outline['membership_person_profile'] = {}
membership_person_outline['membership_person_profile_id'] = None
membership_person_outline['membership_person_type'] = {}
membership_person_outline['membership_person_type_id'] = None
if membership_person_obj.membership_person_group_list:
log.info('Looping through Membership Person Group object list')
for membership_person_group_obj in membership_person_obj.membership_person_group_list:
if membership_person_group_id := membership_person_group_obj.id:
log.info('Updating Membership Person Group object')
if membership_person_group_update_result := update_membership_person_group_obj(
membership_person_group_id = membership_person_group_id,
membership_person_group_dict_obj = membership_person_group_obj,
): pass
else: return False
else:
log.info('Creating Membership Person Group object')
if membership_person_group_create_result := create_membership_person_group_obj(
membership_person_id = membership_person_id,
membership_person_group_dict_obj = membership_person_group_obj,
): pass
else: pass
if membership_person_obj.membership_person_type and membership_person_obj.membership_person_type.id:
log.info('Updating Membership Person Type object')
membership_person_type_id = membership_person_obj.membership_person_type.id
if membership_person_type_update_result := update_membership_person_type_obj(
membership_person_type_id = membership_person_type_id,
membership_person_type_dict_obj = membership_person_obj.membership_person_type,
): pass
else: return False
elif membership_person_obj.membership_person_type:
log.info('Creating Membership Type object')
if membership_person_type_create_result := create_membership_person_type_obj(
membership_person_id = membership_person_id,
membership_person_type_dict_obj = membership_person_obj.membership_person_type,
): pass
else: return False
else: pass
if return_outline:
log.debug(f'Returning the Membership Person Outline: {membership_person_outline}')
return membership_person_outline
else:
log.debug(f'Returning the Membership Person ID: {membership_person_id}')
return membership_person_id
# ### END ### API Membership Person Methods ### create_membership_person_obj() ###
# ### BEGIN ### API Membership Person Methods ### update_membership_person_obj() ###
# Updated 2022-01-04
@logger_reset
def update_membership_person_obj(
membership_person_id: int,
membership_person_dict_obj: Membership_Person_Base,
create_sub_obj: bool = False,
fail_any: bool = False, # Fail if any thing goes wrong for sub objects
return_outline: bool = False,
) -> bool|dict:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
# ### SECTION ### Secondary data validation
log.info('Checking requirements...')
if membership_person_id := redis_lookup_id_random(record_id_random=membership_person_id, table_name='membership_person'): pass
else:
log.error('Membership Person ID passed but is invalid. Failed requirement.')
return False
# ### SECTION ### Secondary data validation
log.info('Create dictionary or Pydantic object')
log.debug(type(membership_person_dict_obj))
if isinstance(membership_person_dict_obj, dict):
membership_person_dict = membership_person_dict_obj
membership_person_dict['membership_person_id'] = membership_person_id
try:
membership_person_obj = Membership_Person_Base(**membership_person_dict)
except ValidationError as e:
log.error(e.json())
return False
else:
membership_person_obj = membership_person_dict_obj
membership_person_obj.id = membership_person_id
log.debug(json.dumps(membership_person_obj, indent=2, default=str))
membership_person_dict = membership_person_obj.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'membership_cfg', 'membership_person_group_list', 'membership_person_profile', 'membership_person_type', 'membership_group', 'membership_group_list', 'membership_type', 'membership_type_list', 'person', 'product', 'created_on', 'updated_on'})
log.debug(membership_person_dict)
# ### SECTION ### Process data
if membership_person_dict_up_result := sql_update(
data = membership_person_dict,
table_name = 'membership_person',
rm_id_random = True,
): pass
elif membership_person_dict_up_result is None: pass
else:
log.warning(f'Membership Person not updated.')
log.debug(membership_person_dict_up_result)
return False
membership_person_outline = {}
membership_person_outline['membership_person_id'] = membership_person_id
# membership_person_outline['account_id'] = account_id
# membership_person_outline['person_id'] = person_id
membership_person_outline['membership_person_group_list'] = []
membership_person_outline['membership_person_profile'] = {}
membership_person_outline['membership_person_profile_id'] = None
membership_person_outline['membership_person_type'] = {}
membership_person_outline['membership_person_type_id'] = None
if membership_person_obj.membership_person_group_list:
log.info('Looping through Membership Person Group object list')
for membership_person_group_obj in membership_person_obj.membership_person_group_list:
if membership_person_group_id := membership_person_group_obj.id:
log.info('Updating Membership Person Group object')
if membership_person_group_update_result := update_membership_person_group_obj(
membership_person_group_id = membership_person_group_id,
membership_person_group_dict_obj = membership_person_group_obj,
): pass
else: return False
else:
log.info('Creating Membership Person Group object')
if membership_person_group_create_result := create_membership_person_group_obj(
membership_person_id = membership_person_id,
membership_person_group_dict_obj = membership_person_group_obj,
): pass
else: pass
if membership_person_obj.membership_person_type and membership_person_obj.membership_person_type.id:
log.info('Updating Membership Person Type object')
membership_person_type_id = membership_person_obj.membership_person_type.id
if membership_person_type_update_result := update_membership_person_type_obj(
membership_person_type_id = membership_person_type_id,
membership_person_type_dict_obj = membership_person_obj.membership_person_type,
): pass
else: return False
elif membership_person_obj.membership_person_type:
log.info('Creating Membership Person Type object')
if membership_person_type_create_result := create_membership_person_type_obj(
membership_person_id = membership_person_id,
membership_person_type_dict_obj = membership_person_obj.membership_person_type,
): pass
else: return False
else: pass
# # NOTE: Use object model version because of better type checking and validations
# if membership_person_obj.membership_person_type:
# membership_person_outline['membership_person_type_id'] = None
# membership_person_type_obj = membership_person_obj.membership_person_type
# if membership_person_type_id := membership_person_obj.membership_person_type_id: pass
# elif membership_person_type_id := membership_person_type_obj.id: pass
# else: membership_person_type_id = None
# membership_person_type_obj.membership_type_id = 6
# if membership_person_type_id:
# update_membership_person_type_obj_result = update_membership_person_type_obj(
# membership_person_type_dict_obj = membership_person_type_obj,
# membership_person_type_id = memberssql_update
# else:
# pass
# else:
# create_membership_person_type_obj_result = create_membership_person_type_obj(
# membership_person_type_dict_obj = membership_person_type_obj,
# membership_person_id = membership_person_id,
# fail_any = fail_any,
# return_outline = return_outline,
# )
# if isinstance(create_membership_person_type_obj_result, int):
# membership_person_type_id = create_membership_person_type_obj_result
# membership_person_outline['membership_person_type_id'] = membership_person_type_id
# else:
# pass
if return_outline:
log.debug(f'Returning the Membership Person Outline: {membership_person_outline}')
return membership_person_outline
else:
log.debug(f'Returning True')
return True
# ### END ### API Membership Person Methods ### create_update_membership_person_obj() ###
# ### BEGIN ### API Membership Person Methods ### create_update_membership_person_obj() ###
# Updated 2021-08-25
@logger_reset
def create_update_membership_person_obj(
membership_person_dict_obj: Membership_Person_Base|dict,
person_id: int|str,
account_id: int|str = None,
membership_person_id: int|str = None,
create_sub_obj: bool = False,
fail_any: bool = False, # Fail if any thing goes wrong for sub objects
return_outline: bool = False,
) -> int|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
log.info('Checking requirements...')
if membership_person_id:
log.info(f'Membership Person ID passed. Update existing Membership Person. Membership Person ID: {membership_person_id}')
if membership_person_id := redis_lookup_id_random(record_id_random=membership_person_id, table_name='membership_person'): pass
else:
log.error('Membership Person ID passed but is invalid. Failed requirement.')
return False
if person_id := redis_lookup_id_random(record_id_random=person_id, table_name='person'): pass
else:
log.error('Missing or invalid Person ID passed. Not required. Ignoring.')
log.info(f'Person ID: {person_id}')
# log.info('Attempting to get Person ID from related object.')
# from app.methods.person_methods import get_person_id_w_for_type_id
# if person_id := get_person_id_w_for_type_id(for_type='person_session', for_id=person_session_id): pass
# elif person_id := get_person_id_w_for_type_id(for_type='person_presentation', for_id=person_presentation_id): pass
# elif person_id := get_person_id_w_for_type_id(for_type='membership_person', for_id=membership_person_id): pass
# else:
# log.error('Unable to get Person ID from related object.')
# False
else:
log.info('No Membership Person ID passed. Create new Membership Person. Required: Account ID, Person ID')
if person_id := redis_lookup_id_random(record_id_random=person_id, table_name='person'): pass
else:
log.error('Missing or invalid Person ID passed. Failed requirement.')
log.info(f'Person ID: {person_id}')
log.info('Attempting to get Person ID from related object.')
from app.methods.person_methods import get_person_id_w_for_type_id
if person_id := get_person_id_w_for_type_id(for_type='person_session', for_id=person_session_id): pass
elif person_id := get_person_id_w_for_type_id(for_type='person_presentation', for_id=person_presentation_id): pass
else:
log.error('Unable to get Person ID from related object.')
False
# ### END ### API Membership Person Methods ### create_update_membership_person_obj() ###

View File

@@ -0,0 +1,124 @@
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_select
from app.lib_general import log, logging
from app.methods.membership_cfg_methods import load_membership_cfg_obj
from app.models.membership_person_profile_models import Membership_Person_Profile_Base
# ### BEGIN ### API Membership Person Profile Methods ### load_membership_person_profile_obj() ###
# Updated 2021-07-13
def load_membership_person_profile_obj(
membership_person_profile_id: int|str|None = None,
membership_person_id: int|str|None = None,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
inc_address: bool = False,
inc_contact: bool = False,
inc_membership_cfg: bool = False,
inc_organization: bool = False,
) -> Membership_Person_Profile_Base|dict|bool:
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if membership_person_profile_id := redis_lookup_id_random(record_id_random=membership_person_profile_id, table_name='membership_person_profile'): pass
if membership_person_id := redis_lookup_id_random(record_id_random=membership_person_id, table_name='membership_person'): pass
if membership_person_profile_id:
if membership_person_profile_rec := sql_select(
table_name = 'v_membership_person_profile',
record_id = membership_person_profile_id,
): pass
else: return None
elif membership_person_id:
if membership_person_profile_rec := sql_select(
table_name = 'v_membership_person_profile',
field_name = 'membership_person_id',
field_value = membership_person_id,
): pass
else: return None
else:
return None
log.debug(membership_person_profile_rec)
try:
membership_person_profile_obj = Membership_Person_Profile_Base(**membership_person_profile_rec)
log.debug(membership_person_profile_obj)
except ValidationError as e:
log.error(e.json())
# Updated 2021-07-13
if inc_membership_cfg:
if membership_cfg_obj_result := load_membership_cfg_obj(
account_id = membership_person_profile_rec.get('account_id', None),
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
):
membership_person_profile_obj.membership_cfg = membership_cfg_obj_result
else: membership_person_profile_obj.membership_cfg = None
if model_as_dict:
return membership_person_profile_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return membership_person_profile_obj
# ### END ### API Membership Person Profile Methods ### load_membership_person_profile_obj() ###
# ### BEGIN ### API Membership Person Profile Methods ### get_membership_person_profile_rec_list() ###
def get_membership_person_profile_rec_list(
for_obj_type: str,
for_obj_id: str,
limit: int = 1000,
enabled: str = 'enabled', # enabled, disabled, all
) -> list|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if for_obj_id := redis_lookup_id_random(record_id_random=for_obj_id, table_name=for_obj_type): pass
else: return False
data = {}
data[f'{for_obj_type}_id'] = for_obj_id
# data['for_obj_type'] = for_obj_type
sql_obj_type_id = f'`tbl`.{for_obj_type}_id = :{for_obj_type}_id'
if enabled in ['enabled', 'disabled', 'all']:
if enabled == 'enabled':
data['enable'] = True
sql_enabled = f'AND `tbl`.enable = :enable'
elif enabled == 'disabled':
data['enable'] = False
sql_enabled = f'AND `tbl`.enable = :enable'
elif enabled == 'all':
sql_enabled = ''
if limit:
data['limit'] = limit
sql_limit = f'LIMIT :limit'
else:
sql_limit = ''
sql = f"""
SELECT `tbl`.id AS 'membership_person_profile_id', `tbl`.id_random AS 'membership_person_profile_id_random'
FROM `membership_person_profile` AS `tbl`
WHERE
{sql_obj_type_id}
{sql_enabled}
ORDER BY `tbl`.created_on DESC, `tbl`.updated_on DESC
{sql_limit};
"""
if membership_person_profile_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
membership_person_profile_rec_li = membership_person_profile_rec_li_result
else:
membership_person_profile_rec_li = []
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(membership_person_profile_rec_li_result)
return membership_person_profile_rec_li
# ### END ### API Membership Person Profile Methods ### get_membership_person_profile_rec_list() ###

View File

@@ -0,0 +1,325 @@
from __future__ import annotations
import datetime, json
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, logger_reset
# from app.methods.membership_person_methods import get_membership_person_rec_list, load_membership_person_obj
from app.methods.membership_cfg_methods import load_membership_cfg_obj
from app.methods.product_methods import get_product_rec_list, load_product_obj
from app.models.common_field_schema import default_num_bytes
from app.models.membership_person_type_models import Membership_Person_Type_Base
# ### BEGIN ### API Membership Type Person Methods ### load_membership_person_type_obj() ###
# Updated 2021-07-09
@logger_reset
def load_membership_person_type_obj(
membership_person_type_id: int|str,
limit: int = 1000,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
enabled: str = 'enabled', # enabled, disabled, all
inc_address: bool = False,
inc_contact: bool = False,
inc_membership_cfg: bool = False,
inc_membership_group: bool = False,
inc_membership_group_list: bool = False,
inc_membership_person: bool = False,
inc_membership_person_profile: bool = False,
inc_membership_type: bool = False,
inc_organization: bool = False,
inc_product: bool = False,
inc_product_list: bool = False,
inc_person: bool = False,
) -> Membership_Person_Type_Base|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if membership_person_type_id := redis_lookup_id_random(record_id_random=membership_person_type_id, table_name='membership_person_type'): pass
else: return False
log.debug(membership_person_type_id)
if membership_person_type_rec := sql_select(table_name='v_membership_person_type', record_id=membership_person_type_id):
log.debug(membership_person_type_rec)
else: return False
log.debug(membership_person_type_rec)
try:
membership_person_type_obj = Membership_Person_Type_Base(**membership_person_type_rec)
log.debug(membership_person_type_obj)
except ValidationError as e:
log.error(e.json())
# Updated 2021-06-24
if inc_membership_cfg:
if membership_cfg_obj_result := load_membership_cfg_obj(
account_id = membership_person_type_rec.get('account_id', None),
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
):
membership_person_type_obj.membership_cfg = membership_cfg_obj_result
else: membership_person_type_obj.membership_cfg = None
# Updated 2021-06-21
if inc_membership_person:
from app.methods.membership_person_methods import load_membership_person_obj
membership_person_id = membership_person_type_rec.get('membership_person_id', None)
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(membership_person_id)
if membership_person_result := load_membership_person_obj(
membership_person_id = membership_person_id,
limit = limit,
enabled = enabled,
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
inc_address = inc_address,
inc_contact = inc_contact,
inc_membership_person_profile = inc_membership_person_profile,
inc_membership_type = inc_membership_type,
inc_organization = inc_organization,
inc_person = inc_person,
inc_user = inc_user,
):
membership_person_type_obj.membership_person = membership_person_result
else: membership_person_type_obj.membership_person = None
log.debug(membership_person_result)
# Updated 2021-07-09
if inc_membership_type: # The primary membership type for the person
from app.methods.membership_type_methods import load_membership_type_obj
membership_type_id = membership_person_type_rec.get('membership_type_id', None)
log.debug(membership_type_id)
if membership_type_result := load_membership_type_obj(
membership_type_id = membership_type_id,
limit = limit,
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
enabled = enabled,
# inc_membership_cfg = inc_membership_cfg,
inc_product_list = inc_product_list,
):
membership_person_type_obj.membership_type = membership_type_result
else: membership_person_type_obj.membership_type = None
log.debug(membership_type_result)
# Updated 2021-07-09
if inc_product:
product_id = membership_person_type_rec.get('product_id', None)
log.debug(product_id)
if product_result := load_product_obj(
product_id = product_id,
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
):
membership_person_type_obj.product = product_result
else: membership_person_type_obj.product = None
log.debug(product_result)
if model_as_dict:
return membership_person_type_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return membership_person_type_obj
# ### END ### API Membership Type Person Methods ### load_membership_person_type_obj() ###
# ### BEGIN ### API Membership Type Person Methods ### get_membership_person_type_rec_list() ###
def get_membership_person_type_rec_list(
for_obj_type: str,
for_obj_id: str,
limit: int = 1000,
enabled: str = 'enabled', # enabled, disabled, all
) -> list|bool:
# log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if for_obj_id := redis_lookup_id_random(record_id_random=for_obj_id, table_name=for_obj_type): pass
else: return False
data = {}
data[f'{for_obj_type}_id'] = for_obj_id
# data['for_obj_type'] = for_obj_type
sql_obj_type_id = f'`tbl`.{for_obj_type}_id = :{for_obj_type}_id'
if enabled in ['enabled', 'disabled', 'all']:
if enabled == 'enabled':
data['enable'] = True
sql_enabled = f'AND `tbl`.enable = :enable'
elif enabled == 'disabled':
data['enable'] = False
sql_enabled = f'AND `tbl`.enable = :enable'
elif enabled == 'all':
sql_enabled = ''
if limit:
data['limit'] = limit
sql_limit = f'LIMIT :limit'
else:
sql_limit = ''
sql = f"""
SELECT `tbl`.id AS 'membership_person_type_id', `tbl`.id_random AS 'membership_person_type_id_random'
FROM `membership_person_type` AS `tbl`
WHERE
{sql_obj_type_id}
{sql_enabled}
ORDER BY `tbl`.created_on DESC, `tbl`.updated_on DESC
{sql_limit};
"""
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(sql)
if membership_person_type_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
membership_person_type_rec_li = membership_person_type_rec_li_result
else:
membership_person_type_rec_li = []
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(membership_person_type_rec_li_result)
return membership_person_type_rec_li
# ### END ### API Membership Type Person Methods ### get_membership_person_type_rec_list() ###
# ### BEGIN ### API Membership Type Person Methods ### create_membership_person_type_obj() ###
def create_membership_person_type_obj(
membership_person_id: int|str,
membership_person_type_dict_obj: Membership_Person_Type_Base,
fail_any: bool = False, # Fail if any thing goes wrong for sub objects
return_outline: bool = False,
) -> bool|dict|int:
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
# ### SECTION ### Secondary data validation
log.info('Checking requirements...')
if membership_person_id := redis_lookup_id_random(record_id_random=membership_person_id, table_name='membership_person'): pass
else:
log.error('Missing or invalid Membership Person ID passed. Failed requirement.')
log.info(f'Membership Person ID: {membership_person_id}')
return False
log.info('Create dictionary or Pydantic object')
log.debug(type(membership_person_type_dict_obj))
if isinstance(membership_person_type_dict_obj, dict):
membership_person_type_dict = membership_person_type_dict_obj
try:
membership_person_type_obj = Membership_Person_Type_Base(**membership_person_type_dict)
log.debug(membership_person_type_obj)
except ValidationError as e:
log.error(e.json())
return False
else:
membership_person_type_obj = membership_person_type_dict_obj
membership_person_type_obj.membership_person_id = membership_person_id
membership_person_type_dict = membership_person_type_obj.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'membership_cfg', 'membership_type', 'membership_type_list', 'product', 'created_on', 'updated_on'})
# ### SECTION ### Process data
membership_person_type_obj.membership_person_id = membership_person_id # Is this needed?
membership_person_type_dict['membership_person_id'] = membership_person_id
if membership_person_type_dict_in_result := sql_insert(
data = membership_person_type_dict,
table_name = 'membership_person_type',
rm_id_random = True,
id_random_length = default_num_bytes
): pass
else:
log.warning(f'Membership Person Type not created.')
log.debug(membership_person_type_dict_in_result)
return False
log.debug(membership_person_type_dict_in_result)
membership_person_type_id = membership_person_type_dict_in_result
membership_person_data = {}
membership_person_data['id'] = membership_person_id
membership_person_data['membership_person_type_id'] = membership_person_type_id
# NOTE: Need to update the membership_person record with the new primary membership person type record.
# NOTE: This makes it so that there is only one primary type.
if membership_person_dict_up_result := sql_update(
data = membership_person_data,
table_name = 'membership_person',
rm_id_random = True
):
log.debug(f'Returning True')
log.debug(membership_person_dict_up_result)
else:
log.warning(f'Membership Person not updated with the new Membership Person Type ID. Membership Person ID: {membership_person_id}; Membership Person Type ID: {membership_person_type_id}')
log.debug(membership_person_type_dict_up_result)
return False
log.info(f'Returning the Membership Person Type ID: {membership_person_type_id}')
return membership_person_type_id
# ### END ### API Membership Type Person Methods ### create_membership_person_type_obj() ###
# ### BEGIN ### API Membership Type Person Methods ### update_membership_person_type_obj() ###
def update_membership_person_type_obj(
membership_person_type_id: int|str, # This allows for updating of the id_random value.
membership_person_type_dict_obj: Membership_Person_Type_Base,
fail_any: bool = False, # Fail if any thing goes wrong for sub objects
return_outline: bool = False,
) -> bool|dict:
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
# ### SECTION ### Secondary data validation
log.info('Checking requirements...')
if membership_person_type_id := redis_lookup_id_random(record_id_random=membership_person_type_id, table_name='membership_person_type'): pass
else:
log.error('Membership Person Type ID passed but is invalid. Failed requirement.')
return False
log.info('Create dictionary or Pydantic object')
log.debug(type(membership_person_type_dict_obj))
if isinstance(membership_person_type_dict_obj, dict):
membership_person_type_dict = membership_person_type_dict_obj
try:
membership_person_type_obj = Membership_Person_Type_Base(**membership_person_type_dict)
log.debug(membership_person_type_obj)
except ValidationError as e:
log.error(e.json())
return False
else:
membership_person_type_obj = membership_person_type_dict_obj
membership_person_type_dict = membership_person_type_obj.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'membership_cfg', 'membership_type', 'membership_type_list', 'product', 'created_on', 'updated_on'})
# ### SECTION ### Process data
membership_person_type_obj.id = membership_person_type_id # Is this needed?
membership_person_type_dict['id'] = membership_person_type_id
log.debug(membership_person_type_obj)
log.debug(membership_person_type_obj.dict(by_alias=False, exclude_unset=True))
# log.debug(membership_person_type_obj.dict(by_alias=False, exclude_unset=False))
membership_person_type_dict = membership_person_type_obj.dict(by_alias=False, exclude_unset=True)
log.debug(membership_person_type_dict)
if membership_person_type_dict_up_result := sql_update(
data = membership_person_type_dict,
table_name = 'membership_person_type',
rm_id_random = True
):
log.debug(f'Returning True')
log.debug(membership_person_type_dict_up_result)
return True
elif membership_person_type_dict_up_result is None:
log.debug(f'Returning True')
log.debug(membership_person_type_dict_up_result)
return None
else:
log.warning(f'Membership Person Type not updated.')
log.debug(membership_person_type_dict_up_result)
return False
# ### END ### API Membership Type Person Methods ### update_membership_person_type_obj() ###

View File

@@ -0,0 +1,259 @@
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.lib_general import log, logging
from app.db_sql import redis_lookup_id_random, sql_select
from app.methods.membership_cfg_methods import load_membership_cfg_obj
from app.methods.membership_person_methods import get_membership_person_rec_list, load_membership_person_obj
from app.methods.product_methods import get_product_rec_list, load_product_obj
from app.models.membership_type_models import Membership_Type_Base
# ### BEGIN ### API Membership Type Methods ### load_membership_type_obj() ###
def load_membership_type_obj(
membership_type_id: int|str,
limit: int = 1000,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
enabled: str = 'enabled', # enabled, disabled, all
inc_address: bool = False,
inc_contact: bool = False,
inc_membership_cfg: bool = False,
inc_membership_group_list: bool = False, # under membership_person
inc_membership_person_group_list: bool = False,
inc_membership_person_list: bool = False,
inc_membership_person_profile: bool = False, # under membership_person
inc_organization: bool = False,
inc_person: bool = False,
inc_product: bool = False, # Per membership member
inc_product_list: bool = False, # One or more er membership type
inc_user: bool = False,
) -> Membership_Type_Base:
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if membership_type_id := redis_lookup_id_random(record_id_random=membership_type_id, table_name='membership_type'): pass
else: return False
if membership_type_rec := sql_select(table_name='v_membership_type', record_id=membership_type_id): pass
else: return False
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(membership_type_rec)
try:
membership_type_obj = Membership_Type_Base(**membership_type_rec)
log.debug(membership_type_obj)
except ValidationError as e:
log.error(e.json())
# Updated 2021-06-23
if inc_membership_cfg:
if membership_cfg_result := load_membership_cfg_obj(
account_id = membership_type_rec.get('account_id', None),
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
):
membership_type_obj.cfg = membership_cfg_result
else: membership_type_obj.cfg = None
# Updated 2021-06-23
if inc_membership_person_list:
if membership_person_rec_list_result := get_membership_person_rec_list(
for_obj_type = 'membership_type',
for_obj_id = membership_type_id,
limit = limit,
enabled = enabled,
):
membership_person_result_list = []
for membership_person_rec in membership_person_rec_list_result:
if load_membership_person_result := load_membership_person_obj(
membership_person_id = membership_person_rec.get('membership_person_id', None),
limit = limit,
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
enabled = enabled,
inc_membership_person_group_list = inc_membership_person_group_list,
inc_membership_person_profile = inc_membership_person_profile,
# inc_organization = inc_organization,
inc_person = inc_person,
# inc_product = inc_product,
):
membership_person_result_list.append(load_membership_person_result)
else: membership_person_result_list.append(None)
membership_type_obj.membership_person_list = membership_person_result_list
else: membership_type_obj.membership_person_list = []
# Updated 2021-06-23
if inc_product_list:
if product_rec_list_result := get_product_rec_list(
for_obj_type = 'membership_type',
for_obj_id = membership_type_id,
# prod_type = prod_type,
limit = limit,
enabled = enabled,
):
product_result_list = []
for product_rec in product_rec_list_result:
product_result_list.append(
load_product_obj(
product_id = product_rec.get('product_id', None),
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
)
)
membership_type_obj.product_list = product_result_list
else: membership_type_obj.product_list = []
if model_as_dict:
return membership_type_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return membership_type_obj
# ### END ### API Membership Type Methods ### load_membership_type_obj() ###
# ### BEGIN ### API Membership Type Methods ### get_membership_type_rec_list() ###
def get_membership_type_rec_list(
account_id: str = None,
# product_id: str = None,
type_level: int = None,
limit: int = 1000,
enabled: str = 'enabled', # enabled, disabled, all
) -> list|bool:
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if account_id := redis_lookup_id_random(record_id_random=account_id, table_name='account'): pass
# if product_id := redis_lookup_id_random(record_id_random=product_id, table_name='product'): pass
data = {}
data['account_id'] = account_id
# data['product_id'] = product_id
data['level'] = type_level
if account_id:
sql_account_id = f'`membership_type`.account_id = :account_id'
else: sql_account_id = ''
# if product_id:
# sql_product_id = f'`membership_type`.product_id = :product_id'
# else: sql_product_id = ''
if enabled in ['enabled', 'disabled', 'all']:
if enabled == 'enabled':
data['enable'] = True
sql_enabled = f'AND `membership_type`.enable = :enable'
elif enabled == 'disabled':
data['enable'] = False
sql_enabled = f'AND `membership_type`.enable = :enable'
elif enabled == 'all':
sql_enabled = ''
else: sql_enabled = ''
if type_level:
sql_type_level = f"""AND membership_type.level = :level"""
else: sql_type_level = ''
if limit:
data['limit'] = limit
sql_limit = f'LIMIT :limit'
else:
sql_limit = ''
sql = f"""
SELECT `membership_type`.id AS 'membership_type_id', `membership_type`.id_random AS 'membership_type_id_random'
FROM `v_membership_type` AS `membership_type`
WHERE
{sql_account_id}
{sql_type_level}
{sql_enabled}
ORDER BY -`membership_type`.sort DESC, `membership_type`.created_on DESC, `membership_type`.updated_on DESC
{sql_limit};
"""
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(sql)
if membership_type_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
membership_type_rec_li = membership_type_rec_li_result
else:
membership_type_rec_li = []
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(membership_type_rec_li_result)
return membership_type_rec_li
# ### END ### API Membership Type Methods ### get_membership_type_rec_list() ###
# ### BEGIN ### API Membership Type Methods ### create_membership_type_obj() ###
def create_membership_type_obj(
membership_type_dict_obj: Membership_Type_Base
) -> bool|dict|int:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
return membership_type_id
# ### END ### API Membership Type Methods ### create_membership_type_obj() ###
# ### BEGIN ### API Membership Type Methods ### get_membership_type_rec_list() ###
# def get_membership_type_rec_list(
# for_obj_type: str,
# for_obj_id: str,
# limit: int = 1000,
# enabled: str = 'enabled', # enabled, disabled, all
# ) -> list|bool:
# log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
# log.debug(locals())
# if for_obj_id := redis_lookup_id_random(record_id_random=for_obj_id, table_name=for_obj_type): pass
# else: return False
# data = {}
# data[f'{for_obj_type}_id'] = for_obj_id
# # data['for_obj_type'] = for_obj_type
# sql_obj_type_id = f'`tbl`.{for_obj_type}_id = :{for_obj_type}_id'
# if enabled in ['enabled', 'disabled', 'all']:
# if enabled == 'enabled':
# data['enable'] = True
# sql_enabled = f'AND `tbl`.enable = :enable'
# elif enabled == 'disabled':
# data['enable'] = False
# sql_enabled = f'AND `tbl`.enable = :enable'
# elif enabled == 'all':
# sql_enabled = ''
# if limit:
# data['limit'] = limit
# sql_limit = f'LIMIT :limit'
# else:
# sql_limit = ''
# sql = f"""
# SELECT `tbl`.id AS 'membership_type_id', `tbl`.id_random AS 'membership_type_id_random'
# FROM `membership_type` AS `tbl`
# WHERE
# {sql_obj_type_id}
# {sql_enabled}
# ORDER BY `tbl`.created_on DESC, `tbl`.updated_on DESC
# {sql_limit};
# """
# if membership_type_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
# membership_type_rec_li = membership_type_rec_li_result
# else:
# membership_type_rec_li = []
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
# log.debug(membership_type_rec_li_result)
# return membership_type_rec_li
# ### END ### API Membership Type Methods ### get_membership_type_rec_list() ###

View File

@@ -0,0 +1,187 @@
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.order_cart_line_models import Order_Cart_Line_Base
# ### BEGIN ### API Order Cart Line Methods ### create_order_cart_line_obj() ###
def create_order_cart_line_obj(order_cart_line_obj_new:Order_Cart_Line_Base):
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if not order_cart_line_obj_new:
return False
# Something needs to be added to lookup the current product information and copy that into the order_cart_line. Also knowing this will eventually be order_line.
order_cart_line_obj_data = order_cart_line_obj_new.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'user', 'created_on', 'updated_on'})
if order_cart_line_obj_in_result := sql_insert(data=order_cart_line_obj_data, table_name='order_cart_line', rm_id_random=True, id_random_length=8): pass
else:
return False
log.debug(order_cart_line_obj_in_result)
order_cart_line_id = order_cart_line_obj_in_result
log.debug(f'Returning the new order_cart_line_id: {order_cart_line_id}')
return order_cart_line_id
# ### END ### API Order Cart Line Methods ### create_order_cart_line_obj() ###
# ### BEGIN ### API Order Cart Line Methods ### load_order_cart_line_obj() ###
def load_order_cart_line_obj(
order_cart_line_id: int|str,
limit: int = 1000,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
) -> Order_Cart_Line_Base|dict|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if order_cart_line_id := redis_lookup_id_random(record_id_random=order_cart_line_id, table_name='order_cart_line'): pass
else: return False
if order_cart_line_rec := sql_select(table_name='v_order_cart_line', record_id=order_cart_line_id): pass
else: return False
#log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(order_cart_line_rec)
try:
order_cart_line_obj = Order_Cart_Line_Base(**order_cart_line_rec)
log.debug(order_cart_line_obj)
except ValidationError as e:
log.error(e.json())
return False
if model_as_dict:
return order_cart_line_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return order_cart_line_obj
# ### END ### API Order Cart Line Methods ### load_order_cart_line_obj() ###
# ### BEGIN ### API Order Cart Line Methods ### update_order_cart_line_obj() ###
def update_order_cart_line_obj(
order_cart_line_id: int|str, # Ideally the int ID should be passed. This allows for updating of the id_random value.
order_cart_line_obj_up: Order_Cart_Line_Base,
create_sub_obj: bool = False,
) -> bool:
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if order_cart_line_id := redis_lookup_id_random(record_id_random=order_cart_line_id, table_name='order_cart_line'): pass
else: return False
order_cart_line_obj_up.id = order_cart_line_id
log.debug(order_cart_line_obj_up)
# log.debug(order_cart_line_obj_up.dict(by_alias=True, exclude_unset=True))
log.debug(order_cart_line_obj_up.dict(by_alias=False, exclude_unset=True))
# log.debug(order_cart_line_obj_up.dict(by_alias=False, exclude_unset=False))
#order_cart_line_dict_up = order_cart_line_obj_up.dict(by_alias=False, exclude_unset=True)
# if order_cart_line_obj_up.person_id and order_cart_line_obj_up.person:
# person_id = order_cart_line_obj_up.person_id
# person_obj_up = order_cart_line_obj_up.person
# log.debug(person_id)
# log.debug(person_obj_up)
# if person_obj_up_result := update_person_obj(
# person_id = person_id,
# person_obj_up = person_obj_up,
# create_sub_obj = create_sub_obj,
# ):
# log.debug(person_obj_up_result)
# else:
# log.debug(person_obj_up_result)
# return False
# if order_cart_line_obj_up.user_id and order_cart_line_obj_up.user:
# user_id = order_cart_line_obj_up.user_id
# user_obj_up = order_cart_line_obj_up.user
# log.debug(user_id)
# log.debug(user_obj_up)
# if user_obj_up_result := update_user_obj(
# user_id = user_id,
# user_dict_obj = user_obj_up,
# create_sub_obj = create_sub_obj,
# ):
# log.debug(user_obj_up_result)
# else:
# log.debug(user_obj_up_result)
# return False
order_cart_line_dict_up = order_cart_line_obj_up.dict(by_alias=False, exclude_unset=True, exclude={'user'})
log.debug(order_cart_line_dict_up)
if order_cart_line_obj_up_result := sql_update(data=order_cart_line_dict_up, table_name='order_cart_line', rm_id_random=True):
log.debug(order_cart_line_obj_up_result)
return True
else:
log.debug(order_cart_line_obj_up_result)
return False
# ### END ### API Order Cart Line Methods ### update_order_cart_line_obj() ###
# ### BEGIN ### API Order Cart Line Methods ### get_order_cart_line_rec_list() ###
def get_order_cart_line_rec_list(
for_obj_type: str,
for_obj_id: str,
limit: int = 1000,
) -> list|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if for_obj_id := redis_lookup_id_random(record_id_random=for_obj_id, table_name=for_obj_type): pass
else: return False
data = {}
data[f'{for_obj_type}_id'] = for_obj_id
# data['for_obj_type'] = for_obj_type
sql_obj_type_id = f'`tbl`.{for_obj_type}_id = :{for_obj_type}_id'
# if enabled in ['enabled', 'disabled', 'all']:
# if enabled == 'enabled':
# data['enable'] = True
# sql_enabled = f'AND `tbl`.enable = :enable'
# elif enabled == 'disabled':
# data['enable'] = False
# sql_enabled = f'AND `tbl`.enable = :enable'
# elif enabled == 'all':
# sql_enabled = ''
sql_enabled = ''
if limit:
data['limit'] = limit
sql_limit = f'LIMIT :limit'
else:
sql_limit = ''
sql = f"""
SELECT `tbl`.id AS 'order_cart_line_id', `tbl`.id_random AS 'order_cart_line_id_random'
FROM `order_cart_line` AS `tbl`
WHERE
{sql_obj_type_id}
{sql_enabled}
ORDER BY `tbl`.created_on DESC, `tbl`.updated_on DESC
{sql_limit};
"""
if order_cart_line_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
order_cart_line_rec_li = order_cart_line_rec_li_result
else:
order_cart_line_rec_li = []
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(order_cart_line_rec_li_result)
return order_cart_line_rec_li
# ### END ### API Order Cart Line Methods ### get_order_cart_line_rec_list() ###

View File

@@ -0,0 +1,427 @@
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.lib_general import log, logging
from app.db_sql import redis_lookup_id_random, sql_insert_or_update, sql_insert, sql_update, sql_select
from app.methods.order_cart_line_methods import get_order_cart_line_rec_list, load_order_cart_line_obj
from app.methods.order_cfg_methods import load_order_cfg_obj
from app.models.order_cart_models import Order_Cart_Base
from app.models.order_cart_line_models import Order_Cart_Line_Base
def update_order_cart_obj(
order_cart_obj: Order_Cart_Base,
repl_order_cart_line_list: bool = False,
) -> bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if order_cart_obj.account_id_random and (order_cart_obj.person_id_random or order_cart_obj.user_id_random):
log.info(f'An account.id_random {order_cart_obj.account_id_random} was passed. And either a person.id_random {order_cart_obj.person_id_random} or user.id_random {order_cart_obj.user_id_random} was passed. We can update the order cart.')
elif order_cart_obj.account_id_random and not (order_cart_obj.person_id_random or order_cart_obj.user_id_random):
log.info(f'An account.id_random {order_cart_obj.account_id_random} was passed. A person.id_random {order_cart_obj.person_id_random} or user.id_random {order_cart_obj.user_id_random} was not passed. We can update the order cart without one of those.')
else:
log.error('An account ID. A person ID or user ID is not required until starting to process an order.')
return False
#if order_cart_id := redis_lookup_id_random(record_id_random=order_cart_id_random, table_name='order_cart'): pass
#else: return False
order_cart_id_random = order_cart_obj.id_random # id_random because can't ref the alias
#log.setLevel(logging.DEBUG)
log.debug(order_cart_obj.id)
log.debug(order_cart_id_random)
if order_cart_id := redis_lookup_id_random(record_id_random=order_cart_id_random, table_name='order_cart'): pass
else: return False
# log.setLevel(logging.DEBUG)
log.info('Loop through lines to update and calculate totals')
# Calculate totals
order_cart_total_amount: int = 0
order_cart_total_quantity: int = 0
for order_cart_line_obj in order_cart_obj.order_cart_line_list:
log.debug(order_cart_line_obj)
order_cart_line_obj.order_cart_id = order_cart_id
order_cart_line_obj.order_cart_id_random = order_cart_id_random
log.setLevel(logging.DEBUG)
if product_sql_select_result := sql_select(table_name='product', record_id=order_cart_line_obj.product_id):
product_obj = product_sql_select_result
log.debug(product_sql_select_result)
order_cart_line_obj_data = order_cart_line_obj.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'product_type_id', 'product_type', 'created_on', 'updated_on'})
# log.setLevel(logging.DEBUG)
log.debug(order_cart_line_obj_data)
data = {}
data['order_cart_id'] = order_cart_id
data['product_id'] = order_cart_line_obj.product_id
order_cart_line_obj_data['product_for_type'] = product_obj.get('for_type', None)
order_cart_line_obj_data['product_for_id'] = product_obj.get('for_id', None)
order_cart_line_obj_data['product_type_id'] = product_obj.get('type_id', None)
order_cart_line_obj_data['product_name'] = product_obj.get('name', None)
order_cart_line_obj_data['product_unit_price'] = product_obj.get('unit_price', None)
order_cart_line_obj_data['product_recurring'] = product_obj.get('recurring', None)
sql_select_result = sql_select(table_name='order_cart_line', data=data, rm_id_random=True)
# log.setLevel(logging.DEBUG)
log.debug(sql_select_result)
if sql_select_result:
# log.setLevel(logging.DEBUG)
log.info('A matching order cart line was found. Update...')
if order_cart_line_obj_up_result := sql_update(
data = order_cart_line_obj_data,
table_name = 'order_cart_line',
record_id = sql_select_result.get('id'),
rm_id_random = True,
id_random_length = 8,
): pass
else:
log.error('Something went wrong while trying to update an order cart line record.')
return False
else:
# log.setLevel(logging.DEBUG)
log.info('A matching order cart line was not found. Insert...')
if order_cart_line_obj_in_result := sql_insert(data=order_cart_line_obj_data, table_name='order_cart_line', rm_id_random=True, id_random_length=8): pass
else:
log.error('Something went wrong while trying to insert an order cart line record.')
return False
# order_cart_total_amount += order_cart_line_obj.quantity * order_cart_line_obj.amount
# order_cart_total_quantity += order_cart_line_obj.quantity
order_cart_line_data = {}
order_cart_line_data['order_cart_id'] = order_cart_id
if order_cart_line_sql_select_result := sql_select(table_name='order_cart_line', data=order_cart_line_data, rm_id_random=True, as_list=True):
log.setLevel(logging.DEBUG)
log.debug(sql_select_result)
for order_cart_line_rec in order_cart_line_sql_select_result:
log.debug(order_cart_line_rec)
quantity = order_cart_line_rec.get('quantity', 0)
amount = order_cart_line_rec.get('amount', 0)
order_cart_total_amount += quantity * amount
order_cart_total_quantity += quantity
order_cart_obj_new = {}
order_cart_obj_new['id'] = order_cart_id
order_cart_obj_new['account_id_random'] = order_cart_obj.account_id_random
order_cart_obj_new['person_id_random'] = order_cart_obj.person_id_random
# order_cart_obj_new['user_id_random'] = order_cart_obj.user_id_random
order_cart_obj_new['order_id_random'] = order_cart_obj.order_id_random
order_cart_obj_new['total_amount'] = order_cart_total_amount
order_cart_obj_new['total_quantity'] = order_cart_total_quantity
order_cart_obj_new['notes'] = order_cart_obj.notes
if order_cart_obj_resp := sql_update(data=order_cart_obj_new, table_name='order_cart', rm_id_random=True): pass
else:
log.error('Something went wrong while trying to update an order cart record.')
return False
return False
# ### BEGIN ### API Order Cart Methods ### load_order_cart_obj() ###
# Updated 2021-08-07
def load_order_cart_obj(
order_cart_id: int|str,
limit: int = 1000,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
enabled: str = 'enabled', # enabled, disabled, all
inc_order_cart_line_list: bool = False,
inc_order_cfg: bool = False,
) -> Order_Cart_Base|dict|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if order_cart_id := redis_lookup_id_random(record_id_random=order_cart_id, table_name='order_cart'): pass
else: return False
if order_cart_rec := sql_select(table_name='v_order_cart', record_id=order_cart_id): pass
else: return False
#log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(order_cart_rec)
try:
order_cart_obj = Order_Cart_Base(**order_cart_rec)
log.debug(order_cart_obj)
except ValidationError as e:
log.error(e.json())
# Updated 2021-08-07
if inc_order_cart_line_list:
if order_cart_line_rec_list_result := get_order_cart_line_rec_list(
for_obj_type = 'order_cart',
for_obj_id = order_cart_id,
limit = limit,
):
order_cart_line_result_list = []
for order_cart_line_rec in order_cart_line_rec_list_result:
order_cart_line_result_list.append(
load_order_cart_line_obj(
order_cart_line_id = order_cart_line_rec.get('order_cart_line_id', None),
limit = limit,
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
)
)
order_cart_obj.order_cart_line_list = order_cart_line_result_list
else: order_cart_obj.order_cart_line_list = []
if inc_order_cfg:
if order_cfg_result := load_order_cfg_obj(
account_id = order_cart_rec.get('account_id', None),
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
):
order_cart_obj.cfg = order_cfg_result
else: order_cart_obj.cfg = None
if model_as_dict:
return order_cart_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return order_cart_obj
# ### END ### API Order Cart Methods ### load_order_cart_obj() ###
# ### BEGIN ### API Order Cart Methods ### get_order_cart_rec_list() ###
# Updated 2021-08-07
def get_order_cart_rec_list(
for_obj_type: str,
for_obj_id: str,
limit: int = 1000,
enabled: str = 'enabled', # enabled, disabled, all
from_datetime: datetime.datetime = None,
to_datetime: datetime.datetime = None,
# status: str = 'complete', # started, in progress, complete, all
balance_gt: int = 0, # $0 to $99999
) -> list|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if for_obj_id := redis_lookup_id_random(record_id_random=for_obj_id, table_name=for_obj_type): pass
else: return False
data = {}
data[f'{for_obj_type}_id'] = for_obj_id
# data['for_obj_type'] = for_obj_type
sql_obj_type_id = f'`tbl`.{for_obj_type}_id = :{for_obj_type}_id'
if limit:
data['limit'] = limit
sql_limit = f'LIMIT :limit'
else:
sql_limit = ''
sql = f"""
SELECT `tbl`.id AS 'order_cart_id', `tbl`.id_random AS 'order_cart_id_random'
FROM `order` AS `tbl`
WHERE
{sql_obj_type_id}
ORDER BY `tbl`.created_on DESC, `tbl`.updated_on DESC
{sql_limit};
"""
if order_cart_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
order_cart_rec_li = order_cart_rec_li_result
else:
order_cart_rec_li = []
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(order_cart_rec_li_result)
return order_cart_rec_li
# ### END ### API Order Cart Methods ### get_order_cart_rec_list() ###
# ### BEGIN ### API Order Cart Methods ### get_order_cart_id_for_person_id() ###
# Updated 2021-11-16
def get_order_cart_id_for_person_id(
person_id: int|str,
) -> bool|int|None:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if person_id := redis_lookup_id_random(record_id_random=person_id, table_name='person'): pass
else: return False
data = {}
data['person_id'] = person_id
sql = f"""
SELECT `order_cart`.id AS 'order_cart_id', `order_cart`.id_random AS 'order_cart_id_random', `order_cart`.person_id AS person_id
FROM `order_cart` AS `order_cart`
WHERE `order_cart`.person_id = :person_id
LIMIT 1;
"""
if order_cart_data_result := sql_select(data=data, sql=sql):
log.debug(order_cart_data_result)
if order_cart_id := order_cart_data_result.get('order_cart_id', None): return order_cart_id
else: return False
else: return None
# ### END ### API Order Cart Methods ### get_order_cart_id_for_person_id() ###
# IS THIS STILL NEEDED?
# ### BEGIN ### API Order Cart Model ### save_order_cart_obj() ###
def old_save_order_cart_obj(order_cart_obj_new=None):
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if not order_cart_obj_new:
return False
#log.debug(order_cart_obj_new.dict(by_alias=False, exclude_defaults=False, exclude_unset=True))
# Get the current order_cart_line li to compare with what was sent
data = {}
data['order_cart_id_random'] = order_cart_obj_new.id_random
if order_cart_line_rec_li_curr := sql_select(table_name='v_order_cart_line', data=data, rm_id_random=True, as_list=True):
#log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
#log.debug(order_cart_line_rec_li_curr)
order_cart_line_obj_li_curr = []
for order_cart_line_rec in order_cart_line_rec_li_curr:
try:
order_cart_line_obj = Order_Cart_Line_Base(**order_cart_line_rec)
log.debug(order_cart_line_obj)
except ValidationError as e:
log.error(e.json())
order_cart_line_obj_li_curr.append(order_cart_line_obj)
#log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(order_cart_line_obj_li_curr)
else:
#log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(order_cart_line_rec_li_curr)
order_cart_line_obj_li_curr = []
# Loop through the line list that was sent and compare with what was pulled from the DB
# Only insert if a product ID does not match
# Only update if a product ID does match
for order_cart_line_obj_new in order_cart_obj_new.order_cart_line_list:
#log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
if not any(order_cart_line_obj_curr.product_id_random == order_cart_line_obj_new.product_id_random for order_cart_line_obj_curr in order_cart_line_obj_li_curr):
# Need to append to current list
log.info('Need to append to current list')
order_cart_line_obj_li_curr.append(order_cart_line_obj_new)
else:
# Need to update a current list item ... loop through to find
log.info('Need to update a current list item ... loop through to find')
for index, order_cart_line_obj_curr in enumerate(order_cart_line_obj_li_curr):
log.info(index)
if order_cart_line_obj_new.product_id_random == order_cart_line_obj_curr.product_id_random:
log.info(f'Match: {order_cart_line_obj_curr.product_id_random}')
order_cart_line_obj_new.id_random = order_cart_line_obj_curr.id_random
order_cart_line_obj_li_curr[index] = order_cart_line_obj_new
else:
log.info(f'Not a match: {order_cart_line_obj_curr.product_id_random}')
# Save merged current and new list to the new order cart object
order_cart_obj_new.order_cart_line_list = order_cart_line_obj_li_curr
log.debug(order_cart_obj_new)
# Final loop through to get the new order totals
# Calculate totals
order_cart_total_amount = 0
order_cart_total_quantity = 0
for order_cart_line_obj_curr in order_cart_line_obj_li_curr:
order_cart_total_amount += order_cart_line_obj_curr.quantity * order_cart_line_obj_curr.amount
order_cart_total_quantity += order_cart_line_obj_curr.quantity
order_cart_obj_new.total_amount = order_cart_total_amount
order_cart_obj_new.total_quantity = order_cart_total_quantity
log.debug(order_cart_obj_new.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'order_cart_id_random', 'order_cart_line_list', 'cfg', 'created_on', 'updated_on'}))
order_cart_obj_data = order_cart_obj_new.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'order_cart_id_random', 'order_cart_line_list', 'cfg', 'created_on', 'updated_on'})
# SQL INSERT or UPDATE the order_cart record
log.info('SQL INSERT or UPDATE the order_cart record')
if order_cart_obj_resp := sql_insert_or_update(sql=None, data=order_cart_obj_data, table_name='order_cart', rm_id_random=True, id_random_length=8): pass
else: return False
#log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(order_cart_obj_resp)
if isinstance(order_cart_obj_resp, bool) and order_cart_obj_resp:
if order_cart_id := order_cart_obj_new.id: pass
elif order_cart_id := order_cart_obj_new.id_random: pass
elif isinstance(order_cart_obj_resp, int):
order_cart_id = order_cart_obj_resp
else:
return False
# Loop through the order_cart_line list to SQL INSERT or UPDATE the records
log.info('Loop through the order_cart_line list to SQL INSERT or UPDATE the records')
for order_cart_line_obj in order_cart_obj_new.order_cart_line_list:
log.debug(f"--- {order_cart_line_obj}")
log.debug(order_cart_line_obj.dict(by_alias=False, exclude_defaults=False, exclude_unset=False, exclude={}))
order_cart_line_obj_data = order_cart_line_obj.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'product_type_id', 'product_type', 'product_name', 'product_description', 'product_unit_price', 'product_max_quantity', 'order_cart_line_id_random', 'created_on', 'updated_on'})
order_cart_line_obj_data['order_cart_id'] = order_cart_id
if order_cart_line_obj_resp := sql_insert_or_update(sql=None, data=order_cart_line_obj_data, table_name='order_cart_line', rm_id_random=True, id_random_length=8): pass
else: return False
log.debug(order_cart_line_obj_resp)
return order_cart_id
# ### END ### API Order Cart Model ### save_order_cart_obj() ###
# IS THIS STILL NEEDED?
# ### BEGIN ### API Order Cart Model ### get_order_cart_obj() ###
def old_get_order_cart_obj(order_cart_id=None, inc_order_cart_line_list=None, inc_order_cfg=None):
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if order_cart_id := redis_lookup_id_random(record_id_random=order_cart_id, table_name='order_cart'): pass
else:
return False
if order_cart_rec := sql_select(table_name='v_order_cart', record_id=order_cart_id):
#log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(order_cart_rec)
if inc_order_cart_line_list:
order_cart_line_data = {}
order_cart_line_data['order_cart_id'] = order_cart_id
if order_cart_line_rec_li := sql_select(table_name='v_order_cart_line', data=order_cart_line_data, as_list=True):
order_cart_rec['order_cart_line_list'] = order_cart_line_rec_li
if inc_order_cfg:
if order_cfg_rec := sql_select(table_name='v_account_cfg', field_name='account_id', field_value=order_cart_rec.get('account_id', None)):
order_cart_rec['cfg'] = order_cfg_rec
log.debug(order_cart_rec)
else:
return False
try:
order_cart_obj = Order_Cart_Base(**order_cart_rec)
log.debug(order_cart_obj)
except ValidationError as e:
log.error(e.json())
return order_cart_obj
# ### END ### API Order Cart Model ### get_order_cart_obj() ###

View File

@@ -0,0 +1,46 @@
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_select
from app.lib_general import log, logging
from app.models.order_cfg_models import Order_Cfg_Base
# ### BEGIN ### API Order Cfg Methods ### load_order_cfg_obj() ###
def load_order_cfg_obj(
account_id: int|str,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = True,
) -> Order_Cfg_Base|dict|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if account_id := redis_lookup_id_random(record_id_random=account_id, table_name='account'): pass
else: return False
if order_cfg_rec := sql_select(
table_name = 'v_account_cfg',
field_name = 'account_id',
field_value = account_id,
): pass
else: return False
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(order_cfg_rec)
try:
order_cfg_obj = Order_Cfg_Base(**order_cfg_rec)
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(order_cfg_obj)
except ValidationError as e:
log.error(e.json())
if model_as_dict:
return order_cfg_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return order_cfg_obj
# ### END ### API Order Cfg Methods ### load_order_cfg_obj() ###

View File

@@ -0,0 +1,512 @@
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_delete, sql_enable_part, sql_insert, sql_limit_offset_part, sql_select, sql_update
from app.lib_general import log, logging, logger_reset
from app.models.common_field_schema import default_num_bytes
# from app.models.order_line_models import Order_Line_Base, Order_Line_Full_Detail_Base
from app.models.order_line_models_v3 import Order_Line_Base, Order_Line_Full_Detail_Base
# ### BEGIN ### API Order Line Methods ### create_order_obj_line() ###
# Updated 2022-01-18
def create_order_obj_line(
order_id: int,
order_line_dict_obj: Order_Line_Base,
) -> int|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
# ### SECTION ### Secondary data validation
log.info('Create dictionary or Pydantic object')
log.debug(type(order_line_dict_obj))
if isinstance(order_line_dict_obj, dict):
order_line_dict = order_line_dict_obj
try:
order_obj = Order_Line_Base(**order_line_dict)
except ValidationError as e:
log.error(e.json())
return False
else:
order_obj = order_line_dict_obj
# order_obj.order_id = order_id
order_line_dict = order_obj.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'product', 'remove_line', 'created_on', 'updated_on'})
log.debug(order_obj)
# ### SECTION ### Process data
order_obj.order_id = order_id # Is this needed?
order_line_dict['order_id'] = order_id
if order_line_dict_in_result := sql_insert(
data = order_line_dict,
table_name = 'order_line',
rm_id_random = True,
id_random_length = default_num_bytes
): pass
else:
log.warning(f'Order Line not created.')
log.debug(order_line_dict_in_result)
return False
log.debug(order_line_dict_in_result)
order_line_id = order_line_dict_in_result
log.info(f'Returning the Order Line ID: {order_line_id}')
return order_line_id
# ### END ### API Order Methods ### create_order_obj_line() ###
# # ### BEGIN ### API Order Line Methods ### create_order_obj_line() ###
# @logger_reset
# def create_order_obj_line(order_line_dict_obj:Order_Line_Base):
# log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
# log.debug(locals())
# if not order_line_dict_obj:
# return False
# order_line_obj_data = order_line_dict_obj.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'user', 'created_on', 'updated_on'})
# if order_line_obj_in_result := sql_insert(data=order_line_obj_data, table_name='order_line', rm_id_random=True, id_random_length=8): pass
# else:
# return False
# log.debug(order_line_obj_in_result)
# order_line_id = order_line_obj_in_result
# log.debug(f'Returning the new order_line_id: {order_line_id}')
# return order_line_id
# # ### END ### API Order Line Methods ### create_order_obj_line() ###
# ### BEGIN ### API Order Line Methods ### update_order_obj_line() ###
# Updated 2022-01-18
def update_order_obj_line(
order_line_id: int,
order_line_dict_obj: Order_Line_Base,
) -> int|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
# ### SECTION ### Secondary data validation
if order_line_id := redis_lookup_id_random(record_id_random=order_line_id, table_name='order_line'): pass
else: return False
log.info('Create dictionary or Pydantic object')
log.debug(type(order_line_dict_obj))
if isinstance(order_line_dict_obj, dict):
order_line_dict = order_line_dict_obj
try:
order_line_obj = Order_Line_Base(**order_line_dict)
except ValidationError as e:
log.error(e.json())
return False
else:
order_line_obj = order_line_dict_obj
# order_line_obj.order_line_id = order_line_id
order_line_dict = order_line_obj.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'product', 'remove_line', 'updated_on', 'updated_on'})
log.debug(order_line_obj)
# ### SECTION ### Process data
order_line_obj.id = order_line_id # Is this needed?
order_line_dict['id'] = order_line_id
if order_line_dict_up_result := sql_update(
data = order_line_dict,
table_name = 'order_line',
rm_id_random = True,
): pass
else:
log.warning(f'Order Line not updated.')
log.debug(order_line_dict_up_result)
return False
log.debug(order_line_dict_up_result)
return True
# ### END ### API Order Methods ### update_order_obj_line() ###
# # ### BEGIN ### API Order Line Methods ### update_order_obj_line() ###
# @logger_reset
# def update_order_obj_line(
# order_line_id: int|str, # Ideally the int ID should be passed. This allows for updating of the id_random value.
# order_obj_line_obj_up: Order_Line_Base,
# create_sub_obj: bool = False,
# ) -> bool:
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
# log.debug(locals())
# if order_line_id := redis_lookup_id_random(record_id_random=order_line_id, table_name='order_line'): pass
# else: return False
# order_obj_line_obj_up.id = order_line_id
# log.debug(order_obj_line_obj_up)
# # log.debug(order_obj_line_obj_up.dict(by_alias=True, exclude_unset=True))
# log.debug(order_obj_line_obj_up.dict(by_alias=False, exclude_unset=True))
# # log.debug(order_obj_line_obj_up.dict(by_alias=False, exclude_unset=False))
# #order_line_dict_up = order_obj_line_obj_up.dict(by_alias=False, exclude_unset=True)
# # if order_obj_line_obj_up.person_id and order_obj_line_obj_up.person:
# # person_id = order_obj_line_obj_up.person_id
# # person_obj_up = order_obj_line_obj_up.person
# # log.debug(person_id)
# # log.debug(person_obj_up)
# # if person_obj_up_result := update_person_obj(
# # person_id = person_id,
# # person_obj_up = person_obj_up,
# # create_sub_obj = create_sub_obj,
# # ):
# # log.debug(person_obj_up_result)
# # else:
# # log.debug(person_obj_up_result)
# # return False
# # if order_obj_line_obj_up.user_id and order_obj_line_obj_up.user:
# # user_id = order_obj_line_obj_up.user_id
# # user_obj_up = order_obj_line_obj_up.user
# # log.debug(user_id)
# # log.debug(user_obj_up)
# # if user_obj_up_result := update_user_obj(
# # user_id = user_id,
# # user_dict_obj = user_obj_up,
# # create_sub_obj = create_sub_obj,
# # ):
# # log.debug(user_obj_up_result)
# # else:
# # log.debug(user_obj_up_result)
# # return False
# order_line_dict_up = order_obj_line_obj_up.dict(by_alias=False, exclude_unset=True, exclude={'user'})
# log.debug(order_line_dict_up)
# if order_obj_line_obj_up_result := sql_update(data=order_line_dict_up, table_name='order_line', rm_id_random=True):
# log.debug(order_obj_line_obj_up_result)
# return True
# else:
# log.debug(order_obj_line_obj_up_result)
# return False
# # ### END ### API Order Line Methods ### update_order_obj_line() ###
# ### BEGIN ### API Order Line Methods ### load_order_obj_line() ###
# Updated 2021-11-19
@logger_reset
def load_order_obj_line(
order_line_id: int|str,
# limit: int = 500,
# offset: int = 0,
by_alias: bool = True,
exclude_unset: bool = True, # NOTE: Normally this is True
model_as_dict: bool = False, # NOTE: Normally this is False
) -> Order_Line_Base|dict|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if order_line_id := redis_lookup_id_random(record_id_random=order_line_id, table_name='order_line'): pass
else: return False
if order_line_rec := sql_select(table_name='v_order_line', record_id=order_line_id): pass
else: return False
log.debug(order_line_rec)
try:
order_line_obj = Order_Line_Base(**order_line_rec)
except ValidationError as e:
log.error(e.json())
return False
log.debug(order_line_obj)
if model_as_dict:
return order_line_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return order_line_obj
# ### END ### API Order Line Methods ### load_order_obj_line() ###
# ### BEGIN ### API Order Line Methods ### load_order_obj_line_full_detail() ###
# Updated 2021-11-22
@logger_reset
def load_order_obj_line_full_detail(
order_line_rec: dict,
# limit: int = 500,
# offset: int = 0,
by_alias: bool = True,
exclude_unset: bool = True, # NOTE: Normally this is True
model_as_dict: bool = False, # NOTE: Normally this is False
) -> Order_Line_Full_Detail_Base|dict|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
try:
order_line_obj_full_detail = Order_Line_Full_Detail_Base(**order_line_rec)
log.debug(order_line_obj_full_detail)
except ValidationError as e:
log.error(e.json())
return False
if model_as_dict:
return order_line_obj_full_detail.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return order_line_obj_full_detail
# ### END ### API Order Line Methods ### load_order_obj_line_full_detail() ###
# ### BEGIN ### API Order Line Methods ### get_order_line_rec_list() ###
@logger_reset
def get_order_line_rec_list(
for_obj_type: str,
for_obj_id: str,
from_datetime: datetime.datetime = None, # For the order, not order_line
to_datetime: datetime.datetime = None, # For the order, not order_line
product_for_type: str = 'all', # all, cont_edu_cert, event, fundraising, membership, etc
status: str = 'all', # started, in progress, complete, all
full_detail: bool = False,
# enabled: str = 'enabled', # enabled, disabled, all
limit: int = 500,
offset: int = 0,
) -> list|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if for_obj_id := redis_lookup_id_random(record_id_random=for_obj_id, table_name=for_obj_type): pass
else: return False
data = {}
data[f'{for_obj_type}_id'] = for_obj_id
# data['for_obj_type'] = for_obj_type
sql_obj_type_id = f'`order_line`.{for_obj_type}_id = :{for_obj_type}_id'
allowed_prod_type_li = ['cont_edu_cert', 'event', 'fundraising', 'membership', 'other'] # TEMPORARY list...
sql_product_for_type = ''
if product_for_type in allowed_prod_type_li:
if product_for_type == 'closed' or product_for_type == 'complete':
data['product_for_type'] = ['closed', 'complete']
sql_product_for_type = f'AND `order_line`.product_for_type IN :product_for_type'
elif product_for_type == 'locked' or product_for_type == 'in progress':
data['product_for_type'] = ['locked', 'in progress']
sql_product_for_type = f'AND `order_line`.product_for_type IN :product_for_type'
else:
data['product_for_type'] = product_for_type
sql_product_for_type = f'AND `order_line`.product_for_type = :product_for_type'
elif product_for_type == 'all':
sql_product_for_type = f'AND (`order_line`.product_for_type IS NULL OR `order_line`.product_for_type IS NOT NULL)'
else:
log.warning('The product_for_type value passed is not allowed. Returning None')
return False
allowed_status_li = ['open', 'locked', 'in progress', 'reopened', 'closed', 'complete', 'canceled', 'other'] # TEMPORARY list...
sql_status = ''
if status in allowed_status_li:
if status == 'closed' or status == 'complete':
data['status'] = ['closed', 'complete']
sql_status = f'AND `order_line`.order_status IN :status'
elif status == 'locked' or status == 'in progress':
data['status'] = ['locked', 'in progress']
sql_status = f'AND `order_line`.order_status IN :status'
else:
data['status'] = status
sql_status = f'AND `order_line`.order_status = :status'
elif status == 'all':
sql_status = f'AND `order_line`.order_status IS NOT NULL'
else:
log.warning('The status value passed is not allowed. Returning None')
return False
if from_datetime and to_datetime:
data['from_datetime'] = from_datetime
data['to_datetime'] = to_datetime
# sql_from_to_datetime = f'AND `order_line`.order_created_on >= :from_datetime AND `order_line`.order_created_on <= :to_datetime'
sql_from_to_datetime = f'AND `order_line`.order_updated_on >= :from_datetime AND `order_line`.order_updated_on <= :to_datetime'
elif from_datetime:
data['from_datetime'] = from_datetime
# sql_from_to_datetime = f'AND `order_line`.order_created_on >= :from_datetime'
sql_from_to_datetime = f'AND `order_line`.order_updated_on >= :from_datetime'
elif to_datetime:
data['to_datetime'] = to_datetime
# sql_from_to_datetime = f'AND `order_line`.order_created_on <= :to_datetime'
sql_from_to_datetime = f'AND `order_line`.order_updated_on <= :to_datetime'
else:
sql_from_to_datetime = ''
# sql_enabled, data['enable'] = sql_enable_part(table_name='order', enabled=enabled) # Reasonably safe return str and bool
sql_limit = sql_limit_offset_part(limit=limit, offset=offset) # Reasonably safe return str
log.debug(data)
if not full_detail:
sql = f"""
SELECT `order_line`.id AS 'order_line_id', `order_line`.id_random AS 'order_line_id_random'
FROM `v_order_line` AS `order_line`
WHERE
{sql_obj_type_id}
{sql_product_for_type}
{sql_status}
{sql_from_to_datetime}
ORDER BY order_line.name, `order_line`.created_on DESC, `order_line`.updated_on DESC
{sql_limit};
"""
else:
sql = f"""
SELECT *
FROM `v_order_line_full_detail` AS `order_line`
WHERE
{sql_obj_type_id}
{sql_product_for_type}
{sql_status}
{sql_from_to_datetime}
ORDER BY `order_line`.created_on DESC, `order_line`.updated_on DESC
{sql_limit};
"""
log.debug(sql)
if order_line_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
order_line_rec_li = order_line_rec_li_result
else: # [] or False
order_line_rec_li = order_line_rec_li_result
log.debug(order_line_rec_li_result)
return order_line_rec_li
# ### END ### API Order Line Methods ### get_order_line_rec_list() ###
# ### BEGIN ### API Order Line Methods ### check_order_obj_line_list() ###
@logger_reset
def check_order_obj_line_list(
order_id: int|str,
product_id: int|str = None,
# product_for_id: int|str = 'all',
product_for_type: str = 'all', # all, cont_edu_cert, event, fundraising, membership, etc
for_person_id: int|str = None,
limit: int = 50,
offset: int = 0,
) -> list|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if order_id := redis_lookup_id_random(record_id_random=order_id, table_name='order'): pass
else: return False
if product_id := redis_lookup_id_random(record_id_random=product_id, table_name='product'): pass
elif product_id is None: pass
else: return False
if for_person_id := redis_lookup_id_random(record_id_random=for_person_id, table_name='person'): pass
elif for_person_id is None: pass
else: return False
data = {}
data['order_id'] = order_id
sql_order_id = f'`order_line`.order_id = :order_id'
if product_id:
data['product_id'] = product_id
sql_product_id = f'AND `order_line`.product_id = :product_id'
allowed_prod_type_li = ['cont_edu_cert', 'event', 'fundraising', 'membership', 'other'] # TEMPORARY list...
sql_product_for_type = ''
if product_for_type in allowed_prod_type_li:
if product_for_type == 'closed' or product_for_type == 'complete':
data['product_for_type'] = ['closed', 'complete']
sql_product_for_type = f'AND `order_line`.product_for_type IN :prod_type'
elif product_for_type == 'locked' or product_for_type == 'in progress':
data['product_for_type'] = ['locked', 'in progress']
sql_product_for_type = f'AND `order_line`.product_for_type IN :prod_type'
else:
data['product_for_type'] = prod_type
sql_product_for_type = f'AND `order_line`.product_for_type = :prod_type'
elif product_for_type == 'all':
sql_product_for_type = f'AND (`order_line`.product_for_type IS NULL OR `order_line`.product_for_type IS NOT NULL)'
else:
log.warning('The product_for_type value passed is not allowed. Returning None')
return False
sql_for_person_id = ''
if for_person_id:
data['for_person_id'] = for_person_id
sql_for_person_id = f'AND `order_line`.for_person_id = :for_person_id'
# sql_enabled, data['enable'] = sql_enable_part(table_name='order', enabled=enabled) # Reasonably safe return str and bool
sql_limit = sql_limit_offset_part(limit=limit, offset=offset) # Reasonably safe return str
log.debug(data)
sql = f"""
SELECT `order_line`.id AS 'order_line_id', `order_line`.id_random AS 'order_line_id_random'
FROM `order_line` AS `order_line`
WHERE
{sql_order_id}
{sql_product_id}
{sql_product_for_type}
{sql_for_person_id}
ORDER BY order_line.name, `order_line`.created_on DESC, `order_line`.updated_on DESC
{sql_limit};
"""
log.debug(sql)
if order_line_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
order_line_rec_li = order_line_rec_li_result
else: # [] or False
order_line_rec_li = order_line_rec_li_result
log.debug(order_line_rec_li_result)
return order_line_rec_li
# ### END ### API Order Line Methods ### check_order_obj_line_list() ###
# ### BEGIN ### API Order Line Methods ### remove_order_obj_line() ###
# Updated 2022-01-19
def remove_order_obj_line(
order_id: int,
order_line_id: int,
product_id: int,
) -> bool|None:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
# ### SECTION ### Secondary data validation
if order_id := redis_lookup_id_random(record_id_random=order_id, table_name='order'): pass
elif order_id is None: pass
else: return False
if order_line_id := redis_lookup_id_random(record_id_random=order_line_id, table_name='order_line'): pass
elif order_line_id is None: pass
else: return False
if product_id := redis_lookup_id_random(record_id_random=product_id, table_name='product'): pass
elif product_id is None: pass
else: return False
if order_line_id:
log.info(f'Deleting Order Line ID: {order_line_id}')
if order_line_del_result := sql_delete(
table_name = 'order_line',
record_id = order_line_id
):
log.debug(order_line_del_result)
return True
else: return False
elif order_id and product_id:
log.info(f'Deleting Order Line with Order ID: {order_id} and Product ID {product_id}')
data = { 'order_id': order_id, 'product_id': product_id}
if order_line_del_result := sql_delete(
table_name = 'order_line',
data = data,
):
log.debug(order_line_del_result)
return True
else: return False
# ### END ### API Order Line Methods ### remove_order_obj_line() ###

View File

@@ -0,0 +1,623 @@
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_delete, sql_enable_part, sql_insert, sql_insert_or_update, sql_limit_offset_part, sql_select, sql_update
from app.lib_general import log, logging, logger_reset
from app.methods.order_cfg_methods import load_order_cfg_obj
from app.methods.order_line_methods import check_order_obj_line_list, create_order_obj_line, get_order_line_rec_list, load_order_obj_line, remove_order_obj_line, update_order_obj_line
# from app.methods.person_methods import load_person_obj
from app.models.common_field_schema import default_num_bytes
# from app.models.order_models import Order_Base
# from app.models.order_line_models import Order_Line_Base, Order_Line_DB_Base # This should go away later.
from app.models.order_models_v3 import Order_Base
from app.models.order_line_models_v3 import Order_Line_Base, Order_Line_DB_Base # This should go away later.
# from app.models.person_models import Person_Base
# ### BEGIN ### API Order Methods ### create_order_obj() ###
# Updated 2022-01-18
def create_order_obj(
account_id: int,
order_dict_obj: Order_Base,
person_id: int|None = None,
) -> int|bool:
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
# ### SECTION ### Secondary data validation
log.info('Create dictionary or Pydantic object')
log.debug(type(order_dict_obj))
if isinstance(order_dict_obj, dict):
order_dict = order_dict_obj
try:
order_obj = Order_Base(**order_dict)
except ValidationError as e:
log.error(e.json())
return False
else:
order_obj = order_dict_obj
# order_obj.account_id = account_id
order_dict = order_obj.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'cfg', 'order_line_list', 'person', 'user', 'created_on', 'updated_on'})
log.debug(order_obj)
# ### SECTION ### Process data
# Look for an account_id in the order_obj
# if account_id: pass
# elif account_id := order_obj.account_id: pass
order_obj.account_id = account_id # Is this needed?
order_dict['account_id'] = account_id
# Look for a person_id in the contact_obj
if person_id:
order_obj.person_id = person_id # Is this needed?
order_dict['person_id'] = person_id
elif person_id := order_obj.person.id: pass
if order_dict_in_result := sql_insert(
data = order_dict,
table_name = 'order',
rm_id_random = True,
id_random_length = default_num_bytes
): pass
else:
log.warning(f'Order not created.')
log.debug(order_dict_in_result)
return False
log.debug(order_dict_in_result)
order_id = order_dict_in_result
if order_obj.order_line_list:
log.info('Looping through Order Line list')
for order_line in order_obj.order_line_list:
# NOTE: Should there be a check here using check_order_obj_line_list()???
# In theory there should not already be any order_line records associated with the new order_id.
order_line_id = order_line.id
if order_line_id:
log.info('Updating Order Line')
if update_order_obj_result := update_order_obj_line(
order_line_id = order_line_id,
order_line_dict_obj = order_line,
): pass
else: return False
else:
log.info('Creating Order Line')
if create_order_obj_line_result := create_order_obj_line(
order_id = order_id,
order_line_dict_obj = order_line,
): pass
else: pass
log.info(f'Returning the Order ID: {order_id}')
return order_id
# ### END ### API Order Methods ### create_order_obj() ###
# ### BEGIN ### API Order Methods ### update_order_obj() ###
# Updated 2022-01-18
def update_order_obj(
order_id: int,
order_dict_obj: Order_Base,
# person_id: int|None = None,
) -> int|bool:
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
# ### SECTION ### Secondary data validation
log.info('Create dictionary or Pydantic object')
log.debug(type(order_dict_obj))
if isinstance(order_dict_obj, dict):
order_dict = order_dict_obj
try:
order_obj = Order_Base(**order_dict)
except ValidationError as e:
log.error(e.json())
return False
else:
order_obj = order_dict_obj
# order_obj.account_id = account_id
order_dict = order_obj.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'cfg', 'order_line_list', 'person', 'user', 'created_on', 'updated_on'})
log.debug(order_obj)
# ### SECTION ### Process data
order_obj.id = order_id # Is this needed?
order_dict['id'] = order_id
# # Look for a person_id in the order_obj
# if person_id:
# order_obj.person_id = person_id # Is this needed?
# order_dict['person_id'] = person_id
# elif person_id := order_obj.person.id: pass
if order_dict_up_result := sql_update(
data = order_dict,
table_name = 'order',
rm_id_random = True,
): pass
elif order_dict_up_result is None: pass # More than likely no order specific data was passed. There might be order lines though.
else:
log.warning(f'Person not updated.')
log.debug(order_dict_up_result)
return False
log.debug(order_dict_up_result)
if order_obj.order_line_list:
log.info('Looping through Order Line list')
for order_line in order_obj.order_line_list:
order_line_id = order_line.id
product_id = order_line.product_id
if order_line.remove_line:
remove_order_obj_line(
order_id = order_id,
order_line_id = order_line_id,
product_id = product_id,
)
continue # Do not need to check for anything else on this loop
order_line_id = order_line.id
product_id = order_line.product_id
if for_person_id := order_line.for_person_id: pass
log.info(f'Checking for matching order lines. Order ID: {order_id}; Product ID: {product_id}; For Person ID: {for_person_id}')
order_line_id_found = None
if check_order_obj_line_list_result := check_order_obj_line_list(
order_id = order_id,
product_id = product_id,
for_person_id = for_person_id,
):
for order_line_rec in check_order_obj_line_list_result:
order_line_id_found = order_line_rec.get('order_line_id')
if order_line_id == order_line_id_found: pass
elif order_line_id := order_line_id_found: pass
if order_line_id:
log.info('Updating Order Line')
if update_order_obj_result := update_order_obj_line(
order_line_id = order_line_id,
order_line_dict_obj = order_line,
): pass
else: return False
else:
log.info('Creating Order Line')
if create_order_obj_line_result := create_order_obj_line(
order_id = order_id,
order_line_dict_obj = order_line,
): pass
else: pass
return True
# ### END ### API Order Methods ### update_order_obj() ###
# ### BEGIN ### API Order Methods ### load_order_obj() ###
# Updated 2021-11-19
# @logger_reset
def load_order_obj(
order_id: int|str,
inc_address: bool = False,
inc_contact: bool = False,
inc_order_cfg: bool = False,
inc_order_line_list: bool = False,
inc_person: bool = False,
inc_user: bool = False,
enabled: str = 'enabled', # enabled, disabled, all
limit: int = 500,
offset: int = 0,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
) -> None|bool|dict|list:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if order_id := redis_lookup_id_random(record_id_random=order_id, table_name='order'): pass
else: return False # None, false bool
if order_rec := sql_select(table_name='v_order', record_id=order_id): pass
else: return order_rec # None, empty dict, empty list, false bool
log.debug(order_rec)
try:
order_obj = Order_Base(**order_rec)
except ValidationError as e:
log.error(e.json())
return False
log.debug(order_obj)
# Updated 2022-01-18
if inc_order_cfg:
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
if order_cfg_result := load_order_cfg_obj(
account_id = order_rec.get('account_id'),
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = True,
):
order_obj.cfg = order_cfg_result
else: order_obj.cfg = {} # None
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(order_cfg_result)
# Updated 2021-06-18
if inc_order_line_list:
if order_line_rec_list_result := get_order_line_rec_list(
for_obj_type = 'order',
for_obj_id = order_id,
# product_for_type = 'all',
# status = 'all',
limit = limit,
):
order_line_result_list = []
for order_line_rec in order_line_rec_list_result:
order_line_result_list.append(
load_order_obj_line(
order_line_id = order_line_rec.get('order_line_id'),
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
)
)
order_obj.order_line_list = order_line_result_list
else: order_obj.order_line_list = [] # None
# Updated 2022-01-18
if inc_person:
from app.methods.person_methods import load_person_obj
if person_result := load_person_obj(
person_id = order_rec.get('person_id'),
inc_address = inc_address,
inc_contact = inc_contact,
inc_user = inc_user,
enabled = enabled,
limit = limit,
offset = offset,
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
):
order_obj.person = person_result
else: order_obj.person = {} # None
pass
# Updated 2021-06-22
# NOTE: Phasing out! Use *inc_user* under load_person_obj() instead.
# if inc_user:
# log.warning(f'This is being deprecated? load_order_obj() inc_user')
# from app.methods.user_methods import load_user_obj
# if user_result := load_user_obj(
# user_id = order_rec.get('user_id', None),
# limit = limit,
# by_alias = by_alias,
# exclude_unset = exclude_unset,
# model_as_dict = model_as_dict,
# enabled = enabled,
# ):
# order_obj.user = user_result
# else: order_obj.user = None
# pass
if model_as_dict:
return order_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return order_obj
# ### END ### API Order Methods ### load_order_obj() ###
# ### BEGIN ### API Order Methods ### get_order_obj_cart_for_person_id_v3() ###
# Updated 2022-01-21
@logger_reset
def get_order_id_cart_for_person_id_v3(
person_id: int|str,
enabled: str = 'enabled', # enabled, disabled, all
limit: int = 5,
offset: int = 0,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
) -> None|bool|dict|list:
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if person_id := redis_lookup_id_random(record_id_random=person_id, table_name='person'): pass
else: return False # None, false bool
data = {}
data['person_id'] = person_id
sql_enabled, data['enable'] = sql_enable_part(table_name='order', enabled=enabled) # Reasonably safe return str and bool
sql_limit = sql_limit_offset_part(limit=limit, offset=offset) # Reasonably safe return str
sql = f"""
SELECT `order`.id AS 'order_id', `order`.id_random AS 'order_id_random'
FROM `order` AS `order`
WHERE
`order`.person_id = :person_id
AND `order`.status IN ('open', 'locked')
{sql_enabled}
ORDER BY `order`.created_on DESC, `order`.updated_on DESC
{sql_limit};
"""
log.debug(sql)
if order_rec_result := sql_select(data=data, sql=sql):
log.debug(order_rec_result)
if isinstance(order_rec_result, dict):
order_id_cart = order_rec_result.get('order_id')
log.info(f'Got Order ID {order_id_cart} cart for Person ID {person_id}')
return order_id_cart
elif isinstance(order_rec_result, list):
log.warning(f'Got multiple Orders for a cart for Person ID {person_id}. This should not happen.')
return False
else: # None or [] or False
log.debug(order_rec_result)
return order_rec_result
# ### END ### API Order Methods ### get_order_obj_cart_for_person_id_v3() ###
# ### BEGIN ### API Order Methods ### save_order_obj() ###
# @logger_reset
def save_order_obj(order_obj_new:Order_Base, repl_order_line_li: bool=False):
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
log.debug(order_obj_new.dict(by_alias=False, exclude_defaults=False, exclude_unset=True))
order_line_obj_li_curr = [] # Initialize to store order_line list
if order_obj_new.id_random:
log.info(f'An order.id {order_obj_new.id} or order.id_random {order_obj_new.id_random} was included. We can update an existing order.')
log.info(f'Get the current order_line list to compare with what was sent...')
data = {}
data['order_id_random'] = order_obj_new.id_random
if order_line_rec_li_curr := sql_select(table_name='v_order_line', data=data, rm_id_random=True, as_list=True):
#log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(order_line_rec_li_curr)
for order_line_rec in order_line_rec_li_curr:
try:
order_line_obj = Order_Line_Base(**order_line_rec)
log.debug(order_line_obj)
except ValidationError as e:
log.error(e.json())
order_line_obj_li_curr.append(order_line_obj)
else:
log.info(f'No order_line records were found')
elif order_obj_new.account_id_random and order_obj_new.person_id_random:
log.info(f'An account.id_random {order_obj_new.account_id_random} was passed. And a person.id_random {order_obj_new.person_id_random} was passed. We can create a new order.')
# Because there was not an order ID, assume there are no order lines yet. So no look up.
else:
log.info('Either an order ID is required to update an order or an account ID along with a person ID is required to create an order.')
return False
#log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(order_line_obj_li_curr)
if repl_order_line_li: # This will remove any order line list items not sent with the new order information.
log.info('Removing any order line list items not sent with the new order information...')
for index, order_line_obj_curr in enumerate(order_line_obj_li_curr):
log.info(f'Current: order line ID={order_line_obj_curr.id_random} and product ID= {order_line_obj_curr.product_id_random}')
matched_product_id = False
for order_line_obj_new in order_obj_new.order_line_li:
log.debug(f'Checking new: product ID={order_line_obj_new.product_id_random}')
if order_line_obj_curr.product_id_random == order_line_obj_new.product_id_random:
matched_product_id = True
log.debug(f'Matched: product ID={order_line_obj_new.product_id_random}')
break
else:
log.debug(f'No match: product ID={order_line_obj_new.product_id_random}')
if not matched_product_id: # Was not found in the new order line list sent
log.info(f'Current order line product ID did not match any of the new list. DELETE order line ID {order_line_obj_curr.id_random} with product ID {order_line_obj_curr.product_id_random}')
if order_line_del_result := sql_delete(table_name='order_line', record_id_random=order_line_obj_curr.id_random):
log.info(f'Deleted record and now pop the current list item {index}...')
order_line_obj_li_curr.pop(index)
else:
log.info(f'Current order line product ID matched. Keeping order line ID {order_line_obj_curr.id_random} with product ID {order_line_obj_curr.product_id_random}')
# NOTE: That this current order line item will be updated below.
log.debug(order_line_obj_li_curr)
#log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.info('Loop through the line list that was sent and compare with what was pulled from the DB')
# Loop through the new line list that was sent and compare with the current line list that was pulled from the DB
# Only insert if a product ID does not match
# Only update if a product ID does match
for order_line_obj_new in order_obj_new.order_line_li:
log.info(f'New: order line ID={order_line_obj_new.id_random} and product ID= {order_line_obj_new.product_id_random}')
matched_product_id = False
for index, order_line_obj_curr in enumerate(order_line_obj_li_curr):
log.debug(f'Checking current: product ID={order_line_obj_curr.product_id_random}')
if order_line_obj_new.product_id_random == order_line_obj_curr.product_id_random:
matched_product_id = True
log.debug(f'Matched: product ID={order_line_obj_curr.product_id_random}')
log.info(f'Updating the current line item with the new line item.')
order_line_obj_new.id_random = order_line_obj_curr.id_random
order_line_obj_new.id = order_line_obj_curr.id
order_line_obj_li_curr[index] = order_line_obj_new
break
else:
log.debug(f'No match: product ID={order_line_obj_curr.product_id_random}')
if not matched_product_id: # Was not found in the current order line list that was pulled from the DB
log.info(f'New order line product ID did not match any of the current list. Append order line ID {order_line_obj_new.id_random} with product ID {order_line_obj_new.product_id_random}')
log.info('Append to current list...')
order_line_obj_li_curr.append(order_line_obj_new) # These will be inserted/updated below
# Save merged current and new list to the new order object
order_obj_new.order_line_li = order_line_obj_li_curr
log.debug(order_obj_new)
# Final loop through to get the new order totals
# Calculate totals
order_total_amount: int = 0
order_total_quantity: int = 0
for order_line_obj_new in order_obj_new.order_line_li:
order_total_amount += order_line_obj_new.quantity * order_line_obj_new.amount
order_total_quantity += order_line_obj_new.quantity
order_obj_new.total_bill = order_total_amount # "amount" is used by order_cart and Stripe
order_obj_new.total_quantity = order_total_quantity
# order_obj_new.balance = order_total_amount - order_obj_new.total_paid # No longer used 2022-03-16
log.debug(order_obj_new.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'order_line_li', 'cfg', 'created_on', 'updated_on'}))
order_obj_data = order_obj_new.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'order_line_li', 'cfg', 'created_on', 'updated_on'})
# SQL INSERT or UPDATE the order record
log.info('SQL INSERT or UPDATE the order record')
if order_obj_resp := sql_insert_or_update(data=order_obj_data, table_name='order', rm_id_random=True, id_random_length=8): pass
else: return False
#log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(order_obj_resp)
if isinstance(order_obj_resp, bool) and order_obj_resp:
if order_id := order_obj_new.id: pass
elif order_id := order_obj_new.id_random: pass
elif isinstance(order_obj_resp, int):
order_id = order_obj_resp
else:
return False
log.debug(f'Order ID={order_id}')
# Loop through the order_line list to SQL INSERT or UPDATE the records
log.info('Loop through the order_line list to SQL INSERT or UPDATE the records')
for order_line_obj_new in order_obj_new.order_line_li:
log.info(f"New order_line: order_line_id_random={order_line_obj_new.id_random}; product_id_random={order_line_obj_new.product_id_random}")
log.debug(order_line_obj_new.dict(by_alias=False, exclude_defaults=False, exclude_unset=False, exclude={'order_line_id_random', 'product_type_id', 'product_type', 'created_on', 'updated_on'}))
order_line_obj_data = order_line_obj_new.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'order_line_id_random', 'product_type_id', 'product_type', 'created_on', 'updated_on'})
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
try:
order_line_obj_db = Order_Line_DB_Base(**order_line_obj_new)
log.debug(order_line_obj_db)
except ValidationError as e:
log.error(e.json())
return False
order_line_obj_db_data = order_line_obj_db.dict(by_alias=False, exclude_defaults=False, exclude_unset=True)
log.debug(order_line_obj_db_data)
order_line_obj_data['order_id'] = order_id
if order_line_obj_resp := sql_insert_or_update(sql=None, data=order_line_obj_data, table_name='order_line', rm_id_random=True, id_random_length=8): pass
else: return False
log.debug(order_line_obj_resp)
return order_id
# ### END ### API Order Methods ### save_order_obj() ###
# ### BEGIN ### API Order Methods ### get_order_rec_list() ###
# Updated 2021-12-13
# @logger_reset
def get_order_rec_list(
for_obj_type: str,
for_obj_id: str,
from_datetime: datetime.datetime = None,
to_datetime: datetime.datetime = None,
# balance_gt: int = 0, # $0 to $99999
status: list|str = 'closed', # started, in progress, complete, all
# checkout_status: str = 'none', # none, canceled, waiting, success, failed, unknown
enabled: str = 'enabled', # enabled, disabled, all
limit: int = 500,
offset: int = 0,
) -> list|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
# if for_obj_id := redis_lookup_id_random(record_id_random=for_obj_id, table_name=for_obj_type): pass
# else: return False
data = {}
if for_obj_type == 'account' and for_obj_id:
data['account_id'] = redis_lookup_id_random(record_id_random=for_obj_id, table_name='account')
sql_obj_type_id = f'`order`.account_id = :account_id'
elif for_obj_type == 'person' and for_obj_id:
data['person_id'] = redis_lookup_id_random(record_id_random=for_obj_id, table_name='person')
sql_obj_type_id = f'`order`.person_id = :person_id'
else:
return False
# allowed_status_li = ['started', 'in progress', 'complete', 'all'] # OLD list
# allowed_status_li = ['open', 'locked', 'reopened', 'closed', 'canceled', 'other'] # NEW list
allowed_status_li = ['open', 'locked', 'in progress', 'reopened', 'closed', 'complete', 'canceled', 'other'] # TEMPORARY list...
sql_status = ''
if isinstance(status, list):
data['status'] = status
sql_status = f'AND `order`.status IN :status'
elif status in allowed_status_li:
if status == 'closed' or status == 'complete':
data['status'] = ['closed', 'complete']
sql_status = f'AND `order`.status IN :status'
elif status == 'locked' or status == 'in progress':
data['status'] = ['locked', 'in progress']
sql_status = f'AND `order`.status IN :status'
else:
data['status'] = status
sql_status = f'AND `order`.status = :status'
elif status == 'all':
sql_status = f'AND `order`.status IS NOT NULL'
else:
log.warning('The status value passed is not allowed. Returning None')
return False
if from_datetime and to_datetime:
data['from_datetime'] = from_datetime
data['to_datetime'] = to_datetime
sql_from_to_datetime = f'AND `order`.created_on >= :from_datetime AND `order`.created_on <= :to_datetime'
elif from_datetime:
data['from_datetime'] = from_datetime
sql_from_to_datetime = f'AND `order`.created_on >= :from_datetime'
elif to_datetime:
data['to_datetime'] = to_datetime
sql_from_to_datetime = f'AND `order`.created_on <= :to_datetime'
else:
sql_from_to_datetime = ''
sql_enabled, data['enable'] = sql_enable_part(table_name='order', enabled=enabled) # Reasonably safe return str and bool
sql_limit = sql_limit_offset_part(limit=limit, offset=offset) # Reasonably safe return str
log.debug(data)
sql = f"""
SELECT `order`.id AS 'order_id', `order`.id_random AS 'order_id_random'
FROM `order` AS `order`
WHERE
{sql_obj_type_id}
{sql_status}
{sql_from_to_datetime}
{sql_enabled}
ORDER BY `order`.created_on DESC, `order`.updated_on DESC
{sql_limit};
"""
log.debug(sql)
if order_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
log.info('Got a list result')
order_rec_li = order_rec_li_result
else: # [] or False
log.info('No results or something went wrong')
order_rec_li = order_rec_li_result
log.debug(order_rec_li_result)
return order_rec_li
# ### END ### API Order Methods ### get_order_rec_list() ###

View File

@@ -0,0 +1,250 @@
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.contact_methods import create_contact_obj, create_update_contact_obj, load_contact_obj, update_contact_obj
from app.models.common_field_schema import default_num_bytes
from app.models.organization_models import Organization_Base
# ### BEGIN ### API Organization Methods ### load_organization_obj() ###
# NOTE: This needs to be updated to the newer method template. Like address, contact, or person -STI 2021-06-10
def load_organization_obj(
organization_id: int|str,
limit: int = 1000,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
enabled: str = 'enabled', # enabled, disabled, all
inc_contact: bool=False,
inc_address: bool=False
) -> Organization_Base|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if organization_id := redis_lookup_id_random(record_id_random=organization_id, table_name='organization'): pass
else: return False
if organization_rec := sql_select(table_name='v_organization', record_id=organization_id): pass
else: return False
#log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(organization_rec)
try:
organization_obj = Organization_Base(**organization_rec)
log.debug(organization_obj)
except ValidationError as e:
log.error(e.json())
# Updated 2021-06-18
if inc_contact:
contact_id = organization_rec.get('contact_id', None)
log.debug(contact_id)
if contact_result := load_contact_obj(
contact_id = contact_id,
limit = limit,
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
enabled = enabled,
inc_address = inc_address,
):
organization_obj.contact = contact_result
else: organization_obj.contact = None
if model_as_dict:
return organization_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return organization_obj
# ### END ### API Organization Methods ### load_organization_obj() ###
# ### BEGIN ### API Organization Methods ### get_organization_rec_list() ###
def get_organization_rec_list(
for_obj_type: str,
for_obj_id: str,
limit: int = 1000,
enabled: str = 'enabled', # enabled, disabled, all
) -> list|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if for_obj_id := redis_lookup_id_random(record_id_random=for_obj_id, table_name=for_obj_type): pass
else: return False
data = {}
data[f'{for_obj_type}_id'] = for_obj_id
# data['for_obj_type'] = for_obj_type
sql_obj_type_id = f'`tbl`.{for_obj_type}_id = :{for_obj_type}_id'
# if enabled in ['enabled', 'disabled', 'all']:
# if enabled == 'enabled':
# data['enable'] = True
# sql_enabled = f'AND `tbl`.enable = :enable'
# elif enabled == 'disabled':
# data['enable'] = False
# sql_enabled = f'AND `tbl`.enable = :enable'
# elif enabled == 'all':
# sql_enabled = ''
sql_enabled = ''
if limit:
data['limit'] = limit
sql_limit = f'LIMIT :limit'
else:
sql_limit = ''
sql = f"""
SELECT `tbl`.id AS 'organization_id', `tbl`.id_random AS 'organization_id_random'
FROM `organization` AS `tbl`
WHERE
{sql_obj_type_id}
{sql_enabled}
ORDER BY `tbl`.created_on DESC, `tbl`.updated_on DESC
{sql_limit};
"""
if organization_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
organization_rec_li = organization_rec_li_result
else:
organization_rec_li = []
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(organization_rec_li_result)
return organization_rec_li
# ### END ### API Organization Methods ### get_organization_rec_list() ###
# ### BEGIN ### API Organization Methods ### update_organization_obj() ###
# NOTE: This will create an organization and then also create a linked contact if organization_obj.contact data is passed. The create_contact_obj will create a contact and then also create a linked address if organization_obj.contact.address data is passed.
# Reviewed and updated 2021-08-10
def update_organization_obj(
organization_id: int|str, # Ideally the int ID should be passed. This allows for updating of the id_random value.
organization_obj_up: Organization_Base,
create_sub_obj: bool = False,
) -> bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if organization_obj_up.contact_id and organization_obj_up.contact:
contact_id = organization_obj_up.contact_id
contact_obj_up = organization_obj_up.contact
log.debug(contact_id)
log.debug(contact_obj_up)
if contact_obj_up_result := update_contact_obj(
contact_id = contact_id,
contact_dict_obj = contact_obj_up,
create_sub_obj = create_sub_obj,
):
log.debug(contact_obj_up_result)
else:
log.debug(contact_obj_up_result)
return False
elif organization_obj_up.contact and not organization_obj_up.contact.id:
# NOTE: This will blindly create a new contact even if there was one associated but the organization.contact_id was not found.
contact_obj_in = organization_obj_up.contact
log.debug(contact_obj_in)
if contact_obj_in_result := create_contact_obj(
account_id = contact_obj_in.account_id,
contact_dict_obj=contact_obj_in,
):
# log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(contact_obj_in_result)
organization_obj_up.contact_id = contact_obj_in_result
else:
# log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(contact_obj_in_result)
return False
organization_dict_up = organization_obj_up.dict(by_alias=False, exclude_unset=True, exclude={'contact', 'person', 'user'})
log.debug(organization_dict_up)
if organization_obj_up_result := sql_update(data=organization_dict_up, table_name='organization', rm_id_random=True):
log.debug(organization_obj_up_result)
return True
else:
log.debug(organization_obj_up_result)
return False
# ### END ### API Organization Methods ### update_organization_obj() ###
# ### BEGIN ### API Organization Methods ### create_update_organization_obj() ###
def create_update_organization_obj(
organization_id: int|str|None, # Ideally the int ID should be passed. This allows for updating of the id_random value.
organization_obj: Organization_Base,
process_contact: bool = False,
) -> bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if organization_id:
if organization_id := redis_lookup_id_random(record_id_random=organization_id, table_name='organization'): pass
else: return False
organization_obj.id = organization_id
else:
# Insert record now and update later
organization_dict_in = organization_obj.dict(by_alias=False, exclude_unset=True, exclude={'contact', 'person', 'user'})
log.debug(organization_dict_in)
organization_in_result = sql_insert(
data = organization_dict_in,
table_name = 'organization',
rm_id_random = True,
id_random_length = default_num_bytes,
)
log.debug(organization_in_result)
if isinstance(organization_in_result, bool) and organization_in_result is True:
return organization_in_result
elif isinstance(organization_in_result, int):
organization_id = organization_in_result
organization_obj.id = organization_id
else:
return False # This should not happen.
# Process contact data
if process_contact and organization_obj.contact:
contact_obj = organization_obj.contact
contact_obj.for_type = 'organization'
contact_obj.for_id = organization_id
contact_id = organization_obj.contact_id_random
contact_result = create_update_contact_obj(
contact_id = contact_id,
contact_obj = contact_obj,
process_address = True, # Setting to True under the assumption that if there is contact information then there is probably an address.
)
log.debug(contact_result)
if isinstance(contact_result, bool) and contact_result is True:
pass # Do not need to update organization object.
elif isinstance(contact_result, bool) and contact_result is False:
pass # Do not need to update organization object.
elif isinstance(contact_result, int):
organization_obj.contact_id = contact_result
# pass # Do not need to update organization object.
else:
log.warning('Something may have gone wrong while trying to create or update a contact.')
# Process organization data
organization_dict_up = organization_obj.dict(by_alias=False, exclude_unset=True, exclude={'contact', 'person', 'user'})
log.debug(organization_dict_up)
# Update record
organization_up_result = sql_update(
data = organization_dict_up,
table_name = 'organization',
rm_id_random = True,
)
log.debug(organization_up_result)
if isinstance(organization_up_result, bool) and organization_up_result is True:
return organization_id
elif isinstance(organization_up_result, bool) and organization_up_result is False:
return False
elif isinstance(organization_up_result, int):
return organization_up_result
else:
return False
# ### END ### API Organization Methods ### create_update_organization_obj() ###

147
app/methods/page_methods.py Normal file
View File

@@ -0,0 +1,147 @@
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_insert_or_update, sql_select, sql_update
from app.lib_general import log, logging
# from app.methods.page_methods import load_page_obj, get_page_rec_list
from app.models.common_field_schema import default_num_bytes
from app.models.page_models import Page_Base
# ### BEGIN ### API Page Methods ### load_page_obj() ###
# Updated 2021-08-20
def load_page_obj(
page_id: int|str,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
enabled: str = 'enabled', # enabled, disabled, all
) -> Page_Base|dict|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if page_id := redis_lookup_id_random(record_id_random=page_id, table_name='page'): pass
else: return False
if page_rec := sql_select(table_name='page', record_id=page_id): pass
else: return False
log.debug(page_rec)
try:
page_obj = Page_Base(**page_rec)
log.debug(page_obj)
except ValidationError as e:
log.error(e.json())
return False
if model_as_dict:
return page_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return page_obj
# ### END ### API Page Methods ### load_page_obj() ###
# ### BEGIN ### API Page Methods ### get_page_rec_list() ###
# Partially updated 2021-08-20
def get_page_rec_list(
for_obj_type: str,
for_obj_id: str,
limit: int = 1000,
enabled: str = 'enabled', # enabled, disabled, all
) -> list|bool:
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if for_obj_id := redis_lookup_id_random(record_id_random=for_obj_id, table_name=for_obj_type): pass
else: return False
data = {}
data[f'{for_obj_type}_id'] = for_obj_id
# data['for_obj_type'] = for_obj_type
sql_obj_type_id = f'`page`.{for_obj_type}_id = :{for_obj_type}_id'
if enabled in ['enabled', 'disabled', 'all']:
if enabled == 'enabled':
data['enable'] = True
sql_enabled = f'AND `page`.enable = :enable'
elif enabled == 'disabled':
data['enable'] = False
sql_enabled = f'AND `page`.enable = :enable'
elif enabled == 'all':
sql_enabled = ''
if limit:
data['limit'] = limit
sql_limit = f'LIMIT :limit'
else:
sql_limit = ''
sql = f"""
SELECT `page`.id AS 'page_id', `page`.id_random AS 'page_id_random'
FROM `page` AS `page`
WHERE
{sql_obj_type_id}
{sql_enabled}
ORDER BY `page`.created_on DESC, `page`.updated_on DESC
{sql_limit};
"""
if page_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
page_rec_li = page_rec_li_result
else:
page_rec_li = []
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(page_rec_li_result)
return page_rec_li
# ### END ### API Page Methods ### get_page_rec_list() ###
# ### BEGIN ### API Page Methods ### get_page_rec_w_alias() ###
# Updated 2021-08-19
def get_page_rec_w_alias(
account_id: str,
alias: str,
enabled: str = 'enabled', # enabled, disabled, all
) -> dict|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if account_id := redis_lookup_id_random(record_id_random=account_id, table_name='account'): pass
else: return False
data = {}
data['account_id'] = account_id
data['alias'] = alias
if enabled in ['enabled', 'disabled', 'all']:
if enabled == 'enabled':
data['enable'] = True
sql_enabled = f'AND `page`.enable = :enable'
elif enabled == 'disabled':
data['enable'] = False
sql_enabled = f'AND `page`.enable = :enable'
elif enabled == 'all':
sql_enabled = ''
sql = f"""
SELECT `page`.id AS 'page_id', `page`.id_random AS 'page_id_random'
FROM `page` AS `page`
WHERE page.account_id = :account_id
AND page.alias = :alias
{sql_enabled}
LIMIT 1;
"""
if page_rec_result := sql_select(data=data, sql=sql):
page_rec = page_rec_result
else:
page_rec = None
log.debug(page_rec_result)
return page_rec
# ### END ### API Page Methods ### get_page_rec_w_alias() ###

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,230 @@
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, logger_reset
from app.methods.person_methods import load_person_obj
# from app.methods.user_methods import load_user_obj
from app.models.post_comment_models import Post_Comment_Base
# ### BEGIN ### API Post Comment Methods ### create_post_comment_obj() ###
@logger_reset
def create_post_comment_obj(post_comment_obj_new:Post_Comment_Base):
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if not post_comment_obj_new:
return False
post_comment_obj_data = post_comment_obj_new.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'user', 'created_on', 'updated_on'})
if post_comment_obj_in_result := sql_insert(data=post_comment_obj_data, table_name='post_comment', rm_id_random=True, id_random_length=8): pass
else:
return False
log.debug(post_comment_obj_in_result)
post_comment_id = post_comment_obj_in_result
log.debug(f'Returning the new post_comment_id: {post_comment_id}')
return post_comment_id
# ### END ### API Post Comment Methods ### create_post_comment_obj() ###
# ### BEGIN ### API Post Comment Methods ### load_post_comment_obj() ###
@logger_reset
def load_post_comment_obj(
post_comment_id: int|str,
limit: int = 1000,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
enabled: str = 'enabled', # enabled, disabled, all
inc_person: bool = False,
inc_user: bool = False,
) -> Post_Comment_Base|dict|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if post_comment_id := redis_lookup_id_random(record_id_random=post_comment_id, table_name='post_comment'): pass
else: return False
if post_comment_rec := sql_select(table_name='v_post_comment', record_id=post_comment_id):
#log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(post_comment_rec)
else:
return False
try:
post_comment_obj = Post_Comment_Base(**post_comment_rec)
log.debug(post_comment_obj)
except ValidationError as e:
log.error(e.json())
return False
# Updated 2021-06-22
if inc_person:
# from app.methods.person_methods import load_person_obj
if person_result := load_person_obj(
person_id = post_comment_rec.get('person_id', None),
limit = limit,
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
enabled = enabled,
):
post_comment_obj.person = person_result
else: post_comment_obj.person = None
# Updated 2021-06-18
if inc_user:
log.warning(f'This is being deprecated? load_post_comment_obj() inc_user')
# from app.methods.user_methods import load_user_obj
# user_result = load_user_obj(
# user_id = post_comment_rec.get('user_id', None),
# limit = limit,
# by_alias = by_alias,
# exclude_unset = exclude_unset,
# model_as_dict = model_as_dict,
# enabled = enabled,
# )
# post_comment_obj.user = user_result
pass
if model_as_dict:
return post_comment_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return post_comment_obj
# ### END ### API Post Comment Methods ### load_post_comment_obj() ###
# ### BEGIN ### API Post Comment Methods ### update_post_comment_obj() ###
@logger_reset
def update_post_comment_obj(
post_comment_id: int|str, # Ideally the int ID should be passed. This allows for updating of the id_random value.
post_comment_obj_up: Post_Comment_Base,
create_sub_obj: bool = False,
) -> bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if post_comment_id := redis_lookup_id_random(record_id_random=post_comment_id, table_name='post_comment'): pass
else: return False
post_comment_obj_up.id = post_comment_id
log.debug(post_comment_obj_up)
# log.debug(post_comment_obj_up.dict(by_alias=True, exclude_unset=True))
log.debug(post_comment_obj_up.dict(by_alias=False, exclude_unset=True))
# log.debug(post_comment_obj_up.dict(by_alias=False, exclude_unset=False))
#post_comment_dict_up = post_comment_obj_up.dict(by_alias=False, exclude_unset=True)
# if post_comment_obj_up.person_id and post_comment_obj_up.person:
# person_id = post_comment_obj_up.person_id
# person_obj_up = post_comment_obj_up.person
# log.debug(person_id)
# log.debug(person_obj_up)
# if person_obj_up_result := update_person_obj(
# person_id = person_id,
# person_obj_up = person_obj_up,
# create_sub_obj = create_sub_obj,
# ):
# log.debug(person_obj_up_result)
# else:
# log.debug(person_obj_up_result)
# return False
# if post_comment_obj_up.user_id and post_comment_obj_up.user:
# user_id = post_comment_obj_up.user_id
# user_obj_up = post_comment_obj_up.user
# log.debug(user_id)
# log.debug(user_obj_up)
# if user_obj_up_result := update_user_obj(
# user_id = user_id,
# user_dict_obj = user_obj_up,
# create_sub_obj = create_sub_obj,
# ):
# log.debug(user_obj_up_result)
# else:
# log.debug(user_obj_up_result)
# return False
post_comment_dict_up = post_comment_obj_up.dict(by_alias=False, exclude_unset=True, exclude={'user'})
log.debug(post_comment_dict_up)
if post_comment_obj_up_result := sql_update(data=post_comment_dict_up, table_name='post_comment', rm_id_random=True):
log.debug(post_comment_obj_up_result)
return True
else:
log.debug(post_comment_obj_up_result)
return False
# ### END ### API Post Comment Methods ### update_post_comment_obj() ###
# ### BEGIN ### API Post Comment Methods ### get_post_comment_rec_list() ###
# Updated 2021-12-13
@logger_reset
def get_post_comment_rec_list(
for_obj_type: str,
for_obj_id: str,
limit: int = 1000,
enabled: str = 'enabled', # enabled, disabled, all
) -> list|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if for_obj_id := redis_lookup_id_random(record_id_random=for_obj_id, table_name=for_obj_type): pass
else: return False
data = {}
data[f'{for_obj_type}_id'] = for_obj_id
# data['for_obj_type'] = for_obj_type
sql_obj_type_id = f'`post_comment`.{for_obj_type}_id = :{for_obj_type}_id'
# if enabled in ['enabled', 'disabled', 'all']:
# if enabled == 'enabled':
# data['enable'] = True
# sql_enabled = f'AND `post_comment`.enable = :enable'
# elif enabled == 'disabled':
# data['enable'] = False
# sql_enabled = f'AND `post_comment`.enable = :enable'
# elif enabled == 'all':
# sql_enabled = ''
sql_enabled = ''
if limit:
data['limit'] = limit
sql_limit = f'LIMIT :limit'
else:
sql_limit = ''
sql = f"""
SELECT `post_comment`.id AS 'post_comment_id', `post_comment`.id_random AS 'post_comment_id_random'
FROM `post_comment` AS `post_comment`
WHERE
{sql_obj_type_id}
{sql_enabled}
ORDER BY `post_comment`.created_on DESC, `post_comment`.updated_on DESC
{sql_limit};
"""
log.debug(sql)
if post_comment_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
log.info('Got a list result')
post_comment_rec_li = post_comment_rec_li_result
else: # [] or False
log.info('No results or something went wrong')
post_comment_rec_li = post_comment_rec_li_result
log.debug(post_comment_rec_li_result)
return post_comment_rec_li
# ### END ### API Post Comment Methods ### get_post_comment_rec_list() ###

263
app/methods/post_methods.py Normal file
View File

@@ -0,0 +1,263 @@
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, logger_reset
from app.methods.person_methods import load_person_obj
# from app.methods.post_comment_methods import create_post_comment_obj, update_post_comment_obj
from app.methods.post_comment_methods import get_post_comment_rec_list, load_post_comment_obj
# from app.methods.user_methods import load_user_obj
from app.models.post_models import Post_Base
# ### BEGIN ### API Post Methods ### create_post_obj() ###
@logger_reset
def create_post_obj(post_obj_new:Post_Base):
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if not post_obj_new:
return False
post_obj_data = post_obj_new.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'user', 'created_on', 'updated_on'})
if post_obj_in_result := sql_insert(data=post_obj_data, table_name='post', rm_id_random=True, id_random_length=8): pass
else:
return False
log.debug(post_obj_in_result)
post_id = post_obj_in_result
log.debug(f'Returning the new post_id: {post_id}')
return post_id
# ### END ### API Post Methods ### create_post_obj() ###
# ### BEGIN ### API Post Methods ### load_post_obj() ###
# Updated 2021-12-16
@logger_reset
def load_post_obj(
post_id: int|str,
limit: int = 1000,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
enabled: str = 'enabled', # enabled, disabled, all
inc_person: bool = False,
inc_post_comment_list: bool = False,
inc_user: bool = False,
) -> Post_Base|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if post_id := redis_lookup_id_random(record_id_random=post_id, table_name='post'): pass
else: return False
if post_rec := sql_select(table_name='v_post', record_id=post_id):
log.debug(post_rec)
else: return False
log.debug(post_rec)
try:
post_obj = Post_Base(**post_rec)
log.debug(post_obj)
except ValidationError as e:
log.error(e.json())
# Updated 2021-06-22
if inc_person:
# from app.methods.person_methods import load_person_obj
if person_result := load_person_obj(
person_id = post_rec.get('person_id', None),
limit = limit,
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
enabled = enabled,
):
post_obj.person = person_result
else: post_obj.person = None
# Updated 2021-06-17
if inc_post_comment_list:
if post_comment_rec_list_result := get_post_comment_rec_list(
for_obj_type = 'post',
for_obj_id = post_id,
limit = limit,
enabled = enabled,
):
post_comment_result_list = []
for post_comment_rec in post_comment_rec_list_result:
post_comment_result_list.append(
load_post_comment_obj(
post_comment_id = post_comment_rec.get('post_comment_id', None),
limit = limit,
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
enabled = enabled,
inc_person = inc_person,
inc_user = inc_user,
)
)
post_obj.post_comment_list = post_comment_result_list
else: post_obj.post_comment_list = []
# Updated 2021-06-18
if inc_user:
log.warning(f'This is being deprecated? load_post_obj() inc_user')
# from app.methods.user_methods import load_user_obj
# user_result = load_user_obj(
# user_id = post_rec.get('user_id', None),
# limit = limit,
# by_alias = by_alias,
# exclude_unset = exclude_unset,
# model_as_dict = model_as_dict,
# enabled = enabled,
# )
# post_obj.user = user_result
pass
if model_as_dict:
return post_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return post_obj
# ### END ### API Post Methods ### load_post_obj() ###
# ### BEGIN ### API Post Methods ### update_post_obj() ###
@logger_reset
def update_post_obj(
post_id: int|str, # Ideally the int ID should be passed. This allows for updating of the id_random value.
post_obj_up: Post_Base,
create_sub_obj: bool = False,
) -> bool:
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if post_id := redis_lookup_id_random(record_id_random=post_id, table_name='post'): pass
else: return False
post_obj_up.id = post_id
log.debug(post_obj_up)
# log.debug(post_obj_up.dict(by_alias=True, exclude_unset=True))
log.debug(post_obj_up.dict(by_alias=False, exclude_unset=True))
# log.debug(post_obj_up.dict(by_alias=False, exclude_unset=False))
#post_dict_up = post_obj_up.dict(by_alias=False, exclude_unset=True)
# if post_obj_up.person_id and post_obj_up.person:
# person_id = post_obj_up.person_id
# person_obj_up = post_obj_up.person
# log.debug(person_id)
# log.debug(person_obj_up)
# if person_obj_up_result := update_person_obj(
# person_id=person_id,
# person_obj_up=person_obj_up,
# create_sub_obj=create_sub_obj,
# ):
# log.debug(person_obj_up_result)
# else:
# log.debug(person_obj_up_result)
# return False
# if post_obj_up.user_id and post_obj_up.user:
# user_id = post_obj_up.user_id
# user_obj_up = post_obj_up.user
# log.debug(user_id)
# log.debug(user_obj_up)
# if user_obj_up_result := update_user_obj(
# user_id=user_id,
# user_dict_obj=user_obj_up,
# create_sub_obj=create_sub_obj,
# ):
# log.debug(user_obj_up_result)
# else:
# log.debug(user_obj_up_result)
# return False
post_dict_up = post_obj_up.dict(by_alias=False, exclude_unset=True, exclude={'user'})
log.debug(post_dict_up)
if post_obj_up_result := sql_update(data=post_dict_up, table_name='post', rm_id_random=True):
log.debug(post_obj_up_result)
return True
else:
log.debug(post_obj_up_result)
return False
# ### END ### API Post Methods ### update_post_obj() ###
# ### BEGIN ### API Post Methods ### get_post_rec_list() ###
# Updated 2021-12-13
@logger_reset
def get_post_rec_list(
for_obj_type: str,
for_obj_id: str,
limit: int = 1000,
enabled: str = 'enabled', # enabled, disabled, all
archive_on: datetime.datetime = None,
) -> list|bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if for_obj_id := redis_lookup_id_random(record_id_random=for_obj_id, table_name=for_obj_type): pass
else: return False
data = {}
data[f'{for_obj_type}_id'] = for_obj_id
# data['for_obj_type'] = for_obj_type
sql_obj_type_id = f'`post`.{for_obj_type}_id = :{for_obj_type}_id'
if enabled in ['enabled', 'disabled', 'all']:
if enabled == 'enabled':
data['enable'] = True
sql_enabled = f'AND `post`.enable = :enable'
elif enabled == 'disabled':
data['enable'] = False
sql_enabled = f'AND `post`.enable = :enable'
elif enabled == 'all':
sql_enabled = ''
if archive_on:
data['archive_on'] = archive_on
sql_archive_on = 'AND post.archive_on >= :archive_on'
else:
sql_archive_on = ''
if limit:
data['limit'] = limit
sql_limit = f'LIMIT :limit'
else:
sql_limit = ''
sql = f"""
SELECT `post`.id AS 'post_id', `post`.id_random AS 'post_id_random'
FROM `post` AS `post`
WHERE
{sql_obj_type_id}
{sql_enabled}
{sql_archive_on}
ORDER BY `post`.created_on DESC, `post`.updated_on DESC
{sql_limit};
"""
log.debug(sql)
if post_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
log.info('Got a list result')
post_rec_li = post_rec_li_result
else: # [] or False
log.info('No results or something went wrong')
post_rec_li = post_rec_li_result
log.debug(post_rec_li_result)
return post_rec_li
# ### END ### API Post Methods ### get_post_rec_list() ###

View File

@@ -0,0 +1,122 @@
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.lib_general import log, logging
from app.db_sql import redis_lookup_id_random, sql_insert_or_update, sql_select
from app.models.product_models import Product_Base
# ### BEGIN ### API Product Methods ### load_product_obj() ###
def load_product_obj(
product_id: int|str,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
) -> Product_Base:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if product_id := redis_lookup_id_random(record_id_random=product_id, table_name='product'): pass
else: return False
if product_rec := sql_select(table_name='v_product', record_id=product_id): pass
else: return False
try:
product_obj = Product_Base(**product_rec)
log.debug(product_obj)
except ValidationError as e:
log.error(e.json())
if model_as_dict:
return product_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return product_obj
# ### END ### API Product Methods ### load_product_obj() ###
# ### BEGIN ### API Product Methods ### get_product_rec_list() ###
# Updated 2021-07-01
def get_product_rec_list(
account_id: str = None,
for_obj_type: str = None,
for_obj_id: str = None,
prod_type: str = None,
limit: int = 1000,
enabled: str = 'enabled', # enabled, disabled, all
) -> list|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if account_id := redis_lookup_id_random(record_id_random=account_id, table_name='account'): pass
if for_obj_id := redis_lookup_id_random(record_id_random=for_obj_id, table_name=for_obj_type): pass
data = {}
data['account_id'] = account_id
data['for_obj_type'] = for_obj_type
data['for_obj_id'] = for_obj_id
if account_id:
sql_account_id = f'`product`.account_id = :account_id'
else: sql_account_id = ''
if for_obj_type and for_obj_id: # event_exhibit, event_registration, fundraising, membership_group, membership_type
if for_obj_type == 'account':
sql_account_id = f'`product`.account_id = :for_obj_id'
sql_for_obj_type_id = ''
else:
sql_for_obj_type_id = f'`product`.for_type = :for_obj_type AND `product`.for_id = :for_obj_id'
else: sql_for_obj_type_id = ''
if enabled in ['enabled', 'disabled', 'all']:
if enabled == 'enabled':
data['enable'] = True
sql_enabled = f'AND `product`.enable = :enable'
elif enabled == 'disabled':
data['enable'] = False
sql_enabled = f'AND `product`.enable = :enable'
elif enabled == 'all':
sql_enabled = ''
else: sql_enabled = ''
if prod_type in ['event', 'event_option', 'event_registration', 'fundraising', 'membership_group', 'membership_type']:
data['type_name'] = prod_type
sql_product_type = f"""AND product.type_name = :type_name"""
else: sql_product_type = ''
if limit:
data['limit'] = limit
sql_limit = f'LIMIT :limit'
else:
sql_limit = ''
sql = f"""
SELECT `product`.id AS 'product_id', `product`.id_random AS 'product_id_random'
FROM `v_product` AS `product`
WHERE
{sql_account_id}
{sql_for_obj_type_id}
{sql_product_type}
{sql_enabled}
ORDER BY `product`.created_on DESC, `product`.updated_on DESC
{sql_limit};
"""
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(sql)
if product_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
product_rec_li = product_rec_li_result
else:
product_rec_li = []
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(product_rec_li_result)
return product_rec_li
# ### END ### API Product Methods ### get_product_rec_list() ###

View File

@@ -0,0 +1,179 @@
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_enable_part, sql_insert, sql_limit_offset_part, sql_select, sql_update
from app.lib_general import log, logging, logger_reset
from app.models.site_domain_models import Site_Domain_Base
# ### BEGIN ### API Site Domain Methods ### create_site_domain_obj() ###
def create_site_domain_obj(site_domain_obj_new:Site_Domain_Base):
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if not site_domain_obj_new:
return False
site_domain_obj_data = site_domain_obj_new.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'user', 'created_on', 'updated_on'})
if site_domain_obj_in_result := sql_insert(
data=site_domain_obj_data,
table_name='site_domain',
rm_id_random=True,
id_random_length=8
): pass
else: return False
log.debug(site_domain_obj_in_result)
site_domain_id = site_domain_obj_in_result
log.debug(f'New site_domain_id: {site_domain_id}')
return site_domain_id
# ### END ### API Site Domain Methods ### create_site_domain_obj() ###
# ### BEGIN ### API Site Domain Methods ### load_site_domain_obj() ###
def load_site_domain_obj(
site_domain_id: int|str,
enabled: str = 'enabled', # enabled, disabled, all
limit: int = 100,
offset: int = 0,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
# inc_site_domain_list: bool = False,
) -> Site_Domain_Base|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if site_domain_id := redis_lookup_id_random(record_id_random=site_domain_id, table_name='site_domain'): pass
else: return False
if site_domain_rec := sql_select(table_name='v_site_domain', record_id=site_domain_id):
log.debug(site_domain_rec)
else: return False
# if site_domain_rec := sql_select(table_name='site_domain', record_id=site_domain_id):
# log.debug(site_domain_rec)
# else: return False
log.debug(site_domain_rec)
try:
site_domain_obj = Site_Domain_Base(**site_domain_rec)
log.debug(site_domain_obj)
except ValidationError as e:
log.error(e.json())
if model_as_dict:
return site_domain_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return site_domain_obj
# ### END ### API Site Domain Methods ### load_site_domain_obj() ###
# ### BEGIN ### API Site Domain Methods ### update_site_domain_obj() ###
def update_site_domain_obj(
site_domain_id: int|str, # This allows for updating of the id_random value.
site_domain_obj_up: Site_Domain_Base,
create_sub_obj: bool = False,
) -> bool:
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if site_domain_id := redis_lookup_id_random(record_id_random=site_domain_id, table_name='site_domain'): pass
else: return False
site_domain_obj_up.id = site_domain_id
log.debug(site_domain_obj_up)
log.debug(site_domain_obj_up.dict(by_alias=False, exclude_unset=True))
# log.debug(site_domain_obj_up.dict(by_alias=False, exclude_unset=False))
site_domain_dict_up = site_domain_obj_up.dict(by_alias=False, exclude_unset=True, exclude={'user'})
log.debug(site_domain_dict_up)
if site_domain_obj_up_result := sql_update(data=site_domain_dict_up, table_name='site_domain', rm_id_random=True):
log.debug(site_domain_obj_up_result)
return True
else:
log.debug(site_domain_obj_up_result)
return False
# ### END ### API Site Domain Methods ### update_site_domain_obj() ###
# ### BEGIN ### API Site Domain Methods ### get_site_domain_rec_list() ###
def get_site_domain_rec_list(
site_id: int,
enabled: str = 'enabled', # enabled, disabled, all
limit: int = 100,
offset: int = 0,
) -> list|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if site_id := redis_lookup_id_random(record_id_random=site_id, table_name='site'): pass
else: return False
data = {}
data['site_id'] = site_id
sql_enabled, data['enable'] = sql_enable_part(table_name='site_domain', enabled=enabled) # Reasonably safe return str and bool
sql_limit = sql_limit_offset_part(limit=limit, offset=offset) # Reasonably safe return str
sql = f"""
SELECT `site_domain`.id AS 'site_domain_id', `site_domain`.id_random AS 'site_domain_id_random'
FROM `site_domain` AS `site_domain`
WHERE
`site_domain`.site_id = :site_id
{sql_enabled}
ORDER BY `site_domain`.fqdn ASC, `site_domain`.access_key ASC, `site_domain`.required_referrer ASC, `site_domain`.created_on DESC, `site_domain`.updated_on DESC
{sql_limit};
"""
if site_domain_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
site_domain_rec_li = site_domain_rec_li_result
else:
site_domain_rec_li = []
return site_domain_rec_li
# ### END ### API Site Domain Methods ### get_site_domain_rec_list() ###
# ### BEGIN ### API Site Domain Methods ### lookup_site_domain_fqdn() ###
def lookup_site_domain_fqdn(
fqdn: str,
enabled: str = 'enabled', # enabled, disabled, all
limit: int = 100,
offset: int = 0,
) -> list|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
data = {}
data['fqdn'] = fqdn
sql_enabled, data['enable'] = sql_enable_part(table_name='site_domain', enabled=enabled) # Reasonably safe return str and bool
sql_limit = sql_limit_offset_part(limit=limit, offset=offset) # Reasonably safe return str
sql = f"""
SELECT `site_domain`.id AS 'site_domain_id', `site_domain`.id_random AS 'site_domain_id_random'
FROM `v_site_domain` AS site_domain
WHERE
site_domain.fqdn = :fqdn
{sql_enabled}
ORDER BY `site_domain`.fqdn ASC, `site_domain`.access_key ASC, `site_domain`.required_referrer ASC, `site_domain`.created_on DESC, `site_domain`.updated_on DESC
{sql_limit};
"""
if site_domain_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
site_domain_rec_li = site_domain_rec_li_result
else:
site_domain_rec_li = []
return site_domain_rec_li
# ### END ### API Site Domain Methods ### get_site_domain_rec_list() ###

164
app/methods/site_methods.py Normal file
View File

@@ -0,0 +1,164 @@
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_enable_part, sql_insert, sql_limit_offset_part, sql_select, sql_update
from app.lib_general import log, logging, logger_reset
from app.methods.site_domain_methods import get_site_domain_rec_list, load_site_domain_obj
from app.models.site_models import Site_Base
# ### BEGIN ### API Site Methods ### create_site_obj() ###
def create_site_obj(site_obj_new:Site_Base):
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if not site_obj_new:
return False
site_obj_data = site_obj_new.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'user', 'created_on', 'updated_on'})
if site_obj_in_result := sql_insert(
data=site_obj_data,
table_name='site',
rm_id_random=True,
id_random_length=8
): pass
else: return False
log.debug(site_obj_in_result)
site_id = site_obj_in_result
log.debug(f'New site_id: {site_id}')
return site_id
# ### END ### API Site Methods ### create_site_obj() ###
# ### BEGIN ### API Site Methods ### load_site_obj() ###
def load_site_obj(
site_id: int|str,
enabled: str = 'enabled', # enabled, disabled, all
limit: int = 100,
offset: int = 0,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
inc_site_domain_list: bool = False,
) -> Site_Base|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if site_id := redis_lookup_id_random(record_id_random=site_id, table_name='site'): pass
else: return False
if site_rec := sql_select(table_name='v_site', record_id=site_id): pass
else: return False
log.debug(site_rec)
try:
site_obj = Site_Base(**site_rec)
log.debug(site_obj)
except ValidationError as e:
log.error(e.json())
# Updated 2021-06-18
if inc_site_domain_list:
if site_domain_rec_list_result := get_site_domain_rec_list(
site_id = site_id,
enabled = enabled,
limit = limit,
offset = offset,
):
site_domain_result_list = []
for site_domain_rec in site_domain_rec_list_result:
site_domain_result_list.append(
load_site_domain_obj(
site_domain_id = site_domain_rec.get('site_domain_id', None),
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
)
)
site_obj.site_domain_list = site_domain_result_list
else: site_obj.site_domain_list = []
if model_as_dict:
return site_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return site_obj
# ### END ### API Site Methods ### load_site_obj() ###
# ### BEGIN ### API Site Methods ### update_site_obj() ###
def update_site_obj(
site_id: int|str, # This allows for updating of the id_random value.
site_obj_up: Site_Base,
create_sub_obj: bool = False,
) -> bool:
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if site_id := redis_lookup_id_random(record_id_random=site_id, table_name='site'): pass
else: return False
site_obj_up.id = site_id
log.debug(site_obj_up)
log.debug(site_obj_up.dict(by_alias=False, exclude_unset=True))
# log.debug(site_obj_up.dict(by_alias=False, exclude_unset=False))
site_dict_up = site_obj_up.dict(by_alias=False, exclude_unset=True, exclude={'user'})
log.debug(site_dict_up)
if site_obj_up_result := sql_update(data=site_dict_up, table_name='site', rm_id_random=True):
log.debug(site_obj_up_result)
return True
else:
log.debug(site_obj_up_result)
return False
# ### END ### API Site Methods ### update_site_obj() ###
# ### BEGIN ### API Site Methods ### get_site_rec_list() ###
# Updated 2022-06-14
@logger_reset
def get_site_rec_list(
account_id: int,
enabled: str = 'enabled', # enabled, disabled, all
limit: int = 100,
offset: int = 0,
) -> list|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if account_id := redis_lookup_id_random(record_id_random=account_id, table_name='account'): pass
else: return False
data = {}
data['account_id'] = account_id
sql_enabled, data['enable'] = sql_enable_part(table_name='site', enabled=enabled) # Reasonably safe return str and bool
sql_limit = sql_limit_offset_part(limit=limit, offset=offset) # Reasonably safe return str
sql = f"""
SELECT `site`.id AS 'site_id', `site`.id_random AS 'site_id_random'
FROM `site` AS `site`
WHERE
`site`.account_id = :account_id
{sql_enabled}
ORDER BY `site`.created_on DESC, `site`.updated_on DESC
{sql_limit};
"""
if site_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
site_rec_li = site_rec_li_result
else:
site_rec_li = []
return site_rec_li
# ### END ### API Site Methods ### get_site_rec_list() ###

728
app/methods/user_methods.py Normal file
View File

@@ -0,0 +1,728 @@
import datetime, random, secrets
import urllib.parse
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_enable_part, sql_insert, sql_limit_offset_part, sql_select, sql_update
from app.lib_general import log, logging, logger_reset, secure_hash_string, send_email
# from app.methods.account_methods import load_account_cfg_obj
# from app.methods.contact_methods import create_contact_obj, load_contact_obj, update_contact_obj
from app.methods.order_methods import load_order_obj, get_order_rec_list
from app.methods.organization_methods import load_organization_obj # , update_organization_obj
from app.methods.person_methods import create_person_obj_v3, load_person_obj, update_person_obj
# from app.methods.post_methods import get_post_rec_list, load_post_obj
from app.methods.user_role_methods import get_user_role_rec_list, load_user_role_obj
from app.models.common_field_schema import default_num_bytes
from app.models.user_models import User_Base, User_New_Base, User_Out_Base
# ### BEGIN ### API User Methods ### create_user_obj() ###
# NOTE: This will create a new user and also hash a new password string if given.
# NOTE: This uses the User_New_Base model, not User_Base or User_Out_Base
# Updated 2022-01-06
def create_user_obj(
account_id: int|str,
user_dict_obj: User_New_Base,
person_id: int = None, # This should be required in the future
allow_update: bool = False, # Allow updating the user account if one is found
avoid_dup_username: bool = False, # Avoid creating a duplicate by modifying the supplied username
set_default_password: bool = True,
create_sub_obj: bool = False,
fail_any: bool = True, # Fail if any thing goes wrong for sub objects
return_dict: bool = False,
) -> bool|dict|int:
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
# ### SECTION ### Secondary data validation
# NOTE: Remove at future date. Is this check needed if we trust that the ID is checked ahead of time?
if account_id := redis_lookup_id_random(record_id_random=account_id, table_name='account'): pass
else: return False
if person_id := redis_lookup_id_random(record_id_random=person_id, table_name='person'): pass
elif person_id is None: pass
else: return False
log.info('Create dictionary or Pydantic object')
# log.debug(type(user_dict_obj))
if isinstance(user_dict_obj, dict):
user_dict = user_dict_obj
try:
user_obj = User_New_Base(**user_dict)
except ValidationError as e:
log.error(e.json())
return False
else:
user_obj = user_dict_obj
user_obj.account_id = account_id
user_dict = user_obj.dict(by_alias=False, exclude_defaults=False, exclude_unset=True, exclude={'contact', 'contact_id_random', 'new_password', 'organization', 'person', 'person_id_random', 'created_on', 'updated_on'})
log.debug(user_obj)
log.debug(user_dict)
# ### SECTION ### Process data
# Look for an account_id in the user_obj
if account_id: pass
elif account_id := user_obj.account_id: pass
user_obj.account_id = account_id # Is this needed?
user_dict['account_id'] = account_id
if user_obj.new_password:
log.info('A new password was passed.')
log.debug(user_obj.new_password)
elif set_default_password:
log.warning('A new password was not passed. Setting a default password.')
user_obj.new_password = secrets.token_urlsafe(default_num_bytes)
log.debug(user_obj.new_password)
hash_string = secure_hash_string(string=user_obj.new_password)
user_obj.password = hash_string
user_dict['password'] = hash_string
else:
log.warning('A new password was not passed and not setting a default password.')
# user_dict['password'] = user_obj.password # There has to be a better way to do this??? It thinks "password" is unset and so is excluded?
# Look for a person_id in the user_obj
if person_id: pass
elif person_id := user_obj.person.id: pass
if person_id:
# Link to an existing person
log.info(f'Adding person_id to user_dict. User ID: {person_id}')
user_obj.person_id = person_id # Is this needed?
user_dict['person_id'] = person_id
log.debug(user_obj)
log.debug(user_dict)
# if account_id := redis_lookup_id_random(record_id_random=account_id, table_name='account'): pass
# elif account_id := user_dict.get('account_id', None): pass
# else: return False
# account_id = user_dict.get('account_id', None)
username = user_dict.get('username', None)
log.info(f'Checking if the username is already in use for the account... Account: {account_id}; Username: {username}')
sql_select_user = f"""
SELECT user.id, user.id_random, user.name, user.email
FROM `user` AS user
WHERE user.account_id = :account_id and user.username = :username
"""
if sql_select_user_result := sql_select(sql=sql_select_user, data=user_dict, rm_id_random=True):
if isinstance(sql_select_user_result, list):
log.exception(f'Multiple user accounts already exists with this username. The database needs to be checked. Account ID: {account_id}; Username: {username}')
return False
user_id = sql_select_user_result.get('id', None)
person_id_for_user_id = sql_select_user_result.get('person_id', None)
log.info(f'A user account already exists with this username. Current User ID: {user_id}; Username: {username}; Person ID for User: {person_id_for_user_id}; Person ID: {person_id}')
if allow_update:
log.info(f'Updating instead of inserting. Current User ID: {user_id}; Username: {username}')
# NOTE: Should this call the update_user_obj() function instead??? NOTE NOTE NOTE NOTE
if person_id_for_user_id == person_id or person_id_for_user_id is None:
user_dict['id'] = user_id
user_dict['person_id'] = person_id
if user_dict_up_result := sql_update(data=user_dict, table_name='user', rm_id_random=True):
log.info(f'User updated with new user data. User ID: {user_id}')
else:
log.warning(f'User not updated with new user data. User ID: {user_id}')
log.debug(user_dict_up_result)
return False
else:
log.info(f'Updating is now allowed. Current User ID: {user_id}; Username: {username}')
if avoid_dup_username:
log.info(f'Avoiding duplicate username is now allowed. Suggested Username: {username}')
new_username = username+'-'+str(random.randint(10, 99))
user_dict['username'] = new_username
if user_dict_in_result := sql_insert(
data = user_dict,
table_name = 'user',
rm_id_random = True,
id_random_length = default_num_bytes
): pass
else:
log.warning(f'User not created.')
log.debug(user_dict_in_result)
return False
user_id = user_dict_in_result
else:
log.warning(f'Updating is not allowed and avoid duplicate username is not allowed. Username: {username}')
return False
#log.setLevel(logging.DEBUG)
# log.debug(user_dict_up_result)
log.debug(f'Returning the existing user_id: {user_id}')
else:
if user_dict_in_result := sql_insert(
data = user_dict,
table_name = 'user',
rm_id_random = True,
id_random_length = default_num_bytes
): pass
else:
log.warning(f'User not created.')
log.debug(user_dict_in_result)
return False
user_id = user_dict_in_result
log.info(f'Returning the new user_id: {user_id}')
return user_id
# ### END ### API User Methods ### create_user_obj() ###
# ### BEGIN ### API User Methods ### update_user_obj() ###
# Updated 2022-01-06
def update_user_obj(
user_id: int|str, # Ideally the int ID should be passed. This allows for updating of the id_random value.
user_dict_obj: User_Base,
person_id: int = None, # This should be required in the future?
set_default_password: bool = True,
create_sub_obj: bool = False,
fail_any: bool = True, # Fail if any thing goes wrong for sub objects
return_dict: bool = False,
) -> bool|int:
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
# ### SECTION ### Secondary data validation
# NOTE: Remove at future date. Is this check needed if we trust that the ID is checked ahead of time?
if user_id := redis_lookup_id_random(record_id_random=user_id, table_name='user'): pass
else: return False
if person_id := redis_lookup_id_random(record_id_random=person_id, table_name='person'): pass
elif person_id is None: pass
else: return False
log.info('Create dictionary or Pydantic object')
log.debug(type(user_dict_obj))
if isinstance(user_dict_obj, dict):
user_dict = user_dict_obj
user_dict['id'] = user_id
try:
user_obj = User_Base(**user_dict)
except ValidationError as e:
log.error(e.json())
return False
else:
user_obj = user_dict_obj
user_obj.id = user_id
log.debug(user_obj)
# IMPORTANT NOTE: Need to be extra careful if allowing an update to password, super, or manager. Maybe other fields?
user_dict = user_obj.dict(by_alias=False, exclude_unset=True, exclude={'password', 'super', 'manager', 'contact', 'organization', 'person', 'created_on', 'updated_on'})
# log.debug(type(user_dict_obj))
# if isinstance(user_dict_obj, dict):
# try:
# user_obj = User_Base(**user_dict_obj)
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
# log.debug(user_obj)
# except ValidationError as e:
# log.error(e.json())
# return False
# user_obj.id = user_id
# log.debug(user_obj)
# log.debug(user_obj.dict(by_alias=True, exclude_unset=True))
log.debug(user_obj.dict(by_alias=False, exclude_unset=True))
# log.debug(user_obj.dict(by_alias=False, exclude_unset=False))
# if user_obj.contact_id and user_obj.contact:
# contact_id = user_obj.contact_id
# contact_obj_up = user_obj.contact
# log.debug(contact_id)
# log.debug(contact_obj_up)
# if contact_obj_up_result := update_contact_obj(
# contact_id=contact_id,
# contact_dict_obj=contact_obj_up,
# create_sub_obj=create_sub_obj,
# ):
# log.debug(contact_obj_up_result)
# else:
# log.debug(contact_obj_up_result)
# return False
# elif user_obj.contact and not user_obj.contact.id:
# # NOTE: This will blindly create a new contact even if there was one associated but the user_obj.contact_id was not found.
# contact_obj_in = user_obj.contact
# log.debug(contact_obj_in)
# if contact_obj_in_result := create_contact_obj(contact_dict_obj=contact_obj_in):
# # log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
# log.debug(contact_obj_in_result)
# user_obj.contact_id = contact_obj_in_result
# else:
# # log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
# log.debug(contact_obj_in_result)
# return False
# if organization_obj_update := user_obj.organization:
# log.debug(organization_obj_update)
# if organization_obj_up_result := update_organization_obj(organization_obj_update):
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
# log.debug(organization_obj_up_result)
# return True
# else:
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
# log.debug(organization_obj_up_result)
# return False
# else:
# if organization_obj_in_result := insert_organization_obj(organization_obj_insert):
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
# log.debug(organization_obj_in_result)
# return True # NOTE: This needs to return the new organization ID
# else:
# log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
# log.debug(organization_obj_in_result)
# return False
# if user_obj.person_id and user_obj.person:
# person_id = user_obj.person_id
# person_obj_up = user_obj.person
# log.debug(person_id)
# log.debug(person_obj_up)
# if person_obj_up_result := update_person_obj(
# person_id=person_id,
# person_obj_up=person_obj_up,
# create_sub_obj=create_sub_obj,
# ):
# log.debug(person_obj_up_result)
# else:
# log.debug(person_obj_up_result)
# return False
# elif user_obj.person and not user_obj.person.id:
# # NOTE: This will blindly create a new person even if there was one associated but the user_obj.person_id was not found.
# person_obj_in = user_obj.person
# log.debug(person_obj_in)
# if person_obj_in_result := create_person_obj_v3(person_obj_new=person_obj_in):
# # log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
# log.debug(person_obj_in_result)
# person_obj_up.person_id = person_obj_in_result
# else:
# # log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
# log.debug(person_obj_in_result)
# return False
# IMPORTANT NOTE: Need to be extra careful if allowing an update to password, super, or manager. Maybe other fields?
# user_dict = user_obj.dict(by_alias=False, exclude_unset=True, exclude={'password', 'super', 'manager', 'contact', 'organization', 'person'})
# log.debug(user_dict)
# ### SECTION ### Process data
user_obj.id = user_id # Is this needed?
user_dict['id'] = user_id
# if user_obj.new_password:
log.debug(user_obj.new_password)
# if user_obj.password:
log.debug(user_obj.password)
# else:
# user_obj.new_password = secrets.token_urlsafe(default_num_bytes)
# hash_string = secure_hash_string(string=user_obj.new_password)
# user_obj.password = hash_string
# user_dict['password'] = hash_string
# log.debug(user_obj.new_password)
# Look for a person_id in the user_obj
if person_id: pass
elif person_id := user_obj.person.id: pass
if person_id:
# Link to an existing person
log.info(f'Adding person_id to person_dict. Person ID: {person_id}')
user_obj.person_id = person_id # Is this needed?
user_dict['person_id'] = person_id
log.debug(user_obj)
log.debug(user_dict)
if user_dict_up_result := sql_update(
data = user_dict,
table_name = 'user',
rm_id_random = True
): pass
else:
log.warning(f'User not updated.')
log.debug(user_dict_up_result)
return False
log.debug(user_dict_up_result)
return True
# ### END ### API User Methods ### update_user_obj() ###
# ### BEGIN ### API User Methods ### load_user_obj() ###
def load_user_obj(
user_id: int|str,
enabled: str = 'enabled', # enabled, disabled, all
limit: int = 100,
offset: int = 0,
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
inc_address: bool = False,
# inc_archive_list: bool = False, # deprecated
inc_contact: bool = False,
inc_event_list: bool = False, # deprecated
# inc_hosted_file_list: bool = False, # deprecated
# inc_journal_list: bool = False, # deprecated
# inc_journal_entry_list: bool = False, # deprecated
# inc_membership_person: bool = False, # deprecated
inc_order_cfg: bool = False, # deprecated
inc_order_line_list: bool = False, # deprecated
inc_order_list: bool = False, # deprecated
inc_order_cart_list: bool = False, # deprecated
inc_organization: bool = False, # deprecated
inc_person: bool = False,
# inc_person_list: bool = False, # deprecated
# inc_post_list: bool = False, # deprecated
# inc_post_comment_list: bool = False, # deprecated
inc_user_role_list: bool = False,
) -> User_Out_Base|dict|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if user_id := redis_lookup_id_random(record_id_random=user_id, table_name='user'): pass
else: return False
if user_rec := sql_select(table_name='v_user', record_id=user_id): pass
else: return False
log.debug(user_rec)
try:
user_obj = User_Out_Base(**user_rec)
log.debug(user_obj)
except ValidationError as e:
log.error(e.json())
return False
# Updated 2021-06-18
if inc_contact:
log.warning(f'This is being deprecated? load_user_obj() inc_contact')
# contact_id = user_rec.get('contact_id', None)
# log.debug(contact_id)
# if contact_result := load_contact_obj(
# contact_id = contact_id,
# limit = limit,
# by_alias = by_alias,
# exclude_unset = exclude_unset,
# model_as_dict = model_as_dict,
# enabled = enabled,
# inc_address = inc_address,
# ):
# user_obj.contact = contact_result
# else: user_obj.contact = {} # None
if inc_event_list:
log.warning(f'This is being deprecated? load_user_obj() inc_event_list')
from app.methods.event_methods import load_event_obj_list
if event_dict_list := load_event_obj_list(
user_id = user_id,
limit = limit,
model_as_dict = model_as_dict,
enabled = enabled,
):
user_obj.event_list = event_dict_list
else: user_obj.event_list = []
# Updated 2021-06-18
if inc_order_list:
log.warning(f'This is being deprecated? load_user_obj() inc_order_list')
if order_rec_list_result := get_order_rec_list(
for_obj_type = 'user',
for_obj_id = user_id,
limit = limit,
enabled = enabled,
):
order_result_list = []
for order_rec in order_rec_list_result:
if load_order_result := load_order_obj(
order_id = order_rec.get('order_id', None),
limit = limit,
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
enabled = enabled,
inc_order_cfg = inc_order_cfg,
inc_order_line_list = inc_order_line_list,
inc_person = inc_person,
):
order_result_list.append(load_order_result)
else: order_result_list.append(None)
user_obj.order_list = order_result_list
else: user_obj.order_list = []
# Updated 2021-12-14
if inc_organization:
log.warning(f'This is being deprecated? load_user_obj() inc_organization')
organization_id = user_rec.get('organization_id', None)
log.debug(organization_id)
if organization_result := load_organization_obj(
organization_id = organization_id,
limit = limit,
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
enabled = enabled,
inc_address = inc_address,
inc_contact = inc_contact,
):
user_obj.organization = organization_result
else: user_obj.organization = {} # None
# Updated 2021-12-14
if inc_person:
person_id = user_rec.get('person_id', None)
log.debug(person_id)
if person_result := load_person_obj(
person_id = person_id,
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
inc_address = inc_address,
inc_contact = inc_contact,
inc_organization = inc_organization,
):
user_obj.person = person_result
else: user_obj.person = {} # None
log.debug(person_result)
# Updated 2021-06-18
# if inc_post_list:
# log.warning(f'This is being deprecated? load_user_obj() inc_post_list')
# if post_rec_list_result := get_post_rec_list(
# for_obj_type = 'user',
# for_obj_id = user_id,
# limit = limit,
# enabled = enabled,
# ):
# post_result_list = []
# for post_rec in post_rec_list_result:
# if load_post_result := load_post_obj(
# post_id = post_rec.get('post_id', None),
# limit = limit,
# by_alias = by_alias,
# exclude_unset = exclude_unset,
# model_as_dict = model_as_dict,
# enabled = enabled,
# inc_post_comment_list = inc_post_comment_list,
# inc_person = inc_person,
# # inc_user = inc_user,
# ):
# post_result_list.append(load_post_result)
# else: post_result_list.append(None)
# user_obj.post_list = post_result_list
# else: user_obj.post_list = []
# Updated 2021-06-25
if inc_user_role_list:
if user_role_rec_list_result := get_user_role_rec_list(
for_obj_type = 'user',
for_obj_id = user_id,
limit = limit,
enabled = enabled,
):
user_role_result_list = []
for user_role_rec in user_role_rec_list_result:
if load_user_role_result := load_user_role_obj(
user_role_id = user_role_rec.get('user_role_id', None),
by_alias = by_alias,
exclude_unset = exclude_unset,
model_as_dict = model_as_dict,
):
user_role_result_list.append(load_user_role_result)
else: user_role_result_list.append(None)
user_obj.user_role_list = user_role_result_list
else: user_obj.user_role_list = []
log.debug(user_obj)
if model_as_dict:
return user_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return user_obj
# ### END ### API User Methods ### load_user_obj() ###
# ### BEGIN ### API User Methods ### get_user_rec_list() ###
# Updated 2021-12-13
@logger_reset
def get_user_rec_list(
account_id: int|str,
hidden: str = 'not_hidden', # hidden, not_hidden, all
enabled: str = 'enabled', # enabled, disabled, all
limit: int = 1000,
offset: int = 0,
) -> list|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if account_id := redis_lookup_id_random(record_id_random=account_id, table_name='account'): pass
else: return False
data = {}
data['account_id'] = account_id
sql_where_account_id = f'`user`.account_id = :account_id'
# data[f'{for_obj_type}_id'] = for_obj_id
# data['for_obj_type'] = for_obj_type
# sql_obj_type_id = f'`user`.{for_obj_type}_id = :{for_obj_type}_id'
sql_enabled, data['enable'] = sql_enable_part(table_name='user', enabled=enabled) # Reasonably safe return str and bool
sql_limit = sql_limit_offset_part(limit=limit, offset=offset) # Reasonably safe return str
sql = f"""
SELECT `user`.id AS 'user_id', `user`.id_random AS 'user_id_random'
FROM `user` AS `user`
WHERE
{sql_where_account_id}
{sql_enabled}
ORDER BY user.name, user.email, user.username, `user`.created_on DESC, `user`.updated_on DESC
{sql_limit};
"""
log.debug(sql)
if user_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
log.info('Got a list result')
user_rec_li = user_rec_li_result
else: # [] or False
log.info('No results or something went wrong')
user_rec_li = user_rec_li_result
log.debug(user_rec_li_result)
return user_rec_li
# ### END ### API User Methods ### get_user_rec_list() ###
# ### BEGIN ### User Methods ### email_user_auth_key_url() ###
# This generates a new auth_key token and emails the actual one time use sign in URL to the user's email.
# Updated 2025-04-08
def email_user_auth_key_url(
account_id: int|str,
user_id: int|str,
root_url: str,
key_param_name: str = 'auth_key',
):
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if account_id := redis_lookup_id_random(record_id_random=account_id, table_name='account'): pass
else: return False
if user_id := redis_lookup_id_random(record_id_random=user_id, table_name='user'): pass
else: return False
if user_obj := load_user_obj(
user_id = user_id,
):
log.info('User object loaded')
if user_obj.enable and user_obj.allow_auth_key:
new_auth_key = secrets.token_urlsafe(default_num_bytes)
update_user_data = {}
update_user_data['auth_key'] = new_auth_key
if user_rec_update_result := sql_update(
table_name = 'user',
record_id = user_id,
data = update_user_data
):
log.info('The user record was updated with a new auth_key')
else:
log.warning('The user record was not updated with a new auth_key')
return False
else:
log.warning('The user record was not enabled or auth_key is not allowed')
return False
else: return False
log.debug(user_obj)
from app.methods.account_cfg_methods import load_account_cfg_obj
if account_cfg := load_account_cfg_obj(
account_id = account_id,
):
log.info('Account config loaded')
else: return False
log.debug(account_cfg)
user_id_random = user_obj.id_random # NOTE: Not user_id_random because of alias
from_email = account_cfg.default_no_reply_email
from_name = account_cfg.default_no_reply_name
to_name = user_obj.name
to_email = user_obj.email
bcc_email = account_cfg.confirm_email
bcc_name = account_cfg.confirm_name
help_tech_email = account_cfg.help_tech_email
help_tech_name = account_cfg.help_tech_name
account_short_name = account_cfg.account_short_name
username = user_obj.username
enable = user_obj.enable
if enable_from := user_obj.enable_from:
# enable_from_datetime = datetime.datetime.fromisoformat(enable_from).replace(tzinfo=datetime.timezone.utc)
enable_from_datetime = enable_from.replace(tzinfo=datetime.timezone.utc)
enable_from_datetime = enable_from
enable_from_str = enable_from_datetime.strftime('%A, %B %-d, %Y %-I:%M %p %Z')
else: enable_from_str = '-- Not Set --'
if enable_to := user_obj.enable_to:
# enable_to_datetime = datetime.datetime.fromisoformat(enable_to).replace(tzinfo=datetime.timezone.utc)
enable_to_datetime = enable_to.replace(tzinfo=datetime.timezone.utc)
enable_to_str = enable_to_datetime.strftime('%A, %B %-d, %Y %-I:%M %p %Z')
else: enable_to_str = '-- Not Set --'
auth_key = user_obj.auth_key
user_login_url = f'{root_url}?username={urllib.parse.quote(username)}&user_email={urllib.parse.quote(to_email)}'
# user_login_url = f'{root_url}user/login?username={urllib.parse.quote(username)}&email={urllib.parse.quote(to_email)}'
if key_param_name == 'auth_key':
user_login_auth_key_url = f'{root_url}?user_id={urllib.parse.quote(user_id_random)}&auth_key={urllib.parse.quote(new_auth_key)}&valid_email={True}'
elif key_param_name:
user_login_auth_key_url = f'{root_url}?user_id={urllib.parse.quote(user_id_random)}&{key_param_name}={urllib.parse.quote(new_auth_key)}&valid_email={True}'
subject = f'{account_short_name}: One Time Use Sign In Link ({new_auth_key})'
body_html = f"""
<p>{to_name},</p>
<p>If you did not request this sign in link, please delete this email. It is suggested that you delete this email after the sign in link has been used or if a new link has been requested.</p>
<p>The link below can only be used to sign in once. If you would like to sign in again using this method, you must <a href="{user_login_url}">request a new sign in link</a>. If you request multiple links, only the newest link will sign you in.</p>
<p><strong><a href="{user_login_auth_key_url}" style="appearance: button; display: inline-block; text-align: center; text-decoration: none; padding: .2rem .4rem; border: solid thin gray; border-radius: .2rem; background-color: lightyellow; color: black; font-size: larger;">Click to Sign In With One Time Use Link</a></strong></p>
<p>Or copy and paste the link:<br>
<strong style="background-color: lightyellow; color: black; font-size: larger;"><a href="{user_login_auth_key_url}">{user_login_auth_key_url}</a></strong></p>
<p>Your username is: {username}<br>
User account enabled: {enable}<br>
User account enabled from: {enable_from_str}<br>
User account enabled to: {enable_to_str}<br>
Current one time use auth key for user account: {new_auth_key}</p>
<p>If you have questions about this email or trouble with this one time use link, you can email <a href="mailto:{help_tech_email}">{help_tech_name} ({help_tech_email})</a>.</p>
<p>Thank you!</p>
"""
log.info('Trying send_email()...')
if send_email(from_email=from_email, from_name=from_name, to_email=to_email, to_name=to_name, bcc_email=bcc_email, bcc_name=bcc_name, subject=subject, body_text=None, body_html=body_html):
log.info(f'An email with a one time use sign in link was sent to {to_email}.')
return True
else:
log.info(f'An email with a one time use sign in link was not sent to {to_email}.')
return False
# ### END ### User Methods ### email_user_auth_key_url() ###

View File

@@ -0,0 +1,104 @@
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_select
from app.lib_general import log, logging
from app.models.user_role_models import User_Role_Base
# ### BEGIN ### API User Role Methods ### load_user_role_obj() ###
# Updated 2021-06-25
def load_user_role_obj(
user_role_id: int|str, # NOTE: This is currently just an auto ID number, not a random string.
by_alias: bool = True,
exclude_unset: bool = True,
model_as_dict: bool = False,
) -> User_Role_Base|dict|bool:
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
# if user_role_id := redis_lookup_id_random(record_id_random=user_role_id, table_name='user_role'): pass
# else: return False
if user_role_rec := sql_select(table_name='v_user_role', record_id=user_role_id): pass
else: return False
#log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(user_role_rec)
try:
user_role_obj = User_Role_Base(**user_role_rec)
log.debug(user_role_obj)
except ValidationError as e:
log.error(e.json())
return False
if model_as_dict:
return user_role_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pylint: disable=no-member
else:
return user_role_obj
# ### END ### API User Role Methods ### load_user_role_obj() ###
# ### BEGIN ### API User Role Methods ### get_user_role_rec_list() ###
# Updated 2021-06-25
def get_user_role_rec_list(
for_obj_type: str,
for_obj_id: str,
limit: int = 1000,
enabled: str = 'enabled', # enabled, disabled, all
) -> list|bool:
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
if for_obj_id := redis_lookup_id_random(record_id_random=for_obj_id, table_name=for_obj_type): pass
else: return False
data = {}
data[f'{for_obj_type}_id'] = for_obj_id
# data['for_obj_type'] = for_obj_type
sql_obj_type_id = f'`tbl`.{for_obj_type}_id = :{for_obj_type}_id'
allowed_forign_key_li = ['user']
if for_obj_type in allowed_forign_key_li:
log.info(f'Query using forign key: {for_obj_type} {for_obj_id}')
sql_obj_type_id = f'`tbl`.{for_obj_type}_id = :{for_obj_type}_id'
if enabled in ['enabled', 'disabled', 'all']:
if enabled == 'enabled':
data['enable'] = True
sql_enabled = f'AND `tbl`.enable = :enable'
elif enabled == 'disabled':
data['enable'] = False
sql_enabled = f'AND `tbl`.enable = :enable'
elif enabled == 'all':
sql_enabled = ''
if limit:
data['limit'] = limit
sql_limit = f'LIMIT :limit'
else:
sql_limit = ''
sql = f"""
SELECT `tbl`.id AS 'user_role_id'/*, `tbl`.id_random AS 'user_role_id_random'*/
FROM `user_role` AS `tbl`
WHERE
{sql_obj_type_id}
{sql_enabled}
ORDER BY `tbl`.created_on DESC, `tbl`.updated_on DESC
{sql_limit};
"""
else: return False
if user_role_rec_li_result := sql_select(data=data, sql=sql, as_list=True):
user_role_rec_li = user_role_rec_li_result
else:
user_role_rec_li = []
log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(user_role_rec_li_result)
return user_role_rec_li
# ### END ### API User Role Methods ### get_user_role_rec_list() ###

12
app/middleware.py Normal file
View File

@@ -0,0 +1,12 @@
import time
from fastapi import Request
async def add_process_time_header(request: Request, call_next):
"""
Middleware to add the processing time to the response header.
"""
start_time = time.time()
response = await call_next(request)
process_time = time.time() - start_time
response.headers['X-Process-Time'] = str(process_time)
return response

0
app/models/__init__.py Normal file
View File

View File

@@ -0,0 +1,135 @@
import datetime, pytz
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.fundraising_cfg_models import Fundraising_Cfg_Base
from app.models.membership_cfg_models import Membership_Cfg_Base
# ### BEGIN ### API Account Cfg Models ### Account_Cfg_Base() ###
class Account_Cfg_Base(BaseModel):
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
id_random: Optional[str] = Field(
# **base_fields['account_cfg_id_random'],
alias = 'account_cfg_id_random',
)
id: Optional[int] = Field(
alias = 'account_cfg_id'
)
account_id_random: Optional[str]
account_id: Optional[int]
account_code: Optional[str]
account_name: Optional[str]
account_short_name: Optional[str]
account_description: Optional[str]
account_enable: Optional[bool]
account_enable_from: Optional[datetime.datetime] = None
account_enable_to: Optional[datetime.datetime] = None
modules_enabled: Optional[Json] = None
show_user_availability: Optional[bool]
show_person_create: Optional[bool]
person_create_label: Optional[str]
show_person_view: Optional[bool]
person_view_label: Optional[str]
show_person_load: Optional[bool]
person_load_label: Optional[str]
show_cart: Optional[bool]
cart_label: Optional[str]
default_no_reply_email: Optional[str]
default_no_reply_name: Optional[str]
default_reply_to_email: Optional[str]
default_reply_to_name: Optional[str]
confirm_email: Optional[str]
confirm_name: Optional[str]
# For general event help (attendees and registration)
help_event_email: Optional[str]
help_event_name: Optional[str]
# For event exhibit help
help_event_exhibit_email: Optional[str]
help_event_exhibit_name: Optional[str]
# For event presentation management help (chairs, organizers, persenters)
help_event_presenter_email: Optional[str]
help_event_presenter_name: Optional[str]
# General catch all help
help_general_email: Optional[str]
help_general_name: Optional[str]
# For contacting the organizations leadership (board, council, committee)
help_leadership_email: Optional[str]
help_leadership_name: Optional[str]
# For general membership help
help_member_email: Optional[str]
help_member_name: Optional[str]
# For general technical support
help_tech_email: Optional[str]
help_tech_name: Optional[str]
order_header: Optional[str]
order_thanks: Optional[str]
order_message: Optional[str]
order_footer: Optional[str]
order_fundraising_thanks: Optional[str]
order_fundraising_message: Optional[str]
fundraising_message: Optional[str]
post_rules: Optional[str]
post_comment_rules: Optional[str]
show_post_title: Optional[bool]
show_post_comment_title: Optional[bool]
hide_posts_after: Optional[int] # Should posts be singular?
delete_posts_after: Optional[int] # Should posts be singular?
stripe_api_key: Optional[str] # Secret/Private
stripe_publishable_key: Optional[str] # Publish/Sharable
stripe_account_id: Optional[str] # Connected Stripe Account ID
created_on: Optional[datetime.datetime] = None
updated_on: Optional[datetime.datetime] = None
# Including other related objects
fundraising_cfg: Optional[Fundraising_Cfg_Base] # Priority l2
membership_cfg: Optional[Membership_Cfg_Base] # Priority l2
_processed_at: datetime.datetime = PrivateAttr(default_factory=datetime.datetime.now)
@validator('id', always=True)
def account_cfg_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='account_cfg')
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
class Config:
underscore_attrs_are_private = True
allow_population_by_field_name = True
fields = base_fields
# ### END ### API Account Cfg Models ### Account_Cfg_Base() ###

View File

@@ -0,0 +1,104 @@
import datetime, pytz
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.account_cfg_models import Account_Cfg_Base
# from app.models.address_models import Address_Base
# from app.models.contact_models import Contact_Base
# from app.models.event_models import Event_Base
from app.models.fundraising_cfg_models import Fundraising_Cfg_Base
from app.models.membership_cfg_models import Membership_Cfg_Base
# from app.models.person_models import Person_Base
# from app.models.user_models import User_Base
# ### BEGIN ### API Account Models ### Account_Base() ###
class Account_Base(BaseModel):
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
id_random: Optional[str] = Field(
**base_fields['account_id_random'],
alias = 'account_id_random',
# default_factory = lambda:secrets.token_urlsafe(default_num_bytes),
)
id: Optional[int] = Field(
alias = 'account_id'
)
# account_id: Optional[int] = Field(
# )
code: Optional[str]
name: Optional[str]
short_name: Optional[str]
description: Optional[str]
hide: Optional[bool]
priority: Optional[bool]
sort: Optional[int]
group: Optional[str]
enable: Optional[bool]
enable_from: Optional[datetime.datetime] = None
enable_to: Optional[datetime.datetime] = None
notes: Optional[str]
created_on: Optional[datetime.datetime] = None
updated_on: Optional[datetime.datetime] = None
# testing: Optional[str]
# Including other related objects
account_cfg: Optional[Account_Cfg_Base] # Priority l1
address_list: Optional[list] # Address_Base() # Priority l3
archive_list: Optional[list] # Archive_Base() # Priority l1
contact_list: Optional[list] # Contact_Base() # Priority l3
event_list: Optional[list] # Event_Base() # Priority l1
fundraising_cfg: Optional[Fundraising_Cfg_Base] # Priority l2
hosted_file_list: Optional[list] # Hosted_File_Base() # Priority l2
journal_list: Optional[list] # Journal_Base() # Priority l3
membership_cfg: Optional[Membership_Cfg_Base] # Priority l2
membership_group_list: Optional[list] # Membership_Group_Base() # Priority l2
membership_person_list: Optional[list] # Membership_Person_Base() # Priority l2
membership_type_list: Optional[list] # Membership_Type_Base() # Priority l2
order_list: Optional[list] # Order_Base() # Priority l2
organization_list: Optional[list] # Organization_Base() # Priority l3
page_list: Optional[list] # Page_Base() # Priority l3
person_list: Optional[list] # Person_Base() # Priority l2
post_list: Optional[list] # Post_Base() # Priority l1
product_list: Optional[list] # Product_Base() # Priority l3
site_list: Optional[list] # Site_Base() # Priority l3
user_list: Optional[list] # User_Base() # Priority l2
_processed_at: datetime.datetime = PrivateAttr(default_factory=datetime.datetime.now)
@validator('id', always=True)
def account_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='account')
return None
# @validator('account_id', always=True)
# def account_id_duplicate(cls, v, values, **kwargs):
# log.setLevel(logging.DEBUG)
# log.debug(locals())
# if values['id']:
# log.debug(values['id'])
# return values['id']
# return None
class Config:
underscore_attrs_are_private = True
fields = base_fields
allow_population_by_field_name = True
# ### END ### API Account Models ### Account_Base() ###

View File

@@ -0,0 +1,128 @@
import datetime, pytz
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
# ### BEGIN ### API Activity Log Models ### Activity_Log_Base() ###
class Activity_Log_Base(BaseModel):
log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
id_random: Optional[str] = Field(
**base_fields['activity_log_id_random'],
alias = 'activity_log_id_random',
)
id: Optional[int] = Field(
alias = 'activity_log_id'
)
account_id_random: Optional[str]
account_id: Optional[int]
person_id_random: Optional[str]
person_id: Optional[int]
user_id_random: Optional[str]
user_id: Optional[int]
external_client_id: Optional[str]
google_ga: Optional[str]
google_gid: Optional[str]
name: Optional[str]
description: Optional[str]
source: Optional[str]
url_root: Optional[str]
url_full_path: Optional[str]
url_params: Optional[str]
object_type: Optional[str]
object_id_random: Optional[str]
object_id: Optional[int]
action: Optional[str]
action_with: Optional[str]
action_on_type: Optional[str]
action_on_id_random: Optional[str]
action_on_id: Optional[int]
action_on_code: Optional[str]
action_data: Optional[str]
code: Optional[str]
type_id: Optional[int]
type_name: Optional[str]
details: Optional[str]
# For now just using string instead of Json Pydantic data type
other_json: Optional[str] # When getting the dict version for SQL this should be a string.
meta_json: Optional[str] # When getting the dict version for SQL this should be a string.
enable: Optional[bool]
hide: Optional[bool]
priority: Optional[bool]
sort: Optional[int]
group: Optional[str]
notes: Optional[str]
created_on: Optional[datetime.datetime] = None
updated_on: Optional[datetime.datetime] = None
_processed_at: datetime.datetime = PrivateAttr(default_factory=datetime.datetime.now)
#@validator('activity_log_id_random', always=True)
def activity_log_id_random_copy(cls, v, values, **kwargs):
log.setLevel(logging.WARNING)
log.debug(locals())
if id_random := values.get('id_random'):
return id_random
return None
@validator('id', always=True)
def activity_log_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='activity_log')
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
class Config:
underscore_attrs_are_private = True
allow_population_by_field_name = True
fields = base_fields
# ### END ### API Activity Log Models ### Activity_Log_Base() ###

View File

@@ -0,0 +1,94 @@
import datetime, pytz
from typing import Dict, List, Optional, Set, Union
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
from app.models.common_field_schema import base_fields, default_num_bytes
# ### BEGIN ### API Address Models ### Address_Base() ###
class Address_Base(BaseModel):
log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL
log.debug(locals())
# --- 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] = Field(None, exclude=True)
#organization: Optional[Organization_Base] = Organization_Base()
name: Optional[str]
attention_to: Optional[str]
organization_name: Optional[str]
line_1: Optional[str]
line_2: Optional[str]
line_3: Optional[str]
city: Optional[str]
country_subdivision_code: Optional[str]
country_subdivision_name: Optional[str] # From country subdivision lookup table
state_province: Optional[str] # Avoid using
postal_code: Optional[str]
country_alpha_2_code: Optional[str]
country_name: Optional[str] # From country lookup table
country: Optional[str] # Avoid using
lu_time_zone_id: Optional[str]
timezone: Optional[str]
latitude: Optional[str]
longitude: Optional[str]
map_url: Optional[str]
congressional_district: Optional[str]
enable: Optional[bool]
hide: Optional[bool]
priority: Optional[bool]
sort: Optional[int]
group: Optional[str]
notes: Optional[str]
created_on: Optional[datetime.datetime] = None
updated_on: Optional[datetime.datetime] = None
_processed_at: datetime.datetime = PrivateAttr(default_factory=datetime.datetime.now)
@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 = False
fields = base_fields
# ### END ### API Address Models ### Address_Base() ###

Some files were not shown because too many files have changed in this diff Show More