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

r672:d41325ea3a15 - - 20 files changed: 365 inserted, 10 deleted

@@ -0,0 +1,86
1 + class GroupsController < ApplicationController
2 + before_action :set_group, only: [:show, :edit, :update, :destroy,
3 + :add_user, :remove_user,
4 + :add_problem, :remove_problem,
5 + ]
6 + before_action :authenticate, :admin_authorization
7 +
8 + # GET /groups
9 + def index
10 + @groups = Group.all
11 + end
12 +
13 + # GET /groups/1
14 + def show
15 + end
16 +
17 + # GET /groups/new
18 + def new
19 + @group = Group.new
20 + end
21 +
22 + # GET /groups/1/edit
23 + def edit
24 + end
25 +
26 + # POST /groups
27 + def create
28 + @group = Group.new(group_params)
29 +
30 + if @group.save
31 + redirect_to @group, notice: 'Group was successfully created.'
32 + else
33 + render :new
34 + end
35 + end
36 +
37 + # PATCH/PUT /groups/1
38 + def update
39 + if @group.update(group_params)
40 + redirect_to @group, notice: 'Group was successfully updated.'
41 + else
42 + render :edit
43 + end
44 + end
45 +
46 + # DELETE /groups/1
47 + def destroy
48 + @group.destroy
49 + redirect_to groups_url, notice: 'Group was successfully destroyed.'
50 + end
51 +
52 + def remove_user
53 + user = User.find(params[:user_id])
54 + @group.users.delete(user)
55 + redirect_to group_path(@group), notice: "User #{user.login} was removed from the group #{@group.name}"
56 + end
57 +
58 + def add_user
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}"
62 + end
63 +
64 + def remove_problem
65 + problem = Problem.find(params[:problem_id])
66 + @group.problems.delete(problem)
67 + redirect_to group_path(@group), notice: "Problem #{problem.name} was removed from the group #{@group.name}"
68 + end
69 +
70 + def add_problem
71 + 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}"
74 + end
75 +
76 + private
77 + # Use callbacks to share common setup or constraints between actions.
78 + def set_group
79 + @group = Group.find(params[:id])
80 + end
81 +
82 + # Only allow a trusted parameter "white list" through.
83 + def group_params
84 + params.require(:group).permit(:name, :description)
85 + end
86 + end
@@ -0,0 +1,2
1 + module GroupsHelper
2 + end
@@ -0,0 +1,5
1 + class Group < ActiveRecord::Base
2 + has_and_belongs_to_many :problems
3 + has_and_belongs_to_many :users
4 + end
5 +
@@ -0,0 +1,16
1 + = form_for @group do |f|
2 + - if @group.errors.any?
3 + #error_explanation
4 + %h2= "#{pluralize(@group.errors.count, "error")} prohibited this group from being saved:"
5 + %ul
6 + - @group.errors.full_messages.each do |msg|
7 + %li= msg
8 +
9 + .form-group.field
10 + = f.label :name
11 + = f.text_field :name, class: 'form-control'
12 + .form-group.field
13 + = f.label :description
14 + = f.text_field :description, class: 'form-control'
15 + .form-group.actions
16 + = f.submit 'Save', class: 'btn btn-primary'
@@ -0,0 +1,7
1 + %h1 Editing group
2 +
3 + = render 'form'
4 +
5 + = link_to 'Show', @group
6 + \|
7 + = link_to 'Back', groups_path
@@ -0,0 +1,24
1 + %h1 Groups
2 +
3 + %p
4 + = link_to 'New Group', new_group_path, class: 'btn btn-primary'
5 + %table.table.table-hover
6 + %thead
7 + %tr
8 + %th Name
9 + %th Description
10 + %th
11 + %th
12 + %th
13 +
14 + %tbody
15 + - @groups.each do |group|
16 + %tr
17 + %td= group.name
18 + %td= group.description
19 + %td= link_to 'Show', group, class: 'btn btn-default'
20 + %td= link_to 'Edit', edit_group_path(group), class: 'btn btn-default'
21 + %td= link_to 'Destroy', group, :method => :delete, :data => { :confirm => 'Are you sure?' }, class: 'btn btn-danger'
22 +
23 + %br
24 +
@@ -0,0 +1,5
1 + %h1 New group
2 +
3 + = render 'form'
4 +
5 + = link_to 'Back', groups_path
@@ -0,0 +1,67
1 + %p#notice= notice
2 +
3 + %p
4 + %b Name:
5 + = @group.name
6 + %p
7 + %b Description:
8 + = @group.description
9 +
10 + %br
11 + = link_to 'Edit', edit_group_path(@group)
12 + \|
13 + = link_to 'Back', groups_path
14 +
15 + .row
16 + .col-md-6
17 + %h1 Users in this group
18 +
19 + =form_tag add_user_group_path(@group), class: 'form-inline' do
20 + .form-group
21 + =label_tag :user_id, "User"
22 + =select_tag :user_id, options_from_collection_for_select(User.all,'id','full_name'), class: 'select2'
23 + =submit_tag "Add",class: 'btn btn-primary'
24 +
25 +
26 + %table.table.table-hover
27 + %thead
28 + %tr
29 + %th Login
30 + %th Full name
31 + %th Remark
32 + %th
33 +
34 + %tbody
35 + - @group.users.each do |user|
36 + %tr
37 + %td= user.login
38 + %td= user.full_name
39 + %td= user.remark
40 + %td= link_to 'Remove', remove_user_group_path(@group,user), :method => :delete, :data => { :confirm => "Remove #{user.full_name}?" }, class: 'btn btn-danger'
41 + .col-md-6
42 + %h1 Problems in this group
43 +
44 + =form_tag add_problem_group_path(@group), class: 'form-inline' do
45 + .form-group
46 + =label_tag :problem_id, "Problem"
47 + =select_tag :problem_id, options_from_collection_for_select(Problem.all,'id','full_name'), class: 'select2'
48 + =submit_tag "Add",class: 'btn btn-primary'
49 +
50 +
51 + %table.table.table-hover
52 + %thead
53 + %tr
54 + %th name
55 + %th Full name
56 + %th Full score
57 + %th
58 +
59 + %tbody
60 + - @group.problems.each do |problem|
61 + %tr
62 + %td= problem.name
63 + %td= problem.full_name
64 + %td= problem.full_score
65 + %td= link_to 'Remove', remove_problem_group_path(@group,problem), :method => :delete, :data => { :confirm => "Remove #{problem.full_name}?" }, class: 'btn btn-danger'
66 +
67 +
@@ -0,0 +1,30
1 + class CreateGroups < ActiveRecord::Migration
2 +
3 + def change
4 + create_table :groups do |t|
5 + t.string :name
6 + t.string :description
7 + end
8 +
9 + create_join_table :groups, :users do |t|
10 + # t.index [:group_id, :user_id]
11 + t.index [:user_id, :group_id]
12 + end
13 +
14 + create_join_table :problems, :groups do |t|
15 + # t.index [:problem_id, :group_id]
16 + t.index [:group_id, :problem_id]
17 + end
18 +
19 + reversible do |change|
20 + change.up do
21 + GraderConfiguration.where(key: 'system.use_problem_group').first_or_create(value_type: 'boolean', value: 'false',
22 + description: 'If true, available problem to the user will be only ones associated with the group of the user');
23 + end
24 +
25 + change.down do
26 + GraderConfiguration.where(key: 'system.use_problem_group').destroy_all
27 + end
28 + end
29 + end
30 + end
@@ -0,0 +1,49
1 + require 'test_helper'
2 +
3 + class GroupsControllerTest < ActionController::TestCase
4 + setup do
5 + @group = groups(:one)
6 + end
7 +
8 + test "should get index" do
9 + get :index
10 + assert_response :success
11 + assert_not_nil assigns(:groups)
12 + end
13 +
14 + test "should get new" do
15 + get :new
16 + assert_response :success
17 + end
18 +
19 + test "should create group" do
20 + assert_difference('Group.count') do
21 + post :create, group: { description: @group.description, name: @group.name }
22 + end
23 +
24 + assert_redirected_to group_path(assigns(:group))
25 + end
26 +
27 + test "should show group" do
28 + get :show, id: @group
29 + assert_response :success
30 + end
31 +
32 + test "should get edit" do
33 + get :edit, id: @group
34 + assert_response :success
35 + end
36 +
37 + test "should update group" do
38 + patch :update, id: @group, group: { description: @group.description, name: @group.name }
39 + assert_redirected_to group_path(assigns(:group))
40 + end
41 +
42 + test "should destroy group" do
43 + assert_difference('Group.count', -1) do
44 + delete :destroy, id: @group
45 + end
46 +
47 + assert_redirected_to groups_path
48 + end
49 + end
@@ -150,96 +150,101
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 + elsif params.has_key? 'add_group'
199 + group = Group.find(params[:group_id])
200 + get_problems_from_params.each do |p|
201 + group.problems << p
202 + end
198 203 end
199 204 redirect_to :action => 'manage'
200 205 end
201 206
202 207 def import
203 208 @allow_test_pair_import = allow_test_pair_import?
204 209 end
205 210
206 211 def do_import
207 212 old_problem = Problem.find_by_name(params[:name])
208 213 if !allow_test_pair_import? and params.has_key? :import_to_db
209 214 params.delete :import_to_db
210 215 end
211 216 @problem, import_log = Problem.create_from_import_form_params(params,
212 217 old_problem)
213 218
214 219 if !@problem.errors.empty?
215 220 render :action => 'import' and return
216 221 end
217 222
218 223 if old_problem!=nil
219 224 flash[:notice] = "The test data has been replaced for problem #{@problem.name}"
220 225 end
221 226 @log = import_log
222 227 end
223 228
224 229 def remove_contest
225 230 problem = Problem.find(params[:id])
226 231 contest = Contest.find(params[:contest_id])
227 232 if problem!=nil and contest!=nil
228 233 problem.contests.delete(contest)
229 234 end
230 235 redirect_to :action => 'manage'
231 236 end
232 237
233 238 ##################################
234 239 protected
235 240
236 241 def allow_test_pair_import?
237 242 if defined? ALLOW_TEST_PAIR_IMPORT
238 243 return ALLOW_TEST_PAIR_IMPORT
239 244 else
240 245 return false
241 246 end
242 247 end
243 248
244 249 def change_date_added
245 250 problems = get_problems_from_params
@@ -395,100 +395,99
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 ?',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 - if @action[:add_group]
444 - @uses.each do |u|
445 -
446 - end
443 + if @action[:add_group] and @action[:group_name]
444 + @group = Group.find(@action[:group_name])
445 + @users.each { |user| @group.users << user }
447 446 end
448 447 end
449 448 end
450 449
451 450 protected
452 451
453 452 def random_password(length=5)
454 453 chars = 'abcdefghijkmnopqrstuvwxyz23456789'
455 454 newpass = ""
456 455 length.times { newpass << chars[rand(chars.size-1)] }
457 456 return newpass
458 457 end
459 458
460 459 def import_from_file(f)
461 460 data_hash = YAML.load(f)
462 461 @import_log = ""
463 462
464 463 country_data = data_hash[:countries]
465 464 site_data = data_hash[:sites]
466 465 user_data = data_hash[:users]
467 466
468 467 # import country
469 468 countries = {}
470 469 country_data.each_pair do |id,country|
471 470 c = Country.find_by_name(country[:name])
472 471 if c!=nil
473 472 countries[id] = c
474 473 @import_log << "Found #{country[:name]}\n"
475 474 else
476 475 countries[id] = Country.new(:name => country[:name])
477 476 countries[id].save
478 477 @import_log << "Created #{country[:name]}\n"
479 478 end
480 479 end
481 480
482 481 # import sites
483 482 sites = {}
484 483 site_data.each_pair do |id,site|
485 484 s = Site.find_by_name(site[:name])
486 485 if s!=nil
487 486 @import_log << "Found #{site[:name]}\n"
488 487 else
489 488 s = Site.new(:name => site[:name])
490 489 @import_log << "Created #{site[:name]}\n"
491 490 end
492 491 s.password = site[:password]
493 492 s.country = countries[site[:country_id]]
494 493 s.save
@@ -1,62 +1,63
1 1 require 'yaml'
2 2
3 3 #
4 4 # This class also contains various login of the system.
5 5 #
6 6 class GraderConfiguration < ActiveRecord::Base
7 7
8 8 SYSTEM_MODE_CONF_KEY = 'system.mode'
9 9 TEST_REQUEST_EARLY_TIMEOUT_KEY = 'contest.test_request.early_timeout'
10 10 MULTICONTESTS_KEY = 'system.multicontests'
11 11 CONTEST_TIME_LIMIT_KEY = 'contest.time_limit'
12 12 MULTIPLE_IP_LOGIN_KEY = 'right.multiple_ip_login'
13 13 VIEW_TESTCASE = 'right.view_testcase'
14 14 SINGLE_USER_KEY = 'system.single_user_mode'
15 + SYSTEM_USE_PROBLEM_GROUP = 'system.use_problem_group'
15 16
16 17 cattr_accessor :config_cache
17 18 cattr_accessor :task_grading_info_cache
18 19 cattr_accessor :contest_time_str
19 20 cattr_accessor :contest_time
20 21
21 22 GraderConfiguration.config_cache = nil
22 23 GraderConfiguration.task_grading_info_cache = nil
23 24
24 25 def self.config_cached?
25 26 (defined? CONFIGURATION_CACHE_ENABLED) and (CONFIGURATION_CACHE_ENABLED)
26 27 end
27 28
28 29 def self.get(key)
29 30 if GraderConfiguration.config_cached?
30 31 if GraderConfiguration.config_cache == nil
31 32 self.read_config
32 33 end
33 34 return GraderConfiguration.config_cache[key]
34 35 else
35 36 return GraderConfiguration.read_one_key(key)
36 37 end
37 38 end
38 39
39 40 def self.[](key)
40 41 self.get(key)
41 42 end
42 43
43 44 def self.reload
44 45 self.read_config
45 46 end
46 47
47 48 def self.clear
48 49 GraderConfiguration.config_cache = nil
49 50 end
50 51
51 52 #
52 53 # View decision
53 54 #
54 55 def self.show_submitbox_to?(user)
55 56 mode = get(SYSTEM_MODE_CONF_KEY)
56 57 return false if mode=='analysis'
57 58 if (mode=='contest')
58 59 return false if (user.site!=nil) and
59 60 ((user.site.started!=true) or (user.site.finished?))
60 61 end
61 62 return true
62 63 end
@@ -74,96 +75,100
74 75
75 76 def self.show_testcase
76 77 return get(VIEW_TESTCASE)
77 78 end
78 79
79 80 def self.allow_test_request(user)
80 81 mode = get(SYSTEM_MODE_CONF_KEY)
81 82 early_timeout = get(TEST_REQUEST_EARLY_TIMEOUT_KEY)
82 83 if (mode=='contest')
83 84 return false if ((user.site!=nil) and
84 85 ((user.site.started!=true) or
85 86 (early_timeout and (user.site.time_left < 30.minutes))))
86 87 end
87 88 return false if mode=='analysis'
88 89 return true
89 90 end
90 91
91 92 def self.task_grading_info
92 93 if GraderConfiguration.task_grading_info_cache==nil
93 94 read_grading_info
94 95 end
95 96 return GraderConfiguration.task_grading_info_cache
96 97 end
97 98
98 99 def self.standard_mode?
99 100 return get(SYSTEM_MODE_CONF_KEY) == 'standard'
100 101 end
101 102
102 103 def self.contest_mode?
103 104 return get(SYSTEM_MODE_CONF_KEY) == 'contest'
104 105 end
105 106
106 107 def self.indv_contest_mode?
107 108 return get(SYSTEM_MODE_CONF_KEY) == 'indv-contest'
108 109 end
109 110
110 111 def self.multicontests?
111 112 return get(MULTICONTESTS_KEY) == true
112 113 end
113 114
114 115 def self.time_limit_mode?
115 116 mode = get(SYSTEM_MODE_CONF_KEY)
116 117 return ((mode == 'contest') or (mode == 'indv-contest'))
117 118 end
118 119
119 120 def self.analysis_mode?
120 121 return get(SYSTEM_MODE_CONF_KEY) == 'analysis'
121 122 end
123 +
124 + def self.use_problem_group?
125 + return get(SYSTEM_USE_PROBLEM_GROUP)
126 + end
122 127
123 128 def self.contest_time_limit
124 129 contest_time_str = GraderConfiguration[CONTEST_TIME_LIMIT_KEY]
125 130
126 131 if not defined? GraderConfiguration.contest_time_str
127 132 GraderConfiguration.contest_time_str = nil
128 133 end
129 134
130 135 if GraderConfiguration.contest_time_str != contest_time_str
131 136 GraderConfiguration.contest_time_str = contest_time_str
132 137 if tmatch = /(\d+):(\d+)/.match(contest_time_str)
133 138 h = tmatch[1].to_i
134 139 m = tmatch[2].to_i
135 140
136 141 GraderConfiguration.contest_time = h.hour + m.minute
137 142 else
138 143 GraderConfiguration.contest_time = nil
139 144 end
140 145 end
141 146 return GraderConfiguration.contest_time
142 147 end
143 148
144 149 protected
145 150
146 151 def self.convert_type(val,type)
147 152 case type
148 153 when 'string'
149 154 return val
150 155
151 156 when 'integer'
152 157 return val.to_i
153 158
154 159 when 'boolean'
155 160 return (val=='true')
156 161 end
157 162 end
158 163
159 164 def self.read_config
160 165 GraderConfiguration.config_cache = {}
161 166 GraderConfiguration.all.each do |conf|
162 167 key = conf.key
163 168 val = conf.value
164 169 GraderConfiguration.config_cache[key] = GraderConfiguration.convert_type(val,conf.value_type)
165 170 end
166 171 end
167 172
168 173 def self.read_one_key(key)
169 174 conf = GraderConfiguration.find_by_key(key)
@@ -1,52 +1,53
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_many :test_pairs, :dependent => :delete_all
6 7 has_many :testcases, :dependent => :destroy
7 8
8 9 validates_presence_of :name
9 10 validates_format_of :name, :with => /\A\w+\z/
10 11 validates_presence_of :full_name
11 12
12 13 scope :available, -> { where(available: true) }
13 14
14 15 DEFAULT_TIME_LIMIT = 1
15 16 DEFAULT_MEMORY_LIMIT = 32
16 17
17 18 def self.available_problems
18 19 available.order(date_added: :desc).order(:name)
19 20 #Problem.available.all(:order => "date_added DESC, name ASC")
20 21 end
21 22
22 23 def self.create_from_import_form_params(params, old_problem=nil)
23 24 org_problem = old_problem || Problem.new
24 25 import_params, problem = Problem.extract_params_and_check(params,
25 26 org_problem)
26 27
27 28 if !problem.errors.empty?
28 29 return problem, 'Error importing'
29 30 end
30 31
31 32 problem.full_score = 100
32 33 problem.date_added = Time.new
33 34 problem.test_allowed = true
34 35 problem.output_only = false
35 36 problem.available = false
36 37
37 38 if not problem.save
38 39 return problem, 'Error importing'
39 40 end
40 41
41 42 import_to_db = params.has_key? :import_to_db
42 43
43 44 importer = TestdataImporter.new(problem)
44 45
45 46 if not importer.import_from_file(import_params[:file],
46 47 import_params[:time_limit],
47 48 import_params[:memory_limit],
48 49 import_params[:checker_name],
49 50 import_to_db)
50 51 problem.errors.add(:base,'Import error.')
51 52 end
52 53
@@ -1,57 +1,58
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
11 12 has_many :test_requests, -> {order(submitted_at: DESC)}
12 13
13 14 has_many :messages, -> { order(created_at: DESC) },
14 15 :class_name => "Message",
15 16 :foreign_key => "sender_id"
16 17
17 18 has_many :replied_messages, -> { order(created_at: DESC) },
18 19 :class_name => "Message",
19 20 :foreign_key => "receiver_id"
20 21
21 22 has_one :contest_stat, :class_name => "UserContestStat", :dependent => :destroy
22 23
23 24 belongs_to :site
24 25 belongs_to :country
25 26
26 27 has_and_belongs_to_many :contests, -> { order(:name); uniq}
27 28
28 29 scope :activated_users, -> {where activated: true}
29 30
30 31 validates_presence_of :login
31 32 validates_uniqueness_of :login
32 33 validates_format_of :login, :with => /\A[\_A-Za-z0-9]+\z/
33 34 validates_length_of :login, :within => 3..30
34 35
35 36 validates_presence_of :full_name
36 37 validates_length_of :full_name, :minimum => 1
37 38
38 39 validates_presence_of :password, :if => :password_required?
39 40 validates_length_of :password, :within => 4..20, :if => :password_required?
40 41 validates_confirmation_of :password, :if => :password_required?
41 42
42 43 validates_format_of :email,
43 44 :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i,
44 45 :if => :email_validation?
45 46 validate :uniqueness_of_email_from_activated_users,
46 47 :if => :email_validation?
47 48 validate :enough_time_interval_between_same_email_registrations,
48 49 :if => :email_validation?
49 50
50 51 # these are for ytopc
51 52 # disable for now
52 53 #validates_presence_of :province
53 54
54 55 attr_accessor :password
55 56
56 57 before_save :encrypt_new_password
57 58 before_save :assign_default_site
@@ -245,113 +246,125
245 246 end
246 247 end
247 248
248 249 def problem_in_user_contests?(problem)
249 250 problem_contests = problem.contests.all
250 251
251 252 if problem_contests.length == 0 # this is public contest
252 253 return true
253 254 end
254 255
255 256 contests.each do |contest|
256 257 if problem_contests.find {|c| c.id == contest.id }
257 258 return true
258 259 end
259 260 end
260 261 return false
261 262 end
262 263
263 264 def available_problems_group_by_contests
264 265 contest_problems = []
265 266 pin = {}
266 267 contests.enabled.each do |contest|
267 268 available_problems = contest.problems.available
268 269 contest_problems << {
269 270 :contest => contest,
270 271 :problems => available_problems
271 272 }
272 273 available_problems.each {|p| pin[p.id] = true}
273 274 end
274 275 other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0}
275 276 contest_problems << {
276 277 :contest => nil,
277 278 :problems => other_avaiable_problems
278 279 }
279 280 return contest_problems
280 281 end
281 282
282 283 def solve_all_available_problems?
283 284 available_problems.each do |p|
284 285 u = self
285 286 sub = Submission.find_last_by_user_and_problem(u.id,p.id)
286 287 return false if !p or !sub or sub.points < p.full_score
287 288 end
288 289 return true
289 290 end
290 291
291 292 def available_problems
292 293 if not GraderConfiguration.multicontests?
293 - return Problem.available_problems
294 + if GraderConfiguration.use_problem_group?
295 + return available_problems_in_group
296 + else
297 + return Problem.available_problems
298 + end
294 299 else
295 300 contest_problems = []
296 301 pin = {}
297 302 contests.enabled.each do |contest|
298 303 contest.problems.available.each do |problem|
299 304 if not pin.has_key? problem.id
300 305 contest_problems << problem
301 306 end
302 307 pin[problem.id] = true
303 308 end
304 309 end
305 310 other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0}
306 311 return contest_problems + other_avaiable_problems
307 312 end
308 313 end
309 314
315 + def available_problems_in_group
316 + problem = []
317 + self.groups.each do |group|
318 + group.problems.where(available: true).each { |p| problem << p }
319 + end
320 + return problem.uniq
321 + end
322 +
310 323 def can_view_problem?(problem)
311 324 if not GraderConfiguration.multicontests?
312 325 return problem.available
313 326 else
314 327 return problem_in_user_contests? problem
315 328 end
316 329 end
317 330
318 331 def self.clear_last_login
319 332 User.update_all(:last_ip => nil)
320 333 end
321 334
322 335 protected
323 336 def encrypt_new_password
324 337 return if password.blank?
325 338 self.salt = (10+rand(90)).to_s
326 339 self.hashed_password = User.encrypt(self.password,self.salt)
327 340 end
328 341
329 342 def assign_default_site
330 343 # have to catch error when migrating (because self.site is not available).
331 344 begin
332 345 if self.site==nil
333 346 self.site = Site.find_by_name('default')
334 347 if self.site==nil
335 348 self.site = Site.find(1) # when 'default has be renamed'
336 349 end
337 350 end
338 351 rescue
339 352 end
340 353 end
341 354
342 355 def assign_default_contest
343 356 # have to catch error when migrating (because self.site is not available).
344 357 begin
345 358 if self.contests.length == 0
346 359 default_contest = Contest.find_by_name(GraderConfiguration['contest.default_contest_name'])
347 360 if default_contest
348 361 self.contests = [default_contest]
349 362 end
350 363 end
351 364 rescue
352 365 end
353 366 end
354 367
355 368 def password_required?
356 369 self.hashed_password.blank? || !self.password.blank?
357 370 end
@@ -1,85 +1,90
1 1 - content_for :head do
2 2 = stylesheet_link_tag 'problems'
3 3 = javascript_include_tag 'local_jquery'
4 4
5 5 :javascript
6 6 $(document).ready( function() {
7 7 function shiftclick(start,stop,value) {
8 8 $('tr input').each( function(id,input) {
9 9 var $input=$(input);
10 10 var iid=parseInt($input.attr('id').split('-')[2]);
11 11 if(iid>=start&&iid<=stop){
12 12 $input.prop('checked',value)
13 13 }
14 14 });
15 15 }
16 16
17 17 $('tr input').click( function(e) {
18 18 if (e.shiftKey) {
19 19 stop = parseInt($(this).attr('id').split('-')[2]);
20 20 var orig_stop = stop
21 21 if (typeof start !== 'undefined') {
22 22 if (start > stop) {
23 23 var tmp = start;
24 24 start = stop;
25 25 stop = tmp;
26 26 }
27 27 shiftclick(start,stop,$(this).is(':checked') )
28 28 }
29 29 start = orig_stop
30 30 } else {
31 31 start = parseInt($(this).attr('id').split('-')[2]);
32 32 }
33 33 });
34 34 });
35 35
36 36
37 37 %h1 Manage problems
38 38
39 39 %p= link_to '[Back to problem list]', :action => 'list'
40 40
41 41 = form_tag :action=>'do_manage' do
42 - .submitbox
42 + .submitbox.panel
43 43 What do you want to do to the selected problem?
44 44 %br/
45 45 (You can shift-click to select a range of problems)
46 46 %ul
47 47 %li
48 48 Change date added to
49 49 = select_date Date.current, :prefix => 'date_added'
50 50 &nbsp;&nbsp;&nbsp;
51 51 = submit_tag 'Change', :name => 'change_date_added'
52 52 %li
53 53 Set available to
54 54 = submit_tag 'True', :name => 'enable_problem'
55 55 = submit_tag 'False', :name => 'disable_problem'
56 56
57 57 - if GraderConfiguration.multicontests?
58 58 %li
59 59 Add to
60 60 = select("contest","id",Contest.all.collect {|c| [c.title, c.id]})
61 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'
66 +
62 67
63 - %table
68 + %table.table.table-hover
64 69 %tr{style: "text-align: left;"}
65 70 %th= check_box_tag 'select_all'
66 71 %th Name
67 72 %th Full name
68 73 %th Available
69 74 %th Date added
70 75 - if GraderConfiguration.multicontests?
71 76 %th Contests
72 77
73 78 - num = 0
74 79 - for problem in @problems
75 80 - num += 1
76 81 %tr{:id => "row-prob-#{problem.id}", :name=> "prob-#{problem.id}"}
77 82 %td= check_box_tag "prob-#{problem.id}-#{num}"
78 83 %td= problem.name
79 84 %td= problem.full_name
80 85 %td= problem.available
81 86 %td= problem.date_added
82 87 - if GraderConfiguration.multicontests?
83 88 %td
84 89 - problem.contests.each do |contest|
85 90 = "(#{contest.name} [#{link_to 'x', :action => 'remove_contest', :id => problem.id, :contest_id => contest.id }])"
@@ -8,79 +8,79
8 8 Filter User
9 9 .panel-body
10 10 Filtering users whose login match the following MySQL regex
11 11 .form-group
12 12 = label_tag "regex", 'Regex Pattern'
13 13 = text_field_tag "regex", params[:regex], class: 'form-control'
14 14 %p
15 15 Example
16 16 %ul
17 17 %li
18 18 %code root
19 19 matches every user whose login contains "root"
20 20 %li
21 21 %code ^56
22 22 matches every user whose login starts with "56"
23 23 %li
24 24 %code 21$
25 25 matches every user whose login ends with "21"
26 26 .col-md-6
27 27 .panel.panel-primary
28 28 .panel-title.panel-heading
29 29 Action
30 30 .panel-body
31 31 .row.form-group
32 32 .col-md-6
33 33 %label.checkbox-inline
34 34 = check_box_tag "enabled", true, params[:enabled]
35 35 Change "Enabled" to
36 36 .col-md-3
37 37 %label.radio-inline
38 38 = radio_button_tag "enable", 1, params[:enable] == '1', id: 'enable-yes'
39 39 Yes
40 40 .col-md-3
41 41 %label.radio-inline
42 42 = radio_button_tag "enable", 0, params[:enable] == '0', id: 'enable-no'
43 43 No
44 44 .row.form-group
45 45 .col-md-6
46 46 %label.checkbox-inline
47 47 = check_box_tag "gen_password", true, params[:gen_password]
48 48 Generate new random password
49 49 .row.form-group
50 50 .col-md-4
51 51 %label.checkbox-inline
52 52 = check_box_tag "add_group", true, params[:add_group]
53 53 Add users to group
54 54 %label.col-md-3.control-label.text-right Group name
55 55 .col-md-5
56 - = text_field_tag "group_name", params[:group_name], id: 'group_name',class: 'form-control select2'
56 + = select_tag "group_name", options_from_collection_for_select( Group.all, 'id','name',params[:group_name]), id: 'group_name',class: 'form-control select2'
57 57
58 58
59 59 .row
60 60 .col-md-12
61 61 = submit_tag "Preview Result", class: 'btn btn-default'
62 62 - if @users
63 63 .row
64 64 .col-md-4
65 65 - if @action
66 66 %h2 Confirmation
67 67 - if @action[:set_enable]
68 68 .alert.alert-info The following users will be set #{(@action[:enabled] ? 'enable' : 'disable')}.
69 69 - if @action[:gen_password]
70 70 .alert.alert-info The password of the following users will be randomly generated.
71 71 .row
72 72 .col-md-4
73 73 = submit_tag "Perform", class: 'btn btn-primary'
74 74 .row
75 75 .col-md-12
76 76 The pattern matches #{@users.count} following users.
77 77 %br
78 78 - @users.each do |user|
79 79 = user.login
80 80 = ' '
81 81 = user.full_name
82 82 = ' '
83 83 = "(#{user.remark})" if user.remark
84 84 %br
85 85
86 86
@@ -1,80 +1,88
1 1 CafeGrader::Application.routes.draw do
2 2 get "sources/direct_edit"
3 3
4 4 root :to => 'main#login'
5 5
6 6 #logins
7 7 get 'login/login', to: 'login#login'
8 8
9 9 resources :contests
10 10
11 11 resources :sites
12 12
13 13 resources :announcements do
14 14 member do
15 15 get 'toggle','toggle_front'
16 16 end
17 17 end
18 18
19 19 resources :problems do
20 20 member do
21 21 get 'toggle'
22 22 get 'toggle_test'
23 23 get 'toggle_view_testcase'
24 24 get 'stat'
25 25 end
26 26 collection do
27 27 get 'turn_all_off'
28 28 get 'turn_all_on'
29 29 get 'import'
30 30 get 'manage'
31 31 end
32 + end
32 33
34 + resources :groups do
35 + member do
36 + post 'add_user', to: 'groups#add_user', as: 'add_user'
37 + delete 'remove_user/:user_id', to: 'groups#remove_user', as: 'remove_user'
38 + post 'add_problem', to: 'groups#add_problem', as: 'add_problem'
39 + delete 'remove_problem/:problem_id', to: 'groups#remove_problem', as: 'remove_problem'
40 + end
33 41 end
34 42
35 43 resources :testcases, only: [] do
36 44 member do
37 45 get 'download_input'
38 46 get 'download_sol'
39 47 end
40 48 collection do
41 49 get 'show_problem/:problem_id(/:test_num)' => 'testcases#show_problem', as: 'show_problem'
42 50 end
43 51 end
44 52
45 53 resources :grader_configuration, controller: 'configurations'
46 54
47 55 resources :users do
48 56 member do
49 57 get 'toggle_activate', 'toggle_enable'
50 58 get 'stat'
51 59 end
52 60 end
53 61
54 62 resources :submissions do
55 63 member do
56 64 get 'download'
57 65 get 'compiler_msg'
58 66 get 'rejudge'
59 67 end
60 68 collection do
61 69 get 'prob/:problem_id', to: 'submissions#index', as: 'problem'
62 70 get 'direct_edit_problem/:problem_id', to: 'submissions#direct_edit_problem', as: 'direct_edit_problem'
63 71 get 'get_latest_submission_status/:uid/:pid', to: 'submissions#get_latest_submission_status', as: 'get_latest_submission_status'
64 72 end
65 73 end
66 74
67 75
68 76
69 77 #main
70 78 get "main/list"
71 79 get 'main/submission(/:id)', to: 'main#submission', as: 'main_submission'
72 80
73 81 #user admin
74 82 get 'user_admin/bulk_manage', to: 'user_admin#bulk_manage', as: 'bulk_manage_user_admin'
75 83
76 84 #report
77 85 get 'report/current_score', to: 'report#current_score', as: 'report_current_score'
78 86 get 'report/problem_hof(/:id)', to: 'report#problem_hof', as: 'report_problem_hof'
79 87 get "report/login"
80 88 get 'report/max_score', to: 'report#max_score', as: 'report_max_score'
@@ -1,129 +1,148
1 1 # encoding: UTF-8
2 2 # This file is auto-generated from the current state of the database. Instead
3 3 # of editing this file, please use the migrations feature of Active Record to
4 4 # incrementally modify your database, and then regenerate this schema definition.
5 5 #
6 6 # Note that this schema.rb definition is the authoritative source for your
7 7 # database schema. If you need to create the application database on another
8 8 # system, you should be using db:schema:load, not running all the migrations
9 9 # from scratch. The latter is a flawed and unsustainable approach (the more migrations
10 10 # you'll amass, the slower it'll run and the greater likelihood for issues).
11 11 #
12 12 # It's strongly recommended that you check this file into your version control system.
13 13
14 - ActiveRecord::Schema.define(version: 20170427070345) do
14 + ActiveRecord::Schema.define(version: 20170911091143) do
15 15
16 16 create_table "announcements", force: :cascade do |t|
17 17 t.string "author", limit: 255
18 18 t.text "body", limit: 65535
19 19 t.boolean "published"
20 20 t.datetime "created_at", null: false
21 21 t.datetime "updated_at", null: false
22 22 t.boolean "frontpage", default: false
23 23 t.boolean "contest_only", default: false
24 24 t.string "title", limit: 255
25 25 t.string "notes", limit: 255
26 26 end
27 27
28 28 create_table "contests", force: :cascade do |t|
29 29 t.string "title", limit: 255
30 30 t.boolean "enabled"
31 31 t.datetime "created_at", null: false
32 32 t.datetime "updated_at", null: false
33 33 t.string "name", limit: 255
34 34 end
35 35
36 36 create_table "contests_problems", id: false, force: :cascade do |t|
37 37 t.integer "contest_id", limit: 4
38 38 t.integer "problem_id", limit: 4
39 39 end
40 40
41 41 create_table "contests_users", id: false, force: :cascade do |t|
42 42 t.integer "contest_id", limit: 4
43 43 t.integer "user_id", limit: 4
44 44 end
45 45
46 46 create_table "countries", force: :cascade do |t|
47 47 t.string "name", limit: 255
48 48 t.datetime "created_at", null: false
49 49 t.datetime "updated_at", null: false
50 50 end
51 51
52 52 create_table "descriptions", force: :cascade do |t|
53 53 t.text "body", limit: 65535
54 54 t.boolean "markdowned"
55 55 t.datetime "created_at", null: false
56 56 t.datetime "updated_at", null: false
57 57 end
58 58
59 59 create_table "grader_configurations", force: :cascade do |t|
60 60 t.string "key", limit: 255
61 61 t.string "value_type", limit: 255
62 62 t.string "value", limit: 255
63 63 t.datetime "created_at", null: false
64 64 t.datetime "updated_at", null: false
65 65 t.text "description", limit: 65535
66 66 end
67 67
68 68 create_table "grader_processes", force: :cascade do |t|
69 69 t.string "host", limit: 255
70 70 t.integer "pid", limit: 4
71 71 t.string "mode", limit: 255
72 72 t.boolean "active"
73 73 t.datetime "created_at", null: false
74 74 t.datetime "updated_at", null: false
75 75 t.integer "task_id", limit: 4
76 76 t.string "task_type", limit: 255
77 77 t.boolean "terminated"
78 78 end
79 79
80 80 add_index "grader_processes", ["host", "pid"], name: "index_grader_processes_on_ip_and_pid", using: :btree
81 81
82 + create_table "groups", force: :cascade do |t|
83 + t.string "name", limit: 255
84 + t.string "description", limit: 255
85 + end
86 +
87 + create_table "groups_problems", id: false, force: :cascade do |t|
88 + t.integer "problem_id", limit: 4, null: false
89 + t.integer "group_id", limit: 4, null: false
90 + end
91 +
92 + add_index "groups_problems", ["group_id", "problem_id"], name: "index_groups_problems_on_group_id_and_problem_id", using: :btree
93 +
94 + create_table "groups_users", id: false, force: :cascade do |t|
95 + t.integer "group_id", limit: 4, null: false
96 + t.integer "user_id", limit: 4, null: false
97 + end
98 +
99 + add_index "groups_users", ["user_id", "group_id"], name: "index_groups_users_on_user_id_and_group_id", using: :btree
100 +
82 101 create_table "heart_beats", force: :cascade do |t|
83 102 t.integer "user_id", limit: 4
84 103 t.string "ip_address", limit: 255
85 104 t.datetime "created_at", null: false
86 105 t.datetime "updated_at", null: false
87 106 t.string "status", limit: 255
88 107 end
89 108
90 109 add_index "heart_beats", ["updated_at"], name: "index_heart_beats_on_updated_at", using: :btree
91 110
92 111 create_table "languages", force: :cascade do |t|
93 112 t.string "name", limit: 10
94 113 t.string "pretty_name", limit: 255
95 114 t.string "ext", limit: 10
96 115 t.string "common_ext", limit: 255
97 116 end
98 117
99 118 create_table "logins", force: :cascade do |t|
100 119 t.integer "user_id", limit: 4
101 120 t.string "ip_address", limit: 255
102 121 t.datetime "created_at", null: false
103 122 t.datetime "updated_at", null: false
104 123 end
105 124
106 125 create_table "messages", force: :cascade do |t|
107 126 t.integer "sender_id", limit: 4
108 127 t.integer "receiver_id", limit: 4
109 128 t.integer "replying_message_id", limit: 4
110 129 t.text "body", limit: 65535
111 130 t.boolean "replied"
112 131 t.datetime "created_at", null: false
113 132 t.datetime "updated_at", null: false
114 133 end
115 134
116 135 create_table "problems", force: :cascade do |t|
117 136 t.string "name", limit: 30
118 137 t.string "full_name", limit: 255
119 138 t.integer "full_score", limit: 4
120 139 t.date "date_added"
121 140 t.boolean "available"
122 141 t.string "url", limit: 255
123 142 t.integer "description_id", limit: 4
124 143 t.boolean "test_allowed"
125 144 t.boolean "output_only"
126 145 t.string "description_filename", limit: 255
127 146 t.boolean "view_testcase"
128 147 end
129 148
@@ -118,97 +118,106
118 118 },
119 119
120 120 {
121 121 :key => 'system.admin_email',
122 122 :value_type => 'string',
123 123 :default_value => 'admin@admin.email'
124 124 },
125 125
126 126 {
127 127 :key => 'system.user_setting_enabled',
128 128 :value_type => 'boolean',
129 129 :default_value => 'true',
130 130 :description => 'If this option is true, users can change their settings'
131 131 },
132 132
133 133 {
134 134 :key => 'system.user_setting_enabled',
135 135 :value_type => 'boolean',
136 136 :default_value => 'true',
137 137 :description => 'If this option is true, users can change their settings'
138 138 },
139 139
140 140 # If Configuration['contest.test_request.early_timeout'] is true
141 141 # the user will not be able to use test request at 30 minutes
142 142 # before the contest ends.
143 143 {
144 144 :key => 'contest.test_request.early_timeout',
145 145 :value_type => 'boolean',
146 146 :default_value => 'false'
147 147 },
148 148
149 149 {
150 150 :key => 'system.multicontests',
151 151 :value_type => 'boolean',
152 152 :default_value => 'false'
153 153 },
154 154
155 155 {
156 156 :key => 'contest.confirm_indv_contest_start',
157 157 :value_type => 'boolean',
158 158 :default_value => 'false'
159 159 },
160 160
161 161 {
162 162 :key => 'contest.default_contest_name',
163 163 :value_type => 'string',
164 164 :default_value => 'none',
165 165 :description => "New user will be assigned to this contest automatically, if it exists. Set to 'none' if there is no default contest."
166 - }
166 + },
167 +
168 + {
169 + :key => 'system.use_problem_group',
170 + :value_type => 'boolean',
171 + :default_value => 'false',
172 + :description => "If true, available problem to the user will be only ones associated with the group of the user."
173 + },
174 +
175 +
167 176
168 177 ]
169 178
170 179
171 180 def create_configuration_key(key,
172 181 value_type,
173 182 default_value,
174 183 description='')
175 184 conf = (GraderConfiguration.find_by_key(key) ||
176 185 GraderConfiguration.new(:key => key,
177 186 :value_type => value_type,
178 187 :value => default_value))
179 188 conf.description = description
180 189 conf.save
181 190 end
182 191
183 192 def seed_config
184 193 CONFIGURATIONS.each do |conf|
185 194 if conf.has_key? :description
186 195 desc = conf[:description]
187 196 else
188 197 desc = ''
189 198 end
190 199 create_configuration_key(conf[:key],
191 200 conf[:value_type],
192 201 conf[:default_value],
193 202 desc)
194 203 end
195 204 end
196 205
197 206 def seed_roles
198 207 return if Role.find_by_name('admin')
199 208
200 209 role = Role.create(:name => 'admin')
201 210 user_admin_right = Right.create(:name => 'user_admin',
202 211 :controller => 'user_admin',
203 212 :action => 'all')
204 213 problem_admin_right = Right.create(:name=> 'problem_admin',
205 214 :controller => 'problems',
206 215 :action => 'all')
207 216
208 217 graders_right = Right.create(:name => 'graders_admin',
209 218 :controller => 'graders',
210 219 :action => 'all')
211 220
212 221 role.rights << user_admin_right;
213 222 role.rights << problem_admin_right;
214 223 role.rights << graders_right;
You need to be logged in to leave comments. Login now