4

I have a model that has close to 150K entries in the Database.
Today, it's possible to upload an image for each entry separately, when editing that specific entry.

We have a need to upload close to 4k image files, one for each entry, and I would like to achieve it by allowing a multiple files upload, where the image file name will match to a name attribute of the model.

Basic model example:

class Entry(models.Model):
    name = models.CharField(unique=True, max_length=200, db_index=True)
    image = models.ImageField(null=True, blank=True)

Is there a way to create a view that will support one upload button for multiple files (where each file represents a different entry of the model in the DB), and handle the rest in an upload handler/storage backend?

Meny Issakov
  • 1,400
  • 1
  • 14
  • 30

1 Answers1

2

Update thousands of records at once is challenging as default.

One issue is the performance on the database side. the best I could come up is using update and overwriting some methods. assume you already have your images in some directory.

from django.db.models import fields
from django.db.models import F
from django.db.models.expressions import Value , CombinedExpression
from django.db.models import QuerySet

class TextValue(Value):
    def as_sql(self, compiler, connection):
        connection.ops.check_expression_support(self)
        return '%s', [self.value]

class Expr(F):
    ADD = '||'  # standard concat row value + value in PostgreSQL

    #overwrite method to support text
    def _combine(self, other, connector, reversed):
        if not hasattr(other, 'resolve_expression'):
            other = TextValue(other, output_field=fields.CharField())

        return CombinedExpression(self, connector, other)


class Entry(models.Model):
    name = models.CharField(unique=True, max_length=200, db_index=True)
    image = models.ImageField(upload_to= 'media/' , null=True, blank=True , default='default.png')

now having this piece code you can bulk update

entries= Entry.objects.all() 
entries.update(**{'image': Expr('name') + '.png'})

the best part of doing it like is the performance. this is the only query that is executed

{'sql': 'UPDATE "entry" SET "image" = ("entry"."name" || \'.png\')', 'time': '0.024'}]

update

doing the same thing as admin and saving one instance of model per entry.

from django.contrib import  admin
from django import forms


class EntryForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(EntryForm, self).__init__(*args, **kwargs)
        self.fields['image'].widget.attrs.update(
            {'multiple': True, 'accept': 'image/jpg,image/png,image/gif', })


class EntryAdmin(admin.ModelAdmin):
    form = EntryForm

    def save_model(self, request, obj, form, change):
        files = request.FILES.getlist('image')

        # if image exist
        if files:
            for image_field in files:
                try:
                    instance = Entry.objects.get(name=image_field.name[:-4])
                    instance.image = image_field
                    instance.save()
                except Entry.DoesNotExist:
                    pass
        else:
            return super().save_model(request, obj, form, change)



admin.site.register(Entry , EntryAdmin)

you can mix these two part to get batter performance

Naqib Hakimi
  • 874
  • 4
  • 15
  • 1
    Thanks Naqib for the detailed answer, but I'm afraid it doesn't address the Django Admin part of the question. The issue at hand is not how to set a different value to multiple DB rows, but how to do it with the Django Admin framework. In the Django Admin the Django UI is coupled with the models behind the scene and there are views that allow manipulating these objects (MVC). The manipulation usually takes place via Django forms, but I couldn't find a way to use a single form to allow uploading multiple images and assign each to a different row in the DB. – Meny Issakov Jun 30 '19 at 05:03