##// END OF EJS Templates
Added django management command to clean old users. Cleaned up failing tests. Fixed error when trying to get a non-existent post
neko259 -
r689:3d81370e 1.8-dev
parent child Browse files
Show More
@@ -0,0 +1,1 b''
1 __author__ = 'vurdalak'
@@ -0,0 +1,1 b''
1 __author__ = 'vurdalak'
@@ -0,0 +1,29 b''
1 from datetime import datetime, timedelta
2 from django.core.management import BaseCommand
3 from django.db import transaction
4 from django.db.models import Count
5 from boards.models import User, Post
6
7 __author__ = 'neko259'
8
9 OLD_USER_AGE_DAYS = 90
10
11
12 class Command(BaseCommand):
13 help = 'Removes empty users (that don\'t have posts or tags'
14
15 @transaction.atomic
16 def handle(self, *args, **options):
17 old_registration_date = datetime.now().date() - timedelta(
18 OLD_USER_AGE_DAYS)
19
20 old_users = User.objects.annotate(tags_count=Count('fav_tags')).filter(
21 tags_count=0).filter(registration_time__lt=old_registration_date)
22 deleted_users = 0
23 for user in old_users:
24 if not Post.objects.filter(user=user).exists():
25 self.stdout.write('Deleting user %s' % user.user_id)
26 user.delete()
27 deleted_users += 1
28
29 self.stdout.write('Deleted %d users' % deleted_users) No newline at end of file
@@ -1,260 +1,259 b''
1 # coding=utf-8
1 # coding=utf-8
2 import time
2 import time
3 import logging
3 import logging
4 from django.core.paginator import Paginator
4 from django.core.paginator import Paginator
5
5
6 from django.test import TestCase
6 from django.test import TestCase
7 from django.test.client import Client
7 from django.test.client import Client
8 from django.core.urlresolvers import reverse, NoReverseMatch
8 from django.core.urlresolvers import reverse, NoReverseMatch
9
9
10 from boards.models import Post, Tag, Thread
10 from boards.models import Post, Tag, Thread
11 from boards import urls
11 from boards import urls
12 from neboard import settings
12 from neboard import settings
13
13
14 PAGE_404 = 'boards/404.html'
14 PAGE_404 = 'boards/404.html'
15
15
16 TEST_TEXT = 'test text'
16 TEST_TEXT = 'test text'
17
17
18 NEW_THREAD_PAGE = '/'
18 NEW_THREAD_PAGE = '/'
19 THREAD_PAGE_ONE = '/thread/1/'
19 THREAD_PAGE_ONE = '/thread/1/'
20 THREAD_PAGE = '/thread/'
20 THREAD_PAGE = '/thread/'
21 TAG_PAGE = '/tag/'
21 TAG_PAGE = '/tag/'
22 HTTP_CODE_REDIRECT = 302
22 HTTP_CODE_REDIRECT = 302
23 HTTP_CODE_OK = 200
23 HTTP_CODE_OK = 200
24 HTTP_CODE_NOT_FOUND = 404
24 HTTP_CODE_NOT_FOUND = 404
25
25
26 logger = logging.getLogger(__name__)
26 logger = logging.getLogger(__name__)
27
27
28
28
29 class PostTests(TestCase):
29 class PostTests(TestCase):
30
30
31 def _create_post(self):
31 def _create_post(self):
32 return Post.objects.create_post(title='title',
32 return Post.objects.create_post(title='title',
33 text='text')
33 text='text')
34
34
35 def test_post_add(self):
35 def test_post_add(self):
36 """Test adding post"""
36 """Test adding post"""
37
37
38 post = self._create_post()
38 post = self._create_post()
39
39
40 self.assertIsNotNone(post, 'No post was created')
40 self.assertIsNotNone(post, 'No post was created')
41
41
42 def test_delete_post(self):
42 def test_delete_post(self):
43 """Test post deletion"""
43 """Test post deletion"""
44
44
45 post = self._create_post()
45 post = self._create_post()
46 post_id = post.id
46 post_id = post.id
47
47
48 Post.objects.delete_post(post)
48 Post.objects.delete_post(post)
49
49
50 self.assertFalse(Post.objects.filter(id=post_id).exists())
50 self.assertFalse(Post.objects.filter(id=post_id).exists())
51
51
52 def test_delete_thread(self):
52 def test_delete_thread(self):
53 """Test thread deletion"""
53 """Test thread deletion"""
54
54
55 opening_post = self._create_post()
55 opening_post = self._create_post()
56 thread = opening_post.get_thread()
56 thread = opening_post.get_thread()
57 reply = Post.objects.create_post("", "", thread=thread)
57 reply = Post.objects.create_post("", "", thread=thread)
58
58
59 thread.delete_with_posts()
59 thread.delete_with_posts()
60
60
61 self.assertFalse(Post.objects.filter(id=reply.id).exists())
61 self.assertFalse(Post.objects.filter(id=reply.id).exists())
62
62
63 def test_post_to_thread(self):
63 def test_post_to_thread(self):
64 """Test adding post to a thread"""
64 """Test adding post to a thread"""
65
65
66 op = self._create_post()
66 op = self._create_post()
67 post = Post.objects.create_post("", "", thread=op.get_thread())
67 post = Post.objects.create_post("", "", thread=op.get_thread())
68
68
69 self.assertIsNotNone(post, 'Reply to thread wasn\'t created')
69 self.assertIsNotNone(post, 'Reply to thread wasn\'t created')
70 self.assertEqual(op.get_thread().last_edit_time, post.pub_time,
70 self.assertEqual(op.get_thread().last_edit_time, post.pub_time,
71 'Post\'s create time doesn\'t match thread last edit'
71 'Post\'s create time doesn\'t match thread last edit'
72 ' time')
72 ' time')
73
73
74 def test_delete_posts_by_ip(self):
74 def test_delete_posts_by_ip(self):
75 """Test deleting posts with the given ip"""
75 """Test deleting posts with the given ip"""
76
76
77 post = self._create_post()
77 post = self._create_post()
78 post_id = post.id
78 post_id = post.id
79
79
80 Post.objects.delete_posts_by_ip('0.0.0.0')
80 Post.objects.delete_posts_by_ip('0.0.0.0')
81
81
82 self.assertFalse(Post.objects.filter(id=post_id).exists())
82 self.assertFalse(Post.objects.filter(id=post_id).exists())
83
83
84 def test_get_thread(self):
84 def test_get_thread(self):
85 """Test getting all posts of a thread"""
85 """Test getting all posts of a thread"""
86
86
87 opening_post = self._create_post()
87 opening_post = self._create_post()
88
88
89 for i in range(0, 2):
89 for i in range(0, 2):
90 Post.objects.create_post('title', 'text',
90 Post.objects.create_post('title', 'text',
91 thread=opening_post.get_thread())
91 thread=opening_post.get_thread())
92
92
93 thread = opening_post.get_thread()
93 thread = opening_post.get_thread()
94
94
95 self.assertEqual(3, thread.replies.count())
95 self.assertEqual(3, thread.replies.count())
96
96
97 def test_create_post_with_tag(self):
97 def test_create_post_with_tag(self):
98 """Test adding tag to post"""
98 """Test adding tag to post"""
99
99
100 tag = Tag.objects.create(name='test_tag')
100 tag = Tag.objects.create(name='test_tag')
101 post = Post.objects.create_post(title='title', text='text', tags=[tag])
101 post = Post.objects.create_post(title='title', text='text', tags=[tag])
102
102
103 thread = post.get_thread()
103 thread = post.get_thread()
104 self.assertIsNotNone(post, 'Post not created')
104 self.assertIsNotNone(post, 'Post not created')
105 self.assertTrue(tag in thread.tags.all(), 'Tag not added to thread')
105 self.assertTrue(tag in thread.tags.all(), 'Tag not added to thread')
106 self.assertTrue(thread in tag.threads.all(), 'Thread not added to tag')
106 self.assertTrue(thread in tag.threads.all(), 'Thread not added to tag')
107
107
108 def test_thread_max_count(self):
108 def test_thread_max_count(self):
109 """Test deletion of old posts when the max thread count is reached"""
109 """Test deletion of old posts when the max thread count is reached"""
110
110
111 for i in range(settings.MAX_THREAD_COUNT + 1):
111 for i in range(settings.MAX_THREAD_COUNT + 1):
112 self._create_post()
112 self._create_post()
113
113
114 self.assertEqual(settings.MAX_THREAD_COUNT,
114 self.assertEqual(settings.MAX_THREAD_COUNT,
115 len(Thread.objects.filter(archived=False)))
115 len(Thread.objects.filter(archived=False)))
116
116
117 def test_pages(self):
117 def test_pages(self):
118 """Test that the thread list is properly split into pages"""
118 """Test that the thread list is properly split into pages"""
119
119
120 for i in range(settings.MAX_THREAD_COUNT):
120 for i in range(settings.MAX_THREAD_COUNT):
121 self._create_post()
121 self._create_post()
122
122
123 all_threads = Thread.objects.filter(archived=False)
123 all_threads = Thread.objects.filter(archived=False)
124
124
125 paginator = Paginator(Thread.objects.filter(archived=False),
125 paginator = Paginator(Thread.objects.filter(archived=False),
126 settings.THREADS_PER_PAGE)
126 settings.THREADS_PER_PAGE)
127 posts_in_second_page = paginator.page(2).object_list
127 posts_in_second_page = paginator.page(2).object_list
128 first_post = posts_in_second_page[0]
128 first_post = posts_in_second_page[0]
129
129
130 self.assertEqual(all_threads[settings.THREADS_PER_PAGE].id,
130 self.assertEqual(all_threads[settings.THREADS_PER_PAGE].id,
131 first_post.id)
131 first_post.id)
132
132
133 def test_linked_tag(self):
133 def test_linked_tag(self):
134 """Test adding a linked tag"""
134 """Test adding a linked tag"""
135
135
136 linked_tag = Tag.objects.create(name=u'tag1')
136 linked_tag = Tag.objects.create(name=u'tag1')
137 tag = Tag.objects.create(name=u'tag2', linked=linked_tag)
137 tag = Tag.objects.create(name=u'tag2', linked=linked_tag)
138
138
139 post = Post.objects.create_post("", "", tags=[tag])
139 post = Post.objects.create_post("", "", tags=[tag])
140
140
141 self.assertTrue(linked_tag in post.get_thread().tags.all(),
141 self.assertTrue(linked_tag in post.get_thread().tags.all(),
142 'Linked tag was not added')
142 'Linked tag was not added')
143
143
144
144
145 class PagesTest(TestCase):
145 class PagesTest(TestCase):
146
146
147 def test_404(self):
147 def test_404(self):
148 """Test receiving error 404 when opening a non-existent page"""
148 """Test receiving error 404 when opening a non-existent page"""
149
149
150 tag_name = u'test_tag'
150 tag_name = u'test_tag'
151 tag = Tag.objects.create(name=tag_name)
151 tag = Tag.objects.create(name=tag_name)
152 client = Client()
152 client = Client()
153
153
154 Post.objects.create_post('title', TEST_TEXT, tags=[tag])
154 Post.objects.create_post('title', TEST_TEXT, tags=[tag])
155
155
156 existing_post_id = Post.objects.all()[0].id
156 existing_post_id = Post.objects.all()[0].id
157 response_existing = client.get(THREAD_PAGE + str(existing_post_id) +
157 response_existing = client.get(THREAD_PAGE + str(existing_post_id) +
158 '/')
158 '/')
159 self.assertEqual(HTTP_CODE_OK, response_existing.status_code,
159 self.assertEqual(HTTP_CODE_OK, response_existing.status_code,
160 u'Cannot open existing thread')
160 u'Cannot open existing thread')
161
161
162 response_not_existing = client.get(THREAD_PAGE + str(
162 response_not_existing = client.get(THREAD_PAGE + str(
163 existing_post_id + 1) + '/')
163 existing_post_id + 1) + '/')
164 self.assertEqual(PAGE_404,
164 self.assertEqual(PAGE_404, response_not_existing.templates[0].name,
165 response_not_existing.templates[0].name,
166 u'Not existing thread is opened')
165 u'Not existing thread is opened')
167
166
168 response_existing = client.get(TAG_PAGE + tag_name + '/')
167 response_existing = client.get(TAG_PAGE + tag_name + '/')
169 self.assertEqual(HTTP_CODE_OK,
168 self.assertEqual(HTTP_CODE_OK,
170 response_existing.status_code,
169 response_existing.status_code,
171 u'Cannot open existing tag')
170 u'Cannot open existing tag')
172
171
173 response_not_existing = client.get(TAG_PAGE + u'not_tag' + '/')
172 response_not_existing = client.get(TAG_PAGE + u'not_tag' + '/')
174 self.assertEqual(PAGE_404,
173 self.assertEqual(PAGE_404,
175 response_not_existing.templates[0].name,
174 response_not_existing.templates[0].name,
176 u'Not existing tag is opened')
175 u'Not existing tag is opened')
177
176
178 reply_id = Post.objects.create_post('', TEST_TEXT,
177 reply_id = Post.objects.create_post('', TEST_TEXT,
179 thread=Post.objects.all()[0]
178 thread=Post.objects.all()[0]
180 .thread)
179 .get_thread())
181 response_not_existing = client.get(THREAD_PAGE + str(
180 response_not_existing = client.get(THREAD_PAGE + str(
182 reply_id) + '/')
181 reply_id) + '/')
183 self.assertEqual(PAGE_404,
182 self.assertEqual(PAGE_404,
184 response_not_existing.templates[0].name,
183 response_not_existing.templates[0].name,
185 u'Reply is opened as a thread')
184 u'Reply is opened as a thread')
186
185
187
186
188 class FormTest(TestCase):
187 class FormTest(TestCase):
189 def test_post_validation(self):
188 def test_post_validation(self):
190 # Disable captcha for the test
189 # Disable captcha for the test
191 captcha_enabled = settings.ENABLE_CAPTCHA
190 captcha_enabled = settings.ENABLE_CAPTCHA
192 settings.ENABLE_CAPTCHA = False
191 settings.ENABLE_CAPTCHA = False
193
192
194 client = Client()
193 client = Client()
195
194
196 valid_tags = u'tag1 tag_2 тег_3'
195 valid_tags = u'tag1 tag_2 тег_3'
197 invalid_tags = u'$%_356 ---'
196 invalid_tags = u'$%_356 ---'
198
197
199 response = client.post(NEW_THREAD_PAGE, {'title': 'test title',
198 response = client.post(NEW_THREAD_PAGE, {'title': 'test title',
200 'text': TEST_TEXT,
199 'text': TEST_TEXT,
201 'tags': valid_tags})
200 'tags': valid_tags})
202 self.assertEqual(response.status_code, HTTP_CODE_REDIRECT,
201 self.assertEqual(response.status_code, HTTP_CODE_REDIRECT,
203 msg='Posting new message failed: got code ' +
202 msg='Posting new message failed: got code ' +
204 str(response.status_code))
203 str(response.status_code))
205
204
206 self.assertEqual(1, Post.objects.count(),
205 self.assertEqual(1, Post.objects.count(),
207 msg='No posts were created')
206 msg='No posts were created')
208
207
209 client.post(NEW_THREAD_PAGE, {'text': TEST_TEXT,
208 client.post(NEW_THREAD_PAGE, {'text': TEST_TEXT,
210 'tags': invalid_tags})
209 'tags': invalid_tags})
211 self.assertEqual(1, Post.objects.count(), msg='The validation passed '
210 self.assertEqual(1, Post.objects.count(), msg='The validation passed '
212 'where it should fail')
211 'where it should fail')
213
212
214 # Change posting delay so we don't have to wait for 30 seconds or more
213 # Change posting delay so we don't have to wait for 30 seconds or more
215 old_posting_delay = settings.POSTING_DELAY
214 old_posting_delay = settings.POSTING_DELAY
216 # Wait fot the posting delay or we won't be able to post
215 # Wait fot the posting delay or we won't be able to post
217 settings.POSTING_DELAY = 1
216 settings.POSTING_DELAY = 1
218 time.sleep(settings.POSTING_DELAY + 1)
217 time.sleep(settings.POSTING_DELAY + 1)
219 response = client.post(THREAD_PAGE_ONE, {'text': TEST_TEXT,
218 response = client.post(THREAD_PAGE_ONE, {'text': TEST_TEXT,
220 'tags': valid_tags})
219 'tags': valid_tags})
221 self.assertEqual(HTTP_CODE_REDIRECT, response.status_code,
220 self.assertEqual(HTTP_CODE_REDIRECT, response.status_code,
222 msg=u'Posting new message failed: got code ' +
221 msg=u'Posting new message failed: got code ' +
223 str(response.status_code))
222 str(response.status_code))
224 # Restore posting delay
223 # Restore posting delay
225 settings.POSTING_DELAY = old_posting_delay
224 settings.POSTING_DELAY = old_posting_delay
226
225
227 self.assertEqual(2, Post.objects.count(),
226 self.assertEqual(2, Post.objects.count(),
228 msg=u'No posts were created')
227 msg=u'No posts were created')
229
228
230 # Restore captcha setting
229 # Restore captcha setting
231 settings.ENABLE_CAPTCHA = captcha_enabled
230 settings.ENABLE_CAPTCHA = captcha_enabled
232
231
233
232
234 class ViewTest(TestCase):
233 class ViewTest(TestCase):
235
234
236 def test_all_views(self):
235 def test_all_views(self):
237 '''
236 '''
238 Try opening all views defined in ulrs.py that don't need additional
237 Try opening all views defined in ulrs.py that don't need additional
239 parameters
238 parameters
240 '''
239 '''
241
240
242 client = Client()
241 client = Client()
243 for url in urls.urlpatterns:
242 for url in urls.urlpatterns:
244 try:
243 try:
245 view_name = url.name
244 view_name = url.name
246 logger.debug('Testing view %s' % view_name)
245 logger.debug('Testing view %s' % view_name)
247
246
248 try:
247 try:
249 response = client.get(reverse(view_name))
248 response = client.get(reverse(view_name))
250
249
251 self.assertEqual(HTTP_CODE_OK, response.status_code,
250 self.assertEqual(HTTP_CODE_OK, response.status_code,
252 '%s view not opened' % view_name)
251 '%s view not opened' % view_name)
253 except NoReverseMatch:
252 except NoReverseMatch:
254 # This view just needs additional arguments
253 # This view just needs additional arguments
255 pass
254 pass
256 except Exception, e:
255 except Exception, e:
257 self.fail('Got exception %s at %s view' % (e, view_name))
256 self.fail('Got exception %s at %s view' % (e, view_name))
258 except AttributeError:
257 except AttributeError:
259 # This is normal, some views do not have names
258 # This is normal, some views do not have names
260 pass
259 pass
@@ -1,248 +1,245 b''
1 from datetime import datetime
1 from datetime import datetime
2 import json
2 import json
3 import logging
3 import logging
4 from django.db import transaction
4 from django.db import transaction
5 from django.http import HttpResponse
5 from django.http import HttpResponse
6 from django.shortcuts import get_object_or_404, render
6 from django.shortcuts import get_object_or_404, render
7 from django.template import RequestContext
7 from django.template import RequestContext
8 from django.utils import timezone
8 from django.utils import timezone
9 from django.core import serializers
9 from django.core import serializers
10
10
11 from boards.forms import PostForm, PlainErrorList
11 from boards.forms import PostForm, PlainErrorList
12 from boards.models import Post, Thread, Tag
12 from boards.models import Post, Thread, Tag
13 from boards.utils import datetime_to_epoch
13 from boards.utils import datetime_to_epoch
14 from boards.views.thread import ThreadView
14 from boards.views.thread import ThreadView
15
15
16 __author__ = 'neko259'
16 __author__ = 'neko259'
17
17
18 PARAMETER_TRUNCATED = 'truncated'
18 PARAMETER_TRUNCATED = 'truncated'
19 PARAMETER_TAG = 'tag'
19 PARAMETER_TAG = 'tag'
20 PARAMETER_OFFSET = 'offset'
20 PARAMETER_OFFSET = 'offset'
21 PARAMETER_DIFF_TYPE = 'type'
21 PARAMETER_DIFF_TYPE = 'type'
22
22
23 DIFF_TYPE_HTML = 'html'
23 DIFF_TYPE_HTML = 'html'
24 DIFF_TYPE_JSON = 'json'
24 DIFF_TYPE_JSON = 'json'
25
25
26 STATUS_OK = 'ok'
26 STATUS_OK = 'ok'
27 STATUS_ERROR = 'error'
27 STATUS_ERROR = 'error'
28
28
29 logger = logging.getLogger(__name__)
29 logger = logging.getLogger(__name__)
30
30
31
31
32 @transaction.atomic
32 @transaction.atomic
33 def api_get_threaddiff(request, thread_id, last_update_time):
33 def api_get_threaddiff(request, thread_id, last_update_time):
34 """
34 """
35 Gets posts that were changed or added since time
35 Gets posts that were changed or added since time
36 """
36 """
37
37
38 thread = get_object_or_404(Post, id=thread_id).get_thread()
38 thread = get_object_or_404(Post, id=thread_id).get_thread()
39
39
40 logger.info('Getting thread #%s diff since %s' % (thread_id,
41 last_update_time))
42
43 filter_time = datetime.fromtimestamp(float(last_update_time) / 1000000,
40 filter_time = datetime.fromtimestamp(float(last_update_time) / 1000000,
44 timezone.get_current_timezone())
41 timezone.get_current_timezone())
45
42
46 json_data = {
43 json_data = {
47 'added': [],
44 'added': [],
48 'updated': [],
45 'updated': [],
49 'last_update': None,
46 'last_update': None,
50 }
47 }
51 added_posts = Post.objects.filter(thread_new=thread,
48 added_posts = Post.objects.filter(thread_new=thread,
52 pub_time__gt=filter_time) \
49 pub_time__gt=filter_time) \
53 .order_by('pub_time')
50 .order_by('pub_time')
54 updated_posts = Post.objects.filter(thread_new=thread,
51 updated_posts = Post.objects.filter(thread_new=thread,
55 pub_time__lte=filter_time,
52 pub_time__lte=filter_time,
56 last_edit_time__gt=filter_time)
53 last_edit_time__gt=filter_time)
57
54
58 diff_type = DIFF_TYPE_HTML
55 diff_type = DIFF_TYPE_HTML
59 if PARAMETER_DIFF_TYPE in request.GET:
56 if PARAMETER_DIFF_TYPE in request.GET:
60 diff_type = request.GET[PARAMETER_DIFF_TYPE]
57 diff_type = request.GET[PARAMETER_DIFF_TYPE]
61
58
62 for post in added_posts:
59 for post in added_posts:
63 json_data['added'].append(_get_post_data(post.id, diff_type, request))
60 json_data['added'].append(_get_post_data(post.id, diff_type, request))
64 for post in updated_posts:
61 for post in updated_posts:
65 json_data['updated'].append(_get_post_data(post.id, diff_type, request))
62 json_data['updated'].append(_get_post_data(post.id, diff_type, request))
66 json_data['last_update'] = datetime_to_epoch(thread.last_edit_time)
63 json_data['last_update'] = datetime_to_epoch(thread.last_edit_time)
67
64
68 return HttpResponse(content=json.dumps(json_data))
65 return HttpResponse(content=json.dumps(json_data))
69
66
70
67
71 def api_add_post(request, opening_post_id):
68 def api_add_post(request, opening_post_id):
72 """
69 """
73 Adds a post and return the JSON response for it
70 Adds a post and return the JSON response for it
74 """
71 """
75
72
76 opening_post = get_object_or_404(Post, id=opening_post_id)
73 opening_post = get_object_or_404(Post, id=opening_post_id)
77
74
78 logger.info('Adding post via api...')
75 logger.info('Adding post via api...')
79
76
80 status = STATUS_OK
77 status = STATUS_OK
81 errors = []
78 errors = []
82
79
83 if request.method == 'POST':
80 if request.method == 'POST':
84 form = PostForm(request.POST, request.FILES, error_class=PlainErrorList)
81 form = PostForm(request.POST, request.FILES, error_class=PlainErrorList)
85 form.session = request.session
82 form.session = request.session
86
83
87 if form.need_to_ban:
84 if form.need_to_ban:
88 # Ban user because he is suspected to be a bot
85 # Ban user because he is suspected to be a bot
89 # _ban_current_user(request)
86 # _ban_current_user(request)
90 status = STATUS_ERROR
87 status = STATUS_ERROR
91 if form.is_valid():
88 if form.is_valid():
92 post = ThreadView().new_post(request, form, opening_post,
89 post = ThreadView().new_post(request, form, opening_post,
93 html_response=False)
90 html_response=False)
94 if not post:
91 if not post:
95 status = STATUS_ERROR
92 status = STATUS_ERROR
96 else:
93 else:
97 logger.info('Added post #%d via api.' % post.id)
94 logger.info('Added post #%d via api.' % post.id)
98 else:
95 else:
99 status = STATUS_ERROR
96 status = STATUS_ERROR
100 errors = form.as_json_errors()
97 errors = form.as_json_errors()
101
98
102 response = {
99 response = {
103 'status': status,
100 'status': status,
104 'errors': errors,
101 'errors': errors,
105 }
102 }
106
103
107 return HttpResponse(content=json.dumps(response))
104 return HttpResponse(content=json.dumps(response))
108
105
109
106
110 def get_post(request, post_id):
107 def get_post(request, post_id):
111 """
108 """
112 Gets the html of a post. Used for popups. Post can be truncated if used
109 Gets the html of a post. Used for popups. Post can be truncated if used
113 in threads list with 'truncated' get parameter.
110 in threads list with 'truncated' get parameter.
114 """
111 """
115
112
116 logger.info('Getting post #%s' % post_id)
113 logger.info('Getting post #%s' % post_id)
117
114
118 post = get_object_or_404(Post, id=post_id)
115 post = get_object_or_404(Post, id=post_id)
119
116
120 context = RequestContext(request)
117 context = RequestContext(request)
121 context['post'] = post
118 context['post'] = post
122 if PARAMETER_TRUNCATED in request.GET:
119 if PARAMETER_TRUNCATED in request.GET:
123 context[PARAMETER_TRUNCATED] = True
120 context[PARAMETER_TRUNCATED] = True
124
121
125 return render(request, 'boards/api_post.html', context)
122 return render(request, 'boards/api_post.html', context)
126
123
127
124
128 # TODO Test this
125 # TODO Test this
129 def api_get_threads(request, count):
126 def api_get_threads(request, count):
130 """
127 """
131 Gets the JSON thread opening posts list.
128 Gets the JSON thread opening posts list.
132 Parameters that can be used for filtering:
129 Parameters that can be used for filtering:
133 tag, offset (from which thread to get results)
130 tag, offset (from which thread to get results)
134 """
131 """
135
132
136 if PARAMETER_TAG in request.GET:
133 if PARAMETER_TAG in request.GET:
137 tag_name = request.GET[PARAMETER_TAG]
134 tag_name = request.GET[PARAMETER_TAG]
138 if tag_name is not None:
135 if tag_name is not None:
139 tag = get_object_or_404(Tag, name=tag_name)
136 tag = get_object_or_404(Tag, name=tag_name)
140 threads = tag.threads.filter(archived=False)
137 threads = tag.threads.filter(archived=False)
141 else:
138 else:
142 threads = Thread.objects.filter(archived=False)
139 threads = Thread.objects.filter(archived=False)
143
140
144 if PARAMETER_OFFSET in request.GET:
141 if PARAMETER_OFFSET in request.GET:
145 offset = request.GET[PARAMETER_OFFSET]
142 offset = request.GET[PARAMETER_OFFSET]
146 offset = int(offset) if offset is not None else 0
143 offset = int(offset) if offset is not None else 0
147 else:
144 else:
148 offset = 0
145 offset = 0
149
146
150 threads = threads.order_by('-bump_time')
147 threads = threads.order_by('-bump_time')
151 threads = threads[offset:offset + int(count)]
148 threads = threads[offset:offset + int(count)]
152
149
153 opening_posts = []
150 opening_posts = []
154 for thread in threads:
151 for thread in threads:
155 opening_post = thread.get_opening_post()
152 opening_post = thread.get_opening_post()
156
153
157 # TODO Add tags, replies and images count
154 # TODO Add tags, replies and images count
158 opening_posts.append(_get_post_data(opening_post.id,
155 opening_posts.append(_get_post_data(opening_post.id,
159 include_last_update=True))
156 include_last_update=True))
160
157
161 return HttpResponse(content=json.dumps(opening_posts))
158 return HttpResponse(content=json.dumps(opening_posts))
162
159
163
160
164 # TODO Test this
161 # TODO Test this
165 def api_get_tags(request):
162 def api_get_tags(request):
166 """
163 """
167 Gets all tags or user tags.
164 Gets all tags or user tags.
168 """
165 """
169
166
170 # TODO Get favorite tags for the given user ID
167 # TODO Get favorite tags for the given user ID
171
168
172 tags = Tag.objects.get_not_empty_tags()
169 tags = Tag.objects.get_not_empty_tags()
173 tag_names = []
170 tag_names = []
174 for tag in tags:
171 for tag in tags:
175 tag_names.append(tag.name)
172 tag_names.append(tag.name)
176
173
177 return HttpResponse(content=json.dumps(tag_names))
174 return HttpResponse(content=json.dumps(tag_names))
178
175
179
176
180 # TODO The result can be cached by the thread last update time
177 # TODO The result can be cached by the thread last update time
181 # TODO Test this
178 # TODO Test this
182 def api_get_thread_posts(request, opening_post_id):
179 def api_get_thread_posts(request, opening_post_id):
183 """
180 """
184 Gets the JSON array of thread posts
181 Gets the JSON array of thread posts
185 """
182 """
186
183
187 opening_post = get_object_or_404(Post, id=opening_post_id)
184 opening_post = get_object_or_404(Post, id=opening_post_id)
188 thread = opening_post.get_thread()
185 thread = opening_post.get_thread()
189 posts = thread.get_replies()
186 posts = thread.get_replies()
190
187
191 json_data = {
188 json_data = {
192 'posts': [],
189 'posts': [],
193 'last_update': None,
190 'last_update': None,
194 }
191 }
195 json_post_list = []
192 json_post_list = []
196
193
197 for post in posts:
194 for post in posts:
198 json_post_list.append(_get_post_data(post.id))
195 json_post_list.append(_get_post_data(post.id))
199 json_data['last_update'] = datetime_to_epoch(thread.last_edit_time)
196 json_data['last_update'] = datetime_to_epoch(thread.last_edit_time)
200 json_data['posts'] = json_post_list
197 json_data['posts'] = json_post_list
201
198
202 return HttpResponse(content=json.dumps(json_data))
199 return HttpResponse(content=json.dumps(json_data))
203
200
204
201
205 def api_get_post(request, post_id):
202 def api_get_post(request, post_id):
206 """
203 """
207 Gets the JSON of a post. This can be
204 Gets the JSON of a post. This can be
208 used as and API for external clients.
205 used as and API for external clients.
209 """
206 """
210
207
211 post = get_object_or_404(Post, id=post_id)
208 post = get_object_or_404(Post, id=post_id)
212
209
213 json = serializers.serialize("json", [post], fields=(
210 json = serializers.serialize("json", [post], fields=(
214 "pub_time", "_text_rendered", "title", "text", "image",
211 "pub_time", "_text_rendered", "title", "text", "image",
215 "image_width", "image_height", "replies", "tags"
212 "image_width", "image_height", "replies", "tags"
216 ))
213 ))
217
214
218 return HttpResponse(content=json)
215 return HttpResponse(content=json)
219
216
220
217
221 def get_tag_popularity(request, tag_name):
218 def get_tag_popularity(request, tag_name):
222 tag = get_object_or_404(Tag, name=tag_name)
219 tag = get_object_or_404(Tag, name=tag_name)
223
220
224 json_data = []
221 json_data = []
225 json_data['popularity'] = tag.get_popularity()
222 json_data['popularity'] = tag.get_popularity()
226
223
227 return HttpResponse(content=json.dumps(json_data))
224 return HttpResponse(content=json.dumps(json_data))
228
225
229
226
230 # TODO Add pub time and replies
227 # TODO Add pub time and replies
231 def _get_post_data(post_id, format_type=DIFF_TYPE_JSON, request=None,
228 def _get_post_data(post_id, format_type=DIFF_TYPE_JSON, request=None,
232 include_last_update=False):
229 include_last_update=False):
233 if format_type == DIFF_TYPE_HTML:
230 if format_type == DIFF_TYPE_HTML:
234 return get_post(request, post_id).content.strip()
231 return get_post(request, post_id).content.strip()
235 elif format_type == DIFF_TYPE_JSON:
232 elif format_type == DIFF_TYPE_JSON:
236 post = get_object_or_404(Post, id=post_id)
233 post = get_object_or_404(Post, id=post_id)
237 post_json = {
234 post_json = {
238 'id': post.id,
235 'id': post.id,
239 'title': post.title,
236 'title': post.title,
240 'text': post.text.rendered,
237 'text': post.text.rendered,
241 }
238 }
242 if post.image:
239 if post.image:
243 post_json['image'] = post.image.url
240 post_json['image'] = post.image.url
244 post_json['image_preview'] = post.image.url_200x150
241 post_json['image_preview'] = post.image.url_200x150
245 if include_last_update:
242 if include_last_update:
246 post_json['bump_time'] = datetime_to_epoch(
243 post_json['bump_time'] = datetime_to_epoch(
247 post.thread_new.bump_time)
244 post.thread_new.bump_time)
248 return post_json
245 return post_json
@@ -1,127 +1,107 b''
1 from datetime import datetime, timedelta
1 from datetime import datetime, timedelta
2 import hashlib
2 import hashlib
3 from django.db import transaction
3 from django.db import transaction
4 from django.db.models import Count
4 from django.db.models import Count
5 from django.template import RequestContext
5 from django.template import RequestContext
6 from django.utils import timezone
6 from django.utils import timezone
7 from django.views.generic import View
7 from django.views.generic import View
8 from boards import utils
8 from boards import utils
9 from boards.models import User, Post
9 from boards.models import User, Post
10 from boards.models.post import SETTING_MODERATE
10 from boards.models.post import SETTING_MODERATE
11 from boards.models.user import RANK_USER, Ban
11 from boards.models.user import RANK_USER, Ban
12 import neboard
12 import neboard
13
13
14 BAN_REASON_SPAM = 'Autoban: spam bot'
14 BAN_REASON_SPAM = 'Autoban: spam bot'
15
15
16 OLD_USER_AGE_DAYS = 90
17
18 PARAMETER_FORM = 'form'
16 PARAMETER_FORM = 'form'
19
17
20
18
21 class BaseBoardView(View):
19 class BaseBoardView(View):
22
20
23 def get_context_data(self, **kwargs):
21 def get_context_data(self, **kwargs):
24 request = kwargs['request']
22 request = kwargs['request']
25 context = self._default_context(request)
23 context = self._default_context(request)
26
24
27 context['version'] = neboard.settings.VERSION
25 context['version'] = neboard.settings.VERSION
28 context['site_name'] = neboard.settings.SITE_NAME
26 context['site_name'] = neboard.settings.SITE_NAME
29
27
30 return context
28 return context
31
29
32 def _default_context(self, request):
30 def _default_context(self, request):
33 """Create context with default values that are used in most views"""
31 """Create context with default values that are used in most views"""
34
32
35 context = RequestContext(request)
33 context = RequestContext(request)
36
34
37 user = self._get_user(request)
35 user = self._get_user(request)
38 context['user'] = user
36 context['user'] = user
39 context['tags'] = user.fav_tags.all()
37 context['tags'] = user.fav_tags.all()
40 context['posts_per_day'] = float(Post.objects.get_posts_per_day())
38 context['posts_per_day'] = float(Post.objects.get_posts_per_day())
41
39
42 theme = self._get_theme(request, user)
40 theme = self._get_theme(request, user)
43 context['theme'] = theme
41 context['theme'] = theme
44 context['theme_css'] = 'css/' + theme + '/base_page.css'
42 context['theme_css'] = 'css/' + theme + '/base_page.css'
45
43
46 # This shows the moderator panel
44 # This shows the moderator panel
47 moderate = user.get_setting(SETTING_MODERATE)
45 moderate = user.get_setting(SETTING_MODERATE)
48 if moderate == 'True':
46 if moderate == 'True':
49 context['moderator'] = user.is_moderator()
47 context['moderator'] = user.is_moderator()
50 else:
48 else:
51 context['moderator'] = False
49 context['moderator'] = False
52
50
53 return context
51 return context
54
52
55 def _get_user(self, request):
53 def _get_user(self, request):
56 """
54 """
57 Get current user from the session. If the user does not exist, create
55 Get current user from the session. If the user does not exist, create
58 a new one.
56 a new one.
59 """
57 """
60
58
61 session = request.session
59 session = request.session
62 if not 'user_id' in session:
60 if not 'user_id' in session:
63 request.session.save()
61 request.session.save()
64
62
65 md5 = hashlib.md5()
63 md5 = hashlib.md5()
66 md5.update(session.session_key)
64 md5.update(session.session_key)
67 new_id = md5.hexdigest()
65 new_id = md5.hexdigest()
68
66
69 while User.objects.filter(user_id=new_id).exists():
67 while User.objects.filter(user_id=new_id).exists():
70 md5.update(str(timezone.now()))
68 md5.update(str(timezone.now()))
71 new_id = md5.hexdigest()
69 new_id = md5.hexdigest()
72
70
73 time_now = timezone.now()
71 time_now = timezone.now()
74 user = User.objects.create(user_id=new_id, rank=RANK_USER,
72 user = User.objects.create(user_id=new_id, rank=RANK_USER,
75 registration_time=time_now)
73 registration_time=time_now)
76
74
77 # TODO Move this to manage.py commands
78 #self._delete_old_users()
79
80 session['user_id'] = user.id
75 session['user_id'] = user.id
81 else:
76 else:
82 user = User.objects.select_related('fav_tags').get(
77 user = User.objects.select_related('fav_tags').get(
83 id=session['user_id'])
78 id=session['user_id'])
84
79
85 return user
80 return user
86
81
87 def _get_theme(self, request, user=None):
82 def _get_theme(self, request, user=None):
88 """
83 """
89 Get user's CSS theme
84 Get user's CSS theme
90 """
85 """
91
86
92 if not user:
87 if not user:
93 user = self._get_user(request)
88 user = self._get_user(request)
94 theme = user.get_setting('theme')
89 theme = user.get_setting('theme')
95 if not theme:
90 if not theme:
96 theme = neboard.settings.DEFAULT_THEME
91 theme = neboard.settings.DEFAULT_THEME
97
92
98 return theme
93 return theme
99
94
100 def _delete_old_users(self):
101 """
102 Delete users with no favorite tags and posted messages. These can be spam
103 bots or just old user accounts
104 """
105
106 old_registration_date = datetime.now().date() - timedelta(
107 OLD_USER_AGE_DAYS)
108
109 for user in User.objects.annotate(tags_count=Count('fav_tags')).filter(
110 tags_count=0).filter(
111 registration_time__lt=old_registration_date):
112 if not Post.objects.filter(user=user).exists():
113 user.delete()
114
115 @transaction.atomic
95 @transaction.atomic
116 def _ban_current_user(self, request):
96 def _ban_current_user(self, request):
117 """
97 """
118 Add current user to the IP ban list
98 Add current user to the IP ban list
119 """
99 """
120
100
121 ip = utils.get_client_ip(request)
101 ip = utils.get_client_ip(request)
122 ban, created = Ban.objects.get_or_create(ip=ip)
102 ban, created = Ban.objects.get_or_create(ip=ip)
123 if created:
103 if created:
124 ban.can_read = False
104 ban.can_read = False
125 ban.reason = BAN_REASON_SPAM
105 ban.reason = BAN_REASON_SPAM
126 ban.save()
106 ban.save()
127
107
@@ -1,129 +1,132 b''
1 import string
1 import string
2 from django.core.urlresolvers import reverse
2 from django.core.urlresolvers import reverse
3 from django.db import transaction
3 from django.db import transaction
4 from django.http import Http404
4 from django.http import Http404
5 from django.shortcuts import get_object_or_404, render, redirect
5 from django.shortcuts import get_object_or_404, render, redirect
6 from django.views.generic.edit import FormMixin
6 from django.views.generic.edit import FormMixin
7 from boards import utils
7 from boards import utils
8 from boards.forms import PostForm, PlainErrorList
8 from boards.forms import PostForm, PlainErrorList
9 from boards.models import Post, Ban, Tag
9 from boards.models import Post, Ban, Tag
10 from boards.views.banned import BannedView
10 from boards.views.banned import BannedView
11 from boards.views.base import BaseBoardView, PARAMETER_FORM
11 from boards.views.base import BaseBoardView, PARAMETER_FORM
12 from boards.views.posting_mixin import PostMixin
12 from boards.views.posting_mixin import PostMixin
13 import neboard
13 import neboard
14
14
15 MODE_GALLERY = 'gallery'
15 MODE_GALLERY = 'gallery'
16 MODE_NORMAL = 'normal'
16 MODE_NORMAL = 'normal'
17
17
18 PARAMETER_MAX_REPLIES = 'max_replies'
18 PARAMETER_MAX_REPLIES = 'max_replies'
19 PARAMETER_THREAD = 'thread'
19 PARAMETER_THREAD = 'thread'
20 PARAMETER_BUMPABLE = 'bumpable'
20 PARAMETER_BUMPABLE = 'bumpable'
21
21
22
22
23 class ThreadView(BaseBoardView, PostMixin, FormMixin):
23 class ThreadView(BaseBoardView, PostMixin, FormMixin):
24
24
25 def get(self, request, post_id, mode=MODE_NORMAL, form=None):
25 def get(self, request, post_id, mode=MODE_NORMAL, form=None):
26 opening_post = Post.objects.filter(id=post_id).only('thread_new')[0]
26 try:
27 opening_post = Post.objects.filter(id=post_id).only('thread_new')[0]
28 except IndexError:
29 raise Http404
27
30
28 # If this is not OP, don't show it as it is
31 # If this is not OP, don't show it as it is
29 if not opening_post or not opening_post.is_opening():
32 if not opening_post or not opening_post.is_opening():
30 raise Http404
33 raise Http404
31
34
32 if not form:
35 if not form:
33 form = PostForm(error_class=PlainErrorList)
36 form = PostForm(error_class=PlainErrorList)
34
37
35 thread_to_show = opening_post.get_thread()
38 thread_to_show = opening_post.get_thread()
36
39
37 context = self.get_context_data(request=request)
40 context = self.get_context_data(request=request)
38
41
39 context[PARAMETER_FORM] = form
42 context[PARAMETER_FORM] = form
40 context["last_update"] = utils.datetime_to_epoch(
43 context["last_update"] = utils.datetime_to_epoch(
41 thread_to_show.last_edit_time)
44 thread_to_show.last_edit_time)
42 context[PARAMETER_THREAD] = thread_to_show
45 context[PARAMETER_THREAD] = thread_to_show
43 context[PARAMETER_MAX_REPLIES] = neboard.settings.MAX_POSTS_PER_THREAD
46 context[PARAMETER_MAX_REPLIES] = neboard.settings.MAX_POSTS_PER_THREAD
44
47
45 if MODE_NORMAL == mode:
48 if MODE_NORMAL == mode:
46 context[PARAMETER_BUMPABLE] = thread_to_show.can_bump()
49 context[PARAMETER_BUMPABLE] = thread_to_show.can_bump()
47 if context[PARAMETER_BUMPABLE]:
50 if context[PARAMETER_BUMPABLE]:
48 context['posts_left'] = neboard.settings.MAX_POSTS_PER_THREAD \
51 context['posts_left'] = neboard.settings.MAX_POSTS_PER_THREAD \
49 - thread_to_show.get_reply_count()
52 - thread_to_show.get_reply_count()
50 context['bumplimit_progress'] = str(
53 context['bumplimit_progress'] = str(
51 float(context['posts_left']) /
54 float(context['posts_left']) /
52 neboard.settings.MAX_POSTS_PER_THREAD * 100)
55 neboard.settings.MAX_POSTS_PER_THREAD * 100)
53
56
54 context['opening_post'] = opening_post
57 context['opening_post'] = opening_post
55
58
56 document = 'boards/thread.html'
59 document = 'boards/thread.html'
57 elif MODE_GALLERY == mode:
60 elif MODE_GALLERY == mode:
58 posts = thread_to_show.get_replies(view_fields_only=True)
61 posts = thread_to_show.get_replies(view_fields_only=True)
59 context['posts'] = posts.filter(image_width__gt=0)
62 context['posts'] = posts.filter(image_width__gt=0)
60
63
61 document = 'boards/thread_gallery.html'
64 document = 'boards/thread_gallery.html'
62 else:
65 else:
63 raise Http404
66 raise Http404
64
67
65 return render(request, document, context)
68 return render(request, document, context)
66
69
67 def post(self, request, post_id, mode=MODE_NORMAL):
70 def post(self, request, post_id, mode=MODE_NORMAL):
68 opening_post = get_object_or_404(Post, id=post_id)
71 opening_post = get_object_or_404(Post, id=post_id)
69
72
70 # If this is not OP, don't show it as it is
73 # If this is not OP, don't show it as it is
71 if not opening_post.is_opening():
74 if not opening_post.is_opening():
72 raise Http404
75 raise Http404
73
76
74 if not opening_post.get_thread().archived:
77 if not opening_post.get_thread().archived:
75 form = PostForm(request.POST, request.FILES,
78 form = PostForm(request.POST, request.FILES,
76 error_class=PlainErrorList)
79 error_class=PlainErrorList)
77 form.session = request.session
80 form.session = request.session
78
81
79 if form.is_valid():
82 if form.is_valid():
80 return self.new_post(request, form, opening_post)
83 return self.new_post(request, form, opening_post)
81 if form.need_to_ban:
84 if form.need_to_ban:
82 # Ban user because he is suspected to be a bot
85 # Ban user because he is suspected to be a bot
83 self._ban_current_user(request)
86 self._ban_current_user(request)
84
87
85 return self.get(request, post_id, mode, form)
88 return self.get(request, post_id, mode, form)
86
89
87 @transaction.atomic
90 @transaction.atomic
88 def new_post(self, request, form, opening_post=None, html_response=True):
91 def new_post(self, request, form, opening_post=None, html_response=True):
89 """Add a new post (in thread or as a reply)."""
92 """Add a new post (in thread or as a reply)."""
90
93
91 ip = utils.get_client_ip(request)
94 ip = utils.get_client_ip(request)
92 is_banned = Ban.objects.filter(ip=ip).exists()
95 is_banned = Ban.objects.filter(ip=ip).exists()
93
96
94 if is_banned:
97 if is_banned:
95 if html_response:
98 if html_response:
96 return redirect(BannedView().as_view())
99 return redirect(BannedView().as_view())
97 else:
100 else:
98 return None
101 return None
99
102
100 data = form.cleaned_data
103 data = form.cleaned_data
101
104
102 title = data['title']
105 title = data['title']
103 text = data['text']
106 text = data['text']
104
107
105 text = self._remove_invalid_links(text)
108 text = self._remove_invalid_links(text)
106
109
107 if 'image' in data.keys():
110 if 'image' in data.keys():
108 image = data['image']
111 image = data['image']
109 else:
112 else:
110 image = None
113 image = None
111
114
112 tags = []
115 tags = []
113
116
114 post_thread = opening_post.get_thread()
117 post_thread = opening_post.get_thread()
115
118
116 post = Post.objects.create_post(title=title, text=text, ip=ip,
119 post = Post.objects.create_post(title=title, text=text, ip=ip,
117 thread=post_thread, image=image,
120 thread=post_thread, image=image,
118 tags=tags,
121 tags=tags,
119 user=self._get_user(request))
122 user=self._get_user(request))
120
123
121 thread_to_show = (opening_post.id if opening_post else post.id)
124 thread_to_show = (opening_post.id if opening_post else post.id)
122
125
123 if html_response:
126 if html_response:
124 if opening_post:
127 if opening_post:
125 return redirect(reverse(
128 return redirect(reverse(
126 'thread',
129 'thread',
127 kwargs={'post_id': thread_to_show}) + '#' + str(post.id))
130 kwargs={'post_id': thread_to_show}) + '#' + str(post.id))
128 else:
131 else:
129 return post
132 return post
General Comments 0
You need to be logged in to leave comments. Login now