Commit 7e82f324 by David Ormsbee Committed by Matthew Mongeau

Add scanning of data directory for courses.

These are then displayed at /courses.
parent 8a1e275c
from collections import namedtuple
import logging
import os
from path import path
import yaml
log = logging.getLogger('mitx.courseware.courses')
_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"
'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.
"""
@property
def id(self):
return "{0.institution},{0.number},{0.run_id}".format(self)
@classmethod
def load_from_path(cls, course_path):
course_path = path(course_path) # convert it from string if necessary
try:
with open(course_path / "course_info.yaml") as course_info_file:
course_info = yaml.load(course_info_file)
summary = course_info['course']
summary.update(path=course_path, grader=None)
return cls(**summary)
except Exception as ex:
log.exception(ex)
raise CourseInfoLoadError("Could not read course info: {0}:{1}"
.format(type(ex).__name__, ex))
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.
"""
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)
......@@ -33,6 +33,14 @@ etree.set_default_parser(etree.XMLParser(dtd_validation=False, load_dtd=False,
template_imports={'urllib':urllib}
@ensure_csrf_cookie
def courses(request):
csrf_token = csrf(request)['csrf_token']
# TODO: Clean up how 'error' is done.
context = {'courses' : settings.COURSES,
'csrf' : csrf_token}
return render_to_response("courses.html", context)
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
def gradebook(request):
if 'course_admin' not in content_parser.user_groups(request.user):
......
......@@ -471,8 +471,3 @@ def course_info(request):
# TODO: Couse should be a model
return render_to_response('course_info.html', {'csrf': csrf_token })
@ensure_csrf_cookie
def courses(request):
csrf_token = csrf(request)['csrf_token']
# TODO: Clean up how 'error' is done.
return render_to_response('courses.html', {'csrf': csrf_token })
......@@ -64,6 +64,11 @@ sys.path.append(PROJECT_ROOT / 'lib')
sys.path.append(COMMON_ROOT / 'djangoapps')
sys.path.append(COMMON_ROOT / 'lib')
######### EDX dormsbee/portal changes #################
from courseware.courses import load_courses
COURSES = load_courses(ENV_ROOT / "data")
#######################################################
################################## MITXWEB #####################################
# This is where we stick our compiled template files. Most of the app uses Mako
# templates
......
<%namespace name='static' file='static_content.html'/>
%for i in xrange(6):
%for course in courses:
<article class="course">
<a href="/course_info">
<div class="cover">
......@@ -10,8 +10,8 @@
</div>
<section class="info">
<hgroup>
<h2>18th Century History</h2>
<p>Adam Smith, Harvard University</p>
<h2>${course.title}</h2>
<p>${",".join(course.instructors)} &mdash; ${course.institution}</p>
</hgroup>
<div class="edit">Register</div>
<section class="meta">
......
......@@ -11,7 +11,8 @@
</header>
<section class="container">
<%include file="course_filter.html" />
## I'm removing this for now since we aren't using it for the fall.
## <%include file="course_filter.html" />
<section class="courses">
<%include file="course.html" />
</section>
......
......@@ -14,7 +14,6 @@ urlpatterns = ('',
url(r'^$', 'student.views.index'), # Main marketing page, or redirect to courseware
url(r'^dashboard$', 'student.views.dashboard'),
url(r'^course_info$', 'student.views.course_info'),
url(r'^courses$', 'student.views.courses'),
url(r'^change_email$', 'student.views.change_email_request'),
url(r'^email_confirm/(?P<key>[^/]*)$', 'student.views.confirm_email_change'),
url(r'^change_name$', 'student.views.change_name_request'),
......@@ -74,6 +73,9 @@ if settings.COURSEWARE_ENABLED:
url(r'^save_circuit/(?P<circuit>[^/]*)$', 'circuit.views.save_circuit'),
url(r'^calculate$', 'util.views.calculate'),
url(r'^heartbeat$', include('heartbeat.urls')),
# Multicourse related:
url(r'^courses$', 'courseware.views.courses'),
)
if settings.ENABLE_MULTICOURSE:
......
......@@ -28,3 +28,4 @@ django_nose
nosexcover
rednose
-e common/lib/xmodule
PyYAML
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