Search code examples
pythondjangodjango-viewsdjango-filter

Django Search in Models with 3 fields


I have search fields, keyword, cdate_start, cdate_end. They can be null. What I want is if keyword entered, get results the only keyword contained results. If keyword not entered but cdate_start entered get items only greater than this cdate_start. Or keyword and cdate_end entered then get items only contains this keyword and less than cdate_end, and etc. I have to get all combinations like this. I tried something like this.

if self.request.GET.get('feedback_s_keyword') is not None:
    keyword = self.request.GET.get('feedback_s_keyword')
else:
    keyword = ''

name_or = Q(author__name__icontains=keyword)
surname_or = Q(author__surname__icontains=keyword)
feedback_or = Q(feedback__icontains=keyword)

if self.request.GET.get('feedback_s_cdate_start') is not None:
    cdate_start = self.request.GET.get('feedback_s_cdate_start')
    cdate_start = cdate_start.split('/')
    try:
        year_s = cdate_start[0]
        month_s = cdate_start[1]
        day_s = cdate_start[2]
    except:
        year_s = 2001
        month_s = 1
        day_s = 1
else:
    year_s = 2001
    month_s = 1
    day_s = 1

cdate_gte = Q(cdate__gte=datetime.date(year_s, month_s, day_s))

if self.request.GET.get('feedback_s_cdate_end') is not None:
    cdate_end = self.request.GET.get('feedback_s_cdate_end')
    cdate_end = cdate_end.split('/')
    try:
        year_e = cdate_end[0]
        month_e = cdate_end[1]
        day_e = cdate_end[2]
    except:
        year_e = 3000
        month_e = 12
        day_e = 31
else:
    year_e = 3000
    month_e = 12
    day_e = 31

cdate_lte = Q(cdate__lte=datetime.date(year_e, month_e, day_e))

feedbacks = Feedback.objects.filter(name_or | surname_or | feedback_or | cdate_gte | cdate_lte).order_by(
    "-cdate")

But of course, if cdate is not entered it returned everything. I think I can set flags smth like keyword_entered =True, feedback_s_cdate_start_entered=True, feedback_s_cdate_end_entered=True and writing multiple if conditions and generate filters based on these flags.

if keyword_entered and not feedback_s_cdate_start_entered and not feedback_s_cdate_end_entered:
    feedbacks = Feedback.objects.filter(name_or | surname_or | feedback_or ).order_by(
        "-cdate")
elif keyword_entered and feedback_s_cdate_start_entered and not feedback_s_cdate_end_entered:
    feedbacks = Feedback.objects.filter(name_or | surname_or | feedback_or | cdate_gte).order_by(
        "-cdate")

Above solutions is not looking good. How can I create such a search for it?


Solution

  • You make your task much more difficult by the confusing logic you use (Also a form would have been much easier to use than manually cleaning all this data). Write simpler logic:

    from datetime import datetime
    
    
    filters = []
    
    keyword = self.request.GET.get('feedback_s_keyword')
    if keyword is not None:
        name_contains = Q(author__name__icontains=keyword)
        surname_contains = Q(author__surname__icontains=keyword)
        feedback_contains = Q(feedback__icontains=keyword)
        filters.append(name_contains | surname_contains | feedback_contains)
    
    cdate_start = self.request.GET.get('feedback_s_cdate_start')
    if cdate_start is not None:
        start_date = datetime.strptime(cdate_start, '%Y/%m/%d')
        filters.append(Q(cdate__gte=start_date))
    
    # Any more filters
    
    feedbacks = Feedback.objects.filter(*filters)
    # filter(*filters) would mean something like `filter(q1, q2, q3)` meaning they would be ANDed.
    # You can OR them by using functools.reduce(operator.or_, filters)
    

    Note: Instead of doing all this manually look into using django-filter a great package to make filters (much easier to use than what you do).