Add additional field to response in pytest-django - django

I'm new to testing and I spent a day finding a solution for my problem but I couldn't find any.
this is my serializer
serilaizer.py
class LeadSerializer(serializers.ModelSerializer):
def create(self, validated_data):
user = self.context['user']
return Lead.objects.create(organizer=user.organizeruser, **validated_data)
class Meta:
model = Lead
fields = ['id', 'first_name', 'last_name', 'age', 'agent', 'category', 'description', 'date_added',
'phone_number', 'email', 'converted_date'
]
I have two types of users, organizer, and agent. organizer can create a lead but agent can't. and as you see I don't have organizer field. authenticated user will be added to the organizer field when a Lead is created.
test.py
def test_if_lead_exist_return_200(self, api_client, leads_factory, user_factory):
user = user_factory.create(is_organizer=True)
api_client.force_authenticate(user=User(is_staff=True))
lead = leads_factory.create()
serializer = LeadSerializer(context={'request': user})
print(serializer)
# here I can see the user
response = api_client.get(f'/api/leads/{lead.id}/', )
assert response.status_code == status.HTTP_200_OK
assert response.data == {
'id': lead.id,
'first_name': lead.first_name,
'last_name': lead.last_name,
'age': lead.age,
'organizer': lead.organizer.id,
'agent': lead.agent.id,
'category': lead.category.id,
'description': lead.description,
'date_added': lead.date_added,
'phone_number': lead.phone_number,
'email': lead.email,
'converted_date': lead.converted_date,
}
because there is no organizer field in the serialzier test won't pass and this is the result of the test
what can I do here? can I pass the organizer user to the response?

You should add the organizer into the fields.
class LeadSerializer(serializers.ModelSerializer):
class Meta:
model = Lead
# here I added the `organizer` field
fields = ['id', 'first_name', 'last_name', 'age', 'agent', 'category', 'description', 'date_added', 'phone_number', 'email', 'converted_date', 'organizer']
def create(self, validated_data):
...

Related

Update/merge data instead of replacing it with PUT method in Django Rest Framework

In a Django app (that uses DRF), I have a user profile model. To update the profile info, I want to use PUT. However, I have a field called "meta", that is an object/dict itself. If I am missing any of its properties (gender, mobile, birthday), I will lose that data since the whole "meta" is replaced with the new one. That does not happen with any of the fields (e.g., if I do not specify the first name, the field will just stay the same). Here's an example of a PUT request body:
{
"id": 1,
"first_name": "Jane",
"last_name": "Doe",
"email": "jane#example.com",
"meta": {
"gender": "female",
"mobile": 123456789,
"birthday":"01-01-1970"
}
}
What can I do to assure that the missing properties are not lost? Is there a way to implement or force a merge/update of the previous data with the one in the request?
Here's the method:
def put(self, request, pk):
user = User.objects.get(id=pk)
serializer = UserSerializer(user, data=request.data, context={'request': request})
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
And here's the serializer:
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = [
'id',
'url',
'first_name',
'last_name',
'email',
'meta'
]
Override the update(...) method of the serializer
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = [
'id',
'url',
'first_name',
'last_name',
'email',
'meta'
]
def update(self, instance, validated_data):
instance_meta = instance.meta.copy()
instance_meta.update(validated_data.get("meta", {}))
validated_data["meta"] = instance_meta
return super().update(instance, validated_data)

How to use a nested serialize for serializing and deserializing data? django-rest-framework

I have following serializers:
class UserSerializer(ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'password')
class ProfileSerializer(ModelSerializer):
user = UserSerializer()
class Meta:
model = Profile
fields = ('id', 'user', 'name', 'address')
when I want to create a profile I should send following data:
{
"user":{
"username": "test_username",
"password": "123456789"
},
"name": "David",
"address": "Baker St"
}
my question is, is it possible to just send "user": 5 instead of sending dictionary in case of POST request?
You have two options, either you can use two different serializers for retrieve and create like this
class ProfileCreateSerializer(ModelSerializer):
class Meta:
model = Profile
fields = ('id', 'user', 'name', 'address')
and
class ProfileRetrieveSerializer(ProfileCreateSerializer):
user = UserSerializer()
and decide which serializer to use in view (hint: override get_serializer_class method)
OR
Use one serialzer and decide field type according to action type:
class ProfileCreateSerializer(ModelSerializer):
class Meta:
model = Profile
fields = ('id', 'user', 'name', 'address')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.context['request'].method == 'GET': # or whatever condition you want to use
self.fields['user'] = UserSerializer()
you can do, that you can create your custom serializer fields
class CustomForeignKeyField(serializers.PrimaryKeyRelatedField):
def get_queryset(self):
return self.queryset
def to_representation(self, value):
value = super().to_representation(value)
user = User.objects.get(pk=value)
return UserSerializer(user).data
in serializer you can use this fiedls
class ProfileSerializer(ModelSerializer):
user = CustomForeignKeyField(queryset=User.objects.all())
class Meta:
model = Profile
fields = ('id', 'user', 'name', 'address')
it will accept value as int and return the response in json

Django query taking too much time

I'm using django and django rest framework to make a query from all users in data that have a permission sent as a url parameter but this query is taking too long.
I'm user pycharm debugger how can I try to check why is it taking to long, this is the function:
#list_route(url_path='permission/(?P<permission>.+)')
def read_permission(self, request, *args, **kwargs):
serializer = self.get_serializer_class()
qs = get_user_model().objects.filter_by_permission(self.kwargs.get('permission'))
qs = qs.order_by(Lower('username'))
return Response(serializer(qs, many=True).data)
Update
Adding the serializer
class UserSerializer(UserLabelMixin):
user_permissions = serializers.SlugRelatedField(many=True, read_only=True, slug_field='codename')
class Meta:
model = get_user_model()
fields = ['id', 'email', 'is_superuser', 'is_staff', 'label',
'full_name', 'first_name', 'last_name', 'username',
'teams', 'date_joined', 'last_login',
'user_permissions', 'groups', 'ui_preferences', 'internal_project',
'staff_id', 'oem_id', 'oem_email', 'oem_department', 'comment']
read_only_fields = fields
This may help you
get_user_model().objects.prefetch_related("user_permissions", "groups").filter_by_permission...

tastypie check for missing fields in json

I am creating an api using tastypie. Code is as follows. I have inspected code using ipdb.
class UserProfileResource(ModelResource):
class Meta:
queryset = User.objects.all()
resource_name = 'userprofiles'
excludes = ['password', 'is_active', 'is_staff', 'is_superuser']
filtering = {
'username': ALL,
}
class UserResource(ModelResource):
user = fields.ForeignKey(UserProfileResource, 'user', full=True)
class Meta:
queryset = User.objects.all()
resource_name = 'users'
list_allowed_methods = ['get','post']
allowed_methods = ['get', 'put', 'patch', 'delete']
authentication = ApiKeyAuthentication()
serializer = Serializer(formats=['json', 'jsonp', 'xml', 'yaml', 'html', 'plist'])
def post_list(self, request, **kwargs):
print 'post list method'
mandatory_fields = ['username', 'email', 'first_name', 'last_name', 'address', 'city', 'pin']
fields_apicall = request.GET.keys()
#import ipdb;ipdb.set_trace();
print request.POST
if set(mandatory_fields).issubset(fields_apicall):
print request.GET.keys()
create_object = True
else:
print 'NO'
err_dict = { 'error':'Mandatory Fields Missing.' }
return HttpResponse(json.dumps(err_dict))
def obj_create(self, bundle, **kwargs):
print 'obj_create() '
mandatory_fields = ['username', 'email', 'first_name', 'last_name', 'address', 'city', 'pin']
fields_apicall = bundle.data.keys()
# import ipdb;ipdb.set_trace();
if set(mandatory_fields).issubset(fields_apicall):
bundle.obj = self._meta.object_class()
else:
err_dict = { 'error':'Mandatory Fields Missing.' }
return HttpResponse(json.dumps(err_dict))
I want to create nested user object, ie user object together with userprofile. For that I have to make sure all fields exists in the json. I tried overriding hydrate(),dehydrate(),and post_list() methods. Among these only post_list() get invoked on a post request.
I can check for fields in request json by overriding post_list() method but is this a good way to check for missing fields? I have googled and have gone through many SO posts but I didn't find any posts mentioning to override post_list() to check for missing fields some of them said to override hydrate(). Also is it necessary to override obj_create() method to create objects using tastypie?
obj_create() seems like the best place to do it. Something like this should work:
def obj_create(self, bundle, **kwargs):
mandatory_fields = ['username', 'email', 'first_name', 'last_name', 'address', 'city', 'pin']
for field_name, field_object in self.fields.items():
if field_name not in mandatory_fields:
err_dict = { 'error':'Mandatory Fields Missing.' }
return HttpResponse(json.dumps(err_dict))
super(SomeResource, self).obj_create(self, bundle, **kwargs)

Django Rest Framework make OnetoOne relation ship feel like it is one model

I have my User saved in two different models, UserProfile and User. Now from API perspective, nobody really cares that these two are different.
So here I have:
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('url', 'username', 'first_name', 'last_name', 'email')
and
class UserPSerializer(serializers.HyperlinkedModelSerializer):
full_name = Field(source='full_name')
class Meta:
model = UserProfile
fields = ('url', 'mobile', 'user','favourite_locations')
So in UserPSerializer the field user is just a link to that resource. But form a User perspective there is really no reason for him to know about User at all.
Is there some tricks with which I can just mash them together and present them to the user as one model or do I have to do this manually somehow.
You can POST and PUT with #kahlo's approach if you also override the create and update methods on your serializer.
Given a profile model like this:
class Profile(models.Model):
user = models.OneToOneField(User)
avatar_url = models.URLField(default='', blank=True) # e.g.
Here's a user serializer that both reads and writes the additional profile field(s):
class UserSerializer(serializers.HyperlinkedModelSerializer):
# A field from the user's profile:
avatar_url = serializers.URLField(source='profile.avatar_url', allow_blank=True)
class Meta:
model = User
fields = ('url', 'username', 'avatar_url')
def create(self, validated_data):
profile_data = validated_data.pop('profile', None)
user = super(UserSerializer, self).create(validated_data)
self.update_or_create_profile(user, profile_data)
return user
def update(self, instance, validated_data):
profile_data = validated_data.pop('profile', None)
self.update_or_create_profile(instance, profile_data)
return super(UserSerializer, self).update(instance, validated_data)
def update_or_create_profile(self, user, profile_data):
# This always creates a Profile if the User is missing one;
# change the logic here if that's not right for your app
Profile.objects.update_or_create(user=user, defaults=profile_data)
The resulting API presents a flat user resource, as desired:
GET /users/5/
{
"url": "http://localhost:9090/users/5/",
"username": "test",
"avatar_url": "http://example.com/avatar.jpg"
}
and you can include the profile's avatar_url field in both POST and PUT requests. (And DELETE on the user resource will also delete its Profile model, though that's just Django's normal delete cascade.)
The logic here will always create a Profile model for the User if it's missing (on any update). With users and profiles, that's probably what you want. For other relationships it may not be, and you'll need to change the update-or-create logic. (Which is why DRF doesn't automatically write through a nested relationship for you.)
I just came across this; I have yet to find a good solution particularly for writing back to my User and UserProfile models. I am currently flattening my serializers manually using the SerializerMethodField, which is hugely irritating, but it works:
class UserSerializer(serializers.HyperlinkedModelSerializer):
mobile = serializers.SerializerMethodField('get_mobile')
favourite_locations = serializers.SerializerMethodField('get_favourite_locations')
class Meta:
model = User
fields = ('url', 'username', 'first_name', 'last_name', 'email', 'mobile', 'favourite_locations')
def get_mobile(self, obj):
return obj.get_profile().mobile
def get_favourite_locations(self, obj):
return obj.get_profile().favourite_locations
This is horribly manual, but you do end up with:
{
"url": "http://example.com/api/users/1",
"username": "jondoe",
"first_name": "Jon",
"last_name": "Doe",
"email": "jdoe#example.com",
"mobile": "701-680-3095",
"favourite_locations": [
"Paris",
"London",
"Tokyo"
]
}
Which, I guess is what you're looking for.
I would implement the modifications on the UserPSerializer as the fields are not going to grow:
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('url', 'username', 'first_name', 'last_name', 'email')
class UserPSerializer(serializers.HyperlinkedModelSerializer):
url = serializers.CharField(source='user.url')
username = serializers.CharField(source='user.username')
first_name = serializers.CharField(source='user.first_name')
last_name = serializers.CharField(source='user.last_name')
email = serializers.CharField(source='user.email')
class Meta:
model = UserProfile
fields = ('mobile', 'favourite_locations',
'url', 'username', 'first_name', 'last_name', 'email')