##// END OF EJS Templates
Moved delete view to class-based views
neko259 -
r552:39f0b88d 1.7-dev
parent child Browse files
Show More
@@ -0,0 +1,25 b''
1 from django.shortcuts import redirect, get_object_or_404
2 from django.db import transaction
3
4 from boards.views.base import BaseBoardView
5 from boards.views.redirect_next_mixin import RedirectNextMixin
6 from boards.models import Post
7
8 class DeletePostView(BaseBoardView, RedirectNextMixin):
9
10 @transaction.atomic
11 def get(self, request, post_id):
12 user = self._get_user(request)
13 post = get_object_or_404(Post, id=post_id)
14
15 opening_post = post.is_opening()
16
17 if user.is_moderator():
18 # TODO Show confirmation page before deletion
19 Post.objects.delete_post(post)
20
21 if not opening_post:
22 thread = post.thread_new
23 return redirect('thread', post_id=thread.get_opening_post().id)
24 else:
25 return self.redirect_to_next(request)
@@ -0,0 +1,17 b''
1 from django.shortcuts import redirect
2 from django.http import HttpResponseRedirect
3
4 class RedirectNextMixin:
5
6 def redirect_to_next(self, request):
7 """
8 If a 'next' parameter was specified, redirect to the next page. This
9 is used when the user is required to return to some page after the
10 current view has finished its work.
11 """
12
13 if 'next' in request.GET:
14 next_page = request.GET['next']
15 return HttpResponseRedirect(next_page)
16 else:
17 return redirect('index')
@@ -1,395 +1,393 b''
1 from datetime import datetime, timedelta
1 from datetime import datetime, timedelta
2 from datetime import time as dtime
2 from datetime import time as dtime
3 import os
3 import os
4 from random import random
4 from random import random
5 import time
5 import time
6 import math
6 import math
7 import re
7 import re
8 import hashlib
8 import hashlib
9
9
10 from django.core.cache import cache
10 from django.core.cache import cache
11 from django.core.paginator import Paginator
11 from django.core.paginator import Paginator
12
12
13 from django.db import models
13 from django.db import models
14 from django.http import Http404
14 from django.http import Http404
15 from django.utils import timezone
15 from django.utils import timezone
16 from markupfield.fields import MarkupField
16 from markupfield.fields import MarkupField
17
17
18 from neboard import settings
18 from neboard import settings
19 from boards import thumbs
19 from boards import thumbs
20
20
21 MAX_TITLE_LENGTH = 50
21 MAX_TITLE_LENGTH = 50
22
22
23 APP_LABEL_BOARDS = 'boards'
23 APP_LABEL_BOARDS = 'boards'
24
24
25 CACHE_KEY_PPD = 'ppd'
25 CACHE_KEY_PPD = 'ppd'
26
26
27 POSTS_PER_DAY_RANGE = range(7)
27 POSTS_PER_DAY_RANGE = range(7)
28
28
29 BAN_REASON_AUTO = 'Auto'
29 BAN_REASON_AUTO = 'Auto'
30
30
31 IMAGE_THUMB_SIZE = (200, 150)
31 IMAGE_THUMB_SIZE = (200, 150)
32
32
33 TITLE_MAX_LENGTH = 50
33 TITLE_MAX_LENGTH = 50
34
34
35 DEFAULT_MARKUP_TYPE = 'markdown'
35 DEFAULT_MARKUP_TYPE = 'markdown'
36
36
37 NO_PARENT = -1
37 NO_PARENT = -1
38 NO_IP = '0.0.0.0'
38 NO_IP = '0.0.0.0'
39 UNKNOWN_UA = ''
39 UNKNOWN_UA = ''
40 ALL_PAGES = -1
40 ALL_PAGES = -1
41 IMAGES_DIRECTORY = 'images/'
41 IMAGES_DIRECTORY = 'images/'
42 FILE_EXTENSION_DELIMITER = '.'
42 FILE_EXTENSION_DELIMITER = '.'
43
43
44 SETTING_MODERATE = "moderate"
44 SETTING_MODERATE = "moderate"
45
45
46 REGEX_REPLY = re.compile('>>(\d+)')
46 REGEX_REPLY = re.compile('>>(\d+)')
47
47
48
48
49 class PostManager(models.Manager):
49 class PostManager(models.Manager):
50
50
51 def create_post(self, title, text, image=None, thread=None,
51 def create_post(self, title, text, image=None, thread=None,
52 ip=NO_IP, tags=None, user=None):
52 ip=NO_IP, tags=None, user=None):
53 """
53 """
54 Create new post
54 Create new post
55 """
55 """
56
56
57 posting_time = timezone.now()
57 posting_time = timezone.now()
58 if not thread:
58 if not thread:
59 thread = Thread.objects.create(bump_time=posting_time,
59 thread = Thread.objects.create(bump_time=posting_time,
60 last_edit_time=posting_time)
60 last_edit_time=posting_time)
61 else:
61 else:
62 thread.bump()
62 thread.bump()
63 thread.last_edit_time = posting_time
63 thread.last_edit_time = posting_time
64 thread.save()
64 thread.save()
65
65
66 post = self.create(title=title,
66 post = self.create(title=title,
67 text=text,
67 text=text,
68 pub_time=posting_time,
68 pub_time=posting_time,
69 thread_new=thread,
69 thread_new=thread,
70 image=image,
70 image=image,
71 poster_ip=ip,
71 poster_ip=ip,
72 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
72 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
73 # last!
73 # last!
74 last_edit_time=posting_time,
74 last_edit_time=posting_time,
75 user=user)
75 user=user)
76
76
77 thread.replies.add(post)
77 thread.replies.add(post)
78 if tags:
78 if tags:
79 linked_tags = []
79 linked_tags = []
80 for tag in tags:
80 for tag in tags:
81 tag_linked_tags = tag.get_linked_tags()
81 tag_linked_tags = tag.get_linked_tags()
82 if len(tag_linked_tags) > 0:
82 if len(tag_linked_tags) > 0:
83 linked_tags.extend(tag_linked_tags)
83 linked_tags.extend(tag_linked_tags)
84
84
85 tags.extend(linked_tags)
85 tags.extend(linked_tags)
86 map(thread.add_tag, tags)
86 map(thread.add_tag, tags)
87
87
88 self._delete_old_threads()
88 self._delete_old_threads()
89 self.connect_replies(post)
89 self.connect_replies(post)
90
90
91 return post
91 return post
92
92
93 def delete_post(self, post):
93 def delete_post(self, post):
94 """
94 """
95 Delete post and update or delete its thread
95 Delete post and update or delete its thread
96 """
96 """
97
97
98 thread = post.thread_new
98 thread = post.thread_new
99
99
100 if thread.get_opening_post() == self:
100 if post.is_opening():
101 thread.replies.delete()
101 thread.delete_with_posts()
102
103 thread.delete()
104 else:
102 else:
105 thread.last_edit_time = timezone.now()
103 thread.last_edit_time = timezone.now()
106 thread.save()
104 thread.save()
107
105
108 post.delete()
106 post.delete()
109
107
110 def delete_posts_by_ip(self, ip):
108 def delete_posts_by_ip(self, ip):
111 """
109 """
112 Delete all posts of the author with same IP
110 Delete all posts of the author with same IP
113 """
111 """
114
112
115 posts = self.filter(poster_ip=ip)
113 posts = self.filter(poster_ip=ip)
116 map(self.delete_post, posts)
114 map(self.delete_post, posts)
117
115
118 # TODO This method may not be needed any more, because django's paginator
116 # TODO This method may not be needed any more, because django's paginator
119 # is used
117 # is used
120 def get_threads(self, tag=None, page=ALL_PAGES,
118 def get_threads(self, tag=None, page=ALL_PAGES,
121 order_by='-bump_time', archived=False):
119 order_by='-bump_time', archived=False):
122 if tag:
120 if tag:
123 threads = tag.threads
121 threads = tag.threads
124
122
125 if not threads.exists():
123 if not threads.exists():
126 raise Http404
124 raise Http404
127 else:
125 else:
128 threads = Thread.objects.all()
126 threads = Thread.objects.all()
129
127
130 threads = threads.filter(archived=archived).order_by(order_by)
128 threads = threads.filter(archived=archived).order_by(order_by)
131
129
132 if page != ALL_PAGES:
130 if page != ALL_PAGES:
133 threads = Paginator(threads, settings.THREADS_PER_PAGE).page(
131 threads = Paginator(threads, settings.THREADS_PER_PAGE).page(
134 page).object_list
132 page).object_list
135
133
136 return threads
134 return threads
137
135
138 # TODO Move this method to thread manager
136 # TODO Move this method to thread manager
139 def _delete_old_threads(self):
137 def _delete_old_threads(self):
140 """
138 """
141 Preserves maximum thread count. If there are too many threads,
139 Preserves maximum thread count. If there are too many threads,
142 archive the old ones.
140 archive the old ones.
143 """
141 """
144
142
145 threads = self.get_threads()
143 threads = self.get_threads()
146 thread_count = threads.count()
144 thread_count = threads.count()
147
145
148 if thread_count > settings.MAX_THREAD_COUNT:
146 if thread_count > settings.MAX_THREAD_COUNT:
149 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
147 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
150 old_threads = threads[thread_count - num_threads_to_delete:]
148 old_threads = threads[thread_count - num_threads_to_delete:]
151
149
152 for thread in old_threads:
150 for thread in old_threads:
153 thread.archived = True
151 thread.archived = True
154 thread.last_edit_time = timezone.now()
152 thread.last_edit_time = timezone.now()
155 thread.save()
153 thread.save()
156
154
157 def connect_replies(self, post):
155 def connect_replies(self, post):
158 """
156 """
159 Connect replies to a post to show them as a reflink map
157 Connect replies to a post to show them as a reflink map
160 """
158 """
161
159
162 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
160 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
163 post_id = reply_number.group(1)
161 post_id = reply_number.group(1)
164 ref_post = self.filter(id=post_id)
162 ref_post = self.filter(id=post_id)
165 if ref_post.count() > 0:
163 if ref_post.count() > 0:
166 referenced_post = ref_post[0]
164 referenced_post = ref_post[0]
167 referenced_post.referenced_posts.add(post)
165 referenced_post.referenced_posts.add(post)
168 referenced_post.last_edit_time = post.pub_time
166 referenced_post.last_edit_time = post.pub_time
169 referenced_post.save()
167 referenced_post.save()
170
168
171 referenced_thread = referenced_post.thread_new
169 referenced_thread = referenced_post.thread_new
172 referenced_thread.last_edit_time = post.pub_time
170 referenced_thread.last_edit_time = post.pub_time
173 referenced_thread.save()
171 referenced_thread.save()
174
172
175 def get_posts_per_day(self):
173 def get_posts_per_day(self):
176 """
174 """
177 Get average count of posts per day for the last 7 days
175 Get average count of posts per day for the last 7 days
178 """
176 """
179
177
180 today = datetime.now().date()
178 today = datetime.now().date()
181 ppd = cache.get(CACHE_KEY_PPD + str(today))
179 ppd = cache.get(CACHE_KEY_PPD + str(today))
182 if ppd:
180 if ppd:
183 return ppd
181 return ppd
184
182
185 posts_per_days = []
183 posts_per_days = []
186 for i in POSTS_PER_DAY_RANGE:
184 for i in POSTS_PER_DAY_RANGE:
187 day_end = today - timedelta(i + 1)
185 day_end = today - timedelta(i + 1)
188 day_start = today - timedelta(i + 2)
186 day_start = today - timedelta(i + 2)
189
187
190 day_time_start = timezone.make_aware(datetime.combine(day_start,
188 day_time_start = timezone.make_aware(datetime.combine(day_start,
191 dtime()), timezone.get_current_timezone())
189 dtime()), timezone.get_current_timezone())
192 day_time_end = timezone.make_aware(datetime.combine(day_end,
190 day_time_end = timezone.make_aware(datetime.combine(day_end,
193 dtime()), timezone.get_current_timezone())
191 dtime()), timezone.get_current_timezone())
194
192
195 posts_per_days.append(float(self.filter(
193 posts_per_days.append(float(self.filter(
196 pub_time__lte=day_time_end,
194 pub_time__lte=day_time_end,
197 pub_time__gte=day_time_start).count()))
195 pub_time__gte=day_time_start).count()))
198
196
199 ppd = (sum(posts_per_day for posts_per_day in posts_per_days) /
197 ppd = (sum(posts_per_day for posts_per_day in posts_per_days) /
200 len(posts_per_days))
198 len(posts_per_days))
201 cache.set(CACHE_KEY_PPD, ppd)
199 cache.set(CACHE_KEY_PPD, ppd)
202 return ppd
200 return ppd
203
201
204
202
205 class Post(models.Model):
203 class Post(models.Model):
206 """A post is a message."""
204 """A post is a message."""
207
205
208 objects = PostManager()
206 objects = PostManager()
209
207
210 class Meta:
208 class Meta:
211 app_label = APP_LABEL_BOARDS
209 app_label = APP_LABEL_BOARDS
212
210
213 # TODO Save original file name to some field
211 # TODO Save original file name to some field
214 def _update_image_filename(self, filename):
212 def _update_image_filename(self, filename):
215 """Get unique image filename"""
213 """Get unique image filename"""
216
214
217 path = IMAGES_DIRECTORY
215 path = IMAGES_DIRECTORY
218 new_name = str(int(time.mktime(time.gmtime())))
216 new_name = str(int(time.mktime(time.gmtime())))
219 new_name += str(int(random() * 1000))
217 new_name += str(int(random() * 1000))
220 new_name += FILE_EXTENSION_DELIMITER
218 new_name += FILE_EXTENSION_DELIMITER
221 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
219 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
222
220
223 return os.path.join(path, new_name)
221 return os.path.join(path, new_name)
224
222
225 title = models.CharField(max_length=TITLE_MAX_LENGTH)
223 title = models.CharField(max_length=TITLE_MAX_LENGTH)
226 pub_time = models.DateTimeField()
224 pub_time = models.DateTimeField()
227 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
225 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
228 escape_html=False)
226 escape_html=False)
229
227
230 image_width = models.IntegerField(default=0)
228 image_width = models.IntegerField(default=0)
231 image_height = models.IntegerField(default=0)
229 image_height = models.IntegerField(default=0)
232
230
233 image_pre_width = models.IntegerField(default=0)
231 image_pre_width = models.IntegerField(default=0)
234 image_pre_height = models.IntegerField(default=0)
232 image_pre_height = models.IntegerField(default=0)
235
233
236 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
234 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
237 blank=True, sizes=(IMAGE_THUMB_SIZE,),
235 blank=True, sizes=(IMAGE_THUMB_SIZE,),
238 width_field='image_width',
236 width_field='image_width',
239 height_field='image_height',
237 height_field='image_height',
240 preview_width_field='image_pre_width',
238 preview_width_field='image_pre_width',
241 preview_height_field='image_pre_height')
239 preview_height_field='image_pre_height')
242 image_hash = models.CharField(max_length=36)
240 image_hash = models.CharField(max_length=36)
243
241
244 poster_ip = models.GenericIPAddressField()
242 poster_ip = models.GenericIPAddressField()
245 poster_user_agent = models.TextField()
243 poster_user_agent = models.TextField()
246
244
247 thread = models.ForeignKey('Post', null=True, default=None)
245 thread = models.ForeignKey('Post', null=True, default=None)
248 thread_new = models.ForeignKey('Thread', null=True, default=None)
246 thread_new = models.ForeignKey('Thread', null=True, default=None)
249 last_edit_time = models.DateTimeField()
247 last_edit_time = models.DateTimeField()
250 user = models.ForeignKey('User', null=True, default=None)
248 user = models.ForeignKey('User', null=True, default=None)
251
249
252 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
250 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
253 null=True,
251 null=True,
254 blank=True, related_name='rfp+')
252 blank=True, related_name='rfp+')
255
253
256 def __unicode__(self):
254 def __unicode__(self):
257 return '#' + str(self.id) + ' ' + self.title + ' (' + \
255 return '#' + str(self.id) + ' ' + self.title + ' (' + \
258 self.text.raw[:50] + ')'
256 self.text.raw[:50] + ')'
259
257
260 def get_title(self):
258 def get_title(self):
261 title = self.title
259 title = self.title
262 if len(title) == 0:
260 if len(title) == 0:
263 title = self.text.rendered
261 title = self.text.rendered
264
262
265 return title
263 return title
266
264
267 def get_sorted_referenced_posts(self):
265 def get_sorted_referenced_posts(self):
268 return self.referenced_posts.order_by('id')
266 return self.referenced_posts.order_by('id')
269
267
270 def is_referenced(self):
268 def is_referenced(self):
271 return self.referenced_posts.all().exists()
269 return self.referenced_posts.all().exists()
272
270
273 def is_opening(self):
271 def is_opening(self):
274 return self.thread_new.get_replies()[0] == self
272 return self.thread_new.get_replies()[0] == self
275
273
276 def save(self, *args, **kwargs):
274 def save(self, *args, **kwargs):
277 """
275 """
278 Save the model and compute the image hash
276 Save the model and compute the image hash
279 """
277 """
280
278
281 if not self.pk and self.image:
279 if not self.pk and self.image:
282 md5 = hashlib.md5()
280 md5 = hashlib.md5()
283 for chunk in self.image.chunks():
281 for chunk in self.image.chunks():
284 md5.update(chunk)
282 md5.update(chunk)
285 self.image_hash = md5.hexdigest()
283 self.image_hash = md5.hexdigest()
286 super(Post, self).save(*args, **kwargs)
284 super(Post, self).save(*args, **kwargs)
287
285
288
286
289 class Thread(models.Model):
287 class Thread(models.Model):
290
288
291 class Meta:
289 class Meta:
292 app_label = APP_LABEL_BOARDS
290 app_label = APP_LABEL_BOARDS
293
291
294 tags = models.ManyToManyField('Tag')
292 tags = models.ManyToManyField('Tag')
295 bump_time = models.DateTimeField()
293 bump_time = models.DateTimeField()
296 last_edit_time = models.DateTimeField()
294 last_edit_time = models.DateTimeField()
297 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
295 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
298 blank=True, related_name='tre+')
296 blank=True, related_name='tre+')
299 archived = models.BooleanField(default=False)
297 archived = models.BooleanField(default=False)
300
298
301 def get_tags(self):
299 def get_tags(self):
302 """
300 """
303 Get a sorted tag list
301 Get a sorted tag list
304 """
302 """
305
303
306 return self.tags.order_by('name')
304 return self.tags.order_by('name')
307
305
308 def bump(self):
306 def bump(self):
309 """
307 """
310 Bump (move to up) thread
308 Bump (move to up) thread
311 """
309 """
312
310
313 if self.can_bump():
311 if self.can_bump():
314 self.bump_time = timezone.now()
312 self.bump_time = timezone.now()
315
313
316 def get_reply_count(self):
314 def get_reply_count(self):
317 return self.replies.count()
315 return self.replies.count()
318
316
319 def get_images_count(self):
317 def get_images_count(self):
320 return self.replies.filter(image_width__gt=0).count()
318 return self.replies.filter(image_width__gt=0).count()
321
319
322 def can_bump(self):
320 def can_bump(self):
323 """
321 """
324 Check if the thread can be bumped by replying
322 Check if the thread can be bumped by replying
325 """
323 """
326
324
327 if self.archived:
325 if self.archived:
328 return False
326 return False
329
327
330 post_count = self.get_reply_count()
328 post_count = self.get_reply_count()
331
329
332 return post_count < settings.MAX_POSTS_PER_THREAD
330 return post_count < settings.MAX_POSTS_PER_THREAD
333
331
334 def delete_with_posts(self):
332 def delete_with_posts(self):
335 """
333 """
336 Completely delete thread and all its posts
334 Completely delete thread and all its posts
337 """
335 """
338
336
339 if self.replies.count() > 0:
337 if self.replies.count() > 0:
340 self.replies.all().delete()
338 self.replies.all().delete()
341
339
342 self.delete()
340 self.delete()
343
341
344 def get_last_replies(self):
342 def get_last_replies(self):
345 """
343 """
346 Get last replies, not including opening post
344 Get last replies, not including opening post
347 """
345 """
348
346
349 if settings.LAST_REPLIES_COUNT > 0:
347 if settings.LAST_REPLIES_COUNT > 0:
350 reply_count = self.get_reply_count()
348 reply_count = self.get_reply_count()
351
349
352 if reply_count > 0:
350 if reply_count > 0:
353 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
351 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
354 reply_count - 1)
352 reply_count - 1)
355 last_replies = self.replies.all().order_by('pub_time')[
353 last_replies = self.replies.all().order_by('pub_time')[
356 reply_count - reply_count_to_show:]
354 reply_count - reply_count_to_show:]
357
355
358 return last_replies
356 return last_replies
359
357
360 def get_skipped_replies_count(self):
358 def get_skipped_replies_count(self):
361 last_replies = self.get_last_replies()
359 last_replies = self.get_last_replies()
362 return self.get_reply_count() - len(last_replies) - 1
360 return self.get_reply_count() - len(last_replies) - 1
363
361
364 def get_replies(self):
362 def get_replies(self):
365 """
363 """
366 Get sorted thread posts
364 Get sorted thread posts
367 """
365 """
368
366
369 return self.replies.all().order_by('pub_time')
367 return self.replies.all().order_by('pub_time')
370
368
371 def add_tag(self, tag):
369 def add_tag(self, tag):
372 """
370 """
373 Connect thread to a tag and tag to a thread
371 Connect thread to a tag and tag to a thread
374 """
372 """
375
373
376 self.tags.add(tag)
374 self.tags.add(tag)
377 tag.threads.add(self)
375 tag.threads.add(self)
378
376
379 def get_opening_post(self):
377 def get_opening_post(self):
380 """
378 """
381 Get first post of the thread
379 Get first post of the thread
382 """
380 """
383
381
384 return self.get_replies()[0]
382 return self.get_replies()[0]
385
383
386 def __unicode__(self):
384 def __unicode__(self):
387 return str(self.get_replies()[0].id)
385 return str(self.id)
388
386
389 def get_pub_time(self):
387 def get_pub_time(self):
390 """
388 """
391 Thread does not have its own pub time, so we need to get it from
389 Thread does not have its own pub time, so we need to get it from
392 the opening post
390 the opening post
393 """
391 """
394
392
395 return self.get_opening_post().pub_time
393 return self.get_opening_post().pub_time
@@ -1,96 +1,96 b''
1 {% load i18n %}
1 {% load i18n %}
2 {% load board %}
2 {% load board %}
3 {% load cache %}
3 {% load cache %}
4
4
5 {% get_current_language as LANGUAGE_CODE %}
5 {% get_current_language as LANGUAGE_CODE %}
6
6
7 {% cache 300 post post.id post.thread_new.last_edit_time truncated moderator LANGUAGE_CODE need_open_link %}
7 {% cache 300 post post.id post.thread_new.last_edit_time truncated moderator LANGUAGE_CODE need_open_link %}
8 {% spaceless %}
8 {% spaceless %}
9 {% with thread=post.thread_new %}
9 {% with thread=post.thread_new %}
10 {% if thread.archived %}
10 {% if thread.archived %}
11 <div class="post archive_post" id="{{ post.id }}">
11 <div class="post archive_post" id="{{ post.id }}">
12 {% elif thread.can_bump %}
12 {% elif thread.can_bump %}
13 <div class="post" id="{{ post.id }}">
13 <div class="post" id="{{ post.id }}">
14 {% else %}
14 {% else %}
15 <div class="post dead_post" id="{{ post.id }}">
15 <div class="post dead_post" id="{{ post.id }}">
16 {% endif %}
16 {% endif %}
17
17
18 {% if post.image %}
18 {% if post.image %}
19 <div class="image">
19 <div class="image">
20 <a
20 <a
21 class="thumb"
21 class="thumb"
22 href="{{ post.image.url }}"><img
22 href="{{ post.image.url }}"><img
23 src="{{ post.image.url_200x150 }}"
23 src="{{ post.image.url_200x150 }}"
24 alt="{{ post.id }}"
24 alt="{{ post.id }}"
25 width="{{ post.image_pre_width }}"
25 width="{{ post.image_pre_width }}"
26 height="{{ post.image_pre_height }}"
26 height="{{ post.image_pre_height }}"
27 data-width="{{ post.image_width }}"
27 data-width="{{ post.image_width }}"
28 data-height="{{ post.image_height }}"/>
28 data-height="{{ post.image_height }}"/>
29 </a>
29 </a>
30 </div>
30 </div>
31 {% endif %}
31 {% endif %}
32 <div class="message">
32 <div class="message">
33 <div class="post-info">
33 <div class="post-info">
34 <span class="title">{{ post.title }}</span>
34 <span class="title">{{ post.title }}</span>
35 <a class="post_id" href="{% post_url post.id %}">
35 <a class="post_id" href="{% post_url post.id %}">
36 ({{ post.id }}) </a>
36 ({{ post.id }}) </a>
37 [<span class="pub_time">{{ post.pub_time }}</span>]
37 [<span class="pub_time">{{ post.pub_time }}</span>]
38 {% if thread.archived %}
38 {% if thread.archived %}
39 β€” [{{ thread.bump_time }}]
39 β€” [{{ thread.bump_time }}]
40 {% endif %}
40 {% endif %}
41 {% if not truncated %}
41 {% if not truncated and not thread.archived%}
42 [<a href="#" onclick="javascript:addQuickReply('{{ post.id }}')
42 [<a href="#" onclick="javascript:addQuickReply('{{ post.id }}')
43 ; return false;">&gt;&gt;</a>]
43 ; return false;">&gt;&gt;</a>]
44 {% endif %}
44 {% endif %}
45 {% if post.is_opening and need_open_link %}
45 {% if post.is_opening and need_open_link %}
46 {% if post.thread_new.archived %}
46 {% if post.thread_new.archived %}
47 [<a class="link" href="{% url 'thread' post.id %}">{% trans "Open" %}</a>]
47 [<a class="link" href="{% url 'thread' post.id %}">{% trans "Open" %}</a>]
48 {% else %}
48 {% else %}
49 [<a class="link" href="{% url 'thread' post.id %}#form">{% trans "Reply" %}</a>]
49 [<a class="link" href="{% url 'thread' post.id %}#form">{% trans "Reply" %}</a>]
50 {% endif %}
50 {% endif %}
51 {% endif %}
51 {% endif %}
52
52
53 {% if moderator %}
53 {% if moderator %}
54 <span class="moderator_info">
54 <span class="moderator_info">
55 [<a href="{% url 'delete' post_id=post.id %}"
55 [<a href="{% url 'delete' post_id=post.id %}"
56 >{% trans 'Delete' %}</a>]
56 >{% trans 'Delete' %}</a>]
57 ({{ post.poster_ip }})
57 ({{ post.poster_ip }})
58 [<a href="{% url 'ban' post_id=post.id %}?next={{ request.path }}"
58 [<a href="{% url 'ban' post_id=post.id %}?next={{ request.path }}"
59 >{% trans 'Ban IP' %}</a>]
59 >{% trans 'Ban IP' %}</a>]
60 </span>
60 </span>
61 {% endif %}
61 {% endif %}
62 </div>
62 </div>
63 {% autoescape off %}
63 {% autoescape off %}
64 {% if truncated %}
64 {% if truncated %}
65 {{ post.text.rendered|truncatewords_html:50 }}
65 {{ post.text.rendered|truncatewords_html:50 }}
66 {% else %}
66 {% else %}
67 {{ post.text.rendered }}
67 {{ post.text.rendered }}
68 {% endif %}
68 {% endif %}
69 {% endautoescape %}
69 {% endautoescape %}
70 {% if post.is_referenced %}
70 {% if post.is_referenced %}
71 <div class="refmap">
71 <div class="refmap">
72 {% trans "Replies" %}:
72 {% trans "Replies" %}:
73 {% for ref_post in post.get_sorted_referenced_posts %}
73 {% for ref_post in post.get_sorted_referenced_posts %}
74 <a href="{% post_url ref_post.id %}">&gt;&gt;{{ ref_post.id }}</a
74 <a href="{% post_url ref_post.id %}">&gt;&gt;{{ ref_post.id }}</a
75 >{% if not forloop.last %},{% endif %}
75 >{% if not forloop.last %},{% endif %}
76 {% endfor %}
76 {% endfor %}
77 </div>
77 </div>
78 {% endif %}
78 {% endif %}
79 </div>
79 </div>
80 {% if post.is_opening and thread.tags.exists %}
80 {% if post.is_opening and thread.tags.exists %}
81 <div class="metadata">
81 <div class="metadata">
82 {% if post.is_opening and need_open_link %}
82 {% if post.is_opening and need_open_link %}
83 {{ thread.get_images_count }} {% trans 'images' %}.
83 {{ thread.get_images_count }} {% trans 'images' %}.
84 {% endif %}
84 {% endif %}
85 <span class="tags">
85 <span class="tags">
86 {% for tag in thread.get_tags %}
86 {% for tag in thread.get_tags %}
87 <a class="tag" href="{% url 'tag' tag.name %}">
87 <a class="tag" href="{% url 'tag' tag.name %}">
88 #{{ tag.name }}</a>{% if not forloop.last %},{% endif %}
88 #{{ tag.name }}</a>{% if not forloop.last %},{% endif %}
89 {% endfor %}
89 {% endfor %}
90 </span>
90 </span>
91 </div>
91 </div>
92 {% endif %}
92 {% endif %}
93 </div>
93 </div>
94 {% endwith %}
94 {% endwith %}
95 {% endspaceless %}
95 {% endspaceless %}
96 {% endcache %}
96 {% endcache %}
@@ -1,79 +1,81 b''
1 from django.conf.urls import patterns, url, include
1 from django.conf.urls import patterns, url, include
2 from boards import views
2 from boards import views
3 from boards.rss import AllThreadsFeed, TagThreadsFeed, ThreadPostsFeed
3 from boards.rss import AllThreadsFeed, TagThreadsFeed, ThreadPostsFeed
4 from boards.views import api, tag_threads, all_threads, archived_threads, \
4 from boards.views import api, tag_threads, all_threads, archived_threads, \
5 login, settings, all_tags
5 login, settings, all_tags
6 from boards.views.authors import AuthorsView
6 from boards.views.authors import AuthorsView
7 from boards.views.delete_post import DeletePostView
7
8
8 js_info_dict = {
9 js_info_dict = {
9 'packages': ('boards',),
10 'packages': ('boards',),
10 }
11 }
11
12
12 urlpatterns = patterns('',
13 urlpatterns = patterns('',
13
14
14 # /boards/
15 # /boards/
15 url(r'^$', all_threads.AllThreadsView.as_view(), name='index'),
16 url(r'^$', all_threads.AllThreadsView.as_view(), name='index'),
16 # /boards/page/
17 # /boards/page/
17 url(r'^page/(?P<page>\w+)/$', all_threads.AllThreadsView.as_view(),
18 url(r'^page/(?P<page>\w+)/$', all_threads.AllThreadsView.as_view(),
18 name='index'),
19 name='index'),
19
20
20 url(r'^archive/$', archived_threads.ArchiveView.as_view(), name='archive'),
21 url(r'^archive/$', archived_threads.ArchiveView.as_view(), name='archive'),
21 url(r'^archive/page/(?P<page>\w+)/$',
22 url(r'^archive/page/(?P<page>\w+)/$',
22 archived_threads.ArchiveView.as_view(), name='archive'),
23 archived_threads.ArchiveView.as_view(), name='archive'),
23
24
24 # login page
25 # login page
25 url(r'^login/$', login.LoginView.as_view(), name='login'),
26 url(r'^login/$', login.LoginView.as_view(), name='login'),
26
27
27 # /boards/tag/tag_name/
28 # /boards/tag/tag_name/
28 url(r'^tag/(?P<tag_name>\w+)/$', tag_threads.TagView.as_view(),
29 url(r'^tag/(?P<tag_name>\w+)/$', tag_threads.TagView.as_view(),
29 name='tag'),
30 name='tag'),
30 # /boards/tag/tag_id/page/
31 # /boards/tag/tag_id/page/
31 url(r'^tag/(?P<tag_name>\w+)/page/(?P<page>\w+)/$',
32 url(r'^tag/(?P<tag_name>\w+)/page/(?P<page>\w+)/$',
32 tag_threads.TagView.as_view(), name='tag'),
33 tag_threads.TagView.as_view(), name='tag'),
33
34
34 # /boards/tag/tag_name/unsubscribe/
35 # /boards/tag/tag_name/unsubscribe/
35 url(r'^tag/(?P<tag_name>\w+)/subscribe/$', views.tag_subscribe,
36 url(r'^tag/(?P<tag_name>\w+)/subscribe/$', views.tag_subscribe,
36 name='tag_subscribe'),
37 name='tag_subscribe'),
37 # /boards/tag/tag_name/unsubscribe/
38 # /boards/tag/tag_name/unsubscribe/
38 url(r'^tag/(?P<tag_name>\w+)/unsubscribe/$', views.tag_unsubscribe,
39 url(r'^tag/(?P<tag_name>\w+)/unsubscribe/$', views.tag_unsubscribe,
39 name='tag_unsubscribe'),
40 name='tag_unsubscribe'),
40
41
41 # /boards/thread/
42 # /boards/thread/
42 url(r'^thread/(?P<post_id>\w+)/$', views.thread.ThreadView.as_view(),
43 url(r'^thread/(?P<post_id>\w+)/$', views.thread.ThreadView.as_view(),
43 name='thread'),
44 name='thread'),
44 url(r'^thread/(?P<post_id>\w+)/(?P<mode>\w+)/$', views.thread.ThreadView
45 url(r'^thread/(?P<post_id>\w+)/(?P<mode>\w+)/$', views.thread.ThreadView
45 .as_view(), name='thread_mode'),
46 .as_view(), name='thread_mode'),
46
47
47 url(r'^settings/$', settings.SettingsView.as_view(), name='settings'),
48 url(r'^settings/$', settings.SettingsView.as_view(), name='settings'),
48 url(r'^tags/$', all_tags.AllTagsView.as_view(), name='tags'),
49 url(r'^tags/$', all_tags.AllTagsView.as_view(), name='tags'),
49 url(r'^captcha/', include('captcha.urls')),
50 url(r'^captcha/', include('captcha.urls')),
50 url(r'^jump/(?P<post_id>\w+)/$', views.jump_to_post, name='jumper'),
51 url(r'^jump/(?P<post_id>\w+)/$', views.jump_to_post, name='jumper'),
51 url(r'^authors/$', AuthorsView.as_view(), name='authors'),
52 url(r'^authors/$', AuthorsView.as_view(), name='authors'),
52 url(r'^delete/(?P<post_id>\w+)/$', views.delete, name='delete'),
53 url(r'^delete/(?P<post_id>\w+)/$', DeletePostView.as_view(),
54 name='delete'),
53 url(r'^ban/(?P<post_id>\w+)/$', views.ban, name='ban'),
55 url(r'^ban/(?P<post_id>\w+)/$', views.ban, name='ban'),
54
56
55 url(r'^banned/$', views.banned.BannedView.as_view(), name='banned'),
57 url(r'^banned/$', views.banned.BannedView.as_view(), name='banned'),
56 url(r'^staticpage/(?P<name>\w+)/$', views.static_page, name='staticpage'),
58 url(r'^staticpage/(?P<name>\w+)/$', views.static_page, name='staticpage'),
57
59
58 # RSS feeds
60 # RSS feeds
59 url(r'^rss/$', AllThreadsFeed()),
61 url(r'^rss/$', AllThreadsFeed()),
60 url(r'^page/(?P<page>\w+)/rss/$', AllThreadsFeed()),
62 url(r'^page/(?P<page>\w+)/rss/$', AllThreadsFeed()),
61 url(r'^tag/(?P<tag_name>\w+)/rss/$', TagThreadsFeed()),
63 url(r'^tag/(?P<tag_name>\w+)/rss/$', TagThreadsFeed()),
62 url(r'^tag/(?P<tag_name>\w+)/page/(?P<page>\w+)/rss/$', TagThreadsFeed()),
64 url(r'^tag/(?P<tag_name>\w+)/page/(?P<page>\w+)/rss/$', TagThreadsFeed()),
63 url(r'^thread/(?P<post_id>\w+)/rss/$', ThreadPostsFeed()),
65 url(r'^thread/(?P<post_id>\w+)/rss/$', ThreadPostsFeed()),
64
66
65 # i18n
67 # i18n
66 url(r'^jsi18n/$', 'boards.views.cached_js_catalog', js_info_dict, name='js_info_dict'),
68 url(r'^jsi18n/$', 'boards.views.cached_js_catalog', js_info_dict, name='js_info_dict'),
67
69
68 # API
70 # API
69 url(r'^api/post/(?P<post_id>\w+)/$', api.get_post, name="get_post"),
71 url(r'^api/post/(?P<post_id>\w+)/$', api.get_post, name="get_post"),
70 url(r'^api/diff_thread/(?P<thread_id>\w+)/(?P<last_update_time>\w+)/$',
72 url(r'^api/diff_thread/(?P<thread_id>\w+)/(?P<last_update_time>\w+)/$',
71 api.api_get_threaddiff, name="get_thread_diff"),
73 api.api_get_threaddiff, name="get_thread_diff"),
72 url(r'^api/threads/(?P<count>\w+)/$', api.api_get_threads,
74 url(r'^api/threads/(?P<count>\w+)/$', api.api_get_threads,
73 name='get_threads'),
75 name='get_threads'),
74 url(r'^api/tags/$', api.api_get_tags, name='get_tags'),
76 url(r'^api/tags/$', api.api_get_tags, name='get_tags'),
75 url(r'^api/thread/(?P<opening_post_id>\w+)/$', api.api_get_thread_posts,
77 url(r'^api/thread/(?P<opening_post_id>\w+)/$', api.api_get_thread_posts,
76 name='get_thread'),
78 name='get_thread'),
77 url(r'^api/add_post/(?P<opening_post_id>\w+)/$', api.api_add_post, name='add_post'),
79 url(r'^api/add_post/(?P<opening_post_id>\w+)/$', api.api_add_post, name='add_post'),
78
80
79 )
81 )
@@ -1,49 +1,50 b''
1 from django.db import transaction
1 from django.db import transaction
2 from django.shortcuts import render, redirect
2 from django.shortcuts import render, redirect
3
3
4 from boards.views.base import BaseBoardView, PARAMETER_FORM
4 from boards.views.base import BaseBoardView, PARAMETER_FORM
5 from boards.forms import SettingsForm, ModeratorSettingsForm, PlainErrorList
5 from boards.forms import SettingsForm, ModeratorSettingsForm, PlainErrorList
6 from boards.views import SETTING_MODERATE
6
7
7 class SettingsView(BaseBoardView):
8 class SettingsView(BaseBoardView):
8
9
9 def get(self, request):
10 def get(self, request):
10 context = self.get_context_data(request=request)
11 context = self.get_context_data(request=request)
11 user = context['user']
12 user = context['user']
12 is_moderator = user.is_moderator()
13 is_moderator = user.is_moderator()
13
14
14 selected_theme = context['theme']
15 selected_theme = context['theme']
15
16
16 if is_moderator:
17 if is_moderator:
17 form = ModeratorSettingsForm(initial={'theme': selected_theme,
18 form = ModeratorSettingsForm(initial={'theme': selected_theme,
18 'moderate': context['moderator']},
19 'moderate': context['moderator']},
19 error_class=PlainErrorList)
20 error_class=PlainErrorList)
20 else:
21 else:
21 form = SettingsForm(initial={'theme': selected_theme},
22 form = SettingsForm(initial={'theme': selected_theme},
22 error_class=PlainErrorList)
23 error_class=PlainErrorList)
23
24
24 context[PARAMETER_FORM] = form
25 context[PARAMETER_FORM] = form
25
26
26 return render(request, 'boards/settings.html', context)
27 return render(request, 'boards/settings.html', context)
27
28
28 def post(self, request):
29 def post(self, request):
29 context = self.get_context_data(request=request)
30 context = self.get_context_data(request=request)
30 user = context['user']
31 user = context['user']
31 is_moderator = user.is_moderator()
32 is_moderator = user.is_moderator()
32
33
33 with transaction.atomic():
34 with transaction.atomic():
34 if is_moderator:
35 if is_moderator:
35 form = ModeratorSettingsForm(request.POST,
36 form = ModeratorSettingsForm(request.POST,
36 error_class=PlainErrorList)
37 error_class=PlainErrorList)
37 else:
38 else:
38 form = SettingsForm(request.POST, error_class=PlainErrorList)
39 form = SettingsForm(request.POST, error_class=PlainErrorList)
39
40
40 if form.is_valid():
41 if form.is_valid():
41 selected_theme = form.cleaned_data['theme']
42 selected_theme = form.cleaned_data['theme']
42
43
43 user.save_setting('theme', selected_theme)
44 user.save_setting('theme', selected_theme)
44
45
45 if is_moderator:
46 if is_moderator:
46 moderate = form.cleaned_data['moderate']
47 moderate = form.cleaned_data['moderate']
47 user.save_setting(SETTING_MODERATE, moderate)
48 user.save_setting(SETTING_MODERATE, moderate)
48
49
49 return redirect('settings')
50 return redirect('settings')
General Comments 0
You need to be logged in to leave comments. Login now