Search code examples
pythondjangofsm

Django FSM state transition error


from datetime import datetime

from django.db import models
from django_fsm import FSMField, transition

class Network(models.Model):
    name = models.CharField(max_length=100, unique=True)    
    prefix = models.CharField(max_length=20, default='')
    country = models.CharField(max_length=50, default='')
    client_wsdl = models.TextField(blank=True, default='')

    class Meta:
        db_table = 'network'

    def __unicode__(self):
        return u'{0}'.format(self.name)


class SmsMessage(models.Model):
    #Define State Machine
    received, started, failed, submitted, completed = "received", "started", "failed", "submitted", "completed"
    STATE_CHOICES = (
        (received, received),
        (started, started),
        (failed, failed),
        (submitted, submitted),
        (completed, completed),
    )

    message_id = models.CharField(max_length=50)
    sender_id = models.CharField(max_length=50, default='')
    msisdn = models.CharField(max_length=50)
    message = models.CharField(max_length=200)
    priority = models.CharField(max_length=50)
    status = models.CharField(max_length=20, default='')    
    state = FSMField(default=received, choices=STATE_CHOICES, protected=True)
    callback = models.CharField(max_length=200, default='')
    network = models.ForeignKey(Network, null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    modified_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = 'sms_message'

    def __unicode__(self):
        return self.message_id

    @transition(field=state, source='received', target=started)
    def started(self):
        '''
        Change message request to started state.
        '''        
        return

    @transition(field=state, source='started', target=failed)
    def failed(self):
        '''
        For started requests that cannot be submitted to Network
        hence in a failed state.
        '''
        return

    @transition(field=state, source=started, target=submitted)   
    def submitted(self):
        '''
        Change message request to submitted state.
        '''
        return

    @transition(field=state, source='submitted', target=completed)
    def completed(self):
        '''
        Request was sucessfully submited to mno and a response returned.
        '''
        return

From my code above, submitted state transitions fail with this error:

Traceback (most recent call last):
worker_1   |   File "/usr/local/lib/python2.7/dist-packages/celery/app/trace.py", line 240, in trace_task
worker_1   |     R = retval = fun(*args, **kwargs)
worker_1   |   File "/usr/local/lib/python2.7/dist-packages/celery/app/trace.py", line 438, in __protected_call__
worker_1   |     return self.run(*args, **kwargs)
worker_1   |   File "/app/sms_platform/apps/api/tasks.py", line 176, in run
worker_1   |     return self._send_sms(msisdn, message, request_id, correlation_id, mno, sender_id, pk=pk)
worker_1   |   File "/app/sms_platform/apps/api/tasks.py", line 126, in _send_sms
worker_1   |     sms_obj.submitted()
worker_1   |   File "/usr/local/lib/python2.7/dist-packages/django_fsm/__init__.py", line 512, in _change_state
worker_1   |     return fsm_meta.field.change_state(instance, func, *args, **kwargs)
worker_1   |   File "/usr/local/lib/python2.7/dist-packages/django_fsm/__init__.py", line 304, in change_state
worker_1   |     object=instance, method=method)
worker_1   | TransitionNotAllowed: Can't switch from state 'started' using method 'submitted'

The only difference is that submitted in transition source is NOT quoted like the others. Otherwise if I quote 'submitted', it works okay.

Am puzzled at this since all the states are defined at the beginning of the class.


Solution

  • The problem that you have started variable redifined to the method name

    class SmsMessage(models.Model)
        ... started, ...= ... "started", ...
    
        @transition(field=state, source='received', target=started)
        def started(self):  # <- LOL
            ....
    
       @transition(field=state, source=started, target=submitted)   
       def submitted(self):
            ....
    
    >>> print(SmsMessage.started)
    <unbound method SmsMessage.started>