104👍
You can pass a function into upload_to
field:
def f(instance, filename):
ext = filename.split('.')[-1]
if instance.pk:
return '{}.{}'.format(instance.pk, ext)
else:
pass
# do something if pk is not there yet
My suggestions would be to return a random filename instead of {pk}.{ext}
. As a bonus, it will be more secure.
What happens is that Django will call this function to determine where the file should be uploaded to. That means that your function is responsible for returning the whole path of the file including the filename. Below is modified function where you can specify where to upload to and how to use it:
import os
from uuid import uuid4
def path_and_rename(path):
def wrapper(instance, filename):
ext = filename.split('.')[-1]
# get filename
if instance.pk:
filename = '{}.{}'.format(instance.pk, ext)
else:
# set filename as random string
filename = '{}.{}'.format(uuid4().hex, ext)
# return the whole path to the file
return os.path.join(path, filename)
return wrapper
FileField(upload_to=path_and_rename('upload/here/'), ...)
32👍
Django 1.7 and newer won’t make migration with function like this. Based on answer by @miki725 and this ticket, you need to make your function like this:
import os
from uuid import uuid4
from django.utils.deconstruct import deconstructible
@deconstructible
class UploadToPathAndRename(object):
def __init__(self, path):
self.sub_path = path
def __call__(self, instance, filename):
ext = filename.split('.')[-1]
# get filename
if instance.pk:
filename = '{}.{}'.format(instance.pk, ext)
else:
# set filename as random string
filename = '{}.{}'.format(uuid4().hex, ext)
# return the whole path to the file
return os.path.join(self.sub_path, filename)
FileField(upload_to=UploadToPathAndRename(os.path.join(MEDIA_ROOT, 'upload', 'here'), ...)
- [Django]-Running ./manage.py migrate during Heroku deployment
- [Django]-Whats the simplest and safest method to generate a API KEY and SECRET in Python
- [Django]-Accessing dictionary by key in Django template
7👍
You can replace the string your assigning to upload_to
with a callable as described in the docs. However, I suspect the primary key may not be available at the point the upload_to
parameter is used.
- [Django]-Can't connect to local MySQL server through socket '/tmp/mysql.sock
- [Django]-Django Cannot set values on a ManyToManyField which specifies an intermediary model. Use Manager instead
- [Django]-Mysql error : ERROR 1018 (HY000): Can't read dir of '.' (errno: 13)
5👍
By default Django keeps the original name of the uploaded file but more than likely you will want to rename it to something else (like the object’s id). Luckily, with ImageField or FileField of Django forms, you can assign a callable function to the upload_to parameter to do the renaming. For example:
from django.db import models
from django.utils import timezone
import os
from uuid import uuid4
def path_and_rename(instance, filename):
upload_to = 'photos'
ext = filename.split('.')[-1]
# get filename
if instance.pk:
filename = '{}.{}'.format(instance.pk, ext)
else:
# set filename as random string
filename = '{}.{}'.format(uuid4().hex, ext)
# return the whole path to the file
return os.path.join(upload_to, filename)
and in models field:
class CardInfo(models.Model):
...
photo = models.ImageField(upload_to=path_and_rename, max_length=255, null=True, blank=True)
In this example, every image that is uploaded will be rename to the CardInfo object’s primary key which is id_number.
- [Django]-How to find out the request.session sessionid and use it as a variable in Django?
- [Django]-Django dump data for a single model?
- [Django]-Convert Django Model object to dict with all of the fields intact
1👍
Another option, as following this answer https://stackoverflow.com/a/15141228/3445802, we found the issue when we need return path with %Y/%m/%d
, example:
FileField(upload_to=path_and_rename('upload/here/%Y/%m/%d'), ...)
so, we handle it with this:
FileField(upload_to=path_and_rename('upload/here/{}'.format(time.strftime("%Y/%m/%d"))), ...)
Makesure the module
time
has been imported.
- [Django]-VueJS + Django Channels
- [Django]-Django's SuspiciousOperation Invalid HTTP_HOST header
- [Django]-Can WordPress be replaced by a Framework like Django or Ruby on Rails?
0👍
I do have a more customizable implementation of Aidan Ewen’s solution.
What’s new?
- You can send the fields you want to use in the filenames as a list (as pre-ordered)
- ^ one of them must be unique
- ^ else, this list must include (at least one of) the tuple of unique together fields
- ^ else, fields you sent are will be ignored and will use the uuid4 as filename
example 1:
image = models.ImageField(upload_to=PathAndRename('images/').wrapper)
filename = {pk}.{ext}
# default is pk for filenames
example 2:
name = models.CharField(max_length=20) # not unique
image = models.ImageField(upload_to=PathAndRename('images/', ['name']).wrapper)
filename = {uuid4}.{ext}
# if given fields are did not accepted will use the uuid4
example 3:
name = models.CharField(max_length=20, unique=True)
no = models.CharField(max_length=10)
image = models.ImageField(upload_to=PathAndRename('images/', ['name','no']).wrapper)
filename = {name}_{no}.{ext}
# one unique field is enough to use all of the given fields in the filename
example 4:
name = models.CharField(max_length=20) # not unique
no = models.CharField(max_length=10) # not unique
image = models.ImageField(upload_to=PathAndRename('images/', ['name','no']).wrapper)
class Meta:
unique_together = ('name', 'no')
# (('name', 'no'),) is acceptable too or multiple unique togethers
filename = {name}_{no}.{ext}
# if one of the unique together fields exists in the given fields, will use all of the given fields in the filename
I may be forgot to give some more examples but you can understand from the code below:
class PathAndRename:
"""
fields to use for naming, order is important
"""
def __init__(self, path, fields_to_use=('pk',)):
self.path = path
self.fields_to_use = fields_to_use
def wrapper(self, instance, filename):
# multiple extensions
ext = '.'.join(filename.split('.')[1:])
# check the uniqueness of the fields given for filename
if self.is_any_unique_exist(instance):
# if any unique field exist in the given list
# create filename by using given field values
filename = '{}.{}'.format(self.get_filename_by_fields(instance), ext)
# else check the existence of at least one unique together
elif self.is_any_unique_together_exist(instance):
# create filename by using given field values
filename = '{}.{}'.format(self.get_filename_by_fields(instance), ext)
# if any unique or unique together not exists
else:
# then create a filename by using uuid4
filename = '{}.{}'.format(uuid4().hex, ext)
# return the whole path to the file
return os.path.join(self.path, filename)
def is_any_unique_exist(self, instance):
if 'pk' in self.fields_to_use:
return True
return any([instance._meta.get_field(field).unique for field in self.fields_to_use if hasattr(instance, field)])
def is_any_unique_together_exist(self, instance):
if hasattr(instance._meta, 'unique_together'):
if isinstance(instance._meta.unique_together, (list, tuple)):
for uniques in instance._meta.unique_together:
# if any one of the unique together set is exists in the fields to use
if all(map(lambda field: field in self.fields_to_use, uniques)):
return True
else:
if all(map(lambda field: field in self.fields_to_use, instance._meta.unique_together)):
return True
return False
def get_filename_by_fields(self, instance):
return '_'.join([str(getattr(instance, field)) for field in self.fields_to_use])
WARNING: Every method-based solution to this upload_to problem is problematic for preformed migration files when you abandon using these solutions. If you use these solutions for a while and then delete them, the old migrations will fail because of the unexistence of these methods. (of course, you can fix this problem by modifying the old migration files)
- [Django]-How to query directly the table created by Django for a ManyToMany relation?
- [Django]-Django, Models & Forms: replace "This field is required" message
- [Django]-How to set and get session in Django?
0👍
I changed the accepted answer a little bit for easy understanding.
def wrapper(instance, filename):
ext = filename.split('.')[-1]
# get filename
if instance.pk:
filename = '{}.{}'.format(instance.pk, ext) # do instance.username
# if you want to save as username
else:
# set filename as random string
filename = '{}.{}'.format(uuid4().hex, ext)
# return the whole path to the file
return os.path.join('path/to/save/', filename)
image = models.ImageField(upload_to=wrapper, default="/user.png", null=True, blank=True)
- [Django]-Django model CharField: max_length does not work?
- [Django]-Add request.GET variable using django.shortcuts.redirect
- [Django]-Turn off automatic pagination of Django Rest Framework ModelViewSet