Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow comments on news articles (closes #197) #200

Merged
merged 1 commit into from
Oct 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
38 changes: 37 additions & 1 deletion app/comments/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -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
62 changes: 62 additions & 0 deletions app/comments/migrations/0002_alter_comment_options_newscomment.py
Original file line number Diff line number Diff line change
@@ -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"],
},
),
]
17 changes: 17 additions & 0 deletions app/comments/migrations/0003_rename_article_newscomment_news.py
Original file line number Diff line number Diff line change
@@ -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",
),
]
26 changes: 24 additions & 2 deletions app/comments/models.py
Original file line number Diff line number Diff line change
@@ -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()
6 changes: 6 additions & 0 deletions app/comments/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,10 @@
path("add/<uuid:uuid>/", views.AddComment.as_view(), name="add"),
path("delete/<uuid:uuid>/", views.DeleteComment.as_view(), name="delete"),
path("htmx/<uuid:uuid>/", views.HTMXTripComment.as_view(), name="htmx_comments"),
path("news/add/<slug:slug>/", views.AddNewsComment.as_view(), name="news_add"),
path(
"news/delete/<uuid:uuid>/",
views.DeleteNewsComment.as_view(),
name="news_delete",
),
]
43 changes: 41 additions & 2 deletions app/comments/views.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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())
6 changes: 6 additions & 0 deletions app/core/views.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from comments.forms import NewsCommentForm
from django.contrib.auth import get_user_model
from django.views.generic import DetailView, TemplateView

Expand Down Expand Up @@ -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"
Expand Down
10 changes: 5 additions & 5 deletions app/templates/comments/_comments.html
Original file line number Diff line number Diff line change
Expand Up @@ -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 %}<hr class="my-4">{% endif %}

<div class="m-0 comment-display">
Expand All @@ -23,7 +23,7 @@
</p>
</div>
{% 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 %}
<div class="modal fade" id="deleteComment{{ comment.uuid }}" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
Expand All @@ -36,7 +36,7 @@ <h1 class="modal-title fs-5">Delete comment</h1>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<form action="{% url 'comments:delete' comment.uuid %}" method="post">
{% if comment_obj_type == "news" %}<form action="{% url 'comments:news_delete' comment.uuid %}" method="post">{% else %}<form action="{% url 'comments:delete' comment.uuid %}" method="post">{% endif %}
{% csrf_token %}
<button type="submit" class="btn btn-danger">Delete comment</button>
</form>
Expand Down
2 changes: 1 addition & 1 deletion app/templates/comments/_comments_card.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
</div>

<div class="card-body">
{% include "comments/_comments.html" %}
{% include "comments/_comments.html" with comment_obj=trip %}
</div>
</div>
{% endif %}
2 changes: 1 addition & 1 deletion app/templates/core/_news_item.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
{% load markdownify %}
{% load humanize %}

<div class="mb-5">
<div class="mb-3">
<h4 class="subtitle fs-3 mb-1 pb-1" id="news{{ news.id }}">
<a class="text-decoration-none text-black" href="{{ news.get_absolute_url }}">
{{ news.title }}
Expand Down
14 changes: 13 additions & 1 deletion app/templates/core/news.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,19 @@
{% block main %}
<div class="mw-45">
{% for item in news %}
{% include "core/_news_item.html" with news=item %}
<article class="mb-5">
{% include "core/_news_item.html" with news=item %}

<a class="link-primary text-decoration-none" href="{{ item.get_absolute_url }}">
<i class="bi bi-box-arrow-up-right me-1"></i> View article
</a>

<span class="mx-2">&middot;</span>

<a class="link-primary text-decoration-none" href="{{ item.get_absolute_url }}">
<i class="bi bi-chat me-1"></i> {{ item.comments.all|length }} comment{{ item.comments.all|length|pluralize }}
</a>
</article>
{% endfor %}
</div>
{% endblock %}
7 changes: 5 additions & 2 deletions app/templates/core/news_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@
{% load markdownify %}

{% block title %}{{ news.title }}{% endblock %}
{% block display_title %}News{% endblock %}
{% block display_title_container %}{% endblock %}
{% block description %}Information about the latest updates to caves.app.{% endblock %}

{% block main %}
<div class="mw-45">
{% include "core/_news_item.html" %}

<a href="{% url 'core:news' %}" class="btn btn-primary"><i class="bi bi-arrow-left"></i>&nbsp; Back to all news</a>
<h5 class="mb-3 mt-5">Comments</h5>
{% include "comments/_comments.html" with comment_obj=news comment_obj_type="news" %}

<a href="{% url 'core:news' %}" class="btn btn-outline-primary mt-3"><i class="bi bi-arrow-left"></i>&nbsp; Back to all news</a>
</div>
{% endblock %}
2 changes: 1 addition & 1 deletion app/templates/logger/trip_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,6 @@ <h5 class="m-0">
</h5>
{% include "logger/_htmx_trip_follow.html" %}
</div>
{% include "comments/_comments.html" %}
{% include "comments/_comments.html" with comment_obj=trip %}
{% endif %}
{% endblock %}