rayzz

rayzz 查看完整档案

杭州编辑  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

rayzz 赞了文章 · 2019-01-16

flask入门1

flask

每天的内容

  1. flask跑起来
  2. 模板引擎
  3. flask表单
  4. 文件上传邮件发送
  5. flask-sqlalchemy

一、web框架的简介

M 模型 负责数据的操作

V 视图 负责数据的展示

C 控制器 控制你的M的操作以及视图模板的渲染

在python中叫做MVT

M 模型 负责数据的操作

V 控制你的M的操作以及视图模板的渲染 业务逻辑的操作

T templates 模板 负责数据的展示

二、架构

BS browser-》server

CS client-》server

三、FLASK

概念: flask是一个非常小的web框架 被称为微型框架 只提供了一个强健的核心 其它的都是通过第三方扩展库来实现

组成

  1. 调试 路由 WSGI
  2. 模板引擎 jinja2 (就是由flask核心人员开发的模板引擎)

使用:

安装 pip install flask

实例
from flask import Flask

app = Flask(__name__) #实例化flask

#路由地址 根据用户不同的url进行处理
@app.route('/')
def index():#处理当前请求的函数
    return 'Hello Flask'

if __name__ == '__main__':
    app.run() #运行当前的flask

四、视图函数

(1) 无参路由

#路由地址和视图函数名称 是否同名没有关系
#http://127.0.0.1:5000/test/
@app.route('/test/') #路由地址末尾的/建议加上
def test():
    return '我是测试使用的视图函数'

(2) 带一个参数的路由

http://127.0.0.1:5000/page/10/

@app.route('/page/<pagenum>/') #参数的语法格式 /路由名称/<形参名>/
def page(pagenum):
    return '当前的页码为{}'.format(pagenum)

(3) 带多个参数

#带多个参数
# http://127.0.0.1:5000/arg/10/zhansgan/
@app.route('/arg/<age>/<name>/')
def getarg(age,name):
    return '我叫{} 我见年{}岁了'.format(name,age)
# http://127.0.0.1:5000/arg/zhansgan_10/
@app.route('/arg/<name>_<age>/')
def getarg(age,name):
    return '我叫{} 我见年{}岁了'.format(name,age)

(4) 限制参数的类型

#参数类型
# @app.route('/argtype/<arg>/')
# @app.route('/argtype/<int:arg>/') #限定参数类型为int
# @app.route('/argtype/<float:arg>/') #限定参数类型为float
# @app.route('/argtype/<string:arg>/') #限定参数类型为string 默认就是字符串
@app.route('/argtype/<path:arg>/') #其实path就是string 但是path会将路由地址后面的所有的路由或者值都认为是一个值 /不在作为分隔符来使用
def argtype(arg):
    print(type(arg))
    print(arg)
    return '获取参数类型的视图函数'

注意

  1. 路由地址末尾的/建议加上 因为如果输入的时候没有加默认的/浏览器会自动帮你加上
  2. 形参名字写在路由的<> 中间
  3. 参数默认类型都为string

五、视图函数的响应

(1) return 字符串进行响应

@app.route('/response/')
def res():
    return '我是响应',404 #响应一个指定标准的状态码

(2) 通过make_response构造响应

导入:

from flask import make_response

@app.route('/make_response/')
def makeResponse():
    res = make_response('我是响应的内容')
    # res = make_response('我是响应的内容',404)
    return res

六、重定向 redirect

作用: 从一个地址跳向另外一个地址

导入

from flask import redirect

实例

@app.route('/')
def index():#处理当前请求的函数
    return 'Hello Flask'

#重定向
@app.route('/redirect/')
def redirect_index():
    return redirect('/') #参数为路由地址
    return redirect('/argtype/redirect_index/') #带参数路由地址的重定向

url_for 通过视图函数名称 反向构造出路由地址

导入

from flask import redirect,url_for

实例

@app.route('/redirect/')
def redirect_index():
    url = url_for('test')
    url = url_for('getarg',name='zhangsan',age=18) #带多个参数
      #@app.route('/arg/<name>_<age>/')
      #def getarg(age,name):
    return url #/test/

注意:

如果给定的视图函数名称不存在 则抛出异常

url_for 和 redirect 组合使用

@app.route('/redirect/')
def redirect_index():
    return redirect(url_for('test'))
    return redirect(url_for('getarg',name='zhangsan',age=18)) #带多个参数

七、abort 终止

概念:

在视图函数中处理的时候 可以使用abort抛出指定状态码的错误 下面代码不在执行

需要抛出标准http的状态码

from flask import abort

实例

@app.route('/abort/')
def my_abort():
    # abort(404)
    # abort(500)
    return '抛出状态码'
#捕获500的错误
@app.errorhandler(500)
def server_error(e):
    return '现在能看到了吗{}'.format(e)

#捕获404的错误信息
@app.errorhandler(404)
def server_error(e):
    return '您访问的页面被外星人劫持走了!!!'

八、app.run() 参数说明

参数参数说明默认值
host主机名127.0.0.1
port端口号5000
debug调试False
threaded多线程False

实例

if __name__ == '__main__':
    # app.run(debug=True) #开启调试模式
    app.run(host='0.0.0.0',port=5001,debug=True,threaded=True)

十、请求 request

说明:

request是由flask框架为我们提供好的对象 使用时 只要导入即可

用户在请求的时候 框架会为当前请求的用户 创建一个request(请求的对象) 包含当前用户请求的所有信息

导入

from flask import request

  1. url 用户请求的完整的url
  2. base_url 去除get传参后的url
  3. host_url 只有主机和端口号的url
  4. path 获取请求的路由地址
  5. method 请求的方法
  6. args 获取get传参
  7. form 获取表单传递过来的数据
  8. files 获取文件上传过来的数据
  9. headers 获取用户请求过来的头信息
  10. cookies 获取用户请求过来的所有cookie
  11. json 获取用户请求过来的json数据

实例

@app.route('/request/')
def get_request():
    print('用户请求的完整的url',request.url)
    print('去除get传参后的url',request.base_url)
    print('只有主机和端口号的url',request.host_url)
    print('获取请求的路由地址',request.path)
    print(' 请求的方法',request.method)
    print('获取拼凑的get传参',request.args)
    print('获取拼凑的get传参',request.args.get('name'))
    print('获取拼凑的get传参',request.args.get('age'))
    print('获取表单传递过来的数据',request.form)
    print('获取文件上传过来的数据',request.files)
    print('获取用户请求过来的头信息',request.headers)
    print('获取用户请求过来的所有cookie',request.cookies)
    print('获取用户请求过来的json数据',request.json)
    return 'request对象'

十一、会话控制 cookie和session

cookie

设置cookie

response.set_cookie(
    key,  #设置键
    value,#设置值
    max_age=None, #过期时间
    path = '/' #当前cookie的存储路径
)

获取cookie

@app.route('/get_cookie/')
def get_cookie():
    print(request.cookies)
    return request.cookies.get('name','default默认值')

删除cookie

#清除cookie
@app.route('/del_cookie/')
def del_cookie():
    res = make_response('清除cookie')
    res.delete_cookie('name')
    return res

cookie存储值为明文存储 安全性低

cookie存在客户端(浏览器中)

cookie默认存活时间为 当前浏览结束(关闭当前的浏览器)

session

session的使用 需要一个secret_key 来进行加密产生加密的字符串

app.config['SECRET_KEY'] = 'secretkey'

会给cookie设置一个唯一的标识符 sessionId 服务器端会通过cookie携带着唯一的sessionId来区分是哪一个用户的请求 如果客户端的cookie被禁用了 那么服务器端的session将无法使用 session基于cookie

设置session

#设置session
@app.route('/set_session/')
def set_session():
     默认存活当前浏览器结束
    session['username'] = '张三'
    return '设置session'

设置session 及过期时间

#设置session
@app.route('/set_session/')
def set_session():
    session.permanent = True #设置session持久化存储
    #设置当前session的存活时间60秒 如果当前设置失败 那么存活时间为1月
    app.permanent_session_lifetime = timedelta(seconds=60)
    session['username'] = '张三'
    return '设置session'

获取session

#获取session
@app.route('/get_session/')
def get_session():
    return session.get('username','default默认值')

删除session

@app.route('/del_session/')
def del_session():
    #删除 key为username的session
    session.pop('username')
    #删除所有session
    # session.clear()
    return '删除session'

十二、flask-script扩展

简介:

就是一个flask终端运行的解析器 通过不同参数 来设置flask的启动项

安装

sudo pip3 install flask-script

使用

from flask_script import Manager #导入终端运行的解析器
app = Flask(__name__)
manager = Manager(app)
...
if __name__ == '__main__':
    manager.run()

启动参数

-h主机
-p端口号
-d调试
-r重新加载
-threaded多线程
python manage.py runserver -h

python manage.py runserver -h0.0.0.0 -p5000 -d -r --threaded

python manage.py runserver -d -r

十三、蓝本蓝图 Blueprint

概述

当所有代码越爱越多的时候 在manage.py中 很明显是不合理的 我们需要将不同功能的视图函数 存放在不同的文件中 使用我们的项目的目录结构更加的清晰

使用

user.py 用户的处理
from flask import Blueprint

user = Blueprint('user',__name__)

@user.route('/login/')
def login():
    return '登录'
manage.py中
from mysession import mysession
from user import user
#http://127.0.0.1:5000/login/
app.register_blueprint(user) #注册蓝本
#http://127.0.0.1:5000/user/login/
app.register_blueprint(user,url_prefix='/user') #注册蓝本并添加前缀

蓝本中的重定向

@app.route('/')
def index():
    # return '首页'
    return redirect('/user/login/')
    return redirect(url_for('user.login')) #使用url_for反向构造出路由的时候 需要指定当前的视图函数 是哪一个蓝本对象的

十四、请求钩子函数

在manage文件中使用

钩子函数功能描述
before_first_request第一次请求之前
before_request每次请求之前
after_request每次请求之后 没有异常
teardown_request每次请求之后 即使有异常出现

实例

@app.before_first_request
def before_first_request():
    print('before_first_request')

@app.before_request
def before_request():
    print('before_request')
    if request.method == 'GET' and request.path == '/form/':
        abort(500)

@app.after_request
def before_request(r):
    print('before_request',r)
    return r

@app.teardown_request
def teardown_request(r):
    print('teardown_request')
    return r
在蓝本中使用
钩子函数功能描述
before_app_first_request第一次请求之前
before_app_request每次请求之前
after_app_request每次请求之后 没有异常
teardown_app_request每次请求之后 即使有异常出现

实例

@user.before_app_first_request
def before_first_request():
    print('before_first_request')

@user.before_app_request
def before_request():
    print('before_request')
    if request.method == 'GET' and request.path == '/form/':
        abort(500)

@user.after_app_request
def after_request(r):
    print('after_request',r)
    return r

@user.teardown_app_request
def teardown_request(r):
    print('teardown_request')
    return r

注意:

钩子函数写在蓝本或者启动文件中 都可以捕获到所有的请求和响应(一样)一个flask中只需要写一个钩子函数而不需要重复写钩子函数

flask入门2-模板引擎

查看原文

赞 4 收藏 4 评论 0

rayzz 发布了文章 · 2019-01-09

python 读取excel文件并写入json

excel内容:
图片描述
代码:

import xlrd
import json
import operator
 
 
def read_xlsx(filename):
    # 打开excel文件
    data1 = xlrd.open_workbook(filename)
    # 读取第一个工作表
    table = data1.sheets()[0]
    # 统计行数
    n_rows = table.nrows
 
    data = []
 
    # 微信文章属性:wechat_name wechat_id title abstract url time read like number
    for v in range(1, n_rows-1):
        # 每一行数据形成一个列表
        values = table.row_values(v)
        # 列表形成字典
        data.append({'wechat_name': values[0],
                     'wechat_id':   values[1],
                     'title':       values[2],
                     'abstract':    values[3],
                     'url':         values[4],
                     'time':        values[5],
                     'read':        values[6],
                     'like':        values[7],
                     'number':      values[8],
                     })
    # 返回所有数据
    return data
 
 
if __name__ == '__main__':
    d = []
    # 循环打开每个excel
    for i in range(1, 16):
        d1 = read_xlsx('./excel data/'+str(i)+'.xlsx')
        d.extend(d1)
 
    # 微信文章属性
    # 按时间升序排列
    d = sorted(d, key=operator.itemgetter('time'))
    # 写入json文件
    with open('article.json', 'w', encoding='utf-8') as f:
        #ensure_ascii=False显示中文,indent=2缩进为2
        f.write(json.dumps(d, ensure_ascii=False, indent=2))
 
    name = []
    # 微信id写文件
    f1 = open('wechat_id.txt', 'w')
    for i in d:
        if i['wechat_id'] not in name:
            name.append(i['wechat_id'])
        f1.writelines(i['wechat_id'])
        f1.writelines('\n')
 
    print(len(name))
查看原文

赞 1 收藏 0 评论 0

rayzz 发布了文章 · 2019-01-09

linux下typora安装

# optional, but recommended
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys BA300B7755AFCFAE

# add Typora's repository
sudo add-apt-repository 'deb https://typora.io ./linux/'
sudo apt-get update

# install typora
sudo apt-get install typora
查看原文

赞 0 收藏 0 评论 0

rayzz 赞了回答 · 2018-10-16

解决普通用户没有权限使用pip,怎么解决?

要么使用sudo
要么virtualenv建立一个你自己的python环境

python教程

关注 1 回答 1

rayzz 发布了文章 · 2018-09-18

linux下typora安装

# optional, but recommended
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys BA300B7755AFCFAE

# add Typora's repository
sudo add-apt-repository 'deb https://typora.io ./linux/'
sudo apt-get update

# install typora
sudo apt-get install typora
查看原文

赞 0 收藏 0 评论 0

rayzz 发布了文章 · 2018-09-17

python 读取excel文件并写入json

excel内容:
图片描述
代码:

import xlrd
import json
import operator
 
 
def read_xlsx(filename):
    # 打开excel文件
    data1 = xlrd.open_workbook(filename)
    # 读取第一个工作表
    table = data1.sheets()[0]
    # 统计行数
    n_rows = table.nrows
 
    data = []
 
    # 微信文章属性:wechat_name wechat_id title abstract url time read like number
    for v in range(1, n_rows-1):
        # 每一行数据形成一个列表
        values = table.row_values(v)
        # 列表形成字典
        data.append({'wechat_name': values[0],
                     'wechat_id':   values[1],
                     'title':       values[2],
                     'abstract':    values[3],
                     'url':         values[4],
                     'time':        values[5],
                     'read':        values[6],
                     'like':        values[7],
                     'number':      values[8],
                     })
    # 返回所有数据
    return data
 
 
if __name__ == '__main__':
    d = []
    # 循环打开每个excel
    for i in range(1, 16):
        d1 = read_xlsx('./excel data/'+str(i)+'.xlsx')
        d.extend(d1)
 
    # 微信文章属性
    # 按时间升序排列
    d = sorted(d, key=operator.itemgetter('time'))
    # 写入json文件
    with open('article.json', 'w', encoding='utf-8') as f:
        #ensure_ascii=False显示中文,indent=2缩进为2
        f.write(json.dumps(d, ensure_ascii=False, indent=2))
 
    name = []
    # 微信id写文件
    f1 = open('wechat_id.txt', 'w')
    for i in d:
        if i['wechat_id'] not in name:
            name.append(i['wechat_id'])
        f1.writelines(i['wechat_id'])
        f1.writelines('\n')
 
    print(len(name))
查看原文

赞 1 收藏 0 评论 0

rayzz 发布了文章 · 2018-08-25

Django的 select_related 和 prefetch_related 函数对 QuerySet 查询的优化

在数据库有外键的时候,使用 select_related() 和 prefetch_related() 可以很好的减少数据库请求的次数,从而提高性能。本文通过一个简单的例子详解这两个函数的作用。虽然QuerySet的文档中已经详细说明了,但本文试图从QuerySet触发的SQL语句来分析工作方式,从而进一步了解Django具体的运作方式。

1. 实例的背景说明

假定一个个人信息系统,需要记录系统中各个人的故乡、居住地、以及到过的城市。数据库设计如下:

20140804002519328

Models.py 内容如下:

from django.db import models

class Province(models.Model):
    name = models.CharField(max_length=10)
    def __unicode__(self):
        return self.name

class City(models.Model):
    name = models.CharField(max_length=5)
    province = models.ForeignKey(Province)
    def __unicode__(self):
        return self.name

class Person(models.Model):
    firstname  = models.CharField(max_length=10)
    lastname   = models.CharField(max_length=10)
    visitation = models.ManyToManyField(City, related_name = "visitor")
    hometown   = models.ForeignKey(City, related_name = "birth")
    living     = models.ForeignKey(City, related_name = "citizen")
    def __unicode__(self):
        return self.firstname + self.lastname

注1:创建的app名为“QSOptimize”

注2:为了简化起见,qsoptimize_province 表中只有2条数据:湖北省和广东省,qsoptimize_city表中只有三条数据:武汉市、十堰市和广州市

2. select_related()

对于一对一字段(OneToOneField)和外键字段(ForeignKey),可以使用select_related 来对QuerySet进行优化

作用和方法

在对QuerySet使用select_related()函数后,Django会获取相应外键对应的对象,从而在之后需要的时候不必再查询数据库了。以上例说明,如果我们需要打印数据库中的所有市及其所属省份,最直接的做法是:

>>> citys = City.objects.all()
>>> for c in citys:
...   print c.province
...

这样会导致线性的SQL查询,如果对象数量n太多,每个对象中有k个外键字段的话,就会导致n*k+1次SQL查询。在本例中,因为有3个city对象就导致了4次SQL查询:

SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`
FROM `QSOptimize_city`
 
SELECT `QSOptimize_province`.`id`, `QSOptimize_province`.`name` 
FROM `QSOptimize_province`
WHERE `QSOptimize_province`.`id` = 1 ;
 
SELECT `QSOptimize_province`.`id`, `QSOptimize_province`.`name` 
FROM `QSOptimize_province`
WHERE `QSOptimize_province`.`id` = 2 ;
 
SELECT `QSOptimize_province`.`id`, `QSOptimize_province`.`name` 
FROM `QSOptimize_province`
WHERE `QSOptimize_province`.`id` = 1 ;

注:这里的SQL语句是直接从Django的logger:‘django.db.backends’输出出来的

如果我们使用select_related()函数:

>>> citys = City.objects.select_related().all()
>>> for c in citys:
...   print c.province
...

就只有一次SQL查询,显然大大减少了SQL查询的次数:

SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`, 
`QSOptimize_city`.`province_id`, `QSOptimize_province`.`id`, `QSOptimize_province`.`name` 
FROM`QSOptimize_city` 
INNER JOIN `QSOptimize_province` ON (`QSOptimize_city`.`province_id` = `QSOptimize_province`.`id`) ;

这里我们可以看到,Django使用了INNER JOIN来获得省份的信息。顺便一提这条SQL查询得到的结果如下:

+----+-----------+-------------+----+-----------+
| id | name      | province_id | id | name      |
+----+-----------+-------------+----+-----------+
|  1 | 武汉市    |           1 |  1 | 湖北省    |
|  2 | 广州市    |           2 |  2 | 广东省    |
|  3 | 十堰市    |           1 |  1 | 湖北省    |
+----+-----------+-------------+----+-----------+
3 rows in set (0.00 sec)

使用方法

函数支持如下三种用法:

*fields 参数

select_related() 接受可变长参数,每个参数是需要获取的外键(父表的内容)的字段名,以及外键的外键的字段名、外键的外键的外键…。若要选择外键的外键需要使用两个下划线“__”来连接。

例如我们要获得张三的现居省份,可以用如下方式:

>>> zhangs = Person.objects.select_related('living__province').get(firstname=u"张",lastname=u"三")
>>> zhangs.living.province

触发的SQL查询如下:

SELECT `QSOptimize_person`.`id`, `QSOptimize_person`.`firstname`, 
`QSOptimize_person`.`lastname`, `QSOptimize_person`.`hometown_id`, `QSOptimize_person`.`living_id`, 
`QSOptimize_city`.`id`, `QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`, `QSOptimize_province`.`id`, 
`QSOptimize_province`.`name` 
FROM `QSOptimize_person` 
INNER JOIN `QSOptimize_city` ON (`QSOptimize_person`.`living_id` = `QSOptimize_city`.`id`) 
INNER JOIN `QSOptimize_province` ON (`QSOptimize_city`.`province_id` = `QSOptimize_province`.`id`) 
WHERE (`QSOptimize_person`.`lastname` = '三'  AND `QSOptimize_person`.`firstname` = '张' );

可以看到,Django使用了2次 INNER JOIN 来完成请求,获得了city表和province表的内容并添加到结果表的相应列,这样在调用 zhangs.living的时候也不必再次进行SQL查询。

+----+-----------+----------+-------------+-----------+----+-----------+-------------+----+-----------+
| id | firstname | lastname | hometown_id | living_id | id | name      | province_id | id | name      |
+----+-----------+----------+-------------+-----------+----+-----------+-------------+----+-----------+
|  1 | 张        | 三       |           3 |         1 |  1 | 武汉市    |   1         |  1 | 湖北省    |
+----+-----------+----------+-------------+-----------+----+-----------+-------------+----+-----------+
1 row in set (0.00 sec)

然而,未指定的外键则不会被添加到结果中。这时候如果需要获取张三的故乡就会进行SQL查询了:

>>> zhangs.hometown.province
SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`, 
`QSOptimize_city`.`province_id` 
FROM `QSOptimize_city` 
WHERE `QSOptimize_city`.`id` = 3 ;

SELECT `QSOptimize_province`.`id`, `QSOptimize_province`.`name` 
FROM `QSOptimize_province` 
WHERE `QSOptimize_province`.`id` = 1

同时,如果不指定外键,就会进行两次查询。如果深度更深,查询的次数更多。

值得一提的是,从Django 1.7开始,select_related()函数的作用方式改变了。在本例中,如果要同时获得张三的故乡和现居地的省份,在1.7以前你只能这样做:

>>> zhangs = Person.objects.select_related('hometown__province','living__province').get(firstname=u"张",lastname=u"三")
>>> zhangs.hometown.province
>>> zhangs.living.province

但是1.7及以上版本,你可以像和queryset的其他函数一样进行链式操作:

>>> zhangs = Person.objects.select_related('hometown__province').select_related('living__province').get(firstname=u"张",lastname=u"三")
>>> zhangs.hometown.province
>>> zhangs.living.province

如果你在1.7以下版本这样做了,你只会获得最后一个操作的结果,在本例中就是只有现居地而没有故乡。在你打印故乡省份的时候就会造成两次SQL查询。

depth 参数

select_related() 接受depth参数,depth参数可以确定select_related的深度。Django会递归遍历指定深度内的所有的OneToOneField和ForeignKey。以本例说明:

>>> zhangs = Person.objects.select_related(depth = d)

d=1  相当于 select_related(‘hometown’,’living’)

d=2  相当于 select_related(‘hometown__province’,’living__province’)

无参数

select_related() 也可以不加参数,这样表示要求Django尽可能深的select_related。例如:zhangs = Person.objects.select_related().get(firstname=u”张”,lastname=u”三”)。但要注意两点:

  1. Django本身内置一个上限,对于特别复杂的表关系,Django可能在你不知道的某处跳出递归,从而与你想的做法不一样。具体限制是怎么工作的我表示不清楚。
  2. Django并不知道你实际要用的字段有哪些,所以会把所有的字段都抓进来,从而会造成不必要的浪费而影响性能。 

小结

  1. select_related主要针一对一和多对一关系进行优化。
  2. select_related使用SQL的JOIN语句进行优化,通过减少SQL查询的次数来进行优化、提高性能。
  3. 可以通过可变长参数指定需要select_related的字段名。也可以通过使用双下划线“__”连接字段名来实现指定的递归查询。没有指定的字段不会缓存,没有指定的深度不会缓存,如果要访问的话Django会再次进行SQL查询。
  4. 也可以通过depth参数指定递归的深度,Django会自动缓存指定深度内所有的字段。如果要访问指定深度外的字段,Django会再次进行SQL查询。
  5. 也接受无参数的调用,Django会尽可能深的递归查询所有的字段。但注意有Django递归的限制和性能的浪费。
  6. Django >= 1.7,链式调用的select_related相当于使用可变长参数。Django < 1.7,链式调用会导致前边的select_related失效,只保留最后一个。

3. prefetch_related()

对于多对多字段(ManyToManyField)和一对多字段,可以使用prefetch_related()来进行优化。或许你会说,没有一个叫OneToManyField的东西啊。实际上 ,ForeignKey就是一个多对一的字段,而被ForeignKey关联的字段就是一对多字段了。

作用和方法

prefetch_related()和select_related()的设计目的很相似,都是为了减少SQL查询的数量,但是实现的方式不一样。后者是通过JOIN语句,在SQL查询内解决问题。但是对于多对多关系,使用SQL语句解决就显得有些不太明智,因为JOIN得到的表将会很长,会导致SQL语句运行时间的增加和内存占用的增加。若有n个对象,每个对象的多对多字段对应Mi条,就会生成Σ(n)Mi 行的结果表。prefetch_related()的解决方法是,分别查询每个表,然后用Python处理他们之间的关系。继续以上边的例子进行说明,如果我们要获得张三所有去过的城市,使用prefetch_related()应该是这么做:

>>> zhangs = Person.objects.prefetch_related('visitation').get(firstname=u"张",lastname=u"三")
>>> for city in zhangs.visitation.all() :
...   print city
...

上述代码触发的SQL查询如下:

SELECT `QSOptimize_person`.`id`, `QSOptimize_person`.`firstname`,
`QSOptimize_person`.`lastname`, `QSOptimize_person`.`hometown_id`, `QSOptimize_person`.`living_id`
FROM `QSOptimize_person`
WHERE (`QSOptimize_person`.`lastname` = '三'  AND `QSOptimize_person`.`firstname` = '张');
 
SELECT (`QSOptimize_person_visitation`.`person_id`) AS `_prefetch_related_val`, `QSOptimize_city`.`id`,
`QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`
FROM `QSOptimize_city`
INNER JOIN `QSOptimize_person_visitation` ON (`QSOptimize_city`.`id` = `QSOptimize_person_visitation`.`city_id`)
WHERE `QSOptimize_person_visitation`.`person_id` IN (1);

第一条SQL查询仅仅是获取张三的Person对象,第二条比较关键,它选取关系表QSOptimize_person_visitationperson_id为张三的行,然后和city表内联(INNER JOIN 也叫等值连接)得到结果表。

+----+-----------+----------+-------------+-----------+
| id | firstname | lastname | hometown_id | living_id |
+----+-----------+----------+-------------+-----------+
|  1 | 张        | 三       |           3 |         1 |
+----+-----------+----------+-------------+-----------+
1 row in set (0.00 sec)
 
+-----------------------+----+-----------+-------------+
| _prefetch_related_val | id | name      | province_id |
+-----------------------+----+-----------+-------------+
|                     1 |  1 | 武汉市    |           1 |
|                     1 |  2 | 广州市    |           2 |
|                     1 |  3 | 十堰市    |           1 |
+-----------------------+----+-----------+-------------+
3 rows in set (0.00 sec)

显然张三武汉、广州、十堰都去过。

又或者,我们要获得湖北的所有城市名,可以这样:

>>> hb = Province.objects.prefetch_related('city_set').get(name__iexact=u"湖北省")
>>> for city in hb.city_set.all():
...   city.name
...

触发的SQL查询:

SELECT `QSOptimize_province`.`id`, `QSOptimize_province`.`name` 
FROM `QSOptimize_province` 
WHERE `QSOptimize_province`.`name` LIKE '湖北省' ;

SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`, `QSOptimize_city`.`province_id` 
FROM `QSOptimize_city` 
WHERE `QSOptimize_city`.`province_id` IN (1);

得到的表:

+----+-----------+
| id | name      |
+----+-----------+
|  1 | 湖北省    |
+----+-----------+
1 row in set (0.00 sec)

+----+-----------+-------------+
| id | name      | province_id |
+----+-----------+-------------+
|  1 | 武汉市    |           1 |
|  3 | 十堰市    |           1 |
+----+-----------+-------------+
2 rows in set (0.00 sec)

我们可以看见,prefetch使用的是 IN 语句实现的。这样,在QuerySet中的对象数量过多的时候,根据数据库特性的不同有可能造成性能问题。

使用方法

*lookups 参数

prefetch_related()在Django < 1.7 只有这一种用法。和select_related()一样,prefetch_related()也支持深度查询,例如要获得所有姓张的人去过的省:

>>> zhangs = Person.objects.prefetch_related('visitation__province').filter(firstname__iexact=u'张')
>>> for i in zhangs:
...   for city in i.visitation.all():
...     print city.province
...

触发的SQL:

SELECT `QSOptimize_person`.`id`, `QSOptimize_person`.`firstname`, 
`QSOptimize_person`.`lastname`, `QSOptimize_person`.`hometown_id`, `QSOptimize_person`.`living_id` 
FROM `QSOptimize_person` 
WHERE `QSOptimize_person`.`firstname` LIKE '张' ;

SELECT (`QSOptimize_person_visitation`.`person_id`) AS `_prefetch_related_val`, `QSOptimize_city`.`id`,
`QSOptimize_city`.`name`, `QSOptimize_city`.`province_id` FROM `QSOptimize_city` 
INNER JOIN `QSOptimize_person_visitation` ON (`QSOptimize_city`.`id` = `QSOptimize_person_visitation`.`city_id`)
WHERE `QSOptimize_person_visitation`.`person_id` IN (1, 4);

SELECT `QSOptimize_province`.`id`, `QSOptimize_province`.`name` 
FROM `QSOptimize_province` 
WHERE `QSOptimize_province`.`id` IN (1, 2);

获得的结果:

+----+-----------+----------+-------------+-----------+
| id | firstname | lastname | hometown_id | living_id |
+----+-----------+----------+-------------+-----------+
|  1 | 张        | 三       |           3 |         1 |
|  4 | 张        | 六       |           2 |         2 |
+----+-----------+----------+-------------+-----------+
2 rows in set (0.00 sec)

+-----------------------+----+-----------+-------------+
| _prefetch_related_val | id | name      | province_id |
+-----------------------+----+-----------+-------------+
|                     1 |  1 | 武汉市    |           1 |
|                     1 |  2 | 广州市    |           2 |
|                     4 |  2 | 广州市    |           2 |
|                     1 |  3 | 十堰市    |           1 |
+-----------------------+----+-----------+-------------+
4 rows in set (0.00 sec)

+----+-----------+
| id | name      |
+----+-----------+
|  1 | 湖北省    |
|  2 | 广东省    |
+----+-----------+
2 rows in set (0.00 sec)

值得一提的是,链式prefetch_related会将这些查询添加起来,就像1.7中的select_related那样。 

要注意的是,在使用QuerySet的时候,一旦在链式操作中改变了数据库请求,之前用prefetch_related缓存的数据将会被忽略掉。这会导致Django重新请求数据库来获得相应的数据,从而造成性能问题。这里提到的改变数据库请求指各种filter()、exclude()等等最终会改变SQL代码的操作。而all()并不会改变最终的数据库请求,因此是不会导致重新请求数据库的。

举个例子,要获取所有人访问过的城市中带有“市”字的城市,这样做会导致大量的SQL查询:

plist = Person.objects.prefetch_related('visitation')
[p.visitation.filter(name__icontains=u"市") for p in plist]

因为数据库中有4人,导致了2+4次SQL查询:

SELECT `QSOptimize_person`.`id`, `QSOptimize_person`.`firstname`, `QSOptimize_person`.`lastname`, 
`QSOptimize_person`.`hometown_id`, `QSOptimize_person`.`living_id` 
FROM `QSOptimize_person`;

SELECT (`QSOptimize_person_visitation`.`person_id`) AS `_prefetch_related_val`, `QSOptimize_city`.`id`,
`QSOptimize_city`.`name`, `QSOptimize_city`.`province_id` 
FROM `QSOptimize_city` 
INNER JOIN `QSOptimize_person_visitation` ON (`QSOptimize_city`.`id` = `QSOptimize_person_visitation`.`city_id`)
WHERE `QSOptimize_person_visitation`.`person_id` IN (1, 2, 3, 4);

SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`, `QSOptimize_city`.`province_id` 
FROM `QSOptimize_city` 
INNER JOIN `QSOptimize_person_visitation` ON (`QSOptimize_city`.`id` = `QSOptimize_person_visitation`.`city_id`) 
WHERE(`QSOptimize_person_visitation`.`person_id` = 1  AND `QSOptimize_city`.`name` LIKE '%市%' );

SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`, `QSOptimize_city`.`province_id` 
FROM `QSOptimize_city` 
INNER JOIN `QSOptimize_person_visitation` ON (`QSOptimize_city`.`id` = `QSOptimize_person_visitation`.`city_id`) 
WHERE (`QSOptimize_person_visitation`.`person_id` = 2  AND `QSOptimize_city`.`name` LIKE '%市%' ); 

SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`, `QSOptimize_city`.`province_id` 
FROM `QSOptimize_city`
INNER JOIN `QSOptimize_person_visitation` ON (`QSOptimize_city`.`id` = `QSOptimize_person_visitation`.`city_id`) 
WHERE (`QSOptimize_person_visitation`.`person_id` = 3  AND `QSOptimize_city`.`name` LIKE '%市%' );

SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`, `QSOptimize_city`.`province_id` 
FROM `QSOptimize_city` 
INNER JOIN `QSOptimize_person_visitation` ON (`QSOptimize_city`.`id` = `QSOptimize_person_visitation`.`city_id`) 
WHERE (`QSOptimize_person_visitation`.`person_id` = 4  AND `QSOptimize_city`.`name` LIKE '%市%' );

详细分析一下这些请求事件。

众所周知,QuerySet是lazy的,要用的时候才会去访问数据库。运行到第二行Python代码时,for循环将plist看做iterator,这会触发数据库查询。最初的两次SQL查询就是prefetch_related导致的。

虽然已经查询结果中包含所有所需的city的信息,但因为在循环体中对Person.visitation进行了filter操作,这显然改变了数据库请求。因此这些操作会忽略掉之前缓存到的数据,重新进行SQL查询。

 

但是如果有这样的需求了应该怎么办呢?在Django >= 1.7,可以通过下一节的Prefetch对象来实现,如果你的环境是Django < 1.7,可以在Python中完成这部分操作。

plist = Person.objects.prefetch_related('visitation')
[[city for city in p.visitation.all() if u"市" in city.name] for p in plist]
Prefetch 对象

在Django >= 1.7,可以用Prefetch对象来控制prefetch_related函数的行为。

注:由于我没有安装1.7版本的Django环境,本节内容是参考Django文档写的,没有进行实际的测试。

 Prefetch对象的特征:

  1. 一个Prefetch对象只能指定一项prefetch操作。
  2. Prefetch对象对字段指定的方式和prefetch_related中的参数相同,都是通过双下划线连接的字段名完成的。
  3. 可以通过 queryset 参数手动指定prefetch使用的QuerySet。
  4. 可以通过 to_attr 参数指定prefetch到的属性名。
  5. Prefetch对象和字符串形式指定的lookups参数可以混用。

继续上面的例子,获取所有人访问过的城市中带有“武”字和“州”的城市:

wus = City.objects.filter(name__icontains = u"武")
zhous = City.objects.filter(name__icontains = u"州")
plist = Person.objects.prefetch_related(
    Prefetch('visitation', queryset = wus, to_attr = "wu_city"),
    Prefetch('visitation', queryset = zhous, to_attr = "zhou_city"),)
[p.wu_city for p in plist]
[p.zhou_city for p in plist]

注:这段代码没有在实际环境中测试过,若有不正确的地方请指正。

顺带一提,Prefetch对象和字符串参数可以混用。

None

可以通过传入一个None来清空之前的prefetch_related。就像这样:

>>> prefetch_cleared_qset = qset.prefetch_related(None)

小结

  1. prefetch_related主要针一对多和多对多关系进行优化。
  2. prefetch_related通过分别获取各个表的内容,然后用Python处理他们之间的关系来进行优化。
  3. 可以通过可变长参数指定需要select_related的字段名。指定方式和特征与select_related是相同的。
  4. 在Django >= 1.7可以通过Prefetch对象来实现复杂查询,但低版本的Django好像只能自己实现。
  5. 作为prefetch_related的参数,Prefetch对象和字符串可以混用。
  6. prefetch_related的链式调用会将对应的prefetch添加进去,而非替换,似乎没有基于不同版本上区别。
  7. 可以通过传入None来清空之前的prefetch_related。

4. 一些实例

选择哪个函数

如果我们想要获得所有家乡是湖北的人,最无脑的做法是先获得湖北省,再获得湖北的所有城市,最后获得故乡是这个城市的人。就像这样:

>>> hb = Province.objects.get(name__iexact=u"湖北省")
>>> people = []
>>> for city in hb.city_set.all():
...   people.extend(city.birth.all())
...

显然这不是一个明智的选择,因为这样做会导致1+(湖北省城市数)次SQL查询。反正是个反例,导致的查询和获得掉结果就不列出来了。

prefetch_related() 或许是一个好的解决方法,让我们来看看。

>>> hb = Province.objects.prefetch_related("city_set__birth").objects.get(name__iexact=u"湖北省")
>>> people = []
>>> for city in hb.city_set.all():
...   people.extend(city.birth.all())
...

因为是一个深度为2的prefetch,所以会导致3次SQL查询:

SELECT `QSOptimize_province`.`id`, `QSOptimize_province`.`name`
FROM `QSOptimize_province`
WHERE `QSOptimize_province`.`name` LIKE '湖北省' ;
 
SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`
FROM `QSOptimize_city`
WHERE `QSOptimize_city`.`province_id` IN (1);
 
SELECT `QSOptimize_person`.`id`, `QSOptimize_person`.`firstname`, `QSOptimize_person`.`lastname`,
`QSOptimize_person`.`hometown_id`, `QSOptimize_person`.`living_id`
FROM `QSOptimize_person`
WHERE `QSOptimize_person`.`hometown_id` IN (1, 3);

嗯…看上去不错,但是3次查询么?倒过来查询可能会更简单?

>>> people = list(Person.objects.select_related("hometown__province").filter(hometown__province__name__iexact=u"湖北省"))
SELECT `QSOptimize_person`.`id`, `QSOptimize_person`.`firstname`, `QSOptimize_person`.`lastname`,
`QSOptimize_person`.`hometown_id`, `QSOptimize_person`.`living_id`, `QSOptimize_city`.`id`,
`QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`, `QSOptimize_province`.`id`, `QSOptimize_province`.`name`
FROM `QSOptimize_person`
INNER JOIN `QSOptimize_city` ON (`QSOptimize_person`.`hometown_id` = `QSOptimize_city`.`id`)
INNER JOIN `QSOptimize_province` ON (`QSOptimize_city`.`province_id` = `QSOptimize_province`.`id`)
WHERE `QSOptimize_province`.`name` LIKE '湖北省';
+----+-----------+----------+-------------+-----------+----+--------+-------------+----+--------+
| id | firstname | lastname | hometown_id | living_id | id | name   | province_id | id | name   |
+----+-----------+----------+-------------+-----------+----+--------+-------------+----+--------+
|  1 | 张        | 三       |           3 |         1 |  3 | 十堰市 |           1 |  1 | 湖北省 |
|  2 | 李        | 四       |           1 |         3 |  1 | 武汉市 |           1 |  1 | 湖北省 |
|  3 | 王        | 麻子     |           3 |         2 |  3 | 十堰市 |           1 |  1 | 湖北省 |
+----+-----------+----------+-------------+-----------+----+--------+-------------+----+--------+
3 rows in set (0.00 sec)

完全没问题。不仅SQL查询的数量减少了,python程序上也精简了。

select_related()的效率要高于prefetch_related()。因此,最好在能用select_related()的地方尽量使用它,也就是说,对于ForeignKey字段,避免使用prefetch_related()。

联用

对于同一个QuerySet,你可以同时使用这两个函数。

在我们一直使用的例子上加一个model:Order (订单)

class Order(models.Model):
    customer   = models.ForeignKey(Person)
    orderinfo  = models.CharField(max_length=50)
    time       = models.DateTimeField(auto_now_add = True)
    def __unicode__(self):
        return self.orderinfo

如果我们拿到了一个订单的id 我们要知道这个订单的客户去过的省份。因为有ManyToManyField显然必须要用prefetch_related()。如果只用prefetch_related()会怎样呢?

>>> plist = Order.objects.prefetch_related('customer__visitation__province').get(id=1)
>>> for city in plist.customer.visitation.all():
...   print city.province.name
...

显然,关系到了4个表:Order、Person、City、Province,根据prefetch_related()的特性就得有4次SQL查询

SELECT `QSOptimize_order`.`id`, `QSOptimize_order`.`customer_id`, `QSOptimize_order`.`orderinfo`, `QSOptimize_order`.`time`
FROM `QSOptimize_order`
WHERE `QSOptimize_order`.`id` = 1 ;
 
SELECT `QSOptimize_person`.`id`, `QSOptimize_person`.`firstname`, `QSOptimize_person`.`lastname`, `QSOptimize_person`.`hometown_id`, `QSOptimize_person`.`living_id`
FROM `QSOptimize_person`
WHERE `QSOptimize_person`.`id` IN (1);
 
SELECT (`QSOptimize_person_visitation`.`person_id`) AS `_prefetch_related_val`, `QSOptimize_city`.`id`,
`QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`
FROM `QSOptimize_city`
INNER JOIN `QSOptimize_person_visitation` ON (`QSOptimize_city`.`id` = `QSOptimize_person_visitation`.`city_id`)
WHERE `QSOptimize_person_visitation`.`person_id` IN (1);
 
SELECT `QSOptimize_province`.`id`, `QSOptimize_province`.`name`
FROM `QSOptimize_province`
WHERE `QSOptimize_province`.`id` IN (1, 2);
+----+-------------+---------------+---------------------+
| id | customer_id | orderinfo     | time                |
+----+-------------+---------------+---------------------+
|  1 |           1 | Info of Order | 2014-08-10 17:05:48 |
+----+-------------+---------------+---------------------+
1 row in set (0.00 sec)
 
+----+-----------+----------+-------------+-----------+
| id | firstname | lastname | hometown_id | living_id |
+----+-----------+----------+-------------+-----------+
|  1 | 张        | 三       |           3 |         1 |
+----+-----------+----------+-------------+-----------+
1 row in set (0.00 sec)
 
+-----------------------+----+--------+-------------+
| _prefetch_related_val | id | name   | province_id |
+-----------------------+----+--------+-------------+
|                     1 |  1 | 武汉市 |           1 |
|                     1 |  2 | 广州市 |           2 |
|                     1 |  3 | 十堰市 |           1 |
+-----------------------+----+--------+-------------+
3 rows in set (0.00 sec)
 
+----+--------+
| id | name   |
+----+--------+
|  1 | 湖北省 |
|  2 | 广东省 |
+----+--------+
2 rows in set (0.00 sec)

更好的办法是先调用一次select_related()再调用prefetch_related(),最后再select_related()后面的表

>>> plist = Order.objects.select_related('customer').prefetch_related('customer__visitation__province').get(id=1)
>>> for city in plist.customer.visitation.all():
...   print city.province.name
...

这样只会有3次SQL查询,Django会先做select_related,之后prefetch_related的时候会利用之前缓存的数据,从而避免了1次额外的SQL查询:

SELECT `QSOptimize_order`.`id`, `QSOptimize_order`.`customer_id`, `QSOptimize_order`.`orderinfo`, 
`QSOptimize_order`.`time`, `QSOptimize_person`.`id`, `QSOptimize_person`.`firstname`, 
`QSOptimize_person`.`lastname`, `QSOptimize_person`.`hometown_id`, `QSOptimize_person`.`living_id` 
FROM `QSOptimize_order` 
INNER JOIN `QSOptimize_person` ON (`QSOptimize_order`.`customer_id` = `QSOptimize_person`.`id`) 
WHERE `QSOptimize_order`.`id` = 1 ;
 
SELECT (`QSOptimize_person_visitation`.`person_id`) AS `_prefetch_related_val`, `QSOptimize_city`.`id`, 
`QSOptimize_city`.`name`, `QSOptimize_city`.`province_id` 
FROM `QSOptimize_city` 
INNER JOIN `QSOptimize_person_visitation` ON (`QSOptimize_city`.`id` = `QSOptimize_person_visitation`.`city_id`) 
WHERE `QSOptimize_person_visitation`.`person_id` IN (1);
 
SELECT `QSOptimize_province`.`id`, `QSOptimize_province`.`name` 
FROM `QSOptimize_province` 
WHERE `QSOptimize_province`.`id` IN (1, 2);
+----+-------------+---------------+---------------------+----+-----------+----------+-------------+-----------+
| id | customer_id | orderinfo     | time                | id | firstname | lastname | hometown_id | living_id |
+----+-------------+---------------+---------------------+----+-----------+----------+-------------+-----------+
|  1 |           1 | Info of Order | 2014-08-10 17:05:48 |  1 | 张        | 三       |           3 |         1 |
+----+-------------+---------------+---------------------+----+-----------+----------+-------------+-----------+
1 row in set (0.00 sec)
 
+-----------------------+----+--------+-------------+
| _prefetch_related_val | id | name   | province_id |
+-----------------------+----+--------+-------------+
|                     1 |  1 | 武汉市 |           1 |
|                     1 |  2 | 广州市 |           2 |
|                     1 |  3 | 十堰市 |           1 |
+-----------------------+----+--------+-------------+
3 rows in set (0.00 sec)
 
+----+--------+
| id | name   |
+----+--------+
|  1 | 湖北省 |
|  2 | 广东省 |
+----+--------+
2 rows in set (0.00 sec)

小结

  1. 因为select_related()总是在单次SQL查询中解决问题,而prefetch_related()会对每个相关表进行SQL查询,因此select_related()的效率通常比后者高。
  2. 鉴于第一条,尽可能的用select_related()解决问题。只有在select_related()不能解决问题的时候再去想prefetch_related()。
  3. 你可以在一个QuerySet中同时使用select_related()和prefetch_related(),从而减少SQL查询的次数。
  4. 只有prefetch_related()之前的select_related()是有效的,之后的将会被无视掉。

转自

查看原文

赞 1 收藏 0 评论 0

rayzz 关注了用户 · 2018-08-25

布客飞龙 @wizardforcel

欢迎来星球做客:t.zsxq.com/Jq3vZZB

请关注我们的公众号“ApacheCN”,回复“教程/路线/比赛/报告/技术书/轻小说/漫画/新知”来获取更多资源。

关注 788

rayzz 关注了用户 · 2018-08-25

Andy @andy_5b2f636406f35

关注 2

rayzz 关注了用户 · 2018-08-25

sjtuzyl @sjtuzyl

关注 3

rayzz 赞了文章 · 2018-08-25

Git命令简单总结

集中式vs分布式

svn集中式:版本库是集中存放在中央服务器的,需要联网才能工作

git 分布式:每个人的电脑上都是一个完整的版本库

和集中式版本控制系统相比,分布式版本控制系统的安全性要高很多,因为每个人电脑里都有完整的版本库,某一个人的电脑坏掉了不要紧,随便从其他人那里复制一个就可以了。而集中式版本控制系统的中央服务器要是出了问题,所有人都没法干活了。

基本命令

初始化仓库:git init

把文件添加到仓库:git add a.txt 添加到暂存区(state)

把文件提交到仓库:git commit -m '注释信息'

仓库状态:git status

查看修改内容:git diff

显示最近到最有远的提交日志:git loggit log --pretty=oneline

版本回退

  • git reset --hard HEAD^ 回退到上一版本
  • git reset --hard HEAD^ 回退到上上版本
  • git reset --hard HEAD~100 回退到上100个版本
  • git reset --hard 具体版本号 回退到具体版本号

记录每一次命令 : git reflog

git checkout -- readme.txt

命令git checkout -- readme.txt意思就是,把readme.txt文件在工作区的修改全部撤销,这里有两种情况:

一种是readme.txt自修改后还没有被放到暂存区,现在,撤销修改就回到和版本库一模一样的状态;

一种是readme.txt已经添加到暂存区后,又作了修改,现在,撤销修改就回到添加到暂存区后的状态。

总之,就是让这个文件回到最近一次git commitgit add时的状态。

删除

删除文件后,Git知道你删除了文件,因此,工作区和版本库就不一致了,git status命令会立刻告诉你哪些文件被删除了:

现在你有两个选择,一是确实要从版本库中删除该文件,那就用命令git rm filename 删掉,并且git commit

另一种情况是删错了,因为版本库里还有呢,所以可以很轻松地把误删的文件恢复到最新版本: git checkout -- filename

远程仓库

git config -- global user.name 'GitHub用户名 '

git config --global user.email '注册邮箱'

ssh-keygen -t rsa -C 'GitHub的注册邮箱'

git remote add origin 远程仓库地址 :关联远程仓库

git remote rm origin :删除关联

git push origin master : 推送 (第一次 加上-u 就会一直关联这个地址,就不需要再写origin master)

git pull origin master --allow-unrelated-histories:如果本地仓库和远程库有冲突,比如GitHub上有markdown文件,则加上 --allow-unrelatered..

git clone git@github.com:rottengeek/test.git:克隆远程库到本地

分支

git branch 分支名:创建分支

git checkout 分支名:切换分支

git checkout -b 分支名 :创建与切换同时进行

git branch :列出所有分支

git merge dev :把dev分支的工作成果合并到master分支上

git branch -d 分支名 : 删除分支

分支策略

在实际开发中,我们应该按照几个基本原则进行分支管理:

首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;

那在哪干活呢?干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本;

你和你的小伙伴们每个人都在dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并就可以了。

所以,团队合作的分支看起来就像这样:

clipboard.png

多人协作

clipboard.png

查看原文

赞 27 收藏 20 评论 0

rayzz 发布了文章 · 2018-08-24

Django-分页

Django-分页

django内置的分页

Paginator对象

clipboard.png

Page对象

clipboard.png

示例

from django.core.paginator import Paginator
def students(request, num):
    allStudents = Student.objects.all()
    #分页 每页6条数据
    paginator = Paginator(allStudents, 6)
    print(paginator.count, paginator.num_pages, paginator.page_range)
    pageStus = paginator.page(num)
    return render(request, "students.html", {"stus":pageStus})
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>学生信息</title>
</head>
<body>
<h1>学生信息</h1>
<ul>
    {% for stu in stus %}
        <li>{{ stu.name }}--{{ stu.grade }}--{{ stu.age }}</li>
    {% endfor %}
</ul>
{% if stus.has_previous %}
    <a href="/students/{{ stus.previous_page_number }}">上一页</a>
{% endif %}


{% for index in stus.paginator.page_range %}
    {% if index == stus.number %}
        {{ index }}
    {% else %}
    <a href="/students/{{ index }}/">{{ index }}</a>
    {% endif %}
{% endfor %}
{% if stus.has_next %}
<a href="/students/{{ stus.next_page_number }}">下一页</a>
{% endif %}
</body>
</html>

django-pure-pagination 分页

地址

(1)安装

pip install django-pure-pagination

(2)settings里面添加

INSTALLED_APPS = (
    ...
    'pure_pagination',
)

(3)views中使用方法

class OrgView(View):
    '''课程机构'''

    def get(self, request):
        # 所有课程机构
        all_orgs = CourseOrg.objects.all()
        # 有多少家机构
        org_nums = all_orgs.count()
        # 所有城市
        all_citys = CityDict.objects.all()
        # 对课程机构进行分页
        # 尝试获取前台get请求传递过来的page参数
        # 如果是不合法的配置参数默认返回第一页
        try:
            page = request.GET.get('page', 1)
        except PageNotAnInteger:
            page = 1
        # 这里指从allorg中取五个出来,每页显示5个
        p = Paginator(all_orgs, 5, request=request)
        orgs = p.page(page)

        return render(request, "org-list.html", {
            "all_orgs": orgs,
            "all_citys": all_citys,
            "org_nums": org_nums,
        })

(4)模板修改

#{%for course_org in all_orgs%}
{%for course_org in all_orgs.object_list%}
...
{%endfor%}

(5)分页功能

<div class="pageturn">
    <ul class="pagelist">
        {% if all_orgs.has_previous %}
            <li class="long"><a href="?{{ all_orgs.previous_page_number.querystring }}">上一页</a></li>
        {% endif %}

        {% for page in all_orgs.pages %}
            {% if page %}
                {% ifequal page all_orgs.number %}
                    <li class="active"><a href="?{{ page.querystring }}">{{ page }}</a></li>
                {% else %}
                    <li><a href="?{{ page.querystring }}" class="page">{{ page }}</a></li>
                {% endifequal %}
            {% else %}
                <li class="none"><a href="">...</a></li>
            {% endif %}
        {% endfor %}
        {% if all_orgs.has_next %}
            <li class="long"><a href="?{{ all_orgs.next_page_number.querystring }}">下一页</a></li>
        {% endif %}
    </ul>
</div>
查看原文

赞 3 收藏 2 评论 0

rayzz 发布了文章 · 2018-08-23

Django解决ajax跨域访问问题

Django解决ajax跨域访问问题

这篇文章主要给大家介绍了关于Django跨域请求问题解决的相关资料,文中介绍的实现方法包括:使用django-cors-headers全局控制、使用JsonP,只能用于Get方法以及在views.py里设置响应头,只能控制单个接口,需要的朋友可以参考下。

使用Django在服务器端写了一个API,返回一个JSON数据。使用Ajax调用该API:

但是,Chrome浏览器提示错误:

 No 'Access-Control-Allow-Origin' header is present on the requested resource. 

这是由于CORS导致的。

什么是CORS?

CORS(跨域资源共享,Cross-Origin Resource Sharing)是一种跨域访问的机制,可以让Ajax实现跨域访问。
其实,在服务器的response header中,加入“Access-Control-Allow-Origin: *”即可支持CORS,非常的简单,apache/nginx等怎么配置,见参考文档。
举个例子:

  • API部署在DomainA上;
  • Ajax文件部署在DomainB上,Ajax文件会向API发送请求,返回数据;
  • 用户通过DomainC访问DomainB的Ajax文件,请求数据

几种方法:

1.使用JSONP使用Ajax获取json数据时,存在跨域的限制。不过,在Web页面上调用js的script脚本文件时却不受跨域的影响,JSONP就是利用这个来实现跨域的传输。因此,我们需要将Ajax调用中的dataType从JSON改为JSONP(相应的API也需要支持JSONP)格式。
JSONP只能用于GET请求。

2.直接修改Django中的views.py文件修改views.py中对应API的实现函数,允许其他域通过Ajax请求数据:

 def myview(_request):  
    response = HttpResponse(json.dumps({"key": "value", "key2": "value"}))
    response["Access-Control-Allow-Origin"] = "*"  
    response["Access-Control-Allow-Methods"] = "POST, GET, OPTIONS"  
    response["Access-Control-Max-Age"] = "1000"  
    response["Access-Control-Allow-Headers"] = "*"  
    return response 

3.安装django-cors-headers这里还有一各发现!在Django中,有人开发了CORS-header的middleware,只在settings.py中做一些简单的配置即可,见:https://github.com/ottoyiu/dj...,开启CORS,没有跨域烦恼,真爽!~

django-cors-headers

首先安装

然后在settings.py里配置一番就可以

 INSTALLED_APPS = [  
     ...  
     'corsheaders', 
     ...  ]    
 MIDDLEWARE_CLASSES = ( 
     ...  
     #尽可能靠前,必须在CsrfViewMiddleware、CommonMiddleware之前。
     #我们直接放在第一个位置就好了
     'django.middleware.csrf.CsrfViewMiddleware',
     'corsheaders.middleware.CorsMiddleware',  
     'django.middleware.common.CommonMiddleware', 
     ... 
 ) 
添加参数为true
CORS_ORIGIN_ALLOW_ALL = True
#下面这些可以不用设置
#跨域增加忽略 
CORS_ALLOW_CREDENTIALS = True
#设置白名单
CORS_ORIGIN_WHITELIST = (  '*')   
CORS_ALLOW_METHODS = (  'DELETE',  'GET',  'OPTIONS',  'PATCH',  'POST',  'PUT',  'VIEW', )   
CORS_ALLOW_HEADERS = (  
'XMLHttpRequest',  
'X_FILENAME',  
'accept-encoding', 
'authorization',  
'content-type',  
'dnt',
'origin',  
'user-agent',  
'x-csrftoken',  
'x-requested-with',  
'Pragma', 
) 
查看原文

赞 2 收藏 1 评论 0

rayzz 发布了文章 · 2018-08-22

Django—限制用户访问频率

django中间件

一、定义限制访问频率的中间件

  • common/middleware.py
import time

from django.utils.deprecation import MiddlewareMixin

MAX_REQUEST_PER_SECOND=2 #每秒访问次数

class RequestBlockingMiddleware(MiddlewareMixin):

    def process_request(self,request):
        now=time.time()
        request_queue = request.session.get('request_queue',[])
        if len(request_queue) < MAX_REQUEST_PER_SECOND:
            request_queue.append(now)
            request.session['request_queue']=request_queue
        else:
            time0=request_queue[0]
            if (now-time0)<1:
                time.sleep(5)

            request_queue.append(time.time())
            request.session['request_queue']=request_queue[1:]

二、将中间件加入配置文件

  • setting.py
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'common.middleware.RequestBlockingMiddleware', #在sessions之后,auth之前
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]12345678910

drf的throttle设置api的访问速率

作用:防止爬虫无节制的爬取数据 减少服务器的压力。
drf的自带功能

官方文档:http://www.django-rest-framew...

一、throttle配置到setting中

'DEFAULT_THROTTLE_CLASSES': (
        'rest_framework.throttling.AnonRateThrottle',
        'rest_framework.throttling.UserRateThrottle'
    ),
    'DEFAULT_THROTTLE_RATES': {
        'anon': '100/day',
        'user': '1000/day'
    }
限速规则与限速的类。未登录情况下限速,通过ip地址。登录情况下通过session或token来判断。
The rate descriptions used in DEFAULT_THROTTLE_RATES may include second, minute, hour or day as the throttle period.

二、设置到我们的接口

from rest_framework.throttling import UserRateThrottle,AnonRateThrottle

throttle_classes = (UserRateThrottle, AnonRateThrottle)

在throttling的源码中

parse_rate:进行解析我们的规则
allow_request:中使用django的cache进行缓存。将每个ip的访问次数设置到缓存中
get_ident:会通过request.meta.get('remote_addr')取出ip
已登录用户的限制是通过 request.user.pk
查看原文

赞 4 收藏 4 评论 0

rayzz 发布了文章 · 2018-08-22

uwsgi+nginx项目部署

部署Django项目

Django+uWSGI+nginx 部署

  • django 一个pyhton的开源web框架。
  • uWSGI 一个基于自有的uwsgi协议、WSGI协议和http服务协议的web网关
  • nginx 常用的代理服务器

    WSGI:一种实现python解析的通用接口标准/协议,是一种通用的接口标准或者接口协议,实现了python web程序与服务器之间交互的通用性。 
    利用它,web.py或bottle或者django等等的python web开发框架,就可以轻松地部署在不同的web server上了;

    uwsgi:同WSGI一样是一种通信协议 
    uwsgi协议是一个uWSGI服务器自有的协议,它用于定义传输信息的类型,它与WSGI相比是两样东西。

    uWSGI :一种python web server或称为Server/Gateway 
    uWSGI类似tornadoweb或者flup,是一种python web server,uWSGI是实现了uwsgi和WSGI两种协议的Web服务器,负责响应python 的web请求。 
    因为apache、nginx等,它们自己都没有解析动态语言如php的功能,而是分派给其他模块来做,比如apache就可以说内置了php模块,让人感觉好像apache就支持php一样。 
    uWSGI实现了wsgi协议、uwsgi协议、http等协议。 Nginx中HttpUwsgiModule的作用是与uWSGI服务器进行交换。

项目流程

首先客户端请求服务资源,
nginx作为直接对外的服务接口,接收到客户端发送过来的http请求,会解包、分析,
如果是静态文件请求就根据nginx配置的静态文件目录,返回请求的资源,
如果是动态的请求,nginx就通过配置文件,将请求传递给uWSGI;uWSGI 将接收到的包进行处理,并转发给wsgi,
wsgi根据请求调用django工程的某个文件或函数,处理完后django将返回值交给wsgi,
wsgi将返回值进行打包,转发给uWSGI,
uWSGI接收后转发给nginx,nginx最终将返回值返回给客户端(如浏览器)。
*注:不同的组件之间传递信息涉及到数据格式和协议的转换

作用: 

  1. 第一级的nginx并不是必须的,uwsgi完全可以完成整个的和浏览器交互的流程; 
  2. 在nginx上加上安全性或其他的限制,可以达到保护程序的作用; 
  3. uWSGI本身是内网接口,开启多个work和processes可能也不够用,而nginx可以代理多台uWSGI完成uWSGI的负载均衡; 
  4. django在debug=False下对静态文件的处理能力不是很好,而用nginx来处理更加高效。

安装与配置

  1. 创建项目运行的虚拟环境

    virtualenv env --python=python3.6
    pip install -r requirements.txt  #安装django运行环境
  2. 运行开发服务器测试

                    cd project # 进入项目 project 目录
                    python manage.py runserver
                运行开发服务器测试,确保开发服务器下能正常打开网站。
    
  3. 安装uWSGI

                    # 在普通用户下安装
                    sudo apt install libpython3.6-dev
                    # 虚拟环境中
                    pip install uwsgi
  4. 测试uWSGI: 新建文件test.py,写入以下内容

                    def application(env, start_response):
                        start_response('200 OK', [('Content-Type','text/html')])
                        return "Hello World"
  5. 运行

                    # 0.0.0.0可以省略 
                    sudo uwsgi --http 0.0.0.0:8000 --wsgi-file test.py --processes 4 --threads 3
          如果提示端口已经被占用,这时可以把相关的进程 kill 掉。
    
                probably another instance of uWSGI is running on the same address (:8002).
                bind(): Address already in use [core/socket.c line 764]
          按照端口进行查询进程
    
              lsof -i :8002
          可以查出:
    
               COMMAND  PID USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
                uwsgi   2208   tu    4u  IPv4 0x53492abadb5c9659      0t0  TCP *:teradataordbms (LISTEN)
                uwsgi   2209   tu    4u  IPv4 0x53492abadb5c9659      0t0  TCP *:teradataordbms (LISTEN)
          这时根据 PID 可以用下面的命令 kill 掉相关程序:
    
sudo kill -9 2208 2209
  1. 运行django项目

          # --chdir 项目目录 --home 虚拟环境目录 project.wsgi 指的是 project/wsgi.py 文件
          uwsgi --http :8000 --chdir=/path/to/project  --home=/path/to/env --module project.wsgi
  2. 配置文件运行

                上面这样使用一行命令太长了,我们使用 ini 配置文件来搞定,比如项目在 /home/ray/project 这个位置,在其中新建一个 uwsgi.ini 全路径为 /home/ray/project/uwsgi.ini
    
                    [uwsgi]
                    #socket 为上线使用,http为直接作为服务器使用。
                    socket = 127.0.0.1:8080 #ip和端口号可以改
                    http = 127.0.0.1:8000
                    #项目目录
                    chdir=/home/ray/project
                    module=project.wsgi
                    #虚拟环境目录
                    #home = home/ray/MxOnline/mxonlineEnv
                    master = true         
                    processes=4
                    threads=2
                    # 下面的参数不一定要加
                    # pidfile=uwsgi.pid   uwsgi.pid 和uwsgi.log会在启动uwsgi时自动生成在项目目录下。
                    # daemonize=uswgi.log
                    # max-requests=2000    
                    # chmod-socket=664
                    # vacuum=true
    # uwsgi启动
    uwsgi --ini uwsgi.ini
    #uwsgi 停止
    uwsgi --stop uwsgi.pid
  3. 安装nginx,在普通用户下安装。

                    # 安装
                    sudo apt install nginx 
                    #重载
                    sudo /etc/init.d/nginx reload
                    sudo nginx -s reload
                    # 启动
                    sudo /etc/init.d/nginx start
                    # 停止
                    sudo /etc/init.d/nginx stop
                    # 重启
                    sudo /etc/init.d/nginx restart
                    #查看nginx是否启动
                    ps -ef | grep nginx
                root     24956     1  0 19:41 ?        00:00:00 nginx: master process /usr/local/nginx/sbin/nginx
                nobody   24957 24956  0 19:41 ?        00:00:00 nginx: worker process
                root     24959 10533  0 19:41 pts/0    00:00:00 grep --color=auto nginx
  4. 配置 nginx

            ##### sites-enable 和 sites-available
    
     These directories are used to define configurations for your websites. Files are generally created in the "sites-available" directory, and thensymbolically linked to the "sites-enabled" directory when they are ready to go live.
    

    ​ 都是在nginx.conf作修改,因为nginx.conf include指令已经包括了sites-enabled的内容,在site-enabled作修改就相当于在nginx.conf作修改,可维护性高。

    include /etc/nginx/conf.d/*.conf;

    include /etc/nginx/sites-enabled/*;

    ites-available是存放当前的server配置, 在这里修改。

    sites-enabled是激活并使用的server配置(从sites_available的文件创建快捷方式到sites-enabled)

    新建一个网站 test

# 不用sudo没有权限修改
sudo vim /etc/nginx/sites-available/test.conf
 #配置负载均衡
     # upstream ray {
     #    server 127.0.0.1:8000; # for a web port socket
     #}

     server {
         listen 80;
         server_name www.helloray.cn;#域名或者ip地址
         charset utf-8;
         # Django 的static和 media等静态资源交给Nginx处理
         location /static {
             # 路径必须和STATIC_ROOT一样
             alias /var/www/myApp/static/;
         }
          location /media  {
              #项目下的media路径
             alias /var/www/myApp/media/; 
         } 
         location /{
             # 必须和uwsgi.ini中socket一样,配置了upstream可以将uwsgi_pass配置为:http:// +             upstream名称,即“http://ray”.
             uwsgi_pass 127.0.0.1:8080; 
             #uwsgi_pass http://ray; 
             include uwsgi_params;
         }
     }

激活网站:建立软链接

 sudo ln -s /etc/nginx/sites-available/test.conf  /etc/nginx/sites-enabled/test.conf

nginx创建静态文件目录,并更改权限

 sudo mkdir -vp /var/www/myApp/static/
 sudo chmod 777 /var/www/myApp/static/

在项目下setting.py 文件中

 STATIC_URL = 'static'
 STATIC_ROOT = '/var/www/myApp/static/'
 STATICFILES_DIRS = [
   os.path.join(BASE_DIR,'static'),
 ]
 MEDIA_URL = '/media/'
 MEDIA_ROOT = os.path.join(BASE_DIR,'media')

在项目目录下迁移静态文件

 python manage.py collectstatic

Django中settings.py中的五个设置参数的一些故事:

1、MEDIA_ROOT与MEDIA_URL

事实上MEDIA_ROOT和MEDIA_URL代表的是用户上传后的文件一般保存的地方。我的理解是,可变文件的文件夹。

与这两个参数有联系的,是在Django的FileField和ImageField这样的Model类中,有upload_to参数可选。当upload_to设置相关的地址后,如:upload_to="username";文件上传后将自动保存到 os.path.join(MEDIA_ROOT, upload_to)。

而MEDIA_URL,,则代表用户通过URL来访问这个本地地址的URL。如本机http://127.0.0.1/, MEDIA_URL设置为"/site_media/",那么通过 http://127.0.0.1/site_media/***  就可以访问相关的上传图片或者其他资源。

2、STATIC_ROOT与STATIC_URL

STATIC_ROOT和STATIC_URL则是网站中,用于网站显示的静态图片、CSS、JS等文件的保存地址。我的理解是,运行中不会再变文件的文件夹(即不会删除或者新增)

2.1 STATIC_URL

 同MEDIA_URL类似;STATIC_URL为"/static/"时候,通过http://127.0.0.1/static/***就可以访问相关的静态文件了。

2.2 STATIC_ROOT

STATIC_ROOT是一个比较特殊的文件夹。这是区别Django的开发模式和部署模式下最大的地方了。

通常我们在开发模式下,可以在我们所在的project下建立相应的app, 然后每个app下都建立相应的static文件夹。在开发模式下(Debug=True),Django将为我们自动查找这些静态文件(每个app)并在网页上显示出来。然而,在部署模式下,Django认为这些工作交由web服务器来运行会更有效率。

因此,在部署时,我们需要运行一下python manage.py collectstatic 这个命令。这个命令将会把每个app里的static目录下的文件copy到STATIC_ROOT这个文件夹下,这时候如果在部署模式下(Debug=False),网页中相关的,如: http://127.0.0.1/static/*** 的访问,将不会访问Django下各个App中的static,而是STATIC_ROOT中所指定的文件夹。

3、Debug=False后,为何无法访问图片和js等文件了?

其实这个问题,是在于web服务器没有对STATIC_ROOT以及MEDIA_ROOT这两个文件夹进行映射所导致的。

以apache为例,假定:

STATIC_ROOT="/home/user/static/" 

STATIC_URL="/static/"

 MEDIA_ROOT="/home/user/media/"

MEDIA_URL="/media/"

那么可以在apache的配置文件中,增加以下:


<Location "/static/">
Order deny,allow
Allow from all
Satisfy Any
</Location>
Alias /static/     "/home/user/static"
<Location "/media/">
Order deny,allow
Allow from all
Satisfy Any
</Location>
Alias /media/      "/home/user/media/"


4、STATICFILES_DIRS:和TEMPLATE_DIRS的含义差不多,就是除了各个app的static目录以外还需要管理的静态文件,添加到这里的文件会在collectstatic时 copy到STATIC_ROOT中

负载均衡的设置

网站的访问量越来越大,服务器的服务模式也得进行相应的升级,比如分离出数据库服务器、分离出图片作为单独服务,这些是简单的数据的负载均衡,将压力分散到不同的机器上。有时候来自web前端的压力,也能让人十分头痛。怎样将同一个域名的访问分散到两台或更多的机器上呢?这其实就是另一种负载均衡了,nginx自身就可以做到,只需要做个简单的配置就行。

  nginx不单可以作为强大的web服务器,也可以作为一个反向代理服务器,而且nginx还可以按照调度规则实现动态、静态页面的分离,可以按照轮询、ip哈希、URL哈希、权重等多种方式对后端服务器做负载均衡,同时还支持后端服务器的健康检查。

nginx 的 upstream目前支持 4 种方式的分配 

轮询:将请求依次轮询发给每个服务器,如果后端服务器down掉,能自动剔除。

最少链接:将请求发送给持有最少活动链接的服务器。

ip哈希:通过ip的哈希函数结果决定请求发送给哪个服务器。这样每个访客固定访问一个后端服务器,可以解决session的问题。

权重:服务器的权重越高,处理请求的概率越大。用于后端服务器性能不均的情况。

轮询负载均衡

在nginx.conf配置文件中添加如下配置,此配置有三台服务器提供支付服务。

  1. 缺省配置就是轮询策略;
  2. nginx负载均衡支持http和https协议,只需要修改 proxy_pass后面的协议即可;

    1. nginx支持FastCGI, uwsgi, SCGI,memcached的负载均衡,只需将 proxy_pass改为uwsgi_pass, fastcgi_pass, scgi_pass,memcached_pass即可。
  3. 此策略适合服务器配置相当,无状态且短平快的服务使用。
http {
    upstream CashServers {
        server CashServers1.com;
        server CashServers2.com;
        server CashServers3.com;
    }

    server {
        listen 80;

        location / {
            proxy_pass http://CashServers;
        }
    }
}

最少链接负载均衡

  1. 最少链接负载均衡通过least_conn指令定义;
  2. 此负载均衡策略适合请求处理时间长短不一造成服务器过载的情况;
http {
    upstream CashServers {
      least_conn;
        server CashServers1.com;
        server CashServers2.com;
        server CashServers3.com;
    }

    server {
        listen 80;

        location / {
            proxy_pass http://CashServers;
        }
    }
}

ip哈希负载均衡

  1. ip哈希负载均衡使用ip_hash指令定义;
  2. nginx使用请求客户端的ip地址进行哈希计算,确保使用同一个服务器响应请求;
  3. 此策略适合有状态服务,比如session;
http {
    upstream CashServers {
      ip_hash;
        server CashServers1.com;
        server CashServers2.com;
        server CashServers3.com;
    }

    server {
        listen 80;

        location / {
            proxy_pass http://CashServers;
        }
    }
}

 权重负载均衡

  1. 权重负载均衡需要使用weight指令定义;
  2. 权重越高分配到需要处理的请求越多;
  3. 此策略可以与最少链接负载和ip哈希策略结合使用;
  4. 此策略比较适合服务器的硬件配置差别比较大的情况;
http {
    upstream CashServers {      
        server CashServers1.com weight=3;
        server CashServers2.com weight=2;
        server CashServers3.com weight=1;
    }

    server {
        listen 80;
        location / {
            proxy_pass http://CashServers;
        }
    }
}

附录:参数说明

   >----------------附录:uwsgi参数说明----------------
   >
   >- http : 协议类型和端口号
   >- processes : 开启的进程数量
   >- workers : 开启的进程数量,等同于processes(官网的说法是spawn the specified number ofworkers / processes)
   >- chdir : 指定运行目录(chdir to specified directory before apps loading)
   >- wsgi-file : 载入wsgi-file(load .wsgi file)
   >- stats : 在指定的地址上,开启状态服务(enable the stats server on the specified address)
   >- threads : 运行线程。由于GIL的存在,我觉得这个真心没啥用。(run each worker in prethreaded mode with the specified number of threads)
   >- master : 允许主进程存在(enable master process)
   >- daemonize : 使进程在后台运行,并将日志打到指定的日志文件或者udp服务器(daemonize uWSGI)。实际上最常
   >  用的,还是把运行记录输出到一个本地文件上。
   >- daemonize : 使进程在后台运行,并将日志打到指定的日志文件或者udp服务器(daemonize uWSGI)。实际上最常
   >  用的,还是把运行记录输出到一个本地文件上。
   >- vacuum : 当服务器退出的时候自动清理环境,删除unix socket文件和pid文件(try to remove all of the generated file/sockets)

查看原文

赞 9 收藏 7 评论 1

rayzz 发布了文章 · 2018-08-22

队列、栈和递归遍历目录

栈是一种内存结构,先进后出,后进先出。python中没有栈的概念,我们目前只能仿写。

# 模拟栈结构
stack = []
# 入栈(添加元素)
stack.append("A")
print(stack)
stack.append("B")
print(stack)
stack.append("C")
print(stack)
#入栈顺序 A B C

# 出栈(移除元素)
stack.pop()
print(stack)
stack.pop()
print(stack)
#出栈顺序 C B A

队列

队列也是一种内存结构,先进先出,后进后出。

创建队列

import collections
queue = collections.deque()

# 进队(向队列中添加元素)
queue.append("A")
queue.append("B")
queue.append("C")
print(queue)
# 出队(移除队列中的元素)
queue.popleft()
print(queue)
queue.popleft()
print(queue)

递归遍历目录和文件

import os

path = r'F:\PycharmProjects\basic gram\作业和习题\test'

def getAllFileAndDir(path):
    # 获取当前目录下所有文件及文件目录
    fileList = os.listdir(path)
    # print(fileList)

    # 遍历fileList列表
    for fileName in fileList:
        # isdir isfile
        # print(fileName)
        # 拼接绝对路径
        absFile = os.path.join(path,fileName)
        if os.path.isdir(absFile):
            print(absFile+'---目录')
            getAllFileAndDir(absFile)
        else:
            print(absFile+'---文件')    
getAllFileAndDir(path)

栈 深度遍历

import collections

def getAllFileAndDir(sourcePath):

    stack = collections.deque()
    stack.append(sourcePath)
    while len(stack) != 0:
        path = stack.pop()
        fileList = os.listdir(path)
        for fileName in fileList:
            absFile = os.path.join(path, fileName)
            if os.path.isdir(absFile):
                print(absFile+'---目录')
                stack.append(absFile)
            else:
                print(absFile+'---文件')

getAllFileAndDir(path)

队列 广度遍历

def getAllFileAndDir(sourcePath):

    queue = collections.deque()
    queue.append(sourcePath)

    while len(queue) !=0:
        path = queue.popleft()
        fileList = os.listdir(path)
        for fileName in fileList:
            absFile = os.path.join(path, fileName)
            if os.path.isdir(absFile):
                print(absFile+'---目录')
                queue.append(absFile)
            else:
                print(absFile+'---文件')


getAllFileAndDir(path)

复制目录和文件

import os
# 复制目录
def copyDir(sourDir,targetDir):
    if not os.path.exists(sourDir):
        print("如果源目录不存在,直接停止")
        return

    if not os.path.exists(targetDir):
        os.makedirs(targetDir)

    listName = os.listdir(sourDir)
    for dirNameAndFileName in listName:
        sourAbsPath = os.path.join(sourDir,dirNameAndFileName)
        targetAbsPath = os.path.join(targetDir,dirNameAndFileName)
        if os.path.isdir(sourAbsPath):
            copyDir(sourAbsPath,targetAbsPath)
        if os.path.isfile(sourAbsPath):
            #   如果目标文件不存在,   或者   如果该文件已经存在但是文件大小不一样
            if (not os.path.exists(targetAbsPath)) or (os.path.exists(targetAbsPath) and (os.path.getsize(sourAbsPath) != os.path.getsize(targetAbsPath))):
                rf = open(sourAbsPath,"rb")
                wf = open(targetAbsPath,"wb")
                while True:
                    content = rf.read(1024*1024)
                    if len(content) == 0:
                        break
                    wf.write(content)
                    wf.flush()
                wf.close()
                rf.close()

sPath = r'F:\PycharmProjects\basic gram\作业和习题\test'
tPath = r'F:\PycharmProjects\basic gram\作业和习题\testNew'
copyDir(sPath, tPath)

文件复制实例

1.一个函数接受文件夹的名称作为输入参数,请将该文件夹中的所有文件复制到 文件夹名-副本 中去,请补充缺失的代码. (20分)
def copyFile(sPath)
2.题1复制过程中,每隔一秒打印一次复制进度(即当前已复制个数/总文件个数)(15分)
import os
import collections
import time
import sys
def getFileNum(sPath):
    num = 0
    stack = collections.deque()
    stack.append(sPath)
    while len(stack) != 0:
        path = stack.pop()
        fileList = os.listdir(path)
        for fileName in fileList:
            absFile = os.path.join(path, fileName)
            if os.path.isdir(absFile):
                stack.append(absFile)
            else:
                num += 1
    return num

def copyFile(sPath):
    tPath = r'F:\PycharmProjects\basic gram\作业和习题\Anaconda3-副本'
    stack1 = collections.deque()
    stack1.append(sPath)
    stack2 = collections.deque()
    stack2.append(tPath)
    timepoint = 1
    filenum = 0
    while len(stack1) != 0:
        sPath = stack1.pop()
        tPath = stack2.pop()
        if not os.path.exists(tPath):
            os.makedirs(tPath)
        listName = os.listdir(sPath)
        for filename in listName:
            absfile = os.path.join(sPath, filename)
            tabsfile = os.path.join(tPath, filename)
            if os.path.isdir(absfile):
                stack1.append(absfile)
                stack2.append(tabsfile)
            else:
                rf = open(absfile, 'rb')
                wf = open(tabsfile, 'wb')
                while True:
                    content = rf.read(1024*1024)
                    if len(content) == 0:
                        break
                    wf.write(content)
                    # 刷新缓冲区
                    wf.flush()
                    if time.clock()//1 == timepoint:
                         sys.stdout.write('\r进度:%d/%d'%(filenum,num))
                         timepoint += 1
                wf.close()
                rf.close()
                filenum += 1
    sys.stdout.write('\r进度:%d/%d' % (num, num))


sPath = r'F:\PycharmProjects\basic gram\作业和习题\Anaconda3'

num = getFileNum(sPath)
# print(num)
start_time = time.clock()
copyFile(sPath)
查看原文

赞 0 收藏 0 评论 0

rayzz 发布了文章 · 2018-08-22

Django-发送邮件

概述

Python中内置了一个smtp邮件发送模块,Django在此基础上进行了简单地封装,让我们在Django环境中可以更方便更灵活i的发送邮件。所有的功能都在django.core.mail中,常用的免费服务器有:163、126、QQ等。

SSL和TLS

SSL:(Secure Socket Layer,安全套接字层)位于可靠的面向连接的网络层协议和应用层协议之间的一种协议层。SSL通过互相认证、使用数字签名确保完整性、使用加密确保私密性,以实现客户端和服务器之间的安全通讯。

TLS:(Transport LayerSecurity,传输层安全协议),用于两个应用程序之间提供保密性和数据完整性。

我们通常所说的 HTTPS 协议,就是“HTTP 协议”和“SSL/TLS 协议”的组合。你可以把 HTTPS 大致理解为——“HTTP over SSL”或“HTTP over TLS”(反正 SSL 和 TLS 差不多)。浏览器地址栏的那把锁指的就是SSL协议。

TLS和SSL的关系:并列关系

  最新版本的TLS(Transport Layer Security,传输层安全协议)是IETF(Internet Engineering Task Force,Internet工程任务组)制定的一种新的协议,它建立在SSL 3.0协议规范之上,是SSL 3.0的后续版本。在TLS与SSL 3.0之间存在着显著的差别,主要是它们所支持的加密算法不同,所以TLS与SSL 3.0不能互操作。

TLS的主要目标是使SSL更安全,并使协议的规范更精确和完善。TLS在SSL v3.0的基础上,提供了以下增加内容:

  • 更安全的MAC算法
  • 更严密的警报
  • “灰色区域”规范的更明确的定义

配置

首先,我们需要一个本地SMTP服务器或者在项目setting.py中添加以下设置来配置一个外部SMTP服务器:

  • EMAIL_HOST: SMTP服务器主机,默认为localhost。
  • EMAIL_PORT: SMTP服务器端口,默认为25。
  • EMAIL_HOST_USER: SMTP 服务器的用户名。
  • EMAIL_HOST_PASSWORD: SMTP 服务器的密码。
  • EMAIL_USE_TLS: 是否使用TLS安全连接,默认False。
  • EMAIL_USE_SSL: 是否使用SSL安全连接,默认False。

如果没有本地SMTP服务器,可以使用e-mail提供者的SMTP服务器。下面的简单配置是通过QQ服务器发送e-mail的配置。

# SMTP后端是Django的默认配置,如果要明确指定,请在settings中输入以下内容
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = "smtp.qq.com"  # SMTP服务器主机
EMAIL_PORT = 25             # 端口
EMAIL_HOST_USER = "695485***@qq.com"       # 发送邮件的邮箱
EMAIL_HOST_PASSWORD = "*********"    # #邮箱的授权密码
EMAIL_USE_SSL= True
注意:

配置中,EMAIL_USE_TLS和EMAIL_USE_SSL都默认设置为False,需要配置其中一个为True,但是不能两个都设置为True。一般端口587对应TLS,端口465对应SSL。

阿里云默认禁用了25端口,使用465端口,EMAIL_USE_TLS=True

发送邮件

# 前面四个参数必须要写,后面的参数可以为空
def send_mail(subject, message, from_email, recipient_list,
              fail_silently=False, auth_user=None, auth_password=None,
              connection=None, html_message=None):
  • subject:一个字符串,发送邮件的主题。
  • message:一个字符串,邮件内容。
  • from_email:一个字符串,收件人看到的发件人,可以自己设定。
  • recipient_list:字符串列表,每个字符串都是电子邮件地址。每个成员可以在收件人字段中看到其他收件人。
# apps/utils/email_send.py
from django.core.mail import send_mail

def sendMail(request): 
    from_email = 'ray<695485075@qq.com>'
    msg = '<a href="http://127.0.0.1:8000/index/">点击激活</a>'
    send_mail("注册激活","",msg,from_email,recipient_list=['',])
    return HttpResponse("邮件已发送")
查看原文

赞 0 收藏 0 评论 0

rayzz 发布了文章 · 2018-08-21

Django-缓存

概述:对于中等流量的网站来说,尽可能的减少开销是非常必要的。缓存数据就是为了保存那些需要很多计算资源的结果,这样的话就不必在下次重复消耗计算资源。获取数据的数据的时候就是去缓存中拿,拿到了直接返回,没拿到就去数据库中查询,筛选,然后缓存到数据库, 然后返回给模板。

Django自带了一个健壮的缓存系统来保存动态页面,避免每次请求都重新计算。

Django提供了不同级别的缓存策略,可以缓存特定的视图的输出、可以仅仅缓存那些很难计算出来的部分、或者缓存整个网站

目的:优化数据结构;优化了对数据的查询;筛选,过滤;减少了对磁盘的IO

官方文档:https://docs.djangoproject.co...

设置缓存

通过设置决定把数据缓存在哪里,是数据库中、文件系统中还是内存中

默认缓存(内存)

CACHES={
    'default':{
        'BACKEND':'django.core.cache.backends.locmem.LocMemCache',
        'LOCATION': 'unique-snowflake',
        'TIMEOUT':60
    }
}

参数TIMEOUT:缓存的默认过期时间,以秒为单位

  • 默认为300秒
  • 设置为None,表示永不过期
  • 设置为0造成缓存立即失效

文件缓存

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
        'LOCATION': 'c:/foo/bar',
        'TIMEOUT':300,
    }
}

数据库缓存

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
        'LOCATION': 'my_cache_table',
        'TIMEOUT': '60',
        'KEY_PREFIX': 'bbs',
        'VERSION': '1',
        'OPTIONS': {
            'MAX_ENTRIES': '300'
        }
    }
}

创建缓存表: python manage.py createcachetable

drf的缓存

https://github.com/chibisov/drf-extensions

这是drf的一个扩展,不止增强了缓存还有其他的。

pip install drf-extensions

缓存viewset中的retrieve和list 方法是很常见的。这就是为什么CacheResponseMixin存在。

获取数据的才会用到cache

from rest_framework_extensions.cache.mixins import CacheResponseMixin

把这个CacheResponseMixin,放在list之前。尽量放在第一个继承的类

class GoodsListViewSet(CacheResponseMixin,mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):

设置过期时间

REST_FRAMEWORK_EXTENSIONS = {
    'DEFAULT_CACHE_RESPONSE_TIMEOUT': 5
}

根据自己需求加缓存。目前这个缓存使用的是内存。每次系统重启会丢失。

redis缓存

官网: http://django-redis-chs.readt...

redis操作文档:http://redisdoc.com

默认使用redis中的1数据库,但可以指定使用哪个db

安装pip install django-redis

配置

# ---配置Session和Cache---
CACHES={
    'default':{
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION':'127.0.0.1:6379/12', # 指定db12
        'TIMEOUT':60,
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient',  # 指定连接Redis的客户端类
            # 'PASSWORD': 'mysecret',
             # "SOCKET_CONNECT_TIMEOUT": 5,  # in seconds
             # "SOCKET_TIMEOUT": 5,  # in seconds
             # "CONNECTION_POOL_KWARGS": {"max_connections": 100},
             # "CONNECTION_POOL_CLASS": "myproj.mypool.MyOwnPool",
        }
    }
}
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'

# --结束Session和Cache配置
参数解释
SOCKET_CONNECT_TIMEOUTsocket 建立连接超时设置
SOCKET_TIMEOUT连接建立后的读写操作超时设置
CONNECTION_POOL_KWARGS设置连接池的最大连接数量
CONNECTION_POOL_CLASS自己的连接池子类

连接池

django-redis 使用 redis-py 的连接池接口, 并提供了简单的配置方式. 除此之外, 你可以为 backend 定制化连接池的产生。redis-py 默认不会关闭连接, 尽可能重用连接。

连接池概念

为什么使用连接池?首先Redis也是一种数据库,它基于C/S模式,因此如果需要使用必须建立连接,稍微熟悉网络的人应该都清楚地知道为什么需要建立连接,C/S模式本身就是一种远程通信的交互模式,因此Redis服务器可以单独作为一个数据库服务器来独立存在。假设Redis服务器与客户端分处在异地,虽然基于内存的Redis数据库有着超高的性能,但是底层的网络通信却占用了一次数据请求的大量时间,因为每次数据交互都需要先建立连接,假设一次数据交互总共用时30ms,超高性能的Redis数据库处理数据所花的时间可能不到1ms,也即是说前期的连接占用了29ms,连接池则可以实现在客户端建立多个链接并且不释放,当需要使用连接的时候通过一定的算法获取已经建立的连接,使用完了以后则还给连接池,这就免去了数据库连接所占用的时间。

配置默认连接池

配置默认连接池很简单, 你只需要在 CACHES 中使用 CONNECTION_POOL_KWARGS 设置连接池的最大连接数量即可

你可以得知连接池已经打开多少连接:

from django.core.cache import get_cache
from django_redis import get_redis_connection

r = get_redis_connection("default")  # Use the name you have defined for Redis in settings.CACHES
connection_pool = r.connection_pool
print("Created connections so far: %d" % connection_pool._created_connections)

使用自己的连接池子类

有时你想使用自己的连接池子类. django-redis 提供了 CONNECTION_POOL_CLASS 来配置连接池子类

myproj/mypool.py

from redis.connection import ConnectionPool

class MyOwnPool(ConnectionPool):
    # Just doing nothing, only for example purpose
    pass

缓存的用法

单个view缓存

django.views.decorators.cache.cache_page装饰器用于对视图的输出进行缓存

from django.views.decorators.cache import cache_page

@cache_page(60 * 2)
def index(request):
    # return HttpResponse("sunck is a good man")
    return HttpResponse("sunck is a nice man")
参数:
timeout : 有效时长  # we've written it as 60 * 15 for the purpose of readability
cache: 缓存到哪一个库中;很少使用;针对于系统配置了多个缓存
       如: @cache_page(timeout=60, cache='filecache')
key_prefix: 前缀      

模板片段缓存

cache标签: 参数

  • 缓存时间,以秒为单位
  • 给缓存片段起名字
{#{% load static from staticfiles %}#}
{% load static %}
{% load cache %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>主页</title>

{#    <link rel="stylesheet" type="text/css" href="/static/css/index.css">#}
    <link rel="stylesheet" type="text/css" href="{% static 'css/index.css' %}">
</head>
<body>
    <h1>sunck is a nice man</h1>
    {% cache 120 sunck %}
        <h1>nice man</h1>
        <!--<h1>good man</h1>-->
    {% endcache %}
</body>
</html>

原生cache

from django.core.cache import cache
查看所有缓存的key: cache.keys('*')
设置:cache.set(键, 值, 有效时间)
获取:cache.get(键)
删除:cache.delete(键)
清空:cache.clear()
>>> cache.set_many({'a': 1, 'b': 2, 'c': 3})
>>> cache.getmany(['a','b','c'])
{'a': 1, 'b': 2, 'c': 3}
cache.delete_pattern("foo_*") # 全局通配符
查看原文

赞 1 收藏 1 评论 0

rayzz 赞了文章 · 2018-08-21

3-django进阶之celery

Django集成Celery到项目

​ 本节将celery集成到Django项目中,实现异步任务处理和定时任务处理

Celery工作流程

celery流程图

Celery的架构由三部分组成,消息中间件(message broker),任务执行单元(worker)和任务执行结果存储(task result store)组成。

消息中间件

Celery本身不提供消息服务,但是可以方便的和第三方提供的消息中间件集成。包括,RabbitMQ, Redis, MongoDB (experimental), Amazon SQS (experimental),CouchDB (experimental), SQLAlchemy (experimental),Django ORM (experimental), IronMQ

任务执行单元

Worker是Celery提供的任务执行的单元,worker并发的运行在分布式的系统节点中。

任务结果存储

Task result store用来存储Worker执行的任务的结果,Celery支持以不同方式存储任务的结果,包括AMQP, Redis,memcached, MongoDB,SQLAlchemy, Django ORM,Apache Cassandra, IronCache

1 Celery安装与配置

在虚拟环境中安装:

pip install django-celery==3.2.2

pip install django-redis

pip install flower # celery 的web管理平台(异步任务可视化)

查看集成到Django中的celery版本, pip freeze

celery==3.1.26.post2 django-celery==3.2.2 flower==0.9.2

启动redis服务, 端口假设为6379

发现pip安装比较慢的情况

pip install pillow -i https://pypi.douban.com/simple

2 Django中配置

(1)在主工程的配置文件settings.py 中应用注册表INSTALLED_APPS中加入 djcelery

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'art',
    'xadmin',
    'crispy_forms',
    'DjangoUeditor',
    'djcelery',       #加入djcelery
]

(2) 在settings.py 中加入celery配置信息

#############################
# celery 配置信息 start
#############################
import djcelery
djcelery.setup_loader()
BROKER_URL = 'redis://127.0.0.1:6379/1'
CELERY_IMPORTS = ('art.tasks')
CELERY_TIMEZONE = 'Asia/Shanghai'
CELERYBEAT_SCHEDULER = 'djcelery.schedulers.DatabaseScheduler' 
from celery.schedules import crontab
from celery.schedules import timedelta

CELERYBEAT_SCHEDULE = {    #定时器策略
    #定时任务一: 每隔30s运行一次
    u'测试定时器1': {
        "task": "art.tasks.tsend_email",
        #"schedule": crontab(minute='*/2'),  # or 'schedule':   timedelta(seconds=3),
        "schedule":timedelta(seconds=30),
        "args": (),
    },
}
#############################
# celery 配置信息 end
#############################

​ 当djcelery.setup_loader()运行时,Celery便会去查看INSTALLD_APPS下包含的所有app目录中的tasks.py文件,找到标记为task的方法,将它们注册为celery task

​ BROKER_URL:broker是代理人,它负责分发任务给worker去执行。我使用的是Redis作为broker

​ 没有设置 CELERY_RESULT_BACKEND,默认没有配置,此时Django会使用默认的数据库(也是你指定的orm数据库)。

CELERY_IMPORTS:是导入目标任务文件

CELERYBEAT_SCHEDULER:使用了django-celery默认的数据库调度模型,任务执行周期都被存在默认指定的orm数据库中.

CELERYBEAT_SCHEDULE:设置定时的时间配置, 可以精确到秒,分钟,小时,天,周等。

(3)创建应用实例

​ 在主工程目录添加celery.py, 添加自动检索django工程tasks任务

​ vim artproject/celery.py

#!/usr/bin/env python  
# encoding: utf-8  
#目的是拒绝隐式引入,celery.py和celery冲突。
from __future__ import absolute_import,unicode_literals 
import os
from celery import Celery
from django.conf import settings

# 设置环境变量
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "artproject.settings")

#创建celery应用
app = Celery('art_project')
app.config_from_object('django.conf:settings')

#如果在工程的应用中创建了tasks.py模块,那么Celery应用就会自动去检索创建的任务。比如你添加了一个任#务,在django中会实时地检索出来。
app.autodiscover_tasks(lambda :settings.INSTALLED_APPS)

(4) 创建任务 tasks

每个任务本质上就是一个函数,在tasks.py中,写入你想要执行的函数即可。

在应用art中添加我们需要提供的异步服务和定时服务

vim art/tasks.py

#!/usr/bin/env python  
# encoding: utf-8  
from __future__ import absolute_import
import time
from django.core.mail import send_mail
from celery.utils.log import get_task_logger
from artproject.celery import app

from art.utils.send_mail import pack_html, send_email

@app.task
def tsend_email():
   url = "http://1000phone.com"
   receiver = 'diyuhuan@1000phone.com'
   content = pack_html(receiver, url)
   # content = 'this is email content.'
   send_email(receiver, content)
   print('send email ok!')


@app.task
def add(x, y):
   return x+y

上述我们把异步处理任务add和定时器任务tsend_email都放在了tasks.py 中

(5)迁移生成celery需要的数据表

python manage.py migrate

此时数据库表结构多出了几个

celery_taskmeta            |
| celery_tasksetmeta         |
| djcelery_crontabschedule   |
| djcelery_intervalschedule  |
| djcelery_periodictask      |
| djcelery_periodictasks     |
| djcelery_taskstate         |
| djcelery_workerstate 

3 启动服务,测试

我们可以采用 python manage.py help 发现多出了 celery 相关选项。

(1)启动django celery 服务

启动服务:

python manage.py celery worker --loglevel=info

此时异步处理和定时处理服务都已经启动了

(2)web端接口触发异步任务处理

我们在web端加入一个入口,触发异步任务处理add函数

在应用art的urls.py 中加入如下对应关系

from art.views import add_handler


url(r'^add', add_handler),

art/views.py 中加入处理逻辑

def add_handler(request):
   x = request.GET.get('x', '1')
   y = request.GET.get('y', '1')
   from .tasks import add
   add.delay(int(x), int(y))
   res = {'code':200, 'message':'ok', 'data':[{'x':x, 'y':y}]}
   return HttpResponse(json.dumps(res))

启动web服务,通过url传入的参数,通过handler的add.delay(x, y)计算并存入mysql

http://127.0.0.1:8000/art/add?x=188&y=22

(4) 测试定时器,发送邮件

在终端输入 python manage.py celerybeat -l info

会自动触发每隔30s执行一次tsend_email定时器函数,发送邮件:

CELERYBEAT_SCHEDULE = {    #定时器策略
    #定时任务一: 每隔30s运行一次
    u'测试定时器1': {
        "task": "art.tasks.tsend_email",
        #"schedule": crontab(minute='*/2'),  # or 'schedule': timedelta(seconds=3),
        "schedule":timedelta(seconds=30),
        "args": (),
    },
}

具体发送邮件服务程序见下面的第4节

4 邮件发送服务

项目中经常会有定时发送邮件的情形,比如发送数据报告,发送异常服务报告等。

可以编辑文件 art/utils/send_mail.py, 内容编辑如下:

#!/usr/bin/env python
#-*- coding:utf-8 -*-
#written by diyuhuan
#发送邮件(wd_email_check123账号用于内部测试使用,不要用于其他用途)

import smtplib  
from email.mime.multipart import MIMEMultipart  
from email.mime.text import MIMEText  
from email.mime.image import MIMEImage 
from email.header import Header
import time

sender = 'wd_email_check123@163.com'  
subject = u'api开放平台邮箱验证'
smtpserver = 'smtp.163.com'
username = 'wd_email_check123'
password = 'wandacheck1234'
mail_postfix="163.com"

def send_email(receiver, content):
    try:
        me = username+"<"+username+"@"+mail_postfix+">"
        msg = MIMEText(content, 'html', 'utf-8')
        msg['Subject'] = subject
        msg['From'] = sender
        msg['To'] = receiver
        smtp = smtplib.SMTP()  
        smtp.connect(smtpserver)  
        smtp.login(username, password)
        smtp.sendmail(sender, receiver, msg.as_string())  
        smtp.quit()
        return True
    except Exception as e:
        print('send_email has error with : ' + str(e))
        return False


def pack_html(receiver, url):
    html_content = u"<html><div>尊敬的用户<font color='#0066FF'>%s</font> 您好!</div><br>" \
                   "<div>感谢您关注我们的平台 ,我们将为您提供最贴心的服务,祝您购物愉快。</div><br>" \
                   "<div>点击以下链接,即可完成邮箱安全验证:</div><br>"  \
                   "<div><a href='%s'>%s</a></div><br>"  \
                   "<div>为保障您的帐号安全,请在24小时内点击该链接; </div><br>" \
                   "<div>若您没有申请过验证邮箱 ,请您忽略此邮件,由此给您带来的不便请谅解。</div>" \
                   "</html>" % (receiver, url, url)
    html_content = html_content
    return html_content


if __name__ == "__main__":
    url = "http://1000phone.com"
    receiver = 'diyuhuan@1000phone.com'
    #content = pack_html(receiver, url)
    content = 'this is email content. at %s.'%int(time.time())
    send_email(receiver,  content)

至此,在celery ui界面可以看到两类,定时器处理和异步处理。

5 启动flower服务

​ python manager celery flower

案例

clipboard.png

读书网站实现抢读功能

qd(request, id) :抢读视图函数

quereyQD(request,id) :查询抢读的视图函数

settings.py

INSTALLED_APPS = [
   'djcelery',
]

...
import djcelery

# 装载djcelery对象
djcelery.setup_loader()
# 配置消息中间件的位置
BROKER_URL = 'redis://127.0.0.1:6379/12'

CELERY_TIMEZONE = 'Asia/Shanghai'
# 配置批量调试器
CELERYBEAT_SCHEDULER = 'djcelery.schedulers.DatabaseScheduler'

在主工程目录添加celery.py, 添加自动检索django工程tasks任务

celery.py

from __future__ import absolute_import
import os
from celery import Celery

# 设置环境变量
from HArtPro import settings

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'HArtPro.settings')

# 创建Celery对象
app = Celery('hart')

# 加载配置
app.config_from_object('django.conf:settings')

# 自动发现task的异步任务
app.autodiscover_tasks(lambda :settings.INSTALLED_APPS)

当前app目录下建立tasks.py

from MArtPro.celery import app
from utils import redis_cache


@app.task
def advanceArt(artId, userId):
    # 抢读文章(artId 文章id, userId 当前用户登录的Id)
    print('用户', userId, '正在抢读', artId)

    # 判断当前抢读的hash对象AdvanceArt长度是否达到5个
    if redis_cache.hlen('AdvanceArt') >= 5:
        return artId + '抢读失败'

    redis_cache.hset('AdvanceArt', userId, artId)

    return artId + '抢读成功!'

views.py

from redis_ import rd  # rd 对象
from art import tasks

def (request, artId):
    # 抢读
    login_user = request.session.get('login_user')

    if not login_user:
        return JsonResponse({'status': 101,
                             'msg': '亲,请先登录,再抢读,谢谢!'})

    # 判断当前用户是否已抢过
    user_id =json.loads(login_user).get('id')
    if redis_cache.hexists('AdvanceArt', user_id):
        return JsonResponse({'status': 205,
                             'msg': '亲,你只能抢一本'})
    # 任务延迟执行
    tasks.advanceArt.delay(artId, user_id)
    return JsonResponse({'status': 201,
                         'msg': '正在抢读...'})


def queryAdvance(request, artId):
    # 查询抢读是否成功
    login_user = request.session.get('login_user')

    if not login_user:
        return JsonResponse({'status': 101,
                             'msg': '亲,请先登录,再查看抢读,谢谢!'})

    user_id = json.loads(login_user).get('id')

    artId = redis_cache.hget('AdvanceArt', user_id)
    if artId:
        art = Art.objects.get(id=artId.decode())
        return JsonResponse({'status': 200,
                             'msg': '恭喜您,抢读%s 成功'%art.title})
    else:
        if redis_cache.hlen('AdvanceArt')< 5:
            return JsonResponse({'status': 202,
                                 'msg': '正在抢读...'})
        else:
            return JsonResponse({'status': 203,
                                 'msg': '抢读失败, 请下次碰碰运气!'})

前端通过定时器,每秒执行查询函数

查看原文

赞 9 收藏 4 评论 5

rayzz 发布了文章 · 2018-08-20

Xadmin和Ueditor

xadmin的安装

xadmin依赖

six
future
httplib2
django-reversion
django-formtools
django-crispy-forms
django-import-export

方法一:

命令行模式下:

pip install xadmin

方法二:

django2.0的安装(源码安装方式):

https://github.com/sshwsfc/xadmin/tree/django2

README.rst这个文件的编码有问题,可以内容没什么重要的,可以直接到github上下载安装包,然后新建一个txt空文件,把文件名改成README.rst,替换原来的文件。替换成功后,把压缩包放到文件夹(C:Users18845EnvsxinyuonlineScripts)中,在命令窗口中进入存放压缩包的文件下,执行pip命令 

(Env) C:\Users\18845\Envs\xinyuonline\Scripts>pip install xadmin-master.zip

如果上面安装提示Runtime错误:

更换安装源(使用豆瓣源)

pip install -i https://pypi.douban.com/simple xadmin-django2

安装成功后,同时也安装了很多依赖的包。

xadmin的配置

 (1)新建Python Package "extra_apps",把源码xadmin文件夹放到extra_apps文件夹下面。

(2)把extra_apps右键mark为Source Root并在settings中加入

sys.path.insert(0,os.path.join(BASE_DIR, 'extra_apps'))

图片描述
(3)因为我们用源码的xadmin,所以要卸载之前安装的。也可以一开始不安装xadmin,直接安装依赖包,把源文件拉到项目中。

pip uninstall xadmin

(4)配置路由

把admin改成xadmin

# urls.py

from django.urls import path

import xadmin

urlpatterns = [
    path('xadmin/', xadmin.site.urls),
]

(5)注册app

把下面两个app注册到settings.py的INSTALLED_APPS中

'xadmin',
'crispy_forms'

(6)重新生成数据库

python manage.py makemigrations

python manage.py migrate

(7)设置成中文

LANGUAGE_CODE = 'zh-hans'

TIME_ZONE = 'Asia/Shanghai'

USE_I18N = True

USE_L10N = True

USE_TZ = False #设置为本地时区

(8)创建一个管理员用户

python manage.py createsuperuser

现在就可以运行了 

python manage.py runserver 

访问后台:http://127.0.0.1:8000/xadmin

 可以看到成功进入管理界面

app的注册

四个app下面都新建文件adminx.py,然后分别注册到后台.

UserProfile继承User因此自动注册到xadmin.

(1)users/adminx.py

# users/adminx.py

import xadmin

from .models import EmailVerifyRecord

#xadmin中这里是继承object,不再是继承admin
class EmailVerifyRecordAdmin(object):
    pass

xadmin.site.register(EmailVerifyRecord,EmailVerifyRecordAdmin)

完善功能,增加显示字段,搜索和过滤

# users/adminx.py

import xadmin

from .models import EmailVerifyRecord

#xadmin中这里是继承object,不再是继承admin
class EmailVerifyRecordAdmin(object):
    # 显示的列
    list_display = ['code', 'email', 'send_type', 'send_time']
    # 搜索的字段,不要添加时间搜索
    search_fields = ['code', 'email', 'send_type']
    # 过滤
    list_filter = ['code', 'email', 'send_type', 'send_time']
    #每页显示多少数据
    list_per_page = 3
    #属性的先后顺序
    fields = ['','']
    #属性分组
    fieldsets = [
      ('base',{'fields':['','']}),
      ('more',{'fields':['','']}),
    ]
    #fields和fieldsets只能有一个
xadmin.site.register(EmailVerifyRecord,EmailVerifyRecordAdmin)

xadmin的全局配置

将全局配置修改:

  • 如左上角:django Xadmin。下面的我的公司
  • 主题修改,app名称汉化,菜单收叠。

 使用Xadmin的主题功能。

把全站的配置放在usersadminx.py中:

 (1)添加主题功能

添加主题后,可以选择自己喜欢的主题

from xadmin import views

# 创建xadmin的最基本管理器配置,并与view绑定
class BaseSetting(object):
    # 开启主题功能
    enable_themes = True
    use_bootswatch = True

# 将基本配置管理与view绑定
xadmin.site.register(views.BaseAdminView,BaseSetting)

(2)全局配置

 修改django admin 和下面的我的公司收起菜单

# 全局修改,固定写法
class GlobalSettings(object):
    # 修改title
    site_title = 'NBA后台管理界面'
    # 修改footer
    site_footer = '科比的公司'
    # 收起菜单,只显示app名,具体表名隐藏
    menu_style = 'accordion'
    # 图标
     

# 将title和footer信息进行注册
# CommAdminView源码中有配置,下面
#   global_search_models    全局搜索model
#    global_models_icon = {} 全局设置model图标
#   default_model_icon = None  默认图标
#   apps_label_title = {}   app名字
#   apps_icons = {}          app图标
xadmin.site.register(views.CommAdminView,GlobalSettings)

(3)app名字改为中文

# users/apps.py

from django.apps import AppConfig


class UsersConfig(AppConfig):
    name = 'users'
    #app名字后台显示中文
    verbose_name = "用户管理"

还需要__init__.py中修改默认配置才生效

# users/__init__.py

default_app_config = 'users.apps.UsersConfig'

或者修改setting中app名称为补全名称

     'users.apps.UsersConfig',
    'goods.apps.GoodsConfig',
    'trade.apps.TradeConfig',
    'user_operation.apps.UserOperationConfig',

然后在apps中添加

from django.apps import AppConfig

class UsersConfig(AppConfig):
    name = 'users'
    verbose_name = "用户管理"

或者 apps_label_title = {}这里设置名字

xadmin进阶开发

权限管理

(1)用户权限

超级用户拥有所有权限,其它添加的用户默认没有任何权限,为其添加权限

2)组的权限

 添加一个组“编辑部门”,赋予权限 ,把用户加入这个组,则这个用户拥有和组相同的权限

自定义icon、默认排序、只读字段和不显示的字段

xadmin的图标采用的是第三方css样式“font awesome”,我们可以进官网下载最新的样式替代原本的,下载地址:http://www.fontawesome.com.cn/

下载完后把里面的“css”和“fonts”两个文件夹拷贝到xadmin的源码(路径:xadmin/static/vendor/font-awesome)里面

# Course的admin管理器
class CourseAdmin(object):
    '''课程'''

    list_display = [ 'name','desc','detail','degree','learn_times','students']   #显示的字段
    search_fields = ['name', 'desc', 'detail', 'degree', 'students']             #搜索
    list_filter = [ 'name','desc','detail','degree','learn_times','students']    #过滤 
    model_icon = 'fa fa-book'            #图标
    ordering = ['-click_nums']           #排序
    readonly_fields = ['click_nums']     #只读字段,不能编辑
    exclude = ['fav_nums']               #不显示的字段 ,和readonly_fields是冲突的
    # 执行动作的位置
    actions_on_bottom = True
    actions_on_top = True

下拉框搜索:

relfield_style = 'fk-ajax'

当有外键指向他,会以ajax方式加载

数据量过大时很有用

inlines添加数据

 目前在添加课程的时候没法添加章节和课程资源,我们可以用inlines去实现这一功能

没法完成在章节中再嵌套视频
但是可以有多个inline。在添加课程时添加课程资源

course/adminx.py

class LessonInline(object):
    model = Lesson
    #extra表示创建课程时创建的章节数量
    extra = 1
    #style='tab'表示横向显示?
    style = 'tab'

class CourseResourceInline(object):
    model = CourseResource
    extra = 1


#在CourseAdmin中使用inlines添加上面两个的方法
class CourseAdmin(object):
    inlines = [LessonInline,CourseResourceInline]    #增加章节和课程资源

一张表分两个Model来管理

课程里面分为轮播课程和不是轮播课程两种类型,我们可以分开来管理

course/models.py里面新建一个Model

class BannerCourse(Course):
    '''显示轮播课程'''
    class Meta:
        verbose_name = '轮播课程'
        verbose_name_plural = verbose_name
        #这里必须设置proxy=True,这样就不会再生成一张表,同时还具有Model的功能
        proxy = True

course/adminx.py中重载queryset方法。

from .models import BannerCourse

class CourseAdmin(object):
    '''课程'''

    list_display = [ 'name','desc','detail','degree','learn_times','students']   #显示的字段
    ......

    def queryset(self):
        # 重载queryset方法,来过滤出我们想要的数据的
        qs = super(CourseAdmin, self).queryset()
        # 只显示is_banner=True的课程
        qs = qs.filter(is_banner=False)
        return qs


class BannerCourseAdmin(object):
    '''轮播课程'''

    list_display = [ 'name','desc','detail','degree','learn_times','students']
    ......
    
    def queryset(self):
        #重载queryset方法,来过滤出我们想要的数据的
        qs = super(BannerCourseAdmin, self).queryset()
        #只显示is_banner=True的课程
        qs = qs.filter(is_banner=True)
        return qs

# 将管理器与model进行注册关联
xadmin.site.register(Course, CourseAdmin)
xadmin.site.register(BannerCourse, BannerCourseAdmin)

xadmin的其它常见功能

(1)list_editable

在列表页可以直接编辑的,而不用点进去编辑。

class CourseAdmin(object):
    list_editable = ['degree','desc']

(2)自定义函数作为列显示

course/models.py中

class Course(models.Model):
    '
    '
    '
    def get_zj_nums(self):
        #获取课程的章节数
        return self.lesson_set.all().count()
    get_zj_nums.short_description = '章节数'   #在后台显示的名称

course/adminx.py中

class CourseAdmin(object):
    list_display = ['get_zj_nums']  #直接使用函数名作为字段显示
   #布尔值、下拉框的字段也可以定义一个函数,把函数名作为字段显示,比如 性别,这里的机构类别啊等等。

    def sex(self):
        if self.sex:
            return '男'
        else:
            return '女'

    sex.short_description = '性别' #函数命名为汉字
    list_display = [sex,]

效果:列表字段多了个“章节数”

(3)显示自定义的html代码

course/models.py中

class Course(models.Model):
    .
    .
    .
    def go_to(self):
        from django.utils.safestring import mark_safe
        #mark_safe后就不会转义
        return mark_safe("<a href='https://home.cnblogs.com/u/derek1184405959/'>跳转</a>")
    go_to.short_description = "跳转"
class CourseAdmin(object):
    list_display = ['go_to']

效果:多了一个列表“跳转”,点击后跳转到上面定义的地址

(4)refresh定时刷新工具

 course/adminx.py中

class CourseAdmin(object):
    refresh_times = [3,5]           #自动刷新(里面是秒数)

后台效果:

可以选择3s或者5s自动刷新页面

(5)字段联动

 应用场景:当添加一门课程的时候,希望课程机构里面的课程数 +1

 重写xadmin的save_models方法

class CourseAdmin(object):
    .
    .
    .
    def save_models(self):
        # 在保存课程的时候统计课程机构的课程数
        # obj实际是一个course对象
        obj = self.new_obj
        # 如果这里不保存,新增课程,统计的课程数会少一个
        obj.save()
        # 确定课程的课程机构存在。
        if obj.course_org is not None:
            #找到添加的课程的课程机构
            course_org = obj.course_org
            #课程机构的课程数量等于添加课程后的数量
            course_org.course_nums = Course.objects.filter(course_org=course_org).count()
            course_org.save()

富文本编辑器Ueditor

(1)下载

地址:https://github.com/twz915/Dja...

解压后,把DjangoUeditor文件夹拷贝到项目目录下面

注意:直接pip install DjangoUeditor的方法会出问题

(2)settings中添加app

INSTALLED_APPS = [
    'DjangoUeditor',
]

(3)MxOnline/urls.py

   # 富文本编辑器url
    path('ueditor/',include('DjangoUeditor.urls' )),

 (4)course/models.py中Course修改detail字段

from DjangoUeditor .models import UEditorField

class Course(models.Model):
    # detail = models.TextField("课程详情")
    detail = UEditorField(verbose_name=u'课程详情', width=600, height=300, imagePath="courses/ueditor/",
                          filePath="courses/ueditor/", default='')

(5)xadmin/plugins目录下新建ueditor.py文件,代码如下

import xadmin
from xadmin.views import BaseAdminPlugin, CreateAdminView, ModelFormAdminView, UpdateAdminView
from DjangoUeditor.models import UEditorField
from DjangoUeditor.widgets import UEditorWidget
from django.conf import settings


class XadminUEditorWidget(UEditorWidget):
    def __init__(self, **kwargs):
        self.ueditor_options = kwargs
        self.Media.js = None
        super(XadminUEditorWidget,self).__init__(kwargs)


class UeditorPlugin(BaseAdminPlugin):

    def get_field_style(self, attrs, db_field, style, **kwargs):
        if style == 'ueditor':
            if isinstance(db_field, UEditorField):
                widget = db_field.formfield().widget
                param = {}
                param.update(widget.ueditor_settings)
                param.update(widget.attrs)
                return {'widget':XadminUEditorWidget(**param)}
        return attrs

    def block_extrahead(self, context, nodes):
        js  = '<script type="text/javascript" data-original="%s"></script>' %(settings.STATIC_URL + "ueditor/ueditor.config.js")
        js += '<script type="text/javascript" data-original="%s"></script>' %(settings.STATIC_URL + "ueditor/ueditor.all.min.js")
        nodes.append(js)

xadmin.site.register_plugin(UeditorPlugin, UpdateAdminView)
xadmin.site.register_plugin(UeditorPlugin, CreateAdminView)

(6)xadmin/plugs/__init__.py里面添加ueditor插件

PLUGINS = (
   'ueditor',
)

(7)course/adminx.py中使用

class CourseAdmin(object):
    #detail就是要显示为富文本的字段名
    style_fields = {"detail": "ueditor"}

(8)course-detail.html

在模板中必须关闭Django的自动转义才能正常显示

<div class="tab_cont tab_cont1">
     {% autoescape off %}
     {{ course.detail }}
     {% endautoescape %}
     </div>

导入excel

class CourseAdmin(object):
    '''课程'''

    list_display = ['name', 'desc', 'detail', 'degree', 'learn_times', 'students']
    search_fields = ['name', 'desc', 'detail', 'degree', 'students']
    list_filter = ['name', 'desc', 'detail', 'degree', 'learn_times', 'students']

    inlines = [LessonInline]
    style_fields = {"detail":"ueditor"}
    import_excel = True

    def queryset(self):
        return super().queryset().filter(is_banner=False)

    def post(self, request, *args, **kwargs):
        #  导入逻辑
        if 'excel' in request.FILES:
            pass
        return super(CourseAdmin, self).post(request, args, kwargs)
查看原文

赞 2 收藏 2 评论 0