使用 ModelForm 过滤 ForeignKey 选择项
2017-09-06
Django 已有129人围观

    在 Django Admin中,编辑外键(ForeignKey)字段时,列表中默认会显示所有的选项,但是有时候你希望能够根据某些条件过滤掉列表中的选项。本文就这种场景介绍两种可行方案。


一、使用 limit_choices_to


    Django 的 ForeignKey 自带选项,可以通过 limit_choices_to 实现一些简单的筛选需求。假如我们有个 Country, City Model 如下:

class Country(BaseModel):
    name = models.CharField(u'国家名', max_length=255, null=False, blank=False, unique=True)
    currency = models.CharField(u'货币', max_length=100, null=False, blank=False)
    currency_sign = models.CharField(u'货币符号', max_length=100, null=False, blank=False)
    calling_code = models.CharField(u'电话区号', max_length=100, null=True, blank=True)

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = u"国家"
        verbose_name_plural = u"国家"


class City(BaseModel):
    name = models.CharField(u'城市名', max_length=255, null=False, blank=False, unique=True)
    country = models.ForeignKey(Country, verbose_name=u'国家', related_name='cities', null=False, blank=False)
    level = models.IntegerField(u'城市级别', default=0, choices=((0, u'顶级城市'), (1, u'二级城市')))
    top_city = models.ForeignKey('self', verbose_name=u'父城市', limit_choices_to={'level': 0}, null=True, blank=True)
    time_zone = models.DecimalField(u'时区', max_digits=19, decimal_places=10, default=0)

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = u"城市"
        verbose_name_plural = u"城市"

    其中的 top_city 字段我只想获取 level 为顶级城市的所有城市,就可以指定 limit_choices_to={'level': 0}。剩下的工作就交给 Django 处理就行了。

    limit_choices_to 除了可以指定为一个 dict,还可以使用高级的 Q 操作,具体这里不作介绍,想了解的可以看 官方文档


二、重置 Admin 的 ModelFrom


    上述方法可以满足简单的需求,但是如果你想根据当前 City 对象中的 country 信息筛选 top_city 时,limit_choices_to 就不能满足需求了。假如目前我们只想在 top_city 中显示当前 obj 相同国家的所有顶级城市,可以使用 ModelForm 实现。

    这种方法复杂一点儿,简单来说就是 Admin class 中有个 form 选项,不指定的话 Django 会使用默认的 ModelForm。我们可以指定自己定义的 ModelForm,在 Form 中实现对外键字段选项的过滤。具体代码如下:

#### forms.py ####

# -*- coding: UTF-8 -*-
from django import forms

from .models import City


class CityForm(forms.ModelForm):

    def __init__(self, *args, **kwargs):
        super(CityForm, self).__init__(*args, **kwargs)
        if self.instance.country:
            self.fields['top_city'].queryset = City.objects.filter(level=0, country=self.instance.country)

    然后在 admin.py 中指定form

#### admin.py ####

from django.contrib import admin

class CityAdmin(admin.ModelAdmin):
    list_display = ('name', 'country', 'level', 'top_city', 'time_zone')
    search_fields = ('name',)
    list_filter = ('country', 'level')
    form = CityForm


Over!

本文地址:http://xianglong.me/article/use-django-modelform-filter-foreignkey-list/

特别声明:本站文章,如非注明,皆为降龙原创。转载需注明本文链接并保证链接可用。