Commit 7d24a4b78f91c1c985ed4966a910f5b68d34befd

Authored by Luan
2 parents 139dbed9 869bf1c1

Merge branch 'master' into haystack

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  
... ...
src/accounts/management/__init__.py 0 → 100644
src/accounts/management/commands/__init__.py 0 → 100644
src/accounts/management/commands/delete_invalid.py 0 → 100644
... ... @@ -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>&nbsp;&nbsp;{% 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>&nbsp;&nbsp;{% 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&#39;Colab blog aggregator&#39;)
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 = &#39;superarchives/tags/&#39;
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 )
... ...
src/super_archives/utils.py 0 → 100644
... ... @@ -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)
... ...