制作如图所示的表结构:
1.posts.py里面代码如下:
from flask_wtf import FlaskForm
from wtforms import TextAreaField,SubmitField
from wtforms.validators import DataRequired,Length
class PostsForm(FlaskForm):
content = TextAreaField('',render_kw={'placeholder':'这一刻不想说点什么'},validators=[DataRequired(),Length(min=10,max=140,message='说话不要说太多注意分寸')])
submit = SubmitField('即刻发表')
2.users.py里面的代码如下:
from flask_wtf import FlaskForm
from wtforms import StringField,PasswordField,SubmitField,BooleanField
from wtforms.validators import DataRequired,Length,Email,EqualTo
from wtforms.validators import ValidationError
from apps.models import User
from flask_wtf.file import FileField,FileRequired,FileAllowed
from apps.exts import photos
#用户注册表单
class RegisterForm(FlaskForm):
username = StringField('用户名',validators=[DataRequired(),Length(6,20,message="用户名在6到20位之间")])
password = PasswordField('密码',validators=[DataRequired(),Length(6,30,message='密码必须在6到30位之间')])
confirm = PasswordField('确认密码',validators=[EqualTo('password',message='两次密码必须一致')])
email = StringField('邮箱',validators=[Email(message='邮箱格式不正确')])
submit = SubmitField('立即注册')
#用户名 和 邮箱 这两个 光符合上面的要求还不够 收到用户提交后
#从数据库进行查询判断是否已经存在
def validate_username(self,field):
if User.query.filter_by(username=field.data).first():
raise ValidationError('该用户名已经存在,请更换其它用户名')
def validate_email(self,field):
if User.query.filter_by(email=field.data).first():
raise ValidationError('该邮箱名已经存在,请更换其它邮箱名')
class LoginForm(FlaskForm):
username = StringField('用户名', validators=[DataRequired(), Length(6, 20, message="用户名在6到20位之间")])
password = PasswordField('密码', validators=[DataRequired(), Length(6, 30, message='密码必须在6到30位之间')])
remember = BooleanField('记住我')
submit = SubmitField('立即登录')
class UploadForm(FlaskForm):
icon = FileField('头像',validators=[FileRequired('请选择头像'),FileAllowed(photos,message='只能上传图片')])
submit = SubmitField('点击上传')
3.init.py里面的代码如下:
from .users import RegisterForm
from .users import LoginForm,UploadForm
from .posts import PostsForm
4.posts.py里面代码如下:
from apps.exts import db
from datetime import datetime
# id rid
# 1 0
# 2 0
# 3 0
# 4 1
# 5 1
# 6 1
class Posts(db.Model):
__tablename__ = 'posts'
id = db.Column(db.Integer,primary_key=True,autoincrement=True)
rid = db.Column(db.Integer,index=True,default=0)
content = db.Column(db.Text)
pub_time = db.Column(db.DateTime,default=datetime.utcnow)
uid = db.Column(db.Integer,db.ForeignKey("users.id"))
author = db.relationship("User",backref="postes")
5.users.py里面的代码如下:
from flask import current_app
from apps.exts import db,login_manager
from werkzeug.security import generate_password_hash,check_password_hash
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
from flask_login import UserMixin
class User(UserMixin,db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer,primary_key=True)
username = db.Column(db.String(32),unique=True)
password_hash = db.Column(db.String(256))
email = db.Column(db.String(64),unique=True)
#是否激活
confirmed = db.Column(db.Boolean,default=False)
icon = db.Column(db.String(128),default='default.jpg')
@property #把方法可以当成属性来调用
def password(self):
raise AttributeError('密码不可读属性')
# password 对外
#对内 password_hash 加密后
#密码永不返回
#密码不可读
#password表示的是用户传递过来的密码
@password.setter
def password(self,password):
self.password_hash = generate_password_hash(password)
#密码校验 参数为用户提交的密码
# 先对用户的提交的密码 进行加密 然后跟数据库中存在的加密的密码进行比较
#正确返回True 否则返回False
def verify_password(self,password):
return check_password_hash(self.password_hash,password)
#生成token的方法 设置过期时间
def generate_token(self,expires_in=3600):
s = Serializer(current_app.config['SECRET_KEY'],expires_in=expires_in)
return s.dumps({'id':self.id}) #把用户的id 藏到加密的字符串中
#服务器收到以后 解密 然后拿出id 然后就知道 是哪个用户要激活
@staticmethod
def check_activate_token(token):
s = Serializer(current_app.config['SECRET_KEY'])
try:
data = s.loads(token) #解密 之前加密的字符串
except:
return False
u = User.query.get(data.get('id')) #解密后拿到 藏的用户id
#根据这个id 取出用户的详细信息
if not u:
return False
if not u.confirmed:
u.confirmed = True
db.session.commit()
return True
#登录认证的回调
#登录成功以后存的是用的id
#需要一个方法根据用户的id 取出用户的详细信息
@login_manager.user_loader
def load_user(uid):
return User.query.get(uid)
6.init.py里面的代码如下:
from .users import User
from .posts import Posts
7.micro.html里面的代码如下:
{#第一个参数是 分页对象 第二个参数 跳转的时候 指定的 url#}
{% macro pagination_show(pagination,endpoint) %}
<nav aria-label="Page navigation">
<ul class="pagination">
{# 上一页#}
<li {% if not pagination.has_prev %}class="disabled" {% endif %}>
<a href="{% if pagination.has_prev %}{{ url_for(endpoint,p=pagination.prev_num,**kwargs) }}{% else %}#{% endif %}" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
{# 中间页码#}
{% for pp in pagination.iter_pages() %}
{% if pp %}
<li {% if pagination.page == pp%} class="active" {% endif %}><a href="{{ url_for(endpoint,p=pp,*kwargs) }}">{{ pp }}</a></li>
{% else %}
<li><a href="#">…</a></li>
{% endif %}
{% endfor %}
{# 下一页#}
<li {% if not pagination.has_next %}class="disabled" {% endif %}>
<a href="{% if pagination.has_next %}{{ url_for(endpoint,p=pagination.next_num,**kwargs) }}{% else %}#{% endif %}" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
</nav>
{% endmacro %}
8.base.html里面的代码如下:
{% extends 'bootstrap/base.html' %}
{% import 'bootstrap/wtf.html' as wtf %}
{#定制标题#}
{% block title %}{% endblock %}
{#定制导航条#}
{#定制脚本#}
{% block scripts %}
{# 父模板有jquery 所以我们直接用super就可以导入#}
{{ super() }}
{# 导入moment.js #}
{{ moment.include_moment() }}
{# 支持中文显示#}
{{ moment.locale('zh-CN') }}
{%- endblock scripts %}
{% block navbar %}
<nav class="navbar navbar-inverse">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<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 navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">2002博客</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 class="active"><a href="#">博客 <span class="sr-only">(current)</span></a></li>
<li><a href="#">Link</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">Action</a></li>
<li><a href="#">Another action</a></li>
<li><a href="#">Something else here</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">Separated link</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">One more separated link</a></li>
</ul>
</li>
</ul>
<form class="navbar-form navbar-left">
<div class="form-group">
<input type="text" class="form-control" placeholder="Search">
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
{% if current_user.is_authenticated %}
<ul class="nav navbar-nav navbar-right">
<li><a href="{{ url_for('users.logout_demo') }}">退出</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{ current_user.username }} <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">修改密码</a></li>
<li><a href="#">修改邮箱</a></li>
<li><a href="{{ url_for('users.change_icon')}}">修改头像</a></li>
<li><a href="#">设置</a></li>
</ul>
</li>
</ul>
{% else %}
<ul class="nav navbar-nav navbar-right">
<li><a href="{{ url_for('users.login') }}">登录</a></li>
<li><a href="{{ url_for('users.register') }}">注册</a></li>
</ul>
{% endif %}
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
{%- endblock navbar %}
{#定制内容#}
{% block content -%}
{% for message in get_flashed_messages() %}
<div class="container">
<div class="alert alert-warning alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
<strong>Warning!</strong>{{ message }}
</div>
{% endfor %}
{% block page_content %}
{% endblock %}
</div>
{%- endblock content %}
9.activate.html里面的代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>hello {{ username }}</h1>
<p>请点击以下链接完成激活</p>
{# https://mail.qq.com/users/activate/eyJhbGciOiJIUzUxMiIsImlhdCI6MTU5ODM0MDgxNSwiZXhwIjoxNTk4MzQ0NDE1fQ.eyJpZCI6Nn0.atiUoLRuUOXi3P2hmzfT20MhFIFcGj4MsxIhLJnR1F6pdkn2E7TpA_DX8R8SvC4h_3A9eHa3hw-JFPvVuVgLKg/#}
{# 默认前面是 qq.com 163.com等 加上 _external=True以后 就变成我们自己的
主机地址#}
<a href="{{ url_for('users.activate_user',token=token,_external=True) }}">点我激活</a>
</body>
</html>
10.activate.txt里面的代码如下:
<h1>hello {{ username }}</h1>
<p>请点击以下链接完成激活</p>
<a href="{{ url_for('users.activate_user',token=token) }}">点我激活</a>
11.index.html里面的代码如下:
{% extends 'common/base.html' %}
{% from 'common/macro/macro.html' import pagination_show%}
{% block title %}
博客首页
{% endblock %}
{% block content %}
{% block page_content %}
{{ wtf.quick_form(form) }}
{% for p in posts %}
<div class="media">
<div class="media-left">
<a href="#">
<img class="media-object" src="{{ url_for('static',filename='uploads/'+p.author.icon) }}"
alt="头像">
</a>
</div>
<div class="media-body">
<div style="float: right">{{ moment(p.pub_time).fromNow() }}</div>
<h4 class="media-heading">{{ p.author.username }}</h4>
{{ p.content }}
</div>
</div>
{% endfor %}
{% endblock %}
{{ pagination_show(pagination,'main.index') }}
{% endblock %}
12.change_icon.html里面的代码如下:
{% extends 'common/base.html' %}
{% block title %}
修改头像
{% endblock %}
{% block page_content %}
{% if img_url %}
<img src="{{ img_url }}" alt="">
{% endif %}
{{ wtf.quick_form(form) }}
{% endblock %}
13.login.html里面的代码如下:
{% extends 'common/base.html' %}
{% block title %}
欢迎登录
{% endblock %}
{% block page_content %}
{{ wtf.quick_form(form) }}
{% endblock %}
<input type="text" placeholder="请输入用户名">
14.register.html里面的代码如下:
{% extends 'common/base.html' %}
{% block title %}
欢迎注册
{% endblock %}
{% block page_content %}
{{ wtf.quick_form(form) }}
{% endblock %}
15.main.py里面的代码如下:
from flask import Blueprint,render_template,flash,redirect,url_for,request,current_app
from apps.models import Posts
from apps.forms import PostsForm
from flask_login import login_required,current_user
from apps.exts import db
main = Blueprint("main",__name__)
@main.route('/',methods=['GET','POST'])
# @login_required
def index():
form = PostsForm()
if form.validate():
#判断是否登录了
if current_user.is_authenticated:
#获取当前登录的用户
u = current_user._get_current_object()
p = Posts(content=form.content.data,author=u)
db.session.add(p)
db.session.commit()
return redirect(url_for('main.index'))
else:
flash('登录以后才可以发表')
return redirect(url_for('users.login'))
#读取所有的博客
# posts = Posts.query.filter_by(rid=0).all()
# return render_template('main/index.html',form=form,posts=posts)
#接收用户想查看第几页
#http://www.baidu.com/?p=1 如果不写p参数 默认为1 类型必须是int 类型
page = request.args.get('p',1,type=int)
#拿到分页对象
pagination = Posts.query.filter_by(rid=0).order_by(Posts.pub_time.desc()).paginate(page,per_page=current_app.config['PAGE_COUNT'],error_out=False)
posts = pagination.items
return render_template('main/index.html',form=form,posts=posts,pagination=pagination)
16.posts.py里面的代码如下:
from flask import Blueprint,render_template
posts = Blueprint("posts",__name__)
@posts.route('/posts/')
def index():
return '感谢老铁收藏'
17.users.py里面的代码如下:
import os
from flask import Blueprint,render_template,redirect,url_for,flash,request,current_app
from apps.models import User
from apps.forms import RegisterForm,LoginForm,UploadForm
from apps.exts import db
from apps.email import send_mail
from flask_login import login_user,logout_user,login_required,current_user
from apps.exts import photos
#pip install pillow
from PIL import Image
users = Blueprint("users",__name__)
@users.route('/login/',methods=['GET','POST'])
def login():
form = LoginForm()
if form.validate():
u = User.query.filter_by(username=form.username.data).first()
if not u:
flash('该用户名不存在')
elif not u.confirmed:
flash('该用户还没激活')
elif u.verify_password(form.password.data):
login_user(u,remember=form.remember.data)
flash('登录成功')
#如果连接中有next 参数 那么我们就跳到这个地址
#如果没有next 那么就跳转到首页
# print(request.args['next'])
return redirect(request.args.get('next') or url_for('main.index'))
else:
flash('无效的密码')
return render_template('users/login.html',form=form)
@users.route('/logout/')
def logout_demo():
logout_user()
flash('退出登录成功')
return redirect(url_for('main.index'))
@users.route('/profile/')
@login_required #装饰器必须写在路由的下面
def profile():
return '个人中心'
@users.route('/test/')
@login_required #装饰器必须写在路由的下面
def test():
return 'test'
@users.route('/register/',methods=['GET','POST'])
def register():
form = RegisterForm()
if form.validate():
u = User(username= form.username.data,password = form.password.data,
email = form.email.data)
db.session.add(u)
db.session.commit()
#生成一个加密的字符串 保存该用户注册成功后的信息
token = u.generate_token()
#发送给用户一封邮件
send_mail(u.email,'77账户激活','email/activate',username=u.username,token=token)
return redirect(url_for('users.login'))
return render_template('users/register.html',form=form)
#当用户点击链接 然后这个方法响应
@users.route('/activate/<token>/')
def activate_user(token):
if User.check_activate_token(token):
flash('账户已经激活')
return redirect(url_for('users.login'))
else:
flash('账户激活失败')
return redirect(url_for('main.index'))
@users.route('/change_icon/',methods=['GET','POST'])
@login_required
def change_icon():
#current_user 当前登录的用户
print(current_user.icon)
form = UploadForm()
img_url =''
if form.validate():
#随机文件名
suffix = os.path.splitext(form.icon.data.filename)[1]
filename = random_string()+ suffix
photos.save(form.icon.data,name=filename)
#文件缩略图
pathname = os.path.join(current_app.config['UPLOADED_PHOTOS_DEST'],filename)
img = Image.open(pathname)
img.thumbnail((128,128))
img.save(pathname)
#获取上传后文件的地址 然后返回到页面上
# img_url = photos.url(filename)
#current_user 直接获取当前登录的用户
if current_user.icon != 'default.jpg': #说明已经上传过一次头像
os.remove(os.path.join(current_app.config['UPLOADED_PHOTOS_DEST'],current_user.icon))
current_user.icon = filename
db.session.commit()
flash('头像上传完毕')
return redirect(url_for('users.change_icon'))
img_url = photos.url(current_user.icon) #从数据库中取出图片的地址 然后展示页面上
print(img_url)
#新的地址要保存到数据库中
return render_template('users/change_icon.html',form=form,img_url=img_url)
def random_string(length=20):
import random
base_dir = 'qwertyuiopasdfghjklzxcvbnm1234567890'
return ''.join(random.choice(base_dir) for i in range(length))
18.init.py里面的代码如下:
from .main import main
from .users import users
from .posts import posts
#蓝本的配置 =
DEFAULT_BLUEPRINT = (
(main,''),
(users,'/users'),
(posts,'/posts'),
)
#封装函数 完成蓝本的注册
def config_blueprint(app):
for blueprint,url_prefix in DEFAULT_BLUEPRINT:
app.register_blueprint(blueprint, url_prefix=url_prefix)
19.config.py里面的代码如下:
import os
base_dir = os.path.abspath(os.path.dirname(__file__))
print(base_dir)
#通用配置
class Config(object):
SECRET_KEY = os.environ.get('SECRET_KEY') or 'ADADSF1212'
#bootstrap使用本地的静态文件
BOOTSTRAP_SERVE_LOCAL = True
PAGE_COUNT = 10
MAIL_SERVER = 'smtp.163.com'
MAIL_USERNAME = 'gaohj5@163.com'
MAIL_PASSWORD = 'TLNOFJSYQPYWISOA'
#上传文件的设置
MAX_CONTENT_LENGTH = 1024*1024*8
#上传位置设置
UPLOADED_PHOTOS_DEST = os.path.join(base_dir,'static/uploads')
#开发环境
class DevelopmentConfig(Config):
# 数据库的配置变量
HOSTNAME = '127.0.0.1'
PORT = '3306'
DATABASE = 'kangbazi2002'
USERNAME = 'root'
PASSWORD = '123456'
# 用户名:密码@数据库地址:端口号/数据库名字
DB_URI = 'mysql+pymysql://{}:{}@{}:{}/{}'.format(USERNAME, PASSWORD, HOSTNAME, PORT, DATABASE)
SQLALCHEMY_DATABASE_URI = DB_URI
SQLALCHEMY_TRACK_MODIFICATIONS = False
#测试环境
class TestingConfig(Config):
SQLALCHEMY_DATABASE_URI = 'sqlite:///'+os.path.join(base_dir,'testing.sqlite')
#生产环境
class ProductionConfig(Config):
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(base_dir, 'product.sqlite')
config = {
'test':TestingConfig,
'product':ProductionConfig,
'default':DevelopmentConfig,
}
20.email.py里面的代码如下:
from flask import current_app,render_template
from apps.exts import mail
from flask_mail import Message
from threading import Thread
def async_send_mail(app,msg):
with app.app_context():
mail.send(message=msg)
#封装函数 邮件发送
def send_mail(to,subject,template,**kwargs):
#获取当前的实例
app = current_app._get_current_object()
#message对象
msg = Message(subject=subject,recipients=[to],sender=app.config['MAIL_USERNAME'])
#浏览器打开显示的内容
msg.html = render_template(template+'.html',**kwargs)
#客户端打开
msg.body = render_template(template+'.txt',**kwargs)
#创建线程
thr = Thread(target=async_send_mail,args=[app,msg])
#启动线程
thr.start()
return thr
21.exts.py里面的代码如下:
#导入类库
from flask_bootstrap import Bootstrap
from flask_mail import Mail
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_login import LoginManager
from flask_uploads import UploadSet,IMAGES
from flask_uploads import configure_uploads,patch_request_class
from flask_moment import Moment
#实例化对象
bootstrap = Bootstrap()
mail = Mail()
db = SQLAlchemy()
migrate = Migrate(db=db)
login_manager = LoginManager()
photos = UploadSet('photos',IMAGES)
moment = Moment()
#封装函数 完成初始化
def config_extensions(app):
bootstrap.init_app(app)
mail.init_app(app)
db.init_app(app)
migrate.init_app(app)
moment.init_app(app)
login_manager.init_app(app)
#当我们发表博客 发现没有登录 那么跳到登录页面
login_manager.login_view = 'users.login'
#给没有登录的用户发送提示信息
login_manager.login_message = '登录以后才可以发表'
#session的保护级别
login_manager.session_protection = 'basic'
#完成上传文件的初始化
configure_uploads(app,photos)
patch_request_class(app,size=None)
22.init.py里面的代码如下:
from flask import Flask
from .views import config_blueprint
from .exts import config_extensions
from .config import config
#封装一个函数 专门用来创建app
#开发过程中 有 生产环境、测试环境、开发环境
#需要三个数据库 为了提升效率 传参数 指定你的环境是哪一个
def create_app(config_name):
app = Flask(__name__)
app.config.from_object(config[config_name])
#配置蓝本
config_blueprint(app)
config_extensions(app)
return app
现在pycharm终端输入:
1.python manage.py db init 进行初始化
2.python manage.py db migrate 进行迁移化
3.python manage.py db update 进行更新
运行此三步之前把没有装的包通过pip install 包名(也是在终端操作)
先把自己的数据库连接上
运行的时候如果报错了,把报错的句子通过网易有道词典等进行查阅,自行处理,不要烦躁,保持耐心。