2
# IVLE - Informatics Virtual Learning Environment
3
# Copyright (C) 2007-2009 The University of Melbourne
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.
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.
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
19
# Program: Fetch Submissions
22
# Script to retrieve all submissions for a particular project.
23
# Requires root to run.
33
print >>sys.stderr, "Must run %s as root." % os.path.basename(sys.argv[0])
39
from ivle.database import Project, ProjectSet, Offering, Subject
41
def fetch_submission(submission, target, svnclient, config):
42
"""Fetch a submission from a user's repository, and dump it in a given
44
@param submission: Submission object, detailing the submission.
45
@param target: Target directory for the project (will create a
46
subdirectory for each submission).
47
@param config: Config object.
49
# submission_name is the name of the user or group who owns the repo
50
submission_name = submission.assessed.principal.name
51
# target_final is the directory to place the files in
52
target_final = os.path.join(target, submission.assessed.principal.name)
53
if not os.path.exists(target_final):
54
os.makedirs(target_final)
55
url = get_repo_url(submission, config)
56
revision = pysvn.Revision(pysvn.opt_revision_kind.number,
58
svnclient.export(url, target_final, force=True,
59
revision=revision, recurse=True)
61
def get_repo_url(submission, config):
62
"""Gets a local (file:) URL to the repository for a given submission.
63
This will consult submission.path to find the path within the repository
65
@param submission: Submission object, detailing the submission.
66
@param config: Config object.
68
# NOTE: This code is mostly copied from services/usrmgt-server
69
if submission.assessed.is_group:
70
# The offering this group is in
71
offering = submission.assessed.project.project_set.offering
72
groupname = submission.assessed.principal.name
73
# The name of the repository directory within 'groups' is
74
# SUBJECT_YEAR_SEMESTER_GROUP
75
namespace = "_".join([offering.subject.short_name,
76
offering.semester.year, offering.semester.semester, groupname])
77
repo_path = os.path.join(config['paths']['svn']['repo_path'],
80
# The name of the repository directory within 'users' is the username
81
username = submission.assessed.principal.name
82
repo_path = os.path.join(config['paths']['svn']['repo_path'],
85
path_in_repo = submission.path
86
# Change an absolute path into a relative one (to the top of SVN)
87
if path_in_repo[:1] == os.sep or path_in_repo[:1] == os.altsep:
88
path_in_repo = path_in_repo[1:]
90
# Attach "file://" to the front of the absolute path, to make it a URL
91
return "file://" + os.path.join(os.path.abspath(repo_path), path_in_repo)
98
usage = """usage: %prog [options] subject projname
100
Retrieves all submissions for a given project. Places each submission in
101
its own directory, in a subdirectory of '.'. Any errors are reported to
102
stderr (otherwise is silent).
103
subject/projname is the subject/project's short name.
107
parser = optparse.OptionParser(usage)
108
parser.add_option("-s", "--semester",
109
action="store", dest="semester", metavar="YEAR/SEMESTER",
110
help="Semester of the subject's offering (eg. 2009/1). "
111
"Defaults to the currently active semester.",
113
parser.add_option("-c", "--cutoff",
114
action="store", dest="cutoff", metavar="DATE",
115
help="Cutoff date (retrieve the submissions as of this date)."
118
parser.add_option("-d", "--dest",
119
action="store", dest="dest", metavar="PATH",
120
help="Destination directory (default to '.', creates a subdirectory, "
121
"so will not pollute PATH).",
123
(options, args) = parser.parse_args(argv[1:])
129
subject_name = unicode(args[0])
130
project_name = unicode(args[1])
132
if options.semester is None:
133
year, semester = None, None
136
year, semester = options.semester.split('/')
137
if len(year) == 0 or len(semester) == 0:
140
parser.error('Invalid semester (must have form "year/semester")')
142
if options.cutoff is not None:
144
cutoff = datetime.datetime.strptime(options.cutoff,
147
parser.error("Invalid date format: '%s' "
148
"(must be YYYY-MM-DD H:M:S)." % options.cutoff)
152
svnclient = pysvn.Client()
153
config = ivle.config.Config(plugins=False)
154
store = ivle.database.get_store(config)
156
# Get the subject from the DB
157
subject = store.find(Subject,
158
Subject.short_name == subject_name).one()
160
print >>sys.stderr, "No subject with short name '%s'" % subject_name
163
# Get the offering from the DB
165
# None specified - get the current offering from the DB
166
offerings = list(subject.active_offerings())
167
if len(offerings) == 0:
168
print >>sys.stderr, ("No active offering for subject '%s'"
171
elif len(offerings) > 1:
172
print >>sys.stderr, ("Multiple active offerings for subject '%s':"
174
print >>sys.stderr, "Please use one of:"
175
for offering in offerings:
176
print >>sys.stderr, (" --semester=%s/%s"
177
% (offering.semester.year, offering.semester.semester))
180
offering = offerings[0]
182
# Get the offering for the specified semester
183
offering = subject.offering_for_semester(year, semester)
185
print >>sys.stderr, (
186
"No offering for subject '%s' in semester %s/%s"
187
% (subject_name, year, semester))
190
# Get the project from the DB
191
project = store.find(Project,
192
Project.project_set_id == ProjectSet.id,
193
ProjectSet.offering == offering,
194
Project.short_name == project_name).one()
196
print >>sys.stderr, "No project with short name '%s'" % project_name
199
# Target directory is DEST/subject/year/semester/project
200
target_dir = os.path.join(options.dest, subject_name,
201
offering.semester.year, offering.semester.semester, project_name)
202
if not os.path.exists(target_dir):
203
os.makedirs(target_dir)
205
for submission in project.latest_submissions:
207
fetch_submission(submission, target_dir, svnclient, config)
209
# Catch all exceptions (to ensure if one student has a problem, it
210
# is reported, and we can continue)
211
print >>sys.stderr, "%s: %s: %s" % (
212
submission.assessed.principal.display_name,
215
if __name__ == "__main__":
216
sys.exit(main(sys.argv))