##// END OF EJS Templates
Enlarged title field
neko259 -
r612:7722a7e6 default
parent child Browse files
Show More
@@ -0,0 +1,90 b''
1 # -*- coding: utf-8 -*-
2 from south.utils import datetime_utils as datetime
3 from south.db import db
4 from south.v2 import SchemaMigration
5 from django.db import models
6
7
8 class Migration(SchemaMigration):
9
10 def forwards(self, orm):
11
12 # Changing field 'Post.title'
13 db.alter_column(u'boards_post', 'title', self.gf('django.db.models.fields.CharField')(max_length=200))
14 # Adding index on 'Tag', fields ['name']
15 db.create_index(u'boards_tag', ['name'])
16
17
18 def backwards(self, orm):
19 # Removing index on 'Tag', fields ['name']
20 db.delete_index(u'boards_tag', ['name'])
21
22
23 # Changing field 'Post.title'
24 db.alter_column(u'boards_post', 'title', self.gf('django.db.models.fields.CharField')(max_length=50))
25
26 models = {
27 'boards.ban': {
28 'Meta': {'object_name': 'Ban'},
29 'can_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
30 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
31 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
32 'reason': ('django.db.models.fields.CharField', [], {'default': "'Auto'", 'max_length': '200'})
33 },
34 'boards.post': {
35 'Meta': {'object_name': 'Post'},
36 '_text_rendered': ('django.db.models.fields.TextField', [], {}),
37 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
38 'image': ('boards.thumbs.ImageWithThumbsField', [], {'max_length': '100', 'blank': 'True'}),
39 'image_hash': ('django.db.models.fields.CharField', [], {'max_length': '36'}),
40 'image_height': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
41 'image_pre_height': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
42 'image_pre_width': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
43 'image_width': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
44 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}),
45 'poster_ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
46 'poster_user_agent': ('django.db.models.fields.TextField', [], {}),
47 'pub_time': ('django.db.models.fields.DateTimeField', [], {}),
48 'referenced_posts': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'rfp+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
49 'text': ('markupfield.fields.MarkupField', [], {'rendered_field': 'True'}),
50 'text_markup_type': ('django.db.models.fields.CharField', [], {'default': "'markdown'", 'max_length': '30'}),
51 'thread': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.Post']", 'null': 'True'}),
52 'thread_new': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.Thread']", 'null': 'True'}),
53 'title': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
54 'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.User']", 'null': 'True'})
55 },
56 'boards.setting': {
57 'Meta': {'object_name': 'Setting'},
58 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
59 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
60 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['boards.User']"}),
61 'value': ('django.db.models.fields.CharField', [], {'max_length': '50'})
62 },
63 'boards.tag': {
64 'Meta': {'object_name': 'Tag'},
65 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
66 'linked': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['boards.Tag']", 'null': 'True', 'blank': 'True'}),
67 'name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'db_index': 'True'}),
68 'threads': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'tag+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Thread']"})
69 },
70 'boards.thread': {
71 'Meta': {'object_name': 'Thread'},
72 'archived': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
73 'bump_time': ('django.db.models.fields.DateTimeField', [], {}),
74 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
75 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}),
76 'replies': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'tre+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
77 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['boards.Tag']", 'symmetrical': 'False'})
78 },
79 'boards.user': {
80 'Meta': {'object_name': 'User'},
81 'fav_tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['boards.Tag']", 'null': 'True', 'blank': 'True'}),
82 'fav_threads': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
83 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
84 'rank': ('django.db.models.fields.IntegerField', [], {}),
85 'registration_time': ('django.db.models.fields.DateTimeField', [], {}),
86 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '50'})
87 }
88 }
89
90 complete_apps = ['boards'] No newline at end of file
@@ -1,431 +1,429 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 os
3 import os
4 from random import random
4 from random import random
5 import time
5 import time
6 import math
6 import math
7 import re
7 import re
8 import hashlib
8 import hashlib
9
9
10 from django.core.cache import cache
10 from django.core.cache import cache
11 from django.core.paginator import Paginator
11 from django.core.paginator import Paginator
12 from django.core.urlresolvers import reverse
12 from django.core.urlresolvers import reverse
13
13
14 from django.db import models, transaction
14 from django.db import models, transaction
15 from django.http import Http404
15 from django.http import Http404
16 from django.utils import timezone
16 from django.utils import timezone
17 from markupfield.fields import MarkupField
17 from markupfield.fields import MarkupField
18
18
19 from neboard import settings
19 from neboard import settings
20 from boards import thumbs
20 from boards import thumbs
21
21
22 MAX_TITLE_LENGTH = 50
23
24 APP_LABEL_BOARDS = 'boards'
22 APP_LABEL_BOARDS = 'boards'
25
23
26 CACHE_KEY_PPD = 'ppd'
24 CACHE_KEY_PPD = 'ppd'
27 CACHE_KEY_POST_URL = 'post_url'
25 CACHE_KEY_POST_URL = 'post_url'
28 CACHE_KEY_OPENING_POST = 'opening_post'
26 CACHE_KEY_OPENING_POST = 'opening_post'
29
27
30 POSTS_PER_DAY_RANGE = range(7)
28 POSTS_PER_DAY_RANGE = range(7)
31
29
32 BAN_REASON_AUTO = 'Auto'
30 BAN_REASON_AUTO = 'Auto'
33
31
34 IMAGE_THUMB_SIZE = (200, 150)
32 IMAGE_THUMB_SIZE = (200, 150)
35
33
36 TITLE_MAX_LENGTH = 50
34 TITLE_MAX_LENGTH = 200
37
35
38 DEFAULT_MARKUP_TYPE = 'markdown'
36 DEFAULT_MARKUP_TYPE = 'markdown'
39
37
40 NO_PARENT = -1
38 NO_PARENT = -1
41 NO_IP = '0.0.0.0'
39 NO_IP = '0.0.0.0'
42 UNKNOWN_UA = ''
40 UNKNOWN_UA = ''
43 ALL_PAGES = -1
41 ALL_PAGES = -1
44 IMAGES_DIRECTORY = 'images/'
42 IMAGES_DIRECTORY = 'images/'
45 FILE_EXTENSION_DELIMITER = '.'
43 FILE_EXTENSION_DELIMITER = '.'
46
44
47 SETTING_MODERATE = "moderate"
45 SETTING_MODERATE = "moderate"
48
46
49 REGEX_REPLY = re.compile('>>(\d+)')
47 REGEX_REPLY = re.compile('>>(\d+)')
50
48
51
49
52 class PostManager(models.Manager):
50 class PostManager(models.Manager):
53
51
54 def create_post(self, title, text, image=None, thread=None,
52 def create_post(self, title, text, image=None, thread=None,
55 ip=NO_IP, tags=None, user=None):
53 ip=NO_IP, tags=None, user=None):
56 """
54 """
57 Create new post
55 Create new post
58 """
56 """
59
57
60 posting_time = timezone.now()
58 posting_time = timezone.now()
61 if not thread:
59 if not thread:
62 thread = Thread.objects.create(bump_time=posting_time,
60 thread = Thread.objects.create(bump_time=posting_time,
63 last_edit_time=posting_time)
61 last_edit_time=posting_time)
64 else:
62 else:
65 thread.bump()
63 thread.bump()
66 thread.last_edit_time = posting_time
64 thread.last_edit_time = posting_time
67 thread.save()
65 thread.save()
68
66
69 post = self.create(title=title,
67 post = self.create(title=title,
70 text=text,
68 text=text,
71 pub_time=posting_time,
69 pub_time=posting_time,
72 thread_new=thread,
70 thread_new=thread,
73 image=image,
71 image=image,
74 poster_ip=ip,
72 poster_ip=ip,
75 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
73 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
76 # last!
74 # last!
77 last_edit_time=posting_time,
75 last_edit_time=posting_time,
78 user=user)
76 user=user)
79
77
80 thread.replies.add(post)
78 thread.replies.add(post)
81 if tags:
79 if tags:
82 linked_tags = []
80 linked_tags = []
83 for tag in tags:
81 for tag in tags:
84 tag_linked_tags = tag.get_linked_tags()
82 tag_linked_tags = tag.get_linked_tags()
85 if len(tag_linked_tags) > 0:
83 if len(tag_linked_tags) > 0:
86 linked_tags.extend(tag_linked_tags)
84 linked_tags.extend(tag_linked_tags)
87
85
88 tags.extend(linked_tags)
86 tags.extend(linked_tags)
89 map(thread.add_tag, tags)
87 map(thread.add_tag, tags)
90
88
91 # TODO Delete old threads only if this is a new thread
89 # TODO Delete old threads only if this is a new thread
92 self._delete_old_threads()
90 self._delete_old_threads()
93 self.connect_replies(post)
91 self.connect_replies(post)
94
92
95 return post
93 return post
96
94
97 def delete_post(self, post):
95 def delete_post(self, post):
98 """
96 """
99 Delete post and update or delete its thread
97 Delete post and update or delete its thread
100 """
98 """
101
99
102 thread = post.thread_new
100 thread = post.thread_new
103
101
104 if post.is_opening():
102 if post.is_opening():
105 thread.delete_with_posts()
103 thread.delete_with_posts()
106 else:
104 else:
107 thread.last_edit_time = timezone.now()
105 thread.last_edit_time = timezone.now()
108 thread.save()
106 thread.save()
109
107
110 post.delete()
108 post.delete()
111
109
112 def delete_posts_by_ip(self, ip):
110 def delete_posts_by_ip(self, ip):
113 """
111 """
114 Delete all posts of the author with same IP
112 Delete all posts of the author with same IP
115 """
113 """
116
114
117 posts = self.filter(poster_ip=ip)
115 posts = self.filter(poster_ip=ip)
118 map(self.delete_post, posts)
116 map(self.delete_post, posts)
119
117
120 # TODO Move this method to thread manager
118 # TODO Move this method to thread manager
121 def _delete_old_threads(self):
119 def _delete_old_threads(self):
122 """
120 """
123 Preserves maximum thread count. If there are too many threads,
121 Preserves maximum thread count. If there are too many threads,
124 archive the old ones.
122 archive the old ones.
125 """
123 """
126
124
127 threads = Thread.objects.filter(archived=False).order_by('-bump_time')
125 threads = Thread.objects.filter(archived=False).order_by('-bump_time')
128 thread_count = threads.count()
126 thread_count = threads.count()
129
127
130 if thread_count > settings.MAX_THREAD_COUNT:
128 if thread_count > settings.MAX_THREAD_COUNT:
131 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
129 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
132 old_threads = threads[thread_count - num_threads_to_delete:]
130 old_threads = threads[thread_count - num_threads_to_delete:]
133
131
134 for thread in old_threads:
132 for thread in old_threads:
135 thread.archived = True
133 thread.archived = True
136 thread.last_edit_time = timezone.now()
134 thread.last_edit_time = timezone.now()
137 thread.save()
135 thread.save()
138
136
139 def connect_replies(self, post):
137 def connect_replies(self, post):
140 """
138 """
141 Connect replies to a post to show them as a reflink map
139 Connect replies to a post to show them as a reflink map
142 """
140 """
143
141
144 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
142 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
145 post_id = reply_number.group(1)
143 post_id = reply_number.group(1)
146 ref_post = self.filter(id=post_id)
144 ref_post = self.filter(id=post_id)
147 if ref_post.count() > 0:
145 if ref_post.count() > 0:
148 referenced_post = ref_post[0]
146 referenced_post = ref_post[0]
149 referenced_post.referenced_posts.add(post)
147 referenced_post.referenced_posts.add(post)
150 referenced_post.last_edit_time = post.pub_time
148 referenced_post.last_edit_time = post.pub_time
151 referenced_post.save()
149 referenced_post.save()
152
150
153 referenced_thread = referenced_post.thread_new
151 referenced_thread = referenced_post.thread_new
154 referenced_thread.last_edit_time = post.pub_time
152 referenced_thread.last_edit_time = post.pub_time
155 referenced_thread.save()
153 referenced_thread.save()
156
154
157 def get_posts_per_day(self):
155 def get_posts_per_day(self):
158 """
156 """
159 Get average count of posts per day for the last 7 days
157 Get average count of posts per day for the last 7 days
160 """
158 """
161
159
162 today = date.today()
160 today = date.today()
163 ppd = cache.get(CACHE_KEY_PPD + str(today))
161 ppd = cache.get(CACHE_KEY_PPD + str(today))
164 if ppd:
162 if ppd:
165 return ppd
163 return ppd
166
164
167 posts_per_days = []
165 posts_per_days = []
168 for i in POSTS_PER_DAY_RANGE:
166 for i in POSTS_PER_DAY_RANGE:
169 day_end = today - timedelta(i + 1)
167 day_end = today - timedelta(i + 1)
170 day_start = today - timedelta(i + 2)
168 day_start = today - timedelta(i + 2)
171
169
172 day_time_start = timezone.make_aware(datetime.combine(
170 day_time_start = timezone.make_aware(datetime.combine(
173 day_start, dtime()), timezone.get_current_timezone())
171 day_start, dtime()), timezone.get_current_timezone())
174 day_time_end = timezone.make_aware(datetime.combine(
172 day_time_end = timezone.make_aware(datetime.combine(
175 day_end, dtime()), timezone.get_current_timezone())
173 day_end, dtime()), timezone.get_current_timezone())
176
174
177 posts_per_days.append(float(self.filter(
175 posts_per_days.append(float(self.filter(
178 pub_time__lte=day_time_end,
176 pub_time__lte=day_time_end,
179 pub_time__gte=day_time_start).count()))
177 pub_time__gte=day_time_start).count()))
180
178
181 ppd = (sum(posts_per_day for posts_per_day in posts_per_days) /
179 ppd = (sum(posts_per_day for posts_per_day in posts_per_days) /
182 len(posts_per_days))
180 len(posts_per_days))
183 cache.set(CACHE_KEY_PPD + str(today), ppd)
181 cache.set(CACHE_KEY_PPD + str(today), ppd)
184 return ppd
182 return ppd
185
183
186
184
187 class Post(models.Model):
185 class Post(models.Model):
188 """A post is a message."""
186 """A post is a message."""
189
187
190 objects = PostManager()
188 objects = PostManager()
191
189
192 class Meta:
190 class Meta:
193 app_label = APP_LABEL_BOARDS
191 app_label = APP_LABEL_BOARDS
194
192
195 # TODO Save original file name to some field
193 # TODO Save original file name to some field
196 def _update_image_filename(self, filename):
194 def _update_image_filename(self, filename):
197 """Get unique image filename"""
195 """Get unique image filename"""
198
196
199 path = IMAGES_DIRECTORY
197 path = IMAGES_DIRECTORY
200 new_name = str(int(time.mktime(time.gmtime())))
198 new_name = str(int(time.mktime(time.gmtime())))
201 new_name += str(int(random() * 1000))
199 new_name += str(int(random() * 1000))
202 new_name += FILE_EXTENSION_DELIMITER
200 new_name += FILE_EXTENSION_DELIMITER
203 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
201 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
204
202
205 return os.path.join(path, new_name)
203 return os.path.join(path, new_name)
206
204
207 title = models.CharField(max_length=TITLE_MAX_LENGTH)
205 title = models.CharField(max_length=TITLE_MAX_LENGTH)
208 pub_time = models.DateTimeField()
206 pub_time = models.DateTimeField()
209 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
207 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
210 escape_html=False)
208 escape_html=False)
211
209
212 image_width = models.IntegerField(default=0)
210 image_width = models.IntegerField(default=0)
213 image_height = models.IntegerField(default=0)
211 image_height = models.IntegerField(default=0)
214
212
215 image_pre_width = models.IntegerField(default=0)
213 image_pre_width = models.IntegerField(default=0)
216 image_pre_height = models.IntegerField(default=0)
214 image_pre_height = models.IntegerField(default=0)
217
215
218 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
216 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
219 blank=True, sizes=(IMAGE_THUMB_SIZE,),
217 blank=True, sizes=(IMAGE_THUMB_SIZE,),
220 width_field='image_width',
218 width_field='image_width',
221 height_field='image_height',
219 height_field='image_height',
222 preview_width_field='image_pre_width',
220 preview_width_field='image_pre_width',
223 preview_height_field='image_pre_height')
221 preview_height_field='image_pre_height')
224 image_hash = models.CharField(max_length=36)
222 image_hash = models.CharField(max_length=36)
225
223
226 poster_ip = models.GenericIPAddressField()
224 poster_ip = models.GenericIPAddressField()
227 poster_user_agent = models.TextField()
225 poster_user_agent = models.TextField()
228
226
229 thread = models.ForeignKey('Post', null=True, default=None)
227 thread = models.ForeignKey('Post', null=True, default=None)
230 thread_new = models.ForeignKey('Thread', null=True, default=None)
228 thread_new = models.ForeignKey('Thread', null=True, default=None)
231 last_edit_time = models.DateTimeField()
229 last_edit_time = models.DateTimeField()
232 user = models.ForeignKey('User', null=True, default=None)
230 user = models.ForeignKey('User', null=True, default=None)
233
231
234 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
232 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
235 null=True,
233 null=True,
236 blank=True, related_name='rfp+')
234 blank=True, related_name='rfp+')
237
235
238 def __unicode__(self):
236 def __unicode__(self):
239 return '#' + str(self.id) + ' ' + self.title + ' (' + \
237 return '#' + str(self.id) + ' ' + self.title + ' (' + \
240 self.text.raw[:50] + ')'
238 self.text.raw[:50] + ')'
241
239
242 def get_title(self):
240 def get_title(self):
243 title = self.title
241 title = self.title
244 if len(title) == 0:
242 if len(title) == 0:
245 title = self.text.rendered
243 title = self.text.rendered
246
244
247 return title
245 return title
248
246
249 def get_sorted_referenced_posts(self):
247 def get_sorted_referenced_posts(self):
250 return self.referenced_posts.order_by('id')
248 return self.referenced_posts.order_by('id')
251
249
252 def is_referenced(self):
250 def is_referenced(self):
253 return self.referenced_posts.all().exists()
251 return self.referenced_posts.all().exists()
254
252
255 def is_opening(self):
253 def is_opening(self):
256 return self.thread_new.get_opening_post() == self
254 return self.thread_new.get_opening_post() == self
257
255
258 def save(self, *args, **kwargs):
256 def save(self, *args, **kwargs):
259 """
257 """
260 Save the model and compute the image hash
258 Save the model and compute the image hash
261 """
259 """
262
260
263 if not self.pk and self.image:
261 if not self.pk and self.image:
264 md5 = hashlib.md5()
262 md5 = hashlib.md5()
265 for chunk in self.image.chunks():
263 for chunk in self.image.chunks():
266 md5.update(chunk)
264 md5.update(chunk)
267 self.image_hash = md5.hexdigest()
265 self.image_hash = md5.hexdigest()
268 super(Post, self).save(*args, **kwargs)
266 super(Post, self).save(*args, **kwargs)
269
267
270 @transaction.atomic
268 @transaction.atomic
271 def add_tag(self, tag):
269 def add_tag(self, tag):
272 edit_time = timezone.now()
270 edit_time = timezone.now()
273
271
274 thread = self.thread_new
272 thread = self.thread_new
275 thread.add_tag(tag)
273 thread.add_tag(tag)
276 self.last_edit_time = edit_time
274 self.last_edit_time = edit_time
277 self.save()
275 self.save()
278
276
279 thread.last_edit_time = edit_time
277 thread.last_edit_time = edit_time
280 thread.save()
278 thread.save()
281
279
282 @transaction.atomic
280 @transaction.atomic
283 def remove_tag(self, tag):
281 def remove_tag(self, tag):
284 edit_time = timezone.now()
282 edit_time = timezone.now()
285
283
286 thread = self.thread_new
284 thread = self.thread_new
287 thread.remove_tag(tag)
285 thread.remove_tag(tag)
288 self.last_edit_time = edit_time
286 self.last_edit_time = edit_time
289 self.save()
287 self.save()
290
288
291 thread.last_edit_time = edit_time
289 thread.last_edit_time = edit_time
292 thread.save()
290 thread.save()
293
291
294 def get_url(self):
292 def get_url(self):
295 """
293 """
296 Get full url to this post
294 Get full url to this post
297 """
295 """
298
296
299 cache_key = CACHE_KEY_POST_URL + str(self.id)
297 cache_key = CACHE_KEY_POST_URL + str(self.id)
300 link = cache.get(cache_key)
298 link = cache.get(cache_key)
301
299
302 if not link:
300 if not link:
303 opening_post = self.thread_new.get_opening_post()
301 opening_post = self.thread_new.get_opening_post()
304 if self != opening_post:
302 if self != opening_post:
305 link = reverse('thread', kwargs={
303 link = reverse('thread', kwargs={
306 'post_id': opening_post.id}) + '#' + str(self.id)
304 'post_id': opening_post.id}) + '#' + str(self.id)
307 else:
305 else:
308 link = reverse('thread', kwargs={'post_id': self.id})
306 link = reverse('thread', kwargs={'post_id': self.id})
309
307
310 cache.set(cache_key, link)
308 cache.set(cache_key, link)
311
309
312 return link
310 return link
313
311
314
312
315 class Thread(models.Model):
313 class Thread(models.Model):
316
314
317 class Meta:
315 class Meta:
318 app_label = APP_LABEL_BOARDS
316 app_label = APP_LABEL_BOARDS
319
317
320 tags = models.ManyToManyField('Tag')
318 tags = models.ManyToManyField('Tag')
321 bump_time = models.DateTimeField()
319 bump_time = models.DateTimeField()
322 last_edit_time = models.DateTimeField()
320 last_edit_time = models.DateTimeField()
323 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
321 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
324 blank=True, related_name='tre+')
322 blank=True, related_name='tre+')
325 archived = models.BooleanField(default=False)
323 archived = models.BooleanField(default=False)
326
324
327 def get_tags(self):
325 def get_tags(self):
328 """
326 """
329 Get a sorted tag list
327 Get a sorted tag list
330 """
328 """
331
329
332 return self.tags.order_by('name')
330 return self.tags.order_by('name')
333
331
334 def bump(self):
332 def bump(self):
335 """
333 """
336 Bump (move to up) thread
334 Bump (move to up) thread
337 """
335 """
338
336
339 if self.can_bump():
337 if self.can_bump():
340 self.bump_time = timezone.now()
338 self.bump_time = timezone.now()
341
339
342 def get_reply_count(self):
340 def get_reply_count(self):
343 return self.replies.count()
341 return self.replies.count()
344
342
345 def get_images_count(self):
343 def get_images_count(self):
346 return self.replies.filter(image_width__gt=0).count()
344 return self.replies.filter(image_width__gt=0).count()
347
345
348 def can_bump(self):
346 def can_bump(self):
349 """
347 """
350 Check if the thread can be bumped by replying
348 Check if the thread can be bumped by replying
351 """
349 """
352
350
353 if self.archived:
351 if self.archived:
354 return False
352 return False
355
353
356 post_count = self.get_reply_count()
354 post_count = self.get_reply_count()
357
355
358 return post_count < settings.MAX_POSTS_PER_THREAD
356 return post_count < settings.MAX_POSTS_PER_THREAD
359
357
360 def delete_with_posts(self):
358 def delete_with_posts(self):
361 """
359 """
362 Completely delete thread and all its posts
360 Completely delete thread and all its posts
363 """
361 """
364
362
365 if self.replies.count() > 0:
363 if self.replies.count() > 0:
366 self.replies.all().delete()
364 self.replies.all().delete()
367
365
368 self.delete()
366 self.delete()
369
367
370 def get_last_replies(self):
368 def get_last_replies(self):
371 """
369 """
372 Get last replies, not including opening post
370 Get last replies, not including opening post
373 """
371 """
374
372
375 if settings.LAST_REPLIES_COUNT > 0:
373 if settings.LAST_REPLIES_COUNT > 0:
376 reply_count = self.get_reply_count()
374 reply_count = self.get_reply_count()
377
375
378 if reply_count > 0:
376 if reply_count > 0:
379 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
377 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
380 reply_count - 1)
378 reply_count - 1)
381 last_replies = self.replies.all().order_by(
379 last_replies = self.replies.all().order_by(
382 'pub_time')[reply_count - reply_count_to_show:]
380 'pub_time')[reply_count - reply_count_to_show:]
383
381
384 return last_replies
382 return last_replies
385
383
386 def get_skipped_replies_count(self):
384 def get_skipped_replies_count(self):
387 last_replies = self.get_last_replies()
385 last_replies = self.get_last_replies()
388 return self.get_reply_count() - len(last_replies) - 1
386 return self.get_reply_count() - len(last_replies) - 1
389
387
390 def get_replies(self):
388 def get_replies(self):
391 """
389 """
392 Get sorted thread posts
390 Get sorted thread posts
393 """
391 """
394
392
395 return self.replies.all().order_by('pub_time')
393 return self.replies.all().order_by('pub_time')
396
394
397 def add_tag(self, tag):
395 def add_tag(self, tag):
398 """
396 """
399 Connect thread to a tag and tag to a thread
397 Connect thread to a tag and tag to a thread
400 """
398 """
401
399
402 self.tags.add(tag)
400 self.tags.add(tag)
403 tag.threads.add(self)
401 tag.threads.add(self)
404
402
405 def remove_tag(self, tag):
403 def remove_tag(self, tag):
406 self.tags.remove(tag)
404 self.tags.remove(tag)
407 tag.threads.remove(self)
405 tag.threads.remove(self)
408
406
409 def get_opening_post(self):
407 def get_opening_post(self):
410 """
408 """
411 Get first post of the thread
409 Get first post of the thread
412 """
410 """
413
411
414 # cache_key = CACHE_KEY_OPENING_POST + str(self.id)
412 # cache_key = CACHE_KEY_OPENING_POST + str(self.id)
415 # opening_post = cache.get(cache_key)
413 # opening_post = cache.get(cache_key)
416 # if not opening_post:
414 # if not opening_post:
417 opening_post = self.get_replies()[0]
415 opening_post = self.get_replies()[0]
418 # cache.set(cache_key, opening_post)
416 # cache.set(cache_key, opening_post)
419
417
420 return opening_post
418 return opening_post
421
419
422 def __unicode__(self):
420 def __unicode__(self):
423 return str(self.id)
421 return str(self.id)
424
422
425 def get_pub_time(self):
423 def get_pub_time(self):
426 """
424 """
427 Thread does not have its own pub time, so we need to get it from
425 Thread does not have its own pub time, so we need to get it from
428 the opening post
426 the opening post
429 """
427 """
430
428
431 return self.get_opening_post().pub_time
429 return self.get_opening_post().pub_time
General Comments 0
You need to be logged in to leave comments. Login now