Commit 9f84d9b6 by Bridger Maxwell

Merge remote-tracking branch 'origin/master' into release/1.0

parents 1a8d9996 04b8dbe0
......@@ -23,6 +23,7 @@ from django.core.urlresolvers import reverse
from BeautifulSoup import BeautifulSoup
from django.core.cache import cache
from courseware.courses import check_course
from django_future.csrf import ensure_csrf_cookie
from student.models import Registration, UserProfile, PendingNameChange, PendingEmailChange, CourseEnrollment
from util.cache import cache_if_anonymous
......@@ -511,10 +512,9 @@ def accept_name_change(request):
@ensure_csrf_cookie
@cache_if_anonymous
def course_info(request, course_id):
course = check_course(course_id, course_must_be_open=False)
# 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', {'course': course})
......@@ -522,8 +522,10 @@ def course_info(request, course_id):
@login_required
@ensure_csrf_cookie
def enroll(request, course_id):
course = check_course(course_id, course_must_be_open=False)
user = request.user
enrollment = CourseEnrollment(user=user,
course_id=course_id)
course_id=course.id)
enrollment.save()
return redirect(reverse('dashboard'))
import time
import dateutil.parser
from fs.errors import ResourceNotFoundError
import logging
from path import path
......@@ -13,6 +15,21 @@ log = logging.getLogger(__name__)
class CourseDescriptor(SequenceDescriptor):
module_class = SequenceModule
def __init__(self, system, definition=None, **kwargs):
super(CourseDescriptor, self).__init__(system, definition, **kwargs)
try:
self.start = time.strptime(self.metadata["start"], "%Y-%m-%dT%H:%M")
except KeyError:
self.start = time.gmtime(0) #The epoch
log.critical("Course loaded without a start date. " + str(self.id))
except ValueError, e:
self.start = time.gmtime(0) #The epoch
log.critical("Course loaded with a bad start date. " + str(self.id) + " '" + str(e) + "'")
def has_started(self):
return time.gmtime() > self.start
@classmethod
def id_to_location(cls, course_id):
org, course, name = course_id.split('/')
......@@ -25,7 +42,7 @@ class CourseDescriptor(SequenceDescriptor):
@property
def title(self):
return self.metadata['display_name']
@property
def number(self):
return self.location.course
......
......@@ -214,7 +214,7 @@ class XModuleDescriptor(Plugin):
# A list of metadata that this module can inherit from its parent module
inheritable_metadata = (
'graded', 'due', 'graceperiod', 'showanswer', 'rerandomize',
'graded', 'start', 'due', 'graceperiod', 'showanswer', 'rerandomize',
# This is used by the XMLModuleStore to provide for locations for static files,
# and will need to be removed when that code is removed
......@@ -251,6 +251,7 @@ class XModuleDescriptor(Plugin):
display_name: The name to use for displaying this module to the user
format: The format of this module ('Homework', 'Lab', etc)
graded (bool): Whether this module is should be graded or not
start (string): The date for which this module will be available
due (string): The due date for this module
graceperiod (string): The amount of grace period to allow when enforcing the due date
showanswer (string): When to show answers for this module
......
......@@ -88,7 +88,7 @@ class XmlDescriptor(XModuleDescriptor):
# The attributes will be removed from the definition xml passed
# to definition_from_xml, and from the xml returned by definition_to_xml
metadata_attributes = ('format', 'graceperiod', 'showanswer', 'rerandomize',
'due', 'graded', 'name', 'slug')
'start', 'due', 'graded', 'name', 'slug')
# A dictionary mapping xml attribute names to functions of the value
# that return the metadata key and value
......
from collections import namedtuple
import logging
import os
from functools import wraps
from path import path
import yaml
from django.http import Http404
log = logging.getLogger('mitx.courseware.courses')
from xmodule.course_module import CourseDescriptor
from xmodule.modulestore.django import modulestore
_FIELDS = ['number', # 6.002x
'title', # Circuits and Electronics
'short_title', # Circuits
'run_id', # Spring 2012
'path', # /some/absolute/filepath/6.002x --> course.xml is in here.
'instructors', # ['Anant Agarwal']
'institution', # "MIT"
'wiki_namespace',
'grader', # a courseware.graders.CourseGrader object
#'start', # These should be datetime fields
#'end'
]
class CourseInfoLoadError(Exception):
pass
class Course(namedtuple('Course', _FIELDS)):
"""Course objects encapsulate general information about a given run of a
course. This includes things like name, grading policy, etc.
def check_course(course_id, course_must_be_open=True, course_required=True):
"""
def load_courses(courses_path):
"""Given a directory of courses, returns a list of Course objects. For the
sake of backwards compatibility, if you point it at the top level of a
specific course, it will return a list with one Course object in it.
Given a course_id, this returns the course object. By default,
if the course is not found or the course is not open yet, this
method will raise a 404.
If course_must_be_open is False, the course will be returned
without a 404 even if it is not open.
If course_required is False, a course_id of None is acceptable. The
course returned will be None. Even if the course is not required,
if a course_id is given that does not exist a 404 will be raised.
"""
courses_path = path(courses_path)
def _is_course_path(p):
return os.path.exists(p / "course_info.yaml")
log.info("Loading courses from {0}".format(courses_path))
# Compatibility: courses_path is the path for a single course
if _is_course_path(courses_path):
log.warning("course_info.yaml found in top-level ({0})"
.format(courses_path) +
" -- assuming there is only a single course.")
return [Course.load_from_path(courses_path)]
# Default: Each dir in courses_path is a separate course
courses = []
log.info("Reading courses from {0}".format(courses_path))
for course_dir_name in os.listdir(courses_path):
course_path = courses_path / course_dir_name
if _is_course_path(course_path):
log.info("Initializing course {0}".format(course_path))
courses.append(Course.load_from_path(course_path))
return courses
def create_lookup_table(courses):
return dict((c.id, c) for c in courses)
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("This course has not yet started.")
return course
......@@ -16,18 +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, cache_if_anonymous
from student.models import UserTestGroup
from courseware import grades
from courseware.courses import check_course
from xmodule.modulestore.django import modulestore
log = logging.getLogger("mitx.courseware")
template_imports = {'urllib': urllib}
def user_groups(user):
if not user.is_authenticated():
return []
......@@ -57,20 +56,19 @@ def courses(request):
context = {'courses': modulestore().get_courses()}
return render_to_response("courses.html", context)
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
def gradebook(request, course_id):
if 'course_admin' not in user_groups(request.user):
raise Http404
course_location = CourseDescriptor.id_to_location(course_id)
course = check_course(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,
......@@ -87,8 +85,8 @@ def gradebook(request, course_id):
def profile(request, course_id, student_id=None):
''' User profile. Show username, location, etc, as well as grades .
We need to allow the user to change some of these settings .'''
course = check_course(course_id)
course_location = CourseDescriptor.id_to_location(course_id)
if student_id is None:
student = request.user
else:
......@@ -98,8 +96,8 @@ def profile(request, course_id, student_id=None):
user_info = UserProfile.objects.get(user=student)
student_module_cache = StudentModuleCache(request.user, modulestore().get_item(course_location))
course, _, _, _ = get_module(request.user, request, course_location, student_module_cache)
student_module_cache = StudentModuleCache(request.user, course)
course, _, _, _ = get_module(request.user, request, course.location, student_module_cache)
context = {'name': user_info.name,
'username': student.username,
......@@ -142,7 +140,7 @@ def render_accordion(request, course, chapter, section):
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
def index(request, course_id=None, chapter=None, section=None,
def index(request, course_id, chapter=None, section=None,
position=None):
''' Displays courseware accordion, and any associated content.
If course, chapter, and section aren't all specified, just returns
......@@ -161,6 +159,8 @@ def index(request, course_id=None, chapter=None, section=None,
- HTTPresponse
'''
course = check_course(course_id)
def clean(s):
''' Fixes URLs -- we convert spaces to _ in URLs to prevent
funny encoding characters and keep the URLs readable. This undoes
......@@ -168,9 +168,6 @@ def index(request, course_id=None, chapter=None, section=None,
'''
return s.replace('_', ' ') if s is not None else None
course_location = CourseDescriptor.id_to_location(course_id)
course = modulestore().get_item(course_location)
chapter = clean(chapter)
section = clean(section)
......@@ -249,12 +246,6 @@ def jump_to(request, probname=None):
@ensure_csrf_cookie
def course_info(request, course_id):
csrf_token = csrf(request)['csrf_token']
try:
course_location = CourseDescriptor.id_to_location(course_id)
course = modulestore().get_item(course_location)
except KeyError:
raise Http404("Course not found")
course = check_course(course_id)
return render_to_response('info.html', {'csrf': csrf_token, 'course': course})
return render_to_response('info.html', {'course': course})
......@@ -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.courses 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)
course = check_course(course_id, course_required=False)
(article, err) = get_article(request, article_path, course )
if err:
return err
......@@ -72,7 +63,8 @@ def view(request, article_path, course_id=None):
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)
course = check_course(course_id, course_required=False)
(article, err) = get_article(request, article_path, course )
if err:
return err
......@@ -93,24 +85,26 @@ 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)
course = check_course(course_id, course_required=False)
#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)
def create(request, article_path, course_id=None):
course = check_course(course_id, course_required=False)
article_path_components = article_path.split('/')
# Ensure the namespace exists
......@@ -169,7 +163,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)
course = check_course(course_id, course_required=False)
(article, err) = get_article(request, article_path, course )
if err:
return err
......@@ -215,7 +210,8 @@ def edit(request, article_path, course_id=None):
return render_to_response('simplewiki/simplewiki_edit.html', d)
def history(request, article_path, page=1, course_id=None):
course = get_course(course_id)
course = check_course(course_id, course_required=False)
(article, err) = get_article(request, article_path, course )
if err:
return err
......@@ -295,9 +291,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)
course = check_course(course_id, course_required=False)
page_size = 10
......@@ -329,6 +324,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):
course = check_course(course_id, course_required=False)
# 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 +336,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 +372,9 @@ 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):
course = check_course(course_id, course_required=False)
(article, err) = get_article(request, slug, namespace if namespace else course_id )
if err:
return err
......@@ -409,6 +405,8 @@ def search_add_related(request, course_id, slug, namespace):
return HttpResponse(json, mimetype='application/json')
def add_related(request, course_id, slug, namespace):
course = check_course(course_id, course_required=False)
(article, err) = get_article(request, slug, namespace if namespace else course_id )
if err:
return err
......@@ -430,6 +428,8 @@ def add_related(request, course_id, slug, namespace):
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
def remove_related(request, course_id, namespace, slug, related_id):
course = check_course(course_id, course_required=False)
(article, err) = get_article(request, slug, namespace if namespace else course_id )
if err:
......@@ -449,8 +449,9 @@ 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)
def random_article(request, course_id=None):
course = check_course(course_id, course_required=False)
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.courses 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)
def index(request, course_id, page=0):
course = check_course(course_id)
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)
......@@ -126,6 +126,8 @@ COURSE_TITLE = "Circuits and Electronics"
ENABLE_MULTICOURSE = False # set to False to disable multicourse display (see lib.util.views.mitxhome)
QUICKEDIT = False
WIKI_ENABLED = False
###
COURSE_DEFAULT = '6.002x_Fall_2012'
......
......@@ -13,6 +13,8 @@ from .logsettings import get_logger_config
DEBUG = True
TEMPLATE_DEBUG = True
WIKI_ENABLED = True
LOGGING = get_logger_config(ENV_ROOT / "log",
logging_env="dev",
tracking_filename="tracking.log",
......
......@@ -15,6 +15,8 @@ Dir structure:
"""
from .dev import *
WIKI_ENABLED = True
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
......@@ -42,3 +44,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',
)
......@@ -98,9 +98,6 @@ if settings.COURSEWARE_ENABLED:
url(r'^xqueue/(?P<username>[^/]*)/(?P<id>.*?)/(?P<dispatch>[^/]*)$', 'courseware.module_render.xqueue_callback'),
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'),
......@@ -109,29 +106,34 @@ if settings.COURSEWARE_ENABLED:
url(r'^heartbeat$', include('heartbeat.urls')),
# Multicourse related:
url(r'^courses/?$', 'courseware.views.courses', name="courses"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/info$',
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$',
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>[^/]+/[^/]+/[^/]+)/courseware/?$',
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>[^/]*)/$',
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/courseware/(?P<chapter>[^/]*)/(?P<section>[^/]*)/$',
'courseware.views.index', name="courseware_section"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/profile$',
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/profile$',
'courseware.views.profile', name="profile"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/profile/(?P<student_id>[^/]*)/$',
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/profile/(?P<student_id>[^/]*)/$',
'courseware.views.profile'),
# TODO (vshnayder): there is no student.views.course_info.
# Where should this point instead? same as the info view?
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)$',
'student.views.course_info', name="about_course"),
)
# Multicourse wiki
if settings.WIKI_ENABLED:
urlpatterns += (
url(r'^wiki/', include('simplewiki.urls')),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/wiki/', include('simplewiki.urls')),
......
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