1
1
from __future__ import annotations
2
2
3
- import importlib
3
+ from typing import Protocol
4
4
5
- from django .apps import apps
6
- from django .core .files .storage import Storage
7
5
from django .db import transaction
8
6
from PIL import Image
9
7
10
- from pictures import conf
11
- from pictures .models import PictureFieldFile
8
+ from pictures import conf , utils
12
9
13
10
14
- def _process_picture (field_file : PictureFieldFile ) -> None :
15
- # field_file.file may already be closed and can't be reopened.
16
- # Therefore, we always open it from storage.
17
- with field_file .storage .open (field_file .name ) as fs :
18
- with Image .open (fs ) as img :
19
- for ratio , sources in field_file .aspect_ratios .items ():
20
- for file_type , srcset in sources .items ():
21
- for width , picture in srcset .items ():
22
- picture .save (img )
11
+ class PictureProcessor (Protocol ):
23
12
13
+ def __call__ (
14
+ self ,
15
+ storage : tuple [str , list , dict ],
16
+ file_name : str ,
17
+ new : list [tuple [str , list , dict ]] | None = None ,
18
+ old : list [tuple [str , list , dict ]] | None = None ,
19
+ ) -> None : ...
24
20
25
- process_picture = _process_picture
26
21
22
+ def _process_picture (
23
+ storage : tuple [str , list , dict ],
24
+ file_name : str ,
25
+ new : list [tuple [str , list , dict ]] | None = None ,
26
+ old : list [tuple [str , list , dict ]] | None = None ,
27
+ ) -> None :
28
+ new = new or []
29
+ old = old or []
30
+ storage = utils .reconstruct (* storage )
31
+ if new :
32
+ with storage .open (file_name ) as fs :
33
+ with Image .open (fs ) as img :
34
+ for picture in new :
35
+ picture = utils .reconstruct (* picture )
36
+ picture .save (img )
27
37
28
- def construct_storage (
29
- storage_cls : str , storage_args : tuple , storage_kwargs : dict
30
- ) -> Storage :
31
- storage_module , storage_class = storage_cls .rsplit ("." , 1 )
32
- storage_cls = getattr (importlib .import_module (storage_module ), storage_class )
33
- return storage_cls (* storage_args , ** storage_kwargs )
34
-
38
+ for picture in old :
39
+ picture = utils .reconstruct (* picture )
40
+ picture .delete ()
35
41
36
- def process_picture_async (
37
- app_name : str , model_name : str , field_name : str , file_name : str , storage_construct
38
- ) -> None :
39
- model = apps .get_model (f"{ app_name } .{ model_name } " )
40
- field = model ._meta .get_field (field_name )
41
- storage = construct_storage (* storage_construct )
42
42
43
- with storage .open (file_name ) as file :
44
- with Image .open (file ) as img :
45
- for ratio , sources in PictureFieldFile .get_picture_files (
46
- file_name = file_name ,
47
- img_width = img .width ,
48
- img_height = img .height ,
49
- storage = storage ,
50
- field = field ,
51
- ).items ():
52
- for file_type , srcset in sources .items ():
53
- for width , picture in srcset .items ():
54
- picture .save (img )
43
+ process_picture : PictureProcessor = _process_picture
55
44
56
45
57
46
try :
@@ -62,21 +51,25 @@ def process_picture_async(
62
51
63
52
@dramatiq .actor (queue_name = conf .get_settings ().QUEUE_NAME )
64
53
def process_picture_with_dramatiq (
65
- app_name , model_name , field_name , file_name , storage_construct
54
+ storage : tuple [str , list , dict ],
55
+ file_name : str ,
56
+ new : list [tuple [str , list , dict ]] | None = None ,
57
+ old : list [tuple [str , list , dict ]] | None = None ,
66
58
) -> None :
67
- process_picture_async (
68
- app_name , model_name , field_name , file_name , storage_construct
69
- )
59
+ _process_picture (storage , file_name , new , old )
70
60
71
- def process_picture (field_file : PictureFieldFile ) -> None : # noqa: F811
72
- opts = field_file .instance ._meta
61
+ def process_picture ( # noqa: F811
62
+ storage : tuple [str , list , dict ],
63
+ file_name : str ,
64
+ new : list [tuple [str , list , dict ]] | None = None ,
65
+ old : list [tuple [str , list , dict ]] | None = None ,
66
+ ) -> None :
73
67
transaction .on_commit (
74
68
lambda : process_picture_with_dramatiq .send (
75
- app_name = opts .app_label ,
76
- model_name = opts .model_name ,
77
- field_name = field_file .field .name ,
78
- file_name = field_file .name ,
79
- storage_construct = field_file .storage .deconstruct (),
69
+ storage = storage ,
70
+ file_name = file_name ,
71
+ new = new ,
72
+ old = old ,
80
73
)
81
74
)
82
75
@@ -92,22 +85,26 @@ def process_picture(field_file: PictureFieldFile) -> None: # noqa: F811
92
85
retry_backoff = True ,
93
86
)
94
87
def process_picture_with_celery (
95
- app_name , model_name , field_name , file_name , storage_construct
88
+ storage : tuple [str , list , dict ],
89
+ file_name : str ,
90
+ new : list [tuple [str , list , dict ]] | None = None ,
91
+ old : list [tuple [str , list , dict ]] | None = None ,
96
92
) -> None :
97
- process_picture_async (
98
- app_name , model_name , field_name , file_name , storage_construct
99
- )
93
+ _process_picture (storage , file_name , new , old )
100
94
101
- def process_picture (field_file : PictureFieldFile ) -> None : # noqa: F811
102
- opts = field_file .instance ._meta
95
+ def process_picture ( # noqa: F811
96
+ storage : tuple [str , list , dict ],
97
+ file_name : str ,
98
+ new : list [tuple [str , list , dict ]] | None = None ,
99
+ old : list [tuple [str , list , dict ]] | None = None ,
100
+ ) -> None :
103
101
transaction .on_commit (
104
102
lambda : process_picture_with_celery .apply_async (
105
103
kwargs = dict (
106
- app_name = opts .app_label ,
107
- model_name = opts .model_name ,
108
- field_name = field_file .field .name ,
109
- file_name = field_file .name ,
110
- storage_construct = field_file .storage .deconstruct (),
104
+ storage = storage ,
105
+ file_name = file_name ,
106
+ new = new ,
107
+ old = old ,
111
108
),
112
109
queue = conf .get_settings ().QUEUE_NAME ,
113
110
)
@@ -122,20 +119,24 @@ def process_picture(field_file: PictureFieldFile) -> None: # noqa: F811
122
119
123
120
@job (conf .get_settings ().QUEUE_NAME )
124
121
def process_picture_with_django_rq (
125
- app_name , model_name , field_name , file_name , storage_construct
122
+ storage : tuple [str , list , dict ],
123
+ file_name : str ,
124
+ new : list [tuple [str , list , dict ]] | None = None ,
125
+ old : list [tuple [str , list , dict ]] | None = None ,
126
126
) -> None :
127
- process_picture_async (
128
- app_name , model_name , field_name , file_name , storage_construct
129
- )
127
+ _process_picture (storage , file_name , new , old )
130
128
131
- def process_picture (field_file : PictureFieldFile ) -> None : # noqa: F811
132
- opts = field_file .instance ._meta
129
+ def process_picture ( # noqa: F811
130
+ storage : tuple [str , list , dict ],
131
+ file_name : str ,
132
+ new : list [tuple [str , list , dict ]] | None = None ,
133
+ old : list [tuple [str , list , dict ]] | None = None ,
134
+ ) -> None :
133
135
transaction .on_commit (
134
136
lambda : process_picture_with_django_rq .delay (
135
- app_name = opts .app_label ,
136
- model_name = opts .model_name ,
137
- field_name = field_file .field .name ,
138
- file_name = field_file .name ,
139
- storage_construct = field_file .storage .deconstruct (),
137
+ storage = storage ,
138
+ file_name = file_name ,
139
+ new = new ,
140
+ old = old ,
140
141
)
141
142
)
0 commit comments