Django Rest Framework first glance

After learning Django Rest Framework a few month, I think it is time to make a summary. my project is host in github:
https://github.com/cagegong/erplite/tree/master/Server

There are three good packages we may use:

django-cors-headers Django app for handling the server headers required for Cross-Origin Resource Sharing (CORS)
rest_framework_extensions DRF-extensions is a collection of custom extensions for Django REST Framework
drf-nested-routers This package provides routers and relations to create nested resources in the Django Rest Framework

1. model:

//models.py
class Contacts(models.Model):
    name = models.CharField(max_length=255)
    avator = models.CharField(max_length=100, blank=True)
    description = models.TextField(blank=True)
    createdDate = models.DateTimeField(auto_now_add=True)
    createdBy = models.CharField(max_length=100)
    modifiedDate = models.DateTimeField(auto_now=True)
    modifiedBy = models.CharField(max_length=100)

    def __unicode__(self):
        return '%s' % (self.name)

class ContactTag(models.Model):
    contact = models.ForeignKey('Contacts', related_name='tags')
    tag = models.CharField(max_length=100)
    createdDate = models.DateTimeField(auto_now_add=True)
    createdBy = models.CharField(max_length=100)
    modifiedDate = models.DateTimeField(auto_now=True)
    modifiedBy = models.CharField(max_length=100)

    def __unicode__(self):
        return '%s' % (self.tag)

now we have contact and contacttag with 1:n relationship.
in contact = models.ForeignKey('Contacts', related_name='tags'), Contacts is model name, related_name='tags' is a name used in serializer.

2. serializer

//serializers.py
from rest_framework import serializers
from Contacts.models import Contacts, ContactTag

class ContactsListSerializer(serializers.HyperlinkedModelSerializer):
    tags = serializers.RelatedField(many=True)
    class Meta:
        model = Contacts
        fields = ('id','url', 'name', 'avator', 'tags', 'description', 'createdDate', 'createdBy', 'modifiedDate', 'modifiedBy')

class ContactsDetailSerializer(serializers.HyperlinkedModelSerializer):
    tags = serializers.HyperlinkedRelatedField(many=True,view_name='contacttag-detail')
    class Meta:
        model = Contacts
        fields = ('id', 'name', 'avator', 'tags', 'data', 'links', 'description', 'createdDate', 'createdBy', 'modifiedDate', 'modifiedBy')

class ContactTagSerializer(serializers.ModelSerializer):
    contactname = serializers.Field(source='contact.name')
    class Meta:
        model = ContactTag
        fields = ('contact','contactname','tag', 'createdDate', 'createdBy', 'modifiedDate', 'modifiedBy')

For contacts we need to display list and detail with different content
List:

屏幕快照 2014-05-01 下午10.50.16.png
屏幕快照 2014-05-01 下午10.50.16.png

Detail:

屏幕快照 2014-05-01 下午10.52.02.png
屏幕快照 2014-05-01 下午10.52.02.png

In list, we just need tag name but in detail, we will give the API for tag source.
In order to make this, in ContactsListSerializer we set tags = serializers.RelatedField(many=True), so it displays the tag model return:

def __unicode__(self):
        return '%s' % (self.tag)

In ContactsDetailSerializer we set tags = serializers.HyperlinkedRelatedField(many=True,view_name='contacttag-detail')

3. view

we have two serializers for Contacts, but in DRF default viewset, we can only set one serializer_class, in order to support custom serializer, we need rewrite viewset. rest_framework_extensions package make it easy, we just import DetailSerializerMixin and use serializer_detail_class = ContactsDetailSerializer.

//views.py
class ContactViewSet(DetailSerializerMixin, viewsets.ModelViewSet):
    authentication_classes = (SessionAuthentication, TokenAuthentication, BasicAuthentication)
    permission_classes = (IsAuthenticated,)

    queryset = Contacts.objects.all()
    serializer_class = ContactsListSerializer
    serializer_detail_class = ContactsDetailSerializer

class ContactTagViewSet(viewsets.ModelViewSet):
    authentication_classes = (SessionAuthentication, TokenAuthentication, BasicAuthentication)
    permission_classes = (IsAuthenticated,)

    queryset = ContactTag.objects.all()
    serializer_class = ContactTagSerializer
  
  def get_queryset(self):
        contact_id = self.kwargs.get('contact_pk', None)
        print contact_id
        if contact_id:
            return ContactTag.objects.filter(contact=contact_id)
        return super(ContactTagViewSet, self).get_queryset()

4. URL

now we can use DRF default router.

//urls.py
from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register(r'contacts', views.ContactViewSet)
router.register(r'contacttag', views.ContactTagViewSet)

5. Nested router

Now we have API like this:

[
    {
        "contacts": "http://localhost:8000/contacts/", 
        "contacttag": "http://localhost:8000/contacttag/", 
    }
]

and we get json result like this:

[
    {
        "id": 1, 
        "url": "http://localhost:8000/contacts/1/", 
        "name": "rinz", 
        "avator": "http://localhost", 
        "tags": [
            "human", 
            "girl"
        ], 
        "description": "", 
        "createdDate": "2014-03-26T14:31:31Z", 
        "createdBy": "admin", 
        "modifiedDate": "2014-04-02T15:35:44Z", 
        "modifiedBy": "cage"
    }
]
[
    {
        "contact": 1, 
        "contactname": "rinz", 
        "tag": "human", 
        "createdDate": "2014-03-26T14:32:38Z", 
        "createdBy": "admin", 
        "modifiedDate": "2014-04-02T15:35:44Z", 
        "modifiedBy": "admin"
    }
]

We can get all contacts from ./contacts and get detail contact from ./contact/x/, we can also get API point to contact's tags in contact detail. It takes two steps to get a contact's tag information, if we want to get tag's information in one step like ./contact/x/tag/ and ./contact/x/tag/y/. So we need nested router, drf-nested-routers is a package can make this simple.

//urls.py
from rest_framework.routers import DefaultRouter
from rest_framework_nested import routers

admin.autodiscover()

router = DefaultRouter()
router.register(r'contacts', views.ContactViewSet)
router.register(r'contacttag', views.ContactTagViewSet)

contacts_router = routers.NestedSimpleRouter(router, r'contacts', lookup='contact')
contacts_router.register(r'contacttag', views.ContactTagViewSet)

Use nested router we will get a url like:
^contacts/(?P<contact_pk>[^/]+)/contacttag/$ [name='contacttag-list']
^contacts/(?P<contact_pk>[^/]+)/contacttag/(?P<pk>[^/]+)/$ [name='contacttag-detail']
so, we we should handle this url in view:

//views.py
class ContactTagViewSet(viewsets.ModelViewSet):
    authentication_classes = (SessionAuthentication, TokenAuthentication, BasicAuthentication)
    permission_classes = (IsAuthenticated,)

    queryset = ContactTag.objects.all()
    serializer_class = ContactTagSerializer

    def get_queryset(self):
        contact_id = self.kwargs.get('contact_pk', None)
        if contact_id:
            return ContactTag.objects.filter(contact=contact_id)
        return super(ContactTagViewSet, self).get_queryset()

Add custom get_queryset function to return ContactTag result.

6. Deploy

It's time to deploy our server to apache server.

My environment is AWS EC2 + Amazon Linux AMI + httpd + mod_wsgi.

step 1: update whole environment
install httpd first, yum install httpd

step 2: install python2.7
Download python resource package
tar zxvf Python-2.7.2.tgz
./configure –enable-shared
make sure you add –enable-shared or you will face unknow error in future.
make && make install
start python:
python: error while loading shared libraries: libpython2.7.so.1.0: cannot open shared object file: No such file or directory
python2.7 cannot find the lib, we have to configure using ldconfig
ldconfig /usr/local/lib
now, python2.7 has been installed but yum cannot work because it is relay on python2.6, it is easy to resolve.

step 3: install mod_wsgi
Download mod_wsgi resource,
./configure –with-apxs=/usr/local/apache2/bin/apxs –with-python=/usr/local/bin/python2.7
make && make install

step 4: configure
add LoadModule wsgi_module modules/mod_wsgi.so in /etc/httpd/conf/httpd.conf
add new file /etc/httpd/conf.d/wsgi.conf

//wsgi.conf
WSGIScriptAlias / /your_project_path/wsgi.py

AddType text/html .py

WSGIPassAuthorization On

<Directory /your_project_path/>
        Order deny,allow
        Allow from all
</Directory>
//wsgi.py
import os
import sys

path='/var/www/server/'
sys.path.append(path)

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Server.settings")

from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()

7. authentication

# Note: Apache mod_wsgi specific configuration
# this can go in either server config, virtual host, directory or .htaccess
WSGIPassAuthorization On

django-oauth2-provideris a Django application that provides customizable OAuth2-authentication and we use it to provide authentication for our server.
First install django-oauth2-provider and add it to setting.

//setting.py
INSTALLED_APPS = (
    ... 
    'provider',
    'provider.oauth2',
)
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.OAuth2Authentication',
    ),
}

READ = 1 << 1
WRITE = 1 << 2
READ_WRITE = READ | WRITE

OAUTH_SCOPES = (
        ...
    (READ_WRITE, 'read+write'),
)

OAUTH_DELETE_EXPIRED = True
OAUTH_EXPIRE_DELTA = datetime.timedelta(seconds=60*60)

fellow django-oauth2-provider document add urls and views:

//urls.py
url(r'^login/', include('provider.oauth2.urls', namespace = 'oauth2')),

add OAuth2Authentication to authentication_classes like this:

//views.py
class ContactViewSet(DetailSerializerMixin, viewsets.ModelViewSet):
    authentication_classes = (OAuth2Authentication,)
    permission_classes = (IsAuthenticated,)

    queryset = Contacts.objects.all()
    serializer_class = ContactsListSerializer
    serializer_detail_class = ContactsDetailSerializer

now we finished most work.
create clinet in django admin page, get clientID and client sercet.
get access token from http://localhost:8000/login/access_token/

屏幕快照 2014-05-10 下午5.44.45.png
屏幕快照 2014-05-10 下午5.44.45.png

request API with token:

屏幕快照 2014-05-10 下午5.47.36.png
屏幕快照 2014-05-10 下午5.47.36.png

Now, it seems everything OK.
But we have a trouble: one user changed his password but the token he get before can still work, it shouldn't not allow, he should post his password again to get a new token. How?
It is very easy, we just delete or expire all tokens for the user when he change his password.
Create Account app to handle user register and management.
It is easy to create login, logout, register and change password page, and we just need add some control in change password view.

//Accounts/views.py
from provider.oauth2 import models as oauth2

def changepwd(request):
    if request.method == 'GET':
        form = ChangepwdForm()
        return render_to_response('accounts/changepwd.html', RequestContext(request, {'form': form,}))
    else:
        form = ChangepwdForm(request.POST)
        if form.is_valid():
            username = request.user.username
            oldpassword = request.POST.get('oldpassword', '')
            user = authenticate(username=username, password=oldpassword)
            if user is not None and user.is_active:
                newpassword = request.POST.get('newpassword1', '')
                user.set_password(newpassword)
                user.save()
                access_token = oauth2.AccessToken.objects.filter(user=user)
                access_token.delete()
                _login(request,username,newpassword)
                return HttpResponseRedirect(reverse("index"))
            else:
                return render_to_response('accounts/changepwd.html', RequestContext(request, {'form': form,'oldpassword_is_wrong':True}))
        else:
            return render_to_response('accounts/changepwd.html', RequestContext(request, {'form': form,}))

access_token = oauth2.AccessToken.objects.filter(user=user)
access_token.delete()
So easy, done!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容

  • **2014真题Directions:Read the following text. Choose the be...
    又是夜半惊坐起阅读 9,363评论 0 23
  • 昨日月照街巷迟,那年槐下讨酒食。 未尽前情恕不周,口中无言心内执。
    四酒阅读 120评论 0 0
  • 这几天,尝试了一种比较极端的学习方式,也就是在3天看了九本书,平均每天看了三本。而当我看完以后我发现了一件事。就是...
    邓小怪阅读 174评论 0 0
  • 左挑挑,右挑挑,瓜果梨桃各妖娆,娃儿拍手笑。 情滔滔,意滔滔,笑语欢歌入九霄,更盼来年好!
    刘芷源07阅读 165评论 0 9
  • 5月10日,如约而至。 准备不足,今天开会领导把我问住了,我不能很好地和领导有问有答。所以我需要很好的做一些准备工...
    郭腾达阅读 157评论 0 0