Description:
- add view testcase toggle for each problem - refactor: move view testcase to testcase_controller - change flash rendering
Commit status:
[Not Reviewed]
References:
Comments:
0 Commit comments 0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
Add another comment

r632:b4dd5e5f23ab - - 14 files changed: 107 inserted, 48 deleted

@@ -0,0 +1,2
1 + = render partial: 'toggle_button',
2 + locals: {button_id: "#problem-view-testcase-#{@problem.id}",button_on: @problem.view_testcase?}
@@ -0,0 +1,25
1 + %h1 Test cases
2 + %h2= @problem.long_name
3 +
4 + /navbar
5 + %ul.nav.nav-pills{role: :tablist}
6 + - @problem.testcases.each.with_index do |tc,id|
7 + %li{role: :presentation, class: ('active' if id == 0)}
8 + %a{href:"#tc#{tc.id}", role: 'tab', data: {toggle: 'tab'}}= tc.num
9 +
10 + /actual data
11 + .tab-content
12 + - @problem.testcases.each.with_index do |tc,id|
13 + .tab-pane{id: "tc#{tc.id}",class: ('active' if id == 0)}
14 + .row
15 + .col-md-6
16 + %h3 Input
17 + = link_to "Download",download_input_testcase_path(tc),class: 'btn btn-info btn-sm'
18 + .col-md-6
19 + %h3 Output
20 + = link_to "Download",download_sol_testcase_path(tc),class: 'btn btn-info btn-sm'
21 + .row
22 + .col-md-6
23 + %textarea{ rows: 25,readonly: true,style: "width:100%;resize=none;overflow-y: scroll;"}= tc.input
24 + .col-md-6
25 + %textarea{ rows: 25,readonly: true,style: "width:100%;resize=none;overflow-y: scroll;"}= tc.sol
@@ -0,0 +1,5
1 + class AddViewTestcaseToProblem < ActiveRecord::Migration
2 + def change
3 + add_column :problems, :view_testcase, :bool
4 + end
5 + end
@@ -1,137 +1,142
1 1 class ApplicationController < ActionController::Base
2 2 protect_from_forgery
3 3
4 4 before_filter :current_user
5 5
6 6 SINGLE_USER_MODE_CONF_KEY = 'system.single_user_mode'
7 7 MULTIPLE_IP_LOGIN_CONF_KEY = 'right.multiple_ip_login'
8 8
9 9 #report and redirect for unauthorized activities
10 10 def unauthorized_redirect
11 11 flash[:notice] = 'You are not authorized to view the page you requested'
12 12 redirect_to :controller => 'main', :action => 'login'
13 13 end
14 14
15 15 # Returns the current logged-in user (if any).
16 16 def current_user
17 17 return nil unless session[:user_id]
18 18 @current_user ||= User.find(session[:user_id])
19 19 end
20 20
21 21 def admin_authorization
22 22 return false unless authenticate
23 23 user = User.includes(:roles).find(session[:user_id])
24 24 unless user.admin?
25 25 unauthorized_redirect
26 26 return false
27 27 end
28 28 return true
29 29 end
30 30
31 31 def authorization_by_roles(allowed_roles)
32 32 return false unless authenticate
33 33 user = User.find(session[:user_id])
34 34 unless user.roles.detect { |role| allowed_roles.member?(role.name) }
35 35 unauthorized_redirect
36 36 return false
37 37 end
38 38 end
39 39
40 40 def testcase_authorization
41 41 #admin always has privileged
42 + puts "haha"
42 43 if @current_user.admin?
43 44 return true
44 45 end
45 46
46 - unauthorized_redirect if GraderConfiguration["right.view_testcase"]
47 + puts "hehe"
48 + puts GraderConfiguration["right.view_testcase"]
49 + unauthorized_redirect unless GraderConfiguration["right.view_testcase"]
47 50 end
48 51
49 52 protected
50 53
51 54 def authenticate
52 55 unless session[:user_id]
53 56 flash[:notice] = 'You need to login'
54 57 if GraderConfiguration[SINGLE_USER_MODE_CONF_KEY]
55 58 flash[:notice] = 'You need to login but you cannot log in at this time'
56 59 end
57 60 redirect_to :controller => 'main', :action => 'login'
58 61 return false
59 62 end
60 63
61 64 # check if run in single user mode
62 65 if GraderConfiguration[SINGLE_USER_MODE_CONF_KEY]
63 66 user = User.find_by_id(session[:user_id])
64 67 if user==nil or (not user.admin?)
65 68 flash[:notice] = 'You cannot log in at this time'
66 69 redirect_to :controller => 'main', :action => 'login'
67 70 return false
68 71 end
69 72 unless user.enabled?
70 73 flash[:notice] = 'Your account is disabled'
71 74 redirect_to :controller => 'main', :action => 'login'
72 75 return false
73 76 end
74 77 return true
75 78 end
76 79
77 80 if GraderConfiguration.multicontests?
78 81 user = User.find(session[:user_id])
79 82 return true if user.admin?
80 83 begin
81 84 if user.contest_stat(true).forced_logout
82 85 flash[:notice] = 'You have been automatically logged out.'
83 86 redirect_to :controller => 'main', :action => 'index'
84 87 end
85 88 rescue
86 89 end
87 90 end
88 91 return true
89 92 end
90 93
91 94 def authenticate_by_ip_address
92 95 #this assume that we have already authenticate normally
93 96 unless GraderConfiguration[MULTIPLE_IP_LOGIN_CONF_KEY]
94 97 user = User.find(session[:user_id])
95 98 if (not user.admin? and user.last_ip and user.last_ip != request.remote_ip)
96 99 flash[:notice] = "You cannot use the system from #{request.remote_ip}. Your last ip is #{user.last_ip}"
97 100 redirect_to :controller => 'main', :action => 'login'
98 101 puts "CHEAT: user #{user.login} tried to login from '#{request.remote_ip}' while last ip is '#{user.last_ip}' at #{Time.zone.now}"
99 102 return false
100 103 end
101 104 unless user.last_ip
102 105 user.last_ip = request.remote_ip
103 106 user.save
104 107 end
105 108 end
106 109 return true
107 110 end
108 111
109 112 def authorization
110 113 return false unless authenticate
114 + puts "haha 1"
111 115 user = User.find(session[:user_id])
112 116 unless user.roles.detect { |role|
113 117 role.rights.detect{ |right|
114 118 right.controller == self.class.controller_name and
115 119 (right.action == 'all' or right.action == action_name)
116 120 }
117 121 }
122 + puts "haha 2"
118 123 flash[:notice] = 'You are not authorized to view the page you requested'
119 124 #request.env['HTTP_REFERER'] ? (redirect_to :back) : (redirect_to :controller => 'login')
120 125 redirect_to :controller => 'main', :action => 'login'
121 126 return false
122 127 end
123 128 end
124 129
125 130 def verify_time_limit
126 131 return true if session[:user_id]==nil
127 132 user = User.find(session[:user_id], :include => :site)
128 133 return true if user==nil or user.site == nil
129 134 if user.contest_finished?
130 135 flash[:notice] = 'Error: the contest you are participating is over.'
131 136 redirect_to :back
132 137 return false
133 138 end
134 139 return true
135 140 end
136 141
137 142 end
@@ -1,284 +1,288
1 1 class ProblemsController < ApplicationController
2 2
3 3 before_action :authenticate, :authorization
4 4 before_action :testcase_authorization, only: [:show_testcase]
5 5
6 6 in_place_edit_for :problem, :name
7 7 in_place_edit_for :problem, :full_name
8 8 in_place_edit_for :problem, :full_score
9 9
10 10 def index
11 11 @problems = Problem.order(date_added: :desc)
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 => [ :create, :quick_create,
16 16 :do_manage,
17 17 :do_import,
18 18 ],
19 19 :redirect_to => { :action => :index }
20 20
21 21 def show
22 22 @problem = Problem.find(params[:id])
23 23 end
24 24
25 25 def new
26 26 @problem = Problem.new
27 27 @description = nil
28 28 end
29 29
30 30 def create
31 31 @problem = Problem.new(params[:problem])
32 32 @description = Description.new(params[:description])
33 33 if @description.body!=''
34 34 if !@description.save
35 35 render :action => new and return
36 36 end
37 37 else
38 38 @description = nil
39 39 end
40 40 @problem.description = @description
41 41 if @problem.save
42 42 flash[:notice] = 'Problem was successfully created.'
43 43 redirect_to action: :index
44 44 else
45 45 render :action => 'new'
46 46 end
47 47 end
48 48
49 49 def quick_create
50 50 @problem = Problem.new(params[:problem])
51 51 @problem.full_name = @problem.name if @problem.full_name == ''
52 52 @problem.full_score = 100
53 53 @problem.available = false
54 54 @problem.test_allowed = true
55 55 @problem.output_only = false
56 56 @problem.date_added = Time.new
57 57 if @problem.save
58 58 flash[:notice] = 'Problem was successfully created.'
59 59 redirect_to action: :index
60 60 else
61 61 flash[:notice] = 'Error saving problem'
62 62 redirect_to action: :index
63 63 end
64 64 end
65 65
66 66 def edit
67 67 @problem = Problem.find(params[:id])
68 68 @description = @problem.description
69 69 end
70 70
71 71 def update
72 72 @problem = Problem.find(params[:id])
73 73 @description = @problem.description
74 74 if @description.nil? and params[:description][:body]!=''
75 75 @description = Description.new(params[:description])
76 76 if !@description.save
77 77 flash[:notice] = 'Error saving description'
78 78 render :action => 'edit' and return
79 79 end
80 80 @problem.description = @description
81 81 elsif @description
82 82 if !@description.update_attributes(params[:description])
83 83 flash[:notice] = 'Error saving description'
84 84 render :action => 'edit' and return
85 85 end
86 86 end
87 87 if params[:file] and params[:file].content_type != 'application/pdf'
88 88 flash[:notice] = 'Error: Uploaded file is not PDF'
89 89 render :action => 'edit' and return
90 90 end
91 91 if @problem.update_attributes(params[:problem])
92 92 flash[:notice] = 'Problem was successfully updated.'
93 93 unless params[:file] == nil or params[:file] == ''
94 94 flash[:notice] = 'Problem was successfully updated and a new PDF file is uploaded.'
95 95 out_dirname = "#{Problem.download_file_basedir}/#{@problem.id}"
96 96 if not FileTest.exists? out_dirname
97 97 Dir.mkdir out_dirname
98 98 end
99 99
100 100 out_filename = "#{out_dirname}/#{@problem.name}.pdf"
101 101 if FileTest.exists? out_filename
102 102 File.delete out_filename
103 103 end
104 104
105 105 File.open(out_filename,"wb") do |file|
106 106 file.write(params[:file].read)
107 107 end
108 108 @problem.description_filename = "#{@problem.name}.pdf"
109 109 @problem.save
110 110 end
111 111 redirect_to :action => 'show', :id => @problem
112 112 else
113 113 render :action => 'edit'
114 114 end
115 115 end
116 116
117 117 def destroy
118 118 p = Problem.find(params[:id]).destroy
119 119 redirect_to action: :index
120 120 end
121 121
122 122 def toggle
123 123 @problem = Problem.find(params[:id])
124 124 @problem.update_attributes(available: !(@problem.available) )
125 125 respond_to do |format|
126 126 format.js { }
127 127 end
128 128 end
129 129
130 130 def toggle_test
131 131 @problem = Problem.find(params[:id])
132 132 @problem.update_attributes(test_allowed: !(@problem.test_allowed?) )
133 133 respond_to do |format|
134 134 format.js { }
135 135 end
136 136 end
137 137
138 + def toggle_view_testcase
139 + @problem = Problem.find(params[:id])
140 + @problem.update_attributes(view_testcase: !(@problem.view_testcase?) )
141 + respond_to do |format|
142 + format.js { }
143 + end
144 + end
145 +
138 146 def turn_all_off
139 147 Problem.available.all.each do |problem|
140 148 problem.available = false
141 149 problem.save
142 150 end
143 151 redirect_to action: :index
144 152 end
145 153
146 154 def turn_all_on
147 155 Problem.where.not(available: true).each do |problem|
148 156 problem.available = true
149 157 problem.save
150 158 end
151 159 redirect_to action: :index
152 160 end
153 161
154 162 def stat
155 163 @problem = Problem.find(params[:id])
156 164 unless @problem.available or session[:admin]
157 165 redirect_to :controller => 'main', :action => 'list'
158 166 return
159 167 end
160 168 @submissions = Submission.includes(:user).where(problem_id: params[:id]).order(:user_id,:id)
161 169
162 170 #stat summary
163 171 range =65
164 172 @histogram = { data: Array.new(range,0), summary: {} }
165 173 user = Hash.new(0)
166 174 @submissions.find_each do |sub|
167 175 d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60
168 176 @histogram[:data][d.to_i] += 1 if d < range
169 177 user[sub.user_id] = [user[sub.user_id], ((sub.try(:points) || 0) >= @problem.full_score) ? 1 : 0].max
170 178 end
171 179 @histogram[:summary][:max] = [@histogram[:data].max,1].max
172 180
173 181 @summary = { attempt: user.count, solve: 0 }
174 182 user.each_value { |v| @summary[:solve] += 1 if v == 1 }
175 183 end
176 184
177 185 def manage
178 186 @problems = Problem.order(date_added: :desc)
179 187 end
180 188
181 189 def do_manage
182 190 if params.has_key? 'change_date_added'
183 191 change_date_added
184 192 elsif params.has_key? 'add_to_contest'
185 193 add_to_contest
186 194 elsif params.has_key? 'enable_problem'
187 195 set_available(true)
188 196 elsif params.has_key? 'disable_problem'
189 197 set_available(false)
190 198 end
191 199 redirect_to :action => 'manage'
192 200 end
193 201
194 202 def import
195 203 @allow_test_pair_import = allow_test_pair_import?
196 204 end
197 205
198 206 def do_import
199 207 old_problem = Problem.find_by_name(params[:name])
200 208 if !allow_test_pair_import? and params.has_key? :import_to_db
201 209 params.delete :import_to_db
202 210 end
203 211 @problem, import_log = Problem.create_from_import_form_params(params,
204 212 old_problem)
205 213
206 214 if !@problem.errors.empty?
207 215 render :action => 'import' and return
208 216 end
209 217
210 218 if old_problem!=nil
211 219 flash[:notice] = "The test data has been replaced for problem #{@problem.name}"
212 220 end
213 221 @log = import_log
214 222 end
215 223
216 224 def remove_contest
217 225 problem = Problem.find(params[:id])
218 226 contest = Contest.find(params[:contest_id])
219 227 if problem!=nil and contest!=nil
220 228 problem.contests.delete(contest)
221 229 end
222 230 redirect_to :action => 'manage'
223 231 end
224 232
225 - def show_testcase
226 - @problem = Problem.includes(:testcases).find(params[:id])
227 - end
228 -
229 233 ##################################
230 234 protected
231 235
232 236 def allow_test_pair_import?
233 237 if defined? ALLOW_TEST_PAIR_IMPORT
234 238 return ALLOW_TEST_PAIR_IMPORT
235 239 else
236 240 return false
237 241 end
238 242 end
239 243
240 244 def change_date_added
241 245 problems = get_problems_from_params
242 246 year = params[:date_added][:year].to_i
243 247 month = params[:date_added][:month].to_i
244 248 day = params[:date_added][:day].to_i
245 249 date = Date.new(year,month,day)
246 250 problems.each do |p|
247 251 p.date_added = date
248 252 p.save
249 253 end
250 254 end
251 255
252 256 def add_to_contest
253 257 problems = get_problems_from_params
254 258 contest = Contest.find(params[:contest][:id])
255 259 if contest!=nil and contest.enabled
256 260 problems.each do |p|
257 261 p.contests << contest
258 262 end
259 263 end
260 264 end
261 265
262 266 def set_available(avail)
263 267 problems = get_problems_from_params
264 268 problems.each do |p|
265 269 p.available = avail
266 270 p.save
267 271 end
268 272 end
269 273
270 274 def get_problems_from_params
271 275 problems = []
272 276 params.keys.each do |k|
273 277 if k.index('prob-')==0
274 278 name, id, order = k.split('-')
275 279 problems << Problem.find(id)
276 280 end
277 281 end
278 282 problems
279 283 end
280 284
281 285 def get_problems_stat
282 286 end
283 287
284 288 end
@@ -1,24 +1,32
1 1 class TestcasesController < ApplicationController
2 2 before_action :set_testcase, only: [:download_input,:download_sol]
3 3 before_action :testcase_authorization
4 4
5 5 def download_input
6 6 send_data @testcase.input, type: 'text/plain', filename: "#{@testcase.problem.name}.#{@testcase.num}.in"
7 7 end
8 8
9 9 def download_sol
10 10 send_data @testcase.sol, type: 'text/plain', filename: "#{@testcase.problem.name}.#{@testcase.num}.sol"
11 11 end
12 12
13 + def show_problem
14 + @problem = Problem.includes(:testcases).find(params[:problem_id])
15 + unless @current_user.admin? or @problem.view_testcase
16 + flash[:error] = 'You cannot view the testcase of this problem'
17 + redirect_to :controller => 'main', :action => 'list'
18 + end
19 + end
20 +
13 21
14 22 private
15 23 # Use callbacks to share common setup or constraints between actions.
16 24 def set_testcase
17 25 @testcase = Testcase.find(params[:id])
18 26 end
19 27
20 28 # Only allow a trusted parameter "white list" through.
21 29 def testcase_params
22 30 params[:testcase]
23 31 end
24 32 end
@@ -1,199 +1,222
1 1 # Methods added to this helper will be available to all templates in the application.
2 2 module ApplicationHelper
3 3
4 4 #new bootstrap header
5 5 def navbar_user_header
6 6 left_menu = ''
7 7 right_menu = ''
8 8 user = User.find(session[:user_id])
9 9
10 10 if (user!=nil) and (GraderConfiguration.show_tasks_to?(user))
11 11 left_menu << add_menu("#{I18n.t 'menu.tasks'}", 'tasks', 'list')
12 12 left_menu << add_menu("#{I18n.t 'menu.submissions'}", 'main', 'submission')
13 13 left_menu << add_menu("#{I18n.t 'menu.test'}", 'test', 'index')
14 14 end
15 15
16 16 if GraderConfiguration['right.user_hall_of_fame']
17 17 left_menu << add_menu("#{I18n.t 'menu.hall_of_fame'}", 'report', 'problem_hof')
18 18 end
19 19
20 20 right_menu << add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-question-sign')}".html_safe, 'main', 'help')
21 21 right_menu << add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-comment')}".html_safe, 'messages', 'list', {title: I18n.t('menu.messages'), data: {toggle: 'tooltip'}})
22 22 if GraderConfiguration['system.user_setting_enabled']
23 23 right_menu << add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-cog')}".html_safe, 'users', 'index', {title: I18n.t('menu.settings'), data: {toggle: 'tooltip'}})
24 24 end
25 25 right_menu << add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-log-out')} #{user.full_name}".html_safe, 'main', 'login', {title: I18n.t('menu.log_out'), data: {toggle: 'tooltip'}})
26 26
27 27
28 28 result = content_tag(:ul,left_menu.html_safe,class: 'nav navbar-nav') + content_tag(:ul,right_menu.html_safe,class: 'nav navbar-nav navbar-right')
29 29 end
30 30
31 31 def add_menu(title, controller, action,html_option = {})
32 32 link_option = {controller: controller, action: action}
33 33 html_option[:class] = (html_option[:class] || '') + " active" if current_page?(link_option)
34 34 content_tag(:li, link_to(title,link_option),html_option)
35 35 end
36 36
37 37 def user_header
38 38 menu_items = ''
39 39 user = User.find(session[:user_id])
40 40
41 41 if (user!=nil) and (session[:admin])
42 42 # admin menu
43 43 menu_items << "<b>Administrative task:</b> "
44 44 append_to menu_items, '[Announcements]', 'announcements', 'index'
45 45 append_to menu_items, '[Msg console]', 'messages', 'console'
46 46 append_to menu_items, '[Problems]', 'problems', 'index'
47 47 append_to menu_items, '[Users]', 'user_admin', 'index'
48 48 append_to menu_items, '[Results]', 'user_admin', 'user_stat'
49 49 append_to menu_items, '[Report]', 'report', 'multiple_login'
50 50 append_to menu_items, '[Graders]', 'graders', 'list'
51 51 append_to menu_items, '[Contests]', 'contest_management', 'index'
52 52 append_to menu_items, '[Sites]', 'sites', 'index'
53 53 append_to menu_items, '[System config]', 'configurations', 'index'
54 54 menu_items << "<br/>"
55 55 end
56 56
57 57 # main page
58 58 append_to menu_items, "[#{I18n.t 'menu.main'}]", 'main', 'list'
59 59 append_to menu_items, "[#{I18n.t 'menu.messages'}]", 'messages', 'list'
60 60
61 61 if (user!=nil) and (GraderConfiguration.show_tasks_to?(user))
62 62 append_to menu_items, "[#{I18n.t 'menu.tasks'}]", 'tasks', 'list'
63 63 append_to menu_items, "[#{I18n.t 'menu.submissions'}]", 'main', 'submission'
64 64 append_to menu_items, "[#{I18n.t 'menu.test'}]", 'test', 'index'
65 65 end
66 66
67 67 if GraderConfiguration['right.user_hall_of_fame']
68 68 append_to menu_items, "[#{I18n.t 'menu.hall_of_fame'}]", 'report', 'problem_hof'
69 69 end
70 70 append_to menu_items, "[#{I18n.t 'menu.help'}]", 'main', 'help'
71 71
72 72 if GraderConfiguration['system.user_setting_enabled']
73 73 append_to menu_items, "[#{I18n.t 'menu.settings'}]", 'users', 'index'
74 74 end
75 75 append_to menu_items, "[#{I18n.t 'menu.log_out'}]", 'main', 'login'
76 76
77 77 menu_items.html_safe
78 78 end
79 79
80 80 def append_to(option,label, controller, action)
81 81 option << ' ' if option!=''
82 82 option << link_to_unless_current(label,
83 83 :controller => controller,
84 84 :action => action)
85 85 end
86 86
87 87 def format_short_time(time)
88 88 now = Time.now.gmtime
89 89 st = ''
90 90 if (time.yday != now.yday) or
91 91 (time.year != now.year)
92 92 st = time.strftime("%x ")
93 93 end
94 94 st + time.strftime("%X")
95 95 end
96 96
97 97 def format_short_duration(duration)
98 98 return '' if duration==nil
99 99 d = duration.to_f
100 100 return Time.at(d).gmtime.strftime("%X")
101 101 end
102 102
103 103 def read_textfile(fname,max_size=2048)
104 104 begin
105 105 File.open(fname).read(max_size)
106 106 rescue
107 107 nil
108 108 end
109 109 end
110 110
111 111 def toggle_button(on,toggle_url,id, option={})
112 112 btn_size = option[:size] || 'btn-xs'
113 113 link_to (on ? "Yes" : "No"), toggle_url,
114 114 {class: "btn btn-block #{btn_size} btn-#{on ? 'success' : 'default'} ajax-toggle",
115 115 id: id,
116 116 data: {remote: true, method: 'get'}}
117 117 end
118 118
119 119 def get_ace_mode(language)
120 120 # return ace mode string from Language
121 121
122 122 case language.pretty_name
123 123 when 'Pascal'
124 124 'ace/mode/pascal'
125 125 when 'C++','C'
126 126 'ace/mode/c_cpp'
127 127 when 'Ruby'
128 128 'ace/mode/ruby'
129 129 when 'Python'
130 130 'ace/mode/python'
131 131 when 'Java'
132 132 'ace/mode/java'
133 133 else
134 134 'ace/mode/c_cpp'
135 135 end
136 136 end
137 137
138 138
139 139 def user_title_bar(user)
140 140 header = ''
141 141 time_left = ''
142 142
143 143 #
144 144 # if the contest is over
145 145 if GraderConfiguration.time_limit_mode?
146 146 if user.contest_finished?
147 147 header = <<CONTEST_OVER
148 148 <tr><td colspan="2" align="center">
149 149 <span class="contest-over-msg">THE CONTEST IS OVER</span>
150 150 </td></tr>
151 151 CONTEST_OVER
152 152 end
153 153 if !user.contest_started?
154 154 time_left = "&nbsp;&nbsp;" + (t 'title_bar.contest_not_started')
155 155 else
156 156 time_left = "&nbsp;&nbsp;" + (t 'title_bar.remaining_time') +
157 157 " #{format_short_duration(user.contest_time_left)}"
158 158 end
159 159 end
160 160
161 161 #
162 162 # if the contest is in the anaysis mode
163 163 if GraderConfiguration.analysis_mode?
164 164 header = <<ANALYSISMODE
165 165 <tr><td colspan="2" align="center">
166 166 <span class="contest-over-msg">ANALYSIS MODE</span>
167 167 </td></tr>
168 168 ANALYSISMODE
169 169 end
170 170
171 171 contest_name = GraderConfiguration['contest.name']
172 172
173 173 #
174 174 # build real title bar
175 175 result = <<TITLEBAR
176 176 <div class="title">
177 177 <table>
178 178 #{header}
179 179 <tr>
180 180 <td class="left-col">
181 181 #{user.full_name}<br/>
182 182 #{t 'title_bar.current_time'} #{format_short_time(Time.zone.now)}
183 183 #{time_left}
184 184 <br/>
185 185 </td>
186 186 <td class="right-col">#{contest_name}</td>
187 187 </tr>
188 188 </table>
189 189 </div>
190 190 TITLEBAR
191 191 result.html_safe
192 192 end
193 193
194 194 def markdown(text)
195 195 markdown = RDiscount.new(text)
196 196 markdown.to_html.html_safe
197 197 end
198 198
199 +
200 + BOOTSTRAP_FLASH_MSG = {
201 + success: 'alert-success',
202 + error: 'alert-danger',
203 + alert: 'alert-block',
204 + notice: 'alert-info'
205 + }
206 +
207 + def bootstrap_class_for(flash_type)
208 + BOOTSTRAP_FLASH_MSG.fetch(flash_type.to_sym, flash_type.to_s)
199 209 end
210 +
211 + def flash_messages
212 + puts "flahs size = #{flash.count}"
213 + flash.each do |msg_type, message|
214 + concat(content_tag(:div, message, class: "alert #{bootstrap_class_for(msg_type)} fade in") do
215 + concat content_tag(:button, 'x', class: "close", data: { dismiss: 'alert' })
216 + concat message
217 + end)
218 + end
219 + nil
220 + end
221 +
222 + end
@@ -1,15 +1,16
1 1 <!DOCTYPE html>
2 2 %html
3 3 %head
4 4 %title= GraderConfiguration['contest.name']
5 5 = stylesheet_link_tag "application", params[:controller], :media => "all"
6 6 = javascript_include_tag "application", params[:controller]
7 7 = csrf_meta_tags
8 8 = content_for :header
9 9 = yield :head
10 10
11 11 %body
12 12 = render 'layouts/header'
13 13
14 - = content_tag(:p,flash[:notice],class: 'alert alert-success') if flash[:notice]!=nil
14 + /= content_tag(:p,flash[:notice],class: 'alert alert-success') if flash[:notice]!=nil
15 + = flash_messages
15 16 = yield
@@ -1,39 +1,41
1 1 %b= GraderConfiguration['ui.front.welcome_message']
2 2 %br/
3 3
4 4 - if !@hidelogin
5 5 =t 'login.message'
6 6 %br/
7 7 %br/
8 8
9 + - puts flash.inspect
9 10 - if flash[:notice]
10 11 %hr/
11 12 %b= flash[:notice]
13 + %b= haha
12 14 %hr/
13 15
14 16 %div{ :style => "border: solid 1px gray; padding: 4px; background: #eeeeff;"}
15 17 = form_tag login_login_path do
16 18 %table
17 19 %tr
18 20 %td{:align => "right"}
19 21 ="#{t 'login_label'}:"
20 22 %td= text_field_tag 'login'
21 23 %tr
22 24 %td{:align => "right"}
23 25 ="#{t 'password_label'}:"
24 26 %td= password_field_tag
25 27 - unless GraderConfiguration['right.bypass_agreement']
26 28 %tr
27 29 %td{:align => "right"}= check_box_tag 'accept_agree'
28 30 %td ยอมรับข้อตกลงการใช้งาน
29 31
30 32 = submit_tag t('login.login_submit')
31 33 %br/
32 34
33 35 - if GraderConfiguration['system.online_registration']
34 36 =t 'login.participation'
35 37 %b
36 38 = "#{t 'login.please'} "
37 39 = link_to "#{t 'login.register'}", :controller => :users, :action => :new
38 40 %br/
39 41 = link_to "#{t 'login.forget_password'}", :controller => :users, :action => :forget
@@ -1,29 +1,29
1 1
2 2 - if submission.nil?
3 3 = "-"
4 4 - else
5 5 - unless submission.graded_at
6 6 = t 'main.submitted_at'
7 7 = format_short_time(submission.submitted_at.localtime)
8 8 - else
9 9 %strong= t 'main.graded_at'
10 10 = "#{format_short_time(submission.graded_at.localtime)} "
11 11 %br
12 12 - if GraderConfiguration['ui.show_score']
13 13 %strong=t 'main.score'
14 14 = "#{(submission.points*100/submission.problem.full_score).to_i} "
15 15 = " ["
16 16 %tt
17 17 = submission.grader_comment
18 18 = "]"
19 19 %br
20 20 %strong View:
21 21 - if GraderConfiguration.show_grading_result
22 22 = link_to '[detailed result]', :action => 'result', :id => submission.id
23 23 /= link_to "#{t 'main.cmp_msg'}", {:action => 'compiler_msg', :id => submission.id}, {popup: true,class: 'btn btn-xs btn-info'}
24 24 = link_to "#{t 'main.cmp_msg'}", compiler_msg_submission_path(submission.id), {popup: true,remote: true,class: 'btn btn-xs btn-info'}
25 25 = link_to "#{t 'main.src_link'}",{:action => 'source', :id => submission.id}, class: 'btn btn-xs btn-info'
26 26 = link_to "#{t 'main.submissions_link'}", problem_submissions_path(problem_id), class: 'btn btn-xs btn-info'
27 27 - if GraderConfiguration.show_testcase
28 - = link_to "testcases", show_testcase_problem_path(problem_id), class: 'btn btn-xs btn-info'
28 + = link_to "testcases", show_problem_testcases_path(problem_id), class: 'btn btn-xs btn-info'
29 29
@@ -1,50 +1,54
1 1 - content_for :head do
2 2 = stylesheet_link_tag 'problems'
3 3 %h1 Listing problems
4 4 %p
5 5 = link_to 'New problem', new_problem_path, class: 'btn btn-default btn-sm'
6 6 = link_to 'Manage problems', { action: 'manage'}, class: 'btn btn-default btn-sm'
7 7 = link_to 'Import problems', {:action => 'import'}, class: 'btn btn-default btn-sm'
8 8 = link_to 'Turn off all problems', {:action => 'turn_all_off'}, class: 'btn btn-default btn-sm'
9 9 = link_to 'Turn on all problems', {:action => 'turn_all_on'}, class: 'btn btn-default btn-sm'
10 10 .submitbox
11 11 = form_tag :action => 'quick_create' do
12 12 %b Quick New:
13 13 %label{:for => "problem_name"} Name
14 14 = text_field 'problem', 'name'
15 15 |
16 16 %label{:for => "problem_full_name"} Full name
17 17 = text_field 'problem', 'full_name'
18 18 = submit_tag "Create"
19 19 %table.table.table-condense.table-hover
20 20 %thead
21 21 %th Name
22 22 %th Full name
23 23 %th.text-right Full score
24 24 %th Date added
25 25 %th.text-center
26 26 Avail?
27 27 %sup{class: 'text-primary',data: {toggle: 'tooltip'}, title: 'Let user submits to this problem?' } [?]
28 28 %th.text-center
29 + View Data?
30 + %sup{class: 'text-primary',data: {toggle: 'tooltip'}, title: 'Let user view the testcase of this problem?' } [?]
31 + %th.text-center
29 32 Test?
30 33 %sup{class: 'text-primary',data: {toggle: 'tooltip'}, title: 'Let user uses test interface on this problem?' } [?]
31 34 - if GraderConfiguration.multicontests?
32 35 %th Contests
33 36 - for problem in @problems
34 37 %tr{:class => "#{(problem.available) ? "success" : "danger"}", :id => "prob-#{problem.id}", :name => "prob-#{problem.id}"}
35 38 - @problem=problem
36 39 %td= problem.name #in_place_editor_field :problem, :name, {}, :rows=>1
37 40 %td= problem.full_name #in_place_editor_field :problem, :full_name, {}, :rows=>1
38 41 %td.text-right= problem.full_score #in_place_editor_field :problem, :full_score, {}, :rows=>1
39 42 %td= problem.date_added
40 43 %td= toggle_button(@problem.available?, toggle_problem_path(@problem), "problem-avail-#{@problem.id}")
44 + %td= toggle_button(@problem.view_testcase?, toggle_view_testcase_problem_path(@problem), "problem-view-testcase-#{@problem.id}")
41 45 %td= toggle_button(@problem.test_allowed?, toggle_test_problem_path(@problem), "problem-test-#{@problem.id}")
42 46 - if GraderConfiguration.multicontests?
43 47 %td
44 48 = problem.contests.collect { |c| c.name }.join(', ')
45 49 %td= link_to 'Stat', {:action => 'stat', :id => problem.id}, class: 'btn btn-info btn-xs btn-block'
46 50 %td= link_to 'Show', {:action => 'show', :id => problem}, class: 'btn btn-info btn-xs btn-block'
47 51 %td= link_to 'Edit', {:action => 'edit', :id => problem}, class: 'btn btn-info btn-xs btn-block'
48 52 %td= link_to 'Destroy', { :action => 'destroy', :id => problem }, :confirm => 'Are you sure?', :method => :delete, class: 'btn btn-danger btn-xs btn-block'
49 53 %br/
50 54 = link_to '[New problem]', :action => 'new'
@@ -1,88 +1,92
1 1 CafeGrader::Application.routes.draw do
2 2 get "sources/direct_edit"
3 3
4 4 root :to => 'main#login'
5 5
6 6 #logins
7 7 get 'login/login', to: 'login#login'
8 8
9 9 resources :contests
10 10
11 11 resources :sites
12 12
13 13 resources :announcements do
14 14 member do
15 15 get 'toggle','toggle_front'
16 16 end
17 17 end
18 18
19 19 resources :problems do
20 20 member do
21 21 get 'toggle'
22 22 get 'toggle_test'
23 + get 'toggle_view_testcase'
23 24 get 'stat'
24 - get 'show_testcase'
25 25 end
26 26 collection do
27 27 get 'turn_all_off'
28 28 get 'turn_all_on'
29 29 get 'import'
30 30 get 'manage'
31 31 end
32 32
33 + end
34 +
33 35 resources :testcases, only: [] do
34 36 member do
35 37 get 'download_input'
36 38 get 'download_sol'
37 39 end
40 + collection do
41 + get 'show_problem/:problem_id(/:test_num)' => 'testcases#show_problem', as: 'show_problem'
38 42 end
39 43 end
40 44
41 45 resources :grader_configuration, controller: 'configurations'
42 46
43 47 resources :users do
44 48 member do
45 49 get 'toggle_activate', 'toggle_enable'
46 50 get 'stat'
47 51 end
48 52 end
49 53
50 54 resources :submissions do
51 55 member do
52 56 get 'download'
53 57 get 'compiler_msg'
54 58 end
55 59 collection do
56 60 get 'prob/:problem_id', to: 'submissions#index', as: 'problem'
57 61 get 'direct_edit_problem/:problem_id', to: 'submissions#direct_edit_problem', as: 'direct_edit_problem'
58 62 get 'get_latest_submission_status/:uid/:pid', to: 'submissions#get_latest_submission_status', as: 'get_latest_submission_status'
59 63 end
60 64 end
61 65
62 66 get 'tasks/view/:file.:ext' => 'tasks#view'
63 67 get 'tasks/download/:id/:file.:ext' => 'tasks#download'
64 68 get 'heartbeat/:id/edit' => 'heartbeat#edit'
65 69
66 70 #main
67 71 get "main/list"
68 72 get 'main/submission(/:id)', to: 'main#submission', as: 'main_submission'
69 73
70 74 #report
71 75 get 'report/current_score', to: 'report#current_score', as: 'report_current_score'
72 76 get 'report/problem_hof(/:id)', to: 'report#problem_hof', as: 'report_problem_hof'
73 77 get "report/login"
74 78 get 'report/max_score', to: 'report#max_score', as: 'report_max_score'
75 79 post 'report/show_max_score', to: 'report#show_max_score', as: 'report_show_max_score'
76 80
77 81 #grader
78 82 get 'graders/list', to: 'graders#list', as: 'grader_list'
79 83
80 84
81 85 get 'heartbeat/:id/edit' => 'heartbeat#edit'
82 86
83 87 # See how all your routes lay out with "rake routes"
84 88
85 89 # This is a legacy wild controller route that's not recommended for RESTful applications.
86 90 # Note: This route will make all actions in every controller accessible via GET requests.
87 91 match ':controller(/:action(/:id))(.:format)', via: [:get, :post]
88 92 end
@@ -1,280 +1,281
1 1 # encoding: UTF-8
2 2 # This file is auto-generated from the current state of the database. Instead
3 3 # of editing this file, please use the migrations feature of Active Record to
4 4 # incrementally modify your database, and then regenerate this schema definition.
5 5 #
6 6 # Note that this schema.rb definition is the authoritative source for your
7 7 # database schema. If you need to create the application database on another
8 8 # system, you should be using db:schema:load, not running all the migrations
9 9 # from scratch. The latter is a flawed and unsustainable approach (the more migrations
10 10 # you'll amass, the slower it'll run and the greater likelihood for issues).
11 11 #
12 12 # It's strongly recommended that you check this file into your version control system.
13 13
14 - ActiveRecord::Schema.define(version: 20170123162543) do
14 + ActiveRecord::Schema.define(version: 20170124024527) do
15 15
16 16 create_table "announcements", force: :cascade do |t|
17 17 t.string "author", limit: 255
18 - t.text "body", limit: 16777215
18 + t.text "body", limit: 65535
19 19 t.boolean "published"
20 20 t.datetime "created_at", null: false
21 21 t.datetime "updated_at", null: false
22 22 t.boolean "frontpage", default: false
23 23 t.boolean "contest_only", default: false
24 24 t.string "title", limit: 255
25 25 t.string "notes", limit: 255
26 26 end
27 27
28 28 create_table "contests", force: :cascade do |t|
29 29 t.string "title", limit: 255
30 30 t.boolean "enabled"
31 31 t.datetime "created_at", null: false
32 32 t.datetime "updated_at", null: false
33 33 t.string "name", limit: 255
34 34 end
35 35
36 36 create_table "contests_problems", id: false, force: :cascade do |t|
37 37 t.integer "contest_id", limit: 4
38 38 t.integer "problem_id", limit: 4
39 39 end
40 40
41 41 create_table "contests_users", id: false, force: :cascade do |t|
42 42 t.integer "contest_id", limit: 4
43 43 t.integer "user_id", limit: 4
44 44 end
45 45
46 46 create_table "countries", force: :cascade do |t|
47 47 t.string "name", limit: 255
48 48 t.datetime "created_at", null: false
49 49 t.datetime "updated_at", null: false
50 50 end
51 51
52 52 create_table "descriptions", force: :cascade do |t|
53 - t.text "body", limit: 16777215
53 + t.text "body", limit: 65535
54 54 t.boolean "markdowned"
55 55 t.datetime "created_at", null: false
56 56 t.datetime "updated_at", null: false
57 57 end
58 58
59 59 create_table "grader_configurations", force: :cascade do |t|
60 60 t.string "key", limit: 255
61 61 t.string "value_type", limit: 255
62 62 t.string "value", limit: 255
63 63 t.datetime "created_at", null: false
64 64 t.datetime "updated_at", null: false
65 - t.text "description", limit: 16777215
65 + t.text "description", limit: 65535
66 66 end
67 67
68 68 create_table "grader_processes", force: :cascade do |t|
69 69 t.string "host", limit: 255
70 70 t.integer "pid", limit: 4
71 71 t.string "mode", limit: 255
72 72 t.boolean "active"
73 73 t.datetime "created_at", null: false
74 74 t.datetime "updated_at", null: false
75 75 t.integer "task_id", limit: 4
76 76 t.string "task_type", limit: 255
77 77 t.boolean "terminated"
78 78 end
79 79
80 80 add_index "grader_processes", ["host", "pid"], name: "index_grader_processes_on_ip_and_pid", using: :btree
81 81
82 82 create_table "heart_beats", force: :cascade do |t|
83 83 t.integer "user_id", limit: 4
84 84 t.string "ip_address", limit: 255
85 85 t.datetime "created_at", null: false
86 86 t.datetime "updated_at", null: false
87 87 t.string "status", limit: 255
88 88 end
89 89
90 90 add_index "heart_beats", ["updated_at"], name: "index_heart_beats_on_updated_at", using: :btree
91 91
92 92 create_table "languages", force: :cascade do |t|
93 93 t.string "name", limit: 10
94 94 t.string "pretty_name", limit: 255
95 95 t.string "ext", limit: 10
96 96 t.string "common_ext", limit: 255
97 97 end
98 98
99 99 create_table "logins", force: :cascade do |t|
100 100 t.integer "user_id", limit: 4
101 101 t.string "ip_address", limit: 255
102 102 t.datetime "created_at", null: false
103 103 t.datetime "updated_at", null: false
104 104 end
105 105
106 106 create_table "messages", force: :cascade do |t|
107 107 t.integer "sender_id", limit: 4
108 108 t.integer "receiver_id", limit: 4
109 109 t.integer "replying_message_id", limit: 4
110 - t.text "body", limit: 16777215
110 + t.text "body", limit: 65535
111 111 t.boolean "replied"
112 112 t.datetime "created_at", null: false
113 113 t.datetime "updated_at", null: false
114 114 end
115 115
116 116 create_table "problems", force: :cascade do |t|
117 117 t.string "name", limit: 30
118 118 t.string "full_name", limit: 255
119 119 t.integer "full_score", limit: 4
120 120 t.date "date_added"
121 121 t.boolean "available"
122 122 t.string "url", limit: 255
123 123 t.integer "description_id", limit: 4
124 124 t.boolean "test_allowed"
125 125 t.boolean "output_only"
126 126 t.string "description_filename", limit: 255
127 + t.boolean "view_testcase"
127 128 end
128 129
129 130 create_table "rights", force: :cascade do |t|
130 131 t.string "name", limit: 255
131 132 t.string "controller", limit: 255
132 133 t.string "action", limit: 255
133 134 end
134 135
135 136 create_table "rights_roles", id: false, force: :cascade do |t|
136 137 t.integer "right_id", limit: 4
137 138 t.integer "role_id", limit: 4
138 139 end
139 140
140 141 add_index "rights_roles", ["role_id"], name: "index_rights_roles_on_role_id", using: :btree
141 142
142 143 create_table "roles", force: :cascade do |t|
143 144 t.string "name", limit: 255
144 145 end
145 146
146 147 create_table "roles_users", id: false, force: :cascade do |t|
147 148 t.integer "role_id", limit: 4
148 149 t.integer "user_id", limit: 4
149 150 end
150 151
151 152 add_index "roles_users", ["user_id"], name: "index_roles_users_on_user_id", using: :btree
152 153
153 154 create_table "sessions", force: :cascade do |t|
154 155 t.string "session_id", limit: 255
155 - t.text "data", limit: 16777215
156 + t.text "data", limit: 65535
156 157 t.datetime "updated_at"
157 158 end
158 159
159 160 add_index "sessions", ["session_id"], name: "index_sessions_on_session_id", using: :btree
160 161 add_index "sessions", ["updated_at"], name: "index_sessions_on_updated_at", using: :btree
161 162
162 163 create_table "sites", force: :cascade do |t|
163 164 t.string "name", limit: 255
164 165 t.boolean "started"
165 166 t.datetime "start_time"
166 167 t.datetime "created_at", null: false
167 168 t.datetime "updated_at", null: false
168 169 t.integer "country_id", limit: 4
169 170 t.string "password", limit: 255
170 171 end
171 172
172 173 create_table "submission_view_logs", force: :cascade do |t|
173 174 t.integer "user_id", limit: 4
174 175 t.integer "submission_id", limit: 4
175 176 t.datetime "created_at", null: false
176 177 t.datetime "updated_at", null: false
177 178 end
178 179
179 180 create_table "submissions", force: :cascade do |t|
180 181 t.integer "user_id", limit: 4
181 182 t.integer "problem_id", limit: 4
182 183 t.integer "language_id", limit: 4
183 - t.text "source", limit: 16777215
184 + t.text "source", limit: 65535
184 185 t.binary "binary", limit: 65535
185 186 t.datetime "submitted_at"
186 187 t.datetime "compiled_at"
187 - t.text "compiler_message", limit: 16777215
188 + t.text "compiler_message", limit: 65535
188 189 t.datetime "graded_at"
189 190 t.integer "points", limit: 4
190 - t.text "grader_comment", limit: 16777215
191 + t.text "grader_comment", limit: 65535
191 192 t.integer "number", limit: 4
192 193 t.string "source_filename", limit: 255
193 194 t.float "max_runtime", limit: 24
194 195 t.integer "peak_memory", limit: 4
195 196 t.integer "effective_code_length", limit: 4
196 197 t.string "ip_address", limit: 255
197 198 end
198 199
199 200 add_index "submissions", ["user_id", "problem_id", "number"], name: "index_submissions_on_user_id_and_problem_id_and_number", unique: true, using: :btree
200 201 add_index "submissions", ["user_id", "problem_id"], name: "index_submissions_on_user_id_and_problem_id", using: :btree
201 202
202 203 create_table "tasks", force: :cascade do |t|
203 204 t.integer "submission_id", limit: 4
204 205 t.datetime "created_at"
205 206 t.integer "status", limit: 4
206 207 t.datetime "updated_at"
207 208 end
208 209
209 210 create_table "test_pairs", force: :cascade do |t|
210 211 t.integer "problem_id", limit: 4
211 - t.text "input", limit: 4294967295
212 - t.text "solution", limit: 4294967295
212 + t.text "input", limit: 16777215
213 + t.text "solution", limit: 16777215
213 214 t.datetime "created_at", null: false
214 215 t.datetime "updated_at", null: false
215 216 end
216 217
217 218 create_table "test_requests", force: :cascade do |t|
218 219 t.integer "user_id", limit: 4
219 220 t.integer "problem_id", limit: 4
220 221 t.integer "submission_id", limit: 4
221 222 t.string "input_file_name", limit: 255
222 223 t.string "output_file_name", limit: 255
223 224 t.string "running_stat", limit: 255
224 225 t.integer "status", limit: 4
225 226 t.datetime "updated_at", null: false
226 227 t.datetime "submitted_at"
227 228 t.datetime "compiled_at"
228 - t.text "compiler_message", limit: 16777215
229 + t.text "compiler_message", limit: 65535
229 230 t.datetime "graded_at"
230 231 t.string "grader_comment", limit: 255
231 232 t.datetime "created_at", null: false
232 233 t.float "running_time", limit: 24
233 234 t.string "exit_status", limit: 255
234 235 t.integer "memory_usage", limit: 4
235 236 end
236 237
237 238 add_index "test_requests", ["user_id", "problem_id"], name: "index_test_requests_on_user_id_and_problem_id", using: :btree
238 239
239 240 create_table "testcases", force: :cascade do |t|
240 241 t.integer "problem_id", limit: 4
241 242 t.integer "num", limit: 4
242 243 t.integer "group", limit: 4
243 244 t.integer "score", limit: 4
244 245 t.text "input", limit: 4294967295
245 246 t.text "sol", limit: 4294967295
246 - t.datetime "created_at", null: false
247 - t.datetime "updated_at", null: false
247 + t.datetime "created_at"
248 + t.datetime "updated_at"
248 249 end
249 250
250 251 add_index "testcases", ["problem_id"], name: "index_testcases_on_problem_id", using: :btree
251 252
252 253 create_table "user_contest_stats", force: :cascade do |t|
253 254 t.integer "user_id", limit: 4
254 255 t.datetime "started_at"
255 256 t.datetime "created_at", null: false
256 257 t.datetime "updated_at", null: false
257 258 t.boolean "forced_logout"
258 259 end
259 260
260 261 create_table "users", force: :cascade do |t|
261 262 t.string "login", limit: 50
262 263 t.string "full_name", limit: 255
263 264 t.string "hashed_password", limit: 255
264 265 t.string "salt", limit: 5
265 266 t.string "alias", limit: 255
266 267 t.string "email", limit: 255
267 268 t.integer "site_id", limit: 4
268 269 t.integer "country_id", limit: 4
269 270 t.boolean "activated", default: false
270 271 t.datetime "created_at"
271 272 t.datetime "updated_at"
272 - t.string "section", limit: 255
273 273 t.boolean "enabled", default: true
274 274 t.string "remark", limit: 255
275 275 t.string "last_ip", limit: 255
276 + t.string "section", limit: 255
276 277 end
277 278
278 279 add_index "users", ["login"], name: "index_users_on_login", unique: true, using: :btree
279 280
280 281 end
deleted file
You need to be logged in to leave comments. Login now