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