Commit ad6b59eeaa0f6b5e1da59fbc51cd11c366cc164c

Authored by Charles Oliveira
1 parent 1b21cac6

Detached super_archives from colab, searching for crashes now

Showing 45 changed files with 0 additions and 3373 deletions   Show diff stats
colab/super_archives/__init__.py
... ... @@ -1,3 +0,0 @@
1   -
2   -
3   -default_app_config = 'colab.super_archives.apps.SuperArchivesConfig'
colab/super_archives/admin.py
... ... @@ -1,64 +0,0 @@
1   -
2   -from django.contrib import admin
3   -from .models import MailingList, Message, Thread, EmailAddress
4   -
5   -
6   -class EmailAddressAdmin(admin.ModelAdmin):
7   - search_fields = (
8   - 'address',
9   - 'user__username',
10   - 'user__first_name',
11   - 'user__last_name',
12   - )
13   -
14   -
15   -class MessageAdmin(admin.ModelAdmin):
16   - list_filter = ('spam', 'thread__mailinglist', 'received_time', )
17   - search_fields = (
18   - 'id',
19   - 'subject',
20   - 'subject_clean',
21   - 'body',
22   - 'from_address__real_name',
23   - 'from_address__address',
24   - 'from_address__user__first_name',
25   - 'from_address__user__last_name',
26   - 'from_address__user__username',
27   - )
28   - readonly_fields = ('thread', 'from_address', 'mailinglist')
29   -
30   -
31   -class ThreadAdmin(admin.ModelAdmin):
32   - list_filter = ('spam', 'mailinglist', 'message__received_time',)
33   - search_fields = (
34   - 'id',
35   - 'subject_token',
36   - 'message__subject',
37   - 'message__subject_clean',
38   - 'message__from_address__real_name',
39   - 'message__from_address__address',
40   - 'message__from_address__user__first_name',
41   - 'message__from_address__user__last_name',
42   - 'message__from_address__user__username',
43   - )
44   -
45   - readonly_fields = (
46   - 'mailinglist',
47   - 'subject_token',
48   - 'latest_message',
49   - 'score',
50   - )
51   -
52   - fields = (
53   - 'mailinglist',
54   - 'subject_token',
55   - 'latest_message',
56   - 'score',
57   - 'spam',
58   - )
59   -
60   -
61   -admin.site.register(MailingList)
62   -admin.site.register(Thread, ThreadAdmin)
63   -admin.site.register(Message, MessageAdmin)
64   -admin.site.register(EmailAddress, EmailAddressAdmin)
colab/super_archives/apps.py
... ... @@ -1,11 +0,0 @@
1   -
2   -from django.apps import AppConfig
3   -
4   -
5   -class SuperArchivesConfig(AppConfig):
6   - name = 'colab.super_archives'
7   - verbose_name = 'Super Archives'
8   -
9   - def ready(self):
10   - pass
11   - # from . import signals
colab/super_archives/context_processors.py
... ... @@ -1,14 +0,0 @@
1   -
2   -from .models import Message
3   -
4   -
5   -def mailarchive(request):
6   - context = {}
7   -
8   - try:
9   - context['last_imported_message'] = \
10   - Message.objects.latest('received_time')
11   - except Message.DoesNotExist:
12   - pass
13   -
14   - return context
colab/super_archives/fixtures/mailinglistdata.json
... ... @@ -1,186 +0,0 @@
1   -[
2   - {
3   - "fields": {
4   - "last_name": "Jar",
5   - "webpage": null,
6   - "twitter": null,
7   - "is_staff": false,
8   - "user_permissions": [
9   -
10   - ],
11   - "date_joined": "2015-02-24T21:10:35.004Z",
12   - "google_talk": null,
13   - "first_name": "John",
14   - "is_superuser": false,
15   - "last_login": "2015-02-26T17:56:13.378Z",
16   - "verification_hash": null,
17   - "role": null,
18   - "email": "johndoe@example.com",
19   - "username": "johndoe",
20   - "bio": null,
21   - "needs_update": false,
22   - "is_active": true,
23   - "facebook": null,
24   - "groups": [
25   -
26   - ],
27   - "password": "pbkdf2_sha256$12000$ez83ccNOUQZk$vYT/QcYMukXZ7D7L1qQPyYlzCUEEEF20J7/Xjef0Rqg=",
28   - "institution": null,
29   - "github": null,
30   - "modified": "2015-02-24T21:11:22.323Z"
31   - },
32   - "model": "accounts.user",
33   - "pk": 1
34   - },
35   - {
36   - "fields": {
37   - "real_name": "",
38   - "user": 1,
39   - "md5": "ed8f47ae6048f8d4456c0554578f53ff",
40   - "address": "johndoe@example.com"
41   - },
42   - "model": "super_archives.emailaddress",
43   - "pk": 1
44   - },
45   - {
46   - "fields": {
47   - "description": "",
48   - "email": "",
49   - "logo": "",
50   - "last_imported_index": 0,
51   - "is_private": true,
52   - "name": "mailman"
53   - },
54   - "model": "super_archives.mailinglist",
55   - "pk": 1
56   - },
57   - {
58   - "fields": {
59   - "description": "",
60   - "email": "",
61   - "logo": "",
62   - "last_imported_index": 0,
63   - "is_private": false,
64   - "name": "lista"
65   - },
66   - "model": "super_archives.mailinglist",
67   - "pk": 2
68   - },
69   - {
70   - "fields": {
71   - "description": "",
72   - "email": "",
73   - "logo": "",
74   - "last_imported_index": 0,
75   - "is_private": true,
76   - "name": "privatelist"
77   - },
78   - "model": "super_archives.mailinglist",
79   - "pk": 3
80   - },
81   - {
82   - "fields": {
83   - "spam": false,
84   - "subject_token": "no-subject",
85   - "mailinglist": 1,
86   - "score": 34,
87   - "latest_message": 1
88   - },
89   - "model": "super_archives.thread",
90   - "pk": 1
91   - },
92   - {
93   - "fields": {
94   - "spam": false,
95   - "subject_token": "no-subject",
96   - "mailinglist": 2,
97   - "score": 34,
98   - "latest_message": 2
99   - },
100   - "model": "super_archives.thread",
101   - "pk": 2
102   - },
103   - {
104   - "fields": {
105   - "spam": false,
106   - "subject_token": "no-subject",
107   - "mailinglist": 3,
108   - "score": 33,
109   - "latest_message": 3
110   - },
111   - "model": "super_archives.thread",
112   - "pk": 3
113   - },
114   - {
115   - "fields": {
116   - "body": "lista Mailman email",
117   - "received_time": "2015-02-24T14:23:42Z",
118   - "from_address": 1,
119   - "thread": 1,
120   - "spam": false,
121   - "subject_clean": "(no subject)",
122   - "message_id": "<20150224142347.9ED2419A5B0@localhost.localdomain>",
123   - "subject": "[Mailman] (no subject)"
124   - },
125   - "model": "super_archives.message",
126   - "pk": 1
127   - },
128   - {
129   - "fields": {
130   - "body": "nada",
131   - "received_time": "2015-02-24T14:15:39Z",
132   - "from_address": 1,
133   - "thread": 2,
134   - "spam": false,
135   - "subject_clean": "(no subject)",
136   - "message_id": "<20150224141545.1AECA19A5A0@localhost.localdomain>",
137   - "subject": "[Lista] (no subject)"
138   - },
139   - "model": "super_archives.message",
140   - "pk": 2
141   - },
142   - {
143   - "fields": {
144   - "body": "Mensagem da lista privada nada",
145   - "received_time": "2015-02-24T14:15:39Z",
146   - "from_address": 1,
147   - "thread": 3,
148   - "spam": false,
149   - "subject_clean": "(no subject)",
150   - "message_id": "<20150224141545.1AECA19A5A0@localhost.localdomain>",
151   - "subject": "[PrivateList] (no subject)"
152   - },
153   - "model": "super_archives.message",
154   - "pk": 3
155   - },
156   - {
157   - "fields": {
158   - "text": "lista Mailman email\n",
159   - "message": 1,
160   - "is_reply": false,
161   - "order": 0
162   - },
163   - "model": "super_archives.messageblock",
164   - "pk": 1
165   - },
166   - {
167   - "fields": {
168   - "text": "nada\n",
169   - "message": 2,
170   - "is_reply": false,
171   - "order": 0
172   - },
173   - "model": "super_archives.messageblock",
174   - "pk": 2
175   - },
176   - {
177   - "fields": {
178   - "text": "Mensagem da lista privada nada\n",
179   - "message": 3,
180   - "is_reply": false,
181   - "order": 0
182   - },
183   - "model": "super_archives.messageblock",
184   - "pk": 3
185   - }
186   -]
colab/super_archives/locale/en/LC_MESSAGES/django.mo
No preview for this file type
colab/super_archives/locale/en/LC_MESSAGES/django.po
... ... @@ -1,269 +0,0 @@
1   -# SOME DESCRIPTIVE TITLE.
2   -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3   -# This file is distributed under the same license as the PACKAGE package.
4   -# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5   -#
6   -#, fuzzy
7   -msgid ""
8   -msgstr ""
9   -"Project-Id-Version: PACKAGE VERSION\n"
10   -"Report-Msgid-Bugs-To: \n"
11   -"POT-Creation-Date: 2015-09-01 13:15+0000\n"
12   -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13   -"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14   -"Language-Team: LANGUAGE <LL@li.org>\n"
15   -"Language: \n"
16   -"MIME-Version: 1.0\n"
17   -"Content-Type: text/plain; charset=UTF-8\n"
18   -"Content-Transfer-Encoding: 8bit\n"
19   -
20   -#: super_archives/management/commands/import_emails.py:213
21   -msgid "[Colab] Warning - Email sent with a blank subject."
22   -msgstr ""
23   -
24   -#: super_archives/models.py:77
25   -#: super_archives/templates/message-preview.html:62
26   -#: super_archives/templates/message-thread.html:4
27   -msgid "Anonymous"
28   -msgstr ""
29   -
30   -#: super_archives/models.py:133
31   -msgid "Mailing List"
32   -msgstr ""
33   -
34   -#: super_archives/models.py:134
35   -msgid "The Mailing List where is the thread"
36   -msgstr ""
37   -
38   -#: super_archives/models.py:137
39   -msgid "Latest message"
40   -msgstr ""
41   -
42   -#: super_archives/models.py:138
43   -msgid "Latest message posted"
44   -msgstr ""
45   -
46   -#: super_archives/models.py:139
47   -msgid "Score"
48   -msgstr ""
49   -
50   -#: super_archives/models.py:140
51   -msgid "Thread score"
52   -msgstr ""
53   -
54   -#: super_archives/models.py:153
55   -msgid "Thread"
56   -msgstr ""
57   -
58   -#: super_archives/models.py:154
59   -msgid "Threads"
60   -msgstr ""
61   -
62   -#: super_archives/models.py:267
63   -msgid "Subject"
64   -msgstr ""
65   -
66   -#: super_archives/models.py:268
67   -msgid "Please enter a message subject"
68   -msgstr ""
69   -
70   -#: super_archives/models.py:271
71   -msgid "Message body"
72   -msgstr ""
73   -
74   -#: super_archives/models.py:272
75   -msgid "Please enter a message body"
76   -msgstr ""
77   -
78   -#: super_archives/models.py:282
79   -msgid "Message"
80   -msgstr ""
81   -
82   -#: super_archives/models.py:283
83   -msgid "Messages"
84   -msgstr ""
85   -
86   -#: super_archives/templates/message-preview.html:42
87   -#: super_archives/templates/message-preview.html:62
88   -msgid "by"
89   -msgstr ""
90   -
91   -#: super_archives/templates/message-preview.html:65
92   -#: super_archives/templates/message-thread.html:161
93   -msgid "ago"
94   -msgstr ""
95   -
96   -#: super_archives/templates/message-thread.html:35
97   -msgid "You must login before voting."
98   -msgstr ""
99   -
100   -#: super_archives/templates/message-thread.html:132
101   -msgid "Order by"
102   -msgstr ""
103   -
104   -#: super_archives/templates/message-thread.html:136
105   -msgid "Votes"
106   -msgstr ""
107   -
108   -#: super_archives/templates/message-thread.html:140
109   -msgid "Date"
110   -msgstr ""
111   -
112   -#: super_archives/templates/message-thread.html:145
113   -msgid "Related:"
114   -msgstr ""
115   -
116   -#: super_archives/templates/message-thread.html:156
117   -msgid "Statistics:"
118   -msgstr ""
119   -
120   -#: super_archives/templates/message-thread.html:160
121   -msgid "started at"
122   -msgstr ""
123   -
124   -#: super_archives/templates/message-thread.html:166
125   -msgid "viewed"
126   -msgstr ""
127   -
128   -#: super_archives/templates/message-thread.html:167
129   -#: super_archives/templates/message-thread.html:172
130   -#: super_archives/templates/message-thread.html:177
131   -msgid "times"
132   -msgstr ""
133   -
134   -#: super_archives/templates/message-thread.html:171
135   -msgid "answered"
136   -msgstr ""
137   -
138   -#: super_archives/templates/message-thread.html:176
139   -msgid "voted"
140   -msgstr ""
141   -
142   -#: super_archives/templates/message-thread.html:182
143   -msgid "Tags:"
144   -msgstr ""
145   -
146   -#: super_archives/templates/superarchives/emails/email_blank_subject.txt:2
147   -msgid "Hello"
148   -msgstr ""
149   -
150   -#: super_archives/templates/superarchives/emails/email_blank_subject.txt:3
151   -#, python-format
152   -msgid ""
153   -"\n"
154   -"You've sent an email to %(mailinglist)s with a blank subject and the "
155   -"following content:\n"
156   -"\n"
157   -"\"%(body)s\"\n"
158   -"\n"
159   -"Please, fill the subject in every email you send it.\n"
160   -"\n"
161   -"Thank you.\n"
162   -msgstr ""
163   -
164   -#: super_archives/templates/superarchives/emails/email_verification.txt:2
165   -#, python-format
166   -msgid ""
167   -"Hey, we want to verify that you are indeed \"%(fullname)s (%(username)s)\". "
168   -"If that's the case, please follow the link below:"
169   -msgstr ""
170   -
171   -#: super_archives/templates/superarchives/emails/email_verification.txt:6
172   -#, python-format
173   -msgid ""
174   -"If you're not %(username)s or didn't request verification you can ignore "
175   -"this email."
176   -msgstr ""
177   -
178   -#: super_archives/templates/superarchives/includes/message.html:17
179   -#: super_archives/templates/superarchives/includes/message.html:18
180   -msgid "Link to this message"
181   -msgstr ""
182   -
183   -#: super_archives/templates/superarchives/includes/message.html:46
184   -msgid "Reply"
185   -msgstr ""
186   -
187   -#: super_archives/templates/superarchives/includes/message.html:63
188   -msgid "Send a message"
189   -msgstr ""
190   -
191   -#: super_archives/templates/superarchives/includes/message.html:66
192   -msgid ""
193   -"After sending a message it will take few minutes before it shows up in here. "
194   -"Why don't you grab a coffee?"
195   -msgstr ""
196   -
197   -#: super_archives/templates/superarchives/includes/message.html:69
198   -msgid "Send"
199   -msgstr ""
200   -
201   -#: super_archives/templates/superarchives/thread-dashboard.html:4
202   -#: super_archives/templates/superarchives/thread-dashboard.html:7
203   -msgid "Groups"
204   -msgstr ""
205   -
206   -#: super_archives/templates/superarchives/thread-dashboard.html:14
207   -#, python-format
208   -msgid "%(number_of_users)s members"
209   -msgstr ""
210   -
211   -#: super_archives/templates/superarchives/thread-dashboard.html:20
212   -msgid "latest"
213   -msgstr ""
214   -
215   -#: super_archives/templates/superarchives/thread-dashboard.html:28
216   -#: super_archives/templates/superarchives/thread-dashboard.html:42
217   -msgid "more..."
218   -msgstr ""
219   -
220   -#: super_archives/templates/superarchives/thread-dashboard.html:34
221   -msgid "most relevant"
222   -msgstr ""
223   -
224   -#: super_archives/utils/email.py:14
225   -msgid "Please verify your email "
226   -msgstr ""
227   -
228   -#: super_archives/views.py:112
229   -msgid "Error trying to connect to Mailman API"
230   -msgstr ""
231   -
232   -#: super_archives/views.py:115
233   -msgid "Timeout trying to connect to Mailman API"
234   -msgstr ""
235   -
236   -#: super_archives/views.py:119
237   -msgid ""
238   -"Your message was sent to this topic. It may take some minutes before it's "
239   -"delivered by email to the group. Why don't you breath some fresh air in the "
240   -"meanwhile?"
241   -msgstr ""
242   -
243   -#: super_archives/views.py:128
244   -msgid "You cannot send an empty email"
245   -msgstr ""
246   -
247   -#: super_archives/views.py:130
248   -msgid "Mailing list does not exist"
249   -msgstr ""
250   -
251   -#: super_archives/views.py:133
252   -msgid "Unknown error trying to connect to Mailman API"
253   -msgstr ""
254   -
255   -#: super_archives/views.py:188
256   -msgid ""
257   -"The email address you are trying to verify either has already been verified "
258   -"or does not exist."
259   -msgstr ""
260   -
261   -#: super_archives/views.py:199
262   -msgid ""
263   -"The email address you are trying to verify is already an active email "
264   -"address."
265   -msgstr ""
266   -
267   -#: super_archives/views.py:213
268   -msgid "Email address verified!"
269   -msgstr ""
colab/super_archives/locale/es/LC_MESSAGES/django.mo
No preview for this file type
colab/super_archives/locale/es/LC_MESSAGES/django.po
... ... @@ -1,224 +0,0 @@
1   -# colab translation.
2   -# Copyright (C) 2012, 2013 colab
3   -# This file is distributed under the same license as the colab package.
4   -# Leonardo J. Caballero G. <leonardocaballero@gmail.com>, 2012, 2013.
5   -msgid ""
6   -msgstr ""
7   -"Project-Id-Version: colab\n"
8   -"Report-Msgid-Bugs-To: \n"
9   -"POT-Creation-Date: 2013-07-18 21:53-0300\n"
10   -"PO-Revision-Date: 2013-10-14 19:16-0430\n"
11   -"Last-Translator: Leonardo J. Caballero G. <leonardocaballero@gmail.com>\n"
12   -"Language-Team: ES <LL@li.org>\n"
13   -"Language: es\n"
14   -"MIME-Version: 1.0\n"
15   -"Content-Type: text/plain; charset=UTF-8\n"
16   -"Content-Transfer-Encoding: 8bit\n"
17   -"Plural-Forms: nplurals=2; plural=(n != 1);\n"
18   -"X-Generator: Virtaal 0.7.1\n"
19   -
20   -#: forms.py:18
21   -msgid "Name"
22   -msgstr "Nombre"
23   -
24   -#: forms.py:19
25   -msgid "Last name"
26   -msgstr "Apellido"
27   -
28   -#: forms.py:23
29   -msgid "Institution"
30   -msgstr "Institución"
31   -
32   -#: forms.py:25
33   -msgid "Role"
34   -msgstr "Rol"
35   -
36   -#: forms.py:26
37   -msgid "Twitter"
38   -msgstr "Cuenta Twitter"
39   -
40   -#: forms.py:27
41   -msgid "Facebook"
42   -msgstr "Cuenta Facebook"
43   -
44   -#: forms.py:28
45   -msgid "Google Talk"
46   -msgstr "Cuenta Google Talk"
47   -
48   -#: forms.py:29
49   -msgid "Personal Website/Blog"
50   -msgstr "Sitio Web / Blog personal"
51   -
52   -#: models.py:66
53   -msgid "User Profile"
54   -msgstr "Perfil de usuario"
55   -
56   -#: models.py:67
57   -msgid "Users Profiles"
58   -msgstr "Perfiles de usuarios"
59   -
60   -#: models.py:100
61   -msgid "Mailing List"
62   -msgstr "Lista de correo electrónico"
63   -
64   -#: models.py:101
65   -msgid "The Mailing List where is the thread"
66   -msgstr "La lista de correo electrónico donde esta el hilo de discusión"
67   -
68   -#: models.py:104
69   -msgid "Latest message"
70   -msgstr "Últimos mensaje"
71   -
72   -#: models.py:105
73   -msgid "Latest message posted"
74   -msgstr "Últimos mensajes enviados"
75   -
76   -#: models.py:106
77   -msgid "Score"
78   -msgstr "Puntuación"
79   -
80   -#: models.py:106
81   -msgid "Thread score"
82   -msgstr "Puntuación de mensaje"
83   -
84   -#: models.py:113
85   -msgid "Thread"
86   -msgstr "Hilo"
87   -
88   -#: models.py:114
89   -msgid "Threads"
90   -msgstr "Hilos"
91   -
92   -#: models.py:196
93   -msgid "Subject"
94   -msgstr "Asunto"
95   -
96   -#: models.py:197
97   -msgid "Please enter a message subject"
98   -msgstr "Por favor, ingrese un asunto del mensaje"
99   -
100   -#: models.py:200
101   -msgid "Message body"
102   -msgstr "Cuerpo de mensaje"
103   -
104   -#: models.py:201
105   -msgid "Please enter a message body"
106   -msgstr "Por favor, ingrese un cuerpo de contenido del mensaje"
107   -
108   -#: models.py:210
109   -msgid "Message"
110   -msgstr "Mensaje"
111   -
112   -#: models.py:211
113   -msgid "Messages"
114   -msgstr "Mensajes"
115   -
116   -#: templates/message-list.html:6
117   -msgid "Discussions"
118   -msgstr "Discusiones"
119   -
120   -#: templates/message-list.html:10
121   -msgid "Filters"
122   -msgstr "Filtros"
123   -
124   -#: templates/message-list.html:12
125   -msgid "Sort by"
126   -msgstr "Ordenado por"
127   -
128   -#: templates/message-list.html:14 templates/message-list.html.py:17
129   -#: templates/message-list.html:27
130   -msgid "Remove filter"
131   -msgstr "Remover filtro"
132   -
133   -#: templates/message-list.html:16
134   -msgid "Relevance"
135   -msgstr "Relevancia"
136   -
137   -#: templates/message-list.html:19
138   -msgid "Recent activity"
139   -msgstr "Actividad reciente"
140   -
141   -#: templates/message-list.html:24
142   -msgid "Lists"
143   -msgstr "Listas"
144   -
145   -#: templates/message-list.html:41
146   -msgid "No discussion found"
147   -msgstr "Sin discusión encontrada"
148   -
149   -#: templates/message-list.html:51
150   -msgid "Previous"
151   -msgstr "Anterior"
152   -
153   -#: templates/message-list.html:55
154   -msgid "Page"
155   -msgstr "Página"
156   -
157   -#: templates/message-list.html:55
158   -msgid "of"
159   -msgstr "de"
160   -
161   -#: templates/message-list.html:59
162   -msgid "Next"
163   -msgstr "Próxima"
164   -
165   -#: templates/message-preview.html:35
166   -msgid "by"
167   -msgstr "por"
168   -
169   -#: templates/message-preview.html:41
170   -msgid "anonymous"
171   -msgstr "anónimo"
172   -
173   -#: templates/message-preview.html:47 templates/message-thread.html:59
174   -msgid "ago"
175   -msgstr "atrás"
176   -
177   -#: templates/message-thread.html:19
178   -msgid "Anonymous"
179   -msgstr "Anónimo"
180   -
181   -#: templates/message-thread.html:27
182   -msgid "Vote"
183   -msgstr "Votar"
184   -
185   -#: templates/message-thread.html:31
186   -msgid "Remove votes"
187   -msgstr "Remover votos"
188   -
189   -#: templates/message-thread.html:47
190   -msgid "Order by"
191   -msgstr "Ordenar por"
192   -
193   -#: templates/message-thread.html:49
194   -msgid "Votes"
195   -msgstr "Votos"
196   -
197   -#: templates/message-thread.html:50
198   -msgid "Date"
199   -msgstr "Fecha"
200   -
201   -#: templates/message-thread.html:55
202   -msgid "Statistics:"
203   -msgstr "Estadísticas:"
204   -
205   -#: templates/message-thread.html:58
206   -msgid "started at"
207   -msgstr "iniciada"
208   -
209   -#: templates/message-thread.html:61
210   -msgid "viewed"
211   -msgstr "vistos"
212   -
213   -#: templates/message-thread.html:62 templates/message-thread.html.py:65
214   -#: templates/message-thread.html:68
215   -msgid "times"
216   -msgstr "veces"
217   -
218   -#: templates/message-thread.html:64
219   -msgid "answered"
220   -msgstr "respondido"
221   -
222   -#: templates/message-thread.html:67
223   -msgid "voted"
224   -msgstr "votado"
colab/super_archives/locale/pt_BR/LC_MESSAGES/django.mo
No preview for this file type
colab/super_archives/locale/pt_BR/LC_MESSAGES/django.po
... ... @@ -1,291 +0,0 @@
1   -# SOME DESCRIPTIVE TITLE.
2   -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3   -# This file is distributed under the same license as the PACKAGE package.
4   -#
5   -# Translators:
6   -# Sergio Oliveira <seocam@seocam.com>, 2014
7   -msgid ""
8   -msgstr ""
9   -"Project-Id-Version: colab\n"
10   -"Report-Msgid-Bugs-To: \n"
11   -"POT-Creation-Date: 2015-09-01 13:15+0000\n"
12   -"PO-Revision-Date: 2014-11-21 17:38+0000\n"
13   -"Last-Translator: Sergio Oliveira <seocam@seocam.com>\n"
14   -"Language-Team: Portuguese (Brazil) (http://www.transifex.com/projects/p/"
15   -"colab/language/pt_BR/)\n"
16   -"Language: pt_BR\n"
17   -"MIME-Version: 1.0\n"
18   -"Content-Type: text/plain; charset=UTF-8\n"
19   -"Content-Transfer-Encoding: 8bit\n"
20   -"Plural-Forms: nplurals=2; plural=(n > 1);\n"
21   -
22   -#: super_archives/management/commands/import_emails.py:213
23   -msgid "[Colab] Warning - Email sent with a blank subject."
24   -msgstr "[Colab] Atenção - Email enviado sem assunto."
25   -
26   -#: super_archives/models.py:77
27   -#: super_archives/templates/message-preview.html:62
28   -#: super_archives/templates/message-thread.html:4
29   -msgid "Anonymous"
30   -msgstr "Anônimo"
31   -
32   -#: super_archives/models.py:133
33   -msgid "Mailing List"
34   -msgstr "Lista de discussão"
35   -
36   -#: super_archives/models.py:134
37   -msgid "The Mailing List where is the thread"
38   -msgstr "Lista de emails da discussão"
39   -
40   -#: super_archives/models.py:137
41   -msgid "Latest message"
42   -msgstr "Última mensagem"
43   -
44   -#: super_archives/models.py:138
45   -msgid "Latest message posted"
46   -msgstr "Última mensagem enviada"
47   -
48   -#: super_archives/models.py:139
49   -msgid "Score"
50   -msgstr "Pontuação"
51   -
52   -#: super_archives/models.py:140
53   -msgid "Thread score"
54   -msgstr "Pontuação da discussão"
55   -
56   -#: super_archives/models.py:153
57   -msgid "Thread"
58   -msgstr "Discussão"
59   -
60   -#: super_archives/models.py:154
61   -msgid "Threads"
62   -msgstr "Discussões"
63   -
64   -#: super_archives/models.py:267
65   -msgid "Subject"
66   -msgstr "Assuntos"
67   -
68   -#: super_archives/models.py:268
69   -msgid "Please enter a message subject"
70   -msgstr "Por favor digite um assunto de email"
71   -
72   -#: super_archives/models.py:271
73   -msgid "Message body"
74   -msgstr "Corpo do email"
75   -
76   -#: super_archives/models.py:272
77   -msgid "Please enter a message body"
78   -msgstr "Por favor digite o corpo do email"
79   -
80   -#: super_archives/models.py:282
81   -msgid "Message"
82   -msgstr "Mensagem"
83   -
84   -#: super_archives/models.py:283
85   -msgid "Messages"
86   -msgstr "Mensagens"
87   -
88   -#: super_archives/templates/message-preview.html:42
89   -#: super_archives/templates/message-preview.html:62
90   -msgid "by"
91   -msgstr "por"
92   -
93   -#: super_archives/templates/message-preview.html:65
94   -#: super_archives/templates/message-thread.html:161
95   -msgid "ago"
96   -msgstr "atrás"
97   -
98   -#: super_archives/templates/message-thread.html:35
99   -msgid "You must login before voting."
100   -msgstr "Você deve logar antes de votar."
101   -
102   -#: super_archives/templates/message-thread.html:132
103   -msgid "Order by"
104   -msgstr "Ordenar por"
105   -
106   -#: super_archives/templates/message-thread.html:136
107   -msgid "Votes"
108   -msgstr "Votos"
109   -
110   -#: super_archives/templates/message-thread.html:140
111   -msgid "Date"
112   -msgstr "Data"
113   -
114   -#: super_archives/templates/message-thread.html:145
115   -msgid "Related:"
116   -msgstr "Relacionado:"
117   -
118   -#: super_archives/templates/message-thread.html:156
119   -msgid "Statistics:"
120   -msgstr "Estatísticas:"
121   -
122   -#: super_archives/templates/message-thread.html:160
123   -msgid "started at"
124   -msgstr "iniciada em"
125   -
126   -#: super_archives/templates/message-thread.html:166
127   -msgid "viewed"
128   -msgstr "vizualizada"
129   -
130   -#: super_archives/templates/message-thread.html:167
131   -#: super_archives/templates/message-thread.html:172
132   -#: super_archives/templates/message-thread.html:177
133   -msgid "times"
134   -msgstr "vezes"
135   -
136   -#: super_archives/templates/message-thread.html:171
137   -msgid "answered"
138   -msgstr "respondida"
139   -
140   -#: super_archives/templates/message-thread.html:176
141   -msgid "voted"
142   -msgstr "votada"
143   -
144   -#: super_archives/templates/message-thread.html:182
145   -msgid "Tags:"
146   -msgstr "Tags:"
147   -
148   -#: super_archives/templates/superarchives/emails/email_blank_subject.txt:2
149   -msgid "Hello"
150   -msgstr "Olá"
151   -
152   -#: super_archives/templates/superarchives/emails/email_blank_subject.txt:3
153   -#, python-format
154   -msgid ""
155   -"\n"
156   -"You've sent an email to %(mailinglist)s with a blank subject and the "
157   -"following content:\n"
158   -"\n"
159   -"\"%(body)s\"\n"
160   -"\n"
161   -"Please, fill the subject in every email you send it.\n"
162   -"\n"
163   -"Thank you.\n"
164   -msgstr ""
165   -"\n"
166   -"Você enviou um e-mail para a lista %(mailinglist)s com assunto em branco e "
167   -"com o seguinte conteúdo:\n"
168   -"\n"
169   -"\"%(body)s\"\n"
170   -"\n"
171   -"Por favor, preencha o assunto de seus e-mails antes de enviá-los.\n"
172   -"\n"
173   -"Obrigado.\n"
174   -
175   -#: super_archives/templates/superarchives/emails/email_verification.txt:2
176   -#, python-format
177   -msgid ""
178   -"Hey, we want to verify that you are indeed \"%(fullname)s (%(username)s)\". "
179   -"If that's the case, please follow the link below:"
180   -msgstr ""
181   -"Ei, nós queremos verificar que você é realmente \"%(fullname)s "
182   -"(%(username)s)\". Se este for o caso, por favor clique no link abaixo:"
183   -
184   -#: super_archives/templates/superarchives/emails/email_verification.txt:6
185   -#, python-format
186   -msgid ""
187   -"If you're not %(username)s or didn't request verification you can ignore "
188   -"this email."
189   -msgstr ""
190   -"Se você não é %(username)s ou não solicitou verificação apenas ignore este "
191   -"email."
192   -
193   -#: super_archives/templates/superarchives/includes/message.html:17
194   -#: super_archives/templates/superarchives/includes/message.html:18
195   -msgid "Link to this message"
196   -msgstr "Link para esta mensagem"
197   -
198   -#: super_archives/templates/superarchives/includes/message.html:46
199   -msgid "Reply"
200   -msgstr "Responder"
201   -
202   -#: super_archives/templates/superarchives/includes/message.html:63
203   -msgid "Send a message"
204   -msgstr "Enviar mensagem"
205   -
206   -#: super_archives/templates/superarchives/includes/message.html:66
207   -msgid ""
208   -"After sending a message it will take few minutes before it shows up in here. "
209   -"Why don't you grab a coffee?"
210   -msgstr ""
211   -"Após enviar uma mensagem pode levar alguns minutos até que ela seja exibida "
212   -"aqui. Por que você não busca um café enquanto isso?"
213   -
214   -#: super_archives/templates/superarchives/includes/message.html:69
215   -msgid "Send"
216   -msgstr "Enviar"
217   -
218   -#: super_archives/templates/superarchives/thread-dashboard.html:4
219   -#: super_archives/templates/superarchives/thread-dashboard.html:7
220   -msgid "Groups"
221   -msgstr "Grupos"
222   -
223   -#: super_archives/templates/superarchives/thread-dashboard.html:14
224   -#, python-format
225   -msgid "%(number_of_users)s members"
226   -msgstr "%(number_of_users)s membros"
227   -
228   -#: super_archives/templates/superarchives/thread-dashboard.html:20
229   -msgid "latest"
230   -msgstr "última"
231   -
232   -#: super_archives/templates/superarchives/thread-dashboard.html:28
233   -#: super_archives/templates/superarchives/thread-dashboard.html:42
234   -msgid "more..."
235   -msgstr "mais..."
236   -
237   -#: super_archives/templates/superarchives/thread-dashboard.html:34
238   -msgid "most relevant"
239   -msgstr "Mais relevante"
240   -
241   -#: super_archives/utils/email.py:14
242   -msgid "Please verify your email "
243   -msgstr "Por favor verifique seu email"
244   -
245   -#: super_archives/views.py:112
246   -msgid "Error trying to connect to Mailman API"
247   -msgstr "Erro tentando conectar à API do Mailman"
248   -
249   -#: super_archives/views.py:115
250   -msgid "Timeout trying to connect to Mailman API"
251   -msgstr "Tempo excedido tentando conectar à API do Mailman"
252   -
253   -#: super_archives/views.py:119
254   -msgid ""
255   -"Your message was sent to this topic. It may take some minutes before it's "
256   -"delivered by email to the group. Why don't you breath some fresh air in the "
257   -"meanwhile?"
258   -msgstr ""
259   -"Sua mensagem foi enviada para esta discussão. Pode ser que leve alguns "
260   -"minutos até que ela seja enviada por email para o grupo. Por que você não "
261   -"respira um ar fresco enquanto isso?"
262   -
263   -#: super_archives/views.py:128
264   -msgid "You cannot send an empty email"
265   -msgstr "Você não pode enviar um email vazio"
266   -
267   -#: super_archives/views.py:130
268   -msgid "Mailing list does not exist"
269   -msgstr "Lista de email inexistente"
270   -
271   -#: super_archives/views.py:133
272   -msgid "Unknown error trying to connect to Mailman API"
273   -msgstr "Erro desconhecido tentando conectar à API do Mailman"
274   -
275   -#: super_archives/views.py:188
276   -msgid ""
277   -"The email address you are trying to verify either has already been verified "
278   -"or does not exist."
279   -msgstr ""
280   -"O email que você está tentando verificar não existe ou já foi verificado "
281   -"anteriormente. "
282   -
283   -#: super_archives/views.py:199
284   -msgid ""
285   -"The email address you are trying to verify is already an active email "
286   -"address."
287   -msgstr "O email que você está tentando verificar já é um email ativo."
288   -
289   -#: super_archives/views.py:213
290   -msgid "Email address verified!"
291   -msgstr "Endereço de email verificado!"
colab/super_archives/management/__init__.py
colab/super_archives/management/commands/__init__.py
colab/super_archives/management/commands/import_emails.py
... ... @@ -1,301 +0,0 @@
1   -#!/usr/bin/env python
2   -# -*- encoding: utf-8 -*-
3   -
4   -"""Import emails from a mailman storage to the django database."""
5   -
6   -import os
7   -import re
8   -import sys
9   -import mailbox
10   -import logging
11   -from optparse import make_option
12   -
13   -from django.conf import settings
14   -from django.db import transaction
15   -from django.template.defaultfilters import slugify
16   -from django.core.management.base import BaseCommand, CommandError
17   -from django.template.loader import render_to_string
18   -from django.utils.translation import ugettext as _
19   -
20   -from colab.super_archives.utils.email import colab_send_email
21   -from colab.super_archives.models import (MailingList, Message,
22   - Thread, EmailAddress)
23   -from message import Message as CustomMessage
24   -
25   -
26   -class Command(BaseCommand, object):
27   - """Get emails from mailman archives and import them in the django db. """
28   -
29   - help = __doc__
30   -
31   - RE_SUBJECT_CLEAN = re.compile('((re|res|fw|fwd|en|enc):)|\[.*?\]',
32   - re.IGNORECASE)
33   - THREAD_CACHE = {}
34   - EMAIL_ADDR_CACHE = {}
35   -
36   - lock_file = settings.SUPER_ARCHIVES_LOCK_FILE
37   -
38   - # A new command line option to get the dump file to parse.
39   - option_list = BaseCommand.option_list + (
40   - make_option(
41   - '--archives_path',
42   - dest='archives_path',
43   - help=('Path of email archives to be imported. '
44   - '(default: {})').format(settings.SUPER_ARCHIVES_PATH),
45   - default=settings.SUPER_ARCHIVES_PATH),
46   -
47   - make_option(
48   - '--exclude-list',
49   - dest='exclude_lists',
50   - help=("Mailing list that won't be imported. It can be used many"
51   - "times for more than one list."),
52   - action='append',
53   - default=settings.SUPER_ARCHIVES_EXCLUDE),
54   -
55   - make_option(
56   - '--all',
57   - dest='all',
58   - help='Import all messages (default: False)',
59   - action="store_true",
60   - default=False),
61   - )
62   -
63   - def __init__(self, *args, **kwargs):
64   - super(Command, self).__init__(*args, **kwargs)
65   -
66   - def log(self, msg, error=False):
67   - """Log message helper."""
68   - output = self.stdout
69   - if error:
70   - output = self.stderr
71   -
72   - output.write(msg)
73   - output.write('\n')
74   -
75   - def parse_emails(self, email_filename, index=0):
76   - """Generator function that parse and extract emails from the file
77   - `email_filename` starting from the position `index`.
78   -
79   - Yield: An instance of `mailbox.mboxMessage` for each email in the
80   - file.
81   -
82   - """
83   - self.log("Parsing email dump: %s." % email_filename)
84   - mbox = mailbox.mbox(email_filename, factory=CustomMessage)
85   -
86   - # Get each email from mbox file
87   - #
88   - # The following implementation was used because the object
89   - # mbox does not support slicing. Converting the object to a
90   - # tuple (as represented in the code down here) was a valid
91   - # option but its performance was too poor.
92   - #
93   - # for message in tuple(mbox)[index:]:
94   - # yield message
95   - #
96   - key = index
97   - while key in mbox:
98   - key += 1
99   - yield key-1, mbox[key-1]
100   -
101   - def get_emails(self, mailinglist_dir, all, exclude_lists):
102   - """Generator function that get the emails from each mailing
103   - list dump dirctory. If `all` is set to True all the emails in the
104   - mbox will be imported if not it will just resume from the last
105   - message previously imported. The lists set in `exclude_lists`
106   - won't be imported.
107   -
108   - Yield: A tuple in the form: (mailing list name, email message).
109   -
110   - """
111   - self.log("Getting emails dumps from: %s" % mailinglist_dir)
112   -
113   - # Get the list of directories ending with .mbox
114   - mailing_lists_mboxes = (mbox for mbox in os.listdir(mailinglist_dir)
115   - if mbox.endswith('.mbox'))
116   -
117   - # Get messages from each mbox
118   - for mbox in mailing_lists_mboxes:
119   - mbox_path = os.path.join(mailinglist_dir, mbox, mbox)
120   - mailinglist_name = mbox.split('.')[0]
121   -
122   - # Check if the mailinglist is set not to be imported
123   - if exclude_lists and mailinglist_name in exclude_lists:
124   - continue
125   -
126   - # Find the index of the last imported message
127   - if all:
128   - n_msgs = 0
129   - else:
130   - try:
131   - mailinglist = MailingList.objects.get(
132   - name=mailinglist_name
133   - )
134   - n_msgs = mailinglist.last_imported_index
135   - except MailingList.DoesNotExist:
136   - n_msgs = 0
137   -
138   - for index, msg in self.parse_emails(mbox_path, n_msgs):
139   - yield mailinglist_name, msg, index
140   -
141   - def get_thread(self, email, mailinglist):
142   - """Group messages by thread looking for similar subjects"""
143   -
144   - subject_slug = slugify(email.subject_clean)
145   - thread = self.THREAD_CACHE.get(subject_slug, {}).get(mailinglist.id)
146   - if thread is None:
147   - thread = Thread.all_objects.get_or_create(
148   - mailinglist=mailinglist,
149   - subject_token=subject_slug
150   - )[0]
151   -
152   - if self.THREAD_CACHE.get(subject_slug) is None:
153   - self.THREAD_CACHE[subject_slug] = dict()
154   - self.THREAD_CACHE[subject_slug][mailinglist.id] = thread
155   -
156   - thread.latest_message = email
157   - thread.update_keywords()
158   - thread.save()
159   - return thread
160   -
161   - def save_email(self, list_name, email_msg, index):
162   - """Save email message into the database."""
163   -
164   - msg_id = email_msg.get('Message-ID')
165   - if not msg_id:
166   - return
167   -
168   - # Update last imported message into the DB
169   - mailinglist, created = MailingList.objects.get_or_create(
170   - name=list_name
171   - )
172   - mailinglist.last_imported_index = index
173   -
174   - if created:
175   - # if the mailinglist is newly created it's sure that the message
176   - # is not in the DB yet.
177   - self.create_email(mailinglist, email_msg)
178   -
179   - else:
180   - # If the message is already at the database don't do anything
181   - try:
182   - Message.all_objects.get(
183   - message_id=msg_id,
184   - thread__mailinglist=mailinglist
185   - )
186   -
187   - except Message.DoesNotExist:
188   - self.create_email(mailinglist, email_msg)
189   -
190   - mailinglist.save()
191   -
192   - def create_email(self, mailinglist, email_msg):
193   - received_time = email_msg.get_received_datetime()
194   - if not received_time:
195   - return
196   -
197   - real_name, from_ = email_msg.get_from_addr()
198   -
199   - email_addr = self.EMAIL_ADDR_CACHE.get(from_)
200   - if email_addr is None:
201   - email_addr = EmailAddress.objects.get_or_create(
202   - address=from_)[0]
203   - self.EMAIL_ADDR_CACHE[from_] = email_addr
204   -
205   - if not email_addr.real_name and real_name:
206   - email_addr.real_name = real_name[:64]
207   - email_addr.save()
208   -
209   - subject = email_msg.get_subject()
210   - if not subject:
211   - colab_send_email(
212   - subject=_(
213   - u"[Colab] Warning - Email sent with a blank subject."
214   - ),
215   - message=render_to_string(
216   - u'superarchives/emails/email_blank_subject.txt',
217   - {
218   - 'email_body': email_msg.get_body(),
219   - 'mailinglist': mailinglist.name,
220   - 'user': email_addr.get_full_name()
221   - }
222   - ),
223   - to=email_addr.address
224   - )
225   -
226   - email = Message.all_objects.create(
227   - message_id=email_msg.get('Message-ID'),
228   - from_address=email_addr,
229   - subject=subject,
230   - subject_clean=self.RE_SUBJECT_CLEAN.sub('', subject).strip(),
231   - body=email_msg.get_body(),
232   - received_time=email_msg.get_received_datetime(),
233   - )
234   - email.thread = self.get_thread(email, mailinglist)
235   - email.save()
236   - email.update_blocks()
237   -
238   - @transaction.commit_manually
239   - def import_emails(self, archives_path, all, exclude_lists=None):
240   - """Get emails from the filesystem from the `archives_path`
241   - and store them into the database. If `all` is set to True all
242   - the filesystem storage will be imported otherwise the
243   - importation will resume from the last message previously
244   - imported. The lists set in `exclude_lists` won't be imported.
245   -
246   - """
247   -
248   - count = 0
249   - email_generator = self.get_emails(archives_path, all, exclude_lists)
250   - for mailinglist_name, msg, index in email_generator:
251   - try:
252   - self.save_email(mailinglist_name, msg, index)
253   - except:
254   - # This anti-pattern is needed to avoid the transations to
255   - # get stuck in case of errors.
256   - transaction.rollback()
257   - raise
258   -
259   - count += 1
260   - if count % 1000 == 0:
261   - transaction.commit()
262   -
263   - transaction.commit()
264   -
265   - def handle(self, *args, **options):
266   - """Main command method."""
267   -
268   - # Already running, so quit
269   - if os.path.exists(self.lock_file):
270   - self.log(("This script is already running. "
271   - "(If your are sure it's not please "
272   - "delete the lock file in {}')").format(self.lock_file))
273   - sys.exit(0)
274   -
275   - if not os.path.exists(os.path.dirname(self.lock_file)):
276   - os.mkdir(os.path.dirname(self.lock_file), 0755)
277   -
278   - archives_path = options.get('archives_path')
279   - self.log('Using archives_path `%s`' % settings.SUPER_ARCHIVES_PATH)
280   -
281   - if not os.path.exists(archives_path):
282   - msg = 'archives_path ({}) does not exist'.format(archives_path)
283   - raise CommandError(msg)
284   - run_lock = file(self.lock_file, 'w')
285   - run_lock.close()
286   -
287   - try:
288   - self.import_emails(
289   - archives_path,
290   - options.get('all'),
291   - options.get('exclude_lists'),
292   - )
293   - except Exception as e:
294   - logging.exception(e)
295   - raise
296   - finally:
297   - os.remove(self.lock_file)
298   -
299   - for mlist in MailingList.objects.all():
300   - mlist.update_privacy()
301   - mlist.save()
colab/super_archives/management/commands/message.py
... ... @@ -1,103 +0,0 @@
1   -
2   -import re
3   -import pytz
4   -import email
5   -import codecs
6   -import mailbox
7   -import datetime
8   -from email.iterators import typed_subpart_iterator
9   -
10   -import chardet
11   -
12   -
13   -def get_charset(message, default='ASCII'):
14   - """Get the message charset"""
15   -
16   - charset = message.get_content_charset()
17   -
18   - if not charset:
19   - charset = message.get_charset()
20   -
21   - if not charset:
22   - charset = default
23   -
24   - try:
25   - codecs.lookup(charset)
26   - except LookupError:
27   - charset = default
28   -
29   - return charset
30   -
31   -
32   -class Message(mailbox.mboxMessage):
33   -
34   - RECEIVED_DELIMITER = re.compile('\n|;')
35   -
36   - def get_subject(self):
37   - subject = email.header.decode_header(self['Subject'])
38   -
39   - if isinstance(subject, list):
40   - new_subject = u''
41   - for text_part, encoding in subject:
42   - if not encoding:
43   - encoding = get_charset(self)
44   -
45   - try:
46   - new_subject += unicode(text_part, encoding)
47   - except (UnicodeDecodeError, LookupError):
48   - try:
49   - new_subject += unicode(text_part, get_charset(self))
50   - except (UnicodeDecodeError, LookupError):
51   - encoding = chardet.detect(text_part)['encoding']
52   - new_subject += unicode(text_part, encoding)
53   -
54   - return ''.join(new_subject)
55   -
56   - def get_body(self):
57   - """Get the body of the email message"""
58   -
59   - if self.is_multipart():
60   - # get the plain text version only
61   - text_parts = [part
62   - for part in typed_subpart_iterator(self,
63   - 'text',
64   - 'plain')]
65   - body = []
66   - for part in text_parts:
67   - charset = get_charset(part, get_charset(self))
68   - body.append(unicode(part.get_payload(decode=True),
69   - charset,
70   - "replace"))
71   -
72   - return u"\n".join(body).strip()
73   -
74   - else: # if it is not multipart, the payload will be a string
75   - # representing the message body
76   - body = unicode(self.get_payload(decode=True),
77   - get_charset(self),
78   - "replace")
79   - return body.strip()
80   -
81   - def get_received_datetime(self):
82   - if 'Received' not in self:
83   - return None
84   - # The time received should always be the last element
85   - # in the `Received` attribute from the message headers
86   - received_header = self.RECEIVED_DELIMITER.split(self['Received'])
87   - received_time_header = received_header[-1].strip()
88   -
89   - date_tuple = email.utils.parsedate_tz(received_time_header)
90   - utc_timestamp = email.utils.mktime_tz(date_tuple)
91   - utc_datetime = datetime.datetime.fromtimestamp(utc_timestamp,
92   - pytz.utc)
93   -
94   - return utc_datetime
95   -
96   - def get_from_addr(self):
97   - real_name_raw, from_ = email.utils.parseaddr(self['From'])
98   - real_name_str, encoding = email.header.decode_header(real_name_raw)[0]
99   - if not encoding:
100   - encoding = 'ascii'
101   -
102   - real_name = unicode(real_name_str, encoding, errors='replace')
103   - return real_name, from_
colab/super_archives/management/commands/update_blocks.py
... ... @@ -1,12 +0,0 @@
1   -#!/usr/bin/env python
2   -
3   -from django.core.management.base import BaseCommand
4   -from colab.super_archives.models import Message
5   -
6   -
7   -class Command(BaseCommand):
8   - help = "Update message blocks (used to hide the reply part messages)"
9   -
10   - def handle(self, *args, **kwargs):
11   - for message in Message.objects.iterator():
12   - message.update_blocks()
colab/super_archives/management/commands/update_keywords.py
... ... @@ -1,12 +0,0 @@
1   -#!/usr/bin/env python
2   -
3   -from django.core.management.base import BaseCommand
4   -from colab.super_archives.models import Thread
5   -
6   -
7   -class Command(BaseCommand):
8   - help = "Update keywords used in tag cloud and related thread"
9   -
10   - def handle(self, *args, **kwargs):
11   - for thread in Thread.objects.iterator():
12   - thread.update_keywords()
colab/super_archives/managers.py
... ... @@ -1,43 +0,0 @@
1   -from django.db import models
2   -
3   -from haystack.query import SearchQuerySet
4   -
5   -
6   -class NotSpamManager(models.Manager):
7   - """Only return objects which are not marked as spam."""
8   -
9   - def get_queryset(self):
10   - return super(NotSpamManager, self).get_queryset().exclude(spam=True)
11   -
12   -
13   -class HighestScore(NotSpamManager):
14   - def get_queryset(self):
15   - queryset = super(HighestScore, self).get_queryset()
16   - return queryset.order_by('-score', '-latest_message__received_time')
17   -
18   - def from_haystack(self):
19   - return SearchQuerySet().filter(type='thread')
20   -
21   -
22   -class MostVotedManager(NotSpamManager):
23   - def get_queryset(self):
24   - """Query for the most voted messages sorting by the sum of
25   - voted and after by date."""
26   -
27   - queryset = super(MostVotedManager, self).get_queryset()
28   -
29   - sql = """
30   - SELECT
31   - count(sav.id)
32   - FROM
33   - super_archives_vote AS sav
34   - WHERE
35   - super_archives_message.id = sav.message_id
36   - """
37   -
38   - messages = queryset.extra(
39   - select={
40   - 'vote_count': sql,
41   - }
42   - )
43   - return messages.order_by('-vote_count', 'received_time')
colab/super_archives/migrations/0001_initial.py
... ... @@ -1,230 +0,0 @@
1   -# -*- coding: utf-8 -*-
2   -from __future__ import unicode_literals
3   -
4   -from django.db import models, migrations
5   -import hitcounter.models
6   -import taggit.managers
7   -import django.db.models.deletion
8   -from django.conf import settings
9   -import colab.super_archives.models
10   -
11   -
12   -class Migration(migrations.Migration):
13   -
14   - dependencies = [
15   - migrations.swappable_dependency(settings.AUTH_USER_MODEL),
16   - ('taggit', '0001_initial'),
17   - ]
18   -
19   - operations = [
20   - migrations.CreateModel(
21   - name='EmailAddress',
22   - fields=[
23   - ('id', models.AutoField(verbose_name='ID', serialize=False,
24   - auto_created=True, primary_key=True)),
25   - ('address', models.EmailField(unique=True, max_length=75)),
26   - ('real_name', models.CharField(db_index=True, max_length=64,
27   - blank=True)),
28   - ('md5', models.CharField(max_length=32, null=True)),
29   - ('user', models.ForeignKey(related_name=b'emails',
30   - on_delete=django.db.models.deletion.SET_NULL,
31   - to=settings.AUTH_USER_MODEL, null=True)),
32   - ],
33   - options={
34   - 'ordering': ('id',),
35   - },
36   - bases=(models.Model,),
37   - ),
38   - migrations.CreateModel(
39   - name='EmailAddressValidation',
40   - fields=[
41   - ('id', models.AutoField(verbose_name='ID', serialize=False,
42   - auto_created=True, primary_key=True)),
43   - ('address', models.EmailField(unique=True, max_length=75)),
44   - ('validation_key',
45   - models.CharField(
46   - default=colab.super_archives.models.get_validation_key,
47   - max_length=32, null=True)),
48   - ('created', models.DateTimeField(auto_now_add=True)),
49   - ('user',
50   - models.ForeignKey(related_name=b'emails_not_validated',
51   - to=settings.AUTH_USER_MODEL, null=True)),
52   - ],
53   - options={
54   - },
55   - bases=(models.Model,),
56   - ),
57   - migrations.CreateModel(
58   - name='Keyword',
59   - fields=[
60   - ('id', models.AutoField(verbose_name='ID', serialize=False,
61   - auto_created=True, primary_key=True)),
62   - ('keyword', models.CharField(max_length=b'128')),
63   - ('weight', models.IntegerField(default=0)),
64   - ],
65   - options={
66   - 'ordering': ('?',),
67   - },
68   - bases=(models.Model,),
69   - ),
70   - migrations.CreateModel(
71   - name='MailingList',
72   - fields=[
73   - ('id', models.AutoField(verbose_name='ID', serialize=False,
74   - auto_created=True, primary_key=True)),
75   - ('name', models.CharField(max_length=80)),
76   - ('email', models.EmailField(max_length=75)),
77   - ('description', models.TextField()),
78   - ('logo', models.FileField(upload_to=b'list_logo')),
79   - ('last_imported_index', models.IntegerField(default=0)),
80   - ],
81   - options={
82   - },
83   - bases=(models.Model,),
84   - ),
85   - migrations.CreateModel(
86   - name='MailingListMembership',
87   - fields=[
88   - ('id', models.AutoField(verbose_name='ID', serialize=False,
89   - auto_created=True, primary_key=True)),
90   - ('mailinglist',
91   - models.ForeignKey(to='super_archives.MailingList')),
92   - ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
93   - ],
94   - options={
95   - },
96   - bases=(models.Model,),
97   - ),
98   - migrations.CreateModel(
99   - name='Message',
100   - fields=[
101   - ('id', models.AutoField(verbose_name='ID', serialize=False,
102   - auto_created=True, primary_key=True)),
103   - ('subject',
104   - models.CharField(help_text='Please enter a message subject',
105   - max_length=512, verbose_name='Subject',
106   - db_index=True)),
107   - ('subject_clean', models.CharField(max_length=512,
108   - db_index=True)),
109   - ('body',
110   - models.TextField(default=b'',
111   - help_text='Please enter a message body',
112   - verbose_name='Message body')),
113   - ('received_time', models.DateTimeField(db_index=True)),
114   - ('message_id', models.CharField(max_length=512)),
115   - ('spam', models.BooleanField(default=False)),
116   - ('from_address',
117   - models.ForeignKey(to='super_archives.EmailAddress')),
118   - ],
119   - options={
120   - 'ordering': ('received_time',),
121   - 'verbose_name': 'Message',
122   - 'verbose_name_plural': 'Messages',
123   - },
124   - bases=(models.Model,),
125   - ),
126   - migrations.CreateModel(
127   - name='MessageBlock',
128   - fields=[
129   - ('id', models.AutoField(verbose_name='ID', serialize=False,
130   - auto_created=True, primary_key=True)),
131   - ('text', models.TextField()),
132   - ('is_reply', models.BooleanField(default=False)),
133   - ('order', models.IntegerField()),
134   - ('message', models.ForeignKey(related_name=b'blocks',
135   - to='super_archives.Message')),
136   - ],
137   - options={
138   - 'ordering': ('order',),
139   - },
140   - bases=(models.Model,),
141   - ),
142   - migrations.CreateModel(
143   - name='MessageMetadata',
144   - fields=[
145   - ('id', models.AutoField(verbose_name='ID', serialize=False,
146   - auto_created=True, primary_key=True)),
147   - ('name', models.CharField(max_length=512)),
148   - ('value', models.TextField()),
149   - ('Message', models.ForeignKey(to='super_archives.Message')),
150   - ],
151   - options={
152   - },
153   - bases=(models.Model,),
154   - ),
155   - migrations.CreateModel(
156   - name='Thread',
157   - fields=[
158   - ('id', models.AutoField(verbose_name='ID', serialize=False,
159   - auto_created=True, primary_key=True)),
160   - ('subject_token', models.CharField(max_length=512)),
161   - ('score', models.IntegerField(default=0,
162   - help_text='Thread score',
163   - verbose_name='Score')),
164   - ('spam', models.BooleanField(default=False)),
165   - ('latest_message',
166   - models.OneToOneField(related_name=b'+',
167   - null=True, to='super_archives.Message',
168   - help_text='Latest message posted',
169   - verbose_name='Latest message')),
170   - ('mailinglist',
171   - models.ForeignKey(
172   - verbose_name='Mailing List',
173   - to='super_archives.MailingList',
174   - help_text='The Mailing List where is the thread')),
175   - ('tags',
176   - taggit.managers.TaggableManager(
177   - to='taggit.Tag',
178   - through='taggit.TaggedItem',
179   - help_text='A comma-separated list of tags.',
180   - verbose_name='Tags')),
181   - ],
182   - options={
183   - 'ordering': ('-latest_message__received_time',),
184   - 'verbose_name': 'Thread',
185   - 'verbose_name_plural': 'Threads',
186   - },
187   - bases=(models.Model, hitcounter.models.HitCounterModelMixin),
188   - ),
189   - migrations.CreateModel(
190   - name='Vote',
191   - fields=[
192   - ('id', models.AutoField(verbose_name='ID', serialize=False,
193   - auto_created=True, primary_key=True)),
194   - ('created', models.DateTimeField(auto_now_add=True)),
195   - ('message', models.ForeignKey(to='super_archives.Message')),
196   - ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
197   - ],
198   - options={
199   - },
200   - bases=(models.Model,),
201   - ),
202   - migrations.AlterUniqueTogether(
203   - name='vote',
204   - unique_together=set([('user', 'message')]),
205   - ),
206   - migrations.AlterUniqueTogether(
207   - name='thread',
208   - unique_together=set([('subject_token', 'mailinglist')]),
209   - ),
210   - migrations.AddField(
211   - model_name='message',
212   - name='thread',
213   - field=models.ForeignKey(to='super_archives.Thread', null=True),
214   - preserve_default=True,
215   - ),
216   - migrations.AlterUniqueTogether(
217   - name='message',
218   - unique_together=set([('thread', 'message_id')]),
219   - ),
220   - migrations.AddField(
221   - model_name='keyword',
222   - name='thread',
223   - field=models.ForeignKey(to='super_archives.Thread'),
224   - preserve_default=True,
225   - ),
226   - migrations.AlterUniqueTogether(
227   - name='emailaddressvalidation',
228   - unique_together=set([('user', 'address')]),
229   - ),
230   - ]
colab/super_archives/migrations/0002_mailinglist_is_private.py
... ... @@ -1,20 +0,0 @@
1   -# -*- coding: utf-8 -*-
2   -from __future__ import unicode_literals
3   -
4   -from django.db import models, migrations
5   -
6   -
7   -class Migration(migrations.Migration):
8   -
9   - dependencies = [
10   - ('super_archives', '0001_initial'),
11   - ]
12   -
13   - operations = [
14   - migrations.AddField(
15   - model_name='mailinglist',
16   - name='is_private',
17   - field=models.BooleanField(default=False),
18   - preserve_default=True,
19   - ),
20   - ]
colab/super_archives/migrations/__init__.py
colab/super_archives/models.py
... ... @@ -1,415 +0,0 @@
1   -# -*- coding: utf-8 -*-
2   -
3   -import urllib
4   -
5   -from uuid import uuid4
6   -from hashlib import md5
7   -
8   -from django.db import models
9   -from django.conf import settings
10   -from django.utils import timezone
11   -from django.core.urlresolvers import reverse
12   -from django.utils.translation import ugettext_lazy as _
13   -
14   -from html2text import html2text
15   -from haystack.query import SearchQuerySet
16   -from taggit.managers import TaggableManager
17   -from hitcounter.models import HitCounterModelMixin
18   -
19   -from .managers import NotSpamManager, MostVotedManager, HighestScore
20   -from .utils import blocks, email
21   -from .utils.etiquetador import etiquetador
22   -from colab.accounts.utils import mailman
23   -
24   -
25   -def get_validation_key():
26   - return uuid4().hex
27   -
28   -
29   -class EmailAddressValidation(models.Model):
30   - address = models.EmailField(unique=True)
31   - user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True,
32   - related_name='emails_not_validated')
33   - validation_key = models.CharField(max_length=32, null=True,
34   - default=get_validation_key)
35   - created = models.DateTimeField(auto_now_add=True)
36   -
37   - class Meta:
38   - unique_together = ('user', 'address')
39   -
40   - @classmethod
41   - def create(cls, address, user):
42   - email_address_validation = cls.objects.create(address=address,
43   - user=user)
44   - return email_address_validation
45   -
46   - @classmethod
47   - def verify_email(cls, email_address_validation, verification_url):
48   - return email.send_verification_email(
49   - email_address_validation.address,
50   - email_address_validation.user,
51   - email_address_validation.validation_key,
52   - verification_url
53   - )
54   -
55   -
56   -class EmailAddress(models.Model):
57   - user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True,
58   - related_name='emails', on_delete=models.SET_NULL)
59   - address = models.EmailField(unique=True)
60   - real_name = models.CharField(max_length=64, blank=True, db_index=True)
61   - md5 = models.CharField(max_length=32, null=True)
62   -
63   - class Meta:
64   - ordering = ('id', )
65   -
66   - def save(self, *args, **kwargs):
67   - self.md5 = md5(self.address).hexdigest()
68   - super(EmailAddress, self).save(*args, **kwargs)
69   -
70   - def get_full_name(self):
71   - if self.user and self.user.get_full_name():
72   - return self.user.get_full_name()
73   - else:
74   - return self.real_name
75   -
76   - def get_full_name_or_anonymous(self):
77   - return self.get_full_name() or _('Anonymous')
78   -
79   - def __unicode__(self):
80   - return '"%s" <%s>' % (self.get_full_name(), self.address)
81   -
82   -
83   -class MailingList(models.Model):
84   - name = models.CharField(max_length=80)
85   - email = models.EmailField()
86   - description = models.TextField()
87   - logo = models.FileField(upload_to='list_logo') # TODO
88   - last_imported_index = models.IntegerField(default=0)
89   - is_private = models.BooleanField(default=False)
90   -
91   - def update_privacy(self):
92   - self.is_private = mailman.is_private_list(self.name)
93   -
94   - def get_absolute_url(self):
95   - params = {
96   - 'list': self.name,
97   - 'type': 'thread',
98   - 'order': 'latest',
99   - }
100   - return u'{}?{}'.format(reverse('haystack_search'),
101   - urllib.urlencode(params))
102   -
103   - def __unicode__(self):
104   - return self.name
105   -
106   -
107   -class MailingListMembership(models.Model):
108   - user = models.ForeignKey(settings.AUTH_USER_MODEL)
109   - mailinglist = models.ForeignKey(MailingList)
110   -
111   - def __unicode__(self):
112   - return '%s on %s' % (self.user.email, self.mailinglist.name)
113   -
114   -
115   -class Keyword(models.Model):
116   - keyword = models.CharField(max_length='128')
117   - weight = models.IntegerField(default=0)
118   - thread = models.ForeignKey('Thread')
119   -
120   - class Meta:
121   - ordering = ('?', ) # random order
122   -
123   - def __unicode__(self):
124   - return self.keyword
125   -
126   -
127   -class Thread(models.Model, HitCounterModelMixin):
128   -
129   - subject_token = models.CharField(max_length=512)
130   - mailinglist = \
131   - models.ForeignKey(
132   - MailingList,
133   - verbose_name=_(u"Mailing List"),
134   - help_text=_(u"The Mailing List where is the thread"))
135   - latest_message = \
136   - models.OneToOneField('Message', null=True, related_name='+',
137   - verbose_name=_(u"Latest message"),
138   - help_text=_(u"Latest message posted"))
139   - score = models.IntegerField(default=0, verbose_name=_(u"Score"),
140   - help_text=_(u"Thread score"))
141   - spam = models.BooleanField(default=False)
142   -
143   - highest_score = HighestScore()
144   - all_objects = models.Manager()
145   - objects = NotSpamManager()
146   - tags = TaggableManager()
147   -
148   - # Save this pseudo now to avoid calling the
149   - # function N times in the loops below
150   - now = timezone.now()
151   -
152   - class Meta:
153   - verbose_name = _(u"Thread")
154   - verbose_name_plural = _(u"Threads")
155   - unique_together = ('subject_token', 'mailinglist')
156   - ordering = ('-latest_message__received_time', )
157   -
158   - @models.permalink
159   - def get_absolute_url(self):
160   - return ('thread_view', [self.mailinglist, self.subject_token])
161   -
162   - def update_keywords(self):
163   - blocks = MessageBlock.objects.filter(message__thread__pk=self.pk,
164   - is_reply=False)
165   -
166   - self.tags.clear()
167   -
168   - text = u'\n'.join(map(unicode, blocks))
169   - tags = etiquetador(html2text(text))
170   -
171   - for tag, weight in tags:
172   - keyword, created = Keyword.objects.get_or_create(thread=self,
173   - keyword=tag)
174   - if created or keyword.weight != weight:
175   - keyword.weight = weight
176   - keyword.save()
177   -
178   - if weight >= 3:
179   - self.tags.add(tag)
180   -
181   - # removing old tags not used anylonger
182   - if tags:
183   - qs = Keyword.objects.filter(thread=self)
184   - qs = qs.exclude(keyword__in=zip(*tags)[0])
185   - qs.delete()
186   -
187   - def get_related(self):
188   - query_string = u' '.join(self.tags.names())
189   - if query_string:
190   - query_set = SearchQuerySet().exclude(django_id=self.pk)
191   - return query_set.filter(content=query_string, type='thread')
192   -
193   - return tuple()
194   -
195   - def __unicode__(self):
196   - return '%s - %s (%s)' % (self.id,
197   - self.subject_token,
198   - self.message_set.count())
199   -
200   - def _days_ago(self, date):
201   - return (self.now - date).days
202   -
203   - def _get_score(self, weight, created):
204   - return max(weight - (self._days_ago(created) // 3), 5)
205   -
206   - def update_score(self):
207   - """Update the relevance score for this thread.
208   -
209   - The score is calculated with the following variables:
210   -
211   - * vote_weight: 100 - (minus) 1 for each 3 days since
212   - voted with minimum of 5.
213   - * replies_weight: 300 - (minus) 1 for each 3 days since
214   - replied with minimum of 5.
215   - * page_view_weight: 10.
216   -
217   - * vote_score: sum(vote_weight)
218   - * replies_score: sum(replies_weight)
219   - * page_view_score: sum(page_view_weight)
220   -
221   - * score = (vote_score + replies_score + page_view_score) // 10
222   - with minimum of 0 and maximum of 5000
223   -
224   - """
225   -
226   - if not self.subject_token:
227   - return
228   -
229   - vote_score = 0
230   - replies_score = 0
231   - for msg in self.message_set.all():
232   - # Calculate replies_score
233   - replies_score += self._get_score(300, msg.received_time)
234   -
235   - # Calculate vote_score
236   - for vote in msg.vote_set.all():
237   - vote_score += self._get_score(100, vote.created)
238   -
239   - # Calculate page_view_score
240   - page_view_score = self.hits * 10
241   -
242   - self.score = (page_view_score + vote_score + replies_score) // 10
243   - self.save()
244   -
245   -
246   -class Vote(models.Model):
247   - user = models.ForeignKey(settings.AUTH_USER_MODEL)
248   - message = models.ForeignKey('Message')
249   - created = models.DateTimeField(auto_now_add=True)
250   -
251   - class Meta:
252   - unique_together = ('user', 'message')
253   -
254   - def __unicode__(self):
255   - return 'Vote on %s by %s' % (self.message.id,
256   - self.user.username)
257   -
258   -
259   -class Message(models.Model):
260   -
261   - from_address = models.ForeignKey(EmailAddress, db_index=True)
262   - thread = models.ForeignKey(Thread, null=True, db_index=True)
263   - # RFC 2822 recommends to use 78 chars + CRLF (so 80 chars) for
264   - # the max_length of a subject but most of implementations
265   - # goes for 256. We use 512 just in case.
266   - subject = models.CharField(max_length=512, db_index=True,
267   - verbose_name=_(u"Subject"),
268   - help_text=_(u"Please enter a message subject"))
269   - subject_clean = models.CharField(max_length=512, db_index=True)
270   - body = models.TextField(default='',
271   - verbose_name=_(u"Message body"),
272   - help_text=_(u"Please enter a message body"))
273   - received_time = models.DateTimeField(db_index=True)
274   - message_id = models.CharField(max_length=512)
275   - spam = models.BooleanField(default=False)
276   -
277   - all_objects = models.Manager()
278   - objects = NotSpamManager()
279   - most_voted = MostVotedManager()
280   -
281   - class Meta:
282   - verbose_name = _(u"Message")
283   - verbose_name_plural = _(u"Messages")
284   - unique_together = ('thread', 'message_id')
285   - ordering = ('received_time', )
286   -
287   - def __unicode__(self):
288   - return '(%s) %s: %s' % (self.id,
289   - self.from_address.get_full_name(),
290   - self.subject_clean)
291   -
292   - def update_blocks(self):
293   - # delete all blocks for that message
294   - self.blocks.all().delete()
295   -
296   - for i, block in enumerate(blocks.EmailBlockParser(self)):
297   - MessageBlock.from_emailblock(block, self, i)
298   -
299   - @property
300   - def mailinglist(self):
301   - if not self.thread:
302   - return None
303   -
304   - return self.thread.mailinglist
305   -
306   - def vote_list(self):
307   - """Return a list of user that voted in this message."""
308   - return [vote.user for vote in self.vote_set.iterator()]
309   -
310   - def votes_count(self):
311   - return len(self.vote_list())
312   -
313   - def vote(self, user):
314   - Vote.objects.create(
315   - message=self,
316   - user=user
317   - )
318   -
319   - def unvote(self, user):
320   - Vote.objects.get(
321   - message=self,
322   - user=user
323   - ).delete()
324   -
325   - @property
326   - def url(self):
327   - """Shortcut to get thread url"""
328   - return reverse('thread_view', args=[self.mailinglist.name,
329   - self.thread.subject_token])
330   -
331   - @property
332   - def description(self):
333   - """Alias to self.body"""
334   - return self.body
335   -
336   - @property
337   - def title(self):
338   - """Alias to self.subject_clean"""
339   - return self.subject_clean
340   -
341   - @property
342   - def modified(self):
343   - """Alias to self.modified"""
344   - return self.received_time
345   -
346   - @property
347   - def tag(self):
348   - if not self.thread:
349   - return None
350   - return self.mailinglist.name
351   -
352   - @property
353   - def author(self):
354   - return self.fullname
355   -
356   - @property
357   - def author_url(self):
358   - if self.from_address.user_id:
359   - return self.from_address.user.get_absolute_url()
360   - return None
361   -
362   - # An alias for author
363   - @property
364   - def modified_by(self):
365   - return self.author
366   -
367   - # An alias for author_url
368   - @property
369   - def modified_by_url(self):
370   - return self.author_url
371   -
372   - @property
373   - def fullname(self):
374   - return self.from_address.get_full_name()
375   -
376   - @property
377   - def icon_name(self):
378   - return u'envelope'
379   -
380   - @property
381   - def type(self):
382   - return u'thread'
383   -
384   -
385   -class MessageBlock(models.Model):
386   - message = models.ForeignKey(Message, related_name='blocks')
387   - text = models.TextField()
388   - is_reply = models.BooleanField(default=False)
389   - order = models.IntegerField()
390   -
391   - def __unicode__(self):
392   - return self.text
393   -
394   - class Meta:
395   - ordering = ('order', )
396   -
397   - @classmethod
398   - def from_emailblock(klass, emailblock, message, order):
399   - obj = klass.objects.create(text=emailblock.text,
400   - is_reply=emailblock.is_reply,
401   - message=message,
402   - order=order)
403   - return obj
404   -
405   -
406   -class MessageMetadata(models.Model):
407   - Message = models.ForeignKey(Message)
408   - # Same problem here than on subjects. Read comment above
409   - # on Message.subject
410   - name = models.CharField(max_length=512)
411   - value = models.TextField()
412   -
413   - def __unicode__(self):
414   - return 'Email Message Id: %s - %s: %s' % (self.Message.id,
415   - self.name, self.value)
colab/super_archives/search_indexes.py
... ... @@ -1,100 +0,0 @@
1   -# -*- coding: utf-8 -*-
2   -
3   -import math
4   -
5   -from haystack import indexes
6   -
7   -from colab.search.base_indexes import BaseIndex
8   -from .models import Thread
9   -
10   -
11   -class ThreadIndex(BaseIndex, indexes.Indexable):
12   - title = indexes.CharField(model_attr='latest_message__subject_clean')
13   - description = indexes.CharField(use_template=True)
14   - latest_description = indexes.CharField(
15   - model_attr='latest_message__description',
16   - indexed=False,
17   - )
18   - created = indexes.DateTimeField()
19   - modified = indexes.DateTimeField(
20   - model_attr='latest_message__modified'
21   - )
22   - tag = indexes.CharField(model_attr='mailinglist__name')
23   - collaborators = indexes.CharField(use_template=True, stored=False)
24   - mailinglist_url = indexes.CharField(
25   - model_attr='mailinglist__get_absolute_url',
26   - indexed=False,
27   - )
28   - latest_message_pk = indexes.IntegerField(
29   - model_attr='latest_message__pk', indexed=False
30   - )
31   - rating = indexes.IntegerField(model_attr='score')
32   -
33   - def get_model(self):
34   - return Thread
35   -
36   - def get_updated_field(self):
37   - return 'latest_message__received_time'
38   -
39   - # def prepare_fullname(self, obj):
40   - # return obj.message_set.first().from_address.get_full_name()
41   -
42   - def prepare_fullname_and_username(self, obj):
43   - from_address = obj.message_set.first().from_address
44   - if not from_address.user:
45   - return from_address.get_full_name()
46   -
47   - return u'{}\n{}'.format(
48   - from_address.get_full_name(),
49   - from_address.user.username,
50   - )
51   -
52   - def prepare_author(self, obj):
53   - first_message = obj.message_set.first()
54   - return first_message.from_address.get_full_name()
55   -
56   - def prepare_author_url(self, obj):
57   - first_message = obj.message_set.first()
58   - return first_message.author_url
59   -
60   - def prepare_modified_by(self, obj):
61   - modified_by = obj.latest_message.modified_by
62   - if modified_by:
63   - return modified_by
64   - return obj.message_set.first().author
65   -
66   - def prepare_modified_by_url(self, obj):
67   - modified_by_url = obj.latest_message.modified_by_url
68   - if modified_by_url:
69   - return modified_by_url
70   - return None
71   -
72   - def prepare_created(self, obj):
73   - return obj.message_set.first().received_time
74   -
75   - def prepare_fullname(self, obj):
76   - fullname = obj.latest_message.from_address.get_full_name()
77   - if not fullname:
78   - fullname = obj.message_set.first().from_address.get_full_name()
79   - return fullname
80   -
81   - def prepare_icon_name(self, obj):
82   - return u'envelope'
83   -
84   - def prepare_type(self, obj):
85   - return u'thread'
86   -
87   - def index_queryset(self, using=None):
88   - elements = self.get_model().objects.filter(
89   - spam=False, mailinglist__is_private=False
90   - ).exclude(subject_token='')
91   -
92   - return elements
93   -
94   - def get_boost(self, obj):
95   - boost = super(ThreadIndex, self).get_boost(obj)
96   -
97   - if obj.score >= 10:
98   - boost = boost * math.log(obj.score)
99   -
100   - return boost
colab/super_archives/signals.py
... ... @@ -1,23 +0,0 @@
1   -
2   -from django.db.models.signals import post_save
3   -from django.dispatch import receiver
4   -from django.conf import settings
5   -
6   -from .models import EmailAddress
7   -
8   -
9   -@receiver(post_save, sender=settings.AUTH_USER_MODEL)
10   -def create_email_address(sender, instance, created, **kwargs):
11   - if not created:
12   - return
13   -
14   - email, email_created = EmailAddress.objects.get_or_create(
15   - address=instance.email,
16   - defaults={
17   - 'real_name': instance.get_full_name(),
18   - 'user': instance,
19   - }
20   - )
21   -
22   - email.user = instance
23   - email.save()
colab/super_archives/templates/message-preview.html
... ... @@ -1,69 +0,0 @@
1   -{% load i18n tz highlight %}
2   -
3   -<li class="preview-message">
4   -<span class="glyphicon glyphicon-{{ result.icon_name }}" title="{{ result.type }}"></span>
5   -
6   -{% if result.tag %}
7   -<a href="{% firstof result.mailinglist_url result.mailinglist.get_absolute_url result.url %}">
8   - <span class="label label-primary">{{ result.tag }}</span>
9   -</a>
10   -{% endif %}
11   -
12   -{% if result.title %}
13   - <a href="{{ result.url }}{% if result.type == 'thread' and result.latest_message_pk %}#msg-{{ result.latest_message_pk }}{% elif result.type == 'thread' and result.pk %}#msg-{{ result.pk }}{% endif %}" {% if result.latest_description %}title="{{ result.latest_description|escape|truncatechars:150 }}"{% elif result.description %}title="{{ result.description|escape|truncatechars:150 }}"{% endif %}>
14   - <span class="subject">
15   - <!-- a striptags filter was raising an error here because using with highlight -->
16   - {% if query %}
17   - {% highlight result.title with query max_length "1000" %}
18   - {% else %}
19   - {{ result.title }}
20   - {% endif %}
21   - </span>
22   - </a>
23   -{% endif %}
24   -
25   -{% if result.description %}
26   - <!-- a striptags filter was raising an error here because using with highlight -->
27   - <span class="quiet">- {% if query %}
28   - {% highlight result.description with query max_length "150" %}
29   - {% else %}
30   - {% if result.latest_description %}
31   - {{ result.latest_description|striptags|escape|truncatechars:150 }}
32   - {% elif result.description %}
33   - {{ result.description|striptags|escape|truncatechars:150 }}
34   - {% endif %}
35   - {% endif %}
36   - </span>
37   -{% endif %}
38   -
39   -{% if result.fullname or result.modified or result.modified_by %}
40   - <div class="quiet">
41   - {% if result.modified_by %}
42   - <span class="pull-left">{% trans "by" %}
43   - {% if result.modified_by_url %}
44   - <a href="{{ result.modified_by_url }}">
45   - {% else %}
46   - <span>
47   - {% endif %}
48   -
49   - {% if query %}
50   - {% highlight result.modified_by with query %}
51   - {% else %}
52   - {{ result.modified_by }}
53   - {% endif %}
54   -
55   - {% if result.modified_by_url %}
56   - </a>
57   - {% else %}
58   - </span>
59   - {% endif %}
60   - </span>
61   - {% else %}
62   - <span class="pull-left">{% trans "by" %} {% trans "Anonymous" %}</span>
63   - {% endif %}
64   - {% if result.modified %}
65   - <span class="pull-right">{{ result.modified|localtime|timesince }} {% trans "ago" %}</span>
66   - {% endif %}
67   - </div>
68   -{% endif %}
69   -</li>
colab/super_archives/templates/message-thread.html
... ... @@ -1,193 +0,0 @@
1   -{% extends "base.html" %}
2   -{% load i18n tz superarchives %}
3   -
4   -{% trans "Anonymous" as anonymous %}
5   -
6   -{% block title %}{{ first_msg.subject_clean }}{% endblock %}
7   -
8   -{% block head_js %}
9   -
10   -<script>
11   - function vote_done_callback(step) {
12   - console.debug('(un)vote successfuly (step ' + step + ')');
13   - var $btn = $(this);
14   - var step;
15   -
16   - if ($btn.hasClass('btn-default')) {
17   - step = 1;
18   - } else {
19   - step = -1;
20   - }
21   -
22   - $btn.prev('.vote-count').text(function(self, count) {
23   - return parseInt(count) + step;
24   - });
25   -
26   - $btn.toggleClass('btn-success');
27   - $btn.toggleClass('btn-default');
28   - $btn.button('reset')
29   - }
30   -
31   - function vote_fail_callback(jqXHR, textStatus, errorThrown) {
32   - var msg;
33   -
34   - if (jqXHR.status === 403) {
35   - msg = " {% trans 'You must login before voting.' %}"
36   -
37   - $('#alert-js #alert-message').html(msg);
38   - $('#alert-js').show();
39   - scroll(0, 0);
40   - }
41   -
42   - }
43   -
44   - function vote(event) {
45   - var $ajax;
46   - var $btn = $(this);
47   - var $msg = $(this).parents('.email-message');
48   -
49   - var method;
50   - var csrftoken = $.cookie('csrftoken');
51   - var msg_id = $msg.attr('id').split('-')[1];
52   -
53   - if($btn.hasClass('btn-default')) {
54   - method = 'PUT';
55   - } else {
56   - method = 'DELETE';
57   - }
58   -
59   - console.debug('trying to vote');
60   - $btn.button('loading');
61   - $ajax = $.ajax({
62   - url: "/archives/message/" + msg_id + "/vote",
63   - type: method,
64   - context: $btn.get(0),
65   - beforeSend: function(xhr, settings) {
66   - xhr.setRequestHeader("X-CSRFToken", csrftoken);
67   - }
68   - });
69   - $ajax.done(vote_done_callback);
70   - $ajax.fail(vote_fail_callback);
71   - }
72   -
73   - function scrollToAnchor(aid){
74   - var aTag = $(aid);
75   - console.log(aTag);
76   - $('html, body').animate({scrollTop: aTag.offset().top}, 800);
77   - }
78   -
79   - {% if user.is_active %}
80   - function focus_reply(event) {
81   - scrollToAnchor('#msg-reply');
82   - $('textarea', '#msg-reply').focus();
83   - }
84   - {% endif %}
85   -
86   - // Binding functions
87   - $(function() {
88   - $(".panel-heading").on('click', function(event) {
89   - var $target = $(event.target);
90   - // Do not collapse the the message if the clicked element (target)
91   - // is a button or a link
92   - if($target.hasClass('btn') || $target.is('a') || $target.parent().is('a')) {
93   - return;
94   - }
95   -
96   - $(this).next('.panel-collapse').collapse('toggle');
97   - });
98   -
99   - $('.vote.btn', this).on('click', vote);
100   - {% if user.is_active %}
101   - $('.reply.btn', this).on('click', focus_reply);
102   - {% endif %}
103   -
104   - $('.message-link').popover({'placement': 'right'});
105   - });
106   -
107   -</script>
108   -
109   -{% endblock %}
110   -
111   -{% block main-content %}
112   -<div class="row">
113   -
114   - <div class="col-lg-12">
115   - <h2>{{ first_msg.subject_clean }}</h2>
116   - <hr />
117   - </div>
118   -
119   - <div class="col-lg-9 col-md-9 col-sm-12">
120   - <ul class="unstyled-list">
121   - {% for email in emails %}
122   - {% include "superarchives/includes/message.html" with userprofile=email.from_address.user emailaddress=email.from_address fullname=email.from_address.get_full_name_or_anonymous %}
123   - {% endfor %}
124   -
125   - {% if user.is_active %}
126   - {% include "superarchives/includes/message.html" with userprofile=user emailaddress=user.email fullname=user.get_full_name reply=True %}
127   - {% endif %}
128   - </ul>
129   - </div>
130   -
131   - <div class="col-lg-3 col-md-3 hidden-sm hidden-xs">
132   - <h4><strong>{% trans "Order by" %}:</strong></h4>
133   - <ul class="unstyled-list">
134   - <li>
135   - <span class="glyphicon glyphicon-chevron-right"></span>
136   - <a href="{% append_to_get order='voted' %}">{% trans "Votes" %}</a>
137   - </li>
138   - <li>
139   - <span class="glyphicon glyphicon-chevron-right"></span>
140   - <a href="{% append_to_get order='date' %}">{% trans "Date" %}</a>
141   - </li>
142   - </ul>
143   -
144   - {% if thread.get_related %}
145   - <h4><strong>{% trans "Related:" %}</strong></h4>
146   - <ul class="unstyled-list">
147   - {% for similar in thread.get_related|slice:":10" %}
148   - <li>
149   - <span class="label label-primary label-small">{{ similar.tag }}</span>
150   - <a href="{{ similar.url }}">{{ similar.title|truncatechars:50 }}</a>
151   - </li>
152   - {% endfor %}
153   - </ul>
154   - {% endif %}
155   -
156   - <h4><strong>{% trans "Statistics:" %}</strong></h4>
157   - <ul class="unstyled-list">
158   - <li>
159   - <span class="glyphicon glyphicon-chevron-right"></span>
160   - {% trans "started at" %}
161   - <h5>{{ first_msg.received_time|localtime|timesince }} {% trans "ago" %}</h5>
162   - </li>
163   -
164   - <li>
165   - <span class="glyphicon glyphicon-chevron-right"></span>
166   - {% trans "viewed" %}
167   - <h5>{{ pagehits }} {% trans "times" %}</h5>
168   - </li>
169   - <li>
170   - <span class="glyphicon glyphicon-chevron-right"></span>
171   - {% trans "answered" %}
172   - <h5>{{ emails|length }} {% trans "times" %}</h5>
173   - </li>
174   - <li>
175   - <span class="glyphicon glyphicon-chevron-right"></span>
176   - {% trans "voted" %}
177   - <h5>{{ total_votes }} {% trans "times" %}</h5>
178   - </li>
179   - </ul>
180   -
181   - {% if thread.keyword_set.count %}
182   - <h4><strong>{% trans "Tags:" %}</strong></h4>
183   - <div class="tag-cloud">
184   - {% for keyword in thread.keyword_set.all %}
185   - <a class="tag size-{{ keyword.weight }}" href="{% url 'haystack_search' %}?q={{ keyword }}">{{ keyword|escape }}</a>
186   - {% endfor %}
187   - </div>
188   - {% endif %}
189   -
190   - </div>
191   -
192   -</div>
193   -{% endblock %}
colab/super_archives/templates/search/indexes/super_archives/thread_collaborators.txt
... ... @@ -1,5 +0,0 @@
1   -{% for message in object.message_set.iterator %}
2   - {{ message.from_address.get_full_name }}
3   - {{ message.from_address.get_full_name|slugify }}
4   - {{ message.from_address.user.username }}
5   -{% endfor %}
colab/super_archives/templates/search/indexes/super_archives/thread_description.txt
... ... @@ -1,3 +0,0 @@
1   -{% for message in object.message_set.iterator %}
2   - {{ message.body }}
3   -{% endfor %}
colab/super_archives/templates/search/indexes/super_archives/thread_text.txt
... ... @@ -1,10 +0,0 @@
1   -{{ object.subject_token }}
2   -
3   -{% for message in object.message_set.iterator %}
4   - {{ message.title }}
5   - {{ message.title|slugify }}
6   - {{ message.author }}
7   - {{ message.author|slugify }}
8   - {{ message.body }}
9   - {{ message.body|slugify }}
10   -{% endfor %}
colab/super_archives/templates/superarchives/emails/email_blank_subject.txt
... ... @@ -1,11 +0,0 @@
1   -{% load i18n %}
2   -{% trans 'Hello' %} {{ user }},
3   -{% blocktrans with body=email_body mailinglist=mailinglist %}
4   -You've sent an email to {{ mailinglist }} with a blank subject and the following content:
5   -
6   -"{{ body }}"
7   -
8   -Please, fill the subject in every email you send it.
9   -
10   -Thank you.
11   -{% endblocktrans %}
colab/super_archives/templates/superarchives/emails/email_verification.txt
... ... @@ -1,6 +0,0 @@
1   -{% load i18n %}
2   -{% blocktrans with fullname=user.get_full_name|title username=user.username|lower %}Hey, we want to verify that you are indeed "{{ fullname }} ({{ username }})". If that's the case, please follow the link below:{% endblocktrans %}
3   -
4   -{{ verification_url }}
5   -
6   -{% blocktrans with username=user.username %}If you're not {{ username }} or didn't request verification you can ignore this email.{% endblocktrans %}
colab/super_archives/templates/superarchives/includes/message.html
... ... @@ -1,78 +0,0 @@
1   -{% load gravatar superarchives tz i18n %}
2   -<li>
3   - {% spaceless %}
4   - <div class="email-message" id="msg-{% firstof email.id 'reply' %}">
5   - <div class="panel panel-default">
6   - <div class="panel-heading clearfix">
7   - <div class="col-lg-6 col-md-6 col-sm-6">
8   - {% if userprofile.get_absolute_url %}
9   - <a href="{{ userprofile.get_absolute_url }}">
10   - {% endif %}
11   - {% gravatar emailaddress 34 %}
12   - <strong class="user-fullname">{{ fullname }}</strong>
13   - {% if userprofile.get_absolute_url %}
14   - </a>
15   - {% endif %}
16   - {% if email.id %}
17   - <div class="hidden-xs hidden-sm div-message-link" title="{% trans 'Link to this message' %}">
18   - <button class="btn btn-default message-link" data-toggle="popover" data-content="<input type='text' value='{{ request.get_host }}{{ request.path }}#msg-{{ email.id }}' class='form-control' />" data-title="{% trans 'Link to this message' %}" data-html="true" data-container="body">
19   - <span class="glyphicon glyphicon-link"></span>
20   - </button>
21   - </div>
22   - {% endif %}
23   - </div>
24   -
25   - {% if not reply %}
26   - <div class="col-lg-6 col-md-6 col-sm-6">
27   - <div class="pull-right text-right">
28   - <span class="date">
29   - {{ email.received_time|localtime|date:'DATETIME_FORMAT' }}
30   - </span>
31   -
32   - <div class="btn-group">
33   - <button class="btn btn-default vote-count disabled">
34   - {{ email.votes_count }}
35   - </button>
36   - {% if user in email.vote_list %}
37   - <button class="btn btn-success vote" data-loading-text="...">
38   - {% else %}
39   - <button class="btn btn-default vote" data-loading-text="...">
40   - {% endif %}
41   - <span class="glyphicon glyphicon-thumbs-up"></span>
42   - </button>
43   - </div>
44   -
45   - {% if user.is_active %}
46   - <button class="btn btn-default reply" title="{% trans 'Reply' %}">
47   - <span class="glyphicon glyphicon-share"></span>
48   - </button>
49   - {% endif %}
50   - </div>
51   - </div>
52   - {% endif %}
53   -
54   - </div>
55   - <div class="panel-collapse in">
56   - <div class="panel-body">
57   - {% if not reply %}
58   - {% display_message email %}
59   - {% else %}
60   - <form method="POST">
61   - {% csrf_token %}
62   - <p>
63   - <textarea name="emailbody" placeholder="{% trans 'Send a message' %}" rows="5" class="form-control"></textarea>
64   - </p>
65   - <div class="col-lg-9 col-md-8 col-sm-8 col-xs-7">
66   - <p class="quiet">{% trans "After sending a message it will take few minutes before it shows up in here. Why don't you grab a coffee?" %}</p>
67   - </div>
68   - <div class="col-lg-3 col-md-4 col-sm-4 col-xs-5 text-right">
69   - <button class="btn btn-success" type="submit">{% trans 'Send' %}</button>
70   - </div>
71   - </form>
72   - {% endif %}
73   - </div>
74   - </div>
75   - </div>
76   - </div>
77   -{% endspaceless %}
78   -</li>
colab/super_archives/templates/superarchives/tags/display_message.html
... ... @@ -1,5 +0,0 @@
1   -{% for block in blocks %}
2   - {% if block.is_reply %}
3   - <button class="btn btn-info btn-xs toggle-reply" onclick="$(this).next().toggle();">...</button>
4   - <div style="display: none; color: #707;">{% else %}<div>{% endif %}{{ block|safe|linebreaksbr }}</div>
5   -{% endfor %}
colab/super_archives/templates/superarchives/thread-dashboard.html
... ... @@ -1,52 +0,0 @@
1   -{% extends 'base.html' %}
2   -{% load i18n %}
3   -
4   -{% block title %}{% trans 'Groups'|title %}{% endblock %}
5   -
6   -{% block main-content %}
7   - <h2>{% trans 'Groups'|title %}</h2>
8   - <hr/>
9   -
10   - {% for listname, description, latest, most_relevant, number_of_users in lists %}
11   - {% if latest or most_relevant %}
12   - <h3><b>{{ listname|title|lower }} {% if description %} ({{ description }}){% endif %}</b></h3>
13   - <div class="btn-group btn-group-sm">
14   - <a href="#" class="btn btn-default" disabled="disabled">{% blocktrans %}{{ number_of_users }} members{% endblocktrans %}</a>
15   - </div>
16   - <hr/>
17   -
18   - <div class="row">
19   - <div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
20   - <h4>{% trans 'latest'|title %}</h4>
21   - <ul class="message-list">
22   - {% for thread in latest %}
23   - {% include "message-preview.html" with result=thread.latest_message %}
24   - {% endfor %}
25   - </ul>
26   - <div class="text-right">
27   - <a href="{% url 'haystack_search' %}?order=latest&list={{ listname }}&type=thread">
28   - {% trans "more..." %}
29   - </a>
30   - </div>
31   - </div>
32   -
33   - <div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
34   - <h4>{% trans 'most relevant'|title %}</h4>
35   - <ul class="message-list">
36   - {% for thread in most_relevant %}
37   - {% include "message-preview.html" with result=thread %}
38   - {% endfor %}
39   - </ul>
40   - <div class="text-right">
41   - <a href="{% url 'haystack_search' %}?list={{ listname }}&type=thread">
42   - {% trans "more..." %}
43   - </a>
44   - </div>
45   - </div>
46   - </div>
47   -
48   -
49   - {% endif %}
50   - {% endfor %}
51   -
52   -{% endblock %}
colab/super_archives/templatetags/__init__.py
colab/super_archives/templatetags/superarchives.py
... ... @@ -1,33 +0,0 @@
1   -
2   -from django import template
3   -from colab.super_archives.utils import url
4   -
5   -
6   -register = template.Library()
7   -TEMPLATE_PATH = 'superarchives/tags/'
8   -
9   -
10   -@register.inclusion_tag(TEMPLATE_PATH + 'display_message.html')
11   -def display_message(email):
12   - if not email.blocks.count():
13   - email.update_blocks()
14   -
15   - return {'blocks': email.blocks.all}
16   -
17   -
18   -@register.simple_tag(takes_context=True)
19   -def append_to_get(context, **kwargs):
20   - return url.append_to_get(
21   - context['request'].META['PATH_INFO'],
22   - context['request'].META['QUERY_STRING'],
23   - **kwargs
24   - )
25   -
26   -
27   -@register.simple_tag(takes_context=True)
28   -def pop_from_get(context, **kwargs):
29   - return url.pop_from_get(
30   - context['request'].META['PATH_INFO'],
31   - context['request'].META['QUERY_STRING'],
32   - **kwargs
33   - )
colab/super_archives/tests/__init__.py
colab/super_archives/tests/test_privatelist.py
... ... @@ -1,71 +0,0 @@
1   -# -*- coding:utf-8 -*-
2   -import mock
3   -
4   -from colab.accounts.utils import mailman
5   -from django.test import TestCase, Client
6   -
7   -
8   -class ArchivesViewTest(TestCase):
9   -
10   - fixtures = ['mailinglistdata.json']
11   -
12   - def setUp(self):
13   - self.client = Client()
14   -
15   - def authenticate_user(self):
16   - self.client.login(username='johndoe', password='1234')
17   -
18   - def test_see_only_private_list_if_member(self):
19   - mailman.get_user_mailinglists = mock.Mock(
20   - return_value="[{'listname': 'privatelist'}]")
21   - mailman.extract_listname_from_list = mock.Mock(
22   - return_value="['privatelist']")
23   - mailman.list_users = mock.Mock(return_value="['johndoe@example.com']")
24   -
25   - self.authenticate_user()
26   - request = self.client.get('/archives/thread/')
27   -
28   - list_data = request.context['lists']
29   -
30   - self.assertEqual('lista', list_data[0][0])
31   - self.assertEqual('privatelist', list_data[1][0])
32   - self.assertEqual(2, len(list_data))
33   -
34   - def test_see_only_public_if_not_logged_in(self):
35   - request = self.client.get('/archives/thread/')
36   -
37   - list_data = request.context['lists']
38   -
39   - self.assertEqual('lista', list_data[0][0])
40   - self.assertEqual(1, len(list_data))
41   -
42   - def test_see_private_thread_in_dashboard_if_member(self):
43   - mailman.get_user_mailinglists = mock.Mock(
44   - return_value="[{'listname': 'privatelist'}]")
45   - mailman.extract_listname_from_list = mock.Mock(
46   - return_value="['privatelist']")
47   -
48   - self.authenticate_user()
49   - request = self.client.get('/dashboard')
50   -
51   - latest_threads = request.context['latest_threads']
52   - hottest_threads = request.context['hottest_threads']
53   -
54   - self.assertEqual(2, len(latest_threads))
55   - self.assertEqual(2, len(hottest_threads))
56   -
57   - def test_dont_see_private_thread_if_logged_out(self):
58   - request = self.client.get('/dashboard')
59   -
60   - latest_threads = request.context['latest_threads']
61   - hottest_threads = request.context['hottest_threads']
62   -
63   - self.assertEqual(1, len(latest_threads))
64   - self.assertEqual(1, len(hottest_threads))
65   -
66   - def test_dont_see_private_threads_in_profile_if_logged_out(self):
67   - request = self.client.get('/account/johndoe')
68   -
69   - emails = request.context['emails']
70   -
71   - self.assertEqual(1, len(emails))
colab/super_archives/urls.py
... ... @@ -1,17 +0,0 @@
1   -from django.conf.urls import patterns, url
2   -
3   -from .views import (EmailView, EmailValidationView, ThreadView,
4   - ThreadDashboardView, VoteView)
5   -
6   -
7   -urlpatterns = patterns(
8   - 'super_archives.views',
9   - url(r'thread/(?P<mailinglist>[-\w]+)/(?P<thread_token>[-\w]+)$',
10   - ThreadView.as_view(), name="thread_view"),
11   - url(r'thread/$', ThreadDashboardView.as_view(), name='thread_list'),
12   - url(r'manage/email/validate/?$', EmailValidationView.as_view(),
13   - name="archive_email_validation_view"),
14   - url(r'manage/email/(?P<key>[0-9a-z]{32})?', EmailView.as_view(),
15   - name="archive_email_view"),
16   - url(r'message/(?P<msg_id>\d+)/vote$', VoteView.as_view()),
17   -)
colab/super_archives/utils/__init__.py
colab/super_archives/utils/blocks.py
... ... @@ -1,101 +0,0 @@
1   -
2   -import re
3   -
4   -from django.utils.html import strip_tags
5   -
6   -from html2text import html2text
7   -
8   -
9   -EXTENDED_PUNCTUATION = '!"#$%&\'()*+,-./:;=?@[\\]^_`{|}~ \t\n\r\x0b\x0c'
10   -RE_WRAPPED_BY_HTML = re.compile(r'^<[a-z]+[^>]*>.*</[a-z]+[^>]*>$',
11   - re.MULTILINE | re.IGNORECASE | re.DOTALL)
12   -RE_LINKS = re.compile(r'(?P<link>https?://[^ \t\r\n\<]+)')
13   -LINK_MARKUP = u'<a target="_blank" href="\g<link>">\g<link></a>'
14   -
15   -RE_REPLY_LINE = re.compile(r'^[\s\t>]*>[\s\t]*')
16   -
17   -RE_BR_TO_LINEBREAK = re.compile(r'<\s*/?\s*br\s*/?\s*>')
18   -
19   -
20   -class EmailBlock(list):
21   - def __init__(self, is_reply=False, mark_links=True, html2text=True):
22   - self.mark_links = mark_links
23   - self.html2text = html2text
24   - self.is_reply = is_reply
25   -
26   - def _html2text(self, text):
27   - if RE_WRAPPED_BY_HTML.match(text.strip()):
28   - return html2text(text)
29   -
30   - text, n = RE_BR_TO_LINEBREAK.subn('\n', text)
31   - text = strip_tags(text)
32   - return text
33   -
34   - def _mark_links(self, text):
35   - text, n = RE_LINKS.subn(LINK_MARKUP, text)
36   - return text
37   -
38   - @property
39   - def text(self):
40   - block = u''.join(self)
41   -
42   - if self.html2text:
43   - block = self._html2text(block)
44   -
45   - if self.mark_links:
46   - block = self._mark_links(block)
47   -
48   - return block
49   -
50   - def __unicode__(self):
51   - return self.text
52   -
53   - def __str__(self):
54   - return self.text
55   -
56   -
57   -class EmailBlockParser(list):
58   - def __init__(self, email):
59   - self.email = email
60   - self.thread_emails = email.thread.message_set
61   -
62   - message = email.body
63   - block = EmailBlock()
64   -
65   - for line in message.split('\n'):
66   - if self.context_switch(line, block):
67   - self.append(block)
68   - new_block_context = not block.is_reply
69   - block = EmailBlock(is_reply=new_block_context)
70   -
71   - block.append(line + '\n')
72   -
73   - self.append(block)
74   -
75   - def context_switch(self, line, block):
76   - if line.strip(EXTENDED_PUNCTUATION):
77   - if self.is_reply(line):
78   - if not block.is_reply:
79   - return True
80   -
81   - else:
82   - if block.is_reply:
83   - return True
84   -
85   - return False
86   -
87   - def is_reply(self, line):
88   - stripped_line = line.strip()
89   - if stripped_line.startswith('>') or RE_REPLY_LINE.match(line):
90   - return True
91   -
92   - clean_line = RE_REPLY_LINE.subn('', stripped_line)[0]
93   - queryset = \
94   - self.thread_emails.filter(
95   - received_time__lt=self.email.received_time,
96   - body__contains=clean_line).order_by('-received_time')
97   -
98   - if queryset[:1]:
99   - return True
100   -
101   - return False
colab/super_archives/utils/email.py
... ... @@ -1,20 +0,0 @@
1   -
2   -from django.core import mail
3   -from django.conf import settings
4   -from django.template import Context, loader
5   -from django.utils.translation import ugettext as _
6   -
7   -
8   -def colab_send_email(subject, message, to):
9   - from_email = settings.COLAB_FROM_ADDRESS
10   - return mail.send_mail(subject, message, from_email, [to])
11   -
12   -
13   -def send_verification_email(to, user, validation_key, verification_url):
14   - subject = _('Please verify your email ') + u'{}'.format(to)
15   - msg_tmpl = \
16   - loader.get_template('superarchives/emails/email_verification.txt')
17   - message = msg_tmpl.render(Context({'to': to, 'user': user,
18   - 'verification_url': verification_url
19   - }))
20   - return colab_send_email(subject, message, to)
colab/super_archives/utils/etiquetador.py
... ... @@ -1,5 +0,0 @@
1   -
2   -from etiquetando import Etiquetador
3   -
4   -etiquetador = Etiquetador(word_min_size=3, max_tags=24,
5   - weight_range=(1, 4))
colab/super_archives/utils/url.py
... ... @@ -1,25 +0,0 @@
1   -# -*- coding: utf-8 -*-
2   -
3   -import urllib
4   -import urlparse
5   -
6   -
7   -def append_to_get(path, query=None, **kwargs):
8   - query_dict = dict(urlparse.parse_qsl(query))
9   - for key, value in kwargs.items():
10   - query_dict[key] = value
11   - return u'{}?{}'.format(path, urllib.urlencode(query_dict))
12   -
13   -
14   -def pop_from_get(path, query=None, **kwargs):
15   - query_dict = dict(urlparse.parse_qsl(query))
16   - for key, value in kwargs.items():
17   - if query_dict not in (key):
18   - continue
19   - if query_dict[key] == value:
20   - del query_dict[key]
21   - continue
22   - if value in query_dict[key]:
23   - aux = query_dict[key].split(value)
24   - query_dict[key] = u''.join(aux).strip()
25   - return u'{}?{}'.format(path, urllib.urlencode(query_dict))
colab/super_archives/views.py
... ... @@ -1,348 +0,0 @@
1   -# -*- coding: utf-8 -*-
2   -
3   -import smtplib
4   -import logging
5   -import urlparse
6   -
7   -import requests
8   -
9   -from django import http
10   -from django.conf import settings
11   -from django.contrib import messages
12   -from django.core.urlresolvers import reverse
13   -from django.db import IntegrityError
14   -from django.views.generic import View
15   -from django.utils.translation import ugettext as _
16   -from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
17   -from django.utils.decorators import method_decorator
18   -from django.contrib.auth.decorators import login_required
19   -from django.shortcuts import render, redirect, get_object_or_404
20   -
21   -from colab.accounts.utils import mailman
22   -from colab.accounts.models import User
23   -from .utils.email import send_verification_email
24   -from .models import (MailingList, Thread, EmailAddress,
25   - EmailAddressValidation, Message)
26   -
27   -
28   -class ThreadView(View):
29   - http_method_names = [u'get', u'post']
30   -
31   - def get(self, request, mailinglist, thread_token):
32   -
33   - thread = get_object_or_404(Thread, subject_token=thread_token,
34   - mailinglist__name=mailinglist)
35   -
36   - all_privates = []
37   - all_privates.extend(
38   - [mlist.get('listname')
39   - for mlist in mailman.all_lists()
40   - if mlist.get('archive_private')]
41   - )
42   -
43   - if all_privates.count(thread.mailinglist.name):
44   - if not request.user.is_authenticated():
45   - raise PermissionDenied
46   - else:
47   - user = User.objects.get(username=request.user)
48   - emails = user.emails.values_list('address', flat=True)
49   - lists_for_user = mailman.get_user_mailinglists(user)
50   - listnames_for_user = mailman.extract_listname_from_list(
51   - lists_for_user)
52   - if thread.mailinglist.name not in listnames_for_user:
53   - raise PermissionDenied
54   -
55   - thread.hit(request)
56   -
57   - try:
58   - first_message = thread.message_set.first()
59   - except ObjectDoesNotExist:
60   - raise http.Http404
61   -
62   - order_by = request.GET.get('order')
63   - if order_by == 'voted':
64   - msgs_query = Message.most_voted
65   - else:
66   - msgs_query = Message.objects
67   -
68   - msgs_query = msgs_query.filter(thread__subject_token=thread_token)
69   - msgs_query = msgs_query.filter(thread__mailinglist__name=mailinglist)
70   - emails = msgs_query.exclude(id=first_message.id)
71   -
72   - total_votes = first_message.votes_count()
73   - for email in emails:
74   - total_votes += email.votes_count()
75   -
76   - # Update relevance score
77   - thread.update_score()
78   -
79   - context = {
80   - 'first_msg': first_message,
81   - 'emails': [first_message] + list(emails),
82   - 'pagehits': thread.hits,
83   - 'total_votes': total_votes,
84   - 'thread': thread,
85   - }
86   -
87   - return render(request, 'message-thread.html', context)
88   -
89   - def post(self, request, mailinglist, thread_token):
90   - try:
91   - thread = Thread.objects.get(subject_token=thread_token,
92   - mailinglist__name=mailinglist)
93   - except Thread.DoesNotExist:
94   - raise http.Http404
95   -
96   - data = {
97   - 'in_reply_to': thread.message_set.last().message_id,
98   - 'email_from': request.user.email,
99   - 'name_from': request.user.get_full_name(),
100   - 'subject': thread.message_set.first().subject_clean,
101   - 'body': request.POST.get('emailbody', '').strip(),
102   - }
103   -
104   - url = urlparse.urljoin(settings.MAILMAN_API_URL,
105   - mailinglist + '/sendmail')
106   -
107   - error_msg = None
108   - try:
109   - resp = requests.post(url, data=data, timeout=2)
110   - except requests.exceptions.ConnectionError:
111   - resp = None
112   - error_msg = _('Error trying to connect to Mailman API')
113   - except requests.exceptions.Timeout:
114   - resp = None
115   - error_msg = _('Timeout trying to connect to Mailman API')
116   -
117   - if resp and resp.status_code == 200:
118   - messages.success(request, _(
119   - "Your message was sent to this topic. "
120   - "It may take some minutes before it's delivered by email "
121   - "to the group. Why don't you breath some fresh air in the "
122   - "meanwhile?"
123   - ))
124   - else:
125   - if not error_msg:
126   - if resp is not None:
127   - if resp.status_code == 400:
128   - error_msg = _('You cannot send an empty email')
129   - elif resp.status_code == 404:
130   - error_msg = _('Mailing list does not exist')
131   - else:
132   - error_msg = \
133   - _('Unknown error trying to connect to Mailman API')
134   -
135   - messages.error(request, error_msg)
136   -
137   - return self.get(request, mailinglist, thread_token)
138   -
139   -
140   -class ThreadDashboardView(View):
141   - http_method_names = ['get']
142   -
143   - def get(self, request):
144   - MAX = 6
145   - context = {}
146   -
147   - all_privates = {}
148   - private_mailinglist = MailingList.objects.filter(is_private=True)
149   - for mailinglist in private_mailinglist:
150   - all_privates[mailinglist.name] = True
151   -
152   - context['lists'] = []
153   -
154   - listnames_for_user = []
155   - if request.user.is_authenticated():
156   - user = User.objects.get(username=request.user)
157   - lists_for_user = mailman.get_user_mailinglists(user)
158   - listnames_for_user = mailman.extract_listname_from_list(
159   - lists_for_user)
160   -
161   - for list_ in MailingList.objects.order_by('name'):
162   - if list_.name not in all_privates\
163   - or list_.name in listnames_for_user:
164   - context['lists'].append((
165   - list_.name,
166   - mailman.get_list_description(list_.name),
167   - list_.thread_set.filter(spam=False).order_by(
168   - '-latest_message__received_time'
169   - )[:MAX],
170   - [t.latest_message for t in Thread.highest_score.filter(
171   - mailinglist__name=list_.name)[:MAX]],
172   - len(mailman.list_users(list_.name)),
173   - ))
174   -
175   - return render(request, 'superarchives/thread-dashboard.html', context)
176   -
177   -
178   -class EmailView(View):
179   -
180   - http_method_names = [u'head', u'get', u'post', u'delete', u'update']
181   -
182   - def get(self, request, key):
183   - """Validate an email with the given key"""
184   -
185   - try:
186   - email_val = EmailAddressValidation.objects.get(validation_key=key)
187   - except EmailAddressValidation.DoesNotExist:
188   - messages.error(request, _('The email address you are trying to '
189   - 'verify either has already been verified'
190   - ' or does not exist.'))
191   - return redirect('/')
192   -
193   - try:
194   - email = EmailAddress.objects.get(address=email_val.address)
195   - except EmailAddress.DoesNotExist:
196   - email = EmailAddress(address=email_val.address)
197   -
198   - if email.user and email.user.is_active:
199   - messages.error(request, _('The email address you are trying to '
200   - 'verify is already an active email '
201   - 'address.'))
202   - email_val.delete()
203   - return redirect('/')
204   -
205   - email.user = email_val.user
206   - email.save()
207   - email_val.delete()
208   -
209   - user = User.objects.get(username=email.user.username)
210   - user.is_active = True
211   - user.save()
212   -
213   - messages.success(request, _('Email address verified!'))
214   - return redirect('user_profile', username=email_val.user.username)
215   -
216   - @method_decorator(login_required)
217   - def post(self, request, key):
218   - """Create new email address that will wait for validation"""
219   -
220   - email = request.POST.get('email')
221   - user_id = request.POST.get('user')
222   - if not email:
223   - return http.HttpResponseBadRequest()
224   -
225   - try:
226   - EmailAddressValidation.objects.create(address=email,
227   - user_id=user_id)
228   - except IntegrityError:
229   - # 409 Conflict
230   - # duplicated entries
231   - # email exist and it's waiting for validation
232   - return http.HttpResponse(status=409)
233   -
234   - return http.HttpResponse(status=201)
235   -
236   - @method_decorator(login_required)
237   - def delete(self, request, key):
238   - """Remove an email address, validated or not."""
239   -
240   - request.DELETE = http.QueryDict(request.body)
241   - email_addr = request.DELETE.get('email')
242   - user_id = request.DELETE.get('user')
243   -
244   - if not email_addr:
245   - return http.HttpResponseBadRequest()
246   -
247   - try:
248   - email = EmailAddressValidation.objects.get(address=email_addr,
249   - user_id=user_id)
250   - except EmailAddressValidation.DoesNotExist:
251   - pass
252   - else:
253   - email.delete()
254   - return http.HttpResponse(status=204)
255   -
256   - try:
257   - email = EmailAddress.objects.get(address=email_addr,
258   - user_id=user_id)
259   - except EmailAddress.DoesNotExist:
260   - raise http.Http404
261   -
262   - email.user = None
263   - email.save()
264   - return http.HttpResponse(status=204)
265   -
266   - @method_decorator(login_required)
267   - def update(self, request, key):
268   - """Set an email address as primary address."""
269   -
270   - request.UPDATE = http.QueryDict(request.body)
271   -
272   - email_addr = request.UPDATE.get('email')
273   - user_id = request.UPDATE.get('user')
274   - if not email_addr:
275   - return http.HttpResponseBadRequest()
276   -
277   - try:
278   - email = EmailAddress.objects.get(address=email_addr,
279   - user_id=user_id)
280   - except EmailAddress.DoesNotExist:
281   - raise http.Http404
282   -
283   - email.user.email = email_addr
284   - email.user.save()
285   - return http.HttpResponse(status=204)
286   -
287   -
288   -class EmailValidationView(View):
289   -
290   - http_method_names = [u'post']
291   -
292   - def post(self, request):
293   - email_addr = request.POST.get('email')
294   - user_id = request.POST.get('user')
295   - try:
296   - email = EmailAddressValidation.objects.get(address=email_addr,
297   - user_id=user_id)
298   - except http.DoesNotExist:
299   - raise http.Http404
300   -
301   - try:
302   - location = reverse('archive_email_view',
303   - kwargs={'key': email.validation_key})
304   - verification_url = request.build_absolute_uri(location)
305   - send_verification_email(request, email_addr, email.user,
306   - email.validation_key, verification_url)
307   - except smtplib.SMTPException:
308   - logging.exception('Error sending validation email')
309   - return http.HttpResponseServerError()
310   -
311   - return http.HttpResponse(status=204)
312   -
313   -
314   -class VoteView(View):
315   -
316   - http_method_names = [u'get', u'put', u'delete', u'head']
317   -
318   - def put(self, request, msg_id):
319   - if not request.user.is_authenticated():
320   - return http.HttpResponseForbidden()
321   -
322   - try:
323   - Message.objects.get(id=msg_id).vote(request.user)
324   - except IntegrityError:
325   - # 409 Conflict
326   - # used for duplicated entries
327   - return http.HttpResponse(status=409)
328   -
329   - # 201 Created
330   - return http.HttpResponse(status=201)
331   -
332   - def get(self, request, msg_id):
333   - votes = Message.objects.get(id=msg_id).votes_count()
334   - return http.HttpResponse(votes, content_type='application/json')
335   -
336   - def delete(self, request, msg_id):
337   - if not request.user.is_authenticated():
338   - return http.HttpResponseForbidden()
339   -
340   - try:
341   - Message.objects.get(id=msg_id).unvote(request.user)
342   - except ObjectDoesNotExist:
343   - return http.HttpResponseGone()
344   -
345   - # 204 No Content
346   - # empty body, as per RFC2616.
347   - # object deleted
348   - return http.HttpResponse(status=204)