我正在将内部网站的后端从PHP重写为Django(使用REST框架)。
两个版本(PHP和Django)都需要同时部署一段时间,并且我们拥有一组软件工具,可以通过简单的AJAX API与旧版网站进行交互。所有请求都使用GET方法完成。
到目前为止,我的使请求在这两个站点上都能工作的方法是制作一个简单的适配器应用程序,路由到“ http://<site-name>/ajax.php
”以模拟对Ajax控制器的调用。所述应用程序包含一个基于功能的简单视图,该视图从传入请求中检索数据,以确定在传入请求上调用哪个对应的Django视图(基本上是Ajax控制器在PHP版本上执行的操作)。
它确实有效,但是我遇到了问题。我的API动作之一是在数据库表中创建一个简单的条目。因此,我使用一些通用的混合定义了DRF视图集:
class MyViewSet(MyGenericViewSet, CreateModelMixin):
# ...
这将添加一个create
操作,该操作将路由到页面上的POST
个请求。正是我所需要的。除了我的传入请求正在使用GET
方法之外……我可以编写自己的create
操作并使其接受GET
请求,但从长远来看,我们的工具将适应Django不再需要API和适配器应用程序,因此我宁愿拥有“干净的”视图集和模型。将POST
用于此类操作更有意义。
在适配器应用程序视图中,我天真地尝试了此操作:
request.method = "POST"
request.POST = request.GET
在将请求提交到create
视图之前。正如预期的那样,尽管我的适配器应用程序视图具有一个@csrf_exempt
装饰器,但还是收到了CSRF身份验证失败消息……
我知道我可能会在这里尝试将三角形拟合为正方形,但是有没有一种方法可以在不重写我自己的create
操作的情况下进行此工作?
答案 0 :(得分:1)
您可以通过使用@action
装饰器来接受create
请求并进行创建,从而在ViewSet中定义一个自定义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
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
请求创建模型实例(不过您需要添加执行该创建的逻辑),并且可以使用以下网址进行访问:< / p>
your_domain/my_api/something/create-from-get
当您不再需要该终结点时,只需删除代码的这一部分,然后执行该操作即可(或者您可以保留遗留原因,由您决定)!
答案 1 :(得分:1)
所有答案的建议都指向创建另一个视图,这就是我最终要做的。在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 mixin create
的{{1}},perform_create
和get_success_header
方法中发生了什么基于功能的DRF视图。作为一个独立的函数,它可以放在我的CreateModelMixin
应用视图中,以便所有旧版API代码都只放在一个地方,这就是我对这个问题的意图。
答案 2 :(得分:0)
您可以为视图集(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 :(得分:0)
根据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/