在 Django REST Framework 中将 GET 参数转换为 Request 对象上的 POST 数据
Posted
技术标签:
【中文标题】在 Django REST Framework 中将 GET 参数转换为 Request 对象上的 POST 数据【英文标题】:Convert GET parameters to POST data on a Request object in Django REST Framework 【发布时间】:2019-09-22 01:06:57 【问题描述】:我正在将内部网站的后端从 php 重写为 Django(使用 REST 框架)。
两个版本(PHP 和 Django)都需要同时部署一段时间,我们有一套软件工具可以通过简单的 AJAX API 与遗留网站进行交互。所有请求都使用 GET 方法完成。
到目前为止,我让请求在两个站点上工作的方法是制作一个简单的适配器应用程序,路由到“http://<site-name>/ajax.php
”以模拟对 Ajax 控制器的调用。所述应用程序包含一个简单的基于函数的视图,该视图从传入请求中检索数据,以确定在传入请求上调用哪个相应的 Django 视图(基本上是 Ajax 控制器在 PHP 版本上所做的事情)。
它确实有效,但我遇到了问题。我的 API 操作之一是在数据库表中创建一个简单的条目。所以我使用一些通用的 mixins 定义了我的 DRF 视图集:
class MyViewSet(MyGenericViewSet, CreateModelMixin):
# ...
这会添加一个create
操作,路由到页面上的POST
请求。正是我需要的。除了我的传入请求使用GET
方法...我可以编写自己的create
操作并使其接受GET
请求,但从长远来看,我们的工具将适应Django API,适配器应用程序将不再需要,所以我宁愿拥有“干净”的视图集和模型。使用POST
进行此类操作更有意义。
在我的适配器应用程序视图中,我天真地尝试了这个:
request.method = "POST"
request.POST = request.GET
在将请求交给create
视图之前。正如预期的那样,它不起作用,并且我收到了 CSRF 身份验证失败消息,尽管我的适配器应用程序视图有一个 @csrf_exempt
装饰器...
我知道我可能会在这里尝试将三角形放入正方形中,但是有没有办法在不重写我自己的 create
操作的情况下完成这项工作?
【问题讨论】:
您使用的是会话身份验证吗?如果是这种情况,您应该包含 csrf 令牌。 您的适配器视图可能会在没有令牌的情况下授权请求,但我猜您的视图集没有 另外,请注意 GET 请求将被 drf 认为是安全的,因此默认情况下比 POST 请求需要更少的身份验证。这就是为什么在绕过 drf 的默认行为时应该特别注意安全性 @ElioMaisonneuve 是的,我知道这一点。我意识到我没有在我的问题中提到它,但这是针对仅在本地网络上可用的内部网站,因此安全考虑不是问题。 您是否尝试过删除MyViewSet
上的csrf 保护?
【参考方案1】:
您可以在 ViewSet 中定义一个自定义的 create
方法,而无需覆盖原始方法,方法是利用可以接受 GET
请求并进行创建的 @action
装饰器:
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
in your urls 才能将action
自动连接到您的网址(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
当您不再需要此端点时,只需删除这部分代码,动作就会存在(或者您可以出于遗留原因保留它,这取决于您)!
【讨论】:
感谢您的回答。这是我正在考虑的一个解决方案(我已经使用了action
装饰器),但我宁愿将所有特定于旧 AJAX API 的代码保留在适配器应用程序中,即不在我的其他视图集中添加/修改任何内容应用程序。
@ValentinB。适配器应用程序是 Django 项目的一部分还是作为单独的项目存在?
它是同一个 Django 项目的一部分。
@ValentinB。然后,您可以在相应的adapter
应用程序视图中创建上面的此操作,以从 GET 请求初始化对象的创建。或者,您是否考虑过创建一个单独的适配器(如小型 Flask 应用程序)作为旧代码和新 Django 应用程序之间的媒介?
中间件应用程序可能是一种选择,但它需要我现在不愿意获得的知识和技能。我真的希望有一种方法可以从我的 get 请求中重新构建一个 post 请求,将后者的参数作为前者的主体传递。【参考方案2】:
您可以为您的视图集 (custom_get
) 编写一个方法,当对您的 url 进行 GET
调用时将调用该方法,然后从那里调用您的 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'),
]
【讨论】:
感谢您的回答!然而,就像约翰穆塔菲斯的回答一样,它并不完全符合我在这里尝试做的事情,请参阅对他的回答的评论。【参考方案3】:根据REST
架构原则请求方法GET
仅用于检索信息。因此,我们不应该使用请求方法GET
执行create
操作。要执行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/
【讨论】:
我知道,这正是我希望新站点使用通用操作的原因。我的问题主要是我必须让两个网站都使用我们的旧 API 运行一段时间,然后才能在我们的工具中使用正确的方法。 @ValentinB。更新了解决方案,您现在可以检查它。【参考方案4】:所有答案的建议都指向创建另一个视图,这就是我最终要做的。里面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)
当然,我已经混淆了我的项目特定的所有内容的名称。基本上,我几乎完全复制了(除了对查询参数的一些调整)在基于单个函数的 DRF 视图中 DRF 混合 CreateModelMixin
的 create
、perform_create
和 get_success_header
方法中发生的事情。作为一个独立的功能,它可以位于我的adapter
应用程序视图中,因此所有遗留 API 代码都只位于一个位置,这是我提出这个问题的意图。
【讨论】:
以上是关于在 Django REST Framework 中将 GET 参数转换为 Request 对象上的 POST 数据的主要内容,如果未能解决你的问题,请参考以下文章