Commit ecc50314 by Bridger Maxwell

Added a decorator that prevents access to course pages until the course has started.

parent 8d7c4ad8
from datetime import datetime from datetime import datetime, date
import dateutil.parser import dateutil.parser
from fs.errors import ResourceNotFoundError from fs.errors import ResourceNotFoundError
import logging import logging
...@@ -18,7 +18,14 @@ class CourseDescriptor(SequenceDescriptor): ...@@ -18,7 +18,14 @@ class CourseDescriptor(SequenceDescriptor):
def __init__(self, system, definition=None, **kwargs): def __init__(self, system, definition=None, **kwargs):
super(CourseDescriptor, self).__init__(system, definition, **kwargs) super(CourseDescriptor, self).__init__(system, definition, **kwargs)
self.start = dateutil.parser.parse(self.metadata["start"]) try:
self.start = dateutil.parser.parse(self.metadata["start"])
except KeyError:
self.start = date.fromtimestamp(0) #The epoch
log.critical("Course loaded without a start date. " + str(self.id))
except ValueError, e:
self.start = date.fromtimestamp(0) #The epoch
log.critical("Course loaded with a bad start date. " + str(self.id) + " '" + str(e) + "'")
def has_started(self): def has_started(self):
return datetime.now() > self.start return datetime.now() > self.start
......
from django.http import Http404
from xmodule.course_module import CourseDescriptor
from xmodule.modulestore.django import modulestore
def check_course(course_must_be_open=True, course_required=True):
"""
This is a decorator for views that are within a course.
It converts the string passed to the view as 'course_id'
to a course, and then passes it to the view function as
a parameter named 'course'.
This decorator also checks that the course has started.
If the course has not started, it raises a 404. This check
can be skipped by setting course_must_be_open to False.
If course_required is False, course_id is not required. If
course_id is still provided, but is None, course will be
set to None.
"""
def inner_check_course(function):
def wrapped_function(*args, **kwargs):
if course_required or 'course_id' in kwargs:
course_id = kwargs['course_id']
course = None
if course_required or course_id:
try:
course_loc = CourseDescriptor.id_to_location(course_id)
course = modulestore().get_item(course_loc)
except KeyError:
raise Http404("Course not found.")
if course_must_be_open and not course.has_started():
raise Http404
del kwargs['course_id']
kwargs['course'] = course
return function(*args, **kwargs)
return wrapped_function
# If no arguments were passed to the decorator, the function itself
# will be in course_must_be_open
if hasattr(course_must_be_open, '__call__'):
function = course_must_be_open
course_must_be_open = True
return inner_check_course(function)
else:
return inner_check_course
...@@ -16,37 +16,17 @@ from module_render import toc_for_course, get_module, get_section ...@@ -16,37 +16,17 @@ from module_render import toc_for_course, get_module, get_section
from models import StudentModuleCache from models import StudentModuleCache
from student.models import UserProfile from student.models import UserProfile
from multicourse import multicourse_settings from multicourse import multicourse_settings
from xmodule.modulestore.django import modulestore
from xmodule.course_module import CourseDescriptor
from util.cache import cache #from util.cache import cache #TODO: Where did this go? lib/util/cache no longer exists
from student.models import UserTestGroup from student.models import UserTestGroup
from courseware import grades from courseware import grades
from courseware.decorators import check_course
from xmodule.modulestore.django import modulestore
log = logging.getLogger("mitx.courseware") log = logging.getLogger("mitx.courseware")
template_imports = {'urllib': urllib} template_imports = {'urllib': urllib}
def check_course(function, course_must_be_open=True):
def inner_function(*args, **kwargs):
course_id = kwargs['course_id']
try:
course_loc = CourseDescriptor.id_to_location(course_id)
course = modulestore().get_item(course_loc)
except KeyError:
raise Http404("Course not found.")
if course_must_be_open and not course.has_started():
raise Http404
del kwargs['course_id']
kwargs['course'] = course
return function(*args, **kwargs)
return inner_function
def user_groups(user): def user_groups(user):
if not user.is_authenticated(): if not user.is_authenticated():
return [] return []
...@@ -77,20 +57,18 @@ def courses(request): ...@@ -77,20 +57,18 @@ def courses(request):
'csrf': csrf_token} 'csrf': csrf_token}
return render_to_response("courses.html", context) return render_to_response("courses.html", context)
@check_course
@cache_control(no_cache=True, no_store=True, must_revalidate=True) @cache_control(no_cache=True, no_store=True, must_revalidate=True)
def gradebook(request, course_id): def gradebook(request, course):
if 'course_admin' not in user_groups(request.user): if 'course_admin' not in user_groups(request.user):
raise Http404 raise Http404
course_location = CourseDescriptor.id_to_location(course_id)
student_objects = User.objects.all()[:100] student_objects = User.objects.all()[:100]
student_info = [] student_info = []
for student in student_objects: for student in student_objects:
student_module_cache = StudentModuleCache(student, modulestore().get_item(course_location)) student_module_cache = StudentModuleCache(student, course)
course, _, _, _ = get_module(request.user, request, course_location, student_module_cache) course, _, _, _ = get_module(request.user, request, course.location, student_module_cache)
student_info.append({ student_info.append({
'username': student.username, 'username': student.username,
'id': student.id, 'id': student.id,
......
...@@ -9,20 +9,12 @@ from django.utils import simplejson ...@@ -9,20 +9,12 @@ from django.utils import simplejson
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from mitxmako.shortcuts import render_to_response from mitxmako.shortcuts import render_to_response
from courseware.decorators import check_course
from xmodule.course_module import CourseDescriptor from xmodule.course_module import CourseDescriptor
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from models import Revision, Article, Namespace, CreateArticleForm, RevisionFormWithTitle, RevisionForm from models import Revision, Article, Namespace, CreateArticleForm, RevisionFormWithTitle, RevisionForm
import wiki_settings import wiki_settings
def get_course(course_id):
if course_id == None:
return None
course_loc = CourseDescriptor.id_to_location(course_id)
course = modulestore().get_item(course_loc)
# raise Http404("Course not found")
return course
def wiki_reverse(wiki_page, article = None, course = None, namespace=None, args=[], kwargs={}): def wiki_reverse(wiki_page, article = None, course = None, namespace=None, args=[], kwargs={}):
kwargs = dict(kwargs) # TODO: Figure out why if I don't do this kwargs sometimes contains {'article_path'} kwargs = dict(kwargs) # TODO: Figure out why if I don't do this kwargs sometimes contains {'article_path'}
...@@ -55,10 +47,9 @@ def update_template_dictionary(dictionary, request = None, course = None, articl ...@@ -55,10 +47,9 @@ def update_template_dictionary(dictionary, request = None, course = None, articl
if request: if request:
dictionary.update(csrf(request)) dictionary.update(csrf(request))
@check_course(course_required=False)
def view(request, article_path, course_id=None): def view(request, article_path, course=None):
course = get_course(course_id)
(article, err) = get_article(request, article_path, course ) (article, err) = get_article(request, article_path, course )
if err: if err:
return err return err
...@@ -71,8 +62,9 @@ def view(request, article_path, course_id=None): ...@@ -71,8 +62,9 @@ def view(request, article_path, course_id=None):
update_template_dictionary(d, request, course, article, article.current_revision) update_template_dictionary(d, request, course, article, article.current_revision)
return render_to_response('simplewiki/simplewiki_view.html', d) return render_to_response('simplewiki/simplewiki_view.html', d)
def view_revision(request, revision_number, article_path, course_id=None): @check_course(course_required=False)
course = get_course(course_id) def view_revision(request, revision_number, article_path, course=None):
(article, err) = get_article(request, article_path, course ) (article, err) = get_article(request, article_path, course )
if err: if err:
return err return err
...@@ -93,24 +85,24 @@ def view_revision(request, revision_number, article_path, course_id=None): ...@@ -93,24 +85,24 @@ def view_revision(request, revision_number, article_path, course_id=None):
return render_to_response('simplewiki/simplewiki_view.html', d) return render_to_response('simplewiki/simplewiki_view.html', d)
@check_course(course_required=False)
def root_redirect(request, course_id=None): def root_redirect(request, course=None):
course = get_course(course_id) #TODO: Add a default namespace to settings.
namespace = course.wiki_namespace if course else "edX"
try: try:
root = Article.get_root(course.wiki_namespace) root = Article.get_root(namespace)
return HttpResponseRedirect(reverse('wiki_view', kwargs={'course_id' : course_id, 'article_path' : root.get_path()} ))
except: except:
# If the root is not found, we probably are loading this class for the first time # If the root is not found, we probably are loading this class for the first time
# We should make sure the namespace exists so the root article can be created. # We should make sure the namespace exists so the root article can be created.
Namespace.ensure_namespace(course.wiki_namespace) Namespace.ensure_namespace(namespace)
err = not_found(request, course.wiki_namespace + '/', course) err = not_found(request, namespace + '/', course)
return err return err
return HttpResponseRedirect(reverse('wiki_view', kwargs={'course_id' : course_id, 'article_path' : root.get_path()} ))
def create(request, article_path, course_id=None): @check_course(course_required=False)
course = get_course(course_id) def create(request, article_path, course=None):
article_path_components = article_path.split('/') article_path_components = article_path.split('/')
# Ensure the namespace exists # Ensure the namespace exists
...@@ -168,8 +160,8 @@ def create(request, article_path, course_id=None): ...@@ -168,8 +160,8 @@ def create(request, article_path, course_id=None):
return render_to_response('simplewiki/simplewiki_edit.html', d) return render_to_response('simplewiki/simplewiki_edit.html', d)
def edit(request, article_path, course_id=None): @check_course(course_required=False)
course = get_course(course_id) def edit(request, article_path, course=None):
(article, err) = get_article(request, article_path, course ) (article, err) = get_article(request, article_path, course )
if err: if err:
return err return err
...@@ -214,8 +206,8 @@ def edit(request, article_path, course_id=None): ...@@ -214,8 +206,8 @@ def edit(request, article_path, course_id=None):
update_template_dictionary(d, request, course, article) update_template_dictionary(d, request, course, article)
return render_to_response('simplewiki/simplewiki_edit.html', d) return render_to_response('simplewiki/simplewiki_edit.html', d)
def history(request, article_path, page=1, course_id=None): @check_course(course_required=False)
course = get_course(course_id) def history(request, article_path, page=1, course=None):
(article, err) = get_article(request, article_path, course ) (article, err) = get_article(request, article_path, course )
if err: if err:
return err return err
...@@ -295,10 +287,8 @@ def history(request, article_path, page=1, course_id=None): ...@@ -295,10 +287,8 @@ def history(request, article_path, page=1, course_id=None):
return render_to_response('simplewiki/simplewiki_history.html', d) return render_to_response('simplewiki/simplewiki_history.html', d)
@check_course(course_required=False)
def revision_feed(request, page=1, namespace=None, course_id=None): def revision_feed(request, page=1, namespace=None, course=None):
course = get_course(course_id)
page_size = 10 page_size = 10
if page == None: if page == None:
...@@ -328,7 +318,8 @@ def revision_feed(request, page=1, namespace=None, course_id=None): ...@@ -328,7 +318,8 @@ def revision_feed(request, page=1, namespace=None, course_id=None):
return render_to_response('simplewiki/simplewiki_revision_feed.html', d) return render_to_response('simplewiki/simplewiki_revision_feed.html', d)
def search_articles(request, namespace=None, course_id = None): @check_course(course_required=False)
def search_articles(request, namespace=None, course = None):
# blampe: We should check for the presence of other popular django search # blampe: We should check for the presence of other popular django search
# apps and use those if possible. Only fall back on this as a last resort. # apps and use those if possible. Only fall back on this as a last resort.
# Adding some context to results (eg where matches were) would also be nice. # Adding some context to results (eg where matches were) would also be nice.
...@@ -339,9 +330,7 @@ def search_articles(request, namespace=None, course_id = None): ...@@ -339,9 +330,7 @@ def search_articles(request, namespace=None, course_id = None):
querystring = request.GET.get('value', '').strip() querystring = request.GET.get('value', '').strip()
else: else:
querystring = "" querystring = ""
course = get_course(course_id)
results = Article.objects.all() results = Article.objects.all()
if namespace: if namespace:
results = results.filter(namespace__name__exact = namespace) results = results.filter(namespace__name__exact = namespace)
...@@ -377,8 +366,8 @@ def search_articles(request, namespace=None, course_id = None): ...@@ -377,8 +366,8 @@ def search_articles(request, namespace=None, course_id = None):
update_template_dictionary(d, request, course) update_template_dictionary(d, request, course)
return render_to_response('simplewiki/simplewiki_searchresults.html', d) return render_to_response('simplewiki/simplewiki_searchresults.html', d)
@check_course(course_required=False)
def search_add_related(request, course_id, slug, namespace): def search_add_related(request, course, slug, namespace):
(article, err) = get_article(request, slug, namespace if namespace else course_id ) (article, err) = get_article(request, slug, namespace if namespace else course_id )
if err: if err:
return err return err
...@@ -408,7 +397,8 @@ def search_add_related(request, course_id, slug, namespace): ...@@ -408,7 +397,8 @@ def search_add_related(request, course_id, slug, namespace):
json = simplejson.dumps({'results': results}) json = simplejson.dumps({'results': results})
return HttpResponse(json, mimetype='application/json') return HttpResponse(json, mimetype='application/json')
def add_related(request, course_id, slug, namespace): @check_course(course_required=False)
def add_related(request, course, slug, namespace):
(article, err) = get_article(request, slug, namespace if namespace else course_id ) (article, err) = get_article(request, slug, namespace if namespace else course_id )
if err: if err:
return err return err
...@@ -429,7 +419,8 @@ def add_related(request, course_id, slug, namespace): ...@@ -429,7 +419,8 @@ def add_related(request, course_id, slug, namespace):
finally: finally:
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),))) return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
def remove_related(request, course_id, namespace, slug, related_id): @check_course(course_required=False)
def remove_related(request, course, namespace, slug, related_id):
(article, err) = get_article(request, slug, namespace if namespace else course_id ) (article, err) = get_article(request, slug, namespace if namespace else course_id )
if err: if err:
...@@ -449,8 +440,8 @@ def remove_related(request, course_id, namespace, slug, related_id): ...@@ -449,8 +440,8 @@ def remove_related(request, course_id, namespace, slug, related_id):
finally: finally:
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),))) return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
def random_article(request, course_id): @check_course(course_required=False)
course = get_course(course_id) def random_article(request, course=None):
from random import randint from random import randint
num_arts = Article.objects.count() num_arts = Article.objects.count()
article = Article.objects.all()[randint(0, num_arts-1)] article = Article.objects.all()[randint(0, num_arts-1)]
......
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from mitxmako.shortcuts import render_to_response from mitxmako.shortcuts import render_to_response
from xmodule.modulestore.django import modulestore
from xmodule.course_module import CourseDescriptor from courseware.decorators import check_course
@login_required @login_required
def index(request, course_id=None, page=0): @check_course
course_location = CourseDescriptor.id_to_location(course_id) def index(request, course, page=0):
course = modulestore().get_item(course_location)
return render_to_response('staticbook.html',{'page':int(page), 'course': course}) return render_to_response('staticbook.html',{'page':int(page), 'course': course})
def index_shifted(request, page): def index_shifted(request, course_id, page):
return index(request, int(page)+24) return index(request, course_id=course_id, page=int(page)+24)
...@@ -20,6 +20,7 @@ from django.shortcuts import redirect ...@@ -20,6 +20,7 @@ from django.shortcuts import redirect
from mitxmako.shortcuts import render_to_response, render_to_string from mitxmako.shortcuts import render_to_response, render_to_string
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from courseware.decorators import check_course
from xmodule.course_module import CourseDescriptor from xmodule.course_module import CourseDescriptor
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from django_future.csrf import ensure_csrf_cookie from django_future.csrf import ensure_csrf_cookie
...@@ -495,11 +496,10 @@ def accept_name_change(request): ...@@ -495,11 +496,10 @@ def accept_name_change(request):
@ensure_csrf_cookie @ensure_csrf_cookie
def course_info(request, course_id): @check_course(course_must_be_open=False)
def course_info(request, course):
# This is the advertising page for a student to look at the course before signing up # This is the advertising page for a student to look at the course before signing up
csrf_token = csrf(request)['csrf_token'] csrf_token = csrf(request)['csrf_token']
course_loc = CourseDescriptor.id_to_location(course_id)
course = modulestore().get_item(course_loc)
# TODO: Couse should be a model # TODO: Couse should be a model
return render_to_response('portal/course_about.html', {'csrf': csrf_token, 'course': course}) return render_to_response('portal/course_about.html', {'csrf': csrf_token, 'course': course})
...@@ -520,10 +520,11 @@ def help(request): ...@@ -520,10 +520,11 @@ def help(request):
@login_required @login_required
@check_course(course_must_be_open=False)
@ensure_csrf_cookie @ensure_csrf_cookie
def enroll(request, course_id): def enroll(request, course):
user = request.user user = request.user
enrollment = CourseEnrollment(user=user, enrollment = CourseEnrollment(user=user,
course_id=course_id) course_id=course.id)
enrollment.save() enrollment.save()
return redirect(reverse('dashboard')) return redirect(reverse('dashboard'))
...@@ -42,3 +42,26 @@ CACHES = { ...@@ -42,3 +42,26 @@ CACHES = {
} }
SESSION_ENGINE = 'django.contrib.sessions.backends.cache' SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
################################ DEBUG TOOLBAR #################################
INSTALLED_APPS += ('debug_toolbar',)
MIDDLEWARE_CLASSES += ('debug_toolbar.middleware.DebugToolbarMiddleware',)
INTERNAL_IPS = ('127.0.0.1',)
DEBUG_TOOLBAR_PANELS = (
'debug_toolbar.panels.version.VersionDebugPanel',
'debug_toolbar.panels.timer.TimerDebugPanel',
'debug_toolbar.panels.settings_vars.SettingsVarsDebugPanel',
'debug_toolbar.panels.headers.HeaderDebugPanel',
'debug_toolbar.panels.request_vars.RequestVarsDebugPanel',
'debug_toolbar.panels.sql.SQLDebugPanel',
'debug_toolbar.panels.signals.SignalDebugPanel',
'debug_toolbar.panels.logger.LoggingPanel',
# Enabling the profiler has a weird bug as of django-debug-toolbar==0.9.4 and
# Django=1.3.1/1.4 where requests to views get duplicated (your method gets
# hit twice). So you can uncomment when you need to diagnose performance
# problems, but you shouldn't leave it on.
# 'debug_toolbar.panels.profiling.ProfilingDebugPanel',
)
...@@ -57,9 +57,6 @@ if settings.COURSEWARE_ENABLED: ...@@ -57,9 +57,6 @@ if settings.COURSEWARE_ENABLED:
url(r'^modx/(?P<id>.*?)/(?P<dispatch>[^/]*)$', 'courseware.module_render.modx_dispatch'), #reset_problem'), url(r'^modx/(?P<id>.*?)/(?P<dispatch>[^/]*)$', 'courseware.module_render.modx_dispatch'), #reset_problem'),
url(r'^change_setting$', 'student.views.change_setting'), url(r'^change_setting$', 'student.views.change_setting'),
url(r'^s/(?P<template>[^/]*)$', 'static_template_view.views.auth_index'), url(r'^s/(?P<template>[^/]*)$', 'static_template_view.views.auth_index'),
url(r'^book/(?P<page>[^/]*)$', 'staticbook.views.index'),
url(r'^book-shifted/(?P<page>[^/]*)$', 'staticbook.views.index_shifted'),
url(r'^book*$', 'staticbook.views.index'),
# url(r'^course_info/$', 'student.views.courseinfo'), # url(r'^course_info/$', 'student.views.courseinfo'),
# url(r'^show_circuit/(?P<circuit>[^/]*)$', 'circuit.views.show_circuit'), # url(r'^show_circuit/(?P<circuit>[^/]*)$', 'circuit.views.show_circuit'),
url(r'^edit_circuit/(?P<circuit>[^/]*)$', 'circuit.views.edit_circuit'), url(r'^edit_circuit/(?P<circuit>[^/]*)$', 'circuit.views.edit_circuit'),
...@@ -69,15 +66,21 @@ if settings.COURSEWARE_ENABLED: ...@@ -69,15 +66,21 @@ if settings.COURSEWARE_ENABLED:
# Multicourse related: # Multicourse related:
url(r'^courses/?$', 'courseware.views.courses', name="courses"), url(r'^courses/?$', 'courseware.views.courses', name="courses"),
#About the course
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/about$', 'student.views.course_info', name="about_course"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/enroll$', 'student.views.enroll', name="enroll"),
#Inside the course
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/info$', 'courseware.views.course_info', name="info"), url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/info$', 'courseware.views.course_info', name="info"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/book$', 'staticbook.views.index', name="book"), url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/book$', 'staticbook.views.index', name="book"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/enroll$', 'student.views.enroll', name="enroll"), url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/book/(?P<page>[^/]*)$', 'staticbook.views.index'),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/book-shifted/(?P<page>[^/]*)$', 'staticbook.views.index_shifted'),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/courseware/?$', 'courseware.views.index', name="courseware"), url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/courseware/?$', 'courseware.views.index', name="courseware"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/courseware/(?P<chapter>[^/]*)/(?P<section>[^/]*)/$', 'courseware.views.index', name="courseware_section"), url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/courseware/(?P<chapter>[^/]*)/(?P<section>[^/]*)/$', 'courseware.views.index', name="courseware_section"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/profile$', 'courseware.views.profile', name="profile"), url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/profile$', 'courseware.views.profile', name="profile"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/profile/(?P<student_id>[^/]*)/$', 'courseware.views.profile'), url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/profile/(?P<student_id>[^/]*)/$', 'courseware.views.profile'),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/about$', 'student.views.course_info', name="about_course"),
) )
# Multicourse wiki # Multicourse wiki
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment