I have a model Message
that has a FileField
. My API accepts files in Base64
encoding so they can be sent alongside other data.
To know a filename and an extension, there is one more field attachment_filename
in the serializer that is not a model field. It is used inside Base64Field
.
I want to be able to validate if there are both attachment_filename
, attachment
, or none of them.
The problem is that if the attachment_filename
is read-only, it is not present in validate
- data
variable.
On the other hand, if it's required=False
, allow_null=True
, the serializer raises an error when creating a message:
TypeError: ChatMessage() got an unexpected keyword argument 'attachment_filename'
Code:
class Base64File(Base64FileField): # todo make accept a list of extensions (finite eg. pdf, xlsx, csv, txt )
ALLOWED_TYPES = ['pdf', 'xlsx', 'png', 'jpg', 'jpeg', 'docx', 'doc', 'zip']
def get_file_extension(self, filename, decoded_file):
extension = self.get_full_name().split('.')[-1]
return extension
def get_file_name(self, decoded_file):
attachment_filename = self.get_full_name()
return '.'.join(attachment_filename.split('.')[:-1])
def get_full_name(self):
return self.context['request'].data['attachment_filename'] # todo validate name
class ChatMessageSerializer(serializers.ModelSerializer):
attachment = Base64File(required=False)
attachment_filename = serializers.CharField(required=False, allow_null=True)
class Meta:
model = ChatMessage
fields = '__all__'
def validate(self, data):
"""
Validation of start and end date.
"""
attachment = data.get('attachment')
attachment_filename = data.get('attachment_filename')
if bool(attachment) ^ bool(attachment_filename):
raise serializers.ValidationError("Either none or both 'attachment' and 'attachment_filename' must be present")
# del data['attachment_filename'] # works but dirty
return data
How to make it work?
EDIT
I managed to make it work by adding
del data['attachment_filename']
before validate
method return but that seems to be too "dirty".
You should handle this behavior in serializer.save
method, for example, you can pop it from validated_data
like that:
def save(self, **kwargs):
self.validated_data.pop("attachment_filename")
return super().save(**kwargs)