Khóa học lập trình Python

Phần 1 : Python cơ bản

Tổng quan về Python

Python là một trong những ngôn ngữ lập trình thông dịch bậc cao ra đời từ năm 1991. Trong những năm gần đây, xu hướng sử dụng Python đang tăng nhanh trên thế giới. Python được sử dụng làm ngôn ngữ dạy nhập môn lập trình cho sinh viên các ngành kỹ thuật ở nhiều trường đại học ở Mỹ. Python được xem là ngôn ngữ quan trọng hàng đầu trong các lĩnh vực của ký nguyên công nghiệp 4.0 như AI, Machine Learning, Data Science. Ngoài ra xu hướng lập trình backend với Python cũng phát triển mạnh mẽ trong những năm gần đây với việc Django (framework phổ biến nhất về backend viết trên Python) đã được nhiều công ty sử dụng để phát triển website của mình như : Youtube, Quora, Dropbox, Reddit, Instagram …

Trong bảng xếp hạng về mức độ phổ biến của các ngôn ngữ lập trình, Python thường được xếp ở vị trí từ thứ 3 đến thứ 1 ( tùy theo mỗi loại xếp hạng)

Hình 1 . Bảng xếp hạng ngôn ngữ lập trình của pypl.com (tháng 2/2019)

Hình 2. Bảng xếp hạng ngôn ngữ lập trình của tiobe (tháng 3/2019)

Hình 3. Xếp hạng ngôn ngữ lập trình của redmonk (quý I/2019)

Hình 4. Xếp hạng ngôn ngữ lập trình của IEEE (năm 2018)

Đặc điểm của Python

Python có ưu điểm:

Bên cạnh đó Python có một số nhược điểm:

Cài đặt Python

Hình 5. Thêm lựa chọn “Add Python to PATH” khi cài đặt Python

Từ menu start của Windows tìm ứng dụng IDLE và mở ứng dụng này

Hình 6. Khởi động ứng dụng IDLE

Khi cửa sổ IDLE khởi động xong, gõ vào cửa sổ một lệnh để chạy thử:

print('Hello')

Hình 7. Chạy thử lệnh trong cửa sổ IDLE

Tạo mới một chương trình : Từ Menu File, chọn New File, chương trình sẽ tạo ra một cửa sổ soạn thảo mới, nhập vào cửa sổ này nội dung một chương trình đơn giản, ví dụ:

x = 1

y = 2

z = x + y

print(f'{x} + {y} = {z}')

Hình 8. Tạo mới một chương trình

Lưu lại chương trình thành file (ví dụ hello.py), sau đó chạy chương trình bằng cách từ Menu Run, chọn Run Module (F5). Chương trình sẽ chạy và in kết quả ra cửa sổ IDLE:

Hình 9. Kết quả chạy chương trình được in ra cửa sổ IDLE

Cài đặt Python IDE

IDLE cho phép soạn thảo và chạy chương trình Python nhưng không hỗ trợ nhiều các chức năng phát triển ứng dụng. Với các ứng dụng python có nhiều file, nên sử dụng IDE khác. Có thể sử dụng một trong các IDE:

Pycharm được xem là IDE hỗ trợ Python đầy đủ nhất, tuy nhiên Visual Studio Code tương đối nhẹ trong khi hỗ trợ Python tương đối tốt. Muốn sử dụng Visual Studio Code để phát triển ứng dụng Python, cần cài đặt Python Extension cho Visual Studio Code:

Hình 10 . Cài đặt python extension cho Visual Studio Code

Biến số / Kiểu dữ liệu

Biến số được sử dụng để lưu trữ các giá trị trong quá trình tính toán của chương trình

Cách thức khai báo biến trong Python :

bienSo = <gia_tri>

Trong đó bienSo là tên của biến số cần khai báo, <gia_tri> là giá trị ban đầu chúng ta muốn đặt cho biến số. Giá trị này có thể là một hằng số hoặc một biểu thức của các biến số đã được khai báo.

Ví dụ:

x = 1

y = 2

z = x + y

Sau khi được khai báo, giá trị của biến số có thể được thay đổi bằng cách gán lại một biểu thức mới cho biến:

      x = 1

      x = 2

      x = x + 1

Để xóa một biến khỏi bộ nhớ, có thể sử dụng lệnh del :

del x, y, z

Mỗi biến số sau khi được khai báo sẽ chứa giá trị nhất định, gọi là dữ liệu. Giá trị dữ liệu này sẽ thuộc vào một trong các kiểu dữ liệu mà Python hỗ trợ

Ở giai đoạn mới học, chúng ta chủ yếu quan tâm đến 2 kiểu dữ liệu : kiểu số và kiểu String. Các kiểu dữ liệu còn lại sẽ được tìm hiểu dần trong các phần sau.

Vào ra dữ liệu

Đọc dữ liệu từ bàn phím

Lệnh input được sử dụng để nhập dữ liệu từ bàn phím. Dữ liệu trả về của lệnh là một biến string chứa dòng văn bản mà người dùng đã nhập vào:

giatri = input('Nhập giá trị cho biến : ')

Ví dụ 1

ten = input('Mời bạn cho biết tên của bạn : ')
print(
'Xin chào ', ten)

Ví dụ 2:

a = input('Số thứ nhất : ')
a = float(a)

b = input(
'Số thứ hai : ')
b = float(b)

print(
'Tổng của 2 số là : ', a+b)

In dữ liệu ra màn hình

Lệnh print được sử dụng để in dữ liệu ra màn hình, cú pháp của lệnh này như sau:

print(<danh sách giá trị ngăn cách nhau bởi dấu phảy>)

Ví dụ:

x = 1
y =
2
z = x + y
print(x,
'+', y, '=', z)

Cấu trúc điều khiển, cấu trúc lặp

Cấu trúc điều khiển if

Các cấu trúc if trong Python:

    <Lệnh>

    <Lệnh 1>

else:

    <Lệnh 2>

    <Lệnh 1>

elif <điều kiện 2>  :

    <Lệnh 2>

elif <điều kiện 3>  :

    <Lệnh 3>

else:

    <Lệnh 4>

Lưu ý:

Điều kiện logic trong lệnh if

Điều kiện logic trong lệnh if về bản chất là một biểu thức, nhưng chỉ nhận một trong hai giá trị True/False . Điều kiện này thường được tạo ra từ các phép toán :

 

Một số ví dụ minh họa

Ví dụ 1:

Nhập vào từ bàn phím 2 số và in ra giá trị nhỏ nhất của 2 số đó

a = input('Số thứ nhất : ')
a = float(a)

b = input(
'Số thứ hai : ')
b = float(b)

if a < b:
  print(a)

else:
  print(b)

Ví dụ 2:

Chỉ số BMI (Body mass index) dùng để đánh giá thân hình của một người. Chỉ số này được đo bằng tỉ số giữa cân nặng (tính theo kg) và bình phương của chiều cao (tính theo mét). Dựa vào chỉ số BMI, người ta có thể đánh giá được thân hình của một người:

 

 

Nhập vào giá trị chiều cao và cân nặng của một người từ bàn phím và cho biết tình trạng thân hình của người đó.

height = input('Chiều cao (mét) : ')
height = float(height)

mass = input(
'Cân nặng (kg) : ')
mass = float(mass)

bmi = mass / (height * height)

if bmi < 15:
   print(
'Thân hình quá gầy')

elif bmi < 16:
   print(
'Thân hình gầy')

elif bmi < 18.5:
   print(
'Thân hình hơi gầy')

elif bmi < 25:
   print(
'Thân hình bình thường')

elif bmi < 30:
   print(
'Thân hình hơi béo')

elif bmi < 35:
   print(
'Thân hình béo')

else:
   print(
'Thân hình quá béo')

Cấu trúc lặp for

Cấu trúc lặp for được dùng để thực hiện một vòng lặp với số lần lặp biết trước. Cấu trúc lặp for như sau:

 

for bienLap in tapGiaTri:

    <khoi_lenh>

Cách biểu diễn tập giá trị lặp trong vòng lặp for

range(<end>)

Biểu thức này thể hiện tập các số nguyên từ 0 đến trước giá trị <end>,(tức <end-1>)

Ví dụ:

range(5) → 0, 1, 2, 3, 4

range(start, end)

Biểu thức này thể hiện tập các số nguyên từ <start> đến <end-1>

Ví dụ:

range(1, 5) → 1, 2, 3, 4

range(start, end, increment)

Biểu thức này thể hiện tập các số nguyên từ <start> đến <end-1> và tăng đều với khoảng cách increment.

Ví dụ:

range(0, 10, 2) → 0, 2, 4, 6, 8

Một số ví dụ minh họa

Ví dụ 1:

Tính tổng các số từ 1 đến 100

S = 0
for i in range(1, 101):
   S += i

print(
'S = ', S)

Ví dụ 2:

Viết chương trình in ra bảng cửu chương

for i in range(2, 10):
   
for j in range(2, 10):
       print(i,
'*', j, '=', i*j)
   print()

Cấu trúc lặp while

Cấu trúc lặp while dùng để thực hiện một vòng lặp với số lần lặp không biết trước. Cấu trúc lặp while như sau:

 

while dieukien:

    <khoi_lenh>

Một số ví dụ minh họa

Ví dụ 1:

Kiểm tra một số có phải số nguyên tố không

x = int(input('x='))

i =
2
while i * i <= x:
   
if x % i == 0:
       j = x // i
       print(x,
'=', i , '*', j)
       exit()
   i +=
1

print(x,
' là số nguyên tố')

Ví dụ 2:

Chuyển một số từ hệ thập phân sang hệ nhị phân

x = int(input('x='))

s =
''

while x > 0:
   i = x %
2
   s = str(i) + s
   x = int(x/
2)

print(s)

Dữ liệu kiểu Number, String, Date&Time

Kiểu dữ liệu Number

Trong Python có 2 kiểu số : số nguyên (int) và số thực (float)

Số nguyên là các giá trị số không chứa dấu phảy:

x = 1

y = 2

z = -10000

Số thập phân là các số có chứa dấu phảy :

x = 1.0

y = 10.5

z = 9e38

Các phép tính trên kiểu dữ liệu số trong Python:

Với Python 3, phép chia 2 số nguyên cho kết quả là một số thập phân:

>>> print(4/3)

1.3333333333333333

Phép tính dạng rút gọn :

Các phép tính dạng rút gọn +=, -=, *=, /=, //=, %=, **=, … được sử dụng để tính giá trị phép tính trên 2 biến và gán kết quả vào biến thứ nhất. Ví dụ:

Các hàm toán học:

Các hàm toán học (trong thư viện math) cho phép thực hiện một số hàm lượng giác và giải tích trên số thập phân:

Để sử dụng các hàm toán học, cần khai báo thư viện math : import math

Ví dụ :

import math
print(math.sin(math.pi/
2))
print(math.sqrt(
4))

Kiểu dữ liệu String

Trong Python, giá trị của kiểu dữ liệu String được đặt trong cặp 2 dấu nháy đơn (') hoặc 2 dấu nháu kép (") :

hoten = 'Nguyễn Văn An'
diachi =
"Hà Nội"

Các hàm thông dụng trên kiểu dữ liệu String:

Ví dụ:

>>> str(100)

'100'

Ví dụ:

>>> 'Chào ' + 'bạn'

'Chào bạn'

Ví dụ:

>>> 'Chào bạn'.lower()

'chào bạn'

Ví dụ:

>>> 'Chào bạn'.upper()

'CHÀO BẠN'

Ví dụ:

>>> 'Tôi sống ở Hà Nội'.replace('Hà Nội', 'Huế')

'Tôi sống ở Huế'

Ví dụ:

>>> 'Hà Nội'.split()

['Hà', 'Nội']

>>> 'Hà Nội, Việt Nam'.split(',')

['Hà Nội', ' Việt Nam']

Ví dụ:

>>> '   Hà Nội    '.strip()

'Hà Nội'

Ví dụ:

>>> len('Chào bạn')

8

Truy nhập đến từng kí tự của String:

Một giá trị string được tạo thành từ một dãy các kí tự, để lấy kí tự ở vị trí index (bắt đầu từ 0) của một giá trị string, chúng ta dùng cú pháp:

c = text[index]

Bản thân một giá trị string có thể xem là một tập hợp kí tự và có thể dùng vòng lặp for chạy qua tập kí tự này

Ví dụ :

text = 'Chào bạn'

for c in text:

    print( c )

Một số phép tính trên kí tự:

Ví dụ:

>>> ord('A')

65

Ví dụ:

>>> chr(65)

'A'

Substring:

Mỗi đoạn nhỏ của một giá trị string được gọi là substring. Bản thân mỗi substring cũng là một giá trị kiểu string.

Các cách để lấy ra substring trong Python:

text[start:end]

Biểu thức này trả về đoạn kí tự từ vị trí start đến end-1 của string gốc.

Ví dụ:

>>> text = 'Chào bạn'; print(text[0:4])

Chào

text[start:]

Biểu thức này trả về đoạn kí tự từ vị trí start đến hết string gốc.

Ví dụ:

>>> text = 'Chào bạn'; print(text[5:])

bạn

text[:end]

Biểu thức này trả về đoạn kí tự từ đầu đến vị trí end của string gốc.

Ví dụ:

>>> text = 'Chào bạn'; print(text[:4])

Chào

Ví dụ:

>>> text = 'Chào bạn'; print(text[-3:])

bạn

Format string

Để ghép nhiều giá trị string thành một có thể dùng phép cộng string như đã trình bày ở phía trên:

x = 1
y =
2
z =
3
st =
'Tổng của ' + str(x) + ' và ' + str(y) + ' là ' + str(z)
print(st)

Tuy nhiên việc cộng nhiều string sẽ làm chương trình khó theo dõi, do đó có thể dùng hàm format của kiểu dữ liệu string để ghép nhiều string (và các kiểu dữ liệu khác) với nhau.Cách thức hiện như sau:

x = 1
y =
2
z =
3
st =
'Tổng của {} và {} là {}'.format(x, y, z)
print(st)

Mỗi cặp {} trong template của string sẽ tương ứng với một biến/biểu thức nằm bên trong hàm format.

Ngoài ra, có thể viết tên biến/biểu thức ngay bên trong cặp {} nếu sử dụng tiền tố f ở trước template của string, cách thực hiện như sau:

x = 1
y =
2
z =
3
st =
f'Tổng của {x} và {y} là {z}'
print(st)

Một số ví dụ minh họa:

Ví dụ 1:

Kiểm tra tính hợp lệ của mật khẩu (là một giá trị string). Mật khẩu hợp lệ nếu:

password = input('Nhập mật khẩu : ')

if len(password) < 6:
   print(
'Mật khẩu quá ngắn')
   exit()

chuaChuCai =
False
for c in password:
   C = c.upper()
   
if (C >= 'A' and C <= 'Z'):
       chuaChuCai =
True
       
break

if not chuaChuCai:
   print(
'Mật khẩu cần chứa ít nhất một chữ cái (a-z/A-Z)')
   exit()

chuaChuSo =
False
for c in password:
   
if c >= '0' and c <= '9':
       chuaChuSo =
True
       
break

if not chuaChuSo:
   print(
'Mật khẩu cần chứa ít nhất một chữ số (0-9)')
   exit()

print(
'Mật khẩu hợp lệ !')

Ví dụ 2 :

Chuyển một số trong phạm vi 0-99 thành phát âm tiếng Việt. Ví dụ : 85 → tám mươi lăm

bangso = ['không', 'một', 'hai', 'ba', 'bốn', 'năm', 'sáu', 'bảy', 'tám', 'chín']

x = int(input(
'x='))

if x < 10:
   text = bangso[x]
else:
   chuc = x //
10
   donvi = x %
10

   text = (bangso[chuc] +
' mươi') if chuc > 1 else 'mười'   

   
if donvi > 0:
       text +=
' '

       
if donvi == 5:
           text +=
'lăm'

       
elif donvi == 1 and chuc > 1:
           text +=
'mốt'

       
else:
           text += bangso[donvi]

print(text)

Kiểu dữ liệu Date & Time

Dữ liệu kiểu Date & Time được cung cấp bởi thư viện datetime của hệ thống:

from datetime import date, datetime

Kiểu dữ liệu date chứa thông tin về ngày, tháng, năm. Kiểu dữ liệu datetime chứa thông tin về ngày, tháng, năm, giờ, phút , giây, mili giây.

Để tạo mới một đối tượng kiểu date/datetime:

from datetime import date, datetime

d = date(
2019,1,1)                  # 01/01/2019
dt = datetime(
2019,1,1,23,59,59)    # 23:59:59 01/01/2019

Lấy ngày giờ hiện tại của hệ thống:

from datetime import date, datetime

d = date.today()
dt = datetime.now()

Truy nhập đến các thành phần của date/datetime :

from datetime import date, datetime

dt = datetime(
2019,1,1,23,59,59)
print(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second)

d = date(
2019,1,1)
print(d.year, d.month, d.day)

Chuyển đổi từ datetime sang String :

from datetime import datetime

now = datetime.now()

print(now.strftime(
'%d-%m-%Y'))
print(now.strftime(
'%d/%m/%Y %H:%M:%S'))

Chuyển đổi từ String sang datetime :

from datetime import datetime

dt1 = datetime.strptime(
'01/01/2019', '%d/%m/%Y')
print(dt1)

dt2 = datetime.strptime(
'01-01-2019 23:59:59', '%d-%m-%Y %H:%M:%S')
print(dt2)

Khoảng thời gian :

Để đo sự chênh lệch giữa các mốc thời gian, thư viện datetime cung cấp kiểu dữ liệu timedelta. Kiểu dữ liệu này có thể hiểu là một bộ gồm 2 thành phần là số ngày (days) và số giây (seconds)

from datetime import timedelta

duration = timedelta(days=
30, seconds=3600)

Tính khoảng thời gian giữa 2 thời điểm :

from datetime import datetime

dt1 = datetime(
2019, 2, 1)
dt2 = datetime(
2019, 2, 28, 23, 59, 59)

duration = dt2 - dt1

days = duration.days
hours = duration.seconds //
3600
minutes = (duration.seconds %  
3600) // 60
seconds = duration.seconds %
60

print(
f'Duration : {days} days, {hours} hours,  {minutes} minutes, {seconds} seconds')

Cộng một mốc thời điểm với một khoảng thời gian :

from datetime import datetime, timedelta

dt1 = datetime(
2019, 2, 1)
duration = timedelta(days=
30, seconds=3600)
dt2 = dt1 + duration

print(dt2)

Các kiểu dữ liệu List, Tuple, Set, Dictionary

Kiểu dữ liệu List

Kiểu dữ liệu List dùng để chứa một dãy nhiều phần tử. Khi khai báo giá trị, các phần từ của một List được đặt trong cặp dấu [ ].

Ví dụ:

dayso = [1, 3, 5, 7, 9]
danhsach_hocsinh = [
"Nguyễn Văn An", "Nguyễn Chí Cường", "Nguyễn Mạnh Tuấn"]
diachi = [
322, "Tây Sơn", "Hà Nội"]

Các phần tử của một List có thể không cùng kiểu dữ liệu.

Một số phép tính trên kiểu dữ liệu List

Ví dụ:

>>> ds = [1, 2, 3, 4, 5] ; print(len(ds))

5

Ví dụ:

>>> ds = [1, 2, 3, 4, 5] ; ds.append(6); print(ds)

[1, 2, 3, 4, 5, 6]

Ví dụ:

>>> ds = [1, 2, 3, 4, 5] ; ds.remove(3); print(ds)

[1, 2, 4, 5]

Ví dụ:

>>> ds = [1, 2, 3, 4, 5] ; ds.extend([6, 7]); print(ds)

[1, 2, 3, 4, 5, 6, 7]

Ví dụ:

>>> ds = [1, 2, 3, 4, 5] ; ds += [6, 7]; print(ds)

[1, 2, 3, 4, 5, 6, 7]

Ví dụ:

>>> ds = [1, 2, 3, 4, 5] ; ds.reverse(); print(ds)

[5, 4, 3, 2, 1]

Ví dụ:

>>> ds = [1, 2, 3, 4, 5] ; print(1 in ds)

True

Ví dụ:

>>> ds = [1, 2, 3, 4, 5] ; print(3 not in ds)

False

Ví dụ:

>>> ds = [1, 2, 3, 4, 5] ; print(ds.index(3))

2

Ví dụ:

>>> ds = [1, 3, 2, 5, 4] ; print(min(ds))

1

Ví dụ:

>>> ds = [1, 3, 2, 5, 4] ; print(max(ds))

5

Truy nhập đến từng phần tử của List

Tương tự kiểu String, kiểu List cũng có thể truy nhập đến từng phần tử hoặc lấy ra các đoạn con bằng cách sử dụng các cấu trúc:

Sắp xếp một List

Để sắp xếp các phần tử trong một List theo một thứ tự tăng dần hoặc giảm dần có thể sử dụng hàm:

sorted(lst [, key=key_function, reverse=True/False)])

Các tham số cần truyền vào cho hàm sắp xếp:

Ví dụ:

>>> print(sorted([1, 3, 2, 5, 4]))

[1, 2, 3, 4, 5]

>>> print(sorted([1, 3, 2, 5, 4], reverse=True))

[5, 4, 3, 2, 1]

>>> def number_of_element(x): return len(x)    

...

>>> print(sorted([[1,2], [3,4,6],[1]], key=number_of_element)) # sắp xếp theo kích thước các phần tử trong list

[[1], [1, 2], [3, 4, 6]]

Giá trị hàm thuộc tính key_function đôi khi có thể được truyền vào hàm sorted dưới dạng  “lambda function”:

print(sorted([[1,2], [3,4,6],[1]], key=lambda x : len(x)))

Biểu thức

lambda x : len(x)

Tương đương với giá trị của một hàm f trong đó f được định nghĩa bằng

def f(x) : return len(x)

Một số ví dụ minh họa

Ví dụ 1:

In ra các số nguyên tố nhỏ hơn 1000

ds = []

for x in range(2, 1000):
   nguyento =
True

   
for p in ds:
       
if x % p == 0:
            nguyento =
False
           
break

       
if p *p > x:
           
break   

   
if nguyento:
       ds.append(x)

print(ds)

Ví dụ 2:

In ra 10 dòng đầu của tam giác Pascal (các hệ số của khai triển nhị thức (a+b)n )

(a+b)0 = 1                     → 1

(a+b)1  = a + b                → 1  1

(a+b)2  = a2 + 2ab + b2         → 1  2  1

(a+b)3  = a3 + 3a2b + 3ab2 + b3  → 1  3  3  1

N = 10
heso = []

for i in range(N):
   heso.append(
1)
   
for j in range(i-1, 0, -1):
       heso[j] += heso[j
-1]

   print(heso)

Kiểu dữ liệu Tuple

Dữ liệu kiểu bộ nhóm (Tuple) tương tự với kiểu danh sách (List), chỉ khác là các phần tử sau khi khai báo thì không sửa (thêm, xóa) được. Các phần tử của dữ liệu kiểu nhóm được đặt trong cặp dấu ( )

dayso = (1, 3, 5, 7, 9)
danhsach_hocsinh = (
"Nguyễn Văn An", "Nguyễn Chí Cường", "Nguyễn Mạnh Tuấn")

Các phép tính trên Tuple

Các phép tính trên Tuple gần giống với phép tính trên List, tuy nhiên Tuple chỉ hỗ trợ các phép tính đọc thông tin của Tuple, không hỗ trợ các phép tính làm thay đổi nội dung của Tuple

Kiểu dữ liệu Set

Kiểu dữ liệu Set dùng để chứa các phần tử của một tập hợp. So với kiểu List, kiểu Set có điểm khác là:

Các phép tính trên Set

Ví dụ:

>>> s = set([1, 2]); s.add(3); print(s)

{1, 2, 3}

Ví dụ:

>>> s = set([1, 2, 3]); s.remove(2); print(s)

{1, 3}

Ví dụ:

>>> s = set([1, 2, 3]); print(1 in s)

True

Ví dụ:

>>> s = set([1, 2, 3]); print(3 not in s)

False

Ví dụ:

>>> s1 = set([1, 2, 3]); s2 = set([3, 4, 5]); print(s1.union(s2))

{1, 2, 3, 4, 5}

Ví dụ:

>>> s1 = set([1, 2, 3]); s2 = set([3, 4, 5]); print(s1.intersection(s2))

{3}

Ví dụ:

>>> s1 = set([1, 2, 3]); s2 = set([3, 4, 5]); print(s1.difference(s2))

{1, 2}

Một số ví dụ minh họa

Ví dụ 1:

s1 = set([1, 2])
s1.add(
3)
s1.remove(
1)

if 1 not in s1:
   print(
"1 không nằm trong tập hợp ", s1)

if 2 in s1:
  print(
"2 nằm trong tập hợp ", s1)

s2 = set([
3, 4])
s3 = set.union(s1, s2)
print(s3)

s4 = set.intersection(s1, s2)
print(s4)

Ví dụ 2:

weekdays = set(['Thứ hai', 'Thứ ba', 'Thứ tư', 'Thứ năm', 'Thứ sáu'])
weekends = set([
'Thứ bảy', 'Chủ nhật'])

day =
"Thứ ba"

if day in weekdays:
  print(day,
' là ngày làm việc')

if day in weekends:
  print(day,
' là ngày nghỉ cuối tuần')

Kiểu dữ liệu Dictionary

Kiểu dữ liệu Dictionary dùng để chứa một bảng biến đổi 1-1 giữa 2 tập hợp :

 

Cú pháp khai báo kiểu Dictionary trong Python:

d = {

    <key1> : <value1>,

    <key2> : <value2>,

    ...

}

Ví dụ:

d = {"một" : 1, "hai" : 2, "ba" : 3}

Để truy nhập đến phần tử có key là <key> trong Dictionary, Python sử dụng phép toán []:

d[<key>]

Ví dụ:

>>> d = {"một" : 1, "hai" : 2, "ba" : 3}; print(d['một'])

1

Trong trường hợp nếu giá trị <key> không nằm trong tập nguồn của Dictionary, Python sẽ đưa ra thông báo Exception:

>>> d = {"một" : 1, "hai" : 2, "ba" : 3}; print(d['bốn'])

Traceback (most recent call last):

  File "<pyshell#118>", line 1, in <module>

    d = {"một" : 1, "hai" : 2, "ba" : 3}; print(d['bốn'])

KeyError: 'bốn'

Để tránh gặp lỗi này, có thể dùng hàm get của Dictionary để truyền giá trị mặc định trong trường hợp <key> không nằm trong tập nguồn của Dictionary:

d.get(<key>, <gia_tri_mac_dinh>)

Ví dụ:

>>> d = {"một" : 1, "hai" : 2, "ba" : 3}; print(d.get('bốn', -1))

-1

Một số ví dụ minh họa

Ví dụ 1:

Đếm tần suất của các từ trong một câu

text = 'Một năm có mười hai tháng, tháng hai có hai mươi tám ngày, các tháng còn lại có ba mươi hoặc ba mươi mốt ngày'

text = text.lower()
for c in ['.', ',' , ':']:
   text = text.replace(c,
' ')

words_count = {}

for word in text.split():
   words_count[word] = words_count.get(word,
0) + 1

for word in words_count:
   print(word ,
' : ', words_count[word])

Ví dụ 2:

Chuyển phát âm tiếng Việt của một số trong phạm vi 1-99 thành giá trị số đó. Ví dụ : tám mươi lăm → 85

bang_so1 = {'một' : 1, 'hai' : 2, 'ba' : 3, 'bốn' : 4, 'năm' : 5, 'sáu' : 6, 'bảy' : 7, 'tám' : 8, 'chín' : 9, 'mười' : 10}

bang_so2 = {
'một' : 1, 'hai' : 2, 'ba' : 3, 'bốn' : 4, 'lăm' : 5, 'sáu' : 6, 'bảy' : 7, 'tám' : 8, 'chín' : 9}

bang_so3 = {
'mươi' : 0, 'mốt' : 1, 'hai' : 2, 'ba' : 3, 'bốn' : 4, 'tư' : 4, 'lăm' : 5, 'sáu' : 6, 'bảy' : 7, 'tám' : 8, 'chín' : 9}

text = input(
'text=')
words = text.lower().split()
N = len(words)

if N == 1:
   
if words[0] in bang_so1:
       print(bang_so1[words[
0]])        
   
else:
       print(
'Không tồn tại số')
       
   exit()

chuc, donvi =
-1, -1

if (N == 3 and words[1] == 'mươi') or N == 2:
   chuc = bang_so1.get(words[
0], -1)
   donvi = bang_so3.get(words[
-1], -1)

if N == 2 and words[0] == 'mười':
   chuc =
1
   donvi = bang_so2.get(words[
1], -1)

if chuc >= 0 and donvi >= 0:
   print(
10 * chuc + donvi)
else:
   print(
'Không tồn tại số')

Hàm (Function)

Cấu trúc để khai báo hàm trong Python:

def tenham(<danh_sach_bien>):

    <noi_dung_ham>

    return <ket_qua>

Ví dụ:

def square(x):
 
return x*x

for i in range(1,11):
  print(
f'{i} * {i} = {square(i)}')

Hàm trả về nhiều giá trị

Một hàm có thể trả về nhiều giá trị, danh sách các giá trị trả về ở được ngăn cách nhau bởi dấu phảy

Ví dụ:

def calcAreaAndPerimeter(width, height):
   S = width * height
   P =
2*(width + height)
   
return S, P

A, P = calcAreaAndPerimeter(
5, 4)
print(
'A = ', A)
print(
'P = ', P)

Trong trường hợp này, có thể hiểu kết quả trả về là một giá trị kiểu Tuple

Giá trị mặc định của tham số trong hàm

Khi khai báo hàm, có thể  khai báo giá trị mặc định cho các tham số. Khi gọi hàm, nếu một tham số bị thiếu nhưng đã được khai báo giá trị mặc định trong khai báo hàm thì tham số đó sẽ được truyền vào giá trị mặc định.

Ví dụ:

def getAddress(street, city, country='Việt Nam'):
   
return f'{street}, {city}, {country}'
        
print(getAddress(
'Ba Đình', 'Hà Nội'))

Module & làm việc với thư viện

Khi xây dựng một chương trình lớn, thông thường các chức năng sẽ được chia nhỏ thành các module để việc phát triển và bảo trì được dễ dàng.

Ví dụ, chương trình main.py như sau:

# main.py

def add2num(a,b):
   
return a + b
   
print(add2num(
1,2))

Khi chương trình lớn, thay vì việc để tất cả các hàm trong 1 file, chúng ta tạo các file nhỏ để chứa các hàm xử lý, ví dụ file add.py với nội dung sau:

# add.py

def add2num(a,b):
   
return a + b

Trong file main.py, chúng ta sử dụng lệnh import đến file add.py để gọi các hàm:

# main.py
import add

print(add.add2num(
1,2))

Ngoài việc import toàn bộ module như trên, có thể import từng hàm trong module với lệnh :

from <module> import <functions>

Ví dụ:

# main.py
from add import add2num

print(add2num(
1,2))

Ngoài ra, các biến khai báo trong module cũng có thể được import tương tự như các hàm.

Ví dụ :

# const.py
PI =
3.141592654

# main.py
from const import PI
print(PI/
2)

File I/O

Mở file

Lệnh mở file trong Python:

f = open(<file_name>, <mode>, encoding=<encoding>)

Trong đó :

Đọc file

data = f.read()

Nếu file được mở theo chế độ văn bản ('r'), kết quả đọc file là một biến string. Nếu file được mở theo chế độ nhị phân ('rb'), kết quả đọc file là một biến có kiểu dữ liệu bytes.

Nếu mở file theo chế độ văn bản ('r') có thể đọc nội dung file theo từng dòng với lệnh :

lines = f.readlines()

Kết quả trả về là một List, mỗi phần tử là một string chứa nội dung các dòng của file

Nếu mở file theo chế độ văn bản ('r') có thể đọc từng dòng của file với cấu trúc:

for line in f:
   
# process line

Ghi file:

f.write(<string>)

Ví dụ:

f = open('test.txt', 'w')
f.write(
'hello')
f.close()

f.write(<bytes>)

Ví dụ:

f = open('test.dat', 'wb')
f.write(
'hello'.encode())
f.close()

Đóng file

f.close()

Truy xuất file với cấu trúc 'with'

Sau khi mở file với lệnh open, phải đóng file với lệnh close để tránh bị thiếu nội dung file khi kết thúc chương trình. Để tránh việc quên đóng file sau khi mở, có thể dùng cấu trúc with:

with open(<file_name>, <mode>, <encoding>) as f:

    # Process file

Với cấu trúc with, file sẽ tự động đươc đóng khi chương trình chạy xong khối lệnh bên trong with.

Ví dụ:

with open('test.txt', 'w') as f:
   f.write(
'Line1\n')
   f.write(
'Line2\n')

Một số ví dụ minh họa

Ví dụ 1:

Đọc vào một file văn bản, tạo ra một file văn bản mới chứa các dòng của file nguồn, bỏ đi các dòng trống.

File input.txt:

 

Một năm có 365 hoặc 366 ngày

Năm thường có 365 ngày

 

Năm nhuận có 366 ngày

 

 

File output.txt:

 

Một năm có 365 hoặc 366 ngày

Năm thường có 365 ngày

Năm nhuận có 366 ngày

infile = open('input.txt', encoding='utf-8')
outfile = open(
'output.txt', 'w', encoding='utf-8')

for line in infile:
   
if line.strip() != '':
       outfile.write(line)

infile.close()
outfile.close()

Ví dụ 2:

Một file csv chứa bảng điểm một môn học của các học sinh một lớp học. Mỗi dòng của file là thông tin điểm của một học sinh bao gồm : họ tên, điểm hệ số 1, điểm hệ số 2, điểm hệ số 3, các giá trị này ngăn cách nhau bởi dấu phảy. Viết chương trình để đọc file csv đầu vào và tạo ra một file csv mới có thêm cột điểm trung bình.

File input.csv

Nguyễn Văn An, 8, 7, 8
Nguyễn Văn Bình, 6, 6, 8
Nguyễn Thị Chi, 8, 8, 9
Lê Văn Cường, 8, 7, 9
Phạm Thu Trang, 7, 8, 8

File output.csv

Nguyễn Văn An, 8, 7, 8,  7.7

Nguyễn Văn Bình, 6, 6, 8,  7.0

Nguyễn Thị Chi, 8, 8, 9,  8.5

Lê Văn Cường, 8, 7, 9,  8.2

Phạm Thu Trang, 7, 8, 8,  7.8

fi = open('input.csv', encoding='utf-8')
fo = open(
'output.csv', 'w', encoding='utf-8')

for line in fi:
   hoten, diemhs1, diemhs2, diemhs3 = line.split(
',')
   diemhs1 = int(diemhs1)
   diemhs2 = int(diemhs2)
   diemhs3 = int(diemhs3)
   diem_tb = (diemhs1 +
2*diemhs2 + 3*diemhs3) / 6
   fo.write(
f'{hoten}, {diemhs1}, {diemhs2}, {diemhs3}, {round(diem_tb,1)}\n')

fi.close()
fo.close()

Xử lý ngoại lệ

Ngoại lệ là các lỗi có thể phát sinh trong quá trình chạy chương trình. Ví dụ:

Khi một lỗi xảy ra, nếu không được xử lý, Python sẽ đưa ra thông báo lỗi và dừng chương trình.

Ví dụ:

print(int('a'))

Chương trình trên khi chạy sẽ đưa ra thông báo:

ValueError: invalid literal for int() with base 10: 'a'

Cách xử lý ngoại lệ trong Python :

try:
   
# do something
except:
   print(
"Exception occurred")

Khi ngoại lệ xảy ra, chương trình sẽ chạy vào khối lệnh bên dưới dòng except:, sau đó đoạn chương trình tiếp theo khối try/except vẫn tiếp tục được thực hiện.

Để xử lý riêng với từng loại ngoại lệ, có thể chỉ định rõ loại của ngoại lệ trong khai báo except:

try:
   
# do something
except ErrorType1:
   print(
"Error type 1")
except ErrorType2:
   print(
"Error type 2")
except:
   print(
"Other exception")

Ví dụ:

try:
   x = int(
'a')
except ValueError:
   print(
"Invalid input number")
except:
   print(
"Other exception")

Trong trường hợp muốn thực hiện một hành động sau khi kết thúc một công việc bất chấp ngoại lệ có xảy ra trong quá trình thực hiện công việc đó hay không, có thể dùng cấu trúc:

try:
   
# do something
except:
   print(
"Exception occurred")
finally:
   print(
'After try/except finished')

Virtual environment

Khi một chương trình Python thực hiện ở chế độ mặc định của hệ thống, nó có thể sử dụng toàn bộ các thư viện đã được cài đặt (thông qua lệnh import ) Tuy nhiên điều này cũng có thể gây ra xung đột vì một số chương trình yêu cầu phiên bản cụ thể của một số thư viện (trong khi phiên bản đã được cài trong hệ thống có thể cũ hoặc mới hơn phiên bản mong muốn của chương trình). Trong trường hợp đó, Python cung cấp khả năng làm việc với môi trường ảo (Virtual environment) để tránh tình trạng xung đột thư viện trên. Mỗi môi trường ảo sẽ có tập các thư viện độc lập với nhau và không chia sẻ với thư viện mặc định của hệ thống.

Tạo mới Virtual environment:

mkdir project1

cd project1

python -m venv .

Activate Virtual environment:

Script\activate        

Sau khi thực hiện lệnh activate trên, con trỏ dòng lệnh sẽ xuất hiện kí hiệu (project1) ở đầu:

(project1) C:<path_to_project_dir>

Khởi động Python ở cửa sổ dòng lệnh này và thử import một thư viện đã cài đặt trong hệ thống:

(project1) C:<path_to_project_dir> python

>>> import requests

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

ModuleNotFoundError: No module named 'requests'

Chương trình sẽ đưa ra thông báo lỗi không tìm thấy thư viện kể cả thư viện đã được cài trong hệ thống trước đó hay không. Để cài đặt thư viện riêng cho Virtual Environment, sử dụng lệnh :

pip install <module>

Trong trường hợp cần chỉ định chính xác phiên bản của thư viện cần cài đặt:

pip install <module>==<version>

Ví dụ:

pip install requests==2.21.0

Lập trình Python nâng cao

Lập trình hướng đối tượng

Khai báo Class trong Python

Trong Python, một Class thường có cấu trúc như sau:

class ClassName:
   
   
# Hàm khởi tạo đối tượng
   
def __init__(self, var1, var2, ...):
       self.var1 = var1
       self.var2 = var2
       ....
       
   
# Các method
   
   
def method1(self, ...)
       #
Ni dung hàm
       
   
def method2(self, ...)
       #
Ni dung hàm

       

Ví dụ:

class Person:
   
def __init__(self, name, address):
       self.name = name
       self.address = address
       
   
def sayHello(self):
       print(
f'Xin chào, tên tôi là {self.name}')
       
   
def updateAddress(self, newAddress):
       self.address = newAddress

 

Khởi tạo và sử dụng đối tượng

Cú pháp để khởi tạo một đối tượng của một Class:

obj = ClassName(var1, var2, …)

Để truy nhập đến phương thức của đối tượng:

obj.method1(...)

obj.method2(...)

Để truy nhập đến thuộc tính đối tượng:

obj.attr1

Ví dụ:

person = Person('Nguyễn Văn An', 'Hà Nội')
person.sayHello()
person.updateAddress(
'TP. HCM')
print(
f'{person.name} sống tại {person.address}')

Thừa kế

Để khai báo một class mới thừa kế từ một class đã có:

class ChildName(ParentClass):
   
    ....

Lớp con sẽ kế thừa các thuộc tính và phương thức của lớp cha.

Ngoài ra lớp con có thể “ghi đè” phương thức đã có ở lớp cha bằng cách khai báo một phương thức cùng tên.

Ví dụ:

class Person:
   
def __init__(self, name, address):
       self.name = name
       self.address = address
       
   
def sayHello(self):
       print(
f'Xin chào, tên tôi là {self.name}')    
       
   
def updateAddress(self, newAddress):
       self.address = newAddress

class Student(Person):
   
def __init__(self, name, address, schoolYear):
       self.name = name
       self.address = address
       self.schoolYear = schoolYear
       
   
def sayHello(self):
       print(
f'Xin chào, tên tôi là {self.name}, học khóa {self.schoolYear}')
   
student = Student(
'Nguyễn Văn An', 'Hà Nội', 2019)
student.sayHello()

Xử lý đa luồng

Thread

Thread được sử dụng để tạo ra một luồng xử lý song song với luồng xử lý chính của chương trình.

Ví dụ:

import time
from threading import Thread

def threadFunc():
   time.sleep(
2)
   print(
'Inside thread')
        
thr = Thread(target=threadFunc)
thr.start()

time.sleep(
5)
print(
'Inside main program')

Chương trình trên tạo ra một thread chạy song song với chương trình chính. Trong chương trình chính, khi gặp lệnh time.sleep(5),  chương trình sẽ  ngừng trong 5 giây mà không thực hiện hoạt động nào. Tuy nhiên, bên trong thread các hoạt động vẫn được thực hiện, do đó khi chạy chương trình chúng ta vẫn thấy lệnh print bên trong thread in dữ liệu ra màn hình trong khoảng 5 giây ngừng hoạt động của chương trình chính .

Có thể tạo nhiều thread một lúc, khi đó các thread sẽ chạy song song với nhau. Ví dụ:

from threading import Thread

def threadFunc(threadId):
   print(
f'Inside thread {threadId}')
   
for i in range(5):
   thr = Thread(target=threadFunc, args=(i,))
   thr.start()

Đồng bộ giữa các thread : Khi các thread chạy song song nhau, trình tự thực hiện các đoạn chương trình bên trong các thread sẽ không phụ thuộc lẫn nhau. Trong trường hợp cần đợi cho một thread thực hiện xong trước khi bắt đầu thực hiện các công việc khác, có thể sử dụng lệnh thread.join() như trong ví dụ sau:

import time
from threading import Thread

def threadFunc():
   time.sleep(
2)
   print(
'Inside thread')
        
thr = Thread(target=threadFunc)
thr.start()

time.sleep(
1)
thr.join()      
# đợi thread thực hiện xong

print(
'Thread completed.')
print(
'In main program')

Nhược điểm của thread:

Các đoạn code Python thông thường đều sử dụng GIL. Chỉ một số hàm của các thư viện của Python không sử dụng GIL như các hàm truy nhập IO (bàn phìm, File, Download, Upload qua internet …). Do đó về cơ bản, việc sử dụng nhiều Thread không giúp tăng tốc độ xử lý chương trình.

Process

Process cũng giống như Thread cho phép xử lý các tác vụ song song với nhau, nhưng cho phép các công việc được thực hiện đồng thời trên nhiều core CPU một lúc.

Ví dụ:

from multiprocessing import Process

def processFunc(pid):    
   s =
0
   
for i in range(10000000):
       s +=
1
       
   print(
f'Process id : {pid}, result : {s}')

if __name__ == '__main__':
   processes = []
   
   
for i in range(5):
       p = Process(target=processFunc, args=(i,))
       processes.append(p)
       p.start()
       
   
for p in processes:
       p.join()
   
   print(
'All process completed')

Nếu so với cách sử dụng Thread, đoạn chương trình trên sẽ thực hiện nhanh hơn vì các tác vụ tính toán được thực hiện trên nhiều core CPU cùng lúc.

Nhược điểm của Process:

Ví dụ sau minh họa sự khác nhau giữa Thread và Process :

from threading import Thread

a = [
0] * 5

def threadFunc(id):
   s =
0
   
for i in range(1000):
       s +=
1
       
   a[id] = s

if __name__ == '__main__':
   threads = []
   
   
for i in range(5):
       t = Thread(target=threadFunc, args=(i,))
       threads.append(t)
       t.start()
       
   
for thr in threads:
       thr.join()
       
   print(a)

Chương trình khi chạy sẽ in ra kết quả là  [1000, 1000, 1000, 1000, 1000], đúng như mong đợi

from multiprocessing import Process

a = [
0] * 5

def processFunc(id):    
   s =
0
   
for i in range(1000):
       s +=
1
       
   a[id] = s

if __name__ == '__main__':
   processes = []
   
   
for i in range(5):
       p = Process(target=processFunc, args=(i,))
       processes.append(p)
       p.start()
       
   
for p in processes:
       p.join()
   
   print(a)

Chương trình khi chạy sẽ in ra kết quả là [0, 0, 0, 0, 0]. Nguyên nhân là do biến a ở trong process là một biến mới, không phải biến a khai báo ở đầu chương trình.

Pool

Pool cũng giống như Process, tức cho phép các tác vụ xử lý được thực hiện trên nhiều core CPU. Pool thường được sử dụng thay cho Process khi cần tập hợp kết quả các Process với nhau vào một biến chung của chương trình chính (điều mà Process không làm được như ở ví dụ minh họa phía trên)

Ví dụ:

from multiprocessing import Pool

def poolFunc(id):
   print(
'Process id : ', id)
   s =
0
   
for i in range(1000):
       s +=
1
       
   
return s
   
if __name__ == '__main__':
   p = Pool(
5)
   a = p.map(poolFunc, list(range(
5)))
   print(a)

Làm việc với JSON

Trong Python, để chuyển đổi giữa Python Object thành JSON string và ngược lại, có thể sử dụng 2 hàm của thư viện json:

Ví dụ:

import json

students = [
   {
'name' : 'Nguyễn Văn An', 'address' : 'Hà Nội'},
   {
'name' : 'Nguyễn Thị Bình', 'address' : 'TP.HCM'},
]
print(json.dumps(students))

jsonstring =
'{"name" : "Nguyễn Văn An", "address" : "Hà Nội"}'
student = json.loads(jsonstring)
print(student[
'name'], student['address'])

Làm việc với XML

Chuyển từ xml sang object của python

import xml.etree.ElementTree as ET

xml =
"""
   <students>
       <student>
           <name>Nguyễn Văn An</name>
           <address>Hà Nội</address>
       </student>
       <student>
           <name>Nguyễn Thị Bình</name>
           <address>TP.HCM</address>
       </student>
   </students>
"""


root = ET.fromstring(xml)
for student in root:    
   name = student.find(
'name').text
   address = student.find(
'address').text
   print(name, address)

Chuyển object của Python sang xml

import xml.etree.cElementTree as ET

students = [
   {
'name' : 'Nguyễn Văn An', 'address' : 'Hà Nội'},
   {
'name' : 'Nguyễn Thị Bình', 'address' : 'TP.HCM'},
]


root = ET.Element(
"students")

for student in students:
   tag = ET.SubElement(root,
"student")
   ET.SubElement(tag,
"name").text = student['name']
   ET.SubElement(tag,
"address").text = student['address']

tree = ET.ElementTree(root)
tree.write(
"output.xml", encoding="UTF-8")

Làm việc với Base64

Encode base64:

import base64

msg =
'Xin chào'
msg_bytes = msg.encode(
'utf-8')
b64_msg_bytes = base64.b64encode(msg_bytes)
b64_msg = b64_msg_bytes.decode()

print(b64_msg)

Decode base64:

import base64

msg =
'WGluIGNow6Bv'
msg_bytes = msg.encode()
decoded_msg_bytes = base64.b64decode(msg_bytes)
decoded_msg = decoded_msg_bytes.decode(
'utf-8')

print(decoded_msg)

Làm việc với csv

Để đọc, ghi file csv, có thể sử dụng thư viện csv của Python.

Đọc file csv:

import csv

with open('input.csv', encoding='utf-8') as fi:
   reader = csv.reader(fi,delimiter=
',')
   rows = [row
for row in reader]
   
for row in rows:
   print(row)

Ghi file csv:

with open('output.csv', 'w', newline='',  encoding='utf-8') as fo:
   writer = csv.writer(fo, delimiter=
',')
   
for row in rows:
       writer.writerow(row)

Gọi API qua HTTP POST và GET

Để thực hiện gọi API qua HTTP POST/GET,  nên sử dụng thư viện requests:

pip install requests

Ví dụ 1 :

Lấy về nội dung một trang web:

import requests
html = requests.get(
'https://dantri.com.vn').text

with open('index.html', 'w', encoding='utf-8') as f:
   f.write(html)

Ví dụ 2:

Sử dụng requests để gọi API dịch vụ của các platform.

Các platform như Google cloud, Amazon, Microsoft đều cung cấp các dịch vụ như tìm kiếm, bản đồ, AI (dịch, xử lý ảnh, nhận dạng chữ viết ,...). Thông thường các platform đều hỗ trợ việc gọi service qua Restful API. Trong trường hợp đó, có thể sử dụng thư viện requests để thực hiện gọi API.

Ví dụ sau minh họa cách dùng dịch vụ của IBM Watson để thực hiện dịch một văn bản từ một ngôn ngữ sang một ngôn ngữ khác. Trước hết, để truy nhập được dịch vụ của Watson, cần đăng ký tài khoản (miễn phí) tại link https://cloud.ibm.com/registration :

Hình 11 . Đăng ký tài khoản IBM Watson

Sau khi đăng ký xong tài khoản, xác nhận tài khoản và đăng nhập vào Watson, từ thanh tìm kiếm gõ “Translator” và chọn “Language Translator-sj” :

Hình 12 . Tìm dịch vụ Language Translator trong Watson

Khi vào màn hình của Language Translator, thực hiện đăng ký dịch vụ free, sau đó vào tab Manage để xem thông tin truy nhập dịch vụ : bao gồm API Key & URL

Hình 13 . Xem thông tin truy nhập dịch vụ tại tab Manage

Để xem hướng dẫn cách gọi service, click vào “Getting started tutorial”, Watson sẽ chuyển qua trang hướng dẫn

Hình 14 . Hướng dẫn sử dụng service của Watson

Để sử dụng dịch vụ dịch văn bản, Watson hướng dẫn cách gọi qua curl như sau:

curl -X POST -u "apikey:{apikey}" --header "Content-Type: application/json" --data "{\"text\": [\"Hello, world! \", \"How are you?\"], \"model_id\":\"en-es\"}" "{url}/v3/translate?version=2018-05-01"

Trong đó {apikey} và {url} là các thông tin truy nhập dịch vụ đã có ở phía trên. Trên linux, sử dụng lệnh curl ở trên sẽ nhận được kết quả:

{

    "translations": [

        {

            "translation": "¡ Hola, mundo! "

        },

        {

            "translation": "¿Cómo estás?"

        }

    ],

    "word_count": 5,

    "character_count": 26

}

Trên windows, có thể dùng Postman để gọi dịch vụ với các thiết lập sau:

Hình 15 . Dùng postman để gọi service của Watson

Nếu muốn gọi service một cách tự động, hoặc gọi nhiều lần, cần sử dụng đến thư viện REST Client của các ngôn ngữ lập trình. Với python, việc này được thực hiện như ở chương trình dưới đây:

import requests
import json
from requests.auth import HTTPBasicAuth

API_KEY =
"<API_KEY>"   # sử dụng API_KEY từ trang thông tin truy nhập dịch vụ
URL =
"https://gateway.watsonplatform.net/language-translator/api"

data = {
"text": ["Hello, world! ", "How are you?"], "model_id":"en-es"}

response = requests.post(
f"{URL}/v3/translate?version=2018-05-01",
                   data=json.dumps(data),
                   auth=HTTPBasicAuth(
"apikey", API_KEY),
                   headers={
'Content-type': 'application/json'})
                   
result = json.loads(response.text)

for sentence in result['translations']:
   print(sentence[
'translation'])

Làm việc với các file cấu hình

File cấu hình (tên mở rộng .ini) chứa các thông tin khởi tạo trước khi bắt đầu chương trình. Một file cấu hình thường có nhiều Section, tên mỗi section được đặt trong cặp ngoặc [], bên dưới mỗi Section là các trường thông tin để ở dạng <key>=<value>. Ví dụ, file server.ini chứa thông tin về môi trường deploy server có dạng như sau:

[PROD]

port = 8080

log_level=WARN

[DEV]

port = 8081

log_level=DEBUG

Đọc file cấu hình:

import configparser

config = configparser.ConfigParser()
config.read(
'server.ini')

print(config[
'PROD']['port'])
print(config[
'DEV']['log_level'])

Ghi file cấu hình

import configparser

config = configparser.ConfigParser()
config[
'PROD'] = {'port' : 8080, 'log_level': 'WARN'}
config[
'DEV'] = {'port' : 8081, 'log_level': 'DEBUG'}

with open('server.ini', 'w') as f:
   config.write(f)

Làm việc với thư viện hệ thống (os)

Lấy tham số từ dòng lệnh

Để lấy tham số từ dòng lệnh, có thể sử dụng thư viện sys. Với một chương trình, danh sách tham số truyền vào dòng lệnh được lấy từ biến:

sys.argv : ~  một list string, mỗi phần tử là một tham số dòng lệnh

Trong đó sys.argv[0] luôn là tên file .py được chạy, sys.argv[1] trở đi là các tham số truyền vào chương trình.

Ví dụ :

import sys

for arg in sys.argv:
  print(arg)

Thao tác với file và thư mục

Các lệnh thường dùng khi thao tác với file và thư mục:

import os
print(os.getcwd())

import os
os.chdir(
'C:\\Python37')

import os
os.mkdir(
'new_dir')

import os

dir_name =
'C:\\'
file_name =
'test.txt'
file_path = os.path.join(dir_name, file_name)

print(file_path)

import os

file_path =
'C:\\test.txt'
file_name = os.path.basename(file_path)

print(file_name)

import os

file_name =
'test.txt'
name, ext_name = os.path.splitext(file_name)

print(name, ext_name)

import os

file_path =
'C:\\test.txt'

if os.path.exists(file_path):
  print(
f'File {file_path} có tồn tại')
else:
  print(
f'File {file_path} không tồn tại')

import os

stat = os.stat(
'C:\\test.txt')
print(
f'File size : {stat.st_size}, Created time : {stat.st_ctime}, Modified time : {stat.st_mtime}')

import os

file_path =
'C:\\test.txt'

if os.path.exists(file_path):
  os.remove(file_path)

import os

dir =
'.'
items = os.listdir(dir)
files = [f
for f in items if os.path.isfile(os.path.join(dir, f))]
subdirs = [d
for d in items if os.path.isdir(os.path.join(dir, d))]

import os

dir =
'.'

for root, dirs, files in os.walk(dir):
  print(
'Thư mục : ', os.path.join(dir, root))
 
  print(
' + Danh sách file : ')    
 
for name in files:
      print(os.path.join(dir, root, name))
     
  print(
' + Danh sách thư mục con : ')
 
for name in dirs:
      print(os.path.join(dir, root, name))

Chạy chương trình của hệ thống

import os
os.system(
'calc.exe')

import subprocess
output = subprocess.getoutput(
"dir")
print(output)

import subprocess
import time

proc = subprocess.Popen(
'ping 127.0.0.1', stdout=subprocess.PIPE)

while True:
  time.sleep(
1)
  poll = proc.poll()    
 
 
if poll == None:            # program is running
     
while True:
          line = proc.stdout.readline().decode()
         
         
if line:
              print(line)
         
else:
             
break
 
else:                       # program completed
      print(
'Subprocess completed.')
     
break

Kết nối Client-Server qua TCP/IP

TCP-IP là giao thức cho phép kết nối giữa nhiều Client và một Server. Khi kết nối được hình thành, Client và Server có thể trao đổi dữ liệu với nhau qua các packet. Quá trình kết nối giữa client và server được diễn ra theo các bước sau:

Ví dụ kết nối TCP/IP với Python:

import socket

HOST =
'127.0.0.1'
PORT =
8080

s = socket.socket()
s.bind((HOST, PORT))
s.listen()

print(
f'Listen at {HOST}:{PORT}')

while True:
  conn, addr = s.accept()
  print(
'Connected from ', addr)
  data = conn.recv(
4096)
  clientID = data.decode()
  conn.send(
f'Hello {clientID}'.encode())
  conn.close()

import socket

HOST =
'127.0.0.1'
PORT =
8080
CLIENT_ID =
'Client1'

s = socket.socket()

s.connect((HOST, PORT))
s.send(CLIENT_ID.encode())
data = s.recv(
4096)
print(data.decode())
s.close()  

Gửi email tự động

Việc gửi mail trong Python được thực hiện nhờ thư viện smtp

import smtplib, ssl
from email.mime.text import MIMEText

port =
465  # For SSL
smtp_server =
"smtp.gmail.com"
sender_email =
"from@gmail.com"
receiver_email =
"to@gmail.com"
password = input(
"Type your password and press enter: ")

message =
"""        
       Sample Email sent by Python.
       """


msg = MIMEText(message)
msg[
'Subject'] = 'Mail subject'
msg[
'From'] = 'from'
msg[
'To'] = 'to'


context = ssl.create_default_context()

with smtplib.SMTP_SSL(smtp_server, port, context=context) as server:
   server.login(sender_email, password)
   server.sendmail(sender_email, receiver_email, msg.as_string())

Lưu ý : Để gửi mail tự động được với gmail cần bật tính năng cho phép ứng dụng ít bảo mật của google.

Unit Test

Việc viết Unit Test là tương đối quan trọng vì đảm bảo các chức năng cơ bản thực hiện theo đúng logic đã được thiết kế, giảm trừ các lỗi phát sinh trong quá trình phát triển sản phẩm về sau.

Trong Python, việc viết Unit Test thường sử dụng thư viện unittest, với lớp đối tượng unittest.TestCase

Ví dụ:

import unittest

def add(a, b):
   
return a + b

class TestCase1(unittest.TestCase):

   
def test1(self):
       self.assertEqual(add(
1, 2), 3)

   
def test2(self):
       self.assertTrue(add(
1, 2) == 3)
       self.assertFalse(add(
1, 2) > 3)

if __name__ == '__main__':
   unittest.main()

Kết nối cơ sở dữ liệu MySQL

Cài đặt thư viện :

Lựa chọn theo đúng phiên bản Python và kiến trúc cpu (32 bit/64bit) của phiên bản Python đã cài đặt.

Mở cửa sổ cmd, di chuyển vào trong thư mục chứa file thư viện đã download, gõ lệnh sau để cài đặt thư viện:

pip install <tên file thư viện đã download>

Sử dụng thư viện để kết nối với database:

import MySQLdb

db = MySQLdb.connect(
"localhost", "admin", "1234", "testdb" )

print(
'Connected to database')

Các tham số cần truyền vào để kết nối đến database theo thứ tự là : host, username, password, dbname

Tạo mới bảng trong database

import MySQLdb

db = MySQLdb.connect(
"localhost", "admin", "1234", "testdb" )

cursor = db.cursor()

cursor.execute(
"DROP TABLE IF EXISTS STUDENT")

cursor.execute(
"""CREATE TABLE STUDENT (
        id INT NOT NULL AUTO_INCREMENT,
        name  VARCHAR(50),
        address  VARCHAR(100),
        PRIMARY KEY(id))"""
)

db.close()

Thêm mới bản ghi

import MySQLdb

db = MySQLdb.connect(
"localhost", "admin", "1234", "testdb" )

cursor = db.cursor()

try:
  cursor.execute(
'''INSERT INTO STUDENT(name,address)
                    VALUES ('Nguyen Van An', 'Ha Noi')'''
)
                                        
  cursor.execute(
'''INSERT INTO STUDENT(name,address)
                    VALUES ('Nguyen Thi Binh', 'TP.HCM')'''
)
                                        
  db.commit()
except Exception as e:
  print(str(e))
  db.rollback()

db.close()

Đọc dữ liệu

import MySQLdb

db = MySQLdb.connect(
"localhost", "admin", "1234", "testdb" )

cursor = db.cursor()

try:
   cursor.execute(
"SELECT name, address FROM STUDENT")
   results = cursor.fetchall()

   
for name, address in results:
            print(name, address)
                
except Exception as e:
  print(str(e))

db.close()

Lập trình web với flask

Cài đặt flask

pip install flask

>> import flask

>> print(flask.__version__) 

Tạo mới ứng dụng flask

Để bắt đầu xây dựng ứng dụng với flask, tạo một file app.py có nội dung như dưới đây:

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
   
return 'Hello World'

if __name__ == '__main__':
   app.run()

Khởi động ứng dụng như một chương trình python bình thường:

python app.py

Từ cửa sổ trình duyệt (Edge/Chrome/Firefox) truy nhập ứng dụng theo địa chỉ http://localhost:5000 :

Hình 16. Truy nhập ứng dụng flask tại địa chỉ http://localhost:5000

Thay đổi địa chỉ và cổng của server:

Khi một ứng dụng web chạy, server sẽ lắng nghe tại một địa chỉ (IP) và cổng (Port). Với flask, ứng dụng sẽ sử dụng địa chỉ IP mặc định là 127.0.0.1 (localhost) và port mặc định là 5000. Để thay đổi các giá trị IP và port này, có thể sử dụng các tham số truyền vào từ hàm khởi tạo ứng dụng:

app.run(host='<IP>', port=<port>)

Các tham số:

Ví dụ:

app.run(host='0.0.0.0', port=8080)

Tạo mới một endpoint

Khi truy nhập ứng dụng web theo url (nhập vào trình duyệt), mỗi endpoint tương ứng với phần sau của url truy nhập (sau khi đã bỏ đi IP và port). Mỗi endpoint sẽ thực hiện một chức năng khác nhau và trả về kết quả tương ứng cho người dùng.

Để khai báo endpoint trong flask, sử dụng kí hiệu @app.route('<endpoint>'), ví dụ:

from flask import Flask
app = Flask(__name__)

@app.route('/hello')
def hello_world():
   
return 'Hello World'

if __name__ == '__main__':
   app.run()

Sau khi chạy chương trình trên, nếu từ trình duyệt truy nhập vào địa chỉ http://localhost:5000 sẽ gặp lỗi Not found, do endpoint mặc định '/' đã không được khai báo trong chương trình. Để sử dụng ứng dụng, phải truy nhập vào địa chỉ của endpoint mới là : http://localhost:5000/hello

Một ứng dụng flask có thể khai báo nhiều endpoint để phục vụ các chức năng khác nhau cho người dùng truy nhập đến:

from flask import Flask
app = Flask(__name__)

@app.route('/')
def homePage():
   
return 'Home page'
   
@app.route('/hello')
def hello_world():
   
return 'Hello World'

@app.route('/goodbye')
def goodbye():
   
return 'Goodbye, see you later.'
   
if __name__ == '__main__':
   app.run()

Với ứng dụng trên, có thể truy nhập đến các URL tương ứng với các endpoint đã khai báo : http://localhost:5000, http://localhost:5000/hello, http://localhost:5000/goodbye

Lấy tham số từ request:

Trong ứng dụng web, với cùng một endpoint truy nhập, người dùng vẫn có thể truyền lên server các tham số khác nhau. Có thể hiểu đây là các tham số sẽ được truyền vào hàm xử lý của endpoint. 

Để thực hiện truyền tham số từ trình duyệt (client) lên server, có 2 phương thức truyền:

http://localhost:5000/hello?name=NVA&address=Hanoi 

Các cặp giá trị ngăn cách nhau bởi kí tự &, toàn bộ phần tham số được ngăn cách với URL gốc của endpoint bằng dấu ?

<form action='http://localhost:5000/hello' method='POST'>
   <
p>Name : <input name='name'></p>
   <
p>Address : <input name='address'></p>
   <
input type='submit'>
</
form>

Để lấy tham số do client truyền lên endpoint xử lý của flask, trước hết cần khai báo sử dụng module request của flask :

from flask import Flask, request

Với GET method:

Các tham số do client gửi lên server được lấy qua đối tượng request.args , đối tượng này có kiểu dữ liệu là dictionary. Ví dụ:

from flask import Flask, request
app = Flask(__name__)

@app.route('/hello')
def hello_world():
   name = request.args.get(
'name', '')
   
return f'Hello {name}'
   
if __name__ == '__main__':
   app.run()

Để sử dụng ứng dụng trên, truyền tham số name vào phía cuối của URL truy nhập endpoint, ví dụ:

http://localhost:5000/hello?name=Nguyễn+Văn+An

Với POST method:

Các tham số do client gửi lên server được lấy qua đối tượng request.form , đối tượng này có kiểu dữ liệu là dictionary. Ví dụ:

from flask import Flask, request
app = Flask(__name__)

@app.route('/hello', methods=['POST'])
def hello_world():
   name = request.form.get(
'name', '')
   
return f'Hello {name}'
   
if __name__ == '__main__':
   app.run()

Để sử dụng ứng dụng trên, tạo mới một file test.html với nội dung sau:

<form action='http://localhost:5000/hello' method='POST'>
   <
p>Name : <input name='name'></p>
   <
input type='submit'>
</
form>

Dùng trình duyệt mở file test.html trên (Từ File Explorer → Open with → Edge/Chrome/FireFox), trong màn hình ứng dụng trên trình duyệt, nhập giá trị cho trường input, sau đó chọn submit :

Hình 17. Mở file html bằng trình duyệt

Sau khi submit xong, trình duyệt sẽ nhận được kết quả do server trả về:

Hình 18. Kết quả do server trả về sau khi submit form

Với POST method và mã hóa dữ liệu dạng json

Thông thường mã hóa dữ liệu khi gọi POST method có dạng chuẩn là form-data. Tuy nhiên, trong một số trường hợp, ví dụ giao tiếp giữa các server hoặc gọi service từ client theo dạng AJAX thì dạng mã hóa dữ liệu thường sử dụng là JSON. Trong trường hợp này để lấy tham số trong request, chúng ta thay đối tượng request.form bằng đối tượng request.json :

from flask import Flask, request
app = Flask(__name__)

@app.route('/hello', methods=['POST'])
def hello_world():
   name = request.json.get(
'name', '')
   
return f'Hello {name}'
   
if __name__ == '__main__':
   app.run()

Để gọi ứng dụng trên, có thể dùng curl hoặc postman :

Sử dụng curl :

curl -X POST -H "Content-Type: application/json" --data "{\"name\":\"Nguyễn Văn An\"}" http://localhost:5000/hello

Sử dụng Postman:

Hình 19. Sử dụng postman để gọi POST method của flask

Trường hợp tổng quát, một endpoint có thể hỗ trợ nhiều phương thức (GET/POST) và nhiều dạng mã hóa dữ liệu, để lấy tham số từ request, chúng ta cần kiểm tra phương thức & loại mã hóa dữ liệu mà client gửi tới server:

from flask import Flask, request
app = Flask(__name__)

@app.route('/hello', methods = ['POST', 'GET'])
def hello_world():
   
if request.method == 'POST':
       
if 'name' in request.form:
           name = request.form[
'name']
           
return f'Hello {name} from POST method (form-data)'
                        
       
elif 'name' in request.json:
           name = request.json[
'name']
           
return f'Hello {name} from POST method (JSON)'       
                        
       
else:
           
return 'Hello from POST method'
   
else:
       name = request.args.get(
'name', '')
       
return f'Hello {name} from GET method'

if __name__ == '__main__':
   app.run()

Mapping tham số trong URL

Ngoài cách lấy tham số từ request, có thể mapping tham số trong khai báo url. Sử dụng cách này, tham số do client gửi lên sẽ được chuyển thành tham số trong hàm xử lý của endpoint. Ví dụ:

from flask import Flask
app = Flask(__name__)

@app.route('/hello/<name>')
def hello(name):
 
return f'Hello {name}'
 
if __name__ == '__main__':
  app.run(debug =
True)

Để sử dụng ứng dụng, truyền tham số name vào cuối URL theo cấu trúc : http://localhost:5000/hello/<name>,  ví dụ:

http://localhost:5000/hello/Nguyễn Văn An

Sử dụng template

Khi endpoint trả về kết quả dưới dạng string, nếu client là trình duyệt thì nội dung này sẽ được hiện lên trong cửa sổ trình duyệt để người dùng xem. Trên thực tế, nội dung trả về thường khá dài (kèm theo style và format) do đó phía server sẽ không trả về kết quả dưới dạng một biến string, mà trả về nội dung thông qua các file template với tên mở rộng html.

Thư mục chứa template có tên là templates và đặt ngang hàng với file chạy ứng dụng (app.py) , ví dụ:

app.py

templates/

    Index.html

Chương trình sử dụng template để trả về nội dung html cho client:

from flask import Flask, render_template
app = Flask(__name__)

@app.route('/')
def hello():
 
return render_template('index.html')

if __name__ == '__main__':
  app.run()

File index.html đặt trong thư mục templates và chứa nội dung muốn trả về cho client, ví dụ:

<html>
   <
body>
       <
b>Hello, world!</b>
   </
body>
</
html>

Truyền biến từ hàm xử lý endpoint xuống template

Trong hàm xử lý endpoint thường thực hiện một số tính toán (truy nhập database, file, …), sau đó sẽ tạo ra một số biến để chứa dữ liệu kết quả. Các biến này cần được truyền xuống template để tạo ra nội dung tùy biến cho mỗi lần request khác nhau từ người dùng. Việc truyền biến từ hàm xử lý endpoint (còn gọi là controller), xuống các file template (còn gọi là lớp view), được thực hiện bằng cách truyền biến trực tiếp vào hàm render_template. Ví dụ :

from flask import Flask, render_template
app = Flask(__name__)

@app.route('/hello/<user>')
def hello_name(user):
 
return render_template('index.html', user=user)

if __name__ == '__main__':
  app.run()

Trong template, nội dung các biến từ server truyền xuống sẽ được đặt trong cặp dấu {{ }}, ví dụ file index.html ứng với chương trình phía trên cần có nội dung sau:

<html>
   <
body>
       <
b>Hello, {{ user }}!</b>
   </
body>
</
html>

Để sử dụng ứng dụng trên, từ trình duyệt truy nhập đến địa chỉ http://localhost:5000/hello/<name> , chương trình sẽ hiện ra lời chào theo tham số <name> đã truyền vào.

Ngoài cấu trúc lấy giá trị biến được truyền xuống template như trên, trong template còn có các cấu trúc lệnh tương tự với chương trình python thông thường. Hai cấu trúc thường xuyên sử dụng là :

Cấu trúc điều khiển if:

{% if <cond> %}

    <html content>

{% else %}

    <html content>

{% endif %}

Cấu trúc lặp for:

{% for item in item_list %}

    {{ item }}

{% endfor %}

Ví dụ : Chương trình dưới đây trả về một danh sách sinh viên trong nội dung hiển thị:

from flask import Flask, render_template
app = Flask(__name__)

@app.route('/get_students')
def getStudents():
  students = [
            {
'id' : '100001', 'name' : 'Nguyễn Văn An', 'address' : 'Hà Nội'},
            {
'id' : '100002', 'name' : 'Nguyễn Thị Bình', 'address' : 'TP. HCM'}
           ]
           
 
return render_template('students.html', students=students)
 
if __name__ == '__main__':
  app.run(debug =
True)

File app.py

{% if students %}
  <
table border='1'>
      <
tr>
           <
th>Id</th>
           <
th>Name</th>
           <
th>Address</th>
      </
tr>
      {% for student in students %}
          <
tr>
               <
td> {{ student.id }} </td>
               <
td> {{ student.name }} </td>
               <
td> {{ student.address }} </td>
          </
tr>
      {% endfor %}
  </
table>
{% else %}
  No students
{% endif %}

File templates/students.html

Truy nhập ứng dụng từ trình duyệt với địa chỉ http://localhost:5000/get_students, màn hình hiển thị sẽ có dạng như ở dưới đây:

Hình 20. Màn hình ứng dụng sử dụng các cấu trúc điều khiển & lặp trong template để tạo nội dung hiển thị

Chuyển hướng trang với redirect và abort

Chuyển hướng với redirect

Khi đang trong một endpoint, thay vì chạy đến hết quá trình xử lý mà chúng ta muốn chuyển qua một url khác thì có thể sử dụng hàm redirect . Hàm này nhận tham số là url cần chuyển hướng tới. Ví dụ :

from flask import Flask, request, redirect, url_for
app = Flask(__name__)

@app.route('/hello/<name>')
def hello(name):
   
return f'Hello {name}'

@app.route('/')
def homePage():
   user = request.args.get(
'user', 'world')
   
return redirect(url_for('hello', name=user))

if __name__ == '__main__':
  app.run()

Trong chương trình trên, hàm url_for của flask có tác dụng tạo ra url dựa trên tên hàm của một endpoint xử lý và các tham số đi kèm với hàm.

Truy nhập ứng dụng tại địa chỉ : http://localhost:5000?user=<user>, chương trình sẽ được chuyển qua địa chỉ http://localhost:5000/hello/<user>

Chuyển hướng với abort

Trong trường hợp xảy ra lỗi trong hàm xử lý endpoint, để kết thúc nhanh chương trình, có thể dùng hàm abort, kèm theo mã lỗi. Ví dụ :

from flask import Flask, request, redirect, url_for
app = Flask(__name__)

@app.route('/hello')
def hello():
   
if 'name' not in request.args:
       abort(
404)
       
   name = request.args[
'name']
   
return f'Hello {name}'   

if __name__ == '__main__':
  app.run()

Ở ứng dụng trên, nếu truy nhập vào địa chỉ http://localhost:5000/hello?name=<name> , chương trình sẽ đưa ra lời chào theo tham số name đã truyền vào. Nếu truy nhập vào địa chỉ http://localhost:5000/hello, chương trình sẽ không xác định được tham số name và đưa ra thông báo lỗi.

Upload file

Ở html form, để thực hiện upload file, cần thêm thuộc tính enctype='multipart/form-data'. Ở phía server, trong hàm xử lý endpoint, các file do client upload lên có thể lấy được từ đối tượng request.files. Đối tượng này có kiểu dữ liệu là dictionary.

Ví dụ:

from flask import Flask, request, render_template
app = Flask(__name__)

@app.route('/upload', methods=['POST'])
def uploadFile():
   file = request.files.get(
'file')
   
if file and file.filename != '':
       file.save(file.filename)
   
return 'File uploaded'   

@app.route('/')
def homePage():
   
return render_template('index.html')
   
if __name__ == '__main__':
  app.run()

File app.py

<form method='POST' enctype='multipart/form-data' action='/upload'>
   <
input type='file' name='file'>
   <
input type='submit'>
</
form>

File templates/index.html

Để sử dụng ứng dụng trên,  truy nhập địa chỉ http://localhost:5000, màn hình upload file sẽ hiện lên :

Hình 21. Màn hình upload file

Sau khi chọn file và submit form, chương trình sẽ đưa ra thông báo file đã upload thành công:

Hình 22. Thông báo file đã upload thành công

File do client gửi lên sẽ được lưu trong thư mục chạy ứng dụng (thư mục chứa file app.py)

Truy nhập database với flask_sqlalchemy

Flask_sqlalchemy (dựa trên sql_alchemy) là thư viện cho phép truy nhập database theo dạng ORM (Object relational mapping). Khi sử dụng ORM, việc truy nhập database được thực hiện nhờ các lệnh python và mỗi đối tượng trong database được thể hiện bằng một Object class của python.

Cài đặt flask_sqlalchemy :

pip install flask_sqlalchemy 

Cấu hình database cho flask_sqlalchemy

Việc cấu hình database được thực hiện bằng lệnh:

app.config['SQLALCHEMY_DATABASE_URI'] = '<db_url>'

db = SQLAlchemy(app)

Trong đó db_url là url chứa thông tin về database sử dụng (engine, IP, port, user, password). Một số URL tương ứng với các database engine thông dụng:

Ngoài Sqlite thì với các engine database khác, cần cài đặt thư viện database client tương ứng để có thể kết nối được với database. Các thư viện cần cài đặt:

Khai báo model với flask_sqlalchemy

Các model database trong flask_sqlalchemy cần kế thừa class db.Model trong đó db là biến đã được khai báo trong phần cấu hình database ở bước trước. Ví dụ:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config[
'SQLALCHEMY_DATABASE_URI'] = 'sqlite:///students.db'
db = SQLAlchemy(app)


class Student(db.Model):
   id = db.Column(db.Integer, primary_key =
True)
   studentNo = db.Column(
'student_no', db.String(20), unique=True)
   studentName = db.Column(
'student_name', db.String(50))
   address = db.Column(
'address', db.String(50))
 
   
def __init__(self, studentNo, studentName, address):
       self.studentNo = studentNo
       self.studentName = studentName
       self.address = address

Ở chương trình trên, chúng ta khai báo một model Student với các trường:

Các thao tác truy nhập database với flask_sqlalchemy

Tạo mới các bảng dữ liệu

>>> from app import db

>>> db.create_all()

Tạo mới bản ghi

>>> from app import Student

>>> student1 = Student('101', 'Nguyễn Văn An', 'Hà Nội')

>>> student2 = Student('102', 'Nguyễn Thị Bình', 'TP.HCM')

>>> db.session.add(student1)

>>> db.session.add(student2)

>>> db.session.commit()

Lấy toàn bộ các bản ghi trong một bảng

>>> students = Student.query.all()

>>> for student in students:

>>>     print(student.id, student.studentNo, student.studentName, student.address)

Tìm kiếm bản ghi theo id

>>> student1 = Student.query.get(1)

>>> print(student1.id, student1.studentNo, student1.studentName, student1.address)

Chỉnh sửa bản ghi

>>> student1 = Student.query.get(1)

>>> student1.address = 'TP. Hà Nội'

>>> db.session.commit()

Xóa bản ghi

>>> student1 = Student.query.get(1)

>>> db.session.delete(student1)

>>> db.session.commit()

Lập trình Django cơ bản

Cài đặt django

pip install django

Từ cửa sỗ cmd, chạy python, sau đó kiểm tra phiên bản django đã cài đặt với các lệnh:

>> import django

>> print(django.get_version())

Tạo mới project với Django

django-admin startproject mysite

Project mới sẽ được tạo ra trong thư mục mysite với các file như sau:

mysite/

    manage.py

    mysite/

        __init__.py

        settings.py

        urls.py

        wsgi.py

Từ cửa sổ cmd, di chuyển vào trong thư mục project (mysite) và khởi động server với lệnh:

cd mysite

python manage.py runserver

Hình 23 . Khởi động server django

Theo mặc định, server sẽ lắng nghe tại địa chỉ http://127.0.0.1:8000

Hình 24. Truy cập ứng dụng web django tại địa chỉ http://127.0.0.1:8000

Để thay đổi địa chỉ ip và cổng mà server lắng nghe, có thể dùng lệnh:

python manage.py runserver <ip:port>

     Ví dụ:

python manage.py runserver 8080          # nghe tại cổng 8080

python manage.py runserver 0.0.0.0:8080  # nghe tại cổng 8080, tất cả ip

python manage.py runserver 0:8080        # nghe tại cổng 8080, tất cả ip

Tạo mới và cấu hình một ứng dụng

Mỗi project django thường được chia thành nhiều ứng dụng nhỏ (app). Để tạo mới một ứng dụng trong một project django, sử dụng lệnh:

python manage.py startapp <app>

Trong đó <app > là tên ứng dụng muốn tạo mới. Sau khi được tạo mới, cấu trúc thư mục ứng dụng như sau:

app/

   admin.py

   apps.py

   migrations/

      __init__.py

   models.py

   tests.py

   views.py

   __init__.py

Các thành phần trong ứng dụng:

Ngoài các file trên, cần tạo thêm một thư mục với tên templates đặt trong thư mục ứng dụng để chứa các file tempate html cho tầng view. Cấu trúc thư mục ứng dụng sẽ như sau:

app/

   admin.py

   apps.py

   migrations/

      __init__.py

   models.py

   tests.py

   views.py

   __init__.py

   templates/

      *.html

Sau khi tạo xong ứng dụng, cần sửa file settings.py của project để thêm khai báo tên ứng dụng vào mục INSTALLED_APPS :

INSTALLED_APPS = [

    'app',  # new

        ….

Cấu hình database :

Database engine và các thông tin kết nối sử dụng trong project hiện tại được lưu trong phần DATABASES của file settings.py.Theo mặc định, database engine được sử dụng là sqlite, toàn bộ database của project được chứa trong file db.sqlite3 nằm trong thư mục gốc của project:

DATABASES = {

    'default': {

        'ENGINE': 'django.db.backends.sqlite3',

        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),

    }

}

Để chuyển sang sử dụng các database engine khác, trước hết cần cài đặt driver cho database engine. Ví dụ, cài đặt driver cho MySQL :

pip instal MySQLdb

Sau đó bổ sung các thông tin kết nối database trong file settings.py :

DATABASES = {
   
'default': {
       
'ENGINE': 'django.db.backends.mysql',
       
'NAME': 'testdb',
       
'USER': 'admin',
       
'PASSWORD': '1234',
       
'HOST': 'localhost',
       
'PORT': '',
   }
}

Sau khi cấu hình database xong, thực hiện khởi tạo database cho ứng dụng với lệnh:

python manage.py migrate

ORM tương tác với Database

Django sử dụng ORM để tương tác với Database. Các model Database được khai báo trong file models.py của ứng dụng và cần kế thứa lớp django.db.models.Model.

Ví dụ:

from django.db import models

class Student(models.Model):

   studentNo = models.CharField(db_column=
'student_no',  max_length=20, unique=True)                                
   studentName = models.CharField(db_column=
'student_name', max_length=50)                                    
   address = models.CharField(db_column=
'address', max_length=100)

Khai báo một đối tượng trong file models.py

Sau khi khai báo đối tượng xong, để update các thay đổi mới vào database, cần thực hiện lệnh:

python manage.py makemigrations app

python manage.py migrate

Sau khi thực hiện lệnh trên, django sẽ tạo ra các bảng trong database như sau:

Hình 25. Các bảng được django tạo ra sau khi thực hiện lệnh migrate

Việc truy xuất đến database được thực hiện thông qua lớp đại diện Student (Object Relational Mapper). Để test việc truy xuất dữ liệu từ cửa sổ cmd, có thể sử dụng lệnh:

python manage.py shell

Một số thao tác truy xuất database cơ bản:

from app.models import Student

Student.objects.all()

Student.objects.create(studentNo='10001', studentName='Nguyễn Văn An', address='Hà Nội')

student = Student.objects.get(pk=1)

student = Student.objects.get(pk=1)

student.address = 'TP.Hà Nội'

student.save()

students = Student.objects.filter(address='TP.Hà Nội', studentName__startswith='Nguyễn')

Với các trường dữ liệu String, ngoài phép so sánh giống nhau ( <field>=<value> ), có thể sử dụng các phép so sánh:

students = Student.objects.filter(address='TP.Hà Nội').order_by('studentNo')   # ASC

students = Student.objects.filter(address='TP.Hà Nội').order_by('-studentNo')  # DESC

students = Student.objects.raw('Select * from app_student where address=%s and student_name LIKE %s', ['TP.Hà Nội', '%Nguyễn%'])

student = Student.objects.get(pk=1)

student.delete()

Mô hình MVC trong Django

Các thành phần trong mô hình MVC của Django như sau:

        

Hình 26. Các thành phần trong mô hình MVC của ứng dụng Django

Các bước để xây dựng một trang theo mô hình MVC

Khai báo url:

Trước hết cần khai báo địa chỉ url cho trang. Đây là địa chỉ sẽ được dùng để truy nhập đến trang. Việc khai báo được thực hiện trong file urls.py của project, tuy nhiên để dễ quản lý, thường tạo mới file urls.py trong thư mục của từng app, sau đó include file urls.py của app vào trong file urls.py của projects.

from django.contrib import admin
from django.urls import path, include  # new

urlpatterns = [
   path(
'app/', include('app.urls')),  # new
   path(
'admin/', admin.site.urls),
]

File urls.py của project (mysite/urls.py)

from django.urls import path

from . import views

urlpatterns = [
   path(
'hello', views.hello),
]

File urls.py của ứng dụng (app/urls.py)

Tạo hàm xử lý trong views.py:

from django.shortcuts import render

def hello(request):
   
return render(request, 'hello.html')

Tạo hàm xử lý trong views.py

Tạo template html:

Tạo file hello.html trong thư mục app/templates với nội dung sau:

Hello world!

File hello.html (trong thư mục app/templates)

Khởi động server, truy nhập địa chỉ http://localhost:8000/app/hello

Hình 27. Màn hình một trang được tạo theo mô hình MVC của django

Cách truyền biến từ controller xuống template, ngôn ngữ template

Các biến từ controller truyền xuống template sẽ lưu trong biến context truyền vào hàm render trong phần controller:

from django.shortcuts import render

def hello(request):
   context = {
'name' : 'world'}
   
return render(request, 'hello.html', context=context)

File views.py : Truyền biến từ controller xuống template qua context

Hello {{ name }}!

File hello.html : Sử dụng biến từ controller truyền xuống trong file template

Để sử dụng biến do controller truyền xuống trong file template, cần sử dụng các cấu trúc điều khiển của ngôn ngữ template của django. Một số cấu trúc thông dụng của ngôn ngữ template:

{{ bien_so }}

{% if <dieu_kien> %}

    <html>

{% else %}

    <html>

{% endif %}

{% for item in item_list %}

    {{ item }}

{% endfor %}

Ví dụ sau minh họa cách tạo một trang hiển thị danh sách học sinh trong hệ thống:

from django.urls import path

from . import views

urlpatterns = [
   path(
'list_student', views.listStudent),
]

 File app/urls.py

from django.shortcuts import render
from .models import Student

def listStudent(request):
   student_list = Student.objects.all()
   
   context = {
       
'student_list': student_list,
   }
   
return render(request, 'list_student.html', context)

File app/views.py

{% if student_list %}
   <
table border=1>
       <
tr>
           <
th>Student Number</th>
           <
th>Student Name</th>
           <
th>Address</th>
       </
tr>
   {%
for student in student_list %}    
       <
tr>
           <
td> {{ student.studentNo }} </td>
           <
td> {{ student.studentName }} </td>
           <
td> {{ student.address }} </td>
       </
tr>        
   {%
endfor %}
   </
table>
{%
else %}
   <
p>No student available.</p>
{%
endif %}

File app/templates/list_student.html

Mapping tham số trong url

Tham số về chi tiết đối tượng (thường là id) có thể được mapping trong đường dẫn của url.

Ví dụ:

from django.urls import path

from . import views

urlpatterns = [
   path(
'student/<int:studentId>', views.studentDetail),
]

 File app/urls.py

from django.shortcuts import render, get_object_or_404
from .models import Student

def studentDetail(request, studentId):
   student = get_object_or_404(Student, pk=studentId)
   
   context = {
       
'student': student,
   }
   
return render(request, 'student_detail.html', context)

File app/views.py

<p> Student Number : {{ student.studentNo }} </p>
<
p> Student Name : {{ student.studentName }} </p>
<
p> Address : {{ student.address }} </p>

File app/templates/student_detail.html

Cách lấy tham số từ request

Với GET method, tham số được lấy từ request.GET['param'], với POST method tham số được lấy từ request.POST['param']

Ví dụ:

from django.urls import path

from . import views

urlpatterns = [
   path(
'hello', views.hello),
]

 File app/urls.py

from django.shortcuts import render

def hello(request):
   name = request.GET.get(
'name' , 'world')
   
   context = {
       
'name': name,
   }
   
return render(request, 'hello.html', context)

File app/views.py

Hello {{ name }}

File app/templates/hello.html

Upload file

Đối với form dạng multipart (chứa file), để lấy file từ client upload lên, sử dụng request.FILES.get['file_variable']

Ví dụ:

from django.urls import path

from . import views

urlpatterns = [
   path(
'upload', views.uploadFile),
]

 File app/urls.py

import os
from django.shortcuts import render
from django.core.files.storage import FileSystemStorage

STATIC_DIR =
'static'

def uploadFile(request):
   
if request.method == 'POST':
       file = request.FILES.get(
'file')

       
if file:
           fs = FileSystemStorage()
           filepath = os.path.join(STATIC_DIR, file.name)
           fs.save(filepath, file)    
 
   
return render(request, 'upload.html')

File app/views.py

<form method="post" enctype='multipart/form-data'>
   {%
csrf_token %}
   <
input type='file' name='file'>
   <
input type='submit' value='Save'>
</
form>

File app/templates/upload.html

Sử dụng form của django

Khi cần tạo form nhập liệu với nhiều trường thông tin, cách tốt nhất là sử dụng form của django vì sẽ hỗ trợ việc validate và lấy giá trị biến từ html form một cách tự động.

Để sử dụng form của django, cần tạo một file với tên forms.py trong thư mục ứng dụng, trong file này khai báo các form kế thừa lớp django.forms.Form

Ví dụ sau minh họa cách sử dụng form của django để tạo một trang thêm mới thông tin học sinh :

from django.urls import path

from . import views

urlpatterns = [
   path(
'student/<int:studentId>', views.studentDetail, name='student_detail'),
   path(
'add_student', views.addStudent),
]

 File app/urls.py

from django.db import models

class Student(models.Model):

   studentNo = models.CharField(db_column=
'student_no',  max_length=20, unique=True)                                
   studentName = models.CharField(db_column=
'student_name', max_length=50)                                    
   address = models.CharField(db_column=
'address', max_length=100)

 File app/models.py

from django import forms

class StudentForm(forms.Form):
   studentNo = forms.CharField(label=
'Student Number', max_length=20)
   studentName = forms.CharField(label=
'Student Name', max_length=50)
   address = forms.CharField(label=
'Address', max_length=100) 

 File app/forms.py

import os
from django.shortcuts import render, redirect, get_object_or_404
from .models import Student
from .forms import StudentForm

def studentDetail(request, studentId):
   student = get_object_or_404(Student, pk=studentId)
   
   context = {
       
'student': student,
   }
   
return render(request, 'student_detail.html', context)
   
def addStudent(request):
   
if request.method == 'POST':
       form = StudentForm(request.POST)
       
       
if form.is_valid():
           student = Student.objects.create(
                       studentNo=form.cleaned_data[
'studentNo'],
                       studentName=form.cleaned_data[
'studentName'],
                       address=form.cleaned_data[
'address'])
                       
           
return redirect('student_detail', studentId=student.id)
       
else:
           
return render(request, 'add_student.html', {'form' : form})
       
   
return render(request, 'add_student.html', {'form' : StudentForm() })

File app/views.py

<form method="post">
   {%
csrf_token %}
        <
table>
                
{{ form }}
        </
table>
   <
input type='submit' value='Add'>
</
form>

File app/templates/add_student.html

<p> Student Number : {{ student.studentNo }} </p>
<p> Student Name : {{ student.studentName }} </p>
<p> Address : {{ student.address }} </p>

File app/templates/student_detail.html

Validate các trường trong form:

Ngoài các validator có sẵn của django như require, min_length, max_length, … có thể bổ sung các validator tùy chọn cho từng trường thông tin hoặc cho cả form.

Để thêm validator cho một trường, chúng ta thêm hàm clean_<filed_name> vào trong form để thực hiện kiểm tra giá trị của trường. Để thêm validator cho toàn bộ form, chúng ta dùng hàm clean cho toàn bộ form

Ví dụ:

from django import forms
from .models import Student

class StudentForm(forms.Form):
   studentNo = forms.CharField(label=
'Student Number', max_length=20)
   studentName = forms.CharField(label=
'Student Name', max_length=50)
   address = forms.CharField(label=
'Address', max_length=100)
   
   
def clean_studentNo(self):
       studentNo = self.cleaned_data[
'studentNo']
       students = Student.objects.filter(studentNo=studentNo)
       
       
if len(students) > 0:
           
raise forms.ValidationError(
                   
'Student number already existed: %s',
                   params=(studentNo,),
                   code=
'studentNo_existed')
                   
       
return studentNo
       
   
def clean(self):
       
return self.cleaned_data

       

File app/forms.py

Sử dụng custom html cho form:

Việc render form theo cú pháp {{ form }} trong template rất nhanh và thuận tiện, tuy nhiên trong nhiều trường hợp chúng ta phải thực hiện render form theo cấu trúc html riêng, khi đó có thể sử dụng các thẻ html thông thường và dùng {{ form.<field>.value }} để truy nhập tới từng trường dữ liệu của form.

<form method="post">
   {%
csrf_token %}
   <
table>
       <
tr>
           <
td>{{ form.studentNo.label_tag }}<td>
           <
td><input name='studentNo' value='{{ form.studentNo.value |default:""}}'></td>
           <
td><div style='color:red'>{{ form.studentNo.errors }}</div></td>            
       </
tr>
       <
tr>
           <
td>{{ form.studentName.label_tag }}<td>
           <
td><input name='studentName' value='{{ form.studentName.value |default:""}}'></td>
           <
td><div style='color:red'>{{ form.studentName.errors }}</div></td>            
       </
tr>
       <
tr>
           <
td>{{ form.address.label_tag }}<td>
           <
td><input name='address' value='{{ form.address.value |default:""}}'></td>
           <
td><div style='color:red'>{{ form.address.errors }}</div></td>            
       </
tr>        
   </
table>
   <
input type='submit' value='Add'>
</
form>

File app/templates/add_student.html

Admin trong django

Theo mặc định, django cung cấp giao diện admin tại địa chỉ http://127.0.0.1:8000/admin/.

Hình 28. Đăng nhập vào màn hình admin của django

Để tạo tài khoản admin cho django, sử dụng lệnh :

python manage.py createsuperuser

Sau khi có tài khoản admin và đăng nhập, màn hình admin của django sẽ có dạng như sau:

Hình 29. Màn hình admin của django sau khi đăng nhập

Giao diện admin cho phép thêm mới/ chỉnh sửa/ xóa các đối tượng dữ liệu do django quản lý như User, Group. Ngoài ra, giao diện admin còn cho phép thêm các đối tượng dữ liệu mà các ứng dụng tạo ra vào danh sách quản lý để thực hiện các chức năng thêm mới/ chỉnh sửa/ xóa một cách nhanh chóng.

Ví dụ:

Tạo đối tượng Student trong file models.py như sau :

from django.db import models

class Student(models.Model):

   studentNo = models.CharField(db_column=
'student_no',  max_length=20, unique=True)                                
   studentName = models.CharField(db_column=
'student_name', max_length=50)                                    
   address = models.CharField(db_column=
'address', max_length=100)

 File app/models.py

Đăng ký đối tượng trong admin.py :

from django.contrib import admin
from .models import Student  # new

admin.site.register(Student)  
# new

 File app/admin.py

Sau khi khởi động lại server và đăng nhập lại, đối tượng Student sẽ xuất hiện trong màn hình admin :

Hình 30. Màn hình admin của django sau khi thêm đăng ký đối tượng Student

Sau khi đã đăng ký đối tượng, có thể dùng màn hình admin để thực hiện tạo mới và sửa thông tin các bản ghi trong bảng Student :

Hình 31. Dùng màn hình admin để thêm mới/chỉnh sửa bản ghi

Tích hợp chức năng đăng ký người dùng & đăng nhập của django

Trước hết, tạo mới một project:

django-admin startproject mysite

cd mysite

python manage.py migrate

Các chức năng quản lý người dùng của django nằm trong module django.contrib.auth. Các url mặc định để thực hiện các chức năng login/logout của module này gồm :

Để sử dụng các url này, chúng ta thêm khai báo trong file urls.py của project như sau:

from django.contrib import admin
from django.urls import path, include # new

urlpatterns = [
   path(
'accounts/', include('django.contrib.auth.urls')),  # new
   path(
'admin/', admin.site.urls),
]

Theo mặc định, django sẽ tìm kiếm template cho các chức năng quản lý User tại thư mục templates/registration. Do đó chúng ta tạo mới thư mục templates/registration trong thư mục gốc của project, sau đó tạo mới các file html như sau:

mysite/

        templates

            home.html

            registration

                login.html

                signup.html

File template home.html được tạo ra để chứa nội dung trang mặc định sau khi login/logout.  Ngoài ra phần đăng ký mới người dùng (signup) cũng không nằm trong django.contrib.auth do đó chúng ta cũng phải tạo mới chức năng này. Việc thực hiện đăng ký url cho các chức năng này trong file urls.py của project như sau:

from django.contrib import admin
from django.urls import path, include
from django.views.generic.base import TemplateView  # new

from . import views

urlpatterns = [
   path(
'admin/', admin.site.urls),
   path(
'accounts/', include('django.contrib.auth.urls')),
   path(
'accounts/signup', views.signup),  # new
   path(
'', TemplateView.as_view(template_name='home.html'), name='home'),  # new  
]

Trong file settings.py của project, thêm ở cuối file khai báo sau:

LOGIN_REDIRECT_URL = 'home'
LOGOUT_REDIRECT_URL =
'home'

Đồng thời update cấu hình của thư mục chứa templates :

TEMPLATES = [
   {
       
....
       
'DIRS': [os.path.join(BASE_DIR, 'templates')],   # new
       ....

Tạo màn hình đăng ký (file signup.html):

<h2>Sign up</h2>

<
form method="post">
  {%
csrf_token %}
  <
table>
      <
tr>
          <
td>Username</td>
          <
td><input name='username' value='{{ form.username }}'></td>
      </
tr>
      <
tr>
          <
td>Email</td>
          <
td><input name='email' value='{{ form.email }}'></td>
      </
tr>
      <
tr>
          <
td>Password</td>
          <
td><input type='password' name='password1' value='{{ form.password1 }}'></td>
      </
tr>
      <
tr>
          <
td>Confirm Password</td>
          <
td><input type='password' name='password2' value='{{ form.password2 }}'></td>
      </
tr>
  </
table>
  <
span style='color:red'>{{ error }} </span>
  <
input type="submit" value='Sign up'>
</
form>

File templates/registration/signup.html

Tạo file mysite/views.py để thêm hàm xử lý view cho chức năng đăng ký người dùng :

from django.contrib.auth import login, authenticate
from django.shortcuts import render, redirect
from django.contrib.auth.models import User

def signup(request):
   error =
''
   
   
if request.method == 'POST':
       username = request.POST[
'username']
       email = request.POST[
'username']
       password1 =  request.POST[
'password1']
       password2 =  request.POST[
'password2']
       
       
if password1 != password2:
           error =
'Confirmed password does not match'
       
else:
           
try:
               user = User.objects.create_user(
                                  username=username,
                                  email=email,
                                  password=password1)
             
               user = authenticate(username=username,
                                    password=password1)

               login(request, user)
               
return redirect('home')
         
           
except Exception as e:                
               error = str(e)

   
return render(request, 'registration/signup.html', {'error': error})

File mysite/views.py

Tạo màn hình đăng nhập (file login.html):

<h2>Login</h2>
<
form method="post">
 {%
csrf_token %}  
 <
p>Username : <input name='username'></p>
 <
p>Password : <input type='password' name='password'></p>
 <
input type="submit" value='Login'>
 <
a href="/accounts/signup">Sign up</a>
</
form>

File templates/registration/login.html

Tạo màn hình trang chủ (file home.html):

{% if user.is_authenticated %}
 You logged in as
{{ user.username }}
 <
a href="{% url 'logout' %}">logout</a>

{%
else %}
 <
br>
 <
a href="{% url 'login' %}">login</a> / <a href="/accounts/signup">signup</a>
{%
endif %}

File templates/home.html

Khởi động server, truy nhập địa chỉ http://localhost:8000

Hình 32. Màn hình trang chủ khi chưa đăng nhập

Sử dụng chức năng signup để tạo tài khoản mới:

Hình 33. Màn hình tạo tài khoản mới

Sau khi tạo tài khoản xong, chương trình sẽ tự redirect về trang chủ

:

Hình 34. Màn hình trang chủ sau khi đăng nhập

Tích hợp chức năng đăng nhập cho ứng dụng:

Đối với các trang của ứng dụng, phần template có thể sử dụng điều kiện {{ user.is_authenticated }} để biết người dùng đã đăng nhập hay chưa. Đối với các hàm của views.py, có thể sử dụng ký hiệu @login_required để bắt buộc đăng nhập với các chức năng cần bảo mật.

Ví dụ, thêm một view mới với tên loginRequiredView như sau:

from django.contrib import admin
from django.urls import path, include
from django.views.generic.base import TemplateView

from . import views

urlpatterns = [
   path(
'admin/', admin.site.urls),
   path(
'accounts/', include('django.contrib.auth.urls')),
   path(
'accounts/signup', views.signup),  
   path(
'', TemplateView.as_view(template_name='home.html'), name='home'),    
   path(
'login-required-view', views.loginRequiredView),   # new
]

File urls.py

from django.contrib.auth import login, authenticate
from django.shortcuts import render, redirect
from django.contrib.auth.models import User

from django.contrib.auth.decorators import login_required # new
 

@login_required
def loginRequiredView(request):
   
return render(request, 'login_required.html')

File mysite/views.py

You logged in as {{ user.username }}
<
a href="{% url 'logout' %}">logout</a>

File templates/login_required.html

Khởi động server và truy nhập các địa chỉ http://localhost:8000/login-required-view, nếu chưa đăng nhập, chương trình sẽ chuyển qua màn hình đăng nhập trước sau đó mới cho phép xem nội dung trang này.

Lập trình django nâng cao

Django-rest-framework (DRF)

Django-rest-framework (DRF) là framework cho phép tạo ra các Restful webservice một cách nhanh chóng, phục vụ cho các ứng dụng Single Page Application (SPA) như Angular/React/Vue.

Cài đặt DRF:

pip install djangorestframework

Cách tạo webservice với DRF

Tạo mới project:

django-admin startproject mysite

cd mysite

python manage.py migrate

Tạo mới một ứng dụng trong project:

python manage.py startapp app

Thêm ứng dụng vừa tạo vào file settings.py của project:

INSTALLED_APPS = [
   
'app',                  # new
   
'rest_framework',       # new
   
'django.contrib.admin',

Tạo mới webservice trong file views.py của project:

from rest_framework.decorators import api_view
from rest_framework.response import Response

@api_view(['GET', 'POST'])
def hello(request):    
   
return Response({"message" : "Hello world!"})

File app/views.py

Thêm cấu hình url cho webservice:

from django.urls import path
from .views import hello

urlpatterns = [
   path(
'hello/', hello),
]

File app/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
   path(
'app/', include('app.urls')),
   path(
'admin/', admin.site.urls),    
]

File urls.py của project

Khởi động ứng dụng và truy cập api tại địa chỉ http://localhost:8000/app/hello, chúng ta sẽ thấy nội dung như sau:

Hình 35 .Test thử webservice từ trình duyệt

Cách lấy tham số từ request:

Ví dụ:

from rest_framework.decorators import api_view
from rest_framework.response import Response

@api_view(['GET', 'POST'])
def hello(request):    
   
if request.method == 'POST':
       name = request.data.get(
'name', 'world')
       
return Response({"message" : f"Hello {name}!"})        
   
else:
       name = request.query_params.get(
'name', 'world')
       
return Response({"message" : f"Hello {name}!"})

Cách lấy dữ liệu từ request với POST và GET method (file app/views.py)

Sử dụng View Class

Ngoài việc sử dụng kí hiệu @api_view để tạo endpoint, có thể sử dụng View Class để tạo endpoint. View Class cần kế thừa class rest_framework.views.APIView và cần cung cấp các hàm get, post, … tương ứng với các endpoint xử lý cho các method.

Ví dụ:

from rest_framework.views import APIView
from rest_framework.response import Response

class HelloView(APIView):
   
   
def get(self, request, format=None):
       name = request.query_params.get(
'name', 'world')
       
return Response({"message" : f"Hello {name}!"})
       
   
def post(self, request, format=None):
       name = request.data.get(
'name', 'world')
       
return Response({"message" : f"Hello {name}!"})

Cách Sử dụng View Class để tạo API (file app/views.py )

from django.urls import path
from .views import HelloView

urlpatterns = [
   path(
'hello', HelloView.as_view()),
]

Thực hiện mapping url cho View Class (file app/urls.py)

Truy xuất dữ liệu với Serializer

Serializer được dùng để chuyển đổi giữa JSON object và database object.

Ví dụ:

Xây dựng ứng dụng webservice để quản lý thông tin học sinh.

Tạo mới project và ứng dụng:

django-admin startproject mysite

cd mysite

python manage.py migrate

python manage.py startapp students

Thêm ứng dụng vào setting.py của project :

INSTALLED_APPS = [
   
'students',             # new
   
'rest_framework',       # new
   
'django.contrib.admin',

Tạo database model trong file students/models.py:

from django.db import models

class Student(models.Model):

   studentNo = models.CharField(db_column=
'student_no', max_length=20, unique=True)                              
   studentName = models.CharField(db_column=
'student_name', max_length=50)                              
   address = models.CharField(db_column=
'address', max_length=100)

File students/models.py

Cập nhập database:

python manage.py makemigrations students

python manage.py migrate

Tạo mới file students/serializers.py với nội dung sau:

from rest_framework import serializers
from .models import Student


class StudentSerializer(serializers.ModelSerializer):
   
class Meta:
       model = Student
       fields = (
'studentNo', 'studentName', 'address')

File students/serializers.py

Class StudentSerializer kế thừa class ModelSerializer của DRF, có nhiệm vụ thực hiện chuyển đổi từ database model sang JSON object và ngược lại.

Tạo các api xử lý dữ liệu trong file students/views.py :

from rest_framework.views import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from django.shortcuts import get_object_or_404

from .models import Student
from .serializers import StudentSerializer


@api_view(['GET'])
def getAllStudents(request, format=None):
   students = [StudentSerializer(student).data
for student in Student.objects.all()]
   
return Response(students)

@api_view(['GET'])
def getStudent(request, pk):
   student = get_object_or_404(Student, pk=pk)
   
return Response(StudentSerializer(student).data)
   
@api_view(['POST'])
def createStudent(request):
   serializer = StudentSerializer(data=request.data)
       
   
if not serializer.is_valid():
       
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
       
   student = serializer.save()
   
return Response(StudentSerializer(student).data)

File students/views.py

Việc sử dụng Serializer giúp cho việc chuyển đổi giữa database model sang JSON thực hiện nhanh chóng, ví dụ :

StudentSerializer(student).data

Sẽ tương đương với :

{
        
'studentNo' : student.studentNo,
        
'studentName' : student.studentName,
        
'address' : student.address
}

Cuối cùng, thêm mapping url cho các API vừa tạo:

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
   path(
'students/', include('students.urls')),
   path(
'admin/', admin.site.urls),
]

File urls.py của project

from django.urls import path
from .views import getAllStudents, getStudent, createStudent

urlpatterns = [
   path(
'all', getAllStudents),        
   path(
'<int:pk>', getStudent),
   path(
'new', createStudent),    
]

File students/urls.py

Khởi động ứng dụng và sử dụng postman để test các API:

Hình 36 .Dùng postman test API tạo mới bản ghi

Hình 37 .Dùng postman test API lấy bản ghi theo id

CORS

Cross-Origin Resource Sharing (CORS) là việc cho phép gọi webservice (từ trình duyệt) giữa các website có origin (tên miền/ip) khác nhau. Thông thường việc gọi webservice từ một địa chỉ (ví dụ http://www.site1) sang một địa chỉ ở site khác (ví dụ http://www.site2) sẽ bị trình duyệt chặn lại nếu trong response trả về từ site 2 không có thông tin chỉ ra rằng có thể được phép gọi service từ site 1 đến.

Để cho phép các site khác địa chỉ gọi service đến ứng dụng, cần đặt cấu hình CORS cho ứng dụng server chỉ rõ những site nào sẽ được phép gọi service đến.

Với Django, việc này được thực hiện như sau:

Cài đặt thư viện django-cors-headers:

pip install django-cors-headers

Thêm 'corsheaders' vào danh sách ứng dụng trong file settings.py của project:

INSTALLED_APPS = [

    ...

    'corsheaders',

    ...

]

Bổ sung cấu hình MIDDLEWARE_CLASSES trong file settings.py:

MIDDLEWARE_CLASSES = [

    ...

    'corsheaders.middleware.CorsMiddleware',  

    ...

]

Thêm vào cuối file settings.py :

CORS_ORIGIN_ALLOW_ALL = True

CORS_ALLOW_CREDENTIALS = True

CORS_ORIGIN_WHITELIST = (

    'localhost:9999',

)

Trong đó là CORS_ORIGIN_WHITELIST  danh sách các site được phép gọi service đến ứng dụng.

Để test xem việc cấu hình CORS đã thành công chưa, chúng ta tạo một file test.html với nội dung sau:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<
script>
   
function testAPI() {
       
       $.ajax(
           {    
               
url: "http://localhost:8000/students/all",                
               
success: function(result){
                   $(
'#content').html(JSON.stringify(result));
               },
           }
       );    
   }    
</
script>

<
input type='button' value='Test API' onclick='testAPI()'/>
<
br>
<
div id='content'></div>

Mở cửa sổ cmd trong thư mục chứa file test.html, gõ lệnh:

python -m http.server 9999

Truy nhập địa chỉ http://localhost:9999/test.html từ trình duyêt, click nút “Test API”, nếu trả về kết quả thì CORS đã được cấu hình thành công cho project của django.

Sử dụng webservice để xây dựng ứng dụng SPA

Ứng dụng Single Page Application (SPA) hiện nay đang rất phổ biến. Với các ứng dụng SPA, việc render nội dung website sẽ hoàn toàn do phía client thực hiện, server chỉ thực hiện cung cấp các dịch vụ dữ liệu dưới dạng webservice. Phía client gọi webservice của server, nhận kết quả và sinh ra nội dung hiển thị tương ứng.

Các framework SPA frontend phổ biến là : Angular, React, Vue.

Phần này minh họa nguyên lý hoạt động của SPA mà không sử dụng framework frontend nào. Chúng ta dựa vào ví dụ webservice đã xây dựng ở phần trước:

http://localhost:8000/students/all

Phía client sẽ dựa vào kết quả của webservice để sinh ra nội dung html:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<
script>
   
function getStudents() {
       
       $.ajax(
           {    
               
url: "http://localhost:8000/students/all",
               
               
success: function(result){
                   
var html = `<table border='1'>
                                   <tr>
                                       <th>Student Number</th>
                                       <th>Student Name</th>
                                       <th>Address</th>
                                   </tr>`
;
                   
                   
for(var i = 0; i < result.length; i++) {
                       html +=
`<tr>
                                   <td>
${result[i].studentNo}</td>
                                   <td>
${result[i].studentName}</td>
                                   <td>
${result[i].address}</td>
                               </tr>`
;                        
                   }
                   html +=
`</table>`;
                   
                   $(
'#content').html(html);
               },
           }
       );    
   }    
</
script>

<
input type='button' value='Get Students' onclick='getStudents()'/>
<
br><br>
<
div id='content'></div>

Truy nhập từ địa chỉ http://localhost:9999/test.html để test ứng dụng SPA .

Bảo mật với JWT

JWT là một trong các cơ chế bảo mật khi thực hiện gọi webservice. Cơ chế authentication qua JWT như sau:

Sử dụng JWT với DRF:

Cài đặt thư viện djangorestframework_simplejwt :

pip install djangorestframework_simplejwt

Thêm chức năng authentication cho API:

from rest_framework.response import Response
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated

@api_view(['GET'])
@permission_classes((IsAuthenticated,))
def testAPI(request, format=None):
   
return Response({"message" : "Hello"})

Nếu sử dụng View Class, việc thêm permission như sau:

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.decorators import permission_classes
from rest_framework.permissions import IsAuthenticated

class TestView(APIView):
   permission_classes = (IsAuthenticated,)
   
   
def get(request, format=None):        
       
return Response({"message" : "Hello"})

Trong file urls.py project, bổ sung thêm khai báo sau url như sau:

from django.contrib import admin
from django.urls import path, include
from rest_framework_simplejwt import views as jwt_views              # new

urlpatterns = [
   path(
'app/', include('app.urls')),
   path(
'api/token', jwt_views.TokenObtainPairView.as_view()),      # new
   path(
'api/token/refresh', jwt_views.TokenRefreshView.as_view()), # new
   path(
'admin/', admin.site.urls),
]

Bố sung vào cuối file settings.py của project thông tin sau:

REST_FRAMEWORK = {
   
'DEFAULT_AUTHENTICATION_CLASSES': [
       
'rest_framework_simplejwt.authentication.JWTAuthentication',
   ],
}

Sau khi xây dựng xong API, nếu truy nhập API từ trình duyệt, chúng ta thấy sẽ có lỗi 403 vì thiếu thông tin đăng nhập:

Hình 38 .Lỗi 403 khi truy nhập API mà không có thông tin đăng nhập

Để gọi được API, phải theo quy trình sau:

{

    "username" : "<username>",

    "password" : "<password>"

}

Trong đó <username>, <password> là thông tin tài khoản của django (có thể tạo ra từ lệnh python manage.py createsuperuser)

Kết quả trả về sẽ chứa 2 trường:

{

    "refresh" : "<refresh>"

}

Trong đó <refresh> là giá trị đã nhận được khi gọi hàm /api/token ở phía trên. Kết quả sẽ chứa trường access là giá trị mới của token để truy nhập đến API của server.

Minh họa các bước trên sử dụng postman như sau:

Hình 39 .Lấy token bằng cách POST đến địa chỉ /api/token

Hình 40 .Thêm giá trị Authorization vào header request để truy nhập được API

Để dễ hình dung hơn, chúng ta xây dựng ứng dụng SPA với 2 màn hình sau:

Do ứng dụng SPA độc lập với ứng dụng backend của django, cần cấu hình CORS cho project của django để phía client truy nhập được API. Chi tiết xem ở phần về CORS đã trình bày phần trước.

Ứng dụng SPA gồm 2 file : login.html (màn hình đăng nhập), và index.html (màn hình chính)

<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
<
div class='container'>
   <
p>Username : <input class='form-control' id='username'></p>
   <
p>Password : <input class='form-control' id='password' type='password'></p>
   <
span id='errMsg' style='color : red;'></span>
   <
br>
   <
input type='button' class='btn-primary' value='Log in' onclick='logIn();'>    
</
div>

<
script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<
script>
   $(
document).ready(function(){
     
if(sessionStorage.getItem('token_access') && sessionStorage.getItem('token_refresh')) {
       
window.location.href = 'index.html';
     }      
   });
   
   
function logIn() {
       
var data = JSON.stringify({
           
"username" : $('#username').val(),
           
"password" : $('#password').val()
       });
       
       $.ajax(
           {    
               
url: "http://localhost:8000/api/token",
               
data : data,
               
dataType : "json",
               
method : 'POST',
               
contentType: "application/json; charset=utf-8",
               
               
success: function(result){
                   sessionStorage.setItem(
'access_token', result['access']);
                   sessionStorage.setItem(
'refresh_token', result['refresh']);
                   
window.location.href = 'index.html';
               },
               
               
error: function (error) {
                   $(
'#errMsg').html('Incorrect username or password<br>');
               }                
           }
       );
   }
</
script>

File login.html

<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
<
body style='display:none'>
   <
div class='container'>
       Hello , you have logged in.    
       <
br>    
       <
div class="text-right">
           <
a href='javascript:logOut()'>Log out</a>
       </
div>
       <
input type='button' class='btn-primary' value='Test API' onclick='testAPI()'>    
       <
br><br>
       <
div id='content'></div>
   </
div>
</
body>

<
script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<
script>
   $(
document).ready(function(){
       
if(!sessionStorage.getItem('access_token') || !sessionStorage.getItem('refresh_token')) {
           
window.location.href = 'login.html';
       }
else{
           $(
'body').show();
       }
   });
   
   
   
function logOut() {
       sessionStorage.removeItem(
'access_token');
       sessionStorage.removeItem(
'refresh_token');
       
window.location.href = 'login.html';
   }
   
   
function refreshToken() {
       
var data = JSON.stringify({'refresh' : sessionStorage.getItem('refresh_token')});
       
       $.ajax(
           {    
               
url: "http://localhost:8000/api/token/refresh",
               
data : data,
               
dataType : "json",
               
method : 'POST',
               
contentType: "application/json; charset=utf-8",
               
               
success: function(result){
                   sessionStorage.setItem(
'access_token', result['access']);                    
               },
               
               
error: function (error) {
                   $(
'#errMsg').html('Incorrect username or password');
               }                
           }
       );
   }
   
   
function testAPI() {
       $.ajax(
           {    
               
url: "http://localhost:8000/app/test_api",
               
               
beforeSend: function(request) {                    
                   request.setRequestHeader(
"Authorization", 'Bearer ' + sessionStorage.getItem('access_token'));
               },
               
               
success: function(result){
                   $(
'#content').html(JSON.stringify(result));
               },
               
               
error: function (error) {
                   $(
'#content').html('Error :' + error.responseText);
                   refreshToken();
               }                
           }
       );
   }
</
script>

 

File index.html

Để chạy ứng dụng SPA, từ thư mục chứa các file index.html & login.html, gõ lệnh:

python -m http.server 9999

Từ trình duyệt, truy nhập địa chỉ http://localhost:9999, màn hình đăng nhập sẽ xuất hiện

Hình 41 .Màn hình đăng nhập của ứng dụng SPA

Sau khi đăng nhập, ứng dụng SPA sẽ chuyển qua màn hình chính, tại đây có thể thực hiện gọi API đến server:

Hình 42 . Gọi API đến server sau khi đã đăng nhập

Bảo mật với OAUTH2

OAUTH2 là cơ chế đăng nhập và xác thực qua một Authentication server riêng. Nguyên tắc đăng nhập và xác thực của OAUTH2 như sau:

Xây dựng ứng dụng đăng nhập qua OAUTH2 với django:

Cài đặt thư viện django-oauth-toolkit:

pip install django-oauth-toolkit

Tạo ứng dụng OAUTH2 provider :

Ứng dụng này sẽ thực hiện việc đăng nhập & xác thực người dùng (tương tự như google, facebook, github, …)

Tạo mới và cấu hình ứng dụng:

Tạo project và ứng dụng:

django-admin startproject oauth2provider

cd oauth2provider

python manage.py migrate

Bổ sung ứng dụng trong file settings.py của project:

INSTALLED_APPS = [
   
'oauth2_provider',
   ....

Bổ sung cấu hình url trong file urls.py của project:

from django.contrib import admin
from django.urls import path, include #new

urlpatterns = [
   path(
'admin/', admin.site.urls),
   path(
'o/', include('oauth2_provider.urls', namespace='oauth2_provider')),  # new
   path(
'accounts/', include('django.contrib.auth.urls')),  # new
]

Update database :

python manage.py migrate

Cấu hình CORS cho project :

Do service của OAUTH2 provider sẽ được gọi từ ứng dụng từ một địa chỉ khác nên cần cấu hình CORS cho project. Chi tiết xem ở phần về CORS đã trình bày ở phần trước.

Tạo màn hình đăng nhập :

OAUTH2 provider cần cung cấp màn hình login để xác thực người dùng. Cách thức tạo màn hình đăng nhập đã trình bày trong phần  “Tích hợp chức năng đăng ký người dùng & đăng nhập của django”. Ở đây, có thể tóm tắt cách thực hiện như sau:

TEMPLATES = [
   {
       ....
       
'DIRS': [os.path.join(BASE_DIR, 'templates')],
       ....

<h2>Login</h2>
<
form method="post">  
 {% csrf_token %}
 <
p>Username : <input name='username'></p>
 <
p>Password : <input type='password' name='password'></p>
 <
button type="submit">Login</button>
</
form>

python manage.py createsuperuser

Hình 43 . Màn hình đăng nhập của ứng dụng OAUTH2 Provider

Sau khi đăng nhập thành công, ứng dụng sẽ chuyển sang màn hình lỗi 404, do chưa có màn hình trang chủ ứng dụng, chúng ta bỏ qua thông báo này.

Hình 44 . Màn hình 404 sau đăng nhập (bỏ qua thông báo này)

Tạo một test API :

API này đóng vai trò như service của resource server và được gọi đến từ ứng dụng client. Để xây dựng API này, chúng ta bổ sung cấu hình url trong file urls.py của project như sau:

from django.contrib import admin
from django.urls import path, include
from .views import ApiEndpoint  # new

urlpatterns = [
   path(
'admin/', admin.site.urls),
   path(
'o/', include('oauth2_provider.urls', namespace='oauth2_provider')),  
   path(
'accounts/', include('django.contrib.auth.urls')),  
   path(
'api/test', ApiEndpoint.as_view()), # new
]

Tạo mới file oauth2provider/views.py với nội dung sau:

from oauth2_provider.views.generic import ProtectedResourceView
from django.http import HttpResponse

class ApiEndpoint(ProtectedResourceView):
   
def get(self, request, *args, **kwargs):
       
return HttpResponse({'Hello, OAuth2!'})

Sau khi tạo xong API, khởi động server và truy nhập API từ địa chỉ http://localhost:8000/api/test, chúng ta sẽ thấy lỗi 403, vì không có thông tin đăng nhập trong header của request.

Đăng ký mới một ứng dụng OAuth2 Client:

Truy nhập địa chỉ http://localhost:8000/o/applications/ để tạo mới một ứng dụng OAuth2 Client. Thông tin các trường nhập vào như sau:

Hình 45 . Đăng ký mới một ứng dụng OAuth2 Client

Lấy token để test thử API :

Sau khi đăng ký xong client, có thể thực hiện lấy token để test việc gọi API. Cách lấy token như sau:

Hình 46. Màn hình Authorize của OAUTH2

Hình 47. Sử dụng Postman để lấy token

Lưu ý việc lấy token có thể không thành công nếu thời gian từ khi nhận code đến khi thực hiện request bị lâu (timeout), trong trường hợp đó phải lấy lại code bằng cách thực hiện lại link Authorize ở bước 1.

Hình 48. Sử dụng token để gọi API

Tạo ứng dụng client

Ứng dụng client sẽ thực hiện đăng nhập thông qua ứng dụng OAUTH2 provider đã xây dựng ở trên, khi đăng nhập thành công, ứng dụng client sẽ lưu token lại để thực hiện các lần gọi API về sau.

Tạo mới project cho ứng dụng:

django-admin startproject oauth2client

cd oauth2client

python manage.py migrate

Tạo màn hình trang chủ cho ứng dụng :

Thêm khai báo url trong file urls.py của project:

from django.contrib import admin
from django.urls import path
from .views import homePage, logOut  # new

urlpatterns = [
   path(
'admin/', admin.site.urls),
   path(
'', homePage, name='home'),  #new
   path(
'logout', logOut), # new
]

Tạo mới file oauth2client/views.py với nội dung sau:

from django.shortcuts import render, redirect
import requests
import json

CLIENT_ID =
'<client_id>'
CLIENT_SECRET =
'<client_secret>'
OAUTH_URL =
f'http://localhost:8000/o/authorize?client_id={CLIENT_ID}&state=random_state_string&response_type=code'


def homePage(request):
   code = request.GET.get(
'code')
   
   
if code:
       result = requests.post(
'http://localhost:8000/o/token/',
               data={
'code' : code, 'grant_type' : 'authorization_code'},
               auth=requests.auth .HTTPBasicAuth(CLIENT_ID, CLIENT_SECRET)
           ).text
           
   
       result = json.loads(result)
       
       request.session[
'access_token'] = result.get('access_token', '')
       request.session[
'refresh_token'] = result.get('refresh_token', '')
       
   
   
if 'access_token' not in request.session or 'refresh_token' not in request.session:
       
return redirect(OAUTH_URL)
   
else:
       
return render(request, 'home.html')

def logOut(request):
   request.session[
'access_token'] = None
   request.session[
'refresh_token'] = None
   
return redirect(OAUTH_URL)

Lưu ý thay đổi các giá trị CLIENT_ID  và CLIENT_SECRET  theo các giá trị đã đăng ký.

Tạo mới thư mục templates trong thư mục gốc của project, thêm cấu hình thư mục template trong file setting.py của project :

TEMPLATES = [
   {
       ....
       
'DIRS': [os.path.join(BASE_DIR, 'templates')],
       ....

Tạo file templates/home.html  với nội dung sau:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<
script>
   
function testAPI() {
       
       $.ajax(
           {    
               
url: "http://localhost:8000/api/hello",
               
               
beforeSend: function(request) {                    
                   request.setRequestHeader(
"Authorization", 'Bearer ' + '{{ request.session.access_token }}' );
               },
               
               
success: function(result){
                   $(
'#content').html(JSON.stringify(result));
               },
               
               
error: function (error) {
                   $(
'#content').html('Error :' + JSON.stringify(error));                    
               }                
           }
       );    
   }
   
</
script>

<
input type='button' value='Test API' onclick='testAPI()'/>
<
a href='/logout'>Log out</a>
<
br>
<
div id='content'></div>

Khởi động server tại cổng 9999:

python manage.py runserver 9999

Truy nhập ứng dụng tại địa chỉ http://localhost:9999. Ứng dụng sẽ tự động chuyển sang màn hình đăng nhập của OAUTH2 provider tại http://localhost:8000. Sau khi đăng nhập xong, OAUTH2 provider sẽ redirect trở về http://localhost:9999 và tại màn hình này có thể gọi API đến resource  server.

Hình 49. Gọi API từ ứng dụng client sau khi đã đăng nhập