• Django:之中间件、微信接口和单元测试


    Django中间件

    我们从浏览器发出一个请求 Request,得到一个响应后的内容 HttpResponse ,这个请求传递到 Django的过程如下:

    也就是说,每一个请求都是先通过中间件中的 process_request 函数,这个函数返回 None 或者 HttpResponse 对象,如果返回前者,继续处理其它中间件,如果返回一个 HttpResponse,就处理中止,返回到网页上。

    中间件不用继承自任何类(可以继承 object ),下面一个中间件大概的样子:

    class CommonMiddleware(object):
        def process_request(self, request):
            return None
     
        def process_response(self, request, response):
            return response
    

    还有process_view,process_exception和process_template_response函数。

    一、比如我们要做一个拦截器,发生有恶意访问网站的人,就拦截它!

    假如我们通过一种技术,比如统计一分钟访问页面数,太多酒把他的ip加入到黑名单BLOCKED_IPS(这部分没有提供代码,主要讲中间件部分)

    #项目 wulaoer 文件名 wulaoer/middleware.py
     
    class BlockedIpMiddleware(object):
        def process_request(self, request):
            if request.META['REMOTE_ADDR'] in getattr(settings, "BLOCKED_IPS", []):
                return http.HttpResponseForbidden('<h1>Forbidden</h1>')
    

    这里的代码的功能就是获取当前访问者的IP(request.META['REMOTE_ADDR']),如果这个IP在黑名单中就拦截,如果不在就返回None(函数中没有返回值其实就是默认为None),把这个中间件的Python路径写到settings.py中

    MIDDLEWARE_CLASSES = (
        'wulaoer.middleware.BlockedIpMiddleware',
        ...其它的中间件
    )
    

    Django会从MIDDLEWARE_CLASSES中按照从上到下到顺序一个个执行中间件中的process_request函数,而其中process_response函数则是最前面的最后执行。

    二、在比如,我们在网站放到服务器上正式运行后,DEBUG改为了False ,这样更安全,但是有时候发生错误不能显示错误详情页面,有没有办法处理好这两个事情呢?

    1、普通访问者看到的是友好的报错信息

    2、管理员看到的是错误详情,以便修复BUG

    当然可以有,利用中间件就可以做到!代码如下:

    import sys
    from django.views.debug import technical_500_response
    from django.conf import settings
     
    class UserBasedExceptionMiddleware(object):
        def process_exception(self, request, exception):
            if request.user.is_superuser or request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS:
                return technical_500_response(request, *sys.exc_info())
    

    把这个中间件像上面一样,加到你的 settings.py 中的 MIDDLEWARE_CLASSES 中,可以放到最后,这样可以看到其它中间件的 process_request的错误。

    当访问者为管理员时,就给出错误详情,比如访问本站的不存在的页面:http://localhost/admin/  

    普通人看到的是普通的 404(自己点开看看),而我可以看到:

    三,分享一个简单的识别手机的中间件,更详细的可以参考这个:django-mobi 或 django-mobile

    MOBILE_USERAGENTS = ("2.0 MMP","240x320","400X240","AvantGo","BlackBerry",
        "Blazer","Cellphone","Danger","DoCoMo","Elaine/3.0","EudoraWeb",
        "Googlebot-Mobile","hiptop","IEMobile","KYOCERA/WX310K","LG/U990",
        "MIDP-2.","MMEF20","MOT-V","NetFront","Newt","Nintendo Wii","Nitro",
        "Nokia","Opera Mini","Palm","PlayStation Portable","portalmmm","Proxinet",
        "ProxiNet","SHARP-TQ-GX10","SHG-i900","Small","SonyEricsson","Symbian OS",
        "SymbianOS","TS21i-10","UP.Browser","UP.Link","webOS","Windows CE",
        "WinWAP","YahooSeeker/M1A1-R2D2","iPhone","iPod","Android",
        "BlackBerry9530","LG-TU915 Obigo","LGE VX","webOS","Nokia5800")
     
    class MobileTemplate(object):
        """
        If a mobile user agent is detected, inspect the default args for the view 
        func, and if a template name is found assume it is the template arg and 
        attempt to load a mobile template based on the original template name.
        """
     
        def process_view(self, request, view_func, view_args, view_kwargs):
            if any(ua for ua in MOBILE_USERAGENTS if ua in 
                request.META["HTTP_USER_AGENT"]):
                template = view_kwargs.get("template")
                if template is None:
                    for default in view_func.func_defaults:
                        if str(default).endswith(".html"):
                            template = default
                if template is not None:
                    template = template.rsplit(".html", 1)[0] + ".mobile.html"
                    try:
                        get_template(template)
                    except TemplateDoesNotExist:
                        pass
                    else:
                        view_kwargs["template"] = template
                        return view_func(request, *view_args, **view_kwargs)
            return None
    

    参考文档:https://docs.djangoproject.com/en/1.8/topics/http/middleware/

    Python/Django微信接口

    填写相应的网址,Token(令牌) 是随便写的,你自己想写什么就写什么,微信验证时检验是否写的和服务器上的TOKEN一样,一样则通过。

    关注一下吴老二的微信号吧,可以随时随地查阅教程哦,体验一下自强学堂的微信的各种功能再阅读效果更佳!

    自己动手写微信的验证: views.py

    #coding=utf-8
    import hashlib
    import json
    from lxml import etree
    from django.utils.encoding import smart_str
    from django.views.decorators.csrf import csrf_exempt
    from django.http import HttpResponse
    from auto_reply.views import auto_reply_main # 修改这里
     
    WEIXIN_TOKEN = 'write-a-value'
     
    @csrf_exempt
    def weixin_main(request):
        """
        所有的消息都会先进入这个函数进行处理,函数包含两个功能,
        微信接入验证是GET方法,
        微信正常的收发消息是用POST方法。
        """
        if request.method == "GET":
            signature = request.GET.get("signature", None)
            timestamp = request.GET.get("timestamp", None)
            nonce = request.GET.get("nonce", None)
            echostr = request.GET.get("echostr", None)
            token = WEIXIN_TOKEN
            tmp_list = [token, timestamp, nonce]
            tmp_list.sort()
            tmp_str = "%s%s%s" % tuple(tmp_list)
            tmp_str = hashlib.sha1(tmp_str).hexdigest()
            if tmp_str == signature:
                return HttpResponse(echostr)
            else:
                return HttpResponse("weixin  index")
        else:
            xml_str = smart_str(request.body)
            request_xml = etree.fromstring(xml_str)
            response_xml = auto_reply_main(request_xml)# 修改这里
            return HttpResponse(response_xml)
    

    auto_reply_main 是用来处理消息,回复消息的,需要自己进一步完善。

    使用第三方包实现:

    关于Django开发微信,有已经做好的现在的包可以使用 wechat_sdk 这个包,使用文档 也比较完善,但是在处理加密一部分没有做,在微信公众平台上,需要用明文验证,如果要加密,自己参照微信官网的加密算法。

    使用 wechat_sdk 的例子(吴老二微信号简化后的例子):

    # -*- coding: utf-8 -*-
    from __future__ import unicode_literals
     
    from django.http.response import HttpResponse, HttpResponseBadRequest
    from django.views.decorators.csrf import csrf_exempt
     
    from wechat_sdk import WechatBasic
    from wechat_sdk.exceptions import ParseError
    from wechat_sdk.messages import TextMessage
     
     
    WECHAT_TOKEN = 'zqxt'
    AppID = ''
    AppSecret = ''
     
    # 实例化 WechatBasic
    wechat_instance = WechatBasic(
        token=WECHAT_TOKEN,
        appid=AppID,
        appsecret=AppSecret
    )
     
    @csrf_exempt
    def index(request):
        if request.method == 'GET':
            # 检验合法性
            # 从 request 中提取基本信息 (signature, timestamp, nonce, xml)
            signature = request.GET.get('signature')
            timestamp = request.GET.get('timestamp')
            nonce = request.GET.get('nonce')
     
            if not wechat_instance.check_signature(
                    signature=signature, timestamp=timestamp, nonce=nonce):
                return HttpResponseBadRequest('Verify Failed')
     
            return HttpResponse(
                request.GET.get('echostr', ''), content_type="text/plain")
     
     
        # 解析本次请求的 XML 数据
        try:
            wechat_instance.parse_data(data=request.body)
        except ParseError:
            return HttpResponseBadRequest('Invalid XML Data')
     
        # 获取解析好的微信请求信息
        message = wechat_instance.get_message()
     
        # 关注事件以及不匹配时的默认回复
        response = wechat_instance.response_text(
            content = (
                '感谢您的关注!
    回复【功能】两个字查看支持的功能,还可以回复任意内容开始聊天'
                '
    【<a href="http://www.ziqiangxuetang.com">自强学堂手机版</a>】'
                ))
        if isinstance(message, TextMessage):
            # 当前会话内容
            content = message.content.strip()
            if content == '功能':
                reply_text = (
                        '目前支持的功能:
    1. 关键词后面加上【教程】两个字可以搜索教程,'
                        '比如回复 "Django 后台教程"
    '
                        '2. 回复任意词语,查天气,陪聊天,讲故事,无所不能!
    '
                        '还有更多功能正在开发中哦 ^_^
    '
                        '【<a href="http://www.ziqiangxuetang.com">自强学堂手机版</a>】'
                    )
            elif content.endswith('教程'):
                reply_text = '您要找的教程如下:'
     
            response = wechat_instance.response_text(content=reply_text)
     
        return HttpResponse(response, content_type="application/xml")
    

    下面是一个更详细复杂的使用例子:

    models.py

    # -*- coding: utf-8 -*-
    from __future__ import unicode_literals
     
    from django.db import models
     
     
    class KeyWord(models.Model):
        keyword = models.CharField(
            '关键词', max_length=256, primary_key=True, help_text='用户发出的关键词')
        content = models.TextField(
            '内容', null=True, blank=True, help_text='回复给用户的内容')
     
        pub_date = models.DateTimeField('发表时间', auto_now_add=True)
        update_time = models.DateTimeField('更新时间', auto_now=True, null=True)
        published = models.BooleanField('发布状态', default=True)
     
        def __unicode__(self):
            return self.keyword
     
        class Meta:
            verbose_name='关键词'
            verbose_name_plural=verbose_name
    

    views.py

    # -*- coding: utf-8 -*-
    from __future__ import unicode_literals
     
    from django.http.response import HttpResponse, HttpResponseBadRequest
    from django.views.decorators.csrf import csrf_exempt
     
    from wechat_sdk import WechatBasic
    from wechat_sdk.exceptions import ParseError
    from wechat_sdk.messages import (TextMessage, VoiceMessage, ImageMessage,
        VideoMessage, LinkMessage, LocationMessage, EventMessage
    )
     
    from wechat_sdk.context.framework.django import DatabaseContextStore
    from .models import KeyWord as KeyWordModel
     
     
    # 实例化 WechatBasic
    wechat_instance = WechatBasic(
        token='zqxt',
        appid='xx',
        appsecret='xx'
    )
     
     
    @csrf_exempt
    def index(request):
        if request.method == 'GET':
            # 检验合法性
            # 从 request 中提取基本信息 (signature, timestamp, nonce, xml)
            signature = request.GET.get('signature')
            timestamp = request.GET.get('timestamp')
            nonce = request.GET.get('nonce')
     
            if not wechat_instance.check_signature(
                    signature=signature, timestamp=timestamp, nonce=nonce):
                return HttpResponseBadRequest('Verify Failed')
     
            return HttpResponse(
                request.GET.get('echostr', ''), content_type="text/plain")
     
        # POST
        # 解析本次请求的 XML 数据
        try:
            wechat_instance.parse_data(data=request.body)
        except ParseError:
            return HttpResponseBadRequest('Invalid XML Data')
     
        # 获取解析好的微信请求信息
        message = wechat_instance.get_message()
        # 利用本次请求中的用户OpenID来初始化上下文对话
        context = DatabaseContextStore(openid=message.source)
     
        response = None
     
        if isinstance(message, TextMessage):
            step = context.get('step', 1)  # 当前对话次数,如果没有则返回 1
            # last_text = context.get('last_text')  # 上次对话内容
            content = message.content.strip()  # 当前会话内容
     
            if message.content == '新闻':
                response = wechat_instance.response_news([
                    {
                        'title': '自强学堂',
                        'picurl': 'http://www.ziqiangxuetang.com/static/images/newlogo.png',
                        'description': '自强学堂致力于提供优质的IT技术教程, 网页制作,服务器后台编写,以及编程语言,如HTML,JS,Bootstrap,Python,Django。同时也提供大量在线实例,通过实例,学习更容易,更轻松。',
                        'url': 'http://www.ziqiangxuetang.com',
                    }, {
                        'title': '百度',
                        'picurl': 'http://doraemonext.oss-cn-hangzhou.aliyuncs.com/test/wechat-test.jpg',
                        'url': 'http://www.baidu.com',
                    }, {
                        'title': 'Django 教程',
                        'picurl': 'http://www.ziqiangxuetang.com/media/uploads/images/django_logo_20140508_061519_35.jpg',
                        'url': 'http://www.ziqiangxuetang.com/django/django-tutorial.html',
                    }
                ])
                return HttpResponse(response, content_type="application/xml")
     
            else:
                try:
                    keyword_object = KeyWordModel.objects.get(keyword=content)
                    reply_text = keyword_object.content
                except KeyWordModel.DoesNotExist:
                    try:
                        reply_text = KeyWordModel.objects.get(keyword='提示').content
                    except KeyWordModel.DoesNotExist:
                        reply_text = ('/:P-(好委屈,数据库翻个遍也没找到你输的关键词!
    '
                            '试试下面这些关键词吧:
    KEYWORD_LIST
    '
                            '<a href="http://www.rxnfinder.org">RxnFinder</a>'
                            '感谢您的支持!/:rose'
                            )
     
            # 将新的数据存入上下文对话中
            context['step'] = step + 1
            context['last_text'] = content
            context.save()  # 非常重要!请勿忘记!临时数据保存入数据库!
     
            if 'KEYWORD_LIST' in reply_text:
                keyword_objects = KeyWordModel.objects.exclude(keyword__in=[
                    '关注事件', '测试', 'test', '提示']).filter(published=True)
                keywords = ('{}. {}'.format(str(i), k.keyword)
                            for i, k in enumerate(keyword_objects, 1))
                reply_text = reply_text.replace(
                    'KEYWORD_LIST', '
    '.join(keywords))
     
            # 文本消息结束
     
        elif isinstance(message, VoiceMessage):
            reply_text = '语音信息我听不懂/:P-(/:P-(/:P-('
        elif isinstance(message, ImageMessage):
            reply_text = '图片信息我也看不懂/:P-(/:P-(/:P-('
        elif isinstance(message, VideoMessage):
            reply_text = '视频我不会看/:P-('
        elif isinstance(message, LinkMessage):
            reply_text = '链接信息'
        elif isinstance(message, LocationMessage):
            reply_text = '地理位置信息'
        elif isinstance(message, EventMessage):  # 事件信息
            if message.type == 'subscribe':  # 关注事件(包括普通关注事件和扫描二维码造成的关注事件)
                follow_event = KeyWordModel.objects.get(keyword='关注事件')
                reply_text = follow_event.content
     
                # 如果 key 和 ticket 均不为空,则是扫描二维码造成的关注事件
                if message.key and message.ticket:
                    reply_text += '
    来源:扫描二维码关注'
                else:
                    reply_text += '
    来源:搜索名称关注'
            elif message.type == 'unsubscribe':
                reply_text = '取消关注事件'
            elif message.type == 'scan':
                reply_text = '已关注用户扫描二维码!'
            elif message.type == 'location':
                reply_text = '上报地理位置'
            elif message.type == 'click':
                reply_text = '自定义菜单点击'
            elif message.type == 'view':
                reply_text = '自定义菜单跳转链接'
            elif message.type == 'templatesendjobfinish':
                reply_text = '模板消息'
     
        response = wechat_instance.response_text(content=reply_text)
        return HttpResponse(response, content_type="application/xml")
    

    Django单元测试

    Django一系列教程,前面的例子都是我们写好代码后,运行开发服务器,在浏览器上自己点击测试,看写的代码是否正常,但是这样做很麻烦,因为以后如果有改动,可能会影响以前本来正常的功能,这样以前的功能又得测试一遍,非常不方便,Django中有完善的单元测试,我们可以对开发的每一个功能进行单元测试,这样只要运行一个命令 python manage.py test,就可以测试功能是否正常。  

    一言以蔽之,测试就是检查代码是否按照自己的预期那样运行。

    测试驱动开发: 有时候,我们知道自己需要的功能(结果),并不知道代码如何书写,这时候就可以利用测试驱动开发(Test Driven Development),先写出我们期待得到的结果(把测试代码先写出来),再去完善代码,直到不报错,我们就完成了。

    《改善Python的91个建议》一书中说:单元测试绝不是浪费时间的无用功,它是高质量代码的保障之一,在软件开发的一节中值得投入精力和时间去把好这一关。 

    1. Python 中 单元测试简介:

    下面是一个 Python的单元测试简单的例子: 

    假如我们开发一个除法的功能,有的同学可能觉得很简单,代码是这样的:

    def division_funtion(x, y):
        return x / y
    

    但是这样写究竟对还是不对呢,有些同学可以在代码下面这样测试:

    def division_funtion(x, y):
        return x / y
     
     
    if __name__ == '__main__':
        print division_funtion(2, 1)
        print division_funtion(2, 4)
        print division_funtion(8, 3)
    

    但是这样运行后得到的结果,自己每次都得算一下去核对一遍,很不方便,Python中有 unittest 模块,可以很方便地进行测试,详情可以文章最后的链接,看官网文档的详细介绍。

    下面是一个简单的示例:

    import unittest
     
     
    def division_funtion(x, y):
        return x / y
     
     
    class TestDivision(unittest.TestCase):
        def test_int(self):
            self.assertEqual(division_funtion(9, 3), 3)
     
        def test_int2(self):
            self.assertEqual(division_funtion(9, 4), 2.25)
     
        def test_float(self):
            self.assertEqual(division_funtion(4.2, 3), 1.4)
     
     
    if __name__ == '__main__':
        unittest.main()
    

    我简单地写了三个测试示例(不一定全面,只是示范,比如没有考虑除数是0的情况),运行后发现:

    F.F
    ======================================================================
    FAIL: test_float (__main__.TestDivision)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "/Users/tu/YunPan/mydivision.py", line 16, in test_float
        self.assertEqual(division_funtion(4.2, 3), 1.4)
    AssertionError: 1.4000000000000001 != 1.4
     
    ======================================================================
    FAIL: test_int2 (__main__.TestDivision)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "/Users/tu/YunPan/1.py", line 13, in test_int2
        self.assertEqual(division_funtion(9, 4), 2.25)
    AssertionError: 2 != 2.25
     
    ----------------------------------------------------------------------
    Ran 3 tests in 0.001s
     
    FAILED (failures=2)
    

    汗!发现了没,竟然两个都失败了,测试发现:

    4.2除以3 等于 1.4000000000000001 不等于期望值 1.4

    9除以4等于2,不等于期望的 2.25

    下面我们就是要修复这些问题,再次运行测试,直到运行不报错为止。

    譬如根据实际情况,假设我们只需要保留到小数点后6位,可以这样改:

    def division_funtion(x, y):
        return round(float(x) / y, 6)
    

    再次运行就不报错了:

    ...
    ----------------------------------------------------------------------
    Ran 3 tests in 0.000s
     
    OK
    

    Python 单元测试 官方文档:

    Python 2 (https://docs.python.org/2/library/unittest.html

    Python 3 (https://docs.python.org/3/library/unittest.html)

    2. Django 中 单元测试:(不断完善中,后期会增加对前面讲解的内容的测试)

    2.1 简单测试例子:

    from django.test import TestCase
    from myapp.models import Animal
     
     
    class AnimalTestCase(TestCase):
        def setUp(self):
            Animal.objects.create(name="lion", sound="roar")
            Animal.objects.create(name="cat", sound="meow")
     
        def test_animals_can_speak(self):
            """Animals that can speak are correctly identified"""
            lion = Animal.objects.get(name="lion")
            cat = Animal.objects.get(name="cat")
            self.assertEqual(lion.speak(), 'The lion says "roar"')
            self.assertEqual(cat.speak(), 'The cat says "meow"')
    

    这个例子是测试myapp.models 中的 Animal 类相关的方法功能。

    2.2 用代码访问网址的方法:

    >>> from django.test import Client
    >>> c = Client()
    >>> response = c.post('/login/', {'username': 'john', 'password': 'smith'})
    >>> response.status_code
    200
    >>> response = c.get('/customer/details/')
    >>> response.content
    '<!DOCTYPE html...'
    

    我们可以用 django.test.Client 的实例来实现 get 或 post 内容,检查一个网址返回的网页源代码。

    默认情况下CSRF检查是被禁用的,如果测试需要,可以用下面的方法:

    >>> from django.test import Client
    >>> csrf_client = Client(enforce_csrf_checks=True)
    

    使用 csrf_client 这个实例进行请求即可。

    指定浏览USER-AGENT:

    >>> c = Client(HTTP_USER_AGENT='Mozilla/5.0')
    

    模拟post上传附件:

    from django.test import Client
    c = Client()
     
    with open('wishlist.doc') as fp:
        c.post('/customers/wishes/', {'name': 'fred', 'attachment': fp})
    

    测试网页返回状态: 

    from django.test import TestCase
     
    class SimpleTest(TestCase):
        def test_details(self):
            response = self.client.get('/customer/details/')
            self.assertEqual(response.status_code, 200)
     
        def test_index(self):
            response = self.client.get('/customer/index/')
            self.assertEqual(response.status_code, 200)
    

    我们用 self.client 即可,不用 client = Client() 这样实例化,更方便,我们还可以继承 Client,添加一些其它方法:  

    from django.test import TestCase, Client
     
    class MyTestClient(Client):
        # Specialized methods for your environment
        ...
         
    class MyTest(TestCase):
        client_class = MyTestClient
     
        def test_my_stuff(self):
            # Here self.client is an instance of MyTestClient...
            call_some_test_code()
    

    定制 self.client 的方法:

    from django.test import Client, TestCase
     
     
    class MyAppTests(TestCase):
        def setUp(self):
            super(MyAppTests, self).setUp()
            self.client = Client(enforce_csrf_checks=True)
     
        def test_home(self):
            response = self.client.get('/')
            self.assertEqual(response.status_code, 200)
    

      

  • 相关阅读:
    hive 总结一
    常见排序算法
    HBase 入门
    Spark:三种任务提交流程standalone、yarn-cluster、yarn-client
    YARN 原理简介
    Vue中v-show和v-if的使用以及区别
    JFinal Enjoy指令扩展管理常用文本模板
    JFinalSwagger插件
    layui table 表头和内容数据不能对齐
    start.sh在linux下启动报错 Can't connect to any repository: ,cannot open git-receive-pack
  • 原文地址:https://www.cnblogs.com/wulaoer/p/5397346.html
Copyright © 2020-2023  润新知