I'm trying to write a test to check that the create method of my Viewset works ok:
#pytest.mark.django_db
def test_create(admin_user):
parent = factories.ParentFactory()
payload = {
'name': 'child',
'parent_id': parent.pk,
}
view = views.ParentViewSet.as_view({'post': 'create'})
request = factory.post('/', payload, format='json')
force_authenticate(request, user=admin_user)
response = view(request)
assert response.status_code == status.HTTP_201_CREATED
assert models.Child.objects.count() == 1
child = models.Child.objects.first()
assert child.name == 'child'
However, I get the following error when I run the code:
psycopg2.errors.NotNullViolation: null value in column "parent_id" violates not-null constraint
But the test for the Parent create method runs fine:
def test_create(admin_user):
payload = {
'name': 'parent',
'children': [
{
'name': 'child',
}
],
}
view = views.ParentViewSet.as_view({'post': 'create'})
request = factory.post('/', payload, format='json')
force_authenticate(request, user=admin_user)
response = view(request)
assert response.status_code == status.HTTP_201_CREATED
assert models.Parent.objects.count() == 1
season = models.Parent.objects.first()
assert season.name == 'parent'
assert season.children.count() == 1
Can someone tell me the correct way to write the payload for the child test create?
Edit:
serializers.py
class ChildSerializer(serializers.ModelSerializer):
class Meta:
model = models.Child
fields = ['id', 'name']
class ParentSerializer(serializers.ModelSerializer):
children = ChildSerializer(many=True)
class Meta:
model = models.Parent
fields = ['id', 'name', 'children']
def create(self, validated_data):
children = validated_data.pop('children')
parent = super().create(validated_data)
for child in children:
child['parent'] = parent
self.fields['children'].create(children)
return parent
views.py
class ParentViewSet(
viewsets.GenericViewSet,
mixins.CreateModelMixin,
mixins.ListModelMixin,
mixins.RetrieveModelMixin,
mixins.DestroyModelMixin,
):
queryset = models.Parent.objects.all().prefetch_related(
'children'
)
serializer_class = serializers.ParentSerializer
class ChildViewSet(
viewsets.GenericViewSet,
mixins.CreateModelMixin,
mixins.ListModelMixin,
mixins.RetrieveModelMixin,
mixins.DestroyModelMixin,
):
queryset = models.Child.objects.all().prefetch_related(
'children'
)
serializer_class = serializers.ChildSerializer
I think you have a field "parent" in the child model, but as you are not including that field in ChildSerializer, even if you send it in the request body, it is not included while creating a Child instance, hence the error you're getting. You have several ways to solve it
1 - You can add parent field to ChildSerializer:
class ChildSerializer(serializers.ModelSerializer):
class Meta:
model = models.Child
fields = ['id', 'name', 'parent']
and send the request like this:
payload = {
'name': 'child',
'parent': parent.pk,
}
2 - If you do not want that, you may keep the ChildSerializer as it is now, and add parent only while saving the child instance, with something like this:
class ChildViewSet(
...
):
queryset = models.Child.objects.all().prefetch_related('children')
serializer_class = serializers.ChildSerializer
def perform_create(self, serializer):
serializer.save(parent_id=self.request.data.get('parent'))
Again you'd need to send the request like this:
payload = {
'name': 'child',
'parent': parent.pk,
}
But in this second scenario, you're not including parent field in request validation, and if a client does not send parent field or sends an invalid value there, you'll get an exception. If you are to go with this solution, I'd advise to somehow validate parent field as well.
Related
Need help , i am trying to push nested relations inside DB don't know where I am going wrong in this, is there something wrong with validated_data , which is a list of dict here , thanks in advance
class CatalogSerializer(serializers.ModelSerializer):
catalog_products = CatalogProductsSerializer(source = 'catalogproducts_set',many=True)
class Meta:
model = Catalog
fields = ['created_by','client','catalog_products','created_datetime','is_active']
def create(self,validate_data):
client_id = validate_data.pop('id')
client = User.objects.get(id=client_id),
catalog_obj = Catalog.objects.create(
client = client,
created_by = self.context['user'],
is_active =True,
)
for pricelist_ins in validate_data:
CatalogProducts.objects.create(
catalog = catalog_obj,**pricelist_ins)
return catalog_obj
Basic Viewset
class CatalogViewset(viewsets.ModelViewSet):
queryset = Catalog.objects.all()
serializer_class = CatalogSerializer
permission_classes = []
authentication_classes = []
def create(self, request, *args, **kwargs):
if request.data:
try:
serialized_data = self.get_serializer(data = request.data)
if serialized_data.is_valid(raise_exception=True):
serialized_data.save()
return Response(serialized_data.data,status=200)
except Exception as e:
return Response({'error':str(e)},status=400)
return Response({'status':'invalid request'},status=400)
the error I am getting in Postman
{
"error": "{'catalog_products': [ErrorDetail(string='This field is required.', code='required')]}"
}
data i am posting
{
"id":"2",
"pricing_list":[
{
"from_quantity":"101",
"to_quantiy":"34",
"price":"1000"
},
{
"from_quantity":"10",
"to_quantiy":"501",
"price":"2000"
}
]
}
You have catelogue_products in the fields, it is by default required. But you are not posting any catelogue_products. You need to post data based on the fields of the serializer. validated data will not contain any other data, but valid data that was set in serializer.
To make it optional you may try to add required=False in the serialzier like this:
class CatalogSerializer(serializers.ModelSerializer):
catalog_products = CatalogProductsSerializer(source = 'catalogproducts_set',many=True, required=False)
class Meta:
model = Catalog
fields = ['created_by','client','catalog_products','created_datetime','is_active']
I have the following serializer :
class SalesProjectListSerializer(serializers.ModelSerializer):
permissions = serializers.SerializerMethodField('get_custompermissions',read_only=True)
class Meta:
model = SalesProject
fields = ['sales_project_id', 'sales_project_name',
'sales_project_est_rev', 'project_status','permissions']
depth = 2
def get_custompermissions(self, obj):
permission_list = ['add_salesproject']
user_perms = User.get_user_permissions(self.context['request'].user)
return { permission: True if permission in user_perms else False for permission in permission_list }
This serializer is used to serialize the data thats used to render the project listing page.
The serialized data would give me something like :
projects = [{sales_project_id : 1 , sales_project_name = 'test , ... ,permissions: [...]}]
However instead what i wish to return is somthing like this :
projects = {projects:[{sales_project_id : 1 , sales_project_name = 'test , ... }]
,permissions: [...]}
You cand override the method responsible for the response depending on your View type.
I assume you are using a ListAPIView so this is how you would do it:
class YourView(ListAPIView):
model = SalesProject
serializer_class = SalesProjectListSerializer
def list(self, request, *args, **kwargs):
serializer = self.get_serializer(self.get_queryset(), many=True)
# change the data
# serializer.data is the response that your serializer generates
res = {"projects": serializer.data}
return Response(res)
It's the same for other views such as RetrieveAPIView but you should override the retrieve method instead.
I have a problem with filtering objects in View Set... I am trying to show objects only where field 'point' is null.
I always get error: NameError: name 'null' is not defined
Could you please HELP ME ?
My code:
class CompanyMapSerializer(serializers.ModelSerializer):
class Meta:
model = Company
fields = ('name', 'point', 'url', 'pk')
extra_kwargs = {
'url': {'view_name': 'api:company-detail'},
}
def to_representation(self, instance):
ret = super(CompanyMapSerializer, self).to_representation(instance)
ret['point'] = {
'latitude': instance.point.x,
'longitude': instance.point.y
}
return ret
And view set code:
class CompanyMapViewSet(viewsets.ModelViewSet):
queryset = Company.objects.filter(point = null)
serializer_class = CompanyMapSerializer
PageNumberPagination.page_size = 10000
Please help me.
You are not defining what null is, and Python doesn't recognize null as a primitive, you've got two options:
queryset = Company.objects.filter(point = None) # using None
queryset = Company.objects.filter(point__isnull = True) # explicitly asking for Null
These two queries are equally valid.
I was bit confused about having custom structure with custom field, attached my reference implementation. If you see do_representation you output was not as expected, anyone know why is it so?
class ImageSerializer(serializers.ModelSerializer):
thumb = serializers.ImageField()
class Meta:
model = Image
fields = ('thumb',)
class ProductSerializer(serializers.ModelSerializer):
def product_url(self,obj):
request = self.context['request']
return request.build_absolute_uri(reverse('product', args=(obj.slug,)))
url = serializers.SerializerMethodField('product_url')
images = ImageSerializer(many=True)
class Meta:
model = Product
fields = ('url', 'images', 'id')
def to_representation(self,product):
raise Exception(product.url) # Not working
raise Exception(product.images) # Not working
raise Exception(product.id) # Working
Here is the error message
'Product' object has no attribute 'url'
Note: But if I don't override with to_representation then json response has url field
My workaround
Models
class Product(models.Model):
title = models.CharField(max_length=256,null=True,blank=False)
class ProductImage(models.Model):
product = models.ForeignKey('Product',on_delete=models.CASCADE,null=True,blank=True,related_name='product_images')
image = models.ImageField(upload_to='product/',null=True)
thumb = ImageSpecField(source='image',
processors=[ResizeToFill(100, 1100)],
format='JPEG',
options={'quality': 70})
Actual Output
{
"count":5,
"next":"http://localhost:8000/api/products/?format=json&page=2",
"previous":null,
"results":[
{
"id":1,
"images":[
{
"thumb":"http://localhost:8000/media/CACHE/images/product/Product1/fee2eb25a1d7b954632dd377aca39995.jpg"
},
{
"thumb":"http://localhost:8000/media/CACHE/images/product/Product2/a279c5057bb5ee6e06945f98d89cc411.jpg"
}
],
"url":"http://localhost:8000/product/wooden-furniture/"
}
]
}
Expected Output
{
"count":5,
"next":"http://localhost:8000/api/products/?format=json&page=2",
"previous":null,
"results":[
{
"id":1,
"images":[
"thumb":"http://localhost:8000/media/CACHE/images/product/Product1/fee2eb25a1d7b954632dd377aca39995.jpg",
"popup":"http://localhost:8000/media/CACHE/images/product/Product2/a279c5057bb5ee6e06945f98d89cc411.jpg" # Will work on it once I achieve it wil popup
],
"url":"http://localhost:8000/product/wooden-furniture/"
}
]
}
My custom structure tryout
def to_representation(self,obj):
return {
'id': obj.id,
'images': {
'thumb': obj.images # May be I have to work to get only first image here
},
'url': obj.url
}
As you are respresnting the data by your own. So you have to do other things by own.
Try this,
class ProductSerializer(serializers.ModelSerializer):
def product_url(self,obj):
request = self.context['request']
return request.build_absolute_uri(reverse('product', args=(obj.slug,)))
class Meta:
model = Product
fields = ('url', 'images', 'id')
def to_representation(self, product):
image_objs = ProductImage.objects.filter(product=product)
images = ImageSerializer(image_objs, many=True)
return {
"id": product.id,
"images" images.data,
"url": self.product_url(product)
}
Using django & django-rest-framework, I have the following model (this is simplified but it's all there):
class Device(Model):
#stuff
class DeviceInformation(Model):
device = ForeignKey(Device, reverse='infos')
key = CharField(max_length=32)
value = CharField(max_length=1024)
When serializing a device through django-rest-framework's ModelSerializer, I get something like this:
{
//stuff
infos: [{
'key':'BatteryLevel',
'value':'80%'
},{
'key':'DeviceName',
'value':'my device'
}, //etc
]
}
Which is perfectly normal. However, it would make much more sense to serialize into something like this:
{
//stuff
infos: {
'BatteryLevel':'80%',
'DeviceName':'my device',
//etc
}
}
How do I do that? Is it even possible?
Note that I don't need to deserialize any of these information.
EDIT: my serializers are as follows:
class DeviceInfoSerializer(ModelSerializer):
class Meta:
model = DeviceInformation
fields = ('key', 'value')
read_only_fields = fields
class DeviceSerializer(HyperlinkedModelSerializer):
udid = serializers.CharField(read_only=True)
def __init__(self, *args, **kwargs):
super(DeviceSerializer, self).__init__(*args, **kwargs)
if hasattr(self, 'object') and self.object and not self.many:
self.data['infos'] = DeviceInfoSerializer(
self.object.infos.all(), many=True).data
class Meta:
model = Device
fields = ['udid', 'model', 'tracked']
read_only_fields = ('model', 'tracked')
slug_field = 'udid'
For your readonly-case, the best way is to use SerializerMethodField.
This would change your DeviceSerializer and remove the need for your DeviceInfoSerializer.
class DeviceSerializer(HyperlinkedModelSerializer):
udid = serializers.CharField(read_only=True)
infos = serializers.SerializerMethodField('get_infos')
def get_infos(self, obj):
return {
info.key: info.value
for info in obj.infos.all()
}
class Meta:
model = Device
fields = ['udid', 'model', 'tracked', 'infos']
read_only_fields = ('model', 'tracked', 'infos')
slug_field = 'udid'