Build Your Own Insta with React and Django - Part 1
May 28th, 2019
tutorial
In this series, we'll be creating a simple version of a photo sharing social network using React and Django Rest Framework. Creatively, we'll call the project "Djangogram".
Photo by @throughmyaperture on Unsplash.
This is a long, multi-part tutorial. In each part, we will cover:
- Part 1 (you're here!): Starting a new Django Project and building database models.
- Part 2: Creating an API for our models.
- Part 3: Using React with Mobx for our frontend.
- Part 4: Creating the UI for our web app.
If you haven't used Django or React before, this might not be the best introduction to either! However, I will still explain things thoroughly. If you feel up to a deep dive, follow along!
Launching A New Django App
Since Django is a web framework for Python, you'll need a few Python tools to get started.
python3
, at least version 3.6.8 for this tutorial.virtualenv
orvenv
for creating isolated and locale environments.pip
in yourvirtualenv
for installing Python packages (like Django!)
Let's start our new project by creating a folder djangogram
and adding a requirements.txt
file with the following contents:
django
djangorestframework
Now you can create your virtual environment, and install the dependencies. Run the following commands in your shell:
python -m virtualenv .env -p python3
# Or .env\scripts\activate on Windows
source .env/bin/activate
pip install -r requirements.txt
Since we didn't version our dependencies, pip
will automatically install the latest versions of each. For a serious application, you should version your dependencies, but here we're just skipping it.
Finally, we can create our Django project using the built-in django-admin
command. Again in your shell, run the following command:
django-admin startproject gram .
This command bootstraps a new Django project in the current working directory. It creates a root project folder and all required files! Your project folder should look like this by now:
djangogram
├── gram
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── manage.py
└── requirements.txt
1 directory, 6 files
Let's fire up the development server and see if everything worked. To launch a Django dev server, you can use the manage.py
file and the runserver
command:
python manage.py runserver
Open up your favorite web browser to http://localhost:8000/ and you should see the Django rocket.
If you see the rocket, you're all set. Let's start writing some code!
Social App and Settings
We need to create the social
application that we'll build our API on. The social
app will contain models for users, photos, comments, and more. Our new application is started with the following shell command:
python manage.py startapp social
Your directory's file structure should look like the following:
djangogram
├── gram
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── manage.py
├── requirements.txt
└── social
├── admin.py
├── apps.py
├── __init__.py
├── migrations
│ └── __init__.py
├── models.py
├── tests.py
└── views.py
4 directories, 18 files
manage.py
has created a directory named social
with required Django files for models, the admin, etc. You also need to install the application in gram/settings.py
, otherwise Django won't "pick up" the app.
# gram/settings.py
# The rest of your settings file above here
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'social',
]
With the social
app installed, we can create our base model and custom User
model for the application. Open social/models.py
and add the following two, new models:
# social/models.py
"""Models for the social application."""
import uuid
from django.db import models
from django.contrib.auth.models import AbstractUser
class BaseModel(models.Model):
"""Base model for the application. Uses UUID for pk."""
id = models.UUIDField(
primary_key=True,
editable=False,
default=uuid.uuid4,
)
class Meta:
"""Metadata."""
abstract = True
class User(BaseModel, AbstractUser):
"""Custom user model."""
The newly created BaseModel
class is an abstract model that adds a UUID primary key to all models that inherit from it. Our User
class inherits from BaseModel
and django.contrib.auth.models.AbstractUser
to create the custom user model for our backend application. Ever since Django added custom user model support, it has been recommended to start each new project with a custom user.
However, you need to point the Django authentication system at your user model. Add the following line to gram/settings.py
:
# gram/settings.py
AUTH_USER_MODEL = 'social.User'
Finally, you need to make migrations for the application and then run a migration. This sets up the database for the rest of your app!
python manage.py makemigrations
python manage.py migrate
And that's it! Let's add our other models to the project.
Photo and Media Settings
When working with photos in Django, there's a great library that I almost always reach for named Django Imagekit. Django Imagekit adds super helpful image processing tools that save lots of development time. We're going to install it alongside Pillow
, and then add the former to our INSTALLED_APPS
setting:
# requirements.txt
django
djangorestframework
pillow
django-imagekit
Run pip install -r requirements.txt
to install the new dependencies. You also need to add imagekit
to your INSTALLED_APPS
setting:
# gram/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'imagekit', # New
'social',
]
While we're modifying gram/settings.py
, we can add standard media settings. After adding these, all uploaded photos/files in development will get stored in a new media
directory.
# gram/settings.py
# Add this near the end
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = 'media/'
Since we're using DEBUG = True
and just developing, let's add a media endpoint to our gram/urls.py
file too. This URL will only work in development, but it will let us see uploaded photos while working on the site. In production, you should be using either a web server like Nginx/Apache or a service like AWS S3 with Cloudfront to serve static files.
# gram/urls.py
"""gram URL Configuration"""
from django.contrib import admin
from django.urls import path
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('admin/', admin.site.urls),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Our media settings are good to go! Let's add our photo model.
Adding Our First (Real) Model
Finally we can add in our photo model! In your social/models.py
file, add the following code:
# social/models.py
"""Models for the social application."""
import uuid
from django.db import models
from django.contrib.auth.models import AbstractUser
from imagekit.models import ProcessedImageField
from imagekit.processors import ResizeToFit
# === BaseModel and User ===
class Photo(BaseModel):
"""A photo posted by a user."""
user = models.ForeignKey(
User, verbose_name="Created By", on_delete=models.CASCADE, related_name="photos"
)
caption = models.TextField()
photo = ProcessedImageField(
upload_to="photos",
format="JPEG",
options={"quality": 90},
processors=[ResizeToFit(width=1200, height=1200)],
)
date_created = models.DateTimeField(auto_now_add=True)
date_updated = models.DateTimeField(auto_now=True)
def __str__(self):
return f"{self.user}'s Photo on {self.date_created}"
class Meta:
"""Metadata."""
ordering = ["-date_created"]
We've added two imports from the new imagekit
library, ProcessedImageField
and ResizeToFit
. Our photo
field will automatically resize any uploaded files to be 1200px times 1200px or less, while preserving aspect ratio. This isn't required, and you can make your photos be larger or smaller.
Our Photo
model extends the BaseModel
model, and has some standard fields like who created it, when it was created, and the last time it was edited. Finally, we also defined a default ordering for photos as well as a magic method for displaying it as a string. You can now create the model with manage.py makemigrations
and manage.py migrate
, which will create it in the database!
While we're here, let's add it to the administration panel in social/admin.py
:
# social/admin.py
"""Administration for our social app."""
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from . import models
@admin.register(models.Photo)
class PhotoAdmin(admin.ModelAdmin):
"""Admin for photos."""
@admin.register(models.User)
class CustomUserAdmin(UserAdmin):
"""Custom user admin."""
If you create a superuser with python manage.py createsuperuser
, you should be able to navigate to admin/
when the server is running and create a new photo under "Social > Photos".
Thankfully this isn't our end user interface.
If you check the media/
directory in your project, you'll see the uploaded photo is stored in there and resized if necessary. If everything looks good, then congrats! We've got about 1/100th feature parity with Instagram.
Adding Likes and Comments
We want our users to be able to like photos that interest them and share constructive feedback in comments. Moderation may be an unsolved problem, but we can at least add Like
and Comment
models. In your social/models.py
file, add the following code for a Like
model.
# social/models.py
# === Imports and other models. ===
class Like(BaseModel):
"""A 'like' on a photo."""
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="likes")
photo = models.ForeignKey(Photo, on_delete=models.CASCADE, related_name="likes")
date_created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f"{self.user} Like"
class Meta:
"""Metadata."""
unique_together = (("user", "photo"),)
ordering = ["-date_created"]
A Like
is a many to many relationship between users and photos, but rather than use a ManyToMany
field we are implementing it ourself. By creating the model explicitly we get more control, like being able to specify unique together constraints. Plus, we don't really need the syntax sugar created by a ManyToMany
field with through
in Django.
Our comment model will look remarkably similar:
# social/models.py
# === Imports and other models. ===
class Comment(BaseModel):
"""A comment on a post."""
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="comments")
photo = models.ForeignKey(Photo, on_delete=models.CASCADE, related_name="comments")
content = models.TextField(max_length=2000)
date_created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.content
class Meta:
"""Metadata."""
ordering = ["-date_created"]
As above, we are avoiding a traditional ManyToMany
field. Our comment also (obviously) stores the text content written by the user. In contrast to the Like
model, a User
can create any number of Comment
objects on any Photo
because there is no uniqueness constraints.
Create your new models with a migration command:
python manage.py makemigrations
python manage.py migrate
If you'd like, you can also add them to the social/admin.py
file:
@admin.register(models.Like)
class LikeAdmin(admin.ModelAdmin):
"""Admin for Likes."""
@admin.register(models.Comment)
class CommentAdmin(admin.ModelAdmin):
"""Admin for Comments."""
And that's all the models we will be creating for our application. You're at the end of this section of the tutorial!
Wrapping Up
With this tutorial you have created a solid foundation for a basic photo sharing application. The next part will be creating a RESTful API layer for our React-based client to consume. Be sure to check back, watch for updates on github, or follow my RSS feed
If you found this tutorial helpful or hit some problems, please feel free to contact me and let me know! Thanks for reading!