Description:
- after submission, redirecto to edit_submission_path - add more info to submission status - fix bug #20 and #21
Commit status:
[Not Reviewed]
References:
Comments:
0 Commit comments 0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
Add another comment

r696:0b7243db68e6 - - 11 files changed: 28 inserted, 18 deleted

@@ -4,193 +4,193
4 4 before_filter :check_viewability, :except => [:index, :login]
5 5
6 6 append_before_filter :confirm_and_update_start_time,
7 7 :except => [:index,
8 8 :login,
9 9 :confirm_contest_start]
10 10
11 11 # to prevent log in box to be shown when user logged out of the
12 12 # system only in some tab
13 13 prepend_before_filter :reject_announcement_refresh_when_logged_out,
14 14 :only => [:announcements]
15 15
16 16 before_filter :authenticate_by_ip_address, :only => [:list]
17 17
18 18 # COMMENTED OUT: filter in each action instead
19 19 # before_filter :verify_time_limit, :only => [:submit]
20 20
21 21 verify :method => :post, :only => [:submit],
22 22 :redirect_to => { :action => :index }
23 23
24 24 # COMMENT OUT: only need when having high load
25 25 # caches_action :index, :login
26 26
27 27 # NOTE: This method is not actually needed, 'config/routes.rb' has
28 28 # assigned action login as a default action.
29 29 def index
30 30 redirect_to :action => 'login'
31 31 end
32 32
33 33 def login
34 34 saved_notice = flash[:notice]
35 35 reset_session
36 36 flash.now[:notice] = saved_notice
37 37
38 38 # EXPERIMENT:
39 39 # Hide login if in single user mode and the url does not
40 40 # explicitly specify /login
41 41 #
42 42 # logger.info "PATH: #{request.path}"
43 43 # if GraderConfiguration['system.single_user_mode'] and
44 44 # request.path!='/main/login'
45 45 # @hidelogin = true
46 46 # end
47 47
48 48 @announcements = Announcement.frontpage
49 49 render :action => 'login', :layout => 'empty'
50 50 end
51 51
52 52 def list
53 53 prepare_list_information
54 54 end
55 55
56 56 def help
57 57 @user = User.find(session[:user_id])
58 58 end
59 59
60 60 def submit
61 61 user = User.find(session[:user_id])
62 62
63 63 @submission = Submission.new
64 64 @submission.problem_id = params[:submission][:problem_id]
65 65 @submission.user = user
66 66 @submission.language_id = 0
67 67 if (params['file']) and (params['file']!='')
68 68 @submission.source = File.open(params['file'].path,'r:UTF-8',&:read)
69 69 @submission.source.encode!('UTF-8','UTF-8',invalid: :replace, replace: '')
70 70 @submission.source_filename = params['file'].original_filename
71 71 end
72 72
73 73 if (params[:editor_text])
74 74 language = Language.find_by_id(params[:language_id])
75 75 @submission.source = params[:editor_text]
76 76 @submission.source_filename = "live_edit.#{language.ext}"
77 77 @submission.language = language
78 78 end
79 79
80 80 @submission.submitted_at = Time.new.gmtime
81 81 @submission.ip_address = request.remote_ip
82 82
83 83 if GraderConfiguration.time_limit_mode? and user.contest_finished?
84 84 @submission.errors.add(:base,"The contest is over.")
85 85 prepare_list_information
86 86 render :action => 'list' and return
87 87 end
88 88
89 89 if @submission.valid?(@current_user)
90 90 if @submission.save == false
91 91 flash[:notice] = 'Error saving your submission'
92 92 elsif Task.create(:submission_id => @submission.id,
93 93 :status => Task::STATUS_INQUEUE) == false
94 94 flash[:notice] = 'Error adding your submission to task queue'
95 95 end
96 96 else
97 97 prepare_list_information
98 98 render :action => 'list' and return
99 99 end
100 - redirect_to :action => 'list'
100 + redirect_to edit_submission_path(@submission)
101 101 end
102 102
103 103 def source
104 104 submission = Submission.find(params[:id])
105 105 if ((submission.user_id == session[:user_id]) and
106 106 (submission.problem != nil) and
107 107 (submission.problem.available))
108 108 send_data(submission.source,
109 109 {:filename => submission.download_filename,
110 110 :type => 'text/plain'})
111 111 else
112 112 flash[:notice] = 'Error viewing source'
113 113 redirect_to :action => 'list'
114 114 end
115 115 end
116 116
117 117 def compiler_msg
118 118 @submission = Submission.find(params[:id])
119 119 if @submission.user_id == session[:user_id]
120 120 render :action => 'compiler_msg', :layout => 'empty'
121 121 else
122 122 flash[:notice] = 'Error viewing source'
123 123 redirect_to :action => 'list'
124 124 end
125 125 end
126 126
127 127 def result
128 128 if !GraderConfiguration.show_grading_result
129 129 redirect_to :action => 'list' and return
130 130 end
131 131 @user = User.find(session[:user_id])
132 132 @submission = Submission.find(params[:id])
133 133 if @submission.user!=@user
134 134 flash[:notice] = 'You are not allowed to view result of other users.'
135 135 redirect_to :action => 'list' and return
136 136 end
137 137 prepare_grading_result(@submission)
138 138 end
139 139
140 140 def load_output
141 141 if !GraderConfiguration.show_grading_result or params[:num]==nil
142 142 redirect_to :action => 'list' and return
143 143 end
144 144 @user = User.find(session[:user_id])
145 145 @submission = Submission.find(params[:id])
146 146 if @submission.user!=@user
147 147 flash[:notice] = 'You are not allowed to view result of other users.'
148 148 redirect_to :action => 'list' and return
149 149 end
150 150 case_num = params[:num].to_i
151 151 out_filename = output_filename(@user.login,
152 152 @submission.problem.name,
153 153 @submission.id,
154 154 case_num)
155 155 if !FileTest.exists?(out_filename)
156 156 flash[:notice] = 'Output not found.'
157 157 redirect_to :action => 'list' and return
158 158 end
159 159
160 160 if defined?(USE_APACHE_XSENDFILE) and USE_APACHE_XSENDFILE
161 161 response.headers['Content-Type'] = "application/force-download"
162 162 response.headers['Content-Disposition'] = "attachment; filename=\"output-#{case_num}.txt\""
163 163 response.headers["X-Sendfile"] = out_filename
164 164 response.headers['Content-length'] = File.size(out_filename)
165 165 render :nothing => true
166 166 else
167 167 send_file out_filename, :stream => false, :filename => "output-#{case_num}.txt", :type => "text/plain"
168 168 end
169 169 end
170 170
171 171 def error
172 172 @user = User.find(session[:user_id])
173 173 end
174 174
175 175 # announcement refreshing and hiding methods
176 176
177 177 def announcements
178 178 if params.has_key? 'recent'
179 179 prepare_announcements(params[:recent])
180 180 else
181 181 prepare_announcements
182 182 end
183 183 render(:partial => 'announcement',
184 184 :collection => @announcements,
185 185 :locals => {:announcement_effect => true})
186 186 end
187 187
188 188 def confirm_contest_start
189 189 user = User.find(session[:user_id])
190 190 if request.method == 'POST'
191 191 user.update_start_time
192 192 redirect_to :action => 'list'
193 193 else
194 194 @contests = user.contests
195 195 @user = user
196 196 end
@@ -1,150 +1,152
1 1 require 'csv'
2 2
3 3 class ReportController < ApplicationController
4 4
5 5 before_filter :authenticate
6 6
7 7 before_filter :admin_authorization, only: [:login_stat,:submission_stat, :stuck, :cheat_report, :cheat_scruntinize, :show_max_score]
8 8
9 9 before_filter(only: [:problem_hof]) { |c|
10 10 return false unless authenticate
11 11
12 12 admin_authorization unless GraderConfiguration["right.user_view_submission"]
13 13 }
14 14
15 15 def max_score
16 16 end
17 17
18 18 def current_score
19 19 @problems = Problem.available_problems
20 20 @users = User.includes(:contests).includes(:contest_stat).where(enabled: true)
21 21 @scorearray = calculate_max_score(@problems, @users,0,0,true)
22 22
23 23 #rencer accordingly
24 24 if params[:button] == 'download' then
25 25 csv = gen_csv_from_scorearray(@scorearray,@problems)
26 26 send_data csv, filename: 'max_score.csv'
27 27 else
28 28 #render template: 'user_admin/user_stat'
29 29 render 'current_score'
30 30 end
31 31 end
32 32
33 33 def show_max_score
34 34 #process parameters
35 35 #problems
36 36 @problems = []
37 37 if params[:problem_id]
38 38 params[:problem_id].each do |id|
39 39 next unless id.strip != ""
40 40 pid = Problem.find_by_id(id.to_i)
41 41 @problems << pid if pid
42 42 end
43 43 end
44 44
45 45 #users
46 46 @users = if params[:user] == "all" then
47 47 User.includes(:contests).includes(:contest_stat)
48 48 else
49 49 User.includes(:contests).includes(:contest_stat).where(enabled: true)
50 50 end
51 51
52 52 #set up range from param
53 53 @since_id = params.fetch(:from_id, 0).to_i
54 54 @until_id = params.fetch(:to_id, 0).to_i
55 + @since_id = nil if @since_id == 0
56 + @until_id = nil if @until_id == 0
55 57
56 58 #calculate the routine
57 59 @scorearray = calculate_max_score(@problems, @users, @since_id, @until_id)
58 60
59 61 #rencer accordingly
60 62 if params[:button] == 'download' then
61 63 csv = gen_csv_from_scorearray(@scorearray,@problems)
62 64 send_data csv, filename: 'max_score.csv'
63 65 else
64 66 #render template: 'user_admin/user_stat'
65 67 render 'max_score'
66 68 end
67 69
68 70 end
69 71
70 72 def score
71 73 if params[:commit] == 'download csv'
72 74 @problems = Problem.all
73 75 else
74 76 @problems = Problem.available_problems
75 77 end
76 78 @users = User.includes(:contests, :contest_stat).where(enabled: true)
77 79 @scorearray = Array.new
78 80 @users.each do |u|
79 81 ustat = Array.new
80 82 ustat[0] = u
81 83 @problems.each do |p|
82 84 sub = Submission.find_last_by_user_and_problem(u.id,p.id)
83 85 if (sub!=nil) and (sub.points!=nil) and p and p.full_score
84 86 ustat << [(sub.points.to_f*100/p.full_score).round, (sub.points>=p.full_score)]
85 87 else
86 88 ustat << [0,false]
87 89 end
88 90 end
89 91 @scorearray << ustat
90 92 end
91 93 if params[:commit] == 'download csv' then
92 94 csv = gen_csv_from_scorearray(@scorearray,@problems)
93 95 send_data csv, filename: 'last_score.csv'
94 96 else
95 97 render template: 'user_admin/user_stat'
96 98 end
97 99
98 100 end
99 101
100 102 def login_stat
101 103 @logins = Array.new
102 104
103 105 date_and_time = '%Y-%m-%d %H:%M'
104 106 begin
105 107 md = params[:since_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
106 108 @since_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
107 109 rescue
108 110 @since_time = DateTime.new(1000,1,1)
109 111 end
110 112 begin
111 113 md = params[:until_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
112 114 @until_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
113 115 rescue
114 116 @until_time = DateTime.new(3000,1,1)
115 117 end
116 118
117 119 User.all.each do |user|
118 120 @logins << { id: user.id,
119 121 login: user.login,
120 122 full_name: user.full_name,
121 123 count: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
122 124 user.id,@since_time,@until_time)
123 125 .count(:id),
124 126 min: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
125 127 user.id,@since_time,@until_time)
126 128 .minimum(:created_at),
127 129 max: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
128 130 user.id,@since_time,@until_time)
129 131 .maximum(:created_at),
130 132 ip: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
131 133 user.id,@since_time,@until_time)
132 134 .select(:ip_address).uniq
133 135
134 136 }
135 137 end
136 138 end
137 139
138 140 def submission_stat
139 141
140 142 date_and_time = '%Y-%m-%d %H:%M'
141 143 begin
142 144 @since_time = DateTime.strptime(params[:since_datetime],date_and_time)
143 145 rescue
144 146 @since_time = DateTime.new(1000,1,1)
145 147 end
146 148 begin
147 149 @until_time = DateTime.strptime(params[:until_datetime],date_and_time)
148 150 rescue
149 151 @until_time = DateTime.new(3000,1,1)
150 152 end
@@ -1,198 +1,201
1 1 # Methods added to this helper will be available to all templates in the application.
2 2 module ApplicationHelper
3 3
4 4 #new bootstrap header
5 5 def navbar_user_header
6 6 left_menu = ''
7 7 right_menu = ''
8 8 user = User.find(session[:user_id])
9 9
10 10 if (user!=nil) and (GraderConfiguration.show_tasks_to?(user))
11 11 left_menu << add_menu("#{I18n.t 'menu.tasks'}", 'tasks', 'list')
12 12 left_menu << add_menu("#{I18n.t 'menu.submissions'}", 'main', 'submission')
13 13 left_menu << add_menu("#{I18n.t 'menu.test'}", 'test', 'index')
14 14 end
15 15
16 16 if GraderConfiguration['right.user_hall_of_fame']
17 17 left_menu << add_menu("#{I18n.t 'menu.hall_of_fame'}", 'report', 'problem_hof')
18 18 end
19 19
20 20 right_menu << add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-question-sign')}".html_safe, 'main', 'help')
21 21 right_menu << add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-comment')}".html_safe, 'messages', 'list', {title: I18n.t('menu.messages'), data: {toggle: 'tooltip'}})
22 22 if GraderConfiguration['system.user_setting_enabled']
23 23 right_menu << add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-cog')}".html_safe, 'users', 'index', {title: I18n.t('menu.settings'), data: {toggle: 'tooltip'}})
24 24 end
25 25 right_menu << add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-log-out')} #{user.full_name}".html_safe, 'main', 'login', {title: I18n.t('menu.log_out'), data: {toggle: 'tooltip'}})
26 26
27 27
28 28 result = content_tag(:ul,left_menu.html_safe,class: 'nav navbar-nav') + content_tag(:ul,right_menu.html_safe,class: 'nav navbar-nav navbar-right')
29 29 end
30 30
31 31 def add_menu(title, controller, action, html_option = {})
32 32 link_option = {controller: controller, action: action}
33 33 html_option[:class] = (html_option[:class] || '') + " active" if current_page?(link_option)
34 34 content_tag(:li, link_to(title,link_option),html_option)
35 35 end
36 36
37 37 def user_header
38 38 menu_items = ''
39 39 user = User.find(session[:user_id])
40 40
41 41 if (user!=nil) and (session[:admin])
42 42 # admin menu
43 43 menu_items << "<b>Administrative task:</b> "
44 44 append_to menu_items, '[Announcements]', 'announcements', 'index'
45 45 append_to menu_items, '[Msg console]', 'messages', 'console'
46 46 append_to menu_items, '[Problems]', 'problems', 'index'
47 47 append_to menu_items, '[Users]', 'user_admin', 'index'
48 48 append_to menu_items, '[Results]', 'user_admin', 'user_stat'
49 49 append_to menu_items, '[Report]', 'report', 'multiple_login'
50 50 append_to menu_items, '[Graders]', 'graders', 'list'
51 51 append_to menu_items, '[Contests]', 'contest_management', 'index'
52 52 append_to menu_items, '[Sites]', 'sites', 'index'
53 53 append_to menu_items, '[System config]', 'configurations', 'index'
54 54 menu_items << "<br/>"
55 55 end
56 56
57 57 # main page
58 58 append_to menu_items, "[#{I18n.t 'menu.main'}]", 'main', 'list'
59 59 append_to menu_items, "[#{I18n.t 'menu.messages'}]", 'messages', 'list'
60 60
61 61 if (user!=nil) and (GraderConfiguration.show_tasks_to?(user))
62 62 append_to menu_items, "[#{I18n.t 'menu.tasks'}]", 'tasks', 'list'
63 63 append_to menu_items, "[#{I18n.t 'menu.submissions'}]", 'main', 'submission'
64 64 append_to menu_items, "[#{I18n.t 'menu.test'}]", 'test', 'index'
65 65 end
66 66
67 67 if GraderConfiguration['right.user_hall_of_fame']
68 68 append_to menu_items, "[#{I18n.t 'menu.hall_of_fame'}]", 'report', 'problem_hof'
69 69 end
70 70 append_to menu_items, "[#{I18n.t 'menu.help'}]", 'main', 'help'
71 71
72 72 if GraderConfiguration['system.user_setting_enabled']
73 73 append_to menu_items, "[#{I18n.t 'menu.settings'}]", 'users', 'index'
74 74 end
75 75 append_to menu_items, "[#{I18n.t 'menu.log_out'}]", 'main', 'login'
76 76
77 77 menu_items.html_safe
78 78 end
79 79
80 80 def append_to(option,label, controller, action)
81 81 option << ' ' if option!=''
82 82 option << link_to_unless_current(label,
83 83 :controller => controller,
84 84 :action => action)
85 85 end
86 86
87 87 def format_short_time(time)
88 - now = Time.now.gmtime
88 + now = Time.zone.now
89 89 st = ''
90 - if (time.yday != now.yday) or
91 - (time.year != now.year)
92 - st = time.strftime("%x ")
90 + if (time.yday != now.yday) or (time.year != now.year)
91 + st = time.strftime("%d/%m/%y ")
93 92 end
94 93 st + time.strftime("%X")
95 94 end
96 95
97 96 def format_short_duration(duration)
98 97 return '' if duration==nil
99 98 d = duration.to_f
100 99 return Time.at(d).gmtime.strftime("%X")
101 100 end
102 101
102 + def format_full_time_ago(time)
103 + st = time_ago_in_words(time) + ' ago (' + format_short_time(time) + ')'
104 + end
105 +
103 106 def read_textfile(fname,max_size=2048)
104 107 begin
105 108 File.open(fname).read(max_size)
106 109 rescue
107 110 nil
108 111 end
109 112 end
110 113
111 114 def toggle_button(on,toggle_url,id, option={})
112 115 btn_size = option[:size] || 'btn-xs'
113 116 link_to (on ? "Yes" : "No"), toggle_url,
114 117 {class: "btn btn-block #{btn_size} btn-#{on ? 'success' : 'default'} ajax-toggle",
115 118 id: id,
116 119 data: {remote: true, method: 'get'}}
117 120 end
118 121
119 122 def get_ace_mode(language)
120 123 # return ace mode string from Language
121 124
122 125 case language.pretty_name
123 126 when 'Pascal'
124 127 'ace/mode/pascal'
125 128 when 'C++','C'
126 129 'ace/mode/c_cpp'
127 130 when 'Ruby'
128 131 'ace/mode/ruby'
129 132 when 'Python'
130 133 'ace/mode/python'
131 134 when 'Java'
132 135 'ace/mode/java'
133 136 else
134 137 'ace/mode/c_cpp'
135 138 end
136 139 end
137 140
138 141
139 142 def user_title_bar(user)
140 143 header = ''
141 144 time_left = ''
142 145
143 146 #
144 147 # if the contest is over
145 148 if GraderConfiguration.time_limit_mode?
146 149 if user.contest_finished?
147 150 header = <<CONTEST_OVER
148 151 <tr><td colspan="2" align="center">
149 152 <span class="contest-over-msg">THE CONTEST IS OVER</span>
150 153 </td></tr>
151 154 CONTEST_OVER
152 155 end
153 156 if !user.contest_started?
154 157 time_left = "&nbsp;&nbsp;" + (t 'title_bar.contest_not_started')
155 158 else
156 159 time_left = "&nbsp;&nbsp;" + (t 'title_bar.remaining_time') +
157 160 " #{format_short_duration(user.contest_time_left)}"
158 161 end
159 162 end
160 163
161 164 #
162 165 # if the contest is in the anaysis mode
163 166 if GraderConfiguration.analysis_mode?
164 167 header = <<ANALYSISMODE
165 168 <tr><td colspan="2" align="center">
166 169 <span class="contest-over-msg">ANALYSIS MODE</span>
167 170 </td></tr>
168 171 ANALYSISMODE
169 172 end
170 173
171 174 contest_name = GraderConfiguration['contest.name']
172 175
173 176 #
174 177 # build real title bar
175 178 result = <<TITLEBAR
176 179 <div class="title">
177 180 <table>
178 181 #{header}
179 182 <tr>
180 183 <td class="left-col">
181 184 #{user.full_name}<br/>
182 185 #{t 'title_bar.current_time'} #{format_short_time(Time.zone.now)}
183 186 #{time_left}
184 187 <br/>
185 188 </td>
186 189 <td class="right-col">#{contest_name}</td>
187 190 </tr>
188 191 </table>
189 192 </div>
190 193 TITLEBAR
191 194 result.html_safe
192 195 end
193 196
194 197 def markdown(text)
195 198 markdown = RDiscount.new(text)
196 199 markdown.to_html.html_safe
197 200 end
198 201
@@ -1,134 +1,134
1 1 class Submission < ActiveRecord::Base
2 2
3 3 belongs_to :language
4 4 belongs_to :problem
5 5 belongs_to :user
6 6
7 7 before_validation :assign_problem
8 8 before_validation :assign_language
9 9
10 10 validates_presence_of :source
11 11 validates_length_of :source, :maximum => 100_000, :allow_blank => true, :message => 'too long'
12 12 validates_length_of :source, :minimum => 1, :allow_blank => true, :message => 'too short'
13 13 validate :must_have_valid_problem
14 14 validate :must_specify_language
15 15
16 16 has_one :task
17 17
18 18 before_save :assign_latest_number_if_new_recond
19 19
20 20 def self.find_last_by_user_and_problem(user_id, problem_id)
21 21 where("user_id = ? AND problem_id = ?",user_id,problem_id).last
22 22 end
23 23
24 24 def self.find_all_last_by_problem(problem_id)
25 25 # need to put in SQL command, maybe there's a better way
26 26 Submission.includes(:user).find_by_sql("SELECT * FROM submissions " +
27 27 "WHERE id = " +
28 28 "(SELECT MAX(id) FROM submissions AS subs " +
29 29 "WHERE subs.user_id = submissions.user_id AND " +
30 30 "problem_id = " + problem_id.to_s + " " +
31 31 "GROUP BY user_id) " +
32 32 "ORDER BY user_id")
33 33 end
34 34
35 35 def self.find_in_range_by_user_and_problem(user_id, problem_id,since_id,until_id)
36 36 records = Submission.where(problem_id: problem_id,user_id: user_id)
37 - records = records.where('id >= ?',since_id) if since_id > 0
38 - records = records.where('id <= ?',until_id) if until_id > 0
37 + records = records.where('id >= ?',since_id) if since_id and since_id > 0
38 + records = records.where('id <= ?',until_id) if until_id and until_id > 0
39 39 records.all
40 40 end
41 41
42 42 def self.find_last_for_all_available_problems(user_id)
43 43 submissions = Array.new
44 44 problems = Problem.available_problems
45 45 problems.each do |problem|
46 46 sub = Submission.find_last_by_user_and_problem(user_id, problem.id)
47 47 submissions << sub if sub!=nil
48 48 end
49 49 submissions
50 50 end
51 51
52 52 def self.find_by_user_problem_number(user_id, problem_id, number)
53 53 where("user_id = ? AND problem_id = ? AND number = ?",user_id,problem_id,number).first
54 54 end
55 55
56 56 def self.find_all_by_user_problem(user_id, problem_id)
57 57 where("user_id = ? AND problem_id = ?",user_id,problem_id)
58 58 end
59 59
60 60 def download_filename
61 61 if self.problem.output_only
62 62 return self.source_filename
63 63 else
64 64 timestamp = self.submitted_at.localtime.strftime("%H%M%S")
65 65 return "#{self.problem.name}-#{timestamp}.#{self.language.ext}"
66 66 end
67 67 end
68 68
69 69 protected
70 70
71 71 def self.find_option_in_source(option, source)
72 72 if source==nil
73 73 return nil
74 74 end
75 75 i = 0
76 76 source.each_line do |s|
77 77 if s =~ option
78 78 words = s.split
79 79 return words[1]
80 80 end
81 81 i = i + 1
82 82 if i==10
83 83 return nil
84 84 end
85 85 end
86 86 return nil
87 87 end
88 88
89 89 def self.find_language_in_source(source, source_filename="")
90 90 langopt = find_option_in_source(/^LANG:/,source)
91 91 if langopt
92 92 return (Language.find_by_name(langopt) ||
93 93 Language.find_by_pretty_name(langopt))
94 94 else
95 95 if source_filename
96 96 return Language.find_by_extension(source_filename.split('.').last)
97 97 else
98 98 return nil
99 99 end
100 100 end
101 101 end
102 102
103 103 def self.find_problem_in_source(source, source_filename="")
104 104 prob_opt = find_option_in_source(/^TASK:/,source)
105 105 if problem = Problem.find_by_name(prob_opt)
106 106 return problem
107 107 else
108 108 if source_filename
109 109 return Problem.find_by_name(source_filename.split('.').first)
110 110 else
111 111 return nil
112 112 end
113 113 end
114 114 end
115 115
116 116 def assign_problem
117 117 if self.problem_id!=-1
118 118 begin
119 119 self.problem = Problem.find(self.problem_id)
120 120 rescue ActiveRecord::RecordNotFound
121 121 self.problem = nil
122 122 end
123 123 else
124 124 self.problem = Submission.find_problem_in_source(self.source,
125 125 self.source_filename)
126 126 end
127 127 end
128 128
129 129 def assign_language
130 130 self.language = Submission.find_language_in_source(self.source,
131 131 self.source_filename)
132 132 end
133 133
134 134 # validation codes
@@ -1,26 +1,28
1 -
2 1 - if submission.nil?
3 2 = "-"
4 3 - else
4 + %strong= "Submission ID:"
5 + = submission.id
6 + %br
5 7 - unless submission.graded_at
6 - = t 'main.submitted_at'
7 - = format_short_time(submission.submitted_at.localtime)
8 + %strong= t 'main.submitted_at:'
9 + = format_full_time_ago(submission.submitted_at.localtime)
8 10 - else
9 - %strong= t 'main.graded_at'
10 - = "#{format_short_time(submission.graded_at.localtime)} "
11 + %strong= t 'main.graded_at:'
12 + = format_full_time_ago(submission.graded_at.localtime)
11 13 %br
12 14 - if GraderConfiguration['ui.show_score']
13 15 %strong=t 'main.score'
14 16 = "#{(submission.points*100/submission.problem.full_score).to_i} "
15 17 = " ["
16 18 %tt
17 19 = submission.grader_comment
18 20 = "]"
19 21 %br
20 22 %strong View:
21 23 - if GraderConfiguration.show_grading_result
22 24 = link_to '[detailed result]', :action => 'result', :id => submission.id
23 - = link_to "#{t 'main.cmp_msg'}", {:action => 'compiler_msg', :id => submission.id}, {popup: true,class: 'btn btn-xs btn-info'}
25 + = link_to "#{t 'main.cmp_msg'}", {:action => 'compiler_msg', :id => submission.id}, {popup: true,class: 'btn btn-xs btn-info'} if submission.graded_at
24 26 = link_to "#{t 'main.src_link'}", download_submission_path(submission.id), class: 'btn btn-xs btn-info'
25 27 = link_to "#{t 'main.submissions_link'}", problem_submissions_path(problem_id), class: 'btn btn-xs btn-info'
26 28
@@ -1,49 +1,49
1 1 %h1 Maximum score
2 2
3 3 = form_tag report_show_max_score_path
4 4 .row
5 5 .col-md-4
6 6 .panel.panel-primary
7 7 .panel-heading
8 8 Problems
9 9 .panel-body
10 10 %p
11 11 Select problem(s) that we wish to know the score.
12 12 = label_tag :problem_id, "Problems"
13 13 = select_tag 'problem_id[]',
14 14 options_for_select(Problem.all.collect {|p| ["[#{p.name}] #{p.full_name}", p.id]},params[:problem_id]),
15 15 { class: 'select2 form-control', multiple: "true" }
16 16 .col-md-4
17 17 .panel.panel-primary
18 18 .panel-heading
19 19 Submission range
20 20 .panel-body
21 21 %p
22 22 Input minimum and maximum range of submission ID that should be included. A blank value for min and max means -1 and infinity, respectively.
23 23 .form-group
24 24 = label_tag :from, "Min"
25 25 = text_field_tag 'from_id', @since_id, class: "form-control"
26 26 .form-group
27 27 = label_tag :from, "Max"
28 28 = text_field_tag 'to_id', @until_id, class: "form-control"
29 29 .col-md-4
30 30 .panel.panel-primary
31 31 .panel-heading
32 32 Users
33 33 .panel-body
34 34 .radio
35 35 %label
36 - = radio_button_tag 'users', 'all', true
36 + = radio_button_tag 'users', 'all', (params[:users] == "all")
37 37 All users
38 38 .radio
39 39 %label
40 - = radio_button_tag 'users', 'enabled'
40 + = radio_button_tag 'users', 'enabled', (params[:users] == "enabled")
41 41 Only enabled users
42 42 .row
43 43 .col-md-12
44 44 = button_tag 'Show', class: "btn btn-primary btn-large", value: "show"
45 45 = button_tag 'Download CSV', class: "btn btn-primary btn-large", value: "download"
46 46
47 47 - if @scorearray
48 48 %h2 Result
49 49 =render "score_table"
@@ -1,126 +1,126
1 1 %h2 Live submit
2 2 %br
3 3
4 4 %textarea#text_sourcecode{style: "display:none"}~ @source
5 5 .container
6 6 .row
7 7 .col-md-12
8 8 .alert.alert-info
9 9 Write your code in the following box, choose language, and click submit button when finished
10 10 .row
11 11 .col-md-8
12 12 %div#editor{style: 'height: 500px; border-radius: 7px; font-size: 14px;'}
13 13 .col-md-4
14 14 - # submission form
15 15 = form_tag({controller: :main, :action => 'submit'}, :multipart => true, class: 'form') do
16 16
17 17 = hidden_field_tag 'editor_text', @source
18 18 = hidden_field_tag 'submission[problem_id]', @problem.id
19 19 .form-group
20 20 = label_tag "Task:"
21 21 = text_field_tag 'asdf', "#{@problem.long_name}", class: 'form-control', disabled: true
22 22
23 23 .form-group
24 24 = label_tag 'Language'
25 25 = select_tag 'language_id', options_from_collection_for_select(Language.all, 'id', 'pretty_name', @lang_id || Language.find_by_pretty_name("Python").id || Language.first.id), class: 'form-control select', style: "width: 100px"
26 26 .form-group
27 27 = submit_tag 'Submit', class: 'btn btn-success', id: 'live_submit',
28 28 data: {confirm: "Submitting this source code for task #{@problem.long_name}?"}
29 29 - # latest submission status
30 - .panel.panel-info
30 + .panel{class: (@submission && @submission.graded_at) ? "panel-info" : "panel-warning"}
31 31 .panel-heading
32 32 Latest Submission Status
33 33 = link_to "Refresh",get_latest_submission_status_submissions_path(@submission.user,@problem), class: "btn btn-default btn-sm", remote: true if @submission
34 34 .panel-body
35 35 - if @submission
36 36 = render :partial => 'submission_short',
37 37 :locals => {submission: @submission, problem_name: @problem.name, problem_id: @problem.id }
38 38 .row
39 39 .col-md-12
40 40 %h2 Console
41 41 %textarea#console{style: 'height: 100%; width: 100%;background-color:#000;color:#fff;font-family: consolas, monaco, "Droid Sans Mono";',rows: 20}
42 42
43 43 :javascript
44 44 $(document).ready(function() {
45 45 e = ace.edit("editor")
46 46 e.setValue($("#text_sourcecode").val());
47 47 e.gotoLine(1);
48 48 $("#language_id").trigger('change');
49 49 brython();
50 50 });
51 51
52 52
53 53 %script#__main__{type:'text/python3'}
54 54 :plain
55 55 import sys
56 56 import traceback
57 57
58 58 from browser import document as doc
59 59 from browser import window, alert, console
60 60
61 61 _credits = """ Thanks to CWI, CNRI, BeOpen.com, Zope Corporation and a cast of thousands
62 62 for supporting Python development. See www.python.org for more information."""
63 63
64 64 _copyright = """Copyright (c) 2012, Pierre Quentel pierre.quentel@gmail.com
65 65 All Rights Reserved.
66 66
67 67 Copyright (c) 2001-2013 Python Software Foundation.
68 68 All Rights Reserved.
69 69
70 70 Copyright (c) 2000 BeOpen.com.
71 71 All Rights Reserved.
72 72
73 73 Copyright (c) 1995-2001 Corporation for National Research Initiatives.
74 74 All Rights Reserved.
75 75
76 76 Copyright (c) 1991-1995 Stichting Mathematisch Centrum, Amsterdam.
77 77 All Rights Reserved."""
78 78
79 79 _license = """Copyright (c) 2012, Pierre Quentel pierre.quentel@gmail.com
80 80 All rights reserved.
81 81
82 82 Redistribution and use in source and binary forms, with or without
83 83 modification, are permitted provided that the following conditions are met:
84 84
85 85 Redistributions of source code must retain the above copyright notice, this
86 86 list of conditions and the following disclaimer. Redistributions in binary
87 87 form must reproduce the above copyright notice, this list of conditions and
88 88 the following disclaimer in the documentation and/or other materials provided
89 89 with the distribution.
90 90 Neither the name of the <ORGANIZATION> nor the names of its contributors may
91 91 be used to endorse or promote products derived from this software without
92 92 specific prior written permission.
93 93
94 94 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
95 95 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
96 96 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
97 97 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
98 98 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
99 99 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
100 100 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
101 101 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
102 102 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
103 103 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
104 104 POSSIBILITY OF SUCH DAMAGE.
105 105 """
106 106
107 107 def credits():
108 108 print(_credits)
109 109 credits.__repr__ = lambda:_credits
110 110
111 111 def copyright():
112 112 print(_copyright)
113 113 copyright.__repr__ = lambda:_copyright
114 114
115 115 def license():
116 116 print(_license)
117 117 license.__repr__ = lambda:_license
118 118
119 119 def write(data):
120 120 doc['console'].value += str(data)
121 121
122 122
123 123 sys.stdout.write = sys.stderr.write = write
124 124 history = []
125 125 current = 0
126 126 _status = "main" # or "block" if typing inside a block
@@ -1,72 +1,72
1 1 require File.expand_path('../boot', __FILE__)
2 2
3 3 require 'rails/all'
4 4
5 5 if defined?(Bundler)
6 6 # If you precompile assets before deploying to production, use this line
7 7 Bundler.require(*Rails.groups(:assets => %w(development test)))
8 8 # If you want your assets lazily compiled in production, use this line
9 9 # Bundler.require(:default, :assets, Rails.env)
10 10 end
11 11
12 12 module CafeGrader
13 13 class Application < Rails::Application
14 14 # Settings in config/environments/* take precedence over those specified here.
15 15 # Application configuration should go into files in config/initializers
16 16 # -- all .rb files in that directory are automatically loaded.
17 17
18 18 # Custom directories with classes and modules you want to be autoloadable.
19 19 config.autoload_paths += %W(#{config.root}/lib)
20 20
21 21 # Only load the plugins named here, in the order given (default is alphabetical).
22 22 # :all can be used as a placeholder for all plugins not explicitly named.
23 23 # config.plugins = [ :exception_notification, :ssl_requirement, :all ]
24 24
25 25 # Activate observers that should always be running.
26 26 # config.active_record.observers = :cacher, :garbage_collector, :forum_observer
27 27
28 28 # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
29 29 # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
30 30 config.time_zone = 'UTC'
31 31
32 32 # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
33 33 # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
34 34 config.i18n.default_locale = :en
35 35
36 36 # Configure the default encoding used in templates for Ruby 1.9.
37 37 config.encoding = "utf-8"
38 38
39 39 # Configure sensitive parameters which will be filtered from the log file.
40 40 config.filter_parameters += [:password]
41 41
42 42 # Enable escaping HTML in JSON.
43 43 config.active_support.escape_html_entities_in_json = true
44 44
45 45 # Use SQL instead of Active Record's schema dumper when creating the database.
46 46 # This is necessary if your schema can't be completely dumped by the schema dumper,
47 47 # like if you have constraints or database-specific column types
48 48 # config.active_record.schema_format = :sql
49 49
50 50 # Enable the asset pipeline
51 51 config.assets.enabled = true
52 52
53 53 # Version of your assets, change this if you want to expire all your assets
54 54 config.assets.version = '1.0'
55 55
56 56 # ---------------- IMPORTANT ----------------------
57 57 # If we deploy the app into a subdir name "grader", be sure to do "rake assets:precompile RAILS_RELATIVE_URL_ROOT=/grader"
58 58 # moreover, using the following line instead also known to works
59 59 #config.action_controller.relative_url_root = '/grader'
60 60
61 61 #font path
62 62 config.assets.paths << "#{Rails}/vendor/assets/fonts"
63 63
64 64 config.assets.precompile += ['announcement_refresh.js','effects.js','site_update.js']
65 65 config.assets.precompile += ['local_jquery.js','tablesorter-theme.cafe.css']
66 66 %w( announcements submissions configurations contests contest_management graders heartbeat
67 67 login main messages problems report site sites sources tasks
68 - test user_admin users ).each do |controller|
68 + test user_admin users testcases).each do |controller|
69 69 config.assets.precompile += ["#{controller}.js", "#{controller}.css"]
70 70 end
71 71 end
72 72 end
@@ -1,23 +1,23
1 1 # Be sure to restart your server when you modify this file.
2 2
3 3 # Version of your assets, change this if you want to expire all your assets.
4 4 Rails.application.config.assets.version = '1.0'
5 5
6 6 # Add additional assets to the asset load path.
7 7 # Rails.application.config.assets.paths << Emoji.images_path
8 8 # Add Yarn node_modules folder to the asset load path.
9 9 Rails.application.config.assets.paths << Rails.root.join('node_modules')
10 10 Rails.application.config.assets.paths << Rails.root.join('vendor/assets/fonts')
11 11
12 12 # Precompile additional assets.
13 13 # application.js, application.css, and all non-JS/CSS in the app/assets
14 14 # folder are already added.
15 15 # Rails.application.config.assets.precompile += %w( admin.js admin.css )
16 16
17 17 Rails.application.config.assets.precompile += ['announcement_refresh.js','effects.js','site_update.js']
18 18 Rails.application.config.assets.precompile += ['local_jquery.js','tablesorter-theme.cafe.css']
19 19 %w( announcements submissions configurations contests contest_management graders heartbeat
20 20 login main messages problems report site sites sources tasks groups
21 - test user_admin users tags).each do |controller|
21 + test user_admin users tags testcases).each do |controller|
22 22 Rails.application.config.assets.precompile += ["#{controller}.js", "#{controller}.css"]
23 23 end
@@ -1,73 +1,74
1 1 module GraderScript
2 2
3 3 def self.grader_control_enabled?
4 4 if defined? GRADER_ROOT_DIR
5 5 GRADER_ROOT_DIR != ''
6 6 else
7 7 false
8 8 end
9 9 end
10 10
11 11 def self.raw_dir
12 12 File.join GRADER_ROOT_DIR, "raw"
13 13 end
14 14
15 15 def self.call_grader(params)
16 16 if GraderScript.grader_control_enabled?
17 17 cmd = File.join(GRADER_ROOT_DIR, "scripts/grader") + " " + params
18 18 system(cmd)
19 19 end
20 20 end
21 21
22 22 def self.stop_grader(pid)
23 23 GraderScript.call_grader "stop #{pid}"
24 24 end
25 25
26 26 def self.stop_graders(pids)
27 27 pid_str = (pids.map { |process| process.pid.to_s }).join ' '
28 28 GraderScript.call_grader "stop #{pid_str}"
29 29 end
30 30
31 31 def self.start_grader(env)
32 32 GraderScript.call_grader "#{env} queue --err-log &"
33 33 GraderScript.call_grader "#{env} test_request -err-log &"
34 34 end
35 35
36 + #call the import problem script
36 37 def self.call_import_problem(problem_name,
37 38 problem_dir,
38 39 time_limit=1,
39 40 memory_limit=32,
40 41 checker_name='text')
41 42 if GraderScript.grader_control_enabled?
42 43 cur_dir = `pwd`.chomp
43 44 Dir.chdir(GRADER_ROOT_DIR)
44 45
45 46 script_name = File.join(GRADER_ROOT_DIR, "scripts/import_problem")
46 47 cmd = "#{script_name} #{problem_name} #{problem_dir} #{checker_name}" +
47 48 " -t #{time_limit} -m #{memory_limit}"
48 49
49 50 output = `#{cmd}`
50 51
51 52 Dir.chdir(cur_dir)
52 53
53 54 return "import CMD: #{cmd}\n" + output
54 55 end
55 56 return ''
56 57 end
57 58
58 59 def self.call_import_testcase(problem_name)
59 60 if GraderScript.grader_control_enabled?
60 61 cur_dir = `pwd`.chomp
61 62 Dir.chdir(GRADER_ROOT_DIR)
62 63
63 64 script_name = File.join(GRADER_ROOT_DIR, "scripts/load_testcase")
64 65 cmd = "#{script_name} #{problem_name}"
65 66
66 67 output = `#{cmd}`
67 68
68 69 Dir.chdir(cur_dir)
69 70 return "Testcase import result:\n" + output
70 71 end
71 72 end
72 73
73 74 end
@@ -1,150 +1,152
1 1 require 'tmpdir'
2 2
3 3 class TestdataImporter
4 4
5 5 attr :log_msg
6 6
7 7 def initialize(problem)
8 8 @problem = problem
9 9 end
10 10
11 + #Create or update problem according to the parameter
11 12 def import_from_file(tempfile,
12 13 time_limit,
13 14 memory_limit,
14 15 checker_name='text',
15 16 import_to_db=false)
16 17
17 18 dirname = extract(tempfile)
18 19 return false if not dirname
19 20 if not import_to_db
20 21 @log_msg = GraderScript.call_import_problem(@problem.name,
21 22 dirname,
22 23 time_limit,
23 24 memory_limit,
24 25 checker_name)
25 26 else
26 27 # Import test data to test pairs.
27 28
28 29 @problem.test_pairs.clear
29 30 if import_test_pairs(dirname)
30 31 test_pair_count = TestPair.count :conditions => "problem_id = #{@problem.id}"
31 32 @log_msg = "Importing test pair successful. (#{test_pair_count} test pairs imported)"
32 33 else
33 34 @log_msg = "Importing test pair failed. (0 test pairs imported)"
34 35 end
35 36 end
36 37
37 38 @log_msg << import_problem_description(dirname)
38 39 @log_msg << import_problem_pdf(dirname)
39 40 @log_msg << import_full_score(dirname)
40 41
41 42 #import test data
42 43 @log_msg << GraderScript.call_import_testcase(@problem.name)
43 44
44 45 return true
45 46 end
46 47
47 48 protected
48 49
49 50 def self.long_ext(filename)
50 51 i = filename.index('.')
51 52 len = filename.length
52 53 return filename.slice(i..len)
53 54 end
54 55
56 + # extract an archive file located at +tempfile+ to the +raw_dir+
55 57 def extract(tempfile)
56 58 testdata_filename = save_testdata_file(tempfile)
57 59 ext = TestdataImporter.long_ext(tempfile.original_filename)
58 60
59 61 extract_dir = File.join(GraderScript.raw_dir, @problem.name)
60 62 if File.exists? extract_dir
61 63 backup_count = 0
62 64 begin
63 65 backup_count += 1
64 66 backup_dirname = "#{extract_dir}.backup.#{backup_count}"
65 67 end while File.exists? backup_dirname
66 68 File.rename(extract_dir, backup_dirname)
67 69 end
68 70 Dir.mkdir extract_dir
69 71
70 72 if ext=='.tar.gz' or ext=='.tgz'
71 73 cmd = "tar -zxvf #{testdata_filename} -C #{extract_dir}"
72 74 elsif ext=='.tar'
73 75 cmd = "tar -xvf #{testdata_filename} -C #{extract_dir}"
74 76 elsif ext=='.zip'
75 77 cmd = "unzip -o #{testdata_filename} -d #{extract_dir}"
76 78 else
77 79 return nil
78 80 end
79 81
80 82 system(cmd)
81 83
82 84 files = Dir["#{extract_dir}/**/*1*.in"]
83 85 return nil if files.length==0
84 86
85 87 File.delete(testdata_filename)
86 88
87 89 return File.dirname(files[0])
88 90 end
89 91
90 92 def save_testdata_file(tempfile)
91 93 ext = TestdataImporter.long_ext(tempfile.original_filename)
92 94 testdata_filename = File.join(Dir.tmpdir,"#{@problem.name}#{ext}")
93 95
94 96 return nil if tempfile==""
95 97
96 98 if tempfile.instance_of?(Tempfile)
97 99 tempfile.close
98 100 FileUtils.move(tempfile.path,testdata_filename)
99 101 else
100 102 File.open(testdata_filename, "wb") do |f|
101 103 f.write(tempfile.read)
102 104 end
103 105 end
104 106
105 107 return testdata_filename
106 108 end
107 109
108 110 def import_test_pairs(dirname)
109 111 test_num = 1
110 112 while FileTest.exists? "#{dirname}/#{test_num}.in"
111 113 in_filename = "#{dirname}/#{test_num}.in"
112 114 sol_filename = "#{dirname}/#{test_num}.sol"
113 115
114 116 break if not FileTest.exists? sol_filename
115 117
116 118 test_pair = TestPair.new(:input => open(in_filename).read,
117 119 :solution => open(sol_filename).read,
118 120 :problem => @problem)
119 121 break if not test_pair.save
120 122
121 123 test_num += 1
122 124 end
123 125 return test_num > 1
124 126 end
125 127
126 128 def import_problem_description(dirname)
127 129 html_files = Dir["#{dirname}/*.html"]
128 130 markdown_files = Dir["#{dirname}/*.md"] + Dir["#{dirname}/*.markdown"]
129 131 if (html_files.length != 0) or (markdown_files.length != 0)
130 132 description = @problem.description || Description.new
131 133
132 134 if html_files.length != 0
133 135 filename = html_files[0]
134 136 description.markdowned = false
135 137 else
136 138 filename = markdown_files[0]
137 139 description.markdowned = true
138 140 end
139 141
140 142 description.body = open(filename).read
141 143 description.save
142 144 @problem.description = description
143 145 @problem.save
144 146 return "\nProblem description imported from #{filename}."
145 147 else
146 148 return ''
147 149 end
148 150 end
149 151
150 152 def import_problem_pdf(dirname)
You need to be logged in to leave comments. Login now