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

r685:101d67319b73 - - 43 files changed: 525 inserted, 159 deleted

@@ -0,0 +1,86
1 + class GroupsController < ApplicationController
2 + before_action :set_group, only: [:show, :edit, :update, :destroy,
3 + :add_user, :remove_user,
4 + :add_problem, :remove_problem,
5 + ]
6 + before_action :authenticate, :admin_authorization
7 +
8 + # GET /groups
9 + def index
10 + @groups = Group.all
11 + end
12 +
13 + # GET /groups/1
14 + def show
15 + end
16 +
17 + # GET /groups/new
18 + def new
19 + @group = Group.new
20 + end
21 +
22 + # GET /groups/1/edit
23 + def edit
24 + end
25 +
26 + # POST /groups
27 + def create
28 + @group = Group.new(group_params)
29 +
30 + if @group.save
31 + redirect_to @group, notice: 'Group was successfully created.'
32 + else
33 + render :new
34 + end
35 + end
36 +
37 + # PATCH/PUT /groups/1
38 + def update
39 + if @group.update(group_params)
40 + redirect_to @group, notice: 'Group was successfully updated.'
41 + else
42 + render :edit
43 + end
44 + end
45 +
46 + # DELETE /groups/1
47 + def destroy
48 + @group.destroy
49 + redirect_to groups_url, notice: 'Group was successfully destroyed.'
50 + end
51 +
52 + def remove_user
53 + user = User.find(params[:user_id])
54 + @group.users.delete(user)
55 + redirect_to group_path(@group), notice: "User #{user.login} was removed from the group #{@group.name}"
56 + end
57 +
58 + def add_user
59 + user = User.find(params[:user_id])
60 + @group.users << user
61 + redirect_to group_path(@group), notice: "User #{user.login} was add to the group #{@group.name}"
62 + end
63 +
64 + def remove_problem
65 + problem = Problem.find(params[:problem_id])
66 + @group.problems.delete(problem)
67 + redirect_to group_path(@group), notice: "Problem #{problem.name} was removed from the group #{@group.name}"
68 + end
69 +
70 + def add_problem
71 + problem = Problem.find(params[:problem_id])
72 + @group.problems << problem
73 + redirect_to group_path(@group), notice: "Problem #{problem.name} was add to the group #{@group.name}"
74 + end
75 +
76 + private
77 + # Use callbacks to share common setup or constraints between actions.
78 + def set_group
79 + @group = Group.find(params[:id])
80 + end
81 +
82 + # Only allow a trusted parameter "white list" through.
83 + def group_params
84 + params.require(:group).permit(:name, :description)
85 + end
86 + end
@@ -0,0 +1,2
1 + module GroupsHelper
2 + end
@@ -0,0 +1,5
1 + class Group < ActiveRecord::Base
2 + has_and_belongs_to_many :problems
3 + has_and_belongs_to_many :users
4 + end
5 +
@@ -0,0 +1,20
1 + %h1 Editing contest
2 + = form_for(@contest) do |f|
3 + = f.error_messages
4 + %table
5 + %tr
6 + %td= f.label :name
7 + %td= f.text_field :name
8 + %tr
9 + %td= f.label :title
10 + %td= f.text_field :title
11 + %tr
12 + %td
13 + %td
14 + = f.check_box :enabled
15 + = f.label :enabled
16 + %p
17 + = f.submit 'Update'
18 + = link_to 'Show', @contest
19 + |
20 + = link_to 'Back', contests_path
@@ -0,0 +1,27
1 + %h1 Listing contests
2 + .infobox
3 + %b Go back to:
4 + [#{link_to 'contest management', :controller => 'contest_management', :action => 'index'}]
5 + %p= link_to 'New contest', new_contest_path, class: 'btn btn-success'
6 + %table.table.table-striped
7 + %tr
8 + %th Name
9 + %th Title
10 + %th Enabled
11 + %th
12 + %th
13 + %th
14 +
15 + - @contests.each do |contest|
16 + - @contest = contest
17 + %tr
18 + -#%td= in_place_editor_field :contest, :name, {}, :rows => 1
19 + -#%td= in_place_editor_field :contest, :title, {}, :rows => 1
20 + -#%td= in_place_editor_field :contest, :enabled, {}, :rows => 1
21 + %td= best_in_place @contest, :name
22 + %td= best_in_place @contest, :title
23 + %td= best_in_place @contest, :enabled
24 + %td= link_to 'Show', contest
25 + %td= link_to 'Edit', edit_contest_path(contest)
26 + %td= link_to 'Destroy', contest, :confirm => 'Are you sure?', :method => :delete
27 + %br/
@@ -0,0 +1,18
1 + %h1 New contest
2 + = form_for(@contest) do |f|
3 + = f.error_messages
4 + %p
5 + = f.label :name
6 + %br/
7 + = f.text_field :name
8 + %p
9 + = f.label :title
10 + %br/
11 + = f.text_field :title
12 + %p
13 + = f.label :enabled
14 + %br/
15 + = f.check_box :enabled
16 + %p
17 + = f.submit 'Create'
18 + = link_to 'Back', contests_path
@@ -0,0 +1,11
1 + %h1
2 + Contest: #{h @contest.title}
3 + .infobox
4 + %b Go back to:
5 + [#{link_to 'contest management', :controller => 'contest_management', :action => 'index'}]
6 + %p
7 + %b Enabled:
8 + = h @contest.enabled
9 + = link_to 'Edit', edit_contest_path(@contest)
10 + |
11 + = link_to 'Back', contests_path
@@ -0,0 +1,16
1 + = form_for @group do |f|
2 + - if @group.errors.any?
3 + #error_explanation
4 + %h2= "#{pluralize(@group.errors.count, "error")} prohibited this group from being saved:"
5 + %ul
6 + - @group.errors.full_messages.each do |msg|
7 + %li= msg
8 +
9 + .form-group.field
10 + = f.label :name
11 + = f.text_field :name, class: 'form-control'
12 + .form-group.field
13 + = f.label :description
14 + = f.text_field :description, class: 'form-control'
15 + .form-group.actions
16 + = f.submit 'Save', class: 'btn btn-primary'
@@ -0,0 +1,7
1 + %h1 Editing group
2 +
3 + = render 'form'
4 +
5 + = link_to 'Show', @group
6 + \|
7 + = link_to 'Back', groups_path
@@ -0,0 +1,22
1 + %h1 Groups
2 +
3 + %p
4 + = link_to 'New Group', new_group_path, class: 'btn btn-primary'
5 + %table.table.table-hover
6 + %thead
7 + %tr
8 + %th Name
9 + %th Description
10 + %th
11 + %th
12 +
13 + %tbody
14 + - @groups.each do |group|
15 + %tr
16 + %td= group.name
17 + %td= group.description
18 + %td= link_to 'View', group, class: 'btn btn-default'
19 + %td= link_to 'Destroy', group, :method => :delete, :data => { :confirm => 'Are you sure?' }, class: 'btn btn-danger'
20 +
21 + %br
22 +
@@ -0,0 +1,5
1 + %h1 New group
2 +
3 + = render 'form'
4 +
5 + = link_to 'Back', groups_path
@@ -0,0 +1,72
1 + %p
2 + %b Name:
3 + = @group.name
4 + %p
5 + %b Description:
6 + = @group.description
7 +
8 + %br
9 + = link_to 'Edit', edit_group_path(@group)
10 + \|
11 + = link_to 'Back', groups_path
12 +
13 + .row
14 + %h1 Group details
15 + .row
16 + .col-md-6
17 + .panel.panel-default
18 + .panel-heading
19 + .panel-title Users in this group
20 + .panel-body
21 + =form_tag add_user_group_path(@group), class: 'form-inline' do
22 + .form-group
23 + =label_tag :user_id, "User"
24 + =select_tag :user_id, options_from_collection_for_select(User.all,'id','full_name'), class: 'select2'
25 + =submit_tag "Add",class: 'btn btn-primary'
26 +
27 +
28 + %table.table.table-hover
29 + %thead
30 + %tr
31 + %th Login
32 + %th Full name
33 + %th Remark
34 + %th
35 +
36 + %tbody
37 + - @group.users.each do |user|
38 + %tr
39 + %td= user.login
40 + %td= user.full_name
41 + %td= user.remark
42 + %td= link_to 'Remove', remove_user_group_path(@group,user), :method => :delete, :data => { :confirm => "Remove #{user.full_name}?" }, class: 'btn btn-danger'
43 + .col-md-6
44 + .panel.panel-default
45 + .panel-heading
46 + .panel-title Problems
47 + .panel-body
48 +
49 + =form_tag add_problem_group_path(@group), class: 'form-inline' do
50 + .form-group
51 + =label_tag :problem_id, "Problem"
52 + =select_tag :problem_id, options_from_collection_for_select(Problem.all,'id','full_name'), class: 'select2'
53 + =submit_tag "Add",class: 'btn btn-primary'
54 +
55 +
56 + %table.table.table-hover
57 + %thead
58 + %tr
59 + %th name
60 + %th Full name
61 + %th Full score
62 + %th
63 +
64 + %tbody
65 + - @group.problems.each do |problem|
66 + %tr
67 + %td= problem.name
68 + %td= problem.full_name
69 + %td= problem.full_score
70 + %td= link_to 'Remove', remove_problem_group_path(@group,problem), :method => :delete, :data => { :confirm => "Remove #{problem.full_name}?" }, class: 'btn btn-danger'
71 +
72 +
@@ -0,0 +1,31
1 + = error_messages_for 'user'
2 + / [form:user]
3 + .form-group
4 + %label.col-md-2.control-label{for: :login} Login
5 + .col-md-4
6 + = text_field 'user', 'login', class: 'form-control'
7 + .form-group
8 + %label.col-md-2.control-label{for: :full_name} Full name
9 + .col-md-4
10 + = text_field 'user', 'full_name', class: 'form-control'
11 + .form-group
12 + %label.col-md-2.control-label{for: :password} Password
13 + .col-md-4
14 + = password_field 'user', 'password', class: 'form-control'
15 + .form-group
16 + %label.col-md-2.control-label{for: :password_confirmation} Password (confirm)
17 + .col-md-4
18 + = password_field 'user', 'password_confirmation', class: 'form-control'
19 + .form-group
20 + %label.col-md-2.control-label{for: :email} E-mail
21 + .col-md-4
22 + = email_field 'user', 'email', class: 'form-control'
23 + .form-group
24 + %label.col-md-2.control-label{for: :alias} Alias
25 + .col-md-4
26 + = text_field 'user', 'alias', class: 'form-control'
27 + .form-group
28 + %label.col-md-2.control-label{for: :remark} Remark
29 + .col-md-4
30 + = text_field 'user', 'remark', class: 'form-control'
31 + / [eoform:user]
@@ -0,0 +1,14
1 + %h1 User information
2 + - for column in User.content_columns
3 + %p
4 + %b
5 + = column.human_name
6 + \:
7 + = h @user.send(column.name)
8 + %p
9 + %strong Group
10 + \:
11 + = @user.groups.map{ |x| link_to(x.name,group_path(x)).html_safe}.join(', ').html_safe
12 + = link_to 'Edit', :action => 'edit', :id => @user
13 + |
14 + = link_to 'Back', :action => 'index'
@@ -0,0 +1,30
1 + class CreateGroups < ActiveRecord::Migration
2 +
3 + def change
4 + create_table :groups do |t|
5 + t.string :name
6 + t.string :description
7 + end
8 +
9 + create_join_table :groups, :users do |t|
10 + # t.index [:group_id, :user_id]
11 + t.index [:user_id, :group_id]
12 + end
13 +
14 + create_join_table :problems, :groups do |t|
15 + # t.index [:problem_id, :group_id]
16 + t.index [:group_id, :problem_id]
17 + end
18 +
19 + reversible do |change|
20 + change.up do
21 + GraderConfiguration.where(key: 'system.use_problem_group').first_or_create(value_type: 'boolean', value: 'false',
22 + description: 'If true, available problem to the user will be only ones associated with the group of the user');
23 + end
24 +
25 + change.down do
26 + GraderConfiguration.where(key: 'system.use_problem_group').destroy_all
27 + end
28 + end
29 + end
30 + end
@@ -0,0 +1,49
1 + require 'test_helper'
2 +
3 + class GroupsControllerTest < ActionController::TestCase
4 + setup do
5 + @group = groups(:one)
6 + end
7 +
8 + test "should get index" do
9 + get :index
10 + assert_response :success
11 + assert_not_nil assigns(:groups)
12 + end
13 +
14 + test "should get new" do
15 + get :new
16 + assert_response :success
17 + end
18 +
19 + test "should create group" do
20 + assert_difference('Group.count') do
21 + post :create, group: { description: @group.description, name: @group.name }
22 + end
23 +
24 + assert_redirected_to group_path(assigns(:group))
25 + end
26 +
27 + test "should show group" do
28 + get :show, id: @group
29 + assert_response :success
30 + end
31 +
32 + test "should get edit" do
33 + get :edit, id: @group
34 + assert_response :success
35 + end
36 +
37 + test "should update group" do
38 + patch :update, id: @group, group: { description: @group.description, name: @group.name }
39 + assert_redirected_to group_path(assigns(:group))
40 + end
41 +
42 + test "should destroy group" do
43 + assert_difference('Group.count', -1) do
44 + delete :destroy, id: @group
45 + end
46 +
47 + assert_redirected_to groups_path
48 + end
49 + end
@@ -1,132 +1,130
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"
43 42 if @current_user.admin?
44 43 return true
45 44 end
46 45
47 - puts "hehe"
48 - puts GraderConfiguration["right.view_testcase"]
49 46 unauthorized_redirect unless GraderConfiguration["right.view_testcase"]
50 47 end
51 48
52 49 protected
53 50
54 51 def authenticate
55 52 unless session[:user_id]
56 53 flash[:notice] = 'You need to login'
57 54 if GraderConfiguration[SINGLE_USER_MODE_CONF_KEY]
58 55 flash[:notice] = 'You need to login but you cannot log in at this time'
59 56 end
60 57 redirect_to :controller => 'main', :action => 'login'
61 58 return false
62 59 end
63 60
61 +
64 62 # check if run in single user mode
65 63 if GraderConfiguration[SINGLE_USER_MODE_CONF_KEY]
66 - user = User.find_by_id(session[:user_id])
67 - if user==nil or (not user.admin?)
64 + if @current_user==nil or (not @current_user.admin?)
68 65 flash[:notice] = 'You cannot log in at this time'
69 66 redirect_to :controller => 'main', :action => 'login'
70 67 return false
71 68 end
72 - unless user.enabled?
73 - flash[:notice] = 'Your account is disabled'
74 - redirect_to :controller => 'main', :action => 'login'
75 - return false
76 - end
77 69 return true
78 70 end
79 71
72 + # check if the user is enabled
73 + unless @current_user.enabled? or @current_user.admin?
74 + flash[:notice] = 'Your account is disabled'
75 + redirect_to :controller => 'main', :action => 'login'
76 + return false
77 + end
78 +
80 79 if GraderConfiguration.multicontests?
81 - user = User.find(session[:user_id])
82 - return true if user.admin?
80 + return true if @current_user.admin?
83 81 begin
84 - if user.contest_stat(true).forced_logout
82 + if @current_user.contest_stat(true).forced_logout
85 83 flash[:notice] = 'You have been automatically logged out.'
86 84 redirect_to :controller => 'main', :action => 'index'
87 85 end
88 86 rescue
89 87 end
90 88 end
91 89 return true
92 90 end
93 91
94 92 def authenticate_by_ip_address
95 93 #this assume that we have already authenticate normally
96 94 unless GraderConfiguration[MULTIPLE_IP_LOGIN_CONF_KEY]
97 95 user = User.find(session[:user_id])
98 96 if (not user.admin? and user.last_ip and user.last_ip != request.remote_ip)
99 97 flash[:notice] = "You cannot use the system from #{request.remote_ip}. Your last ip is #{user.last_ip}"
100 98 redirect_to :controller => 'main', :action => 'login'
101 99 puts "CHEAT: user #{user.login} tried to login from '#{request.remote_ip}' while last ip is '#{user.last_ip}' at #{Time.zone.now}"
102 100 return false
103 101 end
104 102 unless user.last_ip
105 103 user.last_ip = request.remote_ip
106 104 user.save
107 105 end
108 106 end
109 107 return true
110 108 end
111 109
112 110 def authorization
113 111 return false unless authenticate
114 112 user = User.find(session[:user_id])
115 113 unless user.roles.detect { |role|
116 114 role.rights.detect{ |right|
117 115 right.controller == self.class.controller_name and
118 116 (right.action == 'all' or right.action == action_name)
119 117 }
120 118 }
121 119 flash[:notice] = 'You are not authorized to view the page you requested'
122 120 #request.env['HTTP_REFERER'] ? (redirect_to :back) : (redirect_to :controller => 'login')
123 121 redirect_to :controller => 'main', :action => 'login'
124 122 return false
125 123 end
126 124 end
127 125
128 126 def verify_time_limit
129 127 return true if session[:user_id]==nil
130 128 user = User.find(session[:user_id], :include => :site)
131 129 return true if user==nil or user.site == nil
132 130 if user.contest_finished?
@@ -150,96 +150,101
150 150 end
151 151 redirect_to action: :index
152 152 end
153 153
154 154 def turn_all_on
155 155 Problem.where.not(available: true).each do |problem|
156 156 problem.available = true
157 157 problem.save
158 158 end
159 159 redirect_to action: :index
160 160 end
161 161
162 162 def stat
163 163 @problem = Problem.find(params[:id])
164 164 unless @problem.available or session[:admin]
165 165 redirect_to :controller => 'main', :action => 'list'
166 166 return
167 167 end
168 168 @submissions = Submission.includes(:user).where(problem_id: params[:id]).order(:user_id,:id)
169 169
170 170 #stat summary
171 171 range =65
172 172 @histogram = { data: Array.new(range,0), summary: {} }
173 173 user = Hash.new(0)
174 174 @submissions.find_each do |sub|
175 175 d = (DateTime.now.in_time_zone - sub.submitted_at) / 24 / 60 / 60
176 176 @histogram[:data][d.to_i] += 1 if d < range
177 177 user[sub.user_id] = [user[sub.user_id], ((sub.try(:points) || 0) >= @problem.full_score) ? 1 : 0].max
178 178 end
179 179 @histogram[:summary][:max] = [@histogram[:data].max,1].max
180 180
181 181 @summary = { attempt: user.count, solve: 0 }
182 182 user.each_value { |v| @summary[:solve] += 1 if v == 1 }
183 183 end
184 184
185 185 def manage
186 186 @problems = Problem.order(date_added: :desc)
187 187 end
188 188
189 189 def do_manage
190 190 if params.has_key? 'change_date_added'
191 191 change_date_added
192 192 elsif params.has_key? 'add_to_contest'
193 193 add_to_contest
194 194 elsif params.has_key? 'enable_problem'
195 195 set_available(true)
196 196 elsif params.has_key? 'disable_problem'
197 197 set_available(false)
198 + elsif params.has_key? 'add_group'
199 + group = Group.find(params[:group_id])
200 + get_problems_from_params.each do |p|
201 + group.problems << p
202 + end
198 203 end
199 204 redirect_to :action => 'manage'
200 205 end
201 206
202 207 def import
203 208 @allow_test_pair_import = allow_test_pair_import?
204 209 end
205 210
206 211 def do_import
207 212 old_problem = Problem.find_by_name(params[:name])
208 213 if !allow_test_pair_import? and params.has_key? :import_to_db
209 214 params.delete :import_to_db
210 215 end
211 216 @problem, import_log = Problem.create_from_import_form_params(params,
212 217 old_problem)
213 218
214 219 if !@problem.errors.empty?
215 220 render :action => 'import' and return
216 221 end
217 222
218 223 if old_problem!=nil
219 224 flash[:notice] = "The test data has been replaced for problem #{@problem.name}"
220 225 end
221 226 @log = import_log
222 227 end
223 228
224 229 def remove_contest
225 230 problem = Problem.find(params[:id])
226 231 contest = Contest.find(params[:contest_id])
227 232 if problem!=nil and contest!=nil
228 233 problem.contests.delete(contest)
229 234 end
230 235 redirect_to :action => 'manage'
231 236 end
232 237
233 238 ##################################
234 239 protected
235 240
236 241 def allow_test_pair_import?
237 242 if defined? ALLOW_TEST_PAIR_IMPORT
238 243 return ALLOW_TEST_PAIR_IMPORT
239 244 else
240 245 return false
241 246 end
242 247 end
243 248
244 249 def change_date_added
245 250 problems = get_problems_from_params
@@ -183,96 +183,97
183 183 #set up range from param
184 184 since_id = params.fetch(:since_id, 0).to_i
185 185 until_id = params.fetch(:until_id, 0).to_i
186 186 @users.each do |u|
187 187 ustat = Array.new
188 188 ustat[0] = u
189 189 @problems.each do |p|
190 190 max_points = 0
191 191 Submission.find_in_range_by_user_and_problem(u.id,p.id,since_id,until_id).each do |sub|
192 192 max_points = sub.points if sub and sub.points and (sub.points > max_points)
193 193 end
194 194 ustat << [(max_points.to_f*100/p.full_score).round, (max_points>=p.full_score)]
195 195 end
196 196 @scorearray << ustat
197 197 end
198 198
199 199 if params[:commit] == 'download csv' then
200 200 csv = gen_csv_from_scorearray(@scorearray,@problems)
201 201 send_data csv, filename: 'max_score.csv'
202 202 else
203 203 render template: 'user_admin/user_stat'
204 204 end
205 205 end
206 206
207 207 def import
208 208 if params[:file]==''
209 209 flash[:notice] = 'Error importing no file'
210 210 redirect_to :action => 'index' and return
211 211 end
212 212 import_from_file(params[:file])
213 213 end
214 214
215 215 def random_all_passwords
216 216 users = User.all
217 217 @prefix = params[:prefix] || ''
218 218 @non_admin_users = User.find_non_admin_with_prefix(@prefix)
219 219 @changed = false
220 220 if request.request_method == 'POST'
221 221 @non_admin_users.each do |user|
222 222 password = random_password
223 223 user.password = password
224 224 user.password_confirmation = password
225 225 user.save
226 226 end
227 227 @changed = true
228 228 end
229 229 end
230 230
231 +
231 232 # contest management
232 233
233 234 def contests
234 235 @contest, @users = find_contest_and_user_from_contest_id(params[:id])
235 236 @contests = Contest.enabled
236 237 end
237 238
238 239 def assign_from_list
239 240 contest_id = params[:users_contest_id]
240 241 org_contest, users = find_contest_and_user_from_contest_id(contest_id)
241 242 contest = Contest.find(params[:new_contest][:id])
242 243 if !contest
243 244 flash[:notice] = 'Error: no contest'
244 245 redirect_to :action => 'contests', :id =>contest_id
245 246 end
246 247
247 248 note = []
248 249 users.each do |u|
249 250 u.contests = [contest]
250 251 note << u.login
251 252 end
252 253 flash[:notice] = 'User(s) ' + note.join(', ') +
253 254 " were successfully reassigned to #{contest.title}."
254 255 redirect_to :action => 'contests', :id =>contest.id
255 256 end
256 257
257 258 def add_to_contest
258 259 user = User.find(params[:id])
259 260 contest = Contest.find(params[:contest_id])
260 261 if user and contest
261 262 user.contests << contest
262 263 end
263 264 redirect_to :action => 'index'
264 265 end
265 266
266 267 def remove_from_contest
267 268 user = User.find(params[:id])
268 269 contest = Contest.find(params[:contest_id])
269 270 if user and contest
270 271 user.contests.delete(contest)
271 272 end
272 273 redirect_to :action => 'index'
273 274 end
274 275
275 276 def contest_management
276 277 end
277 278
278 279 def manage_contest
@@ -378,110 +379,116
378 379 if !lines or lines.blank?
379 380 flash[:notice] = 'You entered an empty list.'
380 381 redirect_to :action => 'mass_mailing' and return
381 382 end
382 383
383 384 mail_subject = params[:subject]
384 385 if !mail_subject or mail_subject.blank?
385 386 flash[:notice] = 'You entered an empty mail subject.'
386 387 redirect_to :action => 'mass_mailing' and return
387 388 end
388 389
389 390 mail_body = params[:email_body]
390 391 if !mail_body or mail_body.blank?
391 392 flash[:notice] = 'You entered an empty mail body.'
392 393 redirect_to :action => 'mass_mailing' and return
393 394 end
394 395
395 396 note = []
396 397 users = []
397 398 lines.split("\n").each do |line|
398 399 user = User.find_by_login(line.chomp)
399 400 if user
400 401 send_mail(user.email, mail_subject, mail_body)
401 402 note << user.login
402 403 end
403 404 end
404 405
405 406 flash[:notice] = 'User(s) ' + note.join(', ') +
406 407 ' were successfully modified. '
407 408 redirect_to :action => 'mass_mailing'
408 409 end
409 410
410 411 #bulk manage
411 412 def bulk_manage
412 413
413 414 begin
414 415 @users = User.where('login REGEXP ?',params[:regex]) if params[:regex]
415 416 @users.count if @users #i don't know why I have to call count, but if I won't exception is not raised
416 417 rescue Exception
417 418 flash[:error] = 'Regular Expression is malformed'
418 419 @users = nil
419 420 end
420 421
421 422 if params[:commit]
422 423 @action = {}
423 424 @action[:set_enable] = params[:enabled]
424 425 @action[:enabled] = params[:enable] == "1"
425 426 @action[:gen_password] = params[:gen_password]
427 + @action[:add_group] = params[:add_group]
428 + @action[:group_name] = params[:group_name]
426 429 end
427 430
428 431 if params[:commit] == "Perform"
429 432 if @action[:set_enable]
430 433 @users.update_all(enabled: @action[:enabled])
431 434 end
432 435 if @action[:gen_password]
433 436 @users.each do |u|
434 437 password = random_password
435 438 u.password = password
436 439 u.password_confirmation = password
437 440 u.save
438 441 end
439 442 end
443 + if @action[:add_group] and @action[:group_name]
444 + @group = Group.find(@action[:group_name])
445 + @users.each { |user| @group.users << user }
446 + end
440 447 end
441 448 end
442 449
443 450 protected
444 451
445 452 def random_password(length=5)
446 453 chars = 'abcdefghijkmnopqrstuvwxyz23456789'
447 454 newpass = ""
448 455 length.times { newpass << chars[rand(chars.size-1)] }
449 456 return newpass
450 457 end
451 458
452 459 def import_from_file(f)
453 460 data_hash = YAML.load(f)
454 461 @import_log = ""
455 462
456 463 country_data = data_hash[:countries]
457 464 site_data = data_hash[:sites]
458 465 user_data = data_hash[:users]
459 466
460 467 # import country
461 468 countries = {}
462 469 country_data.each_pair do |id,country|
463 470 c = Country.find_by_name(country[:name])
464 471 if c!=nil
465 472 countries[id] = c
466 473 @import_log << "Found #{country[:name]}\n"
467 474 else
468 475 countries[id] = Country.new(:name => country[:name])
469 476 countries[id].save
470 477 @import_log << "Created #{country[:name]}\n"
471 478 end
472 479 end
473 480
474 481 # import sites
475 482 sites = {}
476 483 site_data.each_pair do |id,site|
477 484 s = Site.find_by_name(site[:name])
478 485 if s!=nil
479 486 @import_log << "Found #{site[:name]}\n"
480 487 else
481 488 s = Site.new(:name => site[:name])
482 489 @import_log << "Created #{site[:name]}\n"
483 490 end
484 491 s.password = site[:password]
485 492 s.country = countries[site[:country_id]]
486 493 s.save
487 494 sites[id] = s
@@ -1,62 +1,63
1 1 require 'yaml'
2 2
3 3 #
4 4 # This class also contains various login of the system.
5 5 #
6 6 class GraderConfiguration < ActiveRecord::Base
7 7
8 8 SYSTEM_MODE_CONF_KEY = 'system.mode'
9 9 TEST_REQUEST_EARLY_TIMEOUT_KEY = 'contest.test_request.early_timeout'
10 10 MULTICONTESTS_KEY = 'system.multicontests'
11 11 CONTEST_TIME_LIMIT_KEY = 'contest.time_limit'
12 12 MULTIPLE_IP_LOGIN_KEY = 'right.multiple_ip_login'
13 13 VIEW_TESTCASE = 'right.view_testcase'
14 14 SINGLE_USER_KEY = 'system.single_user_mode'
15 + SYSTEM_USE_PROBLEM_GROUP = 'system.use_problem_group'
15 16
16 17 cattr_accessor :config_cache
17 18 cattr_accessor :task_grading_info_cache
18 19 cattr_accessor :contest_time_str
19 20 cattr_accessor :contest_time
20 21
21 22 GraderConfiguration.config_cache = nil
22 23 GraderConfiguration.task_grading_info_cache = nil
23 24
24 25 def self.config_cached?
25 26 (defined? CONFIGURATION_CACHE_ENABLED) and (CONFIGURATION_CACHE_ENABLED)
26 27 end
27 28
28 29 def self.get(key)
29 30 if GraderConfiguration.config_cached?
30 31 if GraderConfiguration.config_cache == nil
31 32 self.read_config
32 33 end
33 34 return GraderConfiguration.config_cache[key]
34 35 else
35 36 return GraderConfiguration.read_one_key(key)
36 37 end
37 38 end
38 39
39 40 def self.[](key)
40 41 self.get(key)
41 42 end
42 43
43 44 def self.reload
44 45 self.read_config
45 46 end
46 47
47 48 def self.clear
48 49 GraderConfiguration.config_cache = nil
49 50 end
50 51
51 52 #
52 53 # View decision
53 54 #
54 55 def self.show_submitbox_to?(user)
55 56 mode = get(SYSTEM_MODE_CONF_KEY)
56 57 return false if mode=='analysis'
57 58 if (mode=='contest')
58 59 return false if (user.site!=nil) and
59 60 ((user.site.started!=true) or (user.site.finished?))
60 61 end
61 62 return true
62 63 end
@@ -75,96 +76,100
75 76 def self.show_testcase
76 77 return get(VIEW_TESTCASE)
77 78 end
78 79
79 80 def self.allow_test_request(user)
80 81 mode = get(SYSTEM_MODE_CONF_KEY)
81 82 early_timeout = get(TEST_REQUEST_EARLY_TIMEOUT_KEY)
82 83 if (mode=='contest')
83 84 return false if ((user.site!=nil) and
84 85 ((user.site.started!=true) or
85 86 (early_timeout and (user.site.time_left < 30.minutes))))
86 87 end
87 88 return false if mode=='analysis'
88 89 return true
89 90 end
90 91
91 92 def self.task_grading_info
92 93 if GraderConfiguration.task_grading_info_cache==nil
93 94 read_grading_info
94 95 end
95 96 return GraderConfiguration.task_grading_info_cache
96 97 end
97 98
98 99 def self.standard_mode?
99 100 return get(SYSTEM_MODE_CONF_KEY) == 'standard'
100 101 end
101 102
102 103 def self.contest_mode?
103 104 return get(SYSTEM_MODE_CONF_KEY) == 'contest'
104 105 end
105 106
106 107 def self.indv_contest_mode?
107 108 return get(SYSTEM_MODE_CONF_KEY) == 'indv-contest'
108 109 end
109 110
110 111 def self.multicontests?
111 112 return get(MULTICONTESTS_KEY) == true
112 113 end
113 114
114 115 def self.time_limit_mode?
115 116 mode = get(SYSTEM_MODE_CONF_KEY)
116 117 return ((mode == 'contest') or (mode == 'indv-contest'))
117 118 end
118 119
119 120 def self.analysis_mode?
120 121 return get(SYSTEM_MODE_CONF_KEY) == 'analysis'
121 122 end
122 123
124 + def self.use_problem_group?
125 + return get(SYSTEM_USE_PROBLEM_GROUP)
126 + end
127 +
123 128 def self.contest_time_limit
124 129 contest_time_str = GraderConfiguration[CONTEST_TIME_LIMIT_KEY]
125 130
126 131 if not defined? GraderConfiguration.contest_time_str
127 132 GraderConfiguration.contest_time_str = nil
128 133 end
129 134
130 135 if GraderConfiguration.contest_time_str != contest_time_str
131 136 GraderConfiguration.contest_time_str = contest_time_str
132 137 if tmatch = /(\d+):(\d+)/.match(contest_time_str)
133 138 h = tmatch[1].to_i
134 139 m = tmatch[2].to_i
135 140
136 141 GraderConfiguration.contest_time = h.hour + m.minute
137 142 else
138 143 GraderConfiguration.contest_time = nil
139 144 end
140 145 end
141 146 return GraderConfiguration.contest_time
142 147 end
143 148
144 149 protected
145 150
146 151 def self.convert_type(val,type)
147 152 case type
148 153 when 'string'
149 154 return val
150 155
151 156 when 'integer'
152 157 return val.to_i
153 158
154 159 when 'boolean'
155 160 return (val=='true')
156 161 end
157 162 end
158 163
159 164 def self.read_config
160 165 GraderConfiguration.config_cache = {}
161 166 GraderConfiguration.all.each do |conf|
162 167 key = conf.key
163 168 val = conf.value
164 169 GraderConfiguration.config_cache[key] = GraderConfiguration.convert_type(val,conf.value_type)
165 170 end
166 171 end
167 172
168 173 def self.read_one_key(key)
169 174 conf = GraderConfiguration.find_by_key(key)
170 175 if conf
@@ -1,52 +1,53
1 1 class Problem < ActiveRecord::Base
2 2
3 3 belongs_to :description
4 4 has_and_belongs_to_many :contests, :uniq => true
5 + has_and_belongs_to_many :groups
5 6 has_many :test_pairs, :dependent => :delete_all
6 7 has_many :testcases, :dependent => :destroy
7 8
8 9 validates_presence_of :name
9 10 validates_format_of :name, :with => /\A\w+\z/
10 11 validates_presence_of :full_name
11 12
12 13 scope :available, -> { where(available: true) }
13 14
14 15 DEFAULT_TIME_LIMIT = 1
15 16 DEFAULT_MEMORY_LIMIT = 32
16 17
17 18 def self.available_problems
18 19 available.order(date_added: :desc).order(:name)
19 20 #Problem.available.all(:order => "date_added DESC, name ASC")
20 21 end
21 22
22 23 def self.create_from_import_form_params(params, old_problem=nil)
23 24 org_problem = old_problem || Problem.new
24 25 import_params, problem = Problem.extract_params_and_check(params,
25 26 org_problem)
26 27
27 28 if !problem.errors.empty?
28 29 return problem, 'Error importing'
29 30 end
30 31
31 32 problem.full_score = 100
32 33 problem.date_added = Time.new
33 34 problem.test_allowed = true
34 35 problem.output_only = false
35 36 problem.available = false
36 37
37 38 if not problem.save
38 39 return problem, 'Error importing'
39 40 end
40 41
41 42 import_to_db = params.has_key? :import_to_db
42 43
43 44 importer = TestdataImporter.new(problem)
44 45
45 46 if not importer.import_from_file(import_params[:file],
46 47 import_params[:time_limit],
47 48 import_params[:memory_limit],
48 49 import_params[:checker_name],
49 50 import_to_db)
50 51 problem.errors.add(:base,'Import error.')
51 52 end
52 53
@@ -1,57 +1,58
1 1 require 'digest/sha1'
2 2 require 'net/pop'
3 3 require 'net/https'
4 4 require 'net/http'
5 5 require 'json'
6 6
7 7 class User < ActiveRecord::Base
8 8
9 9 has_and_belongs_to_many :roles
10 + has_and_belongs_to_many :groups
10 11
11 12 has_many :test_requests, -> {order(submitted_at: DESC)}
12 13
13 14 has_many :messages, -> { order(created_at: DESC) },
14 15 :class_name => "Message",
15 16 :foreign_key => "sender_id"
16 17
17 18 has_many :replied_messages, -> { order(created_at: DESC) },
18 19 :class_name => "Message",
19 20 :foreign_key => "receiver_id"
20 21
21 22 has_one :contest_stat, :class_name => "UserContestStat", :dependent => :destroy
22 23
23 24 belongs_to :site
24 25 belongs_to :country
25 26
26 27 has_and_belongs_to_many :contests, -> { order(:name); uniq}
27 28
28 29 scope :activated_users, -> {where activated: true}
29 30
30 31 validates_presence_of :login
31 32 validates_uniqueness_of :login
32 33 validates_format_of :login, :with => /\A[\_A-Za-z0-9]+\z/
33 34 validates_length_of :login, :within => 3..30
34 35
35 36 validates_presence_of :full_name
36 37 validates_length_of :full_name, :minimum => 1
37 38
38 39 validates_presence_of :password, :if => :password_required?
39 40 validates_length_of :password, :within => 4..20, :if => :password_required?
40 41 validates_confirmation_of :password, :if => :password_required?
41 42
42 43 validates_format_of :email,
43 44 :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i,
44 45 :if => :email_validation?
45 46 validate :uniqueness_of_email_from_activated_users,
46 47 :if => :email_validation?
47 48 validate :enough_time_interval_between_same_email_registrations,
48 49 :if => :email_validation?
49 50
50 51 # these are for ytopc
51 52 # disable for now
52 53 #validates_presence_of :province
53 54
54 55 attr_accessor :password
55 56
56 57 before_save :encrypt_new_password
57 58 before_save :assign_default_site
@@ -245,113 +246,125
245 246 end
246 247 end
247 248
248 249 def problem_in_user_contests?(problem)
249 250 problem_contests = problem.contests.all
250 251
251 252 if problem_contests.length == 0 # this is public contest
252 253 return true
253 254 end
254 255
255 256 contests.each do |contest|
256 257 if problem_contests.find {|c| c.id == contest.id }
257 258 return true
258 259 end
259 260 end
260 261 return false
261 262 end
262 263
263 264 def available_problems_group_by_contests
264 265 contest_problems = []
265 266 pin = {}
266 267 contests.enabled.each do |contest|
267 268 available_problems = contest.problems.available
268 269 contest_problems << {
269 270 :contest => contest,
270 271 :problems => available_problems
271 272 }
272 273 available_problems.each {|p| pin[p.id] = true}
273 274 end
274 275 other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0}
275 276 contest_problems << {
276 277 :contest => nil,
277 278 :problems => other_avaiable_problems
278 279 }
279 280 return contest_problems
280 281 end
281 282
282 283 def solve_all_available_problems?
283 284 available_problems.each do |p|
284 285 u = self
285 286 sub = Submission.find_last_by_user_and_problem(u.id,p.id)
286 287 return false if !p or !sub or sub.points < p.full_score
287 288 end
288 289 return true
289 290 end
290 291
291 292 def available_problems
292 293 if not GraderConfiguration.multicontests?
294 + if GraderConfiguration.use_problem_group?
295 + return available_problems_in_group
296 + else
293 297 return Problem.available_problems
298 + end
294 299 else
295 300 contest_problems = []
296 301 pin = {}
297 302 contests.enabled.each do |contest|
298 303 contest.problems.available.each do |problem|
299 304 if not pin.has_key? problem.id
300 305 contest_problems << problem
301 306 end
302 307 pin[problem.id] = true
303 308 end
304 309 end
305 310 other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0}
306 311 return contest_problems + other_avaiable_problems
307 312 end
308 313 end
309 314
315 + def available_problems_in_group
316 + problem = []
317 + self.groups.each do |group|
318 + group.problems.where(available: true).each { |p| problem << p }
319 + end
320 + return problem.uniq
321 + end
322 +
310 323 def can_view_problem?(problem)
311 324 if not GraderConfiguration.multicontests?
312 325 return problem.available
313 326 else
314 327 return problem_in_user_contests? problem
315 328 end
316 329 end
317 330
318 331 def self.clear_last_login
319 332 User.update_all(:last_ip => nil)
320 333 end
321 334
322 335 protected
323 336 def encrypt_new_password
324 337 return if password.blank?
325 338 self.salt = (10+rand(90)).to_s
326 339 self.hashed_password = User.encrypt(self.password,self.salt)
327 340 end
328 341
329 342 def assign_default_site
330 343 # have to catch error when migrating (because self.site is not available).
331 344 begin
332 345 if self.site==nil
333 346 self.site = Site.find_by_name('default')
334 347 if self.site==nil
335 348 self.site = Site.find(1) # when 'default has be renamed'
336 349 end
337 350 end
338 351 rescue
339 352 end
340 353 end
341 354
342 355 def assign_default_contest
343 356 # have to catch error when migrating (because self.site is not available).
344 357 begin
345 358 if self.contests.length == 0
346 359 default_contest = Contest.find_by_name(GraderConfiguration['contest.default_contest_name'])
347 360 if default_contest
348 361 self.contests = [default_contest]
349 362 end
350 363 end
351 364 rescue
352 365 end
353 366 end
354 367
355 368 def password_required?
356 369 self.hashed_password.blank? || !self.password.blank?
357 370 end
@@ -2,92 +2,93
2 2 %nav
3 3 .container-fluid
4 4 .navbar-header
5 5 %button.navbar-toggle.collapsed{ data: {toggle: 'collapse', target: '#navbar-collapse'} }
6 6 %span.sr-only Togggle Navigation
7 7 %span.icon-bar
8 8 %span.icon-bar
9 9 %span.icon-bar
10 10 %a.navbar-brand{href: main_list_path}
11 11 %span.glyphicon.glyphicon-home
12 12 MAIN
13 13 .collapse.navbar-collapse#navbar-collapse
14 14 %ul.nav.navbar-nav
15 15 / submission
16 16 - if (@current_user!=nil) and (GraderConfiguration.show_tasks_to?(@current_user))
17 17 %li.dropdown
18 18 %a.dropdown-toggle{href: '#', data: {toggle:'dropdown'}, aria: {haspopup:"true", expanded:"false"}, role: "button"}
19 19 = "#{I18n.t 'menu.submissions'}"
20 20 %span.caret
21 21 %ul.dropdown-menu
22 22 = add_menu("View", 'submissions', 'index')
23 23 = add_menu("Self Test", 'test', 'index')
24 24 / hall of fame
25 25 - if GraderConfiguration['right.user_hall_of_fame']
26 26 = add_menu("#{I18n.t 'menu.hall_of_fame'}", 'report', 'problem_hof')
27 27 / display MODE button (with countdown in contest mode)
28 28 - if GraderConfiguration.analysis_mode?
29 29 %div.navbar-btn.btn.btn-success#countdown= "ANALYSIS MODE"
30 30 - elsif GraderConfiguration.time_limit_mode?
31 31 - if @current_user.contest_finished?
32 32 %div.navbar-btn.btn.btn-danger#countdown= "Contest is over"
33 33 - elsif !@current_user.contest_started?
34 34 %div.navbar-btn.btn.btn-primary#countdown= (t 'title_bar.contest_not_started')
35 35 - else
36 36 %div.navbar-btn.btn.btn-primary#countdown asdf
37 37 :javascript
38 38 $("#countdown").countdown({until: "+#{@current_user.contest_time_left.to_i}s", layout: 'Time left: {hnn}:{mnn}:{snn}'});
39 39 / admin section
40 40 - if (@current_user!=nil) and (session[:admin])
41 41 / management
42 42 %li.dropdown
43 43 %a.dropdown-toggle{href: '#', data: {toggle:'dropdown'}, aria: {haspopup:"true", expanded:"false"}, role: "button"}
44 44 Manage
45 45 %span.caret
46 46 %ul.dropdown-menu
47 47 = add_menu( 'Announcements', 'announcements', 'index')
48 48 = add_menu( 'Problems', 'problems', 'index')
49 49 = add_menu( 'Users', 'user_admin', 'index')
50 + = add_menu( 'User Groups', 'groups', 'index')
50 51 = add_menu( 'Graders', 'graders', 'list')
51 52 = add_menu( 'Message ', 'messages', 'console')
52 53 %li.divider{role: 'separator'}
53 54 = add_menu( 'System config', 'configurations', 'index')
54 55 %li.divider{role: 'separator'}
55 56 = add_menu( 'Sites', 'sites', 'index')
56 57 = add_menu( 'Contests', 'contest_management', 'index')
57 58 / report
58 59 %li.dropdown
59 60 %a.dropdown-toggle{href: '#', data: {toggle:'dropdown'}, aria: {haspopup:"true", expanded:"false"}, role: "button"}
60 61 Report
61 62 %span.caret
62 63 %ul.dropdown-menu
63 64 = add_menu( 'Current Score', 'report', 'current_score')
64 65 = add_menu( 'Score Report', 'report', 'max_score')
65 66 = add_menu( 'Report', 'report', 'multiple_login')
66 67 - if (ungraded = Submission.where('graded_at is null').where('submitted_at < ?', 1.minutes.ago).count) > 0
67 68 =link_to "#{ungraded} backlogs!",
68 69 grader_list_path,
69 70 class: 'navbar-btn btn btn-default btn-warning', data: {toggle: 'tooltip'},title: 'Number of ungraded submission'
70 71
71 72 %ul.nav.navbar-nav.navbar-right
72 73 = add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-question-sign')}".html_safe, 'main', 'help')
73 74 = add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-comment')}".html_safe, 'messages', 'list', {title: I18n.t('menu.messages'), data: {toggle: 'tooltip'}})
74 75 - if GraderConfiguration['system.user_setting_enabled']
75 76 = add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-cog')}".html_safe, 'users', 'index', {title: I18n.t('menu.settings'), data: {toggle: 'tooltip'}})
76 77 = add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-log-out')} #{@current_user.full_name}".html_safe, 'main', 'login', {title: I18n.t('menu.log_out'), data: {toggle: 'tooltip'}})
77 78
78 79 /
79 80 - if (@current_user!=nil) and (session[:admin])
80 81 %nav.navbar.navbar-fixed-top.navbar-inverse.secondnavbar
81 82 .container-fluid
82 83 .collapse.navbar-collapse
83 84 %ul.nav.navbar-nav
84 85 = add_menu( '[Announcements]', 'announcements', 'index')
85 86 = add_menu( '[Msg console]', 'messages', 'console')
86 87 = add_menu( '[Problems]', 'problems', 'index')
87 88 = add_menu( '[Users]', 'user_admin', 'index')
88 89 = add_menu( '[Results]', 'user_admin', 'user_stat')
89 90 = add_menu( '[Report]', 'report', 'multiple_login')
90 91 = add_menu( '[Graders]', 'graders', 'list')
91 92 = add_menu( '[Contests]', 'contest_management', 'index')
92 93 = add_menu( '[Sites]', 'sites', 'index')
93 94 = add_menu( '[System config]', 'configurations', 'index')
@@ -1,85 +1,90
1 1 - content_for :head do
2 2 = stylesheet_link_tag 'problems'
3 3 = javascript_include_tag 'local_jquery'
4 4
5 5 :javascript
6 6 $(document).ready( function() {
7 7 function shiftclick(start,stop,value) {
8 8 $('tr input').each( function(id,input) {
9 9 var $input=$(input);
10 10 var iid=parseInt($input.attr('id').split('-')[2]);
11 11 if(iid>=start&&iid<=stop){
12 12 $input.prop('checked',value)
13 13 }
14 14 });
15 15 }
16 16
17 17 $('tr input').click( function(e) {
18 18 if (e.shiftKey) {
19 19 stop = parseInt($(this).attr('id').split('-')[2]);
20 20 var orig_stop = stop
21 21 if (typeof start !== 'undefined') {
22 22 if (start > stop) {
23 23 var tmp = start;
24 24 start = stop;
25 25 stop = tmp;
26 26 }
27 27 shiftclick(start,stop,$(this).is(':checked') )
28 28 }
29 29 start = orig_stop
30 30 } else {
31 31 start = parseInt($(this).attr('id').split('-')[2]);
32 32 }
33 33 });
34 34 });
35 35
36 36
37 37 %h1 Manage problems
38 38
39 39 %p= link_to '[Back to problem list]', :action => 'list'
40 40
41 41 = form_tag :action=>'do_manage' do
42 - .submitbox
42 + .submitbox.panel
43 43 What do you want to do to the selected problem?
44 44 %br/
45 45 (You can shift-click to select a range of problems)
46 46 %ul
47 47 %li
48 48 Change date added to
49 49 = select_date Date.current, :prefix => 'date_added'
50 50 &nbsp;&nbsp;&nbsp;
51 51 = submit_tag 'Change', :name => 'change_date_added'
52 52 %li
53 53 Set available to
54 54 = submit_tag 'True', :name => 'enable_problem'
55 55 = submit_tag 'False', :name => 'disable_problem'
56 56
57 57 - if GraderConfiguration.multicontests?
58 58 %li
59 59 Add to
60 60 = select("contest","id",Contest.all.collect {|c| [c.title, c.id]})
61 61 = submit_tag 'Add', :name => 'add_to_contest'
62 + %li
63 + Add problems to group
64 + = select_tag "group_id", options_from_collection_for_select( Group.all, 'id','name',params[:group_name]), id: 'group_name',class: 'select2'
65 + = submit_tag 'Add', name: 'add_group'
62 66
63 - %table
67 +
68 + %table.table.table-hover
64 69 %tr{style: "text-align: left;"}
65 70 %th= check_box_tag 'select_all'
66 71 %th Name
67 72 %th Full name
68 73 %th Available
69 74 %th Date added
70 75 - if GraderConfiguration.multicontests?
71 76 %th Contests
72 77
73 78 - num = 0
74 79 - for problem in @problems
75 80 - num += 1
76 81 %tr{:id => "row-prob-#{problem.id}", :name=> "prob-#{problem.id}"}
77 82 %td= check_box_tag "prob-#{problem.id}-#{num}"
78 83 %td= problem.name
79 84 %td= problem.full_name
80 85 %td= problem.available
81 86 %td= problem.date_added
82 87 - if GraderConfiguration.multicontests?
83 88 %td
84 89 - problem.contests.each do |contest|
85 90 = "(#{contest.name} [#{link_to 'x', :action => 'remove_contest', :id => problem.id, :contest_id => contest.id }])"
@@ -1,77 +1,86
1 1 %h1 Bulk Manage User
2 2
3 3 = form_tag bulk_manage_user_admin_path
4 4 .row
5 5 .col-md-6
6 6 .panel.panel-primary
7 7 .panel-title.panel-heading
8 8 Filter User
9 9 .panel-body
10 10 Filtering users whose login match the following MySQL regex
11 11 .form-group
12 12 = label_tag "regex", 'Regex Pattern'
13 13 = text_field_tag "regex", params[:regex], class: 'form-control'
14 14 %p
15 15 Example
16 16 %ul
17 17 %li
18 18 %code root
19 19 matches every user whose login contains "root"
20 20 %li
21 21 %code ^56
22 22 matches every user whose login starts with "56"
23 23 %li
24 24 %code 21$
25 25 matches every user whose login ends with "21"
26 26 .col-md-6
27 27 .panel.panel-primary
28 28 .panel-title.panel-heading
29 29 Action
30 30 .panel-body
31 31 .row.form-group
32 32 .col-md-6
33 33 %label.checkbox-inline
34 34 = check_box_tag "enabled", true, params[:enabled]
35 35 Change "Enabled" to
36 36 .col-md-3
37 37 %label.radio-inline
38 38 = radio_button_tag "enable", 1, params[:enable] == '1', id: 'enable-yes'
39 39 Yes
40 40 .col-md-3
41 41 %label.radio-inline
42 42 = radio_button_tag "enable", 0, params[:enable] == '0', id: 'enable-no'
43 43 No
44 44 .row.form-group
45 45 .col-md-6
46 46 %label.checkbox-inline
47 47 = check_box_tag "gen_password", true, params[:gen_password]
48 48 Generate new random password
49 + .row.form-group
50 + .col-md-4
51 + %label.checkbox-inline
52 + = check_box_tag "add_group", true, params[:add_group]
53 + Add users to group
54 + %label.col-md-3.control-label.text-right Group name
55 + .col-md-5
56 + = select_tag "group_name", options_from_collection_for_select( Group.all, 'id','name',params[:group_name]), id: 'group_name',class: 'form-control select2'
57 +
49 58
50 59 .row
51 60 .col-md-12
52 61 = submit_tag "Preview Result", class: 'btn btn-default'
53 62 - if @users
54 63 .row
55 64 .col-md-4
56 65 - if @action
57 66 %h2 Confirmation
58 67 - if @action[:set_enable]
59 68 .alert.alert-info The following users will be set #{(@action[:enabled] ? 'enable' : 'disable')}.
60 69 - if @action[:gen_password]
61 70 .alert.alert-info The password of the following users will be randomly generated.
62 71 .row
63 72 .col-md-4
64 73 = submit_tag "Perform", class: 'btn btn-primary'
65 74 .row
66 75 .col-md-12
67 76 The pattern matches #{@users.count} following users.
68 77 %br
69 78 - @users.each do |user|
70 79 = user.login
71 80 = ' '
72 81 = user.full_name
73 82 = ' '
74 83 = "(#{user.remark})" if user.remark
75 84 %br
76 85
77 86
@@ -1,11 +1,13
1 1 %h1 Editing user
2 2
3 - = form_tag :action => 'update', :id => @user do
3 + = form_tag( {:action => 'update', :id => @user}, {class: 'form-horizontal'}) do
4 4 = error_messages_for 'user'
5 5 = render partial: "form"
6 - = submit_tag "Edit"
6 + .form-group
7 + .col-md-offset-2.col-md-4
8 + = submit_tag "Edit", class: 'btn btn-primary'
7 9
8 10
9 11 = link_to 'Show', :action => 'show', :id => @user
10 12 |
11 13 = link_to 'Back', :action => 'list'
@@ -1,80 +1,88
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 23 get 'toggle_view_testcase'
24 24 get 'stat'
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 + end
32 33
34 + resources :groups do
35 + member do
36 + post 'add_user', to: 'groups#add_user', as: 'add_user'
37 + delete 'remove_user/:user_id', to: 'groups#remove_user', as: 'remove_user'
38 + post 'add_problem', to: 'groups#add_problem', as: 'add_problem'
39 + delete 'remove_problem/:problem_id', to: 'groups#remove_problem', as: 'remove_problem'
40 + end
33 41 end
34 42
35 43 resources :testcases, only: [] do
36 44 member do
37 45 get 'download_input'
38 46 get 'download_sol'
39 47 end
40 48 collection do
41 49 get 'show_problem/:problem_id(/:test_num)' => 'testcases#show_problem', as: 'show_problem'
42 50 end
43 51 end
44 52
45 53 resources :grader_configuration, controller: 'configurations'
46 54
47 55 resources :users do
48 56 member do
49 57 get 'toggle_activate', 'toggle_enable'
50 58 get 'stat'
51 59 end
52 60 end
53 61
54 62 resources :submissions do
55 63 member do
56 64 get 'download'
57 65 get 'compiler_msg'
58 66 get 'rejudge'
59 67 end
60 68 collection do
61 69 get 'prob/:problem_id', to: 'submissions#index', as: 'problem'
62 70 get 'direct_edit_problem/:problem_id', to: 'submissions#direct_edit_problem', as: 'direct_edit_problem'
63 71 get 'get_latest_submission_status/:uid/:pid', to: 'submissions#get_latest_submission_status', as: 'get_latest_submission_status'
64 72 end
65 73 end
66 74
67 75
68 76
69 77 #main
70 78 get "main/list"
71 79 get 'main/submission(/:id)', to: 'main#submission', as: 'main_submission'
72 80
73 81 #user admin
74 82 get 'user_admin/bulk_manage', to: 'user_admin#bulk_manage', as: 'bulk_manage_user_admin'
75 83
76 84 #report
77 85 get 'report/current_score', to: 'report#current_score', as: 'report_current_score'
78 86 get 'report/problem_hof(/:id)', to: 'report#problem_hof', as: 'report_problem_hof'
79 87 get "report/login"
80 88 get 'report/max_score', to: 'report#max_score', as: 'report_max_score'
@@ -1,16 +1,16
1 1 class AddLanguageExt < ActiveRecord::Migration
2 2 def self.up
3 3 add_column :languages, :ext, :string, :limit => 10
4 4
5 5 Language.reset_column_information
6 - langs = Language.find(:all)
6 + langs = Language.all
7 7 langs.each do |l|
8 8 l.ext = l.name
9 9 l.save
10 10 end
11 11 end
12 12
13 13 def self.down
14 14 remove_column :languages, :ext
15 15 end
16 16 end
@@ -1,17 +1,17
1 1 class AddStatusToTasks < ActiveRecord::Migration
2 2 def self.up
3 3 add_column :tasks, :status, :integer
4 4 add_column :tasks, :updated_at, :datetime
5 5
6 6 Task.reset_column_information
7 - Task.find(:all).each do |task|
7 + Task.all.each do |task|
8 8 task.status_complete
9 9 task.save
10 10 end
11 11 end
12 12
13 13 def self.down
14 14 remove_column :tasks, :updated_at
15 15 remove_column :tasks, :status
16 16 end
17 17 end
@@ -1,33 +1,32
1 1 class AddNumberToSubmissions < ActiveRecord::Migration
2 2 def self.up
3 3 add_column :submissions, :number, :integer
4 4
5 5 # add number field for all records
6 6 Submission.reset_column_information
7 7
8 8 last_user_id = nil
9 9 last_problem_id = nil
10 10 current_number = 0
11 11
12 - Submission.find(:all,
13 - :order => 'user_id, problem_id, submitted_at').each do |submission|
12 + Submission.order('user_id, problem_id, submitted_at').each do |submission|
14 13 if submission.user_id==last_user_id and submission.problem_id==last_problem_id
15 14 current_number += 1
16 15 else
17 16 current_number = 1
18 17 end
19 18 submission.number = current_number
20 19 submission.save
21 20
22 21 last_user_id = submission.user_id
23 22 last_problem_id = submission.problem_id
24 23 end
25 24
26 25 add_index :submissions, [:user_id, :problem_id, :number], :unique => true
27 26 end
28 27
29 28 def self.down
30 29 remove_index :submissions, :column => [:user_id, :problem_id, :number]
31 30 remove_column :submissions, :number
32 31 end
33 32 end
@@ -1,29 +1,29
1 1 class AddSiteToUserAndAddDefaultSite < ActiveRecord::Migration
2 2 def self.up
3 3 default_site = Site.new({:name => 'default',
4 4 :started => false})
5 5 default_site.save!
6 6
7 7 add_column :users, :site_id, :integer
8 8 User.reset_column_information
9 9
10 - User.find(:all).each do |user|
10 + User.all.each do |user|
11 11
12 12 class << user
13 13 def valid?
14 14 true
15 15 end
16 16 end
17 17
18 18 user.site_id = default_site.id
19 19 user.save
20 20 end
21 21 end
22 22
23 23 def self.down
24 24 remove_column :users, :site_id
25 25
26 26 default_site = Site.find_by_name('default')
27 27 default_site.destroy if default_site
28 28 end
29 29 end
@@ -1,33 +1,33
1 1 class RefactorProblemBodyToDescription < ActiveRecord::Migration
2 2 def self.up
3 3 add_column :problems, :description_id, :integer
4 4 Problem.reset_column_information
5 5
6 - Problem.find(:all).each do |problem|
6 + Problem.all.each do |problem|
7 7 if problem.body!=nil
8 8 description = Description.new
9 9 description.body = problem.body
10 10 description.markdowned = false
11 11 description.save
12 12 problem.description_id = description.id
13 13 problem.save
14 14 end
15 15 end
16 16
17 17 remove_column :problems, :body
18 18 end
19 19
20 20 def self.down
21 21 add_column :problems, :body, :text
22 22 Problem.reset_column_information
23 23
24 - Problem.find(:all).each do |problem|
24 + Problem.all.each do |problem|
25 25 if problem.description_id != nil
26 26 problem.body = Description.find(problem.description_id).body
27 27 problem.save
28 28 end
29 29 end
30 30
31 31 remove_column :problems, :description_id
32 32 end
33 33 end
@@ -1,15 +1,15
1 1 class AddTestAllowedToProblems < ActiveRecord::Migration
2 2 def self.up
3 3 add_column :problems, :test_allowed, :boolean
4 4 Problem.reset_column_information
5 5
6 - Problem.find(:all).each do |problem|
6 + Problem.all.each do |problem|
7 7 problem.test_allowed = true
8 8 problem.save
9 9 end
10 10 end
11 11
12 12 def self.down
13 13 remove_column :problems, :test_allowed
14 14 end
15 15 end
@@ -1,25 +1,25
1 1 class AddActivatedToUsers < ActiveRecord::Migration
2 2 def self.up
3 3 add_column :users, :activated, :boolean, :default => 0
4 4
5 5 User.reset_column_information
6 6
7 - User.find(:all).each do |user|
7 + User.all.each do |user|
8 8
9 9 # disable validation
10 10 class <<user
11 11 def valid?
12 12 return true
13 13 end
14 14 end
15 15
16 16 user.activated = true
17 17 user.save
18 18 end
19 19 end
20 20
21 21
22 22 def self.down
23 23 remove_column :users, :activated
24 24 end
25 25 end
@@ -1,23 +1,23
1 1 class AddCommonExtToLanguages < ActiveRecord::Migration
2 2 def self.up
3 3 # language.common_ext is a comma-separated list of common file
4 4 # extensions.
5 5 add_column :languages, :common_ext, :string
6 6
7 7 # updating table information
8 8 Language.reset_column_information
9 9 common_ext = {
10 10 'c' => 'c',
11 11 'cpp' => 'cpp,cc',
12 12 'pas' => 'pas'
13 13 }
14 - Language.find(:all).each do |lang|
14 + Language.all.each do |lang|
15 15 lang.common_ext = common_ext[lang.name]
16 16 lang.save
17 17 end
18 18 end
19 19
20 20 def self.down
21 21 remove_column :languages, :common_ext
22 22 end
23 23 end
@@ -1,129 +1,148
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: 20170427070345) do
14 + ActiveRecord::Schema.define(version: 20170911091143) do
15 15
16 16 create_table "announcements", force: :cascade do |t|
17 17 t.string "author", limit: 255
18 18 t.text "body", limit: 16777215
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 53 t.text "body", limit: 16777215
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 65 t.text "description", limit: 16777215
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 + create_table "groups", force: :cascade do |t|
83 + t.string "name", limit: 255
84 + t.string "description", limit: 255
85 + end
86 +
87 + create_table "groups_problems", id: false, force: :cascade do |t|
88 + t.integer "problem_id", limit: 4, null: false
89 + t.integer "group_id", limit: 4, null: false
90 + end
91 +
92 + add_index "groups_problems", ["group_id", "problem_id"], name: "index_groups_problems_on_group_id_and_problem_id", using: :btree
93 +
94 + create_table "groups_users", id: false, force: :cascade do |t|
95 + t.integer "group_id", limit: 4, null: false
96 + t.integer "user_id", limit: 4, null: false
97 + end
98 +
99 + add_index "groups_users", ["user_id", "group_id"], name: "index_groups_users_on_user_id_and_group_id", using: :btree
100 +
82 101 create_table "heart_beats", force: :cascade do |t|
83 102 t.integer "user_id", limit: 4
84 103 t.string "ip_address", limit: 255
85 104 t.datetime "created_at", null: false
86 105 t.datetime "updated_at", null: false
87 106 t.string "status", limit: 255
88 107 end
89 108
90 109 add_index "heart_beats", ["updated_at"], name: "index_heart_beats_on_updated_at", using: :btree
91 110
92 111 create_table "languages", force: :cascade do |t|
93 112 t.string "name", limit: 10
94 113 t.string "pretty_name", limit: 255
95 114 t.string "ext", limit: 10
96 115 t.string "common_ext", limit: 255
97 116 end
98 117
99 118 create_table "logins", force: :cascade do |t|
100 119 t.integer "user_id", limit: 4
101 120 t.string "ip_address", limit: 255
102 121 t.datetime "created_at", null: false
103 122 t.datetime "updated_at", null: false
104 123 end
105 124
106 125 create_table "messages", force: :cascade do |t|
107 126 t.integer "sender_id", limit: 4
108 127 t.integer "receiver_id", limit: 4
109 128 t.integer "replying_message_id", limit: 4
110 129 t.text "body", limit: 16777215
111 130 t.boolean "replied"
112 131 t.datetime "created_at", null: false
113 132 t.datetime "updated_at", null: false
114 133 end
115 134
116 135 create_table "problems", force: :cascade do |t|
117 136 t.string "name", limit: 30
118 137 t.string "full_name", limit: 255
119 138 t.integer "full_score", limit: 4
120 139 t.date "date_added"
121 140 t.boolean "available"
122 141 t.string "url", limit: 255
123 142 t.integer "description_id", limit: 4
124 143 t.boolean "test_allowed"
125 144 t.boolean "output_only"
126 145 t.string "description_filename", limit: 255
127 146 t.boolean "view_testcase"
128 147 end
129 148
@@ -118,97 +118,106
118 118 },
119 119
120 120 {
121 121 :key => 'system.admin_email',
122 122 :value_type => 'string',
123 123 :default_value => 'admin@admin.email'
124 124 },
125 125
126 126 {
127 127 :key => 'system.user_setting_enabled',
128 128 :value_type => 'boolean',
129 129 :default_value => 'true',
130 130 :description => 'If this option is true, users can change their settings'
131 131 },
132 132
133 133 {
134 134 :key => 'system.user_setting_enabled',
135 135 :value_type => 'boolean',
136 136 :default_value => 'true',
137 137 :description => 'If this option is true, users can change their settings'
138 138 },
139 139
140 140 # If Configuration['contest.test_request.early_timeout'] is true
141 141 # the user will not be able to use test request at 30 minutes
142 142 # before the contest ends.
143 143 {
144 144 :key => 'contest.test_request.early_timeout',
145 145 :value_type => 'boolean',
146 146 :default_value => 'false'
147 147 },
148 148
149 149 {
150 150 :key => 'system.multicontests',
151 151 :value_type => 'boolean',
152 152 :default_value => 'false'
153 153 },
154 154
155 155 {
156 156 :key => 'contest.confirm_indv_contest_start',
157 157 :value_type => 'boolean',
158 158 :default_value => 'false'
159 159 },
160 160
161 161 {
162 162 :key => 'contest.default_contest_name',
163 163 :value_type => 'string',
164 164 :default_value => 'none',
165 165 :description => "New user will be assigned to this contest automatically, if it exists. Set to 'none' if there is no default contest."
166 - }
166 + },
167 +
168 + {
169 + :key => 'system.use_problem_group',
170 + :value_type => 'boolean',
171 + :default_value => 'false',
172 + :description => "If true, available problem to the user will be only ones associated with the group of the user."
173 + },
174 +
175 +
167 176
168 177 ]
169 178
170 179
171 180 def create_configuration_key(key,
172 181 value_type,
173 182 default_value,
174 183 description='')
175 184 conf = (GraderConfiguration.find_by_key(key) ||
176 185 GraderConfiguration.new(:key => key,
177 186 :value_type => value_type,
178 187 :value => default_value))
179 188 conf.description = description
180 189 conf.save
181 190 end
182 191
183 192 def seed_config
184 193 CONFIGURATIONS.each do |conf|
185 194 if conf.has_key? :description
186 195 desc = conf[:description]
187 196 else
188 197 desc = ''
189 198 end
190 199 create_configuration_key(conf[:key],
191 200 conf[:value_type],
192 201 conf[:default_value],
193 202 desc)
194 203 end
195 204 end
196 205
197 206 def seed_roles
198 207 return if Role.find_by_name('admin')
199 208
200 209 role = Role.create(:name => 'admin')
201 210 user_admin_right = Right.create(:name => 'user_admin',
202 211 :controller => 'user_admin',
203 212 :action => 'all')
204 213 problem_admin_right = Right.create(:name=> 'problem_admin',
205 214 :controller => 'problems',
206 215 :action => 'all')
207 216
208 217 graders_right = Right.create(:name => 'graders_admin',
209 218 :controller => 'graders',
210 219 :action => 'all')
211 220
212 221 role.rights << user_admin_right;
213 222 role.rights << problem_admin_right;
214 223 role.rights << graders_right;
deleted file
deleted file
deleted file
deleted file
deleted file
deleted file
You need to be logged in to leave comments. Login now