##// END OF EJS Templates
Added quotes to the title
neko259 -
r851:cb2cfb7f default
parent child Browse files
Show More
@@ -1,346 +1,346 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:
51 if not tags:
52 tags = []
52 tags = []
53
53
54 posting_time = timezone.now()
54 posting_time = timezone.now()
55 if not thread:
55 if not thread:
56 thread = Thread.objects.create(bump_time=posting_time,
56 thread = Thread.objects.create(bump_time=posting_time,
57 last_edit_time=posting_time)
57 last_edit_time=posting_time)
58 new_thread = True
58 new_thread = True
59 else:
59 else:
60 thread.bump()
60 thread.bump()
61 thread.last_edit_time = posting_time
61 thread.last_edit_time = posting_time
62 thread.save()
62 thread.save()
63 new_thread = False
63 new_thread = False
64
64
65 post = self.create(title=title,
65 post = self.create(title=title,
66 text=text,
66 text=text,
67 pub_time=posting_time,
67 pub_time=posting_time,
68 thread_new=thread,
68 thread_new=thread,
69 poster_ip=ip,
69 poster_ip=ip,
70 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
70 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
71 # last!
71 # last!
72 last_edit_time=posting_time)
72 last_edit_time=posting_time)
73
73
74 logger.info('Created post #%d with title %s' % (post.id,
74 logger.info('Created post #%d with title "%s"' % (post.id,
75 post.title))
75 post.title))
76
76
77 if image:
77 if image:
78 post_image = PostImage.objects.create(image=image)
78 post_image = PostImage.objects.create(image=image)
79 post.images.add(post_image)
79 post.images.add(post_image)
80 logger.info('Created image #%d for post #%d' % (post_image.id,
80 logger.info('Created image #%d for post #%d' % (post_image.id,
81 post.id))
81 post.id))
82
82
83 thread.replies.add(post)
83 thread.replies.add(post)
84 list(map(thread.add_tag, tags))
84 list(map(thread.add_tag, tags))
85
85
86 if new_thread:
86 if new_thread:
87 Thread.objects.process_oldest_threads()
87 Thread.objects.process_oldest_threads()
88 self.connect_replies(post)
88 self.connect_replies(post)
89
89
90 return post
90 return post
91
91
92 def delete_post(self, post):
92 def delete_post(self, post):
93 """
93 """
94 Deletes post and update or delete its thread
94 Deletes post and update or delete its thread
95 """
95 """
96
96
97 post_id = post.id
97 post_id = post.id
98
98
99 thread = post.get_thread()
99 thread = post.get_thread()
100
100
101 if post.is_opening():
101 if post.is_opening():
102 thread.delete()
102 thread.delete()
103 else:
103 else:
104 thread.last_edit_time = timezone.now()
104 thread.last_edit_time = timezone.now()
105 thread.save()
105 thread.save()
106
106
107 post.delete()
107 post.delete()
108
108
109 logger.info('Deleted post #%d (%s)' % (post_id, post.get_title()))
109 logger.info('Deleted post #%d (%s)' % (post_id, post.get_title()))
110
110
111 def delete_posts_by_ip(self, ip):
111 def delete_posts_by_ip(self, ip):
112 """
112 """
113 Deletes all posts of the author with same IP
113 Deletes all posts of the author with same IP
114 """
114 """
115
115
116 posts = self.filter(poster_ip=ip)
116 posts = self.filter(poster_ip=ip)
117 for post in posts:
117 for post in posts:
118 self.delete_post(post)
118 self.delete_post(post)
119
119
120 def connect_replies(self, post):
120 def connect_replies(self, post):
121 """
121 """
122 Connects replies to a post to show them as a reflink map
122 Connects replies to a post to show them as a reflink map
123 """
123 """
124
124
125 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
125 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
126 post_id = reply_number.group(1)
126 post_id = reply_number.group(1)
127 ref_post = self.filter(id=post_id)
127 ref_post = self.filter(id=post_id)
128 if ref_post.count() > 0:
128 if ref_post.count() > 0:
129 referenced_post = ref_post[0]
129 referenced_post = ref_post[0]
130 referenced_post.referenced_posts.add(post)
130 referenced_post.referenced_posts.add(post)
131 referenced_post.last_edit_time = post.pub_time
131 referenced_post.last_edit_time = post.pub_time
132 referenced_post.build_refmap()
132 referenced_post.build_refmap()
133 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
133 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
134
134
135 referenced_thread = referenced_post.get_thread()
135 referenced_thread = referenced_post.get_thread()
136 referenced_thread.last_edit_time = post.pub_time
136 referenced_thread.last_edit_time = post.pub_time
137 referenced_thread.save(update_fields=['last_edit_time'])
137 referenced_thread.save(update_fields=['last_edit_time'])
138
138
139 def get_posts_per_day(self):
139 def get_posts_per_day(self):
140 """
140 """
141 Gets average count of posts per day for the last 7 days
141 Gets average count of posts per day for the last 7 days
142 """
142 """
143
143
144 day_end = date.today()
144 day_end = date.today()
145 day_start = day_end - timedelta(POSTS_PER_DAY_RANGE)
145 day_start = day_end - timedelta(POSTS_PER_DAY_RANGE)
146
146
147 cache_key = CACHE_KEY_PPD + str(day_end)
147 cache_key = CACHE_KEY_PPD + str(day_end)
148 ppd = cache.get(cache_key)
148 ppd = cache.get(cache_key)
149 if ppd:
149 if ppd:
150 return ppd
150 return ppd
151
151
152 day_time_start = timezone.make_aware(datetime.combine(
152 day_time_start = timezone.make_aware(datetime.combine(
153 day_start, dtime()), timezone.get_current_timezone())
153 day_start, dtime()), timezone.get_current_timezone())
154 day_time_end = timezone.make_aware(datetime.combine(
154 day_time_end = timezone.make_aware(datetime.combine(
155 day_end, dtime()), timezone.get_current_timezone())
155 day_end, dtime()), timezone.get_current_timezone())
156
156
157 posts_per_period = float(self.filter(
157 posts_per_period = float(self.filter(
158 pub_time__lte=day_time_end,
158 pub_time__lte=day_time_end,
159 pub_time__gte=day_time_start).count())
159 pub_time__gte=day_time_start).count())
160
160
161 ppd = posts_per_period / POSTS_PER_DAY_RANGE
161 ppd = posts_per_period / POSTS_PER_DAY_RANGE
162
162
163 cache.set(cache_key, ppd)
163 cache.set(cache_key, ppd)
164 return ppd
164 return ppd
165
165
166
166
167 class Post(models.Model, Viewable):
167 class Post(models.Model, Viewable):
168 """A post is a message."""
168 """A post is a message."""
169
169
170 objects = PostManager()
170 objects = PostManager()
171
171
172 class Meta:
172 class Meta:
173 app_label = APP_LABEL_BOARDS
173 app_label = APP_LABEL_BOARDS
174 ordering = ('id',)
174 ordering = ('id',)
175
175
176 title = models.CharField(max_length=TITLE_MAX_LENGTH)
176 title = models.CharField(max_length=TITLE_MAX_LENGTH)
177 pub_time = models.DateTimeField()
177 pub_time = models.DateTimeField()
178 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
178 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
179 escape_html=False)
179 escape_html=False)
180
180
181 images = models.ManyToManyField(PostImage, null=True, blank=True,
181 images = models.ManyToManyField(PostImage, null=True, blank=True,
182 related_name='ip+', db_index=True)
182 related_name='ip+', db_index=True)
183
183
184 poster_ip = models.GenericIPAddressField()
184 poster_ip = models.GenericIPAddressField()
185 poster_user_agent = models.TextField()
185 poster_user_agent = models.TextField()
186
186
187 thread_new = models.ForeignKey('Thread', null=True, default=None,
187 thread_new = models.ForeignKey('Thread', null=True, default=None,
188 db_index=True)
188 db_index=True)
189 last_edit_time = models.DateTimeField()
189 last_edit_time = models.DateTimeField()
190
190
191 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
191 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
192 null=True,
192 null=True,
193 blank=True, related_name='rfp+',
193 blank=True, related_name='rfp+',
194 db_index=True)
194 db_index=True)
195 refmap = models.TextField(null=True, blank=True)
195 refmap = models.TextField(null=True, blank=True)
196
196
197 def __unicode__(self):
197 def __unicode__(self):
198 return '#' + str(self.id) + ' ' + self.title + ' (' + \
198 return '#' + str(self.id) + ' ' + self.title + ' (' + \
199 self.text.raw[:50] + ')'
199 self.text.raw[:50] + ')'
200
200
201 def get_title(self):
201 def get_title(self):
202 """
202 """
203 Gets original post title or part of its text.
203 Gets original post title or part of its text.
204 """
204 """
205
205
206 title = self.title
206 title = self.title
207 if not title:
207 if not title:
208 title = self.text.rendered
208 title = self.text.rendered
209
209
210 return title
210 return title
211
211
212 def build_refmap(self):
212 def build_refmap(self):
213 """
213 """
214 Builds a replies map string from replies list. This is a cache to stop
214 Builds a replies map string from replies list. This is a cache to stop
215 the server from recalculating the map on every post show.
215 the server from recalculating the map on every post show.
216 """
216 """
217 map_string = ''
217 map_string = ''
218
218
219 first = True
219 first = True
220 for refpost in self.referenced_posts.all():
220 for refpost in self.referenced_posts.all():
221 if not first:
221 if not first:
222 map_string += ', '
222 map_string += ', '
223 map_string += '<a href="%s">&gt;&gt;%s</a>' % (refpost.get_url(),
223 map_string += '<a href="%s">&gt;&gt;%s</a>' % (refpost.get_url(),
224 refpost.id)
224 refpost.id)
225 first = False
225 first = False
226
226
227 self.refmap = map_string
227 self.refmap = map_string
228
228
229 def get_sorted_referenced_posts(self):
229 def get_sorted_referenced_posts(self):
230 return self.refmap
230 return self.refmap
231
231
232 def is_referenced(self):
232 def is_referenced(self):
233 return len(self.refmap) > 0
233 return len(self.refmap) > 0
234
234
235 def is_opening(self):
235 def is_opening(self):
236 """
236 """
237 Checks if this is an opening post or just a reply.
237 Checks if this is an opening post or just a reply.
238 """
238 """
239
239
240 return self.get_thread().get_opening_post_id() == self.id
240 return self.get_thread().get_opening_post_id() == self.id
241
241
242 @transaction.atomic
242 @transaction.atomic
243 def add_tag(self, tag):
243 def add_tag(self, tag):
244 edit_time = timezone.now()
244 edit_time = timezone.now()
245
245
246 thread = self.get_thread()
246 thread = self.get_thread()
247 thread.add_tag(tag)
247 thread.add_tag(tag)
248 self.last_edit_time = edit_time
248 self.last_edit_time = edit_time
249 self.save(update_fields=['last_edit_time'])
249 self.save(update_fields=['last_edit_time'])
250
250
251 thread.last_edit_time = edit_time
251 thread.last_edit_time = edit_time
252 thread.save(update_fields=['last_edit_time'])
252 thread.save(update_fields=['last_edit_time'])
253
253
254 @transaction.atomic
254 @transaction.atomic
255 def remove_tag(self, tag):
255 def remove_tag(self, tag):
256 edit_time = timezone.now()
256 edit_time = timezone.now()
257
257
258 thread = self.get_thread()
258 thread = self.get_thread()
259 thread.remove_tag(tag)
259 thread.remove_tag(tag)
260 self.last_edit_time = edit_time
260 self.last_edit_time = edit_time
261 self.save(update_fields=['last_edit_time'])
261 self.save(update_fields=['last_edit_time'])
262
262
263 thread.last_edit_time = edit_time
263 thread.last_edit_time = edit_time
264 thread.save(update_fields=['last_edit_time'])
264 thread.save(update_fields=['last_edit_time'])
265
265
266 def get_url(self, thread=None):
266 def get_url(self, thread=None):
267 """
267 """
268 Gets full url to the post.
268 Gets full url to the post.
269 """
269 """
270
270
271 cache_key = CACHE_KEY_POST_URL + str(self.id)
271 cache_key = CACHE_KEY_POST_URL + str(self.id)
272 link = cache.get(cache_key)
272 link = cache.get(cache_key)
273
273
274 if not link:
274 if not link:
275 if not thread:
275 if not thread:
276 thread = self.get_thread()
276 thread = self.get_thread()
277
277
278 opening_id = thread.get_opening_post_id()
278 opening_id = thread.get_opening_post_id()
279
279
280 if self.id != opening_id:
280 if self.id != opening_id:
281 link = reverse('thread', kwargs={
281 link = reverse('thread', kwargs={
282 'post_id': opening_id}) + '#' + str(self.id)
282 'post_id': opening_id}) + '#' + str(self.id)
283 else:
283 else:
284 link = reverse('thread', kwargs={'post_id': self.id})
284 link = reverse('thread', kwargs={'post_id': self.id})
285
285
286 cache.set(cache_key, link)
286 cache.set(cache_key, link)
287
287
288 return link
288 return link
289
289
290 def get_thread(self):
290 def get_thread(self):
291 """
291 """
292 Gets post's thread.
292 Gets post's thread.
293 """
293 """
294
294
295 return self.thread_new
295 return self.thread_new
296
296
297 def get_referenced_posts(self):
297 def get_referenced_posts(self):
298 return self.referenced_posts.only('id', 'thread_new')
298 return self.referenced_posts.only('id', 'thread_new')
299
299
300 def get_text(self):
300 def get_text(self):
301 return self.text
301 return self.text
302
302
303 def get_view(self, moderator=False, need_open_link=False,
303 def get_view(self, moderator=False, need_open_link=False,
304 truncated=False, *args, **kwargs):
304 truncated=False, *args, **kwargs):
305 if 'is_opening' in kwargs:
305 if 'is_opening' in kwargs:
306 is_opening = kwargs['is_opening']
306 is_opening = kwargs['is_opening']
307 else:
307 else:
308 is_opening = self.is_opening()
308 is_opening = self.is_opening()
309
309
310 if 'thread' in kwargs:
310 if 'thread' in kwargs:
311 thread = kwargs['thread']
311 thread = kwargs['thread']
312 else:
312 else:
313 thread = self.get_thread()
313 thread = self.get_thread()
314
314
315 if 'can_bump' in kwargs:
315 if 'can_bump' in kwargs:
316 can_bump = kwargs['can_bump']
316 can_bump = kwargs['can_bump']
317 else:
317 else:
318 can_bump = thread.can_bump()
318 can_bump = thread.can_bump()
319
319
320 if is_opening:
320 if is_opening:
321 opening_post_id = self.id
321 opening_post_id = self.id
322 else:
322 else:
323 opening_post_id = thread.get_opening_post_id()
323 opening_post_id = thread.get_opening_post_id()
324
324
325 return render_to_string('boards/post.html', {
325 return render_to_string('boards/post.html', {
326 'post': self,
326 'post': self,
327 'moderator': moderator,
327 'moderator': moderator,
328 'is_opening': is_opening,
328 'is_opening': is_opening,
329 'thread': thread,
329 'thread': thread,
330 'bumpable': can_bump,
330 'bumpable': can_bump,
331 'need_open_link': need_open_link,
331 'need_open_link': need_open_link,
332 'truncated': truncated,
332 'truncated': truncated,
333 'opening_post_id': opening_post_id,
333 'opening_post_id': opening_post_id,
334 })
334 })
335
335
336 def get_first_image(self):
336 def get_first_image(self):
337 return self.images.earliest('id')
337 return self.images.earliest('id')
338
338
339 def delete(self, using=None):
339 def delete(self, using=None):
340 """
340 """
341 Deletes all post images and the post itself.
341 Deletes all post images and the post itself.
342 """
342 """
343
343
344 self.images.all().delete()
344 self.images.all().delete()
345
345
346 super(Post, self).delete(using)
346 super(Post, self).delete(using)
General Comments 0
You need to be logged in to leave comments. Login now