Django – using instance.id while uploading image
Question:
I was referring to this youtube video, to understand how to upload image using ImageField. He has explained how to use the instance.id
while saving the image. I tried it, but instance.id
is returning None
. Whereas for him it worked perfectly. The following is the code:
#models.py
import os
def get_image_path(instance, filename):
return os.path.join(str(instance.id), filename)
class AdProfile(models.Model):
name = models.CharField(max_length=100)
profile_image = models.ImageField(upload_to=get_image_path, blank=True, null=True)
Whenever the file is saved, its saving as None/filename
.
Even this link informs the same. I am using Django 10.5 and MySQL database.
What might be the problem?
Answers:
Django admin somehow called the get_image_path function without saving the model to database so id is None. We can override django model using save method and make sure image is saved and get_image_path get the instance with id
class AdProfile(models.Model):
name = models.CharField(max_length=100)
profile_image = models.ImageField(upload_to=get_image_path, blank=True, null=True)
# Model Save override
def save(self, *args, **kwargs):
if self.id is None:
saved_image = self.profile_image
self.profile_image = None
super(AdProfile, self).save(*args, **kwargs)
self.profile_image = saved_image
if 'force_insert' in kwargs:
kwargs.pop('force_insert')
super(AdProfile, self).save(*args, **kwargs)
Using Raja Simon’s answer, there is recipe to process all FileField
in the model
class MyModel(models.Model):
file_field = models.FileField(upload_to=upload_to, blank=True, null=True)
def save(self, *args, **kwargs):
if self.id is None:
saved = []
for f in self.__class__._meta.get_fields():
if isinstance(f, models.FileField):
saved.append((f.name, getattr(self, f.name)))
setattr(self, f.name, None)
super(self.__class__, self).save(*args, **kwargs)
for name, val in saved:
setattr(self, name, val)
super(self.__class__, self).save(*args, **kwargs)
Moreover, we can make file location dynamic, i.e. based not only on self.id, but also on id of foreign key or whatever. Just iterate over fields and check if path changed.
def upload_to(o, fn):
if o.parent and o.parent.id:
return parent_upload_to(o.parent, fn)
return "my_temp_dir/{}/{}".format(o.id, fn)
class MyModel(models.Model):
parent = models.ForeignKey(Parent)
def save(self, *args, **kwargs):
# .... code from save() above here
for f in [f for f in self.__class__._meta.get_fields() if isinstance(f, models.FileField)]:
upload_to = f.upload_to
f = getattr(self, f.name) # f is FileField now
if f and callable(upload_to):
_, fn = os.path.split(f.name)
old_name = os.path.normpath(f.name)
new_name = os.path.normpath(upload_to(self, fn))
if old_name != new_name:
old_path = os.path.join(settings.MEDIA_ROOT, old_name)
new_path = os.path.join(settings.MEDIA_ROOT, new_name)
new_dir, _ = os.path.split(new_path)
if not os.path.exists(new_dir):
print "Making dir {}", new_dir
os.makedirs(new_dir)
print "Moving {} to {}".format(old_path, new_path)
try:
os.rename(old_path, new_path)
f.name = new_name
except WindowsError as e:
print "Can not move file, WindowsError: {}".format(e)
super(self.__class__, self).save(*args, **kwargs)
you can create a model instance by passing cleaned data from your form as **kwargs to django model i did it that way & its much easier than anything else
iam naming the path according to owner field which refers to user model of django
in your views post method add this (this code is from my project not adapted to this question)
pk = request.session['_auth_user_id']
user_obj = User.objects.get(pk=pk)
lab_form_instance = lab_form(request.POST,request.FILES)
lab_form_instance.save(commit=False)
# here you can put the form.is_valid() statement
lab_form_instance.cleaned_data['owner'] =user_obj # here iam adding additional needed data for the model
obj = lab(**lab_form_instance.cleaned_data)
obj.save()
*django==4.1
I was referring to this youtube video, to understand how to upload image using ImageField. He has explained how to use the instance.id
while saving the image. I tried it, but instance.id
is returning None
. Whereas for him it worked perfectly. The following is the code:
#models.py
import os
def get_image_path(instance, filename):
return os.path.join(str(instance.id), filename)
class AdProfile(models.Model):
name = models.CharField(max_length=100)
profile_image = models.ImageField(upload_to=get_image_path, blank=True, null=True)
Whenever the file is saved, its saving as None/filename
.
Even this link informs the same. I am using Django 10.5 and MySQL database.
What might be the problem?
Django admin somehow called the get_image_path function without saving the model to database so id is None. We can override django model using save method and make sure image is saved and get_image_path get the instance with id
class AdProfile(models.Model):
name = models.CharField(max_length=100)
profile_image = models.ImageField(upload_to=get_image_path, blank=True, null=True)
# Model Save override
def save(self, *args, **kwargs):
if self.id is None:
saved_image = self.profile_image
self.profile_image = None
super(AdProfile, self).save(*args, **kwargs)
self.profile_image = saved_image
if 'force_insert' in kwargs:
kwargs.pop('force_insert')
super(AdProfile, self).save(*args, **kwargs)
Using Raja Simon’s answer, there is recipe to process all FileField
in the model
class MyModel(models.Model):
file_field = models.FileField(upload_to=upload_to, blank=True, null=True)
def save(self, *args, **kwargs):
if self.id is None:
saved = []
for f in self.__class__._meta.get_fields():
if isinstance(f, models.FileField):
saved.append((f.name, getattr(self, f.name)))
setattr(self, f.name, None)
super(self.__class__, self).save(*args, **kwargs)
for name, val in saved:
setattr(self, name, val)
super(self.__class__, self).save(*args, **kwargs)
Moreover, we can make file location dynamic, i.e. based not only on self.id, but also on id of foreign key or whatever. Just iterate over fields and check if path changed.
def upload_to(o, fn):
if o.parent and o.parent.id:
return parent_upload_to(o.parent, fn)
return "my_temp_dir/{}/{}".format(o.id, fn)
class MyModel(models.Model):
parent = models.ForeignKey(Parent)
def save(self, *args, **kwargs):
# .... code from save() above here
for f in [f for f in self.__class__._meta.get_fields() if isinstance(f, models.FileField)]:
upload_to = f.upload_to
f = getattr(self, f.name) # f is FileField now
if f and callable(upload_to):
_, fn = os.path.split(f.name)
old_name = os.path.normpath(f.name)
new_name = os.path.normpath(upload_to(self, fn))
if old_name != new_name:
old_path = os.path.join(settings.MEDIA_ROOT, old_name)
new_path = os.path.join(settings.MEDIA_ROOT, new_name)
new_dir, _ = os.path.split(new_path)
if not os.path.exists(new_dir):
print "Making dir {}", new_dir
os.makedirs(new_dir)
print "Moving {} to {}".format(old_path, new_path)
try:
os.rename(old_path, new_path)
f.name = new_name
except WindowsError as e:
print "Can not move file, WindowsError: {}".format(e)
super(self.__class__, self).save(*args, **kwargs)
you can create a model instance by passing cleaned data from your form as **kwargs to django model i did it that way & its much easier than anything else
iam naming the path according to owner field which refers to user model of django
in your views post method add this (this code is from my project not adapted to this question)
pk = request.session['_auth_user_id']
user_obj = User.objects.get(pk=pk)
lab_form_instance = lab_form(request.POST,request.FILES)
lab_form_instance.save(commit=False)
# here you can put the form.is_valid() statement
lab_form_instance.cleaned_data['owner'] =user_obj # here iam adding additional needed data for the model
obj = lab(**lab_form_instance.cleaned_data)
obj.save()
*django==4.1