2

I'm using django-fsm to implement a state machine. The code looks like

def user_ok_to_check_me( instance, user):
    ... 

class Job( models.Model):

# ... many screenfulls of code

    @transition( field=state,  target=BOOKING, source=CHECKING, permission=user_ok_to_check_me)
    def fail_checking(self, **kwargs):
    ...

and it's working. Code readability is impaired by having that little utility function outside the class it belongs with, so I tried

class Job( models.Model):

    # ... many screenfulls of code

    @staticmethod
    def user_ok_to_check_me( instance, user):
        ... 
    @transition( field=state,  target=BOOKING, source=CHECKING, permission=user_ok_to_check_me)
    def fail_checking(self, **kwargs):

which does not work. Not sure what user_ok_to_check_me does now do, it behaves like a no-op function always returning True even when all it does is return False

Why? And is there any way to declare this little function inside the class? (It's just a little bit too long to use lambda instance, user: )

nigel222
  • 7,582
  • 1
  • 14
  • 22
  • What does it mean *which does not work*? It returns *True* when it should return *False*? How do you call this method? – Tomasz Bartkowiak Mar 04 '20 at 17:43
  • It is passed via the `permission=` argument of the `@transition` decorator which is part of the well-known django_fsm package. Yes, it behaves as if it is always returning True even when it does nothing but `return False`. The documentation shows `permission=lambda instance, user: expression` but my function is slightly too complex for that so it has to be a named function. – nigel222 Mar 04 '20 at 17:47
  • What happens when you pass `permission=Job.user_ok_to_check_me` instead? – Tomasz Bartkowiak Mar 04 '20 at 18:38
  • @Tomasz_Bartkowiak NameError: name 'Job' is not defined (because we are in the middle of the code that is defining the class `Job`?) – nigel222 Mar 05 '20 at 11:32

1 Answers1

0

Have found the answer, although I'm not sure I understand it.

class Job( models.Model):

    # ... many screenfulls of code

    # @staticmethod  #NO DECORATOR
    def user_ok_to_check_me( instance, user):
    ... 
    @transition( field=state,  target=BOOKING, source=CHECKING, permission=user_ok_to_check_me)
    def fail_checking(self, **kwargs):

The use of ok_to_check_me in @transition occurs during the execution of the code that creates the class, and not during the instantiation thereof. So it needs a reference to the actual function defined above. Application of @staticmethod to that function replaces it by something else, and whatever that is, is not acceptable to the transition decorator.

When the class is instantiated, the function gets bound to the instance. This does not, however, affect the reference to the function which @transition has already stored in its internals. In this case the binding is harmless since instance and self normally refer to the same. In other cases one might want to delete the un-intended bound function from the instance in its __init__ method (or just heavily document not to try to use it as an object method).

nigel222
  • 7,582
  • 1
  • 14
  • 22