From 0625388dc18ff6d216c897b0e2b6c3cd09565750 Mon Sep 17 00:00:00 2001 From: seocam Date: Wed, 25 Jan 2012 21:46:40 +0000 Subject: [PATCH] Entrega de numero 5 do Edital ATU-COLAB publicado em 2011 --- MANIFEST.in | 5 +++++ README.rst | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ TODO | 0 TODO.rst | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ colab/api/handlers.py | 31 ++++++++++++++++++++++++++++++- colab/api/urls.py | 4 +++- colab/django.wsgi | 27 --------------------------- colab/settings.py | 50 ++++++++++++++++++++++++++++++++++++++++---------- colab/settings_local-dev.py | 18 ++++++++++++++++++ colab/signup.py | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ colab/socks.py | 387 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ colab/solrutils.py | 264 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ colab/static/css/screen.css | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++---- colab/static/img/COPYRIGHT | 10 ++++++++++ colab/static/img/changeset.png | Bin 0 -> 158 bytes colab/static/img/favicon.ico | Bin 0 -> 5430 bytes colab/static/img/logo_small.png | Bin 3004 -> 0 bytes colab/static/img/plus.png | Bin 0 -> 397 bytes colab/static/img/thread.png | Bin 0 -> 208 bytes colab/static/img/thumbs_up.jpg | Bin 5016 -> 0 bytes colab/static/img/ticket.png | Bin 0 -> 270 bytes colab/static/img/user.png | Bin 0 -> 442 bytes colab/static/img/wiki.png | Bin 0 -> 202 bytes colab/static/img/x.png | Bin 0 -> 274 bytes colab/static/js/base.js | 24 ++++++++++++++++++++++-- colab/super_archives/admin.py | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++-------- colab/super_archives/fixtures/initial_data.json | 10 ++++++++++ colab/super_archives/forms.py | 14 +++++++------- colab/super_archives/management/commands/import_emails.py | 61 ++++++++++++++++++++++++++++++++++++++++++++++--------------- colab/super_archives/migrations/0001_initial.py | 255 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ colab/super_archives/migrations/0002_auto__add_field_userprofile_verification_hash.py | 133 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ colab/super_archives/migrations/0003_auto__add_field_thread_score.py | 134 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ colab/super_archives/migrations/0004_auto__add_field_vote_created.py | 135 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ colab/super_archives/migrations/0005_auto__add_field_message_spam__add_field_thread_spam.py | 143 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ colab/super_archives/migrations/0006_auto.py | 143 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ colab/super_archives/migrations/0007_auto.py | 137 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ colab/super_archives/migrations/__init__.py | 0 colab/super_archives/models.py | 140 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------- colab/super_archives/queries.py | 36 ++++++++++-------------------------- colab/super_archives/templates/message-list.html | 24 ++++++++++++++---------- colab/super_archives/templates/message-preview.html | 52 ++++++++++++++++++++++++++++++++++++---------------- colab/super_archives/templates/message-thread.html | 43 +++++++++++++++++++++++++++++++++++-------- colab/super_archives/templatetags/append_to_get.py | 13 +++++-------- colab/super_archives/templatetags/form_field.py | 13 ++++++++++--- colab/super_archives/urls.py | 6 ++++-- colab/super_archives/validators.py | 2 +- colab/super_archives/views.py | 36 +++++++++++++++++++++++++----------- colab/templates/404.html | 1 + colab/templates/500.html | 1 + colab/templates/account_change_password.html | 24 ++++++++++++++++++++++++ colab/templates/account_message.html | 10 ++++++++++ colab/templates/account_request_reset_password.html | 18 ++++++++++++++++++ colab/templates/base.html | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------------- colab/templates/email_account-reset-password.html | 19 +++++++++++++++++++ colab/templates/email_signup-email-confirmation.html | 9 +++++++++ colab/templates/home.html | 72 +++++++++++++++++++++++++++++++++++++++++++++++------------------------- colab/templates/login.html | 13 +++++++------ colab/templates/pizza-chart.html | 45 +++++++++++++++++++++++++++++++++++++++++++++ colab/templates/search.html | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ colab/templates/signup-form.html | 14 +++++++++++++- colab/templates/user-profile.html | 106 +++++++++++++++++++++++++++++++++++++++++++++++++--------------------------------------------------------- colab/urls.py | 43 ++++++++++++++++++++++++++++++------------- colab/views.py | 159 --------------------------------------------------------------------------------------------------------------------------------------------------------------- colab/views/__init__.py | 2 ++ colab/views/other.py | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ colab/views/signup.py | 218 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ colab/views/userprofile.py | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ etc/apache2/sites-available/colab | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ etc/apache2/wsgi/colab.wsgi | 12 ++++++++++++ etc/autofs/listas | 1 + etc/cron.d/colab_import_emails | 2 ++ etc/cron.d/colab_solr_reindex | 3 +++ requirements.txt | 5 +++-- setup.py | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ setupext/__init__.py | 15 +++++++++++++++ setupext/install_data.py | 197 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ solr-conf/README | 46 ++++++++++++++++++++++++++++++++++++++++++++++ solr-conf/data-config.xml | 337 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ solr-conf/schema.xml | 612 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ solr-conf/solr-tomcat.xml | 12 ++++++++++++ solr-conf/solrconfig.xml | 1535 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sorl-conf/README | 16 ---------------- sorl-conf/data-config.xml | 314 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- sorl-conf/schema.xml | 499 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- sorl-conf/solrconfig.xml | 1040 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 85 files changed, 6219 insertions(+), 2342 deletions(-) delete mode 100644 TODO create mode 100644 TODO.rst delete mode 100644 colab/django.wsgi create mode 100644 colab/settings_local-dev.py create mode 100644 colab/signup.py create mode 100644 colab/socks.py create mode 100644 colab/solrutils.py create mode 100644 colab/static/img/COPYRIGHT create mode 100644 colab/static/img/changeset.png create mode 100644 colab/static/img/favicon.ico create mode 100644 colab/static/img/plus.png create mode 100644 colab/static/img/thread.png delete mode 100644 colab/static/img/thumbs_up.jpg create mode 100644 colab/static/img/ticket.png create mode 100644 colab/static/img/user.png create mode 100644 colab/static/img/wiki.png create mode 100644 colab/static/img/x.png create mode 100644 colab/super_archives/fixtures/initial_data.json create mode 100644 colab/super_archives/migrations/0001_initial.py create mode 100644 colab/super_archives/migrations/0002_auto__add_field_userprofile_verification_hash.py create mode 100644 colab/super_archives/migrations/0003_auto__add_field_thread_score.py create mode 100644 colab/super_archives/migrations/0004_auto__add_field_vote_created.py create mode 100644 colab/super_archives/migrations/0005_auto__add_field_message_spam__add_field_thread_spam.py create mode 100644 colab/super_archives/migrations/0006_auto.py create mode 100644 colab/super_archives/migrations/0007_auto.py create mode 100644 colab/super_archives/migrations/__init__.py create mode 100644 colab/templates/404.html create mode 100644 colab/templates/500.html create mode 100644 colab/templates/account_change_password.html create mode 100644 colab/templates/account_message.html create mode 100644 colab/templates/account_request_reset_password.html create mode 100644 colab/templates/email_account-reset-password.html create mode 100644 colab/templates/email_signup-email-confirmation.html create mode 100644 colab/templates/pizza-chart.html create mode 100644 colab/templates/search.html delete mode 100644 colab/views.py create mode 100644 colab/views/__init__.py create mode 100644 colab/views/other.py create mode 100644 colab/views/signup.py create mode 100644 colab/views/userprofile.py create mode 100644 etc/apache2/sites-available/colab create mode 100644 etc/apache2/wsgi/colab.wsgi create mode 100644 etc/autofs/listas create mode 100644 etc/cron.d/colab_import_emails create mode 100644 etc/cron.d/colab_solr_reindex create mode 100755 setupext/__init__.py create mode 100755 setupext/install_data.py create mode 100644 solr-conf/README create mode 100644 solr-conf/data-config.xml create mode 100644 solr-conf/schema.xml create mode 100644 solr-conf/solr-tomcat.xml create mode 100644 solr-conf/solrconfig.xml delete mode 100644 sorl-conf/README delete mode 100644 sorl-conf/data-config.xml delete mode 100644 sorl-conf/schema.xml delete mode 100644 sorl-conf/solrconfig.xml diff --git a/MANIFEST.in b/MANIFEST.in index e69de29..faf4ba0 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -0,0 +1,5 @@ +include colab/templates/*.html +include colab/super_archives/templates/*.html +include colab/super_archives/fixtures/initial_data.json +recursive-include etc * +recursive-include colab/static * \ No newline at end of file diff --git a/README.rst b/README.rst index e69de29..59c32f6 100644 --- a/README.rst +++ b/README.rst @@ -0,0 +1,106 @@ +Installation instructions for Ubuntu 10.04 +------------------------------------------- + +* Install Apache2 with WSGI support: + + * apt-get install apache2 libapache2-mod-wsgi + +* Install dependencies to compile psycopg2: + + * apt-get build-dep python-psycopg2 + +* Install Python PIP and update it: + + * apt-get install python-pip + * pip install -U pip + +* Install python virtualenv: + + * pip install virtualenv + +* Create a virtualenv for the deploy + + * mkdir /usr/local/django/ + * virtualenv /usr/local/django/colab/ + +* Download the colab src code: + + * hg clone https://bitbucket.org/seocam/atu-colab /usr/local/src/colab/ + +* Install the django site: + + * pip install /usr/local/src/colab -E /usr/local/django/colab/ + +* Configure your database settings in /usr/local/django/colab/lib/python2.6/site-packages/settings_local.py + +* Enable the colab site on apache and reload it: + + * ln -s /usr/local/django/colab/apache-site/colab /etc/apache2/sites-available + * a2ensite colab + * service apache2 restart + + +Configuring server to send emails +---------------------------------- + +* Install postfix and mailutils: + + * apt-get install mailutils postfix + +* Update the file /etc/aliases adding users that should receive root's messages and run the update command: + + * newaliases + + +Cron job to import emails +--------------------------- + +* Install sshfs: + + * apt-get install sshfs autofs + +* Create SSH keys. You should use a password but this tutorial won't cover it (if you use you will need to install and configure keychain process to be able to proceed): + + * ssh-keygen + +* Copy the content of your key (/root/.ssh/id_rsa.pub) to the file /root/.ssh/authorized_keys on the mailinglist server. + +* Append the following content to /etc/auto.master file: + + * /usr/local/django/colab/mnt /usr/local/django/colab/autofs/listas --timeout=600,--ghost + +* Restart autofs: + + * service autofs restart + +* Link cron script into /etc/cron.d/ folder: + + * ln -s /usr/local/django/colab/cron.d/colab_import_emails /etc/cron.d/ + +* From now on the emails should be imported every minute + + +Cron job to reindex Solr +------------------------- + +* Install wget: + + * apt-get install wget + +* Link cron script into /etc/cron.d/ folder: + + * ln -s /usr/local/django/colab/cron.d/colab_solr_reindex /etc/cron.d/ + +* From now on delta reindex should run every 10 minutes and full reindex once a day. + + +Updating an installed version +------------------------------ + +* Update the source code: + + * cd /usr/local/src/colab/ + * hg pull + * hg up + * pip install /usr/local/src/colab/ -E /usr/local/django/colab/ -U + * service apache2 restart \ No newline at end of file diff --git a/TODO b/TODO deleted file mode 100644 index e69de29..0000000 --- a/TODO +++ /dev/null diff --git a/TODO.rst b/TODO.rst new file mode 100644 index 0000000..a5633e2 --- /dev/null +++ b/TODO.rst @@ -0,0 +1,64 @@ +TODO +----- + +* Sergio: Permitir que usuario atualize nome e sobrenome + +* Jean: Pagina Reporte um problema +* Jean: Pagina Contribua + +* 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 + +* Configurar ADMINS no arquivo settings_local.py +* HTTPS para o trac, subversion e colab +* Autorizar usuarios a commitar no svn pelo django.contrib.admin +* Enviar emails para usuarios pedindo que se cadastrem no novo colab +* 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'. +* Nome dos usuarios errado nos emails que vem do Solr +* Adicionar ordering na busca +* 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) +* Indexar nome do repositorio como campo e exibi-lo no titulo dos changesets retornados +* Criacao de repositorios distribuidos pela interface do colab +* Link para última msg recebida na thread +* Fazer thread querysets ter um objeto (most_relevant_message) +* Implementar enviar email +* BUG: alguns subjects comecam e terminam com [] fazendo com que a RE de limpeza apague todo o subject. +* BUG: mensagens importadas por listas erradas +* Implementar login/signup usando LDAP +* Claime email address +* Merge emails dos usuarios +* Implementar badge system +* Melhorar filtros +* Link do thread preview deve enviar para mensagem da thread (anchor) (Útil? discutir com Jean) +* 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 +* Sistema de gerencia de conteúdo +* Versao mobile +* Exibir discussões relacionadas na barra da direita das discussões +* Migração e reorganização do conteúdo do trac/wiki para o novo colab +* Ao importar mensagem sem subject enviar email avisando o usuario que ele esta enviando um email sem o campos "Assunto" +* Remover ou ocultar trechos da mensagem que iniciem com ">" assim como o Gmail. +* 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 +* 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 +* Indice criado manualmente. Automatizar: + * create index super_archives_message_body_idx ON super_archives_message ((substring(body,0,1024))); diff --git a/colab/api/handlers.py b/colab/api/handlers.py index e729be9..57a1cf0 100644 --- a/colab/api/handlers.py +++ b/colab/api/handlers.py @@ -1,4 +1,5 @@ +from django.core.cache import cache from django.db import IntegrityError from django.core.exceptions import ObjectDoesNotExist from django.contrib.auth.decorators import login_required @@ -6,7 +7,7 @@ from django.contrib.auth.decorators import login_required from piston.utils import rc from piston.handler import BaseHandler -from super_archives.models import Message +from colab.super_archives.models import Message, PageHit class VoteHandler(BaseHandler): @@ -38,5 +39,33 @@ class VoteHandler(BaseHandler): return rc.DELETED +class CountHandler(BaseHandler): + allowed_methods = ('POST') + + def create(self, request): + """Add one page view for the given url""" + + # If missing the path_info argument we can't do anything + path_info = request.POST.get('path_info') + if not path_info: + return rc.BAD_REQUEST + + # Here we cache the user's IP to ensure that the same + # IP won't hit the same page again for while + ip_addr = request.META.get('REMOTE_ADDR') + page_hits_cache = cache.get('page_hits', {}) + duplicate = page_hits_cache.get(path_info, {}).get(ip_addr) + if duplicate: + return rc.DUPLICATE_ENTRY + else: + page_hits_cache.update({path_info: {ip_addr: True }}) + cache.set('page_hits', page_hits_cache) + + # Everything ok, so just increment the page count + page_hit = PageHit.objects.get_or_create(url_path=path_info)[0] + page_hit.hit_count += 1 + page_hit.save() + + return rc.CREATED diff --git a/colab/api/urls.py b/colab/api/urls.py index f823995..c6deb97 100644 --- a/colab/api/urls.py +++ b/colab/api/urls.py @@ -2,11 +2,13 @@ from django.conf.urls.defaults import patterns, include, url from piston.resource import Resource -from api.handlers import VoteHandler +from colab.api.handlers import VoteHandler, CountHandler vote_handler = Resource(VoteHandler) +count_handler = Resource(CountHandler) urlpatterns = patterns('', url(r'message/(?P\d+)/vote$', vote_handler), + url(r'hit/$', count_handler), ) \ No newline at end of file diff --git a/colab/django.wsgi b/colab/django.wsgi deleted file mode 100644 index 6ddb198..0000000 --- a/colab/django.wsgi +++ /dev/null @@ -1,27 +0,0 @@ -import sys -import site -import os - -app_path = os.path.abspath(os.path.dirname(__file__)) -app_parent_path = os.path.abspath(os.path.join(app_path, os.path.pardir)) - -vepath = '/home/seocam/.virtualenvs/colab/lib/python2.6/site-packages' - -prev_sys_path = list(sys.path) -# add the site-packages of our virtualenv as a site dir -site.addsitedir(vepath) -# add the app's directory to the PYTHONPATH -sys.path.append(app_path) -sys.path.append(app_parent_path) - -# reorder sys.path so new directories from the addsitedir show up first -new_sys_path = [p for p in sys.path if p not in prev_sys_path] -for item in new_sys_path: - sys.path.remove(item) -sys.path[:0] = new_sys_path - -# import from down here to pull in possible virtualenv django install -from django.core.handlers.wsgi import WSGIHandler -os.environ['DJANGO_SETTINGS_MODULE'] = 'colab.settings' -application = WSGIHandler() - diff --git a/colab/settings.py b/colab/settings.py index 4065e33..9bbe83c 100644 --- a/colab/settings.py +++ b/colab/settings.py @@ -5,8 +5,12 @@ import os.path DEBUG = True TEMPLATE_DEBUG = DEBUG +if DEBUG: + import logging + logging.root.setLevel(logging.DEBUG) + + ADMINS = ( - # ('Your Name', 'your_email@example.com'), ) PROJECT_PATH = os.path.abspath(os.path.dirname(__file__)) @@ -22,7 +26,7 @@ LOGIN_URL = '/login/' # timezone as the operating system. # If running in a Windows environment this must be set to the same as your # system time zone. -TIME_ZONE = 'America/Chicago' +TIME_ZONE = 'America/Sao_Paulo' # Language code for this installation. All choices can be found here: # http://www.i18nguy.com/unicode/language-identifiers.html @@ -39,12 +43,12 @@ USE_I18N = True USE_L10N = True MEDIA_ROOT = os.path.join(PROJECT_PATH, os.pardir, 'site_media/media') -MEDIA_URL = '/site_media/media/' +MEDIA_URL = '/media/' STATIC_ROOT = os.path.join(PROJECT_PATH, os.pardir, 'site_media/static') -STATIC_URL = '/site_media/static/' +STATIC_URL = '/static/' -#ADMIN_MEDIA_PREFIX = '/site_media/static/admin/' +ADMIN_MEDIA_PREFIX = '/static/admin/' # Additional locations of static files STATICFILES_DIRS = ( @@ -66,12 +70,23 @@ TEMPLATE_LOADERS = ( # 'django.template.loaders.eggs.Loader', ) +TEMPLATE_CONTEXT_PROCESSORS = ( + 'django.contrib.auth.context_processors.auth', + 'django.core.context_processors.debug', + 'django.core.context_processors.i18n', + 'django.core.context_processors.media', + 'django.core.context_processors.static', + 'django.contrib.messages.context_processors.messages', + 'django.core.context_processors.request', +) + MIDDLEWARE_CLASSES = ( 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', + #'debug_toolbar.middleware.DebugToolbarMiddleware', ) ROOT_URLCONF = 'colab.urls' @@ -91,17 +106,18 @@ INSTALLED_APPS = ( 'django.contrib.messages', 'django.contrib.staticfiles', # Uncomment the next line to enable the admin: - # 'django.contrib.admin', + 'django.contrib.admin', # Uncomment the next line to enable admin documentation: # 'django.contrib.admindocs', # Not standard apps 'south', - 'registration', + 'cliauth', + #'debug_toolbar', # My apps - 'super_archives', - 'api', + 'colab.super_archives', + 'colab.api', ) # A sample logging configuration. The only tangible logging @@ -115,7 +131,8 @@ LOGGING = { 'handlers': { 'mail_admins': { 'level': 'ERROR', - 'class': 'django.utils.log.AdminEmailHandler' + 'class': 'django.utils.log.AdminEmailHandler', + 'include_html': True, } }, 'loggers': { @@ -127,5 +144,18 @@ LOGGING = { } } +SERVER_EMAIL = '"Colab Interlegis" ' +EMAIL_HOST_USER = SERVER_EMAIL + +#SOLR_HOSTNAME = 'solr.interlegis.leg.br' +SOLR_HOSTNAME = '10.1.2.154' +SOLR_PORT = '8080' +SOLR_SELECT_PATH = '/solr/select' + +SOLR_COLAB_URI = 'http://colab.interlegis.gov.br' +SOLR_BASE_QUERY = """ + (Type:changeset OR Type:ticket OR Type:wiki OR Type:thread) +""" + from settings_local import * diff --git a/colab/settings_local-dev.py b/colab/settings_local-dev.py new file mode 100644 index 0000000..3c7cee3 --- /dev/null +++ b/colab/settings_local-dev.py @@ -0,0 +1,18 @@ + +DEBUG = True +TEMPLATE_DEBUG = DEBUG + +ADMINS = ( +) + +MANAGERS = ADMINS + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': 'colab.db', + } +} + +# Make this unique, and don't share it with anybody. +SECRET_KEY = ')(jksdfhsjkadfhjkh234ns!8fqu-1186h$vuj' diff --git a/colab/signup.py b/colab/signup.py new file mode 100644 index 0000000..76b36fc --- /dev/null +++ b/colab/signup.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python +# encoding: utf-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 + + +def send_verification_email(request, user): + + subject = _(u'Colab: Verificação de email') + from_ = settings.SERVER_EMAIL + to = user.email + + email_data = { + 'hash': user.profile.verification_hash, + 'server_name': request.get_host(), + } + + html_content = render_to_string('email_signup-email-confirmation.html', + email_data) + text_content = strip_tags(html_content) + email_msg = EmailMultiAlternatives(subject, text_content, from_, [to]) + email_msg.attach_alternative(html_content, 'text/html') + email_msg.send() + + +def send_reset_password_email(request, user): + + subject = _(u'Altereção de senha do Colab Interlegis') + from_ = settings.SERVER_EMAIL + to = user.email + + email_data = { + 'hash': user.profile.verification_hash, + 'server_name': request.get_host(), + 'username': user.username, + } + + html_content = render_to_string('email_account-reset-password.html', + email_data) + text_content = strip_tags(html_content) + + email_msg = EmailMultiAlternatives(subject, text_content, from_, [to]) + email_msg.attach_alternative(html_content, 'text/html') + email_msg.send() + + \ No newline at end of file diff --git a/colab/socks.py b/colab/socks.py new file mode 100644 index 0000000..7cc1bc3 --- /dev/null +++ b/colab/socks.py @@ -0,0 +1,387 @@ +"""SocksiPy - Python SOCKS module. +Version 1.00 + +Copyright 2006 Dan-Haim. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +3. Neither the name of Dan Haim nor the names of his contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE. + + +This module provides a standard socket-like interface for Python +for tunneling connections through SOCKS proxies. + +""" + +import socket +import struct + +PROXY_TYPE_SOCKS4 = 1 +PROXY_TYPE_SOCKS5 = 2 +PROXY_TYPE_HTTP = 3 + +_defaultproxy = None +_orgsocket = socket.socket + +class ProxyError(Exception): + def __init__(self, value): + self.value = value + def __str__(self): + return repr(self.value) + +class GeneralProxyError(ProxyError): + def __init__(self, value): + self.value = value + def __str__(self): + return repr(self.value) + +class Socks5AuthError(ProxyError): + def __init__(self, value): + self.value = value + def __str__(self): + return repr(self.value) + +class Socks5Error(ProxyError): + def __init__(self, value): + self.value = value + def __str__(self): + return repr(self.value) + +class Socks4Error(ProxyError): + def __init__(self, value): + self.value = value + def __str__(self): + return repr(self.value) + +class HTTPError(ProxyError): + def __init__(self, value): + self.value = value + def __str__(self): + return repr(self.value) + +_generalerrors = ("success", + "invalid data", + "not connected", + "not available", + "bad proxy type", + "bad input") + +_socks5errors = ("succeeded", + "general SOCKS server failure", + "connection not allowed by ruleset", + "Network unreachable", + "Host unreachable", + "Connection refused", + "TTL expired", + "Command not supported", + "Address type not supported", + "Unknown error") + +_socks5autherrors = ("succeeded", + "authentication is required", + "all offered authentication methods were rejected", + "unknown username or invalid password", + "unknown error") + +_socks4errors = ("request granted", + "request rejected or failed", + "request rejected because SOCKS server cannot connect to identd on the client", + "request rejected because the client program and identd report different user-ids", + "unknown error") + +def setdefaultproxy(proxytype=None,addr=None,port=None,rdns=True,username=None,password=None): + """setdefaultproxy(proxytype, addr[, port[, rdns[, username[, password]]]]) + Sets a default proxy which all further socksocket objects will use, + unless explicitly changed. + """ + global _defaultproxy + _defaultproxy = (proxytype,addr,port,rdns,username,password) + +class socksocket(socket.socket): + """socksocket([family[, type[, proto]]]) -> socket object + + Open a SOCKS enabled socket. The parameters are the same as + those of the standard socket init. In order for SOCKS to work, + you must specify family=AF_INET, type=SOCK_STREAM and proto=0. + """ + + def __init__(self, family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0, _sock=None): + _orgsocket.__init__(self,family,type,proto,_sock) + if _defaultproxy != None: + self.__proxy = _defaultproxy + else: + self.__proxy = (None, None, None, None, None, None) + self.__proxysockname = None + self.__proxypeername = None + + def __recvall(self, bytes): + """__recvall(bytes) -> data + Receive EXACTLY the number of bytes requested from the socket. + Blocks until the required number of bytes have been received. + """ + data = "" + while len(data) < bytes: + data = data + self.recv(bytes-len(data)) + return data + + def setproxy(self,proxytype=None,addr=None,port=None,rdns=True,username=None,password=None): + """setproxy(proxytype, addr[, port[, rdns[, username[, password]]]]) + Sets the proxy to be used. + proxytype - The type of the proxy to be used. Three types + are supported: PROXY_TYPE_SOCKS4 (including socks4a), + PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP + addr - The address of the server (IP or DNS). + port - The port of the server. Defaults to 1080 for SOCKS + servers and 8080 for HTTP proxy servers. + rdns - Should DNS queries be preformed on the remote side + (rather than the local side). The default is True. + Note: This has no effect with SOCKS4 servers. + username - Username to authenticate with to the server. + The default is no authentication. + password - Password to authenticate with to the server. + Only relevant when username is also provided. + """ + self.__proxy = (proxytype,addr,port,rdns,username,password) + + def __negotiatesocks5(self,destaddr,destport): + """__negotiatesocks5(self,destaddr,destport) + Negotiates a connection through a SOCKS5 server. + """ + # First we'll send the authentication packages we support. + if (self.__proxy[4]!=None) and (self.__proxy[5]!=None): + # The username/password details were supplied to the + # setproxy method so we support the USERNAME/PASSWORD + # authentication (in addition to the standard none). + self.sendall("\x05\x02\x00\x02") + else: + # No username/password were entered, therefore we + # only support connections with no authentication. + self.sendall("\x05\x01\x00") + # We'll receive the server's response to determine which + # method was selected + chosenauth = self.__recvall(2) + if chosenauth[0] != "\x05": + self.close() + raise GeneralProxyError((1,_generalerrors[1])) + # Check the chosen authentication method + if chosenauth[1] == "\x00": + # No authentication is required + pass + elif chosenauth[1] == "\x02": + # Okay, we need to perform a basic username/password + # authentication. + self.sendall("\x01" + chr(len(self.__proxy[4])) + self.__proxy[4] + chr(len(self.proxy[5])) + self.__proxy[5]) + authstat = self.__recvall(2) + if authstat[0] != "\x01": + # Bad response + self.close() + raise GeneralProxyError((1,_generalerrors[1])) + if authstat[1] != "\x00": + # Authentication failed + self.close() + raise Socks5AuthError,((3,_socks5autherrors[3])) + # Authentication succeeded + else: + # Reaching here is always bad + self.close() + if chosenauth[1] == "\xFF": + raise Socks5AuthError((2,_socks5autherrors[2])) + else: + raise GeneralProxyError((1,_generalerrors[1])) + # Now we can request the actual connection + req = "\x05\x01\x00" + # If the given destination address is an IP address, we'll + # use the IPv4 address request even if remote resolving was specified. + try: + ipaddr = socket.inet_aton(destaddr) + req = req + "\x01" + ipaddr + except socket.error: + # Well it's not an IP number, so it's probably a DNS name. + if self.__proxy[3]==True: + # Resolve remotely + ipaddr = None + req = req + "\x03" + chr(len(destaddr)) + destaddr + else: + # Resolve locally + ipaddr = socket.inet_aton(socket.gethostbyname(destaddr)) + req = req + "\x01" + ipaddr + req = req + struct.pack(">H",destport) + self.sendall(req) + # Get the response + resp = self.__recvall(4) + if resp[0] != "\x05": + self.close() + raise GeneralProxyError((1,_generalerrors[1])) + elif resp[1] != "\x00": + # Connection failed + self.close() + if ord(resp[1])<=8: + raise Socks5Error(ord(resp[1]),_generalerrors[ord(resp[1])]) + else: + raise Socks5Error(9,_generalerrors[9]) + # Get the bound address/port + elif resp[3] == "\x01": + boundaddr = self.__recvall(4) + elif resp[3] == "\x03": + resp = resp + self.recv(1) + boundaddr = self.__recvall(resp[4]) + else: + self.close() + raise GeneralProxyError((1,_generalerrors[1])) + boundport = struct.unpack(">H",self.__recvall(2))[0] + self.__proxysockname = (boundaddr,boundport) + if ipaddr != None: + self.__proxypeername = (socket.inet_ntoa(ipaddr),destport) + else: + self.__proxypeername = (destaddr,destport) + + def getproxysockname(self): + """getsockname() -> address info + Returns the bound IP address and port number at the proxy. + """ + return self.__proxysockname + + def getproxypeername(self): + """getproxypeername() -> address info + Returns the IP and port number of the proxy. + """ + return _orgsocket.getpeername(self) + + def getpeername(self): + """getpeername() -> address info + Returns the IP address and port number of the destination + machine (note: getproxypeername returns the proxy) + """ + return self.__proxypeername + + def __negotiatesocks4(self,destaddr,destport): + """__negotiatesocks4(self,destaddr,destport) + Negotiates a connection through a SOCKS4 server. + """ + # Check if the destination address provided is an IP address + rmtrslv = False + try: + ipaddr = socket.inet_aton(destaddr) + except socket.error: + # It's a DNS name. Check where it should be resolved. + if self.__proxy[3]==True: + ipaddr = "\x00\x00\x00\x01" + rmtrslv = True + else: + ipaddr = socket.inet_aton(socket.gethostbyname(destaddr)) + # Construct the request packet + req = "\x04\x01" + struct.pack(">H",destport) + ipaddr + # The username parameter is considered userid for SOCKS4 + if self.__proxy[4] != None: + req = req + self.__proxy[4] + req = req + "\x00" + # DNS name if remote resolving is required + # NOTE: This is actually an extension to the SOCKS4 protocol + # called SOCKS4A and may not be supported in all cases. + if rmtrslv==True: + req = req + destaddr + "\x00" + self.sendall(req) + # Get the response from the server + resp = self.__recvall(8) + if resp[0] != "\x00": + # Bad data + self.close() + raise GeneralProxyError((1,_generalerrors[1])) + if resp[1] != "\x5A": + # Server returned an error + self.close() + if ord(resp[1]) in (91,92,93): + self.close() + raise Socks4Error((ord(resp[1]),_socks4errors[ord(resp[1])-90])) + else: + raise Socks4Error((94,_socks4errors[4])) + # Get the bound address/port + self.__proxysockname = (socket.inet_ntoa(resp[4:]),struct.unpack(">H",resp[2:4])[0]) + if rmtrslv != None: + self.__proxypeername = (socket.inet_ntoa(ipaddr),destport) + else: + self.__proxypeername = (destaddr,destport) + + def __negotiatehttp(self,destaddr,destport): + """__negotiatehttp(self,destaddr,destport) + Negotiates a connection through an HTTP server. + """ + # If we need to resolve locally, we do this now + if self.__proxy[3] == False: + addr = socket.gethostbyname(destaddr) + else: + addr = destaddr + self.sendall("CONNECT " + addr + ":" + str(destport) + " HTTP/1.1\r\n" + "Host: " + destaddr + "\r\n\r\n") + # We read the response until we get the string "\r\n\r\n" + resp = self.recv(1) + while resp.find("\r\n\r\n")==-1: + resp = resp + self.recv(1) + # We just need the first line to check if the connection + # was successful + statusline = resp.splitlines()[0].split(" ",2) + if statusline[0] not in ("HTTP/1.0","HTTP/1.1"): + self.close() + raise GeneralProxyError((1,_generalerrors[1])) + try: + statuscode = int(statusline[1]) + except ValueError: + self.close() + raise GeneralProxyError((1,_generalerrors[1])) + if statuscode != 200: + self.close() + raise HTTPError((statuscode,statusline[2])) + self.__proxysockname = ("0.0.0.0",0) + self.__proxypeername = (addr,destport) + + def connect(self,destpair): + """connect(self,despair) + Connects to the specified destination through a proxy. + destpar - A tuple of the IP/DNS address and the port number. + (identical to socket's connect). + To select the proxy server use setproxy(). + """ + # Do a minimal input check first + if (type(destpair) in (list,tuple)==False) or (len(destpair)<2) or (type(destpair[0])!=str) or (type(destpair[1])!=int): + raise GeneralProxyError((5,_generalerrors[5])) + if self.__proxy[0] == PROXY_TYPE_SOCKS5: + if self.__proxy[2] != None: + portnum = self.__proxy[2] + else: + portnum = 1080 + _orgsocket.connect(self,(self.__proxy[1],portnum)) + self.__negotiatesocks5(destpair[0],destpair[1]) + elif self.__proxy[0] == PROXY_TYPE_SOCKS4: + if self.__proxy[2] != None: + portnum = self.__proxy[2] + else: + portnum = 1080 + _orgsocket.connect(self,(self.__proxy[1],portnum)) + self.__negotiatesocks4(destpair[0],destpair[1]) + elif self.__proxy[0] == PROXY_TYPE_HTTP: + if self.__proxy[2] != None: + portnum = self.__proxy[2] + else: + portnum = 8080 + _orgsocket.connect(self,(self.__proxy[1],portnum)) + self.__negotiatehttp(destpair[0],destpair[1]) + elif self.__proxy[0] == None: + _orgsocket.connect(self,(destpair[0],destpair[1])) + else: + raise GeneralProxyError((4,_generalerrors[4])) diff --git a/colab/solrutils.py b/colab/solrutils.py new file mode 100644 index 0000000..595e35c --- /dev/null +++ b/colab/solrutils.py @@ -0,0 +1,264 @@ +#!/usr/bin/env python +# encoding: utf-8 + +import math +import json +import urllib +import socket +import logging +import httplib + +from dateutil.parser import parse as parse_timestamp + +from django.conf import settings + +from colab.super_archives.models import EmailAddress + + +def build_query(user_query, filters=None): + """Build the query that will be sent to Solr""" + + if not user_query: + user_query = '*' + + query = settings.SOLR_BASE_QUERY.strip() + ' AND ' + user_query + if filters: + query = "(%s)" % query + + for (key, value) in filters.items(): + if value: + query += " AND %s:%s" % (key, value) + + logging.info(query) + return query.encode('utf-8') + + +def parse_document_timestamps(doc, date_attrs=('modified', 'created')): + """Converts the `modified' and `created' dates from + ISO 8601 format to a date time object for the given + document. + + """ + + for date in date_attrs: + date_str = doc.get(date) + try: + date_obj = parse_timestamp(date_str) + except ValueError: + logging.error('Error trying to parse "%s"', date_str) + date_obj = None + doc.update({date: date_obj}) + + return doc + + +def get_document_url(doc): + """Set the url attribute for a document using the path_string. + In case the resource comes from an external domain it will + be prepended to this URL. + + """ + doc_type = doc.get('Type') + + url = '' + if doc_type in ('ticket', 'wiki', 'changeset'): + url += settings.SOLR_COLAB_URI + + url += doc.get('path_string', '') + doc.update({'url': url}) + + return doc + + +def get_document_from_addr(doc): + """Get a EmailAddress instance for the given document if + its available. + + """ + + username = doc.get('Creator') + from_addresses = EmailAddress.objects.filter(user__username=username) + if username and from_addresses: + doc.update({'from_address': from_addresses[0]}) + + +def add_attrs_to_doc(doc): + """Wraps the call of functions that adds or modifies keys + of the giving doc (which should be a dict). + + """ + get_document_url(doc) + parse_document_timestamps(doc) + get_document_from_addr(doc) + + +class SolrPaginator(list): + + def __init__(self, response_dict, current_page): + super(SolrPaginator, self).__init__() + + responseHeader = response_dict.get('responseHeader', {}) + response = response_dict.get('response', {}) + request_params = responseHeader.get('params', {}) + + docs = response.get('docs', []) + self.extend(docs) + + self.QTime = int(responseHeader.get('QTime', 1)) / 1000.0 + + self.per_page = int(request_params.get('rows', 10)) + self.numFound = int(response.get('numFound', 0)) + self.page_num = current_page + + self.num_of_pages = int(math.ceil(self.numFound / float(self.per_page))) + + self.has_previous = self.page_num > 1 + if self.has_previous: + self.previous_page_number = self.page_num - 1 + else: + self.previous_page_number = None + + self.has_next = self.page_num < self.num_of_pages + if self.has_next: + self.next_page_number = self.page_num + 1 + else: + self.next_page_number = None + + @property + def last_page(self): + return self.num_of_pages + + +def select(query, results_per_page=None, page_number=None, sort=None, fields=None, link_attrs=True): + """Perform a select in a Solr instance using the configuration + set in settings.py. + + """ + + data = { + 'q': query, + 'wt': 'json', + } + + # Number of results per page + if results_per_page: + data.update({'rows': results_per_page}) + + # Page number + if page_number: + data.update({ + 'start': (page_number - 1) * results_per_page, + }) + + # Sort order + if sort: + data.update({ + 'sort': sort, + }) + + # Only select those fields + if fields: + data.update({ + 'fl': ','.join(fields), + }) + # First version of this was implemented using urllib2 and was + # a milion times easier but unfortunatelly urllib2.urlopen + # does not support http headers. Without setting http headers + # for charset the solr server tries to decode utf-8 params + # as ASCII causing it to crash. HTTPConnection deals with + # encodings automagically. + solr_conn = httplib.HTTPConnection(settings.SOLR_HOSTNAME, + settings.SOLR_PORT) + query_params = urllib.urlencode(data) + solr_select_uri = settings.SOLR_SELECT_PATH + '?' + query_params + + # Socks proxy configuration. Only required for development + # if the solr server is behind a firewall. + socks_server = getattr(settings, "SOCKS_SERVER", None) + if socks_server: + import socks + logging.debug('Socks enabled: %s:%s', settings.SOCKS_SERVER, + settings.SOLR_PORT) + + socks.setdefaultproxy(settings.SOCKS_TYPE, + settings.SOCKS_SERVER, + settings.SOCKS_PORT) + socket.socket = socks.socksocket + + try: + solr_conn.request('GET', solr_select_uri) + solr_response = solr_conn.getresponse() + except socket.error as err: + solr_response = None + logging.exception(err) + + if solr_response and solr_response.status == 200: + #TODO: Log error connecting to solr + solr_json_resp = solr_response.read() + solr_dict_resp = json.loads(solr_json_resp) + else: + solr_dict_resp = {} + + docs = solr_dict_resp.get('response', {}).get("docs", []) + + if link_attrs: + # Loop over all documents adding or linking its information + # with the data from this app or database + map(add_attrs_to_doc, docs) + + return solr_dict_resp + + +def get_latest_collaborations(number=10, username=None): + """Get the n documents recently modified that this username + has helped in somehow. + + """ + + if username: + filters = {'collaborator': username} + else: + filters = None + + query = build_query('*', filters) + solr_response = select( + query=query, + results_per_page=number, + sort='modified desc' + ) + + return solr_response.get('response', {}).get('docs', []) + + +def count_types(sample=100, filters=None): + """Count the type of the last modifications returning the + results in dict. + + Example: { + 'wiki' 30, + 'thread': 40, + 'ticket', 10, + 'changeset' 20, + } + + """ + + query = build_query('*', filters) + solr_response = select( + query=query, + results_per_page=sample, + sort='modified desc', + link_attrs=False, + ) + + docs = solr_response.get('response', {}).get('docs', []) + + type_count = {} + for doc in docs: + doc_type = doc.get('Type') + count = type_count.get(doc_type, 0) + 1 + type_count.update({doc_type: count}) + + return type_count + + diff --git a/colab/static/css/screen.css b/colab/static/css/screen.css index da8f9d4..14eebba 100644 --- a/colab/static/css/screen.css +++ b/colab/static/css/screen.css @@ -25,6 +25,9 @@ h4 { margin-bottom: 10px; } +form { + display: inline; +} #main-content { margin-bottom: 20px; @@ -48,16 +51,24 @@ h4 { text-decoration: none; } +.preview-message img { + margin-right: 5px; +} + /* Header */ #header-searchbox { - width: 150px; + width: 290px; +} + +#header-menu span { + line-height: 33px; } /* Thread view */ .plus { - border: 1px dashed #ddd; + border: 1px dotted #ddd; margin: 0; } @@ -72,8 +83,13 @@ h4 { .plus img { vertical-align: middle; - height: 40px; cursor: pointer; + margin-top: 8px; + margin-right: 20px; +} + +.plus img:hover { + opacity: 0.8; } /* User Profile */ @@ -125,11 +141,32 @@ input[type="password"] { color: #335; } +.subject img { + margin-right: 5px; +} + .avatar { border: 1px solid #ddd; padding: 3px; } +div.avatar-placeholder { + border: 1px solid #cccccc; +} + +.avatar-image { + float: left; + margin-right: 10px; + height: 40px; + padding: 8px 12px 0; + background-color: white; +} + +label.avatar-placeholder { + display: block; + margin-left: 6px; +} + .tag { background-color: #2183b3; padding: 2px 4px; @@ -173,4 +210,16 @@ img.center { .filters li:before { content: "\00BB\0020"; -} \ No newline at end of file +} + + +.filters .legend li span { + display: inline-block; + width: 25px; +} + +.filters .legend li:before { + content: ''; +} + + diff --git a/colab/static/img/COPYRIGHT b/colab/static/img/COPYRIGHT new file mode 100644 index 0000000..5e88aad --- /dev/null +++ b/colab/static/img/COPYRIGHT @@ -0,0 +1,10 @@ +The icons listed bellow were copied from the Iconic icons package. The icons in this set were originally designed for the Franklin Street WordPress theme and are available under CC Attribution-Share Alike 3.0 license - http://creativecommons.org/licenses/by-sa/3.0/us/ + +* ticket.png +* changeset.png +* thread.png +* wiki.png +* x.png +* plus.png + +The full Iconic package can be found here: https://github.com/downloads/somerandomdude/Iconic/iconic.zip diff --git a/colab/static/img/changeset.png b/colab/static/img/changeset.png new file mode 100644 index 0000000..80e3ee1 Binary files /dev/null and b/colab/static/img/changeset.png differ diff --git a/colab/static/img/favicon.ico b/colab/static/img/favicon.ico new file mode 100644 index 0000000..d5df681 Binary files /dev/null and b/colab/static/img/favicon.ico differ diff --git a/colab/static/img/logo_small.png b/colab/static/img/logo_small.png index f2620e9..7e657ff 100644 Binary files a/colab/static/img/logo_small.png and b/colab/static/img/logo_small.png differ diff --git a/colab/static/img/plus.png b/colab/static/img/plus.png new file mode 100644 index 0000000..16194ef Binary files /dev/null and b/colab/static/img/plus.png differ diff --git a/colab/static/img/thread.png b/colab/static/img/thread.png new file mode 100644 index 0000000..c429cbe Binary files /dev/null and b/colab/static/img/thread.png differ diff --git a/colab/static/img/thumbs_up.jpg b/colab/static/img/thumbs_up.jpg deleted file mode 100644 index 553fc61..0000000 Binary files a/colab/static/img/thumbs_up.jpg and /dev/null differ diff --git a/colab/static/img/ticket.png b/colab/static/img/ticket.png new file mode 100644 index 0000000..09fcc0c Binary files /dev/null and b/colab/static/img/ticket.png differ diff --git a/colab/static/img/user.png b/colab/static/img/user.png new file mode 100644 index 0000000..f027000 Binary files /dev/null and b/colab/static/img/user.png differ diff --git a/colab/static/img/wiki.png b/colab/static/img/wiki.png new file mode 100644 index 0000000..c267a33 Binary files /dev/null and b/colab/static/img/wiki.png differ diff --git a/colab/static/img/x.png b/colab/static/img/x.png new file mode 100644 index 0000000..0a766db Binary files /dev/null and b/colab/static/img/x.png differ diff --git a/colab/static/js/base.js b/colab/static/js/base.js index 97aab21..2743a8d 100644 --- a/colab/static/js/base.js +++ b/colab/static/js/base.js @@ -5,6 +5,7 @@ function vote_callback(msg_id, step) { return parseInt(count) + step; }); jQuery('#msg-' + msg_id + ' .minus').toggleClass('hide'); + jQuery('#vote-notification').addClass('hide'); } } @@ -21,6 +22,18 @@ function get_vote_ajax_dict(msg_id, type_) { url: "/api/message/" + msg_id + "/vote", type: type_, success: vote_callback(msg_id, step), + error: function (jqXHR, textStatus, errorThrown) { + + error_msg = 'Seu voto não foi computado.' + if (jqXHR.status === 401) { + error_msg += ' Você deve estar autenticado para votar.'; + } else { + error_msg += ' Erro desconhecido ao tentando votar.'; + } + + jQuery('#vote-notification').html(error_msg).removeClass('hide'); + scroll(0, 0); + } } } @@ -35,7 +48,6 @@ function unvote(msg_id) { jQuery(document).ready(function() { jQuery('.email_message').each(function() { var msg_id = this.getAttribute('id').split('-')[1]; - console.debug(msg_id); jQuery('.plus img', this).bind('click', function() { vote(msg_id); }); @@ -44,4 +56,12 @@ jQuery(document).ready(function() { return false; }); }); -}); \ No newline at end of file +}); + +function pagehit(path_info) { + jQuery.ajax({ + url: '/api/hit/', + type: 'POST', + data: {'path_info': path_info}, + }); +} diff --git a/colab/super_archives/admin.py b/colab/super_archives/admin.py index b106f3d..37e4055 100644 --- a/colab/super_archives/admin.py +++ b/colab/super_archives/admin.py @@ -1,10 +1,54 @@ from django.contrib import admin -from super_archives.models import MailingList, MailingListMembership, \ - Message, MessageMetadata, Vote - -admin.site.register(MailingList) -admin.site.register(MailingListMembership) -admin.site.register(Message) -admin.site.register(MessageMetadata) -admin.site.register(Vote) +from colab.super_archives.models import Message, Thread, UserProfile + +class MessageAdmin(admin.ModelAdmin): + list_filter = ('spam', 'mailinglist', 'received_time', ) + search_fields = ( + 'id', + 'subject', + 'subject_clean', + 'body', + 'from_address__real_name', + 'from_address__address', + 'from_address__user__first_name', + 'from_address__user__last_name', + 'from_address__user__username', + ) + readonly_fields = ('thread', 'from_address', 'mailinglist') + + +class ThreadAdmin(admin.ModelAdmin): + list_filter = ('spam', 'mailinglist', 'message__received_time',) + search_fields = ( + 'id', + 'subject_token', + 'message__subject', + 'message__subject_clean', + 'message__from_address__real_name', + 'message__from_address__address', + 'message__from_address__user__first_name', + 'message__from_address__user__last_name', + 'message__from_address__user__username', + ) + + readonly_fields = ( + 'mailinglist', + 'subject_token', + 'latest_message', + 'score', + ) + + fields = ( + 'mailinglist', + 'subject_token', + 'latest_message', + 'score', + 'spam', + ) + + +admin.site.register(UserProfile) +admin.site.register(Thread, ThreadAdmin) +admin.site.register(Message, MessageAdmin) + diff --git a/colab/super_archives/fixtures/initial_data.json b/colab/super_archives/fixtures/initial_data.json new file mode 100644 index 0000000..42c25fb --- /dev/null +++ b/colab/super_archives/fixtures/initial_data.json @@ -0,0 +1,10 @@ +[ + { + "pk": 1, + "model": "auth.group", + "fields": { + "name": "developer", + "permissions": [] + } + } +] diff --git a/colab/super_archives/forms.py b/colab/super_archives/forms.py index 851e5e3..e22b14a 100644 --- a/colab/super_archives/forms.py +++ b/colab/super_archives/forms.py @@ -1,15 +1,15 @@ -# -*- coding: utf8 -*- +# -*- coding: utf-8 -*- from django import forms from django.core.exceptions import ValidationError from django.contrib.auth.models import User from django.contrib.auth.forms import UserCreationForm as UserCreationForm_ -from super_archives.validators import UniqueValidator +from colab.super_archives.validators import UniqueValidator # XXX: I know that this code does not look nice AT ALL. -# probably it should be implemented using formsets instead of the hacking -# below. Feel free to improve it! :) +# probably it should be implemented using formsets instead of +# the hack below. Feel free to improve it! :) # User fields username_field = UserCreationForm_().fields.get('username') @@ -21,10 +21,10 @@ email_field = forms.EmailField(validators=[UniqueValidator(User, 'email')]) institution_field = forms.CharField(max_length=120, label=u'Instituição', required=False) role_field = forms.CharField(max_length=60, label='Função', required=False) -twitter_field = forms.CharField(label=u'Twitter', required=False) -facebook_field = forms.CharField(label=u'Facebook', required=False) +twitter_field = forms.URLField(label=u'Twitter', required=False) +facebook_field = forms.URLField(label=u'Facebook', required=False) google_talk_field = forms.EmailField(label=u'Google Talk', required=False) -webpage_field = forms.CharField(label=u'Página Pessoal/Blog', required=False) +webpage_field = forms.URLField(label=u'Página Pessoal/Blog', required=False) class UserCreationForm(UserCreationForm_): diff --git a/colab/super_archives/management/commands/import_emails.py b/colab/super_archives/management/commands/import_emails.py index f2ab579..34a6ec8 100644 --- a/colab/super_archives/management/commands/import_emails.py +++ b/colab/super_archives/management/commands/import_emails.py @@ -1,10 +1,11 @@ #!/usr/bin/env python -# -*- encoding: utf8 -*- +# -*- encoding: utf-8 -*- """Import emails from a mailman storage to the django database.""" import os import re +import sys import mailbox from optparse import make_option @@ -13,8 +14,10 @@ from django.template.defaultfilters import slugify from django.core.exceptions import ObjectDoesNotExist from django.core.management.base import BaseCommand, CommandError -from super_archives.models import MailingList, Message, Thread, EmailAddress -from super_archives.management.commands.message import Message as CustomMessage +from colab.super_archives.models import MailingList, Message, \ + Thread, EmailAddress +from colab.super_archives.management.commands.message import Message as \ + CustomMessage class Command(BaseCommand, object): @@ -35,7 +38,14 @@ class Command(BaseCommand, object): help='Path of email archives to be imported. (default: %s)' % default_archives_path, default=default_archives_path), - + + make_option('--exclude-list', + dest='exclude_lists', + help=("Mailing list that won't be imported. It can be used many" + "times for more than one list."), + action='append', + default=None), + make_option('--all', dest='all', help='Import all messages (default: False)', @@ -81,11 +91,12 @@ class Command(BaseCommand, object): key += 1 yield key-1, mbox[key-1] - def get_emails(self, mailinglist_dir, all): + def get_emails(self, mailinglist_dir, all, exclude_lists): """Generator function that get the emails from each mailing list dump dirctory. If `all` is set to True all the emails in the mbox will be imported if not it will just resume from the last - message previously imported. + message previously imported. The lists set in `exclude_lists` + won't be imported. Yield: A tuple in the form: (mailing list name, email message). @@ -101,6 +112,10 @@ class Command(BaseCommand, object): mbox_path = os.path.join(mailinglist_dir, mbox, mbox) mailinglist_name = mbox.split('.')[0] + # Check if the mailinglist is set not to be imported + if exclude_lists and mailinglist_name in exclude_lists: + continue + # Find the index of the last imported message if all: n_msgs = 0 @@ -154,10 +169,7 @@ class Command(BaseCommand, object): email_addr = self.EMAIL_ADDR_CACHE.get(from_) if email_addr is None: email_addr = EmailAddress.objects.get_or_create( - address=from_, - real_name=real_name[:64] - )[0] - + address=from_)[0] self.EMAIL_ADDR_CACHE[from_] = email_addr if not email_addr.real_name and real_name: @@ -179,16 +191,18 @@ class Command(BaseCommand, object): email.save() @transaction.commit_manually - def import_emails(self, archives_path, all): + def import_emails(self, archives_path, all, exclude_lists=None): """Get emails from the filesystem from the `archives_path` and store them into the database. If `all` is set to True all - the filesystem storage will be imported otherwise the importation - will resume from the last message previously imported. + the filesystem storage will be imported otherwise the + importation will resume from the last message previously + imported. The lists set in `exclude_lists` won't be imported. """ count = 0 - for mailinglist_name, msg, index in self.get_emails(archives_path, all): + email_generator = self.get_emails(archives_path, all, exclude_lists) + for mailinglist_name, msg, index in email_generator: try: self.save_email(mailinglist_name, msg, index) except: @@ -206,6 +220,20 @@ class Command(BaseCommand, object): def handle(self, *args, **options): """Main command method.""" + lock_file = '/var/lock/colab/import_emails.lock' + + # Already running, so quit + if os.path.exists(lock_file): + self.log(("This script is already running. (If your are sure it's " + "not please delete the lock file in %s')") % lock_file) + sys.exit(0) + + if not os.path.exists(os.path.dirname(lock_file)): + os.mkdir(os.path.dirname(lock_file), 0755) + + run_lock = file(lock_file, 'w') + run_lock.close() + archives_path = options.get('archives_path') self.log('Using archives_path `%s`' % self.default_archives_path) @@ -213,5 +241,8 @@ class Command(BaseCommand, object): raise CommandError('archives_path (%s) does not exist' % archives_path) - self.import_emails(archives_path, options.get('all')) + self.import_emails(archives_path, + options.get('all'), options.get('exclude_lists')) + + os.remove(lock_file) \ No newline at end of file diff --git a/colab/super_archives/migrations/0001_initial.py b/colab/super_archives/migrations/0001_initial.py new file mode 100644 index 0000000..cbf3c99 --- /dev/null +++ b/colab/super_archives/migrations/0001_initial.py @@ -0,0 +1,255 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding model 'PageHit' + db.create_table('super_archives_pagehit', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('url_path', self.gf('django.db.models.fields.CharField')(unique=True, max_length=2048, db_index=True)), + ('hit_count', self.gf('django.db.models.fields.IntegerField')(default=0)), + )) + db.send_create_signal('super_archives', ['PageHit']) + + # Adding model 'EmailAddress' + db.create_table('super_archives_emailaddress', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('user', self.gf('django.db.models.fields.related.ForeignKey')(related_name='emails', null=True, to=orm['auth.User'])), + ('address', self.gf('django.db.models.fields.EmailField')(unique=True, max_length=75)), + ('real_name', self.gf('django.db.models.fields.CharField')(max_length=64, blank=True)), + ('md5', self.gf('django.db.models.fields.CharField')(max_length=32, null=True)), + )) + db.send_create_signal('super_archives', ['EmailAddress']) + + # Adding model 'UserProfile' + db.create_table('super_archives_userprofile', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('user', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['auth.User'], unique=True)), + ('institution', self.gf('django.db.models.fields.CharField')(max_length=128, null=True)), + ('role', self.gf('django.db.models.fields.CharField')(max_length=128, null=True)), + ('twitter', self.gf('django.db.models.fields.CharField')(max_length=128, null=True)), + ('facebook', self.gf('django.db.models.fields.CharField')(max_length=128, null=True)), + ('google_talk', self.gf('django.db.models.fields.EmailField')(max_length=75, null=True)), + ('webpage', self.gf('django.db.models.fields.CharField')(max_length=256)), + )) + db.send_create_signal('super_archives', ['UserProfile']) + + # Adding model 'MailingList' + db.create_table('super_archives_mailinglist', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=80)), + ('email', self.gf('django.db.models.fields.EmailField')(max_length=75)), + ('description', self.gf('django.db.models.fields.TextField')()), + ('logo', self.gf('django.db.models.fields.files.FileField')(max_length=100)), + ('last_imported_index', self.gf('django.db.models.fields.IntegerField')(default=0)), + )) + db.send_create_signal('super_archives', ['MailingList']) + + # Adding model 'MailingListMembership' + db.create_table('super_archives_mailinglistmembership', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), + ('mailinglist', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['super_archives.MailingList'])), + )) + db.send_create_signal('super_archives', ['MailingListMembership']) + + # Adding model 'Thread' + db.create_table('super_archives_thread', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('subject_token', self.gf('django.db.models.fields.CharField')(max_length=512)), + ('mailinglist', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['super_archives.MailingList'])), + ('latest_message', self.gf('django.db.models.fields.related.OneToOneField')(related_name='+', unique=True, null=True, to=orm['super_archives.Message'])), + )) + db.send_create_signal('super_archives', ['Thread']) + + # Adding unique constraint on 'Thread', fields ['subject_token', 'mailinglist'] + db.create_unique('super_archives_thread', ['subject_token', 'mailinglist_id']) + + # Adding model 'Vote' + db.create_table('super_archives_vote', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), + ('message', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['super_archives.Message'])), + )) + db.send_create_signal('super_archives', ['Vote']) + + # Adding unique constraint on 'Vote', fields ['user', 'message'] + db.create_unique('super_archives_vote', ['user_id', 'message_id']) + + # Adding model 'Message' + db.create_table('super_archives_message', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('from_address', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['super_archives.EmailAddress'])), + ('mailinglist', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['super_archives.MailingList'])), + ('thread', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['super_archives.Thread'], null=True)), + ('subject', self.gf('django.db.models.fields.CharField')(max_length=512)), + ('subject_clean', self.gf('django.db.models.fields.CharField')(max_length=512)), + ('body', self.gf('django.db.models.fields.TextField')(default='')), + ('received_time', self.gf('django.db.models.fields.DateTimeField')()), + ('message_id', self.gf('django.db.models.fields.CharField')(max_length=512)), + )) + db.send_create_signal('super_archives', ['Message']) + + # Adding model 'MessageMetadata' + db.create_table('super_archives_messagemetadata', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('Message', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['super_archives.Message'])), + ('name', self.gf('django.db.models.fields.CharField')(max_length=512)), + ('value', self.gf('django.db.models.fields.TextField')()), + )) + db.send_create_signal('super_archives', ['MessageMetadata']) + + + def backwards(self, orm): + + # Removing unique constraint on 'Vote', fields ['user', 'message'] + db.delete_unique('super_archives_vote', ['user_id', 'message_id']) + + # Removing unique constraint on 'Thread', fields ['subject_token', 'mailinglist'] + db.delete_unique('super_archives_thread', ['subject_token', 'mailinglist_id']) + + # Deleting model 'PageHit' + db.delete_table('super_archives_pagehit') + + # Deleting model 'EmailAddress' + db.delete_table('super_archives_emailaddress') + + # Deleting model 'UserProfile' + db.delete_table('super_archives_userprofile') + + # Deleting model 'MailingList' + db.delete_table('super_archives_mailinglist') + + # Deleting model 'MailingListMembership' + db.delete_table('super_archives_mailinglistmembership') + + # Deleting model 'Thread' + db.delete_table('super_archives_thread') + + # Deleting model 'Vote' + db.delete_table('super_archives_vote') + + # Deleting model 'Message' + db.delete_table('super_archives_message') + + # Deleting model 'MessageMetadata' + db.delete_table('super_archives_messagemetadata') + + + 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', [], {'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', [], {}), + 'subject': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'subject_clean': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + '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']"}), + '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'}), + 'webpage': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + }, + 'super_archives.vote': { + 'Meta': {'unique_together': "(('user', 'message'),)", 'object_name': 'Vote'}, + '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/migrations/0002_auto__add_field_userprofile_verification_hash.py b/colab/super_archives/migrations/0002_auto__add_field_userprofile_verification_hash.py new file mode 100644 index 0000000..a47db14 --- /dev/null +++ b/colab/super_archives/migrations/0002_auto__add_field_userprofile_verification_hash.py @@ -0,0 +1,133 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding field 'UserProfile.verification_hash' + db.add_column('super_archives_userprofile', 'verification_hash', self.gf('django.db.models.fields.CharField')(max_length=32, null=True), keep_default=False) + + + def backwards(self, orm): + + # Deleting field 'UserProfile.verification_hash' + db.delete_column('super_archives_userprofile', 'verification_hash') + + + 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', [], {'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', [], {}), + 'subject': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'subject_clean': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + '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']"}), + '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'}, + '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/migrations/0003_auto__add_field_thread_score.py b/colab/super_archives/migrations/0003_auto__add_field_thread_score.py new file mode 100644 index 0000000..afb9069 --- /dev/null +++ b/colab/super_archives/migrations/0003_auto__add_field_thread_score.py @@ -0,0 +1,134 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding field 'Thread.score' + db.add_column('super_archives_thread', 'score', self.gf('django.db.models.fields.IntegerField')(default=0), keep_default=False) + + + def backwards(self, orm): + + # Deleting field 'Thread.score' + db.delete_column('super_archives_thread', 'score') + + + 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', [], {'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', [], {}), + 'subject': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'subject_clean': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + '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'}), + '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'}, + '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/migrations/0004_auto__add_field_vote_created.py b/colab/super_archives/migrations/0004_auto__add_field_vote_created.py new file mode 100644 index 0000000..9be9e33 --- /dev/null +++ b/colab/super_archives/migrations/0004_auto__add_field_vote_created.py @@ -0,0 +1,135 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding field 'Vote.created' + db.add_column('super_archives_vote', 'created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, default=datetime.datetime(2012, 1, 19, 18, 8, 46, 813949), blank=True), keep_default=False) + + + def backwards(self, orm): + + # Deleting field 'Vote.created' + db.delete_column('super_archives_vote', 'created') + + + 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', [], {'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', [], {}), + 'subject': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'subject_clean': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + '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'}), + '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/migrations/0005_auto__add_field_message_spam__add_field_thread_spam.py b/colab/super_archives/migrations/0005_auto__add_field_message_spam__add_field_thread_spam.py new file mode 100644 index 0000000..1fd669d --- /dev/null +++ b/colab/super_archives/migrations/0005_auto__add_field_message_spam__add_field_thread_spam.py @@ -0,0 +1,143 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding field 'Message.spam' + db.add_column('super_archives_message', 'spam', self.gf('django.db.models.fields.BooleanField')(default=False), keep_default=False) + + # Adding field 'Thread.spam' + db.add_column('super_archives_thread', 'spam', self.gf('django.db.models.fields.BooleanField')(default=False), keep_default=False) + + + def backwards(self, orm): + + # Deleting field 'Message.spam' + db.delete_column('super_archives_message', 'spam') + + # Deleting field 'Thread.spam' + db.delete_column('super_archives_thread', 'spam') + + + 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', [], {'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'}), + 'subject_clean': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + '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/migrations/0006_auto.py b/colab/super_archives/migrations/0006_auto.py new file mode 100644 index 0000000..bfbad99 --- /dev/null +++ b/colab/super_archives/migrations/0006_auto.py @@ -0,0 +1,143 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding index on 'Message', fields ['subject_clean'] + db.create_index('super_archives_message', ['subject_clean']) + + # Adding index on 'Message', fields ['subject'] + db.create_index('super_archives_message', ['subject']) + + + def backwards(self, orm): + + # Removing index on 'Message', fields ['subject'] + db.delete_index('super_archives_message', ['subject']) + + # Removing index on 'Message', fields ['subject_clean'] + db.delete_index('super_archives_message', ['subject_clean']) + + + 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', [], {'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/migrations/0007_auto.py b/colab/super_archives/migrations/0007_auto.py new file mode 100644 index 0000000..e8fedca --- /dev/null +++ b/colab/super_archives/migrations/0007_auto.py @@ -0,0 +1,137 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding index on 'EmailAddress', fields ['real_name'] + db.create_index('super_archives_emailaddress', ['real_name']) + + + def backwards(self, orm): + + # Removing index on 'EmailAddress', fields ['real_name'] + db.delete_index('super_archives_emailaddress', ['real_name']) + + + 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/migrations/__init__.py b/colab/super_archives/migrations/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/colab/super_archives/migrations/__init__.py diff --git a/colab/super_archives/models.py b/colab/super_archives/models.py index b757235..76a723a 100644 --- a/colab/super_archives/models.py +++ b/colab/super_archives/models.py @@ -1,17 +1,30 @@ -# -*- coding: utf8 -*- +# -*- coding: utf-8 -*- +import datetime from hashlib import md5 from django.db import models from django.conf import settings from django.contrib.auth.models import User -from django.core.urlresolvers import reverse +from django.core.urlresolvers import reverse, NoReverseMatch + + +class NotSpamManager(models.Manager): + """Only return objects which are not marked as spam.""" + + def get_query_set(self): + return super(NotSpamManager, self).get_query_set().exclude(spam=True) + + +class PageHit(models.Model): + url_path = models.CharField(max_length=2048, unique=True, db_index=True) + hit_count = models.IntegerField(default=0) class EmailAddress(models.Model): user = models.ForeignKey(User, null=True, related_name='emails') address = models.EmailField(unique=True) - real_name = models.CharField(max_length=64, blank=True) + real_name = models.CharField(max_length=64, blank=True, db_index=True) md5 = models.CharField(max_length=32, null=True) def save(self, *args, **kwargs): @@ -28,12 +41,14 @@ class EmailAddress(models.Model): def get_profile_link(self): if self.user: - return reverse('colab.views.user_profile_username', - args=[self.user.username]) + return reverse('user_profile', args=[self.user.username]) else: - return reverse('colab.views.user_profile_emailhash', + return reverse('colab.views.userprofile.by_emailhash', args=[self.md5]) + def __unicode__(self): + return '"%s" <%s>' % (self.get_full_name(), self.address) + class UserProfile(models.Model): user = models.OneToOneField(User, unique=True) @@ -43,6 +58,10 @@ class UserProfile(models.Model): facebook = models.CharField(max_length=128, null=True) google_talk = models.EmailField(null=True) webpage = models.CharField(max_length=256) + verification_hash = models.CharField(max_length=32, null=True) + + def __unicode__(self): + return '%s (%s)' % (self.user.get_full_name(), self.user.username) # This does the same the same than related_name argument but it also creates # a profile in the case it doesn't exist yet. @@ -69,17 +88,80 @@ class MailingListMembership(models.Model): class Thread(models.Model): + class Meta: + unique_together = ('subject_token', 'mailinglist') + subject_token = models.CharField(max_length=512) mailinglist = models.ForeignKey(MailingList) latest_message = models.OneToOneField('Message', null=True, - related_name='+') - class Meta: - unique_together = ('subject_token', 'mailinglist') + related_name='+') + score = models.IntegerField(default=0) + spam = models.BooleanField(default=False) + + all_objects = models.Manager() + objects = NotSpamManager() + + def __unicode__(self): + return '%s - %s (%s)' % (self.id, + self.subject_token, + self.message_set.count()) + + def update_score(self): + """Update the relevance score for this thread. + + The score is calculated with the following variables: + + * vote_weight: 100 - (minus) 1 for each 3 days since + voted with minimum of 5. + * replies_weight: 300 - (minus) 1 for each 3 days since + replied with minimum of 5. + * page_view_weight: 10. + + * vote_score: sum(vote_weight) + * replies_score: sum(replies_weight) + * page_view_score: sum(page_view_weight) + + * score = (vote_score + replies_score + page_view_score) // 10 + with minimum of 0 and maximum of 5000 + + """ + + if not self.subject_token: + return + + # Save this pseudo now to avoid calling the + # function N times in the loops below + now = datetime.datetime.now() + days_ago = lambda date: (now - date).days + get_score = lambda weight, created: \ + max(weight - (days_ago(created) // 3), 5) + + vote_score = 0 + replies_score = 0 + for msg in self.message_set.all(): + # Calculate replies_score + replies_score += get_score(300, msg.received_time) + + # Calculate vote_score + for vote in msg.vote_set.all(): + vote_score += get_score(100, vote.created) + + # Calculate page_view_score + try: + url = reverse('thread_view', args=[self.subject_token]) + pagehit = PageHit.objects.get(url_path=url) + page_view_score = pagehit.hit_count * 10 + except (NoReverseMatch, PageHit.DoesNotExist): + page_view_score = 0 + + self.score = (page_view_score + vote_score + replies_score) // 10 + self.save() class Vote(models.Model): user = models.ForeignKey(User) message = models.ForeignKey('Message') + created = models.DateTimeField(auto_now_add=True) class Meta: unique_together = ('user', 'message') @@ -91,20 +173,26 @@ class Vote(models.Model): class Message(models.Model): - from_address = models.ForeignKey(EmailAddress) + from_address = models.ForeignKey(EmailAddress, db_index=True) mailinglist = models.ForeignKey(MailingList) - thread = models.ForeignKey(Thread, null=True) + thread = models.ForeignKey(Thread, null=True, db_index=True) # RFC 2822 recommends to use 78 chars + CRLF (so 80 chars) for # the max_length of a subject but most of implementations # goes for 256. We use 512 just in case. - subject = models.CharField(max_length=512) - subject_clean = models.CharField(max_length=512) + subject = models.CharField(max_length=512, db_index=True) + subject_clean = models.CharField(max_length=512, db_index=True) body = models.TextField(default='') received_time = models.DateTimeField() message_id = models.CharField(max_length=512) + spam = models.BooleanField(default=False) + all_objects = models.Manager() + objects = NotSpamManager() + def __unicode__(self): - return 'Email Message Id: %s' % self.id + return '(%s) %s: %s' % (self.id, + self.from_address.get_full_name(), + self.subject_clean) def vote_list(self): """Return a list of user that voted in this message.""" @@ -125,7 +213,27 @@ class Message(models.Model): message=self, user=user ).delete() - + + @property + def url(self): + """Shortcut to get thread url""" + return reverse('thread_view', args=[self.thread.subject_token]) + + @property + def Description(self): + """Alias to self.body""" + return self.body + + @property + def Title(self): + """Alias to self.subject_clean""" + return self.subject_clean + + @property + def modified(self): + """Alias to self.modified""" + return self.received_time + class MessageMetadata(models.Model): Message = models.ForeignKey(Message) @@ -137,4 +245,4 @@ class MessageMetadata(models.Model): def __unicode__(self): return 'Email Message Id: %s - %s: %s' % (self.Message.id, self.name, self.value) - + diff --git a/colab/super_archives/queries.py b/colab/super_archives/queries.py index 2feb770..eac21c1 100644 --- a/colab/super_archives/queries.py +++ b/colab/super_archives/queries.py @@ -1,5 +1,5 @@ -from super_archives.models import Thread, Vote, Message +from colab.super_archives.models import Thread, Vote, Message, PageHit def get_messages_by_date(): @@ -34,30 +34,14 @@ def get_latest_threads(): return Thread.objects.order_by('-latest_message__received_time') -def get_voted_threads(): - """Query for the most voted threads sorting by the sum of votes - and latest messages received. - - NOTE: This implementation has serious performance issues on - MySQL databases but it performes quite well on PostgreSQL. - - """ - - sql = """ - SELECT - count(sav.id) - FROM - super_archives_message AS sam - JOIN super_archives_vote AS sav - ON sav.message_id = sam.id - WHERE - super_archives_thread.id = sam.thread_id - """ +def get_hotest_threads(): + return Thread.objects.order_by('-score', '-latest_message__received_time') - threads = Thread.objects.extra( - select={ - 'vote_count': sql - } - ) - return threads.order_by('-vote_count', '-latest_message__received_time') + +def get_page_hits(path_info): + pagehit = PageHit.objects.filter(url_path=path_info) + if pagehit: + return pagehit[0].hit_count + return 0 + diff --git a/colab/super_archives/templates/message-list.html b/colab/super_archives/templates/message-list.html index 216cd59..319cbbe 100644 --- a/colab/super_archives/templates/message-list.html +++ b/colab/super_archives/templates/message-list.html @@ -1,4 +1,5 @@ {% extends "base.html" %} +{% load i18n %} {% load append_to_get %} {% block main-content %}
@@ -10,27 +11,30 @@

Ordenar por


Listas

    {% for thread in threads.object_list %} - {% with thread.latest_message as email %} - {% include "message-preview.html" %} - {% endwith %} + {% include "message-preview.html" with doc=thread.latest_message %} {% empty %}

    @@ -61,4 +65,4 @@
-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/colab/super_archives/templates/message-preview.html b/colab/super_archives/templates/message-preview.html index dfa0ffe..a992ef6 100644 --- a/colab/super_archives/templates/message-preview.html +++ b/colab/super_archives/templates/message-preview.html @@ -1,28 +1,48 @@ {% load i18n %} -{% with email.from_address as from %} -{% if email.subject_clean %} + +{% if doc.Title %}
  • - -
    - {{ email.mailinglist }} -
    + {% if doc.Type %} + {{ doc.Type }} + {% endif %} + + {% if doc.mailinglist %} +
    + {{ doc.mailinglist }} - + + + - + {{ doc.Description|striptags }} + +
    - {% trans "por" %} - - {% firstof from.get_full_name "Anônimo" %} + {% trans "criado por" %} + {% if doc.from_address.get_full_name %} + + {{ doc.from_address.get_full_name }} - + {% else %} + {% firstof doc.Creator _("anônimo") %} + {% endif %} + + - {{ email.received_time|timesince }} {% trans "atrás" %} + {{ doc.modified|timesince }} + {% trans "atrás" %}
  • {% endif %} -{% endwith %} \ No newline at end of file diff --git a/colab/super_archives/templates/message-thread.html b/colab/super_archives/templates/message-thread.html index 31b6f99..368632a 100644 --- a/colab/super_archives/templates/message-thread.html +++ b/colab/super_archives/templates/message-thread.html @@ -2,9 +2,11 @@ {% load i18n %} {% load append_to_get %} {% block main-content %} +
    +

    {{ first_msg.subject_clean }}


    -
    +
      {% for email in emails %}
    • @@ -21,7 +23,7 @@
      {{ email.votes_count }} - +

      @@ -29,7 +31,7 @@

    -
    +
    {{ email.body }}
    @@ -40,11 +42,36 @@ {% endfor %}
    -
    -

    Ordenar por

    +
    +

    {% trans "Ordenar por" %}:

    + + +
    + +

    {% trans "Estatísticas:" %}

    +
      -
    • Votos
    • -
    • Data
    • +
    • {% trans "iniciada à" %} +

      {{ first_msg.received_time|timesince }} {% trans "atrás" %}

      +
    • +
    • {% trans "visualizada" %} +

      {{ pagehits }} {% trans "vezes" %}

      +
    • +
    • {% trans "respondida" %} +

      {{ emails|length }} {% trans "vezes" %}

      +
    • +
    • {% trans "votada" %} +

      {{ total_votes }} {% trans "vezes" %}

      +
    +
    -{% endblock %} \ No newline at end of file + + + +{% endblock %} diff --git a/colab/super_archives/templatetags/append_to_get.py b/colab/super_archives/templatetags/append_to_get.py index 251145d..e1407c6 100644 --- a/colab/super_archives/templatetags/append_to_get.py +++ b/colab/super_archives/templatetags/append_to_get.py @@ -1,3 +1,5 @@ + +import urllib from django import template register = template.Library() @@ -33,15 +35,10 @@ class AppendGetNode(template.Node): get[key] = self.dict_pairs[key].resolve(context) path = context['request'].META['PATH_INFO'] - - #print "&".join(["%s=%s" % (key, value) - # for (key, value) in get.items() - # if value]) - + if len(get): - path += "?%s" % "&".join(["%s=%s" % (key, value) - for (key, value) in get.items() - if value]) + path = '?' + urllib.urlencode(get) + return path @register.tag() diff --git a/colab/super_archives/templatetags/form_field.py b/colab/super_archives/templatetags/form_field.py index 8c987ae..cd82a03 100644 --- a/colab/super_archives/templatetags/form_field.py +++ b/colab/super_archives/templatetags/form_field.py @@ -1,4 +1,5 @@ from django import template +from django import forms def render_form_field(parser, token): variables = token.split_contents() @@ -9,7 +10,7 @@ def render_form_field(parser, token): elif len(variables) == 3: tag_name, form_field, default_value = variables else: - raise TemplateSyntaxError + raise template.TemplateSyntaxError return RenderFormField(form_field, default_value) @@ -26,7 +27,10 @@ class RenderFormField(template.Node): class_ = '' errors = '' form_field_tag = '' - form_field = self.form_field_nocontext.resolve(context) + try: + form_field = self.form_field_nocontext.resolve(context) + except template.VariableDoesNotExist: + return '' if form_field.errors: class_ += 'error' @@ -42,6 +46,9 @@ class RenderFormField(template.Node): if editable: form_field_tag = '
    ' + str(form_field) + elif isinstance(form_field.field, forms.URLField): + form_field_tag = """%s""" % ( + default_value, default_value) else: form_field_tag = default_value @@ -53,4 +60,4 @@ class RenderFormField(template.Node): ) register = template.Library() -register.tag('render_form_field', render_form_field) \ No newline at end of file +register.tag('render_form_field', render_form_field) diff --git a/colab/super_archives/urls.py b/colab/super_archives/urls.py index 657fa93..2964fa5 100644 --- a/colab/super_archives/urls.py +++ b/colab/super_archives/urls.py @@ -2,6 +2,8 @@ from django.conf.urls.defaults import patterns, include, url urlpatterns = patterns('', # url(r'thread/(?P\d+)/$', 'super_archives.views.thread', name='thread'), - url(r'thread/(?P[-\w]+)$', 'super_archives.views.thread'), - url(r'thread/$', 'super_archives.views.list_messages') + url(r'thread/(?P[-\w]+)$', + 'colab.super_archives.views.thread', name="thread_view"), + url(r'thread/$', + 'colab.super_archives.views.list_messages', name='thread_list') ) diff --git a/colab/super_archives/validators.py b/colab/super_archives/validators.py index 0bd9df5..09049dc 100644 --- a/colab/super_archives/validators.py +++ b/colab/super_archives/validators.py @@ -1,4 +1,4 @@ -# -*- coding: utf8 -*- +# -*- coding: utf-8 -*- from django.core.exceptions import ValidationError diff --git a/colab/super_archives/views.py b/colab/super_archives/views.py index 2e8a727..5d41f31 100644 --- a/colab/super_archives/views.py +++ b/colab/super_archives/views.py @@ -1,38 +1,51 @@ -# -*- coding: utf8 -*- +# -*- coding: utf-8 -*- from django.template import RequestContext from django.core.paginator import Paginator from django.shortcuts import render_to_response, get_list_or_404 -from super_archives import queries -from super_archives.models import MailingList +from colab.super_archives import queries +from colab.super_archives.models import MailingList, Thread def thread(request, thread_token): first_message = queries.get_first_message_in_thread(thread_token) order_by = request.GET.get('order') - if order_by == 'date': - msgs_query = queries.get_messages_by_date() - else: + if order_by == 'voted': msgs_query = queries.get_messages_by_voted() + else: + msgs_query = queries.get_messages_by_date() msgs_query = msgs_query.filter(thread__subject_token=thread_token) emails = msgs_query.exclude(id=first_message.id) + + total_votes = first_message.votes_count() + for email in emails: + total_votes += email.votes_count() + + # Update relevance score + thread = Thread.objects.get(subject_token=thread_token) + thread.update_score() + template_data = { - 'request': request, 'first_msg': first_message, 'emails': [first_message] + list(emails), + 'pagehits': queries.get_page_hits(request.path_info), + 'total_votes': total_votes, } + return render_to_response('message-thread.html', template_data, RequestContext(request)) def list_messages(request): + selected_list = request.GET.get('list') + order_by = request.GET.get('order') - if order_by == 'voted': - threads = queries.get_voted_threads() + if order_by == 'hotest': + threads = queries.get_hotest_threads() else: threads = queries.get_latest_threads() @@ -50,7 +63,8 @@ def list_messages(request): 'lists': lists, 'n_results': paginator.count, 'threads': threads, - 'request': request, + 'selected_list': selected_list, + 'order_by': order_by, } return render_to_response('message-list.html', template_data, - RequestContext(request)) \ No newline at end of file + RequestContext(request)) diff --git a/colab/templates/404.html b/colab/templates/404.html new file mode 100644 index 0000000..d18574d --- /dev/null +++ b/colab/templates/404.html @@ -0,0 +1 @@ +

    Not found. Keep searching! :)

    \ No newline at end of file diff --git a/colab/templates/500.html b/colab/templates/500.html new file mode 100644 index 0000000..1f3090a --- /dev/null +++ b/colab/templates/500.html @@ -0,0 +1 @@ +

    Ooopz... something went wrong!

    \ No newline at end of file diff --git a/colab/templates/account_change_password.html b/colab/templates/account_change_password.html new file mode 100644 index 0000000..94b6f4d --- /dev/null +++ b/colab/templates/account_change_password.html @@ -0,0 +1,24 @@ +{% extends "base.html" %} +{% load i18n %} +{% load form_field %} + +{% block main-content %} + +{% if form.errors %} +
    + Por favor, corrija os erros abaixo e tente novamente. +
    +{% endif %} + +
    + {% csrf_token %} + +
    + {% render_form_field form.old_password %} + {% render_form_field form.new_password1 %} + {% render_form_field form.new_password2 %} + +
    + +
    +{% endblock %} \ No newline at end of file diff --git a/colab/templates/account_message.html b/colab/templates/account_message.html new file mode 100644 index 0000000..43abdf8 --- /dev/null +++ b/colab/templates/account_message.html @@ -0,0 +1,10 @@ +{% extends "base.html" %} +{% load i18n %} + +{% block main-content %} + + + {% trans msg %} + + +{% endblock %} \ No newline at end of file diff --git a/colab/templates/account_request_reset_password.html b/colab/templates/account_request_reset_password.html new file mode 100644 index 0000000..c6dbc35 --- /dev/null +++ b/colab/templates/account_request_reset_password.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} +{% load i18n %} + +{% block main-content %} +

    {% trans "Esqueci minha senha" %}

    + +
    + {% csrf_token %} + +
    + + + +
    + +
    + +{% endblock %} \ No newline at end of file diff --git a/colab/templates/base.html b/colab/templates/base.html index 62643a7..8c226a2 100644 --- a/colab/templates/base.html +++ b/colab/templates/base.html @@ -1,22 +1,49 @@ +{% load i18n %} - - - - - - + + + + + - + {% block head_js %} {% endblock %} + {% block head_css %} + + + + {% endblock %} @@ -24,55 +51,66 @@
    {% if not user.is_authenticated %} - Cadastre-se + {% trans "Cadastre-se" %} - Acessar + {% trans "Acessar" %} {% else %} - Meu Perfil + {% trans "autenticado como" %} {{ user.username }} + + + + {% trans "Meu Perfil" %} + - Sair + + {% trans "Sair" %} + {% endif %}
    - + {% block header %}

    COLAB Colab

    {% endblock %} - - - -
    + +
    {% block main-content %} {% endblock %} @@ -82,11 +120,11 @@ - \ No newline at end of file + diff --git a/colab/templates/email_account-reset-password.html b/colab/templates/email_account-reset-password.html new file mode 100644 index 0000000..222bf4c --- /dev/null +++ b/colab/templates/email_account-reset-password.html @@ -0,0 +1,19 @@ +{% load i18n %} + +{% blocktrans %} + Este email foi enviado para confirmar a solicitação de troca de senha + para o usuário {{ username }} do Colab Interlegis. Caso você não + tenha realizado a solicitação por favor ignore esta mensagem. +{% endblocktrans %} + +
    +
    + +{% blocktrans %} + Para realizar a troca de senha acesse o link abaixo: +{% endblocktrans %} +
    + + + http://{{ server_name }}{% url reset_password hash %} + \ No newline at end of file diff --git a/colab/templates/email_signup-email-confirmation.html b/colab/templates/email_signup-email-confirmation.html new file mode 100644 index 0000000..66bb2ba --- /dev/null +++ b/colab/templates/email_signup-email-confirmation.html @@ -0,0 +1,9 @@ +{% load i18n %} + +Bem-vindo ao Colab! + +Para ativar sua conta por favor valide seu email acessando o link seguinte: + + + http://{{ server_name }}{% url email_verification hash %} + \ No newline at end of file diff --git a/colab/templates/home.html b/colab/templates/home.html index ec49aed..f4ee296 100644 --- a/colab/templates/home.html +++ b/colab/templates/home.html @@ -1,7 +1,11 @@ {% extends "base.html" %} {% load i18n %} -{% block header %} +{% block head_js %} + {% include "pizza-chart.html" with chart_div="collabs" chart_width=410 chart_height=330 %} +{% endblock %} + +{% block header %} @@ -12,34 +16,52 @@ {% endblock %} {% block main-content %} -

    {% trans "Últimas Menssagens:" %}

    -

    {% trans "Discussões mais votadas:" %}

    - -
      - {% for thread in latest_threads %} - {% with thread.latest_message as email %} - {% include "message-preview.html" %} - {% endwith %} - {% endfor %} -
    - -
      - {% for thread in hotest_threads %} - {% with thread.latest_message as email %} - {% include "message-preview.html" %} - {% endwith %} - {% endfor %} -
    - + +
    +

    {% trans "Últimas Colaborações:" %}

    +
      + {% for doc in latest_docs %} + {% include "message-preview.html" %} + {% endfor %} +
    +
    + + {% trans "Ver mais colaborações..." %} + +
    + +
    +

    {% trans "Distribuição das Colaborações:" %}

    +
    +
    +
    -
    - - {% trans "Ver mais mensagens..." %} +
    + +
    + + {% endblock %} diff --git a/colab/templates/login.html b/colab/templates/login.html index ca62cd2..1ed023c 100644 --- a/colab/templates/login.html +++ b/colab/templates/login.html @@ -10,8 +10,8 @@ {% endif %}
    -
    - {% csrf_token %} + + {% csrf_token %}
    Login @@ -27,8 +27,9 @@

    - {% url colab.views.user_profile_empty as user_profile_url %} - + Esqueci minha senha + +
    @@ -39,8 +40,8 @@ comunidade Interlegis clique no link abaixo e comece a colaborar!

    Cadastre-se + href="{% url signup %}">Cadastre-se
    -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/colab/templates/pizza-chart.html b/colab/templates/pizza-chart.html new file mode 100644 index 0000000..99b8a7d --- /dev/null +++ b/colab/templates/pizza-chart.html @@ -0,0 +1,45 @@ +{% load i18n %} + +{% if type_count %} + + + +{% endif %} diff --git a/colab/templates/search.html b/colab/templates/search.html new file mode 100644 index 0000000..b596fdc --- /dev/null +++ b/colab/templates/search.html @@ -0,0 +1,78 @@ +{% extends "base.html" %} +{% load i18n %} +{% load append_to_get %} +{% block main-content %} +

    {% trans "Busca" %}

    + + {{ docs.numFound }} {% trans "documentos encontrados em" %} + {{ docs.QTime|floatformat:3 }} {% trans "segundos" %} + + +
    + +
    +

    Filtros

    + +

    {% trans "Tipos" %}

    + + +
    + +
    +
      + {% for doc in docs %} + {% include "message-preview.html" %} + {% empty %} +
    • + {% trans "Sem resultados para a busca." %} +
    • + {% endfor %} +
    + +
    + + {% if docs.numFound %} + + {% endif %} +
    + +{% endblock %} diff --git a/colab/templates/signup-form.html b/colab/templates/signup-form.html index d7b9900..4d0b4df 100644 --- a/colab/templates/signup-form.html +++ b/colab/templates/signup-form.html @@ -1,5 +1,6 @@ {% extends "base.html" %} {% load form_field %} +{% load i18n %} {% block main-content %}

    Cadastre-se

    @@ -8,6 +9,17 @@
    Por favor, corrija os erros abaixo e tente novamente.
    {% endif %} +
    + +
    + user +
    +

    + {% trans "Adicione um avatar à sua conta utilizando" %} Gravatar. +

    +
    + +

    @@ -47,4 +59,4 @@
    -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/colab/templates/user-profile.html b/colab/templates/user-profile.html index 0ed1067..868cf27 100644 --- a/colab/templates/user-profile.html +++ b/colab/templates/user-profile.html @@ -1,57 +1,28 @@ {% extends "base.html" %} +{% load i18n %} {% load form_field %} {% block head_js %} - - - + {% include "pizza-chart.html" with chart_div="collabs" chart_width=390 chart_height=230 %} {% endblock %} - {% block main-content %} {% if not user_profile %} Usuário não cadastrado. Você é dono deste perfil? - Clique aqui + Clique aqui e cadastre-se. + + {% else %} + + {% ifequal request.user.username user_profile.user.username %} + + Ei, olha você aqui! Quer + editar seu perfil? + + {% endifequal %} + {% endif %}
    @@ -63,13 +34,18 @@
    -
    -
    +
    + {% csrf_token %}

    Informações Pessoais

      -
    • {{ form.username.label_tag }}: {{ user_profile.user.username }}
    • +
    • + {{ form.username.label_tag }}: {{ user_profile.user.username }} + {% ifequal request.user.username user_profile.user.username %} + ({% trans "alterar senha" %}) + {% endifequal %} +
    • {% render_form_field form.institution user_profile.institution %}
    • @@ -105,21 +81,37 @@
    -
    -

    Avaliação das Menssagens

    -
    + {% if type_count %} +
    +

    {% trans "Colaborações por área" %}

    +
    + {% endif %}

    -

    Últimas mensagens enviadas

    -
      - {% for email in emails %} - {% include "message-preview.html" %} - {% empty %} -
    • Não existem mensagens enviadas por este usuário até o momento.
    • - {% endfor %} -
    +
    +

    {% trans "Últimas mensagens enviadas" %}

    +
      + {% for doc in emails %} + {% include "message-preview.html" %} + {% empty %} +
    • Não existem mensagens enviadas por este usuário até o momento.
    • + {% endfor %} +
    +
    + +
    +

    {% trans "Participações na comunidade:" %}

    +
      + {% for doc in docs %} + {% include "message-preview.html" %} + {% empty %} +
    • Sem colaborações deste usuário até o momento.
    • + {% endfor %} +
    +
    +
    -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/colab/urls.py b/colab/urls.py index 84c6d55..83868af 100644 --- a/colab/urls.py +++ b/colab/urls.py @@ -5,31 +5,48 @@ from django.contrib import admin admin.autodiscover() urlpatterns = patterns('', - url(r'^$', 'colab.views.home', name='home'), + url(r'^$', 'colab.views.other.home', name='home'), url(r'^archives/', include('colab.super_archives.urls')), url(r'^api/', include('colab.api.urls')), + url(r'^user/(?P[\w@+.-]+)/?$', + 'colab.views.userprofile.by_username', name='user_profile'), + + url(r'^user/$', 'colab.views.userprofile.by_request_user', + name='user_profile_by_request_user'), + url(r'^user/hash/(?P[\w]+)$', - 'colab.views.user_profile_emailhash'), - - url(r'^user/(?P[\w]+)/?$', 'colab.views.user_profile_username'), + 'colab.views.userprofile.by_emailhash'), url(r'^user/(?P[\w]+)/edit/?$', - 'colab.views.user_profile_edit'), + 'colab.views.userprofile.update', name='user_profile_update'), + + url(r'^search/$', 'colab.views.other.search', name='search'), - url(r'^user/$', - 'colab.views.user_profile_empty'), + url(r'^account/$', 'colab.views.signup.signup', name='signup'), - url(r'^signup/$', 'colab.views.signup'), + url(r'^account/changepassword/$', 'colab.views.signup.change_password', + name='change_password'), - url(r'^login/$', 'django.contrib.auth.views.login', - {'template_name': 'login.html'}), + url(r'^account/resetpassword/$', + 'colab.views.signup.request_reset_password', + name='request_reset_password'), + + url(r'^account/reset_password/(?P[\w]{32})/$', + 'colab.views.signup.reset_password', name='reset_password'), - url(r'^logout/$', 'django.contrib.auth.views.logout', - {'next_page': '/'}), + url(r'^signup/verify/(?P[\w]{32})/$', + 'colab.views.signup.verify_email', name='email_verification'), + + url(r'^account/login/$', 'django.contrib.auth.views.login', + {'template_name': 'login.html'}, name='login'), + + url(r'^account/logout/$', 'django.contrib.auth.views.logout', + {'next_page': '/'}, name='logout'), # Uncomment the next line to enable the admin: - #url(r'^admin/', include(admin.site.urls)), + url(r'^colab/admin/', include(admin.site.urls)), ) + diff --git a/colab/views.py b/colab/views.py deleted file mode 100644 index 55547bd..0000000 --- a/colab/views.py +++ /dev/null @@ -1,159 +0,0 @@ - -from django.template import RequestContext -from django.contrib.auth.models import User -from django.forms.models import model_to_dict -from django.contrib.auth.decorators import login_required -from django.shortcuts import render_to_response, get_object_or_404, redirect - -from super_archives import queries -from super_archives.forms import UserCreationForm, UserUpdateForm -from super_archives.models import Message, Thread, UserProfile, EmailAddress - - -def home(request): - """Index page view""" - - latest_threads = queries.get_latest_threads() - hotest_threads = queries.get_voted_threads() - - template_data = { - 'hotest_threads': hotest_threads[:6], - 'latest_threads': latest_threads[:6], - } - return render_to_response('home.html', template_data, - context_instance=RequestContext(request)) - - -def signup(request): - - # If the request method is GET just return the form - if request.method == 'GET': - form = UserCreationForm() - return render_to_response('signup-form.html', {'form': form}, - RequestContext(request)) - - # If the request method is POST try to store data - form = UserCreationForm(request.POST) - - # If there is validation errors give the form back to the user - if not form.is_valid(): - return render_to_response('signup-form.html', {'form': form}, - RequestContext(request)) - - user = User( - username=form.cleaned_data.get('username'), - email=form.cleaned_data.get('email'), - first_name=form.cleaned_data.get('first_name'), - last_name=form.cleaned_data.get('last_name'), - ) - user.set_password(form.cleaned_data.get('password')) - user.save() - - profile = UserProfile( - user=user, - institution=form.cleaned_data.get('institution'), - role=form.cleaned_data.get('role'), - twitter=form.cleaned_data.get('twitter'), - facebook=form.cleaned_data.get('facebook'), - google_talk=form.cleaned_data.get('google_talk'), - webpage=form.cleaned_data.get('webpage'), - ) - profile.save() - - # Check if the user's email have been used previously - # in the mainling lists to link the user to old messages - email_addr, created = EmailAddress.objects.get_or_create(address=user.email) - if created: - email_addr.real_name = user.get_full_name() - - email_addr.user = user - email_addr.save() - - return redirect('colab.views.user_profile_username', user.username) - - -def user_profile(request, user, email_address=None, editable=False, form=None): - - if form is None: - form = UserCreationForm() - - if user: - email_addresses = user.emails.all() - else: - email_addresses = [email_address] - - if not email_address: - email_address = email_addresses[0] - - email_addresses_ids = tuple([str(addr.id) for addr in email_addresses]) - - query = """ - SELECT - * - FROM - super_archives_message - WHERE - from_address_id IN (%(ids)s) - GROUP BY - thread_id - ORDER BY - received_time DESC - LIMIT 10; - """ % {'ids': ','.join(email_addresses_ids)} - - emails = Message.objects.raw(query) - n_sent = Message.objects.filter(from_address__in=email_addresses).count() - - if user: - profile = user.profile - else: - profile = None - - template_data = { - 'user_profile': profile, - 'email_address': email_address, - 'emails': emails or [], - 'form': form, - 'editable': editable, - } - return render_to_response('user-profile.html', template_data, - RequestContext(request)) - - -@login_required -def user_profile_empty(request): - return user_profile(request, request.user) - - -def user_profile_username(request, username): - user = get_object_or_404(User, username=username) - return user_profile(request, user) - - -def user_profile_emailhash(request, emailhash): - email_addr = get_object_or_404(EmailAddress, md5=emailhash) - return user_profile(request, email_addr.user, email_addr) - - -@login_required -def user_profile_edit(request, username): - profile = get_object_or_404(UserProfile, user__username=username) - form = UserUpdateForm(initial=model_to_dict(profile)) - - if request.method == "GET": - return user_profile(request, profile.user, editable=True, form=form) - - form = UserUpdateForm(request.POST) - if not form.is_valid(): - return user_profile(request, profile.user, editable=True, form=form) - - profile.institution = form.cleaned_data.get('institution') - profile.role = form.cleaned_data.get('role') - profile.twitter = form.cleaned_data.get('twitter') - profile.facebook = form.cleaned_data.get('facebook') - profile.google_talk = form.cleaned_data.get('google_talk') - profile.webpage = form.cleaned_data.get('webpage') - profile.save() - - return redirect('colab.views.user_profile_username', profile.user.username) - \ No newline at end of file diff --git a/colab/views/__init__.py b/colab/views/__init__.py new file mode 100644 index 0000000..6f804bc --- /dev/null +++ b/colab/views/__init__.py @@ -0,0 +1,2 @@ +#!/usr/bin/env python +# encoding: utf-8 diff --git a/colab/views/other.py b/colab/views/other.py new file mode 100644 index 0000000..b935c60 --- /dev/null +++ b/colab/views/other.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +other.py + +Created by Sergio Campos on 2012-01-10. +""" + +from django.template import RequestContext +from django.shortcuts import render_to_response +from django.utils.translation import ugettext as _ + +from colab import solrutils +from colab.super_archives import queries + + +def home(request): + """Index page view""" + + latest_threads = queries.get_latest_threads() + hotest_threads = queries.get_hotest_threads() + + template_data = { + 'hotest_threads': hotest_threads[:6], + 'latest_threads': latest_threads[:6], + 'type_count': solrutils.count_types(sample=1000), + 'latest_docs': solrutils.get_latest_collaborations(6), + } + return render_to_response('home.html', template_data, + context_instance=RequestContext(request)) + + +def search(request): + if request.method == 'GET': + query = request.GET.get('q') + sort = request.GET.get('o') + type_ = request.GET.get('type') + page_number = int(request.GET.get('p', 1)) + results_per_page = int(request.GET.get('per_page', 16)) + + filters = { + 'Type': type_, + } + + query = solrutils.build_query(query, filters) + + # Query Solr for results + solr_dict_resp = solrutils.select(query, results_per_page, + page_number, sort) + + docs = solrutils.SolrPaginator(solr_dict_resp, page_number) + + template_data = { + 'docs': docs, + 'anonymous': _(u'anônimo'), + 'q': query, + 'type': type_, + } + + return render_to_response('search.html', template_data, + RequestContext(request)) diff --git a/colab/views/signup.py b/colab/views/signup.py new file mode 100644 index 0000000..2bdf26a --- /dev/null +++ b/colab/views/signup.py @@ -0,0 +1,218 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +signup.py + +Created by Sergio Campos on 2012-01-10. +""" + +import uuid +from colab import signup as signup_ + +from django.template import RequestContext +from django.contrib.auth.models import User +from django.utils.translation import ugettext as _ +from django.contrib.auth.decorators import login_required +from django.contrib.auth.forms import SetPasswordForm, PasswordChangeForm +from django.shortcuts import render_to_response, redirect, get_object_or_404 + +from colab.super_archives.forms import UserCreationForm +from colab.super_archives.models import UserProfile, EmailAddress + + +def signup(request): + + # If the request method is GET just return the form + if request.method == 'GET': + form = UserCreationForm() + return render_to_response('signup-form.html', {'form': form}, + RequestContext(request)) + + # If the request method is POST try to store data + form = UserCreationForm(request.POST) + + # If there is validation errors give the form back to the user + if not form.is_valid(): + return render_to_response('signup-form.html', {'form': form}, + RequestContext(request)) + + user = User( + username=form.cleaned_data.get('username'), + email=form.cleaned_data.get('email'), + first_name=form.cleaned_data.get('first_name'), + last_name=form.cleaned_data.get('last_name'), + is_active=False, + ) + user.set_password(form.cleaned_data.get('password1')) + user.save() + + profile = UserProfile( + user=user, + institution=form.cleaned_data.get('institution'), + role=form.cleaned_data.get('role'), + twitter=form.cleaned_data.get('twitter'), + facebook=form.cleaned_data.get('facebook'), + google_talk=form.cleaned_data.get('google_talk'), + webpage=form.cleaned_data.get('webpage'), + verification_hash=uuid.uuid4().get_hex(), + ) + profile.save() + + signup_.send_verification_email(request, user) + + # Check if the user's email have been used previously + # in the mainling lists to link the user to old messages + email_addr, created = EmailAddress.objects.get_or_create(address=user.email) + if created: + email_addr.real_name = user.get_full_name() + + email_addr.user = user + email_addr.save() + + template_data = { + 'msg': _((u'Cadastro efetuado com sucesso. Por favor acesse seu ' + u'endereço de email para validá-lo.')), + 'msg_css_class': 'success', + } + + return render_to_response('account_message.html', template_data, + RequestContext(request)) + + +def verify_email(request, hash): + """Verify hash and activate user's account""" + + profile = get_object_or_404(UserProfile, verification_hash=hash) + + profile.verification_hash = 'verified' + profile.save() + + profile.user.is_active = True + profile.user.save() + + template_data = { + 'msg': _(u'Endereço de e-mail validado corretamente.'), + 'msg_css_class': 'success', + } + + return render_to_response('account_message.html', template_data, + RequestContext(request)) + + +def request_reset_password(request): + """Request a password reset. + + In case request method is GET it will display the password reset + form. Otherwise we'll look for a username in the POST request to + have its password reset. This user will receive a link where he + will be allowed to change his password. + + """ + + if request.method == 'GET': + return render_to_response('account_request_reset_password.html', {}, + RequestContext(request)) + + username = request.POST.get('username') + + try: + user = User.objects.get(username=username) + except User.DoesNotExist: + user = None + + if user and user.is_active: + profile = user.profile + profile.verification_hash = uuid.uuid4().get_hex() + profile.save() + + signup_.send_reset_password_email(request, user) + + msg = _((u'Para sua segurança, dentro de alguns instantes você ' + u'receberá um email solicitando a confirmação do pedido ' + u'de troca de senha. Por favor aguarde.')) + + template_data = { + 'msg': msg, + 'msg_css_class': 'info', + } + + return render_to_response('account_message.html', template_data, + RequestContext(request)) + + +def reset_password(request, hash): + """Perform a password change. + + If the request method is set to GET and the hash matches a form + will be displayed to the user allowing the password change. + If the request method is POST the user password will be changed + to the newly set data. + + """ + + profile = get_object_or_404(UserProfile, verification_hash=hash) + user = profile.user + + form = SetPasswordForm(profile.user) + + template_data = { + 'form': form, + 'hash': hash, + } + + if request.method == 'GET': + return render_to_response('account_change_password.html', + template_data, RequestContext(request)) + + + form = SetPasswordForm(user, request.POST) + template_data.update({'form': form}) + + if not form.is_valid(): + return render_to_response('account_change_password.html', + template_data, RequestContext(request)) + + profile.verification_hash = 'verified' + profile.save() + + user.set_password(form.cleaned_data.get('new_password1')) + user.save() + + template_data.update({ + 'msg': _(u'Senha alterada com sucesso!'), + 'msg_css_class': 'success', + }) + return render_to_response('account_message.html', template_data, + RequestContext(request)) + +@login_required +def change_password(request): + """Change a password for an authenticated user.""" + + form = PasswordChangeForm(request.user) + + template_data = { + 'form': form + } + + if request.method == 'GET': + return render_to_response('account_change_password.html', + template_data, RequestContext(request)) + + form = PasswordChangeForm(request.user, request.POST) + template_data.update({'form': form}) + + if not form.is_valid(): + return render_to_response('account_change_password.html', + template_data, RequestContext(request)) + + request.user.set_password(form.cleaned_data.get('new_password1')) + request.user.save() + + template_data.update({ + 'msg': _(u'Senha alterada com sucesso!'), + 'msg_css_class': 'success', + }) + return render_to_response('account_message.html', template_data, + RequestContext(request)) + diff --git a/colab/views/userprofile.py b/colab/views/userprofile.py new file mode 100644 index 0000000..d552b49 --- /dev/null +++ b/colab/views/userprofile.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +userprofile.py + +Created by Sergio Campos on 2012-01-10. +""" + +from django.template import RequestContext +from django.contrib.auth.models import User +from django.forms.models import model_to_dict +from django.contrib.auth.decorators import login_required +from django.shortcuts import render_to_response, get_object_or_404, redirect + +from colab import solrutils +from colab.super_archives.forms import UserCreationForm, UserUpdateForm +from colab.super_archives.models import Message, UserProfile, EmailAddress + + +def read(request, user, email_address=None, editable=False, form=None): + + if form is None: + form = UserCreationForm() + + if user: + email_addresses = user.emails.all() + profile = user.profile + last_modified_docs = solrutils.get_latest_collaborations( + username=user.username + ) + + type_count = solrutils.count_types( + filters={'collaborator': user.username} + ) + + else: + email_addresses = [email_address] + profile = None + last_modified_docs = [] + type_count = {} + + if not email_address and email_addresses: + email_address = email_addresses[0] + + email_addresses_ids = tuple([str(addr.id) for addr in email_addresses]) + + query = """ + SELECT + * + FROM + super_archives_message JOIN ( + SELECT id + FROM super_archives_message + WHERE from_address_id IN (%(ids)s) + GROUP BY thread_id, id + ) AS subquery + ON subquery.id = super_archives_message.id + ORDER BY + received_time DESC + LIMIT 10; + + """ % {'ids': ','.join(email_addresses_ids)} + + emails = Message.objects.raw(query) + #n_sent = Message.objects.filter(from_address__in=email_addresses).count() + + template_data = { + 'user_profile': profile, + 'email_address': email_address, + 'emails': emails or [], + 'form': form, + 'editable': editable, + 'type_count': type_count, + 'docs': last_modified_docs, + } + + return render_to_response('user-profile.html', template_data, + RequestContext(request)) + + +@login_required +def by_request_user(request): + return read(request, request.user) + + +def by_username(request, username): + user = get_object_or_404(User, username=username) + return read(request, user) + + +def by_emailhash(request, emailhash): + email_addr = get_object_or_404(EmailAddress, md5=emailhash) + return read(request, email_addr.user, email_addr) + + +@login_required +def update(request, username): + profile = get_object_or_404(UserProfile, user__username=username) + form = UserUpdateForm(initial=model_to_dict(profile)) + + if request.method == "GET": + return read(request, profile.user, editable=True, form=form) + + form = UserUpdateForm(request.POST) + if not form.is_valid(): + return read(request, profile.user, editable=True, form=form) + + profile.institution = form.cleaned_data.get('institution') + profile.role = form.cleaned_data.get('role') + profile.twitter = form.cleaned_data.get('twitter') + profile.facebook = form.cleaned_data.get('facebook') + profile.google_talk = form.cleaned_data.get('google_talk') + profile.webpage = form.cleaned_data.get('webpage') + profile.save() + + return redirect('user_profile', profile.user.username) diff --git a/etc/apache2/sites-available/colab b/etc/apache2/sites-available/colab new file mode 100644 index 0000000..b899771 --- /dev/null +++ b/etc/apache2/sites-available/colab @@ -0,0 +1,64 @@ + + ServerName colab01a.interlegis.leg.br + ServerAlias colab.interlegis.leg.br + ServerAlias colab.interlegis.gov.br + + + Order deny,allow + Allow from all + + + RewriteEngine On + RewriteRule ^/admin(.*) http://colab-backend.interlegis.leg.br/admin$1 [P] + RewriteRule ^/wiki(.*) http://colab-backend.interlegis.leg.br/wiki$1 [P] + RewriteRule ^/changeset(.*) http://colab-backend.interlegis.leg.br/changeset$1 [P] + RewriteRule ^/newticket(.*) http://colab-backend.interlegis.leg.br/newticket$1 [P] + RewriteRule ^/ticket(.*) http://colab-backend.interlegis.leg.br/ticket$1 [P] + RewriteRule ^/chrome(.*) http://colab-backend.interlegis.leg.br/chrome$1 [P] + RewriteRule ^/timeline(.*) http://colab-backend.interlegis.leg.br/timeline$1 [P] + RewriteRule ^/roadmap(.*) http://colab-backend.interlegis.leg.br/roadmap$1 [P] + RewriteRule ^/browser(.*) http://colab-backend.interlegis.leg.br/browser$1 [P] + RewriteRule ^/report(.*) http://colab-backend.interlegis.leg.br/report$1 [P] + RewriteRule ^/tags(.*) http://colab-backend.interlegis.leg.br/tags$1 [P] + RewriteRule ^/query(.*) http://colab-backend.interlegis.leg.br/query$1 [P] + RewriteRule ^/about(.*) http://colab-backend.interlegis.leg.br/about$1 [P] + RewriteRule ^/prefs(.*) http://colab-backend.interlegis.leg.br/prefs$1 [P] + RewriteRule ^/login(.*) http://colab-backend.interlegis.leg.br/login$1 [P] + RewriteRule ^/logout(.*) http://colab-backend.interlegis.leg.br/logout$1 [P] + RewriteRule ^/attachment(.*) http://colab-backend.interlegis.leg.br/attachment$1 [P] + RewriteRule ^/raw-attachment(.*) http://colab-backend.interlegis.leg.br/raw-attachment$1 [P] + + WSGIDaemonProcess colab user=www-data group=www-data threads=25 + WSGIProcessGroup colab + + Alias /static/admin/ /usr/local/django/colab/lib/python2.6/site-packages/django/contrib/admin/media/ + + Options -Indexes + Order deny,allow + Allow from all + AllowOverride None + + + Alias /static/ /usr/local/django/colab/static/ + + Options -Indexes + Order deny,allow + Allow from all + AllowOverride None + + + WSGIScriptAlias / /usr/local/django/colab/wsgi/colab.wsgi + + Order deny,allow + Allow from all + AllowOverride None + + + ErrorLog /var/log/apache2/error.log + + # Possible values include: debug, info, notice, warn, error, crit, + # alert, emerg. + LogLevel warn + + CustomLog /var/log/apache2/access.log combined + diff --git a/etc/apache2/wsgi/colab.wsgi b/etc/apache2/wsgi/colab.wsgi new file mode 100644 index 0000000..8645c6e --- /dev/null +++ b/etc/apache2/wsgi/colab.wsgi @@ -0,0 +1,12 @@ +import site +import os + +cwd_path = os.path.abspath(os.path.dirname(__file__)) +virtualenv_path = os.path.join(cwd_path, '../lib/python2.6/site-packages') +site.addsitedir(virtualenv_path) + +from django.core.handlers.wsgi import WSGIHandler +os.environ['DJANGO_SETTINGS_MODULE'] = 'colab.settings' +application = WSGIHandler() + + diff --git a/etc/autofs/listas b/etc/autofs/listas new file mode 100644 index 0000000..94c29c5 --- /dev/null +++ b/etc/autofs/listas @@ -0,0 +1 @@ +archives -fstype=fuse,rw,nodev,users,noatime :sshfs\#root@listas.interlegis.gov.br\:/var/lib/mailman/archives/private diff --git a/etc/cron.d/colab_import_emails b/etc/cron.d/colab_import_emails new file mode 100644 index 0000000..583e332 --- /dev/null +++ b/etc/cron.d/colab_import_emails @@ -0,0 +1,2 @@ +PATH="/usr/local/django/colab/bin" +* * * * * root DJANGO_SETTINGS_MODULE="colab.settings" django-admin.py import_emails --archives_path=/usr/local/django/colab/mnt/archives/ --exclude-list=saberes-divulgacao --exclude-list=pml --exclude-list=mailman --exclude-list=lexml-anuncios > /tmp/colab-import-emails-last.log diff --git a/etc/cron.d/colab_solr_reindex b/etc/cron.d/colab_solr_reindex new file mode 100644 index 0000000..e588180 --- /dev/null +++ b/etc/cron.d/colab_solr_reindex @@ -0,0 +1,3 @@ +PATH="/usr/bin" +*/1 * * * * root wget "http://solr.interlegis.leg.br:8080/solr/dataimport?wt=json&indent=true&command=delta-import" -q -O - >> /tmp/solr-delta-import.log +4 6 * * * root wget "http://solr.interlegis.leg.br:8080/solr/dataimport?wt=json&indent=true&command=full-import" -q -O - >> /tmp/solr-full-import.log diff --git a/requirements.txt b/requirements.txt index 7293d4a..abb7500 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ Django==1.3.1 South==0.7.3 -django-registration==0.7 django-piston==0.2.3 -pytz +pytz==2011n chardet==1.0.1 +python-dateutil==1.5 +django-cliauth==0.9 diff --git a/setup.py b/setup.py index e69de29..1b36900 100755 --- a/setup.py +++ b/setup.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python + +from setuptools import setup, find_packages +from setupext import Data_Files, install_Data_Files + +setup(name='colab', + version='0.1-dev', + author='Sergio Oliveira', + author_email='seocam@seocam.com', + url='https://bitbucket.org/seocam/atu-colab', + packages=find_packages(), + package_data={'colab': ['templates/*.html', + 'super_archives/templates/*.html', + 'super_archives/fixtures/initial_data.json']}, + data_files=[ + Data_Files(base_dir='install_data', + copy_to='static', + template=['recursive-include colab/static *'], + preserve_path=1, + strip_dirs=2), + Data_Files(base_dir='install_data', + copy_to='wsgi', + template=['include etc/apache2/wsgi/colab.wsgi'], + preserve_path=0), + Data_Files(base_dir='install_data', + copy_to='apache-site', + template=['include etc/apache2/sites-available/colab'], + preserve_path=0), + Data_Files(base_dir='install_data', + copy_to='autofs', + template=['include etc/autofs/listas'], + preserve_path=0), + Data_Files(base_dir='install_data', + copy_to='cron.d', + template=['include etc/cron.d/*'], + preserve_path=0), + ], + install_requires=( + 'distribute', + 'Django==1.3.1', + 'South==0.7.3', + 'django-piston==0.2.3', + 'django-cliauth==0.9', + 'pytz==2011n', + 'chardet==1.0.1', + 'python-dateutil==1.5', + 'psycopg2==2.4.4'), + cmdclass = {"install_data": install_Data_Files}, +) diff --git a/setupext/__init__.py b/setupext/__init__.py new file mode 100755 index 0000000..5334452 --- /dev/null +++ b/setupext/__init__.py @@ -0,0 +1,15 @@ +"Package providing extensions to the standard distutils" + +from install_data import Data_Files, install_Data_Files + +from distutils.command.bdist_wininst import bdist_wininst + +# When building a windows installer, put some more text into +# the long description +class wininst_request_delete(bdist_wininst): + add_text = "\nIf you have installed earlier versions of this package, please remove them through 'Add/Remove Programs' before installing this release." + + def get_inidata(self): + m = self.distribution.metadata + m.long_description = m.long_description + self.add_text + return bdist_wininst.get_inidata(self) diff --git a/setupext/install_data.py b/setupext/install_data.py new file mode 100755 index 0000000..9774531 --- /dev/null +++ b/setupext/install_data.py @@ -0,0 +1,197 @@ +# -*- coding: utf-8 -*- +"""install_data.py + +Provides a more sophisticated facility to install data files +than distutils' install_data does. +You can specify your files as a template like in MANIFEST.in +and you have more control over the copy process. + +Copyright 2000 by Rene Liebscher, Germany. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +""" + +# created 2000/08/01, Rene Liebscher +# modified 2000/12/18, Martin v. Löwis + +########################################################################### +# import some modules we need + +import os,sys,string +from types import StringType,TupleType,ListType +from distutils.util import change_root +from distutils.filelist import FileList +from distutils.command.install_data import install_data + +########################################################################### +# a container class for our more sophisticated install mechanism + +class Data_Files: + """ container for list of data files. + supports alternate base_dirs e.g. 'install_lib','install_header',... + supports a directory where to copy files + supports templates as in MANIFEST.in + supports preserving of paths in filenames + eg. foo/xyz is copied to base_dir/foo/xyz + supports stripping of leading dirs of source paths + eg. foo/bar1/xyz, foo/bar2/abc can be copied to bar1/xyz, bar2/abc + """ + + def __init__(self,base_dir=None,files=None,copy_to=None,template=None,preserve_path=0,strip_dirs=0): + self.base_dir = base_dir + self.files = files + self.copy_to = copy_to + if template is not None: + t = [] + for item in template: + item = string.strip(item) + if not item:continue + t.append(item) + template = t + self.template = template + self.preserve_path = preserve_path + self.strip_dirs = strip_dirs + self.finalized = 0 + + def warn (self, msg): + sys.stderr.write ("warning: %s: %s\n" % + ("install_data", msg)) + + def debug_print (self, msg): + """Print 'msg' to stdout if the global DEBUG (taken from the + DISTUTILS_DEBUG environment variable) flag is true. + """ + from distutils.core import DEBUG + if DEBUG: + print msg + + + def finalize(self): + """ complete the files list by processing the given template """ + if self.finalized: + return + if self.files == None: + self.files = [] + if self.template != None: + if type(self.template) == StringType: + self.template = string.split(self.template,";") + filelist = FileList(self.warn,self.debug_print) + for line in self.template: + filelist.process_template_line(string.strip(line)) + filelist.sort() + filelist.remove_duplicates() + self.files.extend(filelist.files) + self.finalized = 1 + +# end class Data_Files + +########################################################################### +# a more sophisticated install routine than distutils install_data + +class install_Data_Files (install_data): + + def check_data(self,d): + """ check if data are in new format, if not create a suitable object. + returns finalized data object + """ + if not isinstance(d, Data_Files): + self.warn(("old-style data files list found " + "-- please convert to Data_Files instance")) + if type(d) is TupleType: + if len(d) != 2 or not (type(d[1]) is ListType): + raise DistutilsSetupError, \ + ("each element of 'data_files' option must be an " + "Data File instance, a string or 2-tuple (string,[strings])") + d = Data_Files(copy_to=d[0],files=d[1]) + else: + if not (type(d) is StringType): + raise DistutilsSetupError, \ + ("each element of 'data_files' option must be an " + "Data File instance, a string or 2-tuple (string,[strings])") + d = Data_Files(files=[d]) + d.finalize() + return d + + def run(self): + self.outfiles = [] + install_cmd = self.get_finalized_command('install') + + for d in self.data_files: + d = self.check_data(d) + + install_dir = self.install_dir + # alternative base dir given => overwrite install_dir + if d.base_dir != None: + install_dir = getattr(install_cmd,d.base_dir) + + # copy to an other directory + if d.copy_to != None: + if not os.path.isabs(d.copy_to): + # relatiev path to install_dir + dir = os.path.join(install_dir, d.copy_to) + elif install_cmd.root: + # absolute path and alternative root set + dir = change_root(self.root,d.copy_to) + else: + # absolute path + dir = d.copy_to + else: + # simply copy to install_dir + dir = install_dir + # warn if necceassary + self.warn("setup script did not provide a directory to copy files to " + " -- installing right in '%s'" % install_dir) + + dir=os.path.normpath(dir) + # create path + self.mkpath(dir) + + # copy all files + for src in d.files: + if d.strip_dirs > 0: + dst = string.join(string.split(os.path.normcase(src),os.sep)[d.strip_dirs:],os.sep) + else: + dst = src + if d.preserve_path: + # preserve path in filename + self.mkpath(os.path.dirname(os.path.join(dir,dst))) + out = self.copy_file(src, os.path.join(dir,dst)) + else: + out = self.copy_file(src, dir) + if type(out) is TupleType: + out = out[0] + self.outfiles.append(out) + + return self.outfiles + + def get_inputs (self): + inputs = [] + for d in self.data_files: + d = self.check_data(d) + inputs.append(d.files) + return inputs + + def get_outputs (self): + return self.outfiles + + +########################################################################### + diff --git a/solr-conf/README b/solr-conf/README new file mode 100644 index 0000000..391f918 --- /dev/null +++ b/solr-conf/README @@ -0,0 +1,46 @@ +Installation instructions for Ubuntu 10.04 +------------------------------------------- + +* Install Java, tomcat, JDBC Postgres drivers (Ubuntu partner repositories must be enabled): +sudo apt-get install sun-java6-bin tomcat6 libpg-java + +* Download Solr 3.3 and extract it: +wget http://ftp.unicamp.br/pub/apache/lucene/solr/3.3.0/apache-solr-3.3.0.tgz +tar xzf apache-solr-3.3.0.tgz + +* Create the directory /var/local/lib/solr/ and give the right permissions: +sudo mkdir -p /var/local/lib/solr/ +sudo chown tomcat6:tomcat6 /var/local/lib/solr/ + +* Copy the solr home example to /usr/local/share/: +sudo cp -R apache-solr-3.3.0/example/solr /usr/local/share/ + +* Create a folder for libs in the solr home: +sudo mkdir /usr/local/share/solr/lib/ + +* Copy Solr libs to libs folder: +sudo cp apache-solr-3.3.0/dist/*.jar /usr/local/share/solr/lib/ + +* Copy Solr distribution to solr home: +sudo cp apache-solr-3.3.0/dist/apache-solr-3.3.0.war /usr/local/share/solr/ + +* Link the JDBC Postgres drivers into the Solr installation: +sudo ln -s /usr/share/java/postgresql-jdbc3-8.4.jar /usr/local/share/solr/lib/ + +* Link configurations to /etc +sudo ln -s /usr/local/share/solr/conf/ /etc/solr + +* Copy the configuration files from this folder into /etc/solr/ + +* Link the solr-tomcat.xml file in the Tomcat configuration: +sudo ln -s /etc/solr/solr-tomcat.xml /etc/tomcat6/Catalina/localhost/solr.xml + +* Check data-config.xml to make sure all information to connect to the databases are right + +* Create a dataimport.properties on /etc/solr and give write access to tomcat6: +sudo touch /etc/solr/dataimport.properties +sudo chown tomcat6:tomcat6 /etc/solr/dataimport.properties + +* Restart tomcat: +sudo /etc/init.d/tomcat6 restart + diff --git a/solr-conf/data-config.xml b/solr-conf/data-config.xml new file mode 100644 index 0000000..efb67d7 --- /dev/null +++ b/solr-conf/data-config.xml @@ -0,0 +1,337 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/solr-conf/schema.xml b/solr-conf/schema.xml new file mode 100644 index 0000000..9269719 --- /dev/null +++ b/solr-conf/schema.xml @@ -0,0 +1,612 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + UID + + + default + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/solr-conf/solr-tomcat.xml b/solr-conf/solr-tomcat.xml new file mode 100644 index 0000000..e6ee996 --- /dev/null +++ b/solr-conf/solr-tomcat.xml @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/solr-conf/solrconfig.xml b/solr-conf/solrconfig.xml new file mode 100644 index 0000000..18baead --- /dev/null +++ b/solr-conf/solrconfig.xml @@ -0,0 +1,1535 @@ + + + + + + + + + ${solr.abortOnConfigurationError:true} + + + LUCENE_33 + + + + + + + + + + + + + + + + + ${solr.data.dir:/var/local/lib/solr/data} + + + + + + + + + + false + + 10 + + 32 + + + + 10000 + 1000 + 10000 + + + + + + + + + native + + + + + + + + + false + 32 + 10 + + + false + + + true + + + + + 1 + + 0 + + + + + + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1024 + + + + + + + + + + + + + + + + + + + + + + true + + + + + + 20 + + + 200 + + + + + + + + + + + + static firstSearcher warming in solrconfig.xml + + + + + + false + + + 2 + + + + + + + + + + + + + + + + + + + + + + + explicit + 10 + + + + + + + + + + + + + + explicit + + + velocity + + browse + layout + Solritas + + edismax + *:* + 10 + *,score + + text^0.5 features^1.0 name^1.2 sku^1.5 id^10.0 manu^1.1 cat^1.4 + + text,features,name,sku,id,manu,cat + 3 + + + text^0.5 features^1.0 name^1.2 sku^1.5 id^10.0 manu^1.1 cat^1.4 + + + on + cat + manu_exact + ipod + GB + 1 + cat,inStock + price + 0 + 600 + 50 + after + manufacturedate_dt + NOW/YEAR-10YEARS + NOW + +1YEAR + before + after + + + + on + text features name + 0 + name + + + spellcheck + + + + + + + + + + + + + + + + + + + + + + + text + true + ignored_ + + + true + links + ignored_ + + + + + + + + + + + + + + + + + + + + + search + solrpingquery + all + + + + + + + explicit + true + + + + + + + + + + + + textSpell + + + + + + default + name + spellchecker + + + + + + + + + + + + + + + + + false + false + 1 + + + spellcheck + + + + + + + + + + true + + + tvComponent + + + + + + + + + default + + + org.carrot2.clustering.lingo.LingoClusteringAlgorithm + + + 20 + + + clustering/carrot2 + + + ENGLISH + + + stc + org.carrot2.clustering.stc.STCClusteringAlgorithm + + + + + + + true + default + true + + name + id + + features + + true + + + + false + + edismax + + text^0.5 features^1.0 name^1.2 sku^1.5 id^10.0 manu^1.1 cat^1.4 + + *:* + 10 + *,score + + + clustering + + + + + + + + + + true + + + terms + + + + + + + + string + elevate.xml + + + + + + explicit + + + elevator + + + + + + + + + + + 100 + + + + + + + + 70 + + 0.5 + + [-\w ,/\n\"']{20,200} + + + + + + + ]]> + ]]> + + + + + + + + + + + + + + + + + + + + + ,, + ,, + ,, + ,, + ,]]> + ]]> + + + + + + + + + + + + + + + + + + 5 + + + + + + + + + + + + + *:* + + + + + + + + /etc/solr/data-config.xml + + + + + diff --git a/sorl-conf/README b/sorl-conf/README deleted file mode 100644 index d52da10..0000000 --- a/sorl-conf/README +++ /dev/null @@ -1,16 +0,0 @@ -Installation instructions for Ubuntu 10.04 -------------------------------------------- - -* Install Java, tomcat, Solr and JDBC Postgres drivers (Ubuntu partner repositories must be enabled): -sudo apt-get install sun-java6-bin tomcat6 solr-common libpg-java - -* Link the JDBC Postgres drivers into the Solr installation: -sudo ln -s /usr/share/java/postgresql-jdbc3-8.4.jar /usr/share/solr/WEB-INF/lib/ - -* Check data-config.xml to make sure all information to connect to the databases are right - -* Copy the configuration files from this folder into /etc/solr/conf/ - -* Restart tomcat: -sudo /etc/init.d/tomcat6 restart - diff --git a/sorl-conf/data-config.xml b/sorl-conf/data-config.xml deleted file mode 100644 index f224932..0000000 --- a/sorl-conf/data-config.xml +++ /dev/null @@ -1,314 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/sorl-conf/schema.xml b/sorl-conf/schema.xml deleted file mode 100644 index d9510f8..0000000 --- a/sorl-conf/schema.xml +++ /dev/null @@ -1,499 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - uid - - - default - - - - - - - - - - - - - - - - - - - - diff --git a/sorl-conf/solrconfig.xml b/sorl-conf/solrconfig.xml deleted file mode 100644 index 7d09eff..0000000 --- a/sorl-conf/solrconfig.xml +++ /dev/null @@ -1,1040 +0,0 @@ - - - - - - ${solr.abortOnConfigurationError:true} - - - - - - - - - - - - - /var/lib/solr/data - - - - - - false - - 10 - - - - - 32 - - 10000 - 1000 - 10000 - - - - - - - - - - - - - native - - - - - - - false - 32 - 10 - - - - - - - - false - - - true - - - - - - - - 1 - - 0 - - - - - false - - - - - - - - - - - - - - - - - - - - - - - - - - - 1024 - - - - - - - - - - - - - - - - true - - - - - - - - 20 - - - 200 - - - - - - - - - - - - - solr rocks010 - static firstSearcher warming query from solrconfig.xml - - - - - false - - - 2 - - - - - - - - - - - - - - - - - - - - - - - explicit - - - - - - - - - - - - - dismax - explicit - 0.01 - - text^0.5 features^1.0 name^1.2 sku^1.5 id^10.0 manu^1.1 cat^1.4 - - - text^0.2 features^1.1 name^1.5 manu^1.4 manu_exact^1.9 - - - popularity^0.5 recip(price,1,1000,1000)^0.3 - - - id,name,price,score - - - 2<-1 5<-2 6<90% - - 100 - *:* - - text features name - - 0 - - name - regex - - - - - - - dismax - explicit - text^0.5 features^1.0 name^1.2 sku^1.5 id^10.0 - 2<-1 5<-2 6<90% - - incubationdate_dt:[* TO NOW/DAY-1MONTH]^2.2 - - - - inStock:true - - - - cat - manu_exact - price:[* TO 500] - price:[500 TO *] - - - - - - - - - - textSpell - - - default - name - ./spellchecker - - - - - - - - - - - - false - - false - - 1 - - - spellcheck - - - - - - - - true - - - tvComponent - - - - - - - - - default - - org.carrot2.clustering.lingo.LingoClusteringAlgorithm - - 20 - - - stc - org.carrot2.clustering.stc.STCClusteringAlgorithm - - - - - true - default - true - - name - id - - features - - true - - - - false - - - clusteringComponent - - - - - - - - text - true - ignored_ - - - true - links - ignored_ - - - - - - - - - - true - - - termsComponent - - - - - - - - string - elevate.xml - - - - - - explicit - - - elevator - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - standard - solrpingquery - all - - - - - - - explicit - true - - - - - - - - - 100 - - - - - - - - 70 - - 0.5 - - [-\w ,/\n\"']{20,200} - - - - - - - ]]> - ]]> - - - - - - - - - - - - - 5 - - - - - - - - - - solr - - - - - - - /etc/solr/conf/data-config.xml - - - - - -- libgit2 0.21.2