Description:
merge
Commit status:
[Not Reviewed]
References:
Diff options:
Comments:
0 Commit comments
0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
r701:f26733606a76 - - 21 files changed: 132 inserted, 57 deleted
@@ -1,292 +1,292 | |||
|
1 | 1 | class MainController < ApplicationController |
|
2 | 2 | |
|
3 | 3 | before_filter :authenticate, :except => [:index, :login] |
|
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 |
|
197 | 197 | end |
|
198 | 198 | |
|
199 | 199 | protected |
|
200 | 200 | |
|
201 | 201 | def prepare_announcements(recent=nil) |
|
202 | 202 | if GraderConfiguration.show_tasks_to?(@user) |
|
203 | 203 | @announcements = Announcement.published(true) |
|
204 | 204 | else |
|
205 | 205 | @announcements = Announcement.published |
|
206 | 206 | end |
|
207 | 207 | if recent!=nil |
|
208 | 208 | recent_id = recent.to_i |
|
209 | 209 | @announcements = @announcements.find_all { |a| a.id > recent_id } |
|
210 | 210 | end |
|
211 | 211 | end |
|
212 | 212 | |
|
213 | 213 | def prepare_list_information |
|
214 | 214 | @user = User.find(session[:user_id]) |
|
215 | 215 | if not GraderConfiguration.multicontests? |
|
216 | 216 | @problems = @user.available_problems |
|
217 | 217 | else |
|
218 | 218 | @contest_problems = @user.available_problems_group_by_contests |
|
219 | 219 | @problems = @user.available_problems |
|
220 | 220 | end |
|
221 | 221 | @prob_submissions = {} |
|
222 | 222 | @problems.each do |p| |
|
223 | 223 | sub = Submission.find_last_by_user_and_problem(@user.id,p.id) |
|
224 | 224 | if sub!=nil |
|
225 | 225 | @prob_submissions[p.id] = { :count => sub.number, :submission => sub } |
|
226 | 226 | else |
|
227 | 227 | @prob_submissions[p.id] = { :count => 0, :submission => nil } |
|
228 | 228 | end |
|
229 | 229 | end |
|
230 | 230 | prepare_announcements |
|
231 | 231 | end |
|
232 | 232 | |
|
233 | 233 | def check_viewability |
|
234 | 234 | @user = User.find(session[:user_id]) |
|
235 | 235 | if (!GraderConfiguration.show_tasks_to?(@user)) and |
|
236 | 236 | ((action_name=='submission') or (action_name=='submit')) |
|
237 | 237 | redirect_to :action => 'list' and return |
|
238 | 238 | end |
|
239 | 239 | end |
|
240 | 240 | |
|
241 | 241 | def prepare_grading_result(submission) |
|
242 | 242 | if GraderConfiguration.task_grading_info.has_key? submission.problem.name |
|
243 | 243 | grading_info = GraderConfiguration.task_grading_info[submission.problem.name] |
|
244 | 244 | else |
|
245 | 245 | # guess task info from problem.full_score |
|
246 | 246 | cases = submission.problem.full_score / 10 |
|
247 | 247 | grading_info = { |
|
248 | 248 | 'testruns' => cases, |
|
249 | 249 | 'testcases' => cases |
|
250 | 250 | } |
|
251 | 251 | end |
|
252 | 252 | @test_runs = [] |
|
253 | 253 | if grading_info['testruns'].is_a? Integer |
|
254 | 254 | trun_count = grading_info['testruns'] |
|
255 | 255 | trun_count.times do |i| |
|
256 | 256 | @test_runs << [ read_grading_result(@user.login, |
|
257 | 257 | submission.problem.name, |
|
258 | 258 | submission.id, |
|
259 | 259 | i+1) ] |
|
260 | 260 | end |
|
261 | 261 | else |
|
262 | 262 | grading_info['testruns'].keys.sort.each do |num| |
|
263 | 263 | run = [] |
|
264 | 264 | testrun = grading_info['testruns'][num] |
|
265 | 265 | testrun.each do |c| |
|
266 | 266 | run << read_grading_result(@user.login, |
|
267 | 267 | submission.problem.name, |
|
268 | 268 | submission.id, |
|
269 | 269 | c) |
|
270 | 270 | end |
|
271 | 271 | @test_runs << run |
|
272 | 272 | end |
|
273 | 273 | end |
|
274 | 274 | end |
|
275 | 275 | |
|
276 | 276 | def grading_result_dir(user_name, problem_name, submission_id, case_num) |
|
277 | 277 | return "#{GRADING_RESULT_DIR}/#{user_name}/#{problem_name}/#{submission_id}/test-result/#{case_num}" |
|
278 | 278 | end |
|
279 | 279 | |
|
280 | 280 | def output_filename(user_name, problem_name, submission_id, case_num) |
|
281 | 281 | dir = grading_result_dir(user_name,problem_name, submission_id, case_num) |
|
282 | 282 | return "#{dir}/output.txt" |
|
283 | 283 | end |
|
284 | 284 | |
|
285 | 285 | def read_grading_result(user_name, problem_name, submission_id, case_num) |
|
286 | 286 | dir = grading_result_dir(user_name,problem_name, submission_id, case_num) |
|
287 | 287 | result_file_name = "#{dir}/result" |
|
288 | 288 | if !FileTest.exists?(result_file_name) |
|
289 | 289 | return {:num => case_num, :msg => 'program did not run'} |
|
290 | 290 | else |
|
291 | 291 | results = File.open(result_file_name).readlines |
|
292 | 292 | run_stat = extract_running_stat(results) |
@@ -1,306 +1,310 | |||
|
1 | 1 | class ProblemsController < ApplicationController |
|
2 | 2 | |
|
3 | 3 | before_action :authenticate, :authorization |
|
4 | 4 | before_action :testcase_authorization, only: [:show_testcase] |
|
5 | 5 | |
|
6 | 6 | in_place_edit_for :problem, :name |
|
7 | 7 | in_place_edit_for :problem, :full_name |
|
8 | 8 | in_place_edit_for :problem, :full_score |
|
9 | 9 | |
|
10 | 10 | def index |
|
11 | 11 | @problems = Problem.order(date_added: :desc) |
|
12 | 12 | end |
|
13 | 13 | |
|
14 | 14 | # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html) |
|
15 | 15 | verify :method => :post, :only => [ :create, :quick_create, |
|
16 | 16 | :do_manage, |
|
17 | 17 | :do_import, |
|
18 | 18 | ], |
|
19 | 19 | :redirect_to => { :action => :index } |
|
20 | 20 | |
|
21 | 21 | def show |
|
22 | 22 | @problem = Problem.find(params[:id]) |
|
23 | 23 | end |
|
24 | 24 | |
|
25 | 25 | def new |
|
26 | 26 | @problem = Problem.new |
|
27 | 27 | @description = nil |
|
28 | 28 | end |
|
29 | 29 | |
|
30 | 30 | def create |
|
31 | 31 | @problem = Problem.new(problem_params) |
|
32 | 32 | @description = Description.new(params[:description]) |
|
33 | 33 | if @description.body!='' |
|
34 | 34 | if !@description.save |
|
35 | 35 | render :action => new and return |
|
36 | 36 | end |
|
37 | 37 | else |
|
38 | 38 | @description = nil |
|
39 | 39 | end |
|
40 | 40 | @problem.description = @description |
|
41 | 41 | if @problem.save |
|
42 | 42 | flash[:notice] = 'Problem was successfully created.' |
|
43 | 43 | redirect_to action: :index |
|
44 | 44 | else |
|
45 | 45 | render :action => 'new' |
|
46 | 46 | end |
|
47 | 47 | end |
|
48 | 48 | |
|
49 | 49 | def quick_create |
|
50 | 50 | @problem = Problem.new(problem_params) |
|
51 | 51 | @problem.full_name = @problem.name if @problem.full_name == '' |
|
52 | 52 | @problem.full_score = 100 |
|
53 | 53 | @problem.available = false |
|
54 | 54 | @problem.test_allowed = true |
|
55 | 55 | @problem.output_only = false |
|
56 | 56 | @problem.date_added = Time.new |
|
57 | 57 | if @problem.save |
|
58 | 58 | flash[:notice] = 'Problem was successfully created.' |
|
59 | 59 | redirect_to action: :index |
|
60 | 60 | else |
|
61 | 61 | flash[:notice] = 'Error saving problem' |
|
62 | 62 | redirect_to action: :index |
|
63 | 63 | end |
|
64 | 64 | end |
|
65 | 65 | |
|
66 | 66 | def edit |
|
67 | 67 | @problem = Problem.find(params[:id]) |
|
68 | 68 | @description = @problem.description |
|
69 | 69 | end |
|
70 | 70 | |
|
71 | 71 | def update |
|
72 | 72 | @problem = Problem.find(params[:id]) |
|
73 | 73 | @description = @problem.description |
|
74 | 74 | if @description.nil? and params[:description][:body]!='' |
|
75 | 75 | @description = Description.new(params[:description]) |
|
76 | 76 | if !@description.save |
|
77 | 77 | flash[:notice] = 'Error saving description' |
|
78 | 78 | render :action => 'edit' and return |
|
79 | 79 | end |
|
80 | 80 | @problem.description = @description |
|
81 | 81 | elsif @description |
|
82 | 82 | if !@description.update_attributes(params[:description]) |
|
83 | 83 | flash[:notice] = 'Error saving description' |
|
84 | 84 | render :action => 'edit' and return |
|
85 | 85 | end |
|
86 | 86 | end |
|
87 | 87 | if params[:file] and params[:file].content_type != 'application/pdf' |
|
88 | 88 | flash[:notice] = 'Error: Uploaded file is not PDF' |
|
89 | 89 | render :action => 'edit' and return |
|
90 | 90 | end |
|
91 | 91 | if @problem.update_attributes(problem_params) |
|
92 | 92 | flash[:notice] = 'Problem was successfully updated.' |
|
93 | 93 | unless params[:file] == nil or params[:file] == '' |
|
94 | 94 | flash[:notice] = 'Problem was successfully updated and a new PDF file is uploaded.' |
|
95 | 95 | out_dirname = "#{Problem.download_file_basedir}/#{@problem.id}" |
|
96 | 96 | if not FileTest.exists? out_dirname |
|
97 | 97 | Dir.mkdir out_dirname |
|
98 | 98 | end |
|
99 | 99 | |
|
100 | 100 | out_filename = "#{out_dirname}/#{@problem.name}.pdf" |
|
101 | 101 | if FileTest.exists? out_filename |
|
102 | 102 | File.delete out_filename |
|
103 | 103 | end |
|
104 | 104 | |
|
105 | 105 | File.open(out_filename,"wb") do |file| |
|
106 | 106 | file.write(params[:file].read) |
|
107 | 107 | end |
|
108 | 108 | @problem.description_filename = "#{@problem.name}.pdf" |
|
109 | 109 | @problem.save |
|
110 | 110 | end |
|
111 | 111 | redirect_to :action => 'show', :id => @problem |
|
112 | 112 | else |
|
113 | 113 | render :action => 'edit' |
|
114 | 114 | end |
|
115 | 115 | end |
|
116 | 116 | |
|
117 | 117 | def destroy |
|
118 | 118 | p = Problem.find(params[:id]).destroy |
|
119 | 119 | redirect_to action: :index |
|
120 | 120 | end |
|
121 | 121 | |
|
122 | 122 | def toggle |
|
123 | 123 | @problem = Problem.find(params[:id]) |
|
124 | 124 | @problem.update_attributes(available: !(@problem.available) ) |
|
125 | 125 | respond_to do |format| |
|
126 | 126 | format.js { } |
|
127 | 127 | end |
|
128 | 128 | end |
|
129 | 129 | |
|
130 | 130 | def toggle_test |
|
131 | 131 | @problem = Problem.find(params[:id]) |
|
132 | 132 | @problem.update_attributes(test_allowed: !(@problem.test_allowed?) ) |
|
133 | 133 | respond_to do |format| |
|
134 | 134 | format.js { } |
|
135 | 135 | end |
|
136 | 136 | end |
|
137 | 137 | |
|
138 | 138 | def toggle_view_testcase |
|
139 | 139 | @problem = Problem.find(params[:id]) |
|
140 | 140 | @problem.update_attributes(view_testcase: !(@problem.view_testcase?) ) |
|
141 | 141 | respond_to do |format| |
|
142 | 142 | format.js { } |
|
143 | 143 | end |
|
144 | 144 | end |
|
145 | 145 | |
|
146 | 146 | def turn_all_off |
|
147 | 147 | Problem.available.all.each do |problem| |
|
148 | 148 | problem.available = false |
|
149 | 149 | problem.save |
|
150 | 150 | end |
|
151 | 151 | redirect_to action: :index |
|
152 | 152 | end |
|
153 | 153 | |
|
154 | 154 | def turn_all_on |
|
155 | 155 | Problem.where.not(available: true).each do |problem| |
|
156 | 156 | problem.available = true |
|
157 | 157 | problem.save |
|
158 | 158 | end |
|
159 | 159 | redirect_to action: :index |
|
160 | 160 | end |
|
161 | 161 | |
|
162 | 162 | def stat |
|
163 | 163 | @problem = Problem.find(params[:id]) |
|
164 | 164 | unless @problem.available or session[:admin] |
|
165 | 165 | redirect_to :controller => 'main', :action => 'list' |
|
166 | 166 | return |
|
167 | 167 | end |
|
168 | - @submissions = Submission.includes(:user).where(problem_id: params[:id]).order(:user_id,:id) | |
|
168 | + @submissions = Submission.includes(:user).includes(:language).where(problem_id: params[:id]).order(:user_id,:id) | |
|
169 | 169 | |
|
170 | 170 | #stat summary |
|
171 | 171 | range =65 |
|
172 | 172 | @histogram = { data: Array.new(range,0), summary: {} } |
|
173 | 173 | user = Hash.new(0) |
|
174 | 174 | @submissions.find_each do |sub| |
|
175 | 175 | d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60 |
|
176 | 176 | @histogram[:data][d.to_i] += 1 if d < range |
|
177 | 177 | user[sub.user_id] = [user[sub.user_id], ((sub.try(:points) || 0) >= @problem.full_score) ? 1 : 0].max |
|
178 | 178 | end |
|
179 | 179 | @histogram[:summary][:max] = [@histogram[:data].max,1].max |
|
180 | 180 | |
|
181 | 181 | @summary = { attempt: user.count, solve: 0 } |
|
182 | 182 | user.each_value { |v| @summary[:solve] += 1 if v == 1 } |
|
183 | 183 | end |
|
184 | 184 | |
|
185 | 185 | def manage |
|
186 | 186 | @problems = Problem.order(date_added: :desc) |
|
187 | 187 | end |
|
188 | 188 | |
|
189 | 189 | def do_manage |
|
190 | 190 | if params.has_key? 'change_date_added' |
|
191 | 191 | change_date_added |
|
192 | 192 | elsif params.has_key? 'add_to_contest' |
|
193 | 193 | add_to_contest |
|
194 | 194 | elsif params.has_key? 'enable_problem' |
|
195 | 195 | set_available(true) |
|
196 | 196 | elsif params.has_key? 'disable_problem' |
|
197 | 197 | set_available(false) |
|
198 | 198 | elsif params.has_key? 'add_group' |
|
199 | 199 | group = Group.find(params[:group_id]) |
|
200 | 200 | ok = [] |
|
201 | 201 | failed = [] |
|
202 | 202 | get_problems_from_params.each do |p| |
|
203 | 203 | begin |
|
204 | 204 | group.problems << p |
|
205 | 205 | ok << p.full_name |
|
206 | 206 | rescue => e |
|
207 | 207 | failed << p.full_name |
|
208 | 208 | end |
|
209 | 209 | end |
|
210 | 210 | flash[:success] = "The following problems are added to the group #{group.name}: " + ok.join(', ') if ok.count > 0 |
|
211 | 211 | flash[:alert] = "The following problems are already in the group #{group.name}: " + failed.join(', ') if failed.count > 0 |
|
212 | + elsif params.has_key? 'add_tags' | |
|
213 | + get_problems_from_params.each do |p| | |
|
214 | + p.tag_ids += params[:tag_ids] | |
|
215 | + end | |
|
212 | 216 | end |
|
213 | 217 | |
|
214 | 218 | redirect_to :action => 'manage' |
|
215 | 219 | end |
|
216 | 220 | |
|
217 | 221 | def import |
|
218 | 222 | @allow_test_pair_import = allow_test_pair_import? |
|
219 | 223 | end |
|
220 | 224 | |
|
221 | 225 | def do_import |
|
222 | 226 | old_problem = Problem.find_by_name(params[:name]) |
|
223 | 227 | if !allow_test_pair_import? and params.has_key? :import_to_db |
|
224 | 228 | params.delete :import_to_db |
|
225 | 229 | end |
|
226 | 230 | @problem, import_log = Problem.create_from_import_form_params(params, |
|
227 | 231 | old_problem) |
|
228 | 232 | |
|
229 | 233 | if !@problem.errors.empty? |
|
230 | 234 | render :action => 'import' and return |
|
231 | 235 | end |
|
232 | 236 | |
|
233 | 237 | if old_problem!=nil |
|
234 | 238 | flash[:notice] = "The test data has been replaced for problem #{@problem.name}" |
|
235 | 239 | end |
|
236 | 240 | @log = import_log |
|
237 | 241 | end |
|
238 | 242 | |
|
239 | 243 | def remove_contest |
|
240 | 244 | problem = Problem.find(params[:id]) |
|
241 | 245 | contest = Contest.find(params[:contest_id]) |
|
242 | 246 | if problem!=nil and contest!=nil |
|
243 | 247 | problem.contests.delete(contest) |
|
244 | 248 | end |
|
245 | 249 | redirect_to :action => 'manage' |
|
246 | 250 | end |
|
247 | 251 | |
|
248 | 252 | ################################## |
|
249 | 253 | protected |
|
250 | 254 | |
|
251 | 255 | def allow_test_pair_import? |
|
252 | 256 | if defined? ALLOW_TEST_PAIR_IMPORT |
|
253 | 257 | return ALLOW_TEST_PAIR_IMPORT |
|
254 | 258 | else |
|
255 | 259 | return false |
|
256 | 260 | end |
|
257 | 261 | end |
|
258 | 262 | |
|
259 | 263 | def change_date_added |
|
260 | 264 | problems = get_problems_from_params |
|
261 | 265 | date = Date.parse(params[:date_added]) |
|
262 | 266 | problems.each do |p| |
|
263 | 267 | p.date_added = date |
|
264 | 268 | p.save |
|
265 | 269 | end |
|
266 | 270 | end |
|
267 | 271 | |
|
268 | 272 | def add_to_contest |
|
269 | 273 | problems = get_problems_from_params |
|
270 | 274 | contest = Contest.find(params[:contest][:id]) |
|
271 | 275 | if contest!=nil and contest.enabled |
|
272 | 276 | problems.each do |p| |
|
273 | 277 | p.contests << contest |
|
274 | 278 | end |
|
275 | 279 | end |
|
276 | 280 | end |
|
277 | 281 | |
|
278 | 282 | def set_available(avail) |
|
279 | 283 | problems = get_problems_from_params |
|
280 | 284 | problems.each do |p| |
|
281 | 285 | p.available = avail |
|
282 | 286 | p.save |
|
283 | 287 | end |
|
284 | 288 | end |
|
285 | 289 | |
|
286 | 290 | def get_problems_from_params |
|
287 | 291 | problems = [] |
|
288 | 292 | params.keys.each do |k| |
|
289 | 293 | if k.index('prob-')==0 |
|
290 | 294 | name, id, order = k.split('-') |
|
291 | 295 | problems << Problem.find(id) |
|
292 | 296 | end |
|
293 | 297 | end |
|
294 | 298 | problems |
|
295 | 299 | end |
|
296 | 300 | |
|
297 | 301 | def get_problems_stat |
|
298 | 302 | end |
|
299 | 303 | |
|
300 | 304 | private |
|
301 | 305 | |
|
302 | 306 | def problem_params |
|
303 | - params.require(:problem).permit(:name, :full_name, :full_score, :date_added, :available, :test_allowed,:output_only, :url, :description) | |
|
307 | + params.require(:problem).permit(:name, :full_name, :full_score, :date_added, :available, :test_allowed,:output_only, :url, :description, tag_ids:[]) | |
|
304 | 308 | end |
|
305 | 309 | |
|
306 | 310 | end |
@@ -1,246 +1,248 | |||
|
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 |
|
151 | 153 | |
|
152 | 154 | @submissions = {} |
|
153 | 155 | |
|
154 | 156 | User.find_each do |user| |
|
155 | 157 | @submissions[user.id] = { login: user.login, full_name: user.full_name, count: 0, sub: { } } |
|
156 | 158 | end |
|
157 | 159 | |
|
158 | 160 | Submission.where("submitted_at >= ? AND submitted_at <= ?",@since_time,@until_time).find_each do |s| |
|
159 | 161 | if @submissions[s.user_id] |
|
160 | 162 | if not @submissions[s.user_id][:sub].has_key?(s.problem_id) |
|
161 | 163 | a = Problem.find_by_id(s.problem_id) |
|
162 | 164 | @submissions[s.user_id][:sub][s.problem_id] = |
|
163 | 165 | { prob_name: (a ? a.full_name : '(NULL)'), |
|
164 | 166 | sub_ids: [s.id] } |
|
165 | 167 | else |
|
166 | 168 | @submissions[s.user_id][:sub][s.problem_id][:sub_ids] << s.id |
|
167 | 169 | end |
|
168 | 170 | @submissions[s.user_id][:count] += 1 |
|
169 | 171 | end |
|
170 | 172 | end |
|
171 | 173 | end |
|
172 | 174 | |
|
173 | 175 | def problem_hof |
|
174 | 176 | # gen problem list |
|
175 | 177 | @user = User.find(session[:user_id]) |
|
176 | 178 | @problems = @user.available_problems |
|
177 | 179 | |
|
178 | 180 | # get selected problems or the default |
|
179 | 181 | if params[:id] |
|
180 | 182 | begin |
|
181 | 183 | @problem = Problem.available.find(params[:id]) |
|
182 | 184 | rescue |
|
183 | 185 | redirect_to action: :problem_hof |
|
184 | 186 | flash[:notice] = 'Error: submissions for that problem are not viewable.' |
|
185 | 187 | return |
|
186 | 188 | end |
|
187 | 189 | end |
|
188 | 190 | |
|
189 | 191 | return unless @problem |
|
190 | 192 | |
|
191 | 193 | @by_lang = {} #aggregrate by language |
|
192 | 194 | |
|
193 | 195 | range =65 |
|
194 | 196 | @histogram = { data: Array.new(range,0), summary: {} } |
|
195 | 197 | @summary = {count: 0, solve: 0, attempt: 0} |
|
196 | 198 | user = Hash.new(0) |
|
197 | 199 | Submission.where(problem_id: @problem.id).find_each do |sub| |
|
198 | 200 | #histogram |
|
199 | 201 | d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60 |
|
200 | 202 | @histogram[:data][d.to_i] += 1 if d < range |
|
201 | 203 | |
|
202 | 204 | next unless sub.points |
|
203 | 205 | @summary[:count] += 1 |
|
204 | 206 | user[sub.user_id] = [user[sub.user_id], (sub.points >= @problem.full_score) ? 1 : 0].max |
|
205 | 207 | |
|
206 | 208 | lang = Language.find_by_id(sub.language_id) |
|
207 | 209 | next unless lang |
|
208 | 210 | next unless sub.points >= @problem.full_score |
|
209 | 211 | |
|
210 | 212 | #initialize |
|
211 | 213 | unless @by_lang.has_key?(lang.pretty_name) |
|
212 | 214 | @by_lang[lang.pretty_name] = { |
|
213 | 215 | runtime: { avail: false, value: 2**30-1 }, |
|
214 | 216 | memory: { avail: false, value: 2**30-1 }, |
|
215 | 217 | length: { avail: false, value: 2**30-1 }, |
|
216 | 218 | first: { avail: false, value: DateTime.new(3000,1,1) } |
|
217 | 219 | } |
|
218 | 220 | end |
|
219 | 221 | |
|
220 | 222 | if sub.max_runtime and sub.max_runtime < @by_lang[lang.pretty_name][:runtime][:value] |
|
221 | 223 | @by_lang[lang.pretty_name][:runtime] = { avail: true, user_id: sub.user_id, value: sub.max_runtime, sub_id: sub.id } |
|
222 | 224 | end |
|
223 | 225 | |
|
224 | 226 | if sub.peak_memory and sub.peak_memory < @by_lang[lang.pretty_name][:memory][:value] |
|
225 | 227 | @by_lang[lang.pretty_name][:memory] = { avail: true, user_id: sub.user_id, value: sub.peak_memory, sub_id: sub.id } |
|
226 | 228 | end |
|
227 | 229 | |
|
228 | 230 | if sub.submitted_at and sub.submitted_at < @by_lang[lang.pretty_name][:first][:value] and sub.user and |
|
229 | 231 | !sub.user.admin? |
|
230 | 232 | @by_lang[lang.pretty_name][:first] = { avail: true, user_id: sub.user_id, value: sub.submitted_at, sub_id: sub.id } |
|
231 | 233 | end |
|
232 | 234 | |
|
233 | 235 | if @by_lang[lang.pretty_name][:length][:value] > sub.effective_code_length |
|
234 | 236 | @by_lang[lang.pretty_name][:length] = { avail: true, user_id: sub.user_id, value: sub.effective_code_length, sub_id: sub.id } |
|
235 | 237 | end |
|
236 | 238 | end |
|
237 | 239 | |
|
238 | 240 | #process user_id |
|
239 | 241 | @by_lang.each do |lang,prop| |
|
240 | 242 | prop.each do |k,v| |
|
241 | 243 | v[:user] = User.exists?(v[:user_id]) ? User.find(v[:user_id]).full_name : "(NULL)" |
|
242 | 244 | end |
|
243 | 245 | end |
|
244 | 246 | |
|
245 | 247 | #sum into best |
|
246 | 248 | if @by_lang and @by_lang.first |
@@ -1,58 +1,60 | |||
|
1 | 1 | class TagsController < ApplicationController |
|
2 | 2 | before_action :set_tag, only: [:show, :edit, :update, :destroy] |
|
3 | 3 | |
|
4 | 4 | # GET /tags |
|
5 | 5 | def index |
|
6 | 6 | @tags = Tag.all |
|
7 | 7 | end |
|
8 | 8 | |
|
9 | 9 | # GET /tags/1 |
|
10 | 10 | def show |
|
11 | 11 | end |
|
12 | 12 | |
|
13 | 13 | # GET /tags/new |
|
14 | 14 | def new |
|
15 | 15 | @tag = Tag.new |
|
16 | 16 | end |
|
17 | 17 | |
|
18 | 18 | # GET /tags/1/edit |
|
19 | 19 | def edit |
|
20 | 20 | end |
|
21 | 21 | |
|
22 | 22 | # POST /tags |
|
23 | 23 | def create |
|
24 | 24 | @tag = Tag.new(tag_params) |
|
25 | 25 | |
|
26 | 26 | if @tag.save |
|
27 | 27 | redirect_to @tag, notice: 'Tag was successfully created.' |
|
28 | 28 | else |
|
29 | 29 | render :new |
|
30 | 30 | end |
|
31 | 31 | end |
|
32 | 32 | |
|
33 | 33 | # PATCH/PUT /tags/1 |
|
34 | 34 | def update |
|
35 | 35 | if @tag.update(tag_params) |
|
36 | 36 | redirect_to @tag, notice: 'Tag was successfully updated.' |
|
37 | 37 | else |
|
38 | 38 | render :edit |
|
39 | 39 | end |
|
40 | 40 | end |
|
41 | 41 | |
|
42 | 42 | # DELETE /tags/1 |
|
43 | 43 | def destroy |
|
44 | + #remove any association | |
|
45 | + ProblemTag.where(tag_id: @tag.id).destroy_all | |
|
44 | 46 | @tag.destroy |
|
45 | 47 | redirect_to tags_url, notice: 'Tag was successfully destroyed.' |
|
46 | 48 | end |
|
47 | 49 | |
|
48 | 50 | private |
|
49 | 51 | # Use callbacks to share common setup or constraints between actions. |
|
50 | 52 | def set_tag |
|
51 | 53 | @tag = Tag.find(params[:id]) |
|
52 | 54 | end |
|
53 | 55 | |
|
54 | 56 | # Only allow a trusted parameter "white list" through. |
|
55 | 57 | def tag_params |
|
56 | 58 | params.require(:tag).permit(:name, :description, :public) |
|
57 | 59 | end |
|
58 | 60 | end |
@@ -1,221 +1,224 | |||
|
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 |
|
|
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 = " " + (t 'title_bar.contest_not_started') |
|
155 | 158 | else |
|
156 | 159 | time_left = " " + (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 | |
|
199 | 202 | |
|
200 | 203 | BOOTSTRAP_FLASH_MSG = { |
|
201 | 204 | success: 'alert-success', |
|
202 | 205 | error: 'alert-danger', |
|
203 | 206 | alert: 'alert-danger', |
|
204 | 207 | notice: 'alert-info' |
|
205 | 208 | } |
|
206 | 209 | |
|
207 | 210 | def bootstrap_class_for(flash_type) |
|
208 | 211 | BOOTSTRAP_FLASH_MSG.fetch(flash_type.to_sym, flash_type.to_s) |
|
209 | 212 | end |
|
210 | 213 | |
|
211 | 214 | def flash_messages |
|
212 | 215 | flash.each do |msg_type, message| |
|
213 | 216 | concat(content_tag(:div, message, class: "alert #{bootstrap_class_for(msg_type)} fade in") do |
|
214 | 217 | concat content_tag(:button, 'x', class: "close", data: { dismiss: 'alert' }) |
|
215 | 218 | concat message |
|
216 | 219 | end) |
|
217 | 220 | end |
|
218 | 221 | nil |
|
219 | 222 | end |
|
220 | 223 | |
|
221 | 224 | end |
@@ -1,166 +1,166 | |||
|
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 |
|
135 | 135 | def must_specify_language |
|
136 | 136 | return if self.source==nil |
|
137 | 137 | |
|
138 | 138 | # for output_only tasks |
|
139 | 139 | return if self.problem!=nil and self.problem.output_only |
|
140 | 140 | |
|
141 | 141 | if self.language==nil |
|
142 | 142 | errors.add('source',"Cannot detect language. Did you submit a correct source file?") unless self.language!=nil |
|
143 | 143 | end |
|
144 | 144 | end |
|
145 | 145 | |
|
146 | 146 | def must_have_valid_problem |
|
147 | 147 | return if self.source==nil |
|
148 | 148 | if self.problem==nil |
|
149 | 149 | errors.add('problem',"must be specified.") |
|
150 | 150 | else |
|
151 | 151 | #admin always have right |
|
152 | 152 | return if self.user.admin? |
|
153 | 153 | |
|
154 | 154 | #check if user has the right to submit the problem |
|
155 | 155 | errors.add('problem',"must be valid.") if (!self.user.available_problems.include?(self.problem)) and (self.new_record?) |
|
156 | 156 | end |
|
157 | 157 | end |
|
158 | 158 | |
|
159 | 159 | # callbacks |
|
160 | 160 | def assign_latest_number_if_new_recond |
|
161 | 161 | return if !self.new_record? |
|
162 | 162 | latest = Submission.find_last_by_user_and_problem(self.user_id, self.problem_id) |
|
163 | 163 | self.number = (latest==nil) ? 1 : latest.number + 1; |
|
164 | 164 | end |
|
165 | 165 | |
|
166 | 166 | end |
@@ -1,26 +1,28 | |||
|
1 | - | |
|
2 | 1 |
|
|
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_ |
|
|
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 |
- = |
|
|
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 |
- |
|
|
21 |
- |
|
|
22 |
- |
|
|
23 |
- |
|
|
22 | + %strong View: | |
|
23 | + - if GraderConfiguration.show_grading_result | |
|
24 | + = link_to '[detailed result]', :action => 'result', :id => submission.id | |
|
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,94 +1,95 | |||
|
1 | 1 | %header.navbar.navbar-default.navbar-fixed-top |
|
2 | 2 | %nav |
|
3 | 3 | .container-fluid |
|
4 | 4 | .navbar-header |
|
5 | 5 | %button.navbar-toggle.collapsed{ data: {toggle: 'collapse', target: '#navbar-collapse'} } |
|
6 | 6 | %span.sr-only Togggle Navigation |
|
7 | 7 | %span.icon-bar |
|
8 | 8 | %span.icon-bar |
|
9 | 9 | %span.icon-bar |
|
10 | 10 | %a.navbar-brand{href: main_list_path} |
|
11 | 11 | %span.glyphicon.glyphicon-home |
|
12 | 12 | MAIN |
|
13 | 13 | .collapse.navbar-collapse#navbar-collapse |
|
14 | 14 | %ul.nav.navbar-nav |
|
15 | 15 | / submission |
|
16 | 16 | - if (@current_user!=nil) and (GraderConfiguration.show_tasks_to?(@current_user)) |
|
17 | 17 | %li.dropdown |
|
18 | 18 | %a.dropdown-toggle{href: '#', data: {toggle:'dropdown'}, aria: {haspopup:"true", expanded:"false"}, role: "button"} |
|
19 | 19 | = "#{I18n.t 'menu.submissions'}" |
|
20 | 20 | %span.caret |
|
21 | 21 | %ul.dropdown-menu |
|
22 | 22 | = add_menu("View", 'submissions', 'index') |
|
23 | 23 | = add_menu("Self Test", 'test', 'index') |
|
24 | 24 | / hall of fame |
|
25 | 25 | - if GraderConfiguration['right.user_hall_of_fame'] |
|
26 | 26 | = add_menu("#{I18n.t 'menu.hall_of_fame'}", 'report', 'problem_hof') |
|
27 | 27 | / display MODE button (with countdown in contest mode) |
|
28 | 28 | - if GraderConfiguration.analysis_mode? |
|
29 | 29 | %div.navbar-btn.btn.btn-success#countdown= "ANALYSIS MODE" |
|
30 | 30 | - elsif GraderConfiguration.time_limit_mode? |
|
31 | 31 | - if @current_user.contest_finished? |
|
32 | 32 | %div.navbar-btn.btn.btn-danger#countdown= "Contest is over" |
|
33 | 33 | - elsif !@current_user.contest_started? |
|
34 | 34 | %div.navbar-btn.btn.btn-primary#countdown= (t 'title_bar.contest_not_started') |
|
35 | 35 | - else |
|
36 | 36 | %div.navbar-btn.btn.btn-primary#countdown asdf |
|
37 | 37 | :javascript |
|
38 | 38 | $("#countdown").countdown({until: "+#{@current_user.contest_time_left.to_i}s", layout: 'Time left: {hnn}:{mnn}:{snn}'}); |
|
39 | 39 | / admin section |
|
40 | 40 | - if (@current_user!=nil) and (session[:admin]) |
|
41 | 41 | / management |
|
42 | 42 | %li.dropdown |
|
43 | 43 | %a.dropdown-toggle{href: '#', data: {toggle:'dropdown'}, aria: {haspopup:"true", expanded:"false"}, role: "button"} |
|
44 | 44 | Manage |
|
45 | 45 | %span.caret |
|
46 | 46 | %ul.dropdown-menu |
|
47 | 47 | = add_menu( 'Announcements', 'announcements', 'index') |
|
48 | 48 | = add_menu( 'Problems', 'problems', 'index') |
|
49 | + = add_menu( 'Tags', 'tags', 'index') | |
|
49 | 50 | = add_menu( 'Users', 'user_admin', 'index') |
|
50 | 51 | = add_menu( 'User Groups', 'groups', 'index') |
|
51 | 52 | = add_menu( 'Graders', 'graders', 'list') |
|
52 | 53 | = add_menu( 'Message ', 'messages', 'console') |
|
53 | 54 | %li.divider{role: 'separator'} |
|
54 | 55 | = add_menu( 'System config', 'configurations', 'index') |
|
55 | 56 | %li.divider{role: 'separator'} |
|
56 | 57 | = add_menu( 'Sites', 'sites', 'index') |
|
57 | 58 | = add_menu( 'Contests', 'contest_management', 'index') |
|
58 | 59 | / report |
|
59 | 60 | %li.dropdown |
|
60 | 61 | %a.dropdown-toggle{href: '#', data: {toggle:'dropdown'}, aria: {haspopup:"true", expanded:"false"}, role: "button"} |
|
61 | 62 | Report |
|
62 | 63 | %span.caret |
|
63 | 64 | %ul.dropdown-menu |
|
64 | 65 | = add_menu( 'Current Score', 'report', 'current_score') |
|
65 | 66 | = add_menu( 'Score Report', 'report', 'max_score') |
|
66 | 67 | = add_menu( 'Report', 'report', 'multiple_login') |
|
67 | 68 | - if (ungraded = Submission.where('graded_at is null').where('submitted_at < ?', 1.minutes.ago).count) > 0 |
|
68 | 69 | =link_to "#{ungraded} backlogs!", |
|
69 | 70 | grader_list_path, |
|
70 | 71 | class: 'navbar-btn btn btn-default btn-warning', data: {toggle: 'tooltip'},title: 'Number of ungraded submission' |
|
71 | 72 | |
|
72 | 73 | %ul.nav.navbar-nav.navbar-right |
|
73 | 74 | = add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-question-sign')}".html_safe, 'main', 'help') |
|
74 | 75 | = add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-comment')}".html_safe, 'messages', 'list', {title: I18n.t('menu.messages'), data: {toggle: 'tooltip'}}) |
|
75 | 76 | - if GraderConfiguration['system.user_setting_enabled'] |
|
76 | 77 | = add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-cog')}".html_safe, 'users', 'index', {title: I18n.t('menu.settings'), data: {toggle: 'tooltip'}}) |
|
77 | 78 | = add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-log-out')} #{@current_user.full_name}".html_safe, 'main', 'login', {title: I18n.t('menu.log_out'), data: {toggle: 'tooltip'}}) |
|
78 | 79 | |
|
79 | 80 | / |
|
80 | 81 | - if (@current_user!=nil) and (session[:admin]) |
|
81 | 82 | %nav.navbar.navbar-fixed-top.navbar-inverse.secondnavbar |
|
82 | 83 | .container-fluid |
|
83 | 84 | .collapse.navbar-collapse |
|
84 | 85 | %ul.nav.navbar-nav |
|
85 | 86 | = add_menu( '[Announcements]', 'announcements', 'index') |
|
86 | 87 | = add_menu( '[Msg console]', 'messages', 'console') |
|
87 | 88 | = add_menu( '[Problems]', 'problems', 'index') |
|
88 | 89 | = add_menu( '[Users]', 'user_admin', 'index') |
|
89 | 90 | = add_menu( '[Results]', 'user_admin', 'user_stat') |
|
90 | 91 | = add_menu( '[Report]', 'report', 'multiple_login') |
|
91 | 92 | = add_menu( '[Graders]', 'graders', 'list') |
|
92 | 93 | = add_menu( '[Contests]', 'contest_management', 'index') |
|
93 | 94 | = add_menu( '[Sites]', 'sites', 'index') |
|
94 | 95 | = add_menu( '[System config]', 'configurations', 'index') |
@@ -1,52 +1,55 | |||
|
1 | 1 | = error_messages_for 'problem' |
|
2 | 2 | / [form:problem] |
|
3 | 3 | .form-group |
|
4 | 4 | %label{:for => "problem_name"} Name |
|
5 | 5 | = text_field 'problem', 'name', class: 'form-control' |
|
6 | 6 | %small |
|
7 | 7 | Do not directly edit the problem name, unless you know what you are doing. If you want to change the name, use the name change button in the problem management menu instead. |
|
8 | 8 | .form-group |
|
9 | 9 | %label{:for => "problem_full_name"} Full name |
|
10 | 10 | = text_field 'problem', 'full_name', class: 'form-control' |
|
11 | 11 | .form-group |
|
12 | 12 | %label{:for => "problem_full_score"} Full score |
|
13 | 13 | = text_field 'problem', 'full_score', class: 'form-control' |
|
14 | 14 | .form-group |
|
15 | + %label{:for => "problem_full_score"} Tags | |
|
16 | + = collection_select(:problem, :tag_ids, Tag.all, :id, :name, {}, {multiple: true, class: 'form-control select2'}) | |
|
17 | + .form-group | |
|
15 | 18 | %label{:for => "problem_date_added"} Date added |
|
16 | 19 | = date_select 'problem', 'date_added', class: 'form-control' |
|
17 | 20 | - # TODO: these should be put in model Problem, but I can't think of |
|
18 | 21 | - # nice default values for them. These values look fine only |
|
19 | 22 | - # in this case (of lazily adding new problems). |
|
20 | 23 | - @problem.available = true if @problem!=nil and @problem.available==nil |
|
21 | 24 | - @problem.test_allowed = true if @problem!=nil and @problem.test_allowed==nil |
|
22 | 25 | - @problem.output_only = false if @problem!=nil and @problem.output_only==nil |
|
23 | 26 | .checkbox |
|
24 | 27 | %label{:for => "problem_available"} |
|
25 | 28 | = check_box :problem, :available |
|
26 | 29 | Available? |
|
27 | 30 | .checkbox |
|
28 | 31 | %label{:for => "problem_test_allowed"} |
|
29 | 32 | = check_box :problem, :test_allowed |
|
30 | 33 | Test allowed? |
|
31 | 34 | .checkbox |
|
32 | 35 | %label{:for => "problem_output_only"} |
|
33 | 36 | = check_box :problem, :output_only |
|
34 | 37 | Output only? |
|
35 | 38 | = error_messages_for 'description' |
|
36 | 39 | .form-group |
|
37 | 40 | %label{:for => "description_body"} Description |
|
38 | 41 | %br/ |
|
39 | 42 | = text_area :description, :body, :rows => 10, :cols => 80,class: 'form-control' |
|
40 | 43 | .form-group |
|
41 | 44 | %label{:for => "description_markdowned"} Markdowned? |
|
42 | 45 | = select "description", | |
|
43 | 46 | "markdowned", | |
|
44 | 47 | [['True',true],['False',false]], | |
|
45 | 48 | {:selected => (@description) ? @description.markdowned : false } | |
|
46 | 49 | .form-group |
|
47 | 50 | %label{:for => "problem_url"} URL |
|
48 | 51 | %br/ |
|
49 | 52 | = text_field 'problem', 'url',class: 'form-control' |
|
50 | 53 | %p |
|
51 | 54 | Task PDF #{file_field_tag 'file'} |
|
52 | 55 | / [eoform:problem] |
@@ -1,60 +1,65 | |||
|
1 | 1 | - content_for :head do |
|
2 | 2 | = stylesheet_link_tag 'problems' |
|
3 | 3 | %h1 Problems |
|
4 | 4 | %p |
|
5 | 5 | = link_to 'Import problems', {:action => 'import'}, class: 'btn btn-success btn-sm' |
|
6 | 6 | = link_to 'New problem', new_problem_path, class: 'btn btn-success btn-sm' |
|
7 | 7 | = link_to 'Bulk Manage', { action: 'manage'}, class: 'btn btn-info btn-sm' |
|
8 | 8 | = link_to 'Turn off all problems', {:action => 'turn_all_off'}, class: 'btn btn-default btn-sm' |
|
9 | 9 | = link_to 'Turn on all problems', {:action => 'turn_all_on'}, class: 'btn btn-default btn-sm' |
|
10 | 10 | .submitbox |
|
11 | 11 | = form_tag :action => 'quick_create' do |
|
12 | 12 | %b Quick New: |
|
13 | 13 | %label{:for => "problem_name"} Name |
|
14 | 14 | = text_field 'problem', 'name' |
|
15 | 15 | | |
|
16 | 16 | %label{:for => "problem_full_name"} Full name |
|
17 | 17 | = text_field 'problem', 'full_name' |
|
18 | 18 | = submit_tag "Create" |
|
19 | 19 | %table.table.table-condense.table-hover |
|
20 | 20 | %thead |
|
21 | 21 | %th Name |
|
22 | 22 | %th Full name |
|
23 | 23 | %th.text-right Full score |
|
24 | + %th Tags | |
|
24 | 25 | %th |
|
25 | 26 | Submit |
|
26 | 27 | %sup{class: 'text-primary',data: {toggle: 'tooltip'}, title: 'Admin can always submit to any problem' } [?] |
|
27 | 28 | %th Date added |
|
28 | 29 | %th.text-center |
|
29 | 30 | Avail? |
|
30 | 31 | %sup{class: 'text-primary',data: {toggle: 'tooltip'}, title: 'Let user submits to this problem?' } [?] |
|
31 | 32 | %th.text-center |
|
32 | 33 | View Data? |
|
33 | 34 | %sup{class: 'text-primary',data: {toggle: 'tooltip'}, title: 'Let user view the testcase of this problem?' } [?] |
|
34 | 35 | %th.text-center |
|
35 | 36 | Test? |
|
36 | 37 | %sup{class: 'text-primary',data: {toggle: 'tooltip'}, title: 'Let user uses test interface on this problem?' } [?] |
|
37 | 38 | - if GraderConfiguration.multicontests? |
|
38 | 39 | %th Contests |
|
39 | 40 | - for problem in @problems |
|
40 | 41 | %tr{:class => "#{(problem.available) ? "success" : "danger"}", :id => "prob-#{problem.id}", :name => "prob-#{problem.id}"} |
|
41 | 42 | - @problem=problem |
|
42 | 43 | %td= problem.name #in_place_editor_field :problem, :name, {}, :rows=>1 |
|
43 | 44 | %td |
|
44 | 45 | = problem.full_name #in_place_editor_field :problem, :full_name, {}, :rows=>1 |
|
45 | 46 | = link_to_description_if_any "[#{t 'main.problem_desc'}] <span class='glyphicon glyphicon-file'></span>".html_safe, problem |
|
46 | 47 | %td.text-right= problem.full_score #in_place_editor_field :problem, :full_score, {}, :rows=>1 |
|
48 | + %td | |
|
49 | + - problem.tags.each do |t| | |
|
50 | + - #%button.btn.btn-default.btn-xs= t.name | |
|
51 | + %span.label.label-default= t.name | |
|
47 | 52 | %td= link_to "Submit", direct_edit_problem_submissions_path(problem,@current_user.id), class: 'btn btn-xs btn-primary' |
|
48 | 53 | %td= problem.date_added |
|
49 | 54 | %td= toggle_button(@problem.available?, toggle_problem_path(@problem), "problem-avail-#{@problem.id}") |
|
50 | 55 | %td= toggle_button(@problem.view_testcase?, toggle_view_testcase_problem_path(@problem), "problem-view-testcase-#{@problem.id}") |
|
51 | 56 | %td= toggle_button(@problem.test_allowed?, toggle_test_problem_path(@problem), "problem-test-#{@problem.id}") |
|
52 | 57 | - if GraderConfiguration.multicontests? |
|
53 | 58 | %td |
|
54 | 59 | = problem.contests.collect { |c| c.name }.join(', ') |
|
55 | 60 | %td= link_to 'Stat', {:action => 'stat', :id => problem.id}, class: 'btn btn-info btn-xs btn-block' |
|
56 | 61 | %td= link_to 'Show', {:action => 'show', :id => problem}, class: 'btn btn-info btn-xs btn-block' |
|
57 | 62 | %td= link_to 'Edit', {:action => 'edit', :id => problem}, class: 'btn btn-info btn-xs btn-block' |
|
58 | 63 | %td= link_to 'Destroy', { :action => 'destroy', :id => problem }, :confirm => 'Are you sure?', :method => :delete, class: 'btn btn-danger btn-xs btn-block' |
|
59 | 64 | %br/ |
|
60 | 65 | = link_to '[New problem]', :action => 'new' |
@@ -1,107 +1,118 | |||
|
1 | 1 | - content_for :head do |
|
2 | 2 | = stylesheet_link_tag 'problems' |
|
3 | 3 | = javascript_include_tag 'local_jquery' |
|
4 | 4 | |
|
5 | 5 | :javascript |
|
6 | 6 | $(document).ready( function() { |
|
7 | 7 | function shiftclick(start,stop,value) { |
|
8 | 8 | $('tr input').each( function(id,input) { |
|
9 | 9 | var $input=$(input); |
|
10 | 10 | var iid=parseInt($input.attr('id').split('-')[2]); |
|
11 | 11 | if(iid>=start&&iid<=stop){ |
|
12 | 12 | $input.prop('checked',value) |
|
13 | 13 | } |
|
14 | 14 | }); |
|
15 | 15 | } |
|
16 | 16 | |
|
17 | 17 | $('tr input').click( function(e) { |
|
18 | 18 | if (e.shiftKey) { |
|
19 | 19 | stop = parseInt($(this).attr('id').split('-')[2]); |
|
20 | 20 | var orig_stop = stop |
|
21 | 21 | if (typeof start !== 'undefined') { |
|
22 | 22 | if (start > stop) { |
|
23 | 23 | var tmp = start; |
|
24 | 24 | start = stop; |
|
25 | 25 | stop = tmp; |
|
26 | 26 | } |
|
27 | 27 | shiftclick(start,stop,$(this).is(':checked') ) |
|
28 | 28 | } |
|
29 | 29 | start = orig_stop |
|
30 | 30 | } else { |
|
31 | 31 | start = parseInt($(this).attr('id').split('-')[2]); |
|
32 | 32 | } |
|
33 | 33 | }); |
|
34 | 34 | }); |
|
35 | 35 | |
|
36 | 36 | |
|
37 | 37 | %h1 Manage problems |
|
38 | 38 | |
|
39 | 39 | %p= link_to '[Back to problem list]', problems_path |
|
40 | 40 | |
|
41 | 41 | = form_tag :action=>'do_manage' do |
|
42 | 42 | .panel.panel-primary |
|
43 | 43 | .panel-heading |
|
44 | 44 | Action |
|
45 | 45 | .panel-body |
|
46 | 46 | .submit-box |
|
47 | 47 | What do you want to do to the selected problem? |
|
48 | 48 | %br/ |
|
49 | 49 | (You can shift-click to select a range of problems) |
|
50 | 50 | %ul.form-inline |
|
51 | 51 | %li |
|
52 |
- Change |
|
|
52 | + Change "Date added" to | |
|
53 | 53 | .input-group.date |
|
54 | 54 | = text_field_tag :date_added, class: 'form-control' |
|
55 | 55 | %span.input-group-addon |
|
56 | 56 | %span.glyphicon.glyphicon-calendar |
|
57 | 57 | -# = select_date Date.current, :prefix => 'date_added' |
|
58 | 58 | |
|
59 | 59 | = submit_tag 'Change', :name => 'change_date_added', class: 'btn btn-primary btn-sm' |
|
60 | 60 | %li |
|
61 |
- Set |
|
|
61 | + Set "Available" to | |
|
62 | 62 | = submit_tag 'True', :name => 'enable_problem', class: 'btn btn-primary btn-sm' |
|
63 | 63 | = submit_tag 'False', :name => 'disable_problem', class: 'btn btn-primary btn-sm' |
|
64 | 64 | |
|
65 | 65 | - if GraderConfiguration.multicontests? |
|
66 | 66 | %li |
|
67 | - Add to | |
|
67 | + Add selected problems to contest | |
|
68 | 68 | = select("contest","id",Contest.all.collect {|c| [c.title, c.id]}) |
|
69 | 69 | = submit_tag 'Add', :name => 'add_to_contest', class: 'btn btn-primary btn-sm' |
|
70 | 70 | %li |
|
71 | - Add problems to group | |
|
71 | + Add selected problems to user group | |
|
72 | 72 | = select_tag "group_id", options_from_collection_for_select( Group.all, 'id','name',params[:group_name]), id: 'group_name',class: 'select2' |
|
73 |
- = submit_tag 'Add', name: 'add_group', class: 'btn btn- |
|
|
74 | - | |
|
73 | + = submit_tag 'Add', name: 'add_group', class: 'btn btn-primary' | |
|
74 | + %li | |
|
75 | + Add the following tags to the selected problems | |
|
76 | + = select_tag "tag_ids", options_from_collection_for_select( Tag.all, 'id','name'), id: 'tags_name',class: 'select2', multiple: true, data: {placeholder: 'Select tags by clicking', width: "200px"} | |
|
77 | + = submit_tag 'Add', name: 'add_tags', class: 'btn btn-primary' | |
|
75 | 78 | |
|
76 | - %table.table.table-hover | |
|
77 | - %tr{style: "text-align: left;"} | |
|
78 | - %th= check_box_tag 'select_all' | |
|
79 | - %th Name | |
|
80 |
- %th |
|
|
81 |
- %th |
|
|
82 |
- %th |
|
|
83 | - - if GraderConfiguration.multicontests? | |
|
84 |
- %th |
|
|
79 | + %table.table.table-hover.datatable | |
|
80 | + %thead | |
|
81 | + %tr{style: "text-align: left;"} | |
|
82 | + %th= check_box_tag 'select_all' | |
|
83 | + %th Name | |
|
84 | + %th Full name | |
|
85 | + %th Tags | |
|
86 | + %th Available | |
|
87 | + %th Date added | |
|
88 | + - if GraderConfiguration.multicontests? | |
|
89 | + %th Contests | |
|
85 | 90 | |
|
86 | - - num = 0 | |
|
87 | - - for problem in @problems | |
|
88 | - - num += 1 | |
|
89 | - %tr{:id => "row-prob-#{problem.id}", :name=> "prob-#{problem.id}"} | |
|
90 | - %td= check_box_tag "prob-#{problem.id}-#{num}" | |
|
91 | - %td= problem.name | |
|
92 |
- %td= problem. |
|
|
93 |
- %td= problem. |
|
|
94 | - %td= problem.date_added | |
|
95 | - - if GraderConfiguration.multicontests? | |
|
91 | + %tbody | |
|
92 | + - num = 0 | |
|
93 | + - for problem in @problems | |
|
94 | + - num += 1 | |
|
95 | + %tr{:id => "row-prob-#{problem.id}", :name=> "prob-#{problem.id}"} | |
|
96 | + %td= check_box_tag "prob-#{problem.id}-#{num}" | |
|
97 | + %td= problem.name | |
|
98 | + %td= problem.full_name | |
|
96 | 99 | %td |
|
97 |
- - problem. |
|
|
98 | - = "(#{contest.name} [#{link_to 'x', :action => 'remove_contest', :id => problem.id, :contest_id => contest.id }])" | |
|
100 | + - problem.tags.each do |t| | |
|
101 | + %span.label.label-default= t.name | |
|
102 | + %td= problem.available | |
|
103 | + %td= problem.date_added | |
|
104 | + - if GraderConfiguration.multicontests? | |
|
105 | + %td | |
|
106 | + - problem.contests.each do |contest| | |
|
107 | + = "(#{contest.name} [#{link_to 'x', :action => 'remove_contest', :id => problem.id, :contest_id => contest.id }])" | |
|
99 | 108 | |
|
100 | 109 | :javascript |
|
101 | 110 | $('.input-group.date').datetimepicker({ |
|
102 | 111 | format: 'DD/MMM/YYYY', |
|
103 | 112 | showTodayButton: true, |
|
104 | 113 | widgetPositioning: {horizontal: 'auto', vertical: 'bottom'}, |
|
105 | 114 | |
|
106 | 115 | }); |
|
107 | - | |
|
116 | + $('.datatable').DataTable({ | |
|
117 | + paging: false | |
|
118 | + }); |
@@ -1,57 +1,59 | |||
|
1 | 1 | :css |
|
2 | 2 | .fix-width { |
|
3 | 3 | font-family: "Consolas, Monaco, Droid Sans Mono,Mono, Monospace,Courier" |
|
4 | 4 | } |
|
5 | 5 | |
|
6 | 6 | %h1 Problem stat: #{@problem.name} |
|
7 | 7 | %h2 Overview |
|
8 | 8 | |
|
9 | 9 | |
|
10 | 10 | %table.info |
|
11 | 11 | %thead |
|
12 | 12 | %tr.info-head |
|
13 | 13 | %th Stat |
|
14 | 14 | %th Value |
|
15 | 15 | %tbody |
|
16 | 16 | %tr{class: cycle('info-even','info-odd')} |
|
17 | 17 | %td Submissions |
|
18 | 18 | %td= @submissions.count |
|
19 | 19 | %tr{class: cycle('info-even','info-odd')} |
|
20 | 20 | %td Solved/Attempted User |
|
21 | 21 | %td #{@summary[:solve]}/#{@summary[:attempt]} (#{(@summary[:solve]*100.0/@summary[:attempt]).round(1)}%) |
|
22 | 22 | |
|
23 | 23 | %h2 Submissions Count |
|
24 | 24 | = render partial: 'application/bar_graph', locals: { histogram: @histogram } |
|
25 | 25 | |
|
26 | 26 | %h2 Submissions |
|
27 | 27 | - if @submissions and @submissions.count > 0 |
|
28 | 28 | %table#main_table.table.table-condensed.table-striped |
|
29 | 29 | %thead |
|
30 | 30 | %tr |
|
31 | 31 | %th ID |
|
32 | 32 | %th Login |
|
33 | 33 | %th Name |
|
34 | 34 | %th Submitted_at |
|
35 | + %th language | |
|
35 | 36 | %th Points |
|
36 | 37 | %th comment |
|
37 | 38 | %th IP |
|
38 | 39 | %tbody |
|
39 | 40 | - row_odd,curr = true,'' |
|
40 | 41 | - @submissions.each do |sub| |
|
41 | 42 | - next unless sub.user |
|
42 | 43 | - row_odd,curr = !row_odd, sub.user if curr != sub.user |
|
43 | 44 | %tr |
|
44 | 45 | %td= link_to sub.id, submission_path(sub) |
|
45 | 46 | %td= link_to sub.user.login, stat_user_path(sub.user) |
|
46 | 47 | %td= sub.user.full_name |
|
47 | 48 | %td{data: {order: sub.submitted_at}}= time_ago_in_words(sub.submitted_at) + " ago" |
|
49 | + %td= sub.language.name | |
|
48 | 50 | %td= sub.points |
|
49 | 51 | %td.fix-width= sub.grader_comment |
|
50 | 52 | %td= sub.ip_address |
|
51 | 53 | - else |
|
52 | 54 | No submission |
|
53 | 55 | |
|
54 | 56 | :javascript |
|
55 | 57 | $("#main_table").DataTable({ |
|
56 | 58 | paging: false |
|
57 | 59 | }); |
@@ -1,34 +1,69 | |||
|
1 | 1 | %table.table.sortable.table-striped.table-bordered.table-condensed |
|
2 | 2 | %thead |
|
3 | 3 | %tr |
|
4 | 4 | %th Login |
|
5 | 5 | %th Name |
|
6 | 6 | / %th Activated? |
|
7 | 7 | / %th Logged_in |
|
8 | 8 | / %th Contest(s) |
|
9 | 9 | %th Remark |
|
10 | 10 | - @problems.each do |p| |
|
11 | 11 | %th.text-right= p.name.gsub('_',' ') |
|
12 | 12 | %th.text-right Total |
|
13 | 13 | %th.text-right Passed |
|
14 | 14 | %tbody |
|
15 | + - sum = Array.new(@scorearray[0].count,0) | |
|
16 | + - nonzero = Array.new(@scorearray[0].count,0) | |
|
17 | + - full = Array.new(@scorearray[0].count,0) | |
|
15 | 18 | - @scorearray.each do |sc| |
|
16 | 19 | %tr |
|
17 | 20 | - total,num_passed = 0,0 |
|
18 | 21 | - sc.each_index do |i| |
|
19 | 22 | - if i == 0 |
|
20 | 23 | %td= link_to sc[i].login, stat_user_path(sc[i]) |
|
21 | 24 | %td= sc[i].full_name |
|
22 | 25 | / %td= sc[i].activated |
|
23 | 26 | / %td= sc[i].try(:contest_stat).try(:started_at) ? 'yes' : 'no' |
|
24 | 27 | / %td= sc[i].contests.collect {|c| c.name}.join(', ') |
|
25 | 28 | %td= sc[i].remark |
|
26 | 29 | - else |
|
27 | 30 | %td.text-right= sc[i][0] |
|
28 | 31 | - total += sc[i][0] |
|
29 | 32 | - num_passed += 1 if sc[i][1] |
|
33 | + - sum[i] += sc[i][0] | |
|
34 | + - nonzero[i] += 1 if sc[i][0] > 0 | |
|
35 | + - full[i] += 1 if sc[i][1] | |
|
30 | 36 | %td.text-right= total |
|
31 | 37 | %td.text-right= num_passed |
|
38 | + %tfoot | |
|
39 | + %tr | |
|
40 | + %td Summation | |
|
41 | + %td | |
|
42 | + %td | |
|
43 | + - sum.each.with_index do |s,i| | |
|
44 | + - next if i == 0 | |
|
45 | + %td.text-right= number_with_delimiter(s) | |
|
46 | + %td | |
|
47 | + %td | |
|
48 | + %tr | |
|
49 | + %td partial solver | |
|
50 | + %td | |
|
51 | + %td | |
|
52 | + - nonzero.each.with_index do |s,i| | |
|
53 | + - next if i == 0 | |
|
54 | + %td.text-right= number_with_delimiter(s) | |
|
55 | + %td | |
|
56 | + %td | |
|
57 | + %tr | |
|
58 | + %td Full solver | |
|
59 | + %td | |
|
60 | + %td | |
|
61 | + - full.each.with_index do |s,i| | |
|
62 | + - next if i == 0 | |
|
63 | + %td.text-right= number_with_delimiter(s) | |
|
64 | + %td | |
|
65 | + %td | |
|
66 | + | |
|
32 | 67 | |
|
33 | 68 | :javascript |
|
34 | 69 | $.bootstrapSortable(true,'reversed') |
@@ -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', |
|
|
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,35 +1,36 | |||
|
1 | 1 | %h1 Editing site |
|
2 | 2 | = error_messages_for :site |
|
3 | 3 | = form_for(@site) do |f| |
|
4 | 4 | .row |
|
5 | 5 | .col-md-4 |
|
6 | 6 | .form-group.field |
|
7 | 7 | = f.label :name, "Name" |
|
8 | 8 | = f.text_field :name, class: 'form-control' |
|
9 | 9 | .form-group.field |
|
10 | 10 | = f.label :password, "Password" |
|
11 | 11 | = f.text_field :password, class: 'form-control' |
|
12 | 12 | .form-group.field |
|
13 | 13 | = f.label :started, "Started" |
|
14 | 14 | = f.check_box :started, class: 'form-control' |
|
15 | 15 | .form-group.field |
|
16 | 16 | = f.label :start_time, "Start time" |
|
17 | 17 | -# = f.datetime_select :start_time, :include_blank => true |
|
18 | 18 | .input-group.date |
|
19 | 19 | = f.text_field :start_time, class:'form-control' , value: (@site.start_time ? @site.start_time.strftime('%d/%b/%Y %H:%M') : '') |
|
20 | 20 | %span.input-group-addon |
|
21 | 21 | %span.glyphicon.glyphicon-calendar |
|
22 | 22 | .actions |
|
23 | 23 | = f.submit "Update", class: 'btn btn-primary' |
|
24 | 24 | .col-md-8 |
|
25 | 25 | |
|
26 | 26 | = link_to 'Show', @site |
|
27 | 27 | | |
|
28 | 28 | = link_to 'Back', sites_path |
|
29 | 29 | |
|
30 | 30 | |
|
31 | 31 | :javascript |
|
32 | 32 | $('.input-group.date').datetimepicker({ |
|
33 | 33 | format: 'DD/MMM/YYYY HH:mm', |
|
34 | + showTodayButton: true, | |
|
34 | 35 | }); |
|
35 | 36 |
@@ -1,229 +1,230 | |||
|
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 | - - if @submission | |
|
36 |
- |
|
|
37 | - :locals => {submission: @submission, problem_name: @problem.name, problem_id: @problem.id } | |
|
35 | + %div#latest_status | |
|
36 | + - if @submission | |
|
37 | + = render :partial => 'submission_short', | |
|
38 | + :locals => {submission: @submission, problem_name: @problem.name, problem_id: @problem.id } | |
|
38 | 39 | .row |
|
39 | 40 | .col-md-12 |
|
40 | 41 | %h2 Console |
|
41 | 42 | %textarea#console{style: 'height: 100%; width: 100%;background-color:#000;color:#fff;font-family: consolas, monaco, "Droid Sans Mono";',rows: 20} |
|
42 | 43 | |
|
43 | 44 | :javascript |
|
44 | 45 | $(document).ready(function() { |
|
45 | 46 | e = ace.edit("editor") |
|
46 | 47 | e.setValue($("#text_sourcecode").val()); |
|
47 | 48 | e.gotoLine(1); |
|
48 | 49 | $("#language_id").trigger('change'); |
|
49 | 50 | brython(); |
|
50 | 51 | }); |
|
51 | 52 | |
|
52 | 53 | |
|
53 | 54 | %script#__main__{type:'text/python3'} |
|
54 | 55 | :plain |
|
55 | 56 | import sys |
|
56 | 57 | import traceback |
|
57 | 58 | |
|
58 | 59 | from browser import document as doc |
|
59 | 60 | from browser import window, alert, console |
|
60 | 61 | |
|
61 | 62 | _credits = """ Thanks to CWI, CNRI, BeOpen.com, Zope Corporation and a cast of thousands |
|
62 | 63 | for supporting Python development. See www.python.org for more information.""" |
|
63 | 64 | |
|
64 | 65 | _copyright = """Copyright (c) 2012, Pierre Quentel pierre.quentel@gmail.com |
|
65 | 66 | All Rights Reserved. |
|
66 | 67 | |
|
67 | 68 | Copyright (c) 2001-2013 Python Software Foundation. |
|
68 | 69 | All Rights Reserved. |
|
69 | 70 | |
|
70 | 71 | Copyright (c) 2000 BeOpen.com. |
|
71 | 72 | All Rights Reserved. |
|
72 | 73 | |
|
73 | 74 | Copyright (c) 1995-2001 Corporation for National Research Initiatives. |
|
74 | 75 | All Rights Reserved. |
|
75 | 76 | |
|
76 | 77 | Copyright (c) 1991-1995 Stichting Mathematisch Centrum, Amsterdam. |
|
77 | 78 | All Rights Reserved.""" |
|
78 | 79 | |
|
79 | 80 | _license = """Copyright (c) 2012, Pierre Quentel pierre.quentel@gmail.com |
|
80 | 81 | All rights reserved. |
|
81 | 82 | |
|
82 | 83 | Redistribution and use in source and binary forms, with or without |
|
83 | 84 | modification, are permitted provided that the following conditions are met: |
|
84 | 85 | |
|
85 | 86 | Redistributions of source code must retain the above copyright notice, this |
|
86 | 87 | list of conditions and the following disclaimer. Redistributions in binary |
|
87 | 88 | form must reproduce the above copyright notice, this list of conditions and |
|
88 | 89 | the following disclaimer in the documentation and/or other materials provided |
|
89 | 90 | with the distribution. |
|
90 | 91 | Neither the name of the <ORGANIZATION> nor the names of its contributors may |
|
91 | 92 | be used to endorse or promote products derived from this software without |
|
92 | 93 | specific prior written permission. |
|
93 | 94 | |
|
94 | 95 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
|
95 | 96 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|
96 | 97 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|
97 | 98 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
|
98 | 99 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
|
99 | 100 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
|
100 | 101 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
|
101 | 102 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
|
102 | 103 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
|
103 | 104 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|
104 | 105 | POSSIBILITY OF SUCH DAMAGE. |
|
105 | 106 | """ |
|
106 | 107 | |
|
107 | 108 | def credits(): |
|
108 | 109 | print(_credits) |
|
109 | 110 | credits.__repr__ = lambda:_credits |
|
110 | 111 | |
|
111 | 112 | def copyright(): |
|
112 | 113 | print(_copyright) |
|
113 | 114 | copyright.__repr__ = lambda:_copyright |
|
114 | 115 | |
|
115 | 116 | def license(): |
|
116 | 117 | print(_license) |
|
117 | 118 | license.__repr__ = lambda:_license |
|
118 | 119 | |
|
119 | 120 | def write(data): |
|
120 | 121 | doc['console'].value += str(data) |
|
121 | 122 | |
|
122 | 123 | |
|
123 | 124 | sys.stdout.write = sys.stderr.write = write |
|
124 | 125 | history = [] |
|
125 | 126 | current = 0 |
|
126 | 127 | _status = "main" # or "block" if typing inside a block |
|
127 | 128 | |
|
128 | 129 | # execution namespace |
|
129 | 130 | editor_ns = {'credits':credits, |
|
130 | 131 | 'copyright':copyright, |
|
131 | 132 | 'license':license, |
|
132 | 133 | '__name__':'__main__'} |
|
133 | 134 | |
|
134 | 135 | def cursorToEnd(*args): |
|
135 | 136 | pos = len(doc['console'].value) |
|
136 | 137 | doc['console'].setSelectionRange(pos, pos) |
|
137 | 138 | doc['console'].scrollTop = doc['console'].scrollHeight |
|
138 | 139 | |
|
139 | 140 | def get_col(area): |
|
140 | 141 | # returns the column num of cursor |
|
141 | 142 | sel = doc['console'].selectionStart |
|
142 | 143 | lines = doc['console'].value.split('\n') |
|
143 | 144 | for line in lines[:-1]: |
|
144 | 145 | sel -= len(line) + 1 |
|
145 | 146 | return sel |
|
146 | 147 | |
|
147 | 148 | |
|
148 | 149 | def myKeyPress(event): |
|
149 | 150 | global _status, current |
|
150 | 151 | if event.keyCode == 9: # tab key |
|
151 | 152 | event.preventDefault() |
|
152 | 153 | doc['console'].value += " " |
|
153 | 154 | elif event.keyCode == 13: # return |
|
154 | 155 | src = doc['console'].value |
|
155 | 156 | if _status == "main": |
|
156 | 157 | currentLine = src[src.rfind('>>>') + 4:] |
|
157 | 158 | elif _status == "3string": |
|
158 | 159 | currentLine = src[src.rfind('>>>') + 4:] |
|
159 | 160 | currentLine = currentLine.replace('\n... ', '\n') |
|
160 | 161 | else: |
|
161 | 162 | currentLine = src[src.rfind('...') + 4:] |
|
162 | 163 | if _status == 'main' and not currentLine.strip(): |
|
163 | 164 | doc['console'].value += '\n>>> ' |
|
164 | 165 | event.preventDefault() |
|
165 | 166 | return |
|
166 | 167 | doc['console'].value += '\n' |
|
167 | 168 | history.append(currentLine) |
|
168 | 169 | current = len(history) |
|
169 | 170 | if _status == "main" or _status == "3string": |
|
170 | 171 | try: |
|
171 | 172 | _ = editor_ns['_'] = eval(currentLine, editor_ns) |
|
172 | 173 | if _ is not None: |
|
173 | 174 | write(repr(_)+'\n') |
|
174 | 175 | doc['console'].value += '>>> ' |
|
175 | 176 | _status = "main" |
|
176 | 177 | except IndentationError: |
|
177 | 178 | doc['console'].value += '... ' |
|
178 | 179 | _status = "block" |
|
179 | 180 | except SyntaxError as msg: |
|
180 | 181 | if str(msg) == 'invalid syntax : triple string end not found' or \ |
|
181 | 182 | str(msg).startswith('Unbalanced bracket'): |
|
182 | 183 | doc['console'].value += '... ' |
|
183 | 184 | _status = "3string" |
|
184 | 185 | elif str(msg) == 'eval() argument must be an expression': |
|
185 | 186 | try: |
|
186 | 187 | exec(currentLine, editor_ns) |
|
187 | 188 | except: |
|
188 | 189 | traceback.print_exc() |
|
189 | 190 | doc['console'].value += '>>> ' |
|
190 | 191 | _status = "main" |
|
191 | 192 | elif str(msg) == 'decorator expects function': |
|
192 | 193 | doc['console'].value += '... ' |
|
193 | 194 | _status = "block" |
|
194 | 195 | else: |
|
195 | 196 | traceback.print_exc() |
|
196 | 197 | doc['console'].value += '>>> ' |
|
197 | 198 | _status = "main" |
|
198 | 199 | except: |
|
199 | 200 | traceback.print_exc() |
|
200 | 201 | doc['console'].value += '>>> ' |
|
201 | 202 | _status = "main" |
|
202 | 203 | elif currentLine == "": # end of block |
|
203 | 204 | block = src[src.rfind('>>>') + 4:].splitlines() |
|
204 | 205 | block = [block[0]] + [b[4:] for b in block[1:]] |
|
205 | 206 | block_src = '\n'.join(block) |
|
206 | 207 | # status must be set before executing code in globals() |
|
207 | 208 | _status = "main" |
|
208 | 209 | try: |
|
209 | 210 | _ = exec(block_src, editor_ns) |
|
210 | 211 | if _ is not None: |
|
211 | 212 | print(repr(_)) |
|
212 | 213 | except: |
|
213 | 214 | traceback.print_exc() |
|
214 | 215 | doc['console'].value += '>>> ' |
|
215 | 216 | else: |
|
216 | 217 | doc['console'].value += '... ' |
|
217 | 218 | |
|
218 | 219 | cursorToEnd() |
|
219 | 220 | event.preventDefault() |
|
220 | 221 | |
|
221 | 222 | def myKeyDown(event): |
|
222 | 223 | global _status, current |
|
223 | 224 | if event.keyCode == 37: # left arrow |
|
224 | 225 | sel = get_col(doc['console']) |
|
225 | 226 | if sel < 5: |
|
226 | 227 | event.preventDefault() |
|
227 | 228 | event.stopPropagation() |
|
228 | 229 | elif event.keyCode == 36: # line start |
|
229 | 230 | pos = doc['console'].selectionStart |
@@ -1,2 +1,2 | |||
|
1 | 1 | :plain |
|
2 | - $("#latest_status").html("#{j render({partial: 'submission_short', locals: {submission: @submission, problem_name: @problem.name}})}") | |
|
2 | + $("#latest_status").html("#{j render({partial: 'submission_short', locals: {submission: @submission, problem_name: @problem.name, problem_id: @problem.id}})}") |
@@ -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,193 +1,195 | |||
|
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 | - def import_from_file(tempfile, | |
|
12 | - time_limit, | |
|
11 | + #Create or update problem according to the parameter | |
|
12 | + def import_from_file(tempfile, | |
|
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) |
|
151 | 153 | pdf_files = Dir["#{dirname}/*.pdf"] |
|
152 | 154 | puts "CHECKING... #{dirname}" |
|
153 | 155 | if pdf_files.length != 0 |
|
154 | 156 | puts "HAS PDF FILE" |
|
155 | 157 | filename = pdf_files[0] |
|
156 | 158 | |
|
157 | 159 | @problem.save if not @problem.id |
|
158 | 160 | out_dirname = "#{Problem.download_file_basedir}/#{@problem.id}" |
|
159 | 161 | if not FileTest.exists? out_dirname |
|
160 | 162 | Dir.mkdir out_dirname |
|
161 | 163 | end |
|
162 | 164 | |
|
163 | 165 | out_filename = "#{out_dirname}/#{@problem.name}.pdf" |
|
164 | 166 | |
|
165 | 167 | if FileTest.exists? out_filename |
|
166 | 168 | File.delete out_filename |
|
167 | 169 | end |
|
168 | 170 | |
|
169 | 171 | File.rename(filename, out_filename) |
|
170 | 172 | @problem.description_filename = "#{@problem.name}.pdf" |
|
171 | 173 | @problem.save |
|
172 | 174 | return "\nProblem pdf imported from #{filename}." |
|
173 | 175 | else |
|
174 | 176 | return "" |
|
175 | 177 | end |
|
176 | 178 | end |
|
177 | 179 | |
|
178 | 180 | #just set the full score to the total number of test case |
|
179 | 181 | #it is not perfect but works on most normal use case |
|
180 | 182 | def import_full_score(dirname) |
|
181 | 183 | num = 0 |
|
182 | 184 | loop do |
|
183 | 185 | num += 1 |
|
184 | 186 | in_file = Dir["#{dirname}/#{num}*.in"] |
|
185 | 187 | break if in_file.length == 0 |
|
186 | 188 | end |
|
187 | 189 | full_score = (num - 1) * 10 |
|
188 | 190 | @problem.full_score = full_score |
|
189 | 191 | @problem.save |
|
190 | 192 | return "\nFull score is set to #{full_score}." |
|
191 | 193 | end |
|
192 | 194 | |
|
193 | 195 | end |
You need to be logged in to leave comments.
Login now