diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index c42d039..987d144 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -5,6 +5,10 @@ These are notable changes in edx-platform.  This is a rolling list of changes,
 in roughly chronological order, most recent first.  Add your entries at or near
 the top.  Include a label indicating the component affected.
 
+Studio: Add table for tracking course creator permissions (not yet used).
+Update rake django-admin[syncdb] and rake django-admin[migrate] so they
+run for both LMS and CMS.
+
 Common: Student information is now passed to the tracking log via POST instead of GET.
 
 Common: Add tests for documentation generation to test suite
diff --git a/cms/djangoapps/auth/authz.py b/cms/djangoapps/auth/authz.py
index 23dde88..0f2e60d 100644
--- a/cms/djangoapps/auth/authz.py
+++ b/cms/djangoapps/auth/authz.py
@@ -209,15 +209,27 @@ def is_user_in_creator_group(user):
     return True
 
 
-def _grant_instructors_creator_access(caller):
+def get_users_with_instructor_role():
     """
-    This is to be called only by either a command line code path or through an app which has already
-    asserted permissions to do this action.
+    Returns all users with the role 'instructor'
+    """
+    return _get_users_with_role(INSTRUCTOR_ROLE_NAME)
+
+
+def get_users_with_staff_role():
+    """
+    Returns all users with the role 'staff'
+    """
+    return _get_users_with_role(STAFF_ROLE_NAME)
 
-    Gives all users with instructor role course creator rights.
-    This is only intended to be run once on a given environment.
+
+def _get_users_with_role(role):
+    """
+    Returns all users with the specified role.
     """
+    users = set()
     for group in Group.objects.all():
-        if group.name.startswith(INSTRUCTOR_ROLE_NAME + "_"):
+        if group.name.startswith(role + "_"):
             for user in group.user_set.all():
-                add_user_to_creator_group(caller, user)
+                users.add(user)
+    return users
diff --git a/cms/djangoapps/auth/tests/test_authz.py b/cms/djangoapps/auth/tests/test_authz.py
index 658c176..e04c108 100644
--- a/cms/djangoapps/auth/tests/test_authz.py
+++ b/cms/djangoapps/auth/tests/test_authz.py
@@ -9,7 +9,8 @@ from django.core.exceptions import PermissionDenied
 
 from auth.authz import add_user_to_creator_group, remove_user_from_creator_group, is_user_in_creator_group,\
     create_all_course_groups, add_user_to_course_group, STAFF_ROLE_NAME, INSTRUCTOR_ROLE_NAME,\
-    is_user_in_course_group_role, remove_user_from_course_group, _grant_instructors_creator_access
+    is_user_in_course_group_role, remove_user_from_course_group, get_users_with_staff_role,\
+    get_users_with_instructor_role
 
 
 class CreatorGroupTest(TestCase):
@@ -175,41 +176,27 @@ class CourseGroupTest(TestCase):
         with self.assertRaises(PermissionDenied):
             remove_user_from_course_group(self.staff, self.staff, self.location, STAFF_ROLE_NAME)
 
+    def test_get_staff(self):
+        # Do this test with staff in 2 different classes.
+        create_all_course_groups(self.creator, self.location)
+        add_user_to_course_group(self.creator, self.staff, self.location, STAFF_ROLE_NAME)
 
-class GrantInstructorsCreatorAccessTest(TestCase):
-    """
-    Tests granting existing instructors course creator rights.
-    """
-    def create_course(self, index):
-        """
-        Creates a course with one instructor and one staff member.
-        """
-        creator = User.objects.create_user('testcreator' + str(index), 'testcreator+courses@edx.org', 'foo')
-        staff = User.objects.create_user('teststaff' + str(index), 'teststaff+courses@edx.org', 'foo')
-        location = 'i4x', 'mitX', str(index), 'course', 'test'
-        create_all_course_groups(creator, location)
-        add_user_to_course_group(creator, staff, location, STAFF_ROLE_NAME)
-        return [creator, staff]
-
-    def test_grant_creator_access(self):
-        """
-        Test for _grant_instructors_creator_access.
-        """
-        [creator1, staff1] = self.create_course(1)
-        [creator2, staff2] = self.create_course(2)
-        with mock.patch.dict('django.conf.settings.MITX_FEATURES', {"ENABLE_CREATOR_GROUP": True}):
-            # Initially no creators.
-            self.assertFalse(is_user_in_creator_group(creator1))
-            self.assertFalse(is_user_in_creator_group(creator2))
-            self.assertFalse(is_user_in_creator_group(staff1))
-            self.assertFalse(is_user_in_creator_group(staff2))
-
-            admin = User.objects.create_user('populate_creators_command', 'grant+creator+access@edx.org', 'foo')
-            admin.is_staff = True
-            _grant_instructors_creator_access(admin)
-
-            # Now instructors only are creators.
-            self.assertTrue(is_user_in_creator_group(creator1))
-            self.assertTrue(is_user_in_creator_group(creator2))
-            self.assertFalse(is_user_in_creator_group(staff1))
-            self.assertFalse(is_user_in_creator_group(staff2))
+        location2 = 'i4x', 'mitX', '103', 'course2', 'test2'
+        staff2 = User.objects.create_user('teststaff2', 'teststaff2+courses@edx.org', 'foo')
+        create_all_course_groups(self.creator, location2)
+        add_user_to_course_group(self.creator, staff2, location2, STAFF_ROLE_NAME)
+
+        self.assertSetEqual({self.staff, staff2, self.creator}, get_users_with_staff_role())
+
+    def test_get_instructor(self):
+        # Do this test with creators in 2 different classes.
+        create_all_course_groups(self.creator, self.location)
+        add_user_to_course_group(self.creator, self.staff, self.location, STAFF_ROLE_NAME)
+
+        location2 = 'i4x', 'mitX', '103', 'course2', 'test2'
+        creator2 = User.objects.create_user('testcreator2', 'testcreator2+courses@edx.org', 'foo')
+        staff2 = User.objects.create_user('teststaff2', 'teststaff2+courses@edx.org', 'foo')
+        create_all_course_groups(creator2, location2)
+        add_user_to_course_group(creator2, staff2, location2, STAFF_ROLE_NAME)
+
+        self.assertSetEqual({self.creator, creator2}, get_users_with_instructor_role())
diff --git a/cms/djangoapps/contentstore/management/commands/populate_creators.py b/cms/djangoapps/contentstore/management/commands/populate_creators.py
index f627df8..90d8b3c 100644
--- a/cms/djangoapps/contentstore/management/commands/populate_creators.py
+++ b/cms/djangoapps/contentstore/management/commands/populate_creators.py
@@ -3,7 +3,8 @@ Script for granting existing course instructors course creator privileges.
 
 This script is only intended to be run once on a given environment.
 """
-from auth.authz import _grant_instructors_creator_access
+from auth.authz import get_users_with_instructor_role, get_users_with_staff_role
+from course_creators.views import add_user_with_status_granted, add_user_with_status_unrequested
 from django.core.management.base import BaseCommand
 
 from django.contrib.auth.models import User
@@ -31,5 +32,17 @@ class Command(BaseCommand):
             # the admin user will already exist.
             admin = User.objects.get(username=username, email=email)
 
-        _grant_instructors_creator_access(admin)
+        for user in get_users_with_instructor_role():
+            add_user_with_status_granted(admin, user)
+
+        # Some users will be both staff and instructors. Those folks have been
+        # added with status granted above, and add_user_with_status_unrequested
+        # will not try to add them again if they already exist in the course creator database.
+        for user in get_users_with_staff_role():
+            add_user_with_status_unrequested(admin, user)
+
+        # There could be users who are not in either staff or instructor (they've
+        # never actually done anything in Studio). I plan to add those as unrequested
+        # when they first go to their dashboard.
+
         admin.delete()
diff --git a/cms/djangoapps/course_creators/__init__.py b/cms/djangoapps/course_creators/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/cms/djangoapps/course_creators/__init__.py
diff --git a/cms/djangoapps/course_creators/admin.py b/cms/djangoapps/course_creators/admin.py
new file mode 100644
index 0000000..7518946
--- /dev/null
+++ b/cms/djangoapps/course_creators/admin.py
@@ -0,0 +1,63 @@
+"""
+django admin page for the course creators table
+"""
+
+from course_creators.models import CourseCreator, update_creator_state
+from course_creators.views import update_course_creator_group
+
+from django.contrib import admin
+from django.dispatch import receiver
+
+
+def get_email(obj):
+    """ Returns the email address for a user """
+    return obj.user.email
+
+get_email.short_description = 'email'
+
+
+class CourseCreatorAdmin(admin.ModelAdmin):
+    """
+    Admin for the course creator table.
+    """
+
+    # Fields to display on the overview page.
+    list_display = ['user', get_email, 'state', 'state_changed', 'note']
+    readonly_fields = ['user', 'state_changed']
+    # Controls the order on the edit form (without this, read-only fields appear at the end).
+    fieldsets = (
+        (None, {
+            'fields': ['user', 'state', 'state_changed', 'note']
+        }),
+    )
+    # Fields that filtering support
+    list_filter = ['state', 'state_changed']
+    # Fields that search supports.
+    search_fields = ['user__username', 'user__email', 'state', 'note']
+    # Turn off the action bar (we have no bulk actions)
+    actions = None
+
+    def has_add_permission(self, request):
+        return False
+
+    def has_delete_permission(self, request, obj=None):
+        return False
+
+    def has_change_permission(self, request, obj=None):
+        return request.user.is_staff
+
+    def save_model(self, request, obj, form, change):
+        # Store who is making the request.
+        obj.admin = request.user
+        obj.save()
+
+
+admin.site.register(CourseCreator, CourseCreatorAdmin)
+
+
+@receiver(update_creator_state, sender=CourseCreator)
+def update_creator_group_callback(sender, **kwargs):
+    """
+    Callback for when the model's creator status has changed.
+    """
+    update_course_creator_group(kwargs['caller'], kwargs['user'], kwargs['add'])
diff --git a/cms/djangoapps/course_creators/migrations/0001_initial.py b/cms/djangoapps/course_creators/migrations/0001_initial.py
new file mode 100644
index 0000000..ec7fd9a
--- /dev/null
+++ b/cms/djangoapps/course_creators/migrations/0001_initial.py
@@ -0,0 +1,74 @@
+# -*- 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 'CourseCreator'
+        db.create_table('course_creators_coursecreator', (
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], unique=True)),
+            ('state_changed', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
+            ('state', self.gf('django.db.models.fields.CharField')(default='unrequested', max_length=24)),
+            ('note', self.gf('django.db.models.fields.CharField')(max_length=512, blank=True)),
+        ))
+        db.send_create_signal('course_creators', ['CourseCreator'])
+
+
+    def backwards(self, orm):
+        # Deleting model 'CourseCreator'
+        db.delete_table('course_creators_coursecreator')
+
+
+    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'})
+        },
+        'course_creators.coursecreator': {
+            'Meta': {'object_name': 'CourseCreator'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'note': ('django.db.models.fields.CharField', [], {'max_length': '512', 'blank': 'True'}),
+            'state': ('django.db.models.fields.CharField', [], {'default': "'unrequested'", 'max_length': '24'}),
+            'state_changed': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'})
+        }
+    }
+
+    complete_apps = ['course_creators']
\ No newline at end of file
diff --git a/cms/djangoapps/course_creators/migrations/__init__.py b/cms/djangoapps/course_creators/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/cms/djangoapps/course_creators/migrations/__init__.py
diff --git a/cms/djangoapps/course_creators/models.py b/cms/djangoapps/course_creators/models.py
new file mode 100644
index 0000000..607dae4
--- /dev/null
+++ b/cms/djangoapps/course_creators/models.py
@@ -0,0 +1,71 @@
+"""
+Table for storing information about whether or not Studio users have course creation privileges.
+"""
+from django.db import models
+from django.db.models.signals import post_init, post_save
+from django.dispatch import receiver, Signal
+from django.contrib.auth.models import User
+
+from django.utils import timezone
+from django.utils.translation import ugettext as _
+
+# A signal that will be sent when users should be added or removed from the creator group
+update_creator_state = Signal(providing_args=["caller", "user", "add"])
+
+
+class CourseCreator(models.Model):
+    """
+    Creates the database table model.
+    """
+    UNREQUESTED = 'unrequested'
+    PENDING = 'pending'
+    GRANTED = 'granted'
+    DENIED = 'denied'
+
+    # Second value is the "human-readable" version.
+    STATES = (
+        (UNREQUESTED, _(u'unrequested')),
+        (PENDING, _(u'pending')),
+        (GRANTED, _(u'granted')),
+        (DENIED, _(u'denied')),
+    )
+
+    user = models.ForeignKey(User, help_text=_("Studio user"), unique=True)
+    state_changed = models.DateTimeField('state last updated', auto_now_add=True,
+                                         help_text=_("The date when state was last updated"))
+    state = models.CharField(max_length=24, blank=False, choices=STATES, default=UNREQUESTED,
+                             help_text=_("Current course creator state"))
+    note = models.CharField(max_length=512, blank=True, help_text=_("Optional notes about this user (for example, "
+                                                                    "why course creation access was denied)"))
+
+    def __unicode__(self):
+        return u'%str | %str [%str] | %str' % (self.user, self.state, self.state_changed, self.note)
+
+
+@receiver(post_init, sender=CourseCreator)
+def post_init_callback(sender, **kwargs):
+    """
+    Extend to store previous state.
+    """
+    instance = kwargs['instance']
+    instance.orig_state = instance.state
+
+
+@receiver(post_save, sender=CourseCreator)
+def post_save_callback(sender, **kwargs):
+    """
+    Extend to update state_changed time and modify the course creator group in authz.py.
+    """
+    instance = kwargs['instance']
+    # We only wish to modify the state_changed time if the state has been modified. We don't wish to
+    # modify it for changes to the notes field.
+    if instance.state != instance.orig_state:
+        update_creator_state.send(
+            sender=sender,
+            caller=instance.admin,
+            user=instance.user,
+            add=instance.state == CourseCreator.GRANTED
+        )
+        instance.state_changed = timezone.now()
+        instance.orig_state = instance.state
+        instance.save()
diff --git a/cms/djangoapps/course_creators/tests/test_admin.py b/cms/djangoapps/course_creators/tests/test_admin.py
new file mode 100644
index 0000000..6ef4874
--- /dev/null
+++ b/cms/djangoapps/course_creators/tests/test_admin.py
@@ -0,0 +1,80 @@
+"""
+Tests course_creators.admin.py.
+"""
+
+from django.test import TestCase
+from django.contrib.auth.models import User
+from django.contrib.admin.sites import AdminSite
+from django.http import HttpRequest
+import mock
+
+from course_creators.admin import CourseCreatorAdmin
+from course_creators.models import CourseCreator
+from auth.authz import is_user_in_creator_group
+
+
+class CourseCreatorAdminTest(TestCase):
+    """
+    Tests for course creator admin.
+    """
+
+    def setUp(self):
+        """ Test case setup """
+        self.user = User.objects.create_user('test_user', 'test_user+courses@edx.org', 'foo')
+        self.table_entry = CourseCreator(user=self.user)
+        self.table_entry.save()
+
+        self.admin = User.objects.create_user('Mark', 'admin+courses@edx.org', 'foo')
+        self.admin.is_staff = True
+
+        self.request = HttpRequest()
+        self.request.user = self.admin
+
+        self.creator_admin = CourseCreatorAdmin(self.table_entry, AdminSite())
+
+    def test_change_status(self):
+        """
+        Tests that updates to state impact the creator group maintained in authz.py.
+        """
+        def change_state(state, is_creator):
+            """ Helper method for changing state """
+            self.table_entry.state = state
+            self.creator_admin.save_model(self.request, self.table_entry, None, True)
+            self.assertEqual(is_creator, is_user_in_creator_group(self.user))
+
+        with mock.patch.dict('django.conf.settings.MITX_FEATURES', {"ENABLE_CREATOR_GROUP": True}):
+            # User is initially unrequested.
+            self.assertFalse(is_user_in_creator_group(self.user))
+
+            change_state(CourseCreator.GRANTED, True)
+
+            change_state(CourseCreator.DENIED, False)
+
+            change_state(CourseCreator.GRANTED, True)
+
+            change_state(CourseCreator.PENDING, False)
+
+            change_state(CourseCreator.GRANTED, True)
+
+            change_state(CourseCreator.UNREQUESTED, False)
+
+    def test_add_permission(self):
+        """
+        Tests that staff cannot add entries
+        """
+        self.assertFalse(self.creator_admin.has_add_permission(self.request))
+
+    def test_delete_permission(self):
+        """
+        Tests that staff cannot delete entries
+        """
+        self.assertFalse(self.creator_admin.has_delete_permission(self.request))
+
+    def test_change_permission(self):
+        """
+        Tests that only staff can change entries
+        """
+        self.assertTrue(self.creator_admin.has_change_permission(self.request))
+
+        self.request.user = self.user
+        self.assertFalse(self.creator_admin.has_change_permission(self.request))
diff --git a/cms/djangoapps/course_creators/tests/test_views.py b/cms/djangoapps/course_creators/tests/test_views.py
new file mode 100644
index 0000000..bd91208
--- /dev/null
+++ b/cms/djangoapps/course_creators/tests/test_views.py
@@ -0,0 +1,71 @@
+"""
+Tests course_creators.views.py.
+"""
+
+from django.test import TestCase
+from django.contrib.auth.models import User
+from django.core.exceptions import PermissionDenied
+
+from course_creators.views import add_user_with_status_unrequested, add_user_with_status_granted
+from course_creators.views import get_course_creator_status, update_course_creator_group
+from course_creators.models import CourseCreator
+from auth.authz import is_user_in_creator_group
+import mock
+
+
+class CourseCreatorView(TestCase):
+    """
+    Tests for modifying the course creator table.
+    """
+
+    def setUp(self):
+        """ Test case setup """
+        self.user = User.objects.create_user('test_user', 'test_user+courses@edx.org', 'foo')
+        self.admin = User.objects.create_user('Mark', 'admin+courses@edx.org', 'foo')
+        self.admin.is_staff = True
+
+    def test_staff_permission_required(self):
+        """
+        Tests that add methods and course creator group method must be called with staff permissions.
+        """
+        with self.assertRaises(PermissionDenied):
+            add_user_with_status_granted(self.user, self.user)
+
+        with self.assertRaises(PermissionDenied):
+            add_user_with_status_unrequested(self.user, self.user)
+
+        with self.assertRaises(PermissionDenied):
+            update_course_creator_group(self.user, self.user, True)
+
+    def test_table_initially_empty(self):
+        self.assertIsNone(get_course_creator_status(self.user))
+
+    def test_add_unrequested(self):
+        add_user_with_status_unrequested(self.admin, self.user)
+        self.assertEqual('unrequested', get_course_creator_status(self.user))
+
+        # Calling add again will be a no-op (even if state is different).
+        add_user_with_status_granted(self.admin, self.user)
+        self.assertEqual('unrequested', get_course_creator_status(self.user))
+
+    def test_add_granted(self):
+        with mock.patch.dict('django.conf.settings.MITX_FEATURES', {"ENABLE_CREATOR_GROUP": True}):
+            # Calling add_user_with_status_granted impacts is_user_in_course_group_role.
+            self.assertFalse(is_user_in_creator_group(self.user))
+
+            add_user_with_status_granted(self.admin, self.user)
+            self.assertEqual('granted', get_course_creator_status(self.user))
+
+            # Calling add again will be a no-op (even if state is different).
+            add_user_with_status_unrequested(self.admin, self.user)
+            self.assertEqual('granted', get_course_creator_status(self.user))
+
+            self.assertTrue(is_user_in_creator_group(self.user))
+
+    def test_update_creator_group(self):
+        with mock.patch.dict('django.conf.settings.MITX_FEATURES', {"ENABLE_CREATOR_GROUP": True}):
+            self.assertFalse(is_user_in_creator_group(self.user))
+            update_course_creator_group(self.admin, self.user, True)
+            self.assertTrue(is_user_in_creator_group(self.user))
+            update_course_creator_group(self.admin, self.user, False)
+            self.assertFalse(is_user_in_creator_group(self.user))
diff --git a/cms/djangoapps/course_creators/views.py b/cms/djangoapps/course_creators/views.py
new file mode 100644
index 0000000..902406e
--- /dev/null
+++ b/cms/djangoapps/course_creators/views.py
@@ -0,0 +1,76 @@
+"""
+Methods for interacting programmatically with the user creator table.
+"""
+from course_creators.models import CourseCreator
+from django.core.exceptions import PermissionDenied
+
+from auth.authz import add_user_to_creator_group, remove_user_from_creator_group
+
+
+def add_user_with_status_unrequested(caller, user):
+    """
+    Adds a user to the course creator table with status 'unrequested'.
+
+    If the user is already in the table, this method is a no-op
+    (state will not be changed). Caller must have staff permissions.
+    """
+    _add_user(caller, user, CourseCreator.UNREQUESTED)
+
+
+def add_user_with_status_granted(caller, user):
+    """
+    Adds a user to the course creator table with status 'granted'.
+
+    If the user is already in the table, this method is a no-op
+    (state will not be changed). Caller must have staff permissions.
+
+    This method also adds the user to the course creator group maintained by authz.py.
+    """
+    _add_user(caller, user, CourseCreator.GRANTED)
+    update_course_creator_group(caller, user, True)
+
+
+def update_course_creator_group(caller, user, add):
+    """
+    Method for adding and removing users from the creator group.
+
+    Caller must have staff permissions.
+    """
+    if add:
+        add_user_to_creator_group(caller, user)
+    else:
+        remove_user_from_creator_group(caller, user)
+
+
+def get_course_creator_status(user):
+    """
+    Returns the status for a particular user, or None if user is not in the table.
+
+    Possible return values are:
+        'unrequested' = user has not requested course creation rights
+        'pending' = user has requested course creation rights
+        'granted' = user has been granted course creation rights
+        'denied' = user has been denied course creation rights
+        None = user does not exist in the table
+    """
+    user = CourseCreator.objects.filter(user=user)
+    if user.count() == 0:
+        return None
+    else:
+        # User is defined to be unique, can assume a single entry.
+        return user[0].state
+
+
+def _add_user(caller, user, state):
+    """
+    Adds a user to the course creator table with the specified state.
+
+    If the user is already in the table, this method is a no-op
+    (state will not be changed).
+    """
+    if not caller.is_active or not caller.is_authenticated or not caller.is_staff:
+        raise PermissionDenied
+
+    if CourseCreator.objects.filter(user=user).count() == 0:
+        entry = CourseCreator(user=user, state=state)
+        entry.save()
diff --git a/cms/envs/common.py b/cms/envs/common.py
index 87c130a..c8840e9 100644
--- a/cms/envs/common.py
+++ b/cms/envs/common.py
@@ -331,6 +331,7 @@ INSTALLED_APPS = (
     # For CMS
     'contentstore',
     'auth',
+    'course_creators',
     'student',  # misleading name due to sharing with lms
     'course_groups',  # not used in cms (yet), but tests run
 
@@ -345,6 +346,9 @@ INSTALLED_APPS = (
 
     # comment common
     'django_comment_common',
+
+    # for course creator table
+    'django.contrib.admin'
 )
 
 ################# EDX MARKETING SITE ##################################
diff --git a/cms/envs/test.py b/cms/envs/test.py
index 86925ca..6479c93 100644
--- a/cms/envs/test.py
+++ b/cms/envs/test.py
@@ -143,3 +143,6 @@ MITX_FEATURES['ENABLE_SERVICE_STATUS'] = True
 
 # Enabling SQL tracking logs for testing on common/djangoapps/track
 MITX_FEATURES['ENABLE_SQL_TRACKING_LOGS'] = True
+
+# This is to disable a test under the common directory that will not pass when run under CMS
+MITX_FEATURES['DISABLE_PASSWORD_RESET_EMAIL_TEST'] = True
diff --git a/cms/urls.py b/cms/urls.py
index d04c311..1bd9850 100644
--- a/cms/urls.py
+++ b/cms/urls.py
@@ -5,9 +5,9 @@ from django.conf.urls import patterns, include, url
 # pylint: disable=W0611
 from . import one_time_startup
 
-# Uncomment the next two lines to enable the admin:
-# from django.contrib import admin
-# admin.autodiscover()
+# There is a course creators admin table.
+from django.contrib import admin
+admin.autodiscover()
 
 urlpatterns = ('',  # nopep8
     url(r'^$', 'contentstore.views.howitworks', name='homepage'),
@@ -146,6 +146,8 @@ if settings.MITX_FEATURES.get('ENABLE_SERVICE_STATUS'):
         url(r'^status/', include('service_status.urls')),
     )
 
+urlpatterns += (url(r'^admin/', include(admin.site.urls)),)
+
 urlpatterns = patterns(*urlpatterns)
 
 # Custom error pages
diff --git a/common/djangoapps/student/tests/tests.py b/common/djangoapps/student/tests/tests.py
index 844ddb5..3e36016 100644
--- a/common/djangoapps/student/tests/tests.py
+++ b/common/djangoapps/student/tests/tests.py
@@ -9,15 +9,12 @@ import json
 import re
 import unittest
 
-from django import forms
 from django.conf import settings
 from django.test import TestCase
 from django.test.client import RequestFactory
 from django.contrib.auth.models import User
 from django.contrib.auth.hashers import UNUSABLE_PASSWORD
 from django.contrib.auth.tokens import default_token_generator
-from django.template.loader import render_to_string, get_template, TemplateDoesNotExist
-from django.core.urlresolvers import is_valid_path
 from django.utils.http import int_to_base36
 
 
@@ -33,12 +30,6 @@ COURSE_2 = 'edx/full/6.002_Spring_2012'
 
 log = logging.getLogger(__name__)
 
-try:
-    get_template('registration/password_reset_email.html')
-    project_uses_password_reset = True
-except TemplateDoesNotExist:
-    project_uses_password_reset = False
-
 
 class ResetPasswordTests(TestCase):
     """ Tests that clicking reset password sends email, and doesn't activate the user
@@ -75,7 +66,7 @@ class ResetPasswordTests(TestCase):
         self.assertEquals(bad_email_resp.content, json.dumps({'success': False,
                                                               'error': 'Invalid e-mail or user'}))
 
-    @unittest.skipUnless(project_uses_password_reset,
+    @unittest.skipUnless(not settings.MITX_FEATURES.get('DISABLE_PASSWORD_RESET_EMAIL_TEST', False),
                          dedent("""Skipping Test because CMS has not provided necessary templates for password reset.
                                 If LMS tests print this message, that needs to be fixed."""))
     @patch('django.core.mail.send_mail')
diff --git a/rakelib/django.rake b/rakelib/django.rake
index b1adf24..eeb8135 100644
--- a/rakelib/django.rake
+++ b/rakelib/django.rake
@@ -57,18 +57,17 @@ task :resetdb, [:env] do |t, args|
     sh(django_admin(:lms, args.env, 'migrate'))
 end
 
-desc "Update the relational database to the latest migration"
-task :migrate, [:env] do |t, args|
-    args.with_defaults(:env => 'dev')
-    sh(django_admin(:lms, args.env, 'migrate'))
-end
-
 task :runserver => :lms
 
 desc "Run django-admin <action> against the specified system and environment"
 task "django-admin", [:action, :system, :env, :options] do |t, args|
+    # If no system was explicitly set, we want to run both CMS and LMS for migrate and syncdb.
+    no_system_set = !args.system
     args.with_defaults(:env => 'dev', :system => 'lms', :options => '')
     sh(django_admin(args.system, args.env, args.action, args.options))
+    if no_system_set and (args.action == 'migrate' or args.action == 'syncdb')
+      sh(django_admin('cms', args.env, args.action, args.options))
+    end
 end
 
 desc "Set the staff bit for a user"