Add scanning of data directory for courses.
These are then displayed at /courses.
This commit is contained in:
committed by
Matthew Mongeau
parent
cf2c5cba4f
commit
3859c2be19
78
lms/djangoapps/courseware/courses.py
Normal file
78
lms/djangoapps/courseware/courses.py
Normal file
@@ -0,0 +1,78 @@
|
||||
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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<%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)} — ${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
|
||||
|
||||
Reference in New Issue
Block a user