Sometimes it is desirable to use values like the primary key when naming FileField
and ImageField
files, but such values are only available after saving the model instance. This abstract class implements a two-phase save in order to make this case easy. See the example in the docstring.
Another solution would be to write a save()
that requires upload_to
to be a callable that checks for instance.pk
, then calls it again after saving. However, this would require more work from the developer for simple cases.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | from django.utils.encoding import force_unicode
import os.path
class RenameFilesModel(models.Model):
"""
Abstract model implementing a two-phase save in order to rename
`FileField` and `ImageField` filenames after saving. This allows the
final filenames to contain information like the primary key of the model.
Example:
class Photo(RenameFilesModel):
file = models.ImageField(upload_to='uploads/temp')
RENAME_FILES = {
'file': {'dest': 'uploads/photos', 'keep_ext': True}
}
>>> photo = Photo(file='uploads/temp/photo.jpg')
>>> photo.pk
>>> photo.save()
>>> photo.pk
1
>>> photo.file
<ImageFieldFile: uploads/photos/1.jpg>
If the 'dest' option is a callable, it will be called with the model
instance (guaranteed to be saved) and the currently saved filename, and
must return the new filename. Otherwise, the filename is determined by
'dest' and the model's primary key.
If a file already exists at the resulting path, it is deleted. This is
desirable if the filename should always be the primary key, for instance.
To avoid this behavior, write a 'dest' handler that results in a unique
filename.
If 'keep_ext' is True (the default), the extension of the previously saved
filename will be appended to the primary key to construct the filename.
The value of 'keep_ext' is not considered if 'dest' is a callable.
"""
RENAME_FILES = {}
class Meta:
abstract = True
def save(self, force_insert=False, force_update=False):
rename_files = getattr(self, 'RENAME_FILES', None)
if rename_files:
super(RenameFilesModel, self).save(force_insert, force_update)
force_insert, force_update = False, True
for field_name, options in rename_files.iteritems():
field = getattr(self, field_name)
file_name = force_unicode(field)
name, ext = os.path.splitext(file_name)
keep_ext = options.get('keep_ext', True)
final_dest = options['dest']
if callable(final_dest):
final_name = final_dest(self, file_name)
else:
final_name = os.path.join(final_dest, '%s' % (self.pk,))
if keep_ext:
final_name += ext
if file_name != final_name:
field.storage.delete(final_name)
field.storage.save(final_name, field)
field.storage.delete(file_name)
setattr(self, field_name, final_name)
super(RenameFilesModel, self).save(force_insert, force_update)
|
More like this
- Template tag - list punctuation for a list of items by shapiromatron 11 months, 1 week ago
- JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 11 months, 2 weeks ago
- Serializer factory with Django Rest Framework by julio 1 year, 6 months ago
- Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 7 months ago
- Help text hyperlinks by sa2812 1 year, 7 months ago
Comments
FWIW, this class did not work for me when running Django in Windows XP. Specifically, the field.storage.delete(file_name) call results in Windows "Error 32: The process cannot access the file because it is being used by another process".
#
I have the same problem as grahamu
#
Nice one, thanks!
#
@grahamu @abusquets
To fix that error, you should close the file field before you delete it:
field.close()
#
This almost works for me (using Django 2.0) but when it comes to
field.storage.save(final_name, field)
I getValueError: seek of closed file
. This is using local storage and the development webserver. I'm not sure how to get around this.#
Please login first before commenting.