##// END OF EJS Templates
Another log names change
neko259 -
r868:07fdef4a 2.2.1 default
parent child Browse files
Show More
@@ -1,441 +1,441 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 from adjacent import Client
3 from adjacent import Client
4 import logging
4 import logging
5 import re
5 import re
6
6
7 from django.core.cache import cache
7 from django.core.cache import cache
8 from django.core.urlresolvers import reverse
8 from django.core.urlresolvers import reverse
9 from django.db import models, transaction
9 from django.db import models, transaction
10 from django.shortcuts import get_object_or_404
10 from django.shortcuts import get_object_or_404
11 from django.template import RequestContext
11 from django.template import RequestContext
12 from django.template.loader import render_to_string
12 from django.template.loader import render_to_string
13 from django.utils import timezone
13 from django.utils import timezone
14 from markupfield.fields import MarkupField
14 from markupfield.fields import MarkupField
15 from boards import settings
15 from boards import settings
16
16
17 from boards.models import PostImage
17 from boards.models import PostImage
18 from boards.models.base import Viewable
18 from boards.models.base import Viewable
19 from boards.models.thread import Thread
19 from boards.models.thread import Thread
20 from boards.utils import datetime_to_epoch
20 from boards.utils import datetime_to_epoch
21
21
22 WS_CHANNEL_THREAD = "thread:"
22 WS_CHANNEL_THREAD = "thread:"
23
23
24 APP_LABEL_BOARDS = 'boards'
24 APP_LABEL_BOARDS = 'boards'
25
25
26 CACHE_KEY_PPD = 'ppd'
26 CACHE_KEY_PPD = 'ppd'
27 CACHE_KEY_POST_URL = 'post_url'
27 CACHE_KEY_POST_URL = 'post_url'
28
28
29 POSTS_PER_DAY_RANGE = 7
29 POSTS_PER_DAY_RANGE = 7
30
30
31 BAN_REASON_AUTO = 'Auto'
31 BAN_REASON_AUTO = 'Auto'
32
32
33 IMAGE_THUMB_SIZE = (200, 150)
33 IMAGE_THUMB_SIZE = (200, 150)
34
34
35 TITLE_MAX_LENGTH = 200
35 TITLE_MAX_LENGTH = 200
36
36
37 DEFAULT_MARKUP_TYPE = 'bbcode'
37 DEFAULT_MARKUP_TYPE = 'bbcode'
38
38
39 # TODO This should be removed
39 # TODO This should be removed
40 NO_IP = '0.0.0.0'
40 NO_IP = '0.0.0.0'
41
41
42 # TODO Real user agent should be saved instead of this
42 # TODO Real user agent should be saved instead of this
43 UNKNOWN_UA = ''
43 UNKNOWN_UA = ''
44
44
45 REGEX_REPLY = re.compile(r'\[post\](\d+)\[/post\]')
45 REGEX_REPLY = re.compile(r'\[post\](\d+)\[/post\]')
46
46
47 PARAMETER_TRUNCATED = 'truncated'
47 PARAMETER_TRUNCATED = 'truncated'
48 PARAMETER_TAG = 'tag'
48 PARAMETER_TAG = 'tag'
49 PARAMETER_OFFSET = 'offset'
49 PARAMETER_OFFSET = 'offset'
50 PARAMETER_DIFF_TYPE = 'type'
50 PARAMETER_DIFF_TYPE = 'type'
51
51
52 DIFF_TYPE_HTML = 'html'
52 DIFF_TYPE_HTML = 'html'
53 DIFF_TYPE_JSON = 'json'
53 DIFF_TYPE_JSON = 'json'
54
54
55 PREPARSE_PATTERNS = {
55 PREPARSE_PATTERNS = {
56 r'>>(\d+)': r'[post]\1[/post]',
56 r'>>(\d+)': r'[post]\1[/post]',
57 r'>(.+)': r'[quote]\1[/quote]'
57 r'>(.+)': r'[quote]\1[/quote]'
58 }
58 }
59
59
60
60
61 class PostManager(models.Manager):
61 class PostManager(models.Manager):
62 def create_post(self, title, text, image=None, thread=None, ip=NO_IP,
62 def create_post(self, title, text, image=None, thread=None, ip=NO_IP,
63 tags=None):
63 tags=None):
64 """
64 """
65 Creates new post
65 Creates new post
66 """
66 """
67
67
68 if not tags:
68 if not tags:
69 tags = []
69 tags = []
70
70
71 posting_time = timezone.now()
71 posting_time = timezone.now()
72 if not thread:
72 if not thread:
73 thread = Thread.objects.create(bump_time=posting_time,
73 thread = Thread.objects.create(bump_time=posting_time,
74 last_edit_time=posting_time)
74 last_edit_time=posting_time)
75 new_thread = True
75 new_thread = True
76 else:
76 else:
77 thread.bump()
77 thread.bump()
78 thread.last_edit_time = posting_time
78 thread.last_edit_time = posting_time
79 if thread.can_bump() and (
79 if thread.can_bump() and (
80 thread.get_reply_count() >= settings.MAX_POSTS_PER_THREAD):
80 thread.get_reply_count() >= settings.MAX_POSTS_PER_THREAD):
81 thread.bumpable = False
81 thread.bumpable = False
82 thread.save()
82 thread.save()
83 new_thread = False
83 new_thread = False
84
84
85 pre_text = self._preparse_text(text)
85 pre_text = self._preparse_text(text)
86
86
87 post = self.create(title=title,
87 post = self.create(title=title,
88 text=pre_text,
88 text=pre_text,
89 pub_time=posting_time,
89 pub_time=posting_time,
90 thread_new=thread,
90 thread_new=thread,
91 poster_ip=ip,
91 poster_ip=ip,
92 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
92 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
93 # last!
93 # last!
94 last_edit_time=posting_time)
94 last_edit_time=posting_time)
95
95
96 logger = logging.getLogger('create_post')
96 logger = logging.getLogger('boards.post.create')
97
97
98 logger.info('Created post #%d with title "%s"' % (post.id,
98 logger.info('Created post #%d with title "%s"' % (post.id,
99 post.title))
99 post.title))
100
100
101 if image:
101 if image:
102 post_image = PostImage.objects.create(image=image)
102 post_image = PostImage.objects.create(image=image)
103 post.images.add(post_image)
103 post.images.add(post_image)
104 logger.info('Created image #%d for post #%d' % (post_image.id,
104 logger.info('Created image #%d for post #%d' % (post_image.id,
105 post.id))
105 post.id))
106
106
107 thread.replies.add(post)
107 thread.replies.add(post)
108 list(map(thread.add_tag, tags))
108 list(map(thread.add_tag, tags))
109
109
110 if new_thread:
110 if new_thread:
111 Thread.objects.process_oldest_threads()
111 Thread.objects.process_oldest_threads()
112 self.connect_replies(post)
112 self.connect_replies(post)
113
113
114
114
115 return post
115 return post
116
116
117 def delete_post(self, post):
117 def delete_post(self, post):
118 """
118 """
119 Deletes post and update or delete its thread
119 Deletes post and update or delete its thread
120 """
120 """
121
121
122 post_id = post.id
122 post_id = post.id
123
123
124 thread = post.get_thread()
124 thread = post.get_thread()
125
125
126 if post.is_opening():
126 if post.is_opening():
127 thread.delete()
127 thread.delete()
128 else:
128 else:
129 thread.last_edit_time = timezone.now()
129 thread.last_edit_time = timezone.now()
130 thread.save()
130 thread.save()
131
131
132 post.delete()
132 post.delete()
133
133
134 logging.getLogger('delete_post').info('Deleted post #%d (%s)' % (post_id, post.get_title()))
134 logging.getLogger('boards.post.delete').info('Deleted post #%d (%s)' % (post_id, post.get_title()))
135
135
136 def delete_posts_by_ip(self, ip):
136 def delete_posts_by_ip(self, ip):
137 """
137 """
138 Deletes all posts of the author with same IP
138 Deletes all posts of the author with same IP
139 """
139 """
140
140
141 posts = self.filter(poster_ip=ip)
141 posts = self.filter(poster_ip=ip)
142 for post in posts:
142 for post in posts:
143 self.delete_post(post)
143 self.delete_post(post)
144
144
145 def connect_replies(self, post):
145 def connect_replies(self, post):
146 """
146 """
147 Connects replies to a post to show them as a reflink map
147 Connects replies to a post to show them as a reflink map
148 """
148 """
149
149
150 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
150 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
151 post_id = reply_number.group(1)
151 post_id = reply_number.group(1)
152 ref_post = self.filter(id=post_id)
152 ref_post = self.filter(id=post_id)
153 if ref_post.count() > 0:
153 if ref_post.count() > 0:
154 referenced_post = ref_post[0]
154 referenced_post = ref_post[0]
155 referenced_post.referenced_posts.add(post)
155 referenced_post.referenced_posts.add(post)
156 referenced_post.last_edit_time = post.pub_time
156 referenced_post.last_edit_time = post.pub_time
157 referenced_post.build_refmap()
157 referenced_post.build_refmap()
158 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
158 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
159
159
160 referenced_thread = referenced_post.get_thread()
160 referenced_thread = referenced_post.get_thread()
161 referenced_thread.last_edit_time = post.pub_time
161 referenced_thread.last_edit_time = post.pub_time
162 referenced_thread.save(update_fields=['last_edit_time'])
162 referenced_thread.save(update_fields=['last_edit_time'])
163
163
164 def get_posts_per_day(self):
164 def get_posts_per_day(self):
165 """
165 """
166 Gets average count of posts per day for the last 7 days
166 Gets average count of posts per day for the last 7 days
167 """
167 """
168
168
169 day_end = date.today()
169 day_end = date.today()
170 day_start = day_end - timedelta(POSTS_PER_DAY_RANGE)
170 day_start = day_end - timedelta(POSTS_PER_DAY_RANGE)
171
171
172 cache_key = CACHE_KEY_PPD + str(day_end)
172 cache_key = CACHE_KEY_PPD + str(day_end)
173 ppd = cache.get(cache_key)
173 ppd = cache.get(cache_key)
174 if ppd:
174 if ppd:
175 return ppd
175 return ppd
176
176
177 day_time_start = timezone.make_aware(datetime.combine(
177 day_time_start = timezone.make_aware(datetime.combine(
178 day_start, dtime()), timezone.get_current_timezone())
178 day_start, dtime()), timezone.get_current_timezone())
179 day_time_end = timezone.make_aware(datetime.combine(
179 day_time_end = timezone.make_aware(datetime.combine(
180 day_end, dtime()), timezone.get_current_timezone())
180 day_end, dtime()), timezone.get_current_timezone())
181
181
182 posts_per_period = float(self.filter(
182 posts_per_period = float(self.filter(
183 pub_time__lte=day_time_end,
183 pub_time__lte=day_time_end,
184 pub_time__gte=day_time_start).count())
184 pub_time__gte=day_time_start).count())
185
185
186 ppd = posts_per_period / POSTS_PER_DAY_RANGE
186 ppd = posts_per_period / POSTS_PER_DAY_RANGE
187
187
188 cache.set(cache_key, ppd)
188 cache.set(cache_key, ppd)
189 return ppd
189 return ppd
190
190
191 def _preparse_text(self, text):
191 def _preparse_text(self, text):
192 """
192 """
193 Preparses text to change patterns like '>>' to a proper bbcode
193 Preparses text to change patterns like '>>' to a proper bbcode
194 tags.
194 tags.
195 """
195 """
196
196
197 for key, value in PREPARSE_PATTERNS.items():
197 for key, value in PREPARSE_PATTERNS.items():
198 text = re.sub(key, value, text)
198 text = re.sub(key, value, text)
199
199
200 return text
200 return text
201
201
202
202
203 class Post(models.Model, Viewable):
203 class Post(models.Model, Viewable):
204 """A post is a message."""
204 """A post is a message."""
205
205
206 objects = PostManager()
206 objects = PostManager()
207
207
208 class Meta:
208 class Meta:
209 app_label = APP_LABEL_BOARDS
209 app_label = APP_LABEL_BOARDS
210 ordering = ('id',)
210 ordering = ('id',)
211
211
212 title = models.CharField(max_length=TITLE_MAX_LENGTH)
212 title = models.CharField(max_length=TITLE_MAX_LENGTH)
213 pub_time = models.DateTimeField()
213 pub_time = models.DateTimeField()
214 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
214 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
215 escape_html=False)
215 escape_html=False)
216
216
217 images = models.ManyToManyField(PostImage, null=True, blank=True,
217 images = models.ManyToManyField(PostImage, null=True, blank=True,
218 related_name='ip+', db_index=True)
218 related_name='ip+', db_index=True)
219
219
220 poster_ip = models.GenericIPAddressField()
220 poster_ip = models.GenericIPAddressField()
221 poster_user_agent = models.TextField()
221 poster_user_agent = models.TextField()
222
222
223 thread_new = models.ForeignKey('Thread', null=True, default=None,
223 thread_new = models.ForeignKey('Thread', null=True, default=None,
224 db_index=True)
224 db_index=True)
225 last_edit_time = models.DateTimeField()
225 last_edit_time = models.DateTimeField()
226
226
227 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
227 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
228 null=True,
228 null=True,
229 blank=True, related_name='rfp+',
229 blank=True, related_name='rfp+',
230 db_index=True)
230 db_index=True)
231 refmap = models.TextField(null=True, blank=True)
231 refmap = models.TextField(null=True, blank=True)
232
232
233 def __unicode__(self):
233 def __unicode__(self):
234 return '#' + str(self.id) + ' ' + self.title + ' (' + \
234 return '#' + str(self.id) + ' ' + self.title + ' (' + \
235 self.text.raw[:50] + ')'
235 self.text.raw[:50] + ')'
236
236
237 def get_title(self):
237 def get_title(self):
238 """
238 """
239 Gets original post title or part of its text.
239 Gets original post title or part of its text.
240 """
240 """
241
241
242 title = self.title
242 title = self.title
243 if not title:
243 if not title:
244 title = self.text.rendered
244 title = self.text.rendered
245
245
246 return title
246 return title
247
247
248 def build_refmap(self):
248 def build_refmap(self):
249 """
249 """
250 Builds a replies map string from replies list. This is a cache to stop
250 Builds a replies map string from replies list. This is a cache to stop
251 the server from recalculating the map on every post show.
251 the server from recalculating the map on every post show.
252 """
252 """
253 map_string = ''
253 map_string = ''
254
254
255 first = True
255 first = True
256 for refpost in self.referenced_posts.all():
256 for refpost in self.referenced_posts.all():
257 if not first:
257 if not first:
258 map_string += ', '
258 map_string += ', '
259 map_string += '<a href="%s">&gt;&gt;%s</a>' % (refpost.get_url(),
259 map_string += '<a href="%s">&gt;&gt;%s</a>' % (refpost.get_url(),
260 refpost.id)
260 refpost.id)
261 first = False
261 first = False
262
262
263 self.refmap = map_string
263 self.refmap = map_string
264
264
265 def get_sorted_referenced_posts(self):
265 def get_sorted_referenced_posts(self):
266 return self.refmap
266 return self.refmap
267
267
268 def is_referenced(self):
268 def is_referenced(self):
269 return len(self.refmap) > 0
269 return len(self.refmap) > 0
270
270
271 def is_opening(self):
271 def is_opening(self):
272 """
272 """
273 Checks if this is an opening post or just a reply.
273 Checks if this is an opening post or just a reply.
274 """
274 """
275
275
276 return self.get_thread().get_opening_post_id() == self.id
276 return self.get_thread().get_opening_post_id() == self.id
277
277
278 @transaction.atomic
278 @transaction.atomic
279 def add_tag(self, tag):
279 def add_tag(self, tag):
280 edit_time = timezone.now()
280 edit_time = timezone.now()
281
281
282 thread = self.get_thread()
282 thread = self.get_thread()
283 thread.add_tag(tag)
283 thread.add_tag(tag)
284 self.last_edit_time = edit_time
284 self.last_edit_time = edit_time
285 self.save(update_fields=['last_edit_time'])
285 self.save(update_fields=['last_edit_time'])
286
286
287 thread.last_edit_time = edit_time
287 thread.last_edit_time = edit_time
288 thread.save(update_fields=['last_edit_time'])
288 thread.save(update_fields=['last_edit_time'])
289
289
290 @transaction.atomic
290 @transaction.atomic
291 def remove_tag(self, tag):
291 def remove_tag(self, tag):
292 edit_time = timezone.now()
292 edit_time = timezone.now()
293
293
294 thread = self.get_thread()
294 thread = self.get_thread()
295 thread.remove_tag(tag)
295 thread.remove_tag(tag)
296 self.last_edit_time = edit_time
296 self.last_edit_time = edit_time
297 self.save(update_fields=['last_edit_time'])
297 self.save(update_fields=['last_edit_time'])
298
298
299 thread.last_edit_time = edit_time
299 thread.last_edit_time = edit_time
300 thread.save(update_fields=['last_edit_time'])
300 thread.save(update_fields=['last_edit_time'])
301
301
302 def get_url(self, thread=None):
302 def get_url(self, thread=None):
303 """
303 """
304 Gets full url to the post.
304 Gets full url to the post.
305 """
305 """
306
306
307 cache_key = CACHE_KEY_POST_URL + str(self.id)
307 cache_key = CACHE_KEY_POST_URL + str(self.id)
308 link = cache.get(cache_key)
308 link = cache.get(cache_key)
309
309
310 if not link:
310 if not link:
311 if not thread:
311 if not thread:
312 thread = self.get_thread()
312 thread = self.get_thread()
313
313
314 opening_id = thread.get_opening_post_id()
314 opening_id = thread.get_opening_post_id()
315
315
316 if self.id != opening_id:
316 if self.id != opening_id:
317 link = reverse('thread', kwargs={
317 link = reverse('thread', kwargs={
318 'post_id': opening_id}) + '#' + str(self.id)
318 'post_id': opening_id}) + '#' + str(self.id)
319 else:
319 else:
320 link = reverse('thread', kwargs={'post_id': self.id})
320 link = reverse('thread', kwargs={'post_id': self.id})
321
321
322 cache.set(cache_key, link)
322 cache.set(cache_key, link)
323
323
324 return link
324 return link
325
325
326 def get_thread(self):
326 def get_thread(self):
327 """
327 """
328 Gets post's thread.
328 Gets post's thread.
329 """
329 """
330
330
331 return self.thread_new
331 return self.thread_new
332
332
333 def get_referenced_posts(self):
333 def get_referenced_posts(self):
334 return self.referenced_posts.only('id', 'thread_new')
334 return self.referenced_posts.only('id', 'thread_new')
335
335
336 def get_text(self):
336 def get_text(self):
337 return self.text
337 return self.text
338
338
339 def get_view(self, moderator=False, need_open_link=False,
339 def get_view(self, moderator=False, need_open_link=False,
340 truncated=False, *args, **kwargs):
340 truncated=False, *args, **kwargs):
341 if 'is_opening' in kwargs:
341 if 'is_opening' in kwargs:
342 is_opening = kwargs['is_opening']
342 is_opening = kwargs['is_opening']
343 else:
343 else:
344 is_opening = self.is_opening()
344 is_opening = self.is_opening()
345
345
346 if 'thread' in kwargs:
346 if 'thread' in kwargs:
347 thread = kwargs['thread']
347 thread = kwargs['thread']
348 else:
348 else:
349 thread = self.get_thread()
349 thread = self.get_thread()
350
350
351 if 'can_bump' in kwargs:
351 if 'can_bump' in kwargs:
352 can_bump = kwargs['can_bump']
352 can_bump = kwargs['can_bump']
353 else:
353 else:
354 can_bump = thread.can_bump()
354 can_bump = thread.can_bump()
355
355
356 if is_opening:
356 if is_opening:
357 opening_post_id = self.id
357 opening_post_id = self.id
358 else:
358 else:
359 opening_post_id = thread.get_opening_post_id()
359 opening_post_id = thread.get_opening_post_id()
360
360
361 return render_to_string('boards/post.html', {
361 return render_to_string('boards/post.html', {
362 'post': self,
362 'post': self,
363 'moderator': moderator,
363 'moderator': moderator,
364 'is_opening': is_opening,
364 'is_opening': is_opening,
365 'thread': thread,
365 'thread': thread,
366 'bumpable': can_bump,
366 'bumpable': can_bump,
367 'need_open_link': need_open_link,
367 'need_open_link': need_open_link,
368 'truncated': truncated,
368 'truncated': truncated,
369 'opening_post_id': opening_post_id,
369 'opening_post_id': opening_post_id,
370 })
370 })
371
371
372 def get_first_image(self):
372 def get_first_image(self):
373 return self.images.earliest('id')
373 return self.images.earliest('id')
374
374
375 def delete(self, using=None):
375 def delete(self, using=None):
376 """
376 """
377 Deletes all post images and the post itself.
377 Deletes all post images and the post itself.
378 """
378 """
379
379
380 self.images.all().delete()
380 self.images.all().delete()
381
381
382 super(Post, self).delete(using)
382 super(Post, self).delete(using)
383
383
384 def get_post_data(self, format_type=DIFF_TYPE_JSON, request=None,
384 def get_post_data(self, format_type=DIFF_TYPE_JSON, request=None,
385 include_last_update=False):
385 include_last_update=False):
386 """
386 """
387 Gets post HTML or JSON data that can be rendered on a page or used by
387 Gets post HTML or JSON data that can be rendered on a page or used by
388 API.
388 API.
389 """
389 """
390
390
391 if format_type == DIFF_TYPE_HTML:
391 if format_type == DIFF_TYPE_HTML:
392 context = RequestContext(request)
392 context = RequestContext(request)
393 context['post'] = self
393 context['post'] = self
394 if PARAMETER_TRUNCATED in request.GET:
394 if PARAMETER_TRUNCATED in request.GET:
395 context[PARAMETER_TRUNCATED] = True
395 context[PARAMETER_TRUNCATED] = True
396
396
397 return render_to_string('boards/api_post.html', context)
397 return render_to_string('boards/api_post.html', context)
398 elif format_type == DIFF_TYPE_JSON:
398 elif format_type == DIFF_TYPE_JSON:
399 post_json = {
399 post_json = {
400 'id': self.id,
400 'id': self.id,
401 'title': self.title,
401 'title': self.title,
402 'text': self.text.rendered,
402 'text': self.text.rendered,
403 }
403 }
404 if self.images.exists():
404 if self.images.exists():
405 post_image = self.get_first_image()
405 post_image = self.get_first_image()
406 post_json['image'] = post_image.image.url
406 post_json['image'] = post_image.image.url
407 post_json['image_preview'] = post_image.image.url_200x150
407 post_json['image_preview'] = post_image.image.url_200x150
408 if include_last_update:
408 if include_last_update:
409 post_json['bump_time'] = datetime_to_epoch(
409 post_json['bump_time'] = datetime_to_epoch(
410 self.thread_new.bump_time)
410 self.thread_new.bump_time)
411 return post_json
411 return post_json
412
412
413 def send_to_websocket(self, request, recursive=True):
413 def send_to_websocket(self, request, recursive=True):
414 """
414 """
415 Sends post HTML data to the thread web socket.
415 Sends post HTML data to the thread web socket.
416 """
416 """
417
417
418 if not settings.WEBSOCKETS_ENABLED:
418 if not settings.WEBSOCKETS_ENABLED:
419 return
419 return
420
420
421 client = Client()
421 client = Client()
422
422
423 channel_name = WS_CHANNEL_THREAD + str(self.get_thread().get_opening_post_id())
423 channel_name = WS_CHANNEL_THREAD + str(self.get_thread().get_opening_post_id())
424 client.publish(channel_name, {
424 client.publish(channel_name, {
425 'html': self.get_post_data(
425 'html': self.get_post_data(
426 format_type=DIFF_TYPE_HTML,
426 format_type=DIFF_TYPE_HTML,
427 request=request),
427 request=request),
428 'diff_type': 'added' if recursive else 'updated',
428 'diff_type': 'added' if recursive else 'updated',
429 })
429 })
430 client.send()
430 client.send()
431
431
432 logger = logging.getLogger('websocket')
432 logger = logging.getLogger('boards.post.websocket')
433
433
434 logger.info('Sent post #{} to channel {}'.format(self.id, channel_name))
434 logger.info('Sent post #{} to channel {}'.format(self.id, channel_name))
435
435
436 if recursive:
436 if recursive:
437 for reply_number in re.finditer(REGEX_REPLY, self.text.raw):
437 for reply_number in re.finditer(REGEX_REPLY, self.text.raw):
438 post_id = reply_number.group(1)
438 post_id = reply_number.group(1)
439 ref_post = Post.objects.filter(id=post_id)[0]
439 ref_post = Post.objects.filter(id=post_id)[0]
440
440
441 ref_post.send_to_websocket(request, recursive=False)
441 ref_post.send_to_websocket(request, recursive=False)
@@ -1,245 +1,245 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 bbcode_extended
3 from boards.mdx_neboard import bbcode_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 'compressor.finders.CompressorFinder',
85 'compressor.finders.CompressorFinder',
86 )
86 )
87
87
88 if DEBUG:
88 if DEBUG:
89 STATICFILES_STORAGE = \
89 STATICFILES_STORAGE = \
90 'django.contrib.staticfiles.storage.StaticFilesStorage'
90 'django.contrib.staticfiles.storage.StaticFilesStorage'
91 else:
91 else:
92 STATICFILES_STORAGE = \
92 STATICFILES_STORAGE = \
93 'django.contrib.staticfiles.storage.CachedStaticFilesStorage'
93 'django.contrib.staticfiles.storage.CachedStaticFilesStorage'
94
94
95 # Make this unique, and don't share it with anybody.
95 # Make this unique, and don't share it with anybody.
96 SECRET_KEY = '@1rc$o(7=tt#kd+4s$u6wchm**z^)4x90)7f6z(i&amp;55@o11*8o'
96 SECRET_KEY = '@1rc$o(7=tt#kd+4s$u6wchm**z^)4x90)7f6z(i&amp;55@o11*8o'
97
97
98 # List of callables that know how to import templates from various sources.
98 # List of callables that know how to import templates from various sources.
99 TEMPLATE_LOADERS = (
99 TEMPLATE_LOADERS = (
100 'django.template.loaders.filesystem.Loader',
100 'django.template.loaders.filesystem.Loader',
101 'django.template.loaders.app_directories.Loader',
101 'django.template.loaders.app_directories.Loader',
102 )
102 )
103
103
104 TEMPLATE_CONTEXT_PROCESSORS = (
104 TEMPLATE_CONTEXT_PROCESSORS = (
105 'django.core.context_processors.media',
105 'django.core.context_processors.media',
106 'django.core.context_processors.static',
106 'django.core.context_processors.static',
107 'django.core.context_processors.request',
107 'django.core.context_processors.request',
108 'django.contrib.auth.context_processors.auth',
108 'django.contrib.auth.context_processors.auth',
109 'boards.context_processors.user_and_ui_processor',
109 'boards.context_processors.user_and_ui_processor',
110 )
110 )
111
111
112 MIDDLEWARE_CLASSES = (
112 MIDDLEWARE_CLASSES = (
113 'django.contrib.sessions.middleware.SessionMiddleware',
113 'django.contrib.sessions.middleware.SessionMiddleware',
114 'django.middleware.locale.LocaleMiddleware',
114 'django.middleware.locale.LocaleMiddleware',
115 'django.middleware.common.CommonMiddleware',
115 'django.middleware.common.CommonMiddleware',
116 'django.contrib.auth.middleware.AuthenticationMiddleware',
116 'django.contrib.auth.middleware.AuthenticationMiddleware',
117 'django.contrib.messages.middleware.MessageMiddleware',
117 'django.contrib.messages.middleware.MessageMiddleware',
118 'boards.middlewares.BanMiddleware',
118 'boards.middlewares.BanMiddleware',
119 'boards.middlewares.MinifyHTMLMiddleware',
119 'boards.middlewares.MinifyHTMLMiddleware',
120 )
120 )
121
121
122 ROOT_URLCONF = 'neboard.urls'
122 ROOT_URLCONF = 'neboard.urls'
123
123
124 # Python dotted path to the WSGI application used by Django's runserver.
124 # Python dotted path to the WSGI application used by Django's runserver.
125 WSGI_APPLICATION = 'neboard.wsgi.application'
125 WSGI_APPLICATION = 'neboard.wsgi.application'
126
126
127 TEMPLATE_DIRS = (
127 TEMPLATE_DIRS = (
128 # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
128 # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
129 # Always use forward slashes, even on Windows.
129 # Always use forward slashes, even on Windows.
130 # Don't forget to use absolute paths, not relative paths.
130 # Don't forget to use absolute paths, not relative paths.
131 'templates',
131 'templates',
132 )
132 )
133
133
134 INSTALLED_APPS = (
134 INSTALLED_APPS = (
135 'django.contrib.auth',
135 'django.contrib.auth',
136 'django.contrib.contenttypes',
136 'django.contrib.contenttypes',
137 'django.contrib.sessions',
137 'django.contrib.sessions',
138 # 'django.contrib.sites',
138 # 'django.contrib.sites',
139 'django.contrib.messages',
139 'django.contrib.messages',
140 'django.contrib.staticfiles',
140 'django.contrib.staticfiles',
141 # Uncomment the next line to enable the admin:
141 # Uncomment the next line to enable the admin:
142 'django.contrib.admin',
142 'django.contrib.admin',
143 # Uncomment the next line to enable admin documentation:
143 # Uncomment the next line to enable admin documentation:
144 # 'django.contrib.admindocs',
144 # 'django.contrib.admindocs',
145 'django.contrib.humanize',
145 'django.contrib.humanize',
146 'django_cleanup',
146 'django_cleanup',
147
147
148 # Migrations
148 # Migrations
149 'south',
149 'south',
150 'debug_toolbar',
150 'debug_toolbar',
151
151
152 # Search
152 # Search
153 'haystack',
153 'haystack',
154
154
155 # Static files compressor
155 # Static files compressor
156 'compressor',
156 'compressor',
157
157
158 'boards',
158 'boards',
159 )
159 )
160
160
161 # A sample logging configuration. The only tangible logging
161 # A sample logging configuration. The only tangible logging
162 # performed by this configuration is to send an email to
162 # performed by this configuration is to send an email to
163 # the site admins on every HTTP 500 error when DEBUG=False.
163 # the site admins on every HTTP 500 error when DEBUG=False.
164 # See http://docs.djangoproject.com/en/dev/topics/logging for
164 # See http://docs.djangoproject.com/en/dev/topics/logging for
165 # more details on how to customize your logging configuration.
165 # more details on how to customize your logging configuration.
166 LOGGING = {
166 LOGGING = {
167 'version': 1,
167 'version': 1,
168 'disable_existing_loggers': False,
168 'disable_existing_loggers': False,
169 'formatters': {
169 'formatters': {
170 'verbose': {
170 'verbose': {
171 'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
171 'format': '%(levelname)s %(asctime)s %(name)s %(process)d %(thread)d %(message)s'
172 },
172 },
173 'simple': {
173 'simple': {
174 'format': '%(levelname)s %(asctime)s [%(module)s] %(message)s'
174 'format': '%(levelname)s %(asctime)s [%(name)s] %(message)s'
175 },
175 },
176 },
176 },
177 'filters': {
177 'filters': {
178 'require_debug_false': {
178 'require_debug_false': {
179 '()': 'django.utils.log.RequireDebugFalse'
179 '()': 'django.utils.log.RequireDebugFalse'
180 }
180 }
181 },
181 },
182 'handlers': {
182 'handlers': {
183 'console': {
183 'console': {
184 'level': 'DEBUG',
184 'level': 'DEBUG',
185 'class': 'logging.StreamHandler',
185 'class': 'logging.StreamHandler',
186 'formatter': 'simple'
186 'formatter': 'simple'
187 },
187 },
188 },
188 },
189 'loggers': {
189 'loggers': {
190 'boards': {
190 'boards': {
191 'handlers': ['console'],
191 'handlers': ['console'],
192 'level': 'DEBUG',
192 'level': 'DEBUG',
193 }
193 }
194 },
194 },
195 }
195 }
196
196
197 HAYSTACK_CONNECTIONS = {
197 HAYSTACK_CONNECTIONS = {
198 'default': {
198 'default': {
199 'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',
199 'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',
200 'PATH': os.path.join(os.path.dirname(__file__), 'whoosh_index'),
200 'PATH': os.path.join(os.path.dirname(__file__), 'whoosh_index'),
201 },
201 },
202 }
202 }
203
203
204 MARKUP_FIELD_TYPES = (
204 MARKUP_FIELD_TYPES = (
205 ('bbcode', bbcode_extended),
205 ('bbcode', bbcode_extended),
206 )
206 )
207
207
208 THEMES = [
208 THEMES = [
209 ('md', 'Mystic Dark'),
209 ('md', 'Mystic Dark'),
210 ('md_centered', 'Mystic Dark (centered)'),
210 ('md_centered', 'Mystic Dark (centered)'),
211 ('sw', 'Snow White'),
211 ('sw', 'Snow White'),
212 ('pg', 'Photon Gray'),
212 ('pg', 'Photon Gray'),
213 ]
213 ]
214
214
215 POSTING_DELAY = 20 # seconds
215 POSTING_DELAY = 20 # seconds
216
216
217 COMPRESS_HTML = False
217 COMPRESS_HTML = False
218
218
219 # Websocket settins
219 # Websocket settins
220 CENTRIFUGE_HOST = 'localhost'
220 CENTRIFUGE_HOST = 'localhost'
221 CENTRIFUGE_PORT = '9090'
221 CENTRIFUGE_PORT = '9090'
222
222
223 CENTRIFUGE_ADDRESS = 'http://{}:{}'.format(CENTRIFUGE_HOST, CENTRIFUGE_PORT)
223 CENTRIFUGE_ADDRESS = 'http://{}:{}'.format(CENTRIFUGE_HOST, CENTRIFUGE_PORT)
224 CENTRIFUGE_PROJECT_ID = '<project id here>'
224 CENTRIFUGE_PROJECT_ID = '<project id here>'
225 CENTRIFUGE_PROJECT_SECRET = '<project secret here>'
225 CENTRIFUGE_PROJECT_SECRET = '<project secret here>'
226 CENTRIFUGE_TIMEOUT = 5
226 CENTRIFUGE_TIMEOUT = 5
227
227
228 # Debug mode middlewares
228 # Debug mode middlewares
229 if DEBUG:
229 if DEBUG:
230 MIDDLEWARE_CLASSES += (
230 MIDDLEWARE_CLASSES += (
231 'debug_toolbar.middleware.DebugToolbarMiddleware',
231 'debug_toolbar.middleware.DebugToolbarMiddleware',
232 )
232 )
233
233
234 def custom_show_toolbar(request):
234 def custom_show_toolbar(request):
235 return True
235 return True
236
236
237 DEBUG_TOOLBAR_CONFIG = {
237 DEBUG_TOOLBAR_CONFIG = {
238 'ENABLE_STACKTRACES': True,
238 'ENABLE_STACKTRACES': True,
239 'SHOW_TOOLBAR_CALLBACK': 'neboard.settings.custom_show_toolbar',
239 'SHOW_TOOLBAR_CALLBACK': 'neboard.settings.custom_show_toolbar',
240 }
240 }
241
241
242 # FIXME Uncommenting this fails somehow. Need to investigate this
242 # FIXME Uncommenting this fails somehow. Need to investigate this
243 #DEBUG_TOOLBAR_PANELS += (
243 #DEBUG_TOOLBAR_PANELS += (
244 # 'debug_toolbar.panels.profiling.ProfilingDebugPanel',
244 # 'debug_toolbar.panels.profiling.ProfilingDebugPanel',
245 #) No newline at end of file
245 #)
General Comments 0
You need to be logged in to leave comments. Login now