From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: by yocto-www.yoctoproject.org (Postfix, from userid 118) id 7B7A0E00779; Thu, 19 Mar 2015 11:30:46 -0700 (PDT) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on yocto-www.yoctoproject.org X-Spam-Level: X-Spam-Status: No, score=-2.6 required=5.0 tests=BAYES_00,RCVD_IN_DNSWL_LOW autolearn=ham version=3.3.1 X-Spam-HAM-Report: * -0.7 RCVD_IN_DNSWL_LOW RBL: Sender listed at http://www.dnswl.org/, low * trust * [74.125.82.41 listed in list.dnswl.org] * -1.9 BAYES_00 BODY: Bayes spam probability is 0 to 1% * [score: 0.0000] Received: from mail-wg0-f41.google.com (mail-wg0-f41.google.com [74.125.82.41]) by yocto-www.yoctoproject.org (Postfix) with ESMTP id 856B3E0076B for ; Thu, 19 Mar 2015 11:30:40 -0700 (PDT) Received: by wgbcc7 with SMTP id cc7so69938853wgb.0 for ; Thu, 19 Mar 2015 11:30:40 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:message-id:date:from:user-agent:mime-version:to :cc:subject:references:in-reply-to:content-type :content-transfer-encoding; bh=S4AMsOoptjtIplYwDVs2RJW3sokILVfvzDlk1YGK63M=; b=IWfTGdE4jy5KXAhBzTwUREcNaRXpBsYYsD6yU/vJKeb3ZooPSgqtrBS9zk4ltZ9TqA 0UEoqkY8wp1kA6PKO4hYFVIaERhWVJXHpi6UnSn9CrGKfyrn8WtHbezz2FIx0y0bFgZ7 DmECTaUj6ed0IQIlJQZcQanHDux1dncMjNiZZcl+C1VD+P2j0oJJ2KKa3q8ymHUEUvjp 4U1Fe8LEaHrB/xJeo9wRBlutPu9W7Y+AHURJhKdbkqh9wmMY6/q6aFj/7RTlUFxxtYxT voRjHN8Vn0FJOB3kHFzWebIxyLG4Ns3inN44KpB9ZxWLB903+FPRAoIOs0RCh4T0w4wt gCyg== X-Gm-Message-State: ALoCoQl6W753HJAYrk3yhtQ8K3MJilVqI2GvMzXyR2nhZ981iAip2OxDbi5c+0BmGhFueS3Frzqc X-Received: by 10.194.83.66 with SMTP id o2mr157688811wjy.55.1426789840000; Thu, 19 Mar 2015 11:30:40 -0700 (PDT) Received: from [192.168.2.70] ([83.217.123.106]) by mx.google.com with ESMTPSA id fo9sm8538982wib.16.2015.03.19.11.30.39 (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Thu, 19 Mar 2015 11:30:39 -0700 (PDT) Message-ID: <550B15CE.9030408@intel.com> Date: Thu, 19 Mar 2015 18:30:38 +0000 From: Michael Wood User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:31.0) Gecko/20100101 Thunderbird/31.5.0 MIME-Version: 1.0 To: "Reyna, David" , "BARROS PENA, BELEN" , "DAMIAN, ALEXANDRU" References: <5E53D14CE4667A45B9A06760DE5D13D077885651@ALA-MBA.corp.ad.wrs.com> In-Reply-To: <5E53D14CE4667A45B9A06760DE5D13D077885651@ALA-MBA.corp.ad.wrs.com> Cc: "toaster@yoctoproject.org" Subject: Re: [review-request] 6040 and 7249: daterange filtering for builds page X-BeenThere: toaster@yoctoproject.org X-Mailman-Version: 2.1.13 Precedence: list List-Id: Web based interface for BitBake List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 19 Mar 2015 18:30:46 -0000 Content-Type: text/plain; charset=windows-1252; format=flowed Content-Transfer-Encoding: 8bit Review in-line On 19/03/15 14:48, Michael Wood wrote: > From: David Reyna > > Enable date range selections for build start and build complete in > all builds page, managed and interactive. > > [YOCTO #6040] > [YOCTO #7249] > > Signed-off-by: David Reyna > --- > .../lib/toaster/toastergui/templates/build.html | 18 +++ > .../toastergui/templates/filtersnippet.html | 69 +++++++- > .../toastergui/templates/managed_builds.html | 18 +++ > .../toaster/toastergui/templatetags/projecttags.py | 4 + > bitbake/lib/toaster/toastergui/views.py | 173 ++++++++++++++++++--- > 5 files changed, 253 insertions(+), 29 deletions(-) > > diff --git a/bitbake/lib/toaster/toastergui/templates/build.html b/bitbake/lib/toaster/toastergui/templates/build.html > index 684ec65..ee0272f 100644 > --- a/bitbake/lib/toaster/toastergui/templates/build.html > +++ b/bitbake/lib/toaster/toastergui/templates/build.html > @@ -4,7 +4,25 @@ > {% load projecttags %} > {% load humanize %} > > +{% block extraheadcontent %} > + > + > + > + > +{% endblock %} > + > {% block pagecontent %} > + > + > + We can put initialisation of the datepicker in libtoaster with a special class as if we have a date field we will want this date picker across toaster. >
> > {% include "mrb_section.html" %} > diff --git a/bitbake/lib/toaster/toastergui/templates/filtersnippet.html b/bitbake/lib/toaster/toastergui/templates/filtersnippet.html > index fe70e71..703be88 100644 > --- a/bitbake/lib/toaster/toastergui/templates/filtersnippet.html > +++ b/bitbake/lib/toaster/toastergui/templates/filtersnippet.html > @@ -16,16 +16,72 @@ > All {%if filter_search_display%}{{filter_search_display|title}}{%else%}{{objectname|title}}{%endif%} ({{total_count}}) > > {% for option in f.options %} > - {% if option.2 %} > -
> > > - > diff --git a/bitbake/lib/toaster/toastergui/templates/managed_builds.html b/bitbake/lib/toaster/toastergui/templates/managed_builds.html > index e23b832..ec5bbe1 100644 > --- a/bitbake/lib/toaster/toastergui/templates/managed_builds.html > +++ b/bitbake/lib/toaster/toastergui/templates/managed_builds.html > @@ -4,7 +4,25 @@ > {% load projecttags %} > {% load humanize %} > > +{% block extraheadcontent %} > + > + > + > + > +{% endblock %} > + > {% block pagecontent %} > + > + > + >
> > {% include "managed_mrb_section.html" %} > diff --git a/bitbake/lib/toaster/toastergui/templatetags/projecttags.py b/bitbake/lib/toaster/toastergui/templatetags/projecttags.py > index e66910c..3a4a5df 100644 > --- a/bitbake/lib/toaster/toastergui/templatetags/projecttags.py > +++ b/bitbake/lib/toaster/toastergui/templatetags/projecttags.py > @@ -125,6 +125,8 @@ def filtered_icon(options, filter): > for option in options: > if filter == option[1]: > return "btn-primary" > + if ('daterange' == option[1]) and filter.startswith(option[4]): > + return "btn-primary" > return "" > > @register.filter > @@ -134,6 +136,8 @@ def filtered_tooltip(options, filter): > for option in options: > if filter == option[1]: > return "Showing only %s"%option[0] > + if ('daterange' == option[1]) and filter.startswith(option[4]): > + return "Showing only %s"%option[0] > return "" > > @register.filter > diff --git a/bitbake/lib/toaster/toastergui/views.py b/bitbake/lib/toaster/toastergui/views.py > index 8034cfc..088162d 100755 > --- a/bitbake/lib/toaster/toastergui/views.py > +++ b/bitbake/lib/toaster/toastergui/views.py > @@ -35,7 +35,7 @@ from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger > from django.http import HttpResponseBadRequest, HttpResponseNotFound > from django.utils import timezone > from django.utils.html import escape > -from datetime import timedelta > +from datetime import timedelta, datetime > from django.utils import formats > from toastergui.templatetags.projecttags import json as jsonfilter > import json > @@ -276,6 +276,56 @@ def _save_parameters_cookies(response, pagesize, orderby, request): > response.set_cookie(key='orderby', value=html_parser.unescape(orderby), path=request.path) > return response > > +# date range: normalize GUI's dd/mm/yy to date object > +def _normalize_input_date(request,field,default): Not sure what the purpose of this is, for display you can send date objects and use the built in filter: https://docs.djangoproject.com/en/1.7/ref/templates/builtins/#date If I understand the django documentation the date objects are automatically localised to your timezone because we have USE_TZ enabled. > + date_str=request.GET.get(field,'') > + date_str=re.sub('/', '-', date_str) > + # accept dd-mm-yy to d-m-yyyy > + match=re.match(r'(\d+)-(\d+)-([0-9]{4}|[0-9]{2})',date_str) You do not need to parse a date in this way, there is a function that does this, datetime.datetime.strptime e.g. datetime.datetime.strptime("2012-2-3", "%Y-%m-%d") > + if match: > + try: > + year_str=match.group(3) > + if len(year_str) == 2: > + year_str = default.strftime("%Y")[0:2] + year_str > + # set timezone so object not 'naive' > + return datetime(int(year_str),int(match.group(2)),int(match.group(1)),0,0,0,0,default.tzinfo) > + except ValueError: > + return default > + return default > + > +# extract/apply dynamic date range filter and return updated request.GET > +def get_date_range_request(request): > + if '' == request.GET.get('date_id',''): > + return None > + date_id=request.GET.get('date_id') > + # was the date range radio button selected? > + if '' == request.GET.get('date_to_' +date_id,''): > + return None > + > + # normalize GUI dates to database format > + today = timezone.localtime(timezone.now()) > + date_from = _normalize_input_date(request,'filter' ,today) > + date_to = _normalize_input_date(request,'date_to_'+date_id,today) > + last_date_from = request.GET.get('filter') > + last_date_to = request.GET.get('date_to_' +date_id,'') > + # swap dates if manually set dates are out of order > + if date_to < date_from: > + tmp=date_to; date_to=date_from; date_from=tmp > + tmp=last_date_to; last_date_to=last_date_from; last_date_from=tmp Semi colons creeping in there again (should be on new lines) You can do a swap easily without the need for a tmp variable by doing a,b = b,a https://docs.python.org/2/reference/expressions.html#evaluation-order > + # convert to strings, adjusting 'date_to' for '__lt' > + date_from_str = date_from.strftime("%Y-%m-%d") > + date_to_str = (date_to+timedelta(days=1)).strftime("%Y-%m-%d") > + > + request.GET = request.GET.copy() > + filter_string=date_id+'__gte!'+date_id+'__lt:'+date_from_str+'!'+date_to_str > + request.GET['last_date_from' ]=last_date_from > + request.GET['last_date_to' ]=last_date_to > + request.GET['filter' ]=filter_string > + request.GET['daterange_filter']='1' Please use the code style that is used in the rest of Toaster which is along the lines of pep 8, this means not putting whitespace in array index keys like "request.GET['filter' ]" and "date_to = _norm" https://www.python.org/dev/peps/pep-0008/ > + # block recursion > + del request.GET['date_id'] > + # return the new filter page request > + return request.GET > If you use the date from the browser (i.e in the JavaScript) you won't need to fudge the request, intercepting and creating a new version for the request is not something we want to start doing as we could end up with race conditions on the request object, if it's being modified outside it's normal life cycle. see http://www.w3schools.com/jsref/jsref_obj_date.asp for date api reference. > ## > # build dashboard for a single build, coming in as argument > @@ -1807,6 +1857,14 @@ if toastermain.settings.MANAGED: > # be able to display something. 'count' and 'page' are mandatory for all views > # that use paginators. > > + # extract/apply dynamic date range filter, if any > + date_range_request_GET = get_date_range_request(request) > + if None != date_range_request_GET: > + # assert the new filter page request > + (pagesize, orderby) = _get_parameters_values(request, 10, 'created:-') > + mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby } > + return _redirect_parameters( 'all-builds', date_range_request_GET, mandatory_parameters) > + > buildrequests = BuildRequest.objects.exclude(state__lte = BuildRequest.REQ_INPROGRESS).exclude(state=BuildRequest.REQ_DELETED) > > try: > @@ -1853,14 +1911,13 @@ if toastermain.settings.MANAGED: > request.GET['orderby'] = ":".join(ordering_params) > > # boilerplate code that takes a request for an object type and returns a queryset > - # for that object type. copypasta for all needed table searches > + # for that object type. copypaste for all needed table searches > (filter_string, search_term, ordering_string) = _search_tuple(request, BuildRequest) > # we don't display in-progress or deleted builds > queryset_all = buildrequests.exclude(state = BuildRequest.REQ_DELETED) > queryset_all = queryset_all.select_related("build", "build__project").annotate(Count('brerror')) > queryset_with_search = _get_queryset(BuildRequest, queryset_all, filter_string, search_term, ordering_string, '-updated') > > - > # retrieve the objects that will be displayed in the table; builds a paginator and gets a page range to display > build_info = _build_page_range(Paginator(queryset_with_search, pagesize), request.GET.get('page', 1)) > > @@ -1894,7 +1951,6 @@ if toastermain.settings.MANAGED: > comma = ", " > fstypes_map[build_request.build.id]=extensions > > - > # send the data to the template > context = { > # specific info for > @@ -1905,7 +1961,7 @@ if toastermain.settings.MANAGED: > 'default_orderby' : 'updated:-', > 'fstypes' : fstypes_map, > 'search_term' : search_term, > - 'total_count' : queryset_with_search.count(), > + 'total_count' : queryset_all.count(), > # Specifies the display of columns for the table, appearance in "Edit columns" box, toggling default show/hide, and specifying filters for columns > 'tablecols' : [ > {'name': 'Outcome', # column with a single filter > @@ -1944,6 +2000,24 @@ if toastermain.settings.MANAGED: > } > ) > > + # calculate the exact begining of local today and yesterday > + today_begin = timezone.localtime(timezone.now()); > + todays_seconds = int(today_begin.strftime("%H"))*60*60+int(today_begin.strftime("%M"))*60+int(today_begin.strftime("%S")); > + today_begin = today_begin-timedelta(seconds=todays_seconds); > + yesterday_begin = today_begin-timedelta(days=1); The beginning of the day is easier to work out by just creating a new date object only initialised with the d/m/y e.g. today = timezone.now() today_start = datetime.date(today.year, today.month, today.day) To use the timezone.localtime api we need to activate() a timezone, this has to come from the client otherwise it will assume (UTC / GMT) once activated django should "do the right thing" in terms of timezones everywhere, this needs a bit of experimenting as it's not very intuitive... Note that you're muscle memory is adding semi colons on the end up there! > + # add daterange persistent > + context['last_date_from'] = request.GET.get('last_date_from',timezone.localtime(timezone.now()).strftime("%d/%m/%y")) > + context['last_date_to' ] = request.GET.get('last_date_to' ,context['last_date_from']) > + # calculate the date ranges, avoid second sort for 'created' > + queryset_created=queryset_all.order_by('created') > + dateMinCreated=timezone.localtime(queryset_created[0].created).strftime("%d/%m/%y") > + dateMaxCreated=timezone.localtime(queryset_created[queryset_created.count()-1].created).strftime("%d/%m/%y") > + dateMinUpdated=dateMinCreated > + dateMaxUpdated=timezone.localtime(queryset_created[queryset_created.count()-1].created+timedelta(days=1)).strftime("%d/%m/%y") Use QuerySet first() and last() methods to get the valid selectable date range as it's more descriptive, I'd also convert the date format in the template using the |date filter , objects should be kept in their data type right up until the moment you want to display it so that they can be manipulated later on using the object's api (datetime in this case) > + # if dataranging, check the datagange radio button > + if '' != request.GET.get('daterange_filter',''): > + context['daterange_filter']='1' > + > context['tablecols'].append( > {'name': 'Started on', 'clclass': 'started_on', 'hidden' : 1, # this is an unchecked box, which hides the column > 'qhelp': "The date and time you started the build", > @@ -1952,9 +2026,16 @@ if toastermain.settings.MANAGED: > 'filter' : {'class' : 'created', > 'label': 'Show:', > 'options' : [ > - ("Today's builds" , 'created__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_all.filter(created__gte=timezone.now()).count()), > - ("Yesterday's builds", 'created__gte:'+(timezone.now()-timedelta(hours=24)).strftime("%Y-%m-%d"), queryset_all.filter(created__gte=(timezone.now()-timedelta(hours=24))).count()), > - ("This week's builds", 'created__gte:'+(timezone.now()-timedelta(days=7)).strftime("%Y-%m-%d"), queryset_all.filter(created__gte=(timezone.now()-timedelta(days=7))).count()), > + ("Today's builds" , 'created__gte:'+today_begin.strftime("%Y-%m-%d"), queryset_all.filter(created__gte=today_begin).count()), > + ("Yesterday's builds", > + 'created__gte!created__lt:' > + +yesterday_begin.strftime("%Y-%m-%d")+'!' > + +today_begin.strftime("%Y-%m-%d"), > + queryset_all.filter( > + created__gte=yesterday_begin, > + created__lt=today_begin > + ).count()), > + ("Builds from "+context['last_date_from']+" to "+context['last_date_to'], 'daterange', 1, '', 'created', context['last_date_from'], context['last_date_to'], dateMinCreated, dateMaxCreated, ), I understand that the tuple/list is how it's done else where but unfortunately this is not correct it's not a good idea to rely on the fact that the date option is the 4th option, anyone changing that will not realise it and it will all break. > ] > } > } > @@ -1968,9 +2049,16 @@ if toastermain.settings.MANAGED: > 'filter' : {'class' : 'updated', > 'label': 'Show:', > 'options' : [ > - ("Today's builds", 'updated__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_all.filter(updated__gte=timezone.now()).count()), > - ("Yesterday's builds", 'updated__gte:'+(timezone.now()-timedelta(hours=24)).strftime("%Y-%m-%d"), queryset_all.filter(updated__gte=(timezone.now()-timedelta(hours=24))).count()), > - ("This week's builds", 'updated__gte:'+(timezone.now()-timedelta(days=7)).strftime("%Y-%m-%d"), queryset_all.filter(updated__gte=(timezone.now()-timedelta(days=7))).count()), > + ("Today's builds" , 'updated__gte:'+today_begin.strftime("%Y-%m-%d"), queryset_all.filter(updated__gte=today_begin).count()), > + ("Yesterday's builds", > + 'updated__gte!updated__lt:' > + +yesterday_begin.strftime("%Y-%m-%d")+'!' > + +today_begin.strftime("%Y-%m-%d"), > + queryset_all.filter( > + updated__gte=yesterday_begin, > + updated__lt=today_begin > + ).count()), > + ("Builds from "+context['last_date_from']+" to "+context['last_date_to'], 'daterange', 1, '', 'updated', context['last_date_from'], context['last_date_to'], dateMinUpdated, dateMaxUpdated, ), > ] > } > } > @@ -2625,7 +2713,7 @@ if toastermain.settings.MANAGED: > return _redirect_parameters( 'layers', request.GET, mandatory_parameters) > > # boilerplate code that takes a request for an object type and returns a queryset > - # for that object type. copypasta for all needed table searches > + # for that object type. copypaste for all needed table searches > (filter_string, search_term, ordering_string) = _search_tuple(request, Layer_Version) > > prj = Project.objects.get(pk = request.session['project_id']) > @@ -2838,7 +2926,7 @@ if toastermain.settings.MANAGED: > return _redirect_parameters( 'machines', request.GET, mandatory_parameters) > > # boilerplate code that takes a request for an object type and returns a queryset > - # for that object type. copypasta for all needed table searches > + # for that object type. copypaste for all needed table searches > (filter_string, search_term, ordering_string) = _search_tuple(request, Machine) > > queryset_all = Machine.objects.all() > @@ -3115,7 +3203,7 @@ if toastermain.settings.MANAGED: > queryset_all = Project.objects.all() > > # boilerplate code that takes a request for an object type and returns a queryset > - # for that object type. copypasta for all needed table searches > + # for that object type. copypaste for all needed table searches > (filter_string, search_term, ordering_string) = _search_tuple(request, Project) > queryset_with_search = _get_queryset(Project, queryset_all, None, search_term, ordering_string, '-updated') > queryset = _get_queryset(Project, queryset_all, filter_string, search_term, ordering_string, '-updated') > @@ -3233,8 +3321,14 @@ else: > if retval: > return _redirect_parameters( 'all-builds', request.GET, mandatory_parameters) > > + # extract/apply dynamic date range filter, if any > + date_range_request_GET = get_date_range_request(request) > + if None != date_range_request_GET: > + # assert the new filter page request > + return _redirect_parameters( 'all-builds', date_range_request_GET, mandatory_parameters) > + > # boilerplate code that takes a request for an object type and returns a queryset > - # for that object type. copypasta for all needed table searches > + # for that object type. copypaste for all needed table searches > (filter_string, search_term, ordering_string) = _search_tuple(request, Build) > queryset_all = Build.objects.exclude(outcome = Build.IN_PROGRESS) > queryset_with_search = _get_queryset(Build, queryset_all, None, search_term, ordering_string, '-completed_on') > @@ -3265,6 +3359,20 @@ else: > comma = ", " > fstypes_map[build.id]=extensions > > + # calculate the exact begining of local today and yesterday > + today_begin = timezone.localtime(timezone.now()); > + todays_seconds = int(today_begin.strftime("%H"))*60*60+int(today_begin.strftime("%M"))*60+int(today_begin.strftime("%S")); > + today_begin = today_begin-timedelta(seconds=todays_seconds); > + yesterday_begin = today_begin-timedelta(days=1); > + last_date_from = request.GET.get('last_date_from',timezone.localtime(timezone.now()).strftime("%d/%m/%y")) > + last_date_to = request.GET.get('last_date_to' ,last_date_from) > + # calculate the date ranges, avoid second sort for 'completed_on' > + queryset_started_on=queryset_all.order_by('started_on') > + dateMinStarted=timezone.localtime(queryset_started_on[0].started_on).strftime("%d/%m/%y") > + dateMaxStarted=timezone.localtime(queryset_started_on[queryset_started_on.count()-1].started_on).strftime("%d/%m/%y") > + dateMinCompleted=dateMinStarted > + dateMaxCompleted=timezone.localtime(queryset_started_on[queryset_started_on.count()-1].started_on+timedelta(days=1)).strftime("%d/%m/%y") > + > # send the data to the template > context = { > # specific info for > @@ -3275,7 +3383,7 @@ else: > 'default_orderby' : 'completed_on:-', > 'fstypes' : fstypes_map, > 'search_term' : search_term, > - 'total_count' : queryset_with_search.count(), > + 'total_count' : queryset_all.count(), > # Specifies the display of columns for the table, appearance in "Edit columns" box, toggling default show/hide, and specifying filters for columns > 'tablecols' : [ > {'name': 'Outcome', # column with a single filter > @@ -3312,9 +3420,16 @@ else: > 'filter' : {'class' : 'started_on', > 'label': 'Show:', > 'options' : [ > - ("Today's builds" , 'started_on__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_with_search.filter(started_on__gte=timezone.now()).count()), > - ("Yesterday's builds", 'started_on__gte:'+(timezone.now()-timedelta(hours=24)).strftime("%Y-%m-%d"), queryset_with_search.filter(started_on__gte=(timezone.now()-timedelta(hours=24))).count()), > - ("This week's builds", 'started_on__gte:'+(timezone.now()-timedelta(days=7)).strftime("%Y-%m-%d"), queryset_with_search.filter(started_on__gte=(timezone.now()-timedelta(days=7))).count()), > + ("Today's builds" , 'started_on__gte:'+today_begin.strftime("%Y-%m-%d"), queryset_all.filter(started_on__gte=today_begin).count()), > + ("Yesterday's builds", > + 'started_on__gte!started_on__lt:' > + +yesterday_begin.strftime("%Y-%m-%d")+'!' > + +today_begin.strftime("%Y-%m-%d"), > + queryset_all.filter( > + started_on__gte=yesterday_begin, > + started_on__lt=today_begin > + ).count()), > + ("Builds from "+last_date_from+" to "+last_date_to, 'daterange', 1, '', 'started_on', last_date_from, last_date_to, dateMinStarted, dateMaxStarted, ), > ] > } > }, > @@ -3326,9 +3441,16 @@ else: > 'filter' : {'class' : 'completed_on', > 'label': 'Show:', > 'options' : [ > - ("Today's builds", 'completed_on__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_with_search.filter(completed_on__gte=timezone.now()).count()), > - ("Yesterday's builds", 'completed_on__gte:'+(timezone.now()-timedelta(hours=24)).strftime("%Y-%m-%d"), queryset_with_search.filter(completed_on__gte=(timezone.now()-timedelta(hours=24))).count()), > - ("This week's builds", 'completed_on__gte:'+(timezone.now()-timedelta(days=7)).strftime("%Y-%m-%d"), queryset_with_search.filter(completed_on__gte=(timezone.now()-timedelta(days=7))).count()), > + ("Today's builds" , 'completed_on__gte:'+today_begin.strftime("%Y-%m-%d"), queryset_all.filter(completed_on__gte=today_begin).count()), > + ("Yesterday's builds", > + 'completed_on__gte!completed_on__lt:' > + +yesterday_begin.strftime("%Y-%m-%d")+'!' > + +today_begin.strftime("%Y-%m-%d"), > + queryset_all.filter( > + completed_on__gte=yesterday_begin, > + completed_on__lt=today_begin > + ).count()), > + ("Builds from "+last_date_from+" to "+last_date_to, 'daterange', 1, '', 'completed_on', last_date_from, last_date_to, dateMinCompleted, dateMaxCompleted, ), > ] > } > }, > @@ -3389,6 +3511,13 @@ else: > ] > } > > + # add daterange persistent > + context['last_date_from'] = last_date_from > + context['last_date_to' ] = last_date_to > + # if dataranging, check the datagange radio button > + if '' != request.GET.get('daterange_filter',''): > + context['daterange_filter']='1' > + > response = render(request, template, context) > _save_parameters_cookies(response, pagesize, orderby, request) > return response All above is duplicated code, we really need to put this in it's own function or even better a new utility object. Michael On 17/03/15 23:25, Reyna, David wrote: > Hi Belén, > I have pushed the patch for the date ranging filtering in two commits > to make it easier to read, the first with just the static files from > jQuery-ui and the second with the actual view-model page changes. They > are not order dependent, but both are of course required. > dreyna/build_datepicker_static_6040 > dreyna/build_datepicker_6040 > Implementation notes: > 1) PENDING ISSUE: I observed that 'total_count' always uses > “queryset_with_search.count()”, but that is wrong since that query > also include any filtering. The observed error is that the “all” count > in the filter popup will show the current limited filtered/searched > count and not the full count, and that just looks wrong. > I fixed it in the two all builds tables, but I have not touched the > other view classes yet. Here is my fix: > 'total_count' : queryset_all.count(), > 2) I observed that ‘today’ and ‘yesterday’ were broken for all > timezones outside of London, because it turns out that > “timezone.now()” always returns UTC+0. I added > “timezone.localtime(timezone.now())” to fix that, plus I added > explicit code for the beginning of ‘today’ and ‘yesterday’ to insure > everything is in sync. The database date values are of course always > in the local timezone, which saves filtering in the views. > This is not something you can test yourself, unless you have set up a > test host using a different time zone. > 3) I went ahead and fixed the typo “copypasta” with “copypaste”, since > as yummy as it sounded it looked odd :-) > 4) I did a lot of testing and hair pulling (not that I had much to > begin with), and I have hopefully captured all of the edge cases. > * I did test for both managed mode and interactive mode. > * All illegal dates (entered by hand) are caught and internally > mapped to the today’s date. The datepicker is of course very clean > with valid dates. > * Swapped dates (entered by hand) are indeed swapped back. If you > use the datepicker, the start date is automatically set as the end > date’s minimum date, so no date swapping possible there. > * I did allow for single digit days and months (again entered by > hand), plus both 2 and 4 digit years, because it was easy to code and > it allows for fewer surprises for the users. > * I did use the jQuery UI download builder and selected only for > datepicker, as per my previous email. That turned out to be easy and fun! > * I do have page persistence for the date ranges. The > created/updates and starter/completed pairs use the same persistence > values for code economy. > - David > >