Djangogirls Tutorial
Djangogirls Tutorial
Djangogirls Tutorial
1. Introduction
2. How the Internet works?
3. Introduction to command line
4. Python installation
5. Introduction to Python
6. What is Django?
7. Django installation
8. Code editor
9. Starting Django project
10. Django models
11. Django ORM (Querysets)
12. Django admin
13. Deploy!
14. Django urls
15. Django views - time to create!
16. Introduction to HTML
17. Dynamic data in templates
18. Django templates
19. CSS - make it pretty
20. Template extending
21. Extend your application
22. Django Forms
23. Domain
24. What's next?
JOIN CHAT
This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License. To view a
copy of this license, visit http://creativecommons.org/licenses/by-sa/4.0/
Introduction
Have you ever felt that the world is more and more about technology and you are somehow left behind? Have you ever
wondered how to create a website but have never had enough motivation to start? Have you ever thought that the
software world is too complicated for you to even try doing something on your own?
Well, we have good news for you! Programming is not as hard as it seems and we want to show you how fun it can be.
The tutorial will not magically turn you into a programmer. If you want to be good at it, you need months or even years of
learning and practice. But we want to show you that programming or creating websites is not as complicated as it seems.
We will try to explain different bits and pieces as well as we can, so you will not feel intimidated by technology.
We hope that we'll be able to make you love technology as much as we do!
If you work with the tutorial on your own and don't have a coach that will help you in case of any problem, we have
a chat for you:
GITTER
JOIN CHAT . We asked our coaches and previous attendees to be there from time to
time and help others with the tutorial! Don't be afraid to ask your question there!
OK, let's start at the beginning...
Looks like a mess, right? In fact it is a network of connected machines (the above mentioned servers). Hundreds of
thousands of machines! Many, many kilometers of cables around the world! You can visit a Submarine Cable Map
website (http://submarinecablemap.com/) to see how complicated the net is. Here is a screenshot from the website:
It is fascinating, isn't it? But obviously, it is not possible to have a wire between every machine connected to the Internet.
So, to reach a machine (for example the one where http://djangogirls.org is saved) we need to pass a request through
many, many different machines.
It looks like this:
Imagine that when you type http://djangogirls.org, you send a letter that says: "Dear Django Girls, I want to see the
djangogirls.org website. Send it to me, please!"
Your letter goes to the post office closest to you. Then it goes to another that is a bit nearer to your addressee, then to
another and another till it is delivered at its destination. The only unique thing is that if you send letters (data packets)
frequently to the same place, each letter might go through totally different post offices (routers), depending on how they
are distributed in each office.
Yes, it is as simple as that. You send messages and you expect some response. Of course, instead of paper and pen you
use bytes of data, but the idea is the same!
Instead of addresses with a street name, city, zip code and country name, we use IP addresses. Your computer first asks
the DNS (Domain Name System) to translate djangogirls.org into an IP address. It works a little bit like old-fashioned
phonebooks where you could look for the name of the person you want to contact and find their phone number and
address.
When you send a letter, it needs to have certain features to be delivered correctly: an address, stamp etc. You also use a
language that the receiver understands, right? The same is with data packets you send in order to see a website: you use
a protocol called HTTP (Hypertext Transfer Protocol).
So, basically, when you have a website you need to have a server (machine) where it lives. The server is waiting for any
incoming requests (letters that ask the server to send your website) and it sends back your website (in another letter).
Since this is a Django tutorial, you will ask what Django does. When you send a response, you don't always want to send
the same thing to everybody. It is so much better if your letters are personalized, especially for the person that has just
written to you, right? Django helps you with creating these personalized, interesting letters :).
Enough talk, time to create!
Windows
Go to Start menu All Programs Accessories Command Prompt.
Mac OS X
Applications Utilities Terminal.
Linux
It's probably under Applications Accessories Terminal, but that may depend on your version system. If it's not there,
just Google it :)
Prompt
You now should see a white or black window that is waiting for your commands.
If you're on Mac or Linux, you probably see
On Windows, it's a
>
>
Each command will be prepended by this sign and one space, but you don't have to type it. Your computer will do it for
you :)
Just a small note: in your case there maybe something like
C:\Users\ola>
or
Olas-MacBook-Air:~ ola$
prompt sign and that's 100% correct. In this tutorial we will just simplify it to the bare minimum.
before the
$ whoami
or
> whoami
$ whoami
olasitarska
As you can see, the computer just presented you your username. Neat, huh?:)
Try to type each command, do not copy paste. You'll remember more this way!
Basics
Each operating system has a slightly different set of commands for the command line, so make sure to follow instructions
for your operating system. Let's try this, shall we?
Current directory
It'd be nice to know where are we now, right? Let's see. Type this command and hit enter:
$ pwd
/Users/olasitarska
If you're on Windows:
> cd
C:\Users\olasitarska
You'll probably see something similiar on your machine. Once you open the command line you usually start at your user's
home directory.
Note: 'pwd' stands for 'print working directory'.
$ ls
Applications
Desktop
Downloads
Music
...
Windows:
> dir
Directory of C:\Users\olasitarska
05/08/2014 07:28 PM <DIR>
Applications
05/08/2014 07:28 PM <DIR>
Desktop
05/08/2014 07:28 PM <DIR>
Downloads
05/08/2014 07:28 PM <DIR>
Music
...
$ cd Desktop
Windows:
> cd Desktop
$ pwd
/Users/olasitarska/Desktop
Windows:
> cd
C:\Users\olasitarska\Desktop
Here it is!
PRO tip: if you type
cd D
tab
on your keyboard, the command line will automatically autofill the rest of
the name so you can navigate faster. If there is more than one folder starting with "D", hit the
tab
a list of options.
Create directory
How about creating a Django Girls directory on your desktop? You can do it this way:
$ mkdir djangogirls
Windows:
ls
djangogirls
PRO tip: If you don't want to type the same commands over and over, try pressing the
your keyboard to cycle through recently used commands.
up arrow
and
down arrow
on
Exercise!
Small challenge for you: in your newly created
djangogirls
test
. Use
cd
and
mkdir
commands.
Solution:
$ cd djangogirls
$ mkdir test
$ ls
test
Windows:
> cd djangogirls
> mkdir test
> dir
05/08/2014 07:28 PM <DIR>
test
Congrats! :)
Clean up
We don't want to leave a mess, so let's remove everything we did until that point.
First, we need to get back to Desktop:
$ cd ..
Windows:
> cd ..
Making
cd
to
..
will change your current directory to the parent directory (which means the directory that contains your
current directory).
Check where you are:
$ pwd
/Users/olasitarska/Desktop
Windows:
> cd
C:\Users\olasitarska\Desktop
djangogirls
directory.
del
rmdir
or
rm
$ rm -r djangogirls
Windows:
$ ls
Windows:
> dir
Exit
That's it for now! You can safely close the command line now. Let's do it the hacker way, all right?:)
$ exit
Windows:
> exit
Cool, huh?:)
Summary
Here is a summary of some useful commands:
Command
(Windows)
Command (Mac OS /
Linux)
Description
Example
exit
exit
exit
cd
cd
change directory
cd test
dir
ls
list directories/files
dir
copy
cp
copy file
copy c:\test\test.txt
c:\windows\test.txt
move
mv
move file
move c:\test\test.txt
c:\windows\test.txt
mkdir
mkdir
create a new
directory
mkdir testdirectory
del
rm
delete a
directory/file
del c:\test\test.txt
These are just a very few of the commands you can run in your command line but you're not going to use anything more
than that today.
If you're curious, ss64.com contains a complete reference of commands for all operating systems.
Ready?
Let's dive into Python!
Python installation
This subchapter is based on tutorial by Geek Girls Carrots (http://django.carrots.pl/)
Django is written in Python. We need Python to do anything in Django. Let's start with installing it! We want you to install
Python 3.4, so if you have any earlier version, you will need to upgrade it.
Windows
You can download Python for Windows from the website https://www.python.org/downloads/release/python-342/. After
downloading the *.msi file, you should run it (double-click on it) and follow the instructions there. It is important to
remember the path (the directory) where you installed Python. It will be needed later!
Linux
It is very likely that you already have Python installed out of the box. To check if you have it installed (and which version it
is), open a console and type the following command:
$ python3 --version
Python 3.4.2
If you don't have Python installed or if you want a different version, you can install it as follows:
Ubuntu
Type this command into your console:
Fedora
Use this command in your console:
OS X
You need to go to the website https://www.python.org/downloads/release/python-342/ and download the Python installer:
download the Mac OS X 64-bit/32-bit installer DMG file,
double click to open it,
python3
command:
$ python3 --version
Python 3.4.2
If you have any doubts or if something went wrong and you have no idea what to do next - please ask your coach!
Sometimes things are not going smoothly and it's better to ask for help from someone with more experience.
Introduction to Python
Part of this chapter is based on tutorials by Geek Girls Carrots (http://django.carrots.pl/).
Let's write some code!
Python prompt
To start playing with Python, we need to open up a command line on your computer. You should have already knew how
to do that -- you have learned it in the Intro to Command Line chapter.
Once you're ready, follow the instructions below.
We want to open up a Python console, so type in
python3
$ python3
Python 3.4.2 (...)
Type "copyright", "credits" or "license" for more information.
>>>
>>>
>>>
>>>
exit()
Ctrl + Z
Ctrl + D
any longer.
But now, we don't want to exit the Python console. We want to learn more about it. Let's start with something really simple.
For example, try typing some math, like
2+3
>>> 2 + 3
5
Nice! See how the answer popped out? Python knows math! You could try other commands like:
4*5
5-1
40 / 2
Have fun with this for a little while and then get back here :).
As you can see, Python is a great calculator. If you're wondering what else you can do...
Strings
How about your name? Type your first name in quotes like this:
>>> "Ola"
'Ola'
You've now created your first string! It's a set of characters that can be processed by a computer. The string must always
begin and end with the same character. This may be single ( ' ) or double ( " ) quotes - they tell Python that what's inside
of them is a string.
Strings can be strung together. Try this:
>>> "Ola" * 3
'OlaOlaOla'
If you need to put an apostrophe inside your string, you have two ways to do it.
Using double quotes:
>>> "Ola".upper()
'OLA'
upper
upper()
>>> len("Ola")
3
"Ola".upper()
function and place the string in parentheses? Well, in some cases, functions belong to objects, like
upper()
, which can
only be performed on strings. In this case, we call the function a method. Other times, functions don't belong to anything
specific and can be used on different types of objects, just like
len
len()
"Ola"
function.
Summary
OK, enough of strings. So far you've learned about:
the prompt - typing commands (code) into the Python prompt results in answers in Python
numbers and strings - in Python numbers are used for math and strings for text objects
operators - like + and *, combine values to produce a new one
functions - like upper() and len(), perform actions on objects.
as a parameter to the
These are the basics of every programming language you learn. Ready for something harder? We bet you are!
Errors
Let's try something new. Can we get the length of a number the same way we could find out the length of our name? Type
in
len(304023)
>>> len(304023)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: object of type 'int' has no len()
We got our first error! It says that objects of type "int" (integers, whole numbers) have no length. So what can we do now?
Maybe we can write our number as a string? Strings have a length, right?
>>> len(str(304023))
6
str
len
The
str
The
int
function.
str()
Important: we can convert numbers into text, but we can't necessarily convert text into numbers - what would
int('hello')
be anyway?
Variables
An important concept in programming is variables. A variable is nothing more than a name for something so you can use
it later. Programmers use these variables to store data, make their code more readable and so they don't have to keep
remembering what things are.
Let's say we want to create a new variable called
name
name
>>> name
'Ola'
Yippee! Your first variable :)! You can always change what it refers to:
>>> len(name)
5
Awesome, right? Of course, variables can be anything, so numbers too! Try this:
>>> a = 4
>>> b = 6
>>> a * b
24
But what if we used the wrong name? Can you guess what would happen? Let's try!
An error! As you can see, Python has different types of errors and this one is called a NameError. Python will give you
this error if you try to use a variable that hasn't been defined yet. If you encounter this error later, check your code to see if
you've mistyped any names.
Play with this for a while and see what you can do!
name
, the Python interpreter responds with the string representation of the variable 'name', which is
the letters M-a-r-i-a, surrounded by single quotes, ''. When you say
print(name)
print()
is also useful when we want to print things from inside functions, or when we want to print things
on multiple lines.
Lists
Beside strings and integers, Python has all sorts of different types of objects. Now we're going to introduce one called list.
Lists are exactly what you think they are: they are objects which are lists of other objects :)
Go ahead and create a list:
>>> []
[]
Yes, this list is empty. Not very useful, right? Let's create a list of lottery numbers. We don't want to repeat ourselves all the
time, so we will put it in a variable, too:
All right, we have a list! What can we do with it? Let's see how many lottery numbers there are in a list. Do you have any
idea which function you should use for that? You know this already!
>>> len(lottery)
6
Yes!
len()
can give you a number of objects in a list. Handy, right? Maybe we will sort it now:
>>> lottery.sort()
This doesn't return anything, it just changed the order in which the numbers appear in the list. Let's print it out again and
see what happened:
>>> print(lottery)
[3, 12, 19, 30, 42, 59]
As you can see, the numbers in your list are now sorted from the lowest to highest value. Congrats!
Maybe we want to reverse that order? Let's do that!
>>> lottery.reverse()
>>> print(lottery)
[59, 42, 30, 19, 12, 3]
Easy, right? If you want to add something to your list, you can do this by typing this command:
>>> lottery.append(199)
>>> print(lottery)
[59, 42, 30, 19, 12, 3, 199]
If you want to show only the first number, you can do this by using indexes. An index is the number that says where in a
list an item occurs. Computer people like to start counting at 0, so the first object in your list is at index 0, the next one is at
1, and so on. Try this:
>>> print(lottery[0])
59
>>> print(lottery[1])
42
As you can see, you can access different objects in your list by using the list's name and the object's index inside of
square brackets.
For extra fun, try some other indexes: 6, 7, 1000, -1, -6 or -1000. See if you can predict the result before trying the
command. Do the results make sense?
You can find a list of all available list methods in this chapter of the Python documentation:
https://docs.python.org/3/tutorial/datastructures.html
Dictionaries
A dictionary is similar to a list, but you access values by looking up a key instead of an index. A key can be any string or
number. The syntax to define an empty dictionary is:
>>> {}
{}
>>> participant = {'name': 'Ola', 'country': 'Poland', 'favorite_numbers': [7, 42, 92]}
and
name
points to
'Poland'
favorite_numbers
(another
points to
'Ola'
(a
string
string
participant
object),
),
(a
list
You can check the content of individual keys with this syntax:
>>> print(participant['name'])
Ola
See, it's similar to a list. But you don't need to remember the index - just the name.
What happens if we ask Python the value of a key that doesn't exist? Can you guess? Let's try it and see!
>>> participant['age']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'age'
Look, another error! This one is a KeyError. Python is helpful and tells you that the key
'age'
dictionary.
When to use a dictionary or a list? Well, that's a good point to ponder on. Just have a solution in mind before looking at
the answer in the next line.
Do you just need an ordered sequence of items? Go for a list.
Do you need to associate values with keys, so you can look them up efficiently (by key) later on? Use a dictionary.
Dictionaries, like lists, are mutable, meaning that they can be changed after they are created. You can add new key/value
pairs to the dictionary after it is created, like:
len()
method on the dictionaries, returns the number of key-value pairs in the dictionary. Go ahead and
>>> len(participant)
4
I hope it makes sense up to now. :) Ready for some more fun with dictionaries? Hop onto the next line for some amazing
things.
You can use the
the key
del
command to delete an item in the dictionary. Say, if you want to delete the entry corresponding to
As you can see from the output, the key-value pair corresponding to the 'favorite_numbers' key has been deleted.
As well as this, you can also change a value associated with an already created key in the dictionary. Type:
'country'
'Poland'
to
'Germany' . :)
Summary
Awesome! You know a lot about programming now. In this last part you learned about:
errors - you now know how to read and understand errors that show up if Python doesn't understand a command
you've given it
variables - names for objects that allow you to code more easily and to make your code more readable
lists - lists of objects stored in a particular order
dictionaries - objects stored as key-value pairs
Excited for the next part? :)
Compare things
A big part of programming includes comparing things. What's the easiest thing to compare? Numbers, of course. Let's see
how that works:
>>> 5
True
>>> 3
False
>>> 5
True
>>> 1
True
>2
<1
>2*2
== 1
We gave Python some numbers to compare. As you can see, Python can compare not only numbers, but it can also
compare method results. Nice, huh?
Do you wonder why we put two equal signs
==
for assigning values to variables. You always, always need to put two
other.
Give Python two more tasks:
==
>>> 6 >= 12 / 2
True
>>> 3 <= 2
False
>
and
<
>=
and
<=
>
<
<=
>=
You can give Python as many numbers to compare as you want, and it will give you an answer! Pretty smart, right?
and - if you use the
or - if you use the
and
or
operator, both comparisons have to be True in order for the whole command to be True
operator, only one of the comparisons has to be True in order for the whole command to be
True
Have you heard of the expression "comparing apples to oranges"? Let's try the Python equivalent:
Here you see that just like in the expression, Python is not able to compare a number ( int ) and a string ( str ). Instead, it
shows a TypeError and tells us the two types can't be compared together.
Boolean
Incidentally, you just learned about a new type of object in Python. It's called a Boolean -- and it probably is the easiest
type there is.
There are only two Boolean objects:
True
False
But for Python to understand this, you need to always write it as True (first letter uppercased, with the rest of the letter
lowercased). true, TRUE, tRUE won't work -- only True is correct. (The same applies to False as well, of course.)
Booleans can be variables, too! See here:
>>> a = True
>>> a
True
>>> a = 2 > 5
>>> a
False
Practice and have fun with Booleans by trying to run the following commands:
True and True
False and True
True or 1 == 1
1 != 2
If...elif...else
Lots of things in code should only be executed when given conditions are met. That's why Python has something called if
statements.
Try this:
>>> if 3 > 2:
...
...
instead of incentives
>>>
expects us to give further instructions to it which are supposed to be executed if the condition
(or
True
3>2
>>> if 3 > 2:
... print('It works!')
File "<stdin>", line 2
print('It works')
^
IndentationError: expected an indented block
Well... something went wrong here! Python needs to know whether the instruction we have written is a continuation of
or a next instruction not covered by the condition. We need to indent our code to make it work:
>>> if 3 > 2:
...
print('It works!')
...
It works!
...
. To avoid chaos, most Python programmers use four spaces for each level of
indentation.
Everything that is indented after the
>>> if 3 > 2:
...
print('It works!')
...
print('Another command')
...
It works!
Another command
if
if
What if not?
In previous examples, code was executed only when the conditions were True. But Python also has
elif
and
else
statements:
>>> if 5 > 2:
...
print('5 is indeed greater than 2')
... else:
...
print('5 is not greater than 2')
...
5 is indeed greater than 2
If 2 were a greater number than 5, then the second command would be executed. Easy, right? Let's see how
elif
works:
Summary
In the last three exercises you learned about:
comparing things - in Python you can compare things by using
>
Boolean - a type of object that can only have one of two values:
True
>=
or
==
<=
<
and the
and
or
operators
False
if...elif...else - statements that allow you to execute code only when certain conditions are met.
Time for the last part of this chapter!
len()
that you can execute in Python? Well, good news, you will learn how to write your own
functions now!
A function is a set of instructions that Python should execute. Each function in Python starts with the keyword
def
, is given
a name and can have some parameters. Let's start with an easy one:
As you can see, there are those dots again! This means that nothing has really happened yet... and yes, we need to press
the
Space
OK, our first function is ready! Press Enter to get back to the Python prompt again. Now let's execute our function:
>>> hi()
Hi there!
How are you?
As you can see, we now gave our function a parameter that we called
name
function, because
if
>>> hi()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: hi() missing 1 required positional argument: 'name'
Oops, an error. Luckily, Python gives us a pretty useful error message. It tells us that the function
defined) has one required argument (called
name
hi()
(the one we
) and that we forgot to pass it when calling the function. Let's fix it then:
>>> hi("Ola")
Hi Ola!
>>> hi("Sonja")
Hi Sonja!
>>> hi("Anja")
Hi anonymous!
Awesome, right? This way you don't have to repeat yourself every time you want to change the name of the person the
function is supposed to greet. And that's exactly why we need functions - you never want to repeat your code!
Let's do something smarter -- there are more names than two, and writing a condition for each would be hard, right?
>>> hi("Rachel")
Hi Rachel!
Loops
That's the last part already. That was quick, right? :)
As we mentioned, programmers are lazy, they don't like to repeat themselves. Programming is all about automating
things, so we don't want to greet every person by their name manually, right? That's where loops come in handy.
Still remember lists? Let's do a list of girls:
hi
Dots again! Remember what goes after the dots? Yes, a space :)
for
statement with an indent will be repeated for every element of the list
for
range
method:
range
is a function that creates a list of numbers following one after the other (these numbers are provided by you as
parameters).
Note that the second of these two numbers is not included in the list that is output by Python (meaning
from 1 to 5, but does not include the number 6).
Exiting Python
You can exit Python and return to the command line using
exit()
range(1, 6)
counts
Summary
That's it. You totally rock! This really wasn't so easy, so you should feel proud of yourself. We're definitely proud of you
for making it to here!
Grab yourself a cupcake and go to the next chapter :)
What is Django?
Django (/do/ jang-goh) is a free and open source web application framework, written in Python. It's a web
framework - a set of components that helps you to develop websites faster and easier.
You see, when you're building a website, you always need a similiar set of components: a way to handle user
authentication (signing up, signing in, signing out), a management panel for your website, forms, a way to upload files,
etc.
Luckily for you other people long ago noticed that web developers face similar problems when building a new site, so
they teamed up and created frameworks (Django is one of them) that give you ready-made components you can use.
Frameworks exist to save you from having to reinvent the wheel and help alleviate some of the overhead when youre
building a new site.
Django installation
Part of this chapter is based on tutorials by Geek Girls Carrots (http://django.carrots.pl/).
Part of this chapter is based on the django-marcador tutorial licensed under Creative Commons AttributionShareAlike 4.0 International License. The django-marcador tutorial is copyrighted by Markus Zapke-Grndemann
et al.
Virtual environment
Before we install Django, we'll get you to install an extremely useful tool that will help keep your coding environment tidy
on your computer. It's possible to skip this step, but it's highly recommended not to - starting with the best possible setup
will save you a lot of trouble in the future!
So, let's create a virtual environment (also called a virtualenv). It will isolate your Python/Django setup on a per-project
basis, meaning that any changes you make to one website won't affect any others you're also developing. Neat, right?
All you need to do is find a directory in which you want to create the
Windows it might look like
C:\Users\Name\
(where
Name
virtualenv ; your
djangogirls
mkdir djangogirls
cd djangogirls
myvenv . The
Windows
To create a new
run
virtualenv , you
need to open the console (we told you about that a few chapters ago - remember?) and
where
C:\Python34\python
virtualenv . You
myvenv
can use any other name, but stick to lowercase and use no spaces. It is also good idea to keep the name
Linux and OS X
Creating a
virtualenv
myvenv
virtualenv . You
can use any other name, but stick to lowercase and use no spaces. It is also
Error: Command '['/home/eddie/Slask/tmp/venv/bin/python3', '-Im', 'ensurepip', '--upgrade', '--default-pip']' returned non-zero exit status 1
virtualenv
command instead.
myvenv
environment (basically a bunch of directory and files). All we want to do now is start it by running:
C:\Users\Name\djangogirls> myvenv\Scripts\activate
on Windows, or:
on OS X and Linux.
Remember to replace
NOTE: sometimes
myvenv
source
virtualenv
name!
~/djangogirls$ . myvenv/bin/activate
virtualenv
started when you see that the prompt in your console looks like:
(myvenv) C:\Users\Name\djangogirls>
or:
(myvenv) ~/djangogirls$
(myvenv)
appears!
python3
python
python
OK, we have all important dependencies in place. We can finally install Django!
Installing Django
Now that you have your
virtualenv
==
).
pip
on Windows
If you get an error when calling pip on Windows platform please check if your project pathname contains spaces
(i.e.
is:
C:\Users\User Name\djangogirls
C:\djangogirls
on Linux
If you get an error when calling pip on Ubuntu 12.04 please run
to fix the
Code editor
You're about to write your first line of code, so it's time to download a code editor!
There are a lot of different editors and it largely boils down to personal preference. Most Python programmers use
complex but extremely powerful IDEs (Integrated Development Environments), such as PyCharm. As a beginner,
however, that's probably less suitable; our recommendations are equally powerful, but a lot simpler.
Our suggestions are below, but feel free to ask your coach what their preferences are - it'll be easier to get help from
them.
Gedit
Gedit is an open-source, free editor, available for all operating systems.
Download it here
Sublime Text 2
Sublime Text is a very popular editor with a free evaluation period. It's easy to install and use, and it's available for all
operating systems.
Download it here
Atom
Atom is an extremely new code editor created by GitHub. It's free, open-source, easy to install and easy to use. It's
available for Windows, OSX and Linux.
Download it here
(myvenv) ~/djangogirls$
, OK?):
(myvenv)
activate your virtualenv. We explained how to do that in the Django installation chapter in the Working with
virtualenv part.
Run on Windows:
Double-check that you included the period ( . ) at the end of the command, it's important.
django-admin
is a script that will create the directories and files for you. You should now have a directory structure which
djangogirls
manage.py
mysite
settings.py
urls.py
wsgi.py
__init__.py
manage.py
is a script that helps with management of the site. With it we will be able to start a web server on our computer
settings.py
urlresolver
urls.py
Let's ignore the other files for now - we won't change them. The only thing to remember is not to delete them by accident!
Changing settings
mysite/settings.py . Open
USE_TZ
and
TIME_ZONE
Europe/Berlin
Europe/Berlin
with
USE_TZ = False
TIME_ZONE = 'Europe/Berlin'
Setup a database
There's a lot of different database software that can store data for your site. We'll use the default one,
This is already set up in this part of your
mysite/settings.py
sqlite3
file:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
To create a database for our blog, let's run the following in the console:
djangogirls
file). If that goes well, you should see something like this:
manage.py
And we're done! Time to start the web server and see if our website is working!
You need to be in the directory that contains the
web server by running
manage.py
file (the
djangogirls
Now all you need to do is check that your website is running - open your browser (Firefox, Chrome, Safari, Internet
Explorer or whatever you use) and enter the address:
http://127.0.0.1:8000/
You can stop the web server again (e.g. to type other commands on the command prompt) by pressing CTRL+C - Control
and C buttons together.
Congratulations! You've just created your first website and run it using a web server! Isn't that awesome?
Ready for the next step? It's time to create some content!
Django models
What we want to create now is something that will store all posts in our blog. But to be able to do that we need to talk a
little bit about things called
objects
Objects
There is a concept in programming called
Object-oriented programming
boring sequence of programming instructions we can model things and define how they interact with each other.
So what is an object? It is a collection of properties and actions. It sounds weird, but we will give you an example.
If we want to model a cat we will create an object
sleepy ;)),
owner
Cat
(that is a
Person
Cat
color
age
mood
purr
taste
scratch
or
feed
CatFood
, which could be a
).
Cat
-------color
age
mood
owner
purr()
scratch()
feed(cat_food)
CatFood
-------taste
So basically the idea is to describe real things in code with properties (called
methods
object properties
).
How will we model blog posts then? We want to build a blog, right?
We need to answer the question: what is a blog post? What properties should it have?
Well, for sure our blog post needs some text with its content and a title, right? It would be also nice to know who wrote it so we need an author. Finally, we want to know when the post was created and published.
Post
-------title
text
author
created_date
published_date
What kind of things could be done with a blog post? It would be nice to have some
So we will need
publish
method
method.
Since we already know what we want to achieve, we can start modeling it in Django!
Django model
Knowing what an object is, we can create a Django model for our blog post.
A model in Django is a special kind of object - it is saved in the
database
place in which you will store information about users, your blog posts, etc. We will be using a SQLite database to store
our data. This is the default Django database adapter -- it'll be enough for us right now.
You can think of a model in the database as a spreadsheet with columns (fields) and rows (data).
Creating an application
To keep everything tidy, we will create a separate application inside our project. It is very nice to have everything
organized from the very beginning. To create an application we need to run the following command in the console (from
djangogirls
directory where
manage.py
file is):
blog
directory is created and it contains a number of files now. Our directories and files in our
djangogirls
mysite
|
__init__.py
|
settings.py
|
urls.py
|
wsgi.py
manage.py
blog
migrations
|
__init__.py
__init__.py
admin.py
models.py
tests.py
views.py
After creating an application we also need to tell Django that it should use it. We do that in the file
need to find
INSTALLED_APPS
'blog',
just above
mysite/settings.py . We
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'blog',
)
blog/models.py
Let's open
blog/models.py , remove
Models
str
Python and sometimes we also call them "dunder" (short for "double-underscore").
It is scary, right? But no worries, we will explain what these lines mean!
All lines starting with
from
or
import
are lines that add some bits from other files. So instead of copying and pasting the
class
Post
object
).
means that the Post is a Django Model, so Django knows that it should be saved in the database.
title
text
created_date
published_date
and
author
. To do that we
need to define a type of field (is it text? A number? A date? A relation to another object, i.e. a User?).
models.CharField
models.TextField
- this is for long texts without a limit. It will be ideal for a blog post content, right?
- this is a date and time.
models.DateTimeField
models.ForeignKey
We will not explain every bit of code here, since it would take too much time. You should take a look at Django's
documentation, if you want to know more about Model fields and how to define things other than those described above
(https://docs.djangoproject.com/en/1.7/ref/models/fields/#field-types).
What about
def publish(self):
function/method.
publish
? It is exactly our
publish
def
is the name of the method. You can change it, if you want. The rule is that we use lowercase
and underscores instead of whitespaces (i.e. if you want to have a method that calculates average price you could call it
calculate_average_price
).
return
__str__
If something is still not clear about models, feel free to ask your coach! We know it is very complicated, especially when
you learn what objects and functions are at the same time. But hopefully it looks slightly less magic for you now!
Django prepared for us a migration file that we have to apply now to our database, type
Hurray! Our Post model is now in our database, it would be nice to see it, right? Jump to the next chapter to see what your
Post looks like!
What is a QuerySet?
A QuerySet is, in essence, a list of objects of a given Model. QuerySet allows you to read the data from database, filter it
and order it.
It's easiest to learn by example. Let's try this, shall we?
Django shell
Open up your console and type this command:
(InteractiveConsole)
>>>
You're now in Django's interactive console. It's just like Python prompt but with some additional Django magic :). You can
use all the Python commands here too, of course.
All objects
Let's try to display all of our posts first. You can do that with the following command:
>>> Post.objects.all()
Traceback (most recent call last):
File "<console>", line 1, in <module>
NameError: name 'Post' is not defined
Oops! An error showed up. It tells us that there is no Post. It's correct -- we forgot to import it first!
Post
from
blog.models
>>> Post.objects.all()
[]
It's an empty list. That seems to be correct, right? We didn't add any posts yet! Let's fix that.
Create object
This is how you create a Post object in database:
user
User
that?
Let's import User model first:
>>> User.objects.all()
[]
>>> User.objects.create(username='ola')
<User: ola>
>>> User.objects.all()
[<User: ola>]
user = User.objects.get(username='ola')
get
User
with a
username
username.
Now we can finally create our first post:
>>> Post.objects.all()
[<Post: Sample title>]
Filter objects
A big part of QuerySets is an ability to filter them. Let's say, we want to find all posts that are authored by User ola. We will
use
filter
instead of
all
in
Post.objects.all()
author
that is equal to
user
author=user
>>> Post.objects.filter(author=user)
[<Post: Sample title>, <Post: Post number 2>, <Post: My 3rd post!>, <Post: 4th title of post>]
Or maybe we want to see all the posts that contain a word 'title' in the
title
field?
>>> Post.objects.filter(title__contains='title')
[<Post: Sample title>, <Post: 4th title of post>]
title
and
contains
field names ("title") and operations or filters ("contains"). If you only use one underscore, you'll get an error like
"FieldError: Cannot resolve keyword title_contains".
You can also get a list of all published posts. We do it by filtering all the posts that have
published_date
>>> Post.objects.filter(published_date__isnull=False)
[]
Unfortunately, none of our posts are published yet. We can change that! First get an instance of a post we want to publish:
publish
method!
>>> post.publish()
Now try to get list of published posts again (press the up arrow button 3 times and hit Enter):
>>> Post.objects.filter(published_date__isnull=False)
[<Post: Sample title>]
Ordering objects
QuerySets also allow you to order the list of objects. Let's try to order them by
created_date
>>> Post.objects.order_by('created_date')
[<Post: Sample title>, <Post: Post number 2>, <Post: My 3rd post!>, <Post: 4th title of post>]
at the beginning:
>>> Post.objects.order_by('-created_date')
[<Post: 4th title of post>, <Post: My 3rd post!>, <Post: Post number 2>, <Post: Sample title>]
Cool! You're now ready for the next part! To close the shell, type this:
>>> exit()
$
field:
Django admin
To add, edit and delete posts we've just modeled, we will use Django admin.
Let's open the
blog/admin.py
As you can see, we import (include) the Post model defined in the previous chapter. To make our model visible on the
admin page, we need to register the model with
admin.site.register(Post)
http://127.0.0.1:8000/admin/
In order to log in you need to create a superuser - a user which has control over everything on the site. Go back to you
command-line and type
email address and password when you're asked for them. The output should look like this (where username and email
should be your own ones):
Return to your browser and log in with the superuser's credentials you chose, you should see the Django admin
dashboard.
Go to Posts and experiment a little bit with it. Add five or six blog posts. Don't worry about the content - you can simply
copy-paste some text from this tutorial as your posts' content to save time :).
Make sure that at least two or three posts (but not all) have the publish date set. It will be helpful later.
If you want to know more about Django admin, you should check Django's documentation:
https://docs.djangoproject.com/en/1.7/ref/contrib/admin/
It is probably a good moment to grab a coffee (or tea) and eat something sweet. You created your first Django model you deserve a little treat!
Deploy!
Note Following chapter can be sometimes a bit harder to get through. Insist on finishing it, deployment is an important
part of website development process. Chapter is placed in the middle of tutorial so that mentor can help you get your
website online while you can still finish the tutorial alone if you run out of time.
Until now your website was only available on your computer, now you will learn how to deploy it! Deploying is the
process of publishing your application on the Internet so people can finally go and see your app :).
As you learned, a website has to be located on a server. There are a lot of providers, but we will use the one with the
simplest deployment process: Heroku. Heroku is free for small applications that don't have too many visitors, it'll definitely
be enough for you now.
We will be following this tutorial: https://devcenter.heroku.com/articles/getting-started-with-django, but we pasted it here
so it's easier for you.
The
file
requirements.txt
We need to create a
requirements.txt
file to tell Heroku what Python packages need to be installed on our server.
But first, Heroku needs us to install a few packages. Go to your console with
virtualenv
djangogirls
requirements.txt
with a list of your installed packages (i.e. Python libraries that you are using,
psycopg2==2.5.3
Procfile
Another thing we need to create is a Procfile. This will let Heroku know which commands to run in order to start our
website. Open up your code editor, create a file called
Procfile
in
djangogirls
command).
web
waitress-
The
runtime.txt
file
We need to tell Heroku which Python version we want to use. This is simply done by creating a
runtime.txt
and putting
python-3.4.2
mysite/local_settings.py
There is a difference between settings we are using locally (on our computer) and settings for our server. Heroku is using
one database, and your computer is using a different database. That's why we need to create a seperate file for settings
that will only be available for our local enviroment.
Go ahead and create
mysite/local_settings.py
DATABASE
mysite/settings.py
file.
import os
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
DEBUG = True
mysite/settings.py
Another thing we need to do is modify our website's
settings.py
file. Open
import dj_database_url
DATABASES['default'] = dj_database_url.config()
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
ALLOWED_HOSTS = ['*']
STATIC_ROOT = 'staticfiles'
DEBUG = False
mysite/settings.py , copy
try:
from .local_settings import *
except ImportError:
pass
mysite/settings.py
mysite/wsgi.py
Open the
mysite/wsgi.py
All right!
Heroku account
You need to install your Heroku toolbelt which you can find here (you can skip the installation if you've already installed it
during setup): https://toolbelt.heroku.com/
When running the Heroku toolbelt installation program on Windows make sure to choose "Custom Installation"
when being asked which components to install. In the list of components that shows up after that please
additionally check the checkbox in front of "Git and SSH".
On Windows you also must run the following command to add Git and SSH to your command prompt's
PATH "%PATH%;C:\Program Files\Git\bin"
PATH
setx
$ heroku login
In case you don't have an SSH key this command will automatically create one. SSH keys are required to push code to
the Heroku.
Git
Heroku uses a git repository to manage your project files, so we need to use it too.
Create
.gitignore
myvenv
__pycache__
staticfiles
local_settings.py
db.sqlite3
and save it. The dot on the beginning of the file name is important! As you can see, we're now telling Heroku to ignore
local_settings.py
and don't download it, so it's only available on your computer (locally).
myvenv
virtualenv !
Next, well create a new git repository and save our changes. Go to your console and run these commands:
$ git init
Initialized empty Git repository in ~/djangogirls/.git/
$ git add .
$ git commit -m "My Django Girls app"
[master (root-commit) 2943412] My Django Girls
7 files changed, 230 insertions(+)
create mode 100644 .gitignore
create mode 100644 Procfile
create mode 100644 mysite/__init__.py
create mode 100644 mysite/settings.py
create mode 100644 mysite/urls.py
create mode 100644 mysite/wsgi.py
create mode 100644 manage.py
create mode 100644 requirements.txt
create mode 100644 runtime.txt
Deploy to Heroku!
It was a lot of configuration and installing, right? But you need to do that once! Now you can deploy!
It's as simple as running this command, replacing
djangogirlsblog
: Remember to replace
djangogirlsblog
This automatically added the Heroku remote for our app to our repository. Now we can do a simple git push to deploy our
application:
web process
Procfile
(we chose a
web
process type
web
too much power and so it's fine to run just one process. It's possible to ask Heroku to run more processes (by the way,
Heroku calls these processes "Dynos" so don't be surprised if you see this term) but it will no longer be free.
We can now visit the app in our browser with
heroku open
$ heroku open
This will open a url like https://djangogirlsblog.herokuapp.com/ in your browser. Since we only created the admin view for
the app so far, add
admin/
web app.
We created a new database on Heroku, but we also need to sync it:
As you can see, there is an error. Heroku created a new database for us and it's empty. We also need to sync it:
You should now be able to see your website in a browser! Congrats :)!
Django urls
We're about to build our first webpage -- a homepage for your blog! But first, let's learn a little bit about Django urls.
What is a URL?
A URL is simply a web address, you can see a URL every time you visit any website - it is visible in your browser's
address bar (yes!
127.0.0.1:8000
Every page on the Internet needs its own URL. This way your application knows what it should show to a user who opens
a URL. In Django we use something called
URLconf
mysite/urls.py
As you can see, Django already put something here for us.
Lines that start with
are comments - it means that those lines won't be run by Python. Pretty handy, right?
The admin URL, which you visited in previous chapter is already here:
url(r'^admin/', include(admin.site.urls)),
admin/
Django will find a corresponding view. In this case we're including a lot
of admin URLs so it isn't all packed into this small file -- it's more readable and cleaner.
Regex
Do you wonder how Django matches URLs to views? Well, this part is tricky. Django uses
regex
-- regular expressions.
Regex has a lot (a lot!) of rules that form a search pattern. It is not so easy to understand so we won't worry about it today
and you'll definitely get to know them in the future. Today we will only use the ones we need.
Here is a simple example just to not leave you stuck on this sentence: imagine you have a website with the address like
that:
http://www.mysite.com/post/12345/
, where
12345
is the number of your post. Writing separate views for all the post
numbers would be really annoying. Django makes it easier by allowing you to write
number>/
http://www.mysite.com/post/<a
mysite/urls.py
blog
file.
blog.urls
( '' ).
Your
mysite/urls.py
http://127.0.0.1:8000/
to
blog.urls
there.
blog.urls
Create a new
blog/urls.py
views
from
blog
urlpatterns = patterns('',
url(r'^$', views.post_list),
)
view
called
post_list
to
^$
^$
in regex means "the beginning"; from this sign we can start looking for our pattern
matches only "the end" of the string, which means that we will finish looking for our pattern here
If you put these two signs together, it looks like we're looking for an empty string! And that's correct, because in Django url
resolvers,
http://127.0.0.1:8000/
http://127.0.0.1:8000/
address.
Everything all right? Open http://127.0.0.1:8000/ in your browser to see the result.
views.post_list
There is no "It works" anymore, huh? Don't worry, it's just an error page, nothing to be scared of! They're actually pretty
useful:
You can read that there is no attribute 'post_list'. Is post_list reminding you of anything? This is how we called our view!
This means that everything is in place, we just didn't create our view yet. No worries, we will get there.
If you want to know more about Django URLconfs, look at the official documentation:
https://docs.djangoproject.com/en/1.7/topics/http/urls/
template
model
that you will create in the next chapter. Views are just Python methods that are a little bit more
views.py
blog/views.py
file.
blog/views.py
OK, let's open up this file and see what's in there:
Not too much stuff here yet. The simplest view can look like this.
def post_list(request):
return render(request, 'blog/post_list.html', {})
post_list
blog/post_list.html
that takes
request
and
return
a method
Save the file, go to http://127.0.0.1:8000/ and see what we have got now.
Another error! Read what's going on now:
This one is easy: TemplateDoesNotExist. Let's fix this bug and create a template in the next chapter!
Learn more about Django views by reading the official documentation:
https://docs.djangoproject.com/en/1.7/topics/http/views/
render
that will
Introduction to HTML
What's a template, you may ask?
A template is a file that we can re-use to present different information in a consistent format - for example, you could use a
template to help you write a letter, because although each letter might contain a different message and be addressed to a
different person, they will share the same format.
A Django template's format is described in a language called HTML (that's the HTML we mentioned in the first chapter
How the Internet works).
What is HTML?
HTML is a simple code that is interpreted by your web browser - such as Chrome, Firefox or Safari - to display a webpage
for the user.
HTML stands for "HyperText Markup Language." HyperText means it's a type of text that supports hyperlinks between
pages. Markup means we have taken a document and marked it up with code to tell something (in this case, a browser)
how to interpret the page. HTML code is built with tags, each one starting with
<
>
elements.
blog/templates/blog
blog
templates
blog
templates
blog
blog
naming convention that makes life easier when things start to get more complicated.)
And now create a
post_list.html
blog/templates/blog
directory.
TemplateDoesNotExists
, try to restart your server. Go into command line, stop the reserver by
pressing Ctrl+C (Control and C buttons together) and start it again by running a
command.
No error anymore! Congratulations :) However, your website isn't actually publishing anything except an empty page,
because your template is empty too. We need to fix that.
Add the following to your template file:
<html>
<p>Hi there!</p>
<p>It works!</p>
</html>
So how does your website look now? Click to find out: http://127.0.0.1:8000/
<html>
see, the whole content of the website goes between the beginning tag
<p>
</p>
</html>
<html>
</html>
<head>
For example, you can put a webpage title element inside the
<head>
<body>
, like this:
<html>
<head>
<title>Ola's blog</title>
</head>
<body>
<p>Hi there!</p>
<p>It works!</p>
</body>
</html>
Notice how the browser has understood that "Ola's blog" is the title of your page? It has interpreted
blog</title>
<title>Ola's
and placed the text in the title bar of your browser (it will also be used for bookmarks and so on).
Probably you have also noticed that each opening tag is matched by a closing tag, with a
nested (i.e. you can't close a particular tag until all the ones that were inside it have been closed too).
It's like putting things into boxes. You have one big box,
contains still smaller boxes:
<p></p>
<html></html>
; inside it there is
<body></body>
, and that
You need to follow these rules of closing tags, and of nesting elements - if you don't, the browser may not be able to
interpret them properly and your page will display incorrectly.
<h2>A sub-heading</h2>
<h3>A sub-sub-heading</h3>
<em>text</em>
<strong>text</strong>
<br />
<h6>
<a href="http://google.com/">link</a>
creates a link
<html>
<head>
<title>Django Girls blog</title>
</head>
<body>
<div>
<h1><a href="">Django Girls Blog</a></h1>
</div>
<div>
<p>published: 14.06.2014, 12:14</p>
<h2><a href="">My first post</a></h2>
<p>Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Donec id elit non mi porta gravida at eget metus. Fusce
</div>
<div>
<p>published: 14.06.2014, 12:14</p>
<h2><a href="">My second post</a></h2>
<p>Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Donec id elit non mi porta gravida at eget metus. Fusce
</div>
</body>
</html>
div
Another two
p
div
sections here.
element contains the title of our blogpost - it's a heading and a link
div
h2
s (paragraph) of text, one for the date and one for our blogpost.
Yaaay! But so far, our template only ever displays exactly the same information - whereas earlier we were talking about
templates as allowing us to display different information in the same format.
What we want really want to do is display real posts added in our Django admin - and that's where we're going next.
It'd be good to see if your website will be still working on Heroku, right? Let's try deploying again.
First off, let's see what files have changed since we last deployed:
$ git status
Let's tell
git
$ git add -A .
-A
git
will also recognize if you've deleted files (by default, it only recognizes
git
git
in green):
$ git status
We're almost there, now it's time to tell it to save this change in its history. We're going to give it a "commit message"
where we describe what we've changed. You can type anything you'd like at this stage, but it's helpful to type something
descriptive so that you can remember what you've done in the future.
Make sure you use double quotes around the commit message.
Once we've done that, we can finally upload (push) our changes to the website on heroku:
And that should be it! Once Heroku is finished, you can go ahead and refresh your website in the browser. Changes
should be visible!
Django Querysets
We have different pieces in place: the
Post
model is defined in
models.py , we
have
post_list
in
views.py
added. But how will we actually make our posts appear in our HTML template? Because that is what we want: take some
content (models saved in the database) and display it nicely in our template, right?
This is exactly what views are supposed to do: connect models and templates. In our
post_list
models we want to display and pass them to the template. So basically in a view we decide what (model) will be
displayed in a template.
OK, so how will we achieve it?
We need to open our
blog/views.py . So
far
post_list
Remember when we talked about including code written in different files? Now it is the moment when we have to include
the model we have written in
models.py . We
like this:
Dot after
from
.py ). Then
Post
views.py
and
models.py
QuerySet
QuerySet
You should already be familiar with how QuerySets work. We talked about it in Django ORM (QuerySets) chapter.
So now we are interested in a list of blog posts that are published and sorted by
published_date
Post.objects.filter(published_date__isnull=False).order_by('published_date')
blog/views.py
def post_list(request)
posts
render
posts
QuerySet to the template (we will cover how to display it in a next chapter).
request
'blog/post_list.html' . The
things for the template to use. We need to give them names (we will stick to
{'posts': posts}
So finally our
{}
'posts'
'' .
That's it! Time to go back to our template and display this QuerySet!
If you want to read a little bit more about QuerySets in Django you should look here:
https://docs.djangoproject.com/en/1.7/ref/models/querysets/
Django templates
Time to display some data! Django gives us some helpful, built-in template tags for that.
posts
To print a variable in Django template, we use double curly brackets with the variable's name inside, like this:
{{ posts }}
blog/templates/blog/post_list.html
<div></div>
tags with
line), save the file and refresh the page to see the results:
This means that Django understands it as a list of objects. Remember from Introduction to Python how we can display
lists? Yes, with the for loops! In a Django template, you do them this way:
It works! But we want them to be displayed like the static posts we created earlier in the Introduction to HTML chapter.
You can mix HTML and template tags. Our
body
<div>
<h1><a href="/">Django Girls Blog</a></h1>
</div>
{% for post in posts %}
<div>
<p>published: {{ post.published_date }}</p>
<h1><a href="">{{ post.title }}</a></h1>
<p>{{ post.text|linebreaks }}</p>
</div>
{% endfor %}
{% for %}
and
{% endfor %}
will be repeated for each object in the list. Refresh your page:
Have you noticed that we used a slightly different notation this time
data in each of the fields defined in our
line-breaks into paragraphs.
Post
{{ post.title }}
|linebreaks
or
{{ post.text }}
? We are accessing
It'd be good to see if your website will still be working on Heroku, right? Let's try deploying again. If you forgot how to do it,
check the end of chapter 15:
$ git
...
$ git
$ git
...
$ git
...
$ git
status
add -A .
status
commit -m "Used Django templates instead of static HTML."
push heroku master
Congrats! Now go ahead and try adding a new post in your Django admin (remember to add published_date!), then
refresh your page to see if the post appears there.
Works like a charm? We're proud! Treat yourself something sweet, you have earned it :)
What is CSS?
Cascading Style Sheets (CSS) is a language used for describing the look and formatting of a website written in markup
language (like HTML). Treat it as make-up for our webpage ;).
But we don't want to start from scratch again, right? We will, once more, use something that has already been done by
programmers and released on the Internet for free. You know, reinventing the wheel is no fun.
Install Boostrap
To install Bootstrap, you need to add this to your
<head>
in your
.html
file ( blog/templates/blog/post_list.html ):
This doesn't add any files to your project. It just points to files that exist on the internet. Just go ahead, open your website
and refresh the page. Here it is!
CSS is a static file, so in order to customize CSS, we need to first configure static files in Django. You'll only need to do it
once. Let's start:
static
inside your
directory.
djangogirls
static
manage.py
Open up the
mysite/settings.py
STATICFILES_DIRS = (
os.path.join(BASE_DIR, "static"),
)
This way Django will know where to find your static files.
blog.css
inside this
css
css
inside your
static
directory. Ready?
static
css
blog.css
static/css/blog.css
We won't be going too deep into customizing and learning about CSS here, because it's pretty easy and you can learn it
on your own after this workshop. We really recommend doing this Codeacademy HTML & CSS course to learn everything
you need to know about making your websites more pretty with CSS.
But let's do at least a little. Maybe we could change the color of our header? To understand colors, computers use special
codes. They start with
and are followed by 6 letters (A-F) and numbers (0-9). You can find color codes for example
static/css/blog.css
red
and
green
h1 a {
color: #FCA205;
}
h1 a
<h1><a href="">link</a></h1>
element inside of an
h1
#FCA205
which is orange. Of course, you can put your own color here!
In a CSS file we determine styles for elements in the HTML file. The elements are identified by the element name (i.e.
h1
body ), the
attribute
class
or the attribute
id
. Class and id are names you give the element by yourself. Classes
define groups of elements, and ids point to specific elements. For example, the following tag may be identified by CSS
using the tag name
, the class
external_link
, or the id
link_to_wiki_page
blog/templates/blog/post_list.html
file
{% load staticfiles %}
We're just loading static files here :). Then, between the
<head>
and
</head>
(the browser reads the files in the order they're given, so code in our file may override code in Bootstrap files), add this
line:
{% load staticfiles %}
<html>
<head>
<title>Django Girls blog</title>
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css">
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="{% static 'css/blog.css' %}">
</head>
<body>
<div>
<h1><a href="/">Django Girls Blog</a></h1>
</div>
{% for post in posts %}
<div>
<p>published: {{ post.published_date }}</p>
<h1><a href="">{{ post.title }}</a></h1>
<p>{{ post.text|linebreaks }}</p>
</div>
{% endfor %}
</body>
</html>
Nice work! Maybe we would also like to give our website a little air and increase the margin on the left side? Let's try this!
body {
padding-left: 15px;
}
Add this to your CSS, save the file and see how it works!
Maybe we can customize the font in our header? Paste this into your
<head>
in
blog/templates/blog/post_list.html
file:
This line will import a font called Lobster from Google Fonts (https://www.google.com/fonts).
Now add the line
font-family: 'Lobster';
and
h1 a {
color: #FCA205;
font-family: 'Lobster';
}
static/css/blog.css
inside the
h1 a
Great!
As mentioned above, CSS has a concept of classes, which basically allows you to name a part of the HTML code and
apply styles only to this part, not affecting others. It's super helpful if you have two divs, but they're doing something very
different (like your header and your post), so you don't want them to look the same.
Go ahead and name some parts of the HTML code. Add a class called
page-header
to your
div
<div class="page-header">
<h1><a href="/">Django Girls Blog</a></h1>
</div>
post
to your
div
<div class="post">
<p>published: {{ post.published_date }}</p>
<h1><a href="">{{ post.title }}</a></h1>
<p>{{ post.text|linebreaks }}</p>
</div>
We will now add declaration blocks to different selectors. Selectors starting with
tutorials and explanations about CSS on the Web to help you understand the following code. For now, just copy and
paste it into your
mysite/static/css/blog.css
file:
.page-header {
background-color: #ff9400;
margin-top: 0;
padding: 20px 20px 20px 40px;
}
.page-header h1, .page-header h1 a, .page-header h1 a:visited, .page-header h1 a:active {
color: #ffffff;
font-size: 36pt;
text-decoration: none;
}
.content {
margin-left: 40px;
}
h1, h2, h3, h4 {
font-family: 'Lobster', cursive;
}
.date {
float: right;
color: #828282;
}
.save {
float: right;
}
.post-form textarea, .post-form input {
width: 100%;
}
.top-menu, .top-menu:hover, .top-menu:visited {
color: #ffffff;
float: right;
font-size: 26pt;
margin-right: 20px;
}
.post {
margin-bottom: 70px;
}
.post h1 a, .post h1 a:visited {
color: #000000;
}
Then surround the HTML code which displays the posts with declarations of classes. Replace this:
in the
blog/templates/blog/post_list.html
with this:
Woohoo! Looks awesome, right? The code we just pasted is not really so hard to understand and you should be able to
understand most of it just by reading it.
Don't be afraid to tinker with this CSS a little bit and try to change some things. If you break something, don't worry, you
can always undo it!
Anyway, we really recommend taking this free online Codeacademy HTML & CSS course as some post-workshop
homework to learn everything you need to know about making your websites prettier with CSS.
Ready for the next chapter?! :)
Template extending
Another nice thing Django has for you is template extending. What does this mean? It means that you can use the same
parts of your HTML for different pages of your website.
This way you don't have to repeat yourself in every file, when you want to use the same information/layout. And if you
want to change something, you don't have to do it in every template, just once!
base.html
file in
blog/templates/blog/
blog
templates
blog
base.html
post_list.html
post_list.html
to
base.html
{% load staticfiles %}
<html>
<head>
<title>Django Girls blog</title>
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css">
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
<link href='//fonts.googleapis.com/css?family=Lobster&subset=latin,latin-ext' rel='stylesheet' type='text/css'>
<link rel="stylesheet" href="{% static 'css/blog.css' %}">
</head>
<body>
<div class="page-header">
<h1><a href="/">Django Girls Blog</a></h1>
</div>
<div class="content container">
<div class="row">
<div class="col-md-8">
{% for post in posts %}
<div class="post">
<div class="date">
{{ post.published_date }}
</div>
<h1><a href="">{{ post.title }}</a></h1>
<p>{{ post.text|linebreaks }}</p>
</div>
{% endfor %}
</div>
</div>
</div>
</body>
</html>
Then in
base.html
<body>
(everything between
<body>
and
</body>
) with this:
<body>
<div class="page-header">
<h1><a href="/">Django Girls Blog</a></h1>
</div>
<div class="content container">
<div class="row">
<div class="col-md-8">
{% block content %}
{% endblock %}
</div>
</div>
</div>
</body>
with:
{% block content %}
{% endblock %}
base.html
block
, which is a template tag that allows you to insert HTML in this block in other
blog/templates/blog/post_list.html
<div class="page-header"></div>
{% extends 'blog/base.html' %}
base.html
{% block content %}
template in
and
post_list.html
{% endblock content %}
. Like this:
{% extends 'blog/base.html' %}
{% block content %}
{% for post in posts %}
<div class="post">
<div class="date">
{{ post.published_date }}
</div>
<h1><a href="">{{ post.title }}</a></h1>
<p>{{ post.text|linebreaks }}</p>
</div>
{% endfor %}
{% endblock content %}
TemplateDoesNotExists
blog/base.html
runserver
running in the console, try to stop it (by pressing Ctrl+C - Control and C buttons together) and restart it by running a
python manage.py runserver
command.
Post
models.py .
blog/templates/blog/post_list.html
{% extends 'blog/base.html' %}
{% block content %}
{% for post in posts %}
<div class="post">
<div class="date">
{{ post.published_date }}
</div>
<h1><a href="">{{ post.title }}</a></h1>
<p>{{ post.text|linebreaks }}</p>
</div>
{% endfor %}
{% endblock content %}
We want to have a link to a post detail page on the post's title. Let's change
into
a link:
{% %}
notation
means that we are using Django template tags. This time we will use one that will create a URL for us!
blog.views.post_detail
(the directory
blog
),
is a path to a
views
post_detail
views.py
blog
post_detail
http://127.0.0.1:8000/
we will have an error (as expected, since we don't have a URL or a view for
post_detail
for our
urls.py
post_detail
view!
URL: http://127.0.0.1:8000/post/1/
We want to create a URL to point Django to a view called
url(r'^post/(?P<pk>[0-9]+)/$', views.post_detail),
to the
post_detail
blog/urls.py
That one looks scary, but no worries - we will explain it for you:
it starts with
post/
only means that after the beginning, the URL should contain the word post and /. So far so good.
(?P<pk>[0-9]+)
- this part is trickier. It means that Django will take everything that you place here and transfer it to a
valid, but
/
[0-9]
also tells us that it can only be a number, not a letter (so everything between 0
http://127.0.0.1:8000/post/1234567890/
http://127.0.0.1:8000/post//
is not
is perfectly ok!
- "the end"!
pk
means that there needs to be one or more digits there. So something like
post_detail
is shortcut for
http://127.0.0.1:8000/post/5/
into your browser, Django will understand that you are looking for a
pk
equals
to that view.
name is often used in Django projects. But you can name your variable as you like
(?P<post_id>[0-9]+)
http://127.0.0.1:8000/
(?P<pk>[0-9]+)
Do you remember what the next step is? Of course: adding a view!
post_detail view
This time our view is given an extra parameter
post_detail(request, pk):
pk
. Our view needs to catch it, right? So we will define our function as
def
. Note that we need to use exactly the same name as the one we specified in urls ( pk ). Omitting
Post.objects.get(pk=pk)
Post
with given
primary key
We don't want that! But, of course, Django comes with something that will handle that for us:
get_object_or_404
there is no
page).
Post
pk
. In case
The good news is that you can actually create your own
views.py
blog/views.py
file!
Near other
from
lines. And at the end of the file we will add our view:
http://127.0.0.1:8000/
It worked! But what happens when you click a link in blog post title?
Oh no! Another error! But we already know how to deal with it, right? We need to add a template!
We will create a file in
It will look like this:
blog/templates/blog
called
post_detail.html
{% extends 'blog/base.html' %}
{% block content %}
<div class="date">
{% if post.published_date %}
{{ post.published_date }}
{% endif %}
</div>
<h1>{{ post.title }}</h1>
<p>{{ post.text|linebreaks }}</p>
{% endblock %}
base.html
. In the
content
published_date
if ... else ..
from
is not empty.
is gone now.
Yay! It works!
$ git
...
$ git
$ git
...
$ git
...
$ git
status
add -A .
status
commit -m "Added more views to the website."
push heroku master
Django Forms
The final thing we want to do on our website is create a nice way to add and edit blog posts. Django's
is rather hard to customize and make pretty. With
forms
admin
is cool, but it
ModelForm
blog
Post
model.
forms.py .
directory.
blog
forms.py
We need to import Django forms first ( from django import forms ) and, obviously, our
PostForm
Post
, as you probably suspect, is the name of our form. We need to tell Django, that this form is a
class Meta
forms.ModelForm
, where we tell Django which model should be used to create this form ( model = Post ).
created_date
title
and
text
to be exposed -
(so
Finally, we can say which field(s) should end up in our form. In this scenario we want only
author
ModelForm
blog/templates/blog/base.html
div
named
page-header
post_new .
After adding the line, your html file should now look like this:
{% load staticfiles %}
<html>
<head>
<title>Django Girls blog</title>
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css">
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
<link href='//fonts.googleapis.com/css?family=Lobster&subset=latin,latin-ext' rel='stylesheet' type='text/css'>
<link rel="stylesheet" href="{% static 'css/blog.css' %}">
</head>
<body>
<div class="page-header">
<a href="{% url 'blog.views.post_new' %}" class="top-menu"><span class="glyphicon glyphicon-plus"></span></a>
<h1><a href="/">Django Girls Blog</a></h1>
</div>
<div class="content container">
<div class="row">
<div class="col-md-8">
{% block content %}
{% endblock %}
</div>
</div>
</div>
</body>
</html>
http://127.0.0.1:8000
NoReverseMatch
error, right?
URL
We open
blog/urls.py
AttributeError
post_new
post_new view
Time to open the
blog/views.py
file and add the following lines with the rest of the
from
rows:
def post_new(request):
form = PostForm()
return render(request, 'blog/post_edit.html', {'form': form})
To create a new
Post
PostForm()
and pass it to the template. We will go back to this view, but for
Template
We need to create a file
post_edit.html
in the
blog/templates/blog
we have to display the form. We can do that for example with a simple
the line above needs to be wrapped with an HTML form tag:
we need a
Save
<form ...>
{{ form.as_p }}
<form method="POST">...</form>
<button type="submit">Save</button>
{% csrf_token %}
makes your forms secure! Django will complain if you forget about this bit if you try to save the form:
post_edit.html
should look:
{% extends 'blog/base.html' %}
{% block content %}
<h1>New post</h1>
<form method="POST" class="post-form">{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="save btn btn-default">Save</button>
</form>
{% endblock %}
title
and
text
Nothing! We are once again on the same page and our text is gone... and no new post is added. So what went wrong?
The answer is: nothing. We need to do a little bit more work in our view.
blog/views.py
post_new
view is:
def post_new(request):
form = PostForm()
return render(request, 'blog/post_edit.html', {'form': form})
When we submit the form, we are back in the same view, but this time we have some more data in
specifically in
request.POST . Remember
method
is
GET , but we
request.POST . You
<form>
request
, more
method="POST"
? All
So in our view we have two separate situations to handle. First: when we access the page for the first time and we want a
blank form. Second: when we go back to the view with all form's data we just typed. So we need to add a condition (we
will use
if
for that).
if request.method == "POST":
[...]
else:
form = PostForm()
[...]
. If
method
is
POST
PostForm
form = PostForm(request.POST)
Easy! Next thing is to check if the form is correct (all required fields are set and no incorrect values will be saved). We do
that with
form.is_valid()
if form.is_valid():
post = form.save(commit=False)
post.author = request.user
post.save()
PostForm
form.save
commit=False
form.save()
, without
commit=False
Post
author
will preserve changes (adding author) and a new blog post is created!
post_detail
Add it at the very beginning of your file. And now we can say: go to
post_detail
blog.views.post_detail
is the name of the view we want to go to. Remember that this view requires a
pk=post.pk
, where
post
pk
variable? To pass
Ok, we talked a lot, but we probably want to see what the whole view looks like now, right?
def post_new(request):
if request.method == "POST":
form = PostForm(request.POST)
if form.is_valid():
post = form.save(commit=False)
post.author = request.user
post.save()
return redirect('blog.views.post_detail', pk=post.pk)
else:
form = PostForm()
return render(request, 'blog/post_edit.html', {'form': form})
http://127.0.0.1:8000/post/new/
post_detail
, add a
title
and
text
page!
You problably have noticed that we are not setting publish date at all. We will introduce a publish button in Django Girls
Tutorial: Extensions.
That is awesome!
Form validation
Now, we will show you how cool Django forms are. A blog post needs to have
did not say (as opposed to
published_date
title
and
text
fields. In our
Post
model we
) that these fields are not required, so Django, by default, expects them to be
set.
Try to save the form without
title
and
text
Django is taking care of validating that all the fields in our form are correct. Isn't it awesome?
As we have recently used the Django admin interface the system currently thinks we are logged in. There are a few
situations that could lead to us being logged out (closing the browser, restarting the DB etc.). If you find that you are
getting errors creating a post referring to a lack of a logged in user, head to the admin page
http://127.0.0.1:8000/admin
and log in again. This will fix the issue temporarily. There is a permanent fix awaiting you
in the Homework: add security to your website! chapter after the main tutorial.
Edit form
Now we know how to add a new form. But what if we want to edit an existing one? It is very similar to what we just did.
Let's create some important things quickly (if you don't understand something, you should ask your coach or look at the
previous chapters, since we covered all these steps already).
Open
blog/templates/blog/post_detail.html
<a class="btn btn-default" href="{% url 'post_edit' pk=post.pk %}"><span class="glyphicon glyphicon-pencil"></span></a>
{% extends 'blog/base.html' %}
{% block content %}
<div class="date">
{% if post.published_date %}
{{ post.published_date }}
{% endif %}
<a class="btn btn-default" href="{% url 'post_edit' pk=post.pk %}"><span class="glyphicon glyphicon-pencil"></span></a>
</div>
<h1>{{ post.title }}</h1>
<p>{{ post.text|linebreaks }}</p>
{% endblock %}
In
blog/urls.py
blog/views.py
blog/templates/blog/post_edit.html
post_new
Post
instance
get_object_or_404(Post, pk=pk)
form = PostForm(instance=post)
post_detail
pk
When you click it you will see the form with our blog post:
Feel free to change the title or the text and save changes!
Congratulations! Your application is getting more and more complete!
If you need more information about Django forms you should read the documentation:
https://docs.djangoproject.com/en/1.7/topics/forms/
$ git
...
$ git
$ git
...
$ git
...
$ git
status
add -A .
status
commit -m "Added views to create/edit blog post inside the site."
push heroku master
Domain
Heroku gave you a domain, but it's long, hard to remember, and ugly. It'd be awesome to have a domain name that is
short and easy to remember, right?
In this chapter we will teach you how to buy a domain and direct it to Heroku!
You should now see a list of all available domains with the term you put in the search box. As you can see, a smiley face
indicates that the domain is available for you to buy, and a sad face that it is already taken.
djangogirls.in
Go to checkout. You should now sign up for iwantmyname if you don't have an account yet. After that, provide your credit
card info and buy a domain!
After that, click
DNS records
Domains
in the menu and choose your newly purchased domain. Then locate and click on the
manage
link:
Domains
What's next?
Congratulate yourself! You're totally awesome. We're proud! <3
What to do now?
Take a break and relax. You have just done something really huge.
After that make sure to:
Follow Django Girls on Facebook or Twitter to stay up to date