数据库和用户中心

数据库

users 表

posts 表

用户注册 和登陆

保存到 user 表

增加 models

models/account.py
class Users(Base):
__tablename__ = 'users'

id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(50), unique=True, nullable=False)
password = Column(String(50), nullable=False)
created = Column(DateTime, default=datetime.now)
email = Column(String(50))
last_login = Column(DateTime)

def __repr__(self):
return '<User(#{}:{})>'.format(self.id, self.name)

# 判断用户是否存在于数据库中
@classmethod
def is_exists(cls, username):
return session.query(exists().where(Users.name == username)).scalar()

# 增加用户到数据库中
@classmethod
def add_user(cls, username, password, email=''):
user = Users(name=username, password=password, email=email, last_login=datetime.now())
session.add(user)
session.commit()

# 查询用户username对应的密码
@classmethod
def get_passwd(cls, username):
user = session.query(Users).filter_by(name=username).first()
# 如果用户存在 返回密码
if user:
return user.password
# 用户不存在 返回空
else:
return ''

增加 utils 辅助函数

utils/account.py
def hashed(passwd):
return hashlib.md5(passwd.encode('utf8')).hexdigest()


def authenticate(username, password):
"""
登录认证
:param username:
:param password:
:return:
"""
if username and password:
# 获取数据库中username对应的密码
user_passwd = Users.get_passwd(username)
# 如果用户存在 密码匹配
if user_passwd and user_passwd == hashed(password):
return True
return False


def save_last_login(username):
"""
# 保存用户username最后登录时间
:param username:
:return:
"""
t = datetime.now()
print("user {} login at {}".format(username, t))
session.query(Users).filter_by(name=username).update({Users.last_login: t})
session.commit()


def register(username, password, email):
"""
注册
:param username:
:param password:
:param email:
:return:
"""
# 查看用户是否存在于数据库中
if Users.is_exists(username):
return {'msg': '用户已存在'}
hash_passwd = hashed(password)
# 添加用户到数据库
Users.add_user(username, hash_passwd, email)
return {'msg': 'ok'}

完成注册 /signup

auth.py
class SignupHandler(AuthBaseHandler):
"""
注册
"""

def get(self, *args, **kwargs):
self.render('signup.html', msg='')

def post(self, *args, **kwargs):
username = self.get_argument('username', 'no')
email = self.get_argument('email', 'no')
password1 = self.get_argument('password1', 'no')
password2 = self.get_argument('password2', 'no')

if username and password1 and password2:
if password1 != password2:
self.render('signup.html', msg='两次输入的密码不一致')
else:
ret = register(username, password2, email) # 注册函数
# 注册成功
if ret['msg'] == 'ok':
self.session.set('tudo_user_info', username) # 注册成功后直接设置session(自动登录)
self.redirect('/') # 注册成功后自动访问首页
else:
self.render('signup.html', msg=ret['msg'])
else:
self.render('signup.html', msg={'sign failed'})

更新 /login

class LoginHandler(AuthBaseHandler):
"""
登录
"""

def get(self, *args, **kwargs):
if self.current_user: # 如果已经登录 访问/login自动跳转到/
self.redirect('/')
next = self.get_argument('next', '/') # 注意是/ 没有next (从logout跳转过来)就跳转到首页
self.render('login.html', nextname=next, error=None)

def post(self, *args, **kwargs):
username = self.get_argument('username')
password = self.get_argument('password')
next = self.get_argument('next', '/')
passed = authenticate(username, password) # 登录认证

if passed:
self.session.set('tudo_user_info', username)
# 保存最后登录时间
save_last_login(username)
self.redirect(next)
else:
self.render('login.html', nextname=next, error='用户名或密码错误')

.scalar()

如果查询到很多结果,抛出异常。
如果只有一个结果,返回它,

如果没有结果,返回​​None​

图片

保存到 post 表

增加 models

models/account.py
class Posts(Base):
__tablename__ = 'posts'
id = Column(Integer, primary_key=True, autoincrement=True)
image_url = Column(String(50))
thumb_url = Column(String(50))
user_id = Column(Integer, ForeignKey('users.id'))
# Post 有user属性 存放Users对象 , Users有posts属性 存放Posts对象(多对一)
user = relationship('Users', backref='posts', uselist=False, cascade='all')
class Posts(Base):
"""
用户图片信息
"""
__tablename__ = 'posts'
id = Column(Integer, primary_key=True, autoincrement=True)
image_url = Column(String(50)) # 大图路径
thumb_url = Column(String(50)) # 缩略图路径
user_id = Column(Integer, ForeignKey('users.id'))
# Post 有user属性 存放Users对象 , Users有posts属性 存放Posts对象
user = relationship('Users', backref='posts', cascade='all')

# 保存用户上传的图片信息 图片和特定的用户建立关系(这张图片是由这个用户传上来的)
@classmethod
def add_post_for(cls, username, image_url, thumb_url):
user = session.query(Users).filter_by(name=username).first()
post = Posts(image_url=image_url, thumb_url=thumb_url, user=user)
session.add(post)
session.commit()
alembic revision --autogenerate -m 'add table for posts'

alembic upgrade head

增加 utils 辅助函数

utils/account.py
def get_post_for(username):
"""
获取用户上传的图片信息
:param username:
:return:
"""
user = session.query(Users).filter_by(name=username).first()
if user:
return user.posts
else:
return []


def get_post(post_id):
"""
获取用户的特定图片
:param post_id:
:return:
"""
post = session.query(Posts).filter_by(id=post_id).first()
return post

更新 /upload 和辅助函数

main.py
class UploadHandler(AuthBaseHandler):
"""
接受图片上传
"""

@tornado.web.authenticated
def get(self, *args, **kwargs):
self.render('upload.html')

def post(self, *args, **kwargs):
# 提取表单中‘name’为‘newimg’的文件元数据 获取上传文件信息
img_files = self.request.files.get('newimg')

if img_files:
for img in img_files:
# img_file['filename']获取文件的名称 有些文件需要以二进制的形式存储
image_url = 'uploads/{}'.format(img['filename'])
# ./ static / uploads / thumbs / 701728.jpg
save_to = './static/{}'.format(image_url)
with open(save_to, 'wb') as f:
f.write(img['body']) # img_file['body']获取文件的内容

# 生成缩略图 ./ static / uploads /thumbs/ 701728_200x200.jpg
save_thumb_to = photo.make_thumbs(save_to)
thumb_url = os.path.relpath(save_thumb_to, 'static')

# 保存图片的地址 把url存到数据库
Posts.add_post_for(self.current_user, image_url, thumb_url)

self.write({'msg': 'got file: {}'.format(img_files[0]['filename'])})

else:
self.write({'msg': 'empty form data'})

更新 /index 和 /explore

main.py
class IndexHandler(AuthBaseHandler):
"""
首页
"""

@tornado.web.authenticated
def get(self, *args, **kwargs):
post_list = get_post_for(self.current_user)
self.render('index.html', post_list=post_list)


class ExploreHandler(AuthBaseHandler):
"""
发现页
"""

@tornado.web.authenticated
def get(self, *args, **kwargs):
post_list = get_post_for(self.current_user)
self.render('explore.html', post_list=post_list)


class PostHandler(AuthBaseHandler):
"""
详情页
"""

@tornado.web.authenticated
def get(self, post_id):
post = get_post(post_id)
if not post:
self.write('post id is not exists')
else:
self.render('post.html', post=post)
index.html
{% extends 'base.html' %}

{% block title %}
index page
{% end %}

{% block content %}
<p><a href="/logout">登出</a></p>
{% for post in post_list %}
<img src="{{ static_url(post.image_url) }}" width="666">
{% end %}

{% end %}
explore.html
{% extends 'base.html' %}

{% block title %}
explore page
{% end %}

{% block content %}
<p><a href="/logout">登出</a></p>
{% for post in post_list %}
<img src="{{ static_url(post.image_url) }}" width="200px">
{% end %}
{% end %}
post.html
{% extends 'base.html' %}

{% block title %}
post page
{% end %}

{% block content %}
<img src="{{ static_url(post.image_url)}}" width="560px">
{% end %}
photo.py
# 生成缩略图
def make_thumb(path):
print(path) # ./static/uploads/1172020.jpg

# basename:截取path中的去目录部分的最后的文件或路径名
# dirname:截取path中的目录路径

dirname = os.path.dirname(path) # static/uploads
file, ext = os.path.splitext(os.path.basename(path)) # file=1172020 ext=.jpg

im = Image.open(path) # 打开./static/uploads/1172020.jpg图片

size = (200, 200)
im.thumbnail(size) # 按长200高200缩放
# *size = 200,200 解包
save_thumb_to = os.path.join(dirname, 'thumbs', '{}_{}x{}.jpg'.format(file, *size))

# 保存./static/uploads/thumbs/1172020_200x200.jpg
im.save(save_thumb_to, "JPEG")

return save_thumb_to

作业

把数据保存用起来。(提交简单截图就可以了)

完整代码

​app.py​

import tornado.web
import tornado.options
import tornado.ioloop
from tornado.options import define, options

from handlers import main,auth

define(name='port', default='8000', type=int, help='run port')


class Application(tornado.web.Application):
def __init__(self):
handlers = [
(r'/', main.IndexHandler),
(r'/explore', main.ExploreHandler),
(r'/post/(?P<post_id>[0-9]+)', main.PostHandler),
(r'/upload', main.UploadHandler),
(r'/login', auth.LoginHandler),
(r'/logout', auth.LogoutHandler),
(r'/signup', auth.SignupHandler),
]
settings = dict(
debug=True,
template_path='templates',
static_path='static',
login_url='/login',
cookie_secret='bZJc2sWbQLKos6GkHn/VB9oXwQt8S0R0kRvJ5/xJ89E=',
pycket={
'engine': 'redis',
'storage': {
'host': 'localhost',
'port': 6379,
# 'password': '',
'db_sessions': 5, # redis db index
'db_notifications': 11,
'max_connections': 2 ** 30,
},
'cookies': {
'expires_days': 30,
},
}
)

super(Application, self).__init__(handlers, **settings)


application = Application()

if __name__ == '__main__':
tornado.options.parse_command_line()
application.listen(options.port)
print("Server start on port {}".format(str(options.port)))
tornado.ioloop.IOLoop.current().start()

​main.py​

import tornado.web
from utils import photo
from models.account import Posts
from pycket.session import SessionMixin
from utils.account import get_post_for,get_post
import os


class AuthBaseHandler(tornado.web.RequestHandler, SessionMixin):
def get_current_user(self):
return self.session.get('tudo_user_info')


class IndexHandler(AuthBaseHandler):
"""
首页
"""

@tornado.web.authenticated
def get(self, *args, **kwargs):
post_list = get_post_for(self.current_user)
self.render('index.html', post_list=post_list)


class ExploreHandler(AuthBaseHandler):
"""
发现页
"""

@tornado.web.authenticated
def get(self, *args, **kwargs):
post_list = get_post_for(self.current_user)
self.render('explore.html', post_list=post_list)


class PostHandler(AuthBaseHandler):
"""
详情页
"""

@tornado.web.authenticated
def get(self, post_id):
post = get_post(post_id)
if not post:
self.write('post id is not exists')
else:
self.render('post.html', post=post)


class UploadHandler(AuthBaseHandler):
"""
接受图片上传
"""

@tornado.web.authenticated
def get(self, *args, **kwargs):
self.render('upload.html')

def post(self, *args, **kwargs):
# 提取表单中‘name’为‘newimg’的文件元数据 获取上传文件信息
img_files = self.request.files.get('newimg')

if img_files:
for img in img_files:
# img_file['filename']获取文件的名称 有些文件需要以二进制的形式存储
image_url = 'uploads/{}'.format(img['filename'])
# ./ static / uploads / thumbs / 701728.jpg
save_to = './static/{}'.format(image_url)
with open(save_to, 'wb') as f:
f.write(img['body']) # img_file['body']获取文件的内容

# 生成缩略图 ./ static / uploads /thumbs/ 701728_200x200.jpg
save_thumb_to = photo.make_thumbs(save_to)
thumb_url = os.path.relpath(save_thumb_to, 'static')

# 保存图片的地址 把url存到数据库
Posts.add_post_for(self.current_user, image_url, thumb_url)

self.write({'msg': 'got file: {}'.format(img_files[0]['filename'])})

else:
self.write({'msg': 'empty form data'})

​photo.py​

import os
import glob
from PIL import Image


def make_thumbs(path):
"""
为指定的path文件生成它所在目录的 thumbs 目录下的小图文件
:param path: ./static/uploads/701728.jpg
:return:
"""

# ./ static / uploads / 701728.jpg
im = Image.open(path)
size = (200, 200)
im.thumbnail(size)

dirname = os.path.dirname(path) # ./ static / uploads
basename = os.path.basename(path) # 701728.jpg
file, ext = os.path.splitext(basename)

# ./ static / uploads /thumbs/ 701728_200x200.jpg
save_thumbs_to = os.path.join(dirname, 'thumbs', '{}_{}x{}{}'.format(file, *size, ext))
im.save(save_thumbs_to, "JPEG")
return save_thumbs_to

base.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}base{% end %}</title>
</head>
<body>
{% block content %}
base
{% end %}
</body>
</html>

index.html

{% extends 'base.html' %}

{% block title %}
index page
{% end %}

{% block content %}
<p><a href="/logout">登出</a></p>
{% for post in post_list %}
<img src="{{ static_url(post.image_url) }}" width="666">
{% end %}

{% end %}

explore.html

{% extends 'base.html' %}

{% block title %}
explore page
{% end %}

{% block content %}
<p><a href="/logout">登出</a></p>
{% for post in post_list %}
<img src="{{ static_url(post.image_url) }}" width="200px">
{% end %}
{% end %}

post.html

{% extends 'base.html' %}

{% block title %}
post page
{% end %}

{% block content %}
<img src="{{ static_url(post.image_url)}}" width="560px">
{% end %}

upload.html

{% extends base.html %}

{% block title %}
upload
{% end %}

{% block content %}
<p><a href="/logout">登出</a></p>
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="newimg">
<input type="submit">
</form>
{% end %}

signup.html

{% extends 'base.html' %}

{% block title %}
signup page
{% end %}

{% block content %}

<div class="">
{% if msg %}
{{ msg }}
{% end %}
</div>

<form action="/signup" enctype="multipart/form-data" method="post">
<div class="form-group">
Username
<input autofocus="" class="form-control" id="id_username" maxlength="150" name="username" type="text" required="">
</div>
<div class="form-group">
Email
<input class="form-control" id="id_email" name="email" type="email" required="">
</div>
<div class="form-group">
Password
<input class="form-control" id="id_password1" name="password1" type="password" required="">
</div>
<div class="form-group">
Password confirmation
<input class="form-control" id="id_password2" name="password2" type="password" required="">
</div>
<button class="btn btn-default">注册</button>
<div class="text-center help-text">
已有账号请 <a href="/login">登录</a>
</div>
</form>
{% end %}

login.html

{% extends 'base.html' %}

{% block title %}
login page
{% end %}

{% block content %}

<div class="">
{% if error %}
{{ error }}
{% end %}
<form action="/login?next={{ nextname }}" method="post" enctype="multipart/form-data">
<div class="form-group">
Username
<input autofocus="" class="form-control" id="id_username" maxlength="254" name="username" type="text"
required="">
</div>

<div class="form-group">
Password
<input class="form-control" id="id_password" name="password" type="password" required="">
</div>

<button class="">Login</button>

<div>
还没有账号 需要<a href="/signup">注册</a>一个
</div>
</form>
</div>
{% end %}

​auth.py​

from .main import AuthBaseHandler
from utils.account import authenticate, register, save_last_login
from models.account import Users, session
import tornado.web


class LoginHandler(AuthBaseHandler):
"""
登录
"""

def get(self, *args, **kwargs):
if self.current_user: # 如果已经登录 访问/login自动跳转到/
self.redirect('/')
next = self.get_argument('next', '/') # 注意是/ 没有next (从logout跳转过来)就跳转到首页
self.render('login.html', nextname=next, error=None)

def post(self, *args, **kwargs):
username = self.get_argument('username')
password = self.get_argument('password')
next = self.get_argument('next', '/')
passed = authenticate(username, password) # 登录认证

if passed:
self.session.set('tudo_user_info', username)
# 保存最后登录时间
save_last_login(username)
self.redirect(next)
else:
self.render('login.html', nextname=next, error='用户名或密码错误')


class LogoutHandler(AuthBaseHandler):
"""
登出 注销用户
"""

@tornado.web.authenticated
def get(self, *args, **kwargs):
self.session.set('tudo_user_info', '')
self.redirect('/login') # 直接跳转到 login 没有next 默认是/


class SignupHandler(AuthBaseHandler):
"""
注册
"""

def get(self, *args, **kwargs):
self.render('signup.html', msg='')

def post(self, *args, **kwargs):
username = self.get_argument('username', 'no')
email = self.get_argument('email', 'no')
password1 = self.get_argument('password1', 'no')
password2 = self.get_argument('password2', 'no')

if username and password1 and password2:
if password1 != password2:
self.render('signup.html', msg='两次输入的密码不一致')
else:
ret = register(username, password2, email) # 注册函数
# 注册成功
if ret['msg'] == 'ok':
self.session.set('tudo_user_info', username) # 注册成功后直接设置session(自动登录)
self.redirect('/') # 注册成功后自动访问首页
else:
self.render('signup.html', msg=ret['msg'])
else:
self.render('signup.html', msg={'sign failed'})

utils/account.py

import hashlib
from models.account import Users, session, Posts
from datetime import datetime


def hashed(passwd):
return hashlib.md5(passwd.encode('utf8')).hexdigest()


def authenticate(username, password):
"""
登录认证
:param username:
:param password:
:return:
"""
if username and password:
# 获取数据库中username对应的密码
user_passwd = Users.get_passwd(username)
# 如果用户存在 密码匹配
if user_passwd and user_passwd == hashed(password):
return True
return False


def register(username, password, email):
"""
注册
:param username:
:param password:
:param email:
:return:
"""
# 查看用户是否存在于数据库中
if Users.is_exists(username):
return {'msg': '用户已存在'}
hash_passwd = hashed(password)
# 添加用户到数据库
Users.add_user(username, hash_passwd, email)
return {'msg': 'ok'}


def save_last_login(username):
"""
# 保存用户username最后登录时间
:param username:
:return:
"""
t = datetime.now()
print("user {} login at {}".format(username, t))
session.query(Users).filter_by(name=username).update({Users.last_login: t})
session.commit()


def get_post_for(username):
"""
获取用户上传的图片信息
:param username:
:return:
"""
user = session.query(Users).filter_by(name=username).first()
if user:
return user.posts
else:
return []


def get_post(post_id):
"""
获取用户的特定图片
:param post_id:
:return:
"""
post = session.query(Posts).filter_by(id=post_id).first()
return post

models/account.py

from sqlalchemy import (Column, Integer, String, DateTime, ForeignKey)
from sqlalchemy import exists
from sqlalchemy.orm import relationship
from sqlalchemy.sql import exists

from .db import Base, DBSession

from datetime import datetime

session = DBSession()


class Users(Base):
__tablename__ = 'users'

id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(50), unique=True, nullable=False)
password = Column(String(50), nullable=False)
created = Column(DateTime, default=datetime.now)
email = Column(String(50))
last_login = Column(DateTime)

def __repr__(self):
return '<User(#{}:{})>'.format(self.id, self.name)

# 增加用户到数据库中
@classmethod
def add_user(cls, username, password, email=''):
user = Users(name=username, password=password, email=email, last_login=datetime.now())
session.add(user)
session.commit()

# 查询用户username对应的密码
@classmethod
def get_passwd(cls, username):
user = session.query(Users).filter_by(name=username).first()
# 如果用户存在 返回密码
if user:
return user.password
# 用户不存在 返回空
else:
return ''

# 判断用户是否存在于数据库中
@classmethod
def is_exists(cls, username):
ret = session.query(exists().where(Users.name == username)).scalar()
return ret


class Posts(Base):
"""
用户图片信息
"""
__tablename__ = 'posts'
id = Column(Integer, primary_key=True, autoincrement=True)
image_url = Column(String(50)) # 大图路径
thumb_url = Column(String(50)) # 缩略图路径
user_id = Column(Integer, ForeignKey('users.id'))
# Post 有user属性 存放Users对象 , Users有posts属性 存放Posts对象(多对一)
user = relationship('Users', backref='posts', uselist=False, cascade='all')
# 保存用户上传的图片信息 图片和特定的用户建立关系(这张图片是由这个用户传上来的)
@classmethod
def add_post_for(cls, username, image_url, thumb_url):
user = session.query(Users).filter_by(name=username).first()
post = Posts(image_url=image_url, thumb_url=thumb_url, user=user)
session.add(post)
session.commit()


if __name__ == '__main__':
Base.metadata.create_all()