From ec9017e8db27e3f4f1c8c43f8d4e246887e11877 Mon Sep 17 00:00:00 2001 From: Radi85 Date: Sun, 2 May 2021 16:27:24 +0200 Subject: [PATCH 1/2] feat(#143): allow blocking users and emails This will add the required models/managers and utils to allow comment's moderator to block users or emails of commenting or reaction with the comments. The view implementation along with the API are implemented in the next commit. --- comment/admin.py | 18 +++++- comment/conf/defaults.py | 3 + comment/context.py | 1 + comment/managers/__init__.py | 1 + comment/managers/blocker.py | 21 +++++++ .../0012_blockeduser_blockeduserhistory.py | 36 ++++++++++++ comment/models/__init__.py | 1 + comment/models/blocker.py | 30 ++++++++++ comment/templates/comment/base.html | 5 +- .../templates/comment/block/block_icon.html | 22 +++++++ .../templates/comment/block/block_modal.html | 39 +++++++++++++ .../comment/comments/comment_content.html | 14 +++-- comment/templates/comment/flags/flags.html | 3 +- comment/templatetags/comment_tags.py | 20 ++++++- comment/tests/base.py | 57 +++++++++++++++++-- comment/tests/test_models/test_blocker.py | 52 +++++++++++++++++ comment/tests/test_template_tags.py | 22 ++++++- comment/tests/test_utils.py | 25 +++++++- comment/utils.py | 22 ++++++- 19 files changed, 371 insertions(+), 21 deletions(-) create mode 100644 comment/managers/blocker.py create mode 100644 comment/migrations/0012_blockeduser_blockeduserhistory.py create mode 100644 comment/models/blocker.py create mode 100644 comment/templates/comment/block/block_icon.html create mode 100644 comment/templates/comment/block/block_modal.html create mode 100644 comment/tests/test_models/test_blocker.py diff --git a/comment/admin.py b/comment/admin.py index edc6855..d54b462 100644 --- a/comment/admin.py +++ b/comment/admin.py @@ -1,6 +1,8 @@ from django.contrib import admin -from comment.models import Comment, Flag, FlagInstance, Reaction, ReactionInstance, Follower +from comment.models import ( + Comment, Flag, FlagInstance, Reaction, ReactionInstance, Follower, BlockedUser, BlockedUserHistory +) class CommentModelAdmin(admin.ModelAdmin): @@ -43,7 +45,21 @@ class FollowerModelAdmin(admin.ModelAdmin): search_fields = ('email',) +class BlockedUserModelAdmin(admin.ModelAdmin): + list_display = ('user', 'email', 'blocked') + search_fields = ('user__username', 'email') + + +class BlockedUserHistoryModelAdmin(admin.ModelAdmin): + list_display = ('blocked_user', 'blocker', 'reason', 'state', 'date') + search_fields = ( + 'blocked_user__user__username', 'blocked_user__email', 'blocker__username', 'blocker__email', 'state', 'date' + ) + + admin.site.register(Comment, CommentModelAdmin) admin.site.register(Reaction, ReactionModelAdmin) admin.site.register(Flag, FlagModelAdmin) admin.site.register(Follower, FollowerModelAdmin) +admin.site.register(BlockedUser, BlockedUserModelAdmin) +admin.site.register(BlockedUserHistory, BlockedUserHistoryModelAdmin) diff --git a/comment/conf/defaults.py b/comment/conf/defaults.py index c3b801f..045385a 100644 --- a/comment/conf/defaults.py +++ b/comment/conf/defaults.py @@ -35,3 +35,6 @@ COMMENT_WRAP_CONTENT_WORDS = 30 COMMENT_DEFAULT_PROFILE_PIC_LOC = '/static/img/default.png' + +COMMENT_ALLOW_BLOCKING_USERS = False +COMMENT_ALLOW_MODERATOR_PERFORM_BLOCKING = False diff --git a/comment/context.py b/comment/context.py index 157f931..7bd04dc 100644 --- a/comment/context.py +++ b/comment/context.py @@ -55,5 +55,6 @@ def __call__(self): 'is_anonymous_allowed': settings.COMMENT_ALLOW_ANONYMOUS, 'is_translation_allowed': settings.COMMENT_ALLOW_TRANSLATION, 'is_subscription_allowed': settings.COMMENT_ALLOW_SUBSCRIPTION, + 'is_blocking_allowed': settings.COMMENT_ALLOW_BLOCKING_USERS, 'oauth': self.is_oauth() } diff --git a/comment/managers/__init__.py b/comment/managers/__init__.py index 63a6dec..a39fea4 100644 --- a/comment/managers/__init__.py +++ b/comment/managers/__init__.py @@ -2,3 +2,4 @@ from comment.managers.comments import * from comment.managers.reactions import * from comment.managers.flags import * +from comment.managers.blocker import * diff --git a/comment/managers/blocker.py b/comment/managers/blocker.py new file mode 100644 index 0000000..44786ca --- /dev/null +++ b/comment/managers/blocker.py @@ -0,0 +1,21 @@ +from django.db import models + + +class BlockedUserManager(models.Manager): + def is_user_blocked(self, user_id=None, email=None): + try: + user_id = int(user_id) + return self.filter(user_id=user_id, blocked=True).exists() + except (ValueError, TypeError): + if not email: + return False + return self.filter(email=email, blocked=True).exists() + + def get_or_create_blocked_user_by_user_id(self, user_id): + return self.get_or_create(user_id=user_id) + + def get_or_create_blocked_user_by_email(self, email): + try: + return self.get_or_create(email=email) + except self.model.MultipleObjectsReturned: + return self.filter(email=email, user=None).first(), False diff --git a/comment/migrations/0012_blockeduser_blockeduserhistory.py b/comment/migrations/0012_blockeduser_blockeduserhistory.py new file mode 100644 index 0000000..af52699 --- /dev/null +++ b/comment/migrations/0012_blockeduser_blockeduserhistory.py @@ -0,0 +1,36 @@ +# Generated by Django 3.1.4 on 2021-04-03 10:57 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('comment', '0011_follower'), + ] + + operations = [ + migrations.CreateModel( + name='BlockedUser', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('email', models.EmailField(blank=True, max_length=254, null=True)), + ('blocked', models.BooleanField(default=True)), + ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='BlockedUserHistory', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('reason', models.TextField(blank=True, null=True)), + ('state', models.SmallIntegerField(choices=[(0, 'Unblocked'), (1, 'Blocked')], default=1)), + ('date', models.DateTimeField(auto_now_add=True)), + ('blocked_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='comment.blockeduser')), + ('blocker', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/comment/models/__init__.py b/comment/models/__init__.py index 00fa110..886fc17 100644 --- a/comment/models/__init__.py +++ b/comment/models/__init__.py @@ -3,3 +3,4 @@ from comment.models.reactions import * from comment.models.flags import * from comment.models.followers import * +from comment.models.blocker import * diff --git a/comment/models/blocker.py b/comment/models/blocker.py new file mode 100644 index 0000000..22125e2 --- /dev/null +++ b/comment/models/blocker.py @@ -0,0 +1,30 @@ +from django.contrib.auth import get_user_model +from django.db import models + +from comment.messages import BlockState +from comment.managers import BlockedUserManager + + +class BlockedUser(models.Model): + user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, blank=True, null=True) + email = models.EmailField(blank=True, null=True) + blocked = models.BooleanField(default=True) + + objects = BlockedUserManager() + + def __str__(self): + return getattr(self.user, self.user.USERNAME_FIELD) if self.user else self.email + + +class BlockedUserHistory(models.Model): + UNBLOCKED = 0 + BLOCKED = 1 + STATES_CHOICES = [ + (UNBLOCKED, BlockState.UNBLOCKED), + (BLOCKED, BlockState.BLOCKED), + ] + blocked_user = models.ForeignKey(BlockedUser, on_delete=models.CASCADE) + blocker = models.ForeignKey(get_user_model(), on_delete=models.SET_NULL, blank=True, null=True) + reason = models.TextField(blank=True, null=True) + state = models.SmallIntegerField(choices=STATES_CHOICES, default=BLOCKED) + date = models.DateTimeField(auto_now_add=True) diff --git a/comment/templates/comment/base.html b/comment/templates/comment/base.html index 72bdc27..30d0a35 100644 --- a/comment/templates/comment/base.html +++ b/comment/templates/comment/base.html @@ -12,4 +12,7 @@ {% endif %} {% if is_subscription_allowed %} {% include 'comment/follow/follow_modal.html' %} -{% endif %} \ No newline at end of file +{% endif %} +{% if is_blocking_allowed %} + {% include 'comment/block/block_modal.html' %} +{% endif %} diff --git a/comment/templates/comment/block/block_icon.html b/comment/templates/comment/block/block_icon.html new file mode 100644 index 0000000..2443c25 --- /dev/null +++ b/comment/templates/comment/block/block_icon.html @@ -0,0 +1,22 @@ +{% load comment_tags %} +{% load i18n %} + diff --git a/comment/templates/comment/block/block_modal.html b/comment/templates/comment/block/block_modal.html new file mode 100644 index 0000000..7fc24ad --- /dev/null +++ b/comment/templates/comment/block/block_modal.html @@ -0,0 +1,39 @@ +{% load i18n %} +{% load comment_tags %} +
+
+
+ + +
+
+
+ diff --git a/comment/templates/comment/comments/comment_content.html b/comment/templates/comment/comments/comment_content.html index 56a3583..e2287cf 100644 --- a/comment/templates/comment/comments/comment_content.html +++ b/comment/templates/comment/comments/comment_content.html @@ -1,23 +1,24 @@ {% load comment_tags %} {% load i18n %} -
+
{% block comment_content %} {% render_content comment %} {% endblock comment_content %} + {% get_username_for_comment comment as username %}