Commit da862056 by Bridger Maxwell

Merge remote-tracking branch 'origin/release/1.0' into uc_berkeley_press

Conflicts:
	lms/urls.py
parents 7ebcf074 361a6791
...@@ -6,11 +6,7 @@ ...@@ -6,11 +6,7 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
% if settings.MITX_FEATURES['USE_DJANGO_PIPELINE']:
<%static:css group='base-style'/> <%static:css group='base-style'/>
% else:
<link rel="stylesheet" href="${static.url('css/base-style.css')}">
% endif
<link rel="stylesheet" type="text/css" href="${static.url('js/vendor/markitup/skins/simple/style.css')}" /> <link rel="stylesheet" type="text/css" href="${static.url('js/vendor/markitup/skins/simple/style.css')}" />
<link rel="stylesheet" type="text/css" href="${static.url('js/vendor/markitup/sets/wiki/style.css')}" /> <link rel="stylesheet" type="text/css" href="${static.url('js/vendor/markitup/sets/wiki/style.css')}" />
<title><%block name="title"></%block></title> <title><%block name="title"></%block></title>
...@@ -27,12 +23,7 @@ ...@@ -27,12 +23,7 @@
<script type="text/javascript" src="${static.url('js/vendor/backbone-min.js')}"></script> <script type="text/javascript" src="${static.url('js/vendor/backbone-min.js')}"></script>
<script type="text/javascript" src="${static.url('js/vendor/markitup/jquery.markitup.js')}"></script> <script type="text/javascript" src="${static.url('js/vendor/markitup/jquery.markitup.js')}"></script>
<script type="text/javascript" src="${static.url('js/vendor/markitup/sets/wiki/set.js')}"></script> <script type="text/javascript" src="${static.url('js/vendor/markitup/sets/wiki/set.js')}"></script>
% if settings.MITX_FEATURES['USE_DJANGO_PIPELINE']:
<%static:js group='main'/> <%static:js group='main'/>
% else:
<script src="${ STATIC_URL }/js/main.js"></script>
% endif
<%static:js group='module-js'/> <%static:js group='module-js'/>
<script src="${static.url('js/vendor/jquery.inlineedit.js')}"></script> <script src="${static.url('js/vendor/jquery.inlineedit.js')}"></script>
<script src="${static.url('js/vendor/jquery.cookie.js')}"></script> <script src="${static.url('js/vendor/jquery.cookie.js')}"></script>
......
...@@ -10,10 +10,10 @@ ...@@ -10,10 +10,10 @@
<hr> <hr>
</header> </header>
<div id="enroll"> <div id="register">
<form id="enroll_form" method="post"> <form id="register_form" method="post">
<div id="enroll_error" name="enroll_error"></div> <div id="register_error" name="register_error"></div>
<label>E-mail</label> <label>E-mail</label>
<input name="email" type="email" placeholder="E-mail"> <input name="email" type="email" placeholder="E-mail">
<label>Password</label> <label>Password</label>
...@@ -64,17 +64,17 @@ ...@@ -64,17 +64,17 @@
}); });
} }
$('form#enroll_form').submit(function(e) { $('form#register_form').submit(function(e) {
e.preventDefault(); e.preventDefault();
var submit_data = $('#enroll_form').serialize(); var submit_data = $('#register_form').serialize();
postJSON('/create_account', postJSON('/create_account',
submit_data, submit_data,
function(json) { function(json) {
if(json.success) { if(json.success) {
$('#enroll').html(json.value); $('#register').html(json.value);
} else { } 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);
} }
} }
); );
......
...@@ -5,6 +5,24 @@ from static_replace import replace_urls ...@@ -5,6 +5,24 @@ from static_replace import replace_urls
%> %>
<%def name='url(file)'>${staticfiles_storage.url(file)}</%def> <%def name='url(file)'>${staticfiles_storage.url(file)}</%def>
<%def name='css(group)'>${compressed_css(group)}</%def>
<%def name='js(group)'>${compressed_js(group)}</%def> <%def name='css(group)'>
% if settings.MITX_FEATURES['USE_DJANGO_PIPELINE']:
${compressed_css(group)}
% else:
% for filename in settings.PIPELINE_CSS[group]['source_filenames']:
<link rel="stylesheet" href="${staticfiles_storage.url(filename.replace('.scss', '.css'))}" type="text/css" media="all" / >
% endfor
%endif
</%def>
<%def name='js(group)'>
% if settings.MITX_FEATURES['USE_DJANGO_PIPELINE']:
${compressed_js(group)}
% else:
% for filename in settings.PIPELINE_JS[group]['source_filenames']:
<script type="text/javascript" src="${staticfiles_storage.url(filename.replace('.coffee', '.js'))}"></script>
% endfor
%endif
</%def>
<%def name='replace_urls(text)'>${replace_urls(text)}</%def> <%def name='replace_urls(text)'>${replace_urls(text)}</%def>
...@@ -8,19 +8,22 @@ from django.db import models ...@@ -8,19 +8,22 @@ from django.db import models
class Migration(SchemaMigration): class Migration(SchemaMigration):
def forwards(self, orm): def forwards(self, orm):
# Removing unique constraint on 'CourseEnrollment', fields ['user'] # This table is dropped in a subsequent migration. This migration was causing problems when using InnoDB,
db.delete_unique('student_courseenrollment', ['user_id']) # so we are just dropping it.
pass
# # Removing unique constraint on 'CourseEnrollment', fields ['user']
# Changing field 'CourseEnrollment.user' # db.delete_unique('student_courseenrollment', ['user_id'])
db.alter_column('student_courseenrollment', 'user_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])) #
#
# # Changing field 'CourseEnrollment.user'
# db.alter_column('student_courseenrollment', 'user_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User']))
def backwards(self, orm): def backwards(self, orm):
pass
# Changing field 'CourseEnrollment.user' # # Changing field 'CourseEnrollment.user'
db.alter_column('student_courseenrollment', 'user_id', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['auth.User'], unique=True)) # db.alter_column('student_courseenrollment', 'user_id', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['auth.User'], unique=True))
# Adding unique constraint on 'CourseEnrollment', fields ['user'] # # Adding unique constraint on 'CourseEnrollment', fields ['user']
db.create_unique('student_courseenrollment', ['user_id']) # db.create_unique('student_courseenrollment', ['user_id'])
models = { models = {
......
...@@ -8,14 +8,70 @@ from django.db import models ...@@ -8,14 +8,70 @@ from django.db import models
class Migration(SchemaMigration): class Migration(SchemaMigration):
def forwards(self, orm): def forwards(self, orm):
# Deleting field 'UserProfile.occupation'
db.delete_column('auth_userprofile', 'occupation')
# Deleting field 'UserProfile.telephone_number'
db.delete_column('auth_userprofile', 'telephone_number')
# Deleting field 'UserProfile.date_of_birth'
db.delete_column('auth_userprofile', 'date_of_birth')
# Deleting field 'UserProfile.country'
db.delete_column('auth_userprofile', 'country')
# Adding field 'UserProfile.year_of_birth'
db.add_column('auth_userprofile', 'year_of_birth',
self.gf('django.db.models.fields.IntegerField')(db_index=True, null=True, blank=True),
keep_default=False)
# Adding field 'UserProfile.level_of_education'
db.add_column('auth_userprofile', 'level_of_education',
self.gf('django.db.models.fields.CharField')(db_index=True, max_length=6, null=True, blank=True),
keep_default=False)
# Adding field 'UserProfile.goals'
db.add_column('auth_userprofile', 'goals',
self.gf('django.db.models.fields.TextField')(null=True, blank=True),
keep_default=False)
# Adding index on 'UserProfile', fields ['gender']
db.create_index('auth_userprofile', ['gender'])
# 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): def backwards(self, orm):
# Removing index on 'UserProfile', fields ['gender']
db.delete_index('auth_userprofile', ['gender'])
# Adding field 'UserProfile.occupation'
db.add_column('auth_userprofile', 'occupation',
self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True),
keep_default=False)
# Adding field 'UserProfile.telephone_number'
db.add_column('auth_userprofile', 'telephone_number',
self.gf('django.db.models.fields.CharField')(max_length=25, null=True, blank=True),
keep_default=False)
# Adding field 'UserProfile.date_of_birth'
db.add_column('auth_userprofile', 'date_of_birth',
self.gf('django.db.models.fields.DateField')(null=True, blank=True),
keep_default=False)
# Adding field 'UserProfile.country'
db.add_column('auth_userprofile', 'country',
self.gf('django_countries.fields.CountryField')(max_length=2, null=True, blank=True),
keep_default=False)
# Deleting field 'UserProfile.year_of_birth'
db.delete_column('auth_userprofile', 'year_of_birth')
# Deleting field 'UserProfile.level_of_education'
db.delete_column('auth_userprofile', 'level_of_education')
# Deleting field 'UserProfile.goals'
db.delete_column('auth_userprofile', 'goals')
# Changing field 'UserProfile.country'
db.alter_column('auth_userprofile', 'country', self.gf('django.db.models.fields.CharField')(max_length=255, null=True))
models = { models = {
'auth.group': { 'auth.group': {
...@@ -80,8 +136,9 @@ class Migration(SchemaMigration): ...@@ -80,8 +136,9 @@ class Migration(SchemaMigration):
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
}, },
'student.courseenrollment': { 'student.courseenrollment': {
'Meta': {'object_name': 'CourseEnrollment'}, 'Meta': {'unique_together': "(('user', 'course_id'),)", 'object_name': 'CourseEnrollment'},
'course_id': ('django.db.models.fields.CharField', [], {'max_length': '255'}), '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', 'db_index': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
}, },
...@@ -107,19 +164,18 @@ class Migration(SchemaMigration): ...@@ -107,19 +164,18 @@ class Migration(SchemaMigration):
}, },
'student.userprofile': { 'student.userprofile': {
'Meta': {'object_name': 'UserProfile', 'db_table': "'auth_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'}), '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', [], {'db_index': 'True', 'max_length': '6', 'null': 'True', 'blank': 'True'}),
'gender': ('django.db.models.fields.CharField', [], {'max_length': '6', 'null': 'True', 'blank': 'True'}), 'goals': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'language': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), 'language': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}),
'level_of_education': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '6', 'null': 'True', 'blank': 'True'}),
'location': ('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'}), 'mailing_address': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'meta': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), 'meta': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', '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'}), 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'profile'", 'unique': 'True', 'to': "orm['auth.User']"}),
'telephone_number': ('django.db.models.fields.CharField', [], {'max_length': '25', 'null': 'True', 'blank': 'True'}), 'year_of_birth': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'})
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'profile'", 'unique': 'True', 'to': "orm['auth.User']"})
}, },
'student.usertestgroup': { 'student.usertestgroup': {
'Meta': {'object_name': 'UserTestGroup'}, 'Meta': {'object_name': 'UserTestGroup'},
......
...@@ -5,14 +5,15 @@ If you make changes to this model, be sure to create an appropriate migration ...@@ -5,14 +5,15 @@ If you make changes to this model, be sure to create an appropriate migration
file and check it in at the same time as your model changes. To do that, file and check it in at the same time as your model changes. To do that,
1. Go to the mitx dir 1. Go to the mitx dir
2. ./manage.py schemamigration user --auto description_of_your_change 2. django-admin.py schemamigration student --auto --settings=lms.envs.dev --pythonpath=. description_of_your_change
3. Add the migration file created in mitx/courseware/migrations/ 3. Add the migration file created in mitx/common/djangoapps/student/migrations/
""" """
from datetime import datetime
import json
import uuid import uuid
from django.db import models from django.db import models
from django.contrib.auth.models import User from django.contrib.auth.models import User
import json
from django_countries import CountryField from django_countries import CountryField
#from cache_toolbox import cache_model, cache_relation #from cache_toolbox import cache_model, cache_relation
...@@ -21,23 +22,43 @@ class UserProfile(models.Model): ...@@ -21,23 +22,43 @@ class UserProfile(models.Model):
class Meta: class Meta:
db_table = "auth_userprofile" db_table = "auth_userprofile"
GENDER_CHOICES = (('m', 'Male'), ('f', 'Female'), ('o', 'Other'))
## CRITICAL TODO/SECURITY ## CRITICAL TODO/SECURITY
# Sanitize all fields. # Sanitize all fields.
# This is not visible to other users, but could introduce holes later # This is not visible to other users, but could introduce holes later
user = models.OneToOneField(User, unique=True, db_index=True, related_name='profile') user = models.OneToOneField(User, unique=True, db_index=True, related_name='profile')
name = models.CharField(blank=True, max_length=255, db_index=True) name = models.CharField(blank=True, max_length=255, db_index=True)
language = models.CharField(blank=True, max_length=255, db_index=True)
location = models.CharField(blank=True, max_length=255, db_index=True) # TODO: What are we doing with this?
meta = models.TextField(blank=True) # JSON dictionary for future expansion meta = models.TextField(blank=True) # JSON dictionary for future expansion
courseware = models.CharField(blank=True, max_length=255, default='course.xml') courseware = models.CharField(blank=True, max_length=255, default='course.xml')
gender = models.CharField(blank=True, null=True, max_length=6, choices=GENDER_CHOICES)
date_of_birth = models.DateField(blank=True, null=True) # Location is no longer used, but is held here for backwards compatibility
# for users imported from our first class.
language = models.CharField(blank=True, max_length=255, db_index=True)
location = models.CharField(blank=True, max_length=255, db_index=True)
# Optional demographic data we started capturing from Fall 2012
this_year = datetime.now().year
VALID_YEARS = range(this_year, this_year - 120, -1)
year_of_birth = models.IntegerField(blank=True, null=True, db_index=True)
GENDER_CHOICES = (('m', 'Male'), ('f', 'Female'), ('o', 'Other'))
gender = models.CharField(blank=True, null=True, max_length=6, db_index=True,
choices=GENDER_CHOICES)
LEVEL_OF_EDUCATION_CHOICES = (('p_se', 'Doctorate in science or engineering'),
('p_oth', 'Doctorate in another field'),
('m', "Master's or professional degree"),
('b', "Bachelor's degree"),
('hs', "Secondary/high school"),
('jhs', "Junior secondary/junior high/middle school"),
('el', "Elementary/primary school"),
('none', "None"),
('other', "Other"))
level_of_education = models.CharField(
blank=True, null=True, max_length=6, db_index=True,
choices=LEVEL_OF_EDUCATION_CHOICES
)
mailing_address = models.TextField(blank=True, null=True) mailing_address = models.TextField(blank=True, null=True)
country = CountryField(blank=True, null=True) goals = models.TextField(blank=True, null=True)
telephone_number = models.CharField(blank=True, null=True, max_length=25)
occupation = models.CharField(blank=True, null=True, max_length=255)
def get_meta(self): def get_meta(self):
js_str = self.meta js_str = self.meta
......
...@@ -71,26 +71,25 @@ def index(request): ...@@ -71,26 +71,25 @@ def index(request):
for entry in entries: for entry in entries:
soup = BeautifulSoup(entry.description) soup = BeautifulSoup(entry.description)
entry.image = soup.img['src'] if soup.img else None entry.image = soup.img['src'] if soup.img else None
entry.summary = soup.getText()
courses = modulestore().get_courses()
universities = defaultdict(list) universities = defaultdict(list)
for university, group in itertools.groupby(courses, lambda course: course.org): courses = sorted(modulestore().get_courses(), key=lambda course: course.number)
[universities[university].append(course) for course in group] for course in courses:
universities[course.org].append(course)
return render_to_response('index.html', {'universities': universities, 'entries': entries}) 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 @login_required
@ensure_csrf_cookie @ensure_csrf_cookie
def dashboard(request): def dashboard(request):
csrf_token = csrf(request)['csrf_token']
user = request.user user = request.user
enrollments = CourseEnrollment.objects.filter(user=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 # 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 # exist (because the course IDs have changed). Still, we don't delete those
# enrollments, because it could have been a data push snafu. # enrollments, because it could have been a data push snafu.
...@@ -101,18 +100,80 @@ def dashboard(request): ...@@ -101,18 +100,80 @@ def dashboard(request):
except ItemNotFoundError: except ItemNotFoundError:
log.error("User {0} enrolled in non-existant course {1}" log.error("User {0} enrolled in non-existant course {1}"
.format(user.username, enrollment.course_id)) .format(user.username, enrollment.course_id))
message = ""
if not user.is_active:
message = render_to_string('registration/activate_account_notice.html', {'email': user.email})
context = {'csrf': csrf_token, 'courses': courses} context = {'courses': courses, 'message' : message}
return render_to_response('dashboard.html', context) return render_to_response('dashboard.html', context)
def try_change_enrollment(request):
"""
This method calls change_enrollment if the necessary POST
parameters are present, but does not return anything. It
simply logs the result or exception. This is usually
called after a registration or login, as secondary action.
It should not interrupt a successful registration or login.
"""
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.exception("Exception automatically enrolling after login: {0}".format(str(e)))
@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 # Need different levels of logging
@ensure_csrf_cookie @ensure_csrf_cookie
def login_user(request, error=""): def login_user(request, error=""):
''' AJAX request to log in the user. ''' ''' AJAX request to log in the user. '''
if 'email' not in request.POST or 'password' not in request.POST: if 'email' not in request.POST or 'password' not in request.POST:
return HttpResponse(json.dumps({'success': False, return HttpResponse(json.dumps({'success': False,
'error': 'Invalid login'})) # TODO: User error message 'value': 'There was an error receiving your login information. Please email us.'})) # TODO: User error message
email = request.POST['email'] email = request.POST['email']
password = request.POST['password'] password = request.POST['password']
...@@ -121,14 +182,14 @@ def login_user(request, error=""): ...@@ -121,14 +182,14 @@ def login_user(request, error=""):
except User.DoesNotExist: except User.DoesNotExist:
log.warning("Login failed - Unknown user email: {0}".format(email)) log.warning("Login failed - Unknown user email: {0}".format(email))
return HttpResponse(json.dumps({'success': False, return HttpResponse(json.dumps({'success': False,
'error': 'Invalid login'})) # TODO: User error message 'value': 'Email or password is incorrect.'})) # TODO: User error message
username = user.username username = user.username
user = authenticate(username=username, password=password) user = authenticate(username=username, password=password)
if user is None: if user is None:
log.warning("Login failed - password for {0} is invalid".format(email)) log.warning("Login failed - password for {0} is invalid".format(email))
return HttpResponse(json.dumps({'success': False, return HttpResponse(json.dumps({'success': False,
'error': 'Invalid login'})) 'value': 'Email or password is incorrect.'}))
if user is not None and user.is_active: if user is not None and user.is_active:
try: try:
...@@ -143,11 +204,14 @@ def login_user(request, error=""): ...@@ -143,11 +204,14 @@ def login_user(request, error=""):
log.exception(e) log.exception(e)
log.info("Login success - {0} ({1})".format(username, email)) log.info("Login success - {0} ({1})".format(username, email))
try_change_enrollment(request)
return HttpResponse(json.dumps({'success':True})) return HttpResponse(json.dumps({'success':True}))
log.warning("Login failed - Account not active for user {0}".format(username)) log.warning("Login failed - Account not active for user {0}".format(username))
return HttpResponse(json.dumps({'success':False, return HttpResponse(json.dumps({'success':False,
'error': 'Account not active. Check your e-mail.'})) 'value': 'This account has not been activated. Please check your e-mail for the activation instructions.'}))
@ensure_csrf_cookie @ensure_csrf_cookie
def logout_user(request): def logout_user(request):
...@@ -243,17 +307,20 @@ def create_account(request, post_override=None): ...@@ -243,17 +307,20 @@ def create_account(request, post_override=None):
up = UserProfile(user=u) up = UserProfile(user=u)
up.name = post_vars['name'] up.name = post_vars['name']
up.country = post_vars['country'] up.level_of_education = post_vars['level_of_education']
up.gender = post_vars['gender'] up.gender = post_vars['gender']
up.mailing_address = post_vars['mailing_address'] up.mailing_address = post_vars['mailing_address']
up.goals = post_vars['goals']
date_fields = ['date_of_birth__year', 'date_of_birth__month', 'date_of_birth__day']
if all(len(post_vars[field]) > 0 for field in date_fields): try:
up.date_of_birth = date(int(post_vars['date_of_birth__year']), up.year_of_birth = int(post_vars['year_of_birth'])
int(post_vars['date_of_birth__month']), except ValueError:
int(post_vars['date_of_birth__day'])) up.year_of_birth = None # If they give us garbage, just ignore it instead
# of asking them to put an integer.
up.save() try:
up.save()
except Exception:
log.exception("UserProfile creation failed for user {0}.".format(u.id))
d = {'name': post_vars['name'], d = {'name': post_vars['name'],
'key': r.activation_key, 'key': r.activation_key,
...@@ -275,10 +342,17 @@ def create_account(request, post_override=None): ...@@ -275,10 +342,17 @@ def create_account(request, post_override=None):
log.exception(sys.exc_info()) log.exception(sys.exc_info())
js['value'] = 'Could not send activation e-mail.' js['value'] = 'Could not send activation e-mail.'
return HttpResponse(json.dumps(js)) return HttpResponse(json.dumps(js))
js={'success': True, # Immediately after a user creates an account, we log them in. They are only
'value': render_to_string('registration/reg_complete.html', {'email': post_vars['email'], # logged in until they close the browser. They can't log in again until they click
'csrf': csrf(request)['csrf_token']})} # the activation link from the email.
login_user = authenticate(username=post_vars['username'], password = post_vars['password'] )
login(request, login_user)
request.session.set_expiry(0)
try_change_enrollment(request)
js={'success': True}
return HttpResponse(json.dumps(js), mimetype="application/json") return HttpResponse(json.dumps(js), mimetype="application/json")
def create_random_account(create_account_function): def create_random_account(create_account_function):
...@@ -308,14 +382,15 @@ def activate_account(request, key): ...@@ -308,14 +382,15 @@ def activate_account(request, key):
''' '''
r=Registration.objects.filter(activation_key=key) r=Registration.objects.filter(activation_key=key)
if len(r)==1: if len(r)==1:
user_logged_in = request.user.is_authenticated()
already_active = True
if not r[0].user.is_active: if not r[0].user.is_active:
r[0].activate() r[0].activate()
resp = render_to_response("activation_complete.html",{'csrf':csrf(request)['csrf_token']}) already_active = False
return resp resp = render_to_response("registration/activation_complete.html",{'user_logged_in':user_logged_in, 'already_active' : already_active})
resp = render_to_response("activation_active.html",{'csrf':csrf(request)['csrf_token']})
return resp return resp
if len(r)==0: if len(r)==0:
return render_to_response("activation_invalid.html",{'csrf':csrf(request)['csrf_token']}) return render_to_response("registration/activation_invalid.html",{'csrf':csrf(request)['csrf_token']})
return HttpResponse("Unknown error. Please e-mail us to let us know how it happened.") return HttpResponse("Unknown error. Please e-mail us to let us know how it happened.")
@ensure_csrf_cookie @ensure_csrf_cookie
......
...@@ -57,10 +57,10 @@ def format_url_params(params): ...@@ -57,10 +57,10 @@ def format_url_params(params):
@cache_if_anonymous @cache_if_anonymous
def courses(request): def courses(request):
# TODO: Clean up how 'error' is done. # TODO: Clean up how 'error' is done.
courses = modulestore().get_courses() courses = sorted(modulestore().get_courses(), key=lambda course: course.number)
universities = defaultdict(list) universities = defaultdict(list)
for university, group in itertools.groupby(courses, lambda course: course.org): for course in courses:
[universities[university].append(course) for course in group] universities[course.org].append(course)
return render_to_response("courses.html", { 'universities': universities }) return render_to_response("courses.html", { 'universities': universities })
...@@ -269,46 +269,13 @@ def course_about(request, course_id): ...@@ -269,46 +269,13 @@ def course_about(request, course_id):
course = check_course(course_id, course_must_be_open=False) course = check_course(course_id, course_must_be_open=False)
registered = registered_for_course(course, request.user) registered = registered_for_course(course, request.user)
return render_to_response('portal/course_about.html', {'course': course, 'registered': registered}) 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.'}))
@ensure_csrf_cookie @ensure_csrf_cookie
@cache_if_anonymous @cache_if_anonymous
def university_profile(request, org_id): def university_profile(request, org_id):
all_courses = modulestore().get_courses() all_courses = sorted(modulestore().get_courses(), key=lambda course: course.number)
valid_org_ids = set(c.org for c in all_courses) valid_org_ids = set(c.org for c in all_courses)
if org_id not in valid_org_ids: if org_id not in valid_org_ids:
raise Http404("University Profile not found for {0}".format(org_id)) raise Http404("University Profile not found for {0}".format(org_id))
......
...@@ -293,6 +293,10 @@ PIPELINE_CSS = { ...@@ -293,6 +293,10 @@ PIPELINE_CSS = {
'source_filenames': ['sass/application.scss'], 'source_filenames': ['sass/application.scss'],
'output_filename': 'css/application.css', 'output_filename': 'css/application.css',
}, },
'course': {
'source_filenames': ['sass/application.scss', 'css/vendor/codemirror.css', 'css/vendor/jquery.treeview.css'],
'output_filename': 'css/course.css',
},
'ie-fixes': { 'ie-fixes': {
'source_filenames': ['sass/ie.scss'], 'source_filenames': ['sass/ie.scss'],
'output_filename': 'css/ie.css', 'output_filename': 'css/ie.css',
...@@ -301,11 +305,47 @@ PIPELINE_CSS = { ...@@ -301,11 +305,47 @@ PIPELINE_CSS = {
PIPELINE_ALWAYS_RECOMPILE = ['sass/application.scss', 'sass/ie.scss'] PIPELINE_ALWAYS_RECOMPILE = ['sass/application.scss', 'sass/ie.scss']
courseware_only_js = [
PROJECT_ROOT / 'static/coffee/src/' + pth + '.coffee'
for pth
in ['courseware', 'histogram', 'navigation', 'time', ]
]
courseware_only_js += [
pth for pth
in glob2.glob(PROJECT_ROOT / 'static/coffee/src/modules/**/*.coffee')
]
main_vendor_js = [
'js/vendor/jquery.min.js',
'js/vendor/jquery-ui.min.js',
'js/vendor/swfobject/swfobject.js',
'js/vendor/jquery.cookie.js',
'js/vendor/jquery.qtip.min.js',
]
PIPELINE_JS = { PIPELINE_JS = {
'application': { 'application': {
'source_filenames': [pth.replace(PROJECT_ROOT / 'static/', '') for pth in glob2.glob(PROJECT_ROOT / 'static/coffee/src/**/*.coffee')], # Application will contain all paths not in courseware_only_js
'source_filenames': [
pth.replace(PROJECT_ROOT / 'static/', '')
for pth in glob2.glob(PROJECT_ROOT / 'static/coffee/src/**/*.coffee')\
if pth not in courseware_only_js
] + [
'js/form.ext.js',
'js/my_courses_dropdown.js',
'js/toggle_login_modal.js',
'js/sticky_filter.js',
],
'output_filename': 'js/application.js' 'output_filename': 'js/application.js'
}, },
'courseware': {
'source_filenames': [pth.replace(PROJECT_ROOT / 'static/', '') for pth in courseware_only_js],
'output_filename': 'js/courseware.js'
},
'main_vendor': {
'source_filenames': main_vendor_js,
'output_filename': 'js/main_vendor.js',
},
'spec': { 'spec': {
'source_filenames': [pth.replace(PROJECT_ROOT / 'static/', '') for pth in glob2.glob(PROJECT_ROOT / 'static/coffee/spec/**/*.coffee')], 'source_filenames': [pth.replace(PROJECT_ROOT / 'static/', '') for pth in glob2.glob(PROJECT_ROOT / 'static/coffee/spec/**/*.coffee')],
'output_filename': 'js/spec.js' 'output_filename': 'js/spec.js'
......
...@@ -7,9 +7,11 @@ ...@@ -7,9 +7,11 @@
closeButton: null, closeButton: null,
position: 'fixed' position: 'fixed'
} }
var overlay = $("<div id='lean_overlay'></div>"); if ($("#lean_overlay").length == 0) {
$("body").append(overlay); var overlay = $("<div id='lean_overlay'></div>");
$("body").append(overlay);
}
options = $.extend(defaults, options); options = $.extend(defaults, options);
...@@ -51,7 +53,11 @@ ...@@ -51,7 +53,11 @@
$(modal_id).find(".notice").hide().html(""); $(modal_id).find(".notice").hide().html("");
var notice = $(this).data('notice') var notice = $(this).data('notice')
if(notice !== undefined) { if(notice !== undefined) {
$(modal_id).find(".notice").show().html(notice); $notice = $(modal_id).find(".notice");
$notice.show().html(notice);
// This is for activating leanModal links that were in the notice. We should have a cleaner way of
// allowing all dynamically added leanmodal links to work.
$notice.find("a[rel*=leanModal]").leanModal({ top : 120, overlay: 1, closeButton: ".close-modal", position: 'absolute' });
} }
window.scrollTo(0, 0); window.scrollTo(0, 0);
e.preventDefault(); e.preventDefault();
...@@ -71,8 +77,13 @@ ...@@ -71,8 +77,13 @@
$(this).leanModal({ top : 120, overlay: 1, closeButton: ".close-modal", position: 'absolute' }); $(this).leanModal({ top : 120, overlay: 1, closeButton: ".close-modal", position: 'absolute' });
embed = $($(this).attr('href')).find('iframe') embed = $($(this).attr('href')).find('iframe')
if(embed.length > 0) { if(embed.length > 0) {
embed.data('src', embed.attr('src') + '?autoplay=1'); if(embed.attr('src').indexOf("?") > 0) {
embed.attr('src', ''); embed.data('src', embed.attr('src') + '&autoplay=1&rel=0');
embed.attr('src', '');
} else {
embed.data('src', embed.attr('src') + '?autoplay=1&rel=0');
embed.attr('src', '');
}
} }
}); });
})(jQuery); })(jQuery);
...@@ -8,13 +8,6 @@ ...@@ -8,13 +8,6 @@
@import 'base/extends'; @import 'base/extends';
@import 'base/animations'; @import 'base/animations';
// Courseware styles
@import 'sass_old/base/variables';
@import 'sass_old/base/extends';
@import 'sass_old/base/functions';
// Multicourse styles // Multicourse styles
@import 'shared/forms'; @import 'shared/forms';
@import 'shared/footer'; @import 'shared/footer';
...@@ -24,22 +17,12 @@ ...@@ -24,22 +17,12 @@
@import 'shared/modal'; @import 'shared/modal';
@import 'shared/activation_messages'; @import 'shared/activation_messages';
@import 'home'; @import 'multicourse/home';
@import 'dashboard'; @import 'multicourse/dashboard';
@import 'courseware_subnav'; @import 'multicourse/courses';
@import 'courses'; @import 'multicourse/course_about';
@import 'course_about'; @import 'multicourse/jobs';
@import 'jobs'; @import 'multicourse/about_pages';
@import 'about_pages'; @import 'multicourse/press_release';
@import 'press_release'; @import 'multicourse/password_reset';
@import 'password_reset'; @import 'multicourse/error-pages';
@import 'error-pages';
// Courseware styles
@import 'sass_old/courseware/courseware';
@import 'sass_old/courseware/sequence-nav';
@import 'sass_old/courseware/sidebar';
@import 'sass_old/courseware/video';
@import 'sass_old/courseware/amplifier';
@import 'sass_old/courseware/problems';
/* Generated by Font Squirrel (http://www.fontsquirrel.com) on January 25, 2012 05:06:34 PM America/New_York */ /* Generated by Font Squirrel (http://www.fontsquirrel.com) on January 25, 2012 05:06:34 PM America/New_York */
@font-face { @font-face {
font-family: 'Open Sans'; font-family: 'Open Sans';
src: url('../fonts/OpenSans-Light-webfont.eot'); src: url('../fonts/OpenSans-Light-webfont.eot');
...@@ -32,7 +31,7 @@ ...@@ -32,7 +31,7 @@
url('../fonts/OpenSans-Regular-webfont.woff') format('woff'), url('../fonts/OpenSans-Regular-webfont.woff') format('woff'),
url('../fonts/OpenSans-Regular-webfont.ttf') format('truetype'), url('../fonts/OpenSans-Regular-webfont.ttf') format('truetype'),
url('../fonts/OpenSans-Regular-webfont.svg#OpenSansRegular') format('svg'); url('../fonts/OpenSans-Regular-webfont.svg#OpenSansRegular') format('svg');
font-weight: 600; font-weight: 400;
font-style: normal; font-style: normal;
} }
...@@ -49,31 +48,6 @@ ...@@ -49,31 +48,6 @@
} }
// Not used in UI
// @font-face {
// font-family: 'Open Sans';
// src: url('../fonts/OpenSans-Semibold-webfont.eot');
// src: url('../fonts/OpenSans-Semibold-webfont.eot?#iefix') format('embedded-opentype'),
// url('../fonts/OpenSans-Semibold-webfont.woff') format('woff'),
// url('../fonts/OpenSans-Semibold-webfont.ttf') format('truetype'),
// url('../fonts/OpenSans-Semibold-webfont.svg#OpenSansSemibold') format('svg');
// font-weight: 600;
// font-style: normal;
// }
// @font-face {
// font-family: 'Open Sans';
// src: url('../fonts/OpenSans-SemiboldItalic-webfont.eot');
// src: url('../fonts/OpenSans-SemiboldItalic-webfont.eot?#iefix') format('embedded-opentype'),
// url('../fonts/OpenSans-SemiboldItalic-webfont.woff') format('woff'),
// url('../fonts/OpenSans-SemiboldItalic-webfont.ttf') format('truetype'),
// url('../fonts/OpenSans-SemiboldItalic-webfont.svg#OpenSansSemiboldItalic') format('svg');
// font-weight: 600;
// font-style: italic;
// }
@font-face { @font-face {
font-family: 'Open Sans'; font-family: 'Open Sans';
src: url('../fonts/OpenSans-Bold-webfont.eot'); src: url('../fonts/OpenSans-Bold-webfont.eot');
......
@import 'bourbon/bourbon';
@import 'base/reset';
@import 'base/font_face';
@import 'base/variables';
@import 'base/base';
@import 'base/mixins';
@import 'base/extends';
@import 'base/animations';
// Course styles
@import 'course/courseware_subnav';
// Courseware styles
@import 'course/old/base/variables';
@import 'course/old/base/extends';
@import 'course/old/base/functions';
@import 'course/old/courseware/courseware';
@import 'course/old/courseware/sequence-nav';
@import 'course/old/courseware/sidebar';
@import 'course/old/courseware/video';
@import 'course/old/courseware/amplifier';
@import 'course/old/courseware/problems';
...@@ -84,7 +84,7 @@ div.leanModal_box { ...@@ -84,7 +84,7 @@ div.leanModal_box {
form { form {
text-align: left; text-align: left;
div#enroll_error, div#login_error, div#pwd_error { div#register_error, div#login_error, div#pwd_error {
$error-color: #333; $error-color: #333;
background-color: $error-color; background-color: $error-color;
border: darken($error-color, 20%); border: darken($error-color, 20%);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment