##// END OF EJS Templates
Don't allow to import the same post twice
neko259 -
r1232:52a45386 decentral
parent child Browse files
Show More
@@ -1,125 +1,128 b''
1 1 import xml.etree.ElementTree as et
2 2 from django.db import transaction
3 3 from boards.models import KeyPair, GlobalId, Signature, Post
4 4
5 5 ENCODING_UNICODE = 'unicode'
6 6
7 7 TAG_MODEL = 'model'
8 8 TAG_REQUEST = 'request'
9 9 TAG_RESPONSE = 'response'
10 10 TAG_ID = 'id'
11 11 TAG_STATUS = 'status'
12 12 TAG_MODELS = 'models'
13 13 TAG_TITLE = 'title'
14 14 TAG_TEXT = 'text'
15 15 TAG_THREAD = 'thread'
16 16 TAG_PUB_TIME = 'pub-time'
17 17 TAG_SIGNATURES = 'signatures'
18 18 TAG_SIGNATURE = 'signature'
19 19 TAG_CONTENT = 'content'
20 20 TAG_ATTACHMENTS = 'attachments'
21 21 TAG_ATTACHMENT = 'attachment'
22 22
23 23 TYPE_GET = 'get'
24 24
25 25 ATTR_VERSION = 'version'
26 26 ATTR_TYPE = 'type'
27 27 ATTR_NAME = 'name'
28 28 ATTR_VALUE = 'value'
29 29 ATTR_MIMETYPE = 'mimetype'
30 30
31 31 STATUS_SUCCESS = 'success'
32 32
33 33
34 34 # TODO Make this fully static
35 35 class SyncManager:
36 36 def generate_response_get(self, model_list: list):
37 37 response = et.Element(TAG_RESPONSE)
38 38
39 39 status = et.SubElement(response, TAG_STATUS)
40 40 status.text = STATUS_SUCCESS
41 41
42 42 models = et.SubElement(response, TAG_MODELS)
43 43
44 44 for post in model_list:
45 45 model = et.SubElement(models, TAG_MODEL)
46 46 model.set(ATTR_NAME, 'post')
47 47
48 48 content_tag = et.SubElement(model, TAG_CONTENT)
49 49
50 50 tag_id = et.SubElement(content_tag, TAG_ID)
51 51 post.global_id.to_xml_element(tag_id)
52 52
53 53 title = et.SubElement(content_tag, TAG_TITLE)
54 54 title.text = post.title
55 55
56 56 text = et.SubElement(content_tag, TAG_TEXT)
57 57 text.text = post.get_sync_text()
58 58
59 59 if not post.is_opening():
60 60 thread = et.SubElement(content_tag, TAG_THREAD)
61 61 thread_id = et.SubElement(thread, TAG_ID)
62 62 post.get_thread().get_opening_post().global_id.to_xml_element(thread_id)
63 63 else:
64 64 # TODO Output tags here
65 65 pass
66 66
67 67 pub_time = et.SubElement(content_tag, TAG_PUB_TIME)
68 68 pub_time.text = str(post.get_pub_time_str())
69 69
70 70 signatures_tag = et.SubElement(model, TAG_SIGNATURES)
71 71 post_signatures = post.signature.all()
72 72 if post_signatures:
73 73 signatures = post.signatures
74 74 else:
75 75 # TODO Maybe the signature can be computed only once after
76 76 # the post is added? Need to add some on_save signal queue
77 77 # and add this there.
78 78 key = KeyPair.objects.get(public_key=post.global_id.key)
79 79 signatures = [Signature(
80 80 key_type=key.key_type,
81 81 key=key.public_key,
82 82 signature=key.sign(et.tostring(model, ENCODING_UNICODE)),
83 83 )]
84 84 for signature in signatures:
85 85 signature_tag = et.SubElement(signatures_tag, TAG_SIGNATURE)
86 86 signature_tag.set(ATTR_TYPE, signature.key_type)
87 87 signature_tag.set(ATTR_VALUE, signature.signature)
88 88
89 89 return et.tostring(response, ENCODING_UNICODE)
90 90
91 91 @transaction.atomic
92 92 def parse_response_get(self, response_xml):
93 93 tag_root = et.fromstring(response_xml)
94 94 tag_status = tag_root.find(TAG_STATUS)
95 95 if STATUS_SUCCESS == tag_status.text:
96 96 tag_models = tag_root.find(TAG_MODELS)
97 97 for tag_model in tag_models:
98 98 tag_content = tag_model.find(TAG_CONTENT)
99 99 tag_id = tag_content.find(TAG_ID)
100 100 try:
101 101 GlobalId.from_xml_element(tag_id, existing=True)
102 102 print('Post with same ID already exists')
103 103 except GlobalId.DoesNotExist:
104 104 global_id = GlobalId.from_xml_element(tag_id)
105 global_id.save()
105
106 if not GlobalId.objects.global_id_exists(global_id):
107 global_id.save()
106 108
107 title = tag_content.find(TAG_TITLE).text
108 text = tag_content.find(TAG_TEXT).text
109 pub_time = tag_content.find(TAG_PUB_TIME).text
110 # TODO Check that the replied posts are already present
111 # before adding new ones
109 title = tag_content.find(TAG_TITLE).text
110 text = tag_content.find(TAG_TEXT).text
111 pub_time = tag_content.find(TAG_PUB_TIME).text
112
113 # TODO Check that the replied posts are already present
114 # before adding new ones
112 115
113 # TODO Pub time, thread, tags
116 # TODO thread, tags
114 117
115 # FIXME This prints are for testing purposes only, they must
116 # be removed after sync is implemented
117 print(title)
118 print(text)
118 # FIXME This prints are for testing purposes only, they must
119 # be removed after sync is implemented
120 print(title)
121 print(text)
119 122
120 post = Post.objects.import_post(title=title, text=text,
121 pub_time=pub_time)
122 post.global_id = global_id
123 post = Post.objects.import_post(title=title, text=text,
124 pub_time=pub_time)
125 post.global_id = global_id
123 126 else:
124 127 # TODO Throw an exception?
125 128 pass
@@ -1,108 +1,117 b''
1 1 import xml.etree.ElementTree as et
2 2 from django.db import models
3 3
4 4
5 5 TAG_MODEL = 'model'
6 6 TAG_REQUEST = 'request'
7 7 TAG_ID = 'id'
8 8
9 9 TYPE_GET = 'get'
10 10
11 11 ATTR_VERSION = 'version'
12 12 ATTR_TYPE = 'type'
13 13 ATTR_NAME = 'name'
14 14
15 15 ATTR_KEY = 'key'
16 16 ATTR_KEY_TYPE = 'type'
17 17 ATTR_LOCAL_ID = 'local-id'
18 18
19 19
20 20 class GlobalIdManager(models.Manager):
21 21 def generate_request_get(self, global_id_list: list):
22 22 """
23 23 Form a get request from a list of ModelId objects.
24 24 """
25 25
26 26 request = et.Element(TAG_REQUEST)
27 27 request.set(ATTR_TYPE, TYPE_GET)
28 28 request.set(ATTR_VERSION, '1.0')
29 29
30 30 model = et.SubElement(request, TAG_MODEL)
31 31 model.set(ATTR_VERSION, '1.0')
32 32 model.set(ATTR_NAME, 'post')
33 33
34 34 for global_id in global_id_list:
35 35 tag_id = et.SubElement(model, TAG_ID)
36 36 global_id.to_xml_element(tag_id)
37 37
38 38 return et.tostring(request, 'unicode')
39 39
40 def global_id_exists(self, global_id):
41 """
42 Checks if the same global id already exists in the system.
43 """
44
45 return self.filter(key=global_id.key,
46 key_type=global_id.key_type,
47 local_id=global_id.local_id).exists()
48
40 49
41 50 class GlobalId(models.Model):
42 51 class Meta:
43 52 app_label = 'boards'
44 53
45 54 objects = GlobalIdManager()
46 55
47 56 def __init__(self, *args, **kwargs):
48 57 models.Model.__init__(self, *args, **kwargs)
49 58
50 59 if 'key' in kwargs and 'key_type' in kwargs and 'local_id' in kwargs:
51 60 self.key = kwargs['key']
52 61 self.key_type = kwargs['key_type']
53 62 self.local_id = kwargs['local_id']
54 63
55 64 key = models.TextField()
56 65 key_type = models.TextField()
57 66 local_id = models.IntegerField()
58 67
59 68 def __str__(self):
60 69 return '%s::%s::%d' % (self.key_type, self.key, self.local_id)
61 70
62 71 def to_xml_element(self, element: et.Element):
63 72 """
64 73 Exports global id to an XML element.
65 74 """
66 75
67 76 element.set(ATTR_KEY, self.key)
68 77 element.set(ATTR_KEY_TYPE, self.key_type)
69 78 element.set(ATTR_LOCAL_ID, str(self.local_id))
70 79
71 80 @staticmethod
72 81 def from_xml_element(element: et.Element, existing=False):
73 82 """
74 83 Parses XML id tag and gets global id from it.
75 84
76 85 Arguments:
77 86 element -- the XML 'id' element
78 87 existing -- if this is False, a new instance of GlobalId will be
79 88 created. Otherwise, we will search for an existing GlobalId instance
80 89 and throw DoesNotExist if there isn't one.
81 90 """
82 91
83 92 if existing:
84 93 return GlobalId.objects.get(key=element.get(ATTR_KEY),
85 94 key_type=element.get(ATTR_KEY_TYPE),
86 95 local_id=int(element.get(
87 96 ATTR_LOCAL_ID)))
88 97 else:
89 98 return GlobalId(key=element.get(ATTR_KEY),
90 99 key_type=element.get(ATTR_KEY_TYPE),
91 100 local_id=int(element.get(ATTR_LOCAL_ID)))
92 101
93 102
94 103 class Signature(models.Model):
95 104 class Meta:
96 105 app_label = 'boards'
97 106
98 107 def __init__(self, *args, **kwargs):
99 108 models.Model.__init__(self, *args, **kwargs)
100 109
101 110 if 'key' in kwargs and 'key_type' in kwargs and 'signature' in kwargs:
102 111 self.key_type = kwargs['key_type']
103 112 self.key = kwargs['key']
104 113 self.signature = kwargs['signature']
105 114
106 115 key_type = models.TextField()
107 116 key = models.TextField()
108 117 signature = models.TextField()
@@ -1,56 +1,60 b''
1 1 from boards.models import KeyPair, Post
2 2 from boards.models.post.sync import SyncManager
3 3 from boards.tests.mocks import MockRequest
4 4 from boards.views.sync import response_get
5 5
6 6 __author__ = 'neko259'
7 7
8 8
9 9 from django.test import TestCase
10 10
11 11
12 12 class SyncTest(TestCase):
13 13 def test_get(self):
14 14 """
15 15 Forms a GET request of a post and checks the response.
16 16 """
17 17
18 18 KeyPair.objects.generate_key(primary=True)
19 19 post = Post.objects.create_post(title='test_title', text='test_text')
20 20
21 21 request = MockRequest()
22 22 request.body = (
23 23 '<request type="get" version="1.0">'
24 24 '<model name="post" version="1.0">'
25 25 '<id key="%s" local-id="%d" type="%s" />'
26 26 '</model>'
27 27 '</request>' % (post.global_id.key,
28 28 post.id,
29 29 post.global_id.key_type)
30 30 )
31 31
32 32 response = response_get(request).content.decode()
33 33 self.assertTrue(
34 34 '<status>success</status>'
35 35 '<models>'
36 36 '<model name="post">'
37 37 '<content>'
38 38 '<id key="%s" local-id="%d" type="%s" />'
39 39 '<title>%s</title>'
40 40 '<text>%s</text>'
41 41 '<pub-time>%s</pub-time>'
42 42 '</content>' % (
43 43 post.global_id.key,
44 44 post.id,
45 45 post.global_id.key_type,
46 46 post.title,
47 47 post.get_raw_text(),
48 48 post.get_pub_time_str(),
49 49 ) in response_get(request).content.decode(),
50 50 'Wrong response generated for the GET request.')
51 51
52 52 post.delete()
53 53
54 54 SyncManager().parse_response_get(response)
55 55 self.assertEqual(1, Post.objects.count(),
56 56 'Post was not created from XML response.')
57
58 SyncManager().parse_response_get(response)
59 self.assertEqual(1, Post.objects.count(),
60 'The same post was imported twice.')
General Comments 0
You need to be logged in to leave comments. Login now