合肥网站seo报价,在wordpress能做些什么,wordpress文章底部加分享,asp做网站计数器Flask学习笔记_异步论坛#xff08;四#xff09; 1.配置和数据库链接1.exts.py里面实例化sqlalchemy数据库2.config.py配置app和数据库信息3.app.py导入exts和config并初始化到app上 2.创建用户模型并映射到数据库1.models/auth.py创建用户模型2.app.py导入模型并用flask-mi… Flask学习笔记_异步论坛四 1.配置和数据库链接1.exts.py里面实例化sqlalchemy数据库2.config.py配置app和数据库信息3.app.py导入exts和config并初始化到app上 2.创建用户模型并映射到数据库1.models/auth.py创建用户模型2.app.py导入模型并用flask-migrate管理数据库3.命令行migrate三部曲将模型映射到数据库 3.登录与注册页面的get请求1.首先写登录和注册的前端页面2.写它们的view蓝图并导入到__init__中3.蓝图注册到app 4.邮箱验证功能1.邮箱验证2.使用celery异步发送邮箱验证网络请求3.使用flask-caching缓存验证码并验证4.重构restful API 5.注册页面的post请求5.1注册页面邮箱验证码的ajax请求5.2注册页面的图形验证码功能5.3注册页面的post提交 6.登录页面的post请求7.首页7.1首页状态切换功能7.2首页设置功能7.3首页头像功能7.4个性签名功能 8.帖子相关设置8.1帖子板块8.2发布帖子8.3帖子详情页8.4首页帖子列表 flask 系列的代码笔记都放在了
仓库。 1.配置和数据库链接
1.exts.py里面实例化sqlalchemy数据库
from flask_sqlalchemy import SQLAlchemy
dbSQLAlchemy()2.config.py配置app和数据库信息
#1.app配置
DEBUGTrue
#2.数据库配置
DB_USERNAMEroot
DB_PASSWORD1xxxx
DB_HOST127.0.0.1
DB_PORT3306
DB_NAMEaforum
DB_URImysqlpymysql://%s:%s%s:%s/%s?charsetutf8mb4 % (DB_USERNAME,DB_PASSWORD,DB_HOST,DB_PORT,DB_NAME)
SQLALCHEMY_DATABASE_URIDB_URI
SQLALCHEMY_TRACK_MODIFIERFalse3.app.py导入exts和config并初始化到app上
from flask import Flask
import config
from exts import db
appFlask(__name__)#1.实例化app
app.config.from_object(config)#2.config配置文件绑定到app
db.init_app(app)#3.数据库绑定到app
app.route(/)
def index():return hello
if __name____main__:app.run()2.创建用户模型并映射到数据库
1.models/auth.py创建用户模型
from exts import db
import shortuuid
from datetime import datetime
from werkzeug.security import generate_password_hash,check_password_hashclass UserModel(db.Model):__tablename__ userid db.Column(db.String(100), primary_keyTrue, defaultshortuuid.uuid)email db.Column(db.String(50), uniqueTrue, nullableFalse)username db.Column(db.String(50), nullableFalse)_password db.Column(db.String(200), nullableFalse)avatar db.Column(db.String(100))signature db.Column(db.String(100))join_time db.Column(db.DateTime, defaultdatetime.now)is_staff db.Column(db.Boolean, defaultFalse)is_active db.Column(db.Boolean, defaultTrue)def __init__(self, *args, **kwargs):if password in kwargs:self.password kwargs.get(password)kwargs.pop(password)super(UserModel, self).__init__(*args, **kwargs)propertydef password(self):return self._passwordpassword.setterdef password(self, newpwd):self._password generate_password_hash(newpwd)def check_password(self,rawpwd):return check_password_hash(self.password, rawpwd)2.app.py导入模型并用flask-migrate管理数据库
from flask_migrate import Migrate
from models import auth
migrateMigrate(app,db)3.命令行migrate三部曲将模型映射到数据库
在app.py文件的目录下
flask db init
flask db migrate
flask db upgrade3.登录与注册页面的get请求
1.首先写登录和注册的前端页面
#1。首先抽出base.html文件
html
headmeta charsetutf-8script srchttp://cdn.bootcss.com/jquery/3.1.1/jquery.min.js/scriptlink hrefhttp://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css relstylesheetscript srchttp://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js/scriptscript src{{ url_for(static, filenamefront/js/zlajax.js) }}/scriptscript src{{ url_for(static, filenamefront/js/zlparam.js) }}/scriptlink relstylesheet href{{ url_for(static, filenamefront/css/front_base.css) }}meta nameviewport contentwidthdevice-width, initial-scale1title{% block title %}{% endblock %}/title{% block head %}{% endblock %}
/headbodynav classnavbar navbar-defaultdiv classcontainerdiv classnavbar-headerbutton typebutton classnavbar-toggle collapsed data-togglecollapse data-target#bs-example-navbar-collapse-1 aria-expandedfalsespan classsr-onlyToggle navigation/spanspan classicon-bar/spanspan classicon-bar/spanspan classicon-bar/span/buttona classnavbar-brand href/论坛/a/div!-- Collect the nav links, forms, and other content for toggling --div classcollapse navbar-collapse idbs-example-navbar-collapse-1ul classnav navbar-navli classactivea href/首页span classsr-only(current)/span/a/li/ulform classnavbar-form navbar-leftdiv classform-groupinput typetext classform-control placeholder请输入关键字/divbutton typesubmit classbtn btn-default搜索/button/formul classnav navbar-nav navbar-right{% if user %}li classdropdowna href# classdropdown-toggle typebutton iddropdownMenu1 data-toggledropdown aria-haspopuptrue aria-expandedtrue{{ user.username }}span classcaret/span/aul classdropdown-menu aria-labelledbydropdownMenu1lia href{{ url_for(front.cms) }}后台管理/a/lilia href{{ url_for(front.setting) }}设置/a/lilia href{{ url_for(front.logout) }}注销/a/li/ul/li{% else %}lia href{{ url_for(front.login) }}登录/a/lilia href{{ url_for(front.register) }}注册/a/li{% endif %}/ul/div!-- /.navbar-collapse --/div!-- /.container-fluid --/navdiv classmain-container{% block body %}{% endblock %}/div
/body/html
#2.login.html文件
{% extends front/base.html %}{% block title %}登录
{% endblock %}{% block head %}link relstylesheet href{{ url_for(static, filenamefront/css/signbase.css) }}script src{{ url_for(static, filenamefront/js/login.js) }}/script
{% endblock %}
{% block body %}div classouter-boxdiv classlogo-boxa href/img src{{ url_for(static, filenamefront/images/logo.png) }} alt/a/divh2 classpage-title登录/h2div classsign-boxdiv classform-groupinput typetext classform-control nameemail placeholder邮箱/divdiv classform-groupinput typepassword classform-control namepassword placeholder密码/divdiv classcheckboxlabelinput typecheckbox nameremember value1记住我/label/divdiv classform-groupbutton classbtn btn-warning btn-block idsubmit-btn立即登录/button/divdiv classform-groupa href# classsignup-link没有账号立即注册/aa href# classresetpwd-link stylefloat:right;找回密码/a/div/div/div
{% endblock %}
#3.register.html文件
{% extends front/base.html %}{% block title %}注册
{% endblock %}{% block head %}link relstylesheet href{{ url_for(static, filenamefront/css/signbase.css) }}script src{{ url_for(static, filenamefront/js/register.js) }}/script
{% endblock %}{% block body %}div classouter-boxdiv classlogo-boxa href/img src{{ url_for(static, filenamefront/images/logo.png) }} alt/a/divh2 classpage-title注册/h2div classsign-boxdiv classform-groupdiv classinput-groupinput typeemail classform-control nameemail placeholder邮箱span classinput-group-btnbutton idemail-captcha-btn classbtn btn-default发送验证码/button/span/div/divdiv classform-groupinput typetext classform-control nameemail-captcha placeholder邮箱验证码/divdiv classform-groupinput typetext classform-control nameusername placeholder用户名/divdiv classform-groupinput typepassword classform-control namepassword placeholder密码/divdiv classform-groupinput typepassword classform-control namerepeat-password placeholder确认密码/divdiv classform-groupdiv classinput-groupinput typetext classform-control namegraph-captcha placeholder图形验证码span classinput-group-addon captcha-addonimg idcaptcha-img classcaptcha-img src# alt/span/div/divdiv classform-groupbutton classbtn btn-warning btn-block idsubmit-btn立即注册/button/div/div/div
{% endblock %}2.写它们的view蓝图并导入到__init__中
#1.在apps/front/views.py里面写蓝图的视图函数
from flask import Blueprint,request,render_template
bpBlueprint(front,__name__,url_prefix/)bp.route(/login/, methods[GET, POST])
def login():if request.method GET:return render_template(front/login.html)bp.route(/register/, methods[GET, POST])
def register():if request.method GET:return render_template(front/register.html)
#2.在apps/front/__init__.py里面导入蓝图
from .views import bp as front_bp 3.蓝图注册到app
#在app.py里面导入蓝图并注册到app上
from apps.front import front_bp
app.register_blueprint(front_bp)4.邮箱验证功能
1.邮箱验证
#1.在config里面配置邮箱第三方服务商来发送邮件
MAIL_SERVERsmtp.qq.com#发送验证码的邮箱服务器这里是自己公司的邮箱服务器
MAIL_PORT587#587是tls协议465是ssl协议
MAIL_USE_TLSTrue
#MAIL_USE_SSL
MAIL_USERNAME1xxxc9qq.com
MAIL_PASSWORDwxxbe
MAIL_DEFAULT_SENDER11xxqq.com
#2.exts里面导入mail
from flask_mail import Mail
mailMail()
#3.在app里面把exts里面的mail导入进来并绑定到app
from exts import db,mail
mail.init_app(app)
#4.开始在views里面写发送邮箱验证码的视图函数
from exts import mail
from flask_mail import Message
from flask importjsonify
import string,random
bp.get(/email/captcha/)
def email_captcha():emailrequest.args.get(email)if not email:return jsonify({code:400,message:请先传入邮箱})sourcelist(string.digits)captcha.join(random.sample(source,6))messageMessage(subject注册验证码,recipients[email],body您的注册验证码是%s % captcha)try:mail.send(message)except Exception as e:print(邮件发送失败)print(e)return jsonify({code:500,message:邮件发送失败})return jsonify({code:200,message:邮件发送成功})2.使用celery异步发送邮箱验证网络请求
celery分布式任务队列/任务调度器和redis内存数据库的教程和安装步骤可以参考学习。Broker和Backend都用redis存储。 pip install gevent pip install redis pip install hiredis 启动celery redis-cli#1.在config中设置reids的相关信息
CELERY_BROKER_URLredis://127.0.0.1:6379/0#broker
CELERY_RESULT_BACKENDredis://127.0.0.1:6379/0#backend
#2.mycelery.py里面定义并添加任务
from flask_mail import Message
from exts import mail
from celery import Celery# 定义任务函数
def send_mail(recipient,subject,body):message Message(subjectsubject,recipients[recipient],bodybody)try:mail.send(message)return {status: SUCCESS}except Exception :return {status: FAILURE}# 创建celery对象
def make_celery(app):celery Celery(app.import_name, backendapp.config[CELERY_RESULT_BACKEND],brokerapp.config[CELERY_BROKER_URL])TaskBase celery.Taskclass ContextTask(TaskBase):abstract Truedef __call__(self, *args, **kwargs):with app.app_context():return TaskBase.__call__(self, *args, **kwargs)celery.Task ContextTaskapp.celery celery# 添加任务celery.task(namesend_mail)(send_mail)return celery
#3.在app.py里面将celery绑定到app
from mycelery import make_celery
mycelerymake_celery(app)
#4.在views.py里面利用current_app调用celery里面的task任务
from flask import current_app
bp.get(/email/captcha/)
def email_captcha():emailrequest.args.get(email)if not email:return jsonify({code:400,message:请先传入邮箱})sourcelist(string.digits)captcha.join(random.sample(source,6))subject注册验证码body您的注册验证码是%s%captchacurrent_app.celery.send_task(send_mail,(email,subject,body))return jsonify({code:200,message:邮件发送成功})
#5.在工程目录下运行这个celery
celery -A app.mycelery worker --loglevelinfo -P gevent
#6.访问这个视图函数就可以成功利用celery进行异步任务调取3.使用flask-caching缓存验证码并验证
flask-caching的相关教程可以查看博文。
#1.安装pip install flask-caching
#2.在config里面写flask-caching相关的配置
CACHE_TYPERedisCache
CACHE_DEFAULT_TIMEOUT300
CACHE_REDIS_HOST127.0.0.1
CACHE_REDIS_PORT6379
#3.在exts里面引入caching
from flask_caching import Cache
cacheCache()
#4.在app.py里面init
from exts import cache
cache.init_app(app)
#5.在view的视图函数里面缓存验证码
from exts import cache
cache.set(email,captcha)#cache缓存是键值对的形式4.重构restful API
#1.在utils/restful.py里面
# Restful API
from flask import jsonifyclass HttpCode(object):# 响应正常ok 200# 没有登陆错误unloginerror 401# 没有权限错误permissionerror 403# 客户端参数错误paramserror 400# 服务器错误servererror 500def _restful_result(code, message, data):return jsonify({ code: code,message: message or , data: data or {}})def ok(messageNone, dataNone):return _restful_result(codeHttpCode.ok, messagemessage, datadata)def unlogin_error(message没有登录):return _restful_result(codeHttpCode.unloginerror, messagemessage, dataNone)def permission_error(message没有权限访问):return _restful_result(codeHttpCode.paramserror, messagemessage, dataNone)def params_error(message参数错误):return _restful_result(codeHttpCode.paramserror, messagemessage, dataNone)def server_error(message服务器开小差啦):return _restful_result(codeHttpCode.servererror, messagemessage or 服务器内部错误, dataNone)
#2.在view视图函数里
from utils import restful
return restful.params_error(message请先传入邮箱)
return restful.ok(message邮件发送成功)5.注册页面的post请求
5.1注册页面邮箱验证码的ajax请求
#1.在register.html里面引入js文件
script src{{ url_for(static, filenamefront/js/register.js) }}/script
#2.在register.js里面监听4步,这里引用zlajax是因为它自动给了csrf-token
var RegisterHandler function (){\\1.定义了一个JavaScript对象}RegisterHandler.prototype.listenSendCaptchaEvent function (){\\2.包含一个方法var callback function (event){// 原生的JS对象this jQuery对象var $this $(this);// 阻止默认的点击事件event.preventDefault();var email $(input[nameemail]).val();var reg /^\w((.\w)|(-\w))[A-Za-z0-9]((.|-)[A-Za-z0-9]).[A-Za-z0-9]$/;if(!email || !reg.test(email)){alert(请输入正确格式的邮箱);return;}zlajax.get({url: /email/captcha?email email,success: function (result){if(result[code] 200){console.log(邮件发送成功);// 取消按钮的点击事件$this.off(click);// 添加禁用状态$this.attr(disabled, disabled);// 开始倒计时var countdown 60;var interval setInterval(function (){if(countdown 0){$this.text(countdown);}else{$this.text(发送验证码);$this.attr(disabled, false);$this.on(click, callback);// 清理定时器clearInterval(interval);}countdown--;}, 1000);}else{var message result[message];alert(message);}}})}$(#email-captcha-btn).on(click, callback);
}
RegisterHandler.prototype.run function (){\\3.方法在run函数里调用this.listenSendCaptchaEvent();
}// $(function(){})
$(function (){\\4.实例化并运行var handler new RegisterHandler();handler.run();
})#3.post请求要用csrf-token所以在base.html里面引入
meta namecsrf-token content{{csrf_token()}}
#4.csrf-token需要先安装
pip install flask-wtf
#5.在config里面设置secretkey
SECRET_KEYFASDFNMLKSDF
#6.在exts里面引入
from flask_wtf import CSRFProtect
csrfCSRFProtect()
#6.在app上绑定init
from exts import csrf
csrf.init_app(app)
5.2注册页面的图形验证码功能
#1.在config里面获取工程的base目录
import os
BASE_DIRos.path.dirname(__file__)
#2.在utils目录下的captcha的init文件里生成图形验证码
import random
import string
# Image一个画布
# ImageDraw一个画笔
# ImageFont:画笔的字体
from PIL import Image,ImageDraw,ImageFontfrom flask import current_app
import os# pip install pillow# Captcha验证码class Captcha(object):# 生成几位数的验证码number 4# 验证码图片的宽度和高度size (100,30)# 验证码字体大小fontsize 25# 加入干扰线的条数line_number 2# 构建一个验证码源文本SOURCE list(string.ascii_letters)for index in range(0, 10):SOURCE.append(str(index))#用来绘制干扰线classmethoddef __gene_line(cls,draw,width,height):begin (random.randint(0, width), random.randint(0, height))end (random.randint(0, width), random.randint(0, height))draw.line([begin, end], fill cls.__gene_random_color(),width2)# 用来绘制干扰点classmethoddef __gene_points(cls,draw,point_chance,width,height):chance min(100, max(0, int(point_chance))) # 大小限制在[0, 100]for w in range(width):for h in range(height):tmp random.randint(0, 100)if tmp 100 - chance:draw.point((w, h), fillcls.__gene_random_color())# 生成随机的颜色classmethoddef __gene_random_color(cls,start0,end255):random.seed()return (random.randint(start,end),random.randint(start,end),random.randint(start,end))# 随机选择一个字体classmethoddef __gene_random_font(cls):fonts [Courgette-Regular.ttf,LHANDW.TTF,Lobster-Regular.ttf,verdana.ttf]font random.choice(fonts)fontpath os.path.join(current_app.config[BASE_DIR],utils,captcha,font)# return utils/captcha/fontreturn fontpath# 用来随机生成一个字符串(包括英文和数字)classmethoddef gene_text(cls, number):# number是生成验证码的位数return .join(random.sample(cls.SOURCE, number))#生成验证码classmethoddef gene_graph_captcha(cls):# 验证码图片的宽和高width,height cls.size# 创建图片# RRed红色0-255# GG绿色0-255# BB蓝色0-255# AAlpha透明度image Image.new(RGBA,(width,height),cls.__gene_random_color(0,100))# 验证码的字体font ImageFont.truetype(cls.__gene_random_font(),cls.fontsize)# 创建画笔draw ImageDraw.Draw(image)# 生成字符串text cls.gene_text(cls.number)# 获取字体的尺寸font_width, font_height font.getsize(text)# 填充字符串draw.text(((width - font_width) / 2, (height - font_height) / 2),text,font font,fillcls.__gene_random_color(150,255))# 绘制干扰线for x in range(0, cls.line_number):cls.__gene_line(draw, width, height)# 绘制噪点cls.__gene_points(draw, 10, width, height)return (text,image)
#3.在views里面写视图函数
from utils.captcha import Captcha
import time
from hashlib import md5
from io import BytesIO
from flask import make_response
bp.route(/graph/captcha/)
def graph_captcha():captcha,imageCaptcha.gene_graph_captcha()keymd5((captchastr(time.time())).encode(utf-8)).hexdigest()cache.set(key,captcha)#cache里面缓存这个captchabufferBytesIO()image.save(buffer,png)buffer.seek(0)#buffer文件指针指向最开始的位置respmake_response(buffer.read())resp.content_typeimage/pngresp.set_cookie(_graph_captcha_key,key,max_age3600)#将key值保存到cookie1个小时return resp
#4.在register.html里面写图片验证码的src
img idcaptcha-img classcaptcha-img src{{url_for(front.graph_captcha)}} alt
#5.实现点击图片重新生成所以在regist.js里面监听
RegisterHandler.prototype.listenGraphCaptchaEvent function (){$(#captcha-img).on(click, function (){console.log(点击了图形验证码);var $this $(this);var src $this.attr(src);// /graph/captcha// /graph/captcha?signMath.random()// 防止一些老的浏览器在两次url相同的情况下不会重新发送请求导致图形验证码不会更新let new_src zlparam.setParam(src, sign, Math.random())$this.attr(src,new_src);});
}
RegisterHandler.prototype.run function (){this.listenSendCaptchaEvent();this.listenGraphCaptchaEvent();
}5.3注册页面的post提交
#1.在front/forms.py里面进行表单验证
from wtforms import Form,ValidationError
from wtforms.fields import StringField
from wtforms.validators import Email,Length,EqualTo
from models.auth import UserModel#对表单进行二次验证
from exts import cache
from flask import request
class BaseForm(Form):propertydef messages(self):message_list []if self.errors:for error in self.errors.values():message_list.extend(error)return message_list
class RegisterForm(BaseForm):emailStringField(validators[Email(message请输入正确的邮箱)])email_captchaStringField(validators[Length(6,6,message请输入6位验证码)])usernameStringField(validators[Length(3,20,message请输入3-20位的用户名)])passwordStringField(validators[Length(6,20,message请输入6-20位的密码)]) repeat_passwordStringField(validators[EqualTo(password,message两次密码不一致)])graph_captchaStringField(validators[Length(4,4,message请输入4位图形验证码)])def validate_email(self,field):emailfield.datauserUserModel.query.filter_by(emailemail).first()if user:raise ValidationError(message邮箱已经被注册)def validate_email_captcha(self,field):email_captchafield.dataemailself.email.datacache_captchacache.get(email)if not cache_captcha or cache_captcha!email_captcha:raise ValidationError(message邮箱验证码错误)def validate_graph_captcha(self,field):graph_captchafield.datakeyrequest.cookies.get(_graph_captcha_key)cache_captchacache.get(key)if not cache_captcha or cache_captcha.lower()!graph_captcha.lower():raise ValidationError(message图形验证码错误)
#2.在front/views.py里面写post视图函数
from .forms import RegisterForm
from models.auth import UserModel
from exts import db
bp.route(/register/, methods[GET, POST])
def register():if request.method GET:return render_template(front/register.html)else:formRegisterForm(request.form)if form.validate():emailform.email.datausernameform.username.datapasswordform.password.datauserUserModel(emailemail,usernameusername,passwordpassword)db.session.add(user)db.session.commit()return restful.ok()else:messageform.messages[0]return restful.params_error(messagemessage)
#3.在js里面绑定点击事件跳到上面的视图函数
RegisterHandler.prototype.listenSubmitEvent function (){$(#submit-btn).on(click, function (event){event.preventDefault();var email $(input[nameemail]).val();var email_captcha $(input[nameemail-captcha]).val();var username $(input[nameusername]).val();var password $(input[namepassword]).val();var repeat_password $(input[namerepeat-password]).val();var graph_captcha $(input[namegraph-captcha]).val();// 如果是商业项目一定要先验证这些数据是否正确zlajax.post({url: /register,data: {email: email,email_captcha: email_captcha,username: username,password, // password: passwordrepeat_password,graph_captcha},success: function (result){if(result[code] 200){window.location /login;}else{alert(result[message]);}}})});
}RegisterHandler.prototype.run function (){this.listenSendCaptchaEvent();this.listenGraphCaptchaEvent();this.listenSubmitEvent();
}6.登录页面的post请求
#1.首先表单验证
from wtforms.fields import IntegerField
class LoginForm(BaseForm):emailStringField(validators[Email(message请输入正确的邮箱)])passwordStringField(validators[Length(6,20,message请输入6-20位的密码)]) rememberIntegerField()
#2.视图函数
from flask import session
from .forms import LoginForm
bp.route(/login/, methods[GET, POST])
def login():if request.method GET:return render_template(front/login.html)else:formLoginForm(request.form)if form.validate():emailform.email.datapasswordform.password.datarememberform.remember.datauserUserModel.query.filter_by(emailemail).first()if not user:return restful.params_error(此邮箱没有注册)if not user.check_password(password):return restful.params_error(邮箱或密码错误)session[user_id]user.idif remember 1:session.permanentTruereturn restful.ok()else:return restful.params_error(messageform.messages[0])
#3.在config里面设置permanent时间
from datetime import timedelta
PERMANENT_SESSION_LIFETIMEtimedelta(days7)
#4.登录的post提交的前端监听,在login.js中并加载到html中
var LoginHandler function (){}LoginHandler.prototype.listenSubmitEvent function (){$(#submit-btn).on(click, function (event){event.preventDefault();var email $(input[nameemail]).val();var password $(input[namepassword]).val();var remember $(input[nameremember]).prop(checked);zlajax.post({url: /login,data: {email,password,remember: remember?1:0},success: function (result){if(result[code] 200){var token result[data][token];var user result[data][user];localStorage.setItem(JWT_TOKEN_KEY, token);localStorage.setItem(USER_KEY, JSON.stringify(user));window.location /}else{alert(result[message]);}}})});
}LoginHandler.prototype.run function (){this.listenSubmitEvent();
}$(function (){var handler new LoginHandler();handler.run();
});7.首页
7.1首页状态切换功能
1.get请求写index视图函数和html 2.状态切换功能
#1.写退出登录的视图函数
bp.route(/logout/)
def logout():session.clear()return redirect(/)
#2.利用钩子函数用户发送请求前的操作和上下文处理器函数视图函数返回给用户数据前的操作将user绑定到g上
bpBlueprint(front,__name__,url_prefix/)
bp.before_request#钩子函数在用户访问视图函数前在session里拿到用户绑到g上
def front_before_request():if user_id in session:user_idsession.get(user_id)userUserModel.query.get(user_id)setattr(g,user,user)
bp.context_processor#上下文处理器函数在视图函数里服务器返回给用户数据前将这里的参数返回给模板进行渲染
def front_after_request():if hasattr(g,user):return {user:g.user}else:return {}
#3.修改html里面的变量
{% if user %}li classdropdowna href# classdropdown-toggle typebutton iddropdownMenu1 data-toggledropdown aria-haspopuptrue aria-expandedtrue{{ user.username }}span classcaret/span/aul classdropdown-menu aria-labelledbydropdownMenu1lia href#后台管理/a/lilia href{{url_for(front.setting)}}设置/a/lilia href{{ url_for(front.logout) }}注销/a/li/ul/li{% else %}lia href{{ url_for(front.login) }}登录/a/lilia href{{ url_for(front.register) }}注册/a/li{% endif %}7.2首页设置功能
#1.在front/decorates.py里面写登录装饰器
from flask import g,redirect,url_for
from functools import wraps
def login_required(func):wraps(func)def inner(*args, **kwargs):if hasattr(g,user):return func(*args, **kwargs)else:return redirect(url_for(front.login))return inner
#2.写视图函数和html以及设置按钮的跳转链接,并要有登录装饰器
bp.route(/setting/)
login_required
def setting():email_hashmd5(g.user.email.encode(utf-8)).hexdigest()return render_template(front/setting.html,email_hashemail_hash)
7.3首页头像功能
#1.pip install flask-avatars
#2.在exts里面导入在app里面初始化
from flask_avatars import Avatars
avatarsAvatars()
from exts import avatars
avatars.init_app(app)
#3.使用Gravatar头像
img src{{ avatars.gravatar(email_hash) }} alt... classimg-circle idavatar-img
#4.使用标识生成头像,并把L尺寸的头像地址保存到数据库
AVATARS_SAVE_PATHos.path.join(BASE_DIR,media,avatars)#在config里面设置图像的保存地址
#在register视图函数里面添加avatar的保存数据
from flask_avatars import Identicon
import osidenticonIdenticon()filenamesidenticon.generate(textmd5(email.encode(utf-8)).hexdigest())avatarfilenames[2]userUserModel(emailemail,usernameusername,passwordpassword,avataravatar)#在media/view里面写访问头像的视图函数并绑定到app上然后修改html的链接from flask import Blueprint,send_from_directory,current_app
bpBlueprint(media,__name__,url_prefix/media)
bp.route(/avatar/filename)
def get_avatar(filename):return send_from_directory(current_app.config[AVATARS_SAVE_PATH],filename)
from apps.media import media_bp
app.register_blueprint(media_bp)
img src{{ url_for(media.get_avatar,filenameuser.avatar) }} alt... classimg-circle idavatar-img
#4.用户自定义图像用户头像上传就是表单提交的方式所以要进行表单验证然后写视图函数,
class UploadAvatarForm(BaseForm):imageFileField(validators[FileAllowed([png, jpg, jpeg,],message图片格式不符合要求),FileSize(max_size1024*1024*5,message图片大小不超过5MB)])
bp.post(/avatar/upload/)
login_required
def upload_avatar():formUploadAvatarForm(request.files)if form.validate():imageform.image.datafilenameimage.filename_,extos.path.splitext(filename)filenamemd5((g.user.emailstr(time.time())).encode(utf-8)).hexdigest()extimage_pathos.path.join(current_app.config[AVATARS_SAVE_PATH],filename)image.save(image_path)g.user.avatarfilenamedb.session.commit()return restful.ok(data{avatar:filename})else:messageform.messages[0]return restful.params_error(messagemessage)
#写图像上传的js并将js导入到html7.4个性签名功能
#1.写form表单验证view视图进行post请求写js导入到html8.帖子相关设置
8.1帖子板块
1.命令行实现板块初始化
#1.在models/post.py里面创建帖子板块模型并导入到app文件然后migrate到数据库
from exts import db
from datetime import datetime
class BoardModel(db.Model):__tablename__ boardiddb.Column(db.Integer, primary_keyTrue,autoincrementTrue)namedb.Column(db.String(20),uniqueTrue)prioritydb.Column(db.Integer, default1)create_timedb.Column(db.DateTime,defaultdatetime.now)
from models import post#app.py里面导入一下
flask db migrate
flask db upgrade
#2.在commands.py里面写初始化板块的命令函数给板块数据到db数据库
from models.post import BoardModel
from exts import db
def init_boards():board_names[flask,fast,ai,爬虫]for index,board_name in enumerate(board_names):boardBoardModel(nameboard_name,prioritylen(board_names)-index)db.session.add(board)db.session.commit()print(板块初始化成功)
#3.在app.py里面导入并注册命令
import commands
app.cli.command(inbo)(commands.init_boards)
#4.在cmd里面调用命令
flask inbo
#5.板块的后端已经实现现在要将这个信息传给前端并显示所以在首页(/)视图函数下传参并在html中循环显示
from models.post import BoardModel
boardsBoardModel.query.order_by(BoardModel.priority.desc()).all()
return render_template(front/index.html,boardsboards)
{% for board in boards %}a href# classlist-group-item{{board.name}}/a
{% endfor %}2.创建帖子相关的模型包括PostModelBannerModelCommentModel并migrate到数据库
8.2发布帖子
1.get
#1.首先修改首页的发布帖子的跳转链接然后写视图函数和html文件
a href{{url_for(front.public_post)}} classbtn btn-warning btn-block发布帖子/a
bp.route(post/public/,methods[POST, GET])
def public_post():if request.method GET:boardsBoardModel.query.all()return render_template(front/public_post.html,boardsboards)2.富文本编辑器wangEditor这里首先需要导入它的js文件可以按官网的示例线上引用也可以下载到本地再导入。
#1.在public_post.html里面导入富文本编辑器的js
script typetext/javascript src{{url_for(static,filenamelib/wangEditor/wangEditor.min.js)}}/script
#2.写自己的初始化编辑器和文本内容提交的js并引入到html中
script typetext/javascript src{{url_for(static,filenamefront/js/public_post.js)}}/script
#3.上传图片到本地首先在wangeditor里面初始化图片上传的路径等信息然后写图片上传的视图函数bp.post(/post/image/upload/)
login_required
def upload_post_image():formUploadAvatarForm(request.files)if form.validate():imageform.image.datafilenameimage.filename_,extos.path.splitext(filename)filenamemd5((g.user.emailstr(time.time())).encode(utf-8)).hexdigest()extimage_pathos.path.join(current_app.config[POST_IMAGE_SAVE_PATH],filename)image.save(image_path)return jsonify({errno: 0, # 注意值是数字不能是字符串data: [{url: url_for(media.get_post_image,filenamefilename), # 图片 src 必须alt: filename, # 图片描述文字非必须href: # 图片的链接非必须}]
})else:messageform.messages[0]return jsonify({errno: 1, #只要不等于 0 就行message: message
})3.发布帖子的提交
#1.首先表单验证然后写帖子内容提交的视图函数js提交前端
bp.route(post/public/,methods[POST, GET])
login_required
def public_post():if request.method GET:boardsBoardModel.query.all()return render_template(front/public_post.html,boardsboards)else:formPublicPostForm(request.form)if form.validate():titleform.title.datacontentform.content.databoard_idform.board_id.datatry:boardBoardModel.query.get(board_id)except Exception as e:return restful.error(message板块不存在)post_modelPostModel(titletitle,contentcontent,boardboard,authorg.user)db.session.add(post_model)db.session.commit()return restful.ok(data{id:post_model.id})else:return restful.params_error(messageform.messages[0])
var PublicPostHandler function (){var csrf_token $(meta[namecsrf-token]).attr(content);var editor new window.wangEditor(#editor);editor.config.uploadImgServer /post/image/upload;editor.config.uploadFileName image;// 1. 放到请求体中// 2. 放到请求头中X-CSRFToken// 再和cookie中的csrf_token进行对比editor.config.uploadImgHeaders {X-CSRFToken: csrf_token}editor.config.uploadImgMaxSize 1024*1024*5;editor.create();this.editor editor;}PublicPostHandler.prototype.listenSubmitEvent function (){var that this;$(#submit-btn).on(click, function (event){event.preventDefault();var title $(input[nametitle]).val();var board_id $(select[nameboard_id]).val();var content that.editor.txt.html();zlajax.post({url: /post/public/,data: {title,board_id,content},success: function (result){if(result[code] 200){let data result[data];let post_id data[id];window.location /post/detail/ post_id;}else{alert(result[message]);}}});});}PublicPostHandler.prototype.run function(){this.listenSubmitEvent();}$(function(){var handler new PublicPostHandler();handler.run();});8.3帖子详情页
#1.帖子详情页的get请求视图函数html
#2.帖子详情的代码高亮功能使用highlight.jslink relstylesheet href{{ url_for(static, filenamelib/highlight/styles/github-dark.min.css) }}script src{{ url_for(static, filenamelib/highlight/highlight.min.js) }}/scriptscript src{{ url_for(static, filenamefront/js/post_detail.js) }}/scripthljs.highlightAll();#post_detail.js里面初始化
#3.帖子详情的评论功能表单验证post视图函数在html中获取填写的信息通过js提交,html中导入js8.4首页帖子列表
1.在首页的视图函数中拿到数据库的帖子数据通过参数传递给前端html,前端通过for循环展示帖子相关信息 2.使用flask-paginate实现帖子分页
pip install flask-paginate
#1.首先在config中配置每页展示帖子的数量
PER_PAGE_COUNT7
#2.在首页的视图函数中查询并进行分页然后返回参数给html渲染
from flask_paginate import get_page_parameter,Pagination
bp.route(/)
def index():boardsBoardModel.query.order_by(BoardModel.priority.desc()).all()post_query PostModel.query.order_by(PostModel.create_time.desc())totalpost_query.count()page request.args.get(get_page_parameter(), typeint, default1)per_page_count current_app.config[PER_PAGE_COUNT]start (page - 1) * per_page_countposts post_query.offset(start).limit(per_page_count).all()pagination Pagination(bs_version3,pagepage,totaltotal,per_pageper_page_count, force_parameterTrue)context{boards:boards, posts:posts,pagination:pagination}return render_template(front/index.html,**context)
#3.在index的html的帖子列表末尾添加翻页按钮
div styletext-align:center;
{{pagination.links}}
/div 3.帖子按评论顺序和时间排列
#1.首先通过html传递st参数即按什么方式排序
{% if st1 %}li classactive
{% else %}
li
{% endif %}a href{{url_for(front.index,st1)}}最新/a/li
{% if st2 %}li classactive
{% else %} li
{% endif %}a href{{url_for(front.index,st2)}}评论最多/a/li
#2.在view里面按照st参数进行排序
from sqlalchemy.sql import func
bp.route(/)
def index():sortrequest.args.get(st,typeint,default1)post_queryNoneif sort1:post_queryPostModel.query.order_by(PostModel.create_time.desc())else:post_querydb.session.query(PostModel).outerjoin(CommentModel).group_by(PostModel.id).order_by(func.count(CommentModel.id).desc(),PostModel.create_time.desc())...context{boards:boards, posts:posts,pagination:pagination,st:sort}return render_template(front/index.html,**context)4.帖子按板块过滤
#1.html传递板块bd参数
{% if not bd %}a href/ classlist-group-item active所有板块/a{% else %}a href/ classlist-group-item所有板块/a{% endif %}{% for board in boards %}{% if board.idbd %}a href{{url_for(front.index,bdboard.id,page1)}} classlist-group-item active{{board.name}}/a{% else %}a href{{url_for(front.index,bdboard.id,page1)}} classlist-group-item{{board.name}}/a{% endif %}{% endfor %}
#2.view里面拿到bd参数对post进行过滤
bp.route(/)
def index():sortrequest.args.get(st,typeint,default1)board_idrequest.args.get(bd,typeint,defaultNone)boardsBoardModel.query.order_by(BoardModel.priority.desc()).all()post_queryNoneif sort1:post_queryPostModel.query.order_by(PostModel.create_time.desc())else:post_query db.session.query(PostModel).outerjoin(CommentModel).group_by(PostModel.id).order_by(func.count(CommentModel.id).desc(), PostModel.create_time.desc())page request.args.get(get_page_parameter(), typeint, default1)per_page_count current_app.config[PER_PAGE_COUNT]start (page - 1) * per_page_countendstartcurrent_app.config[PER_PAGE_COUNT]if board_id:post_querypost_query.filter(PostModel.board_idboard_id)total post_query.count()postspost_query.slice(start,end)pagination Pagination(bs_version3,pagepage,totaltotal,per_pageper_page_count, force_parameterTrue)context{boards:boards, posts:posts,pagination:pagination,st:sort,bd:board_id}return render_template(front/index.html,**context)