From f670c00a91774e7d8ea9d1469ea355ab1043448e Mon Sep 17 00:00:00 2001 From: Mark Silvis Date: Tue, 5 Dec 2017 08:10:29 -0500 Subject: [PATCH 1/6] includes pitt pantry status and eagerness --- pittgrub/db/__init__.py | 7 +++---- pittgrub/db/schema.py | 19 ++++++++++++++++++- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/pittgrub/db/__init__.py b/pittgrub/db/__init__.py index 95cdcf4..ede1e66 100644 --- a/pittgrub/db/__init__.py +++ b/pittgrub/db/__init__.py @@ -5,7 +5,6 @@ from sqlalchemy import create_engine from sqlalchemy.orm import scoped_session, sessionmaker -from sqlalchemy.pool import NullPool from .base import Entity, ReferralStatus, UserStatus, health_check from .default import DEFAULTS @@ -68,7 +67,7 @@ }) def __bulk_insert(engine, data: Dict[str, List[Tuple[Any]]]): - schema.Base.metadata.create_all(bind=engine) + schema.Base.metadata.create_all(bind=engine) for entity, values in data.items(): # get class of entity cls = getattr(sys.modules[__name__], entity) @@ -94,10 +93,10 @@ def init(username: str, password: str, url: str, database: str, engine = create_engine(f"mysql+pymysql://{username}:{password}" f"@{url}/{database}{params}", convert_unicode=True, echo=echo, - poolclass=NullPool) + pool_recycle=1800) session = scoped_session(sessionmaker(bind=engine)) print('Inserting default data') __bulk_insert(engine, DEFAULTS) # add default data - if generate: + if generate: print('Generating test data') __bulk_insert(engine, TEST_DATA) # add test data if generate flag is set to true diff --git a/pittgrub/db/schema.py b/pittgrub/db/schema.py index e5f94c8..6799d2a 100644 --- a/pittgrub/db/schema.py +++ b/pittgrub/db/schema.py @@ -33,6 +33,8 @@ class User(Base, Entity): admin = Column('admin', BOOLEAN, nullable=False, default=False) expo_token = Column('expo_token', VARCHAR(255), nullable=True) login_count = Column('login_count', INT, nullable=False) + pitt_pantry = Column('pitt_pantry', BOOLEAN, nullable=False, default=False) + eagerness = Column('eagerness', INT, nullable=False, default=3) # mappings food_preferences = association_proxy('_user_foodpreferences', 'food_preference') @@ -42,7 +44,8 @@ class User(Base, Entity): def __init__(self, id: int=None, email: str=None, password: str=None, status: UserStatus=None, active: bool=False, disabled: bool=False, - admin: bool=False, login_count: int=0, expo_token: str=None): + admin: bool=False, login_count: int=0, expo_token: str=None, + pitt_pantry: bool=False, eagerness: int=3): self.id = id self.created = datetime.datetime.utcnow() self.email = email @@ -53,6 +56,8 @@ def __init__(self, id: int=None, email: str=None, password: str=None, self.admin = admin self.login_count = login_count self.expo_token = expo_token + self.pitt_pantry = pitt_pantry + self.eagerness = eagerness @property def valid(self): @@ -137,6 +142,18 @@ def make_admin(self): db.session.commit() db.session.refresh(self) + def update_eagerness(self, value: int): + assert 0 < value + self.eagerness = value + db.session.commit() + db.session.refresh(self) + + def set_pitt_pantry(self, status: bool): + assert status is not None + self.pitt_pantry = status + db.session.commit() + db.session.refresh(self) + def json(cls, deep: bool=True) -> Dict[str, Any]: json = dict( id=cls.id, From 08ebc9e1eff23b13ea32809f217fdff2a16ad6df Mon Sep 17 00:00:00 2001 From: Mark Silvis Date: Tue, 5 Dec 2017 08:14:18 -0500 Subject: [PATCH 2/6] temporarily disables user endpoints until superuser access control is enabled --- pittgrub/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pittgrub/app.py b/pittgrub/app.py index 86ec27a..020d7bd 100644 --- a/pittgrub/app.py +++ b/pittgrub/app.py @@ -63,8 +63,8 @@ def __init__(self, debug: bool, image_store: ImageStore, static_path: str=None, endpoints = [ (r"/(/*)", MainHandler), # index (r"/health(/*)", HealthHandler), # check status - (r'/users(/*)', UserHandler), # all users - (r'/users/(\d+/*)', UserHandler), # single user + # (r'/users(/*)', UserHandler), # all users + # (r'/users/(\d+/*)', UserHandler), # single user (r'/users/activate(/*)', UserVerificationHandler), # user activation (r'/users/preferences(/*)', UserPreferenceHandler), # user preferences (food, etc) (r'/users/admin(/*)', AdminHandler), # make user admin From 2b32771508c13d8fdf23c2995ae23bb34e9ec954 Mon Sep 17 00:00:00 2001 From: Mark Silvis Date: Tue, 5 Dec 2017 08:24:44 -0500 Subject: [PATCH 3/6] separates json serializable representation into essential (account info) and non-essential (user settings) --- pittgrub/db/schema.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pittgrub/db/schema.py b/pittgrub/db/schema.py index 6799d2a..a3b3691 100644 --- a/pittgrub/db/schema.py +++ b/pittgrub/db/schema.py @@ -154,6 +154,21 @@ def set_pitt_pantry(self, status: bool): db.session.commit() db.session.refresh(self) + def json_info(cls) -> Dict[str, Union[bool, int, str]]: + """Get json serializable representation of account related info""" + return dict( + id=cls.id, + active=cls.active, + admin=cls.admin, + status=cls.status.name) + + def json_settings(cls) -> Dict[str, Any]: + """Get json serializable representation of user settings""" + return dict( + eagerness=cls.eagerness, + pantry=cls.pitt_pantry, + food_preferences=[f.json() for f in cls.food_preferences]) + def json(cls, deep: bool=True) -> Dict[str, Any]: json = dict( id=cls.id, From dbe6d054fe6e47950002946f682225f3d22771a5 Mon Sep 17 00:00:00 2001 From: Mark Silvis Date: Tue, 5 Dec 2017 08:35:32 -0500 Subject: [PATCH 4/6] includes separate endpoint and handler for general user settings --- pittgrub/app.py | 4 +++- pittgrub/handlers/user.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/pittgrub/app.py b/pittgrub/app.py index 020d7bd..7fbb26e 100644 --- a/pittgrub/app.py +++ b/pittgrub/app.py @@ -24,7 +24,8 @@ ) from handlers.user import ( UserHandler, UserVerificationHandler, UserPreferenceHandler, - UserPasswordHandler, UserPasswordResetHandler + UserPasswordHandler, UserPasswordResetHandler, + UserSettingsHandler ) from handlers.notifications import NotificationHandler from handlers.events import EventImageHandler, EventTestHandler @@ -67,6 +68,7 @@ def __init__(self, debug: bool, image_store: ImageStore, static_path: str=None, # (r'/users/(\d+/*)', UserHandler), # single user (r'/users/activate(/*)', UserVerificationHandler), # user activation (r'/users/preferences(/*)', UserPreferenceHandler), # user preferences (food, etc) + (r'/users/settings(/*)', UserSettingsHandler), # user settings (food prefs, pantry, etc) (r'/users/admin(/*)', AdminHandler), # make user admin (r'/notifications(/*)', NotificationHandler), # handle notifications (r'/token(/*)', NotificationTokenHandler), # add notification token diff --git a/pittgrub/handlers/user.py b/pittgrub/handlers/user.py index 4a42247..40d1034 100644 --- a/pittgrub/handlers/user.py +++ b/pittgrub/handlers/user.py @@ -105,6 +105,39 @@ def post(self, path): self.write_error(400, 'Missing fields') +class UserSettingsHandler(SecureHandler): + def get(self, path): + # check token + user_id = self.get_user_id() + if user_id: + user = User.get_by_id(user_id) + settings = user.json_settings() + self.success(payload=Payload(settings)) + else: + self.write_error(403, 'Authentication is required') + + def post(self, path): + user_id = self.get_user_id() + user = User.get_by_id(user_id) + if user is not None: + # decode json + data = json_decode(self.request.body) + logging.info(f'Updating settings for user {user_id}, settings {data}') + if 'food_preferences' in data: + # ensure preference ids are legit + preference_ids = [pref.id for pref in FoodPreference.get_all()] + if all(pref in preference_ids for pref in data['food_preferences']): + UserFoodPreference.update(user_id, preference_ids) + else: + fields = ", ".join(set(data['food_preferences'])-preference_ids) + self.write_error(401, f'Food preferences not foudn: {fields}') + if 'pantry' in data: + user.set_pitt_pantry(data['pantry']) + if 'eagernes' in data: + user.update_eagerness(data['eagerness']) + self.success(status=204) + + class UserPreferenceHandler(SecureHandler): def get(self, path): # check token From f5773497ded651e102149141d73fb815895c4c60 Mon Sep 17 00:00:00 2001 From: Mark Silvis Date: Tue, 5 Dec 2017 09:11:46 -0500 Subject: [PATCH 5/6] fixes typo --- pittgrub/handlers/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pittgrub/handlers/user.py b/pittgrub/handlers/user.py index 40d1034..d6cfe68 100644 --- a/pittgrub/handlers/user.py +++ b/pittgrub/handlers/user.py @@ -133,7 +133,7 @@ def post(self, path): self.write_error(401, f'Food preferences not foudn: {fields}') if 'pantry' in data: user.set_pitt_pantry(data['pantry']) - if 'eagernes' in data: + if 'eagerness' in data: user.update_eagerness(data['eagerness']) self.success(status=204) From fc602a45e01cbe198ac8c8add23993fccf4cffca Mon Sep 17 00:00:00 2001 From: Mark Silvis Date: Tue, 5 Dec 2017 09:28:53 -0500 Subject: [PATCH 6/6] temporarily includes user settings information in user json (this should be separated by functionality) --- pittgrub/db/schema.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pittgrub/db/schema.py b/pittgrub/db/schema.py index a3b3691..4a5d869 100644 --- a/pittgrub/db/schema.py +++ b/pittgrub/db/schema.py @@ -175,7 +175,9 @@ def json(cls, deep: bool=True) -> Dict[str, Any]: email=cls.email, active=cls.active, admin=cls.admin, - status=cls.status.name + status=cls.status.name, + eagerness=cls.eagerness, + pantry=cls.pitt_pantry ) if deep: json['food_preferences'] = [f.json() for f in cls.food_preferences]