Description:
allow ta to set model submission
Commit status:
[Not Reviewed]
References:
Comments:
0 Commit comments 0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
Add another comment

r856:9a927c70d5aa - - 4 files changed: 13 inserted, 10 deleted

@@ -1,501 +1,501
1 require 'digest/sha1'
1 require 'digest/sha1'
2 require 'net/pop'
2 require 'net/pop'
3 require 'net/https'
3 require 'net/https'
4 require 'net/http'
4 require 'net/http'
5 require 'json'
5 require 'json'
6
6
7 class User < ActiveRecord::Base
7 class User < ActiveRecord::Base
8
8
9 has_and_belongs_to_many :roles
9 has_and_belongs_to_many :roles
10
10
11 #has_and_belongs_to_many :groups
11 #has_and_belongs_to_many :groups
12 has_many :groups_users, class_name: 'GroupUser'
12 has_many :groups_users, class_name: 'GroupUser'
13 has_many :groups, :through => :groups_users
13 has_many :groups, :through => :groups_users
14
14
15 has_many :test_requests, -> {order(submitted_at: :desc)}
15 has_many :test_requests, -> {order(submitted_at: :desc)}
16
16
17 has_many :messages, -> { order(created_at: :desc) },
17 has_many :messages, -> { order(created_at: :desc) },
18 :class_name => "Message",
18 :class_name => "Message",
19 :foreign_key => "sender_id"
19 :foreign_key => "sender_id"
20
20
21 has_many :replied_messages, -> { order(created_at: :desc) },
21 has_many :replied_messages, -> { order(created_at: :desc) },
22 :class_name => "Message",
22 :class_name => "Message",
23 :foreign_key => "receiver_id"
23 :foreign_key => "receiver_id"
24
24
25 has_many :logins
25 has_many :logins
26
26
27 has_one :contest_stat, :class_name => "UserContestStat", :dependent => :destroy
27 has_one :contest_stat, :class_name => "UserContestStat", :dependent => :destroy
28
28
29 belongs_to :site
29 belongs_to :site
30 belongs_to :country
30 belongs_to :country
31
31
32 has_and_belongs_to_many :contests, -> { order(:name)}
32 has_and_belongs_to_many :contests, -> { order(:name)}
33
33
34 scope :activated_users, -> {where activated: true}
34 scope :activated_users, -> {where activated: true}
35
35
36 validates_presence_of :login
36 validates_presence_of :login
37 validates_uniqueness_of :login
37 validates_uniqueness_of :login
38 validates_format_of :login, :with => /\A[\_A-Za-z0-9]+\z/
38 validates_format_of :login, :with => /\A[\_A-Za-z0-9]+\z/
39 validates_length_of :login, :within => 3..30
39 validates_length_of :login, :within => 3..30
40
40
41 validates_presence_of :full_name
41 validates_presence_of :full_name
42 validates_length_of :full_name, :minimum => 1
42 validates_length_of :full_name, :minimum => 1
43
43
44 validates_presence_of :password, :if => :password_required?
44 validates_presence_of :password, :if => :password_required?
45 validates_length_of :password, :within => 4..50, :if => :password_required?
45 validates_length_of :password, :within => 4..50, :if => :password_required?
46 validates_confirmation_of :password, :if => :password_required?
46 validates_confirmation_of :password, :if => :password_required?
47
47
48 validates_format_of :email,
48 validates_format_of :email,
49 :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i,
49 :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i,
50 :if => :email_validation?
50 :if => :email_validation?
51 validate :uniqueness_of_email_from_activated_users,
51 validate :uniqueness_of_email_from_activated_users,
52 :if => :email_validation?
52 :if => :email_validation?
53 validate :enough_time_interval_between_same_email_registrations,
53 validate :enough_time_interval_between_same_email_registrations,
54 :if => :email_validation?
54 :if => :email_validation?
55
55
56 # these are for ytopc
56 # these are for ytopc
57 # disable for now
57 # disable for now
58 #validates_presence_of :province
58 #validates_presence_of :province
59
59
60 attr_accessor :password
60 attr_accessor :password
61
61
62 before_save :encrypt_new_password
62 before_save :encrypt_new_password
63 before_save :assign_default_site
63 before_save :assign_default_site
64 before_save :assign_default_contest
64 before_save :assign_default_contest
65
65
66 # this is for will_paginate
66 # this is for will_paginate
67 cattr_reader :per_page
67 cattr_reader :per_page
68 @@per_page = 50
68 @@per_page = 50
69
69
70 def self.authenticate(login, password)
70 def self.authenticate(login, password)
71 user = find_by_login(login)
71 user = find_by_login(login)
72 if user
72 if user
73 return user if user.authenticated?(password)
73 return user if user.authenticated?(password)
74 if user.authenticated_by_cucas?(password)
74 if user.authenticated_by_cucas?(password)
75 user.password = password
75 user.password = password
76 user.save
76 user.save
77 return user
77 return user
78 end
78 end
79 end
79 end
80 end
80 end
81
81
82
82
83 def authenticated?(password)
83 def authenticated?(password)
84 if self.activated
84 if self.activated
85 hashed_password == User.encrypt(password,self.salt)
85 hashed_password == User.encrypt(password,self.salt)
86 else
86 else
87 false
87 false
88 end
88 end
89 end
89 end
90
90
91 def login_with_name
91 def login_with_name
92 "[#{login}] #{full_name}"
92 "[#{login}] #{full_name}"
93 end
93 end
94
94
95 def authenticated_by_cucas?(password)
95 def authenticated_by_cucas?(password)
96 url = URI.parse('https://www.cas.chula.ac.th/cas/api/?q=studentAuthenticate')
96 url = URI.parse('https://www.cas.chula.ac.th/cas/api/?q=studentAuthenticate')
97 appid = '41508763e340d5858c00f8c1a0f5a2bb'
97 appid = '41508763e340d5858c00f8c1a0f5a2bb'
98 appsecret ='d9cbb5863091dbe186fded85722a1e31'
98 appsecret ='d9cbb5863091dbe186fded85722a1e31'
99 post_args = {
99 post_args = {
100 'appid' => appid,
100 'appid' => appid,
101 'appsecret' => appsecret,
101 'appsecret' => appsecret,
102 'username' => login,
102 'username' => login,
103 'password' => password
103 'password' => password
104 }
104 }
105
105
106 #simple call
106 #simple call
107 begin
107 begin
108 http = Net::HTTP.new('www.cas.chula.ac.th', 443)
108 http = Net::HTTP.new('www.cas.chula.ac.th', 443)
109 http.use_ssl = true
109 http.use_ssl = true
110 http.verify_mode = OpenSSL::SSL::VERIFY_NONE
110 http.verify_mode = OpenSSL::SSL::VERIFY_NONE
111 result = [ ]
111 result = [ ]
112 http.start do |http|
112 http.start do |http|
113 req = Net::HTTP::Post.new('/cas/api/?q=studentAuthenticate')
113 req = Net::HTTP::Post.new('/cas/api/?q=studentAuthenticate')
114 #req = Net::HTTP::Post.new('/appX/prod/?q=studentAuthenticate')
114 #req = Net::HTTP::Post.new('/appX/prod/?q=studentAuthenticate')
115 #req = Net::HTTP::Post.new('/app2/prod/api/?q=studentAuthenticate')
115 #req = Net::HTTP::Post.new('/app2/prod/api/?q=studentAuthenticate')
116 param = "appid=#{appid}&appsecret=#{appsecret}&username=#{login}&password=#{password}"
116 param = "appid=#{appid}&appsecret=#{appsecret}&username=#{login}&password=#{password}"
117 resp = http.request(req,param)
117 resp = http.request(req,param)
118 result = JSON.parse resp.body
118 result = JSON.parse resp.body
119 puts result
119 puts result
120 end
120 end
121 return true if result["type"] == "beanStudent"
121 return true if result["type"] == "beanStudent"
122 rescue => e
122 rescue => e
123 puts e
123 puts e
124 puts e.message
124 puts e.message
125 return false
125 return false
126 end
126 end
127 return false
127 return false
128 end
128 end
129
129
130 def admin?
130 def admin?
131 has_role?('admin')
131 has_role?('admin')
132 end
132 end
133
133
134 def has_role?(role)
134 def has_role?(role)
135 - self.roles.where(name: role).count > 0
135 + self.roles.where(name: [role,'admin']).count > 0
136 end
136 end
137
137
138 def email_for_editing
138 def email_for_editing
139 if self.email==nil
139 if self.email==nil
140 "(unknown)"
140 "(unknown)"
141 elsif self.email==''
141 elsif self.email==''
142 "(blank)"
142 "(blank)"
143 else
143 else
144 self.email
144 self.email
145 end
145 end
146 end
146 end
147
147
148 def email_for_editing=(e)
148 def email_for_editing=(e)
149 self.email=e
149 self.email=e
150 end
150 end
151
151
152 def alias_for_editing
152 def alias_for_editing
153 if self.alias==nil
153 if self.alias==nil
154 "(unknown)"
154 "(unknown)"
155 elsif self.alias==''
155 elsif self.alias==''
156 "(blank)"
156 "(blank)"
157 else
157 else
158 self.alias
158 self.alias
159 end
159 end
160 end
160 end
161
161
162 def alias_for_editing=(e)
162 def alias_for_editing=(e)
163 self.alias=e
163 self.alias=e
164 end
164 end
165
165
166 def activation_key
166 def activation_key
167 if self.hashed_password==nil
167 if self.hashed_password==nil
168 encrypt_new_password
168 encrypt_new_password
169 end
169 end
170 Digest::SHA1.hexdigest(self.hashed_password)[0..7]
170 Digest::SHA1.hexdigest(self.hashed_password)[0..7]
171 end
171 end
172
172
173 def verify_activation_key(key)
173 def verify_activation_key(key)
174 key == activation_key
174 key == activation_key
175 end
175 end
176
176
177 def self.random_password(length=5)
177 def self.random_password(length=5)
178 chars = 'abcdefghjkmnopqrstuvwxyz'
178 chars = 'abcdefghjkmnopqrstuvwxyz'
179 password = ''
179 password = ''
180 length.times { password << chars[rand(chars.length - 1)] }
180 length.times { password << chars[rand(chars.length - 1)] }
181 password
181 password
182 end
182 end
183
183
184 # Contest information
184 # Contest information
185
185
186 def self.find_users_with_no_contest()
186 def self.find_users_with_no_contest()
187 users = User.all
187 users = User.all
188 return users.find_all { |u| u.contests.length == 0 }
188 return users.find_all { |u| u.contests.length == 0 }
189 end
189 end
190
190
191
191
192 def contest_time_left
192 def contest_time_left
193 if GraderConfiguration.contest_mode?
193 if GraderConfiguration.contest_mode?
194 return nil if site==nil
194 return nil if site==nil
195 return site.time_left
195 return site.time_left
196 elsif GraderConfiguration.indv_contest_mode?
196 elsif GraderConfiguration.indv_contest_mode?
197 time_limit = GraderConfiguration.contest_time_limit
197 time_limit = GraderConfiguration.contest_time_limit
198 if time_limit == nil
198 if time_limit == nil
199 return nil
199 return nil
200 end
200 end
201 if contest_stat==nil or contest_stat.started_at==nil
201 if contest_stat==nil or contest_stat.started_at==nil
202 return (Time.now.gmtime + time_limit) - Time.now.gmtime
202 return (Time.now.gmtime + time_limit) - Time.now.gmtime
203 else
203 else
204 finish_time = contest_stat.started_at + time_limit
204 finish_time = contest_stat.started_at + time_limit
205 current_time = Time.now.gmtime
205 current_time = Time.now.gmtime
206 if current_time > finish_time
206 if current_time > finish_time
207 return 0
207 return 0
208 else
208 else
209 return finish_time - current_time
209 return finish_time - current_time
210 end
210 end
211 end
211 end
212 else
212 else
213 return nil
213 return nil
214 end
214 end
215 end
215 end
216
216
217 def contest_finished?
217 def contest_finished?
218 if GraderConfiguration.contest_mode?
218 if GraderConfiguration.contest_mode?
219 return false if site==nil
219 return false if site==nil
220 return site.finished?
220 return site.finished?
221 elsif GraderConfiguration.indv_contest_mode?
221 elsif GraderConfiguration.indv_contest_mode?
222 return false if self.contest_stat==nil
222 return false if self.contest_stat==nil
223 return contest_time_left == 0
223 return contest_time_left == 0
224 else
224 else
225 return false
225 return false
226 end
226 end
227 end
227 end
228
228
229 def contest_started?
229 def contest_started?
230 if GraderConfiguration.indv_contest_mode?
230 if GraderConfiguration.indv_contest_mode?
231 stat = self.contest_stat
231 stat = self.contest_stat
232 return ((stat != nil) and (stat.started_at != nil))
232 return ((stat != nil) and (stat.started_at != nil))
233 elsif GraderConfiguration.contest_mode?
233 elsif GraderConfiguration.contest_mode?
234 return true if site==nil
234 return true if site==nil
235 return site.started
235 return site.started
236 else
236 else
237 return true
237 return true
238 end
238 end
239 end
239 end
240
240
241 def update_start_time
241 def update_start_time
242 stat = self.contest_stat
242 stat = self.contest_stat
243 if stat.nil? or stat.started_at.nil?
243 if stat.nil? or stat.started_at.nil?
244 stat ||= UserContestStat.new(:user => self)
244 stat ||= UserContestStat.new(:user => self)
245 stat.started_at = Time.now.gmtime
245 stat.started_at = Time.now.gmtime
246 stat.save
246 stat.save
247 end
247 end
248 end
248 end
249
249
250 def problem_in_user_contests?(problem)
250 def problem_in_user_contests?(problem)
251 problem_contests = problem.contests.all
251 problem_contests = problem.contests.all
252
252
253 if problem_contests.length == 0 # this is public contest
253 if problem_contests.length == 0 # this is public contest
254 return true
254 return true
255 end
255 end
256
256
257 contests.each do |contest|
257 contests.each do |contest|
258 if problem_contests.find {|c| c.id == contest.id }
258 if problem_contests.find {|c| c.id == contest.id }
259 return true
259 return true
260 end
260 end
261 end
261 end
262 return false
262 return false
263 end
263 end
264
264
265 def available_problems_group_by_contests
265 def available_problems_group_by_contests
266 contest_problems = []
266 contest_problems = []
267 pin = {}
267 pin = {}
268 contests.enabled.each do |contest|
268 contests.enabled.each do |contest|
269 available_problems = contest.problems.available
269 available_problems = contest.problems.available
270 contest_problems << {
270 contest_problems << {
271 :contest => contest,
271 :contest => contest,
272 :problems => available_problems
272 :problems => available_problems
273 }
273 }
274 available_problems.each {|p| pin[p.id] = true}
274 available_problems.each {|p| pin[p.id] = true}
275 end
275 end
276 other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0}
276 other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0}
277 contest_problems << {
277 contest_problems << {
278 :contest => nil,
278 :contest => nil,
279 :problems => other_avaiable_problems
279 :problems => other_avaiable_problems
280 }
280 }
281 return contest_problems
281 return contest_problems
282 end
282 end
283
283
284 def solve_all_available_problems?
284 def solve_all_available_problems?
285 available_problems.each do |p|
285 available_problems.each do |p|
286 u = self
286 u = self
287 sub = Submission.find_last_by_user_and_problem(u.id,p.id)
287 sub = Submission.find_last_by_user_and_problem(u.id,p.id)
288 return false if !p or !sub or sub.points < p.full_score
288 return false if !p or !sub or sub.points < p.full_score
289 end
289 end
290 return true
290 return true
291 end
291 end
292
292
293 #get a list of available problem
293 #get a list of available problem
294 def available_problems
294 def available_problems
295 # first, we check if this is normal mode
295 # first, we check if this is normal mode
296 if not GraderConfiguration.multicontests?
296 if not GraderConfiguration.multicontests?
297
297
298 #if this is a normal mode
298 #if this is a normal mode
299 #we show problem based on problem_group, if the config said so
299 #we show problem based on problem_group, if the config said so
300 if GraderConfiguration.use_problem_group?
300 if GraderConfiguration.use_problem_group?
301 return available_problems_in_group
301 return available_problems_in_group
302 else
302 else
303 return Problem.available_problems
303 return Problem.available_problems
304 end
304 end
305 else
305 else
306 #this is multi contest mode
306 #this is multi contest mode
307 contest_problems = []
307 contest_problems = []
308 pin = {}
308 pin = {}
309 contests.enabled.each do |contest|
309 contests.enabled.each do |contest|
310 contest.problems.available.each do |problem|
310 contest.problems.available.each do |problem|
311 if not pin.has_key? problem.id
311 if not pin.has_key? problem.id
312 contest_problems << problem
312 contest_problems << problem
313 end
313 end
314 pin[problem.id] = true
314 pin[problem.id] = true
315 end
315 end
316 end
316 end
317 other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0}
317 other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0}
318 return contest_problems + other_avaiable_problems
318 return contest_problems + other_avaiable_problems
319 end
319 end
320 end
320 end
321
321
322 # new feature, get list of available problem in all enabled group that the user belongs to
322 # new feature, get list of available problem in all enabled group that the user belongs to
323 def available_problems_in_group
323 def available_problems_in_group
324 problem = []
324 problem = []
325 self.groups.where(enabled: true).each do |group|
325 self.groups.where(enabled: true).each do |group|
326 group.problems.where(available: true).each { |p| problem << p }
326 group.problems.where(available: true).each { |p| problem << p }
327 end
327 end
328 problem.uniq!
328 problem.uniq!
329 if problem
329 if problem
330 problem.sort! do |a,b|
330 problem.sort! do |a,b|
331 case
331 case
332 when a.date_added < b.date_added
332 when a.date_added < b.date_added
333 1
333 1
334 when a.date_added > b.date_added
334 when a.date_added > b.date_added
335 -1
335 -1
336 else
336 else
337 a.name <=> b.name
337 a.name <=> b.name
338 end
338 end
339 end
339 end
340 return problem
340 return problem
341 else
341 else
342 return []
342 return []
343 end
343 end
344 end
344 end
345
345
346 #check if the user has the right to view that problem
346 #check if the user has the right to view that problem
347 #this also consider group based problem policy
347 #this also consider group based problem policy
348 def can_view_problem?(problem)
348 def can_view_problem?(problem)
349 return true if admin?
349 return true if admin?
350 return available_problems.include? problem
350 return available_problems.include? problem
351 end
351 end
352
352
353 def self.clear_last_login
353 def self.clear_last_login
354 User.update_all(:last_ip => nil)
354 User.update_all(:last_ip => nil)
355 end
355 end
356
356
357 #create multiple user, one per lines of input
357 #create multiple user, one per lines of input
358 def self.create_from_list(lines)
358 def self.create_from_list(lines)
359 error_logins = []
359 error_logins = []
360 first_error = nil
360 first_error = nil
361 created_users = []
361 created_users = []
362
362
363 lines.split("\n").each do |line|
363 lines.split("\n").each do |line|
364 #split with large limit, this will cause consecutive ',' to be result in a blank
364 #split with large limit, this will cause consecutive ',' to be result in a blank
365 items = line.chomp.split(',',1000)
365 items = line.chomp.split(',',1000)
366 if items.length>=2
366 if items.length>=2
367 login = items[0]
367 login = items[0]
368 full_name = items[1]
368 full_name = items[1]
369 remark =''
369 remark =''
370 user_alias = ''
370 user_alias = ''
371
371
372 added_random_password = false
372 added_random_password = false
373 added_password = false
373 added_password = false
374
374
375 #given password?
375 #given password?
376 if items.length >= 3
376 if items.length >= 3
377 if items[2].chomp(" ").length > 0
377 if items[2].chomp(" ").length > 0
378 password = items[2].chomp(" ")
378 password = items[2].chomp(" ")
379 added_password = true
379 added_password = true
380 end
380 end
381 else
381 else
382 password = random_password
382 password = random_password
383 added_random_password=true;
383 added_random_password=true;
384 end
384 end
385
385
386 #given alias?
386 #given alias?
387 if items.length>= 4 and items[3].chomp(" ").length > 0;
387 if items.length>= 4 and items[3].chomp(" ").length > 0;
388 user_alias = items[3].chomp(" ")
388 user_alias = items[3].chomp(" ")
389 else
389 else
390 user_alias = login
390 user_alias = login
391 end
391 end
392
392
393 #given remark?
393 #given remark?
394 has_remark = false
394 has_remark = false
395 if items.length>=5
395 if items.length>=5
396 remark = items[4].strip;
396 remark = items[4].strip;
397 has_remark = true
397 has_remark = true
398 end
398 end
399
399
400 user = User.find_by_login(login)
400 user = User.find_by_login(login)
401 if (user)
401 if (user)
402 user.full_name = full_name
402 user.full_name = full_name
403 user.remark = remark if has_remark
403 user.remark = remark if has_remark
404 user.password = password if added_password || added_random_password
404 user.password = password if added_password || added_random_password
405 else
405 else
406 #create a random password if none are given
406 #create a random password if none are given
407 password = random_password unless password
407 password = random_password unless password
408 user = User.new({:login => login,
408 user = User.new({:login => login,
409 :full_name => full_name,
409 :full_name => full_name,
410 :password => password,
410 :password => password,
411 :password_confirmation => password,
411 :password_confirmation => password,
412 :alias => user_alias,
412 :alias => user_alias,
413 :remark => remark})
413 :remark => remark})
414 end
414 end
415 user.activated = true
415 user.activated = true
416
416
417 if user.save
417 if user.save
418 created_users << user
418 created_users << user
419 else
419 else
420 error_logins << "'#{login}'"
420 error_logins << "'#{login}'"
421 first_error = user.errors.full_messages.to_sentence unless first_error
421 first_error = user.errors.full_messages.to_sentence unless first_error
422 end
422 end
423 end
423 end
424 end
424 end
425
425
426 return {error_logins: error_logins, first_error: first_error, created_users: created_users}
426 return {error_logins: error_logins, first_error: first_error, created_users: created_users}
427
427
428 end
428 end
429
429
430 def self.find_non_admin_with_prefix(prefix='')
430 def self.find_non_admin_with_prefix(prefix='')
431 users = User.all
431 users = User.all
432 return users.find_all { |u| !(u.admin?) and u.login.index(prefix)==0 }
432 return users.find_all { |u| !(u.admin?) and u.login.index(prefix)==0 }
433 end
433 end
434
434
435 protected
435 protected
436 def encrypt_new_password
436 def encrypt_new_password
437 return if password.blank?
437 return if password.blank?
438 self.salt = (10+rand(90)).to_s
438 self.salt = (10+rand(90)).to_s
439 self.hashed_password = User.encrypt(self.password,self.salt)
439 self.hashed_password = User.encrypt(self.password,self.salt)
440 end
440 end
441
441
442 def assign_default_site
442 def assign_default_site
443 # have to catch error when migrating (because self.site is not available).
443 # have to catch error when migrating (because self.site is not available).
444 begin
444 begin
445 if self.site==nil
445 if self.site==nil
446 self.site = Site.find_by_name('default')
446 self.site = Site.find_by_name('default')
447 if self.site==nil
447 if self.site==nil
448 self.site = Site.find(1) # when 'default has be renamed'
448 self.site = Site.find(1) # when 'default has be renamed'
449 end
449 end
450 end
450 end
451 rescue
451 rescue
452 end
452 end
453 end
453 end
454
454
455 def assign_default_contest
455 def assign_default_contest
456 # have to catch error when migrating (because self.site is not available).
456 # have to catch error when migrating (because self.site is not available).
457 begin
457 begin
458 if self.contests.length == 0
458 if self.contests.length == 0
459 default_contest = Contest.find_by_name(GraderConfiguration['contest.default_contest_name'])
459 default_contest = Contest.find_by_name(GraderConfiguration['contest.default_contest_name'])
460 if default_contest
460 if default_contest
461 self.contests = [default_contest]
461 self.contests = [default_contest]
462 end
462 end
463 end
463 end
464 rescue
464 rescue
465 end
465 end
466 end
466 end
467
467
468 def password_required?
468 def password_required?
469 self.hashed_password.blank? || !self.password.blank?
469 self.hashed_password.blank? || !self.password.blank?
470 end
470 end
471
471
472 def self.encrypt(string,salt)
472 def self.encrypt(string,salt)
473 Digest::SHA1.hexdigest(salt + string)
473 Digest::SHA1.hexdigest(salt + string)
474 end
474 end
475
475
476 def uniqueness_of_email_from_activated_users
476 def uniqueness_of_email_from_activated_users
477 user = User.activated_users.find_by_email(self.email)
477 user = User.activated_users.find_by_email(self.email)
478 if user and (user.login != self.login)
478 if user and (user.login != self.login)
479 self.errors.add(:base,"Email has already been taken")
479 self.errors.add(:base,"Email has already been taken")
480 end
480 end
481 end
481 end
482
482
483 def enough_time_interval_between_same_email_registrations
483 def enough_time_interval_between_same_email_registrations
484 return if !self.new_record?
484 return if !self.new_record?
485 return if self.activated
485 return if self.activated
486 open_user = User.find_by_email(self.email,
486 open_user = User.find_by_email(self.email,
487 :order => 'created_at DESC')
487 :order => 'created_at DESC')
488 if open_user and open_user.created_at and
488 if open_user and open_user.created_at and
489 (open_user.created_at > Time.now.gmtime - 5.minutes)
489 (open_user.created_at > Time.now.gmtime - 5.minutes)
490 self.errors.add(:base,"There are already unactivated registrations with this e-mail address (please wait for 5 minutes)")
490 self.errors.add(:base,"There are already unactivated registrations with this e-mail address (please wait for 5 minutes)")
491 end
491 end
492 end
492 end
493
493
494 def email_validation?
494 def email_validation?
495 begin
495 begin
496 return VALIDATE_USER_EMAILS
496 return VALIDATE_USER_EMAILS
497 rescue
497 rescue
498 return false
498 return false
499 end
499 end
500 end
500 end
501 end
501 end
@@ -1,154 +1,156
1 :css
1 :css
2 .hof_user { color: orangered; font-style: italic; }
2 .hof_user { color: orangered; font-style: italic; }
3 .hof_language { color: green; font-style: italic; }
3 .hof_language { color: green; font-style: italic; }
4 .hof_value { color: deeppink;font-style: italic; }
4 .hof_value { color: deeppink;font-style: italic; }
5 .info_param { font-weight: bold;text-align: right; }
5 .info_param { font-weight: bold;text-align: right; }
6 .tooltip {
6 .tooltip {
7 font-family: Verdana,sans-serif;
7 font-family: Verdana,sans-serif;
8 font-weight: normal;
8 font-weight: normal;
9 text-align: left;
9 text-align: left;
10 font-size: 1.0em;
10 font-size: 1.0em;
11 color: black;
11 color: black;
12 line-height: 1.1;
12 line-height: 1.1;
13 display: none;
13 display: none;
14 min-width: 20em;
14 min-width: 20em;
15 position: absolute;
15 position: absolute;
16 left: 25px;
16 left: 25px;
17 bottom: 5px;
17 bottom: 5px;
18 border: 1px solid;
18 border: 1px solid;
19 padding: 5px;
19 padding: 5px;
20 background-color: #FFF;
20 background-color: #FFF;
21 word-wrap: break-word;
21 word-wrap: break-word;
22 z-index: 9999;
22 z-index: 9999;
23 overflow: auto;
23 overflow: auto;
24 }
24 }
25
25
26
26
27 .container-fluid
27 .container-fluid
28 .row
28 .row
29 .col-md-8
29 .col-md-8
30 .card
30 .card
31 .card-body
31 .card-body
32 %h2.card-title Submission History
32 %h2.card-title Submission History
33 %canvas#chart{height: '50px'}
33 %canvas#chart{height: '50px'}
34
34
35 .col-md-4
35 .col-md-4
36 .card
36 .card
37 .card-body
37 .card-body
38 %h2.card-title General Info
38 %h2.card-title General Info
39 .row
39 .row
40 .col-sm-6
40 .col-sm-6
41 Subs
41 Subs
42 .col-sm-6
42 .col-sm-6
43 = @summary[:count]
43 = @summary[:count]
44 .row
44 .row
45 .col-sm-6
45 .col-sm-6
46 Solved/Attempted User
46 Solved/Attempted User
47 .col-sm-6
47 .col-sm-6
48 #{@summary[:solve]}/#{@summary[:attempt]} (#{(@summary[:solve]*100.0/@summary[:attempt]).round(1)}%)
48 #{@summary[:solve]}/#{@summary[:attempt]} (#{(@summary[:solve]*100.0/@summary[:attempt]).round(1)}%)
49 .row
49 .row
50 .col-md-4
50 .col-md-4
51 .card
51 .card
52 .card-body
52 .card-body
53 %h2.card-title Model submission
53 %h2.card-title Model submission
54 %table.table.table-hover
54 %table.table.table-hover
55 %thead
55 %thead
56 %tr
56 %tr
57 - %th #Sub
57 + %th #Sub (lang)
58 %th Author
58 %th Author
59 %tbody
59 %tbody
60 - @model_subs.each do |sub|
60 - @model_subs.each do |sub|
61 %tr
61 %tr
62 - %td= link_to "##{sub.id}", submission_path(sub)
62 + %td
63 + = link_to "##{sub.id}", submission_path(sub)
64 + = "(#{sub.language.pretty_name})"
63 %td= sub.user.full_name
65 %td= sub.user.full_name
64 .col-md-8
66 .col-md-8
65 - if @best
67 - if @best
66 .card
68 .card
67 .card-body
69 .card-body
68 %h2.card-title Top Submissions
70 %h2.card-title Top Submissions
69 %table.table.table-hover
71 %table.table.table-hover
70 %thead
72 %thead
71 %tr
73 %tr
72 %th Language
74 %th Language
73 %th Best runtime (ms)
75 %th Best runtime (ms)
74 %th Best memory (kbytes)
76 %th Best memory (kbytes)
75 %th Shortest Code (bytes)
77 %th Shortest Code (bytes)
76 %th First solver
78 %th First solver
77 %tbody
79 %tbody
78 %tr.bg-warning
80 %tr.bg-warning
79 %td
81 %td
80 Overall
82 Overall
81 %td
83 %td
82 by #{link_to @best[:runtime][:user], stat_user_path(@best[:runtime][:user_id])}
84 by #{link_to @best[:runtime][:user], stat_user_path(@best[:runtime][:user_id])}
83 %br
85 %br
84 using <span class="text-success">#{@best[:runtime][:lang]}</span>
86 using <span class="text-success">#{@best[:runtime][:lang]}</span>
85 %br
87 %br
86 with <span class="text-success">#{@best[:runtime][:value] * 1000} milliseconds</span>
88 with <span class="text-success">#{@best[:runtime][:value] * 1000} milliseconds</span>
87 %br= link_to "#" + @best[:runtime][:sub_id].to_s, submission_path(@best[:runtime][:sub_id])
89 %br= link_to "#" + @best[:runtime][:sub_id].to_s, submission_path(@best[:runtime][:sub_id])
88 %td
90 %td
89 by #{link_to @best[:memory][:user], stat_user_path(@best[:memory][:user_id])}
91 by #{link_to @best[:memory][:user], stat_user_path(@best[:memory][:user_id])}
90 %br
92 %br
91 using <span class="text-success">#{@best[:memory][:lang]}</span>
93 using <span class="text-success">#{@best[:memory][:lang]}</span>
92 %br
94 %br
93 with <span class="text-success">#{number_with_delimiter(@best[:memory][:value])} kbytes </span>
95 with <span class="text-success">#{number_with_delimiter(@best[:memory][:value])} kbytes </span>
94 %br= link_to "#" + @best[:memory][:sub_id].to_s, submission_path(@best[:memory][:sub_id])
96 %br= link_to "#" + @best[:memory][:sub_id].to_s, submission_path(@best[:memory][:sub_id])
95 %td
97 %td
96 by #{link_to @best[:length][:user], stat_user_path(@best[:length][:user_id])}
98 by #{link_to @best[:length][:user], stat_user_path(@best[:length][:user_id])}
97 %br
99 %br
98 using <span class="text-success">#{@best[:length][:lang]}</span>
100 using <span class="text-success">#{@best[:length][:lang]}</span>
99 %br
101 %br
100 with <span class="text-success">#{@best[:length][:value]} bytes</span>
102 with <span class="text-success">#{@best[:length][:value]} bytes</span>
101 %br= link_to "#" + @best[:length][:sub_id].to_s, submission_path(@best[:length][:sub_id])
103 %br= link_to "#" + @best[:length][:sub_id].to_s, submission_path(@best[:length][:sub_id])
102 %td
104 %td
103 - if @best[:first][:user] != '(NULL)'
105 - if @best[:first][:user] != '(NULL)'
104 #{link_to @best[:first][:user], stat_user_path(@best[:first][:user_id])} is the first solver
106 #{link_to @best[:first][:user], stat_user_path(@best[:first][:user_id])} is the first solver
105 %br
107 %br
106 using <span class="text-success">#{@best[:first][:lang]}</span>
108 using <span class="text-success">#{@best[:first][:lang]}</span>
107 %br
109 %br
108 on <span class="text-success">#{@best[:first][:value]}</span>
110 on <span class="text-success">#{@best[:first][:value]}</span>
109 %br= link_to "#" + @best[:first][:sub_id].to_s, submission_path( @best[:first][:sub_id])
111 %br= link_to "#" + @best[:first][:sub_id].to_s, submission_path( @best[:first][:sub_id])
110 - else
112 - else
111 no first solver
113 no first solver
112 - @by_lang.each do |lang,value|
114 - @by_lang.each do |lang,value|
113 %tr
115 %tr
114 %td= lang
116 %td= lang
115 %td
117 %td
116 = link_to value[:runtime][:user], stat_user_path(value[:runtime][:user_id])
118 = link_to value[:runtime][:user], stat_user_path(value[:runtime][:user_id])
117 %br
119 %br
118 = "#{(value[:runtime][:value] * 1000).to_i} @"
120 = "#{(value[:runtime][:value] * 1000).to_i} @"
119 = link_to "#" + value[:runtime][:sub_id].to_s, submission_path( value[:runtime][:sub_id])
121 = link_to "#" + value[:runtime][:sub_id].to_s, submission_path( value[:runtime][:sub_id])
120 %td
122 %td
121 = link_to value[:memory][:user], stat_user_path( value[:memory][:user_id])
123 = link_to value[:memory][:user], stat_user_path( value[:memory][:user_id])
122 %br
124 %br
123 = "#{number_with_delimiter(value[:memory][:value])} @"
125 = "#{number_with_delimiter(value[:memory][:value])} @"
124 = link_to "#" + value[:memory][:sub_id].to_s, submission_path(value[:memory][:sub_id])
126 = link_to "#" + value[:memory][:sub_id].to_s, submission_path(value[:memory][:sub_id])
125 %td
127 %td
126 = link_to value[:length][:user], stat_user_path(value[:length][:user_id])
128 = link_to value[:length][:user], stat_user_path(value[:length][:user_id])
127 %br
129 %br
128 = "#{value[:length][:value]} @"
130 = "#{value[:length][:value]} @"
129 = link_to "#" + value[:length][:sub_id].to_s, submission_path(value[:length][:sub_id])
131 = link_to "#" + value[:length][:sub_id].to_s, submission_path(value[:length][:sub_id])
130 %td
132 %td
131 - if value[:first][:user] != '(NULL)' #TODO: i know... this is wrong...
133 - if value[:first][:user] != '(NULL)' #TODO: i know... this is wrong...
132 = link_to value[:first][:user], stat_user_path(value[:first][:user_id])
134 = link_to value[:first][:user], stat_user_path(value[:first][:user_id])
133 %br
135 %br
134 = "#{value[:first][:value]} @"
136 = "#{value[:first][:value]} @"
135 = link_to "#" + value[:first][:sub_id].to_s, submission_path( value[:first][:sub_id])
137 = link_to "#" + value[:first][:sub_id].to_s, submission_path( value[:first][:sub_id])
136
138
137 %script{src:"https://cdn.jsdelivr.net/npm/chart.js"}
139 %script{src:"https://cdn.jsdelivr.net/npm/chart.js"}
138 :javascript
140 :javascript
139 data = #{@chart_dataset}
141 data = #{@chart_dataset}
140 config = {
142 config = {
141 type: 'bar',
143 type: 'bar',
142 data: data,
144 data: data,
143 options: {
145 options: {
144 plugins: {
146 plugins: {
145 legend: {
147 legend: {
146 display: false
148 display: false
147 },
149 },
148 },
150 },
149 }
151 }
150 }
152 }
151 Chart.defaults.font.size = 15
153 Chart.defaults.font.size = 15
152 //Chart.defaults.font.family = 'Sarabun Light'
154 //Chart.defaults.font.family = 'Sarabun Light'
153 chart = new Chart($('#chart'),config)
155 chart = new Chart($('#chart'),config)
154
156
@@ -1,29 +1,30
1
1
2 /- if params[:id]
2 /- if params[:id]
3 / %h1 Tasks Hall of Fame
3 / %h1 Tasks Hall of Fame
4 / = link_to('[back to All-Time Hall of Fame]', action: 'problem_hof', id: nil )
4 / = link_to('[back to All-Time Hall of Fame]', action: 'problem_hof', id: nil )
5 /- else
5 /- else
6 / %h1 All-Time Hall of Fame
6 / %h1 All-Time Hall of Fame
7
7
8 .panel.panel-info
8 .panel.panel-info
9 .panel-heading
9 .panel-heading
10 Select Task
10 Select Task
11 .panel-body
11 .panel-body
12 .form-inline
12 .form-inline
13 = select 'report',
13 = select 'report',
14 'problem_id',
14 'problem_id',
15 @problems.collect {|p| ["[#{p.name}] #{p.full_name}", problem_hof_report_path(p)]},
15 @problems.collect {|p| ["[#{p.name}] #{p.full_name}", problem_hof_report_path(p)]},
16 {:selected => problem_hof_report_path(@problem)},
16 {:selected => problem_hof_report_path(@problem)},
17 { class: 'select2 form-control' }
17 { class: 'select2 form-control' }
18 %button.btn.btn-primary.btn-sm.go-button#problem_go{data: {source: "#report_problem_id"}} Go
18 %button.btn.btn-primary.btn-sm.go-button#problem_go{data: {source: "#report_problem_id"}} Go
19
19
20
20
21 - unless params[:id]
21 - unless params[:id]
22 /=render partial: 'all_time_hof'
22 /=render partial: 'all_time_hof'
23 Please select a problem.
23 Please select a problem.
24 - else
24 - else
25 - %h1 [#{Problem.find(params[:id]).name}] #{Problem.find(params[:id]).full_name}
25 + %h1
26 - -# %h2 Submission History
26 + [#{Problem.find(params[:id]).name}] #{Problem.find(params[:id]).full_name}
27 - -# =render partial: 'application/bar_graph', locals: { histogram: @histogram }
27 + - if @current_user.has_role?('ta')
28 + %a{href:stat_problem_path(@problem)} (stat)
28 =render partial: 'task_hof'
29 =render partial: 'task_hof'
29
30
@@ -1,128 +1,128
1 %h1= "Submission: #{@submission.id}"
1 %h1= "Submission: #{@submission.id}"
2
2
3 %textarea#data{style: "display:none;"}
3 %textarea#data{style: "display:none;"}
4 :preserve
4 :preserve
5 #{@submission.source}
5 #{@submission.source}
6
6
7 //%div.highlight{:style => "border: 1px solid black;"}
7 //%div.highlight{:style => "border: 1px solid black;"}
8 //=@formatted_code.html_safe
8 //=@formatted_code.html_safe
9
9
10
10
11 .containter
11 .containter
12 .row
12 .row
13 .col-md-7
13 .col-md-7
14 %h2 Source Code
14 %h2 Source Code
15 .col-md-5
15 .col-md-5
16 %h2 Stat
16 %h2 Stat
17 .row
17 .row
18 .col-md-7
18 .col-md-7
19 %div#editor{ style: "font-size: 14px; height: 400px; border-radius:5px;" }
19 %div#editor{ style: "font-size: 14px; height: 400px; border-radius:5px;" }
20 :javascript
20 :javascript
21 e = ace.edit("editor")
21 e = ace.edit("editor")
22 e.setOptions({ maxLines: Infinity })
22 e.setOptions({ maxLines: Infinity })
23 e.setValue($("#data").text())
23 e.setValue($("#data").text())
24 e.gotoLine(1)
24 e.gotoLine(1)
25 e.getSession().setMode("#{get_ace_mode(@submission.language)}")
25 e.getSession().setMode("#{get_ace_mode(@submission.language)}")
26 e.setReadOnly(true)
26 e.setReadOnly(true)
27 .col-md-5
27 .col-md-5
28 %table.table.table-striped
28 %table.table.table-striped
29 %tr
29 %tr
30 %td.text-right
30 %td.text-right
31 %strong User
31 %strong User
32 %td
32 %td
33 - if @current_user.admin? ||@current_user == @submission.user
33 - if @current_user.admin? ||@current_user == @submission.user
34 - if @submission.user
34 - if @submission.user
35 = link_to "#{@submission.user.login}", stat_user_path(@submission.user)
35 = link_to "#{@submission.user.login}", stat_user_path(@submission.user)
36 = @submission.user.full_name
36 = @submission.user.full_name
37 - else
37 - else
38 = "(n/a)"
38 = "(n/a)"
39 - else
39 - else
40 = '-- REDACTED --'
40 = '-- REDACTED --'
41 %tr
41 %tr
42 %td.text-right
42 %td.text-right
43 %strong Task
43 %strong Task
44 %td
44 %td
45 - if @submission.problem!=nil
45 - if @submission.problem!=nil
46 = link_to "[#{@submission.problem.name}]", stat_problem_path(@submission.problem)
46 = link_to "[#{@submission.problem.name}]", stat_problem_path(@submission.problem)
47 = @submission.problem.full_name
47 = @submission.problem.full_name
48 = link_to_description_if_any "[download] <span class='glyphicon glyphicon-file'></span>".html_safe, @submission.problem
48 = link_to_description_if_any "[download] <span class='glyphicon glyphicon-file'></span>".html_safe, @submission.problem
49 - else
49 - else
50 = "(n/a)"
50 = "(n/a)"
51 %tr
51 %tr
52 %td.text-right
52 %td.text-right
53 %strong Tries
53 %strong Tries
54 %td= @submission.number
54 %td= @submission.number
55 %tr
55 %tr
56 %td.text-right
56 %td.text-right
57 %strong Language
57 %strong Language
58 %td= @submission.language.pretty_name
58 %td= @submission.language.pretty_name
59 %tr
59 %tr
60 %td.text-right
60 %td.text-right
61 %strong Submitted
61 %strong Submitted
62 %td #{time_ago_in_words(@submission.submitted_at)} ago (at #{@submission.submitted_at.to_formatted_s(:long)})
62 %td #{time_ago_in_words(@submission.submitted_at)} ago (at #{@submission.submitted_at.to_formatted_s(:long)})
63 %tr
63 %tr
64 %td.text-right
64 %td.text-right
65 %strong Graded
65 %strong Graded
66 - if @submission.graded_at
66 - if @submission.graded_at
67 %td #{time_ago_in_words(@submission.graded_at)} ago (at #{@submission.graded_at.to_formatted_s(:long)})
67 %td #{time_ago_in_words(@submission.graded_at)} ago (at #{@submission.graded_at.to_formatted_s(:long)})
68 - else
68 - else
69 %td -
69 %td -
70 %tr
70 %tr
71 %td.text-right
71 %td.text-right
72 %strong Points
72 %strong Points
73 %td #{@submission.points}/#{@submission.try(:problem).try(:full_score)}
73 %td #{@submission.points}/#{@submission.try(:problem).try(:full_score)}
74 %tr
74 %tr
75 %td.text-right
75 %td.text-right
76 %strong Comment
76 %strong Comment
77 %td #{@submission.grader_comment}
77 %td #{@submission.grader_comment}
78 %tr
78 %tr
79 %td.text-right
79 %td.text-right
80 %strong Runtime (s)
80 %strong Runtime (s)
81 %td #{@submission.max_runtime}
81 %td #{@submission.max_runtime}
82 %tr
82 %tr
83 %td.text-right
83 %td.text-right
84 %strong Memory (kb)
84 %strong Memory (kb)
85 %td #{@submission.peak_memory}
85 %td #{@submission.peak_memory}
86 %tr
86 %tr
87 %td.text-right
87 %td.text-right
88 %strong Compiler result
88 %strong Compiler result
89 %td
89 %td
90 %button.btn.btn-info.btn-xs{type: 'button', data: {toggle: 'modal', target: '#compiler'}}
90 %button.btn.btn-info.btn-xs{type: 'button', data: {toggle: 'modal', target: '#compiler'}}
91 view
91 view
92 %tr
92 %tr
93 %td.text-right
93 %td.text-right
94 %strong Grading Task Status
94 %strong Grading Task Status
95 %td
95 %td
96 = @task.status_str if @task
96 = @task.status_str if @task
97 - - if session[:admin]
97 + - if @current_user.admin?
98 = link_to "rejudge", rejudge_submission_path, data: {remote: true}, class: 'btn btn-info btn-xs'
98 = link_to "rejudge", rejudge_submission_path, data: {remote: true}, class: 'btn btn-info btn-xs'
99 - - if session[:admin]
99 + - if @current_user.has_role?('ta')
100 %tr
100 %tr
101 %td.text-right
101 %td.text-right
102 %strong IP
102 %strong IP
103 %td #{@submission.ip_address}
103 %td #{@submission.ip_address}
104 %tr
104 %tr
105 %td.text-right
105 %td.text-right
106 %strong Model solution
106 %strong Model solution
107 %td
107 %td
108 - if @submission.tag_model?
108 - if @submission.tag_model?
109 YES
109 YES
110 - - if session[:admin]
110 + - if @current_user.has_role?('ta')
111 = link_to "remove model status", set_tag_submission_path(@submission, tag: :default), class: 'btn btn-warning btn-xs'
111 = link_to "remove model status", set_tag_submission_path(@submission, tag: :default), class: 'btn btn-warning btn-xs'
112 - else
112 - else
113 No
113 No
114 - - if session[:admin]
114 + - if @current_user.has_role?('ta')
115 = link_to "set as model solution", set_tag_submission_path(@submission, tag: :model), class: 'btn btn-success btn-xs'
115 = link_to "set as model solution", set_tag_submission_path(@submission, tag: :model), class: 'btn btn-success btn-xs'
116
116
117
117
118 .modal.fade#compiler{tabindex: -1,role: 'dialog'}
118 .modal.fade#compiler{tabindex: -1,role: 'dialog'}
119 .modal-dialog.modal-lg{role:'document'}
119 .modal-dialog.modal-lg{role:'document'}
120 .modal-content
120 .modal-content
121 .modal-header
121 .modal-header
122 %button.close{type: 'button', data: {dismissed: :modal}, aria: {label: 'close'}}
122 %button.close{type: 'button', data: {dismissed: :modal}, aria: {label: 'close'}}
123 %span{aria: {hidden: 'true'}, data: {dismiss: 'modal'}} &times;
123 %span{aria: {hidden: 'true'}, data: {dismiss: 'modal'}} &times;
124 %h4 Compiler message
124 %h4 Compiler message
125 .modal-body
125 .modal-body
126 %pre#compiler_msg= @submission.compiler_message
126 %pre#compiler_msg= @submission.compiler_message
127 .modal-footer
127 .modal-footer
128 %button.btn.btn-default{type: 'button', data: {dismiss: 'modal'}} Close
128 %button.btn.btn-default{type: 'button', data: {dismiss: 'modal'}} Close
You need to be logged in to leave comments. Login now