2
# Copyright (C) 2007-2010 The University of Melbourne
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU General Public License for more details.
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20
"""Worksheet marks reporting functionality.
22
Displays students' worksheet marks to users with sufficient privileges.
30
import ivle.worksheet.utils
31
from ivle.webapp.base.views import BaseView
32
from ivle.webapp.base.xhtml import XHTMLView
33
from ivle.webapp.media import media_url
35
class WorksheetsMarksView(XHTMLView):
36
"""View for presenting all students' individual marks for worksheets."""
37
permission = 'view_worksheet_marks'
38
template = 'templates/worksheets_marks.html'
41
def populate(self, req, ctx):
43
offering = self.context
45
ctx['context'] = offering
46
ctx['urllib'] = urllib
47
ctx['WorksheetsMarksCSVView'] = WorksheetsMarksCSVView
49
# User may supply a "cutoff date" to calculate marks as of that date
50
# Default to current time
51
cutoff = offering.worksheet_cutoff or datetime.datetime.now()
52
data = dict(req.get_fieldstorage())
53
if data.get('cutoff') is not None:
55
cutoff = datetime.datetime.strptime(data.get('cutoff'),
59
"Invalid date format: '%s' (must be YYYY-MM-DD H:M:S)."
61
ctx['cutoff'] = cutoff
64
# "worksheets" is a list of (assessable, published) worksheet names
65
worksheets = offering.worksheets.find(assessable=True, published=True)
66
ctx['worksheets'] = [ws.name for ws in worksheets]
68
# "students" is a list of tuples:
69
# (user, worksheet_pcts, total_pct, mark)
70
# user is a User object, worksheet_pcts is a list of floats (one per
71
# worksheet), total_pct is a float, mark is an int
72
ctx['students'] = students = []
73
# Get all users enrolled in this offering
74
users = req.store.find(ivle.database.User,
75
ivle.database.User.id == ivle.database.Enrolment.user_id,
76
offering.id == ivle.database.Enrolment.offering).order_by(
77
ivle.database.User.login)
79
worksheet_pcts, total_pct, mark = get_marks_user(req, worksheets,
81
students.append((user, worksheet_pcts, total_pct, mark))
83
class WorksheetsMarksCSVView(BaseView):
84
"""View for presenting all students' individual marks for worksheets."""
85
permission = 'view_worksheet_marks'
86
template = 'templates/worksheets_marks.html'
89
def render(self, req):
90
offering = self.context
92
# User may supply a "cutoff date" to calculate marks as of that date
93
# Default to current time
94
cutoff = offering.worksheet_cutoff or datetime.datetime.now()
95
data = dict(req.get_fieldstorage())
96
if data.get('cutoff') is not None:
98
cutoff = datetime.datetime.strptime(data.get('cutoff'),
102
"Invalid date format: '%s' (must be YYYY-MM-DD H:M:S)."
103
% data.get('cutoff'))
106
req.content_type = "text/csv"
107
req.headers_out.add('Content-Disposition',
108
"attachment; filename=marks-%s-%ss%s.csv" %
109
(offering.subject.short_name, offering.semester.year,
110
offering.semester.url_name))
112
# "worksheets" is a list of (assessable, published) worksheet names
113
worksheets = offering.worksheets.find(assessable=True, published=True)
115
# Start writing the CSV file - header
116
csvfile = csv.writer(req)
117
csvfile.writerow(csv_get_header(worksheets))
119
# Get all users enrolled in this offering
120
users = req.store.find(ivle.database.User,
121
ivle.database.User.id == ivle.database.Enrolment.user_id,
122
offering.id == ivle.database.Enrolment.offering).order_by(
123
ivle.database.User.login)
125
csv_writeuser(req, worksheets, user, csvfile, cutoff)
127
def get_marks_user(req, worksheets, user, as_of=None):
128
"""Gets marks for a particular user for a particular set of worksheets.
129
@param worksheets: List of Worksheet objects to get marks for.
130
@param user: User to get marks for.
131
@param as_of: Optional datetime. If supplied, gets the marks as of as_of.
132
@returns: (worksheet_pcts, total_pct, mark)
135
# As we go, calculate the total score for this subject
136
# (Assessable worksheets only, mandatory problems only)
140
for worksheet in worksheets:
141
# We simply ignore optional exercises here
142
mand_done, mand_total, _, _ = (
143
ivle.worksheet.utils.calculate_score(req.store, user, worksheet,
146
worksheet_pcts.append(float(mand_done) / mand_total)
148
# Avoid Div0, just give everyone 0 marks if there are none
149
worksheet_pcts.append(0.0)
150
problems_done += mand_done
151
problems_total += mand_total
153
ivle.worksheet.utils.calculate_mark(problems_done, problems_total))
154
return (worksheet_pcts, float(percent)/100, mark)
156
def csv_get_userdata(user):
158
Given a User object, returns a list of strings for the user data which
159
will be part of the output for this user.
160
(This is not marks, it's other user data).
162
last_login = ("" if user.last_login is None else
163
user.last_login.strftime("%Y-%m-%d"))
164
return [user.studentid or "", user.login, user.fullname, last_login]
166
csv_userdata_header = ["Student ID", "Login", "Full name", "Last login"]
167
def csv_get_header(worksheets):
169
Given a list of Worksheet objects (the assessable worksheets), returns a
170
list of strings -- the column headings for the marks section of the CSV
173
return (csv_userdata_header + [ws.name for ws in worksheets]
174
+ ["Total %", "Mark"])
176
def csv_writeuser(req, worksheets, user, csvfile, cutoff=None):
177
userdata = csv_get_userdata(user)
178
worksheet_pcts, total_pct, mark = get_marks_user(req, worksheets, user,
180
data = userdata + worksheet_pcts + [total_pct, mark]
181
# CSV writer can't handle non-ASCII characters. Encode to UTF-8.
182
data = [unicode(x).encode('utf-8') for x in data]
183
csvfile.writerow(data)