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