werkzeug综合应用 - pythonweb(4)

werkzeug综合应用 - pythonweb(4)

​     本文将通过一个完整的用户管理的实例使werkzeug工作起来,并且使代码分层,看起来和SpringMVC的层次相同,完成用户注册、用户登录两个功能。前提说明:

  • 数据模式:User(id, uname, telephone, password)

  • 文件结构

    
    controller
    |	|_____   __init__.py		 # 导入文件,使得装饰器可以被扫描
    |	|_____   user_controller.py		 
    dao
    |	|_____   __init__.py
    |	|_____   user_dao.py
    utils
    |	|_____   session.py
    |	|_____   util.py
    config.py						# 配置文件,配置模板、数据库连接,声明全局变量
    |
    application.py					# 处理请求相应、核心应用类
    |
    __init__.py 
    
    
  • python生成验证码

4.1 控制层

如何在方法上直接通过注解绑定请求路径呢?python的解决方案是使用装饰器

from werkzeug.routing import Map
from werkzeug.routing import Rule

# 全局配置: 路径映射 config.py

url_map = Map()

# 使得与路径匹配的是可直接调用的函数 util.py
def route(rule, method="GET", **kwargs):
    def decorate(func):
        kwargs['endpoint'] = func
        url_map.add(Rule(rule, methods=[method], **kwargs))
        return func
    return decorate

# 同一种类型的请求组织在UserController一个类中 user_controller.py
class UserController:

    @route("/user/register")
    def to_add(self, request):
        return render_template("front/user/register.html", sessionScope=request.session)
    
# 请求处理,函数调用 application.py 
# 第一个类相关的位置参数传入None即可
adapter = url_map.bind_to_environ(environ)
endpoint, args = adapter.match()
response = endpoint(None, request, **args)
4.2 数据层:

​ 得益于python字典以及字符串的格式化,很容易就会格式化拼接得到一条SQL语句,我们执行拿到结果即可。为了避免频繁写连接数据库准备和关闭数据库代码,直接统一到一个方法进行注解:

# 配置数据库连接池 config.py
pool = PooledDB(
    **{**conn_config, **db_config}
)

# 装饰到方法上 util.py
def context_cr(use=''):
    def wrapper(func):
        @wraps(func)
        def inner(*args, **kwargs):
            conn = pool.connection()
            cr = conn.cursor(DictCursor)
            args[0].cr = cr
            try:
                res = func(*args, **kwargs)
            except Exception as e:
                raise e
            else:
                return res
		   finally:
                cr.close()
                conn.close()
        return inner
    return wrapper

# context_cr 修饰类的方法时, args[0]封装的是类的实例,所以把游标放到类的实例中去,方便方法使用
# user_dao.py
class UserDao:
	
    @context_cr
    def insert(self, obj, **kwargs):
        sql = """
            INSERT INTO user(uname, password, telephone) VALUES('{uname}', '{password}', '{telephone}')
        """.format(**obj)
        try:
            self.cr.execute(sql)
            self.cr._con.commit()
            return True
        except Exception as e:
            return False
# 全局使用
user_dao = UserDao()
4.3 用户登录状态的记录:

​      SpringMVC中,我们可以直接使用HttpSession来存放用户登录的登录信息,werkzeug在0.9版本前还提供了session的解决方案,但在后续的模块中已经取消了werkzeug.contrib模块相关内容,因为session机制极具灵活,为了确保分布式应用中可以共享session, 可以将session放到redis集群里 分布式session方案0.9版本以前提供了FileSystemSessionStore来将session信息存放到本地文件系统,以及提供了一个SessionMiddleWare解决如何将session与浏览器cookie绑定,我们可以下载0.9版本的代码学习session的实现方法,实现一个redis或sqlite的session方案,本文采用本地文件系统存放session。

# session配置,session堆 config.py
session_store = FilesystemSessionStore(path=session_path)

# 请求处理session及响应装配cookie
class Root:
    def __init__(self):

        self.dispatch_request = SharedDataMiddleware(self.dispatch_request, {
            "/static": static_path,
            "/img": image_path,
        })

    def dispatch_request(self, environ, start_response):
        request = Request(environ)
        sid = request.cookies.get("sid", None)
        if not sid:
            session = session_store.new()
        else:
            session = session_store.get(sid)
        request.session = session
       	# ...... 路由匹配代码,省略
        def wrapped_start_response(status, headers, exc_info=None):
            headers.append(('Set-Cookie', dump_cookie('sid', session.sid)))
            return start_response(status, headers, exc_info)
        start_response_ = sid and start_response or wrapped_start_response
        return response(environ, start_response_)

    def __call__(self, environ, start_response):
        return self.dispatch_request(environ, start_response)
4.4 python 生成验证码:
import random
from PIL import Image, ImageDraw, ImageFont, ImageFilter
import os
from io import BytesIO


_letter_cases = "abcdefghjkmnpqrstuvwxy"  # 小写字母,去除可能干扰的i,l,o,z
_upper_cases = _letter_cases.upper()  # 大写字母
_numbers = ''.join(map(str, range(10)))  # 数字
init_chars = ''.join((_letter_cases, _upper_cases, _numbers))


def generate_validate_code(size=(120, 30),
                           chars=init_chars,
                           img_type="JPEG",
                           mode="RGB",
                           bg_color=(230, 230, 230),
                           fg_color=(18, 18, 18),
                           font_size=20,
                           length=4,
                           draw_lines=True,
                           n_line=(1, 2),
                           draw_points=True,
                           point_chance=1):
    '''
    @param size: 		图片的大小,格式(宽,高),默认为(120, 30)
    @param chars: 		允许的字符集合,格式字符串
    @param img_type: 	图片保存的格式,默认为GIF,可选的为GIF,JPEG,TIFF,PNG
    @param mode: 		图片模式,默认为RGB
    @param bg_color: 	背景颜色,默认为白色
    @param fg_color: 	前景色,验证码字符颜色,默认为蓝色#0000FF
    @param font_size: 	验证码字体大小
    @param length: 		验证码字符个数
    @param draw_lines: 	是否划干扰线
    @param n_lines: 	干扰线的条数范围,格式元组,默认为(1, 2),只有draw_lines为True时有效
    @param draw_points:  是否画干扰点
    @param point_chance: 干扰点出现的概率,大小范围[0, 100]
    @return: [0]: 		二进制图像数据流
    @return: [1]: 		验证码
    '''
    width, height = size  # 宽, 高
    img = Image.new(mode, size, bg_color)  # 创建图形
    draw = ImageDraw.Draw(img)  # 创建画笔

    def get_chars():
        '''
        生成给定长度的字符串,返回列表格式
        '''
        return random.sample(chars, length)

    def create_lines():
        '''
        绘制干扰线
        '''
        line_num = random.randint(*n_line)  # 干扰线条数
        for i in range(line_num):
            # 起始点
            begin = (random.randint(0, width), random.randint(0, height))
            # 结束点
            end = (random.randint(0, width), random.randint(0, height))
            draw.line([begin, end], fill=(0, 0, 0))

    def create_points():
        '''
        绘制干扰点
        '''
        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), fill=(0, 0, 0))

    def draw_code():
        '''
        绘制验证码字符
        '''
        c_chars = get_chars()
        d_chars = ' '.join(c_chars)  # 每个字符前后以空格隔开
        font_type = os.path.join(os.path.dirname(__file__), 'DejaVu-Sans-Bold.ttf')
        font = ImageFont.truetype(font_type, font_size)
        font_width, font_height = font.getsize(d_chars)
        draw.text(((width - font_width) / 3, (height - font_height) / 3), d_chars, font=font, fill=fg_color)
        return ''.join(c_chars).lower()

    if draw_lines:
        create_lines()
    if draw_points:
        create_points()
    code = draw_code()
    # 图形扭曲参数
    params = [
        1 - float(random.randint(1, 2)) / 100,
        0,
        0,
        0,
        1 - float(random.randint(1, 10)) / 100,
        float(random.randint(1, 2)) / 500,
        0.001,
        float(random.randint(1, 2)) / 500
    ]
    img = img.transform(size, Image.PERSPECTIVE, params)  # 创建扭曲
    img = img.filter(ImageFilter.EDGE_ENHANCE_MORE)  # 滤镜,边界加强(阈值更大)
    f = BytesIO()  # 用完之后,BytesIO会自动清掉
    # img.save(f, 'png')
    data = f.getvalue()
    return data, code

​      werkzeug是可以直接构建web应用的,需要我们自己做的事情比较多,但是可以更好的理解一个web应用的运行方式:路由的匹配、请求的处理、响应的构造,所以学习werkzueg来入门pythonweb是非常有必要的,后续我们可以学习其他python重型web框架,有了基础学习起来更加得心应手,至此,werkzeug篇完。后续会将这个例子上传到github供大家下载。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值