Skip to content
Open
37 changes: 37 additions & 0 deletions docs/settings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,43 @@ 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 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``
-----------------------------------------

Default: ``True`` if `MACHINA_TRIPLE_APPROVAL_STATUS` is `False`, `None` otherwise

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``
-----------------------------------------

Default: ``True``

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
**********

Expand Down
3 changes: 2 additions & 1 deletion machina/apps/forum/abstract_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(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()
Expand Down
2 changes: 1 addition & 1 deletion machina/apps/forum/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 21 additions & 3 deletions machina/apps/forum_conversation/abstract_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.filter(machina_settings.APPROVED_FILTER).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.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
Expand Down Expand Up @@ -246,7 +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=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(
Expand Down Expand Up @@ -303,6 +304,23 @@ def position(self):
position = self.topic.posts.filter(Q(created__lt=self.created) | Q(id=self.id)).count()
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()
else:
self.delete()

def clean(self):
""" Validates the post instance. """
super().clean()
Expand Down
4 changes: 3 additions & 1 deletion machina/apps/forum_conversation/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,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(machina_settings.APPROVED_FILTER)
return qs
10 changes: 8 additions & 2 deletions machina/apps/forum_conversation/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -80,11 +81,16 @@ def get_topic(self):

def get_queryset(self):
""" Returns the list of items for this view. """
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)

self.topic = self.get_topic()
qs = (
self.topic.posts
.all()
.exclude(approved=False)
.filter(cond)
.select_related('poster', 'updated_by')
.prefetch_related('attachments', 'poster__forum_profile')
)
Expand Down Expand Up @@ -658,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')
Expand Down
4 changes: 3 additions & 1 deletion machina/apps/forum_feeds/feeds.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -53,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, 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. """
Expand Down
3 changes: 2 additions & 1 deletion machina/apps/forum_member/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(approved=True, 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
Expand Down
12 changes: 6 additions & 6 deletions machina/apps/forum_moderation/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,8 @@ 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):
Expand Down Expand Up @@ -371,8 +372,8 @@ def get_context_data(self, **kwargs):
if not post.is_topic_head:
# Add the topic review
previous_posts = (
topic.posts
.filter(approved=True, 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')
Expand Down Expand Up @@ -403,8 +404,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)

Expand Down Expand Up @@ -445,7 +445,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)

Expand Down
6 changes: 4 additions & 2 deletions machina/apps/forum_search/search_indexes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -57,7 +58,8 @@ 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')
4 changes: 3 additions & 1 deletion machina/apps/forum_tracking/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -151,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(approved=True).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
Expand Down
10 changes: 9 additions & 1 deletion machina/conf/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"""

from django.conf import settings
from django.db.models import Q


# General
Expand Down Expand Up @@ -95,7 +96,14 @@

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)
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(
Expand Down