Description:
merge
Commit status:
[Not Reviewed]
References:
merge java
Comments:
0 Commit comments 0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
Add another comment

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.gmtime
88 + now = Time.zone.now
89 89 st = ''
90 - if (time.yday != now.yday) or
91 - (time.year != now.year)
92 - st = time.strftime("%x ")
90 + if (time.yday != now.yday) or (time.year != now.year)
91 + st = time.strftime("%d/%m/%y ")
93 92 end
94 93 st + time.strftime("%X")
95 94 end
96 95
97 96 def format_short_duration(duration)
98 97 return '' if duration==nil
99 98 d = duration.to_f
100 99 return Time.at(d).gmtime.strftime("%X")
101 100 end
102 101
102 + def format_full_time_ago(time)
103 + st = time_ago_in_words(time) + ' ago (' + format_short_time(time) + ')'
104 + end
105 +
103 106 def read_textfile(fname,max_size=2048)
104 107 begin
105 108 File.open(fname).read(max_size)
106 109 rescue
107 110 nil
108 111 end
109 112 end
110 113
111 114 def toggle_button(on,toggle_url,id, option={})
112 115 btn_size = option[:size] || 'btn-xs'
113 116 link_to (on ? "Yes" : "No"), toggle_url,
114 117 {class: "btn btn-block #{btn_size} btn-#{on ? 'success' : 'default'} ajax-toggle",
115 118 id: id,
116 119 data: {remote: true, method: 'get'}}
117 120 end
118 121
119 122 def get_ace_mode(language)
120 123 # return ace mode string from Language
121 124
122 125 case language.pretty_name
123 126 when 'Pascal'
124 127 'ace/mode/pascal'
125 128 when 'C++','C'
126 129 'ace/mode/c_cpp'
127 130 when 'Ruby'
128 131 'ace/mode/ruby'
129 132 when 'Python'
130 133 'ace/mode/python'
131 134 when 'Java'
132 135 'ace/mode/java'
133 136 else
134 137 'ace/mode/c_cpp'
135 138 end
136 139 end
137 140
138 141
139 142 def user_title_bar(user)
140 143 header = ''
141 144 time_left = ''
142 145
143 146 #
144 147 # if the contest is over
145 148 if GraderConfiguration.time_limit_mode?
146 149 if user.contest_finished?
147 150 header = <<CONTEST_OVER
148 151 <tr><td colspan="2" align="center">
149 152 <span class="contest-over-msg">THE CONTEST IS OVER</span>
150 153 </td></tr>
151 154 CONTEST_OVER
152 155 end
153 156 if !user.contest_started?
154 157 time_left = "&nbsp;&nbsp;" + (t 'title_bar.contest_not_started')
155 158 else
156 159 time_left = "&nbsp;&nbsp;" + (t 'title_bar.remaining_time') +
157 160 " #{format_short_duration(user.contest_time_left)}"
158 161 end
159 162 end
160 163
161 164 #
162 165 # if the contest is in the anaysis mode
163 166 if GraderConfiguration.analysis_mode?
164 167 header = <<ANALYSISMODE
165 168 <tr><td colspan="2" align="center">
166 169 <span class="contest-over-msg">ANALYSIS MODE</span>
167 170 </td></tr>
168 171 ANALYSISMODE
169 172 end
170 173
171 174 contest_name = GraderConfiguration['contest.name']
172 175
173 176 #
174 177 # build real title bar
175 178 result = <<TITLEBAR
176 179 <div class="title">
177 180 <table>
178 181 #{header}
179 182 <tr>
180 183 <td class="left-col">
181 184 #{user.full_name}<br/>
182 185 #{t 'title_bar.current_time'} #{format_short_time(Time.zone.now)}
183 186 #{time_left}
184 187 <br/>
185 188 </td>
186 189 <td class="right-col">#{contest_name}</td>
187 190 </tr>
188 191 </table>
189 192 </div>
190 193 TITLEBAR
191 194 result.html_safe
192 195 end
193 196
194 197 def markdown(text)
195 198 markdown = RDiscount.new(text)
196 199 markdown.to_html.html_safe
197 200 end
198 201
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 - if submission.nil?
3 2 = "-"
4 3 - else
4 + %strong= "Submission ID:"
5 + = submission.id
6 + %br
5 7 - unless submission.graded_at
6 - = t 'main.submitted_at'
7 - = format_short_time(submission.submitted_at.localtime)
8 + %strong= t 'main.submitted_at:'
9 + = format_full_time_ago(submission.submitted_at.localtime)
8 10 - else
9 - %strong= t 'main.graded_at'
10 - = "#{format_short_time(submission.graded_at.localtime)} "
11 + %strong= t 'main.graded_at:'
12 + = format_full_time_ago(submission.graded_at.localtime)
11 13 %br
12 14 - if GraderConfiguration['ui.show_score']
13 15 %strong=t 'main.score'
14 16 = "#{(submission.points*100/submission.problem.full_score).to_i} "
15 17 = " ["
16 18 %tt
17 19 = submission.grader_comment
18 20 = "]"
19 21 %br
20 - %strong View:
21 - - if GraderConfiguration.show_grading_result
22 - = link_to '[detailed result]', :action => 'result', :id => submission.id
23 - = link_to "#{t 'main.cmp_msg'}", {:action => 'compiler_msg', :id => submission.id}, {popup: true,class: 'btn btn-xs btn-info'}
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 date added to
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 &nbsp;&nbsp;&nbsp;
59 59 = submit_tag 'Change', :name => 'change_date_added', class: 'btn btn-primary btn-sm'
60 60 %li
61 - Set available to
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-default'
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 Full name
81 - %th Available
82 - %th Date added
83 - - if GraderConfiguration.multicontests?
84 - %th Contests
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.full_name
93 - %td= problem.available
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.contests.each do |contest|
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', true
36 + = radio_button_tag 'users', 'all', (params[:users] == "all")
37 37 All users
38 38 .radio
39 39 %label
40 - = radio_button_tag 'users', 'enabled'
40 + = radio_button_tag 'users', 'enabled', (params[:users] == "enabled")
41 41 Only enabled users
42 42 .row
43 43 .col-md-12
44 44 = button_tag 'Show', class: "btn btn-primary btn-large", value: "show"
45 45 = button_tag 'Download CSV', class: "btn btn-primary btn-large", value: "download"
46 46
47 47 - if @scorearray
48 48 %h2 Result
49 49 =render "score_table"
@@ -1,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 - = render :partial => 'submission_short',
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