%if allow_actions:
- +

${_("Add a User to Your Course's Team")}

@@ -147,7 +147,7 @@

${_("Course Team Roles")}

${_("Course team members, or staff, are course co-authors. They have full writing and editing privileges on all course content.")}

-

${_("Admins are course team members who can add and remove other course team members.")}

+

${_("Admins are course team members who can add and remove other course team members.")}

% if user_is_instuctor and len(instructors) == 1: diff --git a/cms/templates/ux/reference/course-create-rerun.html b/cms/templates/ux/reference/course-create-rerun.html new file mode 100644 index 0000000000..89055cd080 --- /dev/null +++ b/cms/templates/ux/reference/course-create-rerun.html @@ -0,0 +1,368 @@ + + +<%inherit file="../../base.html" /> + +<%! from django.utils.translation import ugettext as _ %> +<%! from django.core.urlresolvers import reverse %> + +<%block name="title">[template] ${_("Create a Course Rerun of HarvardX SW12.2x T2_2014")} +<%block name="bodyclass">is-signedin view-course-create view-course-create-rerun + +<%block name="content"> +
+ +
+
+

+ ${_("Create a re-run of a course")} +

+ + + +

+ ${_("You are creating a re-run from:")} + HarvardX SW12.2x T2_2014 + China (Part 2): The Creation and End of a Centralized Empire +

+
+
+ +
+
+
+
+
+
+

+ ${_("Provide identifying information for this re-run of the course. The original course is not affected in any way by a re-run.")} + ${_("Note: Together, the organization, course number, and course run must uniquely identify this new course instance.")} +

+

+
+ + + + +
+ + +
+ +
+ +
+
+ ${_("Required Information to Create a re-run of a course")} + +
    +
  1. + + + + ${_("The public display name for the new course. (This name is often the same as the original course name.)")} + + +
  2. +
  3. + + + + ${_("The name of the organization sponsoring the new course. (This name is often the same as the original organization name.)")} + ${_("Note: No spaces or special characters are allowed.")} + + +
  4. + +
  5. +
    + + + + ${_("The unique number that identifies the new course within the organization. (This number is often the same as the original course number.)")} + ${_("Note: No spaces or special characters are allowed.")} + + +
    + +
    + + + + ${_("The term in which the new course will run. (This value is often different than the original course run value.)")} + ${_("Note: No spaces or special characters are allowed.")} + + +
    +
  6. +
+ + +
+
+ +
+ + +
+ +
+ + + + +
+
+
+ + +
+ +
+
+ ${_("Required Information to Create a re-run of a course")} + +
    +
  1. + + + + ${_("The public display name for the new course. (This name is often the same as the original course name.)")} + + +
  2. +
  3. + + + + ${_("The name of the organization sponsoring the new course. (This name is often the same as the original organization name.)")} + ${_("Note: No spaces or special characters are allowed.")} + + +
  4. + +
  5. +
    + + + + ${_("The unique number that identifies the new course within the organization. (This number is often the same as the original course number.)")} + ${_("Note: No spaces or special characters are allowed.")} + + +
    + +
    + + + + ${_("The term in which the new course will run. (This value is often different than the original course run value.)")} + ${_("Note: No spaces or special characters are allowed.")} + + +
    +
  6. +
+ + + +
+
+ +
+ + +
+
+
+ + + + +
+
+
+ + +
+ +
+
+ ${_("Required Information to Create a re-run of a course")} + +
    +
  1. + + + + ${_("The public display name for the new course. (This name is often the same as the original course name.)")} + + Required field. +
  2. +
  3. + + + + ${_("The name of the organization sponsoring the new course. (This name is often the same as the original organization name.)")} + ${_("Note: No spaces or special characters are allowed.")} + + Please do not use any spaces or special characters in this field. +
  4. + +
  5. +
    + + + + ${_("The unique number that identifies the new course within the organization. (This number is often the same as the original course number.)")} + ${_("Note: No spaces or special characters are allowed.")} + + Please do not use any spaces or special characters in this field. +
    + +
    + + + + ${_("The term in which the new course will run. (This value is often different than the original course run value.)")} + ${_("Note: No spaces or special characters are allowed.")} + + Required field. +
    +
  6. +
+ + + +
+
+ +
+ + +
+
+
+ + + + +
+
+
+
+ +
+
+ ${_("Required Information to Create a re-run of a course")} + +
    +
  1. + + + + ${_("The public display name for the new course. (This name is often the same as the original course name.)")} + + +
  2. +
  3. + + + + ${_("The name of the organization sponsoring the new course. (This name is often the same as the original organization name.)")} + ${_("Note: No spaces or special characters are allowed.")} + + +
  4. + +
  5. +
    + + + + ${_("The unique number that identifies the new course within the organization. (This number is often the same as the original course number.)")} + ${_("Note: No spaces or special characters are allowed.")} + + +
    + +
    + + + + ${_("The term in which the new course will run. (This value is often different than the original course run value.)")} + ${_("Note: No spaces or special characters are allowed.")} + + +
    +
  6. +
+ + +
+
+ +
+ +
+
+
+ +
+ + + +
+
+ +
+
+ diff --git a/cms/urls.py b/cms/urls.py index 46e22e3408..ceea5f039f 100644 --- a/cms/urls.py +++ b/cms/urls.py @@ -74,6 +74,7 @@ urlpatterns += patterns( ), url(r'^course/{}?$'.format(settings.COURSE_KEY_PATTERN), 'course_handler', name='course_handler'), url(r'^course_notifications/{}/(?P\d+)?$'.format(settings.COURSE_KEY_PATTERN), 'course_notifications_handler'), + url(r'^course_rerun/{}$'.format(settings.COURSE_KEY_PATTERN), 'course_rerun_handler', name='course_rerun_handler'), url(r'^container/{}$'.format(settings.USAGE_KEY_PATTERN), 'container_handler'), url(r'^checklists/{}/(?P\d+)?$'.format(settings.COURSE_KEY_PATTERN), 'checklists_handler'), url(r'^orphan/{}$'.format(settings.COURSE_KEY_PATTERN), 'orphan_handler'), diff --git a/common/djangoapps/course_action_state/managers.py b/common/djangoapps/course_action_state/managers.py index 84ba239213..661f417a69 100644 --- a/common/djangoapps/course_action_state/managers.py +++ b/common/djangoapps/course_action_state/managers.py @@ -113,7 +113,7 @@ class CourseRerunUIStateManager(CourseActionUIStateManager): FAILED = "failed" SUCCEEDED = "succeeded" - def initiated(self, source_course_key, destination_course_key, user): + def initiated(self, source_course_key, destination_course_key, user, display_name): """ To be called when a new rerun is initiated for the given course by the given user. """ @@ -123,6 +123,7 @@ class CourseRerunUIStateManager(CourseActionUIStateManager): user=user, allow_not_found=True, source_course_key=source_course_key, + display_name=display_name, ) def succeeded(self, course_key): diff --git a/common/djangoapps/course_action_state/migrations/0002_add_rerun_display_name.py b/common/djangoapps/course_action_state/migrations/0002_add_rerun_display_name.py new file mode 100644 index 0000000000..8710b96bae --- /dev/null +++ b/common/djangoapps/course_action_state/migrations/0002_add_rerun_display_name.py @@ -0,0 +1,76 @@ +# -*- 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 field 'CourseRerunState.display_name' + db.add_column('course_action_state_coursererunstate', 'display_name', + self.gf('django.db.models.fields.CharField')(default='', max_length=255), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'CourseRerunState.display_name' + db.delete_column('course_action_state_coursererunstate', 'display_name') + + + 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_action_state.coursererunstate': { + 'Meta': {'unique_together': "(('course_key', 'action'),)", 'object_name': 'CourseRerunState'}, + 'action': ('django.db.models.fields.CharField', [], {'max_length': '100', 'db_index': 'True'}), + 'course_key': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}), + 'created_time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'created_user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_by_user+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['auth.User']"}), + 'display_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'message': ('django.db.models.fields.CharField', [], {'max_length': '1000'}), + 'should_display': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'source_course_key': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}), + 'state': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'updated_time': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'updated_user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'updated_by_user+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['auth.User']"}) + } + } + + complete_apps = ['course_action_state'] \ No newline at end of file diff --git a/common/djangoapps/course_action_state/models.py b/common/djangoapps/course_action_state/models.py index 83e6231ae2..a6a4c23de2 100644 --- a/common/djangoapps/course_action_state/models.py +++ b/common/djangoapps/course_action_state/models.py @@ -109,6 +109,9 @@ class CourseRerunState(CourseActionUIState): # Original course that is being rerun source_course_key = CourseKeyField(max_length=255, db_index=True) + # Display name for destination course + display_name = models.CharField(max_length=255, default="") + # MANAGERS # Override the abstract class' manager with a Rerun-specific manager that inherits from the base class' manager. objects = CourseRerunUIStateManager() diff --git a/common/djangoapps/course_action_state/tests/test_rerun_manager.py b/common/djangoapps/course_action_state/tests/test_rerun_manager.py index 92dfb7dcc0..e94a6a1cb2 100644 --- a/common/djangoapps/course_action_state/tests/test_rerun_manager.py +++ b/common/djangoapps/course_action_state/tests/test_rerun_manager.py @@ -17,10 +17,13 @@ class TestCourseRerunStateManager(TestCase): self.source_course_key = CourseLocator("source_org", "source_course_num", "source_run") self.course_key = CourseLocator("test_org", "test_course_num", "test_run") self.created_user = UserFactory() + self.display_name = "destination course name" self.expected_rerun_state = { 'created_user': self.created_user, 'updated_user': self.created_user, 'course_key': self.course_key, + 'source_course_key': self.source_course_key, + "display_name": self.display_name, 'action': CourseRerunUIStateManager.ACTION, 'should_display': True, 'message': "", @@ -53,10 +56,16 @@ class TestCourseRerunStateManager(TestCase): }) self.verify_rerun_state() - def test_rerun_initiated(self): + def initiate_rerun(self): CourseRerunState.objects.initiated( - source_course_key=self.source_course_key, destination_course_key=self.course_key, user=self.created_user + source_course_key=self.source_course_key, + destination_course_key=self.course_key, + user=self.created_user, + display_name=self.display_name, ) + + def test_rerun_initiated(self): + self.initiate_rerun() self.expected_rerun_state.update( {'state': CourseRerunUIStateManager.State.IN_PROGRESS} ) @@ -64,9 +73,7 @@ class TestCourseRerunStateManager(TestCase): def test_rerun_succeeded(self): # initiate - CourseRerunState.objects.initiated( - source_course_key=self.source_course_key, destination_course_key=self.course_key, user=self.created_user - ) + self.initiate_rerun() # set state to succeed CourseRerunState.objects.succeeded(course_key=self.course_key) @@ -80,9 +87,7 @@ class TestCourseRerunStateManager(TestCase): def test_rerun_failed(self): # initiate - CourseRerunState.objects.initiated( - source_course_key=self.source_course_key, destination_course_key=self.course_key, user=self.created_user - ) + self.initiate_rerun() # set state to fail exception = Exception("failure in rerunning") diff --git a/common/lib/xmodule/xmodule/modulestore/split_mongo/split.py b/common/lib/xmodule/xmodule/modulestore/split_mongo/split.py index abe9a693f0..f576ea67e0 100644 --- a/common/lib/xmodule/xmodule/modulestore/split_mongo/split.py +++ b/common/lib/xmodule/xmodule/modulestore/split_mongo/split.py @@ -1794,7 +1794,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase): xblock_class = self.mixologist.mix(xblock_class) for field_name, value in fields.iteritems(): - if value: + if value is not None: if isinstance(xblock_class.fields[field_name], Reference): fields[field_name] = value.block_id elif isinstance(xblock_class.fields[field_name], ReferenceList):