##// END OF EJS Templates
Added 'ban' button to the moderator panel.
neko259 -
r156:cf2ed04f default
parent child Browse files
Show More
1 NO CONTENT: modified file, binary diff hidden
NO CONTENT: modified file, binary diff hidden
@@ -1,262 +1,266 b''
1 # SOME DESCRIPTIVE TITLE.
1 # SOME DESCRIPTIVE TITLE.
2 # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
2 # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 # This file is distributed under the same license as the PACKAGE package.
3 # This file is distributed under the same license as the PACKAGE package.
4 # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
4 # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5 #
5 #
6 msgid ""
6 msgid ""
7 msgstr ""
7 msgstr ""
8 "Project-Id-Version: PACKAGE VERSION\n"
8 "Project-Id-Version: PACKAGE VERSION\n"
9 "Report-Msgid-Bugs-To: \n"
9 "Report-Msgid-Bugs-To: \n"
10 "POT-Creation-Date: 2013-09-09 21:00+0300\n"
10 "POT-Creation-Date: 2013-09-09 21:52+0300\n"
11 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
11 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
12 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
12 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13 "Language-Team: LANGUAGE <LL@li.org>\n"
13 "Language-Team: LANGUAGE <LL@li.org>\n"
14 "Language: ru\n"
14 "Language: ru\n"
15 "MIME-Version: 1.0\n"
15 "MIME-Version: 1.0\n"
16 "Content-Type: text/plain; charset=UTF-8\n"
16 "Content-Type: text/plain; charset=UTF-8\n"
17 "Content-Transfer-Encoding: 8bit\n"
17 "Content-Transfer-Encoding: 8bit\n"
18 "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
18 "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
19 "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
19 "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
20
20
21 #: templates/boards/404.html:6
21 #: templates/boards/404.html:6
22 msgid "Not found"
22 msgid "Not found"
23 msgstr "Не найдено"
23 msgstr "Не найдено"
24
24
25 #: templates/boards/404.html:12
25 #: templates/boards/404.html:12
26 msgid "This page does not exist"
26 msgid "This page does not exist"
27 msgstr "Этой страницы не существует"
27 msgstr "Этой страницы не существует"
28
28
29 #: templates/boards/authors.html:6
29 #: templates/boards/authors.html:6
30 msgid "Authors"
30 msgid "Authors"
31 msgstr "Авторы"
31 msgstr "Авторы"
32
32
33 #: templates/boards/authors.html:24
33 #: templates/boards/authors.html:24
34 msgid "Distributed under the"
34 msgid "Distributed under the"
35 msgstr "Распространяется под"
35 msgstr "Распространяется под"
36
36
37 #: templates/boards/authors.html:26
37 #: templates/boards/authors.html:26
38 msgid "license"
38 msgid "license"
39 msgstr "лицензией"
39 msgstr "лицензией"
40
40
41 #: templates/boards/authors.html:28
41 #: templates/boards/authors.html:28
42 msgid "Repository"
42 msgid "Repository"
43 msgstr "Репозиторий"
43 msgstr "Репозиторий"
44
44
45 #: templates/boards/base.html:12
45 #: templates/boards/base.html:12
46 msgid "Feed"
46 msgid "Feed"
47 msgstr "Лента"
47 msgstr "Лента"
48
48
49 #: templates/boards/base.html:29
49 #: templates/boards/base.html:29
50 msgid "All threads"
50 msgid "All threads"
51 msgstr "Все темы"
51 msgstr "Все темы"
52
52
53 #: templates/boards/base.html:34
53 #: templates/boards/base.html:34
54 msgid "Tag management"
54 msgid "Tag management"
55 msgstr "Управление тегами"
55 msgstr "Управление тегами"
56
56
57 #: templates/boards/base.html:36
57 #: templates/boards/base.html:36
58 msgid "Settings"
58 msgid "Settings"
59 msgstr "Настройки"
59 msgstr "Настройки"
60
60
61 #: templates/boards/base.html:43 templates/boards/login.html:6
61 #: templates/boards/base.html:43 templates/boards/login.html:6
62 #: templates/boards/login.html.py:21
62 #: templates/boards/login.html.py:21
63 msgid "Login"
63 msgid "Login"
64 msgstr "Вход"
64 msgstr "Вход"
65
65
66 #: templates/boards/base.html:44
66 #: templates/boards/base.html:44
67 msgid "Up"
67 msgid "Up"
68 msgstr "Вверх"
68 msgstr "Вверх"
69
69
70 #: templates/boards/login.html:15
70 #: templates/boards/login.html:15
71 msgid "User ID"
71 msgid "User ID"
72 msgstr "ID пользователя"
72 msgstr "ID пользователя"
73
73
74 #: templates/boards/login.html:24
74 #: templates/boards/login.html:24
75 msgid "Insert your user id above"
75 msgid "Insert your user id above"
76 msgstr "Вставьте свой ID пользователя выше"
76 msgstr "Вставьте свой ID пользователя выше"
77
77
78 #: templates/boards/posting_general.html:18
78 #: templates/boards/posting_general.html:18
79 msgid "Tag: "
79 msgid "Tag: "
80 msgstr "Тег: "
80 msgstr "Тег: "
81
81
82 #: templates/boards/posting_general.html:35
82 #: templates/boards/posting_general.html:35
83 #: templates/boards/posting_general.html:89 templates/boards/thread.html:27
83 #: templates/boards/posting_general.html:89 templates/boards/thread.html:27
84 #: templates/boards/rss/post.html:5
84 #: templates/boards/rss/post.html:5
85 msgid "Post image"
85 msgid "Post image"
86 msgstr "Изображение сообщения"
86 msgstr "Изображение сообщения"
87
87
88 #: templates/boards/posting_general.html:48
88 #: templates/boards/posting_general.html:48
89 msgid "Reply"
89 msgid "Reply"
90 msgstr "Ответ"
90 msgstr "Ответ"
91
91
92 #: templates/boards/posting_general.html:54 templates/boards/thread.html:46
92 #: templates/boards/posting_general.html:54 templates/boards/thread.html:45
93 msgid "Delete"
93 msgid "Delete"
94 msgstr "Удалить"
94 msgstr "Удалить"
95
95
96 #: templates/boards/posting_general.html:63 templates/boards/thread.html:107
96 #: templates/boards/posting_general.html:63 templates/boards/thread.html:112
97 msgid "replies"
97 msgid "replies"
98 msgstr "ответов"
98 msgstr "ответов"
99
99
100 #: templates/boards/posting_general.html:64 templates/boards/thread.html:108
100 #: templates/boards/posting_general.html:64 templates/boards/thread.html:113
101 msgid "images"
101 msgid "images"
102 msgstr "изображений"
102 msgstr "изображений"
103
103
104 #: templates/boards/posting_general.html:66
104 #: templates/boards/posting_general.html:66
105 #: templates/boards/posting_general.html:139 templates/boards/tags.html:7
105 #: templates/boards/posting_general.html:139 templates/boards/tags.html:7
106 #: templates/boards/thread.html:56 templates/boards/rss/post.html:10
106 #: templates/boards/thread.html:58 templates/boards/rss/post.html:10
107 msgid "Tags"
107 msgid "Tags"
108 msgstr "Теги"
108 msgstr "Теги"
109
109
110 #: templates/boards/posting_general.html:115
110 #: templates/boards/posting_general.html:115
111 msgid "No threads exist. Create the first one!"
111 msgid "No threads exist. Create the first one!"
112 msgstr "Нет тем. Создайте первую!"
112 msgstr "Нет тем. Создайте первую!"
113
113
114 #: templates/boards/posting_general.html:121
114 #: templates/boards/posting_general.html:121
115 msgid "Create new thread"
115 msgid "Create new thread"
116 msgstr "Создать новую тему"
116 msgstr "Создать новую тему"
117
117
118 #: templates/boards/posting_general.html:124 templates/boards/thread.html:75
118 #: templates/boards/posting_general.html:124 templates/boards/thread.html:77
119 msgid "Title"
119 msgid "Title"
120 msgstr "Заголовок"
120 msgstr "Заголовок"
121
121
122 #: templates/boards/posting_general.html:129 templates/boards/thread.html:80
122 #: templates/boards/posting_general.html:129 templates/boards/thread.html:82
123 msgid "Text"
123 msgid "Text"
124 msgstr "Текст"
124 msgstr "Текст"
125
125
126 #: templates/boards/posting_general.html:134 templates/boards/thread.html:85
126 #: templates/boards/posting_general.html:134 templates/boards/thread.html:87
127 msgid "Image"
127 msgid "Image"
128 msgstr "Изображение"
128 msgstr "Изображение"
129
129
130 #: templates/boards/posting_general.html:149 templates/boards/thread.html:96
130 #: templates/boards/posting_general.html:152 templates/boards/thread.html:101
131 msgid "Post"
131 msgid "Post"
132 msgstr "Отправить"
132 msgstr "Отправить"
133
133
134 #: templates/boards/posting_general.html:151
134 #: templates/boards/posting_general.html:154
135 msgid "Tags must be delimited by spaces. Text or image is required."
135 msgid "Tags must be delimited by spaces. Text or image is required."
136 msgstr ""
136 msgstr ""
137 "Теги должны быть разделены пробелами. Текст или изображение обязательны."
137 "Теги должны быть разделены пробелами. Текст или изображение обязательны."
138
138
139 #: templates/boards/posting_general.html:154 templates/boards/thread.html:98
139 #: templates/boards/posting_general.html:157 templates/boards/thread.html:103
140 msgid "Text syntax"
140 msgid "Text syntax"
141 msgstr "Синтаксис текста"
141 msgstr "Синтаксис текста"
142
142
143 #: templates/boards/posting_general.html:164
143 #: templates/boards/posting_general.html:167
144 msgid "Pages:"
144 msgid "Pages:"
145 msgstr "Страницы: "
145 msgstr "Страницы: "
146
146
147 #: templates/boards/settings.html:12
147 #: templates/boards/settings.html:12
148 msgid "User:"
148 msgid "User:"
149 msgstr "Пользователь:"
149 msgstr "Пользователь:"
150
150
151 #: templates/boards/settings.html:14
151 #: templates/boards/settings.html:14
152 msgid "You are moderator."
152 msgid "You are moderator."
153 msgstr "Вы модератор."
153 msgstr "Вы модератор."
154
154
155 #: templates/boards/settings.html:20
155 #: templates/boards/settings.html:20
156 msgid "Theme"
156 msgid "Theme"
157 msgstr "Тема"
157 msgstr "Тема"
158
158
159 #: templates/boards/settings.html:36
159 #: templates/boards/settings.html:36
160 msgid "Save"
160 msgid "Save"
161 msgstr "Сохранить"
161 msgstr "Сохранить"
162
162
163 #: templates/boards/tags.html:17
163 #: templates/boards/tags.html:17
164 msgid "threads"
164 msgid "threads"
165 msgstr "тем"
165 msgstr "тем"
166
166
167 #: templates/boards/tags.html:20
167 #: templates/boards/tags.html:20
168 msgid "Remove"
168 msgid "Remove"
169 msgstr "Удалить"
169 msgstr "Удалить"
170
170
171 #: templates/boards/tags.html:23
171 #: templates/boards/tags.html:23
172 msgid "Add"
172 msgid "Add"
173 msgstr "Добавить"
173 msgstr "Добавить"
174
174
175 #: templates/boards/tags.html:28
175 #: templates/boards/tags.html:28
176 msgid "No tags found."
176 msgid "No tags found."
177 msgstr "Теги не найдены."
177 msgstr "Теги не найдены."
178
178
179 #: templates/boards/thread.html:72
179 #: templates/boards/thread.html:48
180 msgid "Ban IP"
181 msgstr "Заблокировать IP"
182
183 #: templates/boards/thread.html:74
180 msgid "Reply to thread"
184 msgid "Reply to thread"
181 msgstr "Ответить в тему"
185 msgstr "Ответить в тему"
182
186
183 #: templates/boards/thread.html:109
187 #: templates/boards/thread.html:114
184 msgid "Last update: "
188 msgid "Last update: "
185 msgstr "Последнее обновление: "
189 msgstr "Последнее обновление: "
186
190
187 #: templates/boards/staticpages/banned.html:6
191 #: templates/boards/staticpages/banned.html:6
188 #: templates/boards/staticpages/help.html:6
189 msgid "Banned"
192 msgid "Banned"
190 msgstr "Заблокирован"
193 msgstr "Заблокирован"
191
194
192 #: templates/boards/staticpages/banned.html:10
195 #: templates/boards/staticpages/banned.html:10
193 msgid "Your IP address has been banned. Contact the administrator"
196 msgid "Your IP address has been banned. Contact the administrator"
194 msgstr "Ваш IP адрес был заблокирован. Свяжитесь с администратором"
197 msgstr "Ваш IP адрес был заблокирован. Свяжитесь с администратором"
195
198
199 #: templates/boards/staticpages/help.html:6
196 #: templates/boards/staticpages/help.html:10
200 #: templates/boards/staticpages/help.html:10
197 msgid "Syntax"
201 msgid "Syntax"
198 msgstr "Синтаксис"
202 msgstr "Синтаксис"
199
203
200 #: templates/boards/staticpages/help.html:11
204 #: templates/boards/staticpages/help.html:11
201 msgid "2 line breaks for a new line."
205 msgid "2 line breaks for a new line."
202 msgstr "2 перевода строки создают новый абзац."
206 msgstr "2 перевода строки создают новый абзац."
203
207
204 #: templates/boards/staticpages/help.html:12
208 #: templates/boards/staticpages/help.html:12
205 msgid "Italic text"
209 msgid "Italic text"
206 msgstr "Курсивный текст"
210 msgstr "Курсивный текст"
207
211
208 #: templates/boards/staticpages/help.html:13
212 #: templates/boards/staticpages/help.html:13
209 msgid "Bold text"
213 msgid "Bold text"
210 msgstr "Полужирный текст"
214 msgstr "Полужирный текст"
211
215
212 #: templates/boards/staticpages/help.html:14
216 #: templates/boards/staticpages/help.html:14
213 msgid "Spoiler"
217 msgid "Spoiler"
214 msgstr "Спойлер"
218 msgstr "Спойлер"
215
219
216 #: templates/boards/staticpages/help.html:15
220 #: templates/boards/staticpages/help.html:15
217 msgid "Comment"
221 msgid "Comment"
218 msgstr "Комментарий"
222 msgstr "Комментарий"
219
223
220 #: templates/boards/staticpages/help.html:16
224 #: templates/boards/staticpages/help.html:16
221 msgid "Quote"
225 msgid "Quote"
222 msgstr "Цитата"
226 msgstr "Цитата"
223
227
224 #: templates/boards/staticpages/help.html:17
228 #: templates/boards/staticpages/help.html:17
225 msgid "Link to a post"
229 msgid "Link to a post"
226 msgstr "Ссылка на сообщение"
230 msgstr "Ссылка на сообщение"
227
231
228 #~ msgid "Basic markdown syntax."
232 #~ msgid "Basic markdown syntax."
229 #~ msgstr "Базовый синтаксис markdown."
233 #~ msgstr "Базовый синтаксис markdown."
230
234
231 #~ msgid "Example: "
235 #~ msgid "Example: "
232 #~ msgstr "Пример: "
236 #~ msgstr "Пример: "
233
237
234 #~ msgid "italic"
238 #~ msgid "italic"
235 #~ msgstr "курсив"
239 #~ msgstr "курсив"
236
240
237 #~ msgid "bold"
241 #~ msgid "bold"
238 #~ msgstr "полужирный"
242 #~ msgstr "полужирный"
239
243
240 #~ msgid "tags"
244 #~ msgid "tags"
241 #~ msgstr "тегов"
245 #~ msgstr "тегов"
242
246
243 #~ msgid "Get!"
247 #~ msgid "Get!"
244 #~ msgstr "Гет!"
248 #~ msgstr "Гет!"
245
249
246 #~ msgid "View"
250 #~ msgid "View"
247 #~ msgstr "Просмотр"
251 #~ msgstr "Просмотр"
248
252
249 #~ msgid "gets"
253 #~ msgid "gets"
250 #~ msgstr "гетов"
254 #~ msgstr "гетов"
251
255
252 #~ msgid "author"
256 #~ msgid "author"
253 #~ msgstr "автор"
257 #~ msgstr "автор"
254
258
255 #~ msgid "developer"
259 #~ msgid "developer"
256 #~ msgstr "разработчик"
260 #~ msgstr "разработчик"
257
261
258 #~ msgid "javascript developer"
262 #~ msgid "javascript developer"
259 #~ msgstr "разработчик javascript"
263 #~ msgstr "разработчик javascript"
260
264
261 #~ msgid "designer"
265 #~ msgid "designer"
262 #~ msgstr "дизайнер"
266 #~ msgstr "дизайнер"
@@ -1,329 +1,329 b''
1 import os
1 import os
2 from random import random
2 from random import random
3 import re
3 import re
4 import time
4 import time
5 import math
5 import math
6
6
7 from django.db import models
7 from django.db import models
8 from django.http import Http404
8 from django.http import Http404
9 from django.utils import timezone
9 from django.utils import timezone
10 from markupfield.fields import MarkupField
10 from markupfield.fields import MarkupField
11 from threading import Thread
11 from threading import Thread
12
12
13 from neboard import settings
13 from neboard import settings
14 import thumbs
14 import thumbs
15
15
16 IMAGE_THUMB_SIZE = (200, 150)
16 IMAGE_THUMB_SIZE = (200, 150)
17
17
18 TITLE_MAX_LENGTH = 50
18 TITLE_MAX_LENGTH = 50
19
19
20 DEFAULT_MARKUP_TYPE = 'markdown'
20 DEFAULT_MARKUP_TYPE = 'markdown'
21
21
22 NO_PARENT = -1
22 NO_PARENT = -1
23 NO_IP = '0.0.0.0'
23 NO_IP = '0.0.0.0'
24 UNKNOWN_UA = ''
24 UNKNOWN_UA = ''
25 ALL_PAGES = -1
25 ALL_PAGES = -1
26 OPENING_POST_POPULARITY_WEIGHT = 2
26 OPENING_POST_POPULARITY_WEIGHT = 2
27 IMAGES_DIRECTORY = 'images/'
27 IMAGES_DIRECTORY = 'images/'
28 FILE_EXTENSION_DELIMITER = '.'
28 FILE_EXTENSION_DELIMITER = '.'
29
29
30 RANK_ADMIN = 0
30 RANK_ADMIN = 0
31 RANK_MODERATOR = 10
31 RANK_MODERATOR = 10
32 RANK_USER = 100
32 RANK_USER = 100
33
33
34
34
35 class PostManager(models.Manager):
35 class PostManager(models.Manager):
36 def create_post(self, title, text, image=None, parent_id=NO_PARENT,
36 def create_post(self, title, text, image=None, parent_id=NO_PARENT,
37 ip=NO_IP, tags=None, user=None):
37 ip=NO_IP, tags=None, user=None):
38 post = self.create(title=title,
38 post = self.create(title=title,
39 text=text,
39 text=text,
40 pub_time=timezone.now(),
40 pub_time=timezone.now(),
41 parent=parent_id,
41 parent=parent_id,
42 image=image,
42 image=image,
43 poster_ip=ip,
43 poster_ip=ip,
44 poster_user_agent=UNKNOWN_UA,
44 poster_user_agent=UNKNOWN_UA,
45 last_edit_time=timezone.now(),
45 last_edit_time=timezone.now(),
46 user=user)
46 user=user)
47
47
48 if tags:
48 if tags:
49 map(post.tags.add, tags)
49 map(post.tags.add, tags)
50
50
51 if parent_id != NO_PARENT:
51 if parent_id != NO_PARENT:
52 self._bump_thread(parent_id)
52 self._bump_thread(parent_id)
53 else:
53 else:
54 self._delete_old_threads()
54 self._delete_old_threads()
55
55
56 return post
56 return post
57
57
58 def delete_post(self, post):
58 def delete_post(self, post):
59 children = self.filter(parent=post.id)
59 children = self.filter(parent=post.id)
60
60
61 map(self.delete_post, children)
61 map(self.delete_post, children)
62 post.delete()
62 post.delete()
63
63
64 def delete_posts_by_ip(self, ip):
64 def delete_posts_by_ip(self, ip):
65 posts = self.filter(poster_ip=ip)
65 posts = self.filter(poster_ip=ip)
66 map(self.delete_post, posts)
66 map(self.delete_post, posts)
67
67
68 def get_threads(self, tag=None, page=ALL_PAGES,
68 def get_threads(self, tag=None, page=ALL_PAGES,
69 order_by='-last_edit_time'):
69 order_by='-last_edit_time'):
70 if tag:
70 if tag:
71 threads = self.filter(parent=NO_PARENT, tags=tag)
71 threads = self.filter(parent=NO_PARENT, tags=tag)
72
72
73 # TODO Throw error 404 if no threads for tag found?
73 # TODO Throw error 404 if no threads for tag found?
74 else:
74 else:
75 threads = self.filter(parent=NO_PARENT)
75 threads = self.filter(parent=NO_PARENT)
76
76
77 threads = threads.order_by(order_by)
77 threads = threads.order_by(order_by)
78
78
79 if page != ALL_PAGES:
79 if page != ALL_PAGES:
80 thread_count = len(threads)
80 thread_count = len(threads)
81
81
82 if page < self.get_thread_page_count(tag=tag):
82 if page < self.get_thread_page_count(tag=tag):
83 start_thread = page * settings.THREADS_PER_PAGE
83 start_thread = page * settings.THREADS_PER_PAGE
84 end_thread = min(start_thread + settings.THREADS_PER_PAGE,
84 end_thread = min(start_thread + settings.THREADS_PER_PAGE,
85 thread_count)
85 thread_count)
86 threads = threads[start_thread:end_thread]
86 threads = threads[start_thread:end_thread]
87
87
88 return threads
88 return threads
89
89
90 def get_thread(self, opening_post_id):
90 def get_thread(self, opening_post_id):
91 try:
91 try:
92 opening_post = self.get(id=opening_post_id, parent=NO_PARENT)
92 opening_post = self.get(id=opening_post_id, parent=NO_PARENT)
93 except Post.DoesNotExist:
93 except Post.DoesNotExist:
94 raise Http404
94 raise Http404
95
95
96 if opening_post.parent == NO_PARENT:
96 if opening_post.parent == NO_PARENT:
97 replies = self.filter(parent=opening_post_id)
97 replies = self.filter(parent=opening_post_id)
98
98
99 thread = [opening_post]
99 thread = [opening_post]
100 thread.extend(replies)
100 thread.extend(replies)
101
101
102 return thread
102 return thread
103
103
104 def exists(self, post_id):
104 def exists(self, post_id):
105 posts = self.filter(id=post_id)
105 posts = self.filter(id=post_id)
106
106
107 return posts.count() > 0
107 return posts.count() > 0
108
108
109 def get_thread_page_count(self, tag=None):
109 def get_thread_page_count(self, tag=None):
110 if tag:
110 if tag:
111 threads = self.filter(parent=NO_PARENT, tags=tag)
111 threads = self.filter(parent=NO_PARENT, tags=tag)
112 else:
112 else:
113 threads = self.filter(parent=NO_PARENT)
113 threads = self.filter(parent=NO_PARENT)
114
114
115 return int(math.ceil(threads.count() / float(
115 return int(math.ceil(threads.count() / float(
116 settings.THREADS_PER_PAGE)))
116 settings.THREADS_PER_PAGE)))
117
117
118 def _delete_old_threads(self):
118 def _delete_old_threads(self):
119 """
119 """
120 Preserves maximum thread count. If there are too many threads,
120 Preserves maximum thread count. If there are too many threads,
121 delete the old ones.
121 delete the old ones.
122 """
122 """
123
123
124 # TODO Move old threads to the archive instead of deleting them.
124 # TODO Move old threads to the archive instead of deleting them.
125 # Maybe make some 'old' field in the model to indicate the thread
125 # Maybe make some 'old' field in the model to indicate the thread
126 # must not be shown and be able for replying.
126 # must not be shown and be able for replying.
127
127
128 threads = self.get_threads()
128 threads = self.get_threads()
129 thread_count = len(threads)
129 thread_count = len(threads)
130
130
131 if thread_count > settings.MAX_THREAD_COUNT:
131 if thread_count > settings.MAX_THREAD_COUNT:
132 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
132 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
133 old_threads = threads[thread_count - num_threads_to_delete:]
133 old_threads = threads[thread_count - num_threads_to_delete:]
134
134
135 map(self.delete_post, old_threads)
135 map(self.delete_post, old_threads)
136
136
137 def _bump_thread(self, thread_id):
137 def _bump_thread(self, thread_id):
138 thread = self.get(id=thread_id)
138 thread = self.get(id=thread_id)
139
139
140 if thread.can_bump():
140 if thread.can_bump():
141 thread.last_edit_time = timezone.now()
141 thread.last_edit_time = timezone.now()
142 thread.save()
142 thread.save()
143
143
144
144
145 class TagManager(models.Manager):
145 class TagManager(models.Manager):
146 def get_not_empty_tags(self):
146 def get_not_empty_tags(self):
147 all_tags = self.all().order_by('name')
147 all_tags = self.all().order_by('name')
148 tags = []
148 tags = []
149 for tag in all_tags:
149 for tag in all_tags:
150 if not tag.is_empty():
150 if not tag.is_empty():
151 tags.append(tag)
151 tags.append(tag)
152
152
153 return tags
153 return tags
154
154
155 def get_popular_tags(self):
155 def get_popular_tags(self):
156 all_tags = self.get_not_empty_tags()
156 all_tags = self.get_not_empty_tags()
157
157
158 sorted_tags = sorted(all_tags, key=lambda tag: tag.get_popularity(),
158 sorted_tags = sorted(all_tags, key=lambda tag: tag.get_popularity(),
159 reverse=True)
159 reverse=True)
160
160
161 return sorted_tags[:settings.POPULAR_TAGS]
161 return sorted_tags[:settings.POPULAR_TAGS]
162
162
163
163
164 class Tag(models.Model):
164 class Tag(models.Model):
165 """
165 """
166 A tag is a text node assigned to the post. The tag serves as a board
166 A tag is a text node assigned to the post. The tag serves as a board
167 section. There can be multiple tags for each message
167 section. There can be multiple tags for each message
168 """
168 """
169
169
170 objects = TagManager()
170 objects = TagManager()
171
171
172 name = models.CharField(max_length=100)
172 name = models.CharField(max_length=100)
173
173
174 def __unicode__(self):
174 def __unicode__(self):
175 return self.name
175 return self.name
176
176
177 def is_empty(self):
177 def is_empty(self):
178 return self.get_post_count() == 0
178 return self.get_post_count() == 0
179
179
180 def get_post_count(self):
180 def get_post_count(self):
181 posts_with_tag = Post.objects.get_threads(tag=self)
181 posts_with_tag = Post.objects.get_threads(tag=self)
182 return posts_with_tag.count()
182 return posts_with_tag.count()
183
183
184 def get_popularity(self):
184 def get_popularity(self):
185 posts_with_tag = Post.objects.get_threads(tag=self)
185 posts_with_tag = Post.objects.get_threads(tag=self)
186 reply_count = 0
186 reply_count = 0
187 for post in posts_with_tag:
187 for post in posts_with_tag:
188 reply_count += post.get_reply_count()
188 reply_count += post.get_reply_count()
189 reply_count += OPENING_POST_POPULARITY_WEIGHT
189 reply_count += OPENING_POST_POPULARITY_WEIGHT
190
190
191 return reply_count
191 return reply_count
192
192
193
193
194 class Post(models.Model):
194 class Post(models.Model):
195 """A post is a message."""
195 """A post is a message."""
196
196
197 objects = PostManager()
197 objects = PostManager()
198
198
199 def _update_image_filename(self, filename):
199 def _update_image_filename(self, filename):
200 """Get unique image filename"""
200 """Get unique image filename"""
201
201
202 path = IMAGES_DIRECTORY
202 path = IMAGES_DIRECTORY
203 new_name = str(int(time.mktime(time.gmtime())))
203 new_name = str(int(time.mktime(time.gmtime())))
204 new_name += str(int(random() * 1000))
204 new_name += str(int(random() * 1000))
205 new_name += FILE_EXTENSION_DELIMITER
205 new_name += FILE_EXTENSION_DELIMITER
206 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
206 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
207
207
208 return os.path.join(path, new_name)
208 return os.path.join(path, new_name)
209
209
210 title = models.CharField(max_length=TITLE_MAX_LENGTH)
210 title = models.CharField(max_length=TITLE_MAX_LENGTH)
211 pub_time = models.DateTimeField()
211 pub_time = models.DateTimeField()
212 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
212 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
213 escape_html=False)
213 escape_html=False)
214
214
215 image_width = models.IntegerField(default=0)
215 image_width = models.IntegerField(default=0)
216 image_height = models.IntegerField(default=0)
216 image_height = models.IntegerField(default=0)
217
217
218 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
218 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
219 blank=True, sizes=(IMAGE_THUMB_SIZE,),
219 blank=True, sizes=(IMAGE_THUMB_SIZE,),
220 width_field='image_width',
220 width_field='image_width',
221 height_field='image_height')
221 height_field='image_height')
222
222
223 poster_ip = models.GenericIPAddressField()
223 poster_ip = models.GenericIPAddressField()
224 poster_user_agent = models.TextField()
224 poster_user_agent = models.TextField()
225 parent = models.BigIntegerField()
225 parent = models.BigIntegerField()
226 tags = models.ManyToManyField(Tag)
226 tags = models.ManyToManyField(Tag)
227 last_edit_time = models.DateTimeField()
227 last_edit_time = models.DateTimeField()
228 user = models.ForeignKey('User', null=True, default=None)
228 user = models.ForeignKey('User', null=True, default=None)
229
229
230 def __unicode__(self):
230 def __unicode__(self):
231 return '#' + str(self.id) + ' ' + self.title + ' (' + \
231 return '#' + str(self.id) + ' ' + self.title + ' (' + \
232 self.text.raw[:50] + ')'
232 self.text.raw[:50] + ')'
233
233
234 def get_title(self):
234 def get_title(self):
235 title = self.title
235 title = self.title
236 if len(title) == 0:
236 if len(title) == 0:
237 title = self.text.raw[:20]
237 title = self.text.raw[:20]
238
238
239 return title
239 return title
240
240
241 def _get_replies(self):
241 def _get_replies(self):
242 return Post.objects.filter(parent=self.id)
242 return Post.objects.filter(parent=self.id)
243
243
244 def get_reply_count(self):
244 def get_reply_count(self):
245 return self._get_replies().count()
245 return self._get_replies().count()
246
246
247 def get_images_count(self):
247 def get_images_count(self):
248 images_count = 1 if self.image else 0
248 images_count = 1 if self.image else 0
249 for reply in self._get_replies():
249 for reply in self._get_replies():
250 if reply.image:
250 if reply.image:
251 images_count += 1
251 images_count += 1
252
252
253 return images_count
253 return images_count
254
254
255 def can_bump(self):
255 def can_bump(self):
256 """Check if the thread can be bumped by replying"""
256 """Check if the thread can be bumped by replying"""
257
257
258 replies_count = len(Post.objects.get_thread(self.id))
258 replies_count = len(Post.objects.get_thread(self.id))
259
259
260 return replies_count <= settings.MAX_POSTS_PER_THREAD
260 return replies_count <= settings.MAX_POSTS_PER_THREAD
261
261
262 def get_last_replies(self):
262 def get_last_replies(self):
263 if settings.LAST_REPLIES_COUNT > 0:
263 if settings.LAST_REPLIES_COUNT > 0:
264 reply_count = self.get_reply_count()
264 reply_count = self.get_reply_count()
265
265
266 if reply_count > 0:
266 if reply_count > 0:
267 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
267 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
268 reply_count)
268 reply_count)
269 last_replies = self._get_replies()[reply_count
269 last_replies = self._get_replies()[reply_count
270 - reply_count_to_show:]
270 - reply_count_to_show:]
271
271
272 return last_replies
272 return last_replies
273
273
274
274
275 class User(models.Model):
275 class User(models.Model):
276
276
277 user_id = models.CharField(max_length=50)
277 user_id = models.CharField(max_length=50)
278 rank = models.IntegerField()
278 rank = models.IntegerField()
279
279
280 registration_time = models.DateTimeField()
280 registration_time = models.DateTimeField()
281 last_access_time = models.DateTimeField()
281 last_access_time = models.DateTimeField()
282
282
283 fav_tags = models.ManyToManyField(Tag, null=True, blank=True)
283 fav_tags = models.ManyToManyField(Tag, null=True, blank=True)
284 fav_threads = models.ManyToManyField(Post, related_name='+', null=True,
284 fav_threads = models.ManyToManyField(Post, related_name='+', null=True,
285 blank=True)
285 blank=True)
286
286
287 def save_setting(self, name, value):
287 def save_setting(self, name, value):
288 setting, created = Setting.objects.get_or_create(name=name, user=self)
288 setting, created = Setting.objects.get_or_create(name=name, user=self)
289 setting.value = value
289 setting.value = value
290 setting.save()
290 setting.save()
291
291
292 return setting
292 return setting
293
293
294 def get_setting(self, name):
294 def get_setting(self, name):
295 settings = Setting.objects.filter(name=name, user=self)
295 settings = Setting.objects.filter(name=name, user=self)
296 if len(settings) > 0:
296 if len(settings) > 0:
297 setting = settings[0]
297 setting = settings[0]
298 else:
298 else:
299 setting = None
299 setting = None
300
300
301 if setting:
301 if setting:
302 setting_value = setting.value
302 setting_value = setting.value
303 else:
303 else:
304 setting_value = None
304 setting_value = None
305
305
306 return setting_value
306 return setting_value
307
307
308 def is_moderator(self):
308 def is_moderator(self):
309 return RANK_MODERATOR >= self.rank
309 return RANK_MODERATOR >= self.rank
310
310
311 def get_sorted_fav_tags(self):
311 def get_sorted_fav_tags(self):
312 return self.fav_tags.order_by('name')
312 return self.fav_tags.order_by('name')
313
313
314 def __unicode__(self):
314 def __unicode__(self):
315 return self.user_id + '(' + self.rank + ')'
315 return self.user_id + '(' + str(self.rank) + ')'
316
316
317
317
318 class Setting(models.Model):
318 class Setting(models.Model):
319
319
320 name = models.CharField(max_length=50)
320 name = models.CharField(max_length=50)
321 value = models.CharField(max_length=50)
321 value = models.CharField(max_length=50)
322 user = models.ForeignKey(User)
322 user = models.ForeignKey(User)
323
323
324
324
325 class Ban(models.Model):
325 class Ban(models.Model):
326 ip = models.GenericIPAddressField()
326 ip = models.GenericIPAddressField()
327
327
328 def __unicode__(self):
328 def __unicode__(self):
329 return self.ip
329 return self.ip
@@ -1,116 +1,118 b''
1 {% extends "boards/base.html" %}
1 {% extends "boards/base.html" %}
2
2
3 {% load i18n %}
3 {% load i18n %}
4 {% load markup %}
4 {% load markup %}
5
5
6 {% block head %}
6 {% block head %}
7 <title>Neboard - {{ posts.0.get_title }}</title>
7 <title>Neboard - {{ posts.0.get_title }}</title>
8 <script src="{{ STATIC_URL }}js/thread.js"></script>
8 <script src="{{ STATIC_URL }}js/thread.js"></script>
9 {% endblock %}
9 {% endblock %}
10
10
11 {% block content %}
11 {% block content %}
12
12
13 {% if posts %}
13 {% if posts %}
14 <div id="posts">
14 <div id="posts">
15 {% for post in posts %}
15 {% for post in posts %}
16 {% if posts.0.can_bump %}
16 {% if posts.0.can_bump %}
17 <div class="post" id="{{ post.id }}">
17 <div class="post" id="{{ post.id }}">
18 {% else %}
18 {% else %}
19 <div class="post dead_post" id="{{ post.id }}">
19 <div class="post dead_post" id="{{ post.id }}">
20 {% endif %}
20 {% endif %}
21 {% if post.image %}
21 {% if post.image %}
22 <div class="image">
22 <div class="image">
23 <a
23 <a
24 class="fancy"
24 class="fancy"
25 href="{{ post.image.url }}"><img
25 href="{{ post.image.url }}"><img
26 src="{{ post.image.url_200x150 }}"
26 src="{{ post.image.url_200x150 }}"
27 alt="{% trans 'Post image' %}"v
27 alt="{% trans 'Post image' %}"v
28 data-width="{{ post.image_width }}"
28 data-width="{{ post.image_width }}"
29 data-height="{{ post.image_height }}"/>
29 data-height="{{ post.image_height }}"/>
30 </a>
30 </a>
31 </div>
31 </div>
32 {% endif %}
32 {% endif %}
33 <div class="message">
33 <div class="message">
34 <div class="post-info">
34 <div class="post-info">
35 <span class="title">{{ post.title }}</span>
35 <span class="title">{{ post.title }}</span>
36 <a class="post_id" href="#{{ post.id }}">
36 <a class="post_id" href="#{{ post.id }}">
37 (#{{ post.id }})</a>
37 (#{{ post.id }})</a>
38 [{{ post.pub_time }}]
38 [{{ post.pub_time }}]
39 [<a href="#" onclick="javascript:addQuickReply('{{ post.id }}')
39 [<a href="#" onclick="javascript:addQuickReply('{{ post.id }}')
40 ; return false;">&gt;&gt;</a>]
40 ; return false;">&gt;&gt;</a>]
41
41
42 {% if user.is_moderator %}
42 {% if user.is_moderator %}
43 <span class="moderator_info">
43 <span class="moderator_info">
44 ({{ post.poster_ip }})
45 [<a href="{% url 'delete' post_id=post.id %}"
44 [<a href="{% url 'delete' post_id=post.id %}"
46 >{% trans 'Delete' %}</a>]
45 >{% trans 'Delete' %}</a>]
46 ({{ post.poster_ip }})
47 [<a href="{% url 'ban' post_id=post.id %}?next={{ request.path }}"
48 >{% trans 'Ban IP' %}</a>]
47 </span>
49 </span>
48 {% endif %}
50 {% endif %}
49 </div>
51 </div>
50 {% autoescape off %}
52 {% autoescape off %}
51 {{ post.text.rendered }}
53 {{ post.text.rendered }}
52 {% endautoescape %}
54 {% endautoescape %}
53 </div>
55 </div>
54 {% if post.tags.all %}
56 {% if post.tags.all %}
55 <div class="metadata">
57 <div class="metadata">
56 <span class="tags">{% trans 'Tags' %}:
58 <span class="tags">{% trans 'Tags' %}:
57 {% for tag in post.tags.all %}
59 {% for tag in post.tags.all %}
58 <a class="tag" href="{% url 'tag' tag.name %}">
60 <a class="tag" href="{% url 'tag' tag.name %}">
59 {{ tag.name }}</a>
61 {{ tag.name }}</a>
60 {% endfor %}
62 {% endfor %}
61 </span>
63 </span>
62 </div>
64 </div>
63 {% endif %}
65 {% endif %}
64 </div>
66 </div>
65 {% endfor %}
67 {% endfor %}
66 </div>
68 </div>
67 {% endif %}
69 {% endif %}
68
70
69 <form id="form" enctype="multipart/form-data" method="post"
71 <form id="form" enctype="multipart/form-data" method="post"
70 >{% csrf_token %}
72 >{% csrf_token %}
71 <div class="post-form-w">
73 <div class="post-form-w">
72 <div class="form-title">{% trans "Reply to thread" %} #{{ posts.0.id }}</div>
74 <div class="form-title">{% trans "Reply to thread" %} #{{ posts.0.id }}</div>
73 <div class="post-form">
75 <div class="post-form">
74 <div class="form-row">
76 <div class="form-row">
75 <div class="form-label">{% trans 'Title' %}</div>
77 <div class="form-label">{% trans 'Title' %}</div>
76 <div class="form-input">{{ form.title }}</div>
78 <div class="form-input">{{ form.title }}</div>
77 <div class="form-errors">{{ form.title.errors }}</div>
79 <div class="form-errors">{{ form.title.errors }}</div>
78 </div>
80 </div>
79 <div class="form-row">
81 <div class="form-row">
80 <div class="form-label">{% trans 'Text' %}</div>
82 <div class="form-label">{% trans 'Text' %}</div>
81 <div class="form-input">{{ form.text }}</div>
83 <div class="form-input">{{ form.text }}</div>
82 <div class="form-errors">{{ form.text.errors }}</div>
84 <div class="form-errors">{{ form.text.errors }}</div>
83 </div>
85 </div>
84 <div class="form-row">
86 <div class="form-row">
85 <div class="form-label">{% trans 'Image' %}</div>
87 <div class="form-label">{% trans 'Image' %}</div>
86 <div class="form-input">{{ form.image }}</div>
88 <div class="form-input">{{ form.image }}</div>
87 <div class="form-errors">{{ form.image.errors }}</div>
89 <div class="form-errors">{{ form.image.errors }}</div>
88 </div>
90 </div>
89 <div class="form-row">
91 <div class="form-row">
90 {{ form.captcha }}
92 {{ form.captcha }}
91 <div class="form-errors">{{ form.captcha.errors }}</div>
93 <div class="form-errors">{{ form.captcha.errors }}</div>
92 </div>
94 </div>
93 <div class="form-row">
95 <div class="form-row">
94 <div class="form-errors">{{ form.other.errors }}</div>
96 <div class="form-errors">{{ form.other.errors }}</div>
95 </div>
97 </div>
96 </div>
98 </div>
97
99
98 <div class="form-submit"><input type="submit"
100 <div class="form-submit"><input type="submit"
99 value="{% trans "Post" %}"/></div>
101 value="{% trans "Post" %}"/></div>
100 <div><a href="{% url "staticpage" name="help" %}">
102 <div><a href="{% url "staticpage" name="help" %}">
101 {% trans 'Text syntax' %}</a></div>
103 {% trans 'Text syntax' %}</a></div>
102 </div>
104 </div>
103 </form>
105 </form>
104
106
105 {% endblock %}
107 {% endblock %}
106
108
107 {% block metapanel %}
109 {% block metapanel %}
108
110
109 <span class="metapanel">
111 <span class="metapanel">
110 {{ posts.0.get_reply_count }} {% trans 'replies' %},
112 {{ posts.0.get_reply_count }} {% trans 'replies' %},
111 {{ posts.0.get_images_count }} {% trans 'images' %}.
113 {{ posts.0.get_images_count }} {% trans 'images' %}.
112 {% trans 'Last update: ' %}{{ posts.0.last_edit_time }}
114 {% trans 'Last update: ' %}{{ posts.0.last_edit_time }}
113 [<a href="rss/">RSS</a>]
115 [<a href="rss/">RSS</a>]
114 </span>
116 </span>
115
117
116 {% endblock %}
118 {% endblock %}
@@ -1,51 +1,53 b''
1 from django.conf.urls import patterns, url, include
1 from django.conf.urls import patterns, url, include
2 from boards import views
2 from boards import views
3 from boards.rss import AllThreadsFeed, TagThreadsFeed, ThreadPostsFeed
3 from boards.rss import AllThreadsFeed, TagThreadsFeed, ThreadPostsFeed
4
4
5 js_info_dict = {
5 js_info_dict = {
6 'packages': ('boards',),
6 'packages': ('boards',),
7 }
7 }
8
8
9 urlpatterns = patterns('',
9 urlpatterns = patterns('',
10
10
11 # /boards/
11 # /boards/
12 url(r'^$', views.index, name='index'),
12 url(r'^$', views.index, name='index'),
13 # /boards/page/
13 # /boards/page/
14 url(r'^page/(?P<page>\w+)/$', views.index, name='index'),
14 url(r'^page/(?P<page>\w+)/$', views.index, name='index'),
15
15
16 # login page
16 # login page
17 url(r'^login/$', views.login, name='login'),
17 url(r'^login/$', views.login, name='login'),
18
18
19 # /boards/tag/tag_name/
19 # /boards/tag/tag_name/
20 url(r'^tag/(?P<tag_name>\w+)/$', views.tag, name='tag'),
20 url(r'^tag/(?P<tag_name>\w+)/$', views.tag, name='tag'),
21 # /boards/tag/tag_id/page/
21 # /boards/tag/tag_id/page/
22 url(r'^tag/(?P<tag_name>\w+)/page/(?P<page>\w+)/$', views.tag, name='tag'),
22 url(r'^tag/(?P<tag_name>\w+)/page/(?P<page>\w+)/$', views.tag, name='tag'),
23
23
24 # /boards/tag/tag_name/unsubscribe/
24 # /boards/tag/tag_name/unsubscribe/
25 url(r'^tag/(?P<tag_name>\w+)/subscribe/$', views.tag_subscribe,
25 url(r'^tag/(?P<tag_name>\w+)/subscribe/$', views.tag_subscribe,
26 name='tag_subscribe'),
26 name='tag_subscribe'),
27 # /boards/tag/tag_name/unsubscribe/
27 # /boards/tag/tag_name/unsubscribe/
28 url(r'^tag/(?P<tag_name>\w+)/unsubscribe/$', views.tag_unsubscribe,
28 url(r'^tag/(?P<tag_name>\w+)/unsubscribe/$', views.tag_unsubscribe,
29 name='tag_unsubscribe'),
29 name='tag_unsubscribe'),
30
30
31 # /boards/thread/
31 # /boards/thread/
32 url(r'^thread/(?P<post_id>\w+)/$', views.thread, name='thread'),
32 url(r'^thread/(?P<post_id>\w+)/$', views.thread, name='thread'),
33 # /boards/theme/theme_name/
33 # /boards/theme/theme_name/
34 url(r'^settings/$', views.settings, name='settings'),
34 url(r'^settings/$', views.settings, name='settings'),
35 url(r'^tags/$', views.all_tags, name='tags'),
35 url(r'^tags/$', views.all_tags, name='tags'),
36 url(r'^captcha/', include('captcha.urls')),
36 url(r'^captcha/', include('captcha.urls')),
37 url(r'^jump/(?P<post_id>\w+)/$', views.jump_to_post, name='jumper'),
37 url(r'^jump/(?P<post_id>\w+)/$', views.jump_to_post, name='jumper'),
38 url(r'^authors/$', views.authors, name='authors'),
38 url(r'^authors/$', views.authors, name='authors'),
39 url(r'^delete/(?P<post_id>\w+)/$', views.delete, name='delete'),
39 url(r'^delete/(?P<post_id>\w+)/$', views.delete, name='delete'),
40 url(r'^ban/(?P<post_id>\w+)/$', views.ban, name='ban'),
41
40 url(r'^banned/$', views.you_are_banned, name='banned'),
42 url(r'^banned/$', views.you_are_banned, name='banned'),
41 url(r'^staticpage/(?P<name>\w+)/$', views.static_page, name='staticpage'),
43 url(r'^staticpage/(?P<name>\w+)/$', views.static_page, name='staticpage'),
42
44
43 # RSS feeds
45 # RSS feeds
44 url(r'^rss/$', AllThreadsFeed()),
46 url(r'^rss/$', AllThreadsFeed()),
45 url(r'^page/(?P<page>\w+)/rss/$', AllThreadsFeed()),
47 url(r'^page/(?P<page>\w+)/rss/$', AllThreadsFeed()),
46 url(r'^tag/(?P<tag_name>\w+)/rss/$', TagThreadsFeed()),
48 url(r'^tag/(?P<tag_name>\w+)/rss/$', TagThreadsFeed()),
47 url(r'^tag/(?P<tag_name>\w+)/page/(?P<page>\w+)/rss/$', TagThreadsFeed()),
49 url(r'^tag/(?P<tag_name>\w+)/page/(?P<page>\w+)/rss/$', TagThreadsFeed()),
48 url(r'^thread/(?P<post_id>\w+)/rss/$', ThreadPostsFeed()),
50 url(r'^thread/(?P<post_id>\w+)/rss/$', ThreadPostsFeed()),
49
51
50 url(r'^jsi18n/$', 'django.views.i18n.javascript_catalog', js_info_dict),
52 url(r'^jsi18n/$', 'django.views.i18n.javascript_catalog', js_info_dict),
51 )
53 )
@@ -1,337 +1,351 b''
1 import hashlib
1 import hashlib
2 from django.core.urlresolvers import reverse
2 from django.core.urlresolvers import reverse
3 from django.http import HttpResponseRedirect
3 from django.http import HttpResponseRedirect
4 from django.template import RequestContext
4 from django.template import RequestContext
5 from django.shortcuts import render, redirect, get_object_or_404
5 from django.shortcuts import render, redirect, get_object_or_404
6 from django.utils import timezone
6 from django.utils import timezone
7
7
8 from boards import forms
8 from boards import forms
9 import boards
9 import boards
10 from boards import utils
10 from boards import utils
11 from boards.forms import ThreadForm, PostForm, SettingsForm, PlainErrorList, \
11 from boards.forms import ThreadForm, PostForm, SettingsForm, PlainErrorList, \
12 ThreadCaptchaForm, PostCaptchaForm, LoginForm
12 ThreadCaptchaForm, PostCaptchaForm, LoginForm
13
13
14 from boards.models import Post, Tag, Ban, User, RANK_USER, NO_PARENT
14 from boards.models import Post, Tag, Ban, User, RANK_USER, NO_PARENT
15 from boards import authors
15 from boards import authors
16 import neboard
16 import neboard
17
17
18
18
19 def index(request, page=0):
19 def index(request, page=0):
20 context = _init_default_context(request)
20 context = _init_default_context(request)
21
21
22 if utils.need_include_captcha(request):
22 if utils.need_include_captcha(request):
23 threadFormClass = ThreadCaptchaForm
23 threadFormClass = ThreadCaptchaForm
24 kwargs = {'request': request}
24 kwargs = {'request': request}
25 else:
25 else:
26 threadFormClass = ThreadForm
26 threadFormClass = ThreadForm
27 kwargs = {}
27 kwargs = {}
28
28
29 if request.method == 'POST':
29 if request.method == 'POST':
30 form = threadFormClass(request.POST, request.FILES,
30 form = threadFormClass(request.POST, request.FILES,
31 error_class=PlainErrorList, **kwargs)
31 error_class=PlainErrorList, **kwargs)
32 form.session = request.session
32 form.session = request.session
33
33
34 if form.is_valid():
34 if form.is_valid():
35 return _new_post(request, form)
35 return _new_post(request, form)
36 else:
36 else:
37 form = threadFormClass(error_class=PlainErrorList, **kwargs)
37 form = threadFormClass(error_class=PlainErrorList, **kwargs)
38
38
39 threads = Post.objects.get_threads(page=int(page))
39 threads = Post.objects.get_threads(page=int(page))
40
40
41 context['threads'] = None if len(threads) == 0 else threads
41 context['threads'] = None if len(threads) == 0 else threads
42 context['form'] = form
42 context['form'] = form
43 context['pages'] = range(Post.objects.get_thread_page_count())
43 context['pages'] = range(Post.objects.get_thread_page_count())
44
44
45 return render(request, 'boards/posting_general.html',
45 return render(request, 'boards/posting_general.html',
46 context)
46 context)
47
47
48
48
49 def _new_post(request, form, thread_id=boards.models.NO_PARENT):
49 def _new_post(request, form, thread_id=boards.models.NO_PARENT):
50 """Add a new post (in thread or as a reply)."""
50 """Add a new post (in thread or as a reply)."""
51
51
52 ip = _get_client_ip(request)
52 ip = _get_client_ip(request)
53 is_banned = Ban.objects.filter(ip=ip).count() > 0
53 is_banned = Ban.objects.filter(ip=ip).count() > 0
54
54
55 if is_banned:
55 if is_banned:
56 return redirect(you_are_banned)
56 return redirect(you_are_banned)
57
57
58 data = form.cleaned_data
58 data = form.cleaned_data
59
59
60 title = data['title']
60 title = data['title']
61 text = data['text']
61 text = data['text']
62
62
63 if 'image' in data.keys():
63 if 'image' in data.keys():
64 image = data['image']
64 image = data['image']
65 else:
65 else:
66 image = None
66 image = None
67
67
68 tags = []
68 tags = []
69
69
70 new_thread = thread_id == boards.models.NO_PARENT
70 new_thread = thread_id == boards.models.NO_PARENT
71 if new_thread:
71 if new_thread:
72 tag_strings = data['tags']
72 tag_strings = data['tags']
73
73
74 if tag_strings:
74 if tag_strings:
75 tag_strings = tag_strings.split(' ')
75 tag_strings = tag_strings.split(' ')
76 for tag_name in tag_strings:
76 for tag_name in tag_strings:
77 tag_name = tag_name.strip()
77 tag_name = tag_name.strip()
78 if len(tag_name) > 0:
78 if len(tag_name) > 0:
79 tag, created = Tag.objects.get_or_create(name=tag_name)
79 tag, created = Tag.objects.get_or_create(name=tag_name)
80 tags.append(tag)
80 tags.append(tag)
81
81
82 # TODO Add a possibility to define a link image instead of an image file.
82 # TODO Add a possibility to define a link image instead of an image file.
83 # If a link is given, download the image automatically.
83 # If a link is given, download the image automatically.
84
84
85 post = Post.objects.create_post(title=title, text=text, ip=ip,
85 post = Post.objects.create_post(title=title, text=text, ip=ip,
86 parent_id=thread_id, image=image,
86 parent_id=thread_id, image=image,
87 tags=tags)
87 tags=tags)
88
88
89 thread_to_show = (post.id if new_thread else thread_id)
89 thread_to_show = (post.id if new_thread else thread_id)
90
90
91 if new_thread:
91 if new_thread:
92 return redirect(thread, post_id=thread_to_show)
92 return redirect(thread, post_id=thread_to_show)
93 else:
93 else:
94 return redirect(reverse(thread,
94 return redirect(reverse(thread,
95 kwargs={'post_id': thread_to_show}) + '#'
95 kwargs={'post_id': thread_to_show}) + '#'
96 + str(post.id))
96 + str(post.id))
97
97
98
98
99 def tag(request, tag_name, page=0):
99 def tag(request, tag_name, page=0):
100 """Get all tag threads (posts without a parent)."""
100 """Get all tag threads (posts without a parent)."""
101
101
102 tag = get_object_or_404(Tag, name=tag_name)
102 tag = get_object_or_404(Tag, name=tag_name)
103 threads = Post.objects.get_threads(tag=tag, page=int(page))
103 threads = Post.objects.get_threads(tag=tag, page=int(page))
104
104
105 if request.method == 'POST':
105 if request.method == 'POST':
106 form = ThreadForm(request.POST, request.FILES,
106 form = ThreadForm(request.POST, request.FILES,
107 error_class=PlainErrorList)
107 error_class=PlainErrorList)
108 if form.is_valid():
108 if form.is_valid():
109 return _new_post(request, form)
109 return _new_post(request, form)
110 else:
110 else:
111 form = forms.ThreadForm(initial={'tags': tag_name},
111 form = forms.ThreadForm(initial={'tags': tag_name},
112 error_class=PlainErrorList)
112 error_class=PlainErrorList)
113
113
114 context = _init_default_context(request)
114 context = _init_default_context(request)
115 context['threads'] = None if len(threads) == 0 else threads
115 context['threads'] = None if len(threads) == 0 else threads
116 context['tag'] = tag_name
116 context['tag'] = tag_name
117 context['pages'] = range(Post.objects.get_thread_page_count(tag=tag))
117 context['pages'] = range(Post.objects.get_thread_page_count(tag=tag))
118
118
119 context['form'] = form
119 context['form'] = form
120
120
121 return render(request, 'boards/posting_general.html',
121 return render(request, 'boards/posting_general.html',
122 context)
122 context)
123
123
124
124
125 def thread(request, post_id):
125 def thread(request, post_id):
126 """Get all thread posts"""
126 """Get all thread posts"""
127
127
128 if utils.need_include_captcha(request):
128 if utils.need_include_captcha(request):
129 postFormClass = PostCaptchaForm
129 postFormClass = PostCaptchaForm
130 kwargs = {'request': request}
130 kwargs = {'request': request}
131 else:
131 else:
132 postFormClass = PostForm
132 postFormClass = PostForm
133 kwargs = {}
133 kwargs = {}
134
134
135 if request.method == 'POST':
135 if request.method == 'POST':
136 form = postFormClass(request.POST, request.FILES,
136 form = postFormClass(request.POST, request.FILES,
137 error_class=PlainErrorList, **kwargs)
137 error_class=PlainErrorList, **kwargs)
138 form.session = request.session
138 form.session = request.session
139
139
140 if form.is_valid():
140 if form.is_valid():
141 return _new_post(request, form, post_id)
141 return _new_post(request, form, post_id)
142 else:
142 else:
143 form = postFormClass(error_class=PlainErrorList, **kwargs)
143 form = postFormClass(error_class=PlainErrorList, **kwargs)
144
144
145 posts = Post.objects.get_thread(post_id)
145 posts = Post.objects.get_thread(post_id)
146
146
147 context = _init_default_context(request)
147 context = _init_default_context(request)
148
148
149 context['posts'] = posts
149 context['posts'] = posts
150 context['form'] = form
150 context['form'] = form
151
151
152 return render(request, 'boards/thread.html', context)
152 return render(request, 'boards/thread.html', context)
153
153
154
154
155 def login(request):
155 def login(request):
156 """Log in with user id"""
156 """Log in with user id"""
157
157
158 context = _init_default_context(request)
158 context = _init_default_context(request)
159
159
160 if request.method == 'POST':
160 if request.method == 'POST':
161 form = LoginForm(request.POST, request.FILES,
161 form = LoginForm(request.POST, request.FILES,
162 error_class=PlainErrorList)
162 error_class=PlainErrorList)
163 if form.is_valid():
163 if form.is_valid():
164 user = User.objects.get(user_id=form.cleaned_data['user_id'])
164 user = User.objects.get(user_id=form.cleaned_data['user_id'])
165 request.session['user_id'] = user.id
165 request.session['user_id'] = user.id
166 return redirect(index)
166 return redirect(index)
167
167
168 else:
168 else:
169 form = LoginForm()
169 form = LoginForm()
170
170
171 context['form'] = form
171 context['form'] = form
172
172
173 return render(request, 'boards/login.html', context)
173 return render(request, 'boards/login.html', context)
174
174
175
175
176 def settings(request):
176 def settings(request):
177 """User's settings"""
177 """User's settings"""
178
178
179 context = _init_default_context(request)
179 context = _init_default_context(request)
180
180
181 if request.method == 'POST':
181 if request.method == 'POST':
182 form = SettingsForm(request.POST)
182 form = SettingsForm(request.POST)
183 if form.is_valid():
183 if form.is_valid():
184 selected_theme = form.cleaned_data['theme']
184 selected_theme = form.cleaned_data['theme']
185
185
186 user = _get_user(request)
186 user = _get_user(request)
187 user.save_setting('theme', selected_theme)
187 user.save_setting('theme', selected_theme)
188
188
189 return redirect(settings)
189 return redirect(settings)
190 else:
190 else:
191 selected_theme = _get_theme(request)
191 selected_theme = _get_theme(request)
192 form = SettingsForm(initial={'theme': selected_theme})
192 form = SettingsForm(initial={'theme': selected_theme})
193 context['form'] = form
193 context['form'] = form
194
194
195 return render(request, 'boards/settings.html', context)
195 return render(request, 'boards/settings.html', context)
196
196
197
197
198 def all_tags(request):
198 def all_tags(request):
199 """All tags list"""
199 """All tags list"""
200
200
201 context = _init_default_context(request)
201 context = _init_default_context(request)
202 context['all_tags'] = Tag.objects.get_not_empty_tags()
202 context['all_tags'] = Tag.objects.get_not_empty_tags()
203
203
204 return render(request, 'boards/tags.html', context)
204 return render(request, 'boards/tags.html', context)
205
205
206
206
207 def jump_to_post(request, post_id):
207 def jump_to_post(request, post_id):
208 """Determine thread in which the requested post is and open it's page"""
208 """Determine thread in which the requested post is and open it's page"""
209
209
210 post = get_object_or_404(Post, id=post_id)
210 post = get_object_or_404(Post, id=post_id)
211
211
212 if boards.models.NO_PARENT == post.parent:
212 if boards.models.NO_PARENT == post.parent:
213 return redirect(thread, post_id=post.id)
213 return redirect(thread, post_id=post.id)
214 else:
214 else:
215 parent_thread = get_object_or_404(Post, id=post.parent)
215 parent_thread = get_object_or_404(Post, id=post.parent)
216 return redirect(reverse(thread, kwargs={'post_id': parent_thread.id})
216 return redirect(reverse(thread, kwargs={'post_id': parent_thread.id})
217 + '#' + str(post.id))
217 + '#' + str(post.id))
218
218
219
219
220 def authors(request):
220 def authors(request):
221 context = _init_default_context(request)
221 context = _init_default_context(request)
222 context['authors'] = boards.authors.authors
222 context['authors'] = boards.authors.authors
223
223
224 return render(request, 'boards/authors.html', context)
224 return render(request, 'boards/authors.html', context)
225
225
226
226
227 def delete(request, post_id):
227 def delete(request, post_id):
228 user = _get_user(request)
228 user = _get_user(request)
229 post = get_object_or_404(Post, id=post_id)
229 post = get_object_or_404(Post, id=post_id)
230
230
231 if user.is_moderator():
231 if user.is_moderator():
232 # TODO Show confirmation page before deletion
232 # TODO Show confirmation page before deletion
233 Post.objects.delete_post(post)
233 Post.objects.delete_post(post)
234
234
235 if NO_PARENT == post.parent:
235 if NO_PARENT == post.parent:
236 return _redirect_to_next(request)
236 return _redirect_to_next(request)
237 else:
237 else:
238 return redirect(thread, post_id=post.parent)
238 return redirect(thread, post_id=post.parent)
239
239
240
240
241 def ban(request, post_id):
242 user = _get_user(request)
243 post = get_object_or_404(Post, id=post_id)
244
245 if user.is_moderator():
246 # TODO Show confirmation page before ban
247 Ban.objects.get_or_create(ip=post.poster_ip)
248
249 return _redirect_to_next(request)
250
251
241 def you_are_banned(request):
252 def you_are_banned(request):
242 context = _init_default_context(request)
253 context = _init_default_context(request)
243 return render(request, 'boards/staticpages/banned.html', context)
254 return render(request, 'boards/staticpages/banned.html', context)
244
255
245
256
246 def page_404(request):
257 def page_404(request):
247 context = _init_default_context(request)
258 context = _init_default_context(request)
248 return render(request, 'boards/404.html', context)
259 return render(request, 'boards/404.html', context)
249
260
250
261
251 def tag_subscribe(request, tag_name):
262 def tag_subscribe(request, tag_name):
252 user = _get_user(request)
263 user = _get_user(request)
253 tag = get_object_or_404(Tag, name=tag_name)
264 tag = get_object_or_404(Tag, name=tag_name)
254
265
255 if not tag in user.fav_tags.all():
266 if not tag in user.fav_tags.all():
256 user.fav_tags.add(tag)
267 user.fav_tags.add(tag)
257
268
258 return redirect(all_tags)
269 return redirect(all_tags)
259
270
260
271
261 def tag_unsubscribe(request, tag_name):
272 def tag_unsubscribe(request, tag_name):
262 user = _get_user(request)
273 user = _get_user(request)
263 tag = get_object_or_404(Tag, name=tag_name)
274 tag = get_object_or_404(Tag, name=tag_name)
264
275
265 if tag in user.fav_tags.all():
276 if tag in user.fav_tags.all():
266 user.fav_tags.remove(tag)
277 user.fav_tags.remove(tag)
267
278
268 return redirect(all_tags)
279 return redirect(all_tags)
269
280
270
281
271 def static_page(request, name):
282 def static_page(request, name):
272 context = _init_default_context(request)
283 context = _init_default_context(request)
273 return render(request, 'boards/staticpages/' + name + '.html', context)
284 return render(request, 'boards/staticpages/' + name + '.html', context)
274
285
275
286
276 def _get_theme(request, user=None):
287 def _get_theme(request, user=None):
277 """Get user's CSS theme"""
288 """Get user's CSS theme"""
278
289
279 if not user:
290 if not user:
280 user = _get_user(request)
291 user = _get_user(request)
281 theme = user.get_setting('theme')
292 theme = user.get_setting('theme')
282 if not theme:
293 if not theme:
283 theme = neboard.settings.DEFAULT_THEME
294 theme = neboard.settings.DEFAULT_THEME
284
295
285 return theme
296 return theme
286
297
287
298
288 def _get_client_ip(request):
299 def _get_client_ip(request):
289 x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
300 x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
290 if x_forwarded_for:
301 if x_forwarded_for:
291 ip = x_forwarded_for.split(',')[-1].strip()
302 ip = x_forwarded_for.split(',')[-1].strip()
292 else:
303 else:
293 ip = request.META.get('REMOTE_ADDR')
304 ip = request.META.get('REMOTE_ADDR')
294 return ip
305 return ip
295
306
296
307
297 def _init_default_context(request):
308 def _init_default_context(request):
298 """Create context with default values that are used in most views"""
309 """Create context with default values that are used in most views"""
299
310
300 context = RequestContext(request)
311 context = RequestContext(request)
301
312
302 user = _get_user(request)
313 user = _get_user(request)
303 context['user'] = user
314 context['user'] = user
304 context['tags'] = user.get_sorted_fav_tags()
315 context['tags'] = user.get_sorted_fav_tags()
305 context['theme'] = _get_theme(request, user)
316 context['theme'] = _get_theme(request, user)
306
317
307 return context
318 return context
308
319
309
320
310 def _get_user(request):
321 def _get_user(request):
311 """Get current user from the session"""
322 """Get current user from the session"""
312
323
313 session = request.session
324 session = request.session
314 if not 'user_id' in session:
325 if not 'user_id' in session:
315 request.session.save()
326 request.session.save()
316
327
317 md5 = hashlib.md5()
328 md5 = hashlib.md5()
318 md5.update(session.session_key)
329 md5.update(session.session_key)
319 new_id = md5.hexdigest()
330 new_id = md5.hexdigest()
320
331
321 time_now = timezone.now()
332 time_now = timezone.now()
322 user = User.objects.create(user_id=new_id, rank=RANK_USER,
333 user = User.objects.create(user_id=new_id, rank=RANK_USER,
323 registration_time=time_now,
334 registration_time=time_now,
324 last_access_time=time_now)
335 last_access_time=time_now)
325
336
326 session['user_id'] = user.id
337 session['user_id'] = user.id
327 else:
338 else:
328 user = User.objects.get(id=session['user_id'])
339 user = User.objects.get(id=session['user_id'])
329 user.last_access_time = timezone.now()
340 user.last_access_time = timezone.now()
330 user.save()
341 user.save()
331
342
332 return user
343 return user
333
344
334
345
335 def _redirect_to_next(request):
346 def _redirect_to_next(request):
336 next_page = request.GET['next']
347 if 'next' in request.GET:
337 return HttpResponseRedirect(next_page)
348 next_page = request.GET['next']
349 return HttpResponseRedirect(next_page)
350 else:
351 return redirect(index)
General Comments 0
You need to be logged in to leave comments. Login now