##// END OF EJS Templates
Merge tip
Bohdan Horbeshko -
r2146:69a1b01a merge lite
parent child Browse files
Show More
@@ -51,3 +51,4 b' 19785af352684884f94566785ba1b13b3ddc5216'
51 757b4ada4ca121db4296b13ec0df491645db8fd0 3.5.0
51 757b4ada4ca121db4296b13ec0df491645db8fd0 3.5.0
52 3da1a2d02072eec5419956265dcd8c7f47155c12 4.0.0
52 3da1a2d02072eec5419956265dcd8c7f47155c12 4.0.0
53 da8f0f9d5099ee8b22aa317b91beb06543011f1d 4.1.0
53 da8f0f9d5099ee8b22aa317b91beb06543011f1d 4.1.0
54 9619ecc0f79b705c94b3ac873896809eaf7779a6 4.1.1
@@ -1,8 +1,11 b''
1 import xml.etree.ElementTree as et
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 TAG_THREAD = 'thread'
5 TAG_THREAD = 'thread'
6 TAG_TAGS = 'tags'
7 TAG_TAG = 'tag'
8 TAG_TIME_FROM = 'timestamp_from'
6
9
7
10
8 class PostFilter:
11 class PostFilter:
@@ -29,3 +32,35 b' class ThreadFilter(PostFilter):'
29 def add_filter(self, model_tag, value):
32 def add_filter(self, model_tag, value):
30 thread_tag = et.SubElement(model_tag, TAG_THREAD)
33 thread_tag = et.SubElement(model_tag, TAG_THREAD)
31 thread_tag.text = str(value)
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,5 +1,5 b''
1 [Version]
1 [Version]
2 Version = 4.1.0 2017
2 Version = 4.1.1 2017
3 SiteName = Neboard DEV
3 SiteName = Neboard DEV
4
4
5 [Cache]
5 [Cache]
@@ -26,6 +26,10 b' class Command(BaseCommand):'
26 ' number of posts in one')
26 ' number of posts in one')
27 parser.add_argument('--thread', type=int,
27 parser.add_argument('--thread', type=int,
28 help='Get posts of one specific thread')
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 def handle(self, *args, **options):
34 def handle(self, *args, **options):
31 logger = logging.getLogger('boards.sync')
35 logger = logging.getLogger('boards.sync')
@@ -57,9 +61,19 b' class Command(BaseCommand):'
57 else:
61 else:
58 logger.info('Running LIST request...')
62 logger.info('Running LIST request...')
59 h = httplib2.Http()
63 h = httplib2.Http()
64
65 tags = []
66 tags_str = options.get('tags')
67 if tags_str:
68 tags = tags_str.split(',')
69
60 xml = SyncManager.generate_request_list(
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 response, content = h.request(list_url, method="POST", body=xml)
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 logger.info('Processing response...')
77 logger.info('Processing response...')
64
78
65 root = ET.fromstring(content)
79 root = ET.fromstring(content)
@@ -93,6 +107,8 b' class Command(BaseCommand):'
93 logger.info('Processing response...')
107 logger.info('Processing response...')
94
108
95 SyncManager.parse_response_get(content, file_url)
109 SyncManager.parse_response_get(content, file_url)
110
111 logger.info('Sync completed successfully')
96 else:
112 else:
97 logger.info('Nothing to get, everything synced')
113 logger.info('Nothing to get, everything synced')
98 else:
114 else:
@@ -3,7 +3,8 b' import logging'
3 from xml.etree import ElementTree
3 from xml.etree import ElementTree
4
4
5 from boards.abstracts.exceptions import SyncException
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 from boards.models import KeyPair, GlobalId, Signature, Post, Tag
8 from boards.models import KeyPair, GlobalId, Signature, Post, Tag
8 from boards.models.attachment.downloaders import download
9 from boards.models.attachment.downloaders import download
9 from boards.models.signature import TAG_REQUEST, ATTR_TYPE, TYPE_GET, \
10 from boards.models.signature import TAG_REQUEST, ATTR_TYPE, TYPE_GET, \
@@ -17,6 +18,7 b" EXCEPTION_DOWNLOAD = 'File was not downl"
17 EXCEPTION_HASH = 'File hash does not match attachment hash.'
18 EXCEPTION_HASH = 'File hash does not match attachment hash.'
18 EXCEPTION_SIGNATURE = 'Invalid model signature for {}.'
19 EXCEPTION_SIGNATURE = 'Invalid model signature for {}.'
19 EXCEPTION_AUTHOR_SIGNATURE = 'Model {} has no author signature.'
20 EXCEPTION_AUTHOR_SIGNATURE = 'Model {} has no author signature.'
21 EXCEPTION_THREAD = 'No thread exists for post {}'
20 ENCODING_UNICODE = 'unicode'
22 ENCODING_UNICODE = 'unicode'
21
23
22 TAG_MODEL = 'model'
24 TAG_MODEL = 'model'
@@ -193,7 +195,7 b' class SyncManager:'
193 version = int(tag_content.find(TAG_VERSION).text)
195 version = int(tag_content.find(TAG_VERSION).text)
194 is_old = exists and global_id.post.version < version
196 is_old = exists and global_id.post.version < version
195 if exists and not is_old:
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 else:
199 else:
198 global_id.content = content_str
200 global_id.content = content_str
199 global_id.save()
201 global_id.save()
@@ -218,7 +220,7 b' class SyncManager:'
218 if exists:
220 if exists:
219 opening_post = Post.objects.get(global_id=op_global_id)
221 opening_post = Post.objects.get(global_id=op_global_id)
220 else:
222 else:
221 logger.debug('No thread exists for post {}'.format(global_id))
223 raise Exception(EXCEPTION_THREAD.format(global_id))
222 else:
224 else:
223 opening_post = None
225 opening_post = None
224 tag_tags = tag_content.find(TAG_TAGS)
226 tag_tags = tag_content.find(TAG_TAGS)
@@ -339,7 +341,51 b' class SyncManager:'
339 attachment_tag.set(ATTR_ID_TYPE, ID_TYPE_URL)
341 attachment_tag.set(ATTR_ID_TYPE, ID_TYPE_URL)
340 attachment_tag.text = attachment.url
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 attachment_ref = et.SubElement(tag_refs, TAG_ATTACHMENT_REF)
345 attachment_ref = et.SubElement(tag_refs, TAG_ATTACHMENT_REF)
344 attachment_ref.set(ATTR_REF, attachment.hash)
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')
@@ -212,3 +212,42 b' class SyncTest(TestCase):'
212 in response_thread,
212 in response_thread,
213 'Wrong response generated for the LIST request for posts of '
213 'Wrong response generated for the LIST request for posts of '
214 'non-existing thread.')
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
@@ -117,7 +117,7 b' def get_file_hash(file) -> str:'
117
117
118 def validate_file_size(size: int):
118 def validate_file_size(size: int):
119 max_size = boards.settings.get_int('Forms', 'MaxFileSize')
119 max_size = boards.settings.get_int('Forms', 'MaxFileSize')
120 if size > max_size:
120 if max_size > 0 and size > max_size:
121 raise forms.ValidationError(
121 raise forms.ValidationError(
122 _('File must be less than %s but is %s.')
122 _('File must be less than %s but is %s.')
123 % (filesizeformat(max_size), filesizeformat(size)))
123 % (filesizeformat(max_size), filesizeformat(size)))
@@ -29,10 +29,14 b' class LandingView(BaseBoardView):'
29 Tag.objects.filter(required=True))
29 Tag.objects.filter(required=True))
30
30
31 today = datetime.now() - timedelta(1)
31 today = datetime.now() - timedelta(1)
32 max_landing_threads = settings.get_int('View', 'MaxFavoriteThreads')
33 ops = Post.objects.filter(thread__replies__pub_time__gt=today, opening=True, thread__status=STATUS_ACTIVE)\
32 ops = Post.objects.filter(thread__replies__pub_time__gt=today, opening=True, thread__status=STATUS_ACTIVE)\
34 .annotate(today_post_count=Count('thread__replies'))\
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 params[PARAM_LATEST_THREADS] = ops
40 params[PARAM_LATEST_THREADS] = ops
37
41
38 params[PARAM_IMAGES] = Attachment.objects.get_random_images(
42 params[PARAM_IMAGES] = Attachment.objects.get_random_images(
@@ -1,5 +1,6 b''
1 from django.shortcuts import render
1 from django.shortcuts import render
2 from django.views.generic import View
2 from django.views.generic import View
3 from django.db.models import Q
3
4
4 from boards.abstracts.paginator import get_paginator
5 from boards.abstracts.paginator import get_paginator
5 from boards.forms import SearchForm, PlainErrorList
6 from boards.forms import SearchForm, PlainErrorList
@@ -32,8 +33,8 b' class BoardSearchView(View):'
32 if form.is_valid():
33 if form.is_valid():
33 query = form.cleaned_data[FORM_QUERY]
34 query = form.cleaned_data[FORM_QUERY]
34 if len(query) >= MIN_QUERY_LENGTH:
35 if len(query) >= MIN_QUERY_LENGTH:
35 results = Post.objects.filter(text__icontains=query)\
36 results = Post.objects.filter(Q(text__icontains=query) |
36 .order_by('-id')
37 Q(title__icontains=query)).order_by('-id')
37 paginator = get_paginator(results, RESULTS_PER_PAGE)
38 paginator = get_paginator(results, RESULTS_PER_PAGE)
38
39
39 page = int(request.GET.get(REQUEST_PAGE, '1'))
40 page = int(request.GET.get(REQUEST_PAGE, '1'))
@@ -4,7 +4,9 b' import xml.etree.ElementTree as et'
4
4
5 from django.http import HttpResponse, Http404
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 from boards.models import GlobalId, Post
10 from boards.models import GlobalId, Post
9 from boards.models.post.sync import SyncManager
11 from boards.models.post.sync import SyncManager
10
12
@@ -14,6 +16,8 b" logger = logging.getLogger('boards.sync'"
14
16
15 FILTERS = {
17 FILTERS = {
16 TAG_THREAD: ThreadFilter,
18 TAG_THREAD: ThreadFilter,
19 TAG_TAGS: TagsFilter,
20 TAG_TIME_FROM: TimestampFromFilter,
17 }
21 }
18
22
19
23
@@ -30,10 +34,10 b' def response_list(request):'
30
34
31 for tag_filter in model_tag:
35 for tag_filter in model_tag:
32 filter_name = tag_filter.tag
36 filter_name = tag_filter.tag
33 model_filter = FILTERS.get(filter_name)(tag_filter)
37 model_filter = FILTERS.get(filter_name)
34 if not model_filter:
38 if not model_filter:
35 logger.warning('Unavailable filter: {}'.format(filter_name))
39 logger.warning('Unavailable filter: {}'.format(filter_name))
36 filters.append(model_filter)
40 filters.append(model_filter(tag_filter))
37
41
38 response_xml = SyncManager.generate_response_list(filters)
42 response_xml = SyncManager.generate_response_list(filters)
39
43
General Comments 0
You need to be logged in to leave comments. Login now