canvas-lms/app/controllers/assignments_api_controller.rb

435 lines
16 KiB
Ruby
Raw Normal View History

2011-02-01 09:57:29 +08:00
#
# Copyright (C) 2011 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 Assignments
#
# API for accessing assignment information.
#
# @object Assignment
# {
# // the ID of the assignment
# id: 4,
#
# // the name of the assignment
# name: "some assignment",
#
# // the assignment description, in an HTML fragment
# description: "<p>Do the following:</p>...",
#
# // the due date for the assignment. returns null if not present.
# due_at: "2012-07-01T23:59:00-06:00",
#
# // the lock date (assignment is locked after this date). returns
# // null if not present.
# lock_at: "2012-07-01T23:59:00-06:00",
#
# // the unlock date (assignment is unlocked after this date)
# // returns null if not present
# unlock_at: "2012-07-01T23:59:00-06:00",
#
# // the ID of the course the assignment belongs to
# course_id: 123,
#
# // the URL to the assignment's web page
# html_url: "http://canvas.example.com/courses/123/assignments/4",
#
# // the ID of the assignment's group
# assignment_group_id: 2,
#
# // Allowed file extensions, which take effect if submission_types
# // includes "online_upload".
# allowed_extensions: [ "docx", "ppt" ],
#
# // Boolean flag indicating whether or not Turnitin has been enabled
# // for the assignment.
# // NOTE: This flag will not appear unless your account has the
# // Turnitin plugin available
# turnitin_enabled: true,
#
# // Settings to pass along to turnitin to control what kinds of matches
# // should be considered.
# // originality_report_visibility can be 'immediate', 'after_grading', or 'after_due_date'
# // exclude_small_matches_type can be null, 'percent', 'words'
# // exclude_small_matches_value:
# // - if type is null, this will be null also
# // - if type is 'percent', this will be a number between 0 and 100
# // representing match size to exclude as a percentage of the document size.
# // - if type is 'words', this will be number > 0 representing how many
# // words a match must contain for it to be considered
# // NOTE: This flag will not appear unless your account has the
# // Turnitin plugin available
# turnitin_settings: {
# originality_report_visibility => 'after_grading',
# s_paper_check => false,
# internet_check => false,
# journal_check => false,
# exclude_biblio => false,
# exclude_quoted => false,
# exclude_small_matches_type => 'percent',
# exclude_small_matches_value => 50,
# },
#
# // If this is a group assignment, boolean flag indicating whether or
# // not students will be graded individually.
# grade_group_students_individually: false,
#
# // (Optional) assignment's settings for external tools if
# // submission_types include "external_tool".
# // Only url and new_tab are included.
# // Use the "External Tools" API if you need more information about
# // an external tool.
# external_tool_tag_attributes: {
# // URL to the external tool
# url: "http://instructure.com",
# // Whether or not there is a new tab for the external tool
# new_tab: false
# },
#
# // Boolean indicating if peer reviews are required for this assignment
# peer_reviews: false,
#
# // Boolean indicating peer reviews are assigned automatically.
# // If false, the teacher is expected to manually assign peer reviews.
# automatic_peer_reviews: false,
#
# // the ID of the assignments group set (if this is a group assignment)
# group_category_id: 1
#
# // if the requesting user has grading rights, the number of submissions that need grading.
# needs_grading_count: 17,
#
# // the sorting order of the assignment in the group
# position: 1,
#
# // the URL to the Canvas web UI page for the assignment
# html_url: "https://...",
#
# // whether the assignment is muted
# muted: false,
#
# // the maximum points possible for the assignment
# points_possible: 12,
#
# // the types of submissions allowed for this assignment
# // list containing one or more of the following:
# // "discussion_topic", "online_quiz", "on_paper", "none",
# // "external_tool", "online_text_entry", "online_url", "online_upload"
# // "media_recording"
# submission_types: ["online_text_entry"],
#
# // The type of grading the assignment receives; one of "pass_fail",
# // "percent", "letter_grade", "points"
# grading_type: "points",
#
# // (Optional) explanation of lock status
# lock_explanation: "This assignment is locked until September 1 at 12:00am",
#
# // (Optional) whether anonymous submissions are accepted (applies only to quiz assignments)
# anonymous_submissions: false,
#
# // (Optional) the DiscussionTopic associated with the assignment, if applicable
# discussion_topic: { ... },
#
# // (Optional) Boolean indicating if assignment is frozen.
# // NOTE: This field will only be present if the AssignmentFreezer
# // plugin is available for your account.
# frozen: false,
#
# // (Optional) Array of frozen attributes for the assignment.
# // Only account administrators currently have permission to
# // change an attribute in this list. Will be empty if no attributes
# // are frozen for this assignment.
# // NOTE: This field will only be present if the AssignmentFreezer
# // plugin is available for your account.
# frozen_attributes: [ "title" ],
#
# // (Optional) If true, the rubric is directly tied to grading the assignment.
# // Otherwise, it is only advisory. Included if there is an associated rubric.
# use_rubric_for_grading: true,
#
# // (Optional) An object describing the basic attributes of the rubric, including
# // the point total. Included if there is an associated rubric.
# rubric_settings: {
# points_possible: 12
# },
#
# // (Optional) A list of scoring criteria and ratings for each rubric criterion.
# // Included if there is an associated rubric.
# rubric: [
# {
# "points": 10,
# "id": "crit1",
# "description": "Criterion 1",
# "ratings": [
# {
# "points": 10,
# "id": "rat1",
# "description": "Full marks"
# },
# {
# "points": 7,
# "id": "rat2",
# "description": "Partial answer"
# },
# {
# "points": 0,
# "id": "rat3",
# "description": "No marks"
# }
# ]
# },
# {
# "points": 2,
# "id": "crit2",
# "description": "Criterion 2",
# "ratings": [
# {
# "points": 2,
# "id": "rat1",
# "description": "Pass"
# },
# {
# "points": 0,
# "id": "rat2",
# "description": "Fail"
# }
# ]
# }
# ]
# }
#
2011-02-01 09:57:29 +08:00
class AssignmentsApiController < ApplicationController
before_filter :require_context
include Api::V1::Assignment
include Api::V1::AssignmentOverride
2011-02-01 09:57:29 +08:00
# @API List assignments
# Returns the list of assignments for the current context.
# @returns [Assignment]
2011-02-01 09:57:29 +08:00
def index
if authorized_action(@context, @current_user, :read)
@assignments = @context.active_assignments.find(:all,
:include => [:assignment_group, :rubric_association, :rubric],
:order => "assignment_groups.position, assignments.position")
2011-02-01 09:57:29 +08:00
hashes = @assignments.map { |assignment|
assignment_json(assignment, @current_user, session) }
2011-02-01 09:57:29 +08:00
render :json => hashes.to_json
end
end
# @API Get a single assignment
# Returns the assignment with the given id.
# @returns Assignment
2011-02-01 09:57:29 +08:00
def show
if authorized_action(@context, @current_user, :read)
@assignment = @context.active_assignments.find(params[:id],
:include => [:assignment_group, :rubric_association, :rubric])
modules api, closes #10404 also modifies the discussion topic and assignment API controllers to make sure "must_view" requirements are fulfilled test plan: * check the API documentation; ensure it looks okay * create a course with module items of each supported type * set completion criteria of each supported type * create another module, so you can set prerequisites * use the list modules API and verify its output matches the course and the documentation * as a teacher, "state" should be missing * as a student, "state" should be "locked", "unlocked", "started", or "completed" * use the show module API and verify the correct information is returned for a single module * use the list module items API and verify the output * as a teacher, the "completion_requirement" omits the "completed" flag * as a student, "completed" should be true or false, depending on whether the requirement was met * use the show module API and verify the correct information is returned for a single module item * last but not least, verify "must view" requirements can be fulfilled through the api_data_endpoints supplied for files, pages, discussions, and assignments * files are viewed when downloading their content * pages are viewed by the show action (where content is returned) * discussions are viewed when marked read via the mark_topic_read or mark_all_read actions * assignments are viewed by the show action (where description is returned). they are not viewed if the assignment is locked and the user does not have access to the content yet. Change-Id: I0cbbbc542f69215e7b396a501d4d86ff2f76c149 Reviewed-on: https://gerrit.instructure.com/13626 Tested-by: Jenkins <jenkins@instructure.com> Reviewed-by: Simon Williams <simon@instructure.com>
2012-09-12 01:16:48 +08:00
@assignment.context_module_action(@current_user, :read) unless @assignment.locked_for?(@current_user, :check_policies => true)
render :json => assignment_json(@assignment, @current_user, session)
2011-02-01 09:57:29 +08:00
end
end
# @API Create an assignment
2011-02-01 09:57:29 +08:00
# Create a new assignment for this course. The assignment is created in the
# active state.
#
# @argument assignment[name] The assignment name.
#
# @argument assignment[position] [Integer]
# The position of this assignment in the group when displaying
# assignment lists.
#
# @argument assignment[submission_types] [Array]
# List of supported submission types for the assignment.
# Unless the assignment is allowing online submissions, the array should
# only have one element.
#
# If not allowing online submissions, your options are:
# "online_quiz"
# "none"
# "on_paper"
# "online_quiz"
# "discussion_topic"
# "external_tool"
#
# If you are allowing online submissions, you can have one or many
# allowed submission types:
#
# "online_upload"
# "online_media_recording"
# "online_text_entry"
# "online_url"
# "online_media_recording" Only valid when the Kaltura plugin is enabled.
#
# @argument assignment[allowed_extensions] [Array]
# Allowed extensions if submission_types includes "online_upload"
#
# Example:
# allowed_extensions: ["docx","ppt"]
#
# @argument assignment[turnitin_enabled] [Optional,Boolean]
# Only applies when the Turnitin plugin is enabled for a course and
# the submission_types array includes "online_upload".
# Toggles Turnitin submissions for the assignment.
# Will be ignored if Turnitin is not available for the course.
#
# @argument assignment[turnitin_settings] [Optional]
# Settings to send along to turnitin. See Assignment object definition for
# format.
#
# @argument assignment[peer_reviews] [Optional,Boolean]
# If submission_types does not include external_tool,discussion_topic,
# online_quiz, or on_paper, determines whether or not peer reviews
# will be turned on for the assignment.
#
# @argument assignment[automatic_peer_reviews] [Optional,Boolean]
# Whether peer reviews will be assigned automatically by Canvas or if
# teachers must manually assign peer reviews. Does not apply if peer reviews
# are not enabled.
#
# @argument assignment[notify_of_update] [Optional,Boolean]
# If true, Canvas will send a notification to students in the class
# notifying them that the content has changed.
#
# @argument assignment[group_category_id] [Optional,Integer]
# If present, the assignment will become a group assignment assigned
# to the group.
#
# @argument assignment[grade_group_students_individually] [Optional,Integer]
# If this is a group assignment, teachers have the options to grade
# students individually. If false, Canvas will apply the assignment's
# score to each member of the group. If true, the teacher can manually
# assign scores to each member of the group.
#
# @argument assignment[external_tool_tag_attributes] [Optional]
# Hash of attributes if submission_types is ["external_tool"]
# Example:
# external_tool_tag_attributes: {
# // url to the external tool
# url: "http://instructure.com",
# // create a new tab for the module, defaults to false.
# new_tab: false
# }
#
2011-02-01 09:57:29 +08:00
# @argument assignment[points_possible] [Float] The maximum points possible on
# the assignment.
#
# @argument assignment[grading_type] [Optional, "pass_fail"|"percent"|"letter_grade"|"points"]
# The strategy used for grading the assignment.
# The assignment is ungraded if this field is omitted.
# @argument assignment[due_at] [Timestamp]
# The day/time the assignment is due.
# Accepts times in ISO 8601 format, e.g. 2011-10-21T18:48Z.
#
# @argument assignment[lock_at] [Timestamp]
# The day/time the assignment is locked after.
# Accepts times in ISO 8601 format, e.g. 2011-10-21T18:48Z.
#
# @argument assignment[unlock_at] [Timestamp]
# The day/time the assignment is unlocked.
# Accepts times in ISO 8601 format, e.g. 2011-10-21T18:48Z.
#
# @argument assignment[description] [String]
# The assignment's description, supports HTML.
#
# @argument assignment[assignment_group_id] [Integer]
# The assignment group id to put the assignment in.
# Defaults to the top assignment group in the course.
#
# @argument assignment[assignment_overrides] [Optional, [AssignmentOverride]]
# List of overrides for the assignment.
#
# @returns Assignment
2011-02-01 09:57:29 +08:00
def create
@assignment = @context.assignments.build
2011-02-01 09:57:29 +08:00
if authorized_action(@assignment, @current_user, :create)
save_and_render_response
2011-02-01 09:57:29 +08:00
end
end
# @API Edit an assignment
2011-02-01 09:57:29 +08:00
# Modify an existing assignment. See the documentation for assignment
# creation.
#
# If the assignment[assignment_overrides] key is absent, any existing
# overrides are kept as is. If the assignment[assignment_overrides] key is
# present, existing overrides are updated or deleted (and new ones created,
# as necessary) to match the provided list.
#
# @returns Assignment
2011-02-01 09:57:29 +08:00
def update
@assignment = @context.assignments.find(params[:id])
2011-02-01 09:57:29 +08:00
if authorized_action(@assignment, @current_user, :update_content)
save_and_render_response
end
end
def save_and_render_response
if @assignment.frozen_for_user?( @current_user )
render :json => {:message => t("errors.no_edit_frozen",
"You do not have permission to edit frozen assignments. Please see your account administrator."
)}.to_json, :status => 400
elsif update_and_save_assignment(@assignment, params[:assignment])
render :json => assignment_json(@assignment, @current_user, session).to_json, :status => 201
else
# TODO: we don't really have a strategy in the API yet for returning
# errors.
render :json => "error".to_json, :status => 400
2011-02-01 09:57:29 +08:00
end
end
protected
def update_and_save_assignment(assignment, assignment_params)
return if assignment_params.nil?
# convert hashes like {0 => x, 1 => y} into arrays like [x, y]
overrides = assignment_params[:assignment_overrides]
if overrides.is_a?(Hash)
return unless overrides.keys.all?{ |k| k.to_i.to_s == k.to_s }
indices = overrides.keys.sort_by(&:to_i)
return unless indices.map(&:to_i) == (0...indices.size).to_a
overrides = indices.map{ |index| overrides[index] }
end
# require it to be formatted as an array if it's present
return if overrides && !overrides.is_a?(Array)
# do the updating
VDD: notifications; closes #10896 The following changes have been made: - Assignment Created - students see the due date that applies to them - admins see "Multiple Dates" - Assignment Due Date Changed - students see the due date that applies to them; they receive no notification if their date doesn't change - admins receive a separate notification for each due date that changes, that they have access to; the message indicates which section or group applies (section-limited TAs will not get messages about due dates in sections they can't see) - Assignment Submitted Late - the message text does not change, but the student's overridden due date is checked - Group Assignment Submitted Late - same as previous There were some bugs fixed along the way: - no longer send duplicate Assignment Submitted and Assignment Resubmitted notifications when an assignment is resubmitted - Group Assignment Submitted Late actually goes out (there was a typo in the whenever clause) Test plan: - Create a course with two sections and a teacher - Enroll a student in each section - Enroll a section-limited TA in each section - Make sure everybody involved is signed up for "Due Date" notifications, ASAP - Using the API, Create an assignment with a default due date (in the past) and an overridden due date for section 2 (in the future). the assignment and override must be created in the same request (use the "Create an assignment" API and supply assignment[assignment_overrides]; it may be easier to use a JSON request body) - Verify that everybody got an "Assignment Created" message (use /users/X/messages) - the teacher should see "Multiple Dates", as should the TA in section 2 (because the default date is still visible to him) - the student and the TA in section 1 should see the default due date - the student in section 2 should see the overridden due date - "Due Date Changed" messages will not go out for assignments that were created less than 3 hours ago (by design, and not new with this changeset), so for the remaining items, you either need to wait 3 hours, or falsify created_at for the assignment you just made... - Change the default due date for the assignment, leaving it in the past - Everybody except the student in section 2 should get a notification with the new date - Change the overridden due date for section 2, leaving it in the future - everybody except the teacher and TA in section 1 should get a notification about the new date - the teacher and section-2 TA's notifications should indicate that they apply to section 2 (the student's should not) - submit the assignment as each student - the teacher should get one notification about each submission: the one about the student in section 1 should say it's late; the one about the student in section 2 should not - submit again - the teacher should get one notification about each submission: the one about the student in section 1 should say it's late; the one about the student in section 2 should not, and should be identified as a resubmission (there is no late-re-submission notification) Change-Id: I26e57807ea0c83b69e2b532ec8822f6570ba1701 Reviewed-on: https://gerrit.instructure.com/14662 Tested-by: Jenkins <jenkins@instructure.com> Reviewed-by: Jeremy Stanley <jeremy@instructure.com>
2012-10-27 04:10:08 +08:00
update_api_assignment(assignment, assignment_params, false)
if overrides
assignment.transaction do
assignment.save_without_broadcasting!
batch_update_assignment_overrides(assignment, overrides)
end
assignment.do_notifications!
else
assignment.save!
end
return true
rescue ActiveRecord::RecordInvalid
return false
end
2011-02-01 09:57:29 +08:00
end