Where to add default value in CreateAPIView? - django-rest-framework

I have a ListCreateAPIView, on which I want to populate a field with a default value in case of not provided by request.DATA.
Problem is: where should I do that ?
I can't modify the request.DATA because it is immutable and I don't want to copy/paste the CreateMixin implementation.
Here is my code:
class ObjectiveList(generics.ListCreateAPIView):
model = Objective
serializer_class = ObjectiveSerializer
permission_classes = (permissions.IsAuthenticated,)
def create(self, request, *args, **kwargs):
# provide a default value
objective_definition_id = request.DATA.get('objective_definition',-1)
data = request.DATA.copy()
if objective_definition_id == -1:
# support for 0.9.1 version of iOS and below
logger.info(str(self.request.DATA))
mission_url = request.DATA["mission"]
objectivedefinition_pk = self.default_objectivedefinition_id(mission_url)
data["objective_definition"]=objectivedefinition_pk
# I would want to do something like this but I can't
# request.DATA = data
# super(ObjectiveList,self).create(request, *args, **kwargs)
# copy/paste of the super class implementation
serializer = self.get_serializer(data=data, files=request.FILES)
if serializer.is_valid():
self.pre_save(serializer.object)
self.object = serializer.save(force_insert=True)
self.post_save(self.object, created=True)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED,
headers=headers)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Here is my serializer:
class ObjectiveSerializer(serializers.HyperlinkedModelSerializer):
objective_definition = serializers.PrimaryKeyRelatedField(many=False, read_only=False, required=False, default=toto)
class Meta:
model = Objective
fields = (
'url',
'objective_definition',
)

You can use the default= argument on the field, don't know if that meets your use case?

Related

validating json fields

This is my serializer file
from rest_framework import serializers
#All validations here
class BudgetSerializer(serializers.Serializer):
start_date = serializers.DateTimeField(format="%Y-%m-%d %H:%M:%S")
end_date = serializers.DateTimeField(format="%Y-%m-%d %H:%M:%S")
lifetime_budget = serializers.FloatField(max_value=None, min_value=None,allow_null=True)
def validate(self, attrs):
if attrs['start_date'] > attrs['end_date']:
raise serializers.ValidationError("start_date must be less than end date")
return attrs
class IoSerializer(serializers.Serializer):
name = serializers.CharField()
state = serializers.CharField()
currency = serializers.CharField(read_only=True)
budget_type = serializers.CharField(read_only=True)
budget_intervals = BudgetSerializer(many=True)
Json which i am serializing has 1000's of fields but i only want to validate few fields and return all json fields.But problem with serialzer is that it only outputs json consisting of mentioned field in serialiser.
Like i am sending json with 1000s of object names but it only return json which consist of start_date,end_date,lifetime_budget,name because it is mentioned in serializer file.I wanted to only validate few fields so i wrote them in serializer file but only this fields are returned.
My views.py code
class InsertionOrderViewset(viewsets.ViewSet):
def create(self, request, format=None):
advertiser_id = request.query_params.get('advertiser_id', None)
data = json.dumps(request.data)
io_object = json.loads(data, object_hook=lambda x: namedtuple('io_object', x.keys())(*x.values()))
serializer = IoSerializer(data=request.data, instance=io_object)
serializer.is_valid(raise_exception=True)
response_data = IO.create(data=request.data, params={"advertiser_id": advertiser_id })
return Response(request.data)
serializers.data method will only return values which are supplied to it unless you declare some read_only fields and or do any of these - supply an instance during serializer initialization, return an instance from serializer.save method. When serializer.instance has a value then read_only fields are returned with serializer.data. Your use case suggests that the incoming JSON object has 1000s of data of which only a small portion requires validation. My suggestion will be not to pass all the data, pluck only the necessary portion of the data, send them to serializer and do validation, return serializer.errors if any else rejoin the serializer.data with the incoming JSON and return the JSON. Since, majority of the data has nothing to do with validation, passing them in to the serializer will cause significant performance issue due to the way drf-serializer validates and maps values.
Still if you still want to supply all of the JSON to serializer and validate only a few consider supplying an instance when initializing serializer, dynamically declare ReadOnlyField in your serializer.__init__
serializers.py
from rest_framework import serializers
# default attributes of objects
default_attrs = ['__doc__', '__module__', '__slots__', '_fields', '_source']
class BudgetSerializer(serializers.Serializer):
start_date = serializers.DateTimeField(format="%Y-%m-%d %H:%M:%S")
end_date = serializers.DateTimeField(format="%Y-%m-%d %H:%M:%S")
lifetime_budget = serializers.FloatField(max_value=None, min_value=None,allow_null=True)
def __init__(self, *args, **kwargs):
super(BudgetSerializer, self).__init__(*args, **kwargs)
instance = kwargs.get('instance', None)
for attr in dir(instance):
if attr not in self.fields and not callable(getattr(instance, attr)) and attr not in default_attrs:
self.fields[attr] = serializers.ReadOnlyField()
def validate(self, attrs):
if attrs['start_date'] > attrs['end_date']:
raise serializers.ValidationError("start_date must be less than end date")
return attrs
class IoSerializer(serializers.Serializer):
name = serializers.CharField()
state = serializers.CharField()
currency = serializers.CharField(read_only=True)
budget_type = serializers.CharField(read_only=True)
budget_intervals = BudgetSerializer(many=True)
def __init__(self, *args, **kwargs):
super(IoSerializer, self).__init__(*args, **kwargs)
instance = kwargs.get('instance', None)
for attr in dir(instance):
if attr not in self.fields and not callable(getattr(instance, attr)) and attr not in default_attrs:
self.fields[attr] = serializers.ReadOnlyField()
inside views.py
import json
from collections import namedtuple
# inside view
data = json.dumps(request.data)
io_object = json.loads(data, object_hook=lamdba x: namedtuple('io_object', x.keys())(*x.values()))
serializer = IoSerializer(data=request.data, instance=io_object)
serializer.is_valid(raise_exception=True)
print(serializer.data)

How to write the functionality in generics.ListAPIView which can be written in APIView in Django DRF

I have a function base view which get 2 parameters from URL
http://127.0.0.1:8000/api/v1/contest/0b36d92a-51a7-4752-9df1-e5f2733116c1/paintings/
#api_view(['GET',])
#permission_classes([AllowAny])
def Contest_detail_by_id_and_category(request, id, category_name):
if request.method == 'GET':
artcontests = Artwork.objects.filter(artcontest = id,category__name__iexact=category_name)
serializer = ArtworkSerializer(artcontests, many=True)
# serializer = ArtworkSerializer(artcontests, many=True)
return Response(serializer.data)
which give proper result , but when I try to write the same functionality in generics.ListAPIView it gives
TypeError at /api/v1/contesty/0b36d92a-51a7-4752-9df1-e5f2733116c1/paintings/
object of type 'method' has no len()
class Contest_detail_by_id_category(generics.ListAPIView):
serializer_class = ArtworkSerializer1(many=True)
permission_classes = [AllowAny]
def queryset(self):
queryset = Artwork.objects.filter(artcontest = self.kwargs['id'],category__name__iexact=self.kwargs['category_name'])
# queryset = self.get_queryset()
serializer = ArtworkSerializer1(queryset)
return Response(serializer.data)
Can anyone help me - how to write the correct view in generics.ListAPIView or viewsets.ModelViewSet
Remove many=True from serializer_class. Create get_queryset function as below, instead of your queryset function.
class Contest_detail_by_id_category(generics.ListAPIView):
serializer_class = ArtworkSerializer1
queryset = Artwork.objects.all()
permission_classes = [AllowAny]
def get_queryset(self):
return self.queryset.filter(artcontest = self.kwargs['id'],category__name__iexact=self.kwargs['category_name'])
Update: Working:
ListAPIView class inherits ListModelMixin.
class ListModelMixin:
"""
List a queryset.
"""
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
See here. https://github.com/encode/django-rest-framework/blob/master/rest_framework/mixins.py

AttributeError: 'collections.OrderedDict' object has no attribute 'model_id' and 'model_id' is missing from visible fields

Something strange happened: I was defining an endpoint and initially two fields were visible in the API form: model_id and payload, as given in the model definition:
### models.py:
class CarModel(models.Model):
model_id = models.CharField(max_length=10, primary_key=True)
name = models.CharField(max_length=40)
active = models.BooleanField(default=True)
def __str__(self):
return self.model_id
class Calculator(models.Model):
model = models.ForeignKey(CarModel, on_delete=models.CASCADE)
payload = models.TextField()
def model_id(self):
return self.model.model_id
def __str__(self):
return f"Calculations for {self.model.name}"
### serializers.py:
class CalculatorSerializer(serializers.ModelSerializer):
model_id = serializers.SerializerMethodField()
class Meta:
model = Calculator
fields = ['model_id', 'payload']
def get_model_id(self, obj):
return obj.model_id()
### views.py:
class CalculatorViewSet(viewsets.ModelViewSet):
serializer_class = CalculatorSerializer
queryset = Calculator.objects.all()
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
return Response(f"{serializer.data.upper()}", status=status.HTTP_200_OK)
So, both fields were visible, but POST requests ended in the AttributeError: 'collections.OrderedDict' object has no attribute 'model_id'. Trying to fix that, I eventually and accidentally removed model_id from view - it doesn't display in DRF's forms. And the AttributeError still persists.
What is wrong with this piece of code?
OK, it turns out that defining fields in this manner:
fields = '__all__'
makes also the model_id visible. Still, no idea why explicit insert doesn't work.
In case of the other issue, the AttributeError, I had to pull the value out of an OrderedDict. Modified method looks like this:
def get_model_id(self, obj):
return obj["model"].model_id
Beside that, I found one more error inside views.py's create method: serializer.data won't implement upper() method; some key, in my case serializer.data['payload'], has to be referenced, so for example:
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
res = {
"payload": f"{serializer.data['payload'].upper()}"
}
return Response(res, status=status.HTTP_200_OK)

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)

multiple object creation on a single API call on django

i have to create multiple object creation on a single API call.My
serializer class is like this
def create(self, validated_data):
dt = datetime.now()
strg = '{:%d%m%Y%H%M%S}'.format(dt, dt.microsecond // 1000)
hat= "REQ" + strg
creater = dot_order_models.DotOrder(reqnum=hat,**validated_data)
creater.save()
return creater
class Meta:
model = dot_order_models.DotOrder
fields = ('merchant', 'biker','batch','customer_name','contact','city','locality','order_amount')
i just given many=true but did not work. then i give
def __init__(self, *args, **kwargs):
many = kwargs.pop('many', True)
super(BikerSerializer, self).__init__(many=many, *args, **kwargs)
this too didn't work.how can i solve it using serializers and view class.
thanks in advance
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data, many=isinstance(request.data, list))
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
results = dot_order_models.DotOrder.objects.all()
output_serializer = dot_order_serializers.DotOrderSerializer(results, many=True)
return super(DotOrderViewSet, self).create(request)

Resources