Django + matplotlib : image is not refreshed after new figsave() - django

I have a model that calls matplotlib to create and save a figure.
When the user creates a new object instance, matplotlib creates and save a fig and I return to the user a page that includes the fig.This works.
When the user edits the object instance : matplotlib creates a new figure that overrides the existing figure file, this works : I have checked that the figure file is updated on the server.
My problem is that the image is not updated in the page returned to the user : the user has to simply refresh the page and then the image is updated. this happens not always : sometimes the image is updated whithout need for refreshing.
I wonder if perhaps the page is displayed to fast when the new matplotlib fig is not already saved on the disk.
Any idea ?
models.py (extract)
class Appareil(models.Model):
title = models.CharField(max_length=100,default=None, blank=True, null=True)
...............(other fields)
.............
def __unicode__(self):
return self.title
def save(self, *args, **kwargs):
self.picture_file_name=figure(self.filed1,self.field2)
super(Appareil, self).save(*args, **kwargs)
views.py (extract)
def figure(field1, field2) :
#Init figure
fig = plt.figure(figsize=(20, 10),dpi=80, facecolor='w', edgecolor='k')
#various matplot commands
#...........................
#file_png = etc....
picture_file_name = "/static/images/" + file_png + ".png"
plt.savefig("static/images/" + fichier_png + ".png", transparent=True)
return (picture_file_name)
template (extract)
<img src="{{appareil.picture_file_name}}" />

Related

ValueError when uploading resized django images to google cloud

i have this model which works fine when uploading resized images to the media file on my django project
class ItemImage(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
item = models.ForeignKey(Item, on_delete=models.CASCADE)
image = models.ImageField(null=True, blank=True,upload_to='item_img/')
created = models.DateTimeField(auto_now_add=True)
def save(self):
im = Image.open(self.image)
im_name = uuid.uuid4()
im = im.convert('RGB')
output = BytesIO()
# Resize/modify the image
im = im.resize((700, 700))
# after modifications, save it to the output
im.save(output, format='JPEG', quality=90)
output.seek(0)
# change the imagefield value to be the newley modifed image value
self.image = InMemoryUploadedFile(output, 'ImageField', "%s.jpg" % self.image.name, 'image/jpeg',
sys.getsizeof(output), None)
super(ItemImage, self).save()
def __str__(self):
return self.item.title
when i changed the file storage to google cloud i faced this error when uploading the images
ValueError at /ar/dashboard/my_items/edit_item/add_item_image/2/
Size 120495 was specified but the file-like object only had 120373 bytes remaining.
note that the images are uploaded successfully when i remove the save method that is added so is there anything that i need to change in that save method when dealing with gcloud?
i found a similar problem on github and he explained the error as follow "I think this is an error in the end user code which GCS rejects and the other services are more liberal about. The call sys.getsizeof(fi_io) yields the size of the BytesIO object, not the size of the buffer"
so i changed sys.getsizeof(output) to len(output.getbuffer())
and that's it it works with both google cloud and local media files

Populate model with metadata of file uploaded through django admin

I have two models,Foto and FotoMetadata. Foto just has one property called upload, that is an upload field. FotoMetadata has a few properties and should receive metadata from the foto uploaded at Foto. This can be done manually at the admin interface, but I want to do it automatically, i.e: when a photo is uploaded through admin interface, the FotoMetadata is automatically filled.
In my model.py I have a few classes, including Foto and FotoMetadata:
class Foto(models.Model):
upload = models.FileField(upload_to="fotos")
def __str__(self):
return '%s' %(self.upload)
class FotoMetadata(models.Model):
image_formats = (
('RAW', 'RAW'),
('JPG', 'JPG'),
)
date = models.DateTimeField()
camera = models.ForeignKey(Camera, on_delete=models.PROTECT)
format = models.CharField(max_length=8, choices=image_formats)
exposure = models.CharField(max_length=8)
fnumber = models.CharField(max_length=8)
iso = models.IntegerField()
foto = models.OneToOneField(
Foto,
on_delete=models.CASCADE,
primary_key=True,
)
When I login at the admin site, I have an upload form related to the Foto, and this is working fine. My problem is that I can't insert metadata at FotoMetadata on the go. I made a function that parse the photo and give me a dictionary with the info I need. This function is called GetExif is at a file called getexif.py. This will be a simplified version of it:
def GetExif(foto):
# Open image file for reading (binary mode)
f = open(foto, 'rb')
# Parse file
...
<parsing code>
...
f.close()
#create dictionary to receive data
meta={}
meta['date'] = str(tags['EXIF DateTimeOriginal'].values)
meta['fnumber'] = str(tags['EXIF FNumber'])
meta['exposure'] = str(tags['EXIF ExposureTime'])
meta['iso'] = str(tags['EXIF ISOSpeedRatings'])
meta['camera'] =str( tags['Image Model'].values)
return meta
So, basically, what I'm trying to do is use this function at admin.py to automatically populate the FotoMetadata when uploading a photo at Foto, but I really couldn't figure out how to make it. Does any one have a clue?
Edit 24/03/2016
Ok, after a lot more failures, I'm trying to use save_model in admin.py:
from django.contrib import admin
from .models import Autor, Camera, Lente, Foto, FotoMetadata
from fotomanager.local.getexif import GetExif
admin.site.register(Autor)
admin.site.register(Camera)
admin.site.register(Lente)
admin.site.register(FotoMetadata)
class FotoAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
# populate the model
obj.save()
# get metadata
metadados = GetExif(obj.upload.url)
# Create instance of FotoMetadata
fotometa = FotoMetadata()
# FotoMetadata.id = Foto.id
fotometa.foto = obj.pk
# save exposure
fotometa.exposure = metadados['exposure']
admin.site.register(Foto, FotoAdmin)
I thought it would work, or that I will have problems saving data to the model, but actually I got stucked before this. I got this error:
Exception Type: FileNotFoundError
Exception Value:
[Errno 2] No such file or directory: 'http://127.0.0.1:8000/media/fotos/IMG_8628.CR2'
Exception Location: /home/ricardo/Desenvolvimento/fotosite/fotomanager/local/getexif.py in GetExif, line 24
My GetExif function can't read the file, however, the file path is right! If I copy and paste it to my browser, it downloads the file. I'm trying to figure out a way to correct the address, or to pass the internal path, or to pass the real file to the function instead of its path. I'm also thinking about a diferent way to access the file at GetExif() function too. Any idea of how to solve it?
Solution
I solved the problem above! By reading the FileField source, I've found a property called path, which solve the problem. I also made a few other modifications and the code is working. The class FotoAdmin, at admin.py is like this now:
class FotoAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
# populate the model
obj.save()
# get metadata
metadados = GetExif(obj.upload.path)
# Create instance of FotoMetadata
fotometa = FotoMetadata()
# FotoMetadata.id = Foto.id
fotometa.foto = obj
# set and save exposure
fotometa.exposure = metadados['exposure']
fotometa.save()
I also had to set null=True at some properties in models.py and everything is working as it should.
I guess you want to enable post_save a signal
read : django signals
Activate the post_save signal - so after you save a FOTO you have a hook to do other stuff, in your case parse photometa and create a FotoMetadata instance.
More, if you want to save the foto only if fotometa succeed , or any other condition you may use , pre_save signal and save the foto only after meta foto was saved.

how to not display original picture name in Django

I am building a Django project where users can upload pictures. I am wondering what I should do to not show the original picture name.
I want the url to be something like /pic/randomnumber, and when the picture is downloaded from the website, it would have the name randomnumber.jpg. For example, all the pictures on Tumblr have the name tumblr_blabla.jpg.
I think this is something that should be done in models.py, but I am not quite sure how to implement it.
IMO you should write method save in your model
Something like that:
from PIL import Image
import os
class YOURS_MODEL_NAME(models.Model):
photo = models.ImageField(upload_to="photos")
def save(self, miniature=True):
super(YOURS_MODEL_NAME, self).save()
if miniature:
filepath = self.photo.path
image = Image.open(filepath)
new_filepath = filepath.split('.')
new_filepath = '.'.join("HERE YOU CAN ADD EVERYTHING TO PATH TO THIS PHOTO") + "." + new_filepath[-1].lower()
try:
image.save(new_filepath, quality=90, optimize=1)
except:
image.save(new_filepath, quality=90)
photo_name = self.photo.name.split('.')
photo_name = '.'.join("HERE YOU CAN ADD EVERYTHING YOU WANT TO 'PHOTO NAME'") + "." + photo_name[-1].lower()
self.photo = photo_name
self.save(miniature=False)
# remove old image
os.remove(filepath)
The upload_to argument in your Model definition can be a callable function which you use to customize the name of the file. Taken from the Django docs on
FileField (of which ImageField is a subclass):
upload_to takes two arguments: instance and filename, (where filename is the original filename, which you may also chose to ignore).
Something similar to this in models.py should do the trick:
def random_filename(instance, filename):
file_name = "random_string" # use your choice for generating a random string!
return file_name
class SomeModel(models.Model):
file = models.ImageField(upload_to=random_filename)
(this is similar to the answer this question about FileFields).
If you are going down this path, I would recommend that you use either the hash/checksum or date/time of the file upload. Something along these lines should work (although I haven't tested it myself!):
from hashlib import sha1
def unique_filename(instance, field):
filehash = sha1()
for chunk in getattr(instance, field).chunks():
filehash.update(chunk)
return filehash
class SomeModel(models.Model):
file = models.ImageField(upload_to=unique_filename(field='file'))
Hope this helps!

Resize thumbnails django Heroku, 'backend doesn't support absolute paths'

I've got an app deployed on Heroku using Django, and so far it seems to be working but I'm having a problem uploading new thumbnails. I have installed Pillow to allow me to resize images when they're uploaded and save the resized thumbnail, not the original image. However, every time I upload, I get the following error: "This backend doesn't support absolute paths." When I reload the page, the new image is there, but it is not resized. I am using Amazon AWS to store the images.
I'm suspecting it has something to do with my models.py. Here is my resize code:
class Projects(models.Model):
project_thumbnail = models.FileField(upload_to=get_upload_file_name, null=True, blank=True)
def __unicode__(self):
return self.project_name
def save(self):
if not self.id and not self.project_description:
return
super(Projects, self).save()
if self.project_thumbnail:
image = Image.open(self.project_thumbnail)
(width, height) = image.size
image.thumbnail((200,200), Image.ANTIALIAS)
image.save(self.project_thumbnail.path)
Is there something that I'm missing? Do I need to tell it something else?
Working with Heroku and AWS, you just need to change the method of FileField/ImageField 'path' to 'name'. So in your case it would be:
image.save(self.project_thumbnail.name)
instead of
image.save(self.project_thumbnail.path)
I found the answer with the help of others googling as well, since my searches didn't pull the answers I wanted. It was a problem with Pillow and how it uses absolute paths to save, so I switched to using the storages module as a temp save space and it's working now. Here's the code:
from django.core.files.storage import default_storage as storage
...
def save(self):
if not self.id and not self.project_description:
return
super(Projects, self).save()
if self.project_thumbnail:
size = 200, 200
image = Image.open(self.project_thumbnail)
image.thumbnail(size, Image.ANTIALIAS)
fh = storage.open(self.project_thumbnail.name, "w")
format = 'png' # You need to set the correct image format here
image.save(fh, format)
fh.close()
NotImplementedError: This backend doesn't support absolute paths - can be fixed by replacing file.path with file.name
How it looks in the the console
c = ContactImport.objects.last()
>>> c.json_file
<FieldFile: protected/json_files/data_SbLN1MpVGetUiN_uodPnd9yE2prgeTVTYKZ.json>
>>> c.json_file.name
'protected/json_files/data_SbLN1MpVGetUiN_uodPnd9yE2prgeTVTYKZ.json'

upload images from URL to easy_thumbnails field

I want to upload many images from URLs while I create objects with a script.
#models.py
class Widget(TimeStampedModel):
name = CharField ... etc, etc
pic = ThumbnailerImageField(_('Widget Pic'),
upload_to='widget/pic/',
help_text = _('Please submit your picture here.'),
null=True, blank=True)
so I thought of using the save method in that class to download and save the images. So my script creates the Widget objects and saves the image url, and then the save method tries to download and save the image. My save method so far is:
def save(self, *args, **kwargs):
if self.pic:
if self.pic.name.startswith( 'http://') and self.pic.name.endswith(('.png', '.gif', '.jpg', '.jpeg', '.svg')):
my_temp_pic = open('test.image', 'w')
my_temp_pic.write(urllib2.urlopen(self.pic.name).read())
my_temp_pic.close()
my_temp_pic = open('test.image')
thumbnailer = get_thumbnailer(my_temp_pic, relative_name = self.slug+'.'+self.pic.name.split('.')[-1])
self.pic = thumbnailer.get_thumbnail({'size': (200, 0), 'crop': False})
super(Widget, self).save(*args, **kwargs)
I've tried to open the file in different ways with .read() or .open() ... but the only way I found (above) feels quite hackish (save some temp file with the image, re-open, then save). Is there a better way? I'm I missing a straightforward way to do this?
Save the temporary file is the only solution I know too. Check this: http://djangosnippets.org/snippets/1890/
So basically you don't need to do hackish like close() and open() again. You can do:
from django.core.files import File
from django.core.files.temp import NamedTemporaryFile
# ... your code here ...
my_temp_pic = NamedTemporaryFile(delete=True)
my_temp_pic.write(urllib2.urlopen(self.pic.name).read())
my_temp_pic.flush()
relative_name = '%s.%s' % (self.slug, self.pic.name.split('.')[-1])
thumbnailer = get_thumbnailer(my_temp_pic, relative_name=relative_name)
# ... your code again ...
Hope it helps.