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 :)