diff --git a/app/main.py b/app/main.py index 638dca8..d567386 100644 --- a/app/main.py +++ b/app/main.py @@ -18,7 +18,7 @@ from . import config from app.log import log, logging # Import the routers here first: -from app.routers import aether_cfg, api_crud, api, importing, sql, account, activity_log, address, archive, archive_content, contact, cont_edu_cert, cont_edu_cert_person, data_store, event, event_badge, 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, 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, websockets, e_cvent, c_idaa, e_impexium, e_stripe +from app.routers import aether_cfg, api_crud, api, importing, sql, account, activity_log, address, archive, archive_content, contact, cont_edu_cert, cont_edu_cert_person, data_store, event, event_badge, 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, 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, websockets, e_cvent, c_idaa, e_impexium, e_stripe from app.db_sql import db, sql_select # , sql_connect @@ -226,7 +226,6 @@ app.include_router( ) app.include_router( event_person_tracking.router, - # prefix='/event/person/tracking', tags=['Event Person Tracking'], ) app.include_router( @@ -249,6 +248,10 @@ app.include_router( # prefix='/event/session', tags=['Event Session'], ) +app.include_router( + fundraising.router, + tags=['Fundraising'], +) app.include_router( hosted_file.router, prefix='/hosted_file', diff --git a/app/methods/account_cfg_methods.py b/app/methods/account_cfg_methods.py index 65cbdf3..869f305 100644 --- a/app/methods/account_cfg_methods.py +++ b/app/methods/account_cfg_methods.py @@ -41,7 +41,7 @@ def load_account_cfg_obj( log.error(e.json()) if inc_fundraising_cfg: - if fundraising_cfg_dict := load_fundraising_cfg_obj( + if fundraising_cfg_dict := load_fundraising_cfg_obj_old( account_id = account_id, model_as_dict = model_as_dict, ): diff --git a/app/methods/fundraising_cfg_methods.py b/app/methods/fundraising_cfg_methods.py index 2a67700..08b5a03 100644 --- a/app/methods/fundraising_cfg_methods.py +++ b/app/methods/fundraising_cfg_methods.py @@ -1,17 +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.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: @@ -38,4 +70,4 @@ def load_fundraising_cfg_obj( 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() ### +# ### END ### API Fundraising Cfg Methods ### load_fundraising_cfg_obj_old() ### diff --git a/app/methods/fundraising_methods.py b/app/methods/fundraising_methods.py new file mode 100644 index 0000000..983be60 --- /dev/null +++ b/app/methods/fundraising_methods.py @@ -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() ### diff --git a/app/methods/product_methods.py b/app/methods/product_methods.py index 0246ba1..3218396 100644 --- a/app/methods/product_methods.py +++ b/app/methods/product_methods.py @@ -13,7 +13,7 @@ from app.models.product_models import Product_Base # ### BEGIN ### API Product Methods ### load_product_obj() ### def load_product_obj( product_id: int|str, - # limit: int = 1000, + by_alias: bool = True, exclude_unset: bool = True, model_as_dict: bool = False, diff --git a/app/models/fundraising_cfg_models.py b/app/models/fundraising_cfg_models.py index 3128235..3babffa 100644 --- a/app/models/fundraising_cfg_models.py +++ b/app/models/fundraising_cfg_models.py @@ -4,61 +4,65 @@ 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 * +from app.lib_general import log, logging from app.models.common_field_schema import base_fields, default_num_bytes +# ### BEGIN ### API Fundraising Config Models ### Fundraising_Cfg_Base() ### class Fundraising_Cfg_Base(BaseModel): - log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL + log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL log.debug(locals()) id_random: Optional[str] = Field( - **base_fields['fundraising_cfg_id_random'], - alias = 'fundraising_cfg_id_random', + **base_fields['fundraising_id_random'], + alias = 'fundraising_id_random', ) id: Optional[int] = Field( - #alias = 'fundraising_cfg_id' + #alias = 'fundraising_id' ) + account_id_random: Optional[str] account_id: Optional[int] - order_thanks: Optional[str] - order_message: Optional[str] - message: Optional[str] + header_html: Optional[str] + start_html: Optional[str] + message_html: Optional[str] + end_html: Optional[str] + footer_html: Optional[str] + + order_thanks: Optional[str] # Is this needed here or at all? + order_message: Optional[str] # Is this needed here or at all? + message: Optional[str] # Is this needed here or at all? + + enable: Optional[bool] + + hide: Optional[bool] + priority: Optional[bool] + sort: Optional[int] + group: Optional[str] # Same or similar as file_purpose? + + created_on: Optional[datetime.datetime] = None + updated_on: Optional[datetime.datetime] = None _processed_at: datetime.datetime = PrivateAttr(default_factory=datetime.datetime.now) - #@validator('fundraising_cfg_id_random', always=True) - def fundraising_cfg_id_random_copy(cls, v, values, **kwargs): - log.setLevel(logging.WARNING) - log.debug(locals()) - - if values['id_random']: - return values['id_random'] - return None - @validator('id', always=True) def fundraising_cfg_id_lookup(cls, v, values, **kwargs): - log.setLevel(logging.WARNING) - log.debug(locals()) - - if values['id_random']: - log.debug(values['id_random']) - return redis_lookup_id_random(record_id_random=values['id_random'], table_name='fundraising_cfg') + 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='v_fundraising_cfg') # There is only a view 2022-11-18 return None @validator('account_id', always=True) def account_id_lookup(cls, v, values, **kwargs): - log.setLevel(logging.WARNING) - log.debug(locals()) - - if values['account_id_random']: - return redis_lookup_id_random(record_id_random=values['account_id_random'], table_name='account') + 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 - -#Fundraising_Cfg_Base.update_forward_refs() +# ### END ### API Fundraising Config Models ### Fundraising_Cfg_Base() ### diff --git a/app/models/fundraising_models.py b/app/models/fundraising_models.py new file mode 100644 index 0000000..60c3012 --- /dev/null +++ b/app/models/fundraising_models.py @@ -0,0 +1,81 @@ +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.product_models import Product_Base + + +# ### BEGIN ### API Fundraising Models ### Fundraising_Base() ### +class Fundraising_Base(BaseModel): + log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL + log.debug(locals()) + + id_random: Optional[str] = Field( + **base_fields['fundraising_id_random'], + alias = 'fundraising_id_random', + ) + id: Optional[int] = Field( + #alias = 'fundraising_id' + ) + + account_id_random: Optional[str] + account_id: Optional[int] + + title: Optional[str] + summary: Optional[str] + description: Optional[str] + + header_html: Optional[str] + start_html: Optional[str] + message_html: Optional[str] + end_html: Optional[str] + footer_html: Optional[str] + + enable: Optional[bool] + + hide: Optional[bool] + priority: Optional[bool] + sort: Optional[int] + group: Optional[str] # Same or similar as file_purpose? + + notes: Optional[str] + + created_on: Optional[datetime.datetime] = None + updated_on: Optional[datetime.datetime] = None + + # Including convenience data + # This is only for convenience. Probably going to keep unless it causes a problem. + # header_html: Optional[str] + # start_html: Optional[str] + + # Including other related objects + fundraising_cfg: Optional[Union[Fundraising_Cfg_Base, None]] + product_list: Optional[list[Product_Base]] + + _processed_at: datetime.datetime = PrivateAttr(default_factory=datetime.datetime.now) + + @validator('id', always=True) + def fundraising_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='v_fundraising_cfg') # There is only a view 2022-11-18 + 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 Fundraising Models ### Fundraising_Base() ### diff --git a/app/models/user_role_models.py b/app/models/user_role_models.py index 8203b51..1b7cbbb 100644 --- a/app/models/user_role_models.py +++ b/app/models/user_role_models.py @@ -6,8 +6,7 @@ from pydantic import BaseModel, EmailStr, Field, Json, PrivateAttr, ValidationEr from app.db_sql import redis_lookup_id_random from app.lib_general import log, logging -from .common_field_schema import base_fields, default_num_bytes -#from .user_models import User_Base +from app.models.common_field_schema import base_fields, default_num_bytes # ### BEGIN ### API User Role Models ### User_Role_Base() ### diff --git a/app/routers/fundraising.py b/app/routers/fundraising.py new file mode 100644 index 0000000..e56f4f8 --- /dev/null +++ b/app/routers/fundraising.py @@ -0,0 +1,83 @@ +import datetime, time +from fastapi import APIRouter, Body, Depends, Header, HTTPException, Query, Response, status +from pydantic import BaseModel, EmailStr, Field +from typing import Dict, List, Optional, Set, Union + +from app.lib_general import log, logging, secure_hash_string, verify_secure_hash_string, common_route_params, Common_Route_Params +from app.config import settings +from app.db_sql import sql_insert, sql_update, sql_insert_or_update, sql_select, sql_delete, redis_lookup_id_random + +from app.routers.api_crud import delete_obj_template, get_obj_template, get_obj_li_template, patch_obj_template, post_obj_template + +from app.methods.fundraising_methods import get_fundraising_rec_list, load_fundraising_obj +# from app.methods.fundraising_cfg_methods import load_fundraising_cfg_obj + +from app.models.fundraising_models import Fundraising_Base +from app.models.response_models import Resp_Body_Base, mk_resp + + +router = APIRouter() + + +# ### BEGIN ### API Event ### get_account_obj_fundraising_list() ### +# Updated 2021-12-13 +@router.get('/account/{account_id}/fundraising/list', response_model=Resp_Body_Base) +async def get_account_obj_fundraising_list( + account_id: str = Query(..., min_length=11, max_length=22), + + hidden: str = 'not_hidden', # hidden, not_hidden, all + priority: str = 'all', # priority, not_priority, all + + inc_account_cfg: bool = False, + inc_fundraising_cfg: bool = False, + inc_product: bool = False, + inc_product_list: bool = False, + + commons: Common_Route_Params = Depends(common_route_params), + ): + log.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL + log.debug(locals()) + + account_id = commons.x_account_id + + # Updated 2021-12-13 + if fundraising_rec_list_result := get_fundraising_rec_list( + account_id = account_id, + + enabled = commons.enabled, + hidden = hidden, + priority = priority, + + limit = commons.limit, + offset = commons.offset, + ): + fundraising_result_list = [] + for fundraising_rec in fundraising_rec_list_result: + if load_fundraising_result := load_fundraising_obj( + fundraising_id = fundraising_rec.get('fundraising_id', None), + + inc_fundraising_cfg = inc_fundraising_cfg, + inc_product_list = inc_product_list, + + enabled = commons.enabled, + limit = commons.limit, + offset = commons.offset, + + # exclude_unset = commons.exclude_unset, + # model_as_dict = model_as_dict, + ): + fundraising_result_list.append(load_fundraising_result) + else: + fundraising_result_list.append(None) + response_data = fundraising_result_list + elif isinstance(fundraising_rec_list_result, list) or fundraising_rec_list_result is None: # Empty list or None + log.info('No results') + return mk_resp(data=None, status_code=404, response=commons.response) # Not Found + else: + log.warning('Likely bad request') + return mk_resp(data=False, status_code=400, response=commons.response) # Bad Request + + log.debug(response_data) + + return mk_resp(data=response_data, response=commons.response) +# ### END ### API Event ### get_account_obj_fundraising_list() ###