I'd like for my image urls to be /img/1 and /img/2, and I thought using the auto incremented id assigned to every model would be perfect for this, so that every url would be different. The problem is that an instance of a model does not have an id before it is saved. This is an issue in the code below from models.py:
def update_filename(instance, filename):
a = type(instance.id)
if a is not int:
a = 1
else:
a = instance.id
path = "img" + "/" + str(a) + ".jpg"
return path
class User_Image(models.Model):
image = models.ImageField(upload_to=update_filename)
Any suggestions on how to fix this? The id of every instance when django saves the pic is None, so every image saves into my img directory as "None" or "None_1" and so on.
You can't. From the documentation:
In most cases, this object will not have been saved to the database
yet, so if it uses the default AutoField, it might not yet have a
value for its primary key field.
I use function like this:
import uuid
def update_filename(instance, filename):
""" Rename picture """
extension = os.path.splitext(filename)[1]
return 'img/%s%s' % (uuid.uuid4(), extension)
Related
I am trying to replace the images that will be uploaded for a certain ImageField in my models. Now My models are like this:
class Image(models.Model):
image = models.ImageField(upload_to='images/')
def save(self, *args, **kwargs):
# some resizing here which works fine
As you can see, my model saves the file to the 'images/' directory (because i have different image types that need to go in different sub-directories of /media) which will eventually become '/media/images/' due to this setting in my settings.py:
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
Now the problem is where I'm using the receiver method to delete the previously uploaded image.
#receiver(pre_save, sender=Image)
def file_update(sender, **kwargs):
instance = kwargs['instance']
print(instance.image.path) ### HERE IS THE PROBLEM
if instance.image:
path = instance.image.path ### HERE IS THE PROBLEM
os.remove(path)
It's supposed to return this:
/home/.../media/images/file.jpg
But it returns this:
/home/.../media/file.jpg
Which obviously results in a No such file or directory error. What am I missing?
A simple hack would be to do something like this:
path = instance.image.path
name = instance.image.name
correct_path = path.replace(name, 'images/' + name)
But that doesn't answer the question of why it happened or what's the correct way of doing it.
UPDATE:
In case someone is having the same problem, I tried another approach. First, I fetch the object with it's id and then get that path:
instance = kwargs['instance']
if instance.id is not None:
current_image = Image.objects.filter(id=instance.id)[0]
print(current_image.image.path) ### WORKS FINE
os.remove(current_image.image.path) ### WORKS FINE
This approach has two benefits:
The path will be correct.
Replacing the image is guaranteed to work because we are getting the path of previously saved object not calculating based on the newly submitted object (which if not existing, can lead to problems of not having an ID yet).
The only downside is an extra database query.
As Abhyudai indicated: Your 'problem' only occurs in the pre_save. In the post_save you are getting the correct path. I suspect that the correct 'path' is only generated when the file is actually saved (before saving the model it has not been written to the final destination.
However, you can still access the required data in your pre_save:
Absolute path of your media dir:
instance.image.storage.base_location # /home/.../media
The relative path of your upload_to:
instance.image.field.upload_to # images/
Note the .field as we want the field and not the FieldFile normally returned
And of course the name of your file
instance.image.name
Joining all these will give you the path you need to check and delete the existing file, if required
#receiver(pre_save, sender=Image)
def attachment_image_update(sender, **kwargs):
attachment = kwargs['instance']
if attachment.id:
attachment = Image.objects.get(pk=attachment.id)
try:
storage, path = attachment.image.storage, attachment.image.path
storage.delete(path)
except ValueError:
print('Error! The image does not exist')
or
#receiver(pre_save, sender=Image)
def attachment_image_update(sender, **kwargs):
attachment = kwargs['instance']
if attachment.id:
attachment = Image.objects.get(pk=attachment.id)
if attachment.image:
storage, path = attachment.image.storage, attachment.image.path
storage.delete(path)
i'm new to django and sqlite. Is that possible i can get the instance.name value from its foreign key that is related to modeldb? I need to save my pdf file under the same directory as the modeldb class. From the above experiment, im got nothing with modelDB.instance / modelDB.instance.name.
move that upload function outside the class.
instance refer as the your class instance. So you can do like this
instance.modelDB.name
citatiions = models.FileField(...., upload_to=upload) #pass your function name here
check this answerlink
You could do,
def upload(instance, file_name):
return "documents/" + instance.ModelDB.name + "/pdf/" + filename
I'm trying to create a folder and upload an image to in it. My model is similar to this:
class XYZ(models.Model):
def code(self):
syms = ['#','#','$','%','&','*','+','-']
return ''.join(x+random.choice(syms) for x in [self.name[-2:],self.name[2:]])
def make_folder(self):
os.mkdir(os.getcwd()+'/XYZ/'+folder)
def save(self):
self.make_folder()
super(XYZ,self).save(*args,**kwargs)
name = models.CharField(max_length=20)
folder = property(code)
image = models.ImageField(upload_to='HELP_HERE')
I've tried using folder, self.folder, property(code) but it says it isn't defined, how do I access this attribute? Is this the correct approach?
I've also set in the configs.py my MEDIA_URL and MEDIA_ROOT
I think a more simple and understandable way to do this would be to avoid using the inner class function and doing something like this:
# Reference this function outside of your model class.
def xyz_directory_path(instance, filename):
# file will be uploaded to MEDIA_ROOT/xyz_<id>/<filename>
return 'xyz_{0}/{1}'.format(instance.id, filename)
Then, on your models, reference the function in your upload_to field like so:
class XYZ(models.Model):
image = models.ImageField(upload_to=xyz_directory_path)
With your media config set up, this should work. There's a really nice and simple explanation about file handling here.
I have some images inside the static directory and want to create a model that has a ImageField. I want to set the default field to any one of those images. I have tried using this -
def randomImage():
return ImageFile('media/blog/image/' + str(random.randrange(1, 15, 1)) + '.jpg')
# ----------------------- Model for each post in the blog-------------------
class Post(models.Model):
heading = models.CharField(max_length=150)
author = models.ForeignKey(User)
postBody = models.TextField()
postDate = models.DateTimeField('posting date')
postImage = models.ImageField(upload_to='media/blog/image/'+str(int(time.time())), default=randomImage)
Here I'm taking some assumptions,
1. Your default images are inside the static directory
2. Inside the static directory, all files are images
What is the major trick here ?
Django needs a valid file path only to create or update a file entry (image is also a file). So, what we're doing here is, list out all the files (assuming those are only images) and picking up one entry randomly using random.choice() and retun an absoulute path (something like static/my_img.jpg) to the default argument
import time
from random import choice
from os.path import join as path_join
from os import listdir
from os.path import isfile
def random_img():
dir_path = 'static'
files = [content for content in listdir(dir_path) if isfile(path_join(dir_path, content))]
return path_join(dir_path, choice(files))
class Post(models.Model):
# other fields
postImage = models.ImageField(
upload_to='media/blog/image/' + str(int(time.time())),
default=random_img)
UPDATE-1
I've created a minimal example in Django 2.1.1 which can be found in follwing git repo
Repopsitory link -> django2X
1. clone the repository,create a virtual env and install the dependencies (provided a requirements.txt file)
2.create a new superuser or use mine (username -> admin, pass-> jerin123#)
3.run the server and login to django admin : (screenshot-1)
4. Create a new Post instance (again and again)
That's it
UPDATE-2
I've made few changes in my minimal example, which are
1. Created a simple object creation (here, the Post model object) while every django startup (checkout sample/app.py)
2. Added MEDIA_ROOT and MEDIA_URL to the settings.py file
Now, start your project (it will create 3 objects per every startup) and go to http://127.0.0.1:8000/admin/sample/post/. Then open any instance and click on the image link (screenshot-2) and it will open the image (screenshot-3)
My solution is to override the model save method and check if the model is being created for the first time and also check if the postImage image field is empty. If so populate the postImage field with contents of a Radom image. Django will handle the rest
If we use the path of the Radom image directly in our model we will end up like serving some of the post model files from the media folder and some other from the static directory which is not recommended. Instead, we feed the image file content to the postImage field and Django will save the image to media folder and thus we can serve all our model images from media folder itself. Wola
Code
The following code is tested in Python 3.6
Please add the code to your models.py
from pathlib import Path
from random import randint
import time
from django.core.files import File
from django.db import models
allowed_image_extensions = ['.jpg', '.png', '.jpeg']
def get_file_extension(file_path):
return Path(file_path).suffix
def get_files_in_directory(directory, absolute_path=False):
from os import listdir
from os.path import isfile
only_files = [f for f in listdir(directory) if isfile("{}/{}".format(directory, f))]
if not absolute_path:
return only_files
else:
return ["{}/{}".format(directory, file_) for file_ in only_files]
def get_random_image_from_directory(directory_path, image_extension=None):
files_in_directory_path = get_files_in_directory(directory_path, absolute_path=True)
if image_extension:
allowed_file_types = [image_extension]
else:
allowed_file_types = allowed_image_extensions
# filter out files of type other than required file types
filtered_files_list = [_file for _file in files_in_directory_path if
get_file_extension(_file).lower() in allowed_file_types]
random_index = randint(0, len(filtered_files_list) - 1)
random_file_path = filtered_files_list[random_index]
random_file_content = File(open(random_file_path, 'rb'))
return random_file_content
def get_post_image_path(instance, filename):
path_first_component = 'posts'
ext = get_file_extension(filename)
current_time_stamp = int(time.time())
file_name = '{}/posts_{}_{}{}'.format(path_first_component, instance.id, current_time_stamp, ext)
full_path = path_first_component + file_name
return full_path
class Post(models.Model):
heading = models.CharField(max_length=150)
author = models.ForeignKey(User)
postBody = models.TextField()
postDate = models.DateTimeField('posting date')
postImage = models.ImageField(blank=True, null=True, upload_to=get_post_image_path)
# override model save method
def save(self, *args, **kwargs):
# check model is new and postImage is empty
if self.pk is None and not self.postImage:
random_image = get_random_image_from_directory(settings.STATIC_ROOT)
self.postImage = random_image
random_image.close()
super(Post, self).save(*args, **kwargs)
Also no need to set ‘/media’ in upload_to path. Django will read media path from settings variable
The best practice is to move those set of default images out of static directory to another folder probably another folder with name resources or any another meaningful name since the static directory contents will change frequently as the project grows
I have a following Banner class. Which is editable by admin.
class Banner(models.Model):
name = models.CharField(max_length = 128)
link = models.TextField(max_length = 450)
image = models.ImageField(upload_to = 'banner_images')
There are two problems.
When saving image it is saved with original file name. I would like to change it with some unique name so that is not clashed when image with the same name is uploaded again in the specified directory.
While updating the image, the first image file must be deleted. It is not happening...
Any suggestion will be helpful. Thanks in advance.
Try something like this:
from os import rename
class Banner(models.Model):
name = models.CharField(max_length = 128)
link = models.TextField(max_length = 450)
image = models.ImageField(upload_to = 'banner_images')
def save(self):
super(Banner, self).save()
new_filename = <insert code here to change name>
self.image.name = new_filename
rename(static_path+'banner_images/'+self.image, static_path+'banner_images/'+new_filename)
super(Banner, self).save()
I'm not sure if the super(Banner, self).save() called is required twice or not. The 1st might be needed to save the file, and the 2nd one to update the DB record.
1) upload_to can be a callable, on save you can modify it's filename (docs)
2) see https://code.djangoproject.com/ticket/6792, you have to delete it yourself,
Since I was having problem related to savings of image through admin I got following solution which answers all my queries...
First I found that even though admin keeps the original file name, if the file with same name already exists, it keeps on appending a count as a suffix to prevent duplicate file name... for example, if same file is uploaded it is stored as image, image_2, image_3 etc...
Second, while changing image through admin, it was not removing the original file. For that I wrote following code in admin.py. And it does the job well...
Code:
class BannerAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
#Remove the previous file if the object is not new
#and new file supplied.
if obj.id != None and len(request.FILES) > 0:
import os
old_obj = m.Banner.objects.get(id = obj.id)
os.remove(old_obj.image.path)
Hope this helps you if you have got the similar problem.