<file_sep>import pytest
from tests.base import test_app

from cmm_app.models import User


# def test_user_password_correct():
#     user = User(email='<EMAIL>')
#     user.set_password('<PASSWORD>')

#     assert user.check_password('<PASSWORD>')


# def test_user_password_incorrect():
#     user = User(email='<EMAIL>')
#     user.set_password('<PASSWORD>')

#     assert not user.check_password('<PASSWORD>')


# def test_registration_page_get(test_app):
#     client = test_app.test_client()
#     resp = client.get('/auth/register')

#     assert resp.status_code == 200
#     assert b'Register' in resp.data


# def test_registration_page_post(test_app):
#     client = test_app.test_client()
#     resp = client.post(
#         '/auth/register',
#         data=dict(
#             email='<EMAIL>',
#             password='<PASSWORD>',
#             password_confirm='<PASSWORD>'
#         ), follow_redirects=True)

#     assert resp.status_code == 200
#     assert b'Login' in resp.data


# def test_login_page_get(test_app):
#     client = test_app.test_client()
#     resp = client.get('/auth/login')

#     assert resp.status_code == 200
#     assert b'Login' in resp.data


# def test_login_page_post(test_app):
#     """
#     This may not work since there is no user registered.
#     """
<file_sep>import pytest

from cmm_app import app, db
from cmm_app.config import TestingConfig


@pytest.fixture
def test_app():
    app.config.from_object(TestingConfig)
    with app.app_context():
        db.create_all()
        yield app
        db.session.remove()
        db.drop_all()
<file_sep>alembic==1.0.11
astroid==2.2.5
atomicwrites==1.3.0
attrs==19.1.0
autopep8==1.4.4
Click==7.0
Flask==1.1.1
Flask-Login==0.4.1
Flask-Migrate==2.5.2
Flask-SQLAlchemy==2.4.0
Flask-WTF==0.14.2
importlib-metadata==0.19
isort==4.3.21
itsdangerous==1.1.0
Jinja2==2.10.1
lazy-object-proxy==1.4.1
Mako==1.0.14
MarkupSafe==1.1.1
mccabe==0.6.1
more-itertools==7.2.0
packaging==19.1
pathlib2==2.3.4
pep8==1.7.1
pkg-resources==0.0.0
pluggy==0.12.0
psycopg2==2.8.3
py==1.8.0
pycodestyle==2.5.0
pylint==2.3.1
pyparsing==2.4.2
pytest==5.0.1
python-dateutil==2.8.0
python-editor==1.0.4
six==1.12.0
SQLAlchemy==1.3.6
typed-ast==1.4.0
wcwidth==0.1.7
Werkzeug==0.15.5
wrapt==1.11.2
WTForms==2.2.1
zipp==0.5.2
<file_sep>from flask import Blueprint, render_template, redirect, url_for, request, flash
from flask_login import login_user, logout_user, login_required, current_user

from werkzeug.urls import url_parse

from cmm_app import db
from cmm_app.models import User
from cmm_app.auth.forms import RegistrationForm, LoginForm

auth = Blueprint('auth', __name__, template_folder='templates',
                 static_folder='static')


@auth.route('/register', methods=['GET', 'POST'])
def register():
    form = RegistrationForm(request.form)
    if form.validate_on_submit():
        user = User.query.filter_by(email=form.email.data).first()
        if not user:
            new_user = User(email=form.email.data)
            new_user.set_password(form.password.data)

            db.session.add(new_user)
            db.session.commit()

            url = url_for('auth.login')
            flash('Registration successful!', 'success')
            return redirect(url)

        flash('Email already exists!', 'danger')
    return render_template('auth/register.html', form=form)


@auth.route('/login', methods=['GET', 'POST'])
def login():
    if current_user.is_authenticated:
        flash('You are already logged in!', 'warning')
        return redirect(url_for('dashboard.dashboard'))

    form = LoginForm(request.form)
    if form.validate_on_submit():
        user = User.query.filter_by(email=form.email.data).first()
        if user and user.check_password(form.password.data):
            login_user(user, remember=form.remember_me.data)
            next_page = request.args.get('next')
            if not next_page or url_parse(next_page).netloc != '':
                next_page = url_for('dashboard.dashboard')
            return redirect(next_page)
        flash('Invalid credentials!', 'danger')
    return render_template('auth/login.html', form=form)


@auth.route('/logout')
@login_required
def logout():
    logout_user()
    flash('You have been logged out!', 'success')
    return redirect(url_for('auth.login'))
<file_sep># CoverMyMeds
Code Day application

The goal of this code day application was to create a per customer project time tracker application.
Aside from basic authentication, a user would be able to view customers they are doing work for as well as the projects being worked on.
Within these projects, a user can track the time of a task that is being completed for a project.
<file_sep>from datetime import datetime

from cmm_app import db, login_manager

from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash


@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))


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

    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String, index=True, unique=True, nullable=False)
    password = db.Column(db.String)
    created_at = db.Column(db.DateTime, default=datetime.now)

    profile = db.relationship('Profile', cascade='save-update, merge, delete',
                              uselist=False, back_populates='user')

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

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


class Profile(db.Model):
    __tablename__ = 'profile'

    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    first_name = db.Column(db.String)
    last_name = db.Column(db.String)
    birth_date = db.Column(db.DateTime)
    city = db.Column(db.String(50))
    state = db.Column(db.String(50))
    about = db.Column(db.String(300))

    user = db.relationship('User', back_populates='profile')


class Customer(db.Model):
    __tablename__ = 'customer'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String, nullable=False)
    created_at = db.Column(db.DateTime, default=datetime.now)


class Project(db.Model):
    __tablename__ = 'project'

    id = db.Column(db.Integer, primary_key=True)
    customer_id = db.Column(db.Integer, db.ForeignKey('customer.id'))
    name = db.Column(db.String, nullable=False)
    created_at = db.Column(db.DateTime, default=datetime.now)


class Task(db.Model):
    __tablename__ = 'task'

    id = db.Column(db.Integer, primary_key=True)
    project_id = db.Column(db.Integer, db.ForeignKey('project.id'))
    description = db.Column(db.String, nullable=False)
    created_at = db.Column(db.DateTime, default=datetime.now)


class TaskTime(db.Model):
    __tablename__ = 'tasktime'

    id = db.Column(db.Integer, primary_key=True)
    task_id = db.Column(db.Integer, db.ForeignKey('task.id'))
    start_time = db.Column(db.DateTime)
    stop_time = db.Column(db.DateTime)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    created_at = db.Column(db.DateTime, default=datetime.now)

    def start_timer(self):
        self.start_time = datetime.now()

        db.session.add(self)
        db.session.commit()

    def stop_timer(self):
        self.stop_time = datetime.now()
        db.session.commit()

    def total_time_worked(self):
        total_seconds = (self.stop_time - self.start_time).total_seconds()
        total_time_string = '{} hours {} minutes {} seconds'\
            .format(total_seconds // 3600,
                    (total_seconds % 3600) // 60,
                    (total_seconds % 3600) % 60)

        return total_time_string

    @classmethod
    def is_task_being_worked_on(cls):
        task = cls.query.filter_by(stop_time=None).first()

        return task

    def edit_time_duration(self, start_time, end_time):
        if end_time < start_time:
            return 'Invalid time duration'

        conflicting_times = TaskTime.query.filter(
            end_time > TaskTime.start_time).all()
        if len(conflicting_times) > 0:
            return 'End time duration overlaps with other time history'
<file_sep>from flask import Blueprint, render_template, redirect, url_for, request, flash
from flask_login import login_required, current_user

from werkzeug.urls import url_parse

from cmm_app import db
from cmm_app.user.forms import ProfileForm
from cmm_app.models import Profile


user = Blueprint('user', __name__, template_folder='templates',
                 static_folder='static')


@user.route('/profile')
@login_required
def profile():
    return render_template('user/profile.html')


@user.route('/profile/edit', methods=['GET', 'POST'])
@login_required
def profile_edit():
    profile = current_user.profile
    form = ProfileForm(obj=profile)
    form.email.data = current_user.email

    if form.validate_on_submit():
        url = url_for('user.profile')
        current_user.email = form.email.data

        # Need to check if the updated email conflicts with any other user

        if profile:
            form.populate_obj(profile)
            db.session.commit()

            flash('Profile saved!', 'success')
            return redirect(url)
        new_profile = Profile(user_id=current_user.id,
                              first_name=form.first_name.data,
                              last_name=form.last_name.data,
                              birth_date=form.birth_date.data,
                              city=form.city.data,
                              state=form.state.data,
                              about=form.about.data)
        db.session.add(new_profile)
        db.session.commit()

        flash('Profile saved!', 'success')
        return redirect(url)
    return render_template('user/profile_edit.html', form=form)


@user.route('/profile/delete')
@login_required
def profile_delete():
    email = current_user.email
    if request.referrer is None or \
            url_parse(request.referrer).path != url_for('user.profile_edit'):
        flash('This action can only be performed from this page!', 'danger')
        return redirect(url_for('user.profile_edit'))

    db.session.delete(current_user)
    db.session.commit()

    flash('Account {email} has been deleted!'.format(email=email), 'info')
    return redirect(url_for('auth.login'))
<file_sep>from flask_wtf import FlaskForm
from wtforms import TextField, TextAreaField, SubmitField,\
    validators
from wtforms.fields.html5 import EmailField, DateField


class ProfileForm(FlaskForm):
    email = EmailField('Email',
                       [validators.Email(), validators.DataRequired()])
    first_name = TextField('<NAME>')
    last_name = TextField('<NAME>')
    birth_date = DateField('Birth Date', format='%Y-%m-%d')
    city = TextField('City',
                     [validators.Length(max=50)])
    state = TextField('State',
                      [validators.Length(max=50)])
    about = TextAreaField('About',
                          [validators.Length(max=300)])

    submit = SubmitField('Save Profile')
<file_sep>"""empty message

Revision ID: 5ab1e53b6c05
Revises: <PASSWORD>
Create Date: 2019-08-05 22:20:29.604574

"""
from alembic import op
import sqlalchemy as sa


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


def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.add_column('profile', sa.Column('city', sa.String(length=50), nullable=True))
    op.add_column('profile', sa.Column('state', sa.String(length=50), nullable=True))
    # ### end Alembic commands ###


def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_column('profile', 'state')
    op.drop_column('profile', 'city')
    # ### end Alembic commands ###
<file_sep>from flask_wtf import FlaskForm
from wtforms import PasswordField, SubmitField, BooleanField, validators
from wtforms.fields.html5 import EmailField

data_required_message = 'This field must be filled!'


class RegistrationForm(FlaskForm):
    email = EmailField('Email:',
                       [validators.Email('Invalid email!'),
                        validators.DataRequired(data_required_message)])
    password = PasswordField('Password:',
                             [validators.Length(min=8, max=18),
                              validators.DataRequired(data_required_message)])
    password_confirm = PasswordField('Repeat Password:',
                                     [validators.Length(min=8, max=18),
                                      validators.EqualTo('password',
                                                         'Passwords' +
                                                         'must match!'),
                                      validators
                                      .DataRequired(data_required_message)])
    submit = SubmitField('Register')


class LoginForm(FlaskForm):
    email = EmailField('Email:',
                       [validators.Email('Invalid Email'),
                        validators.DataRequired(data_required_message)])
    password = PasswordField('Password:',
                             [validators.DataRequired(data_required_message)])
    remember_me = BooleanField('Remember Me')
    submit = SubmitField('Login')
<file_sep>import os
basedir = os.path.abspath(os.path.dirname(__file__))


class DevelopmentConfig(object):
    SECRET_KEY = 'thisisasecret'

    # SQLAlchemy
    SQLALCHEMY_DATABASE_URI = 'postgres://cwtebzjx:<EMAIL>:5432/cwtebzjx'
    SQLALCHEMY_TRACK_MODIFICATIONS = False


class TestingConfig(object):
    SECRET_KEY = 'testkey'

    DEBUG = True
    TESTING = True

    # SQLAlchemy
    SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'test.db')
    SQLALCHEMY_TRACK_MODIFICATIONS = False
<file_sep>{% extends 'base.html' %}

{% block head %}
<link rel="stylesheet" href="{{ url_for('user.static', filename='css/profile.css') }}">
{% endblock %}

{% block body %}

<div class="container">
    <div class="row">
        <div class="col-md-6 mx-auto">
            <h1 class="mb-3">Profile</h1>

            {% with messages = get_flashed_messages(with_categories=true) %}
                {% if messages %}
                    {% for category, message in messages %}
                        <div class="alert alert-{{ category }} text-center" role="alert">
                            {{ message }}
                        </div>
                    {% endfor %}
                {% endif %}
            {% endwith %}

            <a href="{{ url_for('user.profile_edit') }}">Edit Profile</a>

            <div class="card">
                <div class="card-body">
                    <h5 class="card-title"><strong>Email</strong></h5>
                    <p class="card-text">{{ current_user.email }}</p>
                </div>

                <hr>

                <div class="card-body">
                    <h5 class="card-title"><strong>Full Name</strong></h5>
                    <p class="card-text">{{ current_user.profile.first_name }} {{ current_user.profile.last_name }}</p>
                </div>

                <hr>

                <div class="card-body">
                    <h5 class="card-title"><strong>Birth Date </strong></h5>
                    {% if current_user.profile.birth_date %}
                        <p class="card-text">{{ current_user.profile.birth_date.strftime('%m/%d/%Y') }}</p>
                    {% endif %}
                </div>

                <hr>

                <div class="card-body">
                    <h5 class="card-title"><strong>City/State</strong></h5>
                    <p class="card-text">{{ current_user.profile.city }}{{ ',' if current_user.profile.state }} {{ current_user.profile.state }}</p>
                </div>

                <hr>

                <div class="card-body">
                    <h5 class="card-title"><strong>About</strong></h5>
                    <p class="card-text">{{ current_user.profile.about }}</p>
                </div>
            </div>

        </div>
    </div>
</div>

{% endblock %}<file_sep>from cmm_app import db
from cmm_app.models import User, Customer, Project, Task, TaskTime

from tests.base import test_app

import time
from datetime import datetime, timedelta
import pytest


@pytest.fixture
def set_up_db(test_app):
    user = User(email='<EMAIL>')
    user.set_password('<PASSWORD>')
    db.session.add(user)
    db.session.commit()

    customer = Customer(name='K&G Creative')
    db.session.add(customer)
    db.session.commit()

    project = Project(customer_id=customer.id, name='TimeTracker')
    db.session.add(project)
    db.session.commit()

    task = Task(project_id=project.id, description='Add UI/UX')
    db.session.add(task)
    db.session.commit()


def test_task_timer(set_up_db):
    """
    Test the task timer model and it's start/stop timer functionality.
    """
    user = User.query.filter_by(email='<EMAIL>').first()
    task = Task.query.filter_by(id=1).first()

    work = TaskTime(task_id=task.id, user_id=user.id)
    work.start_timer()
    db.session.add(work)
    db.session.commit()

    time.sleep(5)
    work.stop_timer()

    assert work.start_time is not None
    assert work.stop_time is not None
    assert work.total_time_worked().seconds == 5


def test_task_timer_check_if_task_started_already(set_up_db):
    user = User.query.filter_by(email='<EMAIL>').first()
    task = Task.query.filter_by(id=1).first()

    work = TaskTime(task_id=task.id, user_id=user.id)
    work.start_timer()
    db.session.add(work)
    db.session.commit()

    assert TaskTime.is_task_being_worked_on()


# def test_task_timer_edit_duration(set_up_db):
#     user = User.query.filter_by(email='<EMAIL>').first()
#     task = Task.query.filter_by(id=1).first()

#     task_time_1 = TaskTime(task_id=task.id, user_id=user.id)
#     task_time_1.start_time = datetime.now()
#     task_time_1.stop_time = task_time_1.start_time + timedelta(hours=2)

#     # Task time 2 should be after the first task time
#     task_time_2 = TaskTime(task_id=task.id, user_id=user.id)
#     task_time_2.start_time = task_time_1.stop_time + timedelta(days=1)
#     task_time_2.stop_time = task_time_2.start_time + timedelta(hours=3)

#     # Edit task time 1
#     new_start_time = datetime(2019, 8, 5, 11)
#     new_end_time = datetime(2019, 8, 5, 9)

#     assert task_time_1.edit_time_duration(
#         new_start_time, new_start_time) is not None
<file_sep>from flask import Blueprint, render_template
from flask_login import login_required, current_user
from sqlalchemy.sql import func

import json
from datetime import timedelta

from cmm_app import db
from cmm_app.models import Task, TaskTime

dash = Blueprint('dashboard', __name__, template_folder='templates')


@dash.route('/')
@login_required
def dashboard():
    all_tasks = Task.query.all()

    return render_template('dashboard/dashboard.html', tasks=all_tasks)


@dash.route('/customers')
def customers():
    return render_template('dashboard/customers.html')


@dash.route('/customers/projects')
def customer_projects():
    return render_template('dashboard/customer_projects.html')


@dash.route('/projects')
def projects():
    return render_template('dashboard/projects.html')


@dash.route('/projects/<project_id>/tasks')
def project_tasks():
    return render_template('dashboard/project_tasks.html')


@dash.route('/projects/tasks/<task_id>')
def task(task_id):
    task = Task.query.filter_by(id=task_id).first()
    task_history = TaskTime.query.filter_by(task_id=task_id).all()

    task_total_duration = timedelta(0)
    for task_time in task_history:
        task_total_duration += (task_time.stop_time - task_time.start_time)

    return render_template('dashboard/task.html',
                           task=task,
                           task_total_duration=task_total_duration,
                           task_history=task_history)


@dash.route('/projects/tasks/<task_id>/start_task')
def start_task(task_id):
    task_time = TaskTime(task_id=task_id, user_id=current_user.id)
    task_time.start_timer()

    message = {
        'message': 'Timer started'
    }

    return json.dumps(message)


@dash.route('/projects/tasks/<task_id>/stop_task')
def stop_task(task_id):
    task_time = TaskTime.query.filter(TaskTime.task_id == task_id).filter(
        TaskTime.stop_time == None).first()

    task_time.stop_timer()

    message = {
        'message': 'Timer stopped',
        'task_time': task_time.total_time_worked()
    }

    return json.dumps(message)
<file_sep>from flask import Flask, url_for
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_login import LoginManager


from cmm_app.config import DevelopmentConfig

app = Flask(__name__)

app.config.from_object(DevelopmentConfig)

db = SQLAlchemy(app)
migrate = Migrate(app, db)

login_manager = LoginManager()
login_manager.login_view = 'auth.login'
login_manager.init_app(app)

# Must import at this stage or else the modules cannot read 'db'
from cmm_app.dashboard.routes import dash
from cmm_app.auth.routes import auth
from cmm_app.user.routes import user
app.register_blueprint(auth, url_prefix='/auth')
app.register_blueprint(dash, url_prefix='/dashboard')
app.register_blueprint(user, url_prefix='/user')
<file_sep>"""empty message

Revision ID: <KEY>
Revises: <PASSWORD>
Create Date: 2019-08-06 10:39:10.466399

"""
from alembic import op
import sqlalchemy as sa


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


def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.add_column('customer', sa.Column('created_at', sa.DateTime(), nullable=True))
    op.add_column('project', sa.Column('created_at', sa.DateTime(), nullable=True))
    op.add_column('task', sa.Column('created_at', sa.DateTime(), nullable=True))
    op.add_column('tasktime', sa.Column('created_at', sa.DateTime(), nullable=True))
    # ### end Alembic commands ###


def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_column('tasktime', 'created_at')
    op.drop_column('task', 'created_at')
    op.drop_column('project', 'created_at')
    op.drop_column('customer', 'created_at')
    # ### end Alembic commands ###
