1
1
"""
2
2
State tracking functionality for django models
3
3
"""
4
+ from __future__ import annotations
5
+
4
6
import inspect
5
- from functools import partialmethod , wraps
7
+ from functools import partialmethod
8
+ from functools import wraps
6
9
7
- import django
8
10
from django .apps import apps as django_apps
9
11
from django .db import models
10
12
from django .db .models import Field
11
13
from django .db .models .query_utils import DeferredAttribute
12
14
from django .db .models .signals import class_prepared
13
15
14
- from django_fsm .signals import pre_transition , post_transition
15
-
16
+ from django_fsm .signals import post_transition
17
+ from django_fsm . signals import pre_transition
16
18
17
19
__all__ = [
18
20
"TransitionNotAllowed" ,
@@ -251,26 +253,9 @@ def deconstruct(self):
251
253
return name , path , args , kwargs
252
254
253
255
def get_state (self , instance ):
254
- # The state field may be deferred. We delegate the logic of figuring
255
- # this out and loading the deferred field on-demand to Django's
256
- # built-in DeferredAttribute class. DeferredAttribute's instantiation
257
- # signature changed over time, so we need to check Django version
258
- # before proceeding to call DeferredAttribute. An alternative to this
259
- # would be copying the latest implementation of DeferredAttribute to
260
- # django_fsm, but this comes with the added responsibility of keeping
261
- # the copied code up to date.
262
- if django .VERSION [:3 ] >= (3 , 0 , 0 ):
263
- return DeferredAttribute (self ).__get__ (instance )
264
- elif django .VERSION [:3 ] >= (2 , 1 , 0 ):
265
- return DeferredAttribute (self .name ).__get__ (instance )
266
- elif django .VERSION [:3 ] >= (1 , 10 , 0 ):
267
- return DeferredAttribute (self .name , model = None ).__get__ (instance )
268
- else :
269
- # The field was either not deferred (in which case we can return it
270
- # right away) or ir was, but we are running on an unknown version
271
- # of Django and we do not know the appropriate DeferredAttribute
272
- # interface, and accessing the field will raise KeyError.
273
- return instance .__dict__ [self .name ]
256
+ # The state field may be deferred. We delegate the logic of figuring this out
257
+ # and loading the deferred field on-demand to Django's built-in DeferredAttribute class.
258
+ return DeferredAttribute (self ).__get__ (instance )
274
259
275
260
def set_state (self , instance , state ):
276
261
instance .__dict__ [self .name ] = state
@@ -435,14 +420,15 @@ def set_state(self, instance, state):
435
420
instance .__dict__ [self .attname ] = self .to_python (state )
436
421
437
422
438
- class FSMModelMixin ( object ) :
423
+ class FSMModelMixin :
439
424
"""
440
425
Mixin that allows refresh_from_db for models with fsm protected fields
441
426
"""
442
427
443
428
def _get_protected_fsm_fields (self ):
444
429
def is_fsm_and_protected (f ):
445
430
return isinstance (f , FSMFieldMixin ) and f .protected
431
+
446
432
protected_fields = filter (is_fsm_and_protected , self ._meta .concrete_fields )
447
433
return {f .attname for f in protected_fields }
448
434
@@ -455,13 +441,13 @@ def refresh_from_db(self, *args, **kwargs):
455
441
protected_fields = self ._get_protected_fsm_fields ()
456
442
skipped_fields = deferred_fields .union (protected_fields )
457
443
458
- fields = [f .attname for f in self ._meta .concrete_fields
459
- if f .attname not in skipped_fields ]
444
+ fields = [f .attname for f in self ._meta .concrete_fields if f .attname not in skipped_fields ]
445
+
446
+ kwargs ["fields" ] = fields
447
+ super ().refresh_from_db (* args , ** kwargs )
460
448
461
- kwargs ['fields' ] = fields
462
- super (FSMModelMixin , self ).refresh_from_db (* args , ** kwargs )
463
449
464
- class ConcurrentTransitionMixin ( object ) :
450
+ class ConcurrentTransitionMixin :
465
451
"""
466
452
Protects a Model from undesirable effects caused by concurrently executed transitions,
467
453
e.g. running the same transition multiple times at the same time, or running different
0 commit comments