##// END OF EJS Templates
Added replies manytomany field to the post to get its replies more efficiently.
neko259 -
r169:4857f106 default
parent child Browse files
Show More
@@ -0,0 +1,81 b''
1 # -*- coding: utf-8 -*-
2 import datetime
3 from south.db import db
4 from south.v2 import SchemaMigration
5 from django.db import models
6 from boards.models import Post, NO_PARENT
7
8
9 class Migration(SchemaMigration):
10
11 def forwards(self, orm):
12 # Adding M2M table for field replies on 'Post'
13 m2m_table_name = db.shorten_name(u'boards_post_replies')
14 db.create_table(m2m_table_name, (
15 ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
16 ('from_post', models.ForeignKey(orm[u'boards.post'], null=False)),
17 ('to_post', models.ForeignKey(orm[u'boards.post'], null=False))
18 ))
19 db.create_unique(m2m_table_name, ['from_post_id', 'to_post_id'])
20
21 for post in Post.objects.all():
22 if post.parent != NO_PARENT:
23 parent = Post.objects.get(id=post.parent)
24 parent.replies.add(post)
25
26
27 def backwards(self, orm):
28 # Removing M2M table for field replies on 'Post'
29 db.delete_table(db.shorten_name(u'boards_post_replies'))
30
31
32 models = {
33 u'boards.ban': {
34 'Meta': {'object_name': 'Ban'},
35 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
36 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'})
37 },
38 u'boards.post': {
39 'Meta': {'object_name': 'Post'},
40 '_text_rendered': ('django.db.models.fields.TextField', [], {}),
41 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
42 'image': ('boards.thumbs.ImageWithThumbsField', [], {'max_length': '100', 'blank': 'True'}),
43 'image_height': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
44 'image_width': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
45 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}),
46 'parent': ('django.db.models.fields.BigIntegerField', [], {}),
47 'poster_ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
48 'poster_user_agent': ('django.db.models.fields.TextField', [], {}),
49 'pub_time': ('django.db.models.fields.DateTimeField', [], {}),
50 'replies': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'re+'", 'null': 'True', 'symmetrical': 'False', 'to': u"orm['boards.Post']"}),
51 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['boards.Tag']", 'symmetrical': 'False'}),
52 'text': ('markupfield.fields.MarkupField', [], {'rendered_field': 'True'}),
53 'text_markup_type': ('django.db.models.fields.CharField', [], {'default': "'markdown'", 'max_length': '30'}),
54 'title': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
55 'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['boards.User']", 'null': 'True'})
56 },
57 u'boards.setting': {
58 'Meta': {'object_name': 'Setting'},
59 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
60 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
61 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['boards.User']"}),
62 'value': ('django.db.models.fields.CharField', [], {'max_length': '50'})
63 },
64 u'boards.tag': {
65 'Meta': {'object_name': 'Tag'},
66 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
67 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
68 },
69 u'boards.user': {
70 'Meta': {'object_name': 'User'},
71 'fav_tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['boards.Tag']", 'null': 'True', 'blank': 'True'}),
72 'fav_threads': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'symmetrical': 'False', 'to': u"orm['boards.Post']"}),
73 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
74 'last_access_time': ('django.db.models.fields.DateTimeField', [], {}),
75 'rank': ('django.db.models.fields.IntegerField', [], {}),
76 'registration_time': ('django.db.models.fields.DateTimeField', [], {}),
77 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '50'})
78 }
79 }
80
81 complete_apps = ['boards'] No newline at end of file
@@ -1,326 +1,332 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.db.models import Count
8 from django.http import Http404
9 from django.http import Http404
9 from django.utils import timezone
10 from django.utils import timezone
10 from markupfield.fields import MarkupField
11 from markupfield.fields import MarkupField
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 RANK_ADMIN = 0
30 RANK_ADMIN = 0
31 RANK_MODERATOR = 10
31 RANK_MODERATOR = 10
32 RANK_USER = 100
32 RANK_USER = 100
33
33
34
34
35 class PostManager(models.Manager):
35 class PostManager(models.Manager):
36 def create_post(self, title, text, image=None, parent_id=NO_PARENT,
36 def create_post(self, title, text, image=None, parent_id=NO_PARENT,
37 ip=NO_IP, tags=None, user=None):
37 ip=NO_IP, tags=None, user=None):
38 post = self.create(title=title,
38 post = self.create(title=title,
39 text=text,
39 text=text,
40 pub_time=timezone.now(),
40 pub_time=timezone.now(),
41 parent=parent_id,
41 parent=parent_id,
42 image=image,
42 image=image,
43 poster_ip=ip,
43 poster_ip=ip,
44 poster_user_agent=UNKNOWN_UA,
44 poster_user_agent=UNKNOWN_UA,
45 last_edit_time=timezone.now(),
45 last_edit_time=timezone.now(),
46 user=user)
46 user=user)
47
47
48 if parent_id != NO_PARENT:
49 parent = self.get(id=parent_id)
50 parent.replies.add(post)
51
48 if tags:
52 if tags:
49 map(post.tags.add, tags)
53 map(post.tags.add, tags)
50
54
51 if parent_id != NO_PARENT:
55 if parent_id != NO_PARENT:
52 self._bump_thread(parent_id)
56 self._bump_thread(parent_id)
53 else:
57 else:
54 self._delete_old_threads()
58 self._delete_old_threads()
55
59
56 return post
60 return post
57
61
58 def delete_post(self, post):
62 def delete_post(self, post):
59 children = self.filter(parent=post.id)
63 if post.replies.count() > 0:
60
64 map(self.delete_post, post.replies)
61 map(self.delete_post, children)
62 post.delete()
65 post.delete()
63
66
64 def delete_posts_by_ip(self, ip):
67 def delete_posts_by_ip(self, ip):
65 posts = self.filter(poster_ip=ip)
68 posts = self.filter(poster_ip=ip)
66 map(self.delete_post, posts)
69 map(self.delete_post, posts)
67
70
68 def get_threads(self, tag=None, page=ALL_PAGES,
71 def get_threads(self, tag=None, page=ALL_PAGES,
69 order_by='-last_edit_time'):
72 order_by='-last_edit_time'):
70 if tag:
73 if tag:
71 threads = self.filter(parent=NO_PARENT, tags=tag)
74 threads = self.filter(parent=NO_PARENT, tags=tag)
72
75
73 # TODO Throw error 404 if no threads for tag found?
76 # TODO Throw error 404 if no threads for tag found?
74 else:
77 else:
75 threads = self.filter(parent=NO_PARENT)
78 threads = self.filter(parent=NO_PARENT)
76
79
77 threads = threads.order_by(order_by)
80 threads = threads.order_by(order_by)
78
81
79 if page != ALL_PAGES:
82 if page != ALL_PAGES:
80 thread_count = threads.count()
83 thread_count = threads.count()
81
84
82 if page < self.get_thread_page_count(tag=tag):
85 if page < self.get_thread_page_count(tag=tag):
83 start_thread = page * settings.THREADS_PER_PAGE
86 start_thread = page * settings.THREADS_PER_PAGE
84 end_thread = min(start_thread + settings.THREADS_PER_PAGE,
87 end_thread = min(start_thread + settings.THREADS_PER_PAGE,
85 thread_count)
88 thread_count)
86 threads = threads[start_thread:end_thread]
89 threads = threads[start_thread:end_thread]
87
90
88 return threads
91 return threads
89
92
90 def get_thread(self, opening_post_id):
93 def get_thread(self, opening_post_id):
91 try:
94 try:
92 opening_post = self.get(id=opening_post_id, parent=NO_PARENT)
95 opening_post = self.get(id=opening_post_id, parent=NO_PARENT)
93 except Post.DoesNotExist:
96 except Post.DoesNotExist:
94 raise Http404
97 raise Http404
95
98
96 if opening_post.parent == NO_PARENT:
99 if opening_post.replies:
97 replies = self.filter(parent=opening_post_id)
98
99 thread = [opening_post]
100 thread = [opening_post]
100 thread.extend(replies)
101 thread.extend(opening_post.replies.all())
101
102
102 return thread
103 return thread
103
104
104 def exists(self, post_id):
105 def exists(self, post_id):
105 posts = self.filter(id=post_id)
106 posts = self.filter(id=post_id)
106
107
107 return posts.count() > 0
108 return posts.count() > 0
108
109
109 def get_thread_page_count(self, tag=None):
110 def get_thread_page_count(self, tag=None):
110 if tag:
111 if tag:
111 threads = self.filter(parent=NO_PARENT, tags=tag)
112 threads = self.filter(parent=NO_PARENT, tags=tag)
112 else:
113 else:
113 threads = self.filter(parent=NO_PARENT)
114 threads = self.filter(parent=NO_PARENT)
114
115
115 return int(math.ceil(threads.count() / float(
116 return int(math.ceil(threads.count() / float(
116 settings.THREADS_PER_PAGE)))
117 settings.THREADS_PER_PAGE)))
117
118
118 def _delete_old_threads(self):
119 def _delete_old_threads(self):
119 """
120 """
120 Preserves maximum thread count. If there are too many threads,
121 Preserves maximum thread count. If there are too many threads,
121 delete the old ones.
122 delete the old ones.
122 """
123 """
123
124
124 # TODO Move old threads to the archive instead of deleting them.
125 # TODO Move old threads to the archive instead of deleting them.
125 # Maybe make some 'old' field in the model to indicate the thread
126 # Maybe make some 'old' field in the model to indicate the thread
126 # must not be shown and be able for replying.
127 # must not be shown and be able for replying.
127
128
128 threads = self.get_threads()
129 threads = self.get_threads()
129 thread_count = len(threads)
130 thread_count = len(threads)
130
131
131 if thread_count > settings.MAX_THREAD_COUNT:
132 if thread_count > settings.MAX_THREAD_COUNT:
132 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
133 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
133 old_threads = threads[thread_count - num_threads_to_delete:]
134 old_threads = threads[thread_count - num_threads_to_delete:]
134
135
135 map(self.delete_post, old_threads)
136 map(self.delete_post, old_threads)
136
137
137 def _bump_thread(self, thread_id):
138 def _bump_thread(self, thread_id):
138 thread = self.get(id=thread_id)
139 thread = self.get(id=thread_id)
139
140
140 if thread.can_bump():
141 if thread.can_bump():
141 thread.last_edit_time = timezone.now()
142 thread.last_edit_time = timezone.now()
142 thread.save()
143 thread.save()
143
144
144
145
145 class TagManager(models.Manager):
146 class TagManager(models.Manager):
146 def get_not_empty_tags(self):
147 def get_not_empty_tags(self):
147 all_tags = self.all().order_by('name')
148 all_tags = self.all().order_by('name')
148 tags = []
149 tags = []
149 for tag in all_tags:
150 for tag in all_tags:
150 if not tag.is_empty():
151 if not tag.is_empty():
151 tags.append(tag)
152 tags.append(tag)
152
153
153 return tags
154 return tags
154
155
155 def get_popular_tags(self):
156 def get_popular_tags(self):
156 all_tags = self.get_not_empty_tags()
157 all_tags = self.get_not_empty_tags()
157
158
158 sorted_tags = sorted(all_tags, key=lambda tag: tag.get_popularity(),
159 sorted_tags = sorted(all_tags, key=lambda tag: tag.get_popularity(),
159 reverse=True)
160 reverse=True)
160
161
161 return sorted_tags[:settings.POPULAR_TAGS]
162 return sorted_tags[:settings.POPULAR_TAGS]
162
163
163
164
164 class Tag(models.Model):
165 class Tag(models.Model):
165 """
166 """
166 A tag is a text node assigned to the post. The tag serves as a board
167 A tag is a text node assigned to the post. The tag serves as a board
167 section. There can be multiple tags for each message
168 section. There can be multiple tags for each message
168 """
169 """
169
170
170 objects = TagManager()
171 objects = TagManager()
171
172
172 name = models.CharField(max_length=100)
173 name = models.CharField(max_length=100)
173
174
174 def __unicode__(self):
175 def __unicode__(self):
175 return self.name
176 return self.name
176
177
177 def is_empty(self):
178 def is_empty(self):
178 return self.get_post_count() == 0
179 return self.get_post_count() == 0
179
180
180 def get_post_count(self):
181 def get_post_count(self):
181 posts_with_tag = Post.objects.get_threads(tag=self)
182 posts_with_tag = Post.objects.get_threads(tag=self)
182 return posts_with_tag.count()
183 return posts_with_tag.count()
183
184
184 def get_popularity(self):
185 def get_popularity(self):
185 posts_with_tag = Post.objects.get_threads(tag=self)
186 posts_with_tag = Post.objects.get_threads(tag=self)
186 reply_count = 0
187 reply_count = 0
187 for post in posts_with_tag:
188 for post in posts_with_tag:
188 reply_count += post.get_reply_count()
189 reply_count += post.get_reply_count()
189 reply_count += OPENING_POST_POPULARITY_WEIGHT
190 reply_count += OPENING_POST_POPULARITY_WEIGHT
190
191
191 return reply_count
192 return reply_count
192
193
193
194
194 class Post(models.Model):
195 class Post(models.Model):
195 """A post is a message."""
196 """A post is a message."""
196
197
197 objects = PostManager()
198 objects = PostManager()
198
199
199 def _update_image_filename(self, filename):
200 def _update_image_filename(self, filename):
200 """Get unique image filename"""
201 """Get unique image filename"""
201
202
202 path = IMAGES_DIRECTORY
203 path = IMAGES_DIRECTORY
203 new_name = str(int(time.mktime(time.gmtime())))
204 new_name = str(int(time.mktime(time.gmtime())))
204 new_name += str(int(random() * 1000))
205 new_name += str(int(random() * 1000))
205 new_name += FILE_EXTENSION_DELIMITER
206 new_name += FILE_EXTENSION_DELIMITER
206 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
207 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
207
208
208 return os.path.join(path, new_name)
209 return os.path.join(path, new_name)
209
210
210 title = models.CharField(max_length=TITLE_MAX_LENGTH)
211 title = models.CharField(max_length=TITLE_MAX_LENGTH)
211 pub_time = models.DateTimeField()
212 pub_time = models.DateTimeField()
212 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
213 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
213 escape_html=False)
214 escape_html=False)
214
215
215 image_width = models.IntegerField(default=0)
216 image_width = models.IntegerField(default=0)
216 image_height = models.IntegerField(default=0)
217 image_height = models.IntegerField(default=0)
217
218
218 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
219 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
219 blank=True, sizes=(IMAGE_THUMB_SIZE,),
220 blank=True, sizes=(IMAGE_THUMB_SIZE,),
220 width_field='image_width',
221 width_field='image_width',
221 height_field='image_height')
222 height_field='image_height')
222
223
223 poster_ip = models.GenericIPAddressField()
224 poster_ip = models.GenericIPAddressField()
224 poster_user_agent = models.TextField()
225 poster_user_agent = models.TextField()
226
227 # TODO Convert this field to ForeignKey
225 parent = models.BigIntegerField()
228 parent = models.BigIntegerField()
229
226 tags = models.ManyToManyField(Tag)
230 tags = models.ManyToManyField(Tag)
227 last_edit_time = models.DateTimeField()
231 last_edit_time = models.DateTimeField()
228 user = models.ForeignKey('User', null=True, default=None)
232 user = models.ForeignKey('User', null=True, default=None)
229
233
234 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
235 blank=True, related_name='re+')
236
230 def __unicode__(self):
237 def __unicode__(self):
231 return '#' + str(self.id) + ' ' + self.title + ' (' + \
238 return '#' + str(self.id) + ' ' + self.title + ' (' + \
232 self.text.raw[:50] + ')'
239 self.text.raw[:50] + ')'
233
240
234 def get_title(self):
241 def get_title(self):
235 title = self.title
242 title = self.title
236 if len(title) == 0:
243 if len(title) == 0:
237 title = self.text.raw[:20]
244 title = self.text.raw[:20]
238
245
239 return title
246 return title
240
247
241 def _get_replies(self):
248 def _get_replies(self):
242 return Post.objects.filter(parent=self.id)
249 return self.replies
243
250
244 def get_reply_count(self):
251 def get_reply_count(self):
245 return self._get_replies().count()
252 return self.replies.count()
246
253
247 def get_images_count(self):
254 def get_images_count(self):
248 images_count = 1 if self.image else 0
255 images_count = 1 if self.image else 0
249
256
250 replies = self._get_replies()
257 for reply in self.replies:
251 for reply in replies:
252 if reply.image:
258 if reply.image:
253 images_count += 1
259 images_count += 1
254
260
255 return images_count
261 return images_count
256
262
257 def can_bump(self):
263 def can_bump(self):
258 """Check if the thread can be bumped by replying"""
264 """Check if the thread can be bumped by replying"""
259
265
260 replies_count = self.get_reply_count() + 1
266 post_count = self.get_reply_count() + 1
261
267
262 return replies_count <= settings.MAX_POSTS_PER_THREAD
268 return post_count <= settings.MAX_POSTS_PER_THREAD
263
269
264 def get_last_replies(self):
270 def get_last_replies(self):
265 if settings.LAST_REPLIES_COUNT > 0:
271 if settings.LAST_REPLIES_COUNT > 0:
266 reply_count = self.get_reply_count()
272 reply_count = self.get_reply_count()
267
273
268 if reply_count > 0:
274 if reply_count > 0:
269 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
275 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
270 reply_count)
276 reply_count)
271 last_replies = self._get_replies()[reply_count
277 last_replies = self.replies.all()[reply_count -
272 - reply_count_to_show:]
278 reply_count_to_show:]
273
279
274 return last_replies
280 return last_replies
275
281
276
282
277 class User(models.Model):
283 class User(models.Model):
278
284
279 user_id = models.CharField(max_length=50)
285 user_id = models.CharField(max_length=50)
280 rank = models.IntegerField()
286 rank = models.IntegerField()
281
287
282 registration_time = models.DateTimeField()
288 registration_time = models.DateTimeField()
283 last_access_time = models.DateTimeField()
289 last_access_time = models.DateTimeField()
284
290
285 fav_tags = models.ManyToManyField(Tag, null=True, blank=True)
291 fav_tags = models.ManyToManyField(Tag, null=True, blank=True)
286 fav_threads = models.ManyToManyField(Post, related_name='+', null=True,
292 fav_threads = models.ManyToManyField(Post, related_name='+', null=True,
287 blank=True)
293 blank=True)
288
294
289 def save_setting(self, name, value):
295 def save_setting(self, name, value):
290 setting, created = Setting.objects.get_or_create(name=name, user=self)
296 setting, created = Setting.objects.get_or_create(name=name, user=self)
291 setting.value = value
297 setting.value = value
292 setting.save()
298 setting.save()
293
299
294 return setting
300 return setting
295
301
296 def get_setting(self, name):
302 def get_setting(self, name):
297 if Setting.objects.filter(name=name, user=self).exists():
303 if Setting.objects.filter(name=name, user=self).exists():
298 setting = Setting.objects.get(name=name, user=self)
304 setting = Setting.objects.get(name=name, user=self)
299 setting_value = setting.value
305 setting_value = setting.value
300 else:
306 else:
301 setting_value = None
307 setting_value = None
302
308
303 return setting_value
309 return setting_value
304
310
305 def is_moderator(self):
311 def is_moderator(self):
306 return RANK_MODERATOR >= self.rank
312 return RANK_MODERATOR >= self.rank
307
313
308 def get_sorted_fav_tags(self):
314 def get_sorted_fav_tags(self):
309 return self.fav_tags.order_by('name')
315 return self.fav_tags.order_by('name')
310
316
311 def __unicode__(self):
317 def __unicode__(self):
312 return self.user_id + '(' + str(self.rank) + ')'
318 return self.user_id + '(' + str(self.rank) + ')'
313
319
314
320
315 class Setting(models.Model):
321 class Setting(models.Model):
316
322
317 name = models.CharField(max_length=50)
323 name = models.CharField(max_length=50)
318 value = models.CharField(max_length=50)
324 value = models.CharField(max_length=50)
319 user = models.ForeignKey(User)
325 user = models.ForeignKey(User)
320
326
321
327
322 class Ban(models.Model):
328 class Ban(models.Model):
323 ip = models.GenericIPAddressField()
329 ip = models.GenericIPAddressField()
324
330
325 def __unicode__(self):
331 def __unicode__(self):
326 return self.ip
332 return self.ip
@@ -1,358 +1,359 b''
1 import hashlib
1 import hashlib
2 from django.core.urlresolvers import reverse
2 from django.core.urlresolvers import reverse
3 from django.http import HttpResponseRedirect
3 from django.http import HttpResponseRedirect
4 from django.template import RequestContext
4 from django.template import RequestContext
5 from django.shortcuts import render, redirect, get_object_or_404
5 from django.shortcuts import render, redirect, get_object_or_404
6 from django.utils import timezone
6 from django.utils import timezone
7
7
8 from boards import forms
8 from boards import forms
9 import boards
9 import boards
10 from boards import utils
10 from boards import utils
11 from boards.forms import ThreadForm, PostForm, SettingsForm, PlainErrorList, \
11 from boards.forms import ThreadForm, PostForm, SettingsForm, PlainErrorList, \
12 ThreadCaptchaForm, PostCaptchaForm, LoginForm
12 ThreadCaptchaForm, PostCaptchaForm, LoginForm
13
13
14 from boards.models import Post, Tag, Ban, User, RANK_USER, NO_PARENT
14 from boards.models import Post, Tag, Ban, User, RANK_USER, NO_PARENT
15 from boards import authors
15 from boards import authors
16 import neboard
16 import neboard
17
17
18
18
19 def index(request, page=0):
19 def index(request, page=0):
20 context = _init_default_context(request)
20 context = _init_default_context(request)
21
21
22 if utils.need_include_captcha(request):
22 if utils.need_include_captcha(request):
23 threadFormClass = ThreadCaptchaForm
23 threadFormClass = ThreadCaptchaForm
24 kwargs = {'request': request}
24 kwargs = {'request': request}
25 else:
25 else:
26 threadFormClass = ThreadForm
26 threadFormClass = ThreadForm
27 kwargs = {}
27 kwargs = {}
28
28
29 if request.method == 'POST':
29 if request.method == 'POST':
30 form = threadFormClass(request.POST, request.FILES,
30 form = threadFormClass(request.POST, request.FILES,
31 error_class=PlainErrorList, **kwargs)
31 error_class=PlainErrorList, **kwargs)
32 form.session = request.session
32 form.session = request.session
33
33
34 if form.is_valid():
34 if form.is_valid():
35 return _new_post(request, form)
35 return _new_post(request, form)
36 else:
36 else:
37 form = threadFormClass(error_class=PlainErrorList, **kwargs)
37 form = threadFormClass(error_class=PlainErrorList, **kwargs)
38
38
39 threads = []
39 threads = []
40 for thread in Post.objects.get_threads(page=int(page)):
40 for thread in Post.objects.get_threads(page=int(page)):
41 threads.append({'thread': thread,
41 threads.append({'thread': thread,
42 'bumpable': thread.can_bump()})
42 'bumpable': thread.can_bump()})
43
43
44 context['threads'] = None if len(threads) == 0 else threads
44 context['threads'] = None if len(threads) == 0 else threads
45 context['form'] = form
45 context['form'] = form
46 context['pages'] = range(Post.objects.get_thread_page_count())
46 context['pages'] = range(Post.objects.get_thread_page_count())
47
47
48 return render(request, 'boards/posting_general.html',
48 return render(request, 'boards/posting_general.html',
49 context)
49 context)
50
50
51
51
52 def _new_post(request, form, thread_id=boards.models.NO_PARENT):
52 def _new_post(request, form, thread_id=boards.models.NO_PARENT):
53 """Add a new post (in thread or as a reply)."""
53 """Add a new post (in thread or as a reply)."""
54
54
55 ip = _get_client_ip(request)
55 ip = _get_client_ip(request)
56 is_banned = Ban.objects.filter(ip=ip).count() > 0
56 is_banned = Ban.objects.filter(ip=ip).count() > 0
57
57
58 if is_banned:
58 if is_banned:
59 return redirect(you_are_banned)
59 return redirect(you_are_banned)
60
60
61 data = form.cleaned_data
61 data = form.cleaned_data
62
62
63 title = data['title']
63 title = data['title']
64 text = data['text']
64 text = data['text']
65
65
66 if 'image' in data.keys():
66 if 'image' in data.keys():
67 image = data['image']
67 image = data['image']
68 else:
68 else:
69 image = None
69 image = None
70
70
71 tags = []
71 tags = []
72
72
73 new_thread = thread_id == boards.models.NO_PARENT
73 new_thread = thread_id == boards.models.NO_PARENT
74 if new_thread:
74 if new_thread:
75 tag_strings = data['tags']
75 tag_strings = data['tags']
76
76
77 if tag_strings:
77 if tag_strings:
78 tag_strings = tag_strings.split(' ')
78 tag_strings = tag_strings.split(' ')
79 for tag_name in tag_strings:
79 for tag_name in tag_strings:
80 tag_name = tag_name.strip()
80 tag_name = tag_name.strip()
81 if len(tag_name) > 0:
81 if len(tag_name) > 0:
82 tag, created = Tag.objects.get_or_create(name=tag_name)
82 tag, created = Tag.objects.get_or_create(name=tag_name)
83 tags.append(tag)
83 tags.append(tag)
84
84
85 # TODO Add a possibility to define a link image instead of an image file.
85 # TODO Add a possibility to define a link image instead of an image file.
86 # If a link is given, download the image automatically.
86 # If a link is given, download the image automatically.
87
87
88 post = Post.objects.create_post(title=title, text=text, ip=ip,
88 post = Post.objects.create_post(title=title, text=text, ip=ip,
89 parent_id=thread_id, image=image,
89 parent_id=thread_id, image=image,
90 tags=tags)
90 tags=tags)
91
91
92 thread_to_show = (post.id if new_thread else thread_id)
92 thread_to_show = (post.id if new_thread else thread_id)
93
93
94 if new_thread:
94 if new_thread:
95 return redirect(thread, post_id=thread_to_show)
95 return redirect(thread, post_id=thread_to_show)
96 else:
96 else:
97 return redirect(reverse(thread,
97 return redirect(reverse(thread,
98 kwargs={'post_id': thread_to_show}) + '#'
98 kwargs={'post_id': thread_to_show}) + '#'
99 + str(post.id))
99 + str(post.id))
100
100
101
101
102 def tag(request, tag_name, page=0):
102 def tag(request, tag_name, page=0):
103 """Get all tag threads (posts without a parent)."""
103 """Get all tag threads (posts without a parent)."""
104
104
105 tag = get_object_or_404(Tag, name=tag_name)
105 tag = get_object_or_404(Tag, name=tag_name)
106 threads = []
106 threads = []
107 for thread in Post.objects.get_threads(tag=tag, page=int(page)):
107 for thread in Post.objects.get_threads(tag=tag, page=int(page)):
108 threads.append({'thread': thread,
108 threads.append({'thread': thread,
109 'bumpable': thread.can_bump()})
109 'bumpable': thread.can_bump()})
110
110
111 if request.method == 'POST':
111 if request.method == 'POST':
112 form = ThreadForm(request.POST, request.FILES,
112 form = ThreadForm(request.POST, request.FILES,
113 error_class=PlainErrorList)
113 error_class=PlainErrorList)
114 if form.is_valid():
114 if form.is_valid():
115 return _new_post(request, form)
115 return _new_post(request, form)
116 else:
116 else:
117 form = forms.ThreadForm(initial={'tags': tag_name},
117 form = forms.ThreadForm(initial={'tags': tag_name},
118 error_class=PlainErrorList)
118 error_class=PlainErrorList)
119
119
120 context = _init_default_context(request)
120 context = _init_default_context(request)
121 context['threads'] = None if len(threads) == 0 else threads
121 context['threads'] = None if len(threads) == 0 else threads
122 context['tag'] = tag_name
122 context['tag'] = tag_name
123 context['pages'] = range(Post.objects.get_thread_page_count(tag=tag))
123 context['pages'] = range(Post.objects.get_thread_page_count(tag=tag))
124
124
125 context['form'] = form
125 context['form'] = form
126
126
127 return render(request, 'boards/posting_general.html',
127 return render(request, 'boards/posting_general.html',
128 context)
128 context)
129
129
130
130
131 def thread(request, post_id):
131 def thread(request, post_id):
132 """Get all thread posts"""
132 """Get all thread posts"""
133
133
134 if utils.need_include_captcha(request):
134 if utils.need_include_captcha(request):
135 postFormClass = PostCaptchaForm
135 postFormClass = PostCaptchaForm
136 kwargs = {'request': request}
136 kwargs = {'request': request}
137 else:
137 else:
138 postFormClass = PostForm
138 postFormClass = PostForm
139 kwargs = {}
139 kwargs = {}
140
140
141 if request.method == 'POST':
141 if request.method == 'POST':
142 form = postFormClass(request.POST, request.FILES,
142 form = postFormClass(request.POST, request.FILES,
143 error_class=PlainErrorList, **kwargs)
143 error_class=PlainErrorList, **kwargs)
144 form.session = request.session
144 form.session = request.session
145
145
146 if form.is_valid():
146 if form.is_valid():
147 return _new_post(request, form, post_id)
147 return _new_post(request, form, post_id)
148 else:
148 else:
149 form = postFormClass(error_class=PlainErrorList, **kwargs)
149 form = postFormClass(error_class=PlainErrorList, **kwargs)
150
150
151 posts = Post.objects.get_thread(post_id)
151 posts = Post.objects.get_thread(post_id)
152
152
153 context = _init_default_context(request)
153 context = _init_default_context(request)
154
154
155 context['posts'] = posts
155 context['posts'] = posts
156 context['form'] = form
156 context['form'] = form
157 context['bumpable'] = posts[0].can_bump()
157 context['bumpable'] = posts[0].can_bump()
158
158
159 return render(request, 'boards/thread.html', context)
159 return render(request, 'boards/thread.html', context)
160
160
161
161
162 def login(request):
162 def login(request):
163 """Log in with user id"""
163 """Log in with user id"""
164
164
165 context = _init_default_context(request)
165 context = _init_default_context(request)
166
166
167 if request.method == 'POST':
167 if request.method == 'POST':
168 form = LoginForm(request.POST, request.FILES,
168 form = LoginForm(request.POST, request.FILES,
169 error_class=PlainErrorList)
169 error_class=PlainErrorList)
170 if form.is_valid():
170 if form.is_valid():
171 user = User.objects.get(user_id=form.cleaned_data['user_id'])
171 user = User.objects.get(user_id=form.cleaned_data['user_id'])
172 request.session['user_id'] = user.id
172 request.session['user_id'] = user.id
173 return redirect(index)
173 return redirect(index)
174
174
175 else:
175 else:
176 form = LoginForm()
176 form = LoginForm()
177
177
178 context['form'] = form
178 context['form'] = form
179
179
180 return render(request, 'boards/login.html', context)
180 return render(request, 'boards/login.html', context)
181
181
182
182
183 def settings(request):
183 def settings(request):
184 """User's settings"""
184 """User's settings"""
185
185
186 context = _init_default_context(request)
186 context = _init_default_context(request)
187
187
188 if request.method == 'POST':
188 if request.method == 'POST':
189 form = SettingsForm(request.POST)
189 form = SettingsForm(request.POST)
190 if form.is_valid():
190 if form.is_valid():
191 selected_theme = form.cleaned_data['theme']
191 selected_theme = form.cleaned_data['theme']
192
192
193 user = _get_user(request)
193 user = _get_user(request)
194 user.save_setting('theme', selected_theme)
194 user.save_setting('theme', selected_theme)
195
195
196 return redirect(settings)
196 return redirect(settings)
197 else:
197 else:
198 selected_theme = _get_theme(request)
198 selected_theme = _get_theme(request)
199 form = SettingsForm(initial={'theme': selected_theme})
199 form = SettingsForm(initial={'theme': selected_theme})
200 context['form'] = form
200 context['form'] = form
201
201
202 return render(request, 'boards/settings.html', context)
202 return render(request, 'boards/settings.html', context)
203
203
204
204
205 def all_tags(request):
205 def all_tags(request):
206 """All tags list"""
206 """All tags list"""
207
207
208 context = _init_default_context(request)
208 context = _init_default_context(request)
209 context['all_tags'] = Tag.objects.get_not_empty_tags()
209 context['all_tags'] = Tag.objects.get_not_empty_tags()
210
210
211 return render(request, 'boards/tags.html', context)
211 return render(request, 'boards/tags.html', context)
212
212
213
213
214 def jump_to_post(request, post_id):
214 def jump_to_post(request, post_id):
215 """Determine thread in which the requested post is and open it's page"""
215 """Determine thread in which the requested post is and open it's page"""
216
216
217 post = get_object_or_404(Post, id=post_id)
217 post = get_object_or_404(Post, id=post_id)
218
218
219 if boards.models.NO_PARENT == post.parent:
219 if boards.models.NO_PARENT == post.parent:
220 return redirect(thread, post_id=post.id)
220 return redirect(thread, post_id=post.id)
221 else:
221 else:
222 # TODO Change this code to not use 'parent' field anymore
222 parent_thread = get_object_or_404(Post, id=post.parent)
223 parent_thread = get_object_or_404(Post, id=post.parent)
223 return redirect(reverse(thread, kwargs={'post_id': parent_thread.id})
224 return redirect(reverse(thread, kwargs={'post_id': parent_thread.id})
224 + '#' + str(post.id))
225 + '#' + str(post.id))
225
226
226
227
227 def authors(request):
228 def authors(request):
228 context = _init_default_context(request)
229 context = _init_default_context(request)
229 context['authors'] = boards.authors.authors
230 context['authors'] = boards.authors.authors
230
231
231 return render(request, 'boards/authors.html', context)
232 return render(request, 'boards/authors.html', context)
232
233
233
234
234 def delete(request, post_id):
235 def delete(request, post_id):
235 user = _get_user(request)
236 user = _get_user(request)
236 post = get_object_or_404(Post, id=post_id)
237 post = get_object_or_404(Post, id=post_id)
237
238
238 if user.is_moderator():
239 if user.is_moderator():
239 # TODO Show confirmation page before deletion
240 # TODO Show confirmation page before deletion
240 Post.objects.delete_post(post)
241 Post.objects.delete_post(post)
241
242
242 if NO_PARENT == post.parent:
243 if NO_PARENT == post.parent:
243 return _redirect_to_next(request)
244 return _redirect_to_next(request)
244 else:
245 else:
245 return redirect(thread, post_id=post.parent)
246 return redirect(thread, post_id=post.parent)
246
247
247
248
248 def ban(request, post_id):
249 def ban(request, post_id):
249 user = _get_user(request)
250 user = _get_user(request)
250 post = get_object_or_404(Post, id=post_id)
251 post = get_object_or_404(Post, id=post_id)
251
252
252 if user.is_moderator():
253 if user.is_moderator():
253 # TODO Show confirmation page before ban
254 # TODO Show confirmation page before ban
254 Ban.objects.get_or_create(ip=post.poster_ip)
255 Ban.objects.get_or_create(ip=post.poster_ip)
255
256
256 return _redirect_to_next(request)
257 return _redirect_to_next(request)
257
258
258
259
259 def you_are_banned(request):
260 def you_are_banned(request):
260 context = _init_default_context(request)
261 context = _init_default_context(request)
261 return render(request, 'boards/staticpages/banned.html', context)
262 return render(request, 'boards/staticpages/banned.html', context)
262
263
263
264
264 def page_404(request):
265 def page_404(request):
265 context = _init_default_context(request)
266 context = _init_default_context(request)
266 return render(request, 'boards/404.html', context)
267 return render(request, 'boards/404.html', context)
267
268
268
269
269 def tag_subscribe(request, tag_name):
270 def tag_subscribe(request, tag_name):
270 user = _get_user(request)
271 user = _get_user(request)
271 tag = get_object_or_404(Tag, name=tag_name)
272 tag = get_object_or_404(Tag, name=tag_name)
272
273
273 if not tag in user.fav_tags.all():
274 if not tag in user.fav_tags.all():
274 user.fav_tags.add(tag)
275 user.fav_tags.add(tag)
275
276
276 return redirect(all_tags)
277 return redirect(all_tags)
277
278
278
279
279 def tag_unsubscribe(request, tag_name):
280 def tag_unsubscribe(request, tag_name):
280 user = _get_user(request)
281 user = _get_user(request)
281 tag = get_object_or_404(Tag, name=tag_name)
282 tag = get_object_or_404(Tag, name=tag_name)
282
283
283 if tag in user.fav_tags.all():
284 if tag in user.fav_tags.all():
284 user.fav_tags.remove(tag)
285 user.fav_tags.remove(tag)
285
286
286 return redirect(all_tags)
287 return redirect(all_tags)
287
288
288
289
289 def static_page(request, name):
290 def static_page(request, name):
290 context = _init_default_context(request)
291 context = _init_default_context(request)
291 return render(request, 'boards/staticpages/' + name + '.html', context)
292 return render(request, 'boards/staticpages/' + name + '.html', context)
292
293
293
294
294 def _get_theme(request, user=None):
295 def _get_theme(request, user=None):
295 """Get user's CSS theme"""
296 """Get user's CSS theme"""
296
297
297 if not user:
298 if not user:
298 user = _get_user(request)
299 user = _get_user(request)
299 theme = user.get_setting('theme')
300 theme = user.get_setting('theme')
300 if not theme:
301 if not theme:
301 theme = neboard.settings.DEFAULT_THEME
302 theme = neboard.settings.DEFAULT_THEME
302
303
303 return theme
304 return theme
304
305
305
306
306 def _get_client_ip(request):
307 def _get_client_ip(request):
307 x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
308 x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
308 if x_forwarded_for:
309 if x_forwarded_for:
309 ip = x_forwarded_for.split(',')[-1].strip()
310 ip = x_forwarded_for.split(',')[-1].strip()
310 else:
311 else:
311 ip = request.META.get('REMOTE_ADDR')
312 ip = request.META.get('REMOTE_ADDR')
312 return ip
313 return ip
313
314
314
315
315 def _init_default_context(request):
316 def _init_default_context(request):
316 """Create context with default values that are used in most views"""
317 """Create context with default values that are used in most views"""
317
318
318 context = RequestContext(request)
319 context = RequestContext(request)
319
320
320 user = _get_user(request)
321 user = _get_user(request)
321 context['user'] = user
322 context['user'] = user
322 context['tags'] = user.get_sorted_fav_tags()
323 context['tags'] = user.get_sorted_fav_tags()
323 context['theme'] = _get_theme(request, user)
324 context['theme'] = _get_theme(request, user)
324
325
325 return context
326 return context
326
327
327
328
328 def _get_user(request):
329 def _get_user(request):
329 """Get current user from the session"""
330 """Get current user from the session"""
330
331
331 session = request.session
332 session = request.session
332 if not 'user_id' in session:
333 if not 'user_id' in session:
333 request.session.save()
334 request.session.save()
334
335
335 md5 = hashlib.md5()
336 md5 = hashlib.md5()
336 md5.update(session.session_key)
337 md5.update(session.session_key)
337 new_id = md5.hexdigest()
338 new_id = md5.hexdigest()
338
339
339 time_now = timezone.now()
340 time_now = timezone.now()
340 user = User.objects.create(user_id=new_id, rank=RANK_USER,
341 user = User.objects.create(user_id=new_id, rank=RANK_USER,
341 registration_time=time_now,
342 registration_time=time_now,
342 last_access_time=time_now)
343 last_access_time=time_now)
343
344
344 session['user_id'] = user.id
345 session['user_id'] = user.id
345 else:
346 else:
346 user = User.objects.get(id=session['user_id'])
347 user = User.objects.get(id=session['user_id'])
347 user.last_access_time = timezone.now()
348 user.last_access_time = timezone.now()
348 user.save()
349 user.save()
349
350
350 return user
351 return user
351
352
352
353
353 def _redirect_to_next(request):
354 def _redirect_to_next(request):
354 if 'next' in request.GET:
355 if 'next' in request.GET:
355 next_page = request.GET['next']
356 next_page = request.GET['next']
356 return HttpResponseRedirect(next_page)
357 return HttpResponseRedirect(next_page)
357 else:
358 else:
358 return redirect(index)
359 return redirect(index)
General Comments 0
You need to be logged in to leave comments. Login now