Django拾遗----------长期更新中。。。

Django拾遗

1.事务操作

# 事务操作
    from django.db import transaction
    with transaction.atomic():

2.数据库查询优化

defer('id','name'):取出对象,字段除了id和name都有
only('id','name'):取的对象,只有id和name
如果点,依然能点出其它列,但是不要点了,因为取没有的列,会再次查询数据库

    ret=models.Author.objects.only('id')
    for i in ret:
        # 查询不在的字段,会再次查询数据库,造成数据库压力大
        print(i.name)

3.整体插入

创建对象时,尽可能使用bulk_create()来减少SQL查询的数量。例如:

Entry.objects.bulk_create([
    Entry(headline="Python 3.0 Released"),
    Entry(headline="Python 3.1 Planned")
])
...更优于:

Entry.objects.create(headline="Python 3.0 Released")
Entry.objects.create(headline="Python 3.1 Planned")
注意该方法有很多注意事项,所以确保它适用于你的情况。

这也可以用在ManyToManyFields中,所以:

my_band.members.add(me, my_friend)
...更优于:

my_band.members.add(me)
my_band.members.add(my_friend)
...其中Bands和Artists具有多对多关联。

4.Django时间时区问题

4.1在django1.4以后,存在两个概念

naive time 与 active time

简单点讲,naive time就是不带时区的时间Active time就是带时区的时间

举例来说,使用datetime.datetime.utcnow()、datetime.datetime.now()输出的类似2015-05-11 09:10:33.080451就是不带时区的时间(naive time),

而使用django.util.timezone.now()输出的类似2015-05-11 09:05:19.936835+00:00的时间就是带时区的时间(Active time),其中+00:00表示的就是时区相对性。

另外一个概念UTC时间,UTC时间表示的是格林尼治平均时即可,即零区时间。而北京时间表示的是东八区时间,即UTC+8。

4.2下面列出了几个常见的时区问题

问题一:三个时间datetime.datetime.now()、datetime.datetime.utcnow()与django.util.timezone.now()的区别

datetime.datetime.now():输出的永远是本地时间(naive time)与配置无任任何关系。

datetime.datetime.utcnow():如果setting中配置USE_TZ=True则输出的是UTC时间(naive time),如果setting中配置USE_TZ=False,则该输出时间与datetime.datetime.now()完全相同。

django.util.timezone.now():如果setting中配置USE_TZ=True则输出的是UTC时间(active time),如果配置USE_TZ=False,则与datetime.datetime.now()完全相同。

问题二:django存储到数据库的时间比本地时间小8个小时?

首先要明确的一点,Django1.4版本之前,对时区毫无概概念,对时间的存取、展示不做任何处理,数据库里存储的通常是本地时间,当然都是naive time。

Django在1.4版本之后存储如果设置了USE_TZ=True,则存储到数据库中的时间永远是UTC时间。这时如果settings里面设置了USE_TZ=True与TIME_ZONE = ‘UTC’,用datetime.datetime.now()获取的时间django会把这个时间当成UTC时间存储到数据库中去。如果修改设置为USE_TZ=True与TIME_ZONE = ‘Asia/Shanghai’,用datetime.datetime.now()获取的时间由于不带时区,django会把这个时间当成Asia/Shanghai时间,即东八区时间,然后django会把这个时间转成带时区UTC时间存储到数据库中去,而读的时候直接按UTC时间读出来,这就是网上很多人遇到的存储到数据库中的时间比本地时间会小8个小时的原因。

问题三:DateTimeField role_cost_history.cost_time received a naive datetime (2015-05-12 19:59:01.259517) while time zone support is active?

这个问题是因为如果设置了USE_TZ=True之后,model里面认为DateTimeField使用UTC时间(带时区的时间),这时用datetime.datetime.now()获取的时间是不带时区的就会报这个问题。

问题四:django.util.timezone.now()输出时间比本地时间小8个小时

只要设置了USE_TZ=True,django.util.timezone.now()输出地永远是UTC时间,不管你设置的TIME_ZONE是什么。如果USE_TZ=False,则django.util.timezone.now()输出等同于datetime.datetime.now(),也不管TIME_ZONE设置的是什么。

问题五:模板显示时间

在设置了USE_TZ=True之后,如果设置了TIME_ZONE = ‘Asia/Shanghai’,尽管数据库中存储的是UTC时间,但在模板显示的时候,会转成TIME_ZONE所示的本地时间进行显示。

建议:为了统一时间,在django开发时,尽量使用UTC时间,即设置USE_TZ=True,TIME_ZONE = ‘Asia/Shanghai’,并且在获取时间的时候使用django.util.timezone.now()。因为后台程序使用时间时UTC时间就能满足,也能保证证模板时间的正确显示。

5.Token

基于Token的验证原理

基于Token的身份验证是无状态的,我们不将用户信息存在服务器或Session中。

基于Token的身份验证的过程如下:

1.用户通过用户名和密码发送请求。

2.程序验证。

3.程序返回一个签名的token 给客户端。

4.客户端储存token,并且每次用于每次发送请求。

5.服务端验证token并返回数据。

每一次请求都需要token。token应该在HTTP的头部发送从而保证了Http请求无状态。我们同样通过设置服务器属性Access-Control-Allow-Origin:* ,让服务器能接受到来自所有域的请求。需要主要的是,在ACAO头部标明(designating)*时,不得带有像HTTP认证,客户端SSL证书和cookies的证书。

实现思路:

1.用户登录校验,校验成功后就返回Token给客户端。

2.客户端收到数据后保存在客户端。

3.客户端每次访问API是携带Token到服务器端。

4.服务器端采用filter过滤器校验。校验成功则返回请求数据,校验失败则返回错误码

当我们在程序中认证了信息并取得token之后,我们便能通过这个Token做许多的事情。

我们甚至能基于创建一个基于权限的token传给第三方应用程序,这些第三方程序能够获取到我们的数据(当然只有在我们允许的特定的token)

Tokens的优势

无状态、可扩展

在客户端存储的Tokens是无状态的,并且能够被扩展。基于这种无状态和不存储Session信息,负载负载均衡器能够将用户信息从一个服务传到其他服务器上。

如果我们将已验证的用户的信息保存在Session中,则每次请求都需要用户向已验证的服务器发送验证信息(称为Session亲和性)。用户量大时,可能会造成

一些拥堵。

但是不要着急。使用tokens之后这些问题都迎刃而解,因为tokens自己hold住了用户的验证信息。

安全性

请求中发送token而不再是发送cookie能够防止CSRF(跨站请求伪造)。即使在客户端使用cookie存储token,cookie也仅仅是一个存储机制而不是用于认证。不将信息存储在Session中,让我们少了对session操作。

token是有时效的,一段时间之后用户需要重新验证。我们也不一定需要等到token自动失效,token有撤回的操作,通过token revocataion可以使一个特定的token或是一组有相同认证的token无效。

可扩展性

Tokens能够创建与其它程序共享权限的程序。例如,能将一个随便的社交帐号和自己的大号(Fackbook或是Twitter)联系起来。当通过服务登录Twitter(我们将这个过程Buffer)时,我们可以将这些Buffer附到Twitter的数据流上(we are allowing Buffer to post to our Twitter stream)。

使用tokens时,可以提供可选的权限给第三方应用程序。当用户想让另一个应用程序访问它们的数据,我们可以通过建立自己的API,得出特殊权限的tokens。

多平台跨域

就CORS(跨域资源共享),对应用程序和服务进行扩展的时候,需要介入各种各种的设备和应用程序。

只要用户有一个通过了验证的token,数据和资源就能够在任何域上被请求到。

          Access-Control-Allow-Origin: *       

代码部分

  • 不存数据库的token验证(在rest_framework中使用)
def get_token(id,salt='123'):
    import hashlib
    md=hashlib.md5()
    md.update(bytes(str(id),encoding='utf-8'))
    md.update(bytes(salt,encoding='utf-8'))

    return md.hexdigest()+'|'+str(id)

def check_token(token,salt='123'):
    ll=token.split('|')
    import hashlib
    md=hashlib.md5()
    md.update(bytes(ll[-1],encoding='utf-8'))
    md.update(bytes(salt,encoding='utf-8'))
    if ll[0]==md.hexdigest():
        return True
    else:
        return False

class TokenAuth():
    def authenticate(self, request):
        token = request.GET.get('token')
        success=check_token(token)
        if success:
            return
        else:
            raise AuthenticationFailed('认证失败')
    def authenticate_header(self,request):
        pass
class Login(APIView):
    def post(self,reuquest):
        back_msg={'status':1001,'msg':None}
        try:
            name=reuquest.data.get('name')
            pwd=reuquest.data.get('pwd')
            user=models.User.objects.filter(username=name,password=pwd).first()
            if user:
                token=get_token(user.pk)
                # models.UserToken.objects.update_or_create(user=user,defaults={'token':token})
                back_msg['status']='1000'
                back_msg['msg']='登录成功'
                back_msg['token']=token
            else:
                back_msg['msg'] = '用户名或密码错误'
        except Exception as e:
            back_msg['msg']=str(e)
        return Response(back_msg)
from rest_framework.authentication import BaseAuthentication
class TokenAuth():
    def authenticate(self, request):
        token = request.GET.get('token')
        token_obj = models.UserToken.objects.filter(token=token).first()
        if token_obj:
            return
        else:
            raise AuthenticationFailed('认证失败')
    def authenticate_header(self,request):
        pass

class Course(APIView):
    authentication_classes = [TokenAuth, ]

    def get(self, request):
        return HttpResponse('get')

    def post(self, request):
        return HttpResponse('post')
  • token验证码存入redis中验证
import hashlib
import time
from cmdbserver import settings
from django_redis import get_redis_connection

class HashView():
    '''
    加密数据类
    '''
    def __init__(self):
        pass

    def hash_data(self, token):
        # 这个token是客户端发送请求带过来的,通过settings.SECRET_KEY+客户端时间混合加密的,类似下			面这样的:
        # token = sfqrhfqorhoqhroh|1547251055.6501918
        key,client_time = token.split('|')
        server_time = time.time()
        content = '%s|%s' % (settings.SECRET_KEY, client_time)
        # settings.SECRET_KEY 是Django的settings配置自带的密钥,可用来加密用
        m = hashlib.md5()
        m.update(bytes(content, encoding='utf-8'))
        data = m.hexdigest()
        # print(data)
        
		# token时效只有10秒,超过10秒需要重新发送请求,带新的token过来
        if float(server_time) - float(client_time)<=10:
            # print('通过第一关')
            res, msg = self.second_verification(token,key,data)
            return res,msg
        else:
            # print('时间过长')
            return False,'时间过长'


    # 第二层验证,验证token是否一样
    def second_verification(self,token,key,data):
        if key == data:
            # print('通过第二关')
            res, msg = self.last_verification(token)
            return res,msg
        else:
            # print('令牌错误')
            return False,'令牌错误'

    # 第三次验证,验证redis,一个token只允许使用一次
    def last_verification(self,token):
        try:
            conn = get_redis_connection('default')
            res = conn.get(name=token)
            if not res:
                conn.set(name=token,value='true',ex=10)
                # print('验证通过')
                return True,'验证通过'
            else:
                # print('该token已经用过了')
                return False,'该token已经用过了'
        except Exception as e:
            print(str(e))


6.importlib

importlib是一个可以根据字符串的模块名实现动态导入模块的库。

举个例子:

目录结构:

├── aaa.py
├── bbb.py
└── mypackage
    ├── __init__.py
    └── xxx.py

使用importlib动态导入模块:

bbb.py文件

import importlib

func = importlib.import_module('aaa')
print(func)
func.f1()

m = importlib.import_module('mypackage.xxx')
print(m.age)

猜你喜欢

转载自blog.csdn.net/qq_42721964/article/details/86497884