Why does Django save pictures twice? - python

I am trying to overwrite the save function. What I want is to resize the original photos to a default size (800 * 534) and then to generate a thumbnails for this picture. However, I found the photos were stored twice.
For example, I add a photo named sample.jpg at first time. There are three photos in my directions. One is in ../Media/photos/ and the others are in ../Media/. When I save this picture again, there are four photos. Two are in ../Media/photos/, and the others are in ../Media/.
I am really confused why Django stores the picture twice and why my pictures were stored in ../Media not in ../Media/photos. What I want is to make only two pictures which the 800*534 picture and its thumbnail picture in ../Media/photos.
Here are my codes.
The class Photo:
class Photo(models.Model):
title = models.CharField(max_length=250)
slug = models.SlugField(max_length=250)
summary = models.TextField(blank=True)
created_date = models.DateField(auto_now_add=True)
modified_date = models.DateField(auto_now=True)
image = models.ImageField(upload_to='photo/')
album = models.ForeignKey(Album, on_delete=models.CASCADE)
is_cover_photo = models.BooleanField(default=False)
The save function I wrote in class Photo
def save(self):
if not self.pk:
filename = self.image
if filename:
print(filename)
img = Image.open(filename)
# save the default size photo
default_size = (800, 534)
img.thumbnail(default_size, Image.ANTIALIAS)
img.save(self.get_img_filename(filename))
# save thumbnail photo
cover_size = (342, 229)
img.thumbnail(cover_size, Image.ANTIALIAS)
img.save(self.get_img_thumb_filename(filename))
super(Photo, self).save()
And the other two functions:
def get_img_filename(self, img):
return img.path
def get_img_thumb_filename(self, img):
img_pre, img_ext = os.path.splitext(img.path)
thumb_name = img_pre + '_thumb' + img_ext
return thumb_name

This happens because your program is still calling the super class's save method which implicitly saves all the fields, including "image". Therefore, you have duplicated images in your server.

you can pass a parametter inside the save function like this:
img.save(self.get_img_filename(filename), save=False)
Your problem is that you are PIL.Image is saving the image localy plus in the Model Db you are calling it another save storing it localy like Lukas Herman said.
you need a way to store Image into ImageField possible without using PIL.Image.save(). here is a link I found : link-stackoverflow
Another Posible Solution is to store using Image Class and store the image and save the path location to CharField or TextField

Related

Save list of images from models django

I am using Django 3 and I want to create a Model where I upload a tensor file (called ct), then I elaborate it, extract the slices of this tensor and save the single slices as different images.
here is my model:
class Case(models.Model):
slug = ShortUUIDField(max_length=3)
title = models.CharField(max_length=50)
#...
ct = models.FileField()
# here I want to save a series of images that I extracted from the tensor 'ct'
'ct' is a 3D scan that I have to upload as a file
The number of images extracted is not constant.
I have a function called 'get_slices' that receives a ct and returns the images to be saved
Can you please help me step by step?
You can create another model and connect it to the original model (with ForeignKey).
Use ImageField if you just want to save the image
class Case(models.Model):
slug = ShortUUIDField(max_length=3)
title = models.CharField(max_length=50)
# Use ct = models.ImageField(title) if you want to save at least one photo'ct'
class Case_images(models.Model):
case = models.ForeignKey(Case, on_delete=models.CASCADE)
image = models.ImageField(upload_to=case.title)
Use these codes when you want to upload a ct:
def upload_a_ct(title, slug, ct):
c = Case(slug=slug, title=title)
c.save()
for image in get_slices(ct):
Case_images.objects.create(image=image, case=c)
The solution to the problem is here:
Automatically save list of images in ImageField after upload - django 3 <<<

How to display uploaded Dicom image metadata in detailed view in Django?

I am a new to coding and working on a small Django project in which I want to display Dicom image metadata. I included FileField in the Django model to upload a Dicom image and I want to display the uploaded Dicom image metadata in Generic detailed view. I tried overriding the context method and accessing the image path but getting an error. How do I proceed with it? Please help!
#My model
class Patient(models.Model):
name = models.CharField(max_length=100)
ailment = models.TextField(max_length=200)
date_tested = models.DateTimeField(default=timezone.now)
image = models.ImageField(upload_to='.',default='download.jpg')
dicom = models.FileField(upload_to='dicom/',null=True)
#My Detailed view
class PatientDetailView(LoginRequiredMixin,DetailView):
model = Patient
def get_context_data(self,**kwargs):
medinfo = dict()
context = super().get_context_data(**kwargs)
dcpimg = imageio.imread(self.dicom.path)
for keys in dcpimg.meta:
medinfo[keys] = str(dcpimg.meta[keys])
context['medinfo'] = medinfo
return context
##Iam getting this error
AttributeError: 'PatientDetailView' object has no attribute 'dicom'
When you are calling self.dicom.path, self here refers to PatientDetailView class.
Try calling:
self.model.dicom.path

How to set the maximum image size to upload image in django-ckeditor?

I am using django-ckeditor for my project to upload image along with text content.
I used
body = RichTextUploadingField(blank=True, null=True) in model.
Now I want to restrict the user to upload large size images in content or larger than predefined maximum height/width. I want the uploaded images in content of particular height/weight and size like less then 1mb.
How can I predefine maximum image height and width as well as maximum image size limit?
Is there any way to define it from django-ckeditor configuration?
Or How to resize the uploaded images from content at backend after user submitted the form?
Here is my models.py:
class Post(models.Model):
STATUS_CHOICES = {
('draft', 'Draft'),
('published', 'Published'),
}
title = models.CharField(max_length=250)
slug = models.SlugField(max_length=250, unique=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
body = RichTextUploadingField(blank=True, null=True)
status = models.CharField(max_length=10,
choices=STATUS_CHOICES,
default='draft')
I tried a lot to solve it but failed.Any suggetion to solve the issue?
Thanks in advance.
You can set CKEDITOR_THUMBNAIL_SIZE in setting.py
CKEDITOR_THUMBNAIL_SIZE = (500, 500)
With the pillow backend, you can change the thumbnail size with the CKEDITOR_THUMBNAIL_SIZE setting (formerly THUMBNAIL_SIZE). Default value: (75, 75)
With the pillow backend, you can convert and compress the uploaded images to jpeg, to save disk space. Set the CKEDITOR_FORCE_JPEG_COMPRESSION setting to True (default False) You can change the CKEDITOR_IMAGE_QUALITY setting (formerly IMAGE_QUALITY), which is passed to Pillow:
The image quality, on a scale from 1 (worst) to 95 (best). The default
is 75. Values above 95 should be avoided; 100 disables portions of the
JPEG compression algorithm and results in large files with hardly any
gain in image quality.
This feature is disabled for animated images.
check official doc. https://github.com/django-ckeditor/django-ckeditor/blob/master/README.rst
I think there should be a way to resize the picture on uploading. But to be honest I didn't find nor in documentation neither by debugging.
However, I can suggest you a work-around to post-process all the images inside the content upon saving of Post model.
models.py
from django.conf import settings
from PIL import Image
def resize_image(filename):
"""
here goes resize logic.
For example, I've put 50% of current size if width>=900.
Might be something more sophisticated
"""
im = Image.open(filename)
width, height = im.size
if width >= 500:
im = im.resize((int(width * 0.5), int(height * 0.5)))
im.save(filename)
class Post(models.Model):
...
body = RichTextUploadingField(blank=True, null=True)
...
def save(self, *args, **kwargs):
for token in self.body.split():
# find all <img src="/media/.../img.jpg">
if token.startswith('src='):
filepath = token.split('=')[1].strip('"') # get path of src
filepath = filepath.replace('/media', settings.MEDIA_ROOT) # resolve path to MEDIA_ROOT
resize_image(filepath) #do resize in-place
super().save(*args, **kwargs)
The solution is not super nice because it resizes picture only after it was uploaded.
Probably the library itself should have some kind of exit-point/callback for image pre-processing when uploaded.

How to Register Images using Django's ORM

How do you register an image file in a Django ImageField without using a form, and not copying any files?
I have several thousand JPGs located at /images, and I want to register them in an Image model similar to:
class Image(models.Model):
image = models.ImageField(upload_to='images', max_length=1000)
hash = models.CharField(max_length=1000, unique=True)
However, all the docs I can find on "loading" images into a Django project assume I'm doing so via a form, which also implies the image will be copied to MEDIA_ROOT. I'm not using a form, and I don't want to re-copy the several thousand JPGs, since they're already where they're supposed to be. I just want to create Image records that will store the filename of all the images I currently have. I've written a simple Python script to loop over each image, but I can't find how to properly create the Image record.
I also want to store a hash of the image content, to prevent duplicate records. e.g.
import hashlib
content = open(image_filename).read()
h = hashlib.sha512()
h.update(content)
imgobj.hash = h.hexdigest()
imgobj.save()
Would I override the default model.Model.save() method to do this?
If you have the script to loop over the images in your directory, you're nearly to a solution. Django will only store the path to the image in your Image.image field so basically all you need to do in your loop is:
#pseudo-code
for image_file in image_files:
image, created = Image.objects.get_or_create(hash=the_hash, \
defaults={'image' : 'path/to/image', 'hash' : the_hash)
That's a pretty easy way to build up only the unique records in your database without having to move the files, or use a form. You're either going to harmlessly return the image by the hash if it exists, or you're going to create a new record.
Hope that helps!
After digging through the code, and piecing together a few snippets I found, the following seems to work for me:
models.py
import os, hashlib
from django.db import models
class Image(models.Model):
image = models.ImageField(upload_to=IMAGE_UPLOAD_TO, max_length=1000)
hash = models.CharField(max_length=1000, unique=True)
def save(self, *args, **kwargs):
# Update image hash to ensure uniqueness.
h = hashlib.sha512()
h.update(self.image.read())
self.hash = h.hexdigest()
return models.Model.save(self, *args, **kwargs)
import_images.py
import os
from django.conf import settings
from django.core.files import File
from myapp import models
fn = os.path.join(settings.MEDIA_ROOT, 'images', 'mytestimage.jpg')
img = models.Image()
img.image.save(fn, File(open(fn, 'r')))
img.save()

Image resizing with django?

I'm new to Django (and Python) and I have been trying to work out a few things myself, before jumping into using other people's apps. I'm having trouble understanding where things 'fit' in the Django (or Python's) way of doing things. What I'm trying to work out is how to resize an image, once it's been uploaded. I have my model setup nicely and plugged into the admin, and the image uploads fine to the directory:
from django.db import models
# This is to list all the countries
# For starters though, this will be just United Kingdom (GB)
class Country(models.Model):
name = models.CharField(max_length=120, help_text="Full name of country")
code = models.CharField(max_length=2, help_text="This is the ISO 3166 2-letter country code (see: http://www.theodora.com/country_digraphs.html)")
flag = models.ImageField(upload_to="images/uploaded/country/", max_length=150, help_text="The flag image of the country.", blank=True)
class Meta:
verbose_name_plural = "Countries"
def __unicode__(self):
return self.name
The thing I'm now having trouble with is taking that file and making a new file into a thumbnail. Like I say, I'd like to know how to do it without using others' apps (for now). I have got this code from DjangoSnippets:
from PIL import Image
import os.path
import StringIO
def thumbnail(filename, size=(50, 50), output_filename=None):
image = Image.open(filename)
if image.mode not in ('L', 'RGB'):
image = image.convert('RGB')
image = image.resize(size, Image.ANTIALIAS)
# get the thumbnail data in memory.
if not output_filename:
output_filename = get_default_thumbnail_filename(filename)
image.save(output_filename, image.format)
return output_filename
def thumbnail_string(buf, size=(50, 50)):
f = StringIO.StringIO(buf)
image = Image.open(f)
if image.mode not in ('L', 'RGB'):
image = image.convert('RGB')
image = image.resize(size, Image.ANTIALIAS)
o = StringIO.StringIO()
image.save(o, "JPEG")
return o.getvalue()
def get_default_thumbnail_filename(filename):
path, ext = os.path.splitext(filename)
return path + '.thumb.jpg'
...but this has ultimately confused me... As I don't know how this 'fits in' to my Django app? And really, is it the best solution for simply making a thumbnail of an image that has been successfully uploaded? Can anyone possibly show me a good, solid, decent way that a beginner like me can learn to do this properly? As in, knowing where to put that sort of code (models.py? forms.py? ...) and how it would work in context? ... I just need a bit of help understanding and working this problem out.
Thank you!
If it's OK for you, there is a Django application ready, doing exactly what you want:
https://github.com/sorl/sorl-thumbnail
This is what I use in my models to save a new thumbnail if the uploaded image has changed. It's based of another DjangoSnippet but it I can't remember who wrote the orginal - if you know please add a comment so that I can credit them.
from PIL import Image
from django.db import models
from django.contrib.auth.models import User
import os
import settings
class Photo_Ex(models.Model):
user = models.ForeignKey(User, blank=True, null=True)
photo = models.ImageField(upload_to='photos')
thumbnail = models.ImageField(upload_to='profile_thumb', blank=True,
null=True, editable=False)
def save(self, *args, **kwargs):
size = (256,256)
if not self.id and not self.photo:
return
try:
old_obj = Photo_Ex.objects.get(pk=self.pk)
old_path = old_obj.photo.path
except:
pass
thumb_update = False
if self.thumbnail:
try:
statinfo1 = os.stat(self.photo.path)
statinfo2 = os.stat(self.thumbnail.path)
if statinfo1 > statinfo2:
thumb_update = True
except:
thumb_update = True
pw = self.photo.width
ph = self.photo.height
nw = size[0]
nh = size[1]
if self.photo and not self.thumbnail or thumb_update:
# only do this if the image needs resizing
if (pw, ph) != (nw, nh):
filename = str(self.photo.path)
image = Image.open(filename)
pr = float(pw) / float(ph)
nr = float(nw) / float(nh)
if image.mode not in ('L', 'RGB'):
image = image.convert('RGB')
if pr > nr:
# photo aspect is wider than destination ratio
tw = int(round(nh * pr))
image = image.resize((tw, nh), Image.ANTIALIAS)
l = int(round(( tw - nw ) / 2.0))
image = image.crop((l, 0, l + nw, nh))
elif pr < nr:
# photo aspect is taller than destination ratio
th = int(round(nw / pr))
image = image.resize((nw, th), Image.ANTIALIAS)
t = int(round(( th - nh ) / 2.0))
image = image.crop((0, t, nw, t + nh))
else:
# photo aspect matches the destination ratio
image = image.resize(size, Image.ANTIALIAS)
image.save(self.get_thumbnail_path())
(a, b) = os.path.split(self.photo.name)
self.thumbnail = a + '/thumbs/' + b
super(Photo_Ex, self).save()
try:
os.remove(old_path)
os.remove(self.get_old_thumbnail_path(old_path))
except:
pass
def get_thumbnail_path(self):
(head, tail) = os.path.split(self.photo.path)
if not os.path.isdir(head + '/thumbs'):
os.mkdir(head + '/thumbs')
return head + '/thumbs/' + tail
def get_old_thumbnail_path(self, old_photo_path):
(head, tail) = os.path.split(old_photo_path)
return head + '/thumbs/' + tail
Not sure about the code you sent, because I never use Models as such, but there is another method.
You can implement your own FileUploadHandler for handling image file uploads. Example is
here.
Just after line 37 (dest.close()) use thumbnail(upload_dir + upload.name) function (the one you sent).
Hope it helps you.
A key question is: when should the thumbnail be generated?
Dynamically when the user requests a thumbnail image?
Or do you want to create a physical disk file whenever a country is INSERTed/UPDATEd in the database.
If (1) I suggest you create a view that maps to url /flagthumbnail/countryid. The view method would then have to:
Get the country instance from the database
Read the flag image into a PIL Image and resize that.
Create (and return) a HTTPResponse with correct content-type and write the PIL Image to the response.
Whenever you need to display a thumbnail flag, just use <a href="/flagthumbnail/countryid">.
If (2), you could connect to Django's django.db.models.signals.post_save signal and in the signal handler create and save a thumbnail file.
I guess it depends on how and when your using your thumbnails.
If you want to create some thumbnails every time the Country is saved, you could do it like so:
from django.db import models
# This is to list all the countries
# For starters though, this will be just United Kingdom (GB)
class Country(models.Model):
name = models.CharField(max_length=120, help_text="Full name of country")
code = models.CharField(max_length=2, help_text="This is the ISO 3166 2-letter country code (see: http://www.theodora.com/country_digraphs.html)")
flag = models.ImageField(upload_to="images/uploaded/country/", max_length=150, help_text="The flag image of the country.", blank=True)
class Meta:
verbose_name_plural = "Countries"
def __unicode__(self):
return self.name
def save(self, force_insert=False, force_update=False):
resize_image(self.flag)
super(Country, self).save(force_insert, force_update)
If you aren't 100% sure what sizes you'll need your images, you could resize them last minute. I've seen this effectively done with a templatetag (I believe in a version on Pinax). You create a templatetag that takes the image and a size, then create and save the image of the appropriate size if you need to, or display a previously created one if it's there. It works pretty well.
Overriding the save method is a good option, but I'd be more tempted to use a signal in this case. Django signals allow you to "listen" to a given model type's various events; in this case, you'd be interested in the post_save event.
I usually subscribe to such signals in my models.py file. Code for you would look something like this:
from django.db.models.signals import post_save
from models import Country
def resize_image(sender, **kwargs):
country = kwargs["instance"]
resize_image(country.flag) # where resize_image generates a thumbnail given a Country instance
post_save.connect(resize_image, sender=Country)
You can give a try to:
image headcut
features:
Allows you to set the center of attention of an image... heads won't get cut anymore.
Video thumbnailing
Prevents cross-site image linking
Simple setup and usage
Ryan is correct signals are a better way to go however the advantage of the overriden save is that we can get the old and new image paths, see if the image has changed (and if it has create a new thumbnail), save the model instance and then delete the old image and thumbnail.
Remember django does not clean up the old images for you so unless you have script to check that the images/thumbnails are still in use and clean out any that are not you will leak disk space. (This may or may-not be a problem for you depending on image size and frequency of updates)
I'm not sure how you could do this with a post_save signal, and I don't know enough about signals (That's research for tonight!) to know if there is a suitable pre_save signal. If i find one then I'll re-write the code above to use signals as a generic pre save listner.
Another alternative is to use Imagemagick directly, if you want to avoid several difficulties with Pillow and Python 3 such as this one.
from subprocess import call
call(['convert', img_path_file_name, '-thumbnail', target_size_str, '-antialias', style_path_file_name])
You could call this on model save, or on a template tag to generate an one-off manipulated copy of the original file in a file-caching fashion, or even a celery task.
You can get an example of how I have used this in one of my projects:
main function
relevant settings
templatetag
how to use
I also swear by Justin Driscoll's django-photologue is also great for resizing. It:
Resizes (that is can be scaled to a height or width proportionately)
Crops (kind-of intelligently: from the center, top, left, bottom or right)
Optionally upsizes
Can add effects (such as "color", "brightness", "contrast" and "sharpness" as well as filters like "Find Edges" and "Emboss". Sharpen your images. Make your thumbnails black and white.)
Can add simple watermark
Cache the results
Basically it's awesome.
A very easy way is to resize and/or crop your image on display with django-imagefit.
It will preserve the original image so you can have multiple versions, refactor your frontend later and it also works with non-model images.
you can change photo size after save data in database models using 'Pillow' , for example i am going
to change the user profile image to 300 x 300 , just i will define save() method inside model and change the size , just following the steps below:
1- first make sure 'Pillow' is installed .
2- here is my code in profile model class :
from PIL import Image
class Profile(models.Model):
PRF_image = models.ImageField(upload_to='profile_img', blank=True, null=True)
def save(self , *args , **kwargs):
# change profile image size
img = Image.open(self.PRF_image.path)
if img.width > 300 or img.height > 300:
output_size = (300, 300)
img.thumbnail(output_size)
img.save(self.PRF_image.path)
this is just example , and change it beasd on you case/requirements.
and don't forget to use your_object.save() inside your view to use this method.
i hope this helpful

Categories

Resources