diff --git a/alembic/versions/2561c39ac4d9_allow_instructor_answers_to_be_included_.py b/alembic/versions/2561c39ac4d9_allow_instructor_answers_to_be_included_.py
new file mode 100644
index 000000000..1536f834c
--- /dev/null
+++ b/alembic/versions/2561c39ac4d9_allow_instructor_answers_to_be_included_.py
@@ -0,0 +1,53 @@
+"""Allow instructor answers to be included in comparisons
+
+Revision ID: 2561c39ac4d9
+Revises: f6145781f130
+Create Date: 2017-12-06 21:07:23.219244
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '2561c39ac4d9'
+down_revision = 'f6145781f130'
+
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy.sql import text
+
+from compair.models import convention
+
+def upgrade():
+ # add a new "comparable" column. default as true
+ with op.batch_alter_table('answer', naming_convention=convention) as batch_op:
+ batch_op.add_column(sa.Column('comparable', sa.Integer(), nullable=False, default='1', server_default='1'))
+
+ # Patch existing answers from instructors and TAs as non-comparable.
+ # Note that existing answers from sys admin are considered comparable (i.e. no need to patch).
+ # sqlite doesn't support in-clause with multiple columns...
+ # update = text(
+ # "UPDATE answer SET comparable = 0 "
+ # "WHERE (assignment_id, user_id) IN ( "
+ # " SELECT a.id, uc.user_id "
+ # " FROM user_course uc "
+ # " JOIN assignment a "
+ # " ON a.course_id = uc.course_id "
+ # " WHERE uc.course_role IN ('Instructor', 'Teaching Assistant'))"
+ # )
+ # ... use a potentially slower query
+ update = text(
+ "UPDATE answer SET comparable = 0 "
+ "WHERE EXISTS ( "
+ " SELECT 1 "
+ " FROM user_course "
+ " JOIN assignment "
+ " ON assignment.course_id = user_course.course_id "
+ " WHERE "
+ " assignment.id = answer.assignment_id "
+ " AND user_course.user_id = answer.user_id "
+ " AND user_course.course_role IN ('Instructor', 'Teaching Assistant'))"
+ )
+ op.get_bind().execute(update)
+
+def downgrade():
+ with op.batch_alter_table('answer', naming_convention=convention) as batch_op:
+ batch_op.drop_column('comparable')
diff --git a/compair/api/answer.py b/compair/api/answer.py
index a3f80bf75..4b11dd1b4 100644
--- a/compair/api/answer.py
+++ b/compair/api/answer.py
@@ -21,6 +21,7 @@
new_answer_parser = RequestParser()
new_answer_parser.add_argument('user_id', default=None)
+new_answer_parser.add_argument('comparable', type=bool, default=True)
new_answer_parser.add_argument('content', default=None)
new_answer_parser.add_argument('file_id', default=None)
new_answer_parser.add_argument('draft', type=bool, default=False)
@@ -28,6 +29,7 @@
existing_answer_parser = RequestParser()
existing_answer_parser.add_argument('id', required=True, help="Answer id is required.")
existing_answer_parser.add_argument('user_id', default=None)
+existing_answer_parser.add_argument('comparable', type=bool, default=True)
existing_answer_parser.add_argument('content', default=None)
existing_answer_parser.add_argument('file_id', default=None)
existing_answer_parser.add_argument('draft', type=bool, default=False)
@@ -72,7 +74,7 @@ def get(self, course_uuid, assignment_uuid):
"""
Return a list of answers for a assignment based on search criteria. The
list of the answers are paginated. If there is any answers from instructor
- or TA, their answers will be on top of the list.
+ or TA, their answers will be on top of the list (unless they are comparable).
:param course_uuid: course uuid
:param assignment_uuid: assignment uuid
@@ -103,8 +105,14 @@ def get(self, course_uuid, assignment_uuid):
UserCourse.course_id == course.id
)) \
.add_columns(
- UserCourse.course_role.__eq__(CourseRole.instructor).label("instructor_role"),
- UserCourse.course_role.__eq__(CourseRole.teaching_assistant).label("ta_role")
+ and_(
+ UserCourse.course_role.__eq__(CourseRole.instructor),
+ not Answer.comparable
+ ).label("instructor_role"),
+ and_(
+ UserCourse.course_role.__eq__(CourseRole.teaching_assistant),
+ not Answer.comparable
+ ).label("ta_role")
) \
.filter(and_(
Answer.assignment_id == assignment.id,
@@ -194,12 +202,20 @@ def post(self, course_uuid, assignment_uuid):
if user_uuid and not allow(MANAGE, Answer(course_id=course.id)):
abort(400, title="Answer Not Submitted", message="Only instructors and teaching assistants can submit an answer on behalf of another.")
+ explicitSubmitAsStudent = False
if user_uuid:
user = User.get_by_uuid_or_404(user_uuid)
answer.user_id = user.id
+ explicitSubmitAsStudent = user.get_course_role(course.id) == CourseRole.student
else:
answer.user_id = current_user.id
+ # instructor / TA can mark the answer as non-comparable, unless the answer is for a student
+ if allow(MANAGE, Answer(course_id=course.id)) and not explicitSubmitAsStudent:
+ answer.comparable = params.get("comparable")
+ else:
+ answer.comparable = True
+
user_course = UserCourse.query \
.filter_by(
course_id=course.id,
@@ -302,6 +318,15 @@ def post(self, course_uuid, assignment_uuid, answer_uuid):
answer.content = params.get("content")
user_uuid = params.get("user_id")
+ explicitSubmitAsStudent = False
+ if user_uuid:
+ user = User.get_by_uuid_or_404(user_uuid)
+ explicitSubmitAsStudent = user.get_course_role(course.id) == CourseRole.student
+
+ # instructor / TA can mark the answer as non-comparable if it is not a student answer
+ if allow(MANAGE, Answer(course_id=course.id)) and not explicitSubmitAsStudent:
+ answer.comparable = params.get("comparable")
+
# we allow instructor and TA to submit multiple answers for other users in the class
if user_uuid and user_uuid != answer.user_uuid:
if not allow(MANAGE, answer) or not answer.draft:
diff --git a/compair/api/assignment.py b/compair/api/assignment.py
index 95ef5d26b..e2db9fc89 100644
--- a/compair/api/assignment.py
+++ b/compair/api/assignment.py
@@ -484,8 +484,8 @@ def get(self, course_uuid, assignment_uuid):
comparison_count = assignment.completed_comparison_count_for_user(current_user.id)
comparison_draft_count = assignment.draft_comparison_count_for_user(current_user.id)
- other_student_answers = assignment.student_answer_count - answer_count
- comparison_available = comparison_count < other_student_answers * (other_student_answers - 1) / 2
+ other_comparable_answers = assignment.comparable_answer_count - answer_count
+ comparison_available = comparison_count < other_comparable_answers * (other_comparable_answers - 1) / 2
status = {
'answers': {
@@ -659,8 +659,8 @@ def get(self, course_uuid):
assignment_drafts = [draft for draft in drafts if draft.assignment_id == assignment.id]
comparison_count = assignment.completed_comparison_count_for_user(current_user.id)
comparison_draft_count = assignment.draft_comparison_count_for_user(current_user.id)
- other_student_answers = assignment.student_answer_count - answer_count
- comparison_available = comparison_count < other_student_answers * (other_student_answers - 1) / 2
+ other_comparable_answers = assignment.comparable_answer_count - answer_count
+ comparison_available = comparison_count < other_comparable_answers * (other_comparable_answers - 1) / 2
statuses[assignment.uuid] = {
'answers': {
diff --git a/compair/api/classlist.py b/compair/api/classlist.py
index 877812127..006e64c7e 100644
--- a/compair/api/classlist.py
+++ b/compair/api/classlist.py
@@ -9,7 +9,7 @@
from flask_login import login_required, current_user
from flask_restful import Resource, marshal
from six import BytesIO
-from sqlalchemy import and_
+from sqlalchemy import and_, or_
from sqlalchemy.orm import joinedload
from werkzeug.utils import secure_filename
from flask_restful.reqparse import RequestParser
@@ -67,6 +67,7 @@
on_classlist_instructor_label = event.signal('CLASSLIST_INSTRUCTOR_LABEL_GET')
on_classlist_instructor = event.signal('CLASSLIST_INSTRUCTOR_GET')
on_classlist_student = event.signal('CLASSLIST_STUDENT_GET')
+on_classlist_instructional = event.signal('CLASSLIST_INSTRUCTIONAL_GET')
on_classlist_update_users_course_roles = event.signal('CLASSLIST_UPDATE_USERS_COURSE_ROLES')
@@ -619,6 +620,59 @@ def get(self, course_uuid):
api.add_resource(StudentsAPI, '/students')
+# /instructionals - return list of Instructors and TAs of the course
+class InstructionalsAPI(Resource):
+ @login_required
+ def get(self, course_uuid):
+ course = Course.get_active_by_uuid_or_404(course_uuid)
+ require(READ, course,
+ title="Instructional users Unavailable",
+ message="Instructional users can only be seen here by those enrolled in the course. Please double-check your enrollment in this course.")
+ restrict_user = not allow(MANAGE, course)
+
+ instructionals = User.query \
+ .with_entities(User, UserCourse.group_name, UserCourse.course_role) \
+ .join(UserCourse, UserCourse.user_id == User.id) \
+ .filter(
+ UserCourse.course_id == course.id,
+ or_(
+ UserCourse.course_role == CourseRole.instructor,
+ UserCourse.course_role == CourseRole.teaching_assistant
+ )
+ ) \
+ .order_by(User.lastname, User.firstname) \
+ .all()
+
+ users = []
+ user_course = UserCourse(course_id=course.id)
+ for u in instructionals:
+ if allow(READ, user_course):
+ users.append({
+ 'id': u.User.uuid,
+ 'name': u.User.fullname_sortable,
+ 'group_name': u.group_name,
+ 'role': u.course_role.value
+ })
+ else:
+ name = u.User.displayname
+ users.append({
+ 'id': u.User.uuid,
+ 'name': name,
+ 'group_name': u.group_name,
+ 'role': u.course_role.value
+ })
+
+ on_classlist_instructional.send(
+ self,
+ event_name=on_classlist_instructional.name,
+ user=current_user,
+ course_id=course.id
+ )
+
+ return { 'objects': users }
+
+
+api.add_resource(InstructionalsAPI, '/instructionals')
# /roles - set course role for multi users at once
class UserCourseRoleAPI(Resource):
diff --git a/compair/api/comparison.py b/compair/api/comparison.py
index 3d6640981..6008f6bc8 100644
--- a/compair/api/comparison.py
+++ b/compair/api/comparison.py
@@ -104,7 +104,7 @@ def get(self, course_uuid, assignment_uuid):
abort(500, title="Comparisons Unavailable", message="Generating scored pairs failed, this really shouldn't happen.")
return {
- 'comparison': marshal(comparison, dataformat.get_comparison(restrict_user)),
+ 'comparison': marshal(comparison, dataformat.get_comparison(restrict_user, include_answer_user=False, include_score=False)),
'new_pair': new_pair,
'current': comparison_count+1
}
@@ -248,6 +248,6 @@ def post(self, course_uuid, assignment_uuid):
is_comparison_example=is_comparison_example,
data=marshal(comparison, dataformat.get_comparison(restrict_user)))
- return {'comparison': marshal(comparison, dataformat.get_comparison(restrict_user))}
+ return {'comparison': marshal(comparison, dataformat.get_comparison(restrict_user, include_answer_user=False, include_score=False))}
api.add_resource(CompareRootAPI, '')
diff --git a/compair/api/dataformat/__init__.py b/compair/api/dataformat/__init__.py
index b9f0a0621..ca3eb8d67 100644
--- a/compair/api/dataformat/__init__.py
+++ b/compair/api/dataformat/__init__.py
@@ -178,12 +178,11 @@ def get_assignment(restrict_user=True):
'created': fields.DateTime(dt_format='iso8601', attribute=lambda x: replace_tzinfo(x.created))
}
-def get_answer(restrict_user=True):
- return {
+def get_answer(restrict_user=True, include_answer_user=True, include_score=True):
+ ret = {
'id': fields.String(attribute="uuid"),
'course_id': fields.String(attribute="course_uuid"),
'assignment_id': fields.String(attribute="assignment_uuid"),
- 'user_id': fields.String(attribute="user_uuid"),
'content': fields.String,
'file': fields.Nested(get_file(), allow_null=True),
@@ -191,16 +190,23 @@ def get_answer(restrict_user=True):
'draft': fields.Boolean,
'top_answer': fields.Boolean,
- 'score': fields.Nested(get_score(restrict_user), allow_null=True),
-
'comment_count': fields.Integer,
'private_comment_count': fields.Integer,
'public_comment_count': fields.Integer,
- 'user': get_partial_user(restrict_user),
- 'created': fields.DateTime(dt_format='iso8601', attribute=lambda x: replace_tzinfo(x.created))
+ 'created': fields.DateTime(dt_format='iso8601', attribute=lambda x: replace_tzinfo(x.created)),
+ 'comparable': fields.Boolean
}
+ if include_score:
+ ret['score'] = fields.Nested(get_score(restrict_user), allow_null=True)
+
+ if include_answer_user:
+ ret['user_id'] = fields.String(attribute="user_uuid")
+ ret['user'] = get_partial_user(restrict_user)
+
+ return ret
+
def get_assignment_comment(restrict_user=True):
return {
@@ -253,7 +259,7 @@ def get_kaltura_media():
}
-def get_comparison(restrict_user=True, with_answers=True, with_feedback=False):
+def get_comparison(restrict_user=True, with_answers=True, with_feedback=False, include_answer_user=True, include_score=True):
ret = {
'id': fields.String(attribute="uuid"),
'course_id': fields.String(attribute="course_uuid"),
@@ -269,8 +275,8 @@ def get_comparison(restrict_user=True, with_answers=True, with_feedback=False):
}
if with_answers:
- ret['answer1'] = fields.Nested(get_answer(restrict_user))
- ret['answer2'] = fields.Nested(get_answer(restrict_user))
+ ret['answer1'] = fields.Nested(get_answer(restrict_user, include_answer_user, include_score))
+ ret['answer2'] = fields.Nested(get_answer(restrict_user, include_answer_user, include_score))
if with_feedback:
ret['answer1_feedback'] = fields.List(fields.Nested(get_answer_comment(restrict_user)))
diff --git a/compair/api/report.py b/compair/api/report.py
index 51fda5164..4b14180dc 100644
--- a/compair/api/report.py
+++ b/compair/api/report.py
@@ -89,6 +89,7 @@ def post(self, course_uuid):
title = [
'Assignment', 'User UUID', 'Last Name', 'First Name', 'Answer Submitted', 'Answer ID',
+ 'Answer', 'Students Ranked', 'Overall Score',
'Evaluations Submitted', 'Evaluations Required', 'Evaluation Requirements Met',
'Replies Submitted']
titles = [title]
@@ -134,6 +135,7 @@ def post(self, course_uuid):
]
data = peer_feedback_report(course, assignments, group_name)
titles = [titles1, titles2]
+
else:
abort(400, title="Report Not Run", message="Please try again with a report type from the list of report types provided.")
@@ -163,29 +165,45 @@ def post(self, course_uuid):
def participation_stat_report(course, assignments, group_name, overall):
report = []
- user_course_students = UserCourse.query \
- .filter_by(
- course_id=course.id,
- course_role=CourseRole.student
+ classlist = UserCourse.query \
+ .join(User, User.id == UserCourse.user_id) \
+ .filter(
+ and_(
+ UserCourse.course_id == course.id,
+ or_(
+ UserCourse.course_role == CourseRole.teaching_assistant,
+ UserCourse.course_role == CourseRole.instructor,
+ UserCourse.course_role == CourseRole.student
+ )
+ )
)
if group_name:
- user_course_students = user_course_students.filter_by(group_name=group_name)
- user_course_students = user_course_students.all()
+ classlist = classlist.filter(group_name == UserCourse.group_name)
+ classlist = classlist.order_by(User.lastname, User.firstname, User.id).all()
+
+ class_ids = [u.user.id for u in classlist]
total_req = 0
total = {}
for assignment in assignments:
- # ANSWERS: assume max one answer per user
+ # ANSWERS: instructors / TAs could submit multiple answers. normally 1 answer per student
answers = Answer.query \
- .filter_by(
- active=True,
- assignment_id=assignment.id,
- draft=False,
- practice=False
- ) \
+ .options(joinedload('score')) \
+ .filter(and_(
+ Answer.active == True,
+ Answer.assignment_id == assignment.id,
+ Answer.comparable == True,
+ Answer.draft == False,
+ Answer.practice == False,
+ Answer.user_id.in_(class_ids)
+ )) \
+ .order_by(Answer.created) \
.all()
- answers = {a.user_id: a.uuid for a in answers}
+ user_answers = {} # structure - user_id/[answer list]
+ for answer in answers:
+ user_answers_list = user_answers.setdefault(answer.user_id, [])
+ user_answers_list.append(answer)
# EVALUATIONS
evaluations = Comparison.query \
@@ -208,8 +226,8 @@ def participation_stat_report(course, assignments, group_name, overall):
total_req += assignment.total_comparisons_required # for overall required
- for user_course_student in user_course_students:
- user = user_course_student.user
+ for user_course in classlist:
+ user = user_course.user
temp = [assignment.name, user.uuid, user.lastname, user.firstname]
# OVERALL
@@ -219,10 +237,15 @@ def participation_stat_report(course, assignments, group_name, overall):
'total_comments': 0
})
- submitted = 1 if user.id in answers else 0
- answer_uuid = answers[user.id] if submitted else 'N/A'
+ # each user has at least 1 line per assignment, regardless whether there is an answer
+ submitted = len(user_answers.get(user.id, []))
+ the_answer = user_answers[user.id][0] if submitted else None
+ answer_uuid = the_answer.uuid if submitted else 'N/A'
+ answer_text = snippet(the_answer.content) if submitted else 'N/A'
+ answer_rank = the_answer.score.rank if submitted and the_answer.score else 'Not Evaluated'
+ answer_score = the_answer.score.normalized_score if submitted and the_answer.score else 'Not Evaluated'
total[user.id]['total_answers'] += submitted
- temp.extend([submitted, answer_uuid])
+ temp.extend([submitted, answer_uuid, answer_text, answer_rank, answer_score])
evaluations = evaluation_submitted.get(user.id, 0)
evaluation_req_met = 'Yes' if evaluations >= assignment.total_comparisons_required else 'No'
@@ -235,8 +258,23 @@ def participation_stat_report(course, assignments, group_name, overall):
report.append(temp)
+ # handle multiple answers from the user (normally only apply for instructors / TAs)
+ if submitted > 1:
+ for answer in user_answers[user.id][1:]:
+ answer_uuid = answer.uuid
+ answer_text = snippet(answer.content)
+ answer_rank = answer.score.rank if submitted and answer.score else 'Not Evaluated'
+ answer_score = answer.score.normalized_score if submitted and answer.score else 'Not Evaluated'
+ temp = [assignment.name, user.uuid, user.lastname,
+ user.firstname, submitted, answer_uuid, answer_text,
+ answer_rank, answer_score,
+ evaluations, assignment.total_comparisons_required,
+ evaluation_req_met, comment_count]
+
+ report.append(temp)
+
if overall:
- for user_course_student in user_course_students:
+ for user_course_student in classlist:
user = user_course_student.user
sum_submission = total.setdefault(user.id, {
'total_answers': 0,
@@ -247,7 +285,8 @@ def participation_stat_report(course, assignments, group_name, overall):
req_met = 'Yes' if sum_submission['total_evaluations'] >= total_req else 'No'
temp = [
'(Overall in Course)', user.uuid, user.lastname, user.firstname,
- sum_submission['total_answers'], '(Overall in Course)',
+ sum_submission['total_answers'], '(Overall in Course)', '(Overall in Course)',
+ '', '',
sum_submission['total_evaluations'], total_req, req_met,
sum_submission['total_comments']]
report.append(temp)
@@ -447,6 +486,7 @@ def peer_feedback_report(course, assignments, group_name):
return report
+
def strip_html(text):
text = re.sub('<[^>]+>', '', text)
text = text.replace(' ', ' ')
@@ -455,4 +495,14 @@ def strip_html(text):
text = text.replace('>', '>')
text = text.replace('"', '"')
text = text.replace(''', '\'')
- return text
\ No newline at end of file
+ return text
+
+def snippet(content, length=100, suffix='...'):
+ if content == None:
+ return ""
+ content = strip_html(content)
+ content = content.replace('\n', ' ').replace('\r', '').strip()
+ if len(content) <= length:
+ return content
+ else:
+ return ' '.join(content[:length+1].split(' ')[:-1]) + suffix
\ No newline at end of file
diff --git a/compair/models/answer.py b/compair/models/answer.py
index 5e844ffc5..4ed04ba69 100644
--- a/compair/models/answer.py
+++ b/compair/models/answer.py
@@ -27,6 +27,7 @@ class Answer(DefaultTableMixin, UUIDMixin, ActiveMixin, WriteTrackingMixin):
nullable=True)
draft = db.Column(db.Boolean(name='draft'), default=False, nullable=False, index=True)
top_answer = db.Column(db.Boolean(name='top_answer'), default=False, nullable=False, index=True)
+ comparable = db.Column(db.Boolean(name='comparable'), default=True, nullable=False)
# relationships
# assignment via Assignment Model
diff --git a/compair/models/assignment.py b/compair/models/assignment.py
index b2bf16929..4858fe172 100644
--- a/compair/models/assignment.py
+++ b/compair/models/assignment.py
@@ -278,6 +278,28 @@ def __declare_last__(cls):
group="counts"
)
+ # Comparable answer count
+ # To be consistent with student_answer_count, we are not counting
+ # answers from sys admin here
+ cls.comparable_answer_count = column_property(
+ select([func.count(Answer.id)]).
+ select_from(join(Answer, UserCourse, UserCourse.user_id == Answer.user_id)).
+ where(and_(
+ Answer.assignment_id == cls.id,
+ Answer.active == True,
+ Answer.draft == False,
+ Answer.practice == False,
+ UserCourse.course_id == cls.course_id,
+ UserCourse.course_role.in_(
+ [CourseRole.student, CourseRole.instructor, \
+ CourseRole.teaching_assistant]
+ ),
+ Answer.comparable == True
+ )),
+ deferred=True,
+ group="counts"
+ )
+
cls.top_answer_count = column_property(
select([func.count(Answer.id)]).
select_from(join(Answer, UserCourse, UserCourse.user_id == Answer.user_id)).
diff --git a/compair/models/comparison.py b/compair/models/comparison.py
index 0e26f8108..4d1336b82 100644
--- a/compair/models/comparison.py
+++ b/compair/models/comparison.py
@@ -109,14 +109,20 @@ def _get_new_comparison_pair(cls, course_id, assignment_id, user_id, pairing_alg
from . import Assignment, UserCourse, CourseRole, Answer, AnswerScore, \
PairingAlgorithm, AnswerCriterionScore, AssignmentCriterion
- # ineligible authors - eg. instructors, TAs, dropped student, current user
- non_students = UserCourse.query \
+ # exclude current user and those not with proper role.
+ # note that sys admin (not enrolled in the course and thus no course role) can create answers.
+ # they are considered eligible
+ ineligibles = UserCourse.query \
+ .with_entities(UserCourse.user_id) \
.filter(and_(
UserCourse.course_id == course_id,
- UserCourse.course_role != CourseRole.student
+ UserCourse.course_role.notin_(
+ [ CourseRole.student, CourseRole.instructor, \
+ CourseRole.teaching_assistant ]
+ )
))
- ineligible_user_ids = [non_student.user_id \
- for non_student in non_students]
+ ineligible_user_ids = [ineligible.user_id \
+ for ineligible in ineligibles]
ineligible_user_ids.append(user_id)
answers_with_score = Answer.query \
@@ -127,7 +133,8 @@ def _get_new_comparison_pair(cls, course_id, assignment_id, user_id, pairing_alg
Answer.assignment_id == assignment_id,
Answer.active == True,
Answer.practice == False,
- Answer.draft == False
+ Answer.draft == False,
+ Answer.comparable == True
)) \
.all()
diff --git a/compair/static/modules/answer/answer-form-partial.html b/compair/static/modules/answer/answer-form-partial.html
index 07fac7bb9..47ece49f7 100644
--- a/compair/static/modules/answer/answer-form-partial.html
+++ b/compair/static/modules/answer/answer-form-partial.html
@@ -37,7 +37,7 @@
{{assignment.name}}
-
Answers submitted as an instructor or TA will not be included in comparisons and appear to students only after they answer/compare.
+
Answers submitted as an instructor or TA will be included in comparisons unless this option is unchecked below. All answers will appear to students when the assignment completes.
Allowed file types for attachments include {{ UploadValidator.getAttachmentExtensionsForDisplay() }}, with a maximum file size of {{UploadValidator.getAttachmentUploadLimit() | filesize }}. Any files you upload will be attached when you save or submit this answer but not before.
@@ -45,11 +45,22 @@
{{assignment.name}}
+
+
+
+
diff --git a/compair/static/modules/answer/answer-module.js b/compair/static/modules/answer/answer-module.js
index 8d007158a..623f2c9f9 100644
--- a/compair/static/modules/answer/answer-module.js
+++ b/compair/static/modules/answer/answer-module.js
@@ -102,10 +102,10 @@ module.factory("AnswerResource", ['$resource', '$cacheFactory', function ($resou
module.controller(
"AnswerWriteController",
["$scope", "$location", "$routeParams", "AnswerResource", "ClassListResource", "$route",
- "AssignmentResource", "Toaster", "$timeout", "UploadValidator",
+ "AssignmentResource", "Toaster", "$timeout", "UploadValidator", "CourseRole",
"answerAttachService", "EditorOptions", "xAPI", "xAPIStatementHelper", "resolvedData",
function ($scope, $location, $routeParams, AnswerResource, ClassListResource, $route,
- AssignmentResource, Toaster, $timeout, UploadValidator,
+ AssignmentResource, Toaster, $timeout, UploadValidator, CourseRole,
answerAttachService, EditorOptions, xAPI, xAPIStatementHelper, resolvedData)
{
$scope.courseId = $routeParams.courseId;
@@ -131,12 +131,16 @@ module.controller(
);
});
$scope.showAssignment = true;
+ // since the "submit as" dropdown (if enabled) is default to current user (or empty if sys admin),
+ // the default value of submitAsInstructorOrTA is based on canManageAssignment
+ $scope.submitAsInstructorOrTA = resolvedData.canManageAssignment;
if ($scope.method == "create") {
$scope.answer = {
draft: true,
course_id: $scope.courseId,
- assignment_id: $scope.assignmentId
+ assignment_id: $scope.assignmentId,
+ comparable: true
};
if (!resolvedData.answerUnsaved.objects.length) {
// if no answers found, create a new draft answer
@@ -213,6 +217,20 @@ module.controller(
);
};
+ $scope.onSubmitAsChanged = function(selectedUserId) {
+ var instructorOrTA = selectedUserId != null &&
+ $scope.classlist.filter(function (el) {
+ return el.id == selectedUserId &&
+ (el.course_role == CourseRole.instructor ||
+ el.course_role == CourseRole.teaching_assistant)
+ }).length > 0;
+
+ $scope.submitAsInstructorOrTA = selectedUserId == null || instructorOrTA;
+ if (!instructorOrTA) {
+ $scope.answer.comparable = true;
+ }
+ }
+
$scope.answerSubmit = function () {
$scope.submitted = true;
var wasDraft = $scope.answer.draft;
@@ -303,6 +321,8 @@ module.controller(
);
});
+ $scope.submitAsInstructorOrTA = $scope.canManageAssignment;
+
$scope.deleteFile = function(file) {
$scope.answer.file = null;
$scope.answer.uploadedFile = false;
diff --git a/compair/static/modules/assignment/assignment-module.js b/compair/static/modules/assignment/assignment-module.js
index 8b35cf52a..ab839c91e 100644
--- a/compair/static/modules/assignment/assignment-module.js
+++ b/compair/static/modules/assignment/assignment-module.js
@@ -700,8 +700,8 @@ module.filter("excludeInstr", function() {
return function(items, instructors) {
var filtered = [];
angular.forEach(items, function(item) {
- // if user id is NOT in the instructors array, keep it
- if (!instructors[item.user_id]) {
+ // exclude instructor answer unless it is comparable
+ if (!instructors[item.user_id] || item.comparable) {
filtered.push(item);
}
});
@@ -831,8 +831,8 @@ module.controller("AssignmentViewController",
$scope.adminFilter = function() {
return function (answer) {
- // assume if any filter is applied - instructor/TAs answer will not meet requirement
- return !$scope.answerFilters.author && !$scope.answerFilters.group
+ // true for non-comparable instructor/TA answer
+ return $scope.instructors[answer.user_id] && !answer.comparable;
}
};
@@ -1170,6 +1170,18 @@ module.controller("AssignmentViewController",
$scope.updateAnswerList();
$scope.resetStudents($scope.allStudents);
+ CourseResource.getInstructionals({'id': $scope.courseId}).$promise.then(
+ function(ret) {
+ $scope.allInstructionals = ret.objects;
+ // Order by role, then name
+ // Underscore sorting is stable, so first sort by name, then role
+ $scope.allInstructionals = _($scope.allInstructionals).chain()
+ .sortBy(function(o) { return o.name; })
+ .sortBy(function(o) { return o.role; })
+ .value();
+ }
+ );
+
var filterWatcher = function(newValue, oldValue) {
if (angular.equals(newValue, oldValue)) return;
if (oldValue.group != newValue.group) {
diff --git a/compair/static/modules/assignment/assignment-module_spec.js b/compair/static/modules/assignment/assignment-module_spec.js
index 601a761d5..13747d77e 100644
--- a/compair/static/modules/assignment/assignment-module_spec.js
+++ b/compair/static/modules/assignment/assignment-module_spec.js
@@ -793,6 +793,14 @@ describe('assignment-module', function () {
});
$httpBackend.expectGET('/api/courses/1abcABC123-abcABC123_Z/groups').respond(mockGroups);
$httpBackend.expectGET('/api/courses/1abcABC123-abcABC123_Z/assignments/1abcABC123-abcABC123_Z/answers?page=1&perPage=20').respond(mockAnswers);
+ $httpBackend.expectGET('/api/courses/1abcABC123-abcABC123_Z/users/instructionals').respond({
+ "objects": [{
+ "group_name": null,
+ "id": "1",
+ "name": "One, Instructor",
+ "role": "Instructor"
+ }]
+ });
$httpBackend.flush();
});
diff --git a/compair/static/modules/assignment/assignment-view-partial.html b/compair/static/modules/assignment/assignment-view-partial.html
index db12a982d..d0c894bfe 100644
--- a/compair/static/modules/assignment/assignment-view-partial.html
+++ b/compair/static/modules/assignment/assignment-view-partial.html
@@ -97,7 +97,7 @@
All answers
@@ -171,7 +171,7 @@
All answers
-
+
@@ -180,6 +180,9 @@
All answers
said on {{answer.created | amDateFormat: 'MMM D @ h:mm a'}}: