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

r689:124074894d4b - - 16 files changed: 126 inserted, 41 deleted

new file 100644
new file 100644
new file 100644
new file 100644
@@ -0,0 +1,7
1 + class GroupProblem < ActiveRecord::Base
2 + self.table_name = 'groups_problems'
3 +
4 + belongs_to :problem
5 + belongs_to :group
6 + validates_uniqueness_of :problem_id, scope: :group_id, message: ->(object, data) { "'#{Problem.find(data[:value]).full_name}' is already in the group" }
7 + end
@@ -0,0 +1,7
1 + class GroupUser < ActiveRecord::Base
2 + self.table_name = 'groups_users'
3 +
4 + belongs_to :user
5 + belongs_to :group
6 + validates_uniqueness_of :user_id, scope: :group_id, message: ->(object, data) { "'#{User.find(data[:value]).full_name}' is already in the group" }
7 + end
@@ -0,0 +1,23
1 + # Be sure to restart your server when you modify this file.
2 +
3 + # Version of your assets, change this if you want to expire all your assets.
4 + Rails.application.config.assets.version = '1.0'
5 +
6 + # Add additional assets to the asset load path.
7 + # Rails.application.config.assets.paths << Emoji.images_path
8 + # Add Yarn node_modules folder to the asset load path.
9 + Rails.application.config.assets.paths << Rails.root.join('node_modules')
10 + Rails.application.config.assets.paths << Rails.root.join('vendor/assets/fonts')
11 +
12 + # Precompile additional assets.
13 + # application.js, application.css, and all non-JS/CSS in the app/assets
14 + # folder are already added.
15 + # Rails.application.config.assets.precompile += %w( admin.js admin.css )
16 +
17 + Rails.application.config.assets.precompile += ['announcement_refresh.js','effects.js','site_update.js']
18 + Rails.application.config.assets.precompile += ['local_jquery.js','tablesorter-theme.cafe.css']
19 + %w( announcements submissions configurations contests contest_management graders heartbeat
20 + login main messages problems report site sites sources tasks groups
21 + test user_admin users ).each do |controller|
22 + Rails.application.config.assets.precompile += ["#{controller}.js", "#{controller}.css"]
23 + end
@@ -1,86 +1,94
1 1 class GroupsController < ApplicationController
2 2 before_action :set_group, only: [:show, :edit, :update, :destroy,
3 3 :add_user, :remove_user,
4 4 :add_problem, :remove_problem,
5 5 ]
6 6 before_action :authenticate, :admin_authorization
7 7
8 8 # GET /groups
9 9 def index
10 10 @groups = Group.all
11 11 end
12 12
13 13 # GET /groups/1
14 14 def show
15 15 end
16 16
17 17 # GET /groups/new
18 18 def new
19 19 @group = Group.new
20 20 end
21 21
22 22 # GET /groups/1/edit
23 23 def edit
24 24 end
25 25
26 26 # POST /groups
27 27 def create
28 28 @group = Group.new(group_params)
29 29
30 30 if @group.save
31 31 redirect_to @group, notice: 'Group was successfully created.'
32 32 else
33 33 render :new
34 34 end
35 35 end
36 36
37 37 # PATCH/PUT /groups/1
38 38 def update
39 39 if @group.update(group_params)
40 40 redirect_to @group, notice: 'Group was successfully updated.'
41 41 else
42 42 render :edit
43 43 end
44 44 end
45 45
46 46 # DELETE /groups/1
47 47 def destroy
48 48 @group.destroy
49 49 redirect_to groups_url, notice: 'Group was successfully destroyed.'
50 50 end
51 51
52 52 def remove_user
53 53 user = User.find(params[:user_id])
54 54 @group.users.delete(user)
55 - redirect_to group_path(@group), notice: "User #{user.login} was removed from the group #{@group.name}"
55 + redirect_to group_path(@group), flash: {success: "User #{user.login} was removed from the group #{@group.name}"}
56 56 end
57 57
58 58 def add_user
59 59 user = User.find(params[:user_id])
60 - @group.users << user
61 - redirect_to group_path(@group), notice: "User #{user.login} was add to the group #{@group.name}"
60 + begin
61 + @group.users << user
62 + redirect_to group_path(@group), flash: { success: "User #{user.login} was add to the group #{@group.name}"}
63 + rescue => e
64 + redirect_to group_path(@group), alert: e.message
65 + end
62 66 end
63 67
64 68 def remove_problem
65 69 problem = Problem.find(params[:problem_id])
66 70 @group.problems.delete(problem)
67 - redirect_to group_path(@group), notice: "Problem #{problem.name} was removed from the group #{@group.name}"
71 + redirect_to group_path(@group), flash: {success: "Problem #{problem.name} was removed from the group #{@group.name}" }
68 72 end
69 73
70 74 def add_problem
71 75 problem = Problem.find(params[:problem_id])
72 - @group.problems << problem
73 - redirect_to group_path(@group), notice: "Problem #{problem.name} was add to the group #{@group.name}"
76 + begin
77 + @group.problems << problem
78 + redirect_to group_path(@group), flash: {success: "Problem #{problem.name} was add to the group #{@group.name}" }
79 + rescue => e
80 + redirect_to group_path(@group), alert: e.message
81 + end
74 82 end
75 83
76 84 private
77 85 # Use callbacks to share common setup or constraints between actions.
78 86 def set_group
79 87 @group = Group.find(params[:id])
80 88 end
81 89
82 90 # Only allow a trusted parameter "white list" through.
83 91 def group_params
84 92 params.require(:group).permit(:name, :description)
85 93 end
86 94 end
@@ -8,292 +8,302
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 168 @submissions = Submission.includes(:user).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 + ok = []
201 + failed = []
200 202 get_problems_from_params.each do |p|
201 - group.problems << p
203 + begin
204 + group.problems << p
205 + ok << p.full_name
206 + rescue => e
207 + failed << p.full_name
208 + end
202 209 end
210 + flash[:success] = "The following problems are added to the group #{group.name}: " + ok.join(', ') if ok.count > 0
211 + flash[:alert] = "The following problems are already in the group #{group.name}: " + failed.join(', ') if failed.count > 0
203 212 end
213 +
204 214 redirect_to :action => 'manage'
205 215 end
206 216
207 217 def import
208 218 @allow_test_pair_import = allow_test_pair_import?
209 219 end
210 220
211 221 def do_import
212 222 old_problem = Problem.find_by_name(params[:name])
213 223 if !allow_test_pair_import? and params.has_key? :import_to_db
214 224 params.delete :import_to_db
215 225 end
216 226 @problem, import_log = Problem.create_from_import_form_params(params,
217 227 old_problem)
218 228
219 229 if !@problem.errors.empty?
220 230 render :action => 'import' and return
221 231 end
222 232
223 233 if old_problem!=nil
224 234 flash[:notice] = "The test data has been replaced for problem #{@problem.name}"
225 235 end
226 236 @log = import_log
227 237 end
228 238
229 239 def remove_contest
230 240 problem = Problem.find(params[:id])
231 241 contest = Contest.find(params[:contest_id])
232 242 if problem!=nil and contest!=nil
233 243 problem.contests.delete(contest)
234 244 end
235 245 redirect_to :action => 'manage'
236 246 end
237 247
238 248 ##################################
239 249 protected
240 250
241 251 def allow_test_pair_import?
242 252 if defined? ALLOW_TEST_PAIR_IMPORT
243 253 return ALLOW_TEST_PAIR_IMPORT
244 254 else
245 255 return false
246 256 end
247 257 end
248 258
249 259 def change_date_added
250 260 problems = get_problems_from_params
251 261 year = params[:date_added][:year].to_i
252 262 month = params[:date_added][:month].to_i
253 263 day = params[:date_added][:day].to_i
254 264 date = Date.new(year,month,day)
255 265 problems.each do |p|
256 266 p.date_added = date
257 267 p.save
258 268 end
259 269 end
260 270
261 271 def add_to_contest
262 272 problems = get_problems_from_params
263 273 contest = Contest.find(params[:contest][:id])
264 274 if contest!=nil and contest.enabled
265 275 problems.each do |p|
266 276 p.contests << contest
267 277 end
268 278 end
269 279 end
270 280
271 281 def set_available(avail)
272 282 problems = get_problems_from_params
273 283 problems.each do |p|
274 284 p.available = avail
275 285 p.save
276 286 end
277 287 end
278 288
279 289 def get_problems_from_params
280 290 problems = []
281 291 params.keys.each do |k|
282 292 if k.index('prob-')==0
283 293 name, id, order = k.split('-')
284 294 problems << Problem.find(id)
285 295 end
286 296 end
287 297 problems
288 298 end
289 299
290 300 def get_problems_stat
291 301 end
292 302
293 303 private
294 304
295 305 def problem_params
296 306 params.require(:problem).permit(:name, :full_name, :full_score, :date_added, :available, :test_allowed,:output_only, :url, :description)
297 307 end
298 308
299 309 end
@@ -253,343 +253,354
253 253 flash[:notice] = 'User(s) ' + note.join(', ') +
254 254 " were successfully reassigned to #{contest.title}."
255 255 redirect_to :action => 'contests', :id =>contest.id
256 256 end
257 257
258 258 def add_to_contest
259 259 user = User.find(params[:id])
260 260 contest = Contest.find(params[:contest_id])
261 261 if user and contest
262 262 user.contests << contest
263 263 end
264 264 redirect_to :action => 'index'
265 265 end
266 266
267 267 def remove_from_contest
268 268 user = User.find(params[:id])
269 269 contest = Contest.find(params[:contest_id])
270 270 if user and contest
271 271 user.contests.delete(contest)
272 272 end
273 273 redirect_to :action => 'index'
274 274 end
275 275
276 276 def contest_management
277 277 end
278 278
279 279 def manage_contest
280 280 contest = Contest.find(params[:contest][:id])
281 281 if !contest
282 282 flash[:notice] = 'You did not choose the contest.'
283 283 redirect_to :action => 'contest_management' and return
284 284 end
285 285
286 286 operation = params[:operation]
287 287
288 288 if not ['add','remove','assign'].include? operation
289 289 flash[:notice] = 'You did not choose the operation to perform.'
290 290 redirect_to :action => 'contest_management' and return
291 291 end
292 292
293 293 lines = params[:login_list]
294 294 if !lines or lines.blank?
295 295 flash[:notice] = 'You entered an empty list.'
296 296 redirect_to :action => 'contest_management' and return
297 297 end
298 298
299 299 note = []
300 300 users = []
301 301 lines.split("\n").each do |line|
302 302 user = User.find_by_login(line.chomp)
303 303 if user
304 304 if operation=='add'
305 305 if ! user.contests.include? contest
306 306 user.contests << contest
307 307 end
308 308 elsif operation=='remove'
309 309 user.contests.delete(contest)
310 310 else
311 311 user.contests = [contest]
312 312 end
313 313
314 314 if params[:reset_timer]
315 315 user.contest_stat.forced_logout = true
316 316 user.contest_stat.reset_timer_and_save
317 317 end
318 318
319 319 if params[:notification_emails]
320 320 send_contest_update_notification_email(user, contest)
321 321 end
322 322
323 323 note << user.login
324 324 users << user
325 325 end
326 326 end
327 327
328 328 if params[:reset_timer]
329 329 logout_users(users)
330 330 end
331 331
332 332 flash[:notice] = 'User(s) ' + note.join(', ') +
333 333 ' were successfully modified. '
334 334 redirect_to :action => 'contest_management'
335 335 end
336 336
337 337 # admin management
338 338
339 339 def admin
340 340 @admins = User.all.find_all {|user| user.admin? }
341 341 end
342 342
343 343 def grant_admin
344 344 login = params[:login]
345 345 user = User.find_by_login(login)
346 346 if user!=nil
347 347 admin_role = Role.find_by_name('admin')
348 348 user.roles << admin_role
349 349 else
350 350 flash[:notice] = 'Unknown user'
351 351 end
352 352 flash[:notice] = 'User added as admins'
353 353 redirect_to :action => 'admin'
354 354 end
355 355
356 356 def revoke_admin
357 357 user = User.find(params[:id])
358 358 if user==nil
359 359 flash[:notice] = 'Unknown user'
360 360 redirect_to :action => 'admin' and return
361 361 elsif user.login == 'root'
362 362 flash[:notice] = 'You cannot revoke admisnistrator permission from root.'
363 363 redirect_to :action => 'admin' and return
364 364 end
365 365
366 366 admin_role = Role.find_by_name('admin')
367 367 user.roles.delete(admin_role)
368 368 flash[:notice] = 'User permission revoked'
369 369 redirect_to :action => 'admin'
370 370 end
371 371
372 372 # mass mailing
373 373
374 374 def mass_mailing
375 375 end
376 376
377 377 def bulk_mail
378 378 lines = params[:login_list]
379 379 if !lines or lines.blank?
380 380 flash[:notice] = 'You entered an empty list.'
381 381 redirect_to :action => 'mass_mailing' and return
382 382 end
383 383
384 384 mail_subject = params[:subject]
385 385 if !mail_subject or mail_subject.blank?
386 386 flash[:notice] = 'You entered an empty mail subject.'
387 387 redirect_to :action => 'mass_mailing' and return
388 388 end
389 389
390 390 mail_body = params[:email_body]
391 391 if !mail_body or mail_body.blank?
392 392 flash[:notice] = 'You entered an empty mail body.'
393 393 redirect_to :action => 'mass_mailing' and return
394 394 end
395 395
396 396 note = []
397 397 users = []
398 398 lines.split("\n").each do |line|
399 399 user = User.find_by_login(line.chomp)
400 400 if user
401 401 send_mail(user.email, mail_subject, mail_body)
402 402 note << user.login
403 403 end
404 404 end
405 405
406 406 flash[:notice] = 'User(s) ' + note.join(', ') +
407 407 ' were successfully modified. '
408 408 redirect_to :action => 'mass_mailing'
409 409 end
410 410
411 411 #bulk manage
412 412 def bulk_manage
413 413
414 414 begin
415 415 @users = User.where('(login REGEXP ?) OR (remark REGEXP ?)',params[:regex],params[:regex]) if params[:regex]
416 416 @users.count if @users #i don't know why I have to call count, but if I won't exception is not raised
417 417 rescue Exception
418 418 flash[:error] = 'Regular Expression is malformed'
419 419 @users = nil
420 420 end
421 421
422 422 if params[:commit]
423 423 @action = {}
424 424 @action[:set_enable] = params[:enabled]
425 425 @action[:enabled] = params[:enable] == "1"
426 426 @action[:gen_password] = params[:gen_password]
427 427 @action[:add_group] = params[:add_group]
428 428 @action[:group_name] = params[:group_name]
429 429 end
430 430
431 431 if params[:commit] == "Perform"
432 432 if @action[:set_enable]
433 433 @users.update_all(enabled: @action[:enabled])
434 434 end
435 435 if @action[:gen_password]
436 436 @users.each do |u|
437 437 password = random_password
438 438 u.password = password
439 439 u.password_confirmation = password
440 440 u.save
441 441 end
442 442 end
443 443 if @action[:add_group] and @action[:group_name]
444 444 @group = Group.find(@action[:group_name])
445 - @users.each { |user| @group.users << user }
445 + ok = []
446 + failed = []
447 + @users.each do |user|
448 + begin
449 + @group.users << user
450 + ok << user.login
451 + rescue => e
452 + failed << user.login
453 + end
454 + end
455 + flash[:success] = "The following users are added to the 'group #{@group.name}': " + ok.join(', ') if ok.count > 0
456 + flash[:alert] = "The following users are already in the 'group #{@group.name}': " + failed.join(', ') if failed.count > 0
446 457 end
447 458 end
448 459 end
449 460
450 461 protected
451 462
452 463 def random_password(length=5)
453 464 chars = 'abcdefghijkmnopqrstuvwxyz23456789'
454 465 newpass = ""
455 466 length.times { newpass << chars[rand(chars.size-1)] }
456 467 return newpass
457 468 end
458 469
459 470 def import_from_file(f)
460 471 data_hash = YAML.load(f)
461 472 @import_log = ""
462 473
463 474 country_data = data_hash[:countries]
464 475 site_data = data_hash[:sites]
465 476 user_data = data_hash[:users]
466 477
467 478 # import country
468 479 countries = {}
469 480 country_data.each_pair do |id,country|
470 481 c = Country.find_by_name(country[:name])
471 482 if c!=nil
472 483 countries[id] = c
473 484 @import_log << "Found #{country[:name]}\n"
474 485 else
475 486 countries[id] = Country.new(:name => country[:name])
476 487 countries[id].save
477 488 @import_log << "Created #{country[:name]}\n"
478 489 end
479 490 end
480 491
481 492 # import sites
482 493 sites = {}
483 494 site_data.each_pair do |id,site|
484 495 s = Site.find_by_name(site[:name])
485 496 if s!=nil
486 497 @import_log << "Found #{site[:name]}\n"
487 498 else
488 499 s = Site.new(:name => site[:name])
489 500 @import_log << "Created #{site[:name]}\n"
490 501 end
491 502 s.password = site[:password]
492 503 s.country = countries[site[:country_id]]
493 504 s.save
494 505 sites[id] = s
495 506 end
496 507
497 508 # import users
498 509 user_data.each_pair do |id,user|
499 510 u = User.find_by_login(user[:login])
500 511 if u!=nil
501 512 @import_log << "Found #{user[:login]}\n"
502 513 else
503 514 u = User.new(:login => user[:login])
504 515 @import_log << "Created #{user[:login]}\n"
505 516 end
506 517 u.full_name = user[:name]
507 518 u.password = user[:password]
508 519 u.country = countries[user[:country_id]]
509 520 u.site = sites[user[:site_id]]
510 521 u.activated = true
511 522 u.email = "empty-#{u.login}@none.com"
512 523 if not u.save
513 524 @import_log << "Errors\n"
514 525 u.errors.each { |attr,msg| @import_log << "#{attr} - #{msg}\n" }
515 526 end
516 527 end
517 528
518 529 end
519 530
520 531 def logout_users(users)
521 532 users.each do |user|
522 533 contest_stat = user.contest_stat(true)
523 534 if contest_stat and !contest_stat.forced_logout
524 535 contest_stat.forced_logout = true
525 536 contest_stat.save
526 537 end
527 538 end
528 539 end
529 540
530 541 def send_contest_update_notification_email(user, contest)
531 542 contest_title_name = GraderConfiguration['contest.name']
532 543 contest_name = contest.name
533 544 mail_subject = t('contest.notification.email_subject', {
534 545 :contest_title_name => contest_title_name,
535 546 :contest_name => contest_name })
536 547 mail_body = t('contest.notification.email_body', {
537 548 :full_name => user.full_name,
538 549 :contest_title_name => contest_title_name,
539 550 :contest_name => contest.name,
540 551 })
541 552
542 553 logger.info mail_body
543 554 send_mail(user.email, mail_subject, mail_body)
544 555 end
545 556
546 557 def find_contest_and_user_from_contest_id(id)
547 558 if id!='none'
548 559 @contest = Contest.find(id)
549 560 else
550 561 @contest = nil
551 562 end
552 563 if @contest
553 564 @users = @contest.users
554 565 else
555 566 @users = User.find_users_with_no_contest
556 567 end
557 568 return [@contest, @users]
558 569 end
559 570
560 571 def gen_csv_from_scorearray(scorearray,problem)
561 572 CSV.generate do |csv|
562 573 #add header
563 574 header = ['User','Name', 'Activated?', 'Logged in', 'Contest']
564 575 problem.each { |p| header << p.name }
565 576 header += ['Total','Passed']
566 577 csv << header
567 578 #add data
568 579 scorearray.each do |sc|
569 580 total = num_passed = 0
570 581 row = Array.new
571 582 sc.each_index do |i|
572 583 if i == 0
573 584 row << sc[i].login
574 585 row << sc[i].full_name
575 586 row << sc[i].activated
576 587 row << (sc[i].try(:contest_stat).try(:started_at).nil? ? 'no' : 'yes')
577 588 row << sc[i].contests.collect {|c| c.name}.join(', ')
578 589 else
579 590 row << sc[i][0]
580 591 total += sc[i][0]
581 592 num_passed += 1 if sc[i][1]
582 593 end
583 594 end
584 595 row << total
585 596 row << num_passed
586 597 csv << row
587 598 end
588 599 end
589 600 end
590 601
591 602 private
592 603 def user_params
593 604 params.require(:user).permit(:login,:password,:password_confirmation,:email, :alias, :full_name,:remark)
594 605 end
595 606 end
@@ -11,211 +11,211
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 88 now = Time.now.gmtime
89 89 st = ''
90 90 if (time.yday != now.yday) or
91 91 (time.year != now.year)
92 92 st = time.strftime("%x ")
93 93 end
94 94 st + time.strftime("%X")
95 95 end
96 96
97 97 def format_short_duration(duration)
98 98 return '' if duration==nil
99 99 d = duration.to_f
100 100 return Time.at(d).gmtime.strftime("%X")
101 101 end
102 102
103 103 def read_textfile(fname,max_size=2048)
104 104 begin
105 105 File.open(fname).read(max_size)
106 106 rescue
107 107 nil
108 108 end
109 109 end
110 110
111 111 def toggle_button(on,toggle_url,id, option={})
112 112 btn_size = option[:size] || 'btn-xs'
113 113 link_to (on ? "Yes" : "No"), toggle_url,
114 114 {class: "btn btn-block #{btn_size} btn-#{on ? 'success' : 'default'} ajax-toggle",
115 115 id: id,
116 116 data: {remote: true, method: 'get'}}
117 117 end
118 118
119 119 def get_ace_mode(language)
120 120 # return ace mode string from Language
121 121
122 122 case language.pretty_name
123 123 when 'Pascal'
124 124 'ace/mode/pascal'
125 125 when 'C++','C'
126 126 'ace/mode/c_cpp'
127 127 when 'Ruby'
128 128 'ace/mode/ruby'
129 129 when 'Python'
130 130 'ace/mode/python'
131 131 when 'Java'
132 132 'ace/mode/java'
133 133 else
134 134 'ace/mode/c_cpp'
135 135 end
136 136 end
137 137
138 138
139 139 def user_title_bar(user)
140 140 header = ''
141 141 time_left = ''
142 142
143 143 #
144 144 # if the contest is over
145 145 if GraderConfiguration.time_limit_mode?
146 146 if user.contest_finished?
147 147 header = <<CONTEST_OVER
148 148 <tr><td colspan="2" align="center">
149 149 <span class="contest-over-msg">THE CONTEST IS OVER</span>
150 150 </td></tr>
151 151 CONTEST_OVER
152 152 end
153 153 if !user.contest_started?
154 154 time_left = "&nbsp;&nbsp;" + (t 'title_bar.contest_not_started')
155 155 else
156 156 time_left = "&nbsp;&nbsp;" + (t 'title_bar.remaining_time') +
157 157 " #{format_short_duration(user.contest_time_left)}"
158 158 end
159 159 end
160 160
161 161 #
162 162 # if the contest is in the anaysis mode
163 163 if GraderConfiguration.analysis_mode?
164 164 header = <<ANALYSISMODE
165 165 <tr><td colspan="2" align="center">
166 166 <span class="contest-over-msg">ANALYSIS MODE</span>
167 167 </td></tr>
168 168 ANALYSISMODE
169 169 end
170 170
171 171 contest_name = GraderConfiguration['contest.name']
172 172
173 173 #
174 174 # build real title bar
175 175 result = <<TITLEBAR
176 176 <div class="title">
177 177 <table>
178 178 #{header}
179 179 <tr>
180 180 <td class="left-col">
181 181 #{user.full_name}<br/>
182 182 #{t 'title_bar.current_time'} #{format_short_time(Time.zone.now)}
183 183 #{time_left}
184 184 <br/>
185 185 </td>
186 186 <td class="right-col">#{contest_name}</td>
187 187 </tr>
188 188 </table>
189 189 </div>
190 190 TITLEBAR
191 191 result.html_safe
192 192 end
193 193
194 194 def markdown(text)
195 195 markdown = RDiscount.new(text)
196 196 markdown.to_html.html_safe
197 197 end
198 198
199 199
200 200 BOOTSTRAP_FLASH_MSG = {
201 201 success: 'alert-success',
202 202 error: 'alert-danger',
203 - alert: 'alert-block',
203 + alert: 'alert-danger',
204 204 notice: 'alert-info'
205 205 }
206 206
207 207 def bootstrap_class_for(flash_type)
208 208 BOOTSTRAP_FLASH_MSG.fetch(flash_type.to_sym, flash_type.to_s)
209 209 end
210 210
211 211 def flash_messages
212 212 flash.each do |msg_type, message|
213 213 concat(content_tag(:div, message, class: "alert #{bootstrap_class_for(msg_type)} fade in") do
214 214 concat content_tag(:button, 'x', class: "close", data: { dismiss: 'alert' })
215 215 concat message
216 216 end)
217 217 end
218 218 nil
219 219 end
220 220
221 221 end
@@ -1,5 +1,13
1 1 class Group < ActiveRecord::Base
2 - has_and_belongs_to_many :problems
3 - has_and_belongs_to_many :users
2 + has_many :groups_problems, class_name: GroupProblem
3 + has_many :problems, :through => :groups_problems
4 +
5 + has_many :groups_users, class_name: GroupUser
6 + has_many :users, :through => :groups_users
7 +
8 + #has_and_belongs_to_many :problems
9 + #has_and_belongs_to_many :users
10 +
11 +
4 12 end
5 13
@@ -1,137 +1,141
1 1 class Problem < ActiveRecord::Base
2 2
3 3 belongs_to :description
4 4 has_and_belongs_to_many :contests, :uniq => true
5 - has_and_belongs_to_many :groups
5 +
6 + #has_and_belongs_to_many :groups
7 + has_many :groups_problems, class_name: GroupProblem
8 + has_many :groups, :through => :groups_problems
9 +
6 10 has_many :test_pairs, :dependent => :delete_all
7 11 has_many :testcases, :dependent => :destroy
8 12
9 13 validates_presence_of :name
10 14 validates_format_of :name, :with => /\A\w+\z/
11 15 validates_presence_of :full_name
12 16
13 17 scope :available, -> { where(available: true) }
14 18
15 19 DEFAULT_TIME_LIMIT = 1
16 20 DEFAULT_MEMORY_LIMIT = 32
17 21
18 22 def self.available_problems
19 23 available.order(date_added: :desc).order(:name)
20 24 #Problem.available.all(:order => "date_added DESC, name ASC")
21 25 end
22 26
23 27 def self.create_from_import_form_params(params, old_problem=nil)
24 28 org_problem = old_problem || Problem.new
25 29 import_params, problem = Problem.extract_params_and_check(params,
26 30 org_problem)
27 31
28 32 if !problem.errors.empty?
29 33 return problem, 'Error importing'
30 34 end
31 35
32 36 problem.full_score = 100
33 37 problem.date_added = Time.new
34 38 problem.test_allowed = true
35 39 problem.output_only = false
36 40 problem.available = false
37 41
38 42 if not problem.save
39 43 return problem, 'Error importing'
40 44 end
41 45
42 46 import_to_db = params.has_key? :import_to_db
43 47
44 48 importer = TestdataImporter.new(problem)
45 49
46 50 if not importer.import_from_file(import_params[:file],
47 51 import_params[:time_limit],
48 52 import_params[:memory_limit],
49 53 import_params[:checker_name],
50 54 import_to_db)
51 55 problem.errors.add(:base,'Import error.')
52 56 end
53 57
54 58 return problem, importer.log_msg
55 59 end
56 60
57 61 def self.download_file_basedir
58 62 return "#{Rails.root}/data/tasks"
59 63 end
60 64
61 65 def get_submission_stat
62 66 result = Hash.new
63 67 #total number of submission
64 68 result[:total_sub] = Submission.where(problem_id: self.id).count
65 69 result[:attempted_user] = Submission.where(problem_id: self.id).group(:user_id)
66 70 result[:pass] = Submission.where(problem_id: self.id).where("points >= ?",self.full_score).count
67 71 return result
68 72 end
69 73
70 74 def long_name
71 75 "[#{name}] #{full_name}"
72 76 end
73 77
74 78 protected
75 79
76 80 def self.to_i_or_default(st, default)
77 81 if st!=''
78 82 result = st.to_i
79 83 end
80 84 result ||= default
81 85 end
82 86
83 87 def self.to_f_or_default(st, default)
84 88 if st!=''
85 89 result = st.to_f
86 90 end
87 91 result ||= default
88 92 end
89 93
90 94 def self.extract_params_and_check(params, problem)
91 95 time_limit = Problem.to_f_or_default(params[:time_limit],
92 96 DEFAULT_TIME_LIMIT)
93 97 memory_limit = Problem.to_i_or_default(params[:memory_limit],
94 98 DEFAULT_MEMORY_LIMIT)
95 99
96 100 if time_limit<=0 or time_limit >60
97 101 problem.errors.add(:base,'Time limit out of range.')
98 102 end
99 103
100 104 if memory_limit==0 and params[:memory_limit]!='0'
101 105 problem.errors.add(:base,'Memory limit format errors.')
102 106 elsif memory_limit<=0 or memory_limit >512
103 107 problem.errors.add(:base,'Memory limit out of range.')
104 108 end
105 109
106 110 if params[:file]==nil or params[:file]==''
107 111 problem.errors.add(:base,'No testdata file.')
108 112 end
109 113
110 114 checker_name = 'text'
111 115 if ['text','float'].include? params[:checker]
112 116 checker_name = params[:checker]
113 117 end
114 118
115 119 file = params[:file]
116 120
117 121 if !problem.errors.empty?
118 122 return nil, problem
119 123 end
120 124
121 125 problem.name = params[:name]
122 126 if params[:full_name]!=''
123 127 problem.full_name = params[:full_name]
124 128 else
125 129 problem.full_name = params[:name]
126 130 end
127 131
128 132 return [{
129 133 :time_limit => time_limit,
130 134 :memory_limit => memory_limit,
131 135 :file => file,
132 136 :checker_name => checker_name
133 137 },
134 138 problem]
135 139 end
136 140
137 141 end
@@ -1,202 +1,205
1 1 require 'digest/sha1'
2 2 require 'net/pop'
3 3 require 'net/https'
4 4 require 'net/http'
5 5 require 'json'
6 6
7 7 class User < ActiveRecord::Base
8 8
9 9 has_and_belongs_to_many :roles
10 - has_and_belongs_to_many :groups
10 +
11 + #has_and_belongs_to_many :groups
12 + has_many :groups_users, class_name: GroupUser
13 + has_many :groups, :through => :groups_users
11 14
12 15 has_many :test_requests, -> {order(submitted_at: DESC)}
13 16
14 17 has_many :messages, -> { order(created_at: DESC) },
15 18 :class_name => "Message",
16 19 :foreign_key => "sender_id"
17 20
18 21 has_many :replied_messages, -> { order(created_at: DESC) },
19 22 :class_name => "Message",
20 23 :foreign_key => "receiver_id"
21 24
22 25 has_one :contest_stat, :class_name => "UserContestStat", :dependent => :destroy
23 26
24 27 belongs_to :site
25 28 belongs_to :country
26 29
27 30 has_and_belongs_to_many :contests, -> { order(:name); uniq}
28 31
29 32 scope :activated_users, -> {where activated: true}
30 33
31 34 validates_presence_of :login
32 35 validates_uniqueness_of :login
33 36 validates_format_of :login, :with => /\A[\_A-Za-z0-9]+\z/
34 37 validates_length_of :login, :within => 3..30
35 38
36 39 validates_presence_of :full_name
37 40 validates_length_of :full_name, :minimum => 1
38 41
39 42 validates_presence_of :password, :if => :password_required?
40 43 validates_length_of :password, :within => 4..20, :if => :password_required?
41 44 validates_confirmation_of :password, :if => :password_required?
42 45
43 46 validates_format_of :email,
44 47 :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i,
45 48 :if => :email_validation?
46 49 validate :uniqueness_of_email_from_activated_users,
47 50 :if => :email_validation?
48 51 validate :enough_time_interval_between_same_email_registrations,
49 52 :if => :email_validation?
50 53
51 54 # these are for ytopc
52 55 # disable for now
53 56 #validates_presence_of :province
54 57
55 58 attr_accessor :password
56 59
57 60 before_save :encrypt_new_password
58 61 before_save :assign_default_site
59 62 before_save :assign_default_contest
60 63
61 64 # this is for will_paginate
62 65 cattr_reader :per_page
63 66 @@per_page = 50
64 67
65 68 def self.authenticate(login, password)
66 69 user = find_by_login(login)
67 70 if user
68 71 return user if user.authenticated?(password)
69 72 if user.authenticated_by_cucas?(password) or user.authenticated_by_pop3?(password)
70 73 user.password = password
71 74 user.save
72 75 return user
73 76 end
74 77 end
75 78 end
76 79
77 80 def authenticated?(password)
78 81 if self.activated
79 82 hashed_password == User.encrypt(password,self.salt)
80 83 else
81 84 false
82 85 end
83 86 end
84 87
85 88 def authenticated_by_pop3?(password)
86 89 Net::POP3.enable_ssl
87 90 pop = Net::POP3.new('pops.it.chula.ac.th')
88 91 authen = true
89 92 begin
90 93 pop.start(login, password)
91 94 pop.finish
92 95 return true
93 96 rescue
94 97 return false
95 98 end
96 99 end
97 100
98 101 def authenticated_by_cucas?(password)
99 102 url = URI.parse('https://www.cas.chula.ac.th/cas/api/?q=studentAuthenticate')
100 103 appid = '41508763e340d5858c00f8c1a0f5a2bb'
101 104 appsecret ='d9cbb5863091dbe186fded85722a1e31'
102 105 post_args = {
103 106 'appid' => appid,
104 107 'appsecret' => appsecret,
105 108 'username' => login,
106 109 'password' => password
107 110 }
108 111
109 112 #simple call
110 113 begin
111 114 http = Net::HTTP.new('www.cas.chula.ac.th', 443)
112 115 http.use_ssl = true
113 116 http.verify_mode = OpenSSL::SSL::VERIFY_NONE
114 117 result = [ ]
115 118 http.start do |http|
116 119 req = Net::HTTP::Post.new('/cas/api/?q=studentAuthenticate')
117 120 param = "appid=#{appid}&appsecret=#{appsecret}&username=#{login}&password=#{password}"
118 121 resp = http.request(req,param)
119 122 result = JSON.parse resp.body
120 123 end
121 124 return true if result["type"] == "beanStudent"
122 125 rescue => e
123 126 return false
124 127 end
125 128 return false
126 129 end
127 130
128 131 def admin?
129 132 self.roles.detect {|r| r.name == 'admin' }
130 133 end
131 134
132 135 def email_for_editing
133 136 if self.email==nil
134 137 "(unknown)"
135 138 elsif self.email==''
136 139 "(blank)"
137 140 else
138 141 self.email
139 142 end
140 143 end
141 144
142 145 def email_for_editing=(e)
143 146 self.email=e
144 147 end
145 148
146 149 def alias_for_editing
147 150 if self.alias==nil
148 151 "(unknown)"
149 152 elsif self.alias==''
150 153 "(blank)"
151 154 else
152 155 self.alias
153 156 end
154 157 end
155 158
156 159 def alias_for_editing=(e)
157 160 self.alias=e
158 161 end
159 162
160 163 def activation_key
161 164 if self.hashed_password==nil
162 165 encrypt_new_password
163 166 end
164 167 Digest::SHA1.hexdigest(self.hashed_password)[0..7]
165 168 end
166 169
167 170 def verify_activation_key(key)
168 171 key == activation_key
169 172 end
170 173
171 174 def self.random_password(length=5)
172 175 chars = 'abcdefghjkmnopqrstuvwxyz'
173 176 password = ''
174 177 length.times { password << chars[rand(chars.length - 1)] }
175 178 password
176 179 end
177 180
178 181 def self.find_non_admin_with_prefix(prefix='')
179 182 users = User.all
180 183 return users.find_all { |u| !(u.admin?) and u.login.index(prefix)==0 }
181 184 end
182 185
183 186 # Contest information
184 187
185 188 def self.find_users_with_no_contest()
186 189 users = User.all
187 190 return users.find_all { |u| u.contests.length == 0 }
188 191 end
189 192
190 193
191 194 def contest_time_left
192 195 if GraderConfiguration.contest_mode?
193 196 return nil if site==nil
194 197 return site.time_left
195 198 elsif GraderConfiguration.indv_contest_mode?
196 199 time_limit = GraderConfiguration.contest_time_limit
197 200 if time_limit == nil
198 201 return nil
199 202 end
200 203 if contest_stat==nil or contest_stat.started_at==nil
201 204 return (Time.now.gmtime + time_limit) - Time.now.gmtime
202 205 else
@@ -1,54 +1,54
1 1 - content_for :head do
2 2 = stylesheet_link_tag 'problems'
3 - %h1 Listing problems
3 + %h1 Problems
4 4 %p
5 - = link_to 'New problem', new_problem_path, class: 'btn btn-default btn-sm'
6 - = link_to 'Manage problems', { action: 'manage'}, class: 'btn btn-default btn-sm'
7 - = link_to 'Import problems', {:action => 'import'}, class: 'btn btn-default btn-sm'
5 + = link_to 'Import problems', {:action => 'import'}, class: 'btn btn-success btn-sm'
6 + = link_to 'New problem', new_problem_path, class: 'btn btn-success btn-sm'
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 24 %th Date added
25 25 %th.text-center
26 26 Avail?
27 27 %sup{class: 'text-primary',data: {toggle: 'tooltip'}, title: 'Let user submits to this problem?' } [?]
28 28 %th.text-center
29 29 View Data?
30 30 %sup{class: 'text-primary',data: {toggle: 'tooltip'}, title: 'Let user view the testcase of this problem?' } [?]
31 31 %th.text-center
32 32 Test?
33 33 %sup{class: 'text-primary',data: {toggle: 'tooltip'}, title: 'Let user uses test interface on this problem?' } [?]
34 34 - if GraderConfiguration.multicontests?
35 35 %th Contests
36 36 - for problem in @problems
37 37 %tr{:class => "#{(problem.available) ? "success" : "danger"}", :id => "prob-#{problem.id}", :name => "prob-#{problem.id}"}
38 38 - @problem=problem
39 39 %td= problem.name #in_place_editor_field :problem, :name, {}, :rows=>1
40 40 %td= problem.full_name #in_place_editor_field :problem, :full_name, {}, :rows=>1
41 41 %td.text-right= problem.full_score #in_place_editor_field :problem, :full_score, {}, :rows=>1
42 42 %td= problem.date_added
43 43 %td= toggle_button(@problem.available?, toggle_problem_path(@problem), "problem-avail-#{@problem.id}")
44 44 %td= toggle_button(@problem.view_testcase?, toggle_view_testcase_problem_path(@problem), "problem-view-testcase-#{@problem.id}")
45 45 %td= toggle_button(@problem.test_allowed?, toggle_test_problem_path(@problem), "problem-test-#{@problem.id}")
46 46 - if GraderConfiguration.multicontests?
47 47 %td
48 48 = problem.contests.collect { |c| c.name }.join(', ')
49 49 %td= link_to 'Stat', {:action => 'stat', :id => problem.id}, class: 'btn btn-info btn-xs btn-block'
50 50 %td= link_to 'Show', {:action => 'show', :id => problem}, class: 'btn btn-info btn-xs btn-block'
51 51 %td= link_to 'Edit', {:action => 'edit', :id => problem}, class: 'btn btn-info btn-xs btn-block'
52 52 %td= link_to 'Destroy', { :action => 'destroy', :id => problem }, :confirm => 'Are you sure?', :method => :delete, class: 'btn btn-danger btn-xs btn-block'
53 53 %br/
54 54 = link_to '[New problem]', :action => 'new'
@@ -1,90 +1,94
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 - %p= link_to '[Back to problem list]', :action => 'list'
39 + %p= link_to '[Back to problem list]', problems_path
40 40
41 41 = form_tag :action=>'do_manage' do
42 - .submitbox.panel
43 - What do you want to do to the selected problem?
44 - %br/
45 - (You can shift-click to select a range of problems)
46 - %ul
47 - %li
48 - Change date added to
49 - = select_date Date.current, :prefix => 'date_added'
50 - &nbsp;&nbsp;&nbsp;
51 - = submit_tag 'Change', :name => 'change_date_added'
52 - %li
53 - Set available to
54 - = submit_tag 'True', :name => 'enable_problem'
55 - = submit_tag 'False', :name => 'disable_problem'
42 + .panel.panel-primary
43 + .panel-heading
44 + Action
45 + .panel-body
46 + .submit-box
47 + What do you want to do to the selected problem?
48 + %br/
49 + (You can shift-click to select a range of problems)
50 + %ul
51 + %li
52 + Change date added to
53 + = select_date Date.current, :prefix => 'date_added'
54 + &nbsp;&nbsp;&nbsp;
55 + = submit_tag 'Change', :name => 'change_date_added', class: 'btn btn-default'
56 + %li
57 + Set available to
58 + = submit_tag 'True', :name => 'enable_problem', class: 'btn btn-default'
59 + = submit_tag 'False', :name => 'disable_problem', class: 'btn btn-default'
56 60
57 - - if GraderConfiguration.multicontests?
58 - %li
59 - Add to
60 - = select("contest","id",Contest.all.collect {|c| [c.title, c.id]})
61 - = submit_tag 'Add', :name => 'add_to_contest'
62 - %li
63 - Add problems to group
64 - = select_tag "group_id", options_from_collection_for_select( Group.all, 'id','name',params[:group_name]), id: 'group_name',class: 'select2'
65 - = submit_tag 'Add', name: 'add_group'
61 + - if GraderConfiguration.multicontests?
62 + %li
63 + Add to
64 + = select("contest","id",Contest.all.collect {|c| [c.title, c.id]})
65 + = submit_tag 'Add', :name => 'add_to_contest', class: 'btn btn-default'
66 + %li
67 + Add problems to group
68 + = select_tag "group_id", options_from_collection_for_select( Group.all, 'id','name',params[:group_name]), id: 'group_name',class: 'select2'
69 + = submit_tag 'Add', name: 'add_group', class: 'btn btn-default'
66 70
67 71
68 72 %table.table.table-hover
69 73 %tr{style: "text-align: left;"}
70 74 %th= check_box_tag 'select_all'
71 75 %th Name
72 76 %th Full name
73 77 %th Available
74 78 %th Date added
75 79 - if GraderConfiguration.multicontests?
76 80 %th Contests
77 81
78 82 - num = 0
79 83 - for problem in @problems
80 84 - num += 1
81 85 %tr{:id => "row-prob-#{problem.id}", :name=> "prob-#{problem.id}"}
82 86 %td= check_box_tag "prob-#{problem.id}-#{num}"
83 87 %td= problem.name
84 88 %td= problem.full_name
85 89 %td= problem.available
86 90 %td= problem.date_added
87 91 - if GraderConfiguration.multicontests?
88 92 %td
89 93 - problem.contests.each do |contest|
90 94 = "(#{contest.name} [#{link_to 'x', :action => 'remove_contest', :id => problem.id, :contest_id => contest.id }])"
You need to be logged in to leave comments. Login now