Commit 72c200d42e977a83fe71914cb07d1bce9f7ffe87

Authored by Sergio Oliveira
1 parent 4b0394f3

multiple emails for each user

allowing users to add, remove and set addresses as primary in the
profile update view.
src/accounts/templates/accounts/user_update_form.html
1 {% extends "base.html" %} 1 {% extends "base.html" %}
2 {% load i18n gravatar %} 2 {% load i18n gravatar %}
3 3
  4 +{% block head_js %}
  5 +<script>
  6 +$(function() {
  7 + $('#add-email').on('click', function(event) {
  8 + $.ajax({
  9 + url: "{% url 'archive_email_view' %}",
  10 + type: 'post',
  11 + data: { email: $('#new_email').val() },
  12 + beforeSend: function(xhr, settings) {
  13 + xhr.setRequestHeader("X-CSRFToken", $.cookie('csrftoken'));
  14 + }
  15 + }).done(function() {
  16 + location.reload();
  17 + });
  18 +
  19 + event.preventDefault();
  20 + });
  21 +
  22 + $('#new_email').on('keypress', function(event) {
  23 + if (event.which == 13) {
  24 + event.preventDefault();
  25 + $('#add-email').trigger('click');
  26 + }
  27 + });
  28 +
  29 + $('.delete-email').on('click', function(event) {
  30 + var $email_block = $(event.target).parent().parent();
  31 + $.ajax({
  32 + url: "{% url 'archive_email_view' %}",
  33 + type: 'delete',
  34 + data: { email: $('.email-address', $email_block).text() },
  35 + context: $email_block[0],
  36 + beforeSend: function(xhr, settings) {
  37 + xhr.setRequestHeader("X-CSRFToken", $.cookie('csrftoken'));
  38 + }
  39 + }).done(function() {
  40 + $(this).remove();
  41 + });
  42 +
  43 + event.preventDefault();
  44 + });
  45 +
  46 + $('.set-primary').on('click', function(event) {
  47 + var $email_block = $(event.target).parent().parent();
  48 + $.ajax({
  49 + url: "{% url 'archive_email_view' %}",
  50 + type: 'update',
  51 + data: { email: $('.email-address', $email_block).text() },
  52 + beforeSend: function(xhr, settings) {
  53 + xhr.setRequestHeader("X-CSRFToken", $.cookie('csrftoken'));
  54 + }
  55 + }).done(function() {
  56 + location.reload();
  57 + });
  58 +
  59 + event.preventDefault();
  60 + });
  61 +
  62 +});
  63 +</script>
  64 +{% endblock %}
  65 +
  66 +
4 {% block main-content %} 67 {% block main-content %}
5 <form method="post"> 68 <form method="post">
6 {% csrf_token %} 69 {% csrf_token %}
@@ -14,19 +77,61 @@ @@ -14,19 +77,61 @@
14 <br> 77 <br>
15 78
16 <div class="row"> 79 <div class="row">
17 - {% for field in form %}  
18 - <div class="col-lg-4 col-md-6 col-sm-12 col-xm-12">  
19 - <div class="form-group{% if field.field.required %} required{% endif %}{% if field.errors %} alert alert-danger has-error{% endif %}">  
20 - <label for="{{ field.name }}" class="control-label">  
21 - {{ field.label }}  
22 - </label>  
23 - {{ field }}  
24 - {{ field.errors }} 80 + <div class="col-lg-8 col-md-7 col-sm-12 col-xm-12">
  81 + {% for field in form %}
  82 + <div class="col-lg-6 col-md-6 col-sm-12 col-xm-12">
  83 + <div class="form-group{% if field.field.required %} required{% endif %}{% if field.errors %} alert alert-danger has-error{% endif %}">
  84 + <label for="{{ field.name }}" class="control-label">
  85 + {{ field.label }}
  86 + </label>
  87 + {{ field }}
  88 + {{ field.errors }}
  89 + </div>
25 </div> 90 </div>
  91 + {% endfor %}
26 </div> 92 </div>
27 - {% endfor %}  
28 - </div>  
29 93
  94 + <div class="col-lg-4 col-md-5 col-sm-12 col-xm-12">
  95 + <div class="panel panel-default">
  96 + <div class="panel-heading">
  97 + <h3 class="panel-title">{% trans "Emails" %}</h3>
  98 + </div>
  99 + <div class="panel-body">
  100 + <ul class="unstyled-list email">
  101 + {% for email in user_.emails.iterator %}
  102 + <li>
  103 + <span class="email-address">{{ email.address }}</span>
  104 + {% if email.address == user_.email %}
  105 + <span class="label label-success">{% trans "Primary" %}</span>
  106 + {% else %}
  107 + <div class="text-right">
  108 + <button class="btn btn-default set-primary">{% trans "Set as Primary" %}</button>
  109 + <button class="btn btn-danger delete-email">{% trans "Delete" %}</button>
  110 + </div>
  111 + {% endif %}
  112 + <hr />
  113 + </li>
  114 + {% endfor %}
  115 + {% for email in user_.emails_not_validated.iterator %}
  116 + <li>
  117 + <span class="email-address">{{ email.address }}</span>
  118 + <span class="label label-warning">{% trans "Not verified" %}</span>
  119 + <div class="text-right">
  120 + <button class="btn btn-danger delete-email">{% trans "Delete" %}</button>
  121 + </div>
  122 + <hr />
  123 + </li>
  124 + {% endfor %}
  125 + </ul>
  126 + <div class="form-group">
  127 + <label for="new_email">{% trans "Add another email address:" %}</label>
  128 + <input id="new_email" name="new_email" class="form-control" autocomplete="off" />
  129 + </div>
  130 + <button class="btn btn-primary pull-right" id="add-email">{% trans "Add" %}</button>
  131 + </div>
  132 + </div>
  133 + </div>
  134 + </div>
30 <div class="row"> 135 <div class="row">
31 <div class="submit"> 136 <div class="submit">
32 <button type="submit" class="btn btn-primary btn-lg btn-block">{% trans "Update" %}</button> 137 <button type="submit" class="btn btn-primary btn-lg btn-block">{% trans "Update" %}</button>
src/super_archives/admin.py
1 1
2 from django.contrib import admin 2 from django.contrib import admin
3 -from .models import Message, Thread 3 +from .models import Message, Thread, EmailAddress
  4 +
  5 +
  6 +class EmailAddressAdmin(admin.ModelAdmin):
  7 + search_fields = (
  8 + 'address',
  9 + 'user__username',
  10 + 'user__first_name',
  11 + 'user__last_name',
  12 + )
4 13
5 class MessageAdmin(admin.ModelAdmin): 14 class MessageAdmin(admin.ModelAdmin):
6 list_filter = ('spam', 'thread__mailinglist', 'received_time', ) 15 list_filter = ('spam', 'thread__mailinglist', 'received_time', )
@@ -50,4 +59,5 @@ class ThreadAdmin(admin.ModelAdmin): @@ -50,4 +59,5 @@ class ThreadAdmin(admin.ModelAdmin):
50 59
51 admin.site.register(Thread, ThreadAdmin) 60 admin.site.register(Thread, ThreadAdmin)
52 admin.site.register(Message, MessageAdmin) 61 admin.site.register(Message, MessageAdmin)
  62 +admin.site.register(EmailAddress, EmailAddressAdmin)
53 63
src/super_archives/models.py
@@ -45,6 +45,12 @@ class EmailAddress(models.Model): @@ -45,6 +45,12 @@ class EmailAddress(models.Model):
45 self.md5 = md5(self.address).hexdigest() 45 self.md5 = md5(self.address).hexdigest()
46 super(EmailAddress, self).save(*args, **kwargs) 46 super(EmailAddress, self).save(*args, **kwargs)
47 47
  48 + def get_full_name(self):
  49 + if self.user and self.user.get_full_name():
  50 + return self.user.get_full_name()
  51 + else:
  52 + return self.real_name
  53 +
48 def get_full_name_or_anonymous(self): 54 def get_full_name_or_anonymous(self):
49 return self.get_full_name() or _('Anonymous') 55 return self.get_full_name() or _('Anonymous')
50 56
src/super_archives/urls.py
1 from django.conf.urls import patterns, include, url 1 from django.conf.urls import patterns, include, url
2 2
  3 +from .views import EmailView
  4 +
  5 +
3 urlpatterns = patterns('super_archives.views', 6 urlpatterns = patterns('super_archives.views',
4 # url(r'thread/(?P<thread>\d+)/$', 'thread', name='thread'), 7 # url(r'thread/(?P<thread>\d+)/$', 'thread', name='thread'),
5 url(r'thread/(?P<mailinglist>[-\w]+)/(?P<thread_token>[-\w]+)$', 'thread', 8 url(r'thread/(?P<mailinglist>[-\w]+)/(?P<thread_token>[-\w]+)$', 'thread',
6 name="thread_view"), 9 name="thread_view"),
7 - url(r'thread/$', 'list_messages', name='thread_list') 10 + url(r'thread/$', 'list_messages', name='thread_list'),
  11 + url(r'manage/email/(?P<key>[0-9a-z]{32})?', EmailView.as_view(),
  12 + name="archive_email_view"),
8 ) 13 )
src/super_archives/views.py
1 # -*- coding: utf-8 -*- 1 # -*- coding: utf-8 -*-
2 2
3 -import queries  
4 -  
5 -from django.http import Http404  
6 -from django.template import RequestContext 3 +from django import http
  4 +from django.contrib import messages
  5 +from django.db import IntegrityError
  6 +from django.views.generic import View
7 from django.core.paginator import Paginator 7 from django.core.paginator import Paginator
  8 +from django.utils.translation import ugettext as _
8 from django.core.exceptions import ObjectDoesNotExist 9 from django.core.exceptions import ObjectDoesNotExist
9 -from django.shortcuts import render, get_list_or_404 10 +from django.utils.decorators import method_decorator
  11 +from django.contrib.auth.decorators import login_required
  12 +from django.shortcuts import render, redirect
10 13
11 -from .models import MailingList, Thread 14 +from . import queries
12 from .decorators import count_hit 15 from .decorators import count_hit
  16 +from .models import MailingList, Thread, EmailAddress, EmailAddressValidation
13 17
14 18
15 @count_hit 19 @count_hit
16 def thread(request, mailinglist, thread_token): 20 def thread(request, mailinglist, thread_token):
17 21
18 try: 22 try:
19 - first_message = queries.get_first_message_in_thread(mailinglist, 23 + first_message = queries.get_first_message_in_thread(mailinglist,
20 thread_token) 24 thread_token)
21 except ObjectDoesNotExist: 25 except ObjectDoesNotExist:
22 - raise Http404 26 + raise http.Http404
23 order_by = request.GET.get('order') 27 order_by = request.GET.get('order')
24 if order_by == 'voted': 28 if order_by == 'voted':
25 msgs_query = queries.get_messages_by_voted() 29 msgs_query = queries.get_messages_by_voted()
@@ -80,3 +84,108 @@ def list_messages(request): @@ -80,3 +84,108 @@ def list_messages(request):
80 'order_by': order_by, 84 'order_by': order_by,
81 } 85 }
82 return render(request, 'message-list.html', template_data) 86 return render(request, 'message-list.html', template_data)
  87 +
  88 +
  89 +class EmailView(View):
  90 +
  91 + http_method_names = [u'head', u'get', u'post', u'delete', u'update']
  92 +
  93 + def get(self, request, key):
  94 + """Validate an email with the given key"""
  95 +
  96 + try:
  97 + email_val = EmailAddressValidation.objects.get(validation_key=key,
  98 + user=request.user)
  99 + except EmailAddressValidation.DoesNotExist:
  100 + messages.error(request, _('The email address you are trying to '
  101 + 'verify either has already been verified '
  102 + 'or does not exist.'))
  103 + return redirect('/')
  104 +
  105 + try:
  106 + email = EmailAddress.objects.get(address=email_val.address)
  107 + except EmailAddress.DoesNotExist:
  108 + email = EmailAddress(address=email_val.address)
  109 +
  110 + if email.user:
  111 + messages.error(request, _('The email address you are trying to '
  112 + 'verify is already an active email '
  113 + 'address.'))
  114 + email_val.delete()
  115 + return redirect('/')
  116 +
  117 + email.user = email_val.user
  118 + email.save()
  119 + email_val.delete()
  120 +
  121 + messages.success(request, _('Email address verified!'))
  122 + return redirect('user_profile', username=email_val.user.username)
  123 +
  124 +
  125 + @method_decorator(login_required)
  126 + def post(self, request, key):
  127 + """Create new email address that will wait for validation"""
  128 +
  129 + email = request.POST.get('email')
  130 + if not email:
  131 + return http.HttpResponseBadRequest()
  132 +
  133 + try:
  134 + EmailAddressValidation.objects.create(address=email,
  135 + user=request.user)
  136 + except IntegrityError:
  137 + # 409 Conflict
  138 + # duplicated entries
  139 + # email exist and it's waiting for validation
  140 + return http.HttpResponse(status=409)
  141 +
  142 + return http.HttpResponse(status=201)
  143 +
  144 + @method_decorator(login_required)
  145 + def delete(self, request, key):
  146 + """Remove an email address, validated or not."""
  147 +
  148 + request.DELETE = http.QueryDict(request.body)
  149 + email_addr = request.DELETE.get('email')
  150 +
  151 + if not email_addr:
  152 + return http.HttpResponseBadRequest()
  153 +
  154 + try:
  155 + email = EmailAddressValidation.objects.get(address=email_addr,
  156 + user=request.user)
  157 + except EmailAddressValidation.DoesNotExist:
  158 + pass
  159 + else:
  160 + email.delete()
  161 + return http.HttpResponse(status=204)
  162 +
  163 + try:
  164 + email = EmailAddress.objects.get(address=email_addr,
  165 + user=request.user)
  166 + except EmailAddress.DoesNotExist:
  167 + raise http.Http404
  168 +
  169 + email.user = None
  170 + email.save()
  171 + return http.HttpResponse(status=204)
  172 +
  173 + @method_decorator(login_required)
  174 + def update(self, request, key):
  175 + """Set an email address as primary address."""
  176 +
  177 + request.UPDATE = http.QueryDict(request.body)
  178 +
  179 + email_addr = request.UPDATE.get('email')
  180 + if not email_addr:
  181 + return http.HttpResponseBadRequest()
  182 +
  183 + try:
  184 + email = EmailAddress.objects.get(address=email_addr,
  185 + user=request.user)
  186 + except EmailAddress.DoesNotExist:
  187 + raise http.Http404
  188 +
  189 + request.user.email = email_addr
  190 + request.user.save()
  191 + return http.HttpResponse(status=204)