1
1
import base64
2
2
import os
3
3
import uuid
4
+ from string import Formatter
4
5
5
6
try : # use unicode-slugify library if installed
6
7
from slugify import slugify
7
-
8
- SLUGIFY_KWARGS = dict (only_ascii = True )
9
8
except ImportError :
10
9
from django .utils .text import slugify
11
10
12
- SLUGIFY_KWARGS = dict (allow_unicode = False )
11
+
12
+ class SlugFormatter (Formatter ):
13
+
14
+ def format_field (self , value , format_spec ):
15
+ if format_spec == 'slug' :
16
+ return slugify (value )
17
+ return super ().format_field (value = value , format_spec = format_spec )
18
+
19
+
20
+ class ExtendedUUID (uuid .UUID ):
21
+
22
+ def __format__ (self , format_spec ):
23
+ if format_spec == '' :
24
+ return str (self )
25
+ if format_spec == 's' :
26
+ return str (self )
27
+ if format_spec == 'i' :
28
+ return str (self .int )
29
+ if format_spec == 'x' :
30
+ return self .hex .lower ()
31
+ if format_spec == 'X' :
32
+ return self .hex .upper ()
33
+ if format_spec == 'base32' :
34
+ return base64 .b32encode (
35
+ self .bytes
36
+ ).decode ('utf-8' ).rstrip ('=\n ' )
37
+ if format_spec == 'base64' :
38
+ return base64 .urlsafe_b64encode (
39
+ self .bytes
40
+ ).decode ('utf-8' ).rstrip ('=\n ' )
41
+ return super ().__format__ (format_spec )
13
42
14
43
15
44
class FilePattern :
@@ -23,7 +52,7 @@ class FilePattern:
23
52
from django.db import models
24
53
from dynamic_names import FilePattern
25
54
26
- upload_to_pattern = FilePattern('{app_name:.25}/{model_name:.30}/{uuid_base32 }{ext}')
55
+ upload_to_pattern = FilePattern('{app_name:.25}/{model_name:.30}/{uuid:base32 }{ext}')
27
56
28
57
class FileModel(models.Model):
29
58
my_file = models.FileField(upload_to=upload_to_pattern)
@@ -34,11 +63,13 @@ class FileModel(models.Model):
34
63
name: Filename excluding the folders.
35
64
model_name: Name of the Django model.
36
65
app_label: App label of the Django model.
37
- uuid_base16: Base16 (hex) representation of a UUID.
38
- uuid_base32: Base32 representation of a UUID.
39
- uuid_base64: Base64 representation of a UUID. Not supported by all file systems.
40
- slug: Auto created slug based on another field on the model instance.
41
- slug_from: Name of the field the slug should be populated from.
66
+ uuid:
67
+ UUID version 4 that supports multiple type specifiers. The UUID will be
68
+ the same should you use it twice in the same string, but different on each
69
+ invocation of the ``upload_to`` callable.
70
+ instance:
71
+ Instance of the model before it has been saved.
72
+ You may not have a primary key at this point.
42
73
43
74
44
75
Auto slug example:
@@ -48,45 +79,38 @@ class FileModel(models.Model):
48
79
from django.db import models
49
80
from dynamic_names import FilePattern
50
81
51
- class SlugPattern (FilePattern):
52
- filename_pattern = '{app_name:.25}/{model_name:.30}/{slug}{ext}'
82
+ class TitleSlugPattern (FilePattern):
83
+ filename_pattern = '{app_name:.25}/{model_name:.30}/{instance.title: slug}{ext}'
53
84
54
85
class FileModel(models.Model):
55
86
title = models.CharField(max_length=100)
56
- my_file = models.FileField(upload_to=SlugPattern(slug_from='title' ))
87
+ my_file = models.FileField(upload_to=TitleSlugPattern( ))
57
88
58
89
"""
59
90
60
- slug_from = None
91
+ formatter = SlugFormatter ()
61
92
62
93
filename_pattern = '{name}{ext}'
63
94
64
95
def __call__ (self , instance , filename ):
65
96
"""Return filename based for given instance and filename."""
66
97
# UUID needs to be set on call, not per instance to avoid state leakage.
67
- guid = self .get_uuid ()
68
98
path , ext = os .path .splitext (filename )
69
99
path , name = os .path .split (path )
70
100
defaults = {
71
101
'ext' : ext ,
72
102
'name' : name ,
73
103
'model_name' : instance ._meta .model_name ,
74
104
'app_label' : instance ._meta .app_label ,
75
- 'uuid_base10' : self .uuid_2_base10 (guid ),
76
- 'uuid_base16' : self .uuid_2_base16 (guid ),
77
- 'uuid_base32' : self .uuid_2_base32 (guid ),
78
- 'uuid_base64' : self .uuid_2_base64 (guid ),
105
+ 'uuid' : self .get_uuid (),
106
+ 'instance' : instance ,
79
107
}
80
108
defaults .update (self .override_values )
81
- if self .slug_from is not None :
82
- field_value = getattr (instance , self .slug_from )
83
- defaults ['slug' ] = slugify (field_value , ** SLUGIFY_KWARGS )
84
- return self .filename_pattern .format (** defaults )
109
+ return self .formatter .format (self .filename_pattern , ** defaults )
85
110
86
111
def __init__ (self , ** kwargs ):
87
112
self .kwargs = kwargs
88
113
override_values = kwargs .copy ()
89
- self .slug_from = override_values .pop ('slug_from' , self .slug_from )
90
114
self .filename_pattern = override_values .pop ('filename_pattern' , self .filename_pattern )
91
115
self .override_values = override_values
92
116
@@ -98,34 +122,4 @@ def deconstruct(self):
98
122
@staticmethod
99
123
def get_uuid ():
100
124
"""Return UUID version 4."""
101
- return uuid .uuid4 ()
102
-
103
- @staticmethod
104
- def uuid_2_base10 (uuid ):
105
- """Return 39 digits long integer UUID as Base10."""
106
- return uuid .int
107
-
108
- @staticmethod
109
- def uuid_2_base16 (uuid ):
110
- """Return 32 char long UUID as Base16 (hex)."""
111
- return uuid .hex
112
-
113
- @staticmethod
114
- def uuid_2_base32 (uuid ):
115
- """Return 27 char long UUIDv4 as Base32."""
116
- return base64 .b32encode (
117
- uuid .bytes
118
- ).decode ('utf-8' ).rstrip ('=\n ' )
119
-
120
- @staticmethod
121
- def uuid_2_base64 (uuid ):
122
- """
123
- Return 23 char long UUIDv4 as Base64.
124
-
125
- .. warning:: Not all file systems support Base64 file names.
126
- e.g. The Apple File System (APFS) is case insensitive by default.
127
-
128
- """
129
- return base64 .urlsafe_b64encode (
130
- uuid .bytes
131
- ).decode ('utf-8' ).rstrip ('=\n ' )
125
+ return ExtendedUUID (bytes = os .urandom (16 ), version = 4 )
0 commit comments