Add new 'resume' module for getting back to a workflow that was interrupted by a necessary redirect
This commit is contained in:
parent
aa0793241f
commit
0a2adeaddb
9 changed files with 174 additions and 1 deletions
|
@ -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'
|
||||
|
|
|
@ -2,3 +2,4 @@ from django.test import TestCase
|
|||
|
||||
from .events import *
|
||||
from .event_reminder import *
|
||||
from .speakers import *
|
||||
|
|
32
get_together/tests/speakers.py
Normal file
32
get_together/tests/speakers.py
Normal 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'))
|
||||
|
|
@ -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
1
resume/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
from resume.api import *
|
29
resume/api.py
Normal file
29
resume/api.py
Normal 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
5
resume/apps.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ResumeConfig(AppConfig):
|
||||
name = 'Resume'
|
67
resume/middleware.py
Normal file
67
resume/middleware.py
Normal 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
33
resume/tests.py
Normal 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)
|
Loading…
Reference in a new issue