Ewanw
Ewanw

Reputation: 738

How to handle state / conditionally modify fields within Django forms

I have a django model representing a task. This task will go through multiple states from 'DRAFT' to 'COMPLETION' and during that time, various fields will change from requiring user input to explicitly refusing it. For example, you can't change the date a task was completed while it's in draft. Additionally i want different links to appear on the page based on the state (i.e. if it's in checking I want the submit button to say 'Complete this task')

I originally planned for the status to be a model in it's own right but could not work out a way - beyond hard coding - that would bring any value to those models so opted for an explicit list instead. (Any better approaches here would be appreciated).

The key problem I have is how to manage these fields and their states. It seems like the easiest would be to have conditional statements in the template like {% if task.status = 'ACCEPTED' %} but that seems like putting an awful lot of business logic into a template. I also get the impression that disabling a field once you're already in a template is much harder than the form.

My current approach is to use the view to manage these states but that seems messy and doesn't (for me) solve how to change link names and the like in the template.

if task.status = Task.ACCEPTED:
    form.fields['datereceived'].disabled = True
if task.status = Task.COMPLETED:
    ...

Is the view the place to manage these and is there a more pythonic/djangonic to manage these without overloading the template?

Sample code so excuse bugs:

Model

class Task(models.Model):
     STATUS_CHOICES = (
         (DRAFT,     DRAFT),
         (ALLOCATED, ALLOCATED),
         (ACCEPTED,  ACCEPTED),
         (CHECKING,  CHECKING),
         (COMPLETED, COMPLETED),
         (WITHDRAWN, WITHDRAWN),
         (ON_HOLD,   ON_HOLD),
     )

     status = models.CharField(max_length=20, choices=STATUS_CHOICES,default=DRAFT)
     datereceived = models.DateField(blank=True, null=True)
     dateworked = models.DateField(blank=True, null=True)
     datechecked = models.DateField(blank=True, null=True)
     datecompleted = models.DateField(blank=True, null=True)
     datedue = models.DateField(blank=True, null=True)

Modelform

class TaskForm(forms.ModelForm):
    class Meta:
    model = Task
    #fields = All fields listed individually but hard to pick out for sample
    widgets = {
        'datereceived': forms.DateInput(attrs={'class':'datepicker'}),
        'datedue': forms.DateInput(attrs={'class':'datepicker'}),
        'datecompleted': forms.DateInput(attrs={'class':'datepicker'}),
    }

Upvotes: 1

Views: 1645

Answers (2)

Iraj Majeed
Iraj Majeed

Reputation: 11

you can use finite state machine. django-fsm to handle states of your task. In this you can define the source and target state of every transition. for reference you can see this example. https://distillery.com/blog/building-for-flexibility-using-finite-state-machines-in-django/

Upvotes: 1

Yatharth Agarwal
Yatharth Agarwal

Reputation: 4564

Try putting the logic in the form instantiation code as so:

class TaskForm(forms.ModelForm):
    class Meta:
        model = Task

    def handle_state(self, *args, **kwargs):
        task = getattr(self, 'instance', None)
        if task:     
            if task.status = Task.ACCEPTED:
                 self.fields['datereceived'].disabled = True
             elif task.status = Task.COMPLETED:
                 ...

    def __init__(self, *args, **kwargs):
        super(TaskForm, self).__init__(*args, **kwargs)
        self.handle_state(*args, **kwargs)      

Upvotes: 1

Related Questions