From 0cb51df4b0e5373a703e896606300331cdfc4dcf Mon Sep 17 00:00:00 2001 From: Sergio Oliveira Date: Sun, 18 Mar 2012 18:31:36 +0000 Subject: [PATCH] Merge com a ultima versao do bitcket: https://bitbucket.org/seocam/atu-colab/src/4ee3ca57614e --- TODO.rst | 20 ++++++++++++-------- colab/rss/__init__.py | 0 colab/rss/feeds.py | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ colab/rss/urls.py | 9 +++++++++ colab/settings.py | 1 + colab/settings_local-dev.py | 8 +++++++- colab/signup.py | 15 +++++++++++++-- colab/solrutils.py | 2 +- colab/super_archives/forms.py | 19 +++++++++++++++++-- colab/super_archives/migrations/0008_add_mailinglist_name_to_url.py | 142 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ colab/super_archives/models.py | 6 ++++-- colab/super_archives/queries.py | 11 +++++++---- colab/super_archives/templates/message-list.html | 4 ++-- colab/super_archives/templatetags/append_to_get.py | 11 ++++++++++- colab/super_archives/templatetags/form_field.py | 25 +++++++++++++------------ colab/super_archives/urls.py | 2 +- colab/super_archives/views.py | 12 +++++++----- colab/templates/home.html | 12 ++++++------ colab/templates/signup-form.html | 7 ++++++- colab/templates/user-profile.html | 38 +++++++++++++++++++------------------- colab/urls.py | 4 +++- colab/views/other.py | 4 ++-- colab/views/signup.py | 5 +++++ solr-conf/data-config.xml | 3 ++- 24 files changed, 363 insertions(+), 71 deletions(-) create mode 100644 colab/rss/__init__.py create mode 100644 colab/rss/feeds.py create mode 100644 colab/rss/urls.py create mode 100644 colab/super_archives/migrations/0008_add_mailinglist_name_to_url.py diff --git a/TODO.rst b/TODO.rst index a5633e2..24ac1a4 100644 --- a/TODO.rst +++ b/TODO.rst @@ -9,9 +9,9 @@ TODO * Yure: Adicionar data do ultimo import de emails no footer * Yure: Detectar links no conteudo e exibi-los como tal * Yure: BUG: Display of HTML emails are wrong -* Yure: Cadastrar usuário em lista pelo formulario de cadastro * 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 -* Criar validador de urls para twitter, facebook e página pessoal do user profile +* BUG: Criar validador de urls para twitter, facebook e página pessoal do user profile +* Mostrar dados do twitter, facebook, gtalk e página pessoal somente para os usuários que estiverem logados * Configurar ADMINS no arquivo settings_local.py * HTTPS para o trac, subversion e colab @@ -20,11 +20,10 @@ TODO * Timezones no trac/colab/solr nao estao compativeis * Template de login nao exibe corretamente no firefox/linux -* Quando usuario se cadastra com email errado o email nunca eh validado, -e o username fica preso 'pra sempre'. +* Quando usuario se cadastra com email errado o email nunca eh validado, e o username fica preso 'pra sempre'. * Nome dos usuarios errado nos emails que vem do Solr * Adicionar ordering na busca -* Criar tipo usuario no solr +* Criar tipo usuario no solr * Substituir sistema de cadastro por django-registration * Utilizar pysolr para efetuar queries no Solr * Melhorar buscas (case insensitive match, palavras com acentos) @@ -40,13 +39,13 @@ e o username fica preso 'pra sempre'. * Merge emails dos usuarios * Implementar badge system * Melhorar filtros -* Link do thread preview deve enviar para mensagem da thread (anchor) (Útil? discutir com Jean) +* Link do thread preview deve enviar para mensagem da thread (anchor) * Tornar todas as strings traduziveis * Sugestão de como a divisão do edital seria melhor * Indexar anexos da wiki (using Tika http://wiki.apache.org/solr/ExtractingRequestHandler) * Filtrar usando calendario (como google analytics) * Melhorar relevancia das buscas usando dismax queryparser -* Chat estilo Gmail +* Chat estilo Gmail usando o mensageiro Interlegis * Sistema de gerencia de conteúdo * Versao mobile * Exibir discussões relacionadas na barra da direita das discussões @@ -56,9 +55,14 @@ e o username fica preso 'pra sempre'. * Contar page views no trac (ticket, wiki e changeset) e utiliza-los para rankear paginas nas buscas * Mostrar highlight nas buscas * Sistema de tags para as mensagens +* Tag cloud para as mensagens, ao lado direito da thread * Pagina home para cada lista com os mesmo filtros da home atual * Permitir que usuario entre e saia de listas ao editar perfil * Planet Interlegis (agregador de blogs) -* Filtros especificos para tipos diferentes na busca +* Filtros específicos para tipos diferentes na busca da thread +* Link para a mensagem original no histórico do Mailman (popup ajax) +* Filtro de mensagens nas listas acumulativos, podendo ligar e desligar todos +* Reduzir campos na tela de cadastro, transferindo-os como aba para a tela de profile, junto com a aba de listas a se inscrever) + * Indice criado manualmente. Automatizar: * create index super_archives_message_body_idx ON super_archives_message ((substring(body,0,1024))); diff --git a/colab/rss/__init__.py b/colab/rss/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/colab/rss/__init__.py diff --git a/colab/rss/feeds.py b/colab/rss/feeds.py new file mode 100644 index 0000000..145b606 --- /dev/null +++ b/colab/rss/feeds.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python +# encoding: utf-8 + +from django.contrib.syndication.views import Feed +from django.utils.translation import ugettext as _ + +from colab.super_archives.models import Thread +from colab.super_archives import queries +from colab import solrutils + +class LatestThreadsFeeds(Feed): + title = _(u'Últimas Discussões') + link = '/rss/threads/latest/' + + def items(self): + return queries.get_latest_threads()[:20] + + def item_link(self, item): + return item.latest_message.url + + def item_title(self, item): + return item.latest_message.subject_clean + + def item_description(self, item): + return item.latest_message.body + + +class HottestThreadsFeeds(Feed): + title = _(u'Discussões Mais Relevantes') + link = '/rss/threads/hottest/' + + def items(self): + return queries.get_hottest_threads()[:20] + + def item_link(self, item): + return item.latest_message.url + + def item_title(self, item): + return item.latest_message.subject_clean + + def item_description(self, item): + return item.latest_message.body + + +class LatestColabFeeds(Feed): + title = _(u'Últimas Colaborações') + link = '/rss/colab/latest/' + + def items(self): + items = solrutils.get_latest_collaborations(20) + return items + + def item_title(self, item): + type_ = item.get('Type') + ': ' + mailinglist = item.get('mailinglist') + + if mailinglist: + prefix = type_ + mailinglist + ' - ' + else: + prefix = type_ + + return prefix + item.get('Title') + + def item_description(self, item): + return item.get('Description') + + def item_link(self, item): + if item.get('Type') != 'thread': + url = item.get('url') + else: + url = 'http://colab.interlegis.leg.br' + url += item.get('url') + return url + diff --git a/colab/rss/urls.py b/colab/rss/urls.py new file mode 100644 index 0000000..cb6d42d --- /dev/null +++ b/colab/rss/urls.py @@ -0,0 +1,9 @@ +from django.conf.urls.defaults import patterns, url +import feeds + +urlpatterns = patterns('', + url(r'threads/latest/$', feeds.LatestThreadsFeeds()), + url(r'colab/latest/$', feeds.LatestColabFeeds()), + url(r'threads/hottest/$', feeds.HottestThreadsFeeds()), +) + diff --git a/colab/settings.py b/colab/settings.py index 9bbe83c..23ffeaa 100644 --- a/colab/settings.py +++ b/colab/settings.py @@ -118,6 +118,7 @@ INSTALLED_APPS = ( # My apps 'colab.super_archives', 'colab.api', + 'colab.rss', ) # A sample logging configuration. The only tangible logging diff --git a/colab/settings_local-dev.py b/colab/settings_local-dev.py index 3c7cee3..23e4d48 100644 --- a/colab/settings_local-dev.py +++ b/colab/settings_local-dev.py @@ -10,9 +10,15 @@ MANAGERS = ADMINS DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': 'colab.db', + 'NAME': 'colab.db', } } # Make this unique, and don't share it with anybody. SECRET_KEY = ')(jksdfhsjkadfhjkh234ns!8fqu-1186h$vuj' + +import socks +SOCKS_TYPE = socks.PROXY_TYPE_SOCKS5 +SOCKS_SERVER = '127.0.0.1' +SOCKS_PORT = 9050 + diff --git a/colab/signup.py b/colab/signup.py index 76b36fc..8b0a5cc 100644 --- a/colab/signup.py +++ b/colab/signup.py @@ -4,8 +4,8 @@ from django.conf import settings from django.utils.html import strip_tags from django.utils.translation import ugettext as _ -from django.core.mail import EmailMultiAlternatives from django.template.loader import render_to_string +from django.core.mail import EmailMultiAlternatives, send_mail def send_verification_email(request, user): @@ -47,4 +47,15 @@ def send_reset_password_email(request, user): email_msg.attach_alternative(html_content, 'text/html') email_msg.send() - \ No newline at end of file +def send_email_lists(user, mailing_lists): + subject = _(u'Inscrição na lista de discussão') + from_ = user.email + to = [] + for list_name in mailing_lists: + # TODO: The following line needs to be generic. Domain should be stored in settings file + # or database (perharps read directly from mailman). + subscribe_addr = list_name + '-subscribe@listas.interlegis.gov.br' + to.append(subscribe_addr) + + send_mail(subject, '', from_, to) + diff --git a/colab/solrutils.py b/colab/solrutils.py index 595e35c..3282cf1 100644 --- a/colab/solrutils.py +++ b/colab/solrutils.py @@ -178,7 +178,7 @@ def select(query, results_per_page=None, page_number=None, sort=None, fields=Non if socks_server: import socks logging.debug('Socks enabled: %s:%s', settings.SOCKS_SERVER, - settings.SOLR_PORT) + settings.SOCKS_PORT) socks.setdefaultproxy(settings.SOCKS_TYPE, settings.SOCKS_SERVER, diff --git a/colab/super_archives/forms.py b/colab/super_archives/forms.py index e22b14a..a078e11 100644 --- a/colab/super_archives/forms.py +++ b/colab/super_archives/forms.py @@ -5,6 +5,7 @@ from django.core.exceptions import ValidationError from django.contrib.auth.models import User from django.contrib.auth.forms import UserCreationForm as UserCreationForm_ +from colab.super_archives.models import MailingList from colab.super_archives.validators import UniqueValidator # 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) google_talk_field = forms.EmailField(label=u'Google Talk', required=False) webpage_field = forms.URLField(label=u'Página Pessoal/Blog', required=False) - +all_lists = MailingList.objects.all() +lists_names = [] +for list_ in all_lists: + choice = (list_.name, list_.name) + lists_names.append(choice) + +lists_field = forms.MultipleChoiceField( + label=u'Listas', + required=False, + widget=forms.CheckboxSelectMultiple, + choices=lists_names +) + + class UserCreationForm(UserCreationForm_): first_name = first_name_field last_name = last_name_field @@ -37,8 +51,9 @@ class UserCreationForm(UserCreationForm_): facebook = facebook_field google_talk = google_talk_field webpage = webpage_field + lists = lists_field - + class UserUpdateForm(forms.Form): username = username_field username.required = False diff --git a/colab/super_archives/migrations/0008_add_mailinglist_name_to_url.py b/colab/super_archives/migrations/0008_add_mailinglist_name_to_url.py new file mode 100644 index 0000000..a5193f4 --- /dev/null +++ b/colab/super_archives/migrations/0008_add_mailinglist_name_to_url.py @@ -0,0 +1,142 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models + +class Migration(DataMigration): + + def forwards(self, orm): + page_hits = orm.PageHit.objects.all() + for page_hit in page_hits: + if page_hit.url_path.startswith('/archives/thread/'): + token = page_hit.url_path.split('/')[-1] + threads = orm.Thread.objects.filter(subject_token=token) + if not len(threads): continue + thread = threads[0] + new_url = '/archives/thread/%s/%s' % (thread.mailinglist.name, + thread.subject_token) + page_hit.url_path = new_url + page_hit.save() + + + def backwards(self, orm): + raise RuntimeError("Cannot reverse this migration.") + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'super_archives.emailaddress': { + 'Meta': {'object_name': 'EmailAddress'}, + 'address': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'md5': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}), + 'real_name': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '64', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'emails'", 'null': 'True', 'to': "orm['auth.User']"}) + }, + 'super_archives.mailinglist': { + 'Meta': {'object_name': 'MailingList'}, + 'description': ('django.db.models.fields.TextField', [], {}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_imported_index': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'logo': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}) + }, + 'super_archives.mailinglistmembership': { + 'Meta': {'object_name': 'MailingListMembership'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'mailinglist': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['super_archives.MailingList']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'super_archives.message': { + 'Meta': {'object_name': 'Message'}, + 'body': ('django.db.models.fields.TextField', [], {'default': "''"}), + 'from_address': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['super_archives.EmailAddress']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'mailinglist': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['super_archives.MailingList']"}), + 'message_id': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'received_time': ('django.db.models.fields.DateTimeField', [], {}), + 'spam': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'subject': ('django.db.models.fields.CharField', [], {'max_length': '512', 'db_index': 'True'}), + 'subject_clean': ('django.db.models.fields.CharField', [], {'max_length': '512', 'db_index': 'True'}), + 'thread': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['super_archives.Thread']", 'null': 'True'}) + }, + 'super_archives.messagemetadata': { + 'Message': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['super_archives.Message']"}), + 'Meta': {'object_name': 'MessageMetadata'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'value': ('django.db.models.fields.TextField', [], {}) + }, + 'super_archives.pagehit': { + 'Meta': {'object_name': 'PageHit'}, + 'hit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'url_path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '2048', 'db_index': 'True'}) + }, + 'super_archives.thread': { + 'Meta': {'unique_together': "(('subject_token', 'mailinglist'),)", 'object_name': 'Thread'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'latest_message': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'+'", 'unique': 'True', 'null': 'True', 'to': "orm['super_archives.Message']"}), + 'mailinglist': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['super_archives.MailingList']"}), + 'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'spam': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'subject_token': ('django.db.models.fields.CharField', [], {'max_length': '512'}) + }, + 'super_archives.userprofile': { + 'Meta': {'object_name': 'UserProfile'}, + 'facebook': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}), + 'google_talk': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'institution': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}), + 'role': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}), + 'twitter': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'}), + 'verification_hash': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}), + 'webpage': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + }, + 'super_archives.vote': { + 'Meta': {'unique_together': "(('user', 'message'),)", 'object_name': 'Vote'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'message': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['super_archives.Message']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + } + } + + complete_apps = ['super_archives'] diff --git a/colab/super_archives/models.py b/colab/super_archives/models.py index 76a723a..88062c2 100644 --- a/colab/super_archives/models.py +++ b/colab/super_archives/models.py @@ -148,7 +148,8 @@ class Thread(models.Model): # Calculate page_view_score try: - url = reverse('thread_view', args=[self.subject_token]) + url = reverse('thread_view', args=[self.mailinglist.name, + self.subject_token]) pagehit = PageHit.objects.get(url_path=url) page_view_score = pagehit.hit_count * 10 except (NoReverseMatch, PageHit.DoesNotExist): @@ -217,7 +218,8 @@ class Message(models.Model): @property def url(self): """Shortcut to get thread url""" - return reverse('thread_view', args=[self.thread.subject_token]) + return reverse('thread_view', args=[self.mailinglist.name, + self.thread.subject_token]) @property def Description(self): diff --git a/colab/super_archives/queries.py b/colab/super_archives/queries.py index eac21c1..ecaba3e 100644 --- a/colab/super_archives/queries.py +++ b/colab/super_archives/queries.py @@ -26,15 +26,18 @@ def get_messages_by_voted(): return messages.order_by('-vote_count', 'received_time') -def get_first_message_in_thread(thread_token): - return get_messages_by_date().filter(thread__subject_token=thread_token)[0] - +def get_first_message_in_thread(mailinglist, thread_token): + query = get_messages_by_date() + query = query.filter(mailinglist__name=mailinglist) + query = query.filter(thread__subject_token=thread_token)[0] + return query + def get_latest_threads(): return Thread.objects.order_by('-latest_message__received_time') -def get_hotest_threads(): +def get_hottest_threads(): return Thread.objects.order_by('-score', '-latest_message__received_time') diff --git a/colab/super_archives/templates/message-list.html b/colab/super_archives/templates/message-list.html index 319cbbe..97bf3fc 100644 --- a/colab/super_archives/templates/message-list.html +++ b/colab/super_archives/templates/message-list.html @@ -11,8 +11,8 @@

Ordenar por