implemented partial batch update for exercises
This commit is contained in:
@ -15,11 +15,45 @@ $(function() {
|
|||||||
$('body, html').scrollTo('#add-file');
|
$('body, html').scrollTo('#add-file');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var ajaxError = function(response) {
|
||||||
|
$.flash.danger({
|
||||||
|
text: $('#flash').data('message-failure')
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var buildCheckboxes = function() {
|
||||||
|
$('tbody tr').each(function(index, element) {
|
||||||
|
var td = $('td.public', element);
|
||||||
|
var checkbox = $('<input>', {
|
||||||
|
checked: td.data('value'),
|
||||||
|
type: 'checkbox'
|
||||||
|
});
|
||||||
|
td.on('click', function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
checkbox.prop('checked', !checkbox.prop('checked'));
|
||||||
|
});
|
||||||
|
td.html(checkbox);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
var discardFile = function(event) {
|
var discardFile = function(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
$(this).parents('li').remove();
|
$(this).parents('li').remove();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var enableBatchUpdate = function() {
|
||||||
|
$('thead .batch a').on('click', function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
if (!$(this).data('toggled')) {
|
||||||
|
$(this).data('toggled', true);
|
||||||
|
$(this).text($(this).data('text'));
|
||||||
|
buildCheckboxes();
|
||||||
|
} else {
|
||||||
|
performBatchUpdate();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
var enableInlineFileCreation = function() {
|
var enableInlineFileCreation = function() {
|
||||||
$('#add-file').on('click', addFileForm);
|
$('#add-file').on('click', addFileForm);
|
||||||
$('#files').on('click', 'li .discard-file', discardFile);
|
$('#files').on('click', 'li .discard-file', discardFile);
|
||||||
@ -84,6 +118,23 @@ $(function() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var performBatchUpdate = function() {
|
||||||
|
var jqxhr = $.ajax({
|
||||||
|
data: {
|
||||||
|
exercises: _.map($('tbody tr'), function(element) {
|
||||||
|
return {
|
||||||
|
id: $(element).data('id'),
|
||||||
|
public: $('.public input', element).prop('checked')
|
||||||
|
};
|
||||||
|
})
|
||||||
|
},
|
||||||
|
dataType: 'json',
|
||||||
|
method: 'PUT'
|
||||||
|
});
|
||||||
|
jqxhr.done(window.CodeOcean.refresh);
|
||||||
|
jqxhr.fail(ajaxError);
|
||||||
|
};
|
||||||
|
|
||||||
var toggleCodeHeight = function() {
|
var toggleCodeHeight = function() {
|
||||||
$('code').on('click', function() {
|
$('code').on('click', function() {
|
||||||
$(this).css({
|
$(this).css({
|
||||||
@ -93,7 +144,9 @@ $(function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if ($.isController('exercises')) {
|
if ($.isController('exercises')) {
|
||||||
if ($('.edit_exercise, .new_exercise').isPresent()) {
|
if ($('table').isPresent()) {
|
||||||
|
enableBatchUpdate();
|
||||||
|
} else if ($('.edit_exercise, .new_exercise').isPresent()) {
|
||||||
execution_environments = $('form').data('execution-environments');
|
execution_environments = $('form').data('execution-environments');
|
||||||
file_types = $('form').data('file-types');
|
file_types = $('form').data('file-types');
|
||||||
new MarkdownEditor('#exercise_instructions');
|
new MarkdownEditor('#exercise_instructions');
|
||||||
|
@ -15,6 +15,17 @@ class ExercisesController < ApplicationController
|
|||||||
end
|
end
|
||||||
private :authorize!
|
private :authorize!
|
||||||
|
|
||||||
|
def batch_update
|
||||||
|
@exercises = Exercise.all
|
||||||
|
authorize!
|
||||||
|
@exercises = params[:exercises].values.map do |exercise_params|
|
||||||
|
exercise = Exercise.find(exercise_params.delete(:id))
|
||||||
|
exercise.update(exercise_params)
|
||||||
|
exercise
|
||||||
|
end
|
||||||
|
render(json: {exercises: @exercises})
|
||||||
|
end
|
||||||
|
|
||||||
def clone
|
def clone
|
||||||
exercise = @exercise.duplicate(public: false, token: nil, user: current_user)
|
exercise = @exercise.duplicate(public: false, token: nil, user: current_user)
|
||||||
exercise.send(:generate_token)
|
exercise.send(:generate_token)
|
||||||
|
@ -4,6 +4,10 @@ class ExercisePolicy < AdminOrAuthorPolicy
|
|||||||
end
|
end
|
||||||
private :author?
|
private :author?
|
||||||
|
|
||||||
|
def batch_update?
|
||||||
|
admin?
|
||||||
|
end
|
||||||
|
|
||||||
[:clone?, :destroy?, :edit?, :show?, :statistics?, :update?].each do |action|
|
[:clone?, :destroy?, :edit?, :show?, :statistics?, :update?].each do |action|
|
||||||
define_method(action) { admin? || author? || team_member? }
|
define_method(action) { admin? || author? || team_member? }
|
||||||
end
|
end
|
||||||
|
@ -17,17 +17,21 @@ h1 = Exercise.model_name.human(count: 2)
|
|||||||
th = sort_link(@search, :execution_environment_id, t('activerecord.attributes.exercise.execution_environment'))
|
th = sort_link(@search, :execution_environment_id, t('activerecord.attributes.exercise.execution_environment'))
|
||||||
th = t('.test_files')
|
th = t('.test_files')
|
||||||
th = t('activerecord.attributes.exercise.maximum_score')
|
th = t('activerecord.attributes.exercise.maximum_score')
|
||||||
th = t('activerecord.attributes.exercise.public')
|
th
|
||||||
|
= t('activerecord.attributes.exercise.public')
|
||||||
|
- if policy(Exercise).batch_update?
|
||||||
|
br
|
||||||
|
span.batch = link_to(t('shared.batch_update'), '#', 'data-text' => t('shared.update', model: t('activerecord.models.exercise.other')))
|
||||||
th colspan=6 = t('shared.actions')
|
th colspan=6 = t('shared.actions')
|
||||||
tbody
|
tbody
|
||||||
- @exercises.each do |exercise|
|
- @exercises.each do |exercise|
|
||||||
tr
|
tr data-id=exercise.id
|
||||||
td = exercise.title
|
td = exercise.title
|
||||||
td = link_to(exercise.author, exercise.author)
|
td = link_to(exercise.author, exercise.author)
|
||||||
td = link_to(exercise.execution_environment, exercise.execution_environment)
|
td = link_to(exercise.execution_environment, exercise.execution_environment)
|
||||||
td = exercise.files.teacher_defined_tests.count
|
td = exercise.files.teacher_defined_tests.count
|
||||||
td = exercise.maximum_score
|
td = exercise.maximum_score
|
||||||
td = symbol_for(exercise.public?)
|
td.public data-value=exercise.public? = symbol_for(exercise.public?)
|
||||||
td = link_to(t('shared.show'), exercise)
|
td = link_to(t('shared.show'), exercise)
|
||||||
td = link_to(t('shared.edit'), edit_exercise_path(exercise))
|
td = link_to(t('shared.edit'), edit_exercise_path(exercise))
|
||||||
td = link_to(t('shared.destroy'), exercise, data: {confirm: t('shared.confirm_destroy')}, method: :delete)
|
td = link_to(t('shared.destroy'), exercise, data: {confirm: t('shared.confirm_destroy')}, method: :delete)
|
||||||
|
@ -283,6 +283,7 @@ de:
|
|||||||
administration: Administration
|
administration: Administration
|
||||||
already_signed_in: Sie sind bereits angemeldet.
|
already_signed_in: Sie sind bereits angemeldet.
|
||||||
apply_filters: Filter anwenden
|
apply_filters: Filter anwenden
|
||||||
|
batch_update: Batch-Update
|
||||||
confirm_destroy: Sind Sie sicher?
|
confirm_destroy: Sind Sie sicher?
|
||||||
create: '%{model} erstellen'
|
create: '%{model} erstellen'
|
||||||
created_at: Erstellt
|
created_at: Erstellt
|
||||||
|
@ -283,6 +283,7 @@ en:
|
|||||||
administration: Administration
|
administration: Administration
|
||||||
already_signed_in: You are already signed in.
|
already_signed_in: You are already signed in.
|
||||||
apply_filters: Apply filters
|
apply_filters: Apply filters
|
||||||
|
batch_update: Batch Update
|
||||||
confirm_destroy: Are you sure?
|
confirm_destroy: Are you sure?
|
||||||
create: 'Create %{model}'
|
create: 'Create %{model}'
|
||||||
created_at: Created At
|
created_at: Created At
|
||||||
|
@ -22,6 +22,10 @@ Rails.application.routes.draw do
|
|||||||
end
|
end
|
||||||
|
|
||||||
resources :exercises do
|
resources :exercises do
|
||||||
|
collection do
|
||||||
|
match '', to: 'exercises#batch_update', via: [:patch, :put]
|
||||||
|
end
|
||||||
|
|
||||||
member do
|
member do
|
||||||
post :clone
|
post :clone
|
||||||
get :implement
|
get :implement
|
||||||
|
@ -5,6 +5,20 @@ describe ExercisesController do
|
|||||||
let(:user) { FactoryGirl.create(:admin) }
|
let(:user) { FactoryGirl.create(:admin) }
|
||||||
before(:each) { allow(controller).to receive(:current_user).and_return(user) }
|
before(:each) { allow(controller).to receive(:current_user).and_return(user) }
|
||||||
|
|
||||||
|
describe 'PUT #batch_update' do
|
||||||
|
let(:attributes) { {public: true} }
|
||||||
|
let(:request) { proc { put :batch_update, exercises: {0 => attributes.merge(id: exercise.id)} } }
|
||||||
|
before(:each) { request.call }
|
||||||
|
|
||||||
|
it 'updates the exercises' do
|
||||||
|
expect_any_instance_of(Exercise).to receive(:update).with(attributes)
|
||||||
|
request.call
|
||||||
|
end
|
||||||
|
|
||||||
|
expect_json
|
||||||
|
expect_status(200)
|
||||||
|
end
|
||||||
|
|
||||||
describe 'POST #clone' do
|
describe 'POST #clone' do
|
||||||
let(:request) { proc { post :clone, id: exercise.id } }
|
let(:request) { proc { post :clone, id: exercise.id } }
|
||||||
|
|
||||||
|
@ -5,6 +5,15 @@ describe ExercisePolicy do
|
|||||||
|
|
||||||
let(:exercise) { FactoryGirl.build(:dummy, team: FactoryGirl.create(:team)) }
|
let(:exercise) { FactoryGirl.build(:dummy, team: FactoryGirl.create(:team)) }
|
||||||
|
|
||||||
|
permissions :batch_update? do
|
||||||
|
it 'grants access to admins only' do
|
||||||
|
expect(subject).to permit(FactoryGirl.build(:admin), exercise)
|
||||||
|
[:external_user, :teacher].each do |factory_name|
|
||||||
|
expect(subject).not_to permit(FactoryGirl.build(factory_name), exercise)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
[:create?, :index?, :new?].each do |action|
|
[:create?, :index?, :new?].each do |action|
|
||||||
permissions(action) do
|
permissions(action) do
|
||||||
it 'grants access to admins' do
|
it 'grants access to admins' do
|
||||||
|
Reference in New Issue
Block a user