class RecipeCuisine(db.Model): """Association Table for many-to-many relationships for recipes and cuisines""" __tablename__ = "recipe_cuisines" __table_args__ = (db.UniqueConstraint("recipe_id", "cuisine_id", name="_recipe_cuisine_constraint"), ) """Columns""" # Parent recipe_id = db.Column(db.Integer, db.ForeignKey("recipes.id"), primary_key=True, nullable=False) # Child cuisine_id = db.Column(db.Integer, db.ForeignKey("cuisines.id"), primary_key=True, nullable=False) """Relationships""" # Parent recipe = db.relationship("Recipe", back_populates="recipe_cuisines") # Child cuisine = db.relationship("Cuisine", back_populates="recipes", lazy="joined") def __init__(self, cuisine=None, recipe=None): """Called when appending Cuisine objects to Recipe objects, child used as first arg""" self.cuisine = cuisine self.recipe = recipe def __repr__(self): return f"<RecipeCuisine - {self.cuisine.name}/{self.recipe.name}>"
class UserRole(db.Model): __tablename__ = "user_roles" __table_args__ = (db.UniqueConstraint("user_id", "role_id", name="_user_role_constraint"), ) """Primary Keys""" # Parent user_id = db.Column(db.Integer, db.ForeignKey("users.id"), primary_key=True, nullable=False) # Child role_id = db.Column(db.Integer, db.ForeignKey("roles.id"), primary_key=True, nullable=False) """Relationships""" # Parent user = db.relationship("User", back_populates="user_roles") # Child role = db.relationship("Role", back_populates="users", lazy="joined") def __init__(self, role=None, user=None): self.role = role self.user = user def __repr__(self): return f"<UserRole - {self.user.username} has role {self.role.name}>"
class RecipeDietType(db.Model): __tablename__ = "recipe_diet_types" __table_args__ = (db.UniqueConstraint( "recipe_id", "diet_type_id", name="_recipe_diet_type_constraint"), ) """Primary Keys""" # Parent recipe_id = db.Column(db.Integer, db.ForeignKey("recipes.id"), primary_key=True, nullable=False) # Child diet_type_id = db.Column(db.Integer, db.ForeignKey("diet_types.id"), primary_key=True, nullable=False) """Relationships""" # Parent recipe = db.relationship("Recipe", back_populates="recipe_diet_types") # Child diet_type = db.relationship("DietType", back_populates="recipes", lazy="joined") def __init__(self, diet_type=None, recipe=None): """Called when appending DietType objects to Recipe objects, child used as first arg""" self.diet_type = diet_type self.recipe = recipe def __repr__(self): return f"<RecipeDietType - {self.recipe.name} / {self.diet_type.name}>"
class RecipeTag(db.Model): __tablename__ = "recipe_tags" __table_args__ = (db.UniqueConstraint("recipe_id", "tag_id", name="_recipe_tag_constraint"), ) """Columns""" recipe_id = db.Column(db.Integer, db.ForeignKey("recipes.id"), primary_key=True, nullable=False) tag_id = db.Column(db.Integer, db.ForeignKey("tags.id"), primary_key=True, nullable=False) """Relationships""" recipe = db.relationship("Recipe", back_populates="tags") tag = db.relationship("Tag", back_populates="recipes") def __repr__(self): return f"<RecipeTag {self.tag.name} on {self.recipe.name}>"
class ShoppingList(db.Model): __tablename__ = "shopping_lists" """Primary Keys""" household_id = db.Column(db.Integer, db.ForeignKey("households.id"), primary_key=True, nullable=False) ingredient_id = db.Column(db.Integer, db.ForeignKey("ingredients.id"), primary_key=True, nullable=False) """Relationships""" ingredient = db.relationship("Ingredient", lazy="joined") household = db.relationship("Household", back_populates="shopping_list_items") """Extra Data""" time_created = db.Column(db.DateTime, default=func.now(), server_default=func.now()) time_updated = db.Column(db.DateTime, onupdate=func.now()) time_removed = db.Column(db.DateTime) still_needed = db.Column(db.Boolean, default=True) def __init__(self, ingredient=None, household=None): self.ingredient = ingredient self.household = household
class RecipeIngredient(db.Model): """Association object for recipe ingredients""" __tablename__ = "recipe_ingredients" """ This constraint was causing issues on recipe import for recipes that had (for example) "lime" and "lime seasoning". Both were assigned the same Spoonacular ID, which caused db issues while assigning recipe ingredients. Removing for now. """ # __table_args__ = ( # db.UniqueConstraint( # "recipe_id", # "ingredient_id", # "original_string", # name="_recipe_ingredient_uix", # ), # ) """Primary Keys""" id = db.Column(db.Integer, primary_key=True, autoincrement=True) recipe_id = db.Column( db.Integer, db.ForeignKey("recipes.id"), primary_key=False, nullable=False ) ingredient_id = db.Column( db.Integer, db.ForeignKey("ingredients.id"), primary_key=False, nullable=False ) """Original Strings In the Spoonacular response, it grabs the original ingredient string from the recipe, which can contain special formatting. For display purposes, this is stored in this table """ original_string = db.Column(db.String) """Quantities""" amount = db.Column(db.Float) unit = db.Column(db.String) us_amount = db.Column(db.Float) us_unit_short = db.Column(db.String) us_unit_long = db.Column(db.String) metric_amount = db.Column(db.Float) metric_unit_short = db.Column(db.String) metric_unit_long = db.Column(db.String) """Nutrition""" caffeine = db.Column(db.Float, default=0.0) calcium = db.Column(db.Float, default=0.0) calories = db.Column(db.Float, default=0.0) carbohydrates = db.Column(db.Float, default=0.0) cholesterol = db.Column(db.Float, default=0.0) choline = db.Column(db.Float, default=0.0) copper = db.Column(db.Float, default=0.0) fat = db.Column(db.Float, default=0.0) fiber = db.Column(db.Float, default=0.0) folate = db.Column(db.Float, default=0.0) folic_acid = db.Column(db.Float, default=0.0) iron = db.Column(db.Float, default=0.0) magnesium = db.Column(db.Float, default=0.0) manganese = db.Column(db.Float, default=0.0) mono_unsaturated_fat = db.Column(db.Float, default=0.0) net_carbohydrates = db.Column(db.Float, default=0.0) phosphorous = db.Column(db.Float, default=0.0) poly_unsaturated_fat = db.Column(db.Float, default=0.0) potassium = db.Column(db.Float, default=0.0) protein = db.Column(db.Float, default=0.0) saturated_fat = db.Column(db.Float, default=0.0) selenium = db.Column(db.Float, default=0.0) sodium = db.Column(db.Float, default=0.0) sugar = db.Column(db.Float, default=0.0) vitamin_a = db.Column(db.Float, default=0.0) vitamin_b1 = db.Column(db.Float, default=0.0) vitamin_b12 = db.Column(db.Float, default=0.0) vitamin_b2 = db.Column(db.Float, default=0.0) vitamin_b3 = db.Column(db.Float, default=0.0) vitamin_b5 = db.Column(db.Float, default=0.0) vitamin_b6 = db.Column(db.Float, default=0.0) vitamin_c = db.Column(db.Float, default=0.0) vitamin_d = db.Column(db.Float, default=0.0) vitamin_e = db.Column(db.Float, default=0.0) vitamin_k = db.Column(db.Float, default=0.0) zinc = db.Column(db.Float, default=0.0) """Glycemic Information""" glycemic_index = db.Column(db.Float, default=0.0) glycemic_load = db.Column(db.Float, default=0.0) """Cost Information""" estimated_cost_cents = db.Column(db.Float, default=0.0) """Relationships""" recipe = db.relationship("Recipe", back_populates="ingredients") ingredient = db.relationship("Ingredient", back_populates="recipes") def __repr__(self): return f"<RecipeIngredient {self.ingredient.name} in {self.recipe.name}>" def __setitem__(self, key, value): setattr(self, key, value) def __getitem__(self, key): return getattr(self, key)
class User(PkModel, TimestampMixin, LookupByNameMixin): """Basic user model""" __tablename__ = "users" """Columns""" # id # time_created # time_updated username = db.Column(db.String, unique=True, nullable=False) email = db.Column(db.String, unique=True, nullable=False) _password = db.Column("password", db.String, nullable=False) active = db.Column(db.Boolean, default=True) first_name = db.Column(db.String) last_name = db.Column(db.String) birthday = db.Column(db.Date) height_inches = db.Column(db.Float) weight_lbs = db.Column(db.Float) gender = db.Column(db.Enum("male", "female", name="gender_enum")) """Relationships""" tags = relationship("Tag", back_populates="user") household = relationship("Household", back_populates="users") household_id = reference_col("households") user_roles = relationship("UserRole", back_populates="user", cascade="all, delete-orphan") roles = association_proxy("user_roles", "role") @hybrid_property def password(self): return self._password @password.setter def password(self, value): self._password = pwd_context.hash(value) # Register a callback function that takes whatever object is passed in as the # identity when creating JWTs and converts it to a JSON serializable format. @staticmethod @jwt_ext.user_identity_loader def user_identity_lookup(user): return { "id": user.id, "username": user.username, "email": user.email, "household_id": user.household_id, } # Register a callback function that loads a user from your database whenever # a protected route is accessed. This should return any python object on a # successful lookup, or None if the lookup failed for any reason (for example # if the user has been deleted from the database). @staticmethod @jwt_ext.user_lookup_loader def user_lookup_callback(_jwt_header, jwt_data): identity = jwt_data["sub"] return User.query.filter_by(id=identity["id"]).one_or_none() def has_role(self, role): return role in self.roles def __init__(self, *args, **kwargs): super().__init__(**kwargs) if len(self.roles) == 0: # TODO add default roles! pass def __repr__(self): return "<User %s>" % self.username def get_access_token(self): jwt = create_access_token(identity=self, additional_claims=self.additional_claims) return jwt def get_refresh_token(self): jwt = create_refresh_token(identity=self, additional_claims=self.additional_claims) return jwt @property def additional_claims(self): return {"roles": [r.name for r in self.roles]} @property def age(self) -> int: if self.birthday is None: return None today = date.today() age = (today.year - self.birthday.year - ((today.month, today.day) < (self.birthday.month, self.birthday.day))) return age def get_password_reset_token(self, expires_in: int = 3600): return encode_jwt( { "reset_password": self.id, "exp": time() + expires_in }, current_app.config["SECRET_KEY"], algorithm=current_app.config["JWT_ALGORITHM"], ) @classmethod def verify_password_reset_token(cls, token): user = None try: token_data = decode_jwt( token, current_app.config["SECRET_KEY"], algorithms=[current_app.config["JWT_ALGORITHM"]], ) user_id = token_data["reset_password"] user = cls.get_by_id(user_id) except InvalidTokenError as ite: current_app.logger.error( f"Someone is trying to use an invalid token to reset a user password!\n{str(ite)}" ) return user