【问题标题】:How can I filter a DateField or a DateTimeField in Django using a string?如何使用字符串过滤 Django 中的 DateField 或 DateTimeField?
【发布时间】:2016-03-24 01:15:46
【问题描述】:

我在前端使用DataTables,它允许全局搜索(遍历所有字段以查找匹配项)。

这就是我希望在 django(版本 1.9.1)中能够做到的:

  • 使用“ma”作为 DateField 的过滤器,以便所有 3 月和 5 月的日期 被退回

  • 使用“Mar 1”作为 DateField 的过滤器,以便 3 月 10 日 - 3 月 19 日返回

经过一番修改,我有了这个自定义查找

from django.db.models import Lookup
from django.db.models.fields import DateField

@DateField.register_lookup
class DateTextFilter(Lookup):
    lookup_name = 'dttxt'
    def as_postgresql(self, compiler, connection):
        lhs, lhs_params = self.process_lhs(compiler, connection)
        rhs = self.rhs.strftime("%b %d, %Y")
        return "to_char(%s,'Mon DD, YYYY') ~* '%s'", ([lhs,rhs])

我是这样运行的:

outputQ = Q(**{"appt_date"+"__dttxt" : "Mar 09"})
Appts.objects.filter(outputQ)

它生成运行的 SQL(在 Postgres 中),但是当我运行 filter() 时,我得到错误

ValidationError: [u"'Mar 09' value has an invalid date format. 
It must be in YYYY-MM-DD format."]

公平地说,“3 月 9 日”不是一个有效的日期——但原始 SQL 是有效的并且可以运行(我用 '2016-03-09' 对此进行了测试以绕过这个错误并让 django 来创建 SQL)

SELECT "appts"."appt_id", "appts"."client_id", "appts"."therapist_id",
 "appts"."agency_id", "appts"."appt_date", "appts"."appt_start", 
"appts"."appt_end", "appts"."cpt_code", "appts"."client_owe", 
"appts"."insur1_owe", "appts"."insur2_owe", "appts"."note", 
"appts"."note_flag", "appts"."ct_fee_pd", "appts"."subm_date", 
"appts"."updated" FROM "appts" WHERE to_char("appts"."appt_date",'Mon DD, 
YYYY') ~* 'Mar 09'

我可以使用extra(),但这将被弃用。

我可能最终会使用 SO algorithm 解析字符串(使用原始 sql 对获得此功能的妥协太大了),但我会失去一些我想要的功能。

不确定这是否可行...

【问题讨论】:

    标签: django django-models


    【解决方案1】:

    解决方案 #1

    创建一个构建 Q 对象的方法来测试部分日期:

    def _filter_by_date_field(self, fld_value, searchableColumn, outputQ):
        mon_val = -1
        Mons = ['jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec']
        if len(fld_value) >= 3 and fld_value[0:3].lower() in Mons:
            mon_val = next(i for i,v in enumerate(Mons) \
                        if v == fld_value[0:3].lower())+1
            outputQ |= Q(**{searchableColumn+"__month" : mon_val})
        if len(fld_value) >= 6 and mon_val > -1 and \
                  re.search(r'^\d{2}$', fld_value[4:6]):
            outputQ &= Q(**{searchableColumn+"__day" : fld_value[4:6]})
        if len(fld_value) >= 8 and mon_val > -1 and \
                  re.search(r'^\d{4}$', fld_value[8:12]):
            outputQ &= Q(**{searchableColumn+"__year" : fld_value[8:12]})
        return outputQ
    

    如果字段是 DateField,则调用方法如下:

     if isinstance(<ModelName>.model._meta.get_field(searchableColumn), DateField):
            outputQ |= self._filter_by_date_field(customSearch,\
                                                  searchableColumn,outputQ)
    

    这是一个完整日期的 SQL Django 构建示例:

    EXTRACT('month' FROM "get_appts_vw"."appt_date") = 3) AND
       EXTRACT('day' FROM "get_appts_vw"."appt_date") = 5 AND 
       "get_appts_vw"."appt_date" BETWEEN '2016-01-01'::date AND '2016-12-31'::date)
    

    解决方案 #2

    使用 CharField 为模型创建数据库视图(原始模型使用 DateField)

    class ApptView(models.Model):
        appt_date = models.CharField(max_length=12)
                ….
    class Meta:
        db_table = u'get_appts_vw' # view name
    

    视图

       CREATE VIEW get_appts_vw AS
        select to_char(a.appt_date,'Mon DD, YYYY') as appt_date
        ….
        from appts
    

    这可行,但仍然requires an extra() 子句进行排序

    解决方案#3

    (我正在使用这个)

    from django.db.models import Lookup
    from django.db.models.fields import DateField
    from django.db.models import CharField, Transform
    
    @DateField.register_lookup
    class TextDate(Transform):
        lookup_name = 'txtdt'
    
        @property
        def output_field(self):
            return CharField()
    
    
    @TextDate.register_lookup
    class DateTextFilter(Lookup):
        lookup_name = 'dttxt'
    
        def as_postgresql(self, compiler, connection):
            lhs, lhs_params = compiler.compile(self.lhs.lhs)
            rhs, rhs_params = self.process_rhs(compiler, connection)
            params = lhs_params + rhs_params
            return 'to_char(%s,\'Mon DD, YYYY\') ~* %s ' % (lhs, rhs), params
    

    这样称呼

    from .CustomFilters import * # or whatever you name your custom filters module
    ....
        if isinstance(<ModelName>.model._meta.get_field(searchableColumn), DateField):
                outputQ |= Q(**{<column_name>+"__txtdt__dttxt" : <search_value>})
    

    【讨论】:

    • 你的最后一个看起来很奇怪。我希望TextDate 转换中的to_char(...) 调用,然后查询是column__txtdt__like=value
    • @ShaiBerger,对您的解决方案感兴趣。我见过使用简单函数(ABS、lower 等)的变换,但我还没有看到变换像我需要的那样的情况,即 to_char(%s,\'Mon DD, YYYY\')。如果你有一个例子,我很想看看
    • 你好像写对了……试试吧。如果您的解决方案不起作用,请随时询问。具有一个参数的函数与具有两个参数且其中一个是常数的函数之间几乎没有区别(就此而言)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-10-02
    • 2021-01-09
    • 2010-11-22
    • 2018-03-26
    • 2013-05-08
    • 1970-01-01
    • 2014-06-17
    相关资源
    最近更新 更多