Just a quick little project I made while practicing Django and backend development.
This is part of my journey as I learn and improve my skills.
A mini social media platform where users can follow others, view a personalized news feed, like, and comment on posts.
This project is built using Django and includes basic frontend styling with HTML, CSS, Bootstrap, and some JavaScript.
I usually focus on the backend side of things and try to keep things simple and clean.
Each project I make is a way for me to learn something new or reinforce what I already know.
- comming soon
- Python
- Django
- HTML
- CSS
- Bootstrap
- JavaScript
Hi, I'm Ashkan — a junior Django developer who recently transitioned from teaching English as a second language to learning backend development.
I’m currently focused on improving my skills, building projects, and looking for opportunities to work as a backend developer.
You can find more of my work here: My GitHub
- Clone the repository
git clone https://github.com/AsHkAn-Django/social_media_django_tutorial.git
- Navigate into the folder
cd social_media_django_tutorial
- Create a virtual environment and activate it
- Install the dependencies
pip install -r requirements.txt
- Run the server
python manage.py runserver
- install django-mpttp
pip install django-mptt
- Add to INSTALLED_APPS in your settings.py:
INSTALLED_APPS = [
# … other built-in apps …
'mptt', # django-mptt
'myApp', # your app containing Comment
]
- Create models.py in myApp/ and define:
from django.db import models
from django.conf import settings
from mptt.models import MPTTModel, TreeForeignKey
class Comment(MPTTModel):
body = models.CharField(max_length=150)
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
related_name="user_comments",
on_delete=models.CASCADE
)
post = models.ForeignKey(
'myApp.Post',
related_name="post_comments",
on_delete=models.CASCADE
)
created_at = models.DateTimeField(auto_now_add=True)
parent = TreeForeignKey(
'self',
on_delete=models.CASCADE,
null=True,
blank=True,
related_name='children'
)
class MPTTMeta:
order_insertion_by = ['created_at']
class Meta:
ordering = ['-created_at']
indexes = [models.Index(fields=['-created_at'])]
def __str__(self):
return f"{self.user.username}: {self.body[:20]}…"
- Generate and apply migrations to add your Comment table with the MPTT fields:
python manage.py makemigrations myApp
python manage.py migrate
- In myApp/admin.py, use the MPTT-aware admin class:
from django.contrib import admin
from mptt.admin import MPTTModelAdmin
from .models import Comment
@admin.register(Comment)
class CommentAdmin(MPTTModelAdmin):
list_display = ('body', 'user', 'created_at', 'parent')
mptt_level_indent = 20 # pixels per level indent
- In myApp/forms.py, define a ModelForm that carries a hidden
parent_id
from django import forms
from .models import Comment
class CommentForm(forms.ModelForm):
parent_id = forms.IntegerField(widget=forms.HiddenInput, required=False)
class Meta:
model = Comment
fields = ['body', 'parent_id']
- In myApp/views.py, add the comment-saving logic:
@login_required
def add_comment(request, pk):
post = get_object_or_404(Post, pk=pk)
if request.method == "POST":
form = CommentForm(request.POST)
if form.is_valid():
comment = form.save(commit=False)
comment.post = post
comment.user = request.user
parent_id = form.cleaned_data.get('parent_id')
if parent_id:
comment.parent = Comment.objects.get(pk=parent_id)
comment.save()
return redirect('myApp:post_detail', pk=pk)
else:
form = CommentForm()
return render(request, 'myApp/post_detail.html', {
'post': post,
'form': form,
})
- Rendering Comments in Templates
{% extends 'base.html' %}
{% load django_bootstrap5 %}
{% load mptt_tags %}
<!-- Load the MPTT template tags, including `recursetree` for nested trees -->
{% block content %}
<div class="container mt-5">
<!-- Post Details -->
{# You’d render title/body/date/author here before comments… #}
<!-- Comments -->
<h4>Comments ({{ post.post_comments.count }})</h4>
{% if post.post_comments.exists %}
<ul class="list-group">
{% recursetree post.post_comments.all %}
<li class="list-group-item">
<div class="comment">
<strong>{{ node.user.username }}</strong>
<small class="text-muted">at {{ node.created_at }}</small>
<p>{{ node.body }}</p>
</div>
<a href="#" class="reply-link" data-comment-id="{{ node.id }}">Reply</a>
<div id="reply-form-{{ node.id }}" style="display:none; margin-top:1rem;">
<form method="post" action="{% url 'myApp:add_comment' post.pk %}">
{% csrf_token %}
<input type="hidden" name="parent_id" value="{{ node.id }}">
{% bootstrap_field form.body %}
<button type="submit" class="btn btn-sm btn-primary">Post Reply</button>
</form>
</div>
{% if not node.is_leaf_node %}
<ul class="list-group ml-4">
{{ children }}
</ul>
{% endif %}
</li>
{% endrecursetree %}
</ul>
{% else %}
<p>No comments yet.</p>
{% endif %}
<!-- Top-level comment form -->
<div class="mt-4">
<h5>Leave a Comment</h5>
<form method="post" action="{% url 'myApp:add_comment' post.pk %}">
{% csrf_token %}
<input type="hidden" name="parent_id">
{% bootstrap_field form.body %}
<button type="submit" class="btn btn-primary">Post Comment</button>
</form>
</div>
<script>
// Simple JS to toggle reply forms
document.querySelectorAll('.reply-link').forEach(link => {
link.addEventListener('click', e => {
e.preventDefault();
const id = link.getAttribute('data-comment-id');
const container = document.getElementById(`reply-form-${id}`);
container.style.display = container.style.display === 'none' ? 'block' : 'none';
});
});
</script>
Term | Meaning |
---|---|
node |
The current item from the queryset (e.g., one Comment object) |
children |
The rendered HTML of the node’s child elements |
is_leaf_node |
True if the node has no children (i.e., no replies) |
recursetree |
A Django template tag that recursively loops through the tree structure |
root_nodes |
Returns top-level items where parent = None |
get_children |
Method to return direct children of a node |