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: 1275 inserted, 462 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
@@ -14,78 +14,79
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'
@@ -16,132 +16,141
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)
@@ -156,80 +165,83
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,83 +1,86
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
@@ -501,48 +504,54
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,132 +1,130
1 1 class ApplicationController < ActionController::Base
2 2 protect_from_forgery
3 3
4 4 before_filter :current_user
5 5
6 6 SINGLE_USER_MODE_CONF_KEY = 'system.single_user_mode'
7 7 MULTIPLE_IP_LOGIN_CONF_KEY = 'right.multiple_ip_login'
8 8
9 9 #report and redirect for unauthorized activities
10 10 def unauthorized_redirect
11 11 flash[:notice] = 'You are not authorized to view the page you requested'
12 12 redirect_to :controller => 'main', :action => 'login'
13 13 end
14 14
15 15 # Returns the current logged-in user (if any).
16 16 def current_user
17 17 return nil unless session[:user_id]
18 18 @current_user ||= User.find(session[:user_id])
19 19 end
20 20
21 21 def admin_authorization
22 22 return false unless authenticate
23 23 user = User.includes(:roles).find(session[:user_id])
24 24 unless user.admin?
25 25 unauthorized_redirect
26 26 return false
27 27 end
28 28 return true
29 29 end
30 30
31 31 def authorization_by_roles(allowed_roles)
32 32 return false unless authenticate
33 33 user = User.find(session[:user_id])
34 34 unless user.roles.detect { |role| allowed_roles.member?(role.name) }
35 35 unauthorized_redirect
36 36 return false
37 37 end
38 38 end
39 39
40 40 def testcase_authorization
41 41 #admin always has privileged
42 - puts "haha"
43 42 if @current_user.admin?
44 43 return true
45 44 end
46 45
47 - puts "hehe"
48 - puts GraderConfiguration["right.view_testcase"]
49 46 unauthorized_redirect unless GraderConfiguration["right.view_testcase"]
50 47 end
51 48
52 49 protected
53 50
54 51 def authenticate
55 52 unless session[:user_id]
56 53 flash[:notice] = 'You need to login'
57 54 if GraderConfiguration[SINGLE_USER_MODE_CONF_KEY]
58 55 flash[:notice] = 'You need to login but you cannot log in at this time'
59 56 end
60 57 redirect_to :controller => 'main', :action => 'login'
61 58 return false
62 59 end
63 60
61 +
64 62 # check if run in single user mode
65 63 if GraderConfiguration[SINGLE_USER_MODE_CONF_KEY]
66 - user = User.find_by_id(session[:user_id])
67 - if user==nil or (not user.admin?)
64 + if @current_user==nil or (not @current_user.admin?)
68 65 flash[:notice] = 'You cannot log in at this time'
69 66 redirect_to :controller => 'main', :action => 'login'
70 67 return false
71 68 end
72 - unless user.enabled?
73 - flash[:notice] = 'Your account is disabled'
74 - redirect_to :controller => 'main', :action => 'login'
75 - return false
76 - end
77 69 return true
78 70 end
79 71
72 + # check if the user is enabled
73 + unless @current_user.enabled? or @current_user.admin?
74 + flash[:notice] = 'Your account is disabled'
75 + redirect_to :controller => 'main', :action => 'login'
76 + return false
77 + end
78 +
80 79 if GraderConfiguration.multicontests?
81 - user = User.find(session[:user_id])
82 - return true if user.admin?
80 + return true if @current_user.admin?
83 81 begin
84 - if user.contest_stat(true).forced_logout
82 + if @current_user.contest_stat(true).forced_logout
85 83 flash[:notice] = 'You have been automatically logged out.'
86 84 redirect_to :controller => 'main', :action => 'index'
87 85 end
88 86 rescue
89 87 end
90 88 end
91 89 return true
92 90 end
93 91
94 92 def authenticate_by_ip_address
95 93 #this assume that we have already authenticate normally
96 94 unless GraderConfiguration[MULTIPLE_IP_LOGIN_CONF_KEY]
97 95 user = User.find(session[:user_id])
98 96 if (not user.admin? and user.last_ip and user.last_ip != request.remote_ip)
99 97 flash[:notice] = "You cannot use the system from #{request.remote_ip}. Your last ip is #{user.last_ip}"
100 98 redirect_to :controller => 'main', :action => 'login'
101 99 puts "CHEAT: user #{user.login} tried to login from '#{request.remote_ip}' while last ip is '#{user.last_ip}' at #{Time.zone.now}"
102 100 return false
103 101 end
104 102 unless user.last_ip
105 103 user.last_ip = request.remote_ip
106 104 user.save
107 105 end
108 106 end
109 107 return true
110 108 end
111 109
112 110 def authorization
113 111 return false unless authenticate
114 112 user = User.find(session[:user_id])
115 113 unless user.roles.detect { |role|
116 114 role.rights.detect{ |right|
117 115 right.controller == self.class.controller_name and
118 116 (right.action == 'all' or right.action == action_name)
119 117 }
120 118 }
121 119 flash[:notice] = 'You are not authorized to view the page you requested'
122 120 #request.env['HTTP_REFERER'] ? (redirect_to :back) : (redirect_to :controller => 'login')
123 121 redirect_to :controller => 'main', :action => 'login'
124 122 return false
125 123 end
126 124 end
127 125
128 126 def verify_time_limit
129 127 return true if session[:user_id]==nil
130 128 user = User.find(session[:user_id], :include => :site)
131 129 return true if user==nil or user.site == nil
132 130 if user.contest_finished?
@@ -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])
14 - session[:user_id] = user.id
15 - session[:admin] = user.admin?
20 + return
21 + end
22 +
23 + #process logging in
24 + session[:user_id] = user.id
25 + session[:admin] = user.admin?
16 26
17 - # clear forced logout flag for multicontests contest change
18 - if GraderConfiguration.multicontests?
19 - contest_stat = user.contest_stat
20 - if contest_stat.respond_to? :forced_logout
21 - if contest_stat.forced_logout
22 - contest_stat.forced_logout = false
23 - contest_stat.save
24 - end
27 + # clear forced logout flag for multicontests contest change
28 + if GraderConfiguration.multicontests?
29 + contest_stat = user.contest_stat
30 + if contest_stat.respond_to? :forced_logout
31 + if contest_stat.forced_logout
32 + contest_stat.forced_logout = false
33 + contest_stat.save
25 34 end
26 35 end
27 -
28 - #save login information
29 - Login.create(user_id: user.id, ip_address: request.remote_ip)
36 + end
30 37
31 - redirect_to :controller => 'main', :action => 'list'
32 - else
33 - flash[:notice] = 'Wrong password'
34 - redirect_to :controller => 'main', :action => 'login'
35 - end
38 + #save login information
39 + Login.create(user_id: user.id, ip_address: request.remote_ip)
40 +
41 + redirect_to :controller => 'main', :action => 'list'
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
@@ -41,108 +41,108
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
@@ -120,175 +120,191
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
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
198 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
@@ -7,96 +7,98
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
@@ -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,74 +1,75
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
@@ -183,96 +184,97
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
@@ -366,122 +368,139
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
@@ -40,111 +40,114
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>
@@ -155,67 +158,67
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,62 +1,63
1 1 require 'yaml'
2 2
3 3 #
4 4 # This class also contains various login of the system.
5 5 #
6 6 class GraderConfiguration < ActiveRecord::Base
7 7
8 8 SYSTEM_MODE_CONF_KEY = 'system.mode'
9 9 TEST_REQUEST_EARLY_TIMEOUT_KEY = 'contest.test_request.early_timeout'
10 10 MULTICONTESTS_KEY = 'system.multicontests'
11 11 CONTEST_TIME_LIMIT_KEY = 'contest.time_limit'
12 12 MULTIPLE_IP_LOGIN_KEY = 'right.multiple_ip_login'
13 13 VIEW_TESTCASE = 'right.view_testcase'
14 14 SINGLE_USER_KEY = 'system.single_user_mode'
15 + SYSTEM_USE_PROBLEM_GROUP = 'system.use_problem_group'
15 16
16 17 cattr_accessor :config_cache
17 18 cattr_accessor :task_grading_info_cache
18 19 cattr_accessor :contest_time_str
19 20 cattr_accessor :contest_time
20 21
21 22 GraderConfiguration.config_cache = nil
22 23 GraderConfiguration.task_grading_info_cache = nil
23 24
24 25 def self.config_cached?
25 26 (defined? CONFIGURATION_CACHE_ENABLED) and (CONFIGURATION_CACHE_ENABLED)
26 27 end
27 28
28 29 def self.get(key)
29 30 if GraderConfiguration.config_cached?
30 31 if GraderConfiguration.config_cache == nil
31 32 self.read_config
32 33 end
33 34 return GraderConfiguration.config_cache[key]
34 35 else
35 36 return GraderConfiguration.read_one_key(key)
36 37 end
37 38 end
38 39
39 40 def self.[](key)
40 41 self.get(key)
41 42 end
42 43
43 44 def self.reload
44 45 self.read_config
45 46 end
46 47
47 48 def self.clear
48 49 GraderConfiguration.config_cache = nil
49 50 end
50 51
51 52 #
52 53 # View decision
53 54 #
54 55 def self.show_submitbox_to?(user)
55 56 mode = get(SYSTEM_MODE_CONF_KEY)
56 57 return false if mode=='analysis'
57 58 if (mode=='contest')
58 59 return false if (user.site!=nil) and
59 60 ((user.site.started!=true) or (user.site.finished?))
60 61 end
61 62 return true
62 63 end
@@ -74,96 +75,100
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
123 +
124 + def self.use_problem_group?
125 + return get(SYSTEM_USE_PROBLEM_GROUP)
126 + end
122 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)
@@ -1,52 +1,60
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
@@ -1,86 +1,86
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
@@ -92,71 +92,75
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,58 +1,62
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
@@ -195,121 +199,146
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?
245 - return Problem.available_problems
250 + if GraderConfiguration.use_problem_group?
251 + return available_problems_in_group
252 + else
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
@@ -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 - %strong View:
21 - - if GraderConfiguration.show_grading_result
22 - = 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'}
22 + %strong View:
23 + - if GraderConfiguration.show_grading_result
24 + = link_to '[detailed result]', :action => 'result', :id => submission.id
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
25 - - unless GraderConfiguration['right.bypass_agreement']
26 - %tr
27 - %td{:align => "right"}= check_box_tag 'accept_agree'
28 - %td ยอมรับข้อตกลงการใช้งาน
29 -
30 - = submit_tag t('login.login_submit')
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'
24 + - unless GraderConfiguration['right.bypass_agreement']
25 + .form-group
26 + .col-sm-offset-3.col-sm-9
27 + .checkbox
28 + %label
29 + = check_box_tag 'accept_agree'
30 + ยอมรับข้อตกลงการใช้งาน
31 +
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
6 - - if @announcements.length!=0
7 - .announcementbox{:style => 'margin-top: 0px'}
8 - %span{:class => 'title'}
9 - Announcements
10 - = render :partial => 'announcement', :collection => @announcements
11 - %td{:style => 'vertical-align: top; width: 40%; padding-left: 20px;'}
12 - = render :partial => 'login_box'
3 + .row
4 + .col-md-6
5 + - if @announcements.length!=0
6 + .announcementbox{:style => 'margin-top: 0px'}
7 + %span{:class => 'title'}
8 + Announcements
9 + = render :partial => 'announcement', :collection => @announcements
10 + .col-md-4{style: "padding-left: 20px;"}
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
43 - What do you want to do to the selected problem?
44 - %br/
45 - (You can shift-click to select a range of problems)
46 - %ul
47 - %li
48 - Change date added to
49 - = select_date Date.current, :prefix => 'date_added'
50 - &nbsp;&nbsp;&nbsp;
51 - = submit_tag 'Change', :name => 'change_date_added'
52 - %li
53 - Set available to
54 - = submit_tag 'True', :name => 'enable_problem'
55 - = submit_tag 'False', :name => 'disable_problem'
42 + .panel.panel-primary
43 + .panel-heading
44 + Action
45 + .panel-body
46 + .submit-box
47 + What do you want to do to the selected problem?
48 + %br/
49 + (You can shift-click to select a range of problems)
50 + %ul.form-inline
51 + %li
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'
58 + &nbsp;&nbsp;&nbsp;
59 + = submit_tag 'Change', :name => 'change_date_added', class: 'btn btn-primary btn-sm'
60 + %li
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 - - if GraderConfiguration.multicontests?
58 - %li
59 - Add to
60 - = select("contest","id",Contest.all.collect {|c| [c.title, c.id]})
61 - = submit_tag 'Add', :name => 'add_to_contest'
65 + - if GraderConfiguration.multicontests?
66 + %li
67 + Add selected problems to contest
68 + = select("contest","id",Contest.all.collect {|c| [c.title, c.id]})
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
64 - %tr{style: "text-align: left;"}
65 - %th= check_box_tag 'select_all'
66 - %th Name
67 - %th Full name
68 - %th Available
69 - %th Date added
70 - - if GraderConfiguration.multicontests?
71 - %th Contests
79 + %table.table.table-hover.datatable
80 + %thead
81 + %tr{style: "text-align: left;"}
82 + %th= check_box_tag 'select_all'
83 + %th Name
84 + %th Full name
85 + %th Tags
86 + %th Available
87 + %th Date added
88 + - if GraderConfiguration.multicontests?
89 + %th Contests
72 90
73 - - num = 0
74 - - for problem in @problems
75 - - num += 1
76 - %tr{:id => "row-prob-#{problem.id}", :name=> "prob-#{problem.id}"}
77 - %td= check_box_tag "prob-#{problem.id}-#{num}"
78 - %td= problem.name
79 - %td= problem.full_name
80 - %td= problem.available
81 - %td= problem.date_added
82 - - if GraderConfiguration.multicontests?
91 + %tbody
92 + - num = 0
93 + - for problem in @problems
94 + - num += 1
95 + %tr{:id => "row-prob-#{problem.id}", :name=> "prob-#{problem.id}"}
96 + %td= check_box_tag "prob-#{problem.id}-#{num}"
97 + %td= problem.name
98 + %td= problem.full_name
83 99 %td
84 - - problem.contests.each do |contest|
85 - = "(#{contest.name} [#{link_to 'x', :action => 'remove_contest', :id => problem.id, :contest_id => contest.id }])"
100 + - problem.tags.each do |t|
101 + %span.label.label-default= t.name
102 + %td= problem.available
103 + %td= problem.date_added
104 + - if GraderConfiguration.multicontests?
105 + %td
106 + - problem.contests.each do |contest|
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,83 +1,86
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
33 - - if @submission
34 - = render :partial => 'submission_short',
35 - :locals => {submission: @submission, problem_name: @problem.name, problem_id: @problem.id }
35 + %div#latest_status
36 + - if @submission
37 + = render :partial => 'submission_short',
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
@@ -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 + });
@@ -20,53 +20,53
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
@@ -118,97 +118,106
118 118 },
119 119
120 120 {
121 121 :key => 'system.admin_email',
122 122 :value_type => 'string',
123 123 :default_value => 'admin@admin.email'
124 124 },
125 125
126 126 {
127 127 :key => 'system.user_setting_enabled',
128 128 :value_type => 'boolean',
129 129 :default_value => 'true',
130 130 :description => 'If this option is true, users can change their settings'
131 131 },
132 132
133 133 {
134 134 :key => 'system.user_setting_enabled',
135 135 :value_type => 'boolean',
136 136 :default_value => 'true',
137 137 :description => 'If this option is true, users can change their settings'
138 138 },
139 139
140 140 # If Configuration['contest.test_request.early_timeout'] is true
141 141 # the user will not be able to use test request at 30 minutes
142 142 # before the contest ends.
143 143 {
144 144 :key => 'contest.test_request.early_timeout',
145 145 :value_type => 'boolean',
146 146 :default_value => 'false'
147 147 },
148 148
149 149 {
150 150 :key => 'system.multicontests',
151 151 :value_type => 'boolean',
152 152 :default_value => 'false'
153 153 },
154 154
155 155 {
156 156 :key => 'contest.confirm_indv_contest_start',
157 157 :value_type => 'boolean',
158 158 :default_value => 'false'
159 159 },
160 160
161 161 {
162 162 :key => 'contest.default_contest_name',
163 163 :value_type => 'string',
164 164 :default_value => 'none',
165 165 :description => "New user will be assigned to this contest automatically, if it exists. Set to 'none' if there is no default contest."
166 - }
166 + },
167 +
168 + {
169 + :key => 'system.use_problem_group',
170 + :value_type => 'boolean',
171 + :default_value => 'false',
172 + :description => "If true, available problem to the user will be only ones associated with the group of the user."
173 + },
174 +
175 +
167 176
168 177 ]
169 178
170 179
171 180 def create_configuration_key(key,
172 181 value_type,
173 182 default_value,
174 183 description='')
175 184 conf = (GraderConfiguration.find_by_key(key) ||
176 185 GraderConfiguration.new(:key => key,
177 186 :value_type => value_type,
178 187 :value => default_value))
179 188 conf.description = description
180 189 conf.save
181 190 end
182 191
183 192 def seed_config
184 193 CONFIGURATIONS.each do |conf|
185 194 if conf.has_key? :description
186 195 desc = conf[:description]
187 196 else
188 197 desc = ''
189 198 end
190 199 create_configuration_key(conf[:key],
191 200 conf[:value_type],
192 201 conf[:default_value],
193 202 desc)
194 203 end
195 204 end
196 205
197 206 def seed_roles
198 207 return if Role.find_by_name('admin')
199 208
200 209 role = Role.create(:name => 'admin')
201 210 user_admin_right = Right.create(:name => 'user_admin',
202 211 :controller => 'user_admin',
203 212 :action => 'all')
204 213 problem_admin_right = Right.create(:name=> 'problem_admin',
205 214 :controller => 'problems',
206 215 :action => 'all')
207 216
208 217 graders_right = Right.create(:name => 'graders_admin',
209 218 :controller => 'graders',
210 219 :action => 'all')
211 220
212 221 role.rights << user_admin_right;
213 222 role.rights << problem_admin_right;
214 223 role.rights << graders_right;
@@ -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,102 +1,104
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 - def import_from_file(tempfile,
12 - time_limit,
11 + #Create or update problem according to the parameter
12 + def import_from_file(tempfile,
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
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