增加功能
增加用户页面
- /profile handler
class ProfileHandler(AuthBaseHandler):
"""
显示用户上传的图片 和 喜欢的图片列表
"""
@tornado.web.authenticated
def get(self, *args, **kwargs):
user = get_user(self.current_user)
like_post_list = get_like_post(user)
self.render('profile.html', user=user, like_post_list=like_post_list)
.card卡片组件(样式)
- html 模板
{% extends 'base.html' %}
{% block title %}
index page
{% end %}
{% block content %}
<div class="row">
<div class="card col-12">
<div class="card-header"><h3>{{ current_user }}用户上传</h3></div>
{% for post in user.posts %}
<div class="col-6 col-sm-3">
<a href="/post/{{ post.id }}">
<img src="{{ static_url(post.thumb_url) }}">
</a>
</div>
{% end %}
</div>
<div class="card col-12">
<div class="card-header"><h3>用户喜欢</h3></div>
{% for post in like_post_list %}
<a href="/post/{{ post.id }}">
<img src="{{ static_url(post.thumb_url) }}">
</a>
{% end%}
</div>
</div>
{% end %}
记录用户喜欢的图片
- 增加 Like model (记录哪个用户 喜欢 哪个图片)
class Like(Base):
"""
记录用户喜欢的图片信息
"""
__tablename__ = 'likes'
user_id = Column(Integer, ForeignKey('users.id'), nullable=False, primary_key=True)
post_id = Column(Integer, ForeignKey('posts.id'), nullable=False, primary_key=True)
alembic revision --autogenerate -m 'add like model'
alembic upgrade head
-
数据库操作
- 用户喜欢的哪些图片
- 一个图片有哪些用户喜欢
-
增加辅助函数
utils/account
def get_user(username):
"""
查询用户user
:return:
"""
user = session.query(Users).filter_by(name=username).first()
return user
def get_like_post(user):
"""
查询用户user喜欢的图片的post对象
:param user: Users的实例
:return: Posts的实例
"""
# posts = []
# likes = session.query(Like).filter_by(user_id=user.id).all()
# for like in likes:
# post = session.query(Posts).filter_by(id=like.post_id).first()
# posts.append(post)
posts = session.query(Posts).filter(Like.user_id == user.id, Posts.id == Like.post_id).all()
return posts
- like 操作 ajax post
展示在用户和单独图片页面
- 读取数据到 handler
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:
like_count = get_like_count(post)
self.render('post.html', post=post, like_count=like_count)
- 修改 html 模板
post.html
{% extends 'base.html' %}
{% block title %}
post page
{% end %}
{% block content %}
<div class="row">
<div class="col-sm-8">
<img src="{{ static_url(post.image_url)}}" width="560">
</div>
<div class="col-sm-4">
upload by {{ post.user.name }}<br>
{% if like_count %}
<i class="fa fa-heart text-danger"></i> {{ like_count }}
{% end %}
</div>
</div>
{% end %}
作业
增加用户页面和 likes 表,展示图片的喜欢信息
代码
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'/profile', main.ProfileHandler),
(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()
utils/account.py
import hashlib
from models.account import Users, session, Posts, Like
from datetime import datetime
from sqlalchemy import func
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_all():
"""
获取所有图片信息(降序)
:return:
"""
post_list = session.query(Posts).order_by(Posts.id.desc()).all()
return post_list
def get_post(post_id):
"""
获取用户的特定图片
:param post_id:
:return:
"""
post = session.query(Posts).filter_by(id=post_id).first()
return post
def get_user(username):
"""
查询用户user
:return:
"""
user = session.query(Users).filter_by(name=username).first()
return user
def get_like_post(user):
"""
查询用户user喜欢的图片的post对象
:param user: Users的实例
:return: Posts的实例
"""
# posts = []
# likes = session.query(Like).filter_by(user_id=user.id).all()
# for like in likes:
# post = session.query(Posts).filter_by(id=like.post_id).first()
# posts.append(post)
# 去除用户自己上传的图片
posts = session.query(Posts).filter(Like.user_id == user.id, Posts.id == Like.post_id,
Posts.user_id != user.id).all()
return posts
def get_like_count(post):
"""
:param post:
:return:
"""
like_count = session.query(Like).filter_by(post_id=post.id).count()
return like_count
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(100)) # 大图路径
thumb_url = Column(String(100)) # 缩略图路径
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()
return post
class Like(Base):
"""
记录用户喜欢的图片信息
"""
__tablename__ = 'likes'
user_id = Column(Integer, ForeignKey('users.id'), nullable=False, primary_key=True)
post_id = Column(Integer, ForeignKey('posts.id'), nullable=False, primary_key=True)
if __name__ == '__main__':
Base.metadata.create_all()
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, get_post_all, get_user, get_like_post, get_like_count
from utils.photo import ImageSave
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_all()
self.render('explore.html', post_list=post_list, current_user=self.current_user)
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:
like_count = get_like_count(post)
self.render('post.html', post=post, like_count=like_count)
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:
post_id = 0
for img in img_files:
# 保存的图片的目录 名字
image_saver = ImageSave(self.settings['static_path'], img['filename'])
# 保存图片
image_saver.save_image(img['body'])
# 生成缩略图 ./ static / uploads /thumbs/ 701728_200x200.jpg
image_saver.make_thumbs()
# 保存图片的用户 大图地址 缩略图地址 把url存到数据库
post = Posts.add_post_for(self.current_user, image_saver.image_url, image_saver.thumb_url)
post_id = post.id
self.redirect('post/{}'.format(post_id))
else:
self.write({'msg': 'empty form data'})
class ProfileHandler(AuthBaseHandler):
"""
显示用户上传的图片 和 喜欢的图片列表
"""
@tornado.web.authenticated
def get(self, *args, **kwargs):
username = self.get_argument('name', None) # url传参
print(username)
if not username: # name=空
username = self.current_user
user = get_user(username)
if user:
like_post_list = get_like_post(user)
self.render('profile.html', user=user, like_post_list=like_post_list)
else:
self.render('error.html')
base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{{ static_url('css/bootstrap.css') }}">
<link rel="stylesheet" href="{{ static_url('font-awesome-4.7.0/css/font-awesome.css') }}">
<title>{% block title %}base{% end %}</title>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="#">
<i class="fa fa-camera"></i>
Tudo 图片
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="/">首页<span class="sr-only">(current)</span></a>
</li>
<li class="nav-item">
<a class="nav-link" href="/explore">发现</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
用户中心
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="/profile">个人信息</a>
<a class="dropdown-item" href="#">收藏</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#">Something else here</a>
</div>
</li>
<li class="nav-item">
<a class="nav-link disabled" href="/logout">{{ current_user }}登出</a>
</li>
</ul>
<a class="btn btn-info" href="/upload">
<i class="fa fa-upload"></i>
上传
</a>
</div>
</nav>
<div class="container">
{% block content %}
base
{% end %}
</div>
<script src="{{ static_url('js/jquery-3.3.1.slim.min.js') }}"></script>
<script src="{{ static_url('js/popper.min.js') }}"></script>
<script src="{{ static_url('js/bootstrap.js') }}"></script>
</body>
</html>
profile.html
{% extends 'base.html' %}
{% block title %}
index page
{% end %}
{% block content %}
<div class="card">
<div class="card-header"><h3>{{ user.name }}用户上传</h3></div>
<div class="row">
{% for post in user.posts %}
<div class="col-6 col-sm-3">
<a href="/post/{{ post.id }}">
<img src="{{ static_url(post.thumb_url) }}" class="img-fluid">
</a>
</div>
{% end %}
</div>
</div>
<div class="card">
<div class="card-header"><h3>用户喜欢</h3></div>
<div class="row">
{% for post in like_post_list %}
<div class="col-6 col-sm-3">
<a href="/post/{{ post.id }}">
<img src="{{ static_url(post.thumb_url) }}" class="img-fluid">
</a>
</div>
{% end%}
</div>
</div>
{% end %}
post.html
{% extends 'base.html' %}
{% block title %}
post page
{% end %}
{% block content %}
<div class="row">
<div class="col-sm-8">
<img src="{{ static_url(post.image_url)}}" width="560">
</div>
<div class="col-sm-4">
upload by {{ post.user.name }}<br>
{% if like_count %}
<i class="fa fa-heart text-danger"></i> {{ like_count }}
{% end %}
</div>
</div>
{% end %}
error.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<img src="{{ static_url('images/error.jpg') }}">
<h3>您访问的页面不存在</h3>
</body>
</html>