Upload files to same random folder in django - python

I am uploading two files using django rest framework, i want to upload these two files into the same folder and create said folder with a random name.
So far I can upload both files to different random folders using the following:
from uuid import uuid4
def path_and_rename(path, dataset):
def wrapper(instance, filename):
main_folder = '{}/{}/'.format(path, uuid4().hex)
name = '{}.csv'.format(dataset)
return os.path.join(main_folder, name)
return wrapper
class Dataset(Model):
trainFile = FileField(null=False, blank=False,
validators=[FileExtensionValidator(['csv'])],
upload_to=path_and_rename('files/', 'train'))
testFile = FileField(null=False, blank=False,
validators=[FileExtensionValidator(['csv'])],
upload_to=path_and_rename('files/', 'test'))
class DatasetSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Dataset
fields = (
'id',
'trainFile',
'testFile',
)
read_only_fields = ('created',)
How could I get both files to be uploaded to the same random folder?

You are calling path_to_rename(), and in turn uuid4() on two separate occasions so you're going to get two random UUIDs. As a sidenote, for better code readability, your "wrapper" should be the outer function.
Try:
def upload_to_wrapper(upload_dir, dataset):
def path_and_rename(instance, filename):
return os.path.join(upload_dir, '{}.csv'.format(dataset))
return path_and_rename
class Dataset(Model):
upload_dir = 'files/{}'.format(uuid4().hex)
trainFile = FileField(null=False, blank=False,
validators=[FileExtensionValidator(['csv'])],
upload_to=upload_to_wrapper(upload_dir, 'train'))
testFile = FileField(null=False, blank=False,
validators=[FileExtensionValidator(['csv'])],
upload_to=upload_to_wrapper(upload_dir, 'test'))

Since the first FileField is required and the fields will be uploaded in order, you could use an alternate upload_to function for all of your fields apart from the first that relies on the initial upload path. Something like:
import posixpath
def dataset_file(dataset):
def wrapper(instance, filename):
main_folder = posixpath.split(instance.trainFile)[0]
name = '{}.csv'.format(dataset)
return posixpath.join(main_folder, name)
return wrapper
```

Related

how to make use of the file field in django

what else do I need to add to that "file = models.FileField()"
this is what I have done but am still not getting any results, why that?
class Course(models.Model):
TOPIC_CHOICES = (
("History", "History"),
("Chemistry", "Chemistry"),
("Computer", "Computer")
)
lecturer = models.ForeignKey(Lecturer, on_delete=models.CASCADE)
category = models.CharField(choices=TOPIC_CHOICES, max_length=100)
topic = models.CharField(max_length=250)
file = models.FileField()
date_created = models.DateTimeField(default=datetime.now)
def __str__(self):
return f"{self.lecturer}: {self.topic}"
According to Django documentation, FileField takes two optional arguments.
upload_to: Sets the upload directory. The value of this argument can have several types. It can be String, Path, or a callable function. Here is an example:
upload = models.FileField(upload_to='uploads/')
If you want to define a function for this argument which returns the upload directory, you have to define it based on Django's specification of such function. The function should have the instance and filename arguments. Here is an example:
def user_directory_path(instance, filename):
# file will be uploaded to MEDIA_ROOT/user_<id>/<filename>
return 'user_{0}/{1}'.format(instance.user.id, filename)
class MyModel(models.Model):
upload = models.FileField(upload_to=user_directory_path)
storage: A storage object, or a callable which returns a storage object. This argument is used to specify a storage setting for your file-upload field. This argument enables you to choose the appropriate storage environment at runtime.
from django.conf import settings
from django.db import models
from .storages import MyLocalStorage, MyRemoteStorage
def select_storage():
return MyLocalStorage() if settings.DEBUG else MyRemoteStorage()
class MyModel(models.Model):
my_file = models.FileField(storage=select_storage)
Another use-case of this argument is having different storage environments for different types of files.
from django.conf import settings
from django.db import models
from .storages import LargeFilesStorage
class MyModel(models.Model):
my_file = models.FileField(storage=LargeFilesStorage())
As these arguments are optional, you can instantiate a FileField without them. The default values for these arguments are: upload_to='', storage=None

How to output the full path of a FilePathField in Django?

Consider the following model:
STOCK_IMAGE_DIR = os.path.join(settings.MEDIA_ROOT, 'stock_images')
class Product(models.Model):
stock_image = models.FilePathField(path=STOCK_IMAGE_DIR, default='image.png')
When accessing the stock_image of a product, it only returns the name of the image:
>>> p = Product.objects.first()
>>> p.stock_image
'image.png'
How can I output the entire path of the file?
import os
p = Product.objects.first()
print(os.path.join(Product._meta.get_field('stock_image').path, p.stock_image))
Or
print(os.path.join(STOCK_IMAGE_DIR, p.stock_image))
Or, if you store the path in the class you can add a property for getting the full path
class Product(models.Model):
STOCK_IMAGE_DIR = os.path.join(settings.MEDIA_ROOT, 'stock_images')
stock_image = models.FilePathField(path=STOCK_IMAGE_DIR, default='image.png')
#property
def stock_image_path(self):
return os.path.join(self.STOCK_IMAGE_DIR, self.stock_image)
Then you can just use the property
p = Product.objects.first()
print(p.stock_image_path)
While #IainShelvington provides a solution to obtain the path, you might want to use a FileField field [Django-doc], or if you work with images, like the name stock_image suggests, with an ImageField field [Django-doc].
If you model this like:
STOCK_IMAGE_DIR = os.path.join(settings.MEDIA_ROOT, 'stock_images')
class Product(models.Model):
stock_image = models.ImageField(upload_to=STOCK_IMAGE_DIR, default='image.png')
then the some_product.stock_image will be an ImageFieldFile [GitHub]. This is an object that mimics a File, and has for example a path attribute to obtain the path where the file is stored:
some_product.stock_image.path
Furthermore a FileField and ImageField make it more convenient to work with forms where you upload images, and furthermore these have a .url attribute to obtain the URL such that the server can serve media files.

Unable to read Django FileField?

I am trying to read from Django Filefield, as can be seen in my Django Model:
import os
import win32api
from django.db import models
from custom.storage import AzureMediaStorage as AMS
class File(models.Model):
'''
File model
'''
file = models.FileField(blank=False, storage=AMS(), null=False)
timestamp = models.DateTimeField(auto_now_add=True)
remark = models.CharField(max_length=100, default="")
class File_Version(File):
"""
Model containing file version information
"""
version = models.CharField(max_length=25, default="")
#property
def get_version(self):
"""
Read all properties of the given file and return them as a dictionary
"""
props = {'FileVersion': None}
# To check if the file exists ?
### This returns FALSE
print("Is the file there? ", os.path.isfile(str(File.file)) )
# To get file version info
fixedInfo = win32api.GetFileVersionInfo(str(File.file), '\\')
print("FixedInfo: ", fixedInfo)
But os.path.isfile() keeps returning False. How do I read from FileField, into my custom model ?
And moreover, the line fixedInfo, gives me the error:
pywintypes.error: (2, 'GetFileVersionInfo:GetFileVersionInfoSize',
'The system cannot find the file specified.')
os.path.isfile returns whether the filepath is pointing to a file (as opposed to a directory, for instance). File.file is pointing to a models.FileField object; the current code will always return False. I suppose you would want File.file.path to get the actual absolute filepath for the file.
In your model definition you may add:
class File(models.Model):
file = models.FileField()
...
...
def filename(self):
return os.path.basename(self.file.name)
Or you may try:
from django.core.files.storage import default_storage
Use:
1) FieldFile.name:
The name of the file including the relative path from the root of the Storage of the associated FileField.
2) default_storage.exists(path)
Return True if a file referenced by the given name already exists in the
storage system, or False if the name is available for a new file.
Hope this works!
As you are using a different storage provider for your files, you need to use the methods of that storage provider to query the file object.
os.path.isfile and win32api.GetFileVersionInfo only work for the local file system.

Django: how to stop Django from altering filenames (e.g. transforming a space into a _ )?

I'm working on a little Django project and I have a filefield in my models.py.
Everything works fine, but I don't like that Django is editing the filenames after upload. It changes spaces into underscores and it removes (square) brackets and stuff like that.
Is there any way to stop Django from doing this?
I can see that it is to make the website safer, more secure and also just avoid errors. But, I'm the only one who's going to be able to upload files anyway.
Hopefully someone knows if (and how) this is possible :)
Thanks!
edit:
Here's the FileField in the models.py:
file = models.FileField(upload_to=file_path)
Here's my upload_to:
def file_path(instance, filename):
extension = filename.split('.')[-1]
new_filename = '%s - %s.%s' % (str(instance.model.object_number), str(instance.model.object_name), str(extension))
return '/'.join(['files', str(instance.model.object_theme), str(instance.model.object_number), new_filename])
Yes, you can write your own upload_to function, as explained in the documentation: https://docs.djangoproject.com/en/1.9/ref/models/fields/#django.db.models.FileField.upload_to
def user_directory_path(instance, filename):
# file will be uploaded to MEDIA_ROOT/user_<id>/<filename>
return 'user_{0}/{1}'.format(instance.user.id, filename)
class MyModel(models.Model):
upload = models.FileField(upload_to=user_directory_path)
If you want to avoid overwriting files with identical names, you have to add that functionality back. The default storage uses this function django.core.file.storage.get_availble_name()
You can read the source code on github
Edit: It looks as though your upload_to function doesn't return anything. It should return a pathname as a string.
import os
def file_path(instance, filename):
base, extension = os.path.splitext(filename)
return '{model.object_number} - {model.object_name}{extension}'.format(
model=instance.model, extension=extension)
You need to create your own storage class
mystorage.py
import re
from django.utils.encoding import force_text
from django.utils.functional import keep_lazy_text
from django.core.files.storage import FileSystemStorage
#keep_lazy_text
def get_valid_filename(s):
"""
>>> get_valid_filename(" tawanda's portrait in 2019.jpg ")
'tawandas portrait in 2019.jpg'
"""
s = force_text(s).strip()
return re.sub(r'(?u)[^-\w. ]', '', s)
class CleanFileNameStorage(FileSystemStorage):
def get_valid_name(self, name):
"""
Return a filename, based on the provided filename, that's suitable for
use in the target storage system.
"""
return get_valid_filename(name)
If you want to apply this storage to all models you can assign the Django setting DEFAULT_FILE_STORAGE with your custom class e.g
DEFAULT_FILE_STORAGE = 'mystorage.CleanFileNameStorage'
If this is only for a particular model then:
models.py
def myfile_save_to(instance, filename):
return 'my_files/{filename}'.format(filename=filename)
class MyFileModel(models.Model):
my_file = models.FileField(
upload_to=myfile_save_to,
storage=CleanFileNameStorage(),
)
Remember if you specify the storage on a model level you must initialize the class e.g. CleanFileNameStorage() if you don't you will get argument errors

Set ImageField default value to random image out of a list of images in Django

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

Categories