##// END OF EJS Templates
Added ability to filter posts in the LIST request
neko259 -
r1834:e64fc2ba default
parent child Browse files
Show More
@@ -0,0 +1,31 b''
1 import xml.etree.ElementTree as et
2
3 from boards.models import Post
4
5 TAG_THREAD = 'thread'
6
7
8 class PostFilter:
9 def __init__(self, content=None):
10 self.content = content
11
12 def filter(self, posts):
13 return posts
14
15 def add_filter(self, model_tag, value):
16 return model_tag
17
18
19 class ThreadFilter(PostFilter):
20 def filter(self, posts):
21 op_id = self.content.text
22
23 op = Post.objects.filter(opening=True, id=op_id).first()
24 if op:
25 return posts.filter(thread=op.get_thread())
26 else:
27 return posts.none()
28
29 def add_filter(self, model_tag, value):
30 thread_tag = et.SubElement(model_tag, TAG_THREAD)
31 thread_tag.text = str(value)
@@ -1,96 +1,99 b''
1 import re
1 import re
2 import logging
2 import logging
3 import xml.etree.ElementTree as ET
3 import xml.etree.ElementTree as ET
4
4
5 import httplib2
5 import httplib2
6 from django.core.management import BaseCommand
6 from django.core.management import BaseCommand
7
7
8 from boards.models import GlobalId
8 from boards.models import GlobalId
9 from boards.models.post.sync import SyncManager, TAG_ID, TAG_VERSION
9 from boards.models.post.sync import SyncManager, TAG_ID, TAG_VERSION
10
10
11 __author__ = 'neko259'
11 __author__ = 'neko259'
12
12
13
13
14 REGEX_GLOBAL_ID = re.compile(r'(\w+)::([\w\+/]+)::(\d+)')
14 REGEX_GLOBAL_ID = re.compile(r'(\w+)::([\w\+/]+)::(\d+)')
15
15
16
16
17 class Command(BaseCommand):
17 class Command(BaseCommand):
18 help = 'Send a sync or get request to the server.'
18 help = 'Send a sync or get request to the server.'
19
19
20 def add_arguments(self, parser):
20 def add_arguments(self, parser):
21 parser.add_argument('url', type=str, help='Server root url')
21 parser.add_argument('url', type=str, help='Server root url')
22 parser.add_argument('--global-id', type=str, default='',
22 parser.add_argument('--global-id', type=str, default='',
23 help='Post global ID')
23 help='Post global ID')
24 parser.add_argument('--split-query', type=int, default=1,
24 parser.add_argument('--split-query', type=int, default=1,
25 help='Split GET query into separate by the given'
25 help='Split GET query into separate by the given'
26 ' number of posts in one')
26 ' number of posts in one')
27 parser.add_argument('--thread', type=int,
28 help='Get posts of one specific thread')
27
29
28 def handle(self, *args, **options):
30 def handle(self, *args, **options):
29 logger = logging.getLogger('boards.sync')
31 logger = logging.getLogger('boards.sync')
30
32
31 url = options.get('url')
33 url = options.get('url')
32
34
33 list_url = url + 'api/sync/list/'
35 list_url = url + 'api/sync/list/'
34 get_url = url + 'api/sync/get/'
36 get_url = url + 'api/sync/get/'
35 file_url = url[:-1]
37 file_url = url[:-1]
36
38
37 global_id_str = options.get('global_id')
39 global_id_str = options.get('global_id')
38 if global_id_str:
40 if global_id_str:
39 match = REGEX_GLOBAL_ID.match(global_id_str)
41 match = REGEX_GLOBAL_ID.match(global_id_str)
40 if match:
42 if match:
41 key_type = match.group(1)
43 key_type = match.group(1)
42 key = match.group(2)
44 key = match.group(2)
43 local_id = match.group(3)
45 local_id = match.group(3)
44
46
45 global_id = GlobalId(key_type=key_type, key=key,
47 global_id = GlobalId(key_type=key_type, key=key,
46 local_id=local_id)
48 local_id=local_id)
47
49
48 xml = GlobalId.objects.generate_request_get([global_id])
50 xml = SyncManager.generate_request_get([global_id])
49 h = httplib2.Http()
51 h = httplib2.Http()
50 response, content = h.request(get_url, method="POST", body=xml)
52 response, content = h.request(get_url, method="POST", body=xml)
51
53
52 SyncManager.parse_response_get(content, file_url)
54 SyncManager.parse_response_get(content, file_url)
53 else:
55 else:
54 raise Exception('Invalid global ID')
56 raise Exception('Invalid global ID')
55 else:
57 else:
56 logger.info('Running LIST request...')
58 logger.info('Running LIST request...')
57 h = httplib2.Http()
59 h = httplib2.Http()
58 xml = GlobalId.objects.generate_request_list()
60 xml = SyncManager.generate_request_list(
61 opening_post=options.get('thread'))
59 response, content = h.request(list_url, method="POST", body=xml)
62 response, content = h.request(list_url, method="POST", body=xml)
60 logger.info('Processing response...')
63 logger.info('Processing response...')
61
64
62 root = ET.fromstring(content)
65 root = ET.fromstring(content)
63 status = root.findall('status')[0].text
66 status = root.findall('status')[0].text
64 if status == 'success':
67 if status == 'success':
65 ids_to_sync = list()
68 ids_to_sync = list()
66
69
67 models = root.findall('models')[0]
70 models = root.findall('models')[0]
68 for model in models:
71 for model in models:
69 tag_id = model.find(TAG_ID)
72 tag_id = model.find(TAG_ID)
70 global_id, exists = GlobalId.from_xml_element(tag_id)
73 global_id, exists = GlobalId.from_xml_element(tag_id)
71 tag_version = model.find(TAG_VERSION)
74 tag_version = model.find(TAG_VERSION)
72 if tag_version is not None:
75 if tag_version is not None:
73 version = int(tag_version.text) or 1
76 version = int(tag_version.text) or 1
74 else:
77 else:
75 version = 1
78 version = 1
76 if not exists or global_id.post.version < version:
79 if not exists or global_id.post.version < version:
77 logger.debug('Processed (+) post {}'.format(global_id))
80 logger.debug('Processed (+) post {}'.format(global_id))
78 ids_to_sync.append(global_id)
81 ids_to_sync.append(global_id)
79 else:
82 else:
80 logger.debug('* Processed (-) post {}'.format(global_id))
83 logger.debug('* Processed (-) post {}'.format(global_id))
81 logger.info('Starting sync...')
84 logger.info('Starting sync...')
82
85
83 if len(ids_to_sync) > 0:
86 if len(ids_to_sync) > 0:
84 limit = options.get('split_query', len(ids_to_sync))
87 limit = options.get('split_query', len(ids_to_sync))
85 for offset in range(0, len(ids_to_sync), limit):
88 for offset in range(0, len(ids_to_sync), limit):
86 xml = GlobalId.objects.generate_request_get(ids_to_sync[offset:offset+limit])
89 xml = SyncManager.generate_request_get(ids_to_sync[offset:offset + limit])
87 h = httplib2.Http()
90 h = httplib2.Http()
88 logger.info('Running GET request...')
91 logger.info('Running GET request...')
89 response, content = h.request(get_url, method="POST", body=xml)
92 response, content = h.request(get_url, method="POST", body=xml)
90 logger.info('Processing response...')
93 logger.info('Processing response...')
91
94
92 SyncManager.parse_response_get(content, file_url)
95 SyncManager.parse_response_get(content, file_url)
93 else:
96 else:
94 logger.info('Nothing to get, everything synced')
97 logger.info('Nothing to get, everything synced')
95 else:
98 else:
96 raise Exception('Invalid response status')
99 raise Exception('Invalid response status')
@@ -1,331 +1,378 b''
1 import xml.etree.ElementTree as et
1 import xml.etree.ElementTree as et
2 import logging
2 import logging
3 from xml.etree import ElementTree
3
4
4 from boards.abstracts.exceptions import SyncException
5 from boards.abstracts.exceptions import SyncException
6 from boards.abstracts.sync_filters import ThreadFilter
5 from boards.models import KeyPair, GlobalId, Signature, Post, Tag
7 from boards.models import KeyPair, GlobalId, Signature, Post, Tag
6 from boards.models.attachment.downloaders import download
8 from boards.models.attachment.downloaders import download
9 from boards.models.signature import TAG_REQUEST, ATTR_TYPE, TYPE_GET, \
10 ATTR_VERSION, TAG_MODEL, ATTR_NAME, TAG_ID, TYPE_LIST
7 from boards.utils import get_file_mimetype, get_file_hash
11 from boards.utils import get_file_mimetype, get_file_hash
8 from django.db import transaction
12 from django.db import transaction
9
13
10 EXCEPTION_NODE = 'Sync node returned an error: {}.'
14 EXCEPTION_NODE = 'Sync node returned an error: {}.'
11 EXCEPTION_DOWNLOAD = 'File was not downloaded.'
15 EXCEPTION_DOWNLOAD = 'File was not downloaded.'
12 EXCEPTION_HASH = 'File hash does not match attachment hash.'
16 EXCEPTION_HASH = 'File hash does not match attachment hash.'
13 EXCEPTION_SIGNATURE = 'Invalid model signature for {}.'
17 EXCEPTION_SIGNATURE = 'Invalid model signature for {}.'
14 EXCEPTION_AUTHOR_SIGNATURE = 'Model {} has no author signature.'
18 EXCEPTION_AUTHOR_SIGNATURE = 'Model {} has no author signature.'
15 ENCODING_UNICODE = 'unicode'
19 ENCODING_UNICODE = 'unicode'
16
20
17 TAG_MODEL = 'model'
21 TAG_MODEL = 'model'
18 TAG_REQUEST = 'request'
22 TAG_REQUEST = 'request'
19 TAG_RESPONSE = 'response'
23 TAG_RESPONSE = 'response'
20 TAG_ID = 'id'
24 TAG_ID = 'id'
21 TAG_STATUS = 'status'
25 TAG_STATUS = 'status'
22 TAG_MODELS = 'models'
26 TAG_MODELS = 'models'
23 TAG_TITLE = 'title'
27 TAG_TITLE = 'title'
24 TAG_TEXT = 'text'
28 TAG_TEXT = 'text'
25 TAG_THREAD = 'thread'
29 TAG_THREAD = 'thread'
26 TAG_PUB_TIME = 'pub-time'
30 TAG_PUB_TIME = 'pub-time'
27 TAG_SIGNATURES = 'signatures'
31 TAG_SIGNATURES = 'signatures'
28 TAG_SIGNATURE = 'signature'
32 TAG_SIGNATURE = 'signature'
29 TAG_CONTENT = 'content'
33 TAG_CONTENT = 'content'
30 TAG_ATTACHMENTS = 'attachments'
34 TAG_ATTACHMENTS = 'attachments'
31 TAG_ATTACHMENT = 'attachment'
35 TAG_ATTACHMENT = 'attachment'
32 TAG_TAGS = 'tags'
36 TAG_TAGS = 'tags'
33 TAG_TAG = 'tag'
37 TAG_TAG = 'tag'
34 TAG_ATTACHMENT_REFS = 'attachment-refs'
38 TAG_ATTACHMENT_REFS = 'attachment-refs'
35 TAG_ATTACHMENT_REF = 'attachment-ref'
39 TAG_ATTACHMENT_REF = 'attachment-ref'
36 TAG_TRIPCODE = 'tripcode'
40 TAG_TRIPCODE = 'tripcode'
37 TAG_VERSION = 'version'
41 TAG_VERSION = 'version'
38
42
39 TYPE_GET = 'get'
43 TYPE_GET = 'get'
40
44
41 ATTR_VERSION = 'version'
45 ATTR_VERSION = 'version'
42 ATTR_TYPE = 'type'
46 ATTR_TYPE = 'type'
43 ATTR_NAME = 'name'
47 ATTR_NAME = 'name'
44 ATTR_VALUE = 'value'
48 ATTR_VALUE = 'value'
45 ATTR_MIMETYPE = 'mimetype'
49 ATTR_MIMETYPE = 'mimetype'
46 ATTR_KEY = 'key'
50 ATTR_KEY = 'key'
47 ATTR_REF = 'ref'
51 ATTR_REF = 'ref'
48 ATTR_URL = 'url'
52 ATTR_URL = 'url'
49 ATTR_ID_TYPE = 'id-type'
53 ATTR_ID_TYPE = 'id-type'
50
54
51 ID_TYPE_MD5 = 'md5'
55 ID_TYPE_MD5 = 'md5'
52 ID_TYPE_URL = 'url'
56 ID_TYPE_URL = 'url'
53
57
54 STATUS_SUCCESS = 'success'
58 STATUS_SUCCESS = 'success'
55
59
56
60
57 logger = logging.getLogger('boards.sync')
61 logger = logging.getLogger('boards.sync')
58
62
59
63
60 class SyncManager:
64 class SyncManager:
61 @staticmethod
65 @staticmethod
62 def generate_response_get(model_list: list):
66 def generate_response_get(model_list: list):
63 response = et.Element(TAG_RESPONSE)
67 response = et.Element(TAG_RESPONSE)
64
68
65 status = et.SubElement(response, TAG_STATUS)
69 status = et.SubElement(response, TAG_STATUS)
66 status.text = STATUS_SUCCESS
70 status.text = STATUS_SUCCESS
67
71
68 models = et.SubElement(response, TAG_MODELS)
72 models = et.SubElement(response, TAG_MODELS)
69
73
70 for post in model_list:
74 for post in model_list:
71 model = et.SubElement(models, TAG_MODEL)
75 model = et.SubElement(models, TAG_MODEL)
72 model.set(ATTR_NAME, 'post')
76 model.set(ATTR_NAME, 'post')
73
77
74 global_id = post.global_id
78 global_id = post.global_id
75
79
76 attachments = post.attachments.all()
80 attachments = post.attachments.all()
77 if global_id.content:
81 if global_id.content:
78 model.append(et.fromstring(global_id.content))
82 model.append(et.fromstring(global_id.content))
79 if len(attachments) > 0:
83 if len(attachments) > 0:
80 internal_attachments = False
84 internal_attachments = False
81 for attachment in attachments:
85 for attachment in attachments:
82 if attachment.is_internal():
86 if attachment.is_internal():
83 internal_attachments = True
87 internal_attachments = True
84 break
88 break
85
89
86 if internal_attachments:
90 if internal_attachments:
87 attachment_refs = et.SubElement(model, TAG_ATTACHMENT_REFS)
91 attachment_refs = et.SubElement(model, TAG_ATTACHMENT_REFS)
88 for file in attachments:
92 for file in attachments:
89 SyncManager._attachment_to_xml(
93 SyncManager._attachment_to_xml(
90 None, attachment_refs, file)
94 None, attachment_refs, file)
91 else:
95 else:
92 content_tag = et.SubElement(model, TAG_CONTENT)
96 content_tag = et.SubElement(model, TAG_CONTENT)
93
97
94 tag_id = et.SubElement(content_tag, TAG_ID)
98 tag_id = et.SubElement(content_tag, TAG_ID)
95 global_id.to_xml_element(tag_id)
99 global_id.to_xml_element(tag_id)
96
100
97 title = et.SubElement(content_tag, TAG_TITLE)
101 title = et.SubElement(content_tag, TAG_TITLE)
98 title.text = post.title
102 title.text = post.title
99
103
100 text = et.SubElement(content_tag, TAG_TEXT)
104 text = et.SubElement(content_tag, TAG_TEXT)
101 text.text = post.get_sync_text()
105 text.text = post.get_sync_text()
102
106
103 thread = post.get_thread()
107 thread = post.get_thread()
104 if post.is_opening():
108 if post.is_opening():
105 tag_tags = et.SubElement(content_tag, TAG_TAGS)
109 tag_tags = et.SubElement(content_tag, TAG_TAGS)
106 for tag in thread.get_tags():
110 for tag in thread.get_tags():
107 tag_tag = et.SubElement(tag_tags, TAG_TAG)
111 tag_tag = et.SubElement(tag_tags, TAG_TAG)
108 tag_tag.text = tag.name
112 tag_tag.text = tag.name
109 else:
113 else:
110 tag_thread = et.SubElement(content_tag, TAG_THREAD)
114 tag_thread = et.SubElement(content_tag, TAG_THREAD)
111 thread_id = et.SubElement(tag_thread, TAG_ID)
115 thread_id = et.SubElement(tag_thread, TAG_ID)
112 thread.get_opening_post().global_id.to_xml_element(thread_id)
116 thread.get_opening_post().global_id.to_xml_element(thread_id)
113
117
114 pub_time = et.SubElement(content_tag, TAG_PUB_TIME)
118 pub_time = et.SubElement(content_tag, TAG_PUB_TIME)
115 pub_time.text = str(post.get_pub_time_str())
119 pub_time.text = str(post.get_pub_time_str())
116
120
117 if post.tripcode:
121 if post.tripcode:
118 tripcode = et.SubElement(content_tag, TAG_TRIPCODE)
122 tripcode = et.SubElement(content_tag, TAG_TRIPCODE)
119 tripcode.text = post.tripcode
123 tripcode.text = post.tripcode
120
124
121 if len(attachments) > 0:
125 if len(attachments) > 0:
122 attachments_tag = et.SubElement(content_tag, TAG_ATTACHMENTS)
126 attachments_tag = et.SubElement(content_tag, TAG_ATTACHMENTS)
123
127
124 internal_attachments = False
128 internal_attachments = False
125 for attachment in attachments:
129 for attachment in attachments:
126 if attachment.is_internal():
130 if attachment.is_internal():
127 internal_attachments = True
131 internal_attachments = True
128 break
132 break
129
133
130 if internal_attachments:
134 if internal_attachments:
131 attachment_refs = et.SubElement(model, TAG_ATTACHMENT_REFS)
135 attachment_refs = et.SubElement(model, TAG_ATTACHMENT_REFS)
132 else:
136 else:
133 attachment_refs = None
137 attachment_refs = None
134
138
135 for file in attachments:
139 for file in attachments:
136 SyncManager._attachment_to_xml(
140 SyncManager._attachment_to_xml(
137 attachments_tag, attachment_refs, file)
141 attachments_tag, attachment_refs, file)
138 version_tag = et.SubElement(content_tag, TAG_VERSION)
142 version_tag = et.SubElement(content_tag, TAG_VERSION)
139 version_tag.text = str(post.version)
143 version_tag.text = str(post.version)
140
144
141 global_id.content = et.tostring(content_tag, ENCODING_UNICODE)
145 global_id.content = et.tostring(content_tag, ENCODING_UNICODE)
142 global_id.save()
146 global_id.save()
143
147
144 signatures_tag = et.SubElement(model, TAG_SIGNATURES)
148 signatures_tag = et.SubElement(model, TAG_SIGNATURES)
145 post_signatures = global_id.signature_set.all()
149 post_signatures = global_id.signature_set.all()
146 if post_signatures:
150 if post_signatures:
147 signatures = post_signatures
151 signatures = post_signatures
148 else:
152 else:
149 key = KeyPair.objects.get(public_key=global_id.key)
153 key = KeyPair.objects.get(public_key=global_id.key)
150 signature = Signature(
154 signature = Signature(
151 key_type=key.key_type,
155 key_type=key.key_type,
152 key=key.public_key,
156 key=key.public_key,
153 signature=key.sign(global_id.content),
157 signature=key.sign(global_id.content),
154 global_id=global_id,
158 global_id=global_id,
155 )
159 )
156 signature.save()
160 signature.save()
157 signatures = [signature]
161 signatures = [signature]
158 for signature in signatures:
162 for signature in signatures:
159 signature_tag = et.SubElement(signatures_tag, TAG_SIGNATURE)
163 signature_tag = et.SubElement(signatures_tag, TAG_SIGNATURE)
160 signature_tag.set(ATTR_TYPE, signature.key_type)
164 signature_tag.set(ATTR_TYPE, signature.key_type)
161 signature_tag.set(ATTR_VALUE, signature.signature)
165 signature_tag.set(ATTR_VALUE, signature.signature)
162 signature_tag.set(ATTR_KEY, signature.key)
166 signature_tag.set(ATTR_KEY, signature.key)
163
167
164 return et.tostring(response, ENCODING_UNICODE)
168 return et.tostring(response, ENCODING_UNICODE)
165
169
166 @staticmethod
170 @staticmethod
167 def parse_response_get(response_xml, hostname):
171 def parse_response_get(response_xml, hostname):
168 tag_root = et.fromstring(response_xml)
172 tag_root = et.fromstring(response_xml)
169 tag_status = tag_root.find(TAG_STATUS)
173 tag_status = tag_root.find(TAG_STATUS)
170 if STATUS_SUCCESS == tag_status.text:
174 if STATUS_SUCCESS == tag_status.text:
171 tag_models = tag_root.find(TAG_MODELS)
175 tag_models = tag_root.find(TAG_MODELS)
172 for tag_model in tag_models:
176 for tag_model in tag_models:
173 SyncManager.parse_post(tag_model, hostname)
177 SyncManager.parse_post(tag_model, hostname)
174 else:
178 else:
175 raise SyncException(EXCEPTION_NODE.format(tag_status.text))
179 raise SyncException(EXCEPTION_NODE.format(tag_status.text))
176
180
177 @staticmethod
181 @staticmethod
178 @transaction.atomic
182 @transaction.atomic
179 def parse_post(tag_model, hostname):
183 def parse_post(tag_model, hostname):
180 tag_content = tag_model.find(TAG_CONTENT)
184 tag_content = tag_model.find(TAG_CONTENT)
181
185
182 content_str = et.tostring(tag_content, ENCODING_UNICODE)
186 content_str = et.tostring(tag_content, ENCODING_UNICODE)
183
187
184 tag_id = tag_content.find(TAG_ID)
188 tag_id = tag_content.find(TAG_ID)
185 global_id, exists = GlobalId.from_xml_element(tag_id)
189 global_id, exists = GlobalId.from_xml_element(tag_id)
186 signatures = SyncManager._verify_model(global_id, content_str, tag_model)
190 signatures = SyncManager._verify_model(global_id, content_str, tag_model)
187
191
188 version = int(tag_content.find(TAG_VERSION).text)
192 version = int(tag_content.find(TAG_VERSION).text)
189 is_old = exists and global_id.post.version < version
193 is_old = exists and global_id.post.version < version
190 if exists and not is_old:
194 if exists and not is_old:
191 print('Post with same ID exists and is up to date.')
195 print('Post with same ID exists and is up to date.')
192 else:
196 else:
193 global_id.content = content_str
197 global_id.content = content_str
194 global_id.save()
198 global_id.save()
195 for signature in signatures:
199 for signature in signatures:
196 signature.global_id = global_id
200 signature.global_id = global_id
197 signature.save()
201 signature.save()
198
202
199 title = tag_content.find(TAG_TITLE).text or ''
203 title = tag_content.find(TAG_TITLE).text or ''
200 text = tag_content.find(TAG_TEXT).text or ''
204 text = tag_content.find(TAG_TEXT).text or ''
201 pub_time = tag_content.find(TAG_PUB_TIME).text
205 pub_time = tag_content.find(TAG_PUB_TIME).text
202 tripcode_tag = tag_content.find(TAG_TRIPCODE)
206 tripcode_tag = tag_content.find(TAG_TRIPCODE)
203 if tripcode_tag is not None:
207 if tripcode_tag is not None:
204 tripcode = tripcode_tag.text or ''
208 tripcode = tripcode_tag.text or ''
205 else:
209 else:
206 tripcode = ''
210 tripcode = ''
207
211
208 thread = tag_content.find(TAG_THREAD)
212 thread = tag_content.find(TAG_THREAD)
209 tags = []
213 tags = []
210 if thread:
214 if thread:
211 thread_id = thread.find(TAG_ID)
215 thread_id = thread.find(TAG_ID)
212 op_global_id, exists = GlobalId.from_xml_element(thread_id)
216 op_global_id, exists = GlobalId.from_xml_element(thread_id)
213 if exists:
217 if exists:
214 opening_post = Post.objects.get(global_id=op_global_id)
218 opening_post = Post.objects.get(global_id=op_global_id)
215 else:
219 else:
216 logger.debug('No thread exists for post {}'.format(global_id))
220 logger.debug('No thread exists for post {}'.format(global_id))
217 else:
221 else:
218 opening_post = None
222 opening_post = None
219 tag_tags = tag_content.find(TAG_TAGS)
223 tag_tags = tag_content.find(TAG_TAGS)
220 for tag_tag in tag_tags:
224 for tag_tag in tag_tags:
221 tag, created = Tag.objects.get_or_create(
225 tag, created = Tag.objects.get_or_create(
222 name=tag_tag.text)
226 name=tag_tag.text)
223 tags.append(tag)
227 tags.append(tag)
224
228
225 # TODO Check that the replied posts are already present
229 # TODO Check that the replied posts are already present
226 # before adding new ones
230 # before adding new ones
227
231
228 files = []
232 files = []
229 urls = []
233 urls = []
230 tag_attachments = tag_content.find(TAG_ATTACHMENTS) or list()
234 tag_attachments = tag_content.find(TAG_ATTACHMENTS) or list()
231 tag_refs = tag_model.find(TAG_ATTACHMENT_REFS)
235 tag_refs = tag_model.find(TAG_ATTACHMENT_REFS)
232 for attachment in tag_attachments:
236 for attachment in tag_attachments:
233 if attachment.get(ATTR_ID_TYPE) == ID_TYPE_URL:
237 if attachment.get(ATTR_ID_TYPE) == ID_TYPE_URL:
234 urls.append(attachment.text)
238 urls.append(attachment.text)
235 else:
239 else:
236 tag_ref = tag_refs.find("{}[@ref='{}']".format(
240 tag_ref = tag_refs.find("{}[@ref='{}']".format(
237 TAG_ATTACHMENT_REF, attachment.text))
241 TAG_ATTACHMENT_REF, attachment.text))
238 url = tag_ref.get(ATTR_URL)
242 url = tag_ref.get(ATTR_URL)
239 attached_file = download(hostname + url, validate=False)
243 attached_file = download(hostname + url, validate=False)
240 if attached_file is None:
244 if attached_file is None:
241 raise SyncException(EXCEPTION_DOWNLOAD)
245 raise SyncException(EXCEPTION_DOWNLOAD)
242
246
243 hash = get_file_hash(attached_file)
247 hash = get_file_hash(attached_file)
244 if hash != attachment.text:
248 if hash != attachment.text:
245 raise SyncException(EXCEPTION_HASH)
249 raise SyncException(EXCEPTION_HASH)
246
250
247 files.append(attached_file)
251 files.append(attached_file)
248
252
249 if is_old:
253 if is_old:
250 post = global_id.post
254 post = global_id.post
251 Post.objects.update_post(
255 Post.objects.update_post(
252 post, title=title, text=text, pub_time=pub_time,
256 post, title=title, text=text, pub_time=pub_time,
253 tags=tags, files=files, file_urls=urls,
257 tags=tags, files=files, file_urls=urls,
254 tripcode=tripcode, version=version)
258 tripcode=tripcode, version=version)
255 logger.debug('Parsed updated post {}'.format(global_id))
259 logger.debug('Parsed updated post {}'.format(global_id))
256 else:
260 else:
257 Post.objects.import_post(
261 Post.objects.import_post(
258 title=title, text=text, pub_time=pub_time,
262 title=title, text=text, pub_time=pub_time,
259 opening_post=opening_post, tags=tags,
263 opening_post=opening_post, tags=tags,
260 global_id=global_id, files=files,
264 global_id=global_id, files=files,
261 file_urls=urls, tripcode=tripcode,
265 file_urls=urls, tripcode=tripcode,
262 version=version)
266 version=version)
263 logger.debug('Parsed new post {}'.format(global_id))
267 logger.debug('Parsed new post {}'.format(global_id))
264
268
265 @staticmethod
269 @staticmethod
266 def generate_response_list():
270 def generate_response_list(filters):
267 response = et.Element(TAG_RESPONSE)
271 response = et.Element(TAG_RESPONSE)
268
272
269 status = et.SubElement(response, TAG_STATUS)
273 status = et.SubElement(response, TAG_STATUS)
270 status.text = STATUS_SUCCESS
274 status.text = STATUS_SUCCESS
271
275
272 models = et.SubElement(response, TAG_MODELS)
276 models = et.SubElement(response, TAG_MODELS)
273
277
274 for post in Post.objects.prefetch_related('global_id').all():
278 posts = Post.objects.prefetch_related('global_id')
279 for post_filter in filters:
280 posts = post_filter.filter(posts)
281
282 for post in posts:
275 tag_model = et.SubElement(models, TAG_MODEL)
283 tag_model = et.SubElement(models, TAG_MODEL)
276 tag_id = et.SubElement(tag_model, TAG_ID)
284 tag_id = et.SubElement(tag_model, TAG_ID)
277 post.global_id.to_xml_element(tag_id)
285 post.global_id.to_xml_element(tag_id)
278 tag_version = et.SubElement(tag_model, TAG_VERSION)
286 tag_version = et.SubElement(tag_model, TAG_VERSION)
279 tag_version.text = str(post.version)
287 tag_version.text = str(post.version)
280
288
281 return et.tostring(response, ENCODING_UNICODE)
289 return et.tostring(response, ENCODING_UNICODE)
282
290
283 @staticmethod
291 @staticmethod
284 def _verify_model(global_id, content_str, tag_model):
292 def _verify_model(global_id, content_str, tag_model):
285 """
293 """
286 Verifies all signatures for a single model.
294 Verifies all signatures for a single model.
287 """
295 """
288
296
289 signatures = []
297 signatures = []
290
298
291 tag_signatures = tag_model.find(TAG_SIGNATURES)
299 tag_signatures = tag_model.find(TAG_SIGNATURES)
292 has_author_signature = False
300 has_author_signature = False
293 for tag_signature in tag_signatures:
301 for tag_signature in tag_signatures:
294 signature_type = tag_signature.get(ATTR_TYPE)
302 signature_type = tag_signature.get(ATTR_TYPE)
295 signature_value = tag_signature.get(ATTR_VALUE)
303 signature_value = tag_signature.get(ATTR_VALUE)
296 signature_key = tag_signature.get(ATTR_KEY)
304 signature_key = tag_signature.get(ATTR_KEY)
297
305
298 if global_id.key_type == signature_type and\
306 if global_id.key_type == signature_type and\
299 global_id.key == signature_key:
307 global_id.key == signature_key:
300 has_author_signature = True
308 has_author_signature = True
301
309
302 signature = Signature(key_type=signature_type,
310 signature = Signature(key_type=signature_type,
303 key=signature_key,
311 key=signature_key,
304 signature=signature_value)
312 signature=signature_value)
305
313
306 if not KeyPair.objects.verify(signature, content_str):
314 if not KeyPair.objects.verify(signature, content_str):
307 raise SyncException(EXCEPTION_SIGNATURE.format(content_str))
315 raise SyncException(EXCEPTION_SIGNATURE.format(content_str))
308
316
309 signatures.append(signature)
317 signatures.append(signature)
310 if not has_author_signature:
318 if not has_author_signature:
311 raise SyncException(EXCEPTION_AUTHOR_SIGNATURE.format(content_str))
319 raise SyncException(EXCEPTION_AUTHOR_SIGNATURE.format(content_str))
312
320
313 return signatures
321 return signatures
314
322
315 @staticmethod
323 @staticmethod
316 def _attachment_to_xml(tag_attachments, tag_refs, attachment):
324 def _attachment_to_xml(tag_attachments, tag_refs, attachment):
317 if tag_attachments is not None:
325 if tag_attachments is not None:
318 attachment_tag = et.SubElement(tag_attachments, TAG_ATTACHMENT)
326 attachment_tag = et.SubElement(tag_attachments, TAG_ATTACHMENT)
319 if attachment.is_internal():
327 if attachment.is_internal():
320 mimetype = get_file_mimetype(attachment.file.file)
328 mimetype = get_file_mimetype(attachment.file.file)
321 attachment_tag.set(ATTR_MIMETYPE, mimetype)
329 attachment_tag.set(ATTR_MIMETYPE, mimetype)
322 attachment_tag.set(ATTR_ID_TYPE, ID_TYPE_MD5)
330 attachment_tag.set(ATTR_ID_TYPE, ID_TYPE_MD5)
323 attachment_tag.text = attachment.hash
331 attachment_tag.text = attachment.hash
324 else:
332 else:
325 attachment_tag.set(ATTR_ID_TYPE, ID_TYPE_URL)
333 attachment_tag.set(ATTR_ID_TYPE, ID_TYPE_URL)
326 attachment_tag.text = attachment.url
334 attachment_tag.text = attachment.url
327
335
328 if tag_refs is not None:
336 if tag_refs is not None:
329 attachment_ref = et.SubElement(tag_refs, TAG_ATTACHMENT_REF)
337 attachment_ref = et.SubElement(tag_refs, TAG_ATTACHMENT_REF)
330 attachment_ref.set(ATTR_REF, attachment.hash)
338 attachment_ref.set(ATTR_REF, attachment.hash)
331 attachment_ref.set(ATTR_URL, attachment.file.url)
339 attachment_ref.set(ATTR_URL, attachment.file.url)
340
341 @staticmethod
342 def generate_request_get(global_id_list: list):
343 """
344 Form a get request from a list of ModelId objects.
345 """
346
347 request = et.Element(TAG_REQUEST)
348 request.set(ATTR_TYPE, TYPE_GET)
349 request.set(ATTR_VERSION, '1.0')
350
351 model = et.SubElement(request, TAG_MODEL)
352 model.set(ATTR_VERSION, '1.0')
353 model.set(ATTR_NAME, 'post')
354
355 for global_id in global_id_list:
356 tag_id = et.SubElement(model, TAG_ID)
357 global_id.to_xml_element(tag_id)
358
359 return et.tostring(request, 'unicode')
360
361 @staticmethod
362 def generate_request_list(opening_post=None):
363 """
364 Form a pull request from a list of ModelId objects.
365 """
366
367 request = et.Element(TAG_REQUEST)
368 request.set(ATTR_TYPE, TYPE_LIST)
369 request.set(ATTR_VERSION, '1.0')
370
371 model = et.SubElement(request, TAG_MODEL)
372 model.set(ATTR_VERSION, '1.0')
373 model.set(ATTR_NAME, 'post')
374
375 if opening_post:
376 ThreadFilter().add_filter(model, opening_post)
377
378 return et.tostring(request, 'unicode') No newline at end of file
@@ -1,158 +1,125 b''
1 import xml.etree.ElementTree as et
1 import xml.etree.ElementTree as et
2
2 from django.db import models
3 from django.db import models
4
3 from boards.models import KeyPair
5 from boards.models import KeyPair
4
6
5
6 TAG_MODEL = 'model'
7 TAG_MODEL = 'model'
7 TAG_REQUEST = 'request'
8 TAG_REQUEST = 'request'
8 TAG_ID = 'id'
9 TAG_ID = 'id'
9
10
10 TYPE_GET = 'get'
11 TYPE_GET = 'get'
11 TYPE_LIST = 'list'
12 TYPE_LIST = 'list'
12
13
13 ATTR_VERSION = 'version'
14 ATTR_VERSION = 'version'
14 ATTR_TYPE = 'type'
15 ATTR_TYPE = 'type'
15 ATTR_NAME = 'name'
16 ATTR_NAME = 'name'
16
17
17 ATTR_KEY = 'key'
18 ATTR_KEY = 'key'
18 ATTR_KEY_TYPE = 'type'
19 ATTR_KEY_TYPE = 'type'
19 ATTR_LOCAL_ID = 'local-id'
20 ATTR_LOCAL_ID = 'local-id'
20
21
21
22
22 class GlobalIdManager(models.Manager):
23 class GlobalIdManager(models.Manager):
23 def generate_request_get(self, global_id_list: list):
24 """
25 Form a get request from a list of ModelId objects.
26 """
27
28 request = et.Element(TAG_REQUEST)
29 request.set(ATTR_TYPE, TYPE_GET)
30 request.set(ATTR_VERSION, '1.0')
31
32 model = et.SubElement(request, TAG_MODEL)
33 model.set(ATTR_VERSION, '1.0')
34 model.set(ATTR_NAME, 'post')
35
36 for global_id in global_id_list:
37 tag_id = et.SubElement(model, TAG_ID)
38 global_id.to_xml_element(tag_id)
39
40 return et.tostring(request, 'unicode')
41
42 def generate_request_list(self):
43 """
44 Form a pull request from a list of ModelId objects.
45 """
46
47 request = et.Element(TAG_REQUEST)
48 request.set(ATTR_TYPE, TYPE_LIST)
49 request.set(ATTR_VERSION, '1.0')
50
51 model = et.SubElement(request, TAG_MODEL)
52 model.set(ATTR_VERSION, '1.0')
53 model.set(ATTR_NAME, 'post')
54
55 return et.tostring(request, 'unicode')
56
57 def global_id_exists(self, global_id):
24 def global_id_exists(self, global_id):
58 """
25 """
59 Checks if the same global id already exists in the system.
26 Checks if the same global id already exists in the system.
60 """
27 """
61
28
62 return self.filter(key=global_id.key,
29 return self.filter(key=global_id.key,
63 key_type=global_id.key_type,
30 key_type=global_id.key_type,
64 local_id=global_id.local_id).exists()
31 local_id=global_id.local_id).exists()
65
32
66
33
67 class GlobalId(models.Model):
34 class GlobalId(models.Model):
68 """
35 """
69 Global model ID and cache.
36 Global model ID and cache.
70 Key, key type and local ID make a single global identificator of the model.
37 Key, key type and local ID make a single global identificator of the model.
71 Content is an XML cache of the model that can be passed along between nodes
38 Content is an XML cache of the model that can be passed along between nodes
72 without manual serialization each time.
39 without manual serialization each time.
73 """
40 """
74 class Meta:
41 class Meta:
75 app_label = 'boards'
42 app_label = 'boards'
76
43
77 objects = GlobalIdManager()
44 objects = GlobalIdManager()
78
45
79 def __init__(self, *args, **kwargs):
46 def __init__(self, *args, **kwargs):
80 models.Model.__init__(self, *args, **kwargs)
47 models.Model.__init__(self, *args, **kwargs)
81
48
82 if 'key' in kwargs and 'key_type' in kwargs and 'local_id' in kwargs:
49 if 'key' in kwargs and 'key_type' in kwargs and 'local_id' in kwargs:
83 self.key = kwargs['key']
50 self.key = kwargs['key']
84 self.key_type = kwargs['key_type']
51 self.key_type = kwargs['key_type']
85 self.local_id = kwargs['local_id']
52 self.local_id = kwargs['local_id']
86
53
87 key = models.TextField()
54 key = models.TextField()
88 key_type = models.TextField()
55 key_type = models.TextField()
89 local_id = models.IntegerField()
56 local_id = models.IntegerField()
90 content = models.TextField(blank=True, null=True)
57 content = models.TextField(blank=True, null=True)
91
58
92 def __str__(self):
59 def __str__(self):
93 return '%s::%s::%d' % (self.key_type, self.key, self.local_id)
60 return '%s::%s::%d' % (self.key_type, self.key, self.local_id)
94
61
95 def to_xml_element(self, element: et.Element):
62 def to_xml_element(self, element: et.Element):
96 """
63 """
97 Exports global id to an XML element.
64 Exports global id to an XML element.
98 """
65 """
99
66
100 element.set(ATTR_KEY, self.key)
67 element.set(ATTR_KEY, self.key)
101 element.set(ATTR_KEY_TYPE, self.key_type)
68 element.set(ATTR_KEY_TYPE, self.key_type)
102 element.set(ATTR_LOCAL_ID, str(self.local_id))
69 element.set(ATTR_LOCAL_ID, str(self.local_id))
103
70
104 @staticmethod
71 @staticmethod
105 def from_xml_element(element: et.Element):
72 def from_xml_element(element: et.Element):
106 """
73 """
107 Parses XML id tag and gets global id from it.
74 Parses XML id tag and gets global id from it.
108
75
109 Arguments:
76 Arguments:
110 element -- the XML 'id' element
77 element -- the XML 'id' element
111
78
112 Returns:
79 Returns:
113 global_id -- id itself
80 global_id -- id itself
114 exists -- True if the global id was taken from database, False if it
81 exists -- True if the global id was taken from database, False if it
115 did not exist and was created.
82 did not exist and was created.
116 """
83 """
117
84
118 try:
85 try:
119 return GlobalId.objects.get(key=element.get(ATTR_KEY),
86 return GlobalId.objects.get(key=element.get(ATTR_KEY),
120 key_type=element.get(ATTR_KEY_TYPE),
87 key_type=element.get(ATTR_KEY_TYPE),
121 local_id=int(element.get(
88 local_id=int(element.get(
122 ATTR_LOCAL_ID))), True
89 ATTR_LOCAL_ID))), True
123 except GlobalId.DoesNotExist:
90 except GlobalId.DoesNotExist:
124 return GlobalId(key=element.get(ATTR_KEY),
91 return GlobalId(key=element.get(ATTR_KEY),
125 key_type=element.get(ATTR_KEY_TYPE),
92 key_type=element.get(ATTR_KEY_TYPE),
126 local_id=int(element.get(ATTR_LOCAL_ID))), False
93 local_id=int(element.get(ATTR_LOCAL_ID))), False
127
94
128 def is_local(self):
95 def is_local(self):
129 """Checks fo the ID is local model's"""
96 """Checks fo the ID is local model's"""
130 return KeyPair.objects.filter(
97 return KeyPair.objects.filter(
131 key_type=self.key_type, public_key=self.key).exists()
98 key_type=self.key_type, public_key=self.key).exists()
132
99
133 def clear_cache(self):
100 def clear_cache(self):
134 """
101 """
135 Removes content cache and signatures.
102 Removes content cache and signatures.
136 """
103 """
137 self.content = None
104 self.content = None
138 self.save()
105 self.save()
139 self.signature_set.all().delete()
106 self.signature_set.all().delete()
140
107
141
108
142 class Signature(models.Model):
109 class Signature(models.Model):
143 class Meta:
110 class Meta:
144 app_label = 'boards'
111 app_label = 'boards'
145
112
146 def __init__(self, *args, **kwargs):
113 def __init__(self, *args, **kwargs):
147 models.Model.__init__(self, *args, **kwargs)
114 models.Model.__init__(self, *args, **kwargs)
148
115
149 if 'key' in kwargs and 'key_type' in kwargs and 'signature' in kwargs:
116 if 'key' in kwargs and 'key_type' in kwargs and 'signature' in kwargs:
150 self.key_type = kwargs['key_type']
117 self.key_type = kwargs['key_type']
151 self.key = kwargs['key']
118 self.key = kwargs['key']
152 self.signature = kwargs['signature']
119 self.signature = kwargs['signature']
153
120
154 key_type = models.TextField()
121 key_type = models.TextField()
155 key = models.TextField()
122 key = models.TextField()
156 signature = models.TextField()
123 signature = models.TextField()
157
124
158 global_id = models.ForeignKey('GlobalId')
125 global_id = models.ForeignKey('GlobalId')
@@ -1,91 +1,90 b''
1 from base64 import b64encode
2 import logging
1 import logging
3
2
4 from django.test import TestCase
3 from django.test import TestCase
5 from boards.models import KeyPair, GlobalId, Post, Signature
4 from boards.models import KeyPair, GlobalId, Post, Signature
6 from boards.models.post.sync import SyncManager
5 from boards.models.post.sync import SyncManager
7
6
8 logger = logging.getLogger(__name__)
7 logger = logging.getLogger(__name__)
9
8
10
9
11 class KeyTest(TestCase):
10 class KeyTest(TestCase):
12 def test_create_key(self):
11 def test_create_key(self):
13 key = KeyPair.objects.generate_key('ecdsa')
12 key = KeyPair.objects.generate_key('ecdsa')
14
13
15 self.assertIsNotNone(key, 'The key was not created.')
14 self.assertIsNotNone(key, 'The key was not created.')
16
15
17 def test_validation(self):
16 def test_validation(self):
18 key = KeyPair.objects.generate_key(key_type='ecdsa')
17 key = KeyPair.objects.generate_key(key_type='ecdsa')
19 message = 'msg'
18 message = 'msg'
20 signature_value = key.sign(message)
19 signature_value = key.sign(message)
21
20
22 signature = Signature(key_type='ecdsa', key=key.public_key,
21 signature = Signature(key_type='ecdsa', key=key.public_key,
23 signature=signature_value)
22 signature=signature_value)
24 valid = KeyPair.objects.verify(signature, message)
23 valid = KeyPair.objects.verify(signature, message)
25
24
26 self.assertTrue(valid, 'Message verification failed.')
25 self.assertTrue(valid, 'Message verification failed.')
27
26
28 def test_primary_constraint(self):
27 def test_primary_constraint(self):
29 KeyPair.objects.generate_key(key_type='ecdsa', primary=True)
28 KeyPair.objects.generate_key(key_type='ecdsa', primary=True)
30
29
31 with self.assertRaises(Exception):
30 with self.assertRaises(Exception):
32 KeyPair.objects.generate_key(key_type='ecdsa', primary=True)
31 KeyPair.objects.generate_key(key_type='ecdsa', primary=True)
33
32
34 def test_model_id_save(self):
33 def test_model_id_save(self):
35 model_id = GlobalId(key_type='test', key='test key', local_id='1')
34 model_id = GlobalId(key_type='test', key='test key', local_id='1')
36 model_id.save()
35 model_id.save()
37
36
38 def test_request_get(self):
37 def test_request_get(self):
39 post = self._create_post_with_key()
38 post = self._create_post_with_key()
40
39
41 request = GlobalId.objects.generate_request_get([post.global_id])
40 request = SyncManager.generate_request_get([post.global_id])
42 logger.debug(request)
41 logger.debug(request)
43
42
44 key = KeyPair.objects.get(primary=True)
43 key = KeyPair.objects.get(primary=True)
45 self.assertTrue('<request type="get" version="1.0">'
44 self.assertTrue('<request type="get" version="1.0">'
46 '<model name="post" version="1.0">'
45 '<model name="post" version="1.0">'
47 '<id key="%s" local-id="1" type="%s" />'
46 '<id key="%s" local-id="1" type="%s" />'
48 '</model>'
47 '</model>'
49 '</request>' % (
48 '</request>' % (
50 key.public_key,
49 key.public_key,
51 key.key_type,
50 key.key_type,
52 ) in request,
51 ) in request,
53 'Wrong XML generated for the GET request.')
52 'Wrong XML generated for the GET request.')
54
53
55 def test_response_get(self):
54 def test_response_get(self):
56 post = self._create_post_with_key()
55 post = self._create_post_with_key()
57 reply_post = Post.objects.create_post(title='test_title',
56 reply_post = Post.objects.create_post(title='test_title',
58 text='[post]%d[/post]' % post.id,
57 text='[post]%d[/post]' % post.id,
59 thread=post.get_thread())
58 thread=post.get_thread())
60
59
61 response = SyncManager.generate_response_get([reply_post])
60 response = SyncManager.generate_response_get([reply_post])
62 logger.debug(response)
61 logger.debug(response)
63
62
64 key = KeyPair.objects.get(primary=True)
63 key = KeyPair.objects.get(primary=True)
65 self.assertTrue('<status>success</status>'
64 self.assertTrue('<status>success</status>'
66 '<models>'
65 '<models>'
67 '<model name="post">'
66 '<model name="post">'
68 '<content>'
67 '<content>'
69 '<id key="%s" local-id="%d" type="%s" />'
68 '<id key="%s" local-id="%d" type="%s" />'
70 '<title>test_title</title>'
69 '<title>test_title</title>'
71 '<text>[post]%s[/post]</text>'
70 '<text>[post]%s[/post]</text>'
72 '<thread><id key="%s" local-id="%d" type="%s" /></thread>'
71 '<thread><id key="%s" local-id="%d" type="%s" /></thread>'
73 '<pub-time>%s</pub-time>'
72 '<pub-time>%s</pub-time>'
74 '<version>%s</version>'
73 '<version>%s</version>'
75 '</content>' % (
74 '</content>' % (
76 key.public_key,
75 key.public_key,
77 reply_post.id,
76 reply_post.id,
78 key.key_type,
77 key.key_type,
79 str(post.global_id),
78 str(post.global_id),
80 key.public_key,
79 key.public_key,
81 post.id,
80 post.id,
82 key.key_type,
81 key.key_type,
83 str(reply_post.get_pub_time_str()),
82 str(reply_post.get_pub_time_str()),
84 post.version,
83 post.version,
85 ) in response,
84 ) in response,
86 'Wrong XML generated for the GET response.')
85 'Wrong XML generated for the GET response.')
87
86
88 def _create_post_with_key(self):
87 def _create_post_with_key(self):
89 KeyPair.objects.generate_key(primary=True)
88 KeyPair.objects.generate_key(primary=True)
90
89
91 return Post.objects.create_post(title='test_title', text='test_text')
90 return Post.objects.create_post(title='test_title', text='test_text')
@@ -1,105 +1,214 b''
1 from django.test import TestCase
1 from django.test import TestCase
2
2
3 from boards.models import KeyPair, Post, Tag
3 from boards.models import KeyPair, Post, Tag
4 from boards.models.post.sync import SyncManager
4 from boards.models.post.sync import SyncManager
5 from boards.tests.mocks import MockRequest
5 from boards.tests.mocks import MockRequest
6 from boards.views.sync import response_get
6 from boards.views.sync import response_get, response_list
7
7
8 __author__ = 'neko259'
8 __author__ = 'neko259'
9
9
10
10
11 class SyncTest(TestCase):
11 class SyncTest(TestCase):
12 def test_get(self):
12 def test_get(self):
13 """
13 """
14 Forms a GET request of a post and checks the response.
14 Forms a GET request of a post and checks the response.
15 """
15 """
16
16
17 key = KeyPair.objects.generate_key(primary=True)
17 key = KeyPair.objects.generate_key(primary=True)
18 tag = Tag.objects.create(name='tag1')
18 tag = Tag.objects.create(name='tag1')
19 post = Post.objects.create_post(title='test_title',
19 post = Post.objects.create_post(title='test_title',
20 text='test_text\rline two',
20 text='test_text\rline two',
21 tags=[tag])
21 tags=[tag])
22
22
23 request = MockRequest()
23 request = MockRequest()
24 request.body = (
24 request.body = (
25 '<request type="get" version="1.0">'
25 '<request type="get" version="1.0">'
26 '<model name="post" version="1.0">'
26 '<model name="post" version="1.0">'
27 '<id key="%s" local-id="%d" type="%s" />'
27 '<id key="%s" local-id="%d" type="%s" />'
28 '</model>'
28 '</model>'
29 '</request>' % (post.global_id.key,
29 '</request>' % (post.global_id.key,
30 post.id,
30 post.id,
31 post.global_id.key_type)
31 post.global_id.key_type)
32 )
32 )
33
33
34 response = response_get(request).content.decode()
34 response = response_get(request).content.decode()
35 self.assertTrue(
35 self.assertTrue(
36 '<status>success</status>'
36 '<status>success</status>'
37 '<models>'
37 '<models>'
38 '<model name="post">'
38 '<model name="post">'
39 '<content>'
39 '<content>'
40 '<id key="%s" local-id="%d" type="%s" />'
40 '<id key="%s" local-id="%d" type="%s" />'
41 '<title>%s</title>'
41 '<title>%s</title>'
42 '<text>%s</text>'
42 '<text>%s</text>'
43 '<tags><tag>%s</tag></tags>'
43 '<tags><tag>%s</tag></tags>'
44 '<pub-time>%s</pub-time>'
44 '<pub-time>%s</pub-time>'
45 '<version>%s</version>'
45 '<version>%s</version>'
46 '</content>' % (
46 '</content>' % (
47 post.global_id.key,
47 post.global_id.key,
48 post.global_id.local_id,
48 post.global_id.local_id,
49 post.global_id.key_type,
49 post.global_id.key_type,
50 post.title,
50 post.title,
51 post.get_sync_text(),
51 post.get_sync_text(),
52 post.get_thread().get_tags().first().name,
52 post.get_thread().get_tags().first().name,
53 post.get_pub_time_str(),
53 post.get_pub_time_str(),
54 post.version,
54 post.version,
55 ) in response,
55 ) in response,
56 'Wrong response generated for the GET request.')
56 'Wrong response generated for the GET request.')
57
57
58 post.delete()
58 post.delete()
59 key.delete()
59 key.delete()
60
60
61 KeyPair.objects.generate_key(primary=True)
61 KeyPair.objects.generate_key(primary=True)
62
62
63 SyncManager.parse_response_get(response, None)
63 SyncManager.parse_response_get(response, None)
64 self.assertEqual(1, Post.objects.count(),
64 self.assertEqual(1, Post.objects.count(),
65 'Post was not created from XML response.')
65 'Post was not created from XML response.')
66
66
67 parsed_post = Post.objects.first()
67 parsed_post = Post.objects.first()
68 self.assertEqual('tag1',
68 self.assertEqual('tag1',
69 parsed_post.get_thread().get_tags().first().name,
69 parsed_post.get_thread().get_tags().first().name,
70 'Invalid tag was parsed.')
70 'Invalid tag was parsed.')
71
71
72 SyncManager.parse_response_get(response, None)
72 SyncManager.parse_response_get(response, None)
73 self.assertEqual(1, Post.objects.count(),
73 self.assertEqual(1, Post.objects.count(),
74 'The same post was imported twice.')
74 'The same post was imported twice.')
75
75
76 self.assertEqual(1, parsed_post.global_id.signature_set.count(),
76 self.assertEqual(1, parsed_post.global_id.signature_set.count(),
77 'Signature was not saved.')
77 'Signature was not saved.')
78
78
79 post = parsed_post
79 post = parsed_post
80
80
81 # Trying to sync the same once more
81 # Trying to sync the same once more
82 response = response_get(request).content.decode()
82 response = response_get(request).content.decode()
83
83
84 self.assertTrue(
84 self.assertTrue(
85 '<status>success</status>'
85 '<status>success</status>'
86 '<models>'
86 '<models>'
87 '<model name="post">'
87 '<model name="post">'
88 '<content>'
88 '<content>'
89 '<id key="%s" local-id="%d" type="%s" />'
89 '<id key="%s" local-id="%d" type="%s" />'
90 '<title>%s</title>'
90 '<title>%s</title>'
91 '<text>%s</text>'
91 '<text>%s</text>'
92 '<tags><tag>%s</tag></tags>'
92 '<tags><tag>%s</tag></tags>'
93 '<pub-time>%s</pub-time>'
93 '<pub-time>%s</pub-time>'
94 '<version>%s</version>'
94 '<version>%s</version>'
95 '</content>' % (
95 '</content>' % (
96 post.global_id.key,
96 post.global_id.key,
97 post.global_id.local_id,
97 post.global_id.local_id,
98 post.global_id.key_type,
98 post.global_id.key_type,
99 post.title,
99 post.title,
100 post.get_sync_text(),
100 post.get_sync_text(),
101 post.get_thread().get_tags().first().name,
101 post.get_thread().get_tags().first().name,
102 post.get_pub_time_str(),
102 post.get_pub_time_str(),
103 post.version,
103 post.version,
104 ) in response,
104 ) in response,
105 'Wrong response generated for the GET request.')
105 'Wrong response generated for the GET request.')
106
107 def test_list_all(self):
108 key = KeyPair.objects.generate_key(primary=True)
109 tag = Tag.objects.create(name='tag1')
110 post = Post.objects.create_post(title='test_title',
111 text='test_text\rline two',
112 tags=[tag])
113 post2 = Post.objects.create_post(title='test title 2',
114 text='test text 2',
115 tags=[tag])
116
117 request_all = MockRequest()
118 request_all.body = (
119 '<request type="list" version="1.0">'
120 '<model name="post" version="1.0">'
121 '</model>'
122 '</request>'
123 )
124
125 response_all = response_list(request_all).content.decode()
126 self.assertTrue(
127 '<status>success</status>'
128 '<models>'
129 '<model>'
130 '<id key="{}" local-id="{}" type="{}" />'
131 '<version>{}</version>'
132 '</model>'
133 '<model>'
134 '<id key="{}" local-id="{}" type="{}" />'
135 '<version>{}</version>'
136 '</model>'
137 '</models>'.format(
138 post.global_id.key,
139 post.global_id.local_id,
140 post.global_id.key_type,
141 post.version,
142 post2.global_id.key,
143 post2.global_id.local_id,
144 post2.global_id.key_type,
145 post2.version,
146 ) in response_all,
147 'Wrong response generated for the LIST request for all posts.')
148
149 def test_list_existing_thread(self):
150 key = KeyPair.objects.generate_key(primary=True)
151 tag = Tag.objects.create(name='tag1')
152 post = Post.objects.create_post(title='test_title',
153 text='test_text\rline two',
154 tags=[tag])
155 post2 = Post.objects.create_post(title='test title 2',
156 text='test text 2',
157 tags=[tag])
158
159 request_thread = MockRequest()
160 request_thread.body = (
161 '<request type="list" version="1.0">'
162 '<model name="post" version="1.0">'
163 '<thread>{}</thread>'
164 '</model>'
165 '</request>'.format(
166 post.id,
167 )
168 )
169
170 response_thread = response_list(request_thread).content.decode()
171 self.assertTrue(
172 '<status>success</status>'
173 '<models>'
174 '<model>'
175 '<id key="{}" local-id="{}" type="{}" />'
176 '<version>{}</version>'
177 '</model>'
178 '</models>'.format(
179 post.global_id.key,
180 post.global_id.local_id,
181 post.global_id.key_type,
182 post.version,
183 ) in response_thread,
184 'Wrong response generated for the LIST request for posts of '
185 'existing thread.')
186
187 def test_list_non_existing_thread(self):
188 key = KeyPair.objects.generate_key(primary=True)
189 tag = Tag.objects.create(name='tag1')
190 post = Post.objects.create_post(title='test_title',
191 text='test_text\rline two',
192 tags=[tag])
193 post2 = Post.objects.create_post(title='test title 2',
194 text='test text 2',
195 tags=[tag])
196
197 request_thread = MockRequest()
198 request_thread.body = (
199 '<request type="list" version="1.0">'
200 '<model name="post" version="1.0">'
201 '<thread>{}</thread>'
202 '</model>'
203 '</request>'.format(
204 0,
205 )
206 )
207
208 response_thread = response_list(request_thread).content.decode()
209 self.assertTrue(
210 '<status>success</status>'
211 '<models />'
212 in response_thread,
213 'Wrong response generated for the LIST request for posts of '
214 'non-existing thread.')
@@ -1,55 +1,79 b''
1 import logging
2
1 import xml.etree.ElementTree as et
3 import xml.etree.ElementTree as et
2
4
3 from django.http import HttpResponse, Http404
5 from django.http import HttpResponse, Http404
6
7 from boards.abstracts.sync_filters import ThreadFilter, TAG_THREAD
4 from boards.models import GlobalId, Post
8 from boards.models import GlobalId, Post
5 from boards.models.post.sync import SyncManager
9 from boards.models.post.sync import SyncManager
6
10
7
11
12 logger = logging.getLogger('boards.sync')
13
14
15 FILTERS = {
16 TAG_THREAD: ThreadFilter,
17 }
18
19
8 def response_list(request):
20 def response_list(request):
9 request_xml = request.body
21 request_xml = request.body
10
22
23 filters = []
24
11 if request_xml is None or len(request_xml) == 0:
25 if request_xml is None or len(request_xml) == 0:
12 return HttpResponse(content='Use the API')
26 return HttpResponse(content='Use the API')
27 else:
28 root_tag = et.fromstring(request_xml)
29 model_tag = root_tag[0]
13
30
14 response_xml = SyncManager.generate_response_list()
31 for tag_filter in model_tag:
32 filter_name = tag_filter.tag
33 model_filter = FILTERS.get(filter_name)(tag_filter)
34 if not model_filter:
35 logger.warning('Unavailable filter: {}'.format(filter_name))
36 filters.append(model_filter)
37
38 response_xml = SyncManager.generate_response_list(filters)
15
39
16 return HttpResponse(content=response_xml)
40 return HttpResponse(content=response_xml)
17
41
18
42
19 def response_get(request):
43 def response_get(request):
20 """
44 """
21 Processes a GET request with post ID list and returns the posts XML list.
45 Processes a GET request with post ID list and returns the posts XML list.
22 Request should contain an 'xml' post attribute with the actual request XML.
46 Request should contain an 'xml' post attribute with the actual request XML.
23 """
47 """
24
48
25 request_xml = request.body
49 request_xml = request.body
26
50
27 if request_xml is None or len(request_xml) == 0:
51 if request_xml is None or len(request_xml) == 0:
28 return HttpResponse(content='Use the API')
52 return HttpResponse(content='Use the API')
29
53
30 posts = []
54 posts = []
31
55
32 root_tag = et.fromstring(request_xml)
56 root_tag = et.fromstring(request_xml)
33 model_tag = root_tag[0]
57 model_tag = root_tag[0]
34 for id_tag in model_tag:
58 for id_tag in model_tag:
35 global_id, exists = GlobalId.from_xml_element(id_tag)
59 global_id, exists = GlobalId.from_xml_element(id_tag)
36 if exists:
60 if exists:
37 posts.append(Post.objects.get(global_id=global_id))
61 posts.append(Post.objects.get(global_id=global_id))
38
62
39 response_xml = SyncManager.generate_response_get(posts)
63 response_xml = SyncManager.generate_response_get(posts)
40
64
41 return HttpResponse(content=response_xml)
65 return HttpResponse(content=response_xml)
42
66
43
67
44 def get_post_sync_data(request, post_id):
68 def get_post_sync_data(request, post_id):
45 try:
69 try:
46 post = Post.objects.get(id=post_id)
70 post = Post.objects.get(id=post_id)
47 except Post.DoesNotExist:
71 except Post.DoesNotExist:
48 raise Http404()
72 raise Http404()
49
73
50 xml_str = SyncManager.generate_response_get([post])
74 xml_str = SyncManager.generate_response_get([post])
51
75
52 return HttpResponse(
76 return HttpResponse(
53 content_type='text/xml; charset=utf-8',
77 content_type='text/xml; charset=utf-8',
54 content=xml_str,
78 content=xml_str,
55 )
79 )
General Comments 0
You need to be logged in to leave comments. Login now