Skip to content

Commit b61d52b

Browse files
Share and rating feedback (#1761)
Co-authored-by: Kushvah Ajaysingh
1 parent c6c5e26 commit b61d52b

22 files changed

+3544
-2562
lines changed

home/admin.py

+27-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
from django.contrib import admin
22
from wagtail.contrib.modeladmin.options import ModelAdmin, modeladmin_register
3+
from django.db.models import Avg, Count
4+
from django.utils.html import format_html
5+
from django.urls import reverse
36

4-
from home.models import ManifestSettings, SVGToPNGMap
7+
from home.models import ManifestSettings, SVGToPNGMap, Article, ArticleFeedback
58

69

710
class ManifestSettingsAdmin(ModelAdmin):
@@ -21,3 +24,26 @@ class SVGToPNGMapAdmin(admin.ModelAdmin):
2124
list_display = ('id', 'svg_path', 'fill_color', 'stroke_color', 'png_image_file')
2225

2326

27+
class ArticleAdmin(ModelAdmin):
28+
model = Article
29+
menu_label = "Article Ratings"
30+
menu_icon = "form"
31+
list_display = ("title", "average_rating", "number_of_reviews", "view_feedback_button")
32+
search_fields = ("title",)
33+
ordering = ("-average_rating", "-number_of_reviews") # Sort by rating & reviews in descending order
34+
35+
def view_feedback_button(self, obj):
36+
"""
37+
Creates a button to view feedback for an article.
38+
"""
39+
url = reverse("admin_article_feedback", args=[obj.id])
40+
return format_html('<a class="button button-small" href="{}">View Feedback</a>', url)
41+
view_feedback_button.short_description = "Feedback"
42+
43+
def has_add_permission(self, request):
44+
"""
45+
Hide the 'Add Article' button in the Wagtail admin panel.
46+
"""
47+
return False # Prevents adding new articles from this page
48+
49+
modeladmin_register(ArticleAdmin)

home/apps.py

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from django.apps import AppConfig
2+
3+
class HomeConfig(AppConfig):
4+
default_auto_field = "django.db.models.BigAutoField"
5+
name = "home"
6+
7+
def ready(self):
8+
import home.signals # Ensure signals are loaded
+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Generated by Django 3.2.25 on 2025-02-26 05:18
2+
3+
from django.conf import settings
4+
from django.db import migrations, models
5+
import django.db.models.deletion
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
dependencies = [
11+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
12+
('home', '0055_enable_use_json_field'),
13+
]
14+
15+
operations = [
16+
migrations.CreateModel(
17+
name='ArticleFeedback',
18+
fields=[
19+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
20+
('session_id', models.CharField(blank=True, max_length=255, null=True)),
21+
('rating', models.IntegerField(choices=[(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)])),
22+
('feedback', models.TextField(blank=True, null=True)),
23+
('created_at', models.DateTimeField(auto_now_add=True)),
24+
('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='feedbacks', to='home.article')),
25+
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
26+
],
27+
options={
28+
'unique_together': {('article', 'user')},
29+
},
30+
),
31+
]
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Generated by Django 3.2.25 on 2025-02-27 08:29
2+
3+
from django.db import migrations, models
4+
import django.db.models.deletion
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('wagtailcore', '0069_log_entry_jsonfield'),
11+
('home', '0056_articlefeedback'),
12+
]
13+
14+
operations = [
15+
migrations.CreateModel(
16+
name='FeedbackSettings',
17+
fields=[
18+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
19+
('enable_feedback', models.BooleanField(default=True, verbose_name='Enable Feedback')),
20+
('site', models.OneToOneField(editable=False, on_delete=django.db.models.deletion.CASCADE, to='wagtailcore.site')),
21+
],
22+
options={
23+
'verbose_name': 'Rating Settings',
24+
},
25+
),
26+
]
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Generated by Django 3.2.25 on 2025-03-06 13:11
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('home', '0057_feedbacksettings'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='article',
15+
name='average_rating',
16+
field=models.FloatField(default=0.0, null=True),
17+
),
18+
migrations.AddField(
19+
model_name='article',
20+
name='number_of_reviews',
21+
field=models.PositiveIntegerField(default=0, null=True),
22+
),
23+
]

home/models.py

+53
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
get_all_renditions_urls,
5555
)
5656
import iogt.iogt_globals as globals_
57+
from django.db.models import Avg, Count
5758

5859
User = get_user_model()
5960
logger = logging.getLogger(__name__)
@@ -432,9 +433,26 @@ class Meta:
432433
verbose_name_plural = _("articles")
433434

434435

436+
@register_setting
437+
class FeedbackSettings(BaseSetting):
438+
"""Global setting to enable or disable feedback submission for all articles."""
439+
enable_feedback = models.BooleanField(default=True, verbose_name="Enable Feedback")
440+
441+
panels = [
442+
FieldPanel("enable_feedback"),
443+
]
444+
445+
class Meta:
446+
verbose_name = "Rating Settings"
447+
448+
435449
class Article(AbstractArticle):
436450
tags = ClusterTaggableManager(through='ArticleTaggedItem', blank=True)
437451

452+
# New fields for precomputed values
453+
average_rating = models.FloatField(default=0.0, null=True)
454+
number_of_reviews = models.PositiveIntegerField(default=0, null=True)
455+
438456
content_panels = AbstractArticle.content_panels + [
439457
MultiFieldPanel([
440458
InlinePanel('recommended_articles',
@@ -463,13 +481,48 @@ def get_context(self, request):
463481
for recommended_article in self.recommended_articles.all() if recommended_article.article.live
464482
]
465483

484+
# Fetch feedback setting correctly
485+
feedback_settings = FeedbackSettings.for_request(request)
486+
context["feedback_enabled"] = feedback_settings.enable_feedback
487+
488+
# Get the latest 5 feedbacks
489+
context["feedbacks"] = self.feedbacks.order_by('-created_at')[:3]
466490
return context
467491

468492
def serve(self, request):
469493
response = super().serve(request)
470494
if response.status_code == status.HTTP_200_OK:
471495
User.record_article_read(request=request, article=self)
472496
return response
497+
498+
def update_feedback_metrics(self):
499+
"""
500+
Updates the average rating and number of reviews for this article.
501+
"""
502+
feedback_stats = self.feedbacks.aggregate(
503+
avg_rating=Avg("rating"), review_count=Count("id")
504+
)
505+
self.average_rating = feedback_stats["avg_rating"] or 0.0
506+
self.number_of_reviews = feedback_stats["review_count"] or 0
507+
self.save(update_fields=["average_rating", "number_of_reviews"])
508+
509+
def compute_average_rating(self):
510+
return self.average_rating if self.average_rating else 0
511+
512+
def compute_number_of_reviews(self):
513+
return self.number_of_reviews if self.number_of_reviews else 0
514+
515+
516+
class ArticleFeedback(models.Model):
517+
article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='feedbacks')
518+
user = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL)
519+
session_id = models.CharField(max_length=255, null=True, blank=True)
520+
rating = models.IntegerField(choices=[(i, i) for i in range(1, 6)])
521+
feedback = models.TextField(blank=True, null=True)
522+
created_at = models.DateTimeField(auto_now_add=True)
523+
524+
class Meta:
525+
unique_together = ('article', 'user')
473526

474527

475528
class MiscellaneousIndexPage(Page):

home/signals.py

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from django.db.models.signals import post_save, post_delete
2+
from django.dispatch import receiver
3+
from .models import ArticleFeedback
4+
5+
@receiver(post_save, sender=ArticleFeedback)
6+
@receiver(post_delete, sender=ArticleFeedback)
7+
def update_article_feedback_metrics(sender, instance, **kwargs):
8+
"""
9+
Update the article's feedback metrics whenever a feedback entry is added, updated, or deleted.
10+
"""
11+
if instance.article:
12+
instance.article.update_feedback_metrics()

home/static/css/custom_admin.css

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/* Hide the "Add Article" button in the Wagtail admin */
2+
a[href*="/admin/home/article/create/"] {
3+
display: none !important;
4+
}

home/static/icons/email.png

1.33 KB
Loading

home/static/icons/facebook.png

414 Bytes
Loading

home/static/icons/link.png

636 Bytes
Loading

home/static/icons/linkedin.png

534 Bytes
Loading

home/static/icons/whatsapp.png

1.87 KB
Loading

home/static/icons/x.png

1.7 KB
Loading

0 commit comments

Comments
 (0)