一. Form&Auth
Form的验证思路
--使用django内部的用户表单验证
前端:form表单
后台:创建form类,当请求到来时,先匹配,匹配出正确和错误信息。
'''*******************
# templates/register.html
注意:
{% csrf_token %}
{{ errors.username }}
*******************'''
<form action="" method="post">
{% csrf_token %}
<p>
用户名: <input type="text" name="username">
<span>{{ errors.username }}</span>
</p>
<p>
密码: <input type="password" name="password">
<span>{{ errors.password }}</span>
</p>
<p>
确认密码: <input type="password" name="password2">
<span>{{ errors.password2 }}</span>
</p>
<input type="submit" value="注册">
</form>
'''*******************
# App/forms.py
注意:在app目录下新建的文件forms.py
可以创建不同的表单验证类,对表单的字段进行验证
from django import forms
*******************'''
# Form的使用
class RegisterForm(forms.Form):
# 使用表单做校验
# 表示username字段必须填写,且最大不超过16字符,最小不低于6字符
username = forms.CharField(
required=True,
max_length=16,
min_length=6,
error_messages={
'required': "注册用户名必须填写",
'max_length': '注册账号最长不超过16',
'min_length': '注册账号最小不少于6',
}
)
password = forms.CharField(
required=True,
max_length=18,
min_length=6,
error_messages={
'required': '密码不能为空',
'max_length': '长度不能超过18',
'min_length': '长度不能小于6',
}
)
password2 = forms.CharField(
required=True,
max_length=18,
min_length=6,
error_messages={
'required': '密码不能为空',
'max_length': '长度不能超过18',
'min_length': '长度不能小于6',
}
)
age = forms.IntegerField(
required= True,
max_value=100,
min_value=18,
error_messages={
'required': '年龄不能为空',
'max_value': '超过最大年龄',
'min_value': '小于最小年龄',
}
# 重写clean方法
def clean(self):
# 获取清洗过之后的数据
username = self.cleaned_data.get('username')
password = self.cleaned_data.get('password')
password2 = self.cleaned_data.get('password2')
if password != password2:
# 两次密码不一致
raise forms.ValidationError({'password2': "两次密码不一致!"})
# 检测用户名是否已存在
if User.objects.filter(username=username).exists():
raise forms.ValidationError({'username': "用户名已存在!"})
return self.cleaned_data
'''*******************
# App/views.py
注意:
from django.contrib.auth.models import User
使用的是django内置模型来创建用户,而且密码是加密的
登录验证密码时要用auth.authenticate()
*******************'''
# 视图函数
def register(request):
if request.method == 'GET':
return render(request, 'register.html')
elif request.method == 'POST':
# 使用form
form = RegisterForm(request.POST)
if form.is_valid():
# 如果所有输入都验证成功
username = form.cleaned_data.get('username')
password = form.cleaned_data.get('password')
# 注册
User.objects.create_user(username=username, password=password)
return redirect(reverse('login'))
else:
# 验证失败
print("form.errors: ", form.errors, type(form.errors))
return render(request, 'register.html', {"errors": form.errors})
'''*******************
登录验证
*******************'''
class LoginForm(forms.Form):
# 对单个输入框验证
username = forms.CharField(
required=True,
max_length=20,
min_length=6,
error_messages={
'required': '用户名不能为空',
'max_length': '长度不能超过20',
'min_length': '长度不能小于6',
}
)
password = forms.CharField(
required=True,
max_length=18,
min_length=6,
error_messages={
'required': '密码不能为空',
'max_length': '长度不能超过18',
'min_length': '长度不能小于6',
}
)
# 重写clean方法
def clean(self):
# 获取清洗过之后的数据
username = self.cleaned_data.get('username')
password = self.cleaned_data.get('password')
# 检测用户名是否已存在
if not User.objects.filter(username=username).exists():
raise forms.ValidationError({'username': "不存在该用户名!"})
# 检测密码
if not auth.authenticate(username=username, password=password):
raise forms.ValidationError({'password': "密码错误!"})
return self.cleaned_data
'''*******************
登录验证views.py
*******************'''
# 登录
def login(request):
if request.method == 'GET':
return render(request, 'login.html')
elif request.method == 'POST':
# username = request.POST.get('username')
# password = request.POST.get('password')
# 使用form
form = LoginForm(request.POST)
if form.is_valid():
username = form.cleaned_data.get('username')
password = form.cleaned_data.get('password')
# 登录验证,设置session
user = auth.authenticate(username=username, password=password) # 多余验证,可以在form
auth.login(request, user) # 设置session
return redirect(reverse('index'))
else:
return render(request, 'login.html', {'errors': form.errors})
Auth
Django内置了强大的用户认证系统auth,它默认使用 auth_user 表来存储用户数据。
在INSTALLED_APPS中添加'django.contrib.auth'使用该APP, auth模块默认启用。
auth 模块
创建超级用户:python manage.py createsuperuser
auth.authenticate():验证用户名和密码是否正确,一般需要username、password,认证成功返回一个User对象。
auth.login():将认证通过后的User对象注入request.user属性,会在后端为该用户生成相关session数据。
auth.logout():调用该函数时,当前请求的session信息会全部清除。该用户即使没有登录,使用该函数也不会报错。
# 注销
def logout(request):
auth.logout(request) # 删除session
return redirect(reverse('index'))
is_authenticated():判断是否认证。
login_requierd():auth给我们提供的一个装饰器工具,用来快捷的给某个视图添加登录校验。
- 若用户没有登录,则会跳转到django默认的登录URL '/accounts/login/' 并传递当前访问url的绝对路径 (登陆成功后,会重定向到该路径)。
- 如果需要自定义登录的URL,则需要在settings.py文件中通过LOGIN_URL进行修改。
- LOGIN_URL = '/login/' # 这里配置成你项目登录页面的路由
# 购物车
@login_required
def cart(request):
return render(request, 'cart.html')
create_user():创建一个新用户,需要username、password等。
create_superuser():创建一个超级用户。
* check_password():检查密码是否正确。
* set_password():修改密码,设置完一定要调用用户对象的save方法。
二.验证码
在用户登录,注册以及一些敏感操作的时候,我们为了防止服务器被暴力请求,或爬虫爬取,我们可以使用验证码进行过滤,减轻服务器的压力。
验证码需要使用绘图 Pillow
pip install Pillow
核心
Image,ImageDraw,ImageFont
绘制流程
backgroundcolor = (10,20,30) RGB颜色
初始化画布
image = Image.new('RGB',(100,50),backgroundcolor)
获取画布中画笔对象
draw = ImageDraw.Draw(image)
绘制验证码,随机四个
font = ImageFont.truetype('path',size)
fontcolor = (20,40,60)
draw.text((x,y),'R',font,fontcolor)
最后扫尾
del draw
import io
buf = io.BytesIO()
Image.save(buf, 'png')
return HttpResponse(buf.getvalue(),'image/png')
'''*******************
# templates/index.html
注意:
{% csrf_token %}
{{ errors.username }}
*******************'''
<form action="" method="post">
{% csrf_token %}
<p> 用户名: <input type="text" name="username"> </p>
<p>
验证码: <input type="text" name="vcode">
<img id="vcode" src="/genvcode/" />
</p>
<input type="submit" value="登录">
</form>
<script>
vcode.onclick = function () {
// 由于浏览器会自动缓存图片,相同的图片url不会重新从服务器获取,所以需要更换随机url
this.src = '/genvcode/?rd=' + Math.random()
}
</script>
'''*******************
# App/views.py
注意:
# 随机验证码(4位数字字母)
# 随机颜色
*******************'''
# 验证码
def gen_vcode(request):
# 1. 创建画布
# 第一个参数: RGB颜色
# 第二个参数: 画布大小
# 第三个参数: 画布背景颜色
image = Image.new('RGB', (100, 50), (200, 200, 100))
# 2. 创建画笔
draw = ImageDraw.Draw(image, 'RGB')
# 3. 创建字体样式
font_path = os.path.join(BASE_DIR, 'App/static/fonts/ADOBEARABIC-BOLD.OTF')
font = ImageFont.truetype(font_path, 30)
# 4. 画
# 后台生成的验证码, 保存在session中
vcode = random_code()
request.session['vcode'] = vcode
# 5、设置每位验证码的左右上下位置,并画在画布上
for i in range(len(vcode)):
xy = (15 + i*20, 10 + random.randint(-5, 5))
draw.text(xy, vcode[i], font=font, fill=random_color())
# 6. 转换成图片
buff = io.BytesIO() # 创建一个二进制的内存缓冲区
image.save(buff, 'png') # 将画布转成成png格式的图片,保存到buff缓冲区中
return HttpResponse(buff.getvalue(), 'image/png')
# 将图片二进制从缓冲区中取出,并以指定图片格式显示
# 随机验证码(4位数字字母)
def random_code():
s = "0123456789qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM"
code = ''
for i in range(4):
code += random.choice(s)
return code
# 随机颜色
def random_color():
return random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)
# index
def index(request):
if request.method == 'POST':
username = request.POST.get('username')
# 用户输入的验证码
verify_code = request.POST.get('vcode')
# 获取session中的vcode
vcode = request.session.get('vcode')
# 检测验证码是否匹配(忽略大小写)
if verify_code.upper() == vcode.upper():
return HttpResponse('验证码输入正确!')
else:
return HttpResponse('验证码输入不正确!')
else:
return render(request, 'index.html')
三.富文本
富文本:Rich Text Format(RTF),是由微软开发的跨平台文档格式,大多数的文字处理软件都能读取和保存RTF文档,其实就是可以添加样式的文档,和HTML有很多相似的地方
tinymce 插件
django的插件
pip install django-tinymce
用处大约有两种
1. 在后台管理中使用
2. 在页面中使用,通常用来作博客
1.后台中使用:
配置settings.py文件
INSTALLED_APPS 添加 tinymce 应用
添加默认配置
TINYMCE_DEFAULT_CONFIG = {
'theme':'advanced',
'width':600,
'height':400,
}
创建模型类
from tinymce.models import HTMLField
class Blog(models.Model):
sBlog = HTMLField()
配置站点
admin.site.register
2.在视图中使用:
使用文本域盛放内容
<form>
<textarea></textarea>
</form>
在head中添加script
<script src='/static/tiny_mce/tiny_mce.js'></script>
<script>
tinyMCE.init({
'mode':'textareas', 'theme':'advanced',
'width':800,'height':600,
})
</script>
四. Cache
缓存
django内置了缓存框架,并提供了几种常用的缓存,
django使用统一的api操作各类的缓存系统,类似于orm
- 基于Memcached缓存
- 使用数据库进行缓存
- 使用文件系统进行缓存
- 使用本地内存进行缓存
- 提供缓存扩展接口
1、基于数据库缓存配置
(1)创建缓存表
python manage.py createcachetable [table_name]
例如: python manage.py createcachetable cache_db
(2)缓存配置(settings)
# 在工程目录下的settings加入下面选项
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.db.DatabaseCache', # 缓存引擎
'LOCATION': 'cache_table', # 缓存表
'TIMEOUT': '60',
'OPTIONS': {
'MAX_ENTRIES': '300',
},
'KEY_PREFIX': 'jack', # 缓存记录的前缀
'VERSION': '1',
}
}
(3)缓存使用(两种使用方式)
'''*******************
# 1.在视图中使用(使用最多的场景)
注意:缓存整个视图
@cache_page(time)
缓存整个视图,当浏览器第一次请求该视图时,会等待5s返回;
然后30秒内浏览器再访问,则从缓存中返回给浏览器,不需等待5s
*******************'''
- @cache_page()
参数:
- time 秒 60*5 缓存五分钟
- cache 缓存配置, 默认default,
- key_prefix 前置字符串
@cache_page(30)
def user_list(request):
users = User.objects.all()
template = loader.get_template('userlist.html')
user_list = template.render({'users': users})
time.sleep(5)
return HttpResponse(user_list)
'''*******************
# 2.在视图中使用(使用最多的场景)
注意:缓存模板
获取cache
from django.core.cache import cache
cache = cache['cache_name'] 或 cache = cache.get('cache_name')
设置cache
from django.core.cache import cache
cache.set(key, value, timeout)
*******************'''
def user_list(request):
data = cache.get('user_list')
if data:
return HttpResponse(data)
else:
users = User.objects.all()
template = loader.get_template('userlist.html')
print(template, type(template)) # <class 'django.template.backends.django.Template'>
user_list = template.render({'users': users})
print(user_list, type(user_list)) # <class 'django.utils.safestring.SafeText'>
cache.set('user_list', user_list, 30)
time.sleep(5)
return HttpResponse(user_list)
(4)缓存底层
使用原生缓存来实现
def get_user_list(request):
# 每次从缓存中获取
user_cache = cache.get('user_cache')
# 如果有缓存,则从缓存中直接取
if user_cache:
result = user_cache
# 如果没有缓存,则从数据库中获取
else:
# 模拟长时间的数据操作
user_list = User.objects.all()
time.sleep(5)
data = {
'users': user_list,
}
# 使用模板渲染,得到result文本
template = loader.get_template('App/stu_list.html')
result = template.render(data)
# 设置缓存
cache.set('user_cache', result, 10)
return HttpResponse(result)
2、基于redis数据库缓存配置
# 在工程目录下的settings下面修改选项
# 缓存cache配置
CACHES = {
# 使用数据库表缓存
# 1. 创建缓存表: createcachetable cache_table
# 2. 在settings中配置
# 'default': {
# 'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
# 'LOCATION': 'cache_table',
# 'TIMEOUT': '60',
# 'OPTIONS': {
# 'MAX_ENTRIES': '300',
# },
# 'KEY_PREFIX': 'QF',
# 'VERSION': '1',
# },
# 使用redis缓存
# 1. 安装插件:pip install django_redis
# 2. 在settings中配置
# 3. 启动redis服务: redis-server
# 4. 启动redis客户端: redis-cli , 查看数据库1中的缓存内容.
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/1",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
# "PASSWORD": "密码"
},
'KEY_PREFIX': 'HWM',
}
}
3、缓存可能造成的问题
1、缓存穿透
缓存穿透,是指查询一个数据库一定不存在的数据。正常的使用缓存流程大致是,数据查询先进行缓存查询,如果key不存在或者key已经过期,再对数据库进行查询,并把查询到的对象,放进缓存。如果数据库查询对象为空,则不放进缓存。想象一下这个情况,如果传入的参数key,查询不到数据,就是一定不存在的对象。就会每次都去查询数据库,而每次查询都是空,每次又都不会进行缓存。假如有恶意攻击,就可以利用这个漏洞,对数据库造成压力,甚至压垮数据库。即便是采用UUID,也是很容易找到一个不存在的KEY,进行攻击
解决方案: 如果从数据库查询的对象为空,也放入缓存,只是设定的缓存过期时间较短,比如设置为60秒。
2、缓存雪崩
缓存雪崩,是指在某一个时间段,缓存集中过期失效。
产生雪崩的原因之一,马上就要到双十二零点,很快就会迎来一波抢购,这波商品时间比较集中的放入了缓存,假设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。
在做电商项目的时候,一般是采取不同分类商品,缓存不同周期。在同一分类中的商品,加上一个随机因子。这样能尽可能分散缓存过期时间,而且,热门类目的商品缓存时间长一些,冷门类目的商品缓存时间短一些,也能节省缓存服务的资源。
其实集中过期,倒不是非常致命,比较致命的缓存雪崩,是缓存服务器某个节点宕机或断网。因为自然形成的缓存雪崩,一定是在某个时间段集中创建缓存,那么那个时候数据库能顶住压力,这个时候,数据库也是可以顶住压力的。无非就是对数据库产生周期性的压力而已。而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的,很有可能瞬间就把数据库压垮。
3、缓存击穿
缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。
其实,大多数情况下这种爆款很难对数据库服务器造成压垮性的压力。达到这个级别的公司没有几家的。所以对主打商品都是早早的做好了准备,让缓存永不过期。即便某些商品自己发酵成了爆款,也是直接设为永不过期就好了
五、cors跨域
1.什么是跨域
跨域(跨源)是指浏览器从一个源的网页去请求另一个源,源指的是域名、端口、协议
以下都属于跨域问题
域名:
主域名不同: http://www.baidu.com/index.html –> http://www.sina.com/test.js
子域名不同: http://www.666.baidu.com/index.html –> http://www.555.baidu.com/test.js
域名和域名ip: http://www.baidu.com/index.html –>http://180.149.132.47/test.js
端口:
http://www.baidu.com:8080/index.html–> http://www.baidu.com:8081/test.js
协议:
http://www.baidu.com:8080/index.html–> https://www.baidu.com:8080/test.js
2.为什么要考虑跨域问题
因为Ajax不能跨域, 一旦客户端和服务端的不在一台服务器, 则需要考虑跨域访问的问题
3.同源策略
同源策略是浏览器的一项最为基本同时也是必须遵守的安全策略。
同源策略的存在,限制了“源”自A的脚本只能操作“同源”页面的DOM,“跨源”操作来源于B的页面将会被拒绝。
所谓的“同源”,必须要求相应的URI的域名、端口、协议均是相同的。
4.使用Ajax发送请求的方式
//js
xhr = new XMLHttpRequest();
xhr.open('get', 'http://127.0.0.1:8000/app/getdata/', true);
xhr.send();
xhr.onreadystatechange = function () {
if (xhr.readyState==4 && xhr.status==200){
console.log(JSON.parse(xhr.responseText))
}
}
//jq
$.ajax({
type: "get",
url: 'http://127.0.0.1:8000/app/getdata/',
data: {},
async: true,
success: function (data) {
console.log(data);
},
error: function (e) {
console.log(e);
}
})
// get请求
$.get("http://127.0.0.1:8000/app/getdata/", function (data) {
console.log(data);
});
// post请求 【csrf验证】
$.ajaxSetup({
data:{csrfmiddlewaretoken:'{{ csrf_token }}'}
});
$.post("http://127.0.0.1:8000/app/getdata/", function (data) {
console.log(data);
})
5.解决跨域问题
方式一: 使用 JSONP (一种非Ajax技术,需要前后端同时支持)
'''*******************
# 1.login.html
注意:js写法
*******************'''
<script>
//jq
//使用jsonp: 只能用于get
$.ajax({
url:'http://127.0.0.1:8000/getdata/',
dataType: 'JSONP', //
# 回调函数参数的名称 相当于http://127.0.0.1:8000/getdata/?callback=fn
jsonp: 'callback',
jsonpCallback: 'fn', # 回调函数名称
});
# fn('情人节快乐!') 服务器返回, 浏览器自动调用
function fn(data) {
console.log("----fn--",data)
}
</script>
'''*******************
# view.py
注意:jsonp后端写法
*******************'''
def get_data(request):
# return JsonResponse({'name': '成龙'})
# jsonp后端写法
callback = request.GET.get('callback')
print(callback)
return HttpResponse('%s("情人节快乐!")' % callback) # fn('情人节快乐!')
方式二: 让服务器支持跨域(推荐)
Django支持跨域
安装django-cors-headers
pip install django-cors-headers
配置settings.py文件
INSTALLED_APPS = [
'corsheaders',
]
MIDDLEWARE = (
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
)
# 跨域增加忽略
CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_ALLOW_ALL = True
# 跨域允许的请求方式(可选)
CORS_ALLOW_METHODS = (
'DELETE',
'GET',
'OPTIONS',
'PATCH',
'POST',
'PUT',
)
# 跨域允许的头部参数(可选)
CORS_ALLOW_HEADERS = (
'XMLHttpRequest',
'X_FILENAME',
'accept-encoding',
'authorization',
'content-type',
'dnt',
'origin',
'user-agent',
'x-csrftoken',
'x-requested-with',
'Pragma',
)
六、Celery分布式任务队列
1.celery介绍与简单使用
1.celery介绍:
Celery - 分布式任务队列. Celery 是一个简单、灵活且可靠的, 基于python开发的分布式异步消息任务队列,通过它可以轻松的实现任务的异步处理, 如果你的业务场景中需要用到异步任务,就可以考虑使用celery.
使用场景:
(1).你想执行一个操作,可能会花很长时间,但你不想让你的程序一直等着结果返回,而是想有创建一个任务,这个任务会在其他地方执行,执行完毕后会你拿到结果, 在任务执行过程,你可以继续做其它的事情。
(2).你想做一个定时任务,比如每个星期五发送一条会议通知.
Celery它是一个专注于实时处理的任务队列,同时也支持任务调度.
Celery是基于Python开发的一个分布式任务队列框架,支持使用任务队列的方式在分布的机器/进程/线程上执行任务调度.
2.Celery 主要包含以下几个模块:
(1).任务模块 Task
包含异步任务和定时任务。其中,异步任务通常在业务逻辑中被触发并发往任务队列,而定时任务由 Celery Beat 进程周期性地将任务发往任务队列。
(2).消息中间件 Broker
Broker,即为任务调度队列,接收任务生产者发来的消息(即任务),将任务存入队列。Celery 本身不提供队列服务,官方推荐使用 RabbitMQ 和 Redis 等。
(3).任务执行单元 Worker
Worker 是执行任务的处理单元,它实时监控消息队列,获取队列中调度的任务,并执行它。
(4).任务结果存储 Backend
Backend 用于存储任务的执行结果,以供查询。同消息中间件一样,存储也可使用 RabbitMQ, redis 和 MongoDB 等。
3.安装celery
创建虚拟环境
mkvirtualenv celeryenv
使用pip安装
pip install celery
4.简单使用celery
创建python工程, 然后新建tasks.py文件, 写入以下代码
from celery import Celery
# 创建celery对象,设置任务队列使用redis
app = Celery('tasks', broker='redis://localhost:6379')
# 创建任务
@app.task
def add(a, b):
time.sleep(5) ## 模拟等待超时
n = a + b
print(n)
return n
if __name__ == '__main__':
# add(10, 5)
# 调用任务
add.delay(10, 5)
print('程序执行结束')
5.启动celery服务
linux:
celery -A tasks worker --loglevel=info
windows:
pip install eventlet
celery -A tasks worker --loglevel=info -P eventlet
6.运行tasks.py文件,结果是:先打印--程序执行结束,过5s之后再打印n的值
2、django中使用celery(配合redis与cache)
1、安装相关库
pip install celery
pip install redis
pip install sqlalchemy
'''*******************
# 2.templates/login.html
注意:
*******************'''
<form action="" method="post">
{% csrf_token %}
<p>手机号:<input type="text" name="phone" class="phe"></p>
<p>验证码:<input type="number" name="vcode">
<span id="send">发送短信</span>
</p>
<input type="submit" value="登录">
</form>
'''*******************
# 3、msgpro/celery.py
注意:新增文件celery.py
msgpro为项目名称,要根据具体更改
*******************'''
from __future__ import absolute_import, unicode_literals
import os
from celery import Celery
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'msgpro.settings')
app = Celery('msgpro')
app.config_from_object('django.conf:settings', namespace='CELERY')
app.autodiscover_tasks()
@app.task(bind=True)
def debug_task(self):
print('Request: {0!r}'.format(self.request))
'''*******************
# 4、msgpro/__init__.py
注意:
*******************'''
from __future__ import absolute_import, unicode_literals
# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
from .celery import app as celery_app
__all__ = ('celery_app',)
'''*******************
# 5、msgpro/settings.py
注意:celery 配置,缓存cache配置
*******************'''
# 静态文件
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'),
]
# 媒体文件
MEDIA_ROOT = os.path.join(BASE_DIR, 'static/upload')
# celery 配置
CELERY_BROKER_URL = 'redis://127.0.0.1:6379/0'
CELERY_RESULT_BACKEND = 'db+sqlite:///result.db'
CELERY_ACCEPT_CONTENT = ['json']
CELERY_TASK_SERIALIZER = 'json'
# 缓存cache配置
CACHES = {
# 使用数据库表缓存
# 1. 创建缓存表: createcachetable cache_table
# 2. 在settings中配置
# 'default': {
# 'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
# 'LOCATION': 'cache_table',
# 'TIMEOUT': '60',
# 'OPTIONS': {
# 'MAX_ENTRIES': '300',
# },
# 'KEY_PREFIX': 'QF',
# 'VERSION': '1',
# },
# 使用redis缓存
# 1. 安装插件:pip install django_redis
# 2. 在settings中配置
# 3. 启动redis服务: redis-server
# 4. 启动redis客户端: redis-cli , 查看数据库1中的缓存内容.
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/1",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
# "PASSWORD": "密码"
},
'KEY_PREFIX': 'HWM',
}
}
'''*******************
# 6、app/tasks.py
注意:在app里新增tasks.py
*******************'''
# Create your tasks here
from __future__ import absolute_import, unicode_literals
import random
import requests
from celery import shared_task
from django.core.cache import cache
@shared_task
def send_sms(phone):
url = "http://106.ihuyi.com/webservice/sms.php?method=Submit"
account = "C89751406" # APIID
password = "10f309b164c51af798ad77f794598292" # APIkey
vcode = get_vcode()
content = "您的验证码是:" + vcode + "。请不要把验证码泄露给其他人。"
# 定义请求的头部
headers = {
"Content-type": "application/x-www-form-urlencoded",
"Accept": "text/plain"
}
# 定义请求的数据
data = {
"account": account,
"password": password,
"mobile": phone,
"content": content,
}
# 发起数据
response = requests.post(url, headers=headers, data=data)
# url 请求的地址
# headers 请求头部
# data 请求的数据
# 设置缓存
cache.set(phone, vcode, 300)
print(response.content.decode())
print(response.text)
return response.text # 响应数据
# 随机验证码
def get_vcode():
vcode = ""
for i in range(4):
vcode += str(random.randint(0, 9))
return vcode
'''*******************
# 7、app/views.py
注意:在视图中验证
*******************'''
def login(request):
if request.method == 'GET':
return render(request, 'login.html')
elif request.method == 'POST':
phone = request.POST.get('phone')
vcode = request.POST.get('vcode')
print(phone, vcode)
vcode2 = cache.get(phone)
if vcode == vcode2:
return HttpResponse('登录成功')
else:
return HttpResponse('验证码错误或已过期')
def send_msg(request):
phone = request.GET.get('phone')
send_sms.delay(phone)
print(phone)
return HttpResponse('ok')
8、启动服务(django应用服务、celery服务与redis服务):
django应用服务: python manage.py runserver
celery服务 : celery -A proj worker -l info -P eventlet # 注意: proj是工程名称
启动redis服务 :redis-server
启动redis客户端:redis-cli
七、日志输出
日志的作用
1. 记录程序运行状态
1. 线上环境所有程序以 deamon 形式运行在后台, 无法使用 print 输出程序状态
2. 线上程序无人值守全天候运行, 需要有一种能持续记录程序运行状态的机制, 以便遇到问题后分析处理
2. 记录统计数据
3. 开发时进行 Debug (调试)
简单的使用:
'''*******************
# 1、log/log.py
注意:新建一个python文件
*******************'''
import logging
from logging import handlers
def set_log():
# 设置日志格式
fmt = '%(asctime)s %(levelname)7.7s %(funcName)s: %(message)s'
formatter = logging.Formatter(fmt, datefmt="%Y-%m-%d %H:%M:%S")
# 设置 handler
handler = logging.handlers.TimedRotatingFileHandler('myapp.log', when='D', backupCount=30)
handler.setFormatter(formatter)
# 定义 logger 对象
logger = logging.getLogger("MyApp")
logger.addHandler(handler)
logger.setLevel(logging.INFO)
logger.info("hello")
logger.error("报错了")
if __name__ == '__main__':
set_log()
# 在当前目录下找到myapp.log文件,内容为
# 2019-08-08 11:21:48 INFO <module>: hello
# 2019-08-08 11:23:39 INFO set_log: hello
1. 日志的等级
- DEBUG: 调试信息
- INFO: 普通信息
- WARNING: 警告
- ERROR: 错误
- FATAL: 致命错误
2. 对应函数
- logger.debug(msg)
- logger.info(msg)
- logger.warning(msg)
- logger.error(msg)
- logger.fatal(msg)
3. 日志格式允许的字段
- %(name)s : Logger的名字
- %(levelno)s : 数字形式的日志级别
- %(levelname)s : 文本形式的日志级别
- %(pathname)s : 调用日志输出函数的模块的完整路径名, 可能没有
- %(filename)s : 调用日志输出函数的模块的文件名
- %(module)s : 调用日志输出函数的模块名
- %(funcName)s : 调用日志输出函数的函数名
- %(lineno)d : 调用日志输出函数的语句所在的代码行
- %(created)f : 当前时间, 用UNIX标准的表示时间的浮点数表示
- %(relativeCreated)d : 输出日志信息时的, 自Logger创建以来的毫秒数
- %(asctime)s : 字符串形式的当前时间。默认格式是“2003-07-08 16:49:45,896”。逗号后面的是毫秒
- %(thread)d : 线程ID。可能没有
- %(threadName)s : 线程名。可能没有
- %(process)d : 进程ID。可能没有
- %(message)s : 用户输出的消息
4、django 配置输出日志
'''*******************
# 1、settings.py
注意:在settings.py里加入下面配置,然后
*******************'''
# 日志配置
LOGGING = {
'version': 1,
'disable_existing_loggers': True,
# 格式配置
'formatters': {
'simple': {
'format': '%(asctime)s %(module)s.%(funcName)s: %(message)s',
'datefmt': '%Y-%m-%d %H:%M:%S',
},
'verbose': {
'format': ('%(asctime)s %(levelname)s [%(process)d-%(threadName)s] '
'%(module)s.%(funcName)s line %(lineno)d: %(message)s'),
'datefmt': '%Y-%m-%d %H:%M:%S',
}
},
# Handler 配置
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'level': 'DEBUG' if DEBUG else 'WARNING'
},
'info': {
'class': 'logging.handlers.TimedRotatingFileHandler',
'filename': f'{BASE_DIR}/logs/info.log', # 正常日志保存路径
'when': 'D', # 每天切割日志
'backupCount': 30, # 日志保留 30天
'formatter': 'simple',
'level': 'INFO',
},
'error': {
'class': 'logging.handlers.TimedRotatingFileHandler',
'filename': f'{BASE_DIR}/logs/error.log', # 错误日志保存路径
'when': 'W0', # 每周一切割日志
'backupCount': 4, # 日志保留 4 周
'formatter': 'verbose',
'level': 'WARNING',
}
},
# Logger 配置
'loggers': {
'django': {
'handlers': ['console'],
},
'inf': {
'handlers': ['info'],
'propagate': True,
'level': 'INFO',
},
'err': {
'handlers': ['error'],
'propagate': True,
'level': 'WARNING',
}
}
}
'''*******************
# 5、user/views.py
注意:创建正常日志对象(加载setting中的'inf'配置)
inf_logger
*******************'''
# 日志
import logging
inf_logger = logging.getLogger('inf')
# 注销
def logout(request):
res = redirect(reverse('index'))
# 日志:记录用户登录,退出
inf_logger.info(f'{username}:登录成功!')
return res
'''*******************
# 6、middlewares/LogMiddleware.py
注意:通过aop中间件获取所有错误并记录到文件中
要在settings中注册中间件:
'middlewares.LogMiddleware.ErrorMiddleware',
err_logger = logging.getLogger('err') # 加载settings配置
err_logger.error(exception) # 捕获错误
*******************'''
from django.utils.deprecation import MiddlewareMixin
import logging
err_logger = logging.getLogger('err')
class ErrorMiddleware(MiddlewareMixin):
def process_exception(self, request, exception):
print("=======>ErrorMiddleware=>process_exception")
# 日志:记录异常
err_logger.error(exception)
八、媒体文件云存储
1. 云存储
- 常见的云存储有:亚马逊 S3 服务、阿里云的 OSS 、七牛云 等
2. 七牛云接入
1. 注册七牛云账号
2. 创建存储空间
3. 获取相关配置
- AccessKey
- SecretKey
- Bucket_name
- Bucket_URL
4. 安装 qiniu SDK:pip install qiniu
5. 根据接口文档进行接口封装
6. 按照需要将上传、下载接口封装成异步任务
7. 程序处理流程
1. 用户图片上传服务器
2. 服务器将图片上传到七牛云
3. 将七牛云返回的图片 URL 存入数据库
例:
将用户注册时上传的头像进行云存储
'''*******************
# 1、templates/register.html
注意:模板定义
<p>头像: <input type="file" name="icon"></p>
*******************'''
<form action="{% url 'register' %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
<p>用户名:<input type="text" name="username"></p>
<p>密码:<input type="password" name="password"></p>
<p>年龄:<input type="number" name="age"></p>
<p>头像: <input type="file" name="icon"></p>
<p><input type="submit" value="注册"></p>
</form>
'''*******************
# 2、user/views.py
注意:视图定义register
*******************'''
# 注册
def register(request):
if request.method == 'GET':
# 进入注册页面
return render(request, 'register.html')
elif request.method == 'POST':
# 点击注册
# 接收前端发送过来的数据
username = request.POST.get('username')
password = request.POST.get('password')
age = request.POST.get('age')
icon = request.FILES.get('icon') # 头像
# 注册前先判断:用户是否已经在
users = UserModel.objects.filter(username=username)
if users.exists():
return HttpResponse('用户名已存在!')
# 注册:给UserModel添加一条记录
try:
user = UserModel()
user.username = username
# user.password = hashlib.md5(password.encode()).hexdigest() # md5加密
user.password = make_password(password) # 使用make_pasword
user.age = age
user.save()
# 上传头像到七牛云,并将云服务器头像地址存入数据库
upload_icon(user, icon)
except:
return HttpResponse('注册失败!')
# 注册成功后进入登录页面
return redirect(reverse('login'))
'''*******************
# 3、user/logic.py
注意:云存储函数
user为app
*******************'''
AccessKey = 'fsxMbgIn0_V_9TNtYeju6BjvVIz7_0Sfy1qQd0GY'
SecretKey = 'JmgnEw-WzP3Jg0ynREh5aLmYseXqROXHxfuDtSAj'
Bucket_name = 'sz1904'
Bucket_URL = 'pvwoavyf8.bkt.clouddn.com'
# 云存储
# 1. 先将前端的图片存入本地服务器
def save_icon(user, icon):
file_name = 'icon-%d.png' % user.id # 图片名称
file_path = os.path.join(MEDIA_ROOT, file_name) # 图片路径
with open(file_path, 'ab') as fp:
for part in icon.chunks():
fp.write(part)
fp.flush()
return file_name, file_path
# 2. 然后将本地服务器的图片再上传到七牛云
def upload_to_qiniu(file_name, file_path):
# 构建鉴权对象
q = Auth(AccessKey, SecretKey)
# 生成上传 Token,可以指定过期时间等
token = q.upload_token(Bucket_name, file_name, 3600)
# 上传
ret, info = put_file(token, file_name, file_path)
print("保存到七牛云服务器:")
print(info)
print(ret)
# 上传头像到七牛云
def upload_icon(user, icon):
# 1. 先将前端的图片存入本地服务器
file_name, file_path = save_icon(user, icon)
# 2. 然后将本地服务器的图片再上传到七牛云
upload_to_qiniu(file_name, file_path)
# 3. 将七牛云存储空间中的图片地址存入数据库中
# user.icon = "http://" + Bucket_URL + '/' + file_name
user.icon = os.path.join('http://', Bucket_URL, file_name)
user.save()
九、CBV类视图
'''*******************
# 1、App/views.py
注意:类视图的写法
View:在该类中没有实现任何请求的方法,如果继承该类需要定义各请求的方法以供调用
TemplateView:
在TemplateView已经定义了get方法,指定template_name,
到时会自动回应get请求,其他请求还需定义
ListView:继承BaseListView,有get方法
DetailView:继承BaseDetailView,有get方法
View类 => as_view方法()
在as_view方法()中又定义了一个view()方法,最后返回view() 的引用 (闭包)
在view()里调用dispatch()
在dispatch方法调用自定义的get、post、put等方法,如果未定义则返回405(不允许浏览器以某种方式访问)
*******************'''
# CBV
class Index2(View):
# def get(self, request):
# name = request.GET.get('name')
# return HttpResponse('get=>获取数据:' + name)
def post(self, request):
name = request.POST.get('name')
return HttpResponse('post=>新增数据' + name)
def put(self, request):
return HttpResponse('put=>修改数据')
def delete(self, request):
return HttpResponse('delete=>删除数据')
# TemplateView(在TemplateView已经定义了get方法)
class UserTempalteView(TemplateView):
# get请求
template_name = 'template.html'
def post(self, request):
return HttpResponse('post')
# ListView
class UserListView(ListView):
template_name = 'list.html'
model = UserModel
context_object_name = 'users'
# => return render(request, 'list.html', {'users': UserModel.objects.all()})
# DetailView
class UserDetailView(DetailView):
template_name = 'detail.html'
model = UserModel
context_object_name = 'user'
# 接收url中path路径中的参数
pk_url_kwarg = 'uid'
# 给user动态添加属性
def get_object(self, queryset=None):
obj = super().get_object()
obj.wechat = 'hello'
return obj
# 额外增加渲染数据
def get_context_data(self, **kwargs):
kwargs['qq'] = 1234
return super().get_context_data(**kwargs)
'''*******************
# 2、urls.py
注意:视图的写法
*******************'''
urlpatterns = [
url(r'^admin/', admin.site.urls),
# FBV
url(r'^index/$', index),
# CBV
url(r'^index2/$', Index2.as_view()),
url(r'^template/$', UserTempalteView.as_view()),
url(r'^list/$', UserListView.as_view()),
url(r'^detail/(?P<uid>\d+)/$', UserDetailView.as_view()),
]