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
from fs.errors import ResourceNotFoundError
import logging
......@@ -18,7 +18,14 @@ class CourseDescriptor(SequenceDescriptor):
def __init__(self, system, definition=None, **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):
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
from models import StudentModuleCache
from student.models import UserProfile
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 courseware import grades
from courseware.decorators import check_course
from xmodule.modulestore.django import modulestore
log = logging.getLogger("mitx.courseware")
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):
if not user.is_authenticated():
return []
......@@ -77,20 +57,18 @@ def courses(request):
'csrf': csrf_token}
return render_to_response("courses.html", context)
@check_course
@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):
raise Http404
course_location = CourseDescriptor.id_to_location(course_id)
student_objects = User.objects.all()[:100]
student_info = []
for student in student_objects:
student_module_cache = StudentModuleCache(student, modulestore().get_item(course_location))
course, _, _, _ = get_module(request.user, request, course_location, student_module_cache)
student_module_cache = StudentModuleCache(student, course)
course, _, _, _ = get_module(request.user, request, course.location, student_module_cache)
student_info.append({
'username': student.username,
'id': student.id,
......
......@@ -9,20 +9,12 @@ from django.utils import simplejson
from django.utils.translation import ugettext_lazy as _
from mitxmako.shortcuts import render_to_response
from courseware.decorators import check_course
from xmodule.course_module import CourseDescriptor
from xmodule.modulestore.django import modulestore
from models import Revision, Article, Namespace, CreateArticleForm, RevisionFormWithTitle, RevisionForm
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={}):
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
if request:
dictionary.update(csrf(request))
def view(request, article_path, course_id=None):
course = get_course(course_id)
@check_course(course_required=False)
def view(request, article_path, course=None):
(article, err) = get_article(request, article_path, course )
if err:
return err
......@@ -71,8 +62,9 @@ def view(request, article_path, course_id=None):
update_template_dictionary(d, request, course, article, article.current_revision)
return render_to_response('simplewiki/simplewiki_view.html', d)
def view_revision(request, revision_number, article_path, course_id=None):
course = get_course(course_id)
@check_course(course_required=False)
def view_revision(request, revision_number, article_path, course=None):
(article, err) = get_article(request, article_path, course )
if err:
return err
......@@ -93,24 +85,24 @@ def view_revision(request, revision_number, article_path, course_id=None):
return render_to_response('simplewiki/simplewiki_view.html', d)
def root_redirect(request, course_id=None):
course = get_course(course_id)
@check_course(course_required=False)
def root_redirect(request, course=None):
#TODO: Add a default namespace to settings.
namespace = course.wiki_namespace if course else "edX"
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:
# 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.
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 HttpResponseRedirect(reverse('wiki_view', kwargs={'course_id' : course_id, 'article_path' : root.get_path()} ))
def create(request, article_path, course_id=None):
course = get_course(course_id)
@check_course(course_required=False)
def create(request, article_path, course=None):
article_path_components = article_path.split('/')
# Ensure the namespace exists
......@@ -168,8 +160,8 @@ def create(request, article_path, course_id=None):
return render_to_response('simplewiki/simplewiki_edit.html', d)
def edit(request, article_path, course_id=None):
course = get_course(course_id)
@check_course(course_required=False)
def edit(request, article_path, course=None):
(article, err) = get_article(request, article_path, course )
if err:
return err
......@@ -214,8 +206,8 @@ def edit(request, article_path, course_id=None):
update_template_dictionary(d, request, course, article)
return render_to_response('simplewiki/simplewiki_edit.html', d)
def history(request, article_path, page=1, course_id=None):
course = get_course(course_id)
@check_course(course_required=False)
def history(request, article_path, page=1, course=None):
(article, err) = get_article(request, article_path, course )
if err:
return err
......@@ -295,10 +287,8 @@ def history(request, article_path, page=1, course_id=None):
return render_to_response('simplewiki/simplewiki_history.html', d)
def revision_feed(request, page=1, namespace=None, course_id=None):
course = get_course(course_id)
@check_course(course_required=False)
def revision_feed(request, page=1, namespace=None, course=None):
page_size = 10
if page == 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)
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
# 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.
......@@ -339,9 +330,7 @@ def search_articles(request, namespace=None, course_id = None):
querystring = request.GET.get('value', '').strip()
else:
querystring = ""
course = get_course(course_id)
results = Article.objects.all()
if namespace:
results = results.filter(namespace__name__exact = namespace)
......@@ -377,8 +366,8 @@ def search_articles(request, namespace=None, course_id = None):
update_template_dictionary(d, request, course)
return render_to_response('simplewiki/simplewiki_searchresults.html', d)
def search_add_related(request, course_id, slug, namespace):
@check_course(course_required=False)
def search_add_related(request, course, slug, namespace):
(article, err) = get_article(request, slug, namespace if namespace else course_id )
if err:
return err
......@@ -408,7 +397,8 @@ def search_add_related(request, course_id, slug, namespace):
json = simplejson.dumps({'results': results})
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 )
if err:
return err
......@@ -429,7 +419,8 @@ def add_related(request, course_id, slug, namespace):
finally:
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 )
if err:
......@@ -449,8 +440,8 @@ def remove_related(request, course_id, namespace, slug, related_id):
finally:
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
def random_article(request, course_id):
course = get_course(course_id)
@check_course(course_required=False)
def random_article(request, course=None):
from random import randint
num_arts = Article.objects.count()
article = Article.objects.all()[randint(0, num_arts-1)]
......
from django.contrib.auth.decorators import login_required
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
def index(request, course_id=None, page=0):
course_location = CourseDescriptor.id_to_location(course_id)
course = modulestore().get_item(course_location)
@check_course
def index(request, course, page=0):
return render_to_response('staticbook.html',{'page':int(page), 'course': course})
def index_shifted(request, page):
return index(request, int(page)+24)
def index_shifted(request, course_id, page):
return index(request, course_id=course_id, page=int(page)+24)
......@@ -20,6 +20,7 @@ from django.shortcuts import redirect
from mitxmako.shortcuts import render_to_response, render_to_string
from django.core.urlresolvers import reverse
from courseware.decorators import check_course
from xmodule.course_module import CourseDescriptor
from xmodule.modulestore.django import modulestore
from django_future.csrf import ensure_csrf_cookie
......@@ -495,11 +496,10 @@ def accept_name_change(request):
@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
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
return render_to_response('portal/course_about.html', {'csrf': csrf_token, 'course': course})
......@@ -520,10 +520,11 @@ def help(request):
@login_required
@check_course(course_must_be_open=False)
@ensure_csrf_cookie
def enroll(request, course_id):
def enroll(request, course):
user = request.user
enrollment = CourseEnrollment(user=user,
course_id=course_id)
course_id=course.id)
enrollment.save()
return redirect(reverse('dashboard'))
......@@ -42,3 +42,26 @@ CACHES = {
}
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:
url(r'^modx/(?P<id>.*?)/(?P<dispatch>[^/]*)$', 'courseware.module_render.modx_dispatch'), #reset_problem'),
url(r'^change_setting$', 'student.views.change_setting'),
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'^show_circuit/(?P<circuit>[^/]*)$', 'circuit.views.show_circuit'),
url(r'^edit_circuit/(?P<circuit>[^/]*)$', 'circuit.views.edit_circuit'),
......@@ -69,15 +66,21 @@ if settings.COURSEWARE_ENABLED:
# Multicourse related:
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>[^/]+/[^/]+/[^/]+)/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/(?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/(?P<student_id>[^/]*)/$', 'courseware.views.profile'),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/about$', 'student.views.course_info', name="about_course"),
)
# 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