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