Commit 72c200d42e977a83fe71914cb07d1bce9f7ffe87
1 parent
4b0394f3
Exists in
master
and in
39 other branches
multiple emails for each user
allowing users to add, remove and set addresses as primary in the profile update view.
Showing
5 changed files
with
255 additions
and
20 deletions
Show diff stats
src/accounts/templates/accounts/user_update_form.html
| 1 | 1 | {% extends "base.html" %} |
| 2 | 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 | 67 | {% block main-content %} |
| 5 | 68 | <form method="post"> |
| 6 | 69 | {% csrf_token %} |
| ... | ... | @@ -14,19 +77,61 @@ |
| 14 | 77 | <br> |
| 15 | 78 | |
| 16 | 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 | 90 | </div> |
| 91 | + {% endfor %} | |
| 26 | 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 | 135 | <div class="row"> |
| 31 | 136 | <div class="submit"> |
| 32 | 137 | <button type="submit" class="btn btn-primary btn-lg btn-block">{% trans "Update" %}</button> | ... | ... |
src/super_archives/admin.py
| 1 | 1 | |
| 2 | 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 | 14 | class MessageAdmin(admin.ModelAdmin): |
| 6 | 15 | list_filter = ('spam', 'thread__mailinglist', 'received_time', ) |
| ... | ... | @@ -50,4 +59,5 @@ class ThreadAdmin(admin.ModelAdmin): |
| 50 | 59 | |
| 51 | 60 | admin.site.register(Thread, ThreadAdmin) |
| 52 | 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 | 45 | self.md5 = md5(self.address).hexdigest() |
| 46 | 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 | 54 | def get_full_name_or_anonymous(self): |
| 49 | 55 | return self.get_full_name() or _('Anonymous') |
| 50 | 56 | ... | ... |
src/super_archives/urls.py
| 1 | 1 | from django.conf.urls import patterns, include, url |
| 2 | 2 | |
| 3 | +from .views import EmailView | |
| 4 | + | |
| 5 | + | |
| 3 | 6 | urlpatterns = patterns('super_archives.views', |
| 4 | 7 | # url(r'thread/(?P<thread>\d+)/$', 'thread', name='thread'), |
| 5 | 8 | url(r'thread/(?P<mailinglist>[-\w]+)/(?P<thread_token>[-\w]+)$', 'thread', |
| 6 | 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 | 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 | 7 | from django.core.paginator import Paginator |
| 8 | +from django.utils.translation import ugettext as _ | |
| 8 | 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 | 15 | from .decorators import count_hit |
| 16 | +from .models import MailingList, Thread, EmailAddress, EmailAddressValidation | |
| 13 | 17 | |
| 14 | 18 | |
| 15 | 19 | @count_hit |
| 16 | 20 | def thread(request, mailinglist, thread_token): |
| 17 | 21 | |
| 18 | 22 | try: |
| 19 | - first_message = queries.get_first_message_in_thread(mailinglist, | |
| 23 | + first_message = queries.get_first_message_in_thread(mailinglist, | |
| 20 | 24 | thread_token) |
| 21 | 25 | except ObjectDoesNotExist: |
| 22 | - raise Http404 | |
| 26 | + raise http.Http404 | |
| 23 | 27 | order_by = request.GET.get('order') |
| 24 | 28 | if order_by == 'voted': |
| 25 | 29 | msgs_query = queries.get_messages_by_voted() |
| ... | ... | @@ -80,3 +84,108 @@ def list_messages(request): |
| 80 | 84 | 'order_by': order_by, |
| 81 | 85 | } |
| 82 | 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) | ... | ... |