Show More
@@ -56,9 +56,9 b' class ThreadAdmin(admin.ModelAdmin):' | |||||
56 | def op(self, obj: Thread): |
|
56 | def op(self, obj: Thread): | |
57 | return obj.get_opening_post_id() |
|
57 | return obj.get_opening_post_id() | |
58 |
|
58 | |||
59 |
list_display = ('id', 'op', 'title', 'reply_count', ' |
|
59 | list_display = ('id', 'op', 'title', 'reply_count', 'status', 'ip', | |
60 | 'display_tags') |
|
60 | 'display_tags') | |
61 |
list_filter = ('bump_time', ' |
|
61 | list_filter = ('bump_time', 'status') | |
62 | search_fields = ('id', 'title') |
|
62 | search_fields = ('id', 'title') | |
63 | filter_horizontal = ('tags',) |
|
63 | filter_horizontal = ('tags',) | |
64 |
|
64 |
@@ -238,7 +238,7 b' class PostForm(NeboardForm):' | |||||
238 | for thread_id in threads_id_list: |
|
238 | for thread_id in threads_id_list: | |
239 | try: |
|
239 | try: | |
240 | thread = Post.objects.get(id=int(thread_id)) |
|
240 | thread = Post.objects.get(id=int(thread_id)) | |
241 | if not thread.is_opening() or thread.get_thread().archived: |
|
241 | if not thread.is_opening() or thread.get_thread().is_archived(): | |
242 | raise ObjectDoesNotExist() |
|
242 | raise ObjectDoesNotExist() | |
243 | threads.append(thread) |
|
243 | threads.append(thread) | |
244 | except (ObjectDoesNotExist, ValueError): |
|
244 | except (ObjectDoesNotExist, ValueError): |
@@ -27,8 +27,8 b' class PostImageManager(models.Manager):' | |||||
27 |
|
27 | |||
28 | return post_image |
|
28 | return post_image | |
29 |
|
29 | |||
30 |
def get_random_images(self, count, |
|
30 | def get_random_images(self, count, tags=None): | |
31 | images = self.filter(post_images__thread__archived=include_archived) |
|
31 | images = self | |
32 | if tags is not None: |
|
32 | if tags is not None: | |
33 | images = images.filter(post_images__threads__tags__in=tags) |
|
33 | images = images.filter(post_images__threads__tags__in=tags) | |
34 | return images.order_by('?')[:count] |
|
34 | return images.order_by('?')[:count] |
@@ -178,7 +178,7 b' class Post(models.Model, Viewable):' | |||||
178 | thread = self.get_thread() |
|
178 | thread = self.get_thread() | |
179 |
|
179 | |||
180 | css_classes = [CSS_CLS_POST] |
|
180 | css_classes = [CSS_CLS_POST] | |
181 | if thread.archived: |
|
181 | if thread.is_archived(): | |
182 | css_classes.append(CSS_CLS_ARCHIVE_POST) |
|
182 | css_classes.append(CSS_CLS_ARCHIVE_POST) | |
183 | elif not thread.can_bump(): |
|
183 | elif not thread.can_bump(): | |
184 | css_classes.append(CSS_CLS_DEAD_POST) |
|
184 | css_classes.append(CSS_CLS_DEAD_POST) | |
@@ -283,7 +283,7 b' class Post(models.Model, Viewable):' | |||||
283 | for thread in self.get_threads().all(): |
|
283 | for thread in self.get_threads().all(): | |
284 | thread.last_edit_time = self.last_edit_time |
|
284 | thread.last_edit_time = self.last_edit_time | |
285 |
|
285 | |||
286 |
thread.save(update_fields=['last_edit_time', ' |
|
286 | thread.save(update_fields=['last_edit_time', 'status']) | |
287 |
|
287 | |||
288 | super().save(force_insert, force_update, using, update_fields) |
|
288 | super().save(force_insert, force_update, using, update_fields) | |
289 |
|
289 | |||
@@ -335,7 +335,7 b' class Post(models.Model, Viewable):' | |||||
335 | thread.update_bump_status() |
|
335 | thread.update_bump_status() | |
336 |
|
336 | |||
337 | thread.last_edit_time = self.last_edit_time |
|
337 | thread.last_edit_time = self.last_edit_time | |
338 |
thread.save(update_fields=['last_edit_time', ' |
|
338 | thread.save(update_fields=['last_edit_time', 'status']) | |
339 | self.threads.add(opening_post.get_thread()) |
|
339 | self.threads.add(opening_post.get_thread()) | |
340 |
|
340 | |||
341 | def get_tripcode(self): |
|
341 | def get_tripcode(self): |
@@ -5,6 +5,7 b' from django.db.models import Count' | |||||
5 | from django.core.urlresolvers import reverse |
|
5 | from django.core.urlresolvers import reverse | |
6 |
|
6 | |||
7 | from boards.models.base import Viewable |
|
7 | from boards.models.base import Viewable | |
|
8 | from boards.models.thread import STATUS_ACTIVE, STATUS_BUMPLIMIT, STATUS_ARCHIVE | |||
8 | from boards.utils import cached_result |
|
9 | from boards.utils import cached_result | |
9 | import boards |
|
10 | import boards | |
10 |
|
11 | |||
@@ -61,22 +62,20 b' class Tag(models.Model, Viewable):' | |||||
61 |
|
62 | |||
62 | return self.get_thread_count() == 0 |
|
63 | return self.get_thread_count() == 0 | |
63 |
|
64 | |||
64 |
def get_thread_count(self, |
|
65 | def get_thread_count(self, status=None) -> int: | |
65 | threads = self.get_threads() |
|
66 | threads = self.get_threads() | |
66 |
if |
|
67 | if status is not None: | |
67 |
threads = threads.filter( |
|
68 | threads = threads.filter(status=status) | |
68 | if bumpable is not None: |
|
|||
69 | threads = threads.filter(bumpable=bumpable) |
|
|||
70 | return threads.count() |
|
69 | return threads.count() | |
71 |
|
70 | |||
72 | def get_active_thread_count(self) -> int: |
|
71 | def get_active_thread_count(self) -> int: | |
73 |
return self.get_thread_count( |
|
72 | return self.get_thread_count(status=STATUS_ACTIVE) | |
74 |
|
73 | |||
75 | def get_bumplimit_thread_count(self) -> int: |
|
74 | def get_bumplimit_thread_count(self) -> int: | |
76 |
return self.get_thread_count( |
|
75 | return self.get_thread_count(status=STATUS_BUMPLIMIT) | |
77 |
|
76 | |||
78 | def get_archived_thread_count(self) -> int: |
|
77 | def get_archived_thread_count(self) -> int: | |
79 |
return self.get_thread_count( |
|
78 | return self.get_thread_count(status=STATUS_ARCHIVE) | |
80 |
|
79 | |||
81 | def get_absolute_url(self): |
|
80 | def get_absolute_url(self): | |
82 | return reverse('tag', kwargs={'tag_name': self.name}) |
|
81 | return reverse('tag', kwargs={'tag_name': self.name}) | |
@@ -106,11 +105,11 b' class Tag(models.Model, Viewable):' | |||||
106 | def get_description(self): |
|
105 | def get_description(self): | |
107 | return self.description |
|
106 | return self.description | |
108 |
|
107 | |||
109 |
def get_random_image_post(self, |
|
108 | def get_random_image_post(self, status=False): | |
110 | posts = boards.models.Post.objects.annotate(images_count=Count( |
|
109 | posts = boards.models.Post.objects.annotate(images_count=Count( | |
111 | 'images')).filter(images_count__gt=0, threads__tags__in=[self]) |
|
110 | 'images')).filter(images_count__gt=0, threads__tags__in=[self]) | |
112 |
if |
|
111 | if status is not None: | |
113 |
posts = posts.filter(thread__ |
|
112 | posts = posts.filter(thread__status=status) | |
114 | return posts.order_by('?').first() |
|
113 | return posts.order_by('?').first() | |
115 |
|
114 | |||
116 | def get_first_letter(self): |
|
115 | def get_first_letter(self): |
@@ -5,6 +5,10 b' from django.db.models import Count, Sum,' | |||||
5 | from django.utils import timezone |
|
5 | from django.utils import timezone | |
6 | from django.db import models |
|
6 | from django.db import models | |
7 |
|
7 | |||
|
8 | STATUS_ACTIVE = 'active' | |||
|
9 | STATUS_BUMPLIMIT = 'bumplimit' | |||
|
10 | STATUS_ARCHIVE = 'archived' | |||
|
11 | ||||
8 | from boards import settings |
|
12 | from boards import settings | |
9 | import boards |
|
13 | import boards | |
10 | from boards.utils import cached_result, datetime_to_epoch |
|
14 | from boards.utils import cached_result, datetime_to_epoch | |
@@ -33,7 +37,7 b' class ThreadManager(models.Manager):' | |||||
33 | archive or delete the old ones. |
|
37 | archive or delete the old ones. | |
34 | """ |
|
38 | """ | |
35 |
|
39 | |||
36 |
threads = Thread.objects. |
|
40 | threads = Thread.objects.exclude(status=STATUS_ARCHIVE).order_by('-bump_time') | |
37 | thread_count = threads.count() |
|
41 | thread_count = threads.count() | |
38 |
|
42 | |||
39 | max_thread_count = settings.get_int('Messages', 'MaxThreadCount') |
|
43 | max_thread_count = settings.get_int('Messages', 'MaxThreadCount') | |
@@ -50,11 +54,10 b' class ThreadManager(models.Manager):' | |||||
50 | logger.info('Processed %d old threads' % num_threads_to_delete) |
|
54 | logger.info('Processed %d old threads' % num_threads_to_delete) | |
51 |
|
55 | |||
52 | def _archive_thread(self, thread): |
|
56 | def _archive_thread(self, thread): | |
53 | thread.archived = True |
|
57 | thread.status = STATUS_ARCHIVE | |
54 | thread.bumpable = False |
|
|||
55 | thread.last_edit_time = timezone.now() |
|
58 | thread.last_edit_time = timezone.now() | |
56 | thread.update_posts_time() |
|
59 | thread.update_posts_time() | |
57 |
thread.save(update_fields=[' |
|
60 | thread.save(update_fields=['last_edit_time', 'status']) | |
58 |
|
61 | |||
59 | def get_new_posts(self, datas): |
|
62 | def get_new_posts(self, datas): | |
60 | query = None |
|
63 | query = None | |
@@ -90,9 +93,8 b' class Thread(models.Model):' | |||||
90 | tags = models.ManyToManyField('Tag', related_name='thread_tags') |
|
93 | tags = models.ManyToManyField('Tag', related_name='thread_tags') | |
91 | bump_time = models.DateTimeField(db_index=True) |
|
94 | bump_time = models.DateTimeField(db_index=True) | |
92 | last_edit_time = models.DateTimeField() |
|
95 | last_edit_time = models.DateTimeField() | |
93 | archived = models.BooleanField(default=False) |
|
|||
94 | bumpable = models.BooleanField(default=True) |
|
|||
95 | max_posts = models.IntegerField(default=get_thread_max_posts) |
|
96 | max_posts = models.IntegerField(default=get_thread_max_posts) | |
|
97 | status = models.CharField(max_length=50, default=STATUS_ACTIVE) | |||
96 |
|
98 | |||
97 | def get_tags(self) -> QuerySet: |
|
99 | def get_tags(self) -> QuerySet: | |
98 | """ |
|
100 | """ | |
@@ -118,7 +120,7 b' class Thread(models.Model):' | |||||
118 |
|
120 | |||
119 | def update_bump_status(self, exclude_posts=None): |
|
121 | def update_bump_status(self, exclude_posts=None): | |
120 | if self.has_post_limit() and self.get_reply_count() >= self.max_posts: |
|
122 | if self.has_post_limit() and self.get_reply_count() >= self.max_posts: | |
121 | self.bumpable = False |
|
123 | self.status = STATUS_BUMPLIMIT | |
122 | self.update_posts_time(exclude_posts=exclude_posts) |
|
124 | self.update_posts_time(exclude_posts=exclude_posts) | |
123 |
|
125 | |||
124 | def _get_cache_key(self): |
|
126 | def _get_cache_key(self): | |
@@ -138,7 +140,7 b' class Thread(models.Model):' | |||||
138 | Checks if the thread can be bumped by replying to it. |
|
140 | Checks if the thread can be bumped by replying to it. | |
139 | """ |
|
141 | """ | |
140 |
|
142 | |||
141 | return self.bumpable and not self.is_archived() |
|
143 | return self.get_status() == STATUS_ACTIVE | |
142 |
|
144 | |||
143 | def get_last_replies(self) -> QuerySet: |
|
145 | def get_last_replies(self) -> QuerySet: | |
144 | """ |
|
146 | """ | |
@@ -255,4 +257,7 b' class Thread(models.Model):' | |||||
255 | return self.get_replies().filter(id__gt=post_id) |
|
257 | return self.get_replies().filter(id__gt=post_id) | |
256 |
|
258 | |||
257 | def is_archived(self): |
|
259 | def is_archived(self): | |
258 | return self.archived |
|
260 | return self.get_status() == STATUS_ARCHIVE | |
|
261 | ||||
|
262 | def get_status(self): | |||
|
263 | return self.status |
@@ -3,6 +3,7 b' from django.core.urlresolvers import rev' | |||||
3 | from django.shortcuts import get_object_or_404 |
|
3 | from django.shortcuts import get_object_or_404 | |
4 | from boards.models import Post, Tag, Thread |
|
4 | from boards.models import Post, Tag, Thread | |
5 | from boards import settings |
|
5 | from boards import settings | |
|
6 | from boards.models.thread import STATUS_ARCHIVE | |||
6 |
|
7 | |||
7 | __author__ = 'nekorin' |
|
8 | __author__ = 'nekorin' | |
8 |
|
9 | |||
@@ -18,7 +19,7 b' class AllThreadsFeed(Feed):' | |||||
18 | description_template = 'boards/rss/post.html' |
|
19 | description_template = 'boards/rss/post.html' | |
19 |
|
20 | |||
20 | def items(self): |
|
21 | def items(self): | |
21 |
return Thread.objects. |
|
22 | return Thread.objects.exclude(status=STATUS_ARCHIVE).order_by('-id')[:MAX_ITEMS] | |
22 |
|
23 | |||
23 | def item_title(self, item): |
|
24 | def item_title(self, item): | |
24 | return item.get_opening_post().title |
|
25 | return item.get_opening_post().title | |
@@ -36,7 +37,7 b' class TagThreadsFeed(Feed):' | |||||
36 | description_template = 'boards/rss/post.html' |
|
37 | description_template = 'boards/rss/post.html' | |
37 |
|
38 | |||
38 | def items(self, obj): |
|
39 | def items(self, obj): | |
39 |
return obj.get_threads(). |
|
40 | return obj.get_threads().exclude(status=STATUS_ARCHIVE).order_by('-id')[:MAX_ITEMS] | |
40 |
|
41 | |||
41 | def get_object(self, request, tag_name): |
|
42 | def get_object(self, request, tag_name): | |
42 | return get_object_or_404(Tag, name=tag_name) |
|
43 | return get_object_or_404(Tag, name=tag_name) |
@@ -21,14 +21,14 b'' | |||||
21 | and this is an opening post (thread death time) or a post for popup |
|
21 | and this is an opening post (thread death time) or a post for popup | |
22 | (we don't see OP here so we show the death time in the post itself). |
|
22 | (we don't see OP here so we show the death time in the post itself). | |
23 | {% endcomment %} |
|
23 | {% endcomment %} | |
24 | {% if thread.archived %} |
|
24 | {% if thread.is_archived %} | |
25 | {% if is_opening %} |
|
25 | {% if is_opening %} | |
26 | — <time datetime="{{ thread.bump_time|date:'c' }}">{{ thread.bump_time }}</time> |
|
26 | — <time datetime="{{ thread.bump_time|date:'c' }}">{{ thread.bump_time }}</time> | |
27 | {% endif %} |
|
27 | {% endif %} | |
28 | {% endif %} |
|
28 | {% endif %} | |
29 | {% if is_opening %} |
|
29 | {% if is_opening %} | |
30 | {% if need_open_link %} |
|
30 | {% if need_open_link %} | |
31 | {% if thread.archived %} |
|
31 | {% if thread.is_archived %} | |
32 | <a class="link" href="{% url 'thread' post.id %}">{% trans "Open" %}</a> |
|
32 | <a class="link" href="{% url 'thread' post.id %}">{% trans "Open" %}</a> | |
33 | {% else %} |
|
33 | {% else %} | |
34 | <a class="link" href="{% url 'thread' post.id %}#form">{% trans "Reply" %}</a> |
|
34 | <a class="link" href="{% url 'thread' post.id %}#form">{% trans "Reply" %}</a> | |
@@ -41,7 +41,7 b'' | |||||
41 | {% endwith %} |
|
41 | {% endwith %} | |
42 | {% endif %} |
|
42 | {% endif %} | |
43 | {% endif %} |
|
43 | {% endif %} | |
44 | {% if reply_link and not thread.archived %} |
|
44 | {% if reply_link and not thread.is_archived %} | |
45 | <a href="#form" onclick="addQuickReply('{{ post.id }}'); return false;">{% trans 'Reply' %}</a> |
|
45 | <a href="#form" onclick="addQuickReply('{{ post.id }}'); return false;">{% trans 'Reply' %}</a> | |
46 | {% endif %} |
|
46 | {% endif %} | |
47 |
|
47 |
@@ -38,7 +38,7 b'' | |||||
38 | {% endfor %} |
|
38 | {% endfor %} | |
39 | </div> |
|
39 | </div> | |
40 |
|
40 | |||
41 | {% if not thread.archived %} |
|
41 | {% if not thread.is_archived %} | |
42 | <div class="post-form-w"> |
|
42 | <div class="post-form-w"> | |
43 | <script src="{% static 'js/panel.js' %}"></script> |
|
43 | <script src="{% static 'js/panel.js' %}"></script> | |
44 | <div class="form-title">{% trans "Reply to thread" %} #{{ opening_post.id }}<span class="reply-to-message"> {% trans "to message " %} #<span id="reply-to-message-id"></span></span></div> |
|
44 | <div class="form-title">{% trans "Reply to thread" %} #{{ opening_post.id }}<span class="reply-to-message"> {% trans "to message " %} #<span id="reply-to-message-id"></span></span></div> |
@@ -31,6 +31,7 b' class ApiTest(TestCase):' | |||||
31 | req = MockRequest() |
|
31 | req = MockRequest() | |
32 | req.POST['thread'] = opening_post.id |
|
32 | req.POST['thread'] = opening_post.id | |
33 | req.POST['uids'] = ' '.join(uids) |
|
33 | req.POST['uids'] = ' '.join(uids) | |
|
34 | req.user = None | |||
34 | # Check the timestamp before post was added |
|
35 | # Check the timestamp before post was added | |
35 | response = api.api_get_threaddiff(req) |
|
36 | response = api.api_get_threaddiff(req) | |
36 | diff = simplejson.loads(response.content) |
|
37 | diff = simplejson.loads(response.content) |
@@ -3,6 +3,7 b' from django.test import TestCase' | |||||
3 |
|
3 | |||
4 | from boards import settings |
|
4 | from boards import settings | |
5 | from boards.models import Tag, Post, Thread |
|
5 | from boards.models import Tag, Post, Thread | |
|
6 | from boards.models.thread import STATUS_ARCHIVE | |||
6 |
|
7 | |||
7 |
|
8 | |||
8 | class PostTests(TestCase): |
|
9 | class PostTests(TestCase): | |
@@ -96,7 +97,7 b' class PostTests(TestCase):' | |||||
96 | self._create_post() |
|
97 | self._create_post() | |
97 |
|
98 | |||
98 | self.assertEqual(settings.get_int('Messages', 'MaxThreadCount'), |
|
99 | self.assertEqual(settings.get_int('Messages', 'MaxThreadCount'), | |
99 |
len(Thread.objects. |
|
100 | len(Thread.objects.exclude(status=STATUS_ARCHIVE))) | |
100 |
|
101 | |||
101 | def test_pages(self): |
|
102 | def test_pages(self): | |
102 | """Test that the thread list is properly split into pages""" |
|
103 | """Test that the thread list is properly split into pages""" | |
@@ -104,9 +105,9 b' class PostTests(TestCase):' | |||||
104 | for i in range(settings.get_int('Messages', 'MaxThreadCount')): |
|
105 | for i in range(settings.get_int('Messages', 'MaxThreadCount')): | |
105 | self._create_post() |
|
106 | self._create_post() | |
106 |
|
107 | |||
107 |
all_threads = Thread.objects. |
|
108 | all_threads = Thread.objects.exclude(status=STATUS_ARCHIVE) | |
108 |
|
109 | |||
109 |
paginator = Paginator(Thread.objects. |
|
110 | paginator = Paginator(Thread.objects.exclude(status=STATUS_ARCHIVE), | |
110 | settings.get_int('View', 'ThreadsPerPage')) |
|
111 | settings.get_int('View', 'ThreadsPerPage')) | |
111 | posts_in_second_page = paginator.page(2).object_list |
|
112 | posts_in_second_page = paginator.page(2).object_list | |
112 | first_post = posts_in_second_page[0] |
|
113 | first_post = posts_in_second_page[0] |
@@ -12,6 +12,7 b' from boards.abstracts.settingsmanager im' | |||||
12 |
|
12 | |||
13 | from boards.forms import PostForm, PlainErrorList |
|
13 | from boards.forms import PostForm, PlainErrorList | |
14 | from boards.models import Post, Thread, Tag |
|
14 | from boards.models import Post, Thread, Tag | |
|
15 | from boards.models.thread import STATUS_ARCHIVE | |||
15 | from boards.utils import datetime_to_epoch |
|
16 | from boards.utils import datetime_to_epoch | |
16 | from boards.views.thread import ThreadView |
|
17 | from boards.views.thread import ThreadView | |
17 | from boards.models.user import Notification |
|
18 | from boards.models.user import Notification | |
@@ -136,9 +137,9 b' def api_get_threads(request, count):' | |||||
136 | tag_name = request.GET[PARAMETER_TAG] |
|
137 | tag_name = request.GET[PARAMETER_TAG] | |
137 | if tag_name is not None: |
|
138 | if tag_name is not None: | |
138 | tag = get_object_or_404(Tag, name=tag_name) |
|
139 | tag = get_object_or_404(Tag, name=tag_name) | |
139 |
threads = tag.get_threads(). |
|
140 | threads = tag.get_threads().exclude(status=STATUS_ARCHIVE) | |
140 | else: |
|
141 | else: | |
141 |
threads = Thread.objects. |
|
142 | threads = Thread.objects.exclude(status=STATUS_ARCHIVE) | |
142 |
|
143 | |||
143 | if PARAMETER_OFFSET in request.GET: |
|
144 | if PARAMETER_OFFSET in request.GET: | |
144 | offset = request.GET[PARAMETER_OFFSET] |
|
145 | offset = request.GET[PARAMETER_OFFSET] | |
@@ -155,8 +156,7 b' def api_get_threads(request, count):' | |||||
155 |
|
156 | |||
156 | # TODO Add tags, replies and images count |
|
157 | # TODO Add tags, replies and images count | |
157 | post_data = opening_post.get_post_data(include_last_update=True) |
|
158 | post_data = opening_post.get_post_data(include_last_update=True) | |
158 |
post_data[' |
|
159 | post_data['status'] = thread.get_status() | |
159 | post_data['archived'] = thread.archived |
|
|||
160 |
|
160 | |||
161 | opening_posts.append(post_data) |
|
161 | opening_posts.append(post_data) | |
162 |
|
162 |
@@ -97,7 +97,7 b' class ThreadView(BaseBoardView, PostMixi' | |||||
97 |
|
97 | |||
98 | return redirect('thread', post_id) # FIXME Different for different modes |
|
98 | return redirect('thread', post_id) # FIXME Different for different modes | |
99 |
|
99 | |||
100 | if not opening_post.get_thread().archived: |
|
100 | if not opening_post.get_thread().is_archived(): | |
101 | form = PostForm(request.POST, request.FILES, |
|
101 | form = PostForm(request.POST, request.FILES, | |
102 | error_class=PlainErrorList) |
|
102 | error_class=PlainErrorList) | |
103 | form.session = request.session |
|
103 | form.session = request.session |
General Comments 0
You need to be logged in to leave comments.
Login now