Use the primary key in FileField and ImageField filenames

 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

  1. Easy file upload handler by mattdw 5 years, 7 months ago
  2. FileField / ImageField with a delete checkbox by tomZ 6 years, 5 months ago
  3. Updated FileField / ImageField with a delete checkbox by tomZ 6 years, 1 month ago
  4. Database file storage by powerfox 5 years, 2 months ago
  5. BigIntegerField and BigAutoField by fnl 5 years, 4 months ago

Comments

grahamu (on December 16, 2008):

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".

#

abusquets (on March 21, 2009):

I have the same problem as grahamu

#

giles (on May 16, 2013):

@grahamu @abusquets

To fix that error, you should close the file field before you delete it: field.close()

#

(Forgotten your password?)