initial
commit
82dcc03a07
|
@ -0,0 +1,9 @@
|
|||
env
|
||||
temp
|
||||
tmp
|
||||
migrations
|
||||
|
||||
*.pyc
|
||||
*.sqlite
|
||||
*.coverage
|
||||
.DS_Store
|
|
@ -0,0 +1,7 @@
|
|||
# Config file for testing at travis-ci.org
|
||||
|
||||
language: python
|
||||
python:
|
||||
- "2.7"
|
||||
install: "pip install -r requirements.txt"
|
||||
script: nosetests
|
|
@ -0,0 +1,35 @@
|
|||
# Flask Skeleton
|
||||
|
||||
Flask starter...
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Set Environment Variables
|
||||
|
||||
Update the configuration setting files in "/project/config" and then run:
|
||||
|
||||
```sh
|
||||
$ export APP_SETTINGS="project.config.development_config"
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```sh
|
||||
$ export APP_SETTINGS="project.config.production_config"
|
||||
```
|
||||
|
||||
### Create DB
|
||||
|
||||
```sh
|
||||
$ python manage.py create_db
|
||||
$ python manage.py db init
|
||||
$ python manage.py db migrate
|
||||
$ python manage.py create_admin
|
||||
$ python manage.py create_data
|
||||
```
|
||||
|
||||
### Run the Application
|
||||
|
||||
```sh
|
||||
$ python manage.py runserver
|
||||
```
|
|
@ -0,0 +1,77 @@
|
|||
# manage.py
|
||||
|
||||
|
||||
import os
|
||||
import unittest
|
||||
import coverage
|
||||
|
||||
from flask.ext.script import Manager
|
||||
from flask.ext.migrate import Migrate, MigrateCommand
|
||||
|
||||
from project import app, db
|
||||
from project.models import User
|
||||
|
||||
|
||||
migrate = Migrate(app, db)
|
||||
manager = Manager(app)
|
||||
|
||||
# migrations
|
||||
manager.add_command('db', MigrateCommand)
|
||||
|
||||
|
||||
@manager.command
|
||||
def test():
|
||||
"""Runs the unit tests without coverage."""
|
||||
tests = unittest.TestLoader().discover('tests')
|
||||
unittest.TextTestRunner(verbosity=2).run(tests)
|
||||
|
||||
|
||||
@manager.command
|
||||
def cov():
|
||||
"""Runs the unit tests with coverage."""
|
||||
cov = coverage.coverage(
|
||||
branch=True,
|
||||
include='project/*',
|
||||
omit=['*/__init__.py', '*/config/*']
|
||||
)
|
||||
cov.start()
|
||||
tests = unittest.TestLoader().discover('tests')
|
||||
unittest.TextTestRunner(verbosity=2).run(tests)
|
||||
cov.stop()
|
||||
cov.save()
|
||||
print 'Coverage Summary:'
|
||||
cov.report()
|
||||
basedir = os.path.abspath(os.path.dirname(__file__))
|
||||
covdir = os.path.join(basedir, 'tmp/coverage')
|
||||
cov.html_report(directory=covdir)
|
||||
print('HTML version: file://%s/index.html' % covdir)
|
||||
cov.erase()
|
||||
|
||||
|
||||
@manager.command
|
||||
def create_db():
|
||||
"""Creates the db tables."""
|
||||
db.create_all()
|
||||
|
||||
|
||||
@manager.command
|
||||
def drop_db():
|
||||
"""Drops the db tables."""
|
||||
db.drop_all()
|
||||
|
||||
|
||||
@manager.command
|
||||
def create_admin():
|
||||
"""Creates the admin user."""
|
||||
db.session.add(User(email='ad@min.com', password='admin', admin=True))
|
||||
db.session.commit()
|
||||
|
||||
|
||||
@manager.command
|
||||
def create_data():
|
||||
"""Creates sample data."""
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
manager.run()
|
|
@ -0,0 +1,79 @@
|
|||
# project/__init__.py
|
||||
|
||||
|
||||
#################
|
||||
#### imports ####
|
||||
#################
|
||||
|
||||
import os
|
||||
|
||||
from flask import Flask, render_template
|
||||
from flask.ext.login import LoginManager
|
||||
from flask.ext.bcrypt import Bcrypt
|
||||
from flask.ext.debugtoolbar import DebugToolbarExtension
|
||||
from flask_bootstrap import Bootstrap
|
||||
from flask.ext.sqlalchemy import SQLAlchemy
|
||||
|
||||
|
||||
################
|
||||
#### config ####
|
||||
################
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config.from_object(os.environ['APP_SETTINGS'])
|
||||
|
||||
|
||||
####################
|
||||
#### extensions ####
|
||||
####################
|
||||
|
||||
login_manager = LoginManager()
|
||||
login_manager.init_app(app)
|
||||
bcrypt = Bcrypt(app)
|
||||
toolbar = DebugToolbarExtension(app)
|
||||
bootstrap = Bootstrap(app)
|
||||
db = SQLAlchemy(app)
|
||||
|
||||
|
||||
###################
|
||||
### blueprints ####
|
||||
###################
|
||||
|
||||
from project.user.views import user_blueprint
|
||||
from project.main.views import main_blueprint
|
||||
app.register_blueprint(user_blueprint)
|
||||
app.register_blueprint(main_blueprint)
|
||||
|
||||
|
||||
###################
|
||||
### flask-login ####
|
||||
###################
|
||||
|
||||
from models import User
|
||||
|
||||
login_manager.login_view = "user.login"
|
||||
login_manager.login_message_category = 'danger'
|
||||
|
||||
|
||||
@login_manager.user_loader
|
||||
def load_user(user_id):
|
||||
return User.query.filter(User.id == int(user_id)).first()
|
||||
|
||||
|
||||
########################
|
||||
#### error handlers ####
|
||||
########################
|
||||
|
||||
@app.errorhandler(403)
|
||||
def forbidden_page(error):
|
||||
return render_template("errors/403.html"), 403
|
||||
|
||||
|
||||
@app.errorhandler(404)
|
||||
def page_not_found(error):
|
||||
return render_template("errors/404.html"), 404
|
||||
|
||||
|
||||
@app.errorhandler(500)
|
||||
def server_error_page(error):
|
||||
return render_template("errors/500.html"), 500
|
|
@ -0,0 +1 @@
|
|||
# project/config/__init__.py
|
|
@ -0,0 +1,14 @@
|
|||
# project/config/development_config.py
|
||||
|
||||
import os
|
||||
basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
|
||||
|
||||
|
||||
SECRET_KEY = 'my_precious'
|
||||
DEBUG = True
|
||||
BCRYPT_LOG_ROUNDS = 13
|
||||
WTF_CSRF_ENABLED = False
|
||||
DEBUG_TB_ENABLED = True
|
||||
DEBUG_TB_INTERCEPT_REDIRECTS = False
|
||||
CACHE_TYPE = 'simple'
|
||||
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'dev.sqlite')
|
|
@ -0,0 +1,11 @@
|
|||
# project/config/production_config.py
|
||||
|
||||
|
||||
SECRET_KEY = 'my_precious'
|
||||
DEBUG = False
|
||||
BCRYPT_LOG_ROUNDS = 13
|
||||
WTF_CSRF_ENABLED = True
|
||||
DEBUG_TB_ENABLED = False
|
||||
DEBUG_TB_INTERCEPT_REDIRECTS = False
|
||||
CACHE_TYPE = 'simple'
|
||||
SQLALCHEMY_DATABASE_URI = 'postgresql://localhost/example'
|
|
@ -0,0 +1,12 @@
|
|||
# project/config/test_config.py
|
||||
|
||||
|
||||
TESTING = True
|
||||
SECRET_KEY = 'my_precious'
|
||||
DEBUG = True
|
||||
BCRYPT_LOG_ROUNDS = 1
|
||||
WTF_CSRF_ENABLED = False
|
||||
DEBUG_TB_ENABLED = False
|
||||
DEBUG_TB_INTERCEPT_REDIRECTS = False
|
||||
CACHE_TYPE = 'simple'
|
||||
SQLALCHEMY_DATABASE_URI = 'sqlite://'
|
|
@ -0,0 +1 @@
|
|||
# project/public/__init__.py
|
|
@ -0,0 +1,30 @@
|
|||
# project/main/views.py
|
||||
|
||||
|
||||
#################
|
||||
#### imports ####
|
||||
#################
|
||||
|
||||
from flask import render_template, Blueprint
|
||||
|
||||
|
||||
################
|
||||
#### config ####
|
||||
################
|
||||
|
||||
main_blueprint = Blueprint('main', __name__,)
|
||||
|
||||
|
||||
################
|
||||
#### routes ####
|
||||
################
|
||||
|
||||
|
||||
@main_blueprint.route('/')
|
||||
def home():
|
||||
return render_template('main/home.html')
|
||||
|
||||
|
||||
@main_blueprint.route("/about/")
|
||||
def about():
|
||||
return render_template("main/about.html")
|
|
@ -0,0 +1,38 @@
|
|||
# project/models.py
|
||||
|
||||
|
||||
import datetime
|
||||
|
||||
from project import db, bcrypt
|
||||
|
||||
|
||||
class User(db.Model):
|
||||
|
||||
__tablename__ = "users"
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
email = db.Column(db.String(255), unique=True, nullable=False)
|
||||
password = db.Column(db.String(255), nullable=False)
|
||||
registered_on = db.Column(db.DateTime, nullable=False)
|
||||
admin = db.Column(db.Boolean, nullable=False, default=False)
|
||||
|
||||
def __init__(self, email, password, admin=False):
|
||||
self.email = email
|
||||
self.password = bcrypt.generate_password_hash(password)
|
||||
self.registered_on = datetime.datetime.now()
|
||||
self.admin = admin
|
||||
|
||||
def is_authenticated(self):
|
||||
return True
|
||||
|
||||
def is_active(self):
|
||||
return True
|
||||
|
||||
def is_anonymous(self):
|
||||
return False
|
||||
|
||||
def get_id(self):
|
||||
return unicode(self.id)
|
||||
|
||||
def __repr__(self):
|
||||
return '<User {0}>'.format(self.email)
|
|
@ -0,0 +1,5 @@
|
|||
/* custom css */
|
||||
|
||||
.site-content {
|
||||
padding-top: 75px;
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
// custom javascript
|
|
@ -0,0 +1,66 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Flask Skeleton{% block title %}{% endblock %}</title>
|
||||
<!-- meta -->
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<!-- styles -->
|
||||
<link href="//maxcdn.bootstrapcdn.com/bootswatch/3.3.1/yeti/bootstrap.min.css" rel="stylesheet" media="screen">
|
||||
<link href="{{url_for('static', filename='main.css')}}" rel="stylesheet" media="screen">
|
||||
{% block css %}{% endblock %}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
{% include 'header.html' %}
|
||||
|
||||
<div class="site-content">
|
||||
<div class="container">
|
||||
|
||||
<!-- messages -->
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
<br>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
{% for category, message in messages %}
|
||||
<div class="alert alert-{{ category }}">
|
||||
<a class="close" title="Close" href="#" data-dismiss="alert">×</a>
|
||||
{{message}}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
|
||||
<!-- child template -->
|
||||
{% block content %}{% endblock %}
|
||||
|
||||
<br>
|
||||
|
||||
<!-- errors -->
|
||||
{% if error %}
|
||||
<p class="error"><strong>Error:</strong> {{ error }}</p>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br><br>
|
||||
|
||||
{% include 'footer.html' %}
|
||||
|
||||
<!-- scripts -->
|
||||
<script src="//code.jquery.com/jquery-1.11.2.min.js" type="text/javascript"></script>
|
||||
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js" type="text/javascript"></script>
|
||||
<script src="{{url_for('static', filename='js/main.js')}}" type="text/javascript"></script>
|
||||
{% block js %}{% endblock %}
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,12 @@
|
|||
{% extends "_base.html" %}
|
||||
|
||||
{% block page_title %}- Unauthorized{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="jumbotron">
|
||||
<div class="text-center">
|
||||
<h1>401</h1>
|
||||
<p>You are not authorized to view this page. Please <a href="{{ url_for('main.login')}}">log in</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -0,0 +1,12 @@
|
|||
{% extends "_base.html" %}
|
||||
|
||||
{% block page_title %}- Page Not Found{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="jumbotron">
|
||||
<div class="text-center">
|
||||
<h1>404</h1>
|
||||
<p>Sorry. The requested page doesn't exist. Go <a href="{{ url_for('main.home')}}">home</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -0,0 +1,12 @@
|
|||
{% extends "_base.html" %}
|
||||
|
||||
{% block page_title %}- Server Error{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="jumbotron">
|
||||
<div class="text-center">
|
||||
<h1>500</h1>
|
||||
<p>Sorry. Something went terribly wrong. Go <a href="{{ url_for('main.home')}}">home</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -0,0 +1,7 @@
|
|||
<footer>
|
||||
<div class="container">
|
||||
<small>
|
||||
<span>© <a href="mailto:michael@realpython.com">Michael Herman</a></span>
|
||||
</small>
|
||||
</div>
|
||||
</footer>
|
|
@ -0,0 +1,35 @@
|
|||
<header class="site-header">
|
||||
<!-- Navigation -->
|
||||
<nav class="navbar navbar-default navbar-fixed-top" role="navigation">
|
||||
<div class="container">
|
||||
<!-- Brand and toggle get grouped for better mobile display -->
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="{{ url_for('main.home') }}">Flask-Skeleton</a>
|
||||
</div>
|
||||
<!-- Collect the nav links, forms, and other content for toggling -->
|
||||
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
|
||||
<ul class="nav navbar-nav">
|
||||
<li><a href="{{ url_for('main.about') }}">About</a></li>
|
||||
{% if current_user.is_authenticated() %}
|
||||
<li><a href="{{ url_for('user.members') }}">Members</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
{% if current_user.is_authenticated() %}
|
||||
<li><a href="{{ url_for('user.logout') }}">Logout</a></li>
|
||||
{% else %}
|
||||
<li><a href="{{ url_for('user.login') }}"><span class="glyphicon glyphicon-user"></span> Register/Login</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
<!-- /.navbar-collapse -->
|
||||
</div>
|
||||
<!-- /.container -->
|
||||
</nav>
|
||||
</header>
|
|
@ -0,0 +1,11 @@
|
|||
{% extends "_base.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="body-content">
|
||||
<div class="row">
|
||||
<h1>About</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,11 @@
|
|||
{% extends "_base.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="body-content">
|
||||
<div class="row">
|
||||
<h1>Welcome!</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,33 @@
|
|||
{% extends '_base.html' %}
|
||||
{% import "bootstrap/wtf.html" as wtf %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<header class="content-header">
|
||||
<h1>Please login</h1>
|
||||
</header>
|
||||
|
||||
<br>
|
||||
|
||||
|
||||
<form class="form" role="form" method="post" action="">
|
||||
|
||||
{{ form.csrf_token }}
|
||||
{{ form.hidden_tag() }}
|
||||
{{ wtf.form_errors(form, hiddens="only") }}
|
||||
|
||||
|
||||
<div class="col-lg-4 col-sm-4">
|
||||
|
||||
{{ wtf.form_field(form.email) }}
|
||||
{{ wtf.form_field(form.password) }}
|
||||
|
||||
<button class="btn btn-success" type="submit">Sign In!</button>
|
||||
<br><br>
|
||||
<p>Need to <a href="{{ url_for('user.register') }}">Register</a>?</p>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
|
||||
{% endblock content %}
|
|
@ -0,0 +1,5 @@
|
|||
{% extends "_base.html" %}
|
||||
{% block content %}
|
||||
<h1>Welcome, <em>{{ current_user.email }}</em>!</h1>
|
||||
<h3>This is the members-only page.</h3>
|
||||
{% endblock %}
|
|
@ -0,0 +1,35 @@
|
|||
{% extends '_base.html' %}
|
||||
{% import "bootstrap/wtf.html" as wtf %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<header class="content-header">
|
||||
<h1>Please Register</h1>
|
||||
</header>
|
||||
|
||||
<br>
|
||||
|
||||
|
||||
<form class="form" role="form" method="post" action="">
|
||||
|
||||
{{ form.csrf_token }}
|
||||
{{ form.hidden_tag() }}
|
||||
{{ wtf.form_errors(form, hiddens="only") }}
|
||||
|
||||
|
||||
<div class="col-lg-4 col-sm-4">
|
||||
|
||||
{{ wtf.form_field(form.email) }}
|
||||
{{ wtf.form_field(form.password) }}
|
||||
{{ wtf.form_field(form.confirm) }}
|
||||
|
||||
<button class="btn btn-success" type="submit">Register!</button>
|
||||
<br><br>
|
||||
<p>Already have an account? <a href="{{ url_for('user.login') }}">Sign in</a>.</p>
|
||||
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
|
||||
{% endblock content %}
|
|
@ -0,0 +1 @@
|
|||
# project/user/__init__.py
|
|
@ -0,0 +1,28 @@
|
|||
# project/user/forms.py
|
||||
|
||||
|
||||
from flask_wtf import Form
|
||||
from wtforms import TextField, PasswordField
|
||||
from wtforms.validators import DataRequired, Email, Length, EqualTo
|
||||
|
||||
|
||||
class LoginForm(Form):
|
||||
email = TextField('Email Address', [DataRequired(), Email()])
|
||||
password = PasswordField('Password', [DataRequired()])
|
||||
|
||||
|
||||
class RegisterForm(Form):
|
||||
email = TextField(
|
||||
'Email Address',
|
||||
validators=[DataRequired(), Email(message=None), Length(min=6, max=40)])
|
||||
password = PasswordField(
|
||||
'Password',
|
||||
validators=[DataRequired(), Length(min=6, max=25)]
|
||||
)
|
||||
confirm = PasswordField(
|
||||
'Confirm password',
|
||||
validators=[
|
||||
DataRequired(),
|
||||
EqualTo('password', message='Passwords must match.')
|
||||
]
|
||||
)
|
|
@ -0,0 +1,73 @@
|
|||
# project/user/views.py
|
||||
|
||||
|
||||
#################
|
||||
#### imports ####
|
||||
#################
|
||||
|
||||
from flask import render_template, Blueprint, url_for, \
|
||||
redirect, flash, request
|
||||
from flask.ext.login import login_user, logout_user, login_required
|
||||
|
||||
from project import bcrypt, db
|
||||
from project.models import User
|
||||
from project.user.forms import LoginForm, RegisterForm
|
||||
|
||||
################
|
||||
#### config ####
|
||||
################
|
||||
|
||||
user_blueprint = Blueprint('user', __name__,)
|
||||
|
||||
|
||||
################
|
||||
#### routes ####
|
||||
################
|
||||
|
||||
@user_blueprint.route('/register', methods=['GET', 'POST'])
|
||||
def register():
|
||||
form = RegisterForm(request.form)
|
||||
if form.validate_on_submit():
|
||||
user = User(
|
||||
email=form.email.data,
|
||||
password=form.password.data
|
||||
)
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
|
||||
login_user(user)
|
||||
|
||||
flash('Thank you for registering.', 'success')
|
||||
return redirect(url_for("user.members"))
|
||||
|
||||
return render_template('user/register.html', form=form)
|
||||
|
||||
|
||||
@user_blueprint.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
form = LoginForm(request.form)
|
||||
if form.validate_on_submit():
|
||||
user = User.query.filter_by(email=form.email.data).first()
|
||||
if user and bcrypt.check_password_hash(
|
||||
user.password, request.form['password']):
|
||||
login_user(user)
|
||||
flash('You are logged in. Welcome!', 'success')
|
||||
return redirect(url_for('user.members'))
|
||||
else:
|
||||
flash('Invalid email and/or password.', 'danger')
|
||||
return render_template('user/login.html', form=form)
|
||||
return render_template('user/login.html', title='Please Login', form=form)
|
||||
|
||||
|
||||
@user_blueprint.route('/logout')
|
||||
@login_required
|
||||
def logout():
|
||||
logout_user()
|
||||
flash('You were logged out. Bye!', 'success')
|
||||
return redirect(url_for('main.home'))
|
||||
|
||||
|
||||
@user_blueprint.route('/members')
|
||||
@login_required
|
||||
def members():
|
||||
return render_template('user/members.html')
|
|
@ -0,0 +1,22 @@
|
|||
Flask==0.10.1
|
||||
Flask-Bcrypt==0.6.0
|
||||
Flask-Bootstrap==3.3.0.1
|
||||
Flask-DebugToolbar==0.9.0
|
||||
Flask-Login==0.2.11
|
||||
Flask-Migrate==1.2.0
|
||||
Flask-SQLAlchemy==2.0
|
||||
Flask-Script==2.0.5
|
||||
Flask-Testing==0.4.2
|
||||
Flask-WTF==0.10.3
|
||||
Jinja2==2.7.3
|
||||
Mako==1.0.0
|
||||
MarkupSafe==0.23
|
||||
SQLAlchemy==0.9.8
|
||||
WTForms==2.0.2
|
||||
Werkzeug==0.9.6
|
||||
alembic==0.7.4
|
||||
blinker==1.3
|
||||
coverage==4.0a2
|
||||
itsdangerous==0.24
|
||||
py-bcrypt==0.4
|
||||
wsgiref==0.1.2
|
|
@ -0,0 +1,24 @@
|
|||
# tests/base.py
|
||||
|
||||
|
||||
from flask.ext.testing import TestCase
|
||||
|
||||
from project import app, db
|
||||
from project.models import User
|
||||
|
||||
|
||||
class BaseTestCase(TestCase):
|
||||
|
||||
def create_app(self):
|
||||
app.config.from_object('project.config.test_config')
|
||||
return app
|
||||
|
||||
def setUp(self):
|
||||
db.create_all()
|
||||
user = User(email="ad@min.com", password="admin_user")
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
|
||||
def tearDown(self):
|
||||
db.session.remove()
|
||||
db.drop_all()
|
|
@ -0,0 +1 @@
|
|||
# tests/helpers.py
|
|
@ -0,0 +1,54 @@
|
|||
# tests/test_config.py
|
||||
|
||||
|
||||
import unittest
|
||||
|
||||
from flask import current_app
|
||||
from flask.ext.testing import TestCase
|
||||
|
||||
from project import app
|
||||
|
||||
|
||||
class TestDevelopmentConfig(TestCase):
|
||||
|
||||
def create_app(self):
|
||||
app.config.from_object('project.config.development_config')
|
||||
return app
|
||||
|
||||
def test_app_is_development(self):
|
||||
self.assertFalse(current_app.config['TESTING'])
|
||||
self.assertTrue(app.config['DEBUG'] is True)
|
||||
self.assertTrue(app.config['WTF_CSRF_ENABLED'] is False)
|
||||
self.assertTrue(app.config['DEBUG_TB_ENABLED'] is True)
|
||||
self.assertFalse(current_app is None)
|
||||
|
||||
|
||||
class TestTestingConfig(TestCase):
|
||||
|
||||
def create_app(self):
|
||||
app.config.from_object('project.config.test_config')
|
||||
return app
|
||||
|
||||
def test_app_is_testing(self):
|
||||
self.assertTrue(current_app.config['TESTING'])
|
||||
self.assertTrue(app.config['DEBUG'] is True)
|
||||
self.assertTrue(app.config['BCRYPT_LOG_ROUNDS'] == 1)
|
||||
self.assertTrue(app.config['WTF_CSRF_ENABLED'] is False)
|
||||
|
||||
|
||||
class TestProductionConfig(TestCase):
|
||||
|
||||
def create_app(self):
|
||||
app.config.from_object('project.config.production_config')
|
||||
return app
|
||||
|
||||
def test_app_is_production(self):
|
||||
self.assertFalse(current_app.config['TESTING'])
|
||||
self.assertTrue(app.config['DEBUG'] is False)
|
||||
self.assertTrue(app.config['DEBUG_TB_ENABLED'] is False)
|
||||
self.assertTrue(app.config['WTF_CSRF_ENABLED'] is True)
|
||||
self.assertTrue(app.config['BCRYPT_LOG_ROUNDS'] == 13)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
|
@ -0,0 +1,32 @@
|
|||
# tests/test_main.py
|
||||
|
||||
|
||||
import unittest
|
||||
|
||||
from base import BaseTestCase
|
||||
|
||||
|
||||
class TestMainBlueprint(BaseTestCase):
|
||||
|
||||
def test_index(self):
|
||||
# Ensure Flask is setup.
|
||||
response = self.client.get('/', follow_redirects=True)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn('Welcome!', response.data)
|
||||
self.assertIn('Register/Login', response.data)
|
||||
|
||||
def test_about(self):
|
||||
# Ensure about route behaves correctly.
|
||||
response = self.client.get('/about', follow_redirects=True)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn('About', response.data)
|
||||
|
||||
def test_404(self):
|
||||
# Ensure 404 error is handled.
|
||||
response = self.client.get('/404')
|
||||
self.assert404(response)
|
||||
self.assertTemplateUsed('errors/404.html')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
|
@ -0,0 +1,116 @@
|
|||
# tests/test_user.py
|
||||
|
||||
|
||||
import datetime
|
||||
import unittest
|
||||
|
||||
from flask.ext.login import current_user
|
||||
|
||||
from base import BaseTestCase
|
||||
from project import bcrypt
|
||||
from project.models import User
|
||||
from project.user.forms import LoginForm
|
||||
|
||||
|
||||
class TestUserBlueprint(BaseTestCase):
|
||||
|
||||
def test_correct_login(self):
|
||||
# Ensure login behaves correctly with correct credentials.
|
||||
with self.client:
|
||||
response = self.client.post(
|
||||
'/login',
|
||||
data=dict(email="ad@min.com", password="admin_user"),
|
||||
follow_redirects=True
|
||||
)
|
||||
self.assertIn('Welcome', response.data)
|
||||
self.assertIn('Logout', response.data)
|
||||
self.assertIn('Members', response.data)
|
||||
self.assertTrue(current_user.email == "ad@min.com")
|
||||
self.assertTrue(current_user.is_active())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_logout_behaves_correctly(self):
|
||||
# Ensure logout behaves correctly - regarding the session.
|
||||
with self.client:
|
||||
self.client.post(
|
||||
'/login',
|
||||
data=dict(email="ad@min.com", password="admin_user"),
|
||||
follow_redirects=True
|
||||
)
|
||||
response = self.client.get('/logout', follow_redirects=True)
|
||||
self.assertIn('You were logged out. Bye!', response.data)
|
||||
self.assertFalse(current_user.is_active())
|
||||
|
||||
def test_logout_route_requires_login(self):
|
||||
# Ensure logout route requres logged in user.
|
||||
response = self.client.get('/logout', follow_redirects=True)
|
||||
self.assertIn('Please log in to access this page', response.data)
|
||||
|
||||
def test_member_route_requires_login(self):
|
||||
# Ensure member route requres logged in user.
|
||||
response = self.client.get('/members', follow_redirects=True)
|
||||
self.assertIn('Please log in to access this page', response.data)
|
||||
|
||||
def test_validate_success_login_form(self):
|
||||
# Ensure correct data validates.
|
||||
form = LoginForm(email='ad@min.com', password='admin_user')
|
||||
self.assertTrue(form.validate())
|
||||
|
||||
def test_validate_invalid_email_format(self):
|
||||
# Ensure invalid email format throws error.
|
||||
form = LoginForm(email='unknown', password='example')
|
||||
self.assertFalse(form.validate())
|
||||
|
||||
def test_get_by_id(self):
|
||||
# Ensure id is correct for the current/logged in user.
|
||||
with self.client:
|
||||
self.client.post('/login', data=dict(
|
||||
email='ad@min.com', password='admin_user'
|
||||
), follow_redirects=True)
|
||||
self.assertTrue(current_user.id == 1)
|
||||
|
||||
def test_registered_on_defaults_to_datetime(self):
|
||||
# Ensure that registered_on is a datetime.
|
||||
with self.client:
|
||||
self.client.post('/login', data=dict(
|
||||
email='ad@min.com', password='admin_user'
|
||||
), follow_redirects=True)
|
||||
user = User.query.filter_by(email='ad@min.com').first()
|
||||
self.assertIsInstance(user.registered_on, datetime.datetime)
|
||||
|
||||
def test_check_password(self):
|
||||
# Ensure given password is correct after unhashing.
|
||||
user = User.query.filter_by(email='ad@min.com').first()
|
||||
self.assertTrue(bcrypt.check_password_hash(user.password, 'admin_user'))
|
||||
self.assertFalse(bcrypt.check_password_hash(user.password, 'foobar'))
|
||||
|
||||
def test_validate_invalid_password(self):
|
||||
# Ensure user can't login when the pasword is incorrect.
|
||||
with self.client:
|
||||
response = self.client.post('/login', data=dict(
|
||||
email='ad@min.com', password='foo_bar'
|
||||
), follow_redirects=True)
|
||||
self.assertIn('Invalid email and/or password.', response.data)
|
||||
|
||||
def test_register_route(self):
|
||||
# Ensure about route behaves correctly.
|
||||
response = self.client.get('/register', follow_redirects=True)
|
||||
self.assertIn('<h1>Please Register</h1>\n', response.data)
|
||||
|
||||
def test_user_registration(self):
|
||||
# Ensure registration behaves correctlys.
|
||||
with self.client:
|
||||
response = self.client.post(
|
||||
'/register',
|
||||
data=dict(email="test@tester.com", password="testing",
|
||||
confirm="testing"),
|
||||
follow_redirects=True
|
||||
)
|
||||
self.assertIn('Welcome', response.data)
|
||||
self.assertTrue(current_user.email == "test@tester.com")
|
||||
self.assertTrue(current_user.is_active())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
Loading…
Reference in New Issue