[Answered ]-Django serializer test post of file with user information

1👍

This is how I deal with this issue:

Simplest: flatten the form

Suck it up and just remove the issue by making your serializer to use user_id and user_username and fix it up on the server side in the serializer’s validate(self, attrs) method. A bit ugly/hacky but it works just fine and can be documented.

def validate(self, attrs):
    attrs["user"] = {
        "id": attrs.pop("user_id"), 
        "name": attrs.pop("user_username")
    }
    return attrs

Nicest if you dont mind the size: B64 Fields

You can base64 encode the file field and pass it in the json. Then to decode it on the server side you would write (or search for) a simple Base64FileField() for DRF.

class UploadedBase64ImageSerializer(serializers.Serializer):
    file = Base64ImageField(required=False)
    created = serializers.DateTimeField()

Alternative – Flatten the form data

You can’t pass nested data, but you can flatten the nested dicts and pass that to a DRF service. Serializers actually can understand nested data if the field names are correct.

I don’t know if this field name format is standardized, but this is what worked for me after experimentation. I only use it for service->service communication TO drf, so you would have to clone it into JS, but you can use the python in unit tests. Let me know if it works for you.

def flatten_dict_for_formdata(input_dict, array_separator="[{i}]"):
    """
    Recursively flattens nested dict()s into a single level suitable
    for passing to a library that makes multipart/form-data posts.
    """

    def __flatten(value, prefix, result_dict, previous=None):
        if isinstance(value, dict):
            # If we just processed a dict, then separate with a "."
            # Don't do this if it is an object inside an array.  
            # In that case the [:id] _is_ the separator, adding 
            # a "." like list[1].name will break but list[x]name 
            # is correct (at least for DRF/django decoding)
            if previous == "dict":
                prefix += "."

            for key, v in value.items():
                __flatten(
                    value=v,
                    prefix=prefix + key,
                    result_dict=result_dict,
                    previous="dict"
                )
        elif isinstance(value, list) or isinstance(value, tuple):
            for i, v in enumerate(value):
                __flatten(
                    value=v,
                    prefix=prefix + array_separator.format(i=i),  # e.g. name[1]
                    result_dict=result_dict,
                    previous="array"
                )
        else:
            result_dict[prefix] = value

        # return her to simplify the caller's life.  ignored during recursion
        return result_dict

    return __flatten(input_dict, '', OrderedDict(), None)
# flatten_dict_for_formdata({...}):
{                                   # output field name
    "file": SimpleUploadFile(...),  # file
    "user": {                       
        "id": 1,                    # user.id
        "name": "foghorn",          # user.name        
        "jobs": [                           
            "driver",               # user.jobs[0]
            "captain",              # user.jobs[1]
            "pilot"                 # user.jobs[1]
        ]
    },
    "objects": [
        {
            "type": "shoe",         # objects[0]type
            "size": "44"            # objects[0]size
        },
    ]
}
👤Andrew

Leave a comment