Let's say I am selecting 10 files to be uploaded using django-filer. They initially have very random names. I'd like to have a set of rules according to which they ought to be renamed and only then passed for further processing (thumbnails etc.).
I need to actually rename everything, especially filename, not just Image model name.
I tried catching pre_save signal for Image model and altering instance.original_filename but that's not renaming a filename. Or maybe should I subclass and override something from filer package?
I'd be grateful for code example cause this is a little bit to hard for me.
I used form_valid(self, form) in views.py to process and manipulate my images. The complete code is a bit long and very specific, but I'll post a few snipplets which should show the idea of how to generate filenames:
def form_valid(self, form):
upload = self.request.FILES['profilbild_original'] #coming from a very simple form
self.request.user.student.profilbild_original = upload
self.request.user.student.save()
#no renaming was required here, but now I did some work:
inputfilepath = os.path.join(my_app.settings.MEDIA_ROOT, profilbild_path(self.request.user, str(upload)))
original = Image.open(inputfilepath)
original.thumbnail((200,200), Image.ANTIALIAS)
filename = str(upload)+'.thumbnail_200_200_aa.jpg'
filepath = profilbild_path(self.request.user, filename)
filepath = os.path.join(my_app.settings.MEDIA_ROOT, filepath)
original.save(filepath, 'JPEG', quality=90)
self.request.user.student.profilbild = profilbild_path(self.request.user, filename).replace("\\", "/")
self.request.user.student.save()
return super(ProfilbildView, self).form_valid(form)
profilbild_pathis a function according to https://docs.djangoproject.com/en/1.3/ref/models/fields/#django.db.models.FileField.upload_to :
def profilbild_path(instance, filename):
return os.path.join('profilbilder', str(instance.id), filename)
I hope this gives you some clues.
Related
I am reading Django Cookbook 2 and I came across an image upload form that doesn't work as expected. When the user uploads an image it names the image as the date and time at that moment in time.
What I can't figure out is when the form saves, it doesn't actually render to have numbers in it and stays with the %Y/%m characters.
return f"profile/{now:%Y/%m/%Y%m%d%H%M%S}{ext}"
Secondly, why is there an "f", because it keeps throwing an error if I don't remove it.
Views.py
from django.utils.timezome inport now as timezone_now
def upload_to(instance, filename):
now = timezone_now()
base, ext = os.path.splitext(filename)
ext = ext.lower()
return f"profile/{now:%Y/%m/%Y%m%d%H%M%S}{ext}"
I generate a file in python, and want to "upload" that file to the django database. This way it is automatically put inside the media folder, and organized neatly with all other files of my application.
Now here is what I tried: (type hinting used, since it's python 3.6)
# forms.py
class UploadForm(forms.ModelForm):
class Meta:
model = UploadedFile
fields = ('document',)
# models.py
class UploadedFile(models.Model):
document = models.FileField(upload_to=get_upload_path)
# mimetype is generated by filename on save
mimetype = models.CharField(max_length=255)
# ... additional fields like temporary
def get_upload_path(instance: UploadedFile, filename):
if instance.temporary:
return "uploaded_files/temp/" + filename
return "uploaded_files/" + filename
# views.py, file_out has been generated
with open(file_out, 'rb') as local_file:
from django.core.files import File
form = UploadForm(dict(), {'document': File(local_file)})
print(form.errors)
if form.is_valid():
file = form.save(commit=False)
# ... set additional fields
file.save()
form.save_m2m()
return file
Now this is not the only thing I've tried. First I've gone with setting the FileField directly, but that resulted in the save() to fail, while the mimetype field is set. Because the original file sits outside the media folder, and thus a suspicious file action is triggered.
Also, the form gives some feedback about the "upload", through the form.errors.
Depending on my approach, either the save() fails as mentioned above -- meaning the "uploading" does not actually copy the file in the media folder -- or the form returns the error that no file was transmitted, and tells to check the form protocol.
Now my theory is, that I would have to go and initialize my own instance of InMemoryUploadedFile, but I could not figure out how to do that myself, and no documentation was available on the internet.
It feels like I'm taking the wrong approach from the get go. How would one do this properly?
Do you have get_upload_path defined? If not, that would explain the errors you're getting.
From what I can see you're on the right track. If you don't need a dynamic path for your uploads, if you just want them in media/uploads, you can pass in a string value for upload_to (from the Django docs):
# file will be uploaded to MEDIA_ROOT/uploads
document = models.FileField(upload_to='uploads/')
First of all, thanks to Franey for pointing me at storage documentation which lead me to contentfile documentation.
The ContentFile actually solves the problem, because it basically is the self-instantiated version of InMemoryUploadedFile that I was looking for. It's a django File that is not stored on disk.
Here's the full solution:
# views.py, file_out has been generated
with open(file_out, 'rb') as local_file:
from django.core.files.base import ContentFile
# we need to provide a name. Otherwise the Storage.save
# method reveives a None-parameter and breaks.
form = UploadForm(dict(), {'document': ContentFile(local_file.read(), name=name)})
if form.is_valid():
file = form.save(commit=False)
# ... set additional fields
file.save()
form.save_m2m()
return file
I am trying to work with a filestorage in Django. Everything is working fine but a thing in my save method I guess. I have a model with a FileField
download_url = models.FileField(verbose_name = 'Konfig', upload_to = file_path, storage = OverwriteStorage())
In this method in my model I create the file_path
def file_path(instance, filename):
path = os.getcwd() + '/files'
return os.path.join(path, str(instance.download_url), filename)
And the filestorage method I use is outsourced in my storage.py which I import in my models.py
from django.core.files.storage import FileSystemStorage
class OverwriteStorage(FileSystemStorage):
def _save(self, name, content):
if self.exists(name):
self.delete(name)
return super(OverwriteStorage, self)._save(name, content)
def get_available_name(self, name):
return name
Now when I create a new file in the admin interface from django, it successfully uploads the file, makes a database entry with the correct filepath, but it fails to create the right path. When my filename is foo the path would look like following:
cwd/files/foo/foo
and if its name would be bar.txt it would look like following:
cwd/files/bar.txt/bar.txt
I don't want django to create a subdirectory based on the filename. Can you guys help me out ?
Im pretty sure you have to rename the save function from "save" to "_save".
On the Super Call, you used ._save, which isnt the same function as the save function above.
You can read alot about Super here
I'm using Django to create a stock photo site, I have an ImageField in my model, the problem is that when the user updates the image field, the original image file isn't deleted from the hard disk.
How can I delete the old images after an update?
Use django-cleanup
pip install django-cleanup
settings.py
INSTALLED_APPS = (
...
'django_cleanup.apps.CleanupConfig', # should be placed after your apps
)
You'll have to delete the old image manually.
The absolute path to the image is stored in your_image_field.path. So you'd do something like:
os.remove(your_image_field.path)
But, as a convenience, you can use the associated FieldFile object, which gives easy access to the underlying file, as well as providing a few convenience methods. See http://docs.djangoproject.com/en/dev/ref/models/fields/#filefield-and-fieldfile
Use this custom save method in your model:
def save(self, *args, **kwargs):
try:
this = MyModelName.objects.get(id=self.id)
if this.MyImageFieldName != self.MyImageFieldName:
this.MyImageFieldName.delete()
except: pass
super(MyModelName, self).save(*args, **kwargs)
It works for me on my site. This problem was bothering me as well and I didn't want to make a cleanup script instead over good bookkeeping in the first place. Let me know if there are any problems with it.
Before updating the model instance, you can use the delete method of FileField object. For example, if the FileField or ImageField is named as photo and your model instance is profile, then the following will remove the file from disk
profile.photo.delete(False)
For more clarification, here is the django doc
https://docs.djangoproject.com/en/1.11/ref/models/fields/#django.db.models.fields.files.FieldFile.delete
You can define pre_save reciever in models:
#receiver(models.signals.pre_save, sender=UserAccount)
def delete_file_on_change_extension(sender, instance, **kwargs):
if instance.pk:
try:
old_avatar = UserAccount.objects.get(pk=instance.pk).avatar
except UserAccount.DoesNotExist:
return
else:
new_avatar = instance.avatar
if old_avatar and old_avatar.url != new_avatar.url:
old_avatar.delete(save=False)
My avatrs has unique url for each person like "avatars/ceb47779-8833-4719-8711-6f4e5cabb2b2.png". If user upload new image with different extension like jpg, delete_file_on_change_extension reciever remove old image, before save new with url "avatars/ceb47779-8833-4719-8711-6f4e5cabb2b2.jpg" (in this case). If user uploads new image with same extension django overwrite old image on storage (disk), because images paths are the same.
This works fine with AWS S3 django-storage.
Here is an app that deletes orphan files by default: django-smartfields.
It will remove files whenever:
field value was replaced with a new one (either uploaded or set manually)
field is cleared through the form (in case that field is not required, of course)
the model instance itself containing the field is deleted.
It is possible to turn that cleanup feature off using an argument: ImageField(keep_orphans=True) on per field basis, or globally in settings SMARTFIELDS_KEEP_ORPHANS = True.
from django.db import models
from smartfields import fields
class MyModel(models.Model):
image = fields.ImageField()
document = fields.FileField()
try this, it will work even if old file is deleted
def logo_file(instance, filename):
try:
this = business.objects.get(id=instance.id)
if this.logo is not None:
path = "%s" % (this.logo)
os.remove(path)
finally:
pass..
code will work even without "try .. finally" but it will generate problem if file was accidently deleted.
changed: move model matching inside "try" so it will not throw any error at user signup
Let me know if there are any problems.
Completing Chris Lawlor's answer, tried this and works.
from YOURAPP.settings import BASE_DIR
try:
os.remove(BASE_DIR + user.userprofile.avatarURL)
except Exception as e:
pass
The URL has a pattern of /media/mypicture.jpg
What I did is saving the path to the old image and if form is valid I would delete the old one.
if request.method == 'POST':
old_image = ""
if request.user.profile.profile_picture:
old_image = request.user.profile.profile_picture.path
form = UpdateProfileForm(request.POST,request.FILES,instance = profile)
if form.is_valid():
if os.path.exists(old_image):
os.remove(old_image)
form.save()
It is a little messy , but you do not install third parties or anythin
having a similar model:
class Foo(models.Model):
slug = models.SlugField(unique=True)
img = ImageWithThumbnailsField(upload_to='uploads/',thumbnail={'size': (56, 34)})
It works fine but I want to add 2 more features to it:
1- It should also generate a second thumbnail sized 195x123, in addition to 56x34
2- While saving the model original image and it's two thumbnails should be renamed as by using the slug.
For instance
I am uploading 1.jpg and I name slug as "i-like-this-country2"
I should save these named versions should be saved:
1- i-like-this-country2_original.jpg
2- i-like-this-country2_middle.jpg #for 195x123
3- i-like-this-country2_small.jpg #for 56x34
First part:
Just pass it in like this: sizes=( (56,34), (195,123), )
Second part:
You can specify a function for the upload_to which Django will call, passing it an instance of the model and the original filename. With that, you can put together a function that renames the file based on the slug because Django will use whatever you return it instead. Untested code, but something like this:
def _Foo_img_name(instance, filename):
# grab the extension
left_path, extension = self.file.name.rsplit('.',1)
# change the filename then return
return 'uploads/%s.%s' % (instance.slug, extension)
class Foo(models.Model):
img = ImageWithThumbnailsField(upload_to=_Foo_img_name, ... )
I don't believe you can do is change the <filename>_56x34.jpg into anything but that.
All you have to do is to create a method in your models.py like this:
def rename_file(instance, filename):
extension = filename.split(".")[1]
rename = "rename_here"
return rename + "." + extension
Then in the class that extends models.Model
class MyImage(models.Model):
image = ImageField(upload_to=rename_file)
Don't forget to import from sorl.thumbnail import ImageField too!