Cannot generate post request for multiple data - django-rest-framework

I am trying to take input multiple data object in post request, but getting such error.
non_field_errors: [ Invalid data. Expected a dictionary, but got a list. ]
models.py
class OrderProduct(BaseModel):
product = models.ForeignKey(Product,on_delete=models.CASCADE)
order = models.ForeignKey(Order,on_delete=models.CASCADE)
order_product_price = models.FloatField(blank=False,null=False,default=0) # product may belong to offer do the price
order_product_qty = models.FloatField(default=1)
serializers.py
class OrderProductSerializer(serializers.ModelSerializer):
def update(self,instance,validated_data):
product = self.validated_data.pop('product')
order = self.validated_data.pop('order')
instance.orderproduct_qty =
self.validated_data.get('orderproduct_qty',instance.orderproduct_qty)
instance.product = product
instance.order = order
instance.save()
return instance
class Meta:
model = OrderProduct
fields = '__all__'
views.py
def post(self,request,*args,**kwargs):
if request.data['contact_number'] == '':
request.POST._mutable =True
request.data['contact_number'] = request.user.mobile_number
request.POST._mutable = False
serializer = OrderSerializer(data=request.data,many=isinstance(request.data,list),context={'request': request})
print(serializer)
if serializer.is_valid():
serializer.save(user = request.user,created_by = request.user)
return Response(serializer.data,status=status.HTTP_200_OK)
else:
return Response(serializer.errors,status=status.HTTP_400_BAD_REQUEST)
urls.py
path('orderproduct/',views.OrderProductList.as_view()),

When you call serializer.save(). It's only perform create() action which is only create one and accept dictionary data type only. If you want to save multiple data like that, you will have to override the create function of the serializer class. You can do something similar like this or run a for loop.
serializers.py
def create(self, validate_data):
# Get the data objects you need to perform bulk create
order_products = OrderProduct.objects.bulk_create(validate_data)
return order_products
views.py
if serializer.is_valid(raise_exception=True):
# Replace the serializer.save() by this line to trigger the create method in serializer
self.perform_create(serializer)
return Response(...)

Related

I have to create serializer instance without data since i am not accepting any data from user

Here i am trying to create serializer instance without data argument because all i want to create a "Like" object which requires "user" object which i can get from request and "post" object that i am getting through querying Post model with pk but since i am not passing any data argument while deserializing it will throw an error.
So how can create instance without passing data argument or do i have to change my code and add data argument?
class Like(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name="likes")
liked_by = models.ForeignKey(User, on_delete=models.RESTRICT, related_name = "liked_posts")
def __str__(self):
return "{0} liked by {1}".format(self.post.img, self.liked_by.username)
class LikeSerializer(serializers.ModelSerializer):
liked_by = UserSerializer(read_only=True)
class Meta:
model = Like
fields = ("id","post", "liked_by")
class AddLike(APIView):
permission_classes = [IsAuthenticated]
def post(self, request, pk):
post = Post.objects.get(pk=pk)
user = request.user
serializer = LikeSerializer()
if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
serializer.save(post=post, liked_by=user)
return Response(data= serializer.data, status=status.HTTP_201_CREATED)
I think you can set the post field as read_only in the LikeSerializer.
class LikeSerializer(serializers.ModelSerializer):
liked_by = UserSerializer(read_only=True)
class Meta:
model = Like
fields = ("id","post", "liked_by")
extra_kwargs = {
'post': { 'read_only': True }
}

'QuerySet' object has no attribute 'pk'

#models
class Student(models.Model):
firstname = models.CharField(max_length=100,default='ll')
lastname = models.CharField(max_length=100,default='fewf')
id_code = models.CharField(max_length=10,default=0,unique=True)
melli = models.CharField(max_length=30,default=0,unique=True)
personal_pic = models.ImageField(upload_to=studentFile)
major = models.ForeignKey(Major, on_delete=models.PROTECT,default=0)
date_of_start = models.DateField(default=datetime.date.today)
def __str__(self):
return self.id_code
#views
class loginView(APIView):
def post(self, request):
data = request.data
melli = data.get('melli')
id_code = data.get('id_code')
student = Student.objects.filter(id_code=id_code,melli=melli)
if not student.exists():
return Response('error')
serializer = StudentSerializer(student,data=data)
serializer.is_valid()
return Response(serializer.data)
when i try to submit a post request i was excepted to recive a response but i got an error. how can i solve it?
You serialize a collection of elements, so you should work with many=True:
serializer = StudentSerializer(student,data=data, many=True)
in case you want to only work with a single object, you need to retrieve a single object, not a collection of objects, for example with .get(…) [Django-doc] instead of .filter(…) [Django-doc].
Model.filter returns a queryset, which is like a list of models, rather than a specific model. Instead, you should use get, which returns a single model instance:
student = Student.objects.get(id_code=id_code,melli=melli)
If you intend on there being more than 1 student in this specific query, you can add many=True to your serializer instead:
serializer = StudentSerializer(student,data=data, many=True)

Django: customizing the field types needed for create and retrieve serializers

I currently have the following serializer:
serializers.py
class SurfGroupSerializer(serializers.ModelSerializer):
instructor = SurfInstructorSerializer(many=False)
surfers = SurferSerializer(many=True)
class Meta:
model = SurfGroup
fields = ['uuid', 'instructor', 'date', 'starting_time', 'ending_time', 'surfers']
def create(self, validated_data):
return SurfGroup(**validated_data)
And the following viewset create method (viewset inherited from viewsets.ViewSet as we need some bespoke customization, extra signals and actions etc):
viewsets.py
# Surf Group Create View:
def create(self, request, format=None):
serializer = SurfGroupSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
response = responses.standardized_json_response(
message='Surf Group Objects Have Been Successfully Created',
data=serializer.data
)
return Response(data=response, status=status.HTTP_201_CREATED, headers=headers)
For the retrieve action, the serializer works well, and we have a nested instructor object in the response. However, I want to perform a create by passing in the instructor uuid attrbiute like (see content in the POST textarea):
Rather than a whole object...I was wondering how we achieve this? Is it best to just have two Serializers, one for performing the create, and one the retrieval?
def create(self, validated_data):
surf_group = SurfGroup(
instructor__uuid=validated_data['instructor'],
)
surf_group.save()
return surf_group
It is good question.
I work with this situations many times and it looks like one option is to have two serializers as you mean: 1 for list/retrieve and 1 for save.
Another option (for me) is to set serializer field input as UUID and output as another serializer data like this:
class SurfGroupSerializer(serializers.ModelSerializer):
instructor = serializers.UUIDField()
surfers = SurferSerializer(many=True, read_only=True)
class Meta:
model = SurfGroup
fields = ['uuid', 'instructor', 'date', 'starting_time', 'ending_time', 'surfers']
# I use this validate method to transform uuid to object which will
# be bypassed to create method for easly save
def validate_instructor(self, instructor_uuid):
try:
return Instructor.objects.get(uuid=instructor_uuid)
except Instructor.DoesNotExist:
# Remember that you dont need to pass field_key: [errors] to ValidationError
# because validate_FIELD will automatically pass field_key for you !
raise ValidationError(['Instructor with the given uuid does not exist.'])
# Overwrite output data
def to_representation(self, instance):
ret = super().to_representation(instance)
ret['instructor'] = SurfInstructorSerializer(instance=instance.instructor).data
return ret

Django REST Framework - Extending ListAPIView with custom PUT method

I developed APIs using Django REST Framework for an inventory management application.
The endpoint to GET the list of products includes query parameters to filter the list. See below.
Product List View:
class ProductListAPIView(ListAPIView):
serializer_class = ProductListSerializer
queryset = Product.objects.all()
permission_classes = [DjangoModelPermissionsWithView]
filter_backends = [SearchFilter, DjangoFilterBackend, OrderingFilter]
search_fields = [
'sku',
'product_name',
...
]
filter_class = ProductFilter
pagination_class = ProductPageNumberPagination
ordering = ['-id']
ordering_fields = [
'id',
'sku',
'product_name',
...
]
def get_serializer_context(self, *args, **kwargs):
return {"request": self.request}
I have created another view to handle requests in order export the products to PDF, CSV, etc:
class ProductExportAPIView(APIView):
def put(self, request, *args, **kwargs):
# We use the seriaziler only to validate request.data
serializer = ProductExportSerializer(data=request.data)
if serializer.is_valid():
user_id = request.user.pk
file_key = request.data.get('file_key')
file_name = request.data.get('file_name', '')
extra_args = request.data.get('extra_args', {})
product_ids = request.data.get('product_ids')
# NOTE THAT export_file IS A CELERY TASK
export_file.delay(user_id, file_key, file_name, product_ids, extra_args)
return Response(status=status.HTTP_204_NO_CONTENT)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
The API it's working fine, but it works only if the user selects the products - the product_ids field is used to provide the list of products to be exported.
I would like to let the users export ALL the products via ProductExportAPIView by providing the query params that I'm using with ProductListAPIView rather than providing product_ids.
product_ids should be an optional field to be used only to export a few products.
How I can enable query parameters filtering on my ProductExportAPIView, there is a way to do this without hardcoding it? Can I extend ProductListAPIView with the PUT method to export products?
In order to use the same query parameters defined in ProductListAPIView, now ProductExportAPIView extends ProductListAPIView, so it inherits everything I needed:
class ProductExportAPIView(ProductListAPIView):
permission_classes = [AllowAny]
http_method_names = ['put'] # disable GET method inherited from ProductListAPIView
def put(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
# We use the serializer only to validate request.data
serializer = ProductExportSerializer(data=request.data)
if serializer.is_valid():
user_id = request.user.pk
file_key = request.data.get('file_key')
file_name = request.data.get('file_name', '')
extra_args = request.data.get('extra_args', {})
product_ids = request.data.get('product_ids', [])
if len(product_ids)==0:
product_ids = [p.pk for p in queryset]
export_file.delay(user_id, file_key, file_name, product_ids, extra_args)
return Response(status=status.HTTP_204_NO_CONTENT)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

django rest_framework serializer parameter

I am using django rest_framework to provide jsGrid json data.
As the rest_framwork example, I create a object fit the jsGrid format
class jsGridResp(object):
def __init__(self, data, itemsCount):
self.data = data
self.itemsCount = itemsCount
and the class based view, create a get function
class RateListViewSet(mixins.CreateModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
generics.GenericAPIView):
"""
API endpoint that allows user to be viewed or edited
"""
queryset = RateList.objects.all().order_by('-create_date')
serializer_class = RateListSerializer
def get_object(self):
queryset = self.filter_queryset(self.get_queryset())
obj = queryset.get(pk=self.request.POST["id"])
self.check_object_permissions(self.request, obj)
return obj
def get(self, request, format=None):
pageIndex = request.GET.get('pageIndex')
pageSize = request.GET.get('pageSize')
sortField = request.GET.get('sortField', 'id')
sortOrder = request.GET.get('sortOrder', 'asc')
sortOrder = "" if sortOrder == "asc" else "-"
rows = RateList.objects.all().order_by("{}{}".format(sortOrder, sortField))
itemsCount = rows.count()
paginator = Paginator(rows, pageSize)
try:
rows = paginator.page(pageIndex)
except PageNotAnInteger:
rows = paginator.page(1)
except EmptyPage:
rows = paginator.page(paginator.num_pages)
result = jsGridResp(data=rows, itemsCount=itemsCount)
serializer = RateListGetSerializer(result)
json = JSONRenderer().render(serializer.data)
return Response(json)
then I create two serializer to serialize the data
class RateListSerializer(serializers.ModelSerializer):
class Meta:
model = RateList
fields = ('id', 'rate_code', 'hr01', 'hr02', 'hr03', 'hr04', 'hr05', 'hr06',
'hr07', 'hr08', 'hr09', 'hr10', 'hr11', 'hr12', 'hr13', 'hr14',
'hr15', 'note', 'create_date', 'update_date')
read_only_fields = ('create_date', 'update_date')
def update(self, instance, validated_data):
result = instance.update(id=instance.id, **validated_data)
return result
def destroy(self, instace, validated_data):
return "{seccuess: true}"
class RateListGetSerializer(serializers.Serializer):
itemsCount = serializers.IntegerField()
data = RateListSerializer(many=True)
but I have many model need to do like these.
can I use just one serializer to serialize all model.
I want to create a jsGridGetSerializer can pass in a model parameter, so I don't need to create many simple serialzer to do the same thing.
Is this possible?
If I understand your problem correctly, you want a generic serializer which accepts a Model variable as a parameter in its Meta class.
One way to do this is to pass in the model name as a url keyword argument, then catching it in views.py. Then you can override the Meta model via overriding the get_serializer_class:
serializers.py
class GenericSerializer(serializers.ModelSerializer):
class Meta:
model = None
views.py
class GenericViewSet(viewsets.ModelViewSet):
def get_queryset(self):
model = self.kwargs.get('model')
return model.objects.all()
def get_serializer_class(self):
GenericSerializer.Meta.model = self.kwargs.get('model')
return GenericSerializer

Resources