Преобразование параметров GET в данные POST для объекта запроса в Django REST Framework - PullRequest
5 голосов
/ 03 мая 2019

Я переписываю бэкэнд внутреннего веб-сайта с PHP на Django (используя REST Framework).

Обе версии (PHP и Django) должны быть развернуты одновременно на некоторое время, и у нас есть набор программных инструментов, которые взаимодействуют с унаследованным веб-сайтом через простой AJAX API. Все запросы выполняются методом GET.

Мой подход к выполнению запросов на обоих сайтах заключался в создании простого приложения-адаптера с маршрутизацией на «http://<site-name>/ajax.php» для имитации вызова к контроллеру Ajax. Упомянутое приложение содержит одно простое представление на основе функций, которое извлекает данные из входящего запроса, чтобы определить, какое соответствующее представление Django вызывать при входящем запросе (в основном то, что контроллер Ajax делает в версии PHP).

Это работает, но я столкнулся с проблемой. Одним из моих действий API было простое создание записи в таблице БД. Итак, я определил свой DRF viewset, используя некоторые общие миксины:

class MyViewSet(MyGenericViewSet, CreateModelMixin):
    # ...

Это добавляет действие create, направленное на POST запросы на странице. Именно то, что мне нужно. За исключением того, что мои входящие запросы используют метод GET ... Я мог бы написать свое собственное действие create и заставить его принимать GET запросы, но в долгосрочной перспективе наши инструменты адаптируются к Django API, и приложение-адаптер будет больше не нужны, поэтому я предпочел бы иметь "чистые" наборы и модели вида. Для такого действия имеет смысл использовать POST.

В моем приложении-адаптере я наивно пытался это сделать:

request.method = "POST"
request.POST = request.GET

Перед передачей запроса в create представление. Как и ожидалось, это не сработало, и я получил сообщение об ошибке аутентификации CSRF, хотя в моем представлении приложения адаптера есть @csrf_exempt декоратор ...

Я знаю, что я мог бы попытаться поместить треугольник в квадраты здесь, но есть ли способ сделать это без переписывания моего собственного действия create?

Ответы [ 4 ]

1 голос
/ 16 мая 2019

С советами из всех ответов, указывающих на создание другого представления, это то, что я в конечном итоге сделал.Внутри adapter/views.py:

from rest_framework.settings import api_settings
from rest_framework.decorators import api_view, renderer_classes
from rest_framework.response import Response
from rest_framework import status

from mycoreapp.renderers import MyJSONRenderer
from myapp.views import MyViewSet

@api_view(http_method_names=["GET"])
@renderer_classes((MyJSONRenderer,))
def create_entity_from_get(request, *args, **kwargs):
    """This view exists for compatibility with the old API only. 
    Use 'POST' method directly to create a new entity."""
    query_params_copy = request.query_params.copy()
    # This is just some adjustments to make the legacy request params work with the serializer
    query_params_copy["foo"] = {"name": request.query_params.get("foo", None)}
    query_params_copy["bar"] = {"name": request.query_params.get("bar", None)}
    serializer = MyViewSet.serializer_class(data=query_params_copy)
    serializer.is_valid(raise_exception=True)
    serializer.save()
    try:
        headers = {'Location': str(serializer.data[api_settings.URL_FIELD_NAME])}
    except (TypeError, KeyError):
        headers = {}
    return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

Конечно, я запутал названия всего, что касается моего проекта.По сути, я почти точно воспроизвел (за исключением нескольких настроек параметров запроса) то, что происходит в методах create, perform_create и get_success_header в DRF mixin CreateModelMixin в представлении DRF с одной функцией.Будучи просто автономной функцией, она может находиться в моих adapter представлениях приложений, так что весь устаревший код API находится только в одном месте, что и было моим намерением в этом вопросе.

1 голос
/ 13 мая 2019

Вы можете определить собственный метод create в вашем ViewSet, не переопределяя исходный, используя декоратор @action, который может принимать GET запросов и создавать:

class MyViewSet(MyGenericViewSet, CreateModelMixin):
    ...
    @action(methods=['get'], detail=False, url_path='create-from-get')
    def create_from_get(self, request, *args, **kwargs):
        # Do your object creation here.

Вам понадобится Router в ваших URL-адресах для автоматического подключения action к вашим URL-адресам (A SimpleRouter, скорее всего, подойдет).
В вашем urls.py:

router = SimpleRouter()
router.register('something', MyViewSet, base_name='something')

urlpatterns = [
    ...
    path('my_api/', include(router.urls),
    ...
]

Теперь у вас есть action, который может создать экземпляр модели из запроса GET (вам нужно добавить логику, которая делает это создание, хотя)и вы можете получить к нему доступ с помощью следующего URL:

your_domain/my_api/something/create-from-get

Если вам больше не нужна эта конечная точка, просто удалите эту часть кода, и действие закроется, чтобы существовать (или вы можете оставить его для наследства).причины, это зависит от вас)!

0 голосов
/ 14 мая 2019

Согласно REST метод запроса архитектурных принципов GET предназначен только для получения информации.Поэтому мы не должны выполнять операцию create с методом запроса GET.Для выполнения операции create используйте метод запроса POST.

Временное исправление вашего вопроса

from rest_framework import generics, status

class CreateAPIView(generics.CreateView):

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.query_params)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return Response(
            serializer.data,
            status=status.HTTP_201_CREATED,
            headers=headers)

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

Для получения дополнительной информации см. Ссылки ниже.
https://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html
https://learnbatta.com/blog/introduction-to-restful-apis-72/

0 голосов
/ 13 мая 2019

Вы можете написать метод для своего набора просмотра (custom_get), который будет вызываться при вызове GET на ваш URL, и вызывать ваш метод create оттуда.

class MyViewSet(MyGenericViewSet, CreateModelMixin):
    ...
    def custom_get(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

А в вашем urls.py для своего набора вы можете определить, что этот метод должен вызываться при вызове GET.

#urls.py
urlpatterns = [
    ...
    url(r'^your-url/$', MyViewSet.as_view({'get': 'custom_get'}), name='url-name'),
]

...