Overwrite image located outside django MEDIA_ROOT - python

My models.imageField uploads images to a directory outside of the django directory path. In storages.py I have a function 'upload_to' that sets the directory and filename. It all works perfectly except that I cannot overwrite an existing file with the same name, django simply appends some random text to the file name.
My question is:
How can I overwrite the remote file?
What I have tried:
I have played around with the script suggested here:
https://www.itachay.com/2020/04/override-replace-uploaded-file-in-django.html
Which would work if I was using MEDIA_ROOT, but with my remote file I only ever get the django "SuspiciousFileOperation" warning.
Here is my 'working' code albeit that it won't overwrite any existing file of the same name.
models.py
from .storages import upload_to, remote_storage
class Newsletter(models.Model):
image_1 = models.ImageField(upload_to=upload_to, storage=remote_storage)
storages.py
from django.core.files.storage import FileSystemStorage
def upload_to(instance, filename):
dir_name = 'volume-'+str(instance.pk)
filename = 'image_1'+filename[-4:]
return '%s/%s' % (dir_name, filename)
remote_storage = FileSystemStorage(
location='/Users/some_dir/Documents/dev/mediatest/',
base_url='/Users/some_dir/Documents/dev/mediatest/'
)
My Tries:
TRY 1:
settings.py
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')
MEDIA_TRACK = '/Users/some_dir/Documents/dev/mediatest/'
models.py
from .storages import upload_to, OverwriteStorage
class Newsletter(models.Model):
image_1 = models.ImageField(upload_to=upload_to, storage=OverwriteStorage())
storages.py
FileSystemStorage(
location='/Users/some_dir/Documents/dev/mediatest/',
base_url='/Users/some_dir/Documents/dev/mediatest/'
)
class OverwriteStorage(FileSystemStorage):
def get_available_name(self, name, max_length=None):
if self.exists(name):
os.remove(os.path.join(settings.MEDIA_TRACK, name))
return name
Try 1 Condition:
New record created and image is saved.
Record edited and a new image is selected and saved.
Try 1 RESULT:
Image is saved to the local django directory, not the remote media/volume-1 (no errors)
FileNotFoundError at /admin/newsletter/newsletter/6/change/
[Errno 2] No such file or directory: '/Users/some_dir/Documents/dev/mediatest/volume-1/image_1.png'
TRY 2:
The only change to the above script is to OverwriteStorage()
class OverwriteStorage(FileSystemStorage):
def get_available_name(self, name, max_length=None):
if self.exists(os.path.join(settings.MEDIA_TRACK, name)):
os.remove(os.path.join(settings.MEDIA_TRACK, name))
return name
Try 2 Condition:
Same record edited and image is saved.
Try 2 Result
SuspiciousFileOperation at /admin/newsletter/newsletter/6/change/
The joined path (/Users/some_dir/Documents/dev/mediatest/volume-1/image_1.png) is located outside of the base path component (/Users/some_dir/Documents/dev/backoffice/media)

Related

how to create new upload_to for FileField folder from views.py

in models.py,
class Files(models.Model):
File = models.FileField(upload_to="documents/")
in my views.py, all works perfectly when i remove the 3rd line
file = Files(File=f)
file.File.upload_to('Media/')
file.save()
Question is.
I want to create new upload_to Folder from views.py only. is it even POSSIBLE ?
I know the instance function method of creating Folder used in models.py, i dont want to use it
You can set the name of the file, and thus work with:
from django.conf import settings
from os.path import join as pathjoin
file = Files(File=f)
old_path = car.photo.path
file.File.name = f'Media/{file.File.name}'
new_path = pathjoin(settings.MEDIA_ROOT, file.File.name)
rename(path, new_path)
file.save()
We thus first construct a Files object with f as File, then we check the path, create a new name, and then move the file to the new directory and update the Files object in the database.
You can do this in bulk by enumerating over the Files items and set the file as described in the answer. Instead of saving the object directly, you can collect these in a list and perform a .bulk_update(…) [Django-doc]:
from django.conf import settings
from os.path import join as pathjoin
files = list(Files.objects.all())
for file in files:
old_path = car.photo.path
file.File.name = f'Media/{file.File.name}'
new_path = pathjoin(settings.MEDIA_ROOT, file.File.name)
rename(path, new_path)
Files.objects.bulk_update(files, ['File'])

Trouble with media directory/files on pythonanywhere

I seem to be having an issue serving up media content on my website. Everything works fine when run on localhost. However, when deployed to python anywhere, I receive a FileNotFoundError when I attempt to upload an image via a form.
I've taken a look through overflow for some related topics however I've not found any threads which have allowed me to solve my problem.
Here is the exact error received when submitting the image upload form:
It seems to be an issue with the resize method in models.py (which works fine on localhost)
Here are the appropriate setup files:
settings.py
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
...
MEDIA_DIR = os.path.join(BASE_DIR, 'media')
# Media
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
IMAGES_DIR = os.path.join(MEDIA_URL,"images")
I believe the error is due to the images_path method not retuning the correct location (but it works on localhost). Here is the model which stores the image and defines how images are saved:
models.py
class Image(models.Model):
# this might work?
def images_path():
return os.path.join(settings.IMAGES_DIR, 'usruploads')
def resize(self):
im = PIL.Image.open(self.image)
size=(200,200)
out = im.resize(size)
out.save(self.image.__str__())
def save(self, *args, **kwargs):
super(Image, self).save(*args, **kwargs)
self.resize()
image = models.ImageField(upload_to=images_path()[1:], max_length=255)
I will also throw in the media directory structure of the site in case this info is of use.
This is my first attempt at a deploying a Django web app via python anywhere so hopefully once this issue is fixed, it is a mistake I will never make again.
I have implemented the change you suggestd and the referenced url exists which is now a start however the server is claiming that it does not.
I have printed out the URL and path of the image as follows:
I can go to the index page and enter this url and it loads the image.
Ok, your suggestion was correct uring path instead of __str__() worked. The reason the issue second issue was occurring was then due to the fact that I was opening the image using url and trying to save it using path it did not like this. Thanks for your help!
Given
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
IMAGES_DIR = os.path.join(MEDIA_URL,"images")
and
class Image(models.Model):
# this might work?
def images_path():
return os.path.join(settings.IMAGES_DIR, 'usruploads')
def resize(self):
im = PIL.Image.open(self.image)
size=(200,200)
out = im.resize(size)
out.save(self.image.__str__())
def save(self, *args, **kwargs):
super(Image, self).save(*args, **kwargs)
self.resize()
image = models.ImageField(upload_to=images_path()[1:], max_length=255)
We can infer that
IMAGES_DIR = "/media/images"
and
image = models.ImageField(upload_to='media/images/usruploads', max_length=255)
That means files are uploaded to /<base_dir>/media/media/images/usruploads.
Issue
Looking at your logs, the error happens at the end of your logs.
...
out.save(self.image.__str__())
The issue is self.image.__str__() returns the relative path/filename of the file, and when you pass a relative path to out.save , it will try to save that file in the provided path RELATIVE TO THE CURRENT WORKING DIRECTORY.
Solution
What you have to do instead (assuming you want to replace the original image) is pass the absolute path of the original image:
...
out.save(self.image.path)

Getting the Django-generated unique name of a file uploaded multiple times

I am using DRF backend to upload files. In my specific case I will want to get the name of the file, after it has been uploaded. The reason is that if a user uploads a file with same name, I am still able to process it independently.
views.py:
class ImageUploaderView(viewsets.ModelViewSet):
renderer_classes = [renderers.JSONRenderer]
queryset = ImageUploader.objects.all()
serializer_class = ImageUploaderSerializer
parser_classes = (MultiPartParser,)
serializer.py:
class ImageUploaderSerializer(serializers.ModelSerializer):
class Meta:
model = ImageUploader
models.py:
class ImageUploader(models.Model):
# name=models.ImageField(upload_to='media')
name=models.FileField(upload_to='media')
I tried to put signals and hooks after the model definitions but I am not being able to get this filename. Can someone shed some light pls?
UPDATE: Let me elaborate what I want to achieve essentially:
User1 hits endpoint "/api/calculate_interest_rate" which is rendered
by a frontend React component. "calculate_interest_rate" is served by
DRF, and lets the user upload a CSV file. This will be stored as
"user1.csv", the file is processed and then tabulated (rendered by
React).
At the same time and in parallel to User1, User2 hits the same endpoint "/api/calculate_interest_rate" and
by mistake he saves his file as "user1.csv", and uploads it to the systemn.
So I want to be able to detect both names of the file in order to process it. By using always the same default filename (ex. using the OverwriteStorage() technique), I will probably cause chaos when two or more users are using the same filename. Therefore I am looking into a technique that allows me to get the filename as is, and process it immediately.
How about using storage option?
class OverwriteStorage(FileSystemStorage):
def get_available_name(self, name, max_length=None):
print("filename", name)
#parts = name.split('.') you can separate name and extension.
return super().get_available_name(name)
upload_image = models.ImageField(
upload_to=[yourpath],
default=[defaultname],
storage=OverwriteStorage()
)
I suggest you to following this configuration:
1. Change your MEDIA_ROOT and MEDIA_URL inside file of settings.py
MEDIA_URL = '/media/'
MEDIA_ROOT = '/path/to/env/projectname/media'
2. Then, I suggest you to change your upload_to='media to upload_to='images/%Y/%m/%d, also rename your field of name with image.
class ImageUploader(models.Model):
image = models.FileField(upload_to='images/%Y/%m/%d')
# OR
# image = models.ImageField(upload_to='images/%Y/%m/%d')
Explanation; If you following this configuration, you could have uploaded images are following, eg: /media/images/2017/01/29/yourimage.jpg. This is one way to handle the problem of duplicated files.
3. But if you want to upload file with multiple times without duplicate files, you can using deconstructible;
import os, time, uuid
from django.db import models
from django.utils.deconstruct import deconstructible
class ImageUploader(models.Model):
#deconstructible
class PathAndRename(object):
def __init__(self, sub_path):
self.path = sub_path
def __call__(self, instance, filename):
# eg: filename = 'my uploaded file.jpg'
ext = filename.split('.')[-1] #eg: '.jpg'
uid = uuid.uuid4().hex[:10] #eg: '567ae32f97'
# eg: 'my-uploaded-file'
new_name = '-'.join(filename.replace('.%s' % ext, '').split())
# eg: 'my-uploaded-file_64c942aa64.jpg'
renamed_filename = '%(new_name)s_%(uid)s.%(ext)s' % {'new_name': new_name, 'uid': uid, 'ext': ext}
# eg: 'images/2017/01/29/my-uploaded-file_64c942aa64.jpg'
return os.path.join(self.path, renamed_filename)
image_path = time.strftime('images/%Y/%m/%d')
image = models.ImageField(upload_to=PathAndRename(self.image_path))

Opening a data file from the media directory in Django

I have an application that allows for users to upload CSV files with data which is then graphed and displayed to the user. These files are saved as media within my media folder. In my graph view however I need to open the file and process it. My problem is that I can only open files that are within my project's current working directory and any time that I attempt to upload a file from somewhere outside of that directory I get this error:
File b'TEST.CSV' does not exist
I have attempted this, but without success:
file_upload_dir = os.path.join(settings.MEDIA_ROOT, 'Data_Files')
data_file = open(os.path.join(file_upload_dir, new_file), 'rb')
The variable new_file is only the name of the file saved from in a session and not a path to that file. Data_Files is a directory within the media directory that contains the uploaded files.
My media settings for Django are
SETTINGS_DIR = os.path.dirname(__file__)
PROJECT_PATH = os.path.join(SETTINGS_DIR, os.pardir)
PROJECT_PATH = os.path.abspath(PROJECT_PATH)
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(PROJECT_PATH, 'media')
Is there a way to reference the media files properly from a view?
Here is the output of file_upload_dir and the location of the new file.
>>> print(file_upload_dir)
C:\\Users\\vut46744\\Desktop\\graphite_project\\media\\Data_Files
>>> print(os.path.join(file_upload_dir, new_file))
C:\\Users\\vut46744\\Desktop\\graphite_project\\media\\Data_Files\\TEST.CSV
Normally, you should not access files using open() in a Django app. You should use the storage API. This allows your code to play well with Django settings, and potential third party apps that augment this API.
https://docs.djangoproject.com/en/1.7/topics/files/#file-storage
So here you should be doing something like
from django.core.files.storage import default_storage
f = default_storage.open(os.path.join('Data_Files', new_file), 'r')
data = f.read()
f.close()
print(data)
By the way, if you want it to be modular, it would be a good idea to have a custom storage class, allowing easy configuration and use of your app, should your requirements change. Also, that allows putting files outside of MEDIA_ROOT. This sample storage will put them in settings.UPLOADS_ROOT (and default to MEDIA_ROOT if the setting is not found).
# Put this in a storage.py files in your app
from django.conf import settings
from django.core.files.storage import FileSystemStorage, get_storage_class
from django.utils.functional import LazyObject
class UploadsStorage(FileSystemStorage):
def __init__(self, location=None, base_url=None, *args, **kwargs):
if location is None:
location = getattr(settings, 'UPLOADS_ROOT', None)
super(UploadsStorage, self).__init__(location, base_url, *args, **kwargs)
self.base_url = None # forbid any URL generation for uploads
class ConfiguredStorage(LazyObject):
def _setup(self):
storage = getattr(settings, 'UPLOADS_STORAGE', None)
klass = UploadsStorage if storage is None else get_storage_class(storage)
self._wrapped = klass()
uploads_storage = ConfiguredStorage()
We create a very simple storage here. It's just the regular one, but that reads its files from another directory. Then we set up a lazy object that will allow overriding that storage from settings.
So now your code becomes:
from myapp.storage import uploads_storage
f = uploads_storage.open(new_files, 'r')
And in your settings, you set UPLOADS_ROOT to whatever you like. Probably something outside your media directory. And if someday you decide to store uploads in a database instead, you can set UPLOADS_STORAGE to a database-backed storage, your code will happily use it.

Django, model.FileFieldm upload to dynamicaly generated url and change filename depending on file md5

I need to upload file with Django and put it into users directory with name, generated from files content md5 like this:
hashlib.md5(open('filename.ext').read()).hexdigest()
File model:
class File(models.Model):
name = models.CharField(max_length=255, blank=False)
path = models.FileField(upload_to=get_file_path())
user = models.ForeignKey('User')
def get_file_path():
#get file, calculate users dir(something like settings.UPLOAD_DIR/user_id/),
#calculate files md5 and return generated path
How to access just uploaded file from get_file_path()?
Something like the following should get you started:
from django.conf import settings
hash_dir = hashlib.md5(open('filename.ext').read()).hexdigest()
def get_file_path(*args):
"""
Creates path if does not exists and returns path
E.g. assign_path_to_folder(settings.MEDIA_ROOT, self.name, hash_dir)
"""
path_to_folder = os.path.join(*args)
if not os.path.exists(path_to_folder):
os.makedirs(path_to_folder)
return os.path.join(path_to_folder, '')
The following creates a folder if it doesn't exist, and returns the absolute path to the folder (because MEDIA_ROOT is supposed to be an absolute path).

Categories

Resources