From 37a47ad9400e2e940fcba1f317c3629030b26638 Mon Sep 17 00:00:00 2001
From: Stephen Carrera
Date: Wed, 6 Dec 2017 11:00:20 -0800
Subject: [PATCH 1/2] login users
---
Unit-02/04-flask-login/app.py | 4 +
Unit-02/04-flask-login/manage.py | 12 ++
Unit-02/04-flask-login/migrations/README | 1 +
Unit-02/04-flask-login/migrations/alembic.ini | 45 ++++++++
Unit-02/04-flask-login/migrations/env.py | 87 ++++++++++++++
.../04-flask-login/migrations/script.py.mako | 24 ++++
.../32d323b1b2da_create_tags_table.py | 39 +++++++
...788d47_create_users_and_messages_tables.py | 45 ++++++++
.../migrations/versions/89838541ca52_.py | 32 +++++
Unit-02/04-flask-login/project/__init__.py | 52 +++++++++
.../project/decorators/__init__.py | 31 +++++
.../04-flask-login/project/messages/forms.py | 17 +++
.../project/messages/templates/edit.html | 30 +++++
.../project/messages/templates/index.html | 27 +++++
.../project/messages/templates/new.html | 26 +++++
.../project/messages/templates/show.html | 15 +++
.../04-flask-login/project/messages/views.py | 81 +++++++++++++
Unit-02/04-flask-login/project/models.py | 64 ++++++++++
Unit-02/04-flask-login/project/tags/forms.py | 18 +++
.../project/tags/templates/tags/edit.html | 30 +++++
.../project/tags/templates/tags/index.html | 12 ++
.../project/tags/templates/tags/new.html | 17 +++
.../project/tags/templates/tags/show.html | 19 +++
Unit-02/04-flask-login/project/tags/views.py | 79 +++++++++++++
.../project/templates/base.html | 50 ++++++++
.../project/templates/errors.html | 8 ++
Unit-02/04-flask-login/project/users/forms.py | 16 +++
.../project/users/templates/users/edit.html | 22 ++++
.../project/users/templates/users/index.html | 22 ++++
.../project/users/templates/users/login.html | 24 ++++
.../project/users/templates/users/show.html | 0
.../project/users/templates/users/signup.html | 23 ++++
Unit-02/04-flask-login/project/users/views.py | 109 ++++++++++++++++++
Unit-02/04-flask-login/requirements.txt | 41 +++++++
34 files changed, 1122 insertions(+)
create mode 100644 Unit-02/04-flask-login/app.py
create mode 100644 Unit-02/04-flask-login/manage.py
create mode 100755 Unit-02/04-flask-login/migrations/README
create mode 100644 Unit-02/04-flask-login/migrations/alembic.ini
create mode 100755 Unit-02/04-flask-login/migrations/env.py
create mode 100755 Unit-02/04-flask-login/migrations/script.py.mako
create mode 100644 Unit-02/04-flask-login/migrations/versions/32d323b1b2da_create_tags_table.py
create mode 100644 Unit-02/04-flask-login/migrations/versions/5656eb788d47_create_users_and_messages_tables.py
create mode 100644 Unit-02/04-flask-login/migrations/versions/89838541ca52_.py
create mode 100644 Unit-02/04-flask-login/project/__init__.py
create mode 100644 Unit-02/04-flask-login/project/decorators/__init__.py
create mode 100644 Unit-02/04-flask-login/project/messages/forms.py
create mode 100644 Unit-02/04-flask-login/project/messages/templates/edit.html
create mode 100644 Unit-02/04-flask-login/project/messages/templates/index.html
create mode 100644 Unit-02/04-flask-login/project/messages/templates/new.html
create mode 100644 Unit-02/04-flask-login/project/messages/templates/show.html
create mode 100644 Unit-02/04-flask-login/project/messages/views.py
create mode 100644 Unit-02/04-flask-login/project/models.py
create mode 100644 Unit-02/04-flask-login/project/tags/forms.py
create mode 100644 Unit-02/04-flask-login/project/tags/templates/tags/edit.html
create mode 100644 Unit-02/04-flask-login/project/tags/templates/tags/index.html
create mode 100644 Unit-02/04-flask-login/project/tags/templates/tags/new.html
create mode 100644 Unit-02/04-flask-login/project/tags/templates/tags/show.html
create mode 100644 Unit-02/04-flask-login/project/tags/views.py
create mode 100644 Unit-02/04-flask-login/project/templates/base.html
create mode 100644 Unit-02/04-flask-login/project/templates/errors.html
create mode 100644 Unit-02/04-flask-login/project/users/forms.py
create mode 100644 Unit-02/04-flask-login/project/users/templates/users/edit.html
create mode 100644 Unit-02/04-flask-login/project/users/templates/users/index.html
create mode 100644 Unit-02/04-flask-login/project/users/templates/users/login.html
create mode 100644 Unit-02/04-flask-login/project/users/templates/users/show.html
create mode 100644 Unit-02/04-flask-login/project/users/templates/users/signup.html
create mode 100644 Unit-02/04-flask-login/project/users/views.py
create mode 100644 Unit-02/04-flask-login/requirements.txt
diff --git a/Unit-02/04-flask-login/app.py b/Unit-02/04-flask-login/app.py
new file mode 100644
index 0000000..d7f70e7
--- /dev/null
+++ b/Unit-02/04-flask-login/app.py
@@ -0,0 +1,4 @@
+from project import app
+
+if __name__ == '__main__':
+ app.run(debug=True,port=3000)
\ No newline at end of file
diff --git a/Unit-02/04-flask-login/manage.py b/Unit-02/04-flask-login/manage.py
new file mode 100644
index 0000000..6468e99
--- /dev/null
+++ b/Unit-02/04-flask-login/manage.py
@@ -0,0 +1,12 @@
+from project import app, db
+
+from flask_script import Manager
+from flask_migrate import Migrate, MigrateCommand
+
+migrate = Migrate(app, db)
+manager = Manager(app)
+
+manager.add_command('db', MigrateCommand)
+
+if __name__ == '__main__':
+ manager.run()
\ No newline at end of file
diff --git a/Unit-02/04-flask-login/migrations/README b/Unit-02/04-flask-login/migrations/README
new file mode 100755
index 0000000..98e4f9c
--- /dev/null
+++ b/Unit-02/04-flask-login/migrations/README
@@ -0,0 +1 @@
+Generic single-database configuration.
\ No newline at end of file
diff --git a/Unit-02/04-flask-login/migrations/alembic.ini b/Unit-02/04-flask-login/migrations/alembic.ini
new file mode 100644
index 0000000..f8ed480
--- /dev/null
+++ b/Unit-02/04-flask-login/migrations/alembic.ini
@@ -0,0 +1,45 @@
+# A generic, single database configuration.
+
+[alembic]
+# template used to generate migration files
+# file_template = %%(rev)s_%%(slug)s
+
+# set to 'true' to run the environment during
+# the 'revision' command, regardless of autogenerate
+# revision_environment = false
+
+
+# Logging configuration
+[loggers]
+keys = root,sqlalchemy,alembic
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = WARN
+handlers = console
+qualname =
+
+[logger_sqlalchemy]
+level = WARN
+handlers =
+qualname = sqlalchemy.engine
+
+[logger_alembic]
+level = INFO
+handlers =
+qualname = alembic
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(levelname)-5.5s [%(name)s] %(message)s
+datefmt = %H:%M:%S
diff --git a/Unit-02/04-flask-login/migrations/env.py b/Unit-02/04-flask-login/migrations/env.py
new file mode 100755
index 0000000..23663ff
--- /dev/null
+++ b/Unit-02/04-flask-login/migrations/env.py
@@ -0,0 +1,87 @@
+from __future__ import with_statement
+from alembic import context
+from sqlalchemy import engine_from_config, pool
+from logging.config import fileConfig
+import logging
+
+# this is the Alembic Config object, which provides
+# access to the values within the .ini file in use.
+config = context.config
+
+# Interpret the config file for Python logging.
+# This line sets up loggers basically.
+fileConfig(config.config_file_name)
+logger = logging.getLogger('alembic.env')
+
+# add your model's MetaData object here
+# for 'autogenerate' support
+# from myapp import mymodel
+# target_metadata = mymodel.Base.metadata
+from flask import current_app
+config.set_main_option('sqlalchemy.url',
+ current_app.config.get('SQLALCHEMY_DATABASE_URI'))
+target_metadata = current_app.extensions['migrate'].db.metadata
+
+# other values from the config, defined by the needs of env.py,
+# can be acquired:
+# my_important_option = config.get_main_option("my_important_option")
+# ... etc.
+
+
+def run_migrations_offline():
+ """Run migrations in 'offline' mode.
+
+ This configures the context with just a URL
+ and not an Engine, though an Engine is acceptable
+ here as well. By skipping the Engine creation
+ we don't even need a DBAPI to be available.
+
+ Calls to context.execute() here emit the given string to the
+ script output.
+
+ """
+ url = config.get_main_option("sqlalchemy.url")
+ context.configure(url=url)
+
+ with context.begin_transaction():
+ context.run_migrations()
+
+
+def run_migrations_online():
+ """Run migrations in 'online' mode.
+
+ In this scenario we need to create an Engine
+ and associate a connection with the context.
+
+ """
+
+ # this callback is used to prevent an auto-migration from being generated
+ # when there are no changes to the schema
+ # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
+ def process_revision_directives(context, revision, directives):
+ if getattr(config.cmd_opts, 'autogenerate', False):
+ script = directives[0]
+ if script.upgrade_ops.is_empty():
+ directives[:] = []
+ logger.info('No changes in schema detected.')
+
+ engine = engine_from_config(config.get_section(config.config_ini_section),
+ prefix='sqlalchemy.',
+ poolclass=pool.NullPool)
+
+ connection = engine.connect()
+ context.configure(connection=connection,
+ target_metadata=target_metadata,
+ process_revision_directives=process_revision_directives,
+ **current_app.extensions['migrate'].configure_args)
+
+ try:
+ with context.begin_transaction():
+ context.run_migrations()
+ finally:
+ connection.close()
+
+if context.is_offline_mode():
+ run_migrations_offline()
+else:
+ run_migrations_online()
diff --git a/Unit-02/04-flask-login/migrations/script.py.mako b/Unit-02/04-flask-login/migrations/script.py.mako
new file mode 100755
index 0000000..2c01563
--- /dev/null
+++ b/Unit-02/04-flask-login/migrations/script.py.mako
@@ -0,0 +1,24 @@
+"""${message}
+
+Revision ID: ${up_revision}
+Revises: ${down_revision | comma,n}
+Create Date: ${create_date}
+
+"""
+from alembic import op
+import sqlalchemy as sa
+${imports if imports else ""}
+
+# revision identifiers, used by Alembic.
+revision = ${repr(up_revision)}
+down_revision = ${repr(down_revision)}
+branch_labels = ${repr(branch_labels)}
+depends_on = ${repr(depends_on)}
+
+
+def upgrade():
+ ${upgrades if upgrades else "pass"}
+
+
+def downgrade():
+ ${downgrades if downgrades else "pass"}
diff --git a/Unit-02/04-flask-login/migrations/versions/32d323b1b2da_create_tags_table.py b/Unit-02/04-flask-login/migrations/versions/32d323b1b2da_create_tags_table.py
new file mode 100644
index 0000000..13c247b
--- /dev/null
+++ b/Unit-02/04-flask-login/migrations/versions/32d323b1b2da_create_tags_table.py
@@ -0,0 +1,39 @@
+"""create tags table
+
+Revision ID: 32d323b1b2da
+Revises: 5656eb788d47
+Create Date: 2017-12-03 15:44:28.391960
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = '32d323b1b2da'
+down_revision = '5656eb788d47'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.create_table('tags',
+ sa.Column('id', sa.Integer(), nullable=False),
+ sa.Column('text', sa.Text(), nullable=True),
+ sa.PrimaryKeyConstraint('id')
+ )
+ op.create_table('message_tags',
+ sa.Column('message_id', sa.Integer(), nullable=True),
+ sa.Column('tag_id', sa.Integer(), nullable=True),
+ sa.ForeignKeyConstraint(['message_id'], ['messages.id'], ),
+ sa.ForeignKeyConstraint(['tag_id'], ['tags.id'], )
+ )
+ # ### end Alembic commands ###
+
+
+def downgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.drop_table('message_tags')
+ op.drop_table('tags')
+ # ### end Alembic commands ###
diff --git a/Unit-02/04-flask-login/migrations/versions/5656eb788d47_create_users_and_messages_tables.py b/Unit-02/04-flask-login/migrations/versions/5656eb788d47_create_users_and_messages_tables.py
new file mode 100644
index 0000000..9dc007f
--- /dev/null
+++ b/Unit-02/04-flask-login/migrations/versions/5656eb788d47_create_users_and_messages_tables.py
@@ -0,0 +1,45 @@
+"""create users and messages tables
+
+Revision ID: 5656eb788d47
+Revises:
+Create Date: 2017-12-02 17:24:15.879546
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = '5656eb788d47'
+down_revision = None
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.create_table('users',
+ sa.Column('id', sa.Integer(), nullable=False),
+ sa.Column('first_name', sa.Text(), nullable=True),
+ sa.Column('last_name', sa.Text(), nullable=True),
+ sa.Column('created_on', sa.DateTime(), server_default=sa.text('now()'), nullable=True),
+ sa.Column('updated_on', sa.DateTime(), server_default=sa.text('now()'), nullable=True),
+ sa.PrimaryKeyConstraint('id')
+ )
+ op.create_table('messages',
+ sa.Column('id', sa.Integer(), nullable=False),
+ sa.Column('content', sa.Text(), nullable=True),
+ sa.Column('created_on', sa.DateTime(), server_default=sa.text('now()'), nullable=True),
+ sa.Column('updated_on', sa.DateTime(), server_default=sa.text('now()'), nullable=True),
+ sa.Column('user_id', sa.Integer(), nullable=True),
+ sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
+ sa.PrimaryKeyConstraint('id')
+ )
+ # ### end Alembic commands ###
+
+
+def downgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.drop_table('messages')
+ op.drop_table('users')
+ # ### end Alembic commands ###
diff --git a/Unit-02/04-flask-login/migrations/versions/89838541ca52_.py b/Unit-02/04-flask-login/migrations/versions/89838541ca52_.py
new file mode 100644
index 0000000..4677f8e
--- /dev/null
+++ b/Unit-02/04-flask-login/migrations/versions/89838541ca52_.py
@@ -0,0 +1,32 @@
+"""empty message
+
+Revision ID: 89838541ca52
+Revises: 32d323b1b2da
+Create Date: 2017-12-04 18:28:37.879257
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = '89838541ca52'
+down_revision = '32d323b1b2da'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.add_column('users', sa.Column('password', sa.Text(), nullable=True))
+ op.add_column('users', sa.Column('username', sa.Text(), nullable=True))
+ op.create_unique_constraint(None, 'users', ['username'])
+ # ### end Alembic commands ###
+
+
+def downgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.drop_constraint(None, 'users', type_='unique')
+ op.drop_column('users', 'username')
+ op.drop_column('users', 'password')
+ # ### end Alembic commands ###
diff --git a/Unit-02/04-flask-login/project/__init__.py b/Unit-02/04-flask-login/project/__init__.py
new file mode 100644
index 0000000..cffa408
--- /dev/null
+++ b/Unit-02/04-flask-login/project/__init__.py
@@ -0,0 +1,52 @@
+from flask import Flask, redirect, url_for, render_template
+from flask_modus import Modus
+from flask_sqlalchemy import SQLAlchemy
+from flask_moment import Moment
+from flask_bcrypt import Bcrypt
+from flask_login import LoginManager
+import os
+
+
+
+app = Flask(__name__)
+app.url_map.strict_slashes = False
+bcrypt = Bcrypt(app)
+login_manager = LoginManager()
+login_manager.init_app(app)
+
+app.config["SQLALCHEMY_DATABASE_URI"] = 'postgres://localhost/users-messages-bcrypt'
+app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
+app.config["SECRET_KEY"] = os.environ.get('SECRET_KEY')
+
+modus = Modus(app)
+moment = Moment(app)
+db = SQLAlchemy(app)
+
+
+from project.users.views import users_blueprint
+from project.messages.views import messages_blueprint
+from project.tags.views import tags_blueprint
+
+app.register_blueprint(users_blueprint, url_prefix='/users')
+app.register_blueprint(messages_blueprint, url_prefix='/users//messages')
+app.register_blueprint(tags_blueprint, url_prefix='/tags')
+
+login_manager.login_view = "users.login"
+
+from project.models import User
+
+@login_manager.user_loader
+def load_user(id):
+ return User.query.get(int(id))
+
+@app.route('/')
+def root():
+ return redirect(url_for('users.login'))
+
+@app.errorhandler(404)
+def page_not_found(error):
+ return render_template('errors.html', error=error), 404
+
+@app.errorhandler(500)
+def server_error(error):
+ return render_template('errors.html', error=error), 500
\ No newline at end of file
diff --git a/Unit-02/04-flask-login/project/decorators/__init__.py b/Unit-02/04-flask-login/project/decorators/__init__.py
new file mode 100644
index 0000000..a528085
--- /dev/null
+++ b/Unit-02/04-flask-login/project/decorators/__init__.py
@@ -0,0 +1,31 @@
+from functools import wraps
+from flask import redirect, url_for, session, flash
+from flask_login import current_user
+
+# def ensure_authenticated(fn):
+# @wraps(fn)
+# def wrapper(*args, **kwargs):
+# if session.get('user_id') is None:
+# flash('Please log in first!')
+# return redirect(url_for('users.login'))
+# return fn(*args,**kwargs)
+# return wrapper
+
+def prevent_login_signup(fn):
+ @wraps(fn)
+ def wrapper(*args, **kwargs):
+ if current_user.is_authenticated:
+ flash('You are logged in already')
+ return redirect(url_for('users.index'))
+ return fn(*args, **kwargs)
+ return wrapper
+
+def ensure_correct_user(fn):
+ @wraps(fn)
+ def wrapper(*args, **kwargs):
+ correct_id = kwargs.get('id') or kwargs.get('user_id')
+ if correct_id != current_user.id:
+ flash('Not Authorized')
+ return redirect(url_for('users.index', id=current_user.id))
+ return fn(*args, **kwargs)
+ return wrapper
\ No newline at end of file
diff --git a/Unit-02/04-flask-login/project/messages/forms.py b/Unit-02/04-flask-login/project/messages/forms.py
new file mode 100644
index 0000000..d86e5b2
--- /dev/null
+++ b/Unit-02/04-flask-login/project/messages/forms.py
@@ -0,0 +1,17 @@
+from flask_wtf import FlaskForm
+from wtforms import TextAreaField, SelectMultipleField, widgets, validators
+from project.models import Tag
+
+class MultiCheckboxField(SelectMultipleField):
+ widget = widgets.ListWidget(prefix_label=False)
+ option_widget = widgets.CheckboxInput()
+
+class MessageForm(FlaskForm):
+ content=TextAreaField('Content', [validators.DataRequired(), validators.length(max=280)], render_kw={"rows": 3, "class": "text-center col-6"})
+ tags = MultiCheckboxField('Tags', coerce=int)
+
+ def set_choices(self):
+ self.tags.choices = [(t.id, t.text) for t in Tag.query.all()]
+
+class DeleteForm(FlaskForm):
+ pass
\ No newline at end of file
diff --git a/Unit-02/04-flask-login/project/messages/templates/edit.html b/Unit-02/04-flask-login/project/messages/templates/edit.html
new file mode 100644
index 0000000..70b17c8
--- /dev/null
+++ b/Unit-02/04-flask-login/project/messages/templates/edit.html
@@ -0,0 +1,30 @@
+{% extends 'base.html' %}
+
+{% block content %}
+ Edit Your Message to {{message.user.first_name}}
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/Unit-02/04-flask-login/project/messages/templates/index.html b/Unit-02/04-flask-login/project/messages/templates/index.html
new file mode 100644
index 0000000..70f2e10
--- /dev/null
+++ b/Unit-02/04-flask-login/project/messages/templates/index.html
@@ -0,0 +1,27 @@
+{% extends 'base.html' %}
+
+{% block content %}
+ See All Messages for {{user.first_name}}
+ Send {{user.first_name}} a message
+
+
+ {% for message in user.messages %}
+
+ {{message.content}}
+
+ posted {{ moment(message.updated_on, local=True).fromNow() }}
+ {% for tag in message.tags %}
+
+ {{tag.text}}
+
+ {% endfor %}
+
+
+ Edit this message |
+ See More
+
+
+
+
+ {% endfor %}
+{% endblock %}
\ No newline at end of file
diff --git a/Unit-02/04-flask-login/project/messages/templates/new.html b/Unit-02/04-flask-login/project/messages/templates/new.html
new file mode 100644
index 0000000..f6d701c
--- /dev/null
+++ b/Unit-02/04-flask-login/project/messages/templates/new.html
@@ -0,0 +1,26 @@
+{% extends 'base.html' %}
+
+{% block content %}
+ Leave a Message for {{user.first_name}}
+
+{% endblock %}
\ No newline at end of file
diff --git a/Unit-02/04-flask-login/project/messages/templates/show.html b/Unit-02/04-flask-login/project/messages/templates/show.html
new file mode 100644
index 0000000..956d9d4
--- /dev/null
+++ b/Unit-02/04-flask-login/project/messages/templates/show.html
@@ -0,0 +1,15 @@
+{% extends 'base.html' %}
+
+{% block content %}
+ {{message.content}}
+ {% if message.tags|length == 0 %}
+ {{message.content}} isn't tagged!
+ {% else %}
+ {% for tag in message.tags %}
+
+ {{tag.text}}
+
+ {% endfor %}
+ {% endif %}
+ Edit
+{% endblock %}
\ No newline at end of file
diff --git a/Unit-02/04-flask-login/project/messages/views.py b/Unit-02/04-flask-login/project/messages/views.py
new file mode 100644
index 0000000..cf4002f
--- /dev/null
+++ b/Unit-02/04-flask-login/project/messages/views.py
@@ -0,0 +1,81 @@
+from flask import Blueprint, render_template, redirect, request, url_for, flash
+from project.models import User, Message, Tag
+from project.messages.forms import MessageForm, DeleteForm
+from project import db
+from project.decorators import ensure_correct_user
+from flask_login import login_required
+
+
+messages_blueprint = Blueprint(
+ 'messages',
+ __name__,
+ template_folder = 'templates'
+)
+
+
+
+@messages_blueprint.route('/', methods=["GET", "POST"])
+@login_required
+def index(user_id):
+ user = User.query.get(user_id)
+ if request.method == "POST":
+ form = MessageForm(request.form)
+ form.set_choices()
+ if form.validate():
+ new_message=Message(form.content.data, user.id)
+ for tag in form.tags.data:
+ new_message.tags.append(Tag.query.get(tag))
+ db.session.add(new_message)
+ db.session.commit()
+ flash('Message Created!')
+ return redirect(url_for('messages.index', user_id=user.id))
+ return render_template('new.html', user=user, form=form)
+ return render_template('index.html', user=user)
+
+@messages_blueprint.route('/new')
+@login_required
+@ensure_correct_user
+def new(user_id):
+ form = MessageForm()
+ form.set_choices()
+ return render_template('new.html', user=User.query.get(user_id), form=form)
+
+@messages_blueprint.route('//edit')
+@login_required
+@ensure_correct_user
+def edit(user_id, id):
+ message=Message.query.get(id)
+ tags = [tag.id for tag in message.tags]
+ form = MessageForm(tags=tags)
+ form.set_choices()
+ form.content.data = message.content
+ return render_template('edit.html', message=message, form=form)
+
+@messages_blueprint.route('/', methods=["GET", "PATCH", "DELETE"])
+@login_required
+@ensure_correct_user
+def show(user_id, id):
+ message=Message.query.get(id)
+ if request.method == b'PATCH':
+ form = MessageForm(request.form)
+ form.set_choices()
+ if form.validate():
+ time = db.func.now()
+ message.updated_on = time
+ message.content = request.form['content']
+ message.tags=[]
+ for tag in form.tags.data:
+ message.tags.append(Tag.query.get(tag))
+ db.session.add(message)
+ db.session.commit()
+ flash('Message Updated!')
+ return redirect(url_for('messages.index', user_id=message.user.id))
+ return render_template('edit.html', message=message, form=form)
+ if request.method == b'DELETE':
+ delete_form = DeleteForm(request.form)
+ if delete_form.validate():
+ db.session.delete(message)
+ db.session.commit()
+ flash('Message Deleted!')
+ return redirect(url_for('messages.index', user_id=user_id))
+ return render_template('show.html', message=message)
\ No newline at end of file
diff --git a/Unit-02/04-flask-login/project/models.py b/Unit-02/04-flask-login/project/models.py
new file mode 100644
index 0000000..18ebe65
--- /dev/null
+++ b/Unit-02/04-flask-login/project/models.py
@@ -0,0 +1,64 @@
+from project import db, bcrypt
+from flask_login import UserMixin
+
+class User(db.Model, UserMixin):
+
+ __tablename__ = 'users'
+
+ id = db.Column(db.Integer, primary_key=True)
+ username = db.Column(db.Text, unique=True)
+ password = db.Column(db.Text)
+ first_name = db.Column(db.Text)
+ last_name = db.Column(db.Text)
+ created_on = db.Column(db.DateTime, server_default=db.func.now())
+ updated_on = db.Column(db.DateTime, server_default=db.func.now(), server_onupdate=db.func.now())
+ messages = db.relationship('Message', backref='user', lazy='dynamic', cascade='all, delete')
+
+ def __init__(self, first_name, last_name, username, password):
+ self.first_name = first_name
+ self.last_name = last_name
+ self.username = username
+ self.password = bcrypt.generate_password_hash(password).decode('UTF-8')
+
+ def __repr__(self):
+ return f"The user's name is {self.first_name} {self.last_name}"
+
+ @classmethod
+ def authenticate(cls, username, password):
+ found_user = cls.query.filter_by(username = username).first()
+ if found_user:
+ authenticated_user = bcrypt.check_password_hash(found_user.password, password)
+ if authenticated_user:
+ return found_user
+ return False
+
+MessageTag = db.Table('message_tags',
+ db.Column('message_id', db.Integer, db.ForeignKey('messages.id')),
+ db.Column('tag_id', db.Integer, db.ForeignKey('tags.id'))
+)
+
+class Message(db.Model):
+
+ __tablename__ = 'messages'
+
+ id = db.Column(db.Integer, primary_key=True)
+ content = db.Column(db.Text)
+ created_on = db.Column(db.DateTime, server_default=db.func.now())
+ updated_on = db.Column(db.DateTime, server_default=db.func.now(), server_onupdate=db.func.now())
+ user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
+ tags = db.relationship('Tag', secondary=MessageTag, backref=db.backref('messages'))
+
+ def __init__(self, content, user_id):
+ self.content = content
+ self.user_id = user_id
+
+
+class Tag(db.Model):
+
+ __tablename__ = 'tags'
+
+ id = db.Column(db.Integer, primary_key=True)
+ text= db.Column(db.Text)
+
+ def __init__(self, text):
+ self.text = text
\ No newline at end of file
diff --git a/Unit-02/04-flask-login/project/tags/forms.py b/Unit-02/04-flask-login/project/tags/forms.py
new file mode 100644
index 0000000..e8a4afd
--- /dev/null
+++ b/Unit-02/04-flask-login/project/tags/forms.py
@@ -0,0 +1,18 @@
+from flask_wtf import FlaskForm
+from wtforms import TextField, SelectMultipleField, widgets
+from wtforms.validators import DataRequired
+from project.models import Message
+
+class MultiCheckboxField(SelectMultipleField):
+ widget = widgets.ListWidget(prefix_label=False)
+ option_widget = widgets.CheckboxInput()
+
+class TagForm(FlaskForm):
+ text = TextField('Text', validators=[DataRequired()], render_kw={"class":"text-center col-3"})
+ messages = MultiCheckboxField('Messages', coerce=int)
+
+ def set_choices(self):
+ self.messages.choices = [(m.id, m.content) for m in Message.query.all()]
+
+class DeleteForm(FlaskForm):
+ pass
\ No newline at end of file
diff --git a/Unit-02/04-flask-login/project/tags/templates/tags/edit.html b/Unit-02/04-flask-login/project/tags/templates/tags/edit.html
new file mode 100644
index 0000000..e3318f8
--- /dev/null
+++ b/Unit-02/04-flask-login/project/tags/templates/tags/edit.html
@@ -0,0 +1,30 @@
+{% extends 'base.html' %}
+
+{% block content %}
+ Edit the {{tag.text}} Tag
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/Unit-02/04-flask-login/project/tags/templates/tags/index.html b/Unit-02/04-flask-login/project/tags/templates/tags/index.html
new file mode 100644
index 0000000..3c22d46
--- /dev/null
+++ b/Unit-02/04-flask-login/project/tags/templates/tags/index.html
@@ -0,0 +1,12 @@
+{% extends 'base.html' %}
+
+{% block content %}
+ All Tags
+ {% for tag in tags %}
+
+ {{ tag.text }}
+
+ Edit | See More
+
+ {% endfor %}
+{% endblock %}
\ No newline at end of file
diff --git a/Unit-02/04-flask-login/project/tags/templates/tags/new.html b/Unit-02/04-flask-login/project/tags/templates/tags/new.html
new file mode 100644
index 0000000..955caeb
--- /dev/null
+++ b/Unit-02/04-flask-login/project/tags/templates/tags/new.html
@@ -0,0 +1,17 @@
+{% extends 'base.html' %}
+
+{% block content %}
+ Add a New Tag
+
+{% endblock %}
\ No newline at end of file
diff --git a/Unit-02/04-flask-login/project/tags/templates/tags/show.html b/Unit-02/04-flask-login/project/tags/templates/tags/show.html
new file mode 100644
index 0000000..a1d1032
--- /dev/null
+++ b/Unit-02/04-flask-login/project/tags/templates/tags/show.html
@@ -0,0 +1,19 @@
+{% extends 'base.html' %}
+
+{% block content %}
+Messages with the "{{tag.text}}"" Tag
+{% if tag.messages|length == 0 %}
+ No messages yet
+{% else %}
+ {% for m in tag.messages %}
+
+ {{m.content}}
+
+ {% endfor %}
+{% endif %}
+Edit
+
+{% endblock %}
\ No newline at end of file
diff --git a/Unit-02/04-flask-login/project/tags/views.py b/Unit-02/04-flask-login/project/tags/views.py
new file mode 100644
index 0000000..6eeab9c
--- /dev/null
+++ b/Unit-02/04-flask-login/project/tags/views.py
@@ -0,0 +1,79 @@
+from flask import redirect, render_template, request, url_for, flash, Blueprint
+from project.models import Message, Tag
+from project.tags.forms import DeleteForm, TagForm
+from project import db
+from project.decorators import ensure_correct_user
+from flask_login import login_required
+
+
+
+tags_blueprint = Blueprint(
+ 'tags',
+ __name__,
+ template_folder='templates'
+)
+
+
+
+@tags_blueprint.route('/', methods=["GET", "POST"])
+@login_required
+def index():
+ if request.method == "POST":
+ form = TagForm(request.form)
+ form.set_choices()
+ if form.validate():
+ tag = Tag(form.text.data)
+ for message in form.messages.data:
+ tag.messages.append(Message.query.get(message))
+ db.session.add(tag)
+ db.session.commit()
+ flash('Tag Created!')
+ else:
+ return redirect(url_for('tags.new.html', form=form))
+ return render_template('tags/index.html', tags=Tag.query.all())
+
+@tags_blueprint.route('/new')
+@login_required
+def new():
+ form = TagForm()
+ form.set_choices()
+ return render_template('tags/new.html', form=form)
+
+@tags_blueprint.route('//edit')
+@login_required
+@ensure_correct_user
+def edit(id):
+ tag = Tag.query.get_or_404(id)
+ messages = [message.id for message in tag.messages]
+ form = TagForm(messages=messages)
+ form.set_choices()
+ delete_form = DeleteForm()
+ return render_template('tags/edit.html', tag=tag, form=form, delete_form=delete_form)
+
+@tags_blueprint.route('/', methods=["GET", "PATCH", "DELETE"])
+@login_required
+def show(id):
+ tag = Tag.query.get(id)
+ delete_form = DeleteForm()
+ if request.method == b"PATCH":
+ form = TagForm(request.form)
+ form.set_choices()
+ if form.validate():
+ tag.text = form.text.data
+ tag.messages = []
+ for message in form.messages.data:
+ tag.messages.append(Message.query.get(message))
+ db.session.add(tag)
+ db.session.commit()
+ flash('Tag Updated!')
+ return redirect(url_for('tags.index'))
+ else:
+ return render_template('tags/edit.html', form=form, tag=tag, delete_form=delete_form)
+ if request.method == b"DELETE":
+ delete_form = DeleteForm(request.form)
+ if delete_form.validate():
+ db.session.delete(tag)
+ db.session.commit()
+ flash('Tag Deleted!')
+ return redirect(url_for('tags.index'))
+ return render_template('tags/show.html', tag=tag, form=delete_form)
\ No newline at end of file
diff --git a/Unit-02/04-flask-login/project/templates/base.html b/Unit-02/04-flask-login/project/templates/base.html
new file mode 100644
index 0000000..a029487
--- /dev/null
+++ b/Unit-02/04-flask-login/project/templates/base.html
@@ -0,0 +1,50 @@
+
+
+
+
+ user-messages-login
+
+ {{ moment.include_moment() }}
+
+
+
+ {% with messages = get_flashed_messages() %}
+ {% if messages %}
+ {% for message in messages %}
+ {{message}}
+ {% endfor %}
+ {% endif %}
+ {% endwith %}
+
+
+ {% block content %}
+ {% endblock %}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Unit-02/04-flask-login/project/templates/errors.html b/Unit-02/04-flask-login/project/templates/errors.html
new file mode 100644
index 0000000..a3eb234
--- /dev/null
+++ b/Unit-02/04-flask-login/project/templates/errors.html
@@ -0,0 +1,8 @@
+{% extends 'base.html' %}
+
+{% block content %}
+ {{error}}
+
+ via GIPHY
+
+{% endblock %}
\ No newline at end of file
diff --git a/Unit-02/04-flask-login/project/users/forms.py b/Unit-02/04-flask-login/project/users/forms.py
new file mode 100644
index 0000000..860058b
--- /dev/null
+++ b/Unit-02/04-flask-login/project/users/forms.py
@@ -0,0 +1,16 @@
+from flask_wtf import FlaskForm
+from wtforms import StringField, PasswordField, validators
+
+class UserForm(FlaskForm):
+ first_name=StringField('First Name', [validators.DataRequired()],render_kw={"class":"col-4 text-center"})
+ last_name=StringField('Last Name', [validators.DataRequired()],render_kw={"class":"col-4 text-center"})
+ username=StringField('Username', [validators.DataRequired()],render_kw={"class":"col-4 text-center"})
+ password=PasswordField('Password', [validators.DataRequired()],render_kw={"class":"col-4 text-center"})
+
+class LoginForm(FlaskForm):
+ username=StringField('Username', [validators.DataRequired()],render_kw={"class":"col-4 text-center"})
+ password=PasswordField('Password', [validators.DataRequired()],render_kw={"class":"col-4 text-center"})
+
+
+class DeleteForm(FlaskForm):
+ pass
\ No newline at end of file
diff --git a/Unit-02/04-flask-login/project/users/templates/users/edit.html b/Unit-02/04-flask-login/project/users/templates/users/edit.html
new file mode 100644
index 0000000..bc652d3
--- /dev/null
+++ b/Unit-02/04-flask-login/project/users/templates/users/edit.html
@@ -0,0 +1,22 @@
+{% extends 'base.html' %}
+
+{% block content %}
+ Edit {{current_user.username}}
+
+{% endblock %}
\ No newline at end of file
diff --git a/Unit-02/04-flask-login/project/users/templates/users/index.html b/Unit-02/04-flask-login/project/users/templates/users/index.html
new file mode 100644
index 0000000..479f6e6
--- /dev/null
+++ b/Unit-02/04-flask-login/project/users/templates/users/index.html
@@ -0,0 +1,22 @@
+{% extends 'base.html' %}
+
+{% block content %}
+
+ All Users
+ Welcome back {{current_user.username}}
+ {% for user in users %}
+
+ {{user.first_name}} {{user.last_name}} member since {{ moment(user.created_on, local=True).fromNow() }}
+
+ Edit {{user.first_name}} |
+ See {{user.first_name}}'s messages
+
+
+
+ {% endfor %}
+
+{% endblock %}
diff --git a/Unit-02/04-flask-login/project/users/templates/users/login.html b/Unit-02/04-flask-login/project/users/templates/users/login.html
new file mode 100644
index 0000000..197e24d
--- /dev/null
+++ b/Unit-02/04-flask-login/project/users/templates/users/login.html
@@ -0,0 +1,24 @@
+{% extends 'base.html' %}
+
+{% block content %}
+ Login!
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/Unit-02/04-flask-login/project/users/templates/users/show.html b/Unit-02/04-flask-login/project/users/templates/users/show.html
new file mode 100644
index 0000000..e69de29
diff --git a/Unit-02/04-flask-login/project/users/templates/users/signup.html b/Unit-02/04-flask-login/project/users/templates/users/signup.html
new file mode 100644
index 0000000..7695779
--- /dev/null
+++ b/Unit-02/04-flask-login/project/users/templates/users/signup.html
@@ -0,0 +1,23 @@
+{% extends 'base.html' %}
+
+{% block content %}
+ New User Sign-Up
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/Unit-02/04-flask-login/project/users/views.py b/Unit-02/04-flask-login/project/users/views.py
new file mode 100644
index 0000000..1785357
--- /dev/null
+++ b/Unit-02/04-flask-login/project/users/views.py
@@ -0,0 +1,109 @@
+from flask import Blueprint, render_template, request, redirect, url_for, flash
+from project.users.forms import UserForm, LoginForm, DeleteForm
+from project import db, bcrypt
+from project.models import User
+from sqlalchemy.exc import IntegrityError
+from project.decorators import prevent_login_signup, ensure_correct_user
+from flask_login import login_user, logout_user, login_required
+
+
+users_blueprint = Blueprint(
+ 'users',
+ __name__,
+ template_folder = 'templates'
+)
+
+@login_required
+@users_blueprint.route('/')
+def index():
+ delete_form = DeleteForm()
+ return render_template('users/index.html', users=User.query.all(), delete_form=delete_form)
+
+
+@users_blueprint.route('/signup', methods=["GET","POST"])
+@prevent_login_signup
+def signup():
+ form = UserForm(request.form)
+ if form.validate():
+ try:
+ new_user = User(
+ form.first_name.data,
+ form.last_name.data,
+ form.username.data,
+ form.password.data)
+ db.session.add(new_user)
+ db.session.commit()
+ login_user(new_user)
+ flash('User Created!')
+ return redirect(url_for('users.index'))
+ except IntegrityError as e:
+ flash('Username already taken')
+ return render_template('users/signup.html', form=form)
+ return render_template('users/signup.html', form=form)
+
+
+@users_blueprint.route('/login', methods=["GET", "POST"])
+@prevent_login_signup
+def login():
+ form = LoginForm(request.form)
+ if request.method == "POST":
+ if form.validate():
+ authenticated_user = User.authenticate(form.username.data, form.password.data)
+ if authenticated_user:
+ login_user(authenticated_user)
+ flash("You are now logged in!")
+ return redirect(url_for('users.index'))
+ else:
+ flash('Invalid Credentials')
+ return redirect(url_for('users.login'))
+ return render_template('users/login.html', form=form)
+
+# @users_blueprint.route('/signup')
+# @prevent_login_signup
+# def new():
+# user_form = UserForm()
+# return render_template('users/signup.html', form=user_form)
+
+@users_blueprint.route('//edit')
+@login_required
+@ensure_correct_user
+def edit(id):
+ found_user = User.query.get(id)
+ user_form = UserForm(obj=found_user)
+ return render_template('users/edit.html', user=found_user, form=user_form)
+
+@users_blueprint.route('/', methods=["GET", "PATCH", "DELETE"])
+@login_required
+@ensure_correct_user
+def show(id):
+ found_user = User.query.get(id)
+ if request.method == b"PATCH":
+ form = UserForm(request.form)
+ if form.validate():
+ found_user.first_name = form.first_name.data
+ found_user.last_name = form.last_name.data
+ found_user.username = form.username.data
+ found_user.password = bcrypt.generate_password_hash(form.password.data).decode('UTF-8')
+ time = db.func.now()
+ found_user.updated_on = time
+ db.session.add(found_user)
+ db.session.commit()
+ flash('User Updated!')
+ return redirect(url_for('users.index'))
+ return render_template('users/edit.html', user=found_user, form=form)
+ if request.method ==b"DELETE":
+ delete_form = DeleteForm(request.form)
+ if delete_form.validate():
+ db.session.delete(found_user)
+ db.session.commit()
+ logout_user()
+ flash('User Deleted!')
+ return redirect(url_for('users.login'))
+ return render_template('users/show.html', user=found_user)
+
+@users_blueprint.route('/logout')
+@login_required
+def logout():
+ logout_user()
+ flash('Logged Out!')
+ return redirect(url_for('users.login'))
\ No newline at end of file
diff --git a/Unit-02/04-flask-login/requirements.txt b/Unit-02/04-flask-login/requirements.txt
new file mode 100644
index 0000000..9beaf71
--- /dev/null
+++ b/Unit-02/04-flask-login/requirements.txt
@@ -0,0 +1,41 @@
+alembic==0.9.6
+appnope==0.1.0
+bcrypt==3.1.4
+cffi==1.11.2
+click==6.7
+decorator==4.1.2
+Flask==0.12.2
+Flask-Bcrypt==0.7.1
+Flask-Login==0.4.1
+Flask-Migrate==2.1.1
+Flask-Modus==0.0.1
+Flask-Moment==0.5.2
+Flask-Script==2.0.6
+Flask-SQLAlchemy==2.3.2
+Flask-Testing==0.6.2
+Flask-WTF==0.14.2
+gunicorn==19.7.1
+ipython==6.2.1
+ipython-genutils==0.2.0
+itsdangerous==0.24
+jedi==0.11.0
+Jinja2==2.10
+Mako==1.0.7
+MarkupSafe==1.0
+parso==0.1.0
+pexpect==4.3.0
+pickleshare==0.7.4
+prompt-toolkit==1.0.15
+psycopg2==2.7.3.2
+ptyprocess==0.5.2
+pycparser==2.18
+Pygments==2.2.0
+python-dateutil==2.6.1
+python-editor==1.0.3
+simplegeneric==0.8.1
+six==1.11.0
+SQLAlchemy==1.1.15
+traitlets==4.3.2
+wcwidth==0.1.7
+Werkzeug==0.12.2
+WTForms==2.1
From 2c5dbf5af08f1f133d1bc26d7e4514d8f0a4a723 Mon Sep 17 00:00:00 2001
From: Stephen Carrera
Date: Wed, 6 Dec 2017 20:14:41 -0800
Subject: [PATCH 2/2] UI changes and auth adjustments
---
Unit-02/04-flask-login/project/__init__.py | 2 ++
.../04-flask-login/project/messages/views.py | 1 -
.../04-flask-login/project/static/style.css | 3 +++
.../project/tags/templates/tags/edit.html | 2 +-
.../project/templates/base.html | 16 +++++++++++---
.../project/users/templates/users/edit.html | 5 +++++
.../project/users/templates/users/index.html | 21 ++++++++++++++-----
Unit-02/04-flask-login/project/users/views.py | 9 ++++----
Unit-02/04-flask-login/requirements.txt | 8 +++++++
9 files changed, 53 insertions(+), 14 deletions(-)
create mode 100644 Unit-02/04-flask-login/project/static/style.css
diff --git a/Unit-02/04-flask-login/project/__init__.py b/Unit-02/04-flask-login/project/__init__.py
index cffa408..0f43cbc 100644
--- a/Unit-02/04-flask-login/project/__init__.py
+++ b/Unit-02/04-flask-login/project/__init__.py
@@ -4,6 +4,8 @@
from flask_moment import Moment
from flask_bcrypt import Bcrypt
from flask_login import LoginManager
+
+
import os
diff --git a/Unit-02/04-flask-login/project/messages/views.py b/Unit-02/04-flask-login/project/messages/views.py
index cf4002f..07278b6 100644
--- a/Unit-02/04-flask-login/project/messages/views.py
+++ b/Unit-02/04-flask-login/project/messages/views.py
@@ -34,7 +34,6 @@ def index(user_id):
@messages_blueprint.route('/new')
@login_required
-@ensure_correct_user
def new(user_id):
form = MessageForm()
form.set_choices()
diff --git a/Unit-02/04-flask-login/project/static/style.css b/Unit-02/04-flask-login/project/static/style.css
new file mode 100644
index 0000000..b511158
--- /dev/null
+++ b/Unit-02/04-flask-login/project/static/style.css
@@ -0,0 +1,3 @@
+ul {
+ list-style: none;
+}
diff --git a/Unit-02/04-flask-login/project/tags/templates/tags/edit.html b/Unit-02/04-flask-login/project/tags/templates/tags/edit.html
index e3318f8..d285740 100644
--- a/Unit-02/04-flask-login/project/tags/templates/tags/edit.html
+++ b/Unit-02/04-flask-login/project/tags/templates/tags/edit.html
@@ -1,7 +1,7 @@
{% extends 'base.html' %}
{% block content %}
- Edit the {{tag.text}} Tag
+ Edit the "{{tag.text}}" Tag