Commit 0cb51df4b0e5373a703e896606300331cdfc4dcf

Authored by Sergio Oliveira
1 parent 845d8ecf

Merge com a ultima versao do bitcket: https://bitbucket.org/seocam/atu-colab/src/4ee3ca57614e

@@ -9,9 +9,9 @@ TODO @@ -9,9 +9,9 @@ TODO
9 * Yure: Adicionar data do ultimo import de emails no footer 9 * Yure: Adicionar data do ultimo import de emails no footer
10 * Yure: Detectar links no conteudo e exibi-los como tal 10 * Yure: Detectar links no conteudo e exibi-los como tal
11 * Yure: BUG: Display of HTML emails are wrong 11 * Yure: BUG: Display of HTML emails are wrong
12 -* Yure: Cadastrar usuário em lista pelo formulario de cadastro  
13 * Arquivo "search.html" existente em "atu-colab/colab/templates" pode ser melhorado com relação ao conteúdo repetitivo dos "Tipos" exibidos no "Filtro" da página 12 * Arquivo "search.html" existente em "atu-colab/colab/templates" pode ser melhorado com relação ao conteúdo repetitivo dos "Tipos" exibidos no "Filtro" da página
14 -* Criar validador de urls para twitter, facebook e página pessoal do user profile 13 +* BUG: Criar validador de urls para twitter, facebook e página pessoal do user profile
  14 +* Mostrar dados do twitter, facebook, gtalk e página pessoal somente para os usuários que estiverem logados
15 15
16 * Configurar ADMINS no arquivo settings_local.py 16 * Configurar ADMINS no arquivo settings_local.py
17 * HTTPS para o trac, subversion e colab 17 * HTTPS para o trac, subversion e colab
@@ -20,11 +20,10 @@ TODO @@ -20,11 +20,10 @@ TODO
20 * Timezones no trac/colab/solr nao estao compativeis 20 * Timezones no trac/colab/solr nao estao compativeis
21 21
22 * Template de login nao exibe corretamente no firefox/linux 22 * Template de login nao exibe corretamente no firefox/linux
23 -* Quando usuario se cadastra com email errado o email nunca eh validado,  
24 -e o username fica preso 'pra sempre'. 23 +* Quando usuario se cadastra com email errado o email nunca eh validado, e o username fica preso 'pra sempre'.
25 * Nome dos usuarios errado nos emails que vem do Solr 24 * Nome dos usuarios errado nos emails que vem do Solr
26 * Adicionar ordering na busca 25 * Adicionar ordering na busca
27 -* Criar tipo usuario no solr 26 +* Criar tipo usuario no solr
28 * Substituir sistema de cadastro por django-registration 27 * Substituir sistema de cadastro por django-registration
29 * Utilizar pysolr para efetuar queries no Solr 28 * Utilizar pysolr para efetuar queries no Solr
30 * Melhorar buscas (case insensitive match, palavras com acentos) 29 * Melhorar buscas (case insensitive match, palavras com acentos)
@@ -40,13 +39,13 @@ e o username fica preso 'pra sempre'. @@ -40,13 +39,13 @@ e o username fica preso 'pra sempre'.
40 * Merge emails dos usuarios 39 * Merge emails dos usuarios
41 * Implementar badge system 40 * Implementar badge system
42 * Melhorar filtros 41 * Melhorar filtros
43 -* Link do thread preview deve enviar para mensagem da thread (anchor) (Útil? discutir com Jean) 42 +* Link do thread preview deve enviar para mensagem da thread (anchor)
44 * Tornar todas as strings traduziveis 43 * Tornar todas as strings traduziveis
45 * Sugestão de como a divisão do edital seria melhor 44 * Sugestão de como a divisão do edital seria melhor
46 * Indexar anexos da wiki (using Tika http://wiki.apache.org/solr/ExtractingRequestHandler) 45 * Indexar anexos da wiki (using Tika http://wiki.apache.org/solr/ExtractingRequestHandler)
47 * Filtrar usando calendario (como google analytics) 46 * Filtrar usando calendario (como google analytics)
48 * Melhorar relevancia das buscas usando dismax queryparser 47 * Melhorar relevancia das buscas usando dismax queryparser
49 -* Chat estilo Gmail 48 +* Chat estilo Gmail usando o mensageiro Interlegis
50 * Sistema de gerencia de conteúdo 49 * Sistema de gerencia de conteúdo
51 * Versao mobile 50 * Versao mobile
52 * Exibir discussões relacionadas na barra da direita das discussões 51 * Exibir discussões relacionadas na barra da direita das discussões
@@ -56,9 +55,14 @@ e o username fica preso 'pra sempre'. @@ -56,9 +55,14 @@ e o username fica preso 'pra sempre'.
56 * Contar page views no trac (ticket, wiki e changeset) e utiliza-los para rankear paginas nas buscas 55 * Contar page views no trac (ticket, wiki e changeset) e utiliza-los para rankear paginas nas buscas
57 * Mostrar highlight nas buscas 56 * Mostrar highlight nas buscas
58 * Sistema de tags para as mensagens 57 * Sistema de tags para as mensagens
  58 +* Tag cloud para as mensagens, ao lado direito da thread
59 * Pagina home para cada lista com os mesmo filtros da home atual 59 * Pagina home para cada lista com os mesmo filtros da home atual
60 * Permitir que usuario entre e saia de listas ao editar perfil 60 * Permitir que usuario entre e saia de listas ao editar perfil
61 * Planet Interlegis (agregador de blogs) 61 * Planet Interlegis (agregador de blogs)
62 -* Filtros especificos para tipos diferentes na busca 62 +* Filtros específicos para tipos diferentes na busca da thread
  63 +* Link para a mensagem original no histórico do Mailman (popup ajax)
  64 +* Filtro de mensagens nas listas acumulativos, podendo ligar e desligar todos
  65 +* Reduzir campos na tela de cadastro, transferindo-os como aba para a tela de profile, junto com a aba de listas a se inscrever)
  66 +
63 * Indice criado manualmente. Automatizar: 67 * Indice criado manualmente. Automatizar:
64 * create index super_archives_message_body_idx ON super_archives_message ((substring(body,0,1024))); 68 * create index super_archives_message_body_idx ON super_archives_message ((substring(body,0,1024)));
colab/rss/__init__.py 0 → 100644
colab/rss/feeds.py 0 → 100644
@@ -0,0 +1,74 @@ @@ -0,0 +1,74 @@
  1 +#!/usr/bin/env python
  2 +# encoding: utf-8
  3 +
  4 +from django.contrib.syndication.views import Feed
  5 +from django.utils.translation import ugettext as _
  6 +
  7 +from colab.super_archives.models import Thread
  8 +from colab.super_archives import queries
  9 +from colab import solrutils
  10 +
  11 +class LatestThreadsFeeds(Feed):
  12 + title = _(u'Últimas Discussões')
  13 + link = '/rss/threads/latest/'
  14 +
  15 + def items(self):
  16 + return queries.get_latest_threads()[:20]
  17 +
  18 + def item_link(self, item):
  19 + return item.latest_message.url
  20 +
  21 + def item_title(self, item):
  22 + return item.latest_message.subject_clean
  23 +
  24 + def item_description(self, item):
  25 + return item.latest_message.body
  26 +
  27 +
  28 +class HottestThreadsFeeds(Feed):
  29 + title = _(u'Discussões Mais Relevantes')
  30 + link = '/rss/threads/hottest/'
  31 +
  32 + def items(self):
  33 + return queries.get_hottest_threads()[:20]
  34 +
  35 + def item_link(self, item):
  36 + return item.latest_message.url
  37 +
  38 + def item_title(self, item):
  39 + return item.latest_message.subject_clean
  40 +
  41 + def item_description(self, item):
  42 + return item.latest_message.body
  43 +
  44 +
  45 +class LatestColabFeeds(Feed):
  46 + title = _(u'Últimas Colaborações')
  47 + link = '/rss/colab/latest/'
  48 +
  49 + def items(self):
  50 + items = solrutils.get_latest_collaborations(20)
  51 + return items
  52 +
  53 + def item_title(self, item):
  54 + type_ = item.get('Type') + ': '
  55 + mailinglist = item.get('mailinglist')
  56 +
  57 + if mailinglist:
  58 + prefix = type_ + mailinglist + ' - '
  59 + else:
  60 + prefix = type_
  61 +
  62 + return prefix + item.get('Title')
  63 +
  64 + def item_description(self, item):
  65 + return item.get('Description')
  66 +
  67 + def item_link(self, item):
  68 + if item.get('Type') != 'thread':
  69 + url = item.get('url')
  70 + else:
  71 + url = 'http://colab.interlegis.leg.br'
  72 + url += item.get('url')
  73 + return url
  74 +
colab/rss/urls.py 0 → 100644
@@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
  1 +from django.conf.urls.defaults import patterns, url
  2 +import feeds
  3 +
  4 +urlpatterns = patterns('',
  5 + url(r'threads/latest/$', feeds.LatestThreadsFeeds()),
  6 + url(r'colab/latest/$', feeds.LatestColabFeeds()),
  7 + url(r'threads/hottest/$', feeds.HottestThreadsFeeds()),
  8 +)
  9 +
colab/settings.py
@@ -118,6 +118,7 @@ INSTALLED_APPS = ( @@ -118,6 +118,7 @@ INSTALLED_APPS = (
118 # My apps 118 # My apps
119 'colab.super_archives', 119 'colab.super_archives',
120 'colab.api', 120 'colab.api',
  121 + 'colab.rss',
121 ) 122 )
122 123
123 # A sample logging configuration. The only tangible logging 124 # A sample logging configuration. The only tangible logging
colab/settings_local-dev.py
@@ -10,9 +10,15 @@ MANAGERS = ADMINS @@ -10,9 +10,15 @@ MANAGERS = ADMINS
10 DATABASES = { 10 DATABASES = {
11 'default': { 11 'default': {
12 'ENGINE': 'django.db.backends.sqlite3', 12 'ENGINE': 'django.db.backends.sqlite3',
13 - 'NAME': 'colab.db', 13 + 'NAME': 'colab.db',
14 } 14 }
15 } 15 }
16 16
17 # Make this unique, and don't share it with anybody. 17 # Make this unique, and don't share it with anybody.
18 SECRET_KEY = ')(jksdfhsjkadfhjkh234ns!8fqu-1186h$vuj' 18 SECRET_KEY = ')(jksdfhsjkadfhjkh234ns!8fqu-1186h$vuj'
  19 +
  20 +import socks
  21 +SOCKS_TYPE = socks.PROXY_TYPE_SOCKS5
  22 +SOCKS_SERVER = '127.0.0.1'
  23 +SOCKS_PORT = 9050
  24 +
colab/signup.py
@@ -4,8 +4,8 @@ @@ -4,8 +4,8 @@
4 from django.conf import settings 4 from django.conf import settings
5 from django.utils.html import strip_tags 5 from django.utils.html import strip_tags
6 from django.utils.translation import ugettext as _ 6 from django.utils.translation import ugettext as _
7 -from django.core.mail import EmailMultiAlternatives  
8 from django.template.loader import render_to_string 7 from django.template.loader import render_to_string
  8 +from django.core.mail import EmailMultiAlternatives, send_mail
9 9
10 10
11 def send_verification_email(request, user): 11 def send_verification_email(request, user):
@@ -47,4 +47,15 @@ def send_reset_password_email(request, user): @@ -47,4 +47,15 @@ def send_reset_password_email(request, user):
47 email_msg.attach_alternative(html_content, 'text/html') 47 email_msg.attach_alternative(html_content, 'text/html')
48 email_msg.send() 48 email_msg.send()
49 49
50 -  
51 \ No newline at end of file 50 \ No newline at end of file
  51 +def send_email_lists(user, mailing_lists):
  52 + subject = _(u'Inscrição na lista de discussão')
  53 + from_ = user.email
  54 + to = []
  55 + for list_name in mailing_lists:
  56 + # TODO: The following line needs to be generic. Domain should be stored in settings file
  57 + # or database (perharps read directly from mailman).
  58 + subscribe_addr = list_name + '-subscribe@listas.interlegis.gov.br'
  59 + to.append(subscribe_addr)
  60 +
  61 + send_mail(subject, '', from_, to)
  62 +
colab/solrutils.py
@@ -178,7 +178,7 @@ def select(query, results_per_page=None, page_number=None, sort=None, fields=Non @@ -178,7 +178,7 @@ def select(query, results_per_page=None, page_number=None, sort=None, fields=Non
178 if socks_server: 178 if socks_server:
179 import socks 179 import socks
180 logging.debug('Socks enabled: %s:%s', settings.SOCKS_SERVER, 180 logging.debug('Socks enabled: %s:%s', settings.SOCKS_SERVER,
181 - settings.SOLR_PORT) 181 + settings.SOCKS_PORT)
182 182
183 socks.setdefaultproxy(settings.SOCKS_TYPE, 183 socks.setdefaultproxy(settings.SOCKS_TYPE,
184 settings.SOCKS_SERVER, 184 settings.SOCKS_SERVER,
colab/super_archives/forms.py
@@ -5,6 +5,7 @@ from django.core.exceptions import ValidationError @@ -5,6 +5,7 @@ from django.core.exceptions import ValidationError
5 from django.contrib.auth.models import User 5 from django.contrib.auth.models import User
6 from django.contrib.auth.forms import UserCreationForm as UserCreationForm_ 6 from django.contrib.auth.forms import UserCreationForm as UserCreationForm_
7 7
  8 +from colab.super_archives.models import MailingList
8 from colab.super_archives.validators import UniqueValidator 9 from colab.super_archives.validators import UniqueValidator
9 10
10 # XXX: I know that this code does not look nice AT ALL. 11 # XXX: I know that this code does not look nice AT ALL.
@@ -26,7 +27,20 @@ facebook_field = forms.URLField(label=u'Facebook', required=False) @@ -26,7 +27,20 @@ facebook_field = forms.URLField(label=u'Facebook', required=False)
26 google_talk_field = forms.EmailField(label=u'Google Talk', required=False) 27 google_talk_field = forms.EmailField(label=u'Google Talk', required=False)
27 webpage_field = forms.URLField(label=u'Página Pessoal/Blog', required=False) 28 webpage_field = forms.URLField(label=u'Página Pessoal/Blog', required=False)
28 29
29 - 30 +all_lists = MailingList.objects.all()
  31 +lists_names = []
  32 +for list_ in all_lists:
  33 + choice = (list_.name, list_.name)
  34 + lists_names.append(choice)
  35 +
  36 +lists_field = forms.MultipleChoiceField(
  37 + label=u'Listas',
  38 + required=False,
  39 + widget=forms.CheckboxSelectMultiple,
  40 + choices=lists_names
  41 +)
  42 +
  43 +
30 class UserCreationForm(UserCreationForm_): 44 class UserCreationForm(UserCreationForm_):
31 first_name = first_name_field 45 first_name = first_name_field
32 last_name = last_name_field 46 last_name = last_name_field
@@ -37,8 +51,9 @@ class UserCreationForm(UserCreationForm_): @@ -37,8 +51,9 @@ class UserCreationForm(UserCreationForm_):
37 facebook = facebook_field 51 facebook = facebook_field
38 google_talk = google_talk_field 52 google_talk = google_talk_field
39 webpage = webpage_field 53 webpage = webpage_field
  54 + lists = lists_field
40 55
41 - 56 +
42 class UserUpdateForm(forms.Form): 57 class UserUpdateForm(forms.Form):
43 username = username_field 58 username = username_field
44 username.required = False 59 username.required = False
colab/super_archives/migrations/0008_add_mailinglist_name_to_url.py 0 → 100644
@@ -0,0 +1,142 @@ @@ -0,0 +1,142 @@
  1 +# encoding: utf-8
  2 +import datetime
  3 +from south.db import db
  4 +from south.v2 import DataMigration
  5 +from django.db import models
  6 +
  7 +class Migration(DataMigration):
  8 +
  9 + def forwards(self, orm):
  10 + page_hits = orm.PageHit.objects.all()
  11 + for page_hit in page_hits:
  12 + if page_hit.url_path.startswith('/archives/thread/'):
  13 + token = page_hit.url_path.split('/')[-1]
  14 + threads = orm.Thread.objects.filter(subject_token=token)
  15 + if not len(threads): continue
  16 + thread = threads[0]
  17 + new_url = '/archives/thread/%s/%s' % (thread.mailinglist.name,
  18 + thread.subject_token)
  19 + page_hit.url_path = new_url
  20 + page_hit.save()
  21 +
  22 +
  23 + def backwards(self, orm):
  24 + raise RuntimeError("Cannot reverse this migration.")
  25 +
  26 + models = {
  27 + 'auth.group': {
  28 + 'Meta': {'object_name': 'Group'},
  29 + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  30 + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
  31 + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
  32 + },
  33 + 'auth.permission': {
  34 + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
  35 + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
  36 + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
  37 + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  38 + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
  39 + },
  40 + 'auth.user': {
  41 + 'Meta': {'object_name': 'User'},
  42 + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
  43 + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
  44 + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
  45 + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
  46 + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  47 + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
  48 + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
  49 + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
  50 + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
  51 + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
  52 + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
  53 + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
  54 + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
  55 + },
  56 + 'contenttypes.contenttype': {
  57 + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
  58 + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
  59 + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  60 + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
  61 + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
  62 + },
  63 + 'super_archives.emailaddress': {
  64 + 'Meta': {'object_name': 'EmailAddress'},
  65 + 'address': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75'}),
  66 + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  67 + 'md5': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
  68 + 'real_name': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '64', 'blank': 'True'}),
  69 + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'emails'", 'null': 'True', 'to': "orm['auth.User']"})
  70 + },
  71 + 'super_archives.mailinglist': {
  72 + 'Meta': {'object_name': 'MailingList'},
  73 + 'description': ('django.db.models.fields.TextField', [], {}),
  74 + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}),
  75 + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  76 + 'last_imported_index': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
  77 + 'logo': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
  78 + 'name': ('django.db.models.fields.CharField', [], {'max_length': '80'})
  79 + },
  80 + 'super_archives.mailinglistmembership': {
  81 + 'Meta': {'object_name': 'MailingListMembership'},
  82 + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  83 + 'mailinglist': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['super_archives.MailingList']"}),
  84 + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
  85 + },
  86 + 'super_archives.message': {
  87 + 'Meta': {'object_name': 'Message'},
  88 + 'body': ('django.db.models.fields.TextField', [], {'default': "''"}),
  89 + 'from_address': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['super_archives.EmailAddress']"}),
  90 + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  91 + 'mailinglist': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['super_archives.MailingList']"}),
  92 + 'message_id': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
  93 + 'received_time': ('django.db.models.fields.DateTimeField', [], {}),
  94 + 'spam': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
  95 + 'subject': ('django.db.models.fields.CharField', [], {'max_length': '512', 'db_index': 'True'}),
  96 + 'subject_clean': ('django.db.models.fields.CharField', [], {'max_length': '512', 'db_index': 'True'}),
  97 + 'thread': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['super_archives.Thread']", 'null': 'True'})
  98 + },
  99 + 'super_archives.messagemetadata': {
  100 + 'Message': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['super_archives.Message']"}),
  101 + 'Meta': {'object_name': 'MessageMetadata'},
  102 + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  103 + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
  104 + 'value': ('django.db.models.fields.TextField', [], {})
  105 + },
  106 + 'super_archives.pagehit': {
  107 + 'Meta': {'object_name': 'PageHit'},
  108 + 'hit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
  109 + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  110 + 'url_path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '2048', 'db_index': 'True'})
  111 + },
  112 + 'super_archives.thread': {
  113 + 'Meta': {'unique_together': "(('subject_token', 'mailinglist'),)", 'object_name': 'Thread'},
  114 + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  115 + 'latest_message': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'+'", 'unique': 'True', 'null': 'True', 'to': "orm['super_archives.Message']"}),
  116 + 'mailinglist': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['super_archives.MailingList']"}),
  117 + 'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
  118 + 'spam': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
  119 + 'subject_token': ('django.db.models.fields.CharField', [], {'max_length': '512'})
  120 + },
  121 + 'super_archives.userprofile': {
  122 + 'Meta': {'object_name': 'UserProfile'},
  123 + 'facebook': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
  124 + 'google_talk': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True'}),
  125 + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  126 + 'institution': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
  127 + 'role': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
  128 + 'twitter': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
  129 + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'}),
  130 + 'verification_hash': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
  131 + 'webpage': ('django.db.models.fields.CharField', [], {'max_length': '256'})
  132 + },
  133 + 'super_archives.vote': {
  134 + 'Meta': {'unique_together': "(('user', 'message'),)", 'object_name': 'Vote'},
  135 + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
  136 + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  137 + 'message': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['super_archives.Message']"}),
  138 + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
  139 + }
  140 + }
  141 +
  142 + complete_apps = ['super_archives']
colab/super_archives/models.py
@@ -148,7 +148,8 @@ class Thread(models.Model): @@ -148,7 +148,8 @@ class Thread(models.Model):
148 148
149 # Calculate page_view_score 149 # Calculate page_view_score
150 try: 150 try:
151 - url = reverse('thread_view', args=[self.subject_token]) 151 + url = reverse('thread_view', args=[self.mailinglist.name,
  152 + self.subject_token])
152 pagehit = PageHit.objects.get(url_path=url) 153 pagehit = PageHit.objects.get(url_path=url)
153 page_view_score = pagehit.hit_count * 10 154 page_view_score = pagehit.hit_count * 10
154 except (NoReverseMatch, PageHit.DoesNotExist): 155 except (NoReverseMatch, PageHit.DoesNotExist):
@@ -217,7 +218,8 @@ class Message(models.Model): @@ -217,7 +218,8 @@ class Message(models.Model):
217 @property 218 @property
218 def url(self): 219 def url(self):
219 """Shortcut to get thread url""" 220 """Shortcut to get thread url"""
220 - return reverse('thread_view', args=[self.thread.subject_token]) 221 + return reverse('thread_view', args=[self.mailinglist.name,
  222 + self.thread.subject_token])
221 223
222 @property 224 @property
223 def Description(self): 225 def Description(self):
colab/super_archives/queries.py
@@ -26,15 +26,18 @@ def get_messages_by_voted(): @@ -26,15 +26,18 @@ def get_messages_by_voted():
26 return messages.order_by('-vote_count', 'received_time') 26 return messages.order_by('-vote_count', 'received_time')
27 27
28 28
29 -def get_first_message_in_thread(thread_token):  
30 - return get_messages_by_date().filter(thread__subject_token=thread_token)[0]  
31 - 29 +def get_first_message_in_thread(mailinglist, thread_token):
  30 + query = get_messages_by_date()
  31 + query = query.filter(mailinglist__name=mailinglist)
  32 + query = query.filter(thread__subject_token=thread_token)[0]
  33 + return query
  34 +
32 35
33 def get_latest_threads(): 36 def get_latest_threads():
34 return Thread.objects.order_by('-latest_message__received_time') 37 return Thread.objects.order_by('-latest_message__received_time')
35 38
36 39
37 -def get_hotest_threads(): 40 +def get_hottest_threads():
38 return Thread.objects.order_by('-score', '-latest_message__received_time') 41 return Thread.objects.order_by('-score', '-latest_message__received_time')
39 42
40 43
colab/super_archives/templates/message-list.html
@@ -11,8 +11,8 @@ @@ -11,8 +11,8 @@
11 11
12 <h4>Ordenar por</h4> 12 <h4>Ordenar por</h4>
13 <ul> 13 <ul>
14 - <li {% ifequal order_by "hotest" %} class="selected" title="{% trans "Retirar filtro" %}" {% endifequal %}>  
15 - <a href="{% ifequal order_by "hotest" %} {% append_to_get order="",p=1 %} {% else %} {% append_to_get order='hotest',p=1 %} {% endifequal %}"> 14 + <li {% ifequal order_by "hottest" %} class="selected" title="{% trans "Retirar filtro" %}" {% endifequal %}>
  15 + <a href="{% ifequal order_by "hottest" %} {% append_to_get order="",p=1 %} {% else %} {% append_to_get order='hottest',p=1 %} {% endifequal %}">
16 Relevância</a></li> 16 Relevância</a></li>
17 <li {% ifequal order_by "latest" %} class="selected" title="{% trans "Retirar filtro" %}" {% endifequal %}> 17 <li {% ifequal order_by "latest" %} class="selected" title="{% trans "Retirar filtro" %}" {% endifequal %}>
18 <a href="{% ifequal order_by "latest" %} {% append_to_get order="",p=1 %} {% else %} {% append_to_get order='latest',p=1 %} {% endifequal %}"> 18 <a href="{% ifequal order_by "latest" %} {% append_to_get order="",p=1 %} {% else %} {% append_to_get order='latest',p=1 %} {% endifequal %}">
colab/super_archives/templatetags/append_to_get.py
@@ -37,7 +37,16 @@ class AppendGetNode(template.Node): @@ -37,7 +37,16 @@ class AppendGetNode(template.Node):
37 path = context['request'].META['PATH_INFO'] 37 path = context['request'].META['PATH_INFO']
38 38
39 if len(get): 39 if len(get):
40 - path = '?' + urllib.urlencode(get) 40 + # Convert all unicode objects in the get dict to
  41 + # str (utf-8 encoded)
  42 + get_utf_encoded = {}
  43 + for (key, value) in get.items():
  44 + if isinstance(value, unicode):
  45 + value = value.encode('utf-8')
  46 + get_utf_encoded.update({key: value})
  47 + get_utf_encoded = dict(get_utf_encoded)
  48 +
  49 + path = '?' + urllib.urlencode(get_utf_encoded)
41 50
42 return path 51 return path
43 52
colab/super_archives/templatetags/form_field.py
@@ -24,40 +24,41 @@ class RenderFormField(template.Node): @@ -24,40 +24,41 @@ class RenderFormField(template.Node):
24 def render(self, context): 24 def render(self, context):
25 editable = context.get('editable', True) 25 editable = context.get('editable', True)
26 26
27 - class_ = ''  
28 - errors = ''  
29 - form_field_tag = '' 27 + class_ = u''
  28 + errors = u''
  29 + form_field_tag = u''
30 try: 30 try:
31 form_field = self.form_field_nocontext.resolve(context) 31 form_field = self.form_field_nocontext.resolve(context)
32 except template.VariableDoesNotExist: 32 except template.VariableDoesNotExist:
33 - return '' 33 + return u''
34 34
35 if form_field.errors: 35 if form_field.errors:
36 - class_ += 'error' 36 + class_ += u'error'
37 if form_field.field.required: 37 if form_field.field.required:
38 - class_ += ' required' 38 + class_ += u' required'
39 if form_field.errors: 39 if form_field.errors:
40 - errors = '<br/>' + form_field.errors.as_text() 40 + errors = u'<br/>' + form_field.errors.as_text()
41 41
42 try: 42 try:
43 default_value = self.default_value_nocontext.resolve(context) 43 default_value = self.default_value_nocontext.resolve(context)
44 except template.VariableDoesNotExist: 44 except template.VariableDoesNotExist:
45 - default_value = '' 45 + default_value = u''
46 46
47 if editable: 47 if editable:
48 - form_field_tag = '<br/>' + str(form_field) 48 + form_field_tag = u'<br/>' + unicode(form_field)
49 elif isinstance(form_field.field, forms.URLField): 49 elif isinstance(form_field.field, forms.URLField):
50 - form_field_tag = """<a href="%s" target="_blank">%s</a>""" % ( 50 + form_field_tag = u"""<a href="%s" target="_blank">%s</a>""" % (
51 default_value, default_value) 51 default_value, default_value)
52 else: 52 else:
53 form_field_tag = default_value 53 form_field_tag = default_value
54 54
55 - return """<p class="%s">%s: %s %s</p>""" % ( 55 + return u"""<p class="%s">%s: %s %s</p>""" % (
56 class_, 56 class_,
57 form_field.label_tag(), 57 form_field.label_tag(),
58 form_field_tag, 58 form_field_tag,
59 errors 59 errors
60 ) 60 )
61 - 61 +
  62 +
62 register = template.Library() 63 register = template.Library()
63 register.tag('render_form_field', render_form_field) 64 register.tag('render_form_field', render_form_field)
colab/super_archives/urls.py
@@ -2,7 +2,7 @@ from django.conf.urls.defaults import patterns, include, url @@ -2,7 +2,7 @@ from django.conf.urls.defaults import patterns, include, url
2 2
3 urlpatterns = patterns('', 3 urlpatterns = patterns('',
4 # url(r'thread/(?P<thread>\d+)/$', 'super_archives.views.thread', name='thread'), 4 # url(r'thread/(?P<thread>\d+)/$', 'super_archives.views.thread', name='thread'),
5 - url(r'thread/(?P<thread_token>[-\w]+)$', 5 + url(r'thread/(?P<mailinglist>[-\w]+)/(?P<thread_token>[-\w]+)$',
6 'colab.super_archives.views.thread', name="thread_view"), 6 'colab.super_archives.views.thread', name="thread_view"),
7 url(r'thread/$', 7 url(r'thread/$',
8 'colab.super_archives.views.list_messages', name='thread_list') 8 'colab.super_archives.views.list_messages', name='thread_list')
colab/super_archives/views.py
@@ -8,9 +8,9 @@ from colab.super_archives import queries @@ -8,9 +8,9 @@ from colab.super_archives import queries
8 from colab.super_archives.models import MailingList, Thread 8 from colab.super_archives.models import MailingList, Thread
9 9
10 10
11 -def thread(request, thread_token): 11 +def thread(request, mailinglist, thread_token):
12 12
13 - first_message = queries.get_first_message_in_thread(thread_token) 13 + first_message = queries.get_first_message_in_thread(mailinglist, thread_token)
14 order_by = request.GET.get('order') 14 order_by = request.GET.get('order')
15 if order_by == 'voted': 15 if order_by == 'voted':
16 msgs_query = queries.get_messages_by_voted() 16 msgs_query = queries.get_messages_by_voted()
@@ -18,6 +18,7 @@ def thread(request, thread_token): @@ -18,6 +18,7 @@ def thread(request, thread_token):
18 msgs_query = queries.get_messages_by_date() 18 msgs_query = queries.get_messages_by_date()
19 19
20 msgs_query = msgs_query.filter(thread__subject_token=thread_token) 20 msgs_query = msgs_query.filter(thread__subject_token=thread_token)
  21 + msgs_query = msgs_query.filter(mailinglist__name=mailinglist)
21 emails = msgs_query.exclude(id=first_message.id) 22 emails = msgs_query.exclude(id=first_message.id)
22 23
23 total_votes = first_message.votes_count() 24 total_votes = first_message.votes_count()
@@ -25,7 +26,8 @@ def thread(request, thread_token): @@ -25,7 +26,8 @@ def thread(request, thread_token):
25 total_votes += email.votes_count() 26 total_votes += email.votes_count()
26 27
27 # Update relevance score 28 # Update relevance score
28 - thread = Thread.objects.get(subject_token=thread_token) 29 + query = Thread.objects.filter(mailinglist__name=mailinglist)
  30 + thread = query.get(subject_token=thread_token)
29 thread.update_score() 31 thread.update_score()
30 32
31 template_data = { 33 template_data = {
@@ -44,8 +46,8 @@ def list_messages(request): @@ -44,8 +46,8 @@ def list_messages(request):
44 selected_list = request.GET.get('list') 46 selected_list = request.GET.get('list')
45 47
46 order_by = request.GET.get('order') 48 order_by = request.GET.get('order')
47 - if order_by == 'hotest':  
48 - threads = queries.get_hotest_threads() 49 + if order_by == 'hottest':
  50 + threads = queries.get_hottest_threads()
49 else: 51 else:
50 threads = queries.get_latest_threads() 52 threads = queries.get_latest_threads()
51 53
colab/templates/home.html
@@ -18,7 +18,7 @@ @@ -18,7 +18,7 @@
18 {% block main-content %} 18 {% block main-content %}
19 19
20 <div class="span-12 colborder"> 20 <div class="span-12 colborder">
21 - <h3>{% trans "Últimas Colaborações:" %}</h3> 21 + <h3>{% trans "Últimas Colaborações" %}</h3>
22 <ul> 22 <ul>
23 {% for doc in latest_docs %} 23 {% for doc in latest_docs %}
24 {% include "message-preview.html" %} 24 {% include "message-preview.html" %}
@@ -31,7 +31,7 @@ @@ -31,7 +31,7 @@
31 </div> 31 </div>
32 32
33 <div class="span-11 last"> 33 <div class="span-11 last">
34 - <h3>{% trans "Distribuição das Colaborações:" %}</h3> 34 + <h3>{% trans "Distribuição das Colaborações" %}</h3>
35 <div id="collabs"></div> 35 <div id="collabs"></div>
36 </div> 36 </div>
37 37
@@ -39,20 +39,20 @@ @@ -39,20 +39,20 @@
39 <hr/> 39 <hr/>
40 40
41 <div class="span-12 colborder"> 41 <div class="span-12 colborder">
42 - <h3>{% trans "Discussões Mais Relevantes:" %}</h3> 42 + <h3>{% trans "Discussões Mais Relevantes" %}</h3>
43 <ul> 43 <ul>
44 - {% for thread in hotest_threads %} 44 + {% for thread in hottest_threads %}
45 {% include "message-preview.html" with doc=thread.latest_message %} 45 {% include "message-preview.html" with doc=thread.latest_message %}
46 {% endfor %} 46 {% endfor %}
47 </ul> 47 </ul>
48 <hr class="space"/> 48 <hr class="space"/>
49 - <a class="right" href="{% url thread_list %}?order=hotest"> 49 + <a class="right" href="{% url thread_list %}?order=hottest">
50 {% trans "Ver mais discussões relevantes..." %} 50 {% trans "Ver mais discussões relevantes..." %}
51 </a> 51 </a>
52 </div> 52 </div>
53 53
54 <div class="span-11 last"> 54 <div class="span-11 last">
55 - <h3>{% trans "Últimas Discussões:" %}</h3> 55 + <h3>{% trans "Últimas Discussões" %}</h3>
56 <ul> 56 <ul>
57 {% for thread in latest_threads %} 57 {% for thread in latest_threads %}
58 {% include "message-preview.html" with doc=thread.latest_message %} 58 {% include "message-preview.html" with doc=thread.latest_message %}
colab/templates/signup-form.html
@@ -54,7 +54,12 @@ @@ -54,7 +54,12 @@
54 {% render_form_field form.google_talk %} 54 {% render_form_field form.google_talk %}
55 {% render_form_field form.webpage %} 55 {% render_form_field form.webpage %}
56 </fieldset> 56 </fieldset>
57 - 57 +
  58 + <fieldset class="box span-11">
  59 + <legend>Inscrever-se nas Listas</legend>
  60 + {% render_form_field form.lists %}
  61 + </fieldset>
  62 +
58 <div class="span-24"> 63 <div class="span-24">
59 <input class="right" type="submit" value="Cadastrar"/> 64 <input class="right" type="submit" value="Cadastrar"/>
60 </div> 65 </div>
colab/templates/user-profile.html
@@ -9,20 +9,20 @@ @@ -9,20 +9,20 @@
9 {% block main-content %} 9 {% block main-content %}
10 {% if not user_profile %} 10 {% if not user_profile %}
11 <span class="notice span-24"> 11 <span class="notice span-24">
12 - <b>Usuário não cadastrado.</b> Você é dono deste perfil? 12 + <b>Usuário não cadastrado.</b> Você é dono deste perfil?
13 <a href="{% url signup %}">Clique aqui 13 <a href="{% url signup %}">Clique aqui
14 e cadastre-se.</a> 14 e cadastre-se.</a>
15 </span> 15 </span>
16 16
17 - {% else %}  
18 - 17 + {% else %}
  18 +
19 {% ifequal request.user.username user_profile.user.username %} 19 {% ifequal request.user.username user_profile.user.username %}
20 <span class="success span-24"> 20 <span class="success span-24">
21 - Ei, olha você aqui! Quer 21 + Ei, olha você aqui! Quer
22 <a href="{% url user_profile_update request.user %}">editar seu perfil</a>? 22 <a href="{% url user_profile_update request.user %}">editar seu perfil</a>?
23 </span> 23 </span>
24 {% endifequal %} 24 {% endifequal %}
25 - 25 +
26 {% endif %} 26 {% endif %}
27 27
28 <div id="user-profile"> 28 <div id="user-profile">
@@ -32,12 +32,12 @@ @@ -32,12 +32,12 @@
32 <img class="avatar" width="120px" heigth="120px" 32 <img class="avatar" width="120px" heigth="120px"
33 src="http://www.gravatar.com/avatar/{{ email_address.md5 }}?s=120&d=identicon" /> 33 src="http://www.gravatar.com/avatar/{{ email_address.md5 }}?s=120&d=identicon" />
34 </div> 34 </div>
35 - 35 +
36 <div class="span-20 last"> 36 <div class="span-20 last">
37 <div class="span-10"> 37 <div class="span-10">
38 <form action="{% url user_profile_update request.user %}" method='post'> 38 <form action="{% url user_profile_update request.user %}" method='post'>
39 {% csrf_token %} 39 {% csrf_token %}
40 - 40 +
41 <h3>Informações Pessoais</h3> 41 <h3>Informações Pessoais</h3>
42 <ul id="user-info"> 42 <ul id="user-info">
43 <li> 43 <li>
@@ -53,9 +53,9 @@ @@ -53,9 +53,9 @@
53 {% render_form_field form.role user_profile.role %} 53 {% render_form_field form.role user_profile.role %}
54 </li> 54 </li>
55 </ul> 55 </ul>
56 - 56 +
57 <hr class="space" /> 57 <hr class="space" />
58 - 58 +
59 <h3>Outras Informações</h3> 59 <h3>Outras Informações</h3>
60 <ul> 60 <ul>
61 <li> 61 <li>
@@ -71,7 +71,7 @@ @@ -71,7 +71,7 @@
71 {% render_form_field form.webpage user_profile.webpage %} 71 {% render_form_field form.webpage user_profile.webpage %}
72 </li> 72 </li>
73 </ul> 73 </ul>
74 - 74 +
75 <hr class="space"/> 75 <hr class="space"/>
76 {% if editable %} 76 {% if editable %}
77 <span class="span-5"> 77 <span class="span-5">
@@ -80,30 +80,30 @@ @@ -80,30 +80,30 @@
80 {% endif %} 80 {% endif %}
81 </form> 81 </form>
82 </div> 82 </div>
83 - 83 +
84 {% if type_count %} 84 {% if type_count %}
85 <div class="span-10 last"> 85 <div class="span-10 last">
86 - <h3 class="center">{% trans "Colaborações por área" %}</h3> 86 + <h3 class="center">{% trans "Colaborações por Área" %}</h3>
87 <div id="collabs"></div> 87 <div id="collabs"></div>
88 </div> 88 </div>
89 {% endif %} 89 {% endif %}
90 </div> 90 </div>
91 - 91 +
92 <hr class="space" /> 92 <hr class="space" />
93 - 93 +
94 <div class="span-13"> 94 <div class="span-13">
95 - <h3>{% trans "Últimas mensagens enviadas" %} </h3> 95 + <h3>{% trans "Últimas Mensagens Enviadas" %} </h3>
96 <ul class="colborder"> 96 <ul class="colborder">
97 {% for doc in emails %} 97 {% for doc in emails %}
98 {% include "message-preview.html" %} 98 {% include "message-preview.html" %}
99 {% empty %} 99 {% empty %}
100 <li>Não existem mensagens enviadas por este usuário até o momento.</li> 100 <li>Não existem mensagens enviadas por este usuário até o momento.</li>
101 - {% endfor %} 101 + {% endfor %}
102 </ul> 102 </ul>
103 </div> 103 </div>
104 - 104 +
105 <div class="span-11 last"> 105 <div class="span-11 last">
106 - <h3>{% trans "Participações na comunidade:" %}</h3> 106 + <h3>{% trans "Participações na Comunidade" %}</h3>
107 <ul> 107 <ul>
108 {% for doc in docs %} 108 {% for doc in docs %}
109 {% include "message-preview.html" %} 109 {% include "message-preview.html" %}
@@ -112,6 +112,6 @@ @@ -112,6 +112,6 @@
112 {% endfor %} 112 {% endfor %}
113 </ul> 113 </ul>
114 </div> 114 </div>
115 - 115 +
116 </div> 116 </div>
117 {% endblock %} 117 {% endblock %}
@@ -10,6 +10,8 @@ urlpatterns = patterns(&#39;&#39;, @@ -10,6 +10,8 @@ urlpatterns = patterns(&#39;&#39;,
10 url(r'^archives/', include('colab.super_archives.urls')), 10 url(r'^archives/', include('colab.super_archives.urls')),
11 11
12 url(r'^api/', include('colab.api.urls')), 12 url(r'^api/', include('colab.api.urls')),
  13 +
  14 + url(r'^rss/', include('colab.rss.urls')),
13 15
14 url(r'^user/(?P<username>[\w@+.-]+)/?$', 16 url(r'^user/(?P<username>[\w@+.-]+)/?$',
15 'colab.views.userprofile.by_username', name='user_profile'), 17 'colab.views.userprofile.by_username', name='user_profile'),
@@ -45,7 +47,7 @@ urlpatterns = patterns(&#39;&#39;, @@ -45,7 +47,7 @@ urlpatterns = patterns(&#39;&#39;,
45 47
46 url(r'^account/logout/$', 'django.contrib.auth.views.logout', 48 url(r'^account/logout/$', 'django.contrib.auth.views.logout',
47 {'next_page': '/'}, name='logout'), 49 {'next_page': '/'}, name='logout'),
48 - 50 +
49 # Uncomment the next line to enable the admin: 51 # Uncomment the next line to enable the admin:
50 url(r'^colab/admin/', include(admin.site.urls)), 52 url(r'^colab/admin/', include(admin.site.urls)),
51 ) 53 )
colab/views/other.py
@@ -18,10 +18,10 @@ def home(request): @@ -18,10 +18,10 @@ def home(request):
18 """Index page view""" 18 """Index page view"""
19 19
20 latest_threads = queries.get_latest_threads() 20 latest_threads = queries.get_latest_threads()
21 - hotest_threads = queries.get_hotest_threads() 21 + hottest_threads = queries.get_hottest_threads()
22 22
23 template_data = { 23 template_data = {
24 - 'hotest_threads': hotest_threads[:6], 24 + 'hottest_threads': hottest_threads[:6],
25 'latest_threads': latest_threads[:6], 25 'latest_threads': latest_threads[:6],
26 'type_count': solrutils.count_types(sample=1000), 26 'type_count': solrutils.count_types(sample=1000),
27 'latest_docs': solrutils.get_latest_collaborations(6), 27 'latest_docs': solrutils.get_latest_collaborations(6),
colab/views/signup.py
@@ -60,6 +60,11 @@ def signup(request): @@ -60,6 +60,11 @@ def signup(request):
60 60
61 signup_.send_verification_email(request, user) 61 signup_.send_verification_email(request, user)
62 62
  63 + mailing_lists = form.cleaned_data.get('lists')
  64 + if mailing_lists:
  65 + signup_.send_email_lists(user, mailing_lists)
  66 +
  67 +
63 # Check if the user's email have been used previously 68 # Check if the user's email have been used previously
64 # in the mainling lists to link the user to old messages 69 # in the mainling lists to link the user to old messages
65 email_addr, created = EmailAddress.objects.get_or_create(address=user.email) 70 email_addr, created = EmailAddress.objects.get_or_create(address=user.email)
solr-conf/data-config.xml
@@ -321,7 +321,8 @@ @@ -321,7 +321,8 @@
321 <field column="UID" template="THREAD_${thread.id}" /> 321 <field column="UID" template="THREAD_${thread.id}" />
322 <field column="getId" template="${thread.name}" /> 322 <field column="getId" template="${thread.name}" />
323 <field column="Type" template="thread" /> 323 <field column="Type" template="thread" />
324 - <field column="path_string" template="/archives/thread/${thread.name}" /> 324 + <field column="path_string"
  325 + template="/archives/thread/${thread.mailinglist}/${thread.name}" />
325 <field column="created" name="created" 326 <field column="created" name="created"
326 dateTimeFormat="yyyy-MM-dd hh:mm:ss" /> 327 dateTimeFormat="yyyy-MM-dd hh:mm:ss" />
327 <field column="modified" name="modified" 328 <field column="modified" name="modified"