| Name | Total Lines | Lines of Code | Total Coverage | Code Coverage |
|---|---|---|---|---|
| app/controllers/submissions_controller.rb | 579 | 472 | 90.33%
|
88.14%
|
Code reported as executed by Ruby looks like this...and this: this line is also marked as covered.Lines considered as run by rcov, but not reported by Ruby, look like this,and this: these lines were inferred by rcov (using simple heuristics).Finally, here's a line marked as not executed.
1 require 'fastercsv' |
2 |
3 class SubmissionsController < ApplicationController |
4 include SubmissionsHelper |
5 include PaginationHelper |
6 |
7 before_filter :authorize_only_for_admin, |
8 :except => [:server_time, |
9 :populate_file_manager, |
10 :browse, |
11 :index, |
12 :file_manager, |
13 :update_files, |
14 :download, |
15 :s_table_paginate, |
16 :collect_and_begin_grading, |
17 :manually_collect_and_begin_grading, |
18 :collect_ta_submissions, |
19 :repo_browser, |
20 :populate_repo_browser, |
21 :update_converted_pdfs] |
22 before_filter :authorize_for_ta_and_admin, |
23 :only => [:browse, |
24 :index, |
25 :s_table_paginate, |
26 :collect_and_begin_grading, |
27 :manually_collect_and_begin_grading, |
28 :collect_ta_submissions, |
29 :repo_browser, |
30 :populate_repo_browser, |
31 :update_converted_pdfs] |
32 before_filter :authorize_for_student, |
33 :only => [:file_manager, |
34 :populate_file_manager, |
35 :update_files] |
36 before_filter :authorize_for_user, :only => [:download] |
37 |
38 S_TABLE_PARAMS = { |
39 :model => Grouping, |
40 :per_pages => [15, 30, 50, 100, 150, 500, 1000], |
41 :filters => { |
42 'none' => { |
43 :display => I18n.t("browse_submissions.show_all"), |
44 :proc => lambda { |params, to_include| |
45 return params[:assignment].groupings.all(:include => to_include)}}, |
46 'unmarked' => { |
47 :display => I18n.t("browse_submissions.show_unmarked"), |
48 :proc => lambda { |params, to_include| return params[:assignment].groupings.all(:include => [to_include]).select{|g| !g.has_submission? || (g.has_submission? && g.current_submission_used.result.marking_state == Result::MARKING_STATES[:unmarked]) } }}, |
49 'partial' => { |
50 :display => I18n.t("browse_submissions.show_partial"), |
51 :proc => lambda { |params, to_include| return params[:assignment].groupings.all(:include => [to_include]).select{|g| g.has_submission? && g.current_submission_used.result.marking_state == Result::MARKING_STATES[:partial] } }}, |
52 'complete' => { |
53 :display => I18n.t("browse_submissions.show_complete"), |
54 :proc => lambda { |params, to_include| return params[:assignment].groupings.all(:include => [to_include]).select{|g| g.has_submission? && g.current_submission_used.result.marking_state == Result::MARKING_STATES[:complete] } }}, |
55 'released' => { |
56 :display => I18n.t("browse_submissions.show_released"), |
57 :proc => lambda { |params, to_include| return params[:assignment].groupings.all(:include => [to_include]).select{|g| g.has_submission? && g.current_submission_used.result.released_to_students} }}, |
58 'assigned' => { |
59 :display => I18n.t("browse_submissions.show_assigned_to_me"), |
60 :proc => lambda { |params, to_include| return params[:assignment].ta_memberships.find_all_by_user_id(params[:user_id], :include => [:grouping => to_include]).collect{|m| m.grouping} }} |
61 }, |
62 :sorts => { |
63 'group_name' => lambda { |a,b| a.group.group_name.downcase <=> b.group.group_name.downcase}, |
64 'repo_name' => lambda { |a,b| a.group.repo_name.downcase <=> b.group.repo_name.downcase }, |
65 'revision_timestamp' => lambda { |a,b| |
66 return -1 if !a.has_submission? |
67 return 1 if !b.has_submission? |
68 return a.current_submission_used.revision_timestamp <=> b.current_submission_used.revision_timestamp |
69 }, |
70 'marking_state' => lambda { |a,b| |
71 return -1 if !a.has_submission? |
72 return 1 if !b.has_submission? |
73 return a.current_submission_used.result.marking_state <=> b.current_submission_used.result.marking_state |
74 }, |
75 'total_mark' => lambda { |a,b| |
76 return -1 if !a.has_submission? |
77 return 1 if !b.has_submission? |
78 return a.current_submission_used.result.total_mark <=> b.current_submission_used.result.total_mark |
79 }, |
80 'grace_credits_used' => lambda { |a,b| |
81 return a.grace_period_deduction_sum <=> b.grace_period_deduction_sum |
82 }, |
83 'section' => lambda { |a,b| |
84 return -1 if !a.section |
85 return 1 if !b.section |
86 return a.section <=> b.section |
87 } |
88 } |
89 } |
90 |
91 def repo_browser |
92 @assignment = Assignment.find(params[:assignment_id]) |
93 @grouping = Grouping.find(params[:id]) |
94 @assignment = @grouping.assignment |
95 @path = params[:path] || '/' |
96 @previous_path = File.split(@path).first |
97 @repository_name = @grouping.group.repository_name |
98 repo = @grouping.group.repo |
99 begin |
100 if !params[:revision_timestamp].nil? |
101 @revision_number = repo.get_revision_by_timestamp(Time.parse(params[:revision_timestamp])).revision_number |
102 elsif !params[:revision_number].nil? |
103 @revision_number = params[:revision_number].to_i |
104 else |
105 @revision_number = repo.get_latest_revision.revision_number |
106 end |
107 @revision = repo.get_revision(@revision_number) |
108 @revision_timestamp = @revision.timestamp |
109 repo.close |
110 rescue Exception => e |
111 flash[:error] = e.message |
112 @revision_number = repo.get_latest_revision.revision_number |
113 @revision_timestamp = repo.get_latest_revision.timestamp |
114 repo.close |
115 end |
116 end |
117 |
118 def populate_repo_browser |
119 @grouping = Grouping.find(params[:id]) |
120 @assignment = @grouping.assignment |
121 @path = params[:path] || '/' |
122 @revision_number = params[:revision_number] |
123 @previous_path = File.split(@path).first |
124 @grouping.group.access_repo do |repo| |
125 begin |
126 @revision = repo.get_revision(params[:revision_number].to_i) |
127 @directories = @revision.directories_at_path(File.join(@assignment.repository_folder, @path)) |
128 @files = @revision.files_at_path(File.join(@assignment.repository_folder, @path)) |
129 rescue Exception => @find_revision_error |
130 render :template => 'submissions/repo_browser/find_revision_error' |
131 return |
132 end |
133 @table_rows = {} |
134 @files.sort.each do |file_name, file| |
135 @table_rows[file.id] = construct_repo_browser_table_row(file_name, file) |
136 end |
137 @directories.sort.each do |directory_name, directory| |
138 @table_rows[directory.id] = construct_repo_browser_directory_table_row(directory_name, directory) |
139 end |
140 render :template => 'submissions/repo_browser/populate_repo_browser' |
141 end |
142 end |
143 |
144 def file_manager |
145 @assignment = Assignment.find(params[:assignment_id]) |
146 @grouping = current_user.accepted_grouping_for(@assignment.id) |
147 |
148 if @grouping.nil? |
149 redirect_to :controller => 'assignments', |
150 :action => 'student_interface', |
151 :id => params[:id] |
152 return |
153 end |
154 |
155 user_group = @grouping.group |
156 @path = params[:path] || '/' |
157 |
158 # Some vars need to be set in update_files too, so do this in a |
159 # helper. See update_files action where this is used as well. |
160 set_filebrowser_vars(user_group, @assignment) |
161 end |
162 |
163 def populate_file_manager |
164 @assignment = Assignment.find(params[:assignment_id]) |
165 @grouping = current_user.accepted_grouping_for(@assignment.id) |
166 user_group = @grouping.group |
167 revision_number= params[:revision_number] |
168 @path = params[:path] || '/' |
169 @previous_path = File.split(@path).first |
170 |
171 user_group.access_repo do |repo| |
172 if revision_number.nil? |
173 @revision = repo.get_latest_revision |
174 else |
175 @revision = repo.get_revision(revision_number.to_i) |
176 end |
177 @directories = @revision.directories_at_path(File.join(@assignment.repository_folder, @path)) |
178 @files = @revision.files_at_path(File.join(@assignment.repository_folder, @path)) |
179 @table_rows = {} |
180 @files.sort.each do |file_name, file| |
181 @table_rows[file.id] = construct_file_manager_table_row(file_name, file) |
182 end |
183 if @grouping.repository_external_commits_only? |
184 @directories.sort.each do |directory_name, directory| |
185 @table_rows[directory.id] = construct_file_manager_dir_table_row(directory_name, directory) |
186 end |
187 end |
188 render :action => 'file_manager_populate' |
189 end |
190 end |
191 |
192 def manually_collect_and_begin_grading |
193 @grouping = Grouping.find(params[:id]) |
194 @revision_number = params[:current_revision_number].to_i |
195 SubmissionCollector.instance.manually_collect_submission(@grouping, |
196 @revision_number) |
197 redirect_to :action => 'update_converted_pdfs', :id => @grouping.id |
198 end |
199 |
200 def collect_and_begin_grading |
201 assignment = Assignment.find(params[:assignment_id]) |
202 grouping = Grouping.find(params[:id]) |
203 if !assignment.submission_rule.can_collect_now? |
204 flash[:error] = I18n.t("browse_submissions.could_not_collect", |
205 :group_name => grouping.group.group_name) |
206 else |
207 #Push grouping to the priority queue |
208 SubmissionCollector.instance.push_grouping_to_priority_queue(grouping) |
209 flash[:success] = I18n.t("collect_submissions.priority_given") |
210 end |
211 redirect_to :action => 'browse', :id => assignment.id |
212 end |
213 |
214 def collect_all_submissions |
215 assignment = Assignment.find(params[:assignment_id], :include => [:groupings]) |
216 if !assignment.submission_rule.can_collect_now? |
217 flash[:error] = I18n.t("collect_submissions.could_not_collect", |
218 :assignment_identifier => assignment.short_identifier) |
219 else |
220 submission_collector = SubmissionCollector.instance |
221 submission_collector.push_groupings_to_queue(assignment.groupings) |
222 flash[:success] = I18n.t("collect_submissions.collection_job_started", |
223 :assignment_identifier => assignment.short_identifier) |
224 end |
225 redirect_to :action => 'browse', :id => assignment.id |
226 end |
227 |
228 def collect_ta_submissions |
229 assignment = Assignment.find(params[:assignment_id]) |
230 if !assignment.submission_rule.can_collect_now? |
231 flash[:error] = I18n.t("collect_submissions.could_not_collect", |
232 :assignment_identifier => assignment.short_identifier) |
233 else |
234 groupings = assignment.groupings.find(:all, :include => :tas, :conditions => ["users.id = ?", current_user.id]) |
235 submission_collector = SubmissionCollector.instance |
236 submission_collector.push_groupings_to_queue(groupings) |
237 flash[:success] = I18n.t("collect_submissions.collection_job_started", |
238 :assignment_identifier => assignment.short_identifier) |
239 end |
240 redirect_to :action => 'browse', :id => assignment.id |
241 end |
242 |
243 def update_converted_pdfs |
244 @grouping = Grouping.find(params[:grouping_id]) |
245 @submission = @grouping.current_submission_used |
246 @pdf_count= 0 |
247 @converted_count = 0 |
248 if !@submission.nil? |
249 @submission.submission_files.each do |file| |
250 if file.is_pdf? |
251 @pdf_count += 1 |
252 if file.is_converted |
253 @converted_count += 1 |
254 end |
255 end |
256 end |
257 end |
258 end |
259 |
260 def browse |
261 if current_user.ta? |
262 params[:filter] = 'assigned' |
263 else |
264 if params[:filter] == nil or params[:filter].blank? |
265 params[:filter] = 'none' |
266 end |
267 end |
268 if params[:sort_by] == nil or params[:sort_by].blank? |
269 params[:sort_by] = 'group_name' |
270 end |
271 @assignment = Assignment.find(params[:assignment_id]) |
272 @groupings, @groupings_total = handle_paginate_event( |
273 S_TABLE_PARAMS, # the data structure to handle filtering and sorting |
274 { :assignment => @assignment, # the assignment to filter by |
275 :user_id => current_user.id}, # the submissions accessable by the current user |
276 params) # additional parameters that affect things like sorting |
277 |
278 #Eager load all data only for those groupings that will be displayed |
279 sorted_groupings = @groupings |
280 @groupings = Grouping.find(:all, :conditions => {:id => sorted_groupings}, |
281 :include => [:assignment, :group, :grace_period_deductions, |
282 {:current_submission_used => :result}, |
283 {:accepted_student_memberships => :user}]) |
284 |
285 #re-sort @groupings by the previous order, because eager loading query |
286 #messed up the grouping order |
287 @groupings = sorted_groupings.map do |sorted_grouping| |
288 @groupings.detect do |unsorted_grouping| |
289 unsorted_grouping == sorted_grouping |
290 end |
291 end |
292 |
293 @current_page = params[:page].to_i() |
294 @per_page = params[:per_page] |
295 @filters = get_filters(S_TABLE_PARAMS) |
296 @per_pages = S_TABLE_PARAMS[:per_pages] |
297 @desc = params[:desc] |
298 @filter = params[:filter] |
299 @sort_by = params[:sort_by] |
300 end |
301 |
302 def index |
303 @assignments = Assignment.all(:order => :id) |
304 render :action => 'index', :layout => 'sidebar' |
305 end |
306 |
307 # update_files action handles transactional submission of files. |
308 # |
309 # Note that you shouldn't use redirect_to in this action. This |
310 # is due to @file_manager_errors, which carries over some state |
311 # to the file_manager view (via render calls). We need to do |
312 # this, because we were storing transaction errors in the flash |
313 # hash (i.e. they were stored in the browser's cookie), and in |
314 # some circumstances, this produces a cookie overflow error |
315 # when the state stored in the cookie exceeds 4k in serialized |
316 # form. This was happening prior to the fix of Github issue #30. |
317 def update_files |
318 # We'll use this hash to carry over some error state to the |
319 # file_manager view. |
320 @file_manager_errors = Hash.new |
321 assignment_id = params[:assignment_id] |
322 @assignment = Assignment.find(assignment_id) |
323 @path = params[:path] || '/' |
324 @grouping = current_user.accepted_grouping_for(assignment_id) |
325 if @grouping.repository_external_commits_only? |
326 raise I18n.t("student.submission.external_submit_only") |
327 end |
328 if !@grouping.is_valid? |
329 # can't use redirect_to here. See comment of this action for more details. |
330 set_filebrowser_vars(@grouping.group, @assignment) |
331 render :action => "file_manager", :id => assignment_id |
332 return |
333 end |
334 @grouping.group.access_repo do |repo| |
335 |
336 assignment_folder = File.join(@assignment.repository_folder, @path) |
337 |
338 # Get the revision numbers for the files that we've seen - these |
339 # values will be the "expected revision numbers" that we'll provide |
340 # to the transaction to ensure that we don't overwrite a file that's |
341 # been revised since the user last saw it. |
342 file_revisions = params[:file_revisions].nil? ? [] : params[:file_revisions] |
343 |
344 # The files that will be replaced - just give an empty array |
345 # if params[:replace_files] is nil |
346 replace_files = params[:replace_files].nil? ? {} : params[:replace_files] |
347 |
348 # The files that will be deleted |
349 delete_files = params[:delete_files].nil? ? {} : params[:delete_files] |
350 |
351 # The files that will be added |
352 new_files = params[:new_files].nil? ? {} : params[:new_files] |
353 |
354 # Create transaction, setting the author. Timestamp is implicit. |
355 txn = repo.get_transaction(current_user.user_name) |
356 |
357 log_messages = [] |
358 begin |
359 # delete files marked for deletion |
360 delete_files.keys.each do |filename| |
361 txn.remove(File.join(assignment_folder, filename), file_revisions[filename]) |
362 log_messages.push("Student '#{current_user.user_name}' deleted file '#{filename}' for assignment '#{@assignment.short_identifier}'.") |
363 end |
364 |
365 # Replace files |
366 replace_files.each do |filename, file_object| |
367 # Sometimes the file pointer of file_object is at the end of the file. |
368 # In order to avoid empty uploaded files, rewind it to be save. |
369 file_object.rewind |
370 txn.replace(File.join(assignment_folder, filename), file_object.read, file_object.content_type, file_revisions[filename]) |
371 log_messages.push("Student '#{current_user.user_name}' replaced content of file '#{filename}' for assignment '#{@assignment.short_identifier}'.") |
372 end |
373 |
374 # Add new files |
375 new_files.each do |file_object| |
376 # sanitize_file_name in SubmissionsHelper |
377 if file_object.original_filename.nil? |
378 raise I18n.t("student.submission.invalid_file_name") |
379 end |
380 # Sometimes the file pointer of file_object is at the end of the file. |
381 # In order to avoid empty uploaded files, rewind it to be save. |
382 file_object.rewind |
383 txn.add(File.join(assignment_folder, sanitize_file_name(file_object.original_filename)), file_object.read, file_object.content_type) |
384 log_messages.push("Student '#{current_user.user_name}' submitted file '#{file_object.original_filename}' for assignment '#{@assignment.short_identifier}'.") |
385 end |
386 |
387 # finish transaction |
388 if !txn.has_jobs? |
389 flash[:transaction_warning] = I18n.t("student.submission.no_action_detected") |
390 # can't use redirect_to here. See comment of this action for more details. |
391 set_filebrowser_vars(@grouping.group, @assignment) |
392 render :action => "file_manager", :id => assignment_id |
393 return |
394 end |
395 if !repo.commit(txn) |
396 @file_manager_errors[:update_conflicts] = txn.conflicts |
397 else |
398 flash[:success] = I18n.t('update_files.success') |
399 # flush log messages |
400 m_logger = MarkusLogger.instance |
401 log_messages.each do |msg| |
402 m_logger.log(msg) |
403 end |
404 end |
405 |
406 # Are we past collection time? |
407 if @assignment.submission_rule.can_collect_now? |
408 flash[:commit_notice] = @assignment.submission_rule.commit_after_collection_message(@grouping) |
409 end |
410 # can't use redirect_to here. See comment of this action for more details. |
411 set_filebrowser_vars(@grouping.group, @assignment) |
412 render :action => "file_manager", :id => assignment_id |
413 |
414 rescue Exception => e |
415 m_logger = MarkusLogger.instance |
416 m_logger.log(e.message) |
417 # can't use redirect_to here. See comment of this action for more details. |
418 @file_manager_errors[:commit_error] = e.message |
419 set_filebrowser_vars(@grouping.group, @assignment) |
420 render :action => "file_manager", :id => assignment_id |
421 end |
422 end |
423 end |
424 |
425 def download |
426 @assignment = Assignment.find(params[:id]) |
427 # find_appropriate_grouping can be found in SubmissionsHelper |
428 |
429 @grouping = find_appropriate_grouping(@assignment.id, params) |
430 |
431 revision_number = params[:revision_number] |
432 path = params[:path] || '/' |
433 @grouping.group.access_repo do |repo| |
434 if revision_number.nil? |
435 @revision = repo.get_latest_revision |
436 else |
437 @revision = repo.get_revision(revision_number.to_i) |
438 end |
439 |
440 begin |
441 file = @revision.files_at_path(File.join(@assignment.repository_folder, path))[params[:file_name]] |
442 file_contents = repo.download_as_string(file) |
443 rescue Exception => e |
444 render :text => I18n.t("student.submission.missing_file", :file_name => params[:file_name], :message => e.message) |
445 return |
446 end |
447 |
448 if SubmissionFile.is_binary?(file_contents) |
449 # If the file appears to be binary, send it as a download |
450 send_data file_contents, :disposition => 'attachment', :filename => params[:file_name] |
451 else |
452 # Otherwise, blast it out to the screen |
453 render :text => file_contents, :layout => 'sanitized_html' |
454 end |
455 end |
456 end |
457 |
458 def update_submissions |
459 return unless request.post? |
460 assignment = Assignment.find(params[:id]) |
461 errors = [] |
462 groupings = [] |
463 if params[:ap_select_full] == 'true' |
464 # We should have been passed a filter |
465 if params[:filter].blank? |
466 raise I18n.t("student.submission.expect_filter") |
467 end |
468 # Get all Groupings for this filter |
469 groupings = S_TABLE_PARAMS[:filters][params[:filter]][:proc].call({:assignment => assignment, :user_id => current_user.id}, {}) |
470 else |
471 # User selected particular Grouping IDs |
472 if params[:groupings].nil? |
473 errors.push(I18n.t('results.must_select_a_group')) |
474 else |
475 groupings = assignment.groupings.find(params[:groupings]) |
476 end |
477 end |
478 |
479 log_message = "" |
480 if !params[:release_results].nil? |
481 changed = set_release_on_results(groupings, true, errors) |
482 log_message = "Marks released for assignment '#{assignment.short_identifier}', ID: '" + |
483 "#{assignment.id}' (for #{changed} groups)." |
484 elsif !params[:unrelease_results].nil? |
485 changed = set_release_on_results(groupings, false, errors) |
486 log_message = "Marks unreleased for assignment '#{assignment.short_identifier}', ID: '" + |
487 "#{assignment.id}' (for #{changed} groups)." |
488 end |
489 |
490 |
491 if !groupings.empty? |
492 assignment.set_results_average |
493 end |
494 |
495 if changed > 0 |
496 flash[:success] = I18n.t('results.successfully_changed', {:changed => changed}) |
497 m_logger = MarkusLogger.instance |
498 m_logger.log(log_message) |
499 end |
500 flash[:errors] = errors |
501 |
502 redirect_to :action => 'browse', :id => params[:id] |
503 end |
504 |
505 def unrelease |
506 return unless request.post? |
507 if params[:groupings].nil? |
508 flash[:release_results] = I18n.t("assignment.group.select_a_group") |
509 else |
510 params[:groupings].each do |g| |
511 g.unrelease_results |
512 end |
513 m_logger = MarkusLogger.instance |
514 assignment = Assignment.find(params[:id]) |
515 m_logger.log("Marks unreleased for assignment '#{assignment.short_identifier}', ID: '" + |
516 "#{assignment.id}' (for #{params[:groupings].length} groups).") |
517 end |
518 redirect_to :action => 'browse', :id => params[:id] |
519 end |
520 |
521 # See Assignment.get_simple_csv_report for details |
522 def download_simple_csv_report |
523 assignment = Assignment.find(params[:assignment_id]) |
524 send_data assignment.get_simple_csv_report, |
525 :disposition => 'attachment', |
526 :type => 'application/vnd.ms-excel', |
527 :filename => "#{assignment.short_identifier} simple report.csv" |
528 end |
529 |
530 # See Assignment.get_detailed_csv_report for details |
531 def download_detailed_csv_report |
532 assignment = Assignment.find(params[:assignment_id]) |
533 send_data assignment.get_detailed_csv_report, |
534 :disposition => 'attachment', |
535 :type => 'application/vnd.ms-excel', |
536 :filename => "#{assignment.short_identifier} detailed report.csv" |
537 end |
538 |
539 # See Assignment.get_svn_export_commands for details |
540 def download_svn_export_commands |
541 assignment = Assignment.find(params[:assignment_id]) |
542 svn_commands = assignment.get_svn_export_commands |
543 send_data svn_commands.join("\n"), |
544 :disposition => 'attachment', |
545 :type => 'text/plain', |
546 :filename => "#{assignment.short_identifier}_svn_exports" |
547 end |
548 |
549 # See Assignment.get_svn_repo_list for details |
550 def download_svn_repo_list |
551 assignment = Assignment.find(params[:assignment_id]) |
552 send_data assignment.get_svn_repo_list, |
553 :disposition => 'attachment', |
554 :type => 'text/plain', |
555 :filename => "#{assignment.short_identifier}_svn_repo_list" |
556 end |
557 |
558 # This action is called periodically from file_manager. |
559 def server_time |
560 render :partial => 'server_time' |
561 end |
562 |
563 private |
564 |
565 # Used in update_files and file_manager actions |
566 def set_filebrowser_vars(user_group, assignment) |
567 user_group.access_repo do |repo| |
568 @revision = repo.get_latest_revision |
569 @files = @revision.files_at_path(File.join(@assignment.repository_folder, @path)) |
570 @missing_assignment_files = [] |
571 assignment.assignment_files.each do |assignment_file| |
572 if !@revision.path_exists?(File.join(assignment.repository_folder, |
573 assignment_file.filename)) |
574 @missing_assignment_files.push(assignment_file) |
575 end |
576 end |
577 end |
578 end |
579 end |
Generated on Sun Feb 05 00:08:06 -0500 2012 with rcov 0.9.10