11from __future__ import annotations
22
3- import importlib
3+ from typing import Protocol
44
5- from django .apps import apps
6- from django .core .files .storage import Storage
75from django .db import transaction
86from PIL import Image
97
10- from pictures import conf
11- from pictures .models import PictureFieldFile
8+ from pictures import conf , utils
129
1310
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 ):
2312
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 : ...
2420
25- process_picture = _process_picture
2621
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 )
2737
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 ()
3541
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 )
4242
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
5544
5645
5746try :
@@ -62,21 +51,25 @@ def process_picture_async(
6251
6352 @dramatiq .actor (queue_name = conf .get_settings ().QUEUE_NAME )
6453 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 ,
6658 ) -> 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 )
7060
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 :
7367 transaction .on_commit (
7468 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 ,
8073 )
8174 )
8275
@@ -92,22 +85,26 @@ def process_picture(field_file: PictureFieldFile) -> None: # noqa: F811
9285 retry_backoff = True ,
9386 )
9487 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 ,
9692 ) -> 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 )
10094
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 :
103101 transaction .on_commit (
104102 lambda : process_picture_with_celery .apply_async (
105103 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 ,
111108 ),
112109 queue = conf .get_settings ().QUEUE_NAME ,
113110 )
@@ -122,20 +119,24 @@ def process_picture(field_file: PictureFieldFile) -> None: # noqa: F811
122119
123120 @job (conf .get_settings ().QUEUE_NAME )
124121 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 ,
126126 ) -> 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 )
130128
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 :
133135 transaction .on_commit (
134136 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 ,
140141 )
141142 )
0 commit comments