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

« back to all changes in this revision

Viewing changes to bin/ivle-fetchsubmissions

Merge submissions-subversion-acls.

Offering staff can now use Subversion to access submissions.

Show diffs side-by-side

added added

removed removed

Lines of Context:
22
22
# Script to retrieve all submissions for a particular project.
23
23
# Requires root to run.
24
24
 
25
 
from __future__ import with_statement
26
 
 
27
25
import sys
28
26
import os
29
27
import shutil
30
28
import datetime
31
 
import codecs
32
29
import optparse
33
30
import zipfile
34
31
import traceback
41
38
 
42
39
import ivle.config
43
40
import ivle.database
44
 
import ivle.util
45
41
 
46
42
from ivle.database import Project, ProjectSet, Offering, Subject
47
43
 
48
 
# Is Python version 2.6 or higher?
49
 
PYTHON26 = map(int, sys.version[:3].split('.')) >= [2, 6]
50
 
 
51
 
def fetch_submission(submission, target, svnclient, config, zip=False,
52
 
    txt=False, verbose=False):
 
44
def fetch_submission(submission, target, svnclient, config, zip=False):
53
45
    """Fetch a submission from a user's repository, and dump it in a given
54
46
    directory.
55
47
    @param submission: Submission object, detailing the submission.
58
50
    @param svnclient: pysvn.Client object.
59
51
    @param config: Config object.
60
52
    @param zip: If True, zips up the submission.
61
 
    @param txt: If True, writes an extra text file with metadata about the
62
 
        submission.
63
 
    @param verbose: If True, writes the name of each submission to stdout.
64
53
    """
65
54
    # submission_name is the name of the user or group who owns the repo
66
 
    submission_name = submission.assessed.principal.short_name
 
55
    submission_name = submission.assessed.principal.name
67
56
    # target_final is the directory to place the files in
68
 
    target_final = os.path.join(target,
69
 
                                submission.assessed.principal.short_name)
70
 
    target_final = target_final.encode('utf-8')
71
 
    if os.path.exists(target_final):
72
 
        # Remove the existing file or directory before re-checking out
73
 
        if os.path.isdir(target_final):
74
 
            ivle.util.safe_rmtree(target_final)
75
 
        else:
76
 
            os.remove(target_final)
 
57
    target_final = os.path.join(target, submission.assessed.principal.name)
 
58
    if not os.path.exists(target_final):
 
59
        os.makedirs(target_final)
77
60
    url = get_repo_url(submission, config)
78
61
    revision = pysvn.Revision(pysvn.opt_revision_kind.number,
79
62
                              submission.revision)
80
63
    svnclient.export(url, target_final, force=True,
81
64
        revision=revision, recurse=True)
82
65
 
83
 
    if txt:
84
 
        # info_final is the directory to place the metadata info files in
85
 
        info_final = target_final + ".txt"
86
 
        write_submission_info(info_final, submission)
87
 
 
88
66
    # If required, zip up the directory
89
67
    if zip:
90
68
        make_zip(target_final, target_final + ".zip")
91
69
        # Remove the target tree
92
 
        if os.path.isdir(target_final):
93
 
            ivle.util.safe_rmtree(target_final)
94
 
        else:
95
 
            os.remove(target_final)
96
 
 
97
 
    if verbose:
98
 
        print "Exported submission by: %s (%s)" % (
99
 
            submission.assessed.principal.short_name,
100
 
            submission.assessed.principal.display_name)
 
70
        shutil.rmtree(target_final)
101
71
 
102
72
def get_repo_url(submission, config):
103
73
    """Gets a local (file:) URL to the repository for a given submission.
110
80
    if submission.assessed.is_group:
111
81
        # The offering this group is in
112
82
        offering = submission.assessed.project.project_set.offering
113
 
        groupname = submission.assessed.principal.short_name
 
83
        groupname = submission.assessed.principal.name
114
84
        # The name of the repository directory within 'groups' is
115
85
        # SUBJECT_YEAR_SEMESTER_GROUP
116
86
        namespace = "_".join([offering.subject.short_name,
119
89
                                'groups', namespace)
120
90
    else:
121
91
        # The name of the repository directory within 'users' is the username
122
 
        username = submission.assessed.principal.short_name
 
92
        username = submission.assessed.principal.name
123
93
        repo_path = os.path.join(config['paths']['svn']['repo_path'],
124
94
                                'users', username)
125
95
 
134
104
def make_zip(source, dest):
135
105
    """Zip up a directory tree or file. The ZIP file will always contain just
136
106
    a single directory or file at the top level (it will not be a ZIP bomb).
137
 
    XXX In Python 2.5 and earlier, this does NOT create empty directories
138
 
    (it's not possible with the Python2.5 version of zipfile).
139
107
    @param source: Path to a directory or file to zip up.
140
108
    @param dest: Path to a zip file to create.
141
109
    """
144
112
 
145
113
    # Write the source file/directory itself
146
114
    # (If this is a directory it will NOT recurse)
147
 
    if PYTHON26 or not os.path.isdir(source):
148
 
        # Python < 2.6 errors if you add a directory
149
 
        # (This means you can't add an empty directory)
150
 
        zip.write(source, os.path.basename(source))
 
115
    zip.write(source, os.path.basename(source))
151
116
 
152
117
    if os.path.isdir(source):
153
118
        # All paths within the zip file are relative to relativeto
158
123
            raise OSError("Could not access a file (zipping)")
159
124
        for (dirpath, dirnames, filenames) in \
160
125
            os.walk(source, onerror=error):
161
 
            arc_dirpath = ivle.util.relpath(dirpath, relativeto)
162
 
            # Python < 2.6 errors if you add a directory
163
 
            # (This means you can't add an empty directory)
164
 
            if PYTHON26:
165
 
                filenames = dirnames + filenames
166
 
            for filename in filenames:
 
126
            arc_dirpath = os.path.relpath(dirpath, relativeto)
 
127
            for filename in dirnames + filenames:
167
128
                zip.write(os.path.join(dirpath, filename),
168
129
                            os.path.join(arc_dirpath, filename))
169
130
 
170
 
    if not PYTHON26:
171
 
        # XXX Write this _didModify attribute of zip, to trick it into writing
172
 
        # footer bytes even if there are no files in the archive (otherwise it
173
 
        # writes a 0-byte archive which is invalid).
174
 
        # Note that in Python2.6 we avoid this by always writing the top-level
175
 
        # file or directory, at least.
176
 
        zip._didModify = True
177
131
    zip.close()
178
132
 
179
 
def write_submission_info(filename, submission):
180
 
    """Write human-readable meta-data about a submission to a file.
181
 
    @param filename: Filename to write to.
182
 
    @param submission: Submission object.
183
 
    """
184
 
    with codecs.open(filename, 'w', 'utf-8') as f:
185
 
        if submission.assessed.is_group:
186
 
            # A group project
187
 
            print >>f, "Group: %s (%s)" % (
188
 
                submission.assessed.principal.short_name,
189
 
                submission.assessed.principal.display_name)
190
 
        else:
191
 
            # A solo project
192
 
            # Only show the two fields if they are different (only in rare
193
 
            # circumstances)
194
 
            if submission.assessed.principal != submission.submitter:
195
 
                print >>f, "Author: %s (%s)" % (
196
 
                    submission.assessed.principal.short_name,
197
 
                    submission.assessed.principal.display_name)
198
 
        print >>f, "Submitter: %s (%s)" % (
199
 
            submission.submitter.short_name,
200
 
            submission.submitter.display_name)
201
 
        print >>f, "Date: %s" % (
202
 
            submission.date_submitted.strftime("%Y-%m-%d %H:%M:%S"))
203
 
        print >>f, "SVN Revision: %s" % submission.revision
204
 
        print >>f, "SVN Path: %s" % submission.path
205
 
 
206
133
def main(argv=None):
207
134
    global store
208
135
    if argv is None:
232
159
        action="store_true", dest="zip",
233
160
        help="Store each submission in a Zip file.",
234
161
        default=False)
235
 
    parser.add_option("-v", "--verbose",
236
 
        action="store_true", dest="verbose",
237
 
        help="Print out the name of each submission as it is extracted.",
238
 
        default=False)
239
 
    parser.add_option("--no-txt",
240
 
        action="store_false", dest="txt",
241
 
        help="Disable writing a text file with data about each submission.",
242
 
        default=True)
243
162
    (options, args) = parser.parse_args(argv[1:])
244
163
 
245
164
    if len(args) < 2:
315
234
    for submission in project.latest_submissions:
316
235
        try:
317
236
            fetch_submission(submission, target_dir, svnclient, config,
318
 
                             zip=options.zip, txt=options.txt,
319
 
                             verbose=options.verbose)
 
237
                             zip=options.zip)
320
238
        except Exception, e:
321
239
            # Catch all exceptions (to ensure if one student has a problem, it
322
240
            # is reported, and we can continue)
323
241
            print >>sys.stderr, "ERROR on submission for %s:" % (
324
242
                submission.assessed.principal.display_name)
325
 
            traceback.print_exc()
 
243
            traceback.print_exc(e)
326
244
 
327
245
if __name__ == "__main__":
328
246
    sys.exit(main(sys.argv))