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 | 4 | class ColabBrowserIDBackend(BrowserIDBackend): |
5 | 5 | def filter_users_by_email(self, email): |
6 | 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 | 36 | class UserUpdateForm(UserForm): |
37 | 37 | class Meta: |
38 | 38 | model = User |
39 | - fields = ('username', 'first_name', 'last_name', 'email', | |
39 | + fields = ('username', 'first_name', 'last_name', | |
40 | 40 | 'institution', 'role', 'twitter', 'facebook', |
41 | 41 | 'google_talk', 'webpage') |
42 | 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 | 19 | <em>{{ user_.username }}</em> |
20 | 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 | 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 | 36 | <ul class="unstyled-list"> |
29 | 37 | {% if user_.institution or user_.role %} | ... | ... |
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 | + $('.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 | 90 | {% block main-content %} |
5 | 91 | <form method="post"> |
6 | 92 | {% csrf_token %} |
7 | 93 | |
8 | 94 | <div class="col-lg-12"> |
9 | 95 | <a href="https://gravatar.com" target="_blank"> |
10 | - {% gravatar request.user.email 50 %} | |
96 | + {% gravatar user_.email 50 %} | |
11 | 97 | {% trans "Change your avatar at Gravatar.com" %} |
12 | 98 | </a> |
13 | 99 | </div> |
14 | 100 | <br> |
15 | 101 | |
16 | 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 | 113 | </div> |
114 | + {% endfor %} | |
26 | 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 | 160 | <div class="row"> |
31 | 161 | <div class="submit"> |
32 | 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 | 8 | from django.utils.translation import ugettext as _ |
9 | 9 | from django.shortcuts import render, redirect |
10 | 10 | from django.core.urlresolvers import reverse |
11 | +from django.core.exceptions import PermissionDenied | |
11 | 12 | |
12 | 13 | from colab.deprecated import solrutils |
13 | 14 | from colab.deprecated import signup as signup_ |
... | ... | @@ -30,7 +31,12 @@ class UserProfileUpdateView(UserProfileBaseMixin, UpdateView): |
30 | 31 | def get_success_url(self): |
31 | 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 | 41 | class UserProfileDetailView(UserProfileBaseMixin, DetailView): |
36 | 42 | template_name = 'accounts/user_detail.html' | ... | ... |
src/colab/custom_settings.py
... | ... | @@ -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 | 140 | TEMPLATE_CONTEXT_PROCESSORS = ( |
141 | 141 | 'django.contrib.auth.context_processors.auth', |
... | ... | @@ -209,6 +209,7 @@ FEEDZILLA_SITE_DESCRIPTION = gettext(u'Colab blog aggregator') |
209 | 209 | ### BrowserID / Persona |
210 | 210 | SITE_URL = 'https://colab.interlegis.leg.br' |
211 | 211 | |
212 | +LOGIN_URL = '/' | |
212 | 213 | LOGIN_REDIRECT_URL = '/' |
213 | 214 | LOGIN_REDIRECT_URL_FAILURE = '/' |
214 | 215 | LOGOUT_REDIRECT_URL = '/' | ... | ... |
src/static/css/screen.css
... | ... | @@ -3,6 +3,10 @@ body { |
3 | 3 | padding-top: 57px; |
4 | 4 | } |
5 | 5 | |
6 | +li hr { | |
7 | + margin: 10px 0; | |
8 | +} | |
9 | + | |
6 | 10 | /* Header */ |
7 | 11 | |
8 | 12 | #header-searchbox { |
... | ... | @@ -348,3 +352,20 @@ ul.unstyled-list .glyphicon-chevron-right { |
348 | 352 | } |
349 | 353 | |
350 | 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 | 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/migrations/0014_auto__chg_field_emailaddressvalidation_validation_key.py
0 → 100644
... | ... | @@ -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 | 139 | \ No newline at end of file | ... | ... |
src/super_archives/migrations/0015_auto__chg_field_emailaddress_user.py
0 → 100644
... | ... | @@ -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 | 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 @@ |
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 | 139 | \ No newline at end of file | ... | ... |
src/super_archives/models.py
... | ... | @@ -30,17 +30,24 @@ class EmailAddressValidation(models.Model): |
30 | 30 | address = models.EmailField(unique=True) |
31 | 31 | user = models.ForeignKey(User, null=True, |
32 | 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 | 34 | default=lambda: uuid4().hex) |
35 | 35 | created = models.DateTimeField(auto_now_add=True) |
36 | 36 | |
37 | + class Meta: | |
38 | + unique_together = ('user', 'address') | |
39 | + | |
37 | 40 | |
38 | 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 | 44 | address = models.EmailField(unique=True) |
41 | 45 | real_name = models.CharField(max_length=64, blank=True, db_index=True) |
42 | 46 | md5 = models.CharField(max_length=32, null=True) |
43 | 47 | |
48 | + class Meta: | |
49 | + ordering = ('id', ) | |
50 | + | |
44 | 51 | def save(self, *args, **kwargs): |
45 | 52 | self.md5 = md5(self.address).hexdigest() |
46 | 53 | super(EmailAddress, self).save(*args, **kwargs) |
... | ... | @@ -48,7 +55,7 @@ class EmailAddress(models.Model): |
48 | 55 | def get_full_name(self): |
49 | 56 | if self.user and self.user.get_full_name(): |
50 | 57 | return self.user.get_full_name() |
51 | - elif self.real_name: | |
58 | + else: | |
52 | 59 | return self.real_name |
53 | 60 | |
54 | 61 | def get_full_name_or_anonymous(self): | ... | ... |
src/super_archives/templates/superarchives/emails/email_verification.txt
0 → 100644
... | ... | @@ -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 | 4 | {% for message, class in messages %} |
5 | 5 | {% if class == 'reply' %} |
6 | 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 | 8 | {% endfor %} |
9 | 9 | {% endcache %} | ... | ... |
src/super_archives/templatetags/superarchives.py
... | ... | @@ -14,15 +14,27 @@ TEMPLATE_PATH = 'superarchives/tags/' |
14 | 14 | EXTENDED_PUNCTUATION = '!"#$%&\'()*+,-./:;=?@[\\]^_`{|}~ \t\n\r\x0b\x0c' |
15 | 15 | RE_WRAPPED_BY_HTML = re.compile(r'^<[a-z]+[^>]*>.*</[a-z]+[^>]*>$', |
16 | 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 | 29 | def join(block): |
20 | 30 | block_txt = u''.join(block) |
21 | 31 | |
22 | 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 | 39 | def is_reply(line, message, thread): |
28 | 40 | clean_line = line.strip() |
... | ... | @@ -41,8 +53,7 @@ def is_reply(line, message, thread): |
41 | 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 | 57 | def display_message(email, thread): |
47 | 58 | message = email.body |
48 | 59 | messages = [] | ... | ... |
src/super_archives/urls.py
1 | 1 | from django.conf.urls import patterns, include, url |
2 | 2 | |
3 | +from .views import EmailView, EmailValidationView | |
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/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 @@ |
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 | 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 | 9 | from django.core.paginator import Paginator |
10 | +from django.utils.translation import ugettext as _ | |
8 | 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 | 18 | from .decorators import count_hit |
19 | +from .models import MailingList, Thread, EmailAddress, EmailAddressValidation | |
13 | 20 | |
14 | 21 | |
15 | 22 | @count_hit |
16 | 23 | def thread(request, mailinglist, thread_token): |
17 | 24 | |
18 | 25 | try: |
19 | - first_message = queries.get_first_message_in_thread(mailinglist, | |
26 | + first_message = queries.get_first_message_in_thread(mailinglist, | |
20 | 27 | thread_token) |
21 | 28 | except ObjectDoesNotExist: |
22 | - raise Http404 | |
29 | + raise http.Http404 | |
23 | 30 | order_by = request.GET.get('order') |
24 | 31 | if order_by == 'voted': |
25 | 32 | msgs_query = queries.get_messages_by_voted() |
... | ... | @@ -80,3 +87,129 @@ def list_messages(request): |
80 | 87 | 'order_by': order_by, |
81 | 88 | } |
82 | 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) | ... | ... |