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

r689:124074894d4b - - 16 files changed: 106 inserted, 21 deleted

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