35
35
36
36
if TYPE_CHECKING :
37
37
from collections .abc import Callable
38
+ from collections .abc import Generator
38
39
from collections .abc import Sequence
39
40
from typing import Any
40
41
41
- from django .contrib .auth .models import AbstractBaseUser
42
+ from django .contrib .auth .models import PermissionsMixin as UserWithPermissions
42
43
from django .utils .functional import _StrOrPromise
43
44
44
45
_Model = models .Model
46
+ _Field = models .Field [Any , Any ]
47
+ CharField = models .CharField [str , str ]
48
+ IntegerField = models .IntegerField [int , int ]
49
+ ForeignKey = models .ForeignKey [Any , Any ]
45
50
else :
46
51
_Model = object
52
+ _Field = object
53
+ CharField = models .CharField
54
+ IntegerField = models .IntegerField
55
+ ForeignKey = models .ForeignKey
47
56
48
57
49
58
class TransitionNotAllowed (Exception ):
50
59
"""Raised when a transition is not allowed"""
51
60
52
- def __init__ (self , * args , ** kwargs ) -> None :
61
+ def __init__ (self , * args : Any , ** kwargs : Any ) -> None :
53
62
self .object = kwargs .pop ("object" , None )
54
63
self .method = kwargs .pop ("method" , None )
55
64
super ().__init__ (* args , ** kwargs )
@@ -70,12 +79,12 @@ class ConcurrentTransition(Exception):
70
79
class Transition :
71
80
def __init__ (
72
81
self ,
73
- method : Callable ,
82
+ method : Callable [..., Any ] ,
74
83
source : str | int | Sequence [str | int ] | State ,
75
84
target : str | int | State | None ,
76
85
on_error : str | int | None ,
77
86
conditions : list [Callable [[Any ], bool ]],
78
- permission : str | Callable [[models .Model , AbstractBaseUser ], bool ] | None ,
87
+ permission : str | Callable [[models .Model , UserWithPermissions ], bool ] | None ,
79
88
custom : dict [str , _StrOrPromise ],
80
89
) -> None :
81
90
self .method = method
@@ -90,7 +99,7 @@ def __init__(
90
99
def name (self ) -> str :
91
100
return self .method .__name__
92
101
93
- def has_perm (self , instance , user ) -> bool :
102
+ def has_perm (self , instance , user : UserWithPermissions ) -> bool :
94
103
if not self .permission :
95
104
return True
96
105
if callable (self .permission ):
@@ -113,7 +122,7 @@ def __eq__(self, other):
113
122
return False
114
123
115
124
116
- def get_available_FIELD_transitions (instance , field ) :
125
+ def get_available_FIELD_transitions (instance , field : FSMFieldMixin ) -> Generator [ Transition , None , None ] :
117
126
"""
118
127
List of transitions available in current model state
119
128
with all conditions met
@@ -127,14 +136,16 @@ def get_available_FIELD_transitions(instance, field):
127
136
yield meta .get_transition (curr_state )
128
137
129
138
130
- def get_all_FIELD_transitions (instance , field ) :
139
+ def get_all_FIELD_transitions (instance , field : FSMFieldMixin ) -> Generator [ Transition , None , None ] :
131
140
"""
132
141
List of all transitions available in current model state
133
142
"""
134
143
return field .get_all_transitions (instance .__class__ )
135
144
136
145
137
- def get_available_user_FIELD_transitions (instance , user , field ):
146
+ def get_available_user_FIELD_transitions (
147
+ instance , user : UserWithPermissions , field : FSMFieldMixin
148
+ ) -> Generator [Transition , None , None ]:
138
149
"""
139
150
List of transitions available in current model state
140
151
with all conditions met and user have rights on it
@@ -153,15 +164,24 @@ def __init__(self, field, method) -> None:
153
164
self .field = field
154
165
self .transitions : dict [str , Any ] = {} # source -> Transition
155
166
156
- def get_transition (self , source ):
167
+ def get_transition (self , source : str ):
157
168
transition = self .transitions .get (source , None )
158
169
if transition is None :
159
170
transition = self .transitions .get ("*" , None )
160
171
if transition is None :
161
172
transition = self .transitions .get ("+" , None )
162
173
return transition
163
174
164
- def add_transition (self , method , source , target , on_error = None , conditions = [], permission = None , custom = {}) -> None :
175
+ def add_transition (
176
+ self ,
177
+ method : Callable [..., Any ],
178
+ source : str ,
179
+ target : str | int ,
180
+ on_error : str | int | None = None ,
181
+ conditions : list [Callable [[Any ], bool ]] = [],
182
+ permission : str | Callable [[models .Model , UserWithPermissions ], bool ] | None = None ,
183
+ custom : dict [str , _StrOrPromise ] = {},
184
+ ) -> None :
165
185
if source in self .transitions :
166
186
raise AssertionError (f"Duplicate transition for { source } state" )
167
187
@@ -204,7 +224,7 @@ def conditions_met(self, instance, state) -> bool:
204
224
205
225
return all (condition (instance ) for condition in transition .conditions )
206
226
207
- def has_transition_perm (self , instance , state , user ) -> bool :
227
+ def has_transition_perm (self , instance , state , user : UserWithPermissions ) -> bool :
208
228
transition = self .get_transition (state )
209
229
210
230
if not transition :
@@ -247,10 +267,10 @@ def __set__(self, instance, value) -> None:
247
267
self .field .set_state (instance , value )
248
268
249
269
250
- class FSMFieldMixin (Field ):
270
+ class FSMFieldMixin (_Field ):
251
271
descriptor_class = FSMFieldDescriptor
252
272
253
- def __init__ (self , * args , ** kwargs ) -> None :
273
+ def __init__ (self , * args : Any , ** kwargs : Any ) -> None :
254
274
self .protected = kwargs .pop ("protected" , False )
255
275
self .transitions : dict [Any , dict [str , Any ]] = {} # cls -> (transitions name -> method)
256
276
self .state_proxy = {} # state -> ProxyClsRef
@@ -275,15 +295,15 @@ def deconstruct(self):
275
295
kwargs ["protected" ] = self .protected
276
296
return name , path , args , kwargs
277
297
278
- def get_state (self , instance ):
298
+ def get_state (self , instance ) -> Any :
279
299
# The state field may be deferred. We delegate the logic of figuring this out
280
300
# and loading the deferred field on-demand to Django's built-in DeferredAttribute class.
281
301
return DeferredAttribute (self ).__get__ (instance ) # type: ignore[attr-defined]
282
302
283
- def set_state (self , instance , state ) :
303
+ def set_state (self , instance , state : str ) -> None :
284
304
instance .__dict__ [self .name ] = state
285
305
286
- def set_proxy (self , instance , state ) :
306
+ def set_proxy (self , instance , state : str ) -> None :
287
307
"""
288
308
Change class
289
309
"""
@@ -304,7 +324,7 @@ def set_proxy(self, instance, state):
304
324
305
325
instance .__class__ = model
306
326
307
- def change_state (self , instance , method , * args , ** kwargs ):
327
+ def change_state (self , instance , method , * args : Any , ** kwargs : Any ):
308
328
meta = method ._django_fsm
309
329
method_name = method .__name__
310
330
current_state = self .get_state (instance )
@@ -357,7 +377,7 @@ def change_state(self, instance, method, *args, **kwargs):
357
377
358
378
return result
359
379
360
- def get_all_transitions (self , instance_cls ):
380
+ def get_all_transitions (self , instance_cls ) -> Generator [ Transition , None , None ] :
361
381
"""
362
382
Returns [(source, target, name, method)] for all field transitions
363
383
"""
@@ -384,7 +404,7 @@ def contribute_to_class(self, cls, name, private_only=False, **kwargs):
384
404
385
405
class_prepared .connect (self ._collect_transitions )
386
406
387
- def _collect_transitions (self , * args , ** kwargs ):
407
+ def _collect_transitions (self , * args : Any , ** kwargs : Any ):
388
408
sender = kwargs ["sender" ]
389
409
390
410
if not issubclass (sender , self .base_cls ):
@@ -413,25 +433,25 @@ def is_field_transition_method(attr):
413
433
self .transitions [sender ] = sender_transitions
414
434
415
435
416
- class FSMField (FSMFieldMixin , models . CharField ):
436
+ class FSMField (FSMFieldMixin , CharField ):
417
437
"""
418
438
State Machine support for Django model as CharField
419
439
"""
420
440
421
- def __init__ (self , * args , ** kwargs ) -> None :
441
+ def __init__ (self , * args : Any , ** kwargs : Any ) -> None :
422
442
kwargs .setdefault ("max_length" , 50 )
423
443
super ().__init__ (* args , ** kwargs )
424
444
425
445
426
- class FSMIntegerField (FSMFieldMixin , models . IntegerField ):
446
+ class FSMIntegerField (FSMFieldMixin , IntegerField ):
427
447
"""
428
448
Same as FSMField, but stores the state value in an IntegerField.
429
449
"""
430
450
431
451
pass
432
452
433
453
434
- class FSMKeyField (FSMFieldMixin , models . ForeignKey ):
454
+ class FSMKeyField (FSMFieldMixin , ForeignKey ):
435
455
"""
436
456
State Machine support for Django model
437
457
"""
@@ -496,7 +516,7 @@ class ConcurrentTransitionMixin(_Model):
496
516
state, thus practically negating their effect.
497
517
"""
498
518
499
- def __init__ (self , * args , ** kwargs ) -> None :
519
+ def __init__ (self , * args : Any , ** kwargs : Any ) -> None :
500
520
super ().__init__ (* args , ** kwargs )
501
521
self ._update_initial_state ()
502
522
@@ -534,14 +554,14 @@ def _do_update(self, base_qs, using, pk_val, values, update_fields, forced_updat
534
554
535
555
return updated
536
556
537
- def _update_initial_state (self ):
557
+ def _update_initial_state (self ) -> None :
538
558
self .__initial_states = {field .attname : field .value_from_object (self ) for field in self .state_fields }
539
559
540
- def refresh_from_db (self , * args , ** kwargs ) :
560
+ def refresh_from_db (self , * args : Any , ** kwargs : Any ) -> None :
541
561
super ().refresh_from_db (* args , ** kwargs )
542
562
self ._update_initial_state ()
543
563
544
- def save (self , * args , ** kwargs ) :
564
+ def save (self , * args : Any , ** kwargs : Any ) -> None :
545
565
super ().save (* args , ** kwargs )
546
566
self ._update_initial_state ()
547
567
@@ -552,7 +572,7 @@ def transition(
552
572
target : str | int | State | None = None ,
553
573
on_error : str | int | None = None ,
554
574
conditions : list [Callable [[Any ], bool ]] = [],
555
- permission : str | Callable [[models .Model , AbstractBaseUser ], bool ] | None = None ,
575
+ permission : str | Callable [[models .Model , UserWithPermissions ], bool ] | None = None ,
556
576
custom : dict [str , _StrOrPromise ] = {},
557
577
):
558
578
"""
@@ -576,7 +596,7 @@ def inner_transition(func):
576
596
func ._django_fsm .add_transition (func , source , target , on_error , conditions , permission , custom )
577
597
578
598
@wraps (func )
579
- def _change_state (instance , * args , ** kwargs ):
599
+ def _change_state (instance , * args : Any , ** kwargs : Any ):
580
600
return fsm_meta .field .change_state (instance , func , * args , ** kwargs )
581
601
582
602
if not wrapper_installed :
@@ -587,7 +607,7 @@ def _change_state(instance, *args, **kwargs):
587
607
return inner_transition
588
608
589
609
590
- def can_proceed (bound_method , check_conditions = True ) -> bool :
610
+ def can_proceed (bound_method , check_conditions : bool = True ) -> bool :
591
611
"""
592
612
Returns True if model in state allows to call bound_method
593
613
@@ -604,7 +624,7 @@ def can_proceed(bound_method, check_conditions=True) -> bool:
604
624
return meta .has_transition (current_state ) and (not check_conditions or meta .conditions_met (self , current_state ))
605
625
606
626
607
- def has_transition_perm (bound_method , user ) -> bool :
627
+ def has_transition_perm (bound_method , user : UserWithPermissions ) -> bool :
608
628
"""
609
629
Returns True if model in state allows to call bound_method and user have rights on it
610
630
"""
@@ -628,7 +648,7 @@ def get_state(self, model, transition, result, args=[], kwargs={}):
628
648
629
649
630
650
class RETURN_VALUE (State ):
631
- def __init__ (self , * allowed_states ) -> None :
651
+ def __init__ (self , * allowed_states : Sequence [ str | int ] ) -> None :
632
652
self .allowed_states = allowed_states if allowed_states else None
633
653
634
654
def get_state (self , model , transition , result , args = [], kwargs = {}):
@@ -639,7 +659,7 @@ def get_state(self, model, transition, result, args=[], kwargs={}):
639
659
640
660
641
661
class GET_STATE (State ):
642
- def __init__ (self , func , states = None ) -> None :
662
+ def __init__ (self , func : Callable [..., str | int ], states : Sequence [ str | int ] | None = None ) -> None :
643
663
self .func = func
644
664
self .allowed_states = states
645
665
0 commit comments