Commit ad6b59eeaa0f6b5e1da59fbc51cd11c366cc164c
1 parent
1b21cac6
Exists in
detach_super_archives
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
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
colab/super_archives/context_processors.py
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
colab/super_archives/templates/search/indexes/super_archives/thread_description.txt
colab/super_archives/templates/search/indexes/super_archives/thread_text.txt
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
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) |