diff --git a/lms/djangoapps/ccx/tests/test_field_override_performance.py b/lms/djangoapps/ccx/tests/test_field_override_performance.py
index 672c9bc38ce4bb5236b2be51755abfbab952ede4..1e91d7e15f60ad4a926568e60d5ee4660621e240 100644
--- a/lms/djangoapps/ccx/tests/test_field_override_performance.py
+++ b/lms/djangoapps/ccx/tests/test_field_override_performance.py
@@ -235,18 +235,18 @@ class TestFieldOverrideMongoPerformance(FieldOverridePerformanceTestCase):
         #     # of sql queries to default,
         #     # of mongo queries,
         # )
-        ('no_overrides', 1, True, False): (18, 1),
-        ('no_overrides', 2, True, False): (18, 1),
-        ('no_overrides', 3, True, False): (18, 1),
-        ('ccx', 1, True, False): (18, 1),
-        ('ccx', 2, True, False): (18, 1),
-        ('ccx', 3, True, False): (18, 1),
-        ('no_overrides', 1, False, False): (18, 1),
-        ('no_overrides', 2, False, False): (18, 1),
-        ('no_overrides', 3, False, False): (18, 1),
-        ('ccx', 1, False, False): (18, 1),
-        ('ccx', 2, False, False): (18, 1),
-        ('ccx', 3, False, False): (18, 1),
+        ('no_overrides', 1, True, False): (20, 1),
+        ('no_overrides', 2, True, False): (20, 1),
+        ('no_overrides', 3, True, False): (20, 1),
+        ('ccx', 1, True, False): (20, 1),
+        ('ccx', 2, True, False): (20, 1),
+        ('ccx', 3, True, False): (20, 1),
+        ('no_overrides', 1, False, False): (20, 1),
+        ('no_overrides', 2, False, False): (20, 1),
+        ('no_overrides', 3, False, False): (20, 1),
+        ('ccx', 1, False, False): (20, 1),
+        ('ccx', 2, False, False): (20, 1),
+        ('ccx', 3, False, False): (20, 1),
     }
 
 
@@ -258,19 +258,19 @@ class TestFieldOverrideSplitPerformance(FieldOverridePerformanceTestCase):
     __test__ = True
 
     TEST_DATA = {
-        ('no_overrides', 1, True, False): (18, 3),
-        ('no_overrides', 2, True, False): (18, 3),
-        ('no_overrides', 3, True, False): (18, 3),
-        ('ccx', 1, True, False): (18, 3),
-        ('ccx', 2, True, False): (18, 3),
-        ('ccx', 3, True, False): (18, 3),
-        ('ccx', 1, True, True): (19, 3),
-        ('ccx', 2, True, True): (19, 3),
-        ('ccx', 3, True, True): (19, 3),
-        ('no_overrides', 1, False, False): (18, 3),
-        ('no_overrides', 2, False, False): (18, 3),
-        ('no_overrides', 3, False, False): (18, 3),
-        ('ccx', 1, False, False): (18, 3),
-        ('ccx', 2, False, False): (18, 3),
-        ('ccx', 3, False, False): (18, 3),
+        ('no_overrides', 1, True, False): (20, 3),
+        ('no_overrides', 2, True, False): (20, 3),
+        ('no_overrides', 3, True, False): (20, 3),
+        ('ccx', 1, True, False): (20, 3),
+        ('ccx', 2, True, False): (20, 3),
+        ('ccx', 3, True, False): (20, 3),
+        ('ccx', 1, True, True): (21, 3),
+        ('ccx', 2, True, True): (21, 3),
+        ('ccx', 3, True, True): (21, 3),
+        ('no_overrides', 1, False, False): (20, 3),
+        ('no_overrides', 2, False, False): (20, 3),
+        ('no_overrides', 3, False, False): (20, 3),
+        ('ccx', 1, False, False): (20, 3),
+        ('ccx', 2, False, False): (20, 3),
+        ('ccx', 3, False, False): (20, 3),
     }
diff --git a/lms/djangoapps/courseware/access.py b/lms/djangoapps/courseware/access.py
index 91360ef18047e0fd599e5a2bb8eb6c86e3de86dc..ae822e836b592b947d93979f457d85a636d426ea 100644
--- a/lms/djangoapps/courseware/access.py
+++ b/lms/djangoapps/courseware/access.py
@@ -11,7 +11,7 @@ Note: The access control logic in this file does NOT check for enrollment in
   It is a wrapper around has_access that additionally checks for enrollment.
 """
 import logging
-from datetime import datetime, timedelta
+from datetime import datetime
 
 from django.conf import settings
 from django.contrib.auth.models import AnonymousUser
diff --git a/lms/djangoapps/courseware/tests/test_access.py b/lms/djangoapps/courseware/tests/test_access.py
index 260101cb7402d4cde4f252cd99b9f7fffc6d50c4..9020bcaf5341d1bb6b1a4bfb61ce94d4fc1b047b 100644
--- a/lms/djangoapps/courseware/tests/test_access.py
+++ b/lms/djangoapps/courseware/tests/test_access.py
@@ -836,15 +836,15 @@ class CourseOverviewAccessTestCase(ModuleStoreTestCase):
             user = getattr(self, user_attr_name)
             user = User.objects.get(id=user.id)
 
-        if (user_attr_name == 'user_staff' and
-            action == 'see_exists' and
-            course_attr_name in
-                ['course_default', 'course_not_started']):
+        if user_attr_name == 'user_staff' and action == 'see_exists':
             # checks staff role
             num_queries = 1
-        elif user_attr_name == 'user_normal' and action == 'see_exists' and course_attr_name != 'course_started':
-            # checks staff role and enrollment data
-            num_queries = 2
+        elif user_attr_name == 'user_normal' and action == 'see_exists':
+            if course_attr_name == 'course_started':
+                num_queries = 1
+            else:
+                # checks staff role and enrollment data
+                num_queries = 2
         else:
             num_queries = 0
 
diff --git a/lms/djangoapps/courseware/tests/test_course_info.py b/lms/djangoapps/courseware/tests/test_course_info.py
index 1d15f3b60fc0559c4619c230a32e7f359b16ea50..8b2ead3fa5036fa1ebb81cfb8d47098d70ec130f 100644
--- a/lms/djangoapps/courseware/tests/test_course_info.py
+++ b/lms/djangoapps/courseware/tests/test_course_info.py
@@ -431,7 +431,7 @@ class SelfPacedCourseInfoTestCase(LoginEnrollmentTestCase, SharedModuleStoreTest
         self.assertEqual(resp.status_code, 200)
 
     def test_num_queries_instructor_paced(self):
-        self.fetch_course_info_with_queries(self.instructor_paced_course, 28, 3)
+        self.fetch_course_info_with_queries(self.instructor_paced_course, 29, 3)
 
     def test_num_queries_self_paced(self):
-        self.fetch_course_info_with_queries(self.self_paced_course, 28, 3)
+        self.fetch_course_info_with_queries(self.self_paced_course, 29, 3)
diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py
index be6d9d92ff35eb064160f331b7f725f5a733ec5d..f86a926430aa721c9ce7fae3e61d9a087bac82b0 100644
--- a/lms/djangoapps/courseware/tests/test_views.py
+++ b/lms/djangoapps/courseware/tests/test_views.py
@@ -205,8 +205,8 @@ class IndexQueryTestCase(ModuleStoreTestCase):
     NUM_PROBLEMS = 20
 
     @ddt.data(
-        (ModuleStoreEnum.Type.mongo, 10, 147),
-        (ModuleStoreEnum.Type.split, 4, 147),
+        (ModuleStoreEnum.Type.mongo, 10, 157),
+        (ModuleStoreEnum.Type.split, 4, 153),
     )
     @ddt.unpack
     def test_index_query_counts(self, store_type, expected_mongo_query_count, expected_mysql_query_count):
@@ -1430,8 +1430,8 @@ class ProgressPageTests(ProgressPageBaseTests):
                 self.assertContains(resp, u"Download Your Certificate")
 
     @ddt.data(
-        (True, 38),
-        (False, 37)
+        (True, 40),
+        (False, 39)
     )
     @ddt.unpack
     def test_progress_queries_paced_courses(self, self_paced, query_count):
@@ -1442,8 +1442,8 @@ class ProgressPageTests(ProgressPageBaseTests):
 
     @patch.dict(settings.FEATURES, {'ASSUME_ZERO_GRADE_IF_ABSENT_FOR_ALL_TESTS': False})
     @ddt.data(
-        (False, 45, 28),
-        (True, 37, 24)
+        (False, 47, 30),
+        (True, 39, 26)
     )
     @ddt.unpack
     def test_progress_queries(self, enable_waffle, initial, subsequent):
diff --git a/lms/djangoapps/discussion/tests/test_views.py b/lms/djangoapps/discussion/tests/test_views.py
index 375fb798000b574ee3ec920e17c816a5c3c8ce63..b6a4c2be3362f1408a913fd8b66925ff16207613 100644
--- a/lms/djangoapps/discussion/tests/test_views.py
+++ b/lms/djangoapps/discussion/tests/test_views.py
@@ -430,18 +430,18 @@ class SingleThreadQueryCountTestCase(ForumsEnableMixin, ModuleStoreTestCase):
         # course is outside the context manager that is verifying the number of queries,
         # and with split mongo, that method ends up querying disabled_xblocks (which is then
         # cached and hence not queried as part of call_single_thread).
-        (ModuleStoreEnum.Type.mongo, False, 1, 5, 2, 17, 5),
-        (ModuleStoreEnum.Type.mongo, False, 50, 5, 2, 17, 5),
+        (ModuleStoreEnum.Type.mongo, False, 1, 5, 2, 19, 7),
+        (ModuleStoreEnum.Type.mongo, False, 50, 5, 2, 19, 7),
         # split mongo: 3 queries, regardless of thread response size.
-        (ModuleStoreEnum.Type.split, False, 1, 3, 3, 17, 5),
-        (ModuleStoreEnum.Type.split, False, 50, 3, 3, 17, 5),
+        (ModuleStoreEnum.Type.split, False, 1, 3, 3, 19, 7),
+        (ModuleStoreEnum.Type.split, False, 50, 3, 3, 19, 7),
 
         # Enabling Enterprise integration should have no effect on the number of mongo queries made.
-        (ModuleStoreEnum.Type.mongo, True, 1, 5, 2, 17, 5),
-        (ModuleStoreEnum.Type.mongo, True, 50, 5, 2, 17, 5),
+        (ModuleStoreEnum.Type.mongo, True, 1, 5, 2, 19, 7),
+        (ModuleStoreEnum.Type.mongo, True, 50, 5, 2, 19, 7),
         # split mongo: 3 queries, regardless of thread response size.
-        (ModuleStoreEnum.Type.split, True, 1, 3, 3, 17, 5),
-        (ModuleStoreEnum.Type.split, True, 50, 3, 3, 17, 5),
+        (ModuleStoreEnum.Type.split, True, 1, 3, 3, 19, 7),
+        (ModuleStoreEnum.Type.split, True, 50, 3, 3, 19, 7),
     )
     @ddt.unpack
     def test_number_of_mongo_queries(
diff --git a/lms/djangoapps/django_comment_client/base/tests.py b/lms/djangoapps/django_comment_client/base/tests.py
index 07d60509ee1584e3f2ec6001e21b5c12f8c501e1..26ae5a59d954ad754bc289bda99280759da74182 100644
--- a/lms/djangoapps/django_comment_client/base/tests.py
+++ b/lms/djangoapps/django_comment_client/base/tests.py
@@ -403,8 +403,8 @@ class ViewsQueryCountTestCase(
         return inner
 
     @ddt.data(
-        (ModuleStoreEnum.Type.mongo, 3, 4, 35),
-        (ModuleStoreEnum.Type.split, 3, 13, 35),
+        (ModuleStoreEnum.Type.mongo, 3, 4, 37),
+        (ModuleStoreEnum.Type.split, 3, 13, 37),
     )
     @ddt.unpack
     @count_queries
@@ -412,8 +412,8 @@ class ViewsQueryCountTestCase(
         self.create_thread_helper(mock_request)
 
     @ddt.data(
-        (ModuleStoreEnum.Type.mongo, 3, 3, 31),
-        (ModuleStoreEnum.Type.split, 3, 10, 31),
+        (ModuleStoreEnum.Type.mongo, 3, 3, 33),
+        (ModuleStoreEnum.Type.split, 3, 10, 33),
     )
     @ddt.unpack
     @count_queries
diff --git a/lms/templates/dashboard/_dashboard_course_listing.html b/lms/templates/dashboard/_dashboard_course_listing.html
index 1b16d0ba3392a07eaafb96baa5af648c4af240e7..e1472f3a8fcb4952519c8ab8ddb5cd3641dc0e51 100644
--- a/lms/templates/dashboard/_dashboard_course_listing.html
+++ b/lms/templates/dashboard/_dashboard_course_listing.html
@@ -33,6 +33,8 @@ from util.course import get_link_for_about_page, get_encoded_course_sharing_utm_
   if cert_name_long == "":
     cert_name_long = settings.CERT_NAME_LONG
   billing_email = settings.PAYMENT_SUPPORT_EMAIL
+
+  is_course_expired = getattr(show_courseware_link, 'error_code') == 'audit_expired'
 %>
 
 <%namespace name='static' file='../static_content.html'/>
@@ -65,7 +67,7 @@ from util.course import get_link_for_about_page, get_encoded_course_sharing_utm_
       <h2 class="hd hd-2 sr" id="details-heading-${enrollment.course_id}">${_('Course details')}</h2>
     <div class="wrapper-course-image" aria-hidden="true">
       % if show_courseware_link and not is_unfulfilled_entitlement:
-        % if not is_course_blocked:
+        % if not is_course_blocked and not is_course_expired:
             <a href="${course_target}" data-course-key="${enrollment.course_id}" class="cover" tabindex="-1">
               <img src="${course_overview.image_urls['small']}" class="course-image" alt="${_('{course_number} {course_name} Home Page').format(course_number=course_overview.number, course_name=course_overview.display_name_with_default)}" />
             </a>
@@ -92,7 +94,7 @@ from util.course import get_link_for_about_page, get_encoded_course_sharing_utm_
       <div class="wrapper-course-details">
         <h3 class="course-title" id="course-title-${enrollment.course_id}">
           % if show_courseware_link and not is_unfulfilled_entitlement:
-            % if not is_course_blocked:
+            % if not is_course_blocked and not is_course_expired:
               <a data-course-key="${enrollment.course_id}" href="${course_target}">${course_overview.display_name_with_default}</a>
             % else:
               <a class="disable-look" data-course-key="${enrollment.course_id}">${course_overview.display_name_with_default}</a>
@@ -128,7 +130,14 @@ from util.course import get_link_for_about_page, get_encoded_course_sharing_utm_
           %>
 
             <span class="info-date-block-container">
-                % if is_unfulfilled_entitlement:
+                % if is_course_expired:
+                  <span class="info-date-block" data-course-key="${enrollment.course_id}">
+                    ${show_courseware_link.user_message}
+                    <span class="sr">
+                      &nbsp;${_('for {course_display_name}').format(course_display_name=course_overview.display_name_with_default)}
+                    </span>
+                  </span>
+                % elif is_unfulfilled_entitlement:
                     <span class="info-date-block" aria-live="polite">
                         <span class="icon fa fa-warning" aria-hidden="true"></span>
                         % if not entitlement_expired_at:
@@ -167,7 +176,7 @@ from util.course import get_link_for_about_page, get_encoded_course_sharing_utm_
           <div class="course-actions">
             % if show_courseware_link or is_unfulfilled_entitlement:
               % if course_overview.has_ended():
-                % if not is_course_blocked:
+                % if not is_course_blocked and not is_course_expired:
                   <a href="${course_target}" class="enter-course archived" data-course-key="${enrollment.course_id}">${_('View Archived Course')}<span class="sr">&nbsp;${course_overview.display_name_with_default}</span></a>
                 % else:
                   <a class="enter-course-blocked archived" data-course-key="${enrollment.course_id}">${_('View Archived Course')}<span class="sr">&nbsp;${course_overview.display_name_with_default}</span></a>
@@ -183,7 +192,7 @@ from util.course import get_link_for_about_page, get_encoded_course_sharing_utm_
                       &nbsp;${course_overview.display_name_with_default}
                     </span>
                   </a>
-                % elif not is_course_blocked:
+                % elif not is_course_blocked and not is_course_expired:
                   <a href="${course_target}"
                      class="enter-course ${'hidden' if is_unfulfilled_entitlement else ''}"
                      data-course-key="${enrollment.course_id}">
@@ -202,14 +211,6 @@ from util.course import get_link_for_about_page, get_encoded_course_sharing_utm_
                   </a>
                 % endif
               % endif
-            % elif hasattr(show_courseware_link, 'user_message'):
-              <span class="enter-course-blocked"
-                 data-course-key="${enrollment.course_id}">
-                ${show_courseware_link.user_message}
-                <span class="sr">
-                  &nbsp;${_('for {course_display_name}').format(course_display_name=course_overview.display_name_with_default)}
-                </span>
-              </span>
             % endif
 
             % if show_courseware_link or course_overview.has_social_sharing_url() or course_overview.has_marketing_url():
diff --git a/openedx/features/course_duration_limits/access.py b/openedx/features/course_duration_limits/access.py
index 866da292a958a1f23ff726a0dc585fc3aedf633b..148d1b7abb783679c7b414eef255aa87afe94b32 100644
--- a/openedx/features/course_duration_limits/access.py
+++ b/openedx/features/course_duration_limits/access.py
@@ -1,3 +1,8 @@
+# -*- coding: utf-8 -*-
+"""
+Contains code related to computing content gating course duration limits
+and course access based on these limits.
+"""
 from datetime import timedelta
 
 from django.apps import apps
@@ -16,11 +21,15 @@ class AuditExpiredError(AccessError):
     def __init__(self, user, course, end_date):
         error_code = "audit_expired"
         developer_message = "User {} had access to {} until {}".format(user, course, end_date)
-        user_message = _("Your audit access period has expired.")
+        # TODO: Translate the end_date
+        user_message = _("Course access expired on ") + end_date.strftime("%B %d, %Y")
         super(AuditExpiredError, self).__init__(error_code, developer_message, user_message)
 
 
 def check_course_expired(user, course):
+    """
+    Check if the course expired for the user.
+    """
     # TODO: Only limit audit users
     # TODO: Limit access to instructor paced courses based on end-date, rather than content availability date
     CourseEnrollment = apps.get_model('student.CourseEnrollment')
diff --git a/openedx/features/course_duration_limits/admin.py b/openedx/features/course_duration_limits/admin.py
deleted file mode 100644
index 13be29d96fa090f33674d220cade585cf961ad8f..0000000000000000000000000000000000000000
--- a/openedx/features/course_duration_limits/admin.py
+++ /dev/null
@@ -1,6 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-from django.contrib import admin
-
-# Register your models here.
diff --git a/openedx/features/course_duration_limits/apps.py b/openedx/features/course_duration_limits/apps.py
index a0f1f4b2d7140392d15b0fba2986c02fe8d08445..d88cc87209c9d493977e213c3f7559db42e8de2f 100644
--- a/openedx/features/course_duration_limits/apps.py
+++ b/openedx/features/course_duration_limits/apps.py
@@ -1,3 +1,6 @@
+"""
+Course duration limits application configuration
+"""
 # -*- coding: utf-8 -*-
 from __future__ import unicode_literals
 
diff --git a/openedx/features/course_duration_limits/migrations/__init__.py b/openedx/features/course_duration_limits/migrations/__init__.py
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/openedx/features/course_duration_limits/models.py b/openedx/features/course_duration_limits/models.py
deleted file mode 100644
index 1dfab76043e92738de321f817d6185000e0a9440..0000000000000000000000000000000000000000
--- a/openedx/features/course_duration_limits/models.py
+++ /dev/null
@@ -1,6 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-from django.db import models
-
-# Create your models here.
diff --git a/openedx/features/course_duration_limits/tests.py b/openedx/features/course_duration_limits/tests.py
deleted file mode 100644
index 5982e6bcd29dd7a86f95cb9ce468697474e59287..0000000000000000000000000000000000000000
--- a/openedx/features/course_duration_limits/tests.py
+++ /dev/null
@@ -1,6 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-from django.test import TestCase
-
-# Create your tests here.
diff --git a/openedx/features/course_duration_limits/views.py b/openedx/features/course_duration_limits/views.py
deleted file mode 100644
index e784a0bd2ca964dad77a68f8c0a31f8e9ae27a36..0000000000000000000000000000000000000000
--- a/openedx/features/course_duration_limits/views.py
+++ /dev/null
@@ -1,6 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-from django.shortcuts import render
-
-# Create your views here.
diff --git a/openedx/features/course_experience/tests/views/test_course_home.py b/openedx/features/course_experience/tests/views/test_course_home.py
index ad298ca7d068d44e4c2945fe870272782ce60fc2..de380d2968a59dc8fc45f7282b79a71c17a9943f 100644
--- a/openedx/features/course_experience/tests/views/test_course_home.py
+++ b/openedx/features/course_experience/tests/views/test_course_home.py
@@ -173,7 +173,7 @@ class TestCourseHomePage(CourseHomePageTestCase):
         course_home_url(self.course)
 
         # Fetch the view and verify the query counts
-        with self.assertNumQueries(54, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST):
+        with self.assertNumQueries(66, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST):
             with check_mongo_calls(4):
                 url = course_home_url(self.course)
                 self.client.get(url)
diff --git a/openedx/features/course_experience/tests/views/test_course_updates.py b/openedx/features/course_experience/tests/views/test_course_updates.py
index 5891c4688fa9ee4379d01259934586af336022d6..67c79757e4f69c989c5a8664c0bb0356b3169842 100644
--- a/openedx/features/course_experience/tests/views/test_course_updates.py
+++ b/openedx/features/course_experience/tests/views/test_course_updates.py
@@ -124,7 +124,7 @@ class TestCourseUpdatesPage(SharedModuleStoreTestCase):
         course_updates_url(self.course)
 
         # Fetch the view and verify that the query counts haven't changed
-        with self.assertNumQueries(34, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST):
+        with self.assertNumQueries(38, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST):
             with check_mongo_calls(4):
                 url = course_updates_url(self.course)
                 self.client.get(url)