diff --git a/articles/admin.py b/articles/admin.py index bc1873f..f3eeae7 100644 --- a/articles/admin.py +++ b/articles/admin.py @@ -5,7 +5,7 @@ from django.contrib.auth.admin import UserAdmin from django.db import models from django.shortcuts import redirect -from .models import Article, Page, User +from .models import Article, Comment, Page, User admin.site.register(User, UserAdmin) @@ -108,3 +108,17 @@ class PageAdmin(ArticleAdmin): ), ("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) diff --git a/articles/forms.py b/articles/forms.py new file mode 100644 index 0000000..01e56f0 --- /dev/null +++ b/articles/forms.py @@ -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"] diff --git a/articles/migrations/0009_comment.py b/articles/migrations/0009_comment.py new file mode 100644 index 0000000..be14d01 --- /dev/null +++ b/articles/migrations/0009_comment.py @@ -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", + ), + ), + ], + ), + ] diff --git a/articles/migrations/0010_auto_20200818_1825.py b/articles/migrations/0010_auto_20200818_1825.py new file mode 100644 index 0000000..19ac146 --- /dev/null +++ b/articles/migrations/0010_auto_20200818_1825.py @@ -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), + ), + ] diff --git a/articles/migrations/0011_auto_20200818_1829.py b/articles/migrations/0011_auto_20200818_1829.py new file mode 100644 index 0000000..92d823f --- /dev/null +++ b/articles/migrations/0011_auto_20200818_1829.py @@ -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", + ), + ] diff --git a/articles/migrations/0012_auto_20200818_1845.py b/articles/migrations/0012_auto_20200818_1845.py new file mode 100644 index 0000000..9ca121b --- /dev/null +++ b/articles/migrations/0012_auto_20200818_1845.py @@ -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), + ), + ] diff --git a/articles/models.py b/articles/models.py index 224de0b..b64953a 100644 --- a/articles/models.py +++ b/articles/models.py @@ -92,8 +92,26 @@ class Page(Article): ordering = ["position", "-published_at"] -# class Comment(models.Model): -# username = models.CharField(max_length=255) -# email = models.EmailField(blank=True, null=True) -# content = models.TextField() -# article = models.ForeignKey(Article, on_delete=models.CASCADE) +class Comment(models.Model): + username = models.CharField( + max_length=255, help_text="Will be displayed with your comment." + ) + 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"] diff --git a/articles/static/style.css b/articles/static/style.css index 2514b89..2535c22 100644 --- a/articles/static/style.css +++ b/articles/static/style.css @@ -75,6 +75,10 @@ a:hover, a:focus { margin-bottom: .2em; } +.article-detail h1 { + font-size: 2em; +} + .date { margin-top: .2em; color: var(--main2); diff --git a/articles/templates/articles/article_detail.html b/articles/templates/articles/article_detail.html index 04b4f68..a7b3af1 100644 --- a/articles/templates/articles/article_detail.html +++ b/articles/templates/articles/article_detail.html @@ -4,11 +4,34 @@ {{ article.title }} {% endblock %} {% block content %} -
+

{{ article.title }}{% if article.status != article.PUBLISHED %} ({{ article.status }}){% endif %}

{% include "articles/metadata_snippet.html" %}
{{ article.get_formatted_content|safe }}
-
+ +
+

Comments

+
+ {% csrf_token %} + {{ comment_form.as_p }} + +
+ {% for comment in comments %} +
+ +

+ {{ comment.content }} +

+
+ {% empty %} + No reaction yet, write your own! + {% endfor %} +
{% endblock %} diff --git a/articles/templates/articles/base.html b/articles/templates/articles/base.html index 2fa4b41..a5fa43a 100644 --- a/articles/templates/articles/base.html +++ b/articles/templates/articles/base.html @@ -28,6 +28,13 @@ {% endfor %} {% endif %} +{% if messages %} +
+ {% for message in messages %} +

{{ message }}

+ {% endfor %} +
+{% endif %}
{% block content %} {% endblock %} diff --git a/articles/views/html.py b/articles/views/html.py index d05d4bb..2016cf5 100644 --- a/articles/views/html.py +++ b/articles/views/html.py @@ -1,7 +1,10 @@ +from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin from django.db.models import F +from django.http import HttpResponseRedirect from django.views import generic +from articles.forms import CommentForm from articles.models import Article @@ -38,9 +41,19 @@ class ArticleDetailView(generic.DetailView): template_name = "articles/article_detail.html" def get_queryset(self): + queryset = super().get_queryset() if self.request.user.is_authenticated: - return super().get_queryset() - return super().get_queryset().filter(status=Article.PUBLISHED) + return queryset + 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): obj = super().get_object(queryset) @@ -51,3 +64,16 @@ class ArticleDetailView(generic.DetailView): obj.save(update_fields=["views_count"]) 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()) diff --git a/blog/urls.py b/blog/urls.py index 2ace0a2..cb56bdd 100644 --- a/blog/urls.py +++ b/blog/urls.py @@ -22,6 +22,10 @@ urlpatterns = [ path("admin/", admin.site.urls), path("", html.ArticlesListView.as_view(), name="articles-list"), path("drafts/", html.DraftsListView.as_view(), name="drafts-list"), - path("", html.ArticleDetailView.as_view(), name="article-detail"), path("feed/", feeds.CompleteFeed(), name="complete-feed"), + path("", html.ArticleDetailView.as_view(), name="article-detail"), + path("/", html.ArticleDetailView.as_view(), name="article-detail"), + path( + "/comment/", html.CreateCommentView.as_view(), name="create-comment" + ), ]