-
Notifications
You must be signed in to change notification settings - Fork 53
Django
One of the benefits of using a web framework is that it 'automatically' handles a lot of the setup and many of the aspects of web development that we often find ourselves recreating every single time. For futher discussion on web frameworks, focusing on Python solutions, check out this article.
We already have experience using a JavaScript backend framework, now let's have a look at a Python option called Django. Note the advertised 'selling points' of 'Ridiculously Fast', 'Reassuringly Secure' and 'Exceedingly Scalable' - sounds good!
One of the benefits and downfalls of Django is that it really provides a lot out of the box. If you need a full CRUD application with ready-to-go security, database configuration and more, Django could be ideal. For smaller projects, this is likely to be way more than is needed and result in unecessarily cumbersome processes and codebase.
Let's put it to the test. How quick can we put together a CRUD application in Django?
pip install Django
- You can confirm your version with
python -m django --version
-
django-admin startproject <project-name>
eg.django-admin startproject shelter
- (To see a list of all django-admin commands, just enter
django-admin
)
python manage.py runserver
- Visit in browser (default port is 8000)
So in just a few commands you've got a fairly attractive visual running on your localhost development server but it's not exactly a webapp! This is merely the environment in which you will craft your work - the overall configuration. Have a look at what it gave us.
-
manage.py
sets us up to run terminal commands to interact with our project -
__init__.py
blank but lets the system know that this is a Python project -
settings.py
what it says on the tin! The settings for the project -
url.py
for mapping out routes to specific sets of handlers -
wsgi.py
some basic setup for WSGI deployment -
asgi.py
some basic setup for ASGI deployment
Within one Django 'project', you can use one or more 'apps'. You can think of these like microservices - they should be self contained but can work together. Let's make one called 'adoption'.
python manage.py startapp adoption
You now have a folder called 'adoption' with a substantial amount of new files.
Finally, install your app into your project by pointing to the config class that is defined in adoption/apps
# shelter/settings.py
INSTALLED_APPS = [
# <appname>.apps.<AppConfigClass>
'adoptions.apps.AdoptionConfig',
# etc...
]
We'll start with a very simple HTML view - just headers for an index and show route.
# adoption/views.py
from django.http import HttpResponse
def index(req):
return HttpResponse("<h1>Hello! Here are all the friends who need adopting!<h1>")
def show(req, id):
return HttpResponse(f'<h3>Dog number {id}!</h3>')
To return more complex HTML, see Templates below
Back in our overall config urls file, route all the /adoption/ routes to the adoption app:
# shelter/urls.py
from django.contrib import admin # this is in by default
from django.urls import path, include # importing include as well as path
urlpatterns = [
path('adoption/', include('adoption.urls')), # add adoption urls
path('admin/', admin.site.urls), # this is in by default, visit :8000/admin to see
]
If you'd prefer to not have /adoption/ as part of your route, you can simply leave the path as an empty string.
Once we get to the adoption app, we can be more specific:
# adoption/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='adoption-index'), # route for /adoption/
path('about/', views.about, name='adoption-about'), # route for /adoption/about
# path('<id>/', views.show, name='adoption-show'), # route for /adoption/:id
path('<int:id>/', views.show, name='adoption-show') # to accept only numbers as id param
]
Start your server as before with python manage.py runserver
, visit localhost:8000/adoption
and behold your index message. Visit localhost:8000/adoptions/3
to see the show view.
You may have noticed by now that, on running your dev server, you receive warnings about 'unapplied migrations'. Django has some default installed apps which, if used, will even provide relevant database tables. If there are any you don't want, just comment them out in the INSTALLED_APPS
list in <project>/settings.py
.
Django comes with its own ORM which will be our interface to the database right from step one.
We will start by making some models. These will end up as database tables. Let's start with a Dog and a Breed model with the idea that a Dog has a Breed. For a rundown of all the possible fieldtypes and their validations, see this exhausive list.
# adoption/models.py
from django.db import models
class Breed(models.Model):
name = models.CharField(max_length=100)
temperament = models.TextField()
class Dog(models.Model):
name = models.CharField(max_length=50)
age = models.PositiveIntegerField()
breed = models.ForeignKey(Breed, null=True, on_delete=models.SET_NULL)
Ready for some magic?
- Run
python manage.py makemigrations adoption
and look in your<app-name>/migrations
file. - Run those migrations with
python manage.py migrate
.
If you'd like to see the SQL that will be run by your migrations run python manage.py sqlmigrate <app-name> <migration #>
eg. python manage.py sqlmigrate adoption 0001
Any time you wish to update your models, simply update in models.py
then run the makemigrations
and migrate
commands again.
You can test these out in an interactive shell by running python manage.py shell
and importing models as required eg. from adoption.models import Breed
.
-
gh = Breed(name='Greyhound', temperament='kind, gentle')
make new instance -
gh.save()
persist new instance as db record -
gh.dog_set.create(name='Zozo', age=4)
make a new Dog record with a breed of greyhound
-
Dog.objects.all()
see all records -
Dog.objects.first()
see first record -
Dog.objects.get(pk=2)
get record by id -
Dog.objects.filter(name='Mochi')
find all dogs with name 'Mochi' -
Dog.objects.filter(breed_name='greyhound')
find all dogs with breed of name 'greyhound'
-
b = Breed.objects.get(pk=2)
get breed with id 2 -
b.temperament='extra snuggly'
update temperament field -
b.save()
save changes
b.delete()
Now you can start using these in your views:
def show(req, id):
dog = Dog.objects.get(pk=id)
return HttpResponse(f'<h3>Meet {dog.name}!</h3>')
There are more query methods at your disposal, for a definitive list, check the documentation.
If you'd like to return more complex HTML from your routes we can use templates. Django naturally has it's own templating system. To get started, make a templates
folder in the relevant app and create an inner folder that matches the app name:
- shelter/
- adoptions/
- templates/
- adoptions/
- index.html
- show.html
- adoptions/
- templates/
- shelter/
- manage.py
- adoptions/
And in your action, render
the template and pass down any data required in a dictionary as the third argument to render
.
# adoption/views.py
def index(req):
context = { 'doggos': Dog.objects.all() }
return render(req, 'adoption/index.html', context)
def about(req):
return render(req, 'adoption/about.html')
If you passed down any data, you can render it (and indeed, execute any python code) using Django's templating syntax:
<!-- adoption/templates/adoption/index.html -->
<h2>Here are all the friends in need of new homes!</h2>
<ul>
{% for dog in doggos %} <!-- {% %} to add python logic -->
<li>{{ dog.name }}</li> <!-- {{ }} to access data -->
{% endfor %}
</ul>
For a deeper dive into templating in Django, including inheritance to share snippets between views, check out the documentation.
Often website owners require some backend access but don't wish to touch code directly. The concept, therefore, of an Admin page is not unfamiliar. Django gives us an entire setup avaiable right out of the box with the admin
app. It allows users to log in to view and interact with the site's data.
To make use of the admin panel (comes in a default Django setup at /admin
), you will need to create a super user.
-
python manage.py createsuperuser
(If this throws an error, make sure you have thedjango.contrib.admin
app installed in your project (see settings.py) and run the migrations). - Follow the terminal prompts to create a new user.
- Visit
/admin
and log in with your new credentials.
To add a model to your admin, register it in your <app-name>/admin.py
file:
# adoption/admin.py
from django.contrib import admin
from .models import Dog
admin.site.register(Dog)
Hint: if you'd like this to list as something more 'readable' than 'Dog Object (1)', in your Dog model, add a method:
# adoption/models in Dog model
def __str__(self):
return f'{self.name} the {self.breed__name}'
For more details on usage of the admin app see the documentation
Django comes with a SQLite integration ready to go but you can change your database engine in <project>/settings.py
. As we have spent some time in PostgreSQL, let's set that up in Django too. Additional documentation on this setup is available here. Your ORM methods and setup remains the same regardless of the database - handy!
Setup a local 'shelter' postgres database in your psql terminal or interface of your choice then connect it up:
# shelter/settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql', # default is sqlite3
'NAME': 'shelter', # default is BASE_DIR / 'db.sqlite3'
'USER': 'postgres', # update accordingly if appropriate
'PORT': 5432, # 5432 is postgres default
'HOST': 'localhost', # update accordingly if not running db locally
'PASSWORD': None, # update accordingly if password protected
}
}
If, on running your server, you receive an error about psychopg2, run pip install psycopg2-binary
.
Django has some very useful tools for User authentication. Here's a quick-start...
-
python manage.py startapp users
(and install in settings.py)
- make a form which extends the freebie UserCreationForm and adds any additional fields
# users/forms.py
from django import forms
from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm
class UserSignupForm(UserCreationForm):
email = forms.EmailField()
first_name = forms.CharField(max_length=50)
last_name = forms.CharField(max_length=50)
class Meta:
model = User
fields = ['username', 'email', 'first_name', 'last_name', 'password1', 'password2']
- create
register
view inusers/views.py
- import our form
from .forms import UserSignupForm
- render a template, passing in our form
# users/views.py
from django.shortcuts import render, redirect
from django.contrib import messages
from .forms import UserSignupForm
def register(req):
if req.method == 'POST':
form = UserSignupForm(req.POST)
if form.is_valid():
form.save()
username = form.cleaned_data.get('username')
messages.success(req, f'Welcome, {username}!')
return redirect('adoption-index')
else:
form = UserSignupForm()
data = {'form': form}
return render(req, 'users/signup.html', data)
<!-- users/templates/users/signup.html -->
<form method="POST">
{% csrf_token %} <!-- Django forms will not function without this additional security token-->
{{ form.as_p }} <!-- .as_p is optional for formatting purposes -->
<input type="submit" value="Sign Up">
</form>
NB: To see flash messages, you'll need to add them in a base template:
# a base template
{% if messages %}
{% for message in messages %}
<section id="alert">{{ message }}</section>
{% endfor %}
{% endif %}
- add signup route to project urls
# <project-name>/urls.py
from users import views as user_views
urlpatterns = [
path('signup/', user_views.signup, name='signup'),
# ...etc...
]
That's it! Log in to your admin console or in your sql database run SELECT * FROM auth_users
to check! When accessing the User model via the Django ORM, you can import the model: from django.contrib.auth.models import User
and then access as per usual.
For more info on Django forms check out the documentation.
- add Django's freebie login and logout views to project urls
# <project-name>/urls.py
from django.contrib.auth import views as auth_views
urlpatterns = [
path('login/', auth_views.LoginView.as_view(template_name='users/login.html'), name='login'),
path('logout/', auth_views.LogoutView.as_view(template_name='users/logout.html'), name='logout')
# ...etc...
]
- add templates
<!-- users/templates/users/login.html -->
<form method="POST">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Login">
</form>
<p>Not yet a member? Sign up <a href="{% url 'register' %}">here</a></p>
<!-- users/templates/users/logout.html -->
{% block content %}
<p>Bye! Come by again anytime <a href="{% url 'login' %}">here</a></p>
{% endblock content %}
- add login redirect rule
# <project-name>/settings.py
LOGIN_REDIRECT_URL = 'adoption-index'
You may wish to eg. show 'Login' or 'Logout' depending on if a User is currently authenticated. We can use the provided user
object and check the is_authenticated
status.
<!-- base template -->
<nav>
{% if user.is_authenticated %}
<a href="{% url 'logout' %}">Logout</a>
{% else %}
<a href="{% url 'login' %}">Login</a>
<a href="{% url 'signup' %}">Signup</a>
{% endif %}
</nav>
You may have routes that you only wish to be accessible to logged-in users.
- add login route rule
LOGIN_URL = 'login'
- add login_required decorator to any views you wish to protect
# users/views.py
from django.contrib.auth.decorators import login_required
@login_required
def profile(req):
return HttpResponse(f'<h1>{req.user.username}\'s profile</h1>')
For more info on Django authentication check out the documentation.
Django provides a very basic Not Found page for when a user tries to access a non-existing route. In order to see what this looks like in production, make the following changes in settings.py, but change debug back to True after as it's very useful in development!
DEBUG = False
ALLOWED_HOSTS = ['*']
Visit /bob
and see the rather uninspiring 404 page. To make a custom one:
- Create views:
# adoption/views.py
def not_found_404(req, exception):
return render(req, 'adoption/404.html')
def server_error_500(req):
return render(req, 'adoption/500.html')
- Create templates:
<!-- adoption/templates/adoption/404.html -->
{% extends 'adoption/base.html' %}
{% block content %}
<h1>Oops, what are you doing out here?</h1>
<p>Let's go <a href="{ url 'adoption-index'}">Home</a></p>
{% endblock content %}
<!-- adoption/templates/adoption/500.html -->
{% extends 'adoption/base.html' %}
{% block content %}
<h1>It's not you, it's us</h1>
<p>Let's go <a href="{ url 'adoption-index'}">Home</a></p>
{% endblock content %}
- Create handlers:
# <project-name>/settings.py
handler404 = 'adoption.views.not_found_404'
handler500 = 'adoption.views.server_error_500'
- Enjoy!
For more info on Django exceptions check out the documentation.
For more information on our transformative coding education, visit us at https://www.lafosseacademy.com/