flask基础 制作博客注册登录系统(下)

制作如图所示的表结构:
在这里插入图片描述

在这里插入图片描述
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">&laquo;</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="#">&hellip;</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">&raquo;</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">&times;</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 包名(也是在终端操作)
先把自己的数据库连接上
运行的时候如果报错了,把报错的句子通过网易有道词典等进行查阅,自行处理,不要烦躁,保持耐心。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值