Description:
make group_user and group_problem unique
Commit status:
[Not Reviewed]
References:
Comments:
0 Commit comments 0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
Add another comment

r678:6d499b0bc3c9 - - 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
@@ -31,56 +31,64
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
@@ -176,52 +176,62
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
@@ -421,49 +421,60
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|
@@ -179,43 +179,43
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,29 +1,33
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'
@@ -1,34 +1,37
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
@@ -1,31 +1,31
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
@@ -15,75 +15,79
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|
You need to be logged in to leave comments. Login now