How to pass flag to child serializer in DRF - django

I have a nested serializer and I want to pass Parent serializer data to the child. But I don't understand how can I do this. I want to do something like this:
class BookingSerializer(serializers.ModelSerializer):
use_additional_fields = serializers.BooleanField()
persons = PersonSerializer(many=True)
class PersonSerializer(serializers.ModelSerializer):
def validate_date_of_birth(self, value):
if parent.use_additional_fields and not value:
raise serializers.ValidationError(_('Date of birth is required'))
return value
class Meta:
model = Person
exclude = ('phone', 'date_of_birth')
So if user select use_additional_fields in parent serializer, then some of my fields in child serializers should be required

You can get data from request object directly:
class PersonSerializer(serializers.ModelSerializer):
def validate_date_of_birth(self, value):
if self.context['request'].data.get('use_additional_fields') and not value:
raise serializers.ValidationError(_('Date of birth is required'))
return value
class Meta:
model = Person
exclude = ('phone', 'date_of_birth')
Note if you initiate serializer instance manually in your view, you should pass request to the serializer's context:
serializer = BookingSerializer(data=data, context={'request': request})

Related

Add fields in Serializer dynamically

I have a View in which I receive the request and it returns the serialized data
views.py
class AllotmentReportsView(APIView):
permission_classes = (permissions.IsAuthenticated,)
def get(self, request):
sfields = request.GET['sfields'] #I can get the fields in params
serializer = AllotReportSerializer(items, many=True)
return Response(serializer.data, status=status.HTTP_201_CREATED)
serializer.py
class AllotReportSerializer(serializers.ModelSerializer):
send_from_warehouse = serializers.SlugRelatedField(read_only=True, slug_field='name')
transport_by = serializers.SlugRelatedField(read_only=True, slug_field='name')
sales_order = AllotSOSerializer(many=False)
flows = AllotFlowsSerializer(many=True)
class Meta:
model = Allotment
fields = ( 'transaction_no', 'dispatch_date', 'sales_order',
'is_delivered', 'send_from_warehouse', 'transport_by',
'flows', )
Instead of defining the fields in serializer can I pass the fields dynamically from the view sfields and pass them to the serializer ?
It is not necessary to describe fields in ModelSerializer class. Django will generate it automatically according model information:
class AllotReportSerializer(serializers.ModelSerializer):
class Meta:
model = Allotment
fields = ( 'transaction_no', 'dispatch_date', 'sales_order',
'is_delivered', 'send_from_warehouse', 'transport_by',
'flows', )
is enough
If you want to add fields that not exists in model, I guess it is possible with meta classes and setattr() function. But that is looking meaningless. Also you need to add logic how to dynamically set field type and parameters.
You do not need to describe the fields in the ModelSerializer -
class AllotReportSerializer(serializers.ModelSerializer):
class Meta:
model = Allotment
fields = ( 'transaction_no', 'dispatch_date', 'sales_order',
'is_delivered', 'send_from_warehouse', 'transport_by',
'flows', )
extra_kwargs = {
"url": {
"lookup_field": "slug",
"view_name": "api-your-model-name-detail",
}
you can define extra kwargs if you want in the above way.
This is enough.
If you want to add all the fields in your model, you can do this -
class Meta:
model = Your model name
fields = "__all__"
You can read more about it here - https://www.django-rest-framework.org/api-guide/serializers/

How to pass request to a Field Serializer in Rest Framework?

Hi I am using a Field Serializer to be able to serialize a PK field then deserialize it as object. Inside the serializer is a SerializerMethodField to build a custom url. It works when I use the itself from serializing its own record. However when I use it to a different serializer as a FieldSerializer, the request object is not passed.
class TelemetryFileSerializer(serializers.ModelSerializer):
telemetry_type = serializers.SlugRelatedField(
slug_field='name', queryset=TelemetryFileType.objects.all())
receiving_station = serializers.SlugRelatedField(
required=False, slug_field='name', queryset=ReceivingStation.objects.all())
link = serializers.SerializerMethodField()
class Meta:
model = TelemetryFile
fields = '__all__'
def get_link(self, object):
request = self.context.get('request')
print(self.context) # request is not passed here from RawImageSerializer/TelemetryFileField
return request.build_absolute_uri('/data_management/telemetry_files/{}'.format(object.id))
class TelemetryFileField(serializers.PrimaryKeyRelatedField):
def to_representation(self, value):
pk = super(TelemetryFileField, self).to_representation(value)
item = TelemetryFile.objects.get(pk=pk)
serializer = TelemetryFileSerializer(item)
return serializer.data
class RawImageSerializer(serializers.ModelSerializer):
from_telemetry_file = TelemetryFileField(queryset=TelemetryFile.objects.all())
link = serializers.SerializerMethodField()
I want to pass a request of itself to be able to create a url of it.
This is the returned when I use the RawImageSerializer:
AttributeError: 'NoneType' object has no attribute
'build_absolute_uri'
There must be a way to pass request from serializer to another...
I am not sure if this is the correct solution but adding this solved my problem...
class TelemetryFileField(serializers.PrimaryKeyRelatedField):
def to_representation(self, value):
print("value", self.context)
pk = super(TelemetryFileField, self).to_representation(value)
item = TelemetryFile.objects.get(pk=pk)
serializer = TelemetryFileSerializer(item, context=self.context) # context was added
return serializer.data

Validation is never fired

I have two models:
class Manufacturer(models.Model):
name = models.CharField(max_length = 100)
class Car(models.Model):
manufacturer = models.ForeignKey(Manufacturer, blank = True, null = True)
My serialisers:
class ManufacturerSerializer(serializers.ModelSerializer):
class Meta:
model = Manufacturer
fields = ('id', 'name')
class CarSerializer(serializers.ModelSerializer):
manufacturer = Manufact
class Meta:
model = Car
fields = ('id', 'name', 'manufacturer')
def validate(self, attrs):
try:
attrs['manufacturer'] = Manufacturer.objects.get(pk = attrs['manufacturer'])
except Manufacturer.DoesNotExist:
raise ValidationError(_('Not found'))
return attrs
My views:
class CarList(generics.ListCreateAPIView):
queryset = Car.objects.all()
serializer_class = CarSerializer
When I try to add a new Car calling POST /cars/ I get a validation error manufacturer is a required field. Car model expects manufacturer field to be Manufacturer object but to make messages small I pass it the manufacturer_id instead.
I know that should raise a ValidationError so to fix this I added a validate(..) to my CarSerializer so during validation I check if Manufacturer by this ID exists and I assign it to attrs.
Problem is this validate(..) is never called I even tried adding a post() method to CarList view and manually calling is_valid() to no success.
But I still get validation errors which I assume are coming from the Model.
It doesn't work because you are complicating things. The ManufacturerSerializer is not necessary (for this view anyway). By default the 'manufacturer' field will be represented as a PrimaryKeyRelatedField which will resolve your manufacturer_id to a Manufacturer object automatically, so you don't need your validate method either.
Updated serializers:
class CarPostSerializer(serializers.ModelSerializer):
class Meta:
model = Car
fields = ('id', 'name', 'manufacturer')
class CarGetSerializer(CarPostSerializer):
manufacturer = ManufacturerSerializer()
Updated view:
class CarList(generics.ListCreateAPIView):
queryset = Car.objects.all()
def get_serializer_class(self):
if self.request.method == 'POST':
return CarPostSerializer
else:
return CarGetSerializer

DRF: Remove field on model serializer after validation but before creation (on CreateAPIView)

I have a contact form on a site that is posting to a CreateAPIView to create a new instance of a model (that is eventually emailed to the admin). On my serializer I have a honeypot field to help reject spam.
The model:
class Message(models.Model):
name = ...
message = ...
and serializer:
class MessageSerializer(serializers.ModelSerializer):
# Honeypot field
url = serializers.CharField(allow_blank=True, required=False)
class Meta:
model = Message
fields = '__all__'
def validate_url(self, value):
if value and len(value) > 0:
raise serializers.ValidationError('Spam')
return value
and view:
class MessageView(generics.CreateAPIView):
''' Create a new contact form message. '''
serializer_class = MessageSerializer
My problem is that as it stands, when I post to this view, I get the error:
TypeError: Got a TypeError when calling Message.objects.create(). This may be because you have a writable field on the serializer class that is not a valid argument to Message.objects.create(). You may need to make the field read-only, or override the MessageSerializer.create() method to handle this correctly.
so obviously the seriazlier is attempting to save the url field to the model in CreateApiView.perform_create()
I tried adding read_only to the serializer field, but this means that the url_validate method is skipped altogether.
How can I keep the field on the serializer until validation has occurred, removing it before the serializer.save() is called in perform_create()?
you can do this overriding the create method like:
class MessageSerializer(serializers.ModelSerializer):
# Honeypot field
url = serializers.CharField(allow_blank=True, required=False)
class Meta:
model = Message
fields = '__all__'
def validate_url(self, value):
if value and len(value) > 0:
raise serializers.ValidationError('Spam')
return value
def create(self, validated_data):
data = validated_data.pop('url')
return Message.objects.create(**data)
OK, I didn't read the error correctly. As it clearly says:
override the MessageSerializer.create() method to handle this correctly.
I was looking at overwriting the CreateAPIView.create() method which didn't make sense.
This works:
class MessageSerializer(serializers.ModelSerializer):
# Honeypot field
url = serializers.CharField(allow_blank=True, required=False)
class Meta:
model = Message
fields = '__all__'
def validate_url(self, value):
if value and len(value) > 0:
raise serializers.ValidationError('Error')
return value
def create(self, validated_data):
if "url" in validated_data:
del validated_data["url"]
return Message.objects.create(**validated_data)

Django Rest Framework optional nested serializer field in ModelSerializer

I have two models: FacebookAccount and Person. There's a OneToOne relationship like so:
class FacebookAccount(models.Model):
person = models.OneToOneField(Person, related_name='facebook')
name = models.CharField(max_length=50, unique=True)
page_id = models.CharField(max_length=100, blank=True)
I created a PersonSerializer which has a facebook field set to a FacebookSerializer I created:
class FacebookSerializer(serializers.ModelSerializer):
class Meta:
model = FacebookAccount
fields = ('name', 'page_id',)
class PersonSerializer(serializers.ModelSerializer):
facebook = FacebookSerializer(required=False)
class Meta:
model = Person
fields = ('id', 'name', 'facebook',)
I then created a view to create a new Person along with a new FacebookAccount instance for a POST request:
class PersonCreate(APIView):
def post(self, request):
# Checking for something here, doesn't affect anything
if 'token' in request.DATA:
serializer = PersonSerializer(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)
This works fine and well when my POST data has a facebook object with a unique name attribute in it. However, what if the facebook information is optional? I set "required=False" in the facebook field on the PersonSerializer to account for this, but I get a 400 error when my POST data does not contain a facebook object. The error says that the "name" field for facebook is required.
If I set "blank=True" on the name field in the FacebookAccount model and don't provide and facebook info in my POST data, a blank FacebookAccount record is created which is expected but not desired.
Does anyone know how to fix this? Thanks.
edit:
I tried overriding the save_object method on the PersonSerializer, but it looks like that method isn't called. I tried working my way back through the methods that are called, and it looks like get_field is the last one:
class PersonSerializer(serializers.ModelSerializer):
facebook = FacebookSerializer(required=False)
def get_default_fields(self):
field = super(PersonSerializer, self).get_default_fields()
print field
return field
def get_pk_field(self, model_field):
print 'MF'
mf = super(PersonSerializer, self).get_pk_field(model_field)
print mf
return mf
def get_field(self, model_field):
print 'FIELD'
mf = super(PersonSerializer, self).get_field(model_field)
print mf
return mf
# not called
def get_validation_exclusions(self, instance=None):
print '**Exclusions'
exclusions = super(PersonSerializer, self).get_validation_exclusions(instance=None)
print exclusions
return exclusions
# not called
def save_object(self, obj, **kwargs):
if isinstance(obj, FacebookAccount):
print 'HELLO'
else:
print '*NONONO'
return super(PersonSerializer, self).save_object(obj, **kwargs)
class Meta:
model = Person
fields = ('id', 'name', 'stripped_name', 'source', 'facebook',)
Simply pass allow_null=True when declaring the nested serializer in the parent serializer, like this:
class FacebookSerializer(serializers.ModelSerializer):
class Meta:
model = FacebookAccount
fields = ('name', 'page_id',)
class PersonSerializer(serializers.ModelSerializer):
facebook = FacebookSerializer(allow_null=True, required=False)
class Meta:
model = Person
fields = ('id', 'name', 'facebook',)
Well, that is the limitation of django-restframework when specifying reverse relation as a field, because it creates a list from the fields specified , required=false only directs the serializer to save the default null value for that related field, to resolve it you can override save_object method of serializer and remove facebook field if its null.
Sorry for late reply, i have somewhat similar models and it is working, by overriding save_object method, the is_valid() must be returning false (have you checked it), anyway saving objects using a serializer is for very basic use cases thus limited, best way would be to save those objects explicitly in the view.