diff --git a/.checkignore b/.checkignore deleted file mode 100644 index e93d446..0000000 --- a/.checkignore +++ /dev/null @@ -1,2 +0,0 @@ -tests/* -django_fsm/tests/* diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 515a86c..9bd7701 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -23,4 +23,4 @@ jobs: python -m pip install --upgrade pip python -m pip install tox tox-gh-actions - name: Test with tox - run: tox \ No newline at end of file + run: tox diff --git a/.gitignore b/.gitignore index a9a6d20..b2d05d8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,132 @@ -*.pyc -dist/ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python build/ -django_fsm.egg-info/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ .tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# sqlite +test.db diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0812619..a5d2dd2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ default_language_version: repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: check-added-large-files args: ["--maxkb=700"] @@ -31,8 +31,16 @@ repos: - id: django-upgrade args: [--target-version, "3.2"] - # - repo: https://github.com/astral-sh/ruff-pre-commit - # rev: v0.0.291 - # hooks: - # - id: ruff-format - # - id: ruff + + - repo: https://github.com/python-poetry/poetry + rev: 1.6.1 + hooks: + - id: poetry-check + - id: poetry-lock + - id: poetry-export + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.0.291 + hooks: + - id: ruff-format + - id: ruff diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index e326809..0000000 --- a/.travis.yml +++ /dev/null @@ -1,16 +0,0 @@ -dist: xenial -language: python -sudo: false -cache: pip - -python: - - 2.7 - - 3.6 - - 3.7 - - 3.8 - -install: - - pip install tox tox-travis - -script: - - tox --skip-missing-interpreters diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..3cbde9d --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,132 @@ +# Changelog + +## django-fsm unreleased + +- Enable Github actions for testing +- Remove support for django deprecated versions +- add support for django 4.2 +- add support for django 5.0 +- Remove support for python deprecated versions +- add support for python 3.11 +- add support for python 3.12 + +## django-fsm 2.8.1 2022-08-15 + +- Improve fix for get_available_FIELD_transition + +## django-fsm 2.8.0 2021-11-05 + +- Fix get_available_FIELD_transition on django\>=3.2 +- Fix refresh_from_db for ConcurrentTransitionMixin + +## django-fsm 2.7.1 2020-10-13 + +- Fix warnings on Django 3.1+ + +## django-fsm 2.7.0 2019-12-03 + +- Django 3.0 support +- Test on Python 3.8 + +## django-fsm 2.6.1 2019-04-19 + +- Update pypi classifiers to latest django/python supported versions +- Several fixes for graph_transition command + +## django-fsm 2.6.0 2017-06-08 + +- Fix django 1.11 compatibility +- Fix TypeError in [graph_transitions]{.title-ref} command when using + django\'s lazy translations + +## django-fsm 2.5.0 2017-03-04 + +- graph_transition command fix for django 1.10 +- graph_transition command supports GET_STATE targets +- signal data extended with method args/kwargs and field +- sets allowed to be passed to the transition decorator + +## django-fsm 2.4.0 2016-05-14 + +- graph_transition commnad now works with multiple FSM\'s per model +- Add ability to set target state from transition return value or + callable + +## django-fsm 2.3.0 2015-10-15 + +- Add source state shortcut \'+\' to specify transitions from all + states except the target +- Add object-level permission checks +- Fix translated labels for graph of FSMIntegerField +- Fix multiple signals for several transition decorators + +## django-fsm 2.2.1 2015-04-27 + +- Improved exception message for unmet transition conditions. +- Don\'t send post transition signal in case of no state changes on + exception +- Allow empty string as correct state value +- Improved graphviz fsm visualisation +- Clean django 1.8 warnings + +## django-fsm 2.2.0 2014-09-03 + +- Support for [class + substitution](http://schinckel.net/2013/06/13/django-proxy-model-state-machine/) + to proxy classes depending on the state +- Added ConcurrentTransitionMixin with optimistic locking support +- Default db_index=True for FSMIntegerField removed +- Graph transition code migrated to new graphviz library with python 3 + support +- Ability to change state on transition exception + +## django-fsm 2.1.0 2014-05-15 + +- Support for attaching permission checks on model transitions + +## django-fsm 2.0.0 2014-03-15 + +- Backward incompatible release +- All public code import moved directly to django_fsm package +- Correct support for several \@transitions decorator with different + source states and conditions on same method +- save parameter from transition decorator removed +- get_available_FIELD_transitions return Transition data object + instead of tuple +- Models got get_available_FIELD_transitions, even if field specified + as string reference +- New get_all_FIELD_transitions method contributed to class + +## django-fsm 1.6.0 2014-03-15 + +- FSMIntegerField and FSMKeyField support + +## django-fsm 1.5.1 2014-01-04 + +- Ad-hoc support for state fields from proxy and inherited models + +## django-fsm 1.5.0 2013-09-17 + +- Python 3 compatibility + +## django-fsm 1.4.0 2011-12-21 + +- Add graph_transition command for drawing state transition picture + +## django-fsm 1.3.0 2011-07-28 + +- Add direct field modification protection + +## django-fsm 1.2.0 2011-03-23 + +- Add pre_transition and post_transition signals + +## django-fsm 1.1.0 2011-02-22 + +- Add support for transition conditions +- Allow multiple FSMField in one model +- Contribute get_available_FIELD_transitions for model class + +## django-fsm 1.0.0 2010-10-12 + +- Initial public release diff --git a/CHANGELOG.rst b/CHANGELOG.rst deleted file mode 100644 index b90ce3b..0000000 --- a/CHANGELOG.rst +++ /dev/null @@ -1,161 +0,0 @@ -Changelog -========= - -django-fsm unreleased -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -- Enable Github actions for testing -- Remove support for django deprecated versions -- add support for django 4.2 -- add support for django 5.0 -- Remove support for python deprecated versions -- add support for python 3.11 -- add support for python 3.12 - -django-fsm 2.8.1 2022-08-15 -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -- Improve fix for get_available_FIELD_transition - - -django-fsm 2.8.0 2021-11-05 -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -- Fix get_available_FIELD_transition on django>=3.2 -- Fix refresh_from_db for ConcurrentTransitionMixin - - -django-fsm 2.7.1 2020-10-13 -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -- Fix warnings on Django 3.1+ - - -django-fsm 2.7.0 2019-12-03 -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -- Django 3.0 support -- Test on Python 3.8 - - -django-fsm 2.6.1 2019-04-19 -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -- Update pypi classifiers to latest django/python supported versions -- Several fixes for graph_transition command - - -django-fsm 2.6.0 2017-06-08 -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -- Fix django 1.11 compatibility -- Fix TypeError in `graph_transitions` command when using django's lazy translations - - -django-fsm 2.5.0 2017-03-04 -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -- graph_transition command fix for django 1.10 -- graph_transition command supports GET_STATE targets -- signal data extended with method args/kwargs and field -- sets allowed to be passed to the transition decorator - - -django-fsm 2.4.0 2016-05-14 -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -- graph_transition commnad now works with multiple FSM's per model -- Add ability to set target state from transition return value or callable - - -django-fsm 2.3.0 2015-10-15 -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -- Add source state shortcut '+' to specify transitions from all states except the target -- Add object-level permission checks -- Fix translated labels for graph of FSMIntegerField -- Fix multiple signals for several transition decorators - - -django-fsm 2.2.1 2015-04-27 -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -- Improved exception message for unmet transition conditions. -- Don't send post transition signal in case of no state changes on - exception -- Allow empty string as correct state value -- Improved graphviz fsm visualisation -- Clean django 1.8 warnings - -django-fsm 2.2.0 2014-09-03 -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -- Support for `class - substitution `__ - to proxy classes depending on the state -- Added ConcurrentTransitionMixin with optimistic locking support -- Default db\_index=True for FSMIntegerField removed -- Graph transition code migrated to new graphviz library with python 3 - support -- Ability to change state on transition exception - -django-fsm 2.1.0 2014-05-15 -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -- Support for attaching permission checks on model transitions - -django-fsm 2.0.0 2014-03-15 -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -- Backward incompatible release -- All public code import moved directly to django\_fsm package -- Correct support for several @transitions decorator with different - source states and conditions on same method -- save parameter from transition decorator removed -- get\_available\_FIELD\_transitions return Transition data object - instead of tuple -- Models got get\_available\_FIELD\_transitions, even if field - specified as string reference -- New get\_all\_FIELD\_transitions method contributed to class - -django-fsm 1.6.0 2014-03-15 -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -- FSMIntegerField and FSMKeyField support - -django-fsm 1.5.1 2014-01-04 -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -- Ad-hoc support for state fields from proxy and inherited models - -django-fsm 1.5.0 2013-09-17 -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -- Python 3 compatibility - -django-fsm 1.4.0 2011-12-21 -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -- Add graph\_transition command for drawing state transition picture - -django-fsm 1.3.0 2011-07-28 -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -- Add direct field modification protection - -django-fsm 1.2.0 2011-03-23 -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -- Add pre\_transition and post\_transition signals - -django-fsm 1.1.0 2011-02-22 -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -- Add support for transition conditions -- Allow multiple FSMField in one model -- Contribute get\_available\_FIELD\_transitions for model class - -django-fsm 1.0.0 2010-10-12 -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -- Initial public release diff --git a/README.md b/README.md new file mode 100644 index 0000000..9336c76 --- /dev/null +++ b/README.md @@ -0,0 +1,419 @@ +# Django friendly finite state machine support + +[![Build +Status](https://travis-ci.org/viewflow/django-fsm.svg?branch=master)](https://travis-ci.org/viewflow/django-fsm) + +django-fsm adds simple declarative state management for django models. + +If you need parallel task execution, view and background task code reuse +over different flows - check my new project django-viewflow: + + + +Instead of adding a state field to a django model and managing its +values by hand, you use `FSMField` and mark model methods with the +`transition` decorator. These methods could contain side-effects of the +state change. + +Nice introduction is available here: + + +You may also take a look at django-fsm-admin project containing a mixin +and template tags to integrate django-fsm state transitions into the +django admin. + + + +Transition logging support could be achieved with help of django-fsm-log +package + + + +FSM really helps to structure the code, especially when a new developer +comes to the project. FSM is most effective when you use it for some +sequential steps. + +## Installation + +``` bash +$ pip install django-fsm +``` + +Or, for the latest git version + +``` bash +$ pip install -e git://github.com/kmmbvnr/django-fsm.git#egg=django-fsm +``` + +## Usage + +Add FSMState field to your model + +``` python +from django_fsm import FSMField, transition + +class BlogPost(models.Model): + state = FSMField(default='new') +``` + +Use the `transition` decorator to annotate model methods + +``` python +@transition(field=state, source='new', target='published') +def publish(self): + """ + This function may contain side-effects, + like updating caches, notifying users, etc. + The return value will be discarded. + """ +``` + +The `field` parameter accepts both a string attribute name or an actual +field instance. + +If calling publish() succeeds without raising an exception, the state +field will be changed, but not written to the database. + +``` python +from django_fsm import can_proceed + +def publish_view(request, post_id): + post = get_object_or_404(BlogPost, pk=post_id) + if not can_proceed(post.publish): + raise PermissionDenied + + post.publish() + post.save() + return redirect('/') +``` + +If some conditions are required to be met before changing the state, use +the `conditions` argument to `transition`. `conditions` must be a list +of functions taking one argument, the model instance. The function must +return either `True` or `False` or a value that evaluates to `True` or +`False`. If all functions return `True`, all conditions are considered +to be met and the transition is allowed to happen. If one of the +functions returns `False`, the transition will not happen. These +functions should not have any side effects. + +You can use ordinary functions + +``` python +def can_publish(instance): + # No publishing after 17 hours + if datetime.datetime.now().hour > 17: + return False + return True +``` + +Or model methods + +``` python +def can_destroy(self): + return self.is_under_investigation() +``` + +Use the conditions like this: + +``` python +@transition(field=state, source='new', target='published', conditions=[can_publish]) +def publish(self): + """ + Side effects galore + """ + +@transition(field=state, source='*', target='destroyed', conditions=[can_destroy]) +def destroy(self): + """ + Side effects galore + """ +``` + +You can instantiate a field with `protected=True` option to prevent +direct state field modification. + +``` python +class BlogPost(models.Model): + state = FSMField(default='new', protected=True) + +model = BlogPost() +model.state = 'invalid' # Raises AttributeError +``` + +Note that calling +[refresh_from_db](https://docs.djangoproject.com/en/1.8/ref/models/instances/#django.db.models.Model.refresh_from_db) +on a model instance with a protected FSMField will cause an exception. + +### `source` state + +`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 excluding `target` state. + +### `target` state + +`target` state parameter could point to a specific state or +`django_fsm.State` implementation + +``` python +from django_fsm import FSMField, transition, RETURN_VALUE, GET_STATE +@transition(field=state, + source='*', + target=RETURN_VALUE('for_moderators', 'published')) +def publish(self, is_public=False): + return 'for_moderators' if is_public else 'published' + +@transition( + field=state, + source='for_moderators', + target=GET_STATE( + lambda self, allowed: 'published' if allowed else 'rejected', + states=['published', 'rejected'])) +def moderate(self, allowed): + pass + +@transition( + field=state, + source='for_moderators', + target=GET_STATE( + lambda self, **kwargs: 'published' if kwargs.get("allowed", True) else 'rejected', + states=['published', 'rejected'])) +def moderate(self, allowed=True): + pass +``` + +### `custom` properties + +Custom properties can be added by providing a dictionary to the `custom` +keyword on the `transition` decorator. + +``` python +@transition(field=state, + source='*', + target='onhold', + custom=dict(verbose='Hold for legal reasons')) +def legal_hold(self): + """ + Side effects galore + """ +``` + +### `on_error` state + +If the transition method raises an exception, you can provide a specific +target state + +``` python +@transition(field=state, source='new', target='published', on_error='failed') +def publish(self): + """ + Some exception could happen here + """ +``` + +### `state_choices` + +Instead of passing a two-item iterable `choices` you can instead use the +three-element `state_choices`, the last element being a string reference +to a model proxy class. + +The base class instance would be dynamically changed to the +corresponding Proxy class instance, depending on the state. Even for +queryset results, you will get Proxy class instances, even if the +QuerySet is executed on the base class. + +Check the [test +case](https://github.com/kmmbvnr/django-fsm/blob/master/tests/testapp/tests/test_state_transitions.py) +for example usage. Or read about [implementation +internals](http://schinckel.net/2013/06/13/django-proxy-model-state-machine/) + +### Permissions + +It is common to have permissions attached to each model transition. +`django-fsm` handles this with `permission` keyword on the `transition` +decorator. `permission` accepts a permission string, or callable that +expects `instance` and `user` arguments and returns True if the user can +perform the transition. + +``` python +@transition(field=state, source='*', target='published', + permission=lambda instance, user: not user.has_perm('myapp.can_make_mistakes')) +def publish(self): + pass + +@transition(field=state, source='*', target='removed', + permission='myapp.can_remove_post') +def remove(self): + pass +``` + +You can check permission with `has_transition_permission` method + +``` python +from django_fsm import has_transition_perm +def publish_view(request, post_id): + post = get_object_or_404(BlogPost, pk=post_id) + if not has_transition_perm(post.publish, request.user): + raise PermissionDenied + + post.publish() + post.save() + return redirect('/') +``` + +### Model methods + +`get_all_FIELD_transitions` Enumerates all declared transitions + +`get_available_FIELD_transitions` Returns all transitions data available +in current state + +`get_available_user_FIELD_transitions` Enumerates all transitions data +available in current state for provided user + +### Foreign Key constraints support + +If you store the states in the db table you could use FSMKeyField to +ensure Foreign Key database integrity. + +In your model : + +``` python +class DbState(models.Model): + id = models.CharField(primary_key=True, max_length=50) + label = models.CharField(max_length=255) + + def __unicode__(self): + return self.label + + +class BlogPost(models.Model): + state = FSMKeyField(DbState, default='new') + + @transition(field=state, source='new', target='published') + def publish(self): + pass +``` + +In your fixtures/initial_data.json : + +``` json +[ + { + "pk": "new", + "model": "myapp.dbstate", + "fields": { + "label": "_NEW_" + } + }, + { + "pk": "published", + "model": "myapp.dbstate", + "fields": { + "label": "_PUBLISHED_" + } + } +] +``` + +Note : source and target parameters in \@transition decorator use pk +values of DBState model as names, even if field \"real\" name is used, +without \_id postfix, as field parameter. + +### Integer Field support + +You can also use `FSMIntegerField`. This is handy when you want to use +enum style constants. + +``` python +class BlogPostStateEnum(object): + NEW = 10 + PUBLISHED = 20 + HIDDEN = 30 + +class BlogPostWithIntegerField(models.Model): + state = FSMIntegerField(default=BlogPostStateEnum.NEW) + + @transition(field=state, source=BlogPostStateEnum.NEW, target=BlogPostStateEnum.PUBLISHED) + def publish(self): + pass +``` + +### Signals + +`django_fsm.signals.pre_transition` and +`django_fsm.signals.post_transition` are called before and after allowed +transition. No signals on invalid transition are called. + +Arguments sent with these signals: + +**sender** The model class. + +**instance** The actual instance being processed + +**name** Transition name + +**source** Source model state + +**target** Target model state + +## Optimistic locking + +`django-fsm` provides optimistic locking mixin, to avoid concurrent +model state changes. If model state was changed in database +`django_fsm.ConcurrentTransition` exception would be raised on +model.save() + +``` python +from django_fsm import FSMField, ConcurrentTransitionMixin + +class BlogPost(ConcurrentTransitionMixin, models.Model): + state = FSMField(default='new') +``` + +For guaranteed protection against race conditions caused by concurrently +executed transitions, make sure: + +- Your transitions do not have any side effects except for changes in + the database, +- You always run the save() method on the object within + `django.db.transaction.atomic()` block. + +Following these recommendations, you can rely on +ConcurrentTransitionMixin to cause a rollback of all the changes that +have been executed in an inconsistent (out of sync) state, thus +practically negating their effect. + +## Drawing transitions + +Renders a graphical overview of your models states transitions + +You need `pip install "graphviz>=0.4"` library and add `django_fsm` to +your `INSTALLED_APPS`: + +``` python +INSTALLED_APPS = ( + ... + 'django_fsm', + ... +) +``` + +``` bash +# Create a dot file +$ ./manage.py graph_transitions > transitions.dot + +# Create a PNG image file only for specific model +$ ./manage.py graph_transitions -o blog_transitions.png myapp.Blog +``` + +## Changelog + +### django-fsm 2.8.1 2022-08-15 + +- Improve fix for get_available_FIELD_transition diff --git a/README.rst b/README.rst deleted file mode 100644 index f5b80fb..0000000 --- a/README.rst +++ /dev/null @@ -1,435 +0,0 @@ -Django friendly finite state machine support -============================================ - -|Build Status| - -django-fsm adds simple declarative state management for django models. - -If you need parallel task execution, view and background task code reuse -over different flows - check my new project django-viewflow: - -https://github.com/viewflow/viewflow - - -Instead of adding a state field to a django model and managing its -values by hand, you use ``FSMField`` and mark model methods with -the ``transition`` decorator. These methods could contain side-effects -of the state change. - -Nice introduction is available here: -https://gist.github.com/Nagyman/9502133 - -You may also take a look at django-fsm-admin project containing a mixin -and template tags to integrate django-fsm state transitions into the -django admin. - -https://github.com/gadventures/django-fsm-admin - -Transition logging support could be achieved with help of django-fsm-log -package - -https://github.com/gizmag/django-fsm-log - -FSM really helps to structure the code, especially when a new developer -comes to the project. FSM is most effective when you use it for some -sequential steps. - - -Installation ------------- - -.. code:: bash - - $ pip install django-fsm - -Or, for the latest git version - -.. code:: bash - - $ pip install -e git://github.com/kmmbvnr/django-fsm.git#egg=django-fsm - -Usage ------ - -Add FSMState field to your model - -.. code:: python - - from django_fsm import FSMField, transition - - class BlogPost(models.Model): - state = FSMField(default='new') - -Use the ``transition`` decorator to annotate model methods - -.. code:: python - - @transition(field=state, source='new', target='published') - def publish(self): - """ - This function may contain side-effects, - like updating caches, notifying users, etc. - The return value will be discarded. - """ - -The ``field`` parameter accepts both a string attribute name or an -actual field instance. - -If calling publish() succeeds without raising an exception, the state -field will be changed, but not written to the database. - -.. code:: python - - from django_fsm import can_proceed - - def publish_view(request, post_id): - post = get_object_or_404(BlogPost, pk=post_id) - if not can_proceed(post.publish): - raise PermissionDenied - - post.publish() - post.save() - return redirect('/') - -If some conditions are required to be met before changing the state, use -the ``conditions`` argument to ``transition``. ``conditions`` must be a -list of functions taking one argument, the model instance. The function -must return either ``True`` or ``False`` or a value that evaluates to -``True`` or ``False``. If all functions return ``True``, all conditions -are considered to be met and the transition is allowed to happen. If one -of the functions returns ``False``, the transition will not happen. -These functions should not have any side effects. - -You can use ordinary functions - -.. code:: python - - def can_publish(instance): - # No publishing after 17 hours - if datetime.datetime.now().hour > 17: - return False - return True - -Or model methods - -.. code:: python - - def can_destroy(self): - return self.is_under_investigation() - -Use the conditions like this: - -.. code:: python - - @transition(field=state, source='new', target='published', conditions=[can_publish]) - def publish(self): - """ - Side effects galore - """ - - @transition(field=state, source='*', target='destroyed', conditions=[can_destroy]) - def destroy(self): - """ - Side effects galore - """ - -You can instantiate a field with ``protected=True`` option to prevent -direct state field modification. - -.. code:: python - - class BlogPost(models.Model): - state = FSMField(default='new', protected=True) - - model = BlogPost() - model.state = 'invalid' # Raises AttributeError - -Note that calling -`refresh_from_db `_ -on a model instance with a protected FSMField will cause an exception. - -``source`` state -~~~~~~~~~~~~~~~~ - -``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 excluding ``target`` state. - -``target`` 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='*', - target=RETURN_VALUE('for_moderators', 'published')) - def publish(self, is_public=False): - return 'for_moderators' if is_public else 'published' - - @transition( - field=state, - source='for_moderators', - target=GET_STATE( - lambda self, allowed: 'published' if allowed else 'rejected', - states=['published', 'rejected'])) - def moderate(self, allowed): - pass - - @transition( - field=state, - source='for_moderators', - target=GET_STATE( - lambda self, **kwargs: 'published' if kwargs.get("allowed", True) else 'rejected', - states=['published', 'rejected'])) - def moderate(self, allowed=True): - pass - - -``custom`` properties -~~~~~~~~~~~~~~~~~~~~~ - -Custom properties can be added by providing a dictionary to the -``custom`` keyword on the ``transition`` decorator. - -.. code:: python - - @transition(field=state, - source='*', - target='onhold', - custom=dict(verbose='Hold for legal reasons')) - def legal_hold(self): - """ - Side effects galore - """ - -``on_error`` state -~~~~~~~~~~~~~~~~~~ - -If the transition method raises an exception, you can provide a -specific target state - -.. code:: python - - @transition(field=state, source='new', target='published', on_error='failed') - def publish(self): - """ - Some exception could happen here - """ - -``state_choices`` -~~~~~~~~~~~~~~~~~ - -Instead of passing a two-item iterable ``choices`` you can instead use the -three-element ``state_choices``, the last element being a string reference -to a model proxy class. - -The base class instance would be dynamically changed to the corresponding Proxy -class instance, depending on the state. Even for queryset results, you -will get Proxy class instances, even if the QuerySet is executed on the base class. - -Check the `test -case `__ -for example usage. Or read about `implementation -internals `__ - -Permissions -~~~~~~~~~~~ - -It is common to have permissions attached to each model transition. -``django-fsm`` handles this with ``permission`` keyword on the -``transition`` decorator. ``permission`` accepts a permission string, or -callable that expects ``instance`` and ``user`` arguments and returns -True if the user can perform the transition. - -.. code:: python - - @transition(field=state, source='*', target='published', - permission=lambda instance, user: not user.has_perm('myapp.can_make_mistakes')) - def publish(self): - pass - - @transition(field=state, source='*', target='removed', - permission='myapp.can_remove_post') - def remove(self): - pass - -You can check permission with ``has_transition_permission`` method - -.. code:: python - - from django_fsm import has_transition_perm - def publish_view(request, post_id): - post = get_object_or_404(BlogPost, pk=post_id) - if not has_transition_perm(post.publish, request.user): - raise PermissionDenied - - post.publish() - post.save() - return redirect('/') - -Model methods -~~~~~~~~~~~~~ - -``get_all_FIELD_transitions`` Enumerates all declared transitions - -``get_available_FIELD_transitions`` Returns all transitions data -available in current state - -``get_available_user_FIELD_transitions`` Enumerates all transitions data -available in current state for provided user - -Foreign Key constraints support -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If you store the states in the db table you could use FSMKeyField to -ensure Foreign Key database integrity. - -In your model : - -.. code:: python - - class DbState(models.Model): - id = models.CharField(primary_key=True, max_length=50) - label = models.CharField(max_length=255) - - def __unicode__(self): - return self.label - - - class BlogPost(models.Model): - state = FSMKeyField(DbState, default='new') - - @transition(field=state, source='new', target='published') - def publish(self): - pass - -In your fixtures/initial\_data.json : - -.. code:: json - - [ - { - "pk": "new", - "model": "myapp.dbstate", - "fields": { - "label": "_NEW_" - } - }, - { - "pk": "published", - "model": "myapp.dbstate", - "fields": { - "label": "_PUBLISHED_" - } - } - ] - -Note : source and target parameters in @transition decorator use pk -values of DBState model as names, even if field "real" name is used, -without \_id postfix, as field parameter. - -Integer Field support -~~~~~~~~~~~~~~~~~~~~~ - -You can also use ``FSMIntegerField``. This is handy when you want to use -enum style constants. - -.. code:: python - - class BlogPostStateEnum(object): - NEW = 10 - PUBLISHED = 20 - HIDDEN = 30 - - class BlogPostWithIntegerField(models.Model): - state = FSMIntegerField(default=BlogPostStateEnum.NEW) - - @transition(field=state, source=BlogPostStateEnum.NEW, target=BlogPostStateEnum.PUBLISHED) - def publish(self): - pass - -Signals -~~~~~~~ - -``django_fsm.signals.pre_transition`` and -``django_fsm.signals.post_transition`` are called before and after -allowed transition. No signals on invalid transition are called. - -Arguments sent with these signals: - -**sender** The model class. - -**instance** The actual instance being processed - -**name** Transition name - -**source** Source model state - -**target** Target model state - -Optimistic locking ------------------- - -``django-fsm`` provides optimistic locking mixin, to avoid concurrent -model state changes. If model state was changed in database -``django_fsm.ConcurrentTransition`` exception would be raised on -model.save() - -.. code:: python - - from django_fsm import FSMField, ConcurrentTransitionMixin - - class BlogPost(ConcurrentTransitionMixin, models.Model): - state = FSMField(default='new') - -For guaranteed protection against race conditions caused by concurrently -executed transitions, make sure: - -- Your transitions do not have any side effects except for changes in the database, -- You always run the save() method on the object within ``django.db.transaction.atomic()`` block. - -Following these recommendations, you can rely on -ConcurrentTransitionMixin to cause a rollback of all the changes that -have been executed in an inconsistent (out of sync) state, thus -practically negating their effect. - -Drawing transitions -------------------- - -Renders a graphical overview of your models states transitions - -You need ``pip install "graphviz>=0.4"`` library and add ``django_fsm`` to -your ``INSTALLED_APPS``: - -.. code:: python - - INSTALLED_APPS = ( - ... - 'django_fsm', - ... - ) - -.. code:: bash - - # Create a dot file - $ ./manage.py graph_transitions > transitions.dot - - # Create a PNG image file only for specific model - $ ./manage.py graph_transitions -o blog_transitions.png myapp.Blog - -Changelog ---------- - - -django-fsm 2.8.1 2022-08-15 -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -- Improve fix for get_available_FIELD_transition - -.. |Build Status| image:: https://travis-ci.org/viewflow/django-fsm.svg?branch=master - :target: https://travis-ci.org/viewflow/django-fsm diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..8113b66 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,388 @@ +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. + +[[package]] +name = "asgiref" +version = "3.7.2" +description = "ASGI specs, helper code, and adapters" +optional = false +python-versions = ">=3.7" +files = [ + {file = "asgiref-3.7.2-py3-none-any.whl", hash = "sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e"}, + {file = "asgiref-3.7.2.tar.gz", hash = "sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4", markers = "python_version < \"3.11\""} + +[package.extras] +tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] + +[[package]] +name = "backports-zoneinfo" +version = "0.2.1" +description = "Backport of the standard library zoneinfo module" +optional = false +python-versions = ">=3.6" +files = [ + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:da6013fd84a690242c310d77ddb8441a559e9cb3d3d59ebac9aca1a57b2e18bc"}, + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:89a48c0d158a3cc3f654da4c2de1ceba85263fafb861b98b59040a5086259722"}, + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:1c5742112073a563c81f786e77514969acb58649bcdf6cdf0b4ed31a348d4546"}, + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-win32.whl", hash = "sha256:e8236383a20872c0cdf5a62b554b27538db7fa1bbec52429d8d106effbaeca08"}, + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:8439c030a11780786a2002261569bdf362264f605dfa4d65090b64b05c9f79a7"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:f04e857b59d9d1ccc39ce2da1021d196e47234873820cbeaad210724b1ee28ac"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:17746bd546106fa389c51dbea67c8b7c8f0d14b5526a579ca6ccf5ed72c526cf"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5c144945a7752ca544b4b78c8c41544cdfaf9786f25fe5ffb10e838e19a27570"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win32.whl", hash = "sha256:e55b384612d93be96506932a786bbcde5a2db7a9e6a4bb4bffe8b733f5b9036b"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a76b38c52400b762e48131494ba26be363491ac4f9a04c1b7e92483d169f6582"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:8961c0f32cd0336fb8e8ead11a1f8cd99ec07145ec2931122faaac1c8f7fd987"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e81b76cace8eda1fca50e345242ba977f9be6ae3945af8d46326d776b4cf78d1"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7b0a64cda4145548fed9efc10322770f929b944ce5cee6c0dfe0c87bf4c0c8c9"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-win32.whl", hash = "sha256:1b13e654a55cd45672cb54ed12148cd33628f672548f373963b0bff67b217328"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:4a0f800587060bf8880f954dbef70de6c11bbe59c673c3d818921f042f9954a6"}, + {file = "backports.zoneinfo-0.2.1.tar.gz", hash = "sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2"}, +] + +[package.extras] +tzdata = ["tzdata"] + +[[package]] +name = "cfgv" +version = "3.4.0" +description = "Validate configuration and produce human readable error messages." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, + {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, +] + +[[package]] +name = "coverage" +version = "7.3.2" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "coverage-7.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d872145f3a3231a5f20fd48500274d7df222e291d90baa2026cc5152b7ce86bf"}, + {file = "coverage-7.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:310b3bb9c91ea66d59c53fa4989f57d2436e08f18fb2f421a1b0b6b8cc7fffda"}, + {file = "coverage-7.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f47d39359e2c3779c5331fc740cf4bce6d9d680a7b4b4ead97056a0ae07cb49a"}, + {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa72dbaf2c2068404b9870d93436e6d23addd8bbe9295f49cbca83f6e278179c"}, + {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:beaa5c1b4777f03fc63dfd2a6bd820f73f036bfb10e925fce067b00a340d0f3f"}, + {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dbc1b46b92186cc8074fee9d9fbb97a9dd06c6cbbef391c2f59d80eabdf0faa6"}, + {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:315a989e861031334d7bee1f9113c8770472db2ac484e5b8c3173428360a9148"}, + {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d1bc430677773397f64a5c88cb522ea43175ff16f8bfcc89d467d974cb2274f9"}, + {file = "coverage-7.3.2-cp310-cp310-win32.whl", hash = "sha256:a889ae02f43aa45032afe364c8ae84ad3c54828c2faa44f3bfcafecb5c96b02f"}, + {file = "coverage-7.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c0ba320de3fb8c6ec16e0be17ee1d3d69adcda99406c43c0409cb5c41788a611"}, + {file = "coverage-7.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ac8c802fa29843a72d32ec56d0ca792ad15a302b28ca6203389afe21f8fa062c"}, + {file = "coverage-7.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:89a937174104339e3a3ffcf9f446c00e3a806c28b1841c63edb2b369310fd074"}, + {file = "coverage-7.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e267e9e2b574a176ddb983399dec325a80dbe161f1a32715c780b5d14b5f583a"}, + {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2443cbda35df0d35dcfb9bf8f3c02c57c1d6111169e3c85fc1fcc05e0c9f39a3"}, + {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4175e10cc8dda0265653e8714b3174430b07c1dca8957f4966cbd6c2b1b8065a"}, + {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf38419fb1a347aaf63481c00f0bdc86889d9fbf3f25109cf96c26b403fda1"}, + {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5c913b556a116b8d5f6ef834038ba983834d887d82187c8f73dec21049abd65c"}, + {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1981f785239e4e39e6444c63a98da3a1db8e971cb9ceb50a945ba6296b43f312"}, + {file = "coverage-7.3.2-cp311-cp311-win32.whl", hash = "sha256:43668cabd5ca8258f5954f27a3aaf78757e6acf13c17604d89648ecc0cc66640"}, + {file = "coverage-7.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10c39c0452bf6e694511c901426d6b5ac005acc0f78ff265dbe36bf81f808a2"}, + {file = "coverage-7.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4cbae1051ab791debecc4a5dcc4a1ff45fc27b91b9aee165c8a27514dd160836"}, + {file = "coverage-7.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12d15ab5833a997716d76f2ac1e4b4d536814fc213c85ca72756c19e5a6b3d63"}, + {file = "coverage-7.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c7bba973ebee5e56fe9251300c00f1579652587a9f4a5ed8404b15a0471f216"}, + {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe494faa90ce6381770746077243231e0b83ff3f17069d748f645617cefe19d4"}, + {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6e9589bd04d0461a417562649522575d8752904d35c12907d8c9dfeba588faf"}, + {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d51ac2a26f71da1b57f2dc81d0e108b6ab177e7d30e774db90675467c847bbdf"}, + {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:99b89d9f76070237975b315b3d5f4d6956ae354a4c92ac2388a5695516e47c84"}, + {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fa28e909776dc69efb6ed975a63691bc8172b64ff357e663a1bb06ff3c9b589a"}, + {file = "coverage-7.3.2-cp312-cp312-win32.whl", hash = "sha256:289fe43bf45a575e3ab10b26d7b6f2ddb9ee2dba447499f5401cfb5ecb8196bb"}, + {file = "coverage-7.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7dbc3ed60e8659bc59b6b304b43ff9c3ed858da2839c78b804973f613d3e92ed"}, + {file = "coverage-7.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f94b734214ea6a36fe16e96a70d941af80ff3bfd716c141300d95ebc85339738"}, + {file = "coverage-7.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:af3d828d2c1cbae52d34bdbb22fcd94d1ce715d95f1a012354a75e5913f1bda2"}, + {file = "coverage-7.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:630b13e3036e13c7adc480ca42fa7afc2a5d938081d28e20903cf7fd687872e2"}, + {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9eacf273e885b02a0273bb3a2170f30e2d53a6d53b72dbe02d6701b5296101c"}, + {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8f17966e861ff97305e0801134e69db33b143bbfb36436efb9cfff6ec7b2fd9"}, + {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b4275802d16882cf9c8b3d057a0839acb07ee9379fa2749eca54efbce1535b82"}, + {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:72c0cfa5250f483181e677ebc97133ea1ab3eb68645e494775deb6a7f6f83901"}, + {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cb536f0dcd14149425996821a168f6e269d7dcd2c273a8bff8201e79f5104e76"}, + {file = "coverage-7.3.2-cp38-cp38-win32.whl", hash = "sha256:307adb8bd3abe389a471e649038a71b4eb13bfd6b7dd9a129fa856f5c695cf92"}, + {file = "coverage-7.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:88ed2c30a49ea81ea3b7f172e0269c182a44c236eb394718f976239892c0a27a"}, + {file = "coverage-7.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b631c92dfe601adf8f5ebc7fc13ced6bb6e9609b19d9a8cd59fa47c4186ad1ce"}, + {file = "coverage-7.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d3d9df4051c4a7d13036524b66ecf7a7537d14c18a384043f30a303b146164e9"}, + {file = "coverage-7.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f7363d3b6a1119ef05015959ca24a9afc0ea8a02c687fe7e2d557705375c01f"}, + {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f11cc3c967a09d3695d2a6f03fb3e6236622b93be7a4b5dc09166a861be6d25"}, + {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:149de1d2401ae4655c436a3dced6dd153f4c3309f599c3d4bd97ab172eaf02d9"}, + {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3a4006916aa6fee7cd38db3bfc95aa9c54ebb4ffbfc47c677c8bba949ceba0a6"}, + {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9028a3871280110d6e1aa2df1afd5ef003bab5fb1ef421d6dc748ae1c8ef2ebc"}, + {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9f805d62aec8eb92bab5b61c0f07329275b6f41c97d80e847b03eb894f38d083"}, + {file = "coverage-7.3.2-cp39-cp39-win32.whl", hash = "sha256:d1c88ec1a7ff4ebca0219f5b1ef863451d828cccf889c173e1253aa84b1e07ce"}, + {file = "coverage-7.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b4767da59464bb593c07afceaddea61b154136300881844768037fd5e859353f"}, + {file = "coverage-7.3.2-pp38.pp39.pp310-none-any.whl", hash = "sha256:ae97af89f0fbf373400970c0a21eef5aa941ffeed90aee43650b81f7d7f47637"}, + {file = "coverage-7.3.2.tar.gz", hash = "sha256:be32ad29341b0170e795ca590e1c07e81fc061cb5b10c74ce7203491484404ef"}, +] + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "distlib" +version = "0.3.7" +description = "Distribution utilities" +optional = false +python-versions = "*" +files = [ + {file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"}, + {file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"}, +] + +[[package]] +name = "django" +version = "4.2.6" +description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." +optional = false +python-versions = ">=3.8" +files = [ + {file = "Django-4.2.6-py3-none-any.whl", hash = "sha256:a64d2487cdb00ad7461434320ccc38e60af9c404773a2f95ab0093b4453a3215"}, + {file = "Django-4.2.6.tar.gz", hash = "sha256:08f41f468b63335aea0d904c5729e0250300f6a1907bf293a65499496cdbc68f"}, +] + +[package.dependencies] +asgiref = ">=3.6.0,<4" +"backports.zoneinfo" = {version = "*", markers = "python_version < \"3.9\""} +sqlparse = ">=0.3.1" +tzdata = {version = "*", markers = "sys_platform == \"win32\""} + +[package.extras] +argon2 = ["argon2-cffi (>=19.1.0)"] +bcrypt = ["bcrypt"] + +[[package]] +name = "django-guardian" +version = "2.4.0" +description = "Implementation of per object permissions for Django." +optional = false +python-versions = ">=3.5" +files = [ + {file = "django-guardian-2.4.0.tar.gz", hash = "sha256:c58a68ae76922d33e6bdc0e69af1892097838de56e93e78a8361090bcd9f89a0"}, + {file = "django_guardian-2.4.0-py3-none-any.whl", hash = "sha256:440ca61358427e575323648b25f8384739e54c38b3d655c81d75e0cd0d61b697"}, +] + +[package.dependencies] +Django = ">=2.2" + +[[package]] +name = "filelock" +version = "3.12.4" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.8" +files = [ + {file = "filelock-3.12.4-py3-none-any.whl", hash = "sha256:08c21d87ded6e2b9da6728c3dff51baf1dcecf973b768ef35bcbc3447edb9ad4"}, + {file = "filelock-3.12.4.tar.gz", hash = "sha256:2e6f249f1f3654291606e046b09f1fd5eac39b360664c27f5aad072012f8bcbd"}, +] + +[package.extras] +docs = ["furo (>=2023.7.26)", "sphinx (>=7.1.2)", "sphinx-autodoc-typehints (>=1.24)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3)", "diff-cover (>=7.7)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "pytest-timeout (>=2.1)"] +typing = ["typing-extensions (>=4.7.1)"] + +[[package]] +name = "graphviz" +version = "0.20.1" +description = "Simple Python interface for Graphviz" +optional = false +python-versions = ">=3.7" +files = [ + {file = "graphviz-0.20.1-py3-none-any.whl", hash = "sha256:587c58a223b51611c0cf461132da386edd896a029524ca61a1462b880bf97977"}, + {file = "graphviz-0.20.1.zip", hash = "sha256:8c58f14adaa3b947daf26c19bc1e98c4e0702cdc31cf99153e6f06904d492bf8"}, +] + +[package.extras] +dev = ["flake8", "pep8-naming", "tox (>=3)", "twine", "wheel"] +docs = ["sphinx (>=5)", "sphinx-autodoc-typehints", "sphinx-rtd-theme"] +test = ["coverage", "mock (>=4)", "pytest (>=7)", "pytest-cov", "pytest-mock (>=3)"] + +[[package]] +name = "identify" +version = "2.5.30" +description = "File identification library for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "identify-2.5.30-py2.py3-none-any.whl", hash = "sha256:afe67f26ae29bab007ec21b03d4114f41316ab9dd15aa8736a167481e108da54"}, + {file = "identify-2.5.30.tar.gz", hash = "sha256:f302a4256a15c849b91cfcdcec052a8ce914634b2f77ae87dad29cd749f2d88d"}, +] + +[package.extras] +license = ["ukkonen"] + +[[package]] +name = "nodeenv" +version = "1.8.0" +description = "Node.js virtual environment builder" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +files = [ + {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, + {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, +] + +[package.dependencies] +setuptools = "*" + +[[package]] +name = "platformdirs" +version = "3.11.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = ">=3.7" +files = [ + {file = "platformdirs-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"}, + {file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"}, +] + +[package.extras] +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 = "pre-commit" +version = "3.5.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pre_commit-3.5.0-py2.py3-none-any.whl", hash = "sha256:841dc9aef25daba9a0238cd27984041fa0467b4199fc4852e27950664919f660"}, + {file = "pre_commit-3.5.0.tar.gz", hash = "sha256:5804465c675b659b0862f07907f96295d490822a450c4c40e747d0b1c6ebcb32"}, +] + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +virtualenv = ">=20.10.0" + +[[package]] +name = "pyyaml" +version = "6.0.1" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, +] + +[[package]] +name = "setuptools" +version = "68.2.2" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"}, + {file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "sqlparse" +version = "0.4.4" +description = "A non-validating SQL parser." +optional = false +python-versions = ">=3.5" +files = [ + {file = "sqlparse-0.4.4-py3-none-any.whl", hash = "sha256:5430a4fe2ac7d0f93e66f1efc6e1338a41884b7ddf2a350cedd20ccc4d9d28f3"}, + {file = "sqlparse-0.4.4.tar.gz", hash = "sha256:d446183e84b8349fa3061f0fe7f06ca94ba65b426946ffebe6e3e8295332420c"}, +] + +[package.extras] +dev = ["build", "flake8"] +doc = ["sphinx"] +test = ["pytest", "pytest-cov"] + +[[package]] +name = "typing-extensions" +version = "4.8.0" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, + {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, +] + +[[package]] +name = "tzdata" +version = "2023.3" +description = "Provider of IANA time zone data" +optional = false +python-versions = ">=2" +files = [ + {file = "tzdata-2023.3-py2.py3-none-any.whl", hash = "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"}, + {file = "tzdata-2023.3.tar.gz", hash = "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a"}, +] + +[[package]] +name = "virtualenv" +version = "20.24.6" +description = "Virtual Python Environment builder" +optional = false +python-versions = ">=3.7" +files = [ + {file = "virtualenv-20.24.6-py3-none-any.whl", hash = "sha256:520d056652454c5098a00c0f073611ccbea4c79089331f60bf9d7ba247bb7381"}, + {file = "virtualenv-20.24.6.tar.gz", hash = "sha256:02ece4f56fbf939dbbc33c0715159951d6bf14aaf5457b092e4548e1382455af"}, +] + +[package.dependencies] +distlib = ">=0.3.7,<1" +filelock = ">=3.12.2,<4" +platformdirs = ">=3.9.1,<4" + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.8" +content-hash = "a86614edcf82151f75bb613525e9ebb46b64d0cdf738dedb3bfb119eff020c94" diff --git a/poetry.toml b/poetry.toml new file mode 100644 index 0000000..53b35d3 --- /dev/null +++ b/poetry.toml @@ -0,0 +1,3 @@ +[virtualenvs] +create = true +in-project = true diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..b087299 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,48 @@ +[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" +homepage = "http://github.com/kmmbvnr/django-fsm" +repository = "http://github.com/kmmbvnr/django-fsm" +documentation = "http://github.com/kmmbvnr/django-fsm" +classifiers = [ + 'Development Status :: 5 - Production/Stable', + 'Environment :: Web Environment', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: MIT License', + 'Operating System :: OS Independent', + "Framework :: Django", + "Framework :: Django :: 3.2", + "Framework :: Django :: 4.1", + "Framework :: Django :: 4.2", + "Framework :: Django :: 5.0", + 'Programming Language :: Python', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'Framework :: Django', + 'Topic :: Software Development :: Libraries :: Python Modules', +] +packages = [{ include = "django_fsm" }] + +[tool.poetry.dependencies] +python = "^3.8" +django = ">=3.2" + +[tool.poetry.group.graphviz.dependencies] +graphviz = "*" + +[tool.poetry.group.dev.dependencies] +coverage = "*" +django-guardian = "*" +graphviz = "*" +pre-commit = "*" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/requirements.txt b/requirements.txt index 562fc68..b0b7553 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,32 @@ -django>=1.6 +asgiref==3.7.2 ; python_version >= "3.8" and python_version < "4.0" \ + --hash=sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e \ + --hash=sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed +backports-zoneinfo==0.2.1 ; python_version >= "3.8" and python_version < "3.9" \ + --hash=sha256:17746bd546106fa389c51dbea67c8b7c8f0d14b5526a579ca6ccf5ed72c526cf \ + --hash=sha256:1b13e654a55cd45672cb54ed12148cd33628f672548f373963b0bff67b217328 \ + --hash=sha256:1c5742112073a563c81f786e77514969acb58649bcdf6cdf0b4ed31a348d4546 \ + --hash=sha256:4a0f800587060bf8880f954dbef70de6c11bbe59c673c3d818921f042f9954a6 \ + --hash=sha256:5c144945a7752ca544b4b78c8c41544cdfaf9786f25fe5ffb10e838e19a27570 \ + --hash=sha256:7b0a64cda4145548fed9efc10322770f929b944ce5cee6c0dfe0c87bf4c0c8c9 \ + --hash=sha256:8439c030a11780786a2002261569bdf362264f605dfa4d65090b64b05c9f79a7 \ + --hash=sha256:8961c0f32cd0336fb8e8ead11a1f8cd99ec07145ec2931122faaac1c8f7fd987 \ + --hash=sha256:89a48c0d158a3cc3f654da4c2de1ceba85263fafb861b98b59040a5086259722 \ + --hash=sha256:a76b38c52400b762e48131494ba26be363491ac4f9a04c1b7e92483d169f6582 \ + --hash=sha256:da6013fd84a690242c310d77ddb8441a559e9cb3d3d59ebac9aca1a57b2e18bc \ + --hash=sha256:e55b384612d93be96506932a786bbcde5a2db7a9e6a4bb4bffe8b733f5b9036b \ + --hash=sha256:e81b76cace8eda1fca50e345242ba977f9be6ae3945af8d46326d776b4cf78d1 \ + --hash=sha256:e8236383a20872c0cdf5a62b554b27538db7fa1bbec52429d8d106effbaeca08 \ + --hash=sha256:f04e857b59d9d1ccc39ce2da1021d196e47234873820cbeaad210724b1ee28ac \ + --hash=sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2 +django==4.2.6 ; python_version >= "3.8" and python_version < "4.0" \ + --hash=sha256:08f41f468b63335aea0d904c5729e0250300f6a1907bf293a65499496cdbc68f \ + --hash=sha256:a64d2487cdb00ad7461434320ccc38e60af9c404773a2f95ab0093b4453a3215 +sqlparse==0.4.4 ; python_version >= "3.8" and python_version < "4.0" \ + --hash=sha256:5430a4fe2ac7d0f93e66f1efc6e1338a41884b7ddf2a350cedd20ccc4d9d28f3 \ + --hash=sha256:d446183e84b8349fa3061f0fe7f06ca94ba65b426946ffebe6e3e8295332420c +typing-extensions==4.8.0 ; python_version >= "3.8" and python_version < "3.11" \ + --hash=sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0 \ + --hash=sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef +tzdata==2023.3 ; python_version >= "3.8" and python_version < "4.0" and sys_platform == "win32" \ + --hash=sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a \ + --hash=sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda diff --git a/tox.ini b/tox.ini index c373e9c..7eb24f4 100644 --- a/tox.ini +++ b/tox.ini @@ -18,6 +18,7 @@ deps = graphviz==0.20.1 pep8==1.7.1 pyflakes==3.1.0 + commands = {posargs:python ./tests/manage.py test} @@ -30,4 +31,4 @@ python = 3.9: py39 3.10: py310 3.11: py311 - 3.12: py312 \ No newline at end of file + 3.12: py312