<repo_name>Artyko/Cheatmeal<file_sep>/test_DB.py
import pytest
from datetime import datetime, timedelta
from app import app, db
from app.models import User, Recipe


class TestBD:
    def setup(self):
        app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://'
        db.create_all()

    def teardown(self):
        db.session.remove()
        db.drop_all()

    def test_password_hashing(self):
        u = User(username='susan')
        u.set_password('cat')
        assert u.check_password('dog') is False
        assert u.check_password('cat') is True

    def test_add_users(self):
        user_1 = User(username='john', email='<EMAIL>')
        user_2 = User(username='susan', email='<EMAIL>')
        db.session.add(user_1)
        db.session.add(user_2)
        db.session.commit()
        assert user_1.recipe.all() == []
        assert user_2.recipe.all() == []

    def test_add_and_remove_recipe_to_user(self):
        user_1 = User(username='john', email='<EMAIL>')
        db.session.add(user_1)
        db.session.commit()
        recipe_1 = Recipe(title='Strawberry pie')
        user_1.add_recipe(recipe_1)
        assert user_1.recipe.count() == 1

        user_1.remove_recipe(recipe_1)
        db.session.commit()
        assert user_1.recipe.count() == 0





<file_sep>/app/main/forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField, TextAreaField
from wtforms.validators import ValidationError, DataRequired, EqualTo, Length
from flask_babel import _, lazy_gettext as _l
from app.models import User


class EditProfileForm(FlaskForm):
    username = StringField(_l('Username'), validators=[DataRequired()])
    submit = SubmitField(_l('Submit'))

    def __init__(self, original_username, *args, **kwargs):
        super(EditProfileForm, self).__init__(*args, **kwargs)
        self.original_username = original_username

    def validate_username(self, username):
        if username.data != self.original_username:
            user = User.query.filter_by(username=self.username.date)
            if user is not None:
                raise ValidationError('Please use a different username')


class RecipeForm(FlaskForm):
    title = StringField(_l('Recipe name'), validators=[DataRequired()])
    ingredients = TextAreaField(_l('Ingredient name'))
    description = TextAreaField(_l('Describe your recipe'))
    submit = SubmitField(_l('Submit'))


class EmptyForm(FlaskForm):
    submit = SubmitField('Submit')
<file_sep>/app/models.py
from datetime import datetime
from app import db, login
from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import UserMixin

recipe_ingredient = db.Table('recipe_ingredient',
                             db.Column('recipe_id', db.Integer, db.ForeignKey('recipe.id')),
                             db.Column('measure_id', db.Integer, db.ForeignKey('measure.id')),
                             db.Column('measure_qty_id', db.Integer, db.ForeignKey('measure_qty.id')),
                             db.Column('ingredient_id', db.Integer, db.ForeignKey('ingredient.id')))


class User(UserMixin, db.Model):
    __tablename__ = 'user'

    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), index=True, unique=True)
    email = db.Column(db.String(120), index=True, unique=True)
    password_hash = db.Column(db.String(128))
    recipe = db.relationship('Recipe', backref='user', lazy='dynamic')

    def __repr__(self):
        return '<User {}>'.format(self.username)

    def set_password(self, password):
        self.password_hash = generate_password_hash(password)

    def check_password(self, password):
        return check_password_hash(self.password_hash, password)

    @login.user_loader
    def load_user(id):
        return User.query.get(int(id))

    def show_recipes(self):
        return Recipe.query.filter_by(user_id=self.id)

    def recipe_exist(self, recipe):
        return self.recipe.filter(recipe_ingredient.c.recipe_id == recipe.id).count() > 0

    def add_recipe(self, recipe):
        if not self.recipe_exist(recipe):
            self.recipe.append(recipe)

    def remove_recipe(self, recipe):
        if not self.recipe_exist(recipe):
            self.recipe.remove(recipe)


class Recipe(db.Model):
    __tablename__ = 'recipe'
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(120))
    description = db.Column(db.String(840))
    timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))

    recipes = db.relationship('Ingredient', secondary=recipe_ingredient,
                              primaryjoin=(recipe_ingredient.c.recipe_id == id),
                              secondaryjoin=(recipe_ingredient.c.ingredient_id == id),
                              backref=db.backref('ingredient', lazy='dynamic'), cascade="all")

    def __repr__(self):
        return f'<Recipe {self.title}>'


class Measure(db.Model):
    __tablename__ = 'measure'
    id = db.Column(db.Integer, primary_key=True)
    description = db.Column(db.String(120))
    recipe_ingredient = db.relationship('Recipe', secondary=recipe_ingredient,
                                        backref=db.backref('recipes_names', lazy='dynamic'), cascade="all")


class MeasureQty(db.Model):
    __tablename__ = 'measure_qty'
    id = db.Column(db.Integer, primary_key=True)
    amount = db.Column(db.Integer)
    recipe_ingredient = db.relationship('Recipe', secondary=recipe_ingredient,
                                        backref=db.backref('recipes_1', lazy='dynamic'), cascade="all")


class Ingredient(db.Model):
    __tablename__ = 'ingredient'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(120))
    calories = db.Column(db.Integer)
    recipe_ingredient = db.relationship('Recipe', secondary=recipe_ingredient,
                                        backref=db.backref('recipes_2', lazy='dynamic'), cascade="all")
<file_sep>/migrations/versions/51f732aa5466_.py
"""empty message

Revision ID: <KEY>
Revises: <KEY>
Create Date: 2020-12-25 21:47:27.090205

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '<KEY>'
down_revision = '<KEY>'
branch_labels = None
depends_on = None


def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.create_table('ingredient',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('name', sa.String(length=120), nullable=True),
    sa.Column('calories', sa.Integer(), nullable=True),
    sa.PrimaryKeyConstraint('id')
    )
    op.create_table('measure',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('description', sa.String(length=120), nullable=True),
    sa.PrimaryKeyConstraint('id')
    )
    op.create_table('measure_qty',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('amount', sa.Integer(), nullable=True),
    sa.PrimaryKeyConstraint('id')
    )
    op.create_table('recipe',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('title', sa.String(length=120), nullable=True),
    sa.Column('description', sa.String(length=840), nullable=True),
    sa.Column('timestamp', sa.DateTime(), nullable=True),
    sa.Column('user_id', sa.Integer(), nullable=True),
    sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
    sa.PrimaryKeyConstraint('id')
    )
    op.create_index(op.f('ix_recipe_timestamp'), 'recipe', ['timestamp'], unique=False)
    op.create_table('recipe_ingredient',
    sa.Column('recipe_id', sa.Integer(), nullable=True),
    sa.Column('measure_id', sa.Integer(), nullable=True),
    sa.Column('measure_qty_id', sa.Integer(), nullable=True),
    sa.Column('ingredient_id', sa.Integer(), nullable=True),
    sa.ForeignKeyConstraint(['ingredient_id'], ['ingredient.id'], ),
    sa.ForeignKeyConstraint(['measure_id'], ['measure.id'], ),
    sa.ForeignKeyConstraint(['measure_qty_id'], ['measure_qty.id'], ),
    sa.ForeignKeyConstraint(['recipe_id'], ['recipe.id'], )
    )
    # ### end Alembic commands ###


def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_table('recipe_ingredient')
    op.drop_index(op.f('ix_recipe_timestamp'), table_name='recipe')
    op.drop_table('recipe')
    op.drop_table('measure_qty')
    op.drop_table('measure')
    op.drop_table('ingredient')
    # ### end Alembic commands ###
<file_sep>/README.md
# Cheatmeal
A cookbook for awesome recipes
<file_sep>/app/main/routes.py
from datetime import datetime
from flask import render_template, flash, redirect, url_for, request, g, current_app
from flask_login import current_user, login_required
from flask_babel import _, get_locale
from app import db
from app.main.forms import EditProfileForm, RecipeForm
from app.models import User, Recipe
from app.main import bp


@bp.before_request
def before_request():
    if current_user.is_authenticated:
        current_user.last_seen = datetime.utcnow()
        db.session.commit()
    g.locale = str(get_locale())


@bp.route('/', methods=['GET', 'POST'])
@bp.route('/index', methods=['GET', 'POST'])
@login_required
def index():
    form = RecipeForm()
    if form.validate_on_submit():
        recipe = Recipe(title=form.title.data,
                        description=form.description.data,
                        user_id=current_user.id)
        db.session.add(recipe)
        db.session.commit()
        flash(_('Your recipe is now live!'))
        return redirect(url_for('main.index'))
    page = request.args.get('page', 1, type=int)
    recipes = current_user.show_recipes().paginate(
        page, current_app.config['RECIPES_PER_PAGE'], False)
    next_url = url_for('main.index', page=recipes.next_num) \
        if recipes.has_next else None
    prev_url = url_for('main.index', page=recipes.prev_num) \
        if recipes.has_prev else None
    return render_template('index.html', title='Home',
                           form=form, recipes=recipes.items,
                           next_url=next_url, prev_url=prev_url)


@bp.route('/user/<username>')
@login_required
def user(username):
    user = User.query.filter_by(username=username).first_or_404()
    page = request.args.get('page', 1, type=int)
    recipes = user.recipe.order_by(Recipe.timestamp.desc()).paginate(
        page, current_app.config['RECIPES_PER_PAGE'], False)
    next_url = url_for('main.user', page=recipes.next_num) \
        if recipes.has_next else None
    prev_url = url_for('main.user', page=recipes.prev_num) \
        if recipes.has_prev else None
    return render_template('user.html', user=user, recipes=recipes.items,
                           next_url=next_url, prev_url=prev_url)


@bp.route('/edit_profile', methods=['GET', 'POST'])
@login_required
def edit_profile():
    form = EditProfileForm(current_user.username)
    if form.validate_on_submit():
        current_user.username = form.username.data
        db.session.commit()
        flash(_('Your changes have been saved.'))
        return redirect(url_for('main.edit_profile'))
    elif request.method == 'GET':
        form.username.data = current_user.username
    return render_template('edit_profile.html', title=_('Edit Profile'),
                           form=form)


@bp.route('/explore')
@login_required
def explore():
    page = request.args.get('page', 1, type=int)
    recipes = Recipe.query.order_by(Recipe.timestamp.desc()).paginate(
        page, current_app.config['RECIPES_PER_PAGE'], False)
    next_url = url_for('main.index', page=recipes.next_num) \
        if recipes.has_next else None
    prev_url = url_for('main.index', page=recipes.prev_num) \
        if recipes.has_prev else None
    return render_template('index.html', title=_('Explore'), recipes=recipes.items,
                           next_url=next_url, prev_url=prev_url)
<file_sep>/requirements.txt
alembic==1.4.3
attrs==20.3.0
Babel==2.9.0
click==7.1.2
dominate==2.6.0
Flask==1.1.2
Flask-Babel==2.0.0
Flask-Bootstrap==3.3.7.1
Flask-Login==0.5.0
Flask-Migrate==2.5.3
Flask-Moment==0.11.0
Flask-SQLAlchemy==2.4.4
Flask-WTF==0.14.3
importlib-metadata==3.4.0
iniconfig==1.1.1
itsdangerous==1.1.0
Jinja2==2.11.2
Mako==1.1.3
MarkupSafe==1.1.1
packaging==20.8
pluggy==0.13.1
py==1.10.0
pyparsing==2.4.7
pytest==6.2.2
python-dateutil==2.8.1
python-editor==1.0.4
pytz==2021.1
six==1.15.0
SQLAlchemy==1.3.22
toml==0.10.2
typing-extensions==3.7.4.3
visitor==0.1.3
Werkzeug==1.0.1
WTForms==2.3.3
zipp==3.4.0

python-dotenv~=0.15.0