Commit 9873911d authored by Taico Aerts's avatar Taico Aerts
Browse files

Merge branch 'development' into 'master'

Project Forum Release 2.8.3 - 01-08-2022

See merge request !798
parents 2558d21e 33ff85da
......@@ -34,6 +34,10 @@ gem 'jbuilder', '~> 2.10'
# For rescaling images
gem 'image_processing', '>= 1.2'
# Captcha
gem 'captchah', '~> 1.1'
gem 'mini_magick', '~> 4.0'
# =================================================================================================
# Database and Models
# =================================================================================================
......
......@@ -20,7 +20,7 @@ GIT
GIT
remote: https://github.com/zdennis/activerecord-import.git
revision: 9f631e2adc53b927ff37da442c6913fe621e87ef
revision: 20c1d3aacdd8454e199c0016c125ddfd4f472d2b
specs:
activerecord-import (1.4.0)
activerecord (>= 4.2)
......@@ -45,7 +45,7 @@ PATH
GEM
remote: https://rubygems.org/
specs:
aasm (5.2.0)
aasm (5.3.0)
concurrent-ruby (~> 1.0)
actioncable (6.1.6.1)
actionpack (= 6.1.6.1)
......@@ -114,7 +114,7 @@ GEM
zeitwerk (~> 2.3)
addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0)
airbrussh (1.4.0)
airbrussh (1.4.1)
sshkit (>= 1.6.1, != 1.7.0)
ansi (1.5.0)
ast (2.4.2)
......@@ -150,6 +150,9 @@ GEM
capistrano-rails (1.6.2)
capistrano (~> 3.1)
capistrano-bundler (>= 1.1, < 3)
captchah (1.1.1)
mini_magick
rails
capybara (3.37.1)
addressable
matrix
......@@ -203,7 +206,7 @@ GEM
multi_json
erubi (1.10.0)
execjs (2.8.1)
faker (2.21.0)
faker (2.22.0)
i18n (>= 1.8.11, < 2)
faraday (1.10.0)
faraday-em_http (~> 1.0)
......@@ -229,7 +232,7 @@ GEM
faraday-rack (1.0.0)
faraday-retry (1.0.3)
ffi (1.15.5)
font-awesome-sass (6.1.1)
font-awesome-sass (6.1.2)
sassc (~> 2.0)
generator (0.0.1)
globalid (1.0.0)
......@@ -320,7 +323,7 @@ GEM
net-ssh (>= 2.6.5, < 7.0.0)
net-ssh (6.1.0)
nio4r (2.5.8)
nokogiri (1.13.7-x86_64-linux)
nokogiri (1.13.8-x86_64-linux)
racc (~> 1.4)
orm_adapter (0.5.0)
paper_trail (12.3.0)
......@@ -435,14 +438,11 @@ GEM
selenium-webdriver (3.142.7)
childprocess (>= 0.5, < 4.0)
rubyzip (>= 1.2.2)
sentry-rails (5.3.1)
sentry-rails (5.4.1)
railties (>= 5.0)
sentry-ruby-core (~> 5.3.1)
sentry-ruby (5.3.1)
sentry-ruby (~> 5.4.1)
sentry-ruby (5.4.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
sentry-ruby-core (= 5.3.1)
sentry-ruby-core (5.3.1)
concurrent-ruby
simplecov (0.21.2)
docile (~> 1.1)
simplecov-html (~> 0.11)
......@@ -474,7 +474,7 @@ GEM
terser (1.1.12)
execjs (>= 0.3.0, < 3)
thor (1.2.1)
tilt (2.0.10)
tilt (2.0.11)
turbolinks (5.2.1)
turbolinks-source (~> 5.2)
turbolinks-source (5.2.0)
......@@ -518,6 +518,7 @@ DEPENDENCIES
cancancan
capistrano (~> 3.17)
capistrano-rails (~> 1.6)
captchah (~> 1.1)
capybara (~> 3.18)
caxlsx
caxlsx_rails
......@@ -544,6 +545,7 @@ DEPENDENCIES
letter_opener
letter_opener_web
lightbox2-rails
mini_magick (~> 4.0)
minitest-reporters
mocha (~> 1.13)
mysql2
......
......@@ -31,7 +31,7 @@
}
.login--wide {
max-width: 630px; margin: 15vh auto 0 auto;
max-width: 840px; margin: 15vh auto 0 auto;
}
.login {
......@@ -47,3 +47,46 @@
transition: all .4s;
}
}
.captchah > p.captchah-action {
display: block;
max-width: 100%;
margin-bottom: 5px;
font-weight: 700;
}
.captchah > p.captchah-action::after {
content: " *";
color: #e21a1a;
}
.captchah > input.captchah-guess {
width: 58%;
height: 34px;
padding: 6px 12px;
font-size: 14px;
color: #555555;
background-color: #ffffff;
background-image: none;
border: 1px solid #dddddd;
border-radius: 0px;
}
.captchah > img.captchah-puzzle {
margin-left: 2%;
float: right;
width: 40%
}
.captchah > img.captchah-reload-animation {
float: right;
margin: 2%
}
.captchah > span.captchah-reload {
float: right;
}
.captchah > span.captchah-reload:hover {
cursor: pointer;
}
......@@ -25,4 +25,8 @@ $thumbnail-caption-padding: 15px !default;
background-color: $brand-primary !important;
border-color: black !important;
color: white !important;
}
.thumbnail-inverse:hover {
background-color: darken($brand-primary, 10%) !important;
}
\ No newline at end of file
......@@ -125,6 +125,13 @@
flex-wrap: wrap;
}
@media (min-width: $screen-md-min) {
.row-equal-height-md {
display: flex;
flex-wrap: wrap;
}
}
/* --------------
BORDER
-------------- */
......
......@@ -80,6 +80,10 @@ module Admin
def filter_params
current_course_edition = CourseEdition.supporting_experiment_projects.last
if current_course_edition.nil?
raise ActionController::RoutingError, 'Not Found'
end
edition_id = current_course_edition.id
round_number = current_course_edition.round
......
......@@ -29,12 +29,10 @@ module CourseEditions
apply_search
apply_prefilter
apply_filters
load_filter_options
apply_ordering
include_information
paginate
# Load the options for the filters (displayed on the page)
load_filter_options
end
def show
......
......@@ -306,8 +306,8 @@ class ProjectsController < ApplicationController
# A user course specific role without a user will be created.
role_hash[csr_id].append(
# course_specific_role will be assigned later
# project is assigned later too, since @project does not exist yet
UserCourseSpecificRole.new(
resource: @project,
user: user,
unregistered_name: csr_user_name,
unregistered_email: (user ? nil : csr_user_email)
......@@ -338,7 +338,7 @@ class ProjectsController < ApplicationController
next if csr.assignment_rule == 'not_specifiable_by_client'
role_hash[csr.id.to_s].each do |user_csr|
user_csr.resource_id = @project.id
user_csr.resource = @project
user_csr.course_specific_role = csr
unless user_csr.save
......
module Users
class RegistrationsController < Devise::RegistrationsController
include ParamsConcerns::Users
include Captchah
# Forbid the creation of student users by redirecting the user.
# All other users are forwarded to the devise super class.
def create
user = User.new(email: params[:user][:email])
if user.role_student?
flash[:danger] = 'Please sign in using TU Delft Single Sign-On instead of registering an account manually.'
if user.internal?
redirect_to new_user_sso_session_path
elsif !Rails.env.test? && verify_captchah != :valid
# Don't check captcha in test env
build_resource(sign_up_params)
clean_up_passwords resource
set_minimum_password_length
flash[:danger] = 'The captcha code on the right is incorrect. Please try again.'
render 'new'
else
super
end
......
......@@ -26,7 +26,7 @@ class Offerer < ApplicationRecord
})
scope :sort_by_name, (lambda {
preload(%i[actable]).sort_by(&:name)
includes(%i[actable]).sort_by(&:name)
})
# Parameters present for all offerers
......@@ -81,4 +81,55 @@ class Offerer < ApplicationRecord
def research_group?
actable_type == ResearchGroup.model_name.name
end
# Can be used to retrieve specific attributes of offerers from the database, without loading all the offerers themselves.
# This method returns elements sorted by name (case insenstive).
# Extra conditions can be given as a relation from Offerer, which will be used as base.
# Only the types specified are returned, in the order that they were specified.
#
# Example return:
# sorted_for_collection(types: %w[ResearchGroup Company User], elements: :id) =>
# [[research group id 1, research group id 2, ...], [company id 1, ...], [user id 1, ...]]
# sorted_for_collection(types: %w[ResearchGroup Company User], elements: %i[id name]) =>
# [[[rg 1 id, rg 1 name], [rg 2 id, rg 2 name], ...], [[company 1 id, company 1 name], ...], [[user 1 id, user 1 name], ...]]
# sorted_for_collection(types: 'ResearchGroup', elements: :id) =>
# [research group 1 id, research group 2 id, ...]
#
# @param conditions [ActiveRecord::Relation, Nil] a relation of Offerer which will be used as base for the relations.
# @param types [Array, String, Nil] the types of offerers to include (array of strings)
# @param elements [Symbol, Array] an array of symbols for the attributes to retrieve
# @return [Array] an array for each type, with an array of elements/arrays where each holds the given elements in the given order (see examples)
def self.sorted_for_collection(conditions: nil, types: nil, elements: :id)
conditions ||= Offerer.where('1=1')
types ||= %w[ResearchGroup Company User]
single = types.is_a?(String)
# Map the types into arrays of the elements
types = [types] if single
elements = types.map do |t|
if t == ResearchGroup.model_name.name
conditions.where(actable_type: ResearchGroup.model_name.name)
.joins('INNER JOIN research_groups ON offerers.actable_id = research_groups.id')
.order(ResearchGroup.arel_table['name'].lower.asc)
.pluck(*elements)
elsif t == Company.model_name.name
conditions.where(actable_type: Company.model_name.name)
.joins('INNER JOIN companies ON offerers.actable_id = companies.id')
.order(Company.arel_table['name'].lower.asc)
.pluck(*elements)
elsif t == User.model_name.name
conditions.where(actable_type: User.model_name.name)
.joins('INNER JOIN users ON offerers.actable_id = users.id')
.order(Arel.sql('TRIM(LOWER(last_name))'))
.order(User.arel_table['first_name'].lower.asc)
.pluck(*elements)
else
raise StandardError, "Unknown type: #{t}"
end
end
return elements.first if single
elements
end
end
......@@ -247,6 +247,12 @@ class User < ApplicationRecord
ImportEntry.matching_user(self).each(&:save)
end
# Useful for form selections.
# @return [Array] an array of arrays, where each first element is the id of the user, and the second is the full name of the user
def self.pluck_id_and_full_name
pluck(User.arel_table[:id], Arel.sql('first_name || " " || TRIM(last_name)'))
end
protected
# Called after user creation. Updates role invitations for the current user by associating the user to them.
......
<div class="nested-fields list-group-item">
<div class='pull-left'>
<% staff = User.staff_and_admins.sort_by_name %>
<% students = User.role_student.sort_by_name %>
<% staff = User.unscoped.staff_and_admins.sort_by_name %>
<% students = User.unscoped.role_student.sort_by_name %>
<%= f.grouped_collection_select :id,
%w[Staff Students],
->(s) { s == 'Staff' ? staff : students }, :to_s,
......
<%= bootstrap_form_for [:admin, @course_participation] do |f| %>
<%= render 'admin/users/user_selection_dropdown', form: f, field_id: :user_id,
staff: nil, students: User.role_student, external: nil %>
staff: nil, students: User.unscoped.role_student, external: nil %>
<%= f.grouped_collection_select :course_edition_id,
Course.sort_by_name,
......
......@@ -37,9 +37,9 @@
end %\>
<% other_staff = User.staff_and_admins.where.not(id: interested.ids).sort_by_name %\>
%>
<% other_staff = User.staff_and_admins %>
<% students = User.role_student %>
<% external = User.role_company %>
<% other_staff = User.unscoped.staff_and_admins %>
<% students = User.unscoped.role_student %>
<% external = User.unscoped.role_company %>
<% end %>
<% edition.course_specific_roles.each do |csr| %>
......
<%= bootstrap_form_for [:admin, @experiment_profile] do |f| %>
<%= f.collection_select :user1_id,
User.role_student.sort_by_name, :id, :name,
User.unscoped.role_student.sort_by_name, :id, :name,
{ prompt: true, selected: false },
{ class: 'selectize', placeholder: 'All students' } %>
<%= f.collection_select :user2_id,
User.role_student.sort_by_name, :id, :name,
User.unscoped.role_student.sort_by_name, :id, :name,
{ prompt: true, selected: false },
{ class: 'selectize', placeholder: 'All students' } %>
<%= f.collection_select :experiment_project_id,
......
<div class="nested-fields list-group-item">
<div class="row">
<div class='col-sm-8'>
<% staff = User.staff_and_admins.sort_by_name %>
<% students = User.role_student.sort_by_name %>
<% staff = User.unscoped.staff_and_admins.sort_by_name %>
<% students = User.unscoped.role_student.sort_by_name %>
<%= f.grouped_collection_select :id,
%w[Staff Students],
->(s) { s == 'Staff' ? staff : students }, :to_s,
......
<div class="nested-fields list-group-item">
<div class="list-group-item nested-fields">
<div class="row">
<div class='col-sm-8'>
<%= f.hidden_field :id, value: f.object.id %>
<%= f.collection_select :user_id,
User.role_student.sort_by_name, :id, :name,
User.unscoped.role_student.sort_by_name.pluck_id_and_full_name, :first, :second,
{prompt: true, selected: f.object.user_id},
class: 'selectize' %>
</div>
......
<div class="nested-fields list-group-item">
<div class="row">
<div class='col-sm-8'>
<% staff = User.staff_and_admins.sort_by_name %>
<% students = User.role_student.sort_by_name %>
<% external = User.role_company.sort_by_name %>
<% staff = User.unscoped.staff_and_admins.sort_by_name.pluck_id_and_full_name %>
<% students = User.unscoped.role_student.sort_by_name.pluck_id_and_full_name %>
<% external = User.unscoped.role_company.sort_by_name.pluck_id_and_full_name %>
<%= f.grouped_collection_select :id,
%w[Staff Students External],
->(s) {
......@@ -15,7 +15,7 @@
external
end
}, :to_s,
:id, :name,
:first, :second,
{ prompt: true, selected: f.object&.id },
class: 'selectize' %>
</div>
......
......@@ -3,7 +3,7 @@
<div class='col-sm-8'>
<%= f.hidden_field :id, value: f.object.id %>
<%= f.collection_select :user_id,
User.role_student.sort_by_name, :id, :name,
User.unscoped.role_student.sort_by_name.pluck_id_and_full_name, :first, :second,
{prompt: true, selected: f.object.user_id},
class: 'selectize' %>
</div>
......
......@@ -2,7 +2,7 @@
<div class="row">
<div class="col-sm-11">
<%= f.collection_select :user_id,
User.sort_by_name, :id, :full_name,
User.unscoped.sort_by_name.pluck_id_and_full_name, :first, :second,
{ prompt: true, selected: f.object.user_id },
class: 'selectize' %>
</div>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment