##// END OF EJS Templates
Optimized threads list.
neko259 -
r163:e0408178 default
parent child Browse files
Show More
@@ -1,326 +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 thread_count = len(threads)
80 thread_count = threads.count()
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 249
250 250 replies = self._get_replies()
251 251 for reply in replies:
252 252 if reply.image:
253 253 images_count += 1
254 254
255 255 return images_count
256 256
257 257 def can_bump(self):
258 258 """Check if the thread can be bumped by replying"""
259 259
260 260 replies_count = self.get_reply_count() + 1
261 261
262 262 return replies_count <= settings.MAX_POSTS_PER_THREAD
263 263
264 264 def get_last_replies(self):
265 265 if settings.LAST_REPLIES_COUNT > 0:
266 266 reply_count = self.get_reply_count()
267 267
268 268 if reply_count > 0:
269 269 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
270 270 reply_count)
271 271 last_replies = self._get_replies()[reply_count
272 272 - reply_count_to_show:]
273 273
274 274 return last_replies
275 275
276 276
277 277 class User(models.Model):
278 278
279 279 user_id = models.CharField(max_length=50)
280 280 rank = models.IntegerField()
281 281
282 282 registration_time = models.DateTimeField()
283 283 last_access_time = models.DateTimeField()
284 284
285 285 fav_tags = models.ManyToManyField(Tag, null=True, blank=True)
286 286 fav_threads = models.ManyToManyField(Post, related_name='+', null=True,
287 287 blank=True)
288 288
289 289 def save_setting(self, name, value):
290 290 setting, created = Setting.objects.get_or_create(name=name, user=self)
291 291 setting.value = value
292 292 setting.save()
293 293
294 294 return setting
295 295
296 296 def get_setting(self, name):
297 297 if Setting.objects.filter(name=name, user=self).exists():
298 298 setting = Setting.objects.get(name=name, user=self)
299 299 setting_value = setting.value
300 300 else:
301 301 setting_value = None
302 302
303 303 return setting_value
304 304
305 305 def is_moderator(self):
306 306 return RANK_MODERATOR >= self.rank
307 307
308 308 def get_sorted_fav_tags(self):
309 309 return self.fav_tags.order_by('name')
310 310
311 311 def __unicode__(self):
312 312 return self.user_id + '(' + str(self.rank) + ')'
313 313
314 314
315 315 class Setting(models.Model):
316 316
317 317 name = models.CharField(max_length=50)
318 318 value = models.CharField(max_length=50)
319 319 user = models.ForeignKey(User)
320 320
321 321
322 322 class Ban(models.Model):
323 323 ip = models.GenericIPAddressField()
324 324
325 325 def __unicode__(self):
326 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 {% if thread.can_bump %}
26 <div class="post" id="{{thread.id}}">
25 {% if thread.bumpable %}
26 <div class="post" id="{{ thread.thread.id }}">
27 27 {% else %}
28 <div class="post dead_post" id="{{ thread.id }}">
28 <div class="post dead_post" id="{{ thread.thread.id }}">
29 29 {% endif %}
30 {% if thread.image %}
30 {% if thread.thread.image %}
31 31 <div class="image">
32 32 <a class="fancy"
33 href="{{ thread.image.url }}"><img
34 src="{{ thread.image.url_200x150 }}"
33 href="{{ thread.thread.image.url }}"><img
34 src="{{ thread.thread.image.url_200x150 }}"
35 35 alt="{% trans 'Post image' %}"
36 data-width="{{ thread.image_width }}"
37 data-height="{{ thread.image_height }}" />
36 data-width="{{ thread.thread.image_width }}"
37 data-height="{{ thread.thread.image_height }}" />
38 38 </a>
39 39 </div>
40 40 {% endif %}
41 41 <div class="message">
42 42 <div class="post-info">
43 <span class="title">{{ thread.title }}</span>
44 <a class="post_id" href="{% url 'thread' thread.id %}"
45 >(#{{ thread.id }})</a>
46 [{{ thread.pub_time }}]
47 [<a class="link" href="{% url 'thread' thread.id %}#form"
43 <span class="title">{{ thread.thread.title }}</span>
44 <a class="post_id" href="{% url 'thread' thread.thread.id %}"
45 >(#{{ thread.thread.id }})</a>
46 [{{ thread.thread.pub_time }}]
47 [<a class="link" href="{% url 'thread' thread.thread.id %}#form"
48 48 >{% trans "Reply" %}</a>]
49 49
50 50 {% if user.is_moderator %}
51 51 <span class="moderator_info">
52 [<a href="{% url 'delete' post_id=thread.id %}?next={{ request.path }}"
52 [<a href="{% url 'delete' post_id=thread.thread.id %}?next={{ request.path }}"
53 53 >{% trans 'Delete' %}</a>]
54 ({{ thread.poster_ip }})
55 [<a href="{% url 'ban' post_id=thread.id %}?next={{ request.path }}"
54 ({{ thread.thread.poster_ip }})
55 [<a href="{% url 'ban' post_id=thread.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 {{ thread.text.rendered|truncatewords_html:50 }}
61 {{ thread.thread.text.rendered|truncatewords_html:50 }}
62 62 {% endautoescape %}
63 63 </div>
64 64 <div class="metadata">
65 {{ thread.get_reply_count }} {% trans 'replies' %},
66 {{ thread.get_images_count }} {% trans 'images' %}.
65 {{ thread.thread.get_reply_count }} {% trans 'replies' %},
66 {{ thread.thread.get_images_count }} {% trans 'images' %}.
67 67 {% if thread.tags.exists %}
68 68 <span class="tags">{% trans 'Tags' %}:
69 {% for tag in thread.tags.all %}
69 {% for tag in thread.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 {% if thread.get_last_replies %}
78 {% if thread.thread.get_last_replies %}
79 79 <div class="last-replies">
80 {% for post in thread.get_last_replies %}
81 {% if thread.can_bump %}
80 {% for post in thread.thread.get_last_replies %}
81 {% if thread.bumpable %}
82 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 {% url 'thread' thread.id %}#{{ post.id }}">
101 {% url 'thread' 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,352 +1,355 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 threads = Post.objects.get_threads(page=int(page))
39 threads = []
40 for thread in Post.objects.get_threads(page=int(page)):
41 threads.append({'thread': thread,
42 'bumpable': thread.can_bump()})
40 43
41 44 context['threads'] = None if len(threads) == 0 else threads
42 45 context['form'] = form
43 46 context['pages'] = range(Post.objects.get_thread_page_count())
44 47
45 48 return render(request, 'boards/posting_general.html',
46 49 context)
47 50
48 51
49 52 def _new_post(request, form, thread_id=boards.models.NO_PARENT):
50 53 """Add a new post (in thread or as a reply)."""
51 54
52 55 ip = _get_client_ip(request)
53 56 is_banned = Ban.objects.filter(ip=ip).count() > 0
54 57
55 58 if is_banned:
56 59 return redirect(you_are_banned)
57 60
58 61 data = form.cleaned_data
59 62
60 63 title = data['title']
61 64 text = data['text']
62 65
63 66 if 'image' in data.keys():
64 67 image = data['image']
65 68 else:
66 69 image = None
67 70
68 71 tags = []
69 72
70 73 new_thread = thread_id == boards.models.NO_PARENT
71 74 if new_thread:
72 75 tag_strings = data['tags']
73 76
74 77 if tag_strings:
75 78 tag_strings = tag_strings.split(' ')
76 79 for tag_name in tag_strings:
77 80 tag_name = tag_name.strip()
78 81 if len(tag_name) > 0:
79 82 tag, created = Tag.objects.get_or_create(name=tag_name)
80 83 tags.append(tag)
81 84
82 85 # TODO Add a possibility to define a link image instead of an image file.
83 86 # If a link is given, download the image automatically.
84 87
85 88 post = Post.objects.create_post(title=title, text=text, ip=ip,
86 89 parent_id=thread_id, image=image,
87 90 tags=tags)
88 91
89 92 thread_to_show = (post.id if new_thread else thread_id)
90 93
91 94 if new_thread:
92 95 return redirect(thread, post_id=thread_to_show)
93 96 else:
94 97 return redirect(reverse(thread,
95 98 kwargs={'post_id': thread_to_show}) + '#'
96 99 + str(post.id))
97 100
98 101
99 102 def tag(request, tag_name, page=0):
100 103 """Get all tag threads (posts without a parent)."""
101 104
102 105 tag = get_object_or_404(Tag, name=tag_name)
103 106 threads = Post.objects.get_threads(tag=tag, page=int(page))
104 107
105 108 if request.method == 'POST':
106 109 form = ThreadForm(request.POST, request.FILES,
107 110 error_class=PlainErrorList)
108 111 if form.is_valid():
109 112 return _new_post(request, form)
110 113 else:
111 114 form = forms.ThreadForm(initial={'tags': tag_name},
112 115 error_class=PlainErrorList)
113 116
114 117 context = _init_default_context(request)
115 118 context['threads'] = None if len(threads) == 0 else threads
116 119 context['tag'] = tag_name
117 120 context['pages'] = range(Post.objects.get_thread_page_count(tag=tag))
118 121
119 122 context['form'] = form
120 123
121 124 return render(request, 'boards/posting_general.html',
122 125 context)
123 126
124 127
125 128 def thread(request, post_id):
126 129 """Get all thread posts"""
127 130
128 131 if utils.need_include_captcha(request):
129 132 postFormClass = PostCaptchaForm
130 133 kwargs = {'request': request}
131 134 else:
132 135 postFormClass = PostForm
133 136 kwargs = {}
134 137
135 138 if request.method == 'POST':
136 139 form = postFormClass(request.POST, request.FILES,
137 140 error_class=PlainErrorList, **kwargs)
138 141 form.session = request.session
139 142
140 143 if form.is_valid():
141 144 return _new_post(request, form, post_id)
142 145 else:
143 146 form = postFormClass(error_class=PlainErrorList, **kwargs)
144 147
145 148 posts = Post.objects.get_thread(post_id)
146 149
147 150 context = _init_default_context(request)
148 151
149 152 context['posts'] = posts
150 153 context['form'] = form
151 154 context['bumpable'] = posts[0].can_bump()
152 155
153 156 return render(request, 'boards/thread.html', context)
154 157
155 158
156 159 def login(request):
157 160 """Log in with user id"""
158 161
159 162 context = _init_default_context(request)
160 163
161 164 if request.method == 'POST':
162 165 form = LoginForm(request.POST, request.FILES,
163 166 error_class=PlainErrorList)
164 167 if form.is_valid():
165 168 user = User.objects.get(user_id=form.cleaned_data['user_id'])
166 169 request.session['user_id'] = user.id
167 170 return redirect(index)
168 171
169 172 else:
170 173 form = LoginForm()
171 174
172 175 context['form'] = form
173 176
174 177 return render(request, 'boards/login.html', context)
175 178
176 179
177 180 def settings(request):
178 181 """User's settings"""
179 182
180 183 context = _init_default_context(request)
181 184
182 185 if request.method == 'POST':
183 186 form = SettingsForm(request.POST)
184 187 if form.is_valid():
185 188 selected_theme = form.cleaned_data['theme']
186 189
187 190 user = _get_user(request)
188 191 user.save_setting('theme', selected_theme)
189 192
190 193 return redirect(settings)
191 194 else:
192 195 selected_theme = _get_theme(request)
193 196 form = SettingsForm(initial={'theme': selected_theme})
194 197 context['form'] = form
195 198
196 199 return render(request, 'boards/settings.html', context)
197 200
198 201
199 202 def all_tags(request):
200 203 """All tags list"""
201 204
202 205 context = _init_default_context(request)
203 206 context['all_tags'] = Tag.objects.get_not_empty_tags()
204 207
205 208 return render(request, 'boards/tags.html', context)
206 209
207 210
208 211 def jump_to_post(request, post_id):
209 212 """Determine thread in which the requested post is and open it's page"""
210 213
211 214 post = get_object_or_404(Post, id=post_id)
212 215
213 216 if boards.models.NO_PARENT == post.parent:
214 217 return redirect(thread, post_id=post.id)
215 218 else:
216 219 parent_thread = get_object_or_404(Post, id=post.parent)
217 220 return redirect(reverse(thread, kwargs={'post_id': parent_thread.id})
218 221 + '#' + str(post.id))
219 222
220 223
221 224 def authors(request):
222 225 context = _init_default_context(request)
223 226 context['authors'] = boards.authors.authors
224 227
225 228 return render(request, 'boards/authors.html', context)
226 229
227 230
228 231 def delete(request, post_id):
229 232 user = _get_user(request)
230 233 post = get_object_or_404(Post, id=post_id)
231 234
232 235 if user.is_moderator():
233 236 # TODO Show confirmation page before deletion
234 237 Post.objects.delete_post(post)
235 238
236 239 if NO_PARENT == post.parent:
237 240 return _redirect_to_next(request)
238 241 else:
239 242 return redirect(thread, post_id=post.parent)
240 243
241 244
242 245 def ban(request, post_id):
243 246 user = _get_user(request)
244 247 post = get_object_or_404(Post, id=post_id)
245 248
246 249 if user.is_moderator():
247 250 # TODO Show confirmation page before ban
248 251 Ban.objects.get_or_create(ip=post.poster_ip)
249 252
250 253 return _redirect_to_next(request)
251 254
252 255
253 256 def you_are_banned(request):
254 257 context = _init_default_context(request)
255 258 return render(request, 'boards/staticpages/banned.html', context)
256 259
257 260
258 261 def page_404(request):
259 262 context = _init_default_context(request)
260 263 return render(request, 'boards/404.html', context)
261 264
262 265
263 266 def tag_subscribe(request, tag_name):
264 267 user = _get_user(request)
265 268 tag = get_object_or_404(Tag, name=tag_name)
266 269
267 270 if not tag in user.fav_tags.all():
268 271 user.fav_tags.add(tag)
269 272
270 273 return redirect(all_tags)
271 274
272 275
273 276 def tag_unsubscribe(request, tag_name):
274 277 user = _get_user(request)
275 278 tag = get_object_or_404(Tag, name=tag_name)
276 279
277 280 if tag in user.fav_tags.all():
278 281 user.fav_tags.remove(tag)
279 282
280 283 return redirect(all_tags)
281 284
282 285
283 286 def static_page(request, name):
284 287 context = _init_default_context(request)
285 288 return render(request, 'boards/staticpages/' + name + '.html', context)
286 289
287 290
288 291 def _get_theme(request, user=None):
289 292 """Get user's CSS theme"""
290 293
291 294 if not user:
292 295 user = _get_user(request)
293 296 theme = user.get_setting('theme')
294 297 if not theme:
295 298 theme = neboard.settings.DEFAULT_THEME
296 299
297 300 return theme
298 301
299 302
300 303 def _get_client_ip(request):
301 304 x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
302 305 if x_forwarded_for:
303 306 ip = x_forwarded_for.split(',')[-1].strip()
304 307 else:
305 308 ip = request.META.get('REMOTE_ADDR')
306 309 return ip
307 310
308 311
309 312 def _init_default_context(request):
310 313 """Create context with default values that are used in most views"""
311 314
312 315 context = RequestContext(request)
313 316
314 317 user = _get_user(request)
315 318 context['user'] = user
316 319 context['tags'] = user.get_sorted_fav_tags()
317 320 context['theme'] = _get_theme(request, user)
318 321
319 322 return context
320 323
321 324
322 325 def _get_user(request):
323 326 """Get current user from the session"""
324 327
325 328 session = request.session
326 329 if not 'user_id' in session:
327 330 request.session.save()
328 331
329 332 md5 = hashlib.md5()
330 333 md5.update(session.session_key)
331 334 new_id = md5.hexdigest()
332 335
333 336 time_now = timezone.now()
334 337 user = User.objects.create(user_id=new_id, rank=RANK_USER,
335 338 registration_time=time_now,
336 339 last_access_time=time_now)
337 340
338 341 session['user_id'] = user.id
339 342 else:
340 343 user = User.objects.get(id=session['user_id'])
341 344 user.last_access_time = timezone.now()
342 345 user.save()
343 346
344 347 return user
345 348
346 349
347 350 def _redirect_to_next(request):
348 351 if 'next' in request.GET:
349 352 next_page = request.GET['next']
350 353 return HttpResponseRedirect(next_page)
351 354 else:
352 355 return redirect(index)
General Comments 0
You need to be logged in to leave comments. Login now