Confused about Django file upload locations - python

When working with manual file uploads, do I need to place the file in the final location before saving it to the model? Or, does the model move the file at some point? If I do need to place it myself, why do I need the upload_to param in the model field? That seems like I would have to keep parity with the upload_to param and the logic I'm using to copy it.
I think I'm just confused. Can someone help me do this right?
My form gets in image url from the web:
class ProductForm(ModelForm):
main_image_url = forms.URLField()
# etc...
My view retrieves it, checks it, and makes a thumbnail:
main_img_temp = NamedTemporaryFile(delete=True)
main_img_temp.write(urllib2.urlopen(main_image_url).read())
main_img_temp.flush()
img_type = imghdr.what(main_img_temp.name)
if not img_type:
errors = form._errors.setdefault("main_image_url", ErrorList())
errors.append(u"Url does not point to a valid image")
return render_to_response('add_image.html', {'form':form}, context_instance=RequestContext(request))
# build a temporary path name
filename = str(uuid.uuid4())
dirname = os.path.dirname(main_img_temp.name)
full_size_tmp = os.path.join(dirname, filename+'_full.jpg')
thumb_size_tmp = os.path.join(dirname, filename+'_thumb.jpg')
shutil.copy2(main_img_temp.name, full_size_tmp)
shutil.copy2(main_img_temp.name, thumb_size_tmp)
# build full size and thumbnail
im = Image.open(full_size_tmp)
im.thumbnail(full_image_size, Image.ANTIALIAS)
im.save(full_size_tmp, "JPEG")
im = Image.open(thumb_size_tmp)
im.thumbnail(thumb_image_size, Image.ANTIALIAS)
im.save(thumb_size_tmp, "JPEG")
# close to delete the original temp file
main_img_tmp.close()
### HERE'S WHERE I'M STUCK. This doesn't move the file... ####
main_image = UploadedImage(image=full_size_tmp, thumbnail=thumb_size_tmp)
main_image.save()
In my models, I've got an UploadedImage model that has the basic fields:
class UploadedImage(models.Model):
image = models.ImageField(upload_to='uploads/images/%Y/%m/%d/full')
thumbnail = models.ImageField(upload_to='uploads/images/%Y/%m/%d/thumb/')

Normally, when you save the model, it writes the files to the location that points upload_to. It handles that by itself so you won't need to do it manually.
Here you are writing the file to a temp, moving it after and a lot of things that can be done automatically. Check the answer to this question where he also uses urllib to get an image and save it into the db.
Note that you can pass a buffer in memory to create a FileField or ImageField which be suitable to do the logic for the thumbnail. Also you may consider using django-thumbnails for this purposes. It's a good library.
Hope this helps!

Answered My Own Question...
When I passed the path to the ImageField in the model, I was just handing it a path. I see now that to invoke all the storage handling built in to Django, you have to hand it a File object. This was enough to make it copy to the upload_to path:
from django.core.files import File
main_image = UploadedImage(image=File(open(full_size_tmp)), thumbnail=File(open(thumb_size_tmp)), creator=request.user)
main_image.save()

Related

How to test image upload on Django Rest Framework

i'm on a struggle. The problem is with the unit testing ("test.py"), and i figured out how to upload images with tempfile and PIL, but those temporary images never get deleted. I think about making a temporary dir and then, with os.remove, delete that temp_dir, but the images upload on different media directorys dependings on the model, so i really don't know how to post temp_images and then delete them.
This is my models.py
class Noticia(models.Model):
...
img = models.ImageField(upload_to="noticias", storage=OverwriteStorage(), default="noticias/tanque_arma3.jpg")
...
test.py
def temporary_image():
import tempfile
from PIL import Image
image = Image.new('RGB', (100, 100))
tmp_file = tempfile.NamedTemporaryFile(suffix='.jpg', prefix="test_img_")
image.save(tmp_file, 'jpeg')
tmp_file.seek(0)
return tmp_file
class NoticiaTest(APITestCase):
def setUp(self):
...
url = reverse('api:noticia-create')
data = {'usuario': usuario.pk, "titulo":"test", "subtitulo":"test", "descripcion":"test", "img": temporary_image()}
response = client.post(url, data,format="multipart")
...
So, to summarize, the question is, ¿How can i delete a temporary file from different directories, taking into account that those files strictly have to be upload on those directorys?
For testing you can use the package dj-inmemorystorage and Django will not save to disk. Serializers and models will still work as expected, and you can read the data back out if needed.
In your settings, when you are in test mode, overwrite the default file storage. You can also put any other "test mode" settings in here, just make sure it runs last, after your other settings.
if 'test' in sys.argv :
# store files in memory, no cleanup after tests are finished
DEFAULT_FILE_STORAGE = 'inmemorystorage.InMemoryStorage'
# much faster password hashing, default one is super slow (on purpose)
PASSWORD_HASHERS = ['django.contrib.auth.hashers.MD5PasswordHasher']
When you are uploading a file you can use SimpleUploadFile, which is purely in-memory. This takes care of the "client" side, while the dj-inmemorystorage package takes care of Django's storage.
def temporary_image():
bts = BytesIO()
img = Image.new("RGB", (100, 100))
img.save(bts, 'jpeg')
return SimpleUploadedFile("test.jpg", bts.getvalue())
def tearDown(self) -> None:
self.Noticia.img.delete()

Image processing after upload with Python Bottle

Context
I have made a simple web app for uploading content to a blog. The front sends AJAX requests (using FormData) to the backend which is Bottle running on Python 3.7. Text content is saved to a MySQL database and images are saved to a folder on the server. Everything works fine.
Image processing and PIL/Pillow
Now, I want to enable processing of uploaded images to standardise them (I need them all resized and/or cropped to 700x400px).
I was hoping to use Pillow for this. My problem is creating a PIL Image object from the file object in Bottle. I cannot initialise a valid Image object.
Code
# AJAX sends request to this route
#post('/update')
def update():
# Form data
title = request.forms.get("title")
body = request.forms.get("body")
image = request.forms.get("image")
author = request.forms.get("author")
# Image upload
file = request.files.get("file")
if file:
extension = file.filename.split(".")[-1]
if extension not in ('png', 'jpg', 'jpeg'):
return {"result" : 0, "message": "File Format Error"}
save_path = "my/save/path"
file.save(save_path)
The problem
This all works as expected, but I cannot create a valid Image object with pillow for processing. I even tried reloading the saved image using the save path but this did not work either.
Other attempts
The code below did not work. It caused an internal server error, though I am having trouble setting up more detailed Python debugging.
path = save_path + "/" + file.filename
image_data = open(path, "rb")
image = Image.open(image_data)
When logged manually, the path is a valid relative URL ("../domain-folder/images") and I have checked that I am definitely importing PIL (Pillow) correctly using PIL.PILLOW_VERSION.
I tried adapting this answer:
image = Image.frombytes('RGBA', (128,128), image_data, 'raw')
However, I won’t know the size until I have created the Image object. I also tried using io:
image = Image.open(io.BytesIO(image_data))
This did not work either. In each case, it is only the line trying to initialise the Image object that causes problems.
Summary
The Bottle documentation says the uploaded file is a file-like object, but I am not having much success in creating an Image object that I can process.
How should I go about this? I do not have a preference about processing before or after saving. I am comfortable with the processing, it is initialising the Image object that is causing the problem.
Edit - Solution
I got this to work by adapting the answer from eatmeimadanish. I had to use a io.BytesIO object to save the file from Bottle, then load it with Pillow from there. After processing, it could be saved in the usual way.
obj = io.BytesIO()
file.save(obj) # This saves the file retrieved by Bottle to the BytesIO object
path = save_path + "/" + file.filename
# Image processing
im = Image.open(obj) # Reopen the object with PIL
im = im.resize((700,400))
im.save(path, optimize=True)
I found this from the Pillow documentation about a different function that may also be of use.
PIL.Image.frombuffer(mode, size, data, decoder_name='raw', *args)
Note that this function decodes pixel data only, not entire images.
If you have an entire image file in a string, wrap it in a BytesIO object, and use open() to load it.
Use StringIO instead.
From PIL import Image
try:
import cStringIO as StringIO
except ImportError:
import StringIO
s = StringIO.StringIO()
#save your in memory file to this instead of a regular file
file = request.files.get("file")
if file:
extension = file.filename.split(".")[-1]
if extension not in ('png', 'jpg', 'jpeg'):
return {"result" : 0, "message": "File Format Error"}
file.save(s)
im = Image.open(s)
im.resize((700,400))
im.save(s, 'png', optimize=True)
s64 = base64.b64encode(s.getvalue())
From what I understand, you're trying to resize the image after it has been saved locally (note that you could try to do the resize before it is saved). If this is what you want to achieve here, you can open the image directly using Pillow, it does the job for you (you do not have to open(path, "rb"):
image = Image.open(path)
image.resize((700,400)).save(path)

Django-admin: images in zip file and inserting each image info in a database

I want to build an image gallery using Django. Each image is a post, of course. Now, I don't want to be uploading each image independently. I want to zip them all and upload them in the Django admin page and maybe create some kind of a trigger to:
decompressed the zip
read all the images info
store the info in a database, each image in a row
Is this possible with Django? what would be your best way to accomplish this? I'll appreciate any kind of help, I'm very new to Django (like 5 hours new)
Yes, it's possible. Here is a broad outline inspired entirely by how Mezzanine implements this.
First you define a field for accepting the zip file:
class BaseGallery(models.Model):
zip_import = models.FileField(blank=True, upload_to=upload_to("galleries")
Then you have a separate model that is Foreign keyed to your parent model. In the example here the parent model is BaseGallery and the image model is GalleryImage:
class GalleryImage(Orderable):
gallery = models.ForeignKey(Gallery, related_name="images")
file = models.ImageField(upload_to="galleries")
Then in your model's save method, you can extract this zip file and save the individual images:
from django.core.files import ContentFile
from django.conf import settings
from zipfile import ZipFile
def save(self, delete_zip_import=True, *args, **kwargs):
"""
If a zip file is uploaded, extract any images from it and add
them to the gallery, before removing the zip file.
"""
super(BaseGallery, self).save(*args, **kwargs)
if self.zip_import:
zip_file = ZipFile(self.zip_import)
for name in zip_file.namelist():
data = zip_file.read(name)
try:
from PIL import Image
image = Image.open(BytesIO(data))
image.load()
image = Image.open(BytesIO(data))
image.verify()
except ImportError:
pass
except:
continue
name = os.path.split(name)[1]
# You now have an image which you can save
path = os.path.join(settings.MEDIA_ROOT, "galleries",
native(str(name, errors="ignore")))
saved_path = default_storage.save(path, ContentFile(data))
self.images.create(file=saved_path)
if delete_zip_import:
zip_file.close()
self.zip_import.delete(save=True)
Note, the bit where the image is actually saved has been simplified and if you look at the source I have linked to there is a bit more jiggery-pokery required to handle unicode file names etc.
Also note that Mezzanine uses its own FileField which isn't the same as Django's FileField. I have tried to refactor this in the example above.
Here's code to extract files from an uploaded ZIP file:
source
import zipfile
localFile = 'beer.zip'
with zipfile.ZipFile(localFile, 'r') as myzip:
myzip.extractall()
print 'got:', [info.filename for info in myzip.infolist()]
reference
Python zipfile module

Downloading Image with urllib.urlretrieve() in Django

I've been attempting to work at this for hours but decided to turn to the experts here on stackoverflow.
I'm trying to download an image from a url:
import urllib
originalphoto = urllib.urlretrieve(bundle.obj.url)
#originalphoto is being saved to the tmp directory in Ubuntu
This works and it saves the image in the tmp directory, but I need to modify this image by resizing it to a 250px by 250px image and then save it to a folder on my Desktop: /home/ubuntu/Desktop/resizedshots
The name of the original image is in bundle.obj.url, for example if bundle.obj.url is:
http://photographs.500px.com/kyle/09-09-201315-47-571378756077.jpg the name of the image is "09-09-201315-47-571378756077.jpg"
After the image is resized, I need to save is to this folder as 09-09-201315-47-571378756077small.jpg
As you can see, I'm adding in the word "small" to the end the file name. Once all of this is done, I would like to delete the temporary image file that was downloaded so that it doesn't take up the disk.
Any ideas on how this can be done?
Thanks
This is the definition:
def urlretrieve(url, filename=None, reporthook=None, data=None):
You can set the second argument to something you know and then do
import os
os.remove(something_you_know)
If you do not set the second argument you do this:
import urllib, os
url = 'http://photographs.500px.com/kyle/09-09-201315-47-571378756077.jpg'
file, headers = urllib.urlretrieve(url)
# do something
os.remove(file)
if os.remove does not work you still have the file open.

changing file name in django

I have a this model...
class MyModel(models.Model):
...
file = models.FileField(upload_to='files/',null=True, blank=True)
...
when i upload a file, example file name is docfile.doc. when i change the file or i rewrite it and upload again docfile.doc the file will become docfile_1.doc and the old docfile.doc is still exist.
i am doing the uploading and saving data in django-admin
my question is, how can i remove the old docfile.doc if i upload the new docfile.doc and the file name is still docfile.doc?
can anyone help me in my case? thanks in advance
i try this one :
def content_file_name(instance, filename):
print instance
print filename
file = os.path.exists(filename)
print file
if file:
os.remove(filename)
return "file/"+str(filename)
class MyModel(models.Model):
...
file = models.FileField(upload_to=content_file_name,null=True, blank=True)
...
but nothing happend, when i upload docfile.doc again, it will become docfile_1.doc and the old docfile.doc still exist.
i got it... i use this
def content_file_name(instance, filename):
print instance
print filename
file = os.path.exists("media/file/"+str(filename))
print file
if file:
os.remove("media/file/"+str(filename))
return "file/"+str(filename)
I don't know exactly how to do it, but i think these links can help you:
Here you can find the two options that a FileField accept. The one that i think will interest you the most is FileField.storage. You can pass a storage object in that parameter.
It says:
FileField.storage: Optional. A storage object, which handles the storage and retrieval of your files.
Then, if you read this you would see that you can write your own storage object. Here is some explanation on how to do it. I think that you could just override the _save method in order to accomplish what you want to do (i.e: if the file already exists, remove it before saving the new copy.)
But be careful! I don't know which is the source of the files you are going to store. Maybe, your app is going to recieve lots of files with the same name, although they are all different. In this case, you would want to use a callable as the FileField.upload_to parameter, so that determine a unique filename for each file your site recieve.
I hope this helps you!
You could also have a look here: ImageField overwrite image file with same name
Define your own storage and overwrite its get available_name method.
The next code solves your problem. You override pre_save method where image is actually saved to storage. Please, rename functions for your project. Use newly created image field ImageFieldWithPermantName with your upload_to function (content_file_name).
If the code is too complicated you could simplify it. I use the code to do more complex operations for uploading images: I create thumbnails on-the-fly in custom _save_image function. So, you can simplify it.
from PIL import Image
import StringIO
from django.db.models import ImageField
from django.db.models.fields.files import FileField
from dargent.settings import MEDIA_ROOT
import os
class ImageFieldWithPermanentName( ImageField ):
def pre_save( self, model_instance, add ):
file = super( FileField, self ).pre_save(model_instance, add)
if file and not file._committed:
if callable( self.upload_to ):
path = self.upload_to( model_instance, "" )
else:
path = self.upload_to
file.name = path # here we set the same name to a file
path = os.path.join( MEDIA_ROOT, path )
chunks = _get_chunks( file.chunks() )
_save_image( chunks, path )
return file
def _get_chunks( chunks ):
chunks_ = ""
for chunk in chunks:
chunks_ += chunk
return chunks_
def _get_image( chunks ):
chunks_ = ""
for chunk in chunks:
chunks_ += chunk
virt_file = StringIO.StringIO( chunks_ )
image = Image.open( virt_file )
return image
def _save_image( chunks, out_file_path ):
image = _get_image( chunks )
image.save( out_file_path, "JPEG", quality = 100 )

Categories

Resources