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