Category Archives: Python

Django – View và Template

Trong phần này chúng ta sẽ tìm hiểu về View và Template trong Django.

Trong Django thì một View là một hàm/phương thức làm một công việc cụ thể nào đó, một View  thường đi kèm với một Template (chúng ta sẽ tìm hiểu ở dưới). Ví dụ, một ứng dụng Blog có một số View sau:

  • Home – hiển thị các bài viết mới nhất.
  • Entry – hiển thị bài viết nào đó.
  • Archive – Lưu trữ các bài viết theo năm/tháng.
  • Comment – xử lý việc đăng bình luận của độc giả.

Trong ứng dụng “Poll” mà chúng ta đã viết từ đầu series tới bây giờ, chúng ta sẽ xây dựng các View sau:

  • Index – Hiển thị các câu hỏi mới.
  • Detail – Hiển thị một câu hỏi nhất định nào đó và đưa các câu trả lời để người dùng chọn.
  • Result – Hiển thị kết quả bầu chọn của người dùng.
  • Vote  – Xử lý việc trả lời của người dùng.

Trong Django, một trang web được tạo ra bởi các hàm View, Django sẽ chọn View nào tùy thuộc vào URL mà chúng ta đã thiết lập. Có thể bạn đã từng thấy những đường dẫn URL nhìn rất “gớm” như “ME2/Sites/dirmod.asp?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B” do website tự tạo ra, Django cho phép chúng ta tạo những đường dẫn dễ nhìn hơn, chẳng hạn như đường dẫn bài viết mà bạn đang đọc 🙂

Để từ một đường dẫn URL đến một View thì Django sử dụng khái niệm URLConf, đây là một module Python của Django làm nhiệm vụ phân tích đường dẫn và chuyển đến một hàm View nhất định.

Tạo View

Chúng ta sẽ viết thêm một số hàm View mới.

def detail(request, question_id):
    return HttpResponse("You're looking at question %s." % question_id)

def results(request, question_id):
    response = "You're looking at the results of question %s."
    return HttpResponse(response % question_id)

def vote(request, question_id):
    return HttpResponse("You're voting on question %s." % question_id)

Chúng ta viết thêm 3 view mới là detail(), results()vote().

Tiếp theo chúng ta tạo thêm các URL trỏ đến từng view này.

from django.conf.urls import url

from . import views

urlpatterns = [
    # ex: /polls/
    url(r'^$', views.index, name='index'),
    # ex: /polls/5/
    url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
    # ex: /polls/5/results/
    url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
    # ex: /polls/5/vote/
    url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
]

Bạn có thể gõ trên thanh địa chỉ trình duyệt đến localhost:8000/polls/34/, Django sẽ gọi đến hàm detail() và hiển thị chuỗi text tương ứng, bạn có thể thử với đường dẫn polls/34/results/polls/34/vote/ nữa.

Khi bạn gõ địa chỉ lên thanh URL của trình duyệt, Django sẽ đọc biến urlpatterns trong file mysite.urls vì mặc định file này được trỏ tới trong biến ROOT_URLCONF trong file mysite/settings.py, các đối tượng url sẽ được đọc dần dần từ trên xuống dưới cho đến khi có một đường dẫn vừa khít với URL mà bạn nhập vào. Khi tìm thấy localhost:8000/polls vừa khít với '^polls/', Django sẽ cắt đoạn phía sau URL ra (vd như /polls/34/) và gửi đến file polls/urls.py để tiếp tục quá trình tìm kiếm, tại đây đoạn tiếp theo vừa khít với chuỗi Regex r'^(?P<question_id>[0-9]+)/$', mà chuỗi Regex này trỏ đến phương thức views.detail() nên Django sẽ gọi phương thức này với tham số như sau:

detail(request=<HttpRequest object>, question_id='34')

Chuỗi Regex (?P<question_id>[0-9]+) cho Django biết có một chuỗi con trong đoạn URL có dạng một con số (có 1 hoặc nhiều chữ số) và biến được truyền vào phương thức detail() có tên là question_id. Với sức mạnh của Regex thì bạn có thể tạo mẫu URL như thế nào cũng được, tất nhiên là tốt nhất nên làm cho nó ngắn gọn và dễ đọc.

Template

Các hàm View trả về một trong 2 thứ: hoặc là trả về một đối tượng HttpReponse chứa nội dung HTML để hiển thị lên trình duyệt, hoặc là một lỗi exception 404. Bạn muốn trả về thứ gì là tùy bạn thiết kế, có thể là đọc dữ liệu từ CSDL và dùng template để hiển thị dữ liệu, tạo file PDF, hiển thị nội dung XML, tạo file ZIP… Django không quan tâm bạn làm gì mà chỉ cần cái đối tượng HttpResponse hoặc exception cuối cùng mà bạn tạo ra thôi.

Chúng ta sẽ sửa lại hàm index() một tí.

from django.http import HttpResponse

from .models import Question

def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    output = ', '.join([q.question_text for q in latest_question_list])
    return HttpResponse(output)

Đoạn code trên rất đơn giản, chỉ là lấy ra 5 đối tượng Question được thêm vào gần đây nhất, sau đó lấy thuộc tính question_text đưa vào biến output rồi trả về thôi.

Bạn có thể vào địa chỉ localhost:8000/polls/ để xem kết quả và thấy là nếu chỉ in text ra như thế này thì giao diện quá xấu, mà nếu muốn thay đổi giao diện thì bạn phải điều chỉnh lại trong hàm index() như thêm các thẻ h1, h2, thêm các mã CSS… như thế rất bất tiện, thế nên Django cung cấp cho chúng ta hệ thống Template, chúng ta có thể viết phần giao diện ở Template và dùng nó để hiển thị dữ liệu của các View cho đẹp mắt hơn, vậy tóm lại mục đích chính của View chính là kết nối giữa Template và Model.

Đầu tiên chúng ta tạo một thư mục có tên là templates trong thư mục polls, Django sẽ tự động tìm các file template trong thư mục này. Ở đây bạn tạo thêm một thư mục khác nữa tên là polls, trong thư mục này bạn tạo một file tên index.html.

{% if latest_question_list %}
    <ul>
       {% for question in latest_question_list %}
       <li>
             <a href="/polls/{{question.id}}/">
                {{question.question_text}} 
             </a>
       <li>
       {% endfor %}
    </ul>
{% else %}
   <p>No polls are available.</p>
{% endif %}

Trong file index.html chúng ta viết như trên.

Trình duyệt chỉ hiểu code HTML chứ không hiểu code Python, để có thể sử dụng code Python thì Django cung cấp cho chúng ta các thẻ template, thẻ template bắt đầu và kết thúc bằng cặp kí tự {% %} hoặc {{ }}, các câu lệnh Python nằm trong cặp dấu {% %}, còn các biến thì nằm trong cặp {{ }}.

from django.shortcuts import render
from django.http import HttpResponse
from django.template import loader

from .models import Question 

def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    template = loader.get_template('polls/index.html')
    context = {
        'latest_question_list': latest_question_list,
    }
    return HttpResponse(template.render(context, request))

Chúng ta cũng sửa lại đoạn code trong hàm index() để sử dụng template.

from django.template import loader

Để có thể dùng template thì trước hết chúng ta import module loader trong gói django.template.

template = loader.get_template('polls/index.html')

Để dùng template thì chúng ta dùng phương thức get_template(), mặc định Django đã biết là sẽ tìm template trong thư mục polls/templates/ nên chúng ta chỉ cần đưa vào đường dẫn đến polls/index.html là đủ.

return HttpResponse(template.render(context, request))

Sau đó chúng ta gọi đến phương thức template.render() để tạo nội dung HTML có sử dụng template. Tham số đầu tiên là nội dung HTML trả về, tham số thứ 2 là đối tượng request được gửi đến.

from django.shortcuts import render

def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    context = {
        'latest_question_list': latest_question_list,
    }
    return render(request, 'polls/index.html', context)

Ngoài ra bạn cũng có thể dùng hàm render() để trả về một đối tượng HttpResponse một cách trực tiếp luôn, với tham số thứ nhất là đối tượng request, tham số thứ 2 là đường dẫn đến file template, tham số thứ 3 là nội dung HTML trả về.

Giải phóng lỗi 404

from django.shortcuts import render
from django.http import HttpResponse, Http404
from django.template import loader

from .models import Question

def detail(request, question_id): 
    try:
        question = Question.objects.get(pk=question_id)
    except Question.DoesNotExist:
        raise Http404("Question does not exist")
    return render(request, 'polls/detail.html', {'question':question})

Chúng ta sẽ giải phóng lỗi exception 404 nếu đường dẫn ID sai trong hàm detail().

from django.http import Http404

Đầu tiên chúng ta import module Http404

except Question.DoesNotExist:
        raise Http404("Question does not exist")

Phương thức get() sẽ trả về một exception DoesNotExist nếu không tìm thấy bản ghi nào trong list, tại đây chúng ta giải phóng một đối tượng Http404. Còn nếu không có gì thì chúng ta trả về nội dung HTML như thường.

<h1>{{ question.question_text }}</h1>
<ul>
    {% for choice in question.choice_set.all %}
        <li>{{ choice.choice_set }}</li>
    {% endfor %}
</ul>

Trên đây là đoạn code template cho hàm view detail().

Bạn có thể thử nhập đường dẫn không có thật chẳng hạn như localhost:8000/polls/123456 để thấy Django trả về trang 404.

URL động

Bây giờ có một vấn đề như thế này, trong các file template như index.html, các đường link chúng ta tham chiếu đến trong các thẻ <a> là do chúng ta tự viết, chẳng hạn như href="/polls/..." ở dưới đây:

<li>
    <a href="/polls/{{ question.id }}/">
        {{ question.question_text }}
    </a>
</li>

Giả sử trong trang web của chúng ta bắt buộc phải có 100 thẻ <a> như thế, việc code từng đường dẫn là cực kỳ mệt mỏi, và khi muốn thay đổi chẳng hạn như chúng ta muốn đổi URL từ localhost:8000/polls/1/ thành localhost:8000/polls/details/1/ thì chúng ta phải đổi bằng tay hết 100 thẻ <a> đó, thế thì chỉ có chết 🙂

Do đó chúng ta có thể tham chiếu đến các đối tượng url đã định nghĩa trong file urls.py của ứng dụng. Khi chúng ta tạo các đối tượng url trong file urls.py, tham số thứ 3 là name, tham số này do chúng ta tự đặt, và Django cho phép chúng ta tham chiếu đến chúng trong các file template.

<a href="{% url 'detail' question.id %}">
    {{ question.question_text }}
</a>

Bằng cách này chúng ta có thể tham chiếu đến đối tượng url trong file urls.py, và khi nào cần thay đổi URL mới thì chúng ta chỉ cần thay đổi trong file urls.py là được:

...
url(r'^details/(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
...

Đặt namespace cho URL

Khi dùng đến URL động thì lại phát sinh một vấn đề nữa, mặc định thì Django tự động tìm các file template bên trong thư mục template, vậy thì giả sử khi chúng ta có thêm nhiều ứng dụng khác ngoài polls, chẳng hạn như một ứng dụng blog, trong đó cũng có hàm view detail(), và hàm view này cũng sử dụng một template tên là detail.html, vậy thì khi đó Django sẽ gắn template của ứng dụng polls vào view detail() của ứng dụng blog,  như thế sẽ báo lỗi vì ứng dụng blog sẽ không có các biến giống như polls.

Để giải quyết vấn đề này, chúng ta sẽ đặt namespace cho các biến url, nếu bạn chưa biết namespace là gì thì cứ nghĩ rằng chúng cũng giống như một cách gộp nhóm những thứ giống nhau lại với nhau thôi, nếu muốn tìm hiểu thêm thì bạn có thể search trên mạng, mình không đi sâu ở đây.

from django.conf.urls import url
from . import views

app_name = "polls"
urlpatterns = [
    # ex: /polls/
    url(r'^$', views.index, name='index'),
    # ex: /polls/detils/5/
    #url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
    url(r'^details/(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
    # ex: /polls/5/results/ 
    url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
    # ex: /polls/5/vote/
    url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote')
]

Để đặt tên namespace cho các đối tượng url thì chúng ta chỉ cần đặt giá trị cho biến app_name trong file urls.py là được.

<a href="{% url 'polls:detail' question.id %}">
    {{ question.question_text }}
</a>

Trong file template, vd như index.html thì chúng ta chỉ cần viết đủ tên <namespace>:<tên_biến_url> là xong, vd url có name là detail thì viết đầy đủ thành polls:detail.

Django – Hệ thống Admin

Trong phần này chúng ta sẽ tìm hiểu về hệ thống Admin có sẵn trong Django.

Thường thì khi viết một ứng dụng nào đó, chẳng hạn như website bán hàng, blog, web tin tức, diễn đàn…v.v. ngoài các trang hiển thị thông tin thì chúng ta còn phải xây dựng một trang nữa là trang admin, trong đó lại bao gồm nhiều trang nhỏ hơn như thêm, sửa, xóa bài viết, cài đặt trang web… việc làm các trang này khá đơn giản, không cầu kỳ nhưng cũng rất nhàm chán. Chính vì vậy Django cung cấp sẵn một trang admin cho riêng chúng ta.

 Tạo user

Để có thể đăng nhập vào admin thì trước hết chúng ta phải tạo một tài khoản admin đã vì Django không tạo sẵn cho chúng ta khi tạo project.

C:\Project\mysite>python manage.py createsuperuser

Để tạo tài khoản thì chúng ta chạy file manage.py với tham số createsuperuser.

Username: admin
Email address: admin@example.com
Password:
Password (again):
Superuser created successfully.

Tiếp theo bạn cung cấp username, passwordemail là xong.

 Truy cập trang admin

Capture

Để truy cập vào trang admin thì bạn chỉ cần thêm /admin vào đường dẫn trang chủ là được.

Capture

Sau đó bạn đăng nhập bằng usernamepassword mà chúng ta đã tạo hồi nãy là sẽ được chuyển đến giao diện admin.

Mặc định Django đã bật chức năng dịch nên có thể trang admin sẽ được hiển thị bằng ngôn ngữ mà bạn dùng trên trình duyệt.

Tại trang admin chúng ta có thể thao tác với 2 bảng là UserGroup. Các bảng QuestionChoice mà chúng ta đã tạo ra không được hiển thị ở đây là vì chúng ta chưa đăng ký các bảng đó với trang admin.

from django.contrib import admin

# Register your models here.
from .models import Question, Choice

admin.site.register(Question)
admin.site.register(Choice)

Để đăng ký các bảng (hay các mô hình) với admin thì chúng ta chỉ cần dùng phương thức admin.site.register() trong file admin.py mà Django đã tạo cho chúng ta.

Capture

Sau khi đã đăng kí xong thì 2 bảng QuestionChoice sẽ hiện ra trong giao diện admin.

Giao diện admin mặc định của Django rất đơn giản, bạn có thể thực hiện thêm, sửa, xóa các bảng này một cách dễ dàng.

Django – Model

Trong phần này chúng ta sẽ học cách tạo các Model để có thể tương tác với cơ sở dữ liệu.

Thiết lập cơ sở dữ liệu

Trong thư mục mysite chứa một file tên là settings.py, file này chứa các thông tin cấu hình server của bạn.

Mặc định thì Django server sử dụng cơ sở dữ liệu SQLite, và trong bài này mình cũng sẽ sử dụng SQLite cho đơn giản, nếu bạn dùng SQLite thì bạn không cần quan tâm đến việc tạo CSDL và cấu hình user, nhưng nếu bạn muốn sử dụng CSDL khác thì trong file settings.py, bạn tìm đến đối tượng DATABASES và thay đổi các giá trị sau:

  • ENGINE: tên module dành cho từng CSDL, mặc định sử dụng SQLite
    • django.db.backends.sqlite3 – cơ sở dữ liệu  SQLite
    • django.db.backends.postgresql – cơ sở dữ liệu PostgreSQL
    • django.db.backends.mysql – cơ sở dữ liệu MySQL
    • django.db.backends.oracle – cơ sở dữ liệu Oracle
  • NAME: tên CSDL, mặc định là file db.sqlite3 được tạo ra ở thư mục gốc của server. Nếu bạn không dùng CSDL SQLite thì bạn phải tạo CSDL với tên trùng với NAME trong CSDL mà bạn dùng (bằng câu lệnh CREATE DATABASE <name>)

Ngoài ra nếu bạn không dùng SQLite thì bạn cũng phải cung cấp thêm các thông tin USER, PASSWORD, HOST nữa và user phải có quyền truy cập CSDL cũng như một số quyền như ghi, xem…

Trong file settings.py còn có một list có tên là INSTALLED_APPS, mặc định khi tạo một project, Django cung cấp cho chúng ta một số ứng dụng thường dùng trong list này. Trong bài sau chúng ta sẽ dùng một ứng dụng trong list này đó là django.contrib.admin.

Mỗi ứng dụng trong list này sẽ giúp Django tìm các model để tạo bảng tương ứng trong file CSDL, nhưng khi tạo project thì các bảng này không tự động được tạo trong file db.sqlite3, để tạo các bảng này thì chúng ta chạy lệnh sau:

C:\Project\mysite>python manage.py migrate

Lệnh migrate sẽ tìm các module được liệt kê trong list INSTALLED_APPS (trong file mysite/settings.py) và tạo các bảng CSDL tương ứng.

Tạo mô hình dữ liệu cho ứng dụng

Mô hình ở đây giống như lớp trong lập trình hướng đối tượng hoặc Model trong mô hình MVC vậy thôi, không có gì khó hiểu cả 🙂

Mặc định khi tạo một ứng dụng web trong một project Django thì Django đã tạo sẵn cho chúng ta một file có tên là models.py để chúng ta khai báo các mô hình trong này rồi nhưng bạn cũng có thể viết trong các file khác nếu thích.

from django.db import models

class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')

class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)

Chúng ta sẽ tạo 2 mô hình (2 lớp…) là QuestionChoice.

class Question(models.Model):
...
class Choice(models.Model):

Mỗi mô hình được tạo ra phải được kế thừa từ lớp django.db.models.Model.

question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
...
votes = models.IntegerField(default=0)

Mỗi thuộc tính trong mô hình được kế thừa từ một lớp Field, đây là một lớp ảo trong Django, lớp này lại có nhiều lớp kế thừa khác đại diện cho mỗi kiểu dữ liệu ví dụ như CharField là kiểu text, DateTimeField là kiểu DateTime

Tham số max_length là số lượng ký tự tối đa, đây là tham số bắt buộc phải có. Các tham số còn lại như default… là tham số tùy chọn, không có cũng được.

question = models.ForeignKey(Question, on_delete=models.CASCADE)

Ngoài các kiểu dữ liệu thường dùng thì chúng ta còn có kiểu khóa ngoại được định nghĩa trong lớp ForeignKey, tham số đầu tiên là bảng mà khóa ngoại này tham chiếu tới, on_delete=models.CASCADE tức là khi dữ liệu trong bảng cha có sự thay đổi thì dữ liệu trong bảng con cũng sẽ thay đổi theo, chẳng hạn như bản ghi trong bảng Question bị xóa thì các bản ghi trong bảng Choice có tham chiếu tới bản ghi trong bảng Question này cũng sẽ bị xóa.

Tạo bảng trong CSDL từ Model

Sau khi đã tạo các lớp mô hình trong Python, Django sẽ nhìn các thuộc tính trong từng lớp để tạo các bảng tương ứng trong CSDL và tạo thêm các lớp mới trong Python cung cấp các hàm để bạn thao tác với các bảng trong CSDL.

INSTALLED_APPS = [
    'polls.apps.PollsConfig',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

Để Django tạo các bảng mới trong CSDL thì chúng ta phải khai báo trước trong list INSTALLED_APPS đã bằng cách thêm dòng polls.apps.PollsConfig.

C:\Project\mysite>python manage.py makemigrations polls

Tiếp theo bạn chạy lệnh makemigration polls để báo cho Django biết là bạn đã thay đổi một số mô hình, ở đây là thêm 2 lớp mới trong gói polls.

Migrations for 'polls':
  0001_initial.py:
    - Create model Choice
    - Create model Question
    - Add field question to choice

Django sẽ tạo một file python để lưu các thông tin về sự thay đổi này, ở đây là file 0001_initial.py.

C:\Project\mysite>python manage.py migrate
Operations to perform: 
Apply all migrations: admin, contenttypes, polls, auth, sessions
Running migrations:
Rendering model states... DONE
Applying polls.0001_initial... OK

Chúng ta chạy lại lệnh migrate để Django cập nhật lại CSDL. Lúc này CSDL chúng ta sẽ có thêm 2 bảng mới là polls_choicepolls_question (tên bảng được đặt theo cú pháp <tên package>_<tên lớp>).

Thao tác với các bảng

Sau khi Django đã tạo các bảng bạn có thể bắt đầu thực hiện các công việc thường dùng như thêm-sửa-xóa… bản ghi.

C:\Project\mysite>python manage.py shell

Bạn có thể sử dụng trình shell mà Django cung cấp sẵn trong file manage.py. Nếu bạn không muốn chạy trực tiếp trong Command Prompt mà muốn chạy trong file .py riêng thì trong file .py đấy bạn phải khai báo các dòng dưới đây:

import os
import django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings");
django.setup()

Các dòng này sẽ thiết lập biến môi trường DJANGO_SETTINGS_MODULE trỏ đến module mysite.settings, mục đích là để chỉ cho Python biết đường dẫn import. Mặc định khi chạy file manage.py từ cmd thì biến này sẽ được Django thiết lập luôn.

>>> from polls.models import Question, Choice
>>> Question.objects.all()
[]

Chúng ta import 2 lớp QuestionChoice. Phương thức Question.objects.all() liệt kê toàn bộ đối tượng Question đang có trong CSDL, hiện tại là không có bản ghi nào nên kết quả trả về list rỗng.

>>> from django.utils import timezone
>>> q = Question(question_text="What's new?", pub_date=timezone.now())

Chúng ta tạo một đối tượng Question và thiết lập pub_date là ngày giờ hiện tại của máy tính, chúng ta lấy thông tin này từ phương thức timezone.now() trong module django.utils.timezone.

>>> q.save()

Để lưu đối tượng Question mới này vào CSDL thì chỉ cần gọi phương thức save() là được.

>>> q.id
1

Khi viết lớp QuestionChoice chúng ta không cung cấp thuộc tính id hay bất cứ thuộc tính nào để làm khóa chính, vì vậy khi tạo CSDL Django sẽ tự động thêm 1 thuộc tính id để làm khóa chính và thuộc tính này sẽ tự động tăng. 

>>> q.question_text
What's new?
>>> q.question_text = "What's up?"
>>> q.save()

Bạn có thể thay đổi trực tiếp giá trị trên các đối tượng này sau đó gọi phương thức save() là dữ liệu trên CSDL sẽ được cập nhật.

from django.db import models
class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')
    def __str__(self):
        return self.question_text
class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    def __str__(self):
        return self.choice_text

Chúng ta override phương thức __str__(), nếu không khi gọi phương thức str() mặc định sẽ cho output khá kì quái.

Sau đó chúng ta tắt và mở lại shell, rồi chạy thử đoạn lệnh sau sẽ được kết quả khác:

>>> from polls.models import Question, Choice
>>> Question.objects.all()
[<Question: What's up?>]

Phương thức objects.all() sẽ gọi đến phương thức __str__().

>>> Question.objects.filter(id=1)
[<Question: What's up?>]
>>> Question.objects.filter(question_text__startswith='What') 
[<Question: What's up?>]

Bạn có thể lọc các bản ghi theo thuộc tính bằng phương thức filter().

>>> q = Question.objects.get(pk=1)

Bạn cũng có thể lấy từng bản ghi đơn lẻ bằng phương thức get(), ở trên chúng ta lấy theo khóa chính pk (Primary Key).

>>> q.choice_set.all()
[]

Bất cứ bảng nào có một bảng khác chứa khóa ngoại tham chiếu đến khóa chính của nó đều sẽ có một thuộc tính được tạo tự động là một tập hợp các đối tượng của bảng kia. Ở đây bảng Choice chứa khóa ngoại tham chiếu đến bảng Question, do đó bảng Question sẽ có một danh sách các đối tượng Choice, chúng ta có thể lấy danh sách này qua choice_set.all() (tên danh sách được đặt theo <tên bảng>_set). Ở đây chúng ta vẫn chưa tạo đối tượng Choice nào nên danh sách trả về rỗng.

>>> q.choice_set.create(choice_text='Not much', votes=0)
<Choice: Not much>
>>> q.choice_set.create(choice_text='The sky', votes=0)
<Choice: The sky> 
>>> c = q.choice_set.create(choice_text='Just hacking again', votes=0)

Chúng ta tạo 3 đối tượng Choice bằng phương thức choice_set.create().

>>> q.choice_set.all()
[<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]
>>> q.choice_set.count()
3

Chúng ta có thể lấy số lượng các bản ghi trong bảng bằng phương thức count().

>>> c = q.choice_set.filter(choice_text__startswith='Just hacking')
>>> c.delete()

Để xóa một bản ghi trong bảng thì chúng ta dùng phương thức delete().

Django – Tạo project Django

Trong phần này chúng ta sẽ bắt đầu học cách tạo một ứng dụng web bằng django.

Tạo project

C:\Project>django-admin startproject mysite

Để tạo một project Django thì bạn mở Command Prompt (cmd) lên và chuyển đến thư mục mà bạn muốn tạo, sau đó gõ đoạn lệnh phía trên, ở trên chúng ta tạo một project với tên là mysite. Lệnh startproject sẽ tạo một thư mục có tên là mysite, cấu trúc bên trong thư mục sẽ như thế này:

mysite/
    manage.py
    mysite/
        __init__.py
        settings.py
        urls.py
        wsgi.py

Chúng ta sẽ từ từ tìm hiểu các file này sau.

Khi đặt tên project bạn tránh đặt những tên trùng với các từ khóa có sẵn trong Python như sys, os, django…. để tránh bị xung đột.

Làm web trong Python Django không giống với các ngôn ngữ khác như PHP, ASP.NET hay Java… bạn phải cài sẵn một web server (như Apache hoặc IIS…) trên máy và cài các phần mềm đi kèm trên web server đó, sau đó bạn viết code và bỏ vào thư mục gốc của web server (vd như www hay htdocs…), mỗi ứng dụng web Django tự chạy một server cho chính nó, do đó bạn có thể đặt code của bạn ở bất kỳ đâu mà bạn muốn.

Chạy server

Thư mục mysite có chức năng tương tự như thư mục gốc trong các web server khác như www hay htdocs… bên trong thư mục này có chứa một file tên là manage.py, file này cung cấp các chức năng để bạn vận hành server của bạn.

C:\Project\mysite>python manage.py runserver

Để chạy server thì bạn chạy file manage.py và đưa vào tham số runserver.

Performing system checks...

System check identified no issues (0 silenced).

You have unapplied migrations; your app may not work properly until they are applied.
Run 'python manage.py migrate' to apply them.

March 21, 2016 - 15:50:53
Django version 1.9, using settings 'mysite.settings'
Starting development server at <a class="reference external" href="http://127.0.0.1:8000/">http://127.0.0.1:8000/</a>
Quit the server with CTRL-BREAK.

Chúng ta đã chạy một web server viết bằng Python. Mặc định Django server sẽ chạy trên cổng 8000 (chúng ta có thể cấu hình để server chạy trên một cổng khác).

Capture

Bạn có thể vào trình duyệt và trỏ đường dẫn đến 127.0.0.1:8000 hoặc localhost:8000 để xem thông báo của server.

Để đổi port mặc định của server thì bạn thêm số port mà bạn muốn sau tham số runserver.

C:\Project\mysite>python manage.py runserver 8080

Đoạn code trên sẽ chạy server trên port 8080.

Cũng giống như các web server khác, vì Python cũng là một ngôn ngữ thông dịch nên khi bạn viết code xong và bấm save, muốn test thì bạn chỉ cần refresh lại trang web trên trình duyệt là sẽ thấy sự thay đổi, bạn không cần phải khởi động lại server làm gì.

Tạo web app

Những gì chúng ta vừa làm ở trên là tạo một webserver cho project của chúng ta. Bây giờ chúng ta sẽ tạo ứng dụng web trên server này.

C:\Project\mysite>python manage.py startapp polls

Bạn mở một trình Command Prompt khác lên và trỏ đến thư mục chứa file manage.py, sau đó chạy file này với tham số startapp để tạo một ứng dụng web, ở đây chúng ta tạo một ứng dụng web với tên là polls, nếu như bạn chưa biết thì đây là một ứng dụng đặt câu hỏi và cho phép người dùng trả lời trắc nghiệm, thường dùng để làm các chương trình khảo sát người dùng… đây sẽ là ứng dụng mà chúng ta làm trong những bài đầu tiên.

Một thư mục với tên polls sẽ được tạo ra và có cấu trúc như sau:

polls/
    __init__.py
    admin.py
    apps.py
    migrations/
        __init__.py
    models.py
    tests.py
    views.py

Thư mục này chứa các file chuẩn của một ứng dụng web Django.

Sự khác nhau giữa một Project và một Web App là project thì bao gồm nhiều app, trong đó mỗi app thực hiện một công việc riêng biệt.

Tạo View

Trong thư mục polls có chứa một file tên là views.py, bản chất thì file này giống như một file .php, .asp… trong các ngôn ngữ khác vậy.

from django.http import HttpResponse

def index(request):
    response = HttpResponse()
    response.write("<h1>Welcome</h1>")
    response.write("This is the polls app")
    return response

Bên trong file views.py này chúng ta viết hàm index, hàm này trả về một đối tượng HttpResponse. Để gọi được tới hàm index này và lấy nội dung HTML thì chúng ta phải tạo đường dẫn URL tới file này.

polls/
    __init__.py
    admin.py
    apps.py
    migrations/
        __init__.py
    models.py
    tests.py
    urls.py
    views.py
Để tạo URL chúng ta tạo một file với tên urls.py bên trong thư mục polls.
from django.conf.urls import url 

from . import views 

 urlpatterns = [ 
     url(r'^$', views.index, name='index'),
]

Bên trong file urls.py chúng ta khai báo một đối tượng List có tên urlpatterns, Django sẽ tìm thông tin về các url trong list này, list này chứa các đối tượng url, mỗi url bao gồm 3 tham số, tham số thứ nhất là biểu thức chính quy (Regex – Regular Expression), tham số thứ hai là tên module và tên hàm sẽ trả về nội dung HTML, ở đây là hàm index trong module views (file views.py), tham số thứ 3 là tên biến toàn cục trong toàn bộ ứng dụng, trong đó 2 tham số đầu tiên là bắt buộc phải có.

Regex (biểu thức chính quy) là một chủ đề khá lớn, Regex cho phép chúng ta tạo ra các mẫu định dạng text dùng trong tìm kiếm, xác thực… ở đây Regex giúp tạo các đường dẫn tới các hàm trả về nội dung HTML một cách tự động. Để tạo các chuỗi Regex thì có một số quy luật sau đây:

  • ^ – bắt đầu regex
  • $ – kết thúc regex
  • \d – một kí tự số
  • + –  kí tự phía trước có thể lặp lại một hoặc nhiều lần
  • / – có một dấu /
  • () – gom nhóm một số kí tự nhất định lại với nhau

Chúng ta sẽ tìm hiểu thêm về Regex sau. Đối với trường hợp của chúng ta thì Regex chỉ đơn giản là ^$, tức là đường dẫn trắng, không có gì cả, khi nhập đường dẫn này thì trả về nội dung từ hàm views.index.

Sau khi đã có đường dẫn cho riêng polls, thì tiếp theo chúng ta phải tạo đường dẫn đến ứng dụng polls này nữa, chúng ta sẽ tạo đường dẫn cho từng ứng dụng trong file mysite/urls.py.

from django.conf.urls import include, url
from django.contrib import admin 

urlpatterns = [
    url(r'^polls/', include('polls.urls')),
    url(r'^admin/', admin.site.urls),
]

Bên trong file mysite/urls.py cũng có một list urlpatterns, list này chứa các đối tượng url dẫn đến các ứng dụng web khác mà chúng ta viết.

Ở đây tham số thứ 2 chúng ta không chỉ đến một hàm index nào đó trong một module nào đó, mà chúng ta chỉ đến một module có chứa đối tượng urlpatterns khác, trong trường  hợp này là polls.urls, nên chúng ta phải dùng đến hàm include(), đối với các url chỉ đến một module url khác thì chuỗi regex không kết thúc bằng dấu $, hay nói cách khác là không có kết thúc.

Khi chúng ta gõ localhost:8000 thì django sẽ đọc các url trong file mysite/urls.py, trong file này chúng ta có một đường dẫn đến admin/, đường dẫn này trỏ đến hàm get_url() trong module admin.site của django, do đó bạn có thể gõ localhost:8000/admin và django sẽ trả về một trang đăng nhập mẫu (chúng ta sẽ tìm hiểu về trang admin này sau), url thứ hai là đường dẫn polls/ trỏ đến một file urls.py khác ở module polls.urls do chúng ta tự viết, file này lại chứa một list urlpatterns khác chứa các đường dẫn đến các hàm trả về HTML riêng của chúng, thế nên chúng ta có thể gõ localhost:8000/polls, và django sẽ trả về trang HTML tạo ra từ hàm index() trong module polls.urls.

Capture

Bạn có thể điều chỉnh chuỗi Regex để hiểu thêm.

Django – Giới thiệu

Django là một web framework miễn phí mã nguồn mở được viết bằng Python. Django sử dụng mô hình Model-View-Control (MVC). Django được phát triển bởi Django Software Foundation(DSF) – một tổ chức phi lợi nhuận độc lập.

Mục tiêu chính của Django là đơn giản hóa việc tạo các website phức tạp có sử dụng cơ sở dữ liệu. Django tập trung vào tính năng “có thể tái sử dụng” và “có thể tự chạy” của các component, tính năng phát triển nhanh, không làm lại những gì đã làm. Một số website phổ biến được xây dựng từ Django là Pinterest, Instagram, Mozilla, và Bitbucket.

Cài đặt Django

Để có thể sử dụng Django thì bạn nhất định phải cài Python trong máy mình rồi, và khi cài thì Python có kèm theo một chương trình có tên là pipđây là một phần mềm quản lý các gói mở rộng dành cho Python. Để cài đặt Django thì bạn sẽ dùng đến pip.

Bạn mở Command Prompt (cmd) lên và gõ lệnh:

C:\User\PhoCode>pip install Django

để Python cài đặt gói Django mới nhất, gói này sẽ nằm trong thư mục Lib/site-packages trong thư mục cài đặt Python, hoặc gõ lệnh

C:\User\PhoCode>pip install Django==1.9.4

để cài đặt gói Django phiên bản 1.9.4, đây là phiên bản mà mình sử dụng để viết series này.

Nếu khi cài Python bạn không cài pip thì bạn có thể lên trên trang GitHub của Django để tải về tại địa chỉ https://github.com/django/django.git

Xem phiên bản Django

Sau khi cài đặt xong gói Django, bạn có thể kiểm tra một số thông tin của gói này.

import django

print(django.get_version())

Bằng cách dùng phương thức django.get_version().

1.9.4

Python – Cơ sở dữ liệu SQLite

Trong phần này chúng ta sẽ làm việc với cơ sở dữ liệu SQLite.

Python cung cấp sẵn module sqlite3 hỗ trợ kết nối và thao tác với CSDL SQLite.

C:\User\PhoCode>python
Python 3.5.1 (v3.5.1:37a07cee5969, Dec 6 2015, 01:38:48) [MSC v.1900 32 bit (Intel)] 
on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import sqlite3
>>> sqlite3.version
'2.6.0'
>>> sqlite3.sqlite_version
'3.8.11'

Chúng ta có thể kiểm tra một số thông tin về module sqlite3.

Trước khi bắt đầu chúng ta cần có một CSDL để test.

C:\User\PhoCode>sqlite3 test.db
SQLite version 3.7.13 2012-06-11 02:05:22
Enter ".help" for instructions
Enter SQL statements terminated with a ";"

Chúng ta tạo CSDL test.db.

Xem phiên bản SQLite

Trong ví dụ dưới đây chúng ta xem phiên bản CSDL SQLite.

import sqlite3 as lite
import sys
import os
con = None

try:
    path = os.path.dirname(__file__) + "\\test.db"
    con = lite.connect(path)
    
    cur = con.cursor()    
    cur.execute('SELECT SQLITE_VERSION()')
    
    data = cur.fetchone()
    
    print ("SQLite version: %s" % data)         
    
except lite.Error as e:
    
    print ("Error %s:" % e.args[0])
    sys.exit(1)
    
finally:
    
    if con:
        con.close()

Chúng ta kết nối với CSDL test.db và thực thi câu truy vấn lấy thông tin về phiên bản SQLite đang sử dụng.

import sqlite3 as lite

Đầu tiên chúng ta import module sqlite3.

con = None

Biến con là biến lưu trữ đối tượng Connection khi chúng ta kết nối CSDL.

path = os.path.dirname(__file__) + "\\test.db"
con = lite.connect(path)

Để kết nối đến CSDL thì chúng ta dùng phương thức connect(), phương thức này trả về một đối tượng Connection.

cur = con.cursor()    
cur.execute('SELECT SQLITE_VERSION()')

Sau khi đã có đối tượng Connection, chúng ta lấy một đối tượng Cursor, đối tượng này làm nhiệm vụ duyệt qua các bản ghi trong tập dữ liệu được lấy về và thực thi các câu truy vấn. Để thực thi một câu truy vấn thì chúng ta dùng phương thức execute().

data = cur.fetchone()

Phương thức fetchone() lấy về dòng đầu tiên của bảng dữ liệu trả về.

print ("SQLite version: %s" % data)

Chúng ta in dòng dữ liệu đó ra màn hình.

finally:
    
    if con:
        con.close() 

Sau khi đã hoàn tất công việc thì chúng ta đóng kết nối tới CSDL với phương thức close().

SQLite version: 3.8.11

Trong ví dụ dưới đây, chúng ta cũng lấy phiên bản SQLite nhưng sử dụng từ khóa with.

import sqlite3 as lite
import sys
import os

path = os.path.dirname(__file__) + "\\test.db"
con = lite.connect(path)

with con:
    
    cur = con.cursor()    
    cur.execute('SELECT SQLITE_VERSION()')
    
    data = cur.fetchone()
    
    print ("SQLite version: %s" % data)

Dùng từ khóa with có tác dụng làm cho code dễ chịu hơn và các câu lệnh SQL có liên quan đến việc cập nhật dữ liệu như INSERT, UPDATE, DELETE… sẽ tự động được thực thi (nếu không thì bạn phải gọi thêm phương thức commit() thì mới thực sự thực thi câu truy vấn lên CSDL).

with con:

Python sẽ tự động xử lý exception và tự động ngắt kết nối CSDL khi không dùng nữa nếu có từ khóa with.

INSERT

Chúng ta sẽ thực thi câu truy vấn INSERT.

import sqlite3 as lite
import sys
import os
path = os.path.dirname(__file__) + "\\test.db"
con = lite.connect(path)

with con:
    
    cur = con.cursor()    
    cur.execute("CREATE TABLE Cars(Id INT, Name TEXT, Price INT)")
    cur.execute("INSERT INTO Cars VALUES(1,'Audi',52642)")
    cur.execute("INSERT INTO Cars VALUES(2,'Mercedes',57127)")
    cur.execute("INSERT INTO Cars VALUES(3,'Skoda',9000)")
    cur.execute("INSERT INTO Cars VALUES(4,'Volvo',29000)")
    cur.execute("INSERT INTO Cars VALUES(5,'Bentley',350000)")
    cur.execute("INSERT INTO Cars VALUES(6,'Citroen',21000)")
    cur.execute("INSERT INTO Cars VALUES(7,'Hummer',41400)")
    cur.execute("INSERT INTO Cars VALUES(8,'Volkswagen',21600)")

Đoạn code trên tạo bảng Cars và insert 8 dòng dữ liệu vào bảng này.

cur.execute("CREATE TABLE Cars(Id INT, Name TEXT, Price INT)")

Bảng Cars sẽ có 3 cột là Id, NamePrice.

cur.execute("INSERT INTO Cars VALUES(1,'Audi',52642)")
cur.execute("INSERT INTO Cars VALUES(2,'Mercedes',57127)")

Chúng ta chỉ cần dùng phương thức execute() để thực thi các câu lệnh SQL. Khi dùng từ khóa with thì các câu lệnh này sẽ được thực thi ngay trên CSDL.

sqlite> .mode column  
sqlite> .headers on
sqlite> SELECT * FROM Cars;
Id          Name        Price     
----------  ----------  ----------
1           Audi        52642     
2           Mercedes    57127     
3           Skoda       9000      
4           Volvo       29000     
5           Bentley     350000    
6           Citroen     21000     
7           Hummer      41400     
8           Volkswagen  21600 

Các câu lệnh như UPDATE, DELETE… bạn cũng làm tương tự.

Phương thức executemany()

Phương thức executemany() tiện lợi hơn bằng cách thực thi nhiều câu lệnh cùng một lúc.

import sqlite3 as lite
import sys
import os
cars = (
    (1, 'Audi', 52642),
    (2, 'Mercedes', 57127),
    (3, 'Skoda', 9000),
    (4, 'Volvo', 29000),
    (5, 'Bentley', 350000),
    (6, 'Hummer', 41400),
    (7, 'Volkswagen', 21600)
)


path = os.path.dirname(__file__) + "\\test.db"
con = lite.connect(path)

with con:
    
    cur = con.cursor()    
    
    cur.execute("DROP TABLE IF EXISTS Cars")
    cur.execute("CREATE TABLE Cars(Id INT, Name TEXT, Price INT)")
    cur.executemany("INSERT INTO Cars VALUES(?, ?, ?)", cars)

Chúng ta xóa bảng và tạo lại bảng Cars.

cur.execute("DROP TABLE IF EXISTS Cars")
cur.execute("CREATE TABLE Cars(Id INT, Name TEXT, Price INT)")

Đầu tiên chúng ta kiểm tra xem bảng Cars đã tồn tại chưa, nếu rồi thì xóa bảng đó và tạo lại bảng mới.

cur.executemany("INSERT INTO Cars VALUES(?, ?, ?)", cars)

Chúng ta insert 8 dòng dữ liệu vào bảng bằng một phương thức duy nhất là executemany(), tham số đầu tiên là một câu lệnh SQL có tham số là các dấu ?, tham số thứ 2 là một tuple chứa nhiều tuple khác là các dữ liệu cần truyền vào.

SELECT

import sqlite3 as lite
import sys
import os

path = os.path.dirname(__file__) + "\\test.db"
con = lite.connect(path)

with con:    
    
    cur = con.cursor()    
    cur.execute("SELECT * FROM Cars")

    rows = cur.fetchall()

    for row in rows:
        print (row)

Chúng ta sẽ lấy các bản ghi từ bảng Cars.

cur.execute("SELECT * FROM Cars")

Việc này cũng rất đơn giản, chỉ cần dùng phương thức execute() với câu SQL tương ứng.

rows = cur.fetchall()

Phương thức fetchall() sẽ trả về một tuple chứa các tuple là các dòng dữ liệu trong bảng.

for row in rows:
    print (row)

Chúng ta in các tuple đó ra màn hình.

(1, u'Audi', 52642)
(2, u'Mercedes', 57127)
(3, u'Skoda', 9000)
(4, u'Volvo', 29000)
(5, u'Bentley', 350000)
(6, u'Citroen', 21000)
(7, u'Hummer', 41400)
(8, u'Volkswagen', 21600)

Bạn cũng có thể in từng dòng một nếu muốn.

import sqlite3 as lite
import sys
import os

path = os.path.dirname(__file__) + "\\test.db"
con = lite.connect(path)

with con:
    
    cur = con.cursor()    
    cur.execute("SELECT * FROM Cars")

    while True:
      
        row = cur.fetchone()
        
        if row == None:
            break
            
        print (row[0], row[1], row[2])

Chúng ta lấy từng dòng và in chúng ra màn hình.

while True:

Chúng ta dùng một vòng lặp để lặp qua từng dòng dữ liệu trong bảng. Vòng lặp kết thúc khi đối tượng Cursor đã đọc hết dữ liệu trong bảng.

row = cur.fetchone()

if row == None:
    break

Đối tượng Cursor có chứa một con trỏ chỉ đến các dòng trong bảng. Phương thức fetchone() sẽ đẩy con trỏ này lên một dòng và trả về dữ liệu của dòng đó, nếu con trỏ chỉ qua dòng cuối cùng thì sẽ trả về một đối tượng None.

print (row[0], row[1], row[2])

Dữ liệu trả về là một tuple nên bạn có thể truy xuất từng phần tử trong tuple bằng cặp dấu [].

1 Audi 52642
2 Mercedes 57127
3 Skoda 9000
4 Volvo 29000
5 Bentley 350000
6 Citroen 21000
7 Hummer 41400
8 Volkswagen 21600

Lấy phần tử thông qua tên cột

Như các ví dụ trên, dữ liệu trả về là một tuple chứa các tuple, nhưng bạn có thể quy định dữ liệu trả về dạng Dictionary, bằng cách đó bạn có thể truy cập vào các cột thông qua tên cột chứ không cần dùng chỉ số nữa.

import sqlite3 as lite
import os

path = os.path.dirname(__file__) + "\\test.db"
con = lite.connect(path)   

with con:
    
    con.row_factory = lite.Row
       
    cur = con.cursor() 
    cur.execute("SELECT * FROM Cars")

    rows = cur.fetchall()

    for row in rows:
        print ("%s %s %s" % (row["Id"], row["Name"], row["Price"]))

Trong ví dụ này chúng ta sẽ lấy dữ liệu về dạng Dictionary.

con.row_factory = lite.Row

Để dữ liệu trả về là Dictionary thì chúng ta thiết lập thuộc tính row_factory trong đối tượng Connectionsqlite3.Row.

for row in rows:
    print ("%s %s %s" % (row["Id"], row["Name"], row["Price"]))

Dữ liệu trả về kiểu Dictionary và bạn có thể truy xuất dữ liệu của các ô thông qua tên cột.

Truyền tham số vào câu truy vấn

Truyền tham số vào câu truy vấn giúp tăng tốc độ thực thi câu truy vấn và đảm bảo an toàn cho ứng dụng khỏi kiểu tấn công SQL Injection.

import sqlite3 as lite
import sys
import os
uId = 1
uPrice = 62300 

path = os.path.dirname(__file__) + "\\test.db"
con = lite.connect(path) 

with con:

    cur = con.cursor()    

    cur.execute("UPDATE Cars SET Price=? WHERE Id=?", (uPrice, uId))        
    con.commit()
    
    print ("Number of rows updated: %d" % cur.rowcount)

Chúng ta thực thi câu lệnh UPDATE và dùng tham số trong câu lệnh SQL.

cur.execute("UPDATE Cars SET Price=? WHERE Id=?", (uPrice, uId)) 

Các tham số trong câu truy vấn được đại diện bằng dấu ?.

print ("Number of rows updated: %d" % cur.rowcount)

Ngoài ra trong đối tượng Cursor có thuộc tính rowcount chứa số lượng các dòng dữ liệu vừa được cập nhật.

Number of rows updated: 1

Python – Lập trình mạng với Socket

Python cung cấp module Socket hỗ trợ thực thi các giao thức mạng, dùng tạo các ứng dụng server hoặc client.

Chúng ta sẽ viết một ứng dụng Echo Server đơn giản.

Server

import socket

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print ("Starting server on port 10000")
server.bind((socket.gethostname(), 10000))
server.listen(1)

while True:
    conn, client = server.accept()
    try:
       print ("Connection from", client)
 
    while True:
        data = conn.recv(1024)
        print ("Receive from client:", data)
        if data:
            print ("Response to client")
            conn.sendall(data)
        else:
            print ("No data received")
            break
    finally:
        conn.close()

Server sẽ lắng nghe nghe các kết nối từ client, nhận tin nhắn từ client nếu có và gửi trả lời về lại client.

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

Đầu tiên chúng ta tạo một đối tượng socket, tham số AF_INET cho biết chúng ta sử dụng IP v4, SOCK_TREAM là dùng giao thức TCP. Ngoài ra còn một số giá trị khác như AF_INET6 là dùng IP v6, AF_UNIX là chỉ kết nối các ứng dụng trong một máy (không dùng mạng), SOCK_DGRAM là dùng giao thức UDP.

print("Starting server on port 10000")
server.bind((socket.gethostname(), 10000))

Phương thức bind() chỉ định socket sẽ lắng nghe với địa chỉ IP của máy lấy từ phương thức gethostname() trên cổng 10000.

server.listen(1)

while True:
    conn, client = server.accept()

Phương thức listen() cho python biết socket này là một server, tham số của phương thức này là số lượng các kết nối có thể có trong hàng đợi, ít nhất là 0 và cao nhất là do hệ điều hành chỉ định (thường là 5). Phương thức accept() sẽ đưa server vào trạng thái chờ đợi cho đến khi có kết nối thì sẽ trả về một tuple gồm có một socket khác dùng để truyền dữ liệu qua lại với client và một tuple nữa bao gồm địa chỉ ip và port của ứng dụng client.

while True:
    data = conn.recv(1024)
    print ("Receive from client:", data)
    if data:
        print ("Response to client")
        conn.sendall(data)
    else:
        print ("No data received")
        break

Phương thức recv() sẽ đọc các byte dữ liệu có trong socket conn, tham số 1024 tức là mỗi lần chỉ đọc tối ta 1024 byte dữ liệu, nên chúng ta đặt trong vòng lặp while để có thể đọc hết những gì client gửi sang, nếu có dữ liệu gửi sang thì chúng ta gửi trả lời về client thông qua phương thức sendall()ở đây chúng ta chỉ đơn giản là gửi lại những gì client đã gửi lên server thôi.

finally:
    conn.close()

Khi đã đọc hết dữ liệu từ client, chúng ta break vòng lặp và ngắt kết nối bằng phương thức close().

Client

import socket

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect((socket.gethostname(), 10000))
try:
    message = "Hello from client"
    client.sendall(message.encode('ascii'))

    amount_received = 0
    amount_expected = len(message)

    while amount_received < amount_expected:
        data = client.recv(1024)
        amount_received += len(data)
        print ("Response from server:", data)
finally:
    print ("Closing socket")
    client.close()

Chúng ta cũng tạo một đối tượng socket kết nối và gửi tin nhắn lên server.

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('localhost', 10000))

Đối tượng socket ở client cũng sử dụng IP v4 và giao thức TCP. Phương thức connect() sẽ chỉ định cho python biết đây là một ứng dụng client và tạo kết nối đến server.

message = "Hello from client"
client.sendall(message.encode('ascii'))

Sau khi đã kết nối đến server, chúng ta gửi tin nhắn lên bằng phương thức sendall(), dữ liệu truyền đi chỉ là các bit mà trong Python 3 thì mặc định chuỗi luôn luôn là Unicode nên chúng ta phải mã hóa chuỗi này sang ASCII bằng phương thức encode() trước (để giải mã thì chúng ta dùng phương thức decode('ascii')).

finally:
    print ("Closing socket")
    client.close()

Cũng tương tự như server, khi đã kết thúc công việc chúng ta phải ngắt kết nối socket nếu không sẽ lãng phí tài nguyên. Mặc định thì khi kết thúc một chương trình python thì bộ thu gom rác của python đã tự động ngắt kết nối socket rồi nhưng chúng ta nên tự ngắt thì tốt hơn.

C:\User\Python>python server.py
Starting server on port 10000
Connecting from ('192.168.0.103', 56419)
Receive from client: b'Hello from client'
Response to client
Receive from client: b''
No data received
C:\User\Python\python client.py
Response from server: b'Hello from client'
Closing socket

Tkinter – Trò chơi rắn săn mồi

Trong phần này chúng ta viết lại một game rất nổi tiếng đó là game rắn săn mồi (Snake).

Snake

Game này được phát hành vào những năm 70 trên các hệ máy cầm tay, một thời gian sau được đưa lên PC. Trong game người chơi sẽ điều khiển một con rắn. Mục tiêu của người chơi là cố gắng cho con rắn ăn được càng nhiều mồi càng tốt. Mỗi lần con rắn ăn mồi, cơ thể nó sẽ dài ra. Game kết thúc khi người chơi cho rắn “ăn” phải tường hoặc cơ thể chính mình.

Mô tả game

Kích thước của mỗi đốt trên con rắn là 10px. Con rắn sẽ được điều khiển bằng các phím mũi tên. Khi game kết thúc, dòng chữ “Game Over” sẽ hiện lên giữa màn hình.

import sys
import random
from PIL import Image, ImageTk
from tkinter import Tk, Frame, Canvas, ALL, NW

WIDTH      = 300
HEIGHT     = 300
DELAY      = 100
DOT_SIZE   = 10
ALL_DOTS   = 900 #WIDTH * HEIGHT / (DOT_SIZE * DOT_SIZE)
RAND_POS   = 27

x = [0] * ALL_DOTS
y = [0] * ALL_DOTS

class Board(Canvas):
   def __init__(self, parent):
       Canvas.__init__(self, width=WIDTH, height=HEIGHT, 
                       background="black", highlightthickness=0)
       self.parent = parent
       self.initGame()
       self.pack()
 
   def initGame(self):
       self.right = True
       self.left = False
       self.up = False
       self.down = False
 
       self.inGame = True
       self.dots = 3
 
       self.apple_x = 100
       self.apple_y = 190
 
       for i in range(self.dots):
           x[i] = 50 - i * 10
           y[i] = 50 
 
       try: 
           self.dotImage = Image.open("dot.png")
           self.dotImage.thumbnail((10, 10), Image.ANTIALIAS)
           self.dot = ImageTk.PhotoImage(self.dotImage)
 
           self.headImage = Image.open("head.png")
           self.headImage.thumbnail((10, 10), Image.ANTIALIAS) 
           self.head = ImageTk.PhotoImage(self.headImage)
 
           self.appleImage = Image.open("apple.png")
           self.appleImage.thumbnail((10, 10), Image.ANTIALIAS) 
           self.apple = ImageTk.PhotoImage(self.appleImage)
 
       except IOError as e:
           print (e)
           sys.exit(1)
 
       self.createObjects()
       self.locateApple()
       self.bind_all("<Key>", self.onKeyPressed)
       self.after(DELAY, self.onTimer)
 
   def createObjects(self):
       self.create_image(self.apple_x, self.apple_y, image=self.apple,
       anchor=NW, tag="apple")
       self.create_image(x[0], y[0], image=self.head, anchor=NW, tag="head")
       self.create_image(x[1], y[1], image=self.dot, anchor=NW, tag="dot")
       self.create_image(x[2], y[2], image=self.dot, anchor=NW, tag="dot") 
 
   def locateApple(self):
       apple = self.find_withtag("apple")
       self.delete(apple[0])
 
       r = random.randint(0, RAND_POS)
       self.apple_x = r * DOT_SIZE
       r = random.randint(0, RAND_POS)
       self.apple_y = r * DOT_SIZE
 
       self.create_image(self.apple_x, self.apple_y, image=self.apple,
                         anchor=NW, tag="apple") 
 
   def doMove(self):
       dots = self.find_withtag("dot")
       head = self.find_withtag("head")
 
       items = dots + head
 
       z = 0
 
       while z < len(items) - 1:
           c1 = self.coords(items[z])
           c2 = self.coords(items[z + 1])
           self.move(items[z], c2[0]-c1[0], c2[1]-c1[1])
           z += 1
 
       if self.left:
           self.move(head, -DOT_SIZE, 0)
 
       if self.right:
           self.move(head, DOT_SIZE, 0)
 
       if self.up:
           self.move(head, 0, -DOT_SIZE)
 
       if self.down:
           self.move(head, 0, DOT_SIZE)
 
   def checkCollisions(self):
       dots = self.find_withtag("dot")
       head = self.find_withtag("head")
 
       x1, y1, x2, y2 = self.bbox(head)
       overlap = self.find_overlapping(x1, y1, x2, y2)
 
       for dot in dots:
           for over in overlap:
               if over == dot:
                   self.inGame = False
 
       if x1 < 0: 
           self.inGame = False 
       if x1 > WIDTH - DOT_SIZE:
           self.inGame = False
       if y1 < 0: 
           self.inGame = False 
       if y1 > HEIGHT - DOT_SIZE:
           self.inGame = False
 
   def checkApple(self):
       apple = self.find_withtag("apple")
       head = self.find_withtag("head")
 
       x1, y1, x2, y2 = self.bbox(head)
       overlap = self.find_overlapping(x1, y1, x2, y2)
 
       for ovr in overlap:
           if apple[0] == ovr:
               x, y = self.coords(apple)
               self.create_image(x, y, image=self.dot, anchor=NW, tag="dot")
               self.locateApple()
 
   def onTimer(self): 
       if self.inGame:
           self.checkCollisions()
           self.checkApple()
           self.doMove()
           self.after(DELAY, self.onTimer)
       else:
           self.gameOver()
 
   def onKeyPressed(self, e):
       key = e.keysym
 
       if key == "Left" and not self.right:
          self.left = True
          self.up = False
          self.down = False
 
       if key == "Right" and not self.left:
           self.right = True
           self.up = False
           self.down = False
 
       if key == "Up" and not self.down:
           self.up = True
           self.right = False
           self.left = False
 
       if key == "Down" and not self.up:
           self.down = True
           self.right = False
           self.left = False
 
      if key == "Escape":
          self.quit()
 
   def gameOver(self):
      self.delete(ALL)
      self.create_text(self.winfo_width() / 2, self.winfo_height() / 2, 
                       text="Game Over", fill="white")
 
class Snake(Frame):
   def __init__(self, parent):
       Frame.__init__(self, parent)
       parent.title("Snake")
       self.board = Board(parent)
       self.pack()
 
root = Tk()
snake = Snake(root)
root.mainloop()

Đầu tiên chúng ta định nghĩa một số hằng số dùng trong game:

  • WIDTHHEIGHT là kích thước cửa sổ chính.
  • DOT_SIZE là kích thước của mồi và mỗi đốt trên con rắn.
  • ALL_DOTS là số lượng đốt tối đa của rắn. Vì cửa sổ có kích thước 300 * 300, mỗi đốt rắn có kích thước 10 * 10 nến số lượng đốt tối đa là (300 * 300) / (10 * 10) = 900.
  • RAND_POS là hằng số để tính vị trí ngẫu nhiên của mồi.
  • DELAY là tốc độ của game.
x = [0] * ALL_DOTS
y = [0] * ALL_DOTS

Tiếp theo chúng ta tạo 2 mảng x và y, hai mảng này lưu trữ tọa độ của tất cả các đốt trên thân con rắn.

Kế tiếp là phương thức initGame(), nhiệm vụ của phương thức này là khởi tạo toàn bộ mọi thứ có trong game, từ khởi tạo biến, load ảnh đến khởi động timer…

try: 
    self.dotImage = Image.open("dot.png")
    self.dotImage.thumbnail((10, 10), Image.ANTIALIAS)
    self.dot = ImageTk.PhotoImage(self.dotImage)
 
    self.headImage = Image.open("head.png")
    self.headImage.thumbnail((10, 10), Image.ANTIALIAS) 
    self.head = ImageTk.PhotoImage(self.headImage)
 
    self.appleImage = Image.open("apple.png")
    self.appleImage.thumbnail((10, 10), Image.ANTIALIAS) 
    self.apple = ImageTk.PhotoImage(self.appleImage)
 
except (IOError, e):
    print (e)
    sys.exit(1)

Đoạn code trên thực hiện load ảnh dùng để hiển thị mồi và các đốt của con rắn. Do mỗi đốt của con rắn và mồi có kích thước 10×10 pixel nên chúng ta nên resize kích thước ảnh về 10×10 phòng trường hợp ảnh của chúng ta quá lớn bằng phương thức thumbnail().

self.createObjects()
self.locateApple()

Phương thức createObjects() có nhiệm vụ vẽ các đối tượng lên canvas. Phương thức locateApple() tạo tọa độ ngẫu nhiên của mồi và vẽ lên canvas.

self.bind_all("<Key>", self.onKeyPressed)

Tiếp theo chúng ta gọi phương thức bind_all() để gắn sự kiện bấm phím vào phương thức onKeyPressed().

def createObjects(self):

    self.create_image(self.apple_x, self.apple_y, image=self.apple,
        anchor=NW, tag="apple")
    self.create_image(50, 50, image=self.head, anchor=NW,  tag="head")
    self.create_image(30, 50, image=self.dot, anchor=NW, tag="dot")
    self.create_image(40, 50, image=self.dot, anchor=NW, tag="dot")

Bên trong phương thức createObjects() chúng ta vẽ các đối tượng lên canvas bao gồm mồi và 3 đốt đầu của con rắn. Ý nghĩa của các tham số đã được giải thích trong bài viết trước ngoại trừ tham số tag. Tham số tag đơn giản là giống như chúng ta đưa một ID cho ảnh vậy thôi, về sau chúng ta sẽ có các phương thức tìm các đối tượng này nhờ vào tag.

Bên trong phương thức checkApple() chúng ta kiểm tra xem con rắn có ăn trúng mồi hay không, nếu có thì thêm một đốt vào sau con rắn và gọi đến phương thức locateApple() để khởi tạo mồi mới.

apple = self.find_withtag("apple")
head = self.find_withtag("head")

Phương thức find_withtag() sẽ tìm đối tượng trên canvas dựa vào tag của đối tượng đó. Ở đây chúng ta cần tìm 2 đối tượng là mồi và đầu của con rắn – đốt đầu tiên.

x1, y1, x2, y2 = self.bbox(head)
overlap = self.find_overlapping(x1, y1, x2, y2)

Phương thức bbox() trả về một tuple là tọa độ điểm trái-trên và phải-dưới của một đối tượng. Phương thức find_overlapping() sẽ tìm các đối tượng nằm đè lên nhau. Tức là ở đây chúng ta kiểm tra xem đầu con rắn có chạm vào mồi hay không.

for ovr in overlap:
  
    if apple[0] == ovr:
        x, y = self.coords(apple)
        self.create_image(x, y, image=self.dot, anchor=NW, tag="dot")
        self.locateApple()

Nếu đầu con rắn chạm vào mồi thì chúng ta vẽ một đốt mới tại vị trí của mồi sau đó gọi đến phương thức locateApple() để khởi tạo mồi mới.

Bên trong phương thức doMove() chúng ta kiểm tra hướng đi hiện tại của con rắn và cập nhật tọa độ của tất cả các đốt trên con rắn.

z = 0
while z < len(items)-1:
    c1 = self.coords(items[z])
    c2 = self.coords(items[z+1])
    self.move(items[z], c2[0]-c1[0], c2[1]-c1[1])
    z += 1

Tọa độ của các đốt phía sau sẽ bằng tọa độ của đốt liền trước đó. Riêng tọa độ của đầu thì dựa trên hướng đi hiện tại.

Trong phương thức checkCollisions() chúng ta kiểm tra xem con rắn có cắn phải mình hay đụng tường hay không.

x1, y1, x2, y2 = self.bbox(head)
overlap = self.find_overlapping(x1, y1, x2, y2)

for dot in dots:
    for over in overlap:
        if over == dot:
          self.inGame = False

Nếu có thì chúng ta cho thuộc tính inGameFalse tức là kết thúc game.

if y1 > HEIGHT - DOT_SIZE:
    self.inGame = False

Phương thức locateApple() sẽ tạo ngẫu nhiên mồi mới trên màn hình.

apple = self.find_withtag("apple")
self.delete(apple[0])

Trước hết chúng ta xóa mồi cũ đi.

r = random.randint(0, RAND_POS)

Sau đó dùng phương thức randint() để lấy một số ngẫu nhiên từ 0 đến RAND_POS – 1.

self.apple_x = r * DOT_SIZE
...
self.apple_y = r * DOT_SIZE

Hai dòng trên set tọa độ của mồi.

Trong phương thức onKeyPressed() chúng ta kiểm tra sự kiện bấm phím.

if key == "Left" and not self.right: 
    self.left = True
    self.up = False
    self.down = False

Trong trường hợp trên chúng ta kiểm tra nếu người dùng bấm phim mũi trên trái và hướng đi hiện tại của con rắn không phải hướng bên phải thì set các thuộc tính leftTrue, các thuộc tính còn lại là False. Tương tự với các hướng còn lại.

def onTimer(self):

    if self.inGame:
        self.checkCollisions()
        self.checkApple()
        self.doMove()
        self.after(DELAY, self.onTimer)
    else:
        self.gameOver() 

Bên trong phương thức onTimer(), chúng ta thực hiện một số công việc cứ sau 100 mili giây (lưu trong hằng số DELAY). Chúng ta kiểm tra sự va chạm của con rắn với mồi hay tường… cập nhật tọa độ con rắn rồi gọi phương thức after() một lần nữa để đồng hồ chạy tiếp 100ms và lại tiếp tục kiểm tra. Nếu vì một lý do gì đó mà thuộc tính inGame là False thì chúng ta gọi phuonwg thức gameOver().

def gameOver(self):

    self.delete(ALL)
    self.create_text(self.winfo_width()/2, self.winfo_height()/2, 
        text="Game Over", fill="white")     

Bên trong phương thức gameOver() chúng ta xóa các đối tượng ra khỏi bộ nhớ và in dòng chữ “Game Over” lên giữa màn hình.

Untitled

Tkinter – Đồ họa

Trong phần này chúng ta sẽ học cách vẽ các đối tượng hình học trong Tkinter bằng cách sử dụng widget Canvas.

Vẽ đoạn thẳng

Để vẽ đoạn thẳng thì chúng ta dùng phương thức create_line() của lớp CanvasBạn cứ hiểu Canvas giống như một tờ giấy để chúng ta vẽ mọi thứ trên đó, nếu bạn đã từng lập trình HTML5 thì chắc không còn xa lạ gì với canvas.

from tkinter import Tk, Canvas, Frame, BOTH

class Example(Frame):
   def __init__(self, parent):
       Frame.__init__(self, parent)
       self.parent = parent
       self.initUI()
 
   def initUI(self):
       self.parent.title("Lines")
       self.pack(fill=BOTH, expand=1)
 
       canvas = Canvas(self)
       canvas.create_line(15, 25, 200, 25)
       canvas.create_line(300, 25, 300, 200, dash=(4, 2))
       canvas.create_line(55, 85, 155, 85, 105, 180, 55, 85)
 
       canvas.pack(fill=BOTH, expand=1)
 
root = Tk()
ex = Example(root)
root.geometry("400x250+300+300")
root.mainloop()

Trong ví dụ trên chúng ta vẽ một vài đoạn thẳng lên canvas.

canvas = Canvas(self)

Đầu tiên chúng ta tạo đối tượng Canvas với tham số self để canvas bao phủ toàn bộ cửa sổ.

canvas.create_line(15, 25, 200, 25)

Phương thức create_line() nhận vào tham số là tọa độ x, y của điểm bắt đầu và điểm kết thúc của một đoạn thẳng.

canvas.create_line(300, 35, 300, 200, dash=(4, 2))

Tham số dash tùy chỉnh kiểu vẽ là các đường nét đứt, ở ví dụ trên, dash=(4, 2) nghĩa là vẽ các nốt dài 4 pixel cách nhau 2 pixel.

canvas.create_line(55, 85, 155, 85, 105, 180, 55, 85)

Bạn cũng có thể đưa vào nhiều điểm, dòng code trên đưa vào 3 điểm để vẽ một hình tam giác.

Capture

Vẽ màu

Màu trong máy tính là màu RGB, là tổ hợp của 3 giá trị đỏ (Red), xanh lá (Green) và xanh lam (Blue).

from tkinter import Tk, Canvas, Frame, BOTH

class Example(Frame):
   def __init__(self, parent):
       Frame.__init__(self, parent)
       self.parent = parent
       self.initUI()
 
   def initUI(self):
       self.parent.title("Colors")
       self.pack(fill=BOTH, expand=1)
 
       canvas = Canvas(self)
       canvas.create_rectangle(30, 10, 120, 80, outline="#fb0", fill="#fb0")
       canvas.create_rectangle(150, 10, 240, 80, outline="#f50", fill="#f50")
       canvas.create_rectangle(270, 10, 370, 80, outline="#05f", fill="#05f")
       canvas.pack(fill=BOTH, expand=1)
 
root = Tk()
ex = Example(root)
root.geometry("400x100+300+300")
root.mainloop()

Trong ví dụ trên chúng ta vẽ 3 hình chữ nhật với 3 màu khác nhau.

canvas.create_rectangle(30, 10, 120, 80, 
    outline="#fb0", fill="#fb0")

Phương thức create_rectangle() tạo một hình chữ nhật trên canvas với 4 tham số đầu tiên là tọa độ x, y của điểm trái-trên và điểm phải-dưới. Tham số outline là giá trị màu đường viền, tham số fill là giá trị màu của hình chữ nhật, tất cả đều ở dạng hexa.

Capture

Vẽ một số đối tượng hình học khác

from tkinter import Tk, Canvas, Frame, BOTH

class Example(Frame):
   def __init__(self, parent):
       Frame.__init__(self, parent)
       self.parent = parent
       self.initUI()
 
   def initUI(self):
       self.parent.title("Shapes")
       self.pack(fill=BOTH, expand=1)
 
       canvas = Canvas(self)
 
       canvas.create_oval(10, 10, 80, 80, outline="gray", fill="gray", width=2)
       canvas.create_oval(110, 10, 210, 80, outline="gray", fill="gray", width=2)
       canvas.create_rectangle(230, 10, 290, 60, outline="gray", fill="gray", width=2)
       canvas.create_arc(30, 200, 90, 100, start=0, extent=210, outline="gray", fill="gray", width=2)
 
       points = [150, 100, 200, 120, 240, 180, 210, 200, 150, 150, 100, 200]
       canvas.create_polygon(points, outline="gray", fill="gray", width=2)
 
       canvas.pack(fill=BOTH, expand=1)
 
root = Tk()
ex = Example(root)
root.geometry("330x220+300+300")
root.mainloop()

Trong ví dụ trên chúng ta vẽ 5 hình là hình tròn, hình elip, hình chữ nhật, hình quạt và một đa giác.

canvas.create_oval(10, 10, 80, 80, outline="red", 
        fill="green", width=2)

Phương thức create_oval() dùng để vẽ hình tròn. Bốn tham số đầu tiên là tọa độ x, y của điểm trái-trên và phải-dưới của hình tròn (bởi vì hình tròn trong Tkinter được nằm trong một hình chữ nhật).

canvas.create_arc(30, 200, 90, 100, start=0, 
    extent=210, outline="#f11", fill="#1f1", width=2)

Phương thức create_arc() vẽ hình quạt, 4 tham số đầu tiên cũng là tọa độ hình chữ nhật chứa hình quạt này. Tham số start là hướng mà quạt hướng tới, tham số extend là độ lớn của quạt, cả 2 tham số này đều có đơn vị độ (từ 0 đến 360 độ).

points = [150, 100, 200, 120, 240, 180, 210, 
    200, 150, 150, 100, 200]
canvas.create_polygon(points, outline='red', 
    fill='green', width=2)

Phương thức create_polygon() vẽ một đa giác, tham số là tọa độ x, y của các điểm, bạn có thể đưa các điểm này vào bằng tay như các phương thức khác hoặc lưu các điểm trong một list rồi đưa list này vào.

Capture

Hiển thị ảnh từ file

from tkinter import Tk, Canvas, Frame, BOTH, NW
from PIL import Image, ImageTk

class Example(Frame):
   def __init__(self, parent):
       Frame.__init__(self, parent)
        self.parent = parent
        self.initUI()
      
   def initUI(self):
       self.parent.title("Image")
       self.pack(fill=BOTH, expand=1)
 
       self.img = Image.open("C:\\tatras.jpeg")
       self.tatras = ImageTk.PhotoImage(self.img)
 
       canvas = Canvas(self, width=self.img.size[0]+20, height=self.img.size[1]+20)
       canvas.create_image(10, 10, anchor=NW, image=self.tatras)
       canvas.pack(fill=BOTH, expand=1)

root = Tk()
ex = Example(root)
root.mainloop()     

Ví dụ trên vẽ hình từ file lên canvas.

self.img = Image.open("tatras.jpg")
self.tatras = ImageTk.PhotoImage(self.img)

Ở đây chúng ta dùng lớp ImageImageTk từ module PIL. Module này không có sẵn trong Python mà bạn phải tự cài thêm vào.

canvas = Canvas(self, width=self.img.size[0]+20, height=self.img.size[1]+20)

Chúng ta tạo đối tượng canvas với chiều dài và chiều rộng của file cộng thêm 20 pixel.

canvas.create_image(10, 10, anchor=NW, image=self.tatras)

Để hiện hình từ file lên canvas chúng ta dùng phương thức create_image(). Hai tham số đầu tiên là tọa độ góc trái-trên của hình. Tham số anchor=NW có tác dụng cắt hình từ vị trí NW = North West = tây-bắc) cho đến hết góc phải-dưới của ảnh, tức là lấy nguyên toàn bộ ảnh, mặc định nếu không đặt tham số này thì chương trình chỉ hiển thị phần giữa (CENTER) của ảnh. Bạn có thể thử các giá trị khác như N, E, SE (nhớ import các hằng số vô trước)… dưới đây là sơ đồ của các hướng.

tkanchor

Tham số image là đối tượng ảnh cần hiển thị.

Capture

Ghi chữ lên canvas

from tkinter import Tk, Canvas, Frame, BOTH, W

class Example(Frame):
   def __init__(self, parent):
       Frame.__init__(self, parent)  
       self.parent = parent 
       self.initUI()
 
 
   def initUI(self):
 
       self.parent.title("Lyrics") 
       self.pack(fill=BOTH, expand=1)

       canvas = Canvas(self)
       canvas.create_text(20, 30, anchor=W, font="VNI-Dom 18", 
                          text="Most relationships seem so transitory")
       canvas.create_text(20, 60, anchor=W, font="VNI-Dom 18", 
                          text="They're good but not the permanent one")
       canvas.create_text(20, 130, anchor=W, font="VNI-Dom 18", 
                          text="Who doesn't long for someone to hold")
       canvas.create_text(20, 160, anchor=W, font="VNI-Dom 18", 
                          text="Who knows how to love without being told") 
       canvas.create_text(20, 190, anchor=W, font="VNI-Dom 18", 
                          text="Somebody tell me why I'm on my own") 
       canvas.create_text(20, 220, anchor=W, font="VNI-Dom 18", 
                          text="If there's a soulmate for everyone") 
       canvas.pack(fill=BOTH, expand=1)

 
root = Tk()
ex = Example(root)
root.geometry("420x250+300+300")
root.mainloop() 

Đoạn code trên thực hiện “vẽ” lời bài hát lên canvas.

canvas.create_text(20, 30, anchor=W, font="VNI-Dom 18",
                   text="Most relationships seem so transitory")

Chúng ta dùng phương thức  create_text() để hiển thị chữ lên canvas. Hai tham số đầu là tọa độ x, y của góc trái trên. Tham số font="VNI-Dom 18" tức là dùng font VNI-Dom với độ lớn là 18px. Tham số text là nội dung đoạn text được in lên canvas.

Capture

Tkinter – Hộp thoại

Trong bài này chúng ta sẽ học cách hiển thị các hộp thoại (Dialog) trong Tkinter.

Messagebox

Đây là kiểu hôp thoại dùng để hiển thị thông báo cho người dùng và đôi khi còn dùng để đưa ra yêu cầu chọn lựa cho người dùng.

from tkinter.ttk import Frame, Button
from tkinter import Tk, BOTH
import tkinter.messagebox as mbox

class Example(Frame):
    def __init__(self, parent):
        Frame.__init__(self, parent)
        self.parent = parent
        self.initUI()
 
    def initUI(self):
        self.parent.title("Message Boxes")
        self.pack()
 
        error = Button(self, text="Error", command=self.onError)
        error.grid(padx=5, pady=5)
        warning = Button(self, text="Warning", command=self.onWarn)
        warning.grid(row=1, column=0)
        question = Button(self, text="Question", command=self.onQuest)
        question.grid(row=0, column=1)
        inform = Button(self, text="Information", command=self.onInfo)
        inform.grid(row=1, column=1)
 
    def onError(self):
        mbox.showerror("Error", "Could not open file")
 
    def onWarn(self):
        mbox.showwarning("Warning", "Deprecated function call")
 
    def onQuest(self):
        mbox.askquestion("Question", "Are you sure to quit?")
      
    def onInfo(self):
        mbox.showinfo("Information", "Download completed")
 
root = Tk()
ex = Example(root)
root.geometry("300x150+300+300")
root.mainloop()

Trong ví dụ trên chúng ta hiển thị 4 button, mỗi button sẽ hiển thị một kiểu message box khác nhau.

import tkinter.messagebox as mbox

Để sử dụng được message box thì bạn import module tkinger.messagebox (hoặc tkMessageBox nếu bạn dùng Python phiên bản 2.x).

error = Button(self, text="Error", command=self.onError)

Ở dòng trên chúng tạo button Error, khi bấm vào button này phương thức onError() sẽ được gọi để hiển thị một message box thông báo lỗi.

def onError(self):
    box.showerror("Error", "Could not open file")

Để hiển thị hộp thoại thông báo lỗi thì chúng ta gọi phương thức showerror(). Ngoài thông báo lỗi thì Tkinter cung cấp cho chúng ta các kiểu messagebox khác như sau:

  • askokcancel(title, message, option)
  • askquestion(title, message, option)
  • askretrycancel(title, message, option)
  • askyesno(title, message, option)
  • showinfo(title, message, option)
  • showwarning(title, message, option)

Trong đó tham số option là tham số tùy chỉnh hộp thoại dùng để tùy chỉnh một số tính chất như chọn Icon cho hộp thoại, chọn cửa sổ cha… bạn có thể không cần quan tâm đến tham số này.

Các hộp thoại askokcancel, askretrycancel, và askyesno trả về giá trị True khi người dùng bấm vào nút “OK” hoặc “Yes“, False khi bấm vào “No” hoặc “Cancel“. Hộp thoại askquestion trả về giá trị “yes” khi bấm nút “Yes” và “no” khi bấm nút “No“.

Capture

Hộp thoại chọn màu (Color chooser)

Các hệ điều hành hay có sẵn hộp thoại chọn màu cho chúng ta sử dụng. Để hiển thị hộp thoại này chúng ta dùng module colorchooser (hoặc tkColorChooser nếu bạn dùng Python phiên bản 2.x).

from tkinter import Tk, Frame, Button, BOTH, SUNKEN
from tkinter.colorchooser import askcolor

class Example(Frame):
  def __init__(self, parent):
     Frame.__init__(self, parent)
 
     self.parent = parent
     self.initUI()
 
  def initUI(self):
     self.parent.title("Color chooser")
     self.pack(fill=BOTH, expand=1)
 
     self.btn = Button(self, text="Choose Color", command=self.onChoose)
     self.btn.place(x=30, y=30)
 
     self.frame = Frame(self, border=1, relief=SUNKEN, width=100, height=100)
     self.frame.place(x=160, y=30)
 
  def onChoose(self):
     (rgb, hx) = askcolor()
     self.frame.config(bg=hx)
 
root = Tk()
ex = Example(root)
root.geometry("300x150+300+300")
root.mainloop()

Trong ví dụ trên chúng ta hiển thị một button và một frame lên cửa sổ chính. Button sẽ mở một hộp thoại chọn màu, chọn màu nào thì màu đó sẽ hiện lên trên frame của chúng ta.

(rgb, hx) = askcolor()
self.frame.config(bg=hx)

Để hiển thị hộp thoại chọn màu thì chúng ta gọi đến hàm askcolor(). Hàm này sẽ trả về một tuple, bên trong tuple này có 2 phần tử là một tuple bao gồm 3 giá trị R-G-B và một giá trị màu ở dạng hexa, bạn có thể dùng hàm print(hx) bên trong phương thức onChoose() để biết giá trị hexa này. Chúng ta dùng giá tị hexa để đặt làm màu nền cho Frame.

Capture

Hộp thoại chọn file (File Dialog)

Trong ví dụ dưới đây chúng ta sẽ dùng hàm Open của module tkinter.filedialog để mở một File Dialog.
from tkinter import Frame, Tk, BOTH, Text, Menu, END
from tkinter.filedialog import Open

class Example(Frame):
    def __init__(self, parent):
        Frame.__init__(self, parent)
        self.parent = parent
        self.initUI()
 
    def initUI(self):
        self.parent.title("File dialog")
        self.pack(fill=BOTH, expand=1)
 
        menubar = Menu(self.parent)
        self.parent.config(menu=menubar)
 
        fileMenu = Menu(menubar)
        fileMenu.add_command(label="Open", command=self.onOpen)
        menubar.add_cascade(label="File", menu=fileMenu)
 
        self.txt = Text(self)
        self.txt.pack(fill=BOTH, expand=1)
 
    def onOpen(self):
         ftypes = [('Python files', '*.py'), ('All files', '*')]
         dlg = Open(self, filetypes = ftypes)
         fl = dlg.show()
 
     if fl != '':
         text = self.readFile(fl)
         self.txt.insert(END, text)
     
    def readFile(self, filename):
       f = open(filename, "r")
       text = f.read()
       return text
 
root = Tk()
ex = Example(root)
root.geometry("300x250+300+300")
root.mainloop()

Chúng ta sẽ tạo một menu cho phép mở file dialog và đọc nội dung của file được chọn vào lớp Text.

ftypes = [('Python files', '*.py'), ('All files', '*')]

Dòng trên là List lưu các định dạng file khác nhau để lọc file.

dlg = Open(self, filetypes = ftypes)
fl = dlg.show()

Để hiển thị file dialog thì chúng ta gọi hàm Open(). Hàm này trả về một string là đường dẫn đến file được chọn.

Capture