How to post multiple images in a directory through the django rest framework in one click

Question:

I am trying to use the django rest framework to carry out multiple POST events once I point it to a directory. The idea is that I will specify the country and a directory containing multiple images. Then the API must go to the directory, and post each image in that directory to the database along with more details available from the filename of each image. Following some pointers from the internet, I managed to build out a little model and framework that looks like it could work except for one thing. The default rest API page doesn’t support the HTML input of lists.

I would like to know if my time will be better spent trying to fit the API page to my requirements or if I should go about building my own HTML interface.

Or to be more direct

What is the fastest (and simplest) way for me to upload multiple images (each image is a separate instance in the db) into a database using Django and the Django REST Framework?

models.py:

from django.db import models


# Create your models here.
class Tiles(models.Model):
    image = models.ImageField(blank=True, null=True)
    lat = models.FloatField(blank=True, null=True)
    lng = models.FloatField(blank=True, null=True)
    country = models.CharField(blank=True, null=True, max_length=10)
    solar = models.FloatField(blank=True, null=True)
    wind = models.FloatField(blank=True, null=True)
    HV = models.FloatField(blank=True, null=True)

    class Meta:
        verbose_name_plural = "Tiles"

views.py

from django.shortcuts import render
from rest_framework import viewsets
from .serializers import FileListSerializer
from rest_framework import parsers
from .models import Tiles


# Create your views here.
def index(request):
    return render(request, 'main/index.html')


class TileViewSet(viewsets.ModelViewSet):
    serializer_class = FileListSerializer
    parser_classes = (parsers.MultiPartParser, parsers.FormParser,)
    queryset = Tiles.objects.all()

serializers.py

from rest_framework import serializers
from .models import Tiles
import mercantile


class FileListSerializer(serializers.Serializer):
    image = serializers.ListField(
                       child=serializers.FileField(max_length=100000,
                                         allow_empty_file=False,
                                         use_url=False)
                                )
    country = serializers.CharField(max_length=10)

    # The create function below overrides the Django default create function to populate multiple fields using the image name

    def create(self, validated_data):

        country = validated_data.pop('country')
        image = validated_data.pop('image')
        for img in image:

            path = img.path
            path = path.split('/')[-1]
            path = path.split('.')[0]
            x, y, z = path.split('-')
            coords = mercantile.ul(int(x), int(y), int(z))

            tile = Tiles.objects.create(image=img, country=country, lat=coords.lat, lng=coords.lng, **validated_data)

        return tile


class TileSerializer(serializers.ModelSerializer):

    class Meta:
        model = Tiles
        fields = '__all__'

Screenshot of the resulting API page:
Django rest framework autogenerated API page

Asked By: rymanso

||

Answers:

I’ve found a solution to this problem. Hope this helps someone down the line. In serializers.py I changed my ListField to a FilePathField that can take in directories as input.

New serializers.py:

from rest_framework import serializers
from .models import Tiles
import mercantile
import os

BASE_DIR = os.path.dirname((os.path.dirname(os.path.abspath(__file__))))


class FileListSerializer(serializers.Serializer):

    # The following FilePathField is named image but refers to a directory containing multiple images
    image = serializers.FilePathField(path=(os.path.join(BASE_DIR, 'opt_rec_master_img_dir')), allow_files=False, allow_folders=True)
    country = serializers.CharField(max_length=10)

    # The following variables for each instance are generated automatically upon clicking create
    # They are included here so that the DRF auto generated json output includes this information
    lat = serializers.FloatField(read_only=True)
    lng = serializers.FloatField(read_only=True)
    solar = serializers.FloatField(read_only=True)
    wind = serializers.FloatField(read_only=True)
    HV = serializers.FloatField(read_only=True)

    # This function overrides the default create function to populate multiple fields given a directory full of images.
    def create(self, validated_data):

        country = validated_data.pop('country')
        image = validated_data.pop('image')

        directory = image.split('/')[-1]
        for img in os.listdir(image):
            path = img.split('.')[0]
            x, y, z = path.split('-')
            coords = mercantile.ul(int(x), int(y), int(z))
            tile = Tiles.objects.create(image=os.path.join('opt_rec_master_img_dir', directory, img), country=country, lat=coords.lat, lng=coords.lng)

        return tile

    class Meta:
        model = Tiles

views.py

from django.shortcuts import render
from rest_framework import viewsets
from .serializers import FileListSerializer
from .models import Tiles
from django.core.files.images import ImageFile
from rest_framework.utils.encoders import JSONEncoder
from rest_framework.renderers import JSONRenderer, BrowsableAPIRenderer


# Create your views here.
# def index(request):
#     return render(request, 'main/index.html')
#

# JSONEncoder and JSONRenderer have been overriden here to serialize the image path rather than image byte data
class OREncoder(JSONEncoder):
    def default(self, obj):
        if isinstance(obj, ImageFile):
            return obj.path

        return super().default(obj)


class ORRenderer(JSONRenderer):
    encoder_class = OREncoder


class TileViewSet(viewsets.ModelViewSet):
    serializer_class = FileListSerializer
    queryset = Tiles.objects.all()
    renderer_classes = [ORRenderer, BrowsableAPIRenderer]
Answered By: rymanso