I want to write unit tests for my project in django rest framwork but I can not compare my res.data with my serializer.data
This is my json for one object Brand :
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"url": "http://localhost:8000/shops/tags/1/",
"id": 1,
"name": "PS4",
"link": "https://www.playstation.com"
}
]
}
This is my unit test :
BRANDS_URL = reverse('brands-list')
def test_retrieve_brand_list(self):
"""Test retrieving a list of brands"""
Brand.objects.create(name='Bestbuy', link='https://bestbuy.ca')
Brand.objects.create(name='Amazon', link='https://amazon.ca')
res = self.client.get(BRANDS_URL)
brands = Brand.objects.all().order_by('-name')
context = {'request': RequestFactory().get('/')}
serializer = BrandsSerializer(brands, context=context, many=True)
self.assertEqual(res.status_code, status.HTTP_200_OK)
self.assertEqual(res.data, serializer.data)
My serializer :
class BrandsSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Brand
fields = ('url', 'id', 'name', 'link')
How I can add count, next, previous and results to my serializer.data ?
How I can have a serializer.data like res.data ?
I'm a little lost, I can not find a solution
You can try this:
*self.assertEqual(res.data['results'], serializer.data)*
Hope this helps.
Related
We have the follwing structure (library->books->pages)
the first serializer
class Library(serializers.ModelSerializer):
books = BookSerializer(many=True)
class Meta:
model = Library
fields = '__all__'
#transaction.atomic
def create(self, validated_data):
# create logic here
the second serializer
class BookSerializer(serializers.ModelSerializer):
pages = PageSerializer(many=True, required=False)
class Meta:
model = Book
fields = '__all__'
we have an endpoint library/, where we post the payload of the following format
{
"ref": "43a0c953-1380-43dd-a844-bbb97a325586",
"books": [
{
"name": "The Jungle Book",
"author": "Rudyard Kipling",
"pages": [
{
"content": "...",
"pagenumber": 22
}
]
}
]
}
all the objects are created in the database, but the response does not contain pages key. It looks like this
{
"id": 27,
"ref": "43a0c953-1380-43dd-a844-bbb97a325586",
"books": [
{
"id": 34,
"name": "The Jungle Book",
"author": "Rudyard Kipling"
}
]
}
depth attribute does not seem to work. What do I have to do to make pages appear in the responce?
We can achieve the desired behavior using depth in the class Meta of the BookSerializer.
class BookSerializer(serializers.ModelSerializer):
...
class Meta:
...
depth = 3
Copied from documentation
The depth option should be set to an integer value that indicates the depth of relationships that should be traversed before reverting to a flat representation.
Another way to get this behavior would be to use serializer.SerializerMethodField for getting the pages of the book serializer.
class BookSerializer(serializers.ModelSerializer):
pages = serializers.SerializerMethodField()
def get_pages(self, obj):
return PageSerializer(obj.page_set.all(), many=True,).data
...
When sending json to the server, it shows the following error:
Direct assignment to the reverse side of a related set is prohibited. Use items.set() instead.
Help me please. I recently started to study DRF, and I don't understand how to correctly write def create in django to write data to a foreign key model?
Here is my code
serializer.py
class ConsignmentNoteSerializer(serializers.ModelSerializer):
create_user = serializers.HiddenField(default=serializers.CurrentUserDefault())
create_user = UserSerializer(source='creator', read_only=True)
contragent_detail = ContragentSerializer(source='contragent', read_only=True)
items = ConsignmentItemSerializer(many=True)
class Meta:
model = ConsignmentNote
fields = ['id', 'doc_type', 'date', 'number', 'contragent_detail', 'comment', 'create_user', 'items', 'created']
**def create(self, validated_data):
items_data = self.validated_data.pop('items')
return ConsignmentNote.objects.create(**validated_data)**
Here is the json I am trying to send
{
"id": 9,
"doc_type": "capitalize",
"date": "2022-06-04",
"number": 98,
"contragent_id": 4,
"comment": "",
"items": [
{
"id": 18,
"product": 10,
"buy_price": "200.00",
"sell_price": "500.00",
"quantity": 5
},
],
}
I think You need to upload the item_ids data.
class ConsignmentNoteSerializer(serializers.ModelSerializer):
...
item_ids = serializers.ListField(
child = serializers.IntegerField,
write_only = True
)
class Meta:
model = ConsignmentNote
fields = ['id', 'doc_type', 'date', 'number', 'contragent_detail', 'comment', 'create_user', 'items', 'created', 'item_ids']
def create(self, validated_data):
item_ids = self.validated_data.pop('item_ids')
new_obj = ConsignmentNote.objects.create(**validated_data)
new_obj.set(item_ids)
return new_obj
And in this case, the payload should be
{
"doc_type": "capitalize",
"date": "2022-06-04",
"number": 98,
"contragent_id": 4,
"comment": "",
"item_ids": [18],
}
This solved my problem
def create(self, validated_data):
items = validated_data.pop('items')
note = ConsignmentNote.objects.create(**validated_data)
for item in items:
product = item.pop('product')
item = ConsignmentItem.objects.create(consignmentnote=note, product=product ,**item)
return note
I m working on this POST request in drf and I lost it somewhere please help.
my models.py
class TargetDefination(models.Model):
targetName=models.CharField(max_length=50)
displayName=models.CharField(max_length=100)
def __str__(self):
return self.targetName
class Target(models.Model):
targetDefn=models.ForeignKey(TargetDefination,on_delete=models.CASCADE)
roleId=models.ForeignKey(Role,on_delete=models.CASCADE)
empId=models.ForeignKey(Employee,on_delete=models.CASCADE)
startDate= models.DateField(default=datetime.date.today)
endDate= models.DateField(null=True,blank=True)
value=models.PositiveIntegerField(default=0)
def __str__(self):
return str(self.empId) + ' ' +str(self.targetDefn)
serializer.py
class TargetSerializers(serializers.ModelSerializer):
targetDefn=TargetDefinationSerializers()
roleId=RoleSerializers()
empId=OnlyEmployeeSerializers()
class Meta:
model = Target
fields = (
'id',
'targetDefn',
'roleId',
'empId',
'startDate',
'endDate',
'value'
)
and this is what I have tried:
views.py
#api_view(['GET','POST'])
def setTarget(request, *args, **kwargs):
if request.method=='GET':
setTrgt=Target.objects.all()
serial=TargetSerializers(setTrgt,many=True)
return JsonResponse(serial.data,safe=False)
elif request.method == 'POST':
data=JSONParser().parse(request)
serial=TargetSerializers(data=data)
if serial.is_valid():
print("working")
target = serial.save()
serializer = TargetSerializers(target)
return JsonResponse(serializer.data,status=status.HTTP_200_OK,safe=False)
return JsonResponse(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
I need to create POST request and the format will be:
{
"id": 6,
"targetDefn": {
"id": 1,
"targetName": "MIN SALES",
"displayName": "MIN SALES"
},
"roleId": {
"id": 1,
"roleName": "CEO",
"description": "Chief executive officer",
"roleReportsTo": null,
"roleReportsToName": null
},
"empId": {
"id": 5,
"empName": "Emp05",
"startDate": "2021-05-04",
"termDate": null
},
"startDate": "2021-05-05",
"endDate": null,
"value": 123
}
this is an example with just values in it.
errors I m getting:
The `.create()` method does not support writable nested fields by default.
Write an explicit `.create()` method for serializer `employee.serializers.TargetSerializers`, or set `read_only=True` on nested serializer fields.
You will have to implement a create() method in your serializer. The implementation of this method should break the request data and then write it into its respective models.
Here is how you can implement the method -
def create(self, validated_data):
#split the objects into multiple objects.
targetDef = validated_data.pop(targetDefn)
#save the objects into its respective models.
targetDefId = TargetDefination.objects.create(**targetDef)
#get the objects of roleId and empID
role = list(validated_data['roleId'].items())
role_id = Role.objects.get(roleName =role[0][1])
emp_id = Employee.objects.get(pk=validated_data['empId']['id'])
target_obj = Target.object.create(targetDef=targetDefId, roleId=role_id, empID=emp_id, startDate=validated_data['startDate'], endDate=validated_data['endDate'], value=validated_data['value'])
return target_obj
I'm trying to create a serializer which outputs the Report and also the User information.
My task is accomplished by this serializer:
class ReportSerializer(serializers.ModelSerializer):
latitude = serializers.CharField()
longitude = serializers.CharField()
city = serializers.IntegerField()
type = serializers.IntegerField()
# We have created a field which returns a value from get_marker_icon_url
marker_icon = serializers.SerializerMethodField('get_marker_icon_url')
status_str = serializers.SerializerMethodField('convert_status_toStr')
type_str = serializers.SerializerMethodField('convert_type_toStr')
status_color = serializers.SerializerMethodField('get_status_color')
likes = serializers.SerializerMethodField('random_likes')
user = ReportUserSerializer()
class Meta:
model = Reports
fields = [
'user',
'id',
'type',
'city',
'latitude',
'longitude',
'likes',
'type_str',
'status_str',
'status_color',
'attached_message',
'marker_icon',
'attached_photo',
'date_created'
]
...
With this code my serializer returns a response like this:
[
{
"user": {
"id": 1,
"username": "3nematix",
"profile_pic": "http://192.168.0.29:8000/frontend/static/frontend/images/reports/user_profile_pic.jpg",
"verified": false
},
"id": 1,
"type": 9,
"city": 0,
"latitude": "6.5123333",
"longitude": "51.512586",
"likes": 27,
"type_str": "OTHER",
"status_str": "PENDING",
"status_color": "orange",
"attached_message": "test",
"marker_icon": "OTHER",
"attached_photo": "http://192.168.0.29:8000/frontend/static/frontend/images/reports/user_profile_pic_VRjIYTs.jpg",
"date_created": "2020-10-21T23:19:06.899302Z"
},
......
]
And this is exactly what I need, but the problem is that when I'm trying to create a new object by a POST request, I get this response:
{
"user": [
"This field is required."
]
}
If I would remove 'user' from Meta and user = ReportUserSerializer() from the ReportSerializer class, then I can create a new object, but when I wish to get the Reports I with the Users information I need to add these two again, how can I fix it?
You need to fill the user field yourself before calling save.
Here's the easy way:
class YourView(generics.ListCreateAPIView):
...
def perform_create(self, serializer):
serializer.save(user=self.request.user)
Or slightly differently:
class YourView(generics.ListCreateAPIView):
...
def perform_create(self, serializer):
serializer.validated_data['user'] = self.request.user
return super().perform_create(serializer)
Context
I have an API endpoint api/v1/checkin/ that returns the current DeviceGroup and the AppVersions for an App that have to be active.
Problem
The endpoint currently returns data along with the correctly filtered AppVersions like this:
{
"customer_device_uuid": "8d252b78-6785-42ea-9aee-b6f9e0f870b5",
"device_group": {
"group_uuid": "869b409d-f281-492e-bb62-d3168aea4394",
"device_group_name": "Default",
"color": "#0a2f45",
"is_default": true,
"app_versions": [
"2.0",
"1.1"
]
}
}
Goal
I want the app_versions in the response to contain more data like this:
{
"customer_device_uuid": "8d252b78-6785-42ea-9aee-b6f9e0f870b5",
"device_group": {
"group_uuid": "869b409d-f281-492e-bb62-d3168aea4394",
"device_group_name": "Default",
"color": "#0a2f45",
"is_default": true,
"app_versions": [
{
"app_version_uuid": "UUID here",
"app_version_name": "1.1",
"package_id": "package name here",
"auto_start": false,
"version_code": 1,
"version_name": "0.1",
"source": "link to file here"
}, ...
]
}
}
Serializers
# serializers.py
class AppVersionSerializer(serializers.ModelSerializer):
auto_start = serializers.BooleanField(source='app_uuid.auto_start')
class Meta:
model = AppVersion
fields = ('app_version_uuid', 'app_version_name', 'package_id', 'auto_start', 'version_code', 'version_name',
'source')
class DeviceGroupSerializer(serializers.ModelSerializer):
app_versions = serializers.SerializerMethodField(read_only=True)
# filters the app versions per app
def get_app_versions(self, model):
qs = model.get_current_app_versions()
return [o.app_version_name for o in qs]
class Meta:
model = DeviceGroup
fields = ('group_uuid', 'device_group_name', 'color', 'is_default', 'app_versions')
class CheckinSerializer(serializers.ModelSerializer):
device_group = DeviceGroupSerializer(many=False, read_only=True, source='group_uuid')
class Meta:
model = CustomerDevice
fields = ('customer_device_uuid', 'customer_uuid', 'device_id_android', 'device_group')
extra_kwargs = {
'customer_uuid': {'write_only': True},
'device_id_android': {'write_only': True}
}
I am guessing that I have to change the get_app_versions() in order to achieve my goal but I have no idea how.
What should I change in order to get the response I want?
EDIT
The get_current_app_versions method that does the filtering
# models.py
def get_current_app_versions(self):
return (
AppVersion.objects
.filter(appversiondevicegrouplink__release_date__lt=timezone.now())
.filter(appversiondevicegrouplink__device_group=self)
.order_by('app_uuid__app_uuid', '-appversiondevicegrouplink__release_date')
.distinct('app_uuid__app_uuid')
)
You are correct in assuming that you will have to change get_app_versions and instead of returning a list in the line return [o.app_version_name for o in qs] you will need to return a list of dictionaries.
You will need to create a full serializer for the AppVersions model. and then in your get_app_versions properly serialize you return values by passing them into your new serializer which contains all the fields you would like to return return AppVersionSerializer2(qs, many=True).data.
You may have to override serialization of certain fields as they may not be handled well by the serializer automatically.