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