Building a Social Media Site With Python and Django: Part 3 Get Post, Comment Model and Form
In this tutorial, we are going to add to the posts feature of our social app by adding the ability to get a single post and set up the comment model. Let’s first start by fixing a couple issues from the the last tutorial. The redirect is not working correctly for logged in users, we also want to change the home link in the navbar brand to go to the social feed if a user is logged in. Let’s open up or settings.py to fix the login redirect first:
LOGIN_REDIRECT_URL = 'post-list'
And then we will update the navbar brand tag to set the link based on if the user is logged in or not:
<a class="navbar-brand"
{% if user.is_authenticated %}
href="{% url 'post-list' %}"
{% else %}
href="{% url 'index' %}"
{% endif %}
> <i class="fas fa-comment"></i>
Social Network
</a>
Now with those changes finished, lets add a details view for each post. First we will create a view for that. For now, we will only handle the get request, but we will put a post method placeholder to handle that later.
class PostDetailView(View):
def get(self, request, pk, *args, **kwargs):
post = Post.objects.get(pk=pk)
form = CommentForm() context = {
'post': post,
'form': form,
} return render(request, 'social/post_detail.html', context) def post(self, request, *args, **kwargs):
pass
In this view, we created a form to be able to post a comment, let’s go into our forms.py to create this form:
class CommentForm(forms.ModelForm):
comment = forms.CharField(
label='',
widget=forms.Textarea(
attrs={'rows': '3',
'placeholder': 'Say Something...'}
)) class Meta:
model = Comment
fields = ['comment']
This will be very similar to the PostForm, except we are basing it off of the Comment model which we haven’t created yet, so let’s open our models.py and create that:
class Comment(models.Model):
comment = models.TextField()
created_on = models.DateTimeField(default=timezone.now)
post = models.ForeignKey('Post', on_delete=models.CASCADE)
author = models.ForeignKey(User, on_delete=models.CASCADE)
Now with that made, let’s create the HTML template for the post detail page, we will call it post_detail.html:
{% extends 'landing/base.html' %}
{% load crispy_forms_tags %}{% block content %}
<div class="container">
<div class="row justify-content-center mt-3">
<div class="col-md-5 col-sm-12 border-bottom">
<p><strong>{{ post.author }}</strong> {{ post.created_on }}</p>
<p>{{ post.body }}</p>
</div>
</div> <div class="row justify-content-center mt-3">
<div class="col-md-5 col-sm-12">
<h5>Add a Comment!</h5>
</div>
</div>
<div class="row justify-content-center mt-3 mb-5">
<div class="col-md-5 col-sm-12">
<form method="POST">
{% csrf_token %}
{{ form | crispy }}
<div class="d-grid gap-2">
<button class="btn btn-success mt-3">Submit!</button>
</div>
</form>
</div>
</div>
</div>
{% endblock content %}
Here, we are just showing the post and showing a form to add a new comment. The form doesn’t work yet but we will fix that in a little bit. First let’s set up a url path so we can view this page in our social/urls.py:
from django.urls import path
from .views import PostListView, PostDetailViewurlpatterns = [
path('', PostListView.as_view(), name='post-list'),
path('/post/<int:pk>', PostDetailView.as_view(), name='post-detail'),
]
We are passing in a pk as an argument to the url, this is how we will differentiate each post from each other and then that will allow us to show the correct post based on what pk we pass in to the url. Now we need to update our post_list to have a link that will take us here:
{% extends 'landing/base.html' %}
{% load crispy_forms_tags %}{% block content %}
<div class="container">
<div class="row justify-content-center mt-3">
<div class="col-md-5 col-sm-12">
<h5>Add a Post!</h5>
</div>
</div>
<div class="row justify-content-center mt-3 mb-5">
<div class="col-md-5 col-sm-12">
<form method="POST">
{% csrf_token %}
{{ form | crispy }}
<div class="d-grid gap-2">
<button class="btn btn-success mt-3">Submit!</button>
</div>
</form>
</div>
</div>
{% for post in post_list %}
<div class="row justify-content-center mt-3">
<div class="col-md-5 col-sm-12 border-bottom position-relative">
<p><strong>{{ post.author }}</strong> {{ post.created_on }}</p>
<p>{{ post.body }}</p>
<a class="stretched-link" href="{% url 'post-detail' post.pk %}"></a>
</div>
</div>
{% endfor %}
</div>
{% endblock content %}
You will see in each row that is listing a post, we added an anchor tag that goes to the post-detail and passes in the current post’s primary key. This will build the correct url to take us where we want to go. You will also see we used the stretched-link class and put position-relative on the column. The stretched link tag will stretch the link across the next parent with a relative position. With this setup, we will have the link be clickable across the entire post container instead of just a small link at the bottom.