tastypie使用

1 首先就是按照常规流程创建虚拟环境,并且在虚拟环境中创建了Django项目。

2 然后安装django-tastypie

$ pip install django-tastypie

3 创建文件expenses/models.py,用来存放和tastypie相关的一些东西。

假设我们的项目是用来解决一些关于一个叫Expense的资源的示例。我们就在expenses/models.py文件里面创建一个模型类如下:

class Expense(models.Model):
    description = models.CharField(max_length=100)
    amount = models.IntegerField()

运行迁移命令:

python manage.py makemigrations
python manage.py migrate

稍后我们会为开支增加一个相关联的用户字段,现在我们先将这个工作放在一边。
我们就先为我们的Expense 创建一些实例:

Expense.objects.create(description='Ate pizza', amount=100)
Expense.objects.create(description='Went to Cinema', amount=200)

4 接下来我们就想要通过一个url去获取所有的expenses对象,这个url是
http://localhost:8000/api/expenses/”.

我们就在expenses/api.py里面新建一个类ExpenseResource 去继承 ModelResource。

from tastypie.resources import ModelResource

from .models import Expense

class ExpenseResource(ModelResource):

    class Meta:
        queryset = Expense.objects.all()
        resource_name = 'expense'

然后我们需要做的是在tastier/urls.py里面去添加路径:

from expenses.api import ExpenseResource

expense_resource = ExpenseResource()

urlpatterns = patterns('',
    url(r'^admin/', include(admin.site.urls)),
    url(r'^api/', include(expense_resource.urls)),
)

这样,我们就可以访问到所有的expenses资源了 ……
我们的返回结果是这样的形式:

{
    "meta":{
        "limit":20,
        "next":null,
        "offset":0,
        "previous":null,
        "total_count":2
    },
    "objects":[
        {
            "amount":100,
            "description":"Ate pizza",
            "id":1,
            "resource_uri":"/api/expense/1/"
        },
        {
            "amount":200,
            "description":"Went to Cinema",
            "id":2,
            "resource_uri":"/api/expense/2/"
        }
    ]
}

我们当然也可以通过一定的设置去访问特定的某个资源:

http://localhost:8000/api/expense/1/?format=json

我们并不必要把这个匹配加进urls里面就可以访问的到,tastypie自动为我们做了这些事情。

如果我们给移动端返回数据,移动APP就需要去访问

http://localhost:8000/api/expense/?format=json

获取到响应,然后解析响应数据,讲响应数据展示在APP上面。
现在,每一个用户都可以看到我们的响应了。随着我们的继续讲解,我们看到当一个用户在他的移动设备上发起一个rest的请求去获取单个用户的expenses怎么实现。

接下来讲的是序列化。你一定意识到了REST转化了你的序列化数据。你或许在疑惑django-tastypie是怎么不通过json.dumps实现它的。
我们毫无疑问可以通过json.dumps而不是使用tastypie来实现一个rest的endpoints。但是接下来你一定会同意,django-tastypie为我们做了更多的工作使事情看起来更加的简单。继续看吧。

改变Meta元类里面的资源名称
我们来改变下ExpenseResource.Meta.resource_name 的资源名称从expense到expenditure

class ExpenseResource(ModelResource):

    class Meta:
        queryset = Expense.objects.all()
        resource_name = 'expenditure'

那么我们之前的urls就不会继续工作了。新的urls将会是这样子的:

http://localhost:8000/api/expenditure/?format=json
http://localhost:8000/api/expenditure/1/?format=json

更改resource_name会更改tastypie提供给我们的url。
现在,我们将resource_name更改回expense。

Meta.fields

假设我们只想要展示expense里面的description而不展示amount。这时候我们可以添加fields属性在ExpenseResource.Meta里面。

class Meta:
    queryset = Expense.objects.all()
    resource_name = 'expenditure'
    fields = ['description']

尝试去请求:

http://localhost:8000/api/expense/?format=json

如果我们在Meta类里面不加入fields属性,Models里面所有的字段都会被显示出来。如果我们加了fields只有列在fields里面的字段才会被展示出来。
现在让我们再次把amount字段也加进fields里面。这将和我们压根不写fields字段的表现一样,都将会展示出所有的字段。

class Meta:
    queryset = Expense.objects.all()
    resource_name = 'expenditure'
    fields = ['description', 'amount']

Filtering

假设我们现在只想得到amount超过150的Expenses。
如果使用Django model来实现我们可能会这样写:

Expense.objects.filter(amount__gt=150)

amount__gt是这里的关键词。预想这将会添加到我们的URL匹配模式里面,去获取amount超过150的数据。

我们通过以下url获取:

http://localhost:8000/api/expense/?amount__gt=150&format=json

我们尝试获取的时候回报错,因为我们还没有告诉tastypie我们是允许过滤的。
在Meta里面添加filtering属性:

class Meta:
    queryset = Expense.objects.all()
    resource_name = 'expense'
    fields = ['description', 'amount']
    filtering = {
        'amount': ['gt']
    }

这时候我们就可以使用以下url啦

http://localhost:8000/api/expense/?amount__gt=150&format=json

这个请求只返回amount大于150的expenses。

现在我们只想要得到所有在Pizza上面的花费。我们在shell上面可以这样获取:

Expense.objects.filter(description__icontains='pizza')

我们只需要在ExpenseResource.Meta.filtering里面做如下修改:

class Meta:
    queryset = Expense.objects.all()
    resource_name = 'expense'
    fields = ['description', 'amount']
    filtering = {
        'amount': ['gt'],
        'description': ['icontains']
    }

然后我们就可以使用以下url去获取有关pizaa的expends啦

http://localhost:8000/api/expense/?description__icontains=pizza&format=json

使用GET endpoints 我们只能去做读的操作。使用POST我们还可以创建新的operations。我们在下一个章节继续看。

Handling POST

我们很难使用浏览器去POST一个请求。我们将会使用requests 库实现。
在做POST请求之前我们先检查一下我们现在的expense数量

>>> Expense.objects.count()
2

tastypie默认不会授权任何一个人去做post请求。默认的授权类是 ReadOnlyAuthorization,只允许get请求,不允许post请求。因此,我们需要暂时停止授权检查,将以下内容添加到ExpenseResource.Meta里面

authorization = Authorization()

我们需要去导入这个类:

from tastypie.authorization import Authorization

这时候,ExpenseResource 类看上去将会是这样的:

class ExpenseResource(ModelResource):

    class Meta:
        queryset = Expense.objects.all()
        resource_name = 'expense'
        fields = ['description', 'amount']
        filtering = {
            'amount': ['gt'],
            'description': ['icontains']
        }
        authorization = Authorization()

现在先不要去深究 Authorization 的细节,我们稍后会继续讲解
现在让我们在我们的rest节点上发送一个post请求

post_url = 'http://localhost:8000/api/expense/'
post_data = {'description': 'Bought first Disworld book', 'amount': 399}
headers = {'Content-type': 'application/json'}
import requests
import json
r = requests.post(post_url, json.dumps(post_data), headers=headers)
>>> print r.status_code
201

201这个状态码说明 你的Expense 对象已经被创建出来了。我们再来通过检查Expense的数量确认下:

>>> Expense.objects.count()
3

然后我们去访问这个节点,就可以看到我们新增的数据了

http://localhost:8000/api/expense/?format=json

有关于post的扩展

你需要在你get到所有expenses的url上进行post操作,这两者需要保持一致。
一种进行post的方法是进行POST json 的encoded 数据。我们使用json.dumps来实现。
如果你在发送json encoded数据,你也需要适当的content-type标头。

和移动设备的关联

不管是安卓还是ios都具有在给定的url上发送pos请求的方法。被post的数据会被tastypie处理创建对象,收入数据库。

Adding authentication

现在即使是没有在站点登录的用户也可以想节点发送get和post请求。我们的第一步就是限制仅仅是注册用户才能get节点。

Api tokens and sessions

在一个web应用中一个用户只要登录过一次,他就可以发送任意次数的请求,而不必每次都重新登录。举个?,一个用户一次登录之后可以看到她的expense列表,在第一次请求之后她可以刷新页面,仍然可以获得这个列表而不用再次获取请求认证。实现的原理是django使用cookie和session去保存用户的登录状态。django可以将用户和cookie相关联,为他们展示定制数据。

在移动端,就木有session的概念了,除非用户使用的是移动客户端的浏览器。在移动端,与session相对应的Api key。所以说每个api key会去关联一个用户。每一个rest 的请求需要去包含这样一个api key,这样tastypie就可以使用这个key 去判断是否是一个登录过的用户去发起这个请求。

Creating user and api token

现在让我们在系统中创建我们的一个用户并且为他建立一个相应的api token
在终端:

u = User.objects.create_user(username='sheryl', password='abc', email='sheryl@abc.com')

tastypie提供一个叫做ApiKey的类,它为用户存储token。现在让我们为Sherly创建一个token。

from tastypie.models import ApiKey
ApiKey.objects.create(key='1a23', user=u)

我们将sheryl的API token设置为‘1a23‘
我们需要确保将tastypie加入我们的INSTALLED_APPS,并且在创建apikey 实例之前已经进行了数据迁移。

tastypie默认提供的身份验证类是Authentication,它允许任何人的get请求。我们现在需要去设置ExpenseResource.Meta.authentication 确保只有提供了有效的api key的用户才能在该节点发送post请求并且得到响应。
在ExpensesResource.Meta 里面添加以下的一行:

authentication = ApiKeyAuthentication()

当然我们也需要去导入这个ApiKeyAuthentication

from tastypie.authentication import ApiKeyAuthentication

现在尝试去获取expenses的list

http://localhost:8000/api/expense/?format=json

我们不会看到任何响应,如果这个时候我们去看我们的控制台,就会看到401状态码。
API key将会被发送到request里面去获取合适的响应。

尝试下下面这个url

http://localhost:8000/api/expense/?format=json&username=sheryl&api_key=1a23

这样就可以获取到正确的响应

如果我们发送了一个错误的api_key 我们也不会得到相应的响应

http://localhost:8000/api/expense/?format=json&username=sheryl&api_key=1a2

这样我们就可以确保只有注册了的且有合法api key 的用户才能得正确的请求响应。

How this ties in with mobile app

当用户安装app的时候,他使用他的username和password第一次登录。这些认证信息使用一个rest请求被发送到django 服务器。django 服务器相应向移动端返回一个api key。移动端存储这个api token,将其应用于后续的rest调用。用户不再需要提供凭据。

Making unauthenticated POST requests

未经身份验证的post请求将不再起到作用。
我们现在尝试去不传递api key创建一个Expense对象

post_data = {'description': 'Bought Two scoops of Django', 'amount': 399}
headers = {'Content-type': 'application/json'}
r = requests.post("http://localhost:8000/api/expense/", data=json.dumps(post_data), headers=headers)
print r.status_code       #This will give 401

在终端检查我们的Expense的数量并没有增长。

Making authenticated POST requests

我们只需要改变url,添加username和api_key到url里面即可。它们会使我们这个url是合法的请求:

r = requests.post("http://localhost:8000/api/expense/?username=sheryl&api_key=1a23", data=json.dumps(post_data), headers=headers)

这样子就可以工作了,Expense数量应该已经增加1。
那相应,如果我们使用错误的api_key当然就是失败请求了。

Getting only User’s expense

直到现在,我们还没有将用户和Expense关联起来。现在让我们增加一个从User到Expense的外键,外键一般是定义在多的那一边。
我们的Expense model变成了这样:

from django.db import models
from django.contrib.auth.models import User


class Expense(models.Model):
    description = models.CharField(max_length=100)
    amount = models.IntegerField()
    user = models.ForeignKey(User, null=True)

因为我们已经有一些Expense在我们的数据库里面了,所以我们设定User是允许为空的字段。
修改了model之后我们重新运行数据迁移:

python manage.py makemigrations
python manage.py migrate

现在我们的authorization类已经加进了Authorization。现在每一个用户只有授权才可以去查看每一个expense。我们还需要加入一个自定义的授权类去确保用户只能看到自己的expense。

将以下内容添加到expenses/api.py里面:

class ExpenseAuthorization(Authorization):

    def read_list(self, object_list, bundle):
        return object_list.filter(user=bundle.request.user)

同时我们也需要改变authorization在ExpenseResource.Meta里面的授权,它将会变成这个样子:

class ExpenseResource(ModelResource):

    class Meta:
        queryset = Expense.objects.all()
        resource_name = 'expense'
        fields = ['description', 'amount']
        filtering = {
            'amount': ['gt'],
            'description': ['icontains']
        }
        authorization = ExpenseAuthorization()
        authentication = ApiKeyAuthentication()

我的理解就是一个是认证,具有api_key即可认证,一个授权,在访问哪些资源上具有权限。

Explanation of ExpenseAuthorization

当我们在expense list的节点调用get请求的时候,object_list就会被创建去给出所有的expenses

之后,权限认证就会再进一步的访问请求时被执行。

在我们例子里面,当我们去GET list 节点的时候,认证类authorization里面的read_list()方法将被执行。object_list将会被read_list过滤。

在tastypie里面有一个名字叫做bundle的变量,bundle具有使用bundle.request去调用request的权限。

正确使用身份验证之后,将使用正确的用户去填充bundle.request.user。

现在我们尝试去获取Sheryl的费用清单节点:

http://localhost:8000/api/expense/?format=json&username=sheryl&api_key=1a23

添加ExpenseAuthorization之后,将不会获取任何的费用信息

{"meta": {"limit": 20, "next": null, "offset": 0, "previous": null, "total_count": 0}, "objects": []}

之所以发生这种情况,是因为现在还没��与Sheryl相关的费用。

Create an expense for Sheryl and try the GET endpoint

在shell上:

u = User.objects.get(username='sheryl')
Expense.objects.create(description='Paid for the servers', amount=1000, user=u)
Expense.objects.create(description='Paid for CI server', amount=500, user=u)

现在我们再次尝试去获取Sheryl 的expense list

http://localhost:8000/api/expense/?format=json&username=sheryl&api_key=1a23

这时候我们就可以看到Shery所有的开销了。

How mobile app will use it.

当Sheryl安装app的时候,她被第一次要求登陆。这时候她请求的rest节点获取到用户名字和密码,如果凭证无误,就会返回用户的api key。Sheryl 的api key将会被返回到移动端,移动端将其存储在本地存储之中。当Sheryl想要查看她的费用时,这个rest的调用就会被发送到django的服务端。

http://localhost:8000/api/expense/?format=json&username=sheryl&api_key=1a23

这时候就只返回了sheryl的费用。

POST and create Sheryl’s expense

到现在为止,如果一个post请求被发送,即使是带上了sheryl的api key,expense会在数据库被创建,但是不会与sheryl进行关联。

我们想要去添加这样的功能:如果post请求来自于sheryl 的设备,就去和sheryl进行关键;如果post请求来自于mark的设备,就去和mark进行关联。

Tastypie提供给了我们几个hookpoint。我们可以使用一个这样的hookpoint。ModelResource提供了一个叫做hydrate的方法,我们需要去重写这个方法。在ExpenseResource里面添加如下的方法:

def hydrate(self, bundle):
    bundle.obj.user = bundle.request.user
    return bundle

这个方法在post和put的过程中被调用。
bundle.obj是一个即将被保存进入数据库的Expense实例
因此我们可以将从bundle.request里面读取的user设置到bundle.obj.user里面。我们在之前已经讨论过了在认证流里面怎么样去填充bundle.request了。

现在我们就可以带上sheryl的api_key去发送一个请求了

post_data = {'description': 'Paid for iDoneThis', 'amount': 700}
r = requests.post("http://localhost:8000/api/expense/?username=sheryl&api_key=1a23", data=json.dumps(post_data), headers=headers)

确认最后一次的请求数据和sheryl相关联。我们可以通过像list endpoint发送一个GET请求观察返回的数据来校验。

Try on your own

1 在shell上往数据库里面创建一个新用户。
2 为这个用户创建api key
3 发送一个post请求,关联到这个新用户
4 检查是否在这个新用户上添加成功数据

现在是我们深入django-tastypie并且理解以下概念的时间:
1 Dehydrate cycle :在get请求期间被调用

2 Hydrate cycle:在post/put请求期间被调用。如果你阅读了解hydrate cycle,你就会理解hydrate方法何时被调用。

3 你可以重写更多的不同的关于Authorization的方法。

Want more?

Authorization on detail endpoint

id为1的Expense没有被绑定到任何的用户,但是Sheryl仍然有权限去查看。

http://localhost:8000/api/expense/1/?format=json&username=sheryl&api_key=1a23

她本不应该有权限去查看不属于她的expese。

所以我们添加如下的

ExpenseAuthorization:
def read_detail(self, object_list, bundle):
    obj = object_list[0]
    return obj.user == bundle.request.user

这样写之后,sheryl就没有权限去查看不属于她的详情节点数据了。
试一下去访问这个url:

http://localhost:8000/api/expense/1/?format=json&username=sheryl&api_key=1a23

PUT endpoint

id为5的Expense属于sheryl。她现在想要更新这个expense,改变这个Expense的描述信息。
当前请求的url应该是:

http://localhost:8000/api/expense/5/?format=json&username=sheryl&api_key=1a23

我们去创建一个PUT请求:

put_url = "http://localhost:8000/api/expense/5/?username=sheryl&api_key=1a23"
put_data = {'description': 'Paid for Travis'}
headers = {'Content-type': 'application/json'}
r = requests.put(put_url, data=json.dumps(put_data), headers=headers)

当我们再次访问的时候,id为5的Expense已经被更新了

http://localhost:8000/api/expense/5/?format=json&username=sheryl&api_key=1a23

注意:amount保持不变,所以put请求是更新你提供的数据并且保持其他的数据不变。

DELETE endpoint

检查下sheryl所有的数据:

http://localhost:8000/api/expense/?format=json&username=sheryl&api_key=1a23

sheryl先要删除她id为5的expense数据,这样做之后,她就会少一个expense数据。

delete_url = "http://localhost:8000/api/expense/5/?username=sheryl&api_key=1a23"
r = requests.delete(delete_url)

再次访问根节点确认这个expense已经被删除了。

至此,我们已经可以通过rest的api调用去create、read、update、delete数据了。

Restrict POST request to certain users only

假设我们只允许用户在web端创建对象,而不允许在app端创建。是的,奇怪的要求。

此外,我们并不是禁止所有用户的post,我们希望sheryl在移动端的发布上仍然具有特权。

为了尝试我们的想法,我们首先需要在系统中创建一个使用api key的用户,现在尝试使用shell去创建他:

u = User.objects.create_user(username='mark', password='def', email='mark@abc.com')
ApiKey.objects.create(key='2b34', user=u)

Restricting POST for everyone except Sheryl

在ExpenseAuthorization里面添加如下的方法:

def create_detail(self, object_list, bundle):
    user = bundle.request.user
    # Return True if current user is Sheryl else return False
    return user.username == "sheryl"

然后我们作为Mark去发送一个post请求:

post_data = {'description': 'Petty expense', 'amount': 3000}
r = requests.post("http://localhost:8000/api/expense/?username=mark&api_key=2b34", data=json.dumps(post_data), headers=headers)
print r.status_code #Should have got 401

状态码401就是告诉你 你并没有这个权限。

Verify that Sheryl is still able to create expense

尝试以sheryl的身份去post相同的数据:

r = requests.post("http://localhost:8000/api/expense/?username=sheryl&api_key=1a23", data=json.dumps(post_data), headers=headers)
print r.status_code

返回的状态码是201,说明新的expense对象已经成功被创建了。

Verify that Mark is still able to do GET

Mark和其他的用户应该是可以继续发送get请求的,即使他们不能发送post请求了。

http://localhost:8000/api/expense/?username=mark&api_key=2b34&format=json

Explicitly defining fields and customising them

ModelResource.Meta.fields可以和 ModelForm.Meta.fields具有相似的默认行为。如果您将字段添加到ModelResource.Meta,它会得到正常的默认行为。但是可以通过在ModelResource中显式地添加字段来定制它。

现在让我们尝试去自定义description字段:

description = fields.CharField()

当然我们需要去导入tastypie里面的field类:

from tastypie import fields

尝试以下请求:

http://localhost:8000/api/expense/?username=sheryl&api_key=1a23&format=json

我们会看到空的描述信息,因为我们提交了一个错误。
我们应该这样声明:

description = fields.CharField(attribute='description')

现在再次发送get请求,我们就会发现它像之前一样工作了。

但是我们并没有通过这样的明确添加字段来实现任何目标,那么添加字段的重点什么?

假设你需要在detail endpoint 的description,但是又不希望它展示在list endpoint上,我们可以这样:

description = fields.CharField(attribute='description', use_in='detail')

尝试请求这两个url,观察不同:

http://localhost:8000/api/expense/?username=sheryl&api_key=1a23&format=json
http://localhost:8000/api/expense/8/?username=sheryl&api_key=1a23&format=json

翻译来源:
(https://www.agiliq.com/blog/2015/03/getting-started-with-django-tastypie)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值