Commit a54fcd97 by David Ormsbee

Merge pull request #5 from MITx/pmitros/name-change

Name and e-mail change functionality
parents 393044fb 162fa594
......@@ -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,18 @@ 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(self):
js_str = self.meta
if not js_str:
js_str = dict()
else:
js_str = json.loads(self.meta)
return js_str
def set_meta(self,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):
......@@ -58,6 +71,16 @@ class Registration(models.Model):
self.user.save()
#self.delete()
class PendingNameChange(models.Model):
user = models.OneToOneField(User, unique=True, db_index=True)
new_name = models.CharField(blank=True, max_length=255)
rationale = models.CharField(blank=True, max_length=1024)
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)
#### Helper methods for use from python manage.py shell.
......@@ -88,6 +111,8 @@ def change_name(email, new_name):
up.save()
def user_count():
print "All users", User.objects.all().count()
print "Active users", User.objects.filter(is_active = True).count()
return User.objects.all().count()
def active_user_count():
......@@ -99,12 +124,29 @@ def create_group(name, description):
utg.description = description
utg.save()
def add_user_to_group(group, user):
def add_user_to_group(user, group):
utg = UserTestGroup.objects.get(name = group)
utg.users.add(User.objects.get(username = user))
utg.save()
def remove_user_from_group(group, user):
def remove_user_from_group(user, group):
utg = UserTestGroup.objects.get(name = group)
utg.users.remove(User.objects.get(username = user))
utg.save()
default_groups = {'email_future_courses' : 'Receive e-mails about future MITx courses',
'email_helpers' : 'Receive e-mails about how to help with MITx',
'mitx_unenroll' : 'Fully unenrolled -- no further communications',
'6002x_unenroll' : 'Took and dropped 6002x'}
def add_user_to_default_group(user, group):
try:
utg = UserTestGroup.objects.get(name = group)
except UserTestGroup.DoesNotExist:
utg = UserTestGroup()
utg.name = group
utg.description = default_groups[group]
utg.save()
utg.users.add(User.objects.get(username = user))
utg.save()
......@@ -7,7 +7,7 @@
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# distribuetd under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
......@@ -15,6 +15,7 @@
from mako.lookup import TemplateLookup
import tempfile
from django.template import RequestContext
from django.conf import settings
requestcontext = None
lookup = {}
......@@ -44,4 +45,5 @@ class MakoMiddleware(object):
def process_request (self, request):
global requestcontext
requestcontext = RequestContext(request)
requestcontext['is_secure'] = request.is_secure()
requestcontext['site'] = settings.SITE_NAME
......@@ -18,18 +18,21 @@ from django.http import HttpResponse
import mitxmako.middleware as middleware
from django.conf import settings
from mitxmako.middleware import requestcontext
import mitxmako.middleware
def render_to_string(template_name, dictionary, context_instance=None, namespace='main'):
context_instance = context_instance or Context(dictionary)
def render_to_string(template_name, dictionary, context=None, namespace='main'):
context_instance = Context(dictionary)
# add dictionary to context_instance
context_instance.update(dictionary or {})
# collapse context_instance to a single dictionary for mako
context_dictionary = {}
context_instance['settings'] = settings
context_instance['request_context'] = requestcontext
for d in mitxmako.middleware.requestcontext:
context_dictionary.update(d)
for d in context_instance:
context_dictionary.update(d)
if context:
context_dictionary.update(context)
# fetch and render template
template = middleware.lookup[namespace].get_template(template_name)
return template.render(**context_dictionary)
......
......@@ -6,8 +6,7 @@
<section class="activation">
<h1>Account already active!</h1>
<!-- <p>Now go <a href="/">log in</a> and try the course!</a></p> -->
<p> This account has already been activated. We will notify you as
soon as the course starts.</p>
<p>For now you can go to the <a href="http://mitx.mit.edu/">MITx homepage</a> or the <a href="/">6.002x course page</a>.</p>
<p> This account has already been activated. You can log in at
the <a href="/">6.002x course page</a>.</p>
</div>
</section>
......@@ -4,7 +4,6 @@
<div>
<h1>Activation Complete!</h1>
<!-- <p>Now go <a href="/">log in</a> and try the course!</a></p> -->
<p>Thanks for activating your email. We will notify you as soon as the course starts.</p>
<p>For now you can go to the <a href="http://mitx.mit.edu/">MITx homepage</a> or the <a href="/">6.002x course page</a>.</p>
<p>Thanks for activating your account. You can log in at the <a href="/">6.002x course page</a>.</p>
</div>
</section>
<h1>E-mail change successful!</h1>
<p> You should see your new name in your profile.
<h1> Could not change e-mail </h1>
An account with the new e-mail address already exists. Sorry.
......@@ -3,7 +3,11 @@ offering of 6.002 using this email address. If it was you, and you'd
like to activate and use your account, copy and paste this address
into your web browser's address bar:
http://${ site }/activate/${ key }
% if is_secure:
https://${ site }/activate/${ key }
% else:
http://${ site }/activate/${ key }
% endif
If you didn't request this, you don't need to do anything; you won't
receive any more email from us. Please do not reply to this e-mail; if
......
This is to confirm that you changed the e-mail associated with MITx
from ${old_email} to ${new_email}. If you did not make this request,
please contact the course staff immediately. Contact information is
listed at:
% if is_secure:
https://${ site }/t/mitx_help.html
% else:
http://${ site }/t/mitx_help.html
% endif
We keep a log of old e-mails, so if this request was unintentional, we
can investigate.
We received a request to change the e-mail associated with your MITx
account from ${old_email} to ${new_email}. If this is correct, please
confirm your new e-mail address by visiting:
% if is_secure:
https://${ site }/email_confirm/${ key }
% else:
http://${ site }/email_confirm/${ key }
% endif
If you didn't request this, you don't need to do anything; you won't
receive any more email from us. Please do not reply to this e-mail; if
you require assistance, check the help section of the MITx web site.
Request to change MITx account e-mail
(Not currently used)
We are sorry. Our course staff did not approve your request to change
your name from ${old_name} to ${new_name}. If you need further
assistance, please e-mail the course staff at ta@mitx.mit.edu.
MITx's prototype offering, 6.002x, is now open. To log in, visit
% if is_secure:
https://6002x.mitx.mit.edu
% else:
http://6002x.mitx.mit.edu
% endif
where you will find a login button at the top right-hand corner of the
window.
......
<h1>E-mail change successful!</h1>
<p> You should see your new name in your profile.
<h1>Invalid key</h1>
<p> This e-mail key is not valid. Please check:
<ul>
<li> Was this key already used? Check whether the e-mail change has already happened.
<li> Did your e-mail client break the URL into two lines?
<li> The keys are valid for a limited amount of time. Has the key expired?
</ul>
<%inherit file="main.html" />
<%include file="navigation.html" args="active_page=''" />
<section class="main-content">
<script>
function name_confirm(id) {
postJSON('/accept_name_change',{"id":id},
function(data){
if(data.success){
$("#div"+id).html("Accepted");
} else {
alert('Error');
}
});
}
function name_deny(id) {
postJSON('/reject_name_change',{"id":id},
function(data){
if(data.success){
$("#div"+id).html("Rejected");
} else {
alert('Error');
}
});
}
</script>
<div class="gradebook-wrapper">
<section class="gradebook-content">
<h1>Pending name changes</h1>
<table>
% for s in students:
<tr>
<td><a href=/profile/${s['uid']}/>${s['old_name']}</td>
<td>${s['new_name']|h}</td>
<td>${s['email']|h}</td>
<td>${s['rationale']|h}</td>
<td><span id="div${s['cid']}"><span onclick="name_confirm(${s['cid']});">[Confirm]</span>
<span onclick="name_deny(${s['cid']});">[Reject]</span></span></td></tr>
% endfor
</table>
</section>
</div>
</section>
......@@ -80,6 +80,46 @@ $(function() {
log_event("profile", {"type":"password_send"});
});
});
$("#change_email_form").submit(function(){
var new_email = $('#new_email_field').val();
var new_password = $('#new_email_password').val();
postJSON('/change_email',{"new_email":new_email,
"password":new_password},
function(data){
if(data.success){
$("#change_email").html("<h1>Please verify your new email</h1><p>You'll receive a confirmation in your in-box. Please click the link in the email to confirm the email change.</p>");
} else {
$("#change_email_error").html(data.error);
}
});
log_event("profile", {"type":"email_change_request",
"old_email":"${email}",
"new_email":new_email});
return false;
});
$("#change_name_form").submit(function(){
var new_name = $('#new_name_field').val();
var rationale = $('#name_rationale_field').val();
postJSON('/change_name',{"new_name":new_name,
"rationale":rationale},
function(data){
if(data.success){
$("#apply_name_change").html("<h1>Your request has been submitted.</h1><p>We'll send you an e-mail when approve the change or need further information. Please allow for up to a week for us to process your request.</p>");
} else {
$("#change_name_error").html(data.error);
}
});
log_event("profile", {"type":"name_change_request",
"new_name":new_name,
"rationale":rationale});
return false;
});
});
</script>
</%block>
......@@ -91,17 +131,19 @@ $(function() {
<div class="profile-wrapper">
<section class="course-info">
<h1>Course Progress</h1>
<header>
<h1>Course Progress</h1>
</header>
<div id="grade-detail-graph"></div>
<ol class="chapters">
%for chapter in courseware_summary:
%if not chapter['chapter'] == "hidden":
<li>
<h2><a href="${reverse('courseware_chapter', args=format_url_params([chapter['course'], chapter['chapter']])) }">
${ chapter['chapter'] }</a></h2>
<ol class="sections">
%for section in chapter['sections']:
<li>
......@@ -134,37 +176,135 @@ $(function() {
%endif
%endfor
</ol> <!--End chapters-->
</section>
<section class="user-info">
<h1>${name}</h1>
<header>
<h1>Student Profile</h1>
</header>
<ul>
<li>Forum name: <strong>${username}</strong></li>
<li>E-mail: <strong>${email}</strong></li>
<li>
Name: <strong>${name}</strong>
%if True:
<a href="#apply_name_change" rel="leanModal" class="name-edit">Edit</a>
%else:
(Name change pending)
%endif
</li>
<li>
Forum name: <strong>${username}</strong>
</li>
<li>
E-mail: <strong>${email}</strong> <a href="#change_email" rel="leanModal" class="edit-email">Edit</a>
</li>
<li>
Location: <div id="location_sub">${location}</div><div id="description"></div> <a href="#" id="change_location">Edit</a>
</li>
<li>
Language: <div id="language_sub">${language}</div> <a href="#" id="change_language">Edit</a>
</li>
<li>
Password reset
<input id="id_email" type="hidden" name="email" maxlength="75" value="${email}" />
<input type="submit" id="pwd_reset_button" value="Reset" />
<p>We'll e-mail a password reset link to ${email}.</p>
</li>
</ul>
<div id="change_password_pop">
<h2>Password change</h2>
<p>We'll e-mail a password reset link to ${email}.</p>
<input id="id_email" type="hidden" name="email" maxlength="75" value="${email}" />
<input type="submit" id="pwd_reset_button" value="Reset Password" />
</div>
</section>
</div>
</section>
<div id="password_reset_complete" class="leanModal_box">
<a href="#password_reset_complete" rel="leanModal" id="password_reset_complete_link"></a>
<h1>Password Reset Email Sent</h1>
An email has been sent to ${email}. Follow the link in the email to change your password.
</div>
<div id="password_reset_complete" class="leanModal_box">
<a href="#password_reset_complete" rel="leanModal" id="password_reset_complete_link"></a>
<h1>Password Reset Email Sent</h1>
<p>
An email has been sent to ${email}. Follow the link in the email to change your password.
</p>
</div>
<div id="apply_name_change" class="leanModal_box">
<h1>Apply to change your name</h1>
<form id="change_name_form">
<div id="change_name_error"> </div>
<fieldset>
<p>To uphold the credibility of MITx certificates, name changes must go through an approval process. A member of the course staff will review your request, and if approved, update your information. Please allow up to a week for your request to be processed. Thank you.</p>
<ul>
<li>
<label>Enter your desired full name, as it will appear on the MITx Certificate: </label>
<input id="new_name_field" value="" type="text" />
</li>
<li>
<label>Reason for name change:</label>
<textarea id="name_rationale_field" value=""></textarea>
</li>
<li>
<input type="submit" id="submit">
</li>
</ul>
</fieldset>
</form>
</div>
<div id="change_email" class="leanModal_box">
<h1>Change e-mail</h1>
<div id="apply_name_change_error"></div>
<form id="change_email_form">
<div id="change_email_error"> </div>
<fieldset>
<ul>
<li>
<label> Please enter your new email address: </label>
<input id="new_email_field" type="email" value="" />
</li>
<li>
<label> Please confirm your password: </label>
<input id="new_email_password" value="" type="password" />
</li>
<li>
<p>We will send a confirmation to both ${email} and your new e-mail as part of the process.</p>
<input type="submit" id="submit_email_change" />
</li>
</ul>
</fieldset>
</form>
</div>
<div id="deactivate-account" class="leanModal_box">
<h1>Deactivate MITx Account</h1>
<p>Once you deactivate you&rsquo;re MIT<em>x</em> account you will no longer recieve updates and new class announcements from MIT<em>x</em>.</p>
<p>If you&rsquo;d like to still get updates and new class announcements you can just <a href="#unenroll" rel="leanModal">unenroll</a> and keep your account active.</p>
<form id="unenroll_form">
<div id="unenroll_error"> </div>
<fieldset>
<ul>
<li>
<input type="submit" id="" value="Yes, I don't want an MITx account or hear about any new classes or updates to MITx" />
</li>
</ul>
</fieldset>
</form>
</div>
<div id="unenroll" class="leanModal_box">
<h1>Unenroll from 6.002x</h1>
<p>Please note: you will still receive updates and new class announcements from MIT<em>x</em>. If you don&rsquo;t wish to receive any more updates or announcements <a href="#deactivate-account" rel="leanModal">deactivate your account</a>.</p>
<form id="unenroll_form">
<div id="unenroll_error"> </div>
<fieldset>
<ul>
<li>
<input type="submit" id="" value="Yes, I want to unenroll from 6.002x but still hear about any new classes or updates to MITx" />
</li>
</ul>
</fieldset>
</form>
</div>
......@@ -8,11 +8,29 @@ div.profile-wrapper {
border-left: 1px solid #d3d3d3;
border-right: 0;
h1 {
header {
padding: lh(.5) lh();
font-size: 18px;
margin: 0 ;
@extend .bottom-border;
h1 {
font-size: 18px;
margin: 0;
padding-right: 30px;
}
a {
position: absolute;
top: 13px;
right: lh(.5);
text-transform: uppercase;
font-size: 12px;
color: #999;
&:hover {
color: #555;
}
}
}
ul {
......@@ -57,7 +75,11 @@ div.profile-wrapper {
font-size: 12px;
}
a#change_language, a#change_location {
a#change_language,
a#change_location,
a.edit-email,
a.name-edit,
a.email-edit {
position: absolute;
top: 9px;
right: lh(.5);
......@@ -69,20 +91,66 @@ div.profile-wrapper {
color: #555;
}
}
p {
font-size: 12px;
margin-bottom: 0;
margin-top: 4px;
color: #999;
}
a.deactivate {
color: #aaa;
font-style: italic;
}
input {
background: none;
border: none;
@include box-shadow(none);
color: #999;
font-size: 12px;
font-weight: normal;
margin: 0;
padding: 0;
position: absolute;
right: lh(.5);
text-transform: uppercase;
top: 9px;
&:hover {
color: #555;
}
}
}
}
div#change_password_pop {
padding: 7px lh();
border-bottom: 1px solid #d3d3d3;
@include box-shadow(0 1px 0 #eee);
color: #4D4D4D;
padding: 7px lh();
h2 {
margin-top: 0;
font-weight: bold;
text-transform: uppercase;
font-size: $body-font-size;
}
}
}
section.course-info {
@extend .content;
> h1 {
@extend .top-header;
header {
@extend h1.top-header;
@extend .clearfix;
h1 {
margin: 0;
float: left;
}
}
div#grade-detail-graph {
......@@ -122,30 +190,30 @@ div.profile-wrapper {
padding-left: flex-gutter(9);
width: flex-grid(7, 9);
> li {
padding:0 0 lh() 0;
> li {
padding:0 0 lh() 0;
&:first-child {
padding-top: 0;
}
&:first-child {
padding-top: 0;
}
&:last-child {
border-bottom: 0;
}
&:last-child {
border-bottom: 0;
}
h3 {
color: #666;
}
h3 {
color: #666;
}
ol {
list-style: none;
ol {
list-style: none;
li {
display: inline-block;
padding-right: 1em;
li {
display: inline-block;
padding-right: 1em;
}
}
}
}
}
}
}
......
@mixin border-image($images) {
-webkit-border-image: border-add-prefix($images, webkit);
-moz-border-image: border-add-prefix($images, moz);
-o-border-image: border-add-prefix($images, o);
border-image: border-add-prefix($images);
@mixin border-image ($image) {
-webkit-border-image: $image;
-moz-border-image: $image;
-ms-border-image: $image;
-o-border-image: $image;
border-image: $image;
}
@function border-add-prefix($images, $vendor: false) {
$border-image: ();
$images-type: type-of(nth($images, 1));
$first-var: nth(nth($images, 1), 1); // Get type of Gradient (Linear || radial)
// If input is a gradient
@if $images-type == string {
@if ($first-var == "linear") or ($first-var == "radial") {
@for $i from 2 through length($images) {
$gradient-type: nth($images, 1); // Get type of gradient (linear || radial)
$gradient-args: nth($images, $i); // Get actual gradient (red, blue)
$border-image: render-gradients($gradient-args, $gradient-type, $vendor);
}
}
// If input is a URL
@else {
$border-image: $images;
}
}
// If input is gradient or url + additional args
@else if $images-type == list {
@for $i from 1 through length($images) {
$type: type-of(nth($images, $i)); // Get type of variable - List or String
// If variable is a list - Gradient
@if $type == list {
$gradient-type: nth(nth($images, $i), 1); // Get type of gradient (linear || radial)
$gradient-args: nth(nth($images, $i), 2); // Get actual gradient (red, blue)
$border-image: render-gradients($gradient-args, $gradient-type, $vendor);
}
// If variable is a string - Image or number
@else if ($type == string) or ($type == number) {
$border-image: append($border-image, nth($images, $i));
}
}
}
@return $border-image;
}
//Examples:
// @include border-image(url("image.png"));
// @include border-image(url("image.png") 20 stretch);
// @include border-image(linear-gradient(45deg, orange, yellow));
// @include border-image(linear-gradient(45deg, orange, yellow) stretch);
// @include border-image(linear-gradient(45deg, orange, yellow) 20 30 40 50 stretch round);
// @include border-image(radial-gradient(top, cover, orange, yellow, orange));
......@@ -17,6 +17,7 @@ div.leanModal_box {
@include box-sizing(border-box);
display: none;
padding: lh(2);
text-align: left;
a.modal_close {
color: #aaa;
......@@ -204,6 +205,35 @@ div#pwd_reset {
}
}
div#apply_name_change,
div#change_email,
div#unenroll,
div#deactivate-account {
max-width: 700px;
ul {
list-style: none;
li {
margin-bottom: lh(.5);
textarea, #{$all-text-inputs} {
display: block;
width: 100%;
@include box-sizing(border-box);
}
textarea {
height: 60px;
}
input[type="submit"] {
white-space: normal;
}
}
}
}
div#feedback_div{
form{
ol {
......
......@@ -8,19 +8,25 @@ import django.contrib.auth.views
# admin.autodiscover()
urlpatterns = ('',
url(r'^$', 'student.views.index'), # Main marketing page, or redirect to courseware
url(r'^change_email$', 'student.views.change_email_request'),
url(r'^email_confirm/(?P<key>[^/]*)$', 'student.views.confirm_email_change'),
url(r'^change_name$', 'student.views.change_name_request'),
url(r'^accept_name_change$', 'student.views.accept_name_change'),
url(r'^reject_name_change$', 'student.views.reject_name_change'),
url(r'^pending_name_changes$', 'student.views.pending_name_changes'),
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'^$', 'student.views.index'),
# url(r'^password_reset/$', 'django.contrib.auth.views.password_reset',
# dict(from_email='registration@mitx.mit.edu'),name='auth_password_reset'),
# url(r'^reactivate/(?P<key>[^/]*)$', 'student.views.reactivation_email'),
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,
......@@ -29,6 +35,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'),
)
......@@ -37,6 +44,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"),
......
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