Merge pull request #66 from openHPI/feature-file-templates
Feature file templates
This commit is contained in:
@ -151,6 +151,22 @@ $(function() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var updateFileTemplates = function(fileType) {
|
||||||
|
var jqxhr = $.ajax({
|
||||||
|
url: '/file_templates/by_file_type/' + fileType + '.json',
|
||||||
|
dataType: 'json'
|
||||||
|
});
|
||||||
|
jqxhr.done(function(response) {
|
||||||
|
var noTemplateLabel = $('#noTemplateLabel').data('text');
|
||||||
|
var options = "<option value>" + noTemplateLabel + "</option>";
|
||||||
|
for (var i = 0; i < response.length; i++) {
|
||||||
|
options += "<option value='" + response[i].id + "'>" + response[i].name + "</option>"
|
||||||
|
}
|
||||||
|
$("#code_ocean_file_file_template_id").find('option').remove().end().append($(options));
|
||||||
|
});
|
||||||
|
jqxhr.fail(ajaxError);
|
||||||
|
}
|
||||||
|
|
||||||
if ($.isController('exercises')) {
|
if ($.isController('exercises')) {
|
||||||
if ($('table').isPresent()) {
|
if ($('table').isPresent()) {
|
||||||
enableBatchUpdate();
|
enableBatchUpdate();
|
||||||
@ -165,6 +181,10 @@ $(function() {
|
|||||||
inferFileAttributes();
|
inferFileAttributes();
|
||||||
observeFileRoleChanges();
|
observeFileRoleChanges();
|
||||||
overrideTextareaTabBehavior();
|
overrideTextareaTabBehavior();
|
||||||
|
} else if ($('#files.jstree').isPresent()) {
|
||||||
|
var fileTypeSelect = $('#code_ocean_file_file_type_id');
|
||||||
|
fileTypeSelect.on("change", function() {updateFileTemplates(fileTypeSelect.val())});
|
||||||
|
updateFileTemplates(fileTypeSelect.val());
|
||||||
}
|
}
|
||||||
toggleCodeHeight();
|
toggleCodeHeight();
|
||||||
if (window.hljs) {
|
if (window.hljs) {
|
||||||
|
3
app/assets/javascripts/file_templates.js.coffee
Normal file
3
app/assets/javascripts/file_templates.js.coffee
Normal file
@ -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/
|
3
app/assets/stylesheets/file_templates.css.scss
Normal file
3
app/assets/stylesheets/file_templates.css.scss
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
// Place all the styles related to the FileTemplates controller here.
|
||||||
|
// They will automatically be included in application.css.
|
||||||
|
// You can use Sass (SCSS) here: http://sass-lang.com/
|
@ -10,6 +10,11 @@ module CodeOcean
|
|||||||
|
|
||||||
def create
|
def create
|
||||||
@file = CodeOcean::File.new(file_params)
|
@file = CodeOcean::File.new(file_params)
|
||||||
|
if @file.file_template_id
|
||||||
|
content = FileTemplate.find(@file.file_template_id).content
|
||||||
|
content.sub! '{{file_name}}', @file.name
|
||||||
|
@file.content = content
|
||||||
|
end
|
||||||
authorize!
|
authorize!
|
||||||
create_and_respond(object: @file, path: proc { implement_exercise_path(@file.context.exercise, tab: 2) })
|
create_and_respond(object: @file, path: proc { implement_exercise_path(@file.context.exercise, tab: 2) })
|
||||||
end
|
end
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
module FileParameters
|
module FileParameters
|
||||||
def file_attributes
|
def file_attributes
|
||||||
%w(content context_id feedback_message file_id file_type_id hidden id name native_file path read_only role weight)
|
%w(content context_id feedback_message file_id file_type_id hidden id name native_file path read_only role weight file_template_id)
|
||||||
end
|
end
|
||||||
private :file_attributes
|
private :file_attributes
|
||||||
end
|
end
|
||||||
|
94
app/controllers/file_templates_controller.rb
Normal file
94
app/controllers/file_templates_controller.rb
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
class FileTemplatesController < ApplicationController
|
||||||
|
before_action :set_file_template, only: [:show, :edit, :update, :destroy]
|
||||||
|
|
||||||
|
def authorize!
|
||||||
|
authorize(@file_template || @file_templates)
|
||||||
|
end
|
||||||
|
private :authorize!
|
||||||
|
|
||||||
|
def by_file_type
|
||||||
|
@file_templates = FileTemplate.where(:file_type_id => params[:file_type_id])
|
||||||
|
authorize!
|
||||||
|
respond_to do |format|
|
||||||
|
format.json { render :show, status: :ok, json: @file_templates.to_json }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# GET /file_templates
|
||||||
|
# GET /file_templates.json
|
||||||
|
def index
|
||||||
|
@file_templates = FileTemplate.all.order(:file_type_id).paginate(page: params[:page])
|
||||||
|
authorize!
|
||||||
|
end
|
||||||
|
|
||||||
|
# GET /file_templates/1
|
||||||
|
# GET /file_templates/1.json
|
||||||
|
def show
|
||||||
|
authorize!
|
||||||
|
end
|
||||||
|
|
||||||
|
# GET /file_templates/new
|
||||||
|
def new
|
||||||
|
@file_template = FileTemplate.new
|
||||||
|
authorize!
|
||||||
|
end
|
||||||
|
|
||||||
|
# GET /file_templates/1/edit
|
||||||
|
def edit
|
||||||
|
authorize!
|
||||||
|
end
|
||||||
|
|
||||||
|
# POST /file_templates
|
||||||
|
# POST /file_templates.json
|
||||||
|
def create
|
||||||
|
@file_template = FileTemplate.new(file_template_params)
|
||||||
|
authorize!
|
||||||
|
|
||||||
|
respond_to do |format|
|
||||||
|
if @file_template.save
|
||||||
|
format.html { redirect_to @file_template, notice: 'File template was successfully created.' }
|
||||||
|
format.json { render :show, status: :created, location: @file_template }
|
||||||
|
else
|
||||||
|
format.html { render :new }
|
||||||
|
format.json { render json: @file_template.errors, status: :unprocessable_entity }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# PATCH/PUT /file_templates/1
|
||||||
|
# PATCH/PUT /file_templates/1.json
|
||||||
|
def update
|
||||||
|
authorize!
|
||||||
|
respond_to do |format|
|
||||||
|
if @file_template.update(file_template_params)
|
||||||
|
format.html { redirect_to @file_template, notice: 'File template was successfully updated.' }
|
||||||
|
format.json { render :show, status: :ok, location: @file_template }
|
||||||
|
else
|
||||||
|
format.html { render :edit }
|
||||||
|
format.json { render json: @file_template.errors, status: :unprocessable_entity }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# DELETE /file_templates/1
|
||||||
|
# DELETE /file_templates/1.json
|
||||||
|
def destroy
|
||||||
|
authorize!
|
||||||
|
@file_template.destroy
|
||||||
|
respond_to do |format|
|
||||||
|
format.html { redirect_to file_templates_url, notice: 'File template was successfully destroyed.' }
|
||||||
|
format.json { head :no_content }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
# Use callbacks to share common setup or constraints between actions.
|
||||||
|
def set_file_template
|
||||||
|
@file_template = FileTemplate.find(params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
# Never trust parameters from the scary internet, only allow the white list through.
|
||||||
|
def file_template_params
|
||||||
|
params[:file_template].permit(:name, :file_type_id, :content)
|
||||||
|
end
|
||||||
|
end
|
10
app/models/file_template.rb
Normal file
10
app/models/file_template.rb
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
class FileTemplate < ActiveRecord::Base
|
||||||
|
|
||||||
|
belongs_to :file_type
|
||||||
|
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
name
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
@ -12,6 +12,7 @@ class FileType < ActiveRecord::Base
|
|||||||
|
|
||||||
has_many :execution_environments
|
has_many :execution_environments
|
||||||
has_many :files
|
has_many :files
|
||||||
|
has_many :file_templates
|
||||||
|
|
||||||
validates :binary, boolean_presence: true
|
validates :binary, boolean_presence: true
|
||||||
validates :editor_mode, presence: true, unless: :binary?
|
validates :editor_mode, presence: true, unless: :binary?
|
||||||
|
11
app/policies/file_template_policy.rb
Normal file
11
app/policies/file_template_policy.rb
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
class FileTemplatePolicy < AdminOnlyPolicy
|
||||||
|
|
||||||
|
def show?
|
||||||
|
everyone
|
||||||
|
end
|
||||||
|
|
||||||
|
def by_file_type?
|
||||||
|
everyone
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
@ -8,7 +8,7 @@
|
|||||||
- if current_user.admin?
|
- if current_user.admin?
|
||||||
li = link_to(t('breadcrumbs.dashboard.show'), admin_dashboard_path)
|
li = link_to(t('breadcrumbs.dashboard.show'), admin_dashboard_path)
|
||||||
li.divider
|
li.divider
|
||||||
- models = [ExecutionEnvironment, Exercise, Consumer, CodeHarborLink, ExternalUser, FileType, InternalUser, Submission].sort_by { |model| model.model_name.human(count: 2) }
|
- models = [ExecutionEnvironment, Exercise, Consumer, CodeHarborLink, ExternalUser, FileType, FileTemplate, InternalUser, Submission].sort_by { |model| model.model_name.human(count: 2) }
|
||||||
- models.each do |model|
|
- models.each do |model|
|
||||||
- if policy(model).index?
|
- if policy(model).index?
|
||||||
li = link_to(model.model_name.human(count: 2), send(:"#{model.model_name.collection}_path"))
|
li = link_to(model.model_name.human(count: 2), send(:"#{model.model_name.collection}_path"))
|
||||||
|
@ -8,5 +8,9 @@
|
|||||||
.form-group
|
.form-group
|
||||||
= f.label(:file_type_id, t('activerecord.attributes.file.file_type_id'))
|
= f.label(:file_type_id, t('activerecord.attributes.file.file_type_id'))
|
||||||
= f.collection_select(:file_type_id, FileType.where(binary: false).order(:name), :id, :name, {selected: @exercise.execution_environment.file_type.try(:id)}, class: 'form-control')
|
= f.collection_select(:file_type_id, FileType.where(binary: false).order(:name), :id, :name, {selected: @exercise.execution_environment.file_type.try(:id)}, class: 'form-control')
|
||||||
|
.form-group
|
||||||
|
= f.label(:file_template_id, t('activerecord.attributes.file.file_template_id'))
|
||||||
|
= f.collection_select(:file_template_id, FileTemplate.all.order(:name), :id, :name, {:include_blank => true}, class: 'form-control')
|
||||||
= f.hidden_field(:context_id)
|
= f.hidden_field(:context_id)
|
||||||
|
.hidden#noTemplateLabel data-text=t('file_template.no_template_label')
|
||||||
.actions = render('shared/submit_button', f: f, object: CodeOcean::File.new)
|
.actions = render('shared/submit_button', f: f, object: CodeOcean::File.new)
|
||||||
|
12
app/views/file_templates/_form.html.slim
Normal file
12
app/views/file_templates/_form.html.slim
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
= form_for(@file_template) do |f|
|
||||||
|
= render('shared/form_errors', object: @file_template)
|
||||||
|
.form-group
|
||||||
|
= f.label(:name)
|
||||||
|
= f.text_field(:name, class: 'form-control', required: true)
|
||||||
|
.form-group
|
||||||
|
= f.label(:file_type_id)
|
||||||
|
= f.collection_select(:file_type_id, FileType.all.order(:name), :id, :name, {}, class: 'form-control')
|
||||||
|
.form-group
|
||||||
|
= f.label(:content)
|
||||||
|
= f.text_area(:content, class: 'form-control')
|
||||||
|
.actions = render('shared/submit_button', f: f, object: @file_template)
|
3
app/views/file_templates/edit.html.slim
Normal file
3
app/views/file_templates/edit.html.slim
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
h1 = @file_template
|
||||||
|
|
||||||
|
= render('form')
|
20
app/views/file_templates/index.html.slim
Normal file
20
app/views/file_templates/index.html.slim
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
h1 = FileTemplate.model_name.human(count: 2)
|
||||||
|
|
||||||
|
.table-responsive
|
||||||
|
table.table
|
||||||
|
thead
|
||||||
|
tr
|
||||||
|
th = t('activerecord.attributes.file_template.name')
|
||||||
|
th = t('activerecord.attributes.file_template.file_type')
|
||||||
|
th colspan=3 = t('shared.actions')
|
||||||
|
tbody
|
||||||
|
- @file_templates.each do |file_template|
|
||||||
|
tr
|
||||||
|
td = file_template.name
|
||||||
|
td = link_to(file_template.file_type, file_type_path(file_template.file_type))
|
||||||
|
td = link_to(t('shared.show'), file_template)
|
||||||
|
td = link_to(t('shared.edit'), edit_file_template_path(file_template))
|
||||||
|
td = link_to(t('shared.destroy'), file_template, data: {confirm: t('shared.confirm_destroy')}, method: :delete)
|
||||||
|
|
||||||
|
= render('shared/pagination', collection: @file_templates)
|
||||||
|
p = render('shared/new_button', model: FileTemplate)
|
3
app/views/file_templates/new.html.slim
Normal file
3
app/views/file_templates/new.html.slim
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
h1 = t('shared.new_model', model: FileTemplate.model_name.human)
|
||||||
|
|
||||||
|
= render('form')
|
7
app/views/file_templates/show.html.slim
Normal file
7
app/views/file_templates/show.html.slim
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
h1
|
||||||
|
= @file_template
|
||||||
|
= render('shared/edit_button', object: @file_template)
|
||||||
|
|
||||||
|
= row(label: 'file_template.name', value: @file_template.name)
|
||||||
|
= row(label: 'file_template.file_type', value: link_to(@file_template.file_type, file_type_path(@file_template.file_type)))
|
||||||
|
= row(label: 'file_template.content', value: @file_template.content)
|
@ -52,6 +52,7 @@ de:
|
|||||||
read_only: Schreibgeschützt
|
read_only: Schreibgeschützt
|
||||||
role: Rolle
|
role: Rolle
|
||||||
weight: Punktzahl
|
weight: Punktzahl
|
||||||
|
file_template_id: "Dateivorlage"
|
||||||
file_type:
|
file_type:
|
||||||
binary: Binär
|
binary: Binär
|
||||||
editor_mode: Editor-Modus
|
editor_mode: Editor-Modus
|
||||||
@ -89,6 +90,10 @@ de:
|
|||||||
files: Dateien
|
files: Dateien
|
||||||
score: Punktzahl
|
score: Punktzahl
|
||||||
user: Autor
|
user: Autor
|
||||||
|
file_template:
|
||||||
|
name: "Name"
|
||||||
|
file_type: "Dateityp"
|
||||||
|
content: "Code"
|
||||||
models:
|
models:
|
||||||
code_harbor_link:
|
code_harbor_link:
|
||||||
one: CodeHarbor-Link
|
one: CodeHarbor-Link
|
||||||
@ -111,6 +116,9 @@ de:
|
|||||||
file:
|
file:
|
||||||
one: Datei
|
one: Datei
|
||||||
other: Dateien
|
other: Dateien
|
||||||
|
file_template:
|
||||||
|
one: Dateivorlage
|
||||||
|
other: Dateivorlagen
|
||||||
file_type:
|
file_type:
|
||||||
one: Dateityp
|
one: Dateityp
|
||||||
other: Dateitypen
|
other: Dateitypen
|
||||||
@ -447,3 +455,5 @@ de:
|
|||||||
next_label: 'Nächste Seite →'
|
next_label: 'Nächste Seite →'
|
||||||
page_gap: '…'
|
page_gap: '…'
|
||||||
previous_label: '← Vorherige Seite'
|
previous_label: '← Vorherige Seite'
|
||||||
|
file_template:
|
||||||
|
no_template_label: "Leere Datei"
|
||||||
|
@ -52,6 +52,7 @@ en:
|
|||||||
read_only: Read-only
|
read_only: Read-only
|
||||||
role: Role
|
role: Role
|
||||||
weight: Score
|
weight: Score
|
||||||
|
file_template_id: "File Template"
|
||||||
file_type:
|
file_type:
|
||||||
binary: Binary
|
binary: Binary
|
||||||
editor_mode: Editor Mode
|
editor_mode: Editor Mode
|
||||||
@ -89,6 +90,10 @@ en:
|
|||||||
files: Files
|
files: Files
|
||||||
score: Score
|
score: Score
|
||||||
user: Author
|
user: Author
|
||||||
|
file_template:
|
||||||
|
name: "Name"
|
||||||
|
file_type: "File Type"
|
||||||
|
content: "Content"
|
||||||
models:
|
models:
|
||||||
code_harbor_link:
|
code_harbor_link:
|
||||||
one: CodeHarbor Link
|
one: CodeHarbor Link
|
||||||
@ -111,6 +116,9 @@ en:
|
|||||||
file:
|
file:
|
||||||
one: File
|
one: File
|
||||||
other: Files
|
other: Files
|
||||||
|
file_template:
|
||||||
|
one: File Template
|
||||||
|
other: File Templates
|
||||||
file_type:
|
file_type:
|
||||||
one: File Type
|
one: File Type
|
||||||
other: File Types
|
other: File Types
|
||||||
@ -447,3 +455,5 @@ en:
|
|||||||
next_label: 'Next Page →'
|
next_label: 'Next Page →'
|
||||||
page_gap: '…'
|
page_gap: '…'
|
||||||
previous_label: '← Previous Page'
|
previous_label: '← Previous Page'
|
||||||
|
file_template:
|
||||||
|
no_template_label: "Empty File"
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
FILENAME_REGEXP = /[\w\.]+/ unless Kernel.const_defined?(:FILENAME_REGEXP)
|
FILENAME_REGEXP = /[\w\.]+/ unless Kernel.const_defined?(:FILENAME_REGEXP)
|
||||||
|
|
||||||
Rails.application.routes.draw do
|
Rails.application.routes.draw do
|
||||||
|
resources :file_templates do
|
||||||
|
collection do
|
||||||
|
get 'by_file_type/:file_type_id', as: :by_file_type, to: :by_file_type
|
||||||
|
end
|
||||||
|
end
|
||||||
resources :code_harbor_links
|
resources :code_harbor_links
|
||||||
resources :request_for_comments do
|
resources :request_for_comments do
|
||||||
member do
|
member do
|
||||||
|
10
db/migrate/20160609185708_create_file_templates.rb
Normal file
10
db/migrate/20160609185708_create_file_templates.rb
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
class CreateFileTemplates < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
create_table :file_templates do |t|
|
||||||
|
t.string :name
|
||||||
|
t.text :content
|
||||||
|
t.belongs_to :file_type
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
5
db/migrate/20160610111602_add_file_template_to_file.rb
Normal file
5
db/migrate/20160610111602_add_file_template_to_file.rb
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
class AddFileTemplateToFile < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
add_reference :files, :file_template
|
||||||
|
end
|
||||||
|
end
|
@ -100,6 +100,14 @@ ActiveRecord::Schema.define(version: 20160704143402) do
|
|||||||
t.datetime "updated_at"
|
t.datetime "updated_at"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "file_templates", force: true do |t|
|
||||||
|
t.string "name"
|
||||||
|
t.text "content"
|
||||||
|
t.integer "file_type_id"
|
||||||
|
t.datetime "created_at"
|
||||||
|
t.datetime "updated_at"
|
||||||
|
end
|
||||||
|
|
||||||
create_table "file_types", force: true do |t|
|
create_table "file_types", force: true do |t|
|
||||||
t.string "editor_mode"
|
t.string "editor_mode"
|
||||||
t.string "file_extension"
|
t.string "file_extension"
|
||||||
@ -131,6 +139,7 @@ ActiveRecord::Schema.define(version: 20160704143402) do
|
|||||||
t.string "feedback_message"
|
t.string "feedback_message"
|
||||||
t.float "weight"
|
t.float "weight"
|
||||||
t.string "path"
|
t.string "path"
|
||||||
|
t.integer "file_template_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
add_index "files", ["context_id", "context_type"], name: "index_files_on_context_id_and_context_type", using: :btree
|
add_index "files", ["context_id", "context_type"], name: "index_files_on_context_id_and_context_type", using: :btree
|
||||||
|
Reference in New Issue
Block a user