Commit d38ea703 authored by danmcquillan's avatar danmcquillan

adding lab-18: sqlalchemy

parent 73c6494d
from flask_wtf import FlaskForm
from wtforms import StringField
from wtforms import HiddenField
from wtforms import PasswordField
from wtforms import SubmitField
from wtforms import validators
class addTwitForm(FlaskForm):
twit = StringField('twit', validators = [validators.DataRequired()])
submit = SubmitField('submit', [validators.DataRequired()])
class editTwitForm(FlaskForm):
twit = StringField('twit', validators = [validators.DataRequired()])
twit_id = HiddenField('twit_id')
submit = SubmitField('submit', [validators.DataRequired()])
class loginForm(FlaskForm):
username = StringField('username', validators = [validators.DataRequired()])
password = PasswordField('password', validators =[validators.DataRequired()])
password2 = PasswordField('password2', validators=[validators.DataRequired(),
validators.EqualTo('password', message='Passwords must match')])
submit = SubmitField('submit', [validators.DataRequired()])
from flask_sqlalchemy import SQLAlchemy
import datetime
# we are using the second of flask-sqlalchemy's configuration options
# which is to create the sqlalchemy object here and bind it to the app once the
# app is initialised
# for more on this see http://flask-sqlalchemy.pocoo.org/2.3/api/#configuration
db = SQLAlchemy()
# the description of our data models follows those in the flask-sqlalchemy
# quickstart
# http://flask-sqlalchemy.pocoo.org/2.3/quickstart/
# for more detail see the doc section on declaring models
# http://flask-sqlalchemy.pocoo.org/2.3/models/
class Users(db.Model):
user_id = db.Column(db.Integer, primary_key = True)
username = db.Column(db.String(50))
hashed = db.Column(db.String(150))
salt = db.Column(db.String(150))
twits = db.relationship('Twits',backref='user', lazy=True)
# these are the user model attributes required by Flask-Login
# see
# https://flask-login.readthedocs.io/en/latest/#configuring-your-application
def get_id(self):
return self.user_id
def is_authenticated(self):
return True
def is_active(self):
return True
def is_anonymous(self):
return False
class Twits(db.Model):
twit_id = db.Column(db.Integer, primary_key = True)
twit = db.Column(db.String(140))
user_id = db.Column(db.Integer, db.ForeignKey('users.user_id'))
created_at = db.Column(db.DateTime, default=datetime.datetime.now)
def __repr__(self):
return self.twit
from flask import Flask, request
from flask import render_template
from flask import redirect, url_for
from flask import session, flash, abort
from vs_url_for import vs_url_for
from forms import addTwitForm, editTwitForm, loginForm
from flask_login import LoginManager, login_required
from flask_login import login_user, logout_user
# importing current_user from flask-login
# so that we can access the user object of the currently logged in user
# at any point (including in templates)
from flask_login import current_user
## import the sqlalchemy database and the users & twits db classes
from models import db,Users,Twits
## import the passwordhelper class to do password hashing / checking
from passwordhelper import PasswordHelper
login_manager = LoginManager()
app = Flask(__name__)
# configure the sqlalchenmy database URI that is used for the connection
# see http://flask-sqlalchemy.pocoo.org/2.3/config/
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://mytwits_user:mytwits_password@localhost/mytwits'
app.config['SQLALCHEMY_ECHO'] = False
# initialise the sqlalchemy database connection
db.init_app(app)
# initialise flask-login
login_manager.init_app(app)
app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'
# instantiate a password helper object
ph = PasswordHelper()
#---- the callback function for flask-login
@login_manager.user_loader
def load_user(user_id):
# we don't use sql any longer to get the user id; we access it through the
# User object provided by sqlalchemy
return Users.query.get(int(user_id))
@app.route('/')
def index():
# we don't use sql any longer to get the twits; we access them through the
# Twits object provided by sqlalchemy
twits = Twits.query.order_by(Twits.created_at.desc()).all()
return render_template("mytwits_mysql.html", twits=twits)
@app.route('/<username>')
def timeline(username):
# instead of having to think in terms of selecting twits using sql queries
# e.g.
# "select twit from twits where user_id = (select user_id from users where username = 'dan1' );"
# we can just use the objects provided by sqlalchemy
twits = Users.query.filter_by(username=username).first().twits
return render_template('timeline.html',twits=twits)
@app.route('/add_twit', methods = ['GET', 'POST'])
@login_required
def add_twit():
form = addTwitForm()
if form.validate_on_submit():
twit = form.twit.data
user_id = current_user.user_id
# SQLAlchemy adds an implicit constructor to all model classes which
# accepts keyword arguments for all its columns and relationships.
# We can use this to create our new twit
new_twit = Twits(twit=twit, user_id=user_id)
# As explained in http://flask-sqlalchemy.pocoo.org/2.3/queries/
# we need to add the object to the flask-sqlalchemy session
# and then commit our changes
# for it to be inserted in to the database
db.session.add(new_twit)
db.session.commit()
return redirect(vs_url_for('index'))
return render_template('add_twit_mysql.html',form=form)
@app.route('/edit_twit', methods = ['GET', 'POST'])
@login_required
def edit_twit():
user_id = current_user.user_id
form = editTwitForm()
if request.args.get('id'):
twit_id = request.args.get('id')
# again, we don't use sql to get the twit
# but instead we use the Twits class
# and the query attribute provided by flask-sqlalchemy
# see also
# http://flask-sqlalchemy.pocoo.org/2.3/queries/#querying-records
twit = Twits.query.filter_by(twit_id = twit_id).first()
# note that the query object is over all records
# to get the specific twit we use the first() methods
# having obtained the twits object we can access the text as
# the attribute twit.twit
form.twit.data = twit.twit
form.twit_id.data = twit_id
return render_template('edit_twit_mysql.html',form=form)
if form.validate_on_submit():
# get the twit_id back from the form
twit_id = form.twit_id.data
# load that twit
twit = Twits.query.filter_by(twit_id = twit_id).first()
# change the twit text to the text submitted in the form
twit.twit = form.twit.data
# commit the change
db.session.commit()
# so in summary, the way to update db entries is
# 1. retrieve the object you want to change
# 2. change the attribute
# 3. commit the session
# see also
# https://stackoverflow.com/questions/6699360/flask-sqlalchemy-update-a-rows-information#6701188
return redirect(vs_url_for('index'))
return render_template('edit_twit_mysql.html',form=form)
@app.route('/delete_twit', methods = ['GET', 'POST'])
@login_required
def delete_twit():
if request.args.get('id'):
twit_id = request.args.get('id')
twit_for_deletion = Twits.query.filter_by(twit_id = twit_id).first()
# deleting records is simple
# instead of add() use delete()
# see http://flask-sqlalchemy.pocoo.org/2.3/queries/#deleting-records
db.session.delete(twit_for_deletion)
db.session.commit()
return redirect(vs_url_for('index'))
@app.route('/login', methods = ['GET', 'POST'])
def login():
form = loginForm()
if form.validate_on_submit():
password = form.password.data
username = form.username.data
# get the user object via sqlalchemy
user = Users.query.filter_by(username=username).first()
# use the password helper to validate the password
if ph.validate_password(password,user.salt,user.hashed):
login_user(user)
flash('successfully logged in as {}'.format(user.username))
return redirect(url_for('index'))
else:
flash('please try again')
return render_template('login.html',form=form)
@app.route('/logout')
def logout():
# remove the username from the session if it's there
logout_user()
return redirect(vs_url_for('index'))
if __name__ == '__main__':
app.run(debug=True,host='0.0.0.0',port=8000)
import hashlib
import os
import base64
class PasswordHelper:
def get_hash(self, plain):
return hashlib.sha512(plain).hexdigest()
def get_salt(self):
return base64.b64encode(os.urandom(20))
def validate_password(self, plain, salt, expected):
return self.get_hash((salt+plain).encode('utf-8')) == expected
{% extends "base.html" %}
{% block body %}
<div class="jumbotron">
<div >
<form class="form-horizontal" method="post" action="/add_twit">
{{ form.csrf_token }}
<div class='form-group'>
<div class='col-md-6'>
{% if form.twit.errors %}
<ul class='errors'>
{% for error in form.twit.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
{{ form.twit(class='form-control',placeholder='twit') }}
</div>
</div>
<div class='form-group'>
<div class='col-md-6'>
{{ form.submit(class='btn btn-primary btn-block') }}
</div>
</div>
</form>
</div>
</div>
{% endblock %}
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.3/css/bootstrap.min.css"
integrity="sha384-Zug+QiDoJOrZ5t4lssLdxGhVrurbmBWopoEl+M6BdEfwnCJZtKxi1KgxUyJq13dy"
crossorigin="anonymous">
</head>
<body>
<div class="container">
<!--- flash messages -->
{% for category, message in get_flashed_messages(with_categories=true) %}
<div class="alert alert-dismissable alert-warning alert-{{ category }}">
<button type="button" class="close" data-dismiss="alert">&times;</button>
{{ message }}
</div>
{% endfor %}
{% if current_user.is_authenticated %}
<div class="lead mark">
logged in as {{ current_user.username }}
</div>
{% endif %}
<!--- the body will come from a template that inherits from this one -->
{% block body %}{% endblock %}
</div>
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.3/js/bootstrap.min.js" integrity="sha384-a5N7Y/aK3qNeh15eJKGWxsqtnX/wWdSZSKp+81YjTmS15nvnvxKHuzaWwXHDli+4" crossorigin="anonymous"></script>
</body>
</html>
{% extends "base.html" %}
{% block body %}
<div class="jumbotron">
<div >
<form class="form-horizontal" method="post" action="/edit_twit">
{{ form.csrf_token }}
<div class='form-group'>
<div class='col-md-6'>
{% if form.twit.errors %}
<ul class='errors'>
{% for error in form.twit.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
{{ form.twit(class='form-control') }}
</div>
</div>
<div>
{{ form.twit_id }}
</div>
<div class='form-group'>
<div class='col-md-6'>
{{ form.submit(class='btn btn-primary btn-block') }}
</div>
</div>
</form>
</div>
</div>
{% endblock %}
{% extends "base.html" %}
{% block body %}
<div class="jumbotron">
<form class='form-horizontal' method="post">
{{ form.csrf_token }}
<div class='form-group'>
<div class='col-md-6'>
{% if form.username.errors %}
<ul class='errors'>
{% for error in form.username.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
{{ form.username(class='form-control',placeholder='username') }}
</div>
</div>
<div class='form-group'>
<div class='col-md-6'>
{% if form.password.errors %}
<ul class='errors'>
{% for error in form.password.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
{{
form.password(class='form-control',placeholder='password') }}
</div>
</div>
<div class='form-group'>
<div class='col-md-6'>
{% if form.password2.errors %}
<ul class='errors'>
{% for error in form.password2.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
{{ form.password2(class='form-control',placeholder='confirm password') }}
</div>
</div>
<div class='form-group'>
<div class='col-md-6'>
{{ form.submit(class='btn btn-primary btn-block') }}
</div>
</div>
</form>
</div >
{% endblock %}
{% extends "base.html" %}
{% block body %}
<div class="jumbotron">
{% for twit in twits %}
<div class="row">
<div class="col-md-4">{{ twit.user.username }}</div>
<div class="col-md-4">{{ twit.twit }}</div>
<div class="col-md-4"><a href='/edit_twit?id={{ twit.twit_id }}'> edit </a><a
href='/delete_twit?id={{ twit.twit_id }}'> delete </a></div>
</div>
<div class="row">&nbsp;</div>
{% endfor %}
</div>
{% endblock %}
{% extends "base.html" %}
{% block body %}
<div class="jumbotron">
{% for twit in twits %}
<div class="row">
<div class="col-md-4">{{ twit.user.username }}</div>
<div class="col-md-4">{{ twit.twit }}</div>
<div class="col-md-4"><a href='/edit_twit?id={{ twit.twit_id }}'> edit </a><a
href='/delete_twit?id={{ twit.twit_id }}'> delete </a></div>
</div>
<div class="row">&nbsp;</div>
{% endfor %}
</div>
{% endblock %}
class User:
def __init__(self, user_id,username):
self.user_id = user_id
self.username = username
@property
def username(self):
return self.username
@property
def is_authenticated(self):
return True
@property
def is_active(self):
return True
@property
def is_anonymous(self):
return False
def get_id(self):
return self.user_id
def __repr__(self):
return 'user_id {}'.format(self.user_id)
File added
from flask import url_for
#URL_PREFIX = '/usr/253'
URL_PREFIX = ''
def vs_url_for(view):
url = url_for(view)
url = URL_PREFIX + url
return url
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment