PrimarySite / django-transitions

A wrapper of pytransitions for django
BSD 2-Clause "Simplified" License
23 stars 3 forks source link

Triggers are not being created #6

Closed matt-jay closed 3 years ago

matt-jay commented 3 years ago

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]: []
matt-jay commented 3 years ago

Last command should have been c.machine.get_triggers(c.state).