REST 框架包含了处理ViewSet的抽象,这样开发者就可以专注于API的状态和交互,而不用去管URL的构造,URL会按照
公共约定自动构造。
ViewSet类和View类差不多,不同的是ViewSet提供如read,update等方法,而不是get或是put
一个ViewSet类只绑定一组方法处理程序,当它被实例化为一组views时,通常用一个Router类来处理复杂的url。

重构代码以使用ViewSets

首先将我们现有的UserListUserDetail重构合并为UserViewSet
编辑views.py

from rest_framework import viewsets


class UserViewSet(viewsets.ReadOnlyModelViewSet):
    """
    这个ViewSet提供`list`和`detail`两个功能
    """
    queryset = User.objects.all()
    serializer_class = UserSerializer

这里我们使用的ReadOnlyModelViewSet类会提供默认的只读操作。
像以前一样,我们依然定义querysetserializer_class属性,只不过之前需要在两个类里面定义,现在只需要定义一遍。

接下来我们将现有的SnippetList, SnippetDetail, SnippetHighlight重构合并为SnippetViewSet
编辑views.py

from rest_framework.decorators import detail_route
from rest_framework.response import Response


class SnippetViewSet(viewsets.ModelViewSet):
    """
    这个ViewSet自动了`list`, `create`, `retrieve`, `update`和`destroy`功能
    我们需要另外定义一个`highlight`功能
    """
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer
    permission_classes = (permissions.IsAuthenticatedOrReadOnly,
                          IsOwnerOrReadOnly)

    @detail_route(renderer_classes=[renderers.StaticHTMLRenderer])
    def highlight(self, request, *args, **kwargs):
        snippet = self.get_object()
        return Response(snippet.highlighted)

    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)

这里我们使用的ModelViewSet类会提供默认的读写操作。
注意,我们在此同样使用了@detail_route装饰器来创建一个额外的功能,名为highlight,当标准的create, update, delete不够用时,这个装饰器能够用来添加任何额外的功能。
使用@detail_route装饰器定制的额外功能会默认使用GET请求,我们可以在装饰器的参数内定义methods参数来使用如POST等等的其它请求。
默认情况下,这种定制的额外功能会使用和方法名同名的url,如想要使用与方法名不同的url,可以在detail_route装饰器中定义url_path参数。

将ViewSets与URLs绑定

编辑urls.py

from rest_framework import renderers
from snippets.views import api_root
from snippets.views import SnippetViewSet
from snippets.views import UserViewSet

snippet_list = SnippetViewSet.as_view({
    'get': 'list',
    'post': 'create'
})

snippet_detail = SnippetViewSet.as_view({
    'get': 'retrieve',
    'put': 'update',
    'patch': 'partial_update',
    'delete': 'destroy'
})

snippet_highlight = SnippetViewSet.as_view(
    {'get': 'highlight'},
    renderer_classes=[renderers.StaticHTMLRenderer]
)

user_list = UserViewSet.as_view({
    'get': 'list'
})

user_detail = UserViewSet.as_view({
    'get': 'retrieve'
})

此时我们通过将http请求方式绑定到views的方法,从ViewSet类创建了一系列views。
接下来我们将这些views注册到url中
编辑urls.py

from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns

urlpatterns = format_suffix_patterns([
    url(r'^$', api_root),
    url(r'^snippets/$',
        snippet_list,
        name='snippet-list'),
    url(r'^snippets/(?P<pk>)[0-9]+/$',
        snippet_detail,
        name='snippet-detail'),
    url(r'^snippets/(?P<pk>[0-9]+)/highlight/$',
        snippet_highlight,
        name='snippet-highlight'),
    url(r'^users/$',
        user_list,
        name='user-list'),
    url(r'^users/(?P<pk>[0-9]+)/$',
        user_detail,
        name='user-detail')
])

使用Router

因为我们使用的是ViewSet类而不是View类,实际上我们不需要自己去设计URL。
通过使用Router类,我们只需要将合适的views注册到Router中,其它的事情就让它自动生成吧。

重写urls.py

from django.conf.urls import url
from django.conf.urls import include
from snippets import views
from rest_framework.routers import DefaultRouter


# 创建一个router并将viewsets注册上去
router = DefaultRouter()
router.register(r'snippets', views.SnippetViewSet)
router.register(r'users', views.UserViewSet)

urlpatterns = [
    url(r'^', include(router.urls)),
    url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]

DefaultRouter类自动提供了根节点的url,所以我们不再需要单独去写。

关于

本人是初学Django REST framework,Django REST framework 学习纪要系列文章是我从官网文档学习后的初步消化成果,如有错误,欢迎指正。

学习用代码Github仓库:shelmingsong/django_rest_framework

本文参考的官网文档:Tutorial 6: ViewSets & Routers

博客更新地址

目前我们使用主键来表示模型之间的关系。在本章,我们将提高API的凝聚性和可读性。

为我们API的根节点创建URL

之前我们给snippetsusers创建了URL接口,但我们没有一个根节点的URL。
在此我们创建一个简单的,基于函数的views,并为它加上@api_view装饰器,修改snippets/views.py

from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.reverse import reverse


@api_view(['GET'])
def api_root(request, format=None):
    return Response({
        'users': reverse('user-list', request=request, format=format),
        'snippets': reverse('snippet-list', request=request, format=format)
    })

需要注意两点:
* 我们使用REST框架中的reverse方法来返回完全符合规则的url
* url参数将会与我们之后在snippets/urls中定义的相同

编写snippets/urls.py

url(r'^$', views.api_root),

为高亮代码创建URL

接下来我们需要为高亮代码提供接口,和其它的接口不同,这个接口我们需要展现渲染好的HTML页面,而不是JSON。
REST框架给我们提供了两种HTML展现形式,一种是使用模板渲染,一种是使用已经提前渲染好的HTML,在此我们使用第二种。
另外,我们需要考虑当创建代码高亮的view时,我们没有现成的类来继承,因为我们不是返回对象,而是返回对象的一个属性,我们需要重写父类的get方法。
编辑snippets/views.py:

from rest_framework import renderers
from rest_framework.response import Response

class SnippetHighlight(generics.GenericAPIView):
    queryset = Snippet.objects.all()
    renderer_classes = (renderers.StaticHTMLRenderer,)

    def get(self, request, *args, **kwargs):
        snippet = self.get_object()
        return Response(snippet.highlighted)

编写snippets/urls.py

url(r'^snippets/(?P<pk>[0-9]+)/highlight/$', views.SnippetHighlight.as_view()),

API之间使用超链接

在Web API中处理各个API之间的关系是一件非常头疼的事情,通常有以下方法表示关系:
* 使用主键
* 在API之间使用超链接
* 在关联API之间使用唯一字段表示
* 在关联API之间使用默认字符串表示
* 将一个API嵌套在另一个API类中
* 其它

REST框架支持上述所有方法,并且可以应用于正向、反向关系或类似外键这类自定义管理项。
在这里我们使用超链接来处理之间的关系,因此我们需要修改serializers,使用HyperlinkedModelSerializer替代原先的ModelSerializer:
* 默认不包含主键
* 使用HyperlinkedRelatedField时,需要在Meta子类的fields中包含“urls字段
* 使用
HyperlinkedRelatedField来代替PrimaryKeyRelatedField`表示关系

编辑snippets/serializers.py

class SnippetSerializer(serializers.HyperlinkedModelSerializer):
    owner = serializers.ReadOnlyField(source='owner.username')
    highlight = serializers.HyperlinkedIdentityField(view_name='snippet-highlight', format='html')

    class Meta:
        model = Snippet
        fields = ('url', 'id', 'highlight', 'owner',
                  'title', 'code', 'linenos', 'language', 'style')


class UserSerializer(serializers.HyperlinkedModelSerializer):
    snippets = serializers.HyperlinkedRelatedField(many=True, view_name='snippet-detail', read_only=True)

    class Meta:
        model = User
        fields = ('url', 'id', 'username', 'snippets')

这里在SnippetSerializer中新增了一个highlight属性,这个属性和url字段类型相同,区别在于它指向的是snippet-highlight而非snippet-detail
另外,由于我们有.json格式的后缀,我们需要指定highlight字段使用.html来返回相应的格式

确保URL都被命名

在此之前我们创建了一些url参数,在此罗列:
* 根节点指向了user-listsnippet-list
* snippet serializer包含了一个指向snippet-highlight的url的字段
* user serializer包含了指向snippet-detail的url的字段
* snippet serializeruser serializer都包含了url字段,这个字段默认指向{model_name}-detail,这里分别是snippet-detailuser-detail

编辑snippet/urls.py

from django.conf.urls import url, include
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views

# API endpoints
urlpatterns = format_suffix_patterns([
    url(r'^$', views.api_root),
    url(r'^snippets/$',
        views.SnippetList.as_view(),
        name='snippet-list'),
    url(r'^snippets/(?P<pk>[0-9]+)/$',
        views.SnippetDetail.as_view(),
        name='snippet-detail'),
    url(r'^snippets/(?P<pk>[0-9]+)/highlight/$',
        views.SnippetHighlight.as_view(),
        name='snippet-highlight'),
    url(r'^users/$',
        views.UserList.as_view(),
        name='user-list'),
    url(r'^users/(?P<pk>[0-9]+)/$',
        views.UserDetail.as_view(),
        name='user-detail')
])

# Login and logout views for the browsable API
urlpatterns += [
    url(r'^api-auth/', include('rest_framework.urls',
                               namespace='rest_framework')),
]

添加分页

后面可能会有很多个数据产生,我们需要对返回结果进行分页,修改tutorial/settings.py

REST_FRAMEWORK = {
    'PAGE_SIZE': 10
}

注意,所有关于REST框架的settings都在一个叫做REST_FRAMEWORK的字典中,这帮助我们与其他的settings分离开来。

关于

本人是初学Django REST framework,Django REST framework 学习纪要系列文章是我从官网文档学习后的初步消化成果,如有错误,欢迎指正。

学习用代码Github仓库:shelmingsong/django_rest_framework

本文参考的官网文档:Tutorial 5: Relationships & Hyperlinked APIs

博客更新地址

目前为止,我们的代码没有限制谁可以编辑和删除代码片段,此节我们需要实现以下功能
* 代码片段需要与创建者关联
* 只有通过验证的用户才能创建代码片段
* 只有创建者才能修改或删除代码片段
* 没有通过验证的用户拥有只读权限

给model添加字段

我们需要添加两个字段,一个用于存储代码片段的创建者信息,一个用于存储代码的高亮信息

    style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100)
    owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE)
    highlighted = models.TextField()

同时,我们需要在该模型类执行保存操作时,自动填充highlighted字段,使用pygments库。
首先,导入一些包

from pygments.lexers import get_lexer_by_name
from pygments.formatters.html import HtmlFormatter
from pygments import highlight

然后为Snippet重写父类的save方法

    def save(self, *args, **kwargs):
        """
        use the 'pygments' library to create a highlighted HTML
        representation of code snippet
        """
        lexer = get_lexer_by_name(self.language)
        linenos = self.linenos and 'table' or False
        options = self.title and {'title': self.title} or {}
        formatter = HtmlFormatter(style=self.style, linenos=linenos,
                                  full=True, **options)
        self.highlighted = highlight(self.code, lexer, formatter)
        super(Snippet, self).save(*args, **kwargs)

接下来需要迁移数据库,方便起见,删库,然后重新迁移

(django_rest_framework) [root@localhost tutorial]# rm -f tmp.db db.sqlite3 && \
> rm -rf snippets/migrations/ && \
> python manage.py makemigrations snippets && \
> python manage.py migrate
Migrations for 'snippets':
  snippets/migrations/0001_initial.py
    - Create model Snippet
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions, snippets
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying sessions.0001_initial... OK
  Applying snippets.0001_initial... OK

为了测试API,我们需要创建一些用户,最快的方式就是通过createsuperuser命令

(django_rest_framework) [root@localhost tutorial]# python manage.py createsuperuser
Username (leave blank to use 'root'): song
Email address: shelmingsong@gmail.com
Password: 
Password (again): 
Superuser created successfully.
(django_rest_framework) [root@localhost tutorial]# python manage.py createsuperuser
Username (leave blank to use 'root'): user_1
Email address: user_1@gmail.com
Password: 
Password (again): 
Superuser created successfully.
(django_rest_framework) [root@localhost tutorial]# python manage.py createsuperuser
Username (leave blank to use 'root'): user_2
Email address: user_2@gmail.com
Password: 
Password (again): 
Superuser created successfully.

为用户模型添加接口

我们已经创建了三个用户,现在我们需要添加用户相关的接口,修改serializers.py

from django.contrib.auth.models import User

class UserSerializer(serializers.ModelSerializer):
    snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())

    class Meta:
        model = User
        fields = ('id', 'username', 'snippets')

因为snippetsuser是一种反向的关联,默认不会包含入ModelSerializer类中,所以需要我们手动添加

我们也需要对views.py进行修改,由于用户页面为只读,所以继承于ListAPIViewRetrieveAPIView

from django.contrib.auth.models import User
from snippets.serializers import UserSerializer


class UserList(generics.ListAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer


class UserDetail(generics.RetrieveAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer

配置url.py

    url(r'^users/$', views.UserList.as_view()),
    url(r'^users/(?P<pk>[0-9]+)/$', views.UserDetail.as_view()),

关联User和Snippet

此时我们创建一个代码片段,是无法与用户关联的,因为用户信息是通过request获取的。
因此我们需要重写snippet的view中perform_create()方法,这个方法允许我们在对象保存前进行相关操作,处理任何有requestrequested URL传递进来的数据

修改views.py中的SnippetList类,添加perform_create()方法

    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)

如此,新建代码片段时,会添加owner字段,该字段存储了request中的用户信息

更新serializer

之前我们在views中的SnippetList类中添加了perform_create方法,保存了owner信息,因而也需要在serializer中的SnippetSerializer类中添加owner信息,同时将owner添加进Meta子类的fields字段中

class SnippetSerializer(serializers.ModelSerializer):
    owner = serializers.ReadOnlyField(source='owner.username')

    class Meta:
        model = Snippet
        fields = ('id', 'title', 'code', 'linenos', 'language', 'style', 'owner')

这里我们使用了ReadOnlyField类型,这个类型是只读的,不能被更新,和Charfield(read_only=True)是一样的效果

添加权限认证

我们希望只有登录的用户能够去增加代码片段,未登录则只有查看的权限,此时我们需要用到IsAuthenticatedOrReadOnly

修改views.py,为snippet的两个类views添加permission_classes字段

from rest_framework import permissions


class SnippetList(generics.ListCreateAPIView):
    permission_classes = (permissions.IsAuthenticatedOrReadOnly, )


class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
    permission_classes = (permissions.IsAuthenticatedOrReadOnly, )

添加登陆接口

修改项目的urls.py

urlpatterns = [
    url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]

r'^api-auth/'可以自定,namespace在Django 1.9 + 的版本中可以省略

运行Django服务器,访问your.domain/snippets/,点击右上角的登陆按钮,登陆我们之前创建的用户后,就可以创建代码片段了

创建完几个代码片段后,再访问your.domain/users/时,就可以看到每个用户创建了哪几个代码片段了

对象级别的权限

现在用户都可以对所有的snippets进行增删改查,我们要确保只有创建者可以对snippets进行改动或删除。

snippetsapp中,创建permissions.py

from rest_framework import permissions


class IsOwnerOrReadOnly(permissions.BasePermission):
    """
    custom permission to only allow owners of an object to edit it
    """
    def has_object_permission(self, request, view, obj):
        # allow all user to read
        if request.method in permissions.SAFE_METHODS:
            return True

        # only allow owner to edit
        return obj.owner == request.user

views.py中添加权限

from snippets.permissions import IsOwnerOrReadOnly

class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
    permission_classes = (permissions.IsAuthenticatedOrReadOnly,
                          IsOwnerOrReadOnly)

此时我们访问your.domain/snippets/1/,若用户未登录或登录用户不是该snippets的创建者,则只有读的权限,页面上表现为没有DELETE(上方中间)和PUT(右下角)按钮

通过接口进行权限认证

之前我们是通过浏览器页面进行登录的,而当我们直接使用接口去请求时,如果没有进行登录,而对某个snippet进行修改或是创建一个新的snippet,则会报错

(django_rest_framework) [root@localhost django_rest_framework]# http POST https://127.0.0.1:80/snippets/ code="hahah"
HTTP/1.0 403 Forbidden
Allow: GET, POST, HEAD, OPTIONS
Content-Length: 58
Content-Type: application/json
Date: Tue, 28 Nov 2017 14:56:18 GMT
Server: WSGIServer/0.2 CPython/3.6.3
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

{
    "detail": "Authentication credentials were not provided."
}

(django_rest_framework) [root@localhost django_rest_framework]# http POST https://127.0.0.1:80/snippets/1/ code="hahah"
HTTP/1.0 403 Forbidden
Allow: GET, PUT, PATCH, DELETE, HEAD, OPTIONS
Content-Length: 58
Content-Type: application/json
Date: Tue, 28 Nov 2017 14:56:26 GMT
Server: WSGIServer/0.2 CPython/3.6.3
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

{
    "detail": "Authentication credentials were not provided."
}

我们在发送请求时,提供用户名和密码,就可以进行操作了

(django_rest_framework) [root@localhost django_rest_framework]# http -a your_username:your_password POST https://127.0.0.1:80/snippets/ code="hahah"
HTTP/1.0 201 Created
Allow: GET, POST, HEAD, OPTIONS
Content-Length: 104
Content-Type: application/json
Date: Tue, 28 Nov 2017 14:58:10 GMT
Server: WSGIServer/0.2 CPython/3.6.3
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

{
    "code": "hahah",
    "id": 4,
    "language": "python",
    "linenos": false,
    "owner": "song",
    "style": "friendly",
    "title": ""
}

关于

本人是初学Django REST framework,Django REST framework 学习纪要系列文章是我从官网文档学习后的初步消化成果,如有错误,欢迎指正。

学习用代码Github仓库:shelmingsong/django_rest_framework

本文参考的官网文档:Tutorial 4: Authentication & Permissions

博客更新地址

基于类的views

之前我们创建的views都是基于函数的,我们也可以基于类来写views

from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from django.http import Http404
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status


class SnippetList(APIView):
    """
    list all snippets, or create a new snippet
    """
    def get(self, request, format=None):
        snippets = Snippet.objects.all()
        serializer = SnippetSerializer(snippets, many=True)
        return Response(serializer.data)

    def post(self, request, format=None):
        serializer = SnippetSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


class SnippetDetail(APIView):
    """
    retrieve, update or delete a snippet instance
    """
    def get_object(self, pk):
        try:
            return Snippet.objects.get(pk=pk)
        except Snippet.DoesNotExist:
            raise Http404

    def get(self, request, pk, format=None):
        snippet = self.get_object(pk)
        serializer = SnippetSerializer(snippet)
        return Response(serializer.data)

    def put(self, request, pk, format=None):
        snippet = self.get_object(pk)
        serializer = SnippetSerializer(snippet, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def delete(self, request, pk, format=None):
        snippet = self.get_object()
        snippet.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

接下来相应地去修改urls.py

from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views

urlpatterns = [
    url(r'^snippets/$', views.SnippetList.as_view()),
    url(r'^snippets/(?P<pk>[0-9]+)/$', views.SnippetDetail.as_view()),
]

urlpatterns = format_suffix_patterns(urlpatterns)

使用mixins

相比函数型views来说,基于类的views可以很容易地通过继承来去复用代码。
刚才我们创建的API中,基本都是数据库的增删改查操作,这种常见的操作都已经在REST框架中定义好了,在mixin类中,我们只需要去继承它。
再次重构views.py

from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework import mixins
from rest_framework import generics


class SnippetList(mixins.ListModelMixin,
                  mixins.CreateModelMixin,
                  generics.GenericAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer

    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

SnippetList这个类,首先继承于GenericAPIView,这个类提供了核心功能,接下来是继承于ListModelMixinCreateModelMixin类,这两个类分别提供了.list().create()功能。

接下来重构下一个API

class SnippetDetail(mixins.RetrieveModelMixin,
                    mixins.UpdateModelMixin,
                    mixins.DestroyModelMixin,
                    generics.GenericAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer

    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)

    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)

    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)

RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin分别提供了retrieve(), update(), destroy()功能

继承更通用的generic类

from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework import generics


class SnippetList(generics.ListCreateAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer


class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer

关于

本人是初学Django REST framework,Django REST framework 学习纪要系列文章是我从官网文档学习后的初步消化成果,如有错误,欢迎指正。

学习用代码Github仓库:shelmingsong/django_rest_framework

本文参考的官网文档:Tutorial 3: Class-based Views

博客更新地址

Request 对象

REST 框架引入了Request对象,继承于HttpRequest,相比HttpRequest提供了更多请求解析,最核心的功能是request.data属性,类似于request.POST,以下是不同之处。
* request.POST
1. 只能处理form表单数据;
2. 只能处理POST请求。
* request.data
1. 能够处理任意一种数据;
2. 能够处理POST、PUT、PATCH请求

Response对象

REST框架也引入了Response对象,它是一个TemplateResponse类型,能够将未处理的文本转换为合适的类型返回给客户端

return Response(data)

状态码

REST框架提供了更可读的状态信息,比如HTTP_400_BAD_REQUEST

API views封装

  • 对于函数views,可以使用@api_view装饰器
  • 对于类views,可以继承于APIView

views应用

  • 修改snippets/views.py
  1. GET获取所有code snippets,与新建code snippet的接口
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer


@api_view(['GET', 'POST'])
def snippet_list(request):
    """
    list all code snippets, or create a new snippet
    """
    if request.method == 'GET':
        snippets = Snippet.objects.all()
        serializer = SnippetSerializer(snippets, many=True)
        return Response(serializer.data)

    elif request.method == 'POST':
        serializer = SnippetSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
  1. GET获取单个code snippetPUT更新单个code snippetDELETE删除单个code snippet
@api_view(['GET', 'PUT', 'DELETE'])
def snippet_detail(request, pk):
    """
    retrieve, update or delete code snippet
    """
    try:
        snippet = Snippet.objects.get(pk=pk)
    except Snippet.DoseNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)

    if request.method == 'GET':
        serializer = SnippetSerializer(snippet)
        return Response(serializer.data)

    elif request.method == 'PUT':
        serializer = SnippetSerializer(snippet, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    elif request.method == 'DELETE':
        snippet.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

和上一步的views明显不同的是,我们不再需要关心输入(request)输出(response)的数据类型,REST框架已经帮我们处理好了

为URLs添加可选格式后缀

如上一步所说的,REST框架已经帮我们处理好了输入(request)输出(response)的数据类型,也就意味着一个API可以去处理不同的数据类型,在URLs中使用格式后缀可以帮助我们处理类似这样的url: https://192.168.0.103/snippets.json

  • 首先我们需要在views中添加形参format=None
def snippet_list(request, format=None):

def snippet_list(request, format=None):
  • 然后我们修改urls.py
from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views

urlpatterns = [
    url(r'^snippets/$', views.snippet_list),
    url(r'^snippets/(?P<pk>[0-9]+)$', views.snippet_detail),
]

urlpatterns = format_suffix_patterns(urlpatterns)

调用接口

  • 在启动服务器前,先修改settings.py中的ALLOWED_HOSTS,方便后面通过外部浏览器请求接口
ALLOWED_HOSTS = ['*']
  • 启动服务器(别管时间,每天晚上回来,让Win10从睡眠状态恢复,虚拟机的IP和时区总会变 🙁 ,懒得每次都改了)
(django_rest_framework) [root@localhost tutorial]# python manage.py runserver 0:80
Performing system checks...

System check identified no issues (0 silenced).
November 21, 2017 - 02:47:02
Django version 1.11.7, using settings 'tutorial.settings'
Starting development server at https://0:80/
Quit the server with CONTROL-C.
  • 打开另一个shell窗口,发送请求
(django_rest_framework) [root@localhost django_rest_framework]# http https://127.0.0.1:80/snippets/
HTTP/1.0 200 OK
Allow: POST, GET, OPTIONS
Content-Length: 505
Content-Type: application/json
Date: Mon, 20 Nov 2017 18:51:07 GMT
Server: WSGIServer/0.2 CPython/3.6.3
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

[
    {
        "code": "foo = \"bar\n\"",
        "id": 1,
        "language": "python",
        "linenos": false,
        "style": "friendly",
        "title": ""
    },
    {
        "code": "print \"hello, world\"\n",
        "id": 2,
        "language": "python",
        "linenos": false,
        "style": "friendly",
        "title": ""
    },
...
]
  • 我们可以添加HTTP HEADERS来控制返回数据的数据类型
  1. json
(django_rest_framework) [root@localhost django_rest_framework]# http https://127.0.0.1:80/snippets/ Accept:application/json
HTTP/1.0 200 OK
Allow: POST, GET, OPTIONS
Content-Length: 505
Content-Type: application/json
Date: Mon, 20 Nov 2017 18:52:27 GMT
Server: WSGIServer/0.2 CPython/3.6.3
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

[
    {
        "code": "foo = \"bar\n\"",
        "id": 1,
        "language": "python",
        "linenos": false,
        "style": "friendly",
        "title": ""
    },
    {
        "code": "print \"hello, world\"\n",
        "id": 2,
        "language": "python",
        "linenos": false,
        "style": "friendly",
        "title": ""
    },
]
  1. html
(django_rest_framework) [root@localhost django_rest_framework]# http https://127.0.0.1:80/snippets/ Accept:text/html
HTTP/1.0 200 OK
Allow: POST, GET, OPTIONS
Content-Length: 8139
Content-Type: text/html; charset=utf-8
Date: Mon, 20 Nov 2017 18:53:39 GMT
Server: WSGIServer/0.2 CPython/3.6.3
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

<!DOCTYPE html>
<html>
  <head>



        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        <meta name="robots" content="NONE,NOARCHIVE" />


      <title>Snippet List – Django REST framework</title>



          <link rel="stylesheet" type="text/css" href="/static/rest_framework/css/bootstrap.min.css"/>
...
  • 或者我们直接可以添加url后缀来控制返回数据的数据类型
  1. json
(django_rest_framework) [root@localhost django_rest_framework]# http https://127.0.0.1:80/snippets.json
HTTP/1.0 200 OK
Allow: POST, GET, OPTIONS
Content-Length: 505
Content-Type: application/json
Date: Mon, 20 Nov 2017 18:55:27 GMT
Server: WSGIServer/0.2 CPython/3.6.3
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

[
    {
        "code": "foo = \"bar\n\"",
        "id": 1,
        "language": "python",
        "linenos": false,
        "style": "friendly",
        "title": ""
    },
    {
        "code": "print \"hello, world\"\n",
        "id": 2,
        "language": "python",
        "linenos": false,
        "style": "friendly",
        "title": ""
    },
...
]
  1. html
(django_rest_framework) [root@localhost django_rest_framework]# http https://127.0.0.1:80/snippets.api
HTTP/1.0 200 OK
Allow: POST, GET, OPTIONS
Content-Length: 8160
Content-Type: text/html; charset=utf-8
Date: Mon, 20 Nov 2017 18:56:35 GMT
Server: WSGIServer/0.2 CPython/3.6.3
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

<!DOCTYPE html>
<html>
  <head>



        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        <meta name="robots" content="NONE,NOARCHIVE" />


      <title>Snippet List – Django REST framework</title>



          <link rel="stylesheet" type="text/css" href="/static/rest_framework/css/bootstrap.min.css"/>
          <link rel="stylesheet" type="text/css" href="/static/rest_framework/css/bootstrap-tweaks.css"/>
...
  • 类似的,我们可以发送不同类型的数据给API
  1. post form data
(django_rest_framework) [root@localhost django_rest_framework]# http --form POST https://127.0.0.1:80/snippets/ code="hello world post form data"
HTTP/1.0 201 Created
Allow: POST, GET, OPTIONS
Content-Length: 110
Content-Type: application/json
Date: Mon, 20 Nov 2017 18:58:58 GMT
Server: WSGIServer/0.2 CPython/3.6.3
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

{
    "code": "hello world post form data",
    "id": 6,
    "language": "python",
    "linenos": false,
    "style": "friendly",
    "title": ""
}
  1. post json data
(django_rest_framework) [root@localhost django_rest_framework]# http --json POST https://127.0.0.1:80/snippets/ code="hello world post json data"
HTTP/1.0 201 Created
Allow: POST, GET, OPTIONS
Content-Length: 110
Content-Type: application/json
Date: Mon, 20 Nov 2017 18:59:44 GMT
Server: WSGIServer/0.2 CPython/3.6.3
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

{
    "code": "hello world post json data",
    "id": 7,
    "language": "python",
    "linenos": false,
    "style": "friendly",
    "title": ""
}
  • 在请求时添加--debug后缀可以查看请求的详细信息
(django_rest_framework) [root@localhost django_rest_framework]# http --json POST https://127.0.0.1:80/snippets/ code="hello world post json data" --debug
HTTPie 0.9.9
Requests 2.18.4
Pygments 2.2.0
Python 3.6.3 (default, Nov  4 2017, 22:19:41) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-16)]
/root/.pyenv/versions/3.6.3/envs/django_rest_framework/bin/python
Linux 3.10.0-693.el7.x86_64

<Environment {
    "colors": 8,
    "config": {
        "__meta__": {
            "about": "HTTPie configuration file",
            "help": "https://httpie.org/docs#config",
            "httpie": "0.9.9"
        },
        "default_options": "[]"
    },
    "config_dir": "/root/.httpie",
    "is_windows": false,
    "stderr": "<_io.TextIOWrapper name='<stderr>' mode='w' encoding='UTF-8'>",
    "stderr_isatty": true,
    "stdin": "<_io.TextIOWrapper name='<stdin>' mode='r' encoding='UTF-8'>",
    "stdin_encoding": "UTF-8",
    "stdin_isatty": true,
    "stdout": "<_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'>",
    "stdout_encoding": "UTF-8",
    "stdout_isatty": true
}>

>>> requests.request(**{
    "allow_redirects": false,
    "auth": "None",
    "cert": "None",
    "data": "{\"code\": \"hello world post json data\"}",
    "files": {},
    "headers": {
        "Accept": "application/json, */*",
        "Content-Type": "application/json",
        "User-Agent": "HTTPie/0.9.9"
    },
    "method": "post",
    "params": {},
    "proxies": {},
    "stream": true,
    "timeout": 30,
    "url": "https://127.0.0.1:80/snippets/",
    "verify": true
})

HTTP/1.0 201 Created
Allow: POST, GET, OPTIONS
Content-Length: 110
Content-Type: application/json
Date: Mon, 20 Nov 2017 19:00:45 GMT
Server: WSGIServer/0.2 CPython/3.6.3
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

{
    "code": "hello world post json data",
    "id": 9,
    "language": "python",
    "linenos": false,
    "style": "friendly",
    "title": ""
}
  • 在浏览器中发送请求
  1. 在浏览器中发送请求,默认会返回html类型的数据

  2. 可以像之前那样,加上url后缀,来请求json数据

关于

本人是初学Django REST framework,Django REST framework 学习纪要系列文章是我从官网文档学习后的初步消化成果,如有错误,欢迎指正。

学习用代码Github仓库:shelmingsong/django_rest_framework

本文参考的官网文档:Tutorial 2: Requests and Responses

博客更新地址

依赖

  • Python 3.6.3 [ Python (2.7, 3.2, 3.3, 3.4, 3.5, 3.6) 都可以 ]
  • Django 1.11.7 [ Django (1.10, 1.11, 2.0 alpha) 都可以 ]

安装、创建并配置虚拟环境

mkdir django_rest_framework && cd django_rest_framework
pyenv virtualenv 3.6.3 django_rest_framework
pyenv local django_rest_framework
  • 安装相关包
pip install django djangorestframework
pip install pygments  # 项目中会用到,提供代码高亮功能

Django项目初始化

  • 创建tutorial项目与snippets应用
django-admin startproject tutorial
cd tutorial/

python manage.py startapp snippets
  • snippets应用和rest_framework添加进settings.pyINSTALLED_APPS (如果Django版本小于1.9,则需要将snippets.apps.SnippetsConfig替换为snippets)
INSTALLED_APPS = (
    ...
    'rest_framework',
    'snippets.apps.SnippetsConfig',
)

创建Model

我们需要创建一个Snippet模型来存储代码片段。
* 修改snippets/models.py文件

from django.db import models
from pygments.lexers import get_all_lexers
from pygments.styles import get_all_styles

# 提取出了pygments支持的所有语言的词法分析程序
LEXERS = [item for item in get_all_lexers() if item[1]]
# 提取出了pygments支持的所有语言列表
LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])
# 提取出了pygments支持的所有格式化风格列表
STYLE_CHOICES = sorted((item, item) for item in get_all_styles())


class Snippet(models.Model):
    created = models.DateTimeField(auto_now_add=True)  # 创建时间
    title = models.CharField(max_length=100, blank=True, default='')  # 标题
    code = models.TextField()  # 代码
    linenos = models.BooleanField(default=False)  # 是否显示行号
    language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100)  # 语言
    style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100)  # 格式化风格

    class Meta:
        ordering = ('created',)
  • 数据库迁移
python manage.py makemigrations snippets
python manage.py migrate

创建Serializer类

  • snippets文件夹内创建serializers.py文件
from rest_framework import serializers
from snippets.models import LANGUAGE_CHOICES
from snippets.models import Snippet
from snippets.models import STYLE_CHOICES


class SnippetSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    title = serializers.CharField(required=False, allow_blank=True, max_length=100)
    code = serializers.CharField(style={'base_template': 'textarea.html'})
    linenos = serializers.BooleanField(required=False)
    language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default='python')
    style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly')

    def create(self, validated_data):
        """
        Create and return a new `Snippet` instance, given the validated data.
        """
        return Snippet.objects.create(**validated_data)

    def update(self, instance, validated_data):
        """
        Update and return an existing `Snippet` instance, given the validated data.
        """
        instance.title = validated_data.get('title', instance.title)
        instance.code = validated_data.get('code', instance.code)
        instance.linenos = validated_data.get('linenos', instance.linenos)
        instance.language = validated_data.get('language', instance.language)
        instance.style = validated_data.get('style', instance.style)
        instance.save()
        return instance

使用Serializers类

  • 打开Django shell
python manage.py shell
  • 创建两个Serializers对象
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser

snippet = Snippet(code='foo = "bar"\n')
snippet.save()

snippet = Snippet(code='print "hello, world"\n')
snippet.save()

序列化

  • 序列化一个对象
In [11]: serializer = SnippetSerializer(snippet)

In [12]: serializer.data
Out[12]: 
ReturnDict([('id', 2),
            ('title', ''),
            ('code', 'print "hello, world"\n'),
            ('linenos', False),
            ('language', 'python'),
            ('style', 'friendly')])
  • 将data数据转换为json
In [13]: content = JSONRenderer().render(serializer.data)

In [14]: content
Out[14]: b'{"id":2,"title":"","code":"print \\"hello, world\\"\\n","linenos":false,"language":"python","style":"friendly"}'

反序列化

  • 将JSON数据转为Python原生数据类型(字典)
In [15]: from django.utils.six import BytesIO

In [16]: stream = BytesIO(content)

In [17]: data = JSONParser().parse(stream)

In [18]: data
Out[18]: 
{'code': 'print "hello, world"\n',
 'id': 2,
 'language': 'python',
 'linenos': False,
 'style': 'friendly',
 'title': ''}
  • 将Python原生数据类型(字典)转换为对象
In [19]: serializer = SnippetSerializer(data=data)

In [20]: serializer.is_valid()
Out[20]: True

In [21]: serializer.validated_data
Out[21]: 
OrderedDict([('title', ''),
             ('code', 'print "hello, world"'),
             ('linenos', False),
             ('language', 'python'),
             ('style', 'friendly')])

In [22]: serializer.save()
Out[22]: <Snippet: Snippet object>
  • 可以序列化查询集,只需要添加many=True参数
In [23]: serializer = SnippetSerializer(Snippet.objects.all(), many=True)

In [24]: serializer.data
Out[24]: [OrderedDict([('id', 1), ('title', ''), ('code', 'foo = "bar\n"'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 2), ('title', ''), ('code', 'print "hello, world"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 3), ('title', ''), ('code', 'print "hello, world"'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])]

使用ModelSerializer

刚才定义的SnippetSerializer类中的类中有很多是和之前在models.py定义的内容重复,其实我们还可以使SnippetSerializer类继承于ModelSerializer,来减少这种重复定义。

  • SnippetSerializer类重新定义,为如下内容:
class SnippetSerializer(serializers.ModelSerializer):
    class Meta:
        model = Snippet
        fields = ('id', 'title', 'code', 'linenos', 'language', 'style')
  • 打开Django shell
python manage.py shell
  • 查看SnippetSerializer
In [1]: from snippets.serializers import SnippetSerializer

In [2]: serializer = SnippetSerializer()

In [3]: print(repr(serializer))
SnippetSerializer():
    id = IntegerField(label='ID', read_only=True)
    title = CharField(allow_blank=True, max_length=100, required=False)
    code = CharField(style={'base_template': 'textarea.html'})
    linenos = BooleanField(required=False)
    language = ChoiceField(choices=[('abap', 'ABAP'), ('abnf', 'ABNF'), ('ada', 'Ada'), ('adl', 'ADL'), ('agda', 'Agda'), ('aheui', 'Aheui'), ('ahk', 'autohotkey'),
 ...

至此我们发现,继承于ModelSerializer类,有两个非常大的作用:
1. 不用重复去定义类属性
2. 自动拥有create()update()方法

Serializer在Django views内的简单运用

  • 此处未使用REST framework的其它特性,仅使用常规的Django views
  • 此处的写法有很多漏洞,仅作为教程中测试展示使用,不能直接应用于生产环境

配置views

打开snippets/views.py
* 导入相关包

from django.shortcuts import render
from django.http import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
  • 创建一个返回所有对象和对象增功能的API
@csrf_exempt
def snippet_list(request):
    """
    List all code snippets, or create a new snippet.
    """
    if request.method == 'GET':
        snippets = Snippet.objects.all()
        serializer = SnippetSerializer(snippets, many=True)
        return JsonResponse(serializer.data, safe=False)

    elif request.method == 'POST':
        data = JSONParser().parse(request)
        serializer = SnippetSerializer(data=data)
        if serializer.is_valid():
            serializer.save()
            return JsonResponse(serializer.data, status=201)
        return JsonResponse(serializer.errors, status=400)
  • 创建一个对象查、改、删功能的API
@csrf_exempt
def snippet_detail(request, pk):
    """
    Retrieve, update or delete a code snippet.
    """
    try:
        snippet = Snippet.objects.get(pk=pk)
    except Snippet.DoesNotExist:
        return HttpResponse(status=404)

    if request.method == 'GET':
        serializer = SnippetSerializer(snippet)
        return JsonResponse(serializer.data)

    elif request.method == 'PUT':
        data = JSONParser().parse(request)
        serializer = SnippetSerializer(snippet, data=data)
        if serializer.is_valid():
            serializer.save()
            return JsonResponse(serializer.data)
        return JsonResponse(serializer.errors, status=400)

    elif request.method == 'DELETE':
        snippet.delete()
        return HttpResponse(status=204)

配置urls

  • 创建snippets/urls.py
from django.conf.urls import url
from snippets import views

urlpatterns = [
    url(r'^snippets/$', views.snippet_list),
    url(r'^snippets/(?P<pk>[0-9]+)/$', views.snippet_detail),
]
  • 配置项目根urls.py
from django.conf.urls import url
from django.conf.urls import include
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^', include('snippets.urls')),
]

测试接口

  • 启动服务
(django_rest_framework) [root@localhost tutorial]# python manage.py runserver
Performing system checks...

System check identified no issues (0 silenced).
November 20, 2017 - 23:01:29
Django version 1.11.7, using settings 'tutorial.settings'
Starting development server at https://127.0.0.1:8000/
Quit the server with CONTROL-C.
  • 打开另一个shell窗口
  • 安装httpie包,用于模拟http请求

pip install httpie
  • 发送请求,获取所有snippets对象
(django_rest_framework) [root@localhost tutorial]# http https://127.0.0.1:8000/snippets/
HTTP/1.0 200 OK
Content-Length: 352
Content-Type: application/json
Date: Mon, 20 Nov 2017 15:03:10 GMT
Server: WSGIServer/0.2 CPython/3.6.3
X-Frame-Options: SAMEORIGIN

[
    {
        "code": "foo = \"bar\n\"",
        "id": 1,
        "language": "python",
        "linenos": false,
        "style": "friendly",
        "title": ""
    },
    {
        "code": "print \"hello, world\"\n",
        "id": 2,
        "language": "python",
        "linenos": false,
        "style": "friendly",
        "title": ""
    },
]
  • 发送请求,获取主键为2的snippets对象
(django_rest_framework) [root@localhost tutorial]# http https://127.0.0.1:8000/snippets/2/
HTTP/1.0 200 OK
Content-Length: 119
Content-Type: application/json
Date: Mon, 20 Nov 2017 15:03:17 GMT
Server: WSGIServer/0.2 CPython/3.6.3
X-Frame-Options: SAMEORIGIN

{
    "code": "print \"hello, world\"\n",
    "id": 2,
    "language": "python",
    "linenos": false,
    "style": "friendly",
    "title": ""
}

关于

本人是初学Django REST framework,Django REST framework 学习纪要系列文章是我从官网文档学习后的初步消化成果,如有错误,欢迎指正。

学习用代码Github仓库:shelmingsong/django_rest_framework

本文参考的官网文档:Tutorial 1: Serialization

博客更新地址

环境

CentOS-7-x86_64-Minimal-1708

Pyenv介绍

  • 可以实现多版本Python并存
  • 使用插件后,可以为不同的项目创建不同的虚拟环境

Pyenv安装

  • 安装依赖
yum -y install gcc git zlib-devel bzip2 bzip2-devel readline-devel sqlite sqlite-devel openssl-devel xz xz-devel
  • 下载pyenv源代码
git clone git://github.com/yyuu/pyenv.git ~/.pyenv
  • 添加环境变量
cat << "EOF" >> ~/.bashrc
export PYENV_ROOT="${HOME}/.pyenv"

if [ -d "${PYENV_ROOT}" ]; then
  export PATH="${PYENV_ROOT}/bin:${PATH}"
  eval "$(pyenv init -)"
fi
EOF

source ~/.bashrc
  • Done

安装指定Python版本(以Python 3.6.3为例)

  • 从国内镜像源下载Python指定版本
wget https://mirrors.sohu.com/python/3.6.3/Python-3.6.3.tar.xz  -P ~/.pyenv/cache
  • 安装指定Python版本(-v表示显示安装过程,可省略)
pyenv install 3.6.3 -v
  • 切换pip镜像源为国内镜像
  1. pyenv在安装python的时候,已经自动将pip安装好了
mkdir ~/.pip

cat << "EOF" >> ~/.pip/pip.conf
[global]
timeout = 6000
index-url = https://pypi.douban.com/simple
trusted-host = pypi.douban.com
EOF
  • Done

Pyenv常用命令

  • 查询所有可以安装的版本
pyenv install --list
  • 安装指定版本
  1. 建议按照上面的步骤,先从国内镜像下载然后再安装,否则会非常慢甚至中断
pyenv install 3.6.3
  • 卸载指定版本
pyenv uninstall 2.7.13
  • 显示已安装的所有版本
  1. 最前面带*的表示当前生效的版本
pyenv versions
  • 显示当前生效的版本
pyenv version
  • 设置全局(整个系统生效)Python版本
pyenv global 3.6.3
  • 设置多个全局(整个系统生效)Python版本
  1. 后面的版本号排序有先后,在前表示默认版本
# 方案1
pyenv global 3.6.3 2.7.13

# 方案1效果如下
python --version
Python 3.6.3

python3.6 --version
Python 3.6.3

python2.7 --version
Python 2.7.13

# 方案2
pyenv global 2.7.13 3.6.3

# 方案2效果如下
python --version
Python 2.7.13

python3.6 --version
Python 3.6.3

python2.7 --version
Python 2.7.13
  • 设置局部(当前目录生效)Python版本
pyenv local 3.6.3
  • 设置多个局部(当前目录生效)Python版本
  1. 后面的版本号排序有先后,在前表示默认版本
# 方案1
pyenv local 3.6.3 2.7.13

# 方案1效果如下
python --version
Python 3.6.3

python3.6 --version
Python 3.6.3

python2.7 --version
Python 2.7.13

# 方案2
pyenv local 2.7.13 3.6.3

# 方案2效果如下
python --version
Python 2.7.13

python3.6 --version
Python 3.6.3

python2.7 --version
Python 2.7.13
  • 取消设置局部(当前目录生效)Python版本
pyenv local --unset

pyenv-virtualenv(Pyenv插件)介绍

  • 可以为不同的项目创建不同的虚拟环境
  • 设置环境变量后,可进入指定目录自动激活虚拟环境

pyenv-virtualenv安装

  • 下载pyenv-virtualenv源代码
git clone git://github.com/yyuu/pyenv-virtualenv.git ~/.pyenv/plugins/pyenv-virtualenv

source ~/.bashrc
  • 添加环境变量(进入指定目录自动激活虚拟环境)
echo 'eval "$(pyenv virtualenv-init -)"' >> ~/.bashrc

source ~/.bashrc

pyenv-virtualenv常用命令

  • 基于指定版本创建虚拟环境(推荐)
pyenv virtualenv 3.6.3 venv_name
  • 基于当前版本创建虚拟环境(不推荐)
pyenv virtualenv venv_name
  • 设置当前目录的虚拟环境(推荐)
  1. 若按照之前的步骤设置了环境变量,则会在进入目录后自动激活虚拟环境
pyenv local venv_name
  • 取消设置当前目录的虚拟环境(推荐)
pyenv local --unset
  • 手动激活虚拟环境(不推荐)
pyenv activate venv_name
  • 手动停用虚拟环境(不推荐)
pyenv deactivate venv_name
  • 显示所有已创建的虚拟环境
  1. 不一定已在当前目录激活
  2. 一个虚拟环境会显示两条记录
pyenv virtualenvs

博客更新地址

0 环境

CentOS-7-x86_64-Minimal-1708

1 部署环境

CentOS-7-x86_64-Minimal-1708
阿里云下载源:https://mirrors.aliyun.com/centos/7.4.1708/isos/x86_64/CentOS-7-x86_64-Minimal-1708.iso

2 系统环境初始化

解决yum包无法正常安装和无法正常ssh的问题

vi /etc/sysconfig/network-scripts/ifcfg-ens33

ONBOOT=no改为ONBOOT=yes
(文件名不一定为ifcfg-ens33,不同的机器可能不一样,一般是以ifcfg开头的。)

重启网络

systemctl restart network

无法使用ifconfig查看IP

yum install net-tools

3 部署内容

部署说明

环境部署较为繁琐,因而写了个自动化部署环境的脚本。

由于MySQL服务器较为不稳定,因而用yum install --downloadonly命令将安装mysql所需的rpm包下了下来,放在了required_rpms文件夹内,和脚本放在了一起,在脚本中会自动调用。

shell脚本的内容都上传至了GitHub,但所需的MySQL的rpm包因为文件过大(部分文件超过了100M无法上传),所以gitignore掉了,把脚本的完整内容放在了百度网盘,大家直接在上一步初始化好的环境下运行即可。

自动化部署脚本下载地址

(下载地址可能会变,如若收藏请收藏文章链接,脚本下载链接如果改变会及时更新)
链接:https://pan.baidu.com/s/1gf4ETbl
密码:0mxe

自动化部署脚本执行命令

cd shell/
chmod 744 *.sh
chmod 744 *.exp
source 0_start.sh

自动化部署脚本所需时间概览

自动化部署脚本执行内容概览

  • 更新安装镜像源为中科大镜像
  • 切换pip安装镜像源为豆瓣镜像
  • 更新防火墙规则:
    1 关闭selinux
    2 开放firewalld的80,3306端口
  • 安装包:
    1 wget # 就是wget
    2 expect # 处理脚本运行中存在的交互命令
    3 git # 就是git
    4 pyenvpython, pip # python版本管理工具
    5 pyenv virtuallenv # 虚拟环境
    6 docker # Docker
    7 docker-compose # Docker官方工具
    8 bashrc_docker # Docker的命令包
    9 mysql # 为了数据持久化,数据库需建立在Docker外
    10 ntpd # 设置并同步时区
  • 安装pip包
    1 Django==1.11.7 # 安装这个就够了,其它的反正是要进Docker的

博客更新地址

本系列的其它文章

个人技术博客项目开发纪要__序

环境

CentOS-7-x86_64-Minimal-1708

开发原因

  • 现有博客基于Hexo&Github,想搭建一个自己全栈开发的博客
  • 用于实验新技术
  • 阿里云买了得用起来

技术栈

  • 下述版本号为待定,但如果使用其它版本的话只会用更新的
  • 如有用到其它技术会及时更新下表
工具 版本 用途
Docker 17.06.2 开发环境容器化
Python 3.6.3 后端开发语言
Django 1.11.7 后端框架
Html/CSS/JS / 前端开发语言
jQuery 3.2.1 前端框架
Bootstrap 3.3.7 前端框架
Celery 4.1.0 异步任务
django-celery-beat 1.0.1 定时任务
Gunicorn 19.7.1 Web服务器
Nginx 1.12.2 负载均衡
MySQL 5.7.20 数据库
Redis 3.2 缓存&&消息队列
shell / 自动化部署脚本基础
expect 5.45-14 自动化部署脚本处理交互

开发工具

  • 如有用到其它工具会及时更新下表
工具 用途
Pycharm IDE
Navicat 数据库调试
xshell ssh本地虚拟机和远程服务器
xftp 与本地虚拟机和远程服务器传输文件
VMware Workstation 虚拟机
Postman 接口调试
Chrome 前端调试
SwitchHosts 切换hosts
Sublime 草稿
印象笔记/马克飞象 笔记
网易云音乐 安抚Debug时暴躁的心

代码仓库地址

https://github.com/shelmingsong/Blog

博客更新地址

环境

Ubuntu 16.04

Python 3.5.2

问题

现有一个生成1-5随机数的函数random_5(概率平均),如何将此转换为生成1-7随机数random_7的函数(概率平均),不得使用random模块

思路一(False)

直接将random_5生成的随机数乘以7/5后再通过round函数进行四舍五入后输出

random_5输出 random_7输出
1 1
2 3
3 4
4 6
5 7

特别糟糕的想法…

思路二(False)

生成两次random_5,将两者的值相加后减一(random_5 + random_5 - 1),得出随机数1-9,而后再做一次判断,将8和9剔除掉,只在返回1-7的时候输出

不过很快就发现,只有在两次random_5都返回1的时候,才会返回1,概率远小于返回其它六个数

又是一个糟糕的想法…

思路三(True)


* 通过之前的两种思路,可以总结出将random_5返回的数据经过各种处理后不筛选返回1-7是不可能实现的(不能说不可能,只是我个人能力有限,暂时还没想出方法),那必须采用思路二中的筛选方法。
* 确定要使用筛选方法之后,目标就变成了生成等概率的、值取值范围大于1-7的随机数。
* 相加和返回值乘系数的方法都尝试过了,那么是否可以将两者结合一下呢。
* 首先思考乘系数,random_5 * 2 - 1的返回结果是[1, 3, 5, 7, 9],想要等概率生成1-10的话,比如再将此返回值加上随机生成的0和1,也就是random.randint(0, 1)
* 因不允许使用random模块,所以我们必须想办法重复利用原有的random_5函数,random_5 * 3 - 2需要对应的random.randint(0, 2),也可以理解为random.randint(1, 3) - 1
* 此时,显而易见,random_5 * 5 - 4需要对应的random.randint(1, 5) - 1,也就是random_5 - 1,那么基本的函数体就出来了,就是

random_5 * 5 + random_5 - 5
  • 再将筛选的逻辑加进去,完整代码如下:
import random


# 原有生成1-5随机数的函数
def random_5():
    return random.randint(1, 5)


# 要获得的生成1~7随机数的函数
def random_7():
    while True:  # 避免没有返回值
        n = (random_5()-1)*5 + random_5()-1  # 生成0~24的随机数
        if n <= 20:
            return n % 7 + 1


if __name__ == '__main__':
    print(random_7())

进阶

刚才得出的核心代码,去掉常变量之后就变成了random_5 * 5 + random_5,看着很像二、八、十六进制转换为十进制的方法,也就是可以理解为五进制,通过这种思想我们可以得出一个通用的随机数转换方法,具体代码如下:

import random
import math


class RandomIJToRandomAB(object):
    """将原来i-j之间的随机数生成函数,转化为a-b之间的随机数生成函数"""
    def __init__(self, i, j):
        self.i = i
        self.j = j
        ij_dif = j-i

        if ij_dif <= 0:
            raise ValueError

        self.ij_dif = ij_dif

    def random_ij(self):
        return random.randint(0, self.ij_dif)

    def random_ab(self, a, b):
        ab_dif = b-a
        digit = int(math.ceil(math.log(ab_dif+1, self.ij_dif+1)))  # 获取差值(ab_dif)转换为ij_dif+1进制后的最大位数

        while True:  # 防止return None
            # 生成最大位数为digit的ij_dif+1进制随机数,而后转化为10进制
            random_res = 0
            for item in range(digit):
                random_res += self.random_ij() * pow(self.ij_dif+1, item)

            # 只取在区间范围内的随机数
            if random_res <= ab_dif:
                return random_res+a


if __name__ == '__main__':
    print('0~1随机生成函数转化为2~11随机生成函数')
    random_1 = RandomIJToRandomAB(0, 1)
    print(random_1.random_ab(2, 11))
    print('-'*50)

    print('1~5随机生成函数转化为1~7随机生成函数')
    random_1_5 = RandomIJToRandomAB(1, 5)
    print(random_1_5.random_ab(1, 7))
    print('-'*50)

    print('1~7随机生成函数转化为1~5随机生成函数')
    random_1_7 = RandomIJToRandomAB(1, 7)
    print(random_1_7.random_ab(1, 5))
    print('-'*50)

Github源码地址:

random5_to_random7:
https://github.com/shelmingsong/algorithm_practice/blob/master/31_random5_to_random7_20170824.py

random_ij_to_random_ab:
https://github.com/shelmingsong/algorithm_practice/blob/master/32_random_ij_to_random_ab_20170824.py