Note: 上午还说 最近更新可能会比较缓慢的 ,结果下午就又上来发了一篇,我自己也很囧啊……
在 Django Admin中使用ForeignKey是一个比较痛苦的事, 默认使用的是 Select 在数据量比较大的情况下要选中需要选择的对象实在是太难了…… 最近刚刚好又碰到了这个问题,为了图方便,干脆就祭起了 raw_id_fields 这个属性来把外键的选择简化为直接输入id。
可是使用下来出现了问题,如果输入的id无效,Django并不会返回期望的包含错误信息的form,而是直接报了个 django.template.TemplateSyntaxError 的错误。情况很奇怪,FK使用的是 ModelChoiceField ,理论上对无效id的错误已经做了捕捉,那问题到底出在什么地方呢?
这儿再次bs以下Django默认的Template Debug。本来图省事就直接用Django内建的server跑测试,当使用werkzeug作为开发服务器后,使用相同的操作重现错误,就看到了最初报错的地方了。
Original Traceback (most recent call last): File "/usr/lib/python2.6/site-packages/django/template/debug.py", line 71, in render_node result = node.render(context) File "/usr/lib/python2.6/site-packages/django/template/debug.py", line 87, in render output = force_unicode(self.filter_expression.resolve(context)) File "/usr/lib/python2.6/site-packages/django/utils/encoding.py", line 71, in force_unicode s = unicode(s) File "/usr/lib/python2.6/site-packages/django/forms/forms.py", line 356, in __unicode__ return self.as_widget() File "/usr/lib/python2.6/site-packages/django/forms/forms.py", line 391, in as_widget return widget.render(name, data, attrs=attrs) File "/usr/lib/python2.6/site-packages/django/contrib/admin/widgets.py", line 126, in render output.append(self.label_for_value(value)) File "/usr/lib/python2.6/site-packages/django/contrib/admin/widgets.py", line 150, in label_for_value obj = self.rel.to._default_manager.get(**{key: value}) File "/usr/lib/python2.6/site-packages/django/db/models/manager.py", line 120, in get return self.get_query_set().get(*args, **kwargs) File "/usr/lib/python2.6/site-packages/django/db/models/query.py", line 305, in get % self.model._meta.object_name) DoesNotExist: TargetUser matching query does not exist.
顺着这个trackback的信息往上找,立刻就找到了问题所在 label_for_value 。问题出在了 ForeignKeyRawIdWidget 的处理上。Raw id field会在值有效的情况下在输入框后面显示链接对象的相关信息,但是这个widget使用了渲染时传入的value是否为空来判断该值是否有效,在有效的情况下,会调用 label_for_value(value) 来获得该值对应的对象信息。但是在这个函数里并没有处理 ObjectDoesNotExist 的异常,所以出现了上述的错误。
那既然发现了错误,在不改变Django源码的前提下,定义一个Widget的子类来覆盖掉原来的 label_for_value 的行为。
from django.contrib.admin.widgets import ForeignKeyRawIdWidget class AdminEnhancedFKRawIdWidget(ForeignKeyRawIdWidget): def label_for_value(self, value): try: return super(AdminEnhancedFKRawIdWidget, self).label_for_value(value) except self.rel.to.DoesNotExist: return ''
然后在相关的ModelAdmin中加上如下代码:
class SomeModelAdmin(admin.ModelAdmin): raw_id_fields = ('some_field', ) def get_form(self, request, obj=None, **kwargs): form = super(SomeModelAdmin,self).get_form(request, obj,**kwargs) w = form.base_fields['some_field'] w.widget = AdminEnhancedFKRawIdWidget(w.widget.rel, w.widget.attrs) return form
当然也可以使用嫁接的方法来达到目的,我这儿就不贴了。
P.S. 跑到Django Code上建了一个 Ticket ,然后很快就被告知duplicated了……

