Show More
@@ -51,3 +51,4 b' 19785af352684884f94566785ba1b13b3ddc5216' | |||
|
51 | 51 | 757b4ada4ca121db4296b13ec0df491645db8fd0 3.5.0 |
|
52 | 52 | 3da1a2d02072eec5419956265dcd8c7f47155c12 4.0.0 |
|
53 | 53 | da8f0f9d5099ee8b22aa317b91beb06543011f1d 4.1.0 |
|
54 | 9619ecc0f79b705c94b3ac873896809eaf7779a6 4.1.1 |
@@ -1,8 +1,11 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: |
@@ -29,3 +32,35 b' class ThreadFilter(PostFilter):' | |||
|
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,5 +1,5 b'' | |||
|
1 | 1 | [Version] |
|
2 |
Version = 4.1. |
|
|
2 | Version = 4.1.1 2017 | |
|
3 | 3 | SiteName = Neboard DEV |
|
4 | 4 | |
|
5 | 5 | [Cache] |
@@ -26,6 +26,10 b' class Command(BaseCommand):' | |||
|
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') |
@@ -57,9 +61,19 b' class Command(BaseCommand):' | |||
|
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) |
@@ -93,6 +107,8 b' class Command(BaseCommand):' | |||
|
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: |
@@ -3,7 +3,8 b' 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, \ |
@@ -17,6 +18,7 b" EXCEPTION_DOWNLOAD = 'File was not downl" | |||
|
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' |
@@ -193,7 +195,7 b' class SyncManager:' | |||
|
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 |
|
|
|
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() |
@@ -218,7 +220,7 b' class SyncManager:' | |||
|
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) |
@@ -339,7 +341,51 b' class SyncManager:' | |||
|
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') |
@@ -212,3 +212,42 b' class SyncTest(TestCase):' | |||
|
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 |
@@ -117,7 +117,7 b' def get_file_hash(file) -> str:' | |||
|
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))) |
@@ -29,10 +29,14 b' class LandingView(BaseBoardView):' | |||
|
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') |
|
|
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( |
@@ -1,5 +1,6 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 |
@@ -32,8 +33,8 b' class BoardSearchView(View):' | |||
|
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')) |
@@ -4,7 +4,9 b' 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, T |
|
|
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 | |
@@ -14,6 +16,8 b" logger = logging.getLogger('boards.sync'" | |||
|
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 | |
@@ -30,10 +34,10 b' def response_list(request):' | |||
|
30 | 34 | |
|
31 | 35 | for tag_filter in model_tag: |
|
32 | 36 | filter_name = tag_filter.tag |
|
33 |
model_filter = FILTERS.get(filter_name) |
|
|
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 |
General Comments 0
You need to be logged in to leave comments.
Login now