Description:
allow ta to set model submission
Commit status:
[Not Reviewed]
References:
Comments:
0 Commit comments 0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
Add another comment

r856:9a927c70d5aa - - 4 files changed: 13 inserted, 10 deleted

@@ -1,327 +1,327
1 1 require 'digest/sha1'
2 2 require 'net/pop'
3 3 require 'net/https'
4 4 require 'net/http'
5 5 require 'json'
6 6
7 7 class User < ActiveRecord::Base
8 8
9 9 has_and_belongs_to_many :roles
10 10
11 11 #has_and_belongs_to_many :groups
12 12 has_many :groups_users, class_name: 'GroupUser'
13 13 has_many :groups, :through => :groups_users
14 14
15 15 has_many :test_requests, -> {order(submitted_at: :desc)}
16 16
17 17 has_many :messages, -> { order(created_at: :desc) },
18 18 :class_name => "Message",
19 19 :foreign_key => "sender_id"
20 20
21 21 has_many :replied_messages, -> { order(created_at: :desc) },
22 22 :class_name => "Message",
23 23 :foreign_key => "receiver_id"
24 24
25 25 has_many :logins
26 26
27 27 has_one :contest_stat, :class_name => "UserContestStat", :dependent => :destroy
28 28
29 29 belongs_to :site
30 30 belongs_to :country
31 31
32 32 has_and_belongs_to_many :contests, -> { order(:name)}
33 33
34 34 scope :activated_users, -> {where activated: true}
35 35
36 36 validates_presence_of :login
37 37 validates_uniqueness_of :login
38 38 validates_format_of :login, :with => /\A[\_A-Za-z0-9]+\z/
39 39 validates_length_of :login, :within => 3..30
40 40
41 41 validates_presence_of :full_name
42 42 validates_length_of :full_name, :minimum => 1
43 43
44 44 validates_presence_of :password, :if => :password_required?
45 45 validates_length_of :password, :within => 4..50, :if => :password_required?
46 46 validates_confirmation_of :password, :if => :password_required?
47 47
48 48 validates_format_of :email,
49 49 :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i,
50 50 :if => :email_validation?
51 51 validate :uniqueness_of_email_from_activated_users,
52 52 :if => :email_validation?
53 53 validate :enough_time_interval_between_same_email_registrations,
54 54 :if => :email_validation?
55 55
56 56 # these are for ytopc
57 57 # disable for now
58 58 #validates_presence_of :province
59 59
60 60 attr_accessor :password
61 61
62 62 before_save :encrypt_new_password
63 63 before_save :assign_default_site
64 64 before_save :assign_default_contest
65 65
66 66 # this is for will_paginate
67 67 cattr_reader :per_page
68 68 @@per_page = 50
69 69
70 70 def self.authenticate(login, password)
71 71 user = find_by_login(login)
72 72 if user
73 73 return user if user.authenticated?(password)
74 74 if user.authenticated_by_cucas?(password)
75 75 user.password = password
76 76 user.save
77 77 return user
78 78 end
79 79 end
80 80 end
81 81
82 82
83 83 def authenticated?(password)
84 84 if self.activated
85 85 hashed_password == User.encrypt(password,self.salt)
86 86 else
87 87 false
88 88 end
89 89 end
90 90
91 91 def login_with_name
92 92 "[#{login}] #{full_name}"
93 93 end
94 94
95 95 def authenticated_by_cucas?(password)
96 96 url = URI.parse('https://www.cas.chula.ac.th/cas/api/?q=studentAuthenticate')
97 97 appid = '41508763e340d5858c00f8c1a0f5a2bb'
98 98 appsecret ='d9cbb5863091dbe186fded85722a1e31'
99 99 post_args = {
100 100 'appid' => appid,
101 101 'appsecret' => appsecret,
102 102 'username' => login,
103 103 'password' => password
104 104 }
105 105
106 106 #simple call
107 107 begin
108 108 http = Net::HTTP.new('www.cas.chula.ac.th', 443)
109 109 http.use_ssl = true
110 110 http.verify_mode = OpenSSL::SSL::VERIFY_NONE
111 111 result = [ ]
112 112 http.start do |http|
113 113 req = Net::HTTP::Post.new('/cas/api/?q=studentAuthenticate')
114 114 #req = Net::HTTP::Post.new('/appX/prod/?q=studentAuthenticate')
115 115 #req = Net::HTTP::Post.new('/app2/prod/api/?q=studentAuthenticate')
116 116 param = "appid=#{appid}&appsecret=#{appsecret}&username=#{login}&password=#{password}"
117 117 resp = http.request(req,param)
118 118 result = JSON.parse resp.body
119 119 puts result
120 120 end
121 121 return true if result["type"] == "beanStudent"
122 122 rescue => e
123 123 puts e
124 124 puts e.message
125 125 return false
126 126 end
127 127 return false
128 128 end
129 129
130 130 def admin?
131 131 has_role?('admin')
132 132 end
133 133
134 134 def has_role?(role)
135 - self.roles.where(name: role).count > 0
135 + self.roles.where(name: [role,'admin']).count > 0
136 136 end
137 137
138 138 def email_for_editing
139 139 if self.email==nil
140 140 "(unknown)"
141 141 elsif self.email==''
142 142 "(blank)"
143 143 else
144 144 self.email
145 145 end
146 146 end
147 147
148 148 def email_for_editing=(e)
149 149 self.email=e
150 150 end
151 151
152 152 def alias_for_editing
153 153 if self.alias==nil
154 154 "(unknown)"
155 155 elsif self.alias==''
156 156 "(blank)"
157 157 else
158 158 self.alias
159 159 end
160 160 end
161 161
162 162 def alias_for_editing=(e)
163 163 self.alias=e
164 164 end
165 165
166 166 def activation_key
167 167 if self.hashed_password==nil
168 168 encrypt_new_password
169 169 end
170 170 Digest::SHA1.hexdigest(self.hashed_password)[0..7]
171 171 end
172 172
173 173 def verify_activation_key(key)
174 174 key == activation_key
175 175 end
176 176
177 177 def self.random_password(length=5)
178 178 chars = 'abcdefghjkmnopqrstuvwxyz'
179 179 password = ''
180 180 length.times { password << chars[rand(chars.length - 1)] }
181 181 password
182 182 end
183 183
184 184 # Contest information
185 185
186 186 def self.find_users_with_no_contest()
187 187 users = User.all
188 188 return users.find_all { |u| u.contests.length == 0 }
189 189 end
190 190
191 191
192 192 def contest_time_left
193 193 if GraderConfiguration.contest_mode?
194 194 return nil if site==nil
195 195 return site.time_left
196 196 elsif GraderConfiguration.indv_contest_mode?
197 197 time_limit = GraderConfiguration.contest_time_limit
198 198 if time_limit == nil
199 199 return nil
200 200 end
201 201 if contest_stat==nil or contest_stat.started_at==nil
202 202 return (Time.now.gmtime + time_limit) - Time.now.gmtime
203 203 else
204 204 finish_time = contest_stat.started_at + time_limit
205 205 current_time = Time.now.gmtime
206 206 if current_time > finish_time
207 207 return 0
208 208 else
209 209 return finish_time - current_time
210 210 end
211 211 end
212 212 else
213 213 return nil
214 214 end
215 215 end
216 216
217 217 def contest_finished?
218 218 if GraderConfiguration.contest_mode?
219 219 return false if site==nil
220 220 return site.finished?
221 221 elsif GraderConfiguration.indv_contest_mode?
222 222 return false if self.contest_stat==nil
223 223 return contest_time_left == 0
224 224 else
225 225 return false
226 226 end
227 227 end
228 228
229 229 def contest_started?
230 230 if GraderConfiguration.indv_contest_mode?
231 231 stat = self.contest_stat
232 232 return ((stat != nil) and (stat.started_at != nil))
233 233 elsif GraderConfiguration.contest_mode?
234 234 return true if site==nil
235 235 return site.started
236 236 else
237 237 return true
238 238 end
239 239 end
240 240
241 241 def update_start_time
242 242 stat = self.contest_stat
243 243 if stat.nil? or stat.started_at.nil?
244 244 stat ||= UserContestStat.new(:user => self)
245 245 stat.started_at = Time.now.gmtime
246 246 stat.save
247 247 end
248 248 end
249 249
250 250 def problem_in_user_contests?(problem)
251 251 problem_contests = problem.contests.all
252 252
253 253 if problem_contests.length == 0 # this is public contest
254 254 return true
255 255 end
256 256
257 257 contests.each do |contest|
258 258 if problem_contests.find {|c| c.id == contest.id }
259 259 return true
260 260 end
261 261 end
262 262 return false
263 263 end
264 264
265 265 def available_problems_group_by_contests
266 266 contest_problems = []
267 267 pin = {}
268 268 contests.enabled.each do |contest|
269 269 available_problems = contest.problems.available
270 270 contest_problems << {
271 271 :contest => contest,
272 272 :problems => available_problems
273 273 }
274 274 available_problems.each {|p| pin[p.id] = true}
275 275 end
276 276 other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0}
277 277 contest_problems << {
278 278 :contest => nil,
279 279 :problems => other_avaiable_problems
280 280 }
281 281 return contest_problems
282 282 end
283 283
284 284 def solve_all_available_problems?
285 285 available_problems.each do |p|
286 286 u = self
287 287 sub = Submission.find_last_by_user_and_problem(u.id,p.id)
288 288 return false if !p or !sub or sub.points < p.full_score
289 289 end
290 290 return true
291 291 end
292 292
293 293 #get a list of available problem
294 294 def available_problems
295 295 # first, we check if this is normal mode
296 296 if not GraderConfiguration.multicontests?
297 297
298 298 #if this is a normal mode
299 299 #we show problem based on problem_group, if the config said so
300 300 if GraderConfiguration.use_problem_group?
301 301 return available_problems_in_group
302 302 else
303 303 return Problem.available_problems
304 304 end
305 305 else
306 306 #this is multi contest mode
307 307 contest_problems = []
308 308 pin = {}
309 309 contests.enabled.each do |contest|
310 310 contest.problems.available.each do |problem|
311 311 if not pin.has_key? problem.id
312 312 contest_problems << problem
313 313 end
314 314 pin[problem.id] = true
315 315 end
316 316 end
317 317 other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0}
318 318 return contest_problems + other_avaiable_problems
319 319 end
320 320 end
321 321
322 322 # new feature, get list of available problem in all enabled group that the user belongs to
323 323 def available_problems_in_group
324 324 problem = []
325 325 self.groups.where(enabled: true).each do |group|
326 326 group.problems.where(available: true).each { |p| problem << p }
327 327 end
@@ -1,154 +1,156
1 1 :css
2 2 .hof_user { color: orangered; font-style: italic; }
3 3 .hof_language { color: green; font-style: italic; }
4 4 .hof_value { color: deeppink;font-style: italic; }
5 5 .info_param { font-weight: bold;text-align: right; }
6 6 .tooltip {
7 7 font-family: Verdana,sans-serif;
8 8 font-weight: normal;
9 9 text-align: left;
10 10 font-size: 1.0em;
11 11 color: black;
12 12 line-height: 1.1;
13 13 display: none;
14 14 min-width: 20em;
15 15 position: absolute;
16 16 left: 25px;
17 17 bottom: 5px;
18 18 border: 1px solid;
19 19 padding: 5px;
20 20 background-color: #FFF;
21 21 word-wrap: break-word;
22 22 z-index: 9999;
23 23 overflow: auto;
24 24 }
25 25
26 26
27 27 .container-fluid
28 28 .row
29 29 .col-md-8
30 30 .card
31 31 .card-body
32 32 %h2.card-title Submission History
33 33 %canvas#chart{height: '50px'}
34 34
35 35 .col-md-4
36 36 .card
37 37 .card-body
38 38 %h2.card-title General Info
39 39 .row
40 40 .col-sm-6
41 41 Subs
42 42 .col-sm-6
43 43 = @summary[:count]
44 44 .row
45 45 .col-sm-6
46 46 Solved/Attempted User
47 47 .col-sm-6
48 48 #{@summary[:solve]}/#{@summary[:attempt]} (#{(@summary[:solve]*100.0/@summary[:attempt]).round(1)}%)
49 49 .row
50 50 .col-md-4
51 51 .card
52 52 .card-body
53 53 %h2.card-title Model submission
54 54 %table.table.table-hover
55 55 %thead
56 56 %tr
57 - %th #Sub
57 + %th #Sub (lang)
58 58 %th Author
59 59 %tbody
60 60 - @model_subs.each do |sub|
61 61 %tr
62 - %td= link_to "##{sub.id}", submission_path(sub)
62 + %td
63 + = link_to "##{sub.id}", submission_path(sub)
64 + = "(#{sub.language.pretty_name})"
63 65 %td= sub.user.full_name
64 66 .col-md-8
65 67 - if @best
66 68 .card
67 69 .card-body
68 70 %h2.card-title Top Submissions
69 71 %table.table.table-hover
70 72 %thead
71 73 %tr
72 74 %th Language
73 75 %th Best runtime (ms)
74 76 %th Best memory (kbytes)
75 77 %th Shortest Code (bytes)
76 78 %th First solver
77 79 %tbody
78 80 %tr.bg-warning
79 81 %td
80 82 Overall
81 83 %td
82 84 by #{link_to @best[:runtime][:user], stat_user_path(@best[:runtime][:user_id])}
83 85 %br
84 86 using <span class="text-success">#{@best[:runtime][:lang]}</span>
85 87 %br
86 88 with <span class="text-success">#{@best[:runtime][:value] * 1000} milliseconds</span>
87 89 %br= link_to "#" + @best[:runtime][:sub_id].to_s, submission_path(@best[:runtime][:sub_id])
88 90 %td
89 91 by #{link_to @best[:memory][:user], stat_user_path(@best[:memory][:user_id])}
90 92 %br
91 93 using <span class="text-success">#{@best[:memory][:lang]}</span>
92 94 %br
93 95 with <span class="text-success">#{number_with_delimiter(@best[:memory][:value])} kbytes </span>
94 96 %br= link_to "#" + @best[:memory][:sub_id].to_s, submission_path(@best[:memory][:sub_id])
95 97 %td
96 98 by #{link_to @best[:length][:user], stat_user_path(@best[:length][:user_id])}
97 99 %br
98 100 using <span class="text-success">#{@best[:length][:lang]}</span>
99 101 %br
100 102 with <span class="text-success">#{@best[:length][:value]} bytes</span>
101 103 %br= link_to "#" + @best[:length][:sub_id].to_s, submission_path(@best[:length][:sub_id])
102 104 %td
103 105 - if @best[:first][:user] != '(NULL)'
104 106 #{link_to @best[:first][:user], stat_user_path(@best[:first][:user_id])} is the first solver
105 107 %br
106 108 using <span class="text-success">#{@best[:first][:lang]}</span>
107 109 %br
108 110 on <span class="text-success">#{@best[:first][:value]}</span>
109 111 %br= link_to "#" + @best[:first][:sub_id].to_s, submission_path( @best[:first][:sub_id])
110 112 - else
111 113 no first solver
112 114 - @by_lang.each do |lang,value|
113 115 %tr
114 116 %td= lang
115 117 %td
116 118 = link_to value[:runtime][:user], stat_user_path(value[:runtime][:user_id])
117 119 %br
118 120 = "#{(value[:runtime][:value] * 1000).to_i} @"
119 121 = link_to "#" + value[:runtime][:sub_id].to_s, submission_path( value[:runtime][:sub_id])
120 122 %td
121 123 = link_to value[:memory][:user], stat_user_path( value[:memory][:user_id])
122 124 %br
123 125 = "#{number_with_delimiter(value[:memory][:value])} @"
124 126 = link_to "#" + value[:memory][:sub_id].to_s, submission_path(value[:memory][:sub_id])
125 127 %td
126 128 = link_to value[:length][:user], stat_user_path(value[:length][:user_id])
127 129 %br
128 130 = "#{value[:length][:value]} @"
129 131 = link_to "#" + value[:length][:sub_id].to_s, submission_path(value[:length][:sub_id])
130 132 %td
131 133 - if value[:first][:user] != '(NULL)' #TODO: i know... this is wrong...
132 134 = link_to value[:first][:user], stat_user_path(value[:first][:user_id])
133 135 %br
134 136 = "#{value[:first][:value]} @"
135 137 = link_to "#" + value[:first][:sub_id].to_s, submission_path( value[:first][:sub_id])
136 138
137 139 %script{src:"https://cdn.jsdelivr.net/npm/chart.js"}
138 140 :javascript
139 141 data = #{@chart_dataset}
140 142 config = {
141 143 type: 'bar',
142 144 data: data,
143 145 options: {
144 146 plugins: {
145 147 legend: {
146 148 display: false
147 149 },
148 150 },
149 151 }
150 152 }
151 153 Chart.defaults.font.size = 15
152 154 //Chart.defaults.font.family = 'Sarabun Light'
153 155 chart = new Chart($('#chart'),config)
154 156
@@ -1,29 +1,30
1 1
2 2 /- if params[:id]
3 3 / %h1 Tasks Hall of Fame
4 4 / = link_to('[back to All-Time Hall of Fame]', action: 'problem_hof', id: nil )
5 5 /- else
6 6 / %h1 All-Time Hall of Fame
7 7
8 8 .panel.panel-info
9 9 .panel-heading
10 10 Select Task
11 11 .panel-body
12 12 .form-inline
13 13 = select 'report',
14 14 'problem_id',
15 15 @problems.collect {|p| ["[#{p.name}] #{p.full_name}", problem_hof_report_path(p)]},
16 16 {:selected => problem_hof_report_path(@problem)},
17 17 { class: 'select2 form-control' }
18 18 %button.btn.btn-primary.btn-sm.go-button#problem_go{data: {source: "#report_problem_id"}} Go
19 19
20 20
21 21 - unless params[:id]
22 22 /=render partial: 'all_time_hof'
23 23 Please select a problem.
24 24 - else
25 - %h1 [#{Problem.find(params[:id]).name}] #{Problem.find(params[:id]).full_name}
26 - -# %h2 Submission History
27 - -# =render partial: 'application/bar_graph', locals: { histogram: @histogram }
25 + %h1
26 + [#{Problem.find(params[:id]).name}] #{Problem.find(params[:id]).full_name}
27 + - if @current_user.has_role?('ta')
28 + %a{href:stat_problem_path(@problem)} (stat)
28 29 =render partial: 'task_hof'
29 30
@@ -1,128 +1,128
1 1 %h1= "Submission: #{@submission.id}"
2 2
3 3 %textarea#data{style: "display:none;"}
4 4 :preserve
5 5 #{@submission.source}
6 6
7 7 //%div.highlight{:style => "border: 1px solid black;"}
8 8 //=@formatted_code.html_safe
9 9
10 10
11 11 .containter
12 12 .row
13 13 .col-md-7
14 14 %h2 Source Code
15 15 .col-md-5
16 16 %h2 Stat
17 17 .row
18 18 .col-md-7
19 19 %div#editor{ style: "font-size: 14px; height: 400px; border-radius:5px;" }
20 20 :javascript
21 21 e = ace.edit("editor")
22 22 e.setOptions({ maxLines: Infinity })
23 23 e.setValue($("#data").text())
24 24 e.gotoLine(1)
25 25 e.getSession().setMode("#{get_ace_mode(@submission.language)}")
26 26 e.setReadOnly(true)
27 27 .col-md-5
28 28 %table.table.table-striped
29 29 %tr
30 30 %td.text-right
31 31 %strong User
32 32 %td
33 33 - if @current_user.admin? ||@current_user == @submission.user
34 34 - if @submission.user
35 35 = link_to "#{@submission.user.login}", stat_user_path(@submission.user)
36 36 = @submission.user.full_name
37 37 - else
38 38 = "(n/a)"
39 39 - else
40 40 = '-- REDACTED --'
41 41 %tr
42 42 %td.text-right
43 43 %strong Task
44 44 %td
45 45 - if @submission.problem!=nil
46 46 = link_to "[#{@submission.problem.name}]", stat_problem_path(@submission.problem)
47 47 = @submission.problem.full_name
48 48 = link_to_description_if_any "[download] <span class='glyphicon glyphicon-file'></span>".html_safe, @submission.problem
49 49 - else
50 50 = "(n/a)"
51 51 %tr
52 52 %td.text-right
53 53 %strong Tries
54 54 %td= @submission.number
55 55 %tr
56 56 %td.text-right
57 57 %strong Language
58 58 %td= @submission.language.pretty_name
59 59 %tr
60 60 %td.text-right
61 61 %strong Submitted
62 62 %td #{time_ago_in_words(@submission.submitted_at)} ago (at #{@submission.submitted_at.to_formatted_s(:long)})
63 63 %tr
64 64 %td.text-right
65 65 %strong Graded
66 66 - if @submission.graded_at
67 67 %td #{time_ago_in_words(@submission.graded_at)} ago (at #{@submission.graded_at.to_formatted_s(:long)})
68 68 - else
69 69 %td -
70 70 %tr
71 71 %td.text-right
72 72 %strong Points
73 73 %td #{@submission.points}/#{@submission.try(:problem).try(:full_score)}
74 74 %tr
75 75 %td.text-right
76 76 %strong Comment
77 77 %td #{@submission.grader_comment}
78 78 %tr
79 79 %td.text-right
80 80 %strong Runtime (s)
81 81 %td #{@submission.max_runtime}
82 82 %tr
83 83 %td.text-right
84 84 %strong Memory (kb)
85 85 %td #{@submission.peak_memory}
86 86 %tr
87 87 %td.text-right
88 88 %strong Compiler result
89 89 %td
90 90 %button.btn.btn-info.btn-xs{type: 'button', data: {toggle: 'modal', target: '#compiler'}}
91 91 view
92 92 %tr
93 93 %td.text-right
94 94 %strong Grading Task Status
95 95 %td
96 96 = @task.status_str if @task
97 - - if session[:admin]
97 + - if @current_user.admin?
98 98 = link_to "rejudge", rejudge_submission_path, data: {remote: true}, class: 'btn btn-info btn-xs'
99 - - if session[:admin]
99 + - if @current_user.has_role?('ta')
100 100 %tr
101 101 %td.text-right
102 102 %strong IP
103 103 %td #{@submission.ip_address}
104 104 %tr
105 105 %td.text-right
106 106 %strong Model solution
107 107 %td
108 108 - if @submission.tag_model?
109 109 YES
110 - - if session[:admin]
110 + - if @current_user.has_role?('ta')
111 111 = link_to "remove model status", set_tag_submission_path(@submission, tag: :default), class: 'btn btn-warning btn-xs'
112 112 - else
113 113 No
114 - - if session[:admin]
114 + - if @current_user.has_role?('ta')
115 115 = link_to "set as model solution", set_tag_submission_path(@submission, tag: :model), class: 'btn btn-success btn-xs'
116 116
117 117
118 118 .modal.fade#compiler{tabindex: -1,role: 'dialog'}
119 119 .modal-dialog.modal-lg{role:'document'}
120 120 .modal-content
121 121 .modal-header
122 122 %button.close{type: 'button', data: {dismissed: :modal}, aria: {label: 'close'}}
123 123 %span{aria: {hidden: 'true'}, data: {dismiss: 'modal'}} &times;
124 124 %h4 Compiler message
125 125 .modal-body
126 126 %pre#compiler_msg= @submission.compiler_message
127 127 .modal-footer
128 128 %button.btn.btn-default{type: 'button', data: {dismiss: 'modal'}} Close
You need to be logged in to leave comments. Login now