271CIS-3 Lab Manual
271CIS-3 Lab Manual
Lab Manual
271-CIS-3
Programming for Data Science
Department of Information systems
College of Computer Science
King Khalid University
Course Coordinator
Hisham Hummadi
Software(s)/Tools Used
Jupyter Notebooks
Google Colab
References
• Lecture notes
• Learning Python, 5th Edition, By Mark Lutz, ISBN: 978-1449355739, Publisher: O'Reilly
Media, Release Date: June 2013ISBN: 978-1680501841.
• https://cognitiveclass.ai/
• https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django
What is Python?
Python is a popular programming language. It was created by Guido van Rossum, and released in 1991. Python is a
high-level, general-purpose and a very popular programming language. Python programming language (latest
Python 3) is being used in web development, Machine Learning applications, along with all cutting edge
technology in Software Industry. Python Programming Language is very well suited for Beginners, also for
experienced programmers with other programming languages like C++ and Java.
What can Python do?
Why Python?
• Python works on different platforms (Windows, Mac, Linux, Raspberry Pi, etc).
• Python has a simple syntax similar to the English language.
• Python has syntax that allows developers to write programs with fewer lines than some other programming
languages.
• Python runs on an interpreter system, meaning that code can be executed as soon as it is written. This means that
prototyping can be very quick.
• Python can be treated in a procedural way, an object-orientated way or a functional way.
• Python was designed for readability, and has some similarities to the English language with influence from
mathematics.
• Python uses new lines to complete a command, as opposed to other programming languages which often use
semicolons or parentheses.
• Python relies on indentation, using whitespace, to define scope; such as the scope of loops, functions and classes.
Other programming languages often use curly-brackets for this purpose.
Python is currently the most widely used multi-purpose, high-level programming language. Python allows
programming in Object-Oriented and Procedural paradigms.
Python programs generally are smaller than other programming languages like Java. Programmers have to type relatively
less and indentation requirement of the language, makes them readable all the time.
Python language is being used by almost all tech-giant companies like – Google, Amazon, Facebook, Instagram,
Dropbox, Uber… etc.
The biggest strength of Python is huge collection of standard library which can be used for the following:
• Machine Learning
• GUI Applications (like Kivy, Tkinter, PyQt etc. )
• Web frameworks like Django (used by YouTube, Instagram, Dropbox)
• Image processing (like OpenCV, Pillow)
• Web scraping (like Scrapy, BeautifulSoup, Selenium)
• Test frameworks
• Multimedia
• Scientific computing
• Text processing and many more..
Advantages :
1. Presence of third-party modules
2. Extensive support libraries(NumPy for numerical calculations, Pandas for data analytics etc)
3. Open source and community development
4. Easy to learn
5. User-friendly data structures
6. High-level language
7. Dynamically typed language(No need to mention data type based on value assigned, it takes data type)
8. Object-oriented language
9. Portable and Interactive
10. Portable across Operating systems
Applications :
Installation
• Download Anaconda3 https://www.anaconda.com/products/individual
• Open Anaconda3 and select Jupyter
• Create folder for your subject to download all lectures on it. You will open that files from
Jupyter.
After executing the cell above, you should see that Python return the string, Hello Python 101. Congratulations on
running your first Python code!
**Tip:** **pr∫()pr∫()** is the **function** that you **executed**, and you *passed in* an **argument**
of 'HelloWorld!'′HelloWorld!′.
Python 3
How do we know that Python 3 is running? Take a look in the top-right hand corner of this notebook. You should see
"Python 3"!
To write comments in your Python code, use the hash symbol (#) before writing your comment. When you run the code
Python will ignore everything after the # in that line.
In [2]:
print('Hello Python 101') #this my comment
#print('Hi')
Hello Python 101
Great! After executing the cell above, you should notice that "this is my comment did not appeared in the output,
because it was a comment (and thus ignored by Python). The second line was also not executed because print('Hi') was
preceded by a hash symbol (#) as well!
Errors in Python
Before continuing, it's important to note when things go wrong in Python, and we cause errors.
There are many kinds of errors, and you do not need to memorize the various types of errors. Instead, what's most
important is know what the error messages mean.
For example, if you spell print as frint, you will get an error message.
In [2]:
frint("Hello World!")
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-2-cbc42dbb0087> in <module>
----> 1 frint("Hello World!")
Does Python know the error in the script before you run it?
No, Python is naive! Python will try to run the code line-by-line, and it will stop if it runs into an error.
In [3]:
print("This will be printed")
frint("This will cause an error")
print("This will NOT be printed")
This will be printed
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-3-aa3f0d14d353> in <module>
1 print("This will be printed")
----> 2 frint("This will cause an error")
3 print("This will NOT be printed")
The following chart summarizes three data types for the last examples, the first column indicates the expression the
second column indicates the data type
In [4]:
11 #integer
Out[4]:
11
In [6]:
2.14 #float
Out[6]:
2.14
In [7]:
"Hello Python 101" #character string
Out[7]:
'Hello Python 101'
We can see the actual data type in python by using the type() command
In [5]:
type(12)
Out[5]:
int
In [6]:
type(2.14)
Out[6]:
float
In [7]:
type("Hello Python 101")
271-CIS-3 Lab Manual - 2022 10
Department of Information Systems ﻗﺴﻢ ﻧﻈﻢ اﻟﻤﻌﻠﻮﻣﺎت
Out[7]:
str
type(12.0)
Out[11]:
float
Integers
Here are some integers, integers can be negative or positive:
We can verify some of the above examples using the type command:
In [8]:
type(-1)
Out[8]:
int
In [13]:
type(4)
Out[13]:
int
In [14]:
type(0)
Out[14]:
int
Floats
Floats are real numbers; they include the integers but also "numbers in-between the integers". Consider the numbers
between 0 and one we can select numbers in-between them, these numbers are floats. Similarly, consider the numbers
between 0.5 and 0.6. We can select numbers in-between them, these are floats as well. We can continue the process,
zooming in for different numbers, of course, there is a limit, but it is quite small.
We can verify some of the above examples using the type command:
In [15]:
type(1.0)
Out[15]:
float
In [16]:
type(0.5)
Out[16]:
float
In [17]:
type(0.56)
Out[17]:
float
Converting to float:
In [19]:
float(2)
Out[19]:
2.0
In [20]:
type(float(2))
Out[20]:
float
Converting to integer:
Nothing really changes. If you cast a float to an integer, you must be careful. For example, if you cast the float 1.1 to 1 you
will lose some information :
In [21]:
int(1.1)
Out[21]:
1
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-11-51b62aeb0293> in <module>
----> 1 int("A")
Converting to strings:
You can convert an int to a string:
In [25]:
str(1)
Out[25]:
'1'
You can convert a float to a string
In [26]:
str(1.2)
Out[26]:
'1.2'
Boolean
Boolean is another important type in Python; a Boolean can take on two values. The first value is true, just remember we
use an uppercase T:
In [12]:
True
Out[12]:
True
Boolean values can also be false, with an uppercase F:
In [13]:
False
Out[13]:
False
Using the type command on a Boolean value we obtain the term bool, this is short for Boolean
In [14]:
type(True)
Out[14]:
bool
In [15]:
type(False)
Out[15]:
bool
If we cast a Boolean true to an integer or float we will get a 1, if we cast a Boolean false to an integer or float. If we get a
zero if you cast a 1 to a boolean, you get a true similarly, if you cast a 0 to a Boolean you get a false.
In [31]:
int(True)
Out[31]:
1
In [32]:
271-CIS-3 Lab Manual - 2022 13
Department of Information Systems ﻗﺴﻢ ﻧﻈﻢ اﻟﻤﻌﻠﻮﻣﺎت
bool(1)
Out[32]:
True
In [33]:
bool(0)
Out[33]:
False
In [34]:
float(True)
Out[34]:
1.0
Expressions
Expressions are operations that Python performs. For example, basic arithmetic operations like adding multiple
numbers.
In [21]:
43 + 60 + 16 + 41
Out[21]:
160
We can perform operations such as subtraction using the subtraction sign. In this case the result is a negative number.
In [22]:
50 - 60
Out[22]:
-10
We can use multiplication using an asterisk:
In [23]:
5 * 5
Out[23]:
25
We can also perform division with the forward slash
In [6]:
25 / 5
Out[6]:
5.0
In [7]:
25 / 6
Out[7]:
4.166666666666667
We can use the double slash for integer division, where the result is rounded
In [8]:
25//5
Out[8]:
5
In [9]:
25//6
Out[9]:
4
[Tip] Summary
when you print x you will have different output that mean it change the value whithout change
variable x.
In [38]:
# This will store 6 in the memory and binds the
# name x to it. After it runs, type of x will
# be int.
6
print(type(6))
print(type(6))
<class 'int'>
<class 'str'>
LAB-4 Python Types: Object Types, Numeric Types, Dynamic Types, (cont.)
TUPLES IN PYTHON
In Python, there are different data types: string, integer and float. These data types can all be contained in a
tuple as follows:
In [1]:
tuple1=("disco",10,1.2)
tuple1
print(tuple1)
('disco', 10, 1.2)
The type of variable is a tuple.
In [5]:
type(tuple1[2])
Out[5]:
float
Each element of a tuple can be accessed via an index. The following table represents the relationship between
the index and the items in the tuple. Each element can be obtained by the name of the tuple followed by a
square bracket with the index number:
We can also use negative indexing. We use the same table above with corresponding negative values:
We can obtain the last element as follows (this time we will not use the print statement to display the values):
In [5]:
tuple1[-1]
Out[5]:
1.2
We can slice tuples obtaining multiple values as demonstrated by the figure below:
We can slice tuples, obtaining new tuples with the corresponding elements:
In [7]:
tuple2[0:3]
Out[7]:
('disco', 10, 1.2)
In [12]:
RatingsSorted=sorted(Ratings )
print(RatingsSorted);
[0, 2, 5, 6, 6, 8, 9, 9, 10]
A tuple can contain another tuple as well as other more complex data types. This process is called 'nesting'.
Consider the following tuple with several elements:
In [10]:
NestedT =(1, 2, ("pop", "rock") ,(3,4),("disco",(1,2)))
In [15]:
print(NestedT[4][1][0])
1
Each element in the tuple including other tuples can be obtained via an index as shown in the figure:
</a>
In [21]:
print("Element 0 of Tuple: ", NestedT[0])
print("Element 1 of Tuple: ", NestedT[1])
print("Element 2 of Tuple: ", NestedT[2])
print("Element 3 of Tuple: ", NestedT[3])
print("Element 4 of Tuple: ", NestedT[4])
Element 0 of Tuple: 1
Element 1 of Tuple: 2
Element 2 of Tuple: ('pop', 'rock')
Element 3 of Tuple: (3, 4)
Element 4 of Tuple: ('disco', (1, 2))
We can use the second index to access other tuples as demonstrated in the figure:
</a>
In [23]:
NestedT[2][1][0]
Out[23]:
'r'
In [24]:
NestedT[2][1][1]
Out[24]:
'o'
We can use a tree to visualise the process. Each new index corresponds to a deeper level in the tree:
</a>
Similarly, we can access elements nested deeper in the tree with a fourth index:
In [25]:
NestedT[4][1][0]
Out[25]:
1
In [26]:
NestedT[4][1][1]
Out[26]:
2
The following figure shows the relationship of the tree and the element NestedT[4][1][1]:
Variables
We can also store our output in variables, so we can use them later on. For example:
In [40]:
x = 43 + 60 + 16 + 41
To return the value of x, we can simply run the variable as a command:
In [41]:
x
Out[41]:
160
We can also perform operations on x and save the result to a new variable:
In [42]:
print(x)
160
In [9]:
y = x / 60
y
Out[9]:
2.6666666666666665
If we save something to an existing variable, it will overwrite the previous value:
In [ ]:
x = x / 60
x
It's good practice to use meaningful variable names, so you don't have to keep track of what variable is what:
In [ ]:
total_min = (43 + 42 )
total_min
In [ ]:
total_hr = total_min / 60
total_hr
You can put this all into a single expression, but remember to use round brackets to add together the album lengths
first, before dividing by 60.
In [ ]:
total_hr = (43 + 42 + 57) / 60 # get total hours in a single expression
total_hr
Sets
In this lab, we are going to take a look at sets in Python. A set is a unique collection of objects in Python. You can denote a
set with a curly bracket {}. Python will remove duplicate items:
In [1]:
set1={"pop", "rock", "soul", "hard rock", "rock", "R&B", "rock", "disco"}
set1
Out[1]:
{'R&B', 'disco', 'hard rock', 'pop', 'rock', 'soul'}
The process of mapping is illustrated in the figure:
album_set = set(album_list)
print (album_set)
['Michael Jackson', 'Thriller', 1982, '00:42:19', 'Pop, Rock, R&B', 46.0, 65, '30-Nov-82'
, None, 10.0]
{65, '30-Nov-82', 10.0, 'Michael Jackson', 46.0, '00:42:19', None, 'Thriller', 1982, 'Pop
, Rock, R&B'}
Notice that the duplicates are removed and the output is sorted.
Let us get the sum of the claimed sales:
Consider the list A=[1,2,2,1] and set B=set([1,2,2,1]), does sum(A)=sum(B)
In [3]:
A=[1,2,2,1]
B=set([1,2,2,1])
print (sum(A))
print(sum(B))
6
3
Set Operations
Let us go over Set Operations, as these can be used to change the set. Consider the set A:
In [21]:
A = set(["Thriller","Back in Black", "AC/DC",21] )
A
Out[21]:
{21, 'AC/DC', 'Back in Black', 'Thriller'}
If we add the same element twice, nothing will happen as there can be no duplicates in a set:
In [7]:
A.add("NSYNC")
A
Out[7]:
{'AC/DC', 'Back in Black', 'NSYNC', 'Thriller'}
In [23]:
album_set1, album_set2
Out[23]:
({'AC/DC', 'Back in Black', 'Thriller'},
{'AC/DC', 'Back in Black', 'The Dark Side of the Moon'})
As both sets contain 'AC/DC' and 'Back in Black' we represent these common elements with the intersection of two
circles.
We can find all the elements that are only contained in album_set1 using the difference method:
In [6]:
album_set2.difference(album_set1)
Out[6]:
{'The Dark Side of the Moon'}
In [7]:
album_set1.difference(album_set2)
Out[7]:
{'Thriller'}
We only consider elements in album_set1; all the elements in album_set2, including the intersection, are not included.
We can also find the intersection, i.e in both album_list2 and album_list1, using the intersection command :
In [9]:
album_set2.intersection(album_set1)
Out[9]:
{'AC/DC', 'Back in Black'}
Intersection of set
The union corresponds to all the elements in both sets, which is represented by colouring both circles:
Strings
A string is contained within 2 quotes:
In [ ]:
"Michael Jackson"
You can also use single quotes:
In [ ]:
'Michael Jackson'
A string can be spaces and digits:
In [ ]:
'1 2 3 4 5 6 '
A string can also be special characters :
In [ ]:
'@#2_#]&*^%$'
We can print our string using the print statement:
In [ ]:
print("hello!")
We can bind or assign a string to another variable:
In [ ]:
Name= "Michael Jackson"
Age = '29'
In [ ]:
print(Name)
print(Age)
Indexing
It is helpful to think of a string as an ordered sequence. Each element in the sequence can be accessed using an index
represented by the array of numbers:
</a>
The last element is given by the index -1:
In [ ]:
print(Name[-1])
The first element can be obtained by index -15:
In [ ]:
print(Name[-15])
We can find the number of characters in a string by using 'len', short for length:
In [ ]:
len("Michael Jackson")
We can obtain multiple characters from a string using slicing, we can obtain the 0 to 4th and 8th to the 12th element:
In [ ]:
Name[0:4]
In [ ]:
In [ ]:
Name[8:12]
In [ ]:
We can also input a stride value as follows, with the '2' indicating that we are selecting every second variable:
</a>
In [ ]:
Name[::2]
We can also incorporate slicing with the stride. In this case, we select the first five elements and then use the stride:
In [ ]:
Name[0:5:2]
We can concatenate or combine strings by using the addition symbols, and the result is a new string that is a
combination of both:
In [ ]:
Statement = Name + " is the best"
Statement
To replicate values of a string we simply multiply the string by the number of times we would like to replicate it. In this
case, the number is three. The result is a new string, and this new string consists of three copies of the original string:
In [ ]:
3*"Michael Jackson "
You can create a new string by setting it to the original variable. Concatenated with a new string, the result is a new
string that changes from Michael Jackson to “Michael Jackson is the best".
In [ ]:
Name= "Michael Jackson"
Name= Name+" is the best"
Name
Statement
Escape Sequences
Back slashes represent the beginning of escape sequences. Escape sequences represent strings that may be difficult to
input. For example, back slash "n" represents a new line. The output is given by a new line after the back slash "n” is
encountered:
In [ ]:
print(" Michael Jackson \n is the best" )
Similarly, back slash "t" represents a tab:
In [ ]:
print(" Michael Jackson \t is the best" )
If you want to place a back slash in your string, use a double back slash:
In [ ]:
print(" Michael Jackson \\ is the best" )
We can also place an "r" before the string to display the backslash:
In [ ]:
print(r" Michael Jackson \ is the best" )
String Operations
There are many string operation methods in Python that can be used to manipulate the data. We are going to use some
basic string operations on the data.
Let's try with the method "upper"; this method converts upper case characters to lower case characters:
In [ ]:
A="Thriller is the sixth studio album"
print("before upper:",A)
B=A.upper()
print("After upper:",B)
The method replaces a segment of the string, i.e. a substring with a new string. We input the part of the string we would
like to change. The second argument is what we would like to exchange the segment with, and the result is a new string
with the segment changed:
In [ ]:
A="Michael Jackson is the best"
B=A.replace('Michael', 'Janet')
B
The method "find" finds a sub-string. The argument is the substring you would like to find, and the output is the first
index of the sequence. We can find the sub-string "jack" or "el".
In [ ]:
Name="Michael Jackson"
Name.find('el')
In [ ]:
In [ ]:
Name.find('Jack')
If the sub-string is not in the string then the output is a negative one. For example, the string 'Jasdfasdasdf' is not a
substring:
In [ ]:
Name.find('Jasdfasdasdf')
LISTS IN PYTHON
About the Dataset
Imagine you received many music recommendations from your friends and compiled all of the recommendations into a
table, with specific information about each movie.
The table has one row for each album and several columns:
Lists
We are going to take a look at lists in Python. A list is a sequenced collection of different objects such as
integers, strings, and other lists as well. The address of each element within a list is called an 'index'. An index
is used to access and refer to items within a list.
Representation of a list
To create a list, type the list within square brackets [ ], with your content inside the parenthesis and separated by
commas. Let’s try it!
In [1]:
L=["Michael Jackson" , 10.1,1982]
L
Out[1]:
['Michael Jackson', 10.1, 1982]
In [2]:
type(L)
Out[2]:
list
Representation of a list
In [3]:
print('the same element using negative and positive indexing:\n Postive:',L[0],
'\n Negative:' , L[-3] )
print('the same element using negative and positive indexing:\n Postive:',L[1],
'\n Negative:' , L[-2] )
print('the same element using negative and positive indexing:\n Postive:',L[2],
'\n Negative:' , L[-1] )
the same element using negative and positive indexing:
Postive: Michael Jackson
Negative: Michael Jackson
the same element using negative and positive indexing:
Postive: 10.1
Negative: 10.1
the same element using negative and positive indexing:
Postive: 1982
Negative: 1982
Lists can contain strings, floats, and integers. We can nest other lists, and we can also nest tuples and other data structures. The same indexing conventions apply for
nesting:
In [2]:
[ "Michael Jackson", 10.1,1982,[1,2],("A",1) ]
Out[2]:
['Michael Jackson', 10.1, 1982, [1, 2], ('A', 1)]
We can also perform slicing in lists. For example, if we want the last two elements, we use the following command:
In [3]:
L=[ "Michael Jackson", 10.1,1982,"MJ",1]
L
Out[3]:
['Michael Jackson', 10.1, 1982, 'MJ', 1]
Representation of a list
In [4]:
L[3:5]
Out[4]:
['MJ', 1]
We can use the method "extend" to add new elements to the list:
In [5]:
L=[ "Michael Jackson", 10.2]
L.extend(['pop',10])
L
Out[5]:
['Michael Jackson', 10.2, 'pop', 10]
Another similar method is 'appended'. If we apply 'appended' instead of 'extended', we add one element to the
list:
In [6]:
L=[ "Michael Jackson", 10.2]
L.append(['pop',10])
L
Out[6]:
['Michael Jackson', 10.2, ['pop', 10]]
Each time we apply a method, the list changes. If we apply "extend" we add two new elements to the list. The
list L is then modified by adding two new elements:
In [7]:
L=[ "Michael Jackson", 10.2]
L.extend(['pop',10])
L
Out[7]:
['Michael Jackson', 10.2, 'pop', 10]
If we append the list ['a','b'] we have one new element consisting of a nested list:
In [8]:
L.append(['a','b'])
L
Out[8]:
['Michael Jackson', 10.2, 'pop', 10, ['a', 'b']]
As lists are mutable, we can change them. For example, we can change the first element as follows:
In [3]:
A=["disco",10,1.2]
print('Before change:', A)
A[0]='hard rock'
print('After change:', A)
Before change: ['disco', 10, 1.2]
After change: ['hard rock', 10, 1.2]
We can also delete an element of a list using the del command:
In [4]:
print('Before change:', A)
del(A[0])
print('After change:', A)
We can convert a string to a list using 'split'. For example, the method split translates every group of characters
separated by a space into an element in a list:
In [11]:
'hard rock'.split()
Out[11]:
['hard', 'rock']
We can use the split function to separate strings on a specific character. We pass the character we would like to
split on into the argument, which in this case is a comma. The result is a list, and each element corresponds to a
set of characters that have been separated by a comma:
In [6]:
'A,B,C,D'.split(',')
Out[6]:
['A', 'B', 'C', 'D']
When we set one variable B equal to A; both A and B are referencing the same list in memory :
In [18]:
A=["hard rock",10,1.2]
B=A
print('A:',A)
print('B:',B)
A: ['hard rock', 10, 1.2]
B: ['hard rock', 10, 1.2]
Initially, the value of the first element in B is set as hard rock. If we change the first element in A to 'banana',
we get an unexpected side effect. As A and B are referencing the same list, if we change list A, then list B also
changes. If we check the first element of B we get banana instead of hard rock:
In [19]:
print('B[0]:',B[0])
A[0]="banana"
print('B[0]:',B[0])
B[0]: hard rock
B[0]: banana
Dictionary:
Dictionaries are used to store data values in key:value pairs. A dictionary is a collection which is unordered, changeable
and does not allow duplicates. Dictionaries are written with curly brackets, and have keys and values:
In [ ]:
In [18]:
thisdict = {
"brand": "Ford",
"model": "Mustang",
"year": 1964
}
print(thisdict)
{'brand': 'Ford', 'model': 'Mustang', 'year': 1964}
Dictionary Items:
Dictionary items are unordered, changeable, and does not allow duplicates.
Dictionary items are presented in key:value pairs, and can be referred to by using the key name.
In [2]:
thisdict = {
"brand": "Ford",
"model": "Mustang",
"year": 1964
}
print(thisdict["brand"])
Ford
Unordered:
When we say that dictionaries are unordered, it means that the items does not have a defined order, you cannot refer to
an item by using an index.
Changeable
Dictionaries are changeable, meaning that we can change, add or remove items after the dictionary has been created.
Duplicates Not Allowed:
Dictionaries cannot have two items with the same key:
In [19]:
thisdict = {
"brand": "Ford",
"model": "Mustang",
"year": 1964,
"year": 2020
}
print(thisdict)
{'brand': 'Ford', 'model': 'Mustang', 'year': 2020}
Dictionary Length
To determine how many items a dictionary has, use the len() function:
In [4]:
print(len(thisdict))
3
{'brand': 'Ford', 'electric': False, 'year': 1964, 'colors': ['red', 'white', 'blue']}
type():
From Python's perspective, dictionaries are defined as objects with the data type 'dict':
In [20]:
thisdict = {
"brand": "Ford",
"model": "Mustang",
"year": 1964
}
print(type(thisdict))
<class 'dict'>
Nested dictionary:
In [7]:
nestdic = {
"brand": {"Cars":"Ford","cloths":"LV","Shoes":"Nike"},
"model": "Mustang",
"year": 1964
}
print(nestdic)
{'brand': {'Cars': 'Ford', 'cloths': 'LV', 'Shoes': 'Nike'}, 'model': 'Mustang', 'year':
1964}
In [23]:
car = {
"brand": "Ford",
"model": "Mustang",
"year": 1964
}
car.clear()
print(car)
{}
In [24]:
car = {
"brand": "Ford",
"model": "Mustang",
"year": 1964
}
x = car.copy()
print(x)
{'brand': 'Ford', 'model': 'Mustang', 'year': 1964}
In [25]:
x = ('key1', 'key2', 'key3')
y = 0
thisdict = dict.fromkeys(x, y)
print(thisdict)
{'key1': 0, 'key2': 0, 'key3': 0}
In [26]:
car = {
"brand": "Ford",
"model": "Mustang",
"year": 1964
}
x = car.get("model")
print(x)
Mustang
In [28]:
car = {
"brand": "Ford",
"model": "Mustang",
"year": 1964
}
x = car.items()
print(x)
dict_items([('brand', 'Ford'), ('model', 'Mustang'), ('year', 1964)])
271-CIS-3 Lab Manual - 2022 44
Department of Information Systems ﻗﺴﻢ ﻧﻈﻢ اﻟﻤﻌﻠﻮﻣﺎت
In [15]:
car = {
"brand": "Ford",
"model": "Mustang",
"year": 1964
}
x = car.keys()
print(x)
dict_keys(['brand', 'model', 'year'])
In [30]:
car = {
"brand": "Ford",
"model": "Mustang",
"year": 1964
}
x = car.keys()
car["color"] = "white"
print(x)
dict_keys(['brand', 'model', 'year', 'color'])
In [32]:
car = {
"brand": "Ford",
"model": "Mustang",
"year": 1964
}
x = car.pop("model")
print(x)
Mustang
In [33]:
print(car)
{'brand': 'Ford', 'year': 1964}
Conditions in Python
Comparison Operators
Comparison operations compare some value or operand and, based on a condition, they produce a Boolean. When
comparing two values you can use these operators:
• equal: ==
• not equal: !=
• greater than: >
• less than: <
• greater than or equal to: >=
• less than or equal to: <=
Let's assign a a value of 5. Use the equality operator denoted with two equal == signs to determine if two values are
equal. The case below compares the variable a with 6.
In [ ]:
# Condition Equal
a = 5
a == 6
The result is False, as 5 does not equal to 6.
Consider the following equality comparison operator i > 5. If the value of the left operand, in this case the variable i, is
greater than the value of the right operand, in this case 5, then the statement is True. Otherwise, the statement is False.
If i is equal to 6, because 6 is larger than 5, the output is True.
In [ ]:
# Greater than Sign
i = 6
i > 5
Set i = 2. The statement is false as 2 is not greater than 5:
In [ ]:
# Greater than Sign
i = 2
i > 5
Let's display some values for i in the figure. Set the values greater than 5 in green and the rest in red. The green region
represents where the condition is True, the red where the statement is False. If the value of i is 2, we get False as the 2
falls in the red region. Similarly, if the value for i is 6 we get a True as the condition falls in the green region.
The inequality test uses an exclamation mark preceding the equal sign, if two operands are not equal then the condition
becomes True. For example, the following condition will produce True as long as the value of i is not equal to 6:
In [ ]:
# Inequality Sign
i = 2
i != 6
When i equals 6 the inequality expression produces False.
In [ ]:
# Inequality Sign
i = 6
i != 6
See the number line below. when the condition is True the corresponding numbers are marked in green and for where
the condition is False the corresponding number is marked in red. If we set i equal to 2 the operator is true as 2 is in the
green region. If we set i equal to 6, we get a False as the condition falls in the red region.
We can apply the same methods on strings. For example, use an equality operator on two different strings. As the strings
are not equal, we get a False.
In [ ]:
# Use Equality sign to compare the strings
When there are multiple letters, the first letter takes precedence in ordering:
In [ ]:
# Compare characters
Note: Upper Case Letters have different ASCII code than Lower Case Letters, which means the comparison between the
letters in python is case-sensitive.
Branching
Branching allows us to run different statements for different inputs. It is helpful to think of an if statement as a locked
room, if the statement is True we can enter the room and your program will run some predefined tasks, but if the
statement is False the program will ignore the task.
For example, consider the blue rectangle representing an ACDC concert. If the individual is older than 18, they can enter
the ACDC concert. If they are 18 or younger than 18 they cannot enter the concert.
Use the condition statements learned before as the conditions need to be checked in the if statement. The syntax is as
simple as if condition statement :, which contains a word if, any condition statement, and a colon at the end. Start your
tasks which need to be executed under this condition in a new line with an indent. The lines of code after the colon and
with an indent will only be executed when the if statement is True. The tasks will end when the line of code does not
contain the indent.
In the case below, the tasks executed print(“you can enter”) only occurs if the variable age is greater than 18 is a True
case because this line of code has the indent. However, the execution of print(“move on”) will not be influenced by the if
statement.
In [ ]:
# If statement example
#age = 19
age = 18
#within an indent, we have the expression that is run if the condition is true
print("you can enter" )
#The statements after the if statement will run regardless if the condition is true or
false
print("move on")
Try uncommenting the age variable
It is helpful to use the following diagram to illustrate the process. On the left side, we see what happens when the
condition is True. The person enters the ACDC concert representing the code in the indent being executed; they then
move on. On the right side, we see what happens when the condition is False; the person is not granted access, and the
person moves on. In this case, the segment of code in the indent does not run, but the rest of the statements are run.
The else statement runs a block of code if none of the conditions are True before this else statement. Let's use the ACDC
concert analogy again. If the user is 17 they cannot go to the ACDC concert, but they can go to the Meatloaf concert. The
syntax of the else statement is similar as the syntax of the if statement, as else :. Notice that, there is no condition
statement for else. Try changing the values of age to see what happens:
In [ ]:
# Else statement example
#age = 18
age = 19
print("move on")
The process is demonstrated below, where each of the possibilities is illustrated on each side of the image. On the left is
the case where the age is 17, we set the variable age to 17, and this corresponds to the individual attending the Meatloaf
concert. The right portion shows what happens when the individual is over 18, in this case 19, and the individual is
granted access to the concert.
The elif statement, short for else if, allows us to check additional conditions if the condition statements before it
are False. If the condition for the elif statement is True, the alternate expressions will be run. Consider the concert
example, where if the individual is 18 they will go to the Pink Floyd concert instead of attending the ACDC or Meat-loaf
concert. The person of 18 years of age enters the area, and as they are not older than 18 they can not see ACDC, but as
they are 18 years of age, they attend Pink Floyd. After seeing Pink Floyd, they move on. The syntax of the elif statement is
similar in that we merely change the if in if statement to elif.
In [ ]:
# Elif statment example
age = 18
print("move on")
The three combinations are shown in the figure below. The left-most region shows what happens when the individual is
less than 18 years of age. The central component shows when the individual is exactly 18. The rightmost shows when
the individual is over 18.
album_year = 1983
album_year = 1970
print('do something..')
Feel free to change album_year value to other values -- you'll see that the result changes!
Notice that the code in the above indented block will only be executed if the results are True.
As before, we can add an else block to the if block. The code in the else block will only be executed if the result is False.
Syntax:
if (condition):
# do something
else:
# do something else
If the condition in the if statement is False, the statement after the else block will execute. This is demonstrated in the
figure:
In [ ]:
# Condition statement example
#album_year = 1983
album_year = 1970
print('do something..')
Feel free to change the album_year value to other values -- you'll see that the result changes based on it!
Logical operators
Sometimes you want to check more than one condition at once. For example, you might want to check if one condition
and another condition is True. Logical operators allow you to combine or modify conditions.
• and
• or
• not
These operators are summarized for two variables using the following truth tables:
The and statement is only True when both conditions are true. The or statement is true if one condition is True.
The not statement outputs the opposite truth value.
Let's see how to determine if an album was released after 1979 (1979 is not included) and before 1990 (1990 is not
included). The time periods between 1980 and 1989 satisfy this condition. This is demonstrated in the figure below. The
green on lines a and b represents periods where the statement is True. The green on line c represents where both
conditions are True, this corresponds to where the green regions overlap.
album_year = 1980
if(album_year > 1979) and (
album_year < 1990):
print ("Album year was in between 1980 and 1989")
print("")
print("Do Stuff..")
To determine if an album was released before 1980 (~ - 1979) or after 1989 (1990 - ~), an or statement can be used.
Periods before 1980 (~ - 1979) or after 1989 (1990 - ~) satisfy this condition. This is demonstrated in the following
figure, the color green in a and b represents periods where the statement is true. The color green in c represents where
at least one of the conditions are true.
album_year = 1990
album_year = 1983
Loops in Python
Range
Sometimes, you might want to repeat a given operation many times. Repeated executions like this are performed
by loops. We will look at two types of loops, for loops and while loops.
Before we discuss loops lets discuss the range object. It is helpful to think of the range object as an ordered list. For now,
let's look at the simplest case. If we would like to generate a sequence that contains three elements ordered from 0 to 2
we simply use the following command:
In [ ]:
# Use the range
range(3)
dates = [1982,1980,1973]
N = len(dates)
for i in range(N):
print(dates[i])
In [ ]:
print(dates[0])
print(dates[1])
print(dates[2])
The code in the indent is executed N times, each time the value of i is increased by 1 for every execution. The statement
executed is to print out the value in the list at index i as shown here:
In [ ]:
range(0,8)
For each iteration, the value of the variable years behaves like the value of dates[i] in the first example:
i = 0
year = 0
while(year != 1973):
year = dates[i]
i = i + 2
print(year)
Functions in Python
A function is a reusable block of code which performs operations specified in the function. They let you break down
tasks and allow you to reuse your code in different programs.
• Pre-defined functions
• User defined functions
What is a Function?
You can define functions to provide the required functionality. Here are simple rules to define a function in Python:
• Functions blocks begin def followed by the function name and parentheses ().
• There are input parameters or arguments that should be placed within these parentheses.
• You can also define parameters inside these parentheses.
• There is a body within every function that starts with a colon (:) and is indented.
• You can also place documentation before the body
• The statement return exits a function, optionally passing back a value
An example of a function that adds on to the parameter a prints and returns the output as b:
In [1]:
# First function example: Add 1 to a and store as b
def add(a):
b = a + 1
print(a, "if you add one", b)
return(b)
The figure below illustrates the terminology:
help(add)
Help on function add in module __main__:
add(a)
add(1)
1 if you add one 2
Out[3]:
2
If we call the function with a new input we get a new result:
In [4]:
# Call the function add()
add(2)
2 if you add one 3
Out[4]:
3
We can create different functions. For example, we can create a function that multiplies two numbers. The numbers will
be represented by the variables a and b:
In [6]:
# Define a function for multiple two numbers
Mult(2, 3)
Out[7]:
6
Two Floats:
In [8]:
# Use mult() multiply two floats
Mult(10.0, 3.14)
Out[8]:
31.400000000000002
We can even replicate a string by multiplying with an integer:
In [ ]:
# Use mult() multiply two different type values together
Variables
The input to a function is called a formal parameter.
A variable that is declared inside a function is called a local variable. The parameter only exists within the function (i.e.
the point where the function starts and stops).
A variable that is declared outside a function definition is a global variable, and its value is accessible and modifiable
throughout the program. We will discuss more about global variables at the end of the lab.
In [11]:
# Function Definition
def square(a):
# Local variable b
b = 10
c = a * a + b
print(a, "if you square + 1", c)
return(c)
The labels are displayed in the figure:
x = 3
# Makes function call and return function a y
y = square(x)
y
3 if you square + 1 19
Out[12]:
19
square(2)
If there is no return statement, the function returns None. The following two functions are equivalent:
In [13]:
# Define functions, one with return value None and other without return value
def MJ():
print('Michael Jackson')
def MJ1():
print('Michael Jackson')
return(None)
In [14]:
# See the output
MJ()
Michael Jackson
In [15]:
# See the output
MJ1()
Michael Jackson
Printing the function after a call reveals a None is the default return statement:
In [16]:
# See what functions returns are
print(MJ())
print(MJ1())
Michael Jackson
None
Michael Jackson
None
Create a function con that concatenates two strings using the addition operation:
In [18]:
# Define the function for combining strings
Block 1:
In [20]:
# a and b calculation block1
a1 = 4
b1 = 5
c1 = a1 + b1 + 2 * a1 * b1 - 1
if(c1 < 0):
c1 = 0
else:
c1 = 5
c1
Out[20]:
5
Block 2:
In [21]:
# a and b calculation block2
a2 = 0
b2 = 0
c2 = a2 + b2 + 2 * a2 * b2 - 1
if(c2 < 0):
c2 = 0
else:
c2 = 5
c2
Out[21]:
0
We can replace the lines of code with a function. A function combines many instructions into a single line of code. Once
a function is defined, it can be used repeatedly. You can invoke the same function many times in your program. You can
save your function and use it in another program or use someone else’s function. The lines of code in code Block 1 and
code Block 2 can be replaced by the following function:
In [22]:
# Make a Function for the calculation above
def Equation(a,b):
c = a + b + 2 * a * b - 1
if(c < 0):
c = 0
else:
c = 5
return(c)
This function takes two inputs, a and b, then applies several operations to return c. We simply define the function,
replace the instructions with the function, and input the new values of a1, b1 and a2, b2 as inputs. The entire process is
demonstrated in the figure:
Code Blocks 1 and Block 2 can now be replaced with code Block 3 and code Block 4.
Block 3:
In [ ]:
a1 = 4
b1 = 5
c1 = Equation(a1, b1)
c1
Block 4:
In [ ]:
a2 = 0
b2 = 0
c2 = Equation(a2, b2)
c2
Pre-defined functions
There are many pre-defined functions in Python, so let's start with the simple ones.
The print() function:
In [ ]:
# Build-in function print()
sum(album_ratings)
The len() function returns the length of a list or tuple:
In [ ]:
# Show the length of the list or tuple
len(album_ratings)
def PrintList(the_list):
for element in the_list:
print(element)
In [ ]:
# Implement the printlist function
def isGoodRating(rating=4):
if(rating < 7):
print("this album sucks it's rating is",rating)
else:
print("this album is good its rating is",rating)
In [24]:
# Test the value with default value and with input
isGoodRating()
isGoodRating(10)
this album sucks it's rating is 4
this album is good its rating is 10
Global variables
So far, we've been creating variables within functions, but we have not discussed variables outside the function. These
are called global variables.
Let's try to see what printer1 returns:
In [25]:
# Example of global variable
printer1(artist)
Michael Jackson is an artist
In [27]:
artist
Out[27]:
'Michael Jackson'
If we print internal_var we get an error.
We got a Name Error: name 'internal_var' is not defined. Why?
It's because all the variables we create in the function is a local variable, meaning that the variable assignment does not
persist outside the function.
But there is a way to create global variables from within a function as follows:
In [28]:
artist = "Michael Jackson"
def printer(artist):
global internal_var
internal_var= "Whitney Houston"
print(artist,"is an artist")
printer(artist)
printer(internal_var)
Michael Jackson is an artist
Whitney Houston is an artist
In [29]:
internal_var
Out[29]:
'Whitney Houston'
Scope of a Variable
The scope of a variable is the part of that program where that variable is accessible. Variables that are declared outside
of all function definitions, such as the myFavouriteBand variable in the code shown here, are accessible from anywhere
within the program. As a result, such variables are said to have global scope, and are known as global
variables. myFavouriteBand is a global variable, so it is accessible from within the getBandRating function, and we can use
it to determine a band's rating. We can also use it outside of the function, such as when we pass it to the print function to
display it:
In [30]:
# Example of global variable
myFavouriteBand = "AC/DC"
def getBandRating(bandname):
if bandname == myFavouriteBand:
return 10.0
else:
return 0.0
Take a look at this modified version of our code. Now the myFavouriteBand variable is defined within
the getBandRating function. A variable that is defined within a function is said to be a local variable of that function. That
means that it is only accessible from within the function in which it is defined. Our getBandRating function will still work,
because myFavouriteBand is still defined within the function. However, we can no longer print myFavouriteBand outside
our function, because it is a local variable of our getBandRating function; it is only defined within
the getBandRating function:
In [31]:
# Example of local variable
def getBandRating(bandname):
myFavouriteBand = "AC/DC"
if bandname == myFavouriteBand:
return 10.0
else:
return 0.0
Finally, take a look at this example. We now have two myFavouriteBand variable definitions. The first one of these has a
global scope, and the second of them is a local variable within the getBandRating function. Within
the getBandRating function, the local variable takes precedence. Deep Purple will receive a rating of 10.0 when passed
to the getBandRating function. However, outside of the getBandRating function, the getBandRating s local variable is not
defined, so the myFavouriteBand variable we print is the global variable, which has a value of AC/DC:
In [32]:
# Example of global variable and local variable with the same name
myFavouriteBand = "AC/DC"
def getBandRating(bandname):
myFavouriteBand = "Deep Purple"
if bandname == myFavouriteBand:
return 10.0
else:
return 0.0
Python Modules
What is a Module?
Consider a module to be the same as a code library. A file containing a set of functions you want to include in your
application.
Create a Module
To define a module, simply use your text editor to type some Python code into a text file, and save it with a “.py”
extension; any such file is automatically considered a Python module.
as here we create file name firstModule and write the code which we want on it
Use a Module
Now we can use the module we just created, by using the import statement:
For instance, if you type the following def into a file called module1.py and import it, you create a module object with
one attribute—the name printer, which happens to be a reference to a function object:
This form of from allows us to list one or more names to be copied out, separated by commas.
In [ ]:
Re-naming a Module
You can create an alias when you import a module, by using the as keyword:
In [ ]:
import module1 as m
m.printer('Hello world!')
In [ ]:
Built-in Modules
There are several built-in modules in Python, which you can import whenever you like. Import and use the platform
module:
In [ ]:
import platform
x = platform.system()
print(x)
x = dir(platform)
print(x)
Example
In [ ]:
from small import x, y # Copy two names out
x = 42 # Changes local x only
y[0] = 42
print (x)
print(y)
G.greeting("Jonathan",25,"USA")
In [ ]:
from greeting1 import person1
print (person1["age"])
In [ ]:
Note:
When importing using the from keyword, do not use the module name when referring to elements in the module.
Example: person1["age"], not mymodule.person1["age"]
In [ ]:
Packages
Python has packages for directories and modules for files.
A Python package can have sub-packages and modules.
A directory
must contain a file named __init__.py in order for Python to consider it as a package. This file can be left
empty but we generally place the initialization code for that package in this file.
Example: Suppose we are developing a game. One possible organization of packages and modules could
be as shown in the figure below.
import Game.Level.start
Practical Example:
1* Create floder inside your folder name it as MyApp
2* go inside Packages inside and create the __init__.py file (any name for it)
def SayHello(name):
def sum(x,y):
return x+y
def average(x,y):
return (x+y)/2
def power(x,y):
return x**y
In [1]:
import MyApp.mypackage.greeting as M
In [2]:
M.SayHello("I'm KKU CS Student")
Hello I'm KKU CS Student
In [5]:
import MyApp.mypackage.Functions as f
f.power(1,2)
Out[5]:
1
In [ ]:
__init__.py
The package folder contains a special file called init.py, which stores the package's content. It serves two
purposes:
An empty __init__.py file makes all functions from the above modules available when this package is imported.
Note that __init__.py is essential for the folder to be recognized by Python as a package. You can optionally define
functions from individual modules to be made available
In [9]:
import MyApp.mypackage.Functions as f
f.power(2,3)
Out[9]:
8
In [10]:
from MyApp.mypackage.Functions import power
from MyApp.mypackage.greeting import SayHello
SayHello("Hello")
x=power(3,2)
print("power(3,2) : ", x)
power(2,3)
Hello Hello
power(3,2) : 9
Out[10]:
8
OOP: Introduction
Object-oriented programming has some advantages over other design patterns. Development is faster
and cheaper, with better software maintainability. This, in turn, leads to higher-quality software, which is
also extensible with new methods and attributes. The learning curve is, however, steeper. The concept
may be too complex for beginners. Computationally, OOP software is slower, and uses more memory
since more lines of code have to be written.
Object-oriented programming is based on the imperative programming paradigm, which uses statements
to change a program's state. It focuses on describing how a program should operate. Examples of
imperative programming languages are C, C++, Java, Go, Ruby and Python. This stands in contrast to
declarative programming, which focuses on what the computer program should accomplish, without
specifying how. Examples are database query languages like SQL and XQuery, where one only tells the
computer what data to query from where, but now how to do it.
OOP uses the concept of objects and classes. A class can be thought of as a 'blueprint' for objects. These
can have their own attributes (characteristics they possess), and methods (actions they perform).
OOP Example
An example of a class is the class Dog. Don't think of it as a specific dog, or your own dog. We're
describing what a dog is and can do, in general. Dogs usually have a name and age; these are instance
attributes. Dogs can also bark; this is a method.
When you talk about a specific dog, you would have an object in programming: an object is an
instantiation of a class. This is the basic principle on which object-oriented programming is based. So my
dog Ozzy, for example, belongs to the class Dog. His attributes are name = 'Ozzy' and age = '2'. A
different dog will have different attributes.
OOP in Python
Python is a great programming language that supports OOP. You will use it to define a class with
attributes and methods, which you will then call. Python offers a number of benefits compared to other
programming languages like Java, C++ or R. It's a dynamic language, with high-level data types. This
means that development happens much faster than with Java or C++. It does not require the programmer
to declare types of variables and arguments. This also makes Python easier to understand and learn for
beginners, its code being more readable and intuitive.
To define a class in Python, you can use the class keyword, followed by the class name and a colon.
Inside the class, an __init__ method has to be defined with def. This is the initializer that you can later
use to instantiate objects. It's similar to a constructor in Java. __init__ must always be present! It takes
one argument: self, which refers to the object itself. Inside the method, the pass keyword is used as of
now, because Python expects you to type something there. Remember to use correct indentation!
class Dog:
def __init__(self):
pass
In this case, you have a (mostly empty) Dog class, but no object yet. Let's create one!
Instantiating objects
To instantiate an object, type the class name, followed by two brackets. You can assign this to a variable
to keep track of the object.
ozzy = Dog()
After printing ozzy, it is clear that this object is a dog. But you haven't added any attributes yet. Let's give
the Dog class a name and age, by rewriting it:
class Dog:
You can see that the function now takes two arguments after self: name and age. These then get
assigned to self.name and self.age respectively. You can now now create a new ozzy object, with a
name and age:
ozzy = Dog("Ozzy", 2)
To access an object's attributes in Python, you can use the dot notation. This is done by typing the name
of the object, followed by a dot and the attribute's name.
print(ozzy.name)
print(ozzy.age)
Ozzy
2
The str() function is used here to convert the age attribute, which is an integer, to a string, so you can
use it in the print() function.
Now that you have aDog class, it does have a name and age which you can keep track of, but it doesn't
actually do anything. This is where instance methods come in. You can rewrite the class to now include
a bark() method. Notice how the def keyword is used again, as well as the self argument.
class Dog:
def bark(self):
print("bark bark!")
The bark method can now be called using the dot notation, after instantiating a new ozzy object. The
method should print "bark bark!" to the screen. Notice the parentheses (curly brackets) in .bark().
These are always used when calling a method. They're empty in this case, since the bark() method does
not take any arguments.
ozzy = Dog("Ozzy", 2)
ozzy.bark()
bark bark!
Recall how you printed ozzy earlier? The code below now implements this functionality in the Dog class,
with the doginfo() method. You then instantiate some objects with different properties, and call the
method on them.
class Dog:
def bark(self):
print("bark bark!")
def doginfo(self):
print(self.name + " is " + str(self.age) + " year(s) old.")
ozzy = Dog("Ozzy", 2)
skippy = Dog("Skippy", 12)
filou = Dog("Filou", 8)
ozzy.doginfo()
skippy.doginfo()
filou.doginfo()
Ozzy is 2 year(s) old.
Skippy is 12 year(s) old.
Filou is 8 year(s) old.
As you can see, you can call the doginfo() method on objects with the dot notation. The response now
depends on which Dog object you are calling the method on.
Since dogs get older, it would be nice if you could adjust their age accordingly. Ozzy just turned 3, so let's
change his age.
ozzy.age = 3
print(ozzy.age)
3
It's as easy as assigning a new value to the attribute. You could also implement this as
a birthday() method in the Dog class:
class Dog:
def bark(self):
print("bark bark!")
def doginfo(self):
print(self.name + " is " + str(self.age) + " year(s) old.")
def birthday(self):
self.age +=1
ozzy = Dog("Ozzy", 2)
print(ozzy.age)
2
ozzy.birthday()
print(ozzy.age)
3
Now, you don't need to manually change the dog's age. whenever it is its birthday, you can just call
the birthday() method.
You would like for our dogs to have a buddy. This should be optional, since not all dogs are as sociable.
Take a look at the setBuddy() method below. It takes self, as per usual, and buddy as arguments. In
this case, buddy will be another Dog object. Set the self.buddy attribute to buddy, and
the buddy.buddy attribute to self. This means that the relationship is reciprocal; you are your buddy's
buddy. In this case, Filou will be Ozzy's buddy, which means that Ozzy automatically becomes Filou's
buddy. You could also set these attributes manually, instead of defining a method, but that would require
more work (writing 2 lines of code instead of 1) every time you want to set a buddy. Notice that in Python,
you don't need to specify of what type the argument is. If this were Java, it would be required.
class Dog:
def bark(self):
print("bark bark!")
def doginfo(self):
print(self.name + " is " + str(self.age) + " year(s) old.")
def birthday(self):
self.age +=1
You can now call the method with the dot notation, and pass it another Dog object. In this case, Ozzy's
buddy will be Filou:
ozzy = Dog("Ozzy", 2)
filou = Dog("Filou", 8)
ozzy.setBuddy(filou)
If you now want to get some information about Ozzy's buddy, you can use the dot notation twice:. First,
to refer to Ozzy's buddy, and a second time to refer to its attribute.
print(ozzy.buddy.name)
print(ozzy.buddy.age)
Filou
8
The buddy's methods can also be called. The self argument that gets passed to doginfo() is
now ozzy.buddy, which is filou.
ozzy.buddy.doginfo()
Filou is 8 year(s) old.
Object-oriented programming (OOP) is a method of structuring a program by bundling related properties and behaviors
into individual objects. In this tutorial, you’ll learn the basics of object-oriented programming in Python.
objects are like the components of a system. Think of a program as a factory assembly line of sorts. At each step of the
assembly line a system component processes some material, ultimately transforming raw material into a finished
product.
An object contains data, like the raw or preprocessed materials at each step:
Create a class, which is like a blueprint for creating an object Use classes to create new objects Model systems with class
inheritance
*Use attributes and methods to define the properties and behaviors of an object
Essentially, a class is a way of grouping functions (as methods) and data (as properties) into a logical unit revolving
around a certain kind of thing. If you don't need that grouping, there's no need to make a class. All class definitions start
with the class keyword, which is followed by the name of the class and a colon. Any code that is indented below the class
definition is considered part of the class’s body.
Note:
Python class names are written in CapitalizedWords notation by convention. For example, a class for a specific
breed of dog like the Jack Russell Terrier would be written as JackRussellTerrier.
The properties that all Dog objects must have are defined in a method called .__init__(). Every time a new Dog object
is created, .__init__() sets the initial state of the object by assigning the values of the object’s properties. That is,
.__init__() initializes each new instance of the class.
You can give .__init__() any number of parameters, but the first parameter will always be a variable called self.
When a new class instance is created, the instance is automatically passed to the self parameter in .__init__() so that
new attributes can be defined on the object.
Let’s update the Dog class with an .__init__() method that creates .name and .age attributes:
In [ ]:
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
Notice that the .__init__() method’s signature is indented four spaces. The body of the method is indented by eight
spaces. This indentation is vitally important. It tells Python that the .__init__() method belongs to the Dog class.
In the body of .__init__(), there are two statements using the self variable:
self.name = name creates an attribute called name and assigns to it the value of the name parameter.
self.age = age creates an attribute called age and assigns to it the value of the age parameter.
Attributes created in .__init__() are called instance attributes. An instance attribute’s value is specific to a particular
instance of the class. All Dog objects have a name and an age, but the values for the name and age attributes will
vary depending on the Dog instance.
On the other hand, class attributes are attributes that have the same value for all class instances. You can define a
class attribute by assigning a value to a variable name outside of .__init__().
For example, the following Dog class has a class attribute called species with the value "Canis familiaris":
In [ ]:
class Dog:
# Class attribute
species = "Canis familiaris"
Use class attributes to define properties that should have the same value for every class instance. Use instance attributes
for properties that vary from one instance to another.
Creating a new object from a class is called instantiating an object. You can instantiate a new Dog object by
typing the name of the class, followed by opening and closing parentheses
In [ ]:
buddy = Dog("Buddy", 9)
miles = Dog("Miles", 4)
The Dog class’s .init() method has three parameters, so why are only two arguments passed to it in the example?
When you instantiate a Dog object, Python creates a new instance and passes it to the first parameter of .init(). This
essentially removes the self parameter, so you only need to worry about the name and age parameters.
After you create the Dog instances, you can access their instance attributes using dot notation:
In [ ]:
buddy.name
In [ ]:
miles.name
In [ ]:
#access class attribute
buddy.species
In [ ]:
#you can change the passed values any time you want
buddy.age
In [ ]:
buddy.age=5
In [ ]:
buddy.age
Instance Methods
Instance methods are functions that are defined inside a class and can only be called from an instance of that class.
Just like .__init__(), an instance method’s first parameter is always self.
# Instance method
def description(self):
#return f"{self.name} is {self.age} years old"
return self.name , self.age
.description() returns a string displaying the name and age of the dog.
.speak() has one parameter called sound and returns a string containing the dog’s name and the sound the dog
makes.
In [ ]:
Dog1 = Dog("mile",4)
Dog1.description()
In [ ]:
Dog1.speak("Woof Woof")
In [ ]:
Dog1.speak("Bow Wow")
in the above Dog class, .description() returns a string containing information about the Dog instance miles. When writing
your own classes, it’s a good idea to have a method that returns a string containing useful information about an instance
of the class. However, .description() isn’t the most Pythonic way of doing this.
you can use print() Let’s see what happens when you print() the miles object
In [ ]:
print(miles)
When you print(miles), you get a <b>cryptic looking message telling you that miles is a Dog object at the memory
address 0x00aeff70</b>. This message isn’t very helpful. You can change what gets printed by defining a special
instance method called .__str__().
In the editor window, change the name of the Dog class’s .description() method to .__str__():
In [ ]:
class Dog:
species = "Canis familiaris"
# Instance method
def __str__(self):
return f"{self.name} is {self.age} years old"
class Dog:
species = "Canis familiaris"
# Instance method
def __str__(self):
return f"{self.name} is {self.age} years old"
# method 1
def showDescription(self):
print("Color of the car:\n",
self.color,"\nMileage of the car\n"
,self.mileage, "Mileage")
In [ ]:
blue_car = Car('Blue', 20000)
# call method 1
blue_car.showDescription()
# Prints This car is a Black Sedan
In [ ]:
red_car = Car('Red', 30000)
# call method 1
red_car.showDescription()
# Prints This car is a Black Sedan
In [ ]:
# Instance method
def description(self):
#return f"{self.name} is {self.age} years old"
return self.name , self.age
You can simplify the experience of working with the Dog class by creating a child class for each breed of dog. This allows
you to extend the functionality that each child class inherits, including specifying a default argument for .speak().
# Instance method
def description(self):
#return f"{self.name} is {self.age} years old"
return self.name , self.age
class Dachshund(Dog):
pass
class Bulldog(Dog):
pass
In [ ]:
miles = JackRussellTerrier("Miles", 4)
buddy = Dachshund("Buddy", 9)
jack = Bulldog("Jack", 3)
jim = Bulldog("Jim", 5)
jam = Dog('Jam',8)
In [ ]:
miles.species
In [ ]:
jack.name
In [ ]:
jim.age
To determine which class a given object belongs to, you can use the built-in type():
In [ ]:
type(miles)
In [ ]:
type(jam)
What if you want to determine if miles is also an instance of the Dog class? You can do this with the built-in isinstance():
271-CIS-3 Lab Manual - 2022 96
Department of Information Systems ﻗﺴﻢ ﻧﻈﻢ اﻟﻤﻌﻠﻮﻣﺎت
In [ ]:
isinstance(miles, JackRussellTerrier)
In [ ]:
isinstance(jam, Bulldog)
To override a method defined on the parent class, you define a method with the same name on the child class. Here’s
what that looks like for the JackRussellTerrier class:
In [ ]:
class JackRussellTerrier(Dog):
def speak(self, sound="Arf"):
return f"{self.name} says {sound}"
In [ ]:
miles = JackRussellTerrier("Miles", 4)
miles.speak()
In [ ]:
abc = JackRussellTerrier("abc", 4)
abc.speak()
In [ ]:
miles.speak("Grrr")
One thing to keep in mind about class inheritance is that changes to the parent class automatically propagate to
child classes. This occurs as long as the attribute or method being changed isn’t overridden in the child class.
For example, in the editor window, change the string returned by .speak() in the Dog class:
In [ ]:
class Dog:
species = "Canis familiaris"
# Instance method
def description(self):
#return f"{self.name} is {self.age} years old"
return self.name , self.age
class Dachshund(Dog):
271-CIS-3 Lab Manual - 2022 97
Department of Information Systems ﻗﺴﻢ ﻧﻈﻢ اﻟﻤﻌﻠﻮﻣﺎت
pass
class Bulldog(Dog):
pass
In [ ]:
jim = Bulldog("Jim", 5)
jim.speak("Woof")
In [ ]:
class JackRussellTerrier(Dog):
def speak(self, sound="Arf"):
return f"{self.name} says {sound}"
However, calling .speak() on a JackRussellTerrier instance won’t show the new style of output:
In [ ]:
miles = JackRussellTerrier("Miles", 4)
miles.speak()
You can access the parent class from inside a method of a child class by using super():
In [ ]:
class JackRussellTerrier(Dog):
def speak(self, sound="Arf"):
return super().speak(sound)
In [ ]:
miles = JackRussellTerrier("Miles", 4)
miles.speak()
In [ ]:
In [ ]:
The finally block lets you execute code, regardless of the result of the try- and except blocks.
Exception Handling
When an error occurs, or exception as we call it, Python will normally stop and generate an error
message.
Example
try:
print(x)
except:
print("An exception occurred")
Since the try block raises an error, the except block will be executed.
Without the try block, the program will crash and raise an error:
Example
print(x)
Many Exceptions
You can define as many exception blocks as you want, e.g. if you want to execute a special block
of code for a special kind of error:
Example
Print one message if the try block raises a NameError and another for other errors:
try:
print(x)
except NameError:
print("Variable x is not defined")
except:
print("Something else went wrong")
Else
You can use the else keyword to define a block of code to be executed if no errors were raised:
Example
In this example, the try block does not generate any error:
try:
print("Hello")
except:
print("Something went wrong")
else:
print("Nothing went wrong")
Finally
The finally block, if specified, will be executed regardless if the try block raises an error or
not.
Example
try:
print(x)
except:
print("Something went wrong")
finally:
print("The 'try except' is finished")
Example
try:
f = open("demofile.txt")
f.write("Lorum Ipsum")
except:
print("Something went wrong when writing to the file")
finally:
f.close()
The program can continue, without leaving the file object open.
Raise an exception
Example
x = -1
if x < 0:
raise Exception("Sorry, no numbers below zero")
You can define what kind of error to raise, and the text to print to the user.
Example
x = "hello"
Exception Examples
print (x)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-1-606ad02f996c> in <module>
----> 1 print (x)
try:
print("hello")
frint()
except NameError as e:
print(e)
except:
print("Something else went wrong")
hello
name 'frint' is not defined
In [8]:
#You can use the else keyword to define a block of code to be executed if no errors were
raised:
try:
print("Hello")
except:
print("Something went wrong")
else:
print("Nothing went wrong")
Hello
Nothing went wrong
In [2]:
#The finally block, if specified, will be executed regardless if the try block raises an
error or not.
try:
print(x)
except:
print("Something went wrong")
finally:
print("The 'try except' is finished")
271-CIS-3 Lab Manual - 2022 104
Department of Information Systems ﻗﺴﻢ ﻧﻈﻢ اﻟﻤﻌﻠﻮﻣﺎت
Example
if x < 0:
raise Exception("Sorry, no numbers below zero")
---------------------------------------------------------------------------
Exception Traceback (most recent call last)
<ipython-input-3-0bedc16e1ab9> in <module>
3
4 if x < 0:
----> 5 raise Exception("Sorry, no numbers below zero")
User-Defined Exceptions
In [1]:
class AlreadyGotOne(Exception):
pass # User-defined exception
def grail():
raise AlreadyGotOne() # Raise an instance
try:
grail()
except AlreadyGotOne: # Catch class name
print('got exception')
got exception
In [30]:
class Career(Exception):
def __str__(self):
return 'So I became a waiter...'
raise Career()
---------------------------------------------------------------------------
Career Traceback (most recent call last)
<ipython-input-30-df005eeec023> in <module>
2 def __str__(self):
3 return 'So I became a waiter...'
----> 4 raise Career()
In [ ]:
Book Example
def fetcher(obj, index):
return obj[index]
In [2]:
x = 'spam'
fetcher(x, 3)
Out[2]:
'm'
In [3]:
fetcher(x, 4)
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
<ipython-input-3-8f3b5aeb5455> in <module>
----> 1 fetcher(x, 4)
IndexError:
In [9]:
271-CIS-3 Lab Manual - 2022 107
Department of Information Systems ﻗﺴﻢ ﻧﻈﻢ اﻟﻤﻌﻠﻮﻣﺎت
try:
fetcher(x, 3)
finally: # Termination actions
print('after fetch')
after fetch
In [10]:
def after():
try:
fetcher(x, 4)
finally:
print('after fetch')
print('after try?')
In [11]:
after()
after fetch
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
<ipython-input-11-4f451c6f09ad> in <module>
----> 1 after()
<ipython-input-10-3140870f4544> in after()
1 def after():
2 try:
----> 3 fetcher(x, 4)
4 finally:
5 print('after fetch')
def after():
try:
fetcher(x, 4)
except IndexError:
print('out of index')
finally:
print('after fetch')
print('after try?')
In [13]:
after()
out of index
after fetch
after try?
In [14]:
def after():
try:
fetcher(x, 3)
finally:
print('after fetch')
print('after try?')
In [15]:
after()
after fetch
after try?
Python MySQL
MySQL Database
To be able to experiment with the code examples in this tutorial, you should have MySQL
installed on your computer.
Navigate your command line to the location of PIP, and type the following:
To test if the installation was successful, or if you already have "MySQL Connector" installed,
create a Python page with the following content:
demo_mysql_test.py:
import mysql.connector
If the above code was executed with no errors, "MySQL Connector" is installed and ready to be
used.
Create Connection
demo_mysql_connection.py:
import mysql.connector
mydb = mysql.connector.connect(
host="localhost",
user="yourusername",
password="yourpassword"
)
print(mydb)
Now you can start querying the database using SQL statements.
Creating a Database
Example
import mysql.connector
mydb = mysql.connector.connect(
host="localhost",
user="yourusername",
password="yourpassword"
)
mycursor = mydb.cursor()
If the above code was executed with no errors, you have successfully created a database.
You can check if a database exist by listing all databases in your system by using the "SHOW
DATABASES" statement:
Example
import mysql.connector
mydb = mysql.connector.connect(
host="localhost",
user="yourusername",
password="yourpassword"
)
mycursor = mydb.cursor()
mycursor.execute("SHOW DATABASES")
for x in mycursor:
print(x)
Or you can try to access the database when making the connection:
Example
import mysql.connector
mydb = mysql.connector.connect(
host="localhost",
user="yourusername",
password="yourpassword",
database="mydatabase"
)
Make sure you define the name of the database when you create the connection
Example
import mysql.connector
mydb = mysql.connector.connect(
host="localhost",
user="yourusername",
password="yourpassword",
database="mydatabase"
)
mycursor = mydb.cursor()
If the above code was executed with no errors, you have now successfully created a table.
You can check if a table exist by listing all tables in your database with the "SHOW TABLES"
statement:
Example
import mysql.connector
mydb = mysql.connector.connect(
host="localhost",
user="yourusername",
password="yourpassword",
database="mydatabase"
)
mycursor = mydb.cursor()
mycursor.execute("SHOW TABLES")
for x in mycursor:
print(x)
Primary Key
When creating a table, you should also create a column with a unique key for each record.
We use the statement "INT AUTO_INCREMENT PRIMARY KEY" which will insert a unique number
for each record. Starting at 1, and increased by one for each record.
Example
import mysql.connector
mydb = mysql.connector.connect(
host="localhost",
user="yourusername",
password="yourpassword",
database="mydatabase"
)
mycursor = mydb.cursor()
Example
import mysql.connector
mydb = mysql.connector.connect(
host="localhost",
user="yourusername",
password="yourpassword",
database="mydatabase"
)
mycursor = mydb.cursor()
Example
import mysql.connector
mydb = mysql.connector.connect(
host="localhost",
user="yourusername",
password="yourpassword",
database="mydatabase"
)
mycursor = mydb.cursor()
mydb.commit()
The second parameter of the executemany() method is a list of tuples, containing the data you
want to insert:
Example
import mysql.connector
mydb = mysql.connector.connect(
host="localhost",
user="yourusername",
password="yourpassword",
database="mydatabase"
)
mycursor = mydb.cursor()
mycursor.executemany(sql, val)
mydb.commit()
Get Inserted ID
You can get the id of the row you just inserted by asking the cursor object.
Note: If you insert more than one row, the id of the last inserted row is returned.
Example
import mysql.connector
mydb = mysql.connector.connect(
host="localhost",
user="yourusername",
password="yourpassword",
database="mydatabase"
)
mycursor = mydb.cursor()
mydb.commit()
Example
Select all records from the "customers" table, and display the result:
import mysql.connector
mydb = mysql.connector.connect(
host="localhost",
user="yourusername",
password="yourpassword",
database="mydatabase"
)
mycursor = mydb.cursor()
myresult = mycursor.fetchall()
for x in myresult:
print(x)
Note: We use the fetchall() method, which fetches all rows from the last executed statement.
Selecting Columns
To select only some of the columns in a table, use the "SELECT" statement followed by the
column name(s):
Example
import mysql.connector
mydb = mysql.connector.connect(
host="localhost",
user="yourusername",
password="yourpassword",
database="mydatabase"
)
mycursor = mydb.cursor()
myresult = mycursor.fetchall()
for x in myresult:
print(x)
If you are only interested in one row, you can use the fetchone() method.
The fetchone() method will return the first row of the result:
Example
import mysql.connector
mydb = mysql.connector.connect(
host="localhost",
user="yourusername",
password="yourpassword",
database="mydatabase"
)
mycursor = mydb.cursor()
myresult = mycursor.fetchone()
print(myresult)
When selecting records from a table, you can filter the selection by using the "WHERE"
statement:
Example
import mysql.connector
mydb = mysql.connector.connect(
host="localhost",
user="yourusername",
password="yourpassword",
database="mydatabase"
)
mycursor = mydb.cursor()
mycursor.execute(sql)
myresult = mycursor.fetchall()
for x in myresult:
print(x)
Wildcard Characters
You can also select the records that starts, includes, or ends with a given letter or phrase.
Example
import mysql.connector
mydb = mysql.connector.connect(
host="localhost",
user="yourusername",
password="yourpassword",
database="mydatabase"
)
mycursor = mydb.cursor()
mycursor.execute(sql)
myresult = mycursor.fetchall()
for x in myresult:
print(x)
When query values are provided by the user, you should escape the values.
This is to prevent SQL injections, which is a common web hacking technique to destroy or
misuse your database.
Example
import mysql.connector
mydb = mysql.connector.connect(
host="localhost",
user="yourusername",
password="yourpassword",
database="mydatabase"
)
mycursor = mydb.cursor()
mycursor.execute(sql, adr)
myresult = mycursor.fetchall()
for x in myresult:
print(x)
Use the ORDER BY statement to sort the result in ascending or descending order.
The ORDER BY keyword sorts the result ascending by default. To sort the result in descending
order, use the DESC keyword.
Example
import mysql.connector
mydb = mysql.connector.connect(
host="localhost",
user="yourusername",
password="yourpassword",
database="mydatabase"
)
mycursor = mydb.cursor()
mycursor.execute(sql)
myresult = mycursor.fetchall()
for x in myresult:
print(x)
ORDER BY DESC
Example
import mysql.connector
mydb = mysql.connector.connect(
host="localhost",
user="yourusername",
password="yourpassword",
database="mydatabase"
)
mycursor = mydb.cursor()
mycursor.execute(sql)
myresult = mycursor.fetchall()
for x in myresult:
print(x)
You can delete records from an existing table by using the "DELETE FROM" statement:
Example
import mysql.connector
mydb = mysql.connector.connect(
host="localhost",
user="yourusername",
password="yourpassword",
database="mydatabase"
)
mycursor = mydb.cursor()
mycursor.execute(sql)
mydb.commit()
Notice the WHERE clause in the DELETE syntax: The WHERE clause specifies which
record(s) that should be deleted. If you omit the WHERE clause, all records will be deleted!
You can delete an existing table by using the "DROP TABLE" statement:
Example
import mysql.connector
mydb = mysql.connector.connect(
host="localhost",
user="yourusername",
password="yourpassword",
database="mydatabase"
)
mycursor = mydb.cursor()
mycursor.execute(sql)
If the the table you want to delete is already deleted, or for any other reason does not exist, you
can use the IF EXISTS keyword to avoid getting an error.
Example
import mysql.connector
mydb = mysql.connector.connect(
host="localhost",
user="yourusername",
password="yourpassword",
database="mydatabase")
mycursor = mydb.cursor()
mycursor.execute(sql)
271-CIS-3 Lab Manual - 2022 127
Department of Information Systems ﻗﺴﻢ ﻧﻈﻢ اﻟﻤﻌﻠﻮﻣﺎت
You can update existing records in a table by using the "UPDATE" statement:
Example
import mysql.connector
mydb = mysql.connector.connect(
host="localhost",
user="yourusername",
password="yourpassword",
database="mydatabase"
)
mycursor = mydb.cursor()
sql = "UPDATE customers SET address = 'Canyon 123' WHERE address = 'Valley 345'"
mycursor.execute(sql)
mydb.commit()
Notice the WHERE clause in the UPDATE syntax: The WHERE clause specifies which record
or records that should be updated. If you omit the WHERE clause, all records will be updated!
It is considered a good practice to escape the values of any query, also in update statements.
This is to prevent SQL injections, which is a common web hacking technique to destroy or
misuse your database.
The mysql.connector module uses the placeholder %s to escape values in the delete statement:
Example
import mysql.connector
mydb = mysql.connector.connect(
host="localhost",
user="yourusername",
password="yourpassword",
database="mydatabase"
)
mycursor = mydb.cursor()
mycursor.execute(sql, val)
mydb.commit()
You can combine rows from two or more tables, based on a related column between them, by
using a JOIN statement.
users
products
These two tables can be combined by using users' fav field and products' id field.
Example
Join users and products to see the name of the users favorite product:
import mysql.connector
mydb = mysql.connector.connect(
host="localhost",
user="yourusername",
password="yourpassword",
database="mydatabase"
)
mycursor = mydb.cursor()
sql = "SELECT \
users.name AS user, \
products.name AS favorite \
FROM users \
INNER JOIN products ON users.fav = products.id"
mycursor.execute(sql)
myresult = mycursor.fetchall()
for x in myresult:
print(x)
Note: You can use JOIN instead of INNER JOIN. They will both give you the same result.
LEFT JOIN
In the example above, Hannah, and Michael were excluded from the result, that is because
INNER JOIN only shows the records where there is a match.
If you want to show all users, even if they do not have a favorite product, use the LEFT JOIN
statement:
Example
sql = "SELECT \
users.name AS user, \
products.name AS favorite \
FROM users \
LEFT JOIN products ON users.fav = products.id"
RIGHT JOIN
If you want to return all products, and the users who have them as their favorite, even if no user
have them as their favorite, use the RIGHT JOIN statement:
Example
Select all products, and the user(s) who have them as their favorite:
sql = "SELECT \
users.name AS user, \
products.name AS favorite \
FROM users \
RIGHT JOIN products ON users.fav = products.id"
Note: Hannah and Michael, who have no favorite product, are not included in the result.
What is Django?
Django is a high-level Python web framework that enables rapid development of secure and maintainable
websites. Built by experienced developers, Django takes care of much of the hassle of web development, so you
can focus on writing your app without needing to reinvent the wheel. It is free and open source, has a thriving
and active community, great documentation, and many options for free and paid-for support.
Django Features:
Complete
Django follows the "Batteries included" philosophy and provides almost everything developers might want to
do "out of the box". Because everything you need is part of the one "product", it all works seamlessly together,
follows consistent design principles, and has extensive and up-to-date documentation.
Versatile
Django can be (and has been) used to build almost any type of website — from content management systems
and wikis, through to social networks and news sites. It can work with any client-side framework, and can
deliver content in almost any format (including HTML, RSS feeds, JSON, XML, etc). The tutorial you are
currently performing is built with Django.
Internally, while it provides choices for almost any functionality you might want (e.g. several popular
databases, templating engines, etc.), it can also be extended to use other components if needed.
Secure
Django helps developers avoid many common security mistakes by providing a framework that has been
engineered to "do the right things" to protect the website automatically. For example, Django provides a secure
way to manage user accounts and passwords, avoiding common mistakes like putting session information in
cookies where it is vulnerable (instead cookies just contain a key, and the actual data is stored in the database)
or directly storing passwords rather than a password hash.
Django Architecture
URLs: While it is possible to process requests from every single URL via a single function, it is much more
maintainable to write a separate view function to handle each resource. A URL mapper is used to
redirect HTTP requests to the appropriate view based on the request URL. The URL mapper can also
match particular patterns of strings or digits that appear in a URL and pass these to a view function as
data.
View: A view is a request handler function, which receives HTTP requests and returns HTTP responses.
Views access the data needed to satisfy requests via models, and delegate the formatting of the response
to templates.
Models: Models are Python objects that define the structure of an application's data, and
provide mechanisms to manage (add, modify, delete) and query records in the database.
Templates: A template is a text file defining the structure or layout of a file (such as an HTML page), with
placeholders used to represent actual content. A view can dynamically create an HTML page using an
HTML template, populating it with data from a model. A template can be used to define the structure of
any type of file; it doesn't have to be HTML!
A URL mapper is typically stored in a file named urls.py. In the example below, the mapper (urlpatterns)
defines a list of mappings between routes (specific URL patterns) and corresponding view functions. If an
HTTP Request is received that has a URL matching a specified pattern, then the associated view function will
be called and passed the request.
The first argument to both methods is a route (pattern) that will be matched. The path() method uses angle
brackets to define parts of a URL that will be captured and passed through to the view function as named
arguments. The re_path() function uses a flexible pattern matching approach known as a regular expression.
We'll talk about these in a later article!
The second argument is another function that will be called when the pattern is matched. The
notation views.book_detail indicates that the function is called book_detail() and can be found in a module
called views (i.e. inside a file named views.py)
Views are the heart of the web application, receiving HTTP requests from web clients and returning HTTP
responses. In between, they marshal the other resources of the framework to access databases, render templates,
etc.
The example below shows a minimal view function index(), which could have been called by our URL mapper
in the previous section. Like all view functions it receives an HttpRequest object as a parameter (request) and
returns an HttpResponse object. In this case we don't do anything with the request, and our response returns a
hard-coded string. We'll show you a request that does something more interesting in a later section.
Django web applications manage and query data through Python objects referred to as models. Models define
the structure of stored data, including the field types and possibly also their maximum size, default values,
selection list options, help text for documentation, label text for forms, etc. The definition of the model is
independent of the underlying database — you can choose one of several as part of your project settings. Once
you've chosen what database you want to use, you don't need to talk to it directly at all — you just write your
model structure and other code, and Django handles all the "dirty work" of communicating with the database for
you.
The code snippet below shows a very simple Django model for a Team object. The Team class is derived from the
django class models.Model. It defines the team name and team level as character fields and specifies a maximum
number of characters to be stored for each record. The team_level can be one of several values, so we define it
as a choice field and provide a mapping between choices to be displayed and data to be stored, along with a
default value.
The Django model provides a simple query API for searching the associated database. This can match against a
number of fields at a time using different criteria (e.g. exact, case-insensitive, greater than, etc.), and can
support complex statements (for example, you can specify a search on U11 teams that have a team name that
starts with "Fr" or ends with "al").
The code snippet shows a view function (resource handler) for displaying all of our U09 teams. The list_teams
= Team.objects.filter(team_level__exact="U09") line shows how we can use the model query API to filter
for all records where the team_level field has exactly the text 'U09' (note how this criteria is passed to
the filter() function as an argument, with the field name and match type separated by a double
underscore: team_level__exact).
This function uses the render() function to create the HttpResponse that is sent back to the browser. This
function is a shortcut; it creates an HTML file by combining a specified HTML template and some data to
insert in the template (provided in the variable named "context"). In the next section we show how the template
has the data inserted in it to create the HTML.
Template systems allow you to specify the structure of an output document, using placeholders for data that will
be filled in when a page is generated. Templates are often used to create HTML, but can also create other types
of document. Django supports both its native templating system and another popular Python library called
Jinja2 out of the box (it can also be made to support other systems if needed).
The code snippet shows what the HTML template called by the render() function in the previous section might
look like. This template has been written under the assumption that it will have access to a list variable
called youngest_teams when it is rendered (this is contained in the context variable inside the render() function
above). Inside the HTML skeleton we have an expression that first checks if the youngest_teams variable exists,
and then iterates it in a for loop. On each iteration the template displays each team's team_name value in
an <li> element.
macOS
macOS recent versions do not include Python 3. You can confirm this by running the following commands in
the zsh or bash terminal:
You can easily install Python 3 (along with the pip3 tool) from python.org:
You can similarly check that pip3 is installed by listing the available packages:
Windows 10
Windows doesn't include Python by default, but you can easily install it (along with the pip3 tool) from
python.org:
1. Go to https://www.python.org/downloads/
2. Select the Download Python 3.8.6 button (the exact version number may differ).
2. Install Python by double-clicking on the downloaded file and following the installation prompts
You can then verify that Python 3 was installed by entering the following text into the command prompt:
The Windows installer incorporates pip3 (the Python package manager) by default. You can list installed
packages as shown:
The libraries we'll use for creating our virtual environments are virtualenvwrapper (Linux and macOS)
and virtualenvwrapper-win (Windows), which in turn both use the virtualenv tool. The wrapper tools creates a
consistent interface for managing interfaces on all platforms.
Setting up virtualenvwrapper on macOS is almost exactly the same as on Ubuntu (again, you can follow the
instructions from either the official installation guide or below).
Then add the following lines to the end of your shell startup file
If you're using the zsh shell then the startup file will be a hidden file named .zshrc in your home directory. If
you're using the bash shell then it will be a hidden file named .bash_profile. You may need to create the file if
it does not yet exist.
Make sure you check python version and directory by using the following command
3) Write the following code to open .zshrc file sudo code ~/.zshrc , then enter the user password if
required
export WORKON_HOME=$HOME/.virtualenvs
export VIRTUALENVWRAPPER_PYTHON=/Library/Frameworks/Python.framework/Versions/3.9/bin/python3
export PROJECT_HOME=$HOME/Devel
source /Library/Frameworks/Python.framework/Versions/3.9/bin/virtualenvwrapper.sh
5) Then, run the following command source ~/.zshrc , if things done properly then you should have
the following output
virtualenvwrapper.user_scripts creating
/home/ubuntu/.virtualenvs/premkproject
virtualenvwrapper.user_scripts creating
/home/ubuntu/.virtualenvs/postmkproject
...
virtualenvwrapper.user_scripts creating
/home/ubuntu/.virtualenvs/preactivate
virtualenvwrapper.user_scripts creating
/home/ubuntu/.virtualenvs/postactivate
virtualenvwrapper.user_scripts creating
/home/ubuntu/.virtualenvs/get_env_details
Now you can create a new virtual environment with the mkvirtualenv command
Once you've installed virtualenvwrapper or virtualenvwrapper-win then working with virtual environments is
very similar on all platforms.
Now you can create a new virtual environment with the mkvirtualenv command. As this command runs you'll
see the environment being set up (what you see is slightly platform-specific). When the command completes the
new virtual environment will be active — you can see this because the start of the prompt will be the name of
the environment in brackets (below we show this for Ubuntu, but the final line is similar for Windows/macOS).
$ mkvirtualenv my_django_environment
Windows output
MacOS output
Now you're inside the virtual environment you can install Django and start developing.
Installing Django
Once you've created a virtual environment, and called workon to enter it, you can use pip3 to install Django.
You can test that Django is installed by running the following command (this just tests that Python can find the
Django module):
# Linux/macOS
3.1.2
# Windows
py -3 -m django --version
3.1.2
Note: If the above Windows command does not show a django module present, try:
py -m django --version
In Windows Python 3 scripts are launched by prefixing the command with py -3, although this can vary
depending on your specific installation. Try omitting the -3 modifier if you encounter any problems with
commands. In Linux/macOS, the command is python3.
There are just a few other useful commands that you should know (there are more in the tool documentation,
but these are the ones you'll use regularly):
The above test works, but it isn't very much fun. A more interesting test is to create a skeleton project and see it
working. To do this, first navigate in your command prompt/terminal to where you want to store your Django
apps. Create a folder for your test site and navigate into it.
mkdir django_test
cd django_test
You can then create a new skeleton site called "mytestsite" using the django-admin tool as shown. After
creating the site you can navigate into the folder where you will find the main script for managing projects,
called manage.py.
cd mytestsite
We can run the development web server from within this folder using manage.py and the runserver command,
as shown.
You have 18 unapplied migration(s). Your project may not work properly until you apply the
migrations for app(s): admin, auth, contenttypes, sessions.
Once the server is running you can view the site by navigating to the following URL on your local web
browser: http://127.0.0.1:8000/. You should see a site that looks like this:
Overview
This article shows how you can create a "skeleton" website, which you can then populate with site-specific settings, paths,
models, views, and templates (we discuss these in later articles).
1. Open a command shell (or a terminal window), and make sure you are in your virtual environment.
2. Navigate to where you want to store your Django apps (make it somewhere easy to find like inside
your Documents folder), and create a folder for your new website (in this case: django_projects). Then change
into your newly-created directory:
mkdir django_projects
cd django_projects
3. Create the new project using the django-admin startproject command as shown, and then change into the
project folder:
django-admin startproject locallibrary
cd locallibrary
code locallibrary
The locallibrary project sub-folder is the entry point for the website:
• __init__.py is an empty file that instructs Python to treat this directory as a Python package.
• settings.py contains all the website settings, including registering any applications we create, the location of our
static files, database configuration details, etc.
• urls.py defines the site URL-to-view mappings. While this could contain all the URL mapping code, it is more
common to delegate some of the mappings to particular applications, as you'll see later.
• wsgi.py is used to help your Django application communicate with the webserver. You can treat this as
boilerplate.
• asgi.py is a standard for Python asynchronous web apps and servers to communicate with each other. ASGI is
the asynchronous successor to WSGI and provides a standard for both asynchronous and synchronous Python
apps (whereas WSGI provided a standard for synchronous apps only). It is backward-compatible with WSGI and
supports multiple servers and application frameworks.
The manage.py script is used to create applications, work with databases, and start the development web server.
Next, run the following command to create the catalog application that will live inside our locallibrary project. Make sure
to run this command from the same folder as your project's manage.py:
Note: The example command is for Linux/macOS X. On Windows, the command should be:
If you are using Python 3.7.0 or later, you should only use py manage.py startapp catalog
The tool creates a new folder and populates it with files for the different parts of the application (shown in the following
example). Most of the files are named after their purpose (e.g. views should be stored in views.py, models
in models.py, tests in tests.py, administration site configuration in admin.py, application registration in apps.py)
and contain some minimal boilerplate code for working with the associated objects.
Now that the application has been created, we have to register it with the project so that it will be included when any tools
are run (like adding models to the database for example). Applications are registered by adding them to
the INSTALLED_APPS list in the project settings.
Open the project settings file, django_projects/locallibrary/locallibrary/settings.py, and find the definition for
the INSTALLED_APPS list. Then add a new line at the end of the list, as shown below:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# Add our new application
'catalog.apps.CatalogConfig', #This object was created for us in /catalog/apps.py
]
The new line specifies the application configuration object (CatalogConfig) that was generated for you
in /locallibrary/catalog/apps.py when you created the application.
Note: You'll notice that there are already a lot of other INSTALLED_APPS (and MIDDLEWARE, further down in the
settings file). These enable support for the Django administration site and the functionality it uses (including sessions,
authentication, etc).
There are two other settings you won't change now, but that you should be aware of:
• SECRET_KEY. This is a secret key that is used as part of Django's website security strategy. If you're not protecting
this code in development, you'll need to use a different code (perhaps read from an environment variable or file)
when putting it into production.
• DEBUG. This enables debugging logs to be displayed on error, rather than HTTP status code responses. This should
be set to False in production as debug information is useful for attackers, but for now we can keep it set to True.
This is also the point where you would normally specify the database to be used for the project. It makes sense to use the
same database for development and production where possible, in order to avoid minor differences in behavior. You can
find out about the different options in Databases (Django docs).
We'll use the SQLite database for this example, because we don't expect to require a lot of concurrent access on a
demonstration database, and it requires no additional work to set up! You can see how this database is configured
in settings.py:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
The website is created with a URL mapper file (urls.py) in the project folder. While you can use this file to manage all
your URL mappings, it is more usual to defer mappings to the associated application.
Open locallibrary/locallibrary/urls.py and note the instructional text which explains some of the ways to use the
URL mapper.
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/3.1/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
urlpatterns = [
path('admin/', admin.site.urls),
]
To add a new list item to the urlpatterns list, add the following lines to the bottom of the file. This new item includes
a path() that forwards requests with the pattern catalog/ to the module catalog.urls (the file with the relative
URL catalog/urls.py).
urlpatterns += [
path('catalog/', include('catalog.urls')),
]
Note: Note that we included the import line (from django.urls import include) with the code that uses it (so it is
easy to see what we've added), but it is common to include all your import lines at the top of a Python file.
Now let's redirect the root URL of our site (i.e. 127.0.0.1:8000) to the URL 127.0.0.1:8000/catalog/. This is the
only app we'll be using in this project. To do this, we'll use a special view function, RedirectView, which takes the new
relative URL to redirect to (/catalog/) as its first argument when the URL pattern specified in the path() function is
matched (the root URL, in this case).
Django does not serve static files like CSS, JavaScript, and images by default, but it can be useful for the development
web server to do so while you're creating your site. As a final addition to this URL mapper, you can enable the serving of
static files during development by appending the following lines.
Add the following final block to the bottom of the file now:
# Use static() to add url mapping to serve static files during development (only)
from django.conf import settings
from django.conf.urls.static import static
As a final step, create a file inside your catalog folder called urls.py, and add the following text to define the (empty)
imported urlpatterns. This is where we'll add our patterns as we build the application.
urlpatterns = [
Django uses an Object-Relational-Mapper (ORM) to map model definitions in the Django code to the data structure used
by the underlying database. As we change our model definitions, Django tracks the changes and can create database
migration scripts (in /locallibrary/catalog/migrations/) to automatically migrate the underlying data structure in the
database to match the model.
When we created the website, Django automatically added a number of models for use by the admin section of the site
(which we'll look at later). Run the following commands to define tables for those models in the database (make sure you
are in the directory that contains manage.py):
Warning: You'll need to run these commands every time your models change in a way that will affect the structure of the
data that needs to be stored (including both addition and removal of whole models and individual fields).
• The makemigrations command creates (but does not apply) the migrations for all applications installed in your
project. You can specify the application name as well to just run a migration for a single project. This gives you a
chance to check out the code for these migrations before they are applied. If you're a Django expert, you may
choose to tweak them slightly!
• The migrate command is what applies the migrations to your database. Django tracks which ones have been
added to the current database.
During development, you can serve the website first using the development web server, and then viewing it on your
local web browser.
• Run the development web server by calling the runserver command (in the same directory as manage.py):
python3 manage.py runserver
Once the server is running, you can view the site by navigating to http://127.0.0.1:8000/ in your local web browser.
You should see a site error page that looks like this:
Don't worry! This error page is expected because we don't have any pages/urls defined in the catalog.urls module
(which we're redirected to when we get a URL to the root of the site).
Model definition
Models are usually defined in an application's models.py file. They are implemented as subclasses
of django.db.models.Model, and can include fields, methods and metadata. The code fragment below shows a "typical"
model, named MyModelName:
class MyModelName(models.Model):
"""A typical class defining a model, derived from the Model class."""
# Fields
my_field_name = models.CharField(max_length=20, help_text='Enter field documentation')
...
# Metadata
class Meta:
ordering = ['-my_field_name']
# Methods
def get_absolute_url(self):
"""Returns the url to access a particular instance of MyModelName."""
return reverse('model-detail-view', args=[str(self.id)])
def __str__(self):
"""String for representing the MyModelName object (in Admin site etc.)."""
return self.my_field_name
271-CIS-3 Lab Manual - 2022 158
Department of Information Systems ﻗﺴﻢ ﻧﻈﻢ اﻟﻤﻌﻠﻮﻣﺎت
In the below sections we'll explore each of the features inside the model in detail:
Fields
A model can have an arbitrary number of fields, of any type — each one represents a column of data that we want to store
in one of our database tables. Each database record (row) will consist of one of each field value. Let's look at the example
seen below:
help_text Provides a text label for HTML forms (e.g. in the admin site), as described above.
A human-readable name for the field used in field labels. If not specified, Django
verbose_name
will infer the default verbose name from the field name.
The default value for the field. This can be a value or a callable object, in which
default
case the object will be called every time a new record is created.
If True, Django will store blank values as NULL in the database for fields where
null this is appropriate (a CharField will instead store an empty string). The default
is False.
If True, the field is allowed to be blank in your forms. The default is False,
which means that Django's form validation will force you to enter a value. This is
blank
often used with null=True , because if you're going to allow blank values, you
also want the database to be able to represent them appropriately.
A group of choices for this field. If this is provided, the default corresponding
choices form widget will be a select box with these choices instead of the standard text
field.
If True, sets the current field as the primary key for the model (A primary key is a
special database column designated to uniquely identify all the different table
primary_key
records). If no field is specified as the primary key then Django will automatically
add a field for this purpose.
The following list describes some of the more commonly used types of fields.
Methods
Minimally, in every model you should define the standard Python class method __str__() to return a
human-readable string for each object. This string is used to represent individual records in the administration site
(and anywhere else you need to refer to a model instance). Often this will return a title or name field from the model.
def __str__(self):
return self.field_name
Another common method to include in Django models is get_absolute_url(), which returns a URL for displaying
individual model records on the website (if you define this method then Django will automatically add a "View on Site"
button to the model's record editing screens in the Admin site). A typical pattern for get_absolute_url() is shown
below.
def get_absolute_url(self):
"""Returns the url to access a particular instance of the model."""
return reverse('model-detail-view', args=[str(self.id)])
Model management
Once you've defined your model classes you can use them to create, update, or delete records, and to run queries to get all
records or particular subsets of records. We'll show you how to do that in the tutorial when we define our views, but here
is a brief summary.
To create a record you can define an instance of the model and then call save().
You can search for records that match certain criteria using the model's objects attribute (provided by the base class).
We can get all records for a model as a QuerySet, using objects.all(). The QuerySet is an iterable object, meaning
that it contains a number of objects that we can iterate/loop through.
all_books = Book.objects.all()
Django's filter() method allows us to filter the returned QuerySet to match a specified text or numeric field against
particular criteria. For example, to filter for books that contain "wild" in the title and then count them, we could do the
following.
wild_books = Book.objects.filter(title__contains='wild')
number_wild_books = wild_books.count()
The fields to match and the type of match are defined in the filter parameter name, using the
format: field_name__match_type (note the double underscore between title and contains above). Above we're
filtering title with a case-sensitive match. There are many other types of matches you can do: icontains (case
insensitive), iexact (case-insensitive exact match), exact (case-sensitive exact match) and in, gt (greater
than), startswith, etc. The full list is here.
In some cases you'll need to filter on a field that defines a one-to-many relationship to another model (e.g. a ForeignKey).
In this case you can "index" to fields within the related model with additional double underscores. So for example to filter
for books with a specific genre pattern, you will have to index to the name through the genre field, as shown below:
In this section we will start defining the models for the library. Open models.py (in /locallibrary/catalog/). The
boilerplate at the top of the page imports the models module, which contains the model base class models.Model that our
models will inherit from.
Genre model
Copy the Genre model code shown below and paste it into the bottom of your models.py file. This model is used to store
information about the book category — for example whether it is fiction or non-fiction, romance or military history, etc.
As mentioned above, we've created the Genre as a model rather than as free text or a selection list so that the possible
values can be managed through the database rather than being hard coded.
class Genre(models.Model):
"""Model representing a book genre."""
name = models.CharField(max_length=200, help_text='Enter a book genre (e.g. Science Fiction)')
def __str__(self):
"""String for representing the Model object."""
return self.name
The model has a single CharField field (name), which is used to describe the genre (this is limited to 200 characters and
has some help_text. At the end of the model we declare a __str__() method, which returns the name of the genre
defined by a particular record. No verbose name has been defined, so the field will be called Name in forms.
Book model
Copy the Book model below and again paste it into the bottom of your file. The Book model represents all information
about an available book in a general sense, but not a particular physical "instance" or "copy" available for loan. The model
uses a CharField to represent the book's title and isbn . For isbn, note how the first unnamed parameter explicitly sets the
label as "ISBN" (otherwise it would default to "Isbn"). We also set parameter unique as true in order to ensure all books
have a unique ISBN (the unique parameter makes the field value globally unique in a table). The model uses TextField for
the summary, because this text may need to be quite long.
from django.urls import reverse # Used to generate URLs by reversing the URL patterns
class Book(models.Model):
"""Model representing a book (but not a specific copy of a book)."""
title = models.CharField(max_length=200)
# Foreign Key used because book can only have one author, but authors can have multiple books
# Author as a string rather than object because it hasn't been declared yet in the file
author = models.ForeignKey('Author', on_delete=models.SET_NULL, null=True)
# ManyToManyField used because genre can contain many books. Books can cover many genres.
# Genre class has already been defined so we can specify the object above.
genre = models.ManyToManyField(Genre, help_text='Select a genre for this book')
def __str__(self):
"""String for representing the Model object."""
return self.title
def get_absolute_url(self):
"""Returns the url to access a detail record for this book."""
return reverse('book-detail', args=[str(self.id)])
The genre is a ManyToManyField, so that a book can have multiple genres and a genre can have many books. The author
is declared as ForeignKey, so each book will only have one author, but an author may have many books (in practice a
book might have multiple authors, but not in this implementation!)
In both field types the related model class is declared as the first unnamed parameter using either the model class or a
string containing the name of the related model. You must use the name of the model as a string if the associated class has
not yet been defined in this file before it is referenced! The other parameters of interest in the author field are null=True,
which allows the database to store a Null value if no author is selected, and on_delete=models.SET_NULL, which will set
the value of the book's author field to Null if the associated author record is deleted.
The model also defines __str__() , using the book's title field to represent a Book record. The final
method, get_absolute_url() returns a URL that can be used to access a detail record for this model (for this to work we
will have to define a URL mapping that has the name book-detail, and define an associated view and template).
BookInstance model
Next, copy the BookInstance model (shown below) under the other models. The BookInstance represents a
specific copy of a book that someone might borrow, and includes information about whether the copy is
available or on what date it is expected back, "imprint" or version details, and a unique id for the book in the
library.
Some of the fields and methods will now be familiar. The model uses:
• ForeignKey to identify the associated Book (each book can have many copies, but a copy can only have
one Book). The key specifies on_delete=models.RESTRICT to ensure that the Book cannot be deleted while
referenced by a BookInstance.
• CharField to represent the imprint (specific release) of the book.
import uuid # Required for unique book instances
class BookInstance(models.Model):
"""Model representing a specific copy of a book (i.e. that can be borrowed from the
library)."""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, help_text='Unique ID for this
particular book across whole library')
book = models.ForeignKey('Book', on_delete=models.RESTRICT, null=True)
imprint = models.CharField(max_length=200)
due_back = models.DateField(null=True, blank=True)
LOAN_STATUS = (
('m', 'Maintenance'),
('o', 'On loan'),
('a', 'Available'),
('r', 'Reserved'),
)
status = models.CharField(
max_length=1,
choices=LOAN_STATUS,
blank=True,
default='m',
help_text='Book availability',
)
class Meta:
ordering = ['due_back']
def __str__(self):
"""String for representing the Model object."""
return f'{self.id} ({self.book.title})'
• UUIDField is used for the id field to set it as the primary_key for this model. This type of field allocates a
globally unique value for each instance (one for every book you can find in the library).
• DateField is used for the due_back date (at which the book is expected to become available after being borrowed
or in maintenance). This value can be blank or null (needed for when the book is available). The model metadata
(Class Meta) uses this field to order records when they are returned in a query.
• status is a CharField that defines a choice/selection list. As you can see, we define a tuple containing tuples of
key-value pairs and pass it to the choices argument. The value in a key/value pair is a display value that a user can
select, while the keys are the values that are actually saved if the option is selected. We've also set a default value
of 'm' (maintenance) as books will initially be created unavailable before they are stocked on the shelves.
The method __str__() represents the BookInstance object using a combination of its unique id and the
associated Book's title.
Author model
Copy the Author model (shown below) underneath the existing code in models.py.
class Author(models.Model):
"""Model representing an author."""
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
date_of_birth = models.DateField(null=True, blank=True)
date_of_death = models.DateField('Died', null=True, blank=True)
class Meta:
ordering = ['last_name', 'first_name']
def get_absolute_url(self):
"""Returns the url to access a particular author instance."""
return reverse('author-detail', args=[str(self.id)])
def __str__(self):
"""String for representing the Model object."""
return f'{self.last_name}, {self.first_name}'
All of the fields/methods should now be familiar. The model defines an author as having a first name, last name, and dates
of birth and death (both optional). It specifies that by default the __str__() returns the name in last
name, firstname order. The get_absolute_url() method reverses the author-detail URL mapping to get the URL
for displaying an individual author.
All your models have now been created. Now re-run your database migrations to add them to your database.
First, open admin.py in the catalog application (/locallibrary/catalog/admin.py). It currently looks like this — note
that it already imports django.contrib.admin:
admin.site.register(Book)
admin.site.register(Author)
admin.site.register(Genre)
admin.site.register(BookInstance)
This is the simplest way of registering a model, or models, with the site. The admin site is highly customisable, and we'll
talk more about the other ways of registering your models further down.
Creating a superuser
In order to log into the admin site, we need a user account with Staff status enabled. In order to view and create records
we also need this user to have permissions to manage all our objects. You can create a "superuser" account that has full
access to the site and all needed permissions using manage.py.
Call the following command, in the same directory as manage.py, to create the superuser. You will be prompted to enter
a username, email address, and strong password.
Once this command completes a new superuser will have been added to the database. Now restart the development server
so we can test the login:
To login to the site, open the /admin URL (e.g. http://127.0.0.1:8000/admin) and enter your new superuser userid and
password credentials (you'll be redirected to the login page, and then back to the /admin URL after you've entered your
details).
This part of the site displays all our models, grouped by installed application. You can click on a model name to go to a
screen that lists all its associated records, and you can further click on those records to edit them. You can also directly
click the Add link next to each model to start creating a record of that type.
Click on the Add link to the right of Books to create a new book (this will display a dialog much like the one below).
Note how the titles of each field, the type of widget used, and the help_text (if any) match the values you specified in the
model.
Enter values for the fields. You can create new authors or genres by pressing the + button next to the respective fields (or
select existing values from the lists if you've already created them). When you're done you can press SAVE, Save and
add another, or Save and continue editing to save the record.
Note: At this point we'd like you to spend some time adding a few books, authors, and genres (e.g. Fantasy) to your
application. Make sure that each author and genre includes a couple of different books (this will make your list and detail
views more interesting when we implement them later on in the article series).
When you've finished adding books, click on the Home link in the top bookmark to be taken back to the main admin
page. Then click on the Books link to display the current list of books (or on one of the other links to see other model
lists). Now that you've added a few books, the list might look similar to the screenshot below. The title of each book is
displayed; this is the value returned in the Book model's __str__() method that we specified in the last article.
From this list you can delete books by selecting the checkbox next to the book you don't want, selecting
the delete... action from the Action drop-down list, and then pressing the Go button. You can also add new books by
pressing the ADD BOOK button.
You can edit a book by selecting its name in the link. The edit page for a book, shown below, is almost identical to the
"Add" page. The main differences are the page title (Change book) and the addition of Delete, HISTORY and VIEW
ON SITE buttons (this last button appears because we defined the get_absolute_url() method in our model).
Now navigate back to the Home page (using the Home link in the breadcrumb trail) and then view
the Author and Genre lists — you should already have quite a few created from when you added the new books, but feel
free to add some more.
What you won't have is any Book Instances, because these are not created from Books (although you can create
a Book from a BookInstance — this is the nature of the ForeignKey field). Navigate back to the Home page and press
the associated Add button to display the Add book instance screen below. Note the large, globally unique Id, which can
be used to separately identify a single copy of a book in the library.
Create a number of these records for each of your books. Set the status as Available for at least some records and On
loan for others. If the status is not Available, then also set a future Due back date.
That's it! You've now learned how to set up and use the administration site. You've also created records
for Book, BookInstance, Genre, and Author that we'll be able to use once we create our own views and templates.
Advanced configuration
Django does a pretty good job of creating a basic admin site using the information from the registered models:
• Each model has a list of individual records, identified by the string created with
the model's __str__() method, and linked to detail views/forms for editing. By default, this view has an action
menu at the top that you can use to perform bulk delete operations on records.
• The model detail record forms for editing and adding records contain all the fields in the model, laid out vertically
in their declaration order.
You can further customise the interface to make it even easier to use. Some of the things you can do are:
• List views:
o Add additional fields/information displayed for each record.
o Add filters to select which records are listed, based on date or some other selection value (e.g. Book loan
status).
o Add additional options to the actions menu in list views and choose where this menu is displayed on the
form.
• Detail views
o Choose which fields to display (or exclude), along with their order, grouping, whether they are editable,
the widget used, orientation etc.
o Add related fields to a record to allow inline editing (e.g. add the ability to add and edit book records
while you're creating their author record).
In this section we're going to look at a few changes that will improve the interface for our LocalLibrary, including adding
more information to Book and Author model lists, and improving the layout of their edit views. We won't change
the Language and Genre model presentation because they only have one field each, so there is no real benefit in doing so!
You can find a complete reference of all the admin site customisation choices in The Django Admin site (Django Docs).
To change how a model is displayed in the admin interface you define a ModelAdmin class (which describes the layout)
and register it with the model.
Let's start with the Author model. Open admin.py in the catalog application (/locallibrary/catalog/admin.py).
Comment out your original registration (prefix it with a #) for the Author model:
# admin.site.register(Author)
# admin.site.register(Book)
# admin.site.register(BookInstance)
Now to create and register the new models; for the purpose of this demonstration, we'll instead use
the @register decorator to register the models (this does exactly the same thing as the admin.site.register() syntax):
pass
Currently all of our admin classes are empty (see pass) so the admin behavior will be unchanged! We can now extend
these to define our model-specific admin behavior.
The LocalLibrary currently lists all authors using the object name generated from the model __str__() method. This is
fine when you only have a few authors, but once you have many you may end up having duplicates. To differentiate them,
or just because you want to show more interesting information about each author, you can use list_display to add
additional fields to the view.
Replace your AuthorAdmin class with the code below. The field names to be displayed in the list are declared in a tuple in
the required order, as shown (these are the same names as specified in your original model).
class AuthorAdmin(admin.ModelAdmin):
Now navigate to the author list in your website. The fields above should now be displayed, like so:
For our Book model we'll additionally display the author and genre. The author is a ForeignKey field (one-to-many)
relationship, and so will be represented by the __str__() value for the associated record. Replace the BookAdmin class
with the version below.
class BookAdmin(admin.ModelAdmin):
Unfortunately we can't directly specify the genre field in list_display because it is a ManyToManyField (Django
prevents this because there would be a large database access "cost" in doing so). Instead we'll define
a display_genre function to get the information as a string (this is the function we've called above; we'll define it below).
Note: Getting the genre may not be a good idea here, because of the "cost" of the database operation. We're showing you
how because calling functions in your models can be very useful for other reasons — for example to add a Delete link
next to every item in the list.
Add the following code into your Book model (models.py). This creates a string from the first three values of
the genre field (if they exist) and creates a short_description that can be used in the admin site for this method.
def display_genre(self):
"""Create a string for the Genre. This is required to display genre in Admin."""
return ', '.join(genre.name for genre in self.genre.all()[:3])
display_genre.short_description = 'Genre'
After saving the model and updated admin, open your website and go to the Books list page; you should see a book list
like the one below:
The Genre model (and the Language model, if you defined one) both have a single field, so there is no point creating an
additional model for them to display additional fields.
Note: It is worth updating the BookInstance model list to show at least the status and the expected return date.
We've added that as a challenge at the end of this article!
Once you've got a lot of items in a list, it can be useful to be able to filter which items are displayed. This is done by
listing fields in the list_filter attribute. Replace your current BookInstanceAdmin class with the code fragment below.
class BookInstanceAdmin(admin.ModelAdmin):
The list view will now include a filter box to the right. Note how you can choose dates and status to filter the values:
By default, the detail views lay out all fields vertically, in their order of declaration in the model. You can change the
order of declaration, which fields are displayed (or excluded), whether sections are used to organize the information,
whether fields are displayed horizontally or vertically, and even what edit widgets are used in the admin forms.
Note: The LocalLibrary models are relatively simple so there isn't a huge need for us to change the layout; we'll make
some changes anyway however, just to show you how.
Update your AuthorAdmin class to add the fields line, as shown below:
271-CIS-3 Lab Manual - 2022 177
Department of Information Systems ﻗﺴﻢ ﻧﻈﻢ اﻟﻤﻌﻠﻮﻣﺎت
class AuthorAdmin(admin.ModelAdmin):
list_display = ('last_name', 'first_name', 'date_of_birth', 'date_of_death')
In your website go to the author detail view — it should now appear as shown below:
Note: You can also use the exclude attribute to declare a list of attributes to be excluded from the form (all other
attributes in the model will be displayed).
You can add "sections" to group related model information within the detail form, using the fieldsets attribute.
In the BookInstance model we have information related to what the book is (i.e. name, imprint, and id) and when it will
be available (status, due_back). We can add these to our BookInstanceAdmin class as shown below, using
the fieldsets property.
@admin.register(BookInstance)
class BookInstanceAdmin(admin.ModelAdmin):
list_filter = ('status', 'due_back')
fieldsets = (
(None, {
'fields': ('book', 'imprint', 'id')
}),
('Availability', {
'fields': ('status', 'due_back')
}),
)
Each section has its own title (or None, if you don't want a title) and an associated tuple of fields in a dictionary — the
format is complicated to describe, but fairly easy to understand if you look at the code fragment immediately above.
271-CIS-3 Lab Manual - 2022 178
Department of Information Systems ﻗﺴﻢ ﻧﻈﻢ اﻟﻤﻌﻠﻮﻣﺎت
Now navigate to a book instance view in your website; the form should appear as shown below:
Sometimes it can make sense to be able to add associated records at the same time. For example, it may make
sense to have both the book information and information about the specific copies you've got on the same detail
page.
You can do this by declaring inlines, of type TabularInline (horizontal layout) or StackedInline (vertical layout,
just like the default model layout). You can add the BookInstance information inline to our Book detail by
specifiying inlines in your BookAdmin:
class BooksInstanceInline(admin.TabularInline):
model = BookInstance
@admin.register(Book)
class BookAdmin(admin.ModelAdmin):
list_display = ('title', 'author', 'display_genre')
inlines = [BooksInstanceInline]
Now navigate to a view for a Book in your website — at the bottom you should now see the book instances relating to this
book (immediately below the book's genre fields):
In this case all we've done is declare our tabular inline class, which just adds all fields from the inlined model. You can
specify all sorts of additional information for the layout, including the fields to display, their order, whether they are read
only or not, etc.
As this version of LocalLibrary is essentially read-only for end users, we just need to provide a landing page for the site
(a home page), and pages that display list and detail views for books and authors.
The first page we'll create is the index page (catalog/). The index page will include some static HTML, along with
generated "counts" of different records in the database. To make this work we'll create a URL mapping, a view, and a
template.
Note: It's worth paying a little extra attention in this section. Most of the information also applies to the other pages we'll
create.
URL mapping
When we created the skeleton website, we updated the locallibrary/urls.py file to ensure that whenever a URL that
starts with catalog/ is received, the URLConf module catalog.urls will process the remaining substring.
The following code snippet from locallibrary/urls.py includes the catalog.urls module:
urlpatterns += [
path('catalog/', include('catalog.urls')),
]
Note: Whenever Django encounters the import function django.urls.include(), it splits the URL string at the
designated end character and sends the remaining substring to the included URLconf module for further processing.
We also created a placeholder file for the URLConf module, named /catalog/urls.py. Add the following lines to that
file:
urlpatterns = [
path('', views.index, name='index'),
]
The path() function defines the following:
• A URL pattern, which is an empty string: ''. We'll discuss URL patterns in detail when working on the other
views.
• A view function that will be called if the URL pattern is detected: views.index, which is the
function named index() in the views.py file.
The path() function also specifies a name parameter, which is a unique identifier for this particular URL mapping. You
can use the name to "reverse" the mapper, i.e. to dynamically create a URL that points to the resource that the mapper is
designed to handle. For example, we can use the name parameter to link to our home page from any other page by adding
the following link in a template:
View (function-based)
A view is a function that processes an HTTP request, fetches the required data from the database, renders the data in an
HTML page using an HTML template, and then returns the generated HTML in an HTTP response to display the page to
the user. The index view follows this model — it fetches information about the number of Book, BookInstance,
available BookInstance and Author records that we have in the database, and passes that information to a template for
display.
Open catalog/views.py and note that the file already imports the render() shortcut function to generate an HTML
file using a template and data:
def index(request):
"""View function for home page of site."""
context = {
'num_books': num_books,
'num_instances': num_instances,
'num_instances_available': num_instances_available,
'num_authors': num_authors,
}
# Render the HTML template index.html with the data in the context variable
return render(request, 'index.html', context=context)
The first line imports the model classes that we'll use to access data in all our views.
The first part of the view function fetches the number of records using the objects.all() attribute on the model classes.
It also gets a list of BookInstance objects that have a value of 'a' (Available) in the status field. You can find more
information about how to access model data in our previous tutorial Django Tutorial: Using models > Searching for
records.
At the end of the view function we call the render() function to create an HTML page and return the page as a response.
This shortcut function wraps a number of other functions to simplify a very common use case. The render() function
accepts the following parameters:
Template
A template is a text file that defines the structure or layout of a file (such as an HTML page), it uses placeholders to
represent actual content.
A Django application created using startapp (like the skeleton of this example) will look for templates in a subdirectory
named 'templates' of your applications. For example, in the index view that we just added, the render() function will
expect to find the file index.html in /locallibrary/catalog/templates/ and will raise an error if the file is not present.
You can check this by saving the previous changes and accessing 127.0.0.1:8000 in your browser - it will display a
fairly intuitive error message: "TemplateDoesNotExist at /catalog/", and other details.
Note: Based on your project's settings file, Django will look for templates in a number of places, searching in your
installed applications by default. You can find out more about how Django finds templates and what template formats it
supports in the Templates section of the Django documentation.
Extending templates
The index template will need standard HTML markup for the head and body, along with navigation sections to link to the
other pages of the site (which we haven't created yet), and to sections that display introductory text and book data.
Much of the HTML and navigation structure will be the same in every page of our site. Instead of duplicating boilerplate
code on every page, you can use the Django templating language to declare a base template, and then extend it
to replace just the bits that are different for each specific page.
The following code snippet is a sample base template from a base_generic.html file. We'll be creating the template for
LocalLibrary shortly. The sample below includes common HTML with sections for a title, a sidebar, and main contents
marked with the named block and endblock template tags. You can leave the blocks empty, or include default content to
use when rendering pages derived from the template.
Note: Template tags are functions that you can use in a template to loop through lists, perform conditional operations
based on the value of a variable, and so on. In addition to template tags, the template syntax allows you to reference
variables that are passed into the template from the view, and use template filters to format variables (for example, to
convert a string to lower case).
<!DOCTYPE html>
<html lang="en">
<head>
{% block title %}<title>Local Library</title>{% endblock %}
</head>
<body>
{% block sidebar %}<!-- insert default navigation text for every page -->{% endblock %}
{% block content %}<!-- default content text (typically empty) -->{% endblock %}
</body>
</html>
When defining a template for a particular view, we first specify the base template using the extends template tag — see
the code sample below. Then we declare what sections from the template we want to replace (if any),
using block/endblock sections as in the base template.
For example, the code snippet below shows how to use the extends template tag and override the content block. The
generated HTML will include the code and structure defined in the base template, including the default content
you defined in the title block, but the new content block in place of the default one.
{% extends "base_generic.html" %}
{% block content %}
<h1>Local Library Home</h1>
<p>Welcome to LocalLibrary, a website developed by <em>Mozilla Developer Network</em>!</p>
{% endblock %}
We will use the following code snippet as the base template for the LocalLibrary website. As you can see, it contains
some HTML code and defines blocks for title, sidebar, and content. We have a default title and a default sidebar with
links to lists of all books and authors, both enclosed in blocks to be easily changed in the future.
Note: We also introduce two additional template tags: url and load static. These tags will be explained in following
sections.
Create a new file **base_generic.html **in /locallibrary/catalog/templates/ and paste the following code to the file:
<!DOCTYPE html>
<html lang="en">
<head>
{% block title %}<title>Local Library</title>{% endblock %}
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-
TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
<!-- Add additional CSS in static file -->
{% load static %}
<link rel="stylesheet" href="{% static 'css/styles.css' %}">
</head>
<body>
<div class="container-fluid">
<div class="row">
<div class="col-sm-2">
{% block sidebar %}
<ul class="sidebar-nav">
<li><a href="{% url 'index' %}">Home</a></li>
<li><a href="">All books</a></li>
<li><a href="">All authors</a></li>
</ul>
{% endblock %}
</div>
<div class="col-sm-10 ">{% block content %}{% endblock %}</div>
</div>
</div>
</body>
</html>
The template includes CSS from Bootstrap to improve the layout and presentation of the HTML page. Using Bootstrap
(or another client-side web framework) is a quick way to create an attractive page that displays well on different screen
sizes.
The base template also references a local css file (styles.css) that provides additional styling. Create a styles.css file
in /locallibrary/catalog/static/css/ and paste the following code in the file:
.sidebar-nav {
margin-top: 20px;
padding: 0;
list-style: none;
Create a new HTML file **index.html **in /locallibrary/catalog/templates/ and paste the following code in the file.
This code extends our base template on the first line, and then replaces the default content block for the template.
{% extends "base_generic.html" %}
{% block content %}
<h1>Local Library Home</h1>
<p>Welcome to LocalLibrary, a website developed by <em>Mozilla Developer Network</em>!</p>
<h2>Dynamic content</h2>
<p>The library has the following record counts:</p>
<ul>
<li><strong>Books:</strong> {{ num_books }}</li>
<li><strong>Copies:</strong> {{ num_instances }}</li>
<li><strong>Copies available:</strong> {{ num_instances_available }}</li>
<li><strong>Authors:</strong> {{ num_authors }}</li>
</ul>
{% endblock %}
In the Dynamic content section we declare placeholders (template variables) for the information from the view that we
want to include. The variables are enclosed with double brace (handlebars).
Note: You can easily recognize template variables and template tags (functions) - variables are enclosed in double braces
({{ num_books }}), and tags are enclosed in single braces with percentage signs ({% extends "base_generic.html"
%}).
The important thing to note here is that variables are named with the keys that we pass into the context dictionary in
the render() function of our view (see sample below). Variables will be replaced with their associated values when the
template is rendered.
context = {
'num_books': num_books,
'num_instances': num_instances,
'num_instances_available': num_instances_available,
'num_authors': num_authors,
}
Your project is likely to use static resources, including JavaScript, CSS, and images. Because the location of these files
might not be known (or might change), Django allows you to specify the location in your templates relative to
the STATIC_URL global setting. The default skeleton website sets the value of STATIC_URL to '/static/', but you might
choose to host these on a content delivery network or elsewhere.
Within the template you first call the load template tag specifying "static" to add the template library, as shown in the
code sample below. You can then use the static template tag and specify the relative URL to the required file.
{% load static %}
You can add an image into the page in a similar way, for example:
{% load static %}
Note: The samples above specify where the files are located, but Django does not serve them by default. We
configured the development web server to serve files by modifying the global URL mapper
(/locallibrary/locallibrary/urls.py) when we created the website skeleton, but still need to enable file serving in
production. We'll look at this later.
Linking to URLs
This tag accepts the name of a path() function called in your urls.py and the values for any arguments that the
associated view will receive from that function, and returns a URL that you can use to link to the resource.
The location where Django searches for templates is specified in the TEMPLATES object in the settings.py file. The
default settings.py (as created for this tutorial) looks something like this:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
The setting of 'APP_DIRS': True, is the most important, as it tells Django to search for templates in a subdirectory of
each application in the project, named "templates" (this makes it easier to group templates with their associated
application for easy re-use).
We can also specify specific locations for Django to search for directories using 'DIRS': [] (but that isn't needed yet).
Note: You can find out more about how Django finds templates and what template formats it
supports in the Templates section of the Django documentation.
At this point we have created all required resources to display the index page. Run the server (python3 manage.py
runserver) and open http://127.0.0.1:8000/ in your browser. If everything is configured correctly, your site should look
like the following screenshot.
Note: The All books and All authors links will not work yet because the paths, views, and templates for those pages
are not defined. We just inserted placeholders for those links in the base_generic.html template.
The book list page will display a list of all the available book records in the page, accessed using the
URL: catalog/books/. The page will display a title and author for each record, with the title being a hyperlink to the
associated book detail page. The page will have the same structure and navigation as all other pages in the site, and we
can, therefore, extend the base template (base_generic.html) we created in the previous tutorial.
URL mapping
Open /catalog/urls.py and copy in the line setting the path for 'books/', as shown below. Just as for the index
page, this path() function defines a pattern to match against the URL ('books/'), a view function that will be
called if the URL matches (views.BookListView.as_view()), and a name for this particular mapping.
urlpatterns = [
As discussed in the previous tutorial the URL must already have matched /catalog, so the view will actually be called
for the URL: /catalog/books/.
The view function has a different format than before — that's because this view will actually be implemented as a class.
We will be inheriting from an existing generic view function that already does most of what we want this view function to
do, rather than writing our own from scratch.
For Django class-based views we access an appropriate view function by calling the class method as_view(). This does
all the work of creating an instance of the class, and making sure that the right handler methods are called for
incoming HTTP requests.
View (class-based)
We could quite easily write the book list view as a regular function (just like our previous index view), which would
query the database for all books, and then call render() to pass the list to a specified template. Instead, however, we're
going to use a class-based generic list view (ListView) — a class that inherits from an existing view. Because the generic
view already implements most of the functionality we need and follows Django best-practice, we will be able to create a
more robust list view with less code, less repetition, and ultimately less maintenance.
Open catalog/views.py, and copy the following code into the bottom of the file:
class BookListView(generic.ListView):
model = Book
That's it! The generic view will query the database to get all records for the specified model (Book) then render a template
located at /locallibrary/catalog/templates/catalog/book_list.html (which we will create below). Within the
template you can access the list of books with the template variable named object_list OR book_list (i.e. generically
"the_model_name_list").
Note: This awkward path for the template location isn't a misprint — the generic views look for templates
in /application_name/the_model_name_list.html (catalog/book_list.html in this case) inside the
application's /application_name/templates/ directory (/catalog/templates/).
You can add attributes to change the default behavior above. For example, you can specify another template file if you
need to have multiple views that use this same model, or you might want to use a different template variable name
if book_list is not intuitive for your particular template use-case. Possibly the most useful variation is to change/filter the
subset of results that are returned — so instead of listing all books you might list top 5 books that were read by other
users.
class BookListView(generic.ListView):
model = Book
context_object_name = 'my_book_list' # your own name for the list as a template variable
queryset = Book.objects.filter(title__icontains='war')[:5] # Get 5 books containing the title
war
template_name = 'books/my_arbitrary_template_name_list.html' # Specify your own template
name/location
While we don't need to do so here, you can also override some of the class methods.
For example, we can override the get_queryset() method to change the list of records returned. This is more flexible
than just setting the queryset attribute as we did in the preceding code fragment (though there is no real benefit in this
case):
class BookListView(generic.ListView):
model = Book
def get_queryset(self):
return Book.objects.filter(title__icontains='war')[:5] # Get 5 books containing the title
war
We might also override get_context_data() in order to pass additional context variables to the template (e.g. the list of
books is passed by default). The fragment below shows how to add a variable named "some_data" to the context (it would
then be available as a template variable).
class BookListView(generic.ListView):
model = Book
Create the HTML file /locallibrary/catalog/templates/catalog/book_list.html and copy in the text below. As
discussed above, this is the default template file expected by the generic class-based list view (for a model named Book in
an application named catalog).
Templates for generic views are just like any other templates (although of course the context/information passed to the
template may differ). As with our index template, we extend our base template in the first line and then replace the block
named content.
{% extends "base_generic.html" %}
{% block content %}
<h1>Book List</h1>
{% if book_list %}
<ul>
{% for book in book_list %}
<li>
<a href="{{ book.get_absolute_url }}">{{ book.title }}</a> ({{book.author}})
</li>
{% endfor %}
</ul>
{% else %}
<p>There are no books in the library.</p>
{% endif %}
{% endblock %}
The view passes the context (list of books) by default as object_list and book_list aliases; either will work.
Conditional execution
We use the if, else, and endif template tags to check whether the book_list has been defined and is not empty.
If book_list is empty, then the else clause displays text explaining that there are no books to list. If book_list is not
empty, then we iterate through the list of books.
{% if book_list %}
<!-- code here to list the books -->
{% else %}
<p>There are no books in the library.</p>
{% endif %}
The condition above only checks for one case, but you can test on additional conditions using the elif template tag
(e.g. {% elif var2 %}). For more information about conditional operators see: if, ifequal/ifnotequal,
and ifchanged in Built-in template tags and filters (Django Docs).
For loops
The template uses the for and endfor template tags to loop through the book list, as shown below. Each iteration
populates the book template variable with information for the current list item.
<li> <!-- code here get information from each book item --> </li>
{% endfor %}
While not used here, within the loop Django will also create other variables that you can use to track the iteration. For
example, you can test the forloop.last variable to perform conditional processing the last time that the loop is run.
Accessing variables
The code inside the loop creates a list item for each book that shows both the title (as a link to the yet-to-be-created detail
view) and the author.
We access the fields of the associated book record using the "dot notation" (e.g. book.title and book.author), where
the text following the book item is the field name (as defined in the model).
We can also call functions in the model from within our template — in this case we call Book.get_absolute_url() to
get a URL you could use to display the associated detail record. This works provided the function does not have any
arguments (there is no way to pass arguments!)
Note: We have to be a little careful of "side effects" when calling functions in templates. Here we just get a URL to
display, but a function can do pretty much anything — we wouldn't want to delete our database (for example) just by
rendering our template!
Open the base template (/locallibrary/catalog/templates/base_generic.html) and insert {% url 'books' %} into
the URL link for All books, as shown below. This will enable the link in all pages (we can successfully put this in place
now that we've created the "books" URL mapper).
You won't be able to build the book list yet, because we're still missing a dependency — the URL map for the book detail
pages, which is needed to create hyperlinks to individual books. We'll show both list and detail views after the next
section.
The book detail page will display information about a specific book, accessed using the
URL catalog/book/<id> (where <id> is the primary key for the book). In addition to fields in the Book model (author,
summary, ISBN, language, and genre), we'll also list the details of the available copies (BookInstances) including the
status, expected return date, imprint, and id. This will allow our readers to not only learn about the book, but also to
confirm whether/when it is available.
URL mapping
Open /catalog/urls.py and add the path named 'book-detail' shown below. This path() function defines a pattern,
associated generic class-based detail view, and a name.
urlpatterns = [
For the book-detail path the URL pattern uses a special syntax to capture the specific id of the book that we want to see.
The syntax is very simple: angle brackets define the part of the URL to be captured, enclosing the name of the variable
that the view can use to access the captured data. For example, <something> , will capture the marked pattern and pass
the value to the view as a variable "something". You can optionally precede the variable name with a converter
specification that defines the type of data (int, str, slug, uuid, path).
In this case we use '<int:pk>' to capture the book id, which must be a specially formatted string and pass it to the view
as a parameter named pk (short for primary key). This is the id that is being used to store the book uniquely in the
database, as defined in the Book Model.
View (class-based)
Open catalog/views.py, and copy the following code into the bottom of the file:
class BookDetailView(generic.DetailView):
model = Book
If you need to, you can change the template used and the name of the context object used to reference the book in the
template. You can also override methods to, for example, add additional information to the context.
If a requested record does not exist then the generic class-based detail view will raise an Http404 exception for you
automatically — in production, this will automatically display an appropriate "resource not found" page, which you can
customise if desired.
Just to give you some idea of how this works, the code fragment below demonstrates how you would implement the class-
based view as a function if you were not using the generic class-based detail view.
Alternatively, we can use the get_object_or_404() function as a shortcut to raise an Http404 exception if the record is
not found.
Create the HTML file /locallibrary/catalog/templates/catalog/book_detail.html and give it the below content. As discussed
above, this is the default template file name expected by the generic class-based detail view (for a model named Book in
an application named catalog).
{% extends "base_generic.html" %}
{% block content %}
<h1>Title: {{ book.title }}</h1>
<p><strong>Author:</strong> <a href="">{{ book.author }}</a></p> <!-- author detail link not yet
defined -->
<p><strong>Summary:</strong> {{ book.summary }}</p>
<p><strong>ISBN:</strong> {{ book.isbn }}</p>
<p><strong>Language:</strong> {{ book.language }}</p>
<p><strong>Genre:</strong> {{ book.genre.all|join:", " }}</p>
<div style="margin-left:20px;margin-top:20px">
<h4>Copies</h4>
The author link in the template above has an empty URL because we've not yet created an author detail page to link to.
Once the detail page exists we can get its URL with either of these two approaches:
• Use the url template tag to reverse the 'author-detail' URL (defined in the URL mapper), passing it the author
instance for the book:
<a href="{% url 'author-detail' book.author.pk %}">{{ book.author }}</a>
• Call the author model's get_absolute_url() method (this performs the same reversing operation):
<a href="{{ book.author.get_absolute_url }}">{{ book.author }}</a>
While both methods effectively do the same thing, get_absolute_url() is preferred because it helps you write more
consistent and maintainable code (any changes only need to be done in one place: the author model).
Though a little larger, almost everything in this template has been described previously:
{% endfor %}
This method is needed because you declare a ForeignKey (one-to many) field in only the "one" side of the relationship
(the BookInstance). Since you don't do anything to declare the relationship in the other ("many") models, it (the Book)
doesn't have any field to get the set of associated records. To overcome this problem, Django constructs an appropriately
named "reverse lookup" function that you can use. The name of the function is constructed by lower-casing the
model name where the ForeignKey was declared, followed by _set (i.e. so the function
created in Book is bookinstance_set()).
Note: Here we use all() to get all records (the default). While you can use the filter() method to get a subset of
records in code, you can't do this directly in templates because you can't specify arguments to functions.
Beware also that if you don't define an order (on your class-based view or model), you will also see errors from the
development server like this one:
/foo/local_library/venv/lib/python3.5/site-packages/django/views/generic/list.py:99:
UnorderedObjectListWarning: Pagination may yield inconsistent results with an unordered
object_list: <QuerySet [<Author: Ortiz, David>, <Author: H. McRaven, William>, <Author:
Leigh, Melinda>]>
allow_empty_first_page=allow_empty_first_page, **kwargs)
That happens because the paginator object expects to see some ORDER BY being executed on your underlying
database. Without it, it can't be sure the records being returned are actually in the right order!
This tutorial hasn't covered Pagination (yet!), but since you can't use sort_by() and pass a parameter (the same
with filter() described above) you will have to choose between three choices:
class Author(models.Model):
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
date_of_birth = models.DateField(null=True, blank=True)
date_of_death = models.DateField('Died', null=True, blank=True)
def get_absolute_url(self):
return reverse('author-detail', args=[str(self.id)])
def __str__(self):
return f'{self.last_name}, {self.first_name}'
class Meta:
ordering = ['last_name']
Of course, the field doesn't need to be last_name: it could be any other.
Last but not least, you should sort by an attribute/column that actually has an index (unique or not) on your database to
avoid performance issues. Of course, this will not be necessary here (we are probably getting ahead of ourselves with so
few books and users), but it is something worth keeping in mind for future projects.
The second interesting (and non-obvious) thing in the template is where we set a class (text-success, text-danger, text-
warning) to color-code the human readable status text for each book instance ("available", "maintenance", etc.). Astute
readers will note that the method BookInstance.get_status_display() that we use to get the status text does not appear
elsewhere in the code.
<p class="{% if copy.status == 'a' %}text-success{% elif copy.status == 'm' %}text-danger{% else
%}text-warning{% endif %}">
{{ copy.get_status_display }} </p>
This function is automatically created because BookInstance.status is a choices field. Django automatically creates a
method get_FOO_display() for every choices field "Foo" in a model, which can be used to get the current value of the
field.
At this point, we should have created everything needed to display both the book list and book detail pages. Run the
server (python3 manage.py runserver) and open your browser to http://127.0.0.1:8000/.
Then click a link to one of your books. If everything is set up correctly, you should see something like the
following screenshot.
Pagination
If you've just got a few records, our book list page will look fine. However, as you get into the tens or hundreds of records
the page will take progressively longer to load (and have far too much content to browse sensibly). The solution to this
problem is to add pagination to your list views, reducing the number of items displayed on each page.
Django has excellent inbuilt support for pagination. Even better, this is built into the generic class-based list views so you
don't have to do very much to enable it!
Views
class BookListView(generic.ListView):
model = Book
paginate_by = 10
With this addition, as soon as you have more than 10 records the view will start paginating the data it sends to the
template. The different pages are accessed using GET parameters — to access page 2 you would use the
URL /catalog/books/?page=2.
Templates
Now that the data is paginated, we need to add support to the template to scroll through the results set. Because we might
want paginate all list views, we'll add this to the base template.
Open /locallibrary/catalog/templates/base_generic.html and find the "content block" (as shown below).
Copy in the following pagination block immediately following the {% endblock %}. The code first checks if pagination is
enabled on the current page. If so, it adds next and previous links as appropriate (and the current page number).
{% block pagination %}
{% if is_paginated %}
<div class="pagination">
<span class="page-links">
{% if page_obj.has_previous %}
<a href="{{ request.path }}?page={{ page_obj.previous_page_number
}}">previous</a>
{% endif %}
<span class="page-current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
<a href="{{ request.path }}?page={{ page_obj.next_page_number }}">next</a>
{% endif %}
</span>
</div>
{% endif %}
{% endblock %}
The page_obj is a Paginator object that will exist if pagination is being used on the current page. It allows you to get all
the information about the current page, previous pages, how many pages there are, etc.
We use {{ request.path }} to get the current page URL for creating the pagination links. This is useful because it is
independent of the object that we're paginating.
The screenshot below shows what the pagination looks like — if you haven't entered more than 10 titles into your
database, then you can test it more easily by lowering the number specified in the paginate_by line in
your catalog/views.py file. To get the below result we changed it to paginate_by = 2.
The pagination links are displayed on the bottom, with next/previous links being displayed depending on which page
you're on.