From 3f72e2827feb9af5808d080441ea2ff86ecde1a6 Mon Sep 17 00:00:00 2001 From: Bo Peng Date: Sat, 23 Apr 2022 09:29:20 -0500 Subject: [PATCH 01/12] Default approval status --- docs/settings.rst | 13 +++++++++++ machina/apps/forum/abstract_models.py | 2 +- .../forum_conversation/abstract_models.py | 23 +++++++++++-------- machina/apps/forum_conversation/managers.py | 3 ++- machina/apps/forum_conversation/views.py | 8 ++++++- machina/apps/forum_feeds/feeds.py | 2 +- machina/apps/forum_member/views.py | 2 +- machina/apps/forum_moderation/views.py | 2 +- machina/apps/forum_tracking/handler.py | 2 +- machina/conf/settings.py | 2 +- tests/unit/apps/forum/test_models.py | 6 ++--- .../apps/forum_conversation/test_models.py | 2 +- 12 files changed, 46 insertions(+), 21 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index d793bf24a..4fa476f52 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -290,6 +290,19 @@ Default: ``15`` The number of posts displayed inside one page of a forum member's posts list. +``MACHINA_DEFAULT_APPROVAL_STATUS`` +----------------------------------------- + +Default: ``True`` + +The default status of `approved` field of `Post`. If set to `True` (the default), the posts will be public +by default until it is set to `False` during moderation. `Post.delete()` will remove posts regardless of +their approval status. If set to `None`, the topics and posts will have three status `True` (approved), +`False` (disapproved), and `None` (pending) and deletion of posts becomes soft. More specifically, deletion +of a post with `approved is not False` will first set its status to `False`. The disapproved posts will only +be viewable by their posters, before they are revised or perminantly deleted. + + Permission ********** diff --git a/machina/apps/forum/abstract_models.py b/machina/apps/forum/abstract_models.py index d2d642383..fdf2a6bb2 100644 --- a/machina/apps/forum/abstract_models.py +++ b/machina/apps/forum/abstract_models.py @@ -174,7 +174,7 @@ def save(self, *args, **kwargs): def update_trackers(self): """ Updates the denormalized trackers associated with the forum instance. """ - direct_approved_topics = self.topics.filter(approved=True).order_by('-last_post_on') + direct_approved_topics = self.topics.exclude(approved=False).order_by('-last_post_on') # Compute the direct topics count and the direct posts count. self.direct_topics_count = direct_approved_topics.count() diff --git a/machina/apps/forum_conversation/abstract_models.py b/machina/apps/forum_conversation/abstract_models.py index 023033b04..271cab0c5 100644 --- a/machina/apps/forum_conversation/abstract_models.py +++ b/machina/apps/forum_conversation/abstract_models.py @@ -204,9 +204,9 @@ def delete(self, using=None): def update_trackers(self): """ Updates the denormalized trackers associated with the topic instance. """ - self.posts_count = self.posts.filter(approved=True).count() + self.posts_count = self.posts.exclude(approved=False).count() first_post = self.posts.all().order_by('created').first() - last_post = self.posts.filter(approved=True).order_by('-created').first() + last_post = self.posts.exclude(approved=False).order_by('-created').first() self.first_post = first_post self.last_post = last_post self.last_post_on = last_post.created if last_post else None @@ -246,7 +246,7 @@ class AbstractPost(DatedModel): username = models.CharField(max_length=155, blank=True, null=True, verbose_name=_('Username')) # A post can be approved before publishing ; defaults to True - approved = models.BooleanField(default=True, db_index=True, verbose_name=_('Approved')) + approved = models.BooleanField(default=machina_settings.DEFAULT_APPROVAL_STATUS, db_index=True, verbose_name=_('Approved')) # The user can choose if they want to display their signature with the content of the post enable_signature = models.BooleanField( @@ -338,10 +338,15 @@ def save(self, *args, **kwargs): def delete(self, using=None): """ Deletes the post instance. """ - if self.is_alone: - # The default way of operating is to trigger the deletion of the associated topic - # only if the considered post is the only post embedded in the topic - self.topic.delete() - else: - super(AbstractPost, self).delete(using) + if machina_settings.DEFAULT_APPROVAL_STATUS is None and self.approved is not False: + self.approved = False + self.save(update_fields=['approved']) self.topic.update_trackers() + else: + if self.is_alone: + # The default way of operating is to trigger the deletion of the associated topic + # only if the considered post is the only post embedded in the topic + self.topic.delete() + else: + super(AbstractPost, self).delete(using) + self.topic.update_trackers() diff --git a/machina/apps/forum_conversation/managers.py b/machina/apps/forum_conversation/managers.py index e0c0d32ef..26a5168bb 100644 --- a/machina/apps/forum_conversation/managers.py +++ b/machina/apps/forum_conversation/managers.py @@ -7,11 +7,12 @@ """ from django.db import models +from machina.conf import settings as machina_settings class ApprovedManager(models.Manager): def get_queryset(self): """ Returns all the approved topics or posts. """ qs = super().get_queryset() - qs = qs.filter(approved=True) + qs = qs.filter(approved=machina_settings.DEFAULT_APPROVAL_STATUS) return qs diff --git a/machina/apps/forum_conversation/views.py b/machina/apps/forum_conversation/views.py index 4d44f0227..ffa772bdb 100644 --- a/machina/apps/forum_conversation/views.py +++ b/machina/apps/forum_conversation/views.py @@ -8,6 +8,7 @@ from django.contrib import messages from django.core.exceptions import ObjectDoesNotExist +from django.db.models import Q from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404 from django.urls import reverse @@ -80,11 +81,16 @@ def get_topic(self): def get_queryset(self): """ Returns the list of items for this view. """ + if machina_settings.DEFAULT_APPROVAL_STATUS is None and self.request.user.is_authenticated: + cond = Q(approved=False) & ~Q(poster=self.request.user) + else: + cond = Q(approved=False) + self.topic = self.get_topic() qs = ( self.topic.posts .all() - .exclude(approved=False) + .exclude(cond) .select_related('poster', 'updated_by') .prefetch_related('attachments', 'poster__forum_profile') ) diff --git a/machina/apps/forum_feeds/feeds.py b/machina/apps/forum_feeds/feeds.py index e4c724e5b..20e0414a3 100644 --- a/machina/apps/forum_feeds/feeds.py +++ b/machina/apps/forum_feeds/feeds.py @@ -53,7 +53,7 @@ def get_object(self, request, *args, **kwargs): def items(self): """ Returns the items to include into the feed. """ - return Topic.objects.filter(forum__in=self.forums, approved=True).order_by('-last_post_on') + return Topic.objects.filter(forum__in=self.forums).exclude(approved=False).order_by('-last_post_on') def item_link(self, item): """ Generates a link for a specific item of the feed. """ diff --git a/machina/apps/forum_member/views.py b/machina/apps/forum_member/views.py index ed1624491..7a051c0f8 100644 --- a/machina/apps/forum_member/views.py +++ b/machina/apps/forum_member/views.py @@ -92,7 +92,7 @@ def get_context_data(self, **kwargs): # Computes the number of topics added by the considered member context['topics_count'] = ( - Topic.objects.filter(approved=True, poster=self.object.user).count() + Topic.objects.filter(poster=self.object.user).exclude(approved=False).count() ) # Fetches the recent posts added by the considered user diff --git a/machina/apps/forum_moderation/views.py b/machina/apps/forum_moderation/views.py index fb127b4ce..6300f5f88 100644 --- a/machina/apps/forum_moderation/views.py +++ b/machina/apps/forum_moderation/views.py @@ -372,7 +372,7 @@ def get_context_data(self, **kwargs): # Add the topic review previous_posts = ( topic.posts - .filter(approved=True, created__lte=post.created) + .filter(approved=None, created__lte=post.created) .select_related('poster', 'updated_by') .prefetch_related('attachments', 'poster__forum_profile') .order_by('-created') diff --git a/machina/apps/forum_tracking/handler.py b/machina/apps/forum_tracking/handler.py index c8c5909f6..18e70d343 100644 --- a/machina/apps/forum_tracking/handler.py +++ b/machina/apps/forum_tracking/handler.py @@ -151,7 +151,7 @@ def mark_topic_read(self, topic, user): not unread_topics.exists() and ( forum_track is not None or - forum_topic_tracks.count() == forum.topics.filter(approved=True).count() + forum_topic_tracks.count() == forum.topics.exclude(approved=False).count() ) ): # The topics that are marked as read inside the forum for the given user will be diff --git a/machina/conf/settings.py b/machina/conf/settings.py index d450cc521..fad64a74f 100644 --- a/machina/conf/settings.py +++ b/machina/conf/settings.py @@ -95,7 +95,7 @@ PROFILE_RECENT_POSTS_NUMBER = getattr(settings, 'MACHINA_PROFILE_RECENT_POSTS_NUMBER', 15) PROFILE_POSTS_NUMBER_PER_PAGE = getattr(settings, 'MACHINA_PROFILE_POSTS_NUMBER_PER_PAGE', 15) - +DEFAULT_APPROVAL_STATUS = getattr(settings, 'MACHINA_DEFAULT_APPROVAL_STATUS') # Permission DEFAULT_AUTHENTICATED_USER_FORUM_PERMISSIONS = getattr( diff --git a/tests/unit/apps/forum/test_models.py b/tests/unit/apps/forum/test_models.py index de8a612ab..46a6fdb6f 100644 --- a/tests/unit/apps/forum/test_models.py +++ b/tests/unit/apps/forum/test_models.py @@ -57,15 +57,15 @@ def test_saves_its_numbers_of_posts_and_topics(self): topic = create_topic(forum=self.top_level_forum, poster=self.u1) PostFactory.create(topic=topic, poster=self.u1) PostFactory.create(topic=topic, poster=self.u1) - assert self.top_level_forum.direct_posts_count == topic.posts.filter(approved=True).count() + assert self.top_level_forum.direct_posts_count == topic.posts.exclude(approved=False).count() assert self.top_level_forum.direct_topics_count == self.top_level_forum.topics.count() topic2 = create_topic(forum=self.top_level_forum, poster=self.u1, approved=False) PostFactory.create(topic=topic2, poster=self.u1, approved=False) assert self.top_level_forum.direct_posts_count == \ - topic.posts.filter(approved=True).count() + topic2.posts.filter(approved=True).count() + topic.posts.exclude(approved=False).count() + topic2.posts.exclude(approved=False).count() assert self.top_level_forum.direct_topics_count == \ - self.top_level_forum.topics.filter(approved=True).count() + self.top_level_forum.topics.exclude(approved=False).count() def test_can_indicate_its_appartenance_to_a_forum_type(self): # Run & check diff --git a/tests/unit/apps/forum_conversation/test_models.py b/tests/unit/apps/forum_conversation/test_models.py index b92194f5e..30ca1c5fc 100644 --- a/tests/unit/apps/forum_conversation/test_models.py +++ b/tests/unit/apps/forum_conversation/test_models.py @@ -112,7 +112,7 @@ def test_saves_its_number_of_posts(self): def test_saves_only_its_number_of_approved_posts(self): # Run & check post = PostFactory.create(topic=self.topic, poster=self.u1, approved=False) - initial_count = self.topic.posts.filter(approved=True).count() + initial_count = self.topic.posts.exclude(approved=False).count() assert initial_count == self.topic.posts_count post.delete() assert initial_count == self.topic.posts_count From 2158066eab976bda86bb10a3525a2592242e6954 Mon Sep 17 00:00:00 2001 From: Bo Peng Date: Mon, 24 Oct 2022 21:39:14 -0500 Subject: [PATCH 02/12] Use multiple settings --- docs/settings.rst | 35 +++++++++++++++---- machina/apps/forum/abstract_models.py | 5 ++- .../forum_conversation/abstract_models.py | 26 +++++++++++--- machina/apps/forum_conversation/managers.py | 2 +- machina/apps/forum_conversation/views.py | 12 ++++--- machina/apps/forum_feeds/feeds.py | 6 +++- machina/apps/forum_member/views.py | 12 +++++-- machina/apps/forum_moderation/views.py | 13 +++---- machina/apps/forum_tracking/handler.py | 5 ++- machina/conf/settings.py | 5 ++- tests/unit/apps/forum/test_models.py | 20 ++++++++--- .../apps/forum_conversation/test_models.py | 7 +++- 12 files changed, 113 insertions(+), 35 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index 4fa476f52..10ad1d85d 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -290,17 +290,40 @@ Default: ``15`` The number of posts displayed inside one page of a forum member's posts list. +``MACHINA_TRIPLE_APPROVAL_STATUS`` +----------------------------------------- + +Default: ``False`` + +By default, machina employes a two-state approval status for posts: `True` (approved) or `False` +(disapproved or pending approval). Posts with approval status `False` will not be displayed, and +will be approved or deleted during moderation. + +If this option is set to `True`, posts will have three states `True` (approved), `None` (pending +approval), and `False` (disapproved)s. Posts with approval status `None` will be moderated, +and be assigned to status of `True` or `False`. Posts that are disapproved are not deleted, which +allows them to be revise and re-post. + ``MACHINA_DEFAULT_APPROVAL_STATUS`` ----------------------------------------- +Default: ``True`` if `MACHINA_TRIPLE_APPROVAL_STATUS` is `False`, `None` otherwise + +The default approval state for posts, which can be `True` (default) or `False` if +`MACHINA_TRIPLE_APPROVAL_STATUS` is `False` (default). Otherwise it can be `True`, `None` (default), +or `False`. + +``MACHINA_PENDING_POSTS_AS_APPROVED`` +----------------------------------------- + Default: ``True`` -The default status of `approved` field of `Post`. If set to `True` (the default), the posts will be public -by default until it is set to `False` during moderation. `Post.delete()` will remove posts regardless of -their approval status. If set to `None`, the topics and posts will have three status `True` (approved), -`False` (disapproved), and `None` (pending) and deletion of posts becomes soft. More specifically, deletion -of a post with `approved is not False` will first set its status to `False`. The disapproved posts will only -be viewable by their posters, before they are revised or perminantly deleted. +If pending posts (`approved=None`) will be treated as approved or disapproved when +`MACHINA_TRIPLE_APPROVAL_STATUS` is set to `True`. If this option is set to `True`, pending posts will +be counted towards `posts_count` and be displayed. Otherwise, pending posts will not be displayed until +they are approved. + +This option is only valid when `MACHINA_TRIPLE_APPROVAL_STATUS` is set to `True`. Permission diff --git a/machina/apps/forum/abstract_models.py b/machina/apps/forum/abstract_models.py index fdf2a6bb2..047af97ab 100644 --- a/machina/apps/forum/abstract_models.py +++ b/machina/apps/forum/abstract_models.py @@ -174,7 +174,10 @@ def save(self, *args, **kwargs): def update_trackers(self): """ Updates the denormalized trackers associated with the forum instance. """ - direct_approved_topics = self.topics.exclude(approved=False).order_by('-last_post_on') + if machina_settings.PENDING_POSTS_AS_APPROVED: + direct_approved_topics = self.topics.exclude(approved=False).order_by('-last_post_on') + else: + direct_approved_topics = self.topics.filter(approved=True).order_by('-last_post_on') # Compute the direct topics count and the direct posts count. self.direct_topics_count = direct_approved_topics.count() diff --git a/machina/apps/forum_conversation/abstract_models.py b/machina/apps/forum_conversation/abstract_models.py index 271cab0c5..8752588e8 100644 --- a/machina/apps/forum_conversation/abstract_models.py +++ b/machina/apps/forum_conversation/abstract_models.py @@ -204,9 +204,13 @@ def delete(self, using=None): def update_trackers(self): """ Updates the denormalized trackers associated with the topic instance. """ - self.posts_count = self.posts.exclude(approved=False).count() + if machina_settings.PENDING_POSTS_AS_APPROVED: + self.posts_count = self.posts.exclude(approved=False).count() + last_post = self.posts.exclude(approved=False).order_by('-created').first() + else: + self.posts_count = self.posts.filter(approved=True).count() + last_post = self.posts.filter(approved=True).order_by('-created').first() first_post = self.posts.all().order_by('created').first() - last_post = self.posts.exclude(approved=False).order_by('-created').first() self.first_post = first_post self.last_post = last_post self.last_post_on = last_post.created if last_post else None @@ -246,7 +250,8 @@ class AbstractPost(DatedModel): username = models.CharField(max_length=155, blank=True, null=True, verbose_name=_('Username')) # A post can be approved before publishing ; defaults to True - approved = models.BooleanField(default=machina_settings.DEFAULT_APPROVAL_STATUS, db_index=True, verbose_name=_('Approved')) + approved = models.BooleanField(machina_settings.DEFAULT_APPROVAL_STATUS, + null=True, db_index=True, verbose_name=_('Approved')) # The user can choose if they want to display their signature with the content of the post enable_signature = models.BooleanField( @@ -303,6 +308,19 @@ def position(self): position = self.topic.posts.filter(Q(created__lt=self.created) | Q(id=self.id)).count() return position + def approve(self): + self.approved = True + self.save(update_fields=['approved']) + self.topic.update_trackers() + + def disapprove(self): + if machina_settings.TRIPLE_APPROVAL_STATUS: + self.approved = False + self.save(update_fields=['approved']) + self.topic.update_trackers() + else: + self.delete() + def clean(self): """ Validates the post instance. """ super().clean() @@ -338,7 +356,7 @@ def save(self, *args, **kwargs): def delete(self, using=None): """ Deletes the post instance. """ - if machina_settings.DEFAULT_APPROVAL_STATUS is None and self.approved is not False: + if machina_settings.TRIPLE_APPROVAL_STATUS is None and self.approved is not False: self.approved = False self.save(update_fields=['approved']) self.topic.update_trackers() diff --git a/machina/apps/forum_conversation/managers.py b/machina/apps/forum_conversation/managers.py index 26a5168bb..9d3e6d41c 100644 --- a/machina/apps/forum_conversation/managers.py +++ b/machina/apps/forum_conversation/managers.py @@ -14,5 +14,5 @@ class ApprovedManager(models.Manager): def get_queryset(self): """ Returns all the approved topics or posts. """ qs = super().get_queryset() - qs = qs.filter(approved=machina_settings.DEFAULT_APPROVAL_STATUS) + qs = qs.filter(approved=None if machina_settings.TRIPLE_APPROVAL_STATUS else False) return qs diff --git a/machina/apps/forum_conversation/views.py b/machina/apps/forum_conversation/views.py index 3176e66c3..e71210265 100644 --- a/machina/apps/forum_conversation/views.py +++ b/machina/apps/forum_conversation/views.py @@ -81,16 +81,18 @@ def get_topic(self): def get_queryset(self): """ Returns the list of items for this view. """ - if machina_settings.DEFAULT_APPROVAL_STATUS is None and self.request.user.is_authenticated: - cond = Q(approved=False) & ~Q(poster=self.request.user) - else: - cond = Q(approved=False) + cond = Q(approved=True) + if machina_settings.PENDING_POSTS_AS_APPROVED: + cond |= Q(approved=None) + if self.request.user.is_authenticated and machina_settings.TRIPLE_APPROVAL_STATUS: + # in triple approval status, disapproved posts can be viewed by their posters + cond |= Q(poster=self.request.user) self.topic = self.get_topic() qs = ( self.topic.posts .all() - .exclude(cond) + .filter(cond) .select_related('poster', 'updated_by') .prefetch_related('attachments', 'poster__forum_profile') ) diff --git a/machina/apps/forum_feeds/feeds.py b/machina/apps/forum_feeds/feeds.py index 20e0414a3..061db5f87 100644 --- a/machina/apps/forum_feeds/feeds.py +++ b/machina/apps/forum_feeds/feeds.py @@ -12,6 +12,7 @@ from django.urls import reverse_lazy from django.utils.translation import gettext_lazy as _ +from machina.conf import settings as machina_settings from machina.core.db.models import get_model @@ -53,7 +54,10 @@ def get_object(self, request, *args, **kwargs): def items(self): """ Returns the items to include into the feed. """ - return Topic.objects.filter(forum__in=self.forums).exclude(approved=False).order_by('-last_post_on') + if machina_settings.PENDING_POSTS_AS_APPROVED: + return Topic.objects.filter(forum__in=self.forums).exclude(approved=False).order_by('-last_post_on') + else: + return Topic.objects.filter(forum__in=self.forums, approved=True).order_by('-last_post_on') def item_link(self, item): """ Generates a link for a specific item of the feed. """ diff --git a/machina/apps/forum_member/views.py b/machina/apps/forum_member/views.py index 7a051c0f8..4709f3528 100644 --- a/machina/apps/forum_member/views.py +++ b/machina/apps/forum_member/views.py @@ -91,9 +91,15 @@ def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) # Computes the number of topics added by the considered member - context['topics_count'] = ( - Topic.objects.filter(poster=self.object.user).exclude(approved=False).count() - ) + + if machina_settings.PENDING_POSTS_AS_APPROVED: + context['topics_count'] = ( + Topic.objects.filter(poster=self.object.user).exclude(approved=False).count() + ) + else: + context['topics_count'] = ( + Topic.objects.filter(poster=self.object.user).filter(approved=True).count() + ) # Fetches the recent posts added by the considered user forums = self.request.forum_permission_handler.get_readable_forums( diff --git a/machina/apps/forum_moderation/views.py b/machina/apps/forum_moderation/views.py index 6300f5f88..e14d452ec 100644 --- a/machina/apps/forum_moderation/views.py +++ b/machina/apps/forum_moderation/views.py @@ -370,10 +370,12 @@ def get_context_data(self, **kwargs): if not post.is_topic_head: # Add the topic review + if machina_settings.PENDING_POSTS_AS_APPROVED: + approved_posts = topic.posts.exclude(approved=False).filter(created__lte=post.created) + else: + approved_posts = topic.posts.filter(approved=None, created__lte=post.created) previous_posts = ( - topic.posts - .filter(approved=None, created__lte=post.created) - .select_related('poster', 'updated_by') + approved_posts.select_related('poster', 'updated_by') .prefetch_related('attachments', 'poster__forum_profile') .order_by('-created') ) @@ -403,8 +405,7 @@ def approve(self, request, *args, **kwargs): """ Approves the considered post and retirects the user to the success URL. """ self.object = self.get_object() success_url = self.get_success_url() - self.object.approved = True - self.object.save() + self.object.approve() messages.success(self.request, self.success_message) return HttpResponseRedirect(success_url) @@ -445,7 +446,7 @@ def disapprove(self, request, *args, **kwargs): """ Disapproves the considered post and retirects the user to the success URL. """ self.object = self.get_object() success_url = self.get_success_url() - self.object.delete() + self.object.disapprove() messages.success(self.request, self.success_message) return HttpResponseRedirect(success_url) diff --git a/machina/apps/forum_tracking/handler.py b/machina/apps/forum_tracking/handler.py index 18e70d343..89dde2f6d 100644 --- a/machina/apps/forum_tracking/handler.py +++ b/machina/apps/forum_tracking/handler.py @@ -9,6 +9,7 @@ from django.db.models import F, Q +from machina.conf import settings as machina_settings from machina.core.db.models import get_model from machina.core.loading import get_class @@ -151,7 +152,9 @@ def mark_topic_read(self, topic, user): not unread_topics.exists() and ( forum_track is not None or - forum_topic_tracks.count() == forum.topics.exclude(approved=False).count() + forum_topic_tracks.count() == + (forum.topics.exclude(approved=False).count() if machina_settings.PENDING_POSTS_AS_APPROVED + else forum.topics.filter(approved=True).count()) ) ): # The topics that are marked as read inside the forum for the given user will be diff --git a/machina/conf/settings.py b/machina/conf/settings.py index fad64a74f..fd7cc2d08 100644 --- a/machina/conf/settings.py +++ b/machina/conf/settings.py @@ -95,7 +95,10 @@ PROFILE_RECENT_POSTS_NUMBER = getattr(settings, 'MACHINA_PROFILE_RECENT_POSTS_NUMBER', 15) PROFILE_POSTS_NUMBER_PER_PAGE = getattr(settings, 'MACHINA_PROFILE_POSTS_NUMBER_PER_PAGE', 15) -DEFAULT_APPROVAL_STATUS = getattr(settings, 'MACHINA_DEFAULT_APPROVAL_STATUS') +TRIPLE_APPROVAL_STATUS = getattr(settings, 'MACHINA_TRIPLE_APPROVAL_STATUS', False) +DEFAULT_APPROVAL_STATUS = getattr(settings, 'MACHINA_DEFAULT_APPROVAL_STATUS', + None if TRIPLE_APPROVAL_STATUS else True) +PENDING_POSTS_AS_APPROVED = getattr(settings, 'MACHINA_PENDING_POSTS_AS_APPROVED', True) # Permission DEFAULT_AUTHENTICATED_USER_FORUM_PERMISSIONS = getattr( diff --git a/tests/unit/apps/forum/test_models.py b/tests/unit/apps/forum/test_models.py index 46a6fdb6f..e95e65002 100644 --- a/tests/unit/apps/forum/test_models.py +++ b/tests/unit/apps/forum/test_models.py @@ -2,6 +2,7 @@ from django.core.exceptions import ValidationError from machina.apps.forum.signals import forum_moved +from machina.conf import settings as machina_settings from machina.core.db.models import get_model from machina.test.context_managers import mock_signal_receiver from machina.test.factories import ( @@ -57,15 +58,24 @@ def test_saves_its_numbers_of_posts_and_topics(self): topic = create_topic(forum=self.top_level_forum, poster=self.u1) PostFactory.create(topic=topic, poster=self.u1) PostFactory.create(topic=topic, poster=self.u1) - assert self.top_level_forum.direct_posts_count == topic.posts.exclude(approved=False).count() + if machina_settings.PENDING_POSTS_AS_APPROVED: + assert self.top_level_forum.direct_posts_count == topic.posts.exclude(approved=False).count() + else: + assert self.top_level_forum.direct_posts_count == topic.posts.filter(approved=True).count() assert self.top_level_forum.direct_topics_count == self.top_level_forum.topics.count() topic2 = create_topic(forum=self.top_level_forum, poster=self.u1, approved=False) PostFactory.create(topic=topic2, poster=self.u1, approved=False) - assert self.top_level_forum.direct_posts_count == \ - topic.posts.exclude(approved=False).count() + topic2.posts.exclude(approved=False).count() - assert self.top_level_forum.direct_topics_count == \ - self.top_level_forum.topics.exclude(approved=False).count() + if machina_settings.PENDING_POSTS_AS_APPROVED: + assert self.top_level_forum.direct_posts_count == \ + topic.posts.exclude(approved=False).count() + topic2.posts.exclude(approved=False).count() + assert self.top_level_forum.direct_topics_count == \ + self.top_level_forum.topics.exclude(approved=False).count() + else: + assert self.top_level_forum.direct_posts_count == \ + topic.posts.filter(approved=True).count() + topic2.posts.filter(approved=True).count() + assert self.top_level_forum.direct_topics_count == \ + self.top_level_forum.topics.filter(approved=True).count() def test_can_indicate_its_appartenance_to_a_forum_type(self): # Run & check diff --git a/tests/unit/apps/forum_conversation/test_models.py b/tests/unit/apps/forum_conversation/test_models.py index 30ca1c5fc..0fd5913e8 100644 --- a/tests/unit/apps/forum_conversation/test_models.py +++ b/tests/unit/apps/forum_conversation/test_models.py @@ -2,6 +2,7 @@ from django.core.exceptions import ValidationError from faker import Faker +from machina.conf import settings as machina_settings from machina.core.db.models import get_model from machina.test.factories import ( PostFactory, UserFactory, build_topic, create_category_forum, create_forum, create_link_forum, @@ -112,7 +113,11 @@ def test_saves_its_number_of_posts(self): def test_saves_only_its_number_of_approved_posts(self): # Run & check post = PostFactory.create(topic=self.topic, poster=self.u1, approved=False) - initial_count = self.topic.posts.exclude(approved=False).count() + + if machina_settings.PENDING_POSTS_AS_APPROVED: + initial_count = self.topic.posts.exclude(approved=False).count() + else: + initial_count = self.topic.posts.filter(approved=True).count() assert initial_count == self.topic.posts_count post.delete() assert initial_count == self.topic.posts_count From 6a13a7d2cc01d48591b327c916e5a102ad286747 Mon Sep 17 00:00:00 2001 From: Bo Peng Date: Tue, 25 Oct 2022 09:01:48 -0500 Subject: [PATCH 03/12] Reduce code repetition --- docs/settings.rst | 8 ++++---- machina/apps/forum/abstract_models.py | 5 +---- machina/apps/forum/views.py | 2 +- .../forum_conversation/abstract_models.py | 8 ++------ machina/apps/forum_conversation/forms.py | 4 ++-- machina/apps/forum_conversation/views.py | 4 +--- machina/apps/forum_feeds/feeds.py | 5 +---- machina/apps/forum_member/views.py | 11 +++-------- machina/apps/forum_moderation/views.py | 7 ++----- machina/apps/forum_tracking/handler.py | 3 +-- machina/conf/settings.py | 6 +++++- tests/unit/apps/forum/test_models.py | 19 +++++-------------- .../apps/forum_conversation/test_models.py | 5 +---- 13 files changed, 29 insertions(+), 58 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index 10ad1d85d..85e47dd14 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -299,10 +299,10 @@ By default, machina employes a two-state approval status for posts: `True` (appr (disapproved or pending approval). Posts with approval status `False` will not be displayed, and will be approved or deleted during moderation. -If this option is set to `True`, posts will have three states `True` (approved), `None` (pending -approval), and `False` (disapproved)s. Posts with approval status `None` will be moderated, -and be assigned to status of `True` or `False`. Posts that are disapproved are not deleted, which -allows them to be revise and re-post. +If this option is set to `True`, posts will have three approval states `True` (approved), `None` +(pending approval), and `False` (disapproved). Posts with approval status `None` will be moderated, +and be assigned to states `True` or `False`. Disapproved posts are not deleted, allowing them to be +revised and re-posted. ``MACHINA_DEFAULT_APPROVAL_STATUS`` ----------------------------------------- diff --git a/machina/apps/forum/abstract_models.py b/machina/apps/forum/abstract_models.py index 047af97ab..cfebea6d9 100644 --- a/machina/apps/forum/abstract_models.py +++ b/machina/apps/forum/abstract_models.py @@ -174,10 +174,7 @@ def save(self, *args, **kwargs): def update_trackers(self): """ Updates the denormalized trackers associated with the forum instance. """ - if machina_settings.PENDING_POSTS_AS_APPROVED: - direct_approved_topics = self.topics.exclude(approved=False).order_by('-last_post_on') - else: - direct_approved_topics = self.topics.filter(approved=True).order_by('-last_post_on') + direct_approved_topics = self.topics.filter(machina_settings.APPROVED_FILTER).order_by('-last_post_on') # Compute the direct topics count and the direct posts count. self.direct_topics_count = direct_approved_topics.count() diff --git a/machina/apps/forum/views.py b/machina/apps/forum/views.py index 5038f7237..dc185f2dd 100644 --- a/machina/apps/forum/views.py +++ b/machina/apps/forum/views.py @@ -83,7 +83,7 @@ def get_queryset(self): qs = ( self.forum.topics .exclude(type=Topic.TOPIC_ANNOUNCE) - .exclude(approved=False) + .filter(machina_settings.APPROVED_FILTER) .select_related('poster', 'last_post', 'last_post__poster') ) return qs diff --git a/machina/apps/forum_conversation/abstract_models.py b/machina/apps/forum_conversation/abstract_models.py index 8752588e8..5b9fa4801 100644 --- a/machina/apps/forum_conversation/abstract_models.py +++ b/machina/apps/forum_conversation/abstract_models.py @@ -204,13 +204,9 @@ def delete(self, using=None): def update_trackers(self): """ Updates the denormalized trackers associated with the topic instance. """ - if machina_settings.PENDING_POSTS_AS_APPROVED: - self.posts_count = self.posts.exclude(approved=False).count() - last_post = self.posts.exclude(approved=False).order_by('-created').first() - else: - self.posts_count = self.posts.filter(approved=True).count() - last_post = self.posts.filter(approved=True).order_by('-created').first() + self.posts_count = self.posts.filter(machina_settings.APPROVED_FILTER).count() first_post = self.posts.all().order_by('created').first() + last_post = self.posts.filter(machina_settings.APPROVED_FILTER).order_by('-created').first() self.first_post = first_post self.last_post = last_post self.last_post_on = last_post.created if last_post else None diff --git a/machina/apps/forum_conversation/forms.py b/machina/apps/forum_conversation/forms.py index b984f75b1..8b1373f7b 100644 --- a/machina/apps/forum_conversation/forms.py +++ b/machina/apps/forum_conversation/forms.py @@ -90,7 +90,7 @@ def create_post(self): post = Post( topic=self.topic, subject=self.cleaned_data['subject'], - approved=self.perm_handler.can_post_without_approval(self.forum, self.user), + approved=self.perm_handler.can_post_without_approval(self.forum, self.user) or machina_settings.DEFAULT_APPROVAL_STATUS, content=self.cleaned_data['content'], enable_signature=self.cleaned_data['enable_signature']) if not self.user.is_anonymous: @@ -216,7 +216,7 @@ def create_topic(self): subject=self.cleaned_data['subject'], # The topic's name is the post's name type=topic_type, status=Topic.TOPIC_UNLOCKED, - approved=self.perm_handler.can_post_without_approval(self.forum, self.user), + approved=self.perm_handler.can_post_without_approval(self.forum, self.user) or machina_settings.DEFAULT_APPROVAL_STATUS, ) if not self.user.is_anonymous: topic.poster = self.user diff --git a/machina/apps/forum_conversation/views.py b/machina/apps/forum_conversation/views.py index 4e2f526d8..d06fc2ea1 100644 --- a/machina/apps/forum_conversation/views.py +++ b/machina/apps/forum_conversation/views.py @@ -81,9 +81,7 @@ def get_topic(self): def get_queryset(self): """ Returns the list of items for this view. """ - cond = Q(approved=True) - if machina_settings.PENDING_POSTS_AS_APPROVED: - cond |= Q(approved=None) + cond = machina_settings.APPROVED_FILTER if self.request.user.is_authenticated and machina_settings.TRIPLE_APPROVAL_STATUS: # in triple approval status, disapproved posts can be viewed by their posters cond |= Q(poster=self.request.user) diff --git a/machina/apps/forum_feeds/feeds.py b/machina/apps/forum_feeds/feeds.py index 061db5f87..d9061d1fb 100644 --- a/machina/apps/forum_feeds/feeds.py +++ b/machina/apps/forum_feeds/feeds.py @@ -54,10 +54,7 @@ def get_object(self, request, *args, **kwargs): def items(self): """ Returns the items to include into the feed. """ - if machina_settings.PENDING_POSTS_AS_APPROVED: - return Topic.objects.filter(forum__in=self.forums).exclude(approved=False).order_by('-last_post_on') - else: - return Topic.objects.filter(forum__in=self.forums, approved=True).order_by('-last_post_on') + return Topic.objects.filter(forum__in=self.forums).filter(machina_settings.APPROVED_FILTER).order_by('-last_post_on') def item_link(self, item): """ Generates a link for a specific item of the feed. """ diff --git a/machina/apps/forum_member/views.py b/machina/apps/forum_member/views.py index 4709f3528..b14485b0f 100644 --- a/machina/apps/forum_member/views.py +++ b/machina/apps/forum_member/views.py @@ -92,14 +92,9 @@ def get_context_data(self, **kwargs): # Computes the number of topics added by the considered member - if machina_settings.PENDING_POSTS_AS_APPROVED: - context['topics_count'] = ( - Topic.objects.filter(poster=self.object.user).exclude(approved=False).count() - ) - else: - context['topics_count'] = ( - Topic.objects.filter(poster=self.object.user).filter(approved=True).count() - ) + context['topics_count'] = ( + Topic.objects.filter(poster=self.object.user).filter(machina_settings.APPROVED_FILTER).count() + ) # Fetches the recent posts added by the considered user forums = self.request.forum_permission_handler.get_readable_forums( diff --git a/machina/apps/forum_moderation/views.py b/machina/apps/forum_moderation/views.py index e14d452ec..001deee58 100644 --- a/machina/apps/forum_moderation/views.py +++ b/machina/apps/forum_moderation/views.py @@ -370,12 +370,9 @@ def get_context_data(self, **kwargs): if not post.is_topic_head: # Add the topic review - if machina_settings.PENDING_POSTS_AS_APPROVED: - approved_posts = topic.posts.exclude(approved=False).filter(created__lte=post.created) - else: - approved_posts = topic.posts.filter(approved=None, created__lte=post.created) previous_posts = ( - approved_posts.select_related('poster', 'updated_by') + topic.posts.filter(machina_settings.APPROVED_FILTER).filter(created__lte=post.created) + .select_related('poster', 'updated_by') .prefetch_related('attachments', 'poster__forum_profile') .order_by('-created') ) diff --git a/machina/apps/forum_tracking/handler.py b/machina/apps/forum_tracking/handler.py index 89dde2f6d..163116f2f 100644 --- a/machina/apps/forum_tracking/handler.py +++ b/machina/apps/forum_tracking/handler.py @@ -153,8 +153,7 @@ def mark_topic_read(self, topic, user): ( forum_track is not None or forum_topic_tracks.count() == - (forum.topics.exclude(approved=False).count() if machina_settings.PENDING_POSTS_AS_APPROVED - else forum.topics.filter(approved=True).count()) + forum.topics.filter(machina_settings.APPROVED_FILTER).count() ) ): # The topics that are marked as read inside the forum for the given user will be diff --git a/machina/conf/settings.py b/machina/conf/settings.py index fd7cc2d08..d68f68282 100644 --- a/machina/conf/settings.py +++ b/machina/conf/settings.py @@ -9,7 +9,7 @@ """ from django.conf import settings - +from django.db.models import Q # General FORUM_NAME = getattr(settings, 'MACHINA_FORUM_NAME', 'Machina') @@ -100,6 +100,10 @@ None if TRIPLE_APPROVAL_STATUS else True) PENDING_POSTS_AS_APPROVED = getattr(settings, 'MACHINA_PENDING_POSTS_AS_APPROVED', True) +APPROVED_FILTER = Q(approved=True) +if TRIPLE_APPROVAL_STATUS and PENDING_POSTS_AS_APPROVED: + APPROVED_FILTER |= Q(approved=None) + # Permission DEFAULT_AUTHENTICATED_USER_FORUM_PERMISSIONS = getattr( settings, 'MACHINA_DEFAULT_AUTHENTICATED_USER_FORUM_PERMISSIONS', [] diff --git a/tests/unit/apps/forum/test_models.py b/tests/unit/apps/forum/test_models.py index e95e65002..cf3ca0bdc 100644 --- a/tests/unit/apps/forum/test_models.py +++ b/tests/unit/apps/forum/test_models.py @@ -58,24 +58,15 @@ def test_saves_its_numbers_of_posts_and_topics(self): topic = create_topic(forum=self.top_level_forum, poster=self.u1) PostFactory.create(topic=topic, poster=self.u1) PostFactory.create(topic=topic, poster=self.u1) - if machina_settings.PENDING_POSTS_AS_APPROVED: - assert self.top_level_forum.direct_posts_count == topic.posts.exclude(approved=False).count() - else: - assert self.top_level_forum.direct_posts_count == topic.posts.filter(approved=True).count() + assert self.top_level_forum.direct_posts_count == topic.posts.filter(machina_settings.APPROVED_FILTER).count() assert self.top_level_forum.direct_topics_count == self.top_level_forum.topics.count() topic2 = create_topic(forum=self.top_level_forum, poster=self.u1, approved=False) PostFactory.create(topic=topic2, poster=self.u1, approved=False) - if machina_settings.PENDING_POSTS_AS_APPROVED: - assert self.top_level_forum.direct_posts_count == \ - topic.posts.exclude(approved=False).count() + topic2.posts.exclude(approved=False).count() - assert self.top_level_forum.direct_topics_count == \ - self.top_level_forum.topics.exclude(approved=False).count() - else: - assert self.top_level_forum.direct_posts_count == \ - topic.posts.filter(approved=True).count() + topic2.posts.filter(approved=True).count() - assert self.top_level_forum.direct_topics_count == \ - self.top_level_forum.topics.filter(approved=True).count() + assert self.top_level_forum.direct_posts_count == \ + topic.posts.filter(machina_settings.APPROVED_FILTER).count() + topic2.posts.filter(approved=True).count() + assert self.top_level_forum.direct_topics_count == \ + self.top_level_forum.topics.filter(machina_settings.APPROVED_FILTER).count() def test_can_indicate_its_appartenance_to_a_forum_type(self): # Run & check diff --git a/tests/unit/apps/forum_conversation/test_models.py b/tests/unit/apps/forum_conversation/test_models.py index 0fd5913e8..d0e3106bc 100644 --- a/tests/unit/apps/forum_conversation/test_models.py +++ b/tests/unit/apps/forum_conversation/test_models.py @@ -114,10 +114,7 @@ def test_saves_only_its_number_of_approved_posts(self): # Run & check post = PostFactory.create(topic=self.topic, poster=self.u1, approved=False) - if machina_settings.PENDING_POSTS_AS_APPROVED: - initial_count = self.topic.posts.exclude(approved=False).count() - else: - initial_count = self.topic.posts.filter(approved=True).count() + initial_count = self.topic.posts.filter(machina_settings.APPROVED_FILTER).count() assert initial_count == self.topic.posts_count post.delete() assert initial_count == self.topic.posts_count From 22a70e482381c4181539346e40c0a29028863cfe Mon Sep 17 00:00:00 2001 From: Bo Peng Date: Tue, 25 Oct 2022 09:14:44 -0500 Subject: [PATCH 04/12] Reduce code repetition --- docs/settings.rst | 7 +++--- .../forum_conversation/abstract_models.py | 23 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index 85e47dd14..efa5cb409 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -309,9 +309,10 @@ revised and re-posted. Default: ``True`` if `MACHINA_TRIPLE_APPROVAL_STATUS` is `False`, `None` otherwise -The default approval state for posts, which can be `True` (default) or `False` if -`MACHINA_TRIPLE_APPROVAL_STATUS` is `False` (default). Otherwise it can be `True`, `None` (default), -or `False`. +The default approval state for posts when it is not explicitly specified during the creation of posts. +It can be `True` (default) or `False` if `MACHINA_TRIPLE_APPROVAL_STATUS` is `False` (default). +Otherwise it can be `True`, `None` (default), or `False`. + ``MACHINA_PENDING_POSTS_AS_APPROVED`` ----------------------------------------- diff --git a/machina/apps/forum_conversation/abstract_models.py b/machina/apps/forum_conversation/abstract_models.py index 5b9fa4801..4123e1636 100644 --- a/machina/apps/forum_conversation/abstract_models.py +++ b/machina/apps/forum_conversation/abstract_models.py @@ -246,7 +246,7 @@ class AbstractPost(DatedModel): username = models.CharField(max_length=155, blank=True, null=True, verbose_name=_('Username')) # A post can be approved before publishing ; defaults to True - approved = models.BooleanField(machina_settings.DEFAULT_APPROVAL_STATUS, + approved = models.BooleanField(default=machina_settings.DEFAULT_APPROVAL_STATUS, null=True, db_index=True, verbose_name=_('Approved')) # The user can choose if they want to display their signature with the content of the post @@ -305,12 +305,16 @@ def position(self): return position def approve(self): + if self.approved: + return self.approved = True self.save(update_fields=['approved']) self.topic.update_trackers() def disapprove(self): if machina_settings.TRIPLE_APPROVAL_STATUS: + if self.approved is False: + return self.approved = False self.save(update_fields=['approved']) self.topic.update_trackers() @@ -352,15 +356,10 @@ def save(self, *args, **kwargs): def delete(self, using=None): """ Deletes the post instance. """ - if machina_settings.TRIPLE_APPROVAL_STATUS is None and self.approved is not False: - self.approved = False - self.save(update_fields=['approved']) - self.topic.update_trackers() + if self.is_alone: + # The default way of operating is to trigger the deletion of the associated topic + # only if the considered post is the only post embedded in the topic + self.topic.delete() else: - if self.is_alone: - # The default way of operating is to trigger the deletion of the associated topic - # only if the considered post is the only post embedded in the topic - self.topic.delete() - else: - super(AbstractPost, self).delete(using) - self.topic.update_trackers() + super(AbstractPost, self).delete(using) + self.topic.update_trackers() From fdf298c5ab626e7fe288708395455f705b1abdbd Mon Sep 17 00:00:00 2001 From: Bo Peng Date: Tue, 25 Oct 2022 09:30:39 -0500 Subject: [PATCH 05/12] Some more approval filtering --- machina/apps/forum_conversation/views.py | 2 +- machina/apps/forum_member/views.py | 3 +-- machina/apps/forum_moderation/views.py | 2 +- machina/apps/forum_search/search_indexes.py | 5 +++-- machina/apps/forum_tracking/handler.py | 3 +-- tests/unit/apps/forum_conversation/test_models.py | 1 - 6 files changed, 7 insertions(+), 9 deletions(-) diff --git a/machina/apps/forum_conversation/views.py b/machina/apps/forum_conversation/views.py index d06fc2ea1..35088ddc6 100644 --- a/machina/apps/forum_conversation/views.py +++ b/machina/apps/forum_conversation/views.py @@ -664,7 +664,7 @@ def get_context_data(self, **kwargs): # Add the previous posts to the context previous_posts = ( - topic.posts.filter(approved=True) + topic.posts.filter(machina_settings.APPROVED_FILTER) .select_related('poster', 'updated_by') .prefetch_related('attachments', 'poster__forum_profile') .order_by('-created') diff --git a/machina/apps/forum_member/views.py b/machina/apps/forum_member/views.py index b14485b0f..2562481cb 100644 --- a/machina/apps/forum_member/views.py +++ b/machina/apps/forum_member/views.py @@ -91,9 +91,8 @@ def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) # Computes the number of topics added by the considered member - context['topics_count'] = ( - Topic.objects.filter(poster=self.object.user).filter(machina_settings.APPROVED_FILTER).count() + Topic.objects.filter(machina_settings.APPROVED_FILTER).filter(poster=self.object.user).count() ) # Fetches the recent posts added by the considered user diff --git a/machina/apps/forum_moderation/views.py b/machina/apps/forum_moderation/views.py index 001deee58..f16f6f984 100644 --- a/machina/apps/forum_moderation/views.py +++ b/machina/apps/forum_moderation/views.py @@ -337,7 +337,7 @@ def get_queryset(self): self.request.user, ) qs = super().get_queryset() - qs = qs.filter(topic__forum__in=forums, approved=False) + qs = qs.filter(topic__forum__in=forums, approved=None if machina_settings.TRIPLE_APPROVAL_STATUS else False) return qs.order_by('-created') def perform_permissions_check(self, user, obj, perms): diff --git a/machina/apps/forum_search/search_indexes.py b/machina/apps/forum_search/search_indexes.py index e7f5c35d4..5a875cafb 100644 --- a/machina/apps/forum_search/search_indexes.py +++ b/machina/apps/forum_search/search_indexes.py @@ -8,6 +8,7 @@ from haystack import indexes +from machina.conf import settings as machina_settings from machina.core.db.models import get_model from machina.core.loading import get_class @@ -57,7 +58,7 @@ def prepare_topic_subject(self, obj): return obj.topic.subject def index_queryset(self, using=None): - return Post.objects.all().exclude(approved=False) + return Post.objects.all().filter(machina_settings.APPROVED_FILTER) def read_queryset(self, using=None): - return Post.objects.all().exclude(approved=False).select_related('topic', 'poster') + return Post.objects.all().filter(machina_settings.APPROVED_FILTER).select_related('topic', 'poster') diff --git a/machina/apps/forum_tracking/handler.py b/machina/apps/forum_tracking/handler.py index 163116f2f..52e9c6e94 100644 --- a/machina/apps/forum_tracking/handler.py +++ b/machina/apps/forum_tracking/handler.py @@ -152,8 +152,7 @@ def mark_topic_read(self, topic, user): not unread_topics.exists() and ( forum_track is not None or - forum_topic_tracks.count() == - forum.topics.filter(machina_settings.APPROVED_FILTER).count() + forum_topic_tracks.count() == forum.topics.filter(machina_settings.APPROVED_FILTER).count() ) ): # The topics that are marked as read inside the forum for the given user will be diff --git a/tests/unit/apps/forum_conversation/test_models.py b/tests/unit/apps/forum_conversation/test_models.py index d0e3106bc..f14214689 100644 --- a/tests/unit/apps/forum_conversation/test_models.py +++ b/tests/unit/apps/forum_conversation/test_models.py @@ -113,7 +113,6 @@ def test_saves_its_number_of_posts(self): def test_saves_only_its_number_of_approved_posts(self): # Run & check post = PostFactory.create(topic=self.topic, poster=self.u1, approved=False) - initial_count = self.topic.posts.filter(machina_settings.APPROVED_FILTER).count() assert initial_count == self.topic.posts_count post.delete() From a42124f6a1e19a34faffde4619ab52189768f88b Mon Sep 17 00:00:00 2001 From: Bo Peng Date: Tue, 25 Oct 2022 09:49:10 -0500 Subject: [PATCH 06/12] Format issues --- machina/apps/forum/abstract_models.py | 3 ++- machina/apps/forum_conversation/abstract_models.py | 4 ++-- machina/apps/forum_conversation/forms.py | 6 ++++-- machina/apps/forum_feeds/feeds.py | 3 ++- machina/apps/forum_member/views.py | 3 ++- machina/apps/forum_moderation/views.py | 6 ++++-- machina/apps/forum_search/search_indexes.py | 3 ++- machina/apps/forum_tracking/handler.py | 3 ++- machina/conf/settings.py | 4 ++-- tests/unit/apps/forum/test_models.py | 4 ++-- 10 files changed, 24 insertions(+), 15 deletions(-) diff --git a/machina/apps/forum/abstract_models.py b/machina/apps/forum/abstract_models.py index cfebea6d9..8bf20aaa9 100644 --- a/machina/apps/forum/abstract_models.py +++ b/machina/apps/forum/abstract_models.py @@ -174,7 +174,8 @@ def save(self, *args, **kwargs): def update_trackers(self): """ Updates the denormalized trackers associated with the forum instance. """ - direct_approved_topics = self.topics.filter(machina_settings.APPROVED_FILTER).order_by('-last_post_on') + direct_approved_topics = self.topics.filter(machina_settings.APPROVED_FILTER).order_by( + '-last_post_on') # Compute the direct topics count and the direct posts count. self.direct_topics_count = direct_approved_topics.count() diff --git a/machina/apps/forum_conversation/abstract_models.py b/machina/apps/forum_conversation/abstract_models.py index 4123e1636..cadca430c 100644 --- a/machina/apps/forum_conversation/abstract_models.py +++ b/machina/apps/forum_conversation/abstract_models.py @@ -246,8 +246,8 @@ class AbstractPost(DatedModel): username = models.CharField(max_length=155, blank=True, null=True, verbose_name=_('Username')) # A post can be approved before publishing ; defaults to True - approved = models.BooleanField(default=machina_settings.DEFAULT_APPROVAL_STATUS, - null=True, db_index=True, verbose_name=_('Approved')) + approved = models.BooleanField(default=machina_settings.DEFAULT_APPROVAL_STATUS, null=True, + db_index=True, verbose_name=_('Approved')) # The user can choose if they want to display their signature with the content of the post enable_signature = models.BooleanField( diff --git a/machina/apps/forum_conversation/forms.py b/machina/apps/forum_conversation/forms.py index 8b1373f7b..423346bce 100644 --- a/machina/apps/forum_conversation/forms.py +++ b/machina/apps/forum_conversation/forms.py @@ -90,7 +90,8 @@ def create_post(self): post = Post( topic=self.topic, subject=self.cleaned_data['subject'], - approved=self.perm_handler.can_post_without_approval(self.forum, self.user) or machina_settings.DEFAULT_APPROVAL_STATUS, + approved=self.perm_handler.can_post_without_approval(self.forum, self.user) or \ + machina_settings.DEFAULT_APPROVAL_STATUS, content=self.cleaned_data['content'], enable_signature=self.cleaned_data['enable_signature']) if not self.user.is_anonymous: @@ -216,7 +217,8 @@ def create_topic(self): subject=self.cleaned_data['subject'], # The topic's name is the post's name type=topic_type, status=Topic.TOPIC_UNLOCKED, - approved=self.perm_handler.can_post_without_approval(self.forum, self.user) or machina_settings.DEFAULT_APPROVAL_STATUS, + approved=self.perm_handler.can_post_without_approval(self.forum, self.user) or \ + machina_settings.DEFAULT_APPROVAL_STATUS, ) if not self.user.is_anonymous: topic.poster = self.user diff --git a/machina/apps/forum_feeds/feeds.py b/machina/apps/forum_feeds/feeds.py index d9061d1fb..d3e9f4cae 100644 --- a/machina/apps/forum_feeds/feeds.py +++ b/machina/apps/forum_feeds/feeds.py @@ -54,7 +54,8 @@ def get_object(self, request, *args, **kwargs): def items(self): """ Returns the items to include into the feed. """ - return Topic.objects.filter(forum__in=self.forums).filter(machina_settings.APPROVED_FILTER).order_by('-last_post_on') + return Topic.objects.filter(forum__in=self.forums).filter( + machina_settings.APPROVED_FILTER).order_by('-last_post_on') def item_link(self, item): """ Generates a link for a specific item of the feed. """ diff --git a/machina/apps/forum_member/views.py b/machina/apps/forum_member/views.py index 2562481cb..4aabbe87c 100644 --- a/machina/apps/forum_member/views.py +++ b/machina/apps/forum_member/views.py @@ -92,7 +92,8 @@ def get_context_data(self, **kwargs): # Computes the number of topics added by the considered member context['topics_count'] = ( - Topic.objects.filter(machina_settings.APPROVED_FILTER).filter(poster=self.object.user).count() + Topic.objects.filter(machina_settings.APPROVED_FILTER).filter(poster=self.object.user) + .count() ) # Fetches the recent posts added by the considered user diff --git a/machina/apps/forum_moderation/views.py b/machina/apps/forum_moderation/views.py index f16f6f984..918c044d8 100644 --- a/machina/apps/forum_moderation/views.py +++ b/machina/apps/forum_moderation/views.py @@ -337,7 +337,8 @@ def get_queryset(self): self.request.user, ) qs = super().get_queryset() - qs = qs.filter(topic__forum__in=forums, approved=None if machina_settings.TRIPLE_APPROVAL_STATUS else False) + qs = qs.filter(topic__forum__in=forums, approved=None if + machina_settings.TRIPLE_APPROVAL_STATUS else False) return qs.order_by('-created') def perform_permissions_check(self, user, obj, perms): @@ -371,7 +372,8 @@ def get_context_data(self, **kwargs): if not post.is_topic_head: # Add the topic review previous_posts = ( - topic.posts.filter(machina_settings.APPROVED_FILTER).filter(created__lte=post.created) + topic.posts.filter(machina_settings.APPROVED_FILTER) + .filter(created__lte=post.created) .select_related('poster', 'updated_by') .prefetch_related('attachments', 'poster__forum_profile') .order_by('-created') diff --git a/machina/apps/forum_search/search_indexes.py b/machina/apps/forum_search/search_indexes.py index 5a875cafb..1c4d62a59 100644 --- a/machina/apps/forum_search/search_indexes.py +++ b/machina/apps/forum_search/search_indexes.py @@ -61,4 +61,5 @@ def index_queryset(self, using=None): return Post.objects.all().filter(machina_settings.APPROVED_FILTER) def read_queryset(self, using=None): - return Post.objects.all().filter(machina_settings.APPROVED_FILTER).select_related('topic', 'poster') + return Post.objects.all().filter(machina_settings.APPROVED_FILTER).select_related('topic', + 'poster') diff --git a/machina/apps/forum_tracking/handler.py b/machina/apps/forum_tracking/handler.py index 52e9c6e94..1d6e836cc 100644 --- a/machina/apps/forum_tracking/handler.py +++ b/machina/apps/forum_tracking/handler.py @@ -152,7 +152,8 @@ def mark_topic_read(self, topic, user): not unread_topics.exists() and ( forum_track is not None or - forum_topic_tracks.count() == forum.topics.filter(machina_settings.APPROVED_FILTER).count() + forum_topic_tracks.count() == forum.topics.filter( + machina_settings.APPROVED_FILTER).count() ) ): # The topics that are marked as read inside the forum for the given user will be diff --git a/machina/conf/settings.py b/machina/conf/settings.py index d68f68282..28ec17b17 100644 --- a/machina/conf/settings.py +++ b/machina/conf/settings.py @@ -96,8 +96,8 @@ PROFILE_RECENT_POSTS_NUMBER = getattr(settings, 'MACHINA_PROFILE_RECENT_POSTS_NUMBER', 15) PROFILE_POSTS_NUMBER_PER_PAGE = getattr(settings, 'MACHINA_PROFILE_POSTS_NUMBER_PER_PAGE', 15) TRIPLE_APPROVAL_STATUS = getattr(settings, 'MACHINA_TRIPLE_APPROVAL_STATUS', False) -DEFAULT_APPROVAL_STATUS = getattr(settings, 'MACHINA_DEFAULT_APPROVAL_STATUS', - None if TRIPLE_APPROVAL_STATUS else True) +DEFAULT_APPROVAL_STATUS = getattr(settings, 'MACHINA_DEFAULT_APPROVAL_STATUS', None if + TRIPLE_APPROVAL_STATUS else True) PENDING_POSTS_AS_APPROVED = getattr(settings, 'MACHINA_PENDING_POSTS_AS_APPROVED', True) APPROVED_FILTER = Q(approved=True) diff --git a/tests/unit/apps/forum/test_models.py b/tests/unit/apps/forum/test_models.py index cf3ca0bdc..53389eca6 100644 --- a/tests/unit/apps/forum/test_models.py +++ b/tests/unit/apps/forum/test_models.py @@ -58,13 +58,13 @@ def test_saves_its_numbers_of_posts_and_topics(self): topic = create_topic(forum=self.top_level_forum, poster=self.u1) PostFactory.create(topic=topic, poster=self.u1) PostFactory.create(topic=topic, poster=self.u1) - assert self.top_level_forum.direct_posts_count == topic.posts.filter(machina_settings.APPROVED_FILTER).count() + assert self.top_level_forum.direct_posts_count == topic.posts.filter(approved=True).count() assert self.top_level_forum.direct_topics_count == self.top_level_forum.topics.count() topic2 = create_topic(forum=self.top_level_forum, poster=self.u1, approved=False) PostFactory.create(topic=topic2, poster=self.u1, approved=False) assert self.top_level_forum.direct_posts_count == \ - topic.posts.filter(machina_settings.APPROVED_FILTER).count() + topic2.posts.filter(approved=True).count() + topic.posts.filter(approved=True).count() + topic2.posts.filter(approved=True).count() assert self.top_level_forum.direct_topics_count == \ self.top_level_forum.topics.filter(machina_settings.APPROVED_FILTER).count() From 26385ea9e54f045147a01b77f5231e96e8d60912 Mon Sep 17 00:00:00 2001 From: Bo Peng Date: Tue, 25 Oct 2022 09:58:28 -0500 Subject: [PATCH 07/12] Fix flake8 --- machina/apps/forum_conversation/abstract_models.py | 2 +- machina/apps/forum_conversation/forms.py | 8 ++++---- machina/apps/forum_moderation/views.py | 2 +- machina/apps/forum_search/search_indexes.py | 2 +- machina/conf/settings.py | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/machina/apps/forum_conversation/abstract_models.py b/machina/apps/forum_conversation/abstract_models.py index cadca430c..d31e29305 100644 --- a/machina/apps/forum_conversation/abstract_models.py +++ b/machina/apps/forum_conversation/abstract_models.py @@ -247,7 +247,7 @@ class AbstractPost(DatedModel): # A post can be approved before publishing ; defaults to True approved = models.BooleanField(default=machina_settings.DEFAULT_APPROVAL_STATUS, null=True, - db_index=True, verbose_name=_('Approved')) + db_index=True, verbose_name=_('Approved')) # The user can choose if they want to display their signature with the content of the post enable_signature = models.BooleanField( diff --git a/machina/apps/forum_conversation/forms.py b/machina/apps/forum_conversation/forms.py index 423346bce..1fde20451 100644 --- a/machina/apps/forum_conversation/forms.py +++ b/machina/apps/forum_conversation/forms.py @@ -90,8 +90,8 @@ def create_post(self): post = Post( topic=self.topic, subject=self.cleaned_data['subject'], - approved=self.perm_handler.can_post_without_approval(self.forum, self.user) or \ - machina_settings.DEFAULT_APPROVAL_STATUS, + approved=self.perm_handler.can_post_without_approval(self.forum, self.user) or + machina_settings.DEFAULT_APPROVAL_STATUS, content=self.cleaned_data['content'], enable_signature=self.cleaned_data['enable_signature']) if not self.user.is_anonymous: @@ -217,8 +217,8 @@ def create_topic(self): subject=self.cleaned_data['subject'], # The topic's name is the post's name type=topic_type, status=Topic.TOPIC_UNLOCKED, - approved=self.perm_handler.can_post_without_approval(self.forum, self.user) or \ - machina_settings.DEFAULT_APPROVAL_STATUS, + approved=self.perm_handler.can_post_without_approval(self.forum, self.user) or + machina_settings.DEFAULT_APPROVAL_STATUS, ) if not self.user.is_anonymous: topic.poster = self.user diff --git a/machina/apps/forum_moderation/views.py b/machina/apps/forum_moderation/views.py index 918c044d8..d6cd974b3 100644 --- a/machina/apps/forum_moderation/views.py +++ b/machina/apps/forum_moderation/views.py @@ -338,7 +338,7 @@ def get_queryset(self): ) qs = super().get_queryset() qs = qs.filter(topic__forum__in=forums, approved=None if - machina_settings.TRIPLE_APPROVAL_STATUS else False) + machina_settings.TRIPLE_APPROVAL_STATUS else False) return qs.order_by('-created') def perform_permissions_check(self, user, obj, perms): diff --git a/machina/apps/forum_search/search_indexes.py b/machina/apps/forum_search/search_indexes.py index 1c4d62a59..fe6515cee 100644 --- a/machina/apps/forum_search/search_indexes.py +++ b/machina/apps/forum_search/search_indexes.py @@ -62,4 +62,4 @@ def index_queryset(self, using=None): def read_queryset(self, using=None): return Post.objects.all().filter(machina_settings.APPROVED_FILTER).select_related('topic', - 'poster') + 'poster') diff --git a/machina/conf/settings.py b/machina/conf/settings.py index 28ec17b17..a46ae205c 100644 --- a/machina/conf/settings.py +++ b/machina/conf/settings.py @@ -97,7 +97,7 @@ PROFILE_POSTS_NUMBER_PER_PAGE = getattr(settings, 'MACHINA_PROFILE_POSTS_NUMBER_PER_PAGE', 15) TRIPLE_APPROVAL_STATUS = getattr(settings, 'MACHINA_TRIPLE_APPROVAL_STATUS', False) DEFAULT_APPROVAL_STATUS = getattr(settings, 'MACHINA_DEFAULT_APPROVAL_STATUS', None if - TRIPLE_APPROVAL_STATUS else True) + TRIPLE_APPROVAL_STATUS else True) PENDING_POSTS_AS_APPROVED = getattr(settings, 'MACHINA_PENDING_POSTS_AS_APPROVED', True) APPROVED_FILTER = Q(approved=True) From deffcff1478320c30997c344402257d12e8f4076 Mon Sep 17 00:00:00 2001 From: Bo Peng Date: Tue, 25 Oct 2022 10:05:53 -0500 Subject: [PATCH 08/12] Fix isort --- machina/apps/forum_conversation/managers.py | 1 + machina/conf/settings.py | 1 + 2 files changed, 2 insertions(+) diff --git a/machina/apps/forum_conversation/managers.py b/machina/apps/forum_conversation/managers.py index 9d3e6d41c..08ea195e6 100644 --- a/machina/apps/forum_conversation/managers.py +++ b/machina/apps/forum_conversation/managers.py @@ -7,6 +7,7 @@ """ from django.db import models + from machina.conf import settings as machina_settings diff --git a/machina/conf/settings.py b/machina/conf/settings.py index a46ae205c..85d0eb522 100644 --- a/machina/conf/settings.py +++ b/machina/conf/settings.py @@ -11,6 +11,7 @@ from django.conf import settings from django.db.models import Q + # General FORUM_NAME = getattr(settings, 'MACHINA_FORUM_NAME', 'Machina') MARKUP_LANGUAGE = getattr( From 9a9475fdb173105729539bf888636870a19f8912 Mon Sep 17 00:00:00 2001 From: Bo Peng Date: Tue, 25 Oct 2022 10:19:32 -0500 Subject: [PATCH 09/12] Revert tests --- machina/apps/forum_conversation/forms.py | 6 ++---- tests/unit/apps/forum/test_models.py | 4 ++-- tests/unit/apps/forum_conversation/test_models.py | 3 +-- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/machina/apps/forum_conversation/forms.py b/machina/apps/forum_conversation/forms.py index 1fde20451..b984f75b1 100644 --- a/machina/apps/forum_conversation/forms.py +++ b/machina/apps/forum_conversation/forms.py @@ -90,8 +90,7 @@ def create_post(self): post = Post( topic=self.topic, subject=self.cleaned_data['subject'], - approved=self.perm_handler.can_post_without_approval(self.forum, self.user) or - machina_settings.DEFAULT_APPROVAL_STATUS, + approved=self.perm_handler.can_post_without_approval(self.forum, self.user), content=self.cleaned_data['content'], enable_signature=self.cleaned_data['enable_signature']) if not self.user.is_anonymous: @@ -217,8 +216,7 @@ def create_topic(self): subject=self.cleaned_data['subject'], # The topic's name is the post's name type=topic_type, status=Topic.TOPIC_UNLOCKED, - approved=self.perm_handler.can_post_without_approval(self.forum, self.user) or - machina_settings.DEFAULT_APPROVAL_STATUS, + approved=self.perm_handler.can_post_without_approval(self.forum, self.user), ) if not self.user.is_anonymous: topic.poster = self.user diff --git a/tests/unit/apps/forum/test_models.py b/tests/unit/apps/forum/test_models.py index 53389eca6..6c6b655ef 100644 --- a/tests/unit/apps/forum/test_models.py +++ b/tests/unit/apps/forum/test_models.py @@ -1,8 +1,8 @@ +from unittest import TestResult import pytest from django.core.exceptions import ValidationError from machina.apps.forum.signals import forum_moved -from machina.conf import settings as machina_settings from machina.core.db.models import get_model from machina.test.context_managers import mock_signal_receiver from machina.test.factories import ( @@ -66,7 +66,7 @@ def test_saves_its_numbers_of_posts_and_topics(self): assert self.top_level_forum.direct_posts_count == \ topic.posts.filter(approved=True).count() + topic2.posts.filter(approved=True).count() assert self.top_level_forum.direct_topics_count == \ - self.top_level_forum.topics.filter(machina_settings.APPROVED_FILTER).count() + self.top_level_forum.topics.filter(approved=TestResult).count() def test_can_indicate_its_appartenance_to_a_forum_type(self): # Run & check diff --git a/tests/unit/apps/forum_conversation/test_models.py b/tests/unit/apps/forum_conversation/test_models.py index f14214689..b92194f5e 100644 --- a/tests/unit/apps/forum_conversation/test_models.py +++ b/tests/unit/apps/forum_conversation/test_models.py @@ -2,7 +2,6 @@ from django.core.exceptions import ValidationError from faker import Faker -from machina.conf import settings as machina_settings from machina.core.db.models import get_model from machina.test.factories import ( PostFactory, UserFactory, build_topic, create_category_forum, create_forum, create_link_forum, @@ -113,7 +112,7 @@ def test_saves_its_number_of_posts(self): def test_saves_only_its_number_of_approved_posts(self): # Run & check post = PostFactory.create(topic=self.topic, poster=self.u1, approved=False) - initial_count = self.topic.posts.filter(machina_settings.APPROVED_FILTER).count() + initial_count = self.topic.posts.filter(approved=True).count() assert initial_count == self.topic.posts_count post.delete() assert initial_count == self.topic.posts_count From 1a43ec06c422486f821c9669e09242456e3ba64a Mon Sep 17 00:00:00 2001 From: Bo Peng Date: Tue, 25 Oct 2022 10:23:54 -0500 Subject: [PATCH 10/12] Fix tests --- machina/apps/forum_conversation/managers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/machina/apps/forum_conversation/managers.py b/machina/apps/forum_conversation/managers.py index 08ea195e6..c2c1855df 100644 --- a/machina/apps/forum_conversation/managers.py +++ b/machina/apps/forum_conversation/managers.py @@ -15,5 +15,5 @@ class ApprovedManager(models.Manager): def get_queryset(self): """ Returns all the approved topics or posts. """ qs = super().get_queryset() - qs = qs.filter(approved=None if machina_settings.TRIPLE_APPROVAL_STATUS else False) + qs = qs.filter(machina_settings.APPROVED_FILTER) return qs From 55fa26a7ab7c865a2d3a053a13a8daee74703fab Mon Sep 17 00:00:00 2001 From: Bo Peng Date: Tue, 25 Oct 2022 10:27:04 -0500 Subject: [PATCH 11/12] Fix tests --- tests/unit/apps/forum/test_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/apps/forum/test_models.py b/tests/unit/apps/forum/test_models.py index 6c6b655ef..440a67b8d 100644 --- a/tests/unit/apps/forum/test_models.py +++ b/tests/unit/apps/forum/test_models.py @@ -66,7 +66,7 @@ def test_saves_its_numbers_of_posts_and_topics(self): assert self.top_level_forum.direct_posts_count == \ topic.posts.filter(approved=True).count() + topic2.posts.filter(approved=True).count() assert self.top_level_forum.direct_topics_count == \ - self.top_level_forum.topics.filter(approved=TestResult).count() + self.top_level_forum.topics.filter(approved=True).count() def test_can_indicate_its_appartenance_to_a_forum_type(self): # Run & check From da500ea621f121c184298de8f7d07bbb4fa8ddec Mon Sep 17 00:00:00 2001 From: Bo Peng Date: Tue, 25 Oct 2022 10:31:40 -0500 Subject: [PATCH 12/12] Fix tests --- tests/unit/apps/forum/test_models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/apps/forum/test_models.py b/tests/unit/apps/forum/test_models.py index 440a67b8d..de8a612ab 100644 --- a/tests/unit/apps/forum/test_models.py +++ b/tests/unit/apps/forum/test_models.py @@ -1,4 +1,3 @@ -from unittest import TestResult import pytest from django.core.exceptions import ValidationError