##// END OF EJS Templates
Added default value for image dimensions fields.
neko259 -
r129:68aca549 default
parent child Browse files
Show More
@@ -1,290 +1,290 b''
1 import os
1 import os
2 from random import random
2 from random import random
3 import re
3 import re
4 import time
4 import time
5 import math
5 import math
6
6
7 from django.db import models
7 from django.db import models
8 from django.http import Http404
8 from django.http import Http404
9 from django.utils import timezone
9 from django.utils import timezone
10 from markupfield.fields import MarkupField
10 from markupfield.fields import MarkupField
11 from threading import Thread
11 from threading import Thread
12
12
13 from neboard import settings
13 from neboard import settings
14 import thumbs
14 import thumbs
15
15
16 IMAGE_THUMB_SIZE = (200, 150)
16 IMAGE_THUMB_SIZE = (200, 150)
17
17
18 TITLE_MAX_LENGTH = 50
18 TITLE_MAX_LENGTH = 50
19
19
20 DEFAULT_MARKUP_TYPE = 'markdown'
20 DEFAULT_MARKUP_TYPE = 'markdown'
21
21
22 NO_PARENT = -1
22 NO_PARENT = -1
23 NO_IP = '0.0.0.0'
23 NO_IP = '0.0.0.0'
24 UNKNOWN_UA = ''
24 UNKNOWN_UA = ''
25 ALL_PAGES = -1
25 ALL_PAGES = -1
26 OPENING_POST_POPULARITY_WEIGHT = 2
26 OPENING_POST_POPULARITY_WEIGHT = 2
27 IMAGES_DIRECTORY = 'images/'
27 IMAGES_DIRECTORY = 'images/'
28 FILE_EXTENSION_DELIMITER = '.'
28 FILE_EXTENSION_DELIMITER = '.'
29
29
30
30
31 class PostManager(models.Manager):
31 class PostManager(models.Manager):
32 def create_post(self, title, text, image=None, parent_id=NO_PARENT,
32 def create_post(self, title, text, image=None, parent_id=NO_PARENT,
33 ip=NO_IP, tags=None):
33 ip=NO_IP, tags=None):
34 post = self.create(title=title,
34 post = self.create(title=title,
35 text=text,
35 text=text,
36 pub_time=timezone.now(),
36 pub_time=timezone.now(),
37 parent=parent_id,
37 parent=parent_id,
38 image=image,
38 image=image,
39 poster_ip=ip,
39 poster_ip=ip,
40 poster_user_agent=UNKNOWN_UA,
40 poster_user_agent=UNKNOWN_UA,
41 last_edit_time=timezone.now())
41 last_edit_time=timezone.now())
42
42
43 if tags:
43 if tags:
44 map(post.tags.add, tags)
44 map(post.tags.add, tags)
45
45
46 if parent_id != NO_PARENT:
46 if parent_id != NO_PARENT:
47 self._bump_thread(parent_id)
47 self._bump_thread(parent_id)
48 else:
48 else:
49 self._delete_old_threads()
49 self._delete_old_threads()
50
50
51 return post
51 return post
52
52
53 def delete_post(self, post):
53 def delete_post(self, post):
54 children = self.filter(parent=post.id)
54 children = self.filter(parent=post.id)
55 for child in children:
55 for child in children:
56 self.delete_post(child)
56 self.delete_post(child)
57 post.delete()
57 post.delete()
58
58
59 def delete_posts_by_ip(self, ip):
59 def delete_posts_by_ip(self, ip):
60 posts = self.filter(poster_ip=ip)
60 posts = self.filter(poster_ip=ip)
61 for post in posts:
61 for post in posts:
62 self.delete_post(post)
62 self.delete_post(post)
63
63
64 def get_threads(self, tag=None, page=ALL_PAGES,
64 def get_threads(self, tag=None, page=ALL_PAGES,
65 order_by='-last_edit_time'):
65 order_by='-last_edit_time'):
66 if tag:
66 if tag:
67 threads = self.filter(parent=NO_PARENT, tags=tag)
67 threads = self.filter(parent=NO_PARENT, tags=tag)
68
68
69 # TODO Throw error 404 if no threads for tag found?
69 # TODO Throw error 404 if no threads for tag found?
70 else:
70 else:
71 threads = self.filter(parent=NO_PARENT)
71 threads = self.filter(parent=NO_PARENT)
72
72
73 threads = threads.order_by(order_by)
73 threads = threads.order_by(order_by)
74
74
75 if page != ALL_PAGES:
75 if page != ALL_PAGES:
76 thread_count = len(threads)
76 thread_count = len(threads)
77
77
78 if page < self.get_thread_page_count(tag=tag):
78 if page < self.get_thread_page_count(tag=tag):
79 start_thread = page * settings.THREADS_PER_PAGE
79 start_thread = page * settings.THREADS_PER_PAGE
80 end_thread = min(start_thread + settings.THREADS_PER_PAGE,
80 end_thread = min(start_thread + settings.THREADS_PER_PAGE,
81 thread_count)
81 thread_count)
82 threads = threads[start_thread:end_thread]
82 threads = threads[start_thread:end_thread]
83
83
84 return threads
84 return threads
85
85
86 def get_thread(self, opening_post_id):
86 def get_thread(self, opening_post_id):
87 try:
87 try:
88 opening_post = self.get(id=opening_post_id, parent=NO_PARENT)
88 opening_post = self.get(id=opening_post_id, parent=NO_PARENT)
89 except Post.DoesNotExist:
89 except Post.DoesNotExist:
90 raise Http404
90 raise Http404
91
91
92 if opening_post.parent == NO_PARENT:
92 if opening_post.parent == NO_PARENT:
93 replies = self.filter(parent=opening_post_id)
93 replies = self.filter(parent=opening_post_id)
94
94
95 thread = [opening_post]
95 thread = [opening_post]
96 thread.extend(replies)
96 thread.extend(replies)
97
97
98 return thread
98 return thread
99
99
100 def exists(self, post_id):
100 def exists(self, post_id):
101 posts = self.filter(id=post_id)
101 posts = self.filter(id=post_id)
102
102
103 return posts.count() > 0
103 return posts.count() > 0
104
104
105 def get_thread_page_count(self, tag=None):
105 def get_thread_page_count(self, tag=None):
106 if tag:
106 if tag:
107 threads = self.filter(parent=NO_PARENT, tags=tag)
107 threads = self.filter(parent=NO_PARENT, tags=tag)
108 else:
108 else:
109 threads = self.filter(parent=NO_PARENT)
109 threads = self.filter(parent=NO_PARENT)
110
110
111 return int(math.ceil(threads.count() / float(
111 return int(math.ceil(threads.count() / float(
112 settings.THREADS_PER_PAGE)))
112 settings.THREADS_PER_PAGE)))
113
113
114 def _delete_old_threads(self):
114 def _delete_old_threads(self):
115 """
115 """
116 Preserves maximum thread count. If there are too many threads,
116 Preserves maximum thread count. If there are too many threads,
117 delete the old ones.
117 delete the old ones.
118 """
118 """
119
119
120 # TODO Move old threads to the archive instead of deleting them.
120 # TODO Move old threads to the archive instead of deleting them.
121 # Maybe make some 'old' field in the model to indicate the thread
121 # Maybe make some 'old' field in the model to indicate the thread
122 # must not be shown and be able for replying.
122 # must not be shown and be able for replying.
123
123
124 threads = self.get_threads()
124 threads = self.get_threads()
125 thread_count = len(threads)
125 thread_count = len(threads)
126
126
127 if thread_count > settings.MAX_THREAD_COUNT:
127 if thread_count > settings.MAX_THREAD_COUNT:
128 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
128 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
129 old_threads = threads[thread_count - num_threads_to_delete:]
129 old_threads = threads[thread_count - num_threads_to_delete:]
130
130
131 for thread in old_threads:
131 for thread in old_threads:
132 self.delete_post(thread)
132 self.delete_post(thread)
133
133
134 def _bump_thread(self, thread_id):
134 def _bump_thread(self, thread_id):
135 thread = self.get(id=thread_id)
135 thread = self.get(id=thread_id)
136
136
137 if thread.can_bump():
137 if thread.can_bump():
138 thread.last_edit_time = timezone.now()
138 thread.last_edit_time = timezone.now()
139 thread.save()
139 thread.save()
140
140
141
141
142 class TagManager(models.Manager):
142 class TagManager(models.Manager):
143 def get_not_empty_tags(self):
143 def get_not_empty_tags(self):
144 all_tags = self.all().order_by('name')
144 all_tags = self.all().order_by('name')
145 tags = []
145 tags = []
146 for tag in all_tags:
146 for tag in all_tags:
147 if not tag.is_empty():
147 if not tag.is_empty():
148 tags.append(tag)
148 tags.append(tag)
149
149
150 return tags
150 return tags
151
151
152 def get_popular_tags(self):
152 def get_popular_tags(self):
153 all_tags = self.get_not_empty_tags()
153 all_tags = self.get_not_empty_tags()
154
154
155 sorted_tags = sorted(all_tags, key=lambda tag: tag.get_popularity(),
155 sorted_tags = sorted(all_tags, key=lambda tag: tag.get_popularity(),
156 reverse=True)
156 reverse=True)
157
157
158 return sorted_tags[:settings.POPULAR_TAGS]
158 return sorted_tags[:settings.POPULAR_TAGS]
159
159
160
160
161 class Tag(models.Model):
161 class Tag(models.Model):
162 """
162 """
163 A tag is a text node assigned to the post. The tag serves as a board
163 A tag is a text node assigned to the post. The tag serves as a board
164 section. There can be multiple tags for each message
164 section. There can be multiple tags for each message
165 """
165 """
166
166
167 objects = TagManager()
167 objects = TagManager()
168
168
169 name = models.CharField(max_length=100)
169 name = models.CharField(max_length=100)
170 # TODO Connect the tag to its posts to check the number of threads for
170 # TODO Connect the tag to its posts to check the number of threads for
171 # the tag.
171 # the tag.
172
172
173 def __unicode__(self):
173 def __unicode__(self):
174 return self.name
174 return self.name
175
175
176 def is_empty(self):
176 def is_empty(self):
177 return self.get_post_count() == 0
177 return self.get_post_count() == 0
178
178
179 def get_post_count(self):
179 def get_post_count(self):
180 posts_with_tag = Post.objects.get_threads(tag=self)
180 posts_with_tag = Post.objects.get_threads(tag=self)
181 return posts_with_tag.count()
181 return posts_with_tag.count()
182
182
183 def get_popularity(self):
183 def get_popularity(self):
184 posts_with_tag = Post.objects.get_threads(tag=self)
184 posts_with_tag = Post.objects.get_threads(tag=self)
185 reply_count = 0
185 reply_count = 0
186 for post in posts_with_tag:
186 for post in posts_with_tag:
187 reply_count += post.get_reply_count()
187 reply_count += post.get_reply_count()
188 reply_count += OPENING_POST_POPULARITY_WEIGHT
188 reply_count += OPENING_POST_POPULARITY_WEIGHT
189
189
190 return reply_count
190 return reply_count
191
191
192
192
193 class Post(models.Model):
193 class Post(models.Model):
194 """A post is a message."""
194 """A post is a message."""
195
195
196 objects = PostManager()
196 objects = PostManager()
197
197
198 def _update_image_filename(self, filename):
198 def _update_image_filename(self, filename):
199 """Get unique image filename"""
199 """Get unique image filename"""
200
200
201 path = IMAGES_DIRECTORY
201 path = IMAGES_DIRECTORY
202 new_name = str(int(time.mktime(time.gmtime())))
202 new_name = str(int(time.mktime(time.gmtime())))
203 new_name += str(int(random() * 1000))
203 new_name += str(int(random() * 1000))
204 new_name += FILE_EXTENSION_DELIMITER
204 new_name += FILE_EXTENSION_DELIMITER
205 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
205 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
206
206
207 return os.path.join(path, new_name)
207 return os.path.join(path, new_name)
208
208
209 title = models.CharField(max_length=TITLE_MAX_LENGTH)
209 title = models.CharField(max_length=TITLE_MAX_LENGTH)
210 pub_time = models.DateTimeField()
210 pub_time = models.DateTimeField()
211 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
211 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
212 escape_html=False)
212 escape_html=False)
213
213
214 image_width = models.IntegerField()
214 image_width = models.IntegerField(default=0)
215 image_height = models.IntegerField()
215 image_height = models.IntegerField(default=0)
216
216
217 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
217 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
218 blank=True, sizes=(IMAGE_THUMB_SIZE,),
218 blank=True, sizes=(IMAGE_THUMB_SIZE,),
219 width_field='image_width',
219 width_field='image_width',
220 height_field='image_height')
220 height_field='image_height')
221
221
222 poster_ip = models.GenericIPAddressField()
222 poster_ip = models.GenericIPAddressField()
223 poster_user_agent = models.TextField()
223 poster_user_agent = models.TextField()
224 parent = models.BigIntegerField()
224 parent = models.BigIntegerField()
225 tags = models.ManyToManyField(Tag)
225 tags = models.ManyToManyField(Tag)
226 last_edit_time = models.DateTimeField()
226 last_edit_time = models.DateTimeField()
227
227
228 def __unicode__(self):
228 def __unicode__(self):
229 return '#' + str(self.id) + ' ' + self.title + ' (' + \
229 return '#' + str(self.id) + ' ' + self.title + ' (' + \
230 self.text.raw[:50] + ')'
230 self.text.raw[:50] + ')'
231
231
232 def _get_replies(self):
232 def _get_replies(self):
233 return Post.objects.filter(parent=self.id)
233 return Post.objects.filter(parent=self.id)
234
234
235 def get_reply_count(self):
235 def get_reply_count(self):
236 return self._get_replies().count()
236 return self._get_replies().count()
237
237
238 def get_images_count(self):
238 def get_images_count(self):
239 images_count = 1 if self.image else 0
239 images_count = 1 if self.image else 0
240 for reply in self._get_replies():
240 for reply in self._get_replies():
241 if reply.image:
241 if reply.image:
242 images_count += 1
242 images_count += 1
243
243
244 return images_count
244 return images_count
245
245
246 def get_gets_count(self):
246 def get_gets_count(self):
247 gets_count = 1 if self.is_get() else 0
247 gets_count = 1 if self.is_get() else 0
248 for reply in self._get_replies():
248 for reply in self._get_replies():
249 if reply.is_get():
249 if reply.is_get():
250 gets_count += 1
250 gets_count += 1
251
251
252 return gets_count
252 return gets_count
253
253
254 def can_bump(self):
254 def can_bump(self):
255 """Check if the thread can be bumped by replying"""
255 """Check if the thread can be bumped by replying"""
256
256
257 replies_count = len(Post.objects.get_thread(self.id))
257 replies_count = len(Post.objects.get_thread(self.id))
258
258
259 return replies_count <= settings.MAX_POSTS_PER_THREAD
259 return replies_count <= settings.MAX_POSTS_PER_THREAD
260
260
261 def get_last_replies(self):
261 def get_last_replies(self):
262 if settings.LAST_REPLIES_COUNT > 0:
262 if settings.LAST_REPLIES_COUNT > 0:
263 reply_count = self.get_reply_count()
263 reply_count = self.get_reply_count()
264
264
265 if reply_count > 0:
265 if reply_count > 0:
266 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
266 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
267 reply_count)
267 reply_count)
268 last_replies = self._get_replies()[reply_count
268 last_replies = self._get_replies()[reply_count
269 - reply_count_to_show:]
269 - reply_count_to_show:]
270
270
271 return last_replies
271 return last_replies
272
272
273
273
274 class Admin(models.Model):
274 class Admin(models.Model):
275 """
275 """
276 Model for admin users
276 Model for admin users
277 """
277 """
278 name = models.CharField(max_length=100)
278 name = models.CharField(max_length=100)
279 password = models.CharField(max_length=100)
279 password = models.CharField(max_length=100)
280
280
281 def __unicode__(self):
281 def __unicode__(self):
282 return self.name + '/' + '*' * len(self.password)
282 return self.name + '/' + '*' * len(self.password)
283
283
284
284
285 class Ban(models.Model):
285 class Ban(models.Model):
286
286
287 ip = models.GenericIPAddressField()
287 ip = models.GenericIPAddressField()
288
288
289 def __unicode__(self):
289 def __unicode__(self):
290 return self.ip
290 return self.ip
General Comments 0
You need to be logged in to leave comments. Login now