Skip to content

Commit 1ff71ac

Browse files
authored
Drop upload_to utils in favor of django-dynamic-filenames (#201)
1 parent b660a02 commit 1ff71ac

File tree

6 files changed

+43
-194
lines changed

6 files changed

+43
-194
lines changed

README.md

Lines changed: 17 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ A variation can be defined both as a tuple or a dictionary.
3939

4040
Example:
4141
```python
42+
from django.db import models
4243
from stdimage.models import StdImageField
4344

4445

@@ -59,7 +60,7 @@ class MyModel(models.Model):
5960
})
6061

6162
## Full ammo here. Please note all the definitions below are equal
62-
image = StdImageField(upload_to=upload_to, blank=True, variations={
63+
image = StdImageField(upload_to='path/to/img', blank=True, variations={
6364
'large': (600, 400),
6465
'thumbnail': (100, 100, True),
6566
'medium': (300, 200),
@@ -74,36 +75,11 @@ Example:
7475
```
7576

7677
### Utils
77-
By default StdImageField stores images without modifying the file name.
78-
If you want to use more consistent file names you can use the build in upload callables.
79-
80-
Example:
81-
```python
82-
from stdimage.utils import UploadToUUID, UploadToClassNameDir, UploadToAutoSlug, \
83-
UploadToAutoSlugClassNameDir
84-
85-
86-
class MyClass(models.Model):
87-
title = models.CharField(max_length=50)
88-
89-
# Gets saved to MEDIA_ROOT/myclass/#FILENAME#.#EXT#
90-
image1 = StdImageField(upload_to=UploadToClassNameDir())
91-
92-
# Gets saved to MEDIA_ROOT/myclass/pic.#EXT#
93-
image2 = StdImageField(upload_to=UploadToClassNameDir(name='pic'))
9478

95-
# Gets saved to MEDIA_ROOT/images/#UUID#.#EXT#
96-
image3 = StdImageField(upload_to=UploadToUUID(path='images'))
79+
Since version 4 the custom `upload_to` utils have been dropped in favor of
80+
[Django Dynamic Filenames][dynamic_filenames].
9781

98-
# Gets saved to MEDIA_ROOT/myclass/#UUID#.#EXT#
99-
image4 = StdImageField(upload_to=UploadToClassNameDirUUID())
100-
101-
# Gets save to MEDIA_ROOT/images/#SLUG#.#EXT#
102-
image5 = StdImageField(upload_to=UploadToAutoSlug(populate_from='title'))
103-
104-
# Gets save to MEDIA_ROOT/myclass/#SLUG#.#EXT#
105-
image6 = StdImageField(upload_to=UploadToAutoSlugClassNameDir(populate_from='title'))
106-
```
82+
[dynamic_filenames]: https://github.com/codingjoe/django-dynamic-filenames
10783

10884
### Validators
10985
The `StdImageField` doesn't implement any size validation. Validation can be specified using the validator attribute
@@ -112,10 +88,12 @@ Validators can be used for both Forms and Models.
11288

11389
Example
11490
```python
91+
from django.db import models
11592
from stdimage.validators import MinSizeValidator, MaxSizeValidator
93+
from stdimage.models import StdImageField
11694

11795

118-
class MyClass(models.Model)
96+
class MyClass(models.Model):
11997
image1 = StdImageField(validators=[MinSizeValidator(800, 600)])
12098
image2 = StdImageField(validators=[MaxSizeValidator(1028, 768)])
12199
```
@@ -133,11 +111,14 @@ Clearing the field if blank is true, does not delete the file. This can also be
133111
This packages contains two signal callback methods that handle file deletion for all SdtImageFields of a model.
134112

135113
```python
114+
from django.db.models.signals import pre_delete, pre_save
136115
from stdimage.utils import pre_delete_delete_callback, pre_save_delete_callback
137116

117+
from . import models
138118

139-
post_delete.connect(pre_delete_delete_callback, sender=MyModel)
140-
pre_save.connect(pre_save_delete_callback, sender=MyModel)
119+
120+
pre_delete.connect(pre_delete_delete_callback, sender=models.MyModel)
121+
pre_save.connect(pre_save_delete_callback, sender=models.MyModel)
141122
```
142123

143124
**Warning:** You should not use the signal callbacks in production. They may result in data loss.
@@ -156,7 +137,7 @@ try:
156137
from django.apps import apps
157138
get_model = apps.get_model
158139
except ImportError:
159-
from django.db.models.loading import get_model
140+
from django.apps import apps
160141

161142
from celery import shared_task
162143

@@ -166,7 +147,7 @@ from stdimage.utils import render_variations
166147
@shared_task
167148
def process_photo_image(file_name, variations, storage):
168149
render_variations(file_name, variations, replace=True, storage=storage)
169-
obj = get_model('myapp', 'Photo').objects.get(image=file_name)
150+
obj = apps.get_model('myapp', 'Photo').objects.get(image=file_name)
170151
obj.processed = True
171152
obj.save()
172153
```
@@ -175,18 +156,17 @@ def process_photo_image(file_name, variations, storage):
175156
```python
176157
from django.db import models
177158
from stdimage.models import StdImageField
178-
from stdimage.utils import UploadToClassNameDir
179159

180160
from tasks import process_photo_image
181161

182162
def image_processor(file_name, variations, storage):
183163
process_photo_image.delay(file_name, variations, storage)
184164
return False # prevent default rendering
185165

186-
class AsyncImageModel(models.Model)
166+
class AsyncImageModel(models.Model):
187167
image = StdImageField(
188168
# above task definition can only handle one model object per image filename
189-
upload_to=UploadToClassNameDir(),
169+
upload_to='path/to/file/',
190170
render_variations=image_processor # pass boolean or callable
191171
)
192172
processed = models.BooleanField(default=False) # flag that could be used for view querysets

stdimage/utils.py

Lines changed: 0 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,8 @@
1-
import os
2-
import uuid
3-
41
from django.core.files.storage import default_storage
5-
from django.utils.text import slugify
62

73
from .models import StdImageField, StdImageFieldFile
84

95

10-
class UploadTo:
11-
file_pattern = "%(name)s%(ext)s"
12-
path_pattern = "%(path)s"
13-
14-
def __call__(self, instance, filename):
15-
path, ext = os.path.splitext(filename)
16-
path, name = os.path.split(path)
17-
defaults = {
18-
'ext': ext,
19-
'name': name,
20-
'path': path,
21-
'class_name': instance.__class__.__name__,
22-
}
23-
defaults.update(self.kwargs)
24-
return os.path.join(self.path_pattern % defaults,
25-
self.file_pattern % defaults).lower()
26-
27-
def __init__(self, *args, **kwargs):
28-
self.kwargs = kwargs
29-
self.args = args
30-
31-
def deconstruct(self):
32-
path = "%s.%s" % (self.__class__.__module__, self.__class__.__name__)
33-
return path, self.args, self.kwargs
34-
35-
36-
class UploadToUUID(UploadTo):
37-
38-
def __call__(self, instance, filename):
39-
self.kwargs.update({
40-
'name': uuid.uuid4().hex,
41-
})
42-
return super().__call__(instance, filename)
43-
44-
45-
class UploadToClassNameDir(UploadTo):
46-
path_pattern = '%(class_name)s'
47-
48-
49-
class UploadToClassNameDirUUID(UploadToClassNameDir, UploadToUUID):
50-
pass
51-
52-
53-
class UploadToAutoSlug(UploadTo):
54-
55-
def __init__(self, populate_from, **kwargs):
56-
self.populate_from = populate_from
57-
super().__init__(populate_from, **kwargs)
58-
59-
def __call__(self, instance, filename):
60-
field_value = getattr(instance, self.populate_from)
61-
self.kwargs.update({
62-
'name': slugify(field_value),
63-
})
64-
return super().__call__(instance, filename)
65-
66-
67-
class UploadToAutoSlugClassNameDir(UploadToClassNameDir, UploadToAutoSlug):
68-
pass
69-
70-
716
def pre_delete_delete_callback(sender, instance, **kwargs):
727
for field in instance._meta.fields:
738
if isinstance(field, StdImageField):

tests/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,6 @@ def imagedata():
1818
@pytest.fixture
1919
def image_upload_file(imagedata):
2020
return SimpleUploadedFile(
21-
'testfile.jpg',
21+
'image.jpg',
2222
imagedata.getvalue()
2323
)

tests/models.py

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,29 +9,30 @@
99
from stdimage import StdImageField
1010
from stdimage.models import StdImageFieldFile
1111
from stdimage.utils import (
12-
UploadTo, UploadToAutoSlugClassNameDir, UploadToUUID,
1312
pre_delete_delete_callback, pre_save_delete_callback, render_variations
1413
)
1514
from stdimage.validators import MaxSizeValidator, MinSizeValidator
1615

16+
upload_to = 'img/'
17+
1718

1819
class SimpleModel(models.Model):
1920
"""works as ImageField"""
20-
image = StdImageField(upload_to='img/')
21+
image = StdImageField(upload_to=upload_to)
2122

2223

2324
class AdminDeleteModel(models.Model):
2425
"""can be deleted through admin"""
2526
image = StdImageField(
26-
upload_to=UploadTo(name='image', path='img'),
27+
upload_to=upload_to,
2728
blank=True
2829
)
2930

3031

3132
class ResizeModel(models.Model):
3233
"""resizes image to maximum size to fit a 640x480 area"""
3334
image = StdImageField(
34-
upload_to=UploadTo(name='image', path='img'),
35+
upload_to=upload_to,
3536
variations={
3637
'medium': {'width': 400, 'height': 400},
3738
'thumbnail': (100, 75),
@@ -42,54 +43,43 @@ class ResizeModel(models.Model):
4243
class ResizeCropModel(models.Model):
4344
"""resizes image to 640x480 cropping if necessary"""
4445
image = StdImageField(
45-
upload_to=UploadTo(name='image', path='img'),
46+
upload_to=upload_to,
4647
variations={'thumbnail': (150, 150, True)}
4748
)
4849

4950

5051
class ThumbnailModel(models.Model):
5152
"""creates a thumbnail resized to maximum size to fit a 100x75 area"""
5253
image = StdImageField(
53-
upload_to=UploadTo(name='image', path='img'),
54+
upload_to=upload_to,
5455
blank=True,
5556
variations={'thumbnail': (100, 75)}
5657
)
5758

5859

5960
class MaxSizeModel(models.Model):
6061
image = StdImageField(
61-
upload_to=UploadTo(name='image', path='img'),
62+
upload_to=upload_to,
6263
validators=[MaxSizeValidator(16, 16)]
6364
)
6465

6566

6667
class MinSizeModel(models.Model):
6768
image = StdImageField(
68-
upload_to=UploadTo(name='image', path='img'),
69+
upload_to=upload_to,
6970
validators=[MinSizeValidator(200, 200)]
7071
)
7172

7273

7374
class ForceMinSizeModel(models.Model):
7475
"""creates a thumbnail resized to maximum size to fit a 100x75 area"""
7576
image = StdImageField(
76-
upload_to=UploadTo(name='image', path='img'),
77+
upload_to=upload_to,
7778
force_min_size=True,
7879
variations={'thumbnail': (600, 600)}
7980
)
8081

8182

82-
class AutoSlugClassNameDirModel(models.Model):
83-
name = models.CharField(max_length=50)
84-
image = StdImageField(
85-
upload_to=UploadToAutoSlugClassNameDir(populate_from='name')
86-
)
87-
88-
89-
class UUIDModel(models.Model):
90-
image = StdImageField(upload_to=UploadToUUID(path='img'))
91-
92-
9383
class CustomManager(models.Manager):
9484
"""Just like Django's default, but a different class."""
9585
pass
@@ -105,7 +95,7 @@ class Meta:
10595
class ManualVariationsModel(CustomManagerModel):
10696
"""delays creation of 150x150 thumbnails until it is called manually"""
10797
image = StdImageField(
108-
upload_to=UploadTo(name='image', path='img'),
98+
upload_to=upload_to,
10999
variations={'thumbnail': (150, 150, True)},
110100
render_variations=False
111101
)
@@ -114,7 +104,7 @@ class ManualVariationsModel(CustomManagerModel):
114104
class MyStorageModel(CustomManagerModel):
115105
"""delays creation of 150x150 thumbnails until it is called manually"""
116106
image = StdImageField(
117-
upload_to=UploadTo(name='image', path='img'),
107+
upload_to=upload_to,
118108
variations={'thumbnail': (150, 150, True)},
119109
storage=FileSystemStorage(),
120110
)
@@ -128,7 +118,7 @@ def render_job(**kwargs):
128118
class UtilVariationsModel(models.Model):
129119
"""delays creation of 150x150 thumbnails until it is called manually"""
130120
image = StdImageField(
131-
upload_to=UploadTo(name='image', path='img'),
121+
upload_to=upload_to,
132122
variations={'thumbnail': (150, 150, True)},
133123
render_variations=render_job
134124
)
@@ -169,7 +159,7 @@ class CustomRenderVariationsModel(models.Model):
169159
"""Use custom render_variations."""
170160

171161
image = StdImageField(
172-
upload_to=UploadTo(name='image', path='img'),
162+
upload_to=upload_to,
173163
variations={'thumbnail': (150, 150)},
174164
render_variations=custom_render_variations,
175165
)

0 commit comments

Comments
 (0)