##// END OF EJS Templates
Fixed posting without a primary key defined. Use more proper way to assert an exception
neko259 -
r835:0f3bc07f decentral
parent child Browse files
Show More
@@ -1,491 +1,491 b''
1 from datetime import datetime, timedelta, date
1 from datetime import datetime, timedelta, date
2 from datetime import time as dtime
2 from datetime import time as dtime
3 import logging
3 import logging
4 import re
4 import re
5 import xml.etree.ElementTree as et
5 import xml.etree.ElementTree as et
6
6
7 from django.core.cache import cache
7 from django.core.cache import cache
8 from django.core.urlresolvers import reverse
8 from django.core.urlresolvers import reverse
9 from django.db import models, transaction
9 from django.db import models, transaction
10 from django.template.loader import render_to_string
10 from django.template.loader import render_to_string
11 from django.utils import timezone
11 from django.utils import timezone
12
12
13 from markupfield.fields import MarkupField
13 from markupfield.fields import MarkupField
14
14
15 from boards.models import PostImage, KeyPair, GlobalId
15 from boards.models import PostImage, KeyPair, GlobalId
16 from boards.models.base import Viewable
16 from boards.models.base import Viewable
17 from boards.models.thread import Thread
17 from boards.models.thread import Thread
18 from boards import utils
18 from boards import utils
19
19
20
20
21 APP_LABEL_BOARDS = 'boards'
21 APP_LABEL_BOARDS = 'boards'
22
22
23 CACHE_KEY_PPD = 'ppd'
23 CACHE_KEY_PPD = 'ppd'
24 CACHE_KEY_POST_URL = 'post_url'
24 CACHE_KEY_POST_URL = 'post_url'
25
25
26 POSTS_PER_DAY_RANGE = 7
26 POSTS_PER_DAY_RANGE = 7
27
27
28 BAN_REASON_AUTO = 'Auto'
28 BAN_REASON_AUTO = 'Auto'
29
29
30 IMAGE_THUMB_SIZE = (200, 150)
30 IMAGE_THUMB_SIZE = (200, 150)
31
31
32 TITLE_MAX_LENGTH = 200
32 TITLE_MAX_LENGTH = 200
33
33
34 DEFAULT_MARKUP_TYPE = 'bbcode'
34 DEFAULT_MARKUP_TYPE = 'bbcode'
35
35
36 # TODO This should be removed
36 # TODO This should be removed
37 NO_IP = '0.0.0.0'
37 NO_IP = '0.0.0.0'
38
38
39 # TODO Real user agent should be saved instead of this
39 # TODO Real user agent should be saved instead of this
40 UNKNOWN_UA = ''
40 UNKNOWN_UA = ''
41
41
42 REGEX_REPLY = re.compile(r'\[post\](\d+)\[/post\]')
42 REGEX_REPLY = re.compile(r'\[post\](\d+)\[/post\]')
43
43
44 TAG_MODEL = 'model'
44 TAG_MODEL = 'model'
45 TAG_REQUEST = 'request'
45 TAG_REQUEST = 'request'
46 TAG_RESPONSE = 'response'
46 TAG_RESPONSE = 'response'
47 TAG_ID = 'id'
47 TAG_ID = 'id'
48 TAG_STATUS = 'status'
48 TAG_STATUS = 'status'
49 TAG_MODELS = 'models'
49 TAG_MODELS = 'models'
50 TAG_TITLE = 'title'
50 TAG_TITLE = 'title'
51 TAG_TEXT = 'text'
51 TAG_TEXT = 'text'
52 TAG_THREAD = 'thread'
52 TAG_THREAD = 'thread'
53 TAG_PUB_TIME = 'pub-time'
53 TAG_PUB_TIME = 'pub-time'
54 TAG_EDIT_TIME = 'edit-time'
54 TAG_EDIT_TIME = 'edit-time'
55 TAG_PREVIOUS = 'previous'
55 TAG_PREVIOUS = 'previous'
56 TAG_NEXT = 'next'
56 TAG_NEXT = 'next'
57
57
58 TYPE_GET = 'get'
58 TYPE_GET = 'get'
59
59
60 ATTR_VERSION = 'version'
60 ATTR_VERSION = 'version'
61 ATTR_TYPE = 'type'
61 ATTR_TYPE = 'type'
62 ATTR_NAME = 'name'
62 ATTR_NAME = 'name'
63 ATTR_REF_ID = 'ref-id'
63 ATTR_REF_ID = 'ref-id'
64
64
65 STATUS_SUCCESS = 'success'
65 STATUS_SUCCESS = 'success'
66
66
67 logger = logging.getLogger(__name__)
67 logger = logging.getLogger(__name__)
68
68
69
69
70 class PostManager(models.Manager):
70 class PostManager(models.Manager):
71 def create_post(self, title, text, image=None, thread=None, ip=NO_IP,
71 def create_post(self, title, text, image=None, thread=None, ip=NO_IP,
72 tags=None):
72 tags=None):
73 """
73 """
74 Creates new post
74 Creates new post
75 """
75 """
76
76
77 if not tags:
77 if not tags:
78 tags = []
78 tags = []
79
79
80 posting_time = timezone.now()
80 posting_time = timezone.now()
81 if not thread:
81 if not thread:
82 thread = Thread.objects.create(bump_time=posting_time,
82 thread = Thread.objects.create(bump_time=posting_time,
83 last_edit_time=posting_time)
83 last_edit_time=posting_time)
84 new_thread = True
84 new_thread = True
85 else:
85 else:
86 thread.bump()
86 thread.bump()
87 thread.last_edit_time = posting_time
87 thread.last_edit_time = posting_time
88 thread.save()
88 thread.save()
89 new_thread = False
89 new_thread = False
90
90
91 post = self.create(title=title,
91 post = self.create(title=title,
92 text=text,
92 text=text,
93 pub_time=posting_time,
93 pub_time=posting_time,
94 thread_new=thread,
94 thread_new=thread,
95 poster_ip=ip,
95 poster_ip=ip,
96 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
96 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
97 # last!
97 # last!
98 last_edit_time=posting_time)
98 last_edit_time=posting_time)
99
99
100 post.set_global_id()
100 post.set_global_id()
101
101
102 if image:
102 if image:
103 post_image = PostImage.objects.create(image=image)
103 post_image = PostImage.objects.create(image=image)
104 post.images.add(post_image)
104 post.images.add(post_image)
105 logger.info('Created image #%d for post #%d' % (post_image.id,
105 logger.info('Created image #%d for post #%d' % (post_image.id,
106 post.id))
106 post.id))
107
107
108 thread.replies.add(post)
108 thread.replies.add(post)
109 list(map(thread.add_tag, tags))
109 list(map(thread.add_tag, tags))
110
110
111 if new_thread:
111 if new_thread:
112 Thread.objects.process_oldest_threads()
112 Thread.objects.process_oldest_threads()
113 self.connect_replies(post)
113 self.connect_replies(post)
114
114
115 logger.info('Created post #%d with title %s and key %s'
115 logger.info('Created post #%d with title %s'
116 % (post.id, post.get_title(), post.global_id.key))
116 % (post.id, post.get_title()))
117
117
118 return post
118 return post
119
119
120 def delete_post(self, post):
120 def delete_post(self, post):
121 """
121 """
122 Deletes post and update or delete its thread
122 Deletes post and update or delete its thread
123 """
123 """
124
124
125 post_id = post.id
125 post_id = post.id
126
126
127 thread = post.get_thread()
127 thread = post.get_thread()
128
128
129 if post.is_opening():
129 if post.is_opening():
130 thread.delete()
130 thread.delete()
131 else:
131 else:
132 thread.last_edit_time = timezone.now()
132 thread.last_edit_time = timezone.now()
133 thread.save()
133 thread.save()
134
134
135 post.delete()
135 post.delete()
136
136
137 logger.info('Deleted post #%d (%s)' % (post_id, post.get_title()))
137 logger.info('Deleted post #%d (%s)' % (post_id, post.get_title()))
138
138
139 def delete_posts_by_ip(self, ip):
139 def delete_posts_by_ip(self, ip):
140 """
140 """
141 Deletes all posts of the author with same IP
141 Deletes all posts of the author with same IP
142 """
142 """
143
143
144 posts = self.filter(poster_ip=ip)
144 posts = self.filter(poster_ip=ip)
145 for post in posts:
145 for post in posts:
146 self.delete_post(post)
146 self.delete_post(post)
147
147
148 def connect_replies(self, post):
148 def connect_replies(self, post):
149 """
149 """
150 Connects replies to a post to show them as a reflink map
150 Connects replies to a post to show them as a reflink map
151 """
151 """
152
152
153 for reply_number in post.get_replied_ids():
153 for reply_number in post.get_replied_ids():
154 ref_post = self.filter(id=reply_number)
154 ref_post = self.filter(id=reply_number)
155 if ref_post.count() > 0:
155 if ref_post.count() > 0:
156 referenced_post = ref_post[0]
156 referenced_post = ref_post[0]
157 referenced_post.referenced_posts.add(post)
157 referenced_post.referenced_posts.add(post)
158 referenced_post.last_edit_time = post.pub_time
158 referenced_post.last_edit_time = post.pub_time
159 referenced_post.build_refmap()
159 referenced_post.build_refmap()
160 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
160 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
161
161
162 referenced_thread = referenced_post.get_thread()
162 referenced_thread = referenced_post.get_thread()
163 referenced_thread.last_edit_time = post.pub_time
163 referenced_thread.last_edit_time = post.pub_time
164 referenced_thread.save(update_fields=['last_edit_time'])
164 referenced_thread.save(update_fields=['last_edit_time'])
165
165
166 def get_posts_per_day(self):
166 def get_posts_per_day(self):
167 """
167 """
168 Gets average count of posts per day for the last 7 days
168 Gets average count of posts per day for the last 7 days
169 """
169 """
170
170
171 day_end = date.today()
171 day_end = date.today()
172 day_start = day_end - timedelta(POSTS_PER_DAY_RANGE)
172 day_start = day_end - timedelta(POSTS_PER_DAY_RANGE)
173
173
174 cache_key = CACHE_KEY_PPD + str(day_end)
174 cache_key = CACHE_KEY_PPD + str(day_end)
175 ppd = cache.get(cache_key)
175 ppd = cache.get(cache_key)
176 if ppd:
176 if ppd:
177 return ppd
177 return ppd
178
178
179 day_time_start = timezone.make_aware(datetime.combine(
179 day_time_start = timezone.make_aware(datetime.combine(
180 day_start, dtime()), timezone.get_current_timezone())
180 day_start, dtime()), timezone.get_current_timezone())
181 day_time_end = timezone.make_aware(datetime.combine(
181 day_time_end = timezone.make_aware(datetime.combine(
182 day_end, dtime()), timezone.get_current_timezone())
182 day_end, dtime()), timezone.get_current_timezone())
183
183
184 posts_per_period = float(self.filter(
184 posts_per_period = float(self.filter(
185 pub_time__lte=day_time_end,
185 pub_time__lte=day_time_end,
186 pub_time__gte=day_time_start).count())
186 pub_time__gte=day_time_start).count())
187
187
188 ppd = posts_per_period / POSTS_PER_DAY_RANGE
188 ppd = posts_per_period / POSTS_PER_DAY_RANGE
189
189
190 cache.set(cache_key, ppd)
190 cache.set(cache_key, ppd)
191 return ppd
191 return ppd
192
192
193
193
194 def generate_request_get(self, model_list: list):
194 def generate_request_get(self, model_list: list):
195 """
195 """
196 Form a get request from a list of ModelId objects.
196 Form a get request from a list of ModelId objects.
197 """
197 """
198
198
199 request = et.Element(TAG_REQUEST)
199 request = et.Element(TAG_REQUEST)
200 request.set(ATTR_TYPE, TYPE_GET)
200 request.set(ATTR_TYPE, TYPE_GET)
201 request.set(ATTR_VERSION, '1.0')
201 request.set(ATTR_VERSION, '1.0')
202
202
203 model = et.SubElement(request, TAG_MODEL)
203 model = et.SubElement(request, TAG_MODEL)
204 model.set(ATTR_VERSION, '1.0')
204 model.set(ATTR_VERSION, '1.0')
205 model.set(ATTR_NAME, 'post')
205 model.set(ATTR_NAME, 'post')
206
206
207 for post in model_list:
207 for post in model_list:
208 tag_id = et.SubElement(model, TAG_ID)
208 tag_id = et.SubElement(model, TAG_ID)
209 post.global_id.to_xml_element(tag_id)
209 post.global_id.to_xml_element(tag_id)
210
210
211 return et.tostring(request, 'unicode')
211 return et.tostring(request, 'unicode')
212
212
213 def generate_response_get(self, model_list: list):
213 def generate_response_get(self, model_list: list):
214 response = et.Element(TAG_RESPONSE)
214 response = et.Element(TAG_RESPONSE)
215
215
216 status = et.SubElement(response, TAG_STATUS)
216 status = et.SubElement(response, TAG_STATUS)
217 status.text = STATUS_SUCCESS
217 status.text = STATUS_SUCCESS
218
218
219 models = et.SubElement(response, TAG_MODELS)
219 models = et.SubElement(response, TAG_MODELS)
220
220
221 ref_id = 1
221 ref_id = 1
222 for post in model_list:
222 for post in model_list:
223 model = et.SubElement(models, TAG_MODEL)
223 model = et.SubElement(models, TAG_MODEL)
224 model.set(ATTR_NAME, 'post')
224 model.set(ATTR_NAME, 'post')
225 model.set(ATTR_REF_ID, str(ref_id))
225 model.set(ATTR_REF_ID, str(ref_id))
226 ref_id += 1
226 ref_id += 1
227
227
228 tag_id = et.SubElement(model, TAG_ID)
228 tag_id = et.SubElement(model, TAG_ID)
229 post.global_id.to_xml_element(tag_id)
229 post.global_id.to_xml_element(tag_id)
230
230
231 title = et.SubElement(model, TAG_TITLE)
231 title = et.SubElement(model, TAG_TITLE)
232 title.text = post.title
232 title.text = post.title
233
233
234 text = et.SubElement(model, TAG_TEXT)
234 text = et.SubElement(model, TAG_TEXT)
235 text.text = post.text.raw
235 text.text = post.text.raw
236
236
237 if not post.is_opening():
237 if not post.is_opening():
238 thread = et.SubElement(model, TAG_THREAD)
238 thread = et.SubElement(model, TAG_THREAD)
239 thread.text = str(post.get_thread().get_opening_post_id())
239 thread.text = str(post.get_thread().get_opening_post_id())
240
240
241 pub_time = et.SubElement(model, TAG_PUB_TIME)
241 pub_time = et.SubElement(model, TAG_PUB_TIME)
242 pub_time.text = str(post.get_pub_time_epoch())
242 pub_time.text = str(post.get_pub_time_epoch())
243
243
244 edit_time = et.SubElement(model, TAG_EDIT_TIME)
244 edit_time = et.SubElement(model, TAG_EDIT_TIME)
245 edit_time.text = str(post.get_edit_time_epoch())
245 edit_time.text = str(post.get_edit_time_epoch())
246
246
247 previous_ids = post.get_replied_ids()
247 previous_ids = post.get_replied_ids()
248 if len(previous_ids) > 0:
248 if len(previous_ids) > 0:
249 previous = et.SubElement(model, TAG_PREVIOUS)
249 previous = et.SubElement(model, TAG_PREVIOUS)
250 for id in previous_ids:
250 for id in previous_ids:
251 prev_id = et.SubElement(previous, TAG_ID)
251 prev_id = et.SubElement(previous, TAG_ID)
252 replied_post = Post.objects.get(id=id)
252 replied_post = Post.objects.get(id=id)
253 replied_post.global_id.to_xml_element(prev_id)
253 replied_post.global_id.to_xml_element(prev_id)
254
254
255
255
256 next_ids = post.referenced_posts.order_by('id').all()
256 next_ids = post.referenced_posts.order_by('id').all()
257 if len(next_ids) > 0:
257 if len(next_ids) > 0:
258 next_el = et.SubElement(model, TAG_NEXT)
258 next_el = et.SubElement(model, TAG_NEXT)
259 for ref_post in next_ids:
259 for ref_post in next_ids:
260 next_id = et.SubElement(next_el, TAG_ID)
260 next_id = et.SubElement(next_el, TAG_ID)
261 ref_post.global_id.to_xml_element(next_id)
261 ref_post.global_id.to_xml_element(next_id)
262
262
263 return et.tostring(response, 'unicode')
263 return et.tostring(response, 'unicode')
264
264
265
265
266 class Post(models.Model, Viewable):
266 class Post(models.Model, Viewable):
267 """A post is a message."""
267 """A post is a message."""
268
268
269 objects = PostManager()
269 objects = PostManager()
270
270
271 class Meta:
271 class Meta:
272 app_label = APP_LABEL_BOARDS
272 app_label = APP_LABEL_BOARDS
273 ordering = ('id',)
273 ordering = ('id',)
274
274
275 title = models.CharField(max_length=TITLE_MAX_LENGTH)
275 title = models.CharField(max_length=TITLE_MAX_LENGTH)
276 pub_time = models.DateTimeField()
276 pub_time = models.DateTimeField()
277 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
277 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
278 escape_html=False)
278 escape_html=False)
279
279
280 images = models.ManyToManyField(PostImage, null=True, blank=True,
280 images = models.ManyToManyField(PostImage, null=True, blank=True,
281 related_name='ip+', db_index=True)
281 related_name='ip+', db_index=True)
282
282
283 poster_ip = models.GenericIPAddressField()
283 poster_ip = models.GenericIPAddressField()
284 poster_user_agent = models.TextField()
284 poster_user_agent = models.TextField()
285
285
286 thread_new = models.ForeignKey('Thread', null=True, default=None,
286 thread_new = models.ForeignKey('Thread', null=True, default=None,
287 db_index=True)
287 db_index=True)
288 last_edit_time = models.DateTimeField()
288 last_edit_time = models.DateTimeField()
289
289
290 # Replies to the post
290 # Replies to the post
291 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
291 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
292 null=True,
292 null=True,
293 blank=True, related_name='rfp+',
293 blank=True, related_name='rfp+',
294 db_index=True)
294 db_index=True)
295
295
296 # Replies map. This is built from the referenced posts list to speed up
296 # Replies map. This is built from the referenced posts list to speed up
297 # page loading (no need to get all the referenced posts from the database).
297 # page loading (no need to get all the referenced posts from the database).
298 refmap = models.TextField(null=True, blank=True)
298 refmap = models.TextField(null=True, blank=True)
299
299
300 # Global ID with author key. If the message was downloaded from another
300 # Global ID with author key. If the message was downloaded from another
301 # server, this indicates the server.
301 # server, this indicates the server.
302 global_id = models.OneToOneField('GlobalId', null=True, blank=True)
302 global_id = models.OneToOneField('GlobalId', null=True, blank=True)
303
303
304 # One post can be signed by many nodes that give their trust to it
304 # One post can be signed by many nodes that give their trust to it
305 signature = models.ManyToManyField('Signature', null=True, blank=True)
305 signature = models.ManyToManyField('Signature', null=True, blank=True)
306
306
307 def __unicode__(self):
307 def __unicode__(self):
308 return '#' + str(self.id) + ' ' + self.title + ' (' + \
308 return '#' + str(self.id) + ' ' + self.title + ' (' + \
309 self.text.raw[:50] + ')'
309 self.text.raw[:50] + ')'
310
310
311 def get_title(self):
311 def get_title(self):
312 """
312 """
313 Gets original post title or part of its text.
313 Gets original post title or part of its text.
314 """
314 """
315
315
316 title = self.title
316 title = self.title
317 if not title:
317 if not title:
318 title = self.text.rendered
318 title = self.text.rendered
319
319
320 return title
320 return title
321
321
322 def build_refmap(self):
322 def build_refmap(self):
323 """
323 """
324 Builds a replies map string from replies list. This is a cache to stop
324 Builds a replies map string from replies list. This is a cache to stop
325 the server from recalculating the map on every post show.
325 the server from recalculating the map on every post show.
326 """
326 """
327 map_string = ''
327 map_string = ''
328
328
329 first = True
329 first = True
330 for refpost in self.referenced_posts.all():
330 for refpost in self.referenced_posts.all():
331 if not first:
331 if not first:
332 map_string += ', '
332 map_string += ', '
333 map_string += '<a href="%s">&gt;&gt;%s</a>' % (refpost.get_url(),
333 map_string += '<a href="%s">&gt;&gt;%s</a>' % (refpost.get_url(),
334 refpost.id)
334 refpost.id)
335 first = False
335 first = False
336
336
337 self.refmap = map_string
337 self.refmap = map_string
338
338
339 def get_sorted_referenced_posts(self):
339 def get_sorted_referenced_posts(self):
340 return self.refmap
340 return self.refmap
341
341
342 def is_referenced(self):
342 def is_referenced(self):
343 return len(self.refmap) > 0
343 return len(self.refmap) > 0
344
344
345 def is_opening(self):
345 def is_opening(self):
346 """
346 """
347 Checks if this is an opening post or just a reply.
347 Checks if this is an opening post or just a reply.
348 """
348 """
349
349
350 return self.get_thread().get_opening_post_id() == self.id
350 return self.get_thread().get_opening_post_id() == self.id
351
351
352 @transaction.atomic
352 @transaction.atomic
353 def add_tag(self, tag):
353 def add_tag(self, tag):
354 edit_time = timezone.now()
354 edit_time = timezone.now()
355
355
356 thread = self.get_thread()
356 thread = self.get_thread()
357 thread.add_tag(tag)
357 thread.add_tag(tag)
358 self.last_edit_time = edit_time
358 self.last_edit_time = edit_time
359 self.save(update_fields=['last_edit_time'])
359 self.save(update_fields=['last_edit_time'])
360
360
361 thread.last_edit_time = edit_time
361 thread.last_edit_time = edit_time
362 thread.save(update_fields=['last_edit_time'])
362 thread.save(update_fields=['last_edit_time'])
363
363
364 @transaction.atomic
364 @transaction.atomic
365 def remove_tag(self, tag):
365 def remove_tag(self, tag):
366 edit_time = timezone.now()
366 edit_time = timezone.now()
367
367
368 thread = self.get_thread()
368 thread = self.get_thread()
369 thread.remove_tag(tag)
369 thread.remove_tag(tag)
370 self.last_edit_time = edit_time
370 self.last_edit_time = edit_time
371 self.save(update_fields=['last_edit_time'])
371 self.save(update_fields=['last_edit_time'])
372
372
373 thread.last_edit_time = edit_time
373 thread.last_edit_time = edit_time
374 thread.save(update_fields=['last_edit_time'])
374 thread.save(update_fields=['last_edit_time'])
375
375
376 def get_url(self, thread=None):
376 def get_url(self, thread=None):
377 """
377 """
378 Gets full url to the post.
378 Gets full url to the post.
379 """
379 """
380
380
381 cache_key = CACHE_KEY_POST_URL + str(self.id)
381 cache_key = CACHE_KEY_POST_URL + str(self.id)
382 link = cache.get(cache_key)
382 link = cache.get(cache_key)
383
383
384 if not link:
384 if not link:
385 if not thread:
385 if not thread:
386 thread = self.get_thread()
386 thread = self.get_thread()
387
387
388 opening_id = thread.get_opening_post_id()
388 opening_id = thread.get_opening_post_id()
389
389
390 if self.id != opening_id:
390 if self.id != opening_id:
391 link = reverse('thread', kwargs={
391 link = reverse('thread', kwargs={
392 'post_id': opening_id}) + '#' + str(self.id)
392 'post_id': opening_id}) + '#' + str(self.id)
393 else:
393 else:
394 link = reverse('thread', kwargs={'post_id': self.id})
394 link = reverse('thread', kwargs={'post_id': self.id})
395
395
396 cache.set(cache_key, link)
396 cache.set(cache_key, link)
397
397
398 return link
398 return link
399
399
400 def get_thread(self):
400 def get_thread(self):
401 """
401 """
402 Gets post's thread.
402 Gets post's thread.
403 """
403 """
404
404
405 return self.thread_new
405 return self.thread_new
406
406
407 def get_referenced_posts(self):
407 def get_referenced_posts(self):
408 return self.referenced_posts.only('id', 'thread_new')
408 return self.referenced_posts.only('id', 'thread_new')
409
409
410 def get_text(self):
410 def get_text(self):
411 return self.text
411 return self.text
412
412
413 def get_view(self, moderator=False, need_open_link=False,
413 def get_view(self, moderator=False, need_open_link=False,
414 truncated=False, *args, **kwargs):
414 truncated=False, *args, **kwargs):
415 if 'is_opening' in kwargs:
415 if 'is_opening' in kwargs:
416 is_opening = kwargs['is_opening']
416 is_opening = kwargs['is_opening']
417 else:
417 else:
418 is_opening = self.is_opening()
418 is_opening = self.is_opening()
419
419
420 if 'thread' in kwargs:
420 if 'thread' in kwargs:
421 thread = kwargs['thread']
421 thread = kwargs['thread']
422 else:
422 else:
423 thread = self.get_thread()
423 thread = self.get_thread()
424
424
425 if 'can_bump' in kwargs:
425 if 'can_bump' in kwargs:
426 can_bump = kwargs['can_bump']
426 can_bump = kwargs['can_bump']
427 else:
427 else:
428 can_bump = thread.can_bump()
428 can_bump = thread.can_bump()
429
429
430 if is_opening:
430 if is_opening:
431 opening_post_id = self.id
431 opening_post_id = self.id
432 else:
432 else:
433 opening_post_id = thread.get_opening_post_id()
433 opening_post_id = thread.get_opening_post_id()
434
434
435 return render_to_string('boards/post.html', {
435 return render_to_string('boards/post.html', {
436 'post': self,
436 'post': self,
437 'moderator': moderator,
437 'moderator': moderator,
438 'is_opening': is_opening,
438 'is_opening': is_opening,
439 'thread': thread,
439 'thread': thread,
440 'bumpable': can_bump,
440 'bumpable': can_bump,
441 'need_open_link': need_open_link,
441 'need_open_link': need_open_link,
442 'truncated': truncated,
442 'truncated': truncated,
443 'opening_post_id': opening_post_id,
443 'opening_post_id': opening_post_id,
444 })
444 })
445
445
446 def get_first_image(self):
446 def get_first_image(self):
447 return self.images.earliest('id')
447 return self.images.earliest('id')
448
448
449 def delete(self, using=None):
449 def delete(self, using=None):
450 """
450 """
451 Deletes all post images and the post itself.
451 Deletes all post images and the post itself.
452 """
452 """
453
453
454 self.images.all().delete()
454 self.images.all().delete()
455 self.signature.all().delete()
455 self.signature.all().delete()
456 if self.global_id:
456 if self.global_id:
457 self.global_id.delete()
457 self.global_id.delete()
458
458
459 super(Post, self).delete(using)
459 super(Post, self).delete(using)
460
460
461 def set_global_id(self, key_pair=None):
461 def set_global_id(self, key_pair=None):
462 """
462 """
463 Sets global id based on the given key pair. If no key pair is given,
463 Sets global id based on the given key pair. If no key pair is given,
464 default one is used.
464 default one is used.
465 """
465 """
466
466
467 if key_pair:
467 if key_pair:
468 key = key_pair
468 key = key_pair
469 else:
469 else:
470 try:
470 try:
471 key = KeyPair.objects.get(primary=True)
471 key = KeyPair.objects.get(primary=True)
472 except KeyPair.DoesNotExist:
472 except KeyPair.DoesNotExist:
473 # Do not update the global id because there is no key defined
473 # Do not update the global id because there is no key defined
474 return
474 return
475 global_id = GlobalId(key_type=key.key_type,
475 global_id = GlobalId(key_type=key.key_type,
476 key=key.public_key,
476 key=key.public_key,
477 local_id = self.id)
477 local_id = self.id)
478 global_id.save()
478 global_id.save()
479
479
480 self.global_id = global_id
480 self.global_id = global_id
481
481
482 self.save(update_fields=['global_id'])
482 self.save(update_fields=['global_id'])
483
483
484 def get_pub_time_epoch(self):
484 def get_pub_time_epoch(self):
485 return utils.datetime_to_epoch(self.pub_time)
485 return utils.datetime_to_epoch(self.pub_time)
486
486
487 def get_edit_time_epoch(self):
487 def get_edit_time_epoch(self):
488 return utils.datetime_to_epoch(self.last_edit_time)
488 return utils.datetime_to_epoch(self.last_edit_time)
489
489
490 def get_replied_ids(self):
490 def get_replied_ids(self):
491 return re.findall(REGEX_REPLY, self.text.raw)
491 return re.findall(REGEX_REPLY, self.text.raw)
@@ -1,97 +1,95 b''
1 import logging
1 import logging
2
2
3 from django.test import TestCase
3 from django.test import TestCase
4 from boards.models import KeyPair, GlobalId, Post
4 from boards.models import KeyPair, GlobalId, Post
5
5
6
6
7 logger = logging.getLogger(__name__)
7 logger = logging.getLogger(__name__)
8
8
9
9
10 class KeyTest(TestCase):
10 class KeyTest(TestCase):
11 def test_create_key(self):
11 def test_create_key(self):
12 key = KeyPair.objects.generate_key('ecdsa')
12 key = KeyPair.objects.generate_key('ecdsa')
13
13
14 self.assertIsNotNone(key, 'The key was not created.')
14 self.assertIsNotNone(key, 'The key was not created.')
15
15
16 def test_validation(self):
16 def test_validation(self):
17 key = KeyPair.objects.generate_key(key_type='ecdsa')
17 key = KeyPair.objects.generate_key(key_type='ecdsa')
18 message = 'msg'
18 message = 'msg'
19 signature = key.sign(message)
19 signature = key.sign(message)
20 valid = KeyPair.objects.verify(key.public_key, message, signature,
20 valid = KeyPair.objects.verify(key.public_key, message, signature,
21 key_type='ecdsa')
21 key_type='ecdsa')
22
22
23 self.assertTrue(valid, 'Message verification failed.')
23 self.assertTrue(valid, 'Message verification failed.')
24
24
25 def test_primary_constraint(self):
25 def test_primary_constraint(self):
26 KeyPair.objects.generate_key(key_type='ecdsa', primary=True)
26 KeyPair.objects.generate_key(key_type='ecdsa', primary=True)
27
27
28 try:
28 with self.assertRaises(Exception):
29 KeyPair.objects.generate_key(key_type='ecdsa', primary=True)
29 KeyPair.objects.generate_key(key_type='ecdsa', primary=True)
30 self.fail('Exception should be thrown indicating there can be only'
31 ' one primary key.')
32 except Exception:
33 pass
34
30
35 def test_model_id_save(self):
31 def test_model_id_save(self):
36 model_id = GlobalId(key_type='test', key='test key', local_id='1')
32 model_id = GlobalId(key_type='test', key='test key', local_id='1')
37 model_id.save()
33 model_id.save()
38
34
39 def test_request_get(self):
35 def test_request_get(self):
40 post = self._create_post_with_key()
36 post = self._create_post_with_key()
41
37
42 request = Post.objects.generate_request_get([post])
38 request = Post.objects.generate_request_get([post])
43 logger.debug(request)
39 logger.debug(request)
44
40
45 self.assertTrue('<request type="get" version="1.0">'
41 self.assertTrue('<request type="get" version="1.0">'
46 '<model name="post" version="1.0">'
42 '<model name="post" version="1.0">'
47 '<id key="pubkey" local-id="1" type="test_key_type" />'
43 '<id key="pubkey" local-id="1" type="test_key_type" />'
48 '</model>'
44 '</model>'
49 '</request>' in request,
45 '</request>' in request,
50 'Wrong XML generated for the GET request.')
46 'Wrong XML generated for the GET request.')
51
47
52 def test_response_get(self):
48 def test_response_get(self):
53 post = self._create_post_with_key()
49 post = self._create_post_with_key()
54 reply_post = Post.objects.create_post(title='test_title',
50 reply_post = Post.objects.create_post(title='test_title',
55 text='[post]%d[/post]' % post.id, thread=post.get_thread())
51 text='[post]%d[/post]' % post.id,
52 thread=post.get_thread())
56 reply_reply_post = Post.objects.create_post(title='',
53 reply_reply_post = Post.objects.create_post(title='',
57 text='[post]%d[/post]' % reply_post.id,
54 text='[post]%d[/post]'
58 thread=post.get_thread())
55 % reply_post.id,
56 thread=post.get_thread())
59
57
60 response = Post.objects.generate_response_get([reply_post])
58 response = Post.objects.generate_response_get([reply_post])
61 logger.debug(response)
59 logger.debug(response)
62
60
63 self.assertTrue('<response>'
61 self.assertTrue('<response>'
64 '<status>success</status>'
62 '<status>success</status>'
65 '<models>'
63 '<models>'
66 '<model name="post" ref-id="1">'
64 '<model name="post" ref-id="1">'
67 '<id key="pubkey" local-id="%d" type="test_key_type" />'
65 '<id key="pubkey" local-id="%d" type="test_key_type" />'
68 '<title>test_title</title>'
66 '<title>test_title</title>'
69 '<text>[post]%d[/post]</text>'
67 '<text>[post]%d[/post]</text>'
70 '<thread>%d</thread>'
68 '<thread>%d</thread>'
71 '<pub-time>%s</pub-time>'
69 '<pub-time>%s</pub-time>'
72 '<edit-time>%s</edit-time>'
70 '<edit-time>%s</edit-time>'
73 '<previous>'
71 '<previous>'
74 '<id key="pubkey" local-id="%d" type="test_key_type" />'
72 '<id key="pubkey" local-id="%d" type="test_key_type" />'
75 '</previous>'
73 '</previous>'
76 '<next>'
74 '<next>'
77 '<id key="pubkey" local-id="%d" type="test_key_type" />'
75 '<id key="pubkey" local-id="%d" type="test_key_type" />'
78 '</next>'
76 '</next>'
79 '</model>'
77 '</model>'
80 '</models>'
78 '</models>'
81 '</response>' % (
79 '</response>' % (
82 reply_post.id,
80 reply_post.id,
83 post.id,
81 post.id,
84 post.id,
82 post.id,
85 str(reply_post.get_edit_time_epoch()),
83 str(reply_post.get_edit_time_epoch()),
86 str(reply_post.get_pub_time_epoch()),
84 str(reply_post.get_pub_time_epoch()),
87 post.id,
85 post.id,
88 reply_reply_post.id,
86 reply_reply_post.id,
89 ) in response,
87 ) in response,
90 'Wrong XML generated for the GET response.')
88 'Wrong XML generated for the GET response.')
91
89
92 def _create_post_with_key(self):
90 def _create_post_with_key(self):
93 key = KeyPair(public_key='pubkey', private_key='privkey',
91 key = KeyPair(public_key='pubkey', private_key='privkey',
94 key_type='test_key_type', primary=True)
92 key_type='test_key_type', primary=True)
95 key.save()
93 key.save()
96
94
97 return Post.objects.create_post(title='test_title', text='test_text')
95 return Post.objects.create_post(title='test_title', text='test_text')
General Comments 0
You need to be logged in to leave comments. Login now