Add basis for a comment system
This commit is contained in:
parent
fb2eae301a
commit
698a5ca30f
12 changed files with 233 additions and 11 deletions
|
@ -5,7 +5,7 @@ from django.contrib.auth.admin import UserAdmin
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
|
|
||||||
from .models import Article, Page, User
|
from .models import Article, Comment, Page, User
|
||||||
|
|
||||||
admin.site.register(User, UserAdmin)
|
admin.site.register(User, UserAdmin)
|
||||||
|
|
||||||
|
@ -108,3 +108,17 @@ class PageAdmin(ArticleAdmin):
|
||||||
),
|
),
|
||||||
("Content", {"fields": ("content",)}),
|
("Content", {"fields": ("content",)}),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@register(Comment)
|
||||||
|
class CommentAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ("username", "email", "content", "article", "created_at", "approved")
|
||||||
|
list_filter = ("approved",)
|
||||||
|
search_fields = ("username", "email", "content")
|
||||||
|
actions = ["approve_comments", "censor_comments"]
|
||||||
|
|
||||||
|
def approve_comments(self, request, queryset):
|
||||||
|
queryset.update(approved=True)
|
||||||
|
|
||||||
|
def censor_comments(self, request, queryset):
|
||||||
|
queryset.update(approved=False)
|
||||||
|
|
9
articles/forms.py
Normal file
9
articles/forms.py
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
from django import forms
|
||||||
|
|
||||||
|
from articles.models import Comment
|
||||||
|
|
||||||
|
|
||||||
|
class CommentForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Comment
|
||||||
|
fields = ["username", "email", "content"]
|
59
articles/migrations/0009_comment.py
Normal file
59
articles/migrations/0009_comment.py
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
# Generated by Django 3.1 on 2020-08-18 16:05
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("articles", "0008_auto_20200817_1748"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Comment",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.AutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"username",
|
||||||
|
models.CharField(
|
||||||
|
help_text="Will be displayed with your comment.", max_length=255
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"email",
|
||||||
|
models.EmailField(
|
||||||
|
blank=True,
|
||||||
|
help_text="Not mandatory, fill only if you want me to be able to contact you. Will never be displayed here nor shared with any third party.",
|
||||||
|
max_length=254,
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"content",
|
||||||
|
models.TextField(
|
||||||
|
help_text="Your comment, limited to 500 characters.",
|
||||||
|
max_length=500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||||
|
(
|
||||||
|
"article",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="comments",
|
||||||
|
to="articles.article",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
21
articles/migrations/0010_auto_20200818_1825.py
Normal file
21
articles/migrations/0010_auto_20200818_1825.py
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# Generated by Django 3.1 on 2020-08-18 16:25
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("articles", "0009_comment"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name="comment", options={"ordering": ["-created_at"]},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="comment",
|
||||||
|
name="active",
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
]
|
16
articles/migrations/0011_auto_20200818_1829.py
Normal file
16
articles/migrations/0011_auto_20200818_1829.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# Generated by Django 3.1 on 2020-08-18 16:29
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("articles", "0010_auto_20200818_1825"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name="comment", old_name="active", new_name="approved",
|
||||||
|
),
|
||||||
|
]
|
21
articles/migrations/0012_auto_20200818_1845.py
Normal file
21
articles/migrations/0012_auto_20200818_1845.py
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# Generated by Django 3.1 on 2020-08-18 16:45
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("articles", "0011_auto_20200818_1829"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name="comment", options={"ordering": ["created_at"]},
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="comment",
|
||||||
|
name="approved",
|
||||||
|
field=models.BooleanField(default=True),
|
||||||
|
),
|
||||||
|
]
|
|
@ -92,8 +92,26 @@ class Page(Article):
|
||||||
ordering = ["position", "-published_at"]
|
ordering = ["position", "-published_at"]
|
||||||
|
|
||||||
|
|
||||||
# class Comment(models.Model):
|
class Comment(models.Model):
|
||||||
# username = models.CharField(max_length=255)
|
username = models.CharField(
|
||||||
# email = models.EmailField(blank=True, null=True)
|
max_length=255, help_text="Will be displayed with your comment."
|
||||||
# content = models.TextField()
|
)
|
||||||
# article = models.ForeignKey(Article, on_delete=models.CASCADE)
|
email = models.EmailField(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
help_text=(
|
||||||
|
"Not mandatory, fill only if you want me to be able to contact you. "
|
||||||
|
"Will never be displayed here nor shared with any third party."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
content = models.TextField(
|
||||||
|
max_length=500, help_text="Your comment, limited to 500 characters."
|
||||||
|
)
|
||||||
|
article = models.ForeignKey(
|
||||||
|
Article, on_delete=models.CASCADE, related_name="comments"
|
||||||
|
)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
approved = models.BooleanField(default=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ["created_at"]
|
||||||
|
|
|
@ -75,6 +75,10 @@ a:hover, a:focus {
|
||||||
margin-bottom: .2em;
|
margin-bottom: .2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.article-detail h1 {
|
||||||
|
font-size: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
.date {
|
.date {
|
||||||
margin-top: .2em;
|
margin-top: .2em;
|
||||||
color: var(--main2);
|
color: var(--main2);
|
||||||
|
|
|
@ -4,11 +4,34 @@
|
||||||
{{ article.title }}
|
{{ article.title }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="article-detail">
|
<article class="article-detail">
|
||||||
<h1>{{ article.title }}{% if article.status != article.PUBLISHED %} ({{ article.status }}){% endif %}</h1>
|
<h1>{{ article.title }}{% if article.status != article.PUBLISHED %} ({{ article.status }}){% endif %}</h1>
|
||||||
{% include "articles/metadata_snippet.html" %}
|
{% include "articles/metadata_snippet.html" %}
|
||||||
<div>
|
<div>
|
||||||
{{ article.get_formatted_content|safe }}
|
{{ article.get_formatted_content|safe }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</article>
|
||||||
|
<section class="comments">
|
||||||
|
<h2>Comments</h2>
|
||||||
|
<form action="{% url 'create-comment' slug=article.slug %}" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ comment_form.as_p }}
|
||||||
|
<button type="submit">Submit</button>
|
||||||
|
</form>
|
||||||
|
{% for comment in comments %}
|
||||||
|
<article class="comment">
|
||||||
|
<p class="metadata">
|
||||||
|
{{ comment.username }} |
|
||||||
|
<time datetime="{{ comment.created_at|date:'c' }}">
|
||||||
|
{{ comment.created_at|date:"DATETIME_FORMAT" }}
|
||||||
|
</time>
|
||||||
|
</p>
|
||||||
|
<p class="content">
|
||||||
|
{{ comment.content }}
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
{% empty %}
|
||||||
|
No reaction yet, write your own!
|
||||||
|
{% endfor %}
|
||||||
|
</section>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -28,6 +28,13 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</nav>
|
</nav>
|
||||||
|
{% if messages %}
|
||||||
|
<div class="messages">
|
||||||
|
{% for message in messages %}
|
||||||
|
<p>{{ message }}</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<div class="content">
|
<div class="content">
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
|
from django.contrib import messages
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.db.models import F
|
from django.db.models import F
|
||||||
|
from django.http import HttpResponseRedirect
|
||||||
from django.views import generic
|
from django.views import generic
|
||||||
|
|
||||||
|
from articles.forms import CommentForm
|
||||||
from articles.models import Article
|
from articles.models import Article
|
||||||
|
|
||||||
|
|
||||||
|
@ -38,9 +41,19 @@ class ArticleDetailView(generic.DetailView):
|
||||||
template_name = "articles/article_detail.html"
|
template_name = "articles/article_detail.html"
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
queryset = super().get_queryset()
|
||||||
if self.request.user.is_authenticated:
|
if self.request.user.is_authenticated:
|
||||||
return super().get_queryset()
|
return queryset
|
||||||
return super().get_queryset().filter(status=Article.PUBLISHED)
|
return queryset.filter(status=Article.PUBLISHED)
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(ArticleDetailView, self).get_context_data(**kwargs)
|
||||||
|
article = self.object
|
||||||
|
if hasattr(article, "article"):
|
||||||
|
article = article.article
|
||||||
|
context["comments"] = article.comments.filter(approved=True)
|
||||||
|
context["comment_form"] = CommentForm()
|
||||||
|
return context
|
||||||
|
|
||||||
def get_object(self, queryset=None):
|
def get_object(self, queryset=None):
|
||||||
obj = super().get_object(queryset)
|
obj = super().get_object(queryset)
|
||||||
|
@ -51,3 +64,16 @@ class ArticleDetailView(generic.DetailView):
|
||||||
obj.save(update_fields=["views_count"])
|
obj.save(update_fields=["views_count"])
|
||||||
|
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
class CreateCommentView(generic.CreateView):
|
||||||
|
model = Article
|
||||||
|
form_class = CommentForm
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
self.object = self.get_object()
|
||||||
|
self.comment = form.save(commit=False)
|
||||||
|
self.comment.article = self.object
|
||||||
|
self.comment.save()
|
||||||
|
messages.success(self.request, "Comment successfully saved.")
|
||||||
|
return HttpResponseRedirect(self.get_success_url())
|
||||||
|
|
|
@ -22,6 +22,10 @@ urlpatterns = [
|
||||||
path("admin/", admin.site.urls),
|
path("admin/", admin.site.urls),
|
||||||
path("", html.ArticlesListView.as_view(), name="articles-list"),
|
path("", html.ArticlesListView.as_view(), name="articles-list"),
|
||||||
path("drafts/", html.DraftsListView.as_view(), name="drafts-list"),
|
path("drafts/", html.DraftsListView.as_view(), name="drafts-list"),
|
||||||
path("<slug:slug>", html.ArticleDetailView.as_view(), name="article-detail"),
|
|
||||||
path("feed/", feeds.CompleteFeed(), name="complete-feed"),
|
path("feed/", feeds.CompleteFeed(), name="complete-feed"),
|
||||||
|
path("<slug:slug>", html.ArticleDetailView.as_view(), name="article-detail"),
|
||||||
|
path("<slug:slug>/", html.ArticleDetailView.as_view(), name="article-detail"),
|
||||||
|
path(
|
||||||
|
"<slug:slug>/comment/", html.CreateCommentView.as_view(), name="create-comment"
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
Reference in a new issue