Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
e19479b
feat: AI moderation system (#1)
jcapphelix Nov 5, 2025
1eb7b28
fix: team posts now visible by handling standalone context properly (#5)
naincy128 Nov 18, 2025
8f6699d
feat: soft delete feature (#4)
jcapphelix Jan 6, 2026
41179a1
Revert "feat: soft delete feature (#4)" (#9)
jcapphelix Jan 7, 2026
9d492b4
feat: added soft delete functionality (#10)
Alam-2U Jan 14, 2026
db2c513
fix: remove read state deletion in soft delete of thread (#12)
jcapphelix Jan 22, 2026
20e90bf
feat: auto deletion of spam via AI (#15)
jcapphelix Feb 9, 2026
02de16c
fix: reported tag remained visible after reported response/comment wa…
Alam-2U Feb 16, 2026
266f5c2
feat: bulkdelete ban feature in forum
mraman-2U Feb 10, 2026
67acca9
fix: optimize mongo query for user vote list (#19)
jcapphelix Mar 24, 2026
040add1
feat: implement discussion mute/unmute feature with user and staff-le…
naincy128 Apr 1, 2026
af5f40c
feat: implement bulk delete for MySQL database (#23)
Alam-2U Apr 7, 2026
82e25d5
fix: add missing audit log for muted learner in mysql table (#24)
naincy128 Apr 8, 2026
d073d1e
fix: restrict mute privileges to discussion moderators only (#27)
Alam-2U Apr 10, 2026
7050d61
fix: migrate soft delete fields and support updating existing content…
Alam-2U Apr 10, 2026
4535222
feat: add instrumentation to forum (#29)
naincy128 Apr 13, 2026
f328d11
feat: update init file (#30)
naincy128 Apr 14, 2026
5eb6f5b
Revert "feat: add instrumentation to forum (#29)"
naincy128 Apr 14, 2026
248ab9c
Merge pull request #31 from edx/revert-29-naincy/Cosmo2-853
santhosh-apphelix-2u Apr 15, 2026
1d063eb
feat: add phase 1 Datadog backend instrumentation for thread creation
santhosh-apphelix-2u Apr 17, 2026
b59e80e
feat: add phase 1 Datadog backend instrumentation for thread creation
santhosh-apphelix-2u Apr 17, 2026
37e617a
chore: linting
santhosh-apphelix-2u Apr 17, 2026
a2123fc
chore: linting
santhosh-apphelix-2u Apr 17, 2026
9ae39d7
fix: team posts not appearing after enabling MySQL waffle flag (#35)
naincy128 Apr 17, 2026
5fee94e
fix: mysql referential integrity error in update_user during retire_f…
Alam-2U Apr 22, 2026
028ab29
fix: mysql referential integrity error in update_user during retire_f…
Alam-2U Apr 23, 2026
c98ed5f
feat: update init file
naincy128 Apr 24, 2026
026b249
fix: team posts not appearing on stage (#41)
naincy128 Apr 24, 2026
529576a
Merge pull request #34 from edx/COSMO2-853-forum-instrumentation
santhosh-apphelix-2u Apr 24, 2026
7c9713c
Merge pull request #42 from edx/Naincy/cosmo2-858
santhosh-apphelix-2u Apr 24, 2026
97a97b2
feat: Update __init__.py
Alam-2U Apr 24, 2026
cf9f10f
Merge pull request #43 from edx/ealam/version_bump
santhosh-apphelix-2u Apr 24, 2026
1a4cbc8
feat: emit backend context for forum ban and unban execution
santhosh-apphelix-2u Apr 28, 2026
a7c736e
feat: Bump version to 0.5.5
santhosh-apphelix-2u Apr 28, 2026
2a896e7
feat: Add backend telemetry for discussion moderation and restore APIs
santhosh-apphelix-2u Apr 28, 2026
e71827d
fix: reduce ban_user statements while keeping telemetry
santhosh-apphelix-2u Apr 28, 2026
aeda07f
fix: type telemetry helpers for forum mypy
santhosh-apphelix-2u Apr 28, 2026
71c6020
fix: avoid mypy redef for telemetry helper
santhosh-apphelix-2u Apr 28, 2026
104ecfe
fix: format typed telemetry helper
santhosh-apphelix-2u Apr 28, 2026
484b927
fix: format typed telemetry helper
santhosh-apphelix-2u Apr 28, 2026
3588238
fix: keep forum ban telemetry changes minimal
santhosh-apphelix-2u Apr 28, 2026
c179109
fix: allow ban telemetry in existing large function
santhosh-apphelix-2u Apr 28, 2026
cac55a8
feat: Bump version to 0.5.6
santhosh-apphelix-2u Apr 28, 2026
2c821b1
Merge pull request #44 from edx/COSMO2-853-forum-observability-modera…
santhosh-apphelix-2u Apr 29, 2026
c59b4bf
feat: add native forum v2 thread telemetry
santhosh-apphelix-2u Apr 30, 2026
06b5141
feat: add native forum v2 comment telemetry
santhosh-apphelix-2u Apr 30, 2026
056a2a8
feat: add native forum V2 moderation telemetry
santhosh-apphelix-2u Apr 30, 2026
2ce9962
fix: correct response sorting to respect oldest/newest order in discu…
Alam-2U May 4, 2026
e9d179b
fix: update MySQL logic to use 0 for non-pinned posts instead of NULL…
Alam-2U May 6, 2026
b28fbc4
fix: resolve read/unread state persistence issue (#48)
Alam-2U May 7, 2026
5be5e8c
Merge pull request #45 from edx/COSMO2-920-forum-v2-thread-telemetry
santhosh-apphelix-2u May 8, 2026
b9ca7ae
fix: resolve read/unread state persistence issue for mongo (#49)
Alam-2U May 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion forum/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
Openedx forum app.
"""

__version__ = "0.3.8"
__version__ = "0.6.0"
128 changes: 126 additions & 2 deletions forum/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
UserVote,
Subscription,
MongoContent,
ModerationAuditLog,
DiscussionMuteRecord,
)


Expand Down Expand Up @@ -55,11 +57,12 @@ class CommentThreadAdmin(admin.ModelAdmin): # type: ignore
"context",
"closed",
"pinned",
"is_spam",
"created_at",
"updated_at",
)
search_fields = ("title", "body", "author__username", "course_id")
list_filter = ("thread_type", "context", "closed", "pinned")
list_filter = ("thread_type", "context", "closed", "pinned", "is_spam")


@admin.register(Comment)
Expand All @@ -74,9 +77,10 @@ class CommentAdmin(admin.ModelAdmin): # type: ignore
"updated_at",
"endorsed",
"anonymous",
"is_spam",
)
search_fields = ("body", "author__username", "comment_thread__title")
list_filter = ("endorsed", "anonymous")
list_filter = ("endorsed", "anonymous", "is_spam")


@admin.register(EditHistory)
Expand Down Expand Up @@ -146,9 +150,129 @@ class SubscriptionAdmin(admin.ModelAdmin): # type: ignore
list_filter = ("source_content_type",)


@admin.register(DiscussionMuteRecord)
class DiscussionMuteAdmin(admin.ModelAdmin): # type: ignore
"""Admin interface for DiscussionMuteRecord model."""

list_display = (
"muted_user",
"muted_by",
"course_id",
"scope",
"reason",
"is_active",
"created",
"modified",
)
search_fields = (
"muted_user__username",
"muted_by__username",
"reason",
"course_id",
)
list_filter = ("scope", "is_active", "created", "modified")


@admin.register(MongoContent)
class MongoContentAdmin(admin.ModelAdmin): # type: ignore
"""Admin interface for MongoContent model."""

list_display = ("mongo_id", "content_object_id", "content_type")
search_fields = ("mongo_id",)


@admin.register(ModerationAuditLog)
class ModerationAuditLogAdmin(admin.ModelAdmin): # type: ignore
"""Admin interface for ModerationAuditLog model."""

list_display = (
"timestamp",
"classification",
"actions_taken",
"body_preview",
"original_author",
"moderator_override",
"confidence_score",
)
list_filter = (
"classification",
"moderator_override",
"timestamp",
)
search_fields = (
"original_author__username",
"moderator__username",
"reasoning",
"override_reason",
"body",
)
readonly_fields = (
"timestamp",
"body",
"classifier_output",
"reasoning",
"classification",
"actions_taken",
"confidence_score",
"original_author",
)
fieldsets = (
(
"Moderation Decision",
{
"fields": (
"timestamp",
"classification",
"actions_taken",
"confidence_score",
"reasoning",
)
},
),
("Content Information", {"fields": ("body",)}),
("Author Information", {"fields": ("original_author",)}),
(
"Human Override",
{
"fields": (
"moderator_override",
"moderator",
"override_reason",
)
},
),
(
"Technical Details",
{
"fields": ("classifier_output",),
"classes": ("collapse",),
},
),
)

def body_preview(self, obj): # type: ignore
"""Return a truncated preview of the body for list display."""
if obj.body:
return obj.body[:100] + "..." if len(obj.body) > 100 else obj.body
return "-"

body_preview.short_description = "Body Preview" # type: ignore

# pylint: disable=unused-argument
def has_add_permission(self, request): # type: ignore[no-untyped-def]
"""Disable adding audit logs manually."""
return False

# pylint: disable=unused-argument
def has_delete_permission(self, request, obj=None): # type: ignore[no-untyped-def]
"""Disable deleting audit logs to maintain integrity."""
return False

def get_queryset(self, request): # type: ignore
"""Optimize queryset with related objects."""
return (
super()
.get_queryset(request)
.select_related("original_author", "moderator")
.order_by("-timestamp")
)
Loading