Dynamic JsonForm with ImageField in Django

343 Views Asked by At

I am creating a dynamic form (using JSON format) in Django, the form needs to save multiple images using Django Storage and save the reference to the file in the JSON field.

This is what I have right now, it works but is really ugly, Django already do this, I can't figure out how to re-use same functionality.

Class SomeModel(models.ModelForm):
    results = JSONField()

class DynamicJsonForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # ... Add dynamic fields to the form
        self.extra = []  # Save the reference for the fields

    class Meta:
        model = SomeModel
        exclude = ("results",)

    def save(self, commit=True):
        results = {}
        for extra in self.extra:
            value = self.cleaned_data.get(extra)
            question = some_query.question
            if "photo" in extra and value:  # value = photo
                filename, ext = value.name.split(".")
                filename = "media/the/path/to/photos/{}_{}.{}".format(filename, uuid4().hex, ext)
                uploaded_file = SimpleUploadedFile(filename, value.read(), value.content_type)
                image = Image.open(uploaded_file)
                if image.mode in ("RGBA", "P"):
                    image = image.convert("RGB")
                image.save(fp=filename)
                results[question][extra] = filename
            else:
                results[question][extra] = value
        self.instance.results = results
        return super().save(commit)

This actually works, it saves the data to the JSONField (results) and saves the image to local file system storage.

How can this be improved to use Django Storage and make it simple? Using Django Model.save() seems a lot easier, but I can't use a normal ImageField() since it needs to be dynamic

1

There are 1 best solutions below

0
On

I mixed FileSystemStorage with forms.ImageField, not a perfect solution at all, but looks better.

fields.py

import os

from django import forms
from django.conf import settings
from django.core.files.storage import FileSystemStorage

class FormFileSystemStorageImageField(forms.ImageField, FileSystemStorage):
    def __init__(self, location=None, *args, **kwargs):
        super().__init__(*args, **kwargs)  # Call ImageField __init__, I wonder how to call second parent's __init__
        self._orig_location = location
        self._location = os.path.join(settings.MEDIA_ROOT, location)

    def storage_path(self, name):
        return os.path.join(self._orig_location, name)

forms.py

from .fields import FormFileSystemStorageImageField


    # ... Same as question code
    def save(self, commit=True):
        results = {}
        for extra in self.extra:
            value = self.cleaned_data.get(extra)
            question = some_query.question
            if "photo" in extra and value:  # value = photo
                image_field = self.fields.get(extra)
                image_field.save(value.name, value)
                results[question][extra] = image_field.storage_path(value.name)
            else:
                results[question][extra] = value
        self.instance.results = results
        return super().save(commit)