Description:
Merge pull request #14 from nattee/master merge commit from nattee
Commit status:
[Not Reviewed]
References:
merge default
Comments:
0 Commit comments 0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
Add another comment

r486:17754ce1a3d6 - - 67 files changed: 1558 inserted, 433 deleted

@@ -0,0 +1,14
1 + //= require jquery
2 + //= require jquery_ujs
3 + //= require jquery.ui.all
4 + //= require jquery.ui.datepicker
5 + //= require jquery.ui.slider
6 + //= require jquery-ui-timepicker-addon
7 + //= require jquery-tablesorter
8 + //= require best_in_place
9 + //= require best_in_place.jquery-ui
10 +
11 + $(document).ready(function() {
12 + /* Activating Best In Place */
13 + jQuery(".best_in_place").best_in_place();
14 + });
@@ -0,0 +1,3
1 + # Place all the behaviors and hooks related to the matching controller here.
2 + # All this logic will automatically be available in application.js.
3 + # You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
@@ -0,0 +1,3
1 + // Place all the styles related to the report controller here.
2 + // They will automatically be included in application.css.
3 + // You can use Sass (SCSS) here: http://sass-lang.com/
@@ -0,0 +1,197
1 + /*************
2 + Metro Dark Theme
3 + *************/
4 + /* overall */
5 + .tablesorter-cafe {
6 + // font: 12px/18px 'Segoe UI Semilight', 'Open Sans', Verdana, Arial, Helvetica, sans-serif;
7 + color: #000;
8 + background-color: #777;
9 + margin: 10px 0 15px;
10 + text-align: left;
11 + border-collapse: collapse;
12 + border: #555 1px solid;
13 + }
14 +
15 + .tablesorter-cafe tr.dark-row th, .tablesorter-cafe tr.dark-row td {
16 + background-color: #222;
17 + color: #fff;
18 + text-align: left;
19 + font-size: 14px;
20 + }
21 +
22 + /* header/footer */
23 + .tablesorter-cafe caption,
24 + .tablesorter-cafe th,
25 + .tablesorter-cafe thead td,
26 + .tablesorter-cafe tfoot th,
27 + .tablesorter-cafe tfoot td {
28 + //font-weight: 300;
29 + //font-size: 15px;
30 + color: #fff;
31 + background-color: #777;
32 + padding: 2px;
33 + border: #555 1px solid;
34 + }
35 +
36 + .tablesorter-cafe .header,
37 + .tablesorter-cafe .tablesorter-header {
38 + background-image: url(data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAQBAMAAADQT4M0AAAAGFBMVEUAAADu7u7u7u7u7u7u7u7u7u7u7u7u7u5jNePWAAAACHRSTlMAMxIHKwEgMWD59H4AAABSSURBVAjXY2BgYFJgAAHzYhDJ6igSAKTYBAUTgJSioKAQAwNzoaCguAFDiCAQuDIkgigxBgiA8cJAVCpQt6AgSL+JoKAzA0gjUBsQqBcBCYhFAAE/CV4zeSzxAAAAAElFTkSuQmCC);
39 + background-position: center right;
40 + background-repeat: no-repeat;
41 + cursor: pointer;
42 + white-space: normal;
43 + }
44 + .tablesorter-cafe .tablesorter-header-inner {
45 + padding: 0 18px 0 4px;
46 + }
47 + .tablesorter-cafe thead .headerSortUp,
48 + .tablesorter-cafe thead .tablesorter-headerSortUp,
49 + .tablesorter-cafe thead .tablesorter-headerAsc {
50 + background-image: url(data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAQBAMAAADQT4M0AAAAIVBMVEUAAADu7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u5meJAOAAAACnRSTlMAMwsqXt+gIBUGxGoDMAAAAFlJREFUCNctzC0SQAAUReEzGNQ3AlHRiSRZFCVZYgeswRL8hLdK7834wj3tAlGP6y7fYHpKS6w6WwbVG0I1NZVnZPG8/DYxOYlnhUYkA06R1s9ESsxR4NIdPhkPFDFYuEnMAAAAAElFTkSuQmCC);
51 + }
52 + .tablesorter-cafe thead .headerSortDown,
53 + .tablesorter-cafe thead .tablesorter-headerSortDown,
54 + .tablesorter-cafe thead .tablesorter-headerDesc {
55 + background-image: url(data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAQBAMAAADQT4M0AAAALVBMVEUAAADu7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7i0NViAAAADnRSTlMAMiweCQITTvDctZZqaTlM310AAABcSURBVAjXY2BgYEtgAAFHERDJqigUAKSYBQUNgFSioKAYAwOLIBA4MASBKFUGQxAlzAAF+94BwWuGKBC1lIFl3rt3Lx0YGCzevWsGSjK9e6cAUlT3HKyW9wADAwDRrBiDy6bKzwAAAABJRU5ErkJggg==);
56 + }
57 + .tablesorter-cafe thead .sorter-false {
58 + background-image: none;
59 + cursor: default;
60 + padding: 4px;
61 + }
62 +
63 + /* tbody */
64 + .tablesorter-cafe td {
65 + background-color: #fff;
66 + padding: 1px 4px;
67 + vertical-align: top;
68 + border-style: solid;
69 + border-color: #666;
70 + border-collapse: collapse;
71 + border-width: 0px 1px;
72 +
73 + }
74 +
75 + /* hovered row colors */
76 + .tablesorter-cafe tbody > tr:hover > td,
77 + .tablesorter-cafe tbody > tr.even:hover > td,
78 + .tablesorter-cafe tbody > tr.odd:hover > td {
79 + background: #bbb;
80 + color: #000;
81 + }
82 +
83 + /* table processing indicator */
84 + .tablesorter-cafe .tablesorter-processing {
85 + background-position: center center !important;
86 + background-repeat: no-repeat !important;
87 + /* background-image: url(../addons/pager/icons/loading.gif) !important; */
88 + background-image: url(data:image/gif;base64,R0lGODlhFAAUAKEAAO7u7lpaWgAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQBCgACACwAAAAAFAAUAAACQZRvoIDtu1wLQUAlqKTVxqwhXIiBnDg6Y4eyx4lKW5XK7wrLeK3vbq8J2W4T4e1nMhpWrZCTt3xKZ8kgsggdJmUFACH5BAEKAAIALAcAAAALAAcAAAIUVB6ii7jajgCAuUmtovxtXnmdUAAAIfkEAQoAAgAsDQACAAcACwAAAhRUIpmHy/3gUVQAQO9NetuugCFWAAAh+QQBCgACACwNAAcABwALAAACE5QVcZjKbVo6ck2AF95m5/6BSwEAIfkEAQoAAgAsBwANAAsABwAAAhOUH3kr6QaAcSrGWe1VQl+mMUIBACH5BAEKAAIALAIADQALAAcAAAIUlICmh7ncTAgqijkruDiv7n2YUAAAIfkEAQoAAgAsAAAHAAcACwAAAhQUIGmHyedehIoqFXLKfPOAaZdWAAAh+QQFCgACACwAAAIABwALAAACFJQFcJiXb15zLYRl7cla8OtlGGgUADs=) !important;
89 + }
90 +
91 + /* pager */
92 + .tablesorter-cafe .tablesorter-pager button {
93 + background-color: #444;
94 + color: #eee;
95 + border: #555 1px solid;
96 + cursor: pointer;
97 + }
98 + .tablesorter-cafe .tablesorter-pager button:hover {
99 + background-color: #555;
100 + }
101 +
102 + /* Zebra Widget - row alternating colors */
103 + .tablesorter-cafe tr.odd td {
104 + background-color: #eee;
105 + }
106 + .tablesorter-cafe tr.even td {
107 + background-color: #fff;
108 + }
109 +
110 + /* Column Widget - column sort colors */
111 + .tablesorter-cafe tr.odd td.primary {
112 + background-color: #bfbfbf;
113 + }
114 + .tablesorter-cafe td.primary,
115 + .tablesorter-cafe tr.even td.primary {
116 + background-color: #d9d9d9;
117 + }
118 + .tablesorter-cafe tr.odd td.secondary {
119 + background-color: #d9d9d9;
120 + }
121 + .tablesorter-cafe td.secondary,
122 + .tablesorter-cafe tr.even td.secondary {
123 + background-color: #e6e6e6;
124 + }
125 + .tablesorter-cafe tr.odd td.tertiary {
126 + background-color: #e6e6e6;
127 + }
128 + .tablesorter-cafe td.tertiary,
129 + .tablesorter-cafe tr.even td.tertiary {
130 + background-color: #f2f2f2;
131 + }
132 +
133 + /* filter widget */
134 + .tablesorter-cafe .tablesorter-filter-row td {
135 + background: #eee;
136 + line-height: normal;
137 + text-align: center; /* center the input */
138 + -webkit-transition: line-height 0.1s ease;
139 + -moz-transition: line-height 0.1s ease;
140 + -o-transition: line-height 0.1s ease;
141 + transition: line-height 0.1s ease;
142 + }
143 + /* optional disabled input styling */
144 + .tablesorter-cafe .tablesorter-filter-row .disabled {
145 + opacity: 0.5;
146 + filter: alpha(opacity=50);
147 + cursor: not-allowed;
148 + }
149 + /* hidden filter row */
150 + .tablesorter-cafe .tablesorter-filter-row.hideme td {
151 + /*** *********************************************** ***/
152 + /*** change this padding to modify the thickness ***/
153 + /*** of the closed filter row (height = padding x 2) ***/
154 + padding: 2px;
155 + /*** *********************************************** ***/
156 + margin: 0;
157 + line-height: 0;
158 + cursor: pointer;
159 + }
160 + .tablesorter-cafe .tablesorter-filter-row.hideme .tablesorter-filter {
161 + height: 1px;
162 + min-height: 0;
163 + border: 0;
164 + padding: 0;
165 + margin: 0;
166 + /* don't use visibility: hidden because it disables tabbing */
167 + opacity: 0;
168 + filter: alpha(opacity=0);
169 + }
170 + /* filters */
171 + .tablesorter-cafe .tablesorter-filter {
172 + width: 95%;
173 + height: auto;
174 + margin: 4px;
175 + padding: 4px;
176 + background-color: #fff;
177 + border: 1px solid #bbb;
178 + color: #333;
179 + -webkit-box-sizing: border-box;
180 + -moz-box-sizing: border-box;
181 + box-sizing: border-box;
182 + -webkit-transition: height 0.1s ease;
183 + -moz-transition: height 0.1s ease;
184 + -o-transition: height 0.1s ease;
185 + transition: height 0.1s ease;
186 + }
187 + /* rows hidden by filtering (needed for child rows) */
188 + .tablesorter .filtered {
189 + display: none;
190 + }
191 +
192 + /* ajax error row */
193 + .tablesorter .tablesorter-errorRow td {
194 + text-align: center;
195 + cursor: pointer;
196 + background-color: #e6bf99;
197 + }
@@ -0,0 +1,218
1 + class ReportController < ApplicationController
2 +
3 + before_filter :admin_authorization, only: [:login_stat,:submission_stat, :stuck]
4 + before_filter(only: [:problem_hof]) { |c|
5 + return false unless authenticate
6 +
7 + if GraderConfiguration["right.user_view_submission"]
8 + return true;
9 + end
10 +
11 + admin_authorization
12 + }
13 +
14 + def login_stat
15 + @logins = Array.new
16 +
17 + date_and_time = '%Y-%m-%d %H:%M'
18 + begin
19 + md = params[:since_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
20 + @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)
21 + rescue
22 + @since_time = DateTime.new(1000,1,1)
23 + end
24 + begin
25 + md = params[:until_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
26 + @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)
27 + rescue
28 + @until_time = DateTime.new(3000,1,1)
29 + end
30 +
31 + User.all.each do |user|
32 + @logins << { id: user.id,
33 + login: user.login,
34 + full_name: user.full_name,
35 + count: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
36 + user.id,@since_time,@until_time)
37 + .count(:id),
38 + min: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
39 + user.id,@since_time,@until_time)
40 + .minimum(:created_at),
41 + max: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
42 + user.id,@since_time,@until_time)
43 + .maximum(:created_at),
44 + ip: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
45 + user.id,@since_time,@until_time)
46 + .select(:ip_address).uniq
47 +
48 + }
49 + end
50 + end
51 +
52 + def submission_stat
53 +
54 + date_and_time = '%Y-%m-%d %H:%M'
55 + begin
56 + @since_time = DateTime.strptime(params[:since_datetime],date_and_time)
57 + rescue
58 + @since_time = DateTime.new(1000,1,1)
59 + end
60 + begin
61 + @until_time = DateTime.strptime(params[:until_datetime],date_and_time)
62 + rescue
63 + @until_time = DateTime.new(3000,1,1)
64 + end
65 +
66 + @submissions = {}
67 +
68 + User.find_each do |user|
69 + @submissions[user.id] = { login: user.login, full_name: user.full_name, count: 0, sub: { } }
70 + end
71 +
72 + Submission.where("submitted_at >= ? AND submitted_at <= ?",@since_time,@until_time).find_each do |s|
73 + if @submissions[s.user_id]
74 + if not @submissions[s.user_id][:sub].has_key?(s.problem_id)
75 + a = nil
76 + begin
77 + a = Problem.find(s.problem_id)
78 + rescue
79 + a = nil
80 + end
81 + @submissions[s.user_id][:sub][s.problem_id] =
82 + { prob_name: (a ? a.full_name : '(NULL)'),
83 + sub_ids: [s.id] }
84 + else
85 + @submissions[s.user_id][:sub][s.problem_id][:sub_ids] << s.id
86 + end
87 + @submissions[s.user_id][:count] += 1
88 + end
89 + end
90 + end
91 +
92 + def problem_hof
93 + # gen problem list
94 + @user = User.find(session[:user_id])
95 + @problems = @user.available_problems
96 +
97 + # get selected problems or the default
98 + if params[:id]
99 + begin
100 + @problem = Problem.available.find(params[:id])
101 + rescue
102 + redirect_to action: :problem_hof
103 + flash[:notice] = 'Error: submissions for that problem are not viewable.'
104 + return
105 + end
106 + end
107 +
108 + return unless @problem
109 +
110 + @by_lang = {} #aggregrate by language
111 +
112 + range =65
113 + @histogram = { data: Array.new(range,0), summary: {} }
114 + @summary = {count: 0, solve: 0, attempt: 0}
115 + user = Hash.new(0)
116 + Submission.where(problem_id: @problem.id).find_each do |sub|
117 + #histogram
118 + d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60
119 + @histogram[:data][d.to_i] += 1 if d < range
120 +
121 + next unless sub.points
122 + @summary[:count] += 1
123 + user[sub.user_id] = [user[sub.user_id], (sub.points >= @problem.full_score) ? 1 : 0].max
124 +
125 + lang = Language.find_by_id(sub.language_id)
126 + next unless lang
127 + next unless sub.points >= @problem.full_score
128 +
129 + #initialize
130 + unless @by_lang.has_key?(lang.pretty_name)
131 + @by_lang[lang.pretty_name] = {
132 + runtime: { avail: false, value: 2**30-1 },
133 + memory: { avail: false, value: 2**30-1 },
134 + length: { avail: false, value: 2**30-1 },
135 + first: { avail: false, value: DateTime.new(3000,1,1) }
136 + }
137 + end
138 +
139 + if sub.max_runtime and sub.max_runtime < @by_lang[lang.pretty_name][:runtime][:value]
140 + @by_lang[lang.pretty_name][:runtime] = { avail: true, user_id: sub.user_id, value: sub.max_runtime, sub_id: sub.id }
141 + end
142 +
143 + if sub.peak_memory and sub.peak_memory < @by_lang[lang.pretty_name][:memory][:value]
144 + @by_lang[lang.pretty_name][:memory] = { avail: true, user_id: sub.user_id, value: sub.peak_memory, sub_id: sub.id }
145 + end
146 +
147 + if sub.submitted_at and sub.submitted_at < @by_lang[lang.pretty_name][:first][:value] and
148 + !sub.user.admin?
149 + @by_lang[lang.pretty_name][:first] = { avail: true, user_id: sub.user_id, value: sub.submitted_at, sub_id: sub.id }
150 + end
151 +
152 + if @by_lang[lang.pretty_name][:length][:value] > sub.effective_code_length
153 + @by_lang[lang.pretty_name][:length] = { avail: true, user_id: sub.user_id, value: sub.effective_code_length, sub_id: sub.id }
154 + end
155 + end
156 +
157 + #process user_id
158 + @by_lang.each do |lang,prop|
159 + prop.each do |k,v|
160 + v[:user] = User.exists?(v[:user_id]) ? User.find(v[:user_id]).full_name : "(NULL)"
161 + end
162 + end
163 +
164 + #sum into best
165 + if @by_lang and @by_lang.first
166 + @best = @by_lang.first[1].clone
167 + @by_lang.each do |lang,prop|
168 + if @best[:runtime][:value] >= prop[:runtime][:value]
169 + @best[:runtime] = prop[:runtime]
170 + @best[:runtime][:lang] = lang
171 + end
172 + if @best[:memory][:value] >= prop[:memory][:value]
173 + @best[:memory] = prop[:memory]
174 + @best[:memory][:lang] = lang
175 + end
176 + if @best[:length][:value] >= prop[:length][:value]
177 + @best[:length] = prop[:length]
178 + @best[:length][:lang] = lang
179 + end
180 + if @best[:first][:value] >= prop[:first][:value]
181 + @best[:first] = prop[:first]
182 + @best[:first][:lang] = lang
183 + end
184 + end
185 + end
186 +
187 + @histogram[:summary][:max] = [@histogram[:data].max,1].max
188 + @summary[:attempt] = user.count
189 + user.each_value { |v| @summary[:solve] += 1 if v == 1 }
190 + end
191 +
192 + def stuck #report struggling user,problem
193 + # init
194 + user,problem = nil
195 + solve = true
196 + tries = 0
197 + @struggle = Array.new
198 + record = {}
199 + Submission.includes(:problem,:user).order(:problem_id,:user_id).find_each do |sub|
200 + next unless sub.problem and sub.user
201 + if user != sub.user_id or problem != sub.problem_id
202 + @struggle << { user: record[:user], problem: record[:problem], tries: tries } unless solve
203 + record = {user: sub.user, problem: sub.problem}
204 + user,problem = sub.user_id, sub.problem_id
205 + solve = false
206 + tries = 0
207 + end
208 + if sub.points >= sub.problem.full_score
209 + solve = true
210 + else
211 + tries += 1
212 + end
213 + end
214 + @struggle.sort!{|a,b| b[:tries] <=> a[:tries] }
215 + @struggle = @struggle[0..50]
216 + end
217 +
218 + end
@@ -0,0 +1,2
1 + module ReportHelper
2 + end
@@ -0,0 +1,3
1 + class Login < ActiveRecord::Base
2 + attr_accessible :ip_address, :logged_in_at, :user_id
3 + end
@@ -0,0 +1,44
1 + - param = {} unless param
2 + - graph_height = param[:graph_height] || 100
3 + - bar_width = param[:bar_width] || 14
4 + - graph_width = (bar_width * histogram[:data].count) + 20
5 + :css
6 + .hist_bar {
7 + width: #{bar_width-1}px;
8 + position: absolute;
9 + background-color: lightblue;
10 + }
11 + .hist_fill {
12 + width: #{bar_width-1}px;
13 + position: absolute;
14 + background-color: #eee;
15 + }
16 + .hist_text {
17 + position: absolute;
18 + font-size:5px;
19 + }
20 +
21 + %div{style: "position: relative; width: #{graph_width}px; height: 125px; background-color:#fff;" }
22 + //draw background
23 + - histogram[:data].each_index do |i|
24 + - height = histogram[:data][i] * graph_height / histogram[:summary][:max]
25 + - top = graph_height - height
26 + - left = graph_width - (i+1)*bar_width
27 + %div.hist_fill{style: "top: 0px; height: #{graph_height - height}px; left: #{left}px;" }
28 + // draw horizontal line
29 + - line = 3
30 + - line.times do |i|
31 + - top = graph_height - graph_height * (i+0.5)/ line
32 + %div{style: "position:absolute;width: #{graph_width-21}px;height: 1px;left: 20px;top:#{top}px;background-color: #333;"}
33 + %div.hist_text{style: "position:absolute;left: 0px;top:#{top-6}px"}
34 + =((i+0.5) * histogram[:summary][:max] / line).to_i
35 + // draw the actual bar and text
36 + - @histogram[:data].each_index do |i|
37 + - height = histogram[:data][i] * graph_height / histogram[:summary][:max]
38 + - top = graph_height - height
39 + - left = graph_width - (i+1)*bar_width
40 + %div.hist_bar{style: "top: #{top}px; height: #{height}px; left: #{left}px; dae: #{histogram[:data][i]}" }
41 + - if i % 7 == 1
42 + %div.hist_text{style: "top:#{graph_height + 5}px;left: #{left}px;"} #{(Time.zone.today - i.day).strftime('%-d')}
43 + - if (Time.now.in_time_zone - i.day).day == 15
44 + %div.hist_text{style: "top:#{graph_height + 15}px;left: #{left}px;"} #{(Time.zone.today - i.day).strftime('%b')}
@@ -0,0 +1,51
1 + :css
2 + .fix-width {
3 + font-family: "Consolas, Monaco, Droid Sans Mono,Mono, Monospace,Courier"
4 + }
5 +
6 + %h1 Problem stat: #{@problem.name}
7 + %h2 Overview
8 +
9 +
10 + %table.info
11 + %thead
12 + %tr.info-head
13 + %th Stat
14 + %th Value
15 + %tbody
16 + %tr{class: cycle('info-even','info-odd')}
17 + %td Submissions
18 + %td= @submissions.count
19 + %tr{class: cycle('info-even','info-odd')}
20 + %td Solved/Attempted User
21 + %td #{@summary[:solve]}/#{@summary[:attempt]} (#{(@summary[:solve]*100.0/@summary[:attempt]).round(1)}%)
22 +
23 + %h2 Submissions Count
24 + = render partial: 'application/bar_graph', locals: { histogram: @histogram }
25 +
26 + %h2 Submissions
27 + - if @submissions and @submissions.count > 0
28 + %table.info#main_table
29 + %thead
30 + %tr.info-head
31 + %th ID
32 + %th Login
33 + %th Name
34 + %th Submitted_at
35 + %th Points
36 + %th comment
37 + %tbody
38 + - row_odd,curr = true,''
39 + - @submissions.each do |sub|
40 + - next unless sub.user
41 + - row_odd,curr = !row_odd, sub.user if curr != sub.user
42 + %tr{class: row_odd ? "info-odd" : "info-even"}
43 + %td= link_to sub.id, controller: 'graders', action: 'submission', id: sub.id
44 + %td= link_to sub.user.login, controller: :users, action: :profile, id: sub.user.id
45 + %td= sub.user.full_name
46 + %td= time_ago_in_words(sub.submitted_at) + " ago"
47 + %td= sub.points
48 + %td.fix-width= sub.grader_comment
49 + - else
50 + No submission
51 +
@@ -0,0 +1,8
1 + %h2 Paid in Full
2 + User with highest number of problem solved
3 +
4 + %h2 Polymaths
5 + User with highest number of problems each solved by more than 1 languages.
6 +
7 + %h2 Icebreakers
8 + If you solve the problem before 95% of your friends, you are an icebreaker.
@@ -0,0 +1,23
1 +
2 + = form_tag({session: :url }) do
3 + .submitbox
4 + %table
5 + %tr
6 + %td{colspan: 6, style: 'font-weight: bold'}= title
7 + %tr
8 + %td{style: 'width: 120px; font-weight: bold'}= param_text
9 + %td{align: 'right'} since:
10 + %td= text_field_tag 'since_datetime'
11 + %tr
12 + %td
13 + %td{align: 'right'} until:
14 + %td= text_field_tag 'until_datetime'
15 + %tr
16 + %td
17 + %td
18 + %td Blank mean no condition
19 + %tr
20 + %td
21 + %td
22 + %td= submit_tag 'query'
23 +
@@ -0,0 +1,7
1 +
2 + .task-menu
3 + Reports
4 + %br/
5 + = link_to '[Hall of Fame]', :action => 'problem_hof'
6 + = link_to '[Struggle]', :action => 'stuck'
7 + = link_to '[Login]', :action => 'login_stat'
@@ -0,0 +1,127
1 + - content_for :header do
2 + = javascript_include_tag 'local_jquery'
3 +
4 + :javascript
5 + $(document).ready( function() {
6 + $("#mem_remark").hover( function() {
7 + $("#mem_remark_box").show();
8 + }, function() {
9 + $("#mem_remark_box").hide();
10 + });
11 + });
12 + :css
13 + .hof_user { color: orangered; font-style: italic; }
14 + .hof_language { color: green; font-style: italic; }
15 + .hof_value { color: deeppink;font-style: italic; }
16 + .info_param { font-weight: bold;text-align: right; }
17 + .tooltip {
18 + font-family: Verdana,sans-serif;
19 + font-weight: normal;
20 + text-align: left;
21 + font-size: 1.0em;
22 + color: black;
23 + line-height: 1.1;
24 + display: none;
25 + min-width: 20em;
26 + position: absolute;
27 + left: 25px;
28 + bottom: 5px;
29 + border: 1px solid;
30 + padding: 5px;
31 + background-color: #FFF;
32 + word-wrap: break-word;
33 + z-index: 9999;
34 + overflow: auto;
35 + }
36 +
37 + %h1 (#{Problem.find(params[:id]).name}) #{Problem.find(params[:id]).full_name}
38 +
39 + %h2 Problem Stat
40 + %table.info
41 + %thead
42 + %tr.info-head
43 + %th Stat
44 + %th Value
45 + %tbody
46 + %tr{class: cycle('info-even','info-odd')}
47 + %td.info_param Submissions
48 + %td= @summary[:count]
49 + %tr{class: cycle('info-even','info-odd')}
50 + %td.info_param Solved/Attempted User
51 + %td #{@summary[:solve]}/#{@summary[:attempt]} (#{(@summary[:solve]*100.0/@summary[:attempt]).round(1)}%)
52 + - if @best
53 + %tr{class: cycle('info-even','info-odd')}
54 + %td.info_param Best Runtime
55 + %td
56 + by #{link_to @best[:runtime][:user], controller:'users', action:'profile', id:@best[:memory][:user_id]}
57 + using <span class="hof_language">#{@best[:runtime][:lang]}</span>
58 + with <span class="hof_value">#{@best[:runtime][:value] * 1000} milliseconds</span>
59 + at submission
60 + = link_to("#" + @best[:runtime][:sub_id].to_s, controller: 'graders', action: 'submission', id:@best[:runtime][:sub_id])
61 +
62 + %tr{class: cycle('info-even','info-odd')}
63 + %td.info_param
64 + Best Memory Usage
65 + %sup{ id: "mem_remark", style: "position:relative; color: blue;"}
66 + [?]
67 + %span.tooltip#mem_remark_box
68 + This counts only for submission with 100% score.
69 + Right now, java is excluded from memory usage competition. (Because it always uses 2GB memory...)
70 + %td
71 + by #{link_to @best[:memory][:user], controller:'users', action:'profile', id:@best[:memory][:user_id]}
72 + using <span class="hof_language">#{@best[:memory][:lang]}</span>
73 + with <span class="hof_value">#{number_with_delimiter(@best[:memory][:value])} kbytes </span>
74 + at submission
75 + = link_to("#" + @best[:memory][:sub_id].to_s, controller: 'graders' , action: 'submission', id:@best[:memory][:sub_id])
76 +
77 + %tr{class: cycle('info-even','info-odd')}
78 + %td.info_param Shortest Code
79 + %td
80 + by #{link_to @best[:length][:user], controller:'users', action:'profile', id:@best[:length][:user_id]}
81 + using <span class="hof_language">#{@best[:length][:lang]}</span>
82 + with <span class="hof_value">#{@best[:length][:value]} bytes</span>
83 + at submission
84 + = link_to("#" + @best[:length][:sub_id].to_s, controller: 'graders' , action: 'submission', id: @best[:length][:sub_id])
85 +
86 + %tr{class: cycle('info-even','info-odd')}
87 + %td.info_param First solver
88 + %td
89 + #{link_to @best[:first][:user], controller:'users', action:'profile', id:@best[:first][:user_id]} is the first solver
90 + using <span class="hof_language">#{@best[:first][:lang]}</span>
91 + on <span class="hof_value">#{@best[:first][:value]}</span>
92 + at submission
93 + = link_to("#" + @best[:first][:sub_id].to_s, controller: 'graders' , action: 'submission', id: @best[:first][:sub_id])
94 +
95 + - if @best
96 + %h2 By language
97 +
98 + %table.info
99 + %thead
100 + %tr.info-head
101 + %th Language
102 + %th Best runtime (ms)
103 + %th Best memory (kbytes)
104 + %th Shortest Code (bytes)
105 + %th First solver
106 + %tbody
107 + - @by_lang.each do |lang,value|
108 + %tr{class: cycle('info-even','info-odd')}
109 + %td= lang
110 + %td
111 + = link_to value[:runtime][:user], controller: 'users', action: 'profile', id: value[:runtime][:user_id]
112 + = "(#{(value[:runtime][:value] * 1000).to_i} @"
113 + = "#{link_to("#" + value[:runtime][:sub_id].to_s, controller: 'graders' , action: 'submission', id: value[:runtime][:sub_id])} )".html_safe
114 + %td
115 + = link_to value[:memory][:user], controller: 'users', action: 'profile', id: value[:memory][:user_id]
116 + = "(#{number_with_delimiter(value[:memory][:value])} @"
117 + = "#{link_to("#" + value[:memory][:sub_id].to_s, controller: 'graders' , action: 'submission', id: value[:memory][:sub_id])} )".html_safe
118 + %td
119 + = link_to value[:length][:user], controller: 'users', action: 'profile', id: value[:length][:user_id]
120 + = "(#{value[:length][:value]} @"
121 + = "#{link_to("#" + value[:length][:sub_id].to_s, controller: 'graders' , action: 'submission', id: value[:length][:sub_id])} )".html_safe
122 + %td
123 + - if value[:first][:user] != '(NULL)' #TODO: i know... this is wrong...
124 + = link_to value[:first][:user], controller: 'users', action: 'profile', id: value[:first][:user_id]
125 + = "(#{value[:first][:value]} @"
126 + = "#{link_to("#" + value[:first][:sub_id].to_s, controller: 'graders' , action: 'submission', id: value[:first][:sub_id])} )".html_safe
127 +
@@ -0,0 +1,36
1 + - content_for :header do
2 + = stylesheet_link_tag 'tablesorter-theme.cafe'
3 + = javascript_include_tag 'local_jquery'
4 +
5 + %script{:type=>"text/javascript"}
6 + $(function () {
7 + $('#since_datetime').datetimepicker({ showButtonPanel: true, dateFormat: "yy-mm-dd", controlType: "slider"} );
8 + $('#until_datetime').datetimepicker({ showButtonPanel: true, dateFormat: "yy-mm-dd", controlType: "slider"} );
9 + $('#my_table').tablesorter({widthFixed: true, widgets: ['zebra']});
10 + });
11 +
12 + %h1 Login status
13 +
14 + =render partial: 'report_menu'
15 + =render partial: 'date_range', locals: {param_text: 'Login date range:', title: 'Query login stat in the range' }
16 +
17 + %table.tablesorter-cafe#my_table
18 + %thead
19 + %tr
20 + %th login
21 + %th full name
22 + %th login count
23 + %th earliest
24 + %th latest
25 + %th IP
26 + %tbody
27 + - @logins.each do |l|
28 + %tr{class: cycle('info-even','info-odd')}
29 + %td= link_to l[:login], controller: 'users', action: 'profile', id: l[:id]
30 + %td= l[:full_name]
31 + %td= l[:count]
32 + %td= l[:min] ? l[:min].in_time_zone.strftime('%Y-%m-%d %H:%M') : ''
33 + %td= l[:max] ? "#{l[:max].in_time_zone.strftime('%Y-%m-%d %H:%M.%S')} (#{time_ago_in_words(l[:max].in_time_zone)} ago)" : ''
34 + %td
35 + - l[:ip].each do |ip|
36 + #{ip.ip_address} <br/>
@@ -0,0 +1,23
1 +
2 + /- if params[:id]
3 + / %h1 Tasks Hall of Fame
4 + / = link_to('[back to All-Time Hall of Fame]', action: 'problem_hof', id: nil )
5 + /- else
6 + / %h1 All-Time Hall of Fame
7 +
8 +
9 + %h1 Hall of Fame
10 + .task-menu
11 + Tasks
12 + %br/
13 + - @problems.each do |prob|
14 + = link_to( "[#{prob.name}]", {id: prob.id})
15 +
16 + - unless params[:id]
17 + /=render partial: 'all_time_hof'
18 + Please select a problem.
19 + - else
20 + =render partial: 'task_hof'
21 + %h2 Submission History
22 + =render partial: 'application/bar_graph', locals: { histogram: @histogram }
23 +
@@ -0,0 +1,17
1 + %table.info
2 + %thead
3 + %tr.info-head
4 + %th Problem
5 + %th User
6 + %th tries
7 + %tbody
8 + - @struggle.each do |s|
9 + %tr
10 + %td
11 + = link_to "(#{s[:problem].name})", controller: :problems, action: :stat, id: s[:problem]
12 + = s[:problem].full_name
13 + %td
14 + = link_to "(#{s[:user].login})", controller: :users, action: :profile, id: s[:user]
15 + = s[:user].full_name
16 + %td
17 + = s[:tries]
@@ -0,0 +1,37
1 + - content_for :header do
2 + = javascript_include_tag 'local_jquery'
3 +
4 + %script{:type=>"text/javascript"}
5 + $(function () {
6 + $('#since_datetime').datetimepicker({ showButtonPanel: true, dateFormat: "yy-mm-dd", controlType: "slider"} );
7 + $('#until_datetime').datetimepicker({ showButtonPanel: true, dateFormat: "yy-mm-dd", controlType: "slider"} );
8 + });
9 +
10 + %h1 Login status
11 +
12 + =render partial: 'report_menu'
13 + =render partial: 'date_range', locals: {param_text: 'Submission date range:', title: 'Query submission stat in the range' }
14 +
15 + %table.info
16 + %thead
17 + %tr.info-head
18 + %th login
19 + %th full name
20 + %th total submissions
21 + %th submissions
22 + %tbody
23 + - @submissions.each do |user_id,data|
24 + %tr{class: cycle('info-even','info-odd')}
25 + %td= data[:login]
26 + %td= data[:full_name]
27 + %td= data[:count]
28 + %td
29 + - data[:sub].each do |prob_id,sub_data|
30 + = "#{sub_data[:prob_name]}: ["
31 + - st = []
32 + - sub_data[:sub_ids].each do |id|
33 + - st << link_to(id, controller: 'graders' , action: 'submission', id: id)
34 + = raw st.join ', '
35 + = ']'
36 + %br/
37 +
@@ -0,0 +1,11
1 + %h1 Editing user
2 +
3 + = form_tag :action => 'update', :id => @user do
4 + = error_messages_for 'user'
5 + = render partial: "form"
6 + = submit_tag "Edit"
7 +
8 +
9 + = link_to 'Show', :action => 'show', :id => @user
10 + |
11 + = link_to 'Back', :action => 'list'
@@ -0,0 +1,59
1 + - content_for :header do
2 + = javascript_include_tag 'local_jquery'
3 + = stylesheet_link_tag 'tablesorter-theme.cafe'
4 +
5 + %script{:type=>"text/javascript"}
6 + $(function () {
7 + $('#since_datetime').datetimepicker({ showButtonPanel: true, dateFormat: "yy-mm-dd", controlType: "slider"} );
8 + $('#until_datetime').datetimepicker({ showButtonPanel: true, dateFormat: "yy-mm-dd", controlType: "slider"} );
9 + $('#my_table').tablesorter({widgets: ['zebra']});
10 + });
11 +
12 + %h1 User grading results
13 + %h2= params[:action] == 'user_stat' ? "Show scores from latest submission" : "Show max scores in submission range"
14 +
15 +
16 + - if @problem and @problem.errors
17 + =error_messages_for 'problem'
18 +
19 + = render partial: 'submission_range'
20 +
21 + - if params[:action] == 'user_stat'
22 + %h3 Latest score
23 + = link_to '[download csv with all problems]', controller: :user_admin, action: :user_stat, commit: 'download csv'
24 + - else
25 + %h3 Max score
26 + = link_to '[Show only latest submissions]', controller: :user_admin, action: :user_stat
27 + = link_to '[download csv with all problems]', controller: :user_admin, action: :user_stat_max, commit: 'download csv'
28 +
29 + %table.tablesorter-cafe#my_table
30 + %thead
31 + %tr
32 + %th User
33 + %th Name
34 + %th Activated?
35 + %th Logged in
36 + %th Contest(s)
37 + %th Remark
38 + - @problems.each do |p|
39 + %th= p.name
40 + %th Total
41 + %th Passed
42 + %tbody
43 + - @scorearray.each do |sc|
44 + %tr{class: cycle('info-even','info-odd')}
45 + - total,num_passed = 0,0
46 + - sc.each_index do |i|
47 + - if i == 0
48 + %td= link_to sc[i].login, controller: 'users', action: 'profile', id: sc[i]
49 + %td= sc[i].full_name
50 + %td= sc[i].activated
51 + %td= sc[i].try(:contest_stat).try(:started_at)!=nil ? 'yes' : 'no'
52 + %td= sc[i].contests.collect {|c| c.name}.join(', ')
53 + %td= sc[i].remark
54 + - else
55 + %td= sc[i][0]
56 + - total += sc[i][0]
57 + - num_passed += 1 if sc[i][1]
58 + %td= total
59 + %td= num_passed
@@ -0,0 +1,66
1 + - content_for :header do
2 + = javascript_include_tag 'local_jquery'
3 +
4 + :javascript
5 + $(function () {
6 + $('#submission_table').tablesorter({widgets: ['zebra']});
7 + });
8 +
9 + :css
10 + .fix-width {
11 + font-family: Droid Sans Mono,Consolas, monospace, mono, Courier New, Courier;
12 + }
13 +
14 + %h1= @user.full_name
15 +
16 + <b>Login:</b> #{@user.login} <br/>
17 + <b>Full name:</b> #{@user.full_name} <br />
18 +
19 +
20 + %h2 Problem Stat
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.tablesorter-cafe#submission_table
40 + %thead
41 + %tr
42 + %th ID
43 + %th Problem code
44 + %th Problem full name
45 + %th Language
46 + %th Submitted at
47 + %th Result
48 + %th Score
49 + - if session[:admin]
50 + %th IP
51 + %tbody
52 + - @submission.each do |s|
53 + - next unless s.problem
54 + %tr
55 + %td= link_to "#{s.id}", controller: "graders", action: "submission", id: s.id
56 + %td= link_to s.problem.name, controller: "problems", action: "stat", id: s.problem
57 + %td= s.problem.full_name
58 + %td= s.language.pretty_name
59 + %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
61 + %td= (s.points*100)/s.problem.full_score
62 + - if session[:admin]
63 + %td= s.ip_address
64 +
65 +
66 +
@@ -0,0 +1,7
1 + class AddMoreDetailToSubmission < ActiveRecord::Migration
2 + def change
3 + add_column :submissions, :max_runtime, :float
4 + add_column :submissions, :peak_memory, :integer
5 + add_column :submissions, :effective_code_length, :integer
6 + end
7 + end
@@ -0,0 +1,10
1 + class CreateLogins < ActiveRecord::Migration
2 + def change
3 + create_table :logins do |t|
4 + t.string :user_id
5 + t.string :ip_address
6 +
7 + t.timestamps
8 + end
9 + end
10 + end
@@ -0,0 +1,5
1 + class AddIpToSubmissions < ActiveRecord::Migration
2 + def change
3 + add_column :submissions, :ip_address, :string
4 + end
5 + end
@@ -0,0 +1,6
1 + class AddMoreToUsers < ActiveRecord::Migration
2 + def change
3 + add_column :users, :enabled, :boolean, default: 1
4 + add_column :users, :remark, :string
5 + end
6 + end
@@ -0,0 +1,5
1 + require 'spec_helper'
2 +
3 + describe Login do
4 + pending "add some examples to (or delete) #{__FILE__}"
5 + end
@@ -1,19 +1,26
1 1 # See http://help.github.com/ignore-files/ for more about ignoring files.
2 2 #
3 3 # If you find yourself ignoring temporary files generated by your text editor
4 4 # or operating system, you probably want to add a global ignore instead:
5 5 # git config --global core.excludesfile ~/.gitignore_global
6 6
7 7 # Ignore bundler config
8 8 /.bundle
9 9
10 10 # Ignore the default SQLite database.
11 11 /db/*.sqlite3
12 12
13 13 # Ignore all logfiles and tempfiles.
14 14 /log/*.log
15 15 /tmp
16 16
17 17 *~
18 18
19 19 /vendor/plugins/rails_upgrade
20 +
21 + #ignore public assets???
22 + /public/assets
23 +
24 + #ignore .orig and .swp
25 + *.orig
26 + *.swp
@@ -1,50 +1,63
1 1 source 'https://rubygems.org'
2 2
3 3 gem 'rails', '3.2.21'
4 4
5 5 # Bundle edge Rails instead:
6 6 # gem 'rails', :git => 'git://github.com/rails/rails.git'
7 7
8 8 gem 'mysql2'
9 9
10 10 # Gems used only for assets and not required
11 11 # in production environments by default.
12 12 group :assets do
13 13 gem 'sass-rails', '~> 3.2.6'
14 14 gem 'coffee-rails', '~> 3.2.2'
15 15
16 16 # See https://github.com/sstephenson/execjs#readme for more supported runtimes
17 17 # gem 'therubyracer', :platforms => :ruby
18 18
19 19 gem 'uglifier'
20 20 end
21 21
22 22 gem 'prototype-rails'
23 23
24 24 # To use ActiveModel has_secure_password
25 25 # gem 'bcrypt-ruby', '~> 3.0.0'
26 26
27 27 # To use Jbuilder templates for JSON
28 28 # gem 'jbuilder'
29 29
30 30 # Use unicorn as the app server
31 31 # gem 'unicorn'
32 32
33 33 # Deploy with Capistrano
34 34 # gem 'capistrano'
35 35
36 36 # To use debugger
37 37 # gem 'debugger'
38 + #
39 +
40 + #in-place editor
41 + gem 'best_in_place', '~> 3.0.1'
42 +
43 + # jquery addition
44 + gem 'jquery-rails'
45 + gem 'jquery-ui-sass-rails'
46 + gem 'jquery-timepicker-addon-rails'
47 + gem 'jquery-tablesorter'
48 +
49 + #syntax highlighter
50 + gem 'rouge'
38 51
39 52 gem 'haml'
40 53 gem 'mail'
41 54 gem 'rdiscount'
42 55 gem 'test-unit'
43 56 gem 'will_paginate', '~> 3.0.7'
44 57 gem 'dynamic_form'
45 58 gem 'in_place_editing'
46 59 gem 'verification', :git => 'https://github.com/sikachu/verification.git'
47 60
48 61 group :test, :development do
49 62 gem 'rspec-rails', '~> 2.99.0'
50 63 end
@@ -1,151 +1,178
1 1 GIT
2 2 remote: https://github.com/sikachu/verification.git
3 3 revision: 76eaf51b13276ecae54bd9cd115832595d2ff56d
4 4 specs:
5 5 verification (1.0.3)
6 6 actionpack (>= 3.0.0, < 5.0)
7 7 activesupport (>= 3.0.0, < 5.0)
8 8
9 9 GEM
10 10 remote: https://rubygems.org/
11 11 specs:
12 12 actionmailer (3.2.21)
13 13 actionpack (= 3.2.21)
14 14 mail (~> 2.5.4)
15 15 actionpack (3.2.21)
16 16 activemodel (= 3.2.21)
17 17 activesupport (= 3.2.21)
18 18 builder (~> 3.0.0)
19 19 erubis (~> 2.7.0)
20 20 journey (~> 1.0.4)
21 21 rack (~> 1.4.5)
22 22 rack-cache (~> 1.2)
23 23 rack-test (~> 0.6.1)
24 24 sprockets (~> 2.2.1)
25 25 activemodel (3.2.21)
26 26 activesupport (= 3.2.21)
27 27 builder (~> 3.0.0)
28 28 activerecord (3.2.21)
29 29 activemodel (= 3.2.21)
30 30 activesupport (= 3.2.21)
31 31 arel (~> 3.0.2)
32 32 tzinfo (~> 0.3.29)
33 33 activeresource (3.2.21)
34 34 activemodel (= 3.2.21)
35 35 activesupport (= 3.2.21)
36 36 activesupport (3.2.21)
37 37 i18n (~> 0.6, >= 0.6.4)
38 38 multi_json (~> 1.0)
39 39 arel (3.0.3)
40 + best_in_place (3.0.2)
41 + actionpack (>= 3.2)
42 + railties (>= 3.2)
40 43 builder (3.0.4)
41 44 coffee-rails (3.2.2)
42 45 coffee-script (>= 2.2.0)
43 46 railties (~> 3.2.0)
44 47 coffee-script (2.3.0)
45 48 coffee-script-source
46 49 execjs
47 50 coffee-script-source (1.8.0)
48 51 diff-lcs (1.2.5)
49 52 dynamic_form (1.1.4)
50 53 erubis (2.7.0)
51 54 execjs (2.2.2)
52 55 haml (4.0.6)
53 56 tilt
54 57 hike (1.2.3)
55 58 i18n (0.7.0)
56 59 in_place_editing (1.2.0)
57 60 journey (1.0.4)
58 61 json (1.8.1)
59 62 mail (2.5.4)
63 + jquery-rails (3.1.1)
64 + railties (>= 3.0, < 5.0)
65 + thor (>= 0.14, < 2.0)
66 + jquery-tablesorter (1.12.7)
67 + railties (>= 3.1, < 5)
68 + jquery-timepicker-addon-rails (1.4.1)
69 + railties (>= 3.1)
70 + jquery-ui-rails (4.0.3)
71 + jquery-rails
72 + railties (>= 3.1.0)
73 + jquery-ui-sass-rails (4.0.3.0)
74 + jquery-rails
75 + jquery-ui-rails (= 4.0.3)
76 + railties (>= 3.1.0)
77 + json (1.8.1)
78 + mail (2.5.4)
60 79 mime-types (~> 1.16)
61 80 treetop (~> 1.4.8)
62 81 mime-types (1.25.1)
63 82 multi_json (1.10.1)
64 83 mysql2 (0.3.17)
65 84 polyglot (0.3.5)
66 85 power_assert (0.2.2)
67 86 prototype-rails (3.2.1)
68 87 rails (~> 3.2)
69 88 rack (1.4.5)
70 89 rack-cache (1.2)
71 90 rack (>= 0.4)
72 91 rack-ssl (1.3.4)
73 92 rack
74 93 rack-test (0.6.2)
75 94 rack (>= 1.0)
76 95 rails (3.2.21)
77 96 actionmailer (= 3.2.21)
78 97 actionpack (= 3.2.21)
79 98 activerecord (= 3.2.21)
80 99 activeresource (= 3.2.21)
81 100 activesupport (= 3.2.21)
82 101 bundler (~> 1.0)
83 102 railties (= 3.2.21)
84 103 railties (3.2.21)
85 104 actionpack (= 3.2.21)
86 105 activesupport (= 3.2.21)
87 106 rack-ssl (~> 1.3.2)
88 107 rake (>= 0.8.7)
89 108 rdoc (~> 3.4)
90 109 thor (>= 0.14.6, < 2.0)
91 110 rake (10.4.2)
92 111 rdiscount (2.1.7.1)
93 112 rdoc (3.12.2)
94 113 json (~> 1.4)
95 - rspec-collection_matchers (1.1.2)
114 + rouge (1.6.2)
115 + rspec-collection_matchers (1.0.0)
96 116 rspec-expectations (>= 2.99.0.beta1)
97 117 rspec-core (2.99.2)
98 118 rspec-expectations (2.99.2)
99 119 diff-lcs (>= 1.1.3, < 2.0)
100 120 rspec-mocks (2.99.2)
101 121 rspec-rails (2.99.0)
102 122 actionpack (>= 3.0)
103 123 activemodel (>= 3.0)
104 124 activesupport (>= 3.0)
105 125 railties (>= 3.0)
106 126 rspec-collection_matchers
107 127 rspec-core (~> 2.99.0)
108 128 rspec-expectations (~> 2.99.0)
109 129 rspec-mocks (~> 2.99.0)
110 130 sass (3.4.9)
111 131 sass-rails (3.2.6)
112 132 railties (~> 3.2.0)
113 133 sass (>= 3.1.10)
114 134 tilt (~> 1.3)
115 135 sprockets (2.2.3)
116 136 hike (~> 1.2)
117 137 multi_json (~> 1.0)
118 138 rack (~> 1.0)
119 139 tilt (~> 1.1, != 1.3.0)
120 140 test-unit (3.0.9)
121 141 power_assert
122 142 thor (0.19.1)
123 143 tilt (1.4.1)
124 144 treetop (1.4.15)
125 145 polyglot
126 146 polyglot (>= 0.3.1)
127 147 tzinfo (0.3.42)
128 148 uglifier (2.6.0)
129 149 execjs (>= 0.3.0)
130 150 json (>= 1.8.0)
131 151 will_paginate (3.0.7)
132 152
133 153 PLATFORMS
134 154 ruby
135 155
136 156 DEPENDENCIES
157 + best_in_place (~> 3.0.1)
137 158 coffee-rails (~> 3.2.2)
138 159 dynamic_form
139 160 haml
140 161 in_place_editing
162 + jquery-rails
163 + jquery-tablesorter
164 + jquery-timepicker-addon-rails
165 + jquery-ui-sass-rails
141 166 mail
142 167 mysql2
143 168 prototype-rails
144 169 rails (= 3.2.21)
145 170 rdiscount
146 - rspec-rails (~> 2.99.0)
171 + rouge
147 172 sass-rails (~> 3.2.6)
173 + rspec-rails (~> 2.0)
174 +
148 175 test-unit
149 176 uglifier
150 177 verification!
151 178 will_paginate (~> 3.0.7)
@@ -1,182 +1,10
1 - == Welcome to Rails
2 -
3 - Rails is a web-application and persistence framework that includes everything
4 - needed to create database-backed web-applications according to the
5 - Model-View-Control pattern of separation. This pattern splits the view (also
6 - called the presentation) into "dumb" templates that are primarily responsible
7 - for inserting pre-built data in between HTML tags. The model contains the
8 - "smart" domain objects (such as Account, Product, Person, Post) that holds all
9 - the business logic and knows how to persist themselves to a database. The
10 - controller handles the incoming requests (such as Save New Account, Update
11 - Product, Show Post) by manipulating the model and directing data to the view.
12 -
13 - In Rails, the model is handled by what's called an object-relational mapping
14 - layer entitled Active Record. This layer allows you to present the data from
15 - database rows as objects and embellish these data objects with business logic
16 - methods. You can read more about Active Record in
17 - link:files/vendor/rails/activerecord/README.html.
18 -
19 - The controller and view are handled by the Action Pack, which handles both
20 - layers by its two parts: Action View and Action Controller. These two layers
21 - are bundled in a single package due to their heavy interdependence. This is
22 - unlike the relationship between the Active Record and Action Pack that is much
23 - more separate. Each of these packages can be used independently outside of
24 - Rails. You can read more about Action Pack in
25 - link:files/vendor/rails/actionpack/README.html.
26 -
27 -
28 - == Getting started
29 -
30 - 1. At the command prompt, start a new rails application using the rails command
31 - and your application name. Ex: rails myapp
32 - (If you've downloaded rails in a complete tgz or zip, this step is already done)
33 - 2. Change directory into myapp and start the web server: <tt>script/server</tt> (run with --help for options)
34 - 3. Go to http://localhost:3000/ and get "Welcome aboard: You’re riding the Rails!"
35 - 4. Follow the guidelines to start developing your application
36 -
37 -
38 - == Web Servers
39 -
40 - By default, Rails will try to use Mongrel and lighttpd if they are installed, otherwise
41 - Rails will use the WEBrick, the webserver that ships with Ruby. When you run script/server,
42 - Rails will check if Mongrel exists, then lighttpd and finally fall back to WEBrick. This ensures
43 - that you can always get up and running quickly.
1 + == cafe grader
44 2
45 - Mongrel is a Ruby-based webserver with a C-component (which requires compilation) that is
46 - suitable for development and deployment of Rails applications. If you have Ruby Gems installed,
47 - getting up and running with mongrel is as easy as: <tt>gem install mongrel</tt>.
48 - More info at: http://mongrel.rubyforge.org
49 -
50 - If Mongrel is not installed, Rails will look for lighttpd. It's considerably faster than
51 - Mongrel and WEBrick and also suited for production use, but requires additional
52 - installation and currently only works well on OS X/Unix (Windows users are encouraged
53 - to start with Mongrel). We recommend version 1.4.11 and higher. You can download it from
54 - http://www.lighttpd.net.
55 -
56 - And finally, if neither Mongrel or lighttpd are installed, Rails will use the built-in Ruby
57 - web server, WEBrick. WEBrick is a small Ruby web server suitable for development, but not
58 - for production.
59 -
60 - But of course its also possible to run Rails on any platform that supports FCGI.
61 - Apache, LiteSpeed, IIS are just a few. For more information on FCGI,
62 - please visit: http://wiki.rubyonrails.com/rails/pages/FastCGI
63 -
64 -
65 - == Debugging Rails
66 -
67 - Have "tail -f" commands running on the server.log and development.log. Rails will
68 - automatically display debugging and runtime information to these files. Debugging
69 - info will also be shown in the browser on requests from 127.0.0.1.
70 -
71 -
72 - == Breakpoints
73 -
74 - Breakpoint support is available through the script/breakpointer client. This
75 - means that you can break out of execution at any point in the code, investigate
76 - and change the model, AND then resume execution! Example:
77 -
78 - class WeblogController < ActionController::Base
79 - def index
80 - @posts = Post.find(:all)
81 - breakpoint "Breaking out from the list"
82 - end
83 - end
84 -
85 - So the controller will accept the action, run the first line, then present you
86 - with a IRB prompt in the breakpointer window. Here you can do things like:
87 -
88 - Executing breakpoint "Breaking out from the list" at .../webrick_server.rb:16 in 'breakpoint'
3 + cafe grader is a programming contest platform used in Thailand IOI training.
4 + The package includes 2 repositories, jittat/cafe-grader-web and jittat/cafe-grader-judge-scripts.
89 5
90 - >> @posts.inspect
91 - => "[#<Post:0x14a6be8 @attributes={\"title\"=>nil, \"body\"=>nil, \"id\"=>\"1\"}>,
92 - #<Post:0x14a6620 @attributes={\"title\"=>\"Rails you know!\", \"body\"=>\"Only ten..\", \"id\"=>\"2\"}>]"
93 - >> @posts.first.title = "hello from a breakpoint"
94 - => "hello from a breakpoint"
95 -
96 - ...and even better is that you can examine how your runtime objects actually work:
97 -
98 - >> f = @posts.first
99 - => #<Post:0x13630c4 @attributes={"title"=>nil, "body"=>nil, "id"=>"1"}>
100 - >> f.
101 - Display all 152 possibilities? (y or n)
102 -
103 - Finally, when you're ready to resume execution, you press CTRL-D
104 -
105 -
106 - == Console
107 -
108 - You can interact with the domain model by starting the console through <tt>script/console</tt>.
109 - Here you'll have all parts of the application configured, just like it is when the
110 - application is running. You can inspect domain models, change values, and save to the
111 - database. Starting the script without arguments will launch it in the development environment.
112 - Passing an argument will specify a different environment, like <tt>script/console production</tt>.
113 -
114 - To reload your controllers and models after launching the console run <tt>reload!</tt>
115 -
116 - To reload your controllers and models after launching the console run <tt>reload!</tt>
117 -
118 -
119 -
120 - == Description of contents
121 -
122 - app
123 - Holds all the code that's specific to this particular application.
124 -
125 - app/controllers
126 - Holds controllers that should be named like weblogs_controller.rb for
127 - automated URL mapping. All controllers should descend from ApplicationController
128 - which itself descends from ActionController::Base.
129 -
130 - app/models
131 - Holds models that should be named like post.rb.
132 - Most models will descend from ActiveRecord::Base.
6 + === Installation
133 7
134 - app/views
135 - Holds the template files for the view that should be named like
136 - weblogs/index.rhtml for the WeblogsController#index action. All views use eRuby
137 - syntax.
138 -
139 - app/views/layouts
140 - Holds the template files for layouts to be used with views. This models the common
141 - header/footer method of wrapping views. In your views, define a layout using the
142 - <tt>layout :default</tt> and create a file named default.rhtml. Inside default.rhtml,
143 - call <% yield %> to render the view using this layout.
144 -
145 - app/helpers
146 - Holds view helpers that should be named like weblogs_helper.rb. These are generated
147 - for you automatically when using script/generate for controllers. Helpers can be used to
148 - wrap functionality for your views into methods.
149 -
150 - config
151 - Configuration files for the Rails environment, the routing map, the database, and other dependencies.
152 -
153 - components
154 - Self-contained mini-applications that can bundle together controllers, models, and views.
8 + The system is tested on ubuntu 14.04 LTS. Use the installation script in
9 + cafe-grader-judge-scripts/installer/install.sh . See http://theory.cpe.ku.ac.th/wiki/index.php/%E0%B8%81%E0%B8%B2%E0%B8%A3%E0%B8%95%E0%B8%B4%E0%B8%94%E0%B8%95%E0%B8%B1%E0%B9%89%E0%B8%87_Cafe_grader for the detail.
155 10
156 - db
157 - Contains the database schema in schema.rb. db/migrate contains all
158 - the sequence of Migrations for your schema.
159 -
160 - doc
161 - This directory is where your application documentation will be stored when generated
162 - using <tt>rake doc:app</tt>
163 -
164 - lib
165 - Application specific libraries. Basically, any kind of custom code that doesn't
166 - belong under controllers, models, or helpers. This directory is in the load path.
167 -
168 - public
169 - The directory available for the web server. Contains subdirectories for images, stylesheets,
170 - and javascripts. Also contains the dispatchers and the default HTML files. This should be
171 - set as the DOCUMENT_ROOT of your web server.
172 -
173 - script
174 - Helper scripts for automation and generation.
175 -
176 - test
177 - Unit and functional tests along with fixtures. When using the script/generate scripts, template
178 - test files will be generated for you and placed in this directory.
179 -
180 - vendor
181 - External libraries that the application depends on. Also includes the plugins subdirectory.
182 - This directory is in the load path.
@@ -1,293 +1,302
1 +
2 + @import jquery.ui.core
3 + @import jquery.ui.theme
4 + @import jquery.ui.datepicker
5 + @import jquery.ui.slider
6 + @import jquery-ui-timepicker-addon
7 + @import jquery-tablesorter/theme.metro-dark
8 + @import tablesorter-theme.cafe
9 +
1 10 body
2 11 background: white image-url("topbg.jpg") repeat-x top center
3 12 font-size: 13px
4 13 font-family: Tahoma, "sans-serif"
5 14 margin: 10px
6 15 padding: 10px
7 16
8 17
9 18 input
10 19 font-family: Tahoma, "sans-serif"
11 20
12 21
13 22 h1
14 23 font-size: 24px
15 24 color: #334488
16 25 line-height: 2em
17 26
18 27
19 28 h2
20 29 font-size: 18px
21 30 color: #5566bb
22 31 line-height: 1.5em
23 32
24 33
25 34 hr
26 35 border-top: 1px solid #dddddd
27 36 border-bottom: 1px solid #eeeeee
28 37
29 38
30 39 a
31 40 color: #6666cc
32 41 text-decoration: none
33 42
34 43 &:link, &:visited
35 44 color: #6666cc
36 45 text-decoration: none
37 46
38 47 &:hover, &:focus
39 48 color: #111166
40 49 text-decoration: none
41 50
42 51
43 52 div
44 53 &.userbar
45 54 line-height: 1.5em
46 55 text-align: right
47 56 font-size: 12px
48 57
49 58 &.title
50 59 padding: 10px 0px
51 60 line-height: 1.5em
52 61 font-size: 13px
53 62
54 63 span.contest-over-msg
55 64 font-size: 15px
56 65 color: red
57 66
58 67 table
59 68 width: 100%
60 69 font-weight: bold
61 70
62 71 td
63 72 &.left-col
64 73 text-align: left
65 74 vertical-align: top
66 75 color: #444444
67 76
68 77 &.right-col
69 78 text-align: right
70 79 vertical-align: top
71 80 font-size: 18px
72 81 color: #116699
73 82
74 83
75 84 table.info
76 85 margin: 10px 0
77 86 border: 1px solid #666666
78 87 border-collapse: collapse
79 88 font-size: 12px
80 89
81 90 th
82 91 border: 1px solid #666666
83 92 line-height: 1.5em
84 93 padding: 0 0.5em
85 94
86 95 td
87 96 border-left: 1px solid #666666
88 97 border-right: 1px solid #666666
89 98 line-height: 1.5em
90 99 padding: 0 0.5em
91 100
92 101
93 102 tr
94 103 &.info-head
95 104 background: #777777
96 105 color: white
97 106
98 107 &.info-odd
99 108 background: #eeeeee
100 109
101 110 &.info-even
102 111 background: #fcfcfc
103 112
104 113 =basicbox
105 114 background: #eeeeff
106 115 border: 1px dotted #99aaee
107 116 padding: 5px
108 117 margin: 10px 0px
109 118 color: black
110 119 font-size: 13px
111 120
112 121 .infobox
113 122 +basicbox
114 123
115 124 .submitbox
116 125 +basicbox
117 126
118 127 .errorExplanation
119 128 border: 1px dotted gray
120 129 color: #bb2222
121 130 padding: 5px 15px 5px 15px
122 131 margin-bottom: 5px
123 132 background-color: white
124 133 font-weight: normal
125 134
126 135 h2
127 136 color: #cc1111
128 137 font-weight: bold
129 138
130 139
131 140 table.uinfo
132 141 border-collapse: collapse
133 142 border: 1px solid black
134 143 font-size: 13px
135 144
136 145
137 146 td.uinfo
138 147 vertical-align: top
139 148 border: 1px solid black
140 149 padding: 5px
141 150
142 151
143 152 th.uinfo
144 153 background: lightgreen
145 154 vertical-align: top
146 155 text-align: right
147 156 border: 1px solid black
148 157 padding: 5px
149 158
150 159
151 160 div
152 161 &.compilermsgbody
153 162 font-family: monospace
154 163
155 164 &.task-menu
156 165 text-align: center
157 166 font-size: 13px
158 167 line-height: 1.75em
159 168 font-weight: bold
160 169 border-top: 1px dashed gray
161 170 border-bottom: 1px dashed gray
162 171 margin-top: 2px
163 172 margin-bottom: 4px
164 173
165 174
166 175 table.taskdesc
167 176 border: 2px solid #dddddd
168 177 border-collapse: collapse
169 178 margin: 10px auto
170 179 width: 90%
171 180 font-size: 13px
172 181
173 182 p
174 183 font-size: 13px
175 184
176 185 tr.name
177 186 border: 2px solid #dddddd
178 187 background: #dddddd
179 188 color: #333333
180 189 font-weight: bold
181 190 font-size: 14px
182 191 line-height: 1.5em
183 192 text-align: center
184 193
185 194 td
186 195 &.desc-odd
187 196 padding: 5px
188 197 padding-left: 20px
189 198 background: #fefeee
190 199
191 200 &.desc-even
192 201 padding: 5px
193 202 padding-left: 20px
194 203 background: #feeefe
195 204
196 205
197 206 .announcementbox
198 207 margin: 10px 0px
199 208 background: #bbddee
200 209 padding: 1px
201 210
202 211 span.title
203 212 font-weight: bold
204 213 color: #224455
205 214 padding-left: 10px
206 215 line-height: 1.6em
207 216
208 217 .announcement
209 218 margin: 2px
210 219 background: white
211 220 padding: 1px
212 221 padding-left: 10px
213 222 padding-right: 10px
214 223 padding-top: 5px
215 224 padding-bottom: 5px
216 225
217 226
218 227 .announcement p
219 228 font-size: 12px
220 229 margin: 2px
221 230
222 231
223 232 .pub-info
224 233 text-align: right
225 234 font-style: italic
226 235 font-size: 9px
227 236
228 237 p
229 238 text-align: right
230 239 font-style: italic
231 240 font-size: 9px
232 241
233 242
234 243 .announcement
235 244 .toggles
236 245 font-weight: normal
237 246 float: right
238 247 font-size: 80%
239 248
240 249 .announcement-title
241 250 font-weight: bold
242 251
243 252
244 253 div
245 254 &.message
246 255 margin: 10px 0 0
247 256
248 257 div
249 258 &.message
250 259 margin: 0 0 0 30px
251 260
252 261 &.body
253 262 border: 2px solid #dddddd
254 263 background: #fff8f8
255 264 padding-left: 5px
256 265
257 266 &.reply-body
258 267 border: 2px solid #bbbbbb
259 268 background: #fffff8
260 269 padding-left: 5px
261 270
262 271 &.stat
263 272 font-size: 10px
264 273 line-height: 1.75em
265 274 padding: 0 5px
266 275 color: #333333
267 276 background: #dddddd
268 277 font-weight: bold
269 278
270 279 &.message div.stat
271 280 font-size: 10px
272 281 line-height: 1.75em
273 282 padding: 0 5px
274 283 color: #444444
275 284 background: #bbbbbb
276 285 font-weight: bold
277 286
278 287 &.contest-title
279 288 color: white
280 289 text-align: center
281 290 line-height: 2em
282 291
283 292 &.registration-desc, &.test-desc
284 293 border: 1px dotted gray
285 294 background: #f5f5f5
286 295 padding: 5px
287 296 margin: 10px 0
288 297 font-size: 12px
289 298 line-height: 1.5em
290 299
291 300 h2.contest-title
292 301 margin-top: 5px
293 - margin-bottom: 5px No newline at end of file
302 + margin-bottom: 5px
@@ -1,83 +1,92
1 1 class ApplicationController < ActionController::Base
2 2 protect_from_forgery
3 3
4 4 SINGLE_USER_MODE_CONF_KEY = 'system.single_user_mode'
5 5
6 6 def admin_authorization
7 7 return false unless authenticate
8 8 user = User.find(session[:user_id], :include => ['roles'])
9 - redirect_to :controller => 'main', :action => 'login' unless user.admin?
9 + unless user.admin?
10 + flash[:notice] = 'You are not authorized to view the page you requested'
11 + redirect_to :controller => 'main', :action => 'login' unless user.admin?
12 + return false
13 + end
14 + return true
10 15 end
11 16
12 17 def authorization_by_roles(allowed_roles)
13 18 return false unless authenticate
14 19 user = User.find(session[:user_id])
15 20 unless user.roles.detect { |role| allowed_roles.member?(role.name) }
16 21 flash[:notice] = 'You are not authorized to view the page you requested'
17 22 redirect_to :controller => 'main', :action => 'login'
18 23 return false
19 24 end
20 25 end
21 26
22 27 protected
23 28
24 29 def authenticate
25 30 unless session[:user_id]
31 + flash[:notice] = 'You need to login'
32 + if GraderConfiguration[SINGLE_USER_MODE_CONF_KEY]
33 + flash[:notice] = 'You need to login but you cannot log in at this time'
34 + end
26 35 redirect_to :controller => 'main', :action => 'login'
27 36 return false
28 37 end
29 38
30 39 # check if run in single user mode
31 40 if GraderConfiguration[SINGLE_USER_MODE_CONF_KEY]
32 41 user = User.find(session[:user_id])
33 42 if user==nil or (not user.admin?)
34 43 flash[:notice] = 'You cannot log in at this time'
35 44 redirect_to :controller => 'main', :action => 'login'
36 45 return false
37 46 end
38 47 return true
39 48 end
40 49
41 50 if GraderConfiguration.multicontests?
42 51 user = User.find(session[:user_id])
43 52 return true if user.admin?
44 53 begin
45 54 if user.contest_stat(true).forced_logout
46 55 flash[:notice] = 'You have been automatically logged out.'
47 56 redirect_to :controller => 'main', :action => 'index'
48 57 end
49 58 rescue
50 59 end
51 60 end
52 61 return true
53 62 end
54 63
55 64 def authorization
56 65 return false unless authenticate
57 66 user = User.find(session[:user_id])
58 67 unless user.roles.detect { |role|
59 68 role.rights.detect{ |right|
60 69 right.controller == self.class.controller_name and
61 70 (right.action == 'all' or right.action == action_name)
62 71 }
63 72 }
64 73 flash[:notice] = 'You are not authorized to view the page you requested'
65 74 #request.env['HTTP_REFERER'] ? (redirect_to :back) : (redirect_to :controller => 'login')
66 75 redirect_to :controller => 'main', :action => 'login'
67 76 return false
68 77 end
69 78 end
70 79
71 80 def verify_time_limit
72 81 return true if session[:user_id]==nil
73 82 user = User.find(session[:user_id], :include => :site)
74 83 return true if user==nil or user.site == nil
75 84 if user.contest_finished?
76 85 flash[:notice] = 'Error: the contest you are participating is over.'
77 86 redirect_to :back
78 87 return false
79 88 end
80 89 return true
81 90 end
82 91
83 92 end
@@ -1,20 +1,28
1 1 class ConfigurationsController < ApplicationController
2 2
3 3 before_filter :authenticate
4 4 before_filter { |controller| controller.authorization_by_roles(['admin'])}
5 5
6 - in_place_edit_for :grader_configuration, :key
7 - in_place_edit_for :grader_configuration, :type
8 - in_place_edit_for :grader_configuration, :value
9 6
10 7 def index
11 8 @configurations = GraderConfiguration.find(:all,
12 9 :order => '`key`')
13 10 end
14 11
15 12 def reload
16 13 GraderConfiguration.reload
17 14 redirect_to :action => 'index'
18 15 end
19 16
17 + def update
18 + @config = GraderConfiguration.find(params[:id])
19 + respond_to do |format|
20 + if @config.update_attributes(params[:grader_configuration])
21 + format.json { head :ok }
22 + else
23 + format.json { respond_with_bip(@config) }
24 + end
25 + end
26 + end
27 +
20 28 end
@@ -1,96 +1,119
1 1 class GradersController < ApplicationController
2 2
3 - before_filter :admin_authorization
3 + before_filter :admin_authorization, except: [ :submission ]
4 + before_filter(only: [:submission]) {
5 + return false unless authenticate
6 +
7 + if GraderConfiguration["right.user_view_submission"]
8 + return true;
9 + end
10 +
11 + admin_authorization
12 + }
4 13
5 14 verify :method => :post, :only => ['clear_all',
6 15 'start_exam',
7 16 'start_grading',
8 17 'stop_all',
9 18 'clear_terminated'],
10 19 :redirect_to => {:action => 'index'}
11 20
12 21 def index
13 22 redirect_to :action => 'list'
14 23 end
15 24
16 25 def list
17 26 @grader_processes = GraderProcess.find_running_graders
18 27 @stalled_processes = GraderProcess.find_stalled_process
19 28
20 29 @terminated_processes = GraderProcess.find_terminated_graders
21 30
22 31 @last_task = Task.find(:first,
23 32 :order => 'created_at DESC')
24 33 @last_test_request = TestRequest.find(:first,
25 34 :order => 'created_at DESC')
35 + @submission = Submission.order("id desc").limit(20)
26 36 end
27 37
28 38 def clear
29 39 grader_proc = GraderProcess.find(params[:id])
30 40 grader_proc.destroy if grader_proc!=nil
31 41 redirect_to :action => 'list'
32 42 end
33 43
34 44 def clear_terminated
35 45 GraderProcess.find_terminated_graders.each do |p|
36 46 p.destroy
37 47 end
38 48 redirect_to :action => 'list'
39 49 end
40 50
41 51 def clear_all
42 52 GraderProcess.find(:all).each do |p|
43 53 p.destroy
44 54 end
45 55 redirect_to :action => 'list'
46 56 end
47 57
48 58 def view
49 59 if params[:type]=='Task'
50 60 redirect_to :action => 'task', :id => params[:id]
51 61 else
52 62 redirect_to :action => 'test_request', :id => params[:id]
53 63 end
54 64 end
55 65
56 66 def test_request
57 67 @test_request = TestRequest.find(params[:id])
58 68 end
59 69
60 70 def task
61 71 @task = Task.find(params[:id])
62 72 end
63 73
64 74 def submission
65 75 @submission = Submission.find(params[:id])
76 + formatter = Rouge::Formatters::HTML.new(css_class: 'highlight', line_numbers: true )
77 + lexer = case @submission.language.name
78 + when "c" then Rouge::Lexers::C.new
79 + when "cpp" then Rouge::Lexers::Cpp.new
80 + when "pas" then Rouge::Lexers::Pas.new
81 + when "ruby" then Rouge::Lexers::Ruby.new
82 + when "python" then Rouge::Lexers::Python.new
83 + when "java" then Rouge::Lexers::Java.new
84 + when "php" then Rouge::Lexers::PHP.new
85 + end
86 + @formatted_code = formatter.format(lexer.lex(@submission.source))
87 + @css_style = Rouge::Themes::ThankfulEyes.render(scope: '.highlight')
88 +
66 89 end
67 90
68 91 # various grader controls
69 92
70 93 def stop
71 94 grader_proc = GraderProcess.find(params[:id])
72 95 GraderScript.stop_grader(grader_proc.pid)
73 96 flash[:notice] = 'Grader stopped. It may not disappear now, but it should disappear shortly.'
74 97 redirect_to :action => 'list'
75 98 end
76 99
77 100 def stop_all
78 101 GraderScript.stop_graders(GraderProcess.find_running_graders +
79 102 GraderProcess.find_stalled_process)
80 103 flash[:notice] = 'Graders stopped. They may not disappear now, but they should disappear shortly.'
81 104 redirect_to :action => 'list'
82 105 end
83 106
84 107 def start_grading
85 108 GraderScript.start_grader('grading')
86 109 flash[:notice] = '2 graders in grading env started, one for grading queue tasks, another for grading test request'
87 110 redirect_to :action => 'list'
88 111 end
89 112
90 113 def start_exam
91 114 GraderScript.start_grader('exam')
92 115 flash[:notice] = '2 graders in grading env started, one for grading queue tasks, another for grading test request'
93 116 redirect_to :action => 'list'
94 117 end
95 118
96 119 end
@@ -1,51 +1,54
1 1 class LoginController < ApplicationController
2 2
3 3 def index
4 4 # show login screen
5 5 reset_session
6 6 redirect_to :controller => 'main', :action => 'login'
7 7 end
8 8
9 9 def login
10 10 if user = User.authenticate(params[:login], params[:password])
11 11 session[:user_id] = user.id
12 12 session[:admin] = user.admin?
13 13
14 14 # clear forced logout flag for multicontests contest change
15 15 if GraderConfiguration.multicontests?
16 16 contest_stat = user.contest_stat
17 17 if contest_stat.respond_to? :forced_logout
18 18 if contest_stat.forced_logout
19 19 contest_stat.forced_logout = false
20 20 contest_stat.save
21 21 end
22 22 end
23 23 end
24 24
25 + #save login information
26 + Login.create(user_id: user.id, ip_address: request.remote_ip)
27 +
25 28 redirect_to :controller => 'main', :action => 'list'
26 29 else
27 30 flash[:notice] = 'Wrong password'
28 31 redirect_to :controller => 'main', :action => 'login'
29 32 end
30 33 end
31 34
32 35 def site_login
33 36 begin
34 37 site = Site.find(params[:login][:site_id])
35 38 rescue ActiveRecord::RecordNotFound
36 39 site = nil
37 40 end
38 41 if site==nil
39 42 flash[:notice] = 'Wrong site'
40 43 redirect_to :controller => 'main', :action => 'login' and return
41 44 end
42 45 if (site.password) and (site.password == params[:login][:password])
43 46 session[:site_id] = site.id
44 47 redirect_to :controller => 'site', :action => 'index'
45 48 else
46 49 flash[:notice] = 'Wrong site password'
47 50 redirect_to :controller => 'site', :action => 'login'
48 51 end
49 52 end
50 53
51 54 end
@@ -1,381 +1,383
1 1 class MainController < ApplicationController
2 2
3 3 before_filter :authenticate, :except => [:index, :login]
4 4 before_filter :check_viewability, :except => [:index, :login]
5 5
6 6 append_before_filter :confirm_and_update_start_time,
7 7 :except => [:index,
8 8 :login,
9 9 :confirm_contest_start]
10 10
11 11 # to prevent log in box to be shown when user logged out of the
12 12 # system only in some tab
13 13 prepend_before_filter :reject_announcement_refresh_when_logged_out,
14 14 :only => [:announcements]
15 15
16 16 # COMMENTED OUT: filter in each action instead
17 17 # before_filter :verify_time_limit, :only => [:submit]
18 18
19 19 verify :method => :post, :only => [:submit],
20 20 :redirect_to => { :action => :index }
21 21
22 22 # COMMENT OUT: only need when having high load
23 23 # caches_action :index, :login
24 24
25 25 # NOTE: This method is not actually needed, 'config/routes.rb' has
26 26 # assigned action login as a default action.
27 27 def index
28 28 redirect_to :action => 'login'
29 29 end
30 30
31 31 def login
32 32 saved_notice = flash[:notice]
33 33 reset_session
34 34 flash.now[:notice] = saved_notice
35 35
36 36 # EXPERIMENT:
37 37 # Hide login if in single user mode and the url does not
38 38 # explicitly specify /login
39 39 #
40 40 # logger.info "PATH: #{request.path}"
41 41 # if GraderConfiguration['system.single_user_mode'] and
42 42 # request.path!='/main/login'
43 43 # @hidelogin = true
44 44 # end
45 45
46 46 @announcements = Announcement.find_for_frontpage
47 47 render :action => 'login', :layout => 'empty'
48 48 end
49 49
50 50 def list
51 51 prepare_list_information
52 52 end
53 53
54 54 def help
55 55 @user = User.find(session[:user_id])
56 56 end
57 57
58 58 def submit
59 59 user = User.find(session[:user_id])
60 60
61 61 @submission = Submission.new
62 62 @submission.problem_id = params[:submission][:problem_id]
63 63 @submission.user = user
64 64 @submission.language_id = 0
65 65 if (params['file']) and (params['file']!='')
66 - @submission.source = params['file'].read
66 + @submission.source = File.open(params['file'].path,'r:UTF-8',&:read)
67 + @submission.source.encode!('UTF-8','UTF-8',invalid: :replace, replace: '')
67 68 @submission.source_filename = params['file'].original_filename
68 69 end
69 70 @submission.submitted_at = Time.new.gmtime
71 + @submission.ip_address = request.remote_ip
70 72
71 73 if GraderConfiguration.time_limit_mode? and user.contest_finished?
72 74 @submission.errors.add(:base,"The contest is over.")
73 75 prepare_list_information
74 76 render :action => 'list' and return
75 77 end
76 78
77 79 if @submission.valid?
78 80 if @submission.save == false
79 81 flash[:notice] = 'Error saving your submission'
80 82 elsif Task.create(:submission_id => @submission.id,
81 83 :status => Task::STATUS_INQUEUE) == false
82 84 flash[:notice] = 'Error adding your submission to task queue'
83 85 end
84 86 else
85 87 prepare_list_information
86 88 render :action => 'list' and return
87 89 end
88 90 redirect_to :action => 'list'
89 91 end
90 92
91 93 def source
92 94 submission = Submission.find(params[:id])
93 95 if ((submission.user_id == session[:user_id]) and
94 96 (submission.problem != nil) and
95 97 (submission.problem.available))
96 98 send_data(submission.source,
97 99 {:filename => submission.download_filename,
98 100 :type => 'text/plain'})
99 101 else
100 102 flash[:notice] = 'Error viewing source'
101 103 redirect_to :action => 'list'
102 104 end
103 105 end
104 106
105 107 def compiler_msg
106 108 @submission = Submission.find(params[:id])
107 109 if @submission.user_id == session[:user_id]
108 110 render :action => 'compiler_msg', :layout => 'empty'
109 111 else
110 112 flash[:notice] = 'Error viewing source'
111 113 redirect_to :action => 'list'
112 114 end
113 115 end
114 116
115 117 def submission
116 118 @user = User.find(session[:user_id])
117 119 @problems = @user.available_problems
118 120 if params[:id]==nil
119 121 @problem = nil
120 122 @submissions = nil
121 123 else
122 124 @problem = Problem.find_by_name(params[:id])
123 125 if not @problem.available
124 126 redirect_to :action => 'list'
125 127 flash[:notice] = 'Error: submissions for that problem are not viewable.'
126 128 return
127 129 end
128 130 @submissions = Submission.find_all_by_user_problem(@user.id, @problem.id)
129 131 end
130 132 end
131 133
132 134 def result
133 135 if !GraderConfiguration.show_grading_result
134 136 redirect_to :action => 'list' and return
135 137 end
136 138 @user = User.find(session[:user_id])
137 139 @submission = Submission.find(params[:id])
138 140 if @submission.user!=@user
139 141 flash[:notice] = 'You are not allowed to view result of other users.'
140 142 redirect_to :action => 'list' and return
141 143 end
142 144 prepare_grading_result(@submission)
143 145 end
144 146
145 147 def load_output
146 148 if !GraderConfiguration.show_grading_result or params[:num]==nil
147 149 redirect_to :action => 'list' and return
148 150 end
149 151 @user = User.find(session[:user_id])
150 152 @submission = Submission.find(params[:id])
151 153 if @submission.user!=@user
152 154 flash[:notice] = 'You are not allowed to view result of other users.'
153 155 redirect_to :action => 'list' and return
154 156 end
155 157 case_num = params[:num].to_i
156 158 out_filename = output_filename(@user.login,
157 159 @submission.problem.name,
158 160 @submission.id,
159 161 case_num)
160 162 if !FileTest.exists?(out_filename)
161 163 flash[:notice] = 'Output not found.'
162 164 redirect_to :action => 'list' and return
163 165 end
164 166
165 167 if defined?(USE_APACHE_XSENDFILE) and USE_APACHE_XSENDFILE
166 168 response.headers['Content-Type'] = "application/force-download"
167 169 response.headers['Content-Disposition'] = "attachment; filename=\"output-#{case_num}.txt\""
168 170 response.headers["X-Sendfile"] = out_filename
169 171 response.headers['Content-length'] = File.size(out_filename)
170 172 render :nothing => true
171 173 else
172 174 send_file out_filename, :stream => false, :filename => "output-#{case_num}.txt", :type => "text/plain"
173 175 end
174 176 end
175 177
176 178 def error
177 179 @user = User.find(session[:user_id])
178 180 end
179 181
180 182 # announcement refreshing and hiding methods
181 183
182 184 def announcements
183 185 if params.has_key? 'recent'
184 186 prepare_announcements(params[:recent])
185 187 else
186 188 prepare_announcements
187 189 end
188 190 render(:partial => 'announcement',
189 191 :collection => @announcements,
190 192 :locals => {:announcement_effect => true})
191 193 end
192 194
193 195 def confirm_contest_start
194 196 user = User.find(session[:user_id])
195 197 if request.method == 'POST'
196 198 user.update_start_time
197 199 redirect_to :action => 'list'
198 200 else
199 201 @contests = user.contests
200 202 @user = user
201 203 end
202 204 end
203 205
204 206 protected
205 207
206 208 def prepare_announcements(recent=nil)
207 209 if GraderConfiguration.show_tasks_to?(@user)
208 210 @announcements = Announcement.find_published(true)
209 211 else
210 212 @announcements = Announcement.find_published
211 213 end
212 214 if recent!=nil
213 215 recent_id = recent.to_i
214 216 @announcements = @announcements.find_all { |a| a.id > recent_id }
215 217 end
216 218 end
217 219
218 220 def prepare_list_information
219 221 @user = User.find(session[:user_id])
220 222 if not GraderConfiguration.multicontests?
221 223 @problems = @user.available_problems
222 224 else
223 225 @contest_problems = @user.available_problems_group_by_contests
224 226 @problems = @user.available_problems
225 227 end
226 228 @prob_submissions = {}
227 229 @problems.each do |p|
228 230 sub = Submission.find_last_by_user_and_problem(@user.id,p.id)
229 231 if sub!=nil
230 232 @prob_submissions[p.id] = { :count => sub.number, :submission => sub }
231 233 else
232 234 @prob_submissions[p.id] = { :count => 0, :submission => nil }
233 235 end
234 236 end
235 237 prepare_announcements
236 238 end
237 239
238 240 def check_viewability
239 241 @user = User.find(session[:user_id])
240 242 if (!GraderConfiguration.show_tasks_to?(@user)) and
241 243 ((action_name=='submission') or (action_name=='submit'))
242 244 redirect_to :action => 'list' and return
243 245 end
244 246 end
245 247
246 248 def prepare_grading_result(submission)
247 249 if GraderConfiguration.task_grading_info.has_key? submission.problem.name
248 250 grading_info = GraderConfiguration.task_grading_info[submission.problem.name]
249 251 else
250 252 # guess task info from problem.full_score
251 253 cases = submission.problem.full_score / 10
252 254 grading_info = {
253 255 'testruns' => cases,
254 256 'testcases' => cases
255 257 }
256 258 end
257 259 @test_runs = []
258 260 if grading_info['testruns'].is_a? Integer
259 261 trun_count = grading_info['testruns']
260 262 trun_count.times do |i|
261 263 @test_runs << [ read_grading_result(@user.login,
262 264 submission.problem.name,
263 265 submission.id,
264 266 i+1) ]
265 267 end
266 268 else
267 269 grading_info['testruns'].keys.sort.each do |num|
268 270 run = []
269 271 testrun = grading_info['testruns'][num]
270 272 testrun.each do |c|
271 273 run << read_grading_result(@user.login,
272 274 submission.problem.name,
273 275 submission.id,
274 276 c)
275 277 end
276 278 @test_runs << run
277 279 end
278 280 end
279 281 end
280 282
281 283 def grading_result_dir(user_name, problem_name, submission_id, case_num)
282 284 return "#{GRADING_RESULT_DIR}/#{user_name}/#{problem_name}/#{submission_id}/test-result/#{case_num}"
283 285 end
284 286
285 287 def output_filename(user_name, problem_name, submission_id, case_num)
286 288 dir = grading_result_dir(user_name,problem_name, submission_id, case_num)
287 289 return "#{dir}/output.txt"
288 290 end
289 291
290 292 def read_grading_result(user_name, problem_name, submission_id, case_num)
291 293 dir = grading_result_dir(user_name,problem_name, submission_id, case_num)
292 294 result_file_name = "#{dir}/result"
293 295 if !FileTest.exists?(result_file_name)
294 296 return {:num => case_num, :msg => 'program did not run'}
295 297 else
296 298 results = File.open(result_file_name).readlines
297 299 run_stat = extract_running_stat(results)
298 300 output_filename = "#{dir}/output.txt"
299 301 if FileTest.exists?(output_filename)
300 302 output_file = true
301 303 output_size = File.size(output_filename)
302 304 else
303 305 output_file = false
304 306 output_size = 0
305 307 end
306 308
307 309 return {
308 310 :num => case_num,
309 311 :msg => results[0],
310 312 :run_stat => run_stat,
311 313 :output => output_file,
312 314 :output_size => output_size
313 315 }
314 316 end
315 317 end
316 318
317 319 # copied from grader/script/lib/test_request_helper.rb
318 320 def extract_running_stat(results)
319 321 running_stat_line = results[-1]
320 322
321 323 # extract exit status line
322 324 run_stat = ""
323 325 if !(/[Cc]orrect/.match(results[0]))
324 326 run_stat = results[0].chomp
325 327 else
326 328 run_stat = 'Program exited normally'
327 329 end
328 330
329 331 logger.info "Stat line: #{running_stat_line}"
330 332
331 333 # extract running time
332 334 if res = /r(.*)u(.*)s/.match(running_stat_line)
333 335 seconds = (res[1].to_f + res[2].to_f)
334 336 time_stat = "Time used: #{seconds} sec."
335 337 else
336 338 seconds = nil
337 339 time_stat = "Time used: n/a sec."
338 340 end
339 341
340 342 # extract memory usage
341 343 if res = /s(.*)m/.match(running_stat_line)
342 344 memory_used = res[1].to_i
343 345 else
344 346 memory_used = -1
345 347 end
346 348
347 349 return {
348 350 :msg => "#{run_stat}\n#{time_stat}",
349 351 :running_time => seconds,
350 352 :exit_status => run_stat,
351 353 :memory_usage => memory_used
352 354 }
353 355 end
354 356
355 357 def confirm_and_update_start_time
356 358 user = User.find(session[:user_id])
357 359 if (GraderConfiguration.indv_contest_mode? and
358 360 GraderConfiguration['contest.confirm_indv_contest_start'] and
359 361 !user.contest_started?)
360 362 redirect_to :action => 'confirm_contest_start' and return
361 363 end
362 364 if not GraderConfiguration.analysis_mode?
363 365 user.update_start_time
364 366 end
365 367 end
366 368
367 369 def reject_announcement_refresh_when_logged_out
368 370 if not session[:user_id]
369 371 render :text => 'Access forbidden', :status => 403
370 372 end
371 373
372 374 if GraderConfiguration.multicontests?
373 375 user = User.find(session[:user_id])
374 376 if user.contest_stat.forced_logout
375 377 render :text => 'Access forbidden', :status => 403
376 378 end
377 379 end
378 380 end
379 381
380 382 end
381 383
@@ -1,86 +1,86
1 1 class MessagesController < ApplicationController
2 2
3 3 before_filter :authenticate
4 4
5 5 verify :method => :post, :only => ['create'],
6 6 :redirect_to => { :action => 'list' }
7 7
8 8 before_filter :admin_authorization, :only => ['console','show',
9 9 'reply','hide','list_all']
10 10
11 11 def list
12 12 @user = User.find(session[:user_id])
13 13 @messages = Message.find_all_sent_by_user(@user)
14 14 end
15 -
15 +
16 16 def console
17 17 @user = User.find(session[:user_id])
18 18 @messages = Message.find_all_system_unreplied_messages
19 19 end
20 20
21 21 def show
22 22 @message = Message.find(params[:id])
23 23 end
24 24
25 25 def list_all
26 26 @user = User.find(session[:user_id])
27 27 @messages = Message.where(receiver_id: nil).order(:created_at)
28 28 end
29 29
30 30 def create
31 31 user = User.find(session[:user_id])
32 32 @message = Message.new(params[:message])
33 33 @message.sender = user
34 34 if @message.body == '' or !@message.save
35 35 flash[:notice] = 'An error occurred'
36 36 else
37 37 flash[:notice] = 'New message posted'
38 38 end
39 39 redirect_to :action => 'list'
40 40 end
41 41
42 42 def reply
43 43 user = User.find(session[:user_id])
44 44 @message = Message.new(params[:r_message])
45 45 @message.sender = user
46 46 if @message.body == '' or !@message.save
47 47 flash[:notice] = 'An error occurred'
48 48 redirect_to :action => 'show', :id => @message.replying_message_id
49 49 else
50 50 flash[:notice] = 'Message replied'
51 51 rep_msg = @message.replying_message
52 52 rep_msg.replied = true
53 53 rep_msg.save
54 54 redirect_to :action => 'console'
55 55 end
56 56 end
57 57
58 58 def hide
59 59 message = Message.find(params[:id])
60 60 message.replied = true
61 61 message.save
62 62 flash[:notice] = 'Message hidden (just marked replied)'
63 63 redirect_to :action => 'console'
64 64 end
65 65
66 66 protected
67 67 def build_replying_message_hierarchy(user)
68 68 @all_messages = {}
69 69
70 70
71 71 # manually build replies hierarchy (to improve efficiency)
72 72 [@messages, @replied_messages].each do |collection|
73 73 collection.each do |m|
74 74 @all_messages[m.id] = {:msg => m, :replies => []}
75 75 end
76 76 end
77 77
78 78 @all_messages.each do |m|
79 79 rep_id = m.replying_message_id
80 80 if @all_messages[rep_id]!=nil
81 81 @all_messages[rep_id][:replies] << m
82 82 end
83 83 end
84 84 end
85 85
86 86 end
@@ -1,248 +1,277
1 1 class ProblemsController < ApplicationController
2 2
3 3 before_filter :authenticate, :authorization
4 4
5 5 in_place_edit_for :problem, :name
6 6 in_place_edit_for :problem, :full_name
7 7 in_place_edit_for :problem, :full_score
8 8
9 9 def index
10 10 list
11 11 render :action => 'list'
12 12 end
13 13
14 14 # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
15 15 verify :method => :post, :only => [ :destroy,
16 16 :create, :quick_create,
17 17 :do_manage,
18 18 :do_import,
19 19 :update ],
20 20 :redirect_to => { :action => :list }
21 21
22 22 def list
23 23 @problems = Problem.find(:all, :order => 'date_added DESC')
24 24 end
25 25
26 26 def show
27 27 @problem = Problem.find(params[:id])
28 28 end
29 29
30 30 def new
31 31 @problem = Problem.new
32 32 @description = nil
33 33 end
34 34
35 35 def create
36 36 @problem = Problem.new(params[:problem])
37 37 @description = Description.new(params[:description])
38 38 if @description.body!=''
39 39 if !@description.save
40 40 render :action => new and return
41 41 end
42 42 else
43 43 @description = nil
44 44 end
45 45 @problem.description = @description
46 46 if @problem.save
47 47 flash[:notice] = 'Problem was successfully created.'
48 48 redirect_to :action => 'list'
49 49 else
50 50 render :action => 'new'
51 51 end
52 52 end
53 53
54 54 def quick_create
55 55 @problem = Problem.new(params[:problem])
56 56 @problem.full_name = @problem.name if @problem.full_name == ''
57 57 @problem.full_score = 100
58 58 @problem.available = false
59 59 @problem.test_allowed = true
60 60 @problem.output_only = false
61 61 @problem.date_added = Time.new
62 62 if @problem.save
63 63 flash[:notice] = 'Problem was successfully created.'
64 64 redirect_to :action => 'list'
65 65 else
66 66 flash[:notice] = 'Error saving problem'
67 67 redirect_to :action => 'list'
68 68 end
69 69 end
70 70
71 71 def edit
72 72 @problem = Problem.find(params[:id])
73 73 @description = @problem.description
74 74 end
75 75
76 76 def update
77 77 @problem = Problem.find(params[:id])
78 78 @description = @problem.description
79 79 if @description == nil and params[:description][:body]!=''
80 80 @description = Description.new(params[:description])
81 81 if !@description.save
82 82 flash[:notice] = 'Error saving description'
83 83 render :action => 'edit' and return
84 84 end
85 85 @problem.description = @description
86 86 elsif @description!=nil
87 87 if !@description.update_attributes(params[:description])
88 88 flash[:notice] = 'Error saving description'
89 89 render :action => 'edit' and return
90 90 end
91 91 end
92 92 if params[:file] and params[:file].content_type != 'application/pdf'
93 93 flash[:notice] = 'Error: Uploaded file is not PDF'
94 94 render :action => 'edit' and return
95 95 end
96 96 if @problem.update_attributes(params[:problem])
97 97 flash[:notice] = 'Problem was successfully updated.'
98 98 unless params[:file] == nil or params[:file] == ''
99 99 flash[:notice] = 'Problem was successfully updated and a new PDF file is uploaded.'
100 100 out_dirname = "#{Problem.download_file_basedir}/#{@problem.id}"
101 101 if not FileTest.exists? out_dirname
102 102 Dir.mkdir out_dirname
103 103 end
104 104
105 105 out_filename = "#{out_dirname}/#{@problem.name}.pdf"
106 106 if FileTest.exists? out_filename
107 107 File.delete out_filename
108 108 end
109 109
110 110 File.open(out_filename,"wb") do |file|
111 111 file.write(params[:file].read)
112 112 end
113 113 @problem.description_filename = "#{@problem.name}.pdf"
114 114 @problem.save
115 115 end
116 116 redirect_to :action => 'show', :id => @problem
117 117 else
118 118 render :action => 'edit'
119 119 end
120 120 end
121 121
122 122 def destroy
123 123 Problem.find(params[:id]).destroy
124 124 redirect_to :action => 'list'
125 125 end
126 126
127 127 def toggle
128 128 @problem = Problem.find(params[:id])
129 129 @problem.available = !(@problem.available)
130 130 @problem.save
131 131 end
132 132
133 133 def turn_all_off
134 134 Problem.find(:all,
135 135 :conditions => "available = 1").each do |problem|
136 136 problem.available = false
137 137 problem.save
138 138 end
139 139 redirect_to :action => 'list'
140 140 end
141 141
142 142 def turn_all_on
143 143 Problem.find(:all,
144 144 :conditions => "available = 0").each do |problem|
145 145 problem.available = true
146 146 problem.save
147 147 end
148 148 redirect_to :action => 'list'
149 149 end
150 150
151 151 def stat
152 152 @problem = Problem.find(params[:id])
153 - if !@problem.available
153 + unless @problem.available or session[:admin]
154 154 redirect_to :controller => 'main', :action => 'list'
155 - else
156 - @submissions = Submission.find_all_last_by_problem(params[:id])
155 + return
157 156 end
157 + @submissions = Submission.includes(:user).where(problem_id: params[:id]).order(:user_id,:id)
158 +
159 + #stat summary
160 + range =65
161 + @histogram = { data: Array.new(range,0), summary: {} }
162 + user = Hash.new(0)
163 + @submissions.find_each do |sub|
164 + d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60
165 + @histogram[:data][d.to_i] += 1 if d < range
166 + user[sub.user_id] = [user[sub.user_id], (sub.points >= @problem.full_score) ? 1 : 0].max
167 + end
168 + @histogram[:summary][:max] = [@histogram[:data].max,1].max
169 +
170 + @summary = { attempt: user.count, solve: 0 }
171 + user.each_value { |v| @summary[:solve] += 1 if v == 1 }
158 172 end
159 173
160 174 def manage
161 175 @problems = Problem.find(:all, :order => 'date_added DESC')
162 176 end
163 177
164 178 def do_manage
165 179 if params.has_key? 'change_date_added'
166 180 change_date_added
167 - else params.has_key? 'add_to_contest'
181 + elsif params.has_key? 'add_to_contest'
168 182 add_to_contest
183 + elsif params.has_key? 'enable_problem'
184 + set_available(true)
185 + elsif params.has_key? 'disable_problem'
186 + set_available(false)
169 187 end
170 188 redirect_to :action => 'manage'
171 189 end
172 190
173 191 def import
174 192 @allow_test_pair_import = allow_test_pair_import?
175 193 end
176 194
177 195 def do_import
178 196 old_problem = Problem.find_by_name(params[:name])
179 197 if !allow_test_pair_import? and params.has_key? :import_to_db
180 198 params.delete :import_to_db
181 199 end
182 200 @problem, import_log = Problem.create_from_import_form_params(params,
183 201 old_problem)
184 202
185 203 if !@problem.errors.empty?
186 204 render :action => 'import' and return
187 205 end
188 206
189 207 if old_problem!=nil
190 208 flash[:notice] = "The test data has been replaced for problem #{@problem.name}"
191 209 end
192 210 @log = import_log
193 211 end
194 212
195 213 def remove_contest
196 214 problem = Problem.find(params[:id])
197 215 contest = Contest.find(params[:contest_id])
198 216 if problem!=nil and contest!=nil
199 217 problem.contests.delete(contest)
200 218 end
201 219 redirect_to :action => 'manage'
202 220 end
203 221
204 222 ##################################
205 223 protected
206 224
207 225 def allow_test_pair_import?
208 226 if defined? ALLOW_TEST_PAIR_IMPORT
209 227 return ALLOW_TEST_PAIR_IMPORT
210 228 else
211 229 return false
212 230 end
213 231 end
214 232
215 233 def change_date_added
216 234 problems = get_problems_from_params
217 235 year = params[:date_added][:year].to_i
218 236 month = params[:date_added][:month].to_i
219 237 day = params[:date_added][:day].to_i
220 238 date = Date.new(year,month,day)
221 239 problems.each do |p|
222 240 p.date_added = date
223 241 p.save
224 242 end
225 243 end
226 244
227 245 def add_to_contest
228 246 problems = get_problems_from_params
229 247 contest = Contest.find(params[:contest][:id])
230 248 if contest!=nil and contest.enabled
231 249 problems.each do |p|
232 250 p.contests << contest
233 251 end
234 252 end
235 253 end
236 254
255 + def set_available(avail)
256 + problems = get_problems_from_params
257 + problems.each do |p|
258 + p.available = avail
259 + p.save
260 + end
261 + end
262 +
237 263 def get_problems_from_params
238 264 problems = []
239 265 params.keys.each do |k|
240 266 if k.index('prob-')==0
241 - name, id = k.split('-')
267 + name, id, order = k.split('-')
242 268 problems << Problem.find(id)
243 269 end
244 270 end
245 271 problems
246 272 end
247 273
274 + def get_problems_stat
275 + end
276 +
248 277 end
@@ -1,476 +1,530
1 + require 'csv'
2 +
1 3 class UserAdminController < ApplicationController
2 4
3 5 include MailHelperMethods
4 6
5 7 before_filter :admin_authorization
6 8
7 9 # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
8 10 verify :method => :post, :only => [ :destroy,
9 11 :create, :create_from_list,
10 12 :update,
11 13 :manage_contest,
12 14 :bulk_mail
13 15 ],
14 16 :redirect_to => { :action => :list }
15 17
16 18 def index
17 19 list
18 20 render :action => 'list'
19 21 end
20 22
21 23 def list
22 24 @user_count = User.count
23 25 if params[:page] == 'all'
24 26 @users = User.all
25 27 @paginated = false
26 28 else
27 29 @users = User.paginate :page => params[:page]
28 30 @paginated = true
29 31 end
30 32 @hidden_columns = ['hashed_password', 'salt', 'created_at', 'updated_at']
31 33 @contests = Contest.enabled
32 34 end
33 35
34 36 def active
35 37 sessions = ActiveRecord::SessionStore::Session.find(:all, :conditions => ["updated_at >= ?", 60.minutes.ago])
36 38 @users = []
37 39 sessions.each do |session|
38 40 if session.data[:user_id]
39 41 @users << User.find(session.data[:user_id])
40 42 end
41 43 end
42 44 end
43 45
44 46 def show
45 47 @user = User.find(params[:id])
46 48 end
47 49
48 50 def new
49 51 @user = User.new
50 52 end
51 53
52 54 def create
53 55 @user = User.new(params[:user])
54 56 @user.activated = true
55 57 if @user.save
56 58 flash[:notice] = 'User was successfully created.'
57 59 redirect_to :action => 'list'
58 60 else
59 61 render :action => 'new'
60 62 end
61 63 end
62 64
63 65 def create_from_list
64 66 lines = params[:user_list]
65 67
66 68 note = []
67 69
68 70 lines.split("\n").each do |line|
69 71 items = line.chomp.split(',')
70 72 if items.length>=2
71 73 login = items[0]
72 74 full_name = items[1]
73 75
74 76 added_random_password = false
75 77 if items.length>=3
76 78 password = items[2].chomp(" ")
77 79 user_alias = (items.length>=4) ? items[3] : login
78 80 else
79 81 password = random_password
80 82 user_alias = (items.length>=4) ? items[3] : login
81 83 added_random_password = true
82 84 end
83 85
84 - user = User.new({:login => login,
85 - :full_name => full_name,
86 - :password => password,
87 - :password_confirmation => password,
88 - :alias => user_alias})
86 + user = User.find_by_login(login)
87 + if (user)
88 + user.full_name = full_name
89 + user.password = password
90 + else
91 + user = User.new({:login => login,
92 + :full_name => full_name,
93 + :password => password,
94 + :password_confirmation => password,
95 + :alias => user_alias})
96 + end
89 97 user.activated = true
90 98 user.save
91 99
92 100 if added_random_password
93 101 note << "'#{login}' (+)"
94 102 else
95 103 note << login
96 104 end
97 105 end
98 106 end
99 107 flash[:notice] = 'User(s) ' + note.join(', ') +
100 108 ' were successfully created. ' +
101 109 '( (+) - created with random passwords.)'
102 110 redirect_to :action => 'list'
103 111 end
104 112
105 113 def edit
106 114 @user = User.find(params[:id])
107 115 end
108 116
109 117 def update
110 118 @user = User.find(params[:id])
111 119 if @user.update_attributes(params[:user])
112 120 flash[:notice] = 'User was successfully updated.'
113 121 redirect_to :action => 'show', :id => @user
114 122 else
115 123 render :action => 'edit'
116 124 end
117 125 end
118 126
119 127 def destroy
120 128 User.find(params[:id]).destroy
121 129 redirect_to :action => 'list'
122 130 end
123 131
124 132 def user_stat
125 - @problems = Problem.find_available_problems
133 + if params[:commit] == 'download csv'
134 + @problems = Problem.all
135 + else
136 + @problems = Problem.find_available_problems
137 + end
126 138 @users = User.find(:all, :include => [:contests, :contest_stat])
127 139 @scorearray = Array.new
128 140 @users.each do |u|
129 141 ustat = Array.new
130 142 ustat[0] = u
131 143 @problems.each do |p|
132 144 sub = Submission.find_last_by_user_and_problem(u.id,p.id)
133 145 if (sub!=nil) and (sub.points!=nil)
134 146 ustat << [(sub.points.to_f*100/p.full_score).round, (sub.points>=p.full_score)]
135 147 else
136 148 ustat << [0,false]
137 149 end
138 150 end
139 151 @scorearray << ustat
140 152 end
141 153 end
142 154
143 155 def user_stat_max
144 - @problems = Problem.find_available_problems
156 + if params[:commit] == 'download csv'
157 + @problems = Problem.all
158 + else
159 + @problems = Problem.find_available_problems
160 + end
145 161 @users = User.find(:all, :include => [:contests, :contest_stat])
146 162 @scorearray = Array.new
147 163 #set up range from param
148 164 since_id = params.fetch(:since_id, 0).to_i
149 165 until_id = params.fetch(:until_id, 0).to_i
150 166 @users.each do |u|
151 167 ustat = Array.new
152 168 ustat[0] = u
153 169 @problems.each do |p|
154 170 max_points = 0
155 171 Submission.find_in_range_by_user_and_problem(u.id,p.id,since_id,until_id).each do |sub|
156 172 max_points = sub.points if sub and sub.points and (sub.points > max_points)
157 173 end
158 174 ustat << [(max_points.to_f*100/p.full_score).round, (max_points>=p.full_score)]
159 175 end
160 176 @scorearray << ustat
161 177 end
178 +
179 + if params[:commit] == 'download csv' then
180 + csv = gen_csv_from_scorearray(@scorearray,@problems)
181 + send_data csv, filename: 'max_score.csv'
182 + else
183 + render template: 'user_admin/user_stat'
184 + end
162 185 end
163 186
164 187 def import
165 188 if params[:file]==''
166 189 flash[:notice] = 'Error importing no file'
167 190 redirect_to :action => 'list' and return
168 191 end
169 192 import_from_file(params[:file])
170 193 end
171 194
172 195 def random_all_passwords
173 196 users = User.find(:all)
174 197 @prefix = params[:prefix] || ''
175 198 @non_admin_users = User.find_non_admin_with_prefix(@prefix)
176 199 @changed = false
177 200 if request.request_method == 'POST'
178 201 @non_admin_users.each do |user|
179 202 password = random_password
180 203 user.password = password
181 204 user.password_confirmation = password
182 205 user.save
183 206 end
184 207 @changed = true
185 208 end
186 209 end
187 210
188 211 # contest management
189 212
190 213 def contests
191 214 @contest, @users = find_contest_and_user_from_contest_id(params[:id])
192 215 @contests = Contest.enabled
193 216 end
194 217
195 218 def assign_from_list
196 219 contest_id = params[:users_contest_id]
197 220 org_contest, users = find_contest_and_user_from_contest_id(contest_id)
198 221 contest = Contest.find(params[:new_contest][:id])
199 222 if !contest
200 223 flash[:notice] = 'Error: no contest'
201 224 redirect_to :action => 'contests', :id =>contest_id
202 225 end
203 226
204 227 note = []
205 228 users.each do |u|
206 229 u.contests = [contest]
207 230 note << u.login
208 231 end
209 232 flash[:notice] = 'User(s) ' + note.join(', ') +
210 233 " were successfully reassigned to #{contest.title}."
211 234 redirect_to :action => 'contests', :id =>contest.id
212 235 end
213 236
214 237 def add_to_contest
215 238 user = User.find(params[:id])
216 239 contest = Contest.find(params[:contest_id])
217 240 if user and contest
218 241 user.contests << contest
219 242 end
220 243 redirect_to :action => 'list'
221 244 end
222 245
223 246 def remove_from_contest
224 247 user = User.find(params[:id])
225 248 contest = Contest.find(params[:contest_id])
226 249 if user and contest
227 250 user.contests.delete(contest)
228 251 end
229 252 redirect_to :action => 'list'
230 253 end
231 254
232 255 def contest_management
233 256 end
234 257
235 258 def manage_contest
236 259 contest = Contest.find(params[:contest][:id])
237 260 if !contest
238 261 flash[:notice] = 'You did not choose the contest.'
239 262 redirect_to :action => 'contest_management' and return
240 263 end
241 264
242 265 operation = params[:operation]
243 266
244 267 if not ['add','remove','assign'].include? operation
245 268 flash[:notice] = 'You did not choose the operation to perform.'
246 269 redirect_to :action => 'contest_management' and return
247 270 end
248 271
249 272 lines = params[:login_list]
250 273 if !lines or lines.blank?
251 274 flash[:notice] = 'You entered an empty list.'
252 275 redirect_to :action => 'contest_management' and return
253 276 end
254 277
255 278 note = []
256 279 users = []
257 280 lines.split("\n").each do |line|
258 281 user = User.find_by_login(line.chomp)
259 282 if user
260 283 if operation=='add'
261 284 if ! user.contests.include? contest
262 285 user.contests << contest
263 286 end
264 287 elsif operation=='remove'
265 288 user.contests.delete(contest)
266 289 else
267 290 user.contests = [contest]
268 291 end
269 292
270 293 if params[:reset_timer]
271 294 user.contest_stat.forced_logout = true
272 295 user.contest_stat.reset_timer_and_save
273 296 end
274 297
275 298 if params[:notification_emails]
276 299 send_contest_update_notification_email(user, contest)
277 300 end
278 301
279 302 note << user.login
280 303 users << user
281 304 end
282 305 end
283 306
284 307 if params[:reset_timer]
285 308 logout_users(users)
286 309 end
287 310
288 311 flash[:notice] = 'User(s) ' + note.join(', ') +
289 312 ' were successfully modified. '
290 313 redirect_to :action => 'contest_management'
291 314 end
292 315
293 316 # admin management
294 317
295 318 def admin
296 319 @admins = User.find(:all).find_all {|user| user.admin? }
297 320 end
298 321
299 322 def grant_admin
300 323 login = params[:login]
301 324 user = User.find_by_login(login)
302 325 if user!=nil
303 326 admin_role = Role.find_by_name('admin')
304 327 user.roles << admin_role
305 328 else
306 329 flash[:notice] = 'Unknown user'
307 330 end
308 331 flash[:notice] = 'User added as admins'
309 332 redirect_to :action => 'admin'
310 333 end
311 334
312 335 def revoke_admin
313 336 user = User.find(params[:id])
314 337 if user==nil
315 338 flash[:notice] = 'Unknown user'
316 339 redirect_to :action => 'admin' and return
317 340 elsif user.login == 'root'
318 341 flash[:notice] = 'You cannot revoke admisnistrator permission from root.'
319 342 redirect_to :action => 'admin' and return
320 343 end
321 344
322 345 admin_role = Role.find_by_name('admin')
323 346 user.roles.delete(admin_role)
324 347 flash[:notice] = 'User permission revoked'
325 348 redirect_to :action => 'admin'
326 349 end
327 350
328 351 # mass mailing
329 352
330 353 def mass_mailing
331 354 end
332 355
333 356 def bulk_mail
334 357 lines = params[:login_list]
335 358 if !lines or lines.blank?
336 359 flash[:notice] = 'You entered an empty list.'
337 360 redirect_to :action => 'mass_mailing' and return
338 361 end
339 362
340 363 mail_subject = params[:subject]
341 364 if !mail_subject or mail_subject.blank?
342 365 flash[:notice] = 'You entered an empty mail subject.'
343 366 redirect_to :action => 'mass_mailing' and return
344 367 end
345 368
346 369 mail_body = params[:email_body]
347 370 if !mail_body or mail_body.blank?
348 371 flash[:notice] = 'You entered an empty mail body.'
349 372 redirect_to :action => 'mass_mailing' and return
350 373 end
351 374
352 375 note = []
353 376 users = []
354 377 lines.split("\n").each do |line|
355 378 user = User.find_by_login(line.chomp)
356 379 if user
357 380 send_mail(user.email, mail_subject, mail_body)
358 381 note << user.login
359 382 end
360 383 end
361 384
362 385 flash[:notice] = 'User(s) ' + note.join(', ') +
363 386 ' were successfully modified. '
364 387 redirect_to :action => 'mass_mailing'
365 388 end
366 389
367 390 protected
368 391
369 392 def random_password(length=5)
370 393 chars = 'abcdefghijkmnopqrstuvwxyz23456789'
371 394 newpass = ""
372 395 length.times { newpass << chars[rand(chars.size-1)] }
373 396 return newpass
374 397 end
375 398
376 399 def import_from_file(f)
377 400 data_hash = YAML.load(f)
378 401 @import_log = ""
379 402
380 403 country_data = data_hash[:countries]
381 404 site_data = data_hash[:sites]
382 405 user_data = data_hash[:users]
383 406
384 407 # import country
385 408 countries = {}
386 409 country_data.each_pair do |id,country|
387 410 c = Country.find_by_name(country[:name])
388 411 if c!=nil
389 412 countries[id] = c
390 413 @import_log << "Found #{country[:name]}\n"
391 414 else
392 415 countries[id] = Country.new(:name => country[:name])
393 416 countries[id].save
394 417 @import_log << "Created #{country[:name]}\n"
395 418 end
396 419 end
397 420
398 421 # import sites
399 422 sites = {}
400 423 site_data.each_pair do |id,site|
401 424 s = Site.find_by_name(site[:name])
402 425 if s!=nil
403 426 @import_log << "Found #{site[:name]}\n"
404 427 else
405 428 s = Site.new(:name => site[:name])
406 429 @import_log << "Created #{site[:name]}\n"
407 430 end
408 431 s.password = site[:password]
409 432 s.country = countries[site[:country_id]]
410 433 s.save
411 434 sites[id] = s
412 435 end
413 436
414 437 # import users
415 438 user_data.each_pair do |id,user|
416 439 u = User.find_by_login(user[:login])
417 440 if u!=nil
418 441 @import_log << "Found #{user[:login]}\n"
419 442 else
420 443 u = User.new(:login => user[:login])
421 444 @import_log << "Created #{user[:login]}\n"
422 445 end
423 446 u.full_name = user[:name]
424 447 u.password = user[:password]
425 448 u.country = countries[user[:country_id]]
426 449 u.site = sites[user[:site_id]]
427 450 u.activated = true
428 451 u.email = "empty-#{u.login}@none.com"
429 452 if not u.save
430 453 @import_log << "Errors\n"
431 454 u.errors.each { |attr,msg| @import_log << "#{attr} - #{msg}\n" }
432 455 end
433 456 end
434 457
435 458 end
436 459
437 460 def logout_users(users)
438 461 users.each do |user|
439 462 contest_stat = user.contest_stat(true)
440 463 if contest_stat and !contest_stat.forced_logout
441 464 contest_stat.forced_logout = true
442 465 contest_stat.save
443 466 end
444 467 end
445 468 end
446 469
447 470 def send_contest_update_notification_email(user, contest)
448 471 contest_title_name = GraderConfiguration['contest.name']
449 472 contest_name = contest.name
450 473 mail_subject = t('contest.notification.email_subject', {
451 474 :contest_title_name => contest_title_name,
452 475 :contest_name => contest_name })
453 476 mail_body = t('contest.notification.email_body', {
454 477 :full_name => user.full_name,
455 478 :contest_title_name => contest_title_name,
456 479 :contest_name => contest.name,
457 480 })
458 481
459 482 logger.info mail_body
460 483 send_mail(user.email, mail_subject, mail_body)
461 484 end
462 485
463 486 def find_contest_and_user_from_contest_id(id)
464 487 if id!='none'
465 488 @contest = Contest.find(id)
466 489 else
467 490 @contest = nil
468 491 end
469 492 if @contest
470 493 @users = @contest.users
471 494 else
472 495 @users = User.find_users_with_no_contest
473 496 end
474 497 return [@contest, @users]
475 498 end
499 +
500 + def gen_csv_from_scorearray(scorearray,problem)
501 + CSV.generate do |csv|
502 + #add header
503 + header = ['User','Name', 'Activated?', 'Logged in', 'Contest']
504 + problem.each { |p| header << p.name }
505 + header += ['Total','Passed']
506 + csv << header
507 + #add data
508 + scorearray.each do |sc|
509 + total = num_passed = 0
510 + row = Array.new
511 + sc.each_index do |i|
512 + if i == 0
513 + row << sc[i].login
514 + row << sc[i].full_name
515 + row << sc[i].activated
516 + row << (sc[i].try(:contest_stat).try(:started_at)!=nil ? 'yes' : 'no')
517 + row << sc[i].contests.collect {|c| c.name}.join(', ')
518 + else
519 + row << sc[i][0]
520 + total += sc[i][0]
521 + num_passed += 1 if sc[i][1]
522 + end
523 + end
524 + row << total
525 + row << num_passed
526 + csv << row
527 + end
528 + end
529 + end
476 530 end
@@ -1,156 +1,195
1 1 require 'net/smtp'
2 2
3 3 class UsersController < ApplicationController
4 4
5 5 include MailHelperMethods
6 6
7 7 before_filter :authenticate, :except => [:new,
8 8 :register,
9 9 :confirm,
10 10 :forget,
11 11 :retrieve_password]
12 12
13 13 before_filter :verify_online_registration, :only => [:new,
14 14 :register,
15 15 :forget,
16 16 :retrieve_password]
17 + before_filter :authenticate, :profile_authorization, only: [:profile]
17 18
18 19 verify :method => :post, :only => [:chg_passwd],
19 20 :redirect_to => { :action => :index }
20 21
21 22 #in_place_edit_for :user, :alias_for_editing
22 23 #in_place_edit_for :user, :email_for_editing
23 24
24 25 def index
25 26 if !GraderConfiguration['system.user_setting_enabled']
26 27 redirect_to :controller => 'main', :action => 'list'
27 28 else
28 29 @user = User.find(session[:user_id])
29 30 end
30 31 end
31 32
32 33 def chg_passwd
33 34 user = User.find(session[:user_id])
34 35 user.password = params[:passwd]
35 36 user.password_confirmation = params[:passwd_verify]
36 37 if user.save
37 38 flash[:notice] = 'password changed'
38 39 else
39 40 flash[:notice] = 'Error: password changing failed'
40 41 end
41 42 redirect_to :action => 'index'
42 43 end
43 44
44 45 def new
45 46 @user = User.new
46 47 render :action => 'new', :layout => 'empty'
47 48 end
48 49
49 50 def register
50 51 if(params[:cancel])
51 52 redirect_to :controller => 'main', :action => 'login'
52 53 return
53 54 end
54 55 @user = User.new(params[:user])
55 56 @user.password_confirmation = @user.password = User.random_password
56 57 @user.activated = false
57 58 if (@user.valid?) and (@user.save)
58 59 if send_confirmation_email(@user)
59 60 render :action => 'new_splash', :layout => 'empty'
60 61 else
61 62 @admin_email = GraderConfiguration['system.admin_email']
62 63 render :action => 'email_error', :layout => 'empty'
63 64 end
64 65 else
65 66 @user.errors.add(:base,"Email cannot be blank") if @user.email==''
66 67 render :action => 'new', :layout => 'empty'
67 68 end
68 69 end
69 70
70 71 def confirm
71 72 login = params[:login]
72 73 key = params[:activation]
73 74 @user = User.find_by_login(login)
74 75 if (@user) and (@user.verify_activation_key(key))
75 76 if @user.valid? # check uniquenss of email
76 77 @user.activated = true
77 78 @user.save
78 79 @result = :successful
79 80 else
80 81 @result = :email_used
81 82 end
82 83 else
83 84 @result = :failed
84 85 end
85 86 render :action => 'confirm', :layout => 'empty'
86 87 end
87 88
88 89 def forget
89 90 render :action => 'forget', :layout => 'empty'
90 91 end
91 92
92 93 def retrieve_password
93 94 email = params[:email]
94 95 user = User.find_by_email(email)
95 96 if user
96 97 last_updated_time = user.updated_at || user.created_at || (Time.now.gmtime - 1.hour)
97 98 if last_updated_time > Time.now.gmtime - 5.minutes
98 99 flash[:notice] = 'The account has recently created or new password has recently been requested. Please wait for 5 minutes'
99 100 else
100 101 user.password = user.password_confirmation = User.random_password
101 102 user.save
102 103 send_new_password_email(user)
103 104 flash[:notice] = 'New password has been mailed to you.'
104 105 end
105 106 else
106 107 flash[:notice] = I18n.t 'registration.password_retrieval.no_email'
107 108 end
108 109 redirect_to :action => 'forget'
109 110 end
110 111
112 + def profile
113 + @user = User.find(params[:id])
114 + @submission = Submission.includes(:problem).where(user_id: params[:id])
115 +
116 + range = 120
117 + @histogram = { data: Array.new(range,0), summary: {} }
118 + @summary = {count: 0, solve: 0, attempt: 0}
119 + problem = Hash.new(0)
120 +
121 + @submission.find_each do |sub|
122 + #histogram
123 + d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60
124 + @histogram[:data][d.to_i] += 1 if d < range
125 +
126 + @summary[:count] += 1
127 + next unless sub.problem
128 + problem[sub.problem] = [problem[sub.problem], (sub.points >= sub.problem.full_score) ? 1 : 0].max
129 + end
130 +
131 + @histogram[:summary][:max] = [@histogram[:data].max,1].max
132 + @summary[:attempt] = problem.count
133 + problem.each_value { |v| @summary[:solve] += 1 if v == 1 }
134 + end
135 +
111 136 protected
112 137
113 138 def verify_online_registration
114 139 if !GraderConfiguration['system.online_registration']
115 140 redirect_to :controller => 'main', :action => 'login'
116 141 end
117 142 end
118 143
119 144 def send_confirmation_email(user)
120 145 contest_name = GraderConfiguration['contest.name']
121 146 activation_url = url_for(:action => 'confirm',
122 147 :login => user.login,
123 148 :activation => user.activation_key)
124 149 home_url = url_for(:controller => 'main', :action => 'index')
125 150 mail_subject = "[#{contest_name}] Confirmation"
126 151 mail_body = t('registration.email_body', {
127 152 :full_name => user.full_name,
128 153 :contest_name => contest_name,
129 154 :login => user.login,
130 155 :password => user.password,
131 156 :activation_url => activation_url,
132 157 :admin_email => GraderConfiguration['system.admin_email']
133 158 })
134 159
135 160 logger.info mail_body
136 161
137 162 send_mail(user.email, mail_subject, mail_body)
138 163 end
139 164
140 165 def send_new_password_email(user)
141 166 contest_name = GraderConfiguration['contest.name']
142 167 mail_subject = "[#{contest_name}] Password recovery"
143 168 mail_body = t('registration.password_retrieval.email_body', {
144 169 :full_name => user.full_name,
145 170 :contest_name => contest_name,
146 171 :login => user.login,
147 172 :password => user.password,
148 173 :admin_email => GraderConfiguration['system.admin_email']
149 174 })
150 175
151 176 logger.info mail_body
152 177
153 178 send_mail(user.email, mail_subject, mail_body)
154 179 end
180 +
181 + # allow viewing of regular user profile only when options allow so
182 + # only admins can view admins profile
183 + def profile_authorization
184 + #if view admins' profile, allow only admin
185 + return false unless(params[:id])
186 + user = User.find(params[:id])
187 + return false unless user
188 + return admin_authorization if user.admin?
189 + return true if GraderConfiguration["right.user_view_submission"]
190 +
191 + #finally, we allow only admin
192 + admin_authorization
193 + end
155 194
156 195 end
@@ -1,133 +1,138
1 1 # Methods added to this helper will be available to all templates in the application.
2 2 module ApplicationHelper
3 3
4 4 def user_header
5 5 menu_items = ''
6 6 user = User.find(session[:user_id])
7 7
8 8 if (user!=nil) and (session[:admin])
9 9 # admin menu
10 10 menu_items << "<b>Administrative task:</b> "
11 11 append_to menu_items, '[Announcements]', 'announcements', 'index'
12 12 append_to menu_items, '[Msg console]', 'messages', 'console'
13 13 append_to menu_items, '[Problems]', 'problems', 'index'
14 14 append_to menu_items, '[Users]', 'user_admin', 'index'
15 15 append_to menu_items, '[Results]', 'user_admin', 'user_stat'
16 + append_to menu_items, '[Report]', 'report', 'login_stat'
16 17 append_to menu_items, '[Graders]', 'graders', 'list'
17 18 append_to menu_items, '[Contests]', 'contest_management', 'index'
18 19 append_to menu_items, '[Sites]', 'sites', 'index'
19 20 append_to menu_items, '[System config]', 'configurations', 'index'
20 21 menu_items << "<br/>"
21 22 end
22 23
23 24 # main page
24 25 append_to menu_items, "[#{I18n.t 'menu.main'}]", 'main', 'list'
25 26 append_to menu_items, "[#{I18n.t 'menu.messages'}]", 'messages', 'list'
26 27
27 28 if (user!=nil) and (GraderConfiguration.show_tasks_to?(user))
28 29 append_to menu_items, "[#{I18n.t 'menu.tasks'}]", 'tasks', 'list'
29 30 append_to menu_items, "[#{I18n.t 'menu.submissions'}]", 'main', 'submission'
30 31 append_to menu_items, "[#{I18n.t 'menu.test'}]", 'test', 'index'
31 32 end
33 +
34 + if GraderConfiguration['right.user_hall_of_fame']
35 + append_to menu_items, "[#{I18n.t 'menu.hall_of_fame'}]", 'report', 'problem_hof'
36 + end
32 37 append_to menu_items, "[#{I18n.t 'menu.help'}]", 'main', 'help'
33 38
34 39 if GraderConfiguration['system.user_setting_enabled']
35 40 append_to menu_items, "[#{I18n.t 'menu.settings'}]", 'users', 'index'
36 41 end
37 42 append_to menu_items, "[#{I18n.t 'menu.log_out'}]", 'main', 'login'
38 43
39 44 menu_items.html_safe
40 45 end
41 46
42 47 def append_to(option,label, controller, action)
43 48 option << ' ' if option!=''
44 49 option << link_to_unless_current(label,
45 50 :controller => controller,
46 51 :action => action)
47 52 end
48 53
49 54 def format_short_time(time)
50 55 now = Time.now.gmtime
51 56 st = ''
52 57 if (time.yday != now.yday) or
53 58 (time.year != now.year)
54 59 st = time.strftime("%x ")
55 60 end
56 61 st + time.strftime("%X")
57 62 end
58 63
59 64 def format_short_duration(duration)
60 65 return '' if duration==nil
61 66 d = duration.to_f
62 67 return Time.at(d).gmtime.strftime("%X")
63 68 end
64 69
65 70 def read_textfile(fname,max_size=2048)
66 71 begin
67 72 File.open(fname).read(max_size)
68 73 rescue
69 74 nil
70 75 end
71 76 end
72 77
73 78 def user_title_bar(user)
74 79 header = ''
75 80 time_left = ''
76 81
77 82 #
78 83 # if the contest is over
79 84 if GraderConfiguration.time_limit_mode?
80 85 if user.contest_finished?
81 86 header = <<CONTEST_OVER
82 87 <tr><td colspan="2" align="center">
83 88 <span class="contest-over-msg">THE CONTEST IS OVER</span>
84 89 </td></tr>
85 90 CONTEST_OVER
86 91 end
87 92 if !user.contest_started?
88 93 time_left = "&nbsp;&nbsp;" + (t 'title_bar.contest_not_started')
89 94 else
90 95 time_left = "&nbsp;&nbsp;" + (t 'title_bar.remaining_time') +
91 96 " #{format_short_duration(user.contest_time_left)}"
92 97 end
93 98 end
94 99
95 100 #
96 101 # if the contest is in the anaysis mode
97 102 if GraderConfiguration.analysis_mode?
98 103 header = <<ANALYSISMODE
99 104 <tr><td colspan="2" align="center">
100 105 <span class="contest-over-msg">ANALYSIS MODE</span>
101 106 </td></tr>
102 107 ANALYSISMODE
103 108 end
104 109
105 110 contest_name = GraderConfiguration['contest.name']
106 111
107 112 #
108 113 # build real title bar
109 114 result = <<TITLEBAR
110 115 <div class="title">
111 116 <table>
112 117 #{header}
113 118 <tr>
114 119 <td class="left-col">
115 120 #{user.full_name}<br/>
116 121 #{t 'title_bar.current_time'} #{format_short_time(Time.zone.now)}
117 122 #{time_left}
118 123 <br/>
119 124 </td>
120 125 <td class="right-col">#{contest_name}</td>
121 126 </tr>
122 127 </table>
123 128 </div>
124 129 TITLEBAR
125 130 result.html_safe
126 131 end
127 132
128 133 def markdown(text)
129 134 markdown = RDiscount.new(text)
130 135 markdown.to_html.html_safe
131 136 end
132 137
133 138 end
@@ -1,60 +1,60
1 1 class Message < ActiveRecord::Base
2 2
3 3 belongs_to :sender, :class_name => "User"
4 4 belongs_to :receiver, :class_name => "User"
5 5
6 6 belongs_to :replying_message, :class_name => "Message"
7 7
8 8 # commented manually do it
9 9 #
10 10 #has_many :replied_messages, {
11 11 # :class_name => "Message",
12 12 # :foreign_key => "replying_message_id"
13 13 #}
14 14 #
15 15
16 16 attr_accessor :replied_messages
17 17
18 18 def self.find_all_sent_by_user(user)
19 19 messages = user.messages
20 20 replied_messages = user.replied_messages
21 21 Message.build_replying_message_hierarchy messages, replied_messages
22 22 return messages
23 23 end
24 -
24 +
25 25 def self.find_all_system_unreplied_messages
26 26 self.find(:all,
27 27 :conditions => 'ISNULL(receiver_id) ' +
28 28 'AND (ISNULL(replied) OR replied=0)',
29 29 :order => 'created_at')
30 30 end
31 31
32 32 def self.build_replying_message_hierarchy(*args)
33 33 # manually build replies hierarchy (to improve efficiency)
34 34 all_messages = {}
35 35
36 36 args.each do |collection|
37 37 collection.each do |m|
38 38 all_messages[m.id] = m
39 39 m.replied_messages = []
40 40 end
41 41 end
42 42
43 43 all_messages.each_value do |m|
44 44 rep_id = m.replying_message_id
45 45 if all_messages[rep_id]!=nil
46 46 all_messages[rep_id].add_replied_message(m)
47 47 end
48 48 end
49 49 end
50 50
51 51 def add_replied_message(m)
52 52 if @replied_messages==nil
53 53 @replied_messages = [m]
54 54 else
55 55 @replied_messages << m
56 56 end
57 57 @replied_messages
58 58 end
59 59
60 60 end
@@ -1,114 +1,128
1 1 class Problem < ActiveRecord::Base
2 2
3 3 belongs_to :description
4 4 has_and_belongs_to_many :contests, :uniq => true
5 5 has_many :test_pairs, :dependent => :delete_all
6 6
7 7 validates_presence_of :name
8 8 validates_format_of :name, :with => /^\w+$/
9 9 validates_presence_of :full_name
10 10
11 11 scope :available, :conditions => {:available => true}
12 12
13 13 DEFAULT_TIME_LIMIT = 1
14 14 DEFAULT_MEMORY_LIMIT = 32
15 15
16 16 def self.find_available_problems
17 - Problem.available.all(:order => "date_added DESC")
17 + Problem.available.all(:order => "date_added DESC, name ASC")
18 18 end
19 19
20 20 def self.create_from_import_form_params(params, old_problem=nil)
21 21 org_problem = old_problem || Problem.new
22 22 import_params, problem = Problem.extract_params_and_check(params,
23 23 org_problem)
24 24
25 25 if !problem.errors.empty?
26 26 return problem, 'Error importing'
27 27 end
28 28
29 29 problem.full_score = 100
30 30 problem.date_added = Time.new
31 31 problem.test_allowed = true
32 32 problem.output_only = false
33 33 problem.available = false
34 34
35 35 if not problem.save
36 36 return problem, 'Error importing'
37 37 end
38 38
39 39 import_to_db = params.has_key? :import_to_db
40 40
41 41 importer = TestdataImporter.new(problem)
42 42
43 43 if not importer.import_from_file(import_params[:file],
44 44 import_params[:time_limit],
45 45 import_params[:memory_limit],
46 + import_params[:checker_name],
46 47 import_to_db)
47 48 problem.errors.add(:base,'Import error.')
48 49 end
49 50
50 51 return problem, importer.log_msg
51 52 end
52 53
53 54 def self.download_file_basedir
54 55 return "#{Rails.root}/data/tasks"
55 56 end
57 +
58 + def get_submission_stat
59 + result = Hash.new
60 + #total number of submission
61 + result[:total_sub] = Submission.where(problem_id: self.id).count
62 + result[:attempted_user] = Submission.where(problem_id: self.id).group_by(:user_id)
63 + end
56 64
57 65 protected
58 66
59 67 def self.to_i_or_default(st, default)
60 68 if st!=''
61 69 result = st.to_i
62 70 end
63 71 result ||= default
64 72 end
65 73
66 74 def self.to_f_or_default(st, default)
67 75 if st!=''
68 76 result = st.to_f
69 77 end
70 78 result ||= default
71 79 end
72 80
73 81 def self.extract_params_and_check(params, problem)
74 82 time_limit = Problem.to_f_or_default(params[:time_limit],
75 83 DEFAULT_TIME_LIMIT)
76 84 memory_limit = Problem.to_i_or_default(params[:memory_limit],
77 85 DEFAULT_MEMORY_LIMIT)
78 86
79 87 if time_limit<=0 or time_limit >60
80 88 problem.errors.add(:base,'Time limit out of range.')
81 89 end
82 90
83 91 if memory_limit==0 and params[:memory_limit]!='0'
84 92 problem.errors.add(:base,'Memory limit format errors.')
85 93 elsif memory_limit<=0 or memory_limit >512
86 94 problem.errors.add(:base,'Memory limit out of range.')
87 95 end
88 96
89 97 if params[:file]==nil or params[:file]==''
90 98 problem.errors.add(:base,'No testdata file.')
91 99 end
92 100
101 + checker_name = 'text'
102 + if ['text','float'].include? params[:checker]
103 + checker_name = params[:checker]
104 + end
105 +
93 106 file = params[:file]
94 107
95 108 if !problem.errors.empty?
96 109 return nil, problem
97 110 end
98 111
99 112 problem.name = params[:name]
100 113 if params[:full_name]!=''
101 114 problem.full_name = params[:full_name]
102 115 else
103 116 problem.full_name = params[:name]
104 117 end
105 118
106 119 return [{
107 120 :time_limit => time_limit,
108 121 :memory_limit => memory_limit,
109 - :file => file
122 + :file => file,
123 + :checker_name => checker_name
110 124 },
111 125 problem]
112 126 end
113 127
114 128 end
@@ -1,173 +1,173
1 1 class Submission < ActiveRecord::Base
2 2
3 3 belongs_to :language
4 4 belongs_to :problem
5 5 belongs_to :user
6 6
7 7 before_validation :assign_problem
8 8 before_validation :assign_language
9 9
10 10 validates_presence_of :source
11 11 validates_length_of :source, :maximum => 100_000, :allow_blank => true, :message => 'too long'
12 12 validates_length_of :source, :minimum => 1, :allow_blank => true, :message => 'too short'
13 13 validate :must_have_valid_problem
14 14 validate :must_specify_language
15 15
16 16 before_save :assign_latest_number_if_new_recond
17 17
18 18 def self.find_last_by_user_and_problem(user_id, problem_id)
19 19 last_sub = find(:first,
20 20 :conditions => {:user_id => user_id,
21 21 :problem_id => problem_id},
22 22 :order => 'number DESC')
23 23 return last_sub
24 24 end
25 25
26 26 def self.find_all_last_by_problem(problem_id)
27 27 # need to put in SQL command, maybe there's a better way
28 - Submission.find_by_sql("SELECT * FROM submissions " +
28 + Submission.includes(:user).find_by_sql("SELECT * FROM submissions " +
29 29 "WHERE id = " +
30 30 "(SELECT MAX(id) FROM submissions AS subs " +
31 31 "WHERE subs.user_id = submissions.user_id AND " +
32 32 "problem_id = " + problem_id.to_s + " " +
33 33 "GROUP BY user_id) " +
34 34 "ORDER BY user_id")
35 35 end
36 36
37 37 def self.find_in_range_by_user_and_problem(user_id, problem_id,since_id,until_id)
38 38 records = Submission.where(problem_id: problem_id,user_id: user_id)
39 39 records = records.where('id >= ?',since_id) if since_id > 0
40 40 records = records.where('id <= ?',until_id) if until_id > 0
41 41 records.all
42 42 end
43 43
44 44 def self.find_last_for_all_available_problems(user_id)
45 45 submissions = Array.new
46 46 problems = Problem.find_available_problems
47 47 problems.each do |problem|
48 48 sub = Submission.find_last_by_user_and_problem(user_id, problem.id)
49 49 submissions << sub if sub!=nil
50 50 end
51 51 submissions
52 52 end
53 53
54 54 def self.find_by_user_problem_number(user_id, problem_id, number)
55 55 Submission.find(:first,
56 56 :conditions => {
57 57 :user_id => user_id,
58 58 :problem_id => problem_id,
59 59 :number => number
60 60 })
61 61 end
62 62
63 63 def self.find_all_by_user_problem(user_id, problem_id)
64 64 Submission.find(:all,
65 65 :conditions => {
66 66 :user_id => user_id,
67 67 :problem_id => problem_id,
68 68 })
69 69 end
70 70
71 71 def download_filename
72 72 if self.problem.output_only
73 73 return self.source_filename
74 74 else
75 75 timestamp = self.submitted_at.localtime.strftime("%H%M%S")
76 76 return "#{self.problem.name}-#{timestamp}.#{self.language.ext}"
77 77 end
78 78 end
79 79
80 80 protected
81 81
82 82 def self.find_option_in_source(option, source)
83 83 if source==nil
84 84 return nil
85 85 end
86 86 i = 0
87 87 source.each_line do |s|
88 88 if s =~ option
89 89 words = s.split
90 90 return words[1]
91 91 end
92 92 i = i + 1
93 93 if i==10
94 94 return nil
95 95 end
96 96 end
97 97 return nil
98 98 end
99 99
100 100 def self.find_language_in_source(source, source_filename="")
101 101 langopt = find_option_in_source(/^LANG:/,source)
102 102 if langopt
103 103 return (Language.find_by_name(langopt) ||
104 104 Language.find_by_pretty_name(langopt))
105 105 else
106 106 if source_filename
107 107 return Language.find_by_extension(source_filename.split('.').last)
108 108 else
109 109 return nil
110 110 end
111 111 end
112 112 end
113 113
114 114 def self.find_problem_in_source(source, source_filename="")
115 115 prob_opt = find_option_in_source(/^TASK:/,source)
116 116 if problem = Problem.find_by_name(prob_opt)
117 117 return problem
118 118 else
119 119 if source_filename
120 120 return Problem.find_by_name(source_filename.split('.').first)
121 121 else
122 122 return nil
123 123 end
124 124 end
125 125 end
126 126
127 127 def assign_problem
128 128 if self.problem_id!=-1
129 129 begin
130 130 self.problem = Problem.find(self.problem_id)
131 131 rescue ActiveRecord::RecordNotFound
132 132 self.problem = nil
133 133 end
134 134 else
135 135 self.problem = Submission.find_problem_in_source(self.source,
136 136 self.source_filename)
137 137 end
138 138 end
139 139
140 140 def assign_language
141 141 self.language = Submission.find_language_in_source(self.source,
142 142 self.source_filename)
143 143 end
144 144
145 145 # validation codes
146 146 def must_specify_language
147 147 return if self.source==nil
148 148
149 149 # for output_only tasks
150 150 return if self.problem!=nil and self.problem.output_only
151 151
152 152 if self.language==nil
153 153 errors.add('source',"must specify programming language") unless self.language!=nil
154 154 end
155 155 end
156 156
157 157 def must_have_valid_problem
158 158 return if self.source==nil
159 159 if self.problem==nil
160 160 errors.add('problem',"must be specified.")
161 161 elsif (!self.problem.available) and (self.new_record?)
162 162 errors.add('problem',"must be valid.")
163 163 end
164 164 end
165 165
166 166 # callbacks
167 167 def assign_latest_number_if_new_recond
168 168 return if !self.new_record?
169 169 latest = Submission.find_last_by_user_and_problem(self.user_id, self.problem_id)
170 170 self.number = (latest==nil) ? 1 : latest.number + 1;
171 171 end
172 172
173 173 end
@@ -1,323 +1,329
1 1 require 'digest/sha1'
2 + require 'net/pop'
3 + require 'net/https'
4 + require 'net/http'
5 + require 'json'
2 6
3 7 class User < ActiveRecord::Base
4 8
5 9 has_and_belongs_to_many :roles
6 10
7 11 has_many :test_requests, :order => "submitted_at DESC"
8 12
9 13 has_many :messages,
10 14 :class_name => "Message",
11 15 :foreign_key => "sender_id",
12 16 :order => 'created_at DESC'
13 17
14 18 has_many :replied_messages,
15 19 :class_name => "Message",
16 20 :foreign_key => "receiver_id",
17 21 :order => 'created_at DESC'
18 22
19 23 has_one :contest_stat, :class_name => "UserContestStat", :dependent => :destroy
20 24
21 25 belongs_to :site
22 26 belongs_to :country
23 27
24 28 has_and_belongs_to_many :contests, :uniq => true, :order => 'name'
25 29
26 30 scope :activated_users, :conditions => {:activated => true}
27 31
28 32 validates_presence_of :login
29 33 validates_uniqueness_of :login
30 34 validates_format_of :login, :with => /^[\_A-Za-z0-9]+$/
31 35 validates_length_of :login, :within => 3..30
32 36
33 37 validates_presence_of :full_name
34 38 validates_length_of :full_name, :minimum => 1
35 39
36 40 validates_presence_of :password, :if => :password_required?
37 41 validates_length_of :password, :within => 4..20, :if => :password_required?
38 42 validates_confirmation_of :password, :if => :password_required?
39 43
40 44 validates_format_of :email,
41 45 :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i,
42 46 :if => :email_validation?
43 47 validate :uniqueness_of_email_from_activated_users,
44 48 :if => :email_validation?
45 49 validate :enough_time_interval_between_same_email_registrations,
46 50 :if => :email_validation?
47 51
48 52 # these are for ytopc
49 53 # disable for now
50 54 #validates_presence_of :province
51 55
52 56 attr_accessor :password
53 57
54 58 before_save :encrypt_new_password
55 59 before_save :assign_default_site
56 60 before_save :assign_default_contest
57 61
58 62 # this is for will_paginate
59 63 cattr_reader :per_page
60 64 @@per_page = 50
61 65
62 66 def self.authenticate(login, password)
63 67 user = find_by_login(login)
64 - return user if user && user.authenticated?(password)
68 + if user
69 + return user if user.authenticated?(password)
70 + end
65 71 end
66 72
67 73 def authenticated?(password)
68 74 if self.activated
69 75 hashed_password == User.encrypt(password,self.salt)
70 76 else
71 77 false
72 78 end
73 79 end
74 80
75 81 def admin?
76 82 self.roles.detect {|r| r.name == 'admin' }
77 83 end
78 84
79 85 def email_for_editing
80 86 if self.email==nil
81 87 "(unknown)"
82 88 elsif self.email==''
83 89 "(blank)"
84 90 else
85 91 self.email
86 92 end
87 93 end
88 94
89 95 def email_for_editing=(e)
90 96 self.email=e
91 97 end
92 98
93 99 def alias_for_editing
94 100 if self.alias==nil
95 101 "(unknown)"
96 102 elsif self.alias==''
97 103 "(blank)"
98 104 else
99 105 self.alias
100 106 end
101 107 end
102 108
103 109 def alias_for_editing=(e)
104 110 self.alias=e
105 111 end
106 112
107 113 def activation_key
108 114 if self.hashed_password==nil
109 115 encrypt_new_password
110 116 end
111 117 Digest::SHA1.hexdigest(self.hashed_password)[0..7]
112 118 end
113 119
114 120 def verify_activation_key(key)
115 121 key == activation_key
116 122 end
117 123
118 124 def self.random_password(length=5)
119 125 chars = 'abcdefghjkmnopqrstuvwxyz'
120 126 password = ''
121 127 length.times { password << chars[rand(chars.length - 1)] }
122 128 password
123 129 end
124 130
125 131 def self.find_non_admin_with_prefix(prefix='')
126 132 users = User.find(:all)
127 133 return users.find_all { |u| !(u.admin?) and u.login.index(prefix)==0 }
128 134 end
129 135
130 136 # Contest information
131 137
132 138 def self.find_users_with_no_contest()
133 139 users = User.find(:all)
134 140 return users.find_all { |u| u.contests.length == 0 }
135 141 end
136 142
137 143
138 144 def contest_time_left
139 145 if GraderConfiguration.contest_mode?
140 146 return nil if site==nil
141 147 return site.time_left
142 148 elsif GraderConfiguration.indv_contest_mode?
143 149 time_limit = GraderConfiguration.contest_time_limit
144 150 if time_limit == nil
145 151 return nil
146 152 end
147 153 if contest_stat==nil or contest_stat.started_at==nil
148 154 return (Time.now.gmtime + time_limit) - Time.now.gmtime
149 155 else
150 156 finish_time = contest_stat.started_at + time_limit
151 157 current_time = Time.now.gmtime
152 158 if current_time > finish_time
153 159 return 0
154 160 else
155 161 return finish_time - current_time
156 162 end
157 163 end
158 164 else
159 165 return nil
160 166 end
161 167 end
162 168
163 169 def contest_finished?
164 170 if GraderConfiguration.contest_mode?
165 171 return false if site==nil
166 172 return site.finished?
167 173 elsif GraderConfiguration.indv_contest_mode?
168 174 return false if self.contest_stat(true)==nil
169 175 return contest_time_left == 0
170 176 else
171 177 return false
172 178 end
173 179 end
174 180
175 181 def contest_started?
176 182 if GraderConfiguration.indv_contest_mode?
177 183 stat = self.contest_stat
178 184 return ((stat != nil) and (stat.started_at != nil))
179 185 elsif GraderConfiguration.contest_mode?
180 186 return true if site==nil
181 187 return site.started
182 188 else
183 189 return true
184 190 end
185 191 end
186 192
187 193 def update_start_time
188 194 stat = self.contest_stat
189 195 if stat == nil or stat.started_at == nil
190 196 stat ||= UserContestStat.new(:user => self)
191 197 stat.started_at = Time.now.gmtime
192 198 stat.save
193 199 end
194 200 end
195 201
196 202 def problem_in_user_contests?(problem)
197 203 problem_contests = problem.contests.all
198 204
199 205 if problem_contests.length == 0 # this is public contest
200 206 return true
201 207 end
202 208
203 209 contests.each do |contest|
204 210 if problem_contests.find {|c| c.id == contest.id }
205 211 return true
206 212 end
207 213 end
208 214 return false
209 215 end
210 216
211 217 def available_problems_group_by_contests
212 218 contest_problems = []
213 219 pin = {}
214 220 contests.enabled.each do |contest|
215 221 available_problems = contest.problems.available
216 222 contest_problems << {
217 223 :contest => contest,
218 224 :problems => available_problems
219 225 }
220 226 available_problems.each {|p| pin[p.id] = true}
221 227 end
222 228 other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0}
223 229 contest_problems << {
224 230 :contest => nil,
225 231 :problems => other_avaiable_problems
226 232 }
227 233 return contest_problems
228 234 end
229 235
230 236 def available_problems
231 237 if not GraderConfiguration.multicontests?
232 238 return Problem.find_available_problems
233 239 else
234 240 contest_problems = []
235 241 pin = {}
236 242 contests.enabled.each do |contest|
237 243 contest.problems.available.each do |problem|
238 244 if not pin.has_key? problem.id
239 245 contest_problems << problem
240 246 end
241 247 pin[problem.id] = true
242 248 end
243 249 end
244 250 other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0}
245 251 return contest_problems + other_avaiable_problems
246 252 end
247 253 end
248 254
249 255 def can_view_problem?(problem)
250 256 if not GraderConfiguration.multicontests?
251 257 return problem.available
252 258 else
253 259 return problem_in_user_contests? problem
254 260 end
255 261 end
256 262
257 263 protected
258 264 def encrypt_new_password
259 265 return if password.blank?
260 266 self.salt = (10+rand(90)).to_s
261 267 self.hashed_password = User.encrypt(self.password,self.salt)
262 268 end
263 269
264 270 def assign_default_site
265 271 # have to catch error when migrating (because self.site is not available).
266 272 begin
267 273 if self.site==nil
268 274 self.site = Site.find_by_name('default')
269 275 if self.site==nil
270 276 self.site = Site.find(1) # when 'default has be renamed'
271 277 end
272 278 end
273 279 rescue
274 280 end
275 281 end
276 282
277 283 def assign_default_contest
278 284 # have to catch error when migrating (because self.site is not available).
279 285 begin
280 286 if self.contests.length == 0
281 287 default_contest = Contest.find_by_name(GraderConfiguration['contest.default_contest_name'])
282 288 if default_contest
283 289 self.contests = [default_contest]
284 290 end
285 291 end
286 292 rescue
287 293 end
288 294 end
289 295
290 296 def password_required?
291 297 self.hashed_password.blank? || !self.password.blank?
292 298 end
293 299
294 300 def self.encrypt(string,salt)
295 301 Digest::SHA1.hexdigest(salt + string)
296 302 end
297 303
298 304 def uniqueness_of_email_from_activated_users
299 305 user = User.activated_users.find_by_email(self.email)
300 306 if user and (user.login != self.login)
301 307 self.errors.add(:base,"Email has already been taken")
302 308 end
303 309 end
304 310
305 311 def enough_time_interval_between_same_email_registrations
306 312 return if !self.new_record?
307 313 return if self.activated
308 314 open_user = User.find_by_email(self.email,
309 315 :order => 'created_at DESC')
310 316 if open_user and open_user.created_at and
311 317 (open_user.created_at > Time.now.gmtime - 5.minutes)
312 318 self.errors.add(:base,"There are already unactivated registrations with this e-mail address (please wait for 5 minutes)")
313 319 end
314 320 end
315 321
316 322 def email_validation?
317 323 begin
318 324 return VALIDATE_USER_EMAILS
319 325 rescue
320 326 return false
321 327 end
322 328 end
323 329 end
@@ -1,29 +1,32
1 + - content_for :header do
2 + = javascript_include_tag 'local_jquery'
3 +
1 4 %h1 System configuration
2 5
3 6 %table.info
4 7 %tr.info-head
5 8 %th Key
6 9 %th Type
7 10 %th Value
8 11 %th Description
9 12 - @configurations.each do |conf|
10 13 - @grader_configuration = conf
11 14 %tr{:class => cycle("info-odd", "info-even")}
12 15 %td
13 16 = in_place_editor_field :grader_configuration, :key, {}, :rows=>1
14 17 %td
15 18 = in_place_editor_field :grader_configuration, :value_type, {}, :rows=>1
16 19 %td
17 - = in_place_editor_field :grader_configuration, :value, {}, :rows=>1
20 + = best_in_place @grader_configuration, :value, ok_button: "ok", cancel_button: "cancel"
18 21 %td= conf.description
19 22
20 23 - if GraderConfiguration.config_cached?
21 24 %br/
22 25 Your config is saved, but it does not automatically take effect.
23 26 %br/
24 27 If you have one mongrel process running, you can
25 28 = link_to '[click]', :action => 'reload'
26 29 here to reload.
27 30 %br/
28 31 If you have more than one process running, you should restart
29 32 them manually.
@@ -1,51 +1,73
1 1 - content_for :head do
2 2 = stylesheet_link_tag 'graders'
3 + = javascript_include_tag 'local_jquery'
3 4 <meta http-equiv ="refresh" content="60"/>
4 5
5 6 %h1 Grader information
6 7
7 8 = link_to '[Refresh]', :action => 'list'
8 9 %br/
9 10
10 11 .submitbox
11 12 .item
12 13 Grader control:
13 14 .item
14 15 = form_for :clear, :url => {:action => 'start_grading'} do |f|
15 16 = submit_tag 'Start graders in grading env'
16 17 .item
17 18 = form_for :clear, :url => {:action => 'start_exam'} do |f|
18 19 = submit_tag 'Start graders in exam env'
19 20 .item
20 21 = form_for :clear, :url => {:action => 'stop_all'} do |f|
21 22 = submit_tag 'Stop all running graders'
22 23 .item
23 24 = form_for :clear, :url => {:action => 'clear_all'} do |f|
24 25 = submit_tag 'Clear all data'
25 26 %br{:style => 'clear:both'}/
26 27
27 - - if @last_task
28 - Last task:
29 - = link_to "#{@last_task.id}", :action => 'view', :id => @last_task.id, :type => 'Task'
28 + %div{style: 'width:500px; float: left;'}
29 + - if @last_task
30 + Last task:
31 + = link_to "#{@last_task.id}", :action => 'view', :id => @last_task.id, :type => 'Task'
32 +
33 + %br/
34 +
35 + - if @last_test_request
36 + Last test_request:
37 + = link_to "#{@last_test_request.id}", :action => 'view', :id => @last_test_request.id, :type => 'TestRequest'
38 +
39 + %h2 Current graders
40 +
41 + = render :partial => 'grader_list', :locals => {:grader_list => @grader_processes}
42 +
43 + %h2 Stalled graders
44 +
45 + = render :partial => 'grader_list', :locals => {:grader_list => @stalled_processes}
46 +
47 + %h2 Terminated graders
30 48
31 - %br/
49 + = form_for :clear, :url => {:action => 'clear_terminated'} do |f|
50 + = submit_tag 'Clear data for terminated graders'
32 51
33 - - if @last_test_request
34 - Last test_request:
35 - = link_to "#{@last_test_request.id}", :action => 'view', :id => @last_test_request.id, :type => 'TestRequest'
52 + = render :partial => 'grader_list', :locals => {:grader_list => @terminated_processes}
53 + %div{}
54 + %h2 Last 20 submissions
55 + %table.graders
56 + %thead
57 + %th ID
58 + %th User
59 + %th Problem
60 + %th Submitted
61 + %th Graded
62 + %th Result
63 + %tbody
64 + - @submission.each do |sub|
65 + %tr.inactive
66 + %td= link_to sub.id, controller: 'graders' ,action: 'submission', id: sub.id
67 + %td= sub.try(:user).try(:full_name)
68 + %td= sub.try(:problem).try(:full_name)
69 + %td= "#{time_ago_in_words(sub.submitted_at)} ago"
70 + %td= sub.graded_at ? "#{time_ago_in_words(sub.graded_at)} ago" : " "
71 + %td= sub.grader_comment
36 72
37 73
38 - %h2 Current graders
39 -
40 - = render :partial => 'grader_list', :locals => {:grader_list => @grader_processes}
41 -
42 - %h2 Stalled graders
43 -
44 - = render :partial => 'grader_list', :locals => {:grader_list => @stalled_processes}
45 -
46 - %h2 Terminated graders
47 -
48 - = form_for :clear, :url => {:action => 'clear_terminated'} do |f|
49 - = submit_tag 'Clear data for terminated graders'
50 -
51 - = render :partial => 'grader_list', :locals => {:grader_list => @terminated_processes}
@@ -1,22 +1,67
1 + %style{type: "text/css"}
2 + = @css_style
3 + :css
4 + .field {
5 + font-weight: bold;
6 + text-align: right;
7 + padding: 3px;
8 + }
9 +
10 +
1 11 %h1= "Submission: #{@submission.id}"
2 12
3 - %p
4 - User:
5 - = "#{@submission.user.login}"
6 - %br/
7 - Problem:
8 - - if @submission.problem!=nil
9 - = "#{@submission.problem.full_name}"
10 - - else
11 - = "(n/a)"
12 - %br/
13 - = "Number: #{@submission.number}"
14 - %br/
15 - = "Submitted at: #{format_short_time(@submission.submitted_at)}"
13 +
14 + %h2 Stat
16 15
17 - %b Source code (first 10kb)
18 - %div{:style => "border: 1px solid black; background: lightgrey"}
19 - - if @submission.source
20 - %pre
21 - =h truncate @submission.source, :length => 10240
16 + %table.info
17 + %thead
18 + %tr.info-head
19 + %th Field
20 + %th Value
21 + %tbody
22 + %tr{class: cycle('info-even','info-odd')}
23 + %td.field User:
24 + %td.value
25 + - if @submission.user
26 + = link_to "(#{@submission.user.login})", controller: "users", action: "profile", id: @submission.user
27 + = @submission.user.full_name
28 + - else
29 + = "(n/a)"
30 + %tr{class: cycle('info-even','info-odd')}
31 + %td.field Problem:
32 + %td.value
33 + - if @submission.problem!=nil
34 + = link_to "(#{@submission.problem.name})", controller: "problems", action: "stat", id: @submission.problem
35 + = @submission.problem.full_name
36 + - else
37 + = "(n/a)"
38 + %tr{class: cycle('info-even','info-odd')}
39 + %td.field Tries:
40 + %td.value= @submission.number
41 + %tr{class: cycle('info-even','info-odd')}
42 + %td.field Submitted:
43 + %td.value #{time_ago_in_words(@submission.submitted_at)} ago (at #{@submission.submitted_at.to_formatted_s(:long)})
44 + %tr{class: cycle('info-even','info-odd')}
45 + %td.field Graded:
46 + %td.value #{time_ago_in_words(@submission.graded_at)} ago (at #{@submission.graded_at.to_formatted_s(:long)})
47 + %tr{class: cycle('info-even','info-odd')}
48 + %td.field Points:
49 + %td.value #{@submission.points}/#{@submission.problem.full_score}
50 + %tr{class: cycle('info-even','info-odd')}
51 + %td.field Comment:
52 + %td.value #{@submission.grader_comment}
53 + %tr{class: cycle('info-even','info-odd')}
54 + %td.field Runtime (s):
55 + %td.value #{@submission.max_runtime}
56 + %tr{class: cycle('info-even','info-odd')}
57 + %td.field Memory (kb):
58 + %td.value #{@submission.peak_memory}
59 + - if session[:admin]
60 + %tr{class: cycle('info-even','info-odd')}
61 + %td.field IP:
62 + %td.value #{@submission.ip_address}
22 63
64 + %h2 Source code
65 + //%div.highlight{:style => "border: 1px solid black;"}
66 + =@formatted_code.html_safe
67 +
@@ -1,21 +1,23
1 1 <!DOCTYPE html>
2 2 <html>
3 3 <head>
4 4 <title><%= GraderConfiguration['contest.name'] %></title>
5 5 <%= stylesheet_link_tag "application", :media => "all" %>
6 6 <%= javascript_include_tag "application" %>
7 7 <%= csrf_meta_tags %>
8 + <%= content_for :header %>
8 9 <%= yield :head %>
10 +
9 11 </head>
10 12 <body>
11 13
12 14 <div class="userbar">
13 15 <%= user_header %>
14 16 </div>
15 17
16 18 <%= content_tag(:p,flash[:notice],:style => "color:green") if flash[:notice]!=nil %>
17 19
18 20 <%= yield %>
19 21
20 22 </body>
21 23 </html>
@@ -1,18 +1,21
1 1 <tr class="info-<%= (problem_counter%2==0) ? "even" : "odd" %>">
2 2 <td>
3 3 <%= "#{problem_counter+1}" %>
4 4 </td>
5 5 <td>
6 - <%= "#{problem.full_name} (#{problem.name})" %>
6 + <%= "#{problem.name}"%>
7 + </td>
8 + <td>
9 + <%= "#{problem.full_name}" %>
7 10 <%= link_to_description_if_any "[#{t 'main.problem_desc'}]", problem %>
8 11 </td>
9 12 <td align="center">
10 13 <%= @prob_submissions[problem.id][:count] %>
11 14 </td>
12 15 <td>
13 16 <%= render :partial => 'submission_short',
14 17 :locals => {
15 18 :submission => @prob_submissions[problem.id][:submission],
16 19 :problem_name => problem.name }%>
17 20 </td>
18 21 </tr>
@@ -1,18 +1,24
1 1
2 2 %tr{:class => ((submission_counter%2==0) ? "info-even" : "info-odd")}
3 3 %td.info{:align => "center"}
4 4 = submission_counter+1
5 - %td.info= format_short_time(submission.submitted_at)
6 5 %td.info{:align => "center"}
6 + = link_to "##{submission.id}", controller: :graders, action: :submission, id: submission.id
7 + %td.info
8 + = l submission.submitted_at, format: :long
9 + = "( #{time_ago_in_words(submission.submitted_at)} ago)"
10 + %td.info{:align => "center"}
11 + = submission.source_filename
12 + = " (#{submission.language.pretty_name}) "
7 13 = link_to('[load]',{:action => 'source', :id => submission.id})
8 14 %td.info
9 15 - if submission.graded_at!=nil
10 16 = "Graded at #{format_short_time(submission.graded_at)}."
11 17 %br/
12 18 = "Score: #{(submission.points*100/submission.problem.full_score).to_i} " if GraderConfiguration['ui.show_score']
13 19 = " ["
14 20 %tt
15 21 = submission.grader_comment
16 22 = "]"
17 23 %td.info
18 24 = render :partial => 'compiler_message', :locals => {:compiler_message => submission.compiler_message }
@@ -1,51 +1,53
1 1 - content_for :head do
2 2 = javascript_include_tag "announcement_refresh"
3 3
4 4 = user_title_bar(@user)
5 5
6 6 .announcementbox{:style => (@announcements.length==0 ? "display:none" : "")}
7 7 %span{:class => 'title'}
8 8 Announcements
9 9 #announcementbox-body
10 10 = render :partial => 'announcement', :collection => @announcements
11 11
12 12 - if GraderConfiguration.show_submitbox_to?(@user)
13 13 .submitbox
14 14 = error_messages_for 'submission'
15 15 = render :partial => 'submission_box'
16 16
17 17
18 18 %hr/
19 19
20 20 - if (GraderConfiguration.contest_mode?) and (@user.site!=nil) and (@user.site.started!=true)
21 21 %p=t 'main.start_soon'
22 22
23 23 - if GraderConfiguration.show_tasks_to?(@user)
24 24 - if not GraderConfiguration.multicontests?
25 25 %table.info
26 26 %tr.info-head
27 27 %th
28 - %th Tasks
28 + %th Tasks name
29 + %th Full name
29 30 %th # of sub(s)
30 31 %th Results
31 32 = render :partial => 'problem', :collection => @problems
32 33 - else
33 34 - @contest_problems.each do |cp|
34 35 - if cp[:problems].length > 0
35 36 %h2{:class =>'contest-title'}
36 37 = "#{cp[:contest] ? cp[:contest].title : 'Public problems'}"
37 38 %table.info
38 39 %tr.info-head
39 40 %th
40 - %th Tasks
41 + %th Tasks name
42 + %th Full name
41 43 %th # of sub(s)
42 44 %th Results
43 45 = render :partial => 'problem', :collection => cp[:problems]
44 46
45 47
46 48 %hr/
47 49
48 50 %script{:type => 'text/javascript'}
49 51 = "Announcement.refreshUrl = '#{url_for :controller => 'main', :action => 'announcements'}';"
50 52 Announcement.registerRefreshEventTimer();
51 53
@@ -1,24 +1,25
1 1 = user_title_bar(@user)
2 2
3 3 .task-menu
4 4 Task List
5 5 %br/
6 6 - @problems.each do |problem|
7 7 = link_to problem.name, :action => 'submission', :id => problem.name
8 8
9 9 - if @problem!=nil
10 10 %h2= "Task: #{@problem.full_name} (#{@problem.name})"
11 11
12 12 - if @submissions!=nil
13 13 - if @submissions.length>0
14 14 %table.info
15 15 %tr.info-head
16 + %th.info No.
16 17 %th.info #
17 18 %th.info At
18 19 %th.info Source
19 20 %th.info Result
20 21 %th.info{:width => "300px"}
21 22 Compiler message
22 23 = render :partial => 'submission', :collection => @submissions
23 24 - else
24 25 No submission
@@ -1,58 +1,71
1 1 - content_for :head do
2 2 = stylesheet_link_tag 'problems'
3 3
4 4 %h1 Import problems
5 5
6 6 %p= link_to '[Back to problem list]', :action => 'list'
7 7
8 8 - if @problem and @problem.errors
9 9 =error_messages_for 'problem'
10 10
11 11 = form_tag({:action => 'do_import'}, :multipart => true) do
12 12 .submitbox
13 13 %table
14 14 %tr
15 15 %td Name:
16 16 %td= text_field_tag 'name'
17 17 %tr
18 18 %td Full name:
19 19 %td
20 20 = text_field_tag 'full_name'
21 21 %span{:class => 'help'} Leave blank to use the same value as the name above.
22 22 %tr
23 23 %td Testdata file:
24 24 %td= file_field_tag 'file'
25 25 %tr
26 26 %td
27 27 %td
28 28 %span{:class => 'help'}
29 29 In .zip, .tgz, tar.gz, .tar format.
30 30 It should includes inputs (e.g., 1.in, 2a.in, 2b.in)
31 31 and solutions (e.g., 1.sol, 2a.sol, 2b.sol).
32 32 %br/
33 33 You may put task description in *.html for raw html
34 34 and *.md or *.markdown for markdown.
35 + %br/
36 + You may also put a pdf file for the task description
37 + %tr
38 + %td Checker:
39 + %td= select_tag 'checker', options_for_select([['Text checker','text'],['Float checker','float']], 'text')
40 + %tr
41 + %td
42 + %td
43 + %span{:class => 'help'}
44 + "Text" checker checks if the text (including numbers) is the same, ignoring any whitespace
45 + %br/
46 + "Float" checker checks if all numbers is within EPSILON error using formula |a-b| < EPSILON * max(|a|,|b|)
47 +
35 48 - if @allow_test_pair_import
36 49 %tr
37 50 %td
38 51 %td
39 52 = check_box_tag 'import_to_db'
40 53 Import test data to database (for a test-pair task)
41 54 %tr
42 55 %td Time limit:
43 56 %td
44 57 = text_field_tag 'time_limit'
45 58 %span{:class => 'help'} In seconds. Leave blank to use 1 sec.
46 59 %tr
47 60 %td Memory limit:
48 61 %td
49 62 = text_field_tag 'memory_limit'
50 63 %span{:class => 'help'} In MB. Leave blank to use 32MB.
51 64 %tr
52 65 %td
53 66 %td= submit_tag 'Import problem'
54 67
55 68 - if @log
56 69 %h3 Import log
57 70 %pre.import-log
58 71 = @log
@@ -1,43 +1,85
1 1 - content_for :head do
2 2 = stylesheet_link_tag 'problems'
3 + = javascript_include_tag 'local_jquery'
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 +
3 36
4 37 %h1 Manage problems
5 38
6 39 %p= link_to '[Back to problem list]', :action => 'list'
7 40
8 41 = form_tag :action=>'do_manage' do
9 42 .submitbox
10 - What do you want to do?
43 + What do you want to do to the selected problem?
11 44 %br/
45 + (You can shift-click to select a range of problems)
12 46 %ul
13 47 %li
14 48 Change date added to
15 49 = select_date Date.current, :prefix => 'date_added'
16 50 &nbsp;&nbsp;&nbsp;
17 51 = submit_tag 'Change', :name => 'change_date_added'
52 + %li
53 + Set available to
54 + = submit_tag 'True', :name => 'enable_problem'
55 + = submit_tag 'False', :name => 'disable_problem'
18 56
19 57 - if GraderConfiguration.multicontests?
20 58 %li
21 59 Add to
22 60 = select("contest","id",Contest.all.collect {|c| [c.title, c.id]})
23 61 = submit_tag 'Add', :name => 'add_to_contest'
24 62
25 63 %table
26 - %tr
27 - %th/
64 + %tr{style: "text-align: left;"}
65 + %th= check_box_tag 'select_all'
28 66 %th Name
29 67 %th Full name
68 + %th Available
30 69 %th Date added
31 70 - if GraderConfiguration.multicontests?
32 71 %th Contests
33 72
73 + - num = 0
34 74 - for problem in @problems
75 + - num += 1
35 76 %tr{:id => "row-prob-#{problem.id}", :name=> "prob-#{problem.id}"}
36 - %td= check_box_tag "prob-#{problem.id}"
77 + %td= check_box_tag "prob-#{problem.id}-#{num}"
37 78 %td= problem.name
38 79 %td= problem.full_name
80 + %td= problem.available
39 81 %td= problem.date_added
40 82 - if GraderConfiguration.multicontests?
41 83 %td
42 84 - problem.contests.each do |contest|
43 85 = "(#{contest.name} [#{link_to 'x', :action => 'remove_contest', :id => problem.id, :contest_id => contest.id }])"
@@ -1,22 +1,25
1 1 <%= error_messages_for 'user' %>
2 2
3 3 <!--[form:user]-->
4 4 <p><label for="user_name">Login</label><br/>
5 5 <%= text_field 'user', 'login' %></p>
6 6
7 7 <p><label for="user_name">Full name</label><br/>
8 8 <%= text_field 'user', 'full_name' %></p>
9 9
10 10 <p><label for="password">Password</label><br/>
11 11 <%= password_field 'user', 'password' %></p>
12 12
13 13 <p><label for="password_confirmation">Password (confirm)</label><br/>
14 14 <%= password_field 'user', 'password_confirmation' %></p>
15 15
16 16 <p><label for="user_email">E-mail</label><br/>
17 17 <%= email_field 'user', 'email' %></p>
18 18
19 19 <p><label for="user_alias">Alias</label><br/>
20 20 <%= text_field 'user', 'alias' %></p>
21 +
22 + <p><label for="user_remark">Remark</label><br/>
23 + <%= text_field 'user', 'remark' %></p>
21 24 <!--[eoform:user]-->
22 25
@@ -1,86 +1,87
1 1 <h1>Listing users</h1>
2 2
3 3 <div class="submitbox">
4 4 <b>Quick add</b>
5 5 <%= form_tag :action => 'create' do %>
6 6 <table border="0">
7 7 <tr>
8 8 <td><label for="user_login">Login</label></td>
9 9 <td><label for="user_full_name">Full name</label></td>
10 10 <td><label for="user_password">Password</label></td>
11 11 <td><label for="user_password_confirmation">Confirm</label></td>
12 12 <td><label for="user_email">Email</label></td>
13 13 </tr>
14 14 <tr>
15 15 <td><%= text_field 'user', 'login', :size => 10 %></td>
16 16 <td><%= text_field 'user', 'full_name', :size => 30 %></td>
17 17 <td><%= password_field 'user', 'password', :size => 10 %></td>
18 18 <td><%= password_field 'user', 'password_confirmation', :size => 10 %></td>
19 19 <td><%= email_field 'user', 'email', :size => 15 %></td>
20 20 <td><%= submit_tag "Create" %></td>
21 21 </tr>
22 22 </table>
23 23 <% end %>
24 24 <br/>
25 25 <b>Import from site management</b>
26 26 <%= form_tag({:action => 'import'}, :multipart => true) do %>
27 27 File: <%= file_field_tag 'file' %> <%= submit_tag 'Import' %>
28 28 <% end %>
29 29 <br/>
30 30 <b>What else: </b>
31 31 <%= link_to '[New user]', :action => 'new' %>
32 32 <%= link_to '[New list of users]', :action => 'new_list' %>
33 33 <%= link_to '[View administrators]', :action => 'admin' %>
34 34 <%= link_to '[Random passwords]', :action => 'random_all_passwords' %>
35 35 <%= link_to '[View active users]', :action => 'active' %>
36 36 <%= link_to '[Mass mailing]', :action => 'mass_mailing' %>
37 37 <% if GraderConfiguration.multicontests? %>
38 38 <br/><b>Multi-contest:</b>
39 39 <%= link_to '[Manage bulk users in contests]', :action => 'contest_management' %>
40 40 View users in:
41 41 <% @contests.each do |contest| %>
42 42 <%= link_to "[#{contest.name}]", :action => 'contests', :id => contest.id %>
43 43 <% end %>
44 44 <%= link_to "[no contest]", :action => 'contests', :id => 'none' %>
45 45 <% end %>
46 46 </div>
47 47
48 48 Total <%= @user_count %> users |
49 49 <% if !@paginated %>
50 50 Display all users.
51 51 <%= link_to '[show in pages]', :action => 'list', :page => '1' %>
52 52 <% else %>
53 53 Display in pages.
54 54 <%= link_to '[display all]', :action => 'list', :page => 'all' %> |
55 55 <%= will_paginate @users, :container => false %>
56 56 <% end %>
57 57 <table class="info">
58 58 <tr class="info-head">
59 59 <% for column in User.content_columns %>
60 60 <% if !@hidden_columns.index(column.name) %>
61 61 <th><%= column.human_name %></th>
62 62 <% end %>
63 63 <% end %>
64 64 <th></th>
65 65 <th></th>
66 66 <th></th>
67 67 </tr>
68 68
69 69 <% for user in @users %>
70 70 <tr class="info-<%= cycle("odd","even") %>">
71 + <td><%= link_to user.login, controller: :users, :action => 'profile', :id => user %></td>
71 72 <% for column in User.content_columns %>
72 - <% if !@hidden_columns.index(column.name) %>
73 + <% if !@hidden_columns.index(column.name) and column.name != 'login' %>
73 74 <td><%=h user.send(column.name) %></td>
74 75 <% end %>
75 76 <% end %>
76 77 <td><%= link_to 'Show', :action => 'show', :id => user %></td>
77 78 <td><%= link_to 'Edit', :action => 'edit', :id => user %></td>
78 79 <td><%= link_to 'Destroy', { :action => 'destroy', :id => user }, :confirm => 'Are you sure?', :method => :post %></td>
79 80 </tr>
80 81 <% end %>
81 82 </table>
82 83
83 84 <br />
84 85
85 86 <%= link_to '[New user]', :action => 'new' %>
86 87 <%= link_to '[New list of users]', :action => 'new_list' %>
@@ -1,64 +1,65
1 1 require File.expand_path('../boot', __FILE__)
2 2
3 3 require 'rails/all'
4 4
5 5 if defined?(Bundler)
6 6 # If you precompile assets before deploying to production, use this line
7 7 Bundler.require(*Rails.groups(:assets => %w(development test)))
8 8 # If you want your assets lazily compiled in production, use this line
9 9 # Bundler.require(:default, :assets, Rails.env)
10 10 end
11 11
12 12 module CafeGrader
13 13 class Application < Rails::Application
14 14 # Settings in config/environments/* take precedence over those specified here.
15 15 # Application configuration should go into files in config/initializers
16 16 # -- all .rb files in that directory are automatically loaded.
17 17
18 18 # Custom directories with classes and modules you want to be autoloadable.
19 19 config.autoload_paths += %W(#{config.root}/lib)
20 20
21 21 # Only load the plugins named here, in the order given (default is alphabetical).
22 22 # :all can be used as a placeholder for all plugins not explicitly named.
23 23 # config.plugins = [ :exception_notification, :ssl_requirement, :all ]
24 24
25 25 # Activate observers that should always be running.
26 26 # config.active_record.observers = :cacher, :garbage_collector, :forum_observer
27 27
28 28 # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
29 29 # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
30 30 config.time_zone = 'UTC'
31 31
32 32 # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
33 33 # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
34 34 config.i18n.default_locale = :en
35 35
36 36 # Configure the default encoding used in templates for Ruby 1.9.
37 37 config.encoding = "utf-8"
38 38
39 39 # Configure sensitive parameters which will be filtered from the log file.
40 40 config.filter_parameters += [:password]
41 41
42 42 # Enable escaping HTML in JSON.
43 43 config.active_support.escape_html_entities_in_json = true
44 44
45 45 # Use SQL instead of Active Record's schema dumper when creating the database.
46 46 # This is necessary if your schema can't be completely dumped by the schema dumper,
47 47 # like if you have constraints or database-specific column types
48 48 # config.active_record.schema_format = :sql
49 49
50 50 # Enforce whitelist mode for mass assignment.
51 51 # This will create an empty whitelist of attributes available for mass-assignment for all models
52 52 # in your app. As such, your models will need to explicitly whitelist or blacklist accessible
53 53 # parameters by using an attr_accessible or attr_protected declaration.
54 54 config.active_record.whitelist_attributes = false
55 55
56 56 # Enable the asset pipeline
57 57 config.assets.enabled = true
58 58
59 59 # Version of your assets, change this if you want to expire all your assets
60 60 config.assets.version = '1.0'
61 61
62 62 config.assets.precompile += ['announcement_refresh.js','effects.js','site_update.js','graders.css','problems.css']
63 + config.assets.precompile += ['local_jquery.js','tablesorter-theme.cafe.css']
63 64 end
64 65 end
@@ -1,158 +1,159
1 1 # Sample localization file for English. Add more files in this directory for other locales.
2 2 # See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
3 3
4 4 en:
5 5 cancel: 'Cancel'
6 6
7 7 login_label: 'Login'
8 8 full_name_label: 'Full name'
9 9 email_label: 'E-mail'
10 10 password_label: 'Password'
11 11
12 12 go_ahead_to: "Go ahead to "
13 13 go_back_to: "Go back to "
14 14 login_page: "login page"
15 15 home_page: "home page"
16 16
17 17 menu:
18 18 main: 'Main'
19 19 messages: 'Messages'
20 20 tasks: 'Tasks'
21 21 submissions: 'Submissions'
22 22 test: 'Test Interface'
23 + hall_of_fame: 'Hall of Fame'
23 24 help: 'Help'
24 25 settings: 'Settings'
25 26 log_out: 'Log out'
26 27
27 28 title_bar:
28 29 current_time: "Current time is"
29 30 remaining_time: "Time left: "
30 31 contest_not_started: "The contest has not started."
31 32
32 33 login:
33 34 message: 'Please login to see the problem list'
34 35 login_submit: 'Login'
35 36 participation: 'Want to participate?'
36 37 please: 'Please'
37 38 register: 'register'
38 39 forget_password: 'Forget password?'
39 40
40 41 main:
41 42 start_soon: "The contest at your site will start soon. Please wait."
42 43 specified_in_header: "Specified in header"
43 44
44 45 problem_desc: "desc"
45 46 submitted_at: "Submitted at"
46 47 graded_at: "Graded at"
47 48 score: "score: "
48 49 cmp_msg: "compiler msg"
49 50 src_link: "src"
50 51 submissions_link: "submissions"
51 52
52 53 confirm_contest_start:
53 54 box_title: "Contest confirmation"
54 55 contest_list: "You will participate in contest:"
55 56 timer_starts_after_click: "The timer will start after you click the start button."
56 57 start_button: "Start!"
57 58 start_button_confirm: "Are you sure?"
58 59
59 60 test:
60 61 title: "Test Interface"
61 62 intro: "You can test your submission with your own test data on the grading environment using this test interface."
62 63 disabled_at_end_announcement: "<b>Note:</b> Test interface will be disabled in the last 30 minutes of the contest time on your site."
63 64
64 65 registration:
65 66 title: "New user registration"
66 67
67 68 description: "Please enter your information below. Please make sure your e-mail is correct, because you will have to confirm the registration through an e-mail we send to that e-mail address."
68 69
69 70 successful_title: "Registration successful"
70 71
71 72 login_guide: "Only a-z, A-Z, 0-9 and _. Can be at most 20 characters long"
72 73 email_guide: "Please make sure that your e-mail is correct.<br/>You'll need to verify your account by email."
73 74 register: "Register"
74 75
75 76 email_body: "Hello %{full_name},
76 77
77 78 You have registered for %{contest_name}
78 79
79 80 Your login is: %{login}
80 81
81 82 Your password is: %{password}
82 83
83 84 Please follow the link:
84 85
85 86 %{activation_url}
86 87
87 88 to activate your user account.
88 89
89 90 If you did not register, please ignore this e-mail
90 91 and report this event to %{admin_email}.
91 92
92 93 Thanks!"
93 94
94 95 email_sent: "We have sent a confimation message to your e-mail. (Please also check the Junk mail box."
95 96 email_verify_at: "Please check at %{email} and confirm."
96 97
97 98 activation_sucessful_title: "User activated"
98 99 account_activated: "Your account has been activated."
99 100
100 101 activation_failed_title: "Activation failed"
101 102
102 103 errors:
103 104 header: "Errors occured during registration"
104 105 email:
105 106 title: "Errors in sending registration confirmation"
106 107 expl: "<h2>Your user account has been created, but the system cannot send you the confirmation e-mail.</h2>
107 108 Maybe there's a problem in the configuration. Please report the admin at %{email}.<br/>Thank you!"
108 109 activation:
109 110 email_exists: "A user with this E-mail exists."
110 111 invalid: "Your activation code is invalid. Please check again."
111 112
112 113 password_retrieval:
113 114 header: "Password retrieval"
114 115 instructions: "Please enter the e-mail address that you used to register."
115 116 button_label: "Request new password"
116 117 no_email: "No user with that e-mail address."
117 118 email_body: "Hello %{full_name},
118 119
119 120 You have requested for new password for %{contest_name}. We have generated it for you:
120 121
121 122 user name: %{login}
122 123 password: %{password}
123 124
124 125 If you didn't ask for new password or you're not the person who registered,
125 126 please ignore this e-mail and inform %{admin_email} of this mistake.
126 127
127 128 Thanks!"
128 129
129 130 contest:
130 131 notification:
131 132 email_subject: "[%{contest_title_name}] You have been upgraded to %{contest_name}"
132 133 email_body: "Congratulations %{full_name},
133 134
134 135 You have advanced to contest %{contest_name} in %{contest_title_name}.
135 136
136 137 You can now log-in and start participating in that contest.
137 138
138 139 Cheers!
139 140 -%{contest_title_name} Admin"
140 141
141 142 help:
142 143 how_to_submit: "How to submit"
143 144 must_specify_language: "You <b>must</b> specify the language you are using in your program header. You can optionally specify the task you are submitting to."
144 145 list_available_language: "The possible language options are <tt>C</tt>, <tt>C++</tt>, and <tt>Pascal</tt>. The follow are examples."
145 146 accept_only_language_specified: "The server <b>will not</b> accept your submission, if you do not specify the language."
146 147 specifying_task: "Optionally, you can also specify the task with <tt>TASK:</tt> <i>taskname</i>. On the first page, the taskname for each task is shown in parentheses."
147 148 example_cpp: "For example, suppose you are using <tt>C++</tt> to write task <b>mobiles</b>, you put the following on top of your source code."
148 149 example_pas: "If you are using <tt>Pascal</tt> to write the same task, you'll use"
149 150 ask_questions_at_messages: "If you have any problems, you can ask at [<a href=\"%{url}\">%{message_link_name}</a>]."
150 151
151 152 activerecord:
152 153 attributes:
153 154 user:
154 155 login: "login"
155 156 full_name: "full name"
156 157 email: "e-mail"
157 158 province: "province"
158 159
@@ -1,157 +1,158
1 1 # Sample localization file for English. Add more files in this directory for other locales.
2 2 # See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
3 3
4 4 th:
5 5 cancel: 'ยกเลิก'
6 6
7 7 login_label: 'ชื่อเข้าใช้ระบบ (login)'
8 8 full_name_label: 'ชื่อเต็ม'
9 9 email_label: 'E-mail'
10 10 password_label: 'รหัสผ่าน'
11 11
12 12 go_ahead_to: "ไปยัง"
13 13 go_back_to: "กลับไปยัง"
14 14 login_page: "หน้าเข้าใช้ระบบ"
15 15 home_page: "หน้าแรก"
16 16
17 17 menu:
18 18 main: 'หน้าหลัก'
19 19 messages: 'ข้อความ'
20 20 tasks: 'โจทย์'
21 21 submissions: 'โปรแกรมที่ส่ง'
22 22 test: 'ทดสอบโปรแกรม'
23 + hall_of_fame: 'หอเกียรติยศ'
23 24 help: 'ความช่วยเหลือ'
24 25 settings: 'เปลี่ยนรหัสผ่าน'
25 26 log_out: 'ออกจากระบบ'
26 27
27 28 title_bar:
28 29 current_time: "เวลาปัจจุบันคือ"
29 30 remaining_time: "เหลือเวลาอีก"
30 31 contest_not_started: "ยังไม่เริ่มแข่งขัน"
31 32
32 33 login:
33 34 message: 'กรุณา login เพื่อเข้าสู่ระบบ'
34 35 login_submit: 'เข้าใช้ระบบ'
35 36 participation: 'ต้องการเข้าร่วม?'
36 37 please: 'กรุณา'
37 38 register: 'ลงทะเบียน'
38 39 forget_password: 'ลืมรหัสผ่าน?'
39 40
40 41 main:
41 42 start_soon: "การแข่งขันกำลังจะเริ่ม กรุณารอก่อน"
42 43 specified_in_header: "ระบุที่หัวโปรแกรมแล้ว"
43 44
44 45 problem_desc: "อ่าน"
45 46 submitted_at: "ส่งเมื่อเวลา"
46 47 graded_at: "ตรวจเมื่อเวลา"
47 48 score: "คะแนน: "
48 49 cmp_msg: "ผลคอมไพล์"
49 50 src_link: "ต้นฉบับ"
50 51 submissions_link: "การส่งครั้งอื่น ๆ"
51 52
52 53 confirm_contest_start:
53 54 box_title: "ยืนยันการเริ่มแข่งขัน"
54 55 contest_list: "การแข่งขันที่คุณจะเข้าร่วมคือ "
55 56 timer_starts_after_click: "การจับเวลาจะเริ่มขึ้นเมื่อคุณกดปุ่มด้านล่าง"
56 57 start_button: "เริ่มแข่ง!"
57 58 start_button_confirm: "แน่ใจที่จะเริ่มแข่งหรือไม่?"
58 59
59 60 test:
60 61 title: "ทดสอบโปรแกรมบนสภาพแวดล้อมของเครื่องตรวจ"
61 62 intro: "คุณสามารถทดลองการทำงานของโปรแกรมที่เขียนกับข้อมูลชุดทดสอบของคุณเองในสภาพแวดล้อมจริงของการตรวจโปรแกรมได้ โดยเลือกโปรแกรมส่งแล้วที่ด้านล่างพร้อมทั้งส่งแฟ้มข้อมูลชุดทดสอบที่ต้องการให้ทำงานด้วย"
62 63 disabled_at_end_announcement: "<b>หมายเหตุ:</b> ระบบทดสอบโปรแกรมจะหยุดทำงานในช่วงเวลา 30 นาทีสุดท้ายของการแข่งขัน"
63 64
64 65 registration:
65 66 title: "ลงทะเบียนผู้ใช้ใหม่"
66 67 description: "ในการลงทะเบียน ให้ผู้สนใจเข้าร่วมการแข่งขันกรอกข้อมูลด้านล่าง จากนั้นระบบจะส่ง e-mail ไปยัง e-mail ที่ระบุเพื่อให้ยืนยันตัวตนและเปิดใช้บัญชีผู้ใช้<br/>ในกรณีที่ผู้เข้าแข่งขันเป็นนักเรียน รบกวนช่วยให้ข้อมูลเกี่ยวกับโรงเรียนและจังหวัดด้วย"
67 68
68 69 successful_title: "การลงทะเบียนเสร็จเรียบร้อย"
69 70
70 71 login_guide: "ใช้ได้เฉพาะ a-z, A-Z, 0-9 และ _ ความยาวไม่เกิน 20 ตัวอักษร"
71 72 email_guide: "กรุณาตรวจสอบ e-mail ที่ใส่ให้ถูกต้อง<br/>คุณจะต้องยืนยันการลงทะเบียนผ่านทางข้อมูลที่จะส่งให้ทาง e-mail"
72 73 register: "ลงทะเบียน"
73 74
74 75 email_body: "สวัสดีครับ %{full_name},
75 76
76 77 คุณได้ลงทะเบียนเข้าร่วมการแข่งขัน %{contest_name}
77 78
78 79 บัญชีเข้าใช้ของคุณคือ: %{login}
79 80
80 81 รหัสผ่านคือ: %{password}
81 82
82 83 กรุณาเข้าลิงก์ต่อไปนี้:
83 84
84 85 %{activation_url}
85 86
86 87 เพื่อเปิดใช้งานบัญชีของคุณ
87 88
88 89 ถ้าคุณไม่ใช่คนที่ลงทะเบียน กรุณาละทิ้ง e-mail ฉบับนี้
89 90 และแจ้งความผิดพลาดนี้กับ %{admin_email} ด้วย
90 91
91 92 ขอบคุณมาก!"
92 93
93 94 email_sent: "เราได้ส่งข้อมูลสำหรับยืนยันไปให้คุณแล้ว (โปรดอย่าลืมตรวจดูในส่วน Junk mail ด้วย)"
94 95 email_verify_at: "กรุณาตรวจสอบที่ %{email} พร้อมทั้งยืนยัน"
95 96
96 97 activation_sucessful_title: "บัณชีผู้ใช้ได้รับการยืนยันแล้ว"
97 98 account_activated: "บัญชีผู้ใช้ของคุณพร้อมใช้งานแล้ว"
98 99
99 100 activation_failed_title: "การยืนยันล้มเหลว"
100 101
101 102 errors:
102 103 header: 'การลงทะเบียนมีข้อผิดพลาด'
103 104 email:
104 105 title: "เกิดปัญหาระหว่างการส่ง e-mail เพื่อยืนยันการสมัคร"
105 106 expl: "<h2>บัญชีผู้ใช้ของคุณถูกสร้างขึ้นแล้ว แต่ระบบไม่สามารถส่ง e-mail เพื่อยืนยันการสมัครได้</h2>
106 107 อาจเกิดปัญหาในการตั้งค่าเริ่มต้นของระบบ กรุณาช่วยติดต่อผู้ดูแลระบบด้วยที่ %{email}<br/>ขอขอบคุณจากทีมงาน"
107 108 activation:
108 109 email_exists: "มีผู้ใช้ที่ใช้ e-mail นี้แล้ว"
109 110 invalid: "รหัสสำหรับยืนยันผิดพลาด กรุณาตรวจสอบอีกครั้ง"
110 111
111 112 password_retrieval:
112 113 header: "การขอรหัสผ่านใหม่"
113 114 instructions: "กรุณากรอก e-mail ที่ลงทะเบียน"
114 115 button_label: "ขอรหัสผ่านใหม่"
115 116 no_email: "ไม่มีบัญชีผู้ใช้ที่ใช้ e-mail ดังกล่าว"
116 117 email_body: "สวัสดีครับ %{full_name},
117 118
118 119 คุณได้ร้องขอรหัสผ่านใหม่ สำหรับการแข่งขัน %{contest_name} ซึ่งเราได้สร้างให้คุณแล้ว ดังนี้
119 120
120 121 บัญชีเข้าใช้ของคุณคือ: %{login}
121 122 รหัสผ่านคือ: %{password}
122 123
123 124 ถ้าคุณไม่ได้ขอรหัสผ่านใหม่ หรือไม่ใช่คนที่ลงทะเบียน กรุณาละทิ้ง e-mail ฉบับนี้
124 125 และแจ้งความผิดพลาดนี้กับ %{admin_email} ด้วย
125 126
126 127 ขอบคุณมาก!"
127 128
128 129 contest:
129 130 notification:
130 131 email_subject: "[%{contest_title_name}] คุณได้รับการเลื่อนขั้นสู่ระดับ %{contest_name}"
131 132 email_body: "ขอแสดงความยินดีด้วย คุณ%{full_name}
132 133
133 134 คุณได้รับการเลื่อนขั้นให้สามารถแข่งขันในการแข่งขันระดับ %{contest_name} ของ %{contest_title_name} แล้ว
134 135
135 136 คุณสามารถเข้าสู่ระบบเพื่อทำโจทย์ได้ทันที คุณสามารถแข่งในระดับ %{contest_name} ได้จนกระทั่งการแข่งขันรอบนี้สิ้นสุดลง
136 137
137 138 -ทีมงาน %{contest_title_name}"
138 139
139 140
140 141 help:
141 142 how_to_submit: "วิธีการส่งโปรแกรม"
142 143 must_specify_language: "คุณ<b>ต้อง</b>ระบุภาษาโปรแกรมที่ใช้ที่ตอนต้นของรหัสโปรแกรม (source code) นอกจากนี้คุณอาจจะระบุโจทย์ที่ต้องการส่งได้ด้วย"
143 144 list_available_language: "ภาษาโปรแกรมที่สามารถระบุได้คือ <tt>C</tt>, <tt>C++</tt>, และ <tt>Pascal</tt> ด้านล่างแสดงตัวอย่างของการระบุสำหรับภาษาต่าง ๆ"
144 145 accept_only_language_specified: "ระบบจะ<b>ไม่รับ</b>โปรแกรมที่ส่งถ้าคุณไม่ได้ระบุภาษาที่ใช้"
145 146 specifying_task: "นอกจากนี้ คุณยังสามารถระบุชื่อของโจทย์ที่ต้องการส่งเพิ่มเติมได้ ในการระบุให้ใส่ <tt>TASK:</tt> <i>taskname</i> คุณสามารถตรวจสอบชื่อของโจทย์ได้ โดยจะแสดงในวงเล็บหลังชื่อภาษาไทยของโจทย์"
146 147 example_cpp: "ยกตัวอย่างเช่น ถ้าคุณใช้ภาษา <tt>C++</tt> สำหรับเขียนโจทย์ <tt>mobiles</tt> ตอนต้นโปรแกรมคุณจะใส่ดังนี้"
147 148 example_pas: "ถ้าคุณใช้ภาษา <tt>Pascal</tt> เพื่อเขียนโจทย์ข้อเดียวกัน คุณจะระบุ"
148 149 ask_questions_at_messages: "ถ้ามีปัญหาในการใช้งานสามารถสอบถามได้ที่หน้า<a href=\"%{url}\">%{message_link_name}</a>"
149 150
150 151 activerecord:
151 152 attributes:
152 153 user:
153 154 login: "ชื่อเข้าใช้ระบบ"
154 155 full_name: "ชื่อเต็ม"
155 156 email: "e-mail"
156 157 province: "จังหวัด"
157 158
@@ -1,68 +1,72
1 1 CafeGrader::Application.routes.draw do
2 + get "report/login"
3 +
2 4 resources :contests
3 5
4 6 resources :announcements
5 7 resources :sites
6 8
9 + resources :grader_configuration, controller: 'configurations'
10 +
7 11 # The priority is based upon order of creation:
8 12 # first created -> highest priority.
9 13
10 14 # Sample of regular route:
11 15 # match 'products/:id' => 'catalog#view'
12 16 # Keep in mind you can assign values other than :controller and :action
13 17
14 18 # Sample of named route:
15 19 # match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase
16 20 # This route can be invoked with purchase_url(:id => product.id)
17 21
18 22 # Sample resource route (maps HTTP verbs to controller actions automatically):
19 23 # resources :products
20 24
21 25 # Sample resource route with options:
22 26 # resources :products do
23 27 # member do
24 28 # get 'short'
25 29 # post 'toggle'
26 30 # end
27 31 #
28 32 # collection do
29 33 # get 'sold'
30 34 # end
31 35 # end
32 36
33 37 # Sample resource route with sub-resources:
34 38 # resources :products do
35 39 # resources :comments, :sales
36 40 # resource :seller
37 41 # end
38 42
39 43 # Sample resource route with more complex sub-resources
40 44 # resources :products do
41 45 # resources :comments
42 46 # resources :sales do
43 47 # get 'recent', :on => :collection
44 48 # end
45 49 # end
46 50
47 51 # Sample resource route within a namespace:
48 52 # namespace :admin do
49 53 # # Directs /admin/products/* to Admin::ProductsController
50 54 # # (app/controllers/admin/products_controller.rb)
51 55 # resources :products
52 56 # end
53 57
54 58 # You can have the root of your site routed with "root"
55 59 # just remember to delete public/index.html.
56 60 # root :to => 'welcome#index'
57 61
58 62 root :to => 'main#login'
59 63
60 64 match 'tasks/view/:file.:ext' => 'tasks#view'
61 65 match 'tasks/download/:id/:file.:ext' => 'tasks#download'
62 66
63 67 # See how all your routes lay out with "rake routes"
64 68
65 69 # This is a legacy wild controller route that's not recommended for RESTful applications.
66 70 # Note: This route will make all actions in every controller accessible via GET requests.
67 71 match ':controller(/:action(/:id))(.:format)'
68 72 end
@@ -1,273 +1,248
1 1 # encoding: UTF-8
2 2 # This file is auto-generated from the current state of the database. Instead
3 3 # of editing this file, please use the migrations feature of Active Record to
4 4 # incrementally modify your database, and then regenerate this schema definition.
5 5 #
6 6 # Note that this schema.rb definition is the authoritative source for your
7 7 # database schema. If you need to create the application database on another
8 8 # system, you should be using db:schema:load, not running all the migrations
9 9 # from scratch. The latter is a flawed and unsustainable approach (the more migrations
10 10 # you'll amass, the slower it'll run and the greater likelihood for issues).
11 11 #
12 12 # It's strongly recommended to check this file into your version control system.
13 13
14 - ActiveRecord::Schema.define(:version => 20121001033508) do
14 + ActiveRecord::Schema.define(:version => 20150203153534) do
15 15
16 16 create_table "announcements", :force => true do |t|
17 17 t.string "author"
18 18 t.text "body"
19 19 t.boolean "published"
20 - t.datetime "created_at"
21 - t.datetime "updated_at"
20 + t.datetime "created_at", :null => false
21 + t.datetime "updated_at", :null => false
22 22 t.boolean "frontpage", :default => false
23 23 t.boolean "contest_only", :default => false
24 24 t.string "title"
25 25 t.string "notes"
26 26 end
27 27
28 - create_table "codejom_statuses", :force => true do |t|
29 - t.integer "user_id"
30 - t.boolean "alive"
31 - t.integer "num_problems_passed"
32 - t.datetime "created_at"
33 - t.datetime "updated_at"
34 - end
35 -
36 28 create_table "contests", :force => true do |t|
37 29 t.string "title"
38 30 t.boolean "enabled"
39 - t.datetime "created_at"
40 - t.datetime "updated_at"
31 + t.datetime "created_at", :null => false
32 + t.datetime "updated_at", :null => false
41 33 t.string "name"
42 34 end
43 35
44 36 create_table "contests_problems", :id => false, :force => true do |t|
45 37 t.integer "contest_id"
46 38 t.integer "problem_id"
47 39 end
48 40
49 41 create_table "contests_users", :id => false, :force => true do |t|
50 42 t.integer "contest_id"
51 43 t.integer "user_id"
52 44 end
53 45
54 46 create_table "countries", :force => true do |t|
55 47 t.string "name"
56 - t.datetime "created_at"
57 - t.datetime "updated_at"
48 + t.datetime "created_at", :null => false
49 + t.datetime "updated_at", :null => false
58 50 end
59 51
60 52 create_table "descriptions", :force => true do |t|
61 53 t.text "body"
62 54 t.boolean "markdowned"
63 - t.datetime "created_at"
64 - t.datetime "updated_at"
55 + t.datetime "created_at", :null => false
56 + t.datetime "updated_at", :null => false
65 57 end
66 58
67 59 create_table "grader_configurations", :force => true do |t|
68 60 t.string "key"
69 61 t.string "value_type"
70 62 t.string "value"
71 - t.datetime "created_at"
72 - t.datetime "updated_at"
63 + t.datetime "created_at", :null => false
64 + t.datetime "updated_at", :null => false
73 65 t.text "description"
74 66 end
75 67
76 68 create_table "grader_processes", :force => true do |t|
77 69 t.string "host", :limit => 20
78 70 t.integer "pid"
79 71 t.string "mode"
80 72 t.boolean "active"
81 - t.datetime "created_at"
82 - t.datetime "updated_at"
73 + t.datetime "created_at", :null => false
74 + t.datetime "updated_at", :null => false
83 75 t.integer "task_id"
84 76 t.string "task_type"
85 77 t.boolean "terminated"
86 78 end
87 79
88 80 add_index "grader_processes", ["host", "pid"], :name => "index_grader_processes_on_ip_and_pid"
89 81
90 82 create_table "languages", :force => true do |t|
91 83 t.string "name", :limit => 10
92 84 t.string "pretty_name"
93 85 t.string "ext", :limit => 10
94 86 t.string "common_ext"
95 87 end
96 88
89 + create_table "logins", :force => true do |t|
90 + t.string "user_id"
91 + t.string "ip_address"
92 + t.datetime "created_at", :null => false
93 + t.datetime "updated_at", :null => false
94 + end
95 +
97 96 create_table "messages", :force => true do |t|
98 97 t.integer "sender_id"
99 98 t.integer "receiver_id"
100 99 t.integer "replying_message_id"
101 100 t.text "body"
102 101 t.boolean "replied"
103 - t.datetime "created_at"
104 - t.datetime "updated_at"
102 + t.datetime "created_at", :null => false
103 + t.datetime "updated_at", :null => false
105 104 end
106 105
107 106 create_table "problems", :force => true do |t|
108 - t.string "name", :limit => 30
109 - t.string "full_name"
110 - t.integer "full_score"
111 - t.date "date_added"
112 - t.boolean "available"
113 - t.string "url"
114 - t.integer "description_id"
115 - t.boolean "test_allowed"
116 - t.boolean "output_only"
117 - t.integer "level", :default => 0
118 - t.datetime "updated_at"
119 - t.string "description_filename"
107 + t.string "name", :limit => 30
108 + t.string "full_name"
109 + t.integer "full_score"
110 + t.date "date_added"
111 + t.boolean "available"
112 + t.string "url"
113 + t.integer "description_id"
114 + t.boolean "test_allowed"
115 + t.boolean "output_only"
116 + t.string "description_filename"
120 117 end
121 118
122 119 create_table "rights", :force => true do |t|
123 120 t.string "name"
124 121 t.string "controller"
125 122 t.string "action"
126 123 end
127 124
128 125 create_table "rights_roles", :id => false, :force => true do |t|
129 126 t.integer "right_id"
130 127 t.integer "role_id"
131 128 end
132 129
133 130 add_index "rights_roles", ["role_id"], :name => "index_rights_roles_on_role_id"
134 131
135 132 create_table "roles", :force => true do |t|
136 133 t.string "name"
137 134 end
138 135
139 136 create_table "roles_users", :id => false, :force => true do |t|
140 137 t.integer "role_id"
141 138 t.integer "user_id"
142 139 end
143 140
144 141 add_index "roles_users", ["user_id"], :name => "index_roles_users_on_user_id"
145 142
146 143 create_table "sessions", :force => true do |t|
147 144 t.string "session_id"
148 145 t.text "data"
149 146 t.datetime "updated_at"
150 147 end
151 148
152 149 add_index "sessions", ["session_id"], :name => "index_sessions_on_session_id"
153 150 add_index "sessions", ["updated_at"], :name => "index_sessions_on_updated_at"
154 151
155 152 create_table "sites", :force => true do |t|
156 153 t.string "name"
157 154 t.boolean "started"
158 155 t.datetime "start_time"
159 - t.datetime "created_at"
160 - t.datetime "updated_at"
156 + t.datetime "created_at", :null => false
157 + t.datetime "updated_at", :null => false
161 158 t.integer "country_id"
162 159 t.string "password"
163 160 end
164 161
165 - create_table "submission_statuses", :force => true do |t|
166 - t.integer "user_id"
167 - t.integer "problem_id"
168 - t.boolean "passed"
169 - t.integer "submission_count"
170 - t.datetime "created_at"
171 - t.datetime "updated_at"
172 - end
173 -
174 162 create_table "submissions", :force => true do |t|
175 163 t.integer "user_id"
176 164 t.integer "problem_id"
177 165 t.integer "language_id"
178 166 t.text "source"
179 167 t.binary "binary"
180 168 t.datetime "submitted_at"
181 169 t.datetime "compiled_at"
182 170 t.text "compiler_message"
183 171 t.datetime "graded_at"
184 172 t.integer "points"
185 173 t.text "grader_comment"
186 174 t.integer "number"
187 175 t.string "source_filename"
176 + t.float "max_runtime"
177 + t.integer "peak_memory"
178 + t.integer "effective_code_length"
179 + t.string "ip_address"
188 180 end
189 181
190 182 add_index "submissions", ["user_id", "problem_id", "number"], :name => "index_submissions_on_user_id_and_problem_id_and_number", :unique => true
191 183 add_index "submissions", ["user_id", "problem_id"], :name => "index_submissions_on_user_id_and_problem_id"
192 184
193 185 create_table "tasks", :force => true do |t|
194 186 t.integer "submission_id"
195 187 t.datetime "created_at"
196 188 t.integer "status"
197 189 t.datetime "updated_at"
198 190 end
199 191
200 - create_table "test_pair_assignments", :force => true do |t|
201 - t.integer "user_id"
202 - t.integer "problem_id"
203 - t.integer "test_pair_id"
204 - t.integer "test_pair_number"
205 - t.integer "request_number"
206 - t.datetime "created_at"
207 - t.datetime "updated_at"
208 - t.boolean "submitted"
209 - end
210 -
211 192 create_table "test_pairs", :force => true do |t|
212 193 t.integer "problem_id"
213 194 t.text "input", :limit => 16777215
214 195 t.text "solution", :limit => 16777215
215 - t.datetime "created_at"
216 - t.datetime "updated_at"
217 - t.integer "number"
196 + t.datetime "created_at", :null => false
197 + t.datetime "updated_at", :null => false
218 198 end
219 199
220 200 create_table "test_requests", :force => true do |t|
221 201 t.integer "user_id"
222 202 t.integer "problem_id"
223 203 t.integer "submission_id"
224 204 t.string "input_file_name"
225 205 t.string "output_file_name"
226 206 t.string "running_stat"
227 207 t.integer "status"
228 - t.datetime "updated_at"
208 + t.datetime "updated_at", :null => false
229 209 t.datetime "submitted_at"
230 210 t.datetime "compiled_at"
231 211 t.text "compiler_message"
232 212 t.datetime "graded_at"
233 213 t.string "grader_comment"
234 - t.datetime "created_at"
214 + t.datetime "created_at", :null => false
235 215 t.float "running_time"
236 216 t.string "exit_status"
237 217 t.integer "memory_usage"
238 218 end
239 219
240 220 add_index "test_requests", ["user_id", "problem_id"], :name => "index_test_requests_on_user_id_and_problem_id"
241 221
242 222 create_table "user_contest_stats", :force => true do |t|
243 223 t.integer "user_id"
244 224 t.datetime "started_at"
245 - t.datetime "created_at"
246 - t.datetime "updated_at"
225 + t.datetime "created_at", :null => false
226 + t.datetime "updated_at", :null => false
247 227 t.boolean "forced_logout"
248 228 end
249 229
250 230 create_table "users", :force => true do |t|
251 - t.string "login", :limit => 50
231 + t.string "login", :limit => 50
252 232 t.string "full_name"
253 233 t.string "hashed_password"
254 - t.string "salt", :limit => 5
234 + t.string "salt", :limit => 5
255 235 t.string "alias"
256 236 t.string "email"
257 237 t.integer "site_id"
258 238 t.integer "country_id"
259 - t.boolean "activated", :default => false
239 + t.boolean "activated", :default => false
260 240 t.datetime "created_at"
261 241 t.datetime "updated_at"
262 - t.string "member1_full_name"
263 - t.string "member2_full_name"
264 - t.string "member3_full_name"
265 - t.boolean "high_school"
266 - t.string "member1_school_name"
267 - t.string "member2_school_name"
268 - t.string "member3_school_name"
242 + t.boolean "enabled", :default => true
243 + t.string "remark"
269 244 end
270 245
271 246 add_index "users", ["login"], :name => "index_users_on_login", :unique => true
272 247
273 248 end
@@ -1,198 +1,223
1 1 CONFIGURATIONS =
2 2 [
3 3 {
4 4 :key => 'system.single_user_mode',
5 5 :value_type => 'boolean',
6 6 :default_value => 'false',
7 7 :description => 'Only admins can log in to the system when running under single user mode.'
8 8 },
9 -
9 +
10 10 {
11 11 :key => 'ui.front.title',
12 12 :value_type => 'string',
13 13 :default_value => 'Grader'
14 14 },
15 -
15 +
16 16 {
17 17 :key => 'ui.front.welcome_message',
18 18 :value_type => 'string',
19 19 :default_value => 'Welcome!'
20 20 },
21 -
21 +
22 22 {
23 23 :key => 'ui.show_score',
24 24 :value_type => 'boolean',
25 25 :default_value => 'true'
26 26 },
27 -
27 +
28 28 {
29 29 :key => 'contest.time_limit',
30 30 :value_type => 'string',
31 31 :default_value => 'unlimited',
32 32 :description => 'Time limit in format hh:mm, or "unlimited" for contests with no time limits. This config is CACHED. Restart the server before the change can take effect.'
33 33 },
34 -
34 +
35 35 {
36 36 :key => 'system.mode',
37 37 :value_type => 'string',
38 38 :default_value => 'standard',
39 39 :description => 'Current modes are "standard", "contest", "indv-contest", and "analysis".'
40 40 },
41 -
41 +
42 42 {
43 43 :key => 'contest.name',
44 44 :value_type => 'string',
45 45 :default_value => 'Grader',
46 46 :description => 'This name will be shown on the user header bar.'
47 47 },
48 -
48 +
49 49 {
50 50 :key => 'contest.multisites',
51 51 :value_type => 'boolean',
52 52 :default_value => 'false',
53 53 :description => 'If the server is in contest mode and this option is true, on the log in of the admin a menu for site selections is shown.'
54 54 },
55 -
55 +
56 56 {
57 - :key => 'system.online_registration',
57 + :key => 'right.user_hall_of_fame',
58 58 :value_type => 'boolean',
59 59 :default_value => 'false',
60 - :description => 'This option enables online registration.'
60 + :description => 'If true, any user can access hall of fame page.'
61 61 },
62 -
62 +
63 + {
64 + :key => 'right.user_view_submission',
65 + :value_type => 'boolean',
66 + :default_value => 'false',
67 + :description => 'If true, any user can view submissions of every one.'
68 + },
69 +
63 70 # If Configuration['system.online_registration'] is true, the
64 71 # system allows online registration, and will use these
65 72 # information for sending confirmation emails.
66 73 {
67 74 :key => 'system.online_registration.smtp',
68 75 :value_type => 'string',
69 76 :default_value => 'smtp.somehost.com'
70 77 },
71 -
78 +
72 79 {
73 80 :key => 'system.online_registration.from',
74 81 :value_type => 'string',
75 82 :default_value => 'your.email@address'
76 83 },
77 -
84 +
78 85 {
79 86 :key => 'system.admin_email',
80 87 :value_type => 'string',
81 88 :default_value => 'admin@admin.email'
82 89 },
83 -
90 +
84 91 {
85 92 :key => 'system.user_setting_enabled',
86 93 :value_type => 'boolean',
87 94 :default_value => 'true',
88 95 :description => 'If this option is true, users can change their settings'
89 96 },
90 -
97 +
98 + {
99 + :key => 'system.user_setting_enabled',
100 + :value_type => 'boolean',
101 + :default_value => 'true',
102 + :description => 'If this option is true, users can change their settings'
103 + },
104 +
91 105 # If Configuration['contest.test_request.early_timeout'] is true
92 106 # the user will not be able to use test request at 30 minutes
93 107 # before the contest ends.
94 108 {
95 109 :key => 'contest.test_request.early_timeout',
96 110 :value_type => 'boolean',
97 111 :default_value => 'false'
98 112 },
99 113
100 114 {
101 115 :key => 'system.multicontests',
102 116 :value_type => 'boolean',
103 117 :default_value => 'false'
104 118 },
105 119
106 120 {
107 121 :key => 'contest.confirm_indv_contest_start',
108 122 :value_type => 'boolean',
109 123 :default_value => 'false'
110 124 },
111 125
112 126 {
113 127 :key => 'contest.default_contest_name',
114 128 :value_type => 'string',
115 129 :default_value => 'none',
116 130 :description => "New user will be assigned to this contest automatically, if it exists. Set to 'none' if there is no default contest."
117 131 }
118 -
132 +
119 133 ]
120 134
121 135
122 136 def create_configuration_key(key,
123 137 value_type,
124 138 default_value,
125 139 description='')
126 140 conf = (GraderConfiguration.find_by_key(key) ||
127 141 GraderConfiguration.new(:key => key,
128 142 :value_type => value_type,
129 143 :value => default_value))
130 144 conf.description = description
131 145 conf.save
132 146 end
133 147
134 148 def seed_config
135 149 CONFIGURATIONS.each do |conf|
136 150 if conf.has_key? :description
137 151 desc = conf[:description]
138 152 else
139 153 desc = ''
140 154 end
141 155 create_configuration_key(conf[:key],
142 156 conf[:value_type],
143 157 conf[:default_value],
144 158 desc)
145 159 end
146 160 end
147 161
148 162 def seed_roles
149 163 return if Role.find_by_name('admin')
150 164
151 165 role = Role.create(:name => 'admin')
152 166 user_admin_right = Right.create(:name => 'user_admin',
153 167 :controller => 'user_admin',
154 168 :action => 'all')
155 169 problem_admin_right = Right.create(:name=> 'problem_admin',
156 170 :controller => 'problems',
157 171 :action => 'all')
158 172
159 173 graders_right = Right.create(:name => 'graders_admin',
160 174 :controller => 'graders',
161 175 :action => 'all')
162 176
163 177 role.rights << user_admin_right;
164 178 role.rights << problem_admin_right;
165 179 role.rights << graders_right;
166 180 role.save
167 181 end
168 182
169 183 def seed_root
170 184 return if User.find_by_login('root')
171 185
172 186 root = User.new(:login => 'root',
173 187 :full_name => 'Administrator',
174 188 :alias => 'root')
175 189 root.password = 'ioionrails';
176 190
177 191 class << root
178 192 public :encrypt_new_password
179 193 def valid?(context=nil)
180 194 true
181 195 end
182 196 end
183 197
184 198 root.encrypt_new_password
185 199
186 200 root.roles << Role.find_by_name('admin')
187 201
188 202 root.activated = true
189 203 root.save
190 204 end
191 205
192 206 def seed_users_and_roles
193 207 seed_roles
194 208 seed_root
195 209 end
196 210
211 + def seed_more_languages
212 + Language.delete_all
213 + Language.create( name: 'c', pretty_name: 'C', ext: 'c', common_ext: 'c' )
214 + Language.create( name: 'cpp', pretty_name: 'C++', ext: 'cpp', common_ext: 'cpp,cc' )
215 + Language.create( name: 'pas', pretty_name: 'Pascal', ext: 'pas', common_ext: 'pas' )
216 + Language.create( name: 'ruby', pretty_name: 'Ruby', ext: 'rb', common_ext: 'rb' )
217 + Language.create( name: 'python', pretty_name: 'Python', ext: 'py', common_ext: 'py' )
218 + Language.create( name: 'java', pretty_name: 'Java', ext: 'java', common_ext: 'java' )
219 + end
220 +
197 221 seed_config
198 222 seed_users_and_roles
223 + seed_more_languages
@@ -1,58 +1,58
1 1 module GraderScript
2 2
3 3 def self.grader_control_enabled?
4 4 if defined? GRADER_ROOT_DIR
5 5 GRADER_ROOT_DIR != ''
6 6 else
7 7 false
8 8 end
9 9 end
10 10
11 11 def self.raw_dir
12 12 File.join GRADER_ROOT_DIR, "raw"
13 13 end
14 14
15 15 def self.call_grader(params)
16 16 if GraderScript.grader_control_enabled?
17 17 cmd = File.join(GRADER_ROOT_DIR, "scripts/grader") + " " + params
18 18 system(cmd)
19 19 end
20 20 end
21 21
22 22 def self.stop_grader(pid)
23 23 GraderScript.call_grader "stop #{pid}"
24 24 end
25 25
26 26 def self.stop_graders(pids)
27 27 pid_str = (pids.map { |process| process.pid.to_s }).join ' '
28 28 GraderScript.call_grader "stop #{pid_str}"
29 29 end
30 30
31 31 def self.start_grader(env)
32 - GraderScript.call_grader "#{env} queue &"
33 - GraderScript.call_grader "#{env} test_request &"
32 + GraderScript.call_grader "#{env} queue --err-log &"
33 + GraderScript.call_grader "#{env} test_request -err-log &"
34 34 end
35 35
36 36 def self.call_import_problem(problem_name,
37 37 problem_dir,
38 38 time_limit=1,
39 39 memory_limit=32,
40 40 checker_name='text')
41 41 if GraderScript.grader_control_enabled?
42 42 cur_dir = `pwd`.chomp
43 43 Dir.chdir(GRADER_ROOT_DIR)
44 44
45 45 script_name = File.join(GRADER_ROOT_DIR, "scripts/import_problem")
46 46 cmd = "#{script_name} #{problem_name} #{problem_dir} #{checker_name}" +
47 47 " -t #{time_limit} -m #{memory_limit}"
48 48
49 49 output = `#{cmd}`
50 50
51 51 Dir.chdir(cur_dir)
52 52
53 - return output
53 + return "import CMD: #{cmd}\n" + output
54 54 end
55 55 return ''
56 56 end
57 57
58 58 end
@@ -1,188 +1,190
1 1 require 'tmpdir'
2 2
3 3 class TestdataImporter
4 4
5 5 attr :log_msg
6 6
7 7 def initialize(problem)
8 8 @problem = problem
9 9 end
10 10
11 11 def import_from_file(tempfile,
12 12 time_limit,
13 13 memory_limit,
14 + checker_name='text',
14 15 import_to_db=false)
15 16
16 17 dirname = extract(tempfile)
17 18 return false if not dirname
18 19 if not import_to_db
19 20 @log_msg = GraderScript.call_import_problem(@problem.name,
20 21 dirname,
21 22 time_limit,
22 - memory_limit)
23 + memory_limit,
24 + checker_name)
23 25 else
24 26 # Import test data to test pairs.
25 27
26 28 @problem.test_pairs.clear
27 29 if import_test_pairs(dirname)
28 30 test_pair_count = TestPair.count :conditions => "problem_id = #{@problem.id}"
29 31 @log_msg = "Importing test pair successful. (#{test_pair_count} test pairs imported)"
30 32 else
31 33 @log_msg = "Importing test pair failed. (0 test pairs imported)"
32 34 end
33 35 end
34 36
35 37 @log_msg << import_problem_description(dirname)
36 38 @log_msg << import_problem_pdf(dirname)
37 39 @log_msg << import_full_score(dirname)
38 40
39 41 return true
40 42 end
41 43
42 44 protected
43 45
44 46 def self.long_ext(filename)
45 47 i = filename.index('.')
46 48 len = filename.length
47 49 return filename.slice(i..len)
48 50 end
49 51
50 52 def extract(tempfile)
51 53 testdata_filename = save_testdata_file(tempfile)
52 54 ext = TestdataImporter.long_ext(tempfile.original_filename)
53 55
54 56 extract_dir = File.join(GraderScript.raw_dir, @problem.name)
55 57 if File.exists? extract_dir
56 58 backup_count = 0
57 59 begin
58 60 backup_count += 1
59 61 backup_dirname = "#{extract_dir}.backup.#{backup_count}"
60 62 end while File.exists? backup_dirname
61 63 File.rename(extract_dir, backup_dirname)
62 64 end
63 65 Dir.mkdir extract_dir
64 66
65 67 if ext=='.tar.gz' or ext=='.tgz'
66 68 cmd = "tar -zxvf #{testdata_filename} -C #{extract_dir}"
67 69 elsif ext=='.tar'
68 70 cmd = "tar -xvf #{testdata_filename} -C #{extract_dir}"
69 71 elsif ext=='.zip'
70 72 cmd = "unzip -o #{testdata_filename} -d #{extract_dir}"
71 73 else
72 74 return nil
73 75 end
74 76
75 77 system(cmd)
76 78
77 79 files = Dir["#{extract_dir}/**/*1*.in"]
78 80 return nil if files.length==0
79 81
80 82 File.delete(testdata_filename)
81 83
82 84 return File.dirname(files[0])
83 85 end
84 86
85 87 def save_testdata_file(tempfile)
86 88 ext = TestdataImporter.long_ext(tempfile.original_filename)
87 89 testdata_filename = File.join(Dir.tmpdir,"#{@problem.name}#{ext}")
88 90
89 91 return nil if tempfile==""
90 92
91 93 if tempfile.instance_of?(Tempfile)
92 94 tempfile.close
93 95 FileUtils.move(tempfile.path,testdata_filename)
94 96 else
95 97 File.open(testdata_filename, "wb") do |f|
96 98 f.write(tempfile.read)
97 99 end
98 100 end
99 101
100 102 return testdata_filename
101 103 end
102 104
103 105 def import_test_pairs(dirname)
104 106 test_num = 1
105 107 while FileTest.exists? "#{dirname}/#{test_num}.in"
106 108 in_filename = "#{dirname}/#{test_num}.in"
107 109 sol_filename = "#{dirname}/#{test_num}.sol"
108 110
109 111 break if not FileTest.exists? sol_filename
110 112
111 113 test_pair = TestPair.new(:input => open(in_filename).read,
112 114 :solution => open(sol_filename).read,
113 115 :problem => @problem)
114 116 break if not test_pair.save
115 117
116 118 test_num += 1
117 119 end
118 120 return test_num > 1
119 121 end
120 122
121 123 def import_problem_description(dirname)
122 124 html_files = Dir["#{dirname}/*.html"]
123 125 markdown_files = Dir["#{dirname}/*.md"] + Dir["#{dirname}/*.markdown"]
124 126 if (html_files.length != 0) or (markdown_files.length != 0)
125 127 description = @problem.description || Description.new
126 128
127 129 if html_files.length != 0
128 130 filename = html_files[0]
129 131 description.markdowned = false
130 132 else
131 133 filename = markdown_files[0]
132 134 description.markdowned = true
133 135 end
134 136
135 137 description.body = open(filename).read
136 138 description.save
137 139 @problem.description = description
138 140 @problem.save
139 141 return "\nProblem description imported from #{filename}."
140 142 else
141 143 return ''
142 144 end
143 145 end
144 146
145 147 def import_problem_pdf(dirname)
146 148 pdf_files = Dir["#{dirname}/*.pdf"]
147 149 puts "CHECKING... #{dirname}"
148 150 if pdf_files.length != 0
149 151 puts "HAS PDF FILE"
150 152 filename = pdf_files[0]
151 153
152 154 @problem.save if not @problem.id
153 155 out_dirname = "#{Problem.download_file_basedir}/#{@problem.id}"
154 156 if not FileTest.exists? out_dirname
155 157 Dir.mkdir out_dirname
156 158 end
157 159
158 160 out_filename = "#{out_dirname}/#{@problem.name}.pdf"
159 161
160 162 if FileTest.exists? out_filename
161 163 File.delete out_filename
162 164 end
163 165
164 166 File.rename(filename, out_filename)
165 167 @problem.description_filename = "#{@problem.name}.pdf"
166 168 @problem.save
167 169 return "\nProblem pdf imported from #{filename}."
168 170 else
169 171 return ""
170 172 end
171 173 end
172 174
173 175 #just set the full score to the total number of test case
174 176 #it is not perfect but works on most normal use case
175 177 def import_full_score(dirname)
176 178 num = 0
177 179 loop do
178 180 num += 1
179 181 in_file = Dir["#{dirname}/#{num}*.in"]
180 182 break if in_file.length == 0
181 183 end
182 184 full_score = (num - 1) * 10
183 185 @problem.full_score = full_score
184 186 @problem.save
185 187 return "\nFull score is set to #{full_score}."
186 188 end
187 189
188 190 end
deleted file
deleted file
deleted file
You need to be logged in to leave comments. Login now