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

« back to all changes in this revision

Viewing changes to bin/ivle-marks

  • Committer: William Grant
  • Date: 2009-01-20 00:12:35 UTC
  • mto: This revision was merged to the branch mainline in revision 1090.
  • Revision ID: grantw@unimelb.edu.au-20090120001235-6m2qebmtpylpzsvn
ivle.db.{get_all,delete}: Kill. Unused.

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
import time
 
31
from xml.dom import minidom
 
32
 
 
33
import ivle.db
 
34
import ivle.database
 
35
import ivle.worksheet
 
36
import ivle.conf
30
37
 
31
38
if os.getuid() != 0:
32
39
    print >>sys.stderr, "Must run %s as root." % os.path.basename(sys.argv[0])
33
40
    sys.exit()
34
41
 
35
 
import ivle.config
36
 
import ivle.database
37
 
import ivle.worksheet.utils
 
42
if len(sys.argv) <= 1:
 
43
    print >>sys.stderr, "Usage: %s subject" % os.path.basename(sys.argv[0])
 
44
    sys.exit()
 
45
 
 
46
# Regex for valid identifiers (subject/worksheet names)
 
47
re_ident = re.compile("[0-9A-Za-z_]+")
 
48
 
 
49
# This code copy/edited from www/apps/tutorial/__init__.py
 
50
def is_valid_subjname(subject):
 
51
    m = re_ident.match(subject)
 
52
    return m is not None and m.end() == len(subject)
 
53
 
 
54
subject = sys.argv[1]
 
55
 
 
56
# Subject names must be valid identifiers
 
57
if not is_valid_subjname(subject):
 
58
    print >>sys.stderr, "Invalid subject name: %s." % repr(subject)
 
59
    sys.exit()
38
60
 
39
61
def get_userdata(user):
40
62
    """
43
65
    (This is not marks, it's other user data).
44
66
    """
45
67
    last_login = (None if user.last_login is None else
46
 
                    user.last_login.strftime("%d/%m/%y"))
 
68
                    time.strftime("%d/%m/%y", user.last_login))
47
69
    return [user.studentid, user.login, user.fullname, last_login]
48
 
 
49
70
userdata_header = ["Student ID", "Login", "Full name", "Last login"]
50
 
def get_header(worksheets):
51
 
    """
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
54
 
    output.
55
 
    """
56
 
    return (userdata_header + [ws.name for ws in worksheets]
57
 
            + ["Total %", "Mark"])
58
 
 
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
 
71
 
 
72
def get_assessable_worksheets(subject):
 
73
    """
 
74
    Given a subject name, returns a list of strings - the worksheet names (not
 
75
    primary key IDs) for all assessable worksheets for that subject.
 
76
    May raise Exceptions, which are fatal.
 
77
    """
 
78
    # NOTE: This code is copy/edited from
 
79
    # www/apps/tutorial/__init__.py:handle_subject_menu
 
80
    # Should be factored out of there.
 
81
 
 
82
    # Parse the subject description file
 
83
    # The subject directory must have a file "subject.xml" in it,
 
84
    # or it does not exist (raise exception).
 
85
    try:
 
86
        subjectfile = open(os.path.join(ivle.conf.subjects_base, subject,
 
87
            "subject.xml"))
 
88
    except:
 
89
        raise Exception("Subject %s not found." % repr(subject))
 
90
 
 
91
    assessable_worksheets = []
 
92
    # Read in data about the subject
 
93
    subjectdom = minidom.parse(subjectfile)
 
94
    subjectfile.close()
 
95
    # TEMP: All of this is for a temporary XML format, which will later
 
96
    # change.
 
97
    worksheetsdom = subjectdom.documentElement
 
98
    worksheets = []     # List of string IDs
 
99
    for worksheetdom in worksheetsdom.childNodes:
 
100
        if worksheetdom.nodeType == worksheetdom.ELEMENT_NODE:
 
101
            # (Note: assessable will default to False, unless it is explicitly
 
102
            # set to "true").
 
103
            if worksheetdom.getAttribute("assessable") == "true":
 
104
                assessable_worksheets.append(worksheetdom.getAttribute("id"))
 
105
 
 
106
    return assessable_worksheets
 
107
 
 
108
def get_marks_header(worksheets):
 
109
    """
 
110
    Given a list of strings - the assessable worksheets - returns a new list
 
111
    of strings - the column headings for the marks section of the CSV output.
 
112
    """
 
113
    return worksheets + ["Total %", "Mark"]
 
114
 
 
115
def get_marks_user(subject, worksheet_names, user):
 
116
    """
 
117
    Given a subject, a list of strings (the assessable worksheets), and a user
 
118
    object, returns the user's percentage for each worksheet, overall, and
65
119
    their final mark, as a list of strings, in a manner which corresponds to
66
120
    the headings produced by get_marks_header.
67
121
    """
 
122
    # NOTE: This code is copy/edited from
 
123
    # www/apps/tutorial/__init__.py:handle_subject_menu
 
124
    # Should be factored out of there.
 
125
 
68
126
    worksheet_pcts = []
69
127
    # As we go, calculate the total score for this subject
70
128
    # (Assessable worksheets only, mandatory problems only)
71
129
    problems_done = 0
72
130
    problems_total = 0
73
131
 
74
 
    for worksheet in worksheets:
 
132
    for worksheet_name in worksheet_names:
 
133
        worksheet = ivle.database.Worksheet.get_by_name(store,
 
134
            subject, worksheet_name)
75
135
        # We simply ignore optional exercises here
76
136
        mand_done, mand_total, _, _ = (
77
 
            ivle.worksheet.utils.calculate_score(store, user, worksheet,
78
 
                                                 as_of))
79
 
        if mand_total > 0:
80
 
            worksheet_pcts.append(float(mand_done) / mand_total)
81
 
        else:
82
 
            # Avoid Div0, just give everyone 0 marks if there are none
83
 
            worksheet_pcts.append(0.0)
 
137
            ivle.worksheet.calculate_score(store, user, worksheet))
 
138
        worksheet_pcts.append(float(mand_done) / mand_total)
84
139
        problems_done += mand_done
85
140
        problems_total += mand_total
86
 
    percent, mark, _ = (
87
 
        ivle.worksheet.utils.calculate_mark(problems_done, problems_total))
88
 
    return worksheet_pcts + [float(percent)/100, mark]
 
141
    problems_pct = float(problems_done) / problems_total
 
142
    problems_pct_int = (100 * problems_done) / problems_total
 
143
    # XXX Marks calculation (should be abstracted out of here!)
 
144
    # percent / 16, rounded down, with a maximum mark of 5
 
145
    max_mark = 5
 
146
    mark = min(problems_pct_int / 16, max_mark)
 
147
    return worksheet_pcts + [problems_pct, mark]
89
148
 
90
 
def writeuser(worksheets, user, csvfile, cutoff=None):
 
149
def writeuser(subject, worksheets, user, csvfile):
91
150
    userdata = get_userdata(user)
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)
97
 
 
98
 
def main(argv=None):
99
 
    global store
100
 
    if argv is None:
101
 
        argv = sys.argv
102
 
 
103
 
    usage = """usage: %prog [options] subject
104
 
    (requires root)
105
 
    Reports each student's marks for a given subject offering."""
106
 
 
107
 
    # Parse arguments
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.",
113
 
        default=None)
114
 
    parser.add_option("-c", "--cutoff",
115
 
        action="store", dest="cutoff", metavar="DATE",
116
 
        help="Cutoff date (calculate the marks as of this date). "
117
 
             "YYYY-MM-DD H:M:S.",
118
 
        default=None)
119
 
    (options, args) = parser.parse_args(argv[1:])
120
 
 
121
 
    if len(args) < 1:
122
 
        parser.print_help()
123
 
        parser.exit()
124
 
 
125
 
    subject_name = unicode(args[0])
126
 
 
127
 
    if options.semester is None:
128
 
        year, semester = None, None
129
 
    else:
130
 
        try:
131
 
            year, semester = options.semester.split('/')
132
 
            if len(year) == 0 or len(semester) == 0:
133
 
                raise ValueError()
134
 
        except ValueError:
135
 
            parser.error('Invalid semester (must have form "year/semester")')
136
 
 
137
 
    if options.cutoff is not None:
138
 
        try:
139
 
            cutoff = datetime.datetime.strptime(options.cutoff,
140
 
                                                "%Y-%m-%d %H:%M:%S")
141
 
        except ValueError:
142
 
            parser.error("Invalid date format: '%s' "
143
 
                         "(must be YYYY-MM-DD H:M:S)." % options.cutoff)
144
 
    else:
145
 
        cutoff = None
146
 
 
147
 
    store = ivle.database.get_store(ivle.config.Config(plugins=False))
148
 
 
149
 
    # Get the subject from the DB
150
 
    subject = store.find(ivle.database.Subject,
151
 
                     ivle.database.Subject.short_name == subject_name).one()
152
 
    if subject is None:
153
 
        print >>sys.stderr, "No subject with short name '%s'" % subject_name
154
 
        return 1
155
 
 
156
 
    # Get the offering from the DB
157
 
    if semester is None:
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'"
162
 
                                 % subject_name)
163
 
            return 1
164
 
        elif len(offerings) > 1:
165
 
            print >>sys.stderr, ("Multiple active offerings for subject '%s':"
166
 
                                 % subject_name)
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))
171
 
            return 1
172
 
        else:
173
 
            offering = offerings[0]
174
 
    else:
175
 
        # Get the offering for the specified semester
176
 
        offering = subject.offering_for_semester(year, semester)
177
 
        if offering is None:
178
 
            print >>sys.stderr, (
179
 
                "No offering for subject '%s' in semester %s/%s"
180
 
                % (subject_name, year, semester))
181
 
            return 1
182
 
 
183
 
    # Get the list of assessable worksheets
184
 
    worksheets = offering.worksheets.find(assessable=True)
185
 
 
186
 
    # Start writing the CSV file - header
187
 
    csvfile = csv.writer(sys.stdout)
188
 
    csvfile.writerow(get_header(worksheets))
189
 
 
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)
195
 
    for user in users:
196
 
        writeuser(worksheets, user, csvfile, cutoff)
197
 
 
198
 
if __name__ == "__main__":
199
 
    sys.exit(main(sys.argv))
 
151
    marksdata = get_marks_user(subject, worksheets, user)
 
152
    csvfile.writerow(userdata + marksdata)
 
153
 
 
154
try:
 
155
    # Get the list of assessable worksheets from the subject.xml file,
 
156
    # and the list of all users from the DB.
 
157
    worksheets = get_assessable_worksheets(subject)
 
158
    db = ivle.db.DB()
 
159
    store = ivle.database.get_store()
 
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