Python Django Social Media App From Scratch

Written by

in

Building a social media app from scratch with Python Django might sound like a daunting task, but with the right roadmap you can turn a simple idea into a fully functional platform that rivals the big players. In this step‑by‑step guide we’ll walk through everything you need – from setting up the development environment to deploying a production‑ready site – while sprinkling in best practices for security, scalability, and SEO. Whether you’re a solo developer, a startup founder, or a Django enthusiast, this tutorial will give you the confidence to launch your own social network today.

Why Choose Django for a Social Media App?

Django’s “batteries‑included” philosophy makes it an ideal framework for complex web applications. Here are a few reasons it shines for social platforms:

  • Rapid Development: Built‑in admin, authentication, and ORM accelerate core features.
  • Scalable Architecture: Supports horizontal scaling with Django Channels, caching, and database sharding.
  • Security First: Automatic CSRF protection, XSS safeguards, and a strong community that patches vulnerabilities quickly.
  • SEO‑Friendly: Clean URL routing, sitemap generation, and easy integration with meta‑tag libraries.

Project Overview – What We’ll Build

Our minimal viable product (MVP) will include the following core components:

  1. User registration, login, and profile management.
  2. Post creation with text, images, and optional hashtags.
  3. Follow system (users can follow/unfollow each other).
  4. Real‑time feed using Django Channels.
  5. Like and comment functionality.
  6. Basic search and hashtag navigation.

Each feature will be introduced in its own section, allowing you to pick and choose what to implement first.

Setting Up the Development Environment

1. Install Python and Virtualenv

# Verify Python version (3.10+ recommended)
python3 --version

# Create a virtual environment
python3 -m venv env
source env/bin/activate  # On Windows use `env\Scripts\activate`

2. Install Django and Required Packages

pip install django==4.2
pip install pillow          # Image handling
pip install django‑crispy‑forms
pip install djangorestframework
pip install channels        # Real‑time features
pip install psycopg2‑binary # PostgreSQL driver (recommended for production)

3. Start a New Django Project

django-admin startproject socialapp
cd socialapp
django-admin startapp core

Designing the Data Model

The heart of any social network is its database schema. Below is a simplified model that captures users, posts, follows, likes, and comments.

# core/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models

class User(AbstractUser):
    bio = models.TextField(blank=True, max_length=500)
    avatar = models.ImageField(upload_to='avatars/', blank=True, null=True)

class Post(models.Model):
    author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
    content = models.TextField(max_length=1000)
    image = models.ImageField(upload_to='posts/', blank=True, null=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ['-created_at']

class Follow(models.Model):
    follower = models.ForeignKey(User, on_delete=models.CASCADE, related_name='following')
    following = models.ForeignKey(User, on_delete=models.CASCADE, related_name='followers')
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        unique_together = ('follower', 'following')

class Like(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='likes')
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        unique_together = ('user', 'post')

class Comment(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
    content = models.CharField(max_length=300)
    created_at = models.DateTimeField(auto_now_add=True)

Running Migrations

python manage.py makemigrations
python manage.py migrate

Implementing Authentication and Profiles

Django’s built‑in authentication system handles most of the heavy lifting. We’ll extend it with a custom User model (as shown above) and use django‑crispy‑forms for a polished UI.

Registration View

# core/forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm
from .models import User

class SignUpForm(UserCreationForm):
    email = forms.EmailField(required=True)
    bio = forms.CharField(widget=forms.Textarea, required=False)

    class Meta:
        model = User
        fields = ('username', 'email', 'bio', 'password1', 'password2')
# core/views.py
from django.shortcuts import render, redirect
from .forms import SignUpForm

def signup(request):
    if request.method == 'POST':
        form = SignUpForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect('login')
    else:
        form = SignUpForm()
    return render(request, 'registration/signup.html', {'form': form})

Profile Page

Show the user’s avatar, bio, and a list of their posts. Use login_required to protect the view.

# core/views.py (continued)
from django.contrib.auth.decorators import login_required

@login_required
def profile(request, username):
    user = User.objects.get(username=username)
    posts = user.posts.all()
    return render(request, 'core/profile.html', {'profile_user': user, 'posts': posts})

Creating Posts with Media Uploads

Allowing users to share images adds a visual dimension to the feed. The ImageField in the Post model works with Pillow to validate uploads.

Post Form

# core/forms.py (add)
class PostForm(forms.ModelForm):
    class Meta:
        model = Post
        fields = ('content', 'image')
        widgets = {
            'content': forms.Textarea(attrs={'rows': 3, 'placeholder': 'What’s on your mind?'}),
        }

View Logic

# core/views.py (add)
from django.contrib.auth.decorators import login_required

@login_required
def create_post(request):
    if request.method == 'POST':
        form = PostForm(request.POST, request.FILES)
        if form.is_valid():
            post = form.save(commit=False)
            post.author = request.user
            post.save()
            return redirect('feed')
    else:
        form = PostForm()
    return render(request, 'core/create_post.html', {'form': form})

Follow System – Building Connections

The follow relationship is stored in a separate model to keep queries efficient. Here’s how you can toggle a follow:

# core/views.py (add)
from django.http import JsonResponse

@login_required
def toggle_follow(request, username):
    target = User.objects.get(username=username)
    relation, created = Follow.objects.get_or_create(follower=request.user, following=target)
    if not created:
        relation.delete()
        status = 'unfollowed'
    else:
        status = 'followed'
    return JsonResponse({'status': status})

Real‑Time Feed with Django Channels

To give users an Instagram‑like experience, we’ll push new posts to followers without a page refresh.

Install and Configure Channels

# settings.py
INSTALLED_APPS += ['channels']
ASGI_APPLICATION = 'socialapp.asgi.application'

# Use Redis as the channel layer (install redis server locally or use Docker)
CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'channels_redis.core.RedisChannelLayer',
        'CONFIG': {'hosts': [('127.0.0.1', 6379)]},
    },
}

Consumer for the Feed

# core/consumers.py
import json
from channels.generic.websocket import AsyncWebsocketConsumer

class FeedConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        if self.scope['user'].is_anonymous:
            await self.close()
        else:
            self.group_name = f'feed_{self.scope["user"].username}'
            await self.channel_layer.group_add(self.group_name, self.channel_name)
            await self.accept()

    async def disconnect(self, close_code):
        await self.channel_layer.group_discard(self.group_name, self.channel_name)

    async def new_post(self, event):
        await self.send(text_data=json.dumps(event['post']))

Triggering the Event

# core/signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import Post
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer

@receiver(post_save, sender=Post)
def broadcast_new_post(sender, instance, created, **kwargs):
if created:
channel_layer = get_channel_layer()
followers = instance.author.followers.all().values_list('follower__username', flat=True)
for username in followers:
async_to_sync(channel_layer.group_send)(
f'feed_{username}',
{
'type': 'new_post',
'post': {
'id': instance.id,
'author': instance.author.username,
'content': instance.content,
'image_url': instance.image.url if instance.image else '',
'

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *