4

I've got source and target rule-based transition decorators working well in django-fsm (Finite State Machine). Now I'm trying to add permissions handling. This seems straightforward, but it seems that no matter what I do, the transition is executed, regardless the user's permissions or lack thereof. I've tried with Django permission strings, and I've tried with lambda, per the documentation. I've tried all of these:

@transition(field=state, source='prog', target='appr', permission='claims.change_claim')

and

@transition(field=state, source='prog', target='appr', permission=lambda instance, user: not user.has_perm('claims.change_claim'),)

and, just as a double-check, since permission should respond to any callable returning True/False, simply:

@transition(field=state, source='prog', target='appr', permission=False)
def approve(self):

Which should raise a TransitionNotAllowed for all users when accessing the transition. But nope - even basic users with no permissions can still execute the transition (claim.approve()).

To prove that I've got permission string right:

print(has_transition_perm(claim.approve, request.user))

prints False. I am doing validation as follows (works for source/target):

class ClaimEditForm(forms.ModelForm):
    '''
    Some users can transition claims through allowable states
    '''

    def clean_state(self):
        state = self.cleaned_data['state']
        if state == 'appr':
            try:
                self.instance.approve()
            except TransitionNotAllowed:
                raise forms.ValidationError("Claim could not be approved")
        return state

    class Meta:
        model = Claim
        fields = (
            'state',
        )

and the view handler is the standard:

if request.method == "POST":
    claim_edit_form = ClaimEditForm(request.POST, instance=claim)
    if claim_edit_form.is_valid():  # Validate transition rules

What am I missing? Thanks.

shacker
  • 14,712
  • 8
  • 89
  • 89

1 Answers1

1

The problem turned out to be that the permission property does validation differently from the source/target validators. Rather than the decorator raising errors, you must evaluate the permissions established in the decorator elsewhere in your code. So to perform permission validation from a form, you need to pass in the user object, receive user in the form's init, and then compare against the result of has_transition_perm. So this works:

# model
@transition(field=state, source='prog', target='appr', permission='claims.change_claim')
def approve(self):
....

# view
if request.method == "POST":
    claim_edit_form = ClaimEditForm(request.user, request.POST, instance=claim)
        ....

# form
from django_fsm import has_transition_perm

class ClaimEditForm(forms.ModelForm):
    '''
    Some users can transition claims through allowable states
    (see permission property on claim.approve() decorator)
    '''

    def __init__(self, user, *args, **kwargs):
        # We need to pass the user into the form to validate permissions
        self.user = user
        super(ClaimEditForm, self).__init__(*args, **kwargs)

    def clean_state(self):
        state = self.cleaned_data['state']
        if state == 'appr':
            if not has_transition_perm(self.instance.approve, self.user):
                raise forms.ValidationError("You do not have permission for this transition")
shacker
  • 14,712
  • 8
  • 89
  • 89