Учебник по разработке полного стека Django 03 - разделение интерфейса YaDjangoBlog

Node.js Python Django Vue.js
Учебник по разработке полного стека Django 03 - разделение интерфейса YaDjangoBlog

0x00 Предисловие

Эта статья является третьей частью полного руководства по разработке Django.

Оглавление находится здесь, а обновленные статьи выглядят следующим образом.

В этой статье должны быть четыре вещи:

  • Прежде всего, нужно интерпретировать DjangoRestFramework, представить необходимость использования DRF на простом примере и кратко представить реализацию DRF в CBV.
  • Второе, кратко представим использование DRF в этом проекте YaDjangoBlog
  • В-третьих, кратко расскажите о спецификации RESTFULAPI и дайте ссылку на передовой опыт.
  • Четвертое — кратко интерпретировать код процесса обработки запроса Django.

PS: Для удобства набора текста следующее:

  • DRF относится к DjangoRestFramework.
  • CBV означает представление на основе классов.
  • FBV расшифровывается как представление на основе функций.
Сиди спокойно и езжай.

0x01 Интерпретация DjangorestFramework

Зачем использовать ДРФ?

Причина использования библиотеки заключается в следующем:

  1. Сэкономьте время разработчиков на создании собственных колес.
  2. Способствует ремонтопригодности кода и/или надежности программы.

Каковы конкретные преимущества внедрения DRF?

  1. Вы можете напрямую просматривать интерфейс отладки. Функция, которая делает интерфейсную отладку захватывающей.
  2. Пакетное быстрое открытие интерфейсов с помощью DRF
  3. Разбиение на страницы, сериализация, проверка, вход в систему, разрешения, веб-дополнительные документы, текущее ограничение, высокая масштабируемость. Где плохо расширяться, так просто
  4. Это колесо лучшего фреймворка RESTFUL в сообществе Django.
  5. Полная поддержка сообщества, например, guardian/django-filter и т. д.

Как написать WebAPI без использования DRF?

Давайте сначала посмотрим, как пишутся API в эпоху неиспользования DRF.

Здесь мы используем представление, основанное на функциях, чтобы кратко объяснить.

# 最简单版本
def simple_hello(request):
    return JsonResponse({
        "这就是 key": "这就是 value",
        "时间": time.time()
    })

Когда я впервые начал изучать DRF, у меня тоже были сомнения: нужен ли фреймворк RESTFULAPI? Засучите рукава и отпустите JSON API.

Причина такого вывода в том, что этот пример действительно слишком упрощен.

Проблема возникает, когда речь идет об API определенного уровня сложности:

  1. Нужно ли различать разрешения?
  2. Нужно ли делать пагинацию?
  3. Когда персонал переднего плана отправляет форму формы, он может отправлять параметры только через командную строку или такие инструменты, как POSTMAN. Не вызовет ли это неудобств? Для обслуживающего персонала также очень болезненно писать различные поля этих форм.
  4. Это нормально для соединения словаря или строки.Может ли быть сериализатор, который поможет мне сериализовать модель напрямую, и если есть связь между моделью и моделью, лучше всего помочь мне завершить связь между моделью и модель.
  5. Как API профиля должен это делать?

Это все, что нам нужно учитывать.

Если DRF не используется, но бэкенд-программисты пишут эти коды напрямую, это не невозможно.

  1. Во-первых, декораторы можно добавлять прямо в fbv.
  2. По второму пункту логику можно напрямую прописать в fbv при пейджинге.
  3. Front-end просто использует такие инструменты, как PostMan напрямую.
  4. Сериализация, вы можете использовать встроенный метод сериализации.
  5. Профиль может добавлять параметр, например отладку, при отправке параметров. При рендеринге он будет отображаться с использованием строки JSON, встроенной в HTML. В этом случае вы можете использовать Django Debug Tools for Profile.

Очевидно, что это систематическая деятельность. Если далее будет рассмотрен дизайн ограничения тока и RESTFUL API, это будет довольно болезненно.

Очевидно, что наш FBV будет выглядеть так:

@a_authority
def complex_hello(request):
    params = getParams(request)
    .....
    query_results = SomeModels.some_query()
    .....
    results = SomeModelsSerial(query_results)
    .....
    return JsonResponse(results)

Кажется, что есть правила, которым нужно следовать.Так как правила есть, мы можем инкапсулировать их, чтобы уменьшить нагрузку. FBV уже такой.Очевидно, что вы можете только жестко закодировать эти параметры, запрашивать и сериализовать каждый раз. Конечно, если вы используете генераторы, вы также можете упростить часть кода функции. Метод реализации yield слишком уродлив и должен быть объявлен устаревшим.

Давайте попробуем CBV и посмотрим, как.

# 继承并重写方法
from django.views.generic import View
class APIView(View):

    def get(self,request):
        query_results = SomeModels.some_query()
        .....
        results = SomeModelsSerial(query_results)
        .....
        return results

    def post(self,request):
        query_results = SomeModels.some_query()
        .....
        results = SomeModelsSerial(query_results)
        .....
        return results

    .....

    # 这里相当于 view 函数
    def dispatch(request, *args, **kwargs):
        # 这里处理正式处理之前的逻辑,比如权限判断。
        # 如果是 GET 方法,则调用
        results = self.get(request, *args, **kwargs):
        # 这里处理正式处理之后的逻辑,比如统计 list 的 total 值,加上时间戳
        return JsonResponse(results)

Следовательно, в дополнение к жесткому кодированию с помощью FBV вы также можете использовать базовый класс CBV для расширенной настройки.

Давайте подумаем об этом:

  1. Если я хочу отобразить список JSON модели, я могу настроить ListViewAPI. Если вам нужен DetailViewAPI, настройте DetailViewAPI.
  2. Если мы объявим некоторые классы разрешений, классы сериализации, модели, а затем будем использовать эти вещи непосредственно в диспетчеризации, нам нужно будет только написать некоторую базовую логику в get и post.
  3. Даже если пейджер и запрос указаны, вообще не нужно писать код в get и post.

Поздравляю, прочитав это, вы уже можете написать минималистичный DRF.

Но чтобы написать программу масштаба DRF, нужно сделать много вещей.

Поток обработки запросов DRF

Чтобы знать процесс обработки запросов в DRF, вы должны сначала узнать процесс обработки запросов в Django.

С точки зрения макро

  1. Сначала запрос проходит через MiddleWare, а затем оценивает urlconf (по умолчанию ROOT_URLCONF),
  2. Соответствует URL-адресу, отправляет контекст запроса в определенное представление.
  3. После обработки через MiddleWare

docs.Django project.com/en/2.0/topi…

В конце этой статьи я также познакомлю вас с точкой зрения исходного кода, включая соответствующий исходный код этого процесса. Пропустить здесь.

Итак, как DRF обрабатывает запрос? Мы игнорируем такие вещи, как маршрутизация, и смотрим непосредственно на исходный код соответствующего CBV.

class APIView(View):

    renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
    parser_classes = api_settings.DEFAULT_PARSER_CLASSES
    authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
    throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
    permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
    content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
    metadata_class = api_settings.DEFAULT_METADATA_CLASS
    versioning_class = api_settings.DEFAULT_VERSIONING_CLASS

    # ...... 其他方法

    # Dispatch methods

    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
        )

    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.
        version, scheme = self.determine_version(request, *args, **kwargs)
        request.version, request.versioning_scheme = version, scheme

        # Ensure that the incoming request is permitted
        self.perform_authentication(request)
        self.check_permissions(request)
        self.check_throttles(request)

    # Note: Views are made CSRF exempt from within `as_view` as to prevent
    # accidental removal of this exemption in cases where `dispatch` needs to
    # be overridden.
    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
        # 这里需要注意
        request = self.initialize_request(request, *args, **kwargs)
        self.request = request
        self.headers = self.default_response_headers  # deprecate?

        try:
            # 这里需要注意
            self.initial(request, *args, **kwargs)

            # Get the appropriate handler method
            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)

        self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response

Видно, что когда запрос поступает в диспетчерскую, DRF добавляет некоторые функции-ловушки для контроля начала/конца/ошибки.

  1. При initialize_request инкапсулировать запрос и добавить контекст parser/auth/negoriator/parser
  2. Затем в начальном методе проверяется версия, выполняется аутентификация и аутентификация, а также проверяется текущий лимит.

На первый взгляд он фактически совпадает с нашей предыдущей идеей об инкапсуляции APIView, но мы только задумаемся, DRF — это детальная реализация.

0x02 Примеры использования DjangorestFramework

Как открыть интерфейс WebAPI

Вернемся к нашему yadjangoblog. В настоящее время мы хотим открыть API списка сообщений в блоге:

# 1. 定义序列器,用于序列化查询的每一条。
class BlogPostListSerializer(serializers.ModelSerializer):
    category = BlogCategorySerializer(read_only=True)
    tags = BlogTagSerializer(many=True, read_only=True)
    title = serializers.CharField()
    id = serializers.IntegerField()

    class Meta:
        model = BlogPost
        fields = ('id', 'title', 'char_num', 'vote_num', 'category', 'tags', 'publish_date')

# 2. 定义过滤器,可以通过过滤器进行查询
class BlogPostFilter(filters.FilterSet):
    title = filters.CharFilter(lookup_expr='contains')
    having_tags = filters.Filter(name="tags", lookup_expr='in')

    class Meta:
        model = BlogPost
        fields = ('title', 'char_num', 'category', 'tags')

# 3. 指定其他设置,具体大家看源码就好了。
class BlogPostListAPIView(generics.ListAPIView):
    """
    依照 category , tags , 时间 (年 / 月 / 日  年 / 月 年)
    """
    queryset = BlogPost.objects.all()
    serializer_class = BlogPostListSerializer
    filter_backends = (filters.DjangoFilterBackend, OrderingFilter,)
    filter_class = BlogPostFilter
    ordering_fields = ('publish_date',)
    ordering = ('publish_date',)
    permission_classes = (permissions.AllowAny,)
    pagination_class = SmallResultsSetPagination

После указания вышеуказанной операции интерфейс быстро открывается.

Конечно, если вы внимательно прочитаете DRF, вы сможете сэкономить много времени.

Это открытый интерфейс. Как внешний интерфейс должен использовать интерфейс?

Как внешний интерфейс использует интерфейс WebAPI

Что такое CORS, можно обратиться к статье Руана Ифэна.Вууху. Руан Ифэн.com/blog/2016/0…

При отладке мы должны использовать метод ajax/fetch для запроса. Это столкнется с проблемой:

  • перекрестный домен

Решение также очень простое: пока сервер реализует интерфейс CORS, сервер может обмениваться данными между источниками.

Установите django-cors-headers и включите CORS_ORIGIN_ALLOW_ALL = True в настройках.

Решение Linshu упоминается здесь, благодаря @林shu, прикрепите адрес ссылкиzhuanlan.zhihu.com/p/24893786

Для этого проекта используется библиотека запросов axios, просто получите ее напрямую. Подробности смотрите во внешнем коде.

0x03 RESTFUL API-дизайн

В процессе разработки старайтесь максимально приблизиться к дизайну RESTFUL API, а не копировать его.

Чтобы привести пример из других областей, некоторые люди выражают красоту только следующим образом:

  • уже трахнул

Но разные красавицы имеют свои внешности:

  • Руки как мягкие сережки, кожа как крем, воротник как жратва, зубы как у носорога, голова как бровь серповидная, улыбка красивая, а глаза смотрят вперед.

Опять же, это происходит при размещении в RESFUL:

Почти вся бизнес-логика будет наконец реализована в CURDE таблицы данных, но вся бизнес-логика не может быть полностью описана CRUDE.

Давайте посмотрим на следующий пример

О запросе

Например, RESTFUL соответствует чистому стилю дизайна CURDE.

Например, добавить блог, обновить блог, запросить блог, удалить блог, проверить, существует ли блог.

Однако в некоторых сценариях семантика недостаточно выражена, например, при оформлении заказа,

URL: /api/v1/user/some_user/orders
你查看订单集合,这个好理解。get 方法
你新增订单,这个好理解。put 方法
URL: /api/v1/user/some_user/order/xxxxxxx
你删除订单,这个好理解。delete 方法
你获取订单,这个好理解。get 方法
你修改订单,这个好理解。post 方法

但修改订单,有的时候可能会比较复杂,有可能是取消订单,有可能是评价订单,有可能是其他。而 RESTFUL 表达这种情况就有些语义不足了。

Конечно, личный опыт показывает, что чем больше полей, тем сложнее подобраться к спецификации RESTFUL.

На данный момент дизайнеру необходимо сбалансировать дизайн и семантику RESTFULAPI.

Об ответе

В адаптивном дизайне нужно отметить два основных момента:

  • Код состояния (код состояния HTTP, а также общий код состояния для бизнес-логики)
  • Содержимое ответа включает в себя общий код состояния бизнес-логики, а остальное зависит от конкретной ситуации.

Коды состояния HTTP используются для обозначения условий ресурсов, таких как:

200 表示获取资源
404 表示 NOT FOUND

Однако иногда возникает проблема недостаточного семантического выражения.Как правило, внешний и внутренний интерфейсы также согласовывают общий код состояния.

通用状态码 错误信息 含义 HTTP 状态码
999	    unknow_v2_error	未知错误	400
1000	need_permission	需要权限	403
1001	uri_not_found	资源不存在	404
1002	missing_args	参数不全	400
1003	image_too_large	上传的图片太大	400
....

Что касается содержания ответа, то это вообще уловка. Рекомендуется проверить соответствующую спецификацию API Douban в конце статьи, чтобы улучшить осанку.

0x04 Интерпретация кода процесса запроса обработки Django

Этот раздел представляет собой побочную историю, написанную по прихоти. К основному содержанию статьи это не имеет никакого отношения. Если вам это не интересно, вы можете сразу перейти к концу статьи и поставить лайк.

Полное имя WSGI называется интерфейсом шлюза веб-сервера.Обычно после того, как gunicorn или uwsgi получает запрос, перенаправленный от nginx, он предоставляет веб-приложению информацию об окружающей среде (независимо от того, лучше ли контекст запроса) и обратный вызов.В этом случае, веб-приложение может получить эту информацию о среде, обработать ее, обработать запрос с помощью функции обратного вызова и вернуть ответ. Минималистское веб-приложение выглядит так:

def app(environ, start_response):
    """Simplest possible application object"""
    data = 'Hello, World!\n'
    status = '200 OK'
    response_headers = [
        ('Content-type','text/plain'),
        ('Content-Length', str(len(data)))
    ]
    start_response(status, response_headers)
    return iter([data])

Теперь давайте посмотрим, как обрабатываются запросы в django. Сначала посмотрите на соответствующий wsgi.py

# wsgi.py
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
application = get_wsgi_application()

# 接着查看 get_wsgi_application
import django
from django.core.handlers.wsgi import WSGIHandler

def get_wsgi_application():
    """
    The public interface to Django's WSGI support. Return a WSGI callable.

    Avoids making django.core.handlers.WSGIHandler a public API, in case the
    internal WSGI implementation changes or moves in the future.
    """
    django.setup(set_prefix=False)
    return WSGIHandler()

# 于是自然而言的看到了 WSGIHandler
class WSGIHandler(base.BaseHandler):
    request_class = WSGIRequest

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.load_middleware()

    def __call__(self, environ, start_response):
        # 有木有看到 environ 和 start_response ?? 这就是极简 web app 中的 webapp 核心方法。
        set_script_prefix(get_script_name(environ))
        signals.request_started.send(sender=self.__class__, environ=environ)
        request = self.request_class(environ)
        # 注意这一行,有请求处理逻辑 具体要见下面代码
        response = self.get_response(request)
        # ......
        return response

Ну, когда вы видите подкласс, вы должны смотреть на базовый класс

class BaseHandler:
    _request_middleware = None
    _view_middleware = None
    _template_response_middleware = None
    _response_middleware = None
    _exception_middleware = None
    _middleware_chain = None

    def load_middleware(self):
        """
        注册 MiddleWare, 并赋值 _middleware_chain 方法,使之调用的时候可以先按照顺序从 setting 的 middleware 里面处理 requests
        并在处理 request 的最后调用 私有方法 _get_response
        """
        self._request_middleware = []
        self._view_middleware = []
        self._template_response_middleware = []
        self._response_middleware = []
        self._exception_middleware = []

        handler = convert_exception_to_response(self._get_response)
        # 注意,这里面是倒着来的 代码中越在前面,实际运行的时候处理就越在后面
        for middleware_path in reversed(settings.MIDDLEWARE):
            # 依次添加 view middleware / template middleware / exception middleware
            middleware = import_string(middleware_path)
            mw_instance = middleware(handler)
            handler = convert_exception_to_response(mw_instance)

        # We only assign to this when initialization is complete as it is used
        # as a flag for initialization being complete.
        self._middleware_chain = handler

    .....

    def get_response(self, request):
        """Return an HttpResponse object for the given HttpRequest."""
        # Setup default url resolver for this thread
        set_urlconf(settings.ROOT_URLCONF)

        response = self._middleware_chain(request)
        # ......
        return response

    def _get_response(self, request):
        """
        Resolve and call the view, then apply view, exception, and
        template_response middleware. This method is everything that happens
        inside the request/response middleware.
        """
        response = None

        # 1. 接着判断 urlconf (默认为 ROOT_URLCONF), 可以通过 middleware 进行设置
        if hasattr(request, 'urlconf'):
            urlconf = request.urlconf
            set_urlconf(urlconf)
            resolver = get_resolver(urlconf)
        else:
            resolver = get_resolver()

        resolver_match = resolver.resolve(request.path_info)
        callback, callback_args, callback_kwargs = resolver_match
        request.resolver_match = resolver_match

        # Apply view middleware....
        # 注意,这个就是 view 函数
        wrapped_callback = self.make_view_atomic(callback)
        response = wrapped_callback(request, *callback_args, **callback_kwargs)
        # Complain if the view returned None (a common error).
        return response

    def process_exception_by_middleware(self, exception, request):
        # ......

Смысл приведенного выше кода относительно прост, и я добавил комментарии к пунктам, на которые стоит обратить внимание.

Особого внимания требует атрибут middleware_chain (собственно метод), именно этот метод позволяет зарегистрированному middleware (в методе load_middleware) обработать запрос до того, как fbv или cbv обработает запрос.

0xEE Ссылочная ссылка

Почему вы все еще колеблетесь, Django — лучшая практика для разделения передней и задней частей После того, как вам это понравится, садитесь в машину


ChangeLog:

  • 2018-02-22открыть эту статью
  • 2018-03-04Переработайте текст