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