diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 39f9761..3d53f13 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -34,12 +34,16 @@ repos: - repo: https://github.com/python-poetry/poetry rev: 1.6.1 hooks: - - id: poetry-check - - id: poetry-lock - - id: poetry-export + - id: poetry-check + additional_dependencies: + - poetry-plugin-sort==0.2.0 + # FIXME: poetry lock export more platform on the CI + # - id: poetry-lock + # args: ["--no-update"] + - id: poetry-export - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.291 + rev: v0.1.9 hooks: - id: ruff-format - id: ruff diff --git a/README.rst b/README.rst index d2f107b..f5b80fb 100644 --- a/README.rst +++ b/README.rst @@ -153,7 +153,7 @@ on a model instance with a protected FSMField will cause an exception. ``source`` parameter accepts a list of states, or an individual state or ``django_fsm.State`` implementation. -You can use ``*`` for ``source`` to allow switching to ``target`` from any state. +You can use ``*`` for ``source`` to allow switching to ``target`` from any state. You can use ``+`` for ``source`` to allow switching to ``target`` from any state excluding ``target`` state. @@ -163,7 +163,7 @@ You can use ``+`` for ``source`` to allow switching to ``target`` from any state ``target`` state parameter could point to a specific state or ``django_fsm.State`` implementation .. code:: python - + from django_fsm import FSMField, transition, RETURN_VALUE, GET_STATE @transition(field=state, source='*', diff --git a/django_fsm/__init__.py b/django_fsm/__init__.py index 3d6dec0..f06ac77 100644 --- a/django_fsm/__init__.py +++ b/django_fsm/__init__.py @@ -1,18 +1,20 @@ """ State tracking functionality for django models """ +from __future__ import annotations + import inspect -from functools import partialmethod, wraps +from functools import partialmethod +from functools import wraps -import django from django.apps import apps as django_apps from django.db import models from django.db.models import Field from django.db.models.query_utils import DeferredAttribute from django.db.models.signals import class_prepared -from django_fsm.signals import pre_transition, post_transition - +from django_fsm.signals import post_transition +from django_fsm.signals import pre_transition __all__ = [ "TransitionNotAllowed", @@ -251,26 +253,9 @@ def deconstruct(self): return name, path, args, kwargs def get_state(self, instance): - # The state field may be deferred. We delegate the logic of figuring - # this out and loading the deferred field on-demand to Django's - # built-in DeferredAttribute class. DeferredAttribute's instantiation - # signature changed over time, so we need to check Django version - # before proceeding to call DeferredAttribute. An alternative to this - # would be copying the latest implementation of DeferredAttribute to - # django_fsm, but this comes with the added responsibility of keeping - # the copied code up to date. - if django.VERSION[:3] >= (3, 0, 0): - return DeferredAttribute(self).__get__(instance) - elif django.VERSION[:3] >= (2, 1, 0): - return DeferredAttribute(self.name).__get__(instance) - elif django.VERSION[:3] >= (1, 10, 0): - return DeferredAttribute(self.name, model=None).__get__(instance) - else: - # The field was either not deferred (in which case we can return it - # right away) or ir was, but we are running on an unknown version - # of Django and we do not know the appropriate DeferredAttribute - # interface, and accessing the field will raise KeyError. - return instance.__dict__[self.name] + # The state field may be deferred. We delegate the logic of figuring this out + # and loading the deferred field on-demand to Django's built-in DeferredAttribute class. + return DeferredAttribute(self).__get__(instance) def set_state(self, instance, state): instance.__dict__[self.name] = state @@ -435,7 +420,7 @@ def set_state(self, instance, state): instance.__dict__[self.attname] = self.to_python(state) -class FSMModelMixin(object): +class FSMModelMixin: """ Mixin that allows refresh_from_db for models with fsm protected fields """ @@ -443,6 +428,7 @@ class FSMModelMixin(object): def _get_protected_fsm_fields(self): def is_fsm_and_protected(f): return isinstance(f, FSMFieldMixin) and f.protected + protected_fields = filter(is_fsm_and_protected, self._meta.concrete_fields) return {f.attname for f in protected_fields} @@ -455,13 +441,13 @@ def refresh_from_db(self, *args, **kwargs): protected_fields = self._get_protected_fsm_fields() skipped_fields = deferred_fields.union(protected_fields) - fields = [f.attname for f in self._meta.concrete_fields - if f.attname not in skipped_fields] + fields = [f.attname for f in self._meta.concrete_fields if f.attname not in skipped_fields] + + kwargs["fields"] = fields + super().refresh_from_db(*args, **kwargs) - kwargs['fields'] = fields - super(FSMModelMixin, self).refresh_from_db(*args, **kwargs) -class ConcurrentTransitionMixin(object): +class ConcurrentTransitionMixin: """ Protects a Model from undesirable effects caused by concurrently executed transitions, e.g. running the same transition multiple times at the same time, or running different diff --git a/django_fsm/management/commands/graph_transitions.py b/django_fsm/management/commands/graph_transitions.py index 18ba9ad..7f7b36a 100644 --- a/django_fsm/management/commands/graph_transitions.py +++ b/django_fsm/management/commands/graph_transitions.py @@ -1,11 +1,15 @@ -import graphviz +from __future__ import annotations + from itertools import chain +import graphviz from django.apps import apps from django.core.management.base import BaseCommand from django.utils.encoding import force_str -from django_fsm import FSMFieldMixin, GET_STATE, RETURN_VALUE +from django_fsm import GET_STATE +from django_fsm import RETURN_VALUE +from django_fsm import FSMFieldMixin def all_fsm_fields_data(model): diff --git a/django_fsm/signals.py b/django_fsm/signals.py index 07fb641..6136d95 100644 --- a/django_fsm/signals.py +++ b/django_fsm/signals.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from django.dispatch import Signal pre_transition = Signal() diff --git a/django_fsm/tests/test_abstract_inheritance.py b/django_fsm/tests/test_abstract_inheritance.py index 8daf864..5b89cd2 100644 --- a/django_fsm/tests/test_abstract_inheritance.py +++ b/django_fsm/tests/test_abstract_inheritance.py @@ -1,7 +1,11 @@ +from __future__ import annotations + from django.db import models from django.test import TestCase -from django_fsm import FSMField, transition, can_proceed +from django_fsm import FSMField +from django_fsm import can_proceed +from django_fsm import transition class BaseAbstractModel(models.Model): diff --git a/django_fsm/tests/test_basic_transitions.py b/django_fsm/tests/test_basic_transitions.py index 0462caf..ce1a37a 100644 --- a/django_fsm/tests/test_basic_transitions.py +++ b/django_fsm/tests/test_basic_transitions.py @@ -1,8 +1,15 @@ +from __future__ import annotations + from django.db import models from django.test import TestCase -from django_fsm import FSMField, TransitionNotAllowed, transition, can_proceed, Transition -from django_fsm.signals import pre_transition, post_transition +from django_fsm import FSMField +from django_fsm import Transition +from django_fsm import TransitionNotAllowed +from django_fsm import can_proceed +from django_fsm import transition +from django_fsm.signals import post_transition +from django_fsm.signals import pre_transition class BlogPost(models.Model): diff --git a/django_fsm/tests/test_conditions.py b/django_fsm/tests/test_conditions.py index 608b08b..0682760 100644 --- a/django_fsm/tests/test_conditions.py +++ b/django_fsm/tests/test_conditions.py @@ -1,6 +1,12 @@ +from __future__ import annotations + from django.db import models from django.test import TestCase -from django_fsm import FSMField, TransitionNotAllowed, transition, can_proceed + +from django_fsm import FSMField +from django_fsm import TransitionNotAllowed +from django_fsm import can_proceed +from django_fsm import transition def condition_func(instance): diff --git a/django_fsm/tests/test_integer_field.py b/django_fsm/tests/test_integer_field.py index 438d545..31d03db 100644 --- a/django_fsm/tests/test_integer_field.py +++ b/django_fsm/tests/test_integer_field.py @@ -1,6 +1,11 @@ +from __future__ import annotations + from django.db import models from django.test import TestCase -from django_fsm import FSMIntegerField, TransitionNotAllowed, transition + +from django_fsm import FSMIntegerField +from django_fsm import TransitionNotAllowed +from django_fsm import transition class BlogPostStateEnum: diff --git a/django_fsm/tests/test_key_field.py b/django_fsm/tests/test_key_field.py index 2e115c9..d8ba939 100644 --- a/django_fsm/tests/test_key_field.py +++ b/django_fsm/tests/test_key_field.py @@ -1,7 +1,12 @@ +from __future__ import annotations + from django.db import models from django.test import TestCase -from django_fsm import FSMKeyField, TransitionNotAllowed, transition, can_proceed +from django_fsm import FSMKeyField +from django_fsm import TransitionNotAllowed +from django_fsm import can_proceed +from django_fsm import transition FK_AVAILABLE_STATES = ( ("New", "_NEW_"), diff --git a/django_fsm/tests/test_protected_field.py b/django_fsm/tests/test_protected_field.py index 4fc0259..b148982 100644 --- a/django_fsm/tests/test_protected_field.py +++ b/django_fsm/tests/test_protected_field.py @@ -1,7 +1,10 @@ +from __future__ import annotations + from django.db import models from django.test import TestCase -from django_fsm import FSMField, transition +from django_fsm import FSMField +from django_fsm import transition class ProtectedAccessModel(models.Model): diff --git a/django_fsm/tests/test_protected_fields.py b/django_fsm/tests/test_protected_fields.py index d476c48..5eb7766 100644 --- a/django_fsm/tests/test_protected_fields.py +++ b/django_fsm/tests/test_protected_fields.py @@ -1,21 +1,25 @@ +from __future__ import annotations + import unittest import django from django.db import models from django.test import TestCase -from django_fsm import FSMField, FSMModelMixin, transition +from django_fsm import FSMField +from django_fsm import FSMModelMixin +from django_fsm import transition class RefreshableProtectedAccessModel(models.Model): - status = FSMField(default='new', protected=True) + status = FSMField(default="new", protected=True) - @transition(field=status, source='new', target='published') + @transition(field=status, source="new", target="published") def publish(self): pass class Meta: - app_label = 'django_fsm' + app_label = "django_fsm" class RefreshableModel(FSMModelMixin, RefreshableProtectedAccessModel): @@ -25,16 +29,16 @@ class RefreshableModel(FSMModelMixin, RefreshableProtectedAccessModel): class TestDirectAccessModels(TestCase): def test_no_direct_access(self): instance = RefreshableProtectedAccessModel() - self.assertEqual(instance.status, 'new') + self.assertEqual(instance.status, "new") def try_change(): - instance.status = 'change' + instance.status = "change" self.assertRaises(AttributeError, try_change) instance.publish() instance.save() - self.assertEqual(instance.status, 'published') + self.assertEqual(instance.status, "published") @unittest.skipIf(django.VERSION < (1, 8), "Django introduced refresh_from_db in 1.8") def test_refresh_from_db(self): diff --git a/django_fsm/tests/test_proxy_inheritance.py b/django_fsm/tests/test_proxy_inheritance.py index 0772e44..8140d05 100644 --- a/django_fsm/tests/test_proxy_inheritance.py +++ b/django_fsm/tests/test_proxy_inheritance.py @@ -1,7 +1,11 @@ +from __future__ import annotations + from django.db import models from django.test import TestCase -from django_fsm import FSMField, transition, can_proceed +from django_fsm import FSMField +from django_fsm import can_proceed +from django_fsm import transition class BaseModel(models.Model): diff --git a/poetry.lock b/poetry.lock index 4018902..9117358 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.6.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "asgiref" @@ -56,6 +56,17 @@ files = [ {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, ] +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + [[package]] name = "coverage" version = "7.3.2" @@ -117,6 +128,9 @@ files = [ {file = "coverage-7.3.2.tar.gz", hash = "sha256:be32ad29341b0170e795ca590e1c07e81fc061cb5b10c74ce7203491484404ef"}, ] +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + [package.extras] toml = ["tomli"] @@ -166,6 +180,20 @@ files = [ [package.dependencies] Django = ">=2.2" +[[package]] +name = "exceptiongroup" +version = "1.1.3" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, + {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, +] + +[package.extras] +test = ["pytest (>=6)"] + [[package]] name = "filelock" version = "3.13.0" @@ -212,6 +240,17 @@ files = [ [package.extras] license = ["ukkonen"] +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + [[package]] name = "nodeenv" version = "1.8.0" @@ -226,6 +265,17 @@ files = [ [package.dependencies] setuptools = "*" +[[package]] +name = "packaging" +version = "23.2" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, +] + [[package]] name = "platformdirs" version = "3.11.0" @@ -241,6 +291,21 @@ files = [ docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] +[[package]] +name = "pluggy" +version = "1.3.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, + {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + [[package]] name = "pre-commit" version = "3.5.0" @@ -259,6 +324,64 @@ nodeenv = ">=0.11.1" pyyaml = ">=5.1" virtualenv = ">=20.10.0" +[[package]] +name = "pytest" +version = "7.4.3" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, + {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-cov" +version = "4.1.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, + {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] + +[[package]] +name = "pytest-django" +version = "4.5.2" +description = "A Django plugin for pytest." +optional = false +python-versions = ">=3.5" +files = [ + {file = "pytest-django-4.5.2.tar.gz", hash = "sha256:d9076f759bb7c36939dbdd5ae6633c18edfc2902d1a69fdbefd2426b970ce6c2"}, + {file = "pytest_django-4.5.2-py3-none-any.whl", hash = "sha256:c60834861933773109334fe5a53e83d1ef4828f2203a1d6a0fa9972f4f75ab3e"}, +] + +[package.dependencies] +pytest = ">=5.4.0" + +[package.extras] +docs = ["sphinx", "sphinx-rtd-theme"] +testing = ["Django", "django-configurations (>=2.0)"] + [[package]] name = "pyyaml" version = "6.0.1" @@ -340,6 +463,17 @@ dev = ["build", "flake8"] doc = ["sphinx"] test = ["pytest", "pytest-cov"] +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + [[package]] name = "typing-extensions" version = "4.8.0" @@ -385,4 +519,4 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "a86614edcf82151f75bb613525e9ebb46b64d0cdf738dedb3bfb119eff020c94" +content-hash = "040bf0f7894980723ad0074242b91923c02e386c087be0ca6ca3ec8a76520012" diff --git a/pyproject.toml b/pyproject.toml index 521cd8b..6158cc8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,22 +1,10 @@ -[tool.ruff] -line-length = 130 -target-version = "py38" - -[tool.ruff.lint] -extend-select = [ - "F", # Pyflakes - "E", # pycodestyle - "W", # pycodestyle - "UP", # pyupgrade -] - [tool.poetry] name = "django-fsm" version = "2.8.1" description = "Django friendly finite state machine support." authors = ["Mikhail Podgurskiy "] license = "MIT License" -readme = "README.md" +readme = "README.rst" homepage = "http://github.com/kmmbvnr/django-fsm" repository = "http://github.com/kmmbvnr/django-fsm" documentation = "http://github.com/kmmbvnr/django-fsm" @@ -54,6 +42,32 @@ coverage = "*" django-guardian = "*" graphviz = "*" pre-commit = "*" +pytest = "*" +pytest-cov = "^4.1.0" +pytest-django = "*" + +[tool.pytest.ini_options] +DJANGO_SETTINGS_MODULE = "tests.settings" + +[tool.ruff] +line-length = 130 +target-version = "py38" +fix = true + +[tool.ruff.lint] +extend-select = [ + "F", # Pyflakes + "E", # pycodestyle + "W", # pycodestyle + "UP", # pyupgrade + "I", # isort +] +fixable = ["I"] + + +[tool.ruff.isort] +force-single-line = true +required-imports = ["from __future__ import annotations"] [build-system] requires = ["poetry-core"] diff --git a/setup.cfg b/setup.cfg index e5cf602..2a9acf1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,2 @@ [bdist_wheel] universal = 1 - -[flake8] -max-line-length = 130 -ignore = D100, D105, D107, W503 diff --git a/setup.py b/setup.py index 143f698..60e07f5 100644 --- a/setup.py +++ b/setup.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from setuptools import setup try: @@ -18,7 +20,7 @@ zip_safe=False, license="MIT License", platforms=["any"], - python_requires=">=3.7", + python_requires=">=3.8", classifiers=[ "Development Status :: 5 - Production/Stable", "Environment :: Web Environment", diff --git a/tests/manage.py b/tests/manage.py index 034110c..0153804 100644 --- a/tests/manage.py +++ b/tests/manage.py @@ -1,5 +1,8 @@ +from __future__ import annotations + import os import sys + from django.core.management import execute_from_command_line PROJECT_ROOT = os.path.dirname(os.path.abspath(os.path.dirname(__file__))) diff --git a/tests/settings.py b/tests/settings.py index 42e895f..8630fdb 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -1,6 +1,10 @@ +from __future__ import annotations + +USE_TZ = True + PROJECT_APPS = ( "django_fsm", - "testapp", + "tests.testapp", ) INSTALLED_APPS = ( diff --git a/tests/testapp/apps.py b/tests/testapp/apps.py new file mode 100644 index 0000000..669dc3e --- /dev/null +++ b/tests/testapp/apps.py @@ -0,0 +1,7 @@ +from __future__ import annotations + +from django.apps import AppConfig + + +class TestAppConfig(AppConfig): + name = "tests.testapp" diff --git a/tests/testapp/models.py b/tests/testapp/models.py index abc1219..7b92868 100644 --- a/tests/testapp/models.py +++ b/tests/testapp/models.py @@ -1,5 +1,10 @@ +from __future__ import annotations + from django.db import models -from django_fsm import FSMField, FSMKeyField, transition + +from django_fsm import FSMField +from django_fsm import FSMKeyField +from django_fsm import transition class Application(models.Model): diff --git a/tests/testapp/tests/test_access_deferred_fsm_field.py b/tests/testapp/tests/test_access_deferred_fsm_field.py index 9f08379..5e1b68c 100644 --- a/tests/testapp/tests/test_access_deferred_fsm_field.py +++ b/tests/testapp/tests/test_access_deferred_fsm_field.py @@ -1,6 +1,11 @@ +from __future__ import annotations + from django.db import models from django.test import TestCase -from django_fsm import FSMField, transition, can_proceed + +from django_fsm import FSMField +from django_fsm import can_proceed +from django_fsm import transition class DeferrableModel(models.Model): diff --git a/tests/testapp/tests/test_custom_data.py b/tests/testapp/tests/test_custom_data.py index 147759d..abf486b 100644 --- a/tests/testapp/tests/test_custom_data.py +++ b/tests/testapp/tests/test_custom_data.py @@ -1,6 +1,10 @@ +from __future__ import annotations + from django.db import models from django.test import TestCase -from django_fsm import FSMField, transition + +from django_fsm import FSMField +from django_fsm import transition class BlogPostWithCustomData(models.Model): diff --git a/tests/testapp/tests/test_exception_transitions.py b/tests/testapp/tests/test_exception_transitions.py index a9894c5..790f3e1 100644 --- a/tests/testapp/tests/test_exception_transitions.py +++ b/tests/testapp/tests/test_exception_transitions.py @@ -1,7 +1,11 @@ +from __future__ import annotations + from django.db import models from django.test import TestCase -from django_fsm import FSMField, transition, can_proceed +from django_fsm import FSMField +from django_fsm import can_proceed +from django_fsm import transition from django_fsm.signals import post_transition diff --git a/tests/testapp/tests/test_lock_mixin.py b/tests/testapp/tests/test_lock_mixin.py index fcaa0c0..27ede7b 100644 --- a/tests/testapp/tests/test_lock_mixin.py +++ b/tests/testapp/tests/test_lock_mixin.py @@ -1,9 +1,12 @@ -import unittest +from __future__ import annotations -import django from django.db import models from django.test import TestCase -from django_fsm import FSMField, ConcurrentTransitionMixin, ConcurrentTransition, transition + +from django_fsm import ConcurrentTransition +from django_fsm import ConcurrentTransitionMixin +from django_fsm import FSMField +from django_fsm import transition class LockedBlogPost(ConcurrentTransitionMixin, models.Model): @@ -90,7 +93,6 @@ def test_inheritance_crud_succeed(self): self.assertEqual("rejected", post.review_state) self.assertEqual("test_inheritance_crud_succeed2", post.text) - @unittest.skipIf(django.VERSION[:3] < (1, 8, 0), "Available on django 1.8+") def test_concurrent_modifications_after_refresh_db_succeed(self): # bug 255 post1 = LockedBlogPost.objects.create() post2 = LockedBlogPost.objects.get(pk=post1.pk) diff --git a/tests/testapp/tests/test_mixin_support.py b/tests/testapp/tests/test_mixin_support.py index f9a3385..c8f3036 100644 --- a/tests/testapp/tests/test_mixin_support.py +++ b/tests/testapp/tests/test_mixin_support.py @@ -1,6 +1,10 @@ +from __future__ import annotations + from django.db import models from django.test import TestCase -from django_fsm import FSMField, transition + +from django_fsm import FSMField +from django_fsm import transition class WorkflowMixin: diff --git a/tests/testapp/tests/test_model_create_with_generic.py b/tests/testapp/tests/test_model_create_with_generic.py index dcc1723..60a979a 100644 --- a/tests/testapp/tests/test_model_create_with_generic.py +++ b/tests/testapp/tests/test_model_create_with_generic.py @@ -1,3 +1,5 @@ +from __future__ import annotations + try: from django.contrib.contenttypes.fields import GenericForeignKey except ImportError: @@ -6,7 +8,9 @@ from django.contrib.contenttypes.models import ContentType from django.db import models from django.test import TestCase -from django_fsm import FSMField, transition + +from django_fsm import FSMField +from django_fsm import transition class Ticket(models.Model): diff --git a/tests/testapp/tests/test_multi_resultstate.py b/tests/testapp/tests/test_multi_resultstate.py index f5fa929..20db8d3 100644 --- a/tests/testapp/tests/test_multi_resultstate.py +++ b/tests/testapp/tests/test_multi_resultstate.py @@ -1,7 +1,14 @@ +from __future__ import annotations + from django.db import models from django.test import TestCase -from django_fsm import FSMField, transition, RETURN_VALUE, GET_STATE -from django_fsm.signals import pre_transition, post_transition + +from django_fsm import GET_STATE +from django_fsm import RETURN_VALUE +from django_fsm import FSMField +from django_fsm import transition +from django_fsm.signals import post_transition +from django_fsm.signals import pre_transition class MultiResultTest(models.Model): diff --git a/tests/testapp/tests/test_multidecorators.py b/tests/testapp/tests/test_multidecorators.py index 8d48a83..eea9617 100644 --- a/tests/testapp/tests/test_multidecorators.py +++ b/tests/testapp/tests/test_multidecorators.py @@ -1,6 +1,10 @@ +from __future__ import annotations + from django.db import models from django.test import TestCase -from django_fsm import FSMField, transition + +from django_fsm import FSMField +from django_fsm import transition from django_fsm.signals import post_transition diff --git a/tests/testapp/tests/test_object_permissions.py b/tests/testapp/tests/test_object_permissions.py index 58129c2..68263df 100644 --- a/tests/testapp/tests/test_object_permissions.py +++ b/tests/testapp/tests/test_object_permissions.py @@ -1,11 +1,14 @@ +from __future__ import annotations + from django.contrib.auth.models import User from django.db import models from django.test import TestCase from django.test.utils import override_settings - from guardian.shortcuts import assign_perm -from django_fsm import FSMField, transition, has_transition_perm +from django_fsm import FSMField +from django_fsm import has_transition_perm +from django_fsm import transition class ObjectPermissionTestModel(models.Model): diff --git a/tests/testapp/tests/test_permissions.py b/tests/testapp/tests/test_permissions.py index e373507..67b4561 100644 --- a/tests/testapp/tests/test_permissions.py +++ b/tests/testapp/tests/test_permissions.py @@ -1,8 +1,11 @@ -from django.contrib.auth.models import User, Permission +from __future__ import annotations + +from django.contrib.auth.models import Permission +from django.contrib.auth.models import User from django.test import TestCase from django_fsm import has_transition_perm -from testapp.models import BlogPost +from tests.testapp.models import BlogPost class PermissionFSMFieldTest(TestCase): diff --git a/tests/testapp/tests/test_state_transitions.py b/tests/testapp/tests/test_state_transitions.py index 6197cd4..532394e 100644 --- a/tests/testapp/tests/test_state_transitions.py +++ b/tests/testapp/tests/test_state_transitions.py @@ -1,6 +1,10 @@ +from __future__ import annotations + from django.db import models from django.test import TestCase -from django_fsm import FSMField, transition + +from django_fsm import FSMField +from django_fsm import transition class Insect(models.Model): diff --git a/tests/testapp/tests/test_string_field_parameter.py b/tests/testapp/tests/test_string_field_parameter.py index 82b8794..f34210f 100644 --- a/tests/testapp/tests/test_string_field_parameter.py +++ b/tests/testapp/tests/test_string_field_parameter.py @@ -1,6 +1,10 @@ +from __future__ import annotations + from django.db import models from django.test import TestCase -from django_fsm import FSMField, transition + +from django_fsm import FSMField +from django_fsm import transition class BlogPostWithStringField(models.Model): diff --git a/tests/testapp/tests/test_transition_all_except_target.py b/tests/testapp/tests/test_transition_all_except_target.py index 6d63813..a7765bf 100644 --- a/tests/testapp/tests/test_transition_all_except_target.py +++ b/tests/testapp/tests/test_transition_all_except_target.py @@ -1,6 +1,11 @@ +from __future__ import annotations + from django.db import models from django.test import TestCase -from django_fsm import FSMField, transition, can_proceed + +from django_fsm import FSMField +from django_fsm import can_proceed +from django_fsm import transition class TestExceptTargetTransitionShortcut(models.Model): diff --git a/tox.ini b/tox.ini index b6ec583..e93a4f5 100644 --- a/tox.ini +++ b/tox.ini @@ -15,13 +15,15 @@ deps = dj42: Django==4.2 dj50: Django==5.0 - coverage==7.3.2 django-guardian==2.4.0 graphviz==0.20.1 pep8==1.7.1 pyflakes==3.1.0 + pytest==7.4.3 + pytest-django==4.5.2 + pytest-cov==4.1.0 -commands = {posargs:python ./tests/manage.py test} +commands = {posargs:python -m pytest} [flake8] max-line-length = 130