Comments#
Introduction#
Now that we have articles, we need comments. Gotta give our users a voice, right?
Model#
Comments are a whole new object for our app, so we need to create a model.
A comment will need a related article, an author, a body, and a date.
Let's create a Comment
model in articles/models.py
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
We've been through this before, so we can be quick:
- Our
article
field is a many-to-one relationship that relates theComment
model to theArticle
model. By default, Django uses thepk
of the related object, but we're working withslug_uuid
fields in this tutorial, henceto_field="slug_uuid"
. - The
author
is a many-to-one relationship that relatesComment
toProfile
. - The string representation of our model (
__str__
) is the truncated comment body. - The canonical URL for a
Comment
instance is the same as the URL for theArticle
instance attached to the comment: we don't need to navigate to specific comments in Conduit, so we might as well have a simplifiedget_absolute_url
.
Time to makemigrations
and migrate
. You should get the following error:
SystemCheckError: System check identified some issues:
ERRORS: articles.Comment.article: (fields.E311) ‘Article.slug_uuid’ must be unique because it is referenced by a foreign key. HINT: Add unique=True to this field or add a UniqueConstraint (without condition) in the model Meta.constraints.
That's because we're using articles' slug_uuid
fields as ForeignKeys for the comments (so that we can filter our comments by the attached articles' slug_uuid
fields instead of their UUIDs). As the error message indicates, this error is easily corrected by adding unique=True
as an argument to the slug_uuid
field in the Article
model in articles/models.py
.
1 2 3 4 5 6 7 |
|
You should be able to makemigrations
and migrate
after that.
1 2 3 4 5 6 7 8 9 10 |
|
Now, we need to register our model in articles/admin.py
, so that we can access that object in the Django admin:
1 2 3 4 5 |
|
Viewing comments#
In order to view comments, we need to have some comments to view first: go to the Django admin and create a few comments for a couple articles by hand.
We want to be able to view the comments on each article's detail page. Consequently, we need to modify templates/article_detail.html
(based on Svelte implementation's [[https://github.com/sveltejs/realworld/blob/master/src/routes/article/%5Bslug%5D/index.svelte][article/[slug]/index.svelte]]):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Now create templates/comment_container.html
and add the following (based on Svelte implementation's [[https://github.com/sveltejs/realworld/blob/master/src/routes/article/\slug\/_CommentContainer.svelte][_CommentContainer.svelte]]):
1 2 3 4 5 |
|
We want to view all the comments for the article we're viewing, from most to least recent (which we achieve with the dictsortreversed
template filter).
We will implement the rendering logic for our comments in templates/comment.html
(based on Svelte implementation's [[https://github.com/sveltejs/realworld/blob/master/src/routes/article/\slug\/_Comment.svelte][_Comment.svelte]]):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Nothing new here: this template displays the comment's body, author's username, and creation date.
Try navigating to one of the articles that you created comments for through the Django admin: you should see them displayed under the article (though they made do with some decoration, which we will get to later).
Creating comments#
We will now start allowing our users to leave comments on the website.
We could do this like in the Django Girls tutorial: the ArticleDetailView
would include a button that would direct to CommentCreateView
on a separate page, and saving the comment would bring the user back to the ArticleDetailView
. However, the RealWorldApp
allows users to create and save their comments directly below the article, on the same page, so that's what we're going to try.
Surprisingly, this is not straightforward to implement in Django, because it implies mixing DetailView
and CreateView
functionalities in a single page, which is made difficult by the fact that the DetailView
doesn't have a POST method, while the CreateView
requires it.
Create CommentCreateView
#
Fortunately, our use case is covered in the Django documentation, which greatly simplifies the task at hand.
First, we'll create a CommentCreateView
in users/views.py
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
We have seen a lot of this before, but a lot is new:
- We subclass a
CreateView
and specify the object this view will be related to (Comment
), which fields we want to be able to modify when we createComment
instances (we only want to modify the comment body), and which template our view is going to be using. - We override the
form_valid
method because we need to specify theauthor
(the currently logged-in user) andarticle
(the article that the comment is being attached to) fields required by theComment
model. - We also override the
get_success_url
because we want the user to be redirected to theArticleDetailView
upon saving the comment (we need to import thereverse
URL resolver for that).
Adapt ArticleDetailView
#
Now, we need to modify the ArticleDetailView
to make the CommentCreateView
's form available to templates/article_detail.html
through the get_context_data
method:
1 2 3 4 5 6 7 8 9 10 11 |
|
Let's explain our override of get_context_data
:
- the
get_context_data
method populates the dictionary that will be used as the template context super().get_context_data(**kwargs)
is boilerplate that populates ourcontext
with the data the view would come with by default- we then pass the newly created
CommentCreateView
's form (which we get through itsget_form_class
method) to theArticleDetailView
'scontext
(DetailView
generic views do not have aform
, so we're not overwriting anything here) - we now have access to the relevant context of both our views.
Combine ArticleDetailView
and CommentCreateView
#
Finally, we create a view that combines ArticleDetailView
and CommentCreateView
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
We now have a new hybrid view that, depending on whether the method is GET
or POST
, will return the ArticleDetailView
or the CommentCreateView
, respectively.
Adapt the urlpattern
#
In order for our new hybrid view to be able to act as intended, we need to specify it as the view that deals with requests to the article/<slug:slug_uuid>
path in articles/urls.py
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Create a template#
Now that articles/views.py
and articles/urls.py
are ready, we need to create the templates.
Create comment_input.html
(based on Svelte implementation's [[https://github.com/sveltejs/realworld/blob/master/src/routes/article/\slug\/_CommentInput.svelte][_CommentInput.svelte]]):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
This POST form corresponds to CommentCreateView
's form: when we send submit this form in our app, the ArticleCommentView
will return the CommentCreateView
, which will process the request and create the requested Comment
instance.
In templates/comment_container.html
, we include the comment_input.html
template:
1 2 3 4 5 6 |
|
Everything should be working now. Try to create some comments on an article.
Deleting comments#
We now want to be able to delete comments.
In articles/views.py
, add CommentDeleteView
:
1 2 3 4 5 6 7 8 9 10 |
|
In articles/urls.py
, we add a urlpattern
:
1 2 3 4 5 6 7 8 |
|
We require pk
as an argument because CommentDeleteView
needs this information to identify the comment to delete. The <slug:slug_uuid>
part is unnecessary, but it makes the path more logical, I find.
Create templates/comment_delete.html
(based on Svelte implementation's [[https://github.com/sveltejs/realworld/blob/master/src/routes/article/\slug\/_Comment.svelte][_Comment.svelte]]):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
In templates/comment.html
:
1 2 3 4 5 6 7 8 9 10 11 |
|