In your command line, you'll need to make sure that pip and python are installed.
If this is not your first time making a Django application, see the Quickstart option instead.
Next is setting up a virtual environment. It is a recommended best practice when working on Python projects, including Django. A virtual environment helps isolate project dependencies and avoids conflicts with other projects.
If you haven't already created a new project directory, now is the time. Once created, we'll want to cd
into it:
mkdir name_of_project
cd name_of_project
If you don't have virtualenv
installed, you can install it using pip
:
pip3 install virtualenv
Create a virtual environment. Replace the second "venv" with your preferred name for the virtual environment
python3 -m venv venv
^^^^
This venv can be any version of 'venv' you like. I usually use 'env' and will refer to it as 'env' for the rest of the walk through
On macOS and Linux:
source env/bin/activate
After activation, your terminal prompt should change, indicating that the virtual environment is active.
You'll want to do this every time you come back to your project!!
While the virtual environment is active, install Django using pip
:
pip3 install django
If you're working on an API, then you'll also want to run the following:
pip3 install djangorestframework
Now that your virtual environment is set up and Django is installed, you can proceed with the steps mentioned earlier to create a new Django project, create a Django app, configure database settings, apply migrations, and run the development server.
When you're done working on your project, you can deactivate the virtual environment:
deactivate
Remember to reactivate the virtual environment every time you work on your project. You can reactivate it using the activation command mentioned in step 3.
By using a virtual environment, you create an isolated environment for your project, making it easier to manage dependencies and ensuring that your project runs with the correct versions of libraries and packages.
-
Create a project directory
mkdir my_django_project
-
Move into the project directory
cd my_django_project
-
Set up a virtual environment (optional but recommended)
python3 -m venv env
-
Activate the virtual environment
source env/bin/activate
-
Install Django
pip install django
-
Create a Django project
django-admin startproject name_of_project
-
Navigate into the project directory
cd name_of_project
-
Run migrations
python manage.py migrate
This list of packages and their uses. It's a good idea to install them ahead of time, but you can install them as needed. For each of these include pip install
before the package name:
django
-- The basic framework for building in djangodjangorestframework
-- Used to create API or strictly back end applicationspsycopg2
-- Used to switch your database to PostgreSQL (more steps still required)psycopg2-binary
-- Used to switch your databases over from SQLite to PostgreSQLwhitenoise
-- Simplifies serving static files (css, js, images, fonts, icons, etc.) for Django web applicationspytest-django
-- Used for Testinggunicorn
-- Needed for deployment purposesdjango-heroku
-- Needed for deploying on Herokupillow
-- Needed for image uploading features
You can chain these packages together into one long command if you know what you want to install ahead of time.
Ex: pip install djangorestframework psycopg2 whitenoise pytest-django gunicorn pillow
Now in order to make a new model you will need to create your models.py
file and create some tables.
1. Include your connection from django.db import models
line in your code or Django won't know to make these model classes!!!
include better steps here in the future...
Before we're able to make these models as migrations, we need to take and extra step. Go to your settings.py file and scroll down to 'INSTALLED_APPS' section. You need to add the name of your project here so that you can call it in your next step and it knows what to make these migrations for.
NOW you need to run the following code in order to create your new migration to your DB:
$ python manage.py makemigrations name_of_your_table
This will create your migration, but [remember], migrations need to be expressly migrated. Run the migrate command again from the beginning of the project:
$ python manage.py migrate
While the admin side of a Django app is optional, it can make 'seeding' your database incredibly streamlined. In order to get your admin set up and see your newly created tables on the admin site, follow the directions below:
Django comes preloaded with a handy feature known as a SuperUser. Which is effectively a Super admin of the application that you can interact with like a website. You can set this up by running the following:
$ python manage.py createsuperuser
It will give a response of the following and you can fill in the blanks
Username (leave blank to use 'ethan.vangorkom'): admin
Email address: email@email.com
Password: (this field will be blank as you type)
Password (again):
If you password is basic and easy, it will give you an error. Type 'y' and hit enter to overwrite the warning.
Now that the admin is set up, let's add the new tables to the admin's scope! To do that we'll need to create a new file called admin.py in our project.
- Within the file we'll start our first import from the admin roles and rules that come with Django.
from django.contrib import admin
- Then we'll need to reference our models, so we'll make the call to our models file and import each model class.
from .models import Name_Of_Model
- Finally we just have to register our new model to be within our admin's scope.
admin.site.register(Name_Of_Model)
Additionally, Django is nice in that it will let you import all of your models at once. You can also write the code above like this:
from django.contrib import admin
from .models import Customer, Subscription, Tea
Now that our admin/superuser has access to the tables, we can run our server.
In the command line run: $ python manage.py runserver
If you have no issues in your code, it will load similar to rails server, and give your a link. http://127.0.0.1:8000/
Paste this into your webbrowser and then add admin/
at the end and log in with the credentials that you entered perviously in the 'createsuperuser' step.
You should come to a home page that looks like this:
From here you can see the various tables that you created and registered previously. Django comes preloaded with an Admin function that allows you to do basic CRUD functionality without needing to write those methods in your views yet (more on that later).
NOTE:
In a Django application, when you create multiple models, and link them together with ForeignKey
or ManytoManyField
You must put the parent model, above the model you call the field attribute in, otherwise it will not be able to find it.
In the Django framework, urls.py
act like the routes
files we're used to using in a rails application. Here we'll need to link them to our views.py
in order to create our endpoints.
First, well need to declare our connections and scope.
# These first two come preloaded in a Django application
from django.contrib import admin
from django.urls import path
# These are the ones you'll need to add.
from your_project_name import views
from rest_framework.urlpatterns import format_suffix_patterns
This lets your application know that we're going to talk to our views.py
and look at some of the methods we have there.
urlpatterns = [
path('teas/', views.tea_list),
path('teas/<int:id>/', views.tea_details),
path('admin/', admin.site.urls),
]
Notice that in our paths we're calling on our file 'views' and then will look at the methods that belong to that view tea_list
and tea_details
in this case. That means it's going to look for those methods and run them when we enter the corresponding url endpoints. teas/
will hit the tea_list
method and so on. Keep this in mind when we move onto creating our views file next.
The Functionality of Controllers in Rails
In the Django framework, views.py
house the CRUD methods (called 'functions' in python) for your class models.
In Django, we need to declare our connections. To achieve this we use the following syntax:
from django.http import JsonResponse
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status
from .serializers import *
from .models import *
- Not all of these imports will make sense immediately, but I promise we'll go over it.
First it's important to know that our urls.py
file will be searching our views for a specific method to run. ALL of the CRUD functionality of all models exists within the same file here (until refactored out anyways), so naming convention is important here.
For simplicity, I've broken up the CRUD functions of each model into two main methods. List and Details.
- List - will refer to the GET (index) endpoint and the POST endpoint as neither require a specific or valid id value to be utilized.
- Details - will be used to GET (show), PUT, and DELETE since these all require a specific and valid id value.
Now then, in these examples I follow along to the video tutorial and utilize a conditional to determine which request is being sent and respond accordingly for both the list
and details
methods
# Let's do the list functionality first
@api_view(['GET', 'POST'])
def tea_list(request):
if request.method == 'GET':
teas = Tea.objects.all()
serializer = TeaSerializer(teas, many=True)
return Response(serializer.data)
elif request.method == 'POST':
serializer = TeaSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
While this may look like a lot at first, if you've been coding in Ruby/Rails for a while, this is actually fairly straightforward. The request.method type is being passed through a conditional to decide how we should handle it. If it's a GET then we'll need to find all of our objects that we're looking for, serializer them and then explicitly return the response. If the request is POST, then we'll need to get the request.data and turn it into an object and serialize it for the response. First we check to make sure that the serializer is valid (which can weed out the bad requests, or invalid inputs, etc.), then we save it!
Now let's take a look at our tea_details
function.
@api_view(['GET', 'PUT', 'DELETE'])
def tea_detail(request, id):
try:
tea = Tea.objects.get(pk=id)
except tea.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
if request.method == 'GET':
serializer = TeaSerializer(tea)
return Response(serializer.data)
elif request.method == 'PUT':
serializer = TeaSerializer(tea, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
elif request.method == 'DELETE':
tea.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
Again, this is using a conditional to determine which action we should take. However, here we have an added parameter in the tea_detail
function of id
. In Django we pass this value in along with our request type parameter to help us locate the specific object we're looking for.
In Python, we can use the exception (see python tutorial if you're not sure what that is) try
to determine if the object exists or not. If it does, then we continue down our conditional!
If you're seeing TeaSerializer
have a bunch of squiggles underneath every instance of it, then congratulations! You're ready to move on to making our serializers.py
file! If you're not seeing that, then you might want to install the Python extension for VS Code or the Pylance extension (which should come as part of the Python extension).
pdb is the Python equivelant to a pry
session. It will stop your code and let you access variables and values from within the code. You must import pdb before you attempt to use it in a file though.
At the top of your file - import pdb
Whereever you want to stop your code - pdb.set_trace()
Django has a TestCase builtin so you don't have to use Pytest if you don't want to. To utilize the Testcase, you'll need to import the funtionality and build out your tests.
from django.test import TestCase
If you're building an API, the rest_framework has it's own TestCase you can utilize as well. Though I would recommend utilizing one or the other.
from rest_framework.test import APITestCase
You can run your entire test suite for the entire application or you can make it more specific.
python3 manage.py test
You can get as specific as calling a specific method!
python3 manage.py test app_name.tests.test_file.TestCase.test_method
Basic commands in Rails and their Django equivalents
rails db:drop --> python manage.py flush
# This will ask if you're sure you want to delete everything
# Note: This will also delete your superuser credentials!
rails db:create --> python manage.py migrate
# This command not only creates the database but also applies any pending migrations to update the database schema.
rails db:migrate --> python manage.py migrate
# As above
rails db:seed --> python manage.py loaddata your_fixture_file
# Replace `your_fixture_file` with your own fixture file (ex. `fixtures`). Fixtures in Django are typically JSON or YAML files containing serialized data
There's a lot that is required for deployment on Heroku with Django. While I'm sure there are many ways to do it, we found that this way works for us. Be sure to do your own research to get a bette understanding of the changes and concepts that are employed here.
Python Deployment with Heroku Docs: https://devcenter.heroku.com/articles/getting-started-with-python
Deploying an already created repository: https://devcenter.heroku.com/articles/preparing-a-codebase-for-heroku-deployment
# These packages will almost always be used!
django
djangorestframework
psycopg2
psycopg2-binary
whitenoise
gunicorn
django-heroku
django-cors-headers
2. Make sure that you are on your main branch in your terminal and that your Required docs are set up:
- runtime.txt
python-3.11.6
Within the runtime file should just be your version of python that you used to write the project with.
- Procfile
web: gunicorn your_project_name.wsgi
Within the Procfile should just be the text above, but substitute your actual project name.
- requirements.txt
Create your requirements file by running the command
pip freeze > requirements.txt
This will auto-generate your requirements file with all of the packages you have installed in your application. (Necessary for Heroku to know what framework you're using, remember Django is a python package too)
Now we're ready to create and connect our application to Heroku.
- Make sure that Heroku is installed on your local machine and that your are in your project's directory.
- Run the command
heroku login
- Follow the prompts and login
- Now that we're logged in, run the command
heroku create
- This will create a new heroku deployment, but it's not completely connected to your application yet. You still need to fix your database.
- Update your
settings.py
file following the next steps
First we'll need to change your DATABASES values to the values below instead of the SQLite database that comes preloaded.
settings.py
if "DATABASE_URL" in os.environ:
DATABASES = {
'default': {
'ENGINE': 'your_info_here',
'USER': 'your_info_here',
'NAME': 'your_info_here',
'PASSWORD': 'your_info_here',
'HOST': 'your_info_here',
'PORT': "5432",
}
}
else:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
Update this information with the data in Heroku!!
go to the Heroku Postgres
, then view credentials!
You'll find the necessary information by clicking the following highlighted links:
-
Heroku Postgres
-
Settings
-
View Credentials...
Note:
The Database
value within the credentials section will be your value for NAME
within your settings.py
file. You will likely not use your URI
value that comes within the credentials section. Also your credentials are NOT permanent! You may have to come back and update these credentials within your app periodically.
Be sure to add this to your settings file too... Again there's a lot of subtle changes to your code base to get Django applications to deploy properly.
from pathlib import Path
import os
# ...
# Heroku settings
# Allow Heroku to set the DJANGO_SETTINGS_MODULE environment variable
SETTINGS_MODE = os.getenv("DJANGO_SETTINGS_MODULE", "your_project_name.settings.production")
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.getenv("SECRET_KEY", "your-secret-key")
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False
# Allow all host headers
ALLOWED_HOSTS = ["*"]
# Databases
import dj_database_url
# Your databases from earlier here
# ... Scroll till the bottom
import django_heroku
STATIC_ROOT = BASE_DIR / "staticfiles"
STATIC_URL = 'static/'
django_heroku.settings(locals())
Finally, git add
, git commit
, git push
, then git push heroku main
.
Run the command python3 manage.py migrate
- This is because we've connected our data to a new database (hosted online), and we need to migrate again to the deployed database.
- Run git add, commit, push, and push heroku main again
At last everything should be connected and using a PostgreSQL database. Check your deployment to make sure everything is working properly!
... but we're not done yet...
A CORS (Cross-Origin Resource Sharing) error occurs when a web application running at one origin (domain) makes a request to a different origin, and the browser blocks the request due to security reasons. This is a common issue when your frontend (client-side) is running on a different domain or port than your backend (server-side). To avoid this, follow the next steps.
- Install
django-cors-headers
if you haven't done that already. Remember to rerunpip freeze > requirements.txt
to update your requirements file if you're installing it now. - Go back to your
settings.py
and update the following fields.
INSTALLED_APPS = [
# ...
'corsheaders',
# ...
]
MIDDLEWARE = [
# ...
'corsheaders.middleware.CorsMiddleware',
# ...
]
# Add this new section to the very bottom
CORS_ALLOWED_ORIGINS = [
"http://localhost:3000",
"https://your_production's_frontend.com",
"https://127.0.0.1:8000/"
#... whatever other sites you want to allow
]
# Or format it like this
CORS_ALLOW_ALL_ORIGINS = True
Be sure to add, commit, push, and push heroku!
- SerializerMethodField() is a field provided by Django REST Framework that allows you to create a custom field in your serializer that does not directly map to a model field. It's useful when you need to include custom logic or data that is not directly available from your model.
-
class PlayerSerializer(serializers.ModelSerializer): campaigns = serializers.SerializerMethodField() class Meta: model = Player fields = ['id', 'campaigns', 'first_name', 'last_name', 'date_created', 'updated_at']