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

« back to all changes in this revision

Viewing changes to bin/ivle-marks

  • Committer: William Grant
  • Date: 2009-04-23 03:36:48 UTC
  • Revision ID: grantw@unimelb.edu.au-20090423033648-da4wxhvvtd50qzkq
Remove ivle.conf.python_site_packages_override.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
#!/usr/bin/env python
2
2
# IVLE - Informatics Virtual Learning Environment
3
 
# Copyright (C) 2007-2009 The University of Melbourne
 
3
# Copyright (C) 2007-2008 The University of Melbourne
4
4
#
5
5
# This program is free software; you can redistribute it and/or modify
6
6
# it under the terms of the GNU General Public License as published by
18
18
 
19
19
# Program: Marks
20
20
# Author:  Matt Giuca
 
21
# Date:    17/4/2008
21
22
 
22
23
# Script to calculate the marks for all students for a particular subject.
23
24
# Requires root to run.
24
25
 
25
26
import sys
26
27
import os
 
28
import re
27
29
import csv
28
 
import datetime
29
 
import optparse
30
30
from xml.dom import minidom
31
31
 
32
 
if os.getuid() != 0:
33
 
    print >>sys.stderr, "Must run %s as root." % os.path.basename(sys.argv[0])
34
 
    sys.exit()
35
 
 
36
32
import ivle.database
37
33
import ivle.worksheet.utils
38
34
import ivle.conf
39
35
 
 
36
if os.getuid() != 0:
 
37
    print >>sys.stderr, "Must run %s as root." % os.path.basename(sys.argv[0])
 
38
    sys.exit()
 
39
 
 
40
if len(sys.argv) <= 1:
 
41
    print >>sys.stderr, "Usage: %s subject" % os.path.basename(sys.argv[0])
 
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
 
40
59
def get_userdata(user):
41
60
    """
42
61
    Given a User object, returns a list of strings for the user data which
46
65
    last_login = (None if user.last_login is None else
47
66
                    user.last_login.strftime("%d/%m/%y"))
48
67
    return [user.studentid, user.login, user.fullname, last_login]
49
 
 
50
68
userdata_header = ["Student ID", "Login", "Full name", "Last login"]
51
 
def get_header(worksheets):
52
 
    """
53
 
    Given a list of Worksheet objects (the assessable worksheets), returns a
54
 
    list of strings -- the column headings for the marks section of the CSV
55
 
    output.
56
 
    """
57
 
    return (userdata_header + [ws.name for ws in worksheets]
58
 
            + ["Total %", "Mark"])
59
 
 
60
 
def get_marks_user(worksheets, user, as_of=None):
61
 
    """Gets marks for a particular user for a particular set of worksheets.
62
 
    @param worksheets: List of Worksheet objects to get marks for.
63
 
    @param user: User to get marks for.
64
 
    @param as_of: Optional datetime. If supplied, gets the marks as of as_of.
65
 
    @returns: The user's percentage for each worksheet, overall, and
 
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(ivle.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, worksheet_names, 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
66
117
    their final mark, as a list of strings, in a manner which corresponds to
67
118
    the headings produced by get_marks_header.
68
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
 
69
124
    worksheet_pcts = []
70
125
    # As we go, calculate the total score for this subject
71
126
    # (Assessable worksheets only, mandatory problems only)
72
127
    problems_done = 0
73
128
    problems_total = 0
74
129
 
75
 
    for worksheet in worksheets:
 
130
    for worksheet_name in worksheet_names:
 
131
        worksheet = ivle.database.Worksheet.get_by_name(store,
 
132
            subject, worksheet_name)
76
133
        # We simply ignore optional exercises here
77
134
        mand_done, mand_total, _, _ = (
78
 
            ivle.worksheet.utils.calculate_score(store, user, worksheet,
79
 
                                                 as_of))
80
 
        if mand_total > 0:
81
 
            worksheet_pcts.append(float(mand_done) / mand_total)
82
 
        else:
83
 
            # Avoid Div0, just give everyone 0 marks if there are none
84
 
            worksheet_pcts.append(0.0)
 
135
            ivle.worksheet.utils.calculate_score(store, user, worksheet))
 
136
        worksheet_pcts.append(float(mand_done) / mand_total)
85
137
        problems_done += mand_done
86
138
        problems_total += mand_total
87
 
    percent, mark, _ = (
88
 
        ivle.worksheet.utils.calculate_mark(problems_done, problems_total))
89
 
    return worksheet_pcts + [float(percent)/100, mark]
 
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
 
143
    max_mark = 5
 
144
    mark = min(problems_pct_int / 16, max_mark)
 
145
    return worksheet_pcts + [problems_pct, mark]
90
146
 
91
 
def writeuser(worksheets, user, csvfile, cutoff=None):
 
147
def writeuser(subject, worksheets, user, csvfile):
92
148
    userdata = get_userdata(user)
93
 
    marksdata = get_marks_user(worksheets, user, cutoff)
94
 
    data = userdata + marksdata
95
 
    # CSV writer can't handle non-ASCII characters. Encode to UTF-8.
96
 
    data = [unicode(x).encode('utf-8') for x in data]
97
 
    csvfile.writerow(data)
98
 
 
99
 
def main(argv=None):
100
 
    global store
101
 
    if argv is None:
102
 
        argv = sys.argv
103
 
 
104
 
    usage = """usage: %prog [options] subject
105
 
    (requires root)
106
 
    Reports each student's marks for a given subject offering."""
107
 
 
108
 
    # Parse arguments
109
 
    parser = optparse.OptionParser(usage)
110
 
    parser.add_option("-s", "--semester",
111
 
        action="store", dest="semester", metavar="YEAR/SEMESTER",
112
 
        help="Semester of the subject's offering (eg. 2009/1). "
113
 
             "Defaults to the currently active semester.",
114
 
        default=None)
115
 
    parser.add_option("-c", "--cutoff",
116
 
        action="store", dest="cutoff", metavar="DATE",
117
 
        help="Cutoff date (calculate the marks as of this date). "
118
 
             "YYYY-MM-DD H:M:S.",
119
 
        default=None)
120
 
    (options, args) = parser.parse_args(argv[1:])
121
 
 
122
 
    if len(args) < 1:
123
 
        parser.print_help()
124
 
        parser.exit()
125
 
 
126
 
    subject_name = unicode(args[0])
127
 
 
128
 
    if options.semester is None:
129
 
        year, semester = None, None
130
 
    else:
131
 
        try:
132
 
            year, semester = options.semester.split('/')
133
 
            if len(year) == 0 or len(semester) == 0:
134
 
                raise ValueError()
135
 
        except ValueError:
136
 
            parser.error('Invalid semester (must have form "year/semester")')
137
 
 
138
 
    if options.cutoff is not None:
139
 
        try:
140
 
            cutoff = datetime.datetime.strptime(options.cutoff,
141
 
                                                "%Y-%m-%d %H:%M:%S")
142
 
        except ValueError:
143
 
            parser.error("Invalid date format: '%s' "
144
 
                         "(must be YYYY-MM-DD H:M:S)." % options.cutoff)
145
 
    else:
146
 
        cutoff = None
147
 
 
 
149
    marksdata = get_marks_user(subject, worksheets, user)
 
150
    csvfile.writerow(userdata + marksdata)
 
151
 
 
152
try:
 
153
    # Get the list of assessable worksheets from the subject.xml file.
 
154
    worksheets = get_assessable_worksheets(subject)
148
155
    store = ivle.database.get_store()
149
 
 
150
 
    # Get the subject from the DB
151
 
    subject = store.find(ivle.database.Subject,
152
 
                     ivle.database.Subject.short_name == subject_name).one()
153
 
    if subject is None:
154
 
        print >>sys.stderr, "No subject with short name '%s'" % subject_name
155
 
        return 1
156
 
 
157
 
    # Get the offering from the DB
158
 
    if semester is None:
159
 
        # None specified - get the current offering from the DB
160
 
        offerings = list(subject.active_offerings())
161
 
        if len(offerings) == 0:
162
 
            print >>sys.stderr, ("No active offering for subject '%s'"
163
 
                                 % subject_name)
164
 
            return 1
165
 
        elif len(offerings) > 1:
166
 
            print >>sys.stderr, ("Multiple active offerings for subject '%s':"
167
 
                                 % subject_name)
168
 
            print >>sys.stderr, "Please use one of:"
169
 
            for offering in offerings:
170
 
                print >>sys.stderr, ("    --semester=%s/%s"
171
 
                    % (offering.semester.year, offering.semester.semester))
172
 
            return 1
173
 
        else:
174
 
            offering = offerings[0]
175
 
    else:
176
 
        # Get the offering for the specified semester
177
 
        offering = subject.offering_for_semester(year, semester)
178
 
        if offering is None:
179
 
            print >>sys.stderr, (
180
 
                "No offering for subject '%s' in semester %s/%s"
181
 
                % (subject_name, year, semester))
182
 
            return 1
183
 
 
184
 
    # Get the list of assessable worksheets
185
 
    worksheets = offering.worksheets.find(assessable=True)
186
 
 
187
 
    # Start writing the CSV file - header
188
 
    csvfile = csv.writer(sys.stdout)
189
 
    csvfile.writerow(get_header(worksheets))
190
 
 
191
 
    # Get all users enrolled in this offering
192
 
    users = store.find(ivle.database.User,
193
 
                   ivle.database.User.id == ivle.database.Enrolment.user_id,
194
 
                   offering.id == ivle.database.Enrolment.offering).order_by(
195
 
                        ivle.database.User.login)
196
 
    for user in users:
197
 
        writeuser(worksheets, user, csvfile, cutoff)
198
 
 
199
 
if __name__ == "__main__":
200
 
    sys.exit(main(sys.argv))
 
156
except Exception, message:
 
157
    print >>sys.stderr, "Error: " + str(message)
 
158
    sys.exit(1)
 
159
 
 
160
# Start writing the CSV file - header
 
161
csvfile = csv.writer(sys.stdout)
 
162
csvfile.writerow(userdata_header + get_marks_header(worksheets))
 
163
 
 
164
for user in store.find(ivle.database.User).order_by(ivle.database.User.login):
 
165
    writeuser(subject, worksheets, user, csvfile)
 
166