I try to implement pictures uploading and processing in my Django project. The problem is with path for ImageField
:
If I use path relative to the media
folder, then image.url
nicely returns /media/img_name.jpg
, and Django handles the pictures correctly. But I cannot use image.width
property, it leads to the error:
SuspiciousFileOperation
The joined path (/user_test/3d55d527-109d-4a07-b0f6-4de0304d41f6.jpg) is located outside of the base path component (/Users/Aivan/Desktop/Code/MyProject/media)
If I use full path, then I can easily access the image.width
property. But image.url
returns full path added to the media
folder: http://127.0.0.1:8000/media/Users/Aivan/Desktop/Code/MyProject/media/user_test/8f3219cd-0333-4488-9d29-2c5506707afb.jpg
What should I do in order to properly access both image.url
and image.width
?
Of course, I can always set up full path and add save the relative path in some CharField
, or add an own method for relative URL extracting, but probably such solutions are bad.
I guess, that's because Django uses relative paths for media files, but to deal with images it uses Pillow which needs full paths... Maybe it's a bug?
My picture class:
def path_user_photo(instance, filename):
return 'user_{0}'.format(instance.account.user.username)
class Photo(models.Model):
image = models.ImageField(upload_to=path_user_photo)
name = models.CharField(max_length=32, unique=True)
And it's static method for images uploading:
@classmethod
def create_file(cls, fl, user, written_name, ext):
folder = '/user_{0}'.format(user.username)
realname = '/' + str(uuid.uuid4()) + '.' + ext
write_path = settings.MEDIA_ROOT + folder
# Create directory if it doesn't exist
if not os.path.exists(write_path):
os.makedirs(write_path)
# Write data
write_path = write_path + realname
with open(write_path, 'wb+') as destination:
for chunk in fl.chunks():
destination.write(chunk)
res = cls()
# Option 1. Relative path
res.image = folder + realname
# Option 2. Full path
res.image = write_path
res.name = written_name
res.save()
return res
Which I call from a View like this:
Photo.create_file(
# the file
request.FILES['file_img'],
# current user
request.user,
# written name
request.POST['written_name'],
# real file extension
file.name.split('.')[-1]
)
The issue here I think is that res.image = folder + realname
is technically an absolute path because folder
begins with a hardcoded /
.
The way to resolve, is to join the paths together using os.path.join
rather than using a /
explicitly, and then you should be safe to use the relative path when saving the image.
See below - I have commented the modified lines.
import os.path
@classmethod
def create_file(cls, fl, user, written_name, ext):
folder = 'user_{0}'.format(user.username) # Drop the leading slash
realname = str(uuid.uuid4()) + '.' + ext # Drop the leading slash
write_path = os.path.join(settings.MEDIA_ROOT, folder) # Join the paths
# Create directory if it doesn't exist
if not os.path.exists(write_path):
os.makedirs(write_path)
# Write data
write_path = os.path.join(write_path, realname) # Join the path and name
with open(write_path, 'wb+') as destination:
for chunk in fl.chunks():
destination.write(chunk)
res = cls()
# Option 1. Relative path
res.image = os.path.join(folder, realname) # Join the folder and name
res.name = written_name
res.save()
return res