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