Django Serializer only serializing objects with many=True - python

I am trying to make a serializer
class StoreSerializer(serializers.ModelSerializer):
class Meta:
model = Store
fields = '__all__'
and in the viewset,
def list(self, request, *args, **kwargs):
obj = Store.objects.first()
ser = StoreSerializer(data=obj)
if ser.is_valid():
pass
print(ser.data)
return Response(ser.data)
this method is returning just an empty dict {} as the response. When defining the serializer as
ser = StoreSerializer(data=[obj], many=True)
the object is getting serialized. What am I doing wrong here?

When you are passing a models data to a ModelSerializer you don't need to call the function
serializer.is_valid()
So you can just pass the data without the data key as
ser = StoreSerializer(obj)
print(ser.data)

Related

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.

data in serializer is always empty

I have following serializer:
class AdminSerializer(serializers.Serializer):
def validate(self, data):
user = data.get("user_pk")
total_licenses = data.get("total_licenses")
#here i do some validation with the vars
But my data is always empty. This is part of my view
serializer_class = self.get_serializer_class()
serializer = serializer_class(
data=self.request.data,
)
serializer.is_valid(raise_exception=True)
This is my unit test request:
response = self.client.patch(
url,
data={"user_pk": self.user.pk, "total_licenses": 3},
)
Why is my 'data' always empty?
You have to explicitly specify fields in your serializer:
class AdminSerializer(serializers.Serializer):
user_pk = serializers.IntegerField()
total_licenses = serializers.IntegerField()
def validate(self, data):
...
You need to call serializer.save() before you can access .data attribute on a serializer. Otherwise, you can access .validated_data attribute on your serializer

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)

Django Rest Framework object is not iterable?

I have serialised one of my models that has a foreign key in. I get 'Parent' object is not iterable
models.py
class Parent(models.Model):
# Parent data
class Child(models.Model):
parent = ForeignKey(Parent)
serializer.py
class ChildSerializers(serializers.ModelSerializer):
parent = serializers.RelatedField(many=True)
class Meta:
model = ReportField
fields = (
'id',
'parent'
)
api.py
class ChildList(APIView):
def get(self, request, format=None):
child = Child.objects.all()
serialized_child = ChildSerializers(child, many=True)
return Response(serialized_child.data)
Im guessing i have to pass the parent list to the child list but not sure of the best way to do it
attempt api.py
class ChildList(APIView):
def get(self, request, format=None):
child = Child.objects.all()
parent = Parent.objects.all()
serialized_child = ChildSerializers(child, many=True)
serialized_parent = ChildSerializers(parent, many=True)
return Response(serialized_child.data, serialized_parent.data)
Why using many=True. Parent is just a single field, no need to use explicit serializer field. Just get rid of these many=True
-answered by mariodev in comment.
You can do something like this using python collections as an intermediate
#serializers.py
class TimelineSerializer(serializers.Serializer):
childs= childSerializer(many=True)
parents = parentSerializer(many=True)
#apiviews.py
from collections import namedtuple
Timeline = namedtuple('Timeline', ('childs', 'parents'))
def list(self, request):
timeline = Timeline(
childs=Child.objects.all(),
parents=Parent.objects.all(),
)
serializer = TimelineSerializer(timeline)
return Response(serializer.data)
If your using a ModalSerializer then you have two foreign keys in a modal, then use "to_representation" function like this
def to_representation(self, instance):
rep = super().to_representation(instance)
rep['pcatalog_name'] = CatalogSerializer(instance.pcatalog_name).data
rep['pcategory_name'] = CategorySerializer(instance.pcategory_name).data
return rep
Replace ForeignKey by ManyToManyField to clarify the serializer field with many = True

Pass extra arguments to Serializer Class in Django Rest Framework

I want to pass some arguments to DRF Serializer class from Viewset, so for I have tried this:
class OneZeroSerializer(rest_serializer.ModelSerializer):
def __init__(self, *args, **kwargs):
print args # show values that passed
location = rest_serializer.SerializerMethodField('get_alternate_name')
def get_alternate_name(self, obj):
return ''
class Meta:
model = OneZero
fields = ('id', 'location')
Views
class OneZeroViewSet(viewsets.ModelViewSet):
serializer_class = OneZeroSerializer(realpart=1)
#serializer_class = OneZeroSerializer
queryset = OneZero.objects.all()
Basically I want to pass some value based on querystring from views to Serializer class and then these will be allocate to fields.
These fields are not include in Model in fact dynamically created fields.
Same case in this question stackoverflow, but I cannot understand the answer.
Can anyone help me in this case or suggest me better options.
It's very easy with "context" arg for "ModelSerializer" constructor.
For example:
in view:
my_objects = MyModelSerializer(
input_collection,
many=True,
context={'user_id': request.user.id}
).data
in serializers:
class MyModelSerializer(serializers.ModelSerializer):
...
is_my_object = serializers.SerializerMethodField('_is_my_find')
...
def _is_my_find(self, obj):
user_id = self.context.get("user_id")
if user_id:
return user_id in obj.my_objects.values_list("user_id", flat=True)
return False
...
so you can use "self.context" for getting extra params.
Reference
You could in the YourView override get_serializer_context method like that:
class YourView(GenericAPIView):
def get_serializer_context(self):
context = super().get_serializer_context()
context["customer_id"] = self.kwargs['customer_id']
context["query_params"] = self.request.query_params
return context
or like that:
class YourView(GenericAPIView):
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.context["customer_id"] = request.user.id
serializer.context["query_params"] = request.query_params
serializer.is_valid(raise_exception=True)
...
and anywhere in your serializer you can get it. For example in a custom method:
class YourSerializer(ModelSerializer):
def get_alternate_name(self, obj):
customer_id = self.context["customer_id"]
query_params = self.context["query_params"]
...
To fulfill the answer of redcyb - consider using in your view the get_serializer_context method from GenericAPIView, like this:
def get_serializer_context(self):
return {'user': self.request.user.email}
A old code I wrote, that might be helpful- done to filter nested serializer:
class MySerializer(serializers.ModelSerializer):
field3 = serializers.SerializerMethodField('get_filtered_data')
def get_filtered_data(self, obj):
param_value = self.context['request'].QUERY_PARAMS.get('Param_name', None)
if param_value is not None:
try:
data = Other_model.objects.get(pk_field=obj, filter_field=param_value)
except:
return None
serializer = OtherSerializer(data)
return serializer.data
else:
print "Error stuff"
class Meta:
model = Model_name
fields = ('filed1', 'field2', 'field3')
How to override get_serializer_class:
class ViewName(generics.ListAPIView):
def get_serializer_class(self):
param_value = self.context['request'].QUERY_PARAMS.get('Param_name', None)
if param_value is not None:
return Serializer1
else:
return Serializer2
def get_queryset(self):
.....
Hope this helps people looking for this.
List of element if your query is a list of elements:
my_data = DataSerializers(queryset_to_investigate,
many=True, context={'value_to_pass': value_passed}
in case off single data query:
my_data = DataSerializers(queryset_to_investigate,
context={'value_to_pass': value_passed}
Then in the serializers:
class MySerializer(serializers.ModelSerializer):
class Meta:
fields = '__all__'
model = 'Name_of_your_model'
def on_representation(self, value):
serialized_data = super(MySerializer, self).to_representation(value)
value_as_passed = self.context['value_to_pass']
# ..... do all you need ......
return serialized_data
As you can see printing the self inside on_representation you can see: query_set: <object (x)>, context={'value_to_pass': value_passed}
This is a simpler way, and you can do this in any function of serializers having self in the parameter list.
These answers are far to complicated; If you have any sort of authentication then add this property to your serializer and call it to access the user sending the request.
class BaseSerializer(serializers.ModelSerializer):
#property
def sent_from_user(self):
return self.context['request'].user
Getting the context kwargs passed to a serializer like;
...
self.fields['category'] = HouseCategorySerializer(read_only=True, context={"all_fields": False})
...
In your serializer, that is HouseCategorySerializer do this in one of your functions
def get_houses(self, instance):
print(self._context.get('all_fields'))
Using self._context.get('keyword') solved my mess quickly, instead of using self.get_extra_context()

Categories