How to get selected attributes as a serialized representation in DRF? - python

This is my serializer:
class MySerializers(serializers.ModelSerializer):
class Meta:
model = Student
fields = ['name','roll', 'city']
When I serialize data using this statement:
serializer = serializers.MySerializer(data, many=True)
return Response(serializer.data)
It will return a list of serialized data as a response, which will include the fields name, roll and city. But if I want to print only name and roll number as a serialized representation,not all attributes, how to do that? I cannot exclude any field as all fields are required in Create API.

Related

Django Rest Framework, creating a model, (serializer and view) with oneToMany field to users

I am trying to create a rather simple model, all the model holds is a week number (as the primary key), and a oneToMany field with a list of users. The idea is that it should function like a schema, where you can see which users is attached to a specific week number. My problem is currently getting the serializer to work with the oneToMany field.
Model:
class Schema(models.Model):
week = models.PositiveIntegerField(primary_key=True,
unique=True,
validators=[MinValueValidator(1), MaxValueValidator(53)],
)
users = models.ForeignKey(MyUser, null=True, on_delete=models.SET_NULL)
class Meta:
ordering = ('week',)
Serializer:
class SchemaSerializer(serializers.Serializer):
class Meta:
model = Schema
fields = ('week', 'users')
def create(self, validated_data):
answer, created = Schema.objects.update_or_create(
week=validated_data.get('week', 1),
defaults={'users', validated_data.get('users', None)}
)
return answer
View:
class SchemaView(APIView):
permission_classes = (IsAuthenticated, IsAdminUser)
def get(self, request):
schemas = Schema.objects.all()
serializer = SchemaSerializer(schemas)
return Response(serializer.data)
def post(self, request):
data = request.data
serializer = SchemaSerializer(data=data)
serializer.is_valid()
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
I get the following error TypeError: cannot convert dictionary update sequence element #0 to a sequence. As I interpret that error, something is wrong with the first element (week number) when trying to do serializer.save().

Django rest framework nested serializer create method

I have created a nested serializer, when I try to post data in it it keeps on displaying either the foreign key value cannot be null or dictionary expected. I have gone through various similar questions and tried the responses but it is not working for me. Here are the models
##CLasses
class Classes(models.Model):
class_name = models.CharField(max_length=255)
class_code = models.CharField(max_length=255)
created_date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.class_name
class Meta:
ordering = ['class_code']
##Streams
class Stream(models.Model):
stream_name = models.CharField(max_length=255)
classes = models.ForeignKey(Classes,related_name="classes",on_delete=models.CASCADE)
created_date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.stream_name
class Meta:
ordering = ['stream_name']
Here is the view
class StreamViewset(viewsets.ModelViewSet):
queryset = Stream.objects.all()
serializer_class = StreamSerializer
Here is the serializer class
class StreamSerializer(serializers.ModelSerializer):
# classesDetails = serializers.SerializerMethodField()
classes = ClassSerializer()
class Meta:
model = Stream
fields = '__all__'
def create(self,validated_data):
classes = Classes.objects.get(id=validated_data["classes"])
return Stream.objects.create(**validated_data, classes=classes)
# def perfom_create(self,serializer):
# serializer.save(classes=self.request.classes)
#depth = 1
# def get_classesDetails(self, obj):
# clas = Classes.objects.get(id=obj.classes)
# classesDetails = ClassSerializer(clas).data
# return classesDetails
I have tried several ways of enabling the create method but like this displays an error {"classes":{"non_field_errors":["Invalid data. Expected a dictionary, but got int."]}}. Any contribution would be deeply appreciated
This is a very common situation when developing APIs with DRF.
The problem
Before DRF reaches the create() method, it validates the input, which I assume has a form similar to
{
"classes": 3,
"stream_name": "example"
}
This means that, since it was specified that
classes = ClassSerializer()
DRF is trying to build the classes dictionary from the integer. Of course, this will fail, and you can see that from the error dictionary
{"classes":{"non_field_errors":["Invalid data. Expected a dictionary, but got int."]}}
Solution 1 (requires a new writable field {field_name}_id)
A possible solution is to set read_only=True in your ClassSerializer, and use an alternative name for the field when writing, it's common to use {field_name}_id. That way, the validation won't be done. See this answer for more details.
class StreamSerializer(serializers.ModelSerializer):
classes = ClassSerializer(read_only=True)
class Meta:
model = Stream
fields = (
'pk',
'stream_name',
'classes',
'created_date',
'classes_id',
)
extra_kwargs = {
'classes_id': {'source': 'classes', 'write_only': True},
}
This is a clean solution but requires changing the user API. In case that's not an option, proceed to the next solution.
Solution 2 (requires overriding to_internal_value)
Here we override the to_internal_value method. This is where the nested ClassSerializer is throwing the error. To avoid this, we set that field to read_only and manage the validation and parsing in the method.
Note that since we're not declaring a classes field in the writable representation, the default action of super().to_internal_value is to ignore the value from the dictionary.
from rest_framework.exceptions import ValidationError
class StreamSerializer(serializers.ModelSerializer):
classes = ClassSerializer(read_only=True)
def to_internal_value(self, data):
classes_pk = data.get('classes')
internal_data = super().to_internal_value(data)
try:
classes = Classes.objects.get(pk=classes_pk)
except Classes.DoesNotExist:
raise ValidationError(
{'classes': ['Invalid classes primary key']},
code='invalid',
)
internal_data['classes'] = classes
return internal_data
class Meta:
model = Stream
fields = (
'pk',
'stream_name',
'classes',
'created_date',
)
With this solution you can use the same field name for both reading and writing, but the code is a bit messy.
Additional notes
You're using the related_name argument incorrectly, see this question. It's the other way around,
classes = models.ForeignKey(
Classes,
related_name='streams',
on_delete=models.CASCADE,
)
In this case it should be streams.
Kevin Languasco describes the behaviour of the create method quite well and his solutions are valid ones. I would add a variation to solution 1:
class StreamSerializer(serializers.ModelSerializer):
classes = ClassSerializer(read_only=True)
classes_id = serializers.IntegerField(write_only=True)
def create(self,validated_data):
return Stream.objects.create(**validated_data, classes=classes)
class Meta:
model = Stream
fields = (
'pk',
'stream_name',
'classes',
'classes_id',
'created_date',
)
The serializer will work without overriding the create method, but you can still do so if you want to as in your example.
Pass the value classes_id in the body of your POST method, not classes. When deserializing the data, the validation will skip classes and will check classes_id instead.
When serializing the data (when you perform a GET request, for example), classes will be used with your nested dictionary and classes_id will be omitted.
You can also solve this issue in such a way,
Serializer class
# Classes serializer
class ClassesSerializer(ModelSerializer):
class Meta:
model = Classes
fields = '__all__'
# Stream serializer
class StreamSerializer(ModelSerializer):
classes = ClassesSerializer(read_only=True)
class Meta:
model = Stream
fields = '__all__'
View
# Create Stream view
#api_view(['POST'])
def create_stream(request):
classes_id = request.data['classes'] # or however you are sending the id
serializer = StreamSerializer(data=request.data)
if serializer.is_valid():
classes_instance = get_object_or_404(Classes, id=classes_id)
serializer.save(classes=classes_instance)
else:
return Response(serializer.errors)
return Response(serializer.data)

How to get all instances in serializer method field

How to get all instances in serializer method field
I have a serializer method field and I am passing list data in the form of context to serializer like below.
name_list = [ "abc", "def",....]
obj_list = abc.objects.all()
Serializer = abcSerializer (obj_list, context=name_list, many=True)
class abcSerializer (serializers.ModelSerializer):
xyz = serializers.SerializerMethodField ("getXYZ", read_only=True)
class Meta:
model = abc
def getXYZ (self, data):
# here I want all instanceses, but I got only one instance in data.
I want to attach name_list data one by one to instace data with same index?
How I can get all instanceses in my serializer method field?
Why do you need all instances? If you want to manipulate something in all instances, better do it before passing it as argument in Serializer. If you want to get indivisual instance, you should get the value in data parameter. But your indentations are wrong. Try like this:
class abcSerializer (serializers.ModelSerializer):
xyz = serializers.SerializerMethodField("getXYZ")
class Meta:
model = abc
def getXYZ(self, data):
print(data) # it will print a instance of abc
return value_based_on_data
Update
Then I think you should try like this:
First update serializer class:
class abcSerializer (serializers.ModelSerializer): # use PascalCase for naming classes
xyz = serializers.ReadOnlyField()
class Meta:
model = abc
fields = '__all__' # use PascalCase for naming classes
Then use the following code to get values of xyz:
obj_list = []
for i, item in enumerate(abc.objects.all()):
item.xyz = name_list[i]
obj_list.append(item)
abcSerializer(obj_list, many=True).data
After dig deep into debugging I realized that I should share my findings with the community.
Look at the below line:
Serializer = abcSerializer (obj_list, context=name_list, many=True)
Here many=True make's abcSerializer to list serializer, according to rest framework documentation in list serializer we can access all objects of queryset in update method like below
class BookListSerializer(serializers.ListSerializer):
def update(self, instance, validated_data):
# Maps for id->instance and id->data item.
book_mapping = {book.id: book for book in instance}
data_mapping = {item['id']: item for item in validated_data}
# Perform creations and updates.
I found that we can also access all objects of queryset in any method even in serializerMethodField using below syntax
def getXYZ (self, data):
objects = self.instance

How to make a field editable=False in DRF

I've a serializer. I want to restrict updating a field. How would I do that?
class ABCSerializer(serializers.ModelSerializer):
class Meta:
"""Meta."""
model = ModelA
fields = ('colA', 'colB', 'colC',)
colA is a required field while creating the object. However, it should not be allowed to update. How can I do that??
Sounds like you need different serializers for PUT and POST methods. In the serializer for the PUT method you can set the colA field to readonly
class ABCViewSet(ModelViewSet):
serializer_class = ABCSerializer
def get_serializer_class(self):
serializer_class = self.serializer_class
if self.request.method == 'PUT':
serializer_class = SerializerWithReadOnlyColA
return serializer_class
You can use Django REST Frameworks field-level validation by validating that field has not changed on update like so:
from rest_framework.exceptions import ValidationError
class ABCSerializer(serializers.ModelSerializer):
colA = serializers.CharField(max_length=100)
def validate_colA(self, value):
if self.instance and self.instance.colA != value:
raise ValidationError("You may not edit colA")
return value
class Meta:
"""Meta."""
model = ModelA
fields = ('colA', 'colB', 'colC',)
This will check whether or not this is an update (via checking if an instance is populated on the serializer) and if so it will then check to see if you have made a change to the field and if you have it will throw a ValidationError. The benefit of this approach is that you can keep your view code the same as before and continue to keep your validation behaviour in your serializer.
You can override the serializer's update method to only update fields that you want.
class ABCSerializer(serializers.ModelSerializer):
def update(self, instance, validated_data):
instance.colB = validated_data.get('colB', instance.colB)
instance.colC = validated_data.get('colC', instance.colC)
# do nothing to instance.colA
instance.save()
return instance
class Meta:
model = ModelA
fields = ('colA', 'colB', 'colC',)
Or if you have many fields, and just want to omit updating colA, you could write your update method like this:
def update(self, instance, validated_data):
validated_data.pop('colA') # validated_data no longer has colA
return super().update(instance, validated_data)
You can read more about overriding update here: https://www.django-rest-framework.org/api-guide/serializers/#saving-instances
I think it's too late to answer but this may be useful for others:)
you can solve your problem this way:
class ABCSerializer(serializers.ModelSerializer):
class Meta:
model = ModelA
fields = ('colA', 'colB', 'colC',)
def get_fields(self):
fields = super().get_fields()
if self.instance:
fields["colA"].read_only = True
return fields
When you want to create, the self.instance is None, it will pass the if clause, and in case of updating the if clause will make the field read only and non-editable.
You can do this with the read_only_fieldsoption
class ABCSerializer(serializers.ModelSerializer):
class Meta:
"""Meta."""
model = ModelA
fields = ('colB', 'colC',)
read_only_fields = ('colA',)

DRF: TypeError: <Model: Model object> is not JSON serializable

I have problem getting an object data from the request using django-rest-framework ModelViewset create(self, request) method.
Here is my model.py
class Heat(models.Model):
# Relationship Fields
animal = models.ForeignKey(
Animal, related_name='heats',
on_delete=models.CASCADE
)
# Fields
performer = models.CharField(max_length=25)
is_bred = models.BooleanField(default=False)
note = models.TextField(max_length=250, blank=True, null=True)
And this is my serializers.py
class HeatSerializer(serializers.ModelSerializer):
class Meta:
model = Heat
fields = [
'id',
'animal',
'performer',
'is_bred',
'note',
]
And here is where we can find the error in my ModelViewset:
class HeatViewSet(viewsets.ModelViewSet):
queryset = Heat.objects.all()
serializer_class = HeatSerializer
#transaction.atomic
def create(self, request):
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
print serializer.validated_data['animal']
If you look at this line of code:
// this purpose is to print the actual value of an animal object
// but it gave me the TypeError: <Animal: Animal object> is not JSON serializable.
print serializer.validated_data['animal']
If we include this one it works fine because this it just a normal field the same true to the other field except the animal which is a relational field:
print serializer.validated_data['performer']
Now when i do this the object was saved directly to the database.
serializer.save()
But django would prompt me the SAME TYPE ERROR!
When removing the entire create(self, request) the method flow works well without an error but i would like to perform many queries inside the create(self, request)!. This won't do right either please help!
When you serialize on Queryset the JSON result will be into a .data object
animal = AnimalSerializer(your_model.animal_set(), many=True, read_only=True)
return Response(animal.data)

Categories

Resources