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