##// END OF EJS Templates
Use cache for thread list.
neko259 -
r318:eebfca22 default
parent child Browse files
Show More
@@ -1,380 +1,382 b''
1 1 import os
2 2 from random import random
3 3 import time
4 4 import math
5 5
6 6 from django.db import models
7 7 from django.db.models import Count
8 8 from django.http import Http404
9 9 from django.utils import timezone
10 10 from markupfield.fields import MarkupField
11 11
12 12 from neboard import settings
13 13 import thumbs
14 14
15 15 import re
16 16
17 17 IMAGE_THUMB_SIZE = (200, 150)
18 18
19 19 TITLE_MAX_LENGTH = 50
20 20
21 21 DEFAULT_MARKUP_TYPE = 'markdown'
22 22
23 23 NO_PARENT = -1
24 24 NO_IP = '0.0.0.0'
25 25 UNKNOWN_UA = ''
26 26 ALL_PAGES = -1
27 27 OPENING_POST_POPULARITY_WEIGHT = 2
28 28 IMAGES_DIRECTORY = 'images/'
29 29 FILE_EXTENSION_DELIMITER = '.'
30 30
31 31 RANK_ADMIN = 0
32 32 RANK_MODERATOR = 10
33 33 RANK_USER = 100
34 34
35 35 SETTING_MODERATE = "moderate"
36 36
37 37 REGEX_REPLY = re.compile('>>(\d+)')
38 38
39 39
40 40 class PostManager(models.Manager):
41 41
42 42 def create_post(self, title, text, image=None, thread=None,
43 43 ip=NO_IP, tags=None, user=None):
44 44 post = self.create(title=title,
45 45 text=text,
46 46 pub_time=timezone.now(),
47 47 thread=thread,
48 48 image=image,
49 49 poster_ip=ip,
50 50 poster_user_agent=UNKNOWN_UA,
51 51 last_edit_time=timezone.now(),
52 52 bump_time=timezone.now(),
53 53 user=user)
54 54
55 55 if tags:
56 56 map(post.tags.add, tags)
57 57 for tag in tags:
58 58 tag.threads.add(post)
59 59
60 60 if thread:
61 61 thread.replies.add(post)
62 62 thread.bump()
63 63 thread.last_edit_time = timezone.now()
64 64 thread.save()
65 65 else:
66 66 self._delete_old_threads()
67 67
68 self.connect_replies(post)
68 self.connect_replies(post)
69 69
70 70 return post
71 71
72 72 def delete_post(self, post):
73 73 if post.replies.count() > 0:
74 74 map(self.delete_post, post.replies.all())
75 75
76 76 # Update thread's last edit time (used as cache key)
77 77 thread = post.thread
78 78 if thread:
79 79 thread.last_edit_time = timezone.now()
80 80 thread.save()
81 81
82 82 post.delete()
83 83
84 84 def delete_posts_by_ip(self, ip):
85 85 posts = self.filter(poster_ip=ip)
86 86 map(self.delete_post, posts)
87 87
88 88 def get_threads(self, tag=None, page=ALL_PAGES,
89 89 order_by='-bump_time'):
90 90 if tag:
91 91 threads = tag.threads
92 92
93 93 if threads.count() == 0:
94 94 raise Http404
95 95 else:
96 96 threads = self.filter(thread=None)
97 97
98 98 threads = threads.order_by(order_by)
99 99
100 100 if page != ALL_PAGES:
101 101 thread_count = threads.count()
102 102
103 if page < self.get_thread_page_count(tag=tag):
103 if page < self._get_page_count(thread_count):
104 104 start_thread = page * settings.THREADS_PER_PAGE
105 105 end_thread = min(start_thread + settings.THREADS_PER_PAGE,
106 106 thread_count)
107 107 threads = threads[start_thread:end_thread]
108 108
109 109 return threads
110 110
111 111 def get_thread(self, opening_post_id):
112 112 try:
113 113 opening_post = self.get(id=opening_post_id, thread=None)
114 114 except Post.DoesNotExist:
115 115 raise Http404
116 116
117 117 if opening_post.replies:
118 118 thread = [opening_post]
119 119 thread.extend(opening_post.replies.all().order_by('pub_time'))
120 120
121 121 return thread
122 122
123 123 def exists(self, post_id):
124 124 posts = self.filter(id=post_id)
125 125
126 126 return posts.count() > 0
127 127
128 128 def get_thread_page_count(self, tag=None):
129 129 if tag:
130 130 threads = self.filter(thread=None, tags=tag)
131 131 else:
132 132 threads = self.filter(thread=None)
133 133
134 return int(math.ceil(threads.count() / float(
135 settings.THREADS_PER_PAGE)))
134 return self._get_page_count(threads.count())
136 135
137 136 def _delete_old_threads(self):
138 137 """
139 138 Preserves maximum thread count. If there are too many threads,
140 139 delete the old ones.
141 140 """
142 141
143 142 # TODO Move old threads to the archive instead of deleting them.
144 143 # Maybe make some 'old' field in the model to indicate the thread
145 144 # must not be shown and be able for replying.
146 145
147 146 threads = self.get_threads()
148 147 thread_count = threads.count()
149 148
150 149 if thread_count > settings.MAX_THREAD_COUNT:
151 150 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
152 151 old_threads = threads[thread_count - num_threads_to_delete:]
153 152
154 153 map(self.delete_post, old_threads)
155 154
156 155 def connect_replies(self, post):
157 """Connect replies to a post to show them as a refmap"""
156 """Connect replies to a post to show them as a refmap"""
158 157
159 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
160 id = reply_number.group(1)
161 ref_post = self.filter(id=id)
162 if ref_post.count() > 0:
163 ref_post[0].referenced_posts.add(post)
158 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
159 id = reply_number.group(1)
160 ref_post = self.filter(id=id)
161 if ref_post.count() > 0:
162 ref_post[0].referenced_posts.add(post)
163
164 def _get_page_count(self, thread_count):
165 return int(math.ceil(thread_count / float(settings.THREADS_PER_PAGE)))
164 166
165 167
166 168 class TagManager(models.Manager):
167 169
168 170 def get_not_empty_tags(self):
169 171 tags = self.annotate(Count('threads')) \
170 172 .filter(threads__count__gt=0).order_by('name')
171 173
172 174 return tags
173 175
174 176
175 177 class Tag(models.Model):
176 178 """
177 179 A tag is a text node assigned to the post. The tag serves as a board
178 180 section. There can be multiple tags for each message
179 181 """
180 182
181 183 objects = TagManager()
182 184
183 185 name = models.CharField(max_length=100)
184 186 threads = models.ManyToManyField('Post', null=True,
185 187 blank=True, related_name='tag+')
186 188 linked = models.ForeignKey('Tag', null=True, blank=True)
187 189
188 190 def __unicode__(self):
189 191 return self.name
190 192
191 193 def is_empty(self):
192 194 return self.get_post_count() == 0
193 195
194 196 def get_post_count(self):
195 197 return self.threads.count()
196 198
197 199 def get_popularity(self):
198 200 posts_with_tag = Post.objects.get_threads(tag=self)
199 201 reply_count = 0
200 202 for post in posts_with_tag:
201 203 reply_count += post.get_reply_count()
202 204 reply_count += OPENING_POST_POPULARITY_WEIGHT
203 205
204 206 return reply_count
205 207
206 208 def get_linked_tags(self):
207 209 tag_list = []
208 210 self.get_linked_tags_list(tag_list)
209 211
210 212 return tag_list
211 213
212 214 def get_linked_tags_list(self, tag_list=[]):
213 215 """
214 216 Returns the list of tags linked to current. The list can be got
215 217 through returned value or tag_list parameter
216 218 """
217 219
218 220 linked_tag = self.linked
219 221
220 222 if linked_tag and not (linked_tag in tag_list):
221 223 tag_list.append(linked_tag)
222 224
223 225 linked_tag.get_linked_tags_list(tag_list)
224 226
225 227
226 228 class Post(models.Model):
227 229 """A post is a message."""
228 230
229 231 objects = PostManager()
230 232
231 233 def _update_image_filename(self, filename):
232 234 """Get unique image filename"""
233 235
234 236 path = IMAGES_DIRECTORY
235 237 new_name = str(int(time.mktime(time.gmtime())))
236 238 new_name += str(int(random() * 1000))
237 239 new_name += FILE_EXTENSION_DELIMITER
238 240 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
239 241
240 242 return os.path.join(path, new_name)
241 243
242 244 title = models.CharField(max_length=TITLE_MAX_LENGTH)
243 245 pub_time = models.DateTimeField()
244 246 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
245 247 escape_html=False)
246 248
247 249 image_width = models.IntegerField(default=0)
248 250 image_height = models.IntegerField(default=0)
249 251
250 252 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
251 253 blank=True, sizes=(IMAGE_THUMB_SIZE,),
252 254 width_field='image_width',
253 255 height_field='image_height')
254 256
255 257 poster_ip = models.GenericIPAddressField()
256 258 poster_user_agent = models.TextField()
257 259
258 260 thread = models.ForeignKey('Post', null=True, default=None)
259 261 tags = models.ManyToManyField(Tag)
260 262 last_edit_time = models.DateTimeField()
261 263 bump_time = models.DateTimeField()
262 264 user = models.ForeignKey('User', null=True, default=None)
263 265
264 266 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
265 267 blank=True, related_name='re+')
266 268 referenced_posts = models.ManyToManyField('Post', symmetrical=False, null=True,
267 269 blank=True, related_name='rfp+')
268 270
269 271 def __unicode__(self):
270 272 return '#' + str(self.id) + ' ' + self.title + ' (' + \
271 273 self.text.raw[:50] + ')'
272 274
273 275 def get_title(self):
274 276 title = self.title
275 277 if len(title) == 0:
276 278 title = self.text.raw[:20]
277 279
278 280 return title
279 281
280 282 def get_reply_count(self):
281 283 return self.replies.count()
282 284
283 285 def get_images_count(self):
284 286 images_count = 1 if self.image else 0
285 287 images_count += self.replies.filter(image_width__gt=0).count()
286 288
287 289 return images_count
288 290
289 291 def can_bump(self):
290 292 """Check if the thread can be bumped by replying"""
291 293
292 294 post_count = self.get_reply_count() + 1
293 295
294 296 return post_count <= settings.MAX_POSTS_PER_THREAD
295 297
296 298 def bump(self):
297 299 """Bump (move to up) thread"""
298 300
299 301 if self.can_bump():
300 302 self.bump_time = timezone.now()
301 303
302 304 def get_last_replies(self):
303 305 if settings.LAST_REPLIES_COUNT > 0:
304 306 reply_count = self.get_reply_count()
305 307
306 308 if reply_count > 0:
307 309 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
308 310 reply_count)
309 311 last_replies = self.replies.all().order_by('pub_time')[reply_count -
310 312 reply_count_to_show:]
311 313
312 314 return last_replies
313 315
314 316 def get_tags(self):
315 317 """Get a sorted tag list"""
316 318
317 319 return self.tags.order_by('name')
318 320
319 321
320 322 class User(models.Model):
321 323
322 324 user_id = models.CharField(max_length=50)
323 325 rank = models.IntegerField()
324 326
325 327 registration_time = models.DateTimeField()
326 328
327 329 fav_tags = models.ManyToManyField(Tag, null=True, blank=True)
328 330 fav_threads = models.ManyToManyField(Post, related_name='+', null=True,
329 331 blank=True)
330 332
331 333 def save_setting(self, name, value):
332 334 setting, created = Setting.objects.get_or_create(name=name, user=self)
333 335 setting.value = str(value)
334 336 setting.save()
335 337
336 338 return setting
337 339
338 340 def get_setting(self, name):
339 341 if Setting.objects.filter(name=name, user=self).exists():
340 342 setting = Setting.objects.get(name=name, user=self)
341 343 setting_value = setting.value
342 344 else:
343 345 setting_value = None
344 346
345 347 return setting_value
346 348
347 349 def is_moderator(self):
348 350 return RANK_MODERATOR >= self.rank
349 351
350 352 def get_sorted_fav_tags(self):
351 353 tags = self.fav_tags.annotate(Count('threads'))\
352 354 .filter(threads__count__gt=0).order_by('name')
353 355
354 356 return tags
355 357
356 358 def get_post_count(self):
357 359 return Post.objects.filter(user=self).count()
358 360
359 361 def __unicode__(self):
360 362 return self.user_id + '(' + str(self.rank) + ')'
361 363
362 364 def get_last_access_time(self):
363 365 posts = Post.objects.filter(user=self)
364 366 if posts.count() > 0:
365 367 return posts.latest('pub_time').pub_time
366 368
367 369
368 370 class Setting(models.Model):
369 371
370 372 name = models.CharField(max_length=50)
371 373 value = models.CharField(max_length=50)
372 374 user = models.ForeignKey(User)
373 375
374 376
375 377 class Ban(models.Model):
376 378
377 379 ip = models.GenericIPAddressField()
378 380
379 381 def __unicode__(self):
380 382 return self.ip
@@ -1,223 +1,227 b''
1 1 {% extends "boards/base.html" %}
2 2
3 3 {% load i18n %}
4 4 {% load markup %}
5 5 {% load cache %}
6 6
7 7 {% block head %}
8 8 {% if tag %}
9 9 <title>Neboard - {{ tag.name }}</title>
10 10 {% else %}
11 11 <title>Neboard</title>
12 12 {% endif %}
13 13 {% endblock %}
14 14
15 15 {% block content %}
16 16
17 {% get_current_language as LANGUAGE_CODE %}
18
17 19 {% if tag %}
18 20 <div class="tag_info">
19 21 <h2>
20 22 {% if tag in user.fav_tags.all %}
21 23 <a href="{% url 'tag_unsubscribe' tag.name %}?next={{ request.path }}"
22 24 class="fav">β˜…</a>
23 25 {% else %}
24 26 <a href="{% url 'tag_subscribe' tag.name %}?next={{ request.path }}"
25 27 class="not_fav">β˜…</a>
26 28 {% endif %}
27 29 #{{ tag.name }}
28 30 </h2>
29 31 </div>
30 32 {% endif %}
31 33
32 34 {% if threads %}
33 35 {% for thread in threads %}
36 {% cache 600 thread_short thread.thread.last_edit_time moderator LANGUAGE_CODE %}
34 37 <div class="thread">
35 38 {% if thread.bumpable %}
36 39 <div class="post" id="{{ thread.thread.id }}">
37 40 {% else %}
38 41 <div class="post dead_post" id="{{ thread.thread.id }}">
39 42 {% endif %}
40 43 {% if thread.thread.image %}
41 44 <div class="image">
42 45 <a class="thumb"
43 46 href="{{ thread.thread.image.url }}"><img
44 47 src="{{ thread.thread.image.url_200x150 }}"
45 48 alt="{{ thread.thread.id }}"
46 49 data-width="{{ thread.thread.image_width }}"
47 50 data-height="{{ thread.thread.image_height }}" />
48 51 </a>
49 52 </div>
50 53 {% endif %}
51 54 <div class="message">
52 55 <div class="post-info">
53 56 <span class="title">{{ thread.thread.title }}</span>
54 57 <a class="post_id" href="{% url 'thread' thread.thread.id %}"
55 58 >({{ thread.thread.id }})</a>
56 59 [{{ thread.thread.pub_time }}]
57 60 [<a class="link" href="{% url 'thread' thread.thread.id %}#form"
58 61 >{% trans "Reply" %}</a>]
59 62
60 63 {% if moderator %}
61 64 <span class="moderator_info">
62 65 [<a href="{% url 'delete' post_id=thread.thread.id %}?next={{ request.path }}"
63 66 >{% trans 'Delete' %}</a>]
64 67 ({{ thread.thread.poster_ip }})
65 68 [<a href="{% url 'ban' post_id=thread.thread.id %}?next={{ request.path }}"
66 69 >{% trans 'Ban IP' %}</a>]
67 70 </span>
68 71 {% endif %}
69 72 </div>
70 73 {% autoescape off %}
71 74 {{ thread.thread.text.rendered|truncatewords_html:50 }}
72 75 {% endautoescape %}
73 76 {% if thread.thread.referenced_posts.all %}
74 77 <div class="refmap">
75 78 {% trans "Replies" %}:
76 79 {% for ref_post in thread.thread.referenced_posts.all %}
77 80 <a href="{% url 'jumper' ref_post.id %}">&gt;&gt;{{ ref_post.id }}</a>
78 81 {% endfor %}
79 82 </div>
80 83 {% endif %}
81 84 </div>
82 85 <div class="metadata">
83 86 {{ thread.thread.get_reply_count }} {% trans 'replies' %},
84 87 {{ thread.thread.get_images_count }} {% trans 'images' %}.
85 88 {% if thread.thread.tags %}
86 89 <span class="tags">
87 90 {% for tag in thread.thread.get_tags %}
88 91 <a class="tag" href="
89 92 {% url 'tag' tag_name=tag.name %}">
90 93 #{{ tag.name }}</a>
91 94 {% endfor %}
92 95 </span>
93 96 {% endif %}
94 97 </div>
95 98 </div>
96 {% if thread.thread.get_last_replies.exists %}
99 {% if thread.last_replies.exists %}
97 100 <div class="last-replies">
98 {% for post in thread.thread.get_last_replies %}
101 {% for post in thread.last_replies %}
99 102 {% if thread.bumpable %}
100 103 <div class="post" id="{{ post.id }}">
101 104 {% else %}
102 105 <div class="post dead_post" id="{{ post.id }}">
103 106 {% endif %}
104 107 {% if post.image %}
105 108 <div class="image">
106 109 <a class="thumb"
107 110 href="{{ post.image.url }}"><img
108 111 src=" {{ post.image.url_200x150 }}"
109 112 alt="{{ post.id }}"
110 113 data-width="{{ post.image_width }}"
111 114 data-height="{{ post.image_height }}"/>
112 115 </a>
113 116 </div>
114 117 {% endif %}
115 118 <div class="message">
116 119 <div class="post-info">
117 120 <span class="title">{{ post.title }}</span>
118 121 <a class="post_id" href="
119 122 {% url 'thread' thread.thread.id %}#{{ post.id }}">
120 123 ({{ post.id }})</a>
121 124 [{{ post.pub_time }}]
122 125 </div>
123 126 {% autoescape off %}
124 127 {{ post.text.rendered|truncatewords_html:50 }}
125 128 {% endautoescape %}
126 129 </div>
127 130 {% if post.referenced_posts.all %}
128 131 <div class="refmap">
129 132 {% trans "Replies" %}:
130 133 {% for ref_post in post.referenced_posts.all %}
131 134 <a href="{% url 'jumper' ref_post.id %}">&gt;&gt;{{ ref_post.id }}</a>
132 135 {% endfor %}
133 136 </div>
134 137 {% endif %}
135 138 </div>
136 139 {% endfor %}
137 140 </div>
138 141 {% endif %}
139 142 </div>
143 {% endcache %}
140 144 {% endfor %}
141 145 {% else %}
142 146 <div class="post">
143 147 {% trans 'No threads exist. Create the first one!' %}</div>
144 148 {% endif %}
145 149
146 150 <form enctype="multipart/form-data" method="post">{% csrf_token %}
147 151 <div class="post-form-w">
148 152
149 153 <div class="form-title">{% trans "Create new thread" %}</div>
150 154 <div class="post-form">
151 155 <div class="form-row">
152 156 <div class="form-label">{% trans 'Title' %}</div>
153 157 <div class="form-input">{{ form.title }}</div>
154 158 <div class="form-errors">{{ form.title.errors }}</div>
155 159 </div>
156 160 <div class="form-row">
157 161 <div class="form-label">{% trans 'Formatting' %}</div>
158 162 <div class="form-input" id="mark_panel">
159 163 <span class="mark_btn" id="quote"><span class="quote">&gt;{% trans 'quote' %}</span></span>
160 164 <span class="mark_btn" id="italic"><i>{% trans 'italic' %}</i></span>
161 165 <span class="mark_btn" id="bold"><b>{% trans 'bold' %}</b></span>
162 166 <span class="mark_btn" id="spoiler"><span class="spoiler">{% trans 'spoiler' %}</span></span>
163 167 <span class="mark_btn" id="comment"><span class="comment">// {% trans 'comment' %}</span></span>
164 168 </div>
165 169 </div>
166 170 <div class="form-row">
167 171 <div class="form-label">{% trans 'Text' %}</div>
168 172 <div class="form-input">{{ form.text }}</div>
169 173 <div class="form-errors">{{ form.text.errors }}</div>
170 174 </div>
171 175 <div class="form-row">
172 176 <div class="form-label">{% trans 'Image' %}</div>
173 177 <div class="form-input">{{ form.image }}</div>
174 178 <div class="form-errors">{{ form.image.errors }}</div>
175 179 </div>
176 180 <div class="form-row">
177 181 <div class="form-label">{% trans 'Tags' %}</div>
178 182 <div class="form-input">{{ form.tags }}</div>
179 183 <div class="form-errors">{{ form.tags.errors }}</div>
180 184 </div>
181 185 <div class="form-row form-email">
182 186 <div class="form-label">{% trans 'e-mail' %}</div>
183 187 <div class="form-input">{{ form.email }}</div>
184 188 <div class="form-errors">{{ form.email.errors }}</div>
185 189 </div>
186 190 <div class="form-row">
187 191 {{ form.captcha }}
188 192 <div class="form-errors">{{ form.captcha.errors }}</div>
189 193 </div>
190 194 <div class="form-row">
191 195 <div class="form-errors">{{ form.other.errors }}</div>
192 196 </div>
193 197 </div>
194 198 <div class="form-submit">
195 199 <input type="submit" value="{% trans "Post" %}"/></div>
196 200 <div>
197 201 {% trans 'Tags must be delimited by spaces. Text or image is required.' %}
198 202 </div>
199 203 <div><a href="{% url "staticpage" name="help" %}">
200 204 {% trans 'Text syntax' %}</a></div>
201 205 </div>
202 206 </form>
203 207
204 208 {% endblock %}
205 209
206 210 {% block metapanel %}
207 211
208 212 <span class="metapanel">
209 213 <b><a href="{% url "authors" %}">Neboard</a> 1.3</b>
210 214 {% trans "Pages:" %}
211 215 {% for page in pages %}
212 216 [<a href="
213 217 {% if tag %}
214 218 {% url "tag" tag_name=tag page=page %}
215 219 {% else %}
216 220 {% url "index" page=page %}
217 221 {% endif %}
218 222 ">{{ page }}</a>]
219 223 {% endfor %}
220 224 [<a href="rss/">RSS</a>]
221 225 </span>
222 226
223 227 {% endblock %}
@@ -1,478 +1,484 b''
1 1 import hashlib
2 2 import string
3 3 from django.core import serializers
4 4 from django.core.urlresolvers import reverse
5 5 from django.http import HttpResponseRedirect
6 6 from django.http.response import HttpResponse
7 7 from django.template import RequestContext
8 8 from django.shortcuts import render, redirect, get_object_or_404
9 9 from django.utils import timezone
10 10
11 11 from boards import forms
12 12 import boards
13 13 from boards import utils
14 14 from boards.forms import ThreadForm, PostForm, SettingsForm, PlainErrorList, \
15 15 ThreadCaptchaForm, PostCaptchaForm, LoginForm, ModeratorSettingsForm
16 16
17 17 from boards.models import Post, Tag, Ban, User, RANK_USER, SETTING_MODERATE, \
18 18 REGEX_REPLY
19 19 from boards import authors
20 20 from boards.utils import get_client_ip
21 21 import neboard
22 22 import re
23 23
24 24
25 25 def index(request, page=0):
26 26 context = _init_default_context(request)
27 27
28 28 if utils.need_include_captcha(request):
29 29 threadFormClass = ThreadCaptchaForm
30 30 kwargs = {'request': request}
31 31 else:
32 32 threadFormClass = ThreadForm
33 33 kwargs = {}
34 34
35 35 if request.method == 'POST':
36 36 form = threadFormClass(request.POST, request.FILES,
37 37 error_class=PlainErrorList, **kwargs)
38 38 form.session = request.session
39 39
40 40 if form.is_valid():
41 41 return _new_post(request, form)
42 42 if form.need_to_ban:
43 43 # Ban user because he is suspected to be a bot
44 44 _ban_current_user(request)
45 45 else:
46 46 form = threadFormClass(error_class=PlainErrorList, **kwargs)
47 47
48 48 threads = []
49 49 for thread in Post.objects.get_threads(page=int(page)):
50 threads.append({'thread': thread,
51 'bumpable': thread.can_bump()})
50 threads.append({
51 'thread': thread,
52 'bumpable': thread.can_bump(),
53 'last_replies': thread.get_last_replies(),
54 })
52 55
53 56 context['threads'] = None if len(threads) == 0 else threads
54 57 context['form'] = form
55 58 context['pages'] = range(Post.objects.get_thread_page_count())
56 59
57 60 return render(request, 'boards/posting_general.html',
58 61 context)
59 62
60 63
61 64 def _new_post(request, form, thread_id=boards.models.NO_PARENT):
62 65 """Add a new post (in thread or as a reply)."""
63 66
64 67 ip = get_client_ip(request)
65 68 is_banned = Ban.objects.filter(ip=ip).exists()
66 69
67 70 if is_banned:
68 71 return redirect(you_are_banned)
69 72
70 73 data = form.cleaned_data
71 74
72 75 title = data['title']
73 76 text = data['text']
74 77
75 78 text = _remove_invalid_links(text)
76 79
77 80 if 'image' in data.keys():
78 81 image = data['image']
79 82 else:
80 83 image = None
81 84
82 85 tags = []
83 86
84 87 new_thread = thread_id == boards.models.NO_PARENT
85 88 if new_thread:
86 89 tag_strings = data['tags']
87 90
88 91 if tag_strings:
89 92 tag_strings = tag_strings.split(' ')
90 93 for tag_name in tag_strings:
91 94 tag_name = string.lower(tag_name.strip())
92 95 if len(tag_name) > 0:
93 96 tag, created = Tag.objects.get_or_create(name=tag_name)
94 97 tags.append(tag)
95 98
96 99 linked_tags = tag.get_linked_tags()
97 100 if len(linked_tags) > 0:
98 101 tags.extend(linked_tags)
99 102
100 103 op = None if thread_id == boards.models.NO_PARENT else \
101 104 get_object_or_404(Post, id=thread_id)
102 105 post = Post.objects.create_post(title=title, text=text, ip=ip,
103 106 thread=op, image=image,
104 107 tags=tags, user=_get_user(request))
105 108
106 109 thread_to_show = (post.id if new_thread else thread_id)
107 110
108 111 if new_thread:
109 112 return redirect(thread, post_id=thread_to_show)
110 113 else:
111 114 return redirect(reverse(thread, kwargs={'post_id': thread_to_show}) +
112 115 '#' + str(post.id))
113 116
114 117
115 118 def tag(request, tag_name, page=0):
116 119 """
117 120 Get all tag threads. Threads are split in pages, so some page is
118 121 requested. Default page is 0.
119 122 """
120 123
121 124 tag = get_object_or_404(Tag, name=tag_name)
122 125 threads = []
123 126 for thread in Post.objects.get_threads(tag=tag, page=int(page)):
124 threads.append({'thread': thread,
125 'bumpable': thread.can_bump()})
127 threads.append({
128 'thread': thread,
129 'bumpable': thread.can_bump(),
130 'last_replies': thread.get_last_replies(),
131 })
126 132
127 133 if request.method == 'POST':
128 134 form = ThreadForm(request.POST, request.FILES,
129 135 error_class=PlainErrorList)
130 136 form.session = request.session
131 137
132 138 if form.is_valid():
133 139 return _new_post(request, form)
134 140 if form.need_to_ban:
135 141 # Ban user because he is suspected to be a bot
136 142 _ban_current_user(request)
137 143 else:
138 144 form = forms.ThreadForm(initial={'tags': tag_name},
139 145 error_class=PlainErrorList)
140 146
141 147 context = _init_default_context(request)
142 148 context['threads'] = None if len(threads) == 0 else threads
143 149 context['tag'] = tag
144 150 context['pages'] = range(Post.objects.get_thread_page_count(tag=tag))
145 151
146 152 context['form'] = form
147 153
148 154 return render(request, 'boards/posting_general.html',
149 155 context)
150 156
151 157
152 158 def thread(request, post_id):
153 159 """Get all thread posts"""
154 160
155 161 if utils.need_include_captcha(request):
156 162 postFormClass = PostCaptchaForm
157 163 kwargs = {'request': request}
158 164 else:
159 165 postFormClass = PostForm
160 166 kwargs = {}
161 167
162 168 if request.method == 'POST':
163 169 form = postFormClass(request.POST, request.FILES,
164 170 error_class=PlainErrorList, **kwargs)
165 171 form.session = request.session
166 172
167 173 if form.is_valid():
168 174 return _new_post(request, form, post_id)
169 175 if form.need_to_ban:
170 176 # Ban user because he is suspected to be a bot
171 177 _ban_current_user(request)
172 178 else:
173 179 form = postFormClass(error_class=PlainErrorList, **kwargs)
174 180
175 181 posts = Post.objects.get_thread(post_id)
176 182
177 183 context = _init_default_context(request)
178 184
179 185 context['posts'] = posts
180 186 context['form'] = form
181 187 context['bumpable'] = posts[0].can_bump()
182 188 if context['bumpable']:
183 189 context['posts_left'] = neboard.settings.MAX_POSTS_PER_THREAD - len(
184 190 posts)
185 191 context['bumplimit_progress'] = str(float(context['posts_left']) /
186 192 neboard.settings.MAX_POSTS_PER_THREAD * 100)
187 193
188 194 return render(request, 'boards/thread.html', context)
189 195
190 196
191 197 def login(request):
192 198 """Log in with user id"""
193 199
194 200 context = _init_default_context(request)
195 201
196 202 if request.method == 'POST':
197 203 form = LoginForm(request.POST, request.FILES,
198 204 error_class=PlainErrorList)
199 205 form.session = request.session
200 206
201 207 if form.is_valid():
202 208 user = User.objects.get(user_id=form.cleaned_data['user_id'])
203 209 request.session['user_id'] = user.id
204 210 return redirect(index)
205 211
206 212 else:
207 213 form = LoginForm()
208 214
209 215 context['form'] = form
210 216
211 217 return render(request, 'boards/login.html', context)
212 218
213 219
214 220 def settings(request):
215 221 """User's settings"""
216 222
217 223 context = _init_default_context(request)
218 224 user = _get_user(request)
219 225 is_moderator = user.is_moderator()
220 226
221 227 if request.method == 'POST':
222 228 if is_moderator:
223 229 form = ModeratorSettingsForm(request.POST,
224 230 error_class=PlainErrorList)
225 231 else:
226 232 form = SettingsForm(request.POST, error_class=PlainErrorList)
227 233
228 234 if form.is_valid():
229 235 selected_theme = form.cleaned_data['theme']
230 236
231 237 user.save_setting('theme', selected_theme)
232 238
233 239 if is_moderator:
234 240 moderate = form.cleaned_data['moderate']
235 241 user.save_setting(SETTING_MODERATE, moderate)
236 242
237 243 return redirect(settings)
238 244 else:
239 245 selected_theme = _get_theme(request)
240 246
241 247 if is_moderator:
242 248 form = ModeratorSettingsForm(initial={'theme': selected_theme,
243 249 'moderate': context['moderator']},
244 250 error_class=PlainErrorList)
245 251 else:
246 252 form = SettingsForm(initial={'theme': selected_theme},
247 253 error_class=PlainErrorList)
248 254
249 255 context['form'] = form
250 256
251 257 return render(request, 'boards/settings.html', context)
252 258
253 259
254 260 def all_tags(request):
255 261 """All tags list"""
256 262
257 263 context = _init_default_context(request)
258 264 context['all_tags'] = Tag.objects.get_not_empty_tags()
259 265
260 266 return render(request, 'boards/tags.html', context)
261 267
262 268
263 269 def jump_to_post(request, post_id):
264 270 """Determine thread in which the requested post is and open it's page"""
265 271
266 272 post = get_object_or_404(Post, id=post_id)
267 273
268 274 if not post.thread:
269 275 return redirect(thread, post_id=post.id)
270 276 else:
271 277 return redirect(reverse(thread, kwargs={'post_id': post.thread.id})
272 278 + '#' + str(post.id))
273 279
274 280
275 281 def authors(request):
276 282 """Show authors list"""
277 283
278 284 context = _init_default_context(request)
279 285 context['authors'] = boards.authors.authors
280 286
281 287 return render(request, 'boards/authors.html', context)
282 288
283 289
284 290 def delete(request, post_id):
285 291 """Delete post"""
286 292
287 293 user = _get_user(request)
288 294 post = get_object_or_404(Post, id=post_id)
289 295
290 296 if user.is_moderator():
291 297 # TODO Show confirmation page before deletion
292 298 Post.objects.delete_post(post)
293 299
294 300 if not post.thread:
295 301 return _redirect_to_next(request)
296 302 else:
297 303 return redirect(thread, post_id=post.thread.id)
298 304
299 305
300 306 def ban(request, post_id):
301 307 """Ban user"""
302 308
303 309 user = _get_user(request)
304 310 post = get_object_or_404(Post, id=post_id)
305 311
306 312 if user.is_moderator():
307 313 # TODO Show confirmation page before ban
308 314 Ban.objects.get_or_create(ip=post.poster_ip)
309 315
310 316 return _redirect_to_next(request)
311 317
312 318
313 319 def you_are_banned(request):
314 320 """Show the page that notifies that user is banned"""
315 321
316 322 context = _init_default_context(request)
317 323 return render(request, 'boards/staticpages/banned.html', context)
318 324
319 325
320 326 def page_404(request):
321 327 """Show page 404 (not found error)"""
322 328
323 329 context = _init_default_context(request)
324 330 return render(request, 'boards/404.html', context)
325 331
326 332
327 333 def tag_subscribe(request, tag_name):
328 334 """Add tag to favorites"""
329 335
330 336 user = _get_user(request)
331 337 tag = get_object_or_404(Tag, name=tag_name)
332 338
333 339 if not tag in user.fav_tags.all():
334 340 user.fav_tags.add(tag)
335 341
336 342 return _redirect_to_next(request)
337 343
338 344
339 345 def tag_unsubscribe(request, tag_name):
340 346 """Remove tag from favorites"""
341 347
342 348 user = _get_user(request)
343 349 tag = get_object_or_404(Tag, name=tag_name)
344 350
345 351 if tag in user.fav_tags.all():
346 352 user.fav_tags.remove(tag)
347 353
348 354 return _redirect_to_next(request)
349 355
350 356
351 357 def static_page(request, name):
352 358 """Show a static page that needs only tags list and a CSS"""
353 359
354 360 context = _init_default_context(request)
355 361 return render(request, 'boards/staticpages/' + name + '.html', context)
356 362
357 363
358 364 def api_get_post(request, post_id):
359 365 """
360 366 Get the JSON of a post. This can be
361 367 used as and API for external clients.
362 368 """
363 369
364 370 post = get_object_or_404(Post, id=post_id)
365 371
366 372 json = serializers.serialize("json", [post], fields=(
367 373 "pub_time", "_text_rendered", "title", "text", "image",
368 374 "image_width", "image_height", "replies", "tags"
369 375 ))
370 376
371 377 return HttpResponse(content=json)
372 378
373 379
374 380 def get_post(request, post_id):
375 381 """Get the html of a post. Used for popups."""
376 382
377 383 post = get_object_or_404(Post, id=post_id)
378 384
379 385 context = RequestContext(request)
380 386 context["post"] = post
381 387
382 388 return render(request, 'boards/post.html', context)
383 389
384 390
385 391 def _get_theme(request, user=None):
386 392 """Get user's CSS theme"""
387 393
388 394 if not user:
389 395 user = _get_user(request)
390 396 theme = user.get_setting('theme')
391 397 if not theme:
392 398 theme = neboard.settings.DEFAULT_THEME
393 399
394 400 return theme
395 401
396 402
397 403 def _init_default_context(request):
398 404 """Create context with default values that are used in most views"""
399 405
400 406 context = RequestContext(request)
401 407
402 408 user = _get_user(request)
403 409 context['user'] = user
404 410 context['tags'] = user.get_sorted_fav_tags()
405 411
406 412 theme = _get_theme(request, user)
407 413 context['theme'] = theme
408 414 context['theme_css'] = 'css/' + theme + '/base_page.css'
409 415
410 416 # This shows the moderator panel
411 417 moderate = user.get_setting(SETTING_MODERATE)
412 418 if moderate == 'True':
413 419 context['moderator'] = user.is_moderator()
414 420 else:
415 421 context['moderator'] = False
416 422
417 423 return context
418 424
419 425
420 426 def _get_user(request):
421 427 """
422 428 Get current user from the session. If the user does not exist, create
423 429 a new one.
424 430 """
425 431
426 432 session = request.session
427 433 if not 'user_id' in session:
428 434 request.session.save()
429 435
430 436 md5 = hashlib.md5()
431 437 md5.update(session.session_key)
432 438 new_id = md5.hexdigest()
433 439
434 440 time_now = timezone.now()
435 441 user = User.objects.create(user_id=new_id, rank=RANK_USER,
436 442 registration_time=time_now)
437 443
438 444 session['user_id'] = user.id
439 445 else:
440 446 user = User.objects.get(id=session['user_id'])
441 447
442 448 return user
443 449
444 450
445 451 def _redirect_to_next(request):
446 452 """
447 453 If a 'next' parameter was specified, redirect to the next page. This is
448 454 used when the user is required to return to some page after the current
449 455 view has finished its work.
450 456 """
451 457
452 458 if 'next' in request.GET:
453 459 next_page = request.GET['next']
454 460 return HttpResponseRedirect(next_page)
455 461 else:
456 462 return redirect(index)
457 463
458 464
459 465 def _ban_current_user(request):
460 466 """Add current user to the IP ban list"""
461 467
462 468 ip = utils.get_client_ip(request)
463 469 Ban.objects.get_or_create(ip=ip)
464 470
465 471
466 472 def _remove_invalid_links(text):
467 473 """
468 474 Replace invalid links in posts so that they won't be parsed.
469 475 Invalid links are links to non-existent posts
470 476 """
471 477
472 478 for reply_number in re.finditer(REGEX_REPLY, text):
473 479 id = reply_number.group(1)
474 480 post = Post.objects.filter(id=id)
475 481 if not post.exists():
476 482 text = string.replace(text, '>>' + id, id)
477 483
478 484 return text
General Comments 0
You need to be logged in to leave comments. Login now