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