3 用户登陆 注册 alembic.md

用户登陆 登出 注册

所有 pip install 需要 workon env 之后执行

用户登录

login.html

{% extends 'base.html' %}

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

{% block content %}

    {% if error %}
        {{ error }}
    {% end %}

    <div class="">
    <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 %}

app.py


class Application(tornado.web.Application):
   def __init__(self):
      handlers = [
         (r'/login', auth.LoginHandler),   
      ]
      settings = dict(
         debug=True,
         template_path='template',
         static_path='static',  # 相对路径
         cookie_secret='bZJc2sWbQLKos6GkHn/VB9oXwQt8S0R0kRvJ5/xJ89E=',
         login_url='/login',
         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)


使用 pycket 和 cookie_secret

pip install pycket

pip install redis

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,
    },
}

account.py

import hashlib


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


USER_DATA = {
	'name': '1',
	'password': hashed('1')
}


def authenticate(username, password):
	if username and password:
		if username == USER_DATA['name'] and hashed(password) == USER_DATA['password']:
			return True
	return False

auth.py

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

	def get(self, *args, **kwargs):
		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)
			self.redirect(next)
		else:
			self.render('login.html', nextname=next, error='用户名或密码错误')

main.py

import tornado.web
import os

from utils import photo
from pycket.session import SessionMixin


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

    
# 图片展示
class IndexHandler(AuthBaseHandler):
   """
   Home page for user,photo feeds.
   """
   @tornado.web.authenticated
   def get(self, *args, **kwargs):
      images_path = os.path.join(self.settings.get('static_path'), 'uploads')
      images = photo.get_images(images_path) 
      print(images)
      self.render('index.html', images=images)

用户登出(注销)

base.html

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

    <div style="width: 200px; height:100px;display: block;">
        <a href="/logout">登出</a>
    </div>
    <div style="width: auto;text-align: center">
        {% block content %}
            base content
        {% end %}
    </div>

</body>
</html>

main.py

class Application(tornado.web.Application):
   def __init__(self):
      handlers = [
         (r'/logout', auth.LogoutHandler),
      ]
      settings = dict(
         debug=True,
         template_path='template',
         static_path='static',  # 相对路径
         cookie_secret='bZJc2sWbQLKos6GkHn/VB9oXwQt8S0R0kRvJ5/xJ89E=',
         login_url='/login',
         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)

auth.py

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

用户注册

signup.html

{% extends 'base.html' %}

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

{% block content %}
    <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>
        <div class="text-center help-text">
            已有账号请 <a href="/login">登录</a>
        </div>
    </form>
{% end %}

auth.py


class SignupHandler(AuthBaseHandler):
   def get(self, *args, **kwargs):
      self.render('signup.html')

   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 password1:
         if password1 != password2:
            self.write({'msg': '两次输入的密码不匹配'})
         else:
            pass

app.py

class Application(tornado.web.Application):
   def __init__(self):
      handlers = [
         (r'/signup', auth.SignupHandler),
      ]
      settings = dict(
         debug=True,
         template_path='template',
         static_path='static',  # 相对路径
         cookie_secret='bZJc2sWbQLKos6GkHn/VB9oXwQt8S0R0kRvJ5/xJ89E=',
         login_url='/login',
         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()

SQLalchemy 版本迁移

pip install pymysql

pip install sqlalchemy

pip install alembic

完成 pip 安装之后

  • 在 shell 里面 cd 到项目根目录执行
    alembic init alembic

  • 用 pycharm 把生成的文件 download 回来(包括 alembic 目录和 alembic.ini

  • 修改 alembic.ini 设置数据库连接。

    sqlalchemy.url = driver://user:pass@localhost/dbname

    变成

    sqlalchemy.url = mysql+pymysql://admin:[email protected]:3306/tudo
    
  • env.py 中设置,将target_metadata赋值成数据库的元数据(metadata)

    
    from models.account import Base
    
    target_metadata = Base.metadata
    
    
  • 如果执行 revision 有 import 报错,注意是否正确将当前项目目录添加到 sys.path 路径


import sys
from os.path import abspath, dirname

root = dirname(dirname(abspath(__file__)))
print(root)
sys.path.append(root)
from __future__ import with_statement
from alembic import context
from sqlalchemy import engine_from_config, pool
from logging.config import fileConfig

import os, sys
from os.path import abspath, dirname

root = dirname(dirname(abspath(__file__)))
print(root)
sys.path.append(root)

from models.account import Base

# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config

# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)

# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
target_metadata = Base.metadata


# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.


def run_migrations_offline():
   """Run migrations in 'offline' mode.

   This configures the context with just a URL
   and not an Engine, though an Engine is acceptable
   here as well.  By skipping the Engine creation
   we don't even need a DBAPI to be available.

   Calls to context.execute() here emit the given string to the
   script output.

   """
   url = config.get_main_option("sqlalchemy.url")
   context.configure(
      url=url, target_metadata=target_metadata, literal_binds=True)

   with context.begin_transaction():
      context.run_migrations()


def run_migrations_online():
   """Run migrations in 'online' mode.

   In this scenario we need to create an Engine
   and associate a connection with the context.

   """
   connectable = engine_from_config(
      config.get_section(config.config_ini_section),
      prefix='sqlalchemy.',
      poolclass=pool.NullPool)

   with connectable.connect() as connection:
      context.configure(
         connection=connection,
         target_metadata=target_metadata
      )

      with context.begin_transaction():
         context.run_migrations()


if context.is_offline_mode():
   run_migrations_offline()
else:
   run_migrations_online()
  • 配置完成执行

    alembic revision --autogenerate -m ‘create users table’

    这里可以看到虚拟机目录在 alembic/versions 里生成了 py 文件,然后执行

    alembic upgrade head

    这样就会更新 mysql 数据库了

  • 删除配置

    rm alembic/versions/…

  • 若想增加一个属性

    先在Users类写好 再在终端执行

    alembic revision --autogenerate -m ‘add created for users’

    or

    (alembic revision --autogenerate -m ‘add field for users’)

    alembic upgrade head

    (alembic revision --autogenerate -m ‘add thumb url’)

  • 若想增加一个类

    alembic revision --autogenerate -m ‘add post model’

    alembic upgrade head

  • 回退一个版本

alembic downgrade -1

如果报这个错误

 Can't locate revision identified by

将alembic_version表中的数据删掉即可

作业:

实现 登陆/登出页面,session 的设置,login 加上 next 跳转回原来访问页面。(提交截图)

hashlib

廖大神

https://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/0013868328251266d86585fc9514536a638f06b41908d44000

import hashlib

md5 = hashlib.md5()
md5.update('how to use md5 in python hashlib?')
print md5.hexdigest()
d26a53750bc40b38b65a520292f69306

附加:

了解数据库迁移工具 alembic 的使用

参考文档:

sqlalchemy数据迁移 - CSDN博客

Python数据模型构建和迁移方案:SQLAlchemy&Alembic

完整代码

app.py

import os

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

from handlers import main
from handlers import auth

define('port', default='8000', type=int, help='Listening 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='template',
         static_path='static',  # 相对路径
         # static_path=os.path.join(os.path.dirname(__file__), 'static'),  # 绝对路径
         cookie_secret='bZJc2sWbQLKos6GkHn/VB9oXwQt8S0R0kRvJ5/xJ89E=',
         login_url='/login',
         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
import os

from utils import photo
from pycket.session import SessionMixin


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


# 图片展示
class IndexHandler(AuthBaseHandler):
   """
   Home page for user,photo feeds.
   """

   @tornado.web.authenticated
   def get(self, *args, **kwargs):
      # os.path.join组合成新目录
      images_path = os.path.join(self.settings.get('static_path'), 'uploads')  # static/uploads
      # 获取该路径下所有的图片路径
      images = photo.get_images(images_path)  # ['static/uploads/1106628.jpg', 'static/uploads/701728.jpg',...]
      print(images)
      self.render('index.html', images=images)


# 缩略图展示
class ExploreHandler(AuthBaseHandler):
   """
   Explore page ,photo of other users.
   """

   @tornado.web.authenticated
   def get(self, *args, **kwargs):
      # 获取该路径下所有的缩略图的路径
      images_url = photo.get_images('./static/uploads/thumbs')
      # ['./static/uploads/thumbs/1106620_200x200.jpg', './static/uploads/thumbs/1106622_200x200.jpg',...]
      self.render('explore.html', images=images_url)


class PostHandler(AuthBaseHandler):
   """
   Single photo page,and maybe comments
   """

   @tornado.web.authenticated
   def get(self, *args, **kwargs):
      self.render('post.html', post_id=kwargs['post_id'])


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')

      print(img_files)  # [{"filename": ..., "content_type": ..., "body": ...},]
      print(img_files[0]['filename'])  # 例:1178645.jpg

      file_size = self.request.headers.get('Content-Length')  # 18539
      print(file_size)

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

            photo.make_thumb('./static/uploads/' + img_file['filename'])  # 生成缩略图

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

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

photo.py

import glob
import os
from PIL import Image


# 获取图片路径
def get_images(path):
   print(path)  # static/uploads
   images = []
   for file in glob.glob(path + '/*.jpg'):
      images.append(file)  # static/uploads/1172020.jpg
   return images


# 生成缩略图
def make_thumb(path):
   print(path)  # ./static/uploads/1172020.jpg

   file, ext = os.path.splitext(os.path.basename(path))

   print(os.path.basename(path))  # 1172020.jpg
   print(file)  # 1172020
   print(ext)  # .jpg

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

   im.thumbnail((200, 200))  # 按长200高200缩放

   # 保存./static/uploads/thumbs/1172020_200x200.jpg
   im.save('./static/uploads/thumbs/{}_{}x{}.jpg'.format(file, 200, 200), "JPEG")

base.html

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

    <div style="width: 200px; height:100px;display: block;">
        <a href="/logout">登出</a>
    </div>
    <div style="width: auto;text-align: center">
        {% block content %}
            base content
        {% end %}
    </div>

</body>
</html>

index.html

{% extends 'base.html' %}

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

{% block content %}
    {% for img in images %}
        <img src="{{ img }} " width="666">
    {% end %}
{% end %}

explore.html

{% extends 'base.html' %}

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

{% block content %}
    {% for img in images %}
        <img src="{{ img }} " width="333">
    {% end %}
{% end %}

post.html

{% extends 'base.html' %}

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

{% block content %}
    <img src="{{ static_url("images/{}.jpg".format(post_id)) }} " width=666">
{% end %}

upload.html

{% extends 'base.html' %}

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

{% block content %}
    {#entype 设置编码格式 上传文件:multipart/form-data#}
    <form action="/upload" enctype="multipart/form-data" method="post">
        <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 %}

    {% if error %}
        {{ error }}
    {% end %}

    <div class="">
    <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

import tornado.web
from utils.account import authenticate
from .main import AuthBaseHandler


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)
			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')

   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 password1:
         if password1 != password2:
            self.write({'msg': '两次输入的密码不匹配'})
         else:
            pass

account.py

import hashlib

USER_DATA = {
   'name': 'wu',
   'password': hashlib.md5('123qwe'.encode('utf8')).hexdigest(),
}


def authenticate(username, password):
   if username and password:
      hash_pw = hashlib.md5(password.encode()).hexdigest()
      if username == USER_DATA['name'] and hash_pw == USER_DATA['password']:
         return True
   return False

数据库迁移代码

db.py

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

HOSTNAME = '127.0.0.1'
PORT = '3306'
DATABASE = 'mydb'
USERNAME = 'admin'
PASSWORD = 'Root110qwe'

DB_URI = 'mysql+pymysql://{}:{}@{}:{}/{}?charset=utf8'.format(
   USERNAME,
   PASSWORD,
   HOSTNAME,
   PORT,
   DATABASE
)

engin = create_engine(DB_URI)

DBSession = sessionmaker(bind=engin)

Base = declarative_base(engin)


if __name__ == '__main__':
	connection = engin.connect()
	result = connection.execute('select 1')
	print(result.fetchone())

account.py

from sqlalchemy import (Column, Integer, String, DateTime)

from .db import Base

from datetime import datetime


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)

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


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

目录结构

tornado2项目

├── alembic
│ ├── env.py
│ ├── pycache
│ │ └── env.cpython-35.pyc
│ ├── README
│ ├── script.py.mako
│ └── versions
│ ├── 084b87695f74_create_user_table.py
│ ├── cf918abbb966_add_created_for_users.py
│ └── pycache
│ ├── 084b87695f74_create_user_table.cpython-35.pyc
│ ├── 16354e8f04e7_create_user_table.cpython-35.pyc
│ └── cf918abbb966_add_created_for_users.cpython-35.pyc
├── alembic.ini
├── app.py
├── handlers
│ ├── auth.py
│ ├── init.py
│ ├── main.py
│ └── pycache
│ ├── auth.cpython-35.pyc
│ ├── init.cpython-35.pyc
│ └── main.cpython-35.pyc
├── models
│ ├── account.py
│ ├── db.py
│ ├── init.py
│ └── pycache
│ ├── account.cpython-35.pyc
│ ├── db.cpython-35.pyc
│ └── init.cpython-35.pyc
├── static
│ ├── images
│ │ ├── 1.jpg
│ │ ├── 2.jpg
│ │ ├── 3.jpg
│ │ └── 4.jpg
│ └── uploads
│ ├── 1106620.jpg
│ ├── 1106622.jpg
│ ├── 1178645.jpg
│ └── thumbs
│ ├── 1106620_200x200.jpg
│ ├── 1106622_200x200.jpg
│ ├── 1178645_200x200.jpg
│ └── 200200
│ └── 2_200
200.jpg
├── template
│ ├── base.html
│ ├── explore.html
│ ├── index.html
│ ├── login.html
│ ├── post.html
│ ├── signup.html
│ └── upload.html
└── utils
​ ├── account.py
​ ├── init.py
​ ├── photo.py
​ └── pycache
​ ├── account.cpython-35.pyc
​ ├── init.cpython-35.pyc
​ └── photo.cpython-35.pyc

猜你喜欢

转载自blog.csdn.net/qq_14993591/article/details/82830175