Hầu hết model Django đều có hai trường thời gian: created_at để biết bản ghi được tạo khi nào và updated_at để biết lần sửa cuối. Cách nhanh nhất là dùng auto_now_add=True cho created_at và auto_now=True cho updated_at — Django tự điền cả hai, bạn không cần chạm vào chúng. Nhưng hai tham số này có ba “cái bẫy” hay gặp: không gán được ngày cũ khi import dữ liệu, update() hàng loạt không đổi updated_at, và trường bị ẩn khỏi form. Bài này chỉ rõ từng bẫy và cách xử lý.
Vấn đề
Giả sử bạn import 500 bài viết cũ từ blog khác và muốn giữ nguyên ngày đăng gốc. Bạn viết:
from datetime import datetimefrom django.utils import timezoneold_date = timezone.make_aware(datetime(2024, 5, 10, 9, 0))# Muốn giữ ngày đăng gốc khi import...post = Post.objects.create(title="Bài cũ", created_at=old_date)print(post.created_at) # Hôm nay, không phải 10/05/2024!
Kết quả: created_at bị ghi đè bằng thời điểm hiện tại, ngày gốc mất. Lý do: auto_now_add chỉ chạy lúc INSERT — mọi giá trị bạn truyền vào create() đều bị thay bằng timezone.now().
Bẫy thứ hai nằm ở cập nhật hàng loạt. Bạn đổi trạng thái 1.000 bài viết:
from django.utils import timezone# Bẫy thứ hai: cập nhật hàng loạtPost.objects.filter(status="draft").update(status="published")# updated_at của 1.000 bài viết vẫn giữ nguyên!
updated_at của toàn bộ bài viết vẫn giữ nguyên — vì QuerySet.update() chạy thẳng SQL, không gọi save() nên auto_now không kích hoạt. Người dùng nhìn vào “cập nhật lần cuối” sẽ thấy ngày cũ dù nội dung vừa đổi.
Giải pháp
Muốn giữ ngày gốc khi import, đừng dùng auto_now_add cho created_at. Thay bằng default=timezone.now — lưu ý truyền hàm, không gọi hàm:
from django.db import modelsfrom django.utils import timezoneclass Post(models.Model): title = models.CharField(max_length=200) status = models.CharField(max_length=20, default="draft") created_at = models.DateTimeField(default=timezone.now) updated_at = models.DateTimeField(default=timezone.now)
Với cách này, tạo mới không truyền created_at thì Django vẫn tự điền thời gian hiện tại, còn khi import bạn truyền ngày cũ vào và nó được giữ nguyên. Hai trường này vẫn chỉnh được trong admin và ModelForm, khác với auto_now/auto_now_add vốn bị ẩn.
Với cập nhật hàng loạt, truyền updated_at tường minh:
from django.utils import timezonePost.objects.filter(status="draft").update( status="published", updated_at=timezone.now(),)
Còn nếu vẫn muốn giữ auto_now_add vì không bao giờ cần sửa created_at, vẫn dùng được — chỉ nhớ gán ngày cũ sau khi tạo: tạo bản ghi trước, gán created_at rồi save() lần nữa. Vì auto_now_add chỉ chạy lúc INSERT nên lần save() thứ hai không ghi đè.
post = Post.objects.create(title="Bài cũ")post.created_at = old_date # Gán sau khi tạo thì giữ đượcpost.save()
Áp dụng vào dự án của bạn
Quy tắc chọn nhanh: dùng auto_now_add/auto_now cho các bảng chỉ cần “đóng dấu” thời gian như log, lịch sử thao tác, bài viết mà bạn không bao giờ sửa ngày. Dùng default=timezone.now khi ngày giờ là dữ liệu nghiệp vụ — ngày đăng bài, ngày hết hạn, ngày bắt đầu sự kiện — vì sớm muộn bạn cũng cần sửa tay, import dữ liệu cũ hoặc hiển thị cho người dùng chỉnh trong admin. Và sau mỗi lần dùng QuerySet.update() hay bulk_update(), kiểm tra lại xem updated_at có cần cập nhật tường minh không.
Bài viết liên quan
- Django: sửa lỗi N+1 query bằng select_related trong 5 phút
- Django: dùng get_or_create() và update_or_create() đúng cách — lấy hay tạo bản ghi chỉ trong 1 dòng
- Django: tạo và cập nhật hàng loạt với bulk_create/bulk_update thay vì save() trong vòng lặp
Câu hỏi thường gặp
auto_now và auto_now_add khác nhau thế nào?
auto_now_add chỉ chạy lúc tạo mới (INSERT), auto_now chạy mỗi lần save(). Cả hai đều tự điền timezone.now() và không sửa được qua form.
Vì sao update() không đổi updated_at?
update() chạy SQL trực tiếp, bỏ qua save() nên pre_save của auto_now không chạy. Truyền updated_at=timezone.now() vào update() để đổi tường minh.
default=timezone.now và default=timezone.now() khác nhau ra sao?
Truyền hàm (không ngoặc) thì Django gọi lại mỗi lần tạo bản ghi, mỗi bản ghi có giờ riêng. Gọi hàm sẵn (có ngoặc) thì mọi bản ghi dùng chung một thời điểm tính lúc import module — lỗi kinh điển.
Có nên dùng auto_now cho trường ngày đăng bài không?
Không, nếu ngày đăng là dữ liệu nghiệp vụ cần sửa tay hoặc import. Dùng default=timezone.now để vừa tự điền vừa sửa được.
bulk_update() có cập nhật auto_now không?
Không, giống update(). Truyền updated_at tường minh hoặc cập nhật từng bản ghi bằng save().
💬 Bạn đã thử cách này trong dự án của mình chưa? Nếu gặp lỗi hoặc có cách làm gọn hơn, để lại bình luận bên dưới — mình đọc hết và sẽ bổ sung những ý hay vào bài.
Nguồn tham khảo
- Django docs — DateField.auto_now / auto_now_add: docs.djangoproject.com/en/6.1/ref/models/fields/#django.db.models.DateField.auto_now
- Django docs — QuerySet.update(): docs.djangoproject.com/en/6.1/ref/models/querysets/#update
- Django docs — django.utils.timezone.now: docs.djangoproject.com/en/6.1/ref/utils/#django.utils.timezone.now
