class Recipe(db.Model): __tablename__ = 'recipe' id_ = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(50), nullable=False) url = db.Column( db.String(200)) # url of where the recipe was gotten (if applicable) creator_id = db.Column( db.Integer, db.ForeignKey('user.id_') ) # the creator of the recipe (used for editing permissions) creator = db.relationship("User", back_populates="recipes") recipe_lines = db.relationship("RecipeLine", back_populates='recipe', cascade="all, delete-orphan") grocery_lists = db.relationship("GroceryList", secondary=recipe_list_associations, back_populates="recipes") ai_list = db.relationship("GroceryList", back_populates="additional_ingredients") def __hash__(self): return hash(self.id_) def __eq__(self, other): return self.id_ == other.id_ def __repr__(self): return f"<Recipe '{self.name}' id_: {self.id_}>"
class CompiledList(db.Model): id = db.Column(db.Integer, primary_key=True) # the primary key date_created = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) name = db.Column(db.String(20), nullable=False, default="Unnamed List") # user created name, optional hex_name = db.Column(db.String(20), unique=True, nullable=False) # name for database #TODO use hex_id() function lines = db.relationship('CleanedLine', backref='list', lazy=True) # cleaned lines for the list recipes = db.relationship('RecipeList', backref='complist', lazy=True) # all recipes that are in the compiled list user_id = db.Column(db.ForeignKey('user.id'), nullable=False) # the id of the user who made the list def __repr__(self): return f"{self.name}"
class LineIngredientAssociations(db.Model): __tablename__='line_ingredient_associations' id_ = db.Column(db.Integer, primary_key=True) # separate because ingredient could appear more than once in a line ingredient_id = db.Column(db.ForeignKey('ingredient.id')) recipeline_id = db.Column(db.ForeignKey('recipe_line.id')) relevant_tokens = db.Column(db.String(), nullable=False) color_index = db.Column(db.Integer, nullable=False, default=0) # the index of the color for the frontend ingredient = db.relationship("Ingredient", back_populates='recipe_lines') recipe_line = db.relationship("RecipeLine", back_populates='ingredients') # TODO: Create a validator to confirm that the ingredient is on the recipe line def __repr__(self): return f"<Association of {self.ingredient} with {self.recipe_line} at {self.relevant_tokens}>"
class User(db.Model): __tablename__ = 'user' id_ = db.Column(db.Integer, primary_key=True) email = db.Column(db.String(100), nullable=False, unique=True) email_validated = db.Column(db.Boolean, nullable=False, default=False) hashed_password = db.Column(db.String(16), nullable=False) access_level = db.Column(db.Integer, nullable=False, default=0) recipes = db.relationship( "Recipe", back_populates="creator") # the recipes created by this user created_lists = db.relationship( "GroceryList", back_populates="creator", cascade="all, delete-orphan") # lists created by this user editable_lists = db.relationship("GroceryList", secondary=user_list_associations, back_populates="editors") # function to hash password def hash_password(self, password): self.hashed_password = pwd_context.encrypt(password) # function to verify password def verify_password(self, password): print(self.hashed_password) return pwd_context.verify(password, self.hashed_password) # generate a secure token for authentication def generate_auth_token(self, expiration=600): s = Serializer(current_app.config['SECRET_KEY'], expires_in=expiration) return s.dumps({'id': self.id}).decode("utf-8") # verify a token @staticmethod def verify_auth_token(token): s = Serializer(current_app.config['SECRET_KEY']) try: data = s.loads(token) except SignatureExpired: print("signature expired") return None # valid token, expired except BadSignature: print("invalid token") return None # invalid token user = User.query.get(data['id']) return user def __repr__(self): return f"<User {id} -- email:'{self.email}' access_level: {self.access_level}>"
class Ingredient(db.Model): __tablename__ = 'ingredient' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String, unique=True) # the actual name of the ingredient recipe_lines = db.relationship("LineIngredientAssociations", # lines where this ingredient appears. back_populates='ingredient') # validator to ensure that an ingredient is in the proper form (all lower case, no dashes or other symbols) @db.validates('name') def validate_name(self, key, address): if not address.islower(): raise ValueError("Ingredient must be in all lower case!") return address # equal function for comparing different instances of the same Ingredient def __eq__(self, other): return self.id == other.id and self.name == other.name # hash function to enable sets to eliminate duplicates def __hash__(self): return hash((self.id, self.name)) def __repr__(self): return f"<Ingredient '{self.name}'>"
class GroceryList(db.Model): __tablename__ = 'grocery_list' id_ = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(50), nullable=False) recipes = db.relationship("Recipe", secondary=recipe_list_associations, back_populates="grocery_lists") additional_ingredients_id = db.Column(db.Integer, db.ForeignKey("recipe.id_")) creator_id = db.Column(db.Integer, db.ForeignKey( "user.id_")) # the creator of the grocerylist. can add others to edit creator = db.relationship("User", back_populates="created_lists") editors = db.relationship( "User", # other users with permission to edit the grocery list secondary=user_list_associations, back_populates="editable_lists") additional_ingredients = db.relationship("Recipe", back_populates="ai_list", cascade="all, delete-orphan", single_parent=True) # clears the grocerylist (for PUT requests) without removing the "Additional Ingredients" recipe # CURRENTLY UNUSED def clear_grocerylist(self): print(self) additional_ingredients = Recipe.query \ .filter(Recipe.name == "Additional Ingredients", Recipe.grocery_lists.contains(self)).first() print(additional_ingredients) additional_ingredients.recipe_lines.clear() self.recipes.clear() self.recipes.append(additional_ingredients) db.session.commit() def create_additional_ingredients_recipe(self): additional_ingredients_recipe = Recipe(name="Additional Ingredients", creator_id=self.creator_id) self.additional_ingredients = additional_ingredients_recipe self.recipes.append(additional_ingredients_recipe) db.session.add(additional_ingredients_recipe) print(additional_ingredients_recipe) db.session.commit() def __repr__(self): return f"<GroceryList '{self.name}'>"
class RecipeLine(db.Model): __tablename__ = 'recipe_line' id_ = db.Column(db.Integer, primary_key=True) text = db.Column(db.String, nullable=False) # the text of the line recipe_id = db.Column(db.Integer, db.ForeignKey('recipe.id_')) recipe = db.relationship("Recipe", back_populates="recipe_lines") ingredients = db.relationship("LineIngredientAssociations", back_populates="recipe_line") def __hash__(self): return hash(self.id_) def __eq__(self, other): return self.id_ == other.id_ def __repr__(self): print(self.recipe) print(self.text) return f"<RecipeLine in '{self.recipe}' -- '{self.text[0:20]}...' >"
class RawLine(db.Model): # TODO: refactor so there are less 'id' labels id = db.Column(db.Integer, primary_key=True) # the primary key hex_id = db.Column(db.String(8), default=get_hex_id, nullable=False, unique=True) # hex identifier for requests full_text = db.Column(db.String(100), nullable=False) # the text of the line list_id = db.Column(db.Integer, db.ForeignKey('recipe_list.id')) # the id of the list for the line text_to_colors = db.Column(db.String) cleaned_lines = db.relationship('CleanedLine', secondary=line_assocs, backref=db.backref('raw_lines', lazy='dynamic')) def __repr__(self): return f"{self.full_text}"
class RecipeList(db.Model): id = db.Column(db.Integer, primary_key=True) # the primary key name = db.Column(db.String, nullable=False) # name for recipe (from url) hex_name = db.Column(db.String(20), unique=True, nullable=False) # name for database #TODO use hex_id() function hex_color = db.Column(db.String(6), nullable=False) # randomly generated color for use in lists image_file = db.Column(db.String(20), nullable=False, default='default.jpg') # image for list recipe_url = db.Column(db.String(200)) compiled_list = db.Column(db.ForeignKey('compiled_list.id')) # the id of the compiled list lines = db.relationship('RawLine', backref='rlist', lazy=True) # the lines of the list def __repr__(self): return f"{self.hex_name} -- {self.recipe_url}"
class User(db.Model, UserMixin): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(20), unique=True, nullable=False) email = db.Column(db.String(120), unique=True, nullable=False) email_validated = db.Column(db.Boolean, default=False) password = db.Column(db.String(60), nullable=False) temporary = db.Column(db.Boolean, default=False) # determines if user account is temporary (for guest users) checklists = db.relationship('CompiledList', backref='user', lazy=True) # the user's grocery lists def __repr__(self): return f"(User('{self.username}', '{self.email}'" def get_reset_token(self, expires_sec=1800): s = TimedSerializer(current_app.config['SECRET_KEY'], expires_sec) return s.dumps({'user_id': self.id}).decode('utf-8') # TODO: combine this with get_reset_token def get_validate_token(self): s = Serializer(current_app.config['SECRET_KEY']) return s.dumps({'user_id': self.id}).decode('utf-8') @staticmethod def verify_reset_token(token): s = TimedSerializer(current_app.config['SECRET_KEY']) try: user_id = s.loads(token)['user_id'] except: return None return User.query.get(user_id) # TODO: combine this with verify_reset_token @staticmethod def verify_email_token(token): s = Serializer(current_app.config['SECRET_KEY']) try: user_id = s.loads(token)['user_id'] except: return None return User.query.get(user_id)