简体   繁体   中英

How to use DRF serializers with Graphene

I am following this tutorial for using Graphene with Django , and everything was going smooth, until I reached the Integration with Django Rest Framework section .

This section says that you can reuse DRF serializers with Graphene , by creating serializers clones, but it doesn't say what to do with such clones in order to reuse DRF serializers with Graphene .

These are my serializers and clones:

from rest_framework import serializers
from graphene_django.rest_framework.mutation import SerializerMutation
from GeneralApp.models import Airport
from ReservationsManagerApp.serializers import ReservationSerializer
from ReservationsManagerApp.models import ReservationComponent, ReservationHotel, ReservationRoundtrip, ReservationTransfer, ReservationTour, ReservationService, Hotel

class ReservationMutation(SerializerMutation):
    class Meta:
        serializer_class = ReservationSerializer

class ReservationComponentGraphSerializer(serializers.ModelSerializer):
    component = serializers.SerializerMethodField()

    class Meta:
        model = ReservationComponent
        fields = ('id', 'reservation', 'dertour_bk', 'day', 'content_type', 'object_id', 'comment', 'is_invoiced', 'component')

    def get_component(self, instance):
        components_models = {
            'reservationhotel': ReservationHotel,
            'reservationroundtrip': ReservationRoundtrip,
            'reservationtransfer': ReservationTransfer,
            'reservationtour': ReservationTour,
            'reservationservice': ReservationService,
        }

        component = components_models[instance.content_type.model].objects.get(id=instance.object_id)

        return self.get_component_string(instance.content_type.model, component)

    def get_component_string(self, component_model, component):
        components_get_string = {
            'reservationhotel': self.get_hotel_string,
            'reservationroundtrip': self.get_roundtrip_string,
            'reservationtransfer': self.get_transfer_string,
            'reservationtour': self.get_tour_string,
            'reservationservice': self.get_service_string,
        }

        return components_get_string[component_model](component):

    def get_hotel_string(self, component):
        return component.hotel.name

    def get_roundtrip_string(self, component):
        return component.roundtrip.name

    def get_transfer_string(self, component):
        origin_str = self.get_place_str('origin', component)
        destination_str = self.get_place_str('destination', component)

        return "{} => {}".format(origin_str, destination_str)

    def get_place_str(self, case, component):
        places_models = {
            'airport': Airport,
            'hotel': Hotel,
        }

        if case == 'origin':
            return places_models[component.origin_content_type.model].objects.get(id=component.origin_object_id).name
        else:
            return places_models[component.destination_content_type.model].objects.get(id=component.destination_object_id).name

    def get_tour_string(self, component):
        return component.tour.name

    def get_service_string(self, component):
        return component.service.name

class ReservationComponentMutation(SerializerMutation):
    class Meta:
        serializer_class = ReservationComponentGraphSerializer

And this is my schemas.py :

import graphene
from graphene_django.types import DjangoObjectType
from ReservationsManagerApp.models import   Reservation, ReservationComponent
from InvoicesManagerApp.models import Invoice, InvoiceEntry, InvoiceEntryComponent
from PaymentsManagerApp.models import Payment, PaymentReservationComponent

class ReservationType(DjangoObjectType):
    class Meta:
        model = Reservation

class ReservationComponentType(DjangoObjectType):
    class Meta:
        model = ReservationComponent

class InvoiceType(DjangoObjectType):
    class Meta:
        model = Invoice

class InvoiceEntryType(DjangoObjectType):
    class Meta:
        model = InvoiceEntry

class InvoiceEntryComponentType(DjangoObjectType):
    class Meta:
        model = InvoiceEntryComponent

class PaymentType(DjangoObjectType):
    class Meta:
        model = Payment

class PaymentReservationComponentType(DjangoObjectType):
    class Meta:
        model = PaymentReservationComponent

class  Query(object):
    all_reservations = graphene.List(ReservationType)
    all_reservation_components = graphene.List(ReservationComponentType)
    all_invoices = graphene.List(InvoiceType)
    all_invoice_components = graphene.List(InvoiceEntryType)
    all_invoice_entries_components = graphene.List(InvoiceEntryComponentType)
    all_payment = graphene.List(PaymentType)
    all_payment_reservation_components = graphene.List(PaymentReservationComponentType)

    def resolve_all_reservations(self, info, **kwargs):
        return Reservation.objects.all()

    def resolve_all_reservation_components(self, info, **kwargs):
        return ReservationComponent.objects.select_related('reservation').all()

    def resolve_all_invoice_entries_components(self, info, **kwargs):
        return InvoiceEntryComponent.objects.select_related('reservation_component').all()

    def resolve_all_payment_reservation_components(self, info, **kwargs):
        return PaymentReservationComponent.objects.select_related('reservation_component').all()

I don't know if I am missing something obvious, but I can't understand how am I suppose to use those serializers mutations with graphene. I guess it must be by configuring the Query class in some way, but I can't find a reference in the documentation.

I don't see any reason why we have to do as shown in that tutorial. It is much easier to connect drf and graphql in following way. Doing this way,you do not need to worry about any vague classes and just rely on main aspects of drf and graphene.

Construct drf serializers normally, and connect it to graphql as shown below.

Consider we have model Subject. Let's create CRUD api for it.

from graphene.types.scalars import Scalar

class ObjectField(Scalar): # to serialize error message from serializer
    @staticmethod
    def serialize(dt):
        return dt 


class SubjectType(DjangoObjectType):
    class Meta:
        model=Subject


# For mutation, use serializers

#creating subject
class CreateSubject(graphene.Mutation):
    subject=graphene.Field(SubjectType)
    message=ObjectField()
    status=graphene.Int()

    class Arguments:
        name=graphene.String(required=True)
        description=graphene.String(required=True)
   
    @classmethod
    def mutate(cls,root,info,**kwargs):
        serializer=SubjectSerializer(data=kwargs)
        if serializer.is_valid():
            obj=serializer.save()
            msg='success'
        else:
            msg=serializer.errors
            obj=None
            print(msg)
        return cls(subject=obj,message=msg,status=200)


'''Updating subject'''
class UpdateSubject(graphene.Mutation):
    subject=graphene.Field(SubjectType)
    status=graphene.Int()
    message=ObjectField()

    class Arguments:
        id=graphene.ID(required=True)
        name=graphene.String()
        description=graphene.String()

    @classmethod
    def mutate(cls,root,info,id,**kwargs):
        sub=Subject.objects.get(id=id)
        serializer=SubjectSerializer(sub,data=kwargs,partial=True)
        if serializer.is_valid():
            obj=serializer.save()
            msg='success'
        else:
            msg=serializer.errors
            obj=None
            print(msg)
        return cls(subject=obj,message=msg,status=200)


'''Delete Subject'''
class DeleteSubject(graphene.Mutation):
    message=ObjectField()
    status=graphene.Int()

    class Arguments:
        id=graphene.ID(required=True)

    @classmethod
    def mutate(cls,root,info,id,**kwargs):
        c=Subject.objects.get(id=id)
        c.delete()
        return cls(message='success',status=200)


class Mutation(graphene.ObjectType):
    create_subject=CreateSubject.Field()
    update_subject=UpdateSubject.Field()
    delete_subject=DeleteSubject.Field()

# Query is normal.

class Query(graphene.ObjectType):
    subject=graphene.Field(SubjectType,id=graphene.Int(), slug=graphene.String())
    
    subjects=graphene.List(SubjectType)

    def resolve_subject(self, info, id=None, slug=None):
        if id:
            return Subject.objects.get(id=id)
        if slug:
            return  Subject.objects.get(slug=slug)

    def resolve_subjects(self,info,**kwargs):
        return Subject.objects.all()

    

You can try making little framework-like thing for yourself to avoid redundant code as seen.

From your example:

import graphene
from your.schemas import Query as YourQuery
from your.serializers import ReservationComponentMutation

# notice its an objecttype, and i've also added some debug
class Mutation(graphene.ObjectType):
    debug = graphene.Field(DjangoDebug, name="_debug")

    create_reservation = ReservationComponentMutation.Field()


class Query(YourQuery, graphene.ObjectType):
    pass


class Mutation(Mutation, graphene.ObjectType):
    pass


root_schema = graphene.Schema(query=Query, mutation=Mutation)

And the url:

urlpatterns = (
    url(r"^graphql", GraphQLView.as_view(schema=root_schema graphiql=True), name="graphql"),
)

# you can wrap csrf_exempt(GraphQLView.as_view(...)) for testing
# or you can setup the frontend `apollo-client` to use csrf_tokens
# also see the related links below

For example for apollo-client and not apollo-boost the window.csrf_token is {% csrf_token %} in the template rendering:

import { ApolloProvider } from "react-apollo";
import ApolloClient from "apollo-client";
import { HttpLink } from "apollo-link-http";
import { InMemoryCache as Cache } from "apollo-cache-inmemory";
import { ApolloLink } from "apollo-link";
import fetch from "unfetch";

const uri = UrlUtils.makeUrl(Urls.graphQl);
const AuthLink = (operation, next) => {
  const token = window.csrf_token; 

  operation.setContext(context => ({
    ...context,
    headers: {
      ...context.headers,
      "X-CSRFToken": token
    }
  }));

  return next(operation);
};

const link = ApolloLink.from([
  AuthLink,
  new HttpLink({
    uri,
    credentials: "same-origin",
    fetch // override fetch implementation for polyfills
  })
]);

const apollo = new ApolloClient({
  link,
  cache: new Cache().restore({})
});

You should then be able to:

query TestQuery() {
    resolveAllReservations () {
         id
    }
}

or:

mutate TestMutate($input: ReservationComponentMutationInput!) {
     createReservation(input: $input) {
         id
         errors {
           field
           messages
         }
     }
    _debug {
      sql {
        rawSql
      }
    }
}

related:

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM