Merge pull request #41 from leoselig/master
Import exercise meta data from CodeHarbour
This commit is contained in:
1
Gemfile
1
Gemfile
@ -35,6 +35,7 @@ gem 'uglifier', '>= 1.3.0'
|
||||
gem 'will_paginate', '~> 3.0'
|
||||
gem 'tubesock'
|
||||
gem 'faye-websocket'
|
||||
gem 'nokogiri'
|
||||
|
||||
group :development do
|
||||
gem 'better_errors', platform: :ruby
|
||||
|
@ -362,6 +362,7 @@ DEPENDENCIES
|
||||
jquery-turbolinks
|
||||
kramdown
|
||||
newrelic_rpm
|
||||
nokogiri
|
||||
nyan-cat-formatter
|
||||
pg
|
||||
pry
|
||||
|
3
app/assets/stylesheets/code_harbor_links.css.scss
Normal file
3
app/assets/stylesheets/code_harbor_links.css.scss
Normal file
@ -0,0 +1,3 @@
|
||||
// Place all the styles related to the CodeHarborLinks controller here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
69
app/assets/stylesheets/scaffolds.css.scss
Normal file
69
app/assets/stylesheets/scaffolds.css.scss
Normal file
@ -0,0 +1,69 @@
|
||||
body {
|
||||
background-color: #fff;
|
||||
color: #333;
|
||||
font-family: verdana, arial, helvetica, sans-serif;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
p, ol, ul, td {
|
||||
font-family: verdana, arial, helvetica, sans-serif;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: #eee;
|
||||
padding: 10px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #000;
|
||||
&:visited {
|
||||
color: #666;
|
||||
}
|
||||
&:hover {
|
||||
color: #fff;
|
||||
background-color: #000;
|
||||
}
|
||||
}
|
||||
|
||||
div {
|
||||
&.field, &.actions {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
#notice {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.field_with_errors {
|
||||
padding: 2px;
|
||||
background-color: red;
|
||||
display: table;
|
||||
}
|
||||
|
||||
#error_explanation {
|
||||
width: 450px;
|
||||
border: 2px solid red;
|
||||
padding: 7px;
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 20px;
|
||||
background-color: #f0f0f0;
|
||||
h2 {
|
||||
text-align: left;
|
||||
font-weight: bold;
|
||||
padding: 5px 5px 5px 15px;
|
||||
font-size: 12px;
|
||||
margin: -7px;
|
||||
margin-bottom: 0px;
|
||||
background-color: #c00;
|
||||
color: #fff;
|
||||
}
|
||||
ul li {
|
||||
font-size: 12px;
|
||||
list-style: square;
|
||||
}
|
||||
}
|
68
app/controllers/code_harbor_links_controller.rb
Normal file
68
app/controllers/code_harbor_links_controller.rb
Normal file
@ -0,0 +1,68 @@
|
||||
class CodeHarborLinksController < ApplicationController
|
||||
include CommonBehavior
|
||||
before_action :set_code_harbor_link, only: [:show, :edit, :update, :destroy]
|
||||
|
||||
def authorize!
|
||||
authorize(@code_harbor_link || @code_harbor_links)
|
||||
end
|
||||
private :authorize!
|
||||
|
||||
# GET /code_harbor_links
|
||||
# GET /code_harbor_links.json
|
||||
def index
|
||||
@code_harbor_links = CodeHarborLink.where(user_id: current_user.id).paginate(page: params[:page])
|
||||
authorize!
|
||||
end
|
||||
|
||||
# GET /code_harbor_links/1
|
||||
# GET /code_harbor_links/1.json
|
||||
def show
|
||||
authorize!
|
||||
end
|
||||
|
||||
# GET /code_harbor_links/new
|
||||
def new
|
||||
@code_harbor_link = CodeHarborLink.new
|
||||
authorize!
|
||||
end
|
||||
|
||||
# GET /code_harbor_links/1/edit
|
||||
def edit
|
||||
authorize!
|
||||
end
|
||||
|
||||
# POST /code_harbor_links
|
||||
# POST /code_harbor_links.json
|
||||
def create
|
||||
@code_harbor_link = CodeHarborLink.new(code_harbor_link_params)
|
||||
@code_harbor_link.user = current_user
|
||||
authorize!
|
||||
create_and_respond(object: @code_harbor_link)
|
||||
end
|
||||
|
||||
# PATCH/PUT /code_harbor_links/1
|
||||
# PATCH/PUT /code_harbor_links/1.json
|
||||
def update
|
||||
update_and_respond(object: @code_harbor_link, params: code_harbor_link_params)
|
||||
authorize!
|
||||
end
|
||||
|
||||
# DELETE /code_harbor_links/1
|
||||
# DELETE /code_harbor_links/1.json
|
||||
def destroy
|
||||
destroy_and_respond(object: @code_harbor_link)
|
||||
end
|
||||
|
||||
private
|
||||
# Use callbacks to share common setup or constraints between actions.
|
||||
def set_code_harbor_link
|
||||
@code_harbor_link = CodeHarborLink.find(params[:id])
|
||||
@code_harbor_link.user = current_user
|
||||
authorize!
|
||||
end
|
||||
|
||||
# Never trust parameters from the scary internet, only allow the white list through.
|
||||
def code_harbor_link_params
|
||||
params.require(:code_harbor_link).permit(:oauth2token)
|
||||
end
|
||||
end
|
@ -11,6 +11,10 @@ class ExercisesController < ApplicationController
|
||||
before_action :set_file_types, only: [:create, :edit, :new, :update]
|
||||
before_action :set_teams, only: [:create, :edit, :new, :update]
|
||||
|
||||
skip_before_filter :verify_authenticity_token, only: [:import_proforma_xml]
|
||||
skip_after_action :verify_authorized, only: [:import_proforma_xml]
|
||||
skip_after_action :verify_policy_scoped, only: [:import_proforma_xml]
|
||||
|
||||
def authorize!
|
||||
authorize(@exercise || @exercises)
|
||||
end
|
||||
@ -62,6 +66,58 @@ class ExercisesController < ApplicationController
|
||||
def edit
|
||||
end
|
||||
|
||||
def import_proforma_xml
|
||||
begin
|
||||
user = user_for_oauth2_request()
|
||||
exercise = Exercise.new
|
||||
request_body = request.body.read
|
||||
exercise.from_proforma_xml(request_body)
|
||||
exercise.user = user
|
||||
saved = exercise.save
|
||||
if saved
|
||||
render :text => 'SUCCESS', :status => 200
|
||||
else
|
||||
logger.info(exercise.errors.full_messages)
|
||||
render :text => 'Invalid exercise', :status => 400
|
||||
end
|
||||
rescue => error
|
||||
if error.class == Hash
|
||||
render :text => error.message, :status => error.status
|
||||
else
|
||||
raise error
|
||||
render :text => '', :status => 500
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def user_for_oauth2_request
|
||||
authorizationHeader = request.headers['Authorization']
|
||||
if authorizationHeader == nil
|
||||
raise ({status: 401, message: 'No Authorization header'})
|
||||
end
|
||||
|
||||
oauth2Token = authorizationHeader.split(' ')[1]
|
||||
if oauth2Token == nil || oauth2Token.size == 0
|
||||
raise ({status: 401, message: 'No token in Authorization header'})
|
||||
end
|
||||
|
||||
user = user_by_code_harbor_token(oauth2Token)
|
||||
if user == nil
|
||||
raise ({status: 401, message: 'Unknown OAuth2 token'})
|
||||
end
|
||||
|
||||
return user
|
||||
end
|
||||
private :user_for_oauth2_request
|
||||
|
||||
def user_by_code_harbor_token(oauth2Token)
|
||||
link = CodeHarborLink.where(:oauth2token => oauth2Token)[0]
|
||||
if link != nil
|
||||
return link.user
|
||||
end
|
||||
end
|
||||
private :user_by_code_harbor_token
|
||||
|
||||
def exercise_params
|
||||
params[:exercise].permit(:description, :execution_environment_id, :file_id, :instructions, :public, :hide_file_tree, :team_id, :title, files_attributes: file_attributes).merge(user_id: current_user.id, user_type: current_user.class.name)
|
||||
end
|
||||
|
2
app/helpers/code_harbor_links_helper.rb
Normal file
2
app/helpers/code_harbor_links_helper.rb
Normal file
@ -0,0 +1,2 @@
|
||||
module CodeHarborLinksHelper
|
||||
end
|
13
app/models/code_harbor_link.rb
Normal file
13
app/models/code_harbor_link.rb
Normal file
@ -0,0 +1,13 @@
|
||||
class CodeHarborLink < ActiveRecord::Base
|
||||
validates :oauth2token, presence: true
|
||||
validates :user_id, presence: true
|
||||
|
||||
belongs_to :internal_user, foreign_key: :user_id
|
||||
alias_method :user, :internal_user
|
||||
alias_method :user=, :internal_user=
|
||||
|
||||
def to_s
|
||||
oauth2token
|
||||
end
|
||||
|
||||
end
|
@ -1,3 +1,4 @@
|
||||
require 'nokogiri'
|
||||
require File.expand_path('../../../lib/active_model/validations/boolean_presence_validator', __FILE__)
|
||||
|
||||
class Exercise < ActiveRecord::Base
|
||||
@ -109,6 +110,54 @@ class Exercise < ActiveRecord::Base
|
||||
exercise
|
||||
end
|
||||
|
||||
def determine_file_role_from_proforma_file(task_node, file_node)
|
||||
file_id = file_node.xpath('@id')
|
||||
file_class = file_node.xpath('@class').first.value
|
||||
comment = file_node.xpath('@comment').first.value
|
||||
is_referenced_by_test = task_node.xpath("p:tests/p:test/p:filerefs/p:fileref[@id=#{file_id}]")
|
||||
is_referenced_by_model_solution = task_node.xpath("p:model-solutions/p:model-solution/p:filerefs/p:fileref[@id=#{file_id}]")
|
||||
if is_referenced_by_test && (file_class == 'internal')
|
||||
return 'teacher_defined_test'
|
||||
elsif is_referenced_by_model_solution && (file_class == 'internal')
|
||||
return 'reference_implementation'
|
||||
elsif (file_class == 'template') && (comment == 'main')
|
||||
return 'main_file'
|
||||
elsif (file_class == 'internal') && (comment == 'main')
|
||||
end
|
||||
return 'regular_file'
|
||||
end
|
||||
|
||||
def from_proforma_xml(xml_string)
|
||||
# how to extract the proforma functionality into a different module in rails?
|
||||
xml = Nokogiri::XML(xml_string)
|
||||
xml.collect_namespaces
|
||||
task_node = xml.xpath('/root/p:task')
|
||||
description = task_node.xpath('p:description/text()')[0].content
|
||||
self.attributes = {
|
||||
title: task_node.xpath('p:meta-data/p:title/text()')[0].content,
|
||||
description: description,
|
||||
instructions: description
|
||||
}
|
||||
task_node.xpath('p:files/p:file').all? { |file|
|
||||
file_name_split = file.xpath('@filename').first.value.split('.')
|
||||
file_class = file.xpath('@class').first.value
|
||||
role = determine_file_role_from_proforma_file(task_node, file)
|
||||
feedback_message_nodes = task_node.xpath("p:tests/p:test/p:test-configuration/c:feedback-message/text()")
|
||||
files.build({
|
||||
name: file_name_split.first,
|
||||
content: file.xpath('text()').first.content,
|
||||
read_only: false,
|
||||
hidden: file_class == 'internal',
|
||||
role: role,
|
||||
feedback_message: (role == 'teacher_defined_test') ? feedback_message_nodes.first.content : nil,
|
||||
file_type: FileType.where(
|
||||
file_extension: ".#{file_name_split.second}"
|
||||
).take
|
||||
})
|
||||
}
|
||||
self.execution_environment_id = 1
|
||||
end
|
||||
|
||||
def generate_token
|
||||
self.token ||= SecureRandom.hex(4)
|
||||
end
|
||||
@ -133,4 +182,5 @@ class Exercise < ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
private :valid_main_file?
|
||||
|
||||
end
|
||||
|
3
app/policies/code_harbor_link_policy.rb
Normal file
3
app/policies/code_harbor_link_policy.rb
Normal file
@ -0,0 +1,3 @@
|
||||
class CodeHarborLinkPolicy < AdminOnlyPolicy
|
||||
|
||||
end
|
@ -8,7 +8,7 @@
|
||||
- if current_user.admin?
|
||||
li = link_to(t('breadcrumbs.dashboard.show'), admin_dashboard_path)
|
||||
li.divider
|
||||
- models = [ExecutionEnvironment, Exercise, Consumer, ExternalUser, FileType, InternalUser, Submission, Team].sort_by { |model| model.model_name.human(count: 2) }
|
||||
- models = [ExecutionEnvironment, Exercise, Consumer, CodeHarborLink, ExternalUser, FileType, InternalUser, Submission, Team].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"))
|
||||
|
6
app/views/code_harbor_links/_form.html.slim
Normal file
6
app/views/code_harbor_links/_form.html.slim
Normal file
@ -0,0 +1,6 @@
|
||||
= form_for(@code_harbor_link) do |f|
|
||||
= render('shared/form_errors', object: @code_harbor_link)
|
||||
.form-group
|
||||
= f.label(:oauth2token)
|
||||
= f.text_field(:oauth2token, class: 'form-control', required: true)
|
||||
.actions = render('shared/submit_button', f: f, object: @code_harbor_link)
|
3
app/views/code_harbor_links/edit.html.slim
Normal file
3
app/views/code_harbor_links/edit.html.slim
Normal file
@ -0,0 +1,3 @@
|
||||
h1 = @code_harbor_link
|
||||
|
||||
= render('form')
|
18
app/views/code_harbor_links/index.html.slim
Normal file
18
app/views/code_harbor_links/index.html.slim
Normal file
@ -0,0 +1,18 @@
|
||||
h1 = CodeHarborLink.model_name.human(count: 2)
|
||||
|
||||
.table-responsive
|
||||
table.table
|
||||
thead
|
||||
tr
|
||||
th = t('activerecord.attributes.code_harbor_link.oauth2token')
|
||||
th colspan=3 = t('shared.actions')
|
||||
tbody
|
||||
- @code_harbor_links.each do |code_harbor_link|
|
||||
tr
|
||||
td = code_harbor_link.oauth2token
|
||||
td = link_to(t('shared.show'), code_harbor_link)
|
||||
td = link_to(t('shared.edit'), edit_code_harbor_link_path(code_harbor_link))
|
||||
td = link_to(t('shared.destroy'), code_harbor_link, data: {confirm: t('shared.confirm_destroy')}, method: :delete)
|
||||
|
||||
= render('shared/pagination', collection: @code_harbor_links)
|
||||
p = render('shared/new_button', model: CodeHarborLink)
|
3
app/views/code_harbor_links/new.html.slim
Normal file
3
app/views/code_harbor_links/new.html.slim
Normal file
@ -0,0 +1,3 @@
|
||||
h1 = t('shared.new_model', model: CodeHarborLink.model_name.human)
|
||||
|
||||
= render('form')
|
7
app/views/code_harbor_links/show.html.slim
Normal file
7
app/views/code_harbor_links/show.html.slim
Normal file
@ -0,0 +1,7 @@
|
||||
h1
|
||||
= @code_harbor_link
|
||||
= render('shared/edit_button', object: @code_harbor_link) if policy(@code_harbor_link).edit?
|
||||
|
||||
- %w[oauth2token].each do |attribute|
|
||||
= row(label: "code_harbor_link.#{attribute}") do
|
||||
= content_tag(:input, nil, class: 'form-control', readonly: true, value: @code_harbor_link.send(attribute))
|
@ -1,6 +1,8 @@
|
||||
de:
|
||||
activerecord:
|
||||
attributes:
|
||||
code_harbor_link:
|
||||
oauth2token: OAuth2 Token
|
||||
consumer:
|
||||
name: Name
|
||||
oauth_key: OAuth Key
|
||||
@ -90,6 +92,9 @@ de:
|
||||
internal_user_ids: Mitglieder
|
||||
name: Name
|
||||
models:
|
||||
code_harbor_link:
|
||||
one: CodeHarbor-Link
|
||||
other: CodeHarbor-Links
|
||||
consumer:
|
||||
one: Konsument
|
||||
other: Konsumenten
|
||||
|
@ -1,6 +1,8 @@
|
||||
en:
|
||||
activerecord:
|
||||
attributes:
|
||||
code_harbor_link:
|
||||
oauth2token: OAuth2 Token
|
||||
consumer:
|
||||
name: Name
|
||||
oauth_key: OAuth Key
|
||||
@ -90,6 +92,9 @@ en:
|
||||
internal_user_ids: Members
|
||||
name: Name
|
||||
models:
|
||||
code_harbor_link:
|
||||
one: CodeHarbor Link
|
||||
other: CodeHarbor Links
|
||||
consumer:
|
||||
one: Consumer
|
||||
other: Consumers
|
||||
|
@ -1,6 +1,7 @@
|
||||
FILENAME_REGEXP = /[\w\.]+/ unless Kernel.const_defined?(:FILENAME_REGEXP)
|
||||
|
||||
Rails.application.routes.draw do
|
||||
resources :code_harbor_links
|
||||
resources :request_for_comments
|
||||
get '/my_request_for_comments', as: 'my_request_for_comments', to: 'request_for_comments#get_my_comment_requests'
|
||||
resources :comments, except: [:destroy] do
|
||||
@ -40,6 +41,8 @@ Rails.application.routes.draw do
|
||||
resources :hints
|
||||
end
|
||||
|
||||
post '/import_proforma_xml' => 'exercises#import_proforma_xml'
|
||||
|
||||
resources :exercises do
|
||||
collection do
|
||||
match '', to: 'exercises#batch_update', via: [:patch, :put]
|
||||
|
9
db/migrate/20160204094409_create_code_harbor_links.rb
Normal file
9
db/migrate/20160204094409_create_code_harbor_links.rb
Normal file
@ -0,0 +1,9 @@
|
||||
class CreateCodeHarborLinks < ActiveRecord::Migration
|
||||
def change
|
||||
create_table :code_harbor_links do |t|
|
||||
t.string :oauth2token
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,5 @@
|
||||
class AddUserToCodeHarborLink < ActiveRecord::Migration
|
||||
def change
|
||||
add_reference :code_harbor_links, :user, index: true, foreign_key: true
|
||||
end
|
||||
end
|
11
db/schema.rb
11
db/schema.rb
@ -11,11 +11,20 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20150922125415) do
|
||||
ActiveRecord::Schema.define(version: 20160204111716) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
||||
create_table "code_harbor_links", force: true do |t|
|
||||
t.string "oauth2token"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.integer "user_id"
|
||||
end
|
||||
|
||||
add_index "code_harbor_links", ["user_id"], name: "index_code_harbor_links_on_user_id", using: :btree
|
||||
|
||||
create_table "comments", force: true do |t|
|
||||
t.integer "user_id"
|
||||
t.integer "file_id"
|
||||
|
Reference in New Issue
Block a user