Skip to content

Commit 5e24a14

Browse files
committed
Merge branch 'adamchainz-drop_old_pythons'
2 parents a1eb871 + 1e981d7 commit 5e24a14

19 files changed

+73
-154
lines changed

.travis.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ sudo: false
44
cache: pip
55

66
python:
7-
- 2.7
8-
- 3.6
97
- 3.7
108
- 3.8
119
- 3.9

CHANGELOG.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ Changelog
44
django-fsm unreleased
55
~~~~~~~~~~~~~~~~~~~~~~~~~~~
66

7+
- Drop support for Python < 3.7.
78
- add support for django 4.2
89
- add support for python 3.11
910

@@ -12,7 +13,6 @@ django-fsm 2.8.1 2022-08-15
1213

1314
- Improve fix for get_available_FIELD_transition
1415

15-
1616
django-fsm 2.8.0 2021-11-05
1717
~~~~~~~~~~~~~~~~~~~~~~~~~~
1818

README.rst

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,6 @@ Or, for the latest git version
4848
4949
$ pip install -e git://github.com/kmmbvnr/django-fsm.git#egg=django-fsm
5050
51-
The library has full Python 3 support
52-
5351
Usage
5452
-----
5553

django_fsm/__init__.py

Lines changed: 39 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
# -*- coding: utf-8 -*-
21
"""
32
State tracking functionality for django models
43
"""
54
import inspect
6-
import sys
7-
from functools import wraps
5+
from functools import partialmethod, wraps
86

97
import django
108
from django.db import models
@@ -13,12 +11,6 @@
1311
from django.db.models.signals import class_prepared
1412
from django_fsm.signals import pre_transition, post_transition
1513

16-
try:
17-
from functools import partialmethod
18-
except ImportError:
19-
# python 2.7, so we are on django<=1.11
20-
from django.utils.functional import curry as partialmethod
21-
2214
try:
2315
from django.apps import apps as django_apps
2416

@@ -46,25 +38,6 @@ def get_model(app_label, model_name):
4638
"RETURN_VALUE",
4739
]
4840

49-
if sys.version_info[:2] == (2, 6):
50-
# Backport of Python 2.7 inspect.getmembers,
51-
# since Python 2.6 ships buggy implementation
52-
def __getmembers(object, predicate=None):
53-
"""Return all members of an object as (name, value) pairs sorted by name.
54-
Optionally, only return members that satisfy a given predicate."""
55-
results = []
56-
for key in dir(object):
57-
try:
58-
value = getattr(object, key)
59-
except AttributeError:
60-
continue
61-
if not predicate or predicate(value):
62-
results.append((key, value))
63-
results.sort()
64-
return results
65-
66-
inspect.getmembers = __getmembers
67-
6841
# South support; see http://south.aeracode.org/docs/tutorial/part4.html#simple-inheritance
6942
try:
7043
from south.modelsinspector import add_introspection_rules
@@ -82,7 +55,7 @@ class TransitionNotAllowed(Exception):
8255
def __init__(self, *args, **kwargs):
8356
self.object = kwargs.pop("object", None)
8457
self.method = kwargs.pop("method", None)
85-
super(TransitionNotAllowed, self).__init__(*args, **kwargs)
58+
super().__init__(*args, **kwargs)
8659

8760

8861
class InvalidResultState(Exception):
@@ -97,7 +70,7 @@ class ConcurrentTransition(Exception):
9770
"""
9871

9972

100-
class Transition(object):
73+
class Transition:
10174
def __init__(self, method, source, target, on_error, conditions, permission, custom):
10275
self.method = method
10376
self.source = source
@@ -155,7 +128,7 @@ def get_available_user_FIELD_transitions(instance, user, field):
155128
yield transition
156129

157130

158-
class FSMMeta(object):
131+
class FSMMeta:
159132
"""
160133
Models methods transitions meta information
161134
"""
@@ -174,7 +147,7 @@ def get_transition(self, source):
174147

175148
def add_transition(self, method, source, target, on_error=None, conditions=[], permission=None, custom={}):
176149
if source in self.transitions:
177-
raise AssertionError("Duplicate transition for {0} state".format(source))
150+
raise AssertionError(f"Duplicate transition for {source} state")
178151

179152
self.transitions[source] = Transition(
180153
method=method,
@@ -226,20 +199,20 @@ def next_state(self, current_state):
226199
transition = self.get_transition(current_state)
227200

228201
if transition is None:
229-
raise TransitionNotAllowed("No transition from {0}".format(current_state))
202+
raise TransitionNotAllowed(f"No transition from {current_state}")
230203

231204
return transition.target
232205

233206
def exception_state(self, current_state):
234207
transition = self.get_transition(current_state)
235208

236209
if transition is None:
237-
raise TransitionNotAllowed("No transition from {0}".format(current_state))
210+
raise TransitionNotAllowed(f"No transition from {current_state}")
238211

239212
return transition.on_error
240213

241214

242-
class FSMFieldDescriptor(object):
215+
class FSMFieldDescriptor:
243216
def __init__(self, field):
244217
self.field = field
245218

@@ -250,14 +223,14 @@ def __get__(self, instance, type=None):
250223

251224
def __set__(self, instance, value):
252225
if self.field.protected and self.field.name in instance.__dict__:
253-
raise AttributeError("Direct {0} modification is not allowed".format(self.field.name))
226+
raise AttributeError(f"Direct {self.field.name} modification is not allowed")
254227

255228
# Update state
256229
self.field.set_proxy(instance, value)
257230
self.field.set_state(instance, value)
258231

259232

260-
class FSMFieldMixin(object):
233+
class FSMFieldMixin:
261234
descriptor_class = FSMFieldDescriptor
262235

263236
def __init__(self, *args, **kwargs):
@@ -277,10 +250,10 @@ def __init__(self, *args, **kwargs):
277250
self.state_proxy[state] = proxy_cls_ref
278251
kwargs["choices"] = choices
279252

280-
super(FSMFieldMixin, self).__init__(*args, **kwargs)
253+
super().__init__(*args, **kwargs)
281254

282255
def deconstruct(self):
283-
name, path, args, kwargs = super(FSMFieldMixin, self).deconstruct()
256+
name, path, args, kwargs = super().deconstruct()
284257
if self.protected:
285258
kwargs["protected"] = self.protected
286259
return name, path, args, kwargs
@@ -326,7 +299,7 @@ def set_proxy(self, instance, state):
326299

327300
model = get_model(app_label, model_name)
328301
if model is None:
329-
raise ValueError("No model found {0}".format(state_proxy))
302+
raise ValueError(f"No model found {state_proxy}")
330303

331304
instance.__class__ = model
332305

@@ -337,13 +310,13 @@ def change_state(self, instance, method, *args, **kwargs):
337310

338311
if not meta.has_transition(current_state):
339312
raise TransitionNotAllowed(
340-
"Can't switch from state '{0}' using method '{1}'".format(current_state, method_name),
313+
f"Can't switch from state '{current_state}' using method '{method_name}'",
341314
object=instance,
342315
method=method,
343316
)
344317
if not meta.conditions_met(instance, current_state):
345318
raise TransitionNotAllowed(
346-
"Transition conditions have not been met for method '{0}'".format(method_name), object=instance, method=method
319+
f"Transition conditions have not been met for method '{method_name}'", object=instance, method=method
347320
)
348321

349322
next_state = meta.next_state(current_state)
@@ -398,15 +371,15 @@ def get_all_transitions(self, instance_cls):
398371
def contribute_to_class(self, cls, name, **kwargs):
399372
self.base_cls = cls
400373

401-
super(FSMFieldMixin, self).contribute_to_class(cls, name, **kwargs)
374+
super().contribute_to_class(cls, name, **kwargs)
402375
setattr(cls, self.name, self.descriptor_class(self))
403-
setattr(cls, "get_all_{0}_transitions".format(self.name), partialmethod(get_all_FIELD_transitions, field=self))
376+
setattr(cls, f"get_all_{self.name}_transitions", partialmethod(get_all_FIELD_transitions, field=self))
404377
setattr(
405-
cls, "get_available_{0}_transitions".format(self.name), partialmethod(get_available_FIELD_transitions, field=self)
378+
cls, f"get_available_{self.name}_transitions", partialmethod(get_available_FIELD_transitions, field=self)
406379
)
407380
setattr(
408381
cls,
409-
"get_available_user_{0}_transitions".format(self.name),
382+
f"get_available_user_{self.name}_transitions",
410383
partialmethod(get_available_user_FIELD_transitions, field=self),
411384
)
412385

@@ -448,7 +421,7 @@ class FSMField(FSMFieldMixin, models.CharField):
448421

449422
def __init__(self, *args, **kwargs):
450423
kwargs.setdefault("max_length", 50)
451-
super(FSMField, self).__init__(*args, **kwargs)
424+
super().__init__(*args, **kwargs)
452425

453426

454427
class FSMIntegerField(FSMFieldMixin, models.IntegerField):
@@ -471,7 +444,7 @@ def set_state(self, instance, state):
471444
instance.__dict__[self.attname] = self.to_python(state)
472445

473446

474-
class ConcurrentTransitionMixin(object):
447+
class ConcurrentTransitionMixin:
475448
"""
476449
Protects a Model from undesirable effects caused by concurrently executed transitions,
477450
e.g. running the same transition multiple times at the same time, or running different
@@ -498,7 +471,7 @@ class ConcurrentTransitionMixin(object):
498471
"""
499472

500473
def __init__(self, *args, **kwargs):
501-
super(ConcurrentTransitionMixin, self).__init__(*args, **kwargs)
474+
super().__init__(*args, **kwargs)
502475
self._update_initial_state()
503476

504477
@property
@@ -513,9 +486,9 @@ def _do_update(self, base_qs, using, pk_val, values, update_fields, forced_updat
513486
filter_on = filter(lambda field: field.model == base_qs.model, self.state_fields)
514487

515488
# state filter will be used to narrow down the standard filter checking only PK
516-
state_filter = dict((field.attname, self.__initial_states[field.attname]) for field in filter_on)
489+
state_filter = {field.attname: self.__initial_states[field.attname] for field in filter_on}
517490

518-
updated = super(ConcurrentTransitionMixin, self)._do_update(
491+
updated = super()._do_update(
519492
base_qs=base_qs.filter(**state_filter),
520493
using=using,
521494
pk_val=pk_val,
@@ -536,14 +509,14 @@ def _do_update(self, base_qs, using, pk_val, values, update_fields, forced_updat
536509
return updated
537510

538511
def _update_initial_state(self):
539-
self.__initial_states = dict((field.attname, field.value_from_object(self)) for field in self.state_fields)
512+
self.__initial_states = {field.attname: field.value_from_object(self) for field in self.state_fields}
540513

541514
def refresh_from_db(self, *args, **kwargs):
542-
super(ConcurrentTransitionMixin, self).refresh_from_db(*args, **kwargs)
515+
super().refresh_from_db(*args, **kwargs)
543516
self._update_initial_state()
544517

545518
def save(self, *args, **kwargs):
546-
super(ConcurrentTransitionMixin, self).save(*args, **kwargs)
519+
super().save(*args, **kwargs)
547520
self._update_initial_state()
548521

549522

@@ -588,36 +561,34 @@ def can_proceed(bound_method, check_conditions=True):
588561
conditions.
589562
"""
590563
if not hasattr(bound_method, "_django_fsm"):
591-
im_func = getattr(bound_method, "im_func", getattr(bound_method, "__func__"))
592-
raise TypeError("%s method is not transition" % im_func.__name__)
564+
raise TypeError("%s method is not transition" % bound_method.__func__.__name__)
593565

594566
meta = bound_method._django_fsm
595-
im_self = getattr(bound_method, "im_self", getattr(bound_method, "__self__"))
596-
current_state = meta.field.get_state(im_self)
567+
self = bound_method.__self__
568+
current_state = meta.field.get_state(self)
597569

598-
return meta.has_transition(current_state) and (not check_conditions or meta.conditions_met(im_self, current_state))
570+
return meta.has_transition(current_state) and (not check_conditions or meta.conditions_met(self, current_state))
599571

600572

601573
def has_transition_perm(bound_method, user):
602574
"""
603575
Returns True if model in state allows to call bound_method and user have rights on it
604576
"""
605577
if not hasattr(bound_method, "_django_fsm"):
606-
im_func = getattr(bound_method, "im_func", getattr(bound_method, "__func__"))
607-
raise TypeError("%s method is not transition" % im_func.__name__)
578+
raise TypeError("%s method is not transition" % bound_method.__func__.__name__)
608579

609580
meta = bound_method._django_fsm
610-
im_self = getattr(bound_method, "im_self", getattr(bound_method, "__self__"))
611-
current_state = meta.field.get_state(im_self)
581+
self = bound_method.__self__
582+
current_state = meta.field.get_state(self)
612583

613584
return (
614585
meta.has_transition(current_state)
615-
and meta.conditions_met(im_self, current_state)
616-
and meta.has_transition_perm(im_self, current_state, user)
586+
and meta.conditions_met(self, current_state)
587+
and meta.has_transition_perm(self, current_state, user)
617588
)
618589

619590

620-
class State(object):
591+
class State:
621592
def get_state(self, model, transition, result, args=[], kwargs={}):
622593
raise NotImplementedError
623594

@@ -629,7 +600,7 @@ def __init__(self, *allowed_states):
629600
def get_state(self, model, transition, result, args=[], kwargs={}):
630601
if self.allowed_states is not None:
631602
if result not in self.allowed_states:
632-
raise InvalidResultState("{} is not in list of allowed states\n{}".format(result, self.allowed_states))
603+
raise InvalidResultState(f"{result} is not in list of allowed states\n{self.allowed_states}")
633604
return result
634605

635606

@@ -642,5 +613,5 @@ def get_state(self, model, transition, result, args=[], kwargs={}):
642613
result_state = self.func(model, *args, **kwargs)
643614
if self.allowed_states is not None:
644615
if result_state not in self.allowed_states:
645-
raise InvalidResultState("{} is not in list of allowed states\n{}".format(result, self.allowed_states))
616+
raise InvalidResultState(f"{result} is not in list of allowed states\n{self.allowed_states}")
646617
return result_state

django_fsm/management/commands/graph_transitions.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
# -*- coding: utf-8; mode: django -*-
21
import graphviz
32
from optparse import make_option
43
from itertools import chain
@@ -37,7 +36,7 @@ def all_fsm_fields_data(model):
3736

3837
def node_name(field, state):
3938
opts = field.model._meta
40-
return "%s.%s.%s.%s" % (opts.app_label, opts.verbose_name.replace(" ", "_"), field.name, state)
39+
return "{}.{}.{}.{}".format(opts.app_label, opts.verbose_name.replace(" ", "_"), field.name, state)
4140

4241

4342
def node_label(field, state):
@@ -79,7 +78,7 @@ def generate_dot(fields_data):
7978
add_transition(source, target, transition.name, source_name, field, sources, targets, edges)
8079

8180
targets.update(
82-
set((node_name(field, target), node_label(field, target)) for target, _ in chain(any_targets, any_except_targets))
81+
{(node_name(field, target), node_label(field, target)) for target, _ in chain(any_targets, any_except_targets)}
8382
)
8483
for target, name in any_targets:
8584
target_name = node_name(field, target)
@@ -91,16 +90,16 @@ def generate_dot(fields_data):
9190
for target, name in any_except_targets:
9291
target_name = node_name(field, target)
9392
all_nodes = sources | targets
94-
all_nodes.remove(((target_name, node_label(field, target))))
93+
all_nodes.remove((target_name, node_label(field, target)))
9594
for source_name, label in all_nodes:
9695
sources.add((source_name, label))
9796
edges.add((source_name, target_name, (("label", name),)))
9897

9998
# construct subgraph
10099
opts = field.model._meta
101100
subgraph = graphviz.Digraph(
102-
name="cluster_%s_%s_%s" % (opts.app_label, opts.object_name, field.name),
103-
graph_attr={"label": "%s.%s.%s" % (opts.app_label, opts.object_name, field.name)},
101+
name=f"cluster_{opts.app_label}_{opts.object_name}_{field.name}",
102+
graph_attr={"label": f"{opts.app_label}.{opts.object_name}.{field.name}"},
104103
)
105104

106105
final_states = targets - sources

django_fsm/models.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
# -*- coding: utf-8 -*-
21
"""
32
Empty file to mark package as valid django application.
43
"""

django_fsm/signals.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
# -*- coding: utf-8 -*-
21
from django.dispatch import Signal
32

43
pre_transition = Signal()

django_fsm/tests/test_abstract_inheritance.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,5 +54,5 @@ def test_field_available_transitions_works(self):
5454
def test_field_all_transitions_works(self):
5555
transitions = self.model.get_all_state_transitions()
5656
self.assertEqual(
57-
set([("new", "published"), ("published", "sticked")]), set((data.source, data.target) for data in transitions)
57+
{("new", "published"), ("published", "sticked")}, {(data.source, data.target) for data in transitions}
5858
)

0 commit comments

Comments
 (0)