Cách sử dụng Django admin và các trường hợp chống chỉ định
Django admin là một app rất hữu ích và đầy đủ tính năng, được tích hợp sẵn trong Django framework. Sử dụng Django admin sẽ giúp chúng ta phát triển một trang Web nhanh hơn. Trong bài viết này, tôi sẽ trình bày một số tip để làm việc với Django admin.
Các tip được trình bày dưới đây sẽ minh họa cho một số bài toán trong thực tế, để các tip được trình bày một cách trực quan nhất. Giả sử chúng ta có một trang Web đơn giản, nơi mà người dùng có thể post các bức ảnh pet mà họ yêu thích và người dùng có thể comment với các bức ảnh được up lên. Đây là một ví dụ khá đơn giản và có những tính năng phổ biến.
- Tip 1: Django admin không phải chỉ dành cho trang Web viết bằng Django
- Tip #2: Lọc dữ liệu theo ý của bạn
- Tip #3: Cho những thao tác phổ biến vào “actions”
- Tip #4: Search trong bất cứ trường nào bạn muốn
- Tip #5: Xem trang view của đối tượng
- Tip #6: Edit một trường ngay tại chỗ trong chính trang index
- Tip #7: Tùy biến total với dữ liệu bạn muốn
- Tip #8: Readonly trang admin với một vài người dùng
- Tip #9: Tùy biến thao tác với từng đối tượng
- Bonus Tip: Giảm thiểu query bằng cách thêm một dòng duy nhất vào admin
- Khi nào thì không nên dùng Django admin
- Kết luận
Tip 1: Django admin không phải chỉ dành cho trang Web viết bằng Django
Trang admin của Django hoạt động rất tốt khi kết hợp với phần còn lại của framework, tuy nhiên nó cũng rất dễ dàng để hoạt động cùng với những cơ sở dữ liệu của trang khác, hoặc làm trang admin của chính trang đó nếu trang đó có giao diện admin không đẹp lắm. Và rất dễ dàng để kiểm tra xem liệu Django admin có phù hợp với nhu cầu của bạn hay không.
Tất cả những gì bạn cần làm là:
- Tạo một app mới trong project của Django và cấu hình để nó kết nối đến đúng cơ sở dữ liệu.
- Tạo model cho Django theo cấu trúc của cơ sở dữ liệu. Một lệnh rất hữu ích trong trường hợp này là
manage.py inspectdb
sẽ giúp bạn, nó sẽ kiểm tra cơ sở dữ liệu đang có và tự động generate model. - Tạo module
admin.py
và cấu hình những gì bạn muốn.
Trở lại với ví dụ của chúng ta về trang up ảnh pet. Nó có thể được viết bằng bất cứ ngôn ngữ nào, giả sử trang admin của nó không được đẹp lắm. Để thay đổi điều này, chúng ta sẽ đưa cấu trúc cơ sở dữ liệu của nó vào model và sử dụng một cấu hình admin đơn giản của Django.
# models.py
class Picture(models.Model):
DOG = 1
CAT = 2
ANIMAL_KIND_CHOICES = (
(DOG, 'dog'),
(CAT, 'cat'),
)
title = models.CharField(max_length=200)
author = models.ForeignKey(Author, related_name='pictures')
animal_kind = models.IntegerField(choices=ANIMAL_KIND_CHOICES)
photo = models.ImageField(upload_to='animals')
is_promoted = models.BooleanField(default=False)
class Author(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField()
class Comment(models.Model):
author = models.ForeignKey(Author, related_name='comments')
picture = models.ForeignKey(Picture, related_name='comments')
comment = models.TextField()
editors_note = models.TextField()
# admin.py
class PictureAdmin(admin.ModelAdmin):
list_display = ('photo', 'animal_kind', 'author', 'is_promoted', )
class AuthorAdmin(admin.ModelAdmin):
list_display = ('name', 'email', )
class CommentAdmin(admin.ModelAdmin):
list_display = ('picture', 'author', )
Tip #2: Lọc dữ liệu theo ý của bạn
Phần lớn người sử dụng Django admin đều dùng filter với một số trường nhất định. Việc này thực hiện rất đơn giản bằng cách thêm tên trường vào list_filter
. Nhưng chúng ta hoàn toàn có thể tùy biến việc lọc dữ liệu này.
Ví dụ với trang Web của chúng ta, bạn muốn lọc ra những bức ảnh có trên 100 comment. Vậy phải làm thế nào? Chúng ta sẽ tạo ra một filter mới và cho nó vào list_filter
.
class ProductiveAuthorsFilter(admin.SimpleListFilter):
parameter_name = 'is_productive'
title = 'Productive author'
YES, NO = 1, 0
# Number of comments for an author to be considered a productive one
THRESHOLD = 100
def lookups(self, request, model_admin):
return (
(self.YES, 'yes'),
(self.NO, 'no'),
)
def queryset(self, request, queryset):
qs = queryset.annotate(Count('comments'))
# Note the syntax. This way we avoid touching the queryset if our
# filter is not used at all.
if self.value() == self.YES:
return qs.filter(comments__count__gte=self.THRESHOLD)
if self.value() == self.NO:
return qs.filter(comments__count__lt=self.THRESHOLD)
return queryset
class PictureAdmin(admin.ModelAdmin):
list_filter = [..., ProductiveAuthorsFilter]
Bây giờ chúng ta đã lọc ra những bức ảnh được cộng đồng quan tâm tích cực một cách dễ dàng.
Và chúng ta có thể thực hiện các thao tác với những user này, ví dụ như quảng cáo chẳng hạn. Cách thực hiện cụ thể sẽ được trình bày trong phần tiếp theo.
Tip #3: Cho những thao tác phổ biến vào “actions”
Đây là một tính năng tuyệt với một trang admin. Bạn có nhớ mục “actions” trong thanh công cụ phía trên của mỗi model. Sẽ vô cùng tiện lợi nếu chúng ta có thể chọn ra một số bức ảnh ấn tượng và quảng bá cho nó chỉ bằng một vài click chuột. Django admin cho phép chúng ta làm như vậy rất dễ dàng.
class PictureAdmin(admin.ModelAdmin):
actions = ['promote', ]
def promote(self, request, queryset):
queryset.update(is_promoted=True)
self.message_user(request, 'The posts are promoted')
promote.short_description = 'Promote the pictures'
Đây chính là điều chúng ta cần. Không cần phải mở form cho từng đối tượng nữa. Hơn nữa, nó rất dễ dàng mở rộng action của chúng ta, ví dụ sử dụng intermediate form. Django đã trình bày điều này rất đầy đủ và chi tiết.
Tip #4: Search trong bất cứ trường nào bạn muốn
Filter là một tính năng hay, nhưng nhiều khi chúng ta cần đến tính năng tìm kiếm. Trong phần lớn các ứng dụng, search được sử dụng để tìm kiếm qua các trường của model. Nhưng Django search có thể làm được nhiều hơn thế, khi nó có thể làm việc với các bảng liên kết. Ví dụ, chúng ta muốn tìm kiếm tiêu đề các bức ảnh, tên tác giả và các comment. Chúng ta sẽ làm thế nào?
class PictureAdmin(admin.ModelAdmin):
search_fields = ('title', 'author__name', 'comments__text', )
Nếu cơ sở dữ liệu của bạn khá lớn, thì bạn hãy thêm index để việc tìm kiếm nhanh chóng hơn.
Tip #5: Xem trang view của đối tượng
Một nhu cầu rất hiển nhiên là chúng ta cần xem trang view của đối tượng được tạo ra ở trang admin hiển thị như thế nào với user thông thường. Bình thường, bạn cần truy cập vào form của đối tượng đó và click “View on site”. Chúng ta có thể làm việc này dễ dàng hơn:
class PictureAdmin(admin.ModelAdmin):
list_display = [..., 'object_link']
def object_link(self, item):
url = item.get_absolute_url()
return format_html('<a href="{url}">open</a>', url=url)
object_link.short_description = 'View on site'
Đoạn code trên sẽ thêm link “View on site” vào các đối tượng ở trang index. Ở ví dụ trên, tôi giả thiết rằng bạn đã cài đặt phương thức get_absolute_url
cho model. Bạn có thể đưa đoạn code này vào mixin
hoặc một trang admin chung để dùng nó cho tất cả các đối tượng.
Tip #6: Edit một trường ngay tại chỗ trong chính trang index
Giả sử chúng ta muốn thêm editor_note
vào các comment, và đương nhiên, chúng ta không muốn mở form của từng comment để làm việc này. Django cho phép chúng ta làm việc này dễ dàng bằng cách thay đổi ModelAdmin.
class CommentAdmin(admin.ModelAdmin):
list_display = ('picture', 'author', 'editors_note', )
list_editable = ('editors_note', )
Chỉ cần vào dòng code như trên là đủ. Bây giờ bạn có thể mở danh sách các comment, lọc theo tiêu chí mà bạn muốn, và có thể viết editor_note
một cách dễ dàng.
Tip #7: Tùy biến total
với dữ liệu bạn muốn
Có một dòng total
ở dưới mỗi “changelist”. Giả sử bạn muốn phân biệt số lượng các bức ảnh về chó và mèo. Tính năng này sẽ yêu cầu chúng ta code khá nhiều vì phải override changelist
và template (bạn có thể tìm hiểu thêm về override template)
“Changelist” là trang mà chúng ta liệt kê các đối tượng theo một số tiêu chí nào đó. Bạn có thể lọc và thực hiện một số thao tác với những đối tượng này. Thông thường, click vào một đối tượng trong changelist sẽ đưa bạn đến changeform của đối tượng đó.
from django.contrib.admin.views.main import ChangeList
class PicturesChangeList(ChangeList):
def get_results(self, request):
super(PicturesChangeList, self).get_results(request)
totals = self.result_list.aggregate(
dogs_count=Sum(Case(When(animal_kind=Picture.DOG, then=1),
output_field=IntegerField())),
cats_count=Sum(Case(When(animal_kind=Picture.CAT, then=1),
output_field=IntegerField())))
self.totals = totals
class PictureAdmin(admin.ModelAdmin):
def get_changelist(self, request):
return PicturesChangeList
Và dưới đây là template
{% extends "admin/change_list.html" %}
{% block result_list %}
{{ block.super }}
<p>
There are
<strong>
{{ cl.totals.dogs_count|default:'none' }} dogs and
{{ cl.totals.cats_count|default:'none' }} cats
</strong>
on this page.
</p>
{% endblock %}
Tip #8: Readonly trang admin với một vài người dùng
Ví dụ chúng ta muốn giới hạn readonly cho một vài người dùng để tránh việc họ có thể làm hỏng cả trang Web. Chúng ta có thể làm việc này khá đơn giản với Django admin.
class GrandmaProofAdmin(admin.ModelAdmin):
def get_readonly_fields(self, request, obj=None):
if request.user.username == 'granny':
return [f.name for f in self.model._meta.fields]
else:
return super(GrandmaProofAdmin, self).get_readonly_fields(request, obj)
class PictureAdmin(GrandmaProofAdmin):
...
Bây giờ, bạn có thể kế thừa quyền edit với những user được chỉ định. Lưu ý rằng, cách làm này không đảm bảo sẽ phù hợp với mọi hoàn cảnh. Bạn có thể cần cài đặt nhiều hơn nữa.
Tip #9: Tùy biến thao tác với từng đối tượng
Có những lúc, bạn chỉ muốn thực hiện action nào đó với chỉ một đối tượng mà thôi. Mục “action” trong thanh công cụ có thể làm được, nhưng việc phải chọn ra đối tượng đó, chọn chọn action khá mất thời gian. Bạn có thể thao tác nhanh hơn bằng cách click chỉ 1 nút.
Ví dụ chúng ta muốn gửi email đến các user, biểu thị sự tri ân với đóng góp của họ với trang Web của chúng ta.
class PictureAdmin(admin.ModelAdmin):
list_display = (..., 'mail_link', )
def mail_link(self, obj):
dest = reverse('admin:myapp_pictures_mail_author',
kwargs={'pk': obj.pk})
return format_html('<a href="{url}">{title}</a>',
url=dest, title='send mail')
mail_link.short_description = 'Show some love'
mail_link.allow_tags = True
def get_urls(self):
urls = [
url('^(?P<pk>\d+)/sendaletter/?$',
self.admin_site.admin_view(self.mail_view),
name='myapp_pictures_mail_author'),
]
return urls + super(PictureAdmin, self).get_urls()
def mail_view(self, request, *args, **kwargs):
obj = get_object_or_404(Picture, pk=kwargs['pk'])
send_mail('Feel the granny\'s love', 'Hey, she loves your pet!',
'granny@yoursite.com', [obj.author.email])
self.message_user(request, 'The letter is on its way')
return redirect(reverse('admin:myapp_picture_changelist'))
Rất đơn giản, bây giờ, một link sẽ hiển thị bên cạnh các trường của đối tượng, cho phép chúng ta có thể send mail nhanh chóng bằng cách click.
Bonus Tip: Giảm thiểu query bằng cách thêm một dòng duy nhất vào admin
Một tip khá phổ biến với Django admin (và cả các thành phần khác) là select_related
. Nó sẽ giúp chúng ta load các đối tượng liên quan bằng cách thêm tên của chúng vào list_select_related
và khi hiển thị thì không cần query thêm nữa. Nhưng nếu chúng ta muốn load tất cả các đối tượng liên quan thì sao? Rất đơn giản, chúng ta có thể set giá trị list_select_related
là True
thì Django sẽ load tất cả các đối tượng được liên kết với đối tượng hiện tại.
class PictureAdmin(admin.ModelAdmin):
list_select_related = True
Khi nào thì không nên dùng Django admin
Trên đây là những tip và chúng ta có thể dùng để làm việc với Django admin. Tuy nhiên, Django admin cũng có những hạn chế nhất định.
Dưới dây là những hạn chế của Django admin, nếu bạn cảm thấy nó đang cản trở hoạt động của trang Web của bạn, thì bạn không nên dùng nó.
Cập nhật template
Một điều khá bất cập là mặc dù đã được test để đảm bảo tương thích, nhưng template và thiết kế của những phiên bản mới vẫn có trường hợp không tương thích với phiên bản cũ. Nhất là khi bạn đã tùy biến và override nhiều thành phần rồi, việc update lên phiên bản mới có thể làm vỡ layout hiện tại. Django 1.9 với thiết kế phẳng cho trang admin, tuy nhiên hãy cân nhắc kỹ trước khi update vì nếu không thiết này không tương thích, bạn sẽ phải sửa khá nhiều.
JavaScript API không ổn định
Cũng giống như những API khác của Django, JavaScript API cũng không được đảm bảo là ổn định. Hơn nữa, nó cũng không cho phép bạn tùy biến. Ví dụ, bạn không thể thay đổi độ rộng của các popup mà không override gần như toàn bộ file.
Permission mặc định không phù hợp
Nếu bạn muốn xem danh sách các đối tượng, Django admin cung cấp cho bạn changelist. Bạn có thể tùy biến nó, rất ấn tượng. Nhưng nó có một vấn đề: để xem được changelist, bạn cần phải có quyền “change”. Nhưng với một trang Web thông thường, việc chỉ xem danh sách đối tượng thì quyền “view” là đủ.
Vì vậy, một developer phải đối mặt với 2 lựa chọn: một là bỏ tính năng changelist, hai là override nó với 90% code dùng lại. Lựa chọn nào cũng không phải ý tưởng hay.
Không thể sử dụng class-based view
Hiện tại, bạn chưa thể sử dụng class-based view cho trang admin. Nếu trang Web của bạn có sử dụng class-based view để chia sẻ những tính năng giống nhau thì việc gắn kết nó vào trang admin cũng không hề dễ dàng. Bạn có thể phải thay đổi middleware.
Cài đặt nguyên khối
Module django.contrib.admin.options
có hơn 1900 dòng code (vào thời điểm bài viết này là 1977 dòng). Tất nhiên là những dòng code này rất dễ hiểu, nhưng việc duy trì một file gần 2000 dòng code không phải là ý tưởng hay. Nếu bạn quan tâm thì có thể tìm hiểu thêm package django.db.models
có thể có những module còn lớn hơn nữa.
Fieldset
chỉ hiệu quả với các trường hợp đơn giản
Với fieldset
của admin, nó sẽ hiệu quả nếu bạn làm việc với những model được liên kết đơn giản. Nếu bạn muốn sử dụng các tab, fieldset đóng mở, v.v… thì có thể bạn sẽ cần code khá nhiều để tùy biến module này.
Kết luận
Django admin là một app rất đầy đủ, tiện lợi với người phát triển Web. Bài viết này trình bày cả những lợi ích và hạn chế của nó. Nên tùy vào tình huống thực tế mà bạn có thể quyết định xem có nên sử dụng nó hay không.
Welcome
Đây là thế giới của manhhomienbienthuy (naa). Chào mừng đến với thế giới của tôi!
Bài viết liên quan
Bài viết mới
Chuyên mục
Lưu trữ theo năm
Thông tin liên hệ
Cảm ơn bạn đã quan tâm blog của tôi. Nếu có bất điều gì muốn nói, bạn có thể liên hệ với tôi qua các mạng xã hội, tạo discussion hoặc report issue trên Github.