diff --git a/.travis.yml b/.travis.yml
index 87b99925874ef9a5fcb8504019eabacd44cc0232..6aeb63e88aff86e38fabba78b5503c041e912d42 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,13 +1,21 @@
 language: python
-python: "2.7"
+
+python:
+  - "2.7"
+  - "3.5"
+
+sudo: false
+
 install:
   - "pip install six"
   - "make install"
-sudo: false
+
 script:
   - make quality
   - make test
+
 branches:
     only:
       - master
+
 after_success: coveralls
diff --git a/lti_consumer/exceptions.py b/lti_consumer/exceptions.py
index 16f3383ffd742e15d72fbe54f54f41fb2200cb1f..e80b55f55642ed82256d5f8ff78d88faa1001926 100644
--- a/lti_consumer/exceptions.py
+++ b/lti_consumer/exceptions.py
@@ -7,4 +7,3 @@ class LtiError(Exception):
     """
     General error class for LTI XBlock.
     """
-    pass
diff --git a/lti_consumer/lti.py b/lti_consumer/lti.py
index ea0b591a109b211070db5a43e906bc4831941b04..7fac705622a420135972e40cf64bcd2318e83682 100644
--- a/lti_consumer/lti.py
+++ b/lti_consumer/lti.py
@@ -5,16 +5,18 @@ For more details see:
 https://www.imsglobal.org/activity/learning-tools-interoperability
 """
 
-import logging
-import urllib
+from __future__ import absolute_import, unicode_literals
+
 import json
+import logging
 
+import six.moves.urllib.error
+import six.moves.urllib.parse
 from six import text_type
 
 from .exceptions import LtiError
 from .oauth import get_oauth_request_signature, verify_oauth_body_signature
 
-
 log = logging.getLogger(__name__)
 
 
@@ -83,14 +85,14 @@ def parse_result_json(json_str):
                 log.error("[LTI] %s", msg)
                 raise LtiError(msg)
         except (TypeError, ValueError) as err:
-            msg = "Could not convert resultScore to float: {}".format(err.message)
+            msg = "Could not convert resultScore to float: {}".format(str(err))
             log.error("[LTI] %s", msg)
             raise LtiError(msg)
 
     return score, json_obj.get('comment', "")
 
 
-class LtiConsumer(object):
+class LtiConsumer(object):  # pylint: disable=bad-option-value, useless-object-inheritance
     """
     Limited implementation of the LTI 1.1/2.0 specification.
 
@@ -202,7 +204,9 @@ class LtiConsumer(object):
         # so '='' becomes '%3D'.
         # We send form via browser, so browser will encode it again,
         # So we need to decode signature back:
-        oauth_signature[u'oauth_signature'] = urllib.unquote(oauth_signature[u'oauth_signature']).decode('utf8')
+        oauth_signature[u'oauth_signature'] = six.moves.urllib.parse.unquote(
+            oauth_signature[u'oauth_signature']
+        )
 
         # Add LTI parameters to OAuth parameters for sending in form.
         lti_parameters.update(oauth_signature)
@@ -290,15 +294,15 @@ class LtiConsumer(object):
         content_type = request.headers.get('Content-Type')
         if verify_content_type and content_type != LtiConsumer.CONTENT_TYPE_RESULT_JSON:
             log.error("[LTI]: v2.0 result service -- bad Content-Type: %s", content_type)
-            raise LtiError(
-                "For LTI 2.0 result service, Content-Type must be %s.  Got %s",
+            error_msg = "For LTI 2.0 result service, Content-Type must be {}.  Got {}".format(
                 LtiConsumer.CONTENT_TYPE_RESULT_JSON,
                 content_type
             )
+            raise LtiError(error_msg)
 
         __, secret = self.xblock.lti_provider_key_secret
         try:
             return verify_oauth_body_signature(request, secret, self.xblock.outcome_service_url)
         except (ValueError, LtiError) as err:
-            log.error("[LTI]: v2.0 result service -- OAuth body verification failed: %s", err.message)
-            raise LtiError(err.message)
+            log.error("[LTI]: v2.0 result service -- OAuth body verification failed: %s", str(err))
+            raise LtiError(str(err))
diff --git a/lti_consumer/lti_consumer.py b/lti_consumer/lti_consumer.py
index c7cec87198583045b130aae16fe0776182e37feb..9afcff06e812dd48a95ef2fbbe8236fc68045905 100644
--- a/lti_consumer/lti_consumer.py
+++ b/lti_consumer/lti_consumer.py
@@ -50,29 +50,29 @@ What is supported:
             GET / PUT / DELETE HTTP methods respectively
 """
 
+from __future__ import absolute_import, unicode_literals
+
 import logging
-import bleach
 import re
-from importlib import import_module
-import json
-import urllib
-
 from collections import namedtuple
-from webob import Response
+from importlib import import_module
 
+import six.moves.urllib.error
+import six.moves.urllib.parse
+import six
+import bleach
 from django.utils import timezone
-
-from xblock.core import String, Scope, List, XBlock
+from webob import Response
+from xblock.core import List, Scope, String, XBlock
 from xblock.fields import Boolean, Float, Integer
 from xblock.fragment import Fragment
 from xblock.validation import ValidationMessage
-
 from xblockutils.resources import ResourceLoader
 from xblockutils.studio_editable import StudioEditableXBlockMixin
 
 from .exceptions import LtiError
-from .oauth import log_authorization_header
 from .lti import LtiConsumer
+from .oauth import log_authorization_header
 from .outcomes import OutcomeService
 from .utils import _
 
@@ -155,7 +155,7 @@ def parse_handler_suffix(suffix):
 LaunchTargetOption = namedtuple('LaunchTargetOption', ['display_name', 'value'])
 
 
-class LaunchTarget(object):
+class LaunchTarget(object):  # pylint: disable=bad-option-value, useless-object-inheritance
     """
     Constants for launch_target field options
     """
@@ -476,7 +476,9 @@ class LtiConsumerXBlock(StudioEditableXBlockMixin, XBlock):
     def validate_field_data(self, validation, data):
         if not isinstance(data.custom_parameters, list):
             _ = self.runtime.service(self, "i18n").ugettext
-            validation.add(ValidationMessage(ValidationMessage.ERROR, unicode(_("Custom Parameters must be a list"))))
+            validation.add(ValidationMessage(ValidationMessage.ERROR, six.text_type(
+                _("Custom Parameters must be a list")
+            )))
 
     def get_settings(self):
         """
@@ -547,7 +549,7 @@ class LtiConsumerXBlock(StudioEditableXBlockMixin, XBlock):
         context_id is an opaque identifier that uniquely identifies the context (e.g., a course)
         that contains the link being launched.
         """
-        return unicode(self.course_id)  # pylint: disable=no-member
+        return six.text_type(self.course_id)  # pylint: disable=no-member
 
     @property
     def role(self):
@@ -589,7 +591,7 @@ class LtiConsumerXBlock(StudioEditableXBlockMixin, XBlock):
         user_id = self.runtime.anonymous_student_id
         if user_id is None:
             raise LtiError(self.ugettext("Could not get user id for current request"))
-        return unicode(urllib.quote(user_id))
+        return six.text_type(six.moves.urllib.parse.quote(user_id))
 
     @property
     def resource_link_id(self):
@@ -623,7 +625,7 @@ class LtiConsumerXBlock(StudioEditableXBlockMixin, XBlock):
         i4x-2-3-lti-31de800015cf4afb973356dbe81496df this part of resource_link_id:
         makes resource_link_id to be unique among courses inside same system.
         """
-        return unicode(urllib.quote(
+        return six.text_type(six.moves.urllib.parse.quote(
             "{}-{}".format(self.runtime.hostname, self.location.html_id())  # pylint: disable=no-member
         ))
 
@@ -638,7 +640,7 @@ class LtiConsumerXBlock(StudioEditableXBlockMixin, XBlock):
         This field is generally optional, but is required for grading.
         """
         return "{context}:{resource_link}:{user_id}".format(
-            context=urllib.quote(self.context_id),
+            context=six.moves.urllib.parse.quote(self.context_id),
             resource_link=self.resource_link_id,
             user_id=self.user_id
         )
@@ -701,7 +703,7 @@ class LtiConsumerXBlock(StudioEditableXBlockMixin, XBlock):
                 if param_name not in LTI_PARAMETERS:
                     param_name = 'custom_' + param_name
 
-                custom_parameters[unicode(param_name)] = unicode(param_value)
+                custom_parameters[six.text_type(param_name)] = six.text_type(param_value)
         return custom_parameters
 
     @property
@@ -849,7 +851,7 @@ class LtiConsumerXBlock(StudioEditableXBlockMixin, XBlock):
             return Response(status=404)
 
         return Response(
-            json.dumps(response_body),
+            json_body=response_body,
             content_type=LtiConsumer.CONTENT_TYPE_RESULT_JSON,
         )
 
diff --git a/lti_consumer/oauth.py b/lti_consumer/oauth.py
index c27e54ba930501357740fffe1e4cda966dae7294..7a01ebf0892ba69578f20cc4d0cbcdda81726854 100644
--- a/lti_consumer/oauth.py
+++ b/lti_consumer/oauth.py
@@ -2,20 +2,23 @@
 Utility functions for working with OAuth signatures.
 """
 
-import logging
-import hashlib
+from __future__ import absolute_import, unicode_literals
+
 import base64
-import urllib
+import hashlib
+import logging
 
+import six.moves.urllib.error
+import six.moves.urllib.parse
+import six
 from oauthlib import oauth1
 
 from .exceptions import LtiError
 
-
 log = logging.getLogger(__name__)
 
 
-class SignedRequest(object):
+class SignedRequest(object):  # pylint: disable=bad-option-value, useless-object-inheritance
     """
     Encapsulates request attributes needed when working
     with the `oauthlib.oauth1` API
@@ -45,14 +48,14 @@ def get_oauth_request_signature(key, secret, url, headers, body):
     Returns:
         str: Authorization header for the OAuth signed request
     """
-    client = oauth1.Client(client_key=unicode(key), client_secret=unicode(secret))
+    client = oauth1.Client(client_key=six.text_type(key), client_secret=six.text_type(secret))
     try:
         # Add Authorization header which looks like:
         # Authorization: OAuth oauth_nonce="80966668944732164491378916897",
         # oauth_timestamp="1378916897", oauth_version="1.0", oauth_signature_method="HMAC-SHA1",
         # oauth_consumer_key="", oauth_signature="frVp4JuvT1mVXlxktiAUjQ7%2F1cw%3D"
-        __, headers, __ = client.sign(
-            unicode(url.strip()),
+        _, headers, _ = client.sign(
+            six.text_type(url.strip()),
             http_method=u'POST',
             body=body,
             headers=headers
@@ -83,7 +86,7 @@ def verify_oauth_body_signature(request, lti_provider_secret, service_url):
     """
 
     headers = {
-        'Authorization': unicode(request.headers.get('Authorization')),
+        'Authorization': six.text_type(request.headers.get('Authorization')),
         'Content-Type': request.content_type,
     }
 
@@ -94,18 +97,18 @@ def verify_oauth_body_signature(request, lti_provider_secret, service_url):
     oauth_headers = dict(oauth_params)
     oauth_signature = oauth_headers.pop('oauth_signature')
     mock_request_lti_1 = SignedRequest(
-        uri=unicode(urllib.unquote(service_url)),
-        http_method=unicode(request.method),
-        params=oauth_headers.items(),
+        uri=six.text_type(six.moves.urllib.parse.unquote(service_url)),
+        http_method=six.text_type(request.method),
+        params=list(oauth_headers.items()),
         signature=oauth_signature
     )
     mock_request_lti_2 = SignedRequest(
-        uri=unicode(urllib.unquote(request.url)),
-        http_method=unicode(request.method),
-        params=oauth_headers.items(),
+        uri=six.text_type(six.moves.urllib.parse.unquote(request.url)),
+        http_method=six.text_type(request.method),
+        params=list(oauth_headers.items()),
         signature=oauth_signature
     )
-    if oauth_body_hash != oauth_headers.get('oauth_body_hash'):
+    if oauth_body_hash.decode('utf-8') != oauth_headers.get('oauth_body_hash'):
         log.error(
             "OAuth body hash verification failed, provided: %s, "
             "calculated: %s, for url: %s, body is: %s",
@@ -123,7 +126,7 @@ def verify_oauth_body_signature(request, lti_provider_secret, service_url):
             "headers:%s url:%s method:%s",
             oauth_headers,
             service_url,
-            unicode(request.method)
+            six.text_type(request.method)
         )
         raise LtiError("OAuth signature verification has failed.")
 
@@ -145,18 +148,18 @@ def log_authorization_header(request, client_key, client_secret):
     """
     sha1 = hashlib.sha1()
     sha1.update(request.body)
-    oauth_body_hash = unicode(base64.b64encode(sha1.digest()))  # pylint: disable=too-many-function-args
+    oauth_body_hash = six.text_type(base64.b64encode(sha1.digest()))  # pylint: disable=too-many-function-args
     log.debug("[LTI] oauth_body_hash = %s", oauth_body_hash)
     client = oauth1.Client(client_key, client_secret)
     params = client.get_oauth_params(request)
     params.append((u'oauth_body_hash', oauth_body_hash))
     mock_request = SignedRequest(
-        uri=unicode(urllib.unquote(request.url)),
+        uri=six.text_type(six.moves.urllib.parse.unquote(request.url)),
         headers=request.headers,
         body=u"",
         decoded_body=u"",
         oauth_params=params,
-        http_method=unicode(request.method),
+        http_method=six.text_type(request.method),
     )
     sig = client.get_oauth_signature(mock_request)
     mock_request.oauth_params.append((u'oauth_signature', sig))
diff --git a/lti_consumer/outcomes.py b/lti_consumer/outcomes.py
index 0a63003301322b32a05d3f61cc696a1a5ff46b3b..9f355d0f81cc62a4a52c21907fbaf4b45f2be3fd 100644
--- a/lti_consumer/outcomes.py
+++ b/lti_consumer/outcomes.py
@@ -5,18 +5,20 @@ For more details see:
 https://www.imsglobal.org/specs/ltiomv1p0
 """
 
-import logging
-import urllib
+from __future__ import absolute_import, unicode_literals
 
-from lxml import etree
+import logging
 from xml.sax.saxutils import escape
 
+import six.moves.urllib.error
+import six.moves.urllib.parse
+from lxml import etree
 from xblockutils.resources import ResourceLoader
 
+
 from .exceptions import LtiError
 from .oauth import verify_oauth_body_signature
 
-
 log = logging.getLogger(__name__)
 
 
@@ -40,13 +42,13 @@ def parse_grade_xml_body(body):
     """
     lti_spec_namespace = "http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0"
     namespaces = {'def': lti_spec_namespace}
-    data = body.strip()
+    data = body.strip().encode('utf-8')
 
     try:
         parser = etree.XMLParser(ns_clean=True, recover=True, encoding='utf-8')  # pylint: disable=no-member
         root = etree.fromstring(data, parser=parser)  # pylint: disable=no-member
     except etree.XMLSyntaxError as ex:
-        raise LtiError(ex.message or 'Body is not valid XML')
+        raise LtiError(str(ex) or 'Body is not valid XML')
 
     try:
         imsx_message_identifier = root.xpath("//def:imsx_messageIdentifier", namespaces=namespaces)[0].text or ''
@@ -81,7 +83,7 @@ def parse_grade_xml_body(body):
     return imsx_message_identifier, sourced_id, score, action
 
 
-class OutcomeService(object):
+class OutcomeService(object):  # pylint: disable=bad-option-value, useless-object-inheritance
     """
     Service for handling LTI Outcome Management Service requests.
 
@@ -166,8 +168,8 @@ class OutcomeService(object):
             imsx_message_identifier, sourced_id, score, action = parse_grade_xml_body(request.body)
         except LtiError as ex:  # pylint: disable=no-member
             body = escape(request.body) if request.body else ''
-            error_message = "Request body XML parsing error: {} {}".format(ex.message, body)
-            log.debug("[LTI]: %s" + error_message)
+            error_message = "Request body XML parsing error: {} {}".format(str(ex), body)
+            log.debug("[LTI]: %s", error_message)
             failure_values['imsx_description'] = error_message
             return response_xml_template.format(**failure_values)
 
@@ -177,12 +179,12 @@ class OutcomeService(object):
             verify_oauth_body_signature(request, secret, self.xblock.outcome_service_url)
         except (ValueError, LtiError) as ex:
             failure_values['imsx_messageIdentifier'] = escape(imsx_message_identifier)
-            error_message = "OAuth verification error: " + escape(ex.message)
+            error_message = "OAuth verification error: " + escape(str(ex))
             failure_values['imsx_description'] = error_message
-            log.debug("[LTI]: " + error_message)
+            log.debug("[LTI]: %s", error_message)
             return response_xml_template.format(**failure_values)
 
-        real_user = self.xblock.runtime.get_real_user(urllib.unquote(sourced_id.split(':')[-1]))
+        real_user = self.xblock.runtime.get_real_user(six.moves.urllib.parse.unquote(sourced_id.split(':')[-1]))
         if not real_user:  # that means we can't save to database, as we do not have real user id.
             failure_values['imsx_messageIdentifier'] = escape(imsx_message_identifier)
             failure_values['imsx_description'] = "User not found."
@@ -197,9 +199,9 @@ class OutcomeService(object):
                 'imsx_messageIdentifier': escape(imsx_message_identifier),
                 'response': '<replaceResultResponse/>'
             }
-            log.debug("[LTI]: Grade is saved.")
+            log.debug(u"[LTI]: Grade is saved.")
             return response_xml_template.format(**values)
 
         unsupported_values['imsx_messageIdentifier'] = escape(imsx_message_identifier)
-        log.debug("[LTI]: Incorrect action.")
+        log.debug(u"[LTI]: Incorrect action.")
         return response_xml_template.format(**unsupported_values)
diff --git a/lti_consumer/tests/unit/test_lti.py b/lti_consumer/tests/unit/test_lti.py
index 60d721af7e8a4c09290fe7bd7ba675cfba3e4d54..f643253d58251bd7ba1bf169674782b4f9893d35 100644
--- a/lti_consumer/tests/unit/test_lti.py
+++ b/lti_consumer/tests/unit/test_lti.py
@@ -3,20 +3,21 @@
 Unit tests for lti_consumer.lti module
 """
 
-import unittest
+from __future__ import absolute_import, unicode_literals
 
+import unittest
 from datetime import timedelta
-from mock import Mock, PropertyMock, patch
-from six import text_type
 
 from django.utils import timezone
+from mock import Mock, PropertyMock, patch
+from six import text_type
+import six
 
-from lti_consumer.tests.unit.test_utils import make_request, patch_signed_parameters
-from lti_consumer.tests.unit.test_lti_consumer import TestLtiConsumerXBlock
-
-from lti_consumer.lti import parse_result_json, LtiConsumer
 from lti_consumer.exceptions import LtiError
-
+from lti_consumer.lti import LtiConsumer, parse_result_json
+from lti_consumer.tests.unit.test_lti_consumer import TestLtiConsumerXBlock
+from lti_consumer.tests.unit.test_utils import (make_request,
+                                                patch_signed_parameters)
 
 INVALID_JSON_INPUTS = [
     ([
@@ -107,7 +108,7 @@ class TestParseResultJson(unittest.TestCase):
         """
         for error_inputs, error_message in INVALID_JSON_INPUTS:
             for error_input in error_inputs:
-                with self.assertRaisesRegexp(LtiError, error_message):
+                with six.assertRaisesRegex(self, LtiError, error_message):
                     parse_result_json(error_input)
 
     def test_valid_json(self):
@@ -194,14 +195,13 @@ class TestLtiConsumer(TestLtiConsumerXBlock):
         self._update_xblock_for_signed_parameters()
         self.xblock.enable_processors = True
 
-        with patch('lti_consumer.lti_consumer.LtiConsumerXBlock.get_settings', return_value={
-            'parameter_processors': [
-                'lti_consumer.tests.unit.test_utils:dummy_processor',
-            ],
-        }):
+        mock_value = {
+            'parameter_processors': ['lti_consumer.tests.unit.test_utils:dummy_processor']
+        }
+        with patch('lti_consumer.lti_consumer.LtiConsumerXBlock.get_settings', return_value=mock_value):
             params = self.lti_consumer.get_signed_lti_parameters()
-            assert '' == params['custom_author_country']
-            assert 'author@example.com' == params['custom_author_email']
+            assert params['custom_author_country'] == u''
+            assert params['custom_author_email'] == u'author@example.com'
             assert not mock_log.exception.called
 
     @patch_signed_parameters
@@ -210,14 +210,13 @@ class TestLtiConsumer(TestLtiConsumerXBlock):
         self._update_xblock_for_signed_parameters()
         self.xblock.enable_processors = True
 
-        with patch('lti_consumer.lti_consumer.LtiConsumerXBlock.get_settings', return_value={
-            'parameter_processors': [
-                'lti_consumer.tests.unit.test_utils:defaulting_processor',
-            ],
-        }):
+        mock_value = {
+            'parameter_processors': ['lti_consumer.tests.unit.test_utils:defaulting_processor']
+        }
+        with patch('lti_consumer.lti_consumer.LtiConsumerXBlock.get_settings', return_value=mock_value):
             params = self.lti_consumer.get_signed_lti_parameters()
-            assert '' == params['custom_country']
-            assert 'Lex' == params['custom_name']
+            assert params['custom_country'] == u''
+            assert params['custom_name'] == u'Lex'
             assert not mock_log.exception.called
 
     @patch_signed_parameters
@@ -226,13 +225,12 @@ class TestLtiConsumer(TestLtiConsumerXBlock):
         self._update_xblock_for_signed_parameters()
         self.xblock.enable_processors = True
 
-        with patch('lti_consumer.lti_consumer.LtiConsumerXBlock.get_settings', return_value={
-            'parameter_processors': [
-                'lti_consumer.tests.unit.test_utils:faulty_processor',
-            ],
-        }):
+        mock_value = {
+            'parameter_processors': ['lti_consumer.tests.unit.test_utils:faulty_processor']
+        }
+        with patch('lti_consumer.lti_consumer.LtiConsumerXBlock.get_settings', return_value=mock_value):
             params = self.lti_consumer.get_signed_lti_parameters()
-            assert 'Lex' == params['custom_name']
+            assert params['custom_name'] == u'Lex'
             assert mock_log.exception.called
 
     def test_get_result(self):
diff --git a/lti_consumer/tests/unit/test_lti_consumer.py b/lti_consumer/tests/unit/test_lti_consumer.py
index 0560e8c82d88eb071e70587d3b8e5809d35b93b0..6bae920cf28b4b8233b9466da4a2ae15b068a807 100644
--- a/lti_consumer/tests/unit/test_lti_consumer.py
+++ b/lti_consumer/tests/unit/test_lti_consumer.py
@@ -2,19 +2,21 @@
 Unit tests for LtiConsumerXBlock
 """
 
+from __future__ import absolute_import
+
 from datetime import timedelta
-import ddt
-from mock import Mock, PropertyMock, patch
 
+import ddt
+import six
 from django.test.testcases import TestCase
 from django.utils import timezone
+from mock import Mock, PropertyMock, patch
 
-from lti_consumer.tests.unit.test_utils import FAKE_USER_ID, make_xblock, make_request
-
-from lti_consumer.lti_consumer import LtiConsumerXBlock, parse_handler_suffix
 from lti_consumer.exceptions import LtiError
+from lti_consumer.lti_consumer import LtiConsumerXBlock, parse_handler_suffix
 from lti_consumer.tests.unit import test_utils
-
+from lti_consumer.tests.unit.test_utils import (FAKE_USER_ID, make_request,
+                                                make_xblock)
 
 HTML_PROBLEM_PROGRESS = '<div class="problem-progress">'
 HTML_ERROR_MESSAGE = '<h3 class="error_message">'
@@ -86,7 +88,7 @@ class TestProperties(TestLtiConsumerXBlock):
         """
         Test `context_id` returns unicode course id
         """
-        self.assertEqual(self.xblock.context_id, unicode(self.xblock.course_id))  # pylint: disable=no-member
+        self.assertEqual(self.xblock.context_id, six.text_type(self.xblock.course_id))  # pylint: disable=no-member
 
     def test_validate(self):
         """
@@ -165,7 +167,7 @@ class TestProperties(TestLtiConsumerXBlock):
         type(mock_course).lti_passports = PropertyMock(return_value=["{}{}{}".format(provider, key, secret)])
 
         with self.assertRaises(LtiError):
-            __, __ = self.xblock.lti_provider_key_secret
+            _, _ = self.xblock.lti_provider_key_secret
 
     def test_user_id(self):
         """
@@ -285,18 +287,16 @@ class TestProperties(TestLtiConsumerXBlock):
         self.xblock.due = now + timedelta(days=1)
         self.assertFalse(self.xblock.is_past_due)
 
-    @patch('lti_consumer.lti_consumer.timezone.now')
-    def test_is_past_due_timezone_now_called(self, mock_timezone_now):
+    def test_is_past_due_timezone_now_called(self):
         """
         Test `is_past_due` calls django.utils.timezone.now to get current datetime
         """
         now = timezone.now()
         self.xblock.graceperiod = None
         self.xblock.due = now
-        mock_timezone_now.return_value = now
-
-        __ = self.xblock.is_past_due
-        self.assertTrue(mock_timezone_now.called)
+        with patch('lti_consumer.lti_consumer.timezone.now', wraps=timezone.now) as mock_timezone_now:
+            __ = self.xblock.is_past_due
+            self.assertTrue(mock_timezone_now.called)
 
 
 class TestEditableFields(TestLtiConsumerXBlock):
@@ -831,28 +831,25 @@ class TestProcessorSettings(TestLtiConsumerXBlock):
     """
     Unit tests for the adding custom LTI parameters.
     """
+    settings = {
+        'parameter_processors': ['lti_consumer.tests.unit.test_utils:dummy_processor']
+    }
+
     def test_no_processors_by_default(self):
         processors = list(self.xblock.get_parameter_processors())
         assert not processors, 'The processor list should empty by default.'
 
     def test_enable_processor(self):
         self.xblock.enable_processors = True
-        with patch('lti_consumer.lti_consumer.LtiConsumerXBlock.get_settings', return_value={
-            'parameter_processors': [
-                'lti_consumer.tests.unit.test_utils:dummy_processor',
-            ],
-        }):
+        with patch('lti_consumer.lti_consumer.LtiConsumerXBlock.get_settings', return_value=self.settings):
             processors = list(self.xblock.get_parameter_processors())
             assert len(processors) == 1, 'One processor should be enabled'
+            # pylint: disable=bad-option-value, comparison-with-callable
             assert processors[0] == test_utils.dummy_processor, 'Should load the correct function'
 
     def test_disabled_processors(self):
         self.xblock.enable_processors = False
-        with patch('lti_consumer.lti_consumer.LtiConsumerXBlock.get_settings', return_value={
-            'parameter_processors': [
-                'lti_consumer.tests.unit.test_utils:dummy_processor',
-            ],
-        }):
+        with patch('lti_consumer.lti_consumer.LtiConsumerXBlock.get_settings', return_value=self.settings):
             processors = list(self.xblock.get_parameter_processors())
             assert not processors, 'No processor should be enabled'
 
diff --git a/lti_consumer/tests/unit/test_oauth.py b/lti_consumer/tests/unit/test_oauth.py
index 57e98cbcd6853b0fdacaff9f6d0c28617c5a0465..3186c384d3daa5cec7b6d517d17fe98846826c0d 100644
--- a/lti_consumer/tests/unit/test_oauth.py
+++ b/lti_consumer/tests/unit/test_oauth.py
@@ -2,19 +2,17 @@
 Unit tests for lti_consumer.oauth module
 """
 
+from __future__ import absolute_import
+
 import unittest
 
 from mock import Mock, patch
 
-from lti_consumer.tests.unit.test_utils import make_request
-
 from lti_consumer.exceptions import LtiError
-from lti_consumer.oauth import (
-    get_oauth_request_signature,
-    verify_oauth_body_signature,
-    log_authorization_header,
-)
-
+from lti_consumer.oauth import (get_oauth_request_signature,
+                                log_authorization_header,
+                                verify_oauth_body_signature)
+from lti_consumer.tests.unit.test_utils import make_request
 
 OAUTH_PARAMS = [
     (u'oauth_nonce', u'80966668944732164491378916897'),
diff --git a/lti_consumer/tests/unit/test_outcomes.py b/lti_consumer/tests/unit/test_outcomes.py
index da01b3b9966344543d3695077a1d3e5684810562..1ebb538ae2b419a7a33ab3fd52ff219da93d4509 100644
--- a/lti_consumer/tests/unit/test_outcomes.py
+++ b/lti_consumer/tests/unit/test_outcomes.py
@@ -3,18 +3,18 @@
 Unit tests for lti_consumer.outcomes module
 """
 
-import unittest
-import textwrap
+from __future__ import absolute_import, unicode_literals
 
+import textwrap
+import unittest
 from copy import copy
-from mock import Mock, PropertyMock, patch
 
-from lti_consumer.tests.unit.test_utils import make_request
-from lti_consumer.tests.unit.test_lti_consumer import TestLtiConsumerXBlock
+from mock import Mock, PropertyMock, patch
 
-from lti_consumer.outcomes import parse_grade_xml_body, OutcomeService
 from lti_consumer.exceptions import LtiError
-
+from lti_consumer.outcomes import OutcomeService, parse_grade_xml_body
+from lti_consumer.tests.unit.test_lti_consumer import TestLtiConsumerXBlock
+from lti_consumer.tests.unit.test_utils import make_request
 
 REQUEST_BODY_TEMPLATE_VALID = textwrap.dedent("""
     <?xml version="1.0" encoding="UTF-8"?>
@@ -211,7 +211,7 @@ class TestParseGradeXmlBody(unittest.TestCase):
         Test missing <imsx_messageIdentifier> raises LtiError
         """
         with self.assertRaises(LtiError):
-            __, __, __, __ = parse_grade_xml_body(
+            _, _, _, _ = parse_grade_xml_body(
                 REQUEST_BODY_TEMPLATE_MISSING_MSG_ID.format(**REQUEST_TEMPLATE_DEFAULTS)
             )
 
@@ -220,7 +220,7 @@ class TestParseGradeXmlBody(unittest.TestCase):
         Test missing <sourcedId> raises LtiError
         """
         with self.assertRaises(LtiError):
-            __, __, __, __ = parse_grade_xml_body(
+            _, _, _, _ = parse_grade_xml_body(
                 REQUEST_BODY_TEMPLATE_MISSING_SOURCED_ID.format(**REQUEST_TEMPLATE_DEFAULTS)
             )
 
@@ -229,7 +229,7 @@ class TestParseGradeXmlBody(unittest.TestCase):
         Test missing <imsx_POXBody> raises LtiError
         """
         with self.assertRaises(LtiError):
-            __, __, __, __ = parse_grade_xml_body(
+            _, _, _, _ = parse_grade_xml_body(
                 REQUEST_BODY_TEMPLATE_MISSING_BODY.format(**REQUEST_TEMPLATE_DEFAULTS)
             )
 
@@ -238,7 +238,7 @@ class TestParseGradeXmlBody(unittest.TestCase):
         Test missing <replaceResultRequest> raises LtiError
         """
         with self.assertRaises(LtiError):
-            __, __, __, __ = parse_grade_xml_body(
+            _, _, _, _ = parse_grade_xml_body(
                 REQUEST_BODY_TEMPLATE_MISSING_ACTION.format(**REQUEST_TEMPLATE_DEFAULTS)
             )
 
@@ -247,7 +247,7 @@ class TestParseGradeXmlBody(unittest.TestCase):
         Test missing score <textString> raises LtiError
         """
         with self.assertRaises(LtiError):
-            __, __, __, __ = parse_grade_xml_body(
+            _, _, _, _ = parse_grade_xml_body(
                 REQUEST_BODY_TEMPLATE_MISSING_SCORE.format(**REQUEST_TEMPLATE_DEFAULTS)
             )
 
@@ -259,11 +259,11 @@ class TestParseGradeXmlBody(unittest.TestCase):
 
         with self.assertRaises(LtiError):
             data['score'] = 10
-            __, __, __, __ = parse_grade_xml_body(REQUEST_BODY_TEMPLATE_VALID.format(**data))
+            _, _, _, _ = parse_grade_xml_body(REQUEST_BODY_TEMPLATE_VALID.format(**data))
 
         with self.assertRaises(LtiError):
             data['score'] = -10
-            __, __, __, __ = parse_grade_xml_body(REQUEST_BODY_TEMPLATE_VALID.format(**data))
+            _, _, _, _ = parse_grade_xml_body(REQUEST_BODY_TEMPLATE_VALID.format(**data))
 
     def test_invalid_score(self):
         """
@@ -273,21 +273,21 @@ class TestParseGradeXmlBody(unittest.TestCase):
         data['score'] = '1,0'
 
         with self.assertRaises(Exception):
-            __, __, __, __ = parse_grade_xml_body(REQUEST_BODY_TEMPLATE_VALID.format(**data))
+            _, _, _, _ = parse_grade_xml_body(REQUEST_BODY_TEMPLATE_VALID.format(**data))
 
     def test_empty_xml(self):
         """
         Test empty xml raises exception
         """
         with self.assertRaises(LtiError):
-            __, __, __, __ = parse_grade_xml_body('')
+            _, _, _, _ = parse_grade_xml_body('')
 
     def test_invalid_xml(self):
         """
         Test invalid xml raises exception
         """
         with self.assertRaises(LtiError):
-            __, __, __, __ = parse_grade_xml_body('<xml>')
+            _, _, _, _ = parse_grade_xml_body('<xml>')
 
     def test_string_with_unicode_chars(self):
         """
diff --git a/lti_consumer/tests/unit/test_utils.py b/lti_consumer/tests/unit/test_utils.py
index 9c32789adb9beb3c5f5d6a0d10f7f0c47a90257e..e328aeabf913791e9f46f4252bf997969516b321 100644
--- a/lti_consumer/tests/unit/test_utils.py
+++ b/lti_consumer/tests/unit/test_utils.py
@@ -2,14 +2,14 @@
 Utility functions used within unit tests
 """
 
-from webob import Request
-from mock import patch, Mock, PropertyMock
-
-from xblock.fields import ScopeIds
-from xblock.runtime import KvsFieldData, DictKeyValueStore
+from __future__ import absolute_import
 
+import six
+from mock import Mock, PropertyMock, patch
+from webob import Request
 from workbench.runtime import WorkbenchRuntime
-
+from xblock.fields import ScopeIds
+from xblock.runtime import DictKeyValueStore, KvsFieldData
 
 FAKE_USER_ID = 'fake_user_id'
 
@@ -31,7 +31,7 @@ def make_xblock(xblock_name, xblock_cls, attributes):
         hostname='localhost',
     )
     xblock.course_id = 'course-v1:edX+DemoX+Demo_Course'
-    for key, value in attributes.iteritems():
+    for key, value in six.iteritems(attributes):
         setattr(xblock, key, value)
     return xblock
 
@@ -100,7 +100,6 @@ def defaulting_processor(_xblock):
     """
     A dummy LTI parameter processor with default params.
     """
-    pass
 
 
 defaulting_processor.lti_xblock_default_params = {
diff --git a/openedx.yaml b/openedx.yaml
index 06ce8055446729a7fb000b91baa052a2b899dbe1..fa679fd566f715daacb1950104e17eea5fd05aa0 100644
--- a/openedx.yaml
+++ b/openedx.yaml
@@ -4,7 +4,7 @@
 nick: lti
 oeps:
     oep-2: true
-    oep-7: false
+    oep-7: true
     oep-18: false
 owner: scottrish
 track-pulls: true
diff --git a/requirements.txt b/requirements.txt
index d098ce2b1d5442c1c08bc3d0be071459c5f1c7f0..555b3eb2c3c6f97179a25a9f465d4fee1d3cb628 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,8 +1,9 @@
 lxml
 bleach
+django==1.11.25
 oauthlib
 mako
 lazy
-git+https://github.com/edx/XBlock.git#egg=XBlock
-git+https://github.com/edx/xblock-utils.git@v1.0.0#egg=xblock-utils==v1.0.0
+-e git+https://github.com/edx/XBlock.git#egg=XBlock
+-e git+https://github.com/edx/xblock-utils.git#egg=xblock-utils
 -e .
diff --git a/setup.py b/setup.py
index 8e4be4194af6c9b8f697dea547b4e86ab158d0af..8ed60ef2a9e67310b9d83ca85006090b5cea6631 100644
--- a/setup.py
+++ b/setup.py
@@ -1,6 +1,9 @@
 """Setup for lti_consumer XBlock."""
 
+from __future__ import absolute_import
+
 import os
+
 from setuptools import setup
 
 
@@ -22,7 +25,7 @@ def package_data(pkg, roots):
 
 setup(
     name='lti_consumer-xblock',
-    version='1.2.0',
+    version='1.2.1',
     description='This XBlock implements the consumer side of the LTI specification.',
     packages=[
         'lti_consumer',
diff --git a/test.py b/test.py
index 56599543313b4f573f2009051131f653312f7e2d..389fd40e5672be31798edf5a823d1c2a3b57915d 100644
--- a/test.py
+++ b/test.py
@@ -3,6 +3,8 @@
 """
 Run tests for the LTI Consumer XBlock
 """
+from __future__ import absolute_import
+
 import os
 import sys
 
diff --git a/test_requirements.txt b/test_requirements.txt
index c7bb14178c9b6c64c46d15d03d7fd1184377bea6..ea45f3f80d92298005181abc31a3fafbc5ba9c20 100644
--- a/test_requirements.txt
+++ b/test_requirements.txt
@@ -1,11 +1,10 @@
 -r requirements.txt
 
-django-nose==1.4.4
-astroid==1.3.8  # Pinning to avoid backwards incompatibility issue with pylint/pylint-django
+django-nose==1.4.6
 ddt
 coveralls
 mock
 pep8
-git+https://github.com/edx/django-pyfs.git@1.0.3#egg=django-pyfs==1.0.3
-git+https://github.com/edx/edx-lint.git@v0.3.2#egg=edx_lint==0.3.2
-git+https://github.com/edx/xblock-sdk.git#egg=xblock-sdk
+git+https://github.com/edx/django-pyfs.git@2.0#egg=django-pyfs==2.0
+git+https://github.com/edx/edx-lint.git@1.4.0#egg=edx_lint==1.4.0
+-e git+https://github.com/edx/xblock-sdk.git#egg=xblock-sdk