Note that in the current state, this hackpack is nearly identical to the one in Django's official documentation. I will attempt to rephrase explanations as I see fit to enhance understanding at the expense of completeness. This tutorial is meant to quickly explain the core ideas of using the Django framework in an easy manner. A solid grasp of python and html as well as web development is required. With no experience in one or both of these languages, following along may be a bit difficult and extending the project as you see fit will be extremely tedious. We will attempt to emulate SignUpGenius at the most basic level (seriously).
First, make sure that python and Django are installed. To check if it was installed correctly, just type python
into terminal and you will see your version number. This tutorial (and the one in official documentation) use Django 2.2, which supports Python 3.5 or later. I'm not going to get into the details too much because this step is rather self-explanatory.
- In terminal, navigate to the desired location of the Django project and use the following:
django-admin startproject signupguru
- This will generate a directory named signupguru and the necessary starter code for the project.
- Notice that two directories named signupguru are made. The outer one is just a folder for the project, we will be working on the inner directory
- To check that everything has been successful so far, use
python manage.py runserver
. You should get a message about unapplied migrations. Always make sure the server is running when testing the site, which you can check at http://127.0.0.1:8000/ - Most changes that you make in code will be reflected on the site instantly
- Now is a good time to introduce apps. Apps are stand alone "mini" projects that serve a specific function. They are "pluggable," meaning you can take the apps you make and insert them into other Django projects. Make sure that you are in the same directory as manage.py and type
python.manage.py startapp signup
. A new directory signup will now be created that houses the app and its files. - One of the autogenerated files in our signup app is views.py. In Django, views are exactly what you would think them to be. You add views to change what a user sees on a page. Let's add a simple view. In signup/views.py, put:
from django.http import HttpResponse
def index(request):
return HttpResponse("Signup home")
- The key idea is that every view takes a Web request and returns a Web response.
- In order to see the view, it has to be mapped to a URL. We are now going to set up the app's URL configuration, which manages the various URLs that a user can type. First create a urls.py file in the signup directory and add the following.
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index'),
]
- Essentially what this means is that when the URL navigates us to the signup app, if there is no more information in the URL then we will be taken to the index view. For more detailed information about the path function, visit the documentation. We will see examples of going to other views later. But how would we get here in the first place?
- From signupguru/urls.py we need to point the navigation towards the signup app whenever the path includes 'signup'. Modify the file to look like this
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('signup/', include('signup.urls')),
]
-
The include function allows us to reference other URL configurations, namely the one we created for the signup app. It will send whatever is left in the URL after 'signup/' to the next URL configuration.
-
To check, again run the server with
python manage.py runserver
in your terminal. Head to http://localhost:8000/signup/ and you should see the view we defined as index earlier.
-
Next we are going to set up the database, one of the most integral parts of a Django project. Several databases can be used, but we will use SQLite3, which comes with Python. If you want to use a different database, there are a few settings you have to change (look in the official documentation for easy steps). Head over to signupguru/settings.py. This file contains all the variables that represent settings in the project. For now, just change the time zone to one that's appropriate.
-
Now it's time to explain what migrations are. First, run
python manage.py migrate
. A migration is required anytime a change to the database's schema structure is made. You might be wondering why we had unapplied migrations earlier if we haven't touched our database schema yet. The answer is that there is an installed apps section of the settings page that automatically creates commonly used apps for the user, and these apps have pre-defined schemas. The migrate command we just used will look in the installed apps section and creates schemas as necessary - pretty convenient. -
We will now define our tables for the database, using Django models. For the signup app in its most basic form, we will probably need over-arching events, and specific roles for each event that can be "signed up" for. Edit signup/models.py to look like this:
from django.db import models
class Event(models.Model):
description = models.CharField(max_length=255, blank=True)
date = models.DateField('event date')
def __str__(self):
return self.description
class Role(models.Model):
event = models.ForeignKey(Event, on_delete=models.CASCADE)
name = models.CharField(max_length=255, blank=True)
selection = models.IntegerField(default=0)
def __str__(self):
return self.name
- Each class represents a table and each variable represents a field. All the fields must derive from some Django Field class, such as CharField. In each field, we also pass arguments to further describe that field (ex. max_length). If you would like to add different fields, the documentation is great for learning what are required and optional parameters in each field. Also, Django allows relationships to be defined. Here, we are defining a one to many relationship with the foreign key, as there can be multiple Roles associated with each Event. Finally, a str function was created for each class for ease of use later.
- Remember that Django will look in the installed apps setting to create migrations. Add
'signup.apps.SignupConfig'
to the list of installed apps in signupguru/settings/py so that Django knows to also look in the signup app. Next, runpython manage.py makemigrations polls
. This lets python know you've made changes that affect the database, and we need to record the changes in a migration. Finally, runpython manage.py migrate
once more. In general,makemigrations
followed bymigrate
is the proper way to make sure changes are reflected in the database.
- If you want to see your migrations, run
python manage.py sqlmigrate signup 0001
. This will return the SQL that Django plans on using for migration with number 0001 (our first migration).
-
You may have noticed that the admin site was already set up in the projects URL configuration. The admin site allows admins to have control and modify the data that gets put in to the project. To start the process of creating an admin, use
python manage.py createsuperuser
. You will then be instructed to enter some information regarding the admin user. If you start the server and head over to http://127.0.0.1:8000/admin/, you should be able to enter the admin site with the credentials you just provided. -
We need to give the admin permission for anything editable in the admin site. Edit the signup/admin.py file to look like this:
from django.contrib import admin
from .models import Event, Role
class RoleInline(admin.StackedInline):
model = Role
extra = 0
class EventAdmin(admin.ModelAdmin):
inlines = [RoleInline]
admin.site.register(Event, EventAdmin)
-
The key line is the last one, which register our EventAdmin class. The remaining code establishes that whenever an Event is created, the opportunity to create Roles as well is also available. The admin site at this point is fairly intutive. Let's create an event called 'Party' and two Roles - 'Brownies' and 'Cake.' Feel free to add some other Events with Roles as well, but we will be using this one as the example.
-
So far, we have learned how to make basic views, establish models, and work with the admin site. The remainder of the hackpack is going to focus on emulating some functionality of a signup website. We will display the Roles of an event and allow a user to signup for one of the roles.
- Next, we need to add more views. Our index view will show a list of all the events. The detail view will show an event's roles and how many people have already signed up for that role. Add the following view definitions to signup/views.py:
def detail(request, question_id):
return HttpResponse("You're looking at event %s." % event_id)
def vote(request, question_id):
return HttpResponse("Choosing role for event %s." % event_id)
- In addition, the
urlpatterns
in signup/urls.py should look like this:
urlpatterns = [
path('', views.index, name='index'),
path('<int:event_id>/', views.detail, name='detail'),
path('<int:event_id>/vote/', views.vote, name='vote')
]
- Since we now have more views, try adding an id to the end of the url. For example, 'localhost:8000/signup/detail/2/'
-
Right, now these views don't actually do anything.. When I'm on the index page, I want to see all the events, and when I'm on the detail page, I want to see the roles for an event. This is a good time to introduce templates. A django template allows one to easily insert dynamic content within static content and layout. Create a directory templates in the polls app. Within the templates directory, create another directory called polls. This is called namespacing and is a good practice in case multiple apps are being used. Inside the signup template folder, create two templates: index.html and detail.html.
-
Add the following imports and change the index view in signup/views.py to now look like the following.
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from .models import Event, Role
def index(request):
event_list = Event.objects.order_by('date')[:5]
context = {'event_list': event_list}
return render(request, 'signup/index.html', context)
- Let's unpack what we just did. The first two lines grab the 5 most recent events and puts them into the context variable as a dictionary. This context variable is the dynamic data I described earlier. The render method passes along the request object and also takes the template that we are about to create as well as the data we want to insert.
- To finish the index view, let's make the index template. In index.html, use the following:
{% if event_list %}
<ul>
{% for event in event_list %}
<li><a href="{% url 'detail' event.id %}">{{ event.description }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No events are available.</p>
{% endif %}
- As you can see, the template is just HTML with python embedded. You should now see a list of all the recent events in the index view, with each one linking to it's proper detail page. Note how the link looks a little funny.
{% url %}
is actually special syntax that will look up the URL name provided after it. Look in urls.py to see where we defined this name!
- Now let's work on the detailed view and selecting a role. For simplicity, I'll give you all the code first and explain after. In views.py, modify our last two views like so:
def detail(request, event_id):
try:
event = Event.objects.get(pk=event_id)
except Event.DoesNotExist:
raise Http404("Event doesn't exist")
return render(request, 'signup/detail.html', {'event': event})
def vote(request, event_id):
event = get_object_or_404(Event, pk=event_id)
try:
selected_role = event.role_set.get(pk=request.POST['role'])
except (KeyError, Role.DoesNotExist):
# Redisplay the question voting form.
return render(request, 'signup/detail.html', {
'event': event,
'error_message': "You didn't select a role.",
})
else:
selected_role.selection += 1
selected_role.save()
# Always return an HttpResponseRedirect after successfully dealing
# with POST data. This prevents data from being posted twice if a
# user hits the Back button.
return HttpResponseRedirect(reverse('detail', args=(event.id,)))
And in detail.html:
<h1>{{ event_description }}</h1>
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
<form action="{% url 'vote' event.id %}" method="post">
{% csrf_token %}
{% for role in event.role_set.all %}
<input type="radio" name="role" id="role{{ forloop.counter }}" value="{{ role.id }}">
<label for="role{{ forloop.counter }}">{{ role.name }} ({{ role.selection }}) </label><br>
{% endfor %}
<input type="submit" value="Vote">
</form>
- The detail view's job is to show the roles of a specific event. A new problem arises: namely there is a possibility that a user types in an event id into the URL that does not exist. The try catch solves this problem. Again, we use the render function to gracefully incorporate data into our template.
- The template itself is a bit more complicated. A radio button is created for each Role. When the user presses the submit button, the value, which is the Role's id, will be set equal to the name of the input, which is "role". Therefore, what will be passed is role=#. The template will send this back to the vote function that we defined in views.py.
- The vote function is rather self explanatory if you understand everything so far. We grab the role the user selected and increment its count by 1, then send our HttpResponse.
And with that, this hackpack is done! Note that what was covered here is just a small sample of the capabilities of Django. If you want to extend this project, I highly recommend visiting the official Django documentation.