- Virtual Environment
- Installing Django
- Start Django Project
- Start Django App
- Running a development server
Virtual environment enables us to run different versions of dependencies. This is useful if you have multiple projects that depend on different versions of third party applications.
$ virtualenv venv # Create the environment
$ source venv/bin/activate #
(venv) $ python -c 'import sys; print(sys.executable)'
# The python binary will be in the virtual environment
(venv) $ pip install django
(venv) $ django-admin startproject todo_project
(venv) $ cd todo_project/
(venv) $ tree
.
├── manage.py
└── todo_project
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py
1 directory, 5 files
You can now run the development server in the project
(venv) $ ./manage.py runserver
Performing system checks...
System check identified no issues (0 silenced).
August 24, 2016 - 20:36:34
Django version 1.10, using settings 'todo_project.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
Then visit http://127.0.0.1:8000/ - It worked!
We need some content!
(venv) $ ./manage.py startapp todo
(venv) $ tree
.
├── manage.py
├── todo
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
└── todo_project
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py
To tell Django that this new app is to be used we have to add it to the INSTALLED_APPS
variable in settings.py
:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'todo',
]
We start by adding a model which will be a basis for our database structure.
In models.py:
from django.db import models
class Task(models.Model):
content = models.TextField()
completed = models.BooleanField(default=False)
Here we define a model called Task
with two fields:
-
content
is the content of the todo. Therefore we define it as amodels.TextField()
. -
completed
denotes whether theTask
has been completed or not. For this we use amodela.BooleanField()
and specifiy that new entries should have this field marked asFalse
(we do not create task that are born completed).
Now we have to create migrations for our app so django knows how to build the database.
(venv) $ ./manage.py makemigrations
Migrations for 'todo':
todo/migrations/0001_initial.py:
- Create model Task
Then apply the actual migration:
(venv) $ ./manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, sessions, todo
Running migrations:
Rendering model states... DONE
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying sessions.0001_initial... OK
Applying todo.0001_initial... OK
We can easily activate the admin editing todo/admin.py
with the following content:
from django.contrib import admin
from . import models
@admin.register(models.Task)
class TaskAdmin(admin.ModelAdmin):
pass
(venv) $ ./manage.py createsuperuser
Username (leave blank to use 'valberg'):
Email address:
Password:
Superuser created successfully.
Beware of the password constraints!
As before start the development server like this:
(venv) $ ./manage.py runserver
Performing system checks...
System check identified no issues (0 silenced).
August 24, 2016 - 20:36:34
Django version 1.10, using settings 'todo_project.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
Now go to http://127.0.0.1:8000/admin/
Now you can create a Task.
Tasks will be represented as "Task object". We can fix that by overriding the __str__
method of the model:
class Task(models.Model):
content = models.TextField()
completed = models.BooleanField(default=False)
def __str__(self):
return self.content
We can do some nifty things with the admin. Add list_display
to the admin to show more columns:
@admin.register(models.Task)
class TaskAdmin(admin.ModelAdmin):
list_display = ['content', 'completed']
Now we can see if an Task has been completed or not.
The admin is good for admin work and checking if your model actually makes sense. But we don't want our users to use the admin. We should make something ourselves for this.
For that we need three things:
- A view
- A template
- An URL conf
We need to have some code which fetches data from the database and renders a template with the data.
We already have a file called todo/views.py
, this is where we will write the views for our application.
We start by writing a "function based view". Django also supports "class based views", but for learning purposes we will only work with "function based views".
todo/views.py
should look like:
from django.shortcuts import render
from . import models
def task_list(request, **kwargs):
tasks = models.Task.objects.all()
context = {'tasks': tasks}
return render(request, 'task_list.html', context)
tasks = models.Task.objects.all()
is where we query our database for allTask
entries. This returns aQuerySet
.context = {'tasks': tasks}
; here we create our context which will be used to render the template with.- At last we
return render(request, 'task_list.html', context)
which means that we return a response in the request-response cycle with thetask_list.html
template rendered using thecontext
context.
The view refers to a template, lets write it!
We should have a place to store our templates. Django searches for templates in <app-name>/templates/
by default.
So we create a todo/templates/
directory. In this we want only one template. Lets call it todo/templates/task_list.html
.
The template should look like this:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Todo</title>
</head>
<body>
<ul>
{% for task in tasks %}
<li>
{{ task.content }} -
{% if task.completed %}
Done!
{% else %}
<a href="">Mark as done</a>
{% endif %}
</li>
{% endfor %}
</ul>
</body>
</html>
Django has a template language which is quite straight forward:
{% for task in tasks %}
tells the template engine to iterate through thetasks
query we made in the view. A{% for ... %}
always ends with a{% endfor %}
{% if task.completed %}
makes a check for the boolean value of the give variable, in this casetaske.completed
. In this specific code it means that iftask.completed
isTrue
we will show "Done!", otherwise we'll show a link to something that marks the given task as completed. A{% if ... %}
always ends with a{% endif %}
.{{ task.content }}
means that we want to access thecontent
attribute of thetask
object.
Every time a request comes in, the URL requested will be used to figure out which view to use. This mapping we call an URL Conf. The main URL conf is placed in todo_project/urls.py
.
from django.conf.urls import url
from django.contrib import admin
from todo import views
url(r'^$', views.todo_list, name='todo_list'),
url(r'^admin/', admin.site.urls),
]
To create a task we have to make some additions to our code:
Update todo/templates/task_list.html
like this (in the <body> tags):
<form method="POST">
{% csrf_token %}
<input type="text" name="content" />
<button type="submit">Add</button>
</form>
<hr />
<ul>
{% for task in tasks %}
<li>
{{ task.content }} -
{% if task.completed %}
Done!
{% else %}
<a href="">Mark as done</a>
{% endif %}
</li>
{% endfor %}
</ul>
Update views.py
like this:
def task_list(request, **kwargs):
if request.method == "POST":
task_content = request.POST['content']
new_task = models.Task(content=task_content)
new_task.save()
return redirect('task-list')
tasks = models.Task.objects.all()
context = {'tasks': tasks}
return render(request, 'task_list.html', context)
To mark a task as fixed we write a very simple view:
def task_complete(request, **kwargs):
pk = kwargs.get('pk')
task = models.Task.objects.get(pk=pk)
task.completed = True
task.save()
return redirect('task-list')
And then we add this view to the URL conf:
urlpatterns = [
url(r'^$', views.task_list, name='task-list'),
url(r'^(?P<pk>\d+)/$', views.task_complete, name='task-complete'),
url(r'^admin/', admin.site.urls),
]
And last we update the template to use this new URL when pressing the completion-link:
<form method="POST">
{% csrf_token %}
<input type="text" name="content" />
<button type="submit">Add</button>
</form>
<hr />
<ul>
{% for task in tasks %}
<li>
{{ task.content }} -
{% if task.completed %}
Done!
{% else %}
<a href="{% url 'task-complete' pk=task.pk %}">Mark as done</a>
{% endif %}
</li>
{% endfor %}
</ul>
Here we are using the {% url ... %}
template tag, which uses the names from the URLs to figure out what to link to.
- Separate completed and non-completed tasks (hint: use the QuerySet
.filter()
method) - Make it possible to delete tasks
- Make it possible to mark already completed tasks as not completed.
- Make it possible to update the content of tasks
- Use Django forms