diff --git a/openassessment/assessment/models/base.py b/openassessment/assessment/models/base.py index 43243ec66c..ed17272cf8 100644 --- a/openassessment/assessment/models/base.py +++ b/openassessment/assessment/models/base.py @@ -19,6 +19,7 @@ import logging import math +import six from lazy import lazy from django.core.cache import cache @@ -105,7 +106,7 @@ def content_hash_from_dict(rubric_dict): rubric_dict.pop("content_hash", None) canonical_form = json.dumps(rubric_dict, sort_keys=True) - return sha1(canonical_form).hexdigest() + return sha1(canonical_form.encode('utf-8')).hexdigest() @staticmethod def structure_hash_from_dict(rubric_dict): @@ -137,7 +138,7 @@ def structure_hash_from_dict(rubric_dict): for criterion in rubric_dict.get('criteria', []) ] canonical_form = json.dumps(structure, sort_keys=True) - return sha1(canonical_form).hexdigest() + return sha1(canonical_form.encode('utf-8')).hexdigest() class Criterion(models.Model): @@ -510,7 +511,7 @@ def get_median_score_dict(cls, scores_dict): """ median_scores = {} - for criterion, criterion_scores in scores_dict.iteritems(): + for criterion, criterion_scores in six.iteritems(scores_dict): criterion_score = Assessment.get_median_score(criterion_scores) median_scores[criterion] = criterion_score return median_scores @@ -673,7 +674,7 @@ def create_from_option_names(cls, assessment, selected, feedback=None): # Validate that we have selections for all criteria # This will raise an exception if we're missing any selections/feedback required for criteria - cls._check_all_criteria_assessed(rubric_index, selected.keys(), feedback.keys()) + cls._check_all_criteria_assessed(rubric_index, list(selected.keys()), list(feedback.keys())) # Retrieve the criteria/option/feedback for criteria that have options. # Since we're using the rubric's index, we'll get an `InvalidRubricSelection` error @@ -684,13 +685,13 @@ def create_from_option_names(cls, assessment, selected, feedback=None): 'option': rubric_index.find_option(criterion_name, option_name), 'feedback': feedback.get(criterion_name, u"")[0:cls.MAX_FEEDBACK_SIZE], } - for criterion_name, option_name in selected.iteritems() + for criterion_name, option_name in six.iteritems(selected) ] # Some criteria may have feedback but no options, only feedback. # For these, we set `option` to None, indicating that the assessment part # is not associated with any option, only a criterion. - for criterion_name, feedback_text in feedback.iteritems(): + for criterion_name, feedback_text in six.iteritems(feedback): if criterion_name not in selected: assessment_parts.append({ 'criterion': rubric_index.find_criterion(criterion_name), @@ -742,7 +743,7 @@ def create_from_option_points(cls, assessment, selected): 'criterion': rubric_index.find_criterion(criterion_name), 'option': rubric_index.find_option_for_points(criterion_name, option_points), } - for criterion_name, option_points in selected.iteritems() + for criterion_name, option_points in six.iteritems(selected) ] # Add in feedback-only criteria diff --git a/openassessment/assessment/models/training.py b/openassessment/assessment/models/training.py index 17e13497c4..a97f5ad84a 100644 --- a/openassessment/assessment/models/training.py +++ b/openassessment/assessment/models/training.py @@ -4,6 +4,8 @@ from hashlib import sha1 import json +import six + from django.core.cache import cache from django.db import models @@ -54,7 +56,7 @@ def create_example(cls, answer, options_selected, rubric): # This will raise `InvalidRubricSelection` if the selected options # do not match the rubric. - for criterion_name, option_name in options_selected.iteritems(): + for criterion_name, option_name in six.iteritems(options_selected): option = rubric.index.find_option(criterion_name, option_name) example.options_selected.add(option) return example @@ -133,7 +135,7 @@ def calculate_hash(answer, options_selected, rubric): 'options_selected': options_selected, 'rubric': rubric.id }) - return sha1(contents).hexdigest() + return sha1(contents.encode('utf-8')).hexdigest() @classmethod def cache_key(cls, answer, options_selected, rubric): diff --git a/openassessment/assessment/test/constants.py b/openassessment/assessment/test/constants.py index 2fc4d7a8b6..f7927ca9b8 100644 --- a/openassessment/assessment/test/constants.py +++ b/openassessment/assessment/test/constants.py @@ -2,6 +2,7 @@ """ Constants used as test data. """ +import six STUDENT_ITEM = { 'student_id': u'𝓽𝓮𝓼𝓽 𝓼𝓽𝓾𝓭𝓮𝓷𝓽', @@ -76,7 +77,7 @@ "expected_points": sum( RUBRIC_OPTIONS[i]["points"] for i in value ) - } for key, value in OPTIONS_SELECTED_CHOICES.iteritems() + } for key, value in six.iteritems(OPTIONS_SELECTED_CHOICES) } EXAMPLES = [ diff --git a/openassessment/fileupload/backends/swift.py b/openassessment/fileupload/backends/swift.py index 4a222f2dbe..de06404366 100644 --- a/openassessment/fileupload/backends/swift.py +++ b/openassessment/fileupload/backends/swift.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- ''' Add in /edx/app/edxapp/edx-platform/lms/envs/aws.py: ORA2_SWIFT_URL = AUTH_TOKENS["ORA2_SWIFT_URL"] @@ -11,10 +12,12 @@ 'swift stat -v' to get it. ''' +from __future__ import absolute_import, division, print_function, unicode_literals + import logging -import urlparse import requests +from six.moves.urllib.parse import urlparse import swiftclient from django.conf import settings @@ -96,5 +99,5 @@ def get_settings(): url = getattr(settings, 'ORA2_SWIFT_URL', None) key = getattr(settings, 'ORA2_SWIFT_KEY', None) - url = urlparse.urlparse(url) + url = urlparse(url) return key, url diff --git a/openassessment/workflow/api.py b/openassessment/workflow/api.py index 2ca1f25c30..df1fb8bd6f 100644 --- a/openassessment/workflow/api.py +++ b/openassessment/workflow/api.py @@ -4,6 +4,8 @@ """ import logging +import six + from django.db import DatabaseError from openassessment.assessment.errors import PeerAssessmentError, PeerAssessmentInternalError @@ -340,7 +342,7 @@ def _get_workflow_model(submission_uuid): problem. """ - if not isinstance(submission_uuid, basestring): + if not isinstance(submission_uuid, six.string_types): raise AssessmentWorkflowRequestError("submission_uuid must be a string type") try: diff --git a/openassessment/workflow/models.py b/openassessment/workflow/models.py index 06d166e451..dd6e8ae0ff 100644 --- a/openassessment/workflow/models.py +++ b/openassessment/workflow/models.py @@ -58,7 +58,7 @@ class AssessmentWorkflow(TimeStampedModel, StatusModel): an after the fact recording of the last known state of that information so we can search easily. """ - STEPS = ASSESSMENT_API_DICT.keys() + STEPS = list(ASSESSMENT_API_DICT.keys()) STATUSES = [ "waiting", # User has done all necessary assessment but hasn't been diff --git a/openassessment/workflow/test/test_api.py b/openassessment/workflow/test/test_api.py index 8f9c03c3eb..3d341d3fda 100644 --- a/openassessment/workflow/test/test_api.py +++ b/openassessment/workflow/test/test_api.py @@ -46,6 +46,13 @@ @ddt.ddt class TestAssessmentWorkflowApi(CacheResetTest): + def test_status_values_include_all_steps(self): + self.assertIsInstance(AssessmentWorkflow.STEPS, list) + self.assertEqual( + AssessmentWorkflow.STATUS_VALUES, + AssessmentWorkflow.STEPS + AssessmentWorkflow.STATUSES + ) + @ddt.file_data('data/assessments.json') def test_create_workflow(self, data): first_step = data["steps"][0] if data["steps"] else "peer" diff --git a/openassessment/xblock/course_items_listing_mixin.py b/openassessment/xblock/course_items_listing_mixin.py index 479575c087..e612b3ef09 100644 --- a/openassessment/xblock/course_items_listing_mixin.py +++ b/openassessment/xblock/course_items_listing_mixin.py @@ -3,8 +3,12 @@ """ +from __future__ import absolute_import + import json +import six + from webob import Response from xblock.core import XBlock @@ -26,5 +30,5 @@ def get_ora2_responses(self, request, suffix=''): # pylint: disable=unused-argu """ # Import is placed here to avoid model import at project startup. from openassessment.data import OraAggregateData - responses = OraAggregateData.collect_ora2_responses(unicode(self.course_id)) + responses = OraAggregateData.collect_ora2_responses(six.text_type(self.course_id)) return Response(json.dumps(responses), content_type='application/json') diff --git a/openassessment/xblock/data_conversion.py b/openassessment/xblock/data_conversion.py index f5a395838b..d1b2d21d31 100644 --- a/openassessment/xblock/data_conversion.py +++ b/openassessment/xblock/data_conversion.py @@ -2,8 +2,12 @@ Data Conversion utility methods for handling ORA2 XBlock data transformations and validation. """ +from __future__ import absolute_import + import json +import six + def convert_training_examples_list_to_dict(examples_list): """ @@ -85,8 +89,7 @@ def update_assessments_format(assessments): for assessment in assessments: if 'examples' in assessment and assessment['examples']: for example in assessment['examples']: - if (isinstance(example, dict) and - (isinstance(example['answer'], unicode) or isinstance(example['answer'], str))): + if isinstance(example, dict) and isinstance(example['answer'], six.string_types): example['answer'] = { 'parts': [ {'text': example['answer']} diff --git a/openassessment/xblock/grade_mixin.py b/openassessment/xblock/grade_mixin.py index ce79817cf3..2e715254a0 100644 --- a/openassessment/xblock/grade_mixin.py +++ b/openassessment/xblock/grade_mixin.py @@ -1,6 +1,9 @@ +# -*- coding: utf-8 -*- """ Grade step in the OpenAssessment XBlock. """ +from __future__ import absolute_import, division, print_function, unicode_literals + import copy from lazy import lazy @@ -8,9 +11,10 @@ from django.utils.translation import ugettext as _ -from data_conversion import create_submission_dict from openassessment.assessment.errors import PeerAssessmentError, SelfAssessmentError +from .data_conversion import create_submission_dict + class GradeMixin(object): """Grade Mixin introduces all handlers for displaying grades @@ -410,6 +414,7 @@ def _peer_median_option(self, submission_uuid, criterion): median_scores = peer_api.get_assessment_median_scores(submission_uuid) median_score = median_scores.get(criterion['name'], None) + median_score = -1 if median_score is None else median_score def median_options(): """ @@ -423,7 +428,7 @@ def median_options(): 5. Options A=1, B=3 and C=5, a median score of 6 returns [C] Note: 5 should not happen as a median should never be out of range. """ - last_score = None + last_score = -1 median_options = [] # Sort the options first by name and then by points, so that if there @@ -436,7 +441,7 @@ def median_options(): current_score = option['points'] # If we have reached a new score, then decide what to do next - if current_score is not last_score: + if current_score != last_score: # If the last score we saw was already larger than the median # score, then we must have collected enough so return all @@ -472,7 +477,7 @@ def median_options(): return options[0] return { 'label': u' / '.join([option['label'] for option in options]), - 'points': median_score, + 'points': median_score if median_score != -1 else None, 'explanation': None, } diff --git a/openassessment/xblock/leaderboard_mixin.py b/openassessment/xblock/leaderboard_mixin.py index fc0c1eecd0..ab571f04ce 100644 --- a/openassessment/xblock/leaderboard_mixin.py +++ b/openassessment/xblock/leaderboard_mixin.py @@ -1,6 +1,10 @@ """ Leaderboard step in the OpenAssessment XBlock. """ +from __future__ import absolute_import + +import six + from xblock.core import XBlock from django.utils.translation import ugettext as _ @@ -94,7 +98,7 @@ def render_leaderboard_complete(self, student_item_dict): if 'text' in score['content'] or 'parts' in score['content']: submission = {'answer': score.pop('content')} score['submission'] = create_submission_dict(submission, self.prompts) - elif isinstance(score['content'], basestring): + elif isinstance(score['content'], six.string_types): pass # Currently, we do not handle non-text submissions. else: diff --git a/openassessment/xblock/lms_mixin.py b/openassessment/xblock/lms_mixin.py index c3ec8fbda6..61e0ae2b41 100644 --- a/openassessment/xblock/lms_mixin.py +++ b/openassessment/xblock/lms_mixin.py @@ -2,6 +2,10 @@ Fields and methods used by the LMS and Studio. """ +from __future__ import absolute_import + +import six + from xblock.fields import DateTime, Dict, Float, Scope, String @@ -13,7 +17,7 @@ def from_json(self, access_dict): def to_json(self, access_dict): if access_dict is not None: - return {unicode(k): access_dict[k] for k in access_dict} + return {six.text_type(k): access_dict[k] for k in access_dict} class LmsCompatibilityMixin(object): diff --git a/openassessment/xblock/openassessmentblock.py b/openassessment/xblock/openassessmentblock.py index 460e603b6f..66dc619892 100644 --- a/openassessment/xblock/openassessmentblock.py +++ b/openassessment/xblock/openassessmentblock.py @@ -1,5 +1,7 @@ """An XBlock where students can read a question and compose their response""" +from __future__ import absolute_import + import copy import datetime as dt import json @@ -9,6 +11,7 @@ from lazy import lazy import pkg_resources import pytz +from six import text_type from webob import Response from xblock.core import XBlock from xblock.fields import Boolean, Integer, List, Scope, String @@ -362,7 +365,7 @@ def get_student_item_dict(self, anonymous_user_id=None): if self.scope_ids.user_id is None: student_id = None else: - student_id = unicode(self.scope_ids.user_id) + student_id = text_type(self.scope_ids.user_id) student_item_dict = dict( student_id=student_id, @@ -1104,7 +1107,7 @@ def _serialize_opaque_key(self, key): if hasattr(key, 'to_deprecated_string'): return key.to_deprecated_string() else: - return unicode(key) + return text_type(key) def get_username(self, anonymous_user_id): """ diff --git a/openassessment/xblock/resolve_dates.py b/openassessment/xblock/resolve_dates.py index cbfb062ee2..f948cb57bb 100644 --- a/openassessment/xblock/resolve_dates.py +++ b/openassessment/xblock/resolve_dates.py @@ -1,10 +1,14 @@ """ Resolve unspecified dates and date strings to datetimes. """ +from __future__ import absolute_import + import datetime as dt from dateutil.parser import parse as parse_date import pytz +import six +from six.moves import range, zip class InvalidDateFormat(Exception): @@ -43,7 +47,7 @@ def _parse_date(value, _): if isinstance(value, dt.datetime): return value.replace(tzinfo=pytz.utc) - elif isinstance(value, basestring): + elif isinstance(value, six.string_types): try: return parse_date(value).replace(tzinfo=pytz.utc) except ValueError: @@ -226,7 +230,7 @@ def resolve_dates(start, end, date_ranges, _): prev_end = step_end # Combine the resolved dates back into a list of tuples - resolved_ranges = zip(resolved_starts, resolved_ends) + resolved_ranges = list(zip(resolved_starts, resolved_ends)) # Now that we have resolved both start and end dates, we can safely compare them for resolved_start, resolved_end in resolved_ranges: diff --git a/openassessment/xblock/schema.py b/openassessment/xblock/schema.py index 9cca820004..7f458d6cd7 100644 --- a/openassessment/xblock/schema.py +++ b/openassessment/xblock/schema.py @@ -2,8 +2,12 @@ Schema for validating and sanitizing data received from the JavaScript client. """ +from __future__ import absolute_import + import dateutil from pytz import utc +import six + from voluptuous import All, Any, In, Invalid, Range, Required, Schema @@ -22,10 +26,10 @@ def utf8_validator(value): """ try: - if isinstance(value, str): + if isinstance(value, six.binary_type): return value.decode('utf-8') else: - return unicode(value) + return six.text_type(value) except (ValueError, TypeError): raise Invalid(u"Could not load unicode from value \"{val}\"".format(val=value)) @@ -51,7 +55,7 @@ def datetime_validator(value): # Parse the date and interpret it as UTC value = dateutil.parser.parse(value).replace(tzinfo=utc) - return unicode(value.isoformat()) + return six.text_type(value.isoformat()) except (ValueError, TypeError): raise Invalid(u"Could not parse datetime from value \"{val}\"".format(val=value)) diff --git a/openassessment/xblock/staff_area_mixin.py b/openassessment/xblock/staff_area_mixin.py index 429dd312ab..034fde6472 100644 --- a/openassessment/xblock/staff_area_mixin.py +++ b/openassessment/xblock/staff_area_mixin.py @@ -6,6 +6,7 @@ from functools import wraps import logging +import six from xblock.core import XBlock from openassessment.assessment.errors import PeerAssessmentInternalError @@ -485,6 +486,6 @@ def _cancel_workflow(self, submission_uuid, comments, requesting_user_id=None): AssessmentWorkflowError, AssessmentWorkflowInternalError ) as ex: - msg = ex.message + msg = six.text_type(ex) logger.exception(msg) return {"success": False, 'msg': msg} diff --git a/openassessment/xblock/staff_assessment_mixin.py b/openassessment/xblock/staff_assessment_mixin.py index b102b517c0..d6bd537064 100644 --- a/openassessment/xblock/staff_assessment_mixin.py +++ b/openassessment/xblock/staff_assessment_mixin.py @@ -1,6 +1,9 @@ +# -*- coding: utf-8 -*- """"" A mixin for staff grading. """ +from __future__ import absolute_import, division, print_function, unicode_literals + import logging from xblock.core import XBlock @@ -8,9 +11,9 @@ from openassessment.assessment.api import staff as staff_api from openassessment.assessment.errors import StaffAssessmentInternalError, StaffAssessmentRequestError from openassessment.workflow import api as workflow_api -from staff_area_mixin import require_course_staff from .data_conversion import clean_criterion_feedback, create_rubric_dict, verify_assessment_parameters +from .staff_area_mixin import require_course_staff logger = logging.getLogger(__name__) diff --git a/openassessment/xblock/student_training_mixin.py b/openassessment/xblock/student_training_mixin.py index 012a1ad024..f198db2ad0 100644 --- a/openassessment/xblock/student_training_mixin.py +++ b/openassessment/xblock/student_training_mixin.py @@ -1,8 +1,12 @@ """ Student training step in the OpenAssessment XBlock. """ +from __future__ import absolute_import + import logging +import six + from webob import Response from xblock.core import XBlock @@ -64,14 +68,14 @@ def _parse_answer_dict(self, answer): """ parts = answer.get('parts', []) if parts and isinstance(parts[0], dict): - if isinstance(parts[0].get('text'), basestring): + if isinstance(parts[0].get('text'), six.string_types): return create_submission_dict({'answer': answer}, self.prompts) def _parse_answer_list(self, answer): """ Helper to parse answer as a list of strings. """ - if answer and isinstance(answer[0], basestring): + if answer and isinstance(answer[0], six.string_types): return self._parse_answer_string(answer[0]) elif len(answer) == 0: return self._parse_answer_string("") @@ -100,7 +104,7 @@ def _parse_example(self, example): ) answer = example['answer'] submission_dict = None - if isinstance(answer, basestring): + if isinstance(answer, six.string_types): submission_dict = self._parse_answer_string(answer) elif isinstance(answer, dict): submission_dict = self._parse_answer_dict(answer) diff --git a/openassessment/xblock/studio_mixin.py b/openassessment/xblock/studio_mixin.py index 264a95eb8e..4131ef5bfc 100644 --- a/openassessment/xblock/studio_mixin.py +++ b/openassessment/xblock/studio_mixin.py @@ -1,11 +1,15 @@ """ Studio editing view for OpenAssessment XBlock. """ +from __future__ import absolute_import + import copy import logging from uuid import uuid4 import pkg_resources +import six +from six.moves import range, zip from voluptuous import MultipleInvalid from xblock.core import XBlock from xblock.fields import List, Scope @@ -384,7 +388,7 @@ def _get_base_url_path_for_course_assets(self, course_key): placeholder_id = uuid4().hex # create a dummy asset location with a fake but unique name. strip off the name, and return it - url_path = unicode(course_key.make_asset_key('asset', placeholder_id).for_branch(None)) + url_path = six.text_type(course_key.make_asset_key('asset', placeholder_id).for_branch(None)) if not url_path.startswith('/'): url_path = '/' + url_path return url_path.replace(placeholder_id, '') diff --git a/openassessment/xblock/submission_mixin.py b/openassessment/xblock/submission_mixin.py index 07b4fc4b4c..421bd80a51 100644 --- a/openassessment/xblock/submission_mixin.py +++ b/openassessment/xblock/submission_mixin.py @@ -1,16 +1,20 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import, division, print_function, unicode_literals + import json import logging +import six from xblock.core import XBlock -from data_conversion import create_submission_dict, prepare_submission_for_serialization from openassessment.fileupload import api as file_upload_api from openassessment.fileupload.exceptions import FileUploadError from openassessment.workflow.errors import AssessmentWorkflowError -from validation import validate_submission +from .data_conversion import create_submission_dict, prepare_submission_for_serialization from .resolve_dates import DISTANT_FUTURE from .user_data import get_user_preferences +from .validation import validate_submission logger = logging.getLogger(__name__) @@ -208,7 +212,10 @@ def save_files_descriptions(self, data, suffix=''): # pylint: disable=unused-ar if 'descriptions' in data: descriptions = data['descriptions'] - if isinstance(descriptions, list) and all(map(lambda description: isinstance(description, basestring), descriptions)): + if ( + isinstance(descriptions, list) and + all(isinstance(description, six.string_types) for description in descriptions) + ): try: self.saved_files_descriptions = json.dumps(descriptions) diff --git a/openassessment/xblock/test/base.py b/openassessment/xblock/test/base.py index 78e5e91e1a..0abbe225c4 100644 --- a/openassessment/xblock/test/base.py +++ b/openassessment/xblock/test/base.py @@ -2,12 +2,15 @@ """ Base class for handler-level testing of the XBlock. """ +from __future__ import absolute_import, print_function + import copy from functools import wraps import json import os.path import mock +from six.moves import zip import webob from workbench.runtime import WorkbenchRuntime @@ -102,7 +105,7 @@ def _wrapped(*args, **kwargs): if isinstance(self, XBlockHandlerTestCaseMixin): # Print a debug message - print "Loading scenario from {path}".format(path=scenario_path) + print("Loading scenario from {path}".format(path=scenario_path)) # Configure the runtime with our user id self.set_user(user_id) @@ -185,7 +188,7 @@ def request(self, xblock, handler_name, content, request_method="POST", response # Create a fake request request = webob.Request(dict()) request.method = request_method - request.body = content + request.body = content.encode('utf-8') # Send the request to the XBlock handler if use_runtime: diff --git a/openassessment/xblock/test/test_grade.py b/openassessment/xblock/test/test_grade.py index 06fc3769f6..bcda608d52 100644 --- a/openassessment/xblock/test/test_grade.py +++ b/openassessment/xblock/test/test_grade.py @@ -6,6 +6,7 @@ import json import ddt +import six from openassessment.assessment.api import peer as peer_api @@ -37,18 +38,18 @@ def test_render_grade(self, xblock): # This isn't strictly speaking part of the grade step rendering, # but we've already done all the setup to get to this point in the flow, # so we might as well verify it here. - resp = self.request(xblock, 'render_submission', json.dumps(dict())) + resp = self.request(xblock, 'render_submission', json.dumps(dict())).decode('utf-8') self.assertIn('response', resp.lower()) self.assertIn('complete', resp.lower()) # Verify that student submission is in the view - self.assertIn(self.SUBMISSION[1], resp.decode('utf-8')) + self.assertIn(self.SUBMISSION[1], resp) - resp = self.request(xblock, 'render_peer_assessment', json.dumps(dict())) + resp = self.request(xblock, 'render_peer_assessment', json.dumps(dict())).decode('utf-8') self.assertIn('peer', resp.lower()) self.assertIn('complete', resp.lower()) - resp = self.request(xblock, 'render_self_assessment', json.dumps(dict())) + resp = self.request(xblock, 'render_self_assessment', json.dumps(dict())).decode('utf-8') self.assertIn('self', resp.lower()) self.assertIn('complete', resp.lower()) @@ -69,15 +70,15 @@ def test_render_grade_self_only(self, xblock): # This isn't strictly speaking part of the grade step rendering, # but we've already done all the setup to get to this point in the flow, # so we might as well verify it here. - resp = self.request(xblock, 'render_submission', json.dumps(dict())) + resp = self.request(xblock, 'render_submission', json.dumps(dict())).decode('utf-8') self.assertIn('response', resp.lower()) self.assertIn('complete', resp.lower()) - resp = self.request(xblock, 'render_peer_assessment', json.dumps(dict())) + resp = self.request(xblock, 'render_peer_assessment', json.dumps(dict())).decode('utf-8') self.assertNotIn('peer', resp.lower()) self.assertNotIn('complete', resp.lower()) - resp = self.request(xblock, 'render_self_assessment', json.dumps(dict())) + resp = self.request(xblock, 'render_self_assessment', json.dumps(dict())).decode('utf-8') self.assertIn('self', resp.lower()) self.assertIn('complete', resp.lower()) @@ -103,12 +104,12 @@ def test_render_grade_feedback_only_criterion(self, xblock): ) # Render the grade section - resp = self.request(xblock, 'render_grade', json.dumps(dict())) + resp = self.request(xblock, 'render_grade', json.dumps(dict())).decode('utf-8') self.assertIn('your response', resp.lower()) # Verify that feedback from each scorer appears in the view - self.assertIn(u'єאςєɭɭєภՇ ฬ๏гк!', resp.decode('utf-8')) - self.assertIn(u'Good job!', resp.decode('utf-8')) + self.assertIn(u'єאςєɭɭєภՇ ฬ๏гк!', resp) + self.assertIn(u'Good job!', resp) @scenario('data/feedback_per_criterion.xml', user_id='Bernard') def test_render_grade_feedback(self, xblock): @@ -251,7 +252,7 @@ def test_peer_update_after_override(self, xblock): # Create all but the last peer assessment of the current user; no peer grade will be available graded_by = xblock.get_assessment_module('peer-assessment')['must_be_graded_by'] - for scorer_sub, scorer_name, assessment in zip(scorer_subs, self.PEERS, PEER_ASSESSMENTS)[:-1]: + for scorer_sub, scorer_name, assessment in list(zip(scorer_subs, self.PEERS, PEER_ASSESSMENTS))[:-1]: self.create_peer_assessment( scorer_sub, scorer_name, @@ -336,7 +337,7 @@ def test_assessment_does_not_match_rubric(self, xblock): # (since it's not part of the original assessment), # but at least it won't display an error. resp = self.request(xblock, 'render_grade', json.dumps({})) - self.assertGreater(resp, 0) + self.assertGreater(len(resp), 0) @ddt.file_data('data/waiting_scenarios.json') @scenario('data/grade_waiting_scenario.xml', user_id='Omar') @@ -405,7 +406,8 @@ def test_submit_feedback(self, xblock): feedback = peer_api.get_assessment_feedback(xblock.submission_uuid) self.assertIsNot(feedback, None) self.assertEqual(feedback['feedback_text'], u'I disliked my assessment') - self.assertItemsEqual( + six.assertCountEqual( + self, feedback['options'], [{'text': u'Option 1'}, {'text': u'Option 2'}] ) @@ -427,7 +429,7 @@ def test_submit_feedback_no_options(self, xblock): # Verify that the feedback was created in the database feedback = peer_api.get_assessment_feedback(xblock.submission_uuid) self.assertIsNot(feedback, None) - self.assertItemsEqual(feedback['options'], []) + six.assertCountEqual(self, feedback['options'], []) @scenario('data/grade_scenario.xml', user_id='Bob') def test_submit_feedback_invalid_options(self, xblock): diff --git a/openassessment/xblock/test/test_staff.py b/openassessment/xblock/test/test_staff.py index 2606b2dabf..8f02e8b159 100644 --- a/openassessment/xblock/test/test_staff.py +++ b/openassessment/xblock/test/test_staff.py @@ -6,6 +6,7 @@ import json import mock +import six from openassessment.assessment.api import staff as staff_api @@ -20,7 +21,7 @@ def _assert_path_and_context(self, xblock, expected_context): path, context = xblock.staff_path_and_context() self.assertEqual('openassessmentblock/staff/oa_staff_grade.html', path) - self.assertItemsEqual(expected_context, context) + six.assertCountEqual(self, expected_context, context) # Verify that we render without error resp = self.request(xblock, 'render_staff_assessment', json.dumps({})) @@ -161,7 +162,7 @@ def test_staff_assess_handler(self, xblock): self.assertEqual(assessment['score_type'], 'ST') self.assertEqual(assessment['feedback'], u'Staff: good job!') - parts = sorted(assessment['parts']) + parts = sorted(assessment['parts'], key=lambda part: part['option']['name']) self.assertEqual(len(parts), 2) self.assertEqual(parts[0]['option']['criterion']['name'], u'Form') self.assertEqual(parts[0]['option']['name'], 'Fair') @@ -201,7 +202,7 @@ def test_permission_error(self, xblock): student_item = xblock.get_student_item_dict() xblock.create_submission(student_item, self.SUBMISSION) resp = self.request(xblock, 'staff_assess', json.dumps(STAFF_GOOD_ASSESSMENT)) - self.assertIn("You do not have permission", resp) + self.assertIn("You do not have permission", resp.decode('utf-8')) @scenario('data/self_assessment_scenario.xml', user_id='Bob') def test_invalid_options(self, xblock): diff --git a/openassessment/xblock/test/test_staff_area.py b/openassessment/xblock/test/test_staff_area.py index 0907d3a628..eaa9bd687c 100644 --- a/openassessment/xblock/test/test_staff_area.py +++ b/openassessment/xblock/test/test_staff_area.py @@ -4,9 +4,9 @@ """ from collections import namedtuple import json -import urllib from mock import MagicMock, Mock, call, patch +from six.moves.urllib.parse import urlencode from openassessment.assessment.api import peer as peer_api from openassessment.assessment.api import self as self_api @@ -169,7 +169,7 @@ def test_staff_area_student_info_no_submission(self, xblock): request.params = {"student_id": "test_student"} # Verify that we can render without error resp = xblock.render_student_info(request) - self.assertIn("a response was not found for this learner.", resp.body.lower()) + self.assertIn("a response was not found for this learner.", resp.body.decode('utf-8').lower()) @scenario('data/peer_only_scenario.xml', user_id='Bob') def test_staff_area_student_info_peer_only(self, xblock): @@ -277,8 +277,8 @@ def test_staff_area_student_info_staff_only_no_options(self, xblock): self.request( xblock, "render_student_info", - urllib.urlencode({"student_username": "Bob"}) - ) + urlencode({"student_username": "Bob"}) + ).decode('utf-8') ) @scenario('data/staff_grade_scenario.xml', user_id='Bob') @@ -415,8 +415,8 @@ def test_staff_area_student_info_image_submission(self, xblock): self.assertEquals('image', context['file_upload_type']) # Check the fully rendered template - payload = urllib.urlencode({"student_username": "Bob"}) - resp = self.request(xblock, "render_student_info", payload) + payload = urlencode({"student_username": "Bob"}) + resp = self.request(xblock, "render_student_info", payload).decode('utf-8') self.assertIn("http://www.example.com/image.jpeg", resp) @scenario('data/self_only_scenario.xml', user_id='Bob') @@ -465,8 +465,8 @@ def test_staff_area_student_info_many_images_submission(self, xblock): self.assertEquals('image', context['file_upload_type']) # Check the fully rendered template - payload = urllib.urlencode({"student_username": "Bob"}) - resp = self.request(xblock, "render_student_info", payload) + payload = urlencode({"student_username": "Bob"}) + resp = self.request(xblock, "render_student_info", payload).decode('utf-8') for i in range(3): self.assertIn("http://www.example.com/image%d.jpeg" % i, resp) self.assertIn("test_description%d" % i, resp) @@ -498,8 +498,8 @@ def test_staff_area_student_info_file_download_url_error(self, xblock): self.assertNotIn('file_url', context['submission']) # Check the fully rendered template - payload = urllib.urlencode({"student_username": "Bob"}) - resp = self.request(xblock, "render_student_info", payload) + payload = urlencode({"student_username": "Bob"}) + resp = self.request(xblock, "render_student_info", payload).decode('utf-8') self.assertIn("Bob Answer", resp) @scenario('data/grade_scenario.xml', user_id='Bob') @@ -559,7 +559,7 @@ def test_staff_area_student_info_full_workflow(self, xblock): request.params = {"student_username": "Bob"} # Verify that we can render without error resp = xblock.render_student_info(request) - self.assertIn("bob answer", resp.body.lower()) + self.assertIn("bob answer", resp.body.decode('utf-8').lower()) @scenario('data/basic_scenario.xml', user_id='Bob') def test_cancel_submission_without_reason(self, xblock): @@ -711,14 +711,14 @@ def test_staff_delete_student_state(self, xblock): request.params = {"student_username": 'Bob'} # Verify that we can see the student's grade resp = xblock.render_student_info(request) - self.assertIn("final grade", resp.body.lower()) + self.assertIn("final grade", resp.body.decode('utf-8').lower()) # Staff user Bob can clear his own submission xblock.clear_student_state('Bob', 'test_course', xblock.scope_ids.usage_id, bob_item['student_id']) # Verify that the submission was cleared resp = xblock.render_student_info(request) - self.assertIn("response was not found", resp.body.lower()) + self.assertIn("response was not found", resp.body.decode('utf-8').lower()) def _verify_staff_assessment_context(self, context, required, ungraded=None, in_progress=None): """ diff --git a/openassessment/xblock/test/test_submission.py b/openassessment/xblock/test/test_submission.py index 62a8807817..ecea5a64ef 100644 --- a/openassessment/xblock/test/test_submission.py +++ b/openassessment/xblock/test/test_submission.py @@ -119,7 +119,7 @@ def test_ability_to_submit_blank_answer_if_text_response_none(self, xblock): @scenario('data/over_grade_scenario.xml', user_id='Alice') def test_closed_submissions(self, xblock): resp = self.request(xblock, 'render_submission', json.dumps(dict())) - self.assertIn("Incomplete", resp) + self.assertIn("Incomplete", resp.decode('utf-8')) @scenario('data/line_breaks.xml') def test_prompt_line_breaks(self, xblock): @@ -127,18 +127,18 @@ def test_prompt_line_breaks(self, xblock): # (backward compatibility in case if prompt_type == 'text') resp = self.request(xblock, 'render_submission', json.dumps(dict())) expected_prompt = u"
Line 1
Line 2
Line 3
Question 123"
- self.assertIn(expected_prompt, resp)
+ self.assertIn(expected_prompt, resp.decode('utf-8'))
xblock.prompts_type = "text"
resp = self.request(xblock, 'render_submission', json.dumps(dict()))
expected_prompt = "<code><strong>Question 123</strong></code>"
- self.assertIn(expected_prompt, resp)
+ self.assertIn(expected_prompt, resp.decode('utf-8'))
@mock_s3
@override_settings(
@@ -156,10 +156,10 @@ def test_upload_url(self, xblock):
resp = self.request(xblock, 'upload_url', json.dumps({"contentType": "image/jpeg",
"filename": "test.jpg"}), response_format='json')
self.assertTrue(resp['success'])
- self.assertTrue(resp['url'].startswith(
- 'https://mybucket.s3.amazonaws.com/submissions_attachments/test_student/test_course/' +
- xblock.scope_ids.usage_id
- ))
+ self.assertIn(
+ '/submissions_attachments/test_student/test_course/' + xblock.scope_ids.usage_id,
+ resp['url']
+ )
@mock_s3
@override_settings(
@@ -251,10 +251,10 @@ def test_upload_files_with_uppercase_ext(self, xblock):
resp = self.request(xblock, 'upload_url', json.dumps({'contentType': 'filename',
'filename': 'test.PDF'}), response_format='json')
self.assertTrue(resp['success'])
- self.assertTrue(resp['url'].startswith(
- 'https://mybucket.s3.amazonaws.com/submissions_attachments/test_student/test_course/' +
- xblock.scope_ids.usage_id
- ))
+ self.assertIn(
+ '/submissions_attachments/test_student/test_course/' + xblock.scope_ids.usage_id,
+ resp['url']
+ )
class SubmissionRenderTest(XBlockHandlerTestCase):
@@ -601,8 +601,8 @@ def test_closed_graded(self, xblock):
def test_integration(self, xblock):
# Expect that the response step is open and displays the deadline
resp = self.request(xblock, 'render_submission', json.dumps(dict()))
- self.assertIn('Enter your response to the prompt', resp)
- self.assertIn('2999-05-06T00:00:00+00:00', resp)
+ self.assertIn('Enter your response to the prompt', resp.decode('utf-8'))
+ self.assertIn('2999-05-06T00:00:00+00:00', resp.decode('utf-8'))
# Create a submission for the user
xblock.create_submission(
@@ -612,7 +612,7 @@ def test_integration(self, xblock):
# Expect that the response step is "submitted"
resp = self.request(xblock, 'render_submission', json.dumps(dict()))
- self.assertIn('your response has been submitted', resp.lower())
+ self.assertIn('your response has been submitted', resp.decode('utf-8').lower())
def _assert_path_and_context(self, xblock, expected_path, expected_context):
"""
diff --git a/openassessment/xblock/validation.py b/openassessment/xblock/validation.py
index 12d7de62a6..41a5b0006a 100644
--- a/openassessment/xblock/validation.py
+++ b/openassessment/xblock/validation.py
@@ -1,8 +1,13 @@
"""
Validate changes to an XBlock before it is updated.
"""
+from __future__ import absolute_import
+
from collections import Counter
+import six
+from six.moves import zip
+
from openassessment.assessment.api.student_training import validate_training_examples
from openassessment.assessment.serializers import InvalidRubric, rubric_from_dict
from openassessment.xblock.data_conversion import convert_training_examples_list_to_dict
@@ -27,7 +32,7 @@ def _match_by_order(items, others):
"""
# Sort each dictionary by its "name" key, then zip them and return
key_func = lambda x: x['order_num']
- return zip(sorted(items, key=key_func), sorted(others, key=key_func))
+ return list(zip(sorted(items, key=key_func), sorted(others, key=key_func)))
def _duplicates(items):
@@ -258,7 +263,7 @@ def validate_dates(start, end, date_ranges, _):
try:
resolve_dates(start, end, date_ranges, _)
except (DateValidationError, InvalidDateFormat) as ex:
- return False, unicode(ex)
+ return False, six.text_type(ex)
else:
return True, u''
@@ -382,7 +387,7 @@ def validate_submission(submission, prompts, _, text_response='required'):
return False, message
for submission_part in submission:
- if type(submission_part) != unicode:
+ if type(submission_part) != six.text_type:
return False, message
return True, u''
diff --git a/openassessment/xblock/xml.py b/openassessment/xblock/xml.py
index 899bea8c04..22270c9615 100644
--- a/openassessment/xblock/xml.py
+++ b/openassessment/xblock/xml.py
@@ -1,6 +1,8 @@
"""
Serialize and deserialize OpenAssessment XBlock content to/from XML.
"""
+from __future__ import absolute_import
+
from uuid import uuid4 as uuid
import logging
import json
@@ -9,6 +11,7 @@
import defusedxml.ElementTree as safe_etree
import lxml.etree as etree
import pytz
+import six
from openassessment.xblock.data_conversion import update_assessments_format
from openassessment.xblock.lms_mixin import GroupAccessDict
@@ -59,7 +62,7 @@ def _safe_get_text(element):
Returns:
unicode
"""
- return unicode(element.text) if element.text is not None else u""
+ return six.text_type(element.text) if element.text is not None else u""
def _serialize_prompts(prompts_root, prompts_list):
@@ -82,7 +85,7 @@ def _serialize_prompts(prompts_root, prompts_list):
# Prompt description
prompt_description = etree.SubElement(prompt_el, 'description')
- prompt_description.text = unicode(prompt.get('description', u''))
+ prompt_description.text = six.text_type(prompt.get('description', u''))
def _serialize_options(options_root, options_list):
@@ -105,22 +108,22 @@ def _serialize_options(options_root, options_list):
option_el = etree.SubElement(options_root, 'option')
# Points (default to 0)
- option_el.set('points', unicode(option.get('points', 0)))
+ option_el.set('points', six.text_type(option.get('points', 0)))
# Name (default to a UUID)
option_name = etree.SubElement(option_el, 'name')
if 'name' in option:
- option_name.text = unicode(option['name'])
+ option_name.text = six.text_type(option['name'])
else:
- option_name.text = unicode(uuid().hex)
+ option_name.text = six.text_type(uuid().hex)
# Label (default to the option name, then an empty string)
option_label = etree.SubElement(option_el, 'label')
- option_label.text = unicode(option.get('label', option.get('name', u'')))
+ option_label.text = six.text_type(option.get('label', option.get('name', u'')))
# Explanation (default to empty str)
option_explanation = etree.SubElement(option_el, 'explanation')
- option_explanation.text = unicode(option.get('explanation', u''))
+ option_explanation.text = six.text_type(option.get('explanation', u''))
def _serialize_criteria(criteria_root, criteria_list):
@@ -146,17 +149,17 @@ def _serialize_criteria(criteria_root, criteria_list):
# Criterion name (default to a UUID)
criterion_name = etree.SubElement(criterion_el, u'name')
if 'name' in criterion:
- criterion_name.text = unicode(criterion['name'])
+ criterion_name.text = six.text_type(criterion['name'])
else:
- criterion_name.text = unicode(uuid().hex)
+ criterion_name.text = six.text_type(uuid().hex)
# Criterion label (default to the name, then an empty string)
criterion_label = etree.SubElement(criterion_el, 'label')
- criterion_label.text = unicode(criterion.get('label', criterion.get('name', u'')))
+ criterion_label.text = six.text_type(criterion.get('label', criterion.get('name', u'')))
# Criterion prompt (default to empty string)
criterion_prompt = etree.SubElement(criterion_el, 'prompt')
- criterion_prompt.text = unicode(criterion.get('prompt', u''))
+ criterion_prompt.text = six.text_type(criterion.get('prompt', u''))
# Criterion feedback disabled, optional, or required
# If disabled, do not set the attribute.
@@ -194,11 +197,11 @@ def serialize_rubric(rubric_root, oa_block):
if oa_block.rubric_feedback_prompt is not None:
feedback_prompt = etree.SubElement(rubric_root, 'feedbackprompt')
- feedback_prompt.text = unicode(oa_block.rubric_feedback_prompt)
+ feedback_prompt.text = six.text_type(oa_block.rubric_feedback_prompt)
if oa_block.rubric_feedback_default_text is not None:
feedback_text = etree.SubElement(rubric_root, 'feedback_default_text')
- feedback_text.text = unicode(oa_block.rubric_feedback_default_text)
+ feedback_text.text = six.text_type(oa_block.rubric_feedback_default_text)
def parse_date(date_str, name=""):
@@ -223,9 +226,9 @@ def parse_date(date_str, name=""):
return None
try:
# Get the date into ISO format
- parsed_date = dateutil.parser.parse(unicode(date_str)).replace(tzinfo=pytz.utc)
+ parsed_date = dateutil.parser.parse(six.text_type(date_str)).replace(tzinfo=pytz.utc)
formatted_date = parsed_date.strftime("%Y-%m-%dT%H:%M:%S")
- return unicode(formatted_date)
+ return six.text_type(formatted_date)
except (ValueError, TypeError):
msg = (
'The format of the given date ({date}) for the {name} is invalid. '
@@ -499,8 +502,8 @@ def parse_examples_xml(examples):
raise UpdateFromXmlError(u'Each "select" element must have an "option" attribute')
example_dict['options_selected'].append({
- 'criterion': unicode(select_el.get('criterion')),
- 'option': unicode(select_el.get('option'))
+ 'criterion': six.text_type(select_el.get('criterion')),
+ 'option': six.text_type(select_el.get('option'))
})
examples_list.append(example_dict)
@@ -530,7 +533,7 @@ def parse_assessments_xml(assessments_root):
# Assessment name
if 'name' in assessment.attrib:
- assessment_dict['name'] = unicode(assessment.get('name'))
+ assessment_dict['name'] = six.text_type(assessment.get('name'))
else:
raise UpdateFromXmlError('All "assessment" elements must contain a "name" element.')
@@ -576,7 +579,7 @@ def parse_assessments_xml(assessments_root):
# Staff assessment is the only type to use an explicit required marker
if assessment_dict['name'] != 'staff-assessment':
raise UpdateFromXmlError('The "required" field is only allowed for staff assessment.')
- assessment_dict['required'] = _parse_boolean(unicode(assessment.get('required')))
+ assessment_dict['required'] = _parse_boolean(six.text_type(assessment.get('required')))
# Training examples
examples = assessment.findall('example')
@@ -622,7 +625,7 @@ def serialize_training_examples(examples, assessment_el):
for part in parts:
part_el = etree.SubElement(answer_el, 'part')
- part_el.text = unicode(part.get('text', u''))
+ part_el.text = six.text_type(part.get('text', u''))
except: # excuse the bare-except, looking for more information on EDUCATOR-1817
log.exception('Error parsing training example: %s', example_dict)
raise
@@ -631,8 +634,8 @@ def serialize_training_examples(examples, assessment_el):
options_selected = example_dict.get('options_selected', [])
for selected_dict in options_selected:
select_el = etree.SubElement(example_el, 'select')
- select_el.set('criterion', unicode(selected_dict.get('criterion', '')))
- select_el.set('option', unicode(selected_dict.get('option', '')))
+ select_el.set('criterion', six.text_type(selected_dict.get('criterion', '')))
+ select_el.set('option', six.text_type(selected_dict.get('option', '')))
def serialize_assessments(assessments_root, oa_block):
@@ -653,22 +656,22 @@ def serialize_assessments(assessments_root, oa_block):
assessment = etree.SubElement(assessments_root, 'assessment')
# Set assessment attributes, defaulting to empty values
- assessment.set('name', unicode(assessment_dict.get('name', '')))
+ assessment.set('name', six.text_type(assessment_dict.get('name', '')))
if 'must_grade' in assessment_dict:
- assessment.set('must_grade', unicode(assessment_dict['must_grade']))
+ assessment.set('must_grade', six.text_type(assessment_dict['must_grade']))
if 'must_be_graded_by' in assessment_dict:
- assessment.set('must_be_graded_by', unicode(assessment_dict['must_be_graded_by']))
+ assessment.set('must_be_graded_by', six.text_type(assessment_dict['must_be_graded_by']))
if assessment_dict.get('start') is not None:
- assessment.set('start', unicode(assessment_dict['start']))
+ assessment.set('start', six.text_type(assessment_dict['start']))
if assessment_dict.get('due') is not None:
- assessment.set('due', unicode(assessment_dict['due']))
+ assessment.set('due', six.text_type(assessment_dict['due']))
if assessment_dict.get('required') is not None:
- assessment.set('required', unicode(assessment_dict['required']))
+ assessment.set('required', six.text_type(assessment_dict['required']))
# Training examples
examples = assessment_dict.get('examples', [])
@@ -693,34 +696,34 @@ def serialize_content_to_xml(oa_block, root):
# Set the submission start date
if oa_block.submission_start is not None:
- root.set('submission_start', unicode(oa_block.submission_start))
+ root.set('submission_start', six.text_type(oa_block.submission_start))
# Set submission due date
if oa_block.submission_due is not None:
- root.set('submission_due', unicode(oa_block.submission_due))
+ root.set('submission_due', six.text_type(oa_block.submission_due))
# Set leaderboard show
if oa_block.leaderboard_show:
- root.set('leaderboard_show', unicode(oa_block.leaderboard_show))
+ root.set('leaderboard_show', six.text_type(oa_block.leaderboard_show))
# Set text response
if oa_block.text_response:
- root.set('text_response', unicode(oa_block.text_response))
+ root.set('text_response', six.text_type(oa_block.text_response))
# Set file upload response
if oa_block.file_upload_response:
- root.set('file_upload_response', unicode(oa_block.file_upload_response))
+ root.set('file_upload_response', six.text_type(oa_block.file_upload_response))
# Set File upload settings
if oa_block.file_upload_type:
- root.set('file_upload_type', unicode(oa_block.file_upload_type))
+ root.set('file_upload_type', six.text_type(oa_block.file_upload_type))
# Set File type white listing
if oa_block.white_listed_file_types:
- root.set('white_listed_file_types', unicode(oa_block.white_listed_file_types_string))
+ root.set('white_listed_file_types', six.text_type(oa_block.white_listed_file_types_string))
if oa_block.allow_latex is not None:
- root.set('allow_latex', unicode(oa_block.allow_latex))
+ root.set('allow_latex', six.text_type(oa_block.allow_latex))
# Set group access setting if not empty
if oa_block.group_access:
@@ -728,7 +731,7 @@ def serialize_content_to_xml(oa_block, root):
# Open assessment displayed title
title = etree.SubElement(root, 'title')
- title.text = unicode(oa_block.title)
+ title.text = six.text_type(oa_block.title)
# Assessment list
assessments_root = etree.SubElement(root, 'assessments')
@@ -738,7 +741,7 @@ def serialize_content_to_xml(oa_block, root):
prompts_root = etree.SubElement(root, 'prompts')
_serialize_prompts(prompts_root, oa_block.prompts)
- root.set('prompts_type', unicode(oa_block.prompts_type))
+ root.set('prompts_type', six.text_type(oa_block.prompts_type))
# Rubric
rubric_root = etree.SubElement(root, 'rubric')
@@ -842,37 +845,37 @@ def parse_from_xml(root):
# Set it to None by default; we will update it to the latest start date later on
submission_start = None
if 'submission_start' in root.attrib:
- submission_start = parse_date(unicode(root.attrib['submission_start']), name="submission start date")
+ submission_start = parse_date(six.text_type(root.attrib['submission_start']), name="submission start date")
# Retrieve the due date for the submission
# Set it to None by default; we will update it to the earliest deadline later on
submission_due = None
if 'submission_due' in root.attrib:
- submission_due = parse_date(unicode(root.attrib['submission_due']), name="submission due date")
+ submission_due = parse_date(six.text_type(root.attrib['submission_due']), name="submission due date")
text_response = None
if 'text_response' in root.attrib:
- text_response = unicode(root.attrib['text_response'])
+ text_response = six.text_type(root.attrib['text_response'])
file_upload_response = None
if 'file_upload_response' in root.attrib:
- file_upload_response = unicode(root.attrib['file_upload_response'])
+ file_upload_response = six.text_type(root.attrib['file_upload_response'])
allow_file_upload = None
if 'allow_file_upload' in root.attrib:
- allow_file_upload = _parse_boolean(unicode(root.attrib['allow_file_upload']))
+ allow_file_upload = _parse_boolean(six.text_type(root.attrib['allow_file_upload']))
file_upload_type = None
if 'file_upload_type' in root.attrib:
- file_upload_type = unicode(root.attrib['file_upload_type'])
+ file_upload_type = six.text_type(root.attrib['file_upload_type'])
white_listed_file_types = None
if 'white_listed_file_types' in root.attrib:
- white_listed_file_types = unicode(root.attrib['white_listed_file_types'])
+ white_listed_file_types = six.text_type(root.attrib['white_listed_file_types'])
allow_latex = False
if 'allow_latex' in root.attrib:
- allow_latex = _parse_boolean(unicode(root.attrib['allow_latex']))
+ allow_latex = _parse_boolean(six.text_type(root.attrib['allow_latex']))
group_access = {}
if 'group_access' in root.attrib:
@@ -897,7 +900,7 @@ def parse_from_xml(root):
prompts_type = 'text'
if 'prompts_type' in root.attrib:
- prompts_type = unicode(root.attrib['prompts_type'])
+ prompts_type = six.text_type(root.attrib['prompts_type'])
# Retrieve the leaderboard if it exists, otherwise set it to 0
leaderboard_show = 0