Django 自定义 Admin change page 的一般方法

Django 的 Admin 系统可用性很高,尤其在一些简单的定制以后。本文主题为针对 change page (view)的常用定制,就是单个 model 实例(数据库某条记录)的显示与编辑页面,url 通常类似于 host/admin/modelname/1234/

定制的代码通常写在 app/admin.py 中的一个名为 ModelAdmin 的类里,主要以属性和方法的形式。或者更进一步说,代码大都存在于该类的 change_view(self, request, object_id, extra_context=None) 方法 或 save_model(self, request, obj, form, change) 方法中,这也就是下面示例代码在修改属性时都会使用一个 self.xxx = 'xxx' 语句的原因。

类名中的 Model 指的是 app/models.py 中具体的模型名。可定制的内容如下: <br /> #修改字段显示

###fields & exclude 属性 这两个属性的作用类似,类型也都是元组(推荐)。其中 fields=() 表示 “仅显示这些字段”,而 excludes=() 则表示 “不要显示这些字段”。视具体情境,一般二者只用其一,通常 fields 出现的机会更高些,毕竟他的控制更加精准。

lang:python
class UserAdmin(models.ModelAdmin):
	def change_view():
		self.fields = ('name','mobile','email')

注意元组元素的类型为字符串,后面类似元素的类型一般也都是字符串,不再强调。 <br /> ###readonly_fields 属性 这个属性的作用就和他的名字一样。默认 change page 显示的字段都是可编辑状态的,因为 admin 本来就是做可视化编辑数据库之用的嘛。

lang:python
...
	self.readonly_fields = ('name',)

额外需注意的 Python 语法:对于单元素的元组,必须在元素后面加一个 “,” 。这是因为 Python 支持对语句使用小括号封装,你不加逗号,会被解释器误认为一条语句,而不是元组。 <br /> ###fieldsets 属性 默认情况下,change page 对字段的显示是一个字段显示一行,然后把所有字段一字排下来。换句话说就是页面只有一个 <fieldset>。而使用 fieldsets 属性,就可以建立多个 <fieldset> ,通常伴随着的,还有将不同字段放在同一行的操作:

lang:python
...
    self.fieldsets = (
        (u'基本信息',{
            'fields':(
                ('name','mobile','email',),#一行
                ('address','city','country',),#另一行
                #新一行
                ),
            'classes':('person',),#html 标签的 class 属性
            }),
        (u'身份证信息',{ #另一个 fieldset
            'fields':(
                ('display_idcrdfnt','display_idcrdbck',),),
            }),
        )

这段代码因为括号太多看起来会比较乱,实际自己写一写就好了。其中 fieldsets 的元素为一个二元组:其中第一元为 <fieldset> 的名称,第二元为字典,键包括定义字段的 fields 和 定义html元素属性的 classes。 <br /> ###显示额外字段 前面的手段都是控制如何隐藏字段,显示 model 的子集。对于 model 里没有的字段,想在 Form 里显示的话就要把他们定义为 方法,并将方法名加入 readonly_fields 元组,因为这些字段都是不存在于数据库中的,所以必须展示为不可编辑状态。

lang:python
...
	self.fields = ('display_img',...)
	self.readonly_fields = ('display_img',...)
	
def display_img(self,obj):
	url = obj.img_url
	return '<img ref="%s" />'%url
	
display_img.short_description = '用户照片'
display_img.allow_tags = True

上例中假设 model 的img_url 字段存储了用户照片的链接,但我们想在 change page 展示出用户的照片,而不是一段字符串,那么就可以通过这种方式自定义一个显示字段,最后定义的两个属性:

  • short_description :对字段的说明性文字,会显示在图片的前面,类似于 sql 的 comment,或 model Field 的第一参数
  • allow_tags :tags 指的是 html 标签,上例中我们直接返回了一个 <img> 标签,那么这里就必须设置为 True,否则会被转义

<br /> ###直接修改 admin 模板 除了像上面通过 `change_view()` 方法来修改显示内容外,还可以直接编辑模板。`template/admin/change_form.html` (或其他路径)文件就是渲染 admin 页面所使用的模板。通常我们会选择在以下路径派生一个 app 专用模板 `template/admin/appname/change_form.html`。

而额外 context 的传递,就要通过 change_view(...,extra_context=None) 参数来实现了:

lang:python
...
	def change_view(self, request, object_id, extra_context=None):
		...
			extra_context = {'custom':'some_value'}
		return super(ModelAdmin,self).change_view(...,extra_context)

<br /> #修改保存请求 --- 在 change page 页面,如果用户点击了保存按钮,那么一个 POST 请求就会被提交上来,然后保存到数据库中。从用户提交请求到更新数据库之间有两个机会修改请求的内容。分别为 **修改 POST 请求** 和 **修改 Model 实例**。 <br /> ###修改 POST 请求 用户提交的 POST 请求会首先被 `change_view(self, request, object_id, extra_context=None)` 捕获,因此可以在这个方法里直接对其进行修改。

lang:python
...
	def change_view(self, request, object_id, extra_context=None)
		...
		if request.method == 'POST':
			request.POST['field'] = xxx
		return super(Model,self).change_view()

注意:django 出于安全考虑,不在 fields 中的字段,添加到 POST 里也没用;在 read_only 中的字段,你改了也没用。

对于这种状况,要么你在方法中改一次 fieldsreadonly_fields 属性,要么就把修改放到下一节的 save_model() 方法里。

lang:python
...
	def change_view(...):
		...
		if request.method == 'POST':
			new_readonly = list(self.readonly_fields)
			new_readonly.remove('some_field')
			self.readonly_fields = set(new_readonly)
			...
		return super(...)

要想避免因此操作导致用户下一次 GET change page 显示出额外可编辑字段的话,就应尽量把对如 readonly_fieldsfieldsets 等字段的定义写在 channge_view()if request.method == 'GET': 里,而不是直接写成类属性。

但其实并不太推荐上面这种过于 hack 的写法。 <br /> ###在保存前修改 Model 实例 更常用的对保存请求的修改发生在 save_model(self, request, obj, form, change) 里,参数里不仅有 request ,还有表示 Model 实例的 obj 可以用。这里因为 POST 已经被处理过了,所以你可以不受限制地修改 obj 的属性(当然要在数据有效的前提下)。

objform 分别是修改后待保存的 model 实例和 POST 提交的 Form 对象,即 obj.attr1form.save(commit=False).attr1 的值是相同的,都是最新的数据。但 form 里可能不会包含全部的模型字段,因为可能有一些字段被隐藏,还有一些是只读状态。change 参数是一个布尔值,表示当前的保存请求是来自于新建还是变更操作。

lang:python
def save_model(self, request, obj, form, change):
    if change:
        obj.mod_time = datetime.datetime.now()
    return super(ModelAdmin,self).save_model(request, obj, form, change)

虽然上面这个功能可以在 Model 中简单使用 auto_now=True 来实现。

一个更有代表性的例子可能是:我们希望用户不必手动修改某个字段,而是依据其点击的提交按钮的不同,来自动修改该字段。具体修改提交按钮(submit button)的方法在更下面给出,这里先假设我们自定义了一个 <input type="submit" value="审核通过" name="apply_approved"/> 按钮,并希望用户按此按钮时,自动将 Model 实例的 state 属性改为 approved

lang:python
...
	def save_model(self,request,obj,...):
		if 'apply_approved' in request.POST:
			obj.state = 'approved'
		...
		return super(...)

注意:save_model() 方法实际执行的就是将 POST 请求更新到数据库中的过程,因此 obj.save()super().save_model() 方法你要保证至少调用一个。不要将此方法用于否决 POST 请求(比如发现数据不合法时就简单的 pass 掉),这项要求是官方文档提出的,具体我也不清楚为什么。

这两种修改方法具体使用哪一种要视具体情况而定。一种可能的状况是:下节要讲到的 Model 里的 clean() 方法对用户提示 POST 数据非法的响应发生在 change_view() 之中,即 save_model() 之前,所以如果你想修改其中将要校验的字段,那么最好在 POST 里改,不然用户即接收不到提示,保存也不会生效。 <br /> ###数据校验(data validation) django 对提交数据的有效性验证提供了三种方式,分别对应三个层面。

  1. Form 对象校验

    通过 Form 对象来进行数据校验的方法很容易找到,包括 django book 中也有一章在讲,这里就不再赘述了

  2. Model 实例校验

    django 的 ORM 将每一条数据库记录映射为一个 Model 的实例,那么通过该实例的方法来对属性进行验证就非常顺理成章了。如

     lang:python
     from django.core.exceptions import ValidationError
    
     class Model(models.Model):
     	...
     	def clean(self):
     		if self.age < 18 and self.18x_authorization == True:
     			raise ValidationError('too young, too simple.')
    

    这里 raise ValidationError 会被 save_model() 捕捉,并将错误信息返回给 change page view,还有针对该非法字段的 CSS 样式展现。总之就是督促用户修改表单,再重新提交。注意若在 admin 的 save_model() 方法中引发此异常会导致 HTTP 500,所以记得要把它写在 Model 的 clean(self) 里。

    说到给用户返回消息,还有一个专用方法:

     lang:python
     from django.contrib import messages
    
     ...
     	messages.info(request, 'Hello world.')
     	messages.warning(request, 'FBI WARNING.')
     	messages.error(request, 'You shall not pass!')
    
  3. validators 字段校验

    这是一种针对 ModelField 的校验规则,具体我也没用过,可以去官网 ref/forms/validation/ 查看文档。

这三种不同的校验方法所作用的层面也不同,一般在 admin 中,或其他处理表单提交的地方,Form 和 Model 实例较常用。Form 多针对单个字段的数据类型或内容合法性验证,Model 实例则擅长做多字段间的逻辑性验证。 <br /> #修改底部提交栏

###submit_line 模板 在 admin 的模板加载路径内,可以找到一个 submit_line.html 文件,这便是 admin change page 底部那三个 保存并继续编辑保存并新增保存 按钮定义的位置。

可惜单在 template/admin/appname/change_form.html 同级目录下新建一个 submit_line.html 的方式并不能使其自动加载。你还得显式地在 change_form.htmlinclude 它。

lang:python
{% block submit_buttons_bottom %}
{% include "admin/audit/mmauth/submit_line.html" %}
{% endblock %}

django 1.4 起 submit row 才包含在一个 block 中,1.3 及以前的版本你需要手动修改 template/admin 目录下的公用模板. <br /> ###保存后重定向 在 admin 页面中,保存并转到下一个 (save and view next) 应该是一个很常用的按钮,可惜 django 并没有标配。

实现此功能的第一步是修改 submit_line 模板,把按钮改成(或添加成)我们需要的样子,注意按钮的 name = "_save" 属性,这个随 form 一起提交的字段是我们判断用户到底按了什么的依据。

save_model() 返回后,还有一个方法会被默认调用:response_change(self,request,obj)。重定向到 下一个 的代码就可以写在这里:

lang:python
...
	def response_change(self,request,obj):
		next = Auth.objects.filter(...)[:1]
		if next:
			return HttpResponseRedirect('../%d/'%next.get().id)
		return super(ModelAdmin,self).response_change(request,obj)

<br /> #其他 --- ###访问 Field 的 verbose_name Model 的 Field 第一位置参数是 `verbose_name` ,这个更加易读的字段名会被自动展示在 Admin Form 中,如果我们想在别处访问它的话,比如 messages 中,那么获取的方式可以是

lang:python
Model[instance]._meta.get_field('field_name').verbose_name

<br />

转载于:https://my.oschina.net/lionets/blog/312576

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值