changing file name in django - python

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 )

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

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

Corrupted Image upload from Django

This is my code for uploading image that I have defined in a class based view,
def _handle_uploaded_file(self, request):
folder = settings.MEDIA_ROOT
uploaded_filename = request.FILES['img_fl'].name
BASE_PATH ='/home/admin1/Desktop/virtualenv-12.1.1/mapfied'
# create the folder if it doesn't exist.
try:
os.mkdir(os.path.join(BASE_PATH, folder))
except Exception as e:
pass
# save the uploaded file inside that folder.
full_filename = os.path.join(BASE_PATH, folder, uploaded_filename)
fd = open(full_filename, 'wb')
file_content = ContentFile( request.FILES['img_fl'].read() )
try:
for chunk in file_content.chunks():
fout.write(chunk)
fout.close()
html = "<html><body>SAVED</body></html>"
print(html)
except Exception as e:
print(e)
The image file is getting saved to correct location with name, but it is corrupted.I am unable to find the exact reason for this , Am I doing something wrong here?
This is what I had from a previous project for writing upload files to disk:
def view_handling_function(request):
for key, value in request.FILES.iteritems():
full_path = ...
save_uploadfile_to_disk(full_path, value)
def save_uploadfile_to_disk(full_path, file):
with open(full_path, 'w+') as destination:
for chunk in file.chunks():
destination.write(chunk)
I think since you're looking to write a binary upload you need to open the file with writable binary mode which is actually wb+.
You could also tidy up a bit by using the 'with' keyword; see Django example here.
Sidenote: if you're persisting the file as a FileField (or a derived class) you could just provide the 'upload_to' function that returns the full path and file name for where you'd like to store the file. That'll let the framework take care of the file io for you.

Confused about Django file upload locations

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

ZipExtFile to Django File

I am wondering whether there is a way to upload a zip file to django web server and put the zip's files into django database WITHOUT accessing the actual file system in the process (e.g. extracting the files in the zip into a tmp dir and then load them)
Django provides a function to convert python File to Django File, so if there is a way to convert ZipExtFile to python File, it should be fine.
thanks for help!
Django model:
from django.db import models
class Foo:
file = models.FileField(upload_to='somewhere')
Usage:
from zipfile import ZipFile
from django.core.exceptions import ValidationError
from django.core.files import File
from io import BytesIO
z = ZipFile('zipFile')
istream = z.open('subfile')
ostream = BytesIO(istream.read())
tmp = Foo(file=File(ostream))
try:
tmp.full_clean()
except Validation, e:
print e
Output:
{'file': [u'This field cannot be blank.']}
[SOLUTION] Solution using an ugly hack:
As correctly pointed out by Don Quest, file-like classes such as StringIO or BytesIO should represent the data as a virtual file. However, Django File's constructor only accepts the build-in file type and nothing else, although the file-like classes would have done the job as well. The hack is to set the variables in Django::File manually:
buf = bytesarray(OPENED_ZIP_OBJECT.read(FILE_NAME))
tmp_file = BytesIO(buf)
dummy_file = File(tmp_file) # this line actually fails
dummy_file.name = SOME_RANDOM_NAME
dummy_file.size = len(buf)
dummy_file.file = tmp_file
# dummy file is now valid
Please keep commenting if you have a better solution (except for custom storage)
There's an easier way to do this:
from django.core.files.base import ContentFile
uploaded_zip = zipfile.ZipFile(uploaded_file, 'r') # ZipFile
for filename in uploaded_zip.namelist():
with uploaded_zip.open(filename) as f: # ZipExtFile
my_django_file = ContentFile(f.read())
Using this, you can convert a file that was uploaded to memory directly to a django file. For a more complete example, let's say you wanted to upload a series of image files inside of a zip to the file system:
# some_app/models.py
class Photo(models.Model):
image = models.ImageField(upload_to='some/upload/path')
...
# Upload code
from some_app.models import Photo
for filename in uploaded_zip.namelist():
with uploaded_zip.open(filename) as f: # ZipExtFile
new_photo = Photo()
new_photo.image.save(filename, ContentFile(f.read(), save=True)
Without knowing to much about Django, i can tell you to take a look at the "io" package.
You could do something like:
from zipfile import ZipFile
from io import StringIO
zname,zipextfile = 'zipcontainer.zip', 'file_in_archive'
istream = ZipFile(zname).open(zipextfile)
ostream = StringIO(istream.read())
And then do whatever you would like to do with your "virtual" ostream Stream/File.
I've used the following django file class to avoid the need to read ZipExtFile into a another datastructure (StingIO or BytesIO) while properly impelementing what Django needs in order to save the file directly.
from django.core.files.base import File
class DjangoZipExtFile(File):
def __init__(self, zipextfile, zipinfo):
self.file = zipextfile
self.zipinfo = zipinfo
self.mode = 'r'
self.name = zipinfo.filename
self._size = zipinfo.file_size
def seek(self, position):
if position != 0:
#this will raise an unsupported operation
return self.file.seek(position)
#TODO if we have already done a read, reopen file
zipextfile = archive.open(path, 'r')
zipinfo = archive.getinfo(path)
djangofile = DjangoZipExtFile(zipextfile, zipinfo)
storage = DefaultStorage()
result = storage.save(djangofile.name, djangofile)

Categories

Resources