Skip to content

Commit c5d16d9

Browse files
authored
Merge pull request #1320 from AaltoScienceIT/collect-before-due-date
Add nbgrader collect --before-duedate option
2 parents 95ec89a + fb09ec2 commit c5d16d9

File tree

3 files changed

+77
-1
lines changed

3 files changed

+77
-1
lines changed

nbgrader/apps/collectapp.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
{'ExchangeCollect' : {'update': True}},
2121
"Update existing submissions with ones that have newer timestamps."
2222
),
23+
'before-duedate': (
24+
{'ExchangeCollect' : {'before_duedate': True}},
25+
"Collect the last submission before due date or the last submission if no submission before due date."
26+
),
2327
})
2428

2529
class CollectApp(NbGrader):

nbgrader/exchange/collect.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
import sys
55
from collections import defaultdict
66
from textwrap import dedent
7+
import datetime
78

89
from traitlets import Bool
910

1011
from .exchange import Exchange
12+
from ..api import Gradebook, MissingEntry
1113
from ..utils import check_mode, parse_utc
1214

1315
# pwd is for matching unix names with student ide, so we shouldn't import it on
@@ -31,6 +33,11 @@ class ExchangeCollect(Exchange):
3133
help="Update existing submissions with ones that have newer timestamps."
3234
).tag(config=True)
3335

36+
before_duedate = Bool(
37+
False,
38+
help="Collect the last submission before due date or the last submission if no submission before due date."
39+
).tag(config=True)
40+
3441
check_owner = Bool(
3542
default_value=True,
3643
help="Whether to cross-check the student_id with the UNIX-owner of the submitted directory."
@@ -63,7 +70,24 @@ def init_src(self):
6370
pattern = os.path.join(self.inbound_path, '{}+{}+*'.format(student_id, self.coursedir.assignment_id))
6471
records = [self._path_to_record(f) for f in glob.glob(pattern)]
6572
usergroups = groupby(records, lambda item: item['username'])
66-
self.src_records = [self._sort_by_timestamp(v)[0] for v in usergroups.values()]
73+
74+
with Gradebook(self.coursedir.db_url, self.coursedir.course_id) as gb:
75+
try:
76+
assignment = gb.find_assignment(self.coursedir.assignment_id)
77+
self.duedate = assignment.duedate
78+
except MissingEntry:
79+
self.duedate = None
80+
if self.duedate is None or not self.before_duedate:
81+
self.src_records = [self._sort_by_timestamp(v)[0] for v in usergroups.values()]
82+
else:
83+
self.src_records = []
84+
for v in usergroups.values():
85+
records = self._sort_by_timestamp(v)
86+
records_before_duedate = [record for record in records if record['timestamp'] <= self.duedate]
87+
if records_before_duedate:
88+
self.src_records.append(records_before_duedate[0])
89+
else:
90+
self.src_records.append(records[0])
6791

6892
def init_dest(self):
6993
pass
@@ -108,6 +132,10 @@ def copy_files(self):
108132
if self.update and (existing_timestamp is None or new_timestamp > existing_timestamp):
109133
copy = True
110134
updating = True
135+
elif self.before_duedate and existing_timestamp != new_timestamp:
136+
copy = True
137+
updating = True
138+
111139
else:
112140
copy = True
113141

nbgrader/tests/apps/test_nbgrader_collect.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import datetime
12
import os
23
import time
34
import pytest
@@ -7,6 +8,7 @@
78
from .. import run_nbgrader
89
from .base import BaseTestApp
910
from .conftest import notwindows
11+
from ...api import Gradebook
1012
from ...utils import parse_utc, get_username
1113

1214

@@ -151,3 +153,45 @@ def test_permissions(self, exchange, course_dir, cache, groupshared):
151153
assert self._get_permissions(join(exchange, "abc101", "inbound")) == ("2733" if not groupshared else "2773")
152154
assert self._get_permissions(join(course_dir, "submitted", "foobar_student", "ps1")) == ("777" if not groupshared else "2777")
153155
assert self._get_permissions(join(course_dir, "submitted", "foobar_student", "ps1", "p1.ipynb")) == ("644" if not groupshared else "664")
156+
157+
@pytest.mark.parametrize('before_duedate',
158+
['yes', 'no', 'nofirst'])
159+
def test_collect_before_due_date(self, exchange, course_dir, cache, db, before_duedate):
160+
"""Test --before-duedate flag.
161+
162+
Test is parameterized so we test both with it and without the flag.
163+
164+
'yes': test with --before-duedate
165+
'no': test without
166+
'nofirst': test with --before-duedate but no assignment before duedate
167+
168+
"""
169+
# Release assignment
170+
self._release_and_fetch("ps1", exchange, course_dir)
171+
172+
# Submit something, wait, submit again. Due date is between.
173+
if before_duedate != 'nofirst':
174+
# We don't submit first assignment.
175+
self._submit("ps1", exchange, cache)
176+
time.sleep(.05)
177+
time_duedate = datetime.datetime.utcnow()
178+
time.sleep(.05)
179+
self._submit("ps1", exchange, cache)
180+
181+
# Set the due date
182+
with Gradebook(db) as gb:
183+
gb.update_or_create_assignment('ps1', duedate=time_duedate)
184+
185+
# Collect
186+
flags = ['--db', db]
187+
if before_duedate != 'no':
188+
flags.append('--before-duedate')
189+
self._collect("ps1", exchange, flags=flags)
190+
191+
root = os.path.os.path.join(os.path.join(course_dir, "submitted", get_username(), "ps1"))
192+
timestamp = self._read_timestamp(root)
193+
# Test both ways: with --before-duedate flag and without
194+
if before_duedate == 'yes':
195+
assert timestamp < time_duedate
196+
else: # 'no', 'nofirst'
197+
assert timestamp > time_duedate

0 commit comments

Comments
 (0)