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

r719:19bd5319581a - - 95 files changed: 1214 inserted, 401 deleted

new file 100644
@@ -0,0 +1,3
1 + # Place all the behaviors and hooks related to the matching controller here.
2 + # All this logic will automatically be available in application.js.
3 + # You can use CoffeeScript in this file: http://coffeescript.org/
new file 100644
new file 100644
@@ -0,0 +1,3
1 + // Place all the styles related to the tags controller here.
2 + // They will automatically be included in application.css.
3 + // You can use Sass (SCSS) here: http://sass-lang.com/
new file 100644
@@ -0,0 +1,104
1 + class GroupsController < ApplicationController
2 + before_action :set_group, only: [:show, :edit, :update, :destroy,
3 + :add_user, :remove_user,:remove_all_user,
4 + :add_problem, :remove_problem,:remove_all_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), flash: {success: "User #{user.login} was removed from the group #{@group.name}"}
56 + end
57 +
58 + def remove_all_user
59 + @group.users.clear
60 + redirect_to group_path(@group), alert: 'All users removed'
61 + end
62 +
63 + def remove_all_problem
64 + @group.problems.clear
65 + redirect_to group_path(@group), alert: 'All problems removed'
66 + end
67 +
68 + def add_user
69 + user = User.find(params[:user_id])
70 + begin
71 + @group.users << user
72 + redirect_to group_path(@group), flash: { success: "User #{user.login} was add to the group #{@group.name}"}
73 + rescue => e
74 + redirect_to group_path(@group), alert: e.message
75 + end
76 + end
77 +
78 + def remove_problem
79 + problem = Problem.find(params[:problem_id])
80 + @group.problems.delete(problem)
81 + redirect_to group_path(@group), flash: {success: "Problem #{problem.name} was removed from the group #{@group.name}" }
82 + end
83 +
84 + def add_problem
85 + problem = Problem.find(params[:problem_id])
86 + begin
87 + @group.problems << problem
88 + redirect_to group_path(@group), flash: {success: "Problem #{problem.name} was add to the group #{@group.name}" }
89 + rescue => e
90 + redirect_to group_path(@group), alert: e.message
91 + end
92 + end
93 +
94 + private
95 + # Use callbacks to share common setup or constraints between actions.
96 + def set_group
97 + @group = Group.find(params[:id])
98 + end
99 +
100 + # Only allow a trusted parameter "white list" through.
101 + def group_params
102 + params.require(:group).permit(:name, :description)
103 + end
104 + end
@@ -0,0 +1,60
1 + class TagsController < ApplicationController
2 + before_action :set_tag, only: [:show, :edit, :update, :destroy]
3 +
4 + # GET /tags
5 + def index
6 + @tags = Tag.all
7 + end
8 +
9 + # GET /tags/1
10 + def show
11 + end
12 +
13 + # GET /tags/new
14 + def new
15 + @tag = Tag.new
16 + end
17 +
18 + # GET /tags/1/edit
19 + def edit
20 + end
21 +
22 + # POST /tags
23 + def create
24 + @tag = Tag.new(tag_params)
25 +
26 + if @tag.save
27 + redirect_to @tag, notice: 'Tag was successfully created.'
28 + else
29 + render :new
30 + end
31 + end
32 +
33 + # PATCH/PUT /tags/1
34 + def update
35 + if @tag.update(tag_params)
36 + redirect_to @tag, notice: 'Tag was successfully updated.'
37 + else
38 + render :edit
39 + end
40 + end
41 +
42 + # DELETE /tags/1
43 + def destroy
44 + #remove any association
45 + ProblemTag.where(tag_id: @tag.id).destroy_all
46 + @tag.destroy
47 + redirect_to tags_url, notice: 'Tag was successfully destroyed.'
48 + end
49 +
50 + private
51 + # Use callbacks to share common setup or constraints between actions.
52 + def set_tag
53 + @tag = Tag.find(params[:id])
54 + end
55 +
56 + # Only allow a trusted parameter "white list" through.
57 + def tag_params
58 + params.require(:tag).permit(:name, :description, :public)
59 + end
60 + end
@@ -0,0 +1,2
1 + module GroupsHelper
2 + end
@@ -0,0 +1,2
1 + module TagsHelper
2 + end
@@ -0,0 +1,13
1 + class Group < ActiveRecord::Base
2 + has_many :groups_problems, class_name: GroupProblem
3 + has_many :problems, :through => :groups_problems
4 +
5 + has_many :groups_users, class_name: GroupUser
6 + has_many :users, :through => :groups_users
7 +
8 + #has_and_belongs_to_many :problems
9 + #has_and_belongs_to_many :users
10 +
11 +
12 + end
13 +
@@ -0,0 +1,7
1 + class GroupProblem < ActiveRecord::Base
2 + self.table_name = 'groups_problems'
3 +
4 + belongs_to :problem
5 + belongs_to :group
6 + validates_uniqueness_of :problem_id, scope: :group_id, message: ->(object, data) { "'#{Problem.find(data[:value]).full_name}' is already in the group" }
7 + end
@@ -0,0 +1,7
1 + class GroupUser < ActiveRecord::Base
2 + self.table_name = 'groups_users'
3 +
4 + belongs_to :user
5 + belongs_to :group
6 + validates_uniqueness_of :user_id, scope: :group_id, message: ->(object, data) { "'#{User.find(data[:value]).full_name}' is already in the group" }
7 + end
@@ -0,0 +1,8
1 + class ProblemTag < ActiveRecord::Base
2 + self.table_name = 'problems_tags'
3 +
4 + belongs_to :problem
5 + belongs_to :tag
6 +
7 + validates_uniqueness_of :problem_id, scope: :tag_id, message: ->(object, data) { "'#{Problem.find(data[:value]).full_name}' is already has this tag" }
8 + end
@@ -0,0 +1,4
1 + class Tag < ActiveRecord::Base
2 + has_many :problems_tags, class_name: ProblemTag
3 + has_many :problems, through: :problems_tags
4 + end
@@ -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,73
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 + .col-md-12
15 + %h1 Group details
16 + .row
17 + .col-md-6
18 + .panel.panel-default
19 + .panel-heading
20 + .panel-title Users in this group
21 + .panel-body
22 + =form_tag add_user_group_path(@group), class: 'form-inline' do
23 + .form-group
24 + =label_tag :user_id, "User"
25 + =select_tag :user_id, options_from_collection_for_select(User.all,'id','full_name'), class: 'select2'
26 + =submit_tag "Add",class: 'btn btn-primary'
27 +
28 +
29 + %table.table.table-hover
30 + %thead
31 + %tr
32 + %th Login
33 + %th Full name
34 + %th Remark
35 + %th= link_to 'Remove All', remove_all_user_group_path(@group), method: :delete, :data => { :confirm => "Remove ALL USERS from group?" }, class: 'btn btn-danger btn-sm'
36 +
37 + %tbody
38 + - @group.users.each do |user|
39 + %tr
40 + %td= user.login
41 + %td= user.full_name
42 + %td= user.remark
43 + %td= link_to 'Remove', remove_user_group_path(@group,user), :method => :delete, :data => { :confirm => "Remove #{user.full_name}?" }, class: 'btn btn-danger btn-sm'
44 + .col-md-6
45 + .panel.panel-default
46 + .panel-heading
47 + .panel-title Problems
48 + .panel-body
49 +
50 + =form_tag add_problem_group_path(@group), class: 'form-inline' do
51 + .form-group
52 + =label_tag :problem_id, "Problem"
53 + =select_tag :problem_id, options_from_collection_for_select(Problem.all,'id','full_name'), class: 'select2'
54 + =submit_tag "Add",class: 'btn btn-primary'
55 +
56 +
57 + %table.table.table-hover
58 + %thead
59 + %tr
60 + %th name
61 + %th Full name
62 + %th Full score
63 + %th= link_to 'Remove All', remove_all_problem_group_path(@group), method: :delete, :data => { :confirm => "Remove ALL PROBLEMS from group?" }, class: 'btn btn-danger btn-sm'
64 +
65 + %tbody
66 + - @group.problems.each do |problem|
67 + %tr
68 + %td= problem.name
69 + %td= problem.full_name
70 + %td= problem.full_score
71 + %td= link_to 'Remove', remove_problem_group_path(@group,problem), :method => :delete, :data => { :confirm => "Remove #{problem.full_name}?" }, class: 'btn btn-danger btn-sm'
72 +
73 +
@@ -0,0 +1,22
1 + = form_for @tag do |f|
2 + - if @tag.errors.any?
3 + #error_explanation
4 + %h2= "#{pluralize(@tag.errors.count, "error")} prohibited this tag from being saved:"
5 + %ul
6 + - @tag.errors.full_messages.each do |msg|
7 + %li= msg
8 +
9 + .row
10 + .col-md-6
11 + .form-group.field
12 + = f.label :name
13 + = f.text_field :name, class: 'form-control'
14 + .form-group.field
15 + = f.label :description
16 + = f.text_area :description, class: 'form-control'
17 + .form-group.field
18 + = f.label :public
19 + = f.text_field :public, class: 'form-control'
20 + .actions
21 + = f.submit 'Save', class: 'btn btn-primary'
22 + .col-md-6
@@ -0,0 +1,7
1 + %h1 Editing tag
2 +
3 + = render 'form'
4 +
5 + = link_to 'Show', @tag
6 + \|
7 + = link_to 'Back', tags_path
@@ -0,0 +1,26
1 + %h1 Tags
2 +
3 + = link_to 'New Tag', new_tag_path, class: 'btn btn-success'
4 +
5 + %table.table.table-hover
6 + %thead
7 + %tr
8 + %th Name
9 + %th Description
10 + %th Public
11 + %th
12 + %th
13 + %th
14 +
15 + %tbody
16 + - @tags.each do |tag|
17 + %tr
18 + %td= tag.name
19 + %td= tag.description
20 + %td= tag.public
21 + %td= link_to 'Show', tag
22 + %td= link_to 'Edit', edit_tag_path(tag)
23 + %td= link_to 'Destroy', tag, :method => :delete, :data => { :confirm => 'Are you sure?' }
24 +
25 + %br
26 +
@@ -0,0 +1,5
1 + %h1 New tag
2 +
3 + = render 'form'
4 +
5 + = link_to 'Back', tags_path
@@ -0,0 +1,15
1 + %p#notice= notice
2 +
3 + %p
4 + %b Name:
5 + = @tag.name
6 + %p
7 + %b Description:
8 + = @tag.description
9 + %p
10 + %b Public:
11 + = @tag.public
12 +
13 + = link_to 'Edit', edit_tag_path(@tag)
14 + \|
15 + = link_to 'Back', tags_path
@@ -0,0 +1,38
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 + .col-md-6
8 + .form-group
9 + %label.col-md-2.control-label{for: :full_name} Full name
10 + .col-md-4
11 + = text_field 'user', 'full_name', class: 'form-control'
12 + .col-md-6
13 + .form-group
14 + %label.col-md-2.control-label{for: :password} Password
15 + .col-md-4
16 + = password_field 'user', 'password', class: 'form-control'
17 + .col-md-6
18 + .form-group
19 + %label.col-md-2.control-label{for: :password_confirmation} Password (confirm)
20 + .col-md-4
21 + = password_field 'user', 'password_confirmation', class: 'form-control'
22 + .col-md-6
23 + .form-group
24 + %label.col-md-2.control-label{for: :email} E-mail
25 + .col-md-4
26 + = email_field 'user', 'email', class: 'form-control'
27 + .col-md-6
28 + .form-group
29 + %label.col-md-2.control-label{for: :alias} Alias
30 + .col-md-4
31 + = text_field 'user', 'alias', class: 'form-control'
32 + .col-md-6
33 + .form-group
34 + %label.col-md-2.control-label{for: :remark} Remark
35 + .col-md-4
36 + = text_field 'user', 'remark', class: 'form-control'
37 + .col-md-6
38 + / [eoform:user]
@@ -0,0 +1,7
1 + %h1 New user
2 + = form_tag( {action: 'create'}, { class: 'form-horizontal'}) do
3 + = render :partial => 'form'
4 + .form-group
5 + .col-md-offset-2.col-md-10
6 + = submit_tag "Create", class: 'btn btn-primary'
7 + = link_to 'Back', :action => 'index'
@@ -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,23
1 + # Be sure to restart your server when you modify this file.
2 +
3 + # Version of your assets, change this if you want to expire all your assets.
4 + Rails.application.config.assets.version = '1.0'
5 +
6 + # Add additional assets to the asset load path.
7 + # Rails.application.config.assets.paths << Emoji.images_path
8 + # Add Yarn node_modules folder to the asset load path.
9 + Rails.application.config.assets.paths << Rails.root.join('node_modules')
10 + Rails.application.config.assets.paths << Rails.root.join('vendor/assets/fonts')
11 +
12 + # Precompile additional assets.
13 + # application.js, application.css, and all non-JS/CSS in the app/assets
14 + # folder are already added.
15 + # Rails.application.config.assets.precompile += %w( admin.js admin.css )
16 +
17 + Rails.application.config.assets.precompile += ['announcement_refresh.js','effects.js','site_update.js']
18 + Rails.application.config.assets.precompile += ['local_jquery.js','tablesorter-theme.cafe.css']
19 + %w( announcements submissions configurations contests contest_management graders heartbeat
20 + login main messages problems report site sites sources tasks groups
21 + test user_admin users tags testcases).each do |controller|
22 + Rails.application.config.assets.precompile += ["#{controller}.js", "#{controller}.css"]
23 + end
@@ -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,11
1 + class CreateTags < ActiveRecord::Migration
2 + def change
3 + create_table :tags do |t|
4 + t.string :name, null: false
5 + t.text :description
6 + t.boolean :public
7 +
8 + t.timestamps null: false
9 + end
10 + end
11 + end
@@ -0,0 +1,10
1 + class CreateProblemTags < ActiveRecord::Migration
2 + def change
3 + create_table :problems_tags do |t|
4 + t.references :problem, index: true, foreign_key: true
5 + t.references :tag, index: true, foreign_key: true
6 +
7 + t.index [:problem_id,:tag_id], unique: true
8 + end
9 + end
10 + 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
@@ -0,0 +1,49
1 + require 'test_helper'
2 +
3 + class TagsControllerTest < ActionController::TestCase
4 + setup do
5 + @tag = tags(:one)
6 + end
7 +
8 + test "should get index" do
9 + get :index
10 + assert_response :success
11 + assert_not_nil assigns(:tags)
12 + end
13 +
14 + test "should get new" do
15 + get :new
16 + assert_response :success
17 + end
18 +
19 + test "should create tag" do
20 + assert_difference('Tag.count') do
21 + post :create, tag: { description: @tag.description, name: @tag.name, public: @tag.public }
22 + end
23 +
24 + assert_redirected_to tag_path(assigns(:tag))
25 + end
26 +
27 + test "should show tag" do
28 + get :show, id: @tag
29 + assert_response :success
30 + end
31 +
32 + test "should get edit" do
33 + get :edit, id: @tag
34 + assert_response :success
35 + end
36 +
37 + test "should update tag" do
38 + patch :update, id: @tag, tag: { description: @tag.description, name: @tag.name, public: @tag.public }
39 + assert_redirected_to tag_path(assigns(:tag))
40 + end
41 +
42 + test "should destroy tag" do
43 + assert_difference('Tag.count', -1) do
44 + delete :destroy, id: @tag
45 + end
46 +
47 + assert_redirected_to tags_path
48 + end
49 + end
@@ -0,0 +1,9
1 + # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2 +
3 + one:
4 + problem_id:
5 + tag_id:
6 +
7 + two:
8 + problem_id:
9 + tag_id:
@@ -0,0 +1,11
1 + # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2 +
3 + one:
4 + name: MyString
5 + description: MyString
6 + public:
7 +
8 + two:
9 + name: MyString
10 + description: MyString
11 + public:
@@ -0,0 +1,7
1 + require 'test_helper'
2 +
3 + class ProblemTagTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 + end
@@ -0,0 +1,7
1 + require 'test_helper'
2 +
3 + class TagTest < ActiveSupport::TestCase
4 + # test "the truth" do
5 + # assert true
6 + # end
7 + end
@@ -1,91 +1,92
1 1 source 'https://rubygems.org'
2 2
3 3 #rails
4 4 gem 'rails', '~>4.2.0'
5 5 gem 'activerecord-session_store'
6 6
7 7
8 8 # Bundle edge Rails instead:
9 9 # gem 'rails', :git => 'git://github.com/rails/rails.git'
10 10
11 11 #---------------- database ---------------------
12 12 #the database
13 13 gem 'mysql2'
14 14 #for testing
15 15 gem 'sqlite3'
16 16 #for dumping database into yaml
17 17 gem 'yaml_db'
18 18
19 19 # Gems used only for assets and not required
20 20 # in production environments by default.
21 21 gem 'sass-rails'
22 22 gem 'coffee-rails'
23 23
24 24 # See https://github.com/sstephenson/execjs#readme for more supported runtimes
25 25 # gem 'therubyracer', :platforms => :ruby
26 26
27 27 gem 'uglifier'
28 28
29 29 gem 'haml'
30 30 gem 'haml-rails'
31 31 # gem 'prototype-rails'
32 32
33 33 # To use ActiveModel has_secure_password
34 34 # gem 'bcrypt-ruby', '~> 3.0.0'
35 35
36 36 # To use Jbuilder templates for JSON
37 37 # gem 'jbuilder'
38 38
39 39 # Use unicorn as the app server
40 40 # gem 'unicorn'
41 41
42 42 # Deploy with Capistrano
43 43 # gem 'capistrano'
44 44
45 45 # To use debugger
46 46 # gem 'debugger'
47 47 #
48 48
49 49 #in-place editor
50 50 gem 'best_in_place', '~> 3.0.1'
51 51
52 52 # jquery addition
53 53 gem 'jquery-rails'
54 54 gem 'jquery-ui-rails'
55 55 gem 'jquery-timepicker-addon-rails'
56 56 gem 'jquery-tablesorter'
57 57 gem 'jquery-countdown-rails'
58 58
59 59 #syntax highlighter
60 60 gem 'rouge'
61 61
62 - #add bootstrap
62 + #bootstrap add-ons
63 63 gem 'bootstrap-sass', '~> 3.2.0'
64 64 gem 'bootstrap-switch-rails'
65 65 gem 'bootstrap-toggle-rails'
66 66 gem 'autoprefixer-rails'
67 -
68 - #bootstrap sortable
69 67 gem 'momentjs-rails'
70 68 gem 'rails_bootstrap_sortable'
69 + gem 'bootstrap-datepicker-rails'
70 + gem 'bootstrap3-datetimepicker-rails'
71 + gem 'jquery-datatables-rails'
71 72
72 73 #----------- user interface -----------------
73 74 #select 2
74 75 gem 'select2-rails'
75 76 #ace editor
76 77 gem 'ace-rails-ap'
77 78 #paginator
78 79 gem 'will_paginate', '~> 3.0.7'
79 80
80 81 gem 'mail'
81 82 gem 'rdiscount'
82 83 gem 'dynamic_form'
83 84 gem 'in_place_editing'
84 85 gem 'verification', :git => 'https://github.com/sikachu/verification.git'
85 86
86 87
87 88 #---------------- testiing -----------------------
88 89 gem 'minitest-reporters'
89 90
90 91 #---------------- for console --------------------
91 92 gem 'fuzzy-string-match'
@@ -1,235 +1,247
1 1 GIT
2 2 remote: https://github.com/sikachu/verification.git
3 3 revision: ff31697b940d7b0e2ec65f08764215c96104e76d
4 4 specs:
5 5 verification (1.0.3)
6 6 actionpack (>= 3.0.0, < 5.1)
7 7 activesupport (>= 3.0.0, < 5.1)
8 8
9 9 GEM
10 10 remote: https://rubygems.org/
11 11 specs:
12 12 RubyInline (3.12.4)
13 13 ZenTest (~> 4.3)
14 14 ZenTest (4.11.1)
15 15 ace-rails-ap (4.1.1)
16 16 actionmailer (4.2.7.1)
17 17 actionpack (= 4.2.7.1)
18 18 actionview (= 4.2.7.1)
19 19 activejob (= 4.2.7.1)
20 20 mail (~> 2.5, >= 2.5.4)
21 21 rails-dom-testing (~> 1.0, >= 1.0.5)
22 22 actionpack (4.2.7.1)
23 23 actionview (= 4.2.7.1)
24 24 activesupport (= 4.2.7.1)
25 25 rack (~> 1.6)
26 26 rack-test (~> 0.6.2)
27 27 rails-dom-testing (~> 1.0, >= 1.0.5)
28 28 rails-html-sanitizer (~> 1.0, >= 1.0.2)
29 29 actionview (4.2.7.1)
30 30 activesupport (= 4.2.7.1)
31 31 builder (~> 3.1)
32 32 erubis (~> 2.7.0)
33 33 rails-dom-testing (~> 1.0, >= 1.0.5)
34 34 rails-html-sanitizer (~> 1.0, >= 1.0.2)
35 35 activejob (4.2.7.1)
36 36 activesupport (= 4.2.7.1)
37 37 globalid (>= 0.3.0)
38 38 activemodel (4.2.7.1)
39 39 activesupport (= 4.2.7.1)
40 40 builder (~> 3.1)
41 41 activerecord (4.2.7.1)
42 42 activemodel (= 4.2.7.1)
43 43 activesupport (= 4.2.7.1)
44 44 arel (~> 6.0)
45 45 activerecord-session_store (1.0.0)
46 46 actionpack (>= 4.0, < 5.1)
47 47 activerecord (>= 4.0, < 5.1)
48 48 multi_json (~> 1.11, >= 1.11.2)
49 49 rack (>= 1.5.2, < 3)
50 50 railties (>= 4.0, < 5.1)
51 51 activesupport (4.2.7.1)
52 52 i18n (~> 0.7)
53 53 json (~> 1.7, >= 1.7.7)
54 54 minitest (~> 5.1)
55 55 thread_safe (~> 0.3, >= 0.3.4)
56 56 tzinfo (~> 1.1)
57 57 ansi (1.5.0)
58 58 arel (6.0.4)
59 59 autoprefixer-rails (6.6.0)
60 60 execjs
61 61 best_in_place (3.0.3)
62 62 actionpack (>= 3.2)
63 63 railties (>= 3.2)
64 + bootstrap-datepicker-rails (1.7.1.1)
65 + railties (>= 3.0)
64 66 bootstrap-sass (3.2.0.2)
65 67 sass (~> 3.2)
66 68 bootstrap-switch-rails (3.3.3)
67 69 bootstrap-toggle-rails (2.2.1.0)
70 + bootstrap3-datetimepicker-rails (4.17.47)
71 + momentjs-rails (>= 2.8.1)
68 72 builder (3.2.2)
69 73 coffee-rails (4.2.1)
70 74 coffee-script (>= 2.2.0)
71 75 railties (>= 4.0.0, < 5.2.x)
72 76 coffee-script (2.4.1)
73 77 coffee-script-source
74 78 execjs
75 79 coffee-script-source (1.12.2)
76 80 concurrent-ruby (1.0.4)
77 81 dynamic_form (1.1.4)
78 82 erubis (2.7.0)
79 83 execjs (2.7.0)
80 84 fuzzy-string-match (1.0.0)
81 85 RubyInline (>= 3.8.6)
82 86 globalid (0.3.7)
83 87 activesupport (>= 4.1.0)
84 88 haml (4.0.7)
85 89 tilt
86 90 haml-rails (0.9.0)
87 91 actionpack (>= 4.0.1)
88 92 activesupport (>= 4.0.1)
89 93 haml (>= 4.0.6, < 5.0)
90 94 html2haml (>= 1.0.1)
91 95 railties (>= 4.0.1)
92 96 html2haml (2.0.0)
93 97 erubis (~> 2.7.0)
94 98 haml (~> 4.0.0)
95 99 nokogiri (~> 1.6.0)
96 100 ruby_parser (~> 3.5)
97 101 i18n (0.7.0)
98 102 in_place_editing (1.2.0)
99 103 jquery-countdown-rails (2.0.2)
104 + jquery-datatables-rails (3.4.0)
105 + actionpack (>= 3.1)
106 + jquery-rails
107 + railties (>= 3.1)
108 + sass-rails
100 109 jquery-rails (4.2.1)
101 110 rails-dom-testing (>= 1, < 3)
102 111 railties (>= 4.2.0)
103 112 thor (>= 0.14, < 2.0)
104 113 jquery-tablesorter (1.23.3)
105 114 railties (>= 3.2, < 6)
106 115 jquery-timepicker-addon-rails (1.4.1)
107 116 railties (>= 3.1)
108 117 jquery-ui-rails (6.0.1)
109 118 railties (>= 3.2.16)
110 119 json (1.8.3)
111 120 loofah (2.0.3)
112 121 nokogiri (>= 1.5.9)
113 122 mail (2.6.4)
114 123 mime-types (>= 1.16, < 4)
115 124 mime-types (3.1)
116 125 mime-types-data (~> 3.2015)
117 126 mime-types-data (3.2016.0521)
118 127 mini_portile2 (2.1.0)
119 128 minitest (5.10.1)
120 129 minitest-reporters (1.1.13)
121 130 ansi
122 131 builder
123 132 minitest (>= 5.0)
124 133 ruby-progressbar
125 134 momentjs-rails (2.15.1)
126 135 railties (>= 3.1)
127 136 multi_json (1.12.1)
128 137 mysql2 (0.4.5)
129 138 nokogiri (1.6.8.1)
130 139 mini_portile2 (~> 2.1.0)
131 140 rack (1.6.5)
132 141 rack-test (0.6.3)
133 142 rack (>= 1.0)
134 143 rails (4.2.7.1)
135 144 actionmailer (= 4.2.7.1)
136 145 actionpack (= 4.2.7.1)
137 146 actionview (= 4.2.7.1)
138 147 activejob (= 4.2.7.1)
139 148 activemodel (= 4.2.7.1)
140 149 activerecord (= 4.2.7.1)
141 150 activesupport (= 4.2.7.1)
142 151 bundler (>= 1.3.0, < 2.0)
143 152 railties (= 4.2.7.1)
144 153 sprockets-rails
145 154 rails-deprecated_sanitizer (1.0.3)
146 155 activesupport (>= 4.2.0.alpha)
147 156 rails-dom-testing (1.0.8)
148 157 activesupport (>= 4.2.0.beta, < 5.0)
149 158 nokogiri (~> 1.6)
150 159 rails-deprecated_sanitizer (>= 1.0.1)
151 160 rails-html-sanitizer (1.0.3)
152 161 loofah (~> 2.0)
153 162 rails_bootstrap_sortable (2.0.1)
154 163 momentjs-rails (>= 2.8.3)
155 164 railties (4.2.7.1)
156 165 actionpack (= 4.2.7.1)
157 166 activesupport (= 4.2.7.1)
158 167 rake (>= 0.8.7)
159 168 thor (>= 0.18.1, < 2.0)
160 169 rake (12.0.0)
161 170 rdiscount (2.2.0.1)
162 171 rouge (2.0.7)
163 172 ruby-progressbar (1.8.1)
164 173 ruby_parser (3.8.3)
165 174 sexp_processor (~> 4.1)
166 175 sass (3.4.23)
167 176 sass-rails (5.0.6)
168 177 railties (>= 4.0.0, < 6)
169 178 sass (~> 3.1)
170 179 sprockets (>= 2.8, < 4.0)
171 180 sprockets-rails (>= 2.0, < 4.0)
172 181 tilt (>= 1.1, < 3)
173 182 select2-rails (4.0.3)
174 183 thor (~> 0.14)
175 184 sexp_processor (4.7.0)
176 185 sprockets (3.7.1)
177 186 concurrent-ruby (~> 1.0)
178 187 rack (> 1, < 3)
179 188 sprockets-rails (3.2.0)
180 189 actionpack (>= 4.0)
181 190 activesupport (>= 4.0)
182 191 sprockets (>= 3.0.0)
183 192 sqlite3 (1.3.12)
184 193 thor (0.19.4)
185 194 thread_safe (0.3.5)
186 195 tilt (2.0.5)
187 196 tzinfo (1.2.2)
188 197 thread_safe (~> 0.1)
189 198 uglifier (3.0.4)
190 199 execjs (>= 0.3.0, < 3)
191 200 will_paginate (3.0.12)
192 201 yaml_db (0.4.2)
193 202 rails (>= 3.0, < 5.1)
194 203 rake (>= 0.8.7)
195 204
196 205 PLATFORMS
197 206 ruby
198 207
199 208 DEPENDENCIES
200 209 ace-rails-ap
201 210 activerecord-session_store
202 211 autoprefixer-rails
203 212 best_in_place (~> 3.0.1)
213 + bootstrap-datepicker-rails
204 214 bootstrap-sass (~> 3.2.0)
205 215 bootstrap-switch-rails
206 216 bootstrap-toggle-rails
217 + bootstrap3-datetimepicker-rails
207 218 coffee-rails
208 219 dynamic_form
209 220 fuzzy-string-match
210 221 haml
211 222 haml-rails
212 223 in_place_editing
213 224 jquery-countdown-rails
225 + jquery-datatables-rails
214 226 jquery-rails
215 227 jquery-tablesorter
216 228 jquery-timepicker-addon-rails
217 229 jquery-ui-rails
218 230 mail
219 231 minitest-reporters
220 232 momentjs-rails
221 233 mysql2
222 234 rails (~> 4.2.0)
223 235 rails_bootstrap_sortable
224 236 rdiscount
225 237 rouge
226 238 sass-rails
227 239 select2-rails
228 240 sqlite3
229 241 uglifier
230 242 verification!
231 243 will_paginate (~> 3.0.7)
232 244 yaml_db
233 245
234 246 BUNDLED WITH
235 - 1.13.6
247 + 1.15.4
@@ -1,41 +1,47
1 1 // This is a manifest file that'll be compiled into application.js, which will include all the files
2 2 // listed below.
3 3 //
4 4 // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5 5 // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
6 6 //
7 7 // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8 8 // the compiled file.
9 9 //
10 10 // WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
11 11 // GO AFTER THE REQUIRES BELOW.
12 12 //
13 13 //= require jquery
14 14 //= require jquery_ujs
15 + //= require dataTables/jquery.dataTables
16 + //= require dataTables/bootstrap/3/jquery.dataTables.bootstrap
15 17 //= require jquery-ui
16 18 //= require bootstrap-sprockets
17 19 //= require moment
20 + //= require moment/th
18 21 //= require bootstrap-sortable
22 + //= require bootstrap-datetimepicker
19 23 //= require select2
20 24 //= require ace-rails-ap
21 25 //= require ace/mode-c_cpp
22 26 //= require ace/mode-python
23 27 //= require ace/mode-ruby
24 28 //= require ace/mode-pascal
25 29 //= require ace/mode-javascript
26 30 //= require ace/mode-java
27 31 //= require ace/theme-merbivore
28 32 //= require custom
29 33 //= require jquery.countdown
30 34 //-------------- addition from local_jquery -----------
31 35 //= require jquery-tablesorter
32 36 //= require best_in_place
33 37 //= require best_in_place.jquery-ui
34 38 //= require brython
39 + //= require bootstrap-datepicker
40 + //= require bootstrap-datetimepicker
35 41
36 42 // since this is after blank line, it is not downloaded
37 43 //x= require prototype
38 44 //x= require prototype_ujs
39 45 //x= require effects
40 46 //x= require dragdrop
41 47 //x= require controls
@@ -1,131 +1,134
1 1 /* This is a manifest file that'll be compiled into application.css, which will include all the files
2 2 * listed below.
3 3 *
4 4 * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
5 5 * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
6 6 *
7 7 * You're free to add application-wide styles to this file and they'll appear at the bottom of the
8 8 * compiled file so the styles you add here take precedence over styles defined in any styles
9 9 * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
10 10 * file per style scope.
11 11 *
12 12 * // bootstrap says that we should not do this, but @import each file instead
13 13 * # *= require_tree .
14 14 * # *= require_self
15 15 */
16 16
17 17 @import "jquery-ui";
18 18 //@import "jquery.ui.core";
19 19 //@import "jquery.ui.theme";
20 20 //@import "jquery.ui.datepicker";
21 21 //@import "jquery.ui.slider";
22 22 @import "jquery-ui-timepicker-addon";
23 23 @import "jquery-tablesorter/theme.metro-dark";
24 24 @import "jquery.countdown";
25 25 @import "tablesorter-theme.cafe";
26 26
27 27 //bootstrap
28 28 @import "bootstrap-sprockets";
29 29 @import "bootstrap";
30 30 @import "select2";
31 31 @import "select2-bootstrap";
32 32
33 33 //@import bootstrap3-switch
34 34 @import "bootstrap-toggle";
35 35 @import "bootstrap-sortable";
36 + @import "bootstrap-datepicker3";
37 + @import "bootstrap-datetimepicker";
38 + @import "dataTables/bootstrap/3/jquery.dataTables.bootstrap";
36 39
37 40 //bootstrap navbar color (from)
38 41 $bgDefault: #19197b;
39 42 $bgHighlight: #06064b;
40 43 $colDefault: #8e8eb4;
41 44 $colHighlight: #ffffff;
42 45 $dropDown: false;
43 46
44 47 @font-face {
45 48 font-family: 'Glyphicons Halflings';
46 49 src: font-path('bootstrap/glyphicons-halflings-regular.eot');
47 50 src: font-path('bootstrap/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'),
48 51 font-path('bootstrap/glyphicons-halflings-regular.woff') format('woff'),
49 52 font-path('bootstrap/glyphicons-halflings-regular.ttf') format('truetype'),
50 53 font-path('bootstrap/glyphicons-halflings-regular.svg#glyphicons-halflingsregular') format('svg');
51 54 }
52 55
53 56
54 57 .navbar-default {
55 58 background-color: $bgDefault;
56 59 border-color: $bgHighlight;
57 60
58 61 .navbar-brand {
59 62 color: $colDefault;
60 63
61 64 &:hover, &:focus {
62 65 color: $colHighlight;
63 66 }
64 67 }
65 68
66 69 .navbar-text {
67 70 color: $colDefault;
68 71 }
69 72
70 73 .navbar-nav {
71 74 > li {
72 75 > a {
73 76 color: $colDefault;
74 77
75 78 &:hover, &:focus {
76 79 color: $colHighlight;
77 80 }
78 81 }
79 82
80 83 @if $dropDown {
81 84 > .dropdown-menu {
82 85 background-color: $bgDefault;
83 86
84 87 > li {
85 88 > a {
86 89 color: $colDefault;
87 90
88 91 &:hover, &:focus {
89 92 color: $colHighlight;
90 93 background-color: $bgHighlight;
91 94 }
92 95 }
93 96
94 97 > .divider {
95 98 background-color: $bgHighlight;
96 99 }
97 100 }
98 101 }
99 102 }
100 103 }
101 104
102 105 @if $dropDown {
103 106 .open .dropdown-menu > .active {
104 107 > a, > a:hover, > a:focus {
105 108 color: $colHighlight;
106 109 background-color: $bgHighlight;
107 110 }
108 111 }
109 112 }
110 113
111 114 > .active {
112 115 > a, > a:hover, > a:focus {
113 116 color: $colHighlight;
114 117 background-color: $bgHighlight;
115 118 }
116 119 }
117 120
118 121 > .open {
119 122 > a, > a:hover, > a:focus {
120 123 color: $colHighlight;
121 124 background-color: $bgHighlight;
122 125 }
123 126 }
124 127 }
125 128
126 129 .navbar-toggle {
127 130 border-color: $bgHighlight;
128 131
129 132 &:hover, &:focus {
130 133 background-color: $bgHighlight;
131 134 }
@@ -453,96 +456,102
453 456 padding-left: 10px;
454 457 padding-right: 10px;
455 458 padding-top: 5px;
456 459 padding-bottom: 5px;
457 460 }
458 461
459 462 .announcement p {
460 463 font-size: 12px;
461 464 margin: 2px;
462 465 }
463 466
464 467 .pub-info {
465 468 text-align: right;
466 469 font-style: italic;
467 470 font-size: 9px;
468 471
469 472 p {
470 473 text-align: right;
471 474 font-style: italic;
472 475 font-size: 9px;
473 476 }
474 477 }
475 478
476 479 .announcement {
477 480 .toggles {
478 481 font-weight: normal;
479 482 float: right;
480 483 font-size: 80%;
481 484 }
482 485
483 486 .announcement-title {
484 487 font-weight: bold;
485 488 }
486 489 }
487 490
488 491 div {
489 492 &.message {
490 493 margin: 10px 0 0;
491 494
492 495 div {
493 496 &.message {
494 497 margin: 0 0 0 30px;
495 498 }
496 499
497 500 &.body {
498 501 border: 2px solid #dddddd;
499 502 background: #fff8f8;
500 503 padding-left: 5px;
501 504 }
502 505
503 506 &.reply-body {
504 507 border: 2px solid #bbbbbb;
505 508 background: #fffff8;
506 509 padding-left: 5px;
507 510 }
508 511
509 512 &.stat {
510 513 font-size: 10px;
511 514 line-height: 1.75em;
512 515 padding: 0 5px;
513 516 color: #333333;
514 517 background: #dddddd;
515 518 font-weight: bold;
516 519 }
517 520
518 521 &.message div.stat {
519 522 font-size: 10px;
520 523 line-height: 1.75em;
521 524 padding: 0 5px;
522 525 color: #444444;
523 526 background: #bbbbbb;
524 527 font-weight: bold;
525 528 }
526 529 }
527 530 }
528 531
529 532 &.contest-title {
530 533 color: white;
531 534 text-align: center;
532 535 line-height: 2em;
533 536 }
534 537
535 538 &.registration-desc, &.test-desc {
536 539 border: 1px dotted gray;
537 540 background: #f5f5f5;
538 541 padding: 5px;
539 542 margin: 10px 0;
540 543 font-size: 12px;
541 544 line-height: 1.5em;
542 545 }
543 546 }
544 547
545 548 h2.contest-title {
546 549 margin-top: 5px;
547 550 margin-bottom: 5px;
548 551 }
552 +
553 +
554 +
555 + .grader-comment {
556 + word-wrap: break-word;
557 + }
@@ -1,140 +1,138
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?
133 131 flash[:notice] = 'Error: the contest you are participating is over.'
134 132 redirect_to :back
135 133 return false
136 134 end
137 135 return true
138 136 end
139 137
140 138 end
@@ -1,128 +1,93
1 1 class GradersController < ApplicationController
2 2
3 - before_filter :admin_authorization, except: [ :submission ]
4 - before_filter(only: [:submission]) {
5 - #check if authenticated
6 - return false unless authenticate
7 -
8 - #admin always has privileged
9 - if @current_user.admin?
10 - return true
11 - end
12 -
13 - if GraderConfiguration["right.user_view_submission"] and Submission.find(params[:id]).problem.available?
14 - return true
15 - else
16 - unauthorized_redirect
17 - return false
18 - end
19 - }
3 + before_filter :admin_authorization
20 4
21 5 verify :method => :post, :only => ['clear_all',
22 6 'start_exam',
23 7 'start_grading',
24 8 'stop_all',
25 9 'clear_terminated'],
26 10 :redirect_to => {:action => 'index'}
27 11
28 12 def index
29 13 redirect_to :action => 'list'
30 14 end
31 15
32 16 def list
33 17 @grader_processes = GraderProcess.find_running_graders
34 18 @stalled_processes = GraderProcess.find_stalled_process
35 19
36 20 @terminated_processes = GraderProcess.find_terminated_graders
37 21
38 22 @last_task = Task.last
39 23 @last_test_request = TestRequest.last
40 24 @submission = Submission.order("id desc").limit(20)
41 25 @backlog_submission = Submission.where('graded_at is null')
42 26 end
43 27
44 28 def clear
45 29 grader_proc = GraderProcess.find(params[:id])
46 30 grader_proc.destroy if grader_proc!=nil
47 31 redirect_to :action => 'list'
48 32 end
49 33
50 34 def clear_terminated
51 35 GraderProcess.find_terminated_graders.each do |p|
52 36 p.destroy
53 37 end
54 38 redirect_to :action => 'list'
55 39 end
56 40
57 41 def clear_all
58 42 GraderProcess.all.each do |p|
59 43 p.destroy
60 44 end
61 45 redirect_to :action => 'list'
62 46 end
63 47
64 48 def view
65 49 if params[:type]=='Task'
66 50 redirect_to :action => 'task', :id => params[:id]
67 51 else
68 52 redirect_to :action => 'test_request', :id => params[:id]
69 53 end
70 54 end
71 55
72 56 def test_request
73 57 @test_request = TestRequest.find(params[:id])
74 58 end
75 59
76 60 def task
77 61 @task = Task.find(params[:id])
78 62 end
79 63
80 - def submission
81 - @submission = Submission.find(params[:id])
82 - formatter = Rouge::Formatters::HTML.new(css_class: 'highlight', line_numbers: true )
83 - lexer = case @submission.language.name
84 - when "c" then Rouge::Lexers::C.new
85 - when "cpp" then Rouge::Lexers::Cpp.new
86 - when "pas" then Rouge::Lexers::Pas.new
87 - when "ruby" then Rouge::Lexers::Ruby.new
88 - when "python" then Rouge::Lexers::Python.new
89 - when "java" then Rouge::Lexers::Java.new
90 - when "php" then Rouge::Lexers::PHP.new
91 - end
92 - @formatted_code = formatter.format(lexer.lex(@submission.source))
93 - @css_style = Rouge::Themes::ThankfulEyes.render(scope: '.highlight')
94 -
95 - user = User.find(session[:user_id])
96 - SubmissionViewLog.create(user_id: session[:user_id],submission_id: @submission.id) unless user.admin?
97 -
98 - end
99 64
100 65 # various grader controls
101 66
102 67 def stop
103 68 grader_proc = GraderProcess.find(params[:id])
104 69 GraderScript.stop_grader(grader_proc.pid)
105 70 flash[:notice] = 'Grader stopped. It may not disappear now, but it should disappear shortly.'
106 71 redirect_to :action => 'list'
107 72 end
108 73
109 74 def stop_all
110 75 GraderScript.stop_graders(GraderProcess.find_running_graders +
111 76 GraderProcess.find_stalled_process)
112 77 flash[:notice] = 'Graders stopped. They may not disappear now, but they should disappear shortly.'
113 78 redirect_to :action => 'list'
114 79 end
115 80
116 81 def start_grading
117 82 GraderScript.start_grader('grading')
118 83 flash[:notice] = '2 graders in grading env started, one for grading queue tasks, another for grading test request'
119 84 redirect_to :action => 'list'
120 85 end
121 86
122 87 def start_exam
123 88 GraderScript.start_grader('exam')
124 89 flash[:notice] = '2 graders in grading env started, one for grading queue tasks, another for grading test request'
125 90 redirect_to :action => 'list'
126 91 end
127 92
128 93 end
@@ -1,57 +1,63
1 1 class LoginController < ApplicationController
2 2
3 3 def index
4 4 # show login screen
5 5 reset_session
6 6 redirect_to :controller => 'main', :action => 'login'
7 7 end
8 8
9 9 def login
10 - if (!GraderConfiguration['right.bypass_agreement']) and (!params[:accept_agree])
10 + user = User.authenticate(params[:login], params[:password])
11 + unless user
12 + flash[:notice] = 'Wrong password'
13 + redirect_to :controller => 'main', :action => 'login'
14 + return
15 + end
16 +
17 + if (!GraderConfiguration['right.bypass_agreement']) and (!params[:accept_agree]) and !user.admin?
11 18 flash[:notice] = 'You must accept the agreement before logging in'
12 19 redirect_to :controller => 'main', :action => 'login'
13 - elsif user = User.authenticate(params[:login], params[:password])
20 + return
21 + end
22 +
23 + #process logging in
14 24 session[:user_id] = user.id
15 25 session[:admin] = user.admin?
16 26
17 27 # clear forced logout flag for multicontests contest change
18 28 if GraderConfiguration.multicontests?
19 29 contest_stat = user.contest_stat
20 30 if contest_stat.respond_to? :forced_logout
21 31 if contest_stat.forced_logout
22 32 contest_stat.forced_logout = false
23 33 contest_stat.save
24 34 end
25 35 end
26 36 end
27 37
28 38 #save login information
29 39 Login.create(user_id: user.id, ip_address: request.remote_ip)
30 40
31 41 redirect_to :controller => 'main', :action => 'list'
32 - else
33 - flash[:notice] = 'Wrong password'
34 - redirect_to :controller => 'main', :action => 'login'
35 - end
36 42 end
37 43
38 44 def site_login
39 45 begin
40 46 site = Site.find(params[:login][:site_id])
41 47 rescue ActiveRecord::RecordNotFound
42 48 site = nil
43 49 end
44 50 if site==nil
45 51 flash[:notice] = 'Wrong site'
46 52 redirect_to :controller => 'main', :action => 'login' and return
47 53 end
48 54 if (site.password) and (site.password == params[:login][:password])
49 55 session[:site_id] = site.id
50 56 redirect_to :controller => 'site', :action => 'index'
51 57 else
52 58 flash[:notice] = 'Wrong site password'
53 59 redirect_to :controller => 'site', :action => 'login'
54 60 end
55 61 end
56 62
57 63 end
@@ -1,196 +1,196
1 1 class MainController < ApplicationController
2 2
3 3 before_filter :authenticate, :except => [:index, :login]
4 4 before_filter :check_viewability, :except => [:index, :login]
5 5
6 6 append_before_filter :confirm_and_update_start_time,
7 7 :except => [:index,
8 8 :login,
9 9 :confirm_contest_start]
10 10
11 11 # to prevent log in box to be shown when user logged out of the
12 12 # system only in some tab
13 13 prepend_before_filter :reject_announcement_refresh_when_logged_out,
14 14 :only => [:announcements]
15 15
16 16 before_filter :authenticate_by_ip_address, :only => [:list]
17 17
18 18 # COMMENTED OUT: filter in each action instead
19 19 # before_filter :verify_time_limit, :only => [:submit]
20 20
21 21 verify :method => :post, :only => [:submit],
22 22 :redirect_to => { :action => :index }
23 23
24 24 # COMMENT OUT: only need when having high load
25 25 # caches_action :index, :login
26 26
27 27 # NOTE: This method is not actually needed, 'config/routes.rb' has
28 28 # assigned action login as a default action.
29 29 def index
30 30 redirect_to :action => 'login'
31 31 end
32 32
33 33 def login
34 34 saved_notice = flash[:notice]
35 35 reset_session
36 36 flash.now[:notice] = saved_notice
37 37
38 38 # EXPERIMENT:
39 39 # Hide login if in single user mode and the url does not
40 40 # explicitly specify /login
41 41 #
42 42 # logger.info "PATH: #{request.path}"
43 43 # if GraderConfiguration['system.single_user_mode'] and
44 44 # request.path!='/main/login'
45 45 # @hidelogin = true
46 46 # end
47 47
48 48 @announcements = Announcement.frontpage
49 49 render :action => 'login', :layout => 'empty'
50 50 end
51 51
52 52 def list
53 53 prepare_list_information
54 54 end
55 55
56 56 def help
57 57 @user = User.find(session[:user_id])
58 58 end
59 59
60 60 def submit
61 61 user = User.find(session[:user_id])
62 62
63 63 @submission = Submission.new
64 64 @submission.problem_id = params[:submission][:problem_id]
65 65 @submission.user = user
66 66 @submission.language_id = 0
67 67 if (params['file']) and (params['file']!='')
68 68 @submission.source = File.open(params['file'].path,'r:UTF-8',&:read)
69 69 @submission.source.encode!('UTF-8','UTF-8',invalid: :replace, replace: '')
70 70 @submission.source_filename = params['file'].original_filename
71 71 end
72 72
73 73 if (params[:editor_text])
74 74 language = Language.find_by_id(params[:language_id])
75 75 @submission.source = params[:editor_text]
76 76 @submission.source_filename = "live_edit.#{language.ext}"
77 77 @submission.language = language
78 78 end
79 79
80 80 @submission.submitted_at = Time.new.gmtime
81 81 @submission.ip_address = request.remote_ip
82 82
83 83 if GraderConfiguration.time_limit_mode? and user.contest_finished?
84 84 @submission.errors.add(:base,"The contest is over.")
85 85 prepare_list_information
86 86 render :action => 'list' and return
87 87 end
88 88
89 - if @submission.valid?
89 + if @submission.valid?(@current_user)
90 90 if @submission.save == false
91 91 flash[:notice] = 'Error saving your submission'
92 92 elsif Task.create(:submission_id => @submission.id,
93 93 :status => Task::STATUS_INQUEUE) == false
94 94 flash[:notice] = 'Error adding your submission to task queue'
95 95 end
96 96 else
97 97 prepare_list_information
98 98 render :action => 'list' and return
99 99 end
100 - redirect_to :action => 'list'
100 + redirect_to edit_submission_path(@submission)
101 101 end
102 102
103 103 def source
104 104 submission = Submission.find(params[:id])
105 105 if ((submission.user_id == session[:user_id]) and
106 106 (submission.problem != nil) and
107 107 (submission.problem.available))
108 108 send_data(submission.source,
109 109 {:filename => submission.download_filename,
110 110 :type => 'text/plain'})
111 111 else
112 112 flash[:notice] = 'Error viewing source'
113 113 redirect_to :action => 'list'
114 114 end
115 115 end
116 116
117 117 def compiler_msg
118 118 @submission = Submission.find(params[:id])
119 119 if @submission.user_id == session[:user_id]
120 120 render :action => 'compiler_msg', :layout => 'empty'
121 121 else
122 122 flash[:notice] = 'Error viewing source'
123 123 redirect_to :action => 'list'
124 124 end
125 125 end
126 126
127 127 def result
128 128 if !GraderConfiguration.show_grading_result
129 129 redirect_to :action => 'list' and return
130 130 end
131 131 @user = User.find(session[:user_id])
132 132 @submission = Submission.find(params[:id])
133 133 if @submission.user!=@user
134 134 flash[:notice] = 'You are not allowed to view result of other users.'
135 135 redirect_to :action => 'list' and return
136 136 end
137 137 prepare_grading_result(@submission)
138 138 end
139 139
140 140 def load_output
141 141 if !GraderConfiguration.show_grading_result or params[:num]==nil
142 142 redirect_to :action => 'list' and return
143 143 end
144 144 @user = User.find(session[:user_id])
145 145 @submission = Submission.find(params[:id])
146 146 if @submission.user!=@user
147 147 flash[:notice] = 'You are not allowed to view result of other users.'
148 148 redirect_to :action => 'list' and return
149 149 end
150 150 case_num = params[:num].to_i
151 151 out_filename = output_filename(@user.login,
152 152 @submission.problem.name,
153 153 @submission.id,
154 154 case_num)
155 155 if !FileTest.exists?(out_filename)
156 156 flash[:notice] = 'Output not found.'
157 157 redirect_to :action => 'list' and return
158 158 end
159 159
160 160 if defined?(USE_APACHE_XSENDFILE) and USE_APACHE_XSENDFILE
161 161 response.headers['Content-Type'] = "application/force-download"
162 162 response.headers['Content-Disposition'] = "attachment; filename=\"output-#{case_num}.txt\""
163 163 response.headers["X-Sendfile"] = out_filename
164 164 response.headers['Content-length'] = File.size(out_filename)
165 165 render :nothing => true
166 166 else
167 167 send_file out_filename, :stream => false, :filename => "output-#{case_num}.txt", :type => "text/plain"
168 168 end
169 169 end
170 170
171 171 def error
172 172 @user = User.find(session[:user_id])
173 173 end
174 174
175 175 # announcement refreshing and hiding methods
176 176
177 177 def announcements
178 178 if params.has_key? 'recent'
179 179 prepare_announcements(params[:recent])
180 180 else
181 181 prepare_announcements
182 182 end
183 183 render(:partial => 'announcement',
184 184 :collection => @announcements,
185 185 :locals => {:announcement_effect => true})
186 186 end
187 187
188 188 def confirm_contest_start
189 189 user = User.find(session[:user_id])
190 190 if request.method == 'POST'
191 191 user.update_start_time
192 192 redirect_to :action => 'list'
193 193 else
194 194 @contests = user.contests
195 195 @user = user
196 196 end
@@ -72,223 +72,239
72 72 @problem = Problem.find(params[:id])
73 73 @description = @problem.description
74 74 if @description.nil? and params[:description][:body]!=''
75 75 @description = Description.new(params[:description])
76 76 if !@description.save
77 77 flash[:notice] = 'Error saving description'
78 78 render :action => 'edit' and return
79 79 end
80 80 @problem.description = @description
81 81 elsif @description
82 82 if !@description.update_attributes(params[:description])
83 83 flash[:notice] = 'Error saving description'
84 84 render :action => 'edit' and return
85 85 end
86 86 end
87 87 if params[:file] and params[:file].content_type != 'application/pdf'
88 88 flash[:notice] = 'Error: Uploaded file is not PDF'
89 89 render :action => 'edit' and return
90 90 end
91 91 if @problem.update_attributes(problem_params)
92 92 flash[:notice] = 'Problem was successfully updated.'
93 93 unless params[:file] == nil or params[:file] == ''
94 94 flash[:notice] = 'Problem was successfully updated and a new PDF file is uploaded.'
95 95 out_dirname = "#{Problem.download_file_basedir}/#{@problem.id}"
96 96 if not FileTest.exists? out_dirname
97 97 Dir.mkdir out_dirname
98 98 end
99 99
100 100 out_filename = "#{out_dirname}/#{@problem.name}.pdf"
101 101 if FileTest.exists? out_filename
102 102 File.delete out_filename
103 103 end
104 104
105 105 File.open(out_filename,"wb") do |file|
106 106 file.write(params[:file].read)
107 107 end
108 108 @problem.description_filename = "#{@problem.name}.pdf"
109 109 @problem.save
110 110 end
111 111 redirect_to :action => 'show', :id => @problem
112 112 else
113 113 render :action => 'edit'
114 114 end
115 115 end
116 116
117 117 def destroy
118 118 p = Problem.find(params[:id]).destroy
119 119 redirect_to action: :index
120 120 end
121 121
122 122 def toggle
123 123 @problem = Problem.find(params[:id])
124 124 @problem.update_attributes(available: !(@problem.available) )
125 125 respond_to do |format|
126 126 format.js { }
127 127 end
128 128 end
129 129
130 130 def toggle_test
131 131 @problem = Problem.find(params[:id])
132 132 @problem.update_attributes(test_allowed: !(@problem.test_allowed?) )
133 133 respond_to do |format|
134 134 format.js { }
135 135 end
136 136 end
137 137
138 138 def toggle_view_testcase
139 139 @problem = Problem.find(params[:id])
140 140 @problem.update_attributes(view_testcase: !(@problem.view_testcase?) )
141 141 respond_to do |format|
142 142 format.js { }
143 143 end
144 144 end
145 145
146 146 def turn_all_off
147 147 Problem.available.all.each do |problem|
148 148 problem.available = false
149 149 problem.save
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 - @submissions = Submission.includes(:user).where(problem_id: params[:id]).order(:user_id,:id)
168 + @submissions = Submission.includes(:user).includes(:language).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 + ok = []
201 + failed = []
202 + get_problems_from_params.each do |p|
203 + begin
204 + group.problems << p
205 + ok << p.full_name
206 + rescue => e
207 + failed << p.full_name
198 208 end
209 + end
210 + flash[:success] = "The following problems are added to the group #{group.name}: " + ok.join(', ') if ok.count > 0
211 + flash[:alert] = "The following problems are already in the group #{group.name}: " + failed.join(', ') if failed.count > 0
212 + elsif params.has_key? 'add_tags'
213 + get_problems_from_params.each do |p|
214 + p.tag_ids += params[:tag_ids]
215 + end
216 + end
217 +
199 218 redirect_to :action => 'manage'
200 219 end
201 220
202 221 def import
203 222 @allow_test_pair_import = allow_test_pair_import?
204 223 end
205 224
206 225 def do_import
207 226 old_problem = Problem.find_by_name(params[:name])
208 227 if !allow_test_pair_import? and params.has_key? :import_to_db
209 228 params.delete :import_to_db
210 229 end
211 230 @problem, import_log = Problem.create_from_import_form_params(params,
212 231 old_problem)
213 232
214 233 if !@problem.errors.empty?
215 234 render :action => 'import' and return
216 235 end
217 236
218 237 if old_problem!=nil
219 238 flash[:notice] = "The test data has been replaced for problem #{@problem.name}"
220 239 end
221 240 @log = import_log
222 241 end
223 242
224 243 def remove_contest
225 244 problem = Problem.find(params[:id])
226 245 contest = Contest.find(params[:contest_id])
227 246 if problem!=nil and contest!=nil
228 247 problem.contests.delete(contest)
229 248 end
230 249 redirect_to :action => 'manage'
231 250 end
232 251
233 252 ##################################
234 253 protected
235 254
236 255 def allow_test_pair_import?
237 256 if defined? ALLOW_TEST_PAIR_IMPORT
238 257 return ALLOW_TEST_PAIR_IMPORT
239 258 else
240 259 return false
241 260 end
242 261 end
243 262
244 263 def change_date_added
245 264 problems = get_problems_from_params
246 - year = params[:date_added][:year].to_i
247 - month = params[:date_added][:month].to_i
248 - day = params[:date_added][:day].to_i
249 - date = Date.new(year,month,day)
265 + date = Date.parse(params[:date_added])
250 266 problems.each do |p|
251 267 p.date_added = date
252 268 p.save
253 269 end
254 270 end
255 271
256 272 def add_to_contest
257 273 problems = get_problems_from_params
258 274 contest = Contest.find(params[:contest][:id])
259 275 if contest!=nil and contest.enabled
260 276 problems.each do |p|
261 277 p.contests << contest
262 278 end
263 279 end
264 280 end
265 281
266 282 def set_available(avail)
267 283 problems = get_problems_from_params
268 284 problems.each do |p|
269 285 p.available = avail
270 286 p.save
271 287 end
272 288 end
273 289
274 290 def get_problems_from_params
275 291 problems = []
276 292 params.keys.each do |k|
277 293 if k.index('prob-')==0
278 294 name, id, order = k.split('-')
279 295 problems << Problem.find(id)
280 296 end
281 297 end
282 298 problems
283 299 end
284 300
285 301 def get_problems_stat
286 302 end
287 303
288 304 private
289 305
290 306 def problem_params
291 - params.require(:problem).permit(:name, :full_name, :full_score, :date_added, :available, :test_allowed,:output_only, :url, :description)
307 + params.require(:problem).permit(:name, :full_name, :full_score, :date_added, :available, :test_allowed,:output_only, :url, :description, tag_ids:[])
292 308 end
293 309
294 310 end
@@ -1,150 +1,152
1 1 require 'csv'
2 2
3 3 class ReportController < ApplicationController
4 4
5 5 before_filter :authenticate
6 6
7 7 before_filter :admin_authorization, only: [:login_stat,:submission_stat, :stuck, :cheat_report, :cheat_scruntinize, :show_max_score]
8 8
9 9 before_filter(only: [:problem_hof]) { |c|
10 10 return false unless authenticate
11 11
12 12 admin_authorization unless GraderConfiguration["right.user_view_submission"]
13 13 }
14 14
15 15 def max_score
16 16 end
17 17
18 18 def current_score
19 19 @problems = Problem.available_problems
20 20 @users = User.includes(:contests).includes(:contest_stat).where(enabled: true)
21 21 @scorearray = calculate_max_score(@problems, @users,0,0,true)
22 22
23 23 #rencer accordingly
24 24 if params[:button] == 'download' then
25 25 csv = gen_csv_from_scorearray(@scorearray,@problems)
26 26 send_data csv, filename: 'max_score.csv'
27 27 else
28 28 #render template: 'user_admin/user_stat'
29 29 render 'current_score'
30 30 end
31 31 end
32 32
33 33 def show_max_score
34 34 #process parameters
35 35 #problems
36 36 @problems = []
37 37 if params[:problem_id]
38 38 params[:problem_id].each do |id|
39 39 next unless id.strip != ""
40 40 pid = Problem.find_by_id(id.to_i)
41 41 @problems << pid if pid
42 42 end
43 43 end
44 44
45 45 #users
46 46 @users = if params[:user] == "all" then
47 47 User.includes(:contests).includes(:contest_stat)
48 48 else
49 49 User.includes(:contests).includes(:contest_stat).where(enabled: true)
50 50 end
51 51
52 52 #set up range from param
53 53 @since_id = params.fetch(:from_id, 0).to_i
54 54 @until_id = params.fetch(:to_id, 0).to_i
55 + @since_id = nil if @since_id == 0
56 + @until_id = nil if @until_id == 0
55 57
56 58 #calculate the routine
57 59 @scorearray = calculate_max_score(@problems, @users, @since_id, @until_id)
58 60
59 61 #rencer accordingly
60 62 if params[:button] == 'download' then
61 63 csv = gen_csv_from_scorearray(@scorearray,@problems)
62 64 send_data csv, filename: 'max_score.csv'
63 65 else
64 66 #render template: 'user_admin/user_stat'
65 67 render 'max_score'
66 68 end
67 69
68 70 end
69 71
70 72 def score
71 73 if params[:commit] == 'download csv'
72 74 @problems = Problem.all
73 75 else
74 76 @problems = Problem.available_problems
75 77 end
76 78 @users = User.includes(:contests, :contest_stat).where(enabled: true)
77 79 @scorearray = Array.new
78 80 @users.each do |u|
79 81 ustat = Array.new
80 82 ustat[0] = u
81 83 @problems.each do |p|
82 84 sub = Submission.find_last_by_user_and_problem(u.id,p.id)
83 85 if (sub!=nil) and (sub.points!=nil) and p and p.full_score
84 86 ustat << [(sub.points.to_f*100/p.full_score).round, (sub.points>=p.full_score)]
85 87 else
86 88 ustat << [0,false]
87 89 end
88 90 end
89 91 @scorearray << ustat
90 92 end
91 93 if params[:commit] == 'download csv' then
92 94 csv = gen_csv_from_scorearray(@scorearray,@problems)
93 95 send_data csv, filename: 'last_score.csv'
94 96 else
95 97 render template: 'user_admin/user_stat'
96 98 end
97 99
98 100 end
99 101
100 102 def login_stat
101 103 @logins = Array.new
102 104
103 105 date_and_time = '%Y-%m-%d %H:%M'
104 106 begin
105 107 md = params[:since_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
106 108 @since_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
107 109 rescue
108 110 @since_time = DateTime.new(1000,1,1)
109 111 end
110 112 begin
111 113 md = params[:until_datetime].match(/(\d+)-(\d+)-(\d+) (\d+):(\d+)/)
112 114 @until_time = Time.zone.local(md[1].to_i,md[2].to_i,md[3].to_i,md[4].to_i,md[5].to_i)
113 115 rescue
114 116 @until_time = DateTime.new(3000,1,1)
115 117 end
116 118
117 119 User.all.each do |user|
118 120 @logins << { id: user.id,
119 121 login: user.login,
120 122 full_name: user.full_name,
121 123 count: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
122 124 user.id,@since_time,@until_time)
123 125 .count(:id),
124 126 min: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
125 127 user.id,@since_time,@until_time)
126 128 .minimum(:created_at),
127 129 max: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
128 130 user.id,@since_time,@until_time)
129 131 .maximum(:created_at),
130 132 ip: Login.where("user_id = ? AND created_at >= ? AND created_at <= ?",
131 133 user.id,@since_time,@until_time)
132 134 .select(:ip_address).uniq
133 135
134 136 }
135 137 end
136 138 end
137 139
138 140 def submission_stat
139 141
140 142 date_and_time = '%Y-%m-%d %H:%M'
141 143 begin
142 144 @since_time = DateTime.strptime(params[:since_datetime],date_and_time)
143 145 rescue
144 146 @since_time = DateTime.new(1000,1,1)
145 147 end
146 148 begin
147 149 @until_time = DateTime.strptime(params[:until_datetime],date_and_time)
148 150 rescue
149 151 @until_time = DateTime.new(3000,1,1)
150 152 end
@@ -1,108 +1,115
1 1 class SubmissionsController < ApplicationController
2 2 before_action :authenticate
3 - before_action :submission_authorization, only: [:show, :direct_edit_submission, :download, :edit]
3 + before_action :submission_authorization, only: [:show, :download, :edit]
4 4 before_action :admin_authorization, only: [:rejudge]
5 5
6 6 # GET /submissions
7 7 # GET /submissions.json
8 8 # Show problem selection and user's submission of that problem
9 9 def index
10 10 @user = @current_user
11 11 @problems = @user.available_problems
12 12
13 13 if params[:problem_id]==nil
14 14 @problem = nil
15 15 @submissions = nil
16 16 else
17 17 @problem = Problem.find_by_id(params[:problem_id])
18 18 if (@problem == nil) or (not @problem.available)
19 19 redirect_to main_list_path
20 20 flash[:notice] = 'Error: submissions for that problem are not viewable.'
21 21 return
22 22 end
23 23 @submissions = Submission.find_all_by_user_problem(@user.id, @problem.id).order(id: :desc)
24 24 end
25 25 end
26 26
27 27 # GET /submissions/1
28 28 # GET /submissions/1.json
29 29 def show
30 30 @submission = Submission.find(params[:id])
31 31
32 32 #log the viewing
33 33 user = User.find(session[:user_id])
34 34 SubmissionViewLog.create(user_id: session[:user_id],submission_id: @submission.id) unless user.admin?
35 35
36 36 @task = @submission.task
37 37 end
38 38
39 39 def download
40 40 @submission = Submission.find(params[:id])
41 41 send_data(@submission.source, {:filename => @submission.download_filename, :type => 'text/plain'})
42 42 end
43 43
44 44 def compiler_msg
45 45 @submission = Submission.find(params[:id])
46 46 respond_to do |format|
47 47 format.js
48 48 end
49 49 end
50 50
51 51 #on-site new submission on specific problem
52 52 def direct_edit_problem
53 53 @problem = Problem.find(params[:problem_id])
54 + unless @current_user.can_view_problem?(@problem)
55 + unauthorized_redirect
56 + return
57 + end
54 58 @source = ''
59 + if (params[:view_latest])
60 + sub = Submission.find_last_by_user_and_problem(@current_user.id,@problem.id)
61 + @source = @submission.source.to_s if @submission and @submission.source
62 + end
55 63 render 'edit'
56 64 end
57 65
58 66 # GET /submissions/1/edit
59 67 def edit
60 68 @submission = Submission.find(params[:id])
61 69 @source = @submission.source.to_s
62 70 @problem = @submission.problem
63 71 @lang_id = @submission.language.id
64 72 end
65 73
66 74
67 75 def get_latest_submission_status
68 76 @problem = Problem.find(params[:pid])
69 77 @submission = Submission.find_last_by_user_and_problem(params[:uid],params[:pid])
70 78 puts User.find(params[:uid]).login
71 79 puts Problem.find(params[:pid]).name
72 80 puts 'nil' unless @submission
73 81 respond_to do |format|
74 82 format.js
75 83 end
76 84 end
77 85
78 86 # GET /submissions/:id/rejudge
79 87 def rejudge
80 88 @submission = Submission.find(params[:id])
81 89 @task = @submission.task
82 90 @task.status_inqueue! if @task
83 91 respond_to do |format|
84 92 format.js
85 93 end
86 94 end
87 95
88 96 protected
89 97
90 98 def submission_authorization
91 99 #admin always has privileged
92 100 if @current_user.admin?
93 101 return true
94 102 end
95 103
96 104 sub = Submission.find(params[:id])
97 - if sub.problem.available?
98 - puts "sub = #{sub.user.id}, current = #{@current_user.id}"
105 + if @current_user.available_problems.include? sub.problem
99 106 return true if GraderConfiguration["right.user_view_submission"] or sub.user == @current_user
100 107 end
101 108
102 109 #default to NO
103 110 unauthorized_redirect
104 111 return false
105 112 end
106 113
107 114
108 115 end
@@ -1,75 +1,75
1 1 class TasksController < ApplicationController
2 2
3 3 before_filter :authenticate, :check_viewability
4 4
5 5 def index
6 6 redirect_to :action => 'list'
7 7 end
8 8
9 9 def list
10 10 @problems = @user.available_problems
11 11 end
12 12
13 13 # this has contest-wide access control
14 14 def view
15 15 base_name = params[:file]
16 16 base_filename = File.basename("#{base_name}.#{params[:ext]}")
17 17 filename = "#{Problem.download_file_basedir}/#{base_filename}"
18 18
19 19 if !FileTest.exists?(filename)
20 20 redirect_to :action => 'index' and return
21 21 end
22 22
23 23 send_file_to_user(filename, base_filename)
24 24 end
25 25
26 26 # this has problem-level access control
27 27 def download
28 28 problem = Problem.find(params[:id])
29 - if !problem or !problem.available or !@user.can_view_problem? problem
29 + unless @current_user.can_view_problem? problem
30 30 redirect_to :action => 'index' and return
31 31 end
32 32
33 33 base_name = params[:file]
34 34 base_filename = File.basename("#{base_name}.#{params[:ext]}")
35 35 filename = "#{Problem.download_file_basedir}/#{params[:id]}/#{base_filename}"
36 36 puts "SENDING: #{filename}"
37 37
38 38 if !FileTest.exists?(filename)
39 39 redirect_to :action => 'index' and return
40 40 end
41 41
42 42 puts "SENDING: #{filename}"
43 43
44 44 send_file_to_user(filename, base_filename)
45 45 end
46 46
47 47 protected
48 48
49 49 def send_file_to_user(filename, base_filename)
50 50 if defined?(USE_APACHE_XSENDFILE) and USE_APACHE_XSENDFILE
51 51 response.headers['Content-Type'] = "application/force-download"
52 52 response.headers['Content-Disposition'] = "attachment; filename=\"#{File.basename(filename)}\""
53 53 response.headers["X-Sendfile"] = filename
54 54 response.headers['Content-length'] = File.size(filename)
55 55 render :nothing => true
56 56 else
57 57 if params[:ext]=='pdf'
58 58 content_type = 'application/pdf'
59 59 else
60 60 content_type = 'application/octet-stream'
61 61 end
62 62
63 63 send_file filename, :stream => false, :disposition => 'inline', :filename => base_filename, :type => content_type
64 64 end
65 65 end
66 66
67 67 def check_viewability
68 68 @user = User.find(session[:user_id])
69 69 if @user==nil or !GraderConfiguration.show_tasks_to?(@user)
70 70 redirect_to :controller => 'main', :action => 'list'
71 71 return false
72 72 end
73 73 end
74 74
75 75 end
@@ -1,122 +1,123
1 1 require 'csv'
2 2
3 3 class UserAdminController < ApplicationController
4 4
5 5 include MailHelperMethods
6 6
7 7 before_filter :admin_authorization
8 8
9 9 # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
10 10 verify :method => :post, :only => [
11 11 :create, :create_from_list,
12 12 :update,
13 13 :manage_contest,
14 14 :bulk_mail
15 15 ],
16 16 :redirect_to => { :action => :list }
17 17
18 18 def index
19 19 @user_count = User.count
20 20 if params[:page] == 'all'
21 21 @users = User.all
22 22 @paginated = false
23 23 else
24 24 @users = User.paginate :page => params[:page]
25 25 @paginated = true
26 26 end
27 + @users = User.all
27 28 @hidden_columns = ['hashed_password', 'salt', 'created_at', 'updated_at']
28 29 @contests = Contest.enabled
29 30 end
30 31
31 32 def active
32 33 sessions = ActiveRecord::SessionStore::Session.where("updated_at >= ?", 60.minutes.ago)
33 34 @users = []
34 35 sessions.each do |session|
35 36 if session.data[:user_id]
36 37 @users << User.find(session.data[:user_id])
37 38 end
38 39 end
39 40 end
40 41
41 42 def show
42 43 @user = User.find(params[:id])
43 44 end
44 45
45 46 def new
46 47 @user = User.new
47 48 end
48 49
49 50 def create
50 51 @user = User.new(user_params)
51 52 @user.activated = true
52 53 if @user.save
53 54 flash[:notice] = 'User was successfully created.'
54 55 redirect_to :action => 'index'
55 56 else
56 57 render :action => 'new'
57 58 end
58 59 end
59 60
60 61 def clear_last_ip
61 62 @user = User.find(params[:id])
62 63 @user.last_ip = nil
63 64 @user.save
64 65 redirect_to action: 'index', page: params[:page]
65 66 end
66 67
67 68 def create_from_list
68 69 lines = params[:user_list]
69 70
70 71 note = []
71 72
72 73 lines.split("\n").each do |line|
73 74 items = line.chomp.split(',')
74 75 if items.length>=2
75 76 login = items[0]
76 77 full_name = items[1]
77 78 remark =''
78 79 user_alias = ''
79 80
80 81 added_random_password = false
81 82 if items.length >= 3 and items[2].chomp(" ").length > 0;
82 83 password = items[2].chomp(" ")
83 84 else
84 85 password = random_password
85 86 add_random_password=true;
86 87 end
87 88
88 89 if items.length>= 4 and items[3].chomp(" ").length > 0;
89 90 user_alias = items[3].chomp(" ")
90 91 else
91 92 user_alias = login
92 93 end
93 94
94 95 if items.length>=5
95 96 remark = items[4].strip;
96 97 end
97 98
98 99 user = User.find_by_login(login)
99 100 if (user)
100 101 user.full_name = full_name
101 102 user.password = password
102 103 user.remark = remark
103 104 else
104 105 user = User.new({:login => login,
105 106 :full_name => full_name,
106 107 :password => password,
107 108 :password_confirmation => password,
108 109 :alias => user_alias,
109 110 :remark => remark})
110 111 end
111 112 user.activated = true
112 113 user.save
113 114
114 115 if added_random_password
115 116 note << "'#{login}' (+)"
116 117 else
117 118 note << login
118 119 end
119 120 end
120 121 end
121 122 flash[:success] = 'User(s) ' + note.join(', ') +
122 123 ' were successfully created. ' +
@@ -135,401 +136,419
135 136 redirect_to :action => 'show', :id => @user
136 137 else
137 138 render :action => 'edit'
138 139 end
139 140 end
140 141
141 142 def destroy
142 143 User.find(params[:id]).destroy
143 144 redirect_to :action => 'index'
144 145 end
145 146
146 147 def user_stat
147 148 if params[:commit] == 'download csv'
148 149 @problems = Problem.all
149 150 else
150 151 @problems = Problem.available_problems
151 152 end
152 153 @users = User.includes(:contests, :contest_stat).where(enabled: true)
153 154 @scorearray = Array.new
154 155 @users.each do |u|
155 156 ustat = Array.new
156 157 ustat[0] = u
157 158 @problems.each do |p|
158 159 sub = Submission.find_last_by_user_and_problem(u.id,p.id)
159 160 if (sub!=nil) and (sub.points!=nil) and p and p.full_score
160 161 ustat << [(sub.points.to_f*100/p.full_score).round, (sub.points>=p.full_score)]
161 162 else
162 163 ustat << [0,false]
163 164 end
164 165 end
165 166 @scorearray << ustat
166 167 end
167 168 if params[:commit] == 'download csv' then
168 169 csv = gen_csv_from_scorearray(@scorearray,@problems)
169 170 send_data csv, filename: 'last_score.csv'
170 171 else
171 172 render template: 'user_admin/user_stat'
172 173 end
173 174 end
174 175
175 176 def user_stat_max
176 177 if params[:commit] == 'download csv'
177 178 @problems = Problem.all
178 179 else
179 180 @problems = Problem.available_problems
180 181 end
181 182 @users = User.includes(:contests).includes(:contest_stat).all
182 183 @scorearray = Array.new
183 184 #set up range from param
184 185 since_id = params.fetch(:since_id, 0).to_i
185 186 until_id = params.fetch(:until_id, 0).to_i
186 187 @users.each do |u|
187 188 ustat = Array.new
188 189 ustat[0] = u
189 190 @problems.each do |p|
190 191 max_points = 0
191 192 Submission.find_in_range_by_user_and_problem(u.id,p.id,since_id,until_id).each do |sub|
192 193 max_points = sub.points if sub and sub.points and (sub.points > max_points)
193 194 end
194 195 ustat << [(max_points.to_f*100/p.full_score).round, (max_points>=p.full_score)]
195 196 end
196 197 @scorearray << ustat
197 198 end
198 199
199 200 if params[:commit] == 'download csv' then
200 201 csv = gen_csv_from_scorearray(@scorearray,@problems)
201 202 send_data csv, filename: 'max_score.csv'
202 203 else
203 204 render template: 'user_admin/user_stat'
204 205 end
205 206 end
206 207
207 208 def import
208 209 if params[:file]==''
209 210 flash[:notice] = 'Error importing no file'
210 211 redirect_to :action => 'index' and return
211 212 end
212 213 import_from_file(params[:file])
213 214 end
214 215
215 216 def random_all_passwords
216 217 users = User.all
217 218 @prefix = params[:prefix] || ''
218 219 @non_admin_users = User.find_non_admin_with_prefix(@prefix)
219 220 @changed = false
220 221 if request.request_method == 'POST'
221 222 @non_admin_users.each do |user|
222 223 password = random_password
223 224 user.password = password
224 225 user.password_confirmation = password
225 226 user.save
226 227 end
227 228 @changed = true
228 229 end
229 230 end
230 231
232 +
231 233 # contest management
232 234
233 235 def contests
234 236 @contest, @users = find_contest_and_user_from_contest_id(params[:id])
235 237 @contests = Contest.enabled
236 238 end
237 239
238 240 def assign_from_list
239 241 contest_id = params[:users_contest_id]
240 242 org_contest, users = find_contest_and_user_from_contest_id(contest_id)
241 243 contest = Contest.find(params[:new_contest][:id])
242 244 if !contest
243 245 flash[:notice] = 'Error: no contest'
244 246 redirect_to :action => 'contests', :id =>contest_id
245 247 end
246 248
247 249 note = []
248 250 users.each do |u|
249 251 u.contests = [contest]
250 252 note << u.login
251 253 end
252 254 flash[:notice] = 'User(s) ' + note.join(', ') +
253 255 " were successfully reassigned to #{contest.title}."
254 256 redirect_to :action => 'contests', :id =>contest.id
255 257 end
256 258
257 259 def add_to_contest
258 260 user = User.find(params[:id])
259 261 contest = Contest.find(params[:contest_id])
260 262 if user and contest
261 263 user.contests << contest
262 264 end
263 265 redirect_to :action => 'index'
264 266 end
265 267
266 268 def remove_from_contest
267 269 user = User.find(params[:id])
268 270 contest = Contest.find(params[:contest_id])
269 271 if user and contest
270 272 user.contests.delete(contest)
271 273 end
272 274 redirect_to :action => 'index'
273 275 end
274 276
275 277 def contest_management
276 278 end
277 279
278 280 def manage_contest
279 281 contest = Contest.find(params[:contest][:id])
280 282 if !contest
281 283 flash[:notice] = 'You did not choose the contest.'
282 284 redirect_to :action => 'contest_management' and return
283 285 end
284 286
285 287 operation = params[:operation]
286 288
287 289 if not ['add','remove','assign'].include? operation
288 290 flash[:notice] = 'You did not choose the operation to perform.'
289 291 redirect_to :action => 'contest_management' and return
290 292 end
291 293
292 294 lines = params[:login_list]
293 295 if !lines or lines.blank?
294 296 flash[:notice] = 'You entered an empty list.'
295 297 redirect_to :action => 'contest_management' and return
296 298 end
297 299
298 300 note = []
299 301 users = []
300 302 lines.split("\n").each do |line|
301 303 user = User.find_by_login(line.chomp)
302 304 if user
303 305 if operation=='add'
304 306 if ! user.contests.include? contest
305 307 user.contests << contest
306 308 end
307 309 elsif operation=='remove'
308 310 user.contests.delete(contest)
309 311 else
310 312 user.contests = [contest]
311 313 end
312 314
313 315 if params[:reset_timer]
314 316 user.contest_stat.forced_logout = true
315 317 user.contest_stat.reset_timer_and_save
316 318 end
317 319
318 320 if params[:notification_emails]
319 321 send_contest_update_notification_email(user, contest)
320 322 end
321 323
322 324 note << user.login
323 325 users << user
324 326 end
325 327 end
326 328
327 329 if params[:reset_timer]
328 330 logout_users(users)
329 331 end
330 332
331 333 flash[:notice] = 'User(s) ' + note.join(', ') +
332 334 ' were successfully modified. '
333 335 redirect_to :action => 'contest_management'
334 336 end
335 337
336 338 # admin management
337 339
338 340 def admin
339 341 @admins = User.all.find_all {|user| user.admin? }
340 342 end
341 343
342 344 def grant_admin
343 345 login = params[:login]
344 346 user = User.find_by_login(login)
345 347 if user!=nil
346 348 admin_role = Role.find_by_name('admin')
347 349 user.roles << admin_role
348 350 else
349 351 flash[:notice] = 'Unknown user'
350 352 end
351 353 flash[:notice] = 'User added as admins'
352 354 redirect_to :action => 'admin'
353 355 end
354 356
355 357 def revoke_admin
356 358 user = User.find(params[:id])
357 359 if user==nil
358 360 flash[:notice] = 'Unknown user'
359 361 redirect_to :action => 'admin' and return
360 362 elsif user.login == 'root'
361 363 flash[:notice] = 'You cannot revoke admisnistrator permission from root.'
362 364 redirect_to :action => 'admin' and return
363 365 end
364 366
365 367 admin_role = Role.find_by_name('admin')
366 368 user.roles.delete(admin_role)
367 369 flash[:notice] = 'User permission revoked'
368 370 redirect_to :action => 'admin'
369 371 end
370 372
371 373 # mass mailing
372 374
373 375 def mass_mailing
374 376 end
375 377
376 378 def bulk_mail
377 379 lines = params[:login_list]
378 380 if !lines or lines.blank?
379 381 flash[:notice] = 'You entered an empty list.'
380 382 redirect_to :action => 'mass_mailing' and return
381 383 end
382 384
383 385 mail_subject = params[:subject]
384 386 if !mail_subject or mail_subject.blank?
385 387 flash[:notice] = 'You entered an empty mail subject.'
386 388 redirect_to :action => 'mass_mailing' and return
387 389 end
388 390
389 391 mail_body = params[:email_body]
390 392 if !mail_body or mail_body.blank?
391 393 flash[:notice] = 'You entered an empty mail body.'
392 394 redirect_to :action => 'mass_mailing' and return
393 395 end
394 396
395 397 note = []
396 398 users = []
397 399 lines.split("\n").each do |line|
398 400 user = User.find_by_login(line.chomp)
399 401 if user
400 402 send_mail(user.email, mail_subject, mail_body)
401 403 note << user.login
402 404 end
403 405 end
404 406
405 407 flash[:notice] = 'User(s) ' + note.join(', ') +
406 408 ' were successfully modified. '
407 409 redirect_to :action => 'mass_mailing'
408 410 end
409 411
410 412 #bulk manage
411 413 def bulk_manage
412 414
413 415 begin
414 - @users = User.where('login REGEXP ?',params[:regex]) if params[:regex]
416 + @users = User.where('(login REGEXP ?) OR (remark REGEXP ?)',params[:regex],params[:regex]) if params[:regex]
415 417 @users.count if @users #i don't know why I have to call count, but if I won't exception is not raised
416 418 rescue Exception
417 419 flash[:error] = 'Regular Expression is malformed'
418 420 @users = nil
419 421 end
420 422
421 423 if params[:commit]
422 424 @action = {}
423 425 @action[:set_enable] = params[:enabled]
424 426 @action[:enabled] = params[:enable] == "1"
425 427 @action[:gen_password] = params[:gen_password]
428 + @action[:add_group] = params[:add_group]
429 + @action[:group_name] = params[:group_name]
426 430 end
427 431
428 432 if params[:commit] == "Perform"
429 433 if @action[:set_enable]
430 434 @users.update_all(enabled: @action[:enabled])
431 435 end
432 436 if @action[:gen_password]
433 437 @users.each do |u|
434 438 password = random_password
435 439 u.password = password
436 440 u.password_confirmation = password
437 441 u.save
438 442 end
439 443 end
444 + if @action[:add_group] and @action[:group_name]
445 + @group = Group.find(@action[:group_name])
446 + ok = []
447 + failed = []
448 + @users.each do |user|
449 + begin
450 + @group.users << user
451 + ok << user.login
452 + rescue => e
453 + failed << user.login
454 + end
455 + end
456 + flash[:success] = "The following users are added to the 'group #{@group.name}': " + ok.join(', ') if ok.count > 0
457 + flash[:alert] = "The following users are already in the 'group #{@group.name}': " + failed.join(', ') if failed.count > 0
458 + end
440 459 end
441 460 end
442 461
443 462 protected
444 463
445 464 def random_password(length=5)
446 465 chars = 'abcdefghijkmnopqrstuvwxyz23456789'
447 466 newpass = ""
448 467 length.times { newpass << chars[rand(chars.size-1)] }
449 468 return newpass
450 469 end
451 470
452 471 def import_from_file(f)
453 472 data_hash = YAML.load(f)
454 473 @import_log = ""
455 474
456 475 country_data = data_hash[:countries]
457 476 site_data = data_hash[:sites]
458 477 user_data = data_hash[:users]
459 478
460 479 # import country
461 480 countries = {}
462 481 country_data.each_pair do |id,country|
463 482 c = Country.find_by_name(country[:name])
464 483 if c!=nil
465 484 countries[id] = c
466 485 @import_log << "Found #{country[:name]}\n"
467 486 else
468 487 countries[id] = Country.new(:name => country[:name])
469 488 countries[id].save
470 489 @import_log << "Created #{country[:name]}\n"
471 490 end
472 491 end
473 492
474 493 # import sites
475 494 sites = {}
476 495 site_data.each_pair do |id,site|
477 496 s = Site.find_by_name(site[:name])
478 497 if s!=nil
479 498 @import_log << "Found #{site[:name]}\n"
480 499 else
481 500 s = Site.new(:name => site[:name])
482 501 @import_log << "Created #{site[:name]}\n"
483 502 end
484 503 s.password = site[:password]
485 504 s.country = countries[site[:country_id]]
486 505 s.save
487 506 sites[id] = s
488 507 end
489 508
490 509 # import users
491 510 user_data.each_pair do |id,user|
492 511 u = User.find_by_login(user[:login])
493 512 if u!=nil
494 513 @import_log << "Found #{user[:login]}\n"
495 514 else
496 515 u = User.new(:login => user[:login])
497 516 @import_log << "Created #{user[:login]}\n"
498 517 end
499 518 u.full_name = user[:name]
500 519 u.password = user[:password]
501 520 u.country = countries[user[:country_id]]
502 521 u.site = sites[user[:site_id]]
503 522 u.activated = true
504 523 u.email = "empty-#{u.login}@none.com"
505 524 if not u.save
506 525 @import_log << "Errors\n"
507 526 u.errors.each { |attr,msg| @import_log << "#{attr} - #{msg}\n" }
508 527 end
509 528 end
510 529
511 530 end
512 531
513 532 def logout_users(users)
514 533 users.each do |user|
515 534 contest_stat = user.contest_stat(true)
516 535 if contest_stat and !contest_stat.forced_logout
517 536 contest_stat.forced_logout = true
518 537 contest_stat.save
519 538 end
520 539 end
521 540 end
522 541
523 542 def send_contest_update_notification_email(user, contest)
524 543 contest_title_name = GraderConfiguration['contest.name']
525 544 contest_name = contest.name
526 545 mail_subject = t('contest.notification.email_subject', {
527 546 :contest_title_name => contest_title_name,
528 547 :contest_name => contest_name })
529 548 mail_body = t('contest.notification.email_body', {
530 549 :full_name => user.full_name,
531 550 :contest_title_name => contest_title_name,
532 551 :contest_name => contest.name,
533 552 })
534 553
535 554 logger.info mail_body
@@ -1,221 +1,224
1 1 # Methods added to this helper will be available to all templates in the application.
2 2 module ApplicationHelper
3 3
4 4 #new bootstrap header
5 5 def navbar_user_header
6 6 left_menu = ''
7 7 right_menu = ''
8 8 user = User.find(session[:user_id])
9 9
10 10 if (user!=nil) and (GraderConfiguration.show_tasks_to?(user))
11 11 left_menu << add_menu("#{I18n.t 'menu.tasks'}", 'tasks', 'list')
12 12 left_menu << add_menu("#{I18n.t 'menu.submissions'}", 'main', 'submission')
13 13 left_menu << add_menu("#{I18n.t 'menu.test'}", 'test', 'index')
14 14 end
15 15
16 16 if GraderConfiguration['right.user_hall_of_fame']
17 17 left_menu << add_menu("#{I18n.t 'menu.hall_of_fame'}", 'report', 'problem_hof')
18 18 end
19 19
20 20 right_menu << add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-question-sign')}".html_safe, 'main', 'help')
21 21 right_menu << add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-comment')}".html_safe, 'messages', 'list', {title: I18n.t('menu.messages'), data: {toggle: 'tooltip'}})
22 22 if GraderConfiguration['system.user_setting_enabled']
23 23 right_menu << add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-cog')}".html_safe, 'users', 'index', {title: I18n.t('menu.settings'), data: {toggle: 'tooltip'}})
24 24 end
25 25 right_menu << add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-log-out')} #{user.full_name}".html_safe, 'main', 'login', {title: I18n.t('menu.log_out'), data: {toggle: 'tooltip'}})
26 26
27 27
28 28 result = content_tag(:ul,left_menu.html_safe,class: 'nav navbar-nav') + content_tag(:ul,right_menu.html_safe,class: 'nav navbar-nav navbar-right')
29 29 end
30 30
31 31 def add_menu(title, controller, action, html_option = {})
32 32 link_option = {controller: controller, action: action}
33 33 html_option[:class] = (html_option[:class] || '') + " active" if current_page?(link_option)
34 34 content_tag(:li, link_to(title,link_option),html_option)
35 35 end
36 36
37 37 def user_header
38 38 menu_items = ''
39 39 user = User.find(session[:user_id])
40 40
41 41 if (user!=nil) and (session[:admin])
42 42 # admin menu
43 43 menu_items << "<b>Administrative task:</b> "
44 44 append_to menu_items, '[Announcements]', 'announcements', 'index'
45 45 append_to menu_items, '[Msg console]', 'messages', 'console'
46 46 append_to menu_items, '[Problems]', 'problems', 'index'
47 47 append_to menu_items, '[Users]', 'user_admin', 'index'
48 48 append_to menu_items, '[Results]', 'user_admin', 'user_stat'
49 49 append_to menu_items, '[Report]', 'report', 'multiple_login'
50 50 append_to menu_items, '[Graders]', 'graders', 'list'
51 51 append_to menu_items, '[Contests]', 'contest_management', 'index'
52 52 append_to menu_items, '[Sites]', 'sites', 'index'
53 53 append_to menu_items, '[System config]', 'configurations', 'index'
54 54 menu_items << "<br/>"
55 55 end
56 56
57 57 # main page
58 58 append_to menu_items, "[#{I18n.t 'menu.main'}]", 'main', 'list'
59 59 append_to menu_items, "[#{I18n.t 'menu.messages'}]", 'messages', 'list'
60 60
61 61 if (user!=nil) and (GraderConfiguration.show_tasks_to?(user))
62 62 append_to menu_items, "[#{I18n.t 'menu.tasks'}]", 'tasks', 'list'
63 63 append_to menu_items, "[#{I18n.t 'menu.submissions'}]", 'main', 'submission'
64 64 append_to menu_items, "[#{I18n.t 'menu.test'}]", 'test', 'index'
65 65 end
66 66
67 67 if GraderConfiguration['right.user_hall_of_fame']
68 68 append_to menu_items, "[#{I18n.t 'menu.hall_of_fame'}]", 'report', 'problem_hof'
69 69 end
70 70 append_to menu_items, "[#{I18n.t 'menu.help'}]", 'main', 'help'
71 71
72 72 if GraderConfiguration['system.user_setting_enabled']
73 73 append_to menu_items, "[#{I18n.t 'menu.settings'}]", 'users', 'index'
74 74 end
75 75 append_to menu_items, "[#{I18n.t 'menu.log_out'}]", 'main', 'login'
76 76
77 77 menu_items.html_safe
78 78 end
79 79
80 80 def append_to(option,label, controller, action)
81 81 option << ' ' if option!=''
82 82 option << link_to_unless_current(label,
83 83 :controller => controller,
84 84 :action => action)
85 85 end
86 86
87 87 def format_short_time(time)
88 - now = Time.now.gmtime
88 + now = Time.zone.now
89 89 st = ''
90 - if (time.yday != now.yday) or
91 - (time.year != now.year)
92 - st = time.strftime("%x ")
90 + if (time.yday != now.yday) or (time.year != now.year)
91 + st = time.strftime("%d/%m/%y ")
93 92 end
94 93 st + time.strftime("%X")
95 94 end
96 95
97 96 def format_short_duration(duration)
98 97 return '' if duration==nil
99 98 d = duration.to_f
100 99 return Time.at(d).gmtime.strftime("%X")
101 100 end
102 101
102 + def format_full_time_ago(time)
103 + st = time_ago_in_words(time) + ' ago (' + format_short_time(time) + ')'
104 + end
105 +
103 106 def read_textfile(fname,max_size=2048)
104 107 begin
105 108 File.open(fname).read(max_size)
106 109 rescue
107 110 nil
108 111 end
109 112 end
110 113
111 114 def toggle_button(on,toggle_url,id, option={})
112 115 btn_size = option[:size] || 'btn-xs'
113 116 link_to (on ? "Yes" : "No"), toggle_url,
114 117 {class: "btn btn-block #{btn_size} btn-#{on ? 'success' : 'default'} ajax-toggle",
115 118 id: id,
116 119 data: {remote: true, method: 'get'}}
117 120 end
118 121
119 122 def get_ace_mode(language)
120 123 # return ace mode string from Language
121 124
122 125 case language.pretty_name
123 126 when 'Pascal'
124 127 'ace/mode/pascal'
125 128 when 'C++','C'
126 129 'ace/mode/c_cpp'
127 130 when 'Ruby'
128 131 'ace/mode/ruby'
129 132 when 'Python'
130 133 'ace/mode/python'
131 134 when 'Java'
132 135 'ace/mode/java'
133 136 else
134 137 'ace/mode/c_cpp'
135 138 end
136 139 end
137 140
138 141
139 142 def user_title_bar(user)
140 143 header = ''
141 144 time_left = ''
142 145
143 146 #
144 147 # if the contest is over
145 148 if GraderConfiguration.time_limit_mode?
146 149 if user.contest_finished?
147 150 header = <<CONTEST_OVER
148 151 <tr><td colspan="2" align="center">
149 152 <span class="contest-over-msg">THE CONTEST IS OVER</span>
150 153 </td></tr>
151 154 CONTEST_OVER
152 155 end
153 156 if !user.contest_started?
154 157 time_left = "&nbsp;&nbsp;" + (t 'title_bar.contest_not_started')
155 158 else
156 159 time_left = "&nbsp;&nbsp;" + (t 'title_bar.remaining_time') +
157 160 " #{format_short_duration(user.contest_time_left)}"
158 161 end
159 162 end
160 163
161 164 #
162 165 # if the contest is in the anaysis mode
163 166 if GraderConfiguration.analysis_mode?
164 167 header = <<ANALYSISMODE
165 168 <tr><td colspan="2" align="center">
166 169 <span class="contest-over-msg">ANALYSIS MODE</span>
167 170 </td></tr>
168 171 ANALYSISMODE
169 172 end
170 173
171 174 contest_name = GraderConfiguration['contest.name']
172 175
173 176 #
174 177 # build real title bar
175 178 result = <<TITLEBAR
176 179 <div class="title">
177 180 <table>
178 181 #{header}
179 182 <tr>
180 183 <td class="left-col">
181 184 #{user.full_name}<br/>
182 185 #{t 'title_bar.current_time'} #{format_short_time(Time.zone.now)}
183 186 #{time_left}
184 187 <br/>
185 188 </td>
186 189 <td class="right-col">#{contest_name}</td>
187 190 </tr>
188 191 </table>
189 192 </div>
190 193 TITLEBAR
191 194 result.html_safe
192 195 end
193 196
194 197 def markdown(text)
195 198 markdown = RDiscount.new(text)
196 199 markdown.to_html.html_safe
197 200 end
198 201
199 202
200 203 BOOTSTRAP_FLASH_MSG = {
201 204 success: 'alert-success',
202 205 error: 'alert-danger',
203 - alert: 'alert-block',
206 + alert: 'alert-danger',
204 207 notice: 'alert-info'
205 208 }
206 209
207 210 def bootstrap_class_for(flash_type)
208 211 BOOTSTRAP_FLASH_MSG.fetch(flash_type.to_sym, flash_type.to_s)
209 212 end
210 213
211 214 def flash_messages
212 215 flash.each do |msg_type, message|
213 216 concat(content_tag(:div, message, class: "alert #{bootstrap_class_for(msg_type)} fade in") do
214 217 concat content_tag(:button, 'x', class: "close", data: { dismiss: 'alert' })
215 218 concat message
216 219 end)
217 220 end
218 221 nil
219 222 end
220 223
221 224 end
@@ -1,183 +1,188
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
63 64
64 65 def self.show_tasks_to?(user)
65 66 if time_limit_mode?
66 67 return false if not user.contest_started?
67 68 end
68 69 return true
69 70 end
70 71
71 72 def self.show_grading_result
72 73 return (get(SYSTEM_MODE_CONF_KEY)=='analysis')
73 74 end
74 75
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
171 176 return GraderConfiguration.convert_type(conf.value,conf.value_type)
172 177 else
173 178 return nil
174 179 end
175 180 end
176 181
177 182 def self.read_grading_info
178 183 f = File.open(TASK_GRADING_INFO_FILENAME)
179 184 GraderConfiguration.task_grading_info_cache = YAML.load(f)
180 185 f.close
181 186 end
182 187
183 188 end
@@ -1,100 +1,108
1 1 class Problem < ActiveRecord::Base
2 2
3 3 belongs_to :description
4 4 has_and_belongs_to_many :contests, :uniq => true
5 +
6 + #has_and_belongs_to_many :groups
7 + has_many :groups_problems, class_name: GroupProblem
8 + has_many :groups, :through => :groups_problems
9 +
10 + has_many :problems_tags, class_name: ProblemTag
11 + has_many :tags, through: :problems_tags
12 +
5 13 has_many :test_pairs, :dependent => :delete_all
6 14 has_many :testcases, :dependent => :destroy
7 15
8 16 validates_presence_of :name
9 17 validates_format_of :name, :with => /\A\w+\z/
10 18 validates_presence_of :full_name
11 19
12 20 scope :available, -> { where(available: true) }
13 21
14 22 DEFAULT_TIME_LIMIT = 1
15 23 DEFAULT_MEMORY_LIMIT = 32
16 24
17 25 def self.available_problems
18 26 available.order(date_added: :desc).order(:name)
19 27 #Problem.available.all(:order => "date_added DESC, name ASC")
20 28 end
21 29
22 30 def self.create_from_import_form_params(params, old_problem=nil)
23 31 org_problem = old_problem || Problem.new
24 32 import_params, problem = Problem.extract_params_and_check(params,
25 33 org_problem)
26 34
27 35 if !problem.errors.empty?
28 36 return problem, 'Error importing'
29 37 end
30 38
31 39 problem.full_score = 100
32 40 problem.date_added = Time.new
33 41 problem.test_allowed = true
34 42 problem.output_only = false
35 43 problem.available = false
36 44
37 45 if not problem.save
38 46 return problem, 'Error importing'
39 47 end
40 48
41 49 import_to_db = params.has_key? :import_to_db
42 50
43 51 importer = TestdataImporter.new(problem)
44 52
45 53 if not importer.import_from_file(import_params[:file],
46 54 import_params[:time_limit],
47 55 import_params[:memory_limit],
48 56 import_params[:checker_name],
49 57 import_to_db)
50 58 problem.errors.add(:base,'Import error.')
51 59 end
52 60
53 61 return problem, importer.log_msg
54 62 end
55 63
56 64 def self.download_file_basedir
57 65 return "#{Rails.root}/data/tasks"
58 66 end
59 67
60 68 def get_submission_stat
61 69 result = Hash.new
62 70 #total number of submission
63 71 result[:total_sub] = Submission.where(problem_id: self.id).count
64 72 result[:attempted_user] = Submission.where(problem_id: self.id).group(:user_id)
65 73 result[:pass] = Submission.where(problem_id: self.id).where("points >= ?",self.full_score).count
66 74 return result
67 75 end
68 76
69 77 def long_name
70 78 "[#{name}] #{full_name}"
71 79 end
72 80
73 81 protected
74 82
75 83 def self.to_i_or_default(st, default)
76 84 if st!=''
77 85 result = st.to_i
78 86 end
79 87 result ||= default
80 88 end
81 89
82 90 def self.to_f_or_default(st, default)
83 91 if st!=''
84 92 result = st.to_f
85 93 end
86 94 result ||= default
87 95 end
88 96
89 97 def self.extract_params_and_check(params, problem)
90 98 time_limit = Problem.to_f_or_default(params[:time_limit],
91 99 DEFAULT_TIME_LIMIT)
92 100 memory_limit = Problem.to_i_or_default(params[:memory_limit],
93 101 DEFAULT_MEMORY_LIMIT)
94 102
95 103 if time_limit<=0 or time_limit >60
96 104 problem.errors.add(:base,'Time limit out of range.')
97 105 end
98 106
99 107 if memory_limit==0 and params[:memory_limit]!='0'
100 108 problem.errors.add(:base,'Memory limit format errors.')
@@ -1,162 +1,166
1 1 class Submission < ActiveRecord::Base
2 2
3 3 belongs_to :language
4 4 belongs_to :problem
5 5 belongs_to :user
6 6
7 7 before_validation :assign_problem
8 8 before_validation :assign_language
9 9
10 10 validates_presence_of :source
11 11 validates_length_of :source, :maximum => 100_000, :allow_blank => true, :message => 'too long'
12 12 validates_length_of :source, :minimum => 1, :allow_blank => true, :message => 'too short'
13 13 validate :must_have_valid_problem
14 14 validate :must_specify_language
15 15
16 16 has_one :task
17 17
18 18 before_save :assign_latest_number_if_new_recond
19 19
20 20 def self.find_last_by_user_and_problem(user_id, problem_id)
21 21 where("user_id = ? AND problem_id = ?",user_id,problem_id).last
22 22 end
23 23
24 24 def self.find_all_last_by_problem(problem_id)
25 25 # need to put in SQL command, maybe there's a better way
26 26 Submission.includes(:user).find_by_sql("SELECT * FROM submissions " +
27 27 "WHERE id = " +
28 28 "(SELECT MAX(id) FROM submissions AS subs " +
29 29 "WHERE subs.user_id = submissions.user_id AND " +
30 30 "problem_id = " + problem_id.to_s + " " +
31 31 "GROUP BY user_id) " +
32 32 "ORDER BY user_id")
33 33 end
34 34
35 35 def self.find_in_range_by_user_and_problem(user_id, problem_id,since_id,until_id)
36 36 records = Submission.where(problem_id: problem_id,user_id: user_id)
37 - records = records.where('id >= ?',since_id) if since_id > 0
38 - records = records.where('id <= ?',until_id) if until_id > 0
37 + records = records.where('id >= ?',since_id) if since_id and since_id > 0
38 + records = records.where('id <= ?',until_id) if until_id and until_id > 0
39 39 records.all
40 40 end
41 41
42 42 def self.find_last_for_all_available_problems(user_id)
43 43 submissions = Array.new
44 44 problems = Problem.available_problems
45 45 problems.each do |problem|
46 46 sub = Submission.find_last_by_user_and_problem(user_id, problem.id)
47 47 submissions << sub if sub!=nil
48 48 end
49 49 submissions
50 50 end
51 51
52 52 def self.find_by_user_problem_number(user_id, problem_id, number)
53 53 where("user_id = ? AND problem_id = ? AND number = ?",user_id,problem_id,number).first
54 54 end
55 55
56 56 def self.find_all_by_user_problem(user_id, problem_id)
57 57 where("user_id = ? AND problem_id = ?",user_id,problem_id)
58 58 end
59 59
60 60 def download_filename
61 61 if self.problem.output_only
62 62 return self.source_filename
63 63 else
64 64 timestamp = self.submitted_at.localtime.strftime("%H%M%S")
65 65 return "#{self.problem.name}-#{timestamp}.#{self.language.ext}"
66 66 end
67 67 end
68 68
69 69 protected
70 70
71 71 def self.find_option_in_source(option, source)
72 72 if source==nil
73 73 return nil
74 74 end
75 75 i = 0
76 76 source.each_line do |s|
77 77 if s =~ option
78 78 words = s.split
79 79 return words[1]
80 80 end
81 81 i = i + 1
82 82 if i==10
83 83 return nil
84 84 end
85 85 end
86 86 return nil
87 87 end
88 88
89 89 def self.find_language_in_source(source, source_filename="")
90 90 langopt = find_option_in_source(/^LANG:/,source)
91 91 if langopt
92 92 return (Language.find_by_name(langopt) ||
93 93 Language.find_by_pretty_name(langopt))
94 94 else
95 95 if source_filename
96 96 return Language.find_by_extension(source_filename.split('.').last)
97 97 else
98 98 return nil
99 99 end
100 100 end
101 101 end
102 102
103 103 def self.find_problem_in_source(source, source_filename="")
104 104 prob_opt = find_option_in_source(/^TASK:/,source)
105 105 if problem = Problem.find_by_name(prob_opt)
106 106 return problem
107 107 else
108 108 if source_filename
109 109 return Problem.find_by_name(source_filename.split('.').first)
110 110 else
111 111 return nil
112 112 end
113 113 end
114 114 end
115 115
116 116 def assign_problem
117 117 if self.problem_id!=-1
118 118 begin
119 119 self.problem = Problem.find(self.problem_id)
120 120 rescue ActiveRecord::RecordNotFound
121 121 self.problem = nil
122 122 end
123 123 else
124 124 self.problem = Submission.find_problem_in_source(self.source,
125 125 self.source_filename)
126 126 end
127 127 end
128 128
129 129 def assign_language
130 130 self.language = Submission.find_language_in_source(self.source,
131 131 self.source_filename)
132 132 end
133 133
134 134 # validation codes
135 135 def must_specify_language
136 136 return if self.source==nil
137 137
138 138 # for output_only tasks
139 139 return if self.problem!=nil and self.problem.output_only
140 140
141 141 if self.language==nil
142 142 errors.add('source',"Cannot detect language. Did you submit a correct source file?") unless self.language!=nil
143 143 end
144 144 end
145 145
146 146 def must_have_valid_problem
147 147 return if self.source==nil
148 148 if self.problem==nil
149 149 errors.add('problem',"must be specified.")
150 - elsif (!self.problem.available) and (self.new_record?)
151 - errors.add('problem',"must be valid.")
150 + else
151 + #admin always have right
152 + return if self.user.admin?
153 +
154 + #check if user has the right to submit the problem
155 + errors.add('problem',"must be valid.") if (!self.user.available_problems.include?(self.problem)) and (self.new_record?)
152 156 end
153 157 end
154 158
155 159 # callbacks
156 160 def assign_latest_number_if_new_recond
157 161 return if !self.new_record?
158 162 latest = Submission.find_last_by_user_and_problem(self.user_id, self.problem_id)
159 163 self.number = (latest==nil) ? 1 : latest.number + 1;
160 164 end
161 165
162 166 end
@@ -1,106 +1,110
1 1 require 'digest/sha1'
2 2 require 'net/pop'
3 3 require 'net/https'
4 4 require 'net/http'
5 5 require 'json'
6 6
7 7 class User < ActiveRecord::Base
8 8
9 9 has_and_belongs_to_many :roles
10 10
11 + #has_and_belongs_to_many :groups
12 + has_many :groups_users, class_name: GroupUser
13 + has_many :groups, :through => :groups_users
14 +
11 15 has_many :test_requests, -> {order(submitted_at: DESC)}
12 16
13 17 has_many :messages, -> { order(created_at: DESC) },
14 18 :class_name => "Message",
15 19 :foreign_key => "sender_id"
16 20
17 21 has_many :replied_messages, -> { order(created_at: DESC) },
18 22 :class_name => "Message",
19 23 :foreign_key => "receiver_id"
20 24
21 25 has_one :contest_stat, :class_name => "UserContestStat", :dependent => :destroy
22 26
23 27 belongs_to :site
24 28 belongs_to :country
25 29
26 30 has_and_belongs_to_many :contests, -> { order(:name); uniq}
27 31
28 32 scope :activated_users, -> {where activated: true}
29 33
30 34 validates_presence_of :login
31 35 validates_uniqueness_of :login
32 36 validates_format_of :login, :with => /\A[\_A-Za-z0-9]+\z/
33 37 validates_length_of :login, :within => 3..30
34 38
35 39 validates_presence_of :full_name
36 40 validates_length_of :full_name, :minimum => 1
37 41
38 42 validates_presence_of :password, :if => :password_required?
39 43 validates_length_of :password, :within => 4..20, :if => :password_required?
40 44 validates_confirmation_of :password, :if => :password_required?
41 45
42 46 validates_format_of :email,
43 47 :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i,
44 48 :if => :email_validation?
45 49 validate :uniqueness_of_email_from_activated_users,
46 50 :if => :email_validation?
47 51 validate :enough_time_interval_between_same_email_registrations,
48 52 :if => :email_validation?
49 53
50 54 # these are for ytopc
51 55 # disable for now
52 56 #validates_presence_of :province
53 57
54 58 attr_accessor :password
55 59
56 60 before_save :encrypt_new_password
57 61 before_save :assign_default_site
58 62 before_save :assign_default_contest
59 63
60 64 # this is for will_paginate
61 65 cattr_reader :per_page
62 66 @@per_page = 50
63 67
64 68 def self.authenticate(login, password)
65 69 user = find_by_login(login)
66 70 if user
67 71 return user if user.authenticated?(password)
68 72 end
69 73 end
70 74
71 75 def authenticated?(password)
72 76 if self.activated
73 77 hashed_password == User.encrypt(password,self.salt)
74 78 else
75 79 false
76 80 end
77 81 end
78 82
79 83 def admin?
80 84 self.roles.detect {|r| r.name == 'admin' }
81 85 end
82 86
83 87 def email_for_editing
84 88 if self.email==nil
85 89 "(unknown)"
86 90 elsif self.email==''
87 91 "(blank)"
88 92 else
89 93 self.email
90 94 end
91 95 end
92 96
93 97 def email_for_editing=(e)
94 98 self.email=e
95 99 end
96 100
97 101 def alias_for_editing
98 102 if self.alias==nil
99 103 "(unknown)"
100 104 elsif self.alias==''
101 105 "(blank)"
102 106 else
103 107 self.alias
104 108 end
105 109 end
106 110
@@ -147,194 +151,219
147 151 time_limit = GraderConfiguration.contest_time_limit
148 152 if time_limit == nil
149 153 return nil
150 154 end
151 155 if contest_stat==nil or contest_stat.started_at==nil
152 156 return (Time.now.gmtime + time_limit) - Time.now.gmtime
153 157 else
154 158 finish_time = contest_stat.started_at + time_limit
155 159 current_time = Time.now.gmtime
156 160 if current_time > finish_time
157 161 return 0
158 162 else
159 163 return finish_time - current_time
160 164 end
161 165 end
162 166 else
163 167 return nil
164 168 end
165 169 end
166 170
167 171 def contest_finished?
168 172 if GraderConfiguration.contest_mode?
169 173 return false if site==nil
170 174 return site.finished?
171 175 elsif GraderConfiguration.indv_contest_mode?
172 176 return false if self.contest_stat(true)==nil
173 177 return contest_time_left == 0
174 178 else
175 179 return false
176 180 end
177 181 end
178 182
179 183 def contest_started?
180 184 if GraderConfiguration.indv_contest_mode?
181 185 stat = self.contest_stat
182 186 return ((stat != nil) and (stat.started_at != nil))
183 187 elsif GraderConfiguration.contest_mode?
184 188 return true if site==nil
185 189 return site.started
186 190 else
187 191 return true
188 192 end
189 193 end
190 194
191 195 def update_start_time
192 196 stat = self.contest_stat
193 197 if stat.nil? or stat.started_at.nil?
194 198 stat ||= UserContestStat.new(:user => self)
195 199 stat.started_at = Time.now.gmtime
196 200 stat.save
197 201 end
198 202 end
199 203
200 204 def problem_in_user_contests?(problem)
201 205 problem_contests = problem.contests.all
202 206
203 207 if problem_contests.length == 0 # this is public contest
204 208 return true
205 209 end
206 210
207 211 contests.each do |contest|
208 212 if problem_contests.find {|c| c.id == contest.id }
209 213 return true
210 214 end
211 215 end
212 216 return false
213 217 end
214 218
215 219 def available_problems_group_by_contests
216 220 contest_problems = []
217 221 pin = {}
218 222 contests.enabled.each do |contest|
219 223 available_problems = contest.problems.available
220 224 contest_problems << {
221 225 :contest => contest,
222 226 :problems => available_problems
223 227 }
224 228 available_problems.each {|p| pin[p.id] = true}
225 229 end
226 230 other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0}
227 231 contest_problems << {
228 232 :contest => nil,
229 233 :problems => other_avaiable_problems
230 234 }
231 235 return contest_problems
232 236 end
233 237
234 238 def solve_all_available_problems?
235 239 available_problems.each do |p|
236 240 u = self
237 241 sub = Submission.find_last_by_user_and_problem(u.id,p.id)
238 242 return false if !p or !sub or sub.points < p.full_score
239 243 end
240 244 return true
241 245 end
242 246
247 + #get a list of available problem
243 248 def available_problems
244 249 if not GraderConfiguration.multicontests?
250 + if GraderConfiguration.use_problem_group?
251 + return available_problems_in_group
252 + else
245 253 return Problem.available_problems
254 + end
246 255 else
247 256 contest_problems = []
248 257 pin = {}
249 258 contests.enabled.each do |contest|
250 259 contest.problems.available.each do |problem|
251 260 if not pin.has_key? problem.id
252 261 contest_problems << problem
253 262 end
254 263 pin[problem.id] = true
255 264 end
256 265 end
257 266 other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0}
258 267 return contest_problems + other_avaiable_problems
259 268 end
260 269 end
261 270
271 + def available_problems_in_group
272 + problem = []
273 + self.groups.each do |group|
274 + group.problems.where(available: true).each { |p| problem << p }
275 + end
276 + problem.uniq!
277 + if problem
278 + problem.sort! do |a,b|
279 + case
280 + when a.date_added < b.date_added
281 + 1
282 + when a.date_added > b.date_added
283 + -1
284 + else
285 + a.name <=> b.name
286 + end
287 + end
288 + return problem
289 + else
290 + return []
291 + end
292 + end
293 +
262 294 def can_view_problem?(problem)
263 - if not GraderConfiguration.multicontests?
264 - return problem.available
265 - else
266 - return problem_in_user_contests? problem
267 - end
295 + return true if admin?
296 + return available_problems.include? problem
268 297 end
269 298
270 299 def self.clear_last_login
271 300 User.update_all(:last_ip => nil)
272 301 end
273 302
274 303 protected
275 304 def encrypt_new_password
276 305 return if password.blank?
277 306 self.salt = (10+rand(90)).to_s
278 307 self.hashed_password = User.encrypt(self.password,self.salt)
279 308 end
280 309
281 310 def assign_default_site
282 311 # have to catch error when migrating (because self.site is not available).
283 312 begin
284 313 if self.site==nil
285 314 self.site = Site.find_by_name('default')
286 315 if self.site==nil
287 316 self.site = Site.find(1) # when 'default has be renamed'
288 317 end
289 318 end
290 319 rescue
291 320 end
292 321 end
293 322
294 323 def assign_default_contest
295 324 # have to catch error when migrating (because self.site is not available).
296 325 begin
297 326 if self.contests.length == 0
298 327 default_contest = Contest.find_by_name(GraderConfiguration['contest.default_contest_name'])
299 328 if default_contest
300 329 self.contests = [default_contest]
301 330 end
302 331 end
303 332 rescue
304 333 end
305 334 end
306 335
307 336 def password_required?
308 337 self.hashed_password.blank? || !self.password.blank?
309 338 end
310 339
311 340 def self.encrypt(string,salt)
312 341 Digest::SHA1.hexdigest(salt + string)
313 342 end
314 343
315 344 def uniqueness_of_email_from_activated_users
316 345 user = User.activated_users.find_by_email(self.email)
317 346 if user and (user.login != self.login)
318 347 self.errors.add(:base,"Email has already been taken")
319 348 end
320 349 end
321 350
322 351 def enough_time_interval_between_same_email_registrations
323 352 return if !self.new_record?
324 353 return if self.activated
325 354 open_user = User.find_by_email(self.email,
326 355 :order => 'created_at DESC')
327 356 if open_user and open_user.created_at and
328 357 (open_user.created_at > Time.now.gmtime - 5.minutes)
329 358 self.errors.add(:base,"There are already unactivated registrations with this e-mail address (please wait for 5 minutes)")
330 359 end
331 360 end
332 361
333 362 def email_validation?
334 363 begin
335 364 return VALIDATE_USER_EMAILS
336 365 rescue
337 366 return false
338 367 end
339 368 end
340 369 end
@@ -1,37 +1,37
1 1 <p>
2 2 <b>Author:</b>
3 3 <%=h @announcement.author %>
4 4 </p>
5 5
6 6 <p>
7 7 <b>Title:</b>
8 8 <%=h @announcement.title %>
9 9 </p>
10 10
11 11 <p>
12 12 <b>Notes:</b>
13 13 <%=h @announcement.notes %>
14 14 </p>
15 15
16 16 <p>
17 17 <b>Body:</b>
18 - <%=h @announcement.body %>
18 + <%=h markdown(@announcement.body) %>
19 19 </p>
20 20
21 21 <p>
22 22 <b>Published:</b>
23 23 <%=h @announcement.published %>
24 24 </p>
25 25
26 26 <p>
27 27 <b>Show on front page:</b>
28 28 <%=h @announcement.frontpage %>
29 29 </p>
30 30
31 31 <p>
32 32 <b>Show only in contest:</b>
33 33 <%=h @announcement.contest_only %>
34 34 </p>
35 35
36 36 <%= link_to 'Edit', edit_announcement_path(@announcement) %> |
37 37 <%= link_to 'Back', announcements_path %>
@@ -1,26 +1,28
1 -
2 1 - if submission.nil?
3 2 = "-"
4 3 - else
4 + %strong= "Submission ID:"
5 + = submission.id
6 + %br
5 7 - unless submission.graded_at
6 - = t 'main.submitted_at'
7 - = format_short_time(submission.submitted_at.localtime)
8 + %strong= t 'main.submitted_at:'
9 + = format_full_time_ago(submission.submitted_at.localtime)
8 10 - else
9 - %strong= t 'main.graded_at'
10 - = "#{format_short_time(submission.graded_at.localtime)} "
11 + %strong= t 'main.graded_at:'
12 + = format_full_time_ago(submission.graded_at.localtime)
11 13 %br
12 14 - if GraderConfiguration['ui.show_score']
13 15 %strong=t 'main.score'
14 16 = "#{(submission.points*100/submission.problem.full_score).to_i} "
15 17 = " ["
16 18 %tt
17 19 = submission.grader_comment
18 20 = "]"
19 21 %br
20 22 %strong View:
21 23 - if GraderConfiguration.show_grading_result
22 24 = link_to '[detailed result]', :action => 'result', :id => submission.id
23 - = link_to "#{t 'main.cmp_msg'}", {:action => 'compiler_msg', :id => submission.id}, {popup: true,class: 'btn btn-xs btn-info'}
25 + = link_to "#{t 'main.cmp_msg'}", {:action => 'compiler_msg', :id => submission.id}, {popup: true,class: 'btn btn-xs btn-info'} if submission.graded_at
24 26 = link_to "#{t 'main.src_link'}", download_submission_path(submission.id), class: 'btn btn-xs btn-info'
25 27 = link_to "#{t 'main.submissions_link'}", problem_submissions_path(problem_id), class: 'btn btn-xs btn-info'
26 28
@@ -1,15 +1,15
1 1 %h1= "Task: #{@task.id}"
2 2
3 3 %p
4 4 User:
5 5 = "#{@task.submission.user.login}"
6 6 %br/
7 7 Status:
8 8 = "#{@task.status_str} (at #{format_short_time(@task.updated_at)})"
9 9 %br/
10 10 = "Submission: #{@task.submission_id}"
11 11 - if @task.submission !=nil
12 - = link_to '[view submission]', :action => 'submission', :id => @task.submission.id
12 + = link_to '[view submission]', submission_path( @task.submission.id )
13 13 %br/
14 14 = "Submitted at: #{format_short_time(@task.created_at)}"
15 15 %br/
@@ -1,93 +1,95
1 1 %header.navbar.navbar-default.navbar-fixed-top
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 + = add_menu( 'Tags', 'tags', 'index')
49 50 = add_menu( 'Users', 'user_admin', 'index')
51 + = add_menu( 'User Groups', 'groups', 'index')
50 52 = add_menu( 'Graders', 'graders', 'list')
51 53 = add_menu( 'Message ', 'messages', 'console')
52 54 %li.divider{role: 'separator'}
53 55 = add_menu( 'System config', 'configurations', 'index')
54 56 %li.divider{role: 'separator'}
55 57 = add_menu( 'Sites', 'sites', 'index')
56 58 = add_menu( 'Contests', 'contest_management', 'index')
57 59 / report
58 60 %li.dropdown
59 61 %a.dropdown-toggle{href: '#', data: {toggle:'dropdown'}, aria: {haspopup:"true", expanded:"false"}, role: "button"}
60 62 Report
61 63 %span.caret
62 64 %ul.dropdown-menu
63 65 = add_menu( 'Current Score', 'report', 'current_score')
64 66 = add_menu( 'Score Report', 'report', 'max_score')
65 67 = add_menu( 'Report', 'report', 'multiple_login')
66 68 - if (ungraded = Submission.where('graded_at is null').where('submitted_at < ?', 1.minutes.ago).count) > 0
67 69 =link_to "#{ungraded} backlogs!",
68 70 grader_list_path,
69 71 class: 'navbar-btn btn btn-default btn-warning', data: {toggle: 'tooltip'},title: 'Number of ungraded submission'
70 72
71 73 %ul.nav.navbar-nav.navbar-right
72 74 = add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-question-sign')}".html_safe, 'main', 'help')
73 75 = add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-comment')}".html_safe, 'messages', 'list', {title: I18n.t('menu.messages'), data: {toggle: 'tooltip'}})
74 76 - if GraderConfiguration['system.user_setting_enabled']
75 77 = add_menu("#{content_tag(:span,'',class: 'glyphicon glyphicon-cog')}".html_safe, 'users', 'index', {title: I18n.t('menu.settings'), data: {toggle: 'tooltip'}})
76 78 = 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 79
78 80 /
79 81 - if (@current_user!=nil) and (session[:admin])
80 82 %nav.navbar.navbar-fixed-top.navbar-inverse.secondnavbar
81 83 .container-fluid
82 84 .collapse.navbar-collapse
83 85 %ul.nav.navbar-nav
84 86 = add_menu( '[Announcements]', 'announcements', 'index')
85 87 = add_menu( '[Msg console]', 'messages', 'console')
86 88 = add_menu( '[Problems]', 'problems', 'index')
87 89 = add_menu( '[Users]', 'user_admin', 'index')
88 90 = add_menu( '[Results]', 'user_admin', 'user_stat')
89 91 = add_menu( '[Report]', 'report', 'multiple_login')
90 92 = add_menu( '[Graders]', 'graders', 'list')
91 93 = add_menu( '[Contests]', 'contest_management', 'index')
92 94 = add_menu( '[Sites]', 'sites', 'index')
93 95 = add_menu( '[System config]', 'configurations', 'index')
@@ -1,39 +1,43
1 1 %b= GraderConfiguration['ui.front.welcome_message']
2 2 %br/
3 3
4 4 - if !@hidelogin
5 5 =t 'login.message'
6 6 %br/
7 7 %br/
8 8
9 9 - if flash[:notice]
10 10 %hr/
11 11 %b= flash[:notice]
12 12 %hr/
13 13
14 14 %div{ :style => "border: solid 1px gray; padding: 4px; background: #eeeeff;"}
15 - = form_tag login_login_path do
16 - %table
17 - %tr
18 - %td{:align => "right"}
19 - ="#{t 'login_label'}:"
20 - %td= text_field_tag 'login'
21 - %tr
22 - %td{:align => "right"}
23 - ="#{t 'password_label'}:"
24 - %td= password_field_tag
15 + = form_tag login_login_path, {class: 'form-horizontal'} do
16 + .form-group
17 + =label_tag :login, "Login",class: 'col-sm-3 control-label'
18 + .col-sm-9
19 + =text_field_tag :login, nil, class: 'form-control'
20 + .form-group
21 + =label_tag :password, "Password", class: 'col-sm-3 control-label'
22 + .col-sm-9
23 + =password_field_tag :password, nil, class: 'form-control'
25 24 - unless GraderConfiguration['right.bypass_agreement']
26 - %tr
27 - %td{:align => "right"}= check_box_tag 'accept_agree'
28 - %td ยอมรับข้อตกลงการใช้งาน
25 + .form-group
26 + .col-sm-offset-3.col-sm-9
27 + .checkbox
28 + %label
29 + = check_box_tag 'accept_agree'
30 + ยอมรับข้อตกลงการใช้งาน
29 31
30 - = submit_tag t('login.login_submit')
32 + .form-group
33 + .col-sm-offset-3.col-sm-9
34 + = submit_tag t('login.login_submit'), class: 'btn btn-primary'
31 35 %br/
32 36
33 37 - if GraderConfiguration['system.online_registration']
34 38 =t 'login.participation'
35 39 %b
36 40 = "#{t 'login.please'} "
37 41 = link_to "#{t 'login.register'}", :controller => :users, :action => :new
38 42 %br/
39 43 = link_to "#{t 'login.forget_password'}", :controller => :users, :action => :forget
@@ -1,22 +1,22
1 1 %tr
2 2 %td
3 3 - if @current_user and @current_user.admin?
4 4 = link_to problem.name, stat_problem_path(problem)
5 5 - else
6 6 = "#{problem.name}"
7 7 %td
8 8 = "#{problem.full_name}"
9 9
10 10 %br
11 11 = link_to_description_if_any "[#{t 'main.problem_desc'}] <span class='glyphicon glyphicon-file'></span>".html_safe, problem
12 12 %td
13 13 = @prob_submissions[problem.id][:count]
14 - = link_to "[subs]", main_submission_path(problem.id)
14 + -#= link_to "[subs]", main_submission_path(problem.id)
15 15 %td
16 16 = render :partial => 'submission_short',
17 17 :locals => {:submission => @prob_submissions[problem.id][:submission], :problem_name => problem.name, :problem_id => problem.id }
18 18 %td
19 19 - if @prob_submissions[problem.id][:submission]
20 20 = link_to 'Edit', edit_submission_path(@prob_submissions[problem.id][:submission]), class: 'btn btn-success'
21 21 - else
22 22 = link_to 'New', direct_edit_problem_submissions_path(problem.id), class: 'btn btn-success'
@@ -1,29 +1,29
1 1
2 2 - if submission.nil?
3 3 = "-"
4 4 - else
5 5 - unless submission.graded_at
6 6 = t 'main.submitted_at'
7 7 = format_short_time(submission.submitted_at.localtime)
8 8 - else
9 9 %strong= t 'main.graded_at'
10 10 = "#{format_short_time(submission.graded_at.localtime)} "
11 11 %br
12 12 - if GraderConfiguration['ui.show_score']
13 13 %strong=t 'main.score'
14 14 = "#{(submission.points*100/submission.problem.full_score).to_i} "
15 15 = " ["
16 - %tt
16 + %tt.grader-comment
17 17 = submission.grader_comment
18 18 = "]"
19 19 %br
20 20 %strong View:
21 21 - if GraderConfiguration.show_grading_result
22 22 = link_to '[detailed result]', :action => 'result', :id => submission.id
23 23 /= link_to "#{t 'main.cmp_msg'}", {:action => 'compiler_msg', :id => submission.id}, {popup: true,class: 'btn btn-xs btn-info'}
24 24 = link_to "#{t 'main.cmp_msg'}", compiler_msg_submission_path(submission.id), {popup: true,remote: true,class: 'btn btn-xs btn-info'}
25 25 = link_to "#{t 'main.src_link'}",{:action => 'source', :id => submission.id}, class: 'btn btn-xs btn-info'
26 26 = link_to "#{t 'main.submissions_link'}", problem_submissions_path(problem_id), class: 'btn btn-xs btn-info'
27 27 - if GraderConfiguration.show_testcase
28 28 = link_to "testcases", show_problem_testcases_path(problem_id), class: 'btn btn-xs btn-info'
29 29
@@ -1,12 +1,11
1 1 %h1= GraderConfiguration['ui.front.title']
2 2
3 - %table
4 - %tr
5 - %td
3 + .row
4 + .col-md-6
6 5 - if @announcements.length!=0
7 6 .announcementbox{:style => 'margin-top: 0px'}
8 7 %span{:class => 'title'}
9 8 Announcements
10 9 = render :partial => 'announcement', :collection => @announcements
11 - %td{:style => 'vertical-align: top; width: 40%; padding-left: 20px;'}
10 + .col-md-4{style: "padding-left: 20px;"}
12 11 = render :partial => 'login_box'
@@ -1,52 +1,55
1 1 = error_messages_for 'problem'
2 2 / [form:problem]
3 3 .form-group
4 4 %label{:for => "problem_name"} Name
5 5 = text_field 'problem', 'name', class: 'form-control'
6 6 %small
7 7 Do not directly edit the problem name, unless you know what you are doing. If you want to change the name, use the name change button in the problem management menu instead.
8 8 .form-group
9 9 %label{:for => "problem_full_name"} Full name
10 10 = text_field 'problem', 'full_name', class: 'form-control'
11 11 .form-group
12 12 %label{:for => "problem_full_score"} Full score
13 13 = text_field 'problem', 'full_score', class: 'form-control'
14 14 .form-group
15 + %label{:for => "problem_full_score"} Tags
16 + = collection_select(:problem, :tag_ids, Tag.all, :id, :name, {}, {multiple: true, class: 'form-control select2'})
17 + .form-group
15 18 %label{:for => "problem_date_added"} Date added
16 19 = date_select 'problem', 'date_added', class: 'form-control'
17 20 - # TODO: these should be put in model Problem, but I can't think of
18 21 - # nice default values for them. These values look fine only
19 22 - # in this case (of lazily adding new problems).
20 23 - @problem.available = true if @problem!=nil and @problem.available==nil
21 24 - @problem.test_allowed = true if @problem!=nil and @problem.test_allowed==nil
22 25 - @problem.output_only = false if @problem!=nil and @problem.output_only==nil
23 26 .checkbox
24 27 %label{:for => "problem_available"}
25 28 = check_box :problem, :available
26 29 Available?
27 30 .checkbox
28 31 %label{:for => "problem_test_allowed"}
29 32 = check_box :problem, :test_allowed
30 33 Test allowed?
31 34 .checkbox
32 35 %label{:for => "problem_output_only"}
33 36 = check_box :problem, :output_only
34 37 Output only?
35 38 = error_messages_for 'description'
36 39 .form-group
37 40 %label{:for => "description_body"} Description
38 41 %br/
39 42 = text_area :description, :body, :rows => 10, :cols => 80,class: 'form-control'
40 43 .form-group
41 44 %label{:for => "description_markdowned"} Markdowned?
42 45 = select "description", |
43 46 "markdowned", |
44 47 [['True',true],['False',false]], |
45 48 {:selected => (@description) ? @description.markdowned : false } |
46 49 .form-group
47 50 %label{:for => "problem_url"} URL
48 51 %br/
49 52 = text_field 'problem', 'url',class: 'form-control'
50 53 %p
51 54 Task PDF #{file_field_tag 'file'}
52 55 / [eoform:problem]
@@ -1,54 +1,65
1 1 - content_for :head do
2 2 = stylesheet_link_tag 'problems'
3 - %h1 Listing problems
3 + %h1 Problems
4 4 %p
5 - = link_to 'New problem', new_problem_path, class: 'btn btn-default btn-sm'
6 - = link_to 'Manage problems', { action: 'manage'}, class: 'btn btn-default btn-sm'
7 - = link_to 'Import problems', {:action => 'import'}, class: 'btn btn-default btn-sm'
5 + = link_to 'Import problems', {:action => 'import'}, class: 'btn btn-success btn-sm'
6 + = link_to 'New problem', new_problem_path, class: 'btn btn-success btn-sm'
7 + = link_to 'Bulk Manage', { action: 'manage'}, class: 'btn btn-info btn-sm'
8 8 = link_to 'Turn off all problems', {:action => 'turn_all_off'}, class: 'btn btn-default btn-sm'
9 9 = link_to 'Turn on all problems', {:action => 'turn_all_on'}, class: 'btn btn-default btn-sm'
10 10 .submitbox
11 11 = form_tag :action => 'quick_create' do
12 12 %b Quick New:
13 13 %label{:for => "problem_name"} Name
14 14 = text_field 'problem', 'name'
15 15 |
16 16 %label{:for => "problem_full_name"} Full name
17 17 = text_field 'problem', 'full_name'
18 18 = submit_tag "Create"
19 19 %table.table.table-condense.table-hover
20 20 %thead
21 21 %th Name
22 22 %th Full name
23 23 %th.text-right Full score
24 + %th Tags
25 + %th
26 + Submit
27 + %sup{class: 'text-primary',data: {toggle: 'tooltip'}, title: 'Admin can always submit to any problem' } [?]
24 28 %th Date added
25 29 %th.text-center
26 30 Avail?
27 31 %sup{class: 'text-primary',data: {toggle: 'tooltip'}, title: 'Let user submits to this problem?' } [?]
28 32 %th.text-center
29 33 View Data?
30 34 %sup{class: 'text-primary',data: {toggle: 'tooltip'}, title: 'Let user view the testcase of this problem?' } [?]
31 35 %th.text-center
32 36 Test?
33 37 %sup{class: 'text-primary',data: {toggle: 'tooltip'}, title: 'Let user uses test interface on this problem?' } [?]
34 38 - if GraderConfiguration.multicontests?
35 39 %th Contests
36 40 - for problem in @problems
37 41 %tr{:class => "#{(problem.available) ? "success" : "danger"}", :id => "prob-#{problem.id}", :name => "prob-#{problem.id}"}
38 42 - @problem=problem
39 43 %td= problem.name #in_place_editor_field :problem, :name, {}, :rows=>1
40 - %td= problem.full_name #in_place_editor_field :problem, :full_name, {}, :rows=>1
44 + %td
45 + = problem.full_name #in_place_editor_field :problem, :full_name, {}, :rows=>1
46 + = link_to_description_if_any "[#{t 'main.problem_desc'}] <span class='glyphicon glyphicon-file'></span>".html_safe, problem
41 47 %td.text-right= problem.full_score #in_place_editor_field :problem, :full_score, {}, :rows=>1
48 + %td
49 + - problem.tags.each do |t|
50 + - #%button.btn.btn-default.btn-xs= t.name
51 + %span.label.label-default= t.name
52 + %td= link_to "Submit", direct_edit_problem_submissions_path(problem,@current_user.id), class: 'btn btn-xs btn-primary'
42 53 %td= problem.date_added
43 54 %td= toggle_button(@problem.available?, toggle_problem_path(@problem), "problem-avail-#{@problem.id}")
44 55 %td= toggle_button(@problem.view_testcase?, toggle_view_testcase_problem_path(@problem), "problem-view-testcase-#{@problem.id}")
45 56 %td= toggle_button(@problem.test_allowed?, toggle_test_problem_path(@problem), "problem-test-#{@problem.id}")
46 57 - if GraderConfiguration.multicontests?
47 58 %td
48 59 = problem.contests.collect { |c| c.name }.join(', ')
49 60 %td= link_to 'Stat', {:action => 'stat', :id => problem.id}, class: 'btn btn-info btn-xs btn-block'
50 61 %td= link_to 'Show', {:action => 'show', :id => problem}, class: 'btn btn-info btn-xs btn-block'
51 62 %td= link_to 'Edit', {:action => 'edit', :id => problem}, class: 'btn btn-info btn-xs btn-block'
52 63 %td= link_to 'Destroy', { :action => 'destroy', :id => problem }, :confirm => 'Are you sure?', :method => :delete, class: 'btn btn-danger btn-xs btn-block'
53 64 %br/
54 65 = link_to '[New problem]', :action => 'new'
@@ -1,85 +1,118
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 - %p= link_to '[Back to problem list]', :action => 'list'
39 + %p= link_to '[Back to problem list]', problems_path
40 40
41 41 = form_tag :action=>'do_manage' do
42 - .submitbox
42 + .panel.panel-primary
43 + .panel-heading
44 + Action
45 + .panel-body
46 + .submit-box
43 47 What do you want to do to the selected problem?
44 48 %br/
45 49 (You can shift-click to select a range of problems)
46 - %ul
50 + %ul.form-inline
47 51 %li
48 - Change date added to
49 - = select_date Date.current, :prefix => 'date_added'
52 + Change "Date added" to
53 + .input-group.date
54 + = text_field_tag :date_added, class: 'form-control'
55 + %span.input-group-addon
56 + %span.glyphicon.glyphicon-calendar
57 + -# = select_date Date.current, :prefix => 'date_added'
50 58 &nbsp;&nbsp;&nbsp;
51 - = submit_tag 'Change', :name => 'change_date_added'
59 + = submit_tag 'Change', :name => 'change_date_added', class: 'btn btn-primary btn-sm'
52 60 %li
53 - Set available to
54 - = submit_tag 'True', :name => 'enable_problem'
55 - = submit_tag 'False', :name => 'disable_problem'
61 + Set "Available" to
62 + = submit_tag 'True', :name => 'enable_problem', class: 'btn btn-primary btn-sm'
63 + = submit_tag 'False', :name => 'disable_problem', class: 'btn btn-primary btn-sm'
56 64
57 65 - if GraderConfiguration.multicontests?
58 66 %li
59 - Add to
67 + Add selected problems to contest
60 68 = select("contest","id",Contest.all.collect {|c| [c.title, c.id]})
61 - = submit_tag 'Add', :name => 'add_to_contest'
69 + = submit_tag 'Add', :name => 'add_to_contest', class: 'btn btn-primary btn-sm'
70 + %li
71 + Add selected problems to user group
72 + = select_tag "group_id", options_from_collection_for_select( Group.all, 'id','name',params[:group_name]), id: 'group_name',class: 'select2'
73 + = submit_tag 'Add', name: 'add_group', class: 'btn btn-primary'
74 + %li
75 + Add the following tags to the selected problems
76 + = select_tag "tag_ids", options_from_collection_for_select( Tag.all, 'id','name'), id: 'tags_name',class: 'select2', multiple: true, data: {placeholder: 'Select tags by clicking', width: "200px"}
77 + = submit_tag 'Add', name: 'add_tags', class: 'btn btn-primary'
62 78
63 - %table
79 + %table.table.table-hover.datatable
80 + %thead
64 81 %tr{style: "text-align: left;"}
65 82 %th= check_box_tag 'select_all'
66 83 %th Name
67 84 %th Full name
85 + %th Tags
68 86 %th Available
69 87 %th Date added
70 88 - if GraderConfiguration.multicontests?
71 89 %th Contests
72 90
91 + %tbody
73 92 - num = 0
74 93 - for problem in @problems
75 94 - num += 1
76 95 %tr{:id => "row-prob-#{problem.id}", :name=> "prob-#{problem.id}"}
77 96 %td= check_box_tag "prob-#{problem.id}-#{num}"
78 97 %td= problem.name
79 98 %td= problem.full_name
99 + %td
100 + - problem.tags.each do |t|
101 + %span.label.label-default= t.name
80 102 %td= problem.available
81 103 %td= problem.date_added
82 104 - if GraderConfiguration.multicontests?
83 105 %td
84 106 - problem.contests.each do |contest|
85 107 = "(#{contest.name} [#{link_to 'x', :action => 'remove_contest', :id => problem.id, :contest_id => contest.id }])"
108 +
109 + :javascript
110 + $('.input-group.date').datetimepicker({
111 + format: 'DD/MMM/YYYY',
112 + showTodayButton: true,
113 + widgetPositioning: {horizontal: 'auto', vertical: 'bottom'},
114 +
115 + });
116 + $('.datatable').DataTable({
117 + paging: false
118 + });
@@ -1,53 +1,59
1 1 :css
2 2 .fix-width {
3 3 font-family: "Consolas, Monaco, Droid Sans Mono,Mono, Monospace,Courier"
4 4 }
5 5
6 6 %h1 Problem stat: #{@problem.name}
7 7 %h2 Overview
8 8
9 9
10 10 %table.info
11 11 %thead
12 12 %tr.info-head
13 13 %th Stat
14 14 %th Value
15 15 %tbody
16 16 %tr{class: cycle('info-even','info-odd')}
17 17 %td Submissions
18 18 %td= @submissions.count
19 19 %tr{class: cycle('info-even','info-odd')}
20 20 %td Solved/Attempted User
21 21 %td #{@summary[:solve]}/#{@summary[:attempt]} (#{(@summary[:solve]*100.0/@summary[:attempt]).round(1)}%)
22 22
23 23 %h2 Submissions Count
24 24 = render partial: 'application/bar_graph', locals: { histogram: @histogram }
25 25
26 26 %h2 Submissions
27 27 - if @submissions and @submissions.count > 0
28 - %table.info#main_table
28 + %table#main_table.table.table-condensed.table-striped
29 29 %thead
30 - %tr.info-head
30 + %tr
31 31 %th ID
32 32 %th Login
33 33 %th Name
34 34 %th Submitted_at
35 + %th language
35 36 %th Points
36 37 %th comment
37 38 %th IP
38 39 %tbody
39 40 - row_odd,curr = true,''
40 41 - @submissions.each do |sub|
41 42 - next unless sub.user
42 43 - row_odd,curr = !row_odd, sub.user if curr != sub.user
43 - %tr{class: row_odd ? "info-odd" : "info-even"}
44 + %tr
44 45 %td= link_to sub.id, submission_path(sub)
45 46 %td= link_to sub.user.login, stat_user_path(sub.user)
46 47 %td= sub.user.full_name
47 - %td= time_ago_in_words(sub.submitted_at) + " ago"
48 + %td{data: {order: sub.submitted_at}}= time_ago_in_words(sub.submitted_at) + " ago"
49 + %td= sub.language.name
48 50 %td= sub.points
49 51 %td.fix-width= sub.grader_comment
50 52 %td= sub.ip_address
51 53 - else
52 54 No submission
53 55
56 + :javascript
57 + $("#main_table").DataTable({
58 + paging: false
59 + });
@@ -1,34 +1,69
1 1 %table.table.sortable.table-striped.table-bordered.table-condensed
2 2 %thead
3 3 %tr
4 4 %th Login
5 5 %th Name
6 6 / %th Activated?
7 7 / %th Logged_in
8 8 / %th Contest(s)
9 9 %th Remark
10 10 - @problems.each do |p|
11 11 %th.text-right= p.name.gsub('_',' ')
12 12 %th.text-right Total
13 13 %th.text-right Passed
14 14 %tbody
15 + - sum = Array.new(@scorearray[0].count,0)
16 + - nonzero = Array.new(@scorearray[0].count,0)
17 + - full = Array.new(@scorearray[0].count,0)
15 18 - @scorearray.each do |sc|
16 19 %tr
17 20 - total,num_passed = 0,0
18 21 - sc.each_index do |i|
19 22 - if i == 0
20 23 %td= link_to sc[i].login, stat_user_path(sc[i])
21 24 %td= sc[i].full_name
22 25 / %td= sc[i].activated
23 26 / %td= sc[i].try(:contest_stat).try(:started_at) ? 'yes' : 'no'
24 27 / %td= sc[i].contests.collect {|c| c.name}.join(', ')
25 28 %td= sc[i].remark
26 29 - else
27 30 %td.text-right= sc[i][0]
28 31 - total += sc[i][0]
29 32 - num_passed += 1 if sc[i][1]
33 + - sum[i] += sc[i][0]
34 + - nonzero[i] += 1 if sc[i][0] > 0
35 + - full[i] += 1 if sc[i][1]
30 36 %td.text-right= total
31 37 %td.text-right= num_passed
38 + %tfoot
39 + %tr
40 + %td Summation
41 + %td
42 + %td
43 + - sum.each.with_index do |s,i|
44 + - next if i == 0
45 + %td.text-right= number_with_delimiter(s)
46 + %td
47 + %td
48 + %tr
49 + %td partial solver
50 + %td
51 + %td
52 + - nonzero.each.with_index do |s,i|
53 + - next if i == 0
54 + %td.text-right= number_with_delimiter(s)
55 + %td
56 + %td
57 + %tr
58 + %td Full solver
59 + %td
60 + %td
61 + - full.each.with_index do |s,i|
62 + - next if i == 0
63 + %td.text-right= number_with_delimiter(s)
64 + %td
65 + %td
66 +
32 67
33 68 :javascript
34 69 $.bootstrapSortable(true,'reversed')
@@ -1,49 +1,49
1 1 %h1 Maximum score
2 2
3 3 = form_tag report_show_max_score_path
4 4 .row
5 5 .col-md-4
6 6 .panel.panel-primary
7 7 .panel-heading
8 8 Problems
9 9 .panel-body
10 10 %p
11 11 Select problem(s) that we wish to know the score.
12 12 = label_tag :problem_id, "Problems"
13 13 = select_tag 'problem_id[]',
14 14 options_for_select(Problem.all.collect {|p| ["[#{p.name}] #{p.full_name}", p.id]},params[:problem_id]),
15 15 { class: 'select2 form-control', multiple: "true" }
16 16 .col-md-4
17 17 .panel.panel-primary
18 18 .panel-heading
19 19 Submission range
20 20 .panel-body
21 21 %p
22 22 Input minimum and maximum range of submission ID that should be included. A blank value for min and max means -1 and infinity, respectively.
23 23 .form-group
24 24 = label_tag :from, "Min"
25 25 = text_field_tag 'from_id', @since_id, class: "form-control"
26 26 .form-group
27 27 = label_tag :from, "Max"
28 28 = text_field_tag 'to_id', @until_id, class: "form-control"
29 29 .col-md-4
30 30 .panel.panel-primary
31 31 .panel-heading
32 32 Users
33 33 .panel-body
34 34 .radio
35 35 %label
36 - = radio_button_tag 'users', 'all', true
36 + = radio_button_tag 'users', 'all', (params[:users] == "all")
37 37 All users
38 38 .radio
39 39 %label
40 - = radio_button_tag 'users', 'enabled'
40 + = radio_button_tag 'users', 'enabled', (params[:users] == "enabled")
41 41 Only enabled users
42 42 .row
43 43 .col-md-12
44 44 = button_tag 'Show', class: "btn btn-primary btn-large", value: "show"
45 45 = button_tag 'Download CSV', class: "btn btn-primary btn-large", value: "download"
46 46
47 47 - if @scorearray
48 48 %h2 Result
49 49 =render "score_table"
@@ -1,24 +1,36
1 1 %h1 Editing site
2 2 = error_messages_for :site
3 3 = form_for(@site) do |f|
4 - %p
5 - %b Name
6 - %br/
7 - = f.text_field :name
8 - %p
9 - %b Password
10 - %br/
11 - = f.text_field :password
12 - %p
13 - %b Started
14 - %br/
15 - = f.check_box :started
16 - %p
17 - %b Start time
18 - %br/
19 - = f.datetime_select :start_time, :include_blank => true
20 - %p
21 - = f.submit "Update"
4 + .row
5 + .col-md-4
6 + .form-group.field
7 + = f.label :name, "Name"
8 + = f.text_field :name, class: 'form-control'
9 + .form-group.field
10 + = f.label :password, "Password"
11 + = f.text_field :password, class: 'form-control'
12 + .form-group.field
13 + = f.label :started, "Started"
14 + = f.check_box :started, class: 'form-control'
15 + .form-group.field
16 + = f.label :start_time, "Start time"
17 + -# = f.datetime_select :start_time, :include_blank => true
18 + .input-group.date
19 + = f.text_field :start_time, class:'form-control' , value: (@site.start_time ? @site.start_time.strftime('%d/%b/%Y %H:%M') : '')
20 + %span.input-group-addon
21 + %span.glyphicon.glyphicon-calendar
22 + .actions
23 + = f.submit "Update", class: 'btn btn-primary'
24 + .col-md-8
25 +
22 26 = link_to 'Show', @site
23 27 |
24 28 = link_to 'Back', sites_path
29 +
30 +
31 + :javascript
32 + $('.input-group.date').datetimepicker({
33 + format: 'DD/MMM/YYYY HH:mm',
34 + showTodayButton: true,
35 + });
36 +
@@ -1,128 +1,131
1 1 %h2 Live submit
2 2 %br
3 3
4 4 %textarea#text_sourcecode{style: "display:none"}~ @source
5 5 .container
6 6 .row
7 7 .col-md-12
8 8 .alert.alert-info
9 9 Write your code in the following box, choose language, and click submit button when finished
10 10 .row
11 11 .col-md-8
12 12 %div#editor{style: 'height: 500px; border-radius: 7px; font-size: 14px;'}
13 13 .col-md-4
14 + - # submission form
14 15 = form_tag({controller: :main, :action => 'submit'}, :multipart => true, class: 'form') do
15 16
16 17 = hidden_field_tag 'editor_text', @source
17 18 = hidden_field_tag 'submission[problem_id]', @problem.id
18 19 .form-group
19 20 = label_tag "Task:"
20 21 = text_field_tag 'asdf', "#{@problem.long_name}", class: 'form-control', disabled: true
21 22
22 23 .form-group
23 24 = label_tag 'Language'
24 25 = select_tag 'language_id', options_from_collection_for_select(Language.all, 'id', 'pretty_name', @lang_id || Language.find_by_pretty_name("Python").id || Language.first.id), class: 'form-control select', style: "width: 100px"
25 26 .form-group
26 27 = submit_tag 'Submit', class: 'btn btn-success', id: 'live_submit',
27 28 data: {confirm: "Submitting this source code for task #{@problem.long_name}?"}
28 - .panel.panel-info
29 + - # latest submission status
30 + .panel{class: (@submission && @submission.graded_at) ? "panel-info" : "panel-warning"}
29 31 .panel-heading
30 32 Latest Submission Status
31 33 = link_to "Refresh",get_latest_submission_status_submissions_path(@submission.user,@problem), class: "btn btn-default btn-sm", remote: true if @submission
32 34 .panel-body
35 + %div#latest_status
33 36 - if @submission
34 37 = render :partial => 'submission_short',
35 38 :locals => {submission: @submission, problem_name: @problem.name, problem_id: @problem.id }
36 39 .row
37 40 .col-md-12
38 41 %h2 Console
39 42 %textarea#console{style: 'height: 100%; width: 100%;background-color:#000;color:#fff;font-family: consolas, monaco, "Droid Sans Mono";',rows: 20}
40 43
41 44 :javascript
42 45 $(document).ready(function() {
43 46 e = ace.edit("editor")
44 47 e.setValue($("#text_sourcecode").val());
45 48 e.gotoLine(1);
46 49 $("#language_id").trigger('change');
47 50 brython();
48 51 });
49 52
50 53
51 54 %script#__main__{type:'text/python3'}
52 55 :plain
53 56 import sys
54 57 import traceback
55 58
56 59 from browser import document as doc
57 60 from browser import window, alert, console
58 61
59 62 _credits = """ Thanks to CWI, CNRI, BeOpen.com, Zope Corporation and a cast of thousands
60 63 for supporting Python development. See www.python.org for more information."""
61 64
62 65 _copyright = """Copyright (c) 2012, Pierre Quentel pierre.quentel@gmail.com
63 66 All Rights Reserved.
64 67
65 68 Copyright (c) 2001-2013 Python Software Foundation.
66 69 All Rights Reserved.
67 70
68 71 Copyright (c) 2000 BeOpen.com.
69 72 All Rights Reserved.
70 73
71 74 Copyright (c) 1995-2001 Corporation for National Research Initiatives.
72 75 All Rights Reserved.
73 76
74 77 Copyright (c) 1991-1995 Stichting Mathematisch Centrum, Amsterdam.
75 78 All Rights Reserved."""
76 79
77 80 _license = """Copyright (c) 2012, Pierre Quentel pierre.quentel@gmail.com
78 81 All rights reserved.
79 82
80 83 Redistribution and use in source and binary forms, with or without
81 84 modification, are permitted provided that the following conditions are met:
82 85
83 86 Redistributions of source code must retain the above copyright notice, this
84 87 list of conditions and the following disclaimer. Redistributions in binary
85 88 form must reproduce the above copyright notice, this list of conditions and
86 89 the following disclaimer in the documentation and/or other materials provided
87 90 with the distribution.
88 91 Neither the name of the <ORGANIZATION> nor the names of its contributors may
89 92 be used to endorse or promote products derived from this software without
90 93 specific prior written permission.
91 94
92 95 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
93 96 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
94 97 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
95 98 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
96 99 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
97 100 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
98 101 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
99 102 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
100 103 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
101 104 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
102 105 POSSIBILITY OF SUCH DAMAGE.
103 106 """
104 107
105 108 def credits():
106 109 print(_credits)
107 110 credits.__repr__ = lambda:_credits
108 111
109 112 def copyright():
110 113 print(_copyright)
111 114 copyright.__repr__ = lambda:_copyright
112 115
113 116 def license():
114 117 print(_license)
115 118 license.__repr__ = lambda:_license
116 119
117 120 def write(data):
118 121 doc['console'].value += str(data)
119 122
120 123
121 124 sys.stdout.write = sys.stderr.write = write
122 125 history = []
123 126 current = 0
124 127 _status = "main" # or "block" if typing inside a block
125 128
126 129 # execution namespace
127 130 editor_ns = {'credits':credits,
128 131 'copyright':copyright,
@@ -1,2 +1,2
1 1 :plain
2 - $("#latest_status").html("#{j render({partial: 'submission_short', locals: {submission: @submission, problem_name: @problem.name}})}")
2 + $("#latest_status").html("#{j render({partial: 'submission_short', locals: {submission: @submission, problem_name: @problem.name, problem_id: @problem.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,101 +1,106
1 - %h1 Listing users
1 + %h1 Users
2 2
3 3 .panel.panel-primary
4 4 .panel-title.panel-heading
5 5 Quick Add
6 6 .panel-body
7 7 = form_tag( {method: 'post'}, {class: 'form-inline'}) do
8 8 .form-group
9 9 = label_tag 'user_login', 'Login'
10 10 = text_field 'user', 'login', :size => 10,class: 'form-control'
11 11 .form-group
12 12 = label_tag 'user_full_name', 'Full Name'
13 13 = text_field 'user', 'full_name', :size => 10,class: 'form-control'
14 14 .form-group
15 15 = label_tag 'user_password', 'Password'
16 16 = text_field 'user', 'password', :size => 10,class: 'form-control'
17 17 .form-group
18 18 = label_tag 'user_password_confirmation', 'Confirm'
19 19 = text_field 'user', 'password_confirmation', :size => 10,class: 'form-control'
20 20 .form-group
21 21 = label_tag 'user_email', 'email'
22 22 = text_field 'user', 'email', :size => 10,class: 'form-control'
23 23 =submit_tag "Create", class: 'btn btn-primary'
24 24
25 25 .panel.panel-primary
26 26 .panel-title.panel-heading
27 27 Import from site management
28 28 .panel-body
29 29 = form_tag({:action => 'import'}, :multipart => true,class: 'form form-inline') do
30 30 .form-group
31 31 = label_tag :file, 'File:'
32 32 .input-group
33 33 %span.input-group-btn
34 34 %span.btn.btn-default.btn-file
35 35 Browse
36 36 = file_field_tag 'file'
37 37 = text_field_tag '' , nil, {readonly: true, class: 'form-control'}
38 38 = submit_tag 'Submit', class: 'btn btn-default'
39 39
40 40
41 41 %p
42 42 = link_to '+ New user', { :action => 'new' }, { class: 'btn btn-success '}
43 43 = link_to '+ New list of users', { :action => 'new_list' }, { class: 'btn btn-success '}
44 + = link_to 'Bulk Manage', bulk_manage_user_admin_path , { class: 'btn btn-default btn-info'}
44 45 = link_to 'View administrators',{ :action => 'admin'}, { class: 'btn btn-default '}
45 - = link_to 'Bulk Manage', bulk_manage_user_admin_path , { class: 'btn btn-default '}
46 46 = link_to 'Random passwords',{ :action => 'random_all_passwords'}, { class: 'btn btn-default '}
47 47 = link_to 'View active users',{ :action => 'active'}, { class: 'btn btn-default '}
48 48 = link_to 'Mass mailing',{ :action => 'mass_mailing'}, { class: 'btn btn-default '}
49 49
50 50 - if GraderConfiguration.multicontests?
51 51 %br/
52 52 %b Multi-contest:
53 53 = link_to '[Manage bulk users in contests]', :action => 'contest_management'
54 54 View users in:
55 55 - @contests.each do |contest|
56 56 = link_to "[#{contest.name}]", :action => 'contests', :id => contest.id
57 57 = link_to "[no contest]", :action => 'contests', :id => 'none'
58 58
59 - Total #{@user_count} users |
60 - - if !@paginated
61 - Display all users.
62 - \#{link_to '[show in pages]', :action => 'index', :page => '1'}
63 - - else
64 - Display in pages.
65 - \#{link_to '[display all]', :action => 'index', :page => 'all'} |
66 - \#{will_paginate @users, :container => false}
59 + -# Total #{@user_count} users |
60 + -# - if !@paginated
61 + -# Display all users.
62 + -# \#{link_to '[show in pages]', :action => 'index', :page => '1'}
63 + -# - else
64 + -# Display in pages.
65 + -# \#{link_to '[display all]', :action => 'index', :page => 'all'} |
66 + -# \#{will_paginate @users, :container => false}
67 67
68 68
69 - %table.table.table-hover.table-condense
69 + %table.table.table-hover.table-condense.datatable
70 70 %thead
71 71 %th Login
72 72 %th Full name
73 73 %th email
74 74 %th Remark
75 75 %th
76 76 Activated
77 77 %sup{class: 'text-primary',data: {toggle: 'tooltip', placement: 'top'}, title: 'User has already confirmed the email?' } [?]
78 78 %th
79 79 Enabled
80 80 %sup{class: 'text-primary',data: {toggle: 'tooltip', placement: 'top'}, title: 'Allow the user to login?' } [?]
81 81 %th Last IP
82 82 %th
83 83 %th
84 84 %th
85 85 %th
86 86 - for user in @users
87 87 %tr
88 88 %td= link_to user.login, stat_user_path(user)
89 89 %td= user.full_name
90 90 %td= user.email
91 91 %td= user.remark
92 92 %td= toggle_button(user.activated?, toggle_activate_user_path(user),"toggle_activate_user_#{user.id}")
93 93 %td= toggle_button(user.enabled?, toggle_enable_user_path(user),"toggle_enable_user_#{user.id}")
94 94 %td= user.last_ip
95 95 %td= link_to 'Clear IP', {:action => 'clear_last_ip', :id => user, :page=>params[:page]}, :confirm => 'This will reset last logging in ip of the user, are you sure?', class: 'btn btn-default btn-xs btn-block'
96 96 %td= link_to 'Show', {:action => 'show', :id => user}, class: 'btn btn-default btn-xs btn-block'
97 97 %td= link_to 'Edit', {:action => 'edit', :id => user}, class: 'btn btn-default btn-xs btn-block'
98 - %td= link_to 'Destroy', { :action => 'destroy', :id => user }, :confirm => 'Are you sure?', :method => :delete, class: 'btn btn-danger btn-xs btn-block'
98 + %td= link_to 'Destroy', user_admin_destroy_path(user), data: {confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-danger btn-xs btn-block'
99 99 %br/
100 100 = link_to '+ New user', { :action => 'new' }, { class: 'btn btn-success '}
101 101 = link_to '+ New list of users', { :action => 'new_list' }, { class: 'btn btn-success '}
102 +
103 + :javascript
104 + $('.datatable').DataTable({
105 + 'pageLength': 50
106 + });
@@ -1,66 +1,70
1 1 - content_for :header do
2 2 = javascript_include_tag 'local_jquery'
3 3
4 4 :javascript
5 5 $(function () {
6 6 $('#submission_table').tablesorter({widgets: ['zebra']});
7 7 });
8 8
9 9 :css
10 10 .fix-width {
11 11 font-family: Droid Sans Mono,Consolas, monospace, mono, Courier New, Courier;
12 12 }
13 13
14 14 %h1= @user.full_name
15 15
16 16 <b>Login:</b> #{@user.login} <br/>
17 17 <b>Full name:</b> #{@user.full_name} <br />
18 18
19 19
20 20 %h2 Problem Stat
21 21 %table.info
22 22 %thead
23 23 %tr.info-head
24 24 %th Stat
25 25 %th Value
26 26 %tbody
27 27 %tr{class: cycle('info-even','info-odd')}
28 28 %td.info_param Submissions
29 29 %td= @summary[:count]
30 30 %tr{class: cycle('info-even','info-odd')}
31 31 %td.info_param Solved/Attempted Problem
32 32 %td #{@summary[:solve]}/#{@summary[:attempt]} (#{(@summary[:solve]*100.0/@summary[:attempt]).round(1)}%)
33 33
34 34 %h2 Submission History
35 35
36 36 =render partial: 'application/bar_graph', locals: {histogram: @histogram, param: {bar_width: 7}}
37 37
38 38
39 - %table.tablesorter-cafe#submission_table
39 + %table#submission_table.table.table-striped
40 40 %thead
41 41 %tr
42 42 %th ID
43 43 %th Problem code
44 44 %th Problem full name
45 45 %th Language
46 46 %th Submitted at
47 47 %th Result
48 48 %th Score
49 49 - if session[:admin]
50 50 %th IP
51 51 %tbody
52 52 - @submission.each do |s|
53 53 - next unless s.problem
54 54 %tr
55 55 %td= link_to s.id, submission_path(s)
56 56 %td= link_to s.problem.name, stat_problem_path(s.problem)
57 57 %td= s.problem.full_name
58 58 %td= s.language.pretty_name
59 59 %td #{s.submitted_at.strftime('%Y-%m-%d %H:%M')} (#{time_ago_in_words(s.submitted_at)} ago)
60 60 %td.fix-width= s.grader_comment
61 61 %td= ( s.try(:points) ? (s.points*100/s.problem.full_score) : '' )
62 62 - if session[:admin]
63 63 %td= s.ip_address
64 64
65 65
66 66
67 + :javascript
68 + $("#submission_table").DataTable({
69 + paging: false
70 + });
@@ -1,72 +1,72
1 1 require File.expand_path('../boot', __FILE__)
2 2
3 3 require 'rails/all'
4 4
5 5 if defined?(Bundler)
6 6 # If you precompile assets before deploying to production, use this line
7 7 Bundler.require(*Rails.groups(:assets => %w(development test)))
8 8 # If you want your assets lazily compiled in production, use this line
9 9 # Bundler.require(:default, :assets, Rails.env)
10 10 end
11 11
12 12 module CafeGrader
13 13 class Application < Rails::Application
14 14 # Settings in config/environments/* take precedence over those specified here.
15 15 # Application configuration should go into files in config/initializers
16 16 # -- all .rb files in that directory are automatically loaded.
17 17
18 18 # Custom directories with classes and modules you want to be autoloadable.
19 19 config.autoload_paths += %W(#{config.root}/lib)
20 20
21 21 # Only load the plugins named here, in the order given (default is alphabetical).
22 22 # :all can be used as a placeholder for all plugins not explicitly named.
23 23 # config.plugins = [ :exception_notification, :ssl_requirement, :all ]
24 24
25 25 # Activate observers that should always be running.
26 26 # config.active_record.observers = :cacher, :garbage_collector, :forum_observer
27 27
28 28 # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
29 29 # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
30 30 config.time_zone = 'UTC'
31 31
32 32 # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
33 33 # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
34 34 config.i18n.default_locale = :en
35 35
36 36 # Configure the default encoding used in templates for Ruby 1.9.
37 37 config.encoding = "utf-8"
38 38
39 39 # Configure sensitive parameters which will be filtered from the log file.
40 40 config.filter_parameters += [:password]
41 41
42 42 # Enable escaping HTML in JSON.
43 43 config.active_support.escape_html_entities_in_json = true
44 44
45 45 # Use SQL instead of Active Record's schema dumper when creating the database.
46 46 # This is necessary if your schema can't be completely dumped by the schema dumper,
47 47 # like if you have constraints or database-specific column types
48 48 # config.active_record.schema_format = :sql
49 49
50 50 # Enable the asset pipeline
51 51 config.assets.enabled = true
52 52
53 53 # Version of your assets, change this if you want to expire all your assets
54 54 config.assets.version = '1.0'
55 55
56 56 # ---------------- IMPORTANT ----------------------
57 57 # If we deploy the app into a subdir name "grader", be sure to do "rake assets:precompile RAILS_RELATIVE_URL_ROOT=/grader"
58 58 # moreover, using the following line instead also known to works
59 59 #config.action_controller.relative_url_root = '/grader'
60 60
61 61 #font path
62 62 config.assets.paths << "#{Rails}/vendor/assets/fonts"
63 63
64 64 config.assets.precompile += ['announcement_refresh.js','effects.js','site_update.js']
65 65 config.assets.precompile += ['local_jquery.js','tablesorter-theme.cafe.css']
66 66 %w( announcements submissions configurations contests contest_management graders heartbeat
67 67 login main messages problems report site sites sources tasks
68 - test user_admin users ).each do |controller|
68 + test user_admin users testcases).each do |controller|
69 69 config.assets.precompile += ["#{controller}.js", "#{controller}.css"]
70 70 end
71 71 end
72 72 end
@@ -1,98 +1,114
1 1 CafeGrader::Application.routes.draw do
2 + resources :tags
2 3 get "sources/direct_edit"
3 4
4 5 root :to => 'main#login'
5 6
6 7 #logins
7 8 get 'login/login', to: 'login#login'
8 9
9 10 resources :contests
10 11
11 12 resources :sites
12 13
13 14 resources :announcements do
14 15 member do
15 16 get 'toggle','toggle_front'
16 17 end
17 18 end
18 19
19 20 resources :problems do
20 21 member do
21 22 get 'toggle'
22 23 get 'toggle_test'
23 24 get 'toggle_view_testcase'
24 25 get 'stat'
25 26 end
26 27 collection do
27 28 get 'turn_all_off'
28 29 get 'turn_all_on'
29 30 get 'import'
30 31 get 'manage'
31 32 end
33 + end
32 34
35 + resources :groups do
36 + member do
37 + post 'add_user', to: 'groups#add_user', as: 'add_user'
38 + delete 'remove_user/:user_id', to: 'groups#remove_user', as: 'remove_user'
39 + delete 'remove_all_user', to: 'groups#remove_all_user', as: 'remove_all_user'
40 + post 'add_problem', to: 'groups#add_problem', as: 'add_problem'
41 + delete 'remove_problem/:problem_id', to: 'groups#remove_problem', as: 'remove_problem'
42 + delete 'remove_all_problem', to: 'groups#remove_all_problem', as: 'remove_all_problem'
43 + end
44 + collection do
45 +
46 + end
33 47 end
34 48
35 49 resources :testcases, only: [] do
36 50 member do
37 51 get 'download_input'
38 52 get 'download_sol'
39 53 end
40 54 collection do
41 55 get 'show_problem/:problem_id(/:test_num)' => 'testcases#show_problem', as: 'show_problem'
42 56 end
43 57 end
44 58
45 59 resources :grader_configuration, controller: 'configurations'
46 60
47 61 resources :users do
48 62 member do
49 63 get 'toggle_activate', 'toggle_enable'
50 64 get 'stat'
51 65 end
52 66 end
53 67
54 68 resources :submissions do
55 69 member do
56 70 get 'download'
57 71 get 'compiler_msg'
58 72 get 'rejudge'
59 73 end
60 74 collection do
61 75 get 'prob/:problem_id', to: 'submissions#index', as: 'problem'
62 - get 'direct_edit_problem/:problem_id', to: 'submissions#direct_edit_problem', as: 'direct_edit_problem'
76 + get 'direct_edit_problem/:problem_id(/:user_id)', to: 'submissions#direct_edit_problem', as: 'direct_edit_problem'
63 77 get 'get_latest_submission_status/:uid/:pid', to: 'submissions#get_latest_submission_status', as: 'get_latest_submission_status'
64 78 end
65 79 end
66 80
67 81
68 82
69 83 #main
70 84 get "main/list"
71 85 get 'main/submission(/:id)', to: 'main#submission', as: 'main_submission'
72 86
73 87 #user admin
74 88 get 'user_admin/bulk_manage', to: 'user_admin#bulk_manage', as: 'bulk_manage_user_admin'
89 + post 'user_admin', to: 'user_admin#create'
90 + delete 'user_admin/:id', to: 'user_admin#destroy', as: 'user_admin_destroy'
75 91
76 92 #report
77 93 get 'report/current_score', to: 'report#current_score', as: 'report_current_score'
78 94 get 'report/problem_hof(/:id)', to: 'report#problem_hof', as: 'report_problem_hof'
79 95 get "report/login"
80 96 get 'report/max_score', to: 'report#max_score', as: 'report_max_score'
81 97 post 'report/show_max_score', to: 'report#show_max_score', as: 'report_show_max_score'
82 98
83 99
84 100 #
85 101 get 'tasks/view/:file.:ext' => 'tasks#view'
86 102 get 'tasks/download/:id/:file.:ext' => 'tasks#download'
87 103 get 'heartbeat/:id/edit' => 'heartbeat#edit'
88 104
89 105 #grader
90 106 get 'graders/list', to: 'graders#list', as: 'grader_list'
91 107
92 108
93 109 # See how all your routes lay out with "rake routes"
94 110
95 111 # This is a legacy wild controller route that's not recommended for RESTful applications.
96 112 # Note: This route will make all actions in every controller accessible via GET requests.
97 113 match ':controller(/:action(/:id))(.:format)', via: [:get, :post]
98 114 end
@@ -1,283 +1,321
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: 20170914150742) 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: 65535
19 19 t.boolean "published"
20 20 t.datetime "created_at", null: false
21 21 t.datetime "updated_at", null: false
22 22 t.boolean "frontpage", default: false
23 23 t.boolean "contest_only", default: false
24 24 t.string "title", limit: 255
25 25 t.string "notes", limit: 255
26 26 end
27 27
28 28 create_table "contests", force: :cascade do |t|
29 29 t.string "title", limit: 255
30 30 t.boolean "enabled"
31 31 t.datetime "created_at", null: false
32 32 t.datetime "updated_at", null: false
33 33 t.string "name", limit: 255
34 34 end
35 35
36 36 create_table "contests_problems", id: false, force: :cascade do |t|
37 37 t.integer "contest_id", limit: 4
38 38 t.integer "problem_id", limit: 4
39 39 end
40 40
41 41 create_table "contests_users", id: false, force: :cascade do |t|
42 42 t.integer "contest_id", limit: 4
43 43 t.integer "user_id", limit: 4
44 44 end
45 45
46 46 create_table "countries", force: :cascade do |t|
47 47 t.string "name", limit: 255
48 48 t.datetime "created_at", null: false
49 49 t.datetime "updated_at", null: false
50 50 end
51 51
52 52 create_table "descriptions", force: :cascade do |t|
53 53 t.text "body", limit: 65535
54 54 t.boolean "markdowned"
55 55 t.datetime "created_at", null: false
56 56 t.datetime "updated_at", null: false
57 57 end
58 58
59 59 create_table "grader_configurations", force: :cascade do |t|
60 60 t.string "key", limit: 255
61 61 t.string "value_type", limit: 255
62 62 t.string "value", limit: 255
63 63 t.datetime "created_at", null: false
64 64 t.datetime "updated_at", null: false
65 65 t.text "description", limit: 65535
66 66 end
67 67
68 68 create_table "grader_processes", force: :cascade do |t|
69 69 t.string "host", limit: 255
70 70 t.integer "pid", limit: 4
71 71 t.string "mode", limit: 255
72 72 t.boolean "active"
73 73 t.datetime "created_at", null: false
74 74 t.datetime "updated_at", null: false
75 75 t.integer "task_id", limit: 4
76 76 t.string "task_type", limit: 255
77 77 t.boolean "terminated"
78 78 end
79 79
80 80 add_index "grader_processes", ["host", "pid"], name: "index_grader_processes_on_ip_and_pid", using: :btree
81 81
82 + 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: 65535
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
149 + create_table "problems_tags", force: :cascade do |t|
150 + t.integer "problem_id", limit: 4
151 + t.integer "tag_id", limit: 4
152 + end
153 +
154 + add_index "problems_tags", ["problem_id", "tag_id"], name: "index_problems_tags_on_problem_id_and_tag_id", unique: true, using: :btree
155 + add_index "problems_tags", ["problem_id"], name: "index_problems_tags_on_problem_id", using: :btree
156 + add_index "problems_tags", ["tag_id"], name: "index_problems_tags_on_tag_id", using: :btree
157 +
130 158 create_table "rights", force: :cascade do |t|
131 159 t.string "name", limit: 255
132 160 t.string "controller", limit: 255
133 161 t.string "action", limit: 255
134 162 end
135 163
136 164 create_table "rights_roles", id: false, force: :cascade do |t|
137 165 t.integer "right_id", limit: 4
138 166 t.integer "role_id", limit: 4
139 167 end
140 168
141 169 add_index "rights_roles", ["role_id"], name: "index_rights_roles_on_role_id", using: :btree
142 170
143 171 create_table "roles", force: :cascade do |t|
144 172 t.string "name", limit: 255
145 173 end
146 174
147 175 create_table "roles_users", id: false, force: :cascade do |t|
148 176 t.integer "role_id", limit: 4
149 177 t.integer "user_id", limit: 4
150 178 end
151 179
152 180 add_index "roles_users", ["user_id"], name: "index_roles_users_on_user_id", using: :btree
153 181
154 182 create_table "sessions", force: :cascade do |t|
155 183 t.string "session_id", limit: 255
156 184 t.text "data", limit: 65535
157 185 t.datetime "updated_at"
158 186 end
159 187
160 188 add_index "sessions", ["session_id"], name: "index_sessions_on_session_id", using: :btree
161 189 add_index "sessions", ["updated_at"], name: "index_sessions_on_updated_at", using: :btree
162 190
163 191 create_table "sites", force: :cascade do |t|
164 192 t.string "name", limit: 255
165 193 t.boolean "started"
166 194 t.datetime "start_time"
167 195 t.datetime "created_at", null: false
168 196 t.datetime "updated_at", null: false
169 197 t.integer "country_id", limit: 4
170 198 t.string "password", limit: 255
171 199 end
172 200
173 201 create_table "submission_view_logs", force: :cascade do |t|
174 202 t.integer "user_id", limit: 4
175 203 t.integer "submission_id", limit: 4
176 204 t.datetime "created_at", null: false
177 205 t.datetime "updated_at", null: false
178 206 end
179 207
180 208 create_table "submissions", force: :cascade do |t|
181 209 t.integer "user_id", limit: 4
182 210 t.integer "problem_id", limit: 4
183 211 t.integer "language_id", limit: 4
184 212 t.text "source", limit: 65535
185 213 t.binary "binary", limit: 65535
186 214 t.datetime "submitted_at"
187 215 t.datetime "compiled_at"
188 216 t.text "compiler_message", limit: 65535
189 217 t.datetime "graded_at"
190 218 t.integer "points", limit: 4
191 219 t.text "grader_comment", limit: 65535
192 220 t.integer "number", limit: 4
193 221 t.string "source_filename", limit: 255
194 222 t.float "max_runtime", limit: 24
195 223 t.integer "peak_memory", limit: 4
196 224 t.integer "effective_code_length", limit: 4
197 225 t.string "ip_address", limit: 255
198 226 end
199 227
200 228 add_index "submissions", ["user_id", "problem_id", "number"], name: "index_submissions_on_user_id_and_problem_id_and_number", unique: true, using: :btree
201 229 add_index "submissions", ["user_id", "problem_id"], name: "index_submissions_on_user_id_and_problem_id", using: :btree
202 230
231 + create_table "tags", force: :cascade do |t|
232 + t.string "name", limit: 255, null: false
233 + t.text "description", limit: 65535
234 + t.boolean "public"
235 + t.datetime "created_at", null: false
236 + t.datetime "updated_at", null: false
237 + end
238 +
203 239 create_table "tasks", force: :cascade do |t|
204 240 t.integer "submission_id", limit: 4
205 241 t.datetime "created_at"
206 242 t.integer "status", limit: 4
207 243 t.datetime "updated_at"
208 244 end
209 245
210 246 add_index "tasks", ["submission_id"], name: "index_tasks_on_submission_id", using: :btree
211 247
212 248 create_table "test_pairs", force: :cascade do |t|
213 249 t.integer "problem_id", limit: 4
214 250 t.text "input", limit: 16777215
215 251 t.text "solution", limit: 16777215
216 252 t.datetime "created_at", null: false
217 253 t.datetime "updated_at", null: false
218 254 end
219 255
220 256 create_table "test_requests", force: :cascade do |t|
221 257 t.integer "user_id", limit: 4
222 258 t.integer "problem_id", limit: 4
223 259 t.integer "submission_id", limit: 4
224 260 t.string "input_file_name", limit: 255
225 261 t.string "output_file_name", limit: 255
226 262 t.string "running_stat", limit: 255
227 263 t.integer "status", limit: 4
228 264 t.datetime "updated_at", null: false
229 265 t.datetime "submitted_at"
230 266 t.datetime "compiled_at"
231 267 t.text "compiler_message", limit: 65535
232 268 t.datetime "graded_at"
233 269 t.string "grader_comment", limit: 255
234 270 t.datetime "created_at", null: false
235 271 t.float "running_time", limit: 24
236 272 t.string "exit_status", limit: 255
237 273 t.integer "memory_usage", limit: 4
238 274 end
239 275
240 276 add_index "test_requests", ["user_id", "problem_id"], name: "index_test_requests_on_user_id_and_problem_id", using: :btree
241 277
242 278 create_table "testcases", force: :cascade do |t|
243 279 t.integer "problem_id", limit: 4
244 280 t.integer "num", limit: 4
245 281 t.integer "group", limit: 4
246 282 t.integer "score", limit: 4
247 283 t.text "input", limit: 4294967295
248 284 t.text "sol", limit: 4294967295
249 285 t.datetime "created_at"
250 286 t.datetime "updated_at"
251 287 end
252 288
253 289 add_index "testcases", ["problem_id"], name: "index_testcases_on_problem_id", using: :btree
254 290
255 291 create_table "user_contest_stats", force: :cascade do |t|
256 292 t.integer "user_id", limit: 4
257 293 t.datetime "started_at"
258 294 t.datetime "created_at", null: false
259 295 t.datetime "updated_at", null: false
260 296 t.boolean "forced_logout"
261 297 end
262 298
263 299 create_table "users", force: :cascade do |t|
264 300 t.string "login", limit: 50
265 301 t.string "full_name", limit: 255
266 302 t.string "hashed_password", limit: 255
267 303 t.string "salt", limit: 5
268 304 t.string "alias", limit: 255
269 305 t.string "email", limit: 255
270 306 t.integer "site_id", limit: 4
271 307 t.integer "country_id", limit: 4
272 308 t.boolean "activated", default: false
273 309 t.datetime "created_at"
274 310 t.datetime "updated_at"
275 311 t.boolean "enabled", default: true
276 312 t.string "remark", limit: 255
277 313 t.string "last_ip", limit: 255
278 314 t.string "section", limit: 255
279 315 end
280 316
281 317 add_index "users", ["login"], name: "index_users_on_login", unique: true, using: :btree
282 318
319 + add_foreign_key "problems_tags", "problems"
320 + add_foreign_key "problems_tags", "tags"
283 321 end
@@ -70,189 +70,198
70 70
71 71 {
72 72 :key => 'right.user_view_submission',
73 73 :value_type => 'boolean',
74 74 :default_value => 'false',
75 75 :description => 'If true, any user can view submissions of every one.'
76 76 },
77 77
78 78 {
79 79 :key => 'right.bypass_agreement',
80 80 :value_type => 'boolean',
81 81 :default_value => 'true',
82 82 :description => 'When false, a user must accept usage agreement before login'
83 83 },
84 84
85 85 {
86 86 :key => 'right.heartbeat_response',
87 87 :value_type => 'string',
88 88 :default_value => 'OK',
89 89 :description => 'Heart beat response text'
90 90 },
91 91
92 92 {
93 93 :key => 'right.heartbeat_response_full',
94 94 :value_type => 'string',
95 95 :default_value => 'OK',
96 96 :description => 'Heart beat response text when user got full score (set this value to the empty string to disable this feature)'
97 97 },
98 98
99 99 {
100 100 :key => 'right.view_testcase',
101 101 :value_type => 'boolean',
102 102 :default_value => 'false',
103 103 :description => 'When true, any user can view/download test data'
104 104 },
105 105 # If Configuration['system.online_registration'] is true, the
106 106 # system allows online registration, and will use these
107 107 # information for sending confirmation emails.
108 108 {
109 109 :key => 'system.online_registration.smtp',
110 110 :value_type => 'string',
111 111 :default_value => 'smtp.somehost.com'
112 112 },
113 113
114 114 {
115 115 :key => 'system.online_registration.from',
116 116 :value_type => 'string',
117 117 :default_value => 'your.email@address'
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;
215 224 role.save
216 225 end
217 226
218 227 def seed_root
219 228 return if User.find_by_login('root')
220 229
221 230 root = User.new(:login => 'root',
222 231 :full_name => 'Administrator',
223 232 :alias => 'root')
224 233 root.password = 'ioionrails';
225 234
226 235 class << root
227 236 public :encrypt_new_password
228 237 def valid?(context=nil)
229 238 true
230 239 end
231 240 end
232 241
233 242 root.encrypt_new_password
234 243
235 244 root.roles << Role.find_by_name('admin')
236 245
237 246 root.activated = true
238 247 root.save
239 248 end
240 249
241 250 def seed_users_and_roles
242 251 seed_roles
243 252 seed_root
244 253 end
245 254
246 255 def seed_more_languages
247 256 Language.delete_all
248 257 Language.create( name: 'c', pretty_name: 'C', ext: 'c', common_ext: 'c' )
249 258 Language.create( name: 'cpp', pretty_name: 'C++', ext: 'cpp', common_ext: 'cpp,cc' )
250 259 Language.create( name: 'pas', pretty_name: 'Pascal', ext: 'pas', common_ext: 'pas' )
251 260 Language.create( name: 'ruby', pretty_name: 'Ruby', ext: 'rb', common_ext: 'rb' )
252 261 Language.create( name: 'python', pretty_name: 'Python', ext: 'py', common_ext: 'py' )
253 262 Language.create( name: 'java', pretty_name: 'Java', ext: 'java', common_ext: 'java' )
254 263 end
255 264
256 265 seed_config
257 266 seed_users_and_roles
258 267 seed_more_languages
@@ -1,73 +1,74
1 1 module GraderScript
2 2
3 3 def self.grader_control_enabled?
4 4 if defined? GRADER_ROOT_DIR
5 5 GRADER_ROOT_DIR != ''
6 6 else
7 7 false
8 8 end
9 9 end
10 10
11 11 def self.raw_dir
12 12 File.join GRADER_ROOT_DIR, "raw"
13 13 end
14 14
15 15 def self.call_grader(params)
16 16 if GraderScript.grader_control_enabled?
17 17 cmd = File.join(GRADER_ROOT_DIR, "scripts/grader") + " " + params
18 18 system(cmd)
19 19 end
20 20 end
21 21
22 22 def self.stop_grader(pid)
23 23 GraderScript.call_grader "stop #{pid}"
24 24 end
25 25
26 26 def self.stop_graders(pids)
27 27 pid_str = (pids.map { |process| process.pid.to_s }).join ' '
28 28 GraderScript.call_grader "stop #{pid_str}"
29 29 end
30 30
31 31 def self.start_grader(env)
32 32 GraderScript.call_grader "#{env} queue --err-log &"
33 33 GraderScript.call_grader "#{env} test_request -err-log &"
34 34 end
35 35
36 + #call the import problem script
36 37 def self.call_import_problem(problem_name,
37 38 problem_dir,
38 39 time_limit=1,
39 40 memory_limit=32,
40 41 checker_name='text')
41 42 if GraderScript.grader_control_enabled?
42 43 cur_dir = `pwd`.chomp
43 44 Dir.chdir(GRADER_ROOT_DIR)
44 45
45 46 script_name = File.join(GRADER_ROOT_DIR, "scripts/import_problem")
46 47 cmd = "#{script_name} #{problem_name} #{problem_dir} #{checker_name}" +
47 48 " -t #{time_limit} -m #{memory_limit}"
48 49
49 50 output = `#{cmd}`
50 51
51 52 Dir.chdir(cur_dir)
52 53
53 54 return "import CMD: #{cmd}\n" + output
54 55 end
55 56 return ''
56 57 end
57 58
58 59 def self.call_import_testcase(problem_name)
59 60 if GraderScript.grader_control_enabled?
60 61 cur_dir = `pwd`.chomp
61 62 Dir.chdir(GRADER_ROOT_DIR)
62 63
63 64 script_name = File.join(GRADER_ROOT_DIR, "scripts/load_testcase")
64 65 cmd = "#{script_name} #{problem_name}"
65 66
66 67 output = `#{cmd}`
67 68
68 69 Dir.chdir(cur_dir)
69 70 return "Testcase import result:\n" + output
70 71 end
71 72 end
72 73
73 74 end
@@ -1,150 +1,152
1 1 require 'tmpdir'
2 2
3 3 class TestdataImporter
4 4
5 5 attr :log_msg
6 6
7 7 def initialize(problem)
8 8 @problem = problem
9 9 end
10 10
11 + #Create or update problem according to the parameter
11 12 def import_from_file(tempfile,
12 13 time_limit,
13 14 memory_limit,
14 15 checker_name='text',
15 16 import_to_db=false)
16 17
17 18 dirname = extract(tempfile)
18 19 return false if not dirname
19 20 if not import_to_db
20 21 @log_msg = GraderScript.call_import_problem(@problem.name,
21 22 dirname,
22 23 time_limit,
23 24 memory_limit,
24 25 checker_name)
25 26 else
26 27 # Import test data to test pairs.
27 28
28 29 @problem.test_pairs.clear
29 30 if import_test_pairs(dirname)
30 31 test_pair_count = TestPair.count :conditions => "problem_id = #{@problem.id}"
31 32 @log_msg = "Importing test pair successful. (#{test_pair_count} test pairs imported)"
32 33 else
33 34 @log_msg = "Importing test pair failed. (0 test pairs imported)"
34 35 end
35 36 end
36 37
37 38 @log_msg << import_problem_description(dirname)
38 39 @log_msg << import_problem_pdf(dirname)
39 40 @log_msg << import_full_score(dirname)
40 41
41 42 #import test data
42 43 @log_msg << GraderScript.call_import_testcase(@problem.name)
43 44
44 45 return true
45 46 end
46 47
47 48 protected
48 49
49 50 def self.long_ext(filename)
50 51 i = filename.index('.')
51 52 len = filename.length
52 53 return filename.slice(i..len)
53 54 end
54 55
56 + # extract an archive file located at +tempfile+ to the +raw_dir+
55 57 def extract(tempfile)
56 58 testdata_filename = save_testdata_file(tempfile)
57 59 ext = TestdataImporter.long_ext(tempfile.original_filename)
58 60
59 61 extract_dir = File.join(GraderScript.raw_dir, @problem.name)
60 62 if File.exists? extract_dir
61 63 backup_count = 0
62 64 begin
63 65 backup_count += 1
64 66 backup_dirname = "#{extract_dir}.backup.#{backup_count}"
65 67 end while File.exists? backup_dirname
66 68 File.rename(extract_dir, backup_dirname)
67 69 end
68 70 Dir.mkdir extract_dir
69 71
70 72 if ext=='.tar.gz' or ext=='.tgz'
71 73 cmd = "tar -zxvf #{testdata_filename} -C #{extract_dir}"
72 74 elsif ext=='.tar'
73 75 cmd = "tar -xvf #{testdata_filename} -C #{extract_dir}"
74 76 elsif ext=='.zip'
75 77 cmd = "unzip -o #{testdata_filename} -d #{extract_dir}"
76 78 else
77 79 return nil
78 80 end
79 81
80 82 system(cmd)
81 83
82 84 files = Dir["#{extract_dir}/**/*1*.in"]
83 85 return nil if files.length==0
84 86
85 87 File.delete(testdata_filename)
86 88
87 89 return File.dirname(files[0])
88 90 end
89 91
90 92 def save_testdata_file(tempfile)
91 93 ext = TestdataImporter.long_ext(tempfile.original_filename)
92 94 testdata_filename = File.join(Dir.tmpdir,"#{@problem.name}#{ext}")
93 95
94 96 return nil if tempfile==""
95 97
96 98 if tempfile.instance_of?(Tempfile)
97 99 tempfile.close
98 100 FileUtils.move(tempfile.path,testdata_filename)
99 101 else
100 102 File.open(testdata_filename, "wb") do |f|
101 103 f.write(tempfile.read)
102 104 end
103 105 end
104 106
105 107 return testdata_filename
106 108 end
107 109
108 110 def import_test_pairs(dirname)
109 111 test_num = 1
110 112 while FileTest.exists? "#{dirname}/#{test_num}.in"
111 113 in_filename = "#{dirname}/#{test_num}.in"
112 114 sol_filename = "#{dirname}/#{test_num}.sol"
113 115
114 116 break if not FileTest.exists? sol_filename
115 117
116 118 test_pair = TestPair.new(:input => open(in_filename).read,
117 119 :solution => open(sol_filename).read,
118 120 :problem => @problem)
119 121 break if not test_pair.save
120 122
121 123 test_num += 1
122 124 end
123 125 return test_num > 1
124 126 end
125 127
126 128 def import_problem_description(dirname)
127 129 html_files = Dir["#{dirname}/*.html"]
128 130 markdown_files = Dir["#{dirname}/*.md"] + Dir["#{dirname}/*.markdown"]
129 131 if (html_files.length != 0) or (markdown_files.length != 0)
130 132 description = @problem.description || Description.new
131 133
132 134 if html_files.length != 0
133 135 filename = html_files[0]
134 136 description.markdowned = false
135 137 else
136 138 filename = markdown_files[0]
137 139 description.markdowned = true
138 140 end
139 141
140 142 description.body = open(filename).read
141 143 description.save
142 144 @problem.description = description
143 145 @problem.save
144 146 return "\nProblem description imported from #{filename}."
145 147 else
146 148 return ''
147 149 end
148 150 end
149 151
150 152 def import_problem_pdf(dirname)
deleted file
deleted file
deleted file
deleted file
deleted file
deleted file
deleted file
deleted file
You need to be logged in to leave comments. Login now