(配置项目基本信息)
import os
from datetime import timedelta
# debug模式
DEBUG = True
# 配置数据库
DIALECT = 'mysql'
DRIVER = 'mysqldb'
USERNAME = 'root'
PASSWORD = '1234'
HOST = '127.0.0.1'
PORT = '3306'
DATABASE = 'questionplantform'
# SQLALCHEMY标志URI
SQLALCHEMY_DATABASE_URI = "{}+{}://{}:{}@{}:{}/{}?charset=utf8" \
.format(DIALECT, DRIVER, USERNAME,
PASSWORD, HOST, PORT,
DATABASE)
# 忽略SQLALCHEMY警告
SQLALCHEMY_TRACK_MODIFICATIONS = False
# session
# 设置session'盐':SECRET_KEY
SECRET_KEY = os.urandom(24)
# 设置session过期时间,25天,设置permanent默认一个月,没设置关闭浏览器就销毁
PERMANENT_SESSION_LIFETIME = timedelta(days=25)
(扩展文件,防止循环引用)
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
(数据库模型文件)
from exts import db
(终端脚本控制文件)
from flask_script import Manager
from flask_migrate import Migrate, MigrateCommand
from app import app
from exts import db
manager = Manager(app)
# 使用migrate绑定app和db
migrate = Migrate(app, db)
# 添加迁移脚本的命令到manager中去
manager.add_command('db', MigrateCommand)
if __name__ == "__main__":
manager.run()
新建base.html和base.css文件
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}{% endblock %}-问答平台title>
{% block head %}{% endblock %}
<link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css"
integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
crossorigin="anonymous">
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js">script>
<script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"
integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"
crossorigin="anonymous">script>
<link rel="stylesheet" href="{{ url_for('static', filename='css/base.css') }}">
head>
<body>
<nav class="navbar navbar-default">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigationspan>
<span class="icon-bar">span>
<span class="icon-bar">span>
<span class="icon-bar">span>
button>
<a class="navbar-brand" href="#">
<img class="logo"
src="{{ url_for('static', filename = 'images/logo.jpg') }}" alt="问答平台">
a>
div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li class="active">
<a href="#">首页
<span class="sr-only">(current)span>
a>
li>
<li>
<a href="#">发布问答a>
li>
ul>
<form class="navbar-form navbar-left">
<div class="form-group">
<input type="text" class="form-control" placeholder="输入搜索内容">
div>
<button type="submit" class="btn btn-default">搜索button>
form>
<ul class="nav navbar-nav navbar-right">
<li>
<a href="#">登录a>
li>
<li>
<a href="#">注册a>
li>
ul>
div>
div>
nav>
{% block body %}{% endblock %}
body>
html>
.logo {
width: 80px;
}
{% extends 'base.html' %}
{% block title%}
首页
{% endblock %}
{% block body %}
我是首页
{% endblock %}
创建模型
from exts import db
class User(db.Model):
__table_name__ = 'user'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
phone = db.Column(db.String(11), nullable=False)
username = db.Column(db.String(50), nullable=False)
password = db.Column(db.String(100), nullable=False)
数据库迁移
from flask_script import Manager
from flask_migrate import Migrate, MigrateCommand
from app import app
from exts import db
from models import User
manager = Manager(app)
# 使用migrate绑定app和db
migrate = Migrate(app, db)
# 添加迁移脚本的命令到manager中去
manager.add_command('db', MigrateCommand)
if __name__ == "__main__":
manager.run()
写好manage.py后,进入虚拟环境并activate,然后在虚拟环境中用cd..回退,进入到项目目录中命令:
python manage.py db init
初始化migrate
然后命令:
python manage.py db migrate
生成一个迁移文件
然后实时迁移更新数据库
python manage.py db upgrade
假设需求:在指定的函数执行之前先打印hell world
实现方案一:
在每个需要打印hello world的函数最前面都调用print_hello_world方法,如下(不够优雅)
# 需要在执行前打印hello world
def add(a, b):
print_hello_world()
c = a + b
print('结果c是:{}'.format(c))
# 需要在执行前打印hello world
def run():
print_hello_world()
print('run')
# 不需要在执行前打印hello world
def do_something():
print('do something')
add(1, 2)
run()
do_something()
用装饰器实现
# 装饰器实际上就是一个函数
# 有两个特别之处
# 1. 参数是一个函数
# 2. 返回值是一个函数
def print_hello_world_decorator(func):
def wrapper(*args, **kwargs):
print('hello world')
func(*args, **kwargs)
# 注意,这里返回的是一个函数体,不是函数执行完后返回
return wrapper
# 需要在执行前打印hello world
@print_hello_world_decorator
def run():
print('run')
# 需要在执行前打印hello world
@print_hello_world_decorator
def add(a, b):
c = a + b
print("相加结果c == {}".format(c))
# 不需要在执行前打印hello world
def do_something():
print('do something')
# 这样写没有参数是OK的
run()
# 有两个参数报错
# TypeError: wrapper() takes 0 positional arguments but 2 were given
# 解决办法是在wrapper和func两个函数都加上a,b参数
# 但是run函数又不行了,因为run函数没有参数
# 解决办法是在wrapper和func函数里传(*args, **kwargs)可以表示任何参数
add(1, 2)
do_something()
然而这样还是不行,当print(run.name_)run函数的名字时,如果加了装饰器修饰之后名称变为了wrapper。名字被偷换了,相当危险,可能连函数都找不到了,所以采用下面这种更为优雅的方式:from functools import wrapper
def print_hello_world_decorator(func):
# 只需要在这里再加个装饰器即可
@wraps(func)
def wrapper(*args, **kwargs):
print('hello world')
func(*args, **kwargs)
return wrapper
print("----------")
print(run.__name__)
print("----------")
总结:
先创建一个decorators.py,里面专门存放装饰器代码:
from functools import wraps
from flask import session, redirect, url_for
# 限制登陆的装饰器
def login_required(func):
@wraps(func)
def wrapper(*args, **kwargs):
if session.get('user_id'):
return func(*args, **kwargs)
else:
return redirect(url_for('login'))
return wrapper
在需要验证登录的视图函数之前加上该装饰器即可:
@app.route('/question/')
@login_required
def question():
return render_template('question.html')
models.py
from datetime import datetime
from exts import db
class User(db.Model):
__table_name__ = 'user'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
phone = db.Column(db.String(11), nullable=False)
username = db.Column(db.String(50), nullable=False)
password = db.Column(db.String(100), nullable=False)
class Question(db.Model):
__table_name__ = 'question'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
title = db.Column(db.String(100), nullable=False)
content = db.Column(db.Text, nullable=False)
# now()表示服务器第一次运行的时间
# now表示当前时间
create_time = db.Column(db.DateTime, default=datetime.now)
# 外键为user表中的id字段
author_id = db.Column(db.Integer, db.ForeignKey('user.id'))
# 按外键反转查找author信息,关系到User模型。然后反向引用得到该author的所有questions
author = db.relationship('User', backref=db.backref('questions'))
manage.py
from models import User, Question
命令:
迁移:python manage.py db migrate
更新:python manage.py db upgrade
index视图函数
@app.route('/')
def index():
context = {
# 按时间由近及远的顺序查找所有数据库中发布的问题
'questions': Question.query.order_by('-create_time').all()
}
return render_template('index.html', **context)
index.html
{% block main %}
<ul class="question-ul">
{% for question in questions %}
<li class="question-li">
<div class="avatar-group">
<img src="{{ url_for('static', filename='images/avatar.jpg') }}" alt=""
class="avatar">
div>
<div class="question-group">
<p class="question-title"><a href="#">{{ question.title }}a>p>
<p class="question-content">{{ question.content }}p>
<div class="question-info">
<span class="question-author">{{ question.author.username }}span>
<span class="question-date">{{ question.create_time }}span>
div>
div>
li>
{% endfor %}
ul>
{% endblock %}
优化方式是是使用g对象得到session数据,方便每个视图函数得到user。凡是需要得到session数据的视图函数都可以用到,即使是context_processor钩子函数也可以
@app.route('/question/', methods=['GET', 'POST'])
@login_required
def question():
if request.method == 'GET':
return render_template('question.html')
else:
title = request.form.get('title')
content = request.form.get('content')
if '' == title or '' == content:
return '输入不能为空!'
else:
new_question = Question(title=title, content=content)
# user_id = session.get('user_id')
# user = User.query.filter(User.id == user_id).first()
user = g.user
new_question.author = user
db.session.add(new_question)
db.session.commit()
return redirect(url_for('index'))
@app.route('/add_answer/', methods=['POST'])
@login_required
def add_answer():
answer_content = request.form.get('answer_content')
print(answer_content)
question_id = request.form.get('question_id')
answer = Answer(content=answer_content)
# user_id = session['user_id']
# user = User.query.filter(User.id == user_id).first()
user = g.user
answer.author = user
question_model = Question.query.filter(Question.id == question_id).first()
answer.question = question_model
db.session.add(answer)
db.session.commit()
return redirect(url_for('detail', question_id=question_id))
# question_model = Question.query.filter(Question.id == question_id).first()
# return render_template('question_detail.html', question=question_model)
@app.context_processor
def my_context_processor():
# user_id = session.get('user_id')
# # 这是两种情况,其中一种没处理返回就报:TypeError: 'NoneType' object is not iterable
# if user_id:
# user = User.query.filter(User.id == user_id).first()
# if user:
# return {'user': user}
# return {}
if hasattr(g, 'user'):
return {'user': g.user}
else:
return {}
# 优化代码,用before_request和g对象避免重复劳动
@app.before_request
def my_before_request():
user_id = session.get('user_id')
if user_id:
user = User.query.filter(User.id == user_id).first()
if user:
g.user = user
models.py
class User(db.Model):
__table_name__ = 'user'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
phone = db.Column(db.String(11), nullable=False)
username = db.Column(db.String(50), nullable=False)
password = db.Column(db.String(100), nullable=False)
# 加密
def __init__(self, *args, **kwargs):
phone = kwargs.get('phone')
username = kwargs.get('username')
password = kwargs.get('password')
self.phone = phone
self.username = username
# 截取密码之后加密,存入数据库
self.password = generate_password_hash(password)
# 解密, raw_password是用户输入的密码
def check_password(self, raw_password):
# self.password是数据库中存的加密的密码
result = check_password_hash(self.password, raw_password)
return result
login.py
@app.route('/login/', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
return render_template('login.html')
else:
phone = request.form.get('phone')
password = request.form.get('password')
user = User.query.filter(User.phone == phone).first()
if user and user.check_password(password):
session['user_id'] = user.id
# 默认session一个月后过期,但是在配置文件中设置为了25天
session.permanent = True
return redirect(url_for('index'))
else:
return '账号密码错误!'