Example #1
0
from api_teams import ApiTeams  # pylint: disable=relative-import
from api_rocket_chat import ApiRocketChat  # pylint: disable=relative-import

from django.conf import settings
from django.contrib.auth.models import User

from xblock.core import XBlock
from xblock.fields import Scope, String, Boolean, DateTime, Integer, Float
from xblock.fragment import Fragment
from xblockutils.resources import ResourceLoader
from xblockutils.settings import XBlockWithSettingsMixin
from xblockutils.studio_editable import StudioEditableXBlockMixin


LOADER = ResourceLoader(__name__)
LOG = logging.getLogger(__name__)


@XBlock.wants("user")  # pylint: disable=too-many-ancestors, too-many-instance-attributes
@XBlock.wants("settings")
class RocketChatXBlock(XBlock, XBlockWithSettingsMixin, StudioEditableXBlockMixin):
    """
    This class allows to embed a chat window inside a unit course
    and set the necessary variables to config the rocketChat enviroment
    """
    display_name = String(
        display_name="Display Name",
        scope=Scope.settings,
        default="Rocket Chat"
    )
Example #2
0
# You should have received a copy of the GNU Affero General Public License
# along with this program in a file in the toplevel directory called
# "AGPLv3".  If not, see <http://www.gnu.org/licenses/>.
#

from xblock.fields import String
from xblockutils.base_test import SeleniumBaseTest, SeleniumXBlockTest
from xblockutils.resources import ResourceLoader

# Studio adds a url_name property to each XBlock but Workbench doesn't.
# Since we rely on it, we need to mock url_name support so it can be set via XML and
# accessed like a normal field.
from problem_builder import MentoringBlock
MentoringBlock.url_name = String()

loader = ResourceLoader(__name__)

CORRECT, INCORRECT, PARTIAL = "correct", "incorrect", "partially-correct"


class PopupCheckMixin(object):
    """
    Code used by MentoringBaseTest and MentoringAssessmentBaseTest
    """
    def popup_check(self, mentoring, item_feedbacks, prefix='', do_submit=True):

        submit = mentoring.find_element_by_css_selector('.submit input.input-main')

        for index, expected_feedback in enumerate(item_feedbacks):
            choice_wrapper = mentoring.find_elements_by_css_selector(prefix + " .choice")[index]
            if do_submit:
Example #3
0
import logging

import markdown2
import pkg_resources
from django.conf import settings as django_settings
from xblock.core import XBlock
from xblock.fields import Scope, String
from xblock.fragment import Fragment
from xblockutils.resources import ResourceLoader
from xblockutils.settings import XBlockWithSettingsMixin
from xblockutils.studio_editable import StudioEditableXBlockMixin, loader

from .utils import _

log = logging.getLogger(__name__)  # pylint: disable=invalid-name
xblock_loader = ResourceLoader(__name__)  # pylint: disable=invalid-name

SETTINGS_KEY = 'markdown'
DEFAULT_EXTRAS = [
    "code-friendly", "fenced-code-blocks", "footnotes", "tables",
    "use-file-vars"
]
DEFAULT_SETTINGS = {"extras": DEFAULT_EXTRAS, "safe_mode": True}


def get_xblock_settings():
    """Extract xblock settings."""
    try:
        xblock_settings = django_settings.XBLOCK_SETTINGS
        settings = xblock_settings.get(SETTINGS_KEY, DEFAULT_SETTINGS)
    except AttributeError:
Example #4
0
 def include_theme_files(self, fragment):
     theme = self.get_theme()
     theme_package, theme_files = theme['package'], theme['locations']
     for theme_file in theme_files:
         fragment.add_css(
             ResourceLoader(theme_package).load_unicode(theme_file))
Example #5
0
""" Google Calendar XBlock implementation """
# -*- coding: utf-8 -*-
#

# Imports ###########################################################
import logging

from django import utils
from xblock.core import XBlock
from xblock.fields import Scope, String, Integer
from xblock.fragment import Fragment
from xblockutils.publish_event import PublishEventMixin
from xblockutils.resources import ResourceLoader

LOG = logging.getLogger(__name__)
RESOURCE_LOADER = ResourceLoader(__name__)

# Constants ###########################################################
DEFAULT_CALENDAR_ID = "*****@*****.**"
DEFAULT_CALENDAR_URL = (
    'https://www.google.com/calendar/embed?mode=Month&src={}&showCalendars=0&hl=en-us'
    .format(DEFAULT_CALENDAR_ID))
CALENDAR_TEMPLATE = "/templates/html/google_calendar.html"
CALENDAR_EDIT_TEMPLATE = "/templates/html/google_calendar_edit.html"


def _(text):
    """
    Dummy ugettext.
    """
    return text
Example #6
0
class OpenClassroomXBlock(XBlock):
    """
    An XBlock providing an embedded Open Classroom lesson.
    """
    loader = ResourceLoader(__name__)

    _EVENT_NAME_EXPLORATION_LOADED = 'openclassroom.exploration.loaded'
    _EVENT_NAME_EXPLORATION_COMPLETED = 'openclassroom.exploration.completed'
    _EVENT_NAME_STATE_TRANSITION = 'openclassroom.exploration.state.changed'

    display_name = String(help=_("Display name of the component"),
                          default=_("Open Classroom lesson"),
                          scope=Scope.content)
    openclassroomid = String(
        help=_("ID of the Open Classroom lesson to embed"),
        default="2DB88aOgiXgD",
        scope=Scope.content)
    src = String(help=_("Source URL of the site"),
                 default="https://lessons.openclassroom.edu.vn",
                 scope=Scope.content)

    def resource_string(self, path):
        """Handy helper for getting resources from our kit."""
        data = pkg_resources.resource_string(__name__, path)
        return data.decode("utf8")

    def render_template(self, path, context):
        return self.loader.render_django_template(
            os.path.join('templates', path),
            context=Context(context),
        )

    def get_translation_content(self):
        try:
            return self.resource_string(
                'static/js/translations/{lang}/textjs.js'.format(
                    lang=utils.translation.get_language(), ))
        except IOError:
            return self.resource_string('static/js/translations/en/textjs.js')

    def student_view(self, context=None):
        """
        The primary view of the OpenClassroomXBlock, shown to students
        when viewing courses.
        """
        frag = Fragment(
            self.render_template("openclassroom.html", {
                'src': self.src,
                'openclassroomid': self.openclassroomid,
            }))
        frag.add_javascript(self.get_translation_content())
        frag.add_javascript(
            self.resource_string('static/js/openclassroom_player.js'))
        frag.add_javascript(self.resource_string("static/js/openclassroom.js"))
        frag.initialize_js('OpenClassroomXBlock')
        return frag

    def author_view(self, context=None):
        """
        A view of the XBlock to show within the Studio preview. For some
        reason, the student_view() does not display, so we show a placeholder
        instead.
        """
        frag = Fragment(
            self.render_template("openclassroom_preview.html", {
                'src': self.src,
                'openclassroomid': self.openclassroomid,
            }))
        frag.add_javascript(self.get_translation_content())
        return frag

    def _log(self, event_name, payload):
        """
        Logger for load, state transition and completion events.
        """
        self.runtime.publish(self, event_name, payload)

    @XBlock.json_handler
    def on_exploration_loaded(self, data, suffix=''):
        """Called when an exploration has loaded."""
        self._log(
            self._EVENT_NAME_EXPLORATION_LOADED, {
                'exploration_id': self.openclassroomid,
                'exploration_version': data['explorationVersion'],
            })

    @XBlock.json_handler
    def on_state_transition(self, data, suffix=''):
        """Called when a state transition in the exploration has occurred."""
        self._log(
            self._EVENT_NAME_STATE_TRANSITION, {
                'exploration_id': self.openclassroomid,
                'old_state_name': data['oldStateName'],
                'new_state_name': data['newStateName'],
                'exploration_version': data['explorationVersion'],
            })

    @XBlock.json_handler
    def on_exploration_completed(self, data, suffix=''):
        """Called when the exploration has been completed."""
        self._log(
            self._EVENT_NAME_EXPLORATION_COMPLETED, {
                'exploration_id': self.openclassroomid,
                'exploration_version': data['explorationVersion'],
            })

    def studio_view(self, context):
        """
        Create a fragment used to display the edit view in the Studio.
        """
        frag = Fragment(
            self.render_template(
                "openclassroom_edit.html", {
                    'src': self.src,
                    'openclassroomid': self.openclassroomid or '',
                    'display_name': self.display_name,
                }))

        frag.add_javascript(self.get_translation_content())
        js_str = pkg_resources.resource_string(
            __name__, "static/js/openclassroom_edit.js")
        frag.add_javascript(unicode(js_str))
        frag.initialize_js('OpenClassroomXBlockEditor')

        return frag

    @XBlock.json_handler
    def studio_submit(self, data, suffix=''):
        """
        Called when submitting the form in Studio.
        """
        self.openclassroomid = data.get('openclassroomid')
        self.src = data.get('src')
        self.display_name = data.get('display_name')

        return {'result': 'success'}

    @staticmethod
    def workbench_scenarios():
        """A canned scenario for display in the workbench."""
        return [
            ("Open Classroom Embedding", """<vertical_demo>
                <openclassroom openclassroomid="0" src="https://lessons.openclassroom.edu.vn"/>
                </vertical_demo>
             """),
        ]
Example #7
0
class FreeTextResponseViewMixin(
        I18nXBlockMixin,
        EnforceDueDates,
        XBlockFragmentBuilderMixin,
        StudioEditableXBlockMixin,
):
    """
    Handle view logic for FreeTextResponse XBlock instances
    """

    loader = ResourceLoader(__name__)
    static_js_init = 'FreeTextResponseView'

    def provide_context(self, context=None):
        """
        Build a context dictionary to render the student view
        """
        context = context or {}
        context = dict(context)
        context.update({
            'display_name': self.display_name,
            'indicator_class': self._get_indicator_class(),
            'nodisplay_class': self._get_nodisplay_class(),
            'problem_progress': self._get_problem_progress(),
            'prompt': self.prompt,
            'student_answer': self.student_answer,
            'is_past_due': self.is_past_due(),
            'used_attempts_feedback': self._get_used_attempts_feedback(),
            'visibility_class': self._get_indicator_visibility_class(),
            'word_count_message': self._get_word_count_message(),
            'display_other_responses': self.display_other_student_responses,
            'other_responses': self.get_other_answers(),
            'user_alert': '',
            'submitted_message': '',
        })
        return context

    def _get_indicator_class(self):
        """
        Returns the class of the correctness indicator element
        """
        result = 'unanswered'
        if self.display_correctness and self._word_count_valid():
            if self._determine_credit() == Credit.zero:
                result = 'incorrect'
            else:
                result = 'correct'
        return result

    def _get_nodisplay_class(self):
        """
        Returns the css class for the submit button
        """
        result = ''
        if self.max_attempts > 0 and self.count_attempts >= self.max_attempts:
            result = 'nodisplay'
        return result

    def _word_count_valid(self):
        """
        Returns a boolean value indicating whether the current
        word count of the user's answer is valid
        """
        word_count = len(self.student_answer.split())
        result = self.max_word_count >= word_count >= self.min_word_count
        return result

    def _determine_credit(self):
        #  Not a standard xlbock pylint disable.
        # This is a problem with pylint 'enums and R0204 in general'
        """
        Helper Method that determines the level of credit that
        the user should earn based on their answer
        """
        result = None
        if self.student_answer == '' or not self._word_count_valid():
            result = Credit.zero
        elif not self.fullcredit_keyphrases \
                and not self.halfcredit_keyphrases:
            result = Credit.full
        elif _is_at_least_one_phrase_present(
                self.fullcredit_keyphrases,
                self.student_answer
        ):
            result = Credit.full
        elif _is_at_least_one_phrase_present(
                self.halfcredit_keyphrases,
                self.student_answer
        ):
            result = Credit.half
        else:
            result = Credit.zero
        return result

    def _get_problem_progress(self):
        """
        Returns a statement of progress for the XBlock, which depends
        on the user's current score
        """
        if self.weight == 0:
            result = ''
        elif self.score == 0.0:
            result = "({})".format(
                self.ngettext(
                    "{weight} point possible",
                    "{weight} points possible",
                    self.weight,
                ).format(
                    weight=self.weight,
                )
            )
        else:
            scaled_score = self.score * self.weight
            # No trailing zero and no scientific notation
            score_string = ('%.15f' % scaled_score).rstrip('0').rstrip('.')
            result = "({})".format(
                self.ngettext(
                    "{score_string}/{weight} point",
                    "{score_string}/{weight} points",
                    self.weight,
                ).format(
                    score_string=score_string,
                    weight=self.weight,
                )
            )
        return result

    def _get_used_attempts_feedback(self):
        """
        Returns the text with feedback to the user about the number of attempts
        they have used if applicable
        """
        result = ''
        if self.max_attempts > 0:
            result = self.ngettext(
                'You have used {count_attempts} of {max_attempts} submission',
                'You have used {count_attempts} of {max_attempts} submissions',
                self.max_attempts,
            ).format(
                count_attempts=self.count_attempts,
                max_attempts=self.max_attempts,
            )
        return result

    def _get_indicator_visibility_class(self):
        """
        Returns the visibility class for the correctness indicator html element
        """
        if self.display_correctness:
            result = ''
        else:
            result = 'hidden'
        return result

    def _get_word_count_message(self):
        """
        Returns the word count message
        """
        result = self.ngettext(
            "Your response must be "
            "between {min} and {max} word.",
            "Your response must be "
            "between {min} and {max} words.",
            self.max_word_count,
        ).format(
            min=self.min_word_count,
            max=self.max_word_count,
        )
        return result

    def get_other_answers(self):
        """
        Returns at most MAX_RESPONSES answers from the pool.

        Does not return answers the student had submitted.
        """
        student_id = self.get_student_id()
        display_other_responses = self.display_other_student_responses
        shouldnt_show_other_responses = not display_other_responses
        student_answer_incorrect = self._determine_credit() == Credit.zero
        if student_answer_incorrect or shouldnt_show_other_responses:
            return []
        return_list = [
            response
            for response in self.displayable_answers
            if response['student_id'] != student_id
        ]
        return_list = return_list[-(MAX_RESPONSES):]
        return return_list

    @XBlock.json_handler
    def submit(self, data, suffix=''):
        # pylint: disable=unused-argument
        """
        Processes the user's submission
        """
        # Fails if the UI submit/save buttons were shut
        # down on the previous sumbisson
        if self._can_submit():
            self.student_answer = data['student_answer']
            # Counting the attempts and publishing a score
            # even if word count is invalid.
            self.count_attempts += 1
            self._compute_score()
            display_other_responses = self.display_other_student_responses
            if display_other_responses and data.get('can_record_response'):
                self.store_student_response()
        result = {
            'status': 'success',
            'problem_progress': self._get_problem_progress(),
            'indicator_class': self._get_indicator_class(),
            'used_attempts_feedback': self._get_used_attempts_feedback(),
            'nodisplay_class': self._get_nodisplay_class(),
            'submitted_message': self._get_submitted_message(),
            'user_alert': self._get_user_alert(
                ignore_attempts=True,
            ),
            'other_responses': self.get_other_answers(),
            'display_other_responses': self.display_other_student_responses,
            'visibility_class': self._get_indicator_visibility_class(),
        }
        return result

    @XBlock.json_handler
    def save_reponse(self, data, suffix=''):
        # pylint: disable=unused-argument
        """
        Processes the user's save
        """
        # Fails if the UI submit/save buttons were shut
        # down on the previous sumbisson
        if self.max_attempts == 0 or self.count_attempts < self.max_attempts:
            self.student_answer = data['student_answer']
        result = {
            'status': 'success',
            'problem_progress': self._get_problem_progress(),
            'used_attempts_feedback': self._get_used_attempts_feedback(),
            'nodisplay_class': self._get_nodisplay_class(),
            'submitted_message': '',
            'user_alert': self.saved_message,
            'visibility_class': self._get_indicator_visibility_class(),
        }
        return result

    def _get_invalid_word_count_message(self, ignore_attempts=False):
        """
        Returns the invalid word count message
        """
        result = ''
        if (
                (ignore_attempts or self.count_attempts > 0) and
                (not self._word_count_valid())
        ):
            word_count_message = self._get_word_count_message()
            result = self.gettext(
                "Invalid Word Count. {word_count_message}"
            ).format(
                word_count_message=word_count_message,
            )
        return result

    def _get_submitted_message(self):
        """
        Returns the message to display in the submission-received div
        """
        result = ''
        if self._word_count_valid():
            result = self.submitted_message
        return result

    def _get_user_alert(self, ignore_attempts=False):
        """
        Returns the message to display in the user_alert div
        depending on the student answer
        """
        result = ''
        if not self._word_count_valid():
            result = self._get_invalid_word_count_message(ignore_attempts)
        return result

    def _can_submit(self):
        """
        Determine if a user may submit a response
        """
        if self.is_past_due():
            return False
        if self.max_attempts == 0:
            return True
        if self.count_attempts < self.max_attempts:
            return True
        return False

    def _generate_validation_message(self, text):
        """
        Helper method to generate a ValidationMessage from
        the supplied string
        """
        result = ValidationMessage(
            ValidationMessage.ERROR,
            self.gettext(text_type(text))
        )
        return result

    def validate_field_data(self, validation, data):
        """
        Validates settings entered by the instructor.
        """
        if data.weight < 0:
            msg = self._generate_validation_message(
                'Weight Attempts cannot be negative'
            )
            validation.add(msg)
        if data.max_attempts < 0:
            msg = self._generate_validation_message(
                'Maximum Attempts cannot be negative'
            )
            validation.add(msg)
        if data.min_word_count < 1:
            msg = self._generate_validation_message(
                'Minimum Word Count cannot be less than 1'
            )
            validation.add(msg)
        if data.min_word_count > data.max_word_count:
            msg = self._generate_validation_message(
                'Minimum Word Count cannot be greater than Max Word Count'
            )
            validation.add(msg)
        if not data.submitted_message:
            msg = self._generate_validation_message(
                'Submission Received Message cannot be blank'
            )
            validation.add(msg)
Example #8
0
class PDFXBlock(XBlock):
    """
    PDF XBlock.
    """

    loader = ResourceLoader(__name__)
    PDF_FILEUPLOAD_MAX_SIZE = 100 * 1000 * 1000  # 100 MB

    # Icon of the XBlock. Values : [other (default), video, problem]

    icon_class = 'other'

    # Enable view as specific student

    show_in_read_only_mode = True

    # Fields

    display_name = String(
        display_name=_('Display Name'),
        default=_('PDF'),
        scope=Scope.settings,
        help=
        _('This name appears in the horizontal navigation at the top of the page.'
          ))

    pdf_file_path = String(
        display_name=_("Upload PDF file"),
        scope=Scope.settings,
    )

    pdf_file_name = String(scope=Scope.settings)

    @classmethod
    def upload_max_size(self):
        """
        returns max file size limit in system
        """
        return getattr(settings, "PDF_FILEUPLOAD_MAX_SIZE",
                       self.PDF_FILEUPLOAD_MAX_SIZE)

    @classmethod
    def file_size_over_limit(self, file_obj):
        """
        checks if file size is under limit.
        """
        file_obj.seek(0, os.SEEK_END)
        return file_obj.tell() > self.upload_max_size()

    def get_live_url(self):
        """
            Get the file url
        """
        if not self.pdf_file_path:
            return ''
        return reverse('eol/pdf:pdf_get_url',
                       kwargs={'file_path': self.pdf_file_path})

    def load_resource(self, resource_path):  # pylint: disable=no-self-use
        """
        Gets the content of a resource
        """

        resource_content = pkg_resources.resource_string(
            __name__, resource_path)
        return resource_content.decode('utf-8')

    def render_template(self, path, context=None):
        """
        Evaluate a template by resource path, applying the provided context
        """

        return self.loader.render_django_template(
            os.path.join('static/html', path),
            context=Context(context or {}),
            i18n_service=self.runtime.service(self, 'i18n'))

    def student_view(self, context=None):
        """
        The primary view of the XBlock, shown to students
        when viewing courses.
        """
        context = {
            'display_name': self.display_name,
            'url': self.get_live_url(),
        }
        html = self.render_template('pdf_view.html', context)
        frag = Fragment(html)
        frag.add_css(self.load_resource('static/css/pdf.css'))
        frag.add_javascript(self.load_resource('static/js/pdf_view.js'))
        frag.initialize_js('pdfXBlockInitView')

        self.runtime.publish(self, 'completion',
                             {'completion': 1.0})  # Set xblock completed
        return frag

    def studio_view(self, context=None):
        """
        The secondary view of the XBlock, shown to teachers
        when editing the XBlock.
        """

        context = {
            'display_name': self.display_name,
            'pdf_file_name': self.pdf_file_name,
        }
        html = self.render_template('pdf_edit.html', context)

        frag = Fragment(html)
        frag.add_javascript(self.load_resource('static/js/pdf_edit.js'))
        frag.initialize_js('pdfXBlockInitEdit')
        return frag

    @XBlock.handler
    def save_pdf(self, request, suffix=''):  # pylint: disable=unused-argument
        """
        The saving handler.
        """
        self.display_name = request.params['display_name']
        response = {"result": "success", "errors": []}
        if not hasattr(request.params["pdf_file"], "file"):
            # File not uploaded
            return Response(json.dumps(response),
                            content_type="application/json",
                            charset="utf8")

        pdf_file = request.params["pdf_file"]
        sha1 = get_sha1(pdf_file.file)
        if self.file_size_over_limit(pdf_file.file):
            response["errors"].append(
                'Unable to upload file. Max size limit is {size}'.format(
                    size=self.upload_max_size()))
            return Response(json.dumps(response),
                            content_type="application/json",
                            charset="utf8")

        path = get_file_storage_path(self.location, sha1, pdf_file.file.name)
        storage = get_storage()
        storage.save(path, File(pdf_file.file))

        logger.info("Saving file: %s at path: %s", pdf_file.file.name, path)
        self.pdf_file_path = path
        self.pdf_file_name = pdf_file.file.name
        return Response(json.dumps(response),
                        content_type="application/json",
                        charset="utf8")
Example #9
0
 def test_load_scenarios(self):
     loader = ResourceLoader(__name__)
     scenarios = loader.load_scenarios_from_path("data")
     self.assertEquals(scenarios, expected_scenarios)
Example #10
0
 def test_load_scenarios_with_identifiers(self):
     loader = ResourceLoader(__name__)
     scenarios = loader.load_scenarios_from_path("data",
                                                 include_identifier=True)
     self.assertEquals(scenarios, expected_scenarios_with_identifiers)
Example #11
0
 def test_render_js_template(self):
     loader = ResourceLoader(__name__)
     s = loader.render_js_template("data/simple_django_template.txt",
                                   example_id, example_context)
     self.assertEquals(s, expected_filled_js_template)
Example #12
0
 def test_load_unicode_from_another_module(self):
     s = ResourceLoader("tests.unit.data").load_unicode(
         "simple_django_template.txt")
     self.assertEquals(s, expected_string)
Example #13
0
 def test_load_unicode(self):
     s = ResourceLoader(__name__).load_unicode(
         "data/simple_django_template.txt")
     self.assertEquals(s, expected_string)
Example #14
0
 def test_render_template_deprecated(self, mock_warn):
     loader = ResourceLoader(__name__)
     s = loader.render_template("data/simple_django_template.txt",
                                example_context)
     self.assertTrue(mock_warn.called)
     self.assertEquals(s, expected_filled_template)
class FreeTextResponse(
        EnforceDueDates,
        MissingDataFetcherMixin,
        StudioEditableXBlockMixin,
        XBlock,
):
    #  pylint: disable=too-many-ancestors, too-many-instance-attributes
    """
    Enables instructors to create questions with free-text responses.
    """

    loader = ResourceLoader(__name__)

    @staticmethod
    def workbench_scenarios():
        """
        Gather scenarios to be displayed in the workbench
        """
        scenarios = [
            ('Free-text Response XBlock', '''<sequence_demo>
                    <freetextresponse />
                    <freetextresponse name='My First XBlock' />
                    <freetextresponse
                        display_name="Full Credit is asdf, half is fdsa"
                        fullcredit_keyphrases="['asdf']"
                        halfcredit_keyphrases="['fdsa']"
                        min_word_count="2"
                        max_word_count="2"
                        max_attempts="5"
                    />
                    <freetextresponse
                        display_name="Min words 2"
                        min_word_count="2"
                    />
                    <freetextresponse
                        display_name="Max Attempts 5 XBlock"
                        max_attempts="5"
                    />
                    <freetextresponse
                        display_name="Full credit is asdf, Max Attempts 3"
                        max_attempts="3"
                        min_word_count="2"
                        fullcredit_keyphrases="['asdf']"
                    />
                    <freetextresponse
                        display_name="New submitted message"
                        submitted_message="Different message"
                    />
                    <freetextresponse
                        display_name="Blank submitted message"
                        submitted_message=""
                    />
                    <freetextresponse
                        display_name="Display correctness if off"
                        display_correctness="False"
                    />
                </sequence_demo>
             '''),
        ]
        return scenarios

    display_correctness = Boolean(
        display_name=_('Display Correctness?'),
        help=_('This is a flag that indicates if the indicator '
               'icon should be displayed after a student enters '
               'their response'),
        default=True,
        scope=Scope.settings,
    )
    display_other_student_responses = Boolean(
        display_name=_('Display Other Student Responses'),
        help=_('This will display other student responses to the '
               'student after they submit their response.'),
        default=False,
        scope=Scope.settings,
    )
    displayable_answers = List(
        default=[],
        scope=Scope.user_state_summary,
        help=_('System selected answers to give to students'),
    )
    display_name = String(
        display_name=_('Display Name'),
        help=_('This is the title for this question type'),
        default='Free-text Response',
        scope=Scope.settings,
    )
    fullcredit_keyphrases = List(
        display_name=_('Full-Credit Key Phrases'),
        help=_('This is a list of words or phrases, one of '
               'which must be present in order for the student\'s answer '
               'to receive full credit'),
        default=[],
        scope=Scope.settings,
    )
    halfcredit_keyphrases = List(
        display_name=_('Half-Credit Key Phrases'),
        help=_('This is a list of words or phrases, one of '
               'which must be present in order for the student\'s answer '
               'to receive half credit'),
        default=[],
        scope=Scope.settings,
    )
    max_attempts = Integer(
        display_name=_('Maximum Number of Attempts'),
        help=_('This is the maximum number of times a '
               'student is allowed to attempt the problem'),
        default=0,
        values={'min': 1},
        scope=Scope.settings,
    )
    max_word_count = Integer(
        display_name=_('Maximum Word Count'),
        help=_('This is the maximum number of words allowed for this '
               'question'),
        default=10000,
        values={'min': 1},
        scope=Scope.settings,
    )
    min_word_count = Integer(
        display_name=_('Minimum Word Count'),
        help=_('This is the minimum number of words required '
               'for this question'),
        default=1,
        values={'min': 1},
        scope=Scope.settings,
    )
    prompt = String(
        display_name=_('Prompt'),
        help=_('This is the prompt students will see when '
               'asked to enter their response'),
        default=_('Please enter your response within this text area'),
        scope=Scope.settings,
        multiline_editor=True,
    )
    submitted_message = String(
        display_name=_('Submission Received Message'),
        help=_('This is the message students will see upon '
               'submitting their response'),
        default='Your submission has been received',
        scope=Scope.settings,
    )
    weight = Integer(
        display_name=_('Weight'),
        help=_('This assigns an integer value representing '
               'the weight of this problem'),
        default=0,
        values={'min': 1},
        scope=Scope.settings,
    )
    saved_message = String(
        display_name=_('Draft Received Message'),
        help=_('This is the message students will see upon '
               'submitting a draft response'),
        default=('Your answers have been saved but not graded. '
                 'Click "Submit" to grade them.'),
        scope=Scope.settings,
    )

    count_attempts = Integer(
        default=0,
        scope=Scope.user_state,
    )
    score = Float(
        default=0.0,
        scope=Scope.user_state,
    )
    student_answer = String(
        default='',
        scope=Scope.user_state,
    )

    has_score = True

    editable_fields = (
        'display_name',
        'prompt',
        'weight',
        'max_attempts',
        'display_correctness',
        'min_word_count',
        'max_word_count',
        'fullcredit_keyphrases',
        'halfcredit_keyphrases',
        'submitted_message',
        'display_other_student_responses',
        'saved_message',
    )

    def ungettext(self, *args, **kwargs):
        """
        XBlock aware version of `ungettext`.
        """
        return self.runtime.service(self, 'i18n').ungettext(*args, **kwargs)

    def build_fragment(
        self,
        rendered_template,
        initialize_js_func,
        additional_css=[],
        additional_js=[],
    ):
        #  pylint: disable=dangerous-default-value, too-many-arguments
        """
        Creates a fragment for display.
        """
        fragment = Fragment(rendered_template)
        for item in additional_css:
            url = self.runtime.local_resource_url(self, item)
            fragment.add_css_url(url)
        for item in additional_js:
            url = self.runtime.local_resource_url(self, item)
            fragment.add_javascript_url(url)
        fragment.initialize_js(initialize_js_func)
        return fragment

    # Decorate the view in order to support multiple devices e.g. mobile
    # See: https://openedx.atlassian.net/wiki/display/MA/Course+Blocks+API
    # section 'View @supports(multi_device) decorator'
    @XBlock.supports('multi_device')
    def student_view(self, context={}):
        # pylint: disable=dangerous-default-value
        """The main view of FreeTextResponse, displayed when viewing courses.

        The main view which displays the general layout for FreeTextResponse

        Args:
            context: Not used for this view.

        Returns:
            (Fragment): The HTML Fragment for this XBlock, which determines the
            general frame of the FreeTextResponse Question.
        """
        display_other_responses = self.display_other_student_responses
        self.runtime.service(self, 'i18n')
        context.update({
            'display_name':
            self.display_name,
            'indicator_class':
            self._get_indicator_class(),
            'nodisplay_class':
            self._get_nodisplay_class(),
            'problem_progress':
            self._get_problem_progress(),
            'prompt':
            self.prompt,
            'student_answer':
            self.student_answer,
            'is_past_due':
            self.is_past_due(),
            'used_attempts_feedback':
            self._get_used_attempts_feedback(),
            'visibility_class':
            self._get_indicator_visibility_class(),
            'word_count_message':
            self._get_word_count_message(),
            'display_other_responses':
            display_other_responses,
            'other_responses':
            self.get_other_answers(),
        })
        template = self.loader.render_django_template(
            'templates/freetextresponse_view.html',
            context=Context(context),
            i18n_service=self.runtime.service(self, 'i18n'),
        )
        fragment = self.build_fragment(
            template,
            initialize_js_func='FreeTextResponseView',
            additional_css=[
                'public/view.css',
            ],
            additional_js=[
                'public/view.js',
            ],
        )
        return fragment

    def max_score(self):
        """
        Returns the configured number of possible points for this component.
        Arguments:
            None
        Returns:
            float: The number of possible points for this component
        """
        return self.weight

    def _generate_validation_message(self, msg):
        """
        Helper method to generate a ValidationMessage from
        the supplied string
        """
        result = ValidationMessage(
            ValidationMessage.ERROR,
            self.ugettext(unicode(msg))  # pylint: disable=undefined-variable
        )
        return result

    def validate_field_data(self, validation, data):
        """
        Validates settings entered by the instructor.
        """
        if data.weight < 0:
            msg = self._generate_validation_message(
                'Weight Attempts cannot be negative')
            validation.add(msg)
        if data.max_attempts < 0:
            msg = self._generate_validation_message(
                'Maximum Attempts cannot be negative')
            validation.add(msg)
        if data.min_word_count < 1:
            msg = self._generate_validation_message(
                'Minimum Word Count cannot be less than 1')
            validation.add(msg)
        if data.min_word_count > data.max_word_count:
            msg = self._generate_validation_message(
                'Minimum Word Count cannot be greater than Max Word Count')
            validation.add(msg)
        if not data.submitted_message:
            msg = self._generate_validation_message(
                'Submission Received Message cannot be blank')
            validation.add(msg)

    def _get_indicator_visibility_class(self):
        """
        Returns the visibility class for the correctness indicator html element
        """
        if self.display_correctness:
            result = ''
        else:
            result = 'hidden'
        return result

    def _get_word_count_message(self):
        """
        Returns the word count message
        """
        result = self.ungettext(
            "Your response must be "
            "between {min} and {max} word.",
            "Your response must be "
            "between {min} and {max} words.",
            self.max_word_count,
        ).format(
            min=self.min_word_count,
            max=self.max_word_count,
        )
        return result

    def _get_invalid_word_count_message(self, ignore_attempts=False):
        """
        Returns the invalid word count message
        """
        result = ''
        if ((ignore_attempts or self.count_attempts > 0)
                and (not self._word_count_valid())):
            word_count_message = self._get_word_count_message()
            result = self.ugettext(
                "Invalid Word Count. {word_count_message}").format(
                    word_count_message=word_count_message, )
        return result

    def _get_indicator_class(self):
        """
        Returns the class of the correctness indicator element
        """
        result = 'unanswered'
        if self.display_correctness and self._word_count_valid():
            if self._determine_credit() == Credit.zero:
                result = 'incorrect'
            else:
                result = 'correct'
        return result

    def _word_count_valid(self):
        """
        Returns a boolean value indicating whether the current
        word count of the user's answer is valid
        """
        word_count = len(self.student_answer.split())
        return self.max_word_count >= word_count >= self.min_word_count

    @classmethod
    def _is_at_least_one_phrase_present(cls, phrases, answer):
        """
        Determines if at least one of the supplied phrases is
        present in the given answer
        """
        answer = answer.lower()
        matches = [phrase.lower() in answer for phrase in phrases]
        return any(matches)

    def _get_problem_progress(self):
        """
        Returns a statement of progress for the XBlock, which depends
        on the user's current score
        """
        if self.weight == 0:
            result = ''
        elif self.score == 0.0:
            result = "({})".format(
                self.ungettext(
                    "{weight} point possible",
                    "{weight} points possible",
                    self.weight,
                ).format(weight=self.weight, ))
        else:
            scaled_score = self.score * self.weight
            # No trailing zero and no scientific notation
            score_string = ('%.15f' % scaled_score).rstrip('0').rstrip('.')
            result = "({})".format(
                self.ungettext(
                    "{score_string}/{weight} point",
                    "{score_string}/{weight} points",
                    self.weight,
                ).format(
                    score_string=score_string,
                    weight=self.weight,
                ))
        return result

    def _compute_score(self):
        """
        Computes and publishes the user's core for the XBlock
        based on their answer
        """
        credit = self._determine_credit()
        self.score = credit.value
        try:
            self.runtime.publish(self, 'grade', {
                'value': self.score,
                'max_value': Credit.full.value
            })
        except IntegrityError:
            pass

    def _determine_credit(self):
        #  Not a standard xlbock pylint disable.
        # This is a problem with pylint 'enums and R0204 in general'
        """
        Helper Method that determines the level of credit that
        the user should earn based on their answer
        """
        result = None
        if self.student_answer == '' or not self._word_count_valid():
            result = Credit.zero
        elif not self.fullcredit_keyphrases \
                and not self.halfcredit_keyphrases:
            result = Credit.full
        elif FreeTextResponse._is_at_least_one_phrase_present(
                self.fullcredit_keyphrases, self.student_answer):
            result = Credit.full
        elif FreeTextResponse._is_at_least_one_phrase_present(
                self.halfcredit_keyphrases, self.student_answer):
            result = Credit.half
        else:
            result = Credit.zero
        return result

    def _get_used_attempts_feedback(self):
        """
        Returns the text with feedback to the user about the number of attempts
        they have used if applicable
        """
        result = ''
        if self.max_attempts > 0:
            result = self.ungettext(
                'You have used {count_attempts} of {max_attempts} submission',
                'You have used {count_attempts} of {max_attempts} submissions',
                self.max_attempts,
            ).format(
                count_attempts=self.count_attempts,
                max_attempts=self.max_attempts,
            )
        return result

    def _get_nodisplay_class(self):
        """
        Returns the css class for the submit button
        """
        result = ''
        if self.max_attempts > 0 and self.count_attempts >= self.max_attempts:
            result = 'nodisplay'
        return result

    def _get_submitted_message(self):
        """
        Returns the message to display in the submission-received div
        """
        result = ''
        if self._word_count_valid():
            result = self.submitted_message
        return result

    def _get_user_alert(self, ignore_attempts=False):
        """
        Returns the message to display in the user_alert div
        depending on the student answer
        """
        result = ''
        if not self._word_count_valid():
            result = self._get_invalid_word_count_message(ignore_attempts)
        return result

    def _can_submit(self):
        if self.is_past_due():
            return False
        if self.max_attempts == 0:
            return True
        if self.count_attempts < self.max_attempts:
            return True
        return False

    @XBlock.json_handler
    def submit(self, data, suffix=''):
        # pylint: disable=unused-argument
        """
        Processes the user's submission
        """
        # Fails if the UI submit/save buttons were shut
        # down on the previous sumbisson
        if self._can_submit():
            self.student_answer = data['student_answer']
            # Counting the attempts and publishing a score
            # even if word count is invalid.
            self.count_attempts += 1
            self._compute_score()
            display_other_responses = self.display_other_student_responses
            if display_other_responses and data.get('can_record_response'):
                self.store_student_response()
        result = {
            'status': 'success',
            'problem_progress': self._get_problem_progress(),
            'indicator_class': self._get_indicator_class(),
            'used_attempts_feedback': self._get_used_attempts_feedback(),
            'nodisplay_class': self._get_nodisplay_class(),
            'submitted_message': self._get_submitted_message(),
            'user_alert': self._get_user_alert(ignore_attempts=True, ),
            'other_responses': self.get_other_answers(),
            'display_other_responses': self.display_other_student_responses,
            'visibility_class': self._get_indicator_visibility_class(),
        }
        return result

    @XBlock.json_handler
    def save_reponse(self, data, suffix=''):
        # pylint: disable=unused-argument
        """
        Processes the user's save
        """
        # Fails if the UI submit/save buttons were shut
        # down on the previous sumbisson
        if self.max_attempts == 0 or self.count_attempts < self.max_attempts:
            self.student_answer = data['student_answer']
        result = {
            'status': 'success',
            'problem_progress': self._get_problem_progress(),
            'used_attempts_feedback': self._get_used_attempts_feedback(),
            'nodisplay_class': self._get_nodisplay_class(),
            'submitted_message': '',
            'user_alert': self.saved_message,
            'visibility_class': self._get_indicator_visibility_class(),
        }
        return result

    def store_student_response(self):
        """
        Submit a student answer to the answer pool by appending the given
        answer to the end of the list.
        """
        # if the answer is wrong, do not display it
        if self.score != Credit.full.value:
            return

        student_id = self.get_student_id()
        # remove any previous answers the student submitted
        for index, response in enumerate(self.displayable_answers):
            if response['student_id'] == student_id:
                del self.displayable_answers[index]
                break

        self.displayable_answers.append({
            'student_id': student_id,
            'answer': self.student_answer,
        })

        # Want to store extra response so student can still see
        # MAX_RESPONSES answers if their answer is in the pool.
        response_index = -(MAX_RESPONSES + 1)
        self.displayable_answers = self.displayable_answers[response_index:]

    def get_other_answers(self):
        """
        Returns at most MAX_RESPONSES answers from the pool.

        Does not return answers the student had submitted.
        """
        student_id = self.get_student_id()
        display_other_responses = self.display_other_student_responses
        shouldnt_show_other_responses = not display_other_responses
        student_answer_incorrect = self._determine_credit() == Credit.zero
        if student_answer_incorrect or shouldnt_show_other_responses:
            return []
        return_list = [
            response for response in self.displayable_answers
            if response['student_id'] != student_id
        ]

        return_list = return_list[-(MAX_RESPONSES):]
        return return_list