Newer
Older
from django.shortcuts import render, get_object_or_404, redirect
from django.conf import settings
from django.contrib import messages
from django.core.urlresolvers import reverse
import django.forms as forms
from course.models import (
Course, Participation,
from markdown.extensions import Extension
from markdown.treeprocessors import Treeprocessor
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit
import re
import datetime
def get_course_file(request, course, full_name, commit_sha=None):
from os.path import join
from gittle import Gittle
repo = Gittle(join(settings.GIT_ROOT, course.identifier))
if commit_sha is None:
commit_sha = course.active_git_commit_sha
if isinstance(commit_sha, unicode):
commit_sha = commit_sha.encode()
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
tree = repo[tree_sha]
try:
for name in names[:-1]:
mode, blob_sha = tree[name.encode()]
assert mode == repo.MODE_DIRECTORY
tree = repo[blob_sha]
mode, blob_sha = tree[names[-1].encode()]
return repo[blob_sha].data
except KeyError:
# TODO: Proper 404
raise RuntimeError("resource '%s' not found" % full_name)
# {{{ tools
class Struct:
def __init__(self, entries):
for name, val in entries.iteritems():
self.__dict__[name] = dict_to_struct(val)
def __repr__(self):
return repr(self.__dict__)
def dict_to_struct(data):
if isinstance(data, list):
return [dict_to_struct(d) for d in data]
elif isinstance(data, dict):
return Struct(data)
else:
return data
# }}}
# {{{ formatting
class LinkFixerTreeprocessor(Treeprocessor):
def __init__(self, course):
Treeprocessor.__init__(self)
self.course = course
def run(self, root):
if root.tag == "a" and root.attrib["href"].startswith("flow:"):
flow_id = root.attrib["href"][5:]
root.set("href",
reverse("course.views.start_flow",
args=(self.course.identifier, flow_id)))
for child in root:
self.run(child)
class LinkFixerExtension(Extension):
def __init__(self, course):
self.course = course
Extension.__init__(self)
def extendMarkdown(self, md, md_globals):
md.treeprocessors["courseflow_link_fixer"] = \
LinkFixerTreeprocessor(self.course)
def html_body(course, text):
import markdown
return markdown.markdown(text,
extensions=[
LinkFixerExtension(course)
])
# }}}
DATE_RE_MATCH = re.compile(r"^([0-9]+)\-([01][0-9])\-([0-3][0-9])$")
WEEK_RE_MATCH = re.compile(r"^(start|end)\s+week\s+([0-9]+)$")
def parse_absolute_date_spec(date_spec):
match = DATE_RE_MATCH.match(date_spec)
if not match:
raise ValueError("invalid absolute datespec: %s" % date_spec)
return datetime.date(int(match.group(1)), int(match.group(2)), int(match.group(3)))
def parse_date_spec(course_desc, date_spec):
match = DATE_RE_MATCH.match(date_spec)
if match:
return datetime.date(int(match.group(1)), int(match.group(2)), int(match.group(3)))
match = WEEK_RE_MATCH.match(date_spec)
if match:
n = int(match.group(2)) - 1
if match.group(1) == "start":
return course_desc.first_course_week_start + datetime.timedelta(days=n*7)
elif match.group(1) == "end":
return course_desc.first_course_week_start + datetime.timedelta(days=n*7+6)
else:
raise ValueError("invalid datespec: %s" % date_spec)
raise ValueError("invalid datespec: %s" % date_spec)
def compute_module_weight(course_desc, module):
now = datetime.datetime.now().date()
for wspec in module.weight:
if hasattr(wspec, "start"):
start_date = parse_date_spec(course_desc, wspec.start)
if now < start_date:
continue
if hasattr(wspec, "end"):
end_date = parse_date_spec(course_desc, wspec.end)
if end_date < now:
continue
return wspec.value
return 0
def get_role_and_participation(request, course):
participations = Participation.objects.filter(
user=request.user, course=course)
if len(participations) > 1:
messages.add_message(request, messages.WARNING,
"Multiple enrollments found. Please contact the course staff.")
if len(participations) == 0:
participation = participations[0]
if participation.status != participation_status.active:
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
else:
return participation.role, participation
def get_course_desc(request, course):
from yaml import load
course_desc = dict_to_struct(load(get_course_file(request, course, "course.yml")))
assert isinstance(course_desc.course_start, datetime.date)
assert isinstance(course_desc.course_end, datetime.date)
# a Monday
course_desc.first_course_week_start = \
course_desc.course_start - datetime.timedelta(
days=course_desc.course_start.weekday())
for module in course_desc.modules:
module.weight = compute_module_weight(course_desc, module)
module.html_content = html_body(course, module.content)
course_desc.modules.sort(key=lambda module: module.weight)
course_desc.modules = [mod for mod in course_desc.modules if mod.weight >= 0]
return course_desc
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
from yaml import load
flow = dict_to_struct(load(get_course_file(request, course,
"flows/%s.yml" % flow_id)))
flow.description_html = html_body(course, getattr(flow, "description", None))
return flow
class AccessResult:
def __init__(self, what, credit_percent):
self.what = what
self.credit_percent = credit_percent
assert what in ["allow", "view", "deny"]
def get_flow_access(course_desc, role, flow, flow_visit):
if flow_visit is not None:
now = flow_visit.start_time.date()
else:
now = datetime.datetime.now().date()
for rule in flow.access_rules:
if role not in rule.roles:
continue
if hasattr(rule, "start"):
start_date = parse_date_spec(course_desc, rule.start)
if now < start_date:
continue
if hasattr(rule, "end"):
end_date = parse_date_spec(course_desc, rule.end)
if end_date < now:
continue
return AccessResult(rule.access, getattr(rule, "credit_percent", 100))
return AccessResult("deny", 100)
def find_flow_visit(role, participation):
return None
# {{{ views
def course_page(request, course_identifier):
course = get_object_or_404(Course, identifier=course_identifier)
course_desc = get_course_desc(request, course)
role, participation = get_role_and_participation(request, course)
return render(request, "course/course-page.html", {
"course": course,
"ick": repr(course_desc),
"course_desc": course_desc,
"participation": participation,
"role": role,
"participation_role": participation_role,
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
})
class StartForm(forms.Form):
def __init__(self, *args, **kwargs):
self.helper = FormHelper()
self.helper.form_class = "form-horizontal"
self.helper.add_input(
Submit("submit", "Get started"))
super(StartForm, self).__init__(*args, **kwargs)
def start_flow(request, course_identifier, flow_identifier):
course = get_object_or_404(Course, identifier=course_identifier)
role, participation = get_role_and_participation(request, course)
# TODO: Could be one of multiple
fvisit = find_flow_visit(role, participation)
if fvisit:
active_git_commit_sha = fvisit.active_git_commit_sha
else:
active_git_commit_sha = course.active_git_commit_sha
course_desc = get_course_desc(request, course)
flow = get_flow(request, course, flow_identifier, active_git_commit_sha)
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
access = get_flow_access(course_desc, role, flow, None)
if access.what == "deny":
messages.add_message(request, messages.WARNING,
"Access denied")
return render(request, "course/blank.html",
{"course": course, "course_desc": course_desc,},
status=403)
if request.method == "POST":
if role != role.unenrolled:
fvisit = FlowVisit()
fvisit.participation = participation
fvisit.active_git_commit_sha = course.active_git_commit_sha
return render(request, "course/flow-start-page.html", {
"course": course,
"course_desc": course_desc,
"flow": flow,
"form": StartForm(),
})
def view_flow_page(request, course_identifier, flow_identifier, page_identifier):
course = get_object_or_404(Course, identifier=course_identifier)
course_desc = get_course_desc(request, course)
return render(request, "course/flow-page.html", {
"course": course,
"course_desc": course_desc,
#"flow_desc": flow_desc,
})
def update_course(request, course_identifier):
#head_sha = repo._commit_sha("HEAD")
#if commit_sha != head_sha:
# FIXME: only instructors should see this
messages.add_message(request, messages.WARNING,
"A new revision (%s) of the course data is available "
"in the git repository." % head_sha)
# }}}
# vim: foldmethod=marker