##// END OF EJS Templates
Moved text parser and preparser to a separate module (BB-64)
neko259 -
r1066:dc65b709 default
parent child Browse files
Show More
@@ -1,210 +1,233 b''
1 # coding=utf-8
1 # coding=utf-8
2
2
3 import re
3 import re
4 import bbcode
4 import bbcode
5
6 from urllib.parse import unquote
7
5 from django.core.exceptions import ObjectDoesNotExist
8 from django.core.exceptions import ObjectDoesNotExist
6 from django.core.urlresolvers import reverse
9 from django.core.urlresolvers import reverse
7
10
8 import boards
11 import boards
9
12
10
13
11 __author__ = 'neko259'
14 __author__ = 'neko259'
12
15
13
16
14 REFLINK_PATTERN = re.compile(r'^\d+$')
17 REFLINK_PATTERN = re.compile(r'^\d+$')
15 MULTI_NEWLINES_PATTERN = re.compile(r'(\r?\n){2,}')
18 MULTI_NEWLINES_PATTERN = re.compile(r'(\r?\n){2,}')
16 ONE_NEWLINE = '\n'
19 ONE_NEWLINE = '\n'
20 REGEX_URL = re.compile(r'https?\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(/\S*)?')
17
21
18
22
19 class TextFormatter():
23 class TextFormatter():
20 """
24 """
21 An interface for formatter that can be used in the text format panel
25 An interface for formatter that can be used in the text format panel
22 """
26 """
23
27
24 def __init__(self):
28 def __init__(self):
25 pass
29 pass
26
30
27 name = ''
31 name = ''
28
32
29 # Left and right tags for the button preview
33 # Left and right tags for the button preview
30 preview_left = ''
34 preview_left = ''
31 preview_right = ''
35 preview_right = ''
32
36
33 # Left and right characters for the textarea input
37 # Left and right characters for the textarea input
34 format_left = ''
38 format_left = ''
35 format_right = ''
39 format_right = ''
36
40
37
41
38 class AutolinkPattern():
42 class AutolinkPattern():
39 def handleMatch(self, m):
43 def handleMatch(self, m):
40 link_element = etree.Element('a')
44 link_element = etree.Element('a')
41 href = m.group(2)
45 href = m.group(2)
42 link_element.set('href', href)
46 link_element.set('href', href)
43 link_element.text = href
47 link_element.text = href
44
48
45 return link_element
49 return link_element
46
50
47
51
48 class QuotePattern(TextFormatter):
52 class QuotePattern(TextFormatter):
49 name = 'q'
53 name = 'q'
50 preview_left = '<span class="multiquote">'
54 preview_left = '<span class="multiquote">'
51 preview_right = '</span>'
55 preview_right = '</span>'
52
56
53 format_left = '[quote]'
57 format_left = '[quote]'
54 format_right = '[/quote]'
58 format_right = '[/quote]'
55
59
56
60
57 class SpoilerPattern(TextFormatter):
61 class SpoilerPattern(TextFormatter):
58 name = 'spoiler'
62 name = 'spoiler'
59 preview_left = '<span class="spoiler">'
63 preview_left = '<span class="spoiler">'
60 preview_right = '</span>'
64 preview_right = '</span>'
61
65
62 format_left = '[spoiler]'
66 format_left = '[spoiler]'
63 format_right = '[/spoiler]'
67 format_right = '[/spoiler]'
64
68
65 def handleMatch(self, m):
69 def handleMatch(self, m):
66 quote_element = etree.Element('span')
70 quote_element = etree.Element('span')
67 quote_element.set('class', 'spoiler')
71 quote_element.set('class', 'spoiler')
68 quote_element.text = m.group(2)
72 quote_element.text = m.group(2)
69
73
70 return quote_element
74 return quote_element
71
75
72
76
73 class CommentPattern(TextFormatter):
77 class CommentPattern(TextFormatter):
74 name = ''
78 name = ''
75 preview_left = '<span class="comment">// '
79 preview_left = '<span class="comment">// '
76 preview_right = '</span>'
80 preview_right = '</span>'
77
81
78 format_left = '[comment]'
82 format_left = '[comment]'
79 format_right = '[/comment]'
83 format_right = '[/comment]'
80
84
81
85
82 # TODO Use <s> tag here
86 # TODO Use <s> tag here
83 class StrikeThroughPattern(TextFormatter):
87 class StrikeThroughPattern(TextFormatter):
84 name = 's'
88 name = 's'
85 preview_left = '<span class="strikethrough">'
89 preview_left = '<span class="strikethrough">'
86 preview_right = '</span>'
90 preview_right = '</span>'
87
91
88 format_left = '[s]'
92 format_left = '[s]'
89 format_right = '[/s]'
93 format_right = '[/s]'
90
94
91
95
92 class ItalicPattern(TextFormatter):
96 class ItalicPattern(TextFormatter):
93 name = 'i'
97 name = 'i'
94 preview_left = '<i>'
98 preview_left = '<i>'
95 preview_right = '</i>'
99 preview_right = '</i>'
96
100
97 format_left = '[i]'
101 format_left = '[i]'
98 format_right = '[/i]'
102 format_right = '[/i]'
99
103
100
104
101 class BoldPattern(TextFormatter):
105 class BoldPattern(TextFormatter):
102 name = 'b'
106 name = 'b'
103 preview_left = '<b>'
107 preview_left = '<b>'
104 preview_right = '</b>'
108 preview_right = '</b>'
105
109
106 format_left = '[b]'
110 format_left = '[b]'
107 format_right = '[/b]'
111 format_right = '[/b]'
108
112
109
113
110 class CodePattern(TextFormatter):
114 class CodePattern(TextFormatter):
111 name = 'code'
115 name = 'code'
112 preview_left = '<code>'
116 preview_left = '<code>'
113 preview_right = '</code>'
117 preview_right = '</code>'
114
118
115 format_left = '[code]'
119 format_left = '[code]'
116 format_right = '[/code]'
120 format_right = '[/code]'
117
121
118
122
119 def render_reflink(tag_name, value, options, parent, context):
123 def render_reflink(tag_name, value, options, parent, context):
120 result = '>>%s' % value
124 result = '>>%s' % value
121
125
122 if REFLINK_PATTERN.match(value):
126 if REFLINK_PATTERN.match(value):
123 post_id = int(value)
127 post_id = int(value)
124
128
125 try:
129 try:
126 post = boards.models.Post.objects.get(id=post_id)
130 post = boards.models.Post.objects.get(id=post_id)
127
131
128 result = '<a href="%s">&gt;&gt;%s</a>' % (post.get_url(), post_id)
132 result = '<a href="%s">&gt;&gt;%s</a>' % (post.get_url(), post_id)
129 except ObjectDoesNotExist:
133 except ObjectDoesNotExist:
130 pass
134 pass
131
135
132 return result
136 return result
133
137
134
138
135 def render_multithread(tag_name, value, options, parent, context):
139 def render_multithread(tag_name, value, options, parent, context):
136 result = '>>>%s' % value
140 result = '>>>%s' % value
137
141
138 if REFLINK_PATTERN.match(value):
142 if REFLINK_PATTERN.match(value):
139 post_id = int(value)
143 post_id = int(value)
140
144
141 try:
145 try:
142 post = boards.models.Post.objects.get(id=post_id)
146 post = boards.models.Post.objects.get(id=post_id)
143
147
144 if post.is_opening():
148 if post.is_opening():
145 result = '<a href="%s">&gt;&gt;&gt;%s</a>' % (post.get_url(), post_id)
149 result = '<a href="%s">&gt;&gt;&gt;%s</a>' % (post.get_url(), post_id)
146 except ObjectDoesNotExist:
150 except ObjectDoesNotExist:
147 pass
151 pass
148
152
149 return result
153 return result
150
154
151
155
152 def render_quote(tag_name, value, options, parent, context):
156 def render_quote(tag_name, value, options, parent, context):
153 source = ''
157 source = ''
154 if 'source' in options:
158 if 'source' in options:
155 source = options['source']
159 source = options['source']
156
160
157 if source:
161 if source:
158 result = '<div class="multiquote"><div class="quote-header">%s</div><div class="quote-text">%s</div></div>' % (source, value)
162 result = '<div class="multiquote"><div class="quote-header">%s</div><div class="quote-text">%s</div></div>' % (source, value)
159 else:
163 else:
160 result = '<div class="multiquote"><div class="quote-text">%s</div></div>' % value
164 result = '<div class="multiquote"><div class="quote-text">%s</div></div>' % value
161
165
162 return result
166 return result
163
167
164
168
165 def render_notification(tag_name, value, options, parent, content):
169 def render_notification(tag_name, value, options, parent, content):
166 username = value.lower()
170 username = value.lower()
167
171
168 return '<a href="{}" class="user-cast">@{}</a>'.format(
172 return '<a href="{}" class="user-cast">@{}</a>'.format(
169 reverse('notifications', kwargs={'username': username}), username)
173 reverse('notifications', kwargs={'username': username}), username)
170
174
171
175
172 def preparse_text(text):
173 """
174 Performs manual parsing before the bbcode parser is used.
175 """
176
177 return MULTI_NEWLINES_PATTERN.sub(ONE_NEWLINE, text)
178
179
180 def bbcode_extended(markup):
181 # The newline hack is added because br's margin does not work in all
182 # browsers except firefox, when the div's does.
183 parser = bbcode.Parser(newline='<div class="br"></div>')
184 parser.add_formatter('post', render_reflink, strip=True)
185 parser.add_formatter('thread', render_multithread, strip=True)
186 parser.add_formatter('quote', render_quote, strip=True)
187 parser.add_formatter('user', render_notification, strip=True)
188 parser.add_simple_formatter('comment',
189 '<span class="comment">//%(value)s</span>')
190 parser.add_simple_formatter('spoiler',
191 '<span class="spoiler">%(value)s</span>')
192 parser.add_simple_formatter('s',
193 '<span class="strikethrough">%(value)s</span>')
194 # TODO Why not use built-in tag?
195 parser.add_simple_formatter('code',
196 '<pre><code>%(value)s</pre></code>',
197 render_embedded=False)
198
199 text = preparse_text(markup)
200 return parser.format(text)
201
202 formatters = [
176 formatters = [
203 QuotePattern,
177 QuotePattern,
204 SpoilerPattern,
178 SpoilerPattern,
205 ItalicPattern,
179 ItalicPattern,
206 BoldPattern,
180 BoldPattern,
207 CommentPattern,
181 CommentPattern,
208 StrikeThroughPattern,
182 StrikeThroughPattern,
209 CodePattern,
183 CodePattern,
210 ]
184 ]
185
186
187 PREPARSE_PATTERNS = {
188 r'>>>(\d+)': r'[thread]\1[/thread]', # Multi-thread post ">>>123"
189 r'(?<!>)>>(\d+)': r'[post]\1[/post]', # Reflink ">>123"
190 r'^>([^>].+)': r'[quote]\1[/quote]', # Quote ">text"
191 r'^//(.+)': r'[comment]\1[/comment]', # Comment "//text"
192 r'\B@(\w+)': r'[user]\1[/user]', # User notification "@user"
193 }
194
195
196 class Parser:
197 def __init__(self):
198 # The newline hack is added because br's margin does not work in all
199 # browsers except firefox, when the div's does.
200 self.parser = bbcode.Parser(newline='<div class="br"></div>')
201
202 self.parser.add_formatter('post', render_reflink, strip=True)
203 self.parser.add_formatter('thread', render_multithread, strip=True)
204 self.parser.add_formatter('quote', render_quote, strip=True)
205 self.parser.add_formatter('user', render_notification, strip=True)
206 self.parser.add_simple_formatter(
207 'comment', '<span class="comment">//%(value)s</span>')
208 self.parser.add_simple_formatter(
209 'spoiler', '<span class="spoiler">%(value)s</span>')
210 self.parser.add_simple_formatter(
211 's', '<span class="strikethrough">%(value)s</span>')
212 # TODO Why not use built-in tag?
213 self.parser.add_simple_formatter('code',
214 '<pre><code>%(value)s</pre></code>',
215 render_embedded=False)
216
217 def preparse(self, text):
218 """
219 Performs manual parsing before the bbcode parser is used.
220 Preparsed text is saved as raw and the text before preparsing is lost.
221 """
222 new_text = MULTI_NEWLINES_PATTERN.sub(ONE_NEWLINE, text)
223
224 for key, value in PREPARSE_PATTERNS.items():
225 new_text = re.sub(key, value, new_text, flags=re.MULTILINE)
226
227 for link in REGEX_URL.findall(text):
228 new_text = new_text.replace(link, unquote(link))
229
230 return new_text
231
232 def parse(self, text):
233 return self.parser.format(text) No newline at end of file
@@ -1,476 +1,450 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 urllib.parse import unquote
7
8 from adjacent import Client
6 from adjacent import Client
9 from django.core.exceptions import ObjectDoesNotExist
7 from django.core.exceptions import ObjectDoesNotExist
10 from django.core.urlresolvers import reverse
8 from django.core.urlresolvers import reverse
11 from django.db import models, transaction
9 from django.db import models, transaction
12 from django.db.models import TextField
10 from django.db.models import TextField
13 from django.template.loader import render_to_string
11 from django.template.loader import render_to_string
14 from django.utils import timezone
12 from django.utils import timezone
15
13
16 from boards import settings
14 from boards import settings
17 from boards.mdx_neboard import bbcode_extended
15 from boards.mdx_neboard import Parser
18 from boards.models import PostImage
16 from boards.models import PostImage
19 from boards.models.base import Viewable
17 from boards.models.base import Viewable
20 from boards.utils import datetime_to_epoch, cached_result
18 from boards.utils import datetime_to_epoch, cached_result
21 from boards.models.user import Notification
19 from boards.models.user import Notification
22 import boards.models.thread
20 import boards.models.thread
23
21
24
22
25 WS_NOTIFICATION_TYPE_NEW_POST = 'new_post'
23 WS_NOTIFICATION_TYPE_NEW_POST = 'new_post'
26 WS_NOTIFICATION_TYPE = 'notification_type'
24 WS_NOTIFICATION_TYPE = 'notification_type'
27
25
28 WS_CHANNEL_THREAD = "thread:"
26 WS_CHANNEL_THREAD = "thread:"
29
27
30 APP_LABEL_BOARDS = 'boards'
28 APP_LABEL_BOARDS = 'boards'
31
29
32 POSTS_PER_DAY_RANGE = 7
30 POSTS_PER_DAY_RANGE = 7
33
31
34 BAN_REASON_AUTO = 'Auto'
32 BAN_REASON_AUTO = 'Auto'
35
33
36 IMAGE_THUMB_SIZE = (200, 150)
34 IMAGE_THUMB_SIZE = (200, 150)
37
35
38 TITLE_MAX_LENGTH = 200
36 TITLE_MAX_LENGTH = 200
39
37
40 # TODO This should be removed
38 # TODO This should be removed
41 NO_IP = '0.0.0.0'
39 NO_IP = '0.0.0.0'
42
40
43 # TODO Real user agent should be saved instead of this
41 # TODO Real user agent should be saved instead of this
44 UNKNOWN_UA = ''
42 UNKNOWN_UA = ''
45
43
46 REGEX_REPLY = re.compile(r'\[post\](\d+)\[/post\]')
44 REGEX_REPLY = re.compile(r'\[post\](\d+)\[/post\]')
47 REGEX_MULTI_THREAD = re.compile(r'\[thread\](\d+)\[/thread\]')
45 REGEX_MULTI_THREAD = re.compile(r'\[thread\](\d+)\[/thread\]')
48 REGEX_URL = re.compile(r'https?\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(/\S*)?')
49 REGEX_NOTIFICATION = re.compile(r'\[user\](\w+)\[/user\]')
46 REGEX_NOTIFICATION = re.compile(r'\[user\](\w+)\[/user\]')
50
47
51 PARAMETER_TRUNCATED = 'truncated'
48 PARAMETER_TRUNCATED = 'truncated'
52 PARAMETER_TAG = 'tag'
49 PARAMETER_TAG = 'tag'
53 PARAMETER_OFFSET = 'offset'
50 PARAMETER_OFFSET = 'offset'
54 PARAMETER_DIFF_TYPE = 'type'
51 PARAMETER_DIFF_TYPE = 'type'
55 PARAMETER_BUMPABLE = 'bumpable'
52 PARAMETER_BUMPABLE = 'bumpable'
56 PARAMETER_THREAD = 'thread'
53 PARAMETER_THREAD = 'thread'
57 PARAMETER_IS_OPENING = 'is_opening'
54 PARAMETER_IS_OPENING = 'is_opening'
58 PARAMETER_MODERATOR = 'moderator'
55 PARAMETER_MODERATOR = 'moderator'
59 PARAMETER_POST = 'post'
56 PARAMETER_POST = 'post'
60 PARAMETER_OP_ID = 'opening_post_id'
57 PARAMETER_OP_ID = 'opening_post_id'
61 PARAMETER_NEED_OPEN_LINK = 'need_open_link'
58 PARAMETER_NEED_OPEN_LINK = 'need_open_link'
62 PARAMETER_REPLY_LINK = 'reply_link'
59 PARAMETER_REPLY_LINK = 'reply_link'
63
60
64 DIFF_TYPE_HTML = 'html'
61 DIFF_TYPE_HTML = 'html'
65 DIFF_TYPE_JSON = 'json'
62 DIFF_TYPE_JSON = 'json'
66
63
67 PREPARSE_PATTERNS = {
68 r'>>>(\d+)': r'[thread]\1[/thread]', # Multi-thread post ">>>123"
69 r'(?<!>)>>(\d+)': r'[post]\1[/post]', # Reflink ">>123"
70 r'^>([^>].+)': r'[quote]\1[/quote]', # Quote ">text"
71 r'^//(.+)': r'[comment]\1[/comment]', # Comment "//text"
72 r'\B@(\w+)': r'[user]\1[/user]', # User notification "@user"
73 }
74
75
64
76 class PostManager(models.Manager):
65 class PostManager(models.Manager):
77 @transaction.atomic
66 @transaction.atomic
78 def create_post(self, title: str, text: str, image=None, thread=None,
67 def create_post(self, title: str, text: str, image=None, thread=None,
79 ip=NO_IP, tags: list=None):
68 ip=NO_IP, tags: list=None):
80 """
69 """
81 Creates new post
70 Creates new post
82 """
71 """
83
72
84 if not tags:
73 if not tags:
85 tags = []
74 tags = []
86
75
87 posting_time = timezone.now()
76 posting_time = timezone.now()
88 if not thread:
77 if not thread:
89 thread = boards.models.thread.Thread.objects.create(
78 thread = boards.models.thread.Thread.objects.create(
90 bump_time=posting_time, last_edit_time=posting_time)
79 bump_time=posting_time, last_edit_time=posting_time)
91 new_thread = True
80 new_thread = True
92 else:
81 else:
93 new_thread = False
82 new_thread = False
94
83
95 pre_text = self._preparse_text(text)
84 pre_text = Parser().preparse(text)
96
85
97 post = self.create(title=title,
86 post = self.create(title=title,
98 text=pre_text,
87 text=pre_text,
99 pub_time=posting_time,
88 pub_time=posting_time,
100 poster_ip=ip,
89 poster_ip=ip,
101 thread=thread,
90 thread=thread,
102 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
91 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
103 # last!
92 # last!
104 last_edit_time=posting_time)
93 last_edit_time=posting_time)
105 post.threads.add(thread)
94 post.threads.add(thread)
106
95
107 logger = logging.getLogger('boards.post.create')
96 logger = logging.getLogger('boards.post.create')
108
97
109 logger.info('Created post {} by {}'.format(
98 logger.info('Created post {} by {}'.format(
110 post, post.poster_ip))
99 post, post.poster_ip))
111
100
112 if image:
101 if image:
113 post.images.add(PostImage.objects.create_with_hash(image))
102 post.images.add(PostImage.objects.create_with_hash(image))
114
103
115 list(map(thread.add_tag, tags))
104 list(map(thread.add_tag, tags))
116
105
117 if new_thread:
106 if new_thread:
118 boards.models.thread.Thread.objects.process_oldest_threads()
107 boards.models.thread.Thread.objects.process_oldest_threads()
119 else:
108 else:
120 thread.last_edit_time = posting_time
109 thread.last_edit_time = posting_time
121 thread.bump()
110 thread.bump()
122 thread.save()
111 thread.save()
123
112
124 post.connect_replies()
113 post.connect_replies()
125 post.connect_threads()
114 post.connect_threads()
126 post.connect_notifications()
115 post.connect_notifications()
127
116
128 return post
117 return post
129
118
130 def delete_posts_by_ip(self, ip):
119 def delete_posts_by_ip(self, ip):
131 """
120 """
132 Deletes all posts of the author with same IP
121 Deletes all posts of the author with same IP
133 """
122 """
134
123
135 posts = self.filter(poster_ip=ip)
124 posts = self.filter(poster_ip=ip)
136 for post in posts:
125 for post in posts:
137 post.delete()
126 post.delete()
138
127
139 @cached_result
128 @cached_result
140 def get_posts_per_day(self):
129 def get_posts_per_day(self):
141 """
130 """
142 Gets average count of posts per day for the last 7 days
131 Gets average count of posts per day for the last 7 days
143 """
132 """
144
133
145 day_end = date.today()
134 day_end = date.today()
146 day_start = day_end - timedelta(POSTS_PER_DAY_RANGE)
135 day_start = day_end - timedelta(POSTS_PER_DAY_RANGE)
147
136
148 day_time_start = timezone.make_aware(datetime.combine(
137 day_time_start = timezone.make_aware(datetime.combine(
149 day_start, dtime()), timezone.get_current_timezone())
138 day_start, dtime()), timezone.get_current_timezone())
150 day_time_end = timezone.make_aware(datetime.combine(
139 day_time_end = timezone.make_aware(datetime.combine(
151 day_end, dtime()), timezone.get_current_timezone())
140 day_end, dtime()), timezone.get_current_timezone())
152
141
153 posts_per_period = float(self.filter(
142 posts_per_period = float(self.filter(
154 pub_time__lte=day_time_end,
143 pub_time__lte=day_time_end,
155 pub_time__gte=day_time_start).count())
144 pub_time__gte=day_time_start).count())
156
145
157 ppd = posts_per_period / POSTS_PER_DAY_RANGE
146 ppd = posts_per_period / POSTS_PER_DAY_RANGE
158
147
159 return ppd
148 return ppd
160
149
161 # TODO Make a separate parser module and move preparser there
162 def _preparse_text(self, text: str) -> str:
163 """
164 Preparses text to change patterns like '>>' to a proper bbcode
165 tags.
166 """
167
168 for key, value in PREPARSE_PATTERNS.items():
169 text = re.sub(key, value, text, flags=re.MULTILINE)
170
171 for link in REGEX_URL.findall(text):
172 text = text.replace(link, unquote(link))
173
174 return text
175
176
150
177 class Post(models.Model, Viewable):
151 class Post(models.Model, Viewable):
178 """A post is a message."""
152 """A post is a message."""
179
153
180 objects = PostManager()
154 objects = PostManager()
181
155
182 class Meta:
156 class Meta:
183 app_label = APP_LABEL_BOARDS
157 app_label = APP_LABEL_BOARDS
184 ordering = ('id',)
158 ordering = ('id',)
185
159
186 title = models.CharField(max_length=TITLE_MAX_LENGTH)
160 title = models.CharField(max_length=TITLE_MAX_LENGTH)
187 pub_time = models.DateTimeField()
161 pub_time = models.DateTimeField()
188 text = TextField(blank=True, null=True)
162 text = TextField(blank=True, null=True)
189 _text_rendered = TextField(blank=True, null=True, editable=False)
163 _text_rendered = TextField(blank=True, null=True, editable=False)
190
164
191 images = models.ManyToManyField(PostImage, null=True, blank=True,
165 images = models.ManyToManyField(PostImage, null=True, blank=True,
192 related_name='ip+', db_index=True)
166 related_name='ip+', db_index=True)
193
167
194 poster_ip = models.GenericIPAddressField()
168 poster_ip = models.GenericIPAddressField()
195 poster_user_agent = models.TextField()
169 poster_user_agent = models.TextField()
196
170
197 last_edit_time = models.DateTimeField()
171 last_edit_time = models.DateTimeField()
198
172
199 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
173 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
200 null=True,
174 null=True,
201 blank=True, related_name='rfp+',
175 blank=True, related_name='rfp+',
202 db_index=True)
176 db_index=True)
203 refmap = models.TextField(null=True, blank=True)
177 refmap = models.TextField(null=True, blank=True)
204 threads = models.ManyToManyField('Thread', db_index=True)
178 threads = models.ManyToManyField('Thread', db_index=True)
205 thread = models.ForeignKey('Thread', db_index=True, related_name='pt+')
179 thread = models.ForeignKey('Thread', db_index=True, related_name='pt+')
206
180
207 def __str__(self):
181 def __str__(self):
208 return 'P#{}/{}'.format(self.id, self.title)
182 return 'P#{}/{}'.format(self.id, self.title)
209
183
210 def get_title(self) -> str:
184 def get_title(self) -> str:
211 """
185 """
212 Gets original post title or part of its text.
186 Gets original post title or part of its text.
213 """
187 """
214
188
215 title = self.title
189 title = self.title
216 if not title:
190 if not title:
217 title = self.get_text()
191 title = self.get_text()
218
192
219 return title
193 return title
220
194
221 def build_refmap(self) -> None:
195 def build_refmap(self) -> None:
222 """
196 """
223 Builds a replies map string from replies list. This is a cache to stop
197 Builds a replies map string from replies list. This is a cache to stop
224 the server from recalculating the map on every post show.
198 the server from recalculating the map on every post show.
225 """
199 """
226
200
227 post_urls = ['<a href="{}">&gt;&gt;{}</a>'.format(
201 post_urls = ['<a href="{}">&gt;&gt;{}</a>'.format(
228 refpost.get_url(), refpost.id) for refpost in self.referenced_posts.all()]
202 refpost.get_url(), refpost.id) for refpost in self.referenced_posts.all()]
229
203
230 self.refmap = ', '.join(post_urls)
204 self.refmap = ', '.join(post_urls)
231
205
232 def get_sorted_referenced_posts(self):
206 def get_sorted_referenced_posts(self):
233 return self.refmap
207 return self.refmap
234
208
235 def is_referenced(self) -> bool:
209 def is_referenced(self) -> bool:
236 return self.refmap and len(self.refmap) > 0
210 return self.refmap and len(self.refmap) > 0
237
211
238 def is_opening(self) -> bool:
212 def is_opening(self) -> bool:
239 """
213 """
240 Checks if this is an opening post or just a reply.
214 Checks if this is an opening post or just a reply.
241 """
215 """
242
216
243 return self.get_thread().get_opening_post_id() == self.id
217 return self.get_thread().get_opening_post_id() == self.id
244
218
245 @cached_result
219 @cached_result
246 def get_url(self):
220 def get_url(self):
247 """
221 """
248 Gets full url to the post.
222 Gets full url to the post.
249 """
223 """
250
224
251 thread = self.get_thread()
225 thread = self.get_thread()
252
226
253 opening_id = thread.get_opening_post_id()
227 opening_id = thread.get_opening_post_id()
254
228
255 if self.id != opening_id:
229 if self.id != opening_id:
256 link = reverse('thread', kwargs={
230 link = reverse('thread', kwargs={
257 'post_id': opening_id}) + '#' + str(self.id)
231 'post_id': opening_id}) + '#' + str(self.id)
258 else:
232 else:
259 link = reverse('thread', kwargs={'post_id': self.id})
233 link = reverse('thread', kwargs={'post_id': self.id})
260
234
261 return link
235 return link
262
236
263 def get_thread(self):
237 def get_thread(self):
264 return self.thread
238 return self.thread
265
239
266 def get_threads(self):
240 def get_threads(self):
267 """
241 """
268 Gets post's thread.
242 Gets post's thread.
269 """
243 """
270
244
271 return self.threads
245 return self.threads
272
246
273 def get_referenced_posts(self):
247 def get_referenced_posts(self):
274 return self.referenced_posts.only('id', 'threads')
248 return self.referenced_posts.only('id', 'threads')
275
249
276 def get_view(self, moderator=False, need_open_link=False,
250 def get_view(self, moderator=False, need_open_link=False,
277 truncated=False, *args, **kwargs):
251 truncated=False, *args, **kwargs):
278 """
252 """
279 Renders post's HTML view. Some of the post params can be passed over
253 Renders post's HTML view. Some of the post params can be passed over
280 kwargs for the means of caching (if we view the thread, some params
254 kwargs for the means of caching (if we view the thread, some params
281 are same for every post and don't need to be computed over and over.
255 are same for every post and don't need to be computed over and over.
282 """
256 """
283
257
284 thread = self.get_thread()
258 thread = self.get_thread()
285 is_opening = kwargs.get(PARAMETER_IS_OPENING, self.is_opening())
259 is_opening = kwargs.get(PARAMETER_IS_OPENING, self.is_opening())
286 can_bump = kwargs.get(PARAMETER_BUMPABLE, thread.can_bump())
260 can_bump = kwargs.get(PARAMETER_BUMPABLE, thread.can_bump())
287
261
288 if is_opening:
262 if is_opening:
289 opening_post_id = self.id
263 opening_post_id = self.id
290 else:
264 else:
291 opening_post_id = thread.get_opening_post_id()
265 opening_post_id = thread.get_opening_post_id()
292
266
293 return render_to_string('boards/post.html', {
267 return render_to_string('boards/post.html', {
294 PARAMETER_POST: self,
268 PARAMETER_POST: self,
295 PARAMETER_MODERATOR: moderator,
269 PARAMETER_MODERATOR: moderator,
296 PARAMETER_IS_OPENING: is_opening,
270 PARAMETER_IS_OPENING: is_opening,
297 PARAMETER_THREAD: thread,
271 PARAMETER_THREAD: thread,
298 PARAMETER_BUMPABLE: can_bump,
272 PARAMETER_BUMPABLE: can_bump,
299 PARAMETER_NEED_OPEN_LINK: need_open_link,
273 PARAMETER_NEED_OPEN_LINK: need_open_link,
300 PARAMETER_TRUNCATED: truncated,
274 PARAMETER_TRUNCATED: truncated,
301 PARAMETER_OP_ID: opening_post_id,
275 PARAMETER_OP_ID: opening_post_id,
302 })
276 })
303
277
304 def get_search_view(self, *args, **kwargs):
278 def get_search_view(self, *args, **kwargs):
305 return self.get_view(args, kwargs)
279 return self.get_view(args, kwargs)
306
280
307 def get_first_image(self) -> PostImage:
281 def get_first_image(self) -> PostImage:
308 return self.images.earliest('id')
282 return self.images.earliest('id')
309
283
310 def delete(self, using=None):
284 def delete(self, using=None):
311 """
285 """
312 Deletes all post images and the post itself.
286 Deletes all post images and the post itself.
313 """
287 """
314
288
315 for image in self.images.all():
289 for image in self.images.all():
316 image_refs_count = Post.objects.filter(images__in=[image]).count()
290 image_refs_count = Post.objects.filter(images__in=[image]).count()
317 if image_refs_count == 1:
291 if image_refs_count == 1:
318 image.delete()
292 image.delete()
319
293
320 thread = self.get_thread()
294 thread = self.get_thread()
321 thread.last_edit_time = timezone.now()
295 thread.last_edit_time = timezone.now()
322 thread.save()
296 thread.save()
323
297
324 super(Post, self).delete(using)
298 super(Post, self).delete(using)
325
299
326 logging.getLogger('boards.post.delete').info(
300 logging.getLogger('boards.post.delete').info(
327 'Deleted post {}'.format(self))
301 'Deleted post {}'.format(self))
328
302
329 def get_post_data(self, format_type=DIFF_TYPE_JSON, request=None,
303 def get_post_data(self, format_type=DIFF_TYPE_JSON, request=None,
330 include_last_update=False):
304 include_last_update=False):
331 """
305 """
332 Gets post HTML or JSON data that can be rendered on a page or used by
306 Gets post HTML or JSON data that can be rendered on a page or used by
333 API.
307 API.
334 """
308 """
335
309
336 if format_type == DIFF_TYPE_HTML:
310 if format_type == DIFF_TYPE_HTML:
337 params = dict()
311 params = dict()
338 params['post'] = self
312 params['post'] = self
339 if PARAMETER_TRUNCATED in request.GET:
313 if PARAMETER_TRUNCATED in request.GET:
340 params[PARAMETER_TRUNCATED] = True
314 params[PARAMETER_TRUNCATED] = True
341 else:
315 else:
342 params[PARAMETER_REPLY_LINK] = True
316 params[PARAMETER_REPLY_LINK] = True
343
317
344 return render_to_string('boards/api_post.html', params)
318 return render_to_string('boards/api_post.html', params)
345 elif format_type == DIFF_TYPE_JSON:
319 elif format_type == DIFF_TYPE_JSON:
346 post_json = {
320 post_json = {
347 'id': self.id,
321 'id': self.id,
348 'title': self.title,
322 'title': self.title,
349 'text': self._text_rendered,
323 'text': self._text_rendered,
350 }
324 }
351 if self.images.exists():
325 if self.images.exists():
352 post_image = self.get_first_image()
326 post_image = self.get_first_image()
353 post_json['image'] = post_image.image.url
327 post_json['image'] = post_image.image.url
354 post_json['image_preview'] = post_image.image.url_200x150
328 post_json['image_preview'] = post_image.image.url_200x150
355 if include_last_update:
329 if include_last_update:
356 post_json['bump_time'] = datetime_to_epoch(
330 post_json['bump_time'] = datetime_to_epoch(
357 self.get_thread().bump_time)
331 self.get_thread().bump_time)
358 return post_json
332 return post_json
359
333
360 def send_to_websocket(self, request, recursive=True):
334 def send_to_websocket(self, request, recursive=True):
361 """
335 """
362 Sends post HTML data to the thread web socket.
336 Sends post HTML data to the thread web socket.
363 """
337 """
364
338
365 if not settings.WEBSOCKETS_ENABLED:
339 if not settings.WEBSOCKETS_ENABLED:
366 return
340 return
367
341
368 client = Client()
342 client = Client()
369
343
370 logger = logging.getLogger('boards.post.websocket')
344 logger = logging.getLogger('boards.post.websocket')
371
345
372 thread_ids = list()
346 thread_ids = list()
373 for thread in self.get_threads().all():
347 for thread in self.get_threads().all():
374 thread_ids.append(thread.id)
348 thread_ids.append(thread.id)
375
349
376 channel_name = WS_CHANNEL_THREAD + str(thread.get_opening_post_id())
350 channel_name = WS_CHANNEL_THREAD + str(thread.get_opening_post_id())
377 client.publish(channel_name, {
351 client.publish(channel_name, {
378 WS_NOTIFICATION_TYPE: WS_NOTIFICATION_TYPE_NEW_POST,
352 WS_NOTIFICATION_TYPE: WS_NOTIFICATION_TYPE_NEW_POST,
379 })
353 })
380 client.send()
354 client.send()
381
355
382 logger.info('Sent notification from post #{} to channel {}'.format(
356 logger.info('Sent notification from post #{} to channel {}'.format(
383 self.id, channel_name))
357 self.id, channel_name))
384
358
385 if recursive:
359 if recursive:
386 for reply_number in re.finditer(REGEX_REPLY, self.get_raw_text()):
360 for reply_number in re.finditer(REGEX_REPLY, self.get_raw_text()):
387 post_id = reply_number.group(1)
361 post_id = reply_number.group(1)
388
362
389 try:
363 try:
390 ref_post = Post.objects.get(id=post_id)
364 ref_post = Post.objects.get(id=post_id)
391
365
392 if ref_post.get_threads().exclude(id__in=thread_ids).exists():
366 if ref_post.get_threads().exclude(id__in=thread_ids).exists():
393 # If post is in this thread, its thread was already notified.
367 # If post is in this thread, its thread was already notified.
394 # Otherwise, notify its thread separately.
368 # Otherwise, notify its thread separately.
395 ref_post.send_to_websocket(request, recursive=False)
369 ref_post.send_to_websocket(request, recursive=False)
396 except ObjectDoesNotExist:
370 except ObjectDoesNotExist:
397 pass
371 pass
398
372
399 def save(self, force_insert=False, force_update=False, using=None,
373 def save(self, force_insert=False, force_update=False, using=None,
400 update_fields=None):
374 update_fields=None):
401 self._text_rendered = bbcode_extended(self.get_raw_text())
375 self._text_rendered = Parser().parse(self.get_raw_text())
402
376
403 super().save(force_insert, force_update, using, update_fields)
377 super().save(force_insert, force_update, using, update_fields)
404
378
405 def get_text(self) -> str:
379 def get_text(self) -> str:
406 return self._text_rendered
380 return self._text_rendered
407
381
408 def get_raw_text(self) -> str:
382 def get_raw_text(self) -> str:
409 return self.text
383 return self.text
410
384
411 def get_absolute_id(self) -> str:
385 def get_absolute_id(self) -> str:
412 """
386 """
413 If the post has many threads, shows its main thread OP id in the post
387 If the post has many threads, shows its main thread OP id in the post
414 ID.
388 ID.
415 """
389 """
416
390
417 if self.get_threads().count() > 1:
391 if self.get_threads().count() > 1:
418 return '{}/{}'.format(self.get_thread().get_opening_post_id(), self.id)
392 return '{}/{}'.format(self.get_thread().get_opening_post_id(), self.id)
419 else:
393 else:
420 return str(self.id)
394 return str(self.id)
421
395
422 def connect_notifications(self):
396 def connect_notifications(self):
423 for reply_number in re.finditer(REGEX_NOTIFICATION, self.get_raw_text()):
397 for reply_number in re.finditer(REGEX_NOTIFICATION, self.get_raw_text()):
424 user_name = reply_number.group(1).lower()
398 user_name = reply_number.group(1).lower()
425 Notification.objects.get_or_create(name=user_name, post=self)
399 Notification.objects.get_or_create(name=user_name, post=self)
426
400
427 def connect_replies(self):
401 def connect_replies(self):
428 """
402 """
429 Connects replies to a post to show them as a reflink map
403 Connects replies to a post to show them as a reflink map
430 """
404 """
431
405
432 for reply_number in re.finditer(REGEX_REPLY, self.get_raw_text()):
406 for reply_number in re.finditer(REGEX_REPLY, self.get_raw_text()):
433 post_id = reply_number.group(1)
407 post_id = reply_number.group(1)
434
408
435 try:
409 try:
436 referenced_post = Post.objects.get(id=post_id)
410 referenced_post = Post.objects.get(id=post_id)
437
411
438 referenced_post.referenced_posts.add(self)
412 referenced_post.referenced_posts.add(self)
439 referenced_post.last_edit_time = self.pub_time
413 referenced_post.last_edit_time = self.pub_time
440 referenced_post.build_refmap()
414 referenced_post.build_refmap()
441 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
415 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
442
416
443 referenced_threads = referenced_post.get_threads().all()
417 referenced_threads = referenced_post.get_threads().all()
444 for thread in referenced_threads:
418 for thread in referenced_threads:
445 if thread.can_bump():
419 if thread.can_bump():
446 thread.update_bump_status()
420 thread.update_bump_status()
447
421
448 thread.last_edit_time = self.pub_time
422 thread.last_edit_time = self.pub_time
449 thread.save(update_fields=['last_edit_time', 'bumpable'])
423 thread.save(update_fields=['last_edit_time', 'bumpable'])
450 except ObjectDoesNotExist:
424 except ObjectDoesNotExist:
451 pass
425 pass
452
426
453 def connect_threads(self):
427 def connect_threads(self):
454 """
428 """
455 If the referenced post is an OP in another thread,
429 If the referenced post is an OP in another thread,
456 make this post multi-thread.
430 make this post multi-thread.
457 """
431 """
458
432
459 for reply_number in re.finditer(REGEX_MULTI_THREAD, self.get_raw_text()):
433 for reply_number in re.finditer(REGEX_MULTI_THREAD, self.get_raw_text()):
460 post_id = reply_number.group(1)
434 post_id = reply_number.group(1)
461
435
462 try:
436 try:
463 referenced_post = Post.objects.get(id=post_id)
437 referenced_post = Post.objects.get(id=post_id)
464
438
465 if referenced_post.is_opening():
439 if referenced_post.is_opening():
466 referenced_threads = referenced_post.get_threads().all()
440 referenced_threads = referenced_post.get_threads().all()
467 for thread in referenced_threads:
441 for thread in referenced_threads:
468 if thread.can_bump():
442 if thread.can_bump():
469 thread.update_bump_status()
443 thread.update_bump_status()
470
444
471 thread.last_edit_time = self.pub_time
445 thread.last_edit_time = self.pub_time
472 thread.save(update_fields=['last_edit_time', 'bumpable'])
446 thread.save(update_fields=['last_edit_time', 'bumpable'])
473
447
474 self.threads.add(thread)
448 self.threads.add(thread)
475 except ObjectDoesNotExist:
449 except ObjectDoesNotExist:
476 pass
450 pass
@@ -1,192 +1,191 b''
1 {% extends "boards/base.html" %}
1 {% extends "boards/base.html" %}
2
2
3 {% load i18n %}
3 {% load i18n %}
4 {% load cache %}
4 {% load cache %}
5 {% load board %}
5 {% load board %}
6 {% load static %}
6 {% load static %}
7 {% load tz %}
7 {% load tz %}
8
8
9 {% block head %}
9 {% block head %}
10 <meta name="robots" content="noindex">
10 <meta name="robots" content="noindex">
11
11
12 {% if tag %}
12 {% if tag %}
13 <title>{{ tag.name }} - {{ site_name }}</title>
13 <title>{{ tag.name }} - {{ site_name }}</title>
14 {% else %}
14 {% else %}
15 <title>{{ site_name }}</title>
15 <title>{{ site_name }}</title>
16 {% endif %}
16 {% endif %}
17
17
18 {% if current_page.has_previous %}
18 {% if current_page.has_previous %}
19 <link rel="prev" href="
19 <link rel="prev" href="
20 {% if tag %}
20 {% if tag %}
21 {% url "tag" tag_name=tag.name page=current_page.previous_page_number %}
21 {% url "tag" tag_name=tag.name page=current_page.previous_page_number %}
22 {% else %}
22 {% else %}
23 {% url "index" page=current_page.previous_page_number %}
23 {% url "index" page=current_page.previous_page_number %}
24 {% endif %}
24 {% endif %}
25 " />
25 " />
26 {% endif %}
26 {% endif %}
27 {% if current_page.has_next %}
27 {% if current_page.has_next %}
28 <link rel="next" href="
28 <link rel="next" href="
29 {% if tag %}
29 {% if tag %}
30 {% url "tag" tag_name=tag.name page=current_page.next_page_number %}
30 {% url "tag" tag_name=tag.name page=current_page.next_page_number %}
31 {% else %}
31 {% else %}
32 {% url "index" page=current_page.next_page_number %}
32 {% url "index" page=current_page.next_page_number %}
33 {% endif %}
33 {% endif %}
34 " />
34 " />
35 {% endif %}
35 {% endif %}
36
36
37 {% endblock %}
37 {% endblock %}
38
38
39 {% block content %}
39 {% block content %}
40
40
41 {% get_current_language as LANGUAGE_CODE %}
41 {% get_current_language as LANGUAGE_CODE %}
42 {% get_current_timezone as TIME_ZONE %}
42 {% get_current_timezone as TIME_ZONE %}
43
43
44 {% if tag %}
44 {% if tag %}
45 <div class="tag_info">
45 <div class="tag_info">
46 <h2>
46 <h2>
47 <form action="{% url 'tag' tag.name %}" method="post" class="post-button-form">
47 <form action="{% url 'tag' tag.name %}" method="post" class="post-button-form">
48 {% if is_favorite %}
48 {% if is_favorite %}
49 <button name="method" value="unsubscribe" class="fav">β˜…</button>
49 <button name="method" value="unsubscribe" class="fav">β˜…</button>
50 {% else %}
50 {% else %}
51 <button name="method" value="subscribe" class="not_fav">β˜…</button>
51 <button name="method" value="subscribe" class="not_fav">β˜…</button>
52 {% endif %}
52 {% endif %}
53 </form>
53 </form>
54 <form action="{% url 'tag' tag.name %}" method="post" class="post-button-form">
54 <form action="{% url 'tag' tag.name %}" method="post" class="post-button-form">
55 {% if is_hidden %}
55 {% if is_hidden %}
56 <button name="method" value="unhide" class="fav">H</button>
56 <button name="method" value="unhide" class="fav">H</button>
57 {% else %}
57 {% else %}
58 <button name="method" value="hide" class="not_fav">H</button>
58 <button name="method" value="hide" class="not_fav">H</button>
59 {% endif %}
59 {% endif %}
60 </form>
60 </form>
61 {% autoescape off %}
61 {% autoescape off %}
62 {{ tag.get_view }}
62 {{ tag.get_view }}
63 {% endautoescape %}
63 {% endautoescape %}
64 {% if moderator %}
64 {% if moderator %}
65 <span class="moderator_info">[<a href="{% url 'admin:boards_tag_change' tag.id %}">{% trans 'Edit tag' %}</a>]</span>
65 <span class="moderator_info">[<a href="{% url 'admin:boards_tag_change' tag.id %}">{% trans 'Edit tag' %}</a>]</span>
66 {% endif %}
66 {% endif %}
67 </h2>
67 </h2>
68 <p>{% blocktrans with thread_count=tag.get_thread_count post_count=tag.get_post_count %}This tag has {{ thread_count }} threads and {{ post_count }} posts.{% endblocktrans %}</p>
68 <p>{% blocktrans with thread_count=tag.get_thread_count post_count=tag.get_post_count %}This tag has {{ thread_count }} threads and {{ post_count }} posts.{% endblocktrans %}</p>
69 </div>
69 </div>
70 {% endif %}
70 {% endif %}
71
71
72 {% if threads %}
72 {% if threads %}
73 {% if current_page.has_previous %}
73 {% if current_page.has_previous %}
74 <div class="page_link">
74 <div class="page_link">
75 <a href="
75 <a href="
76 {% if tag %}
76 {% if tag %}
77 {% url "tag" tag_name=tag.name page=current_page.previous_page_number %}
77 {% url "tag" tag_name=tag.name page=current_page.previous_page_number %}
78 {% else %}
78 {% else %}
79 {% url "index" page=current_page.previous_page_number %}
79 {% url "index" page=current_page.previous_page_number %}
80 {% endif %}
80 {% endif %}
81 ">{% trans "Previous page" %}</a>
81 ">{% trans "Previous page" %}</a>
82 </div>
82 </div>
83 {% endif %}
83 {% endif %}
84
84
85 {% for thread in threads %}
85 {% for thread in threads %}
86 {% cache 600 thread_short thread.id thread.last_edit_time moderator LANGUAGE_CODE TIME_ZONE %}
86 {% cache 600 thread_short thread.id thread.last_edit_time moderator LANGUAGE_CODE TIME_ZONE %}
87 <div class="thread">
87 <div class="thread">
88 {% post_view thread.get_opening_post moderator is_opening=True thread=thread truncated=True need_open_link=True %}
88 {% post_view thread.get_opening_post moderator is_opening=True thread=thread truncated=True need_open_link=True %}
89 {% if not thread.archived %}
89 {% if not thread.archived %}
90 {% with last_replies=thread.get_last_replies %}
90 {% with last_replies=thread.get_last_replies %}
91 {% if last_replies %}
91 {% if last_replies %}
92 {% with skipped_replies_count=thread.get_skipped_replies_count %}
92 {% with skipped_replies_count=thread.get_skipped_replies_count %}
93 {% if skipped_replies_count %}
93 {% if skipped_replies_count %}
94 <div class="skipped_replies">
94 <div class="skipped_replies">
95 <a href="{% url 'thread' thread.get_opening_post_id %}">
95 <a href="{% url 'thread' thread.get_opening_post_id %}">
96 {% blocktrans with count=skipped_replies_count %}Skipped {{ count }} replies. Open thread to see all replies.{% endblocktrans %}
96 {% blocktrans with count=skipped_replies_count %}Skipped {{ count }} replies. Open thread to see all replies.{% endblocktrans %}
97 </a>
97 </a>
98 </div>
98 </div>
99 {% endif %}
99 {% endif %}
100 {% endwith %}
100 {% endwith %}
101 <div class="last-replies">
101 <div class="last-replies">
102 {% for post in last_replies %}
102 {% for post in last_replies %}
103 {% post_view post is_opening=False moderator=moderator truncated=True %}
103 {% post_view post is_opening=False moderator=moderator truncated=True %}
104 {% endfor %}
104 {% endfor %}
105 </div>
105 </div>
106 {% endif %}
106 {% endif %}
107 {% endwith %}
107 {% endwith %}
108 {% endif %}
108 {% endif %}
109 </div>
109 </div>
110 {% endcache %}
110 {% endcache %}
111 {% endfor %}
111 {% endfor %}
112
112
113 {% if current_page.has_next %}
113 {% if current_page.has_next %}
114 <div class="page_link">
114 <div class="page_link">
115 <a href="
115 <a href="
116 {% if tag %}
116 {% if tag %}
117 {% url "tag" tag_name=tag.name page=current_page.next_page_number %}
117 {% url "tag" tag_name=tag.name page=current_page.next_page_number %}
118 {% else %}
118 {% else %}
119 {% url "index" page=current_page.next_page_number %}
119 {% url "index" page=current_page.next_page_number %}
120 {% endif %}
120 {% endif %}
121 ">{% trans "Next page" %}</a>
121 ">{% trans "Next page" %}</a>
122 </div>
122 </div>
123 {% endif %}
123 {% endif %}
124 {% else %}
124 {% else %}
125 <div class="post">
125 <div class="post">
126 {% trans 'No threads exist. Create the first one!' %}</div>
126 {% trans 'No threads exist. Create the first one!' %}</div>
127 {% endif %}
127 {% endif %}
128
128
129 <div class="post-form-w">
129 <div class="post-form-w">
130 <script src="{% static 'js/panel.js' %}"></script>
130 <script src="{% static 'js/panel.js' %}"></script>
131 <div class="post-form">
131 <div class="post-form">
132 <div class="form-title">{% trans "Create new thread" %}</div>
132 <div class="form-title">{% trans "Create new thread" %}</div>
133 <div class="swappable-form-full">
133 <div class="swappable-form-full">
134 <form enctype="multipart/form-data" method="post" id="form">{% csrf_token %}
134 <form enctype="multipart/form-data" method="post" id="form">{% csrf_token %}
135 {{ form.as_div }}
135 {{ form.as_div }}
136 <div class="form-submit">
136 <div class="form-submit">
137 <input type="submit" value="{% trans "Post" %}"/>
137 <input type="submit" value="{% trans "Post" %}"/>
138 </div>
138 </div>
139 (ctrl-enter)
140 </form>
139 </form>
141 </div>
140 </div>
142 <div>
141 <div>
143 {% trans 'Tags must be delimited by spaces. Text or image is required.' %}
142 {% trans 'Tags must be delimited by spaces. Text or image is required.' %}
144 </div>
143 </div>
145 <div><a href="{% url "staticpage" name="help" %}">
144 <div><a href="{% url "staticpage" name="help" %}">
146 {% trans 'Text syntax' %}</a></div>
145 {% trans 'Text syntax' %}</a></div>
147 </div>
146 </div>
148 </div>
147 </div>
149
148
150 <script src="{% static 'js/form.js' %}"></script>
149 <script src="{% static 'js/form.js' %}"></script>
151
150
152 {% endblock %}
151 {% endblock %}
153
152
154 {% block metapanel %}
153 {% block metapanel %}
155
154
156 <span class="metapanel">
155 <span class="metapanel">
157 <b><a href="{% url "authors" %}">{{ site_name }}</a> {{ version }}</b>
156 <b><a href="{% url "authors" %}">{{ site_name }}</a> {{ version }}</b>
158 {% trans "Pages:" %}
157 {% trans "Pages:" %}
159 <a href="
158 <a href="
160 {% if tag %}
159 {% if tag %}
161 {% url "tag" tag_name=tag.name page=paginator.page_range|first %}
160 {% url "tag" tag_name=tag.name page=paginator.page_range|first %}
162 {% else %}
161 {% else %}
163 {% url "index" page=paginator.page_range|first %}
162 {% url "index" page=paginator.page_range|first %}
164 {% endif %}
163 {% endif %}
165 ">&lt;&lt;</a>
164 ">&lt;&lt;</a>
166 [
165 [
167 {% for page in paginator.center_range %}
166 {% for page in paginator.center_range %}
168 <a
167 <a
169 {% ifequal page current_page.number %}
168 {% ifequal page current_page.number %}
170 class="current_page"
169 class="current_page"
171 {% endifequal %}
170 {% endifequal %}
172 href="
171 href="
173 {% if tag %}
172 {% if tag %}
174 {% url "tag" tag_name=tag.name page=page %}
173 {% url "tag" tag_name=tag.name page=page %}
175 {% else %}
174 {% else %}
176 {% url "index" page=page %}
175 {% url "index" page=page %}
177 {% endif %}
176 {% endif %}
178 ">{{ page }}</a>
177 ">{{ page }}</a>
179 {% if not forloop.last %},{% endif %}
178 {% if not forloop.last %},{% endif %}
180 {% endfor %}
179 {% endfor %}
181 ]
180 ]
182 <a href="
181 <a href="
183 {% if tag %}
182 {% if tag %}
184 {% url "tag" tag_name=tag.name page=paginator.page_range|last %}
183 {% url "tag" tag_name=tag.name page=paginator.page_range|last %}
185 {% else %}
184 {% else %}
186 {% url "index" page=paginator.page_range|last %}
185 {% url "index" page=paginator.page_range|last %}
187 {% endif %}
186 {% endif %}
188 ">&gt;&gt;</a>
187 ">&gt;&gt;</a>
189 [<a href="rss/">RSS</a>]
188 [<a href="rss/">RSS</a>]
190 </span>
189 </span>
191
190
192 {% endblock %}
191 {% endblock %}
@@ -1,34 +1,35 b''
1 from django.test import TestCase
1 from django.test import TestCase
2 from boards.mdx_neboard import Parser
2 from boards.models import Post
3 from boards.models import Post
3
4
4
5
5 class ParserTest(TestCase):
6 class ParserTest(TestCase):
6 def test_preparse_quote(self):
7 def test_preparse_quote(self):
7 raw_text = '>quote\nQuote in >line\nLine\n>Quote'
8 raw_text = '>quote\nQuote in >line\nLine\n>Quote'
8 preparsed_text = Post.objects._preparse_text(raw_text)
9 preparsed_text = Parser().preparse(raw_text)
9
10
10 self.assertEqual(
11 self.assertEqual(
11 '[quote]quote[/quote]\nQuote in >line\nLine\n[quote]Quote[/quote]',
12 '[quote]quote[/quote]\nQuote in >line\nLine\n[quote]Quote[/quote]',
12 preparsed_text, 'Quote not preparsed.')
13 preparsed_text, 'Quote not preparsed.')
13
14
14 def test_preparse_comment(self):
15 def test_preparse_comment(self):
15 raw_text = '//comment'
16 raw_text = '//comment'
16 preparsed_text = Post.objects._preparse_text(raw_text)
17 preparsed_text = Parser().preparse(raw_text)
17
18
18 self.assertEqual('[comment]comment[/comment]', preparsed_text,
19 self.assertEqual('[comment]comment[/comment]', preparsed_text,
19 'Comment not preparsed.')
20 'Comment not preparsed.')
20
21
21 def test_preparse_reflink(self):
22 def test_preparse_reflink(self):
22 raw_text = '>>12\nText'
23 raw_text = '>>12\nText'
23 preparsed_text = Post.objects._preparse_text(raw_text)
24 preparsed_text = Parser().preparse(raw_text)
24
25
25 self.assertEqual('[post]12[/post]\nText',
26 self.assertEqual('[post]12[/post]\nText',
26 preparsed_text, 'Reflink not preparsed.')
27 preparsed_text, 'Reflink not preparsed.')
27
28
28 def preparse_user(self):
29 def preparse_user(self):
29 raw_text = '@user\nuser@example.com\n@user\nuser @user'
30 raw_text = '@user\nuser@example.com\n@user\nuser @user'
30 preparsed_text = Post.objects._preparse_text(raw_text)
31 preparsed_text = Parser().preparse(raw_text)
31
32
32 self.assertEqual('[user]user[/user]\nuser@example.com\n[user]user[/user]\nuser [user]user[/user]',
33 self.assertEqual('[user]user[/user]\nuser@example.com\n[user]user[/user]\nuser [user]user[/user]',
33 preparsed_text, 'User link not preparsed.')
34 preparsed_text, 'User link not preparsed.')
34
35
@@ -1,37 +1,39 b''
1 from django.shortcuts import render
1 from django.shortcuts import render
2 from django.template import RequestContext
2 from django.template import RequestContext
3 from django.views.generic import View
3 from django.views.generic import View
4
4
5 from boards.mdx_neboard import bbcode_extended
5 from boards.mdx_neboard import Parser
6
6
7
7 FORM_QUERY = 'query'
8 FORM_QUERY = 'query'
8
9
9 CONTEXT_RESULT = 'result'
10 CONTEXT_RESULT = 'result'
10 CONTEXT_QUERY = 'query'
11 CONTEXT_QUERY = 'query'
11
12
12 __author__ = 'neko259'
13 __author__ = 'neko259'
13
14
14 TEMPLATE = 'boards/preview.html'
15 TEMPLATE = 'boards/preview.html'
15
16
16
17
17 class PostPreviewView(View):
18 class PostPreviewView(View):
18 def get(self, request):
19 def get(self, request):
19 context = RequestContext(request)
20 context = RequestContext(request)
20
21
21 # TODO Use dict here
22 # TODO Use dict here
22 return render(request, TEMPLATE, context_instance=context)
23 return render(request, TEMPLATE, context_instance=context)
23
24
24 def post(self, request):
25 def post(self, request):
25 context = RequestContext(request)
26 context = RequestContext(request)
26
27
27 if FORM_QUERY in request.POST:
28 if FORM_QUERY in request.POST:
28 raw_text = request.POST[FORM_QUERY]
29 raw_text = request.POST[FORM_QUERY]
29
30
30 if len(raw_text) >= 0:
31 if len(raw_text) >= 0:
31 rendered_text = bbcode_extended(raw_text)
32 parser = Parser()
33 rendered_text = parser.parse(parser.preparse(raw_text))
32
34
33 context[CONTEXT_RESULT] = rendered_text
35 context[CONTEXT_RESULT] = rendered_text
34 context[CONTEXT_QUERY] = raw_text
36 context[CONTEXT_QUERY] = raw_text
35
37
36 # TODO Use dict here
38 # TODO Use dict here
37 return render(request, TEMPLATE, context_instance=context)
39 return render(request, TEMPLATE, context_instance=context)
@@ -1,234 +1,233 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
4
3
5 DEBUG = True
4 DEBUG = True
6 TEMPLATE_DEBUG = DEBUG
5 TEMPLATE_DEBUG = DEBUG
7
6
8 ADMINS = (
7 ADMINS = (
9 # ('Your Name', 'your_email@example.com'),
8 # ('Your Name', 'your_email@example.com'),
10 ('admin', 'admin@example.com')
9 ('admin', 'admin@example.com')
11 )
10 )
12
11
13 MANAGERS = ADMINS
12 MANAGERS = ADMINS
14
13
15 DATABASES = {
14 DATABASES = {
16 'default': {
15 'default': {
17 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
16 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
18 'NAME': 'database.db', # Or path to database file if using sqlite3.
17 'NAME': 'database.db', # Or path to database file if using sqlite3.
19 'USER': '', # Not used with sqlite3.
18 'USER': '', # Not used with sqlite3.
20 'PASSWORD': '', # Not used with sqlite3.
19 'PASSWORD': '', # Not used with sqlite3.
21 'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
20 'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
22 'PORT': '', # Set to empty string for default. Not used with sqlite3.
21 'PORT': '', # Set to empty string for default. Not used with sqlite3.
23 'CONN_MAX_AGE': None,
22 'CONN_MAX_AGE': None,
24 }
23 }
25 }
24 }
26
25
27 # Local time zone for this installation. Choices can be found here:
26 # Local time zone for this installation. Choices can be found here:
28 # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
27 # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
29 # although not all choices may be available on all operating systems.
28 # 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.
29 # In a Windows environment this must be set to your system time zone.
31 TIME_ZONE = 'Europe/Kiev'
30 TIME_ZONE = 'Europe/Kiev'
32
31
33 # Language code for this installation. All choices can be found here:
32 # Language code for this installation. All choices can be found here:
34 # http://www.i18nguy.com/unicode/language-identifiers.html
33 # http://www.i18nguy.com/unicode/language-identifiers.html
35 LANGUAGE_CODE = 'en'
34 LANGUAGE_CODE = 'en'
36
35
37 SITE_ID = 1
36 SITE_ID = 1
38
37
39 # If you set this to False, Django will make some optimizations so as not
38 # If you set this to False, Django will make some optimizations so as not
40 # to load the internationalization machinery.
39 # to load the internationalization machinery.
41 USE_I18N = True
40 USE_I18N = True
42
41
43 # If you set this to False, Django will not format dates, numbers and
42 # If you set this to False, Django will not format dates, numbers and
44 # calendars according to the current locale.
43 # calendars according to the current locale.
45 USE_L10N = True
44 USE_L10N = True
46
45
47 # If you set this to False, Django will not use timezone-aware datetimes.
46 # If you set this to False, Django will not use timezone-aware datetimes.
48 USE_TZ = True
47 USE_TZ = True
49
48
50 # Absolute filesystem path to the directory that will hold user-uploaded files.
49 # Absolute filesystem path to the directory that will hold user-uploaded files.
51 # Example: "/home/media/media.lawrence.com/media/"
50 # Example: "/home/media/media.lawrence.com/media/"
52 MEDIA_ROOT = './media/'
51 MEDIA_ROOT = './media/'
53
52
54 # URL that handles the media served from MEDIA_ROOT. Make sure to use a
53 # URL that handles the media served from MEDIA_ROOT. Make sure to use a
55 # trailing slash.
54 # trailing slash.
56 # Examples: "http://media.lawrence.com/media/", "http://example.com/media/"
55 # Examples: "http://media.lawrence.com/media/", "http://example.com/media/"
57 MEDIA_URL = '/media/'
56 MEDIA_URL = '/media/'
58
57
59 # Absolute path to the directory static files should be collected to.
58 # Absolute path to the directory static files should be collected to.
60 # Don't put anything in this directory yourself; store your static files
59 # Don't put anything in this directory yourself; store your static files
61 # in apps' "static/" subdirectories and in STATICFILES_DIRS.
60 # in apps' "static/" subdirectories and in STATICFILES_DIRS.
62 # Example: "/home/media/media.lawrence.com/static/"
61 # Example: "/home/media/media.lawrence.com/static/"
63 STATIC_ROOT = ''
62 STATIC_ROOT = ''
64
63
65 # URL prefix for static files.
64 # URL prefix for static files.
66 # Example: "http://media.lawrence.com/static/"
65 # Example: "http://media.lawrence.com/static/"
67 STATIC_URL = '/static/'
66 STATIC_URL = '/static/'
68
67
69 # Additional locations of static files
68 # Additional locations of static files
70 # It is really a hack, put real paths, not related
69 # It is really a hack, put real paths, not related
71 STATICFILES_DIRS = (
70 STATICFILES_DIRS = (
72 os.path.dirname(__file__) + '/boards/static',
71 os.path.dirname(__file__) + '/boards/static',
73
72
74 # '/d/work/python/django/neboard/neboard/boards/static',
73 # '/d/work/python/django/neboard/neboard/boards/static',
75 # Put strings here, like "/home/html/static" or "C:/www/django/static".
74 # Put strings here, like "/home/html/static" or "C:/www/django/static".
76 # Always use forward slashes, even on Windows.
75 # Always use forward slashes, even on Windows.
77 # Don't forget to use absolute paths, not relative paths.
76 # Don't forget to use absolute paths, not relative paths.
78 )
77 )
79
78
80 # List of finder classes that know how to find static files in
79 # List of finder classes that know how to find static files in
81 # various locations.
80 # various locations.
82 STATICFILES_FINDERS = (
81 STATICFILES_FINDERS = (
83 'django.contrib.staticfiles.finders.FileSystemFinder',
82 'django.contrib.staticfiles.finders.FileSystemFinder',
84 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
83 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
85 'compressor.finders.CompressorFinder',
84 'compressor.finders.CompressorFinder',
86 )
85 )
87
86
88 if DEBUG:
87 if DEBUG:
89 STATICFILES_STORAGE = \
88 STATICFILES_STORAGE = \
90 'django.contrib.staticfiles.storage.StaticFilesStorage'
89 'django.contrib.staticfiles.storage.StaticFilesStorage'
91 else:
90 else:
92 STATICFILES_STORAGE = \
91 STATICFILES_STORAGE = \
93 'django.contrib.staticfiles.storage.CachedStaticFilesStorage'
92 'django.contrib.staticfiles.storage.CachedStaticFilesStorage'
94
93
95 # Make this unique, and don't share it with anybody.
94 # 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'
95 SECRET_KEY = '@1rc$o(7=tt#kd+4s$u6wchm**z^)4x90)7f6z(i&amp;55@o11*8o'
97
96
98 # 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.
99 TEMPLATE_LOADERS = (
98 TEMPLATE_LOADERS = (
100 'django.template.loaders.filesystem.Loader',
99 'django.template.loaders.filesystem.Loader',
101 'django.template.loaders.app_directories.Loader',
100 'django.template.loaders.app_directories.Loader',
102 )
101 )
103
102
104 TEMPLATE_CONTEXT_PROCESSORS = (
103 TEMPLATE_CONTEXT_PROCESSORS = (
105 'django.core.context_processors.media',
104 'django.core.context_processors.media',
106 'django.core.context_processors.static',
105 'django.core.context_processors.static',
107 'django.core.context_processors.request',
106 'django.core.context_processors.request',
108 'django.contrib.auth.context_processors.auth',
107 'django.contrib.auth.context_processors.auth',
109 'boards.context_processors.user_and_ui_processor',
108 'boards.context_processors.user_and_ui_processor',
110 )
109 )
111
110
112 MIDDLEWARE_CLASSES = (
111 MIDDLEWARE_CLASSES = (
113 'django.contrib.sessions.middleware.SessionMiddleware',
112 'django.contrib.sessions.middleware.SessionMiddleware',
114 'django.middleware.locale.LocaleMiddleware',
113 'django.middleware.locale.LocaleMiddleware',
115 'django.middleware.common.CommonMiddleware',
114 'django.middleware.common.CommonMiddleware',
116 'django.contrib.auth.middleware.AuthenticationMiddleware',
115 'django.contrib.auth.middleware.AuthenticationMiddleware',
117 'django.contrib.messages.middleware.MessageMiddleware',
116 'django.contrib.messages.middleware.MessageMiddleware',
118 'boards.middlewares.BanMiddleware',
117 'boards.middlewares.BanMiddleware',
119 'boards.middlewares.TimezoneMiddleware',
118 'boards.middlewares.TimezoneMiddleware',
120 )
119 )
121
120
122 ROOT_URLCONF = 'neboard.urls'
121 ROOT_URLCONF = 'neboard.urls'
123
122
124 # 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.
125 WSGI_APPLICATION = 'neboard.wsgi.application'
124 WSGI_APPLICATION = 'neboard.wsgi.application'
126
125
127 TEMPLATE_DIRS = (
126 TEMPLATE_DIRS = (
128 # 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".
129 # Always use forward slashes, even on Windows.
128 # Always use forward slashes, even on Windows.
130 # Don't forget to use absolute paths, not relative paths.
129 # Don't forget to use absolute paths, not relative paths.
131 'templates',
130 'templates',
132 )
131 )
133
132
134 INSTALLED_APPS = (
133 INSTALLED_APPS = (
135 'django.contrib.auth',
134 'django.contrib.auth',
136 'django.contrib.contenttypes',
135 'django.contrib.contenttypes',
137 'django.contrib.sessions',
136 'django.contrib.sessions',
138 # 'django.contrib.sites',
137 # 'django.contrib.sites',
139 'django.contrib.messages',
138 'django.contrib.messages',
140 'django.contrib.staticfiles',
139 'django.contrib.staticfiles',
141 # Uncomment the next line to enable the admin:
140 # Uncomment the next line to enable the admin:
142 'django.contrib.admin',
141 'django.contrib.admin',
143 # Uncomment the next line to enable admin documentation:
142 # Uncomment the next line to enable admin documentation:
144 # 'django.contrib.admindocs',
143 # 'django.contrib.admindocs',
145 'django.contrib.humanize',
144 'django.contrib.humanize',
146 'django_cleanup',
145 'django_cleanup',
147
146
148 'debug_toolbar',
147 'debug_toolbar',
149
148
150 # Search
149 # Search
151 'haystack',
150 'haystack',
152
151
153 'boards',
152 'boards',
154 )
153 )
155
154
156 # A sample logging configuration. The only tangible logging
155 # A sample logging configuration. The only tangible logging
157 # performed by this configuration is to send an email to
156 # performed by this configuration is to send an email to
158 # the site admins on every HTTP 500 error when DEBUG=False.
157 # the site admins on every HTTP 500 error when DEBUG=False.
159 # See http://docs.djangoproject.com/en/dev/topics/logging for
158 # See http://docs.djangoproject.com/en/dev/topics/logging for
160 # more details on how to customize your logging configuration.
159 # more details on how to customize your logging configuration.
161 LOGGING = {
160 LOGGING = {
162 'version': 1,
161 'version': 1,
163 'disable_existing_loggers': False,
162 'disable_existing_loggers': False,
164 'formatters': {
163 'formatters': {
165 'verbose': {
164 'verbose': {
166 'format': '%(levelname)s %(asctime)s %(name)s %(process)d %(thread)d %(message)s'
165 'format': '%(levelname)s %(asctime)s %(name)s %(process)d %(thread)d %(message)s'
167 },
166 },
168 'simple': {
167 'simple': {
169 'format': '%(levelname)s %(asctime)s [%(name)s] %(message)s'
168 'format': '%(levelname)s %(asctime)s [%(name)s] %(message)s'
170 },
169 },
171 },
170 },
172 'filters': {
171 'filters': {
173 'require_debug_false': {
172 'require_debug_false': {
174 '()': 'django.utils.log.RequireDebugFalse'
173 '()': 'django.utils.log.RequireDebugFalse'
175 }
174 }
176 },
175 },
177 'handlers': {
176 'handlers': {
178 'console': {
177 'console': {
179 'level': 'DEBUG',
178 'level': 'DEBUG',
180 'class': 'logging.StreamHandler',
179 'class': 'logging.StreamHandler',
181 'formatter': 'simple'
180 'formatter': 'simple'
182 },
181 },
183 },
182 },
184 'loggers': {
183 'loggers': {
185 'boards': {
184 'boards': {
186 'handlers': ['console'],
185 'handlers': ['console'],
187 'level': 'DEBUG',
186 'level': 'DEBUG',
188 }
187 }
189 },
188 },
190 }
189 }
191
190
192 HAYSTACK_CONNECTIONS = {
191 HAYSTACK_CONNECTIONS = {
193 'default': {
192 'default': {
194 'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',
193 'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',
195 'PATH': os.path.join(os.path.dirname(__file__), 'whoosh_index'),
194 'PATH': os.path.join(os.path.dirname(__file__), 'whoosh_index'),
196 },
195 },
197 }
196 }
198
197
199 THEMES = [
198 THEMES = [
200 ('md', 'Mystic Dark'),
199 ('md', 'Mystic Dark'),
201 ('md_centered', 'Mystic Dark (centered)'),
200 ('md_centered', 'Mystic Dark (centered)'),
202 ('sw', 'Snow White'),
201 ('sw', 'Snow White'),
203 ('pg', 'Photon Gray'),
202 ('pg', 'Photon Gray'),
204 ]
203 ]
205
204
206 POSTING_DELAY = 20 # seconds
205 POSTING_DELAY = 20 # seconds
207
206
208 # Websocket settins
207 # Websocket settins
209 CENTRIFUGE_HOST = 'localhost'
208 CENTRIFUGE_HOST = 'localhost'
210 CENTRIFUGE_PORT = '9090'
209 CENTRIFUGE_PORT = '9090'
211
210
212 CENTRIFUGE_ADDRESS = 'http://{}:{}'.format(CENTRIFUGE_HOST, CENTRIFUGE_PORT)
211 CENTRIFUGE_ADDRESS = 'http://{}:{}'.format(CENTRIFUGE_HOST, CENTRIFUGE_PORT)
213 CENTRIFUGE_PROJECT_ID = '<project id here>'
212 CENTRIFUGE_PROJECT_ID = '<project id here>'
214 CENTRIFUGE_PROJECT_SECRET = '<project secret here>'
213 CENTRIFUGE_PROJECT_SECRET = '<project secret here>'
215 CENTRIFUGE_TIMEOUT = 5
214 CENTRIFUGE_TIMEOUT = 5
216
215
217 # Debug mode middlewares
216 # Debug mode middlewares
218 if DEBUG:
217 if DEBUG:
219 MIDDLEWARE_CLASSES += (
218 MIDDLEWARE_CLASSES += (
220 'debug_toolbar.middleware.DebugToolbarMiddleware',
219 'debug_toolbar.middleware.DebugToolbarMiddleware',
221 )
220 )
222
221
223 def custom_show_toolbar(request):
222 def custom_show_toolbar(request):
224 return True
223 return True
225
224
226 DEBUG_TOOLBAR_CONFIG = {
225 DEBUG_TOOLBAR_CONFIG = {
227 'ENABLE_STACKTRACES': True,
226 'ENABLE_STACKTRACES': True,
228 'SHOW_TOOLBAR_CALLBACK': 'neboard.settings.custom_show_toolbar',
227 'SHOW_TOOLBAR_CALLBACK': 'neboard.settings.custom_show_toolbar',
229 }
228 }
230
229
231 # FIXME Uncommenting this fails somehow. Need to investigate this
230 # FIXME Uncommenting this fails somehow. Need to investigate this
232 #DEBUG_TOOLBAR_PANELS += (
231 #DEBUG_TOOLBAR_PANELS += (
233 # 'debug_toolbar.panels.profiling.ProfilingDebugPanel',
232 # 'debug_toolbar.panels.profiling.ProfilingDebugPanel',
234 #)
233 #)
General Comments 0
You need to be logged in to leave comments. Login now