~azzar1/unity/add-show-desktop-key

736 by mattgiuca
Added new script "marks.py" which computes the marks for all students and outputs it as a CSV file.
1
#!/usr/bin/env python
2
# IVLE - Informatics Virtual Learning Environment
3
# Copyright (C) 2007-2008 The University of Melbourne
4
#
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.
9
#
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.
14
#
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
18
19
# Program: Marks
20
# Author:  Matt Giuca
21
# Date:    17/4/2008
22
23
# Script to calculate the marks for all students for a particular subject.
24
# Requires root to run.
25
26
import conf
27
28
import sys
29
import os
30
import common.db
31
import re
32
import csv
33
import time
34
from xml.dom import minidom
35
36
if os.getuid() != 0:
37
    print >>sys.stderr, "Must run marks.py as root."
38
    sys.exit()
39
40
if len(sys.argv) <= 1:
41
    print >>sys.stderr, "Usage: python ./marks.py subject"
42
    sys.exit()
43
44
# Regex for valid identifiers (subject/worksheet names)
45
re_ident = re.compile("[0-9A-Za-z_]+")
46
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)
51
52
subject = sys.argv[1]
53
54
# Subject names must be valid identifiers
55
if not is_valid_subjname(subject):
56
    print >>sys.stderr, "Invalid subject name: %s." % repr(subject)
57
    sys.exit()
58
59
def get_userdata(user):
60
    """
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).
64
    """
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"]
69
70
def get_assessable_worksheets(subject):
71
    """
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.
75
    """
76
    # NOTE: This code is copy/edited from
77
    # www/apps/tutorial/__init__.py:handle_subject_menu
78
    # Should be factored out of there.
79
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).
83
    try:
84
        subjectfile = open(os.path.join(conf.subjects_base, subject,
85
            "subject.xml"))
86
    except:
87
        raise Exception("Subject %s not found." % repr(subject))
88
89
    assessable_worksheets = []
90
    # Read in data about the subject
91
    subjectdom = minidom.parse(subjectfile)
92
    subjectfile.close()
93
    # TEMP: All of this is for a temporary XML format, which will later
94
    # change.
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
100
            # set to "true").
101
            if worksheetdom.getAttribute("assessable") == "true":
102
                assessable_worksheets.append(worksheetdom.getAttribute("id"))
103
104
    return assessable_worksheets
105
106
def get_marks_header(worksheets):
107
    """
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.
110
    """
111
    return worksheets + ["Total %", "Mark"]
112
113
def get_marks_user(subject, worksheets, user):
114
    """
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.
119
    """
120
    # NOTE: This code is copy/edited from
121
    # www/apps/tutorial/__init__.py:handle_subject_menu
122
    # Should be factored out of there.
123
124
    worksheet_pcts = []
125
    # As we go, calculate the total score for this subject
126
    # (Assessable worksheets only, mandatory problems only)
127
    problems_done = 0
128
    problems_total = 0
129
130
    for worksheet in worksheets:
131
        try:
132
            # We simply ignore optional exercises here
133
            mand_done, mand_total, _, _ = (
134
                db.calculate_score_worksheet(user.login, subject,
135
                    worksheet))
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
141
            pass
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
146
    max_mark = 5
147
    mark = min(problems_pct_int / 16, max_mark)
148
    return worksheet_pcts + [problems_pct, mark]
149
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)
154
155
try:
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)
159
    db = common.db.DB()
160
    list = db.get_users()
161
except Exception, message:
162
    print >>sys.stderr, "Error: " + str(message)
163
    sys.exit(1)
164
165
# Start writing the CSV file - header
166
csvfile = csv.writer(sys.stdout)
167
csvfile.writerow(userdata_header + get_marks_header(worksheets))
168
169
list.sort(key=lambda user: user.login)
170
for user in list:
171
    writeuser(subject, worksheets, user, csvfile)
172