diff --git a/common/lib/xmodule/xmodule/capa_base.py b/common/lib/xmodule/xmodule/capa_base.py
index 3b8ce984c229be89690b2041eede6d50b6c8c0b0..3082933fe89119a1992674d19d3617bf5a100114 100644
--- a/common/lib/xmodule/xmodule/capa_base.py
+++ b/common/lib/xmodule/xmodule/capa_base.py
@@ -26,7 +26,6 @@ from xmodule.exceptions import NotFoundError
 from xblock.fields import Scope, String, Boolean, Dict, Integer, Float
 from .fields import Timedelta, Date
 from django.utils.timezone import UTC
-from .util.duedate import get_extended_due_date
 from xmodule.capa_base_constants import RANDOMIZATION, SHOWANSWER
 from django.conf import settings
 
@@ -107,14 +106,6 @@ class CapaFields(object):
         values={"min": 0}, scope=Scope.settings
     )
     due = Date(help=_("Date that this problem is due by"), scope=Scope.settings)
-    extended_due = Date(
-        help=_("Date that this problem is due by for a particular student. This "
-               "can be set by an instructor, and will override the global due "
-               "date if it is set to a date that is later than the global due "
-               "date."),
-        default=None,
-        scope=Scope.user_state,
-    )
     graceperiod = Timedelta(
         help=_("Amount of time after the due date that submissions will be accepted"),
         scope=Scope.settings
@@ -218,7 +209,7 @@ class CapaMixin(CapaFields):
     def __init__(self, *args, **kwargs):
         super(CapaMixin, self).__init__(*args, **kwargs)
 
-        due_date = get_extended_due_date(self)
+        due_date = self.due
 
         if self.graceperiod is not None and due_date:
             self.close_date = due_date + self.graceperiod
diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py
index 03b1f217d56301d9215ddf0241e210c048cb6761..7e3d1fe8ccf9a522fca940b997b36de9faa362b3 100644
--- a/common/lib/xmodule/xmodule/combined_open_ended_module.py
+++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py
@@ -23,7 +23,6 @@ V1_SETTINGS_ATTRIBUTES = [
     "accept_file_upload",
     "skip_spelling_checks",
     "due",
-    "extended_due",
     "graceperiod",
     "weight",
     "min_to_calibrate",
@@ -258,16 +257,6 @@ class CombinedOpenEndedFields(object):
         help=_("Date that this problem is due by"),
         scope=Scope.settings
     )
-    extended_due = Date(
-        help=_(
-            "Date that this problem is due by for a particular student. This "
-            "can be set by an instructor, and will override the global due "
-            "date if it is set to a date that is later than the global due "
-            "date."
-        ),
-        default=None,
-        scope=Scope.user_state,
-    )
     graceperiod = Timedelta(
         help=_("Amount of time after the due date that submissions will be accepted"),
         scope=Scope.settings
diff --git a/common/lib/xmodule/xmodule/foldit_module.py b/common/lib/xmodule/xmodule/foldit_module.py
index 666d247bfd6e1c6b73df5bb08f5b1dd18fb32507..de5957db94189ff8c2ec2a6564d44f52750ca566 100644
--- a/common/lib/xmodule/xmodule/foldit_module.py
+++ b/common/lib/xmodule/xmodule/foldit_module.py
@@ -8,7 +8,6 @@ from xmodule.x_module import XModule
 from xmodule.xml_module import XmlDescriptor
 from xblock.fields import Scope, Integer, String
 from .fields import Date
-from .util.duedate import get_extended_due_date
 
 
 log = logging.getLogger(__name__)
@@ -21,14 +20,6 @@ class FolditFields(object):
     required_level = Integer(default=4, scope=Scope.settings)
     required_sublevel = Integer(default=5, scope=Scope.settings)
     due = Date(help="Date that this problem is due by", scope=Scope.settings)
-    extended_due = Date(
-        help="Date that this problem is due by for a particular student. This "
-             "can be set by an instructor, and will override the global due "
-             "date if it is set to a date that is later than the global due "
-             "date.",
-        default=None,
-        scope=Scope.user_state,
-    )
 
     show_basic_score = String(scope=Scope.settings, default='false')
     show_leaderboard = String(scope=Scope.settings, default='false')
@@ -49,7 +40,7 @@ class FolditModule(FolditFields, XModule):
             show_leaderboard="false"/>
         """
         super(FolditModule, self).__init__(*args, **kwargs)
-        self.due_time = get_extended_due_date(self)
+        self.due_time = self.due
 
     def is_complete(self):
         """
diff --git a/common/lib/xmodule/xmodule/modulestore/inheritance.py b/common/lib/xmodule/xmodule/modulestore/inheritance.py
index 2727e2fa9caa3b9e1d653adeb54ce2c311cfc096..d6293647c7185fc8744d9310b9f2576610816547 100644
--- a/common/lib/xmodule/xmodule/modulestore/inheritance.py
+++ b/common/lib/xmodule/xmodule/modulestore/inheritance.py
@@ -44,14 +44,6 @@ class InheritanceMixin(XBlockMixin):
         help=_("Enter the default date by which problems are due."),
         scope=Scope.settings,
     )
-    extended_due = Date(
-        help="Date that this problem is due by for a particular student. This "
-             "can be set by an instructor, and will override the global due "
-             "date if it is set to a date that is later than the global due "
-             "date.",
-        default=None,
-        scope=Scope.user_state,
-    )
     visible_to_staff_only = Boolean(
         help=_("If true, can be seen only by course staff, regardless of start date."),
         default=False,
diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py
index 8e49027203f6fa629f80d53ca6591c795a74e4f5..d99884f392e1afc43528c06066727bbf0aeece38 100644
--- a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py
+++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py
@@ -8,7 +8,6 @@ from xmodule.progress import Progress
 from xmodule.stringify import stringify_children
 from xmodule.open_ended_grading_classes import self_assessment_module
 from xmodule.open_ended_grading_classes import open_ended_module
-from xmodule.util.duedate import get_extended_due_date
 from .combined_open_ended_rubric import CombinedOpenEndedRubric, GRADER_TYPE_IMAGE_DICT, HUMAN_GRADER_TYPE, LEGEND_LIST
 from xmodule.open_ended_grading_classes.peer_grading_service import PeerGradingService, MockPeerGradingService
 from xmodule.open_ended_grading_classes.openendedchild import OpenEndedChild
@@ -150,7 +149,7 @@ class CombinedOpenEndedV1Module(object):
             'peer_grade_finished_submissions_when_none_pending', False
         )
 
-        due_date = get_extended_due_date(instance_state)
+        due_date = instance_state.get('due', None)
         grace_period_string = instance_state.get('graceperiod', None)
         try:
             self.timeinfo = TimeInfo(due_date, grace_period_string)
diff --git a/common/lib/xmodule/xmodule/peer_grading_module.py b/common/lib/xmodule/xmodule/peer_grading_module.py
index 086580f06e06273b405d4a4bac5c60efa6ebc917..6e0da07a8fdb137297bcccacaf2b475d77efb5c3 100644
--- a/common/lib/xmodule/xmodule/peer_grading_module.py
+++ b/common/lib/xmodule/xmodule/peer_grading_module.py
@@ -11,7 +11,6 @@ from xmodule.fields import Date, Timedelta
 from xmodule.modulestore.exceptions import ItemNotFoundError, NoPathToItem
 from xmodule.raw_module import RawDescriptor
 from xmodule.timeinfo import TimeInfo
-from xmodule.util.duedate import get_extended_due_date
 from xmodule.x_module import XModule, module_attr
 from xmodule.open_ended_grading_classes.peer_grading_service import PeerGradingService, MockPeerGradingService
 
@@ -52,14 +51,6 @@ class PeerGradingFields(object):
     due = Date(
         help=_("Due date that should be displayed."),
         scope=Scope.settings)
-    extended_due = Date(
-        help=_("Date that this problem is due by for a particular student. This "
-               "can be set by an instructor, and will override the global due "
-               "date if it is set to a date that is later than the global due "
-               "date."),
-        default=None,
-        scope=Scope.user_state,
-    )
     graceperiod = Timedelta(
         help=_("Amount of grace to give on the due date."),
         scope=Scope.settings
@@ -141,8 +132,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
                 self.linked_problem = self.system.get_module(linked_descriptors[0])
 
         try:
-            self.timeinfo = TimeInfo(
-                get_extended_due_date(self), self.graceperiod)
+            self.timeinfo = TimeInfo(self.due, self.graceperiod)
         except Exception:
             log.error("Error parsing due date information in location {0}".format(self.location))
             raise
@@ -570,7 +560,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
             except (NoPathToItem, ItemNotFoundError):
                 continue
             if descriptor:
-                problem['due'] = get_extended_due_date(descriptor)
+                problem['due'] = descriptor.due
                 grace_period = descriptor.graceperiod
                 try:
                     problem_timeinfo = TimeInfo(problem['due'], grace_period)
diff --git a/common/lib/xmodule/xmodule/seq_module.py b/common/lib/xmodule/xmodule/seq_module.py
index 1bdc289440ec39973ed3efbd840f2947ade98d9b..137b7dd31a4563d3f7cc7ea96295ac4a742d2878 100644
--- a/common/lib/xmodule/xmodule/seq_module.py
+++ b/common/lib/xmodule/xmodule/seq_module.py
@@ -36,14 +36,6 @@ class SequenceFields(object):
         help=_("Enter the date by which problems are due."),
         scope=Scope.settings,
     )
-    extended_due = Date(
-        help="Date that this problem is due by for a particular student. This "
-             "can be set by an instructor, and will override the global due "
-             "date if it is set to a date that is later than the global due "
-             "date.",
-        default=None,
-        scope=Scope.user_state,
-    )
 
     # Entrance Exam flag -- see cms/contentstore/views/entrance_exam.py for usage
     is_entrance_exam = Boolean(
diff --git a/common/lib/xmodule/xmodule/tests/test_capa_module.py b/common/lib/xmodule/xmodule/tests/test_capa_module.py
index d1309fded4578ee4c0807dba88e423ef8f72ce29..2c666743aeb7c986f9571f5fbf917bf57b478437 100644
--- a/common/lib/xmodule/xmodule/tests/test_capa_module.py
+++ b/common/lib/xmodule/xmodule/tests/test_capa_module.py
@@ -430,13 +430,6 @@ class CapaModuleTest(unittest.TestCase):
                                     due=self.yesterday_str)
         self.assertTrue(module.closed())
 
-    def test_due_date_extension(self):
-
-        module = CapaFactory.create(
-            max_attempts="1", attempts="0", due=self.yesterday_str,
-            extended_due=self.tomorrow_str)
-        self.assertFalse(module.closed())
-
     def test_parse_get_params(self):
 
         # Valid GET param dict
@@ -1742,7 +1735,7 @@ class TestProblemCheckTracking(unittest.TestCase):
         self.maxDiff = None
 
     def test_choice_answer_text(self):
-        factory = self.capa_factory_for_problem_xml("""\
+        xml = """\
             <problem display_name="Multiple Choice Questions">
               <p>What color is the open ocean on a sunny day?</p>
               <optionresponse>
@@ -1767,7 +1760,11 @@ class TestProblemCheckTracking(unittest.TestCase):
                 </checkboxgroup>
               </choiceresponse>
             </problem>
-            """)
+            """
+
+        # Whitespace screws up comparisons
+        xml = ''.join(line.strip() for line in xml.split('\n'))
+        factory = self.capa_factory_for_problem_xml(xml)
         module = factory.create()
 
         answer_input_dict = {
diff --git a/conf/locale/eo/LC_MESSAGES/django.po b/conf/locale/eo/LC_MESSAGES/django.po
index 9114ffed402d243a36753422fa805acb68886067..eb351c00af481264e583001c370258bdf767ca52 100644
--- a/conf/locale/eo/LC_MESSAGES/django.po
+++ b/conf/locale/eo/LC_MESSAGES/django.po
@@ -1554,19 +1554,6 @@ msgstr ""
 msgid "Date that this problem is due by"
 msgstr "Däté thät thïs prößlém ïs düé ßý Ⱡ'σяєм ι#"
 
-#: common/lib/xmodule/xmodule/capa_base.py
-#: common/lib/xmodule/xmodule/combined_open_ended_module.py
-#: common/lib/xmodule/xmodule/peer_grading_module.py
-msgid ""
-"Date that this problem is due by for a particular student. This can be set "
-"by an instructor, and will override the global due date if it is set to a "
-"date that is later than the global due date."
-msgstr ""
-"Däté thät thïs prößlém ïs düé ßý för ä pärtïçülär stüdént. Thïs çän ßé sét "
-"ßý än ïnstrüçtör, änd wïll övérrïdé thé glößäl düé däté ïf ït ïs sét tö ä "
-"däté thät ïs lätér thän thé glößäl düé däté. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, "
-"¢σηѕє¢тєтυя α∂ιριѕι¢ιηg єłιт, ѕє#"
-
 #: common/lib/xmodule/xmodule/capa_base.py
 #: common/lib/xmodule/xmodule/combined_open_ended_module.py
 msgid "Amount of time after the due date that submissions will be accepted"
@@ -11920,6 +11907,19 @@ msgstr "änd çhöösé ýöür stüdént träçk Ⱡ'σяєм #"
 msgid "and proceed to verification"
 msgstr "änd pröçééd tö vérïfïçätïön Ⱡ'σяєм#"
 
+#. Translators: This line appears next a checkbox which users can leave
+#. checked
+#. or uncheck in order
+#. to indicate whether they want to receive emails from the organization
+#. offering the course.
+#: lms/templates/courseware/mktg_course_about.html
+msgid ""
+"I would like to receive email about other {organization_full_name} programs "
+"and offers."
+msgstr ""
+"Ì wöüld lïké tö réçéïvé émäïl äßöüt öthér {organization_full_name} prögräms "
+"änd öfférs. Ⱡ'σяєм ιρѕυм ∂σłσя #"
+
 #: lms/templates/courseware/mktg_course_about.html
 msgid "Enrollment Is Closed"
 msgstr "Énröllmént Ìs Çlöséd Ⱡ'σя#"
diff --git a/lms/djangoapps/courseware/field_overrides.py b/lms/djangoapps/courseware/field_overrides.py
new file mode 100644
index 0000000000000000000000000000000000000000..2901d819538275dc6acf4d058988a891ca6bb7d8
--- /dev/null
+++ b/lms/djangoapps/courseware/field_overrides.py
@@ -0,0 +1,205 @@
+"""
+This module provides a :class:`~xblock.field_data.FieldData` implementation
+which wraps an other `FieldData` object and provides overrides based on the
+user.  The use of providers allows for overrides that are arbitrarily
+extensible.  One provider is found in `courseware.student_field_overrides`
+which allows for fields to be overridden for individual students.  One can
+envision other providers being written that allow for fields to be overridden
+base on membership of a student in a cohort, or similar.  The use of an
+extensible, modular architecture allows for overrides being done in ways not
+envisioned by the authors.
+
+Currently, this module is used in the `module_render` module in this same
+package and is used to wrap the `authored_data` when constructing an
+`LmsFieldData`.  This means overrides will be in effect for all scopes covered
+by `authored_data`, e.g. course content and settings stored in Mongo.
+"""
+import threading
+
+from abc import ABCMeta, abstractmethod
+from contextlib import contextmanager
+from django.conf import settings
+from xblock.field_data import FieldData
+from xmodule.modulestore.django import modulestore
+from xmodule.modulestore.inheritance import InheritanceMixin
+
+
+NOTSET = object()
+
+
+def resolve_dotted(name):
+    """
+    Given the dotted name for a Python object, performs any necessary imports
+    and returns the object.
+    """
+    names = name.split('.')
+    path = names.pop(0)
+    target = __import__(path)
+    while names:
+        segment = names.pop(0)
+        path += '.' + segment
+        try:
+            target = getattr(target, segment)
+        except AttributeError:
+            __import__(path)
+            target = getattr(target, segment)
+    return target
+
+
+class OverrideFieldData(FieldData):
+    """
+    A :class:`~xblock.field_data.FieldData` which wraps another `FieldData`
+    object and allows for fields handled by the wrapped `FieldData` to be
+    overriden by arbitrary providers.
+
+    Providers are configured by use of the Django setting,
+    `FIELD_OVERRIDE_PROVIDERS` which should be a tuple of dotted names of
+    :class:`FieldOverrideProvider` concrete implementations.  Note that order
+    is important for this setting.  Override providers will tried in the order
+    configured in the setting.  The first provider to find an override 'wins'
+    for a particular field lookup.
+    """
+    provider_classes = None
+
+    @classmethod
+    def wrap(cls, user, wrapped):
+        """
+        Will return a :class:`OverrideFieldData` which wraps the field data
+        given in `wrapped` for the given `user`, if override providers are
+        configred.  If no override providers are configured, using the Django
+        setting, `FIELD_OVERRIDE_PROVIDERS`, returns `wrapped`, eliminating
+        any performance impact of this feature if no override providers are
+        configured.
+        """
+        if cls.provider_classes is None:
+            cls.provider_classes = tuple(
+                (resolve_dotted(name) for name in
+                 settings.FIELD_OVERRIDE_PROVIDERS))
+
+        if cls.provider_classes:
+            return cls(user, wrapped)
+
+        return wrapped
+
+    def __init__(self, user, fallback):
+        self.fallback = fallback
+        self.providers = tuple((cls(user) for cls in self.provider_classes))
+
+    def get_override(self, block, name):
+        """
+        Checks for an override for the field identified by `name` in `block`.
+        Returns the overridden value or `NOTSET` if no override is found.
+        """
+        if not overrides_disabled():
+            for provider in self.providers:
+                value = provider.get(block, name, NOTSET)
+                if value is not NOTSET:
+                    return value
+        return NOTSET
+
+    def get(self, block, name):
+        value = self.get_override(block, name)
+        if value is not NOTSET:
+            return value
+        return self.fallback.get(block, name)
+
+    def set(self, block, name, value):
+        self.fallback.set(block, name, value)
+
+    def delete(self, block, name):
+        self.fallback.delete(block, name)
+
+    def has(self, block, name):
+        has = self.get_override(block, name)
+        if has is NOTSET:
+            # If this is an inheritable field and an override is set above,
+            # then we want to return False here, so the field_data uses the
+            # override and not the original value for this block.
+            inheritable = InheritanceMixin.fields.keys()
+            if name in inheritable:
+                for ancestor in _lineage(block):
+                    if self.get_override(ancestor, name) is not NOTSET:
+                        return False
+
+        return has is not NOTSET or self.fallback.has(block, name)
+
+    def set_many(self, block, update_dict):
+        return self.fallback.set_many(block, update_dict)
+
+    def default(self, block, name):
+        # The `default` method is overloaded by the field storage system to
+        # also handle inheritance.
+        if not overrides_disabled():
+            inheritable = InheritanceMixin.fields.keys()
+            if name in inheritable:
+                for ancestor in _lineage(block):
+                    value = self.get_override(ancestor, name)
+                    if value is not NOTSET:
+                        return value
+        return self.fallback.default(block, name)
+
+
+class _OverridesDisabled(threading.local):
+    """
+    A thread local used to manage state of overrides being disabled or not.
+    """
+    disabled = ()
+
+
+_OVERRIDES_DISABLED = _OverridesDisabled()
+
+
+@contextmanager
+def disable_overrides():
+    """
+    A context manager which disables field overrides inside the context of a
+    `with` statement, allowing code to get at the `original` value of a field.
+    """
+    prev = _OVERRIDES_DISABLED.disabled
+    _OVERRIDES_DISABLED.disabled += (True,)
+    yield
+    _OVERRIDES_DISABLED.disabled = prev
+
+
+def overrides_disabled():
+    """
+    Checks to see whether overrides are disabled in the current context.
+    Returns a boolean value.  See `disable_overrides`.
+    """
+    return bool(_OVERRIDES_DISABLED.disabled)
+
+
+class FieldOverrideProvider(object):
+    """
+    Abstract class which defines the interface that a `FieldOverrideProvider`
+    must provide.  In general, providers should derive from this class, but
+    it's not strictly necessary as long as they correctly implement this
+    interface.
+
+    A `FieldOverrideProvider` implementation is only responsible for looking up
+    field overrides. To set overrides, there will be a domain specific API for
+    the concrete override implementation being used.
+    """
+    __metaclass__ = ABCMeta
+
+    def __init__(self, user):
+        self.user = user
+
+    @abstractmethod
+    def get(self, block, name, default):  # pragma no cover
+        """
+        Look for an override value for the field named `name` in `block`.
+        Returns the overridden value or `default` if no override is found.
+        """
+        raise NotImplementedError
+
+
+def _lineage(block):
+    """
+    Returns an iterator over all ancestors of the given block, starting with
+    its immediate parent and ending at the root of the block tree.
+    """
+    location = modulestore().get_parent_location(block.location)
+    while location:
+        yield modulestore().get_item(location)
+        location = modulestore().get_parent_location(location)
diff --git a/lms/djangoapps/courseware/grades.py b/lms/djangoapps/courseware/grades.py
index 3db3cb8f359b516fed97c717e9cb8cb6ea98fba0..1abfe2c092aefd5b8c1d21915202e8b02ce4a129 100644
--- a/lms/djangoapps/courseware/grades.py
+++ b/lms/djangoapps/courseware/grades.py
@@ -20,7 +20,6 @@ from xmodule import graders
 from xmodule.graders import Score
 from xmodule.modulestore.django import modulestore
 from xmodule.modulestore.exceptions import ItemNotFoundError
-from xmodule.util.duedate import get_extended_due_date
 from .models import StudentModule
 from .module_render import get_module_for_descriptor
 from submissions import api as sub_api  # installed from the edx-submissions repository
@@ -373,7 +372,7 @@ def _progress_summary(student, request, course):
                     'scores': scores,
                     'section_total': section_total,
                     'format': module_format,
-                    'due': get_extended_due_date(section_module),
+                    'due': section_module.due,
                     'graded': graded,
                 })
 
diff --git a/lms/djangoapps/courseware/migrations/0011_add_model_StudentFieldOverride.py b/lms/djangoapps/courseware/migrations/0011_add_model_StudentFieldOverride.py
new file mode 100644
index 0000000000000000000000000000000000000000..d8f5caadd6285912c521079f3361d9a09e9359af
--- /dev/null
+++ b/lms/djangoapps/courseware/migrations/0011_add_model_StudentFieldOverride.py
@@ -0,0 +1,145 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        # Adding model 'StudentFieldOverride'
+        db.create_table('courseware_studentfieldoverride', (
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('course_id', self.gf('xmodule_django.models.CourseKeyField')(max_length=255, db_index=True)),
+            ('location', self.gf('xmodule_django.models.LocationKeyField')(max_length=255, db_index=True)),
+            ('student', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])),
+            ('field', self.gf('django.db.models.fields.CharField')(max_length=255)),
+            ('value', self.gf('django.db.models.fields.TextField')(default='null')),
+        ))
+        db.send_create_signal('courseware', ['StudentFieldOverride'])
+
+
+    def backwards(self, orm):
+        # Deleting model 'StudentFieldOverride'
+        db.delete_table('courseware_studentfieldoverride')
+
+
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        'courseware.offlinecomputedgrade': {
+            'Meta': {'unique_together': "(('user', 'course_id'),)", 'object_name': 'OfflineComputedGrade'},
+            'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'gradeset': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+        },
+        'courseware.offlinecomputedgradelog': {
+            'Meta': {'ordering': "['-created']", 'object_name': 'OfflineComputedGradeLog'},
+            'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'nstudents': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'seconds': ('django.db.models.fields.IntegerField', [], {'default': '0'})
+        },
+        'courseware.studentfieldoverride': {
+            'Meta': {'unique_together': "(('course_id', 'location', 'student'),)", 'object_name': 'StudentFieldOverride'},
+            'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}),
+            'field': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'location': ('xmodule_django.models.LocationKeyField', [], {'max_length': '255', 'db_index': 'True'}),
+            'student': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
+            'value': ('django.db.models.fields.TextField', [], {'default': "'null'"})
+        },
+        'courseware.studentmodule': {
+            'Meta': {'unique_together': "(('student', 'module_state_key', 'course_id'),)", 'object_name': 'StudentModule'},
+            'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'done': ('django.db.models.fields.CharField', [], {'default': "'na'", 'max_length': '8', 'db_index': 'True'}),
+            'grade': ('django.db.models.fields.FloatField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'max_grade': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}),
+            'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'module_state_key': ('xmodule_django.models.LocationKeyField', [], {'max_length': '255', 'db_column': "'module_id'", 'db_index': 'True'}),
+            'module_type': ('django.db.models.fields.CharField', [], {'default': "'problem'", 'max_length': '32', 'db_index': 'True'}),
+            'state': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'student': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+        },
+        'courseware.studentmodulehistory': {
+            'Meta': {'object_name': 'StudentModuleHistory'},
+            'created': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+            'grade': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'max_grade': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}),
+            'state': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'student_module': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['courseware.StudentModule']"}),
+            'version': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'null': 'True', 'blank': 'True'})
+        },
+        'courseware.xmodulestudentinfofield': {
+            'Meta': {'unique_together': "(('student', 'field_name'),)", 'object_name': 'XModuleStudentInfoField'},
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'field_name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'db_index': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'student': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
+            'value': ('django.db.models.fields.TextField', [], {'default': "'null'"})
+        },
+        'courseware.xmodulestudentprefsfield': {
+            'Meta': {'unique_together': "(('student', 'module_type', 'field_name'),)", 'object_name': 'XModuleStudentPrefsField'},
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'field_name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'db_index': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'module_type': ('django.db.models.fields.CharField', [], {'max_length': '64', 'db_index': 'True'}),
+            'student': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
+            'value': ('django.db.models.fields.TextField', [], {'default': "'null'"})
+        },
+        'courseware.xmoduleuserstatesummaryfield': {
+            'Meta': {'unique_together': "(('usage_id', 'field_name'),)", 'object_name': 'XModuleUserStateSummaryField'},
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'field_name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'db_index': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'usage_id': ('xmodule_django.models.LocationKeyField', [], {'max_length': '255', 'db_index': 'True'}),
+            'value': ('django.db.models.fields.TextField', [], {'default': "'null'"})
+        }
+    }
+
+    complete_apps = ['courseware']
\ No newline at end of file
diff --git a/lms/djangoapps/courseware/models.py b/lms/djangoapps/courseware/models.py
index f477b10e0435a33806f3ffcd8edb00663ad0aa69..75a7afffa3d9c1859086a29c5c058640649917d7 100644
--- a/lms/djangoapps/courseware/models.py
+++ b/lms/djangoapps/courseware/models.py
@@ -230,3 +230,20 @@ class OfflineComputedGradeLog(models.Model):
 
     def __unicode__(self):
         return "[OCGLog] %s: %s" % (self.course_id.to_deprecated_string(), self.created)  # pylint: disable=no-member
+
+
+class StudentFieldOverride(models.Model):
+    """
+    Holds the value of a specific field overriden for a student.  This is used
+    by the code in the `courseware.student_field_overrides` module to provide
+    overrides of xblock fields on a per user basis.
+    """
+    course_id = CourseKeyField(max_length=255, db_index=True)
+    location = LocationKeyField(max_length=255, db_index=True)
+    student = models.ForeignKey(User, db_index=True)
+
+    class Meta:   # pylint: disable=missing-docstring
+        unique_together = (('course_id', 'location', 'student'),)
+
+    field = models.CharField(max_length=255)
+    value = models.TextField(default='null')
diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py
index 799c71eafc8359427737b436a46a6c1e0edcd9cc..923827e84d58826f72d16f7a064eab9aeefbcefb 100644
--- a/lms/djangoapps/courseware/module_render.py
+++ b/lms/djangoapps/courseware/module_render.py
@@ -54,7 +54,6 @@ from opaque_keys.edx.locations import SlashSeparatedCourseKey
 from xmodule.contentstore.django import contentstore
 from xmodule.modulestore.django import modulestore, ModuleI18nService
 from xmodule.modulestore.exceptions import ItemNotFoundError
-from xmodule.util.duedate import get_extended_due_date
 from xmodule_modifiers import (
     replace_course_urls,
     replace_jump_to_id_urls,
@@ -71,6 +70,8 @@ from util.sandboxing import can_execute_unsafe_code, get_python_lib_zip
 from util import milestones_helpers
 from util.module_utils import yield_dynamic_descriptor_descendents
 
+from .field_overrides import OverrideFieldData
+
 log = logging.getLogger(__name__)
 
 
@@ -170,7 +171,7 @@ def toc_for_course(request, course, active_chapter, active_section, field_data_c
                     sections.append({'display_name': section.display_name_with_default,
                                      'url_name': section.url_name,
                                      'format': section.format if section.format is not None else '',
-                                     'due': get_extended_due_date(section),
+                                     'due': section.due,
                                      'active': active,
                                      'graded': section.graded,
                                      })
@@ -496,11 +497,17 @@ def get_module_system_for_user(user, field_data_cache,
             request_token=request_token
         )
         # rebinds module to a different student.  We'll change system, student_data, and scope_ids
+        authored_data = OverrideFieldData.wrap(
+            real_user, module.descriptor._field_data  # pylint: disable=protected-access
+        )
         module.descriptor.bind_for_student(
             inner_system,
-            LmsFieldData(module.descriptor._field_data, inner_student_data),  # pylint: disable=protected-access
+            LmsFieldData(authored_data, inner_student_data),
             real_user.id,
         )
+        module.descriptor.scope_ids = (
+            module.descriptor.scope_ids._replace(user_id=real_user.id)  # pylint: disable=protected-access
+        )
         module.scope_ids = module.descriptor.scope_ids  # this is needed b/c NamedTuples are immutable
         # now bind the module to the new ModuleSystem instance and vice-versa
         module.runtime = inner_system
@@ -689,7 +696,9 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours
         request_token=request_token
     )
 
-    descriptor.bind_for_student(system, field_data, user.id)  # pylint: disable=protected-access
+    authored_data = OverrideFieldData.wrap(user, descriptor._field_data)  # pylint: disable=protected-access
+    descriptor.bind_for_student(system, LmsFieldData(authored_data, field_data), user.id)
+    descriptor.scope_ids = descriptor.scope_ids._replace(user_id=user.id)  # pylint: disable=protected-access
     return descriptor
 
 
diff --git a/lms/djangoapps/courseware/student_field_overrides.py b/lms/djangoapps/courseware/student_field_overrides.py
new file mode 100644
index 0000000000000000000000000000000000000000..1e22efa6bc6a3e17e815d167c6c199422b544498
--- /dev/null
+++ b/lms/djangoapps/courseware/student_field_overrides.py
@@ -0,0 +1,71 @@
+"""
+API related to providing field overrides for individual students.  This is used
+by the individual due dates feature.
+"""
+import json
+
+from .field_overrides import FieldOverrideProvider
+from .models import StudentFieldOverride
+
+
+class IndividualStudentOverrideProvider(FieldOverrideProvider):
+    """
+    A concrete implementation of
+    :class:`~courseware.field_overrides.FieldOverrideProvider` which allows for
+    overrides to be made on a per user basis.
+    """
+    def get(self, block, name, default):
+        return get_override_for_user(self.user, block, name, default)
+
+
+def get_override_for_user(user, block, name, default=None):
+    """
+    Gets the value of the overridden field for the `user`.  `block` and `name`
+    specify the block and the name of the field.  If the field is not
+    overridden for the given user, returns `default`.
+    """
+    try:
+        override = StudentFieldOverride.objects.get(
+            course_id=block.runtime.course_id,
+            location=block.location,
+            student_id=user.id,
+            field=name
+        )
+        field = block.fields[name]
+        return field.from_json(json.loads(override.value))
+    except StudentFieldOverride.DoesNotExist:
+        pass
+    return default
+
+
+def override_field_for_user(user, block, name, value):
+    """
+    Overrides a field for the `user`.  `block` and `name` specify the block
+    and the name of the field on that block to override.  `value` is the
+    value to set for the given field.
+    """
+    override, _ = StudentFieldOverride.objects.get_or_create(
+        course_id=block.runtime.course_id,
+        location=block.location,
+        student_id=user.id,
+        field=name)
+    field = block.fields[name]
+    override.value = json.dumps(field.to_json(value))
+    override.save()
+
+
+def clear_override_for_user(user, block, name):
+    """
+    Clears a previously set field override for the `user`.  `block` and `name`
+    specify the block and the name of the field on that block to clear.
+    This function is idempotent--if no override is set, nothing action is
+    performed.
+    """
+    try:
+        StudentFieldOverride.objects.get(
+            course_id=block.runtime.course_id,
+            student_id=user.id,
+            location=block.location,
+            field=name).delete()
+    except StudentFieldOverride.DoesNotExist:
+        pass
diff --git a/lms/djangoapps/courseware/tests/animport.py b/lms/djangoapps/courseware/tests/animport.py
new file mode 100644
index 0000000000000000000000000000000000000000..268d0bb205a91bd90404d86a971ce19822284001
--- /dev/null
+++ b/lms/djangoapps/courseware/tests/animport.py
@@ -0,0 +1,5 @@
+"""
+A class which never gets imported except for in
+:meth:`~courseware.tests.test_field_overrides.ResolveDottedTests.test_import_something_that_isnt_already_loaded`.
+"""
+SOMENAME = 'bar'
diff --git a/lms/djangoapps/courseware/tests/test_field_overrides.py b/lms/djangoapps/courseware/tests/test_field_overrides.py
new file mode 100644
index 0000000000000000000000000000000000000000..77ea374f4c71eca28f3d80ecab9381e39da8ddc2
--- /dev/null
+++ b/lms/djangoapps/courseware/tests/test_field_overrides.py
@@ -0,0 +1,121 @@
+"""
+Tests for `field_overrides` module.
+"""
+import unittest
+
+from django.test import TestCase
+from django.test.utils import override_settings
+from xblock.field_data import DictFieldData
+
+from ..field_overrides import (
+    disable_overrides,
+    FieldOverrideProvider,
+    OverrideFieldData,
+    resolve_dotted,
+)
+
+
+TESTUSER = object()
+
+
+@override_settings(FIELD_OVERRIDE_PROVIDERS=(
+    'courseware.tests.test_field_overrides.TestOverrideProvider',))
+class OverrideFieldDataTests(TestCase):
+    """
+    Tests for `OverrideFieldData`.
+    """
+
+    def setUp(self):
+        OverrideFieldData.provider_classes = None
+
+    def tearDown(self):
+        OverrideFieldData.provider_classes = None
+
+    def make_one(self):
+        """
+        Factory method.
+        """
+        return OverrideFieldData.wrap(TESTUSER, DictFieldData({
+            'foo': 'bar',
+            'bees': 'knees',
+        }))
+
+    def test_get(self):
+        data = self.make_one()
+        self.assertEqual(data.get('block', 'foo'), 'fu')
+        self.assertEqual(data.get('block', 'bees'), 'knees')
+        with disable_overrides():
+            self.assertEqual(data.get('block', 'foo'), 'bar')
+
+    def test_set(self):
+        data = self.make_one()
+        data.set('block', 'foo', 'yowza')
+        self.assertEqual(data.get('block', 'foo'), 'fu')
+        with disable_overrides():
+            self.assertEqual(data.get('block', 'foo'), 'yowza')
+
+    def test_delete(self):
+        data = self.make_one()
+        data.delete('block', 'foo')
+        self.assertEqual(data.get('block', 'foo'), 'fu')
+        with disable_overrides():
+            # Since field_data is responsible for attribute access, you'd
+            # expect it to raise AttributeError. In fact, it raises KeyError,
+            # so we check for that.
+            with self.assertRaises(KeyError):
+                data.get('block', 'foo')
+
+    def test_has(self):
+        data = self.make_one()
+        self.assertTrue(data.has('block', 'foo'))
+        self.assertTrue(data.has('block', 'bees'))
+        self.assertTrue(data.has('block', 'oh'))
+        with disable_overrides():
+            self.assertFalse(data.has('block', 'oh'))
+
+    def test_many(self):
+        data = self.make_one()
+        data.set_many('block', {'foo': 'baz', 'ah': 'ic'})
+        self.assertEqual(data.get('block', 'foo'), 'fu')
+        self.assertEqual(data.get('block', 'ah'), 'ic')
+        with disable_overrides():
+            self.assertEqual(data.get('block', 'foo'), 'baz')
+
+    @override_settings(FIELD_OVERRIDE_PROVIDERS=())
+    def test_no_overrides_configured(self):
+        data = self.make_one()
+        self.assertIsInstance(data, DictFieldData)
+
+
+class ResolveDottedTests(unittest.TestCase):
+    """
+    Tests for `resolve_dotted`.
+    """
+
+    def test_bad_sub_import(self):
+        with self.assertRaises(ImportError):
+            resolve_dotted('courseware.tests.test_foo')
+
+    def test_bad_import(self):
+        with self.assertRaises(ImportError):
+            resolve_dotted('nosuchpackage')
+
+    def test_import_something_that_isnt_already_loaded(self):
+        self.assertEqual(
+            resolve_dotted('courseware.tests.animport.SOMENAME'),
+            'bar'
+        )
+
+
+class TestOverrideProvider(FieldOverrideProvider):
+    """
+    A concrete implementation of `FieldOverrideProvider` for testing.
+    """
+    def get(self, block, name, default):
+        assert self.user is TESTUSER
+        assert block == 'block'
+        if name == 'foo':
+            return 'fu'
+        if name == 'oh':
+            return 'man'
+        return default
diff --git a/lms/djangoapps/instructor/tests/test_api.py b/lms/djangoapps/instructor/tests/test_api.py
index 77624cfdd85c852695b41589a9f9188f6ab27cd1..d9a399e0bf5b1cb4344a6caf0660e677ff6f84ed 100644
--- a/lms/djangoapps/instructor/tests/test_api.py
+++ b/lms/djangoapps/instructor/tests/test_api.py
@@ -50,6 +50,9 @@ from xmodule.modulestore import ModuleStoreEnum
 from xmodule.modulestore.django import modulestore
 from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
 from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
+from xmodule.fields import Date
+
+from courseware.models import StudentFieldOverride
 
 import instructor_task.api
 import instructor.views.api
@@ -61,8 +64,8 @@ from instructor_task.api_helper import AlreadyRunningError
 from openedx.core.djangoapps.course_groups.cohorts import set_course_cohort_settings
 
 from .test_tools import msk_from_problem_urlname
-from ..views.tools import get_extended_due
 
+DATE_FIELD = Date()
 EXPECTED_CSV_HEADER = (
     '"code","redeem_code_url","course_id","company_name","created_by","redeemed_by","invoice_id","purchaser",'
     '"customer_reference_number","internal_reference"'
@@ -3114,6 +3117,24 @@ class TestInstructorAPIHelpers(TestCase):
         msk_from_problem_urlname(*args)
 
 
+def get_extended_due(course, unit, user):
+    """
+    Gets the overridden due date for the given user on the given unit.  Returns
+    `None` if there is no override set.
+    """
+    try:
+        override = StudentFieldOverride.objects.get(
+            course_id=course.id,
+            student=user,
+            location=unit.location,
+            field='due'
+        )
+        return DATE_FIELD.from_json(json.loads(override.value))
+    except StudentFieldOverride.DoesNotExist:
+        return None
+
+
+@override_settings(MODULESTORE=TEST_DATA_MOCK_MODULESTORE)
 class TestDueDateExtensions(ModuleStoreTestCase, LoginEnrollmentTestCase):
     """
     Test data dumps for reporting.
diff --git a/lms/djangoapps/instructor/tests/test_tools.py b/lms/djangoapps/instructor/tests/test_tools.py
index 9724eb7bcdf20784b4174b6ba19a399a112ba435..1f6e596b635abce408ef53b99c2dc38c8862351d 100644
--- a/lms/djangoapps/instructor/tests/test_tools.py
+++ b/lms/djangoapps/instructor/tests/test_tools.py
@@ -3,14 +3,15 @@ Tests for views/tools.py.
 """
 
 import datetime
-import functools
 import mock
 import json
 import unittest
 
 from django.utils.timezone import utc
+from django.test.utils import override_settings
 
 from courseware.models import StudentModule
+from courseware.field_overrides import OverrideFieldData
 from student.tests.factories import UserFactory
 from xmodule.fields import Date
 from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
@@ -180,6 +181,10 @@ class TestTitleOrUrl(unittest.TestCase):
         self.assertEquals(tools.title_or_url(unit), 'test:hello')
 
 
+@override_settings(
+    FIELD_OVERRIDE_PROVIDERS=(
+        'courseware.student_field_overrides.IndividualStudentOverrideProvider',),
+)
 class TestSetDueDateExtension(ModuleStoreTestCase):
     """
     Test the set_due_date_extensions function.
@@ -189,53 +194,53 @@ class TestSetDueDateExtension(ModuleStoreTestCase):
         Fixtures.
         """
         super(TestSetDueDateExtension, self).setUp()
+        OverrideFieldData.provider_classes = None
 
-        due = datetime.datetime(2010, 5, 12, 2, 42, tzinfo=utc)
+        self.due = due = datetime.datetime(2010, 5, 12, 2, 42, tzinfo=utc)
         course = CourseFactory.create()
         week1 = ItemFactory.create(due=due, parent=course)
         week2 = ItemFactory.create(due=due, parent=course)
         week3 = ItemFactory.create(parent=course)
-
-        homework = ItemFactory.create(
-            parent=week1,
-            due=due
-        )
+        homework = ItemFactory.create(parent=week1)
+        assignment = ItemFactory.create(parent=homework, due=due)
 
         user = UserFactory.create()
-        StudentModule(
-            state='{}',
-            student_id=user.id,
-            course_id=course.id,
-            module_state_key=week1.location).save()
-        StudentModule(
-            state='{}',
-            student_id=user.id,
-            course_id=course.id,
-            module_state_key=homework.location).save()
 
         self.course = course
         self.week1 = week1
         self.homework = homework
+        self.assignment = assignment
         self.week2 = week2
         self.week3 = week3
         self.user = user
 
-        self.extended_due = functools.partial(
-            tools.get_extended_due, course, student=user)
+        # Apparently the test harness doesn't use LmsFieldStorage, and I'm not
+        # sure if there's a way to poke the test harness to do so.  So, we'll
+        # just inject the override field storage in this brute force manner.
+        for block in (course, week1, week2, week3, homework, assignment):
+            block._field_data = OverrideFieldData.wrap(  # pylint: disable=protected-access
+                user, block._field_data)  # pylint: disable=protected-access
+
+    def tearDown(self):
+        OverrideFieldData.provider_classes = None
+
+    def _clear_field_data_cache(self):
+        """
+        Clear field data cache for xblocks under test. Normally this would be
+        done by virtue of the fact that xblocks are reloaded on subsequent
+        requests.
+        """
+        for block in (self.week1, self.week2, self.week3,
+                      self.homework, self.assignment):
+            block.fields['due']._del_cached_value(block)  # pylint: disable=protected-access
 
     def test_set_due_date_extension(self):
         extended = datetime.datetime(2013, 12, 25, 0, 0, tzinfo=utc)
         tools.set_due_date_extension(self.course, self.week1, self.user, extended)
-        self.assertEqual(self.extended_due(self.week1), extended)
-        self.assertEqual(self.extended_due(self.homework), extended)
-
-    def test_set_due_date_extension_create_studentmodule(self):
-        extended = datetime.datetime(2013, 12, 25, 0, 0, tzinfo=utc)
-        user = UserFactory.create()  # No student modules for this user
-        tools.set_due_date_extension(self.course, self.week1, user, extended)
-        extended_due = functools.partial(tools.get_extended_due, self.course, student=user)
-        self.assertEqual(extended_due(self.week1), extended)
-        self.assertEqual(extended_due(self.homework), extended)
+        self._clear_field_data_cache()
+        self.assertEqual(self.week1.due, extended)
+        self.assertEqual(self.homework.due, extended)
+        self.assertEqual(self.assignment.due, extended)
 
     def test_set_due_date_extension_invalid_date(self):
         extended = datetime.datetime(2009, 1, 1, 0, 0, tzinfo=utc)
@@ -251,8 +256,7 @@ class TestSetDueDateExtension(ModuleStoreTestCase):
         extended = datetime.datetime(2013, 12, 25, 0, 0, tzinfo=utc)
         tools.set_due_date_extension(self.course, self.week1, self.user, extended)
         tools.set_due_date_extension(self.course, self.week1, self.user, None)
-        self.assertEqual(self.extended_due(self.week1), None)
-        self.assertEqual(self.extended_due(self.homework), None)
+        self.assertEqual(self.week1.due, self.due)
 
 
 class TestDataDumps(ModuleStoreTestCase):
@@ -278,51 +282,7 @@ class TestDataDumps(ModuleStoreTestCase):
         )
 
         user1 = UserFactory.create()
-        StudentModule(
-            state='{}',
-            student_id=user1.id,
-            course_id=course.id,
-            module_state_key=week1.location).save()
-        StudentModule(
-            state='{}',
-            student_id=user1.id,
-            course_id=course.id,
-            module_state_key=week2.location).save()
-        StudentModule(
-            state='{}',
-            student_id=user1.id,
-            course_id=course.id,
-            module_state_key=week3.location).save()
-        StudentModule(
-            state='{}',
-            student_id=user1.id,
-            course_id=course.id,
-            module_state_key=homework.location).save()
-
         user2 = UserFactory.create()
-        StudentModule(
-            state='{}',
-            student_id=user2.id,
-            course_id=course.id,
-            module_state_key=week1.location).save()
-        StudentModule(
-            state='{}',
-            student_id=user2.id,
-            course_id=course.id,
-            module_state_key=homework.location).save()
-
-        user3 = UserFactory.create()
-        StudentModule(
-            state='{}',
-            student_id=user3.id,
-            course_id=course.id,
-            module_state_key=week1.location).save()
-        StudentModule(
-            state='{}',
-            student_id=user3.id,
-            course_id=course.id,
-            module_state_key=homework.location).save()
-
         self.course = course
         self.week1 = week1
         self.homework = homework
diff --git a/lms/djangoapps/instructor/views/tools.py b/lms/djangoapps/instructor/views/tools.py
index 3959e2e2fba16315d1f2250319ad179921119e51..d1bbb662693aa9b8342c7d63a0e8e8f423cfeeab 100644
--- a/lms/djangoapps/instructor/views/tools.py
+++ b/lms/djangoapps/instructor/views/tools.py
@@ -10,7 +10,13 @@ from django.http import HttpResponseBadRequest
 from django.utils.timezone import utc
 from django.utils.translation import ugettext as _
 
-from courseware.models import StudentModule
+from courseware.models import StudentFieldOverride
+from courseware.field_overrides import disable_overrides
+from courseware.student_field_overrides import (
+    clear_override_for_user,
+    get_override_for_user,
+    override_field_for_user,
+)
 from xmodule.fields import Date
 from xmodule.modulestore import ModuleStoreEnum
 from xmodule.modulestore.django import modulestore
@@ -175,22 +181,6 @@ def title_or_url(node):
     return title
 
 
-def get_extended_due(course, unit, student):
-    """
-    Get the extended due date out of a student's state for a particular unit.
-    """
-    student_module = StudentModule.objects.get(
-        student_id=student.id,
-        course_id=course.id,
-        module_state_key=unit.location
-    )
-
-    state = json.loads(student_module.state)
-    extended = state.get('extended_due', None)
-    if extended:
-        return DATE_FIELD.from_json(extended)
-
-
 def set_due_date_extension(course, unit, student, due_date):
     """
     Sets a due date extension. Raises DashboardError if the unit or extended
@@ -198,56 +188,22 @@ def set_due_date_extension(course, unit, student, due_date):
     """
     if due_date:
         # Check that the new due date is valid:
-        original_due_date = getattr(unit, 'due', None)
+        with disable_overrides():
+            original_due_date = getattr(unit, 'due', None)
 
         if not original_due_date:
             raise DashboardError(_("Unit {0} has no due date to extend.").format(unit.location))
         if due_date < original_due_date:
             raise DashboardError(_("An extended due date must be later than the original due date."))
+
+        override_field_for_user(student, unit, 'due', due_date)
+
     else:
         # We are deleting a due date extension. Check that it exists:
-        if not get_extended_due(course, unit, student):
+        if not get_override_for_user(student, unit, 'due'):
             raise DashboardError(_("No due date extension is set for that student and unit."))
 
-    def set_due_date(node):
-        """
-        Recursively set the due date on a node and all of its children.
-        """
-        try:
-            student_module = StudentModule.objects.get(
-                student_id=student.id,
-                course_id=course.id,
-                module_state_key=node.location
-            )
-            state = json.loads(student_module.state)
-
-        except StudentModule.DoesNotExist:
-            # Normally, a StudentModule is created as a side effect of assigning
-            # a value to a property in an XModule or XBlock which has a scope
-            # of 'Scope.user_state'.  Here, we want to alter user state but
-            # can't use the standard XModule/XBlock machinery to do so, because
-            # it fails to take into account that the state being altered might
-            # belong to a student other than the one currently logged in.  As a
-            # result, in our work around, we need to detect whether the
-            # StudentModule has been created for the given student on the given
-            # unit and create it if it is missing, so we can use it to store
-            # the extended due date.
-            student_module = StudentModule.objects.create(
-                student_id=student.id,
-                course_id=course.id,
-                module_state_key=node.location,
-                module_type=node.category
-            )
-            state = {}
-
-        state['extended_due'] = DATE_FIELD.to_json(due_date)
-        student_module.state = json.dumps(state)
-        student_module.save()
-
-        for child in node.get_children():
-            set_due_date(child)
-
-    set_due_date(unit)
+        clear_override_for_user(student, unit, 'due')
 
 
 def dump_module_extensions(course, unit):
@@ -257,20 +213,17 @@ def dump_module_extensions(course, unit):
     """
     data = []
     header = [_("Username"), _("Full Name"), _("Extended Due Date")]
-    query = StudentModule.objects.filter(
+    query = StudentFieldOverride.objects.filter(
         course_id=course.id,
-        module_state_key=unit.location)
-    for module in query:
-        state = json.loads(module.state)
-        extended_due = state.get("extended_due")
-        if not extended_due:
-            continue
-        extended_due = DATE_FIELD.from_json(extended_due)
-        extended_due = extended_due.strftime("%Y-%m-%d %H:%M")
-        fullname = module.student.profile.name
+        location=unit.location,
+        field='due')
+    for override in query:
+        due = DATE_FIELD.from_json(json.loads(override.value))
+        due = due.strftime("%Y-%m-%d %H:%M")
+        fullname = override.student.profile.name
         data.append(dict(zip(
             header,
-            (module.student.username, fullname, extended_due))))
+            (override.student.username, fullname, due))))
     data.sort(key=lambda x: x[header[0]])
     return {
         "header": header,
@@ -288,23 +241,19 @@ def dump_student_extensions(course, student):
     data = []
     header = [_("Unit"), _("Extended Due Date")]
     units = get_units_with_due_date(course)
-    units = dict([(u.location, u) for u in units])
-    query = StudentModule.objects.filter(
+    units = {u.location: u for u in units}
+    query = StudentFieldOverride.objects.filter(
         course_id=course.id,
-        student_id=student.id)
-    for module in query:
-        state = json.loads(module.state)
-        # temporary hack: module_state_key is missing the run but units are not. fix module_state_key
-        module_loc = module.module_state_key.map_into_course(module.course_id)
-        if module_loc not in units:
-            continue
-        extended_due = state.get("extended_due")
-        if not extended_due:
+        student=student,
+        field='due')
+    for override in query:
+        location = override.location.replace(course_key=course.id)
+        if location not in units:
             continue
-        extended_due = DATE_FIELD.from_json(extended_due)
-        extended_due = extended_due.strftime("%Y-%m-%d %H:%M")
-        title = title_or_url(units[module_loc])
-        data.append(dict(zip(header, (title, extended_due))))
+        due = DATE_FIELD.from_json(json.loads(override.value))
+        due = due.strftime("%Y-%m-%d %H:%M")
+        title = title_or_url(units[location])
+        data.append(dict(zip(header, (title, due))))
     return {
         "header": header,
         "title": _("Due date extensions for {0} {1} ({2})").format(
diff --git a/lms/envs/aws.py b/lms/envs/aws.py
index a9221edf25a3f096b2e427abf099ae283e584e59..b3892c62a76b8ef7df548b543a008395215cba07 100644
--- a/lms/envs/aws.py
+++ b/lms/envs/aws.py
@@ -343,6 +343,10 @@ if FEATURES.get('ENABLE_CORS_HEADERS') or FEATURES.get('ENABLE_CROSS_DOMAIN_CSRF
     CROSS_DOMAIN_CSRF_COOKIE_DOMAIN = ENV_TOKENS.get('CROSS_DOMAIN_CSRF_COOKIE_DOMAIN')
 
 
+# Field overrides.  To use the IDDE feature, add
+# 'courseware.student_field_overrides.IndividualStudentOverrideProvider'.
+FIELD_OVERRIDE_PROVIDERS = tuple(ENV_TOKENS.get('FIELD_OVERRIDE_PROVIDERS', []))
+
 ############################## SECURE AUTH ITEMS ###############
 # Secret things: passwords, access keys, etc.
 
diff --git a/lms/envs/common.py b/lms/envs/common.py
index 60283e969328d6c125a1adbf7f6da56a9f369b49..080e40baed2300a6a8670ad663945f0e1e12fde4 100644
--- a/lms/envs/common.py
+++ b/lms/envs/common.py
@@ -208,6 +208,10 @@ FEATURES = {
     'ENABLE_INSTRUCTOR_BACKGROUND_TASKS': True,
 
     # Enable instructor to assign individual due dates
+    # Note: In order for this feature to work, you must also add
+    # 'courseware.student_field_overrides.IndividualStudentOverrideProvider' to
+    # the setting FIELD_OVERRIDE_PROVIDERS, in addition to setting this flag to
+    # True.
     'INDIVIDUAL_DUE_DATES': False,
 
     # Enable legacy instructor dashboard
@@ -2225,3 +2229,9 @@ ECOMMERCE_API_TIMEOUT = 5
 
 # Reverification checkpoint name pattern
 CHECKPOINT_PATTERN = r'(?P<checkpoint_name>\w+)'
+
+# For the fields override feature
+# If using FEATURES['INDIVIDUAL_DUE_DATES'], you should add
+# 'courseware.student_field_overrides.IndividualStudentOverrideProvider' to
+# this setting.
+FIELD_OVERRIDE_PROVIDERS = ()