From d9ff8b9a847c47b95a217a07bf106d8b0fb2d96f Mon Sep 17 00:00:00 2001 From: Antti Leinonen Date: Mon, 26 Oct 2026 22:47:44 +0200 Subject: [PATCH] Allow teachers to create custom courses when enabled for their organization --- .../setup/course_details_controller.rb | 3 +- app/models/ability.rb | 5 ++- .../list_for_teachers.html.erb | 2 +- app/views/courses/index.html.erb | 2 +- app/views/setup/course_chooser/index.html.erb | 2 +- ..._create_custom_courses_to_organizations.rb | 5 +++ db/schema.rb | 3 +- .../setup/course_details_controller_spec.rb | 35 ++++++++++++++++++- 8 files changed, 50 insertions(+), 7 deletions(-) create mode 100644 db/migrate/20261026120000_add_teachers_can_create_custom_courses_to_organizations.rb diff --git a/app/controllers/setup/course_details_controller.rb b/app/controllers/setup/course_details_controller.rb index e46aa4c85..f710770d4 100644 --- a/app/controllers/setup/course_details_controller.rb +++ b/app/controllers/setup/course_details_controller.rb @@ -17,7 +17,7 @@ def new end def custom - authorize! :create, :custom_course + authorize! :create_custom_course, @organization print_setup_phases(2) @course = Course.new @@ -30,6 +30,7 @@ def create authorize! :teach, @organization custom = params[:course][:course_template_id].blank? + authorize! :create_custom_course, @organization if custom create_params = custom ? course_params_for_create_custom : course_params_for_create_from_template input_name = create_params[:name] diff --git a/app/models/ability.rb b/app/models/ability.rb index 5342793c3..812fc015b 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -12,7 +12,6 @@ def initialize(user, auth_source: nil) if user.administrator? can :manage, :all can :create, Course - can :create, :custom_course cannot :refresh, Course can :refresh, Course, &:custom? can :view, :participants_list @@ -73,6 +72,10 @@ def initialize(user, auth_source: nil) can? :teach, c.organization end + can :create_custom_course, Organization do |o| + o.teachers_can_create_custom_courses? && can?(:teach, o) + end + can :refresh, Course do |c| (c.taught_by?(user) || c.assistant?(user)) && c.custom? # user can only refresh his/her custom course. diff --git a/app/views/course_templates/list_for_teachers.html.erb b/app/views/course_templates/list_for_teachers.html.erb index 88f7988c6..49cf87bed 100644 --- a/app/views/course_templates/list_for_teachers.html.erb +++ b/app/views/course_templates/list_for_teachers.html.erb @@ -26,7 +26,7 @@
-<% if can? :create, :custom_course %> +<% if can? :create_custom_course, @organization %>

Create custom course

<%= link_to 'Create New Custom Course', new_custom_setup_organization_course_path(@organization), class: "btn btn-sm btn-warning" %> diff --git a/app/views/courses/index.html.erb b/app/views/courses/index.html.erb index cabcee2b7..5f753f03a 100644 --- a/app/views/courses/index.html.erb +++ b/app/views/courses/index.html.erb @@ -32,7 +32,7 @@ <%= link_to 'Create New Course', setup_organization_course_chooser_index_path(@organization), class: "btn btn-primary" %> <% end %> -<% if can? :create, :custom_course %> +<% if can? :create_custom_course, @organization %>

<%= link_to 'Create Custom Course', new_custom_setup_organization_course_path(@organization), class: "btn btn-sm btn-danger" %> <% end %> diff --git a/app/views/setup/course_chooser/index.html.erb b/app/views/setup/course_chooser/index.html.erb index 0306cd8d8..ab678e2a0 100644 --- a/app/views/setup/course_chooser/index.html.erb +++ b/app/views/setup/course_chooser/index.html.erb @@ -33,7 +33,7 @@
- <% if can? :create, :custom_course %> + <% if can? :create_custom_course, @organization %>

Create custom course (advanced mode)

<%= link_to 'Create New Custom Course', new_custom_setup_organization_course_path(@organization), class: "btn btn-danger" %> <% end %> diff --git a/db/migrate/20261026120000_add_teachers_can_create_custom_courses_to_organizations.rb b/db/migrate/20261026120000_add_teachers_can_create_custom_courses_to_organizations.rb new file mode 100644 index 000000000..1b8771653 --- /dev/null +++ b/db/migrate/20261026120000_add_teachers_can_create_custom_courses_to_organizations.rb @@ -0,0 +1,5 @@ +class AddTeachersCanCreateCustomCoursesToOrganizations < ActiveRecord::Migration[7.1] + def change + add_column :organizations, :teachers_can_create_custom_courses, :boolean, default: false, null: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 9d62cab13..59701d81b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2026_09_17_120000) do +ActiveRecord::Schema[7.1].define(version: 2026_10_26_120000) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -361,6 +361,7 @@ t.text "website" t.boolean "pinned", default: false, null: false t.string "whitelisted_ips", array: true + t.boolean "teachers_can_create_custom_courses", default: false, null: false t.index ["creator_id"], name: "index_organizations_on_creator_id" end diff --git a/spec/controllers/setup/course_details_controller_spec.rb b/spec/controllers/setup/course_details_controller_spec.rb index 4a89200cb..d71914005 100644 --- a/spec/controllers/setup/course_details_controller_spec.rb +++ b/spec/controllers/setup/course_details_controller_spec.rb @@ -78,7 +78,7 @@ describe 'with invalid parameters' do it 'should not create a course' do - post :create, params: { organization_id: @organization.slug, course: { name: 's p a c e s', title: 'New Course', source_url: @repo_path } } + post :create, params: { organization_id: @organization.slug, course: { name: 's p a c e s', title: 'New Course', course_template_id: @ct.id } } expect(response).to render_template(:new) expect(assigns(:course).name).to eq('s p a c e s') end @@ -191,6 +191,26 @@ end end + describe 'when creating custom course' do + it 'denies access when the organization does not allow teachers to create custom courses' do + get :custom, params: { organization_id: @organization.slug } + expect(response.code.to_i).to eq(403) + expect do + post :create, params: { organization_id: @organization.slug, course: { name: 'NewCourse', title: 'New Course', source_url: @repo_path } } + end.not_to change { Course.count } + expect(response.code.to_i).to eq(403) + end + + it 'creates the course when the organization allows teachers to create custom courses' do + @organization.update!(teachers_can_create_custom_courses: true) + get :custom, params: { organization_id: @organization.slug } + expect(response).to render_template(:new) + expect do + post :create, params: { organization_id: @organization.slug, course: { name: 'NewCourse', title: 'New Course', source_url: @repo_path } } + end.to change { Course.count }.by(1) + end + end + ## TODO: Tests for the rest of the actions, when they are properly implemented end @@ -200,6 +220,13 @@ end describe 'when creating custom course' do + it 'shows the custom course form even if the organization does not allow teachers to create custom courses' do + expect(@organization.teachers_can_create_custom_courses).to be false + get :custom, params: { organization_id: @organization.slug } + expect(response).to render_template(:new) + expect(assigns(:custom)).to be true + end + it 'should create the course' do init_session expect do @@ -231,6 +258,12 @@ expect(response.code.to_i).to eq(403) ## TODO: Rest of the actions end + + it 'should not allow creating custom courses even if teachers are allowed to' do + @organization.update!(teachers_can_create_custom_courses: true) + get :custom, params: { organization_id: @organization.slug } + expect(response.code.to_i).to eq(403) + end end def init_session