primarysite / django-transitions Goto Github PK
View Code? Open in Web Editor NEWA wrapper of pytransitions for django
License: BSD 2-Clause "Simplified" License
A wrapper of pytransitions for django
License: BSD 2-Clause "Simplified" License
I am trying to build a basic implementation of django-transitions following the example here.
It seems to me that I did everything in line with the example. When I create an instance of my model, however (and even of just the defined mixin itself), there are no triggers. I'm at a loss at what I may have done wrong and why that leads to triggers not being created. Can anyone confirm/help?
Here is my workflow definition (lifecycles.py
):
from django_transitions.workflow import StatusBase, StateMachineMixinBase
from transitions import Machine
class ContactLifecycleStatus(StatusBase):
# Define states
ACTIVE = "ACTIVE"
INACTIVE = "INACTIVE"
BLOCKED = "BLOCKED"
# Define human-readable labels for states
# TODO: translate labels
STATE_CHOICES = (
(ACTIVE, "active"),
(INACTIVE, "inactive"),
(BLOCKED, "blocked"),
)
# Define transitions as constants
ACTIVATE = "activate"
DEACTIVATE = "deactivate"
BLOCK = "block"
UNBLOCK = "unblock"
# Define human-readable label and css class for use in Django admin
# TODO: translate labels
TRANSITION_LABELS = {
ACTIVATE: {'label': 'Activate', 'cssclass': 'default'},
DEACTIVATE: {'label': 'Deactivate'},
BLOCK: {'label': 'Block', 'cssclass': 'deletelink'},
UNBLOCK: {'label': 'Unblock', 'cssclass': 'default'},
}
# define collection of states for machine
SM_STATES = [ACTIVE, INACTIVE, BLOCKED,]
# define initial state for machine
SM_INITIAL_STATE = ACTIVE
# define transitions as list of dictionaries
SM_TRANSITIONS = [
{
"trigger": ACTIVATE,
"source": INACTIVE,
"dest": ACTIVE,
# "after": "persist_state",
},
{
"trigger": DEACTIVATE,
"source": ACTIVE,
"dest": INACTIVE,
# "after": "persist_state",
},
{
"trigger": BLOCK,
"source": ACTIVE,
"dest": BLOCKED,
# "after": "persist_state",
},
{
"trigger": UNBLOCK,
"source": BLOCKED,
"dest": ACTIVE,
# "after": "persist_state",
},
]
class ContactLifecycleMixin(StateMachineMixinBase):
status_class = ContactLifecycleStatus
machine = Machine(
model=None,
auto_transitions=False,
**status_class.get_kwargs() # noqa: C815
)
@property
def state(self):
"""Get the items workflowstate or the initial state if none is set."""
if self.lifecycle_state:
return self.lifecycle_state
return self.machine.initial
@state.setter
def state(self, value):
"""Set the items workflow state."""
self.lifecycle_state = value
This is my model class (models.py
):
import rules
from django.db import models
from django.urls import reverse
from django_countries.fields import CountryField
from shared.models import CommonModel
from .lifecycles import ContactLifecycleStatus, ContactLifecycleMixin
from .rules import is_owner
class Contact(ContactLifecycleMixin, CommonModel):
first_name = models.CharField(max_length=255, blank=False)
last_name = models.CharField(max_length=255, blank=False)
date_of_birth = models.DateField(blank=True, null=True)
lifecycle_state = models.CharField(
verbose_name="Lifecycle State",
null=False,
blank=False,
editable=False,
default=ContactLifecycleStatus.SM_INITIAL_STATE,
choices=ContactLifecycleStatus.STATE_CHOICES,
max_length=32,
)
class Meta:
abstract = False
ordering = ["last_name", "first_name"]
get_latest_by = ["created"]
rules_permissions = {
"view": rules.is_authenticated,
"add": rules.is_staff,
"change": rules.is_staff,
"delete": is_owner,
}
def __str__(self):
return self.first_name + " " + self.last_name
def get_absolute_url(self):
return reverse("contact:contact-detail", kwargs={"pk": self.pk})
When I run manage.py shell_plus
, this is what I see:
In [10]: c = Contact(first_name="John", last_name="Doe")
In [11]: c
Out[11]: <Contact: John Doe>
In [12]: c.state
Out[12]: 'ACTIVE'
In [13]: c.machine.get_transitions()
Out[13]:
[<Transition('INACTIVE', 'ACTIVE')@4359042240>,
<Transition('ACTIVE', 'INACTIVE')@4359044592>,
<Transition('ACTIVE', 'BLOCKED')@4359256192>,
<Transition('BLOCKED', 'ACTIVE')@4359303232>]
In [14]: c.machine.get_triggers()
Out[14]: []
We're considering a couple of different libraries for implementing a state machine in our Django app. One of the requirements we have is that writes or transitions are safe in a concurrent environment.
I see that transitions has threadsafe locking https://github.com/pytransitions/transitions#-threadsafe-ish-state-machine, however we're looking for something that will also lock at the database level.
Based on the documentation, I'm unsure if this library provides that. Could someone help me out?
Thanks
reference to RTD:
I've been able to follow the docs and have the example working. I tried to swap the machine
instance from Machine
to GraphMachine
and still can save the new instance using Django admin to assign the initial state, but got an exception clicking on the transition button (I tried using the Django shell and the transitions methods are present but give the same issue).
If I'm not doing something wrong, are graph machines supported? I also plan to use hierarchical machines and wonder if there could be any problem.
Environment:
Request Method: POST
Request URL: http://127.0.0.1:8000/admin/backend/machinecli/2/change/
Django Version: 3.2.7
Python Version: 3.8.10
Installed Applications:
('django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'backend')
Installed Middleware:
['django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware']
Traceback (most recent call last):
File "/home/user/devel/.venv/lib/python3.8/site-packages/django/core/handlers/exception.py", line 47, in inner
response = get_response(request)
File "/home/user/devel/.venv/lib/python3.8/site-packages/django/core/handlers/base.py", line 181, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/home/user/devel/.venv/lib/python3.8/site-packages/django/contrib/admin/options.py", line 616, in wrapper
return self.admin_site.admin_view(view)(*args, **kwargs)
File "/home/user/devel/.venv/lib/python3.8/site-packages/django/utils/decorators.py", line 130, in _wrapped_view
response = view_func(request, *args, **kwargs)
File "/home/user/devel/.venv/lib/python3.8/site-packages/django/views/decorators/cache.py", line 44, in _wrapped_view_func
response = view_func(request, *args, **kwargs)
File "/home/user/devel/.venv/lib/python3.8/site-packages/django/contrib/admin/sites.py", line 232, in inner
return view(request, *args, **kwargs)
File "/home/user/devel/.venv/lib/python3.8/site-packages/django/contrib/admin/options.py", line 1660, in change_view
return self.changeform_view(request, object_id, form_url, extra_context)
File "/home/user/devel/.venv/lib/python3.8/site-packages/django/utils/decorators.py", line 43, in _wrapper
return bound_method(*args, **kwargs)
File "/home/user/devel/.venv/lib/python3.8/site-packages/django/utils/decorators.py", line 130, in _wrapped_view
response = view_func(request, *args, **kwargs)
File "/home/user/devel/.venv/lib/python3.8/site-packages/django/contrib/admin/options.py", line 1540, in changeform_view
return self._changeform_view(request, object_id, form_url, extra_context)
File "/home/user/devel/.venv/lib/python3.8/site-packages/django/contrib/admin/options.py", line 1594, in _changeform_view
return self.response_change(request, new_object)
File "/home/user/devel/.venv/lib/python3.8/site-packages/django_transitions/admin.py", line 25, in response_change
if getattr(obj, event['transition'].name)():
File "/home/user/devel/.venv/lib/python3.8/site-packages/transitions/core.py", line 401, in trigger
return self.machine._process(func)
File "/home/user/devel/.venv/lib/python3.8/site-packages/transitions/core.py", line 1187, in _process
return trigger()
File "/home/user/devel/.venv/lib/python3.8/site-packages/transitions/core.py", line 426, in _trigger
return self._process(event_data)
File "/home/user/devel/.venv/lib/python3.8/site-packages/transitions/core.py", line 435, in _process
if trans.execute(event_data):
File "/home/user/devel/.venv/lib/python3.8/site-packages/transitions/core.py", line 276, in execute
self._change_state(event_data)
File "/home/user/devel/.venv/lib/python3.8/site-packages/transitions/extensions/diagrams.py", line 30, in _change_state
graph = event_data.machine.model_graphs[id(event_data.model)]
Exception Type: KeyError at /admin/backend/machinecli/2/change/
Exception Value: 140334884199680
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.