Package: django-model-mutations
Django Model MutationsThis package adds Mutation classes that make creating graphene mutations with django models easier using Django Rest Framework serializers. It extends graphene Mutation class in a similar way to Django Rest Framework views or original Django views.It also provides easy way to add permissions checks or ensure logged-in user, as well as easy way to override or add funcionality similar to django forms or rest framework views - such asget_queryset()orsave()functions.There is also advanced error reporting from rest framework, that returns non-valid fields and error messages.Inspired bySaleor,graphene-django-extrasanddjango-rest-frameworkInstallationpip install django-model-mutationsBasic UsageMain classes that this package provides:mutationsmixinsCreateModelMutationLoginRequiredMutationMixinCreateBulkModelMutationUpdateModelMutationUpdateBulkModelMutationDeleteModelMutationDeleteBulkModelMutationDjango usageInput type (Arguments) is generated from serializer fieldsReturn type is retrieved by model from global graphene registry, you just have to import it as in examplefromdjango_model_mutationsimportmutations,mixinsfromyour_app.typesimportUserType# important to import types to register in global registryfromyour_app.serializersimportUserSerializer# Create Mutations# use mixins.LoginRequiredMutationMixin to ensure only logged-in user can perform this mutation# MAKE SURE this mixin is FIRST in inheritance orderclassUserCreateMutation(mixins.LoginRequiredMutationMixin,mutations.CreateModelMutation):classMeta:serializer_class=UserSerializer# OPTIONAL META FIELDS:permissions=('your_app.user_permission',)# OPTIONAL: specify user permissionslookup_field='publicId'# OPTIONAL: specify database lookup column, default is 'id' or 'ids'return_field_name='myUser'# OPTIONAL: specify return field name, default is model nameinput_field_name='myUser'# OPTIONAL: specify input field name, defauls is 'input'classUserBulkCreateMutation(mutations.CreateBulkModelMutation):classMeta:serializer_class=UserSerializer# Update MutationsclassUserUpdateMutation(mutations.UpdateModelMutation):classMeta:serializer_class=UserSerializer# WARNING: Bulk update DOES NOT USE serializer, due to limitations of rest framework serializer.# Instead specify model and argument fields by yourself.classUserBulkUpdateMutation(mutations.UpdateBulkModelMutation):classArguments:is_active=graphene.Boolean()classMeta:model=User# Delete Mutations# delete mutations doesn't use serializers, as there is no needclassUserDeleteMutation(mutations.DeleteModelMutation):classMeta:model=UserclassUserBulkDeleteMutation(mutations.DeleteBulkModelMutation):classMeta:model=User# Add to graphene schema as usualclassMutation(graphene.ObjectType):user_create=UserCreateMutation.Field()....schema=graphene.Schema(mutation=Mutation)GraphQl usageThe generated GraphQl schema can be modified withMetafields as described above inUserCreateMutation.By default all mutations haveerrorsfield withfieldandmessagesthat contain validation errors from rest-framework serializer or lookup errors. For now permission denied and other exceptions will not use this error reporting, but a default one, for usage see tests.# default argument name is input# default return field name is model namemutationuserCreate(input:{username:"myUsername"}){user{idusername}errors{fieldmessages}}# Bulk operations return 'count' and errorsmutationuserCreate(input:{username:"myUsername"}){counterrors{fieldmessages}}# update mutations# update and delete mutations by default specify lookup field 'id' or 'ids' for bulk mutationsmutation{userUpdate(id:2,input:{username:"newUsername"}){user{idusername}errors{fieldmessages}}}mutation{userBulkUpdate(ids:[2,3],isActive:false){counterrors{fieldmessages}}}# delete mutationsmutation{userDelete(id:1){user{id}errors{fieldmessages}}}mutation{userBulkDelete(ids:[1,2,3]){counterrors{fieldmessages}}}Adding funcionalityAll classes are derived fromgraphene.Mutation. When you want to override some major functionality, the best place probabably isperform_mutate, which is called after permission checks from graphenemutate.In general probably the main functions that you want to override are:save()andget_object()for single object mutations orget_queryset()for bulk mutations.get_objectorget_querysetyou should override to add more filters for fetching the object.saveperforms final save/update/delete to database and you can add additional fields there.Examples:# lets only update users that are inactive and add some random fieldclassUserUpdateInactiveMutation(mutations.UpdateModelMutation):classMeta:model=User@classmethoddefget_object(cls,object_id,info,**input):# can get the object first and then checkobj=super(UserUpdateInactiveMutation,cls).get_object(object_id,info,**input)ifobj.is_active:returnNonereturnobj@classmethoddefsave(cls,serializer,root,info,**input):saved_object=serializer.save(updated_by=info.context.user)returncls.return_success(saved_object)# same but for bulk mutation we have to override get_querysetclassUserBulkUpdateInactiveMutation(mutations.UpdateBulkModelMutation):classMeta:model=User@classmethoddefget_queryset(cls,object_ids,info,**input):qs=super(UserBulkUpdateInactiveMutation,cls).get_queryset(object_ids,info,**input)qs.filter(is_active=False)returnqsFor the whole function flow, please check the Base models indjango_model_mutations\mutations.py.
It was inspired by rest framework, so you can find functions likeget_serializer_kwargs,get_serializer,validate_instance(for example here you can override defaultValidationErrorexception and return None if you don't want exception of non existing id lookup etc.)ContributingPull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.Please make sure to update tests as appropriate.LicenseMIT