2
# IVLE - Informatics Virtual Learning Environment
3
# Copyright (C) 2007-2008 The University of Melbourne
5
# This program is free software; you can redistribute it and/or modify
6
# it under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 2 of the License, or
8
# (at your option) any later version.
10
# This program is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
15
# You should have received a copy of the GNU General Public License
16
# along with this program; if not, write to the Free Software
17
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
23
# Script to calculate the marks for all students for a particular subject.
24
# Requires root to run.
34
from xml.dom import minidom
37
print >>sys.stderr, "Must run marks.py as root."
40
if len(sys.argv) <= 1:
41
print >>sys.stderr, "Usage: python ./marks.py subject"
44
# Regex for valid identifiers (subject/worksheet names)
45
re_ident = re.compile("[0-9A-Za-z_]+")
47
# This code copy/edited from www/apps/tutorial/__init__.py
48
def is_valid_subjname(subject):
49
m = re_ident.match(subject)
50
return m is not None and m.end() == len(subject)
54
# Subject names must be valid identifiers
55
if not is_valid_subjname(subject):
56
print >>sys.stderr, "Invalid subject name: %s." % repr(subject)
59
def get_userdata(user):
61
Given a User object, returns a list of strings for the user data which
62
will be part of the output for this user.
63
(This is not marks, it's other user data).
65
last_login = (None if user.last_login is None else
66
time.strftime("%d/%m/%y", user.last_login))
67
return [user.studentid, user.login, user.fullname, last_login]
68
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(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, worksheets, 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
117
their final mark, as a list of strings, in a manner which corresponds to
118
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.
125
# As we go, calculate the total score for this subject
126
# (Assessable worksheets only, mandatory problems only)
130
for worksheet in worksheets:
132
# We simply ignore optional exercises here
133
mand_done, mand_total, _, _ = (
134
db.calculate_score_worksheet(user.login, subject,
136
worksheet_pcts.append(float(mand_done) / mand_total)
137
problems_done += mand_done
138
problems_total += mand_total
139
except common.db.DBException:
140
# Worksheet is probably not in database yet
142
problems_pct = float(problems_done) / problems_total
143
problems_pct_int = (100 * problems_done) / problems_total
144
# XXX Marks calculation (should be abstracted out of here!)
145
# percent / 16, rounded down, with a maximum mark of 5
147
mark = min(problems_pct_int / 16, max_mark)
148
return worksheet_pcts + [problems_pct, mark]
150
def writeuser(subject, worksheets, user, csvfile):
151
userdata = get_userdata(user)
152
marksdata = get_marks_user(subject, worksheets, user)
153
csvfile.writerow(userdata + marksdata)
156
# Get the list of assessable worksheets from the subject.xml file,
157
# and the list of all users from the DB.
158
worksheets = get_assessable_worksheets(subject)
160
list = db.get_users()
161
except Exception, message:
162
print >>sys.stderr, "Error: " + str(message)
165
# Start writing the CSV file - header
166
csvfile = csv.writer(sys.stdout)
167
csvfile.writerow(userdata_header + get_marks_header(worksheets))
169
list.sort(key=lambda user: user.login)
171
writeuser(subject, worksheets, user, csvfile)