django_filter 怎么用 choice 的值进行过滤

2019-01-24 19:06:32 +08:00
 leisurelylicht

我有一个 model IPInfo

class IPInfoModel(models.Model):
    TYPE_INTRANET = 1
    TYPE_INTERNET = 2
    IP_TYPES = (
        (TYPE_INTRANET, u'内网'),
        (TYPE_INTERNET, u'外网'),
    )
    ip = models.GenericIPAddressField("IP", unique=True)
    ip_type = models.SmallIntegerField(choices=IP_TYPES)

然后我用 django_filter 做过滤

from django_filters import rest_framework as django_filters 

class IPInfoFilter(django_filters.FilterSet):
    ip_type = django_filters.ChoiceFilter(choices=IPInfoModel.IP_TYPES)

    class Meta:
        model = IPInfoModel
        fields = ["ip_type",]


class IPInfoViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
    queryset = IPInfoModel.objects.all()
    serializer_class = IPInfoSerializer
    filter_class = IPInfoFilter

过滤 ip_type 的时候,我想用 choice 的值“内网“、“外网”做参数值,而不是“ 1 ”和“ 2 ”。

我该怎么做?

2575 次点击
所在节点    Python
10 条回复
arischow
2019-01-24 22:19:09 +08:00
IPInfoModel.TYPE_INTRANET
chi1st
2019-01-24 23:40:53 +08:00
@arischow A 佬?
leisurelylicht
2019-01-25 00:25:57 +08:00
@arischow ? 这... 什么意思?
yim7
2019-01-25 03:27:32 +08:00
你可以写一个函数做映射,或者两个常量直接定义为字符串...
XiaolinLeo
2019-01-25 07:56:30 +08:00
filter_class .get_ip_type_display 试试
arischow
2019-01-25 09:41:29 +08:00
@chi1st 是我
freakxx
2019-01-25 09:49:33 +08:00
不过你数据库存的是 1,2 呀,
不过无论想怎么搞,一般在前端层面的去做 widgets 就 ok 了。

思路这么进去

看这个库的 225 行
```
class ChoiceFilter(Filter):
field_class = ChoiceField # 这一行

def __init__(self, *args, **kwargs):
self.null_value = kwargs.get('null_value', settings.NULL_CHOICE_VALUE)
super(ChoiceFilter, self).__init__(*args, **kwargs)

def filter(self, qs, value):
if value != self.null_value:
return super(ChoiceFilter, self).filter(qs, value)

qs = self.get_method(qs)(**{'%s__%s' % (self.field_name, self.lookup_expr): None})
return qs.distinct() if self.distinct else qs
```


然后追溯到 django 的 form widgets, 第 550 行,
s

```
def render_option(self, selected_choices, option_value, option_label):
if option_value is None:
option_value = ''
option_value = force_text(option_value)
if option_value in selected_choices:
selected_html = mark_safe(' selected="selected"')
if not self.allow_multiple_selected:
# Only allow for a single selection.
selected_choices.remove(option_value)
else:
selected_html = ''
return format_html('<option value="{}"{}>{}</option>', option_value, selected_html, force_text(option_label))

```


然后再把 allow none 那个----也做个处理就 OK 了。
leisurelylicht
2019-01-25 11:21:44 +08:00
@XiaolinLeo 这个是显示用的,不能用于查询
@freakxx @yim7 多谢提供思路
freakxx
2019-01-25 11:51:26 +08:00
@leisurelylicht

你把 option_value 改为 lforce_text(option_label) 就可以了。

另外一种做法是,自定义 filter,做个 dict,key,value 再做一遍反向查询。
leisurelylicht
2019-01-25 20:13:18 +08:00
@freakxx option_value 和 lforce_text 是 django_filter 里的参数吗?我在文档里没搜到?

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/530338

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX