1. 程式人生 > >Django rest framework 的認證流程(原始碼分析一)

Django rest framework 的認證流程(原始碼分析一)

一、基本流程舉例:

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^users/', views.HostView.as_view()),
   
]
urls
from rest_framework.views import APIView
from rest_framework.response import Response
class HostView(APIView):
    def dispatch(self, request, *args, **kwargs):
        
""" 請求到來之後,都要執行dispatch方法,dispatch方法根據請求方式不同觸發 get/post/put等方法 注意:APIView中的dispatch方法有好多好多的功能 """ return super().dispatch(request, *args, **kwargs) def get(self, request, *args, **kwargs): return Response('GET請求,響應內容') def post(self, request, *args, **kwargs):
return Response('POST請求,響應內容') def put(self, request, *args, **kwargs): return Response('PUT請求,響應內容')
Views

二、原始碼分析:

當我們傳送請求後,執行views裡面對應的方法時,最開始執行的是dispatch方法

---------------------------------------------------------------------------------- 插敘-------------------------------------------------------------------------------------------------------------------

為什麼是diapach方法?

我們使用rest framework 框架是基於CBV做的,在url中

   url(r'^hosts/', views.HostView.as_view()),
    url(r'^auth/', views.AuthView.as_view()),
    url(r'^users/', views.Userview.as_view()),
    url(r'^sals/', views.Salview.as_view()),

進入as_view()

APIView類中的as_view(),注意加紅程式碼

複製程式碼
 @classmethod
    def as_view(cls, **initkwargs):
        """
        Store the original class on the view function.

        This allows us to discover information about the view when we do URL
        reverse lookups.  Used for breadcrumb generation.
        """
        if isinstance(getattr(cls, 'queryset', None), models.query.QuerySet):
            def force_evaluation():
                raise RuntimeError(
                    'Do not evaluate the `.queryset` attribute directly, '
                    'as the result will be cached and reused between requests. '
                    'Use `.all()` or call `.get_queryset()` instead.'
                )
            cls.queryset._fetch_all = force_evaluation

        view = super(APIView, cls).as_view(**initkwargs)
        view.cls = cls
        view.initkwargs = initkwargs

        # Note: session based authentication is explicitly CSRF validated,
        # all other authentication is CSRF exempt.
        return csrf_exempt(view)
複製程式碼

進入原生View類中

複製程式碼
    @classonlymethod
    def as_view(cls, **initkwargs):
        """
        Main entry point for a request-response process.
        """
        for key in initkwargs:
            if key in cls.http_method_names:
                raise TypeError("You tried to pass in the %s method name as a "
                                "keyword argument to %s(). Don't do that."
                                % (key, cls.__name__))
            if not hasattr(cls, key):
                raise TypeError("%s() received an invalid keyword %r. as_view "
                                "only accepts arguments that are already "
                                "attributes of the class." % (cls.__name__, key))

        def view(request, *args, **kwargs):
            self = cls(**initkwargs)
            if hasattr(self, 'get') and not hasattr(self, 'head'):
                self.head = self.get
            self.request = request
            self.args = args
            self.kwargs = kwargs
            return self.dispatch(request, *args, **kwargs)
        view.view_class = cls
        view.view_initkwargs = initkwargs

        # take name and docstring from class
        update_wrapper(view, cls, updated=())

        # and possible attributes set by decorators
        # like csrf_exempt from dispatch
        update_wrapper(view, cls.dispatch, assigned=())
        return view
複製程式碼

可以看到最後返回的是

return self.dispatch(request, *args, **kwargs)

所以訪問views中的類是先執行dispatch方法,然後再用呼叫 其他方法

---------------------------------------------------------------------------------- 插敘結束-------------------------------------------------------------------------------------------------------------------

 

自己定義的dispatch方法(當然自己可以不定義直接應用APIView裡面的就可以了,預設就是這種):

複製程式碼
    def dispatch(self, request, *args, **kwargs):
        """
        請求到來之後,都要執行dispatch方法,dispatch方法根據請求方式不同觸發 get/post/put等方法

        注意:APIView中的dispatch方法有好多好多的功能
        """
        return super().dispatch(request, *args, **kwargs)
複製程式碼

APIView中的dispatch方法(原始碼):

複製程式碼
    def dispatch(self, request, *args, **kwargs):
        """
        `.dispatch()` is pretty much the same as Django's regular dispatch,
        but with extra hooks for startup, finalize, and exception handling.
        """
        self.args = args
        self.kwargs = kwargs
        #1.將原來的request進行加工,增加了一些功能,將其放入restframework的Request
        '''
            request,
            parsers=self.get_parsers(),
            authenticators=self.get_authenticators(),
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        '''

          # 原來request物件,django.core.handlers.wsgi.WSGIRequest
          # 現在的request物件,rest_framework.request.Request

        request = self.initialize_request(request, *args, **kwargs)
        self.request = request
        self.headers = self.default_response_headers  # deprecate?

        try:
            #2.處理版本資訊 處理認證資訊 處理許可權資訊 對使用者的訪問頻率進行限制
            self.initial(request, *args, **kwargs)

            # Get the appropriate handler method
            #3.據使用者提交的請求方法利用反射獲取請求方法
            #http_method_names=['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
            if request.method.lower() in self.http_method_names:
                handler = getattr(self, request.method.lower(),
                                  self.http_method_not_allowed)
            else:
                handler = self.http_method_not_allowed
            #呼叫具體的方法做具體的操作,
            response = handler(request, *args, **kwargs)

        except Exception as exc:
            response = self.handle_exception(exc)

        #4.將處理後的response包裝
        self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response
複製程式碼

1.將原來的request進行加工,增加了一些功能,將其放入restframework的Request

request = self.initialize_request(request, *args, **kwargs)

進入initialize_request函式後:

複製程式碼
 def initialize_request(self, request, *args, **kwargs):
        """
        Returns the initial request object.
        """
        parser_context = self.get_parser_context(request)

        return Request(
            request,
            parsers=self.get_parsers(),
            authenticators=self.get_authenticators(),#用於使用者認證,為一個列表
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        )
複製程式碼

將這些東西前部都封裝在rest_framework的Resquest中,並返回她的物件request,從這以後我們呼叫的request物件不再是Django提供的request物件了,而是APIView的Resquest的物件,2.處理版本資訊 處理認證資訊 處理許可權資訊 對使用者的訪問頻率進行限制

self.initial(request, *args, **kwargs)

複製程式碼
    def initial(self, request, *args, **kwargs):
        """
        Runs anything that needs to occur prior to calling the method handler.
        """
        self.format_kwarg = self.get_format_suffix(**kwargs)

        # Perform content negotiation and store the accepted info on the request
        neg = self.perform_content_negotiation(request)
        request.accepted_renderer, request.accepted_media_type = neg

        # Determine the API version, if versioning is in use.
        #2.1處理版本資訊
        version, scheme = self.determine_version(request, *args, **kwargs)
        request.version, request.versioning_scheme = version, scheme

        # Ensure that the incoming request is permitted
        #2.2處理認證資訊
        self.perform_authentication(request)
        #2.3處理許可權資訊
        self.check_permissions(request)
        #2.4對使用者的訪問頻率進行限制
        self.check_throttles(request)
複製程式碼

3.據使用者提交的請求方法利用反射獲取請求方法,並改用請求方法實現其具體功能

4.將處理後的response進行包裝

三、處理認證的具體分析

當diapatch方法進行到第一步時,我們呼叫了initialize_request方法將request等封裝在Request中頂返回其物件,

複製程式碼
    def initialize_request(self, request, *args, **kwargs):
        """
        Returns the initial request object.
        """
        parser_context = self.get_parser_context(request)

        return Request(
            request,
            parsers=self.get_parsers(),
            authenticators=self.get_authenticators(),#用於使用者認證,為一個列表
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        )
複製程式碼

authenticators=self.get_authenticators() #用於使用者認證 是一個由authentication物件組成的列表

    def get_authenticators(self):
        """
        Instantiates and returns the list of authenticators that this view can use.
        """
        return [auth() for auth in self.authentication_classes]
authentication_classes為一個authentication類組成的列表,他預設是呼叫
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES

當然我們一般是自己定義或者配置到settings中,至此我們的得到authentication物件的列表,其封裝在Request物件中,

接的執行第二步

#2.處理版本資訊 處理認證資訊 處理許可權資訊 對使用者的訪問頻率進行限制
self.initial(request, *args, **kwargs)
 #2.2處理認證資訊
        self.perform_authentication(request)

檢視perform_authentication的原始碼如下

複製程式碼
    def perform_authentication(self, request):
        """
        Perform authentication on the incoming request.

        Note that if you override this and simply 'pass', then authentication
        will instead be performed lazily, the first time either
        `request.user` or `request.auth` is accessed.
        """
        request.user
複製程式碼

其呼叫了rest_framework中Request的user方法(這個方法肯定別@property裝飾,不然的話不可能直接不加括號的呼叫)

from rest_framework.request import Request

Request類中的user方法

複製程式碼
 @property
    def user(self):
        """
        Returns the user associated with the current request, as authenticated
        by the authentication classes provided to the request.
        """
        #判斷當前類中是否有已經認證過的user
        if not hasattr(self, '_user'):
            #沒有認證則去認證
            self._authenticate()
        #認證過了直接返回
        return self._user
複製程式碼

注意:user中的self代表的是request物件

沒認證的話執行 呼叫Request類中的_authenticate()方法

複製程式碼
 def _authenticate(self):
        """
        Attempt to authenticate the request using each authentication instance
        in turn.
        """
        #遍歷request物件中封裝的Authentication物件
        for authenticator in self.authenticators:
            try:
                #呼叫Authentication物件中的authenticate方法,必須要有這個方法不然丟擲異常
                #當然Authentication類一般有我們自己定義,實現這個方法就可以了
                user_auth_tuple = authenticator.authenticate(self)
            except exceptions.APIException:
                self._not_authenticated()
                raise

            if user_auth_tuple is not None:
                self._authenticator = authenticator
                self.user, self.auth = user_auth_tuple
                return

        self._not_authenticated()
複製程式碼

認證例項:

區域性認證:直接類中定義Authentication類

複製程式碼
from django.db import models

# Create your models here.
class Userinfo(models.Model):
    name=models.CharField(max_length=32,verbose_name='使用者名稱')
    pwd=models.CharField(max_length=32,verbose_name='密碼')
    token=models.CharField(max_length=64,null=True)

    def __str__(self):
        return self.name
複製程式碼
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^users/', views.HostView.as_view()),
    url(r'^auth/', views.AuthView.as_view()),
]
複製程式碼
import json
import hashlib
import time

from django.shortcuts import render, HttpResponse
from rest_framework.views import APIView
from rest_framework.authentication import SessionAuthentication
from rest_framework.authentication import BaseAuthentication
from rest_framework.request import Request
from rest_framework.exceptions import APIException
from rest_framework.response import Response

from app01 import models


# Create your views here.
class MyAuthentication(object):
    def authenticate(self, request):
        token = request.query_params.get('token')
        user=models.Userinfo.objects.filter(token=token).first()
        if user:
            return (user.name, 'aaaaaa')
        raise APIException('認證失敗')


class AuthView(APIView):
    # 設定為空標識不用認證
    authentication_classes = []

    def get(self, request, *args, **kwargs):
        dic = {'code': 1000, 'msg': ''}
        pwd = request.query_params.get('pwd')
        name = request.query_params.get('name')

        user = models.Userinfo.objects.filter(name=name, pwd=pwd).first()
        if not user:
            dic['msg'] = '使用者名稱或密碼錯誤'
            dic['code'] = 1001
            return Response(dic)
        t = time.time()
        key = '%s|%s' % (name, t)
        m = hashlib.md5()
        m.update(key.encode('utf-8'))
        token = m.hexdigest()

        user.token = token
        user.save()
        dic['token'] = token
        dic['msg'] = '登陸成功'
        return Response(dic)

class HostView(APIView):
    authentication_classes=[MyAuthentication,]
    def dispatch(self, request, *args, **kwargs):
        """
        請求到來之後,都要執行dispatch方法,dispatch方法根據請求方式不同觸發 get/post/put等方法

        注意:APIView中的dispatch方法有好多好多的功能
        """
        return super().dispatch(request, *args, **kwargs)

    def get(self, request, *args, **kwargs):
        return Response('GET請求,響應內容')

    def post(self, request, *args, **kwargs):
        return Response('POST請求,響應內容')

    def put(self, request, *args, **kwargs):
       
        return Response('PUT請求,響應內容')
複製程式碼

全域性認證;在settings中配置

from django.db import models

# Create your models here.
class Userinfo(models.Model):
    name=models.CharField(max_length=32,verbose_name='使用者名稱')
    pwd=models.CharField(max_length=32,verbose_name='密碼')
    token=models.CharField(max_length=64,null=True)

    def __str__(self):
        return self.name
models
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^users/', views.HostView.as_view()),
    url(r'^auth/', views.AuthView.as_view()),
]
urls
from rest_framework.exceptions import APIException
from rest_framework.response import Response
from rest_framework.authentication import BaseAuthentication

from app01 import models



class MyAuthentication(BaseAuthentication):
    def authenticate(self, request):
        token = request.query_params.get('token')
        user=models.Userinfo.objects.filter(token=token).first()
        if user:
            return (user.name, 'aaaaaa')
        raise APIException('認證失敗')
utils
REST_FRAMEWORK = {
    'UNAUTHENTICATED_USER': None,
    'UNAUTHENTICATED_TOKEN': None,
    "DEFAULT_AUTHENTICATION_CLASSES": [
        "app01.utils.MyAuthentication",
    ],
}
settings中

 

一、基本流程舉例:

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^users/', views.HostView.as_view()),
   
]
urls
from rest_framework.views import APIView
from rest_framework.response import Response
class HostView(APIView):
    def dispatch(self, request, *args, **kwargs):
        """
        請求到來之後,都要執行dispatch方法,dispatch方法根據請求方式不同觸發 get/post/put等方法

        注意:APIView中的dispatch方法有好多好多的功能
        """
        return super().dispatch(request, *args, **kwargs)

    def get(self, request, *args, **kwargs):
        return Response('GET請求,響應內容')

    def post(self, request, *args, **kwargs):
        return Response('POST請求,響應內容')

    def put(self, request, *args, **kwargs):
        return Response('PUT請求,響應內容')
Views

二、原始碼分析:

當我們傳送請求後,執行views裡面對應的方法時,最開始執行的是dispatch方法

---------------------------------------------------------------------------------- 插敘-------------------------------------------------------------------------------------------------------------------

為什麼是diapach方法?

我們使用rest framework 框架是基於CBV做的,在url中

   url(r'^hosts/', views.HostView.as_view()),
    url(r'^auth/', views.AuthView.as_view()),
    url(r'^users/', views.Userview.as_view()),
    url(r'^sals/', views.Salview.as_view()),

進入as_view()

APIView類中的as_view(),注意加紅程式碼

複製程式碼
 @classmethod
    def as_view(cls, **initkwargs):
        """
        Store the original class on the view function.

        This allows us to discover information about the view when we do URL
        reverse lookups.  Used for breadcrumb generation.
        """
        if isinstance(getattr(cls, 'queryset', None), models.query.QuerySet):
            def force_evaluation():
                raise RuntimeError(
                    'Do not evaluate the `.queryset` attribute directly, '
                    'as the result will be cached and reused between requests. '
                    'Use `.all()` or call `.get_queryset()` instead.'
                )
            cls.queryset._fetch_all = force_evaluation

        view = super(APIView, cls).as_view(**initkwargs)
        view.cls = cls
        view.initkwargs = initkwargs

        # Note: session based authentication is explicitly CSRF validated,
        # all other authentication is CSRF exempt.
        return csrf_exempt(view)
複製程式碼

進入原生View類中

複製程式碼
    @classonlymethod
    def as_view(cls, **initkwargs):
        """
        Main entry point for a request-response process.
        """
        for key in initkwargs:
            if key in cls.http_method_names:
                raise TypeError("You tried to pass in the %s method name as a "
                                "keyword argument to %s(). Don't do that."
                                % (key, cls.__name__))
            if not hasattr(cls, key):
                raise TypeError("%s() received an invalid keyword %r. as_view "
                                "only accepts arguments that are already "
                                "attributes of the class." % (cls.__name__, key))

        def view(request, *args, **kwargs):
            self = cls(**initkwargs)
            if hasattr(self, 'get') and not hasattr(self, 'head'):
                self.head = self.get
            self.request = request
            self.args = args
            self.kwargs = kwargs
            return self.dispatch(request, *args, **kwargs)
        view.view_class = cls
        view.view_initkwargs = initkwargs

        # take name and docstring from class
        update_wrapper(view, cls, updated=())

        # and possible attributes set by decorators
        # like csrf_exempt from dispatch
        update_wrapper(view, cls.dispatch, assigned=())
        return view
複製程式碼

可以看到最後返回的是

return self.dispatch(request, *args, **kwargs)

所以訪問views中的類是先執行dispatch方法,然後再用呼叫 其他方法

---------------------------------------------------------------------------------- 插敘結束-------------------------------------------------------------------------------------------------------------------

 

自己定義的dispatch方法(當然自己可以不定義直接應用APIView裡面的就可以了,預設就是這種):

複製程式碼
    def dispatch(self, request, *args, **kwargs):
        """
        請求到來之後,都要執行dispatch方法,dispatch方法根據請求方式不同觸發 get/post/put等方法

        注意:APIView中的dispatch方法有好多好多的功能
        """
        return super().dispatch(request, *args, **kwargs)
複製程式碼

APIView中的dispatch方法(原始碼):

複製程式碼
    def dispatch(self, request, *args, **kwargs):
        """
        `.dispatch()` is pretty much the same as Django's regular dispatch,
        but with extra hooks for startup, finalize, and exception handling.
        """
        self.args = args
        self.kwargs = kwargs
        #1.將原來的request進行加工,增加了一些功能,將其放入restframework的Request
        '''
            request,
            parsers=self.get_parsers(),
            authenticators=self.get_authenticators(),
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        '''

          # 原來request物件,django.core.handlers.wsgi.WSGIRequest
          # 現在的request物件,rest_framework.request.Request

        request = self.initialize_request(request, *args, **kwargs)
        self.request = request
        self.headers = self.default_response_headers  # deprecate?

        try:
            #2.處理版本資訊 處理認證資訊 處理許可權資訊 對使用者的訪問頻率進行限制
            self.initial(request, *args, **kwargs)

            # Get the appropriate handler method
            #3.據使用者提交的請求方法利用反射獲取請求方法
            #http_method_names=['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
            if request.method.lower() in self.http_method_names:
                handler = getattr(self, request.method.lower(),
                                  self.http_method_not_allowed)
            else:
                handler = self.http_method_not_allowed
            #呼叫具體的方法做具體的操作,
            response = handler(request, *args, **kwargs)

        except Exception as exc:
            response = self.handle_exception(exc)

        #4.將處理後的response包裝
        self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response
複製程式碼

1.將原來的request進行加工,增加了一些功能,將其放入restframework的Request

request = self.initialize_request(request, *args, **kwargs)

進入initialize_request函式後:

複製程式碼
 def initialize_request(self, request, *args, **kwargs):
        """
        Returns the initial request object.
        """
        parser_context = self.get_parser_context(request)

        return Request(
            request,
            parsers=self.get_parsers(),
            authenticators=self.get_authenticators(),#用於使用者認證,為一個列表
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        )
複製程式碼

將這些東西前部都封裝在rest_framework的Resquest中,並返回她的物件request,從這以後我們呼叫的request物件不再是Django提供的request物件了,而是APIView的Resquest的物件,2.處理版本資訊 處理認證資訊 處理許可權資訊 對使用者的訪問頻率進行限制

self.initial(request, *args, **kwargs)

複製程式碼
    def initial(self, request, *args, **kwargs):
        """
        Runs anything that needs to occur prior to calling the method handler.
        """
        self.format_kwarg = self.get_format_suffix(**kwargs)

        # Perform content negotiation and store the accepted info on the request
        neg = self.perform_content_negotiation(request)
        request.accepted_renderer, request.accepted_media_type = neg

        # Determine the API version, if versioning is in use.
        #2.1處理版本資訊
        version, scheme = self.determine_version(request, *args, **kwargs)
        request.version, request.versioning_scheme = version, scheme

        # Ensure that the incoming request is permitted
        #2.2處理認證資訊
        self.perform_authentication(request)
        #2.3處理許可權資訊
        self.check_permissions(request)
        #2.4對使用者的訪問頻率進行限制
        self.check_throttles(request)
複製程式碼

3.據使用者提交的請求方法利用反射獲取請求方法,並改用請求方法實現其具體功能

4.將處理後的response進行包裝

三、處理認證的具體分析

當diapatch方法進行到第一步時,我們呼叫了initialize_request方法將request等封裝在Request中頂返回其物件,

複製程式碼
    def initialize_request(self, request, *args, **kwargs):
        """
        Returns the initial request object.
        """
        parser_context = self.get_parser_context(request)

        return Request(
            request,
            parsers=self.get_parsers(),
            authenticators=self.get_authenticators(),#用於使用者認證,為一個列表
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        )
複製程式碼

authenticators=self.get_authenticators() #用於使用者認證 是一個由authentication物件組成的列表

    def get_authenticators(self):
        """
        Instantiates and returns the list of authenticators that this view can use.
        """
        return [auth() for auth in self.authentication_classes]
authentication_classes為一個authentication類組成的列表,他預設是呼叫
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES

當然我們一般是自己定義或者配置到settings中,至此我們的得到authentication物件的列表,其封裝在Request物件中,

接的執行第二步

#2.處理版本資訊 處理認證資訊 處理許可權資訊 對使用者的訪問頻率進行限制
self.initial(request, *args, **kwargs)
 #2.2處理認證資訊
        self.perform_authentication(request)

檢視perform_authentication的原始碼如下

複製程式碼
    def perform_authentication(self, request):
        """
        Perform authentication on the incoming request.

        Note that if you override this and simply 'pass', then authentication
        will instead be performed lazily, the first time either
        `request.user` or `request.auth` is accessed.
        """
        request.user
複製程式碼

其呼叫了rest_framework中Request的user方法(這個方法肯定別@property裝飾,不然的話不可能直接不加括號的呼叫)

from rest_framework.request import Request

Request類中的user方法

複製程式碼
 @property
    def user(self):
        """
        Returns the user associated with the current request, as authenticated
        by the authentication classes provided to the request.
        """
        #判斷當前類中是否有已經認證過的user
        if not hasattr(self, '_user'):
            #沒有認證則去認證
            self._authenticate()
        #認證過了直接返回
        return self._user
複製程式碼

注意:user中的self代表的是request物件

沒認證的話執行 呼叫Request類中的_authenticate()方法

複製程式碼
 def _authenticate(self):
        """
        Attempt to authenticate the request using each authentication instance
        in turn.
        """
        #遍歷request物件中封裝的Authentication物件
        for authenticator in self.authenticators:
            try:
                #呼叫Authentication物件中的authenticate方法,必須要有這個方法不然丟擲異常
                #當然Authentication類一般有我們自己定義,實現這個方法就可以了
                user_auth_tuple = authenticator.authenticate(self)
            except exceptions.APIException:
                self._not_authenticated()
                raise

            if user_auth_tuple is not None:
                self._authenticator = authenticator
                self.user, self.auth = user_auth_tuple
                return

        self._not_authenticated()
複製程式碼

認證例項:

區域性認證:直接類中定義Authentication類

複製程式碼
from django.db import models

# Create your models here.
class Userinfo(models.Model):
    name=models.CharField(max_length=32,verbose_name='使用者名稱')
    pwd=models.CharField(max_length=32,verbose_name='密碼')
    token=models.CharField(max_length=64,null=True)

    def __str__(self):
        return self.name
複製程式碼
urlpatterns = [
    url(r'^adm