Django学习笔记 - 简易用户验证

前文

这两天研究Django,简单入门了Django。相关笔记如下。其中Django REST framework部分没来及的做记录,也很简单,直接看官方档案即可。

今日目标

今天就利用Django authentication systemDjango REST framework做一个允许用户上传图片至七牛的API。用户可以查看所有用户上传图片的信息,但只能编辑和删除自己上传的。

API

HTTP方法 URL 用途 权限
POST http://xxx.xxx.xxx.xxx/api/login/ 登录 所有用户
POST http://xxx.xxx.xxx.xxx/api/getuploadtoken/ 获取七牛上传token 所有登录用户
GET http://xxx.xxx.xxx.xxx/api/getpiclist/ 获取图片列表 所有登录用户
POST http://xxx.xxx.xxx.xxx/api/getpiclist/ 增加图片 所有登录用户
GET http://xxx.xxx.xxx.xxx/api/getpiclist/[PICID] 获取具体图片信息 所有登录用户
POST http://xxx.xxx.xxx.xxx/api/getpiclist/[PICID] 修改或删除具体图片信息 图片所有者
POST http://xxx.xxx.xxx.xxx/api/logout/ 登出 所有用户

初始化

首先按照Django学习笔记创建Project:qiniuApp:api
python manage.py shell进入Shell模式并创建两个用户

1
2
3
4
5
6
7
>>> from django.contrib.auth.models import User
>>> user = User.objects.create_user('john', 'lennon@thebeatles.com', 'john')
>>> user.save()
>>> user2 = User.objects.create_user('tom', 'lennon@thebeatles.com', 'tom')
>>> user2.save()
>>> User.objects.all()
[<User: john>, <User: tom>]

注释掉qiniu/qiniu/setting.py中的

登入View

按照官方Authentication in Web requests章节提供的内容,利用一下代码做登录view。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from django.contrib.auth import authenticate, login
from django.views.decorators.csrf import csrf_exempt
#登录前没有设定的Cookie,所以这里需要设置@csrf_exempt
@csrf_exempt
def login_func(request):
username = request.POST['username']
password = request.POST['password']
user = authenticate(username=username, password=password)
if user is not None:
if user.is_active:
login(request, user)
# Redirect to a success page.
return HttpResponse(u"成功登入,记得保存Cookie")
else:
# Return a 'disabled account' error message
return HttpResponse(u"帐号被禁止")
else:
# Return an 'invalid login' error message.
return HttpResponse(u"信息有误")

如果使用了非POST模式或者POST中没有包含username和password信息则会出现服务器错误,因此我们需要增加try方法和统一规范的错误信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#转化为JSON字符串对的HttpResponse
def responseJson(jsonDic):
return HttpResponse(JSON.dumps(jsonDic))
##登录函数改为
@csrf_exempt
def login_func(request):
ressult = {}
try:
username = request.POST['username']
password = request.POST['password']
user = authenticate(username=username, password=password)
if user is not None:
if user.is_active:
login(request, user)
# Redirect to a success page.
ressult = {
'info' : u"成功登入,记得保存Cookie" ,
'success' : True,
'code': 200
}
return responseJson(ressult)
else:
ressult = {
'info' : u"用户被禁止登录" ,
'success' : False,
'code': 300
}
return responseJson(ressult)
else:
# Return an 'invalid login' error message.
ressult = {
'info' : u"帐号或密码不正确" ,
'success' : False,
'code': 300
}
return responseJson(ressult)
except:
ressult = {
'info' : u"请使用POST请求并包含所有参数" ,
'success' : False,
'code': 400
}
return responseJson(ressult)

登录功能完成,对于其他需要登录的页面只需要在view函数前增加一行即可。

1
2
3
4
5
from django.contrib.auth.decorators import login_required
@login_required(login_url='/login/')
def view(request):
...

获取七牛token

以下是我写的生成七牛token的文件qiniutoken.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# encoding: utf-8
# token生成流程:http://developer.qiniu.com/docs/v6/api/reference/security/upload-token.html
import time
from hashlib import sha1
import hmac
from base64 import urlsafe_b64encode, urlsafe_b64decode,b64encode
AccessKey = 'YOUR-AccessKey'
SecretKey = 'YOUR-SecretKey'
Scope = "Bucket-name" #资源空间(Bucket)
def getUploadPolicyJosn():
deadline = int(time.time()) + 3600
return ('{"scope":"%s","deadline":%s}' % (Scope,deadline))
def encodePolicy(policty):
return urlsafe_b64encode(policty)
#计算HMAC-SHA1签名
def hmac_md5(key, msg):
return hmac.new(key, msg, digestmod=sha1).digest()
def getUploadToken():
uploadPolicy = getUploadPolicyJosn()
encodedPolicy = urlsafe_b64encode(uploadPolicy)
hmac_md5_accessKeyAndPolicty = hmac_md5(SecretKey,encodedPolicy)
encoded_signed = urlsafe_b64encode(hmac_md5_accessKeyAndPolicty)
uploadToken = "%s:%s:%s" %(AccessKey,encoded_signed,encodedPolicy)
return {
'token':uploadToken
}

创建响应的View和URL路由表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#引入七牛token生产函数
from . import qiniu
#urls.py中增加相应的url表
url(r'^getuploadtoken/$', views.getQiniuUploadToken , name='getQiniuUploadToken')
#views.py中增加相应的函数
#此请求需要发送登录获取的cookie,否则会返回302
def getUploadToken():
result = {
'info' : u"成功获取" ,
'success' : True,
'code': 200,
'data': qiniu.getUploadToken()
}
return responseJson(result)

图片列表

首先我们按照Django REST framework规范创建图片Model和Serializer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#models.py中配置Picture model
from django.db import models
# Create your models here.
class Picture(models.Model):
created = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=100, blank=True, default='')
anonymous = models.BooleanField(default=False)
picname = models.CharField(max_length=100)
owner = models.ForeignKey('auth.User', related_name='pictures')
#创建serializer.py 并配置PictureSerializer
from rest_framework import serializers
class PictureSerializer(serializers.ModelSerializer):
class Meta:
model = Picture
owner = serializers.ReadOnlyField(source='owner.username')
fields = ('id', 'title', 'created', 'anonymous', 'picname')

配置好Model和Serializer后更新数据库

1
2
$ python manage.py makemigrations api
$ python manage.py migrate

接下来配置相关View

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#views.py文件中配置
#导入Model和JSon解析相关函数
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
from api.models import Picture
from api.serializers import PictureSerializer
@login_required(login_url='/api/login/')
def picturelist(request):
#Get请求返回数据列表
if request.method == 'GET':
pictures = Picture.objects.all()
serializer = PictureSerializer(pictures, many=True)
result = {
'status':201,
'success':True,
'data':serializer.data
}
return responseJson(result)
#Post请求增加一条记录,所有者为请求发起人。
elif request.method == 'POST':
data = JSONParser().parse(request)
serializer = PictureSerializer(data=data)
if serializer.is_valid():
current_user = request.user
serializer.save(owner=request.user)
result = {
'status':201,
'success':True,
}
return responseJson(result)
result = {
'status':400,
'success':False,
}
return responseJson(result)

编辑具体图片

此时view和model没什么要求改的,主要在view中操作即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
@login_required(login_url='/api/login/')
def picdetail(request,pic_id):
if request.method == 'GET':
try :
pic = Picture.objects.get(pk = pic_id)
serializer = PictureSerializer(pic, many=False)
result = {
'status':201,
'success':True,
'info':'',
'data':serializer.data
}
return responseJson(result)
except Picture.DoesNotExist :
result = {
'status':404,
'success':False,
'info':u'图片不存在',
'data':''
}
return responseJson(result)
result = {
'status':404,
'success':False,
'info':u'未知错误',
'data':''
}
return responseJson(result)
#POST模式的话更新该条目信息
elif request.method == 'POST':
try :
pic = Picture.objects.get(pk = pic_id)
data = JSONParser().parse(request)
#该函数允许部分更新
serializer = PictureSerializer(pic,data=data,partial=True)
if serializer.is_valid():
current_user = request.user
serializer.save()
result = {
'status':201,
'success':True,
}
return responseJson(result)
result = {
'status':400,
'success':False,
}
return responseJson(result)
except Picture.DoesNotExist :
result = {
'status':404,
'success':False,
'info':u'图片不存在',
'data':''
}
return responseJson(result)
result = {
'status':404,
'success':False,
'info':u'未知错误',
'data':''
}
return responseJson(result)

功能都实现了,但是有大量的重复代码。需要进一步优化完善。

优化

之前重复代码太多,特别是返回信息部分,所以重构了一下responseJson函数

1
2
3
4
5
6
7
8
9
10
11
#只传进来需要修改的参数即可
def responseJson(code=201,info="",data={},success = True):
if code != 201 :
success = False
jsonDic = {
'status_code':code,
'info':info,
'data':data,
'success':success
}
return HttpResponse(JSON.dumps(jsonDic))

这样一来代码行数大大缩减,比如前面的编辑和查看具体图片的view从原来的66行缩短为25行,少了近2/3。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def picdetail(request,pic_id):
if request.method == 'GET':
try :
pic = Picture.objects.get(pk = pic_id)
serializer = PictureSerializer(pic, many=False)
return responseJson(data = serializer.data)
except Picture.DoesNotExist :
return responseJson(info = u'图片不存在',code = 404)
return responseJson(info = u'未知错误',code = 404)
elif request.method == 'POST':
try :
pic = Picture.objects.get(pk = pic_id)
data = JSONParser().parse(request)
serializer = PictureSerializer(pic,data=data,partial=True)
if serializer.is_valid():
if pic.id == request.user.id:
serializer.save()
return responseJson(info = u'更新成功')
return responseJson(info = u'无权修改',code=404)
return responseJson(info = u'信息错误',code = 404)
except Picture.DoesNotExist :
return responseJson(info = u'图片不存在',code = 404)
return responseJson(info = u'未知错误',code = 404)