Skip to content

Commit 17f7170

Browse files
author
rain
committed
web框架
1 parent db7840d commit 17f7170

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+1808
-0
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Flasky
2+
======
3+
4+
This repository contains the source code examples for my O'Reilly book [Flask Web Development](http://www.flaskbook.com).
5+
6+
The commits and tags in this repository were carefully created to match the sequence in which concepts are presented in the book. Please read the section titled "How to Work with the Example Code" in the book's preface for instructions.
7+

app/__init__.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
from flask import Flask
2+
from flask.ext.bootstrap import Bootstrap
3+
from flask.ext.mail import Mail
4+
from flask.ext.moment import Moment
5+
from flask.ext.sqlalchemy import SQLAlchemy
6+
from flask.ext.login import LoginManager
7+
from flask.ext.pagedown import PageDown
8+
from config import config
9+
10+
bootstrap = Bootstrap()
11+
mail = Mail()
12+
moment = Moment()
13+
db = SQLAlchemy()
14+
pagedown = PageDown()
15+
16+
login_manager = LoginManager()
17+
login_manager.session_protection = 'strong'
18+
login_manager.login_view = 'auth.login'
19+
20+
21+
def create_app(config_name):
22+
app = Flask(__name__)
23+
app.config.from_object(config[config_name])
24+
config[config_name].init_app(app)
25+
26+
bootstrap.init_app(app)
27+
mail.init_app(app)
28+
moment.init_app(app)
29+
db.init_app(app)
30+
login_manager.init_app(app)
31+
pagedown.init_app(app)
32+
33+
if not app.debug and not app.testing and not app.config['SSL_DISABLE']:
34+
from flask.ext.sslify import SSLify
35+
sslify = SSLify(app)
36+
37+
from .main import main as main_blueprint
38+
app.register_blueprint(main_blueprint)
39+
40+
from .auth import auth as auth_blueprint
41+
app.register_blueprint(auth_blueprint, url_prefix='/auth')
42+
43+
from .api_1_0 import api as api_1_0_blueprint
44+
app.register_blueprint(api_1_0_blueprint, url_prefix='/api/v1.0')
45+
46+
return app

app/api_1_0/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from flask import Blueprint
2+
3+
api = Blueprint('api', __name__)
4+
5+
from . import authentication, users, errors

app/api_1_0/authentication.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# encoding: utf-8
2+
from flask import g, jsonify
3+
from flask.ext.httpauth import HTTPBasicAuth
4+
from ..models import User, AnonymousUser
5+
from . import api
6+
from .errors import unauthorized, forbidden
7+
8+
auth = HTTPBasicAuth()
9+
10+
11+
@auth.verify_password
12+
def verify_password(email_or_token, password):
13+
if email_or_token == '':
14+
g.current_user = AnonymousUser()
15+
return True
16+
if password == '':
17+
g.current_user = User.verify_auth_token(email_or_token)
18+
g.token_used = True
19+
return g.current_user is not None
20+
user = User.query.filter_by(email=email_or_token).first()
21+
if not user:
22+
return False
23+
g.current_user = user
24+
g.token_used = False
25+
return user.verify_password(password)
26+
27+
28+
@auth.error_handler
29+
def auth_error():
30+
return unauthorized('Invalid credentials')
31+
32+
33+
@api.before_request
34+
@auth.login_required
35+
def before_request():
36+
if not g.current_user.is_anonymous and \
37+
not g.current_user.confirmed:
38+
return forbidden('未授权的用户.')
39+
40+
41+
@api.route('/token')
42+
def get_token():
43+
if g.current_user.is_anonymous or g.token_used:
44+
return unauthorized('Invalid credentials')
45+
return jsonify({'token': g.current_user.generate_auth_token(
46+
expiration=3600), 'expiration': 3600})

app/api_1_0/decorators.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from functools import wraps
2+
from flask import g
3+
from .errors import forbidden
4+
5+
6+
def permission_required(permission):
7+
def decorator(f):
8+
@wraps(f)
9+
def decorated_function(*args, **kwargs):
10+
if not g.current_user.can(permission):
11+
return forbidden('Insufficient permissions')
12+
return f(*args, **kwargs)
13+
return decorated_function
14+
return decorator

app/api_1_0/errors.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from flask import jsonify
2+
from app.exceptions import ValidationError
3+
from . import api
4+
5+
6+
def bad_request(message):
7+
response = jsonify({'error': 'bad request', 'message': message})
8+
response.status_code = 400
9+
return response
10+
11+
12+
def unauthorized(message):
13+
response = jsonify({'error': 'unauthorized', 'message': message})
14+
response.status_code = 401
15+
return response
16+
17+
18+
def forbidden(message):
19+
response = jsonify({'error': 'forbidden', 'message': message})
20+
response.status_code = 403
21+
return response
22+
23+
24+
@api.errorhandler(ValidationError)
25+
def validation_error(e):
26+
return bad_request(e.args[0])

app/api_1_0/users.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from flask import jsonify, request, current_app, url_for
2+
from . import api
3+
from ..models import User
4+
5+
6+
@api.route('/users/<int:id>')
7+
def get_user(id):
8+
user = User.query.get_or_404(id)
9+
return jsonify(user.to_json())

app/auth/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from flask import Blueprint
2+
3+
auth = Blueprint('auth', __name__)
4+
5+
from . import views

app/auth/forms.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# encoding: utf-8
2+
from flask.ext.wtf import Form
3+
from wtforms import StringField, PasswordField, BooleanField, SubmitField
4+
from wtforms.validators import Required, Length, Email, Regexp, EqualTo
5+
from wtforms import ValidationError
6+
from ..models import User
7+
import sys
8+
reload(sys)
9+
sys.setdefaultencoding('utf-8')
10+
11+
12+
class LoginForm(Form):
13+
email = StringField('邮箱', validators=[Required(), Length(1, 64),
14+
Email()])
15+
password = PasswordField('密码', validators=[Required()])
16+
remember_me = BooleanField('下次自动登录')
17+
submit = SubmitField('登录')
18+
19+
20+
class RegistrationForm(Form):
21+
email = StringField('邮箱', validators=[Required(), Length(1, 64),
22+
Email()])
23+
username = StringField('用户名', validators=[
24+
Required(), Length(1, 64), Regexp('^[A-Za-z][A-Za-z0-9_.]*$', 0,
25+
'用户名只能有字母, '
26+
'数字, 点 or 下划线')])
27+
password = PasswordField('密码', validators=[
28+
Required(), EqualTo('password2', message='密码不一致.')])
29+
password2 = PasswordField('密码确认', validators=[Required()])
30+
submit = SubmitField('注册')
31+
32+
def validate_email(self, field):
33+
if User.query.filter_by(email=field.data).first():
34+
raise ValidationError('邮箱已注册.')
35+
36+
def validate_username(self, field):
37+
if User.query.filter_by(username=field.data).first():
38+
raise ValidationError('用户名已注册.')
39+
40+
41+
class ChangePasswordForm(Form):
42+
old_password = PasswordField('旧密码', validators=[Required()])
43+
password = PasswordField('新密码', validators=[
44+
Required(), EqualTo('password2', message='Passwords must match')])
45+
password2 = PasswordField('确认密码', validators=[Required()])
46+
submit = SubmitField('提交')

app/auth/views.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# encoding: utf-8
2+
from flask import render_template, redirect, request, url_for, flash, current_app
3+
from flask.ext.login import login_user, logout_user, login_required, \
4+
current_user
5+
from . import auth
6+
from .. import db
7+
from ..models import User
8+
from ..email import send_email
9+
from .forms import LoginForm, RegistrationForm, ChangePasswordForm
10+
from ..decorators import admin_required
11+
12+
13+
@auth.before_app_request
14+
def before_request():
15+
if current_user.is_authenticated:
16+
current_user.ping()
17+
if not current_user.confirmed \
18+
and request.endpoint[:5] != 'auth.' \
19+
and request.endpoint != 'static':
20+
return redirect(url_for('auth.unconfirmed'))
21+
22+
23+
@auth.route('/unconfirmed')
24+
def unconfirmed():
25+
if current_user.is_anonymous or current_user.confirmed:
26+
return redirect(url_for('main.index'))
27+
return render_template('auth/unconfirmed.html')
28+
29+
30+
@auth.route('/login', methods=['GET', 'POST'])
31+
def login():
32+
form = LoginForm()
33+
if form.validate_on_submit():
34+
user = User.query.filter_by(email=form.email.data).first()
35+
if user is not None and user.verify_password(form.password.data):
36+
login_user(user, form.remember_me.data)
37+
return redirect(request.args.get('next') or url_for('main.index'))
38+
flash('用户名或密码不正确.')
39+
return render_template('auth/login.html', form=form)
40+
41+
42+
@auth.route('/logout')
43+
@login_required
44+
def logout():
45+
logout_user()
46+
flash('您已退出登录.')
47+
return redirect(url_for('main.index'))
48+
49+
50+
@auth.route('/register', methods=['GET', 'POST'])
51+
def register():
52+
form = RegistrationForm()
53+
if form.validate_on_submit():
54+
user = User(email=form.email.data,
55+
username=form.username.data,
56+
password=form.password.data)
57+
db.session.add(user)
58+
db.session.commit()
59+
# token = user.generate_confirmation_token()
60+
# send_email(user.email, 'Confirm Your Account',
61+
# 'auth/email/confirm', user=user, token=token)
62+
flash('注册申请已提交, 等待管理员确认.')
63+
return redirect(url_for('auth.login'))
64+
return render_template('auth/register.html', form=form)
65+
66+
67+
@auth.route('/list-users')
68+
@login_required
69+
@admin_required
70+
def list_users():
71+
page = request.args.get('page', 1, type=int)
72+
query = User.query
73+
pagination = query.paginate(
74+
page, per_page=current_app.config['FLASKY_USERS_PER_PAGE'],
75+
error_out=False)
76+
users = pagination.items
77+
return render_template('auth/confirm.html', pagination=pagination, users=users)
78+
79+
80+
@auth.route('/confirm/<int:user_id>', methods=['GET', 'POST'])
81+
@login_required
82+
@admin_required
83+
def confirm(user_id):
84+
user = User.query.filter_by(id=user_id).first()
85+
user.confirm()
86+
flash('授权成功.')
87+
return redirect(url_for('auth.list_users'))
88+
89+
90+
@auth.route('/unconfirm/<int:user_id>')
91+
@login_required
92+
@admin_required
93+
def unconfirm(user_id):
94+
user = User.query.filter_by(id=user_id).first()
95+
user.unconfirm()
96+
flash('取消授权成功.')
97+
return redirect(url_for('auth.list_users'))
98+
99+
100+
@auth.route('/change-password', methods=['GET', 'POST'])
101+
@login_required
102+
def change_password():
103+
form = ChangePasswordForm()
104+
if form.validate_on_submit():
105+
if current_user.verify_password(form.old_password.data):
106+
current_user.password = form.password.data
107+
db.session.add(current_user)
108+
flash('Your password has been updated.')
109+
return redirect(url_for('main.index'))
110+
else:
111+
flash('Invalid password.')
112+
return render_template("auth/change_password.html", form=form)

app/decorators.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from functools import wraps
2+
from flask import abort
3+
from flask.ext.login import current_user
4+
from .models import Permission
5+
6+
7+
def permission_required(permission):
8+
def decorator(f):
9+
@wraps(f)
10+
def decorated_function(*args, **kwargs):
11+
if not current_user.can(permission):
12+
abort(403)
13+
return f(*args, **kwargs)
14+
return decorated_function
15+
return decorator
16+
17+
18+
def admin_required(f):
19+
return permission_required(Permission.ADMINISTER)(f)

app/email.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from threading import Thread
2+
from flask import current_app, render_template
3+
from flask.ext.mail import Message
4+
from . import mail
5+
6+
7+
def send_async_email(app, msg):
8+
with app.app_context():
9+
mail.send(msg)
10+
11+
12+
def send_email(to, subject, template, **kwargs):
13+
app = current_app._get_current_object()
14+
msg = Message(app.config['FLASKY_MAIL_SUBJECT_PREFIX'] + ' ' + subject,
15+
sender=app.config['FLASKY_MAIL_SENDER'], recipients=[to])
16+
msg.body = render_template(template + '.txt', **kwargs)
17+
msg.html = render_template(template + '.html', **kwargs)
18+
thr = Thread(target=send_async_email, args=[app, msg])
19+
thr.start()
20+
return thr

app/exceptions.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
class ValidationError(ValueError):
2+
pass

app/main/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from flask import Blueprint
2+
3+
main = Blueprint('main', __name__)
4+
5+
from . import views, errors
6+
from ..models import Permission
7+
8+
9+
@main.app_context_processor
10+
def inject_permissions():
11+
return dict(Permission=Permission)

0 commit comments

Comments
 (0)