views:

86

answers:

1

I'm developing a project management application in Django that requires a somewhat linear response process involving different groups of users (as in Django auth Groups). Each step in the response process has several response options (most options unique to the step) and is assigned to a user within a particular group. The next step in the process is determined by the user's response, and occasionally additional information may need to be requested from one of the project's members.

The problem is that my current implementation seems rather cumbersome and I am certain there is a better way to keep track of the response process. I was hoping someone could provide some insight into a more robust solution.

As a simple example, consider a Project with the following user Groups: Sales Rep, Sales Manager, and Project Manager. The models currently looks like this:

class Project(models.Model):  
    assigned_to = models.ForeignKey(User, related_name="projects_assigned_to") #Indicates which user needs to respond next.  Will be sales_rep, sales_mgr, or project_mgr.
    sales_rep = models.ForeignKey(User, related_name="sales_rep_projects") #choices limited to "Sales Rep" Group  
    sales_mgr = models.ForeignKey(User, related_name="sales_mgr_projects") #choices limited to "Sales Manager" Group 
    project_mgr = models.ForeignKey(User, related_name="project_mgr_projects") #choices limited to "Project Manager" Group
    current_step = models.ForeignKey(Step, related_name="projects_with_current_step")
    previous_step = models.ForeignKey(Step, related_name="projects_with_previous_step")
    status = models.ForeignKey(Status) #Automatically assigned according to the user's response.  Includes things like "On Track", "On Hold", "Rejected", "Accepted", etc.

class Step(models.Model):
    name = models.CharField(max_length=50) 

class Status(models.Model):
    name = models.CharField(max_length=50) 

Here's a simple overview of how the process might work:

  1. Sales Rep creates a new project and it is assigned to Sales Manager
  2. Sales Manager is presented with the following options:
    (a) approve the project or
    (b) request more information from the Sales Rep
  3. If the project is approved, assign to Project Manager who is presented with the following options:
    (a) commence the project
    (b) reject the project
    (c) request more information from the Sales Rep or Sales Manager
  4. If more information is requested from a user, the project is assigned to that user and they just need to provide a textbox response. However, once their response has been received, the project needs to return to the previous step (this is why I keep track of current_step and previous_step above). In this example, if Project Manager requests more information from the Sales Rep, once the Sales Rep responds the project should be assigned back to the Project Manager with the same response options that he had before (commence, reject, request more information).

The full process has about 10 or so steps like these.

To complicate things, I also need to be able to display the response chosen for each step. For example, if the Sales Manager approves the project, it should display "Sales Manager approved the project" along with any comments they may have. The model looks like this:

class Response(models.Model):
    comment = models.TextField()
    response_action = models.ForeignKey(ResponseAction)
    submitted = models.DateTimeField()

class ResponseAction(models.Model):
     """ I.e. 'Sales Manager approved the project', 'Project Manager commenced the project'"""  
     name = models.CharField(max_length=100)

Right now the logic for each response action is hard coded in the view, and there's no formal relationship between one step and another. I feel like there's a better model structure or data structure I should be using to keep track of this workflow, but I've been working with the current system for so long that I'm having trouble thinking about it differently. Any insight or inspiration would be greatly appreciated! Let me know if I need to clarify anything.

A: 

Make more use of the Step model. You can have it hold the possible next steps as foreign keys. This way you can edit the flow by changing data (using the admin for example, instead of hardcoding it). Maybe something like:

class Step(models.Model):
    name = models.CharField(max_length=50)
    responsible_role = models.CharField(max_length=50) # this contains 'sales_rep', 'sales_mgr' etc
    allowed_actions = models.ManyToManyField('AllowedAction')

class AllowedAction(models.Model):
    name = models.CharField(max_length=50)
    next_step = models.ForeignKey('Step') # the next step, if this action is chosen

Seperate the actual project history to another model:

class ProjectHistoryStep(models.Model):
    timestamp = models.DateTimeField()
    step = models.ForeignKey('Step')
    project = models.ForeignKey('Project')  

You can use this model to track the actual progress of your projects (don't forget that models have get_next_by_FOO).

You'll only need 1 view that handles all the logic (it should just call some method on the Project class to do the actual work) - check what state the project is in now (latest ProjectHistoryStep for that project), and what was the User's action, and act accordingly.

Ofri Raviv
Thanks for your response, this looks really promising! I'll play around with it later today and let you know how it goes.
godshall
I wanted to run one other thing by you. For some Allowed Actions, there are several different fields that need to be filled out. Should I specify these fields at the form level or create another ManyToMany field for something like AllowedActionOption? Thanks for all your help.
godshall
If its simple (for example, they are all text boxes, just with different names), then you should create another model with M2M from AllowedAction to it. If its more complex (some fields are FK to other places, some are texts, some are URLs etc), then uses django's forms, and just specify in the Allowed action which form to use.
Ofri Raviv
The fields are more complex, so I'll go the Django forms route. I like the idea of specifying the form class in the AllowedAction model, but it seems I would need to somehow dynamically combine these forms into one, larger form in the view. Can you recommend a solution for this? As an alternative, I could specify the form class in the Step model and create one form that includes all the fields, but this approach is not as dynamic and separates the fields from their AllowedAction. Either way, I like the direction this is heading and have greatly benefited from your help.
godshall
take a look at this awesome presentation by Alex Gaynor: http://www.slideshare.net/kingkilr/forms-getting-your-moneys-worth for some ideas on how to generate dynamic forms.
Ofri Raviv