Author Archives: Phở Code

Django – Phân trang

Trong phần này chúng ta sẽ học cách phân trang. Django cung cấp lớp django.core.paginator hỗ trợ phân trang rất tốt.

Ví dụ

Chúng ta tạo một app có tên là pagination.

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

Thêm app vào danh sách INSTALLED_APPS:

INSTALLED_APPS = [
    #...
    'pagination',
    #...
]

Tiếp theo chúng ta tạo model và thêm vào một số dòng dữ liệu mẫu để test.

from django.db import models

# Create your models here.

class Customer(models.Model):
    name = models.CharField(max_length=100)
    country = models.CharField(max_length=20)

Chúng ta định nghĩa model Customer có 2 trường là namecountry.

Cập nhật lại CSDL:

python manage.py makemigrations
python manage.py migrate

Đoạn script sau sẽ tạo một số dòng dữ liệu mẫu trong bảng pagination_customer.

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

from pagination.models import Customer

Customer.objects.create(name='Alfreds Futterkiste', country='Germany')
Customer.objects.create(name='Ana Trujillo Emparedados y helados', country='Mexico')
Customer.objects.create(name='Antonio Moreno Taquería', country='Mexico')
Customer.objects.create(name='Around the Horn', country='UK')
Customer.objects.create(name='Berglunds snabbköp', country='Sweden')
Customer.objects.create(name='Blauer See Delikatessen', country='Germany')
Customer.objects.create(name='Blondel père et fils', country='France')
Customer.objects.create(name='Bólido Comidas preparadas', country='Spain')
Customer.objects.create(name='Bon app', country='France')
Customer.objects.create(name='Bottom-Dollar Marketse', country='Canada')
Customer.objects.create(name='Bs Beverages', country='UK')
Customer.objects.create(name='Cactus Comidas para llevar', country='Argentina')
Customer.objects.create(name='Centro comercial Moctezuma', country='Mexico')
Customer.objects.create(name='Chop-suey Chinese', country='Switzerland')
Customer.objects.create(name='Comércio Mineiro', country='Brazil')
Customer.objects.create(name='Consolidated Holdings', country='UK')
Customer.objects.create(name='Drachenblut Delikatessend', country='Germany')
Customer.objects.create(name='Du monde entier', country='France')
Customer.objects.create(name='Eastern Connection', country='UK')

Tiếp theo chúng ta viết template và hàm view.

from django.shortcuts import render

# Create your views here.
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from .models import Customer

def listing(request): 
    customer_list = Customer.objects.all()
    paginator = Paginator(customer_list, 5)
 
    pageNumber = request.GET.get('page')
    try:
        customers = paginator.page(pageNumber)
    except PageNotAnInteger:
        customers = paginator.page(1)
    except EmptyPage:
        customers = paginator.page(paginator.num_pages)
 
    return render(request, 'list.html', {'customers':customers})

Chúng ta sử dụng lớp Pagination để thực hiện phân trang.

from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger

Đầu tiên chúng ta import một số lớp cần thiết.

paginator = Paginator(customer_list, 5)

Hàm khởi tạo Paginator() nhận vào 2 tham số, tham số đầu tiên là một đối tượng QuerySet, tham số thứ 2 là số tượng item trên mỗi “trang”. Trong ví dụ trên chúng ta đưa đối tượng customer_list vào với số lượng 5 item mỗi trang.

pageNumber = request.GET.get('page')

URL của chúng ta có thêm tham số page là số thứ tự của trang muốn xem.

try:
    customers = paginator.page(pageNumber)
except PageNotAnInteger:
    customers = paginator.page(1)
except EmptyPage:
    customers = paginator.page(paginator.num_pages)

Nếu tham số page không hợp lệ, chẳng hạn như page=abc thì Paginator sẽ giải phóng lỗi PageNotAnInterger, trong trường hợp này chúng ta trả về trang đầu tiên với phương thức Paginator.page(), hoặc nếu page nằm ngoài pham vi trang cho phép, chẳng hạn như chúng ta chỉ có 4 trang nhưng tham số page=1000 thì Paginator sẽ giải phóng lỗi EmptyPage, ở đây chúng ta xử lý bằng cách trả về trang cuối cùng bằng thuộc tính num_pages.

Template:

<table>
    <tr>
        <th>Customer name</th>
        <th>Country</th>
    </tr>
    {% for customer in customers %}
    <tr>
        <td>{{ customer.name }}</td>
        <td>{{ customer.country }}</td>
    </tr>
    {% endfor %}
</table>
<div class="pagination">
    <span class="step-links">
        {% if customers.has_previous %}
            <a href="?page={{ customers.previous_page_number }}">Previous</a>
        {% endif %}
    </span>

    <span class="current">
        Page {{ customers.number }} of {{ customers.paginator.num_pages }}.
    </span>
    
    <span>
        {% if customers.has_next %}
            <a href="?page={{ customers.next_page_number }}">Next</a>
        {% endif %}
    </span>
</div>

Cuối cùng chúng ta tạo URL và chạy server để xem kết quả.

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

urlpatterns = [
    url(r'^$', views.listing),
]
from django.conf.urls import url, include
from django.contrib import admin

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

Capture

Django – Kiểm tra dữ liệu gửi lên form

Trong bài này chúng ta sẽ tìm hiểu cách kiểm tra sự đúng đắn của dữ liệu.

Kiểm tra dữ liệu ở đây là kiểm tra xem dữ liệu được gửi lên có đúng với yêu cầu hay không, cũng có thể xem đây là câu lệnh catch trong quá trình kiểm tra lỗi exception vậy, chẳng hạn như chúng ta yêu cầu người dùng nhập vào email nhưng người dùng nhập sai cấu trúc, hay upload ảnh avatar mà lại gửi lên một file PDF, nguy hiểm hơn nữa là up file ảnh giả – tức file có đuôi .jpg, .png… nhưng lại không chứa dữ liệu ảnh mà chứa mã độc… vì thế chúng ta nên kiểm tra dữ liệu được gửi lên trước khi lưu chúng vào CSDL, và Django cũng cung cấp một số phương thức/hàm hỗ trợ chúng ta làm việc này.

Django có một số phương thức kiểm tra dữ liệu có sẵn trong các lớp FieldForm, các phương thức này khi kiểm tra nếu dữ liệu đúng thì trả lại dữ liệu đó, nếu sai thì sẽ trả về một lỗi exception là ValidationError.

Chúng ta đã từng dùng phương thức is_valid() của lớp django.forms.form, phương thức này khi được gọi sẽ tự động gọi các phương thức kiểm tra và dữ liệu nào không có lỗi thì sẽ được lưu trong thuộc tính form.cleaned_data dưới dạng một đối tượng Dictionary, bạn vẫn có thể truy xuất dữ liệu gốc trong thuộc tính request.POST.

Các phương thức kiểm tra khi được gọi sẽ thực hiện kiểm tra dữ liệu theo cách của Django nhưng chúng ta cũng nên override lại các phương thức kiểm tra để chúng làm theo yêu cầu của chúng ta. Chúng ta sẽ tìm hiểu về các phương thức này ở dưới.

Validator

Các lớp Field của Django hỗ trợ một công cụ tiện ích có tên là validatorCác validator sẽ kiểm tra dữ liệu của lớp đó theo một quy luật nào đó và trả về lỗi ValidationError nếu có. Chúng ta có thể định nghĩa validator bằng cách truyền vào ngay hàm khởi tạo Field hoặc định nghĩa bên trong các lớp Field riêng với thuộc tính default_validators. Ví dụ:

from django.forms import CharField
from django.core import validators

class SlugField(CharField):
    default_validators = [validators.validate_slug]

Chúng ta định nghĩa lớp SlugField kế thừa từ CharField, thuộc tính default_validators sẽ quy định kiểu kiểm tra dữ liệu của lớp này.

>>> slugExample = forms.SlugField()
>>> slugExample2 = forms.CharField(validators=[validators.validate_slug])

Cả 2 cách tạo đối tượng trên sẽ sử dụng chung validator.

validators.validate_slug

Validators là một module của Django, module này chứa các lớp như EmailValidator, RegexValidatorURLValidator, Min/MaxValueValidator cho phép chúng ta định nghĩa các luật kiểm tra khác nhau. Ngoài các lớp đó Djang còn chứa sẵn một số đối tượng validator tĩnh quy định sẵn các luật thông dụng, ở ví dụ trên chúng ta dùng validate_slug, đối tượng này quy định chuỗi chỉ được phép chứa kí tự chữ cái, kí tự số, dấu gạch ngang và dấu nối _. Ngoài validate_slug ra còn có các đối tượng khác như validate_email, validate_unicode_slug… bạn có thể xem danh sách tại đây.

Các phương thức kiểm tra dữ liệu

Django cung cấp các hàm/phương thức kiểm tra dữ liệu sau đây:

Phương thức kiểm tra trên Field:

  • Phương thức to_python() có sẵn trong mỗi lớp Field, phương thức này sẽ chuyển đổi kiểu dữ liệu field tương ứng sang kiểu dữ liệu của Python, nếu chuyển được thì không có gì, nếu không sẽ có lỗi ValidationError. Ví dụ như khi chuyển một FloatField thì chúng ta sẽ được một đối tượng float hoặc một lỗi exception.
  • Phương thức validate() có sẵn trong mỗi lớp Field, thường được dùng khi không có sẵn các validator.
  • Phương thức run_validators() có trong mỗi lớp Field tự động kiểm tra tất cả các validator của Field và cái nào có lỗi thì gom lại vào một đối tượng ValidationError. Thường chúng ta không dùng phương thức này.
  • Phương thức clean() có trong các lớp kế thừa từ lớp Field sẽ gọi các phương thức to_python(), validate()run_validators() trên field theo thứ tự và nếu gặp lỗi tại đâu thì dừng quá trình kiểm tra và giải phóng exception tại đó. Nếu dữ liệu hợp lệ thì sẽ được truyền vào thuộc tính cleaned_data của form.

Phương thức kiểm tra trên Form:

  • Phương thức clean_<tên thuộc tính field>() được Django tạo ra trong các lớp kế thừa từ lớp Form, trong đó <tên thuộc tính field> là tên do chúng ta đặt cho từng thuộc tính field khi định nghĩa form. Phương thức này kiểm tra dữ liệu theo yêu cầu chứ không quan tâm đến kiểu dữ liệu. Chẳng hạn như form của chúng ta có một thuộc tính tên là serialnumber có kiểu CharField và thuộc tính này phải là duy nhất – tức là không có 2 bản ghi nào có serialnumber giống nhau, thì việc kiểm tra sẽ được thực hiện trong phương thức clean_serialnumber() và chúng ta phải override lại phương thức này.

  • Phương thức clean() có trong các lớp kế thừa từ lớp Form kiểm tra dữ liệu trên từng field, chúng ta sẽ override lại khi cần dùng. Phương thức này được gọi sau khi các phương thức kiểm tra trên Field đã thực thi.

Khi phương thức is_valid() được gọi, từng thuộc tính field mà chúng ta khai báo theo thứ tự từ trên xuống dưới khi định nghĩa Form sẽ gọi từng phương thức kiểm tra đã nêu trên, phương thức Field.clean() sẽ được gọi đầu tiên, sau đó là Form.clean_<tên thuộc tính field>(), cuối cùng là phương thức Form.clean(), phương thức Form.clean() sẽ luôn được gọi bất kế các phương thức trước đó có trả về lỗi hay không.

Ví dụ

Chúng ta sẽ override một số phương thức đã nói trên.

from django import forms
from django.core.validators import validate_email

class MultiEmailField(forms.Field):
    def to_python(self, value):        
        if not value:
            return []
        return value.split(',')

    def validate(self, value):   
        super(MultiEmailField, self).validate(value)

Chúng ta định nghĩa lớp MultiEmailField kế thừa từ lớp Field. Ở đây chúng ta override lại 2 phương thức to_python()validate().

if not value:
    return []
return value.split(',')

Trong phương thức to_python(), chúng ta chỉ đơn giản là kiểm tra xem dữ liệu có rỗng hay không, nếu không thì trả về một list các string tách ra từ string gốc dùng dấu phẩy.

super(MultiEmailField, self).validate(value)

Trong phương thức validate(), chúng ta gọi lại phương thức validate() của lớp cha là lớp Field. 

Bây giờ chúng ta định nghĩa lớp ContactForm và override một số phương thức kiểm tra của Form.

from django import forms

class ContactForm(forms.Form):
    subject = forms.CharField(max_length=100)
    message = forms.CharField()
    sender = forms.EmailField()
    recipients = MultiEmailField()
    cc_myself = forms.BooleanField(required=False)

    def clean_recipients(self):
        data = self.cleaned_data['recipients']
        if "fred@example.com" not in data:
            raise forms.ValidationError("You have forgotten about Fred!")

        return data

    def clean(self):
        cleaned_data = super(ContactForm, self).clean()
        cc_myself = cleaned_data.get("cc_myself")
        subject = cleaned_data.get("subject")

        if cc_myself and subject:
            if "help" not in subject:
                raise forms.ValidationError(
                    "Did not send for 'help' in the subject despite "
                    "CC'ing yourself."
                )

Chúng ta override 2 phương thức là clean_recipients()clean().

recipients = MultiEmailField()

Chúng ta sử dụng lớp MultiEmailField như các lớp field bình thường. Khi phương thức is_valid() được gọi thì các phương thức to_python()validate() của từng field sẽ được gọi, bất kể có override hay không.

def clean_recipients(self):
    data = self.cleaned_data['recipients']     
    if "fred@example.com" not in data:
        raise forms.ValidationError("You have forgotten about Fred!")
    return data   

Sau khi các phương thức to_python()validate() đã được gọi thì Django sẽ lưu các dữ liệu đã được kiểm tra là hợp lệ trong thuộc tính cleaned_data. Chúng ta có thể lấy ra để kiểm tra tiếp.  Trong đoạn code trên chúng ta kiểm tra nếu fred@example.com không có trong list thì giải phóng lỗi ValidationError.

def clean(self):
    cleaned_data = super(ContactForm, self).clean()
    ...
    if cc_myself and subject:
        if "help" not in subject:
            raise forms.ValidationError(
                "Did not send for 'help' in the subject despite "
                "CC'ing yourself."
            )

Phương thức clean() khá đơn giản, chúng ta chỉ kiểm tra xem nếu cc_myselfsubject không rỗng thì trong subject phải có chứa chuỗi “help”, nếu không thì báo lỗi. Ngoài ra trước đó chúng ta cũng gọi lại phương thức clean() của lớp cha.

Ruby – Các thành phần của Ruby

Một ngôn ngữ lập trình bao gồm nhiều thành phần cấu thành nên nó. Trong phần này chúng ta sẽ tìm hiểu các thành phần cấu tạo nên ngôn ngữ lập trình Ruby.

Bình luận – Comment

Các đoạn comment được dùng để ghi chú mã nguồn. Cú pháp comment của Ruby có 2 loại là comment cho một dòng và comment cho nhiều dòng. Comment trên một dòng được bắt đầu bởi dấu #, comment trên nhiều dòng được bọc bởi cặp kí hiệu =begin=end.

=begin
  comments.rb
  Pho Code
=end

puts "Comments example"

Các dòng comment sẽ không được dịch bởi trình thông dịch.

=begin
  comments.rb
  Pho Code
=end

Biến – Variable

Biến chỉ là một cái tên, đại diện cho một thứ gì đó có công việc là lưu trữ một giá trị nào đó. Trong lập trình thì chúng ta nói là “gán giá trị cho biến”, giá trị ở đây có thể là một đoạn text, một con số hay một đối tượng nào đó.

Tên của các biến được đặt bằng các kí tự trong bảng chữ cái và dấu gạch dưới, nhưng không được bắt đầu bằng kí tự số, cũng không được bắt đầu bằng kí tự viết HOA, nếu dùng chữ HOA thì Ruby sẽ gọi đây là “hằng số”.

Value
value2
company_name

Value là một hằng số, value2company_name là biến.

12Val
exx$
first-name

Ở trên là các tên biến không hợp lệ.

Tên biến có thể được bắt đầu bởi 2 kí tự đặc biệt là @$chúng ta sẽ tìm hiểu thêm sau.

Biến trong Ruby là có phân biệt HOA-thường, tức là pricepRice là 2 biến khác nhau.

Hằng số – Constant

Trong các ngôn ngữ khác thì hằng số cũng là các biến thôi nhưng chỉ lưu một giá trị trong suốt chương trình, không thể thay đổi được. Nhưng không giống với các ngôn ngữ khác, giá trị của các hằng số trong Ruby là có thể thay đổi được, khi chúng ta thay đổi giá trị hằng thì Ruby không báo lỗi mà chỉ đưa ra mấy dòng cảnh báo.

Tên hằng số được bắt đầu bởi một kí tự viết HOA, thường thì khi đặt tên hằng chúng ta luôn viết hoa toàn bộ các kí tự trong tên.

Name = "Robert"
AGE = 23

Name = "Juliet"

Trong ví dụ trên chúng ta định nghĩa hằng NameAGE, sau đó thay đổi giá trị của Name và Ruby sẽ báo lỗi tương tự như sau.

C:\Project\Ruby>irb constants.rb
constants.rb:4: warning: already initialized constant Name

Giá trị – Literal

Giá trị là các kí tự mô tả một giá trị của một kiểu dữ liệu nào đó, có thể là một con số, một đoạn text… dùng để gán cho các biến. Chúng ta sẽ tìm hiểu về kiểu dữ liệu sau.

age = 29
nationality = "Hungarian"

Trong ví dụ trên thì 29“Hungarian” là giá trị, agenationality là biến.

require 'date'

sng = true
name = "James"
job = nil
weight = 68.5
born = Date.parse("November 12, 1986")

puts "His name is #{name}"

if sng == true
    puts "He is single"
else
    puts "He is in a relationship"
end

puts "His job is #{job}"
puts "He weighs #{weight} kilograms"
puts "He was born in #{born}"

Trong đoạn code trên thì chúng ta có các giá trị có kiểu dữ liệu boolean, kiểu string, kiểu float, kiểu nil – có thể hiểu là kiểu rỗng, kiểu Date.

His name is James
He is single
His job is 
He weighs 68.5 kilograms
He was born in 1986-11-12

Khối lệnh – Block

Khối lệnh là cách để chúng ta gộp nhóm các lênh lại với nhau, bạn sẽ hiểu về khối lệnh nhiều hơn khi thực hành. Khối lệnh trong Ruby được bắt đầu và kết thúc bởi cặp dấu {} hoặc cặp từ khóa do-end.

puts [2, -1, -4, 0].delete_if { |x| x < 0 }
    
[1, 2, 3].each do |e|
    puts e
end

Trong ví dụ trên chúng ta sử dụng cả 2 loại khối lệnh.

Ngoài các câu lệnh tính toán bình thường thì trong lập trình còn có các câu lệnh điều khiển, ví dụ như câu lệnh if, đây là câu lệnh điều kiện, theo sau if là một biểu thức rồi tới một khối lệnh nằm trong cặp từ khóa then-end. Chúng ta sẽ tìm hiểu thêm về các câu lệnh điều kiện sau.

if true then
    puts "Ruby language"
    puts "Ruby script"
end

Sigil

Sigil là các kí tự $@ dùng để khai báo phạm vi hoạt động của biến. Trong đó $ cho biết biến đó là một biến toàn cục, @ cho biết đó là biến instance, @@ là biến class. Chúng ta sẽ tìm hiểu thêm trong bài lập trình hướng đối tượng.

$car_name = "Peugeot"
@sea_name = "Black sea"
@@species = "Cat"

Sigil luôn được đặt trước tên biến.

Toán tử

Toán tử là các kí tự thực hiện các hành động nào đó trên một giá trị nào đó.

!    +    -    ~    *    **    /    %
<< >>    &    |    ^
==    ===    !=    <=>    >=    >
<    <=    =    %=    /=    -=
+=    *=    **=    ..    ...    not
and    or    ?:    &&    || 

Chúng ta sẽ tìm hiểu thêm về toán tử sau.

Từ khóa

Từ khóa là các từ ưu tiên trong Ruby, thường dùng làm các câu lệnh thực hiện hành động nào đó, chẳng hạn như in giá trị ra màn hình, thực hiện các công việc lặp đi lặp lại hay thực hiện tính toán. Khi đặt tên biến chúng ta không được đặt tên trùng với từ khóa.

alias    and      BEGIN      begin    break    case
class    def      defined?   do       else     elsif
END      end      ensure     false    for      if
in       module   next       nil      not      or
redo     rescue   retry      return   self     super
then     true     undef      unless   until    when 
while    yield 

 

Ở trên là danh sách các từ khóa có trong Ruby.

Django – Tạo form từ model

Giả sử bạn xây dựng một ứng dụng có sử dụng cơ sở dữ liệu – tức là bạn phải có định nghĩa các lớp model, thường thì bạn sẽ phải tạo các form cho người dùng nhập dữ liệu và các form này thường cũng có các field gần như là giống hoàn toàn với các field có trong model, ví dụ như trong một ứng dụng Blog bạn có một model tên là BlogComment có các field lưu trữ thông tin các comment do người dùng nhập vào, vậy thì bạn cũng phải tạo form để người dùng nhập comment vào bài viết.

Nhưng việc định nghĩa một lớp Model và một lớp Form có các field gần như giống nhau hoàn toàn là rất vô nghĩa, mà lại còn viết từ code HTML hoặc cùng lắm là kế thừa từ lớp Form của Django đi nữa thì code vẫn rất mệt mỏi, vì thế nên Django có thể tự định nghĩa luôn các lớp form giùm bạn từ các lớp model mà bạn đã định nghĩa trước, tức là trong model có các field nào thì Django cũng sẽ tự tạo lớp form có các field tương ứng như thế, bạn không cần phải định nghĩa lại nữa.

Kiểu dữ liệu

Mỗi lớp Field của Model sẽ có một lớp Field của Form tương ứng. Dưới đây là bảng tên các lớp Field tương ứng của ModelForm.

Lưu ý là các lớp ModelField hỗ trợ 3 kiểu khóa ngoại là 1-n, n-n1-1 nhưng bên FormField chỉ có 2 kiểu duy nhất được hỗ trợ là ModelChoiceField tương ứng với ForeignKeyModelMultipleChoiceField tương ứng với ManyToManyField. Cả 2 lớp trên đều nhận một tham số khi khởi tạo là một đối tượng QuerySet.

Mỗi lớp Form Field có chung một số thuộc tính như sau:

  • required: nếu thuộc tính blank của ModelTrue thì required = False và ngược lại.
  • label: thuộc tính này được gán tự động bằng thuộc tính verbose_name của Model và kí tự đầu được viết hoa.
  • help_text: thuộc tính này được gán tự động bằng thuộc tính help_text của model.

Ví dụ

Chúng ta định nghĩa các lớp như sau.

from django.db import models
from django.forms import ModelForm

TITLE_CHOICES = (
    ('MR', 'Mr.'),
    ('MRS', 'Mrs.'),
    ('MS', 'Ms.'),
)

class Author(models.Model):
    name = models.CharField(max_length=100)
    title = models.CharField(max_length=3, choices=TITLE_CHOICES)
    birth_date = models.DateField(blank=True, null=True)
   
class Book(models.Model):
    name = models.CharField(max_length=100)
    authors = models.ManyToManyField(Author)

class AuthorForm(ModelForm):
    class Meta:
        model = Author
        fields = ['name', 'title', 'birth_date']

class BookForm(ModelForm):
    class Meta:
        model = Book
        fields = ['name', 'authors']

Chúng ta định nghĩa 2 lớp ModelAuthorBook, sau đó định nghĩa 2 lớp Form có các Field được tạo tự động từ 2 lớp model là AuthorFormBookForm. Các lớp này được kế thừa từ lớp django.forms.ModelForm thay thì lớp django.forms.Form như trước.

class Meta:
    model = Author
    fields = ['name', 'title', 'birth_date']

Chúng ta định nghĩa model được dùng trong lớp nội Meta, thuộc tính model sẽ tạo các field tương ứng từ lớp Model tương ứng, thuộc tính fields sẽ chọn các trường nào được dùng, nếu chúng ta không khai báo thuộc tính fields thì mặc định Django sẽ dùng tất cả các thuộc tính có trong lớp Model, ngoại trừ thuộc tính id nếu bạn không khai báo thuộc tính khóa chính. Ngoài ra lớp Meta còn có thuộc tính exclude, thuộc tính này trái ngược với thuộc tính fields, tức là thuộc tính này sẽ quy định các trường nào không được phép sử dụng.

Định nghĩa từ lớp ModelForm tương đương với định nghĩa từ lớp Form trong các bài trước như sau:

from django import forms

class AuthorForm(forms.Form):
    name = forms.CharField(max_length=100)
    title = forms.CharField(max_length=3,
    widget=forms.Select(choices=TITLE_CHOICES))
    birth_date = forms.DateField(required=False)

class BookForm(forms.Form):
    name = forms.CharField(max_length=100)
    authors = forms.ModelMultipleChoiceField(queryset=Author.objects.all())

Lưu dữ liệu

Lớp ModelForm có một phương thức tên là save(), phương thức này tạo mới hoặc lưu một đối tượng Model vào cơ sở dữ liệu giống như phương thức save() bên các lớp Model vậy.

Khi khởi tạo các đối tượng ModelForm thì chúng ta có thể truyền các đối tượng model có sẵn vào hàm khởi tạo, nếu chúng ta chỉ truyền vào không thôi thì Django sẽ tạo mới một đối tượng trên CSDL, nếu chúng ta truyền vào và ghi rõ truyền với tham số là instance thì Django sẽ cập nhật dữ liệu trong đối tượng đó. Ví dụ:

>>> from myapp.models import Article
>>> from myapp.forms import ArticleForm
>>> f = ArticleForm(request.POST)
>>> new_article = f.save()

Đoạn code trên tạo đối tượng mới từ đối tượng POST được gửi lên và lưu vào CSDL.

>>> a = Article.objects.get(pk=1)
>>> f = ArticleForm(request.POST, instance=a)
>>> f.save()

Đoạn code trên tạo một đối tượng ArticleForm, truyền dữ liệu từ POST vào rồi truyền cập nhật trên đối tượng a chứ không tạo mới.

Lưu ý là nếu chúng ta không kiểm tra sự đúng đắn của dữ liệu thì khi gọi phương thức save(), Django cũng sẽ tự động làm việc đó và sẽ giải phóng lỗi exception ValueError nếu dữ liệu có lỗi, trong bài trước chúng ta đã dùng một phương thức để kiểm tra dữ liệu là is_valid(), chúng ta sẽ tìm hiểu thêm về cách kiểm tra dữ liệu trong các bài sau.

Tùy chỉnh lớp Field

Như trong bảng tên các lớp ModelFieldFormField tương ứng đã chỉ rõ ở trên, nếu bạn khai báo loại field nào trong lớp model thì Django sẽ tạo lớp field tương ứng bên form. Chẳng hạn như khi bạn khai báo trường DateTimeField bên model thì Django sẽ tạo một trường DateTimeField tương ứng bên form.

Mặc dù tạo form bằng cách code từ đầu với HTML hoặc dùng lớp django.forms.Form sẽ cho bạn quyền điều khiển nhiều hơn nhưng không có nghĩa là ModelForm không cho phép bạn tùy chỉnh các lớp field có sẵn.

Chẳng hạn như để tùy chỉnh kiểu hiển thị thì chúng ta dùng thuộc tính widgets có trong lớp nội Meta. Ví dụ:

from django.forms import ModelForm, Textarea
from myapp.models import Author

class AuthorForm(ModelForm):
    class Meta:
        model = Author
        fields = ('name', 'title', 'birth_date')
        widgets = {
            'name': Textarea(attrs={'cols': 80, 'rows': 20}),
        }

Mặc định lớp CharField sẽ hiển thị thẻ <input type="text">, nhưng đoạn code trên sẽ chỉ định CharField hiển thị <input type="textarea">.

Tương tự bạn có thể tùy chỉnh lại các thuộc tính như label, help_text trong lớp nội Meta nếu muốn:

from django.utils.translation import ugettext_lazy as _

class AuthorForm(ModelForm):
    class Meta:
        model = Author
        fields = ('name', 'title', 'birth_date')
        labels = {
            'name': _('Writer'),
        }
        help_texts = {
            'name': _('Some useful help text.'),
        }

Bạn cũng có thể quy định các thuộc tính phải dùng lớp field do bạn định nghĩa thông qua thuộc tính field_classes trong lớp Meta:

from django.forms import ModelForm
from myapp.models import Article

class ArticleForm(ModelForm):
    class Meta:
        model = Article
        fields = ['pub_date', 'headline', 'content', 'reporter', 'slug']
        field_classes = {
            'pub_date': MyDateTimeField,
        }

class MyDateTimeField():
    #...
    pass

Thừa kế Form

ModelForm cũng chỉ là một lớp Python, do đó bạn có thể cho thừa kế và mở rộng các trường hoặc các phương thức của chúng. Ví dụ:

class EnhancedArticleForm(ArticleForm):
    def clean_pub_date(self):
        #... 
        pass

Lớp EnhancedArticleForm kế thừa từ lớp ArticleForm nên sẽ có tất cả các thuộc tính và phương thức của lớp ArticleForm, ngoài ra lớp EnhancedArticleForm còn có phương thức clean_pub_date() của riêng nó nữa.

Tương tự với các lớp Model, các lớp form cũng có thể thừa kế cả lớp nội Meta:

class RestrictedArticleForm(EnhancedArticleForm):
    class Meta(ArticleForm.Meta):
        exclude = ('body',)

Lớp RestrictedArticleForm kế thừa từ lớp EnhancedArticleForm ngoại trừ lớp này không sử dụng thuộc tính body.

Lưu ý là khi sử dụng đa thừa kế thì lớp Meta của lớp con chỉ thừa kế từ lớp Meta của lớp cha đầu tiên trong danh sách thôi.

Hàm factory

Nếu bạn quá “lười” để ngồi định nghĩa lại một lớp kế thừa từ ModelForm, sau đó ngồi khai báo các thuộc tính fields, exclude hay lớp Meta thì Django cũng cung cấp một hàm cho phép bạn tạo một lớp ModelForm một cách “cấp tốc” là hàm modelform_factory(), ví dụ:

from django.forms import modelform_factory
from myapp.models import Book

BookForm = modelform_factory(Book, fields=("author", "title"))

Hàm này đặc biệt hữu dụng khi lớp Form mà bạn muốn định nghĩa không có gì khác nhiều so với lớp cha.

Bạn cũng có thể đưa các thuộc tính chỉnh sửa vào ngay trong hàm modelform_factory():

from django.forms import Textarea
Form = modelform_factory(Book, form=BookForm, widgets={"title": Textarea()}, fields=['name'])

Django – Upload file

Trong phần này chúng ta sẽ tạo form thực hiện chức năng upload file lên thư mục gốc của server.

Các hàm view mà chúng ta đã viết đều nhận một tham số đầu vào là một đối tượng HttpRequest, đối tượng này lưu trữ những thông tin về dữ liệu được gửi từ người dùng lên server, ví dụ như khi bạn nhập localhost:8000/ thì trình duyệt sẽ tạo một đối tượng lưu trữ những thông tin chẳng hạn như phương thức gửi lên, địa chỉ ip, url, ngày giờ gửi… rồi nén tất cả những thông tin đó lại và gửi đến server của chúng ta qua giao thức HTTP. Khi dữ liệu được gửi đến, server Django sẽ tạo một đối tượng thuộc lớp HttpRequest và đưa những thông tin được gửi đến vào đối tượng này rồi truyền vào làm tham số cho hàm view tương ứng, trong hàm view đó chúng ta có thể lấy các thông tin từ đối tượng HttpRequest này để xử lý.

Bên trong lớp HttpRequest có một thuộc tính tên là FILES, thuộc tính này lưu trữ những thông tin về file được gửi lên để chúng ta xử lý. Ngoài ra nếu muốn bạn có thể tìm hiểu các thuộc tính khác ở đây.

Bạn cũng nên lưu ý là vì một số vấn đề bảo mật nên bạn nên cấu hình server chỉ cho phép nhận các file có dung lượng giới hạn tại mức nào đó thôi, ví dụ như chỉ cho phép nhận từ 2 đến 5 MB đối với như file ảnh… Mình sẽ đề cập đến vấn đề này trong bài khác.

Tạo form upload file

Chúng ta tạo một app mới với tên là file_uploader:

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

Chúng ta khai báo app cho project.

INSTALLED_APPS = [
    #...
    'file_uploader',
    #...
]

Kế tiếp chúng ta tạo file forms.py bên trong thư mục file_uploader:

from django import forms

class UploadFileForm(forms.Form):
    title = forms.CharField(max_length=50)
    file = forms.FileField()

Để hiển thị thẻ element chọn file thì chúng ta dùng lớp FileField().

Tiếp theo chúng ta tạo thư mục templates và file template:


<form action="" method="POST" enctype="multipart/form-data"> 
    {% csrf_token %} 
    <table>
        {{ form.as_table }}
    </table>
    <input type="submit" value="Submit" />
</form>

Để gửi file lên thì trong thẻ <form> chúng ta phải khai báo thuộc tính enctype="multipart/form-data".

Sau khi đã có template và form để hiển thị thì chúng ta tiến hành tạo view để kết nối chúng với nhau.

from django.db import models

# Create your models here.
from django.http import HttpResponse
from .forms import UploadFileForm

def fileUploaderView(request):
    if request.method == 'POST':
        form = UploadFileForm(request.POST, request.FILES)
        if form.is_valid():
            upload(request.FILES['file'])
            return HttpResponse("<h2>File uploaded successful!</h2>")
        else:
            return HttpResponse("<h2>File uploaded not successful!</h2>")

    form = UploadFileForm()
    return render(request, 'fileUploaderTemplate.html', {'form':form})
 
def upload(f): 
    file = open(f.name, 'wb+') 
    for chunk in f.chunks():
        file.write(chunk)

Chúng ta viết hàm view fileUploaderView() và hàm xử lý việc ghi file là upload().

if form.is_valid():

Phương thức is_valid() có trong lớp django.forms.Form sẽ kiểm tra tính hợp lệ của dữ liệu được gửi lên. Mục đích chính của lớp Form trong Django chính là hỗ trợ chúng ta kiểm tra sự đúng đắn của dữ liệu được gửi lên. Chẳng hạn như khi bạn xây dựng chức năng up ảnh avatar thì bạn chỉ cho phép user gửi các file ảnh lên thôi, nhưng kẻ tấn công có thể gửi các file ảnh “giả” lên mà trình duyệt không biết, do đó khi dữ liệu được gửi tới server, chúng ta nên kiểm tra trước các dữ liệu này trước khi lưu vào hệ thống.

Ở đây phương thức is_valid() chỉ có chức năng đơn giản là kiểm tra xem ô text title có dữ liệu hay không vì mặc định các field của Form trong Django bắt buộc phải có dữ liệu mới được nhận.

upload(request.FILES['file'])
return HttpResponse("<h2>File uploaded successful!</h2>")

Nếu dữ liệu là hợp lệ thì chúng ta tiến hành lưu file vào thư mục gốc của server và trả lời thông báo gửi thành công về cho người dùng.

def upload(f): 
    file = open(f.name, 'wb+') 
    for chunk in f.chunks():
        file.write(chunk)

Hàm upload() sẽ thực hiện copy file vào thư mục gốc của server (thư mục có file manage.py). 

Cuối cùng chúng ta định nghĩa các url. Chúng ta sẽ tạo url trỏ tới hàm view trong app.

from django.conf.urls import url

from . import views

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

Tiếp theo là url từ địa chỉ server tới app:

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

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^file-upload/', include('file_uploader.urls'))
]

Bây giờ chúng ta có thể chạy server và trỏ tới url localhost:8000/file-upload để thực hiện upload file.

Untitled

File được upload thành công.

Untitled

Thiết lập file

Khi Django server nhận file được gửi đến, trước khi lưu vào đĩa cứng thì dữ liệu của file được Django lưu vào đâu đó rồi mới thực sự được lưu trên đĩa.

Mặc định nếu kích thước file nhỏ hơn 2.5 MB thì Django sẽ lưu toàn bộ dữ liệu của file trong RAM, khi lưu vào đĩa thì Django chỉ cần đọc lại nội dung đó trong RAM rồi lưu vào đĩa thôi, do đó tốc độ lưu file rất nhanh.

Tuy nhiên nếu file tải lên quá lớn, Django sẽ lưu toàn bộ file này vào thư mục temp của hệ thống, trên Windows 7 thì thư mục này nằm tại địa chỉ C:\Users\<username>\AppData\Local\Temp rồi sau đó mới copy từ thư mục đó sang thư mục của serverMà tốc độ đọc ghi với đĩa cứng lại chậm hơn RAM nên giả sử chúng ta có 2 file có dung lượng 2.4 và 2.5 MB thì thời gian up file có dung lượng 2.4 MB không khác gì file có dung lượng 2.5 MB, nhưng thời gian upload file có dung lượng 2.5 MB với file có dung lượng 2.6 MB lại khác nhau rất nhiều.

Tuy nhiên bạn có thể thiết lập lại một số thông tin cấu hình về file nhưng mình không đi sâu ở đây, nếu muốn bạn có thể xem thêm ở đây.

Django – Form có sẵn của Django

Trong bài Form và các View có sẵn chúng ta đã tạo form bằng các thẻ HTML, nếu việc code các form bằng tay như thế mệt mỏi quá thì Django cung cấp cho chúng ta lớp Form để chúng ta có thể tạo form ngay từ code Python và có thể nhận request và trả response về cho người dùng.

Trong HTML thì form là một tập các thẻ element nằm giữa cặp thẻ <form>...</form> cho phép người dùng thực hiện một công việc nào đó, sau đó gửi dữ liệu lên server rồi trả về câu trả lời. Hầu hết HTML chỉ hỗ trợ các thẻ element cơ bản như text, checkbox… muốn hiển thị các element cao cấp hơn như DateTimePicker (chọn ngày tháng), Slider (thanh trượt)… bạn phải dùng đến các ngôn ngữ hỗ trợ thêm khác như Javascript, CSS…

Hoặc bạn có thể dùng lớp Form có sẵn của Django, Django cung cấp lớp Form hỗ trợ tạo form một cách nhanh chóng và dễ dàng. Trong phần này chúng ta sẽ tạo form đăng ký user đơn giản sử dụng lớp Form do Django cung cấp.

Tạo app

Đầu tiên chúng ta tạo một app mới với tên là user_auth:

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

Tiếp theo chúng ta khai báo app này trong list INSTALLED_APP:

...
INSTALLED_APPS = [
    'user_auth',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]
...

Tạo Form

Trong thư mục user-auth chúng ta tạo một file mới với tên là forms.py để lưu các lớp form.

from django import forms

class RegisterForm(forms.Form):
    username = forms.CharField(label='Username', max_length=100)
    password = forms.CharField(widget=forms.PasswordInput)
    email = forms.EmailField(label='Email')

Các lớp dùng để tạo form được kế thừa từ lớp django.forms.Form.

username = forms.CharField(label='Username', max_length=100)
password = forms.CharField(widget=forms.PasswordInput)
email = forms.EmailField(label='Email')

Bên trong lớp này chúng ta cũng khai báo các trường là các đối tượng Field, nhưng các đối tượng Field này không giống như Field khi tạo model, Field ở đây là để tạo form HTML còn field bên model là để tạo bảng CSDL (hơi rối :()

Mỗi loại lớp Field nhận vào vài tham số khi khởi tạo nhưng không có tham số nào là bắt buộc phải có, chẳng hạn như label của CharField sẽ được dùng cho thuộc tính name trong thẻ <label>...

Ở trên chúng ta có 2 CharField dùng để nhập text và một EmailField dùng để nhập email. Đây là danh sách các lớp và tham số của form Field của Django.

password = forms.CharField(widget=forms.PasswordInput)

Vì HTML có nhiều thẻ element có công dụng chung nhưng lại hiển thị dữ liệu khác nhau, chẳng hạn như một thẻ <input> có thể dùng để nhập tên, số điện thoại, password… do đó Django cung cấp cho mỗi đối tượng Field một đối tượng Widget để chúng ta có thể chỉ định loại text nào hiển thị cái gì.

Ở trên chúng ta dùng CharField để hiển thị password bằng cách truyền tham số widget là forms.PasswordInput. Đây là danh sách các lớp Widget.

Tạo Template

Chúng ta tạo một thư mục với tên là templates, trong thư mục này chúng ta lại tạo một thư mục khác là user_auth rồi tạo một file HTML với tên là register.html:


<form action="" method="post">
    {% csrf_token %}
    {{ form }}
    <input type="submit" value="Submit" />
</form>

Khi chúng ta viết các lớp form, Django sẽ nhìn các trường mà chúng ta khai báo rồi sinh ra các thẻ <input> tương ứng, ngoại trừ cặp thẻ <form>...</form> và thẻ <input type="submit"/>

<form action="" method="post">

Thuộc tính action sẽ chuyển dữ liệu tới đường dẫn tương ứng, ở đây mình để trống tức là gửi lại cho chính trang đó. Phương thức POST sẽ mã hóa dữ liệu được gửi đi.

{{ form }}

Chúng ta có thể tham chiếu đến phần tử form trong list context mà chúng ta sẽ khai báo trong các hàm view bên dưới, và Django sẽ tự động tạo các thẻ <label><input> cho chúng ta.

Tạo View

Chúng ta viết hàm view register() như sau.

from django.shortcuts import render

# Create your views here.

from django.http import HttpResponse
from .forms import RegisterForm

def register(request):

    if request.method == 'POST':
        response = HttpResponse()
        response.write("<h1>Thanks for registering</h1></br>")
        response.write("Your username: " + request.POST['username'] + "</br>")
        response.write("Your email: " + request.POST['email'] + "</br>")
        return response 
 
    registerForm = RegisterForm() 
    return render(request, 'user_auth/register.html', {'form':registerForm})

Đoạn code trên rất đơn giản, chúng ta chỉ import lớp RegisterForm từ module forms.py mà chúng ta viết ở trên. Sau đó tạo một đối tượng RegisterForm rồi đưa vào làm phần tử của list context (tham số thứ 3 trong hàm render()).

if request.method == 'POST':
    response = HttpResponse()
    response.write("<h1>Thanks for registering</h1></br>")
    response.write("Your username: " + request.POST['username'] + "</br>")
    response.write("Your email: " + request.POST['email'] + "</br>")
    return response 

Sau khi người dùng đã nhập các thông tin cần thiết vào các ô text và bấm gửi thì dữ liệu sẽ gửi trả lại hàm register() do chúng ta đã thiết lập nó trong thuộc tính action ở file template. Nên ở đây chúng ta kiểm tra xem nếu phương thức gửi tới là phương thức POST thì trả về câu trả lời với thông tin mà người dùng đã nhập

Tiếp theo chúng ta tạo url tới hàm view này, đầu tiên chúng ta khai báo một module urls.py của riêng app user_auth đã:

from django.conf.urls import url

from . import views

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

Sau đó là khai báo url của project tới app user_auth.

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

urlpatterns = [
    url(r'^admin/', admin.site.urls), 
    url(r'^user-auth/', include('user_auth.urls')),
]

Bây giờ bạn có thể chạy server và trỏ đường dẫn tới localhost:8000/user-auth/register/ để xem giao diện mặc định do Form của Django tạo ra.

Untitled

Như bạn thấy trong hình trên giao diện do Django tạo ra mặc định nhìn rất “chuối” vì nhiệm vụ của Django chỉ là tự sinh code cho 2 thẻ element là <label><input> cho chúng ta rảnh tay hơn mà thôi, bản thân Django cũng cung cấp một số thiết lập để chúng ta tùy chỉnh giao diện nhưng rất hạn chế. Phần lớn chúng ta sẽ phải tự chỉnh bằng tay bằng cách sửa đối code trong file template, thêm các file CSS, JS… để giao diện nhìn đẹp hơn.

Có một số tùy chọn hiển thị mà Django cung cấp cho chúng ta là:

  • form.as_table: bọc các thẻ <label><input> trong thẻ cặp thẻ <tr></tr>
  • form.as_p: bọc các thẻ <label><input> trong cặp thẻ <p></p>
  • form.as_ul: bọc các thẻ <label><input> trong cặp thẻ <li>

Tuy nhiên bạn phải khai báo cặp thẻ <table></table><ul></ul> tương ứng trước khi dùng. Ví dụ:


<form action="" method="post">
    {% csrf_token %}
    <table>
        {{ form.as_table }}
    </table>
    <input type="submit" value="Submit" />
</form>

Untitled

Giao diện nhìn có đỡ hơn một tí 🙂

Hoặc nếu muốn bạn có thể truy xuất từng phần tử của biến form trong file template để hiển thị theo ý muốn:


<form action="" method="post">
    {% csrf_token %}
        <table>
            <tr>
                <th>
                    <label for="{{ form.username.id_for_label }}">User:</label>
                </th>
                <td>
                    {{ form.username }}
                <td>
            </tr>
            <tr>
                <th>
                    <label for="{{ form.password.id_for_label }}">Password:</label>
                </th>
               <td>
                    {{ form.password }}
               </td>
            </tr>
            <tr>
                <th>
                    <label for="{{ form.email.id_for_label }}">Email:</label>
                </th>
                <td>
                    {{ form.email }}
                </td>
            </tr>
            <tr>
                <td></td>
                <td>
                    <input type="submit" value="Submit">
                </td>
            </tr>
    </table>
</form>

Bạn chỉ cần lấy theo cú pháp form.<tên thuộc tính> là Django sẽ tự động sinh code cho các thẻ tương ứng. Ví dụ như form.username...

Ngoài ra bạn còn có thể lấy các thuộc tính field nữa. Trong ví dụ trên là form.username.id_for_label sẽ lấy về id được tạo ra tự động. Đây là danh sách các thuộc tính field có trong Django.

Untitled

Đây là giao diện được thiết kế lại dùng thẻ <table> rất đơn giản.

Untitled

Django – Model nâng cao – Phần 3

Sau khi chúng ta đã tạo các lớp model, Django sẽ tự động tạo các phương thức mới trong model đó cho phép bạn thao tác với CSDL, bạn có thể tạo, sửa, xóa, cập nhật dữ liệu.

Trong bài này chúng ta sẽ tạo và sử dụng các lớp model sau đây để thực hành với các phương thức do Django cung cấp.

from django.db import models

class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()

    def __str__(self): 
        return self.name

class Author(models.Model):
    name = models.CharField(max_length=50)
    email = models.EmailField()

    def __str__(self): 
        return self.name

class Entry(models.Model):
    blog = models.ForeignKey(Blog)
    headline = models.CharField(max_length=255)
    body_text = models.TextField()
    pub_date = models.DateField()
    mod_date = models.DateField()
    authors = models.ManyToManyField(Author)
    n_comments = models.IntegerField()
    n_pingbacks = models.IntegerField()
    rating = models.IntegerField()

    def __str__(self): 
        return self.headline

Ở đây chúng ta có 3 model là Blog, AuthorEntry, các model này đại diện cho các lớp model cơ bản của một website blog.

Sau khi bạn đã tạo các lớp model trên thì bạn chạy 2 lệnh python manage.py makemigrationspython manage.py migrate để Django cập nhật các bảng mới trong CSDL. Sau đó bạn chạy trình shell bằng lệnh python manage.py shell để bắt đầu thao tác với CSDL (nếu bạn muốn chạy trong file .py riêng thì phải thiết lập biến môi trường DJANGO_SETTINGS_MODULE đến mysite.settings trước).

Tạo đối tượng

Mỗi bản ghi hay mỗi dòng trong từng bảng CSDL sẽ tương ứng với một đối tượng cụ thể. Để tạo một bản ghi trong CSDL thì chúng ta chỉ cần tạo một đối tượng tương ứng với trong Python rồi gọi phương thức save() là xong. Vd:

>>> from blog.models import Blog
>>> b = Blog(name='J.R.R Blog', tagline='All the latest J.R.R news.')
>>> b.save()

Đoạn code trên sẽ thực hiện câu lệnh INSERT, bạn phải gọi phương thức save() thì dữ liệu mới được cập nhật lên CSDL.

Ngoài ra nếu bạn muốn vừa tạo đối tượng vừa cập nhật thẳng lên CSDL luôn thì dùng phương thức create() trong đối tượng objects, đối tượng này là một đối tượng tĩnh do Django tạo ra cho chúng ta để đơn giản hóa việc thao tác với CSDL. Vd:

>>> Blog.objects.create(name='J.R.R Blog', tagline='All the latest J.R.R news.')

Cập nhật đối tượng

Bạn có thể thay đổi giá trị của các đối tượng trong Python rồi chỉ cần gọi phương thức save() là Django sẽ cập nhật mới trong CSDL.

>>> b.name = 'New name'
>>> b.save()

Cập nhật các thuộc tính khóa ngoại

Chúng ta tạo đối tượng Author mới như sau:

>>> from blog.models import Entry, Author
>>> a = Author(name='J.R.R. Tolkien', email='jrr@middleeast.com')
>>> a.save()

Bây giờ chúng ta sẽ tạo đối tượng Entry, đối tượng này chứa 2 thuộc tính khóa ngoại, một thuộc tính many-to-oneblogmany-to-manyauthors.

>>> from django.utils import timezone
>>> e = Entry(blog=b,
... headline='The lord of the rings', 
... body_text='Chapter 0', 
... pub_date=timezone.now(), 
... mod_date=timezone.now(), 
... n_comments=0, 
... n_pingbacks=0, 
... rating=0)
>>> e.save()

Việc gán giá trị cho các thuộc tính khóa ngoại many-to-one (ForeignKey) rất đơn giản, bạn chỉ cần gán thuộc tính khóa ngoại với đối tượng cần trỏ đến là xong, trong đoạn code trên chúng ta gán khóa ngoại blog = b, trong đó b là đối tượng Blog mà chúng ta đã tạo ra ở đầu bài.

>>> e.blog = Blog.create(...)
>>> e.save()

Khi cần thay đổi chúng ta cũng chỉ cần dùng phép gán như thường là được.

Đối với các thuộc tính khóa ngoại many-to-many thì chúng ta không được phép gán trực tiếp như đối với ForeignKey mà phải dùng phương thức riêng của thuộc tính khóa ngoại đó, bởi vì khóa ngoại ForeignKey chỉ lưu trữ các đối tượng đơn lẻ trong khi khóa ngoại ManyToManyField thì lại lưu trữ một danh sách các đối tượng khác nhau:

>>> e.authors.add(a)
>>> e.save()

Chúng ta lưu đối tượng a vào trong khóa ngoại authors, trong đó a là đối tượng Author mà chúng ta đã tạo ở trên.

Truy xuất dữ liệu

Khi chúng ta tạo một đối tượng thuộc lớp model do chúng ta tự định nghĩa, chẳng hạn như b=Blog(...), thì mặc định đối tượng b sẽ được Django cung cấp cho các phương thức để thao tác với CSDL trên chính bản ghi của b, chúng ta đã học một số phương thức này trong các ví dụ trên.

Ngoài cách thao tác với dữ liệu thông qua từng bản ghi như trên thì Django còn cung cấp cho chúng ta một đối tượng thuộc lớp django.db.models.manager.Manager để chúng ta thao tác với chính bảng được tạo ra đó, đối tượng này mặc định được đặt tên là objects.

>>> Blog.objects
>>> <django.db.models.manager.Manager object at 0x....>

Lấy toàn bộ dữ liệu

Khi chúng ta lấy dữ liệu từ CSDL về thì objects sẽ trả về một đối tượng QuerySet.

>>> Blog.objects.all()
>>> [<Blog: New name>]

Để lấy toàn bộ bản ghi hiện có trong bảng thì chúng ta dùng phương thức all().

>>> type(Blog.objects.all())
<class 'django.db.models.query.QuerySet'>

Như mình đã nói, danh sách được trả về là một đối tượng QuerySet.

Lọc dữ liệu

Có 2 phương thức hỗ trợ lọc dữ liệu là filter()exclude(). Tham số của 2 phương thức là một biểu thức tìm kiếm, trong đó phương thức filter() sẽ trả về dữ liệu khớp với biểu thức tìm kiếm, còn exclude() sẽ trả về dữ liệu không khớp. Chúng ta sẽ tìm hiểu thêm về các biểu thức tìm kiếm ở cuối bài.

Ví dụ:

>>> Entry.objects.filter(pub_date__year==2016)

Đoạn code trên sẽ lọc những bản ghi Entrypub_date (ngày đăng) vào năm 2016.

Bạn cũng có thể lọc theo nhiều điều kiện liên tiếp như sau:

>>> Entry.objects.filter(headline__startswith='What')
... .exclude(pub_date__gte=datetime.date.today())
... .filter(pub_date__gte=datetime(2016, 1, 30))

Đoạn code trên lọc các bản ghi có chuỗi bắt đầu là “What” và có pub_date nằm trong phạm vi từ ngày 30/1/2016 đến thời điểm hiện tại.

Lấy một dòng dữ liệu

Django cung cấp phương thức get() giúp bạn lấy một bản ghi duy nhất, tham số của phương thức này cũng là một biểu thức tìm kiếm như 2 phương thức filter()exclude(). Bạn cũng chỉ dùng get() khi biết dữ liệu trả về chỉ có 1 bản ghi thôi, nếu có nhiều bản ghi khớp với biểu thức tìm kiếm thì get() sẽ báo lỗi exception MultipleObjectsReturned.

>>> one_entry = Entry.objects.get(pk=1)

Một sự khác nhau nữa giữa get()filter()/exclude() là nếu get() không tìm thấy bản ghi nào thì sẽ trả về một lỗi exception là DoesNotExist còn 2 phương thức kia sẽ trả về một đối tượng QuerySet rỗng.

Ngoài 3 phương thức lọc dữ liệu trên là get(), filter()exclude() thì bạn có thể tìm hiểu thêm các phương thức khác tại đây.

Lọc số lượng bản ghi

Bạn có thể lọc số lượng bản ghi cần lấy theo cú pháp của Python. Ví dụ:

>>> Entry.objects.all()[:5]     # lấy 5 phần tử đầu tiên
>>> Entry.objects.all()[5:10]   # lấy các phần tử từ vị trí 5 đến 10

Python không hỗ trợ lọc theo chỉ số âm.

>>> Entry.objects.all()[-1]

Đoạn code trên sẽ báo lỗi.

Biểu thức tìm kiếm

Các biểu thức tìm kiếm sẽ thực hiện câu truy vấn SQL WHERE trong CSDL. Biểu thức tìm kiếm là tham số cho các phương thức lọc dữ liệu filter(), exclude()get().

Cú pháp lọc có dạng <tên thuộc tính>__<kiểu tìm kiếm>=<giá trị> (lưu ý ở đây có 2 dấu gạch dưới).

Ví dụ

>>> Entry.objects.filter(pub_date__lte='2016-01-01')

Đoạn code trên tương đương với câu lệnh SQL sau:

SELECT * FROM blog_entry WHERE pub_date <= '2016-01-01';

Trong biểu thức tìm kiếm thì <tên thuộc tính> bao giờ cũng là tên do chúng ta đặt khi định nghĩa model, ngoại trừ thuộc tính khóa ngoại phải thêm vào _id ở cuối tên. Ví dụ:

>>> Entry.objects.filter(blog_id=4)

Django cung cấp rất nhiều cú pháp <kiểu tìm kiếm> khác nhau, bạn có thể xem thêm ở đây. Ở đây mình giới thiệu một số.

  • exact: dữ liệu tìm được phải giống chính xác với giá trị cần tìm. Vd Entry.objects.get(headline__exact='Cat bites dog') chỉ trả về những bản ghi có headline“Cat bites dog“.
  • iexact: dữ liệu tìm được chỉ cần giống kí tự với giá trị cần tìm, không phân biệt chữ HOA-thường. Vd Blog.objects.get(name__iexact='beatles blog'), những bản ghi có headline“Beatles Blog”, “beatles blog” hoặc “BeAtlES blOG” đều được nhận.
  • contains: dữ liệu chỉ cần chứa chuỗi giá trị là được. Vd Entry.objects.get(headlines__contains='Lennon'). Ngoài ra còn có icontains cũng có chức năng như contains nhưng không phân biệt chữ HOA-thường.
  • startswith, endswith:  dữ liệu có chuỗi bắt đầu hoặc kết thúc giống với giá trị.

Tìm kiếm đa bảng

Các bảng trong CSDL có quan hệ với nhau nhờ vào các khóa ngoại, khi truy vấn dữ liệu, SQL cho phép bạn truy vấn các bản ghi có liên quan với nhau bằng cách nối các bảng lại bằng câu lệnh JOIN, Django cũng cho phép bạn làm điều đó, bạn chỉ cần đưa vào biểu thức tìm kiếm cú pháp <tên khóa ngoại>__<tên thuộc tính của bảng khác>=<giá trị>. Ví dụ:

>>> Entry.objects.filter(blog__name='Beatles Blog')

Câu lệnh trên sẽ tìm các bản ghi Entry có quan hệ với bảng Blog với name là “Beatles Blog”.

Ruby – Giới thiệu về ngôn ngữ Ruby

Chào mừng bạn đến với bài mở đầu series lập trình với ngôn ngữ Ruby. Trong series này, bạn sẽ được học những thành phần chính của ngôn ngữ Ruby, bao gồm biến, biểu thức, tập hợp, luồng điều khiển, biểu thức chính quy và lập trình hướng đối tượng.

Ruby

Ruby logo

Ruby là một ngôn ngữ lập trình động, phản xạ, hướng đối tượng. Tác giả của ngôn ngữ Ruby là một lập trình viên người Nhật tên là Yukihiro Matsumoto. Ruby được giới thiệu lần đầu vào năm 1995.

Ruby hỗ trợ hầu hết các mô hình lập trình truyền thống, bao gồm lập trình hướng đối tượng, lập trình phản xạ, lập trình mệnh lệnh… Ruby được lấy cảm hứng từ Perl, Smalltalk, Effiel và Lisp.

Website chính thức của Ruby có địa chỉ ruby-lang.org.

Trình thông dịch Ruby

Trong series này mình dùng Ruby phiên bản 2.2.4 trên Windows. Bạn có thể tải và cài đặt Ruby cho Windows tại địa chỉ http://rubyinstaller.org/, Khi cài bạn nhớ check lựa chọn cho trình cài đặt tự động gán đường dẫn đến thư mục bin trong biến PATH luôn cho tiện.

Sau khi cài xong, bạn mở Command Prompt (cmd) lên và chạy lệnh irb để mở trình thông dịch Ruby, màn hình cùng với dấu nhắc lệnh của Ruby có dạng như sau:

C:\User\PhoCode> irb
irb(main):001:0> 

Từ đây bạn có thể gõ các lệnh trong Ruby.

irb(main):001:0> puts RUBY_VERSION 
2.2.4 => nil

Ở trên chúng ta chạy lệnh puts, lệnh này sẽ in một đoạn text ra màn hình, ở đây chúng ta in hằng số RUBY_VERSION, hằng số này cho biết phiên bản Ruby mà chúng ta sử dụng.

Viết code Ruby trong file

Ngoài cách chạy Ruby trực tiếp từ trình thông dịch thì chúng ta có thể viết code Ruby trong các file script riêng rồi trình thông dịch sẽ chạy đoạn code trong file đó. Bạn chỉ cần tạo một file text, viết code Ruby trong đó rồi lưu lại với phần mở rộng là .rb.

puts "Hello world"

Đoạn code trên sẽ in chuỗi Hello world ra màn hình.

C:\Project\Ruby>hello.rb 
Hello world

Để chạy file .rb thì bạn mở cmd lên rồi trỏ đến thư mục chứa file này (bằng lệnh cd) sau đó gõ vào tên file là xong.

Tài liệu tham khảo

Django – Model nâng cao – Phần 2

Trong phần này chúng ta tiếp tục tìm hiểu về các tính chất của model trong Django.

Metadata

Các lớp trong Django có thể chứa các metadata (siêu dữ liệu), đây là các thông tin lưu trữ về các thiết lập của model, các thông tin này không được dùng để tạo bảng. Ví dụ:

from django.db import models

class Ox(models.Model):
    horn_length = models.IntegerField()

    class Meta:
        ordering = ["horn_length"]
        db_table = "oxen"

Các siêu dữ liệu sẽ được lưu trong một lớp nội có tên là Meta. Lớp này lưu trữ các thông tin cấu hình model, chẳng hạn như ordering tức là sắp xếp dữ liệu được trả về mặc định theo cột nào, db_table là tên bảng được tạo trong CSDL… bạn có thể xem danh sách tất cả các thuộc tính metadata ở đây.

Thừa kế model

Model trong Django cũng có thể thừa kế lẫn nhau. Chúng ta có thể viết các lớp model cơ sở (hay model cha) rồi sau đó viết các model khác thừa kế từ lớp model cơ sở này. Các lớp model cơ sở phải được thừa kế từ lớp django.db.models.Model, rồi từ đó các lớp con sẽ kế thừa từ lớp cha này. Model cha có thể có bảng riêng hoặc không có.

Có 3 cách để một model được thừa kế trong Django:

  • Thừa kế từ lớp trừu tượng.
  • Thừa kế đa bảng.
  • Dùng lớp Proxy.

Thừa kế từ lớp trừu tượng

Chúng ta sử dụng lớp trừu tượng khi muốn model cha chỉ có nhiệm vụ là lưu trữ các thông tin dùng chung trong các model con chứ không phải nhằm mục đích lưu các thông tin thật trong CSDL. Để một model là “trừu tượng” thì chúng ta gán thuộc tính abstract = True của lớp nội Meta.

Ví dụ:

from django.db import models

class CommonInfo(models.Model):
    name = models.CharField(max_length=100)
    age = models.PositiveIntegerField()

    class Meta:
        abstract = True

class Student(CommonInfo):
    home_group = models.CharField(max_length=5)

Model Student thừa kế từ model trừu tượng là CommonInfo, Student sẽ bao gồm 3 trường là name, agehome_group.

Khi chúng ta dùng lệnh manage.py migrate, chỉ có model Student mới được tạo bảng cùng với 3 trường của nó.

Khi một model con thừa kế model cha mà không khai báo lớp nội Meta thì sẽ tự động thừa kế lớp nội đó từ model cha. Nhưng nếu muốn các model vẫn có thể có lớp nội riêng hoặc kế thừa và thêm các thuộc tính mới từ model cha.

Ví dụ:

from django.db import models

class CommonInfo(models.Model):
    # ...
    class Meta:
        abstract = True
        ordering = ['name']

class Student(CommonInfo):
    # ...
    class Meta(CommonInfo.Meta):
        db_table = 'student_info'

Ở đây lớp con sẽ kế thừa cả lớp nội Meta từ lớp cha. Nhưng có gì đó kì kì, nếu như vậy thì lớp con cũng sẽ trở thành lớp trừu tượng luôn sao vì nó sẽ kế thừa cả thuộc tính abstract? Không! Trước khi kế thừa từ lớp nội Meta thì Django sẽ tự động thiết lập biến abstract thành False ở lớp cha rồi sau đó sẽ cho kế thừa đến các lớp con, sau khi kế thừa xong thì Django mới thiết lập thuộc tính abstract lại như cũ ở lớp cha. Tuy nhiên nếu muốn bạn vẫn có thể thiết lập abstract = True ở các lớp con để tiếp tục kế thừa sâu hơn nữa.

Thừa kế đa bảng

Thừa kế đa bảng chỉ khác kiểu thừa kế lớp trừu tượng ở chỗ là lớp cha ở đây là lớp “thật” chứ không phải lớp trừu tượng. Tức là các model cha bây giờ sẽ có cả các bảng riêng của chúng trong CSDL. Ví dụ:

from django.db import models

class Place(models.Model):
    name = models.CharField(max_length=50)
    address = models.CharField(max_length=80)

class Restaurant(Place):
    serves_hot_dogs = models.BooleanField(default=False)
    serves_pizza = models.BooleanField(default=False)

Chúng ta có 2 model là PlaceRestaurant kế thừa từ Place. Khác với kiểu kế thừa từ lớp trừu tượng, ở lớp trừu tượng thì các trường của lớp cha sẽ nằm cùng bảng với các lớp con vì lớp cha không được tạo bảng riêng, với thừa kế đa bảng thì các lớp con cũng sẽ kế thừa các trường của lớp cha nhưng các trường của lớp cha sẽ nằm trong bảng riêng của lớp cha.

>>> Place.objects.filter(name="Bob's Cafe")
>>> Restaurant.objects.filter(name="Bob's Cafe")

Chúng ta vẫn có thể truy xuất các thông tin từ cả 2 lớp con và lớp cha như thường.

Lớp Proxy – lớp ủy quyền

Đôi khi bạn không muốn các lớp con có thêm các thuộc tính mới mà chỉ muốn có các phương thức mới, với 2 kiểu kế thừa ở trên bạn vẫn có thể làm được điều này nhưng Django sẽ tạo ra các bảng CSDL mới mặc dù không có trường nào cả, làm như thế sẽ lãng phí bộ nhớ và cũng khá kì cục.

Do đó Django cho phép chúng ta tạo ra các lớp Proxy (lớp ủy quyền), các lớp này kế thừa từ lớp cha nhưng không được tạo bảng mới trong CSDL, những lớp con này sẽ chỉ dùng để lưu các phương thức mới chứ không lưu các thuộc tính mới. Bạn có thể thực hiện thêm, sửa, xóa dữ liệu được kế thừa từ lớp cha, những dữ liệu này vẫn lưu ở bảng cha.

Ví dụ:

from django.db import models

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

class MyPerson(Person):
    class Meta:
        proxy = True

    def do_something(self):
        # ...
        pass

Chúng ta tạo lớp Person và lớp proxy MyPerson kế thừa từ lớp Person. Chúng ta tạo một lớp Proxy bằng cách khai báo lớp nội Meta và đưa vào thuộc tính proxy = True trong lớp Meta này.

>>> p = Person.objects.create(first_name="foobar")
>>> MyPerson.objects.get(first_name="foobar")
<MyPerson: foobar>

Bất cứ thao tác cập nhật dữ liệu nào từ lớp MyPerson cũng sẽ là thao tác trực tiếp với dữ liệu từ lớp Person.

class OrderedPerson(Person):
    class Meta:
        ordering = ["last_name"]
        proxy = True

Bạn vẫn có thể định nghĩa lại các thông tin khác trong lớp Meta như sử dụng thuộc tính ordering chẳng hạn… vì bản thân các thuộc tính trong lớp Meta cũng không được dùng để tạo bảng mà nó chỉ là các thông tin cấu hình model thôi.

Đa thừa kế

Vì Django được phát triển từ Python nên các model trong Django có thể thừa kế từ nhiều model cha. Lớp Meta của model con chỉ có thể thừa kế từ lớp Meta của lớp cha đầu tiên.

Thường thì chúng ta cũng ít khi thừa kế từ nhiều bảng vì càng thừa kế nhiều thì sẽ càng làm cho ứng dụng trở nên phức tạp, khó quản lý.

Ngoài ra khi sử dụng đa thừa kế mà các model cha có cùng chung tên thuộc tính khóa chính thì Django sẽ báo lỗi. Do đó khi sử dụng đa thừa kế, chúng ta nên khai báo các trường làm khóa chính một cách rõ ràng. Ví dụ:

class Article(models.Model):
    article_id = models.AutoField(primary_key=True)
    ...

class Book(models.Model):
    book_id = models.AutoField(primary_key=True)
    ...

class BookReview(Book, Article):
    pass

Trong đoạn code trên model BookReview thừa kế từ 2 model cha là ArticleBook, cả 2 lớp cha đều có tên thuộc tính khóa chính khác nhau.

Hoặc nếu không muốn phải khai báo các khóa chính thì chúng ta có thể khai báo các model cha kế thừa lại từ một model khác để dùng chung khóa chính:

class Piece(models.Model):
    pass

class Article(Piece):
    ...
 
class Book(Piece):
    ...

class BookReview(Book, Article):
    pass

Java Swing – Drag và Drop

Trong lập trình GUI thì Drag và Drop (tiếng việt là Kéo Thả) là hành động click và giữ chuột lên một đối tượng rồi “kéo” đối tượng đó lên một vị trí khác hoặc lên một đối tượng khác.

Kéo thả là một trong những tính năng thường thấy trong GUI, cho phép người dùng thực hiện các công việc phức tạp.

Chúng ta có thể kéo thả dữ liệu hoặc các đối tượng có hình thù cụ thể, ví dụ như chúng ta kéo một file ảnh vào cửa sổ chat để gửi file thì đó là kéo dữ liệu, hoặc kéo các tab trong trình duyệt Chrome là kéo đối tượng có hình thù.

Hầu hết các component trong Java Swing đều có phương thức hỗ trợ kéo thả, và chúng ta cũng có thể viết các phương thức kéo thả của riêng chúng ta.

Ví dụ

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JTextField;
import javax.swing.TransferHandler;


public class Example extends JFrame {

    JTextField field;
    JButton button;

    public Example() {

        setTitle("Drag And Drop Example");

        setLayout(null);

        button = new JButton("Button");
        button.setBounds(200, 50, 90, 25);

        field = new JTextField();
        field.setBounds(30, 50, 150, 25);

        add(button);
        add(field);

        field.setDragEnabled(true);
        button.setTransferHandler(new TransferHandler("text"));

        setSize(330, 150);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
        setVisible(true);
    }

    public static void main(String[] args) {
        new Example();
    }
}

Chúng ta hiển thị một JTextField và một JButton, đoạn text trong text field có thể kéo sang làm text cho button.

field.setDragEnabled(true);

Để dữ liệu của một component có thể kéo được thì chúng ta phải thiết lập trong phương thức setDragEnabled() vì mặc định Swing đã vô hiệu hóa tính năng này.

button.setTransferHandler(new TransferHandler("text"));

Lớp TransferHandler là trái tim của Drag và Drop trong Swing, lớp này có nhiệm vụ vận chuyển dữ liệu qua lại giữa các component, tham số của lớp này là một thuộc tính Bean, nếu bạn chưa biết gì về các thuộc tính bean thì chỉ cần nhớ rằng bất kì thuộc tính nào có phương thức getter  setter đều có thể đưa vào làm tham số cho TransferHandler.

Chẳng hạn như JButton có phương thức getText()setText(), do đó bạn có thể đưa tham số là "text", vì khi chúng ta thiết lập kiểu dữ liệu là "text", TransferHandler sẽ dùng các phương thức getter  setter tương ứng để lấy và nhập dữ liệu với các component. Và vì chúng ta thiết lập kiểu text nên bạn chỉ có thể kéo hoặc thả các chuỗi text vào ra component này thôi.

Capture

Tùy chỉnh khả năng kéo

Không phải tất cả các component trong Swing đều có thể kéo được, JLabel là một ví dụ, chúng ta phải code phương thức kéo riêng. Ở đây chúng ta sẽ thực hiện kéo thả thuộc tính icon.

import java.awt.FlowLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.TransferHandler;


public class Example extends JFrame {


    public Example() {

        setTitle("Drag And Drop Example");

        JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT, 50, 15));

        ImageIcon icon1 = new ImageIcon("C:/sad.png");
        ImageIcon icon2 = new ImageIcon("C:/smile.png");
        ImageIcon icon3 = new ImageIcon("C:/crying.png");

        JButton button = new JButton(icon2);
        button.setFocusable(false);

        JLabel label1 = new JLabel(icon1, JLabel.CENTER);
        JLabel label2 = new JLabel(icon3, JLabel.CENTER);

        MouseListener listener = new DragMouseAdapter();
        label1.addMouseListener(listener);
        label2.addMouseListener(listener);

        label1.setTransferHandler(new TransferHandler("icon"));
        button.setTransferHandler(new TransferHandler("icon"));
        label2.setTransferHandler(new TransferHandler("icon"));

        panel.add(label1);
        panel.add(button);
        panel.add(label2);
        add(panel);

        pack();
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
        setVisible(true);
    }

    class DragMouseAdapter extends MouseAdapter {
        public void mousePressed(MouseEvent e) {
            JComponent c = (JComponent) e.getSource();
            TransferHandler handler = c.getTransferHandler();
            handler.exportAsDrag(c, e, TransferHandler.COPY);
        }
    }

    public static void main(String[] args) {
        new Example();
    }
}

Chúng ta hiển thị 2 label và một button, cả 3 component này đều hiển thị icon, chúng ta có thể kéo icon từ 2 label vào làm icon cho button.

MouseListener listener = new DragMouseAdapter();
label1.addMouseListener(listener);
label2.addMouseListener(listener);

Như đã nói, JLabel không được hỗ trợ tính năng kéo, hay nói cách khác là không có phương thức setDragEnabled(), nên ở đây chúng ta tự gắn một MouseAdapter vào để mô phỏng sự kiện kéo.

label1.setTransferHandler(new TransferHandler("icon"));
button.setTransferHandler(new TransferHandler("icon"));
label2.setTransferHandler(new TransferHandler("icon"));

Cả 3 component của chúng ta đều có các phương thức getter/setter cho thuộc tính icon. Đối với các lớp có sẵn phương thức hỗ trợ kéo là setDragEnabled() thì bạn có thể không cần phải chỉ ra kiểu dữ liệu vận chuyển trong TransferHandler và Swing sẽ vận chuyển các dữ liệu mặc định (chẳng hạn như text đối với JTextField), còn với các lớp không có sẵn thì chúng ta phải chỉ ra kiểu dữ liệu rõ ràng trong phương thức setTransferHandler().

JComponent c = (JComponent) e.getSource();
TransferHandler handler = c.getTransferHandler();
handler.exportAsDrag(c, e, TransferHandler.COPY);

Ba dòng code trên thiết lập khả năng kéo cho JLabel bằng phương thức exportAsDrag(), phương thức này nhận vào đối tượng được kéo đi (e.getSource()), dữ liệu về sự kiện kéo chuột (e) và cách mà dữ liệu được, ở đây là TransferHandler.COPY, tức là dữ liệu từ component nguồn sẽ được copy sang đối tượng đích. Ngoài copy thì chúng ta còn có một số thao tác khác như MOVE, NONE, MOVE_OR_COPY, COPY, LINK.

Capture12

Tùy chỉnh khả năng thả

Nếu có một số component không có sẵn phương thức hỗ trợ kéo thì cũng có một số component không có phương thức hỗ trợ thả, JList là một ví dụ. Lý do là bởi vì khi chúng ta kéo thả vào JList thì Swing không biết chúng ta muốn thả vào như thế nào, vd như insert vào đầu list, cuối list hay giữa list hay ở vị trí bất kì nào…

import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;

import javax.swing.DefaultListModel;
import javax.swing.DropMode;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.TransferHandler;


public class Example extends JFrame {

    JTextField field;
    DefaultListModel model;

    public Example() {

        setTitle("Drag And Drop Example");

        JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT, 15, 15));
  
        JScrollPane pane = new JScrollPane();
        pane.setPreferredSize(new Dimension(180, 150));

        model = new DefaultListModel();
        JList list = new JList(model);

        list.setDropMode(DropMode.INSERT);
        list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        list.setTransferHandler(new ListHandler());

        field = new JTextField("");
        field.setPreferredSize(new Dimension(150, 25));
        field.setDragEnabled(true);

        panel.add(field);
        pane.getViewport().add(list); 
        panel.add(pane);

        add(panel);

        pack();

        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
        setVisible(true);
    }


    private class ListHandler extends TransferHandler {
        public boolean canImport(TransferSupport support) {
            if (!support.isDrop()) {
                return false;
            }

            return support.isDataFlavorSupported(DataFlavor.stringFlavor);
        }

        public boolean importData(TransferSupport support) {
            if (!canImport(support)) {
                return false;
            }

            Transferable transferable = support.getTransferable();
            String line;
            try {
                line = (String) transferable.getTransferData(DataFlavor.stringFlavor);
            } catch (Exception e) {
                return false;
            }

            JList.DropLocation dl = (JList.DropLocation) support.getDropLocation();
            int index = dl.getIndex();

            String[] data = line.split(",");
            for (String item: data) {
                if (!item.isEmpty())
                    model.add(index++, item.trim());
            }
            return true;
       }
    }

    public static void main(String[] args) {
        new Example();
    }
}

Chúng ta sử dụng một JTextField và một JList, các đoạn text trong JTextField có thể được kéo qua JList, đoạn text sẽ dược phân ra thành nhiều text con bằng dấu phẩy.

list.setDropMode(DropMode.INSERT);

Phương thức setDropMode() thiết lập cách dữ liệu được thả vào, ở đây là DropMode.INSERT, tức là chèn vào cuối list.

list.setTransferHandler(new ListHandler());

Mặc dù chúng ta chỉ kéo thả các đoạn text bình thường từ JTextField sang nhưng vì JList không có các phương thức getter/setter tương ứng nên chúng ta phải định nghĩa một lớp TransferHandler riêng là ListHandler.

public boolean canImport(TransferSupport support) {
    if (!support.isDrop()) {
        return false;
    }
    return support.isDataFlavorSupported(DataFlavor.stringFlavor);
}

Khi chúng ta kéo dữ liệu sang một component (chỉ kéo thôi chứ chưa thả), phương thức canImport() của đối tượng TransferHandler đó sẽ được gọi liên tục, phương thức này nhận một đối tượng TransferSupport do Swing tự tạo ra, phương thức này sẽ kiểm tra xem dữ liệu được chuyển sang có được nhận hay không. Chúng ta override lại phương thức này trong lớp ListHandler.

Ở đây chúng ta dùng phương thức isDrop() kiểm tra xem người dùng đã thả dữ liệu ra chưa hay vẫn còn giữ chuột để tiếp tục kéo. Phương thức isDataFlavorSupported() kiểm tra xem dữ liệu được chuyển sang có được hỗ trợ hay không, phương thức này nhận vào một đối tượng lớp DataFlavor, lớp này lưu trữ thông tin về các loại dữ liệu khác nhau, ở đây DataFlavor.stringFlavor lưu thông tin về kiểu string, ngoài ra còn một số kiểu khác như imageFlavor, allHtmlFlavor… bạn có thể tìm hiểu thêm tại đây.

public boolean importData(TransferSupport support) {
...
}

Phương thức importData() trong lớp TransferHandler sẽ được gọi khi người dùng thả chuột ra, tức là thả dữ liệu lên component, phương thức này nhận một đối tượng TransferSupport do Swing tự tạo ra, chúng ta cũng override lại phương thức này.

Transferable transferable = support.getTransferable();

Bên trong đối tượng TransferSupport có chứa một đối tượng Transferable, đây là đối tượng chứa thông tin về dữ liệu được vận chuyển.

line = (String) transferable.getTransferData(DataFlavor.stringFlavor);

Để lấy dữ liệu text thì chúng ta dùng phương thức getTransferData() và đưa vào tham số DataFlavor tương ứng.

JList.DropLocation dl = (JList.DropLocation) support.getDropLocation();
int index = dl.getIndex();

Bên trong đối tượng TranferSupport còn chứa một đối tượng TransferHandler.DropLocation, chúng ta lấy ra từ phương thức getDropLocation(), lớp này chứa thông tin về tọa độ chuột mà dữ liệu được thả.

Lớp JList cũng có một lớp DropLocation riêng kế thừa từ lớp này, JList.DropLocation sẽ dựa vào tọa độ được thả ra mà tính vị trí chỉ số trong danh sách các item, chúng ta lấy chỉ số này thông qua phương thức getIndex().

String[] data = line.split(",");
for (String item: data) {
    if (!item.isEmpty())
        model.add(index++, item.trim());
}

Cuối cùng chúng ta phân tích đoạn text được gửi sang rồi insert vào JList.

Capture-13