Django学习笔记 - 设计简单API

前天研究了一番Django,果然很好使,今天就使用Django设计一个简单API Server试试。

目标

Model类型

以一个旅游日志App为例,我们需要两个Model分别为Trip和Day。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//保存每段旅程相关信息
Trip {
id : AutoField(primary_key=True) //唯一ID,
trip_title : CharField(max_length=100) //标题,
trip_subtitle : CharField(max_length=100) //副标题,
cover : TextField() //封面照片名称
pub_time : DateTimeField('date published') //时间,
place : CharField(max_length=100) //地点,
likeCount : IntegerField(default=0) //赞的数量,
}
/保存每段旅程具体日子的相关信息
Day {
id : AutoField(primary_key=True) //唯一ID,
trip_title : CharField(max_length=100) //标题,
trip_subtitle : CharField(max_length=100) //副标题,
trip_day : IntegerField(default=1) //旅行日期,
cover : TextField() //封面照片名称
pub_time : DateTimeField('date published') //时间,
place : CharField(max_length=100) //地点,
likeCount : IntegerField(default=0) //赞的数量,
content : TextField() //日记内容,
trip : ForeignKey(Trip) //对应的旅程
}

URL分配

HTTP方法 动作 URL
GET 获取Trip列表 http://example.com/api001/trips/
GET 获取具体Trip和包含日程信息 http://example.com/api001/trips/[id]
GET 获取具体含日程信息 http://example.com/api001/trips/[tripID]/[dayID]

实现过程

创建Project和App

首先使用$ django-admin startproject sampleapi创建一个Project,按照Django学习笔记中方法设置数据库。

使用$ python manage.py startapp api001来创建第一版API。并且Project配置文件samplepai/settings.py中增加api001

1
2
3
4
5
6
7
8
9
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'api001'
)

Models

创建Model

编辑sampleapi/api001/models.py文件,增加两个model

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
# encoding: utf-8
from django.db import models

# Create your modelss here.
class Trip(models.Model):
id = models.AutoField(primary_key=True) #唯一ID,
trip_title = models. CharField(max_length=100) #标题,
trip_subtitle = models.CharField(max_length=100) #副标题,
cover = models.TextField() #封面照片名称
pub_time = models.DateTimeField('date published') #时间,
place = models.CharField(max_length=100) #地点,
likeCount = models.IntegerField(default=0) #赞的数量
def __str__(self): # __unicode__ on Python 2
return self.trip_title

class Day(models.Model):
id = models.AutoField(primary_key=True) #唯一ID,
trip_title = models.CharField(max_length=100) #标题,
trip_subtitle = models.CharField(max_length=100) #副标题,
trip_day = models.IntegerField(default=1) #旅行日期,
cover = models.TextField() #封面照片名称
pub_time = models.DateTimeField('date published') #时间,
place = models.CharField(max_length=100) #地点,
likeCount = models.IntegerField(default=0) #赞的数量,
content = models.TextField() #日记内容,
trip = models.ForeignKey(Trip) #对应的旅程
def __str__(self): # __unicode__ on Python 2
return self.trip_title

创建迁移(migrations)文件,如果成功则会产生名称为0001_initial.py的文件。

1
2
3
4
5
6
$ python manage.py makemigrations api001
Migrations for 'api001':
0001_initial.py:
- Create model Day
- Create model Trip
- Add field trip to day
1
2
3
4
$ python manage.py sqlmigrate api001 0001 #产生迁移语句,运行后可以看产生的相应SQL语句。此时并没有真正创建数据库。
$ python manage.py migrate #此时才是真正的创建数据表操作
#此时连接出数据就可以看到生成的api001_day和api001_trip两张表。

增加若干数据

使用shell模式很方便的操作数据和熟悉Django的API,现在我们也用Shell增加几条数据。

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
$ python manage.py shell  #进入shell模式

# 引入数据模型
>>> from api001.models import Trip,Day

# 目前并没有数据
>>> Trip.objects.all()
[]
# 引入时间模块
>>> from django.utils import timezone
# 创建第一个Trip数据
>>> firsttrip = Trip(trip_title = 'trip_title',trip_subtitle = "subtitle", cover = 'notyet.png',pub_time = timezone.now(),place = "I don't know")
# 保存第一个Trip数据
>>> firsttrip.save()

# 读取firsttrip的ID
>>> firsttrip.id

# 读取模型的各个数据
>>> firsttrip.trip_title
'trip_title'
>>> firsttrip.cover
'notyet.png'
>>> firsttrip.pub_time
datetime.datetime(2015, 10, 31, 9, 17, 0, 497149, tzinfo=<UTC>)

##关联元素
# 读取第一个Trip
>>> t = Trip.objects.get(pk=1)

# 增加第一第二天行程
>>> t.day_set.create(trip_title = "day1", trip_day = 1 ,trip_subtitle = "subTitleHere", cover = "notyet.png", pub_time = timezone.now(),place = "I still havar no clue", content = "comments.")
<Day: day1>
>>> t.day_set.create(trip_title = "day2", trip_day = 2 ,trip_subtitle = "subTitleHere", cover = "notyet.png", pub_time = timezone.now(),place = "I still havar no clue", content = "comments.")
<Day: day2>

# 查看t Trip包含的行程
>>> t.day_set.all()
[<Day: day1>, <Day: day2>]

View & URL

配置View

打开api001/views.py,增加下面三行

1
2
3
4
5
6
from django.http import HttpResponse #引入HttpResponse

#定义indexView的动作
def index(request):
return HttpResponse("Hello, world. You're at the api001 index.")

配置URL

为了调用该目录,还需要配置URL路由表,在api001文件夹下面增加一个urls.py文件。此时目录结构应该如下。

1
2
3
4
5
6
7
8
api001/
├── admin.py
├── __init__.py
├── migrations
├── models.py
├── tests.py
├── urls.py
└── views.py

编辑api001/urls.py,增加下面几行

1
2
3
4
5
6
7
8
9
10
from django.conf.urls import url

#引入本目录下的views模块
from . import views

#URL匹配规则,使用正则表达式匹配。
urlpatterns = [
url(r'^$', views.index, name='index'),
]

编辑上级目录中的项目路由表配置文件,即sampleapi/sampleapi/urls.py,增加下面几行

1
2
3
4
5
6
from django.conf.urls import include, url

urlpatterns = [
#引入api001 App中的urls.py文件,并指向api001/ URL路径
url(r'^api001/', include('api001.urls')),
]

此时运行python manager.py 0.0.0.0:8000,然后访问http://xxx.xxx.xxx.xxx:8000/api001/即可看到欢迎提示。说明URL和View配置正常。

1
Hello, world. You're at the api001 index.

完善

前面的工作只是为了学习配置View和URl,现在正式配置View和URL。代码如下。
views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from django.shortcuts import render
from django.http import HttpResponse

# Create your views here.
def index(request):
return HttpResponse("Hello, world. You're at the trips index.")

def tripList(request):
return HttpResponse("This is the view for tripList")

def tripDetail(request,trip_id):
return HttpResponse("This is the view for tripDetail %s." % trip_id)

def tripDayDetail(request,trip_id,day_id):
return HttpResponse("This is the view for tripDayDetail %s-%s." % (trip_id,day_id))

urls.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from django.conf.urls import url

from . import views

urlpatterns = [
# ex: /api001/
url(r'^$', views.index, name='index'),
# ex: /api001/trips/
url(r'^trips/$', views.tripList, name='tripList'),
# ex: /api001/trips/2
url(r'^trips/(?P<trip_id>[0-9]+)/$', views.tripDetail, name='tripDetail'),
# ex: /api001/trips/2/2
url(r'^trips/(?P<trip_id>[0-9]+)/(?P<day_id>[0-9]+)/$', views.tripDayDetail, name='tripDayDetail'),
]

配置完成后可以通过目标里的三种url访问到三个页面并且传递相应的参数。接下来要以Json形式返回数据库中的数据。

返回Json数据

Django中可以使用Django REST framework来生成Json文件。不过自己生成也没什么难度。
Python自带Json函数库能够把字典数据格式化为Json数据,所以首先我们在model中写一个相应的方法,用来把model转换成字典。
以Trip Model为例

models.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
···
class Trip(models.Model):
id = models.AutoField(primary_key=True) #唯一ID,
trip_title = models. CharField(max_length=100) #标题,
trip_subtitle = models.CharField(max_length=100) #副标题,
cover = models.TextField() #封面照片名称
pub_time = models.DateTimeField('date published') #时间,
place = models.CharField(max_length=100) #地点,
likeCount = models.IntegerField(default=0) #赞的数量

def __str__(self):
return self.trip_title

def toDict(self):
#datatime时间不能直接发送,需要先转成时间戳再格式化为字典
tmstp = int(time.mktime(self.pub_time.timetuple()))
return {
'trip_title':self.trip_title,
'trip_subtitle':self.trip_subtitle,
'cover':self.cover,
'place':self.place,
'likeCount':self.likeCount,
'pub_time' : tmstp,
'day_Count' : self.day_set.count() #获取Trip对应的日程数量
}

···

urls.py

1
2
3
4
5
6
7
···
def tripList(request):
all_objs=Trip.objects.all()
all_dicts=toDicts(all_objs)
all_jsons=json.dumps(all_dicts)
return HttpResponse(all_jsons)
···

保存后再次访问http://xxx.xxx.xxx.xxx:8000/api001/trips即可看到相应的Json数据。

1
[{"pub_time": 1446283020, "trip_title": "trip_title", "cover": "notyet.png", "likeCount": 0, "dayCount": 2, "place": "I don't know", "trip_subtitle": "subtitle"}]

进阶

关联查询

第一个API接口完成,接下来的端口需要根据Trip ID获取具体Trip和包含日程信息。可以用一下代码来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#为了防止命名重复,把import json改为import json as JSON
import json as JSON

···
def tripDetail(request,trip_id):
#根据ID获取相应的Trip数据
trip = Trip.objects.get(pk=trip_id)
#把Trip数据转换成字典
tripDic = trip.toDict()
#获取该Trip包含的日程信息
days = trip.day_set.all()
#把日程信息转换成字典里表并加到tripDic中的days字段
tripDic["days"] = toDicts(days)
#格式化输出tripDic文件为JSON字符串
jsonObj = JSON.dumps(tripDic)
return HttpResponse(jsonObj)

···

try-catch

有时候可能会请求一些不存在的数据,这种情况下需要做错误处理。Python自带的try/catch功能就能很好的处理这些错误。

我们首先定义一个函数来优化JSon结构,在models.py增加函数

1
2
3
4
5
6
7
8
def toJson(status,info,dic):
jsonDic = {
'success' : status, #是否成功
'info' : info, #详细信息 - 失败时候提醒用
'data' : dic #数据
}
return JSON.dumps(jsonDic)

tripDetail方法改为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def tripDetail(request,trip_id):
json = ""
try:
#根据ID获取相应的Trip数据
trip = Trip.objects.get(pk=trip_id)
#把Trip数据转换成字典
tripDic = trip.toDict()
#获取该Trip包含的日程信息
days = trip.day_set.all()
#把日程信息转换成字典里表并加到tripDic中的days字段
tripDic["days"] = toDicts(days)
json = toJson(True,"Success",tripDic)

#如果没有数据,则返回空数据和错误信息
#更多详细错误类型参考https://docs.djangoproject.com/en/1.8/ref/exceptions/
except Trip.DoesNotExist:
json = toJson(False,"DoesNotExist",[])
return HttpResponse(json)

Admin Panel

Django自带的Admin panel不需要些几行代码就允许我们很方便的管理数据。
首先设置管理员帐号

1
python manage.py createsuperuser

输入帐号密码邮箱即可。创建帐号后启动服务,打开http://xxx.xxx.xxx.xxx:8000/admin/,然后刚刚创建的帐号登录。可以看到Groups和Users两组数据。

此时我们需要把Trip和Days数据引入到admin panel。编辑api001/admin.py,增加一下语句

1
2
3
4
5
6
# 引入
from .models import Trip, Day

# 注册
admin.site.register(Trip)
admin.site.register(Day)

刷新Admin页面就可以看到Day和Trip数据,并且能够直接在网页进行删增编辑操作。关于Admin Panel可以查阅Writing your first Django app, part 2

参考