[Fixed]-Storing Images In DB Using Django Models

12👍

Some people states that using DB for storing images is not a good idea but that’s no true. My advice is to use Django with AppEngine Blobstore Service in this way:

First, create a Django Custom Storage (or pick one from someone else like this one):

from django.core.files.storage import Storage 

class AppEngineBlobStorage(Storage):

    def exists(self, name):
       ...   

    def size(self, name):
       ...

    def url(self, name):
       ...

    def delete(self, name):
       ...

    def listdir(self, path):
       raise NotImplementedError()

This custom storage can receive Django images, convert them to Base64 strings and send them to your AppEngine Blobstore Service application (via xmlrpc for example).

Then, create a Django Image model:

from django.db import models
from django.db.models.fields.files import ImageField
from .storage import AppEngineBlobStorage

class Photo(models.Model):
    caption = models.CharField(max_length=64, blank=True)
    blob = ImageField(
        upload_to='BlobStorage',
        storage=AppEngineBlobStorage(),
        max_length=255,
        blank=False,
    )
    serving_url = models.URLField()
    ...

Then, you have to create an AppEngine application for receiving Django requests for storing images, transform Base64 strings to raw and store them in a Blob. For example:

# coding=utf-8

from __future__ import with_statement

import webapp2

from base64 import b64decode
from StringIO import StringIO
from xmlrpcserver import XmlRpcServer

from google.appengine.api import files
from google.appengine.api import images
from google.appengine.ext import blobstore
from google.appengine.ext.webapp import blobstore_handlers


class ImageRpcServer(object):

    def upload(self, meta, photo, mime_type):
        data = b64decode(photo)
        file_name = files.blobstore.create(mime_type=mime_type)
        with files.open(file_name, 'a') as f:
            f.write(data)
        files.finalize(file_name)
        key = files.blobstore.get_blob_key(file_name)
        return str(key)

    def serving_url(self, meta, key):
        return images.get_serving_url(blobstore.BlobKey(key))

      ...

Serving a blob

The key is the function get_serving_url. From Google docs:

If you are serving images, a more efficient and potentially
less-expensive method is to use get_serving_url using the App Engine
Images API rather than send_blob. The get_serving_url function lets
you serve the image directly, without having to go through your App
Engine instances.

Finally, by serving images with AppEngine, you can use the awesome feature of resize and crop images (check the documentation of get_serving_url function), for example:

// Resize the image to 32 pixels (aspect-ratio preserved)
http://your_app_id.appspot.com/randomStringImageId=s32

Hope it helps. Good luck!

0👍

You can store image in bytea field.

The bytea data type allows storage of binary strings. Postgres Documentation Link

The earlier version of Django does not support bytea field, so I have been using this library called djorm-ext-pgbytea:

👤Mingyu

0👍

I do it like this:

My database is postgres and I usualy ignore djago migrations so I have more flexibility to do things on the database side.
I do creating a view with the proper triggers to encode and decode the image at the database side so for example:

for this table:

CREATE TABLE sales.brand (
   id serial not null, 
   brand_name character varying(100), 
   is_national_brand boolean default true not null, 
   general_comments text, 
   logo_image bytea
   CONSTRAINT brand_pk PRIMARY KEY (id)

)

I create a view like this one:

CREATE OR REPLACE VIEW sales.v_brand_base64 AS 
SELECT brand.id,
    brand.brand_name,
    brand.is_national_brand,
    brand.general_comments,
    logo_image,
    encode(brand.logo_image, 'base64'::text) AS base64_logo_image
FROM sales.brand;

To make the view updatable of coarse you need to create the instead of triggers:

example of the on update trigger:

CREATE OR REPLACE FUNCTION sales.brand_view_update()
   RETURNS trigger
   LANGUAGE plpgsql
AS 
$BODY$
BEGIN

    UPDATE sales.brand SET 
        id = NEW.id, 
        brand_name = NEW.brand_name, 
        is_national_brand = NEW.is_national_brand, 
        general_comments = NEW.general_comments,
        logo_image = decode(NEW.base64_logo_image, 'base64')
    WHERE     
        id = OLD.id;
    
    RETURN NEW;
END;
$BODY$
VOLATILE
SECURITY INVOKER
CALLED ON NULL INPUT
COST 100;

and

CREATE TRIGGER do_brand_update INSTEAD OF UPDATE
ON sales.v_brand_base64
FOR EACH ROW 
EXECUTE PROCEDUE sales.brand_view_update();

At django side:

the model:

class Brand(models.Model):
    id = models.AutoField(primary_key=True)
    brand_name = models.CharField(max_length=100)
    is_national_brand = models.BooleanField()
    general_comments = models.TextField(blank=True, null=True)
    logo_image = models.ImageField(null=True)
    base64_logo_image = models.TextField(blank=True, null=True)

    def b64_logo_image(self):
        base64enc = self.base64_logo_image
        return format_html('<img style="width: 100%" display: block;" src="data:image/bmp;base64,{}">', base64enc)

    def __str__(self):
        return self.brand_name

    @property
    def detail_fields(self):
        # (0:value, 1:href, 2:header, 3:size, 4:list, 5:detail, )
        return (
            (self.brand_name, safe_self_rev(self, 'brand_detail'), 'Brand Name', 10, True, True, ),
            (self.is_national_brand, None, 'National Brand', 5, True, True, ),
            (self.general_comments, None, 'Comments', 5, True, True, ),
            (self.b64_logo_image, None, 'Logo', 5, True, True, ),
        )

    class Meta:
        managed = False
        db_table = '"sales"."v_brand_base64"'

And the view like:

class BrandUpdateView(UpdateView):

    model = Brand
    fields = ['brand_name', 'is_national_brand', 'general_comments', 'logo_image', ]

    template_name = "sales/brand_edit.html"

    def get_success_url(self):
        if self.object.id is None:
            return reverse('index')
        else:
            return reverse('brand_detail', args=[self.object.id])

    def form_valid(self, form):
        if form.is_valid():
            brand = form.save(commit=False)
            logo_image = form.cleaned_data['logo_image'].file.read()
            brand.logo_image = None
            logo_base64 = base64.encodebytes(logo_image).decode();
            brand.base64_logo_image = logo_base64
            brand.save()
        return super().form_valid(form)

It is probably the not best way but it works

the image is saved on the database in a bytea field and you can use the property b64_logo_image like a field to render the image on your templates
something like
{{object.b64_logo_image}}

About to record images at database that is why blob and bytea fields exists.
It is your choice.

I use do do it and I have no problems. As I said I am more a database side guy 🙂

0👍

I think the best approach is to store the ‘main file’ in your media path of your project and save the address of file(path to the file) in your model. this way you dont need to convert….

Leave a comment