Commit 435ccefcd9379390e8aef891e2c21e4ec39affcd

Authored by Sergio Oliveira
1 parent 0c8eb2d2

Moving find blocks to utils

requirements.txt
... ... @@ -8,6 +8,7 @@ chardet==1.0.1
8 8 python-dateutil==1.5
9 9 django-cliauth==0.9
10 10 django-mobile==0.3.0
  11 +etiquetando==0.1
11 12 html2text
12 13  
13 14 gunicorn
... ...
src/static/css/screen.css
... ... @@ -335,6 +335,11 @@ ul.unstyled-list .glyphicon-chevron-right {
335 335 margin-top: 0;
336 336 }
337 337  
  338 +.tag-cloud {
  339 + text-align: justify;
  340 + margin-top: 6px;
  341 +}
  342 +
338 343 .tag-cloud .size-1 {
339 344 font-size: 0.9em;
340 345 }
... ...
src/super_archives/migrations/0017_auto__add_keyword.py 0 → 100644
... ... @@ -0,0 +1,151 @@
  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 model 'Keyword'
  12 + db.create_table(u'super_archives_keyword', (
  13 + (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
  14 + ('keyword', self.gf('django.db.models.fields.CharField')(max_length='128')),
  15 + ('weight', self.gf('django.db.models.fields.IntegerField')(default=0)),
  16 + ('thread', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['super_archives.Thread'])),
  17 + ))
  18 + db.send_create_signal(u'super_archives', ['Keyword'])
  19 +
  20 +
  21 + def backwards(self, orm):
  22 + # Deleting model 'Keyword'
  23 + db.delete_table(u'super_archives_keyword')
  24 +
  25 +
  26 + models = {
  27 + u'accounts.user': {
  28 + 'Meta': {'object_name': 'User'},
  29 + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
  30 + 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}),
  31 + 'facebook': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
  32 + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
  33 + 'google_talk': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}),
  34 + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
  35 + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  36 + 'institution': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
  37 + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
  38 + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
  39 + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
  40 + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
  41 + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
  42 + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
  43 + 'role': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
  44 + 'twitter': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
  45 + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
  46 + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}),
  47 + 'verification_hash': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
  48 + 'webpage': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True', 'blank': 'True'})
  49 + },
  50 + u'auth.group': {
  51 + 'Meta': {'object_name': 'Group'},
  52 + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  53 + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
  54 + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
  55 + },
  56 + u'auth.permission': {
  57 + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
  58 + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
  59 + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
  60 + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  61 + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
  62 + },
  63 + u'contenttypes.contenttype': {
  64 + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
  65 + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
  66 + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  67 + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
  68 + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
  69 + },
  70 + u'super_archives.emailaddress': {
  71 + 'Meta': {'ordering': "('id',)", 'object_name': 'EmailAddress'},
  72 + 'address': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75'}),
  73 + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  74 + 'md5': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
  75 + 'real_name': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '64', 'blank': 'True'}),
  76 + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'emails'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['accounts.User']"})
  77 + },
  78 + u'super_archives.emailaddressvalidation': {
  79 + 'Meta': {'unique_together': "(('user', 'address'),)", 'object_name': 'EmailAddressValidation'},
  80 + 'address': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75'}),
  81 + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
  82 + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  83 + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'emails_not_validated'", 'null': 'True', 'to': u"orm['accounts.User']"}),
  84 + 'validation_key': ('django.db.models.fields.CharField', [], {'default': "'e2d18f1f8d6445fc91c04d17acdbe23a'", 'max_length': '32', 'null': 'True'})
  85 + },
  86 + u'super_archives.keyword': {
  87 + 'Meta': {'object_name': 'Keyword'},
  88 + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  89 + 'keyword': ('django.db.models.fields.CharField', [], {'max_length': "'128'"}),
  90 + 'thread': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['super_archives.Thread']"}),
  91 + 'weight': ('django.db.models.fields.IntegerField', [], {'default': '0'})
  92 + },
  93 + u'super_archives.mailinglist': {
  94 + 'Meta': {'object_name': 'MailingList'},
  95 + 'description': ('django.db.models.fields.TextField', [], {}),
  96 + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}),
  97 + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  98 + 'last_imported_index': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
  99 + 'logo': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
  100 + 'name': ('django.db.models.fields.CharField', [], {'max_length': '80'})
  101 + },
  102 + u'super_archives.mailinglistmembership': {
  103 + 'Meta': {'object_name': 'MailingListMembership'},
  104 + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  105 + 'mailinglist': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['super_archives.MailingList']"}),
  106 + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['accounts.User']"})
  107 + },
  108 + u'super_archives.message': {
  109 + 'Meta': {'ordering': "('received_time',)", 'unique_together': "(('thread', 'message_id'),)", 'object_name': 'Message'},
  110 + 'body': ('django.db.models.fields.TextField', [], {'default': "''"}),
  111 + 'from_address': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['super_archives.EmailAddress']"}),
  112 + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  113 + 'message_id': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
  114 + 'received_time': ('django.db.models.fields.DateTimeField', [], {}),
  115 + 'spam': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
  116 + 'subject': ('django.db.models.fields.CharField', [], {'max_length': '512', 'db_index': 'True'}),
  117 + 'subject_clean': ('django.db.models.fields.CharField', [], {'max_length': '512', 'db_index': 'True'}),
  118 + 'thread': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['super_archives.Thread']", 'null': 'True'})
  119 + },
  120 + u'super_archives.messagemetadata': {
  121 + 'Message': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['super_archives.Message']"}),
  122 + 'Meta': {'object_name': 'MessageMetadata'},
  123 + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  124 + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
  125 + 'value': ('django.db.models.fields.TextField', [], {})
  126 + },
  127 + u'super_archives.pagehit': {
  128 + 'Meta': {'object_name': 'PageHit'},
  129 + 'hit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
  130 + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  131 + 'url_path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '2048', 'db_index': 'True'})
  132 + },
  133 + u'super_archives.thread': {
  134 + 'Meta': {'unique_together': "(('subject_token', 'mailinglist'),)", 'object_name': 'Thread'},
  135 + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  136 + 'latest_message': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'+'", 'unique': 'True', 'null': 'True', 'to': u"orm['super_archives.Message']"}),
  137 + 'mailinglist': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['super_archives.MailingList']"}),
  138 + 'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
  139 + 'spam': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
  140 + 'subject_token': ('django.db.models.fields.CharField', [], {'max_length': '512'})
  141 + },
  142 + u'super_archives.vote': {
  143 + 'Meta': {'unique_together': "(('user', 'message'),)", 'object_name': 'Vote'},
  144 + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
  145 + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  146 + 'message': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['super_archives.Message']"}),
  147 + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['accounts.User']"})
  148 + }
  149 + }
  150 +
  151 + complete_apps = ['super_archives']
0 152 \ No newline at end of file
... ...
src/super_archives/migrations/0018_auto__add_index_message_received_time.py 0 → 100644
... ... @@ -0,0 +1,145 @@
  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 index on 'Message', fields ['received_time']
  12 + db.create_index(u'super_archives_message', ['received_time'])
  13 +
  14 +
  15 + def backwards(self, orm):
  16 + # Removing index on 'Message', fields ['received_time']
  17 + db.delete_index(u'super_archives_message', ['received_time'])
  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': "'1775e2bdc5e341a9b9b8a6346eec1024'", 'max_length': '32', 'null': 'True'})
  79 + },
  80 + u'super_archives.keyword': {
  81 + 'Meta': {'ordering': "('?',)", 'object_name': 'Keyword'},
  82 + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  83 + 'keyword': ('django.db.models.fields.CharField', [], {'max_length': "'128'"}),
  84 + 'thread': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['super_archives.Thread']"}),
  85 + 'weight': ('django.db.models.fields.IntegerField', [], {'default': '0'})
  86 + },
  87 + u'super_archives.mailinglist': {
  88 + 'Meta': {'object_name': 'MailingList'},
  89 + 'description': ('django.db.models.fields.TextField', [], {}),
  90 + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}),
  91 + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  92 + 'last_imported_index': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
  93 + 'logo': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
  94 + 'name': ('django.db.models.fields.CharField', [], {'max_length': '80'})
  95 + },
  96 + u'super_archives.mailinglistmembership': {
  97 + 'Meta': {'object_name': 'MailingListMembership'},
  98 + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  99 + 'mailinglist': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['super_archives.MailingList']"}),
  100 + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['accounts.User']"})
  101 + },
  102 + u'super_archives.message': {
  103 + 'Meta': {'ordering': "('received_time',)", 'unique_together': "(('thread', 'message_id'),)", 'object_name': 'Message'},
  104 + 'body': ('django.db.models.fields.TextField', [], {'default': "''"}),
  105 + 'from_address': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['super_archives.EmailAddress']"}),
  106 + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  107 + 'message_id': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
  108 + 'received_time': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
  109 + 'spam': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
  110 + 'subject': ('django.db.models.fields.CharField', [], {'max_length': '512', 'db_index': 'True'}),
  111 + 'subject_clean': ('django.db.models.fields.CharField', [], {'max_length': '512', 'db_index': 'True'}),
  112 + 'thread': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['super_archives.Thread']", 'null': 'True'})
  113 + },
  114 + u'super_archives.messagemetadata': {
  115 + 'Message': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['super_archives.Message']"}),
  116 + 'Meta': {'object_name': 'MessageMetadata'},
  117 + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  118 + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
  119 + 'value': ('django.db.models.fields.TextField', [], {})
  120 + },
  121 + u'super_archives.pagehit': {
  122 + 'Meta': {'object_name': 'PageHit'},
  123 + 'hit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
  124 + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  125 + 'url_path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '2048', 'db_index': 'True'})
  126 + },
  127 + u'super_archives.thread': {
  128 + 'Meta': {'unique_together': "(('subject_token', 'mailinglist'),)", 'object_name': 'Thread'},
  129 + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  130 + 'latest_message': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'+'", 'unique': 'True', 'null': 'True', 'to': u"orm['super_archives.Message']"}),
  131 + 'mailinglist': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['super_archives.MailingList']"}),
  132 + 'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
  133 + 'spam': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
  134 + 'subject_token': ('django.db.models.fields.CharField', [], {'max_length': '512'})
  135 + },
  136 + u'super_archives.vote': {
  137 + 'Meta': {'unique_together': "(('user', 'message'),)", 'object_name': 'Vote'},
  138 + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
  139 + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  140 + 'message': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['super_archives.Message']"}),
  141 + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['accounts.User']"})
  142 + }
  143 + }
  144 +
  145 + complete_apps = ['super_archives']
0 146 \ No newline at end of file
... ...
src/super_archives/models.py
... ... @@ -6,10 +6,16 @@ from hashlib import md5
6 6 from django.db import models
7 7 from django.conf import settings
8 8 from django.utils import timezone
  9 +from django.core.cache import cache
9 10 from django.contrib.auth import get_user_model
10 11 from django.core.urlresolvers import reverse, NoReverseMatch
11 12 from django.utils.translation import ugettext_lazy as _
12 13  
  14 +from html2text import html2text
  15 +
  16 +from .utils import blocks
  17 +from .utils.etiquetador import etiquetador
  18 +
13 19  
14 20 User = get_user_model()
15 21  
... ... @@ -84,6 +90,18 @@ class MailingListMembership(models.Model):
84 90 return '%s on %s' % (self.user.email, self.mailinglist.name)
85 91  
86 92  
  93 +class Keyword(models.Model):
  94 + keyword = models.CharField(max_length='128')
  95 + weight = models.IntegerField(default=0)
  96 + thread = models.ForeignKey('Thread')
  97 +
  98 + class Meta:
  99 + ordering = ('?', ) # random order
  100 +
  101 + def __unicode__(self):
  102 + return self.keyword
  103 +
  104 +
87 105 class Thread(models.Model):
88 106  
89 107 subject_token = models.CharField(max_length=512)
... ... @@ -105,6 +123,31 @@ class Thread(models.Model):
105 123 verbose_name_plural = _(u"Threads")
106 124 unique_together = ('subject_token', 'mailinglist')
107 125  
  126 + def gen_tags(self):
  127 + blocks = []
  128 + for message in self.message_set.iterator():
  129 + blocks.extend([block for block in message.blocks()
  130 + if not block.is_reply])
  131 + text = u'\n'.join(map(unicode, blocks))
  132 + tags = etiquetador(html2text(text))
  133 + return tags
  134 +
  135 + def save(self, *args, **kwargs):
  136 + super(Thread, self).save(*args, **kwargs)
  137 + tags = self.gen_tags()
  138 +
  139 + for tag, weight in tags:
  140 + keyword, created = Keyword.objects.get_or_create(thread=self,
  141 + keyword=tag)
  142 + if created or keyword.weight != weight:
  143 + keyword.weight = weight
  144 + keyword.save()
  145 +
  146 + # removing old tags
  147 + qs = Keyword.objects.filter(thread=self)
  148 + qs = qs.exclude(keyword__in=zip(*tags)[0])
  149 + qs.delete()
  150 +
108 151 def __unicode__(self):
109 152 return '%s - %s (%s)' % (self.id,
110 153 self.subject_token,
... ... @@ -190,7 +233,7 @@ class Message(models.Model):
190 233 body = models.TextField(default='',
191 234 verbose_name=_(u"Message body"),
192 235 help_text=_(u"Please enter a message body"))
193   - received_time = models.DateTimeField()
  236 + received_time = models.DateTimeField(db_index=True)
194 237 message_id = models.CharField(max_length=512)
195 238 spam = models.BooleanField(default=False)
196 239  
... ... @@ -201,12 +244,21 @@ class Message(models.Model):
201 244 verbose_name = _(u"Message")
202 245 verbose_name_plural = _(u"Messages")
203 246 unique_together = ('thread', 'message_id')
  247 + ordering = ('received_time', )
204 248  
205 249 def __unicode__(self):
206 250 return '(%s) %s: %s' % (self.id,
207 251 self.from_address.get_full_name(),
208 252 self.subject_clean)
209 253  
  254 + def blocks(self):
  255 + cache_key = 'email-blocks-{}'.format(self.pk)
  256 + blks = cache.get(cache_key)
  257 + if not blks:
  258 + blks = blocks.EmailBlockParser(self)
  259 + cache.set(cache_key, blks)
  260 + return blks
  261 +
210 262 @property
211 263 def mailinglist(self):
212 264 if not self.thread or not self.thread.mailinglist:
... ...
src/super_archives/templates/message-thread.html
... ... @@ -140,7 +140,7 @@
140 140 </div>
141 141 <div class="panel-collapse in">
142 142 <div class="panel-body">
143   - {% display_message email emails %}
  143 + {% display_message email %}
144 144 </div>
145 145 </div>
146 146 </div>
... ... @@ -189,6 +189,15 @@
189 189 {% trans "voted" %}
190 190 <h5>{{ total_votes }} {% trans "times" %}</h5>
191 191 </li>
  192 + <li>
  193 + <span class="glyphicon glyphicon-chevron-right"></span>
  194 + {% trans "tags" %}
  195 + <div class="tag-cloud">
  196 + {% for keyword in thread.keyword_set.iterator %}
  197 + <a class="tag size-{{ keyword.weight }}" href="#">{{ keyword|escape }}</a>
  198 + {% endfor %}
  199 + </div>
  200 + </li>
192 201 </ul>
193 202 </div>
194 203  
... ...
src/super_archives/templates/superarchives/tags/display_message.html
1   -{% load cache %}
2   -
3   -{% cache cache_timeout 'display_message' cache_key %}
4   -{% for message, class in messages %}
5   - {% if class == 'reply' %}
  1 +{% for block in blocks %}
  2 + {% if block.is_reply %}
6 3 <button class="btn btn-info btn-xs" onclick="$(this).next().toggle();">...</button>
7   - <div style="display: none; color: #707";>{% else %}<div>{% endif %}{{ message|safe|linebreaksbr }}</div>
  4 + <div style="display: none; color: #707;">{% else %}<div>{% endif %}{{ block|safe|linebreaksbr }}</div>
8 5 {% endfor %}
9   -{% endcache %}
... ...
src/super_archives/templatetags/superarchives.py
1 1  
2   -import re
3   -
4 2 from django import template
5   -from django.core.cache import cache
6   -
7   -from html2text import html2text
8 3  
9 4  
10 5 register = template.Library()
11 6 TEMPLATE_PATH = 'superarchives/tags/'
12 7  
13 8  
14   -EXTENDED_PUNCTUATION = '!"#$%&\'()*+,-./:;=?@[\\]^_`{|}~ \t\n\r\x0b\x0c'
15   -RE_WRAPPED_BY_HTML = re.compile(r'^<[a-z]+[^>]*>.*</[a-z]+[^>]*>$',
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
27   -
28   -
29   -def join(block):
30   - block_txt = u''.join(block)
31   -
32   - if RE_WRAPPED_BY_HTML.match(block_txt.strip()):
33   - block = html2text(block_txt)
34   - else:
35   - block = block_txt
36   -
37   - return find_links(block)
38   -
39   -def is_reply(line, message, thread):
40   - clean_line = line.strip()
41   - if clean_line.startswith('>'):
42   - return True
43   -
44   - for other_msg in thread:
45   - if other_msg == message:
46   - return False
47   -
48   - clean_body = other_msg.body.replace('\r', ' ')\
49   - .replace('\n', ' ')
50   - if clean_line.strip('> ') in clean_body:
51   - return True
52   -
53   - return False
54   -
55   -
56 9 @register.inclusion_tag(TEMPLATE_PATH + 'display_message.html')
57   -def display_message(email, thread):
58   - message = email.body
59   - messages = []
60   -
61   - block = []
62   - reply_block = False
63   -
64   - for line in message.split('\n'):
65   - if line.strip(EXTENDED_PUNCTUATION):
66   - if is_reply(line, email, thread):
67   - if not reply_block:
68   - reply_block = True
69   - messages.append((join(block), 'normal'))
70   - block = []
71   - elif reply_block:
72   - reply_block = False
73   - messages.append((join(block), 'reply'))
74   - block = []
75   -
76   - block.append(line.rstrip() + '\n')
77   -
78   - if reply_block:
79   - messages.append((join(block), 'reply'))
80   - else:
81   - messages.append((join(block), 'normal'))
82   -
83   - return {'messages': messages,
84   - 'cache_key': email.pk,
85   - 'cache_timeout': cache.default_timeout}
  10 +def display_message(email):
  11 + return {'blocks': email.blocks()}
... ...
src/super_archives/utils.py
... ... @@ -1,19 +0,0 @@
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/utils/__init__.py 0 → 100644
src/super_archives/utils/blocks.py 0 → 100644
... ... @@ -0,0 +1,99 @@
  1 +
  2 +import re
  3 +
  4 +from django.utils.html import strip_tags
  5 +
  6 +from html2text import html2text
  7 +
  8 +
  9 +EXTENDED_PUNCTUATION = '!"#$%&\'()*+,-./:;=?@[\\]^_`{|}~ \t\n\r\x0b\x0c'
  10 +RE_WRAPPED_BY_HTML = re.compile(r'^<[a-z]+[^>]*>.*</[a-z]+[^>]*>$',
  11 + re.MULTILINE|re.IGNORECASE|re.DOTALL)
  12 +RE_LINKS = re.compile(r'(?P<link>https?://[^ \t\r\n\<]+)')
  13 +LINK_MARKUP = u'<a target="_blank" href="\g<link>">\g<link></a>'
  14 +
  15 +RE_REPLY_LINE = re.compile(r'^[\s\t>]*>[\s\t]*')
  16 +
  17 +RE_BR_TO_LINEBREAK = re.compile(r'<\s*/?\s*br\s*/?\s*>')
  18 +
  19 +class EmailBlock(list):
  20 + def __init__(self, is_reply=False, mark_links=True, html2text=True):
  21 + self.mark_links = mark_links
  22 + self.html2text = html2text
  23 + self.is_reply = is_reply
  24 +
  25 + def _html2text(self, text):
  26 + if RE_WRAPPED_BY_HTML.match(text.strip()):
  27 + return html2text(text)
  28 +
  29 + text, n = RE_BR_TO_LINEBREAK.subn('\n', text)
  30 + text = strip_tags(text)
  31 + return text
  32 +
  33 + def _mark_links(self, text):
  34 + text, n = RE_LINKS.subn(LINK_MARKUP, text)
  35 + return text
  36 +
  37 + @property
  38 + def text(self):
  39 + block = u''.join(self)
  40 +
  41 + if self.html2text:
  42 + block = self._html2text(block)
  43 +
  44 + if self.mark_links:
  45 + block = self._mark_links(block)
  46 +
  47 + return block
  48 +
  49 + def __unicode__(self):
  50 + return self.text
  51 +
  52 + def __str__(self):
  53 + return self.text
  54 +
  55 +
  56 +class EmailBlockParser(list):
  57 + def __init__(self, email):
  58 + self.email = email
  59 + self.thread_emails = email.thread.message_set
  60 +
  61 + message = email.body
  62 + block = EmailBlock()
  63 +
  64 + for line in message.split('\n'):
  65 + if self.context_switch(line, block):
  66 + self.append(block)
  67 + new_block_context = not block.is_reply
  68 + block = EmailBlock(is_reply=new_block_context)
  69 +
  70 + block.append(line + '\n')
  71 +
  72 + self.append(block)
  73 +
  74 + def context_switch(self, line, block):
  75 + if line.strip(EXTENDED_PUNCTUATION):
  76 + if self.is_reply(line):
  77 + if not block.is_reply:
  78 + return True
  79 +
  80 + else:
  81 + if block.is_reply:
  82 + return True
  83 +
  84 + return False
  85 +
  86 + def is_reply(self, line):
  87 + stripped_line = line.strip()
  88 + if stripped_line.startswith('>') or RE_REPLY_LINE.match(line):
  89 + return True
  90 +
  91 + clean_line = RE_REPLY_LINE.subn('', stripped_line)[0]
  92 + queryset = self.thread_emails.filter(
  93 + received_time__lt=self.email.received_time,
  94 + body__contains=clean_line).order_by('-received_time')
  95 +
  96 + if queryset[:1]:
  97 + return True
  98 +
  99 + return False
... ...
src/super_archives/utils/email.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/utils/etiquetador.py 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +
  2 +from etiquetando import Etiquetador
  3 +
  4 +etiquetador = Etiquetador(word_min_size=3, max_tags=24,
  5 + weight_range=(1, 4))
... ...
src/super_archives/views.py
... ... @@ -14,8 +14,8 @@ from django.contrib.auth.decorators import login_required
14 14 from django.shortcuts import render, redirect
15 15  
16 16 from . import queries
17   -from .utils import send_verification_email
18 17 from .decorators import count_hit
  18 +from .utils.email import send_verification_email
19 19 from .models import MailingList, Thread, EmailAddress, EmailAddressValidation
20 20  
21 21  
... ... @@ -42,18 +42,19 @@ def thread(request, mailinglist, thread_token):
42 42 total_votes += email.votes_count()
43 43  
44 44 # Update relevance score
45   - query = Thread.objects.filter(mailinglist__name=mailinglist)
46   - thread = query.get(subject_token=thread_token)
  45 + thread = Thread.objects.get(subject_token=thread_token,
  46 + mailinglist__name=mailinglist)
47 47 thread.update_score()
48 48  
49   - template_data = {
  49 + context = {
50 50 'first_msg': first_message,
51 51 'emails': [first_message] + list(emails),
52 52 'pagehits': queries.get_page_hits(request.path_info),
53 53 'total_votes': total_votes,
  54 + 'thread': thread,
54 55 }
55 56  
56   - return render(request, 'message-thread.html', template_data)
  57 + return render(request, 'message-thread.html', context)
57 58  
58 59  
59 60 def list_messages(request):
... ...