65
45
last_login = (None if user.last_login is None else
66
46
user.last_login.strftime("%d/%m/%y"))
67
47
return [user.studentid, user.login, user.fullname, last_login]
68
49
userdata_header = ["Student ID", "Login", "Full name", "Last login"]
70
def get_assessable_worksheets(subject):
72
Given a subject name, returns a list of strings - the worksheet names (not
73
primary key IDs) for all assessable worksheets for that subject.
74
May raise Exceptions, which are fatal.
76
# NOTE: This code is copy/edited from
77
# www/apps/tutorial/__init__.py:handle_subject_menu
78
# Should be factored out of there.
80
# Parse the subject description file
81
# The subject directory must have a file "subject.xml" in it,
82
# or it does not exist (raise exception).
84
subjectfile = open(os.path.join(ivle.conf.subjects_base, subject,
87
raise Exception("Subject %s not found." % repr(subject))
89
assessable_worksheets = []
90
# Read in data about the subject
91
subjectdom = minidom.parse(subjectfile)
93
# TEMP: All of this is for a temporary XML format, which will later
95
worksheetsdom = subjectdom.documentElement
96
worksheets = [] # List of string IDs
97
for worksheetdom in worksheetsdom.childNodes:
98
if worksheetdom.nodeType == worksheetdom.ELEMENT_NODE:
99
# (Note: assessable will default to False, unless it is explicitly
101
if worksheetdom.getAttribute("assessable") == "true":
102
assessable_worksheets.append(worksheetdom.getAttribute("id"))
104
return assessable_worksheets
106
def get_marks_header(worksheets):
108
Given a list of strings - the assessable worksheets - returns a new list
109
of strings - the column headings for the marks section of the CSV output.
111
return worksheets + ["Total %", "Mark"]
113
def get_marks_user(subject, worksheet_names, user):
115
Given a subject, a list of strings (the assessable worksheets), and a user
116
object, returns the user's percentage for each worksheet, overall, and
50
def get_header(worksheets):
52
Given a list of Worksheet objects (the assessable worksheets), returns a
53
list of strings -- the column headings for the marks section of the CSV
56
return (userdata_header + [ws.name for ws in worksheets]
57
+ ["Total %", "Mark"])
59
def get_marks_user(worksheets, user, as_of=None):
60
"""Gets marks for a particular user for a particular set of worksheets.
61
@param worksheets: List of Worksheet objects to get marks for.
62
@param user: User to get marks for.
63
@param as_of: Optional datetime. If supplied, gets the marks as of as_of.
64
@returns: The user's percentage for each worksheet, overall, and
117
65
their final mark, as a list of strings, in a manner which corresponds to
118
66
the headings produced by get_marks_header.
120
# NOTE: This code is copy/edited from
121
# www/apps/tutorial/__init__.py:handle_subject_menu
122
# Should be factored out of there.
124
68
worksheet_pcts = []
125
69
# As we go, calculate the total score for this subject
126
70
# (Assessable worksheets only, mandatory problems only)
128
72
problems_total = 0
130
for worksheet_name in worksheet_names:
131
worksheet = ivle.database.Worksheet.get_by_name(store,
132
subject, worksheet_name)
74
for worksheet in worksheets:
133
75
# We simply ignore optional exercises here
134
76
mand_done, mand_total, _, _ = (
135
ivle.worksheet.calculate_score(store, user, worksheet))
136
worksheet_pcts.append(float(mand_done) / mand_total)
77
ivle.worksheet.utils.calculate_score(store, user, worksheet,
80
worksheet_pcts.append(float(mand_done) / mand_total)
82
# Avoid Div0, just give everyone 0 marks if there are none
83
worksheet_pcts.append(0.0)
137
84
problems_done += mand_done
138
85
problems_total += mand_total
139
problems_pct = float(problems_done) / problems_total
140
problems_pct_int = (100 * problems_done) / problems_total
141
# XXX Marks calculation (should be abstracted out of here!)
142
# percent / 16, rounded down, with a maximum mark of 5
144
mark = min(problems_pct_int / 16, max_mark)
145
return worksheet_pcts + [problems_pct, mark]
87
ivle.worksheet.utils.calculate_mark(problems_done, problems_total))
88
return worksheet_pcts + [float(percent)/100, mark]
147
def writeuser(subject, worksheets, user, csvfile):
90
def writeuser(worksheets, user, csvfile, cutoff=None):
148
91
userdata = get_userdata(user)
149
marksdata = get_marks_user(subject, worksheets, user)
150
csvfile.writerow(userdata + marksdata)
153
# Get the list of assessable worksheets from the subject.xml file.
154
worksheets = get_assessable_worksheets(subject)
155
store = ivle.database.get_store()
156
except Exception, message:
157
print >>sys.stderr, "Error: " + str(message)
160
# Start writing the CSV file - header
161
csvfile = csv.writer(sys.stdout)
162
csvfile.writerow(userdata_header + get_marks_header(worksheets))
164
for user in store.find(ivle.database.User).order_by(ivle.database.User.login):
165
writeuser(subject, worksheets, user, csvfile)
92
marksdata = get_marks_user(worksheets, user, cutoff)
93
data = userdata + marksdata
94
# CSV writer can't handle non-ASCII characters. Encode to UTF-8.
95
data = [unicode(x).encode('utf-8') for x in data]
96
csvfile.writerow(data)
103
usage = """usage: %prog [options] subject
105
Reports each student's marks for a given subject offering."""
108
parser = optparse.OptionParser(usage)
109
parser.add_option("-s", "--semester",
110
action="store", dest="semester", metavar="YEAR/SEMESTER",
111
help="Semester of the subject's offering (eg. 2009/1). "
112
"Defaults to the currently active semester.",
114
parser.add_option("-c", "--cutoff",
115
action="store", dest="cutoff", metavar="DATE",
116
help="Cutoff date (calculate the marks as of this date). "
119
(options, args) = parser.parse_args(argv[1:])
125
subject_name = unicode(args[0])
127
if options.semester is None:
128
year, semester = None, None
131
year, semester = options.semester.split('/')
132
if len(year) == 0 or len(semester) == 0:
135
parser.error('Invalid semester (must have form "year/semester")')
137
if options.cutoff is not None:
139
cutoff = datetime.datetime.strptime(options.cutoff,
142
parser.error("Invalid date format: '%s' "
143
"(must be YYYY-MM-DD H:M:S)." % options.cutoff)
147
store = ivle.database.get_store(ivle.config.Config(plugins=False))
149
# Get the subject from the DB
150
subject = store.find(ivle.database.Subject,
151
ivle.database.Subject.short_name == subject_name).one()
153
print >>sys.stderr, "No subject with short name '%s'" % subject_name
156
# Get the offering from the DB
158
# None specified - get the current offering from the DB
159
offerings = list(subject.active_offerings())
160
if len(offerings) == 0:
161
print >>sys.stderr, ("No active offering for subject '%s'"
164
elif len(offerings) > 1:
165
print >>sys.stderr, ("Multiple active offerings for subject '%s':"
167
print >>sys.stderr, "Please use one of:"
168
for offering in offerings:
169
print >>sys.stderr, (" --semester=%s/%s"
170
% (offering.semester.year, offering.semester.semester))
173
offering = offerings[0]
175
# Get the offering for the specified semester
176
offering = subject.offering_for_semester(year, semester)
178
print >>sys.stderr, (
179
"No offering for subject '%s' in semester %s/%s"
180
% (subject_name, year, semester))
183
# Get the list of assessable worksheets
184
worksheets = offering.worksheets.find(assessable=True)
186
# Start writing the CSV file - header
187
csvfile = csv.writer(sys.stdout)
188
csvfile.writerow(get_header(worksheets))
190
# Get all users enrolled in this offering
191
users = store.find(ivle.database.User,
192
ivle.database.User.id == ivle.database.Enrolment.user_id,
193
offering.id == ivle.database.Enrolment.offering).order_by(
194
ivle.database.User.login)
196
writeuser(worksheets, user, csvfile, cutoff)
198
if __name__ == "__main__":
199
sys.exit(main(sys.argv))