diff --git a/student/models.py b/student/models.py index c6592391ae42237e1e4c10dd435e32d87d36fa58..6810ce6d333cc1bd7f814a1892496954cf862b93 100644 --- a/student/models.py +++ b/student/models.py @@ -12,6 +12,7 @@ import uuid from django.db import models from django.contrib.auth.models import User +import json #from cache_toolbox import cache_model, cache_relation @@ -29,6 +30,15 @@ class UserProfile(models.Model): meta = models.CharField(blank=True, max_length=255) # JSON dictionary for future expansion courseware = models.CharField(blank=True, max_length=255, default='course.xml') + def get_meta(): + try: + js = json.reads(self.meta) + except: + js = dict() + return json + def set_meta(js): + self.meta = json.dumps(js) + ## TODO: Should be renamed to generic UserGroup, and possibly # Given an optional field for type of group class UserTestGroup(models.Model): @@ -66,7 +76,7 @@ class PendingNameChange(models.Model): class PendingEmailChange(models.Model): user = models.OneToOneField(User, unique=True, db_index=True) new_email = models.CharField(blank=True, max_length=255, db_index=True) - + activation_key = models.CharField(('activation key'), max_length=32, unique=True, db_index=True) #cache_relation(User.profile) diff --git a/student/views.py b/student/views.py index c3c3d4447cce068ef073c6e0e2b093f5b0498de5..754cdf8091a5dc67bf1d52a6514bf0d93128e9c7 100644 --- a/student/views.py +++ b/student/views.py @@ -8,6 +8,7 @@ from django.contrib.auth import logout, authenticate, login from django.contrib.auth.forms import PasswordResetForm from django.contrib.auth.models import User from django.core.context_processors import csrf +from django.core.mail import send_mail from django.core.validators import validate_email, validate_slug from django.db import connection from django.http import HttpResponse, Http404 @@ -20,6 +21,8 @@ from django_future.csrf import ensure_csrf_cookie log = logging.getLogger("mitx.user") def csrf_token(context): + ''' A csrf token that can be included in a form. + ''' csrf_token = context.get('csrf_token', '') if csrf_token == 'NOTPROVIDED': return '' @@ -27,6 +30,8 @@ def csrf_token(context): @ensure_csrf_cookie def index(request): + ''' Redirects to main page -- info page if user authenticated, or marketing if not + ''' if settings.COURSEWARE_ENABLED and request.user.is_authenticated(): return redirect('/info') else: @@ -37,6 +42,7 @@ def index(request): # Need different levels of logging @ensure_csrf_cookie def login_user(request, error=""): + ''' AJAX request to log in the user. ''' if 'email' not in request.POST or 'password' not in request.POST: return HttpResponse(json.dumps({'success':False, 'error': 'Invalid login'})) # TODO: User error message @@ -78,6 +84,7 @@ def login_user(request, error=""): @ensure_csrf_cookie def logout_user(request): + ''' HTTP request to log in the user. Redirects to marketing page''' logout(request) # print len(connection.queries), connection.queries return redirect('/') @@ -111,8 +118,6 @@ def create_account(request, post_override=None): js['value']="Error (401 {field}). E-mail us.".format(field=a) return HttpResponse(json.dumps(js)) - - if post_vars['honor_code']!=u'true': js['value']="To enroll, you must follow the honor code.".format(field=a) return HttpResponse(json.dumps(js)) @@ -181,16 +186,16 @@ def create_account(request, post_override=None): 'key':r.activation_key, 'site':settings.SITE_NAME} - subject = render_to_string('activation_email_subject.txt',d) + subject = render_to_string('emails/activation_email_subject.txt',d) # Email subject *must not* contain newlines subject = ''.join(subject.splitlines()) - message = render_to_string('activation_email.txt',d) + message = render_to_string('emails/activation_email.txt',d) try: if not settings.GENERATE_RANDOM_USER_CREDENTIALS: res=u.email_user(subject, message, settings.DEFAULT_FROM_EMAIL) except: - js['value']=str(sys.exc_info()) + js['value']='Could not send activation e-mail.' return HttpResponse(json.dumps(js)) js={'success':True, @@ -225,6 +230,8 @@ if settings.GENERATE_RANDOM_USER_CREDENTIALS: @ensure_csrf_cookie def activate_account(request, key): + ''' When link in activation e-mail is clicked + ''' r=Registration.objects.filter(activation_key=key) if len(r)==1: if not r[0].user.is_active: @@ -257,15 +264,44 @@ def password_reset(request): def reactivation_email(request): ''' Send an e-mail to reactivate a deactivated account, or to resend an activation e-mail ''' - pass + email = request.POST['email'] + try: + user = User.objects.get(email = 'email') + except: # TODO: Type of exception + return HttpResponse(json.dumps({'success':False, + 'error': 'No inactive user with this e-mail exists'})) + + if user.is_active: + return HttpResponse(json.dumps({'success':False, + 'error': 'User is already active'})) + + reg = Registration.objects.get(user = user) + reg.register(user) + + d={'name':UserProfile.get(user = user).name, + 'key':r.activation_key, + 'site':settings.SITE_NAME} + + subject = render_to_string('reactivation_email_subject.txt',d) + subject = ''.join(subject.splitlines()) + message = render_to_string('reactivation_email.txt',d) + + res=u.email_user(subject, message, settings.DEFAULT_FROM_EMAIL) + + return HttpResponse(json.dumps({'success':True})) + @ensure_csrf_cookie def change_email_request(request): - ## Maske sure it checks for existing e-mail conflicts + ''' AJAX call from the profile page. User wants a new e-mail. + ''' + ## Make sure it checks for existing e-mail conflicts if not request.user.is_authenticated: raise Http404 - if not request.user.check_password(request.POST['password']): + user = request.user + + if not user.check_password(request.POST['password']): return HttpResponse(json.dumps({'success':False, 'error':'Invalid password'})) @@ -273,15 +309,64 @@ def change_email_request(request): if len(User.objects.filter(email = new_email)) != 0: ## CRITICAL TODO: Handle case for e-mails return HttpResponse(json.dumps({'success':False, - 'error':'An account with this e-mail already exists.'})) + 'error':'An account with this e-mail already exists.'})) - + pec_list = PendingEmailChange.objects.filter(user = request.user) + if len(pec_list) == 0: + pec = PendingEmailChange() + pec.user = user + else : + pec = pec_list[0] + + pec.new_email = request.POST['new_email'] + pec.activation_key = uuid.uuid4().hex + pec.save() + + if pec.new_email == user.email: + pec.delete() + return HttpResponse(json.dumps({'success':False, + 'error':'Old email is the same as the new email.'})) - request.POST['new_email'] + d = {'site':settings.SITE_NAME, + 'key':pec.activation_key, + 'old_email' : user.email, + 'new_email' : pec.email} + + subject = render_to_string('emails/email_change_subject.txt',d) + message = render_to_string('emails/email_change.txt',d) + + res=send_email(subject, message, settings.DEFAULT_FROM_EMAIL, [pec.email]) + + return HttpResponse(json.dumps({'success':True})) @ensure_csrf_cookie -def change_email_confirm(request): - pass +def confirm_email_change(request, key): + ''' User requested a new e-mail. This is called when the activation + link is clicked. We confirm with the old e-mail, and update + ''' + try: + pec=PendingEmailChange.objects.get(activation_key=key) + except: + return render_to_response("email_invalid_key.html") + + subject = render_to_string('emails/confirm_email_change_subject.txt',d) + subject = ''.join(subject.splitlines()) + message = render_to_string('emails/confirm_email_change.txt',d) + + user = pec.user + user.email_user(subject, message, DEFAULT_FROM_EMAIL) + up = UserProfile.objects.get( user = user ) + meta = up.get_meta() + if 'old_emails' not in meta: + meta['old_emails'] = [] + meta['old_emails'].append(user.email) + up.set_meta(meta) + up.save() + user.email = pec.new_email + user.save() + pec.delete() + + return render_to_response("email_change_successful.html") @ensure_csrf_cookie def change_name_request(request): @@ -294,7 +379,6 @@ def change_name_request(request): pnc.rationale = request.POST['rationale'] pnc.save() return HttpResponse(json.dumps({'success':True})) - @ensure_csrf_cookie def change_name_list(request): diff --git a/urls.py b/urls.py index ed669a3e941ed6d9ca711d8ec8ae173d94d7d59e..23932db22b32ee910c608edf88a25786d0468bcd 100644 --- a/urls.py +++ b/urls.py @@ -8,20 +8,21 @@ import django.contrib.auth.views # admin.autodiscover() urlpatterns = ('', + url(r'^$', 'student.views.index'), # Main marketing page, or redirect to courseware url(r'^email_change$', 'student.views.change_email_request'), url(r'^email_confirm$', 'student.views.change_email_confirm'), url(r'^gradebook$', 'courseware.views.gradebook'), url(r'^event$', 'track.views.user_track'), url(r'^t/(?P<template>[^/]*)$', 'static_template_view.views.index'), - url(r'^logout$', 'student.views.logout_user'), - url(r'^info$', 'util.views.info'), url(r'^login$', 'student.views.login_user'), url(r'^login/(?P<error>[^/]*)$', 'student.views.login_user'), + url(r'^logout$', 'student.views.logout_user'), url(r'^create_account$', 'student.views.create_account'), url(r'^activate/(?P<key>[^/]*)$', 'student.views.activate_account'), url(r'^reactivate/(?P<key>[^/]*)$', 'student.views.reactivation_email'), - url(r'^$', 'student.views.index'), url(r'^password_reset/$', 'student.views.password_reset'), + ## Obsolete Django views for password resets + ## TODO: Replace with Mako-ized views url(r'^password_change/$',django.contrib.auth.views.password_change,name='auth_password_change'), url(r'^password_change_done/$',django.contrib.auth.views.password_change_done,name='auth_password_change_done'), url(r'^password_reset_confirm/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$',django.contrib.auth.views.password_reset_confirm, @@ -30,6 +31,7 @@ urlpatterns = ('', name='auth_password_reset_complete'), url(r'^password_reset_done/$',django.contrib.auth.views.password_reset_done, name='auth_password_reset_done'), + ## Feedback url(r'^send_feedback$', 'util.views.send_feedback'), ) @@ -38,6 +40,7 @@ if settings.PERFSTATS: if settings.COURSEWARE_ENABLED: urlpatterns=urlpatterns + (url(r'^courseware/$', 'courseware.views.index', name="courseware"), + url(r'^info$', 'util.views.info'), url(r'^wiki/', include('simplewiki.urls')), url(r'^courseware/(?P<course>[^/]*)/(?P<chapter>[^/]*)/(?P<section>[^/]*)/$', 'courseware.views.index', name="courseware_section"), url(r'^courseware/(?P<course>[^/]*)/(?P<chapter>[^/]*)/$', 'courseware.views.index', name="courseware_chapter"),