web-dev-qa-db-fra.com

Convertir les paramètres GET en POST sur un objet Request dans Django REST

Je suis en train de réécrire le backend d'un site web interne de PHP à Django (en utilisant REST)).

Les deux versions (PHP et Django) doivent être déployées simultanément pendant un certain temps, et nous avons un ensemble d'outils logiciels qui interagissent avec le site Web hérité via une simple API AJAX. Toutes les demandes sont traitées avec la méthode GET.

Jusqu'à présent, mon approche pour faire fonctionner les demandes sur les deux sites consistait à créer une application d'adaptateur simple, routée vers 'http://<site-name>/ajax.php 'pour simuler l'appel au contrôleur Ajax. Cette application contient une vue basée sur des fonctions simples qui récupère les données de la demande entrante pour déterminer quelle vue Django correspondante appeler la demande entrante (essentiellement ce que fait le contrôleur Ajax sur le PHP version).

Cela fonctionne, mais j'ai rencontré un problème. L'une de mes actions API était une simple création d'entrée dans une table DB. J'ai donc défini mon ensemble de vues DRF en utilisant des mixins génériques:

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

Cela ajoute une action create acheminée vers les requêtes POST sur la page. Exactement ce dont j'ai besoin. Sauf que mes requêtes entrantes utilisent la méthode GET ... Je pourrais écrire ma propre action create et la faire accepter GET requêtes, mais à long terme, nos outils s'adapteront à l'API Django et l'application d'adaptateur ne seront plus nécessaires, je préférerais donc des ensembles et modèles de vues "propres". Il est plus logique d'utiliser POST pour une telle action.

Dans la vue de mon application d'adaptateur, j'ai naïvement essayé ceci:

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

Avant de transmettre la demande à la vue create. Comme prévu, cela n'a pas fonctionné et j'ai reçu un message d'échec d'authentification CSRF, bien que la vue de mon application d'adaptateur ait un @csrf_exempt décorateur ...

Je sais que j'essaie peut-être de placer le triangle dans des carrés ici, mais existe-t-il un moyen de faire fonctionner cela sans réécrire ma propre action create?

4
Valentin B.

Avec les conseils de toutes les réponses pointant vers la création d'une autre vue, c'est ce que j'ai fini par faire. À l'intérieur 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)

Bien sûr, j'ai masqué les noms de tout ce qui est spécifique à mon projet. Fondamentalement, j'ai reproduit presque exactement (à l'exception de quelques ajustements à mes paramètres de requête) ce qui se passe dans les create, perform_create et get_success_header méthodes du mix DRF CreateModelMixin dans une vue DRF à fonction unique. Étant juste une fonction autonome, elle peut se trouver dans mes vues d'application adapter de sorte que tout le code API hérité se trouve au même endroit, ce qui était mon intention avec cette question.

1
Valentin B.

Vous pouvez définir une méthode create personnalisée dans votre ViewSet, sans remplacer celle d'origine, en utilisant @action décorateur qui peut accepter les requêtes GET et faire la création:

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.

Vous aurez besoin d'un Router dans vos URL pour connecter automatiquement le action à vos URL (A SimpleRouter le fera probablement).
Dans votre urls.py:

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

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

Vous avez maintenant un action qui peut créer une instance de modèle à partir d'une demande GET (vous devez cependant ajouter la logique qui fait cette création) et vous pouvez y accéder avec l'url suivante:

your_domain/my_api/something/create-from-get

Lorsque vous n'avez plus besoin de ce point de terminaison, supprimez simplement cette partie du code et l'action saisit d'exister (ou vous pouvez la conserver pour des raisons héritées, c'est à vous de décider)!

1
John Moutafis

Selon REST la méthode de demande des principes architecturaux GET est uniquement destinée à récupérer les informations. Par conséquent, nous ne devons pas effectuer d'opération create avec la méthode de demande GET. Pour effectuer l'opération create, utilisez la méthode de demande POST.

Correction temporaire de votre question

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)

Veuillez vous référer aux références ci-dessous pour plus d'informations.
https://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html
https://learnbatta.com/blog/introduction-to-restful-apis-72/

0

Vous pouvez écrire une méthode pour votre ensemble de vues (custom_get) qui sera appelé lors d'un appel GET à votre URL, et appelez votre méthode create à partir de là.

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

Et dans votre urls.py, pour votre ensemble de vues, vous pouvez définir que cette méthode doit être appelée lors d'un appel GET.

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

0
Aman Garg