##// END OF EJS Templates
Using django paginator instead of manual pagination
neko259 -
r493:1e58394d 1.6-dev
parent child Browse files
Show More
@@ -1,393 +1,371 b''
1 1 from datetime import datetime, timedelta
2 2 from datetime import time as dtime
3 3 import os
4 4 from random import random
5 5 import time
6 6 import math
7 7 import re
8 8 from django.core.cache import cache
9 from django.core.paginator import Paginator
9 10
10 11 from django.db import models
11 12 from django.http import Http404
12 13 from django.utils import timezone
13 14 from markupfield.fields import MarkupField
14 15
15 16 from neboard import settings
16 17 from boards import thumbs
17 18
18 19 MAX_TITLE_LENGTH = 50
19 20
20 21 APP_LABEL_BOARDS = 'boards'
21 22
22 23 CACHE_KEY_PPD = 'ppd'
23 24
24 25 POSTS_PER_DAY_RANGE = range(7)
25 26
26 27 BAN_REASON_AUTO = 'Auto'
27 28
28 29 IMAGE_THUMB_SIZE = (200, 150)
29 30
30 31 TITLE_MAX_LENGTH = 50
31 32
32 33 DEFAULT_MARKUP_TYPE = 'markdown'
33 34
34 35 NO_PARENT = -1
35 36 NO_IP = '0.0.0.0'
36 37 UNKNOWN_UA = ''
37 38 ALL_PAGES = -1
38 39 IMAGES_DIRECTORY = 'images/'
39 40 FILE_EXTENSION_DELIMITER = '.'
40 41
41 42 SETTING_MODERATE = "moderate"
42 43
43 44 REGEX_REPLY = re.compile('>>(\d+)')
44 45
45 46
46 47 class PostManager(models.Manager):
47 48
48 49 def create_post(self, title, text, image=None, thread=None,
49 50 ip=NO_IP, tags=None, user=None):
50 51 """
51 52 Create new post
52 53 """
53 54
54 55 posting_time = timezone.now()
55 56 if not thread:
56 57 thread = Thread.objects.create(bump_time=posting_time,
57 58 last_edit_time=posting_time)
58 59 else:
59 60 thread.bump()
60 61 thread.last_edit_time = posting_time
61 62 thread.save()
62 63
63 64 post = self.create(title=title,
64 65 text=text,
65 66 pub_time=posting_time,
66 67 thread_new=thread,
67 68 image=image,
68 69 poster_ip=ip,
69 70 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
70 71 # last!
71 72 last_edit_time=posting_time,
72 73 user=user)
73 74
74 75 thread.replies.add(post)
75 76 if tags:
76 77 linked_tags = []
77 78 for tag in tags:
78 79 tag_linked_tags = tag.get_linked_tags()
79 80 if len(tag_linked_tags) > 0:
80 81 linked_tags.extend(tag_linked_tags)
81 82
82 83 tags.extend(linked_tags)
83 84 map(thread.add_tag, tags)
84 85
85 86 self._delete_old_threads()
86 87 self.connect_replies(post)
87 88
88 89 return post
89 90
90 91 def delete_post(self, post):
91 92 """
92 93 Delete post and update or delete its thread
93 94 """
94 95
95 96 thread = post.thread_new
96 97
97 98 if thread.get_opening_post() == self:
98 99 thread.replies.delete()
99 100
100 101 thread.delete()
101 102 else:
102 103 thread.last_edit_time = timezone.now()
103 104 thread.save()
104 105
105 106 post.delete()
106 107
107 108 def delete_posts_by_ip(self, ip):
108 109 """
109 110 Delete all posts of the author with same IP
110 111 """
111 112
112 113 posts = self.filter(poster_ip=ip)
113 114 map(self.delete_post, posts)
114 115
115 116 # TODO Move this method to thread manager
116 117 def get_threads(self, tag=None, page=ALL_PAGES,
117 118 order_by='-bump_time', archived=False):
118 119 if tag:
119 120 threads = tag.threads
120 121
121 122 if not threads.exists():
122 123 raise Http404
123 124 else:
124 125 threads = Thread.objects.all()
125 126
126 127 threads = threads.filter(archived=archived).order_by(order_by)
127 128
128 129 if page != ALL_PAGES:
129 thread_count = threads.count()
130
131 if page < self._get_page_count(thread_count):
132 start_thread = page * settings.THREADS_PER_PAGE
133 end_thread = min(start_thread + settings.THREADS_PER_PAGE,
134 thread_count)
135 threads = threads[start_thread:end_thread]
130 threads = Paginator(threads, settings.THREADS_PER_PAGE).page(
131 page).object_list
136 132
137 133 return threads
138 134
139 135 # TODO Move this method to thread manager
140 def get_thread_page_count(self, tag=None, archived=False):
141 if tag:
142 threads = Thread.objects.filter(tags=tag)
143 else:
144 threads = Thread.objects.all()
145
146 threads = threads.filter(archived=archived)
147
148 return self._get_page_count(threads.count())
149
150 # TODO Move this method to thread manager
151 136 def _delete_old_threads(self):
152 137 """
153 138 Preserves maximum thread count. If there are too many threads,
154 139 archive the old ones.
155 140 """
156 141
157 142 threads = self.get_threads()
158 143 thread_count = threads.count()
159 144
160 145 if thread_count > settings.MAX_THREAD_COUNT:
161 146 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
162 147 old_threads = threads[thread_count - num_threads_to_delete:]
163 148
164 149 for thread in old_threads:
165 150 thread.archived = True
166 151 thread.last_edit_time = timezone.now()
167 152 thread.save()
168 153
169 154 def connect_replies(self, post):
170 155 """
171 156 Connect replies to a post to show them as a reflink map
172 157 """
173 158
174 159 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
175 160 post_id = reply_number.group(1)
176 161 ref_post = self.filter(id=post_id)
177 162 if ref_post.count() > 0:
178 163 referenced_post = ref_post[0]
179 164 referenced_post.referenced_posts.add(post)
180 165 referenced_post.last_edit_time = post.pub_time
181 166 referenced_post.save()
182 167
183 def _get_page_count(self, thread_count):
184 """
185 Get number of pages that will be needed for all threads
186 """
187
188 return int(math.ceil(thread_count / float(settings.THREADS_PER_PAGE)))
189
190 168 def get_posts_per_day(self):
191 169 """
192 170 Get average count of posts per day for the last 7 days
193 171 """
194 172
195 173 today = datetime.now().date()
196 174 ppd = cache.get(CACHE_KEY_PPD + str(today))
197 175 if ppd:
198 176 return ppd
199 177
200 178 posts_per_days = []
201 179 for i in POSTS_PER_DAY_RANGE:
202 180 day_end = today - timedelta(i + 1)
203 181 day_start = today - timedelta(i + 2)
204 182
205 183 day_time_start = timezone.make_aware(datetime.combine(day_start,
206 184 dtime()), timezone.get_current_timezone())
207 185 day_time_end = timezone.make_aware(datetime.combine(day_end,
208 186 dtime()), timezone.get_current_timezone())
209 187
210 188 posts_per_days.append(float(self.filter(
211 189 pub_time__lte=day_time_end,
212 190 pub_time__gte=day_time_start).count()))
213 191
214 192 ppd = (sum(posts_per_day for posts_per_day in posts_per_days) /
215 193 len(posts_per_days))
216 194 cache.set(CACHE_KEY_PPD, ppd)
217 195 return ppd
218 196
219 197
220 198 class Post(models.Model):
221 199 """A post is a message."""
222 200
223 201 objects = PostManager()
224 202
225 203 class Meta:
226 204 app_label = APP_LABEL_BOARDS
227 205
228 206 # TODO Save original file name to some field
229 207 def _update_image_filename(self, filename):
230 208 """Get unique image filename"""
231 209
232 210 path = IMAGES_DIRECTORY
233 211 new_name = str(int(time.mktime(time.gmtime())))
234 212 new_name += str(int(random() * 1000))
235 213 new_name += FILE_EXTENSION_DELIMITER
236 214 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
237 215
238 216 return os.path.join(path, new_name)
239 217
240 218 title = models.CharField(max_length=TITLE_MAX_LENGTH)
241 219 pub_time = models.DateTimeField()
242 220 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
243 221 escape_html=False)
244 222
245 223 image_width = models.IntegerField(default=0)
246 224 image_height = models.IntegerField(default=0)
247 225
248 226 image_pre_width = models.IntegerField(default=0)
249 227 image_pre_height = models.IntegerField(default=0)
250 228
251 229 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
252 230 blank=True, sizes=(IMAGE_THUMB_SIZE,),
253 231 width_field='image_width',
254 232 height_field='image_height',
255 233 preview_width_field='image_pre_width',
256 234 preview_height_field='image_pre_height')
257 235
258 236 poster_ip = models.GenericIPAddressField()
259 237 poster_user_agent = models.TextField()
260 238
261 239 thread = models.ForeignKey('Post', null=True, default=None)
262 240 thread_new = models.ForeignKey('Thread', null=True, default=None)
263 241 last_edit_time = models.DateTimeField()
264 242 user = models.ForeignKey('User', null=True, default=None)
265 243
266 244 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
267 245 null=True,
268 246 blank=True, related_name='rfp+')
269 247
270 248 def __unicode__(self):
271 249 return '#' + str(self.id) + ' ' + self.title + ' (' + \
272 250 self.text.raw[:50] + ')'
273 251
274 252 def get_title(self):
275 253 title = self.title
276 254 if len(title) == 0:
277 255 title = self.text.rendered[:MAX_TITLE_LENGTH]
278 256
279 257 return title
280 258
281 259 def get_sorted_referenced_posts(self):
282 260 return self.referenced_posts.order_by('id')
283 261
284 262 def is_referenced(self):
285 263 return self.referenced_posts.all().exists()
286 264
287 265 def is_opening(self):
288 266 return self.thread_new.get_replies()[0] == self
289 267
290 268
291 269 class Thread(models.Model):
292 270
293 271 class Meta:
294 272 app_label = APP_LABEL_BOARDS
295 273
296 274 tags = models.ManyToManyField('Tag')
297 275 bump_time = models.DateTimeField()
298 276 last_edit_time = models.DateTimeField()
299 277 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
300 278 blank=True, related_name='tre+')
301 279 archived = models.BooleanField(default=False)
302 280
303 281 def get_tags(self):
304 282 """
305 283 Get a sorted tag list
306 284 """
307 285
308 286 return self.tags.order_by('name')
309 287
310 288 def bump(self):
311 289 """
312 290 Bump (move to up) thread
313 291 """
314 292
315 293 if self.can_bump():
316 294 self.bump_time = timezone.now()
317 295
318 296 def get_reply_count(self):
319 297 return self.replies.count()
320 298
321 299 def get_images_count(self):
322 300 return self.replies.filter(image_width__gt=0).count()
323 301
324 302 def can_bump(self):
325 303 """
326 304 Check if the thread can be bumped by replying
327 305 """
328 306
329 307 if self.archived:
330 308 return False
331 309
332 310 post_count = self.get_reply_count()
333 311
334 312 return post_count < settings.MAX_POSTS_PER_THREAD
335 313
336 314 def delete_with_posts(self):
337 315 """
338 316 Completely delete thread and all its posts
339 317 """
340 318
341 319 if self.replies.count() > 0:
342 320 self.replies.all().delete()
343 321
344 322 self.delete()
345 323
346 324 def get_last_replies(self):
347 325 """
348 326 Get last replies, not including opening post
349 327 """
350 328
351 329 if settings.LAST_REPLIES_COUNT > 0:
352 330 reply_count = self.get_reply_count()
353 331
354 332 if reply_count > 0:
355 333 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
356 334 reply_count - 1)
357 335 last_replies = self.replies.all().order_by('pub_time')[
358 336 reply_count - reply_count_to_show:]
359 337
360 338 return last_replies
361 339
362 340 def get_replies(self):
363 341 """
364 342 Get sorted thread posts
365 343 """
366 344
367 345 return self.replies.all().order_by('pub_time')
368 346
369 347 def add_tag(self, tag):
370 348 """
371 349 Connect thread to a tag and tag to a thread
372 350 """
373 351
374 352 self.tags.add(tag)
375 353 tag.threads.add(self)
376 354
377 355 def get_opening_post(self):
378 356 """
379 357 Get first post of the thread
380 358 """
381 359
382 360 return self.get_replies()[0]
383 361
384 362 def __unicode__(self):
385 363 return str(self.get_replies()[0].id)
386 364
387 365 def get_pub_time(self):
388 366 """
389 367 Thread does not have its own pub time, so we need to get it from
390 368 the opening post
391 369 """
392 370
393 371 return self.get_opening_post().pub_time
@@ -1,145 +1,145 b''
1 1 {% extends "boards/base.html" %}
2 2
3 3 {% load i18n %}
4 4 {% load cache %}
5 5 {% load board %}
6 6 {% load static %}
7 7
8 8 {% block head %}
9 <title>Neboard</title>
9 <title>Neboard - {% trans 'Archive' %}</title>
10 10
11 {% if prev_page %}
12 <link rel="next" href="
11 {% if current_page.has_previous %}
12 <link rel="prev" href="
13 13 {% if tag %}
14 {% url "tag" tag_name=tag page=prev_page %}
14 {% url "tag" tag_name=tag page=current_page.previous_page_number %}
15 15 {% else %}
16 {% url "index" page=prev_page %}
16 {% url "index" page=current_page.previous_page_number %}
17 17 {% endif %}
18 18 " />
19 19 {% endif %}
20 {% if next_page %}
20 {% if current_page.has_next %}
21 21 <link rel="next" href="
22 22 {% if tag %}
23 {% url "tag" tag_name=tag page=next_page %}
23 {% url "tag" tag_name=tag page=current_page.next_page_number %}
24 24 {% else %}
25 {% url "index" page=next_page %}
25 {% url "index" page=current_page.next_page_number %}
26 26 {% endif %}
27 27 " />
28 28 {% endif %}
29 29
30 30 {% endblock %}
31 31
32 32 {% block content %}
33 33
34 34 {% get_current_language as LANGUAGE_CODE %}
35 35
36 36 {% if threads %}
37 {% if prev_page %}
37 {% if current_page.has_previous %}
38 38 <div class="page_link">
39 <a href="{% url "archive" page=prev_page %}">{% trans "Previous page" %}</a>
39 <a href="{% url "archive" page=current_page.previous_page_number %}">{% trans "Previous page" %}</a>
40 40 </div>
41 41 {% endif %}
42 42
43 43 {% for thread in threads %}
44 44 {% cache 600 thread_short thread.id thread.thread.last_edit_time moderator LANGUAGE_CODE %}
45 45 <div class="thread">
46 46 <div class="post archive_post" id="{{ thread.op.id }}">
47 47 {% if thread.op.image %}
48 48 <div class="image">
49 49 <a class="thumb"
50 50 href="{{ thread.op.image.url }}"><img
51 51 src="{{ thread.op.image.url_200x150 }}"
52 52 alt="{{ thread.op.id }}"
53 53 width="{{ thread.op.image_pre_width }}"
54 54 height="{{ thread.op.image_pre_height }}"
55 55 data-width="{{ thread.op.image_width }}"
56 56 data-height="{{ thread.op.image_height }}"/>
57 57 </a>
58 58 </div>
59 59 {% endif %}
60 60 <div class="message">
61 61 <div class="post-info">
62 62 <span class="title">{{ thread.op.title }}</span>
63 63 <a class="post_id" href="{% url 'thread' thread.op.id %}"
64 64 > ({{ thread.op.id }})</a>
65 65 [{{ thread.op.pub_time }}] β€” [{{ thread.thread.last_edit_time }}]
66 66
67 67 [<a class="link" href="
68 68 {% url 'thread' thread.op.id %}">{% trans "Open" %}</a>]
69 69
70 70 {% if moderator %}
71 71 <span class="moderator_info">
72 72 [<a href="
73 73 {% url 'delete' post_id=thread.op.id %}?next={{ request.path }}"
74 74 >{% trans 'Delete' %}</a>]
75 75 ({{ thread.op.poster_ip }})
76 76 [<a href="
77 77 {% url 'ban' post_id=thread.op.id %}?next={{ request.path }}"
78 78 >{% trans 'Ban IP' %}</a>]
79 79 </span>
80 80 {% endif %}
81 81 </div>
82 82 {% autoescape off %}
83 83 {{ thread.op.text.rendered|truncatewords_html:50 }}
84 84 {% endautoescape %}
85 85 {% if thread.op.is_referenced %}
86 86 <div class="refmap">
87 87 {% trans "Replies" %}:
88 88 {% for ref_post in thread.op.get_sorted_referenced_posts %}
89 89 <a href="{% post_url ref_post.id %}">&gt;&gt;{{ ref_post.id }}</a
90 90 >{% if not forloop.last %},{% endif %}
91 91 {% endfor %}
92 92 </div>
93 93 {% endif %}
94 94 </div>
95 95 <div class="metadata">
96 96 {{ thread.thread.get_images_count }} {% trans 'images' %},
97 97 {{ thread.thread.get_reply_count }} {% trans 'replies' %}.
98 98 {% if thread.thread.tags %}
99 99 <span class="tags">
100 100 {% for tag in thread.thread.get_tags %}
101 101 <a class="tag" href="
102 102 {% url 'tag' tag_name=tag.name %}">
103 103 #{{ tag.name }}</a
104 104 >{% if not forloop.last %},{% endif %}
105 105 {% endfor %}
106 106 </span>
107 107 {% endif %}
108 108 </div>
109 109 </div>
110 110 </div>
111 111 {% endcache %}
112 112 {% endfor %}
113 113
114 {% if next_page %}
114 {% if current_page.has_next %}
115 115 <div class="page_link">
116 <a href="{% url "archive" page=next_page %}">{% trans "Next page" %}</a>
116 <a href="{% url "archive" page=current_page.next_page_number %}">{% trans "Next page" %}</a>
117 117 </div>
118 118 {% endif %}
119 119 {% else %}
120 120 <div class="post">
121 121 {% trans 'No threads exist. Create the first one!' %}</div>
122 122 {% endif %}
123 123
124 124 {% endblock %}
125 125
126 126 {% block metapanel %}
127 127
128 128 <span class="metapanel">
129 <b><a href="{% url "authors" %}">Neboard</a> 1.5 Aker</b>
129 <b><a href="{% url "authors" %}">Neboard</a> 1.6 Amon</b>
130 130 {% trans "Pages:" %}[
131 {% for page in pages %}
131 {% for page in paginator.page_range %}
132 132 <a
133 {% ifequal page current_page %}
133 {% ifequal page current_page.number %}
134 134 class="current_page"
135 135 {% endifequal %}
136 136 href="
137 137 {% url "archive" page=page %}
138 138 ">{{ page }}</a>
139 139 {% if not forloop.last %},{% endif %}
140 140 {% endfor %}
141 141 ]
142 142 [<a href="rss/">RSS</a>]
143 143 </span>
144 144
145 145 {% endblock %}
@@ -1,254 +1,254 b''
1 1 {% extends "boards/base.html" %}
2 2
3 3 {% load i18n %}
4 4 {% load cache %}
5 5 {% load board %}
6 6 {% load static %}
7 7
8 8 {% block head %}
9 9 {% if tag %}
10 10 <title>Neboard - {{ tag.name }}</title>
11 11 {% else %}
12 12 <title>Neboard</title>
13 13 {% endif %}
14 14
15 {% if prev_page %}
16 <link rel="next" href="
15 {% if current_page.has_previous %}
16 <link rel="prev" href="
17 17 {% if tag %}
18 {% url "tag" tag_name=tag page=prev_page %}
18 {% url "tag" tag_name=tag page=current_page.previous_page_number %}
19 19 {% else %}
20 {% url "index" page=prev_page %}
20 {% url "index" page=current_page.previous_page_number %}
21 21 {% endif %}
22 22 " />
23 23 {% endif %}
24 {% if next_page %}
24 {% if current_page.has_next %}
25 25 <link rel="next" href="
26 26 {% if tag %}
27 {% url "tag" tag_name=tag page=next_page %}
27 {% url "tag" tag_name=tag page=current_page.next_page_number %}
28 28 {% else %}
29 {% url "index" page=next_page %}
29 {% url "index" page=current_page.next_page_number %}
30 30 {% endif %}
31 31 " />
32 32 {% endif %}
33 33
34 34 {% endblock %}
35 35
36 36 {% block content %}
37 37
38 38 {% get_current_language as LANGUAGE_CODE %}
39 39
40 40 {% if tag %}
41 41 <div class="tag_info">
42 42 <h2>
43 43 {% if tag in user.fav_tags.all %}
44 44 <a href="{% url 'tag_unsubscribe' tag.name %}?next={{ request.path }}"
45 45 class="fav">β˜…</a>
46 46 {% else %}
47 47 <a href="{% url 'tag_subscribe' tag.name %}?next={{ request.path }}"
48 48 class="not_fav">β˜…</a>
49 49 {% endif %}
50 50 #{{ tag.name }}
51 51 </h2>
52 52 </div>
53 53 {% endif %}
54 54
55 55 {% if threads %}
56 {% if prev_page %}
56 {% if current_page.has_previous %}
57 57 <div class="page_link">
58 58 <a href="
59 59 {% if tag %}
60 {% url "tag" tag_name=tag page=prev_page %}
60 {% url "tag" tag_name=tag page=current_page.previous_page_number %}
61 61 {% else %}
62 {% url "index" page=prev_page %}
62 {% url "index" page=current_page.previous_page_number %}
63 63 {% endif %}
64 64 ">{% trans "Previous page" %}</a>
65 65 </div>
66 66 {% endif %}
67 67
68 68 {% for thread in threads %}
69 69 {% cache 600 thread_short thread.id thread.thread.last_edit_time moderator LANGUAGE_CODE %}
70 70 <div class="thread">
71 71 {% if thread.bumpable %}
72 <div class="post" id="{{ thread.op.id }}">
72 <div class="post" id="{{ thread.op.id }}">
73 73 {% else %}
74 <div class="post dead_post" id="{{ thread.op.id }}">
74 <div class="post dead_post" id="{{ thread.op.id }}">
75 75 {% endif %}
76 76 {% if thread.op.image %}
77 77 <div class="image">
78 78 <a class="thumb"
79 79 href="{{ thread.op.image.url }}"><img
80 80 src="{{ thread.op.image.url_200x150 }}"
81 81 alt="{{ thread.op.id }}"
82 82 width="{{ thread.op.image_pre_width }}"
83 83 height="{{ thread.op.image_pre_height }}"
84 84 data-width="{{ thread.op.image_width }}"
85 85 data-height="{{ thread.op.image_height }}"/>
86 86 </a>
87 87 </div>
88 88 {% endif %}
89 89 <div class="message">
90 90 <div class="post-info">
91 91 <span class="title">{{ thread.op.title }}</span>
92 92 <a class="post_id" href="{% url 'thread' thread.op.id %}"
93 93 > ({{ thread.op.id }})</a>
94 94 [{{ thread.op.pub_time }}]
95 95 [<a class="link" href="
96 96 {% url 'thread' thread.op.id %}#form"
97 97 >{% trans "Reply" %}</a>]
98 98
99 99 {% if moderator %}
100 100 <span class="moderator_info">
101 101 [<a href="
102 102 {% url 'delete' post_id=thread.op.id %}?next={{ request.path }}"
103 103 >{% trans 'Delete' %}</a>]
104 104 ({{ thread.op.poster_ip }})
105 105 [<a href="
106 106 {% url 'ban' post_id=thread.op.id %}?next={{ request.path }}"
107 107 >{% trans 'Ban IP' %}</a>]
108 108 </span>
109 109 {% endif %}
110 110 </div>
111 111 {% autoescape off %}
112 112 {{ thread.op.text.rendered|truncatewords_html:50 }}
113 113 {% endautoescape %}
114 114 {% if thread.op.is_referenced %}
115 115 <div class="refmap">
116 116 {% trans "Replies" %}:
117 117 {% for ref_post in thread.op.get_sorted_referenced_posts %}
118 118 <a href="{% post_url ref_post.id %}">&gt;&gt;{{ ref_post.id }}</a
119 119 >{% if not forloop.last %},{% endif %}
120 120 {% endfor %}
121 121 </div>
122 122 {% endif %}
123 123 </div>
124 124 <div class="metadata">
125 125 {{ thread.thread.get_images_count }} {% trans 'images' %}.
126 126 {% if thread.thread.tags %}
127 127 <span class="tags">
128 128 {% for tag in thread.thread.get_tags %}
129 129 <a class="tag" href="
130 130 {% url 'tag' tag_name=tag.name %}">
131 131 #{{ tag.name }}</a
132 132 >{% if not forloop.last %},{% endif %}
133 133 {% endfor %}
134 134 </span>
135 135 {% endif %}
136 136 </div>
137 137 </div>
138 138 {% if thread.last_replies.exists %}
139 139 {% if thread.skipped_replies %}
140 140 <div class="skipped_replies">
141 141 <a href="{% url 'thread' thread.op.id %}">
142 142 {% blocktrans with count=thread.skipped_replies %}Skipped {{ count }} replies. Open thread to see all replies.{% endblocktrans %}
143 143 </a>
144 144 </div>
145 145 {% endif %}
146 146 <div class="last-replies">
147 147 {% for post in thread.last_replies %}
148 148 {% if thread.bumpable %}
149 149 <div class="post" id="{{ post.id }}">
150 150 {% else %}
151 151 <div class="post dead_post" id="{{ post.id }}">
152 152 {% endif %}
153 153 {% if post.image %}
154 154 <div class="image">
155 155 <a class="thumb"
156 156 href="{{ post.image.url }}"><img
157 157 src=" {{ post.image.url_200x150 }}"
158 158 alt="{{ post.id }}"
159 159 width="{{ post.image_pre_width }}"
160 160 height="{{ post.image_pre_height }}"
161 161 data-width="{{ post.image_width }}"
162 162 data-height="{{ post.image_height }}"/>
163 163 </a>
164 164 </div>
165 165 {% endif %}
166 166 <div class="message">
167 167 <div class="post-info">
168 168 <span class="title">{{ post.title }}</span>
169 169 <a class="post_id" href="
170 170 {% url 'thread' thread.op.id %}#{{ post.id }}">
171 171 ({{ post.id }})</a>
172 172 [{{ post.pub_time }}]
173 173 </div>
174 174 {% autoescape off %}
175 175 {{ post.text.rendered|truncatewords_html:50 }}
176 176 {% endautoescape %}
177 177 </div>
178 178 {% if post.is_referenced %}
179 179 <div class="refmap">
180 180 {% trans "Replies" %}:
181 181 {% for ref_post in post.get_sorted_referenced_posts %}
182 182 <a href="{% post_url ref_post.id %}">&gt;&gt;{{ ref_post.id }}</a
183 183 >{% if not forloop.last %},{% endif %}
184 184 {% endfor %}
185 185 </div>
186 186 {% endif %}
187 187 </div>
188 188 {% endfor %}
189 189 </div>
190 190 {% endif %}
191 191 </div>
192 192 {% endcache %}
193 193 {% endfor %}
194 194
195 {% if next_page %}
195 {% if current_page.has_next %}
196 196 <div class="page_link">
197 197 <a href="
198 198 {% if tag %}
199 {% url "tag" tag_name=tag page=next_page %}
199 {% url "tag" tag_name=tag page=current_page.next_page_number %}
200 200 {% else %}
201 {% url "index" page=next_page %}
201 {% url "index" page=current_page.next_page_number %}
202 202 {% endif %}
203 203 ">{% trans "Next page" %}</a>
204 204 </div>
205 205 {% endif %}
206 206 {% else %}
207 207 <div class="post">
208 208 {% trans 'No threads exist. Create the first one!' %}</div>
209 209 {% endif %}
210 210
211 211 <div class="post-form-w">
212 212 <script src="{% static 'js/panel.js' %}"></script>
213 213 <div class="post-form">
214 214 <div class="form-title">{% trans "Create new thread" %}</div>
215 215 <form enctype="multipart/form-data" method="post">{% csrf_token %}
216 216 {{ form.as_div }}
217 217 <div class="form-submit">
218 218 <input type="submit" value="{% trans "Post" %}"/>
219 219 </div>
220 220 </form>
221 221 <div>
222 222 {% trans 'Tags must be delimited by spaces. Text or image is required.' %}
223 223 </div>
224 224 <div><a href="{% url "staticpage" name="help" %}">
225 225 {% trans 'Text syntax' %}</a></div>
226 226 </div>
227 227 </div>
228 228
229 229 {% endblock %}
230 230
231 231 {% block metapanel %}
232 232
233 233 <span class="metapanel">
234 234 <b><a href="{% url "authors" %}">Neboard</a> 1.6 Amon</b>
235 235 {% trans "Pages:" %}[
236 {% for page in pages %}
236 {% for page in paginator.page_range %}
237 237 <a
238 {% ifequal page current_page %}
238 {% ifequal page current_page.number %}
239 239 class="current_page"
240 240 {% endifequal %}
241 241 href="
242 242 {% if tag %}
243 243 {% url "tag" tag_name=tag page=page %}
244 244 {% else %}
245 245 {% url "index" page=page %}
246 246 {% endif %}
247 247 ">{{ page }}</a>
248 248 {% if not forloop.last %},{% endif %}
249 249 {% endfor %}
250 250 ]
251 251 [<a href="rss/">RSS</a>]
252 252 </span>
253 253
254 254 {% endblock %}
@@ -1,604 +1,601 b''
1 1 from datetime import datetime, timedelta
2 2
3 3 from django.db.models import Count
4 4
5 5
6 6 OLD_USER_AGE_DAYS = 90
7 7
8 8 __author__ = 'neko259'
9 9
10 10 import hashlib
11 11 import string
12 12 import time
13 13 import re
14 14
15 15 from django.core import serializers
16 16 from django.core.urlresolvers import reverse
17 17 from django.http import HttpResponseRedirect, Http404
18 18 from django.http.response import HttpResponse
19 19 from django.template import RequestContext
20 20 from django.shortcuts import render, redirect, get_object_or_404
21 21 from django.utils import timezone
22 22 from django.db import transaction
23 23 from django.views.decorators.cache import cache_page
24 24 from django.views.i18n import javascript_catalog
25 from django.core.paginator import Paginator
25 26
26 27 from boards import forms
27 28 import boards
28 29 from boards import utils
29 30 from boards.forms import ThreadForm, PostForm, SettingsForm, PlainErrorList, \
30 31 ThreadCaptchaForm, PostCaptchaForm, LoginForm, ModeratorSettingsForm
31 from boards.models import Post, Tag, Ban, User
32 from boards.models import Post, Tag, Ban, User, Thread
32 33 from boards.models.post import SETTING_MODERATE, REGEX_REPLY
33 34 from boards.models.user import RANK_USER
34 35 from boards import authors
35 36 from boards.utils import get_client_ip
36 37 import neboard
37 38
38 39
39 40 BAN_REASON_SPAM = 'Autoban: spam bot'
40 41 MODE_GALLERY = 'gallery'
41 42 MODE_NORMAL = 'normal'
42 43
44 DEFAULT_PAGE = 1
43 45
44 def index(request, page=0):
46
47 def index(request, page=DEFAULT_PAGE):
45 48 context = _init_default_context(request)
46 49
47 50 if utils.need_include_captcha(request):
48 51 threadFormClass = ThreadCaptchaForm
49 52 kwargs = {'request': request}
50 53 else:
51 54 threadFormClass = ThreadForm
52 55 kwargs = {}
53 56
54 57 if request.method == 'POST':
55 58 form = threadFormClass(request.POST, request.FILES,
56 59 error_class=PlainErrorList, **kwargs)
57 60 form.session = request.session
58 61
59 62 if form.is_valid():
60 63 return _new_post(request, form)
61 64 if form.need_to_ban:
62 65 # Ban user because he is suspected to be a bot
63 66 _ban_current_user(request)
64 67 else:
65 68 form = threadFormClass(error_class=PlainErrorList, **kwargs)
66 69
67 70 threads = []
68 71 for thread_to_show in Post.objects.get_threads(page=int(page)):
69 72 threads.append(_get_template_thread(thread_to_show))
70 73
71 74 # TODO Make this generic for tag and threads list pages
72 75 context['threads'] = None if len(threads) == 0 else threads
73 76 context['form'] = form
74 context['current_page'] = int(page)
75 77
76 page_count = Post.objects.get_thread_page_count()
77 context['pages'] = range(page_count) if page_count > 0 else [0]
78 page = int(page)
79 if page < page_count - 1:
80 context['next_page'] = str(page + 1)
81 if page > 0:
82 context['prev_page'] = str(page - 1)
78 paginator = Paginator(Thread.objects.filter(archived=False),
79 neboard.settings.THREADS_PER_PAGE)
80 _get_page_context(paginator, context, page)
83 81
84 82 return render(request, 'boards/posting_general.html',
85 83 context)
86 84
87 85
88 def archive(request, page=0):
86 def archive(request, page=DEFAULT_PAGE):
89 87 context = _init_default_context(request)
90 88
91 89 threads = []
92 90 for thread_to_show in Post.objects.get_threads(page=int(page),
93 91 archived=True):
94 92 threads.append(_get_template_thread(thread_to_show))
95 93
96 94 context['threads'] = threads
97 context['current_page'] = int(page)
98 95
99 page_count = Post.objects.get_thread_page_count(archived=True)
100 context['pages'] = range(page_count) if page_count > 0 else [0]
101 page = int(page)
102 if page < page_count - 1:
103 context['next_page'] = str(page + 1)
104 if page > 0:
105 context['prev_page'] = str(page - 1)
96 paginator = Paginator(Thread.objects.filter(archived=True),
97 neboard.settings.THREADS_PER_PAGE)
98 _get_page_context(paginator, context, page)
106 99
107 100 return render(request, 'boards/archive.html', context)
108 101
109 102
110 103 @transaction.atomic
111 104 def _new_post(request, form, opening_post=None):
112 105 """Add a new post (in thread or as a reply)."""
113 106
114 107 ip = get_client_ip(request)
115 108 is_banned = Ban.objects.filter(ip=ip).exists()
116 109
117 110 if is_banned:
118 111 return redirect(you_are_banned)
119 112
120 113 data = form.cleaned_data
121 114
122 115 title = data['title']
123 116 text = data['text']
124 117
125 118 text = _remove_invalid_links(text)
126 119
127 120 if 'image' in data.keys():
128 121 image = data['image']
129 122 else:
130 123 image = None
131 124
132 125 tags = []
133 126
134 127 if not opening_post:
135 128 tag_strings = data['tags']
136 129
137 130 if tag_strings:
138 131 tag_strings = tag_strings.split(' ')
139 132 for tag_name in tag_strings:
140 133 tag_name = string.lower(tag_name.strip())
141 134 if len(tag_name) > 0:
142 135 tag, created = Tag.objects.get_or_create(name=tag_name)
143 136 tags.append(tag)
144 137 post_thread = None
145 138 else:
146 139 post_thread = opening_post.thread_new
147 140
148 141 post = Post.objects.create_post(title=title, text=text, ip=ip,
149 142 thread=post_thread, image=image,
150 143 tags=tags, user=_get_user(request))
151 144
152 145 thread_to_show = (opening_post.id if opening_post else post.id)
153 146
154 147 if opening_post:
155 148 return redirect(reverse(thread, kwargs={'post_id': thread_to_show}) +
156 149 '#' + str(post.id))
157 150 else:
158 151 return redirect(thread, post_id=thread_to_show)
159 152
160 153
161 def tag(request, tag_name, page=0):
154 def tag(request, tag_name, page=DEFAULT_PAGE):
162 155 """
163 156 Get all tag threads. Threads are split in pages, so some page is
164 requested. Default page is 0.
157 requested.
165 158 """
166 159
167 160 tag = get_object_or_404(Tag, name=tag_name)
168 161 threads = []
169 162 for thread_to_show in Post.objects.get_threads(page=int(page), tag=tag):
170 163 threads.append(_get_template_thread(thread_to_show))
171 164
172 165 if request.method == 'POST':
173 166 form = ThreadForm(request.POST, request.FILES,
174 167 error_class=PlainErrorList)
175 168 form.session = request.session
176 169
177 170 if form.is_valid():
178 171 return _new_post(request, form)
179 172 if form.need_to_ban:
180 173 # Ban user because he is suspected to be a bot
181 174 _ban_current_user(request)
182 175 else:
183 176 form = forms.ThreadForm(initial={'tags': tag_name},
184 177 error_class=PlainErrorList)
185 178
186 179 context = _init_default_context(request)
187 180 context['threads'] = None if len(threads) == 0 else threads
188 181 context['tag'] = tag
189 context['current_page'] = int(page)
190 182
191 page_count = Post.objects.get_thread_page_count(tag=tag)
192 context['pages'] = range(page_count)
193 page = int(page)
194 if page < page_count - 1:
195 context['next_page'] = str(page + 1)
196 if page > 0:
197 context['prev_page'] = str(page - 1)
183 paginator = Paginator(Post.objects.get_threads(tag=tag),
184 neboard.settings.THREADS_PER_PAGE)
185 _get_page_context(paginator, context, page)
198 186
199 187 context['form'] = form
200 188
201 189 return render(request, 'boards/posting_general.html',
202 190 context)
203 191
204 192
205 193 def thread(request, post_id, mode=MODE_NORMAL):
206 194 """Get all thread posts"""
207 195
208 196 if utils.need_include_captcha(request):
209 197 postFormClass = PostCaptchaForm
210 198 kwargs = {'request': request}
211 199 else:
212 200 postFormClass = PostForm
213 201 kwargs = {}
214 202
215 203 opening_post = get_object_or_404(Post, id=post_id)
216 204
217 205 # If this is not OP, don't show it as it is
218 206 if not opening_post.is_opening():
219 207 raise Http404
220 208
221 209 if request.method == 'POST' and not opening_post.thread_new.archived:
222 210 form = postFormClass(request.POST, request.FILES,
223 211 error_class=PlainErrorList, **kwargs)
224 212 form.session = request.session
225 213
226 214 if form.is_valid():
227 215 return _new_post(request, form, opening_post)
228 216 if form.need_to_ban:
229 217 # Ban user because he is suspected to be a bot
230 218 _ban_current_user(request)
231 219 else:
232 220 form = postFormClass(error_class=PlainErrorList, **kwargs)
233 221
234 222 thread_to_show = opening_post.thread_new
235 223
236 224 context = _init_default_context(request)
237 225
238 226 posts = thread_to_show.get_replies()
239 227 context['form'] = form
240 228 context["last_update"] = _datetime_to_epoch(thread_to_show.last_edit_time)
241 229 context["thread"] = thread_to_show
242 230
243 231 if MODE_NORMAL == mode:
244 232 context['bumpable'] = thread_to_show.can_bump()
245 233 if context['bumpable']:
246 234 context['posts_left'] = neboard.settings.MAX_POSTS_PER_THREAD - posts \
247 235 .count()
248 236 context['bumplimit_progress'] = str(
249 237 float(context['posts_left']) /
250 238 neboard.settings.MAX_POSTS_PER_THREAD * 100)
251 239
252 240 context['posts'] = posts
253 241
254 242 document = 'boards/thread.html'
255 243 elif MODE_GALLERY == mode:
256 244 context['posts'] = posts.filter(image_width__gt=0)
257 245
258 246 document = 'boards/thread_gallery.html'
259 247 else:
260 248 raise Http404
261 249
262 250 return render(request, document, context)
263 251
264 252
265 253 def login(request):
266 254 """Log in with user id"""
267 255
268 256 context = _init_default_context(request)
269 257
270 258 if request.method == 'POST':
271 259 form = LoginForm(request.POST, request.FILES,
272 260 error_class=PlainErrorList)
273 261 form.session = request.session
274 262
275 263 if form.is_valid():
276 264 user = User.objects.get(user_id=form.cleaned_data['user_id'])
277 265 request.session['user_id'] = user.id
278 266 return redirect(index)
279 267
280 268 else:
281 269 form = LoginForm()
282 270
283 271 context['form'] = form
284 272
285 273 return render(request, 'boards/login.html', context)
286 274
287 275
288 276 def settings(request):
289 277 """User's settings"""
290 278
291 279 context = _init_default_context(request)
292 280 user = _get_user(request)
293 281 is_moderator = user.is_moderator()
294 282
295 283 if request.method == 'POST':
296 284 with transaction.atomic():
297 285 if is_moderator:
298 286 form = ModeratorSettingsForm(request.POST,
299 287 error_class=PlainErrorList)
300 288 else:
301 289 form = SettingsForm(request.POST, error_class=PlainErrorList)
302 290
303 291 if form.is_valid():
304 292 selected_theme = form.cleaned_data['theme']
305 293
306 294 user.save_setting('theme', selected_theme)
307 295
308 296 if is_moderator:
309 297 moderate = form.cleaned_data['moderate']
310 298 user.save_setting(SETTING_MODERATE, moderate)
311 299
312 300 return redirect(settings)
313 301 else:
314 302 selected_theme = _get_theme(request)
315 303
316 304 if is_moderator:
317 305 form = ModeratorSettingsForm(initial={'theme': selected_theme,
318 306 'moderate': context['moderator']},
319 307 error_class=PlainErrorList)
320 308 else:
321 309 form = SettingsForm(initial={'theme': selected_theme},
322 310 error_class=PlainErrorList)
323 311
324 312 context['form'] = form
325 313
326 314 return render(request, 'boards/settings.html', context)
327 315
328 316
329 317 def all_tags(request):
330 318 """All tags list"""
331 319
332 320 context = _init_default_context(request)
333 321 context['all_tags'] = Tag.objects.get_not_empty_tags()
334 322
335 323 return render(request, 'boards/tags.html', context)
336 324
337 325
338 326 def jump_to_post(request, post_id):
339 327 """Determine thread in which the requested post is and open it's page"""
340 328
341 329 post = get_object_or_404(Post, id=post_id)
342 330
343 331 if not post.thread:
344 332 return redirect(thread, post_id=post.id)
345 333 else:
346 334 return redirect(reverse(thread, kwargs={'post_id': post.thread.id})
347 335 + '#' + str(post.id))
348 336
349 337
350 338 def authors(request):
351 339 """Show authors list"""
352 340
353 341 context = _init_default_context(request)
354 342 context['authors'] = boards.authors.authors
355 343
356 344 return render(request, 'boards/authors.html', context)
357 345
358 346
359 347 @transaction.atomic
360 348 def delete(request, post_id):
361 349 """Delete post"""
362 350
363 351 user = _get_user(request)
364 352 post = get_object_or_404(Post, id=post_id)
365 353
366 354 if user.is_moderator():
367 355 # TODO Show confirmation page before deletion
368 356 Post.objects.delete_post(post)
369 357
370 358 if not post.thread:
371 359 return _redirect_to_next(request)
372 360 else:
373 361 return redirect(thread, post_id=post.thread.id)
374 362
375 363
376 364 @transaction.atomic
377 365 def ban(request, post_id):
378 366 """Ban user"""
379 367
380 368 user = _get_user(request)
381 369 post = get_object_or_404(Post, id=post_id)
382 370
383 371 if user.is_moderator():
384 372 # TODO Show confirmation page before ban
385 373 ban, created = Ban.objects.get_or_create(ip=post.poster_ip)
386 374 if created:
387 375 ban.reason = 'Banned for post ' + str(post_id)
388 376 ban.save()
389 377
390 378 return _redirect_to_next(request)
391 379
392 380
393 381 def you_are_banned(request):
394 382 """Show the page that notifies that user is banned"""
395 383
396 384 context = _init_default_context(request)
397 385
398 386 ban = get_object_or_404(Ban, ip=utils.get_client_ip(request))
399 387 context['ban_reason'] = ban.reason
400 388 return render(request, 'boards/staticpages/banned.html', context)
401 389
402 390
403 391 def page_404(request):
404 392 """Show page 404 (not found error)"""
405 393
406 394 context = _init_default_context(request)
407 395 return render(request, 'boards/404.html', context)
408 396
409 397
410 398 @transaction.atomic
411 399 def tag_subscribe(request, tag_name):
412 400 """Add tag to favorites"""
413 401
414 402 user = _get_user(request)
415 403 tag = get_object_or_404(Tag, name=tag_name)
416 404
417 405 if not tag in user.fav_tags.all():
418 406 user.add_tag(tag)
419 407
420 408 return _redirect_to_next(request)
421 409
422 410
423 411 @transaction.atomic
424 412 def tag_unsubscribe(request, tag_name):
425 413 """Remove tag from favorites"""
426 414
427 415 user = _get_user(request)
428 416 tag = get_object_or_404(Tag, name=tag_name)
429 417
430 418 if tag in user.fav_tags.all():
431 419 user.remove_tag(tag)
432 420
433 421 return _redirect_to_next(request)
434 422
435 423
436 424 def static_page(request, name):
437 425 """Show a static page that needs only tags list and a CSS"""
438 426
439 427 context = _init_default_context(request)
440 428 return render(request, 'boards/staticpages/' + name + '.html', context)
441 429
442 430
443 431 def api_get_post(request, post_id):
444 432 """
445 433 Get the JSON of a post. This can be
446 434 used as and API for external clients.
447 435 """
448 436
449 437 post = get_object_or_404(Post, id=post_id)
450 438
451 439 json = serializers.serialize("json", [post], fields=(
452 440 "pub_time", "_text_rendered", "title", "text", "image",
453 441 "image_width", "image_height", "replies", "tags"
454 442 ))
455 443
456 444 return HttpResponse(content=json)
457 445
458 446
459 447 @cache_page(86400)
460 448 def cached_js_catalog(request, domain='djangojs', packages=None):
461 449 return javascript_catalog(request, domain, packages)
462 450
463 451
464 452 def _get_theme(request, user=None):
465 453 """Get user's CSS theme"""
466 454
467 455 if not user:
468 456 user = _get_user(request)
469 457 theme = user.get_setting('theme')
470 458 if not theme:
471 459 theme = neboard.settings.DEFAULT_THEME
472 460
473 461 return theme
474 462
475 463
476 464 def _init_default_context(request):
477 465 """Create context with default values that are used in most views"""
478 466
479 467 context = RequestContext(request)
480 468
481 469 user = _get_user(request)
482 470 context['user'] = user
483 471 context['tags'] = user.get_sorted_fav_tags()
484 472 context['posts_per_day'] = float(Post.objects.get_posts_per_day())
485 473
486 474 theme = _get_theme(request, user)
487 475 context['theme'] = theme
488 476 context['theme_css'] = 'css/' + theme + '/base_page.css'
489 477
490 478 # This shows the moderator panel
491 479 moderate = user.get_setting(SETTING_MODERATE)
492 480 if moderate == 'True':
493 481 context['moderator'] = user.is_moderator()
494 482 else:
495 483 context['moderator'] = False
496 484
497 485 return context
498 486
499 487
500 488 def _get_user(request):
501 489 """
502 490 Get current user from the session. If the user does not exist, create
503 491 a new one.
504 492 """
505 493
506 494 session = request.session
507 495 if not 'user_id' in session:
508 496 request.session.save()
509 497
510 498 md5 = hashlib.md5()
511 499 md5.update(session.session_key)
512 500 new_id = md5.hexdigest()
513 501
514 502 while User.objects.filter(user_id=new_id).exists():
515 503 md5.update(str(timezone.now()))
516 504 new_id = md5.hexdigest()
517 505
518 506 time_now = timezone.now()
519 507 user = User.objects.create(user_id=new_id, rank=RANK_USER,
520 508 registration_time=time_now)
521 509
522 510 _delete_old_users()
523 511
524 512 session['user_id'] = user.id
525 513 else:
526 514 user = User.objects.get(id=session['user_id'])
527 515
528 516 return user
529 517
530 518
531 519 def _redirect_to_next(request):
532 520 """
533 521 If a 'next' parameter was specified, redirect to the next page. This is
534 522 used when the user is required to return to some page after the current
535 523 view has finished its work.
536 524 """
537 525
538 526 if 'next' in request.GET:
539 527 next_page = request.GET['next']
540 528 return HttpResponseRedirect(next_page)
541 529 else:
542 530 return redirect(index)
543 531
544 532
545 533 @transaction.atomic
546 534 def _ban_current_user(request):
547 535 """Add current user to the IP ban list"""
548 536
549 537 ip = utils.get_client_ip(request)
550 538 ban, created = Ban.objects.get_or_create(ip=ip)
551 539 if created:
552 540 ban.can_read = False
553 541 ban.reason = BAN_REASON_SPAM
554 542 ban.save()
555 543
556 544
557 545 def _remove_invalid_links(text):
558 546 """
559 547 Replace invalid links in posts so that they won't be parsed.
560 548 Invalid links are links to non-existent posts
561 549 """
562 550
563 551 for reply_number in re.finditer(REGEX_REPLY, text):
564 552 post_id = reply_number.group(1)
565 553 post = Post.objects.filter(id=post_id)
566 554 if not post.exists():
567 555 text = string.replace(text, '>>' + post_id, post_id)
568 556
569 557 return text
570 558
571 559
572 560 def _datetime_to_epoch(datetime):
573 561 return int(time.mktime(timezone.localtime(
574 562 datetime,timezone.get_current_timezone()).timetuple())
575 563 * 1000000 + datetime.microsecond)
576 564
577 565
578 566 def _get_template_thread(thread_to_show):
579 567 """Get template values for thread"""
580 568
581 569 last_replies = thread_to_show.get_last_replies()
582 570 skipped_replies_count = thread_to_show.get_replies().count() \
583 571 - len(last_replies) - 1
584 572 return {
585 573 'thread': thread_to_show,
586 574 'op': thread_to_show.get_replies()[0],
587 575 'bumpable': thread_to_show.can_bump(),
588 576 'last_replies': last_replies,
589 577 'skipped_replies': skipped_replies_count,
590 578 }
591 579
592 580
593 581 def _delete_old_users():
594 582 """
595 583 Delete users with no favorite tags and posted messages. These can be spam
596 584 bots or just old user accounts
597 585 """
598 586
599 587 old_registration_date = datetime.now().date() - timedelta(OLD_USER_AGE_DAYS)
600 588
601 589 for user in User.objects.annotate(tags_count=Count('fav_tags')).filter(
602 590 tags_count=0).filter(registration_time__lt=old_registration_date):
603 591 if not Post.objects.filter(user=user).exists():
604 592 user.delete()
593
594
595 def _get_page_context(paginator, context, page):
596 """
597 Get pagination context variables
598 """
599
600 context['paginator'] = paginator
601 context['current_page'] = paginator.page(int(page))
General Comments 0
You need to be logged in to leave comments. Login now