So I am trying to upload file AND Post JSON data to my API following this solution. I have created the Parser, placed it in my viewset. I even receive the image and the JSON data in the to_internal_value
function, which i think runs AFTER the parser parses the data.
How ever, as soon as the to_internal_value
function is run, the location
field is set to empty.
I overrided the function to see whats happening, and it seems the field.get_value
function is returning the empty value.
Here's the parser:
class MultipartJsonParser(parsers.MultiPartParser):
def parse(self, stream, media_type=None, parser_context=None):
result = super().parse(
stream,
media_type=media_type,
parser_context=parser_context
)
data = {}
# for case1 with nested serializers
# parse each field with json
for key, value in result.data.items():
if type(value) != str:
data[key] = value
continue
if '{' in value or "[" in value:
try:
data[key] = json.loads(value)
except ValueError:
data[key] = value
else:
data[key] = value
qdict = QueryDict('', mutable=True)
print(data)
qdict.update(data)
return parsers.DataAndFiles(qdict, result.files)
Here's the Serializer:
class AssetSerializer(serializers.ModelSerializer):
"""
Serializes data recieved/retrieved to/from endpoints concerning Assets.
"""
asset_location = LocationSerializer(required=False,allow_null=True)
asset_devices = serializers.PrimaryKeyRelatedField(many=True,queryset=Device.objects.all(),required=False)
parent = serializers.PrimaryKeyRelatedField(queryset=Asset.objects.all(),required=False)
busy_ranges = serializers.CharField(required=False)
time = serializers.ReadOnlyField(
source='datetime')
status = serializers.CharField(source="current_status",required=False)
class Meta:
model = Asset
fields = ['id','asset_devices','name','slug','buying_date','asset_location','weight','icon','enabled','parent','is_parent','time','status','busy_ranges']
read_only_fields = ('id','slug','is_parent','busy_ranges')
extra_kwargs = {
'status': {
'help_text': 'Status of the asset. Available options are "AV" for available, "LE" for Lent, "MT" for In Maintenance'
}
}
#exclude_when_nested = {'device'} # not an official DRF meta attribute ...
def create(self,validated_data):
location_data = validated_data.pop('asset_location')
location = Location.objects.create(**location_data)
validated_data['asset_location']=location
owner = self.context['request'].user
asset = Asset.objects.create(**validated_data,owner=owner)
return asset
def to_internal_value(self, data):
"""
Dict of native values <- Dict of primitive datatypes.
"""
if not isinstance(data, Mapping):
message = self.error_messages['invalid'].format(
datatype=type(data).__name__
)
raise ValidationError({
api_settings.NON_FIELD_ERRORS_KEY: [message]
}, code='invalid')
ret = OrderedDict()
errors = OrderedDict()
fields = self._writable_fields
#The line below print(data) prints the following
#<QueryDict: {'name': ['test asset'], 'weight': ['20'], 'asset_location': [{'x': '10', 'y': '30'}], 'icon': [<InMemoryUploadedFile: issues_mzali.png (image/png)>]}>
print(data)
for field in fields:
validate_method = getattr(self, 'validate_' + field.field_name, None)
primitive_value = field.get_value(data)
print(primitive_value) #this line prints 'asset_location' field as <class 'rest_framework.fields.empty'> which gives KeyError in the create method
#All other fields are printed fine.
try:
validated_value = field.run_validation(primitive_value)
if validate_method is not None:
validated_value = validate_method(validated_value)
except ValidationError as exc:
errors[field.field_name] = exc.detail
except DjangoValidationError as exc:
errors[field.field_name] = get_error_detail(exc)
except SkipField:
pass
else:
set_value(ret, field.source_attrs, validated_value)
if errors:
raise ValidationError(errors)
return ret
def update(self,instance,validated_data):
try:
location_data = validated_data['asset_location']
if instance.asset_location is None:
location = Location.objects.create(**location_data)
instance.asset_location = location
instance.save()
else:
instance_location = instance.asset_location
for key,value in location_data.items():
setattr(instance_location,key,value)
instance_location.save()
instance.save()
return instance
except KeyError as e:
print(e.message)
return super(AssetSerializer,self).update(instance,validated_data)
Please check the comments in the to_internal_value
function, you will get a better idea of the problem.