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) | ... | ... |