Flask Tutorial User Profile, Error Handling, Add Post and Pagination - Part 2
Part 1 Tutorial
#app.py from flask import Flask, render_template, flash, redirect, url_for, request from flask_wtf import FlaskForm from wtforms import StringField, PasswordField, BooleanField, SubmitField, TextAreaField from wtforms.validators import DataRequired, ValidationError, Email, EqualTo, Length from flask_sqlalchemy import SQLAlchemy from datetime import datetime from werkzeug.security import generate_password_hash, check_password_hash from flask_login import LoginManager, UserMixin, current_user, login_user, logout_user, login_required import os path = "/" PEOPLE_FOLDER = os.path.join(path,'static', 'img') app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///blog.db' app.config['SECRET_KEY'] = 'cairocoders-ednalan' app.config['UPLOAD_FOLDER'] = PEOPLE_FOLDER login = LoginManager(app) login.login_view = 'login' db = SQLAlchemy(app) followers = db.Table( 'followers', db.Column('follower_id', db.Integer, db.ForeignKey('user.id')), db.Column('followed_id', db.Integer, db.ForeignKey('user.id')) ) class LoginForm(FlaskForm): username = StringField('Username', validators=[DataRequired()]) password = PasswordField('Password', validators=[DataRequired()]) remember_me = BooleanField('Remember Me') submit = SubmitField('Sign In') class RegistrationForm(FlaskForm): username = StringField('Username', validators=[DataRequired()]) email = StringField('Email', validators=[DataRequired(), Email()]) password = PasswordField('Password', validators=[DataRequired()]) password2 = PasswordField( 'Repeat Password', validators=[DataRequired(), EqualTo('password')]) submit = SubmitField('Register') def validate_username(self, username): user = User.query.filter_by(username=username.data).first() if user is not None: raise ValidationError('Please use a different username.') def validate_email(self, email): user = User.query.filter_by(email=email.data).first() if user is not None: raise ValidationError('Please use a different email address.') class EditProfileForm(FlaskForm): username = StringField('Username', validators=[DataRequired()]) about_me = TextAreaField('About me', validators=[Length(min=0, max=140)]) submit = SubmitField('Submit') def __init__(self, original_username, *args, **kwargs): super(EditProfileForm, self).__init__(*args, **kwargs) self.original_username = original_username def validate_username(self, username): if username.data != self.original_username: user = User.query.filter_by(username=self.username.data).first() if user is not None: raise ValidationError('Please use a different username.') class User(UserMixin, db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(64), index=True, unique=True) email = db.Column(db.String(120), index=True, unique=True) password_hash = db.Column(db.String(128)) posts = db.relationship('Post', backref='author', lazy='dynamic') about_me = db.Column(db.String(140)) last_seen = db.Column(db.DateTime, default=datetime.utcnow) profile_pic = db.Column(db.String(150)) #profile_pic = 'mypics.jpg' def __repr__(self): return ''.format(self.username) def set_password(self, password): self.password_hash = generate_password_hash(password) def check_password(self, password): return check_password_hash(self.password_hash, password) def avatar(self, profilepic): profilepic = os.path.join(app.config['UPLOAD_FOLDER'], self.profile_pic) return profilepic def followed_posts(self): followed = Post.query.join( followers, (followers.c.followed_id == Post.user_id)).filter( followers.c.follower_id == self.id) own = Post.query.filter_by(user_id=self.id) return followed.union(own).order_by(Post.timestamp.desc()) class Post(db.Model): id = db.Column(db.Integer, primary_key=True) body = db.Column(db.String(140)) timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow) user_id = db.Column(db.Integer, db.ForeignKey('user.id')) def __repr__(self): return ' '.format(self.body) class PostForm(FlaskForm): post = TextAreaField('Say something', validators=[DataRequired()]) submit = SubmitField('Submit') @login.user_loader def load_user(id): return User.query.get(int(id)) @app.before_request def before_request(): if current_user.is_authenticated: current_user.last_seen = datetime.utcnow() db.session.commit() @app.errorhandler(404) #404 page not found 500 internal server error def not_found_error(error): return render_template('404.html'), 404 @app.route('/', methods=['GET', 'POST']) @app.route('/index', methods=['GET', 'POST']) @login_required def index(): #user_post = {'username': 'cairocoders'} #hash = generate_password_hash('test1') #check_hash = check_password_hash(hash, 'test1') form = PostForm() if form.validate_on_submit(): post = Post(body=form.post.data, author=current_user) db.session.add(post) db.session.commit() flash('Your post is now live!') return redirect(url_for('index')) #posts = current_user.followed_posts().all() page = request.args.get('page', 1, type=int) posts = current_user.followed_posts().paginate(page, 5, False) #POSTS_PER_PAGE = 5 next_url = url_for('index', page=posts.next_num) \ if posts.has_next else None prev_url = url_for('index', page=posts.prev_num) \ if posts.has_prev else None return render_template('index.html', title='Home', form=form, posts=posts.items, next_url=next_url, prev_url=prev_url) #return render_template('index.html', title='Home', user_post=user_post, posts=posts, hash=hash, check_hash=check_hash) @app.route('/login', methods=['GET', 'POST']) def login(): if current_user.is_authenticated: return redirect(url_for('index')) form = LoginForm() if form.validate_on_submit(): user = User.query.filter_by(username=form.username.data).first() if user is None or not user.check_password(form.password.data): flash('Invalid username or password') return redirect(url_for('login')) login_user(user, remember=form.remember_me.data) return redirect(url_for('index')) return render_template('login.html', title='Sign In', form=form) @app.route('/logout') def logout(): logout_user() return redirect(url_for('index')) @app.route('/register', methods=['GET', 'POST']) def register(): if current_user.is_authenticated: return redirect(url_for('index')) form = RegistrationForm() if form.validate_on_submit(): user = User(username=form.username.data, email=form.email.data) user.set_password(form.password.data) db.session.add(user) db.session.commit() flash('Congratulations, you are now a registered user!') return redirect(url_for('login')) return render_template('register.html', title='Register', form=form) @app.route('/user/ ') @login_required def user(username): user = User.query.filter_by(username=username).first_or_404() page = request.args.get('page', 1, type=int) posts = user.posts.order_by(Post.timestamp.desc()).paginate(page, 5, False) next_url = url_for('user', username=user.username, page=posts.next_num) \ if posts.has_next else None prev_url = url_for('user', username=user.username, page=posts.prev_num) \ if posts.has_prev else None return render_template('user.html', user=user, posts=posts.items, next_url=next_url, prev_url=prev_url) @app.route('/edit_profile', methods=['GET', 'POST']) @login_required def edit_profile(): form = EditProfileForm(current_user.username) if form.validate_on_submit(): current_user.username = form.username.data current_user.about_me = form.about_me.data db.session.commit() flash('Your changes have been saved.') return redirect(url_for('edit_profile')) elif request.method == 'GET': form.username.data = current_user.username form.about_me.data = current_user.about_me return render_template('edit_profile.html', title='Edit Profile', form=form) @app.route('/explore') @login_required def explore(): page = request.args.get('page', 1, type=int) posts = Post.query.order_by(Post.timestamp.desc()).paginate(page, 5, False) next_url = url_for('explore', page=posts.next_num) \ if posts.has_next else None prev_url = url_for('explore', page=posts.prev_num) \ if posts.has_prev else None return render_template('index.html', title='Explore', posts=posts.items, next_url=next_url, prev_url=prev_url) if __name__ == '__main__': app.run(debug=True)
//templates/index.html {% extends "base.html" %} {% block content %} <div class="row"> <!--<p>hash, {{ hash }}!</p> <p>Check hash, {{ check_hash }}</p> --> <h1>Hi, {{ current_user.username }}!</h1> {% if form %} <form action="" method="post"> {{ form.hidden_tag() }} <p> {{ form.post.label }}<br> {{ form.post(cols=32, rows=4, class_="form-control") }}<br> {% for error in form.post.errors %} <span style="color: red;">[{{ error }}]</span> {% endfor %} </p> <p>{{ form.submit() }}</p> </form> {% endif %} {% for post in posts %} {% include '_post.html' %} {% endfor %} <p style="padding-bottom:50px;"> {% if prev_url %} <a href="{{ prev_url }}" class="btn btn-primary">Newer posts</a> {% endif %} {% if next_url %} <a href="{{ next_url }}" class="btn btn-primary">Older posts</a> {% endif %} <br/> </p> </div> {% endblock %}
//templates/base.html <html> <head> {% if title %} <title>{{ title }} - blog</title> {% else %} <title>Welcome to blog</title> {% endif %} <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" /> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script> <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}"> </head> <body> <nav class="navbar navbar-default"> <div class="container-fluid"> <div class="navbar-header"> <a class="navbar-brand" href="#">Cairocoders Blog</a> </div> <ul class="nav navbar-nav"> <li class="active"><a href="{{ url_for('index') }}">Home</a></li> <li><a href="{{ url_for('explore') }}">Post</a></li> <li><a href="#">About Us</a></li> <li><a href="#">Contact Us</a></li> </ul> {% if current_user.is_anonymous %} <ul class="nav navbar-nav navbar-right"> <li><a href="{{ url_for('login') }}"><span class="glyphicon glyphicon-log-in"></span> Login</a></li> </ul> {% else %} <ul class="nav navbar-nav navbar-right"> <li><a href="{{ url_for('user', username=current_user.username) }}"> Profile</a></li> <li><a href="{{ url_for('logout') }}"> Logout</a></li> </ul> {% endif %} </div> </nav> <div class="container"> <p>Flask Tutorial User Profile, Error Handling, Add Post and Pagination - Part 2</p> {% with messages = get_flashed_messages() %} {% if messages %} <ul> {% for message in messages %} <li>{{ message }}</li> {% endfor %} </ul> {% endif %} {% endwith %} {% block content %}{% endblock %} </div> </body> </html>
//templates/login.html {% extends "base.html" %} {% block content %} <h1>Sign In</h1> <form action="" method="post" novalidate> {{ form.hidden_tag() }} <p> {{ form.username.label }}<br> {{ form.username(size=32, class_="form-control") }}<br> {% for error in form.username.errors %} <span style="color: red;">[{{ error }}]</span> {% endfor %} </p> <p> {{ form.password.label }}<br> {{ form.password(size=32, class_="form-control") }}<br> {% for error in form.password.errors %} <span style="color: red;">[{{ error }}]</span> {% endfor %} </p> <p>{{ form.remember_me() }} {{ form.remember_me.label }}</p> <p>{{ form.submit() }}</p> </form> <p>New User? <a href="{{ url_for('register') }}">Click to Register!</a></p> {% endblock %}
//templates/register.html {% extends "base.html" %} {% block content %} <h1>Register</h1> <form action="" method="post"> {{ form.hidden_tag() }} <p> {{ form.username.label }}<br> {{ form.username(size=32, class_="form-control") }}<br> {% for error in form.username.errors %} <span style="color: red;">[{{ error }}]</span> {% endfor %} </p> <p> {{ form.email.label }}<br> {{ form.email(size=64, class_="form-control") }}<br> {% for error in form.email.errors %} <span style="color: red;">[{{ error }}]</span> {% endfor %} </p> <p> {{ form.password.label }}<br> {{ form.password(size=32, class_="form-control") }}<br> {% for error in form.password.errors %} <span style="color: red;">[{{ error }}]</span> {% endfor %} </p> <p> {{ form.password2.label }}<br> {{ form.password2(size=32, class_="form-control") }}<br> {% for error in form.password2.errors %} <span style="color: red;">[{{ error }}]</span> {% endfor %} </p> <p>{{ form.submit(class_="btn btn-primary") }}</p> </form> {% endblock %}
//user.html {% extends "base.html" %} {% block content %} <table> <tr valign="top"> <td><img src="{{ user.avatar(profilepic) }}" width="128"></td> <td style="padding-left:10px;"> <h1>User: {{ user.username }}</h1> {% if user.about_me %}<p>{{ user.about_me }}</p>{% endif %} {% if user.last_seen %}<p>Last seen on: {{ user.last_seen }}</p>{% endif %} {% if user == current_user %} <p><a href="{{ url_for('edit_profile') }}">Edit your profile</a></p> {% endif %} </td> </tr> </table> <hr> {% for post in posts %} {% include '_post.html' %} {% endfor %} {% endblock %}
//templates/_post.html <table class="table table-hover"> <tr valign="top"> <td width="36"><img src="{{ post.author.avatar(profilepics) }}" width="50"></td> <td align="left"><a href="{{ url_for('user', username=post.author.username) }}">{{ post.author.username }}</a> says:<br>{{ post.body }}</td> </tr> </table>
//templates/includes/_formhelpers.html {% macro render_field(field) %} {{ field.label }} {{ field(**kwargs)|safe }} {% if field.errors %} {% for error in field.errors %} <span class="help-inline">{{ error }}</span> {% endfor %} {% endif %} {% endmacro %}
//templates/404.html {% extends "base.html" %} {% block content %} <h1>File Not Found</h1> <p><a href="{{ url_for('index') }}">Back</a></p> {% endblock %}