I have a website where users are directed to go through a sequence of pages to perform a sequence of work tasks (transcribe a paragraph, answer a survey, interact with another user, etc). For short, let's call these tasks A, B, C, etc. I'm using Django, and each task is currently implemented as a view function (or method), e.g.
def A(request). Currently, each view function has the next step hardcoded in an HTTP redirect, e.g.
My challenge is that I will soon have a requirement to create various permutations of the sequence of tasks. Sometimes I will want users to do A, B, C; for others, B, C, D; for others, A, B, D, F. How can I architect this so that I can define these permutations elegantly and concisely in my code? Intuitively, I feel like I need to genericize the HTTP redirect calls with a variable, like:
HttpResponseRedirect(reverse(next_view_function)), but I struggle to figure out where
next_view_function should be defined, and how the definition of the sequence (e.g. A, B, D) should be persisted across requests.
Can anyone lead me down the right path with a suggestion or idea? I'm happy to provide more details.
Edit in response to Bart's questions:
- The order of tasks is predetermined. Once we have defined it, we send the users a URL that takes them to the first page. They should then be routed through the tasks. One group of users might get a URL that takes them through steps A,B,C, while another group of users gets a URL that takes them through steps A,B,D. We decide in advance which users should get which URL.
- Since the order is predetermined, the next step does not depend on the results of the current task. The only requirement is for them to complete the task (we of course validate the POST data they submit as a precondition to the HTTP redirect).
Edit 2, my solution so far:
A central concept in my code architecture is that of a "Treatment" (defined in my models.py). A Treatment is a specification of the experience a user has when they use my site, such as what UI is presented to them. I split my users into groups and assign each group to a different treatment by emailing them a URL that contains the PK of their treatment.
(There are also different subclasses of Treatment, since some Treatments are totally different from others. SoccerTreatment and HockeyTreatment have little in common, so it makes sense to subclass them.)
So, the class definition for a Treatment seems like the sensible place to store the sequence of views as a Python list, as follows:
# in soccer/models.py class SoccerTreatment(BaseTreatment): world_cup = models.BooleanField() .... def views(self, request): seq = [soccer.views.ViewClassA] if self.world_cup: seq.append(soccer.views.ViewClassWorldCup) return seq # in shared/models.py class BaseTreatment(models.Model): def next_view(self, request): seq = self.views() # views() method implemented by descendant classes if request.session.get('current_view_index'): request.session['current_view_index'] += 1 else: request.session['current_view_index'] = 0 # [handle boundary cases, code snipped...] # ... return seq[request.session['current_view_index']] # ... # in views.py, at the end of a view function # ... HttpResponseRedirect(reverse(user.treatment.next_view()))
However, the design above wouldn't work, since then models.py would have to import views.py, creating a circular import. So the list elements would have to be strings (either URLs, or a the name of the view class as a string), which could be brittle since renaming my classes or URLs won't automatically rename these strings.
Maybe I am being too picky, but I wonder if there is a better design.