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

r880:31e27f513ce9 - - 9 files changed: 173 inserted, 142 deleted

@@ -1,287 +1,290
1 class ProblemsController < ApplicationController
1 class ProblemsController < ApplicationController
2
2
3 include ActiveStorage::SetCurrent
3 include ActiveStorage::SetCurrent
4
4
5 before_action :admin_authorization, except: [:stat]
5 before_action :admin_authorization, except: [:stat]
6 before_action :set_problem, only: [:show, :edit, :update, :destroy, :get_statement, :toggle, :toggle_test, :toggle_view_testcase, :stat]
6 before_action :set_problem, only: [:show, :edit, :update, :destroy, :get_statement, :toggle, :toggle_test, :toggle_view_testcase, :stat]
7 before_action only: [:stat] do
7 before_action only: [:stat] do
8 authorization_by_roles(['admin','ta'])
8 authorization_by_roles(['admin','ta'])
9 end
9 end
10
10
11
11
12 def index
12 def index
13 @problems = Problem.order(date_added: :desc)
13 @problems = Problem.order(date_added: :desc)
14 end
14 end
15
15
16
16
17 def show
17 def show
18 end
18 end
19
19
20 #get statement download link
20 #get statement download link
21 def get_statement
21 def get_statement
22 unless @current_user.can_view_problem? @problem
22 unless @current_user.can_view_problem? @problem
23 redirect_to list_main_path, error: 'You are not authorized to access this file'
23 redirect_to list_main_path, error: 'You are not authorized to access this file'
24 return
24 return
25 end
25 end
26
26
27 if params[:ext]=='pdf'
27 if params[:ext]=='pdf'
28 content_type = 'application/pdf'
28 content_type = 'application/pdf'
29 else
29 else
30 content_type = 'application/octet-stream'
30 content_type = 'application/octet-stream'
31 end
31 end
32
32
33 filename = @problem.statement.filename.to_s
33 filename = @problem.statement.filename.to_s
34 data =@problem.statement.download
34 data =@problem.statement.download
35
35
36 send_data data, stream: false, disposition: 'inline', filename: filename, type: content_type
36 send_data data, stream: false, disposition: 'inline', filename: filename, type: content_type
37 end
37 end
38
38
39 def new
39 def new
40 @problem = Problem.new
40 @problem = Problem.new
41 end
41 end
42
42
43 def create
43 def create
44 @problem = Problem.new(problem_params)
44 @problem = Problem.new(problem_params)
45 if @problem.save
45 if @problem.save
46 redirect_to action: :index, notice: 'Problem was successfully created.'
46 redirect_to action: :index, notice: 'Problem was successfully created.'
47 else
47 else
48 render :action => 'new'
48 render :action => 'new'
49 end
49 end
50 end
50 end
51
51
52 def quick_create
52 def quick_create
53 @problem = Problem.new(problem_params)
53 @problem = Problem.new(problem_params)
54 @problem.full_name = @problem.name if @problem.full_name == ''
54 @problem.full_name = @problem.name if @problem.full_name == ''
55 @problem.full_score = 100
55 @problem.full_score = 100
56 @problem.available = false
56 @problem.available = false
57 @problem.test_allowed = true
57 @problem.test_allowed = true
58 @problem.output_only = false
58 @problem.output_only = false
59 @problem.date_added = Time.new
59 @problem.date_added = Time.new
60 if @problem.save
60 if @problem.save
61 flash[:notice] = 'Problem was successfully created.'
61 flash[:notice] = 'Problem was successfully created.'
62 redirect_to action: :index
62 redirect_to action: :index
63 else
63 else
64 flash[:notice] = 'Error saving problem'
64 flash[:notice] = 'Error saving problem'
65 redirect_to action: :index
65 redirect_to action: :index
66 end
66 end
67 end
67 end
68
68
69 def edit
69 def edit
70 @description = @problem.description
70 @description = @problem.description
71 end
71 end
72
72
73 def update
73 def update
74 if problem_params[:statement] && problem_params[:statement].content_type != 'application/pdf'
74 if problem_params[:statement] && problem_params[:statement].content_type != 'application/pdf'
75 flash[:error] = 'Error: Uploaded file is not PDF'
75 flash[:error] = 'Error: Uploaded file is not PDF'
76 render :action => 'edit'
76 render :action => 'edit'
77 return
77 return
78 end
78 end
79 if @problem.update(problem_params)
79 if @problem.update(problem_params)
80 flash[:notice] = 'Problem was successfully updated. '
80 flash[:notice] = 'Problem was successfully updated. '
81 flash[:notice] += 'A new statement PDF is uploaded' if problem_params[:statement]
81 flash[:notice] += 'A new statement PDF is uploaded' if problem_params[:statement]
82 @problem.save
82 @problem.save
83 redirect_to edit_problem_path(@problem)
83 redirect_to edit_problem_path(@problem)
84 else
84 else
85 render :action => 'edit'
85 render :action => 'edit'
86 end
86 end
87 end
87 end
88
88
89 def destroy
89 def destroy
90 @problem.destroy
90 @problem.destroy
91 redirect_to action: :index
91 redirect_to action: :index
92 end
92 end
93
93
94 def toggle
94 def toggle
95 @problem.update(available: !(@problem.available) )
95 @problem.update(available: !(@problem.available) )
96 respond_to do |format|
96 respond_to do |format|
97 format.js { }
97 format.js { }
98 end
98 end
99 end
99 end
100
100
101 def toggle_test
101 def toggle_test
102 @problem.update(test_allowed: !(@problem.test_allowed?) )
102 @problem.update(test_allowed: !(@problem.test_allowed?) )
103 respond_to do |format|
103 respond_to do |format|
104 format.js { }
104 format.js { }
105 end
105 end
106 end
106 end
107
107
108 def toggle_view_testcase
108 def toggle_view_testcase
109 @problem.update(view_testcase: !(@problem.view_testcase?) )
109 @problem.update(view_testcase: !(@problem.view_testcase?) )
110 respond_to do |format|
110 respond_to do |format|
111 format.js { }
111 format.js { }
112 end
112 end
113 end
113 end
114
114
115 def turn_all_off
115 def turn_all_off
116 Problem.available.all.each do |problem|
116 Problem.available.all.each do |problem|
117 problem.available = false
117 problem.available = false
118 problem.save
118 problem.save
119 end
119 end
120 redirect_to action: :index
120 redirect_to action: :index
121 end
121 end
122
122
123 def turn_all_on
123 def turn_all_on
124 Problem.where.not(available: true).each do |problem|
124 Problem.where.not(available: true).each do |problem|
125 problem.available = true
125 problem.available = true
126 problem.save
126 problem.save
127 end
127 end
128 redirect_to action: :index
128 redirect_to action: :index
129 end
129 end
130
130
131 def stat
131 def stat
132 unless @problem.available or session[:admin]
132 unless @problem.available or session[:admin]
133 redirect_to :controller => 'main', :action => 'list'
133 redirect_to :controller => 'main', :action => 'list'
134 return
134 return
135 end
135 end
136 @submissions = Submission.includes(:user).includes(:language).where(problem_id: params[:id]).order(:user_id,:id)
136 @submissions = Submission.includes(:user).includes(:language).where(problem_id: params[:id]).order(:user_id,:id)
137
137
138 #stat summary
138 #stat summary
139 range =65
139 range =65
140 @histogram = { data: Array.new(range,0), summary: {} }
140 @histogram = { data: Array.new(range,0), summary: {} }
141 user = Hash.new(0)
141 user = Hash.new(0)
142 @submissions.find_each do |sub|
142 @submissions.find_each do |sub|
143 d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60
143 d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60
144 @histogram[:data][d.to_i] += 1 if d < range
144 @histogram[:data][d.to_i] += 1 if d < range
145 user[sub.user_id] = [user[sub.user_id], ((sub.try(:points) || 0) >= @problem.full_score) ? 1 : 0].max
145 user[sub.user_id] = [user[sub.user_id], ((sub.try(:points) || 0) >= @problem.full_score) ? 1 : 0].max
146 end
146 end
147 @histogram[:summary][:max] = [@histogram[:data].max,1].max
147 @histogram[:summary][:max] = [@histogram[:data].max,1].max
148
148
149 @summary = { attempt: user.count, solve: 0 }
149 @summary = { attempt: user.count, solve: 0 }
150 user.each_value { |v| @summary[:solve] += 1 if v == 1 }
150 user.each_value { |v| @summary[:solve] += 1 if v == 1 }
151 +
152 + #for new graph
153 + @chart_dataset = @problem.get_jschart_history.to_json.html_safe
151 end
154 end
152
155
153 def manage
156 def manage
154 @problems = Problem.order(date_added: :desc)
157 @problems = Problem.order(date_added: :desc)
155 end
158 end
156
159
157 def do_manage
160 def do_manage
158 if params.has_key? 'change_date_added' and params[:date_added].strip.empty? == false
161 if params.has_key? 'change_date_added' and params[:date_added].strip.empty? == false
159 change_date_added
162 change_date_added
160 elsif params.has_key? 'add_to_contest'
163 elsif params.has_key? 'add_to_contest'
161 add_to_contest
164 add_to_contest
162 elsif params.has_key? 'enable_problem'
165 elsif params.has_key? 'enable_problem'
163 set_available(true)
166 set_available(true)
164 elsif params.has_key? 'disable_problem'
167 elsif params.has_key? 'disable_problem'
165 set_available(false)
168 set_available(false)
166 elsif params.has_key? 'add_group'
169 elsif params.has_key? 'add_group'
167 group = Group.find(params[:group_id])
170 group = Group.find(params[:group_id])
168 ok = []
171 ok = []
169 failed = []
172 failed = []
170 get_problems_from_params.each do |p|
173 get_problems_from_params.each do |p|
171 begin
174 begin
172 group.problems << p
175 group.problems << p
173 ok << p.full_name
176 ok << p.full_name
174 rescue => e
177 rescue => e
175 failed << p.full_name
178 failed << p.full_name
176 end
179 end
177 end
180 end
178 flash[:success] = "The following problems are added to the group #{group.name}: " + ok.join(', ') if ok.count > 0
181 flash[:success] = "The following problems are added to the group #{group.name}: " + ok.join(', ') if ok.count > 0
179 flash[:alert] = "The following problems are already in the group #{group.name}: " + failed.join(', ') if failed.count > 0
182 flash[:alert] = "The following problems are already in the group #{group.name}: " + failed.join(', ') if failed.count > 0
180 elsif params.has_key? 'add_tags'
183 elsif params.has_key? 'add_tags'
181 get_problems_from_params.each do |p|
184 get_problems_from_params.each do |p|
182 p.tag_ids += params[:tag_ids]
185 p.tag_ids += params[:tag_ids]
183 end
186 end
184 end
187 end
185
188
186 redirect_to :action => 'manage'
189 redirect_to :action => 'manage'
187 end
190 end
188
191
189 def import
192 def import
190 @allow_test_pair_import = allow_test_pair_import?
193 @allow_test_pair_import = allow_test_pair_import?
191 end
194 end
192
195
193 def do_import
196 def do_import
194 old_problem = Problem.find_by_name(params[:name])
197 old_problem = Problem.find_by_name(params[:name])
195 if !allow_test_pair_import? and params.has_key? :import_to_db
198 if !allow_test_pair_import? and params.has_key? :import_to_db
196 params.delete :import_to_db
199 params.delete :import_to_db
197 end
200 end
198 @problem, import_log = Problem.create_from_import_form_params(params,
201 @problem, import_log = Problem.create_from_import_form_params(params,
199 old_problem)
202 old_problem)
200
203
201 if !@problem.errors.empty?
204 if !@problem.errors.empty?
202 render :action => 'import' and return
205 render :action => 'import' and return
203 end
206 end
204
207
205 if old_problem!=nil
208 if old_problem!=nil
206 flash[:notice] = "The test data has been replaced for problem #{@problem.name}"
209 flash[:notice] = "The test data has been replaced for problem #{@problem.name}"
207 end
210 end
208 @log = import_log
211 @log = import_log
209 end
212 end
210
213
211 def remove_contest
214 def remove_contest
212 problem = Problem.find(params[:id])
215 problem = Problem.find(params[:id])
213 contest = Contest.find(params[:contest_id])
216 contest = Contest.find(params[:contest_id])
214 if problem!=nil and contest!=nil
217 if problem!=nil and contest!=nil
215 problem.contests.delete(contest)
218 problem.contests.delete(contest)
216 end
219 end
217 redirect_to :action => 'manage'
220 redirect_to :action => 'manage'
218 end
221 end
219
222
220 ##################################
223 ##################################
221 protected
224 protected
222
225
223 def allow_test_pair_import?
226 def allow_test_pair_import?
224 if defined? ALLOW_TEST_PAIR_IMPORT
227 if defined? ALLOW_TEST_PAIR_IMPORT
225 return ALLOW_TEST_PAIR_IMPORT
228 return ALLOW_TEST_PAIR_IMPORT
226 else
229 else
227 return false
230 return false
228 end
231 end
229 end
232 end
230
233
231 def change_date_added
234 def change_date_added
232 problems = get_problems_from_params
235 problems = get_problems_from_params
233 date = Date.parse(params[:date_added])
236 date = Date.parse(params[:date_added])
234 problems.each do |p|
237 problems.each do |p|
235 p.date_added = date
238 p.date_added = date
236 p.save
239 p.save
237 end
240 end
238 end
241 end
239
242
240 def add_to_contest
243 def add_to_contest
241 problems = get_problems_from_params
244 problems = get_problems_from_params
242 contest = Contest.find(params[:contest][:id])
245 contest = Contest.find(params[:contest][:id])
243 if contest!=nil and contest.enabled
246 if contest!=nil and contest.enabled
244 problems.each do |p|
247 problems.each do |p|
245 p.contests << contest
248 p.contests << contest
246 end
249 end
247 end
250 end
248 end
251 end
249
252
250 def set_available(avail)
253 def set_available(avail)
251 problems = get_problems_from_params
254 problems = get_problems_from_params
252 problems.each do |p|
255 problems.each do |p|
253 p.available = avail
256 p.available = avail
254 p.save
257 p.save
255 end
258 end
256 end
259 end
257
260
258 def get_problems_from_params
261 def get_problems_from_params
259 problems = []
262 problems = []
260 params.keys.each do |k|
263 params.keys.each do |k|
261 if k.index('prob-')==0
264 if k.index('prob-')==0
262 name, id, order = k.split('-')
265 name, id, order = k.split('-')
263 problems << Problem.find(id)
266 problems << Problem.find(id)
264 end
267 end
265 end
268 end
266 problems
269 problems
267 end
270 end
268
271
269 def get_problems_stat
272 def get_problems_stat
270 end
273 end
271
274
272 private
275 private
273
276
274 def set_problem
277 def set_problem
275 @problem = Problem.find(params[:id])
278 @problem = Problem.find(params[:id])
276 end
279 end
277
280
278 def problem_params
281 def problem_params
279 params.require(:problem).permit(:name, :full_name, :full_score, :change_date_added, :date_added, :available,
282 params.require(:problem).permit(:name, :full_name, :full_score, :change_date_added, :date_added, :available,
280 :test_allowed, :output_only, :url, :description, :statement, :description, tag_ids:[])
283 :test_allowed, :output_only, :url, :description, :statement, :description, tag_ids:[])
281 end
284 end
282
285
283 def description_params
286 def description_params
284 params.require(:description).permit(:body, :markdowned)
287 params.require(:description).permit(:body, :markdowned)
285 end
288 end
286
289
287 end
290 end
@@ -59,394 +59,395
59 end
59 end
60
60
61 #set up range from param
61 #set up range from param
62 @since_id = params.fetch(:from_id, 0).to_i
62 @since_id = params.fetch(:from_id, 0).to_i
63 @until_id = params.fetch(:to_id, 0).to_i
63 @until_id = params.fetch(:to_id, 0).to_i
64 @since_id = nil if @since_id == 0
64 @since_id = nil if @since_id == 0
65 @until_id = nil if @until_id == 0
65 @until_id = nil if @until_id == 0
66
66
67 #calculate the routine
67 #calculate the routine
68 @scorearray = calculate_max_score(@problems, @users, @since_id, @until_id)
68 @scorearray = calculate_max_score(@problems, @users, @since_id, @until_id)
69
69
70 #rencer accordingly
70 #rencer accordingly
71 if params[:button] == 'download' then
71 if params[:button] == 'download' then
72 csv = gen_csv_from_scorearray(@scorearray,@problems)
72 csv = gen_csv_from_scorearray(@scorearray,@problems)
73 send_data csv, filename: 'max_score.csv'
73 send_data csv, filename: 'max_score.csv'
74 else
74 else
75 #render template: 'user_admin/user_stat'
75 #render template: 'user_admin/user_stat'
76 render 'max_score'
76 render 'max_score'
77 end
77 end
78
78
79 end
79 end
80
80
81 def score
81 def score
82 if params[:commit] == 'download csv'
82 if params[:commit] == 'download csv'
83 @problems = Problem.all
83 @problems = Problem.all
84 else
84 else
85 @problems = Problem.available_problems
85 @problems = Problem.available_problems
86 end
86 end
87 @users = User.includes(:contests, :contest_stat).where(enabled: true)
87 @users = User.includes(:contests, :contest_stat).where(enabled: true)
88 @scorearray = Array.new
88 @scorearray = Array.new
89 @users.each do |u|
89 @users.each do |u|
90 ustat = Array.new
90 ustat = Array.new
91 ustat[0] = u
91 ustat[0] = u
92 @problems.each do |p|
92 @problems.each do |p|
93 sub = Submission.find_last_by_user_and_problem(u.id,p.id)
93 sub = Submission.find_last_by_user_and_problem(u.id,p.id)
94 if (sub!=nil) and (sub.points!=nil) and p and p.full_score
94 if (sub!=nil) and (sub.points!=nil) and p and p.full_score
95 ustat << [(sub.points.to_f*100/p.full_score).round, (sub.points>=p.full_score)]
95 ustat << [(sub.points.to_f*100/p.full_score).round, (sub.points>=p.full_score)]
96 else
96 else
97 ustat << [0,false]
97 ustat << [0,false]
98 end
98 end
99 end
99 end
100 @scorearray << ustat
100 @scorearray << ustat
101 end
101 end
102 if params[:commit] == 'download csv' then
102 if params[:commit] == 'download csv' then
103 csv = gen_csv_from_scorearray(@scorearray,@problems)
103 csv = gen_csv_from_scorearray(@scorearray,@problems)
104 send_data csv, filename: 'last_score.csv'
104 send_data csv, filename: 'last_score.csv'
105 else
105 else
106 render template: 'user_admin/user_stat'
106 render template: 'user_admin/user_stat'
107 end
107 end
108
108
109 end
109 end
110
110
111 def login
111 def login
112 end
112 end
113
113
114 def login_summary_query
114 def login_summary_query
115 @users = Array.new
115 @users = Array.new
116
116
117 date_and_time = '%Y-%m-%d %H:%M'
117 date_and_time = '%Y-%m-%d %H:%M'
118 begin
118 begin
119 md = params[:since_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
119 md = params[:since_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
120 @since_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
120 @since_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
121 rescue
121 rescue
122 @since_time = Time.zone.now
122 @since_time = Time.zone.now
123 end
123 end
124 puts @since_time
124 puts @since_time
125 begin
125 begin
126 md = params[:until_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
126 md = params[:until_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
127 @until_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
127 @until_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
128 rescue
128 rescue
129 @until_time = DateTime.new(3000,1,1)
129 @until_time = DateTime.new(3000,1,1)
130 end
130 end
131
131
132 record = User
132 record = User
133 .left_outer_joins(:logins).group('users.id')
133 .left_outer_joins(:logins).group('users.id')
134 .where("logins.created_at >= ? AND logins.created_at <= ?",@since_time, @until_time)
134 .where("logins.created_at >= ? AND logins.created_at <= ?",@since_time, @until_time)
135 case params[:users]
135 case params[:users]
136 when 'enabled'
136 when 'enabled'
137 record = record.where(enabled: true)
137 record = record.where(enabled: true)
138 when 'group'
138 when 'group'
139 record = record.joins(:groups).where(groups: {id: params[:groups]}) if params[:groups]
139 record = record.joins(:groups).where(groups: {id: params[:groups]}) if params[:groups]
140 end
140 end
141
141
142 record = record.pluck("users.id,users.login,users.full_name,count(logins.created_at),min(logins.created_at),max(logins.created_at)")
142 record = record.pluck("users.id,users.login,users.full_name,count(logins.created_at),min(logins.created_at),max(logins.created_at)")
143 record.each do |user|
143 record.each do |user|
144 x = Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
144 x = Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
145 user[0],@since_time,@until_time)
145 user[0],@since_time,@until_time)
146 .pluck(:ip_address).uniq
146 .pluck(:ip_address).uniq
147 puts user[4]
147 puts user[4]
148 puts user[5]
148 puts user[5]
149 @users << { id: user[0],
149 @users << { id: user[0],
150 login: user[1],
150 login: user[1],
151 full_name: user[2],
151 full_name: user[2],
152 count: user[3],
152 count: user[3],
153 min: user[4].in_time_zone,
153 min: user[4].in_time_zone,
154 max: user[5].in_time_zone,
154 max: user[5].in_time_zone,
155 ip: x
155 ip: x
156 }
156 }
157 end
157 end
158 end
158 end
159
159
160 def login_detail_query
160 def login_detail_query
161 @logins = Array.new
161 @logins = Array.new
162
162
163 date_and_time = '%Y-%m-%d %H:%M'
163 date_and_time = '%Y-%m-%d %H:%M'
164 begin
164 begin
165 md = params[:since_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
165 md = params[:since_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
166 @since_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
166 @since_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
167 rescue
167 rescue
168 @since_time = Time.zone.now
168 @since_time = Time.zone.now
169 end
169 end
170 begin
170 begin
171 md = params[:until_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
171 md = params[:until_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
172 @until_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
172 @until_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
173 rescue
173 rescue
174 @until_time = DateTime.new(3000,1,1)
174 @until_time = DateTime.new(3000,1,1)
175 end
175 end
176
176
177 @logins = Login.includes(:user).where("logins.created_at >= ? AND logins.created_at <= ?",@since_time, @until_time)
177 @logins = Login.includes(:user).where("logins.created_at >= ? AND logins.created_at <= ?",@since_time, @until_time)
178 case params[:users]
178 case params[:users]
179 when 'enabled'
179 when 'enabled'
180 @logins = @logins.where(users: {enabled: true})
180 @logins = @logins.where(users: {enabled: true})
181 when 'group'
181 when 'group'
182 @logins = @logins.joins(user: :groups).where(user: {groups: {id: params[:groups]}}) if params[:groups]
182 @logins = @logins.joins(user: :groups).where(user: {groups: {id: params[:groups]}}) if params[:groups]
183 end
183 end
184 end
184 end
185
185
186 def submission
186 def submission
187 end
187 end
188
188
189 def submission_query
189 def submission_query
190 @submissions = Submission
190 @submissions = Submission
191 .includes(:problem).includes(:user).includes(:language)
191 .includes(:problem).includes(:user).includes(:language)
192
192
193 case params[:users]
193 case params[:users]
194 when 'enabled'
194 when 'enabled'
195 @submissions = @submissions.where(users: {enabled: true})
195 @submissions = @submissions.where(users: {enabled: true})
196 when 'group'
196 when 'group'
197 @submissions = @submissions.joins(user: :groups).where(user: {groups: {id: params[:groups]}}) if params[:groups]
197 @submissions = @submissions.joins(user: :groups).where(user: {groups: {id: params[:groups]}}) if params[:groups]
198 end
198 end
199
199
200 case params[:problems]
200 case params[:problems]
201 when 'enabled'
201 when 'enabled'
202 @submissions = @submissions.where(problems: {available: true})
202 @submissions = @submissions.where(problems: {available: true})
203 when 'selected'
203 when 'selected'
204 @submissions = @submissions.where(problem_id: params[:problem_id])
204 @submissions = @submissions.where(problem_id: params[:problem_id])
205 end
205 end
206
206
207 #set default
207 #set default
208 params[:since_datetime] = Date.today.to_s if params[:since_datetime].blank?
208 params[:since_datetime] = Date.today.to_s if params[:since_datetime].blank?
209
209
210 @submissions, @recordsTotal, @recordsFiltered = process_query_record( @submissions,
210 @submissions, @recordsTotal, @recordsFiltered = process_query_record( @submissions,
211 global_search: ['user.login','user.full_name','problem.name','problem.full_name','points'],
211 global_search: ['user.login','user.full_name','problem.name','problem.full_name','points'],
212 date_filter: 'submitted_at',
212 date_filter: 'submitted_at',
213 date_param_since: 'since_datetime',
213 date_param_since: 'since_datetime',
214 date_param_until: 'until_datetime',
214 date_param_until: 'until_datetime',
215 hard_limit: 100_000
215 hard_limit: 100_000
216 )
216 )
217 end
217 end
218
218
219 def login
219 def login
220 end
220 end
221
221
222 def problem_hof
222 def problem_hof
223 # gen problem list
223 # gen problem list
224 @user = User.find(session[:user_id])
224 @user = User.find(session[:user_id])
225 @problems = @user.available_problems
225 @problems = @user.available_problems
226
226
227 # get selected problems or the default
227 # get selected problems or the default
228 if params[:id]
228 if params[:id]
229 begin
229 begin
230 @problem = Problem.available.find(params[:id])
230 @problem = Problem.available.find(params[:id])
231 rescue
231 rescue
232 redirect_to action: :problem_hof
232 redirect_to action: :problem_hof
233 flash[:notice] = 'Error: submissions for that problem are not viewable.'
233 flash[:notice] = 'Error: submissions for that problem are not viewable.'
234 return
234 return
235 end
235 end
236 end
236 end
237
237
238 return unless @problem
238 return unless @problem
239
239
240 #model submisssion
240 #model submisssion
241 @model_subs = Submission.where(problem: @problem,tag: Submission.tags[:model])
241 @model_subs = Submission.where(problem: @problem,tag: Submission.tags[:model])
242
242
243
243
244 #calculate best submission
244 #calculate best submission
245 @by_lang = {} #aggregrate by language
245 @by_lang = {} #aggregrate by language
246
246
247 range =65
247 range =65
248 #@histogram = { data: Array.new(range,0), summary: {} }
248 #@histogram = { data: Array.new(range,0), summary: {} }
249 @summary = {count: 0, solve: 0, attempt: 0}
249 @summary = {count: 0, solve: 0, attempt: 0}
250 user = Hash.new(0)
250 user = Hash.new(0)
251 - Submission.where(problem_id: @problem.id).find_each do |sub|
251 + Submission.where(problem_id: @problem.id).includes(:language).each do |sub|
252 #histogram
252 #histogram
253 d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60
253 d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60
254 #@histogram[:data][d.to_i] += 1 if d < range
254 #@histogram[:data][d.to_i] += 1 if d < range
255
255
256 next unless sub.points
256 next unless sub.points
257 @summary[:count] += 1
257 @summary[:count] += 1
258 user[sub.user_id] = [user[sub.user_id], (sub.points >= @problem.full_score) ? 1 : 0].max
258 user[sub.user_id] = [user[sub.user_id], (sub.points >= @problem.full_score) ? 1 : 0].max
259
259
260 - lang = Language.find_by_id(sub.language_id)
260 + #lang = Language.find_by_id(sub.language_id)
261 + lang = sub.language
261 next unless lang
262 next unless lang
262 next unless sub.points >= @problem.full_score
263 next unless sub.points >= @problem.full_score
263
264
264 #initialize
265 #initialize
265 unless @by_lang.has_key?(lang.pretty_name)
266 unless @by_lang.has_key?(lang.pretty_name)
266 @by_lang[lang.pretty_name] = {
267 @by_lang[lang.pretty_name] = {
267 runtime: { avail: false, value: 2**30-1 },
268 runtime: { avail: false, value: 2**30-1 },
268 memory: { avail: false, value: 2**30-1 },
269 memory: { avail: false, value: 2**30-1 },
269 length: { avail: false, value: 2**30-1 },
270 length: { avail: false, value: 2**30-1 },
270 first: { avail: false, value: DateTime.new(3000,1,1) }
271 first: { avail: false, value: DateTime.new(3000,1,1) }
271 }
272 }
272 end
273 end
273
274
274 if sub.max_runtime and sub.max_runtime < @by_lang[lang.pretty_name][:runtime][:value]
275 if sub.max_runtime and sub.max_runtime < @by_lang[lang.pretty_name][:runtime][:value]
275 @by_lang[lang.pretty_name][:runtime] = { avail: true, user_id: sub.user_id, value: sub.max_runtime, sub_id: sub.id }
276 @by_lang[lang.pretty_name][:runtime] = { avail: true, user_id: sub.user_id, value: sub.max_runtime, sub_id: sub.id }
276 end
277 end
277
278
278 if sub.peak_memory and sub.peak_memory < @by_lang[lang.pretty_name][:memory][:value]
279 if sub.peak_memory and sub.peak_memory < @by_lang[lang.pretty_name][:memory][:value]
279 @by_lang[lang.pretty_name][:memory] = { avail: true, user_id: sub.user_id, value: sub.peak_memory, sub_id: sub.id }
280 @by_lang[lang.pretty_name][:memory] = { avail: true, user_id: sub.user_id, value: sub.peak_memory, sub_id: sub.id }
280 end
281 end
281
282
282 if sub.submitted_at and sub.submitted_at < @by_lang[lang.pretty_name][:first][:value] and sub.user and
283 if sub.submitted_at and sub.submitted_at < @by_lang[lang.pretty_name][:first][:value] and sub.user and
283 !sub.user.admin?
284 !sub.user.admin?
284 @by_lang[lang.pretty_name][:first] = { avail: true, user_id: sub.user_id, value: sub.submitted_at, sub_id: sub.id }
285 @by_lang[lang.pretty_name][:first] = { avail: true, user_id: sub.user_id, value: sub.submitted_at, sub_id: sub.id }
285 end
286 end
286
287
287 if @by_lang[lang.pretty_name][:length][:value] > sub.effective_code_length
288 if @by_lang[lang.pretty_name][:length][:value] > sub.effective_code_length
288 @by_lang[lang.pretty_name][:length] = { avail: true, user_id: sub.user_id, value: sub.effective_code_length, sub_id: sub.id }
289 @by_lang[lang.pretty_name][:length] = { avail: true, user_id: sub.user_id, value: sub.effective_code_length, sub_id: sub.id }
289 end
290 end
290 end
291 end
291
292
292 #process user_id
293 #process user_id
293 @by_lang.each do |lang,prop|
294 @by_lang.each do |lang,prop|
294 prop.each do |k,v|
295 prop.each do |k,v|
295 v[:user] = User.exists?(v[:user_id]) ? User.find(v[:user_id]).full_name : "(NULL)"
296 v[:user] = User.exists?(v[:user_id]) ? User.find(v[:user_id]).full_name : "(NULL)"
296 end
297 end
297 end
298 end
298
299
299 #sum into best
300 #sum into best
300 if @by_lang and @by_lang.first
301 if @by_lang and @by_lang.first
301 @best = @by_lang.first[1].clone
302 @best = @by_lang.first[1].clone
302 @by_lang.each do |lang,prop|
303 @by_lang.each do |lang,prop|
303 if @best[:runtime][:value] >= prop[:runtime][:value]
304 if @best[:runtime][:value] >= prop[:runtime][:value]
304 @best[:runtime] = prop[:runtime]
305 @best[:runtime] = prop[:runtime]
305 @best[:runtime][:lang] = lang
306 @best[:runtime][:lang] = lang
306 end
307 end
307 if @best[:memory][:value] >= prop[:memory][:value]
308 if @best[:memory][:value] >= prop[:memory][:value]
308 @best[:memory] = prop[:memory]
309 @best[:memory] = prop[:memory]
309 @best[:memory][:lang] = lang
310 @best[:memory][:lang] = lang
310 end
311 end
311 if @best[:length][:value] >= prop[:length][:value]
312 if @best[:length][:value] >= prop[:length][:value]
312 @best[:length] = prop[:length]
313 @best[:length] = prop[:length]
313 @best[:length][:lang] = lang
314 @best[:length][:lang] = lang
314 end
315 end
315 if @best[:first][:value] >= prop[:first][:value]
316 if @best[:first][:value] >= prop[:first][:value]
316 @best[:first] = prop[:first]
317 @best[:first] = prop[:first]
317 @best[:first][:lang] = lang
318 @best[:first][:lang] = lang
318 end
319 end
319 end
320 end
320 end
321 end
321
322
322 #@histogram[:summary][:max] = [@histogram[:data].max,1].max
323 #@histogram[:summary][:max] = [@histogram[:data].max,1].max
323 @summary[:attempt] = user.count
324 @summary[:attempt] = user.count
324 user.each_value { |v| @summary[:solve] += 1 if v == 1 }
325 user.each_value { |v| @summary[:solve] += 1 if v == 1 }
325
326
326
327
327 #for new graph
328 #for new graph
328 @chart_dataset = @problem.get_jschart_history.to_json.html_safe
329 @chart_dataset = @problem.get_jschart_history.to_json.html_safe
329 end
330 end
330
331
331 def stuck #report struggling user,problem
332 def stuck #report struggling user,problem
332 # init
333 # init
333 user,problem = nil
334 user,problem = nil
334 solve = true
335 solve = true
335 tries = 0
336 tries = 0
336 @struggle = Array.new
337 @struggle = Array.new
337 record = {}
338 record = {}
338 Submission.includes(:problem,:user).order(:problem_id,:user_id).find_each do |sub|
339 Submission.includes(:problem,:user).order(:problem_id,:user_id).find_each do |sub|
339 next unless sub.problem and sub.user
340 next unless sub.problem and sub.user
340 if user != sub.user_id or problem != sub.problem_id
341 if user != sub.user_id or problem != sub.problem_id
341 @struggle << { user: record[:user], problem: record[:problem], tries: tries } unless solve
342 @struggle << { user: record[:user], problem: record[:problem], tries: tries } unless solve
342 record = {user: sub.user, problem: sub.problem}
343 record = {user: sub.user, problem: sub.problem}
343 user,problem = sub.user_id, sub.problem_id
344 user,problem = sub.user_id, sub.problem_id
344 solve = false
345 solve = false
345 tries = 0
346 tries = 0
346 end
347 end
347 if sub.points >= sub.problem.full_score
348 if sub.points >= sub.problem.full_score
348 solve = true
349 solve = true
349 else
350 else
350 tries += 1
351 tries += 1
351 end
352 end
352 end
353 end
353 @struggle.sort!{|a,b| b[:tries] <=> a[:tries] }
354 @struggle.sort!{|a,b| b[:tries] <=> a[:tries] }
354 @struggle = @struggle[0..50]
355 @struggle = @struggle[0..50]
355 end
356 end
356
357
357
358
358 def multiple_login
359 def multiple_login
359 #user with multiple IP
360 #user with multiple IP
360 raw = Submission.joins(:user).joins(:problem).where("problems.available != 0").group("login,ip_address").order(:login)
361 raw = Submission.joins(:user).joins(:problem).where("problems.available != 0").group("login,ip_address").order(:login)
361 last,count = 0,0
362 last,count = 0,0
362 first = 0
363 first = 0
363 @users = []
364 @users = []
364 raw.each do |r|
365 raw.each do |r|
365 if last != r.user.login
366 if last != r.user.login
366 count = 1
367 count = 1
367 last = r.user.login
368 last = r.user.login
368 first = r
369 first = r
369 else
370 else
370 @users << first if count == 1
371 @users << first if count == 1
371 @users << r
372 @users << r
372 count += 1
373 count += 1
373 end
374 end
374 end
375 end
375
376
376 #IP with multiple user
377 #IP with multiple user
377 raw = Submission.joins(:user).joins(:problem).where("problems.available != 0").group("login,ip_address").order(:ip_address)
378 raw = Submission.joins(:user).joins(:problem).where("problems.available != 0").group("login,ip_address").order(:ip_address)
378 last,count = 0,0
379 last,count = 0,0
379 first = 0
380 first = 0
380 @ip = []
381 @ip = []
381 raw.each do |r|
382 raw.each do |r|
382 if last != r.ip_address
383 if last != r.ip_address
383 count = 1
384 count = 1
384 last = r.ip_address
385 last = r.ip_address
385 first = r
386 first = r
386 else
387 else
387 @ip << first if count == 1
388 @ip << first if count == 1
388 @ip << r
389 @ip << r
389 count += 1
390 count += 1
390 end
391 end
391 end
392 end
392 end
393 end
393
394
394 def cheat_report
395 def cheat_report
395 date_and_time = '%Y-%m-%d %H:%M'
396 date_and_time = '%Y-%m-%d %H:%M'
396 begin
397 begin
397 md = params[:since_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
398 md = params[:since_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
398 @since_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
399 @since_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
399 rescue
400 rescue
400 @since_time = Time.zone.now.ago( 90.minutes)
401 @since_time = Time.zone.now.ago( 90.minutes)
401 end
402 end
402 begin
403 begin
403 md = params[:until_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
404 md = params[:until_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
404 @until_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
405 @until_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
405 rescue
406 rescue
406 @until_time = Time.zone.now
407 @until_time = Time.zone.now
407 end
408 end
408
409
409 #multi login
410 #multi login
410 @ml = Login.joins(:user).where("logins.created_at >= ? and logins.created_at <= ?",@since_time,@until_time).select('users.login,count(distinct ip_address) as count,users.full_name').group("users.id").having("count > 1")
411 @ml = Login.joins(:user).where("logins.created_at >= ? and logins.created_at <= ?",@since_time,@until_time).select('users.login,count(distinct ip_address) as count,users.full_name').group("users.id").having("count > 1")
411
412
412 st = <<-SQL
413 st = <<-SQL
413 SELECT l2.*
414 SELECT l2.*
414 FROM logins l2 INNER JOIN
415 FROM logins l2 INNER JOIN
415 (SELECT u.id,COUNT(DISTINCT ip_address) as count,u.login,u.full_name
416 (SELECT u.id,COUNT(DISTINCT ip_address) as count,u.login,u.full_name
416 FROM logins l
417 FROM logins l
417 INNER JOIN users u ON l.user_id = u.id
418 INNER JOIN users u ON l.user_id = u.id
418 WHERE l.created_at >= '#{@since_time.in_time_zone("UTC")}' and l.created_at <= '#{@until_time.in_time_zone("UTC")}'
419 WHERE l.created_at >= '#{@since_time.in_time_zone("UTC")}' and l.created_at <= '#{@until_time.in_time_zone("UTC")}'
419 GROUP BY u.id
420 GROUP BY u.id
420 HAVING count > 1
421 HAVING count > 1
421 ) ml ON l2.user_id = ml.id
422 ) ml ON l2.user_id = ml.id
422 WHERE l2.created_at >= '#{@since_time.in_time_zone("UTC")}' and l2.created_at <= '#{@until_time.in_time_zone("UTC")}'
423 WHERE l2.created_at >= '#{@since_time.in_time_zone("UTC")}' and l2.created_at <= '#{@until_time.in_time_zone("UTC")}'
423 UNION
424 UNION
424 SELECT l2.*
425 SELECT l2.*
425 FROM logins l2 INNER JOIN
426 FROM logins l2 INNER JOIN
426 (SELECT l.ip_address,COUNT(DISTINCT u.id) as count
427 (SELECT l.ip_address,COUNT(DISTINCT u.id) as count
427 FROM logins l
428 FROM logins l
428 INNER JOIN users u ON l.user_id = u.id
429 INNER JOIN users u ON l.user_id = u.id
429 WHERE l.created_at >= '#{@since_time.in_time_zone("UTC")}' and l.created_at <= '#{@until_time.in_time_zone("UTC")}'
430 WHERE l.created_at >= '#{@since_time.in_time_zone("UTC")}' and l.created_at <= '#{@until_time.in_time_zone("UTC")}'
430 GROUP BY l.ip_address
431 GROUP BY l.ip_address
431 HAVING count > 1
432 HAVING count > 1
432 ) ml on ml.ip_address = l2.ip_address
433 ) ml on ml.ip_address = l2.ip_address
433 INNER JOIN users u ON l2.user_id = u.id
434 INNER JOIN users u ON l2.user_id = u.id
434 WHERE l2.created_at >= '#{@since_time.in_time_zone("UTC")}' and l2.created_at <= '#{@until_time.in_time_zone("UTC")}'
435 WHERE l2.created_at >= '#{@since_time.in_time_zone("UTC")}' and l2.created_at <= '#{@until_time.in_time_zone("UTC")}'
435 ORDER BY ip_address,created_at
436 ORDER BY ip_address,created_at
436 SQL
437 SQL
437 @mld = Login.find_by_sql(st)
438 @mld = Login.find_by_sql(st)
438
439
439 st = <<-SQL
440 st = <<-SQL
440 SELECT s.id,s.user_id,s.ip_address,s.submitted_at,s.problem_id
441 SELECT s.id,s.user_id,s.ip_address,s.submitted_at,s.problem_id
441 FROM submissions s INNER JOIN
442 FROM submissions s INNER JOIN
442 (SELECT u.id,COUNT(DISTINCT ip_address) as count,u.login,u.full_name
443 (SELECT u.id,COUNT(DISTINCT ip_address) as count,u.login,u.full_name
443 FROM logins l
444 FROM logins l
444 INNER JOIN users u ON l.user_id = u.id
445 INNER JOIN users u ON l.user_id = u.id
445 WHERE l.created_at >= ? and l.created_at <= ?
446 WHERE l.created_at >= ? and l.created_at <= ?
446 GROUP BY u.id
447 GROUP BY u.id
447 HAVING count > 1
448 HAVING count > 1
448 ) ml ON s.user_id = ml.id
449 ) ml ON s.user_id = ml.id
449 WHERE s.submitted_at >= ? and s.submitted_at <= ?
450 WHERE s.submitted_at >= ? and s.submitted_at <= ?
450 UNION
451 UNION
451 SELECT s.id,s.user_id,s.ip_address,s.submitted_at,s.problem_id
452 SELECT s.id,s.user_id,s.ip_address,s.submitted_at,s.problem_id
452 FROM submissions s INNER JOIN
453 FROM submissions s INNER JOIN
@@ -1,237 +1,231
1 require 'net/smtp'
1 require 'net/smtp'
2
2
3 class UsersController < ApplicationController
3 class UsersController < ApplicationController
4
4
5 include MailHelperMethods
5 include MailHelperMethods
6
6
7 before_action :check_valid_login, :except => [:new,
7 before_action :check_valid_login, :except => [:new,
8 :register,
8 :register,
9 :confirm,
9 :confirm,
10 :forget,
10 :forget,
11 :retrieve_password]
11 :retrieve_password]
12
12
13 before_action :verify_online_registration, :only => [:new,
13 before_action :verify_online_registration, :only => [:new,
14 :register,
14 :register,
15 :forget,
15 :forget,
16 :retrieve_password]
16 :retrieve_password]
17
17
18 before_action :admin_authorization, only: [:stat, :toggle_activate, :toggle_enable]
18 before_action :admin_authorization, only: [:stat, :toggle_activate, :toggle_enable]
19
19
20
20
21 #in_place_edit_for :user, :alias_for_editing
21 #in_place_edit_for :user, :alias_for_editing
22 #in_place_edit_for :user, :email_for_editing
22 #in_place_edit_for :user, :email_for_editing
23
23
24 def index
24 def index
25 if !GraderConfiguration['system.user_setting_enabled']
25 if !GraderConfiguration['system.user_setting_enabled']
26 redirect_to :controller => 'main', :action => 'list'
26 redirect_to :controller => 'main', :action => 'list'
27 else
27 else
28 @user = User.find(session[:user_id])
28 @user = User.find(session[:user_id])
29 end
29 end
30 end
30 end
31
31
32 # edit logged in user profile
32 # edit logged in user profile
33 def profile
33 def profile
34 if !GraderConfiguration['system.user_setting_enabled']
34 if !GraderConfiguration['system.user_setting_enabled']
35 redirect_to :controller => 'main', :action => 'list'
35 redirect_to :controller => 'main', :action => 'list'
36 else
36 else
37 @user = current_user;
37 @user = current_user;
38 end
38 end
39 end
39 end
40
40
41 def chg_passwd
41 def chg_passwd
42 user = User.find(session[:user_id])
42 user = User.find(session[:user_id])
43 user.password = params[:password]
43 user.password = params[:password]
44 user.password_confirmation = params[:password_confirmation]
44 user.password_confirmation = params[:password_confirmation]
45 if user.save
45 if user.save
46 flash[:notice] = 'password changed'
46 flash[:notice] = 'password changed'
47 else
47 else
48 flash[:notice] = 'Error: password changing failed'
48 flash[:notice] = 'Error: password changing failed'
49 end
49 end
50 redirect_to :action => 'profile'
50 redirect_to :action => 'profile'
51 end
51 end
52
52
53 def chg_default_language
53 def chg_default_language
54 user = User.find(session[:user_id])
54 user = User.find(session[:user_id])
55 user.default_language = params[:default_language]
55 user.default_language = params[:default_language]
56 if user.save
56 if user.save
57 flash[:notice] = 'default language changed'
57 flash[:notice] = 'default language changed'
58 else
58 else
59 flash[:notice] = 'Error: default language changing failed'
59 flash[:notice] = 'Error: default language changing failed'
60 end
60 end
61 redirect_to :action => 'profile'
61 redirect_to :action => 'profile'
62 end
62 end
63
63
64 def new
64 def new
65 @user = User.new
65 @user = User.new
66 render :action => 'new', :layout => 'empty'
66 render :action => 'new', :layout => 'empty'
67 end
67 end
68
68
69 def register
69 def register
70 if(params[:cancel])
70 if(params[:cancel])
71 redirect_to :controller => 'main', :action => 'login'
71 redirect_to :controller => 'main', :action => 'login'
72 return
72 return
73 end
73 end
74 @user = User.new(user_params)
74 @user = User.new(user_params)
75 @user.password_confirmation = @user.password = User.random_password
75 @user.password_confirmation = @user.password = User.random_password
76 @user.activated = false
76 @user.activated = false
77 if (@user.valid?) and (@user.save)
77 if (@user.valid?) and (@user.save)
78 if send_confirmation_email(@user)
78 if send_confirmation_email(@user)
79 render :action => 'new_splash', :layout => 'empty'
79 render :action => 'new_splash', :layout => 'empty'
80 else
80 else
81 @admin_email = GraderConfiguration['system.admin_email']
81 @admin_email = GraderConfiguration['system.admin_email']
82 render :action => 'email_error', :layout => 'empty'
82 render :action => 'email_error', :layout => 'empty'
83 end
83 end
84 else
84 else
85 @user.errors.add(:base,"Email cannot be blank") if @user.email==''
85 @user.errors.add(:base,"Email cannot be blank") if @user.email==''
86 render :action => 'new', :layout => 'empty'
86 render :action => 'new', :layout => 'empty'
87 end
87 end
88 end
88 end
89
89
90 def confirm
90 def confirm
91 login = params[:login]
91 login = params[:login]
92 key = params[:activation]
92 key = params[:activation]
93 @user = User.find_by_login(login)
93 @user = User.find_by_login(login)
94 if (@user) and (@user.verify_activation_key(key))
94 if (@user) and (@user.verify_activation_key(key))
95 if @user.valid? # check uniquenss of email
95 if @user.valid? # check uniquenss of email
96 @user.activated = true
96 @user.activated = true
97 @user.save
97 @user.save
98 @result = :successful
98 @result = :successful
99 else
99 else
100 @result = :email_used
100 @result = :email_used
101 end
101 end
102 else
102 else
103 @result = :failed
103 @result = :failed
104 end
104 end
105 render :action => 'confirm', :layout => 'empty'
105 render :action => 'confirm', :layout => 'empty'
106 end
106 end
107
107
108 def forget
108 def forget
109 render :action => 'forget', :layout => 'empty'
109 render :action => 'forget', :layout => 'empty'
110 end
110 end
111
111
112 def retrieve_password
112 def retrieve_password
113 email = params[:email]
113 email = params[:email]
114 user = User.find_by_email(email)
114 user = User.find_by_email(email)
115 if user
115 if user
116 last_updated_time = user.updated_at || user.created_at || (Time.now.gmtime - 1.hour)
116 last_updated_time = user.updated_at || user.created_at || (Time.now.gmtime - 1.hour)
117 if last_updated_time > Time.now.gmtime - 5.minutes
117 if last_updated_time > Time.now.gmtime - 5.minutes
118 flash[:notice] = 'The account has recently created or new password has recently been requested. Please wait for 5 minutes'
118 flash[:notice] = 'The account has recently created or new password has recently been requested. Please wait for 5 minutes'
119 else
119 else
120 user.password = user.password_confirmation = User.random_password
120 user.password = user.password_confirmation = User.random_password
121 user.save
121 user.save
122 send_new_password_email(user)
122 send_new_password_email(user)
123 flash[:notice] = 'New password has been mailed to you.'
123 flash[:notice] = 'New password has been mailed to you.'
124 end
124 end
125 else
125 else
126 flash[:notice] = I18n.t 'registration.password_retrieval.no_email'
126 flash[:notice] = I18n.t 'registration.password_retrieval.no_email'
127 end
127 end
128 redirect_to :action => 'forget'
128 redirect_to :action => 'forget'
129 end
129 end
130
130
131 def stat
131 def stat
132 @user = User.find(params[:id])
132 @user = User.find(params[:id])
133 - @submission = Submission.joins(:problem).where(user_id: params[:id])
133 + @submission = Submission.joins(:problem).includes(:problem).includes(:language).where(user_id: params[:id])
134 @submission = @submission.where('problems.available = true') unless current_user.admin?
134 @submission = @submission.where('problems.available = true') unless current_user.admin?
135
135
136 - range = 120
137 - @histogram = { data: Array.new(range,0), summary: {} }
138 @summary = {count: 0, solve: 0, attempt: 0}
136 @summary = {count: 0, solve: 0, attempt: 0}
139 problem = Hash.new(0)
137 problem = Hash.new(0)
140
138
141 @submission.find_each do |sub|
139 @submission.find_each do |sub|
142 - #histogram
143 - d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60
144 - @histogram[:data][d.to_i] += 1 if d < range
145 -
146 @summary[:count] += 1
140 @summary[:count] += 1
147 next unless sub.problem
141 next unless sub.problem
148 problem[sub.problem] = [problem[sub.problem], ( (sub.try(:points) || 0) >= sub.problem.full_score) ? 1 : 0].max
142 problem[sub.problem] = [problem[sub.problem], ( (sub.try(:points) || 0) >= sub.problem.full_score) ? 1 : 0].max
149 end
143 end
150
144
151 - @histogram[:summary][:max] = [@histogram[:data].max,1].max
152 @summary[:attempt] = problem.count
145 @summary[:attempt] = problem.count
153 problem.each_value { |v| @summary[:solve] += 1 if v == 1 }
146 problem.each_value { |v| @summary[:solve] += 1 if v == 1 }
147 + @chart_dataset = @user.get_jschart_user_sub_history.to_json.html_safe
154 end
148 end
155
149
156 def toggle_activate
150 def toggle_activate
157 @user = User.find(params[:id])
151 @user = User.find(params[:id])
158 @user.update_attributes( activated: !@user.activated? )
152 @user.update_attributes( activated: !@user.activated? )
159 respond_to do |format|
153 respond_to do |format|
160 format.js { render partial: 'toggle_button',
154 format.js { render partial: 'toggle_button',
161 locals: {button_id: "#toggle_activate_user_#{@user.id}",button_on: @user.activated? } }
155 locals: {button_id: "#toggle_activate_user_#{@user.id}",button_on: @user.activated? } }
162 end
156 end
163 end
157 end
164
158
165 def toggle_enable
159 def toggle_enable
166 @user = User.find(params[:id])
160 @user = User.find(params[:id])
167 @user.update_attributes( enabled: !@user.enabled? )
161 @user.update_attributes( enabled: !@user.enabled? )
168 respond_to do |format|
162 respond_to do |format|
169 format.js { render partial: 'toggle_button',
163 format.js { render partial: 'toggle_button',
170 locals: {button_id: "#toggle_enable_user_#{@user.id}",button_on: @user.enabled? } }
164 locals: {button_id: "#toggle_enable_user_#{@user.id}",button_on: @user.enabled? } }
171 end
165 end
172 end
166 end
173
167
174 protected
168 protected
175
169
176 def verify_online_registration
170 def verify_online_registration
177 if !GraderConfiguration['system.online_registration']
171 if !GraderConfiguration['system.online_registration']
178 redirect_to :controller => 'main', :action => 'login'
172 redirect_to :controller => 'main', :action => 'login'
179 end
173 end
180 end
174 end
181
175
182 def send_confirmation_email(user)
176 def send_confirmation_email(user)
183 contest_name = GraderConfiguration['contest.name']
177 contest_name = GraderConfiguration['contest.name']
184 activation_url = url_for(:action => 'confirm',
178 activation_url = url_for(:action => 'confirm',
185 :login => user.login,
179 :login => user.login,
186 :activation => user.activation_key)
180 :activation => user.activation_key)
187 home_url = url_for(:controller => 'main', :action => 'index')
181 home_url = url_for(:controller => 'main', :action => 'index')
188 mail_subject = "[#{contest_name}] Confirmation"
182 mail_subject = "[#{contest_name}] Confirmation"
189 mail_body = t('registration.email_body', {
183 mail_body = t('registration.email_body', {
190 :full_name => user.full_name,
184 :full_name => user.full_name,
191 :contest_name => contest_name,
185 :contest_name => contest_name,
192 :login => user.login,
186 :login => user.login,
193 :password => user.password,
187 :password => user.password,
194 :activation_url => activation_url,
188 :activation_url => activation_url,
195 :admin_email => GraderConfiguration['system.admin_email']
189 :admin_email => GraderConfiguration['system.admin_email']
196 })
190 })
197
191
198 logger.info mail_body
192 logger.info mail_body
199
193
200 send_mail(user.email, mail_subject, mail_body)
194 send_mail(user.email, mail_subject, mail_body)
201 end
195 end
202
196
203 def send_new_password_email(user)
197 def send_new_password_email(user)
204 contest_name = GraderConfiguration['contest.name']
198 contest_name = GraderConfiguration['contest.name']
205 mail_subject = "[#{contest_name}] Password recovery"
199 mail_subject = "[#{contest_name}] Password recovery"
206 mail_body = t('registration.password_retrieval.email_body', {
200 mail_body = t('registration.password_retrieval.email_body', {
207 :full_name => user.full_name,
201 :full_name => user.full_name,
208 :contest_name => contest_name,
202 :contest_name => contest_name,
209 :login => user.login,
203 :login => user.login,
210 :password => user.password,
204 :password => user.password,
211 :admin_email => GraderConfiguration['system.admin_email']
205 :admin_email => GraderConfiguration['system.admin_email']
212 })
206 })
213
207
214 logger.info mail_body
208 logger.info mail_body
215
209
216 send_mail(user.email, mail_subject, mail_body)
210 send_mail(user.email, mail_subject, mail_body)
217 end
211 end
218
212
219 # allow viewing of regular user profile only when options allow so
213 # allow viewing of regular user profile only when options allow so
220 # only admins can view admins profile
214 # only admins can view admins profile
221 def profile_authorization
215 def profile_authorization
222 #if view admins' profile, allow only admin
216 #if view admins' profile, allow only admin
223 return false unless(params[:id])
217 return false unless(params[:id])
224 user = User.find(params[:id])
218 user = User.find(params[:id])
225 return false unless user
219 return false unless user
226 return admin_authorization if user.admin?
220 return admin_authorization if user.admin?
227 return true if GraderConfiguration["right.user_view_submission"]
221 return true if GraderConfiguration["right.user_view_submission"]
228
222
229 #finally, we allow only admin
223 #finally, we allow only admin
230 admin_authorization
224 admin_authorization
231 end
225 end
232
226
233 private
227 private
234 def user_params
228 def user_params
235 params.require(:user).permit(:login, :full_name, :email)
229 params.require(:user).permit(:login, :full_name, :email)
236 end
230 end
237 end
231 end
@@ -125,337 +125,352
125 def activation_key
125 def activation_key
126 if self.hashed_password==nil
126 if self.hashed_password==nil
127 encrypt_new_password
127 encrypt_new_password
128 end
128 end
129 Digest::SHA1.hexdigest(self.hashed_password)[0..7]
129 Digest::SHA1.hexdigest(self.hashed_password)[0..7]
130 end
130 end
131
131
132 def verify_activation_key(key)
132 def verify_activation_key(key)
133 key == activation_key
133 key == activation_key
134 end
134 end
135
135
136 def self.random_password(length=5)
136 def self.random_password(length=5)
137 chars = 'abcdefghjkmnopqrstuvwxyz'
137 chars = 'abcdefghjkmnopqrstuvwxyz'
138 password = ''
138 password = ''
139 length.times { password << chars[rand(chars.length - 1)] }
139 length.times { password << chars[rand(chars.length - 1)] }
140 password
140 password
141 end
141 end
142
142
143
143
144 # Contest information
144 # Contest information
145
145
146 def self.find_users_with_no_contest()
146 def self.find_users_with_no_contest()
147 users = User.all
147 users = User.all
148 return users.find_all { |u| u.contests.length == 0 }
148 return users.find_all { |u| u.contests.length == 0 }
149 end
149 end
150
150
151
151
152 def contest_time_left
152 def contest_time_left
153 if GraderConfiguration.contest_mode?
153 if GraderConfiguration.contest_mode?
154 return nil if site==nil
154 return nil if site==nil
155 return site.time_left
155 return site.time_left
156 elsif GraderConfiguration.indv_contest_mode?
156 elsif GraderConfiguration.indv_contest_mode?
157 time_limit = GraderConfiguration.contest_time_limit
157 time_limit = GraderConfiguration.contest_time_limit
158 if time_limit == nil
158 if time_limit == nil
159 return nil
159 return nil
160 end
160 end
161 if contest_stat==nil or contest_stat.started_at==nil
161 if contest_stat==nil or contest_stat.started_at==nil
162 return (Time.now.gmtime + time_limit) - Time.now.gmtime
162 return (Time.now.gmtime + time_limit) - Time.now.gmtime
163 else
163 else
164 finish_time = contest_stat.started_at + time_limit
164 finish_time = contest_stat.started_at + time_limit
165 current_time = Time.now.gmtime
165 current_time = Time.now.gmtime
166 if current_time > finish_time
166 if current_time > finish_time
167 return 0
167 return 0
168 else
168 else
169 return finish_time - current_time
169 return finish_time - current_time
170 end
170 end
171 end
171 end
172 else
172 else
173 return nil
173 return nil
174 end
174 end
175 end
175 end
176
176
177 def contest_finished?
177 def contest_finished?
178 if GraderConfiguration.contest_mode?
178 if GraderConfiguration.contest_mode?
179 return false if site==nil
179 return false if site==nil
180 return site.finished?
180 return site.finished?
181 elsif GraderConfiguration.indv_contest_mode?
181 elsif GraderConfiguration.indv_contest_mode?
182 return false if self.contest_stat==nil
182 return false if self.contest_stat==nil
183 return contest_time_left == 0
183 return contest_time_left == 0
184 else
184 else
185 return false
185 return false
186 end
186 end
187 end
187 end
188
188
189 def contest_started?
189 def contest_started?
190 if GraderConfiguration.indv_contest_mode?
190 if GraderConfiguration.indv_contest_mode?
191 stat = self.contest_stat
191 stat = self.contest_stat
192 return ((stat != nil) and (stat.started_at != nil))
192 return ((stat != nil) and (stat.started_at != nil))
193 elsif GraderConfiguration.contest_mode?
193 elsif GraderConfiguration.contest_mode?
194 return true if site==nil
194 return true if site==nil
195 return site.started
195 return site.started
196 else
196 else
197 return true
197 return true
198 end
198 end
199 end
199 end
200
200
201 def update_start_time
201 def update_start_time
202 stat = self.contest_stat
202 stat = self.contest_stat
203 if stat.nil? or stat.started_at.nil?
203 if stat.nil? or stat.started_at.nil?
204 stat ||= UserContestStat.new(:user => self)
204 stat ||= UserContestStat.new(:user => self)
205 stat.started_at = Time.now.gmtime
205 stat.started_at = Time.now.gmtime
206 stat.save
206 stat.save
207 end
207 end
208 end
208 end
209
209
210 def problem_in_user_contests?(problem)
210 def problem_in_user_contests?(problem)
211 problem_contests = problem.contests.all
211 problem_contests = problem.contests.all
212
212
213 if problem_contests.length == 0 # this is public contest
213 if problem_contests.length == 0 # this is public contest
214 return true
214 return true
215 end
215 end
216
216
217 contests.each do |contest|
217 contests.each do |contest|
218 if problem_contests.find {|c| c.id == contest.id }
218 if problem_contests.find {|c| c.id == contest.id }
219 return true
219 return true
220 end
220 end
221 end
221 end
222 return false
222 return false
223 end
223 end
224
224
225 def available_problems_group_by_contests
225 def available_problems_group_by_contests
226 contest_problems = []
226 contest_problems = []
227 pin = {}
227 pin = {}
228 contests.enabled.each do |contest|
228 contests.enabled.each do |contest|
229 available_problems = contest.problems.available
229 available_problems = contest.problems.available
230 contest_problems << {
230 contest_problems << {
231 :contest => contest,
231 :contest => contest,
232 :problems => available_problems
232 :problems => available_problems
233 }
233 }
234 available_problems.each {|p| pin[p.id] = true}
234 available_problems.each {|p| pin[p.id] = true}
235 end
235 end
236 other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0}
236 other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0}
237 contest_problems << {
237 contest_problems << {
238 :contest => nil,
238 :contest => nil,
239 :problems => other_avaiable_problems
239 :problems => other_avaiable_problems
240 }
240 }
241 return contest_problems
241 return contest_problems
242 end
242 end
243
243
244 def solve_all_available_problems?
244 def solve_all_available_problems?
245 available_problems.each do |p|
245 available_problems.each do |p|
246 u = self
246 u = self
247 sub = Submission.find_last_by_user_and_problem(u.id,p.id)
247 sub = Submission.find_last_by_user_and_problem(u.id,p.id)
248 return false if !p or !sub or sub.points < p.full_score
248 return false if !p or !sub or sub.points < p.full_score
249 end
249 end
250 return true
250 return true
251 end
251 end
252
252
253 #get a list of available problem
253 #get a list of available problem
254 def available_problems
254 def available_problems
255 # first, we check if this is normal mode
255 # first, we check if this is normal mode
256 if not GraderConfiguration.multicontests?
256 if not GraderConfiguration.multicontests?
257
257
258 #if this is a normal mode
258 #if this is a normal mode
259 #we show problem based on problem_group, if the config said so
259 #we show problem based on problem_group, if the config said so
260 if GraderConfiguration.use_problem_group?
260 if GraderConfiguration.use_problem_group?
261 return available_problems_in_group
261 return available_problems_in_group
262 else
262 else
263 return Problem.available_problems
263 return Problem.available_problems
264 end
264 end
265 else
265 else
266 #this is multi contest mode
266 #this is multi contest mode
267 contest_problems = []
267 contest_problems = []
268 pin = {}
268 pin = {}
269 contests.enabled.each do |contest|
269 contests.enabled.each do |contest|
270 contest.problems.available.each do |problem|
270 contest.problems.available.each do |problem|
271 if not pin.has_key? problem.id
271 if not pin.has_key? problem.id
272 contest_problems << problem
272 contest_problems << problem
273 end
273 end
274 pin[problem.id] = true
274 pin[problem.id] = true
275 end
275 end
276 end
276 end
277 other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0}
277 other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0}
278 return contest_problems + other_avaiable_problems
278 return contest_problems + other_avaiable_problems
279 end
279 end
280 end
280 end
281
281
282 # new feature, get list of available problem in all enabled group that the user belongs to
282 # new feature, get list of available problem in all enabled group that the user belongs to
283 def available_problems_in_group
283 def available_problems_in_group
284 problem = []
284 problem = []
285 self.groups.where(enabled: true).each do |group|
285 self.groups.where(enabled: true).each do |group|
286 group.problems.where(available: true).each { |p| problem << p }
286 group.problems.where(available: true).each { |p| problem << p }
287 end
287 end
288 problem.uniq!
288 problem.uniq!
289 if problem
289 if problem
290 problem.sort! do |a,b|
290 problem.sort! do |a,b|
291 case
291 case
292 when a.date_added < b.date_added
292 when a.date_added < b.date_added
293 1
293 1
294 when a.date_added > b.date_added
294 when a.date_added > b.date_added
295 -1
295 -1
296 else
296 else
297 a.name <=> b.name
297 a.name <=> b.name
298 end
298 end
299 end
299 end
300 return problem
300 return problem
301 else
301 else
302 return []
302 return []
303 end
303 end
304 end
304 end
305
305
306 #check if the user has the right to view that problem
306 #check if the user has the right to view that problem
307 #this also consider group based problem policy
307 #this also consider group based problem policy
308 def can_view_problem?(problem)
308 def can_view_problem?(problem)
309 return true if admin?
309 return true if admin?
310 return available_problems.include? problem
310 return available_problems.include? problem
311 end
311 end
312
312
313 def self.clear_last_login
313 def self.clear_last_login
314 User.update_all(:last_ip => nil)
314 User.update_all(:last_ip => nil)
315 end
315 end
316
316
317 + def get_jschart_user_sub_history
318 + start = 4.month.ago.beginning_of_day
319 + start_date = start.to_date
320 + count = Submission.where(user: self).where('submitted_at >= ?', start).group('DATE(submitted_at)').count
321 + i = 0
322 + label = []
323 + value = []
324 + while (start_date + i < Time.zone.now.to_date)
325 + label << (start_date+i).strftime("%d-%b")
326 + value << (count[start_date+i] || 0)
327 + i+=1
328 + end
329 + return {labels: label,datasets: [label:'sub',data: value, backgroundColor: 'rgba(54, 162, 235, 0.2)', borderColor: 'rgb(75, 192, 192)']}
330 + end
331 +
317 #create multiple user, one per lines of input
332 #create multiple user, one per lines of input
318 def self.create_from_list(lines)
333 def self.create_from_list(lines)
319 error_logins = []
334 error_logins = []
320 first_error = nil
335 first_error = nil
321 created_users = []
336 created_users = []
322
337
323 lines.split("\n").each do |line|
338 lines.split("\n").each do |line|
324 #split with large limit, this will cause consecutive ',' to be result in a blank
339 #split with large limit, this will cause consecutive ',' to be result in a blank
325 items = line.chomp.split(',',1000)
340 items = line.chomp.split(',',1000)
326 if items.length>=2
341 if items.length>=2
327 login = items[0]
342 login = items[0]
328 full_name = items[1]
343 full_name = items[1]
329 remark =''
344 remark =''
330 user_alias = ''
345 user_alias = ''
331
346
332 added_random_password = false
347 added_random_password = false
333 added_password = false
348 added_password = false
334
349
335 #given password?
350 #given password?
336 if items.length >= 3
351 if items.length >= 3
337 if items[2].chomp(" ").length > 0
352 if items[2].chomp(" ").length > 0
338 password = items[2].chomp(" ")
353 password = items[2].chomp(" ")
339 added_password = true
354 added_password = true
340 end
355 end
341 else
356 else
342 password = random_password
357 password = random_password
343 added_random_password=true;
358 added_random_password=true;
344 end
359 end
345
360
346 #given alias?
361 #given alias?
347 if items.length>= 4 and items[3].chomp(" ").length > 0;
362 if items.length>= 4 and items[3].chomp(" ").length > 0;
348 user_alias = items[3].chomp(" ")
363 user_alias = items[3].chomp(" ")
349 else
364 else
350 user_alias = login
365 user_alias = login
351 end
366 end
352
367
353 #given remark?
368 #given remark?
354 has_remark = false
369 has_remark = false
355 if items.length>=5
370 if items.length>=5
356 remark = items[4].strip;
371 remark = items[4].strip;
357 has_remark = true
372 has_remark = true
358 end
373 end
359
374
360 user = User.find_by_login(login)
375 user = User.find_by_login(login)
361 if (user)
376 if (user)
362 user.full_name = full_name
377 user.full_name = full_name
363 user.remark = remark if has_remark
378 user.remark = remark if has_remark
364 user.password = password if added_password || added_random_password
379 user.password = password if added_password || added_random_password
365 else
380 else
366 #create a random password if none are given
381 #create a random password if none are given
367 password = random_password unless password
382 password = random_password unless password
368 user = User.new({:login => login,
383 user = User.new({:login => login,
369 :full_name => full_name,
384 :full_name => full_name,
370 :password => password,
385 :password => password,
371 :password_confirmation => password,
386 :password_confirmation => password,
372 :alias => user_alias,
387 :alias => user_alias,
373 :remark => remark})
388 :remark => remark})
374 end
389 end
375 user.activated = true
390 user.activated = true
376
391
377 if user.save
392 if user.save
378 created_users << user
393 created_users << user
379 else
394 else
380 error_logins << "'#{login}'"
395 error_logins << "'#{login}'"
381 first_error = user.errors.full_messages.to_sentence unless first_error
396 first_error = user.errors.full_messages.to_sentence unless first_error
382 end
397 end
383 end
398 end
384 end
399 end
385
400
386 return {error_logins: error_logins, first_error: first_error, created_users: created_users}
401 return {error_logins: error_logins, first_error: first_error, created_users: created_users}
387
402
388 end
403 end
389
404
390 def self.find_non_admin_with_prefix(prefix='')
405 def self.find_non_admin_with_prefix(prefix='')
391 users = User.all
406 users = User.all
392 return users.find_all { |u| !(u.admin?) and u.login.index(prefix)==0 }
407 return users.find_all { |u| !(u.admin?) and u.login.index(prefix)==0 }
393 end
408 end
394
409
395 protected
410 protected
396 def encrypt_new_password
411 def encrypt_new_password
397 return if password.blank?
412 return if password.blank?
398 self.salt = (10+rand(90)).to_s
413 self.salt = (10+rand(90)).to_s
399 self.hashed_password = User.encrypt(self.password,self.salt)
414 self.hashed_password = User.encrypt(self.password,self.salt)
400 end
415 end
401
416
402 def assign_default_site
417 def assign_default_site
403 # have to catch error when migrating (because self.site is not available).
418 # have to catch error when migrating (because self.site is not available).
404 begin
419 begin
405 if self.site==nil
420 if self.site==nil
406 self.site = Site.find_by_name('default')
421 self.site = Site.find_by_name('default')
407 if self.site==nil
422 if self.site==nil
408 self.site = Site.find(1) # when 'default has be renamed'
423 self.site = Site.find(1) # when 'default has be renamed'
409 end
424 end
410 end
425 end
411 rescue
426 rescue
412 end
427 end
413 end
428 end
414
429
415 def assign_default_contest
430 def assign_default_contest
416 # have to catch error when migrating (because self.site is not available).
431 # have to catch error when migrating (because self.site is not available).
417 begin
432 begin
418 if self.contests.length == 0
433 if self.contests.length == 0
419 default_contest = Contest.find_by_name(GraderConfiguration['contest.default_contest_name'])
434 default_contest = Contest.find_by_name(GraderConfiguration['contest.default_contest_name'])
420 if default_contest
435 if default_contest
421 self.contests = [default_contest]
436 self.contests = [default_contest]
422 end
437 end
423 end
438 end
424 rescue
439 rescue
425 end
440 end
426 end
441 end
427
442
428 def password_required?
443 def password_required?
429 self.hashed_password.blank? || !self.password.blank?
444 self.hashed_password.blank? || !self.password.blank?
430 end
445 end
431
446
432 def self.encrypt(string,salt)
447 def self.encrypt(string,salt)
433 Digest::SHA1.hexdigest(salt + string)
448 Digest::SHA1.hexdigest(salt + string)
434 end
449 end
435
450
436 def uniqueness_of_email_from_activated_users
451 def uniqueness_of_email_from_activated_users
437 user = User.activated_users.find_by_email(self.email)
452 user = User.activated_users.find_by_email(self.email)
438 if user and (user.login != self.login)
453 if user and (user.login != self.login)
439 self.errors.add(:base,"Email has already been taken")
454 self.errors.add(:base,"Email has already been taken")
440 end
455 end
441 end
456 end
442
457
443 def enough_time_interval_between_same_email_registrations
458 def enough_time_interval_between_same_email_registrations
444 return if !self.new_record?
459 return if !self.new_record?
445 return if self.activated
460 return if self.activated
446 open_user = User.find_by_email(self.email,
461 open_user = User.find_by_email(self.email,
447 :order => 'created_at DESC')
462 :order => 'created_at DESC')
448 if open_user and open_user.created_at and
463 if open_user and open_user.created_at and
449 (open_user.created_at > Time.now.gmtime - 5.minutes)
464 (open_user.created_at > Time.now.gmtime - 5.minutes)
450 self.errors.add(:base,"There are already unactivated registrations with this e-mail address (please wait for 5 minutes)")
465 self.errors.add(:base,"There are already unactivated registrations with this e-mail address (please wait for 5 minutes)")
451 end
466 end
452 end
467 end
453
468
454 def email_validation?
469 def email_validation?
455 begin
470 begin
456 return VALIDATE_USER_EMAILS
471 return VALIDATE_USER_EMAILS
457 rescue
472 rescue
458 return false
473 return false
459 end
474 end
460 end
475 end
461 end
476 end
@@ -1,33 +1,33
1 - if submission.nil?
1 - if submission.nil?
2 = "-"
2 = "-"
3 - else
3 - else
4 - if local_assigns[:show_id]
4 - if local_assigns[:show_id]
5 .row
5 .row
6 - .col-3.text-secondary
6 + .col-3.fw-bold
7 - Sub ID:
7 + Sub ID
8 - %strong.col-9= submission.id
8 + .col-9.text-secondary-x= submission.id
9 - unless submission.graded_at
9 - unless submission.graded_at
10 .row
10 .row
11 - .col-3.text-secondary= t 'main.submitted_at'
11 + .col-3.fw-bold= t 'main.submitted_at'
12 - %strong.col-9= format_full_time_ago(submission.submitted_at.localtime)
12 + .col-9.text-secondary-x= format_full_time_ago(submission.submitted_at.localtime)
13 - else
13 - else
14 .row
14 .row
15 - .col-3.text-secondary= t 'main.graded_at'
15 + .col-3.fw-bold= t 'main.graded_at'
16 - %strong.col-9= format_full_time_ago(submission.graded_at.localtime)
16 + .col-9.text-secondary-x= format_full_time_ago(submission.graded_at.localtime)
17 - if GraderConfiguration['ui.show_score']
17 - if GraderConfiguration['ui.show_score']
18 .row
18 .row
19 - .col-3.text-secondary=t 'main.score'
19 + .col-3.fw-bold=t 'main.score'
20 - %strong.col-9
20 + .col-9.text-secondary-x
21 = (submission.points*100/submission.problem.full_score).to_i
21 = (submission.points*100/submission.problem.full_score).to_i
22 %tt.grader-comment
22 %tt.grader-comment
23 = " [#{submission.grader_comment}]"
23 = " [#{submission.grader_comment}]"
24 - if local_assigns[:show_button]
24 - if local_assigns[:show_button]
25 - if submission.graded_at
25 - if submission.graded_at
26 - if GraderConfiguration.show_grading_result
26 - if GraderConfiguration.show_grading_result
27 = link_to '[detailed result]', :action => 'result', :id => submission.id
27 = link_to '[detailed result]', :action => 'result', :id => submission.id
28 = link_to "#{t 'main.cmp_msg'}", compiler_msg_submission_path(submission), {popup: true,remote: true,class: 'btn btn-sm btn-info'}
28 = link_to "#{t 'main.cmp_msg'}", compiler_msg_submission_path(submission), {popup: true,remote: true,class: 'btn btn-sm btn-info'}
29 = link_to "#{t 'main.src_link'}",download_submission_path(submission), class: 'btn btn-sm btn-info'
29 = link_to "#{t 'main.src_link'}",download_submission_path(submission), class: 'btn btn-sm btn-info'
30 = link_to "#{t 'main.submissions_link'}", problem_submissions_path(submission.problem.id), class: 'btn btn-sm btn-info'
30 = link_to "#{t 'main.submissions_link'}", problem_submissions_path(submission.problem.id), class: 'btn btn-sm btn-info'
31 - if GraderConfiguration.show_testcase
31 - if GraderConfiguration.show_testcase
32 = link_to "testcases", show_problem_testcases_path(submission.problem.id), class: 'btn btn-sm btn-info'
32 = link_to "testcases", show_problem_testcases_path(submission.problem.id), class: 'btn btn-sm btn-info'
33
33
@@ -1,119 +1,129
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
6 - $(document).ready( function() {
7 - function shiftclick(start,stop,value) {
8 - $('tr input').each( function(id,input) {
9 - var $input=$(input);
10 - var iid=parseInt($input.attr('id').split('-')[2]);
11 - if(iid>=start&&iid<=stop){
12 - $input.prop('checked',value)
13 - }
14 - });
15 - }
16 -
17 - $('tr input').click( function(e) {
18 - if (e.shiftKey) {
19 - stop = parseInt($(this).attr('id').split('-')[2]);
20 - var orig_stop = stop
21 - if (typeof start !== 'undefined') {
22 - if (start > stop) {
23 - var tmp = start;
24 - start = stop;
25 - stop = tmp;
26 - }
27 - shiftclick(start,stop,$(this).is(':checked') )
28 - }
29 - start = orig_stop
30 - } else {
31 - start = parseInt($(this).attr('id').split('-')[2]);
32 - }
33 - });
34 - });
35
5
36
6
37 %h1 Manage problems
7 %h1 Manage problems
38
8
39 %p= link_to '[Back to problem list]', problems_path
9 %p= link_to '[Back to problem list]', problems_path
40
10
41 - = form_tag :action=>'do_manage' do
11 + = form_with url: do_manage_problems_path do |f|
42 - .panel.panel-primary
12 + .card.border-primary.mb-2
43 - .panel-heading
13 + .card-header.text-bg-primary.border-primary
44 Action
14 Action
45 - .panel-body
15 + .card-body
46 .submit-box
16 .submit-box
47 What do you want to do to the selected problem?
17 What do you want to do to the selected problem?
48 %br/
18 %br/
49 (You can shift-click to select a range of problems)
19 (You can shift-click to select a range of problems)
20 + .row
21 + .col-md-auto
22 + = f.check_box :change_date_added, class: 'form-check-input'
23 + .col-md-auto
24 + .form-check-label
25 + Change "Date added" to
26 + .col-md-auto
27 +
50 %ul.form-inline
28 %ul.form-inline
51 %li
29 %li
52 Change "Date added" to
30 Change "Date added" to
53 .input-group.date
31 .input-group.date
54 = text_field_tag :date_added, class: 'form-control'
32 = text_field_tag :date_added, class: 'form-control'
55 %span.input-group-addon
33 %span.input-group-addon
56 %span.glyphicon.glyphicon-calendar
34 %span.glyphicon.glyphicon-calendar
57 -# = select_date Date.current, :prefix => 'date_added'
35 -# = select_date Date.current, :prefix => 'date_added'
58 &nbsp;&nbsp;&nbsp;
36 &nbsp;&nbsp;&nbsp;
59 = submit_tag 'Change', :name => 'change_date_added', class: 'btn btn-primary btn-sm'
37 = submit_tag 'Change', :name => 'change_date_added', class: 'btn btn-primary btn-sm'
60 %li
38 %li
61 Set "Available" to
39 Set "Available" to
62 = submit_tag 'True', :name => 'enable_problem', class: 'btn btn-primary btn-sm'
40 = submit_tag 'True', :name => 'enable_problem', class: 'btn btn-primary btn-sm'
63 = submit_tag 'False', :name => 'disable_problem', class: 'btn btn-primary btn-sm'
41 = submit_tag 'False', :name => 'disable_problem', class: 'btn btn-primary btn-sm'
64
42
65 - if GraderConfiguration.multicontests?
43 - if GraderConfiguration.multicontests?
66 %li
44 %li
67 Add selected problems to contest
45 Add selected problems to contest
68 = select("contest","id",Contest.all.collect {|c| [c.title, c.id]})
46 = select("contest","id",Contest.all.collect {|c| [c.title, c.id]})
69 = submit_tag 'Add', :name => 'add_to_contest', class: 'btn btn-primary btn-sm'
47 = submit_tag 'Add', :name => 'add_to_contest', class: 'btn btn-primary btn-sm'
70 %li
48 %li
71 Add selected problems to user group
49 Add selected problems to user group
72 = select_tag "group_id", options_from_collection_for_select( Group.all, 'id','name',params[:group_name]), id: 'group_name',class: 'select2'
50 = select_tag "group_id", options_from_collection_for_select( Group.all, 'id','name',params[:group_name]), id: 'group_name',class: 'select2'
73 = submit_tag 'Add', name: 'add_group', class: 'btn btn-primary'
51 = submit_tag 'Add', name: 'add_group', class: 'btn btn-primary'
74 %li
52 %li
75 Add the following tags to the selected problems
53 Add the following tags to the selected problems
76 = select_tag "tag_ids", options_from_collection_for_select( Tag.all, 'id','name'), id: 'tags_name',class: 'select2', multiple: true, data: {placeholder: 'Select tags by clicking', width: "200px"}
54 = select_tag "tag_ids", options_from_collection_for_select( Tag.all, 'id','name'), id: 'tags_name',class: 'select2', multiple: true, data: {placeholder: 'Select tags by clicking', width: "200px"}
77 = submit_tag 'Add', name: 'add_tags', class: 'btn btn-primary'
55 = submit_tag 'Add', name: 'add_tags', class: 'btn btn-primary'
78
56
79 %table.table.table-hover.datatable
57 %table.table.table-hover.datatable
80 %thead
58 %thead
81 %tr{style: "text-align: left;"}
59 %tr{style: "text-align: left;"}
82 %th= check_box_tag 'select_all'
60 %th= check_box_tag 'select_all'
83 %th Name
61 %th Name
84 %th Full name
62 %th Full name
85 %th Tags
63 %th Tags
86 %th Available
64 %th Available
87 %th Date added
65 %th Date added
88 - if GraderConfiguration.multicontests?
66 - if GraderConfiguration.multicontests?
89 %th Contests
67 %th Contests
90
68
91 %tbody
69 %tbody
92 - num = 0
70 - num = 0
93 - for problem in @problems
71 - for problem in @problems
94 - num += 1
72 - num += 1
95 %tr{:id => "row-prob-#{problem.id}", :name=> "prob-#{problem.id}"}
73 %tr{:id => "row-prob-#{problem.id}", :name=> "prob-#{problem.id}"}
96 %td= check_box_tag "prob-#{problem.id}-#{num}"
74 %td= check_box_tag "prob-#{problem.id}-#{num}"
97 %td= problem.name
75 %td= problem.name
98 %td= problem.full_name
76 %td= problem.full_name
99 %td
77 %td
100 - problem.tags.each do |t|
78 - problem.tags.each do |t|
101 %span.label.label-default= t.name
79 %span.label.label-default= t.name
102 %td= problem.available
80 %td= problem.available
103 %td= problem.date_added
81 %td= problem.date_added
104 - if GraderConfiguration.multicontests?
82 - if GraderConfiguration.multicontests?
105 %td
83 %td
106 - problem.contests.each do |contest|
84 - problem.contests.each do |contest|
107 = "(#{contest.name} [#{link_to 'x', :action => 'remove_contest', :id => problem.id, :contest_id => contest.id }])"
85 = "(#{contest.name} [#{link_to 'x', :action => 'remove_contest', :id => problem.id, :contest_id => contest.id }])"
108
86
109 :javascript
87 :javascript
88 +
89 + $(document).on('import-map-loaded', function() {
90 + function shiftclick(start,stop,value) {
91 + $('tr input').each( function(id,input) {
92 + var $input=$(input);
93 + var iid=parseInt($input.attr('id').split('-')[2]);
94 + if(iid>=start&&iid<=stop){
95 + $input.prop('checked',value)
96 + }
97 + });
98 + }
99 +
100 + $('tr input').click( function(e) {
101 + if (e.shiftKey) {
102 + stop = parseInt($(this).attr('id').split('-')[2]);
103 + var orig_stop = stop
104 + if (typeof start !== 'undefined') {
105 + if (start > stop) {
106 + var tmp = start;
107 + start = stop;
108 + stop = tmp;
109 + }
110 + shiftclick(start,stop,$(this).is(':checked') )
111 + }
112 + start = orig_stop
113 + } else {
114 + start = parseInt($(this).attr('id').split('-')[2]);
115 + }
116 + });
117 +
110 $('.input-group.date').datetimepicker({
118 $('.input-group.date').datetimepicker({
111 format: 'DD/MMM/YYYY',
119 format: 'DD/MMM/YYYY',
112 showTodayButton: true,
120 showTodayButton: true,
113 locale: 'en',
121 locale: 'en',
114 widgetPositioning: {horizontal: 'auto', vertical: 'bottom'},
122 widgetPositioning: {horizontal: 'auto', vertical: 'bottom'},
115
123
116 });
124 });
125 +
117 $('.datatable').DataTable({
126 $('.datatable').DataTable({
118 paging: false
127 paging: false
119 });
128 });
129 + });
@@ -1,63 +1,90
1 :css
1 :css
2 .fix-width {
2 .fix-width {
3 font-family: "Consolas, Monaco, Droid Sans Mono,Mono, Monospace,Courier"
3 font-family: "Consolas, Monaco, Droid Sans Mono,Mono, Monospace,Courier"
4 }
4 }
5
5
6 %h1 Problem stat: #{@problem.name}
6 %h1 Problem stat: #{@problem.name}
7 - %h2 Overview
8
7
8 + .row.mb-3
9 + .col-md-8
10 + .card
11 + .card-body
12 + %h2.card-title Submission History
13 + %canvas#chart{height: '50px'}
14 +
15 + .col-md-4
16 + .card
17 + .card-body
18 + %h2.card-title General Info
9 .row
19 .row
10 - .col-md-2
20 + .col-sm-6
11 - %strong Name:
21 + Name
12 - .col-md-10
22 + .col-sm-6
13 = @problem.full_name #in_place_editor_field :problem, :full_name, {}, :rows=>1
23 = @problem.full_name #in_place_editor_field :problem, :full_name, {}, :rows=>1
14 = link_to_description_if_any "[#{t 'main.problem_desc'}] <span class='glyphicon glyphicon-file'></span>".html_safe, @problem
24 = link_to_description_if_any "[#{t 'main.problem_desc'}] <span class='glyphicon glyphicon-file'></span>".html_safe, @problem
15 .row
25 .row
16 - .col-md-2.strong
26 + .col-sm-6
17 - %strong Submissions:
27 + Subs
18 - .col-md-10
28 + .col-sm-6
19 = @submissions.count
29 = @submissions.count
20 .row
30 .row
21 - .col-md-2.strong
31 + .col-sm-6
22 - %strong Solved/Attemped User
32 + Solved/Attempted User
23 - .col-md-10
33 + .col-sm-6
24 #{@summary[:solve]}/#{@summary[:attempt]} (#{(@summary[:solve]*100.0/@summary[:attempt]).round(1)}%)
34 #{@summary[:solve]}/#{@summary[:attempt]} (#{(@summary[:solve]*100.0/@summary[:attempt]).round(1)}%)
25
35
26 -
27 - %h2 Submissions Count
28 - = render partial: 'application/bar_graph', locals: { histogram: @histogram }
29 -
30 %h2 Submissions
36 %h2 Submissions
31 - if @submissions and @submissions.count > 0
37 - if @submissions and @submissions.count > 0
32 %table#main_table.table.table-condensed.table-striped
38 %table#main_table.table.table-condensed.table-striped
33 %thead
39 %thead
34 %tr
40 %tr
35 %th ID
41 %th ID
36 %th Login
42 %th Login
37 %th Name
43 %th Name
38 %th Submitted_at
44 %th Submitted_at
39 %th language
45 %th language
40 %th Points
46 %th Points
41 %th comment
47 %th comment
42 %th IP
48 %th IP
43 %tbody
49 %tbody
44 - row_odd,curr = true,''
50 - row_odd,curr = true,''
45 - @submissions.each do |sub|
51 - @submissions.each do |sub|
46 - next unless sub.user
52 - next unless sub.user
47 - row_odd,curr = !row_odd, sub.user if curr != sub.user
53 - row_odd,curr = !row_odd, sub.user if curr != sub.user
48 %tr
54 %tr
49 %td= link_to sub.id, submission_path(sub)
55 %td= link_to sub.id, submission_path(sub)
50 %td= link_to sub.user.login, stat_user_path(sub.user)
56 %td= link_to sub.user.login, stat_user_path(sub.user)
51 %td= sub.user.full_name
57 %td= sub.user.full_name
52 %td{data: {order: sub.submitted_at}}= time_ago_in_words(sub.submitted_at) + " ago"
58 %td{data: {order: sub.submitted_at}}= time_ago_in_words(sub.submitted_at) + " ago"
53 %td= sub.language.name
59 %td= sub.language.name
54 %td= sub.points
60 %td= sub.points
55 %td.fix-width= sub.grader_comment
61 %td.fix-width= sub.grader_comment
56 %td= sub.ip_address
62 %td= sub.ip_address
57 - else
63 - else
58 No submission
64 No submission
59
65
60 :javascript
66 :javascript
67 + $(document).on('import-map-loaded',(e) => {
68 + //init datatable
61 $("#main_table").DataTable({
69 $("#main_table").DataTable({
62 paging: false
70 paging: false
63 });
71 });
72 +
73 + //history graph
74 + data = #{@chart_dataset}
75 + config = {
76 + type: 'bar',
77 + data: data,
78 + options: {
79 + plugins: {
80 + legend: {
81 + display: false
82 + },
83 + },
84 + }
85 + }
86 + Chart.defaults.font.size = 15
87 + //Chart.defaults.font.family = 'Sarabun Light'
88 + chart = new Chart($('#chart'),config)
89 + });
90 +
@@ -1,70 +1,95
1 - - content_for :header do
2 - = javascript_include_tag 'local_jquery'
3 -
4 - :javascript
5 - $(function () {
6 - $('#submission_table').tablesorter({widgets: ['zebra']});
7 - });
8
1
9 :css
2 :css
10 .fix-width {
3 .fix-width {
11 font-family: Droid Sans Mono,Consolas, monospace, mono, Courier New, Courier;
4 font-family: Droid Sans Mono,Consolas, monospace, mono, Courier New, Courier;
12 }
5 }
13
6
14 - %h1= @user.full_name
7 + %h1 User stats
8 + %h5.text-secondary= @user.login
9 +
10 + .row.my-3
11 + .col-md-8
12 + .card
13 + .card-body
14 + %h2.card-title Sub Info
15 + %canvas#chart{height: '50px'}
15
16
16 - <b>Login:</b> #{@user.login} <br/>
17 + .col-md-4
17 - <b>Full name:</b> #{@user.full_name} <br />
18 + .card
19 + .card-body
20 + %h2.card-title General Info
21 + .row
22 + .col-sm-6.fw-bold
23 + Login
24 + .col-sm-6
25 + = @user.login
26 + .row
27 + .col-sm-6.fw-bold
28 + Full name
29 + .col-sm-6
30 + = @user.full_name
31 + .row
32 + .col-sm-6.fw-bold
33 + Subs
34 + .col-sm-6
35 + = @summary[:count]
36 + .row
37 + .col-sm-6.fw-bold
38 + Solved/Attempted Problem
39 + .col-sm-6
40 + #{@summary[:solve]}/#{@summary[:attempt]} (#{(@summary[:solve]*100.0/@summary[:attempt]).round(1)}%)
18
41
19
42
20 - %h2 Problem Stat
43 + %table#main_table.table.table-striped
21 - %table.info
22 - %thead
23 - %tr.info-head
24 - %th Stat
25 - %th Value
26 - %tbody
27 - %tr{class: cycle('info-even','info-odd')}
28 - %td.info_param Submissions
29 - %td= @summary[:count]
30 - %tr{class: cycle('info-even','info-odd')}
31 - %td.info_param Solved/Attempted Problem
32 - %td #{@summary[:solve]}/#{@summary[:attempt]} (#{(@summary[:solve]*100.0/@summary[:attempt]).round(1)}%)
33 -
34 - %h2 Submission History
35 -
36 - =render partial: 'application/bar_graph', locals: {histogram: @histogram, param: {bar_width: 7}}
37 -
38 -
39 - %table#submission_table.table.table-striped
40 %thead
44 %thead
41 %tr
45 %tr
42 %th ID
46 %th ID
43 %th Problem code
47 %th Problem code
44 %th Problem full name
48 %th Problem full name
45 %th Language
49 %th Language
46 %th Submitted at
50 %th Submitted at
47 %th Result
51 %th Result
48 %th Score
52 %th Score
49 - if session[:admin]
53 - if session[:admin]
50 %th IP
54 %th IP
51 %tbody
55 %tbody
52 - @submission.each do |s|
56 - @submission.each do |s|
53 - next unless s.problem
57 - next unless s.problem
54 %tr
58 %tr
55 %td= link_to s.id, submission_path(s)
59 %td= link_to s.id, submission_path(s)
56 %td= link_to s.problem.name, stat_problem_path(s.problem)
60 %td= link_to s.problem.name, stat_problem_path(s.problem)
57 %td= s.problem.full_name
61 %td= s.problem.full_name
58 %td= s.language.pretty_name
62 %td= s.language.pretty_name
59 %td #{s.submitted_at.strftime('%Y-%m-%d %H:%M')} (#{time_ago_in_words(s.submitted_at)} ago)
63 %td #{s.submitted_at.strftime('%Y-%m-%d %H:%M')} (#{time_ago_in_words(s.submitted_at)} ago)
60 %td.fix-width= s.grader_comment
64 %td.fix-width= s.grader_comment
61 %td= ( s.try(:points) ? (s.points*100/s.problem.full_score) : '' )
65 %td= ( s.try(:points) ? (s.points*100/s.problem.full_score) : '' )
62 - if session[:admin]
66 - if session[:admin]
63 %td= s.ip_address
67 %td= s.ip_address
64
68
65
69
66
70
67 :javascript
71 :javascript
68 - $("#submission_table").DataTable({
72 + $(document).on('import-map-loaded',(e) => {
73 + //init datatable
74 + $("#main_table").DataTable({
69 paging: false
75 paging: false
70 });
76 });
77 +
78 + //history graph
79 + data = #{@chart_dataset}
80 + config = {
81 + type: 'bar',
82 + data: data,
83 + options: {
84 + plugins: {
85 + legend: {
86 + display: false
87 + },
88 + },
89 + }
90 + }
91 + Chart.defaults.font.size = 15
92 + //Chart.defaults.font.family = 'Sarabun Light'
93 + chart = new Chart($('#chart'),config)
94 + });
95 +
deleted file
You need to be logged in to leave comments. Login now