Skip to content

Commit

Permalink
Merge bc2c081 into 0e2d902
Browse files Browse the repository at this point in the history
  • Loading branch information
m4ra authored Aug 2, 2023
2 parents 0e2d902 + bc2c081 commit 1f4f18f
Show file tree
Hide file tree
Showing 21 changed files with 261 additions and 32 deletions.
1 change: 1 addition & 0 deletions adhocracy-plus/config/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@
"apps.polls",
"apps.topicprio",
"apps.debate",
"apps.ai_reports",
)

MIDDLEWARE = (
Expand Down
2 changes: 2 additions & 0 deletions adhocracy-plus/config/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from apps.interactiveevents.api import LikesViewSet
from apps.interactiveevents.api import LiveQuestionViewSet
from apps.interactiveevents.routers import LikesDefaultRouter
from apps.moderatorfeedback.api import AiCommentFeedbackViewSet
from apps.moderatorfeedback.api import CommentWithFeedbackViewSet
from apps.moderatorfeedback.api import ModeratorCommentFeedbackViewSet
from apps.moderatorremark.api import ModeratorRemarkViewSet
Expand Down Expand Up @@ -86,6 +87,7 @@
comment_router.register(
r"moderatorfeedback", ModeratorCommentFeedbackViewSet, basename="moderatorfeedback"
)
comment_router.register(r"aicomments", AiCommentFeedbackViewSet, basename="aicomments")

urlpatterns = [
# General platform urls
Expand Down
Empty file added apps/ai_reports/__init__.py
Empty file.
15 changes: 15 additions & 0 deletions apps/ai_reports/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from django.contrib import admin

from .models import AiReport


@admin.register(AiReport)
class AiReportAdmin(admin.ModelAdmin):
model = AiReport
fields = [
"is_pending",
"category",
"explanation",
"confidence",
]
raw_id_fields = ("comment",)
5 changes: 5 additions & 0 deletions apps/ai_reports/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.apps import AppConfig


class AiReportsConfig(AppConfig):
name = "apps.ai_reports"
59 changes: 59 additions & 0 deletions apps/ai_reports/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Generated by Django 3.2.19 on 2023-07-27 15:31

from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone


class Migration(migrations.Migration):
initial = True

dependencies = [
("a4comments", "0013_set_project"),
]

operations = [
migrations.CreateModel(
name="AiReport",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"created",
models.DateTimeField(
default=django.utils.timezone.now,
editable=False,
verbose_name="Created",
),
),
(
"modified",
models.DateTimeField(
blank=True, editable=False, null=True, verbose_name="Modified"
),
),
("is_pending", models.BooleanField(default=True)),
("category", models.CharField(max_length=50)),
("explanation", models.TextField()),
("confidence", models.FloatField(default=0)),
(
"comment",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
related_name="ai_report",
to="a4comments.comment",
),
),
],
options={
"abstract": False,
},
),
]
Empty file.
16 changes: 16 additions & 0 deletions apps/ai_reports/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from django.db import models

from adhocracy4.comments.models import Comment
from adhocracy4.models.base import TimeStampedModel


class AiReport(TimeStampedModel):
is_pending = models.BooleanField(default=True)
category = models.CharField(max_length=50)
explanation = models.TextField()
confidence = models.FloatField(default=0)
comment = models.OneToOneField(
Comment,
on_delete=models.CASCADE,
related_name="ai_report",
)
13 changes: 13 additions & 0 deletions apps/ai_reports/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from rest_framework import serializers

from apps.ai_reports.models import AiReport


class AiReportSerializer(serializers.ModelSerializer):
class Meta:
model = AiReport
fields = (
"explanation",
"category",
"comment",
)
17 changes: 17 additions & 0 deletions apps/moderatorfeedback/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from adhocracy4.api.mixins import CommentMixin
from adhocracy4.api.permissions import ViewSetRulesPermission
from adhocracy4.comments_async import api as a4_api
from apps.ai_reports.models import AiReport
from apps.ai_reports.serializers import AiReportSerializer
from apps.moderatorfeedback.models import ModeratorCommentFeedback
from apps.moderatorfeedback.serializers import ModeratorCommentFeedbackSerializer
from apps.moderatorfeedback.serializers import ThreadListSerializer
Expand All @@ -28,6 +30,21 @@ def get_queryset(self):
return ModeratorCommentFeedback.objects.filter(comment=self.comment)


class AiCommentFeedbackViewSet(
CommentMixin,
mixins.ListModelMixin,
viewsets.GenericViewSet,
):
serializer_class = AiReportSerializer
permission_classes = (ViewSetRulesPermission,)

def get_permission_object(self):
return self.comment

def get_queryset(self):
return AiReport.objects.filter(comment=self.comment)


class CommentWithFeedbackViewSet(a4_api.CommentViewSet):
def get_serializer_class(self):
if self.action == "list":
Expand Down
4 changes: 4 additions & 0 deletions apps/moderatorfeedback/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from adhocracy4.comments.models import Comment
from adhocracy4.comments_async import serializers as a4_serializers
from apps.ai_reports.serializers import AiReportSerializer
from apps.contrib.dates import get_date_display
from apps.moderatorfeedback.models import ModeratorCommentFeedback

Expand Down Expand Up @@ -34,11 +35,14 @@ def get_last_edit(self, feedback):

class CommentWithFeedbackSerializer(a4_serializers.CommentSerializer):
moderator_feedback = ModeratorCommentFeedbackSerializer(read_only=True)
ai_feedback = AiReportSerializer(read_only=True)
# ai_feedback = serializers.StringRelatedField(read_only=True)

class Meta:
model = Comment
read_only_fields = a4_serializers.CommentSerializer.Meta.read_only_fields + (
"moderator_comment_feedback",
"ai_feedback",
)
exclude = ("creator",)

Expand Down
28 changes: 16 additions & 12 deletions apps/userdashboard/api.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
from django.db.models import Count
from django.db.models import ExpressionWrapper
from django.db.models import Q
from django.db.models.fields import BooleanField
from django.shortcuts import get_object_or_404
from django_filters.rest_framework import BooleanFilter
from django_filters.rest_framework import DjangoFilterBackend
Expand All @@ -23,9 +20,8 @@

class ModerationCommentFilterSet(DefaultsRestFilterSet):
is_reviewed = BooleanFilter()
has_reports = BooleanFilter()

defaults = {"is_reviewed": "false", "has_reports": "all"}
defaults = {"is_reviewed": "false"}


class ModerationCommentPagination(PageNumberPagination):
Expand Down Expand Up @@ -62,17 +58,25 @@ def get_permission_object(self):
return self.project

def get_queryset(self):
all_comments_project = helpers.get_all_comments_project(self.project)
num_reports = Count("reports", distinct=True)
return all_comments_project.annotate(num_reports=num_reports).annotate(
has_reports=ExpressionWrapper(
Q(num_reports__gt=0), output_field=BooleanField()
)
comments = helpers.get_all_comments_project(self.project)
comments = comments.annotate(
num_reports=Count("reports", distinct=True)
+ Count("ai_report", distinct=True),
)

reported_by = self.request.query_params.get("reported_by")

if reported_by == "ai":
comments = comments.filter(ai_report__isnull=False)
elif reported_by == "users":
comments = comments.filter(reports__isnull=False)

return comments

def update(self, request, *args, **kwargs):
if "is_blocked" in self.request.data and request.data["is_blocked"]:
if request.data.get("is_blocked"):
NotifyCreatorOnModeratorBlocked.send(self.get_object())

return super().update(request, *args, **kwargs)

@action(detail=True)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ const isReadFilterItems = [
]

const reportsFilterItems = [
{ label: django.gettext('Reported'), value: 'True' },
{ label: django.gettext('All comments'), value: 'All' }
{ label: django.gettext('Reported by AI'), value: 'ai' },
{ label: django.gettext('Reported by Users'), value: 'users' },
{ label: django.gettext('All comments'), value: 'off' }
]

const orderingFilterItems = [
Expand All @@ -29,7 +30,7 @@ export default class ModerationNotificationList extends Component {

this.state = {
moderationComments: [],
selectedFilters: { isRead: 'False', hasReports: 'All', ordering: '-num_reports' },
selectedFilters: { isRead: 'False', reportedBy: 'off', ordering: '-num_reports' },
numOfComments: PACKET_COMMENT_SIZE,
hasMore: null,
packetFactor: 1,
Expand Down Expand Up @@ -59,7 +60,7 @@ export default class ModerationNotificationList extends Component {
this.setState({
selectedFilters: {
...this.state.selectedFilters,
hasReports: value
reportedBy: value
},
isLoaded: false
},
Expand All @@ -81,7 +82,7 @@ export default class ModerationNotificationList extends Component {

getUrlParams () {
return '?is_reviewed=' + this.state.selectedFilters.isRead +
'&has_reports=' + this.state.selectedFilters.hasReports +
'&reported_by=' + this.state.selectedFilters.reportedBy +
'&ordering=' + this.state.selectedFilters.ordering +
'&num_of_comments=' + this.state.numOfComments
}
Expand Down Expand Up @@ -200,7 +201,7 @@ export default class ModerationNotificationList extends Component {
filterClass="filter--full dropdown dropdown-menu-end"
filterItems={reportsFilterItems}
onFilterChange={(value) => this.reportsFilterChangeHandle(value)}
selectedFilter={this.state.selectedFilters.hasReports}
selectedFilter={this.state.selectedFilters.reportedBy}
filterText={django.gettext('Filter')}
/>
</div>
Expand Down
3 changes: 3 additions & 0 deletions changelog/7571.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Added

- Ai report serializer for comments viewset (#7571)
Empty file added tests/ai_reports/__init__.py
Empty file.
5 changes: 5 additions & 0 deletions tests/ai_reports/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from pytest_factoryboy import register

from . import factories

register(factories.AiReportFactory)
16 changes: 16 additions & 0 deletions tests/ai_reports/factories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import random

import factory

from adhocracy4.test import factories as a4_factories
from apps.ai_reports.models import AiReport


class AiReportFactory(factory.django.DjangoModelFactory):
class Meta:
model = AiReport

category = factory.Faker("word")
explanation = factory.Faker("sentence", nb_words=6)
confidence = random.random()
comment = factory.SubFactory(a4_factories.ModuleFactory)
2 changes: 2 additions & 0 deletions tests/moderatorfeedback/conftest.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from pytest_factoryboy import register

from tests.ai_reports.factories import AiReportFactory
from tests.ideas.factories import IdeaFactory

from .factories import ModeratorCommentFeedbackFactory

register(AiReportFactory)
register(IdeaFactory)
register(ModeratorCommentFeedbackFactory)
35 changes: 35 additions & 0 deletions tests/moderatorfeedback/test_moderator_comment_feedback_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,41 @@
from apps.moderatorfeedback.models import ModeratorCommentFeedback


@pytest.mark.django_db
def test_moderator_and_ai_feedback_added_in_comment(
idea,
comment_factory,
apiclient,
moderator_comment_feedback_factory,
ai_report_factory,
):
comment = comment_factory(content_object=idea)
feedback = moderator_comment_feedback_factory(
comment=comment,
)
assert feedback.project == comment.project

ai_feedback = ai_report_factory(comment=comment)
url = reverse(
"comments-detail",
kwargs={
"pk": comment.pk,
"content_type": comment.content_type.pk,
"object_pk": comment.object_pk,
},
)
response = apiclient.get(url)
print("URL", url)
assert (
feedback.feedback_text in response.data["moderator_feedback"]["feedback_text"]
)
assert comment.ai_report.explanation == ai_feedback.explanation

# TODO: make the following assertion to pass
# assert ai_feedback.explanation in response.data["ai_feedback"]["explanation"]
assert response.status_code == 200


@pytest.mark.django_db
def test_anonymous_cannot_add_feedback(apiclient, idea, comment_factory):
comment = comment_factory(pk=1, content_object=idea)
Expand Down
2 changes: 2 additions & 0 deletions tests/userdashboard/conftest.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from pytest_factoryboy import register

from tests.ai_reports.factories import AiReportFactory
from tests.ideas.factories import IdeaFactory
from tests.moderatorfeedback.factories import ModeratorCommentFeedbackFactory

register(IdeaFactory)
register(ModeratorCommentFeedbackFactory)
register(AiReportFactory)
Loading

0 comments on commit 1f4f18f

Please sign in to comment.