##// END OF EJS Templates
Delete global ID when deleting post. Cache model's content XML tag into global ID
neko259 -
r1520:ecaafe92 decentral
parent child Browse files
Show More
@@ -1,251 +1,258 b''
1 import xml.etree.ElementTree as et
1 import xml.etree.ElementTree as et
2
2
3 from boards.models.attachment.downloaders import download
3 from boards.models.attachment.downloaders import download
4 from boards.utils import get_file_mimetype, get_file_hash
4 from boards.utils import get_file_mimetype, get_file_hash
5 from django.db import transaction
5 from django.db import transaction
6 from boards.models import KeyPair, GlobalId, Signature, Post, Tag
6 from boards.models import KeyPair, GlobalId, Signature, Post, Tag
7
7
8 ENCODING_UNICODE = 'unicode'
8 ENCODING_UNICODE = 'unicode'
9
9
10 TAG_MODEL = 'model'
10 TAG_MODEL = 'model'
11 TAG_REQUEST = 'request'
11 TAG_REQUEST = 'request'
12 TAG_RESPONSE = 'response'
12 TAG_RESPONSE = 'response'
13 TAG_ID = 'id'
13 TAG_ID = 'id'
14 TAG_STATUS = 'status'
14 TAG_STATUS = 'status'
15 TAG_MODELS = 'models'
15 TAG_MODELS = 'models'
16 TAG_TITLE = 'title'
16 TAG_TITLE = 'title'
17 TAG_TEXT = 'text'
17 TAG_TEXT = 'text'
18 TAG_THREAD = 'thread'
18 TAG_THREAD = 'thread'
19 TAG_PUB_TIME = 'pub-time'
19 TAG_PUB_TIME = 'pub-time'
20 TAG_SIGNATURES = 'signatures'
20 TAG_SIGNATURES = 'signatures'
21 TAG_SIGNATURE = 'signature'
21 TAG_SIGNATURE = 'signature'
22 TAG_CONTENT = 'content'
22 TAG_CONTENT = 'content'
23 TAG_ATTACHMENTS = 'attachments'
23 TAG_ATTACHMENTS = 'attachments'
24 TAG_ATTACHMENT = 'attachment'
24 TAG_ATTACHMENT = 'attachment'
25 TAG_TAGS = 'tags'
25 TAG_TAGS = 'tags'
26 TAG_TAG = 'tag'
26 TAG_TAG = 'tag'
27 TAG_ATTACHMENT_REFS = 'attachment-refs'
27 TAG_ATTACHMENT_REFS = 'attachment-refs'
28 TAG_ATTACHMENT_REF = 'attachment-ref'
28 TAG_ATTACHMENT_REF = 'attachment-ref'
29
29
30 TYPE_GET = 'get'
30 TYPE_GET = 'get'
31
31
32 ATTR_VERSION = 'version'
32 ATTR_VERSION = 'version'
33 ATTR_TYPE = 'type'
33 ATTR_TYPE = 'type'
34 ATTR_NAME = 'name'
34 ATTR_NAME = 'name'
35 ATTR_VALUE = 'value'
35 ATTR_VALUE = 'value'
36 ATTR_MIMETYPE = 'mimetype'
36 ATTR_MIMETYPE = 'mimetype'
37 ATTR_KEY = 'key'
37 ATTR_KEY = 'key'
38 ATTR_REF = 'ref'
38 ATTR_REF = 'ref'
39 ATTR_URL = 'url'
39 ATTR_URL = 'url'
40
40
41 STATUS_SUCCESS = 'success'
41 STATUS_SUCCESS = 'success'
42
42
43
43
44 class SyncException(Exception):
44 class SyncException(Exception):
45 pass
45 pass
46
46
47
47
48 class SyncManager:
48 class SyncManager:
49 @staticmethod
49 @staticmethod
50 def generate_response_get(model_list: list):
50 def generate_response_get(model_list: list):
51 response = et.Element(TAG_RESPONSE)
51 response = et.Element(TAG_RESPONSE)
52
52
53 status = et.SubElement(response, TAG_STATUS)
53 status = et.SubElement(response, TAG_STATUS)
54 status.text = STATUS_SUCCESS
54 status.text = STATUS_SUCCESS
55
55
56 models = et.SubElement(response, TAG_MODELS)
56 models = et.SubElement(response, TAG_MODELS)
57
57
58 # TODO Put global id's content into XML instad of manual serialization
59 for post in model_list:
58 for post in model_list:
60 model = et.SubElement(models, TAG_MODEL)
59 model = et.SubElement(models, TAG_MODEL)
61 model.set(ATTR_NAME, 'post')
60 model.set(ATTR_NAME, 'post')
62
61
63 content_tag = et.SubElement(model, TAG_CONTENT)
62 global_id = post.global_id
64
65 tag_id = et.SubElement(content_tag, TAG_ID)
66 post.global_id.to_xml_element(tag_id)
67
68 title = et.SubElement(content_tag, TAG_TITLE)
69 title.text = post.title
70
63
71 text = et.SubElement(content_tag, TAG_TEXT)
64 if global_id.content:
72 text.text = post.get_sync_text()
65 model.append(et.fromstring(global_id.content))
73
74 thread = post.get_thread()
75 if post.is_opening():
76 tag_tags = et.SubElement(content_tag, TAG_TAGS)
77 for tag in thread.get_tags():
78 tag_tag = et.SubElement(tag_tags, TAG_TAG)
79 tag_tag.text = tag.name
80 else:
66 else:
81 tag_thread = et.SubElement(content_tag, TAG_THREAD)
67 content_tag = et.SubElement(model, TAG_CONTENT)
82 thread_id = et.SubElement(tag_thread, TAG_ID)
68
83 thread.get_opening_post().global_id.to_xml_element(thread_id)
69 tag_id = et.SubElement(content_tag, TAG_ID)
70 global_id.to_xml_element(tag_id)
84
71
85 pub_time = et.SubElement(content_tag, TAG_PUB_TIME)
72 title = et.SubElement(content_tag, TAG_TITLE)
86 pub_time.text = str(post.get_pub_time_str())
73 title.text = post.title
74
75 text = et.SubElement(content_tag, TAG_TEXT)
76 text.text = post.get_sync_text()
87
77
88 images = post.images.all()
78 thread = post.get_thread()
89 attachments = post.attachments.all()
79 if post.is_opening():
90 if len(images) > 0 or len(attachments) > 0:
80 tag_tags = et.SubElement(content_tag, TAG_TAGS)
91 attachments_tag = et.SubElement(content_tag, TAG_ATTACHMENTS)
81 for tag in thread.get_tags():
92 attachment_refs = et.SubElement(model, TAG_ATTACHMENT_REFS)
82 tag_tag = et.SubElement(tag_tags, TAG_TAG)
83 tag_tag.text = tag.name
84 else:
85 tag_thread = et.SubElement(content_tag, TAG_THREAD)
86 thread_id = et.SubElement(tag_thread, TAG_ID)
87 thread.get_opening_post().global_id.to_xml_element(thread_id)
88
89 pub_time = et.SubElement(content_tag, TAG_PUB_TIME)
90 pub_time.text = str(post.get_pub_time_str())
93
91
94 for image in images:
92 images = post.images.all()
95 SyncManager._attachment_to_xml(
93 attachments = post.attachments.all()
96 attachments_tag, attachment_refs, image.image.file,
94 if len(images) > 0 or len(attachments) > 0:
97 image.hash, image.image.url)
95 attachments_tag = et.SubElement(content_tag, TAG_ATTACHMENTS)
98 for file in attachments:
96 attachment_refs = et.SubElement(model, TAG_ATTACHMENT_REFS)
99 SyncManager._attachment_to_xml(
97
100 attachments_tag, attachment_refs, file.file.file,
98 for image in images:
101 file.hash, file.file.url)
99 SyncManager._attachment_to_xml(
100 attachments_tag, attachment_refs, image.image.file,
101 image.hash, image.image.url)
102 for file in attachments:
103 SyncManager._attachment_to_xml(
104 attachments_tag, attachment_refs, file.file.file,
105 file.hash, file.file.url)
106
107 global_id.content = et.tostring(content_tag, ENCODING_UNICODE)
108 global_id.save()
102
109
103 signatures_tag = et.SubElement(model, TAG_SIGNATURES)
110 signatures_tag = et.SubElement(model, TAG_SIGNATURES)
104 post_signatures = post.global_id.signature_set.all()
111 post_signatures = global_id.signature_set.all()
105 if post_signatures:
112 if post_signatures:
106 signatures = post_signatures
113 signatures = post_signatures
107 else:
114 else:
108 key = KeyPair.objects.get(public_key=post.global_id.key)
115 key = KeyPair.objects.get(public_key=global_id.key)
109 signature = Signature(
116 signature = Signature(
110 key_type=key.key_type,
117 key_type=key.key_type,
111 key=key.public_key,
118 key=key.public_key,
112 signature=key.sign(et.tostring(content_tag, encoding=ENCODING_UNICODE)),
119 signature=key.sign(global_id.content),
113 global_id=post.global_id,
120 global_id=global_id,
114 )
121 )
115 signature.save()
122 signature.save()
116 signatures = [signature]
123 signatures = [signature]
117 for signature in signatures:
124 for signature in signatures:
118 signature_tag = et.SubElement(signatures_tag, TAG_SIGNATURE)
125 signature_tag = et.SubElement(signatures_tag, TAG_SIGNATURE)
119 signature_tag.set(ATTR_TYPE, signature.key_type)
126 signature_tag.set(ATTR_TYPE, signature.key_type)
120 signature_tag.set(ATTR_VALUE, signature.signature)
127 signature_tag.set(ATTR_VALUE, signature.signature)
121 signature_tag.set(ATTR_KEY, signature.key)
128 signature_tag.set(ATTR_KEY, signature.key)
122
129
123 return et.tostring(response, ENCODING_UNICODE)
130 return et.tostring(response, ENCODING_UNICODE)
124
131
125 @staticmethod
132 @staticmethod
126 @transaction.atomic
133 @transaction.atomic
127 def parse_response_get(response_xml, hostname):
134 def parse_response_get(response_xml, hostname):
128 tag_root = et.fromstring(response_xml)
135 tag_root = et.fromstring(response_xml)
129 tag_status = tag_root.find(TAG_STATUS)
136 tag_status = tag_root.find(TAG_STATUS)
130 if STATUS_SUCCESS == tag_status.text:
137 if STATUS_SUCCESS == tag_status.text:
131 tag_models = tag_root.find(TAG_MODELS)
138 tag_models = tag_root.find(TAG_MODELS)
132 for tag_model in tag_models:
139 for tag_model in tag_models:
133 tag_content = tag_model.find(TAG_CONTENT)
140 tag_content = tag_model.find(TAG_CONTENT)
134
141
135 signatures = SyncManager._verify_model(tag_content, tag_model)
142 signatures = SyncManager._verify_model(tag_content, tag_model)
136
143
137 tag_id = tag_content.find(TAG_ID)
144 tag_id = tag_content.find(TAG_ID)
138 global_id, exists = GlobalId.from_xml_element(tag_id)
145 global_id, exists = GlobalId.from_xml_element(tag_id)
139
146
140 if exists:
147 if exists:
141 print('Post with same ID already exists')
148 print('Post with same ID already exists')
142 else:
149 else:
143 global_id.content = et.to_string(tag_content,
150 global_id.content = et.tostring(tag_content,
144 ENCODING_UNICODE)
151 ENCODING_UNICODE)
145 global_id.save()
152 global_id.save()
146 for signature in signatures:
153 for signature in signatures:
147 signature.global_id = global_id
154 signature.global_id = global_id
148 signature.save()
155 signature.save()
149
156
150 title = tag_content.find(TAG_TITLE).text or ''
157 title = tag_content.find(TAG_TITLE).text or ''
151 text = tag_content.find(TAG_TEXT).text or ''
158 text = tag_content.find(TAG_TEXT).text or ''
152 pub_time = tag_content.find(TAG_PUB_TIME).text
159 pub_time = tag_content.find(TAG_PUB_TIME).text
153
160
154 thread = tag_content.find(TAG_THREAD)
161 thread = tag_content.find(TAG_THREAD)
155 tags = []
162 tags = []
156 if thread:
163 if thread:
157 thread_id = thread.find(TAG_ID)
164 thread_id = thread.find(TAG_ID)
158 op_global_id, exists = GlobalId.from_xml_element(thread_id)
165 op_global_id, exists = GlobalId.from_xml_element(thread_id)
159 if exists:
166 if exists:
160 opening_post = Post.objects.get(global_id=op_global_id)
167 opening_post = Post.objects.get(global_id=op_global_id)
161 else:
168 else:
162 raise SyncException('Load the OP first')
169 raise SyncException('Load the OP first')
163 else:
170 else:
164 opening_post = None
171 opening_post = None
165 tag_tags = tag_content.find(TAG_TAGS)
172 tag_tags = tag_content.find(TAG_TAGS)
166 for tag_tag in tag_tags:
173 for tag_tag in tag_tags:
167 tag, created = Tag.objects.get_or_create(
174 tag, created = Tag.objects.get_or_create(
168 name=tag_tag.text)
175 name=tag_tag.text)
169 tags.append(tag)
176 tags.append(tag)
170
177
171 # TODO Check that the replied posts are already present
178 # TODO Check that the replied posts are already present
172 # before adding new ones
179 # before adding new ones
173
180
174 files = []
181 files = []
175 tag_attachments = tag_content.find(TAG_ATTACHMENTS) or list()
182 tag_attachments = tag_content.find(TAG_ATTACHMENTS) or list()
176 tag_refs = tag_model.find(TAG_ATTACHMENT_REFS)
183 tag_refs = tag_model.find(TAG_ATTACHMENT_REFS)
177 for attachment in tag_attachments:
184 for attachment in tag_attachments:
178 tag_ref = tag_refs.find("{}[@ref='{}']".format(
185 tag_ref = tag_refs.find("{}[@ref='{}']".format(
179 TAG_ATTACHMENT_REF, attachment.text))
186 TAG_ATTACHMENT_REF, attachment.text))
180 url = tag_ref.get(ATTR_URL)
187 url = tag_ref.get(ATTR_URL)
181 attached_file = download(hostname + url)
188 attached_file = download(hostname + url)
182 if attached_file is None:
189 if attached_file is None:
183 raise SyncException('File was not dowloaded')
190 raise SyncException('File was not dowloaded')
184
191
185 hash = get_file_hash(file)
192 hash = get_file_hash(file)
186 if hash != attachment.text:
193 if hash != attachment.text:
187 raise SyncException('File hash does not match attachment hash')
194 raise SyncException('File hash does not match attachment hash')
188
195
189 files.append(attached_file)
196 files.append(attached_file)
190
197
191 Post.objects.import_post(
198 Post.objects.import_post(
192 title=title, text=text, pub_time=pub_time,
199 title=title, text=text, pub_time=pub_time,
193 opening_post=opening_post, tags=tags,
200 opening_post=opening_post, tags=tags,
194 global_id=global_id, files=files)
201 global_id=global_id, files=files)
195 else:
202 else:
196 raise SyncException('Sync node returned an error: {}'.format(
203 raise SyncException('Sync node returned an error: {}'.format(
197 tag_status.text))
204 tag_status.text))
198
205
199 @staticmethod
206 @staticmethod
200 def generate_response_pull():
207 def generate_response_pull():
201 response = et.Element(TAG_RESPONSE)
208 response = et.Element(TAG_RESPONSE)
202
209
203 status = et.SubElement(response, TAG_STATUS)
210 status = et.SubElement(response, TAG_STATUS)
204 status.text = STATUS_SUCCESS
211 status.text = STATUS_SUCCESS
205
212
206 models = et.SubElement(response, TAG_MODELS)
213 models = et.SubElement(response, TAG_MODELS)
207
214
208 for post in Post.objects.all():
215 for post in Post.objects.all():
209 tag_id = et.SubElement(models, TAG_ID)
216 tag_id = et.SubElement(models, TAG_ID)
210 post.global_id.to_xml_element(tag_id)
217 post.global_id.to_xml_element(tag_id)
211
218
212 return et.tostring(response, ENCODING_UNICODE)
219 return et.tostring(response, ENCODING_UNICODE)
213
220
214 @staticmethod
221 @staticmethod
215 def _verify_model(tag_content, tag_model):
222 def _verify_model(tag_content, tag_model):
216 """
223 """
217 Verifies all signatures for a single model.
224 Verifies all signatures for a single model.
218 """
225 """
219
226
220 signatures = []
227 signatures = []
221
228
222 tag_signatures = tag_model.find(TAG_SIGNATURES)
229 tag_signatures = tag_model.find(TAG_SIGNATURES)
223 for tag_signature in tag_signatures:
230 for tag_signature in tag_signatures:
224 signature_type = tag_signature.get(ATTR_TYPE)
231 signature_type = tag_signature.get(ATTR_TYPE)
225 signature_value = tag_signature.get(ATTR_VALUE)
232 signature_value = tag_signature.get(ATTR_VALUE)
226 signature_key = tag_signature.get(ATTR_KEY)
233 signature_key = tag_signature.get(ATTR_KEY)
227
234
228 signature = Signature(key_type=signature_type,
235 signature = Signature(key_type=signature_type,
229 key=signature_key,
236 key=signature_key,
230 signature=signature_value)
237 signature=signature_value)
231
238
232 content = et.tostring(tag_content, ENCODING_UNICODE)
239 content = et.tostring(tag_content, ENCODING_UNICODE)
233
240
234 if not KeyPair.objects.verify(
241 if not KeyPair.objects.verify(
235 signature, content):
242 signature, content):
236 raise SyncException('Invalid model signature for {}'.format(content))
243 raise SyncException('Invalid model signature for {}'.format(content))
237
244
238 signatures.append(signature)
245 signatures.append(signature)
239
246
240 return signatures
247 return signatures
241
248
242 @staticmethod
249 @staticmethod
243 def _attachment_to_xml(tag_attachments, tag_refs, file, hash, url):
250 def _attachment_to_xml(tag_attachments, tag_refs, file, hash, url):
244 mimetype = get_file_mimetype(file)
251 mimetype = get_file_mimetype(file)
245 attachment = et.SubElement(tag_attachments, TAG_ATTACHMENT)
252 attachment = et.SubElement(tag_attachments, TAG_ATTACHMENT)
246 attachment.set(ATTR_MIMETYPE, mimetype)
253 attachment.set(ATTR_MIMETYPE, mimetype)
247 attachment.text = hash
254 attachment.text = hash
248
255
249 attachment_ref = et.SubElement(tag_refs, TAG_ATTACHMENT_REF)
256 attachment_ref = et.SubElement(tag_refs, TAG_ATTACHMENT_REF)
250 attachment_ref.set(ATTR_REF, hash)
257 attachment_ref.set(ATTR_REF, hash)
251 attachment_ref.set(ATTR_URL, url)
258 attachment_ref.set(ATTR_URL, url)
@@ -1,138 +1,144 b''
1 import xml.etree.ElementTree as et
1 import xml.etree.ElementTree as et
2 from django.db import models
2 from django.db import models
3
3
4
4
5 TAG_MODEL = 'model'
5 TAG_MODEL = 'model'
6 TAG_REQUEST = 'request'
6 TAG_REQUEST = 'request'
7 TAG_ID = 'id'
7 TAG_ID = 'id'
8
8
9 TYPE_GET = 'get'
9 TYPE_GET = 'get'
10 TYPE_PULL = 'pull'
10 TYPE_PULL = 'pull'
11
11
12 ATTR_VERSION = 'version'
12 ATTR_VERSION = 'version'
13 ATTR_TYPE = 'type'
13 ATTR_TYPE = 'type'
14 ATTR_NAME = 'name'
14 ATTR_NAME = 'name'
15
15
16 ATTR_KEY = 'key'
16 ATTR_KEY = 'key'
17 ATTR_KEY_TYPE = 'type'
17 ATTR_KEY_TYPE = 'type'
18 ATTR_LOCAL_ID = 'local-id'
18 ATTR_LOCAL_ID = 'local-id'
19
19
20
20
21 class GlobalIdManager(models.Manager):
21 class GlobalIdManager(models.Manager):
22 def generate_request_get(self, global_id_list: list):
22 def generate_request_get(self, global_id_list: list):
23 """
23 """
24 Form a get request from a list of ModelId objects.
24 Form a get request from a list of ModelId objects.
25 """
25 """
26
26
27 request = et.Element(TAG_REQUEST)
27 request = et.Element(TAG_REQUEST)
28 request.set(ATTR_TYPE, TYPE_GET)
28 request.set(ATTR_TYPE, TYPE_GET)
29 request.set(ATTR_VERSION, '1.0')
29 request.set(ATTR_VERSION, '1.0')
30
30
31 model = et.SubElement(request, TAG_MODEL)
31 model = et.SubElement(request, TAG_MODEL)
32 model.set(ATTR_VERSION, '1.0')
32 model.set(ATTR_VERSION, '1.0')
33 model.set(ATTR_NAME, 'post')
33 model.set(ATTR_NAME, 'post')
34
34
35 for global_id in global_id_list:
35 for global_id in global_id_list:
36 tag_id = et.SubElement(model, TAG_ID)
36 tag_id = et.SubElement(model, TAG_ID)
37 global_id.to_xml_element(tag_id)
37 global_id.to_xml_element(tag_id)
38
38
39 return et.tostring(request, 'unicode')
39 return et.tostring(request, 'unicode')
40
40
41 def generate_request_pull(self):
41 def generate_request_pull(self):
42 """
42 """
43 Form a pull request from a list of ModelId objects.
43 Form a pull request from a list of ModelId objects.
44 """
44 """
45
45
46 request = et.Element(TAG_REQUEST)
46 request = et.Element(TAG_REQUEST)
47 request.set(ATTR_TYPE, TYPE_PULL)
47 request.set(ATTR_TYPE, TYPE_PULL)
48 request.set(ATTR_VERSION, '1.0')
48 request.set(ATTR_VERSION, '1.0')
49
49
50 model = et.SubElement(request, TAG_MODEL)
50 model = et.SubElement(request, TAG_MODEL)
51 model.set(ATTR_VERSION, '1.0')
51 model.set(ATTR_VERSION, '1.0')
52 model.set(ATTR_NAME, 'post')
52 model.set(ATTR_NAME, 'post')
53
53
54 return et.tostring(request, 'unicode')
54 return et.tostring(request, 'unicode')
55
55
56 def global_id_exists(self, global_id):
56 def global_id_exists(self, global_id):
57 """
57 """
58 Checks if the same global id already exists in the system.
58 Checks if the same global id already exists in the system.
59 """
59 """
60
60
61 return self.filter(key=global_id.key,
61 return self.filter(key=global_id.key,
62 key_type=global_id.key_type,
62 key_type=global_id.key_type,
63 local_id=global_id.local_id).exists()
63 local_id=global_id.local_id).exists()
64
64
65
65
66 class GlobalId(models.Model):
66 class GlobalId(models.Model):
67 """
68 Global model ID and cache.
69 Key, key type and local ID make a single global identificator of the model.
70 Content is an XML cache of the model that can be passed along between nodes
71 without manual serialization each time.
72 """
67 class Meta:
73 class Meta:
68 app_label = 'boards'
74 app_label = 'boards'
69
75
70 objects = GlobalIdManager()
76 objects = GlobalIdManager()
71
77
72 def __init__(self, *args, **kwargs):
78 def __init__(self, *args, **kwargs):
73 models.Model.__init__(self, *args, **kwargs)
79 models.Model.__init__(self, *args, **kwargs)
74
80
75 if 'key' in kwargs and 'key_type' in kwargs and 'local_id' in kwargs:
81 if 'key' in kwargs and 'key_type' in kwargs and 'local_id' in kwargs:
76 self.key = kwargs['key']
82 self.key = kwargs['key']
77 self.key_type = kwargs['key_type']
83 self.key_type = kwargs['key_type']
78 self.local_id = kwargs['local_id']
84 self.local_id = kwargs['local_id']
79
85
80 key = models.TextField()
86 key = models.TextField()
81 key_type = models.TextField()
87 key_type = models.TextField()
82 local_id = models.IntegerField()
88 local_id = models.IntegerField()
83 content = models.TextField(blank=True, null=True)
89 content = models.TextField(blank=True, null=True)
84
90
85 def __str__(self):
91 def __str__(self):
86 return '%s::%s::%d' % (self.key_type, self.key, self.local_id)
92 return '%s::%s::%d' % (self.key_type, self.key, self.local_id)
87
93
88 def to_xml_element(self, element: et.Element):
94 def to_xml_element(self, element: et.Element):
89 """
95 """
90 Exports global id to an XML element.
96 Exports global id to an XML element.
91 """
97 """
92
98
93 element.set(ATTR_KEY, self.key)
99 element.set(ATTR_KEY, self.key)
94 element.set(ATTR_KEY_TYPE, self.key_type)
100 element.set(ATTR_KEY_TYPE, self.key_type)
95 element.set(ATTR_LOCAL_ID, str(self.local_id))
101 element.set(ATTR_LOCAL_ID, str(self.local_id))
96
102
97 @staticmethod
103 @staticmethod
98 def from_xml_element(element: et.Element):
104 def from_xml_element(element: et.Element):
99 """
105 """
100 Parses XML id tag and gets global id from it.
106 Parses XML id tag and gets global id from it.
101
107
102 Arguments:
108 Arguments:
103 element -- the XML 'id' element
109 element -- the XML 'id' element
104
110
105 Returns:
111 Returns:
106 global_id -- id itself
112 global_id -- id itself
107 exists -- True if the global id was taken from database, False if it
113 exists -- True if the global id was taken from database, False if it
108 did not exist and was created.
114 did not exist and was created.
109 """
115 """
110
116
111 try:
117 try:
112 return GlobalId.objects.get(key=element.get(ATTR_KEY),
118 return GlobalId.objects.get(key=element.get(ATTR_KEY),
113 key_type=element.get(ATTR_KEY_TYPE),
119 key_type=element.get(ATTR_KEY_TYPE),
114 local_id=int(element.get(
120 local_id=int(element.get(
115 ATTR_LOCAL_ID))), True
121 ATTR_LOCAL_ID))), True
116 except GlobalId.DoesNotExist:
122 except GlobalId.DoesNotExist:
117 return GlobalId(key=element.get(ATTR_KEY),
123 return GlobalId(key=element.get(ATTR_KEY),
118 key_type=element.get(ATTR_KEY_TYPE),
124 key_type=element.get(ATTR_KEY_TYPE),
119 local_id=int(element.get(ATTR_LOCAL_ID))), False
125 local_id=int(element.get(ATTR_LOCAL_ID))), False
120
126
121
127
122 class Signature(models.Model):
128 class Signature(models.Model):
123 class Meta:
129 class Meta:
124 app_label = 'boards'
130 app_label = 'boards'
125
131
126 def __init__(self, *args, **kwargs):
132 def __init__(self, *args, **kwargs):
127 models.Model.__init__(self, *args, **kwargs)
133 models.Model.__init__(self, *args, **kwargs)
128
134
129 if 'key' in kwargs and 'key_type' in kwargs and 'signature' in kwargs:
135 if 'key' in kwargs and 'key_type' in kwargs and 'signature' in kwargs:
130 self.key_type = kwargs['key_type']
136 self.key_type = kwargs['key_type']
131 self.key = kwargs['key']
137 self.key = kwargs['key']
132 self.signature = kwargs['signature']
138 self.signature = kwargs['signature']
133
139
134 key_type = models.TextField()
140 key_type = models.TextField()
135 key = models.TextField()
141 key = models.TextField()
136 signature = models.TextField()
142 signature = models.TextField()
137
143
138 global_id = models.ForeignKey('GlobalId')
144 global_id = models.ForeignKey('GlobalId')
@@ -1,81 +1,87 b''
1 import re
1 import re
2 from boards.mdx_neboard import get_parser
2 from boards.mdx_neboard import get_parser
3
3
4 from boards.models import Post, GlobalId
4 from boards.models import Post, GlobalId
5 from boards.models.post import REGEX_NOTIFICATION
5 from boards.models.post import REGEX_NOTIFICATION
6 from boards.models.post import REGEX_REPLY, REGEX_GLOBAL_REPLY
6 from boards.models.post import REGEX_REPLY, REGEX_GLOBAL_REPLY
7 from boards.models.user import Notification
7 from boards.models.user import Notification
8 from django.db.models.signals import post_save, pre_save, pre_delete, \
8 from django.db.models.signals import post_save, pre_save, pre_delete, \
9 post_delete
9 post_delete
10 from django.dispatch import receiver
10 from django.dispatch import receiver
11 from django.utils import timezone
11 from django.utils import timezone
12
12
13
13
14 @receiver(post_save, sender=Post)
14 @receiver(post_save, sender=Post)
15 def connect_replies(instance, **kwargs):
15 def connect_replies(instance, **kwargs):
16 for reply_number in re.finditer(REGEX_REPLY, instance.get_raw_text()):
16 for reply_number in re.finditer(REGEX_REPLY, instance.get_raw_text()):
17 post_id = reply_number.group(1)
17 post_id = reply_number.group(1)
18
18
19 try:
19 try:
20 referenced_post = Post.objects.get(id=post_id)
20 referenced_post = Post.objects.get(id=post_id)
21
21
22 referenced_post.referenced_posts.add(instance)
22 referenced_post.referenced_posts.add(instance)
23 referenced_post.last_edit_time = instance.pub_time
23 referenced_post.last_edit_time = instance.pub_time
24 referenced_post.build_refmap()
24 referenced_post.build_refmap()
25 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
25 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
26 except Post.ObjectDoesNotExist:
26 except Post.ObjectDoesNotExist:
27 pass
27 pass
28
28
29
29
30 @receiver(post_save, sender=Post)
30 @receiver(post_save, sender=Post)
31 def connect_global_replies(instance, **kwargs):
31 def connect_global_replies(instance, **kwargs):
32 for reply_number in re.finditer(REGEX_GLOBAL_REPLY, instance.get_raw_text()):
32 for reply_number in re.finditer(REGEX_GLOBAL_REPLY, instance.get_raw_text()):
33 key_type = reply_number.group(1)
33 key_type = reply_number.group(1)
34 key = reply_number.group(2)
34 key = reply_number.group(2)
35 local_id = reply_number.group(3)
35 local_id = reply_number.group(3)
36
36
37 try:
37 try:
38 global_id = GlobalId.objects.get(key_type=key_type, key=key,
38 global_id = GlobalId.objects.get(key_type=key_type, key=key,
39 local_id=local_id)
39 local_id=local_id)
40 referenced_post = Post.objects.get(global_id=global_id)
40 referenced_post = Post.objects.get(global_id=global_id)
41 referenced_post.referenced_posts.add(instance)
41 referenced_post.referenced_posts.add(instance)
42 referenced_post.last_edit_time = instance.pub_time
42 referenced_post.last_edit_time = instance.pub_time
43 referenced_post.build_refmap()
43 referenced_post.build_refmap()
44 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
44 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
45 except (GlobalId.ObjectDoesNotExist, Post.ObjectDoesNotExist):
45 except (GlobalId.ObjectDoesNotExist, Post.ObjectDoesNotExist):
46 pass
46 pass
47
47
48
48
49 @receiver(post_save, sender=Post)
49 @receiver(post_save, sender=Post)
50 def connect_notifications(instance, **kwargs):
50 def connect_notifications(instance, **kwargs):
51 for reply_number in re.finditer(REGEX_NOTIFICATION, instance.get_raw_text()):
51 for reply_number in re.finditer(REGEX_NOTIFICATION, instance.get_raw_text()):
52 user_name = reply_number.group(1).lower()
52 user_name = reply_number.group(1).lower()
53 Notification.objects.get_or_create(name=user_name, post=instance)
53 Notification.objects.get_or_create(name=user_name, post=instance)
54
54
55
55
56 @receiver(pre_save, sender=Post)
56 @receiver(pre_save, sender=Post)
57 def preparse_text(instance, **kwargs):
57 def preparse_text(instance, **kwargs):
58 instance._text_rendered = get_parser().parse(instance.get_raw_text())
58 instance._text_rendered = get_parser().parse(instance.get_raw_text())
59
59
60
60
61 @receiver(pre_delete, sender=Post)
61 @receiver(pre_delete, sender=Post)
62 def delete_images(instance, **kwargs):
62 def delete_images(instance, **kwargs):
63 for image in instance.images.all():
63 for image in instance.images.all():
64 image_refs_count = image.post_images.count()
64 image_refs_count = image.post_images.count()
65 if image_refs_count == 1:
65 if image_refs_count == 1:
66 image.delete()
66 image.delete()
67
67
68
68
69 @receiver(pre_delete, sender=Post)
69 @receiver(pre_delete, sender=Post)
70 def delete_attachments(instance, **kwargs):
70 def delete_attachments(instance, **kwargs):
71 for attachment in instance.attachments.all():
71 for attachment in instance.attachments.all():
72 attachment_refs_count = attachment.attachment_posts.count()
72 attachment_refs_count = attachment.attachment_posts.count()
73 if attachment_refs_count == 1:
73 if attachment_refs_count == 1:
74 attachment.delete()
74 attachment.delete()
75
75
76
76
77 @receiver(post_delete, sender=Post)
77 @receiver(post_delete, sender=Post)
78 def update_thread_on_delete(instance, **kwargs):
78 def update_thread_on_delete(instance, **kwargs):
79 thread = instance.get_thread()
79 thread = instance.get_thread()
80 thread.last_edit_time = timezone.now()
80 thread.last_edit_time = timezone.now()
81 thread.save()
81 thread.save()
82
83
84 @receiver(post_delete, sender=Post)
85 def delete_global_id(instance, **kwargs):
86 if instance.global_id and instance.global_id.id:
87 instance.global_id.delete()
General Comments 0
You need to be logged in to leave comments. Login now