Description:
fix bug with duplicate logins are given also increase dropdown size on group view
Commit status:
[Not Reviewed]
References:
Comments:
0 Commit comments 0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
Add another comment

r809:e8c8ed4696af - - 3 files changed: 7 inserted, 3 deleted

@@ -1,20 +1,20
1 1 class Group < ActiveRecord::Base
2 2 has_many :groups_problems, class_name: 'GroupProblem'
3 3 has_many :problems, :through => :groups_problems
4 4
5 5 has_many :groups_users, class_name: 'GroupUser'
6 6 has_many :users, :through => :groups_users
7 7
8 8 #has_and_belongs_to_many :problems
9 9 #has_and_belongs_to_many :users
10 10
11 11 def add_users_skip_existing(users_list)
12 12 new_list = []
13 - users_list.each do |u|
13 + users_list.uniq.each do |u|
14 14 new_list << u unless users.include? u
15 15 end
16 16 users << new_list
17 17 end
18 18
19 19 end
20 20
@@ -1,457 +1,461
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 10
11 11 #has_and_belongs_to_many :groups
12 12 has_many :groups_users, class_name: 'GroupUser'
13 13 has_many :groups, :through => :groups_users
14 14
15 15 has_many :test_requests, -> {order(submitted_at: :desc)}
16 16
17 17 has_many :messages, -> { order(created_at: :desc) },
18 18 :class_name => "Message",
19 19 :foreign_key => "sender_id"
20 20
21 21 has_many :replied_messages, -> { order(created_at: :desc) },
22 22 :class_name => "Message",
23 23 :foreign_key => "receiver_id"
24 24
25 25 has_many :logins
26 26
27 27 has_one :contest_stat, :class_name => "UserContestStat", :dependent => :destroy
28 28
29 29 belongs_to :site
30 30 belongs_to :country
31 31
32 32 has_and_belongs_to_many :contests, -> { order(:name)}
33 33
34 34 scope :activated_users, -> {where activated: true}
35 35
36 36 validates_presence_of :login
37 37 validates_uniqueness_of :login
38 38 validates_format_of :login, :with => /\A[\_A-Za-z0-9]+\z/
39 39 validates_length_of :login, :within => 3..30
40 40
41 41 validates_presence_of :full_name
42 42 validates_length_of :full_name, :minimum => 1
43 43
44 44 validates_presence_of :password, :if => :password_required?
45 45 validates_length_of :password, :within => 4..50, :if => :password_required?
46 46 validates_confirmation_of :password, :if => :password_required?
47 47
48 48 validates_format_of :email,
49 49 :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i,
50 50 :if => :email_validation?
51 51 validate :uniqueness_of_email_from_activated_users,
52 52 :if => :email_validation?
53 53 validate :enough_time_interval_between_same_email_registrations,
54 54 :if => :email_validation?
55 55
56 56 # these are for ytopc
57 57 # disable for now
58 58 #validates_presence_of :province
59 59
60 60 attr_accessor :password
61 61
62 62 before_save :encrypt_new_password
63 63 before_save :assign_default_site
64 64 before_save :assign_default_contest
65 65
66 66 # this is for will_paginate
67 67 cattr_reader :per_page
68 68 @@per_page = 50
69 69
70 70 def self.authenticate(login, password)
71 71 user = find_by_login(login)
72 72 if user
73 73 return user if user.authenticated?(password)
74 74 end
75 75 end
76 76
77 77 def authenticated?(password)
78 78 if self.activated
79 79 hashed_password == User.encrypt(password,self.salt)
80 80 else
81 81 false
82 82 end
83 83 end
84 84
85 + def login_with_name
86 + "[#{login}] #{full_name}"
87 + end
88 +
85 89 def admin?
86 90 has_role?('admin')
87 91 end
88 92
89 93 def has_role?(role)
90 94 self.roles.where(name: role).count > 0
91 95 end
92 96
93 97 def email_for_editing
94 98 if self.email==nil
95 99 "(unknown)"
96 100 elsif self.email==''
97 101 "(blank)"
98 102 else
99 103 self.email
100 104 end
101 105 end
102 106
103 107 def email_for_editing=(e)
104 108 self.email=e
105 109 end
106 110
107 111 def alias_for_editing
108 112 if self.alias==nil
109 113 "(unknown)"
110 114 elsif self.alias==''
111 115 "(blank)"
112 116 else
113 117 self.alias
114 118 end
115 119 end
116 120
117 121 def alias_for_editing=(e)
118 122 self.alias=e
119 123 end
120 124
121 125 def activation_key
122 126 if self.hashed_password==nil
123 127 encrypt_new_password
124 128 end
125 129 Digest::SHA1.hexdigest(self.hashed_password)[0..7]
126 130 end
127 131
128 132 def verify_activation_key(key)
129 133 key == activation_key
130 134 end
131 135
132 136 def self.random_password(length=5)
133 137 chars = 'abcdefghjkmnopqrstuvwxyz'
134 138 password = ''
135 139 length.times { password << chars[rand(chars.length - 1)] }
136 140 password
137 141 end
138 142
139 143
140 144 # Contest information
141 145
142 146 def self.find_users_with_no_contest()
143 147 users = User.all
144 148 return users.find_all { |u| u.contests.length == 0 }
145 149 end
146 150
147 151
148 152 def contest_time_left
149 153 if GraderConfiguration.contest_mode?
150 154 return nil if site==nil
151 155 return site.time_left
152 156 elsif GraderConfiguration.indv_contest_mode?
153 157 time_limit = GraderConfiguration.contest_time_limit
154 158 if time_limit == nil
155 159 return nil
156 160 end
157 161 if contest_stat==nil or contest_stat.started_at==nil
158 162 return (Time.now.gmtime + time_limit) - Time.now.gmtime
159 163 else
160 164 finish_time = contest_stat.started_at + time_limit
161 165 current_time = Time.now.gmtime
162 166 if current_time > finish_time
163 167 return 0
164 168 else
165 169 return finish_time - current_time
166 170 end
167 171 end
168 172 else
169 173 return nil
170 174 end
171 175 end
172 176
173 177 def contest_finished?
174 178 if GraderConfiguration.contest_mode?
175 179 return false if site==nil
176 180 return site.finished?
177 181 elsif GraderConfiguration.indv_contest_mode?
178 182 return false if self.contest_stat==nil
179 183 return contest_time_left == 0
180 184 else
181 185 return false
182 186 end
183 187 end
184 188
185 189 def contest_started?
186 190 if GraderConfiguration.indv_contest_mode?
187 191 stat = self.contest_stat
188 192 return ((stat != nil) and (stat.started_at != nil))
189 193 elsif GraderConfiguration.contest_mode?
190 194 return true if site==nil
191 195 return site.started
192 196 else
193 197 return true
194 198 end
195 199 end
196 200
197 201 def update_start_time
198 202 stat = self.contest_stat
199 203 if stat.nil? or stat.started_at.nil?
200 204 stat ||= UserContestStat.new(:user => self)
201 205 stat.started_at = Time.now.gmtime
202 206 stat.save
203 207 end
204 208 end
205 209
206 210 def problem_in_user_contests?(problem)
207 211 problem_contests = problem.contests.all
208 212
209 213 if problem_contests.length == 0 # this is public contest
210 214 return true
211 215 end
212 216
213 217 contests.each do |contest|
214 218 if problem_contests.find {|c| c.id == contest.id }
215 219 return true
216 220 end
217 221 end
218 222 return false
219 223 end
220 224
221 225 def available_problems_group_by_contests
222 226 contest_problems = []
223 227 pin = {}
224 228 contests.enabled.each do |contest|
225 229 available_problems = contest.problems.available
226 230 contest_problems << {
227 231 :contest => contest,
228 232 :problems => available_problems
229 233 }
230 234 available_problems.each {|p| pin[p.id] = true}
231 235 end
232 236 other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0}
233 237 contest_problems << {
234 238 :contest => nil,
235 239 :problems => other_avaiable_problems
236 240 }
237 241 return contest_problems
238 242 end
239 243
240 244 def solve_all_available_problems?
241 245 available_problems.each do |p|
242 246 u = self
243 247 sub = Submission.find_last_by_user_and_problem(u.id,p.id)
244 248 return false if !p or !sub or sub.points < p.full_score
245 249 end
246 250 return true
247 251 end
248 252
249 253 #get a list of available problem
250 254 def available_problems
251 255 # first, we check if this is normal mode
252 256 if not GraderConfiguration.multicontests?
253 257
254 258 #if this is a normal mode
255 259 #we show problem based on problem_group, if the config said so
256 260 if GraderConfiguration.use_problem_group?
257 261 return available_problems_in_group
258 262 else
259 263 return Problem.available_problems
260 264 end
261 265 else
262 266 #this is multi contest mode
263 267 contest_problems = []
264 268 pin = {}
265 269 contests.enabled.each do |contest|
266 270 contest.problems.available.each do |problem|
267 271 if not pin.has_key? problem.id
268 272 contest_problems << problem
269 273 end
270 274 pin[problem.id] = true
271 275 end
272 276 end
273 277 other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0}
274 278 return contest_problems + other_avaiable_problems
275 279 end
276 280 end
277 281
278 282 # new feature, get list of available problem in all enabled group that the user belongs to
279 283 def available_problems_in_group
280 284 problem = []
281 285 self.groups.where(enabled: true).each do |group|
282 286 group.problems.where(available: true).each { |p| problem << p }
283 287 end
284 288 problem.uniq!
285 289 if problem
286 290 problem.sort! do |a,b|
287 291 case
288 292 when a.date_added < b.date_added
289 293 1
290 294 when a.date_added > b.date_added
291 295 -1
292 296 else
293 297 a.name <=> b.name
294 298 end
295 299 end
296 300 return problem
297 301 else
298 302 return []
299 303 end
300 304 end
301 305
302 306 #check if the user has the right to view that problem
303 307 #this also consider group based problem policy
304 308 def can_view_problem?(problem)
305 309 return true if admin?
306 310 return available_problems.include? problem
307 311 end
308 312
309 313 def self.clear_last_login
310 314 User.update_all(:last_ip => nil)
311 315 end
312 316
313 317 #create multiple user, one per lines of input
314 318 def self.create_from_list(lines)
315 319 error_logins = []
316 320 first_error = nil
317 321 created_users = []
318 322
319 323 lines.split("\n").each do |line|
320 324 #split with large limit, this will cause consecutive ',' to be result in a blank
321 325 items = line.chomp.split(',',1000)
322 326 if items.length>=2
323 327 login = items[0]
324 328 full_name = items[1]
325 329 remark =''
326 330 user_alias = ''
327 331
328 332 added_random_password = false
329 333 added_password = false
330 334
331 335 #given password?
332 336 if items.length >= 3
333 337 if items[2].chomp(" ").length > 0
334 338 password = items[2].chomp(" ")
335 339 added_password = true
336 340 end
337 341 else
338 342 password = random_password
339 343 added_random_password=true;
340 344 end
341 345
342 346 #given alias?
343 347 if items.length>= 4 and items[3].chomp(" ").length > 0;
344 348 user_alias = items[3].chomp(" ")
345 349 else
346 350 user_alias = login
347 351 end
348 352
349 353 #given remark?
350 354 has_remark = false
351 355 if items.length>=5
352 356 remark = items[4].strip;
353 357 has_remark = true
354 358 end
355 359
356 360 user = User.find_by_login(login)
357 361 if (user)
358 362 user.full_name = full_name
359 363 user.remark = remark if has_remark
360 364 user.password = password if added_password || added_random_password
361 365 else
362 366 #create a random password if none are given
363 367 password = random_password unless password
364 368 user = User.new({:login => login,
365 369 :full_name => full_name,
366 370 :password => password,
367 371 :password_confirmation => password,
368 372 :alias => user_alias,
369 373 :remark => remark})
370 374 end
371 375 user.activated = true
372 376
373 377 if user.save
374 378 created_users << user
375 379 else
376 380 error_logins << "'#{login}'"
377 381 first_error = user.errors.full_messages.to_sentence unless first_error
378 382 end
379 383 end
380 384 end
381 385
382 386 return {error_logins: error_logins, first_error: first_error, created_users: created_users}
383 387
384 388 end
385 389
386 390 def self.find_non_admin_with_prefix(prefix='')
387 391 users = User.all
388 392 return users.find_all { |u| !(u.admin?) and u.login.index(prefix)==0 }
389 393 end
390 394
391 395 protected
392 396 def encrypt_new_password
393 397 return if password.blank?
394 398 self.salt = (10+rand(90)).to_s
395 399 self.hashed_password = User.encrypt(self.password,self.salt)
396 400 end
397 401
398 402 def assign_default_site
399 403 # have to catch error when migrating (because self.site is not available).
400 404 begin
401 405 if self.site==nil
402 406 self.site = Site.find_by_name('default')
403 407 if self.site==nil
404 408 self.site = Site.find(1) # when 'default has be renamed'
405 409 end
406 410 end
407 411 rescue
408 412 end
409 413 end
410 414
411 415 def assign_default_contest
412 416 # have to catch error when migrating (because self.site is not available).
413 417 begin
414 418 if self.contests.length == 0
415 419 default_contest = Contest.find_by_name(GraderConfiguration['contest.default_contest_name'])
416 420 if default_contest
417 421 self.contests = [default_contest]
418 422 end
419 423 end
420 424 rescue
421 425 end
422 426 end
423 427
424 428 def password_required?
425 429 self.hashed_password.blank? || !self.password.blank?
426 430 end
427 431
428 432 def self.encrypt(string,salt)
429 433 Digest::SHA1.hexdigest(salt + string)
430 434 end
431 435
432 436 def uniqueness_of_email_from_activated_users
433 437 user = User.activated_users.find_by_email(self.email)
434 438 if user and (user.login != self.login)
435 439 self.errors.add(:base,"Email has already been taken")
436 440 end
437 441 end
438 442
439 443 def enough_time_interval_between_same_email_registrations
440 444 return if !self.new_record?
441 445 return if self.activated
442 446 open_user = User.find_by_email(self.email,
443 447 :order => 'created_at DESC')
444 448 if open_user and open_user.created_at and
445 449 (open_user.created_at > Time.now.gmtime - 5.minutes)
446 450 self.errors.add(:base,"There are already unactivated registrations with this e-mail address (please wait for 5 minutes)")
447 451 end
448 452 end
449 453
450 454 def email_validation?
451 455 begin
452 456 return VALIDATE_USER_EMAILS
453 457 rescue
454 458 return false
455 459 end
456 460 end
457 461 end
@@ -1,82 +1,82
1 1 .container-fluid
2 2 .row
3 3 .col-md-6
4 4 %h1 Group #{@group.name}
5 5 .row
6 6 .col-md-6
7 7 %b Description:
8 8 = @group.description
9 9 %br
10 10 = link_to 'Edit', edit_group_path(@group), class: 'btn btn-primary'
11 11 .row
12 12 .col-md-12
13 13 %h1 Group details
14 14 .row
15 15 .col-md-6
16 16 .panel.panel-default
17 17 .panel-heading
18 18 .panel-title Users in this group
19 19 .panel-body
20 20 %ul
21 21 %li
22 22 If you want to add several users to a group, it may be easier to just re-import those users in
23 23 = link_to 'New list of users', new_list_user_admin_index_path
24 24 page. You can also use
25 25 = link_to 'Bulk Manage User', bulk_manage_user_admin_index_path
26 26 page.
27 27 =form_tag add_user_group_path(@group), class: 'form-inline' do
28 28 .form-group
29 29 =label_tag :user_id, "User"
30 - =select_tag :user_id, options_from_collection_for_select(User.all,'id','full_name'), class: 'select2', style: 'width: 10em';
30 + =select_tag :user_id, options_from_collection_for_select(User.all,'id','login_with_name'), class: 'select2', style: 'width: 25em';
31 31 =submit_tag "Add",class: 'btn btn-primary'
32 32
33 33
34 34 %table.table.table-hover
35 35 %thead
36 36 %tr
37 37 %th Login
38 38 %th Full name
39 39 %th Remark
40 40 %th= link_to 'Remove All', remove_all_user_group_path(@group), method: :delete, :data => { :confirm => "Remove ALL USERS from group?" }, class: 'btn btn-danger btn-sm'
41 41
42 42 %tbody
43 43 - @group.users.each do |user|
44 44 %tr
45 45 %td= user.login
46 46 %td= user.full_name
47 47 %td= user.remark
48 48 %td= link_to 'Remove', remove_user_group_path(@group,user), :method => :delete, :data => { :confirm => "Remove #{user.full_name}?" }, class: 'btn btn-danger btn-sm'
49 49 .col-md-6
50 50 .panel.panel-default
51 51 .panel-heading
52 52 .panel-title Problems
53 53 .panel-body
54 54 %ul
55 55 %li
56 56 If you want to add several problem to a group, it may be easier to bulk manage them in the
57 57 = link_to 'Bulk Manage Problems', manage_problems_path
58 58 page
59 59 =form_tag add_problem_group_path(@group), class: 'form-inline' do
60 60 .form-group
61 61 =label_tag :problem_id, "Problem"
62 - =select_tag :problem_id, options_from_collection_for_select(Problem.all,'id','full_name'), class: 'select2', style: 'width: 10em';
62 + =select_tag :problem_id, options_from_collection_for_select(Problem.all,'id','long_name'), class: 'select2', style: 'width: 25em';
63 63 =submit_tag "Add",class: 'btn btn-primary'
64 64
65 65
66 66 %table.table.table-hover
67 67 %thead
68 68 %tr
69 69 %th name
70 70 %th Full name
71 71 %th Full score
72 72 %th= link_to 'Remove All', remove_all_problem_group_path(@group), method: :delete, :data => { :confirm => "Remove ALL PROBLEMS from group?" }, class: 'btn btn-danger btn-sm'
73 73
74 74 %tbody
75 75 - @group.problems.each do |problem|
76 76 %tr
77 77 %td= problem.name
78 78 %td= problem.full_name
79 79 %td= problem.full_score
80 80 %td= link_to 'Remove', remove_problem_group_path(@group,problem), :method => :delete, :data => { :confirm => "Remove #{problem.full_name}?" }, class: 'btn btn-danger btn-sm'
81 81
82 82
You need to be logged in to leave comments. Login now