diff --git a/app/comments/forms.py b/app/comments/forms.py index 8f7fd657..03c3d8a9 100644 --- a/app/comments/forms.py +++ b/app/comments/forms.py @@ -5,7 +5,7 @@ from django.core.exceptions import ValidationError from django.urls import reverse -from .models import Comment +from .models import Comment, NewsComment User = get_user_model() @@ -54,3 +54,39 @@ def save(self, commit=True) -> Comment: if commit: new.save() return new + + +class NewsCommentForm(forms.Form): + content = forms.CharField(widget=forms.Textarea(attrs={"rows": 4})) + + def __init__(self, request, news, *args, **kwargs): + super().__init__(*args, **kwargs) + self.request = request + self.news = news + + self.helper = FormHelper() + self.helper.form_method = "post" + self.helper.form_class = "" + self.helper.form_show_errors = False + self.helper.form_show_labels = False + self.helper.form_action = reverse( + "comments:news_add", kwargs={"slug": news.slug} + ) + self.helper.add_input(Submit("submit", "Add comment")) + + def clean_content(self) -> str: + content = self.cleaned_data.get("content") + if len(content) > 2000: + raise ValidationError( + "Your comment must be less than 2000 characters long." + ) + return content + + def save(self, commit=True) -> Comment: + content = self.cleaned_data.get("content") + new = NewsComment.objects.create( + content=content, news=self.news, author=self.request.user + ) + if commit: + new.save() + return new diff --git a/app/comments/migrations/0002_alter_comment_options_newscomment.py b/app/comments/migrations/0002_alter_comment_options_newscomment.py new file mode 100644 index 00000000..02506559 --- /dev/null +++ b/app/comments/migrations/0002_alter_comment_options_newscomment.py @@ -0,0 +1,62 @@ +# Generated by Django 4.2.6 on 2023-10-27 02:53 + +import uuid + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("core", "0004_alter_news_slug"), + ("comments", "0001_initial"), + ] + + operations = [ + migrations.AlterModelOptions( + name="comment", + options={"ordering": ["-added"], "verbose_name_plural": "trip comments"}, + ), + migrations.CreateModel( + name="NewsComment", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("content", models.TextField()), + ( + "uuid", + models.UUIDField(default=uuid.uuid4, editable=False, unique=True), + ), + ("added", models.DateTimeField(auto_now_add=True)), + ("updated", models.DateTimeField(auto_now=True)), + ( + "article", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="comments", + to="core.news", + ), + ), + ( + "author", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "verbose_name_plural": "news comments", + "ordering": ["-added"], + }, + ), + ] diff --git a/app/comments/migrations/0003_rename_article_newscomment_news.py b/app/comments/migrations/0003_rename_article_newscomment_news.py new file mode 100644 index 00000000..46ed55c4 --- /dev/null +++ b/app/comments/migrations/0003_rename_article_newscomment_news.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.6 on 2023-10-27 03:10 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("comments", "0002_alter_comment_options_newscomment"), + ] + + operations = [ + migrations.RenameField( + model_name="newscomment", + old_name="article", + new_name="news", + ), + ] diff --git a/app/comments/models.py b/app/comments/models.py index 0a194e65..59222b92 100644 --- a/app/comments/models.py +++ b/app/comments/models.py @@ -1,24 +1,46 @@ import uuid +from core.models import News from django.conf import settings from django.db import models from logger.models import Trip -class Comment(models.Model): +class BaseComment(models.Model): content = models.TextField() author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) - trip = models.ForeignKey(Trip, on_delete=models.CASCADE, related_name="comments") uuid = models.UUIDField(unique=True, editable=False, default=uuid.uuid4) added = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) + class Meta: + abstract = True + + +class Comment(BaseComment): + trip = models.ForeignKey(Trip, on_delete=models.CASCADE, related_name="comments") + class Meta: ordering = ["-added"] + verbose_name_plural = "trip comments" def __str__(self): return f"Comment by {self.author} on {self.trip}" def get_absolute_url(self): return self.trip.get_absolute_url() + + +class NewsComment(BaseComment): + news = models.ForeignKey(News, on_delete=models.CASCADE, related_name="comments") + + class Meta: + ordering = ["-added"] + verbose_name_plural = "news comments" + + def __str__(self): + return f"Comment by {self.author} on {self.article.title}" + + def get_absolute_url(self): + return self.article.get_absolute_url() diff --git a/app/comments/urls.py b/app/comments/urls.py index e82732f5..c8630f48 100644 --- a/app/comments/urls.py +++ b/app/comments/urls.py @@ -7,4 +7,10 @@ path("add//", views.AddComment.as_view(), name="add"), path("delete//", views.DeleteComment.as_view(), name="delete"), path("htmx//", views.HTMXTripComment.as_view(), name="htmx_comments"), + path("news/add//", views.AddNewsComment.as_view(), name="news_add"), + path( + "news/delete//", + views.DeleteNewsComment.as_view(), + name="news_delete", + ), ] diff --git a/app/comments/views.py b/app/comments/views.py index b9a57292..1ab7a016 100644 --- a/app/comments/views.py +++ b/app/comments/views.py @@ -1,6 +1,7 @@ -from comments.forms import CommentForm -from comments.models import Comment +from comments.forms import CommentForm, NewsCommentForm +from comments.models import Comment, NewsComment from core.logging import log_trip_action +from core.models import News from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin from django.core.exceptions import PermissionDenied @@ -112,3 +113,41 @@ def post(self, request, uuid): else: raise PermissionDenied return redirect(comment.trip.get_absolute_url()) + + +class AddNewsComment(LoginRequiredMixin, View): + def post(self, request, slug): + news = get_object_or_404(News, slug=slug) + form = NewsCommentForm(self.request, news, request.POST) + + if form.is_valid(): + form.save() + messages.success( + request, + "Your comment has been added.", + ) + else: + if form.errors.get("content", None): + for error in form.errors["content"]: + messages.error(request, error) + else: + messages.error( + request, + "There was an error adding your comment. Please try again.", + ) + + return redirect(news.get_absolute_url()) + + +class DeleteNewsComment(LoginRequiredMixin, View): + def post(self, request, uuid): + comment = get_object_or_404(NewsComment, uuid=uuid) + if comment.author == request.user or request.user.is_superuser: + comment.delete() + messages.success( + request, + "The comment has been deleted.", + ) + else: + raise PermissionDenied + return redirect(comment.news.get_absolute_url()) diff --git a/app/core/views.py b/app/core/views.py index 6be03124..983a24b4 100644 --- a/app/core/views.py +++ b/app/core/views.py @@ -1,3 +1,4 @@ +from comments.forms import NewsCommentForm from django.contrib.auth import get_user_model from django.views.generic import DetailView, TemplateView @@ -32,6 +33,11 @@ class NewsDetail(DetailView): template_name = "core/news_detail.html" context_object_name = "news" + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["comment_form"] = NewsCommentForm(self.request, self.object) + return context + class HTTP400(TemplateView): template_name = "400.html" diff --git a/app/templates/comments/_comments.html b/app/templates/comments/_comments.html index fca3538d..20d5e0c3 100644 --- a/app/templates/comments/_comments.html +++ b/app/templates/comments/_comments.html @@ -2,9 +2,9 @@ {% load crispy_forms_tags %} {% load markdownify %} -{% if trip.user.allow_comments %} - {% if trip.comments.count > 0 %} - {% for comment in trip.comments.all %} +{% if comment_obj.user.allow_comments or comment_obj_type == "news" %} + {% if comment_obj.comments.count > 0 %} + {% for comment in comment_obj.comments.all %} {% if forloop.counter > 1 %}
{% endif %}
@@ -23,7 +23,7 @@

{% if user.is_authenticated %} - {% if user == comment.author or user == trip.user or user.is_superuser %} + {% if user == comment.author or user == comment_obj.user or user.is_superuser %}