Add new 'resume' module for getting back to a workflow that was interrupted by a necessary redirect

This commit is contained in:
Michael Hall 2018-06-13 09:44:24 -04:00
parent aa0793241f
commit 0a2adeaddb
9 changed files with 174 additions and 1 deletions

View file

@ -54,6 +54,7 @@ INSTALLED_APPS = [
'get_together',
'events',
'accounts',
'resume',
]
LOGIN_URL = 'login'
@ -82,6 +83,7 @@ MIDDLEWARE = [
'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware',
'social_django.middleware.SocialAuthExceptionMiddleware',
'resume.middleware.ResumeMiddleware',
]
ROOT_URLCONF = 'get_together.urls'

View file

@ -2,3 +2,4 @@ from django.test import TestCase
from .events import *
from .event_reminder import *
from .speakers import *

View file

@ -0,0 +1,32 @@
from django.test import TestCase, Client
from django.shortcuts import resolve_url
from model_mommy import mommy
from django.contrib.auth.models import User
from events.models import Speaker, Talk
# Create your tests here.
class TalkCreationTests(TestCase):
def setUp(self):
super().setUp()
def tearDown(self):
super().tearDown()
def test_resume_adding_talk(self):
user = User.objects.create(username='testuser', password='12345', is_active=True)
c = Client()
response = c.force_login(user)
response = c.get(resolve_url('add-talk'))
assert(response.status_code == 302)
assert(response.url == resolve_url('add-speaker'))
response = c.get(resolve_url('add-speaker'))
assert(response.status_code == 200)
response = c.post(resolve_url('add-speaker'), {'title': 'test', 'bio': 'testing'})
assert(response.status_code == 302)
assert(response.url == resolve_url('add-talk'))

View file

@ -23,6 +23,8 @@ from events.models.speakers import Speaker, Talk, Presentation, SpeakerRequest
import datetime
import simplejson
from resume import set_resume, resume_or_redirect
from .teams import *
from .events import *
@ -63,7 +65,7 @@ def add_speaker(request):
speaker_form = SpeakerBioForm(request.POST, request.FILES, instance=new_speaker)
if speaker_form.is_valid():
new_speaker = speaker_form.save()
return redirect('user-talks')
return resume_or_redirect(request, 'user-talks')
else:
context = {
'speaker': new_speaker,
@ -138,6 +140,7 @@ def show_talk(request, talk_id):
def add_talk(request):
if Speaker.objects.filter(user=request.user.profile).count() < 1:
messages.add_message(request, messages.WARNING, message=_('You must create a new Speaker profile before you can add a talk'))
set_resume(request)
return redirect('add-speaker')
new_talk = Talk()

1
resume/__init__.py Normal file
View file

@ -0,0 +1 @@
from resume.api import *

29
resume/api.py Normal file
View file

@ -0,0 +1,29 @@
from django.shortcuts import redirect, resolve_url
class ResumeFailure(Exception):
pass
def set_resume(request):
try:
resume_points = request._resume_points
except AttributeError:
if not hasattr(request, 'META'):
raise TypeError(
"add_resume_point() argument must be an HttpRequest object, not "
"'%s'." % request.__class__.__name__
)
raise ResumeFailure(
'You cannot add resume points without installing '
'resume.middleware.ResumeMiddleware'
)
else:
return resume_points.add(request.get_full_path())
def resume_or_redirect(request, to, permanent=False, *args, **kwargs):
if len(request._resume_points) > 0:
resume_from = request._resume_points.pop()
return redirect(resume_from)
else:
return redirect(to, permanent, *args, **kwargs)

5
resume/apps.py Normal file
View file

@ -0,0 +1,5 @@
from django.apps import AppConfig
class ResumeConfig(AppConfig):
name = 'Resume'

67
resume/middleware.py Normal file
View file

@ -0,0 +1,67 @@
import json
from django.utils.deprecation import MiddlewareMixin
class ResumeStorage:
session_key = '_resume'
def __init__(self, request):
self.request = request
self._resume_points = self.load()
def __len__(self):
return len(self._resume_points)
def __iter__(self):
return iter(self._resume_points)
def __contains__(self, item):
return item in self._resume_points
def load(self):
"""
Retrieve a list of resume points from the request's session.
"""
if self.session_key not in self.request.session:
return []
return json.loads(self.request.session.get(self.session_key))
def store(self):
"""
Store a list of resume points to the request's session.
"""
if self._resume_points:
self.request.session[self.session_key] = json.dumps(self._resume_points)
else:
self.request.session.pop(self.session_key, None)
return []
def add(self, path):
self._resume_points.append(path)
def pop(self):
if len(self._resume_points) > 0:
return self._resume_points.pop()
else:
return None
class ResumeMiddleware(MiddlewareMixin):
"""
Middleware that handles setting resume points in a user flow.
"""
def process_request(self, request):
request._resume_points = ResumeStorage(request)
def process_response(self, request, response):
"""
Update the storage backend (i.e., save the resume points).
Raise ValueError if not all resume points could be stored and DEBUG is True.
"""
# A higher middleware layer may return a request which does not contain
# resume storage, so make no assumption that it will be there.
if hasattr(request, '_resume_points'):
request._resume_points.store()
return response

33
resume/tests.py Normal file
View file

@ -0,0 +1,33 @@
from django.test import TestCase
from django.http.request import HttpRequest
from resume import set_resume
from resume.middleware import ResumeStorage
# Create your tests here.
class ResumeTests(TestCase):
def setUp(self):
super().setUp()
self.request = HttpRequest()
self.request.path = 'test/foo'
self.request.session = {}
self.request._resume_points = ResumeStorage(self.request)
def tearDown(self):
super().tearDown()
del self.request
def test_resume_point_storage(self):
assert(len(self.request._resume_points) == 0)
no_resume_point = self.request._resume_points.pop()
assert(no_resume_point is None)
set_resume(self.request)
assert(len(self.request._resume_points) == 1)
one_resume_point = self.request._resume_points.pop()
assert(one_resume_point == 'test/foo')
assert(len(self.request._resume_points) == 0)