Django Admin管理后台详解2(转)
转载:https://mp.weixin.qq.com/s?__biz=MjM5OTMyODA4Nw==&mid=2247484136&idx=1&sn=b3f983f56a8d6fe94b3c137db5b9a209&chksm=a73c62d0904bebc6709eacdcdff45bf5b1934036b9b884b8cbbe172c608c0f18336ae1e658a3&scene=178&cur_album_id=1351340083605061635#rd
今天我们来看下django admin的一些高级技巧,比如如何自定义list_display和list_filter, 以及如何重写django admin的save方法和get_queryset方法。
自定义list_display
前文中我们已经介绍过django admin的list_display选项不能用于显示多对多的字段(如tags)。如果需要通过list_display选项显示多对多的字段或模型中原本不存在的字段或方法,我们需要新增自定义的list_play方法。
我们现在ArticleAdmin里重新定义一个show_tags方法,该方法有2个变量self和obj。我们先调用obj.tags.all()获取某篇文章obj的所有tags,然后使用join方法将它们连接成字符串。由于show_tags这个名字作为表单的header读起来并不友好,我们可以通过设置该方法的short_description属性来设置表头显示为"标签".
#blog/admin.py
class ArticleAdmin(admin.ModelAdmin): '''设置列表可显示的字段''' list_display = ('title', 'author', 'status', 'mod_date', 'show_tags') '''展示tags''' def show_tags(self, obj): tag_list = [] for tag in obj.tags.all(): tag_list.append(tag.name) return ','.join(tag_list) show_tags.short_description = '标签' # 设置表头
显示效果如下:
你注意到上面图片有个问题没有?当标签为空的时候,显示内容为空白。有的时候我们需要设置空白值(empty value)来更好地提示用户,有的时候我们还要以不同颜色显示提示信息。下面我们就要对admin.py做些改进,当文章没有标签时,我们以红色字体显示没有"没有标签"。在下面中我们使用了format_html方法对字符串添加了样式。
#blog/admin.py
from django.contrib import admin from .models import Article, Category, Tag from django.utils.html import format_html # Register your models here. class ArticleAdmin(admin.ModelAdmin): '''设置列表可显示的字段''' list_display = ('title', 'author', 'status', 'mod_date', 'show_tags') '''展示tags''' def show_tags(self, obj): tag_list = [] tags = obj.tags.all() if tags: for tag in tags: tag_list.append(tag.name) return ','.join(tag_list) else: return format_html( '文章{}无标签', obj.id,) '''设置表头''' show_tags.short_description = '标签' # 设置表头 admin.site.register(Article, ArticleAdmin)
改进的展示效果如下:
注意:
-
尽管我们实现了在列表中显示多对多的字段,但在实际项目中我们并不建议这么做,因为这额外地增加了许多数据库查询的工作量。
-
获取某篇文章obj所对应的tags正确方法是使用obj.tags.all()而不是obj.tags.all, 否则会出现tags is not iterable的错误。你需要了解,obj.tags.all方法仅限于在模板中使用,在admin或view中使用时,千万别忘了加括号。
在本例中我们手动编写了需要显示的empty value。Django实际上是允许我们给所有的空字段设置显示名字的,我们只需要使用empty_value_display选项即可。我们还可以通过设置admin_order_field选项设置需要排序的字段。设置了admin_order_field选项的字段表头会出现一个小三角按钮,用户可以点击三角按钮实现正序或逆序排列。使用例子如下所示:
empty_value_display = "空值" admin_order_field = ('title', 'mod_date')
自定义list_filter
自定义list_filter也是一个非常有用的Django技术,可以让用户快速找到自己需要查看或编辑的对象。在之前的案例中,我们的list_filter已经实现了按文章状态和发布时间对文章进行过滤。现在我们需要自定义一个过滤器,按文章标题所含的关键词(比如python, django)对所有文章进行过滤, 并显示在右边的过滤条件栏目里。
完整代码如下。我们定义了一个TitleKeywordFilter类,该类继承了admin的SimpleListFilter类。我们设置了过滤器的标题和参数名(keyword), 并通过lookups方法定义了过滤参数元组,并调用queryset方法返回符合查询条件的查询数据集。
from django.contrib import admin from .models import Article, Category, Tag class TitleKeywordFilter(admin.SimpleListFilter): # 右侧栏人为可读的标题 title = '标题关键词' # 在url中显示的参数名,如?keyword=xxx. parameter_name = 'keyword' """ 自定义需要筛选的参数元组. """ def lookups(self, request, model_admin): return ( ('python', '含python文章'), ('django', '含django文章'), ) def queryset(self, request, queryset): """ 调用self.value()获取url中的参数, 然后筛选所需的queryset. """ if self.value() == 'python': return queryset.filter(title__icontains='python') if self.value() == 'django': return queryset.filter(title__icontains='django') class ArticleAdmin(admin.ModelAdmin): '''设置过滤选项''' list_filter = ('status', TitleKeywordFilter, 'pub_date', )
展示效果如下所示, 是不是很帅?
注意:
-
自定义的Filter类的代码必需放在ModelAdmin类的前面,否则无法使用。
-
自定义的Filter参数名parameter_name不要使用q和next,这两个参数已作为django admin的默认参数使用了。
重写Django admin的save_model方法
很多时候,我们需要重写Django自带的save_model方法。比如在文章创建时我们希望在后台自动添加作者,而不是允许用户自己选择作者是谁,我们可以选择在创建文章的表单里把作者隐藏,而在后台添加作者。如下所示:
from django.contrib import admin class ArticleAdmin(admin.ModelAdmin): def save_model(self, request, obj, form, change): obj.author = request.user super().save_model(request, obj, form, change)
在我们世界那么大,我想去看看。Django仿制微信朋友圈九宫格相册(1)一文中我们也展示了save_model方法的重写,该方法作用是允许用户在创建Album对象时,还上传一个zip文件包。上传后对zip文件包进行解压存储,并与每个Image对象想关联。
# album/forms.py
from django import forms from .models import Album class AlbumForm(forms.ModelForm): class Meta: model = Album exclude = [] zip = forms.FileField(required=False)
# album/admin.py
import os import uuid import zipfile from django.contrib import admin from django.core.files.base import ContentFile from .models import Album, AlbumImage from .forms import AlbumForm @admin.register(Album) class AlbumModelAdmin(admin.ModelAdmin): form = AlbumForm prepopulated_fields = {'slug': ('title',)} list_display = ('title', 'thumb') list_filter = ('create_date',) def save_model(self, request, obj, form, change): if form.is_valid(): album = form.save() if form.cleaned_data['zip'] is not None: zip = zipfile.ZipFile(form.cleaned_data['zip']) for filename in sorted(zip.namelist()): file_name = os.path.basename(filename) if not file_name: continue data = zip.read(filename) contentfile = ContentFile(data) img = AlbumImage() img.album = album filename = '{0}{1}.jpg'.format(album.slug[:8], str(uuid.uuid4())[-13:]) img.alt = filename img.image.save(filename, contentfile) img.thumb.save('thumb-{0}'.format(filename), contentfile) img.save() zip.close() super().save_model(request, obj, form, change)
还记得我们Django 2.0 项目实战: 扩展Django自带User模型,实现用户注册与登录中对django的User模型做的扩展吗?我们新建了一个UserProfile模型,其与User是一对一的关系。我们现在希望在admin中创建一个User对象时,也同时创建一个UserProfile对象,这时我们就需要用到save_model方法的重写了。代码如下所示:
#myaccount/admin.py
from django.contrib import admin from django.contrib.auth.models import User from django.contrib.auth.admin import UserAdmin from .models import UserProfile admin.site.unregister(User) class UserProfileInline(admin.StackedInline): model = UserProfile exclude = ["uid", "join_date", "mod_date"] class UserAdmin(UserAdmin): inlines = [UserProfileInline, ] def save_model(self, request, obj, form, change): if form.is_valid(): user = form.save() user_profile = UserProfile() user_profile.user = user user_profile.save() super().save_model(request, obj, form, change) class UserProfileAdmin(admin.ModelAdmin): list_display = ('user', 'avatar', 'org', 'join_date') exclude = [] ordering = ('-join_date',) admin.site.register(User, UserAdmin) admin.site.register(UserProfile, UserProfileAdmin)
展示效果如下。我们使用了Inlines使UserProfile与User展示在同一页面上。由于我们重写了save_model方法,这样可以自动在创建User时也创建UserProfile,避免了只创建User而未创建UserProfile的错误,这对1对1的关系非常重要。
重写Django admin的get_queryset方法
Django的admin默认会展示所有对象。通过重写get_queryset方法,我们可以控制所需要获取的对象。比如下例中,我们先对用户进行判定,如果用户是超级用户就展示所有文章,如果不是超级用户,我们仅展示用户自己所发表的文章。
class ArticleAdmin(admin.ModelAdmin): def get_queryset(self, request): qs = super().get_queryset(request) if request.user.is_superuser: return qs return qs.filter(author=request.user)
小结
本文总结了django admin的一些高级技巧,比如如何自定义list_display和list_filter, 以及如何重写django admin的save方法和get_queryset方法。这些方法和技巧都非常有用,要熟练掌握哦。下文将是django admin介绍的最后一篇,小编我将介绍下如何美化django-admin,欢迎关注。