diff --git a/Gemfile b/Gemfile --- a/Gemfile +++ b/Gemfile @@ -59,15 +59,16 @@ #syntax highlighter gem 'rouge' -#add bootstrap +#bootstrap add-ons gem 'bootstrap-sass', '~> 3.2.0' gem 'bootstrap-switch-rails' gem 'bootstrap-toggle-rails' gem 'autoprefixer-rails' - -#bootstrap sortable gem 'momentjs-rails' gem 'rails_bootstrap_sortable' +gem 'bootstrap-datepicker-rails' +gem 'bootstrap3-datetimepicker-rails' +gem 'jquery-datatables-rails' #----------- user interface ----------------- #select 2 diff --git a/Gemfile.lock b/Gemfile.lock --- a/Gemfile.lock +++ b/Gemfile.lock @@ -61,10 +61,14 @@ best_in_place (3.0.3) actionpack (>= 3.2) railties (>= 3.2) + bootstrap-datepicker-rails (1.7.1.1) + railties (>= 3.0) bootstrap-sass (3.2.0.2) sass (~> 3.2) bootstrap-switch-rails (3.3.3) bootstrap-toggle-rails (2.2.1.0) + bootstrap3-datetimepicker-rails (4.17.47) + momentjs-rails (>= 2.8.1) builder (3.2.2) coffee-rails (4.2.1) coffee-script (>= 2.2.0) @@ -97,6 +101,11 @@ i18n (0.7.0) in_place_editing (1.2.0) jquery-countdown-rails (2.0.2) + jquery-datatables-rails (3.4.0) + actionpack (>= 3.1) + jquery-rails + railties (>= 3.1) + sass-rails jquery-rails (4.2.1) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) @@ -201,9 +210,11 @@ activerecord-session_store autoprefixer-rails best_in_place (~> 3.0.1) + bootstrap-datepicker-rails bootstrap-sass (~> 3.2.0) bootstrap-switch-rails bootstrap-toggle-rails + bootstrap3-datetimepicker-rails coffee-rails dynamic_form fuzzy-string-match @@ -211,6 +222,7 @@ haml-rails in_place_editing jquery-countdown-rails + jquery-datatables-rails jquery-rails jquery-tablesorter jquery-timepicker-addon-rails @@ -232,4 +244,4 @@ yaml_db BUNDLED WITH - 1.13.6 + 1.15.4 diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -12,10 +12,14 @@ // //= require jquery //= require jquery_ujs +//= require dataTables/jquery.dataTables +//= require dataTables/bootstrap/3/jquery.dataTables.bootstrap //= require jquery-ui //= require bootstrap-sprockets //= require moment +//= require moment/th //= require bootstrap-sortable +//= require bootstrap-datetimepicker //= require select2 //= require ace-rails-ap //= require ace/mode-c_cpp @@ -32,6 +36,8 @@ //= require best_in_place //= require best_in_place.jquery-ui //= require brython +//= require bootstrap-datepicker +//= require bootstrap-datetimepicker // since this is after blank line, it is not downloaded //x= require prototype diff --git a/app/assets/javascripts/groups.js.coffee b/app/assets/javascripts/groups.js.coffee new file mode 100644 diff --git a/app/assets/javascripts/tags.coffee b/app/assets/javascripts/tags.coffee new file mode 100644 --- /dev/null +++ b/app/assets/javascripts/tags.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/javascripts/testcases.js.coffee b/app/assets/javascripts/testcases.js.coffee new file mode 100644 diff --git a/app/assets/stylesheets/application.css.scss b/app/assets/stylesheets/application.css.scss --- a/app/assets/stylesheets/application.css.scss +++ b/app/assets/stylesheets/application.css.scss @@ -33,6 +33,9 @@ //@import bootstrap3-switch @import "bootstrap-toggle"; @import "bootstrap-sortable"; +@import "bootstrap-datepicker3"; +@import "bootstrap-datetimepicker"; +@import "dataTables/bootstrap/3/jquery.dataTables.bootstrap"; //bootstrap navbar color (from) $bgDefault: #19197b; @@ -546,3 +549,9 @@ margin-top: 5px; margin-bottom: 5px; } + + + +.grader-comment { + word-wrap: break-word; +} diff --git a/app/assets/stylesheets/groups.css.scss b/app/assets/stylesheets/groups.css.scss new file mode 100644 diff --git a/app/assets/stylesheets/tags.scss b/app/assets/stylesheets/tags.scss new file mode 100644 --- /dev/null +++ b/app/assets/stylesheets/tags.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the tags controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/testcases.css.scss b/app/assets/stylesheets/testcases.css.scss new file mode 100644 diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -39,13 +39,10 @@ def testcase_authorization #admin always has privileged - puts "haha" if @current_user.admin? return true end - puts "hehe" - puts GraderConfiguration["right.view_testcase"] unauthorized_redirect unless GraderConfiguration["right.view_testcase"] end @@ -61,27 +58,28 @@ return false end + # check if run in single user mode if GraderConfiguration[SINGLE_USER_MODE_CONF_KEY] - user = User.find_by_id(session[:user_id]) - if user==nil or (not user.admin?) + if @current_user==nil or (not @current_user.admin?) flash[:notice] = 'You cannot log in at this time' redirect_to :controller => 'main', :action => 'login' return false end - unless user.enabled? - flash[:notice] = 'Your account is disabled' - redirect_to :controller => 'main', :action => 'login' - return false - end return true end + # check if the user is enabled + unless @current_user.enabled? or @current_user.admin? + flash[:notice] = 'Your account is disabled' + redirect_to :controller => 'main', :action => 'login' + return false + end + if GraderConfiguration.multicontests? - user = User.find(session[:user_id]) - return true if user.admin? + return true if @current_user.admin? begin - if user.contest_stat(true).forced_logout + if @current_user.contest_stat(true).forced_logout flash[:notice] = 'You have been automatically logged out.' redirect_to :controller => 'main', :action => 'index' end diff --git a/app/controllers/graders_controller.rb b/app/controllers/graders_controller.rb --- a/app/controllers/graders_controller.rb +++ b/app/controllers/graders_controller.rb @@ -1,22 +1,6 @@ class GradersController < ApplicationController - before_filter :admin_authorization, except: [ :submission ] - before_filter(only: [:submission]) { - #check if authenticated - return false unless authenticate - - #admin always has privileged - if @current_user.admin? - return true - end - - if GraderConfiguration["right.user_view_submission"] and Submission.find(params[:id]).problem.available? - return true - else - unauthorized_redirect - return false - end - } + before_filter :admin_authorization verify :method => :post, :only => ['clear_all', 'start_exam', @@ -77,25 +61,6 @@ @task = Task.find(params[:id]) end - def submission - @submission = Submission.find(params[:id]) - formatter = Rouge::Formatters::HTML.new(css_class: 'highlight', line_numbers: true ) - lexer = case @submission.language.name - when "c" then Rouge::Lexers::C.new - when "cpp" then Rouge::Lexers::Cpp.new - when "pas" then Rouge::Lexers::Pas.new - when "ruby" then Rouge::Lexers::Ruby.new - when "python" then Rouge::Lexers::Python.new - when "java" then Rouge::Lexers::Java.new - when "php" then Rouge::Lexers::PHP.new - end - @formatted_code = formatter.format(lexer.lex(@submission.source)) - @css_style = Rouge::Themes::ThankfulEyes.render(scope: '.highlight') - - user = User.find(session[:user_id]) - SubmissionViewLog.create(user_id: session[:user_id],submission_id: @submission.id) unless user.admin? - - end # various grader controls diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb new file mode 100644 --- /dev/null +++ b/app/controllers/groups_controller.rb @@ -0,0 +1,104 @@ +class GroupsController < ApplicationController + before_action :set_group, only: [:show, :edit, :update, :destroy, + :add_user, :remove_user,:remove_all_user, + :add_problem, :remove_problem,:remove_all_problem, + ] + before_action :authenticate, :admin_authorization + + # GET /groups + def index + @groups = Group.all + end + + # GET /groups/1 + def show + end + + # GET /groups/new + def new + @group = Group.new + end + + # GET /groups/1/edit + def edit + end + + # POST /groups + def create + @group = Group.new(group_params) + + if @group.save + redirect_to @group, notice: 'Group was successfully created.' + else + render :new + end + end + + # PATCH/PUT /groups/1 + def update + if @group.update(group_params) + redirect_to @group, notice: 'Group was successfully updated.' + else + render :edit + end + end + + # DELETE /groups/1 + def destroy + @group.destroy + redirect_to groups_url, notice: 'Group was successfully destroyed.' + end + + def remove_user + user = User.find(params[:user_id]) + @group.users.delete(user) + redirect_to group_path(@group), flash: {success: "User #{user.login} was removed from the group #{@group.name}"} + end + + def remove_all_user + @group.users.clear + redirect_to group_path(@group), alert: 'All users removed' + end + + def remove_all_problem + @group.problems.clear + redirect_to group_path(@group), alert: 'All problems removed' + end + + def add_user + user = User.find(params[:user_id]) + begin + @group.users << user + redirect_to group_path(@group), flash: { success: "User #{user.login} was add to the group #{@group.name}"} + rescue => e + redirect_to group_path(@group), alert: e.message + end + end + + def remove_problem + problem = Problem.find(params[:problem_id]) + @group.problems.delete(problem) + redirect_to group_path(@group), flash: {success: "Problem #{problem.name} was removed from the group #{@group.name}" } + end + + def add_problem + problem = Problem.find(params[:problem_id]) + begin + @group.problems << problem + redirect_to group_path(@group), flash: {success: "Problem #{problem.name} was add to the group #{@group.name}" } + rescue => e + redirect_to group_path(@group), alert: e.message + end + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_group + @group = Group.find(params[:id]) + end + + # Only allow a trusted parameter "white list" through. + def group_params + params.require(:group).permit(:name, :description) + end +end diff --git a/app/controllers/login_controller.rb b/app/controllers/login_controller.rb --- a/app/controllers/login_controller.rb +++ b/app/controllers/login_controller.rb @@ -7,32 +7,38 @@ end def login - if (!GraderConfiguration['right.bypass_agreement']) and (!params[:accept_agree]) + user = User.authenticate(params[:login], params[:password]) + unless user + flash[:notice] = 'Wrong password' + redirect_to :controller => 'main', :action => 'login' + return + end + + if (!GraderConfiguration['right.bypass_agreement']) and (!params[:accept_agree]) and !user.admin? flash[:notice] = 'You must accept the agreement before logging in' redirect_to :controller => 'main', :action => 'login' - elsif user = User.authenticate(params[:login], params[:password]) - session[:user_id] = user.id - session[:admin] = user.admin? + return + end + + #process logging in + session[:user_id] = user.id + session[:admin] = user.admin? - # clear forced logout flag for multicontests contest change - if GraderConfiguration.multicontests? - contest_stat = user.contest_stat - if contest_stat.respond_to? :forced_logout - if contest_stat.forced_logout - contest_stat.forced_logout = false - contest_stat.save - end + # clear forced logout flag for multicontests contest change + if GraderConfiguration.multicontests? + contest_stat = user.contest_stat + if contest_stat.respond_to? :forced_logout + if contest_stat.forced_logout + contest_stat.forced_logout = false + contest_stat.save end end - - #save login information - Login.create(user_id: user.id, ip_address: request.remote_ip) + end - redirect_to :controller => 'main', :action => 'list' - else - flash[:notice] = 'Wrong password' - redirect_to :controller => 'main', :action => 'login' - end + #save login information + Login.create(user_id: user.id, ip_address: request.remote_ip) + + redirect_to :controller => 'main', :action => 'list' end def site_login diff --git a/app/controllers/main_controller.rb b/app/controllers/main_controller.rb --- a/app/controllers/main_controller.rb +++ b/app/controllers/main_controller.rb @@ -86,7 +86,7 @@ render :action => 'list' and return end - if @submission.valid? + if @submission.valid?(@current_user) if @submission.save == false flash[:notice] = 'Error saving your submission' elsif Task.create(:submission_id => @submission.id, @@ -97,7 +97,7 @@ prepare_list_information render :action => 'list' and return end - redirect_to :action => 'list' + redirect_to edit_submission_path(@submission) end def source diff --git a/app/controllers/problems_controller.rb b/app/controllers/problems_controller.rb --- a/app/controllers/problems_controller.rb +++ b/app/controllers/problems_controller.rb @@ -165,7 +165,7 @@ redirect_to :controller => 'main', :action => 'list' return end - @submissions = Submission.includes(:user).where(problem_id: params[:id]).order(:user_id,:id) + @submissions = Submission.includes(:user).includes(:language).where(problem_id: params[:id]).order(:user_id,:id) #stat summary range =65 @@ -195,7 +195,26 @@ set_available(true) elsif params.has_key? 'disable_problem' set_available(false) + elsif params.has_key? 'add_group' + group = Group.find(params[:group_id]) + ok = [] + failed = [] + get_problems_from_params.each do |p| + begin + group.problems << p + ok << p.full_name + rescue => e + failed << p.full_name + end + end + flash[:success] = "The following problems are added to the group #{group.name}: " + ok.join(', ') if ok.count > 0 + flash[:alert] = "The following problems are already in the group #{group.name}: " + failed.join(', ') if failed.count > 0 + elsif params.has_key? 'add_tags' + get_problems_from_params.each do |p| + p.tag_ids += params[:tag_ids] + end end + redirect_to :action => 'manage' end @@ -243,10 +262,7 @@ def change_date_added problems = get_problems_from_params - year = params[:date_added][:year].to_i - month = params[:date_added][:month].to_i - day = params[:date_added][:day].to_i - date = Date.new(year,month,day) + date = Date.parse(params[:date_added]) problems.each do |p| p.date_added = date p.save @@ -288,7 +304,7 @@ private def problem_params - params.require(:problem).permit(:name, :full_name, :full_score, :date_added, :available, :test_allowed,:output_only, :url, :description) + params.require(:problem).permit(:name, :full_name, :full_score, :date_added, :available, :test_allowed,:output_only, :url, :description, tag_ids:[]) end end diff --git a/app/controllers/report_controller.rb b/app/controllers/report_controller.rb --- a/app/controllers/report_controller.rb +++ b/app/controllers/report_controller.rb @@ -52,6 +52,8 @@ #set up range from param @since_id = params.fetch(:from_id, 0).to_i @until_id = params.fetch(:to_id, 0).to_i + @since_id = nil if @since_id == 0 + @until_id = nil if @until_id == 0 #calculate the routine @scorearray = calculate_max_score(@problems, @users, @since_id, @until_id) diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -1,6 +1,6 @@ class SubmissionsController < ApplicationController before_action :authenticate - before_action :submission_authorization, only: [:show, :direct_edit_submission, :download, :edit] + before_action :submission_authorization, only: [:show, :download, :edit] before_action :admin_authorization, only: [:rejudge] # GET /submissions @@ -51,7 +51,15 @@ #on-site new submission on specific problem def direct_edit_problem @problem = Problem.find(params[:problem_id]) + unless @current_user.can_view_problem?(@problem) + unauthorized_redirect + return + end @source = '' + if (params[:view_latest]) + sub = Submission.find_last_by_user_and_problem(@current_user.id,@problem.id) + @source = @submission.source.to_s if @submission and @submission.source + end render 'edit' end @@ -94,8 +102,7 @@ end sub = Submission.find(params[:id]) - if sub.problem.available? - puts "sub = #{sub.user.id}, current = #{@current_user.id}" + if @current_user.available_problems.include? sub.problem return true if GraderConfiguration["right.user_view_submission"] or sub.user == @current_user end diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb new file mode 100644 --- /dev/null +++ b/app/controllers/tags_controller.rb @@ -0,0 +1,60 @@ +class TagsController < ApplicationController + before_action :set_tag, only: [:show, :edit, :update, :destroy] + + # GET /tags + def index + @tags = Tag.all + end + + # GET /tags/1 + def show + end + + # GET /tags/new + def new + @tag = Tag.new + end + + # GET /tags/1/edit + def edit + end + + # POST /tags + def create + @tag = Tag.new(tag_params) + + if @tag.save + redirect_to @tag, notice: 'Tag was successfully created.' + else + render :new + end + end + + # PATCH/PUT /tags/1 + def update + if @tag.update(tag_params) + redirect_to @tag, notice: 'Tag was successfully updated.' + else + render :edit + end + end + + # DELETE /tags/1 + def destroy + #remove any association + ProblemTag.where(tag_id: @tag.id).destroy_all + @tag.destroy + redirect_to tags_url, notice: 'Tag was successfully destroyed.' + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_tag + @tag = Tag.find(params[:id]) + end + + # Only allow a trusted parameter "white list" through. + def tag_params + params.require(:tag).permit(:name, :description, :public) + end +end diff --git a/app/controllers/tasks_controller.rb b/app/controllers/tasks_controller.rb --- a/app/controllers/tasks_controller.rb +++ b/app/controllers/tasks_controller.rb @@ -26,7 +26,7 @@ # this has problem-level access control def download problem = Problem.find(params[:id]) - if !problem or !problem.available or !@user.can_view_problem? problem + unless @current_user.can_view_problem? problem redirect_to :action => 'index' and return end diff --git a/app/controllers/user_admin_controller.rb b/app/controllers/user_admin_controller.rb --- a/app/controllers/user_admin_controller.rb +++ b/app/controllers/user_admin_controller.rb @@ -24,6 +24,7 @@ @users = User.paginate :page => params[:page] @paginated = true end + @users = User.all @hidden_columns = ['hashed_password', 'salt', 'created_at', 'updated_at'] @contests = Contest.enabled end @@ -228,6 +229,7 @@ end end + # contest management def contests @@ -411,7 +413,7 @@ def bulk_manage begin - @users = User.where('login REGEXP ?',params[:regex]) if params[:regex] + @users = User.where('(login REGEXP ?) OR (remark REGEXP ?)',params[:regex],params[:regex]) if params[:regex] @users.count if @users #i don't know why I have to call count, but if I won't exception is not raised rescue Exception flash[:error] = 'Regular Expression is malformed' @@ -423,6 +425,8 @@ @action[:set_enable] = params[:enabled] @action[:enabled] = params[:enable] == "1" @action[:gen_password] = params[:gen_password] + @action[:add_group] = params[:add_group] + @action[:group_name] = params[:group_name] end if params[:commit] == "Perform" @@ -437,6 +441,21 @@ u.save end end + if @action[:add_group] and @action[:group_name] + @group = Group.find(@action[:group_name]) + ok = [] + failed = [] + @users.each do |user| + begin + @group.users << user + ok << user.login + rescue => e + failed << user.login + end + end + flash[:success] = "The following users are added to the 'group #{@group.name}': " + ok.join(', ') if ok.count > 0 + flash[:alert] = "The following users are already in the 'group #{@group.name}': " + failed.join(', ') if failed.count > 0 + end end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -85,11 +85,10 @@ end def format_short_time(time) - now = Time.now.gmtime + now = Time.zone.now st = '' - if (time.yday != now.yday) or - (time.year != now.year) - st = time.strftime("%x ") + if (time.yday != now.yday) or (time.year != now.year) + st = time.strftime("%d/%m/%y ") end st + time.strftime("%X") end @@ -100,6 +99,10 @@ return Time.at(d).gmtime.strftime("%X") end + def format_full_time_ago(time) + st = time_ago_in_words(time) + ' ago (' + format_short_time(time) + ')' + end + def read_textfile(fname,max_size=2048) begin File.open(fname).read(max_size) @@ -200,7 +203,7 @@ BOOTSTRAP_FLASH_MSG = { success: 'alert-success', error: 'alert-danger', - alert: 'alert-block', + alert: 'alert-danger', notice: 'alert-info' } diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb new file mode 100644 --- /dev/null +++ b/app/helpers/groups_helper.rb @@ -0,0 +1,2 @@ +module GroupsHelper +end diff --git a/app/helpers/tags_helper.rb b/app/helpers/tags_helper.rb new file mode 100644 --- /dev/null +++ b/app/helpers/tags_helper.rb @@ -0,0 +1,2 @@ +module TagsHelper +end diff --git a/app/models/grader_configuration.rb b/app/models/grader_configuration.rb --- a/app/models/grader_configuration.rb +++ b/app/models/grader_configuration.rb @@ -12,6 +12,7 @@ MULTIPLE_IP_LOGIN_KEY = 'right.multiple_ip_login' VIEW_TESTCASE = 'right.view_testcase' SINGLE_USER_KEY = 'system.single_user_mode' + SYSTEM_USE_PROBLEM_GROUP = 'system.use_problem_group' cattr_accessor :config_cache cattr_accessor :task_grading_info_cache @@ -119,6 +120,10 @@ def self.analysis_mode? return get(SYSTEM_MODE_CONF_KEY) == 'analysis' end + + def self.use_problem_group? + return get(SYSTEM_USE_PROBLEM_GROUP) + end def self.contest_time_limit contest_time_str = GraderConfiguration[CONTEST_TIME_LIMIT_KEY] diff --git a/app/models/group.rb b/app/models/group.rb new file mode 100644 --- /dev/null +++ b/app/models/group.rb @@ -0,0 +1,13 @@ +class Group < ActiveRecord::Base + has_many :groups_problems, class_name: GroupProblem + has_many :problems, :through => :groups_problems + + has_many :groups_users, class_name: GroupUser + has_many :users, :through => :groups_users + + #has_and_belongs_to_many :problems + #has_and_belongs_to_many :users + + +end + diff --git a/app/models/group_problem.rb b/app/models/group_problem.rb new file mode 100644 --- /dev/null +++ b/app/models/group_problem.rb @@ -0,0 +1,7 @@ +class GroupProblem < ActiveRecord::Base + self.table_name = 'groups_problems' + + belongs_to :problem + belongs_to :group + validates_uniqueness_of :problem_id, scope: :group_id, message: ->(object, data) { "'#{Problem.find(data[:value]).full_name}' is already in the group" } +end diff --git a/app/models/group_user.rb b/app/models/group_user.rb new file mode 100644 --- /dev/null +++ b/app/models/group_user.rb @@ -0,0 +1,7 @@ +class GroupUser < ActiveRecord::Base + self.table_name = 'groups_users' + + belongs_to :user + belongs_to :group + validates_uniqueness_of :user_id, scope: :group_id, message: ->(object, data) { "'#{User.find(data[:value]).full_name}' is already in the group" } +end diff --git a/app/models/problem.rb b/app/models/problem.rb --- a/app/models/problem.rb +++ b/app/models/problem.rb @@ -2,6 +2,14 @@ belongs_to :description has_and_belongs_to_many :contests, :uniq => true + + #has_and_belongs_to_many :groups + has_many :groups_problems, class_name: GroupProblem + has_many :groups, :through => :groups_problems + + has_many :problems_tags, class_name: ProblemTag + has_many :tags, through: :problems_tags + has_many :test_pairs, :dependent => :delete_all has_many :testcases, :dependent => :destroy diff --git a/app/models/problem_tag.rb b/app/models/problem_tag.rb new file mode 100644 --- /dev/null +++ b/app/models/problem_tag.rb @@ -0,0 +1,8 @@ +class ProblemTag < ActiveRecord::Base + self.table_name = 'problems_tags' + + belongs_to :problem + belongs_to :tag + + validates_uniqueness_of :problem_id, scope: :tag_id, message: ->(object, data) { "'#{Problem.find(data[:value]).full_name}' is already has this tag" } +end diff --git a/app/models/submission.rb b/app/models/submission.rb --- a/app/models/submission.rb +++ b/app/models/submission.rb @@ -34,8 +34,8 @@ def self.find_in_range_by_user_and_problem(user_id, problem_id,since_id,until_id) records = Submission.where(problem_id: problem_id,user_id: user_id) - records = records.where('id >= ?',since_id) if since_id > 0 - records = records.where('id <= ?',until_id) if until_id > 0 + records = records.where('id >= ?',since_id) if since_id and since_id > 0 + records = records.where('id <= ?',until_id) if until_id and until_id > 0 records.all end @@ -137,7 +137,7 @@ # for output_only tasks return if self.problem!=nil and self.problem.output_only - + if self.language==nil errors.add('source',"Cannot detect language. Did you submit a correct source file?") unless self.language!=nil end @@ -147,8 +147,12 @@ return if self.source==nil if self.problem==nil errors.add('problem',"must be specified.") - elsif (!self.problem.available) and (self.new_record?) - errors.add('problem',"must be valid.") + else + #admin always have right + return if self.user.admin? + + #check if user has the right to submit the problem + errors.add('problem',"must be valid.") if (!self.user.available_problems.include?(self.problem)) and (self.new_record?) end end diff --git a/app/models/tag.rb b/app/models/tag.rb new file mode 100644 --- /dev/null +++ b/app/models/tag.rb @@ -0,0 +1,4 @@ +class Tag < ActiveRecord::Base + has_many :problems_tags, class_name: ProblemTag + has_many :problems, through: :problems_tags +end diff --git a/app/models/user.rb b/app/models/user.rb --- a/app/models/user.rb +++ b/app/models/user.rb @@ -8,6 +8,10 @@ has_and_belongs_to_many :roles + #has_and_belongs_to_many :groups + has_many :groups_users, class_name: GroupUser + has_many :groups, :through => :groups_users + has_many :test_requests, -> {order(submitted_at: DESC)} has_many :messages, -> { order(created_at: DESC) }, @@ -240,9 +244,14 @@ return true end + #get a list of available problem def available_problems if not GraderConfiguration.multicontests? - return Problem.available_problems + if GraderConfiguration.use_problem_group? + return available_problems_in_group + else + return Problem.available_problems + end else contest_problems = [] pin = {} @@ -259,12 +268,32 @@ end end + def available_problems_in_group + problem = [] + self.groups.each do |group| + group.problems.where(available: true).each { |p| problem << p } + end + problem.uniq! + if problem + problem.sort! do |a,b| + case + when a.date_added < b.date_added + 1 + when a.date_added > b.date_added + -1 + else + a.name <=> b.name + end + end + return problem + else + return [] + end + end + def can_view_problem?(problem) - if not GraderConfiguration.multicontests? - return problem.available - else - return problem_in_user_contests? problem - end + return true if admin? + return available_problems.include? problem end def self.clear_last_login diff --git a/app/views/announcements/show.html.erb b/app/views/announcements/show.html.erb --- a/app/views/announcements/show.html.erb +++ b/app/views/announcements/show.html.erb @@ -15,7 +15,7 @@

Body: - <%=h @announcement.body %> + <%=h markdown(@announcement.body) %>

diff --git a/app/views/application/_submission_short.html.haml b/app/views/application/_submission_short.html.haml --- a/app/views/application/_submission_short.html.haml +++ b/app/views/application/_submission_short.html.haml @@ -1,13 +1,15 @@ - - if submission.nil? = "-" - else + %strong= "Submission ID:" + = submission.id + %br - unless submission.graded_at - = t 'main.submitted_at' - = format_short_time(submission.submitted_at.localtime) + %strong= t 'main.submitted_at:' + = format_full_time_ago(submission.submitted_at.localtime) - else - %strong= t 'main.graded_at' - = "#{format_short_time(submission.graded_at.localtime)} " + %strong= t 'main.graded_at:' + = format_full_time_ago(submission.graded_at.localtime) %br - if GraderConfiguration['ui.show_score'] %strong=t 'main.score' @@ -17,10 +19,10 @@ = submission.grader_comment = "]" %br - %strong View: - - if GraderConfiguration.show_grading_result - = link_to '[detailed result]', :action => 'result', :id => submission.id - = link_to "#{t 'main.cmp_msg'}", {:action => 'compiler_msg', :id => submission.id}, {popup: true,class: 'btn btn-xs btn-info'} + %strong View: + - if GraderConfiguration.show_grading_result + = link_to '[detailed result]', :action => 'result', :id => submission.id + = link_to "#{t 'main.cmp_msg'}", {:action => 'compiler_msg', :id => submission.id}, {popup: true,class: 'btn btn-xs btn-info'} if submission.graded_at = link_to "#{t 'main.src_link'}", download_submission_path(submission.id), class: 'btn btn-xs btn-info' = link_to "#{t 'main.submissions_link'}", problem_submissions_path(problem_id), class: 'btn btn-xs btn-info' diff --git a/app/views/contests/edit.html.erb b/app/views/contests/edit.html.erb deleted file mode 100644 --- a/app/views/contests/edit.html.erb +++ /dev/null @@ -1,29 +0,0 @@ -

Editing contest

- -<%= form_for(@contest) do |f| %> - <%= f.error_messages %> - - - - - - - - - - - - - - -
<%= f.label :name %><%= f.text_field :name %>
<%= f.label :title %><%= f.text_field :title %>
- <%= f.check_box :enabled %> - <%= f.label :enabled %> -
-

- <%= f.submit 'Update' %> -

-<% end %> - -<%= link_to 'Show', @contest %> | -<%= link_to 'Back', contests_path %> diff --git a/app/views/contests/edit.html.haml b/app/views/contests/edit.html.haml new file mode 100644 --- /dev/null +++ b/app/views/contests/edit.html.haml @@ -0,0 +1,20 @@ +%h1 Editing contest += form_for(@contest) do |f| + = f.error_messages + %table + %tr + %td= f.label :name + %td= f.text_field :name + %tr + %td= f.label :title + %td= f.text_field :title + %tr + %td + %td + = f.check_box :enabled + = f.label :enabled + %p + = f.submit 'Update' += link_to 'Show', @contest +| += link_to 'Back', contests_path diff --git a/app/views/contests/index.html.erb b/app/views/contests/index.html.erb deleted file mode 100644 --- a/app/views/contests/index.html.erb +++ /dev/null @@ -1,29 +0,0 @@ -

Listing contests

- -
- Go back to: [<%= link_to 'contest management', :controller => 'contest_management', :action => 'index' %>] -
- - - - - - - - -<% @contests.each do |contest| %> - <% @contest = contest %> - - - - - - - - -<% end %> -
NameTitleEnabled
<%= in_place_editor_field :contest, :name, {}, :rows => 1 %><%= in_place_editor_field :contest, :title, {}, :rows => 1 %><%= in_place_editor_field :contest, :enabled, {}, :rows => 1 %><%= link_to 'Show', contest %><%= link_to 'Edit', edit_contest_path(contest) %><%= link_to 'Destroy', contest, :confirm => 'Are you sure?', :method => :delete %>
- -
- -<%= link_to 'New contest', new_contest_path %> diff --git a/app/views/contests/index.html.haml b/app/views/contests/index.html.haml new file mode 100644 --- /dev/null +++ b/app/views/contests/index.html.haml @@ -0,0 +1,27 @@ +%h1 Listing contests +.infobox + %b Go back to: + [#{link_to 'contest management', :controller => 'contest_management', :action => 'index'}] +%p= link_to 'New contest', new_contest_path, class: 'btn btn-success' +%table.table.table-striped + %tr + %th Name + %th Title + %th Enabled + %th + %th + %th + + - @contests.each do |contest| + - @contest = contest + %tr + -#%td= in_place_editor_field :contest, :name, {}, :rows => 1 + -#%td= in_place_editor_field :contest, :title, {}, :rows => 1 + -#%td= in_place_editor_field :contest, :enabled, {}, :rows => 1 + %td= best_in_place @contest, :name + %td= best_in_place @contest, :title + %td= best_in_place @contest, :enabled + %td= link_to 'Show', contest + %td= link_to 'Edit', edit_contest_path(contest) + %td= link_to 'Destroy', contest, :confirm => 'Are you sure?', :method => :delete +%br/ diff --git a/app/views/contests/new.html.erb b/app/views/contests/new.html.erb deleted file mode 100644 --- a/app/views/contests/new.html.erb +++ /dev/null @@ -1,23 +0,0 @@ -

New contest

- -<%= form_for(@contest) do |f| %> - <%= f.error_messages %> - -

- <%= f.label :name %>
- <%= f.text_field :name %> -

-

- <%= f.label :title %>
- <%= f.text_field :title %> -

-

- <%= f.label :enabled %>
- <%= f.check_box :enabled %> -

-

- <%= f.submit 'Create' %> -

-<% end %> - -<%= link_to 'Back', contests_path %> diff --git a/app/views/contests/new.html.haml b/app/views/contests/new.html.haml new file mode 100644 --- /dev/null +++ b/app/views/contests/new.html.haml @@ -0,0 +1,18 @@ +%h1 New contest += form_for(@contest) do |f| + = f.error_messages + %p + = f.label :name + %br/ + = f.text_field :name + %p + = f.label :title + %br/ + = f.text_field :title + %p + = f.label :enabled + %br/ + = f.check_box :enabled + %p + = f.submit 'Create' += link_to 'Back', contests_path diff --git a/app/views/contests/show.html.erb b/app/views/contests/show.html.erb deleted file mode 100644 --- a/app/views/contests/show.html.erb +++ /dev/null @@ -1,14 +0,0 @@ -

Contest: <%=h @contest.title %>

- -
- Go back to: [<%= link_to 'contest management', :controller => 'contest_management', :action => 'index' %>] -
- -

- Enabled: - <%=h @contest.enabled %> -

- - -<%= link_to 'Edit', edit_contest_path(@contest) %> | -<%= link_to 'Back', contests_path %> diff --git a/app/views/contests/show.html.haml b/app/views/contests/show.html.haml new file mode 100644 --- /dev/null +++ b/app/views/contests/show.html.haml @@ -0,0 +1,11 @@ +%h1 + Contest: #{h @contest.title} +.infobox + %b Go back to: + [#{link_to 'contest management', :controller => 'contest_management', :action => 'index'}] +%p + %b Enabled: + = h @contest.enabled += link_to 'Edit', edit_contest_path(@contest) +| += link_to 'Back', contests_path diff --git a/app/views/graders/submission.html.haml b/app/views/graders/submission.html.haml deleted file mode 100644 --- a/app/views/graders/submission.html.haml +++ /dev/null @@ -1,87 +0,0 @@ -%h1= "Submission: #{@submission.id}" - -%textarea#data{style: "display:none;"} - :preserve - #{@submission.source} - -//%div.highlight{:style => "border: 1px solid black;"} -//=@formatted_code.html_safe -.containter - .row - .col-md-7 - %h2 Source Code - .col-md-5 - %h2 Stat - .row - .col-md-7 - %div#editor{ style: "font-size: 14px; height: 400px; border-radius:5px;" } - :javascript - e = ace.edit("editor") - e.setOptions({ maxLines: Infinity }) - e.setValue($("#data").text()) - e.gotoLine(1) - e.getSession().setMode("#{get_ace_mode(@submission.language)}") - e.setReadOnly(true) - .col-md-5 - %table.table.table-striped - %tr - %td.text-right - %strong User - %td - - if @submission.user - = link_to "(#{@submission.user.login})", controller: "users", action: "profile", id: @submission.user - = @submission.user.full_name - - else - = "(n/a)" - %tr - %td.text-right - %strong Task - %td - - if @submission.problem!=nil - = link_to "(#{@submission.problem.name})", controller: "problems", action: "stat", id: @submission.problem - = @submission.problem.full_name - - else - = "(n/a)" - %tr - %td.text-right - %strong Tries - %td= @submission.number - %tr - %td.text-right - %strong Language - %td= @submission.language.pretty_name - %tr - %td.text-right - %strong Submitted - %td #{time_ago_in_words(@submission.submitted_at)} ago (at #{@submission.submitted_at.to_formatted_s(:long)}) - %tr - %td.text-right - %strong Graded - - if @submission.graded_at - %td #{time_ago_in_words(@submission.graded_at)} ago (at #{@submission.graded_at.to_formatted_s(:long)}) - - else - %td - - %tr - %td.text-right - %strong Points - %td #{@submission.points}/#{@submission.problem.full_score} - %tr - %td.text-right - %strong Comment - %td #{@submission.grader_comment} - %tr - %td.text-right - %strong Runtime (s) - %td #{@submission.max_runtime} - %tr - %td.text-right - %strong Memory (kb) - %td #{@submission.peak_memory} - - if session[:admin] - %tr - %td.text-right - %strong IP - %td #{@submission.ip_address} - - - diff --git a/app/views/graders/task.html.haml b/app/views/graders/task.html.haml --- a/app/views/graders/task.html.haml +++ b/app/views/graders/task.html.haml @@ -9,7 +9,7 @@ %br/ = "Submission: #{@task.submission_id}" - if @task.submission !=nil - = link_to '[view submission]', :action => 'submission', :id => @task.submission.id + = link_to '[view submission]', submission_path( @task.submission.id ) %br/ = "Submitted at: #{format_short_time(@task.created_at)}" %br/ diff --git a/app/views/groups/_form.html.haml b/app/views/groups/_form.html.haml new file mode 100644 --- /dev/null +++ b/app/views/groups/_form.html.haml @@ -0,0 +1,16 @@ += form_for @group do |f| + - if @group.errors.any? + #error_explanation + %h2= "#{pluralize(@group.errors.count, "error")} prohibited this group from being saved:" + %ul + - @group.errors.full_messages.each do |msg| + %li= msg + + .form-group.field + = f.label :name + = f.text_field :name, class: 'form-control' + .form-group.field + = f.label :description + = f.text_field :description, class: 'form-control' + .form-group.actions + = f.submit 'Save', class: 'btn btn-primary' diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml new file mode 100644 --- /dev/null +++ b/app/views/groups/edit.html.haml @@ -0,0 +1,7 @@ +%h1 Editing group + += render 'form' + += link_to 'Show', @group +\| += link_to 'Back', groups_path diff --git a/app/views/groups/index.html.haml b/app/views/groups/index.html.haml new file mode 100644 --- /dev/null +++ b/app/views/groups/index.html.haml @@ -0,0 +1,22 @@ +%h1 Groups + +%p + = link_to 'New Group', new_group_path, class: 'btn btn-primary' +%table.table.table-hover + %thead + %tr + %th Name + %th Description + %th + %th + + %tbody + - @groups.each do |group| + %tr + %td= group.name + %td= group.description + %td= link_to 'View', group, class: 'btn btn-default' + %td= link_to 'Destroy', group, :method => :delete, :data => { :confirm => 'Are you sure?' }, class: 'btn btn-danger' + +%br + diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml new file mode 100644 --- /dev/null +++ b/app/views/groups/new.html.haml @@ -0,0 +1,5 @@ +%h1 New group + += render 'form' + += link_to 'Back', groups_path diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml new file mode 100644 --- /dev/null +++ b/app/views/groups/show.html.haml @@ -0,0 +1,73 @@ +%p + %b Name: + = @group.name +%p + %b Description: + = @group.description + +%br += link_to 'Edit', edit_group_path(@group) +\| += link_to 'Back', groups_path + +.row + .col-md-12 + %h1 Group details +.row + .col-md-6 + .panel.panel-default + .panel-heading + .panel-title Users in this group + .panel-body + =form_tag add_user_group_path(@group), class: 'form-inline' do + .form-group + =label_tag :user_id, "User" + =select_tag :user_id, options_from_collection_for_select(User.all,'id','full_name'), class: 'select2' + =submit_tag "Add",class: 'btn btn-primary' + + + %table.table.table-hover + %thead + %tr + %th Login + %th Full name + %th Remark + %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' + + %tbody + - @group.users.each do |user| + %tr + %td= user.login + %td= user.full_name + %td= user.remark + %td= link_to 'Remove', remove_user_group_path(@group,user), :method => :delete, :data => { :confirm => "Remove #{user.full_name}?" }, class: 'btn btn-danger btn-sm' + .col-md-6 + .panel.panel-default + .panel-heading + .panel-title Problems + .panel-body + + =form_tag add_problem_group_path(@group), class: 'form-inline' do + .form-group + =label_tag :problem_id, "Problem" + =select_tag :problem_id, options_from_collection_for_select(Problem.all,'id','full_name'), class: 'select2' + =submit_tag "Add",class: 'btn btn-primary' + + + %table.table.table-hover + %thead + %tr + %th name + %th Full name + %th Full score + %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' + + %tbody + - @group.problems.each do |problem| + %tr + %td= problem.name + %td= problem.full_name + %td= problem.full_score + %td= link_to 'Remove', remove_problem_group_path(@group,problem), :method => :delete, :data => { :confirm => "Remove #{problem.full_name}?" }, class: 'btn btn-danger btn-sm' + + diff --git a/app/views/layouts/_header.html.haml b/app/views/layouts/_header.html.haml --- a/app/views/layouts/_header.html.haml +++ b/app/views/layouts/_header.html.haml @@ -46,7 +46,9 @@ %ul.dropdown-menu = add_menu( 'Announcements', 'announcements', 'index') = add_menu( 'Problems', 'problems', 'index') + = add_menu( 'Tags', 'tags', 'index') = add_menu( 'Users', 'user_admin', 'index') + = add_menu( 'User Groups', 'groups', 'index') = add_menu( 'Graders', 'graders', 'list') = add_menu( 'Message ', 'messages', 'console') %li.divider{role: 'separator'} diff --git a/app/views/main/_login_box.html.haml b/app/views/main/_login_box.html.haml --- a/app/views/main/_login_box.html.haml +++ b/app/views/main/_login_box.html.haml @@ -12,22 +12,26 @@ %hr/ %div{ :style => "border: solid 1px gray; padding: 4px; background: #eeeeff;"} - = form_tag login_login_path do - %table - %tr - %td{:align => "right"} - ="#{t 'login_label'}:" - %td= text_field_tag 'login' - %tr - %td{:align => "right"} - ="#{t 'password_label'}:" - %td= password_field_tag - - unless GraderConfiguration['right.bypass_agreement'] - %tr - %td{:align => "right"}= check_box_tag 'accept_agree' - %td ยอมรับข้อตกลงการใช้งาน - - = submit_tag t('login.login_submit') + = form_tag login_login_path, {class: 'form-horizontal'} do + .form-group + =label_tag :login, "Login",class: 'col-sm-3 control-label' + .col-sm-9 + =text_field_tag :login, nil, class: 'form-control' + .form-group + =label_tag :password, "Password", class: 'col-sm-3 control-label' + .col-sm-9 + =password_field_tag :password, nil, class: 'form-control' + - unless GraderConfiguration['right.bypass_agreement'] + .form-group + .col-sm-offset-3.col-sm-9 + .checkbox + %label + = check_box_tag 'accept_agree' + ยอมรับข้อตกลงการใช้งาน + + .form-group + .col-sm-offset-3.col-sm-9 + = submit_tag t('login.login_submit'), class: 'btn btn-primary' %br/ - if GraderConfiguration['system.online_registration'] diff --git a/app/views/main/_problem.html.haml b/app/views/main/_problem.html.haml --- a/app/views/main/_problem.html.haml +++ b/app/views/main/_problem.html.haml @@ -11,7 +11,7 @@ = link_to_description_if_any "[#{t 'main.problem_desc'}] ".html_safe, problem %td = @prob_submissions[problem.id][:count] - = link_to "[subs]", main_submission_path(problem.id) + -#= link_to "[subs]", main_submission_path(problem.id) %td = render :partial => 'submission_short', :locals => {:submission => @prob_submissions[problem.id][:submission], :problem_name => problem.name, :problem_id => problem.id } diff --git a/app/views/main/_submission_short.html.haml b/app/views/main/_submission_short.html.haml --- a/app/views/main/_submission_short.html.haml +++ b/app/views/main/_submission_short.html.haml @@ -13,7 +13,7 @@ %strong=t 'main.score' = "#{(submission.points*100/submission.problem.full_score).to_i} " = " [" - %tt + %tt.grader-comment = submission.grader_comment = "]" %br diff --git a/app/views/main/login.html.haml b/app/views/main/login.html.haml --- a/app/views/main/login.html.haml +++ b/app/views/main/login.html.haml @@ -1,12 +1,11 @@ %h1= GraderConfiguration['ui.front.title'] -%table - %tr - %td - - if @announcements.length!=0 - .announcementbox{:style => 'margin-top: 0px'} - %span{:class => 'title'} - Announcements - = render :partial => 'announcement', :collection => @announcements - %td{:style => 'vertical-align: top; width: 40%; padding-left: 20px;'} - = render :partial => 'login_box' +.row + .col-md-6 + - if @announcements.length!=0 + .announcementbox{:style => 'margin-top: 0px'} + %span{:class => 'title'} + Announcements + = render :partial => 'announcement', :collection => @announcements + .col-md-4{style: "padding-left: 20px;"} + = render :partial => 'login_box' diff --git a/app/views/problems/_form.html.haml b/app/views/problems/_form.html.haml --- a/app/views/problems/_form.html.haml +++ b/app/views/problems/_form.html.haml @@ -12,6 +12,9 @@ %label{:for => "problem_full_score"} Full score = text_field 'problem', 'full_score', class: 'form-control' .form-group + %label{:for => "problem_full_score"} Tags + = collection_select(:problem, :tag_ids, Tag.all, :id, :name, {}, {multiple: true, class: 'form-control select2'}) +.form-group %label{:for => "problem_date_added"} Date added = date_select 'problem', 'date_added', class: 'form-control' - # TODO: these should be put in model Problem, but I can't think of diff --git a/app/views/problems/index.html.haml b/app/views/problems/index.html.haml --- a/app/views/problems/index.html.haml +++ b/app/views/problems/index.html.haml @@ -1,10 +1,10 @@ - content_for :head do = stylesheet_link_tag 'problems' -%h1 Listing problems +%h1 Problems %p - = link_to 'New problem', new_problem_path, class: 'btn btn-default btn-sm' - = link_to 'Manage problems', { action: 'manage'}, class: 'btn btn-default btn-sm' - = link_to 'Import problems', {:action => 'import'}, class: 'btn btn-default btn-sm' + = link_to 'Import problems', {:action => 'import'}, class: 'btn btn-success btn-sm' + = link_to 'New problem', new_problem_path, class: 'btn btn-success btn-sm' + = link_to 'Bulk Manage', { action: 'manage'}, class: 'btn btn-info btn-sm' = link_to 'Turn off all problems', {:action => 'turn_all_off'}, class: 'btn btn-default btn-sm' = link_to 'Turn on all problems', {:action => 'turn_all_on'}, class: 'btn btn-default btn-sm' .submitbox @@ -21,6 +21,10 @@ %th Name %th Full name %th.text-right Full score + %th Tags + %th + Submit + %sup{class: 'text-primary',data: {toggle: 'tooltip'}, title: 'Admin can always submit to any problem' } [?] %th Date added %th.text-center Avail? @@ -37,8 +41,15 @@ %tr{:class => "#{(problem.available) ? "success" : "danger"}", :id => "prob-#{problem.id}", :name => "prob-#{problem.id}"} - @problem=problem %td= problem.name #in_place_editor_field :problem, :name, {}, :rows=>1 - %td= problem.full_name #in_place_editor_field :problem, :full_name, {}, :rows=>1 + %td + = problem.full_name #in_place_editor_field :problem, :full_name, {}, :rows=>1 + = link_to_description_if_any "[#{t 'main.problem_desc'}] ".html_safe, problem %td.text-right= problem.full_score #in_place_editor_field :problem, :full_score, {}, :rows=>1 + %td + - problem.tags.each do |t| + - #%button.btn.btn-default.btn-xs= t.name + %span.label.label-default= t.name + %td= link_to "Submit", direct_edit_problem_submissions_path(problem,@current_user.id), class: 'btn btn-xs btn-primary' %td= problem.date_added %td= toggle_button(@problem.available?, toggle_problem_path(@problem), "problem-avail-#{@problem.id}") %td= toggle_button(@problem.view_testcase?, toggle_view_testcase_problem_path(@problem), "problem-view-testcase-#{@problem.id}") diff --git a/app/views/problems/manage.html.haml b/app/views/problems/manage.html.haml --- a/app/views/problems/manage.html.haml +++ b/app/views/problems/manage.html.haml @@ -36,50 +36,83 @@ %h1 Manage problems -%p= link_to '[Back to problem list]', :action => 'list' +%p= link_to '[Back to problem list]', problems_path = form_tag :action=>'do_manage' do - .submitbox - What do you want to do to the selected problem? - %br/ - (You can shift-click to select a range of problems) - %ul - %li - Change date added to - = select_date Date.current, :prefix => 'date_added' -     - = submit_tag 'Change', :name => 'change_date_added' - %li - Set available to - = submit_tag 'True', :name => 'enable_problem' - = submit_tag 'False', :name => 'disable_problem' + .panel.panel-primary + .panel-heading + Action + .panel-body + .submit-box + What do you want to do to the selected problem? + %br/ + (You can shift-click to select a range of problems) + %ul.form-inline + %li + Change "Date added" to + .input-group.date + = text_field_tag :date_added, class: 'form-control' + %span.input-group-addon + %span.glyphicon.glyphicon-calendar + -# = select_date Date.current, :prefix => 'date_added' +     + = submit_tag 'Change', :name => 'change_date_added', class: 'btn btn-primary btn-sm' + %li + Set "Available" to + = submit_tag 'True', :name => 'enable_problem', class: 'btn btn-primary btn-sm' + = submit_tag 'False', :name => 'disable_problem', class: 'btn btn-primary btn-sm' - - if GraderConfiguration.multicontests? - %li - Add to - = select("contest","id",Contest.all.collect {|c| [c.title, c.id]}) - = submit_tag 'Add', :name => 'add_to_contest' + - if GraderConfiguration.multicontests? + %li + Add selected problems to contest + = select("contest","id",Contest.all.collect {|c| [c.title, c.id]}) + = submit_tag 'Add', :name => 'add_to_contest', class: 'btn btn-primary btn-sm' + %li + Add selected problems to user group + = select_tag "group_id", options_from_collection_for_select( Group.all, 'id','name',params[:group_name]), id: 'group_name',class: 'select2' + = submit_tag 'Add', name: 'add_group', class: 'btn btn-primary' + %li + Add the following tags to the selected problems + = 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"} + = submit_tag 'Add', name: 'add_tags', class: 'btn btn-primary' - %table - %tr{style: "text-align: left;"} - %th= check_box_tag 'select_all' - %th Name - %th Full name - %th Available - %th Date added - - if GraderConfiguration.multicontests? - %th Contests + %table.table.table-hover.datatable + %thead + %tr{style: "text-align: left;"} + %th= check_box_tag 'select_all' + %th Name + %th Full name + %th Tags + %th Available + %th Date added + - if GraderConfiguration.multicontests? + %th Contests - - num = 0 - - for problem in @problems - - num += 1 - %tr{:id => "row-prob-#{problem.id}", :name=> "prob-#{problem.id}"} - %td= check_box_tag "prob-#{problem.id}-#{num}" - %td= problem.name - %td= problem.full_name - %td= problem.available - %td= problem.date_added - - if GraderConfiguration.multicontests? + %tbody + - num = 0 + - for problem in @problems + - num += 1 + %tr{:id => "row-prob-#{problem.id}", :name=> "prob-#{problem.id}"} + %td= check_box_tag "prob-#{problem.id}-#{num}" + %td= problem.name + %td= problem.full_name %td - - problem.contests.each do |contest| - = "(#{contest.name} [#{link_to 'x', :action => 'remove_contest', :id => problem.id, :contest_id => contest.id }])" + - problem.tags.each do |t| + %span.label.label-default= t.name + %td= problem.available + %td= problem.date_added + - if GraderConfiguration.multicontests? + %td + - problem.contests.each do |contest| + = "(#{contest.name} [#{link_to 'x', :action => 'remove_contest', :id => problem.id, :contest_id => contest.id }])" + +:javascript + $('.input-group.date').datetimepicker({ + format: 'DD/MMM/YYYY', + showTodayButton: true, + widgetPositioning: {horizontal: 'auto', vertical: 'bottom'}, + + }); + $('.datatable').DataTable({ + paging: false + }); diff --git a/app/views/problems/stat.html.haml b/app/views/problems/stat.html.haml --- a/app/views/problems/stat.html.haml +++ b/app/views/problems/stat.html.haml @@ -25,13 +25,14 @@ %h2 Submissions - if @submissions and @submissions.count > 0 - %table.info#main_table + %table#main_table.table.table-condensed.table-striped %thead - %tr.info-head + %tr %th ID %th Login %th Name %th Submitted_at + %th language %th Points %th comment %th IP @@ -40,14 +41,19 @@ - @submissions.each do |sub| - next unless sub.user - row_odd,curr = !row_odd, sub.user if curr != sub.user - %tr{class: row_odd ? "info-odd" : "info-even"} + %tr %td= link_to sub.id, submission_path(sub) %td= link_to sub.user.login, stat_user_path(sub.user) %td= sub.user.full_name - %td= time_ago_in_words(sub.submitted_at) + " ago" + %td{data: {order: sub.submitted_at}}= time_ago_in_words(sub.submitted_at) + " ago" + %td= sub.language.name %td= sub.points %td.fix-width= sub.grader_comment %td= sub.ip_address - else No submission +:javascript + $("#main_table").DataTable({ + paging: false + }); diff --git a/app/views/report/_score_table.html.haml b/app/views/report/_score_table.html.haml --- a/app/views/report/_score_table.html.haml +++ b/app/views/report/_score_table.html.haml @@ -12,6 +12,9 @@ %th.text-right Total %th.text-right Passed %tbody + - sum = Array.new(@scorearray[0].count,0) + - nonzero = Array.new(@scorearray[0].count,0) + - full = Array.new(@scorearray[0].count,0) - @scorearray.each do |sc| %tr - total,num_passed = 0,0 @@ -27,8 +30,40 @@ %td.text-right= sc[i][0] - total += sc[i][0] - num_passed += 1 if sc[i][1] + - sum[i] += sc[i][0] + - nonzero[i] += 1 if sc[i][0] > 0 + - full[i] += 1 if sc[i][1] %td.text-right= total %td.text-right= num_passed + %tfoot + %tr + %td Summation + %td + %td + - sum.each.with_index do |s,i| + - next if i == 0 + %td.text-right= number_with_delimiter(s) + %td + %td + %tr + %td partial solver + %td + %td + - nonzero.each.with_index do |s,i| + - next if i == 0 + %td.text-right= number_with_delimiter(s) + %td + %td + %tr + %td Full solver + %td + %td + - full.each.with_index do |s,i| + - next if i == 0 + %td.text-right= number_with_delimiter(s) + %td + %td + :javascript $.bootstrapSortable(true,'reversed') diff --git a/app/views/report/max_score.html.haml b/app/views/report/max_score.html.haml --- a/app/views/report/max_score.html.haml +++ b/app/views/report/max_score.html.haml @@ -33,11 +33,11 @@ .panel-body .radio %label - = radio_button_tag 'users', 'all', true + = radio_button_tag 'users', 'all', (params[:users] == "all") All users .radio %label - = radio_button_tag 'users', 'enabled' + = radio_button_tag 'users', 'enabled', (params[:users] == "enabled") Only enabled users .row .col-md-12 diff --git a/app/views/sites/edit.html.haml b/app/views/sites/edit.html.haml --- a/app/views/sites/edit.html.haml +++ b/app/views/sites/edit.html.haml @@ -1,24 +1,36 @@ %h1 Editing site = error_messages_for :site = form_for(@site) do |f| - %p - %b Name - %br/ - = f.text_field :name - %p - %b Password - %br/ - = f.text_field :password - %p - %b Started - %br/ - = f.check_box :started - %p - %b Start time - %br/ - = f.datetime_select :start_time, :include_blank => true - %p - = f.submit "Update" + .row + .col-md-4 + .form-group.field + = f.label :name, "Name" + = f.text_field :name, class: 'form-control' + .form-group.field + = f.label :password, "Password" + = f.text_field :password, class: 'form-control' + .form-group.field + = f.label :started, "Started" + = f.check_box :started, class: 'form-control' + .form-group.field + = f.label :start_time, "Start time" + -# = f.datetime_select :start_time, :include_blank => true + .input-group.date + = f.text_field :start_time, class:'form-control' , value: (@site.start_time ? @site.start_time.strftime('%d/%b/%Y %H:%M') : '') + %span.input-group-addon + %span.glyphicon.glyphicon-calendar + .actions + = f.submit "Update", class: 'btn btn-primary' + .col-md-8 + = link_to 'Show', @site | = link_to 'Back', sites_path + + +:javascript + $('.input-group.date').datetimepicker({ + format: 'DD/MMM/YYYY HH:mm', + showTodayButton: true, + }); + diff --git a/app/views/submissions/edit.html.haml b/app/views/submissions/edit.html.haml --- a/app/views/submissions/edit.html.haml +++ b/app/views/submissions/edit.html.haml @@ -11,6 +11,7 @@ .col-md-8 %div#editor{style: 'height: 500px; border-radius: 7px; font-size: 14px;'} .col-md-4 + - # submission form = form_tag({controller: :main, :action => 'submit'}, :multipart => true, class: 'form') do = hidden_field_tag 'editor_text', @source @@ -25,14 +26,16 @@ .form-group = submit_tag 'Submit', class: 'btn btn-success', id: 'live_submit', data: {confirm: "Submitting this source code for task #{@problem.long_name}?"} - .panel.panel-info + - # latest submission status + .panel{class: (@submission && @submission.graded_at) ? "panel-info" : "panel-warning"} .panel-heading Latest Submission Status = link_to "Refresh",get_latest_submission_status_submissions_path(@submission.user,@problem), class: "btn btn-default btn-sm", remote: true if @submission .panel-body - - if @submission - = render :partial => 'submission_short', - :locals => {submission: @submission, problem_name: @problem.name, problem_id: @problem.id } + %div#latest_status + - if @submission + = render :partial => 'submission_short', + :locals => {submission: @submission, problem_name: @problem.name, problem_id: @problem.id } .row .col-md-12 %h2 Console diff --git a/app/views/submissions/get_latest_submission_status.js.haml b/app/views/submissions/get_latest_submission_status.js.haml --- a/app/views/submissions/get_latest_submission_status.js.haml +++ b/app/views/submissions/get_latest_submission_status.js.haml @@ -1,2 +1,2 @@ :plain - $("#latest_status").html("#{j render({partial: 'submission_short', locals: {submission: @submission, problem_name: @problem.name}})}") + $("#latest_status").html("#{j render({partial: 'submission_short', locals: {submission: @submission, problem_name: @problem.name, problem_id: @problem.id}})}") diff --git a/app/views/tags/_form.html.haml b/app/views/tags/_form.html.haml new file mode 100644 --- /dev/null +++ b/app/views/tags/_form.html.haml @@ -0,0 +1,22 @@ += form_for @tag do |f| + - if @tag.errors.any? + #error_explanation + %h2= "#{pluralize(@tag.errors.count, "error")} prohibited this tag from being saved:" + %ul + - @tag.errors.full_messages.each do |msg| + %li= msg + + .row + .col-md-6 + .form-group.field + = f.label :name + = f.text_field :name, class: 'form-control' + .form-group.field + = f.label :description + = f.text_area :description, class: 'form-control' + .form-group.field + = f.label :public + = f.text_field :public, class: 'form-control' + .actions + = f.submit 'Save', class: 'btn btn-primary' + .col-md-6 diff --git a/app/views/tags/edit.html.haml b/app/views/tags/edit.html.haml new file mode 100644 --- /dev/null +++ b/app/views/tags/edit.html.haml @@ -0,0 +1,7 @@ +%h1 Editing tag + += render 'form' + += link_to 'Show', @tag +\| += link_to 'Back', tags_path diff --git a/app/views/tags/index.html.haml b/app/views/tags/index.html.haml new file mode 100644 --- /dev/null +++ b/app/views/tags/index.html.haml @@ -0,0 +1,26 @@ +%h1 Tags + += link_to 'New Tag', new_tag_path, class: 'btn btn-success' + +%table.table.table-hover + %thead + %tr + %th Name + %th Description + %th Public + %th + %th + %th + + %tbody + - @tags.each do |tag| + %tr + %td= tag.name + %td= tag.description + %td= tag.public + %td= link_to 'Show', tag + %td= link_to 'Edit', edit_tag_path(tag) + %td= link_to 'Destroy', tag, :method => :delete, :data => { :confirm => 'Are you sure?' } + +%br + diff --git a/app/views/tags/new.html.haml b/app/views/tags/new.html.haml new file mode 100644 --- /dev/null +++ b/app/views/tags/new.html.haml @@ -0,0 +1,5 @@ +%h1 New tag + += render 'form' + += link_to 'Back', tags_path diff --git a/app/views/tags/show.html.haml b/app/views/tags/show.html.haml new file mode 100644 --- /dev/null +++ b/app/views/tags/show.html.haml @@ -0,0 +1,15 @@ +%p#notice= notice + +%p + %b Name: + = @tag.name +%p + %b Description: + = @tag.description +%p + %b Public: + = @tag.public + += link_to 'Edit', edit_tag_path(@tag) +\| += link_to 'Back', tags_path diff --git a/app/views/user_admin/_form.html.erb b/app/views/user_admin/_form.html.erb deleted file mode 100644 --- a/app/views/user_admin/_form.html.erb +++ /dev/null @@ -1,25 +0,0 @@ -<%= error_messages_for 'user' %> - - -


-<%= text_field 'user', 'login' %>

- -


-<%= text_field 'user', 'full_name' %>

- -


-<%= password_field 'user', 'password' %>

- -


-<%= password_field 'user', 'password_confirmation' %>

- -


-<%= email_field 'user', 'email' %>

- -


-<%= text_field 'user', 'alias' %>

- -


-<%= text_field 'user', 'remark' %>

- - diff --git a/app/views/user_admin/_form.html.haml b/app/views/user_admin/_form.html.haml new file mode 100644 --- /dev/null +++ b/app/views/user_admin/_form.html.haml @@ -0,0 +1,38 @@ += error_messages_for 'user' +/ [form:user] +.form-group + %label.col-md-2.control-label{for: :login} Login + .col-md-4 + = text_field 'user', 'login', class: 'form-control' + .col-md-6 +.form-group + %label.col-md-2.control-label{for: :full_name} Full name + .col-md-4 + = text_field 'user', 'full_name', class: 'form-control' + .col-md-6 +.form-group + %label.col-md-2.control-label{for: :password} Password + .col-md-4 + = password_field 'user', 'password', class: 'form-control' + .col-md-6 +.form-group + %label.col-md-2.control-label{for: :password_confirmation} Password (confirm) + .col-md-4 + = password_field 'user', 'password_confirmation', class: 'form-control' + .col-md-6 +.form-group + %label.col-md-2.control-label{for: :email} E-mail + .col-md-4 + = email_field 'user', 'email', class: 'form-control' + .col-md-6 +.form-group + %label.col-md-2.control-label{for: :alias} Alias + .col-md-4 + = text_field 'user', 'alias', class: 'form-control' + .col-md-6 +.form-group + %label.col-md-2.control-label{for: :remark} Remark + .col-md-4 + = text_field 'user', 'remark', class: 'form-control' + .col-md-6 +/ [eoform:user] diff --git a/app/views/user_admin/bulk_manage.html.haml b/app/views/user_admin/bulk_manage.html.haml --- a/app/views/user_admin/bulk_manage.html.haml +++ b/app/views/user_admin/bulk_manage.html.haml @@ -46,6 +46,15 @@ %label.checkbox-inline = check_box_tag "gen_password", true, params[:gen_password] Generate new random password + .row.form-group + .col-md-4 + %label.checkbox-inline + = check_box_tag "add_group", true, params[:add_group] + Add users to group + %label.col-md-3.control-label.text-right Group name + .col-md-5 + = select_tag "group_name", options_from_collection_for_select( Group.all, 'id','name',params[:group_name]), id: 'group_name',class: 'form-control select2' + .row .col-md-12 diff --git a/app/views/user_admin/edit.html.haml b/app/views/user_admin/edit.html.haml --- a/app/views/user_admin/edit.html.haml +++ b/app/views/user_admin/edit.html.haml @@ -1,9 +1,11 @@ %h1 Editing user -= form_tag :action => 'update', :id => @user do += form_tag( {:action => 'update', :id => @user}, {class: 'form-horizontal'}) do = error_messages_for 'user' = render partial: "form" - = submit_tag "Edit" + .form-group + .col-md-offset-2.col-md-4 + = submit_tag "Edit", class: 'btn btn-primary' = link_to 'Show', :action => 'show', :id => @user diff --git a/app/views/user_admin/index.html.haml b/app/views/user_admin/index.html.haml --- a/app/views/user_admin/index.html.haml +++ b/app/views/user_admin/index.html.haml @@ -1,4 +1,4 @@ -%h1 Listing users +%h1 Users .panel.panel-primary .panel-title.panel-heading @@ -41,8 +41,8 @@ %p = link_to '+ New user', { :action => 'new' }, { class: 'btn btn-success '} = link_to '+ New list of users', { :action => 'new_list' }, { class: 'btn btn-success '} + = link_to 'Bulk Manage', bulk_manage_user_admin_path , { class: 'btn btn-default btn-info'} = link_to 'View administrators',{ :action => 'admin'}, { class: 'btn btn-default '} - = link_to 'Bulk Manage', bulk_manage_user_admin_path , { class: 'btn btn-default '} = link_to 'Random passwords',{ :action => 'random_all_passwords'}, { class: 'btn btn-default '} = link_to 'View active users',{ :action => 'active'}, { class: 'btn btn-default '} = link_to 'Mass mailing',{ :action => 'mass_mailing'}, { class: 'btn btn-default '} @@ -56,17 +56,17 @@ = link_to "[#{contest.name}]", :action => 'contests', :id => contest.id = link_to "[no contest]", :action => 'contests', :id => 'none' -Total #{@user_count} users | -- if !@paginated - Display all users. - \#{link_to '[show in pages]', :action => 'index', :page => '1'} -- else - Display in pages. - \#{link_to '[display all]', :action => 'index', :page => 'all'} | - \#{will_paginate @users, :container => false} +-# Total #{@user_count} users | +-# - if !@paginated +-# Display all users. +-# \#{link_to '[show in pages]', :action => 'index', :page => '1'} +-# - else +-# Display in pages. +-# \#{link_to '[display all]', :action => 'index', :page => 'all'} | +-# \#{will_paginate @users, :container => false} -%table.table.table-hover.table-condense +%table.table.table-hover.table-condense.datatable %thead %th Login %th Full name @@ -95,7 +95,12 @@ %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' %td= link_to 'Show', {:action => 'show', :id => user}, class: 'btn btn-default btn-xs btn-block' %td= link_to 'Edit', {:action => 'edit', :id => user}, class: 'btn btn-default btn-xs btn-block' - %td= link_to 'Destroy', { :action => 'destroy', :id => user }, :confirm => 'Are you sure?', :method => :delete, class: 'btn btn-danger btn-xs btn-block' + %td= link_to 'Destroy', user_admin_destroy_path(user), data: {confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-danger btn-xs btn-block' %br/ = link_to '+ New user', { :action => 'new' }, { class: 'btn btn-success '} = link_to '+ New list of users', { :action => 'new_list' }, { class: 'btn btn-success '} + +:javascript + $('.datatable').DataTable({ + 'pageLength': 50 + }); diff --git a/app/views/user_admin/new.html.erb b/app/views/user_admin/new.html.erb deleted file mode 100644 --- a/app/views/user_admin/new.html.erb +++ /dev/null @@ -1,8 +0,0 @@ -

New user

- -<%= form_tag :action => 'create' do %> - <%= render :partial => 'form' %> - <%= submit_tag "Create" %> -<% end %> - -<%= link_to 'Back', :action => 'list' %> diff --git a/app/views/user_admin/new.html.haml b/app/views/user_admin/new.html.haml new file mode 100644 --- /dev/null +++ b/app/views/user_admin/new.html.haml @@ -0,0 +1,7 @@ +%h1 New user += form_tag( {action: 'create'}, { class: 'form-horizontal'}) do + = render :partial => 'form' + .form-group + .col-md-offset-2.col-md-10 + = submit_tag "Create", class: 'btn btn-primary' += link_to 'Back', :action => 'index' diff --git a/app/views/user_admin/show.html.erb b/app/views/user_admin/show.html.erb deleted file mode 100644 --- a/app/views/user_admin/show.html.erb +++ /dev/null @@ -1,10 +0,0 @@ -

User information

- -<% for column in User.content_columns %> -

- <%= column.human_name %>: <%=h @user.send(column.name) %> -

-<% end %> - -<%= link_to 'Edit', :action => 'edit', :id => @user %> | -<%= link_to 'Back', :action => 'list' %> diff --git a/app/views/user_admin/show.html.haml b/app/views/user_admin/show.html.haml new file mode 100644 --- /dev/null +++ b/app/views/user_admin/show.html.haml @@ -0,0 +1,14 @@ +%h1 User information +- for column in User.content_columns + %p + %b + = column.human_name + \: + = h @user.send(column.name) +%p + %strong Group + \: + = @user.groups.map{ |x| link_to(x.name,group_path(x)).html_safe}.join(', ').html_safe += link_to 'Edit', :action => 'edit', :id => @user +| += link_to 'Back', :action => 'index' diff --git a/app/views/users/stat.html.haml b/app/views/users/stat.html.haml --- a/app/views/users/stat.html.haml +++ b/app/views/users/stat.html.haml @@ -36,7 +36,7 @@ =render partial: 'application/bar_graph', locals: {histogram: @histogram, param: {bar_width: 7}} -%table.tablesorter-cafe#submission_table +%table#submission_table.table.table-striped %thead %tr %th ID @@ -64,3 +64,7 @@ +:javascript + $("#submission_table").DataTable({ + paging: false + }); diff --git a/config/application.rb.SAMPLE b/config/application.rb.SAMPLE --- a/config/application.rb.SAMPLE +++ b/config/application.rb.SAMPLE @@ -65,7 +65,7 @@ config.assets.precompile += ['local_jquery.js','tablesorter-theme.cafe.css'] %w( announcements submissions configurations contests contest_management graders heartbeat login main messages problems report site sites sources tasks - test user_admin users ).each do |controller| + test user_admin users testcases).each do |controller| config.assets.precompile += ["#{controller}.js", "#{controller}.css"] end end diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb new file mode 100644 --- /dev/null +++ b/config/initializers/assets.rb @@ -0,0 +1,23 @@ +# Be sure to restart your server when you modify this file. + +# Version of your assets, change this if you want to expire all your assets. +Rails.application.config.assets.version = '1.0' + +# Add additional assets to the asset load path. +# Rails.application.config.assets.paths << Emoji.images_path +# Add Yarn node_modules folder to the asset load path. +Rails.application.config.assets.paths << Rails.root.join('node_modules') +Rails.application.config.assets.paths << Rails.root.join('vendor/assets/fonts') + +# Precompile additional assets. +# application.js, application.css, and all non-JS/CSS in the app/assets +# folder are already added. +# Rails.application.config.assets.precompile += %w( admin.js admin.css ) + +Rails.application.config.assets.precompile += ['announcement_refresh.js','effects.js','site_update.js'] +Rails.application.config.assets.precompile += ['local_jquery.js','tablesorter-theme.cafe.css'] +%w( announcements submissions configurations contests contest_management graders heartbeat + login main messages problems report site sites sources tasks groups + test user_admin users tags testcases).each do |controller| + Rails.application.config.assets.precompile += ["#{controller}.js", "#{controller}.css"] +end diff --git a/config/routes.rb b/config/routes.rb --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,5 @@ CafeGrader::Application.routes.draw do + resources :tags get "sources/direct_edit" root :to => 'main#login' @@ -29,7 +30,20 @@ get 'import' get 'manage' end + end + resources :groups do + member do + post 'add_user', to: 'groups#add_user', as: 'add_user' + delete 'remove_user/:user_id', to: 'groups#remove_user', as: 'remove_user' + delete 'remove_all_user', to: 'groups#remove_all_user', as: 'remove_all_user' + post 'add_problem', to: 'groups#add_problem', as: 'add_problem' + delete 'remove_problem/:problem_id', to: 'groups#remove_problem', as: 'remove_problem' + delete 'remove_all_problem', to: 'groups#remove_all_problem', as: 'remove_all_problem' + end + collection do + + end end resources :testcases, only: [] do @@ -59,7 +73,7 @@ end collection do get 'prob/:problem_id', to: 'submissions#index', as: 'problem' - get 'direct_edit_problem/:problem_id', to: 'submissions#direct_edit_problem', as: 'direct_edit_problem' + get 'direct_edit_problem/:problem_id(/:user_id)', to: 'submissions#direct_edit_problem', as: 'direct_edit_problem' get 'get_latest_submission_status/:uid/:pid', to: 'submissions#get_latest_submission_status', as: 'get_latest_submission_status' end end @@ -72,6 +86,8 @@ #user admin get 'user_admin/bulk_manage', to: 'user_admin#bulk_manage', as: 'bulk_manage_user_admin' + post 'user_admin', to: 'user_admin#create' + delete 'user_admin/:id', to: 'user_admin#destroy', as: 'user_admin_destroy' #report get 'report/current_score', to: 'report#current_score', as: 'report_current_score' diff --git a/db/migrate/20170911091143_create_groups.rb b/db/migrate/20170911091143_create_groups.rb new file mode 100644 --- /dev/null +++ b/db/migrate/20170911091143_create_groups.rb @@ -0,0 +1,30 @@ +class CreateGroups < ActiveRecord::Migration + + def change + create_table :groups do |t| + t.string :name + t.string :description + end + + create_join_table :groups, :users do |t| + # t.index [:group_id, :user_id] + t.index [:user_id, :group_id] + end + + create_join_table :problems, :groups do |t| + # t.index [:problem_id, :group_id] + t.index [:group_id, :problem_id] + end + + reversible do |change| + change.up do + GraderConfiguration.where(key: 'system.use_problem_group').first_or_create(value_type: 'boolean', value: 'false', + description: 'If true, available problem to the user will be only ones associated with the group of the user'); + end + + change.down do + GraderConfiguration.where(key: 'system.use_problem_group').destroy_all + end + end + end +end diff --git a/db/migrate/20170914150545_create_tags.rb b/db/migrate/20170914150545_create_tags.rb new file mode 100644 --- /dev/null +++ b/db/migrate/20170914150545_create_tags.rb @@ -0,0 +1,11 @@ +class CreateTags < ActiveRecord::Migration + def change + create_table :tags do |t| + t.string :name, null: false + t.text :description + t.boolean :public + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20170914150742_create_problem_tags.rb b/db/migrate/20170914150742_create_problem_tags.rb new file mode 100644 --- /dev/null +++ b/db/migrate/20170914150742_create_problem_tags.rb @@ -0,0 +1,10 @@ +class CreateProblemTags < ActiveRecord::Migration + def change + create_table :problems_tags do |t| + t.references :problem, index: true, foreign_key: true + t.references :tag, index: true, foreign_key: true + + t.index [:problem_id,:tag_id], unique: true + end + end +end diff --git a/db/schema.rb b/db/schema.rb --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20170427070345) do +ActiveRecord::Schema.define(version: 20170914150742) do create_table "announcements", force: :cascade do |t| t.string "author", limit: 255 @@ -79,6 +79,25 @@ add_index "grader_processes", ["host", "pid"], name: "index_grader_processes_on_ip_and_pid", using: :btree + create_table "groups", force: :cascade do |t| + t.string "name", limit: 255 + t.string "description", limit: 255 + end + + create_table "groups_problems", id: false, force: :cascade do |t| + t.integer "problem_id", limit: 4, null: false + t.integer "group_id", limit: 4, null: false + end + + add_index "groups_problems", ["group_id", "problem_id"], name: "index_groups_problems_on_group_id_and_problem_id", using: :btree + + create_table "groups_users", id: false, force: :cascade do |t| + t.integer "group_id", limit: 4, null: false + t.integer "user_id", limit: 4, null: false + end + + add_index "groups_users", ["user_id", "group_id"], name: "index_groups_users_on_user_id_and_group_id", using: :btree + create_table "heart_beats", force: :cascade do |t| t.integer "user_id", limit: 4 t.string "ip_address", limit: 255 @@ -127,6 +146,15 @@ t.boolean "view_testcase" end + create_table "problems_tags", force: :cascade do |t| + t.integer "problem_id", limit: 4 + t.integer "tag_id", limit: 4 + end + + add_index "problems_tags", ["problem_id", "tag_id"], name: "index_problems_tags_on_problem_id_and_tag_id", unique: true, using: :btree + add_index "problems_tags", ["problem_id"], name: "index_problems_tags_on_problem_id", using: :btree + add_index "problems_tags", ["tag_id"], name: "index_problems_tags_on_tag_id", using: :btree + create_table "rights", force: :cascade do |t| t.string "name", limit: 255 t.string "controller", limit: 255 @@ -200,6 +228,14 @@ add_index "submissions", ["user_id", "problem_id", "number"], name: "index_submissions_on_user_id_and_problem_id_and_number", unique: true, using: :btree add_index "submissions", ["user_id", "problem_id"], name: "index_submissions_on_user_id_and_problem_id", using: :btree + create_table "tags", force: :cascade do |t| + t.string "name", limit: 255, null: false + t.text "description", limit: 65535 + t.boolean "public" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "tasks", force: :cascade do |t| t.integer "submission_id", limit: 4 t.datetime "created_at" @@ -280,4 +316,6 @@ add_index "users", ["login"], name: "index_users_on_login", unique: true, using: :btree + add_foreign_key "problems_tags", "problems" + add_foreign_key "problems_tags", "tags" end diff --git a/db/seeds.rb b/db/seeds.rb --- a/db/seeds.rb +++ b/db/seeds.rb @@ -163,7 +163,16 @@ :value_type => 'string', :default_value => 'none', :description => "New user will be assigned to this contest automatically, if it exists. Set to 'none' if there is no default contest." - } + }, + + { + :key => 'system.use_problem_group', + :value_type => 'boolean', + :default_value => 'false', + :description => "If true, available problem to the user will be only ones associated with the group of the user." + }, + + ] diff --git a/lib/grader_script.rb b/lib/grader_script.rb --- a/lib/grader_script.rb +++ b/lib/grader_script.rb @@ -33,6 +33,7 @@ GraderScript.call_grader "#{env} test_request -err-log &" end + #call the import problem script def self.call_import_problem(problem_name, problem_dir, time_limit=1, diff --git a/lib/testdata_importer.rb b/lib/testdata_importer.rb --- a/lib/testdata_importer.rb +++ b/lib/testdata_importer.rb @@ -8,8 +8,9 @@ @problem = problem end - def import_from_file(tempfile, - time_limit, + #Create or update problem according to the parameter + def import_from_file(tempfile, + time_limit, memory_limit, checker_name='text', import_to_db=false) @@ -52,6 +53,7 @@ return filename.slice(i..len) end + # extract an archive file located at +tempfile+ to the +raw_dir+ def extract(tempfile) testdata_filename = save_testdata_file(tempfile) ext = TestdataImporter.long_ext(tempfile.original_filename) diff --git a/test/controllers/groups_controller_test.rb b/test/controllers/groups_controller_test.rb new file mode 100644 --- /dev/null +++ b/test/controllers/groups_controller_test.rb @@ -0,0 +1,49 @@ +require 'test_helper' + +class GroupsControllerTest < ActionController::TestCase + setup do + @group = groups(:one) + end + + test "should get index" do + get :index + assert_response :success + assert_not_nil assigns(:groups) + end + + test "should get new" do + get :new + assert_response :success + end + + test "should create group" do + assert_difference('Group.count') do + post :create, group: { description: @group.description, name: @group.name } + end + + assert_redirected_to group_path(assigns(:group)) + end + + test "should show group" do + get :show, id: @group + assert_response :success + end + + test "should get edit" do + get :edit, id: @group + assert_response :success + end + + test "should update group" do + patch :update, id: @group, group: { description: @group.description, name: @group.name } + assert_redirected_to group_path(assigns(:group)) + end + + test "should destroy group" do + assert_difference('Group.count', -1) do + delete :destroy, id: @group + end + + assert_redirected_to groups_path + end +end diff --git a/test/controllers/tags_controller_test.rb b/test/controllers/tags_controller_test.rb new file mode 100644 --- /dev/null +++ b/test/controllers/tags_controller_test.rb @@ -0,0 +1,49 @@ +require 'test_helper' + +class TagsControllerTest < ActionController::TestCase + setup do + @tag = tags(:one) + end + + test "should get index" do + get :index + assert_response :success + assert_not_nil assigns(:tags) + end + + test "should get new" do + get :new + assert_response :success + end + + test "should create tag" do + assert_difference('Tag.count') do + post :create, tag: { description: @tag.description, name: @tag.name, public: @tag.public } + end + + assert_redirected_to tag_path(assigns(:tag)) + end + + test "should show tag" do + get :show, id: @tag + assert_response :success + end + + test "should get edit" do + get :edit, id: @tag + assert_response :success + end + + test "should update tag" do + patch :update, id: @tag, tag: { description: @tag.description, name: @tag.name, public: @tag.public } + assert_redirected_to tag_path(assigns(:tag)) + end + + test "should destroy tag" do + assert_difference('Tag.count', -1) do + delete :destroy, id: @tag + end + + assert_redirected_to tags_path + end +end diff --git a/test/fixtures/problem_tags.yml b/test/fixtures/problem_tags.yml new file mode 100644 --- /dev/null +++ b/test/fixtures/problem_tags.yml @@ -0,0 +1,9 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + problem_id: + tag_id: + +two: + problem_id: + tag_id: diff --git a/test/fixtures/tags.yml b/test/fixtures/tags.yml new file mode 100644 --- /dev/null +++ b/test/fixtures/tags.yml @@ -0,0 +1,11 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + name: MyString + description: MyString + public: + +two: + name: MyString + description: MyString + public: diff --git a/test/models/problem_tag_test.rb b/test/models/problem_tag_test.rb new file mode 100644 --- /dev/null +++ b/test/models/problem_tag_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class ProblemTagTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/models/tag_test.rb b/test/models/tag_test.rb new file mode 100644 --- /dev/null +++ b/test/models/tag_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class TagTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end