Guardar archivo bs64 en Django FileField

Al crear APIs para APPs, muchas veces los clientes móviles (o web) nos envían los archivos en formato base 64 como un string, y nosotros hemos de descodificarlo y almacenarlo como un archivo en Django. (Por ejemplo, para su posterior subida a Amazon S3).

Lo primero que necesitamos es importar lo siguiente:

from base64 import b64decode
from django.core.files.base import ContentFile

Con esto, tenemos capacidad para leer el string bs64, y para obtener el contenido del fichero (Una vez decodificado).

Para decodificar el string, dando por hecho que le string es una variable llamada "b64_text":

image_data = b64decode(b64_text)

Ahora, obtenemos el contenido del archivo decodificado y lo asociamos al FileField, y guardamos el objeto:

my_model_instance.cool_image_field = ContentFile(image_data, 'whatup.png')
my_model_instance.save()

Ahora un ejemplo práctico. Por ejemplo para Django Rest Framework.
Imaginad que tenemos un modelo llamado "Photography", que tiene únicamente un campo llamado "description" y otro llamado "attachedFile".

class Photography(models.Model):

    attachedFile = models.FileField(
        verbose_name="Photography File",
        upload_to='photography_attached',
        null=True, 
        blank=True
    )

    description = models.CharField(
        verbose_name="Photography Description",
        max_length=300,
        null=True,
        blank=True
    )

Y nosotros queremos hacer un POST de esa entidad, y almacenar el archivo que venga en un FileField (independientemente de que no sea una imágen). Lo se, en este ejemplo, siendo "Photography", deberían ser solo imágenes, pero bueno, echadle imaginación... ;-)

class DamageReportView(APIView):
    def post(self, request, format=None):        
        received = request.data        

        newPhotography = Photography()
        newPhotography.description = received['description']
        img_received = received['attachedFile']
        img_received_ext = img_received.split(';')
        img_received_ext = img_received_ext[0].split(':')
        img_received_ext = img_received_ext[1].split('/')
        img_received_ext = img_received_ext[1]
        clear_image_data = img_received.replace('data:image/jpeg;base64,','')
        image_data = b64decode(clear_image_data)
        newPhotography.attachedFile = ContentFile(image_data, uuid.uuid4()) + '.' + img_received_ext)
        newPhotography.save()

Con esto, tendremos almacenada la imagen o archivo que pasemos. Puesto que recibimos un string, es muy facil parsearlo, por ejemplo para leer la extensión que recibimos. :D

Muchas veces, las apps, envían únicamente el contenido del fichero, sin el indicador de formato (por lo que este ejemplo n osirve para esos casos ,que obviarán la lectura de la extensión y siempre vendrá pre-asignada por el lado cliente.

Y esto es todo, es sencillo pero útil. Probablemente no sea la mejor solución, pero soy todo oídos si tenéis una mejor :D

PS: La info la obtuve en http://stackoverflow.com/questions/15115730/saving-a-decoded-temporary-image-to-django-imagefield , gracias al usuario dshap que a su vez se inspiró de la respuesta: http://stackoverflow.com/questions/3330677/a-stringio-like-class-that-extends-django-core-files-file/3332232#3332232 ;-)

Saludos!