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

r429:82e5ff05bd49 - - 18 files changed: 295 inserted, 15 deleted

@@ -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,62
1 + :css
2 + .hof_user { color: orangered; font-style: italic; }
3 + .hof_language { color: green; font-style: italic; }
4 + .hof_value { color: deeppink;font-style: italic; }
5 +
6 + %h2 Overall
7 +
8 + - if @best
9 + %b Best Runtime:
10 + by <span class="hof_user">#{@best[:runtime][:user]}</span> using <span class="hof_language">#{@best[:runtime][:lang]}</span> with <span class="hof_value">#{@best[:runtime][:value] * 1000} milliseconds</span> at submission
11 + = link_to("#" + @best[:runtime][:sub_id].to_s, controller: 'graders', action: 'submission', id:@best[:runtime][:sub_id])
12 + %br/
13 +
14 + %b Best Memory Usage:
15 + by <span class="hof_user">#{@best[:memory][:user]}</span> using <span class="hof_language">#{@best[:memory][:lang]}</span> with <span class="hof_value">#{@best[:memory][:value]} kbytes </span> at submission
16 + = link_to("#" + @best[:memory][:sub_id].to_s, controller: 'graders' , action: 'submission', id:@best[:memory][:sub_id])
17 + %br/
18 +
19 + %b Shortest Code:
20 + by <span class="hof_user">#{@best[:length][:user]}</span> using <span class="hof_language">#{@best[:length][:lang]}</span> with <span class="hof_value">#{@best[:length][:value]} bytes</span> at submission
21 + = link_to("#" + @best[:length][:sub_id].to_s, controller: 'graders' , action: 'submission', id: @best[:length][:sub_id])
22 + %br/
23 +
24 + %b First solver:
25 + <span class="hof_user">#{@best[:first][:user]}</span> is the first solver using <span class="hof_language">#{@best[:first][:lang]}</span> on <span class="hof_value">#{@best[:first][:value]}</span> at submission
26 + = link_to("#" + @best[:first][:sub_id].to_s, controller: 'graders' , action: 'submission', id: @best[:first][:sub_id])
27 + %br/
28 +
29 +
30 + %p
31 + This counts only for submission with 100% score <br/>
32 + Right now, java is excluded from memory usage competition. (Because it always uses 2GB memory...)
33 +
34 + %h2 By language
35 +
36 + %table.info
37 + %thead
38 + %tr.info-head
39 + %th Language
40 + %th Best runtime (ms)
41 + %th Best memory (kbytes)
42 + %th Shortest Code (bytes)
43 + %th First solver
44 + %tbody
45 + - @by_lang.each do |lang,value|
46 + %tr{class: cycle('info-even','info-odd')}
47 + %td= lang
48 + %td
49 + = "#{value[:runtime][:user]} (#{(value[:runtime][:value] * 1000).to_i} @"
50 + = "#{link_to("#" + value[:runtime][:sub_id].to_s, controller: 'graders' , action: 'submission', id: value[:runtime][:sub_id])} )".html_safe
51 + %td
52 + = "#{value[:memory][:user]} (#{value[:memory][:value]} @"
53 + = "#{link_to("#" + value[:memory][:sub_id].to_s, controller: 'graders' , action: 'submission', id: value[:memory][:sub_id])} )".html_safe
54 + %td
55 + = "#{value[:length][:user]} (#{value[:length][:value]} @"
56 + = "#{link_to("#" + value[:length][:sub_id].to_s, controller: 'graders' , action: 'submission', id: value[:length][:sub_id])} )".html_safe
57 + %td
58 + = "#{value[:first][:user]} (#{value[:first][:value]} @"
59 + = "#{link_to("#" + value[:first][:sub_id].to_s, controller: 'graders' , action: 'submission', id: value[:first][:sub_id])} )".html_safe
60 +
61 + - else
62 + %h3 No submissions
@@ -0,0 +1,22
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 Tasks 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 +
22 +
@@ -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
@@ -1,56 +1,58
1 1 source 'https://rubygems.org'
2 2
3 3 gem 'rails', '3.2.19'
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.3'
14 14 gem 'coffee-rails', '~> 3.2.1'
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', '>= 1.0.3'
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 38 #
39 39
40 40 gem 'jquery-rails'
41 41 gem 'jquery-ui-sass-rails'
42 42 gem 'jquery-timepicker-addon-rails'
43 43
44 + #syntax highlighter
45 + gem 'rouge'
44 46
45 47 gem "haml"
46 48 gem "mail"
47 49 gem "rdiscount"
48 50 gem "test-unit"
49 51 gem 'will_paginate', '~> 3.0.0'
50 52 gem 'dynamic_form'
51 53 gem 'in_place_editing'
52 54 gem 'verification', :git => 'git://github.com/sikachu/verification.git'
53 55
54 56 group :test, :development do
55 57 gem "rspec-rails", "~> 2.0"
56 58 end
@@ -59,108 +59,110
59 59 railties (>= 3.0, < 5.0)
60 60 thor (>= 0.14, < 2.0)
61 61 jquery-timepicker-addon-rails (1.4.1)
62 62 railties (>= 3.1)
63 63 jquery-ui-rails (4.0.3)
64 64 jquery-rails
65 65 railties (>= 3.1.0)
66 66 jquery-ui-sass-rails (4.0.3.0)
67 67 jquery-rails
68 68 jquery-ui-rails (= 4.0.3)
69 69 railties (>= 3.1.0)
70 70 json (1.8.1)
71 71 mail (2.5.4)
72 72 mime-types (~> 1.16)
73 73 treetop (~> 1.4.8)
74 74 mime-types (1.25.1)
75 75 multi_json (1.10.1)
76 76 mysql2 (0.3.16)
77 77 polyglot (0.3.5)
78 78 power_assert (0.1.3)
79 79 prototype-rails (3.2.1)
80 80 rails (~> 3.2)
81 81 rack (1.4.5)
82 82 rack-cache (1.2)
83 83 rack (>= 0.4)
84 84 rack-ssl (1.3.4)
85 85 rack
86 86 rack-test (0.6.2)
87 87 rack (>= 1.0)
88 88 rails (3.2.19)
89 89 actionmailer (= 3.2.19)
90 90 actionpack (= 3.2.19)
91 91 activerecord (= 3.2.19)
92 92 activeresource (= 3.2.19)
93 93 activesupport (= 3.2.19)
94 94 bundler (~> 1.0)
95 95 railties (= 3.2.19)
96 96 railties (3.2.19)
97 97 actionpack (= 3.2.19)
98 98 activesupport (= 3.2.19)
99 99 rack-ssl (~> 1.3.2)
100 100 rake (>= 0.8.7)
101 101 rdoc (~> 3.4)
102 102 thor (>= 0.14.6, < 2.0)
103 103 rake (10.3.2)
104 104 rdiscount (2.1.7.1)
105 105 rdoc (3.12.2)
106 106 json (~> 1.4)
107 + rouge (1.6.2)
107 108 rspec-collection_matchers (1.0.0)
108 109 rspec-expectations (>= 2.99.0.beta1)
109 110 rspec-core (2.99.2)
110 111 rspec-expectations (2.99.2)
111 112 diff-lcs (>= 1.1.3, < 2.0)
112 113 rspec-mocks (2.99.2)
113 114 rspec-rails (2.99.0)
114 115 actionpack (>= 3.0)
115 116 activemodel (>= 3.0)
116 117 activesupport (>= 3.0)
117 118 railties (>= 3.0)
118 119 rspec-collection_matchers
119 120 rspec-core (~> 2.99.0)
120 121 rspec-expectations (~> 2.99.0)
121 122 rspec-mocks (~> 2.99.0)
122 123 sass (3.4.1)
123 124 sass-rails (3.2.6)
124 125 railties (~> 3.2.0)
125 126 sass (>= 3.1.10)
126 127 tilt (~> 1.3)
127 128 sprockets (2.2.2)
128 129 hike (~> 1.2)
129 130 multi_json (~> 1.0)
130 131 rack (~> 1.0)
131 132 tilt (~> 1.1, != 1.3.0)
132 133 test-unit (3.0.1)
133 134 power_assert
134 135 thor (0.19.1)
135 136 tilt (1.4.1)
136 137 treetop (1.4.15)
137 138 polyglot
138 139 polyglot (>= 0.3.1)
139 140 tzinfo (0.3.41)
140 141 uglifier (2.5.3)
141 142 execjs (>= 0.3.0)
142 143 json (>= 1.8.0)
143 144 will_paginate (3.0.7)
144 145
145 146 PLATFORMS
146 147 ruby
147 148
148 149 DEPENDENCIES
149 150 coffee-rails (~> 3.2.1)
150 151 dynamic_form
151 152 haml
152 153 in_place_editing
153 154 jquery-rails
154 155 jquery-timepicker-addon-rails
155 156 jquery-ui-sass-rails
156 157 mail
157 158 mysql2
158 159 prototype-rails
159 160 rails (= 3.2.19)
160 161 rdiscount
162 + rouge
161 163 rspec-rails (~> 2.0)
162 164 sass-rails (~> 3.2.3)
163 165 test-unit
164 166 uglifier (>= 1.0.3)
165 167 verification!
166 168 will_paginate (~> 3.0.0)
@@ -1,73 +1,82
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 + unless user.admin?
10 + flash[:notice] = 'You are not authorized to view the page you requested'
9 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)
@@ -1,96 +1,117
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')
26 35 end
27 36
28 37 def clear
29 38 grader_proc = GraderProcess.find(params[:id])
30 39 grader_proc.destroy if grader_proc!=nil
31 40 redirect_to :action => 'list'
32 41 end
33 42
34 43 def clear_terminated
35 44 GraderProcess.find_terminated_graders.each do |p|
36 45 p.destroy
37 46 end
38 47 redirect_to :action => 'list'
39 48 end
40 49
41 50 def clear_all
42 51 GraderProcess.find(:all).each do |p|
43 52 p.destroy
44 53 end
45 54 redirect_to :action => 'list'
46 55 end
47 56
48 57 def view
49 58 if params[:type]=='Task'
50 59 redirect_to :action => 'task', :id => params[:id]
51 60 else
52 61 redirect_to :action => 'test_request', :id => params[:id]
53 62 end
54 63 end
55 64
56 65 def test_request
57 66 @test_request = TestRequest.find(params[:id])
58 67 end
59 68
60 69 def task
61 70 @task = Task.find(params[:id])
62 71 end
63 72
64 73 def submission
65 74 @submission = Submission.find(params[:id])
75 + formatter = Rouge::Formatters::HTML.new(css_class: 'highlight', line_numbers: true )
76 + lexer = case @submission.language.name
77 + when "c" then Rouge::Lexers::C.new
78 + when "cpp" then Rouge::Lexers::Cpp.new
79 + when "pas" then Rouge::Lexers::Pas.new
80 + when "ruby" then Rouge::Lexers::Ruby.new
81 + when "python" then Rouge::Lexers::Python.new
82 + when "java" then Rouge::Lexers::Java.new
83 + end
84 + @formatted_code = formatter.format(lexer.lex(@submission.source))
85 + @css_style = Rouge::Themes::ThankfulEyes.render(scope: '.highlight')
86 +
66 87 end
67 88
68 89 # various grader controls
69 90
70 91 def stop
71 92 grader_proc = GraderProcess.find(params[:id])
72 93 GraderScript.stop_grader(grader_proc.pid)
73 94 flash[:notice] = 'Grader stopped. It may not disappear now, but it should disappear shortly.'
74 95 redirect_to :action => 'list'
75 96 end
76 97
77 98 def stop_all
78 99 GraderScript.stop_graders(GraderProcess.find_running_graders +
79 100 GraderProcess.find_stalled_process)
80 101 flash[:notice] = 'Graders stopped. They may not disappear now, but they should disappear shortly.'
81 102 redirect_to :action => 'list'
82 103 end
83 104
84 105 def start_grading
85 106 GraderScript.start_grader('grading')
86 107 flash[:notice] = '2 graders in grading env started, one for grading queue tasks, another for grading test request'
87 108 redirect_to :action => 'list'
88 109 end
89 110
90 111 def start_exam
91 112 GraderScript.start_grader('exam')
92 113 flash[:notice] = '2 graders in grading env started, one for grading queue tasks, another for grading test request'
93 114 redirect_to :action => 'list'
94 115 end
95 116
96 117 end
@@ -1,72 +1,188
1 1 class ReportController < ApplicationController
2 +
3 + before_filter :admin_authorization, only: [:login_stat,:submission_stat]
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 +
2 14 def login_stat
3 15 @logins = Array.new
4 16
5 17 date_and_time = '%Y-%m-%d %H:%M'
6 18 begin
7 19 @since_time = DateTime.strptime(params[:since_datetime],date_and_time)
8 20 rescue
9 21 @since_time = DateTime.new(1000,1,1)
10 22 end
11 23 begin
12 24 @until_time = DateTime.strptime(params[:until_datetime],date_and_time)
13 25 rescue
14 26 @until_time = DateTime.new(3000,1,1)
15 27 end
16 28
17 29 User.all.each do |user|
18 30 @logins << { login: user.login,
19 31 full_name: user.full_name,
20 32 count: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
21 33 user.id,@since_time,@until_time)
22 34 .count(:id),
23 35 min: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
24 36 user.id,@since_time,@until_time)
25 37 .minimum(:created_at),
26 38 max: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
27 39 user.id,@since_time,@until_time)
28 40 .maximum(:created_at)
29 41 }
30 42 end
31 43 end
32 44
33 45 def submission_stat
34 46
35 47 date_and_time = '%Y-%m-%d %H:%M'
36 48 begin
37 49 @since_time = DateTime.strptime(params[:since_datetime],date_and_time)
38 50 rescue
39 51 @since_time = DateTime.new(1000,1,1)
40 52 end
41 53 begin
42 54 @until_time = DateTime.strptime(params[:until_datetime],date_and_time)
43 55 rescue
44 56 @until_time = DateTime.new(3000,1,1)
45 57 end
46 58
47 59 @submissions = {}
48 60
49 61 User.find_each do |user|
50 62 @submissions[user.id] = { login: user.login, full_name: user.full_name, count: 0, sub: { } }
51 63 end
52 64
53 65 Submission.where("submitted_at >= ? AND submitted_at <= ?",@since_time,@until_time).find_each do |s|
54 66 if @submissions[s.user_id]
55 67 if not @submissions[s.user_id][:sub].has_key?(s.problem_id)
56 68 a = nil
57 69 begin
58 70 a = Problem.find(s.problem_id)
59 71 rescue
60 72 a = nil
61 73 end
62 74 @submissions[s.user_id][:sub][s.problem_id] =
63 75 { prob_name: (a ? a.full_name : '(NULL)'),
64 76 sub_ids: [s.id] }
65 77 else
66 78 @submissions[s.user_id][:sub][s.problem_id][:sub_ids] << s.id
67 79 end
68 80 @submissions[s.user_id][:count] += 1
69 81 end
70 82 end
71 83 end
84 +
85 + def problem_hof
86 + # gen problem list
87 + @user = User.find(session[:user_id])
88 + @problems = @user.available_problems
89 +
90 + # get selected problems or the default
91 + if params[:id]
92 + begin
93 + @problem = Problem.available.find(params[:id])
94 + rescue
95 + redirect_to action: :problem_hof
96 + flash[:notice] = 'Error: submissions for that problem are not viewable.'
97 + return
72 98 end
99 + end
100 +
101 + if @problem
102 + #aggregrate by language
103 + @by_lang = {}
104 + Submission.where(problem_id: @problem.id).find_each do |sub|
105 + lang = Language.find_by_id(sub.language_id)
106 + next unless lang
107 + next unless sub.points >= @problem.full_score
108 +
109 + #initialize
110 + unless @by_lang.has_key?(lang.pretty_name)
111 + @by_lang[lang.pretty_name] = {
112 + runtime: { avail: false, value: 2**30-1 },
113 + memory: { avail: false, value: 2**30-1 },
114 + length: { avail: false, value: 2**30-1 },
115 + first: { avail: false, value: DateTime.new(3000,1,1) }
116 + }
117 + end
118 +
119 + if sub.max_runtime and sub.max_runtime < @by_lang[lang.pretty_name][:runtime][:value]
120 + @by_lang[lang.pretty_name][:runtime] = {
121 + avail: true,
122 + user_id: sub.user_id,
123 + value: sub.max_runtime,
124 + sub_id: sub.id
125 + }
126 + end
127 +
128 + if sub.peak_memory and sub.peak_memory < @by_lang[lang.pretty_name][:memory][:value]
129 + @by_lang[lang.pretty_name][:memory] = {
130 + avail: true,
131 + user_id: sub.user_id,
132 + value: sub.peak_memory,
133 + sub_id: sub.id
134 + }
135 + end
136 +
137 + if sub.submitted_at and sub.submitted_at < @by_lang[lang.pretty_name][:first][:value] and
138 + !sub.user.admin?
139 + @by_lang[lang.pretty_name][:first] = {
140 + avail: true,
141 + user_id: sub.user_id,
142 + value: sub.submitted_at,
143 + sub_id: sub.id
144 + }
145 + end
146 +
147 + if @by_lang[lang.pretty_name][:length][:value] > sub.effective_code_length
148 + @by_lang[lang.pretty_name][:length] = {
149 + avail: true,
150 + user_id: sub.user_id,
151 + value: sub.effective_code_length,
152 + sub_id: sub.id
153 + }
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]
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 + end
187 + end
188 + end
@@ -1,80 +1,84
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 16 append_to menu_items, '[Report]', 'report', 'login_stat'
17 17 append_to menu_items, '[Graders]', 'graders', 'list'
18 18 append_to menu_items, '[Contests]', 'contest_management', 'index'
19 19 append_to menu_items, '[Sites]', 'sites', 'index'
20 20 append_to menu_items, '[System config]', 'configurations', 'index'
21 21 menu_items << "<br/>"
22 22 end
23 23
24 24 # main page
25 25 append_to menu_items, "[#{I18n.t 'menu.main'}]", 'main', 'list'
26 26 append_to menu_items, "[#{I18n.t 'menu.messages'}]", 'messages', 'list'
27 27
28 28 if (user!=nil) and (GraderConfiguration.show_tasks_to?(user))
29 29 append_to menu_items, "[#{I18n.t 'menu.tasks'}]", 'tasks', 'list'
30 30 append_to menu_items, "[#{I18n.t 'menu.submissions'}]", 'main', 'submission'
31 31 append_to menu_items, "[#{I18n.t 'menu.test'}]", 'test', 'index'
32 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
33 37 append_to menu_items, "[#{I18n.t 'menu.help'}]", 'main', 'help'
34 38
35 39 if GraderConfiguration['system.user_setting_enabled']
36 40 append_to menu_items, "[#{I18n.t 'menu.settings'}]", 'users', 'index'
37 41 end
38 42 append_to menu_items, "[#{I18n.t 'menu.log_out'}]", 'main', 'login'
39 43
40 44 menu_items.html_safe
41 45 end
42 46
43 47 def append_to(option,label, controller, action)
44 48 option << ' ' if option!=''
45 49 option << link_to_unless_current(label,
46 50 :controller => controller,
47 51 :action => action)
48 52 end
49 53
50 54 def format_short_time(time)
51 55 now = Time.now.gmtime
52 56 st = ''
53 57 if (time.yday != now.yday) or
54 58 (time.year != now.year)
55 59 st = time.strftime("%x ")
56 60 end
57 61 st + time.strftime("%X")
58 62 end
59 63
60 64 def format_short_duration(duration)
61 65 return '' if duration==nil
62 66 d = duration.to_f
63 67 return Time.at(d).gmtime.strftime("%X")
64 68 end
65 69
66 70 def read_textfile(fname,max_size=2048)
67 71 begin
68 72 File.open(fname).read(max_size)
69 73 rescue
70 74 nil
71 75 end
72 76 end
73 77
74 78 def user_title_bar(user)
75 79 header = ''
76 80 time_left = ''
77 81
78 82 #
79 83 # if the contest is over
80 84 if GraderConfiguration.time_limit_mode?
@@ -1,22 +1,27
1 + %style{type: "text/css"}
2 + = @css_style
3 +
1 4 %h1= "Submission: #{@submission.id}"
2 5
3 6 %p
4 7 User:
5 - = "#{@submission.user.login}"
8 + = "(#{@submission.user.login}) #{@submission.user.full_name}"
6 9 %br/
7 10 Problem:
8 11 - if @submission.problem!=nil
9 12 = "#{@submission.problem.full_name}"
10 13 - else
11 14 = "(n/a)"
12 15 %br/
13 16 = "Number: #{@submission.number}"
14 17 %br/
15 18 = "Submitted at: #{format_short_time(@submission.submitted_at)}"
19 + %br/
20 + = "Points : #{@submission.points}/#{@submission.problem.full_score}"
21 + %br/
22 + = "Comment : #{@submission.grader_comment}"
16 23
17 24 %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
25 + //%div.highlight{:style => "border: 1px solid black;"}
26 + =@formatted_code.html_safe
22 27
@@ -1,6 +1,7
1 1
2 2 .task-menu
3 3 Reports
4 4 %br/
5 + = link_to '[Hall of Fame]', :action => 'problem_hof'
5 6 = link_to '[Submission]', :action => 'submission_stat'
6 7 = link_to '[Login]', :action => 'login_stat'
@@ -1,33 +1,31
1 1 - content_for :header do
2 2 = javascript_include_tag 'new'
3 3
4 4 %script{:type=>"text/javascript"}
5 5 $(function () {
6 6 $('#since_datetime').datetimepicker({ showButtonPanel: true, dateFormat: "yy-mm-dd", controlType: "slider"} );
7 7 $('#until_datetime').datetimepicker({ showButtonPanel: true, dateFormat: "yy-mm-dd", controlType: "slider"} );
8 8 });
9 9
10 -
11 10 %h1 Login status
12 11
13 -
14 12 =render partial: 'report_menu'
15 13 =render partial: 'date_range', locals: {param_text: 'Login date range:', title: 'Query login stat in the range' }
16 14
17 15 %table.info
18 16 %thead
19 17 %tr.info-head
20 18 %th login
21 19 %th full name
22 20 %th login count
23 21 %th earliest
24 22 %th latest
25 23 %tbody
26 24 - @logins.each do |l|
27 25 %tr{class: cycle('info-even','info-odd')}
28 26 %td= l[:login]
29 27 %td= l[:full_name]
30 28 %td= l[:count]
31 29 %td= l[:min] ? l[:min].in_time_zone.strftime('%Y-%m-%d %H:%M') : ''
32 30 %td= l[:max] ? l[:max].in_time_zone.strftime('%Y-%m-%d %H:%M') : ''
33 31
@@ -1,41 +1,37
1 1 - content_for :header do
2 2 = javascript_include_tag 'new'
3 3
4 4 %script{:type=>"text/javascript"}
5 5 $(function () {
6 6 $('#since_datetime').datetimepicker({ showButtonPanel: true, dateFormat: "yy-mm-dd", controlType: "slider"} );
7 7 $('#until_datetime').datetimepicker({ showButtonPanel: true, dateFormat: "yy-mm-dd", controlType: "slider"} );
8 8 });
9 9
10 -
11 10 %h1 Login status
12 11
13 -
14 -
15 -
16 12 =render partial: 'report_menu'
17 13 =render partial: 'date_range', locals: {param_text: 'Submission date range:', title: 'Query submission stat in the range' }
18 14
19 15 %table.info
20 16 %thead
21 17 %tr.info-head
22 18 %th login
23 19 %th full name
24 20 %th total submissions
25 21 %th submissions
26 22 %tbody
27 23 - @submissions.each do |user_id,data|
28 24 %tr{class: cycle('info-even','info-odd')}
29 25 %td= data[:login]
30 26 %td= data[:full_name]
31 27 %td= data[:count]
32 28 %td
33 29 - data[:sub].each do |prob_id,sub_data|
34 30 = "#{sub_data[:prob_name]}: ["
35 31 - st = []
36 32 - sub_data[:sub_ids].each do |id|
37 33 - st << link_to(id, controller: 'graders' , action: 'submission', id: id)
38 34 = raw st.join ', '
39 35 = ']'
40 36 %br/
41 37
@@ -14,51 +14,51
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 - config.assets.precompile += ['announcement_refresh.js','effects.js','site_update.js','graders.css','problems.css']
62 + config.assets.precompile += ['announcement_refresh.js','effects.js','site_update.js','graders.css','problems.css','new.js']
63 63 end
64 64 end
@@ -1,70 +1,71
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
@@ -1,70 +1,71
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 ตัวอักษร"
@@ -9,130 +9,144
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 + },
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.'
61 68 },
62 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
@@ -149,50 +163,61
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
You need to be logged in to leave comments. Login now