~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-28 08:35:40 UTC
  • Revision ID: grantw@unimelb.edu.au-20090428083540-0mjpvly46rbt8o8l
Drop another unneeded ivle.conf import, from ivle.webapp.help.

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-2008 The University of Melbourne
 
3
# Copyright (C) 2007-2009 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
22
21
 
23
22
# Script to calculate the marks for all students for a particular subject.
24
23
# Requires root to run.
25
24
 
26
25
import sys
27
26
import os
28
 
import re
29
27
import csv
 
28
import datetime
 
29
import optparse
30
30
from xml.dom import minidom
31
31
 
32
 
import ivle.database
33
 
import ivle.worksheet
34
 
import ivle.conf
35
 
 
36
32
if os.getuid() != 0:
37
33
    print >>sys.stderr, "Must run %s as root." % os.path.basename(sys.argv[0])
38
34
    sys.exit()
39
35
 
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()
 
36
import ivle.config
 
37
import ivle.database
 
38
import ivle.worksheet.utils
58
39
 
59
40
def get_userdata(user):
60
41
    """
65
46
    last_login = (None if user.last_login is None else
66
47
                    user.last_login.strftime("%d/%m/%y"))
67
48
    return [user.studentid, user.login, user.fullname, last_login]
 
49
 
68
50
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(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
 
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
117
66
    their final mark, as a list of strings, in a manner which corresponds to
118
67
    the headings produced by get_marks_header.
119
68
    """
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
69
    worksheet_pcts = []
125
70
    # As we go, calculate the total score for this subject
126
71
    # (Assessable worksheets only, mandatory problems only)
127
72
    problems_done = 0
128
73
    problems_total = 0
129
74
 
130
 
    for worksheet_name in worksheet_names:
131
 
        worksheet = ivle.database.Worksheet.get_by_name(store,
132
 
            subject, worksheet_name)
 
75
    for worksheet in worksheets:
133
76
        # We simply ignore optional exercises here
134
77
        mand_done, mand_total, _, _ = (
135
 
            ivle.worksheet.calculate_score(store, user, worksheet))
136
 
        worksheet_pcts.append(float(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)
137
85
        problems_done += mand_done
138
86
        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
143
 
    max_mark = 5
144
 
    mark = min(problems_pct_int / 16, max_mark)
145
 
    return worksheet_pcts + [problems_pct, mark]
 
87
    percent, mark, _ = (
 
88
        ivle.worksheet.utils.calculate_mark(problems_done, problems_total))
 
89
    return worksheet_pcts + [float(percent)/100, mark]
146
90
 
147
 
def writeuser(subject, worksheets, user, csvfile):
 
91
def writeuser(worksheets, user, csvfile, cutoff=None):
148
92
    userdata = get_userdata(user)
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)
155
 
    store = ivle.database.get_store()
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
 
 
 
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
 
 
148
    store = ivle.database.get_store(ivle.config.Config())
 
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))