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:
https://stackoverflow.com/a/39026832/2026508
Related
I want to test that my view redirects after I send it correct data. The problem is that I'm using a form that needs parameters while initializing. I have a method that builds me a response that I later use in the tests. The method works for my other views but I can't make it work with views that use Forms that need parameters to initialize them.
In my forms.py I have:
class SupportIssueForm(forms.ModelForm):
class Meta:
model = SupportIssue
fields = ('user', 'property', 'title', 'text', 'is_urgent', 'is_service')
def __init__(self, person_company, properties, is_property, *args, **kwargs):
super(SupportIssueForm, self).__init__(*args, **kwargs)
self.fields['is_service'].widget.attrs['class'] = 'custom-control-input'
...
My method for generating the response I'm later using in my test looks like this:
def generate_logged_in_user_post_response(self, data):
request = self.factory.post(self.url, data={**self.form_class_args[0], **data})
request.user = self.logged_in_user
return new_support_issue_view(request)
I also tried this:
def generate_logged_in_user_post_response(self, data):
form = SupportIssueForm(**self.form_class_args[0], data=data)
request = self.factory.post(self.url, form)
request.user = self.logged_in_user
return new_support_issue_view(request)
the self.form_class_args[0] is some dictionary I declare elsewhere:
{
"person_company": person_company, # <query object>
"properties": properties, # <query object>
'is_property': False
}
I'm pretty new to Django restframework, what i'm trying now is to return object with foreignkey.
class User(models.Model):
name = models.CharField(max_length=255,blank=True)
date_created = models.DateTimeField(auto_now_add=True)
date_modiefied = models.DateTimeField(auto_now=True)
area = models.CharField(max_length=255,blank=True)
uuid = models.CharField(max_length=255)
home = models.CharField(max_length=255,blank=True)
work = models.CharField(max_length=255,blank=True)
mobileNo = models.CharField(max_length=255,blank=True)
email = models.CharField(max_length=255,blank=True)
appVersionCode = models.CharField(max_length=255,blank=True)
photoUrl = models.CharField(max_length=255,blank=True)
serverTime = models.CharField(max_length=255,blank=True)
fcmTokenId = models.CharField(max_length=255,blank=True)
def __str__(self):
return self.name
class LocationData(models.Model):
user = models.ForeignKey(
User, related_name='user', on_delete=models.DO_NOTHING)
source_id = models.CharField(max_length=255)
latitude = models.CharField(max_length=255)
longitude = models.CharField(max_length=255)
speed = models.CharField(max_length=255)
kms = models.CharField(max_length=255)
date_created = models.DateTimeField(auto_now=True)
date_modiefied = models.DateTimeField(auto
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
class LocationDataSerializer(serializers.ModelSerializer):
class Meta:
model = LocationData
fields = '__all__'
depth = 1
I'm using def get_queryset(self):
class SyncIndexLastDataViewSet(viewsets.ModelViewSet):
serializer_class = LocationDataSerializer
def get_queryset(self):
userid = self.request.query_params.get('user_id', None)
userExist = User.objects.filter(id=userid)
if userExist.exists():
# call the original 'list' to get the original response
queryset = LocationData.objects.values('source_id').filter(user__id=userid).order_by('-source_id')[:1]
lastSourceId = queryset[0]['source_id']
response = {"collection": {"data": lastSourceId,"statusCode": status.HTTP_200_OK,"version":"1.0"}}
json = JSONRenderer().render(response)
# customize the response data
if response is not None:
return json
else:
# return response with this custom representation
response = {"collection": {"data": "","statusCode":status.HTTP_404_NOT_FOUND,"version":"1.0","error":"Not found"}}
return response
Right now the result is inside the response is below and immediately it throws this error
But i want that queryset to return as below one, Hence i can read those key-pair values in android
{ "collection": {
"data": {
"id": 31,
"source_id": "55",
"latitude": "24654",
"longitude": "454654",
"date_created": "2019-02-08T17:10:09.318644Z",
"date_modiefied": "2019-02-08T17:10:09.318714Z",
"area": "54546",
"user": {
"id": 1,
"name": "Dormy",
"date_created": "1992-01-18T03:29:53.388000Z",
"date_modiefied": "2018-02-19T05:17:00.164000Z",
"serverTime": "",
"fcmTokenId": ""
}
},
"statusCode": 200,
"version": "1.0"
}
Now the error throws
AttributeError: Got AttributeError when attempting to get a value for field source_id on serializer LocationDataSerializer.
The serializer field might be named incorrectly and not match any attribute or key on the int instance.
Original exception text was: 'int' object has no attribute 'source_id'.
Thanks!
The answer to this depends on what type of view you are using but the bottom line is you don't do this in get_queryset you do this in the method for the type of reguest.
For example if you are using a RetrieveAPIView you should override the retrieve method from the RetrieveModelMixin like so:
class MyAPIView(RetrieveAPIView):
queryset = MyModel.objects.all()
serializer_class = MySerializer
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
data = {
"collection": {
"data": serializer.data
},
"statusCode": 200,
"version": "1.0"
}
return Response(data)
If you are using something else like a ListAPIView then you want to see what is used by that in the relevant method and override that to wrap your data.
The main thing to realise here is that it has nothing to do with getting the queryset - which is just about getting data from the database. This is about transforming the data into the correct format when sending back a response. As a result the work should be done at the point the response is made.
There are couple of solution possible for this problem. NDevox already mention how we can overwrite our retrive function and get our expected response. But If we want this will be done with every response for every api end-point and if we go this way we need to overwrite every function then its quite burden and its DRY we should avoid this as possible. One of the possible way to introduce a middleware or overwrite Response so that we can get our generic response for every-api end-point without explicitly overwrite every functionality.
Possible Solution One
As we are using DRF here we can add our own return responses with various media types, say for application/json.
First We need to add in our settings.py
REST_FRAMEWORK = {
...
'DEFAULT_RENDERER_CLASSES': (
'app_name.renderers.ApiRenderer', # our own render middleware
),
...
}
And in our custom render middleware
from rest_framework.renderers import BaseRenderer
from rest_framework.utils import json
class ApiRenderer(BaseRenderer):
def render(self, data, accepted_media_type=None, renderer_context=None):
our_response_dict = {
'version': '1.0'
'data': {},
'message': '',
}
if data.get('data'):
our_response_dict['data'] = data.get('data')
if data.get('status'):
our_response_dict['statusCode'] = data.get('status')
if data.get('message'):
our_response_dict['message'] = data.get('message')
data = our_response_dict
return json.dumps(data)
Reference Link
Possible Solution Two
If we are using ModelViewset then there is another way we can achievement that. Say Our Views.py are like following
class A(serializer.ModelSerializer):
........
class B(serializer.ModelSerializer):
........
class C(serializer.ModelSerializer):
........
Our goal is to overwrite ModelViewset's to_representation function and return our custom result. This will like as following
from collections import OrderedDict
class OurParentViewset(serializer.ModelSerializer):
......
def to_representation(self, instance):
data = super(serializers.ModelSerializer, self).to_representation(instance)
result = OrderedDict()
result['data'] = data
result['version'] = '1.0'
result['statusCode'] = '2xx' # i am not fully sure how to customize this
return result
class A(OurParentViewset):
........
class B(OurParentViewset):
........
class C(OurParentViewset):
........
Implementing a custom renderer here seems to be a ways to go.
You can have requests from your android client include in the Accept header a way to identify the client to the renderer. 1 e.g.
Accept: application/json; android=true
Then compose a renderer using the JSONRenderer class to provide the format for your Android client.
# ./formatters/android_format.py
from rest_framework.renderers import JSONRenderer, BaseRenderer
from django.http.multipartparser import parse_header
class AndroidV1FormatRenderer(BaseRenderer):
media_type = 'application/json'
format = 'json'
json_renderer = JSONRenderer()
def android(self, accepted_media_type):
base_media_type, params = parse_header(accepted_media_type.encode('ascii'))
return 'android' in params
def render(self, data, accepted_media_type=None, renderer_context=None):
response = renderer_context['response']
android = self.android(accepted_media_type)
if android:
data = {
"collection": {"data": data},
"statusCode": response.status_code,
"version": "1.0"
}
return json_renderer.render(
wrapped_data, accepted_media_type, renderer_context)
This can then be used where you require response formatted that way using renderer_classes attribute of your APIView. 2
Since get_queryset won't allow you to customize the response data. I decide to take the query value that's important to me.
http://localhost/api/users/?user_id=1 --> changed into ...api/users/1
def retrieve(self, request, *args, **kwargs):
""" userid = self.request.query_params.get('user_id', None) """
userid = kwargs.get('pk')
userExist = User.objects.filter(id=userid)
if userExist.exists():
# call the original 'list' to get the original response
queryset = LocationData.objects.values('source_id').filter(user__id=userid).order_by('-source_id')[:1]
lastSourceId = queryset[0]['source_id']
response = {"collection": {"data": lastSourceId,"statusCode": status.HTTP_200_OK,"version":"1.0"}}
# customize the response data
if response is not None:
return Response(response)
else:
# return response with this custom representation
response = {"collection": {"data": "","statusCode":status.HTTP_404_NOT_FOUND,"version":"1.0","error":"Not found"}}
return response
I am trying to develop a REST API with django-rest-framework for updating a django model.
I want to unit test it with the following unit test
from rest_framework.test import APITestCase
class PatchInvestmentTest(APITestCase):
def test_repartition(self):
investment = Investment.objects.create()
sponsor1 = Investment.objects.create(InvestmentSponsor, name='A')
sponsor2 = Investment.objects.create(InvestmentSponsor, name='B')
url = reverse('investments:investments-detail', args=[investment.id])
data = {
'sponsorships': [
{'sponsor': sponsor1.id, 'percentage': 80},
{'sponsor': sponsor2.id, 'percentage': 10},
]
}
print("> data", data)
response = self.client.patch(url, data=data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(1, Investment.objects.count())
investment = Investment.objects.all()[0]
# It fails below : no investments are created
self.assertEqual(len(investment.sponsorships()), 2)
The model can be summed up with
class Investment(models.Model):
# ... a few fields
def sponsorships(self):
return self.investmentsponsorship_set.all().order_by('sponsor__ordering', 'sponsor__name')
class InvestmentSponsor(models.Model):
name = models.CharField(max_length=200, verbose_name=_('name'))
ordering = models.IntegerField(default=0)
class Meta:
ordering = ('ordering', 'name', )
class InvestmentSponsorship(models.Model):
sponsor = models.ForeignKey(InvestmentSponsor)
investment = models.ForeignKey(Investment)
percentage = models.DecimalField(max_digits=5, decimal_places=2)
The api is using rest-framework base classes
class InvestmentViewSet(viewsets.ModelViewSet):
model = Investment
def get_serializer_class(self):
serializers_class_map = {
'default': InvestmentSerializer,
'partial_update': PartialUpdateInvestmentSerializer,
}
return serializers_class_map.get(self.action, serializers_class_map['default'])
def perform_update(self, serializer):
serializer.save()
Then I expect to get and handle the "sponsorhips" data in the serializers
class InvestmentSponsorshipSerializer(serializers.ModelSerializer):
class Meta:
model = models.InvestmentSponsorship
fields = ('sponsor', 'percentage', )
class PartialUpdateInvestmentSerializer(serializers.ModelSerializer):
sponsorships = InvestmentSponsorshipSerializer(many=True)
class Meta:
model = models.Investment
fields = (
'id', '... others', 'sponsorships',
)
def validate_sponsorships(self, value):
print("validate_sponsorships", value)
return value
def update(self, instance, validated_data):
"""update only fields in data"""
data = validated_data.copy()
print("*** DATA", validated_data)
instance.save()
return instance
The problem is that the data I received from the serializer is empty
> data {'sponsorships': [{'sponsor': 1, 'percentage': 80}, {'sponsor': 2, 'percentage': 10}]}
validate_sponsorships []
*** DATA {'sponsorships': []}
This seems to occur only when unit testing. It seems to work from the dango-rest-framework admin.
I've tried to investigate why I don't received the data as validated_data in the update with no success yet.
Any idea?
You should add format parameter when calling patch:
response = self.client.patch(url, data=data, format='json')
Default multipart format does not support nesting I think.
I found a solution by using regular django unit-test class. A bit more difficult but it works
from django.test import TestCase
class PatchInvestmentTest(TestCase):
def test_repartition(self):
investment = Investment.objects.create()
sponsor1 = mommy.make(InvestmentSponsor, name='A', ordering=3)
sponsor2 = mommy.make(InvestmentSponsor, name='B', ordering=2)
url = reverse('investments:investments-detail', args=[investment.id])
data = {
"sponsorships": [
{"sponsor": sponsor2.id, "percentage": 80},
{"sponsor": sponsor1.id, "percentage": 10},
]
}
date_as_json = json.dumps(data)
response = self.client.patch(url, data=date_as_json, content_type="application/json")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(1, Investment.objects.count())
investment = Investment.objects.all()[0]
self.assertEqual(len(investment.sponsorships()), 2)
I'm looking for a way/function/method to make it impossible to save two identical names on my JSON, for example, I got this JSON with repeated names:
[
[
{
"id": "59a5c80dc75969297837c51e",
"name": "uza",
"password": "3648726"
},
{}
],
[
{
"id": "59a5c811c75969297837c51f",
"name": "kuza",
"password": "3648726"
},
{}
],
[
{
"id": "59a5c83ec75969297837c520",
"name": "kuza",
"password": "3648726"
},
{}
]
]
My code that creates an user is this one:
#api.route('/', methods=['POST'])
def create():
# Grabs the data from the requisition
user_json = request.get_json(silent=True)
if not user_json:
return "FAIL"
# creates an entity JSON
user, errors = schema.load(user_json)
if bool(errors):
return jsonify(errors)
user.save()
return "SUCCESS"
Again, I'm using mongoengine, anybody knows how to do it?
Edited to add my model.py
rom mongoengine import Document
from mongoengine import StringField, ReferenceField
import marshmallow_mongoengine as ma
from marshmallow import Schema, fields
from .service import ImageService
class User(Document):
name = StringField(unique=True)
password = StringField(unique=True)
class Face(Document):
user = ReferenceField(User)
image = StringField()
embedding = StringField()
def get_embedding(self):
return ImageService().from_base64_flat(self.embedding.encode())
def get_image(self):
return ImageService().from_base64(self.image.encode())
class UserSchema(ma.ModelSchema):
class Meta:
model = User
class FaceSchema(ma.ModelSchema):
class Meta:
model = Face
image = ma.fields.Method(deserialize="img_to_base64", serialize="img_to_base64")
embedding = ma.fields.Method(deserialize="to_base64", serialize="to_base64")
def img_to_base64(self, data):
return ImageService().to_base64(data)
def to_base64(self, data):
return ImageService().np_to_base64(data)
In the class User i changed it from required to unique, now i can't add one with the same name but in exchange it returns an INTERNAL SERVER ERROR on Insomnia, and as you can see i put another unique on the password for test and it didn't worked it saves even if there is another user with the same password.
Try this
class User(Document):
name = StringField(unique=True)
password = StringField(unique=True)
meta = {
'indexes': [{'fields': ['name'], 'unique': True}]
}
I'm using Django with DRF and python mock. What I am trying to do is to test my views and mock a serializer and some methods.
This is what I have:
views.py
from gmm_mobile.v1.serializers import RegisterParticipationSerializer
from gmm_mobile.v1.exceptions import GameOrCampaignDoesNotExist
from gmm_util.header import Header
from gmm_util.response import ResponseUtils
from gmm_util.permissions import MobileTokenPermission
from gmm_util.permissions import MobileTokenAuthentication
class ParticipantViewMobile(APIView):
permission_classes = (MobileTokenPermission, )
authentication_classes = (MobileTokenAuthentication, )
def post(self, request, service_id, campaign_id):
try:
environment_info = Header.get_environment_info(request)
request.data[Field.GAME_SERVICE_ID] = service_id
request.data[Field.CAMPAIGN] = campaign_id
request.data[Field.LOCATION] = environment_info
participation_serializer = RegisterParticipationSerializer(data=request.data)
participation_serializer.is_valid(raise_exception=True)
participation_serializer.save()
return ResponseUtils.created()
except Http404:
raise GameOrCampaignDoesNotExist()
serializers.py
class RegisterParticipationSerializer(serializers.ModelSerializer):
location = LocationSerializer(many=False)
campaign = serializers.IntegerField()
game_service_id = serializers.CharField(max_length=254)
class Meta:
model = Participation
fields = (Field.PK, Field.EMAIL, Field.GPG_ID, Field.DATE, Field.LOCATION, Field.INFO, Field.CAMPAIGN,
Field.GAME_SERVICE_ID, Field.GCM_ID)
read_only_fields = (Field.GCM_ID,)
test_views.py
from mock import patch
from django.core.urlresolvers import reverse
from rest_framework.test import APITestCase, APIRequestFactory
from rest_framework import status
from rest_framework.exceptions import ValidationError
from gmm_mobile.v1.views import ParticipantViewMobile
from gmm_mobile.v1.urls import CONSULT_PARTICIPANT
from gmm_push.environment_configuration import EnvironmentConfiguration
from gmm_util.util_test import JsonObjects
class ParticipantViewMobileTests(APITestCase):
factory = APIRequestFactory()
url = reverse(PARTICIPANT_MOBILE, kwargs={'service_id': 1, 'campaign_id': 1})
def setUp(self):
self.view = ParticipantViewMobile.as_view()
#patch('gmm_mobile.v1.views.RegisterParticipationSerializer')
def test__post__invalid_data__status_400(self, mock_serializer):
# Arrange
mock_serializer.is_valid.side_effect = ValidationError({})
request = self.factory.post(
self.url,
{},
HTTP_X_ENV=JsonObjects.environment_info_1(),
HTTP_X_TOKEN=EnvironmentConfiguration.get_token(False),
format='json')
# Act
response = self.view(request, 1, 1)
# Assert
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
But when I run the test, the side_effect is not working. When I change the .is_valid in the test it has no effect on ParticipantViewMobile.post.
If I do #patch('gmm_mobile.v1.views.RegisterParticipationSerializer.is_valid') and mock_serializer.side_effect = ValidationError({}) it works, but I don't want that because there will be cases that I will need to mock more than one method, like .save and .is_valid.
I also tried to change the import styles views.py and test_views.py but it still didn't work.
Am I doind something wrong or missing anything?
EDIT 1:
I've put prints on the view to try to understand what was happening:
def post(self, request, service_id, campaign_id):
try:
environment_info = Header.get_environment_info(request)
request.data[Field.GAME_SERVICE_ID] = service_id
request.data[Field.CAMPAIGN] = campaign_id
request.data[Field.LOCATION] = environment_info
participation_serializer = RegisterParticipationSerializer(data=request.data)
print RegisterParticipationSerializer.is_valid.side_effect
print participation_serializer.is_valid.side_effect
participation_serializer.is_valid(raise_exception=True)
participation_serializer.save()
return ResponseUtils.created()
except Http404:
raise GameOrCampaignDoesNotExist()
And the output:
{}
None
So, when I create an instance of RegisterParticipationSerializer, I lost the mocked methods. How to avoid this?
Your mock doesn't work because you're mocking the class and not the instance being generated from the class. Try this:
mock_instance = Mock()
mock_instance.is_valid.side_effect = ValidationError({})
mock_serializer.return_value = mock_instance