##// END OF EJS Templates
Added some logging
neko259 -
r639:905cd0bf default
parent child Browse files
Show More
@@ -1,461 +1,474 b''
1 from datetime import datetime, timedelta, date
1 from datetime import datetime, timedelta, date
2 from datetime import time as dtime
2 from datetime import time as dtime
3 import logging
3 import os
4 import os
4 from random import random
5 from random import random
5 import time
6 import time
6 import re
7 import re
7 import hashlib
8 import hashlib
8
9
9 from django.core.cache import cache
10 from django.core.cache import cache
10 from django.core.urlresolvers import reverse
11 from django.core.urlresolvers import reverse
11 from django.db import models, transaction
12 from django.db import models, transaction
12 from django.utils import timezone
13 from django.utils import timezone
13 from markupfield.fields import MarkupField
14 from markupfield.fields import MarkupField
14
15
15 from neboard import settings
16 from neboard import settings
16 from boards import thumbs
17 from boards import thumbs
17
18
18
19
19 APP_LABEL_BOARDS = 'boards'
20 APP_LABEL_BOARDS = 'boards'
20
21
21 CACHE_KEY_PPD = 'ppd'
22 CACHE_KEY_PPD = 'ppd'
22 CACHE_KEY_POST_URL = 'post_url'
23 CACHE_KEY_POST_URL = 'post_url'
23 CACHE_KEY_OPENING_POST = 'opening_post_id'
24 CACHE_KEY_OPENING_POST = 'opening_post_id'
24
25
25 POSTS_PER_DAY_RANGE = range(7)
26 POSTS_PER_DAY_RANGE = range(7)
26
27
27 BAN_REASON_AUTO = 'Auto'
28 BAN_REASON_AUTO = 'Auto'
28
29
29 IMAGE_THUMB_SIZE = (200, 150)
30 IMAGE_THUMB_SIZE = (200, 150)
30
31
31 TITLE_MAX_LENGTH = 200
32 TITLE_MAX_LENGTH = 200
32
33
33 DEFAULT_MARKUP_TYPE = 'markdown'
34 DEFAULT_MARKUP_TYPE = 'markdown'
34
35
35 NO_PARENT = -1
36 NO_PARENT = -1
36 NO_IP = '0.0.0.0'
37 NO_IP = '0.0.0.0'
37 UNKNOWN_UA = ''
38 UNKNOWN_UA = ''
38 ALL_PAGES = -1
39 ALL_PAGES = -1
39 IMAGES_DIRECTORY = 'images/'
40 IMAGES_DIRECTORY = 'images/'
40 FILE_EXTENSION_DELIMITER = '.'
41 FILE_EXTENSION_DELIMITER = '.'
41
42
42 SETTING_MODERATE = "moderate"
43 SETTING_MODERATE = "moderate"
43
44
44 REGEX_REPLY = re.compile('>>(\d+)')
45 REGEX_REPLY = re.compile('>>(\d+)')
45
46
47 logger = logging.getLogger(__name__)
48
46
49
47 class PostManager(models.Manager):
50 class PostManager(models.Manager):
48
51
49 def create_post(self, title, text, image=None, thread=None,
52 def create_post(self, title, text, image=None, thread=None,
50 ip=NO_IP, tags=None, user=None):
53 ip=NO_IP, tags=None, user=None):
51 """
54 """
52 Creates new post
55 Creates new post
53 """
56 """
54
57
55 posting_time = timezone.now()
58 posting_time = timezone.now()
56 if not thread:
59 if not thread:
57 thread = Thread.objects.create(bump_time=posting_time,
60 thread = Thread.objects.create(bump_time=posting_time,
58 last_edit_time=posting_time)
61 last_edit_time=posting_time)
59 new_thread = True
62 new_thread = True
60 else:
63 else:
61 thread.bump()
64 thread.bump()
62 thread.last_edit_time = posting_time
65 thread.last_edit_time = posting_time
63 thread.save()
66 thread.save()
64 new_thread = False
67 new_thread = False
65
68
66 post = self.create(title=title,
69 post = self.create(title=title,
67 text=text,
70 text=text,
68 pub_time=posting_time,
71 pub_time=posting_time,
69 thread_new=thread,
72 thread_new=thread,
70 image=image,
73 image=image,
71 poster_ip=ip,
74 poster_ip=ip,
72 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
75 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
73 # last!
76 # last!
74 last_edit_time=posting_time,
77 last_edit_time=posting_time,
75 user=user)
78 user=user)
76
79
77 thread.replies.add(post)
80 thread.replies.add(post)
78 if tags:
81 if tags:
79 linked_tags = []
82 linked_tags = []
80 for tag in tags:
83 for tag in tags:
81 tag_linked_tags = tag.get_linked_tags()
84 tag_linked_tags = tag.get_linked_tags()
82 if len(tag_linked_tags) > 0:
85 if len(tag_linked_tags) > 0:
83 linked_tags.extend(tag_linked_tags)
86 linked_tags.extend(tag_linked_tags)
84
87
85 tags.extend(linked_tags)
88 tags.extend(linked_tags)
86 map(thread.add_tag, tags)
89 map(thread.add_tag, tags)
87
90
88 if new_thread:
91 if new_thread:
89 self._delete_old_threads()
92 self._delete_old_threads()
90 self.connect_replies(post)
93 self.connect_replies(post)
91
94
95 logger.info('Created post #%d' % post.id)
96
92 return post
97 return post
93
98
94 def delete_post(self, post):
99 def delete_post(self, post):
95 """
100 """
96 Deletes post and update or delete its thread
101 Deletes post and update or delete its thread
97 """
102 """
98
103
104 post_id = post.id
105
99 thread = post.get_thread()
106 thread = post.get_thread()
100
107
101 if post.is_opening():
108 if post.is_opening():
102 thread.delete_with_posts()
109 thread.delete_with_posts()
103 else:
110 else:
104 thread.last_edit_time = timezone.now()
111 thread.last_edit_time = timezone.now()
105 thread.save()
112 thread.save()
106
113
107 post.delete()
114 post.delete()
108
115
116 logger.info('Deleted post #%d' % post_id)
117
109 def delete_posts_by_ip(self, ip):
118 def delete_posts_by_ip(self, ip):
110 """
119 """
111 Deletes all posts of the author with same IP
120 Deletes all posts of the author with same IP
112 """
121 """
113
122
114 posts = self.filter(poster_ip=ip)
123 posts = self.filter(poster_ip=ip)
115 map(self.delete_post, posts)
124 map(self.delete_post, posts)
116
125
117 # TODO Move this method to thread manager
126 # TODO Move this method to thread manager
118 def _delete_old_threads(self):
127 def _delete_old_threads(self):
119 """
128 """
120 Preserves maximum thread count. If there are too many threads,
129 Preserves maximum thread count. If there are too many threads,
121 archive the old ones.
130 archive the old ones.
122 """
131 """
123
132
124 threads = Thread.objects.filter(archived=False).order_by('-bump_time')
133 threads = Thread.objects.filter(archived=False).order_by('-bump_time')
125 thread_count = threads.count()
134 thread_count = threads.count()
126
135
127 if thread_count > settings.MAX_THREAD_COUNT:
136 if thread_count > settings.MAX_THREAD_COUNT:
128 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
137 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
129 old_threads = threads[thread_count - num_threads_to_delete:]
138 old_threads = threads[thread_count - num_threads_to_delete:]
130
139
131 for thread in old_threads:
140 for thread in old_threads:
132 thread.archived = True
141 thread.archived = True
133 thread.last_edit_time = timezone.now()
142 thread.last_edit_time = timezone.now()
134 thread.save()
143 thread.save()
135
144
145 logger.info('Archived %d old threads' % num_threads_to_delete)
146
136 def connect_replies(self, post):
147 def connect_replies(self, post):
137 """
148 """
138 Connects replies to a post to show them as a reflink map
149 Connects replies to a post to show them as a reflink map
139 """
150 """
140
151
141 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
152 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
142 post_id = reply_number.group(1)
153 post_id = reply_number.group(1)
143 ref_post = self.filter(id=post_id)
154 ref_post = self.filter(id=post_id)
144 if ref_post.count() > 0:
155 if ref_post.count() > 0:
145 referenced_post = ref_post[0]
156 referenced_post = ref_post[0]
146 referenced_post.referenced_posts.add(post)
157 referenced_post.referenced_posts.add(post)
147 referenced_post.last_edit_time = post.pub_time
158 referenced_post.last_edit_time = post.pub_time
148 referenced_post.save()
159 referenced_post.save()
149
160
150 referenced_thread = referenced_post.get_thread()
161 referenced_thread = referenced_post.get_thread()
151 referenced_thread.last_edit_time = post.pub_time
162 referenced_thread.last_edit_time = post.pub_time
152 referenced_thread.save()
163 referenced_thread.save()
153
164
154 def get_posts_per_day(self):
165 def get_posts_per_day(self):
155 """
166 """
156 Gets average count of posts per day for the last 7 days
167 Gets average count of posts per day for the last 7 days
157 """
168 """
158
169
159 today = date.today()
170 today = date.today()
160 ppd = cache.get(CACHE_KEY_PPD + str(today))
171 ppd = cache.get(CACHE_KEY_PPD + str(today))
161 if ppd:
172 if ppd:
162 return ppd
173 return ppd
163
174
164 posts_per_days = []
175 posts_per_days = []
165 for i in POSTS_PER_DAY_RANGE:
176 for i in POSTS_PER_DAY_RANGE:
166 day_end = today - timedelta(i + 1)
177 day_end = today - timedelta(i + 1)
167 day_start = today - timedelta(i + 2)
178 day_start = today - timedelta(i + 2)
168
179
169 day_time_start = timezone.make_aware(datetime.combine(
180 day_time_start = timezone.make_aware(datetime.combine(
170 day_start, dtime()), timezone.get_current_timezone())
181 day_start, dtime()), timezone.get_current_timezone())
171 day_time_end = timezone.make_aware(datetime.combine(
182 day_time_end = timezone.make_aware(datetime.combine(
172 day_end, dtime()), timezone.get_current_timezone())
183 day_end, dtime()), timezone.get_current_timezone())
173
184
174 posts_per_days.append(float(self.filter(
185 posts_per_days.append(float(self.filter(
175 pub_time__lte=day_time_end,
186 pub_time__lte=day_time_end,
176 pub_time__gte=day_time_start).count()))
187 pub_time__gte=day_time_start).count()))
177
188
178 ppd = (sum(posts_per_day for posts_per_day in posts_per_days) /
189 ppd = (sum(posts_per_day for posts_per_day in posts_per_days) /
179 len(posts_per_days))
190 len(posts_per_days))
180 cache.set(CACHE_KEY_PPD + str(today), ppd)
191 cache.set(CACHE_KEY_PPD + str(today), ppd)
181 return ppd
192 return ppd
182
193
183
194
184 class Post(models.Model):
195 class Post(models.Model):
185 """A post is a message."""
196 """A post is a message."""
186
197
187 objects = PostManager()
198 objects = PostManager()
188
199
189 class Meta:
200 class Meta:
190 app_label = APP_LABEL_BOARDS
201 app_label = APP_LABEL_BOARDS
191
202
192 # TODO Save original file name to some field
203 # TODO Save original file name to some field
193 def _update_image_filename(self, filename):
204 def _update_image_filename(self, filename):
194 """
205 """
195 Gets unique image filename
206 Gets unique image filename
196 """
207 """
197
208
198 path = IMAGES_DIRECTORY
209 path = IMAGES_DIRECTORY
199 new_name = str(int(time.mktime(time.gmtime())))
210 new_name = str(int(time.mktime(time.gmtime())))
200 new_name += str(int(random() * 1000))
211 new_name += str(int(random() * 1000))
201 new_name += FILE_EXTENSION_DELIMITER
212 new_name += FILE_EXTENSION_DELIMITER
202 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
213 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
203
214
204 return os.path.join(path, new_name)
215 return os.path.join(path, new_name)
205
216
206 title = models.CharField(max_length=TITLE_MAX_LENGTH)
217 title = models.CharField(max_length=TITLE_MAX_LENGTH)
207 pub_time = models.DateTimeField()
218 pub_time = models.DateTimeField()
208 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
219 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
209 escape_html=False)
220 escape_html=False)
210
221
211 image_width = models.IntegerField(default=0)
222 image_width = models.IntegerField(default=0)
212 image_height = models.IntegerField(default=0)
223 image_height = models.IntegerField(default=0)
213
224
214 image_pre_width = models.IntegerField(default=0)
225 image_pre_width = models.IntegerField(default=0)
215 image_pre_height = models.IntegerField(default=0)
226 image_pre_height = models.IntegerField(default=0)
216
227
217 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
228 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
218 blank=True, sizes=(IMAGE_THUMB_SIZE,),
229 blank=True, sizes=(IMAGE_THUMB_SIZE,),
219 width_field='image_width',
230 width_field='image_width',
220 height_field='image_height',
231 height_field='image_height',
221 preview_width_field='image_pre_width',
232 preview_width_field='image_pre_width',
222 preview_height_field='image_pre_height')
233 preview_height_field='image_pre_height')
223 image_hash = models.CharField(max_length=36)
234 image_hash = models.CharField(max_length=36)
224
235
225 poster_ip = models.GenericIPAddressField()
236 poster_ip = models.GenericIPAddressField()
226 poster_user_agent = models.TextField()
237 poster_user_agent = models.TextField()
227
238
228 thread = models.ForeignKey('Post', null=True, default=None)
239 thread = models.ForeignKey('Post', null=True, default=None)
229 thread_new = models.ForeignKey('Thread', null=True, default=None)
240 thread_new = models.ForeignKey('Thread', null=True, default=None)
230 last_edit_time = models.DateTimeField()
241 last_edit_time = models.DateTimeField()
231 user = models.ForeignKey('User', null=True, default=None)
242 user = models.ForeignKey('User', null=True, default=None)
232
243
233 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
244 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
234 null=True,
245 null=True,
235 blank=True, related_name='rfp+')
246 blank=True, related_name='rfp+')
236
247
237 def __unicode__(self):
248 def __unicode__(self):
238 return '#' + str(self.id) + ' ' + self.title + ' (' + \
249 return '#' + str(self.id) + ' ' + self.title + ' (' + \
239 self.text.raw[:50] + ')'
250 self.text.raw[:50] + ')'
240
251
241 def get_title(self):
252 def get_title(self):
242 """
253 """
243 Gets original post title or part of its text.
254 Gets original post title or part of its text.
244 """
255 """
245
256
246 title = self.title
257 title = self.title
247 if not title:
258 if not title:
248 title = self.text.rendered
259 title = self.text.rendered
249
260
250 return title
261 return title
251
262
252 def get_sorted_referenced_posts(self):
263 def get_sorted_referenced_posts(self):
253 return self.referenced_posts.order_by('id')
264 return self.referenced_posts.order_by('id')
254
265
255 def is_referenced(self):
266 def is_referenced(self):
256 return self.referenced_posts.exists()
267 return self.referenced_posts.exists()
257
268
258 def is_opening(self):
269 def is_opening(self):
259 """
270 """
260 Checks if this is an opening post or just a reply.
271 Checks if this is an opening post or just a reply.
261 """
272 """
262
273
263 return self.get_thread().get_opening_post_id() == self.id
274 return self.get_thread().get_opening_post_id() == self.id
264
275
265 def save(self, *args, **kwargs):
276 def save(self, *args, **kwargs):
266 """
277 """
267 Saves the model and computes the image hash for deduplication purposes.
278 Saves the model and computes the image hash for deduplication purposes.
268 """
279 """
269
280
270 if not self.pk and self.image:
281 if not self.pk and self.image:
271 md5 = hashlib.md5()
282 md5 = hashlib.md5()
272 for chunk in self.image.chunks():
283 for chunk in self.image.chunks():
273 md5.update(chunk)
284 md5.update(chunk)
274 self.image_hash = md5.hexdigest()
285 self.image_hash = md5.hexdigest()
275 super(Post, self).save(*args, **kwargs)
286 super(Post, self).save(*args, **kwargs)
276
287
277 @transaction.atomic
288 @transaction.atomic
278 def add_tag(self, tag):
289 def add_tag(self, tag):
279 edit_time = timezone.now()
290 edit_time = timezone.now()
280
291
281 thread = self.get_thread()
292 thread = self.get_thread()
282 thread.add_tag(tag)
293 thread.add_tag(tag)
283 self.last_edit_time = edit_time
294 self.last_edit_time = edit_time
284 self.save()
295 self.save()
285
296
286 thread.last_edit_time = edit_time
297 thread.last_edit_time = edit_time
287 thread.save()
298 thread.save()
288
299
289 @transaction.atomic
300 @transaction.atomic
290 def remove_tag(self, tag):
301 def remove_tag(self, tag):
291 edit_time = timezone.now()
302 edit_time = timezone.now()
292
303
293 thread = self.get_thread()
304 thread = self.get_thread()
294 thread.remove_tag(tag)
305 thread.remove_tag(tag)
295 self.last_edit_time = edit_time
306 self.last_edit_time = edit_time
296 self.save()
307 self.save()
297
308
298 thread.last_edit_time = edit_time
309 thread.last_edit_time = edit_time
299 thread.save()
310 thread.save()
300
311
301 def get_url(self, thread=None):
312 def get_url(self, thread=None):
302 """
313 """
303 Gets full url to the post.
314 Gets full url to the post.
304 """
315 """
305
316
306 cache_key = CACHE_KEY_POST_URL + str(self.id)
317 cache_key = CACHE_KEY_POST_URL + str(self.id)
307 link = cache.get(cache_key)
318 link = cache.get(cache_key)
308
319
309 if not link:
320 if not link:
310 if not thread:
321 if not thread:
311 thread = self.get_thread()
322 thread = self.get_thread()
312
323
313 opening_id = thread.get_opening_post_id()
324 opening_id = thread.get_opening_post_id()
314
325
315 if self.id != opening_id:
326 if self.id != opening_id:
316 link = reverse('thread', kwargs={
327 link = reverse('thread', kwargs={
317 'post_id': opening_id}) + '#' + str(self.id)
328 'post_id': opening_id}) + '#' + str(self.id)
318 else:
329 else:
319 link = reverse('thread', kwargs={'post_id': self.id})
330 link = reverse('thread', kwargs={'post_id': self.id})
320
331
321 cache.set(cache_key, link)
332 cache.set(cache_key, link)
322
333
323 return link
334 return link
324
335
325 def get_thread(self):
336 def get_thread(self):
326 """
337 """
327 Gets post's thread.
338 Gets post's thread.
328 """
339 """
329
340
330 return self.thread_new
341 return self.thread_new
331
342
332
343
333 class Thread(models.Model):
344 class Thread(models.Model):
334
345
335 class Meta:
346 class Meta:
336 app_label = APP_LABEL_BOARDS
347 app_label = APP_LABEL_BOARDS
337
348
338 tags = models.ManyToManyField('Tag')
349 tags = models.ManyToManyField('Tag')
339 bump_time = models.DateTimeField()
350 bump_time = models.DateTimeField()
340 last_edit_time = models.DateTimeField()
351 last_edit_time = models.DateTimeField()
341 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
352 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
342 blank=True, related_name='tre+')
353 blank=True, related_name='tre+')
343 archived = models.BooleanField(default=False)
354 archived = models.BooleanField(default=False)
344
355
345 def get_tags(self):
356 def get_tags(self):
346 """
357 """
347 Gets a sorted tag list.
358 Gets a sorted tag list.
348 """
359 """
349
360
350 return self.tags.order_by('name')
361 return self.tags.order_by('name')
351
362
352 def bump(self):
363 def bump(self):
353 """
364 """
354 Bumps (moves to up) thread if possible.
365 Bumps (moves to up) thread if possible.
355 """
366 """
356
367
357 if self.can_bump():
368 if self.can_bump():
358 self.bump_time = timezone.now()
369 self.bump_time = timezone.now()
359
370
371 logger.info('Bumped thread %d' % self.id)
372
360 def get_reply_count(self):
373 def get_reply_count(self):
361 return self.replies.count()
374 return self.replies.count()
362
375
363 def get_images_count(self):
376 def get_images_count(self):
364 return self.replies.filter(image_width__gt=0).count()
377 return self.replies.filter(image_width__gt=0).count()
365
378
366 def can_bump(self):
379 def can_bump(self):
367 """
380 """
368 Checks if the thread can be bumped by replying to it.
381 Checks if the thread can be bumped by replying to it.
369 """
382 """
370
383
371 if self.archived:
384 if self.archived:
372 return False
385 return False
373
386
374 post_count = self.get_reply_count()
387 post_count = self.get_reply_count()
375
388
376 return post_count < settings.MAX_POSTS_PER_THREAD
389 return post_count < settings.MAX_POSTS_PER_THREAD
377
390
378 def delete_with_posts(self):
391 def delete_with_posts(self):
379 """
392 """
380 Completely deletes thread and all its posts
393 Completely deletes thread and all its posts
381 """
394 """
382
395
383 if self.replies.exists():
396 if self.replies.exists():
384 self.replies.all().delete()
397 self.replies.all().delete()
385
398
386 self.delete()
399 self.delete()
387
400
388 def get_last_replies(self):
401 def get_last_replies(self):
389 """
402 """
390 Gets several last replies, not including opening post
403 Gets several last replies, not including opening post
391 """
404 """
392
405
393 if settings.LAST_REPLIES_COUNT > 0:
406 if settings.LAST_REPLIES_COUNT > 0:
394 reply_count = self.get_reply_count()
407 reply_count = self.get_reply_count()
395
408
396 if reply_count > 0:
409 if reply_count > 0:
397 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
410 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
398 reply_count - 1)
411 reply_count - 1)
399 last_replies = self.replies.order_by(
412 last_replies = self.replies.order_by(
400 'pub_time')[reply_count - reply_count_to_show:]
413 'pub_time')[reply_count - reply_count_to_show:]
401
414
402 return last_replies
415 return last_replies
403
416
404 def get_skipped_replies_count(self):
417 def get_skipped_replies_count(self):
405 """
418 """
406 Gets number of posts between opening post and last replies.
419 Gets number of posts between opening post and last replies.
407 """
420 """
408
421
409 last_replies = self.get_last_replies()
422 last_replies = self.get_last_replies()
410 return self.get_reply_count() - len(last_replies) - 1
423 return self.get_reply_count() - len(last_replies) - 1
411
424
412 def get_replies(self):
425 def get_replies(self):
413 """
426 """
414 Gets sorted thread posts
427 Gets sorted thread posts
415 """
428 """
416
429
417 return self.replies.all().order_by('pub_time')
430 return self.replies.all().order_by('pub_time')
418
431
419 def add_tag(self, tag):
432 def add_tag(self, tag):
420 """
433 """
421 Connects thread to a tag and tag to a thread
434 Connects thread to a tag and tag to a thread
422 """
435 """
423
436
424 self.tags.add(tag)
437 self.tags.add(tag)
425 tag.threads.add(self)
438 tag.threads.add(self)
426
439
427 def remove_tag(self, tag):
440 def remove_tag(self, tag):
428 self.tags.remove(tag)
441 self.tags.remove(tag)
429 tag.threads.remove(self)
442 tag.threads.remove(self)
430
443
431 def get_opening_post(self):
444 def get_opening_post(self):
432 """
445 """
433 Gets the first post of the thread
446 Gets the first post of the thread
434 """
447 """
435
448
436 opening_post = self.get_replies()[0]
449 opening_post = self.get_replies()[0]
437
450
438 return opening_post
451 return opening_post
439
452
440 def get_opening_post_id(self):
453 def get_opening_post_id(self):
441 """
454 """
442 Gets ID of the first thread post.
455 Gets ID of the first thread post.
443 """
456 """
444
457
445 cache_key = CACHE_KEY_OPENING_POST + str(self.id)
458 cache_key = CACHE_KEY_OPENING_POST + str(self.id)
446 opening_post_id = cache.get(cache_key)
459 opening_post_id = cache.get(cache_key)
447 if not opening_post_id:
460 if not opening_post_id:
448 opening_post_id = self.get_opening_post().id
461 opening_post_id = self.get_opening_post().id
449 cache.set(cache_key, opening_post_id)
462 cache.set(cache_key, opening_post_id)
450
463
451 return opening_post_id
464 return opening_post_id
452
465
453 def __unicode__(self):
466 def __unicode__(self):
454 return str(self.id)
467 return str(self.id)
455
468
456 def get_pub_time(self):
469 def get_pub_time(self):
457 """
470 """
458 Gets opening post's pub time because thread does not have its own one.
471 Gets opening post's pub time because thread does not have its own one.
459 """
472 """
460
473
461 return self.get_opening_post().pub_time
474 return self.get_opening_post().pub_time
@@ -1,228 +1,237 b''
1 from datetime import datetime
1 from datetime import datetime
2 import json
2 import json
3 import logging
3 from django.db import transaction
4 from django.db import transaction
4 from django.http import HttpResponse
5 from django.http import HttpResponse
5 from django.shortcuts import get_object_or_404, render
6 from django.shortcuts import get_object_or_404, render
6 from django.template import RequestContext
7 from django.template import RequestContext
7 from django.utils import timezone
8 from django.utils import timezone
8 from django.core import serializers
9 from django.core import serializers
9
10
10 from boards.forms import PostForm, PlainErrorList
11 from boards.forms import PostForm, PlainErrorList
11 from boards.models import Post, Thread, Tag
12 from boards.models import Post, Thread, Tag
12 from boards.utils import datetime_to_epoch
13 from boards.utils import datetime_to_epoch
13 from boards.views.thread import ThreadView
14 from boards.views.thread import ThreadView
14
15
15 __author__ = 'neko259'
16 __author__ = 'neko259'
16
17
17 PARAMETER_TRUNCATED = 'truncated'
18 PARAMETER_TRUNCATED = 'truncated'
18 PARAMETER_TAG = 'tag'
19 PARAMETER_TAG = 'tag'
19 PARAMETER_OFFSET = 'offset'
20 PARAMETER_OFFSET = 'offset'
20 PARAMETER_DIFF_TYPE = 'type'
21 PARAMETER_DIFF_TYPE = 'type'
21
22
22 DIFF_TYPE_HTML = 'html'
23 DIFF_TYPE_HTML = 'html'
23 DIFF_TYPE_JSON = 'json'
24 DIFF_TYPE_JSON = 'json'
24
25
25 STATUS_OK = 'ok'
26 STATUS_OK = 'ok'
26 STATUS_ERROR = 'error'
27 STATUS_ERROR = 'error'
27
28
29 logger = logging.getLogger(__name__)
30
28
31
29 @transaction.atomic
32 @transaction.atomic
30 def api_get_threaddiff(request, thread_id, last_update_time):
33 def api_get_threaddiff(request, thread_id, last_update_time):
31 """
34 """
32 Gets posts that were changed or added since time
35 Gets posts that were changed or added since time
33 """
36 """
34
37
38 logger.info('Getting thread diff since %s' % last_update_time)
39
35 thread = get_object_or_404(Post, id=thread_id).thread_new
40 thread = get_object_or_404(Post, id=thread_id).thread_new
36
41
37 filter_time = datetime.fromtimestamp(float(last_update_time) / 1000000,
42 filter_time = datetime.fromtimestamp(float(last_update_time) / 1000000,
38 timezone.get_current_timezone())
43 timezone.get_current_timezone())
39
44
40 json_data = {
45 json_data = {
41 'added': [],
46 'added': [],
42 'updated': [],
47 'updated': [],
43 'last_update': None,
48 'last_update': None,
44 }
49 }
45 added_posts = Post.objects.filter(thread_new=thread,
50 added_posts = Post.objects.filter(thread_new=thread,
46 pub_time__gt=filter_time) \
51 pub_time__gt=filter_time) \
47 .order_by('pub_time')
52 .order_by('pub_time')
48 updated_posts = Post.objects.filter(thread_new=thread,
53 updated_posts = Post.objects.filter(thread_new=thread,
49 pub_time__lte=filter_time,
54 pub_time__lte=filter_time,
50 last_edit_time__gt=filter_time)
55 last_edit_time__gt=filter_time)
51
56
52 diff_type = DIFF_TYPE_HTML
57 diff_type = DIFF_TYPE_HTML
53 if PARAMETER_DIFF_TYPE in request.GET:
58 if PARAMETER_DIFF_TYPE in request.GET:
54 diff_type = request.GET[PARAMETER_DIFF_TYPE]
59 diff_type = request.GET[PARAMETER_DIFF_TYPE]
55
60
56 for post in added_posts:
61 for post in added_posts:
57 json_data['added'].append(_get_post_data(post.id, diff_type, request))
62 json_data['added'].append(_get_post_data(post.id, diff_type, request))
58 for post in updated_posts:
63 for post in updated_posts:
59 json_data['updated'].append(_get_post_data(post.id, diff_type, request))
64 json_data['updated'].append(_get_post_data(post.id, diff_type, request))
60 json_data['last_update'] = datetime_to_epoch(thread.last_edit_time)
65 json_data['last_update'] = datetime_to_epoch(thread.last_edit_time)
61
66
62 return HttpResponse(content=json.dumps(json_data))
67 return HttpResponse(content=json.dumps(json_data))
63
68
64
69
65 def api_add_post(request, opening_post_id):
70 def api_add_post(request, opening_post_id):
66 """
71 """
67 Adds a post and return the JSON response for it
72 Adds a post and return the JSON response for it
68 """
73 """
69
74
70 opening_post = get_object_or_404(Post, id=opening_post_id)
75 opening_post = get_object_or_404(Post, id=opening_post_id)
71
76
72 status = STATUS_OK
77 status = STATUS_OK
73 errors = []
78 errors = []
74
79
75 if request.method == 'POST':
80 if request.method == 'POST':
76 form = PostForm(request.POST, request.FILES,
81 form = PostForm(request.POST, request.FILES,
77 error_class=PlainErrorList)
82 error_class=PlainErrorList)
78 form.session = request.session
83 form.session = request.session
79
84
80 if form.need_to_ban:
85 if form.need_to_ban:
81 # Ban user because he is suspected to be a bot
86 # Ban user because he is suspected to be a bot
82 # _ban_current_user(request)
87 # _ban_current_user(request)
83 status = STATUS_ERROR
88 status = STATUS_ERROR
84 if form.is_valid():
89 if form.is_valid():
85 result = ThreadView().new_post(request, form, opening_post,
90 result = ThreadView().new_post(request, form, opening_post,
86 html_response=False)
91 html_response=False)
87 if not result:
92 if not result:
88 status = STATUS_ERROR
93 status = STATUS_ERROR
89 else:
94 else:
90 status = STATUS_ERROR
95 status = STATUS_ERROR
91 errors = form.as_json_errors()
96 errors = form.as_json_errors()
92
97
93 response = {
98 response = {
94 'status': status,
99 'status': status,
95 'errors': errors,
100 'errors': errors,
96 }
101 }
97
102
103 logger.info('Added post via api. Status: %s' % status)
104
98 return HttpResponse(content=json.dumps(response))
105 return HttpResponse(content=json.dumps(response))
99
106
100
107
101 def get_post(request, post_id):
108 def get_post(request, post_id):
102 """
109 """
103 Gets the html of a post. Used for popups. Post can be truncated if used
110 Gets the html of a post. Used for popups. Post can be truncated if used
104 in threads list with 'truncated' get parameter.
111 in threads list with 'truncated' get parameter.
105 """
112 """
106
113
114 logger.info('Getting post #%s' % post_id)
115
107 post = get_object_or_404(Post, id=post_id)
116 post = get_object_or_404(Post, id=post_id)
108
117
109 context = RequestContext(request)
118 context = RequestContext(request)
110 context['post'] = post
119 context['post'] = post
111 if PARAMETER_TRUNCATED in request.GET:
120 if PARAMETER_TRUNCATED in request.GET:
112 context[PARAMETER_TRUNCATED] = True
121 context[PARAMETER_TRUNCATED] = True
113
122
114 return render(request, 'boards/api_post.html', context)
123 return render(request, 'boards/api_post.html', context)
115
124
116
125
117 # TODO Test this
126 # TODO Test this
118 def api_get_threads(request, count):
127 def api_get_threads(request, count):
119 """
128 """
120 Gets the JSON thread opening posts list.
129 Gets the JSON thread opening posts list.
121 Parameters that can be used for filtering:
130 Parameters that can be used for filtering:
122 tag, offset (from which thread to get results)
131 tag, offset (from which thread to get results)
123 """
132 """
124
133
125 if PARAMETER_TAG in request.GET:
134 if PARAMETER_TAG in request.GET:
126 tag_name = request.GET[PARAMETER_TAG]
135 tag_name = request.GET[PARAMETER_TAG]
127 if tag_name is not None:
136 if tag_name is not None:
128 tag = get_object_or_404(Tag, name=tag_name)
137 tag = get_object_or_404(Tag, name=tag_name)
129 threads = tag.threads.filter(archived=False)
138 threads = tag.threads.filter(archived=False)
130 else:
139 else:
131 threads = Thread.objects.filter(archived=False)
140 threads = Thread.objects.filter(archived=False)
132
141
133 if PARAMETER_OFFSET in request.GET:
142 if PARAMETER_OFFSET in request.GET:
134 offset = request.GET[PARAMETER_OFFSET]
143 offset = request.GET[PARAMETER_OFFSET]
135 offset = int(offset) if offset is not None else 0
144 offset = int(offset) if offset is not None else 0
136 else:
145 else:
137 offset = 0
146 offset = 0
138
147
139 threads = threads.order_by('-bump_time')
148 threads = threads.order_by('-bump_time')
140 threads = threads[offset:offset + int(count)]
149 threads = threads[offset:offset + int(count)]
141
150
142 opening_posts = []
151 opening_posts = []
143 for thread in threads:
152 for thread in threads:
144 opening_post = thread.get_opening_post()
153 opening_post = thread.get_opening_post()
145
154
146 # TODO Add tags, replies and images count
155 # TODO Add tags, replies and images count
147 opening_posts.append(_get_post_data(opening_post.id,
156 opening_posts.append(_get_post_data(opening_post.id,
148 include_last_update=True))
157 include_last_update=True))
149
158
150 return HttpResponse(content=json.dumps(opening_posts))
159 return HttpResponse(content=json.dumps(opening_posts))
151
160
152
161
153 # TODO Test this
162 # TODO Test this
154 def api_get_tags(request):
163 def api_get_tags(request):
155 """
164 """
156 Gets all tags or user tags.
165 Gets all tags or user tags.
157 """
166 """
158
167
159 # TODO Get favorite tags for the given user ID
168 # TODO Get favorite tags for the given user ID
160
169
161 tags = Tag.objects.get_not_empty_tags()
170 tags = Tag.objects.get_not_empty_tags()
162 tag_names = []
171 tag_names = []
163 for tag in tags:
172 for tag in tags:
164 tag_names.append(tag.name)
173 tag_names.append(tag.name)
165
174
166 return HttpResponse(content=json.dumps(tag_names))
175 return HttpResponse(content=json.dumps(tag_names))
167
176
168
177
169 # TODO The result can be cached by the thread last update time
178 # TODO The result can be cached by the thread last update time
170 # TODO Test this
179 # TODO Test this
171 def api_get_thread_posts(request, opening_post_id):
180 def api_get_thread_posts(request, opening_post_id):
172 """
181 """
173 Gets the JSON array of thread posts
182 Gets the JSON array of thread posts
174 """
183 """
175
184
176 opening_post = get_object_or_404(Post, id=opening_post_id)
185 opening_post = get_object_or_404(Post, id=opening_post_id)
177 thread = opening_post.get_thread()
186 thread = opening_post.get_thread()
178 posts = thread.get_replies()
187 posts = thread.get_replies()
179
188
180 json_data = {
189 json_data = {
181 'posts': [],
190 'posts': [],
182 'last_update': None,
191 'last_update': None,
183 }
192 }
184 json_post_list = []
193 json_post_list = []
185
194
186 for post in posts:
195 for post in posts:
187 json_post_list.append(_get_post_data(post.id))
196 json_post_list.append(_get_post_data(post.id))
188 json_data['last_update'] = datetime_to_epoch(thread.last_edit_time)
197 json_data['last_update'] = datetime_to_epoch(thread.last_edit_time)
189 json_data['posts'] = json_post_list
198 json_data['posts'] = json_post_list
190
199
191 return HttpResponse(content=json.dumps(json_data))
200 return HttpResponse(content=json.dumps(json_data))
192
201
193
202
194 def api_get_post(request, post_id):
203 def api_get_post(request, post_id):
195 """
204 """
196 Gets the JSON of a post. This can be
205 Gets the JSON of a post. This can be
197 used as and API for external clients.
206 used as and API for external clients.
198 """
207 """
199
208
200 post = get_object_or_404(Post, id=post_id)
209 post = get_object_or_404(Post, id=post_id)
201
210
202 json = serializers.serialize("json", [post], fields=(
211 json = serializers.serialize("json", [post], fields=(
203 "pub_time", "_text_rendered", "title", "text", "image",
212 "pub_time", "_text_rendered", "title", "text", "image",
204 "image_width", "image_height", "replies", "tags"
213 "image_width", "image_height", "replies", "tags"
205 ))
214 ))
206
215
207 return HttpResponse(content=json)
216 return HttpResponse(content=json)
208
217
209
218
210 # TODO Add pub time and replies
219 # TODO Add pub time and replies
211 def _get_post_data(post_id, format_type=DIFF_TYPE_JSON, request=None,
220 def _get_post_data(post_id, format_type=DIFF_TYPE_JSON, request=None,
212 include_last_update=False):
221 include_last_update=False):
213 if format_type == DIFF_TYPE_HTML:
222 if format_type == DIFF_TYPE_HTML:
214 return get_post(request, post_id).content.strip()
223 return get_post(request, post_id).content.strip()
215 elif format_type == DIFF_TYPE_JSON:
224 elif format_type == DIFF_TYPE_JSON:
216 post = get_object_or_404(Post, id=post_id)
225 post = get_object_or_404(Post, id=post_id)
217 post_json = {
226 post_json = {
218 'id': post.id,
227 'id': post.id,
219 'title': post.title,
228 'title': post.title,
220 'text': post.text.rendered,
229 'text': post.text.rendered,
221 }
230 }
222 if post.image:
231 if post.image:
223 post_json['image'] = post.image.url
232 post_json['image'] = post.image.url
224 post_json['image_preview'] = post.image.url_200x150
233 post_json['image_preview'] = post.image.url_200x150
225 if include_last_update:
234 if include_last_update:
226 post_json['bump_time'] = datetime_to_epoch(
235 post_json['bump_time'] = datetime_to_epoch(
227 post.thread_new.bump_time)
236 post.thread_new.bump_time)
228 return post_json
237 return post_json
@@ -1,252 +1,259 b''
1 # Django settings for neboard project.
1 # Django settings for neboard project.
2 import os
2 import os
3 from boards.mdx_neboard import markdown_extended
3 from boards.mdx_neboard import markdown_extended
4
4
5 DEBUG = True
5 DEBUG = True
6 TEMPLATE_DEBUG = DEBUG
6 TEMPLATE_DEBUG = DEBUG
7
7
8 ADMINS = (
8 ADMINS = (
9 # ('Your Name', 'your_email@example.com'),
9 # ('Your Name', 'your_email@example.com'),
10 ('admin', 'admin@example.com')
10 ('admin', 'admin@example.com')
11 )
11 )
12
12
13 MANAGERS = ADMINS
13 MANAGERS = ADMINS
14
14
15 DATABASES = {
15 DATABASES = {
16 'default': {
16 'default': {
17 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
17 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
18 'NAME': 'database.db', # Or path to database file if using sqlite3.
18 'NAME': 'database.db', # Or path to database file if using sqlite3.
19 'USER': '', # Not used with sqlite3.
19 'USER': '', # Not used with sqlite3.
20 'PASSWORD': '', # Not used with sqlite3.
20 'PASSWORD': '', # Not used with sqlite3.
21 'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
21 'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
22 'PORT': '', # Set to empty string for default. Not used with sqlite3.
22 'PORT': '', # Set to empty string for default. Not used with sqlite3.
23 'CONN_MAX_AGE': None,
23 'CONN_MAX_AGE': None,
24 }
24 }
25 }
25 }
26
26
27 # Local time zone for this installation. Choices can be found here:
27 # Local time zone for this installation. Choices can be found here:
28 # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
28 # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
29 # although not all choices may be available on all operating systems.
29 # although not all choices may be available on all operating systems.
30 # In a Windows environment this must be set to your system time zone.
30 # In a Windows environment this must be set to your system time zone.
31 TIME_ZONE = 'Europe/Kiev'
31 TIME_ZONE = 'Europe/Kiev'
32
32
33 # Language code for this installation. All choices can be found here:
33 # Language code for this installation. All choices can be found here:
34 # http://www.i18nguy.com/unicode/language-identifiers.html
34 # http://www.i18nguy.com/unicode/language-identifiers.html
35 LANGUAGE_CODE = 'en'
35 LANGUAGE_CODE = 'en'
36
36
37 SITE_ID = 1
37 SITE_ID = 1
38
38
39 # If you set this to False, Django will make some optimizations so as not
39 # If you set this to False, Django will make some optimizations so as not
40 # to load the internationalization machinery.
40 # to load the internationalization machinery.
41 USE_I18N = True
41 USE_I18N = True
42
42
43 # If you set this to False, Django will not format dates, numbers and
43 # If you set this to False, Django will not format dates, numbers and
44 # calendars according to the current locale.
44 # calendars according to the current locale.
45 USE_L10N = True
45 USE_L10N = True
46
46
47 # If you set this to False, Django will not use timezone-aware datetimes.
47 # If you set this to False, Django will not use timezone-aware datetimes.
48 USE_TZ = True
48 USE_TZ = True
49
49
50 # Absolute filesystem path to the directory that will hold user-uploaded files.
50 # Absolute filesystem path to the directory that will hold user-uploaded files.
51 # Example: "/home/media/media.lawrence.com/media/"
51 # Example: "/home/media/media.lawrence.com/media/"
52 MEDIA_ROOT = './media/'
52 MEDIA_ROOT = './media/'
53
53
54 # URL that handles the media served from MEDIA_ROOT. Make sure to use a
54 # URL that handles the media served from MEDIA_ROOT. Make sure to use a
55 # trailing slash.
55 # trailing slash.
56 # Examples: "http://media.lawrence.com/media/", "http://example.com/media/"
56 # Examples: "http://media.lawrence.com/media/", "http://example.com/media/"
57 MEDIA_URL = '/media/'
57 MEDIA_URL = '/media/'
58
58
59 # Absolute path to the directory static files should be collected to.
59 # Absolute path to the directory static files should be collected to.
60 # Don't put anything in this directory yourself; store your static files
60 # Don't put anything in this directory yourself; store your static files
61 # in apps' "static/" subdirectories and in STATICFILES_DIRS.
61 # in apps' "static/" subdirectories and in STATICFILES_DIRS.
62 # Example: "/home/media/media.lawrence.com/static/"
62 # Example: "/home/media/media.lawrence.com/static/"
63 STATIC_ROOT = ''
63 STATIC_ROOT = ''
64
64
65 # URL prefix for static files.
65 # URL prefix for static files.
66 # Example: "http://media.lawrence.com/static/"
66 # Example: "http://media.lawrence.com/static/"
67 STATIC_URL = '/static/'
67 STATIC_URL = '/static/'
68
68
69 # Additional locations of static files
69 # Additional locations of static files
70 # It is really a hack, put real paths, not related
70 # It is really a hack, put real paths, not related
71 STATICFILES_DIRS = (
71 STATICFILES_DIRS = (
72 os.path.dirname(__file__) + '/boards/static',
72 os.path.dirname(__file__) + '/boards/static',
73
73
74 # '/d/work/python/django/neboard/neboard/boards/static',
74 # '/d/work/python/django/neboard/neboard/boards/static',
75 # Put strings here, like "/home/html/static" or "C:/www/django/static".
75 # Put strings here, like "/home/html/static" or "C:/www/django/static".
76 # Always use forward slashes, even on Windows.
76 # Always use forward slashes, even on Windows.
77 # Don't forget to use absolute paths, not relative paths.
77 # Don't forget to use absolute paths, not relative paths.
78 )
78 )
79
79
80 # List of finder classes that know how to find static files in
80 # List of finder classes that know how to find static files in
81 # various locations.
81 # various locations.
82 STATICFILES_FINDERS = (
82 STATICFILES_FINDERS = (
83 'django.contrib.staticfiles.finders.FileSystemFinder',
83 'django.contrib.staticfiles.finders.FileSystemFinder',
84 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
84 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
85 )
85 )
86
86
87 if DEBUG:
87 if DEBUG:
88 STATICFILES_STORAGE = \
88 STATICFILES_STORAGE = \
89 'django.contrib.staticfiles.storage.StaticFilesStorage'
89 'django.contrib.staticfiles.storage.StaticFilesStorage'
90 else:
90 else:
91 STATICFILES_STORAGE = \
91 STATICFILES_STORAGE = \
92 'django.contrib.staticfiles.storage.CachedStaticFilesStorage'
92 'django.contrib.staticfiles.storage.CachedStaticFilesStorage'
93
93
94 # Make this unique, and don't share it with anybody.
94 # Make this unique, and don't share it with anybody.
95 SECRET_KEY = '@1rc$o(7=tt#kd+4s$u6wchm**z^)4x90)7f6z(i&amp;55@o11*8o'
95 SECRET_KEY = '@1rc$o(7=tt#kd+4s$u6wchm**z^)4x90)7f6z(i&amp;55@o11*8o'
96
96
97 # List of callables that know how to import templates from various sources.
97 # List of callables that know how to import templates from various sources.
98 TEMPLATE_LOADERS = (
98 TEMPLATE_LOADERS = (
99 'django.template.loaders.filesystem.Loader',
99 'django.template.loaders.filesystem.Loader',
100 'django.template.loaders.app_directories.Loader',
100 'django.template.loaders.app_directories.Loader',
101 )
101 )
102
102
103 TEMPLATE_CONTEXT_PROCESSORS = (
103 TEMPLATE_CONTEXT_PROCESSORS = (
104 'django.core.context_processors.media',
104 'django.core.context_processors.media',
105 'django.core.context_processors.static',
105 'django.core.context_processors.static',
106 'django.core.context_processors.request',
106 'django.core.context_processors.request',
107 'django.contrib.auth.context_processors.auth',
107 'django.contrib.auth.context_processors.auth',
108 )
108 )
109
109
110 MIDDLEWARE_CLASSES = (
110 MIDDLEWARE_CLASSES = (
111 'django.contrib.sessions.middleware.SessionMiddleware',
111 'django.contrib.sessions.middleware.SessionMiddleware',
112 'django.middleware.locale.LocaleMiddleware',
112 'django.middleware.locale.LocaleMiddleware',
113 'django.middleware.common.CommonMiddleware',
113 'django.middleware.common.CommonMiddleware',
114 'django.contrib.auth.middleware.AuthenticationMiddleware',
114 'django.contrib.auth.middleware.AuthenticationMiddleware',
115 'django.contrib.messages.middleware.MessageMiddleware',
115 'django.contrib.messages.middleware.MessageMiddleware',
116 'boards.middlewares.BanMiddleware',
116 'boards.middlewares.BanMiddleware',
117 'boards.middlewares.MinifyHTMLMiddleware',
117 'boards.middlewares.MinifyHTMLMiddleware',
118 )
118 )
119
119
120 ROOT_URLCONF = 'neboard.urls'
120 ROOT_URLCONF = 'neboard.urls'
121
121
122 # Python dotted path to the WSGI application used by Django's runserver.
122 # Python dotted path to the WSGI application used by Django's runserver.
123 WSGI_APPLICATION = 'neboard.wsgi.application'
123 WSGI_APPLICATION = 'neboard.wsgi.application'
124
124
125 TEMPLATE_DIRS = (
125 TEMPLATE_DIRS = (
126 # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
126 # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
127 # Always use forward slashes, even on Windows.
127 # Always use forward slashes, even on Windows.
128 # Don't forget to use absolute paths, not relative paths.
128 # Don't forget to use absolute paths, not relative paths.
129 'templates',
129 'templates',
130 )
130 )
131
131
132 INSTALLED_APPS = (
132 INSTALLED_APPS = (
133 'django.contrib.auth',
133 'django.contrib.auth',
134 'django.contrib.contenttypes',
134 'django.contrib.contenttypes',
135 'django.contrib.sessions',
135 'django.contrib.sessions',
136 # 'django.contrib.sites',
136 # 'django.contrib.sites',
137 'django.contrib.messages',
137 'django.contrib.messages',
138 'django.contrib.staticfiles',
138 'django.contrib.staticfiles',
139 # Uncomment the next line to enable the admin:
139 # Uncomment the next line to enable the admin:
140 'django.contrib.admin',
140 'django.contrib.admin',
141 # Uncomment the next line to enable admin documentation:
141 # Uncomment the next line to enable admin documentation:
142 # 'django.contrib.admindocs',
142 # 'django.contrib.admindocs',
143 'django.contrib.humanize',
143 'django.contrib.humanize',
144 'django_cleanup',
144 'django_cleanup',
145 'boards',
145 'boards',
146 'captcha',
146 'captcha',
147 'south',
147 'south',
148 'debug_toolbar',
148 'debug_toolbar',
149 )
149 )
150
150
151 DEBUG_TOOLBAR_PANELS = (
151 DEBUG_TOOLBAR_PANELS = (
152 'debug_toolbar.panels.version.VersionDebugPanel',
152 'debug_toolbar.panels.version.VersionDebugPanel',
153 'debug_toolbar.panels.timer.TimerDebugPanel',
153 'debug_toolbar.panels.timer.TimerDebugPanel',
154 'debug_toolbar.panels.settings_vars.SettingsVarsDebugPanel',
154 'debug_toolbar.panels.settings_vars.SettingsVarsDebugPanel',
155 'debug_toolbar.panels.headers.HeaderDebugPanel',
155 'debug_toolbar.panels.headers.HeaderDebugPanel',
156 'debug_toolbar.panels.request_vars.RequestVarsDebugPanel',
156 'debug_toolbar.panels.request_vars.RequestVarsDebugPanel',
157 'debug_toolbar.panels.template.TemplateDebugPanel',
157 'debug_toolbar.panels.template.TemplateDebugPanel',
158 'debug_toolbar.panels.sql.SQLDebugPanel',
158 'debug_toolbar.panels.sql.SQLDebugPanel',
159 'debug_toolbar.panels.signals.SignalDebugPanel',
159 'debug_toolbar.panels.signals.SignalDebugPanel',
160 'debug_toolbar.panels.logger.LoggingPanel',
160 'debug_toolbar.panels.logger.LoggingPanel',
161 )
161 )
162
162
163 # TODO: NEED DESIGN FIXES
163 # TODO: NEED DESIGN FIXES
164 CAPTCHA_OUTPUT_FORMAT = (u' %(hidden_field)s '
164 CAPTCHA_OUTPUT_FORMAT = (u' %(hidden_field)s '
165 u'<div class="form-label">%(image)s</div>'
165 u'<div class="form-label">%(image)s</div>'
166 u'<div class="form-text">%(text_field)s</div>')
166 u'<div class="form-text">%(text_field)s</div>')
167
167
168 # A sample logging configuration. The only tangible logging
168 # A sample logging configuration. The only tangible logging
169 # performed by this configuration is to send an email to
169 # performed by this configuration is to send an email to
170 # the site admins on every HTTP 500 error when DEBUG=False.
170 # the site admins on every HTTP 500 error when DEBUG=False.
171 # See http://docs.djangoproject.com/en/dev/topics/logging for
171 # See http://docs.djangoproject.com/en/dev/topics/logging for
172 # more details on how to customize your logging configuration.
172 # more details on how to customize your logging configuration.
173 LOGGING = {
173 LOGGING = {
174 'version': 1,
174 'version': 1,
175 'disable_existing_loggers': False,
175 'disable_existing_loggers': False,
176 'formatters': {
177 'verbose': {
178 'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
179 },
180 'simple': {
181 'format': '%(levelname)s %(asctime)s [%(module)s] %(message)s'
182 },
183 },
176 'filters': {
184 'filters': {
177 'require_debug_false': {
185 'require_debug_false': {
178 '()': 'django.utils.log.RequireDebugFalse'
186 '()': 'django.utils.log.RequireDebugFalse'
179 }
187 }
180 },
188 },
181 'handlers': {
189 'handlers': {
182 'mail_admins': {
190 'console': {
183 'level': 'ERROR',
191 'level': 'DEBUG',
184 'filters': ['require_debug_false'],
192 'class': 'logging.StreamHandler',
185 'class': 'django.utils.log.AdminEmailHandler'
193 'formatter': 'simple'
186 }
194 },
187 },
195 },
188 'loggers': {
196 'loggers': {
189 'django.request': {
197 'boards': {
190 'handlers': ['mail_admins'],
198 'handlers': ['console'],
191 'level': 'ERROR',
199 'level': 'DEBUG',
192 'propagate': True,
200 }
193 },
201 },
194 }
202 }
195 }
196
203
197 MARKUP_FIELD_TYPES = (
204 MARKUP_FIELD_TYPES = (
198 ('markdown', markdown_extended),
205 ('markdown', markdown_extended),
199 )
206 )
200 # Custom imageboard settings
207 # Custom imageboard settings
201 # TODO These should me moved to
208 # TODO These should me moved to
202 MAX_POSTS_PER_THREAD = 10 # Thread bumplimit
209 MAX_POSTS_PER_THREAD = 10 # Thread bumplimit
203 MAX_THREAD_COUNT = 5 # Old threads will be deleted to preserve this count
210 MAX_THREAD_COUNT = 5 # Old threads will be deleted to preserve this count
204 THREADS_PER_PAGE = 3
211 THREADS_PER_PAGE = 3
205 SITE_NAME = 'Neboard'
212 SITE_NAME = 'Neboard'
206
213
207 THEMES = [
214 THEMES = [
208 ('md', 'Mystic Dark'),
215 ('md', 'Mystic Dark'),
209 ('md_centered', 'Mystic Dark (centered)'),
216 ('md_centered', 'Mystic Dark (centered)'),
210 ('sw', 'Snow White'),
217 ('sw', 'Snow White'),
211 ('pg', 'Photon Gray'),
218 ('pg', 'Photon Gray'),
212 ]
219 ]
213
220
214 DEFAULT_THEME = 'md'
221 DEFAULT_THEME = 'md'
215
222
216 POPULAR_TAGS = 10
223 POPULAR_TAGS = 10
217 LAST_REPLIES_COUNT = 3
224 LAST_REPLIES_COUNT = 3
218
225
219 ENABLE_CAPTCHA = False
226 ENABLE_CAPTCHA = False
220 # if user tries to post before CAPTCHA_DEFAULT_SAFE_TIME. Captcha will be shown
227 # if user tries to post before CAPTCHA_DEFAULT_SAFE_TIME. Captcha will be shown
221 CAPTCHA_DEFAULT_SAFE_TIME = 30 # seconds
228 CAPTCHA_DEFAULT_SAFE_TIME = 30 # seconds
222 POSTING_DELAY = 20 # seconds
229 POSTING_DELAY = 20 # seconds
223
230
224 COMPRESS_HTML = True
231 COMPRESS_HTML = True
225
232
226 VERSION = '1.7.2 Anubis'
233 VERSION = '1.7.2 Anubis'
227
234
228 # Debug mode middlewares
235 # Debug mode middlewares
229 if DEBUG:
236 if DEBUG:
230
237
231 SITE_NAME += ' DEBUG'
238 SITE_NAME += ' DEBUG'
232
239
233 MIDDLEWARE_CLASSES += (
240 MIDDLEWARE_CLASSES += (
234 'boards.profiler.ProfilerMiddleware',
241 'boards.profiler.ProfilerMiddleware',
235 'debug_toolbar.middleware.DebugToolbarMiddleware',
242 'debug_toolbar.middleware.DebugToolbarMiddleware',
236 )
243 )
237
244
238 def custom_show_toolbar(request):
245 def custom_show_toolbar(request):
239 return DEBUG
246 return DEBUG
240
247
241 DEBUG_TOOLBAR_CONFIG = {
248 DEBUG_TOOLBAR_CONFIG = {
242 'INTERCEPT_REDIRECTS': False,
249 'INTERCEPT_REDIRECTS': False,
243 'SHOW_TOOLBAR_CALLBACK': custom_show_toolbar,
250 'SHOW_TOOLBAR_CALLBACK': custom_show_toolbar,
244 'HIDE_DJANGO_SQL': False,
251 'HIDE_DJANGO_SQL': False,
245 'ENABLE_STACKTRACES': True,
252 'ENABLE_STACKTRACES': True,
246 }
253 }
247
254
248 # FIXME Uncommenting this fails somehow. Need to investigate this
255 # FIXME Uncommenting this fails somehow. Need to investigate this
249 #DEBUG_TOOLBAR_PANELS += (
256 #DEBUG_TOOLBAR_PANELS += (
250 # 'debug_toolbar.panels.profiling.ProfilingDebugPanel',
257 # 'debug_toolbar.panels.profiling.ProfilingDebugPanel',
251 #)
258 #)
252
259
General Comments 0
You need to be logged in to leave comments. Login now