Programmatically saving image to Django ImageField from another ImageField

In this tutorial, we will learn how to save an image in a model from another ImageField of the same or different model.

Suppose we have a model Product which has ImageField named main_image and rows in the database already have uploaded product images.

from django.db import models

class Product(models.Model):
    name = models.CharField(max_length=256)
    description = models.TextField()
    price = models.DecimalField(max_digits=5, decimal_places=2)
    main_image = models.ImageField(upload_to='shop/%Y-%m/', null=True, blank=True)

    def __str__(self):
        return self.name

 What if we want to upload multiple images for one product? To solve this task we will create a new model called ProductImage

It will have at least two fields: 

  • product - ForeignKey to Product
  • image - ImageField

In models.py

class ProductImage(models.Model):
    product = models.ForeignKey(
        Product,
        on_delete=models.CASCADE,
        related_name='images',
    )
    image = models.ImageField(upload_to='products/%Y-%m/')

    def __str__(self):
        return '{} image'.format(self.product.name)

Next, we will create ProductImage instance for each existing product and save image from its related product.

In copy_images function we iterate over products to create ProductImage. Inside the loop, we will save the image calling save() method on product_image.image. This save method is defined in class ImageFieldFile. We pass to this method the following arguments: 

  • the new name of the image which is being saved
  • content of the old image by instantiating ContentFile class with the old image's bytes
  • and save=True
import os
from uuid import uuid4
from django.core.files.base import ContentFile

from . import models


def copy_images():
    for product in models.Product.objects.all():
        product_image = models.ProductImage.objects.create(product=product)
        _, ext = os.path.splitext(product.main_image.path)
        name = f'{uuid4()}{ext}'
        product_image.image.save(
            name,
            content=ContentFile(product.main_image.read()),
            save=True
        )

The new image's name is defined using uuid4 value to be sure it will be unique on the filesystem, more precisely in directory products/%Y-%m where images will be saved to. Also, here the old image's extension is kept.

Eventually, we can see the created ProductImage instances in Django admin. Note that images on the model Product were saved in shop/%Y-%m directory, but copied images of the new model (ProductImage) will be saved in products/%Y-%m/ directory as the path defined in the model's declarations above in upload_to parameter.

In Django admin, ProductImage instances are attached to its Product using admin.TabularInline class. More about that class you can read in Django's official documentation:

from django.contrib import admin
from . import models


class ProductImageAdminInline(admin.TabularInline):
    model = models.ProductImage
    can_delete = True
    extra = 1


@admin.register(models.Product)
class ProductAdmin(admin.ModelAdmin):
    list_display = (
        'id',
        'name',
        'price',
        'main_image',
    )
    inlines = (
        ProductImageAdminInline,
    )