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

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

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