Coder Social home page Coder Social logo

django-transitions's Issues

Locking mechanism for concurrency

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

Issue with GraphMachine

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

Triggers are not being created

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]: []

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.