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..0f43cbc
--- /dev/null
+++ b/Unit-02/04-flask-login/project/__init__.py
@@ -0,0 +1,54 @@
+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/
+ {{message.content}}
+
+ posted {{ moment(message.updated_on, local=True).fromNow() }}
+ {% for tag in message.tags %}
+
+ {{tag.text}}
+ 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}} 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..07278b6 --- /dev/null +++ b/Unit-02/04-flask-login/project/messages/views.py @@ -0,0 +1,80 @@ +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 +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('/
+ {{ tag.text }}
+
+ Edit | See More
+
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('/{{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}}
+ + + +{% 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..8480d36 --- /dev/null +++ b/Unit-02/04-flask-login/project/users/templates/users/edit.html @@ -0,0 +1,27 @@ +{% extends 'base.html' %} + +{% block content %} +
+ {{user.first_name.title()}} {{user.last_name.title()}} member since {{ moment(user.created_on, local=True).fromNow() }}
+
+ {% if current_user.id == user.id %}
+ Edit Profile |
+ {% endif %}
+
+ {% if current_user.id == user.id %}
+ Check Your Messages
+ {% else %}
+ See {{user.first_name.title()}}'s Messages
+ {% endif %}
+
+
+
+