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) |