##// END OF EJS Templates
Fixed tag problems with python3 (related to the 'map' function that was...
neko259 -
r766:10f4d0f7 default
parent child Browse files
Show More
@@ -1,343 +1,347 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 logging
4 import re
4 import re
5
5
6 from django.core.cache import cache
6 from django.core.cache import cache
7 from django.core.urlresolvers import reverse
7 from django.core.urlresolvers import reverse
8 from django.db import models, transaction
8 from django.db import models, transaction
9 from django.template.loader import render_to_string
9 from django.template.loader import render_to_string
10 from django.utils import timezone
10 from django.utils import timezone
11 from markupfield.fields import MarkupField
11 from markupfield.fields import MarkupField
12
12
13 from boards.models import PostImage
13 from boards.models import PostImage
14 from boards.models.base import Viewable
14 from boards.models.base import Viewable
15 from boards.models.thread import Thread
15 from boards.models.thread import Thread
16
16
17
17
18 APP_LABEL_BOARDS = 'boards'
18 APP_LABEL_BOARDS = 'boards'
19
19
20 CACHE_KEY_PPD = 'ppd'
20 CACHE_KEY_PPD = 'ppd'
21 CACHE_KEY_POST_URL = 'post_url'
21 CACHE_KEY_POST_URL = 'post_url'
22
22
23 POSTS_PER_DAY_RANGE = 7
23 POSTS_PER_DAY_RANGE = 7
24
24
25 BAN_REASON_AUTO = 'Auto'
25 BAN_REASON_AUTO = 'Auto'
26
26
27 IMAGE_THUMB_SIZE = (200, 150)
27 IMAGE_THUMB_SIZE = (200, 150)
28
28
29 TITLE_MAX_LENGTH = 200
29 TITLE_MAX_LENGTH = 200
30
30
31 DEFAULT_MARKUP_TYPE = 'bbcode'
31 DEFAULT_MARKUP_TYPE = 'bbcode'
32
32
33 # TODO This should be removed
33 # TODO This should be removed
34 NO_IP = '0.0.0.0'
34 NO_IP = '0.0.0.0'
35
35
36 # TODO Real user agent should be saved instead of this
36 # TODO Real user agent should be saved instead of this
37 UNKNOWN_UA = ''
37 UNKNOWN_UA = ''
38
38
39 REGEX_REPLY = re.compile(r'\[post\](\d+)\[/post\]')
39 REGEX_REPLY = re.compile(r'\[post\](\d+)\[/post\]')
40
40
41 logger = logging.getLogger(__name__)
41 logger = logging.getLogger(__name__)
42
42
43
43
44 class PostManager(models.Manager):
44 class PostManager(models.Manager):
45 def create_post(self, title, text, image=None, thread=None, ip=NO_IP,
45 def create_post(self, title, text, image=None, thread=None, ip=NO_IP,
46 tags=None):
46 tags=None):
47 """
47 """
48 Creates new post
48 Creates new post
49 """
49 """
50
50
51 if not tags:
52 tags = []
53
51 posting_time = timezone.now()
54 posting_time = timezone.now()
52 if not thread:
55 if not thread:
53 thread = Thread.objects.create(bump_time=posting_time,
56 thread = Thread.objects.create(bump_time=posting_time,
54 last_edit_time=posting_time)
57 last_edit_time=posting_time)
55 new_thread = True
58 new_thread = True
56 else:
59 else:
57 thread.bump()
60 thread.bump()
58 thread.last_edit_time = posting_time
61 thread.last_edit_time = posting_time
59 thread.save()
62 thread.save()
60 new_thread = False
63 new_thread = False
61
64
62 post = self.create(title=title,
65 post = self.create(title=title,
63 text=text,
66 text=text,
64 pub_time=posting_time,
67 pub_time=posting_time,
65 thread_new=thread,
68 thread_new=thread,
66 poster_ip=ip,
69 poster_ip=ip,
67 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
70 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
68 # last!
71 # last!
69 last_edit_time=posting_time)
72 last_edit_time=posting_time)
70
73
71 if image:
74 if image:
72 post_image = PostImage.objects.create(image=image)
75 post_image = PostImage.objects.create(image=image)
73 post.images.add(post_image)
76 post.images.add(post_image)
74 logger.info('Created image #%d for post #%d' % (post_image.id,
77 logger.info('Created image #%d for post #%d' % (post_image.id,
75 post.id))
78 post.id))
76
79
77 thread.replies.add(post)
80 thread.replies.add(post)
78 if tags:
81 for tag in tags:
79 map(thread.add_tag, tags)
82 thread.add_tag(tag)
80
83
81 if new_thread:
84 if new_thread:
82 Thread.objects.process_oldest_threads()
85 Thread.objects.process_oldest_threads()
83 self.connect_replies(post)
86 self.connect_replies(post)
84
87
85 logger.info('Created post #%d with title %s' % (post.id,
88 logger.info('Created post #%d with title %s' % (post.id,
86 post.get_title()))
89 post.get_title()))
87
90
88 return post
91 return post
89
92
90 def delete_post(self, post):
93 def delete_post(self, post):
91 """
94 """
92 Deletes post and update or delete its thread
95 Deletes post and update or delete its thread
93 """
96 """
94
97
95 post_id = post.id
98 post_id = post.id
96
99
97 thread = post.get_thread()
100 thread = post.get_thread()
98
101
99 if post.is_opening():
102 if post.is_opening():
100 thread.delete()
103 thread.delete()
101 else:
104 else:
102 thread.last_edit_time = timezone.now()
105 thread.last_edit_time = timezone.now()
103 thread.save()
106 thread.save()
104
107
105 post.delete()
108 post.delete()
106
109
107 logger.info('Deleted post #%d (%s)' % (post_id, post.get_title()))
110 logger.info('Deleted post #%d (%s)' % (post_id, post.get_title()))
108
111
109 def delete_posts_by_ip(self, ip):
112 def delete_posts_by_ip(self, ip):
110 """
113 """
111 Deletes all posts of the author with same IP
114 Deletes all posts of the author with same IP
112 """
115 """
113
116
114 posts = self.filter(poster_ip=ip)
117 posts = self.filter(poster_ip=ip)
115 map(self.delete_post, posts)
118 for post in posts:
119 self.delete_post(post)
116
120
117 def connect_replies(self, post):
121 def connect_replies(self, post):
118 """
122 """
119 Connects replies to a post to show them as a reflink map
123 Connects replies to a post to show them as a reflink map
120 """
124 """
121
125
122 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
126 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
123 post_id = reply_number.group(1)
127 post_id = reply_number.group(1)
124 ref_post = self.filter(id=post_id)
128 ref_post = self.filter(id=post_id)
125 if ref_post.count() > 0:
129 if ref_post.count() > 0:
126 referenced_post = ref_post[0]
130 referenced_post = ref_post[0]
127 referenced_post.referenced_posts.add(post)
131 referenced_post.referenced_posts.add(post)
128 referenced_post.last_edit_time = post.pub_time
132 referenced_post.last_edit_time = post.pub_time
129 referenced_post.build_refmap()
133 referenced_post.build_refmap()
130 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
134 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
131
135
132 referenced_thread = referenced_post.get_thread()
136 referenced_thread = referenced_post.get_thread()
133 referenced_thread.last_edit_time = post.pub_time
137 referenced_thread.last_edit_time = post.pub_time
134 referenced_thread.save(update_fields=['last_edit_time'])
138 referenced_thread.save(update_fields=['last_edit_time'])
135
139
136 def get_posts_per_day(self):
140 def get_posts_per_day(self):
137 """
141 """
138 Gets average count of posts per day for the last 7 days
142 Gets average count of posts per day for the last 7 days
139 """
143 """
140
144
141 day_end = date.today()
145 day_end = date.today()
142 day_start = day_end - timedelta(POSTS_PER_DAY_RANGE)
146 day_start = day_end - timedelta(POSTS_PER_DAY_RANGE)
143
147
144 cache_key = CACHE_KEY_PPD + str(day_end)
148 cache_key = CACHE_KEY_PPD + str(day_end)
145 ppd = cache.get(cache_key)
149 ppd = cache.get(cache_key)
146 if ppd:
150 if ppd:
147 return ppd
151 return ppd
148
152
149 day_time_start = timezone.make_aware(datetime.combine(
153 day_time_start = timezone.make_aware(datetime.combine(
150 day_start, dtime()), timezone.get_current_timezone())
154 day_start, dtime()), timezone.get_current_timezone())
151 day_time_end = timezone.make_aware(datetime.combine(
155 day_time_end = timezone.make_aware(datetime.combine(
152 day_end, dtime()), timezone.get_current_timezone())
156 day_end, dtime()), timezone.get_current_timezone())
153
157
154 posts_per_period = float(self.filter(
158 posts_per_period = float(self.filter(
155 pub_time__lte=day_time_end,
159 pub_time__lte=day_time_end,
156 pub_time__gte=day_time_start).count())
160 pub_time__gte=day_time_start).count())
157
161
158 ppd = posts_per_period / POSTS_PER_DAY_RANGE
162 ppd = posts_per_period / POSTS_PER_DAY_RANGE
159
163
160 cache.set(cache_key, ppd)
164 cache.set(cache_key, ppd)
161 return ppd
165 return ppd
162
166
163
167
164 class Post(models.Model, Viewable):
168 class Post(models.Model, Viewable):
165 """A post is a message."""
169 """A post is a message."""
166
170
167 objects = PostManager()
171 objects = PostManager()
168
172
169 class Meta:
173 class Meta:
170 app_label = APP_LABEL_BOARDS
174 app_label = APP_LABEL_BOARDS
171 ordering = ('id',)
175 ordering = ('id',)
172
176
173 title = models.CharField(max_length=TITLE_MAX_LENGTH)
177 title = models.CharField(max_length=TITLE_MAX_LENGTH)
174 pub_time = models.DateTimeField()
178 pub_time = models.DateTimeField()
175 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
179 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
176 escape_html=False)
180 escape_html=False)
177
181
178 images = models.ManyToManyField(PostImage, null=True, blank=True,
182 images = models.ManyToManyField(PostImage, null=True, blank=True,
179 related_name='ip+', db_index=True)
183 related_name='ip+', db_index=True)
180
184
181 poster_ip = models.GenericIPAddressField()
185 poster_ip = models.GenericIPAddressField()
182 poster_user_agent = models.TextField()
186 poster_user_agent = models.TextField()
183
187
184 thread_new = models.ForeignKey('Thread', null=True, default=None,
188 thread_new = models.ForeignKey('Thread', null=True, default=None,
185 db_index=True)
189 db_index=True)
186 last_edit_time = models.DateTimeField()
190 last_edit_time = models.DateTimeField()
187
191
188 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
192 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
189 null=True,
193 null=True,
190 blank=True, related_name='rfp+',
194 blank=True, related_name='rfp+',
191 db_index=True)
195 db_index=True)
192 refmap = models.TextField(null=True, blank=True)
196 refmap = models.TextField(null=True, blank=True)
193
197
194 def __unicode__(self):
198 def __unicode__(self):
195 return '#' + str(self.id) + ' ' + self.title + ' (' + \
199 return '#' + str(self.id) + ' ' + self.title + ' (' + \
196 self.text.raw[:50] + ')'
200 self.text.raw[:50] + ')'
197
201
198 def get_title(self):
202 def get_title(self):
199 """
203 """
200 Gets original post title or part of its text.
204 Gets original post title or part of its text.
201 """
205 """
202
206
203 title = self.title
207 title = self.title
204 if not title:
208 if not title:
205 title = self.text.rendered
209 title = self.text.rendered
206
210
207 return title
211 return title
208
212
209 def build_refmap(self):
213 def build_refmap(self):
210 """
214 """
211 Builds a replies map string from replies list. This is a cache to stop
215 Builds a replies map string from replies list. This is a cache to stop
212 the server from recalculating the map on every post show.
216 the server from recalculating the map on every post show.
213 """
217 """
214 map_string = ''
218 map_string = ''
215
219
216 first = True
220 first = True
217 for refpost in self.referenced_posts.all():
221 for refpost in self.referenced_posts.all():
218 if not first:
222 if not first:
219 map_string += ', '
223 map_string += ', '
220 map_string += '<a href="%s">&gt;&gt;%s</a>' % (refpost.get_url(),
224 map_string += '<a href="%s">&gt;&gt;%s</a>' % (refpost.get_url(),
221 refpost.id)
225 refpost.id)
222 first = False
226 first = False
223
227
224 self.refmap = map_string
228 self.refmap = map_string
225
229
226 def get_sorted_referenced_posts(self):
230 def get_sorted_referenced_posts(self):
227 return self.refmap
231 return self.refmap
228
232
229 def is_referenced(self):
233 def is_referenced(self):
230 return len(self.refmap) > 0
234 return len(self.refmap) > 0
231
235
232 def is_opening(self):
236 def is_opening(self):
233 """
237 """
234 Checks if this is an opening post or just a reply.
238 Checks if this is an opening post or just a reply.
235 """
239 """
236
240
237 return self.get_thread().get_opening_post_id() == self.id
241 return self.get_thread().get_opening_post_id() == self.id
238
242
239 @transaction.atomic
243 @transaction.atomic
240 def add_tag(self, tag):
244 def add_tag(self, tag):
241 edit_time = timezone.now()
245 edit_time = timezone.now()
242
246
243 thread = self.get_thread()
247 thread = self.get_thread()
244 thread.add_tag(tag)
248 thread.add_tag(tag)
245 self.last_edit_time = edit_time
249 self.last_edit_time = edit_time
246 self.save(update_fields=['last_edit_time'])
250 self.save(update_fields=['last_edit_time'])
247
251
248 thread.last_edit_time = edit_time
252 thread.last_edit_time = edit_time
249 thread.save(update_fields=['last_edit_time'])
253 thread.save(update_fields=['last_edit_time'])
250
254
251 @transaction.atomic
255 @transaction.atomic
252 def remove_tag(self, tag):
256 def remove_tag(self, tag):
253 edit_time = timezone.now()
257 edit_time = timezone.now()
254
258
255 thread = self.get_thread()
259 thread = self.get_thread()
256 thread.remove_tag(tag)
260 thread.remove_tag(tag)
257 self.last_edit_time = edit_time
261 self.last_edit_time = edit_time
258 self.save(update_fields=['last_edit_time'])
262 self.save(update_fields=['last_edit_time'])
259
263
260 thread.last_edit_time = edit_time
264 thread.last_edit_time = edit_time
261 thread.save(update_fields=['last_edit_time'])
265 thread.save(update_fields=['last_edit_time'])
262
266
263 def get_url(self, thread=None):
267 def get_url(self, thread=None):
264 """
268 """
265 Gets full url to the post.
269 Gets full url to the post.
266 """
270 """
267
271
268 cache_key = CACHE_KEY_POST_URL + str(self.id)
272 cache_key = CACHE_KEY_POST_URL + str(self.id)
269 link = cache.get(cache_key)
273 link = cache.get(cache_key)
270
274
271 if not link:
275 if not link:
272 if not thread:
276 if not thread:
273 thread = self.get_thread()
277 thread = self.get_thread()
274
278
275 opening_id = thread.get_opening_post_id()
279 opening_id = thread.get_opening_post_id()
276
280
277 if self.id != opening_id:
281 if self.id != opening_id:
278 link = reverse('thread', kwargs={
282 link = reverse('thread', kwargs={
279 'post_id': opening_id}) + '#' + str(self.id)
283 'post_id': opening_id}) + '#' + str(self.id)
280 else:
284 else:
281 link = reverse('thread', kwargs={'post_id': self.id})
285 link = reverse('thread', kwargs={'post_id': self.id})
282
286
283 cache.set(cache_key, link)
287 cache.set(cache_key, link)
284
288
285 return link
289 return link
286
290
287 def get_thread(self):
291 def get_thread(self):
288 """
292 """
289 Gets post's thread.
293 Gets post's thread.
290 """
294 """
291
295
292 return self.thread_new
296 return self.thread_new
293
297
294 def get_referenced_posts(self):
298 def get_referenced_posts(self):
295 return self.referenced_posts.only('id', 'thread_new')
299 return self.referenced_posts.only('id', 'thread_new')
296
300
297 def get_text(self):
301 def get_text(self):
298 return self.text
302 return self.text
299
303
300 def get_view(self, moderator=False, need_open_link=False,
304 def get_view(self, moderator=False, need_open_link=False,
301 truncated=False, *args, **kwargs):
305 truncated=False, *args, **kwargs):
302 if 'is_opening' in kwargs:
306 if 'is_opening' in kwargs:
303 is_opening = kwargs['is_opening']
307 is_opening = kwargs['is_opening']
304 else:
308 else:
305 is_opening = self.is_opening()
309 is_opening = self.is_opening()
306
310
307 if 'thread' in kwargs:
311 if 'thread' in kwargs:
308 thread = kwargs['thread']
312 thread = kwargs['thread']
309 else:
313 else:
310 thread = self.get_thread()
314 thread = self.get_thread()
311
315
312 if 'can_bump' in kwargs:
316 if 'can_bump' in kwargs:
313 can_bump = kwargs['can_bump']
317 can_bump = kwargs['can_bump']
314 else:
318 else:
315 can_bump = thread.can_bump()
319 can_bump = thread.can_bump()
316
320
317 if is_opening:
321 if is_opening:
318 opening_post_id = self.id
322 opening_post_id = self.id
319 else:
323 else:
320 opening_post_id = thread.get_opening_post_id()
324 opening_post_id = thread.get_opening_post_id()
321
325
322 return render_to_string('boards/post.html', {
326 return render_to_string('boards/post.html', {
323 'post': self,
327 'post': self,
324 'moderator': moderator,
328 'moderator': moderator,
325 'is_opening': is_opening,
329 'is_opening': is_opening,
326 'thread': thread,
330 'thread': thread,
327 'bumpable': can_bump,
331 'bumpable': can_bump,
328 'need_open_link': need_open_link,
332 'need_open_link': need_open_link,
329 'truncated': truncated,
333 'truncated': truncated,
330 'opening_post_id': opening_post_id,
334 'opening_post_id': opening_post_id,
331 })
335 })
332
336
333 def get_first_image(self):
337 def get_first_image(self):
334 return self.images.earliest('id')
338 return self.images.earliest('id')
335
339
336 def delete(self, using=None):
340 def delete(self, using=None):
337 """
341 """
338 Deletes all post images and the post itself.
342 Deletes all post images and the post itself.
339 """
343 """
340
344
341 self.images.all().delete()
345 self.images.all().delete()
342
346
343 super(Post, self).delete(using)
347 super(Post, self).delete(using)
@@ -1,270 +1,263 b''
1 # coding=utf-8
1 # coding=utf-8
2 import time
2 import time
3 import logging
3 import logging
4 from django.core.paginator import Paginator
4 from django.core.paginator import Paginator
5
5
6 from django.test import TestCase
6 from django.test import TestCase
7 from django.test.client import Client
7 from django.test.client import Client
8 from django.core.urlresolvers import reverse, NoReverseMatch
8 from django.core.urlresolvers import reverse, NoReverseMatch
9 from boards.abstracts.settingsmanager import get_settings_manager
9 from boards.abstracts.settingsmanager import get_settings_manager
10
10
11 from boards.models import Post, Tag, Thread
11 from boards.models import Post, Tag, Thread
12 from boards import urls
12 from boards import urls
13 from boards import settings
13 from boards import settings
14 import neboard
14 import neboard
15
15
16 TEST_TAG = 'test_tag'
16 TEST_TAG = 'test_tag'
17
17
18 PAGE_404 = 'boards/404.html'
18 PAGE_404 = 'boards/404.html'
19
19
20 TEST_TEXT = 'test text'
20 TEST_TEXT = 'test text'
21
21
22 NEW_THREAD_PAGE = '/'
22 NEW_THREAD_PAGE = '/'
23 THREAD_PAGE_ONE = '/thread/1/'
23 THREAD_PAGE_ONE = '/thread/1/'
24 THREAD_PAGE = '/thread/'
24 THREAD_PAGE = '/thread/'
25 TAG_PAGE = '/tag/'
25 TAG_PAGE = '/tag/'
26 HTTP_CODE_REDIRECT = 302
26 HTTP_CODE_REDIRECT = 302
27 HTTP_CODE_OK = 200
27 HTTP_CODE_OK = 200
28 HTTP_CODE_NOT_FOUND = 404
28 HTTP_CODE_NOT_FOUND = 404
29
29
30 logger = logging.getLogger(__name__)
30 logger = logging.getLogger(__name__)
31
31
32
32
33 class PostTests(TestCase):
33 class PostTests(TestCase):
34
34
35 def _create_post(self):
35 def _create_post(self):
36 tag = Tag.objects.create(name=TEST_TAG)
36 tag = Tag.objects.create(name=TEST_TAG)
37 return Post.objects.create_post(title='title', text='text',
37 return Post.objects.create_post(title='title', text='text',
38 tags=[tag])
38 tags=[tag])
39
39
40 def test_post_add(self):
40 def test_post_add(self):
41 """Test adding post"""
41 """Test adding post"""
42
42
43 post = self._create_post()
43 post = self._create_post()
44
44
45 self.assertIsNotNone(post, 'No post was created.')
45 self.assertIsNotNone(post, 'No post was created.')
46 self.assertEqual(TEST_TAG, post.get_thread().tags.all()[0].name,
46 self.assertEqual(TEST_TAG, post.get_thread().tags.all()[0].name,
47 'No tags were added to the post.')
47 'No tags were added to the post.')
48
48
49 def test_delete_post(self):
49 def test_delete_post(self):
50 """Test post deletion"""
50 """Test post deletion"""
51
51
52 post = self._create_post()
52 post = self._create_post()
53 post_id = post.id
53 post_id = post.id
54
54
55 Post.objects.delete_post(post)
55 Post.objects.delete_post(post)
56
56
57 self.assertFalse(Post.objects.filter(id=post_id).exists())
57 self.assertFalse(Post.objects.filter(id=post_id).exists())
58
58
59 def test_delete_thread(self):
59 def test_delete_thread(self):
60 """Test thread deletion"""
60 """Test thread deletion"""
61
61
62 opening_post = self._create_post()
62 opening_post = self._create_post()
63 thread = opening_post.get_thread()
63 thread = opening_post.get_thread()
64 reply = Post.objects.create_post("", "", thread=thread)
64 reply = Post.objects.create_post("", "", thread=thread)
65
65
66 thread.delete()
66 thread.delete()
67
67
68 self.assertFalse(Post.objects.filter(id=reply.id).exists())
68 self.assertFalse(Post.objects.filter(id=reply.id).exists())
69
69
70 def test_post_to_thread(self):
70 def test_post_to_thread(self):
71 """Test adding post to a thread"""
71 """Test adding post to a thread"""
72
72
73 op = self._create_post()
73 op = self._create_post()
74 post = Post.objects.create_post("", "", thread=op.get_thread())
74 post = Post.objects.create_post("", "", thread=op.get_thread())
75
75
76 self.assertIsNotNone(post, 'Reply to thread wasn\'t created')
76 self.assertIsNotNone(post, 'Reply to thread wasn\'t created')
77 self.assertEqual(op.get_thread().last_edit_time, post.pub_time,
77 self.assertEqual(op.get_thread().last_edit_time, post.pub_time,
78 'Post\'s create time doesn\'t match thread last edit'
78 'Post\'s create time doesn\'t match thread last edit'
79 ' time')
79 ' time')
80
80
81 def test_delete_posts_by_ip(self):
81 def test_delete_posts_by_ip(self):
82 """Test deleting posts with the given ip"""
82 """Test deleting posts with the given ip"""
83
83
84 post = self._create_post()
84 post = self._create_post()
85 post_id = post.id
85 post_id = post.id
86
86
87 Post.objects.delete_posts_by_ip('0.0.0.0')
87 Post.objects.delete_posts_by_ip('0.0.0.0')
88
88
89 self.assertFalse(Post.objects.filter(id=post_id).exists())
89 self.assertFalse(Post.objects.filter(id=post_id).exists())
90
90
91 def test_get_thread(self):
91 def test_get_thread(self):
92 """Test getting all posts of a thread"""
92 """Test getting all posts of a thread"""
93
93
94 opening_post = self._create_post()
94 opening_post = self._create_post()
95
95
96 for i in range(0, 2):
96 for i in range(0, 2):
97 Post.objects.create_post('title', 'text',
97 Post.objects.create_post('title', 'text',
98 thread=opening_post.get_thread())
98 thread=opening_post.get_thread())
99
99
100 thread = opening_post.get_thread()
100 thread = opening_post.get_thread()
101
101
102 self.assertEqual(3, thread.replies.count())
102 self.assertEqual(3, thread.replies.count())
103
103
104 def test_create_post_with_tag(self):
104 def test_create_post_with_tag(self):
105 """Test adding tag to post"""
105 """Test adding tag to post"""
106
106
107 tag = Tag.objects.create(name='test_tag')
107 tag = Tag.objects.create(name='test_tag')
108 post = Post.objects.create_post(title='title', text='text', tags=[tag])
108 post = Post.objects.create_post(title='title', text='text', tags=[tag])
109
109
110 thread = post.get_thread()
110 thread = post.get_thread()
111 self.assertIsNotNone(post, 'Post not created')
111 self.assertIsNotNone(post, 'Post not created')
112 self.assertTrue(tag in thread.tags.all(), 'Tag not added to thread')
112 self.assertTrue(tag in thread.tags.all(), 'Tag not added to thread')
113 self.assertTrue(thread in tag.threads.all(), 'Thread not added to tag')
113 self.assertTrue(thread in tag.threads.all(), 'Thread not added to tag')
114
114
115 def test_thread_max_count(self):
115 def test_thread_max_count(self):
116 """Test deletion of old posts when the max thread count is reached"""
116 """Test deletion of old posts when the max thread count is reached"""
117
117
118 for i in range(settings.MAX_THREAD_COUNT + 1):
118 for i in range(settings.MAX_THREAD_COUNT + 1):
119 self._create_post()
119 self._create_post()
120
120
121 self.assertEqual(settings.MAX_THREAD_COUNT,
121 self.assertEqual(settings.MAX_THREAD_COUNT,
122 len(Thread.objects.filter(archived=False)))
122 len(Thread.objects.filter(archived=False)))
123
123
124 def test_pages(self):
124 def test_pages(self):
125 """Test that the thread list is properly split into pages"""
125 """Test that the thread list is properly split into pages"""
126
126
127 for i in range(settings.MAX_THREAD_COUNT):
127 for i in range(settings.MAX_THREAD_COUNT):
128 self._create_post()
128 self._create_post()
129
129
130 all_threads = Thread.objects.filter(archived=False)
130 all_threads = Thread.objects.filter(archived=False)
131
131
132 paginator = Paginator(Thread.objects.filter(archived=False),
132 paginator = Paginator(Thread.objects.filter(archived=False),
133 settings.THREADS_PER_PAGE)
133 settings.THREADS_PER_PAGE)
134 posts_in_second_page = paginator.page(2).object_list
134 posts_in_second_page = paginator.page(2).object_list
135 first_post = posts_in_second_page[0]
135 first_post = posts_in_second_page[0]
136
136
137 self.assertEqual(all_threads[settings.THREADS_PER_PAGE].id,
137 self.assertEqual(all_threads[settings.THREADS_PER_PAGE].id,
138 first_post.id)
138 first_post.id)
139
139
140
140
141 class PagesTest(TestCase):
141 class PagesTest(TestCase):
142
142
143 def test_404(self):
143 def test_404(self):
144 """Test receiving error 404 when opening a non-existent page"""
144 """Test receiving error 404 when opening a non-existent page"""
145
145
146 tag_name = u'test_tag'
146 tag_name = u'test_tag'
147 tag = Tag.objects.create(name=tag_name)
147 tag = Tag.objects.create(name=tag_name)
148 client = Client()
148 client = Client()
149
149
150 Post.objects.create_post('title', TEST_TEXT, tags=[tag])
150 Post.objects.create_post('title', TEST_TEXT, tags=[tag])
151
151
152 existing_post_id = Post.objects.all()[0].id
152 existing_post_id = Post.objects.all()[0].id
153 response_existing = client.get(THREAD_PAGE + str(existing_post_id) +
153 response_existing = client.get(THREAD_PAGE + str(existing_post_id) +
154 '/')
154 '/')
155 self.assertEqual(HTTP_CODE_OK, response_existing.status_code,
155 self.assertEqual(HTTP_CODE_OK, response_existing.status_code,
156 u'Cannot open existing thread')
156 u'Cannot open existing thread')
157
157
158 response_not_existing = client.get(THREAD_PAGE + str(
158 response_not_existing = client.get(THREAD_PAGE + str(
159 existing_post_id + 1) + '/')
159 existing_post_id + 1) + '/')
160 self.assertEqual(PAGE_404, response_not_existing.templates[0].name,
160 self.assertEqual(PAGE_404, response_not_existing.templates[0].name,
161 u'Not existing thread is opened')
161 u'Not existing thread is opened')
162
162
163 response_existing = client.get(TAG_PAGE + tag_name + '/')
163 response_existing = client.get(TAG_PAGE + tag_name + '/')
164 self.assertEqual(HTTP_CODE_OK,
164 self.assertEqual(HTTP_CODE_OK,
165 response_existing.status_code,
165 response_existing.status_code,
166 u'Cannot open existing tag')
166 u'Cannot open existing tag')
167
167
168 response_not_existing = client.get(TAG_PAGE + u'not_tag' + '/')
168 response_not_existing = client.get(TAG_PAGE + u'not_tag' + '/')
169 self.assertEqual(PAGE_404,
169 self.assertEqual(PAGE_404,
170 response_not_existing.templates[0].name,
170 response_not_existing.templates[0].name,
171 u'Not existing tag is opened')
171 u'Not existing tag is opened')
172
172
173 reply_id = Post.objects.create_post('', TEST_TEXT,
173 reply_id = Post.objects.create_post('', TEST_TEXT,
174 thread=Post.objects.all()[0]
174 thread=Post.objects.all()[0]
175 .get_thread())
175 .get_thread())
176 response_not_existing = client.get(THREAD_PAGE + str(
176 response_not_existing = client.get(THREAD_PAGE + str(
177 reply_id) + '/')
177 reply_id) + '/')
178 self.assertEqual(PAGE_404,
178 self.assertEqual(PAGE_404,
179 response_not_existing.templates[0].name,
179 response_not_existing.templates[0].name,
180 u'Reply is opened as a thread')
180 u'Reply is opened as a thread')
181
181
182
182
183 class FormTest(TestCase):
183 class FormTest(TestCase):
184 def test_post_validation(self):
184 def test_post_validation(self):
185 # Disable captcha for the test
186 captcha_enabled = neboard.settings.ENABLE_CAPTCHA
187 neboard.settings.ENABLE_CAPTCHA = False
188
189 client = Client()
185 client = Client()
190
186
191 valid_tags = u'tag1 tag_2 Ρ‚Π΅Π³_3'
187 valid_tags = u'tag1 tag_2 Ρ‚Π΅Π³_3'
192 invalid_tags = u'$%_356 ---'
188 invalid_tags = u'$%_356 ---'
193
189
194 response = client.post(NEW_THREAD_PAGE, {'title': 'test title',
190 response = client.post(NEW_THREAD_PAGE, {'title': 'test title',
195 'text': TEST_TEXT,
191 'text': TEST_TEXT,
196 'tags': valid_tags})
192 'tags': valid_tags})
197 self.assertEqual(response.status_code, HTTP_CODE_REDIRECT,
193 self.assertEqual(response.status_code, HTTP_CODE_REDIRECT,
198 msg='Posting new message failed: got code ' +
194 msg='Posting new message failed: got code ' +
199 str(response.status_code))
195 str(response.status_code))
200
196
201 self.assertEqual(1, Post.objects.count(),
197 self.assertEqual(1, Post.objects.count(),
202 msg='No posts were created')
198 msg='No posts were created')
203
199
204 client.post(NEW_THREAD_PAGE, {'text': TEST_TEXT,
200 client.post(NEW_THREAD_PAGE, {'text': TEST_TEXT,
205 'tags': invalid_tags})
201 'tags': invalid_tags})
206 self.assertEqual(1, Post.objects.count(), msg='The validation passed '
202 self.assertEqual(1, Post.objects.count(), msg='The validation passed '
207 'where it should fail')
203 'where it should fail')
208
204
209 # Change posting delay so we don't have to wait for 30 seconds or more
205 # Change posting delay so we don't have to wait for 30 seconds or more
210 old_posting_delay = neboard.settings.POSTING_DELAY
206 old_posting_delay = neboard.settings.POSTING_DELAY
211 # Wait fot the posting delay or we won't be able to post
207 # Wait fot the posting delay or we won't be able to post
212 settings.POSTING_DELAY = 1
208 settings.POSTING_DELAY = 1
213 time.sleep(neboard.settings.POSTING_DELAY + 1)
209 time.sleep(neboard.settings.POSTING_DELAY + 1)
214 response = client.post(THREAD_PAGE_ONE, {'text': TEST_TEXT,
210 response = client.post(THREAD_PAGE_ONE, {'text': TEST_TEXT,
215 'tags': valid_tags})
211 'tags': valid_tags})
216 self.assertEqual(HTTP_CODE_REDIRECT, response.status_code,
212 self.assertEqual(HTTP_CODE_REDIRECT, response.status_code,
217 msg=u'Posting new message failed: got code ' +
213 msg=u'Posting new message failed: got code ' +
218 str(response.status_code))
214 str(response.status_code))
219 # Restore posting delay
215 # Restore posting delay
220 settings.POSTING_DELAY = old_posting_delay
216 settings.POSTING_DELAY = old_posting_delay
221
217
222 self.assertEqual(2, Post.objects.count(),
218 self.assertEqual(2, Post.objects.count(),
223 msg=u'No posts were created')
219 msg=u'No posts were created')
224
220
225 # Restore captcha setting
226 settings.ENABLE_CAPTCHA = captcha_enabled
227
228
221
229 class ViewTest(TestCase):
222 class ViewTest(TestCase):
230
223
231 def test_all_views(self):
224 def test_all_views(self):
232 """
225 """
233 Try opening all views defined in ulrs.py that don't need additional
226 Try opening all views defined in ulrs.py that don't need additional
234 parameters
227 parameters
235 """
228 """
236
229
237 client = Client()
230 client = Client()
238 for url in urls.urlpatterns:
231 for url in urls.urlpatterns:
239 try:
232 try:
240 view_name = url.name
233 view_name = url.name
241 logger.debug('Testing view %s' % view_name)
234 logger.debug('Testing view %s' % view_name)
242
235
243 try:
236 try:
244 response = client.get(reverse(view_name))
237 response = client.get(reverse(view_name))
245
238
246 self.assertEqual(HTTP_CODE_OK, response.status_code,
239 self.assertEqual(HTTP_CODE_OK, response.status_code,
247 '%s view not opened' % view_name)
240 '%s view not opened' % view_name)
248 except NoReverseMatch:
241 except NoReverseMatch:
249 # This view just needs additional arguments
242 # This view just needs additional arguments
250 pass
243 pass
251 except Exception as e:
244 except Exception as e:
252 self.fail('Got exception %s at %s view' % (e, view_name))
245 self.fail('Got exception %s at %s view' % (e, view_name))
253 except AttributeError:
246 except AttributeError:
254 # This is normal, some views do not have names
247 # This is normal, some views do not have names
255 pass
248 pass
256
249
257
250
258 class AbstractTest(TestCase):
251 class AbstractTest(TestCase):
259 def test_settings_manager(self):
252 def test_settings_manager(self):
260 request = MockRequest()
253 request = MockRequest()
261 settings_manager = get_settings_manager(request)
254 settings_manager = get_settings_manager(request)
262
255
263 settings_manager.set_setting('test_setting', 'test_value')
256 settings_manager.set_setting('test_setting', 'test_value')
264 self.assertEqual('test_value', settings_manager.get_setting(
257 self.assertEqual('test_value', settings_manager.get_setting(
265 'test_setting'), u'Setting update failed.')
258 'test_setting'), u'Setting update failed.')
266
259
267
260
268 class MockRequest:
261 class MockRequest:
269 def __init__(self):
262 def __init__(self):
270 self.session = dict()
263 self.session = dict()
@@ -1,139 +1,139 b''
1 import string
1 import string
2
2
3 from django.db import transaction
3 from django.db import transaction
4 from django.shortcuts import render, redirect
4 from django.shortcuts import render, redirect
5
5
6 from boards import utils, settings
6 from boards import utils, settings
7 from boards.abstracts.paginator import get_paginator
7 from boards.abstracts.paginator import get_paginator
8 from boards.abstracts.settingsmanager import get_settings_manager
8 from boards.abstracts.settingsmanager import get_settings_manager
9 from boards.forms import ThreadForm, PlainErrorList
9 from boards.forms import ThreadForm, PlainErrorList
10 from boards.models import Post, Thread, Ban, Tag
10 from boards.models import Post, Thread, Ban, Tag
11 from boards.views.banned import BannedView
11 from boards.views.banned import BannedView
12 from boards.views.base import BaseBoardView, CONTEXT_FORM
12 from boards.views.base import BaseBoardView, CONTEXT_FORM
13 from boards.views.posting_mixin import PostMixin
13 from boards.views.posting_mixin import PostMixin
14
14
15 FORM_TAGS = 'tags'
15 FORM_TAGS = 'tags'
16 FORM_TEXT = 'text'
16 FORM_TEXT = 'text'
17 FORM_TITLE = 'title'
17 FORM_TITLE = 'title'
18 FORM_IMAGE = 'image'
18 FORM_IMAGE = 'image'
19
19
20 TAG_DELIMITER = ' '
20 TAG_DELIMITER = ' '
21
21
22 PARAMETER_CURRENT_PAGE = 'current_page'
22 PARAMETER_CURRENT_PAGE = 'current_page'
23 PARAMETER_PAGINATOR = 'paginator'
23 PARAMETER_PAGINATOR = 'paginator'
24 PARAMETER_THREADS = 'threads'
24 PARAMETER_THREADS = 'threads'
25
25
26 TEMPLATE = 'boards/posting_general.html'
26 TEMPLATE = 'boards/posting_general.html'
27 DEFAULT_PAGE = 1
27 DEFAULT_PAGE = 1
28
28
29
29
30 class AllThreadsView(PostMixin, BaseBoardView):
30 class AllThreadsView(PostMixin, BaseBoardView):
31
31
32 def __init__(self):
32 def __init__(self):
33 self.settings_manager = None
33 self.settings_manager = None
34 super(AllThreadsView, self).__init__()
34 super(AllThreadsView, self).__init__()
35
35
36 def get(self, request, page=DEFAULT_PAGE, form=None):
36 def get(self, request, page=DEFAULT_PAGE, form=None):
37 context = self.get_context_data(request=request)
37 context = self.get_context_data(request=request)
38
38
39 if not form:
39 if not form:
40 form = ThreadForm(error_class=PlainErrorList)
40 form = ThreadForm(error_class=PlainErrorList)
41
41
42 self.settings_manager = get_settings_manager(request)
42 self.settings_manager = get_settings_manager(request)
43 paginator = get_paginator(self.get_threads(),
43 paginator = get_paginator(self.get_threads(),
44 settings.THREADS_PER_PAGE)
44 settings.THREADS_PER_PAGE)
45 paginator.current_page = int(page)
45 paginator.current_page = int(page)
46
46
47 threads = paginator.page(page).object_list
47 threads = paginator.page(page).object_list
48
48
49 context[PARAMETER_THREADS] = threads
49 context[PARAMETER_THREADS] = threads
50 context[CONTEXT_FORM] = form
50 context[CONTEXT_FORM] = form
51
51
52 self._get_page_context(paginator, context, page)
52 self._get_page_context(paginator, context, page)
53
53
54 return render(request, TEMPLATE, context)
54 return render(request, TEMPLATE, context)
55
55
56 def post(self, request, page=DEFAULT_PAGE):
56 def post(self, request, page=DEFAULT_PAGE):
57 form = ThreadForm(request.POST, request.FILES,
57 form = ThreadForm(request.POST, request.FILES,
58 error_class=PlainErrorList)
58 error_class=PlainErrorList)
59 form.session = request.session
59 form.session = request.session
60
60
61 if form.is_valid():
61 if form.is_valid():
62 return self.create_thread(request, form)
62 return self.create_thread(request, form)
63 if form.need_to_ban:
63 if form.need_to_ban:
64 # Ban user because he is suspected to be a bot
64 # Ban user because he is suspected to be a bot
65 self._ban_current_user(request)
65 self._ban_current_user(request)
66
66
67 return self.get(request, page, form)
67 return self.get(request, page, form)
68
68
69 @staticmethod
69 @staticmethod
70 def _get_page_context(paginator, context, page):
70 def _get_page_context(paginator, context, page):
71 """
71 """
72 Get pagination context variables
72 Get pagination context variables
73 """
73 """
74
74
75 context[PARAMETER_PAGINATOR] = paginator
75 context[PARAMETER_PAGINATOR] = paginator
76 context[PARAMETER_CURRENT_PAGE] = paginator.page(int(page))
76 context[PARAMETER_CURRENT_PAGE] = paginator.page(int(page))
77
77
78 @staticmethod
78 @staticmethod
79 def parse_tags_string(tag_strings):
79 def parse_tags_string(tag_strings):
80 """
80 """
81 Parses tag list string and returns tag object list.
81 Parses tag list string and returns tag object list.
82 """
82 """
83
83
84 tags = []
84 tags = []
85
85
86 if tag_strings:
86 if tag_strings:
87 tag_strings = tag_strings.split(TAG_DELIMITER)
87 tag_strings = tag_strings.split(TAG_DELIMITER)
88 for tag_name in tag_strings:
88 for tag_name in tag_strings:
89 tag_name = string.lower(tag_name.strip())
89 tag_name = tag_name.strip().lower()
90 if len(tag_name) > 0:
90 if len(tag_name) > 0:
91 tag, created = Tag.objects.get_or_create(name=tag_name)
91 tag, created = Tag.objects.get_or_create(name=tag_name)
92 tags.append(tag)
92 tags.append(tag)
93
93
94 return tags
94 return tags
95
95
96 @transaction.atomic
96 @transaction.atomic
97 def create_thread(self, request, form, html_response=True):
97 def create_thread(self, request, form, html_response=True):
98 """
98 """
99 Creates a new thread with an opening post.
99 Creates a new thread with an opening post.
100 """
100 """
101
101
102 ip = utils.get_client_ip(request)
102 ip = utils.get_client_ip(request)
103 is_banned = Ban.objects.filter(ip=ip).exists()
103 is_banned = Ban.objects.filter(ip=ip).exists()
104
104
105 if is_banned:
105 if is_banned:
106 if html_response:
106 if html_response:
107 return redirect(BannedView().as_view())
107 return redirect(BannedView().as_view())
108 else:
108 else:
109 return
109 return
110
110
111 data = form.cleaned_data
111 data = form.cleaned_data
112
112
113 title = data[FORM_TITLE]
113 title = data[FORM_TITLE]
114 text = data[FORM_TEXT]
114 text = data[FORM_TEXT]
115
115
116 text = self._remove_invalid_links(text)
116 text = self._remove_invalid_links(text)
117
117
118 if FORM_IMAGE in data.keys():
118 if FORM_IMAGE in data.keys():
119 image = data[FORM_IMAGE]
119 image = data[FORM_IMAGE]
120 else:
120 else:
121 image = None
121 image = None
122
122
123 tag_strings = data[FORM_TAGS]
123 tag_strings = data[FORM_TAGS]
124
124
125 tags = self.parse_tags_string(tag_strings)
125 tags = self.parse_tags_string(tag_strings)
126
126
127 post = Post.objects.create_post(title=title, text=text, image=image,
127 post = Post.objects.create_post(title=title, text=text, image=image,
128 ip=ip, tags=tags)
128 ip=ip, tags=tags)
129
129
130 if html_response:
130 if html_response:
131 return redirect(post.get_url())
131 return redirect(post.get_url())
132
132
133 def get_threads(self):
133 def get_threads(self):
134 """
134 """
135 Gets list of threads that will be shown on a page.
135 Gets list of threads that will be shown on a page.
136 """
136 """
137
137
138 return Thread.objects.all().order_by('-bump_time')\
138 return Thread.objects.all().order_by('-bump_time')\
139 .exclude(tags__in=self.settings_manager.get_hidden_tags())
139 .exclude(tags__in=self.settings_manager.get_hidden_tags())
@@ -1,260 +1,250 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 )
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 'boards.context_processors.user_and_ui_processor',
108 'boards.context_processors.user_and_ui_processor',
109 )
109 )
110
110
111 MIDDLEWARE_CLASSES = (
111 MIDDLEWARE_CLASSES = (
112 'django.contrib.sessions.middleware.SessionMiddleware',
112 'django.contrib.sessions.middleware.SessionMiddleware',
113 'django.middleware.locale.LocaleMiddleware',
113 'django.middleware.locale.LocaleMiddleware',
114 'django.middleware.common.CommonMiddleware',
114 'django.middleware.common.CommonMiddleware',
115 'django.contrib.auth.middleware.AuthenticationMiddleware',
115 'django.contrib.auth.middleware.AuthenticationMiddleware',
116 'django.contrib.messages.middleware.MessageMiddleware',
116 'django.contrib.messages.middleware.MessageMiddleware',
117 'boards.middlewares.BanMiddleware',
117 'boards.middlewares.BanMiddleware',
118 'boards.middlewares.MinifyHTMLMiddleware',
118 'boards.middlewares.MinifyHTMLMiddleware',
119 )
119 )
120
120
121 ROOT_URLCONF = 'neboard.urls'
121 ROOT_URLCONF = 'neboard.urls'
122
122
123 # Python dotted path to the WSGI application used by Django's runserver.
123 # Python dotted path to the WSGI application used by Django's runserver.
124 WSGI_APPLICATION = 'neboard.wsgi.application'
124 WSGI_APPLICATION = 'neboard.wsgi.application'
125
125
126 TEMPLATE_DIRS = (
126 TEMPLATE_DIRS = (
127 # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
127 # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
128 # Always use forward slashes, even on Windows.
128 # Always use forward slashes, even on Windows.
129 # Don't forget to use absolute paths, not relative paths.
129 # Don't forget to use absolute paths, not relative paths.
130 'templates',
130 'templates',
131 )
131 )
132
132
133 INSTALLED_APPS = (
133 INSTALLED_APPS = (
134 'django.contrib.auth',
134 'django.contrib.auth',
135 'django.contrib.contenttypes',
135 'django.contrib.contenttypes',
136 'django.contrib.sessions',
136 'django.contrib.sessions',
137 # 'django.contrib.sites',
137 # 'django.contrib.sites',
138 'django.contrib.messages',
138 'django.contrib.messages',
139 'django.contrib.staticfiles',
139 'django.contrib.staticfiles',
140 # Uncomment the next line to enable the admin:
140 # Uncomment the next line to enable the admin:
141 'django.contrib.admin',
141 'django.contrib.admin',
142 # Uncomment the next line to enable admin documentation:
142 # Uncomment the next line to enable admin documentation:
143 # 'django.contrib.admindocs',
143 # 'django.contrib.admindocs',
144 'django.contrib.humanize',
144 'django.contrib.humanize',
145 'django_cleanup',
145 'django_cleanup',
146
146
147 # Migrations
147 # Migrations
148 'south',
148 'south',
149 'debug_toolbar',
149 'debug_toolbar',
150
150
151 'captcha',
152
153 # Search
151 # Search
154 'haystack',
152 'haystack',
155
153
156 'boards',
154 'boards',
157 )
155 )
158
156
159 DEBUG_TOOLBAR_PANELS = (
157 DEBUG_TOOLBAR_PANELS = (
160 'debug_toolbar.panels.version.VersionDebugPanel',
158 'debug_toolbar.panels.version.VersionDebugPanel',
161 'debug_toolbar.panels.timer.TimerDebugPanel',
159 'debug_toolbar.panels.timer.TimerDebugPanel',
162 'debug_toolbar.panels.settings_vars.SettingsVarsDebugPanel',
160 'debug_toolbar.panels.settings_vars.SettingsVarsDebugPanel',
163 'debug_toolbar.panels.headers.HeaderDebugPanel',
161 'debug_toolbar.panels.headers.HeaderDebugPanel',
164 'debug_toolbar.panels.request_vars.RequestVarsDebugPanel',
162 'debug_toolbar.panels.request_vars.RequestVarsDebugPanel',
165 'debug_toolbar.panels.template.TemplateDebugPanel',
163 'debug_toolbar.panels.template.TemplateDebugPanel',
166 'debug_toolbar.panels.sql.SQLDebugPanel',
164 'debug_toolbar.panels.sql.SQLDebugPanel',
167 'debug_toolbar.panels.signals.SignalDebugPanel',
165 'debug_toolbar.panels.signals.SignalDebugPanel',
168 'debug_toolbar.panels.logger.LoggingPanel',
166 'debug_toolbar.panels.logger.LoggingPanel',
169 )
167 )
170
168
171 # TODO: NEED DESIGN FIXES
172 CAPTCHA_OUTPUT_FORMAT = (u' %(hidden_field)s '
173 u'<div class="form-label">%(image)s</div>'
174 u'<div class="form-text">%(text_field)s</div>')
175
176 # A sample logging configuration. The only tangible logging
169 # A sample logging configuration. The only tangible logging
177 # performed by this configuration is to send an email to
170 # performed by this configuration is to send an email to
178 # the site admins on every HTTP 500 error when DEBUG=False.
171 # the site admins on every HTTP 500 error when DEBUG=False.
179 # See http://docs.djangoproject.com/en/dev/topics/logging for
172 # See http://docs.djangoproject.com/en/dev/topics/logging for
180 # more details on how to customize your logging configuration.
173 # more details on how to customize your logging configuration.
181 LOGGING = {
174 LOGGING = {
182 'version': 1,
175 'version': 1,
183 'disable_existing_loggers': False,
176 'disable_existing_loggers': False,
184 'formatters': {
177 'formatters': {
185 'verbose': {
178 'verbose': {
186 'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
179 'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
187 },
180 },
188 'simple': {
181 'simple': {
189 'format': '%(levelname)s %(asctime)s [%(module)s] %(message)s'
182 'format': '%(levelname)s %(asctime)s [%(module)s] %(message)s'
190 },
183 },
191 },
184 },
192 'filters': {
185 'filters': {
193 'require_debug_false': {
186 'require_debug_false': {
194 '()': 'django.utils.log.RequireDebugFalse'
187 '()': 'django.utils.log.RequireDebugFalse'
195 }
188 }
196 },
189 },
197 'handlers': {
190 'handlers': {
198 'console': {
191 'console': {
199 'level': 'DEBUG',
192 'level': 'DEBUG',
200 'class': 'logging.StreamHandler',
193 'class': 'logging.StreamHandler',
201 'formatter': 'simple'
194 'formatter': 'simple'
202 },
195 },
203 },
196 },
204 'loggers': {
197 'loggers': {
205 'boards': {
198 'boards': {
206 'handlers': ['console'],
199 'handlers': ['console'],
207 'level': 'DEBUG',
200 'level': 'DEBUG',
208 }
201 }
209 },
202 },
210 }
203 }
211
204
212 HAYSTACK_CONNECTIONS = {
205 HAYSTACK_CONNECTIONS = {
213 'default': {
206 'default': {
214 'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',
207 'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',
215 'PATH': os.path.join(os.path.dirname(__file__), 'whoosh_index'),
208 'PATH': os.path.join(os.path.dirname(__file__), 'whoosh_index'),
216 },
209 },
217 }
210 }
218
211
219 MARKUP_FIELD_TYPES = (
212 MARKUP_FIELD_TYPES = (
220 ('bbcode', bbcode_extended),
213 ('bbcode', bbcode_extended),
221 )
214 )
222
215
223 THEMES = [
216 THEMES = [
224 ('md', 'Mystic Dark'),
217 ('md', 'Mystic Dark'),
225 ('md_centered', 'Mystic Dark (centered)'),
218 ('md_centered', 'Mystic Dark (centered)'),
226 ('sw', 'Snow White'),
219 ('sw', 'Snow White'),
227 ('pg', 'Photon Gray'),
220 ('pg', 'Photon Gray'),
228 ]
221 ]
229
222
230 POPULAR_TAGS = 10
223 POPULAR_TAGS = 10
231
224
232 ENABLE_CAPTCHA = False
233 # if user tries to post before CAPTCHA_DEFAULT_SAFE_TIME. Captcha will be shown
234 CAPTCHA_DEFAULT_SAFE_TIME = 30 # seconds
235 POSTING_DELAY = 20 # seconds
225 POSTING_DELAY = 20 # seconds
236
226
237 COMPRESS_HTML = True
227 COMPRESS_HTML = True
238
228
239 # Debug mode middlewares
229 # Debug mode middlewares
240 if DEBUG:
230 if DEBUG:
241 MIDDLEWARE_CLASSES += (
231 MIDDLEWARE_CLASSES += (
242 #'boards.profiler.ProfilerMiddleware',
232 #'boards.profiler.ProfilerMiddleware',
243 'debug_toolbar.middleware.DebugToolbarMiddleware',
233 'debug_toolbar.middleware.DebugToolbarMiddleware',
244 )
234 )
245
235
246 def custom_show_toolbar(request):
236 def custom_show_toolbar(request):
247 return DEBUG
237 return DEBUG
248
238
249 DEBUG_TOOLBAR_CONFIG = {
239 DEBUG_TOOLBAR_CONFIG = {
250 'INTERCEPT_REDIRECTS': False,
240 'INTERCEPT_REDIRECTS': False,
251 'SHOW_TOOLBAR_CALLBACK': custom_show_toolbar,
241 'SHOW_TOOLBAR_CALLBACK': custom_show_toolbar,
252 'HIDE_DJANGO_SQL': False,
242 'HIDE_DJANGO_SQL': False,
253 'ENABLE_STACKTRACES': True,
243 'ENABLE_STACKTRACES': True,
254 }
244 }
255
245
256 # FIXME Uncommenting this fails somehow. Need to investigate this
246 # FIXME Uncommenting this fails somehow. Need to investigate this
257 #DEBUG_TOOLBAR_PANELS += (
247 #DEBUG_TOOLBAR_PANELS += (
258 # 'debug_toolbar.panels.profiling.ProfilingDebugPanel',
248 # 'debug_toolbar.panels.profiling.ProfilingDebugPanel',
259 #)
249 #)
260
250
General Comments 0
You need to be logged in to leave comments. Login now