Commit 7d24a4b78f91c1c985ed4966a910f5b68d34befd
Exists in
master
and in
39 other branches
Merge branch 'master' into haystack
Showing
21 changed files
with
849 additions
and
37 deletions
Show diff stats
src/accounts/auth.py
@@ -4,6 +4,3 @@ from django_browserid.auth import BrowserIDBackend | @@ -4,6 +4,3 @@ from django_browserid.auth import BrowserIDBackend | ||
4 | class ColabBrowserIDBackend(BrowserIDBackend): | 4 | class ColabBrowserIDBackend(BrowserIDBackend): |
5 | def filter_users_by_email(self, email): | 5 | def filter_users_by_email(self, email): |
6 | return self.User.objects.filter(emails__address=email) | 6 | return self.User.objects.filter(emails__address=email) |
7 | - | ||
8 | - def authenticate(self, *args, **kw): | ||
9 | - return super(ColabBrowserIDBackend, self).authenticate(*args, **kw) |
src/accounts/forms.py
@@ -36,7 +36,7 @@ class UserCreationForm(UserForm): | @@ -36,7 +36,7 @@ class UserCreationForm(UserForm): | ||
36 | class UserUpdateForm(UserForm): | 36 | class UserUpdateForm(UserForm): |
37 | class Meta: | 37 | class Meta: |
38 | model = User | 38 | model = User |
39 | - fields = ('username', 'first_name', 'last_name', 'email', | 39 | + fields = ('username', 'first_name', 'last_name', |
40 | 'institution', 'role', 'twitter', 'facebook', | 40 | 'institution', 'role', 'twitter', 'facebook', |
41 | 'google_talk', 'webpage') | 41 | 'google_talk', 'webpage') |
42 | 42 |
@@ -0,0 +1,42 @@ | @@ -0,0 +1,42 @@ | ||
1 | + | ||
2 | + | ||
3 | +from django.db.models import F | ||
4 | +from django.utils import timezone | ||
5 | +from django.utils.translation import ugettext as _ | ||
6 | +from django.core.management.base import BaseCommand, CommandError | ||
7 | + | ||
8 | + | ||
9 | +from ...models import User | ||
10 | + | ||
11 | + | ||
12 | +class Command(BaseCommand): | ||
13 | + """Delete user accounts that have never logged in. | ||
14 | + | ||
15 | + Delete from database user accounts that have never logged in | ||
16 | + and are at least 24h older. | ||
17 | + | ||
18 | + """ | ||
19 | + | ||
20 | + help = __doc__ | ||
21 | + | ||
22 | + def handle(self, *args, **kwargs): | ||
23 | + seconds = timezone.timedelta(seconds=1) | ||
24 | + now = timezone.now() | ||
25 | + one_day_ago = timezone.timedelta(days=1) | ||
26 | + | ||
27 | + # Query for users that have NEVER logged in | ||
28 | + # | ||
29 | + # By default django sets the last_login as auto_now and then | ||
30 | + # last_login is pretty much the same than date_joined | ||
31 | + # (instead of null as I expected). Because of that we query | ||
32 | + # for users which last_login is between date_joined - N and | ||
33 | + # date_joined + N, where N is a small constant in seconds. | ||
34 | + users = User.objects.filter(last_login__gt=(F('date_joined') - seconds), | ||
35 | + last_login__lt=(F('date_joined') + seconds), | ||
36 | + date_joined__lt=now-one_day_ago) | ||
37 | + count = 0 | ||
38 | + for user in users: | ||
39 | + count += 1 | ||
40 | + user.delete() | ||
41 | + | ||
42 | + print _(u'%(count)s users deleted.') % {'count': count} |
src/accounts/templates/accounts/user_detail.html
@@ -19,11 +19,19 @@ | @@ -19,11 +19,19 @@ | ||
19 | <em>{{ user_.username }}</em> | 19 | <em>{{ user_.username }}</em> |
20 | </h1> | 20 | </h1> |
21 | 21 | ||
22 | - {% ifequal request.user user_ %} | ||
23 | - <a class="btn btn-info" href="{% url 'user_profile_update' user_ %}"><span class="glyphicon glyphicon-pencil"></span> {% trans "update your profile"|title %}</a> | ||
24 | - {% endifequal %} | 22 | + {% if request.user == user_ or request.user.is_superuser %} |
23 | + <a class="btn btn-info" href="{% url 'user_profile_update' user_ %}"><span class="glyphicon glyphicon-pencil"></span> {% trans "edit profile"|title %}</a> | ||
24 | + {% endif %} | ||
25 | 25 | ||
26 | <div class="divider"></div> | 26 | <div class="divider"></div> |
27 | + {% if request.user.is_active %} | ||
28 | + <ul class="unstyled-list"> | ||
29 | + {% for email in user_.emails.iterator %} | ||
30 | + <li><span class="icon-envelope icon-fixed-width"></span> {{ email.address }}</li> | ||
31 | + {% endfor %} | ||
32 | + </ul> | ||
33 | + <div class="divider"></div> | ||
34 | + {% endif %} | ||
27 | 35 | ||
28 | <ul class="unstyled-list"> | 36 | <ul class="unstyled-list"> |
29 | {% if user_.institution or user_.role %} | 37 | {% if user_.institution or user_.role %} |
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 | + $('.verify-email').on('click', function(event) { | ||
47 | + var $email_block = $(event.target).parent().parent(); | ||
48 | + $.ajax({ | ||
49 | + url: "{% url 'archive_email_validation_view' %}", | ||
50 | + type: 'post', | ||
51 | + data: { email: $('.email-address', $email_block).text() }, | ||
52 | + context: $email_block[0], | ||
53 | + beforeSend: function(xhr, settings) { | ||
54 | + xhr.setRequestHeader("X-CSRFToken", $.cookie('csrftoken')); | ||
55 | + } | ||
56 | + }).done(function() { | ||
57 | + var email = $('.email-address', $(this)).text(); | ||
58 | + var msg = '{% trans "We sent a verification email to " %}' + email + '. ' + | ||
59 | + '{% trans "Please follow the instructions in it." %}'; | ||
60 | + $('#alert-message').text(msg); | ||
61 | + $('#alert-js').removeClass('alert-warning').addClass('alert-success'); | ||
62 | + $('#alert-js').show(); | ||
63 | + window.scroll(0, 0); | ||
64 | + }); | ||
65 | + | ||
66 | + event.preventDefault(); | ||
67 | + }); | ||
68 | + | ||
69 | + $('.set-primary').on('click', function(event) { | ||
70 | + var $email_block = $(event.target).parent().parent(); | ||
71 | + $.ajax({ | ||
72 | + url: "{% url 'archive_email_view' %}", | ||
73 | + type: 'update', | ||
74 | + data: { email: $('.email-address', $email_block).text() }, | ||
75 | + beforeSend: function(xhr, settings) { | ||
76 | + xhr.setRequestHeader("X-CSRFToken", $.cookie('csrftoken')); | ||
77 | + } | ||
78 | + }).done(function() { | ||
79 | + location.reload(); | ||
80 | + }); | ||
81 | + | ||
82 | + event.preventDefault(); | ||
83 | + }); | ||
84 | + | ||
85 | +}); | ||
86 | +</script> | ||
87 | +{% endblock %} | ||
88 | + | ||
89 | + | ||
4 | {% block main-content %} | 90 | {% block main-content %} |
5 | <form method="post"> | 91 | <form method="post"> |
6 | {% csrf_token %} | 92 | {% csrf_token %} |
7 | 93 | ||
8 | <div class="col-lg-12"> | 94 | <div class="col-lg-12"> |
9 | <a href="https://gravatar.com" target="_blank"> | 95 | <a href="https://gravatar.com" target="_blank"> |
10 | - {% gravatar request.user.email 50 %} | 96 | + {% gravatar user_.email 50 %} |
11 | {% trans "Change your avatar at Gravatar.com" %} | 97 | {% trans "Change your avatar at Gravatar.com" %} |
12 | </a> | 98 | </a> |
13 | </div> | 99 | </div> |
14 | <br> | 100 | <br> |
15 | 101 | ||
16 | <div class="row"> | 102 | <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 }} | 103 | + <div class="col-lg-8 col-md-7 col-sm-12 col-xm-12"> |
104 | + {% for field in form %} | ||
105 | + <div class="col-lg-6 col-md-6 col-sm-12 col-xm-12"> | ||
106 | + <div class="form-group{% if field.field.required %} required{% endif %}{% if field.errors %} alert alert-danger has-error{% endif %}"> | ||
107 | + <label for="{{ field.name }}" class="control-label"> | ||
108 | + {{ field.label }} | ||
109 | + </label> | ||
110 | + {{ field }} | ||
111 | + {{ field.errors }} | ||
112 | + </div> | ||
25 | </div> | 113 | </div> |
114 | + {% endfor %} | ||
26 | </div> | 115 | </div> |
27 | - {% endfor %} | ||
28 | - </div> | ||
29 | 116 | ||
117 | + <div class="col-lg-4 col-md-5 col-sm-12 col-xm-12"> | ||
118 | + <div class="panel panel-default"> | ||
119 | + <div class="panel-heading"> | ||
120 | + <h3 class="panel-title">{% trans "Emails" %}</h3> | ||
121 | + </div> | ||
122 | + <div class="panel-body"> | ||
123 | + <ul class="unstyled-list emails"> | ||
124 | + {% for email in user_.emails.iterator %} | ||
125 | + <li> | ||
126 | + {% gravatar user_.email 30 %} | ||
127 | + <span class="email-address">{{ email.address }}</span> | ||
128 | + {% if email.address == user_.email %} | ||
129 | + <span class="label label-success">{% trans "Primary" %}</span> | ||
130 | + {% else %} | ||
131 | + <div class="text-right"> | ||
132 | + <button class="btn btn-default set-primary">{% trans "Set as Primary" %}</button> | ||
133 | + <button class="btn btn-danger delete-email">{% trans "Delete" %}</button> | ||
134 | + </div> | ||
135 | + {% endif %} | ||
136 | + <hr /> | ||
137 | + </li> | ||
138 | + {% endfor %} | ||
139 | + {% for email in user_.emails_not_validated.iterator %} | ||
140 | + <li> | ||
141 | + {% gravatar user_.email 30 %} | ||
142 | + <span class="email-address">{{ email.address }}</span> | ||
143 | + <div class="text-right"> | ||
144 | + <button class="btn btn-default verify-email"><span class="icon-warning-sign"></span> {% trans "Verify" %}</button> | ||
145 | + <button class="btn btn-danger delete-email">{% trans "Delete" %}</button> | ||
146 | + </div> | ||
147 | + <hr /> | ||
148 | + </li> | ||
149 | + {% endfor %} | ||
150 | + </ul> | ||
151 | + <div class="form-group"> | ||
152 | + <label for="new_email">{% trans "Add another email address:" %}</label> | ||
153 | + <input id="new_email" name="new_email" class="form-control" autocomplete="off" /> | ||
154 | + </div> | ||
155 | + <button class="btn btn-primary pull-right" id="add-email">{% trans "Add" %}</button> | ||
156 | + </div> | ||
157 | + </div> | ||
158 | + </div> | ||
159 | + </div> | ||
30 | <div class="row"> | 160 | <div class="row"> |
31 | <div class="submit"> | 161 | <div class="submit"> |
32 | <button type="submit" class="btn btn-primary btn-lg btn-block">{% trans "Update" %}</button> | 162 | <button type="submit" class="btn btn-primary btn-lg btn-block">{% trans "Update" %}</button> |
src/accounts/views.py
@@ -8,6 +8,7 @@ from django.views.generic import DetailView, UpdateView | @@ -8,6 +8,7 @@ from django.views.generic import DetailView, UpdateView | ||
8 | from django.utils.translation import ugettext as _ | 8 | from django.utils.translation import ugettext as _ |
9 | from django.shortcuts import render, redirect | 9 | from django.shortcuts import render, redirect |
10 | from django.core.urlresolvers import reverse | 10 | from django.core.urlresolvers import reverse |
11 | +from django.core.exceptions import PermissionDenied | ||
11 | 12 | ||
12 | from colab.deprecated import solrutils | 13 | from colab.deprecated import solrutils |
13 | from colab.deprecated import signup as signup_ | 14 | from colab.deprecated import signup as signup_ |
@@ -30,7 +31,12 @@ class UserProfileUpdateView(UserProfileBaseMixin, UpdateView): | @@ -30,7 +31,12 @@ class UserProfileUpdateView(UserProfileBaseMixin, UpdateView): | ||
30 | def get_success_url(self): | 31 | def get_success_url(self): |
31 | return reverse('user_profile', kwargs={'username': self.object.username}) | 32 | return reverse('user_profile', kwargs={'username': self.object.username}) |
32 | 33 | ||
34 | + def get_object(self, *args, **kwargs): | ||
35 | + obj = super(UserProfileUpdateView, self).get_object(*args, **kwargs) | ||
36 | + if self.request.user != obj and not self.request.user.is_superuser: | ||
37 | + raise PermissionDenied | ||
33 | 38 | ||
39 | + return obj | ||
34 | 40 | ||
35 | class UserProfileDetailView(UserProfileBaseMixin, DetailView): | 41 | class UserProfileDetailView(UserProfileBaseMixin, DetailView): |
36 | template_name = 'accounts/user_detail.html' | 42 | template_name = 'accounts/user_detail.html' |
src/colab/custom_settings.py
@@ -134,8 +134,8 @@ LOGGING = { | @@ -134,8 +134,8 @@ LOGGING = { | ||
134 | } | 134 | } |
135 | } | 135 | } |
136 | 136 | ||
137 | -SERVER_EMAIL = '"Colab Interlegis" <noreply@interlegis.leg.br>' | ||
138 | -EMAIL_HOST_USER = SERVER_EMAIL | 137 | +COLAB_FROM_ADDRESS = '"Colab Interlegis" <noreply@interlegis.leg.br>' |
138 | +SERVER_EMAIL = EMAIL_HOST_USER = COLAB_FROM_ADDRESS | ||
139 | 139 | ||
140 | TEMPLATE_CONTEXT_PROCESSORS = ( | 140 | TEMPLATE_CONTEXT_PROCESSORS = ( |
141 | 'django.contrib.auth.context_processors.auth', | 141 | 'django.contrib.auth.context_processors.auth', |
@@ -209,6 +209,7 @@ FEEDZILLA_SITE_DESCRIPTION = gettext(u'Colab blog aggregator') | @@ -209,6 +209,7 @@ FEEDZILLA_SITE_DESCRIPTION = gettext(u'Colab blog aggregator') | ||
209 | ### BrowserID / Persona | 209 | ### BrowserID / Persona |
210 | SITE_URL = 'https://colab.interlegis.leg.br' | 210 | SITE_URL = 'https://colab.interlegis.leg.br' |
211 | 211 | ||
212 | +LOGIN_URL = '/' | ||
212 | LOGIN_REDIRECT_URL = '/' | 213 | LOGIN_REDIRECT_URL = '/' |
213 | LOGIN_REDIRECT_URL_FAILURE = '/' | 214 | LOGIN_REDIRECT_URL_FAILURE = '/' |
214 | LOGOUT_REDIRECT_URL = '/' | 215 | LOGOUT_REDIRECT_URL = '/' |
src/static/css/screen.css
@@ -3,6 +3,10 @@ body { | @@ -3,6 +3,10 @@ body { | ||
3 | padding-top: 57px; | 3 | padding-top: 57px; |
4 | } | 4 | } |
5 | 5 | ||
6 | +li hr { | ||
7 | + margin: 10px 0; | ||
8 | +} | ||
9 | + | ||
6 | /* Header */ | 10 | /* Header */ |
7 | 11 | ||
8 | #header-searchbox { | 12 | #header-searchbox { |
@@ -348,3 +352,20 @@ ul.unstyled-list .glyphicon-chevron-right { | @@ -348,3 +352,20 @@ ul.unstyled-list .glyphicon-chevron-right { | ||
348 | } | 352 | } |
349 | 353 | ||
350 | /* end Feedzilla */ | 354 | /* end Feedzilla */ |
355 | + | ||
356 | +/* user profile update */ | ||
357 | + | ||
358 | +.btn.delete-email:not(:hover) { | ||
359 | + background-color: #fff; | ||
360 | + color: #D9534F; | ||
361 | +} | ||
362 | + | ||
363 | +.btn .icon-warning-sign { | ||
364 | + color: #F0AD4E; | ||
365 | +} | ||
366 | + | ||
367 | +ul.emails { | ||
368 | + margin: 0; | ||
369 | +} | ||
370 | + | ||
371 | +/* end user profile update */ |
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/migrations/0014_auto__chg_field_emailaddressvalidation_validation_key.py
0 → 100644
@@ -0,0 +1,138 @@ | @@ -0,0 +1,138 @@ | ||
1 | +# -*- coding: utf-8 -*- | ||
2 | +import datetime | ||
3 | +from south.db import db | ||
4 | +from south.v2 import SchemaMigration | ||
5 | +from django.db import models | ||
6 | + | ||
7 | + | ||
8 | +class Migration(SchemaMigration): | ||
9 | + | ||
10 | + def forwards(self, orm): | ||
11 | + | ||
12 | + # Changing field 'EmailAddressValidation.validation_key' | ||
13 | + db.alter_column(u'super_archives_emailaddressvalidation', 'validation_key', self.gf('django.db.models.fields.CharField')(max_length=32, null=True)) | ||
14 | + | ||
15 | + def backwards(self, orm): | ||
16 | + | ||
17 | + # Changing field 'EmailAddressValidation.validation_key' | ||
18 | + db.alter_column(u'super_archives_emailaddressvalidation', 'validation_key', self.gf('django.db.models.fields.CharField')(max_length=32)) | ||
19 | + | ||
20 | + models = { | ||
21 | + u'accounts.user': { | ||
22 | + 'Meta': {'object_name': 'User'}, | ||
23 | + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), | ||
24 | + 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}), | ||
25 | + 'facebook': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), | ||
26 | + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), | ||
27 | + 'google_talk': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}), | ||
28 | + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), | ||
29 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
30 | + 'institution': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), | ||
31 | + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), | ||
32 | + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), | ||
33 | + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), | ||
34 | + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), | ||
35 | + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), | ||
36 | + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), | ||
37 | + 'role': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), | ||
38 | + 'twitter': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), | ||
39 | + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), | ||
40 | + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}), | ||
41 | + 'verification_hash': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}), | ||
42 | + 'webpage': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True', 'blank': 'True'}) | ||
43 | + }, | ||
44 | + u'auth.group': { | ||
45 | + 'Meta': {'object_name': 'Group'}, | ||
46 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
47 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), | ||
48 | + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) | ||
49 | + }, | ||
50 | + u'auth.permission': { | ||
51 | + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, | ||
52 | + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), | ||
53 | + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), | ||
54 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
55 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) | ||
56 | + }, | ||
57 | + u'contenttypes.contenttype': { | ||
58 | + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, | ||
59 | + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), | ||
60 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
61 | + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), | ||
62 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) | ||
63 | + }, | ||
64 | + u'super_archives.emailaddress': { | ||
65 | + 'Meta': {'object_name': 'EmailAddress'}, | ||
66 | + 'address': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75'}), | ||
67 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
68 | + 'md5': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}), | ||
69 | + 'real_name': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '64', 'blank': 'True'}), | ||
70 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'emails'", 'null': 'True', 'to': u"orm['accounts.User']"}) | ||
71 | + }, | ||
72 | + u'super_archives.emailaddressvalidation': { | ||
73 | + 'Meta': {'object_name': 'EmailAddressValidation'}, | ||
74 | + 'address': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75'}), | ||
75 | + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), | ||
76 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
77 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'emails_not_validated'", 'null': 'True', 'to': u"orm['accounts.User']"}), | ||
78 | + 'validation_key': ('django.db.models.fields.CharField', [], {'default': "'30a2065c72c1402986a11e7c5369f5a5'", 'max_length': '32', 'null': 'True'}) | ||
79 | + }, | ||
80 | + u'super_archives.mailinglist': { | ||
81 | + 'Meta': {'object_name': 'MailingList'}, | ||
82 | + 'description': ('django.db.models.fields.TextField', [], {}), | ||
83 | + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), | ||
84 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
85 | + 'last_imported_index': ('django.db.models.fields.IntegerField', [], {'default': '0'}), | ||
86 | + 'logo': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), | ||
87 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}) | ||
88 | + }, | ||
89 | + u'super_archives.mailinglistmembership': { | ||
90 | + 'Meta': {'object_name': 'MailingListMembership'}, | ||
91 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
92 | + 'mailinglist': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['super_archives.MailingList']"}), | ||
93 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['accounts.User']"}) | ||
94 | + }, | ||
95 | + u'super_archives.message': { | ||
96 | + 'Meta': {'unique_together': "(('thread', 'message_id'),)", 'object_name': 'Message'}, | ||
97 | + 'body': ('django.db.models.fields.TextField', [], {'default': "''"}), | ||
98 | + 'from_address': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['super_archives.EmailAddress']"}), | ||
99 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
100 | + 'message_id': ('django.db.models.fields.CharField', [], {'max_length': '512'}), | ||
101 | + 'received_time': ('django.db.models.fields.DateTimeField', [], {}), | ||
102 | + 'spam': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), | ||
103 | + 'subject': ('django.db.models.fields.CharField', [], {'max_length': '512', 'db_index': 'True'}), | ||
104 | + 'subject_clean': ('django.db.models.fields.CharField', [], {'max_length': '512', 'db_index': 'True'}), | ||
105 | + 'thread': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['super_archives.Thread']", 'null': 'True'}) | ||
106 | + }, | ||
107 | + u'super_archives.messagemetadata': { | ||
108 | + 'Message': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['super_archives.Message']"}), | ||
109 | + 'Meta': {'object_name': 'MessageMetadata'}, | ||
110 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
111 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), | ||
112 | + 'value': ('django.db.models.fields.TextField', [], {}) | ||
113 | + }, | ||
114 | + u'super_archives.pagehit': { | ||
115 | + 'Meta': {'object_name': 'PageHit'}, | ||
116 | + 'hit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), | ||
117 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
118 | + 'url_path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '2048', 'db_index': 'True'}) | ||
119 | + }, | ||
120 | + u'super_archives.thread': { | ||
121 | + 'Meta': {'unique_together': "(('subject_token', 'mailinglist'),)", 'object_name': 'Thread'}, | ||
122 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
123 | + 'latest_message': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'+'", 'unique': 'True', 'null': 'True', 'to': u"orm['super_archives.Message']"}), | ||
124 | + 'mailinglist': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['super_archives.MailingList']"}), | ||
125 | + 'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}), | ||
126 | + 'spam': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), | ||
127 | + 'subject_token': ('django.db.models.fields.CharField', [], {'max_length': '512'}) | ||
128 | + }, | ||
129 | + u'super_archives.vote': { | ||
130 | + 'Meta': {'unique_together': "(('user', 'message'),)", 'object_name': 'Vote'}, | ||
131 | + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), | ||
132 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
133 | + 'message': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['super_archives.Message']"}), | ||
134 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['accounts.User']"}) | ||
135 | + } | ||
136 | + } | ||
137 | + | ||
138 | + complete_apps = ['super_archives'] | ||
0 | \ No newline at end of file | 139 | \ No newline at end of file |
src/super_archives/migrations/0015_auto__chg_field_emailaddress_user.py
0 → 100644
@@ -0,0 +1,138 @@ | @@ -0,0 +1,138 @@ | ||
1 | +# -*- coding: utf-8 -*- | ||
2 | +import datetime | ||
3 | +from south.db import db | ||
4 | +from south.v2 import SchemaMigration | ||
5 | +from django.db import models | ||
6 | + | ||
7 | + | ||
8 | +class Migration(SchemaMigration): | ||
9 | + | ||
10 | + def forwards(self, orm): | ||
11 | + | ||
12 | + # Changing field 'EmailAddress.user' | ||
13 | + db.alter_column(u'super_archives_emailaddress', 'user_id', self.gf('django.db.models.fields.related.ForeignKey')(null=True, on_delete=models.SET_NULL, to=orm['accounts.User'])) | ||
14 | + | ||
15 | + def backwards(self, orm): | ||
16 | + | ||
17 | + # Changing field 'EmailAddress.user' | ||
18 | + db.alter_column(u'super_archives_emailaddress', 'user_id', self.gf('django.db.models.fields.related.ForeignKey')(null=True, to=orm['accounts.User'])) | ||
19 | + | ||
20 | + models = { | ||
21 | + u'accounts.user': { | ||
22 | + 'Meta': {'object_name': 'User'}, | ||
23 | + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), | ||
24 | + 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}), | ||
25 | + 'facebook': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), | ||
26 | + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), | ||
27 | + 'google_talk': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}), | ||
28 | + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), | ||
29 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
30 | + 'institution': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), | ||
31 | + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), | ||
32 | + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), | ||
33 | + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), | ||
34 | + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), | ||
35 | + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), | ||
36 | + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), | ||
37 | + 'role': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), | ||
38 | + 'twitter': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), | ||
39 | + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), | ||
40 | + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}), | ||
41 | + 'verification_hash': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}), | ||
42 | + 'webpage': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True', 'blank': 'True'}) | ||
43 | + }, | ||
44 | + u'auth.group': { | ||
45 | + 'Meta': {'object_name': 'Group'}, | ||
46 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
47 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), | ||
48 | + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) | ||
49 | + }, | ||
50 | + u'auth.permission': { | ||
51 | + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, | ||
52 | + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), | ||
53 | + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), | ||
54 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
55 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) | ||
56 | + }, | ||
57 | + u'contenttypes.contenttype': { | ||
58 | + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, | ||
59 | + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), | ||
60 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
61 | + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), | ||
62 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) | ||
63 | + }, | ||
64 | + u'super_archives.emailaddress': { | ||
65 | + 'Meta': {'ordering': "('id',)", 'object_name': 'EmailAddress'}, | ||
66 | + 'address': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75'}), | ||
67 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
68 | + 'md5': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}), | ||
69 | + 'real_name': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '64', 'blank': 'True'}), | ||
70 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'emails'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['accounts.User']"}) | ||
71 | + }, | ||
72 | + u'super_archives.emailaddressvalidation': { | ||
73 | + 'Meta': {'object_name': 'EmailAddressValidation'}, | ||
74 | + 'address': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75'}), | ||
75 | + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), | ||
76 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
77 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'emails_not_validated'", 'null': 'True', 'to': u"orm['accounts.User']"}), | ||
78 | + 'validation_key': ('django.db.models.fields.CharField', [], {'default': "'a9cfbe23d1be41e4beeafa7790325e26'", 'max_length': '32', 'null': 'True'}) | ||
79 | + }, | ||
80 | + u'super_archives.mailinglist': { | ||
81 | + 'Meta': {'object_name': 'MailingList'}, | ||
82 | + 'description': ('django.db.models.fields.TextField', [], {}), | ||
83 | + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), | ||
84 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
85 | + 'last_imported_index': ('django.db.models.fields.IntegerField', [], {'default': '0'}), | ||
86 | + 'logo': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), | ||
87 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}) | ||
88 | + }, | ||
89 | + u'super_archives.mailinglistmembership': { | ||
90 | + 'Meta': {'object_name': 'MailingListMembership'}, | ||
91 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
92 | + 'mailinglist': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['super_archives.MailingList']"}), | ||
93 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['accounts.User']"}) | ||
94 | + }, | ||
95 | + u'super_archives.message': { | ||
96 | + 'Meta': {'unique_together': "(('thread', 'message_id'),)", 'object_name': 'Message'}, | ||
97 | + 'body': ('django.db.models.fields.TextField', [], {'default': "''"}), | ||
98 | + 'from_address': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['super_archives.EmailAddress']"}), | ||
99 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
100 | + 'message_id': ('django.db.models.fields.CharField', [], {'max_length': '512'}), | ||
101 | + 'received_time': ('django.db.models.fields.DateTimeField', [], {}), | ||
102 | + 'spam': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), | ||
103 | + 'subject': ('django.db.models.fields.CharField', [], {'max_length': '512', 'db_index': 'True'}), | ||
104 | + 'subject_clean': ('django.db.models.fields.CharField', [], {'max_length': '512', 'db_index': 'True'}), | ||
105 | + 'thread': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['super_archives.Thread']", 'null': 'True'}) | ||
106 | + }, | ||
107 | + u'super_archives.messagemetadata': { | ||
108 | + 'Message': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['super_archives.Message']"}), | ||
109 | + 'Meta': {'object_name': 'MessageMetadata'}, | ||
110 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
111 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), | ||
112 | + 'value': ('django.db.models.fields.TextField', [], {}) | ||
113 | + }, | ||
114 | + u'super_archives.pagehit': { | ||
115 | + 'Meta': {'object_name': 'PageHit'}, | ||
116 | + 'hit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), | ||
117 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
118 | + 'url_path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '2048', 'db_index': 'True'}) | ||
119 | + }, | ||
120 | + u'super_archives.thread': { | ||
121 | + 'Meta': {'unique_together': "(('subject_token', 'mailinglist'),)", 'object_name': 'Thread'}, | ||
122 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
123 | + 'latest_message': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'+'", 'unique': 'True', 'null': 'True', 'to': u"orm['super_archives.Message']"}), | ||
124 | + 'mailinglist': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['super_archives.MailingList']"}), | ||
125 | + 'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}), | ||
126 | + 'spam': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), | ||
127 | + 'subject_token': ('django.db.models.fields.CharField', [], {'max_length': '512'}) | ||
128 | + }, | ||
129 | + u'super_archives.vote': { | ||
130 | + 'Meta': {'unique_together': "(('user', 'message'),)", 'object_name': 'Vote'}, | ||
131 | + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), | ||
132 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
133 | + 'message': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['super_archives.Message']"}), | ||
134 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['accounts.User']"}) | ||
135 | + } | ||
136 | + } | ||
137 | + | ||
138 | + complete_apps = ['super_archives'] | ||
0 | \ No newline at end of file | 139 | \ No newline at end of file |
src/super_archives/migrations/0016_auto__add_unique_emailaddressvalidation_user_address.py
0 → 100644
@@ -0,0 +1,138 @@ | @@ -0,0 +1,138 @@ | ||
1 | +# -*- coding: utf-8 -*- | ||
2 | +import datetime | ||
3 | +from south.db import db | ||
4 | +from south.v2 import SchemaMigration | ||
5 | +from django.db import models | ||
6 | + | ||
7 | + | ||
8 | +class Migration(SchemaMigration): | ||
9 | + | ||
10 | + def forwards(self, orm): | ||
11 | + # Adding unique constraint on 'EmailAddressValidation', fields ['user', 'address'] | ||
12 | + db.create_unique(u'super_archives_emailaddressvalidation', ['user_id', 'address']) | ||
13 | + | ||
14 | + | ||
15 | + def backwards(self, orm): | ||
16 | + # Removing unique constraint on 'EmailAddressValidation', fields ['user', 'address'] | ||
17 | + db.delete_unique(u'super_archives_emailaddressvalidation', ['user_id', 'address']) | ||
18 | + | ||
19 | + | ||
20 | + models = { | ||
21 | + u'accounts.user': { | ||
22 | + 'Meta': {'object_name': 'User'}, | ||
23 | + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), | ||
24 | + 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}), | ||
25 | + 'facebook': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), | ||
26 | + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), | ||
27 | + 'google_talk': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}), | ||
28 | + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), | ||
29 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
30 | + 'institution': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), | ||
31 | + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), | ||
32 | + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), | ||
33 | + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), | ||
34 | + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), | ||
35 | + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), | ||
36 | + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), | ||
37 | + 'role': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), | ||
38 | + 'twitter': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), | ||
39 | + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), | ||
40 | + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}), | ||
41 | + 'verification_hash': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}), | ||
42 | + 'webpage': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True', 'blank': 'True'}) | ||
43 | + }, | ||
44 | + u'auth.group': { | ||
45 | + 'Meta': {'object_name': 'Group'}, | ||
46 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
47 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), | ||
48 | + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) | ||
49 | + }, | ||
50 | + u'auth.permission': { | ||
51 | + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, | ||
52 | + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), | ||
53 | + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), | ||
54 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
55 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) | ||
56 | + }, | ||
57 | + u'contenttypes.contenttype': { | ||
58 | + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, | ||
59 | + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), | ||
60 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
61 | + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), | ||
62 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) | ||
63 | + }, | ||
64 | + u'super_archives.emailaddress': { | ||
65 | + 'Meta': {'ordering': "('id',)", 'object_name': 'EmailAddress'}, | ||
66 | + 'address': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75'}), | ||
67 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
68 | + 'md5': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}), | ||
69 | + 'real_name': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '64', 'blank': 'True'}), | ||
70 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'emails'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['accounts.User']"}) | ||
71 | + }, | ||
72 | + u'super_archives.emailaddressvalidation': { | ||
73 | + 'Meta': {'unique_together': "(('user', 'address'),)", 'object_name': 'EmailAddressValidation'}, | ||
74 | + 'address': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75'}), | ||
75 | + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), | ||
76 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
77 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'emails_not_validated'", 'null': 'True', 'to': u"orm['accounts.User']"}), | ||
78 | + 'validation_key': ('django.db.models.fields.CharField', [], {'default': "'cfda46852e7e4e4a9e34a57c3d3b2330'", 'max_length': '32', 'null': 'True'}) | ||
79 | + }, | ||
80 | + u'super_archives.mailinglist': { | ||
81 | + 'Meta': {'object_name': 'MailingList'}, | ||
82 | + 'description': ('django.db.models.fields.TextField', [], {}), | ||
83 | + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), | ||
84 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
85 | + 'last_imported_index': ('django.db.models.fields.IntegerField', [], {'default': '0'}), | ||
86 | + 'logo': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), | ||
87 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}) | ||
88 | + }, | ||
89 | + u'super_archives.mailinglistmembership': { | ||
90 | + 'Meta': {'object_name': 'MailingListMembership'}, | ||
91 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
92 | + 'mailinglist': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['super_archives.MailingList']"}), | ||
93 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['accounts.User']"}) | ||
94 | + }, | ||
95 | + u'super_archives.message': { | ||
96 | + 'Meta': {'unique_together': "(('thread', 'message_id'),)", 'object_name': 'Message'}, | ||
97 | + 'body': ('django.db.models.fields.TextField', [], {'default': "''"}), | ||
98 | + 'from_address': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['super_archives.EmailAddress']"}), | ||
99 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
100 | + 'message_id': ('django.db.models.fields.CharField', [], {'max_length': '512'}), | ||
101 | + 'received_time': ('django.db.models.fields.DateTimeField', [], {}), | ||
102 | + 'spam': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), | ||
103 | + 'subject': ('django.db.models.fields.CharField', [], {'max_length': '512', 'db_index': 'True'}), | ||
104 | + 'subject_clean': ('django.db.models.fields.CharField', [], {'max_length': '512', 'db_index': 'True'}), | ||
105 | + 'thread': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['super_archives.Thread']", 'null': 'True'}) | ||
106 | + }, | ||
107 | + u'super_archives.messagemetadata': { | ||
108 | + 'Message': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['super_archives.Message']"}), | ||
109 | + 'Meta': {'object_name': 'MessageMetadata'}, | ||
110 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
111 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), | ||
112 | + 'value': ('django.db.models.fields.TextField', [], {}) | ||
113 | + }, | ||
114 | + u'super_archives.pagehit': { | ||
115 | + 'Meta': {'object_name': 'PageHit'}, | ||
116 | + 'hit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), | ||
117 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
118 | + 'url_path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '2048', 'db_index': 'True'}) | ||
119 | + }, | ||
120 | + u'super_archives.thread': { | ||
121 | + 'Meta': {'unique_together': "(('subject_token', 'mailinglist'),)", 'object_name': 'Thread'}, | ||
122 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
123 | + 'latest_message': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'+'", 'unique': 'True', 'null': 'True', 'to': u"orm['super_archives.Message']"}), | ||
124 | + 'mailinglist': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['super_archives.MailingList']"}), | ||
125 | + 'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}), | ||
126 | + 'spam': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), | ||
127 | + 'subject_token': ('django.db.models.fields.CharField', [], {'max_length': '512'}) | ||
128 | + }, | ||
129 | + u'super_archives.vote': { | ||
130 | + 'Meta': {'unique_together': "(('user', 'message'),)", 'object_name': 'Vote'}, | ||
131 | + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), | ||
132 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
133 | + 'message': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['super_archives.Message']"}), | ||
134 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['accounts.User']"}) | ||
135 | + } | ||
136 | + } | ||
137 | + | ||
138 | + complete_apps = ['super_archives'] | ||
0 | \ No newline at end of file | 139 | \ No newline at end of file |
src/super_archives/models.py
@@ -30,17 +30,24 @@ class EmailAddressValidation(models.Model): | @@ -30,17 +30,24 @@ class EmailAddressValidation(models.Model): | ||
30 | address = models.EmailField(unique=True) | 30 | address = models.EmailField(unique=True) |
31 | user = models.ForeignKey(User, null=True, | 31 | user = models.ForeignKey(User, null=True, |
32 | related_name='emails_not_validated') | 32 | related_name='emails_not_validated') |
33 | - validation_key = models.CharField(max_length=32, | 33 | + validation_key = models.CharField(max_length=32, null=True, |
34 | default=lambda: uuid4().hex) | 34 | default=lambda: uuid4().hex) |
35 | created = models.DateTimeField(auto_now_add=True) | 35 | created = models.DateTimeField(auto_now_add=True) |
36 | 36 | ||
37 | + class Meta: | ||
38 | + unique_together = ('user', 'address') | ||
39 | + | ||
37 | 40 | ||
38 | class EmailAddress(models.Model): | 41 | class EmailAddress(models.Model): |
39 | - user = models.ForeignKey(User, null=True, related_name='emails') | 42 | + user = models.ForeignKey(User, null=True, related_name='emails', |
43 | + on_delete=models.SET_NULL) | ||
40 | address = models.EmailField(unique=True) | 44 | address = models.EmailField(unique=True) |
41 | real_name = models.CharField(max_length=64, blank=True, db_index=True) | 45 | real_name = models.CharField(max_length=64, blank=True, db_index=True) |
42 | md5 = models.CharField(max_length=32, null=True) | 46 | md5 = models.CharField(max_length=32, null=True) |
43 | 47 | ||
48 | + class Meta: | ||
49 | + ordering = ('id', ) | ||
50 | + | ||
44 | def save(self, *args, **kwargs): | 51 | def save(self, *args, **kwargs): |
45 | self.md5 = md5(self.address).hexdigest() | 52 | self.md5 = md5(self.address).hexdigest() |
46 | super(EmailAddress, self).save(*args, **kwargs) | 53 | super(EmailAddress, self).save(*args, **kwargs) |
@@ -48,7 +55,7 @@ class EmailAddress(models.Model): | @@ -48,7 +55,7 @@ class EmailAddress(models.Model): | ||
48 | def get_full_name(self): | 55 | def get_full_name(self): |
49 | if self.user and self.user.get_full_name(): | 56 | if self.user and self.user.get_full_name(): |
50 | return self.user.get_full_name() | 57 | return self.user.get_full_name() |
51 | - elif self.real_name: | 58 | + else: |
52 | return self.real_name | 59 | return self.real_name |
53 | 60 | ||
54 | def get_full_name_or_anonymous(self): | 61 | def get_full_name_or_anonymous(self): |
src/super_archives/templates/superarchives/emails/email_verification.txt
0 → 100644
@@ -0,0 +1,6 @@ | @@ -0,0 +1,6 @@ | ||
1 | +{% load i18n %} | ||
2 | +{% blocktrans with fullname=user.get_full_name|title username=user.username|lower %}Hey, we want to verify that you are indeed "{{ fullname }} ({{ username }})". If that's the case, please follow the link below:{% endblocktrans %} | ||
3 | + | ||
4 | +{{ SITE_URL }}{% url 'archive_email_view' key %} | ||
5 | + | ||
6 | +{% blocktrans with username=user.username %}If you're not {{ username }} or didn't request verification you can ignore this email.{% endblocktrans %} |
src/super_archives/templates/superarchives/tags/display_message.html
@@ -4,6 +4,6 @@ | @@ -4,6 +4,6 @@ | ||
4 | {% for message, class in messages %} | 4 | {% for message, class in messages %} |
5 | {% if class == 'reply' %} | 5 | {% if class == 'reply' %} |
6 | <button class="btn btn-info btn-xs" onclick="$(this).next().toggle();">...</button> | 6 | <button class="btn btn-info btn-xs" onclick="$(this).next().toggle();">...</button> |
7 | - <pre style="display: none; color: #707";>{% else %}<pre>{% endif %}{{ message }}</pre> | 7 | + <div style="display: none; color: #707";>{% else %}<div>{% endif %}{{ message|safe|linebreaksbr }}</div> |
8 | {% endfor %} | 8 | {% endfor %} |
9 | {% endcache %} | 9 | {% endcache %} |
src/super_archives/templatetags/superarchives.py
@@ -14,15 +14,27 @@ TEMPLATE_PATH = 'superarchives/tags/' | @@ -14,15 +14,27 @@ TEMPLATE_PATH = 'superarchives/tags/' | ||
14 | EXTENDED_PUNCTUATION = '!"#$%&\'()*+,-./:;=?@[\\]^_`{|}~ \t\n\r\x0b\x0c' | 14 | EXTENDED_PUNCTUATION = '!"#$%&\'()*+,-./:;=?@[\\]^_`{|}~ \t\n\r\x0b\x0c' |
15 | RE_WRAPPED_BY_HTML = re.compile(r'^<[a-z]+[^>]*>.*</[a-z]+[^>]*>$', | 15 | RE_WRAPPED_BY_HTML = re.compile(r'^<[a-z]+[^>]*>.*</[a-z]+[^>]*>$', |
16 | re.MULTILINE|re.IGNORECASE|re.DOTALL) | 16 | re.MULTILINE|re.IGNORECASE|re.DOTALL) |
17 | +RE_LINKS = re.compile(r'(?P<link>https?://[^ \t\r\n\<]+)') | ||
18 | + | ||
19 | + | ||
20 | +def find_links(block): | ||
21 | + links = RE_LINKS.finditer(block) | ||
22 | + | ||
23 | + block, n = RE_LINKS.subn(r'<a target="_blank" href="\g<link>">\g<link></a>', | ||
24 | + block) | ||
25 | + | ||
26 | + return block | ||
17 | 27 | ||
18 | 28 | ||
19 | def join(block): | 29 | def join(block): |
20 | block_txt = u''.join(block) | 30 | block_txt = u''.join(block) |
21 | 31 | ||
22 | if RE_WRAPPED_BY_HTML.match(block_txt.strip()): | 32 | if RE_WRAPPED_BY_HTML.match(block_txt.strip()): |
23 | - return html2text(block_txt) | 33 | + block = html2text(block_txt) |
34 | + else: | ||
35 | + block = block_txt | ||
24 | 36 | ||
25 | - return block_txt | 37 | + return find_links(block) |
26 | 38 | ||
27 | def is_reply(line, message, thread): | 39 | def is_reply(line, message, thread): |
28 | clean_line = line.strip() | 40 | clean_line = line.strip() |
@@ -41,8 +53,7 @@ def is_reply(line, message, thread): | @@ -41,8 +53,7 @@ def is_reply(line, message, thread): | ||
41 | return False | 53 | return False |
42 | 54 | ||
43 | 55 | ||
44 | -@register.inclusion_tag(TEMPLATE_PATH + 'display_message.html', | ||
45 | - takes_context=False) | 56 | +@register.inclusion_tag(TEMPLATE_PATH + 'display_message.html') |
46 | def display_message(email, thread): | 57 | def display_message(email, thread): |
47 | message = email.body | 58 | message = email.body |
48 | messages = [] | 59 | messages = [] |
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, EmailValidationView | ||
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/validate/?$', EmailValidationView.as_view(), | ||
12 | + name="archive_email_validation_view"), | ||
13 | + url(r'manage/email/(?P<key>[0-9a-z]{32})?', EmailView.as_view(), | ||
14 | + name="archive_email_view"), | ||
8 | ) | 15 | ) |
@@ -0,0 +1,19 @@ | @@ -0,0 +1,19 @@ | ||
1 | + | ||
2 | +from django.core import mail | ||
3 | +from django.conf import settings | ||
4 | +from django.template import Context, loader | ||
5 | +from django.utils.translation import ugettext as _ | ||
6 | + | ||
7 | + | ||
8 | +def colab_send_email(subject, message, to): | ||
9 | + from_email = settings.COLAB_FROM_ADDRESS | ||
10 | + return mail.send_mail(subject, message, from_email, [to]) | ||
11 | + | ||
12 | + | ||
13 | +def send_verification_email(to, user, validation_key): | ||
14 | + subject = _('Please verify your email ') + u'{}'.format(to) | ||
15 | + msg_tmpl = loader.get_template('superarchives/emails/email_verification.txt') | ||
16 | + message = msg_tmpl.render(Context({'to': to, 'user': user, | ||
17 | + 'key': validation_key, | ||
18 | + 'SITE_URL': settings.SITE_URL})) | ||
19 | + return colab_send_email(subject, message, to) |
src/super_archives/views.py
1 | # -*- coding: utf-8 -*- | 1 | # -*- coding: utf-8 -*- |
2 | 2 | ||
3 | -import queries | 3 | +import smtplib |
4 | 4 | ||
5 | -from django.http import Http404 | ||
6 | -from django.template import RequestContext | 5 | +from django import http |
6 | +from django.contrib import messages | ||
7 | +from django.db import IntegrityError | ||
8 | +from django.views.generic import View | ||
7 | from django.core.paginator import Paginator | 9 | from django.core.paginator import Paginator |
10 | +from django.utils.translation import ugettext as _ | ||
8 | from django.core.exceptions import ObjectDoesNotExist | 11 | from django.core.exceptions import ObjectDoesNotExist |
9 | -from django.shortcuts import render, get_list_or_404 | 12 | +from django.utils.decorators import method_decorator |
13 | +from django.contrib.auth.decorators import login_required | ||
14 | +from django.shortcuts import render, redirect | ||
10 | 15 | ||
11 | -from .models import MailingList, Thread | 16 | +from . import queries |
17 | +from .utils import send_verification_email | ||
12 | from .decorators import count_hit | 18 | from .decorators import count_hit |
19 | +from .models import MailingList, Thread, EmailAddress, EmailAddressValidation | ||
13 | 20 | ||
14 | 21 | ||
15 | @count_hit | 22 | @count_hit |
16 | def thread(request, mailinglist, thread_token): | 23 | def thread(request, mailinglist, thread_token): |
17 | 24 | ||
18 | try: | 25 | try: |
19 | - first_message = queries.get_first_message_in_thread(mailinglist, | 26 | + first_message = queries.get_first_message_in_thread(mailinglist, |
20 | thread_token) | 27 | thread_token) |
21 | except ObjectDoesNotExist: | 28 | except ObjectDoesNotExist: |
22 | - raise Http404 | 29 | + raise http.Http404 |
23 | order_by = request.GET.get('order') | 30 | order_by = request.GET.get('order') |
24 | if order_by == 'voted': | 31 | if order_by == 'voted': |
25 | msgs_query = queries.get_messages_by_voted() | 32 | msgs_query = queries.get_messages_by_voted() |
@@ -80,3 +87,129 @@ def list_messages(request): | @@ -80,3 +87,129 @@ def list_messages(request): | ||
80 | 'order_by': order_by, | 87 | 'order_by': order_by, |
81 | } | 88 | } |
82 | return render(request, 'message-list.html', template_data) | 89 | return render(request, 'message-list.html', template_data) |
90 | + | ||
91 | + | ||
92 | +class EmailView(View): | ||
93 | + | ||
94 | + http_method_names = [u'head', u'get', u'post', u'delete', u'update'] | ||
95 | + | ||
96 | + def get(self, request, key): | ||
97 | + """Validate an email with the given key""" | ||
98 | + | ||
99 | + try: | ||
100 | + email_val = EmailAddressValidation.objects.get(validation_key=key, | ||
101 | + user__pk=request.user.pk) | ||
102 | + except EmailAddressValidation.DoesNotExist: | ||
103 | + messages.error(request, _('The email address you are trying to ' | ||
104 | + 'verify either has already been verified ' | ||
105 | + 'or does not exist.')) | ||
106 | + return redirect('/') | ||
107 | + | ||
108 | + try: | ||
109 | + email = EmailAddress.objects.get(address=email_val.address) | ||
110 | + except EmailAddress.DoesNotExist: | ||
111 | + email = EmailAddress(address=email_val.address) | ||
112 | + | ||
113 | + if email.user: | ||
114 | + messages.error(request, _('The email address you are trying to ' | ||
115 | + 'verify is already an active email ' | ||
116 | + 'address.')) | ||
117 | + email_val.delete() | ||
118 | + return redirect('/') | ||
119 | + | ||
120 | + email.user = email_val.user | ||
121 | + email.save() | ||
122 | + email_val.delete() | ||
123 | + | ||
124 | + messages.success(request, _('Email address verified!')) | ||
125 | + return redirect('user_profile', username=email_val.user.username) | ||
126 | + | ||
127 | + | ||
128 | + @method_decorator(login_required) | ||
129 | + def post(self, request, key): | ||
130 | + """Create new email address that will wait for validation""" | ||
131 | + | ||
132 | + email = request.POST.get('email') | ||
133 | + if not email: | ||
134 | + return http.HttpResponseBadRequest() | ||
135 | + | ||
136 | + try: | ||
137 | + EmailAddressValidation.objects.create(address=email, | ||
138 | + user=request.user) | ||
139 | + except IntegrityError: | ||
140 | + # 409 Conflict | ||
141 | + # duplicated entries | ||
142 | + # email exist and it's waiting for validation | ||
143 | + return http.HttpResponse(status=409) | ||
144 | + | ||
145 | + return http.HttpResponse(status=201) | ||
146 | + | ||
147 | + @method_decorator(login_required) | ||
148 | + def delete(self, request, key): | ||
149 | + """Remove an email address, validated or not.""" | ||
150 | + | ||
151 | + request.DELETE = http.QueryDict(request.body) | ||
152 | + email_addr = request.DELETE.get('email') | ||
153 | + | ||
154 | + if not email_addr: | ||
155 | + return http.HttpResponseBadRequest() | ||
156 | + | ||
157 | + try: | ||
158 | + email = EmailAddressValidation.objects.get(address=email_addr, | ||
159 | + user=request.user) | ||
160 | + except EmailAddressValidation.DoesNotExist: | ||
161 | + pass | ||
162 | + else: | ||
163 | + email.delete() | ||
164 | + return http.HttpResponse(status=204) | ||
165 | + | ||
166 | + try: | ||
167 | + email = EmailAddress.objects.get(address=email_addr, | ||
168 | + user=request.user) | ||
169 | + except EmailAddress.DoesNotExist: | ||
170 | + raise http.Http404 | ||
171 | + | ||
172 | + email.user = None | ||
173 | + email.save() | ||
174 | + return http.HttpResponse(status=204) | ||
175 | + | ||
176 | + @method_decorator(login_required) | ||
177 | + def update(self, request, key): | ||
178 | + """Set an email address as primary address.""" | ||
179 | + | ||
180 | + request.UPDATE = http.QueryDict(request.body) | ||
181 | + | ||
182 | + email_addr = request.UPDATE.get('email') | ||
183 | + if not email_addr: | ||
184 | + return http.HttpResponseBadRequest() | ||
185 | + | ||
186 | + try: | ||
187 | + email = EmailAddress.objects.get(address=email_addr, | ||
188 | + user=request.user) | ||
189 | + except EmailAddress.DoesNotExist: | ||
190 | + raise http.Http404 | ||
191 | + | ||
192 | + request.user.email = email_addr | ||
193 | + request.user.save() | ||
194 | + return http.HttpResponse(status=204) | ||
195 | + | ||
196 | + | ||
197 | +class EmailValidationView(View): | ||
198 | + | ||
199 | + http_method_names = [u'post'] | ||
200 | + | ||
201 | + def post(self, request): | ||
202 | + email_addr = request.POST.get('email') | ||
203 | + try: | ||
204 | + email = EmailAddressValidation.objects.get(address=email_addr, | ||
205 | + user=request.user) | ||
206 | + except http.DoesNotExist: | ||
207 | + raise http.Http404 | ||
208 | + | ||
209 | + try: | ||
210 | + send_verification_email(email_addr, request.user, | ||
211 | + email.validation_key) | ||
212 | + except smtplib.SMTPException: | ||
213 | + return http.HttpResponseServerError() | ||
214 | + | ||
215 | + return http.HttpResponse(status=204) |