day05form验证、验证码、cache、cors与celery(任务队列)、CBV

一. 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()),

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值