Skip to content

Commit 5066741

Browse files
committed
Show course title instead of course ID in course list and assignment list GUI
1 parent ba2a404 commit 5066741

File tree

7 files changed

+78
-12
lines changed

7 files changed

+78
-12
lines changed

demos/demo_multiple_classes/global_nbgrader_config.py

+4
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,7 @@
22
c = get_config()
33
c.Exchange.path_includes_course = True
44
c.Authenticator.plugin_class = JupyterHubAuthPlugin
5+
c.NbGrader.course_titles = {
6+
'course101': 'Course 101',
7+
'course123': 'Course 123'
8+
}

nbgrader/apps/baseapp.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from jupyter_core.application import JupyterApp
1313
from textwrap import dedent
1414
from tornado.log import LogFormatter
15-
from traitlets import Unicode, List, Bool, Instance, default
15+
from traitlets import Unicode, List, Bool, Instance, default, Dict
1616
from traitlets.config.application import catch_config_error
1717
from traitlets.config.loader import Config
1818

@@ -68,6 +68,16 @@ class NbGrader(JupyterApp):
6868

6969
_log_formatter_cls = LogFormatter
7070

71+
course_titles = Dict(
72+
{},
73+
help=dedent(
74+
"""
75+
Dict mapping course IDs to human readable course titles. If there is
76+
no title for a course, ID is shown.
77+
"""
78+
)
79+
).tag(config=True)
80+
7181
@default("log_level")
7282
def _log_level_default(self) -> int:
7383
return logging.INFO

nbgrader/coursedir.py

+15
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,21 @@ def _validate_course_id(self, proposal):
3131
self.log.warning("course_id '%s' has trailing whitespace, stripping it away", proposal['value'])
3232
return proposal['value'].strip()
3333

34+
course_title = Unicode(
35+
'',
36+
help=dedent(
37+
"""
38+
A human readable course name for display purposes.
39+
"""
40+
)
41+
).tag(config=True)
42+
43+
@validate('course_title')
44+
def _validate_course_title(self, proposal):
45+
if proposal['value'].strip() != proposal['value']:
46+
self.log.warning("course_title '%s' has trailing whitespace, stripping it away", proposal['value'])
47+
return proposal['value'].strip()
48+
3449
student_id = Unicode(
3550
"*",
3651
help=dedent(

nbgrader/server_extensions/assignment_list/handlers.py

+14-1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,16 @@ def load_config(self):
5151

5252
return app.config
5353

54+
def get_course_titles(self):
55+
paths = jupyter_config_path()
56+
paths.insert(0, os.getcwd())
57+
58+
app = NbGrader()
59+
app.config_file_paths.append(paths)
60+
app.load_config_file()
61+
62+
return app.course_titles
63+
5464
@contextlib.contextmanager
5565
def get_assignment_dir_config(self):
5666

@@ -186,9 +196,12 @@ def list_courses(self):
186196
if not assignments["success"]:
187197
return assignments
188198

199+
course_ids = list(set([x["course_id"] for x in assignments["value"]]))
200+
titles_dict = self.get_course_titles()
201+
course_ids.sort(key=lambda x: titles_dict.get(x, x))
189202
retvalue = {
190203
"success": True,
191-
"value": sorted(list(set([x["course_id"] for x in assignments["value"]])))
204+
"value": [{"course_id": x, "course_title": titles_dict.get(x, x)} for x in course_ids]
192205
}
193206

194207
return retvalue

nbgrader/server_extensions/course_list/handlers.py

+23
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,16 @@ def load_config(self):
5151
app.load_config_file()
5252

5353
return app.config
54+
55+
def get_course_titles(self):
56+
paths = jupyter_config_path()
57+
paths.insert(0, os.getcwd())
58+
59+
app = NbGrader()
60+
app.config_file_paths.append(paths)
61+
app.load_config_file()
62+
63+
return app.course_titles
5464

5565
@gen.coroutine
5666
def check_for_local_formgrader(self, config):
@@ -77,8 +87,12 @@ def check_for_local_formgrader(self, config):
7787
coursedir = CourseDirectory(config=config)
7888

7989
if status:
90+
title = coursedir.course_title
91+
if not title:
92+
title = coursedir.course_id
8093
raise gen.Return([{
8194
'course_id': coursedir.course_id,
95+
'course_title': title,
8296
'url': base_url + '/formgrader',
8397
'kind': 'local'
8498
}])
@@ -111,8 +125,12 @@ def check_for_noauth_jupyterhub_formgraders(self, config):
111125
self.log.error("Formgrader not available at URL: %s", url)
112126
raise gen.Return([])
113127

128+
title = coursedir.course_title
129+
if not title:
130+
title = coursedir.course_id
114131
courses = [{
115132
'course_id': coursedir.course_id,
133+
'course_title': title,
116134
'url': url + "/lab?formgrader=true",
117135
'kind': 'jupyterhub'
118136
}]
@@ -149,13 +167,18 @@ def check_for_jupyterhub_formgraders(self, config):
149167
raise gen.Return([])
150168

151169
courses = []
170+
course_titles = self.get_course_titles()
152171
for course in course_names:
153172
if course not in services:
154173
self.log.warning("Couldn't find formgrader for course '%s'", course)
155174
continue
156175
service = services[course]
176+
title = course_titles.get(course)
177+
if not title:
178+
title = course
157179
courses.append({
158180
'course_id': course,
181+
'course_title': title,
159182
'url': self.get_base_url() + service['prefix'].rstrip('/') + "/lab?formgrader=true",
160183
'kind': 'jupyterhub'
161184
})

src/assignment_list/assignmentlist.ts

+9-9
Original file line numberDiff line numberDiff line change
@@ -580,10 +580,10 @@ export class CourseList{
580580
dropdown_selector: string;
581581
refresh_selector: string;
582582
assignment_list: AssignmentList;
583-
current_course: string;
583+
current_course: { [key: string]: string }
584584
options = new Map();
585585
base_url: string;
586-
data : string[];
586+
data : { [key: string]: string }[];
587587
course_list_element : HTMLUListElement;
588588
default_course_element: HTMLButtonElement;
589589
dropdown_element: HTMLButtonElement;
@@ -673,7 +673,7 @@ private handle_load_list(data: { success: any; value: any; }): void {
673673
}
674674
};
675675

676-
private load_list_success(data: string[]): void {
676+
private load_list_success(data: { [key: string]: string }[]): void {
677677
this.data = data;
678678
this.disable_list()
679679
this.clear_list();
@@ -698,28 +698,28 @@ private load_list_success(data: string[]): void {
698698
}
699699
};
700700

701-
private change_course(course: string): void {
701+
private change_course(course: { [key: string]: string }): void {
702702
this.disable_list();
703703
if (this.current_course !== undefined) {
704-
this.default_course_element.innerText = course;
704+
this.default_course_element.innerText = course['course_title'];
705705
}
706706
this.current_course = course;
707-
this.default_course_element.innerText = this.current_course;
707+
this.default_course_element.innerText = this.current_course['course_title'];
708708
var success = ()=>{this.load_assignment_list_success()};
709-
this.assignment_list.load_list(course, success);
709+
this.assignment_list.load_list(course['course_id'], success);
710710
};
711711

712712
private load_assignment_list_success(): void {
713713
if (this.data) {
714714
var that = this;
715-
var set_course = function (course: string) {
715+
var set_course = function (course: { [key: string]: string }) {
716716
return function () { that.change_course(course); };
717717
}
718718

719719
for (var i=0; i<this.data.length; i++) {
720720
var a = document.createElement('a');
721721
a.href = '#';
722-
a.innerText = this.data[i];
722+
a.innerText = this.data[i]['course_title'];
723723
var element = document.createElement('li');
724724
element.append(a);
725725
element.onclick = set_course(this.data[i]);

src/course_list/courselist.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ function createElementFromCourse(data: any, app: JupyterFrontEnd, isNotebook:boo
5353

5454
var anchor = document.createElement('a') as HTMLAnchorElement;
5555
anchor.href = '#';
56-
anchor.innerText = data['course_id'];
56+
anchor.innerText = data['course_title'];
57+
5758
if (data['kind'] == 'local') {
5859
anchor.href = '#';
5960
anchor.onclick = function() {

0 commit comments

Comments
 (0)