##// END OF EJS Templates
Merge tip
Bohdan Horbeshko -
r2146:69a1b01a merge lite
parent child Browse files
Show More
@@ -1,53 +1,54 b''
1 1 bc8fce57a613175450b8b6d933cdd85f22c04658 1.1
2 2 784258eb652c563c288ca7652c33f52cd4733d83 1.1-stable
3 3 1b53a22467a8fccc798935d7a26efe78e4bc7b25 1.2-stable
4 4 1713fb7543386089e364c39703b79e57d3d851f0 1.3
5 5 80f183ebbe132ea8433eacae9431360f31fe7083 1.4
6 6 4330ff5a2bf6c543d8aaae8a43de1dc062f3bd13 1.4.1
7 7 8531d7b001392289a6b761f38c73a257606552ad 1.5
8 8 78e843c8b04b5a81cee5aa24601e305fae75da24 1.5.1
9 9 4f92838730ed9aa1d17651bbcdca19a097fd0c37 1.6
10 10 4bac2f37ea463337ddd27f98e7985407a74de504 1.7
11 11 1c4febea92c6503ae557fba73b2768659ae90d24 1.7.1
12 12 56a4a4578fc454ee455e33dd74a2cc82234bcb59 1.7.2
13 13 34d6f3d5deb22be56b6c1512ec654bd7f6e03bcc 1.7.3
14 14 f5cca33d29c673b67d43f310bebc4e3a21c6d04c 1.7.4
15 15 7f7c33ba6e3f3797ca866c5ed5d358a2393f1371 1.8
16 16 a6b9dd9547bdc17b681502efcceb17aa5c09adf4 1.8.1
17 17 8318fa1615d1946e4519f5735ae880909521990d 2.0
18 18 e23590ee7e2067a3f0fc3cbcfd66404b47127feb 2.1
19 19 4d998aba79e4abf0a2e78e93baaa2c2800b1c49c 2.2
20 20 07fdef4ac33a859250d03f17c594089792bca615 2.2.1
21 21 bcc74d45f060ecd3ff06ff448165aea0d026cb3e 2.2.2
22 22 b0e629ff24eb47a449ecfb455dc6cc600d18c9e2 2.2.3
23 23 1b52ba60f17fd7c90912c14d9d17e880b7952d01 2.2.4
24 24 957e2fec91468f739b0fc2b9936d564505048c68 2.3.0
25 25 bb91141c6ea5c822ccbe2d46c3c48bdab683b77d 2.4.0
26 26 97eb184637e5691b288eaf6b03e8971f3364c239 2.5.0
27 27 119fafc5381b933bf30d97be0b278349f6135075 2.5.1
28 28 d528d76d3242cced614fa11bb63f3d342e4e1d09 2.5.2
29 29 1b631781ced34fbdeec032e7674bc4e131724699 2.6.0
30 30 0f2ef17dc0de678ada279bf7eedf6c5585f1fd7a 2.6.1
31 31 d53fc814a424d7fd90f23025c87b87baa164450e 2.7.0
32 32 836d8bb9fcd930b952b9a02029442c71c2441983 2.8.0
33 33 dfb6c481b1a2c33705de9a9b5304bc924c46b202 2.8.1
34 34 4a5bec08ccfb47a27f9e98698f12dd5b7246623b 2.8.2
35 35 604935b98f5b5e4a5e903594f048046e1fbb3519 2.8.3
36 36 c48ffdc671566069ed0f33644da1229277f3cd18 2.9.0
37 37 d66dc192d4e089ba85325afeef5229b73cb0fde4 2.10.0
38 38 1c22a38cca9ae3bee13d6f263792c0629d0061f6 2.10.1
39 39 3076e0d03339f3b41dcc71fb6af2b4169920846c 2.11.0
40 40 9cffa58fae74952b8ffe70328af88a5df17059c1 2.12.0
41 41 f5caa9e46201ed5b3f1e31655fb4d57bc1b89ab1 3.0.0
42 42 df2ee5df6e73363c8d8fd8f22b87e1a2b21544d4 3.1.0
43 43 3504151c4799f4e33fb7ff846b119d5693c74145 3.2.0
44 44 a03f50d9723e618d011fde7dcc7288bc6861346e 3.2.1
45 45 507a67acbf2e8dc287ba796fa7e5700f8f725bac 3.2.2
46 46 1376f5fc44354b4dff69631ad187d57690c0d460 3.3.0
47 47 21e5d408a1a59aec0e3a97cb206d70c8ce34e9b8 3.3.1
48 48 f2d19a1cde13d82a3803a7d73a4f9c114ed00e7e 3.3.2
49 49 bb195ee1fe07d68b6fccfdde1dbe7c4dd430e15d 3.3.3
50 50 19785af352684884f94566785ba1b13b3ddc5216 3.4.0
51 51 757b4ada4ca121db4296b13ec0df491645db8fd0 3.5.0
52 52 3da1a2d02072eec5419956265dcd8c7f47155c12 4.0.0
53 53 da8f0f9d5099ee8b22aa317b91beb06543011f1d 4.1.0
54 9619ecc0f79b705c94b3ac873896809eaf7779a6 4.1.1
@@ -1,31 +1,66 b''
1 1 import xml.etree.ElementTree as et
2 2
3 from boards.models import Post
3 from boards.models import Post, Tag
4 4
5 5 TAG_THREAD = 'thread'
6 TAG_TAGS = 'tags'
7 TAG_TAG = 'tag'
8 TAG_TIME_FROM = 'timestamp_from'
6 9
7 10
8 11 class PostFilter:
9 12 def __init__(self, content=None):
10 13 self.content = content
11 14
12 15 def filter(self, posts):
13 16 return posts
14 17
15 18 def add_filter(self, model_tag, value):
16 19 return model_tag
17 20
18 21
19 22 class ThreadFilter(PostFilter):
20 23 def filter(self, posts):
21 24 op_id = self.content.text
22 25
23 26 op = Post.objects.filter(opening=True, id=op_id).first()
24 27 if op:
25 28 return posts.filter(thread=op.get_thread())
26 29 else:
27 30 return posts.none()
28 31
29 32 def add_filter(self, model_tag, value):
30 33 thread_tag = et.SubElement(model_tag, TAG_THREAD)
31 34 thread_tag.text = str(value)
35
36
37 class TagsFilter(PostFilter):
38 def filter(self, posts):
39 tags = []
40 for tag_tag in self.content:
41 try:
42 tags.append(Tag.objects.get(name=tag_tag.text))
43 except Tag.DoesNotExist:
44 pass
45
46 if tags:
47 return posts.filter(thread__tags__in=tags)
48 else:
49 return posts.none()
50
51 def add_filter(self, model_tag, value):
52 tags_tag = et.SubElement(model_tag, TAG_TAGS)
53 for tag_name in value:
54 tag_tag = et.SubElement(tags_tag, TAG_TAG)
55 tag_tag.text = tag_name
56
57
58 class TimestampFromFilter(PostFilter):
59 def filter(self, posts):
60 from_time = self.content.text
61 return posts.filter(pub_time__gt=from_time)
62
63 def add_filter(self, model_tag, value):
64 tags_from_time = et.SubElement(model_tag, TAG_TIME_FROM)
65 tags_from_time.text = value
66
@@ -1,49 +1,49 b''
1 1 [Version]
2 Version = 4.1.0 2017
2 Version = 4.1.1 2017
3 3 SiteName = Neboard DEV
4 4
5 5 [Cache]
6 6 # Timeout for caching, if cache is used
7 7 CacheTimeout = 600
8 8
9 9 [Forms]
10 10 # Max post length in characters
11 11 MaxTextLength = 30000
12 12 MaxFileSize = 8000000
13 13 LimitFirstPosting = true
14 14 LimitPostingSpeed = false
15 15 PowDifficulty = 0
16 16 # Delay in seconds
17 17 PostingDelay = 30
18 18 Autoban = false
19 19 DefaultTag = test
20 20 MaxFileCount = 1
21 21 AdditionalSpoilerSpaces = false
22 22
23 23 [Messages]
24 24 # Thread bumplimit
25 25 MaxPostsPerThread = 10
26 26 ThreadArchiveDays = 300
27 27 AnonymousMode = false
28 28
29 29 [View]
30 30 DefaultTheme = md
31 31 DefaultImageViewer = simple
32 32 LastRepliesCount = 3
33 33 ThreadsPerPage = 3
34 34 PostsPerPage = 10
35 35 ImagesPerPageGallery = 20
36 36 MaxFavoriteThreads = 20
37 37 MaxLandingThreads = 20
38 38 Themes=md:Mystic Dark,md_centered:Mystic Dark (centered),sw:Snow White,pg:Photon Grey,ad:Amanita Dark,iw:Inocibe White
39 39 ImageViewers=simple:Simple,popup:Popup
40 40
41 41 [Storage]
42 42 # Enable archiving threads instead of deletion when the thread limit is reached
43 43 ArchiveThreads = true
44 44
45 45 [RSS]
46 46 MaxItems = 20
47 47
48 48 [External]
49 49 ImageSearchHost=
@@ -1,99 +1,115 b''
1 1 import re
2 2 import logging
3 3 import xml.etree.ElementTree as ET
4 4
5 5 import httplib2
6 6 from django.core.management import BaseCommand
7 7
8 8 from boards.models import GlobalId
9 9 from boards.models.post.sync import SyncManager, TAG_ID, TAG_VERSION
10 10
11 11 __author__ = 'neko259'
12 12
13 13
14 14 REGEX_GLOBAL_ID = re.compile(r'(\w+)::([\w\+/]+)::(\d+)')
15 15
16 16
17 17 class Command(BaseCommand):
18 18 help = 'Send a sync or get request to the server.'
19 19
20 20 def add_arguments(self, parser):
21 21 parser.add_argument('url', type=str, help='Server root url')
22 22 parser.add_argument('--global-id', type=str, default='',
23 23 help='Post global ID')
24 24 parser.add_argument('--split-query', type=int, default=1,
25 25 help='Split GET query into separate by the given'
26 26 ' number of posts in one')
27 27 parser.add_argument('--thread', type=int,
28 28 help='Get posts of one specific thread')
29 parser.add_argument('--tags', type=str,
30 help='Get posts of the tags, comma-separated')
31 parser.add_argument('--time-from', type=str,
32 help='Get posts from the given timestamp')
29 33
30 34 def handle(self, *args, **options):
31 35 logger = logging.getLogger('boards.sync')
32 36
33 37 url = options.get('url')
34 38
35 39 list_url = url + 'api/sync/list/'
36 40 get_url = url + 'api/sync/get/'
37 41 file_url = url[:-1]
38 42
39 43 global_id_str = options.get('global_id')
40 44 if global_id_str:
41 45 match = REGEX_GLOBAL_ID.match(global_id_str)
42 46 if match:
43 47 key_type = match.group(1)
44 48 key = match.group(2)
45 49 local_id = match.group(3)
46 50
47 51 global_id = GlobalId(key_type=key_type, key=key,
48 52 local_id=local_id)
49 53
50 54 xml = SyncManager.generate_request_get([global_id])
51 55 h = httplib2.Http()
52 56 response, content = h.request(get_url, method="POST", body=xml)
53 57
54 58 SyncManager.parse_response_get(content, file_url)
55 59 else:
56 60 raise Exception('Invalid global ID')
57 61 else:
58 62 logger.info('Running LIST request...')
59 63 h = httplib2.Http()
64
65 tags = []
66 tags_str = options.get('tags')
67 if tags_str:
68 tags = tags_str.split(',')
69
60 70 xml = SyncManager.generate_request_list(
61 opening_post=options.get('thread'))
71 opening_post=options.get('thread'), tags=tags,
72 timestamp_from=options.get('time_from')).encode()
62 73 response, content = h.request(list_url, method="POST", body=xml)
74 if response.status != 200:
75 raise Exception('Server returned error {}'.format(response.status))
76
63 77 logger.info('Processing response...')
64 78
65 79 root = ET.fromstring(content)
66 80 status = root.findall('status')[0].text
67 81 if status == 'success':
68 82 ids_to_sync = list()
69 83
70 84 models = root.findall('models')[0]
71 85 for model in models:
72 86 tag_id = model.find(TAG_ID)
73 87 global_id, exists = GlobalId.from_xml_element(tag_id)
74 88 tag_version = model.find(TAG_VERSION)
75 89 if tag_version is not None:
76 90 version = int(tag_version.text) or 1
77 91 else:
78 92 version = 1
79 93 if not exists or global_id.post.version < version:
80 94 logger.debug('Processed (+) post {}'.format(global_id))
81 95 ids_to_sync.append(global_id)
82 96 else:
83 97 logger.debug('* Processed (-) post {}'.format(global_id))
84 98 logger.info('Starting sync...')
85 99
86 100 if len(ids_to_sync) > 0:
87 101 limit = options.get('split_query', len(ids_to_sync))
88 102 for offset in range(0, len(ids_to_sync), limit):
89 103 xml = SyncManager.generate_request_get(ids_to_sync[offset:offset + limit])
90 104 h = httplib2.Http()
91 105 logger.info('Running GET request...')
92 106 response, content = h.request(get_url, method="POST", body=xml)
93 107 logger.info('Processing response...')
94 108
95 109 SyncManager.parse_response_get(content, file_url)
110
111 logger.info('Sync completed successfully')
96 112 else:
97 113 logger.info('Nothing to get, everything synced')
98 114 else:
99 115 raise Exception('Invalid response status')
@@ -1,345 +1,391 b''
1 1 import xml.etree.ElementTree as et
2 2 import logging
3 3 from xml.etree import ElementTree
4 4
5 5 from boards.abstracts.exceptions import SyncException
6 from boards.abstracts.sync_filters import ThreadFilter
6 from boards.abstracts.sync_filters import ThreadFilter, TagsFilter,\
7 TimestampFromFilter
7 8 from boards.models import KeyPair, GlobalId, Signature, Post, Tag
8 9 from boards.models.attachment.downloaders import download
9 10 from boards.models.signature import TAG_REQUEST, ATTR_TYPE, TYPE_GET, \
10 11 ATTR_VERSION, TAG_MODEL, ATTR_NAME, TAG_ID, TYPE_LIST
11 12 from boards.utils import get_file_mimetype, get_file_hash
12 13 from django.db import transaction
13 14 from django import forms
14 15
15 16 EXCEPTION_NODE = 'Sync node returned an error: {}.'
16 17 EXCEPTION_DOWNLOAD = 'File was not downloaded.'
17 18 EXCEPTION_HASH = 'File hash does not match attachment hash.'
18 19 EXCEPTION_SIGNATURE = 'Invalid model signature for {}.'
19 20 EXCEPTION_AUTHOR_SIGNATURE = 'Model {} has no author signature.'
21 EXCEPTION_THREAD = 'No thread exists for post {}'
20 22 ENCODING_UNICODE = 'unicode'
21 23
22 24 TAG_MODEL = 'model'
23 25 TAG_REQUEST = 'request'
24 26 TAG_RESPONSE = 'response'
25 27 TAG_ID = 'id'
26 28 TAG_STATUS = 'status'
27 29 TAG_MODELS = 'models'
28 30 TAG_TITLE = 'title'
29 31 TAG_TEXT = 'text'
30 32 TAG_THREAD = 'thread'
31 33 TAG_PUB_TIME = 'pub-time'
32 34 TAG_SIGNATURES = 'signatures'
33 35 TAG_SIGNATURE = 'signature'
34 36 TAG_CONTENT = 'content'
35 37 TAG_ATTACHMENTS = 'attachments'
36 38 TAG_ATTACHMENT = 'attachment'
37 39 TAG_TAGS = 'tags'
38 40 TAG_TAG = 'tag'
39 41 TAG_ATTACHMENT_REFS = 'attachment-refs'
40 42 TAG_ATTACHMENT_REF = 'attachment-ref'
41 43 TAG_TRIPCODE = 'tripcode'
42 44 TAG_VERSION = 'version'
43 45
44 46 TYPE_GET = 'get'
45 47
46 48 ATTR_VERSION = 'version'
47 49 ATTR_TYPE = 'type'
48 50 ATTR_NAME = 'name'
49 51 ATTR_VALUE = 'value'
50 52 ATTR_MIMETYPE = 'mimetype'
51 53 ATTR_KEY = 'key'
52 54 ATTR_REF = 'ref'
53 55 ATTR_URL = 'url'
54 56 ATTR_ID_TYPE = 'id-type'
55 57
56 58 ID_TYPE_MD5 = 'md5'
57 59 ID_TYPE_URL = 'url'
58 60
59 61 STATUS_SUCCESS = 'success'
60 62
61 63
62 64 logger = logging.getLogger('boards.sync')
63 65
64 66
65 67 class SyncManager:
66 68 @staticmethod
67 69 def generate_response_get(model_list: list):
68 70 response = et.Element(TAG_RESPONSE)
69 71
70 72 status = et.SubElement(response, TAG_STATUS)
71 73 status.text = STATUS_SUCCESS
72 74
73 75 models = et.SubElement(response, TAG_MODELS)
74 76
75 77 for post in model_list:
76 78 model = et.SubElement(models, TAG_MODEL)
77 79 model.set(ATTR_NAME, 'post')
78 80
79 81 global_id = post.global_id
80 82
81 83 attachments = post.attachments.all()
82 84 if global_id.content:
83 85 model.append(et.fromstring(global_id.content))
84 86 if len(attachments) > 0:
85 87 internal_attachments = False
86 88 for attachment in attachments:
87 89 if attachment.is_internal():
88 90 internal_attachments = True
89 91 break
90 92
91 93 if internal_attachments:
92 94 attachment_refs = et.SubElement(model, TAG_ATTACHMENT_REFS)
93 95 for file in attachments:
94 96 SyncManager._attachment_to_xml(
95 97 None, attachment_refs, file)
96 98 else:
97 99 content_tag = et.SubElement(model, TAG_CONTENT)
98 100
99 101 tag_id = et.SubElement(content_tag, TAG_ID)
100 102 global_id.to_xml_element(tag_id)
101 103
102 104 title = et.SubElement(content_tag, TAG_TITLE)
103 105 title.text = post.title
104 106
105 107 text = et.SubElement(content_tag, TAG_TEXT)
106 108 text.text = post.get_sync_text()
107 109
108 110 thread = post.get_thread()
109 111 if post.is_opening():
110 112 tag_tags = et.SubElement(content_tag, TAG_TAGS)
111 113 for tag in thread.get_tags():
112 114 tag_tag = et.SubElement(tag_tags, TAG_TAG)
113 115 tag_tag.text = tag.name
114 116 else:
115 117 tag_thread = et.SubElement(content_tag, TAG_THREAD)
116 118 thread_id = et.SubElement(tag_thread, TAG_ID)
117 119 thread.get_opening_post().global_id.to_xml_element(thread_id)
118 120
119 121 pub_time = et.SubElement(content_tag, TAG_PUB_TIME)
120 122 pub_time.text = str(post.get_pub_time_str())
121 123
122 124 if post.tripcode:
123 125 tripcode = et.SubElement(content_tag, TAG_TRIPCODE)
124 126 tripcode.text = post.tripcode
125 127
126 128 if len(attachments) > 0:
127 129 attachments_tag = et.SubElement(content_tag, TAG_ATTACHMENTS)
128 130
129 131 internal_attachments = False
130 132 for attachment in attachments:
131 133 if attachment.is_internal():
132 134 internal_attachments = True
133 135 break
134 136
135 137 if internal_attachments:
136 138 attachment_refs = et.SubElement(model, TAG_ATTACHMENT_REFS)
137 139 else:
138 140 attachment_refs = None
139 141
140 142 for file in attachments:
141 143 SyncManager._attachment_to_xml(
142 144 attachments_tag, attachment_refs, file)
143 145 version_tag = et.SubElement(content_tag, TAG_VERSION)
144 146 version_tag.text = str(post.version)
145 147
146 148 global_id.content = et.tostring(content_tag, ENCODING_UNICODE)
147 149 global_id.save()
148 150
149 151 signatures_tag = et.SubElement(model, TAG_SIGNATURES)
150 152 post_signatures = global_id.signature_set.all()
151 153 if post_signatures:
152 154 signatures = post_signatures
153 155 else:
154 156 key = KeyPair.objects.get(public_key=global_id.key)
155 157 signature = Signature(
156 158 key_type=key.key_type,
157 159 key=key.public_key,
158 160 signature=key.sign(global_id.content),
159 161 global_id=global_id,
160 162 )
161 163 signature.save()
162 164 signatures = [signature]
163 165 for signature in signatures:
164 166 signature_tag = et.SubElement(signatures_tag, TAG_SIGNATURE)
165 167 signature_tag.set(ATTR_TYPE, signature.key_type)
166 168 signature_tag.set(ATTR_VALUE, signature.signature)
167 169 signature_tag.set(ATTR_KEY, signature.key)
168 170
169 171 return et.tostring(response, ENCODING_UNICODE)
170 172
171 173 @staticmethod
172 174 def parse_response_get(response_xml, hostname):
173 175 tag_root = et.fromstring(response_xml)
174 176 tag_status = tag_root.find(TAG_STATUS)
175 177 if STATUS_SUCCESS == tag_status.text:
176 178 tag_models = tag_root.find(TAG_MODELS)
177 179 for tag_model in tag_models:
178 180 SyncManager.parse_post(tag_model, hostname)
179 181 else:
180 182 raise SyncException(EXCEPTION_NODE.format(tag_status.text))
181 183
182 184 @staticmethod
183 185 @transaction.atomic
184 186 def parse_post(tag_model, hostname):
185 187 tag_content = tag_model.find(TAG_CONTENT)
186 188
187 189 content_str = et.tostring(tag_content, ENCODING_UNICODE)
188 190
189 191 tag_id = tag_content.find(TAG_ID)
190 192 global_id, exists = GlobalId.from_xml_element(tag_id)
191 193 signatures = SyncManager._verify_model(global_id, content_str, tag_model)
192 194
193 195 version = int(tag_content.find(TAG_VERSION).text)
194 196 is_old = exists and global_id.post.version < version
195 197 if exists and not is_old:
196 print('Post with same ID exists and is up to date.')
198 logger.debug('Post {} exists and is up to date.'.format(global_id))
197 199 else:
198 200 global_id.content = content_str
199 201 global_id.save()
200 202 for signature in signatures:
201 203 signature.global_id = global_id
202 204 signature.save()
203 205
204 206 title = tag_content.find(TAG_TITLE).text or ''
205 207 text = tag_content.find(TAG_TEXT).text or ''
206 208 pub_time = tag_content.find(TAG_PUB_TIME).text
207 209 tripcode_tag = tag_content.find(TAG_TRIPCODE)
208 210 if tripcode_tag is not None:
209 211 tripcode = tripcode_tag.text or ''
210 212 else:
211 213 tripcode = ''
212 214
213 215 thread = tag_content.find(TAG_THREAD)
214 216 tags = []
215 217 if thread:
216 218 thread_id = thread.find(TAG_ID)
217 219 op_global_id, exists = GlobalId.from_xml_element(thread_id)
218 220 if exists:
219 221 opening_post = Post.objects.get(global_id=op_global_id)
220 222 else:
221 logger.debug('No thread exists for post {}'.format(global_id))
223 raise Exception(EXCEPTION_THREAD.format(global_id))
222 224 else:
223 225 opening_post = None
224 226 tag_tags = tag_content.find(TAG_TAGS)
225 227 for tag_tag in tag_tags:
226 228 tag, created = Tag.objects.get_or_create(
227 229 name=tag_tag.text)
228 230 tags.append(tag)
229 231
230 232 # TODO Check that the replied posts are already present
231 233 # before adding new ones
232 234
233 235 files = []
234 236 urls = []
235 237 tag_attachments = tag_content.find(TAG_ATTACHMENTS) or list()
236 238 tag_refs = tag_model.find(TAG_ATTACHMENT_REFS)
237 239 for attachment in tag_attachments:
238 240 if attachment.get(ATTR_ID_TYPE) == ID_TYPE_URL:
239 241 urls.append(attachment.text)
240 242 else:
241 243 tag_ref = tag_refs.find("{}[@ref='{}']".format(
242 244 TAG_ATTACHMENT_REF, attachment.text))
243 245 url = tag_ref.get(ATTR_URL)
244 246 try:
245 247 attached_file = download(hostname + url, validate=False)
246 248
247 249 if attached_file is None:
248 250 raise SyncException(EXCEPTION_DOWNLOAD)
249 251
250 252 hash = get_file_hash(attached_file)
251 253 if hash != attachment.text:
252 254 raise SyncException(EXCEPTION_HASH)
253 255
254 256 files.append(attached_file)
255 257 except forms.ValidationError:
256 258 urls.append(hostname+url)
257 259
258 260
259 261 if is_old:
260 262 post = global_id.post
261 263 Post.objects.update_post(
262 264 post, title=title, text=text, pub_time=pub_time,
263 265 tags=tags, files=files, file_urls=urls,
264 266 tripcode=tripcode, version=version)
265 267 logger.debug('Parsed updated post {}'.format(global_id))
266 268 else:
267 269 Post.objects.import_post(
268 270 title=title, text=text, pub_time=pub_time,
269 271 opening_post=opening_post, tags=tags,
270 272 global_id=global_id, files=files,
271 273 file_urls=urls, tripcode=tripcode,
272 274 version=version)
273 275 logger.debug('Parsed new post {}'.format(global_id))
274 276
275 277 @staticmethod
276 278 def generate_response_list(filters):
277 279 response = et.Element(TAG_RESPONSE)
278 280
279 281 status = et.SubElement(response, TAG_STATUS)
280 282 status.text = STATUS_SUCCESS
281 283
282 284 models = et.SubElement(response, TAG_MODELS)
283 285
284 286 posts = Post.objects.prefetch_related('global_id')
285 287 for post_filter in filters:
286 288 posts = post_filter.filter(posts)
287 289
288 290 for post in posts:
289 291 tag_model = et.SubElement(models, TAG_MODEL)
290 292 tag_id = et.SubElement(tag_model, TAG_ID)
291 293 post.global_id.to_xml_element(tag_id)
292 294 tag_version = et.SubElement(tag_model, TAG_VERSION)
293 295 tag_version.text = str(post.version)
294 296
295 297 return et.tostring(response, ENCODING_UNICODE)
296 298
297 299 @staticmethod
298 300 def _verify_model(global_id, content_str, tag_model):
299 301 """
300 302 Verifies all signatures for a single model.
301 303 """
302 304
303 305 signatures = []
304 306
305 307 tag_signatures = tag_model.find(TAG_SIGNATURES)
306 308 has_author_signature = False
307 309 for tag_signature in tag_signatures:
308 310 signature_type = tag_signature.get(ATTR_TYPE)
309 311 signature_value = tag_signature.get(ATTR_VALUE)
310 312 signature_key = tag_signature.get(ATTR_KEY)
311 313
312 314 if global_id.key_type == signature_type and\
313 315 global_id.key == signature_key:
314 316 has_author_signature = True
315 317
316 318 signature = Signature(key_type=signature_type,
317 319 key=signature_key,
318 320 signature=signature_value)
319 321
320 322 if not KeyPair.objects.verify(signature, content_str):
321 323 raise SyncException(EXCEPTION_SIGNATURE.format(content_str))
322 324
323 325 signatures.append(signature)
324 326 if not has_author_signature:
325 327 raise SyncException(EXCEPTION_AUTHOR_SIGNATURE.format(content_str))
326 328
327 329 return signatures
328 330
329 331 @staticmethod
330 332 def _attachment_to_xml(tag_attachments, tag_refs, attachment):
331 333 if tag_attachments is not None:
332 334 attachment_tag = et.SubElement(tag_attachments, TAG_ATTACHMENT)
333 335 if attachment.is_internal():
334 336 mimetype = get_file_mimetype(attachment.file.file)
335 337 attachment_tag.set(ATTR_MIMETYPE, mimetype)
336 338 attachment_tag.set(ATTR_ID_TYPE, ID_TYPE_MD5)
337 339 attachment_tag.text = attachment.hash
338 340 else:
339 341 attachment_tag.set(ATTR_ID_TYPE, ID_TYPE_URL)
340 342 attachment_tag.text = attachment.url
341 343
342 if tag_refs is not None:
344 if tag_refs is not None and attachment.is_internal():
343 345 attachment_ref = et.SubElement(tag_refs, TAG_ATTACHMENT_REF)
344 346 attachment_ref.set(ATTR_REF, attachment.hash)
345 attachment_ref.set(ATTR_URL, attachment.file.url)
347 attachment_ref.set(ATTR_URL, attachment.file.url)
348
349 @staticmethod
350 def generate_request_get(global_id_list: list):
351 """
352 Form a get request from a list of ModelId objects.
353 """
354
355 request = et.Element(TAG_REQUEST)
356 request.set(ATTR_TYPE, TYPE_GET)
357 request.set(ATTR_VERSION, '1.0')
358
359 model = et.SubElement(request, TAG_MODEL)
360 model.set(ATTR_VERSION, '1.0')
361 model.set(ATTR_NAME, 'post')
362
363 for global_id in global_id_list:
364 tag_id = et.SubElement(model, TAG_ID)
365 global_id.to_xml_element(tag_id)
366
367 return et.tostring(request, 'unicode')
368
369 @staticmethod
370 def generate_request_list(opening_post=None, tags=list(),
371 timestamp_from=None):
372 """
373 Form a pull request from a list of ModelId objects.
374 """
375
376 request = et.Element(TAG_REQUEST)
377 request.set(ATTR_TYPE, TYPE_LIST)
378 request.set(ATTR_VERSION, '1.0')
379
380 model = et.SubElement(request, TAG_MODEL)
381 model.set(ATTR_VERSION, '1.0')
382 model.set(ATTR_NAME, 'post')
383
384 if opening_post:
385 ThreadFilter().add_filter(model, opening_post)
386 if tags:
387 TagsFilter().add_filter(model, tags)
388 if timestamp_from:
389 TimestampFromFilter().add_filter(model, timestamp_from)
390
391 return et.tostring(request, 'unicode')
@@ -1,214 +1,253 b''
1 1 from django.test import TestCase
2 2
3 3 from boards.models import KeyPair, Post, Tag
4 4 from boards.models.post.sync import SyncManager
5 5 from boards.tests.mocks import MockRequest
6 6 from boards.views.sync import response_get, response_list
7 7
8 8 __author__ = 'neko259'
9 9
10 10
11 11 class SyncTest(TestCase):
12 12 def test_get(self):
13 13 """
14 14 Forms a GET request of a post and checks the response.
15 15 """
16 16
17 17 key = KeyPair.objects.generate_key(primary=True)
18 18 tag = Tag.objects.create(name='tag1')
19 19 post = Post.objects.create_post(title='test_title',
20 20 text='test_text\rline two',
21 21 tags=[tag])
22 22
23 23 request = MockRequest()
24 24 request.body = (
25 25 '<request type="get" version="1.0">'
26 26 '<model name="post" version="1.0">'
27 27 '<id key="%s" local-id="%d" type="%s" />'
28 28 '</model>'
29 29 '</request>' % (post.global_id.key,
30 30 post.id,
31 31 post.global_id.key_type)
32 32 )
33 33
34 34 response = response_get(request).content.decode()
35 35 self.assertTrue(
36 36 '<status>success</status>'
37 37 '<models>'
38 38 '<model name="post">'
39 39 '<content>'
40 40 '<id key="%s" local-id="%d" type="%s" />'
41 41 '<title>%s</title>'
42 42 '<text>%s</text>'
43 43 '<tags><tag>%s</tag></tags>'
44 44 '<pub-time>%s</pub-time>'
45 45 '<version>%s</version>'
46 46 '</content>' % (
47 47 post.global_id.key,
48 48 post.global_id.local_id,
49 49 post.global_id.key_type,
50 50 post.title,
51 51 post.get_sync_text(),
52 52 post.get_thread().get_tags().first().name,
53 53 post.get_pub_time_str(),
54 54 post.version,
55 55 ) in response,
56 56 'Wrong response generated for the GET request.')
57 57
58 58 post.delete()
59 59 key.delete()
60 60
61 61 KeyPair.objects.generate_key(primary=True)
62 62
63 63 SyncManager.parse_response_get(response, None)
64 64 self.assertEqual(1, Post.objects.count(),
65 65 'Post was not created from XML response.')
66 66
67 67 parsed_post = Post.objects.first()
68 68 self.assertEqual('tag1',
69 69 parsed_post.get_thread().get_tags().first().name,
70 70 'Invalid tag was parsed.')
71 71
72 72 SyncManager.parse_response_get(response, None)
73 73 self.assertEqual(1, Post.objects.count(),
74 74 'The same post was imported twice.')
75 75
76 76 self.assertEqual(1, parsed_post.global_id.signature_set.count(),
77 77 'Signature was not saved.')
78 78
79 79 post = parsed_post
80 80
81 81 # Trying to sync the same once more
82 82 response = response_get(request).content.decode()
83 83
84 84 self.assertTrue(
85 85 '<status>success</status>'
86 86 '<models>'
87 87 '<model name="post">'
88 88 '<content>'
89 89 '<id key="%s" local-id="%d" type="%s" />'
90 90 '<title>%s</title>'
91 91 '<text>%s</text>'
92 92 '<tags><tag>%s</tag></tags>'
93 93 '<pub-time>%s</pub-time>'
94 94 '<version>%s</version>'
95 95 '</content>' % (
96 96 post.global_id.key,
97 97 post.global_id.local_id,
98 98 post.global_id.key_type,
99 99 post.title,
100 100 post.get_sync_text(),
101 101 post.get_thread().get_tags().first().name,
102 102 post.get_pub_time_str(),
103 103 post.version,
104 104 ) in response,
105 105 'Wrong response generated for the GET request.')
106 106
107 107 def test_list_all(self):
108 108 key = KeyPair.objects.generate_key(primary=True)
109 109 tag = Tag.objects.create(name='tag1')
110 110 post = Post.objects.create_post(title='test_title',
111 111 text='test_text\rline two',
112 112 tags=[tag])
113 113 post2 = Post.objects.create_post(title='test title 2',
114 114 text='test text 2',
115 115 tags=[tag])
116 116
117 117 request_all = MockRequest()
118 118 request_all.body = (
119 119 '<request type="list" version="1.0">'
120 120 '<model name="post" version="1.0">'
121 121 '</model>'
122 122 '</request>'
123 123 )
124 124
125 125 response_all = response_list(request_all).content.decode()
126 126 self.assertTrue(
127 127 '<status>success</status>'
128 128 '<models>'
129 129 '<model>'
130 130 '<id key="{}" local-id="{}" type="{}" />'
131 131 '<version>{}</version>'
132 132 '</model>'
133 133 '<model>'
134 134 '<id key="{}" local-id="{}" type="{}" />'
135 135 '<version>{}</version>'
136 136 '</model>'
137 137 '</models>'.format(
138 138 post.global_id.key,
139 139 post.global_id.local_id,
140 140 post.global_id.key_type,
141 141 post.version,
142 142 post2.global_id.key,
143 143 post2.global_id.local_id,
144 144 post2.global_id.key_type,
145 145 post2.version,
146 146 ) in response_all,
147 147 'Wrong response generated for the LIST request for all posts.')
148 148
149 149 def test_list_existing_thread(self):
150 150 key = KeyPair.objects.generate_key(primary=True)
151 151 tag = Tag.objects.create(name='tag1')
152 152 post = Post.objects.create_post(title='test_title',
153 153 text='test_text\rline two',
154 154 tags=[tag])
155 155 post2 = Post.objects.create_post(title='test title 2',
156 156 text='test text 2',
157 157 tags=[tag])
158 158
159 159 request_thread = MockRequest()
160 160 request_thread.body = (
161 161 '<request type="list" version="1.0">'
162 162 '<model name="post" version="1.0">'
163 163 '<thread>{}</thread>'
164 164 '</model>'
165 165 '</request>'.format(
166 166 post.id,
167 167 )
168 168 )
169 169
170 170 response_thread = response_list(request_thread).content.decode()
171 171 self.assertTrue(
172 172 '<status>success</status>'
173 173 '<models>'
174 174 '<model>'
175 175 '<id key="{}" local-id="{}" type="{}" />'
176 176 '<version>{}</version>'
177 177 '</model>'
178 178 '</models>'.format(
179 179 post.global_id.key,
180 180 post.global_id.local_id,
181 181 post.global_id.key_type,
182 182 post.version,
183 183 ) in response_thread,
184 184 'Wrong response generated for the LIST request for posts of '
185 185 'existing thread.')
186 186
187 187 def test_list_non_existing_thread(self):
188 188 key = KeyPair.objects.generate_key(primary=True)
189 189 tag = Tag.objects.create(name='tag1')
190 190 post = Post.objects.create_post(title='test_title',
191 191 text='test_text\rline two',
192 192 tags=[tag])
193 193 post2 = Post.objects.create_post(title='test title 2',
194 194 text='test text 2',
195 195 tags=[tag])
196 196
197 197 request_thread = MockRequest()
198 198 request_thread.body = (
199 199 '<request type="list" version="1.0">'
200 200 '<model name="post" version="1.0">'
201 201 '<thread>{}</thread>'
202 202 '</model>'
203 203 '</request>'.format(
204 204 0,
205 205 )
206 206 )
207 207
208 208 response_thread = response_list(request_thread).content.decode()
209 209 self.assertTrue(
210 210 '<status>success</status>'
211 211 '<models />'
212 212 in response_thread,
213 213 'Wrong response generated for the LIST request for posts of '
214 214 'non-existing thread.')
215
216 def test_list_pub_time(self):
217 key = KeyPair.objects.generate_key(primary=True)
218 tag = Tag.objects.create(name='tag1')
219 post = Post.objects.create_post(title='test_title',
220 text='test_text\rline two',
221 tags=[tag])
222 post2 = Post.objects.create_post(title='test title 2',
223 text='test text 2',
224 tags=[tag])
225
226 request_thread = MockRequest()
227 request_thread.body = (
228 '<request type="list" version="1.0">'
229 '<model name="post" version="1.0">'
230 '<timestamp_from>{}</timestamp_from>'
231 '</model>'
232 '</request>'.format(
233 post.pub_time,
234 )
235 )
236
237 response_thread = response_list(request_thread).content.decode()
238 self.assertTrue(
239 '<status>success</status>'
240 '<models>'
241 '<model>'
242 '<id key="{}" local-id="{}" type="{}" />'
243 '<version>{}</version>'
244 '</model>'
245 '</models>'.format(
246 post2.global_id.key,
247 post2.global_id.local_id,
248 post2.global_id.key_type,
249 post2.version,
250 ) in response_thread,
251 'Wrong response generated for the LIST request for posts of '
252 'existing thread.')
253
@@ -1,157 +1,157 b''
1 1 """
2 2 This module contains helper functions and helper classes.
3 3 """
4 4 import hashlib
5 5 import uuid
6 6
7 7 from boards.abstracts.constants import FILE_DIRECTORY
8 8 from random import random
9 9 import time
10 10 import hmac
11 11
12 12 from django.core.cache import cache
13 13 from django.db.models import Model
14 14 from django import forms
15 15 from django.template.defaultfilters import filesizeformat
16 16 from django.utils import timezone
17 17 from django.utils.translation import ugettext_lazy as _
18 18 import magic
19 19 import os
20 20
21 21 import boards
22 22 from boards.settings import get_bool
23 23 from neboard import settings
24 24
25 25 CACHE_KEY_DELIMITER = '_'
26 26
27 27 HTTP_FORWARDED = 'HTTP_X_FORWARDED_FOR'
28 28 META_REMOTE_ADDR = 'REMOTE_ADDR'
29 29
30 30 SETTING_MESSAGES = 'Messages'
31 31 SETTING_ANON_MODE = 'AnonymousMode'
32 32
33 33 ANON_IP = '127.0.0.1'
34 34
35 35 FILE_EXTENSION_DELIMITER = '.'
36 36
37 37
38 38 def is_anonymous_mode():
39 39 return get_bool(SETTING_MESSAGES, SETTING_ANON_MODE)
40 40
41 41
42 42 def get_client_ip(request):
43 43 if is_anonymous_mode():
44 44 ip = ANON_IP
45 45 else:
46 46 x_forwarded_for = request.META.get(HTTP_FORWARDED)
47 47 if x_forwarded_for:
48 48 ip = x_forwarded_for.split(',')[-1].strip()
49 49 else:
50 50 ip = request.META.get(META_REMOTE_ADDR)
51 51 return ip
52 52
53 53
54 54 # TODO The output format is not epoch because it includes microseconds
55 55 def datetime_to_epoch(datetime):
56 56 return int(time.mktime(timezone.localtime(
57 57 datetime,timezone.get_current_timezone()).timetuple())
58 58 * 1000000 + datetime.microsecond)
59 59
60 60
61 61 def get_websocket_token(user_id='', timestamp=''):
62 62 """
63 63 Create token to validate information provided by new connection.
64 64 """
65 65
66 66 sign = hmac.new(settings.CENTRIFUGE_PROJECT_SECRET.encode())
67 67 sign.update(settings.CENTRIFUGE_PROJECT_ID.encode())
68 68 sign.update(user_id.encode())
69 69 sign.update(timestamp.encode())
70 70 token = sign.hexdigest()
71 71
72 72 return token
73 73
74 74
75 75 # TODO Test this carefully
76 76 def cached_result(key_method=None):
77 77 """
78 78 Caches method result in the Django's cache system, persisted by object name,
79 79 object name, model id if object is a Django model, args and kwargs if any.
80 80 """
81 81 def _cached_result(function):
82 82 def inner_func(obj, *args, **kwargs):
83 83 cache_key_params = [obj.__class__.__name__, function.__name__]
84 84
85 85 cache_key_params += args
86 86 for key, value in kwargs:
87 87 cache_key_params.append(key + ':' + value)
88 88
89 89 if isinstance(obj, Model):
90 90 cache_key_params.append(str(obj.id))
91 91
92 92 if key_method is not None:
93 93 cache_key_params += [str(arg) for arg in key_method(obj)]
94 94
95 95 cache_key = CACHE_KEY_DELIMITER.join(cache_key_params)
96 96
97 97 persisted_result = cache.get(cache_key)
98 98 if persisted_result is not None:
99 99 result = persisted_result
100 100 else:
101 101 result = function(obj, *args, **kwargs)
102 102 if result is not None:
103 103 cache.set(cache_key, result)
104 104
105 105 return result
106 106
107 107 return inner_func
108 108 return _cached_result
109 109
110 110
111 111 def get_file_hash(file) -> str:
112 112 md5 = hashlib.md5()
113 113 for chunk in file.chunks():
114 114 md5.update(chunk)
115 115 return md5.hexdigest()
116 116
117 117
118 118 def validate_file_size(size: int):
119 119 max_size = boards.settings.get_int('Forms', 'MaxFileSize')
120 if size > max_size:
120 if max_size > 0 and size > max_size:
121 121 raise forms.ValidationError(
122 122 _('File must be less than %s but is %s.')
123 123 % (filesizeformat(max_size), filesizeformat(size)))
124 124
125 125
126 126 def get_extension(filename):
127 127 return filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
128 128
129 129
130 130 def get_upload_filename(model_instance, old_filename):
131 131 extension = get_extension(old_filename)
132 132 new_name = '{}.{}'.format(uuid.uuid4(), extension)
133 133
134 134 return os.path.join(FILE_DIRECTORY, new_name)
135 135
136 136
137 137 def get_file_mimetype(file) -> str:
138 138 file_type = magic.from_buffer(file.chunks().__next__(), mime=True)
139 139 if file_type is None:
140 140 file_type = 'application/octet-stream'
141 141 elif type(file_type) == bytes:
142 142 file_type = file_type.decode()
143 143 return file_type
144 144
145 145
146 146 def get_domain(url: str) -> str:
147 147 """
148 148 Gets domain from an URL with random number of domain levels.
149 149 """
150 150 domain_parts = url.split('/')
151 151 if len(domain_parts) >= 2:
152 152 full_domain = domain_parts[2]
153 153 else:
154 154 full_domain = ''
155 155
156 156 return full_domain
157 157
@@ -1,42 +1,46 b''
1 1 from datetime import datetime
2 2 from datetime import timedelta
3 3
4 4 from django.db.models import Count
5 5 from django.shortcuts import render
6 6 from django.utils.decorators import method_decorator
7 7 from django.views.decorators.csrf import csrf_protect
8 8
9 9 from boards import settings
10 10 from boards.models import Post
11 11 from boards.models import Tag, Attachment, STATUS_ACTIVE
12 12 from boards.views.base import BaseBoardView
13 13
14 14 PARAM_SECTION_STR = 'section_str'
15 15 PARAM_LATEST_THREADS = 'latest_threads'
16 16 PARAM_IMAGES = 'images'
17 17
18 18 TEMPLATE = 'boards/landing.html'
19 19
20 20 RANDOM_IMAGE_COUNT = 3
21 21
22 22
23 23 class LandingView(BaseBoardView):
24 24 @method_decorator(csrf_protect)
25 25 def get(self, request):
26 26 params = dict()
27 27
28 28 params[PARAM_SECTION_STR] = Tag.objects.get_tag_url_list(
29 29 Tag.objects.filter(required=True))
30 30
31 31 today = datetime.now() - timedelta(1)
32 max_landing_threads = settings.get_int('View', 'MaxFavoriteThreads')
33 32 ops = Post.objects.filter(thread__replies__pub_time__gt=today, opening=True, thread__status=STATUS_ACTIVE)\
34 33 .annotate(today_post_count=Count('thread__replies'))\
35 .order_by('-pub_time')[:max_landing_threads]
34 .order_by('-pub_time')
35
36 max_landing_threads = settings.get_int('View', 'MaxFavoriteThreads')
37 if max_landing_threads > 0:
38 ops = ops[:max_landing_threads]
39
36 40 params[PARAM_LATEST_THREADS] = ops
37 41
38 42 params[PARAM_IMAGES] = Attachment.objects.get_random_images(
39 43 RANDOM_IMAGE_COUNT)
40 44
41 45 return render(request, TEMPLATE, params)
42 46
@@ -1,44 +1,45 b''
1 1 from django.shortcuts import render
2 2 from django.views.generic import View
3 from django.db.models import Q
3 4
4 5 from boards.abstracts.paginator import get_paginator
5 6 from boards.forms import SearchForm, PlainErrorList
6 7 from boards.models import Post
7 8
8 9
9 10 MIN_QUERY_LENGTH = 3
10 11 RESULTS_PER_PAGE = 10
11 12
12 13 FORM_QUERY = 'query'
13 14
14 15 CONTEXT_QUERY = 'query'
15 16 CONTEXT_FORM = 'form'
16 17 CONTEXT_PAGE = 'page'
17 18
18 19 REQUEST_PAGE = 'page'
19 20
20 21 __author__ = 'neko259'
21 22
22 23 TEMPLATE = 'search/search.html'
23 24
24 25
25 26 class BoardSearchView(View):
26 27 def get(self, request):
27 28 params = dict()
28 29
29 30 form = SearchForm(request.GET, error_class=PlainErrorList)
30 31 params[CONTEXT_FORM] = form
31 32
32 33 if form.is_valid():
33 34 query = form.cleaned_data[FORM_QUERY]
34 35 if len(query) >= MIN_QUERY_LENGTH:
35 results = Post.objects.filter(text__icontains=query)\
36 .order_by('-id')
36 results = Post.objects.filter(Q(text__icontains=query) |
37 Q(title__icontains=query)).order_by('-id')
37 38 paginator = get_paginator(results, RESULTS_PER_PAGE)
38 39
39 40 page = int(request.GET.get(REQUEST_PAGE, '1'))
40 41
41 42 params[CONTEXT_PAGE] = paginator.page(page)
42 43 params[CONTEXT_QUERY] = query
43 44
44 45 return render(request, TEMPLATE, params)
@@ -1,79 +1,83 b''
1 1 import logging
2 2
3 3 import xml.etree.ElementTree as et
4 4
5 5 from django.http import HttpResponse, Http404
6 6
7 from boards.abstracts.sync_filters import ThreadFilter, TAG_THREAD
7 from boards.abstracts.sync_filters import ThreadFilter, TagsFilter,\
8 TimestampFromFilter,\
9 TAG_THREAD, TAG_TAGS, TAG_TIME_FROM
8 10 from boards.models import GlobalId, Post
9 11 from boards.models.post.sync import SyncManager
10 12
11 13
12 14 logger = logging.getLogger('boards.sync')
13 15
14 16
15 17 FILTERS = {
16 18 TAG_THREAD: ThreadFilter,
19 TAG_TAGS: TagsFilter,
20 TAG_TIME_FROM: TimestampFromFilter,
17 21 }
18 22
19 23
20 24 def response_list(request):
21 25 request_xml = request.body
22 26
23 27 filters = []
24 28
25 29 if request_xml is None or len(request_xml) == 0:
26 30 return HttpResponse(content='Use the API')
27 31 else:
28 32 root_tag = et.fromstring(request_xml)
29 33 model_tag = root_tag[0]
30 34
31 35 for tag_filter in model_tag:
32 36 filter_name = tag_filter.tag
33 model_filter = FILTERS.get(filter_name)(tag_filter)
37 model_filter = FILTERS.get(filter_name)
34 38 if not model_filter:
35 39 logger.warning('Unavailable filter: {}'.format(filter_name))
36 filters.append(model_filter)
40 filters.append(model_filter(tag_filter))
37 41
38 42 response_xml = SyncManager.generate_response_list(filters)
39 43
40 44 return HttpResponse(content=response_xml)
41 45
42 46
43 47 def response_get(request):
44 48 """
45 49 Processes a GET request with post ID list and returns the posts XML list.
46 50 Request should contain an 'xml' post attribute with the actual request XML.
47 51 """
48 52
49 53 request_xml = request.body
50 54
51 55 if request_xml is None or len(request_xml) == 0:
52 56 return HttpResponse(content='Use the API')
53 57
54 58 posts = []
55 59
56 60 root_tag = et.fromstring(request_xml)
57 61 model_tag = root_tag[0]
58 62 for id_tag in model_tag:
59 63 global_id, exists = GlobalId.from_xml_element(id_tag)
60 64 if exists:
61 65 posts.append(Post.objects.get(global_id=global_id))
62 66
63 67 response_xml = SyncManager.generate_response_get(posts)
64 68
65 69 return HttpResponse(content=response_xml)
66 70
67 71
68 72 def get_post_sync_data(request, post_id):
69 73 try:
70 74 post = Post.objects.get(id=post_id)
71 75 except Post.DoesNotExist:
72 76 raise Http404()
73 77
74 78 xml_str = SyncManager.generate_response_get([post])
75 79
76 80 return HttpResponse(
77 81 content_type='text/xml; charset=utf-8',
78 82 content=xml_str,
79 83 )
General Comments 0
You need to be logged in to leave comments. Login now