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__'
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]
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__'
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]