From 36373bb457fc114bd729cef81444bf8c6442f94c Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Fri, 20 Jul 2012 12:21:35 -0400 Subject: [PATCH 01/14] Added an unenroll link (and confirmation) to the dashboard. Enrolling is now a POST of a form. --- lms/djangoapps/courseware/views.py | 38 +++++++++++++++----- lms/templates/dashboard.html | 50 ++++++++++++++++++++++++++ lms/templates/portal/course_about.html | 28 ++++++++++++++- lms/urls.py | 8 +++-- 4 files changed, 112 insertions(+), 12 deletions(-) diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py index 07303be06a..77ecc990c3 100644 --- a/lms/djangoapps/courseware/views.py +++ b/lms/djangoapps/courseware/views.py @@ -1,4 +1,5 @@ from collections import defaultdict +import json import logging import urllib import itertools @@ -8,7 +9,7 @@ from django.core.context_processors import csrf from django.core.urlresolvers import reverse from django.contrib.auth.models import User from django.contrib.auth.decorators import login_required -from django.http import Http404 +from django.http import Http404, HttpResponse from django.shortcuts import redirect from mitxmako.shortcuts import render_to_response, render_to_string #from django.views.decorators.csrf import ensure_csrf_cookie @@ -271,14 +272,37 @@ def course_about(request, course_id): @login_required -@ensure_csrf_cookie -def enroll(request, course_id): - course = check_course(course_id, course_must_be_open=False) +def change_enrollment(request): + if request.method != "POST": + raise Http404 + + course_id = request.POST.get("course_id", None) + if course_id == None: + return HttpResponse(json.dumps({'success': False, 'error': 'There was an error receiving the course id.'})) + action = request.POST.get("enrollment_action" , "") + user = request.user - enrollment, created = CourseEnrollment.objects.get_or_create(user=user, course_id=course.id) + if action == "enroll": + # Make sure the course exists + # We don't do this check on unenroll, or a bad course id can't be unenrolled from + course = check_course(course_id, course_must_be_open=False) + + enrollment, created = CourseEnrollment.objects.get_or_create(user=user, course_id=course.id) + return HttpResponse(json.dumps({'success': True})) + + elif action == "unenroll": + try: + enrollment = CourseEnrollment.objects.get(user=user, course_id=course_id) + enrollment.delete() + return HttpResponse(json.dumps({'success': True})) + except CourseEnrollment.DoesNotExist: + return HttpResponse(json.dumps({'success': False, 'error': 'You are not enrolled for this course.'})) + else: + return HttpResponse(json.dumps({'success': False, 'error': 'Invalid enrollment_action.'})) + + return HttpResponse(json.dumps({'success': False, 'error': 'We weren\'t able to unenroll you. Please try again.'})) - return redirect(reverse('dashboard')) @ensure_csrf_cookie @@ -295,5 +319,3 @@ def university_profile(request, org_id): template_file = "university_profile/{0}.html".format(org_id).lower() return render_to_response(template_file, context) - - diff --git a/lms/templates/dashboard.html b/lms/templates/dashboard.html index bdae0dc527..e94701ef60 100644 --- a/lms/templates/dashboard.html +++ b/lms/templates/dashboard.html @@ -51,6 +51,7 @@

Class Starts - 9/2/2012

+ Unenroll
@@ -75,3 +76,52 @@
+ + + + + + + + diff --git a/lms/templates/portal/course_about.html b/lms/templates/portal/course_about.html index 95afcfd73c..d5ba8e24ae 100644 --- a/lms/templates/portal/course_about.html +++ b/lms/templates/portal/course_about.html @@ -22,7 +22,7 @@ %if registered: You are registered for this course (${course.number}). %else: - Register for ${course.number} + Register for ${course.number} %endif %else: Register for ${course.number} @@ -105,4 +105,30 @@ +%if not registered: +
+
+ + +
+ +
+
+
+ +%endif + + <%include file="../video_modal.html" /> diff --git a/lms/urls.py b/lms/urls.py index bce5ad930e..f116ea6037 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -100,14 +100,16 @@ if settings.COURSEWARE_ENABLED: url(r'^save_circuit/(?P[^/]*)$', 'circuit.views.save_circuit'), url(r'^calculate$', 'util.views.calculate'), url(r'^heartbeat$', include('heartbeat.urls')), - + + + url(r'^change_enrollment$', + 'courseware.views.change_enrollment', name="change_enrollment"), + # Multicourse related: url(r'^courses/?$', 'courseware.views.courses', name="courses"), #About the course url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/about$', 'courseware.views.course_about', name="about_course"), - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/enroll$', - 'courseware.views.enroll', name="enroll"), #Inside the course url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/info$', From cdc2ff04f84cbe267a5ec3f79235ab1e08f8d760 Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Fri, 20 Jul 2012 13:14:59 -0400 Subject: [PATCH 02/14] Added a datetime stamp to course enrollments. --- ...ent_date__chg_field_userprofile_country.py | 142 ++++++++++++++++++ common/djangoapps/student/models.py | 2 + 2 files changed, 144 insertions(+) create mode 100644 common/djangoapps/student/migrations/0016_auto__add_field_courseenrollment_date__chg_field_userprofile_country.py diff --git a/common/djangoapps/student/migrations/0016_auto__add_field_courseenrollment_date__chg_field_userprofile_country.py b/common/djangoapps/student/migrations/0016_auto__add_field_courseenrollment_date__chg_field_userprofile_country.py new file mode 100644 index 0000000000..38e25db095 --- /dev/null +++ b/common/djangoapps/student/migrations/0016_auto__add_field_courseenrollment_date__chg_field_userprofile_country.py @@ -0,0 +1,142 @@ +# -*- 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 'CourseEnrollment.date' + db.add_column('student_courseenrollment', 'date', + self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, null=True, blank=True), + keep_default=False) + + + # Changing field 'UserProfile.country' + db.alter_column('auth_userprofile', 'country', self.gf('django_countries.fields.CountryField')(max_length=2, null=True)) + + def backwards(self, orm): + # Deleting field 'CourseEnrollment.date' + db.delete_column('student_courseenrollment', 'date') + + + # Changing field 'UserProfile.country' + db.alter_column('auth_userprofile', 'country', self.gf('django.db.models.fields.CharField')(max_length=255, null=True)) + + 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'}, + 'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'avatar_type': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}), + 'bronze': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'consecutive_days_visit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'blank': 'True'}), + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'display_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}), + 'email_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'gold': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'gravatar': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ignored_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'interesting_tags': ('django.db.models.fields.TextField', [], {'blank': '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'}), + 'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}), + 'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}), + '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'}), + 'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}) + }, + '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'}) + }, + 'student.courseenrollment': { + 'Meta': {'unique_together': "(('user', 'course_id'),)", 'object_name': 'CourseEnrollment'}, + 'course_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'student.pendingemailchange': { + 'Meta': {'object_name': 'PendingEmailChange'}, + 'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'new_email': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'}) + }, + 'student.pendingnamechange': { + 'Meta': {'object_name': 'PendingNameChange'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'new_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'rationale': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'}) + }, + 'student.registration': { + 'Meta': {'object_name': 'Registration', 'db_table': "'auth_registration'"}, + 'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'}) + }, + 'student.userprofile': { + 'Meta': {'object_name': 'UserProfile', 'db_table': "'auth_userprofile'"}, + 'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'null': 'True', 'blank': 'True'}), + 'courseware': ('django.db.models.fields.CharField', [], {'default': "'course.xml'", 'max_length': '255', 'blank': 'True'}), + 'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'gender': ('django.db.models.fields.CharField', [], {'max_length': '6', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'language': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), + 'location': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), + 'mailing_address': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'meta': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), + 'occupation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'telephone_number': ('django.db.models.fields.CharField', [], {'max_length': '25', 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'profile'", 'unique': 'True', 'to': "orm['auth.User']"}) + }, + 'student.usertestgroup': { + 'Meta': {'object_name': 'UserTestGroup'}, + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}), + 'users': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'db_index': 'True', 'symmetrical': 'False'}) + } + } + + complete_apps = ['student'] \ No newline at end of file diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py index f58b5ee754..96836888a7 100644 --- a/common/djangoapps/student/models.py +++ b/common/djangoapps/student/models.py @@ -94,6 +94,8 @@ class CourseEnrollment(models.Model): user = models.ForeignKey(User) course_id = models.CharField(max_length=255, db_index=True) + date = models.DateTimeField(auto_now_add=True, null=True) + class Meta: unique_together = (('user', 'course_id'), ) From 81e7662afc673626db322fbf9f59e23e8c825375 Mon Sep 17 00:00:00 2001 From: Kyle Fiedler Date: Fri, 20 Jul 2012 16:06:13 -0400 Subject: [PATCH 03/14] Added style for the unenroll links --- lms/static/sass/_dashboard.scss | 16 +++++++++++++++- lms/templates/dashboard.html | 4 ++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/lms/static/sass/_dashboard.scss b/lms/static/sass/_dashboard.scss index dbcd170065..d4deeff779 100644 --- a/lms/static/sass/_dashboard.scss +++ b/lms/static/sass/_dashboard.scss @@ -143,7 +143,7 @@ @include clearfix; height: 120px; margin-right: flex-gutter(); - margin-bottom: 25px; + margin-bottom: 10px; overflow: hidden; position: relative; width: flex-grid(12); @@ -379,5 +379,19 @@ } } } + + a.unenroll { + float: right; + font-style: italic; + color: #a0a0a0; + text-decoration: underline; + font-size: .8em; + @include inline-block; + margin-bottom: 40px; + + &:hover { + color: #333; + } + } } } diff --git a/lms/templates/dashboard.html b/lms/templates/dashboard.html index e94701ef60..a90e9a9bf6 100644 --- a/lms/templates/dashboard.html +++ b/lms/templates/dashboard.html @@ -51,7 +51,6 @@

Class Starts - 9/2/2012

- Unenroll
@@ -60,11 +59,12 @@
-

60% compleat

+

60% complete

+ Unenroll % endfor % else: From f337baedb0443f9687b515b15bc3f336b4f7fe2b Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Fri, 20 Jul 2012 16:48:04 -0400 Subject: [PATCH 04/14] Renamed enrollment date to created. --- .../migrations/0017_rename_date_to_created.py | 133 ++++++++++++++++++ common/djangoapps/student/models.py | 2 +- 2 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 common/djangoapps/student/migrations/0017_rename_date_to_created.py diff --git a/common/djangoapps/student/migrations/0017_rename_date_to_created.py b/common/djangoapps/student/migrations/0017_rename_date_to_created.py new file mode 100644 index 0000000000..9b387ed2e9 --- /dev/null +++ b/common/djangoapps/student/migrations/0017_rename_date_to_created.py @@ -0,0 +1,133 @@ +# -*- 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): + # Rename 'date' field to 'created' + db.rename_column('student_courseenrollment', 'date', 'created') + + def backwards(self, orm): + # Rename 'created' field to 'date' + db.rename_column('student_courseenrollment', 'created', 'date') + + + 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'}, + 'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'avatar_type': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}), + 'bronze': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'consecutive_days_visit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'blank': 'True'}), + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'display_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}), + 'email_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'gold': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'gravatar': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ignored_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'interesting_tags': ('django.db.models.fields.TextField', [], {'blank': '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'}), + 'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}), + 'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}), + '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'}), + 'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}) + }, + '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'}) + }, + 'student.courseenrollment': { + 'Meta': {'unique_together': "(('user', 'course_id'),)", 'object_name': 'CourseEnrollment'}, + 'course_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'student.pendingemailchange': { + 'Meta': {'object_name': 'PendingEmailChange'}, + 'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'new_email': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'}) + }, + 'student.pendingnamechange': { + 'Meta': {'object_name': 'PendingNameChange'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'new_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'rationale': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'}) + }, + 'student.registration': { + 'Meta': {'object_name': 'Registration', 'db_table': "'auth_registration'"}, + 'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'}) + }, + 'student.userprofile': { + 'Meta': {'object_name': 'UserProfile', 'db_table': "'auth_userprofile'"}, + 'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'null': 'True', 'blank': 'True'}), + 'courseware': ('django.db.models.fields.CharField', [], {'default': "'course.xml'", 'max_length': '255', 'blank': 'True'}), + 'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'gender': ('django.db.models.fields.CharField', [], {'max_length': '6', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'language': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), + 'location': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), + 'mailing_address': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'meta': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), + 'occupation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'telephone_number': ('django.db.models.fields.CharField', [], {'max_length': '25', 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'profile'", 'unique': 'True', 'to': "orm['auth.User']"}) + }, + 'student.usertestgroup': { + 'Meta': {'object_name': 'UserTestGroup'}, + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}), + 'users': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'db_index': 'True', 'symmetrical': 'False'}) + } + } + + complete_apps = ['student'] \ No newline at end of file diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py index 96836888a7..8b065aeaca 100644 --- a/common/djangoapps/student/models.py +++ b/common/djangoapps/student/models.py @@ -94,7 +94,7 @@ class CourseEnrollment(models.Model): user = models.ForeignKey(User) course_id = models.CharField(max_length=255, db_index=True) - date = models.DateTimeField(auto_now_add=True, null=True) + created = models.DateTimeField(auto_now_add=True, null=True) class Meta: unique_together = (('user', 'course_id'), ) From 764c20eb1b2b53baba9a123d9be95b559c8d98db Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Fri, 20 Jul 2012 16:56:39 -0400 Subject: [PATCH 05/14] Fixed enrollment issue where submission form was automatically sent when it opened on about page. --- lms/templates/portal/course_about.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lms/templates/portal/course_about.html b/lms/templates/portal/course_about.html index d5ba8e24ae..f5804b2604 100644 --- a/lms/templates/portal/course_about.html +++ b/lms/templates/portal/course_about.html @@ -22,7 +22,7 @@ %if registered: You are registered for this course (${course.number}). %else: - Register for ${course.number} + Register for ${course.number} %endif %else: Register for ${course.number} @@ -117,7 +117,7 @@ - - - - - - - - - - - <%block name="headextra"/> - - - - - - - <%block name="header"> -
"> -
- <%block name="header_nav"> - - - - <%block name="header_text"> -
-

MITx

-

MIT’s new online learning initiative

-
- -
-
- - - ${self.body()} - <%block name="bodyextra"/> - - - - % if settings.COURSEWARE_ENABLED: -
<%include file="login.html" />
- % endif -
<%include file="password_reset_form.html" />
-
- - - - <%block name="js_extra"/> - - diff --git a/lms/templates/registration/activate_account_notice.html b/lms/templates/registration/activate_account_notice.html new file mode 100644 index 0000000000..ca051902b1 --- /dev/null +++ b/lms/templates/registration/activate_account_notice.html @@ -0,0 +1,3 @@ +

Thanks For Registering!

+

Your account is not active yet. An activation link has been sent to ${ email }, along with +instructions for activating your account.

diff --git a/lms/templates/registration/reg_complete.html b/lms/templates/registration/reg_complete.html deleted file mode 100644 index aea7e6df1a..0000000000 --- a/lms/templates/registration/reg_complete.html +++ /dev/null @@ -1,7 +0,0 @@ -
-

Thanks For Registering!

-
-
- -

Please check your email. An activation link has been sent to ${ email }, along with -instructions for activating your account.

diff --git a/lms/templates/signup_modal.html b/lms/templates/signup_modal.html index ee59f40396..acbba87eab 100644 --- a/lms/templates/signup_modal.html +++ b/lms/templates/signup_modal.html @@ -121,7 +121,7 @@ (function() { $(document).delegate('#enroll_form', 'ajax:success', function(data, json, xhr) { if(json.success) { - $('#enroll').html(json.value); + location.href="${reverse('dashboard')}"; } else { $('#enroll_error').html(json.value).stop().css("display", "block"); } From 2f4aba8042c0f54309fe8a99f19f57ff9b6508ba Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Sat, 21 Jul 2012 17:52:28 -0400 Subject: [PATCH 08/14] If a user is anonymous when they click the enroll button, they will automatically be enrolled in the course if they immediately log in our create an account. --- cms/templates/signup.html | 14 ++-- common/djangoapps/student/views.py | 65 +++++++++++++++++-- common/djangoapps/util/cache.py | 2 +- lms/djangoapps/courseware/views.py | 33 ---------- .../sass/sass_old/layout/_leanmodal.scss | 2 +- lms/templates/portal/course_about.html | 55 +++++++++++----- lms/templates/signup_modal.html | 12 ++-- lms/urls.py | 2 +- 8 files changed, 115 insertions(+), 70 deletions(-) diff --git a/cms/templates/signup.html b/cms/templates/signup.html index d3eedc8070..f22e3c7950 100644 --- a/cms/templates/signup.html +++ b/cms/templates/signup.html @@ -10,10 +10,10 @@
-
+
-
-
+ +
@@ -64,17 +64,17 @@ }); } - $('form#enroll_form').submit(function(e) { + $('form#register_form').submit(function(e) { e.preventDefault(); - var submit_data = $('#enroll_form').serialize(); + var submit_data = $('#register_form').serialize(); postJSON('/create_account', submit_data, function(json) { if(json.success) { - $('#enroll').html(json.value); + $('#register').html(json.value); } else { - $('#enroll_error').html(json.value).stop().css("background-color", "#933").animate({ backgroundColor: "#333"}, 2000); + $('#register_error').html(json.value).stop().css("background-color", "#933").animate({ backgroundColor: "#333"}, 2000); } } ); diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 003500b48e..1f7ea626a5 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -79,6 +79,9 @@ def index(request): return render_to_response('index.html', {'universities': universities, 'entries': entries}) +def course_from_id(id): + course_loc = CourseDescriptor.id_to_location(id) + return modulestore().get_item(course_loc) @login_required @ensure_csrf_cookie @@ -86,10 +89,6 @@ def dashboard(request): user = request.user enrollments = CourseEnrollment.objects.filter(user=user) - def course_from_id(id): - course_loc = CourseDescriptor.id_to_location(id) - return modulestore().get_item(course_loc) - # Build our courses list for the user, but ignore any courses that no longer # exist (because the course IDs have changed). Still, we don't delete those # enrollments, because it could have been a data push snafu. @@ -110,6 +109,45 @@ def dashboard(request): return render_to_response('dashboard.html', context) +@login_required +def change_enrollment_view(request): + return HttpResponse(json.dumps(change_enrollment(request))) + +def change_enrollment(request): + if request.method != "POST": + raise Http404 + + action = request.POST.get("enrollment_action" , "") + user = request.user + course_id = request.POST.get("course_id", None) + if course_id == None: + return HttpResponse(json.dumps({'success': False, 'error': 'There was an error receiving the course id.'})) + + if action == "enroll": + # Make sure the course exists + # We don't do this check on unenroll, or a bad course id can't be unenrolled from + try: + course = course_from_id(course_id) + except ItemNotFoundError: + log.error("User {0} tried to enroll in non-existant course {1}" + .format(user.username, enrollment.course_id)) + return {'success': False, 'error': 'The course requested does not exist.'} + + enrollment, created = CourseEnrollment.objects.get_or_create(user=user, course_id=course.id) + return {'success': True} + + elif action == "unenroll": + try: + enrollment = CourseEnrollment.objects.get(user=user, course_id=course_id) + enrollment.delete() + return {'success': True} + except CourseEnrollment.DoesNotExist: + return {'success': False, 'error': 'You are not enrolled for this course.'} + else: + return {'success': False, 'error': 'Invalid enrollment_action.'} + + return {'success': False, 'error': 'We weren\'t able to unenroll you. Please try again.'} + # Need different levels of logging @ensure_csrf_cookie def login_user(request, error=""): @@ -147,6 +185,16 @@ def login_user(request, error=""): log.exception(e) log.info("Login success - {0} ({1})".format(username, email)) + + if 'enrollment_action' in request.POST: + try: + enrollment_output = change_enrollment(request) + # There isn't really a way to display the results to the user, so we just log it + # We expect the enrollment to be a success, and will show up on the dashboard anyway + log.info("Attempted to automatically enroll after login. Results: {0}".format(enrollment_output)) + except Exception, e: + log.error("Exception automatically enrolling after login: {0}".format(str(e))) + return HttpResponse(json.dumps({'success':True})) log.warning("Login failed - Account not active for user {0}".format(username)) @@ -287,6 +335,15 @@ def create_account(request, post_override=None): login(request, login_user) request.session.set_expiry(0) + if 'enrollment_action' in request.POST: + try: + enrollment_output = change_enrollment(request) + # There isn't really a way to display the results to the user, so we just log it + # We expect the enrollment to be a success, and will show up on the dashboard anyway + log.info("Attempted to automatically enroll after login. Results: {0}".format(enrollment_output)) + except Exception, e: + log.error("Exception automatically enrolling after login: {0}".format(str(e))) + js={'success': True} return HttpResponse(json.dumps(js), mimetype="application/json") diff --git a/common/djangoapps/util/cache.py b/common/djangoapps/util/cache.py index e253b5b633..35715946a9 100644 --- a/common/djangoapps/util/cache.py +++ b/common/djangoapps/util/cache.py @@ -38,7 +38,7 @@ def cache_if_anonymous(view_func): @wraps(view_func) def _decorated(request, *args, **kwargs): - if not request.user.is_authenticated(): + if False and not request.user.is_authenticated(): #Use the cache cache_key = "cache_if_anonymous." + request.path response = cache.get(cache_key) diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py index 77ecc990c3..d5f24db9d0 100644 --- a/lms/djangoapps/courseware/views.py +++ b/lms/djangoapps/courseware/views.py @@ -269,39 +269,6 @@ def course_about(request, course_id): course = check_course(course_id, course_must_be_open=False) registered = registered_for_course(course, request.user) return render_to_response('portal/course_about.html', {'course': course, 'registered': registered}) - - -@login_required -def change_enrollment(request): - if request.method != "POST": - raise Http404 - - course_id = request.POST.get("course_id", None) - if course_id == None: - return HttpResponse(json.dumps({'success': False, 'error': 'There was an error receiving the course id.'})) - action = request.POST.get("enrollment_action" , "") - - user = request.user - - if action == "enroll": - # Make sure the course exists - # We don't do this check on unenroll, or a bad course id can't be unenrolled from - course = check_course(course_id, course_must_be_open=False) - - enrollment, created = CourseEnrollment.objects.get_or_create(user=user, course_id=course.id) - return HttpResponse(json.dumps({'success': True})) - - elif action == "unenroll": - try: - enrollment = CourseEnrollment.objects.get(user=user, course_id=course_id) - enrollment.delete() - return HttpResponse(json.dumps({'success': True})) - except CourseEnrollment.DoesNotExist: - return HttpResponse(json.dumps({'success': False, 'error': 'You are not enrolled for this course.'})) - else: - return HttpResponse(json.dumps({'success': False, 'error': 'Invalid enrollment_action.'})) - - return HttpResponse(json.dumps({'success': False, 'error': 'We weren\'t able to unenroll you. Please try again.'})) diff --git a/lms/static/sass/sass_old/layout/_leanmodal.scss b/lms/static/sass/sass_old/layout/_leanmodal.scss index 81639493ee..0c96e6524a 100644 --- a/lms/static/sass/sass_old/layout/_leanmodal.scss +++ b/lms/static/sass/sass_old/layout/_leanmodal.scss @@ -84,7 +84,7 @@ div.leanModal_box { form { text-align: left; - div#enroll_error, div#login_error, div#pwd_error { + div#register_error, div#login_error, div#pwd_error { $error-color: #333; background-color: $error-color; border: darken($error-color, 20%); diff --git a/lms/templates/portal/course_about.html b/lms/templates/portal/course_about.html index a6d0106138..730e3f89cf 100644 --- a/lms/templates/portal/course_about.html +++ b/lms/templates/portal/course_about.html @@ -25,7 +25,7 @@ %if registered: You are registered for this course (${course.number}). %else: - Register for ${course.number} + Register for ${course.number} %endif %else: Register for ${course.number} @@ -108,28 +108,49 @@ %if not registered: -
- - - +
+ +
+ + +
- + $(document).delegate('#class_enroll_form', 'ajax:success', function(data, json, xhr) { + if(json.success) { + location.href="${reverse('dashboard')}"; + } + }); + })(this) + + %else: + ## If the user is not authenticated, clicking the enroll button pops up the register + ## field. We also slip in the registration fields into the login/register fields so + ## the user is automatically registered after logging in / registering + + %endif %endif diff --git a/lms/templates/signup_modal.html b/lms/templates/signup_modal.html index acbba87eab..a707dfdd3a 100644 --- a/lms/templates/signup_modal.html +++ b/lms/templates/signup_modal.html @@ -7,16 +7,16 @@