347 lines
15 KiB
Ruby
347 lines
15 KiB
Ruby
#
|
|
# Copyright (C) 2011 - present Instructure, Inc.
|
|
#
|
|
# This file is part of Canvas.
|
|
#
|
|
# Canvas is free software: you can redistribute it and/or modify it under
|
|
# the terms of the GNU Affero General Public License as published by the Free
|
|
# Software Foundation, version 3 of the License.
|
|
#
|
|
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
|
# details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License along
|
|
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
|
|
# @API Sections
|
|
#
|
|
# API for accessing section information.
|
|
#
|
|
# @model Section
|
|
# {
|
|
# "id": "Section",
|
|
# "description": "",
|
|
# "properties": {
|
|
# "id": {
|
|
# "description": "The unique identifier for the section.",
|
|
# "example": 1,
|
|
# "type": "integer"
|
|
# },
|
|
# "name": {
|
|
# "description": "The name of the section.",
|
|
# "example": "Section A",
|
|
# "type": "string"
|
|
# },
|
|
# "sis_section_id": {
|
|
# "description": "The sis id of the section. This field is only included if the user has permission to view SIS information.",
|
|
# "example": "s34643",
|
|
# "type": "string"
|
|
# },
|
|
# "integration_id": {
|
|
# "description": "Optional: The integration ID of the section. This field is only included if the user has permission to view SIS information.",
|
|
# "example": "3452342345",
|
|
# "type": "string"
|
|
# },
|
|
# "sis_import_id": {
|
|
# "description": "The unique identifier for the SIS import if created through SIS. This field is only included if the user has permission to manage SIS information.",
|
|
# "example": 47,
|
|
# "type": "integer"
|
|
# },
|
|
# "course_id": {
|
|
# "description": "The unique Canvas identifier for the course in which the section belongs",
|
|
# "example": 7,
|
|
# "type": "integer"
|
|
# },
|
|
# "sis_course_id": {
|
|
# "description": "The unique SIS identifier for the course in which the section belongs. This field is only included if the user has permission to view SIS information.",
|
|
# "example": 7,
|
|
# "type": "string"
|
|
# },
|
|
# "start_at": {
|
|
# "description": "the start date for the section, if applicable",
|
|
# "example": "2012-06-01T00:00:00-06:00",
|
|
# "type": "datetime"
|
|
# },
|
|
# "end_at": {
|
|
# "description": "the end date for the section, if applicable",
|
|
# "type": "datetime"
|
|
# },
|
|
# "nonxlist_course_id": {
|
|
# "description": "The unique identifier of the original course of a cross-listed section",
|
|
# "type": "integer"
|
|
# },
|
|
# "total_students": {
|
|
# "description": "optional: the total number of active and invited students in the section",
|
|
# "example": 13,
|
|
# "type": "integer"
|
|
# }
|
|
# }
|
|
# }
|
|
#
|
|
class SectionsController < ApplicationController
|
|
before_action :require_context
|
|
before_action :require_section, :except => [:index, :create]
|
|
|
|
include Api::V1::Section
|
|
|
|
# @API List course sections
|
|
# Returns the list of sections for this course.
|
|
#
|
|
# @argument include[] [String, "students"|"avatar_url"|"enrollments"|"total_students"|"passback_status"]
|
|
# - "students": Associations to include with the group. Note: this is only
|
|
# available if you have permission to view users or grades in the course
|
|
# - "avatar_url": Include the avatar URLs for students returned.
|
|
# - "enrollments": If 'students' is also included, return the section
|
|
# enrollment for each student
|
|
# - "total_students": Returns the total amount of active and invited students
|
|
# for the course section
|
|
# - "passback_status": Include the grade passback status.
|
|
#
|
|
# @returns [Section]
|
|
def index
|
|
if authorized_action(@context, @current_user, [:read, :read_roster, :view_all_grades, :manage_grades])
|
|
if params[:include].present? && !@context.grants_any_right?(@current_user, session, :read_roster, :view_all_grades, :manage_grades)
|
|
params[:include] = nil
|
|
end
|
|
|
|
includes = Array(params[:include])
|
|
|
|
sections = @context.active_course_sections.order(CourseSection.best_unicode_collation_key('name'))
|
|
|
|
unless params[:all].present?
|
|
sections = Api.paginate(sections, self, api_v1_course_sections_url)
|
|
end
|
|
|
|
render :json => sections_json(sections, @current_user, session, includes)
|
|
end
|
|
end
|
|
|
|
# @API Create course section
|
|
# Creates a new section for this course.
|
|
#
|
|
# @argument course_section[name] [String]
|
|
# The name of the section
|
|
#
|
|
# @argument course_section[sis_section_id] [String]
|
|
# The sis ID of the section
|
|
#
|
|
# @argument course_section[start_at] [DateTime]
|
|
# Section start date in ISO8601 format, e.g. 2011-01-01T01:00Z
|
|
#
|
|
# @argument course_section[end_at] [DateTime]
|
|
# Section end date in ISO8601 format. e.g. 2011-01-01T01:00Z
|
|
#
|
|
# @argument course_section[restrict_enrollments_to_section_dates] [Boolean]
|
|
# Set to true to restrict user enrollments to the start and end dates of the section.
|
|
#
|
|
# @argument enable_sis_reactivation [Boolean]
|
|
# When true, will first try to re-activate a deleted section with matching sis_section_id if possible.
|
|
#
|
|
# @returns Section
|
|
def create
|
|
if authorized_action(@context.course_sections.temp_record, @current_user, :create)
|
|
sis_section_id = params[:course_section].try(:delete, :sis_section_id)
|
|
can_manage_sis = api_request? && sis_section_id.present? &&
|
|
@context.root_account.grants_right?(@current_user, session, :manage_sis)
|
|
|
|
if can_manage_sis && value_to_boolean(params[:enable_sis_reactivation])
|
|
@section = @context.course_sections.where(:sis_source_id => sis_section_id, :workflow_state => 'deleted').first
|
|
@section.workflow_state = 'active' if @section
|
|
end
|
|
@section ||= @context.course_sections.build(course_section_params)
|
|
@section.sis_source_id = sis_section_id if can_manage_sis
|
|
|
|
respond_to do |format|
|
|
if @section.save
|
|
@context.touch
|
|
flash[:notice] = t('section_created', "Section successfully created!")
|
|
format.html { redirect_to course_settings_url(@context) }
|
|
format.json { render :json => (api_request? ? section_json(@section, @current_user, session, []) : @section) }
|
|
else
|
|
flash[:error] = t('section_creation_failed', "Section creation failed")
|
|
format.html { redirect_to course_settings_url(@context) }
|
|
format.json { render :json => @section.errors, :status => :bad_request }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def require_section
|
|
case @context
|
|
when Course
|
|
section_id = params[:section_id] || params[:id]
|
|
@section = api_find(@context.active_course_sections, section_id)
|
|
when CourseSection
|
|
@section = @context
|
|
raise ActiveRecord::RecordNotFound if @section.deleted? || @section.course.try(:deleted?)
|
|
else
|
|
raise ActiveRecord::RecordNotFound
|
|
end
|
|
end
|
|
|
|
def crosslist_check
|
|
course_id = params[:new_course_id]
|
|
# cross-listing should only be allowed within the same root account
|
|
@new_course = @section.root_account.all_courses.not_deleted.where(id: course_id).first if course_id =~ Api::ID_REGEX
|
|
@new_course ||= @section.root_account.all_courses.not_deleted.where(sis_source_id: course_id).first if course_id.present?
|
|
allowed = @new_course && @section.grants_right?(@current_user, session, :update) && @new_course.grants_right?(@current_user, session, :manage)
|
|
res = {:allowed => !!allowed}
|
|
if allowed
|
|
@account = @new_course.account
|
|
res[:section] = @section.as_json(include_root: false)
|
|
res[:course] = @new_course.as_json(include_root: false)
|
|
res[:account] = @account.as_json(include_root: false)
|
|
end
|
|
render :json => res
|
|
end
|
|
|
|
# @API Cross-list a Section
|
|
# Move the Section to another course. The new course may be in a different account (department),
|
|
# but must belong to the same root account (institution).
|
|
#
|
|
# @returns Section
|
|
def crosslist
|
|
@new_course = api_find(@section.root_account.all_courses.not_deleted, params[:new_course_id])
|
|
if authorized_action(@section, @current_user, :update) && authorized_action(@new_course, @current_user, :manage)
|
|
@section.crosslist_to_course @new_course
|
|
respond_to do |format|
|
|
flash[:notice] = t('section_crosslisted', "Section successfully cross-listed!")
|
|
format.html { redirect_to named_context_url(@new_course, :context_section_url, @section.id) }
|
|
format.json { render :json => (api_request? ? section_json(@section, @current_user, session, []) : @section) }
|
|
end
|
|
end
|
|
end
|
|
|
|
# @API De-cross-list a Section
|
|
# Undo cross-listing of a Section, returning it to its original course.
|
|
#
|
|
# @returns Section
|
|
def uncrosslist
|
|
@new_course = @section.nonxlist_course
|
|
return render(:json => {:message => "section is not cross-listed"}, :status => :bad_request) if @new_course.nil?
|
|
if authorized_action(@section, @current_user, :update) && authorized_action(@new_course, @current_user, :manage)
|
|
@section.uncrosslist
|
|
respond_to do |format|
|
|
flash[:notice] = t('section_decrosslisted', "Section successfully de-cross-listed!")
|
|
format.html { redirect_to named_context_url(@new_course, :context_section_url, @section.id) }
|
|
format.json { render :json => (api_request? ? section_json(@section, @current_user, session, []) : @section) }
|
|
end
|
|
end
|
|
end
|
|
|
|
# @API Edit a section
|
|
# Modify an existing section.
|
|
#
|
|
# @argument course_section[name] [String]
|
|
# The name of the section
|
|
#
|
|
# @argument course_section[sis_section_id] [String]
|
|
# The sis ID of the section
|
|
#
|
|
# @argument course_section[start_at] [DateTime]
|
|
# Section start date in ISO8601 format, e.g. 2011-01-01T01:00Z
|
|
#
|
|
# @argument course_section[end_at] [DateTime]
|
|
# Section end date in ISO8601 format. e.g. 2011-01-01T01:00Z
|
|
#
|
|
# @argument course_section[restrict_enrollments_to_section_dates] [Boolean]
|
|
# Set to true to restrict user enrollments to the start and end dates of the section.
|
|
#
|
|
# @returns Section
|
|
def update
|
|
params[:course_section] ||= {}
|
|
if authorized_action(@section, @current_user, :update)
|
|
params[:course_section][:sis_source_id] = params[:course_section].delete(:sis_section_id) if api_request?
|
|
if sis_id = params[:course_section].delete(:sis_source_id)
|
|
if sis_id != @section.sis_source_id && @section.root_account.grants_right?(@current_user, session, :manage_sis)
|
|
if sis_id == ''
|
|
@section.sis_source_id = nil
|
|
else
|
|
@section.sis_source_id = sis_id
|
|
end
|
|
end
|
|
end
|
|
respond_to do |format|
|
|
if @section.update_attributes(course_section_params)
|
|
@context.touch
|
|
flash[:notice] = t('section_updated', "Section successfully updated!")
|
|
format.html { redirect_to course_section_url(@context, @section) }
|
|
format.json { render :json => (api_request? ? section_json(@section, @current_user, session, []) : @section) }
|
|
else
|
|
flash[:error] = t('section_update_error', "Section update failed")
|
|
format.html { redirect_to course_section_url(@context, @section) }
|
|
format.json { render :json => @section.errors, :status => :bad_request }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
# @API Get section information
|
|
# Gets details about a specific section
|
|
#
|
|
# @argument include[] [String, "students"|"avatar_url"|"enrollments"|"total_students"|"passback_status"]
|
|
# - "students": Associations to include with the group. Note: this is only
|
|
# available if you have permission to view users or grades in the course
|
|
# - "avatar_url": Include the avatar URLs for students returned.
|
|
# - "enrollments": If 'students' is also included, return the section
|
|
# enrollment for each student
|
|
# - "total_students": Returns the total amount of active and invited students
|
|
# for the course section
|
|
# - "passback_status": Include the grade passback status.
|
|
#
|
|
# @returns Section
|
|
def show
|
|
if authorized_action(@section, @current_user, :read)
|
|
respond_to do |format|
|
|
format.html do
|
|
add_crumb(@section.name, named_context_url(@context, :context_section_url, @section))
|
|
@enrollments_count = @section.enrollments.not_fake.where(:workflow_state => 'active').count
|
|
@completed_enrollments_count = @section.enrollments.not_fake.where(:workflow_state => 'completed').count
|
|
@pending_enrollments_count = @section.enrollments.not_fake.where(:workflow_state => %w{invited pending}).count
|
|
@student_enrollments_count = @section.enrollments.not_fake.where(:type => 'StudentEnrollment').count
|
|
can_manage_students = @context.grants_right?(@current_user, session, :manage_students) || @context.grants_right?(@current_user, session, :manage_admin_users)
|
|
js_env(
|
|
:PERMISSIONS => {
|
|
:manage_students => can_manage_students,
|
|
:manage_account_settings => @context.account.grants_right?(@current_user, session, :manage_account_settings)
|
|
})
|
|
if @context.grants_right?(@current_user, session, :manage)
|
|
js_env STUDENT_CONTEXT_CARDS_ENABLED: @domain_root_account.feature_enabled?(:student_context_cards)
|
|
end
|
|
end
|
|
format.json { render :json => section_json(@section, @current_user, session, Array(params[:include])) }
|
|
end
|
|
end
|
|
end
|
|
|
|
# @API Delete a section
|
|
# Delete an existing section. Returns the former Section.
|
|
#
|
|
# @returns Section
|
|
def destroy
|
|
if authorized_action(@section, @current_user, :delete)
|
|
respond_to do |format|
|
|
if @section.deletable?
|
|
@section.destroy
|
|
@context.touch
|
|
flash[:notice] = t('section_deleted', "Course section successfully deleted!")
|
|
format.html { redirect_to course_settings_url(@context) }
|
|
format.json { render :json => (api_request? ? section_json(@section, @current_user, session, []) : @section) }
|
|
else
|
|
flash[:error] = t('section_delete_not_allowed', "You can't delete a section that has enrollments")
|
|
format.html { redirect_to course_section_url(@context, @section) }
|
|
format.json { render :json => (api_request? ? { :message => "You can't delete a section that has enrollments" } : @section), :status => :bad_request }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
protected
|
|
def course_section_params
|
|
params[:course_section] ? params[:course_section].permit(:name, :start_at, :end_at, :restrict_enrollments_to_section_dates) : {}
|
|
end
|
|
end
|