Send request body to serializers - python

in my Django project, I have the following for serializers and views
serializers:
class AnimalSerializer(serializers.ModelSerializer):
class Meta:
model = Animal
fields = [
'pk',
'name',
'animal_type',
'weight',
'color',
]
views:
class AnimalRudView(generics.RetrieveUpdateDestroyAPIView):
serializer = AnimalSerializer
def create(self,request):
body = json.loads(request.body.decode('utf-8')
with connection.cursor() as cursor:
cursor.execute("SELECT * FROM animals WHERE " + body['search'])
animal = [dict(zip([column[0] for column in cursor.description], row)) for row in cursor.fetchall()]
return Response(serializer.data)
In views, I get a request body with certain conditions ( as " search ": " weight>20 "), which gives me then all objects which match these conditions.
I want to add this body to the serializer as well because I want to fill the fields list automatically.
Do you have any idea how I can parse the body into the serializer?
Best regards

As an example from one of my custom post methods from a class extending CreateAPIView:
class BaseQuestionCheckerView(CreateAPIView):
serializer_class = MySerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
# I can now access the validated data from my
# serialiser. But first I have to call is_valid():
try:
serializer.is_valid(raise_exception=True)
except Http404:
raise Http404
validated_data = serializer.validated_data
# common thing to do with serializer.validated_data
# is to return it.
return Response(serializer.validated_data, status=status.HTTP_200_OK)
Hope that gets you on the right track!

Related

Django Rest framework - data between two serializers do not validate

I have a API view in place where I first want to create a new user (already working) and second I want to return the new created user object using my UserSerializer (Not working).
views.py
#api_view(['POST'])
#permission_classes([AllowAny])
def user_create(request):
exception_handler = UserUnavailable
success_handler = UserCreated
if request.method == 'POST':
creation_serializer = CreateUserSerializer(data=request.data)
try:
if creation_serializer.is_valid(raise_exception=True):
creation_serializer.save()
user_serializer = UserSerializer(data=creation_serializer.instance.id)
if user_serializer.is_valid():
return JsonResponse({"status_code": success_handler.status_code,
"default_detail": success_handler.default_detail,
"default_code": success_handler.default_code,
"new_user": user_serializer,
}, safe=False)
except APIException:
return JsonResponse({"status_code": exception_handler.status_code,
"default_detail": exception_handler.default_detail,
"default_code": exception_handler.default_code
}, safe=False)
I can confirm that creation_serializer.instance.id contains the new users id.
serializers.py
class UserSerializer(serializers.ModelSerializer):
id = serializers.PrimaryKeyRelatedField(queryset=User.objects.all())
class Meta:
model = get_user_model()
fields = ('id', 'user')
read_only_fields = ('id', 'user')
I can also confirm that
if user_serializer.is_valid()
does not validate for some reason. Any ideas
Thanks in advance
To initialize a serializer with user instance instead of
user_serializer = UserSerializer(data=creation_serializer.instance.id)
You should write something like this:
user_serializer = UserSerializer(instance=creation_serializer.instance)
instead of
Also, you don't have to validate the object that has already been saved.
And create a response you should like this:
return JsonResponse({
"status_code": success_handler.status_code,
"default_detail": success_handler.default_detail,
"default_code": success_handler.default_code,
"new_user": user_serializer.data,
}, safe=False)
But using api_view is now a bad thing. It's much better to use ModelViewSet. You can use something like this:
class UserViewSet(ModelViewSet):
serializer_class = UserSerializer
queryset = User.objects.all()
def get_serializer_class(self, *args, **kwargs):
if self.action == 'create':
return CreateUserSerializer
return super().get_serializer_class(*args, **kwargs)
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
response_serializer = self.serializer_class(instance=serializer.instance)
return Response(response_serializer.data, status=status.HTTP_201_CREATED, headers=headers)
And I'm sorry, I haven't tested it, there might be typos.
You can read about ModelViewsets more here https://www.django-rest-framework.org/api-guide/viewsets/#modelviewset

how can insert multiple record using

I'm working on a small project Django Rest Framework, I already create add contact function as you can see in my create function. now I'm working on bulk import, but when I submit my data as a list not as a dict I get an error message :
{"non_field_errors":["Invalid data. Expected a dictionary, but got list."]}
this is my code to add a contact,
class ContactView(ListModelMixin, viewsets.GenericViewSet):
queryset = Contact.objects.all()
serializer_class = ContactSerializer
def create(self, request):
serializeObject = ContactSerializer(data = request.data)
if serializeObject.is_valid():
serializeObject.save()
contactObject = Contact.objects.all()
contactSerializer = ContactSerializer(contactObject, many=True)
return Response(contactSerializer.data, status = status.HTTP_201_CREATED)
return Response(serializeObject.errors, status.HTTP_400_BAD_REQUEST)
Now i would like to create another function, for bulk create, since i have a list
This is my header data structure :
[{"Greeting":"amine","first_name":"alain","last_name":"amine","title":"ricardo","language":"ab#xyz.com","email":43822510594,"phone_1":43822510594,"phone_2":"not yet","mobile":43822510594,"fax":"not yet","internal_id":"xname"},{"Greeting":"bill","first_name":"microsoft","last_name":"bill","title":"microsoft","language":"bill#microsoft.com","email":652565455,"phone_1":652565455,"phone_2":"new york","mobile":652565455,"fax":"new york","internal_id":"microsoft"},{"Greeting":"john","first_name":"Yoyo","last_name":"Ruth","title":"xnameagain","language":"rh#xyz.com","email":5465559852,"phone_1":5465559852,"phone_2":"Vancouver","mobile":5465559852,"fax":"Vancouver","internal_id":"yname"}]
This is my serializer:
class ContactSerializer(serializers.ModelSerializer):
class Meta:
model = Contact
fields = "__all__"
I found the Solution on https://www.django-rest-framework.org/api-guide/serializers/#dealing-with-multiple-objects
all what i have to do is to add many=True to create multiple object
serializeObject = ContactSerializer(data = request.data, many=True)
Create method should look like this:
class ContactView(ListModelMixin, viewsets.GenericViewSet):
queryset = Contact.objects.all()
serializer_class = ContactSerializer
def create(self, request):
valid_objects = []
for data in request.data:
serializeObject = ContactSerializer(data=data)
if serializeObject.is_valid():
valid_objects.append(serializeObject)
else:
return Response(serializeObject.errors, status.HTTP_400_BAD_REQUEST)
for obj in valid_objects:
obj.save()
contactObject = Contact.objects.all()
contactSerializer = ContactSerializer(contactObject, many=True)
return Response(contactSerializer.data, status = status.HTTP_201_CREATED)
Advise
They may not be the best practices but it works.

Django DRF change serializer data with CreateListModelMixin

I have this class view, but I am unable to modify the serializer data to insert more data (which is needed and needs to be populated automatically).
Because I am creating many instances at once, the serializer is based on kwargs['many'] = True.
Any idea on how I can add another field to each serializer data?
Thanks,
:
class ReservedServiceView(CreateListModelMixin, ModelViewSet):
queryset = ReservedService.objects.all()
serializer_class = ReservedServiceSerializer
authentication_classes = (authentication.TokenAuthentication,)
def perform_create(self, serializer):
# Create an event that is a Reflection of the Reserved Service
serializer_data = self.request.data
for reserved_service in serializer_data:
print("--------",reserved_service, flush=True)
service_id = reserved_service['original_service']
original_service = Service.objects.get(pk=service_id)
calendar_event = CalendarEvent()
calendar_event.name = original_service.name
calendar_event.description = original_service.description
calendar_event.date = reserved_service['date']
calendar_event.type_id = 1
calendar_event.start_time = reserved_service['start_time']
calendar_event.end_time = reserved_service['end_time']
calendar_event.location_id = original_service.location.id
calendar_event.save()
reserved_service['associated_event'] = calendar_event.id
print("**********1", serializer_data)
print("**********2", self.request.data)
serializer.save()
Based in:
class CreateListModelMixin(object):
def get_serializer(self, *args, **kwargs):
""" if an array is passed, set serializer to many """
if isinstance(kwargs.get('data', {}), list):
kwargs['many'] = True
return super(CreateListModelMixin, self).get_serializer(*args, **kwargs)
I am not able to properly get your question, but if your question is that you are not getting the extra fields which you added to the serializer in the response of the view, then here is the answer for it.
The response of this view is returned by create method of CreateModelMixin which passes serializer.data to the data param of Response. You cannot update serializer.data because it is an immutable object. So, to solve this you will have to override the create method as follows:
class ReservedServiceView(CreateListModelMixin, ModelViewSet):
queryset = ReservedService.objects.all()
serializer_class = ReservedServiceSerializer
authentication_classes = (authentication.TokenAuthentication,)
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
my_data = {}.update(serializer.validated_data)
# Now you can work over the my_data and add extra fields to it and save it
# and instead of passing serializer.data we pass my_data to Response class
headers = self.get_success_headers(serializer.data)
return Response(my_data, status=status.HTTP_201_CREATED, headers=headers)

How to set user_id field using JWT in DRF

I want to set user_id field using JWT token and store in database table when new reservation is created. there can be single or multiple reservation request.
whenever user create reservation i want to store there user_id in our table. currently there is no foreign key associated with it. it is simply an integer field.
I am able to fetch user_id from JWT.but its not updating in database
I know this question had been asked previously i tried all the answer of previous post but its not working for me. i dont know why
model.py
class reservations(models.Model):
pet_id=models.IntegerField()
user_id=models.IntegerField(default=0)
location=models.PositiveSmallIntegerField()
arrival=models.DateTimeField()
depature=models.DateTimeField()
comments=models.TextField(max_length=200)
view.py
class requestReservation(CreateAPIView):
serializer_class = requestReservationSerailizer
permission_classes = [IsAuthenticated]
def create(self, request, *args, **kwargs):
serializer = requestReservationSerailizer(data=request.data,context={'user_id': request.user.id}, many=True)
if not serializer.is_valid(raise_exception=False):
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response({"message":"Success","status_message":"Reservation Created Successfully"},status=status.HTTP_200_OK)
serializer.py
class requestReservationSerailizer(ModelSerializer):
user_id = SerializerMethodField('set_user_id')
class Meta:
model=reservations
fields = [
'pet_id',
'user_id',
'location',
'arrival',
'depature',
'comments',
]
def set_user_id(self, obj):
obj.user_id = self.context.get("user_id")
return obj.user_id
currently it is simply storing user_id as 0 which is default set in model.
SerializerMethodField is read-only by default, here's a quick look at the source code:
def __init__(self, method_name=None, **kwargs):
self.method_name = method_name
kwargs['source'] = '*'
kwargs['read_only'] = True
super(SerializerMethodField, self).__init__(**kwargs)
Assuming you want to read and write into this field; remove the SerializerMethodField overriding from the serializer declaration; and set the user_id in your view
class requestReservationSerailizer(ModelSerializer):
class Meta:
model=reservations
fields = [
'pet_id',
'user_id',
'location',
'arrival',
'depature',
'comments',
]
def create(self, request, *args, **kwargs):
data = request.data.copy()
for datum in data:
datum['user_id'] = request.user.id
serializer = requestReservationSerailizer(data=data, many=True)
if not serializer.is_valid(raise_exception=False):
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response({"message":"Success","status_message":"Reservation Created Successfully"},status=status.HTTP_200_OK)
Ofcourse; if you don't want your view to be doing this (and I'd agree with you here), then pass it in context (explicit) or use self.request.user.id and override validate_user_id
class requestReservationSerailizer(ModelSerializer):
class Meta:
model=reservations
fields = [
'pet_id',
'user_id',
'location',
'arrival',
'depature',
'comments',
]
def validate_user_id(self, value):
user_id = self.context.get('user_id', None) # Assuming you continue to pass it in context
if user_id is None:
# Handle error
return user_id
# You can also do this; might raise an AttributeError if the user is not authenticated:
# return self.request.user.id

How to update multiple records at once (bulk update) in django API

I need to update categories in many Article in one request.
In ArticleViewSet I have:
def get_serializer_class(self):
if self.action in ['partial_update', 'update']:
return ArticlePostSerializer
return ArticleSerializer
So ArticlePostSerializer need to be changed.
This is my serializers code:
class ArticleShortCategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = 'id', 'name'
class ArticleSerializer(serializers.ModelSerializer):
categories = serializers.SerializerMethodField()
def get_categories(self, obj):
return ArticleShortCategorySerializer(obj.categories, many=True).data
class Meta:
model = Article
read_only_fields = 'id'
fields = ('categories', 'text') + read_only_fields
class ArticlePostSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = 'id', 'categories', 'text'
I tried to add:
class ArticlePostListSerializer(serializers.ListSerializer):
and
class Meta:
list_serializer_class = ArticlePostListSerializer
But it doen't work.
How to change this code to do multiple update.
My json request
{
[id: 90, categories: [10,12,14]],
[id: 93, categories: [10,12,14]],
[id: 95, categories: [10,12,14]]
}
Here is sample of CreateMixins OR UpdateMixins you requested.
======================= VIEW ================================
class OrderCreate(mixins.CreateModelMixin,viewsets.GenericViewSet):
pagination_class = None
def get_queryset(self):
return []
def get_serializer_class(self):
return serializers.OrderSerializer
======================= Serializer =============================
class OrderDetailSerializer(serializers.ModelSerializer):
class Meta:
model = crm_models.OrderDetail
fields = (
'product',
'quantity',
'rate_per_unit',
'order_quantity'
)
class OrderSerializer(serializers.ModelSerializer):
order_details = OrderDetailSerializer(many = True)
class Meta:
model = crm_models.OrderMaster
fields = (
'order',
'invoice_number',
'client',
'beat_check',
'target_customer',
'order_editor_client_employee',
'order_marked',
'order_saved',
'edit_marked',
'edit_saved',
'adhoc',
'order_details'
)
def create(self, validated_data,*args,**kwargs):
ordersdetails_data = validated_data.pop('order_details')
user = None
request = self.context.get("request")
if request and hasattr(request, "user"):
user = request.user
validated_data['client'] = user.client
validated_data['order_editor_client_employee'] = user
validated_data['adhoc'] = validated_data['adhoc'] if 'adhoc' in validated_data else False
orderObj = super(OrderSerializer, self).create(validated_data,*args,**kwargs)
orderdetails = []
for details in ordersdetails_data:
orderdetails.append(crm_models.OrderDetail(
product= details['product'],
quantity = details['quantity'],
rate_per_unit = details['rate_per_unit'],
order_quantity = details['order_quantity'],
order = orderObj
))
crm_models.OrderDetail.objects.bulk_create(orderdetails)
return orderObj
In Update view function name would be changed to update, you can find more documentation http://www.django-rest-framework.org/api-guide/generic-views/#createmodelmixin
I found K. Moe's answer to this question: Django Rest Framework POST Update if existing or create much easier to understand and implement. You only need to add a create method in the serializer and use mixins.CreateModelMixin, generics.GenericAPIView in the view. Then you can use a POST request, instead of PUT/PATCH. It allows to create AND update data stored in your database. My code for the view:
class ZipCodeList(mixins.CreateModelMixin, generics.GenericAPIView):
def post(self, request, format=None):
is_many = isinstance(request.data, list)
if is_many:
serializer = ZipCodeSerializer(data=request.data, many=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
else:
serializer = ZipCodeSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
#Greg Holst, why so much duplication? Why not:
class ZipCodeList(mixins.CreateModelMixin, generics.GenericAPIView):
def post(self, request, format=None):
serializer = ZipCodeSerializer(data=request.data, many=isinstance(request.data, list))
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Anyway, that only worked for me for creating new objects, didn't work to create-or-update in one sweep (it kept telling me these objects already exist), so this is what I did:
class ContributorSyncListAPIView(ListAPIView):
permission_classes = (isSuperUserPermission,)
allowed_methods = ("GET", "PUT")
lookup_field = "airtable_id"
serializer_class = ContributorSyncSerializer # Doesn't get used in Put, just in Get.
model = Contributor
def get_queryset(self):
return self.model.objects.all()
def put(self, request, format=None):
objects = list(request.data)
# objects is a list of OrderedDicts
try:
for o in objects:
try:
instance = self.model.objects.get(
**{self.lookup_field: o.get(self.lookup_field)}
)
for key, value in o.items():
setattr(instance, key, value)
except self.model.DoesNotExist:
instance = self.model(**o)
instance.save()
return Response(objects, status=status.HTTP_200_OK)
except Exception as e:
return Response({"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST)
Note my code above is very light on validation just because it was for a process of syncing models from one environment to another by a superuser, different environments with an identical codebase; so the assumption was the data had already been validated when the data was entered into the first environment. For any other purpose you'd want to validate more. But this is what I had to do to handle a list of objects that may need to be created or updating, on an object-by-object basis.

Categories