diff --git a/app/assets/javascripts/exercises.js b/app/assets/javascripts/exercises.js index e6773f40..81da8cf8 100644 --- a/app/assets/javascripts/exercises.js +++ b/app/assets/javascripts/exercises.js @@ -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 = ""; + for (var i = 0; i < response.length; i++) { + options += "" + } + $("#code_ocean_file_file_template_id").find('option').remove().end().append($(options)); + }); + jqxhr.fail(ajaxError); + } + if ($.isController('exercises')) { if ($('table').isPresent()) { enableBatchUpdate(); @@ -165,6 +181,10 @@ $(function() { inferFileAttributes(); observeFileRoleChanges(); 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(); if (window.hljs) { diff --git a/app/assets/javascripts/file_templates.js.coffee b/app/assets/javascripts/file_templates.js.coffee new file mode 100644 index 00000000..24f83d18 --- /dev/null +++ b/app/assets/javascripts/file_templates.js.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/stylesheets/file_templates.css.scss b/app/assets/stylesheets/file_templates.css.scss new file mode 100644 index 00000000..bf8e27e8 --- /dev/null +++ b/app/assets/stylesheets/file_templates.css.scss @@ -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/ diff --git a/app/controllers/code_ocean/files_controller.rb b/app/controllers/code_ocean/files_controller.rb index 74c1932f..55d8b369 100644 --- a/app/controllers/code_ocean/files_controller.rb +++ b/app/controllers/code_ocean/files_controller.rb @@ -10,6 +10,11 @@ module CodeOcean def create @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! create_and_respond(object: @file, path: proc { implement_exercise_path(@file.context.exercise, tab: 2) }) end diff --git a/app/controllers/concerns/file_parameters.rb b/app/controllers/concerns/file_parameters.rb index e61e719e..295b66c3 100644 --- a/app/controllers/concerns/file_parameters.rb +++ b/app/controllers/concerns/file_parameters.rb @@ -1,6 +1,6 @@ module FileParameters 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 private :file_attributes end diff --git a/app/controllers/file_templates_controller.rb b/app/controllers/file_templates_controller.rb new file mode 100644 index 00000000..a6039500 --- /dev/null +++ b/app/controllers/file_templates_controller.rb @@ -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 diff --git a/app/models/file_template.rb b/app/models/file_template.rb new file mode 100644 index 00000000..ef068e13 --- /dev/null +++ b/app/models/file_template.rb @@ -0,0 +1,10 @@ +class FileTemplate < ActiveRecord::Base + + belongs_to :file_type + + + def to_s + name + end + +end diff --git a/app/models/file_type.rb b/app/models/file_type.rb index 53bf18dc..d3b519d5 100644 --- a/app/models/file_type.rb +++ b/app/models/file_type.rb @@ -12,6 +12,7 @@ class FileType < ActiveRecord::Base has_many :execution_environments has_many :files + has_many :file_templates validates :binary, boolean_presence: true validates :editor_mode, presence: true, unless: :binary? diff --git a/app/policies/file_template_policy.rb b/app/policies/file_template_policy.rb new file mode 100644 index 00000000..92ced442 --- /dev/null +++ b/app/policies/file_template_policy.rb @@ -0,0 +1,11 @@ +class FileTemplatePolicy < AdminOnlyPolicy + + def show? + everyone + end + + def by_file_type? + everyone + end + +end diff --git a/app/views/application/_navigation.html.slim b/app/views/application/_navigation.html.slim index 3c260cb8..8aa289d3 100644 --- a/app/views/application/_navigation.html.slim +++ b/app/views/application/_navigation.html.slim @@ -8,7 +8,7 @@ - if current_user.admin? li = link_to(t('breadcrumbs.dashboard.show'), admin_dashboard_path) 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| - if policy(model).index? li = link_to(model.model_name.human(count: 2), send(:"#{model.model_name.collection}_path")) diff --git a/app/views/code_ocean/files/_form.html.slim b/app/views/code_ocean/files/_form.html.slim index 07dd3355..46c5b2c2 100644 --- a/app/views/code_ocean/files/_form.html.slim +++ b/app/views/code_ocean/files/_form.html.slim @@ -8,5 +8,9 @@ .form-group = 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') + .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) + .hidden#noTemplateLabel data-text=t('file_template.no_template_label') .actions = render('shared/submit_button', f: f, object: CodeOcean::File.new) diff --git a/app/views/file_templates/_form.html.slim b/app/views/file_templates/_form.html.slim new file mode 100644 index 00000000..1a5c34bc --- /dev/null +++ b/app/views/file_templates/_form.html.slim @@ -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) diff --git a/app/views/file_templates/edit.html.slim b/app/views/file_templates/edit.html.slim new file mode 100644 index 00000000..c198271f --- /dev/null +++ b/app/views/file_templates/edit.html.slim @@ -0,0 +1,3 @@ +h1 = @file_template + += render('form') diff --git a/app/views/file_templates/index.html.slim b/app/views/file_templates/index.html.slim new file mode 100644 index 00000000..3022ea53 --- /dev/null +++ b/app/views/file_templates/index.html.slim @@ -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) diff --git a/app/views/file_templates/new.html.slim b/app/views/file_templates/new.html.slim new file mode 100644 index 00000000..bf434860 --- /dev/null +++ b/app/views/file_templates/new.html.slim @@ -0,0 +1,3 @@ +h1 = t('shared.new_model', model: FileTemplate.model_name.human) + += render('form') diff --git a/app/views/file_templates/show.html.slim b/app/views/file_templates/show.html.slim new file mode 100644 index 00000000..19f0d28f --- /dev/null +++ b/app/views/file_templates/show.html.slim @@ -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) diff --git a/config/locales/de.yml b/config/locales/de.yml index ce8ba98f..a56ca31e 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -52,6 +52,7 @@ de: read_only: Schreibgeschützt role: Rolle weight: Punktzahl + file_template_id: "Dateivorlage" file_type: binary: Binär editor_mode: Editor-Modus @@ -89,6 +90,10 @@ de: files: Dateien score: Punktzahl user: Autor + file_template: + name: "Name" + file_type: "Dateityp" + content: "Code" models: code_harbor_link: one: CodeHarbor-Link @@ -111,6 +116,9 @@ de: file: one: Datei other: Dateien + file_template: + one: Dateivorlage + other: Dateivorlagen file_type: one: Dateityp other: Dateitypen @@ -447,3 +455,5 @@ de: next_label: 'Nächste Seite →' page_gap: '…' previous_label: '← Vorherige Seite' + file_template: + no_template_label: "Leere Datei" diff --git a/config/locales/en.yml b/config/locales/en.yml index 86acbb15..90590395 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -52,6 +52,7 @@ en: read_only: Read-only role: Role weight: Score + file_template_id: "File Template" file_type: binary: Binary editor_mode: Editor Mode @@ -89,6 +90,10 @@ en: files: Files score: Score user: Author + file_template: + name: "Name" + file_type: "File Type" + content: "Content" models: code_harbor_link: one: CodeHarbor Link @@ -111,6 +116,9 @@ en: file: one: File other: Files + file_template: + one: File Template + other: File Templates file_type: one: File Type other: File Types @@ -447,3 +455,5 @@ en: next_label: 'Next Page →' page_gap: '…' previous_label: '← Previous Page' + file_template: + no_template_label: "Empty File" diff --git a/config/routes.rb b/config/routes.rb index b9f33826..0683a6f1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,6 +1,11 @@ FILENAME_REGEXP = /[\w\.]+/ unless Kernel.const_defined?(:FILENAME_REGEXP) 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 :request_for_comments do member do diff --git a/db/migrate/20160609185708_create_file_templates.rb b/db/migrate/20160609185708_create_file_templates.rb new file mode 100644 index 00000000..43cde0d0 --- /dev/null +++ b/db/migrate/20160609185708_create_file_templates.rb @@ -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 diff --git a/db/migrate/20160610111602_add_file_template_to_file.rb b/db/migrate/20160610111602_add_file_template_to_file.rb new file mode 100644 index 00000000..a595e90b --- /dev/null +++ b/db/migrate/20160610111602_add_file_template_to_file.rb @@ -0,0 +1,5 @@ +class AddFileTemplateToFile < ActiveRecord::Migration + def change + add_reference :files, :file_template + end +end diff --git a/db/schema.rb b/db/schema.rb index 79d13498..57ce32e7 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -100,6 +100,14 @@ ActiveRecord::Schema.define(version: 20160704143402) do t.datetime "updated_at" 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| t.string "editor_mode" t.string "file_extension" @@ -131,6 +139,7 @@ ActiveRecord::Schema.define(version: 20160704143402) do t.string "feedback_message" t.float "weight" t.string "path" + t.integer "file_template_id" end add_index "files", ["context_id", "context_type"], name: "index_files_on_context_id_and_context_type", using: :btree