source: MILK/trunk/milk_server/milk_server/controllers/browse/discovery.py @ 5489

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/MILK/trunk/milk_server/milk_server/controllers/browse/discovery.py@5489
Revision 5489, 27.9 KB checked in by sdonegan, 11 years ago (diff)

Updates to deal with problems of changing bbox params for global search

Line 
1'''
2Controller for the discovery search functionality
3'''
4import socket, logging
5from paste.request import parse_querystring
6from ndg.common.src.clients.ws.discovery.discoveryserviceclient import DiscoveryServiceClient
7from ndg.common.src.clients.xmldb.eXist.searchclient import SearchClient
8from ndg.common.src.clients.http.vocabserverclient import VocabServerClient as VS
9from ndg.common.src.models.vocabtermdata import VocabTermData as VTD
10from ndg.common.src.models.ndgObject import ndgObject
11from ndg.common.src.models.DIF import DIF
12from ndg.common.src.lib.mailer import mailHandler
13from milk_server.lib.base import *
14from milk_server.lib.Date import *
15from milk_server.models.DiscoveryState import DiscoveryState, constraints
16from milk_server.controllers.home import HomeController
17import browserconstants as bc
18from milk_server.lib.Utilities import getURLConstraints
19import milk_server.lib.constants as constants
20
21
22class DiscoveryController(HomeController):
23    '''
24    Provides the pylons controller for NDG discovery
25    '''
26
27    def __setup(self):
28        '''
29        Common setup for controller methods
30        '''
31        self.cf=request.environ['ndgConfig']
32        self.inputs=dict(parse_querystring(request.environ))
33       
34        self.message=''
35        c.inputErrors = {}    # dict to store error messages
36       
37        c.discoveryUrl = h.url_for('discovery')
38                       
39   
40    def index(self):
41        '''
42        Main entry point for doing discovery searches
43        '''
44        self.__setup()
45       
46        # if inputs are not set: if the discovery mode is enabled, display
47        # the search screen, otherwise redirect to the default home page according
48        # to what milk mode is set (i.e. editor/browse)
49        if not self.inputs or 'ClearForm' in self.inputs:
50            if g.discoveryEnabled:
51                return self.__advancedPrompt()
52            else:
53                logging.info("Discovery mode not enabled - redirect to default")
54                return h.redirect_to(h.url_for('default'))
55       
56        self.__getInputs()
57
58        if 'orderBy' not in self.inputs:
59            self.inputs['orderBy'] = g.orderByList[0]
60        c.orderByList = h.options_for_select(g.orderByList, self.inputs['orderBy'])
61
62        if 'orderDirection' not in self.inputs:
63            self.inputs['orderDirection'] = constants.ORDER_BY_DIRECTION[0]
64        c.orderDirection = h.options_for_select(constants.ORDER_BY_DIRECTION,
65                                                self.inputs['orderDirection']) 
66       
67        # if any errors are found, return user to search page
68        if c.inputErrors:
69            return self.__advancedPrompt()
70
71        searchString = self.inputs['searchString']
72        if 'vocabTerm' in self.inputs and self.inputs['vocabTerm']:
73            searchString += " %s" %self.inputs['vocabTerm']
74           
75        # users can return to search page to refine the search inputs; in this case
76        # they will have a 'constrained' input
77        self.constraints = self.__buildconstraints(self.dateRange, self.bbox, self.scope,
78                                                   searchString, self.inputs['geoSearchType'])
79        if 'constrained' in self.inputs: 
80            return self.__advancedPrompt(searchConstraints = self.constraints)
81
82        # ok, now go do the search
83        try:
84            return self.__runSearch(searchString, self.inputs['textTarget'],
85                                    self.inputs['start'], self.inputs['howmany'], 
86                                    orderBy = self.inputs['orderBy'], 
87                                    orderDirection = self.inputs['orderDirection'],
88                                    scope = self.scope, dateRange = self.dateRange, 
89                                    bbox = self.bbox, geoSearch=self.inputs['geoSearchType'])
90        except Exception, e:
91            if g.debugModeOn == 'True':
92                raise e
93            else:
94                c.xml='Unexpected error: %s'%(str(e))
95                return render('error')
96
97
98    def __getInputs(self):
99        '''
100        Retrieve the user inputs and set defaults.  Values are stored in the
101        self.inputs dict
102        '''
103        logging.debug("Getting user inputs")
104       
105        # restore contraints from input, if set
106        if 'constraints' in self.inputs:
107            constraints = getURLConstraints(self.inputs['constraints'])
108            del self.inputs['constraints']
109            # NB, be careful not to overwrite newly passed in info
110            for key, val in constraints.items():
111                if key not in self.inputs:
112                    self.inputs[key] = val
113                   
114        if 'vocabTerm' in self.inputs and 'searchString' not in self.inputs:
115            self.inputs['searchString'] = ""
116           
117        # see if this is a discovery search or a more complicated search
118        if 'searchTarget' not in self.inputs: 
119            self.inputs['searchTarget']='Discovery'
120       
121        # set default for table paging, if not already set
122        # NB, url arguments need converting back to ints
123        if 'start' not in self.inputs:
124            self.inputs['start'] = 1
125        else:
126            self.inputs['start'] = int(self.inputs['start'])
127           
128        if 'howmany' not in self.inputs:
129            self.inputs['howmany'] = 10
130        else:
131            self.inputs['howmany'] = int(self.inputs['howmany'])
132           
133        # the simplest query we might get is a text search, in which case
134        # the inputs should be start, howmany and searchString (although
135        # maybe not in that order. The next simplest is one with
136        # a specified textTarget, after that we need all the inputs.
137        if 'searchString' in self.inputs and 'textTarget' not in self.inputs:
138            # it's a simple text search
139            self.inputs['textTarget']='All'
140
141        # the next simplest is one that includes texttarget as well ...
142        expected=['searchString','textTarget']
143        missingInputs = self.__checkform(expected)
144        if missingInputs:
145            if bc.INCOMPLETE_SEARCH_INPUT_MESSAGE not in c.inputErrors:
146                c.inputErrors[bc.INCOMPLETE_SEARCH_INPUT_MESSAGE] = []
147            c.inputErrors[bc.INCOMPLETE_SEARCH_INPUT_MESSAGE].extend(missingInputs)
148
149        self.__getSpatioTemporalInputs()
150       
151        logging.debug("User inputs retrieved")
152
153
154    def __getSpatioTemporalInputs(self):
155        '''
156        Get spatiotemporal input data - and set up any defaults, if required
157        '''
158        logging.debug("Getting spatiotemporal inputs")
159        if 'geoSearchType' not in self.inputs:
160            self.inputs['geoSearchType']='overlaps'
161
162        # now we add the defaults... this is kind of historical - NOT SURE THIS IS STILL NEEDED
163        if len(self.inputs)==6:
164            self.bbox=None
165            self.dateRange=None
166            self.scope=None
167            return
168       
169        if 'source' in self.inputs and self.inputs['source'] != 'All':
170            # NB, the WSDL expects a list
171            self.scope = [self.inputs['source']]
172        else:
173            self.scope = None
174           
175        missingInputs = self.__checkform(['bboxN','bboxE','bboxS','bboxW','geoSearchType'])
176        if missingInputs: 
177            self.bbox = None
178        else:
179            # default form has a global bounding box, NB, internal to this routine we use bbox=[N,W,E,S], not [W,S,E,N]!
180            self.bbox = [self.inputs['bboxN'], self.inputs['bboxW'],
181                         self.inputs['bboxE'], self.inputs['bboxS']]
182           
183            errors = self.__checkBBoxValidity(self.bbox)
184            if errors:
185                if bc.INVALID_BBOX_MESSAGE not in c.inputErrors:
186                    c.inputErrors[bc.INVALID_BBOX_MESSAGE] = []
187                c.inputErrors[bc.INVALID_BBOX_MESSAGE].extend(errors)
188               
189        #self.bbox = None
190       
191       
192        missingInputs = self.__checkform(['startDate', 'endDate'])
193        self.dateRange = None
194        if missingInputs:
195            self.dateRange = None
196        elif self.inputs['startDate'] and not self.inputs['endDate']:
197            c.inputErrors[bc.INCOMPLETE_DATERANGE_MESSAGE] = ['End date missing']
198        elif not self.inputs['startDate'] and self.inputs['endDate']:
199            c.inputErrors[bc.INCOMPLETE_DATERANGE_MESSAGE] = ['Start date missing']
200        elif self.inputs['startDate'] and self.inputs['endDate']:
201            dateError = None
202            try:
203                year, month, day = self.inputs['startDate'].split('/')
204                self.dateRange = [(day, month, year)]
205                year, month, day = self.inputs['endDate'].split('/')
206                self.dateRange.append((day, month, year))
207
208                if self.dateRange <> [("","",""),("","","")]:
209                    dateError = self.__checkdates(self.dateRange)
210                   
211                else: 
212                    self.dateRange = None
213                               
214            except:
215                dateError = 'Invalid date provided'
216
217            if dateError:
218                if bc.INVALID_DATERANGE_MESSAGE not in c.inputErrors:
219                    c.inputErrors[bc.INVALID_DATERANGE_MESSAGE] = []
220                c.inputErrors[bc.INVALID_DATERANGE_MESSAGE].append(dateError)
221       
222        logging.debug("Spatiotemporal inputs retrieved")
223
224
225    def __getSearchClient(self, clientType):
226        '''
227        Retrieve the appropriate client to complete the search
228        - currently supported are browse and discovery clients
229        @param clientType: type of search client to use.  Currently accepts,
230        'Discovery', 'Browse' and 'NumSim'
231        @raise ValueError if unrecognised search client entered
232        @return search client adhering to the ndg.common.clients.interfacesearchclient
233        interface
234        '''
235        logging.debug("Getting %s type search client" %clientType)
236        searchClient = None
237        if clientType =='Discovery':
238            logging.info(" - use Discovery service to complete search")
239            if g.discoveryServiceURL:
240                searchClient = DiscoveryServiceClient(HostAndPort = g.discoveryServiceURL)
241            else:
242                searchClient = DiscoveryServiceClient()
243               
244        elif clientType in ['Browse','NumSim']:
245            logging.info(" - use Browse service to complete search")
246            searchClient = SearchClient(dbHostName = g.localEXist,
247                                        configFileName = g.pwFile)
248        else:
249            raise ValueError("Unrecognised search type, '%s'" %clientType)
250       
251        logging.debug("- returning search client")
252        return searchClient
253       
254
255    def __runSearch(self, searchString, textTarget, start,
256                    howmany, orderBy = None, orderDirection = None, scope = None, 
257                    dateRange = None, bbox = None, geoSearch = 'overlaps'):
258        '''
259        Carry out a text search for <searchString>
260        in the <textTarget> where the accepted text target values are controlled
261        by the DiscoveryTemplate GUI, and are: All, Authors, Parameters
262        @param searchString: string to search for
263        @param textTarget: target of the search - either, 'All', 'Authors' or 'Parameters'
264        @param start: starting record to return
265        @param howmany: number of records to return
266        @keyword orderBy: Field to order results by - NB, must be one of the
267        getList('orderByFieldList') values
268        @keyword orderDirection: Direction the 'orderBy' results should be returned in
269        - NB, this is currently 'ascending' or 'descending'
270        @keyword scope: scope of search - either NERC, NERC_DDC, MDIP or DPPP. Default = None
271        @keyword dateRange: date range in format [startDate, endDate] where the
272        date objects are tuples with format (day, month, year). Default = None
273        @keyword bbox: Bounding box in format [N,W,E,S]. Default = None
274        @keyword geoSearch: type of spatial search. Defaults to 'overlaps'.
275        '''
276        logging.debug("Running text search with string, '%s'" %searchString)
277       
278        searchClient = self.__getSearchClient(self.inputs['searchTarget'])
279       
280        if self.inputs['searchTarget'] in ['Browse','NumSim']:
281            textTarget = self.inputs['searchTarget']
282            if textTarget == 'Browse':
283                # NB, this switches the searching to be done on atom format
284                # rather than moles1.3 format docs
285                textTarget = SearchClient.ATOM_TARGET#'ndg_B_metadata'
286           
287        # PJK 04/09/08 Handle errors more gracefully
288        #
289        # http://proj.badc.rl.ac.uk/ndg/ticket/984
290        try:
291            searchClient.search(searchString,
292                                start = start,
293                                howmany = howmany,
294                                target = textTarget,
295                                scope = scope,
296                                dateRange = dateRange,
297                                bbox = bbox,
298                                geoSearchType = geoSearch,
299                                orderBy = orderBy,
300                                orderDirection = orderDirection)
301        except socket.error, e:
302            logging.error("Socket error for discovery service search: %s" % e)
303            c.xml='The Discovery Service is unavailable.  Please check with '+\
304                    'your system administrator'
305            return render('error')
306        except Exception, e:
307            logging.error("Calling discovery service search: %s" % e)
308            c.xml='An internal error occured.  Please check with ' + \
309                    'your system administrator'
310            return render('error')
311           
312        logging.debug("Search returned - now processing results")
313        # DiscoveryState object is a wrapper to the various search config
314        # variables
315        c.state = DiscoveryState(searchClient.serverSessionID, searchString,
316                                 request.environ, searchClient.hits, self.constraints,
317                                 start, howmany)
318
319        return self.__processSearchResults(searchClient, c.state)
320
321
322    def __processSearchResults(self, searchClient, ds):
323        '''
324        Process the results from a search - as ran by the input search client object
325        @param searchClient: search client adhering to the ndg.common.clients.interfacesearchclient
326        interface - which has just ran a search
327        @param ds: DiscoveryState object with info on the search
328        '''
329        if searchClient.error:
330            logging.error("Error encountered whilst running search: %s" %searchClient.error)
331            m=''
332            for i in searchClient.error:
333                m+='<p>%s</p>'%i
334            c.xml = m
335            return render('error')
336       
337        hits = searchClient.hits
338        # NB, this is used in the semantic search function of results.kid and short_results.kid
339        c.querystring = request.environ['QUERY_STRING']
340        c.altSearchURL = '%s?%s'%(h.url_for(action='semantic'),
341                                  request.environ['QUERY_STRING'])
342
343        difs = []
344        errors = []
345
346        if hits == 0 and ds.constraintsInstance['textTarget'] != SearchClient.ATOM_TARGET:
347            outMessage = 'No records found for "%s"[constraints: %s]' \
348                %(ds.searchString, ds.constraints)
349            logging.info(outMessage) 
350            c.xml='<p>' + outMessage + '</p>'
351
352        else:
353            try:
354                # display browse search results differently
355                if self.inputs['searchTarget'] != 'Discovery':
356                    return self.__displayBrowseSearchResults(searchClient)
357   
358                # now actually retrieve the search records
359                results = searchClient.getLabelledDocs(format='DIF')
360   
361                if not results:
362                    c.xml='<p>No results for "%s"!</p>'%ds.searchString
363                else:
364                    for result in results: 
365                        obj=ndgObject(result[0], config = self.cf)
366                        try:
367                            difs.append(DIF(result[1],ndgObj=obj))
368                        except ValueError,e:
369                            errors.append((result[0], str(e)))
370       
371                    if not difs:
372                        c.xml='<p>No usable results for "%s"!</p>'%ds.searchString
373                   
374                    elif errors:
375                        c.xml='<p>Search results for "%s"'%ds.searchString
376                        dp=[]
377                        for e in errors:
378                            n=ndgObject(e[0])
379                            if n.repository not in dp: 
380                                dp.append(n.repository)
381                        if len(dp)<>1: 
382                            dp='[Various Data Providers]'
383                        else:
384                            dp='[%s]'%dp[0] 
385                           
386                        c.xml+=' (unfortunately %s hits matched unformattable documents from %s, an internal error has been logged):</p>'%(len(errors),dp)
387                        status, message=mailHandler([g.metadataMaintainer],'DIF errors',
388                                                    str(errors), server = g.mailServer)
389                        if not status:
390                            c.xml+='<p> Actually, not even an internal error has been logged. <br/>'
391                            c.xml+='Internal sending of mail failed with error [%s]</p>'%message
392                   
393                # if we're here, we're ready to display the dif records
394                c.difs = difs
395                session['results'] = h.current_url()
396                session.save()
397               
398                # set up the displayed tabs
399                if len(c.pageTabs)==1: 
400                    c.pageTabs.append(('Results', session['results']))
401                    c.pageTabs.append(('Selections',
402                                       h.url_for(controller='visualise/selectedItems',
403                                                 action='index')))
404                elif c.pageTabs[1][0]!='Results':
405                        c.pageTabs.insert(1,('Results',session['results']))
406                        selectionsNeeded=1
407                        for tab in c.pageTabs[0]:
408                            if tab == 'Selections':
409                                selectionsNeeded=0
410                        if selectionsNeeded:
411                            c.pageTabs.append(('Selections',
412                                       h.url_for(controller='visualise/selectedItems',
413                                                 action='index')))
414                           
415                   
416            except ValueError,e:
417                if g.debugModeOn == 'True':
418                    raise ValueError,str(e)
419
420                c.xml='<p> Error retrieving documents for %s hits is [%s]</p>'%(hits,e)
421
422        return render('browse/results')
423       
424    def __advancedPrompt(self, searchConstraints = None):
425        '''
426        This provides the advanced search input page
427        @keyword searchConstraints: a DiscoveryState.constraints object with the
428        search filter details in
429        '''
430        #defaults
431        c.title = bc.DISCOVERY_HOME_TITLE
432        c.bbox='90.0','-180.0','180.0','-90.0'
433        #c.bbox='','','',''
434        c.startDate = ''
435        c.endDate = ''
436        c.textTarget='All'
437        c.searchString=''
438        c.source=['All']
439        c.geoSearchType='overlaps'
440
441        # apply any available constraints
442        if searchConstraints:
443            if searchConstraints['dateRange']:
444                c.startDate = '%s/%s/%s' %searchConstraints['dateRange'][0]
445                c.endDate = '%s/%s/%s' %searchConstraints['dateRange'][1]
446            if searchConstraints['bbox']:
447                c.bbox=searchConstraints['bbox']
448            if searchConstraints['textTarget']:
449                c.textTarget=searchConstraints['textTarget']
450            if searchConstraints['searchString']:
451                c.searchString=searchConstraints['searchString']
452            if searchConstraints['scope']:
453                c.source=searchConstraints['scope']
454            if searchConstraints['geoSearchType']:
455                c.geoSearchType = searchConstraints['geoSearchType']
456               
457        # NB, htmlfill doesn't handle the checked property correctly, so need
458        # to add this explicitly here
459        if 'source' not in self.inputs:
460            self.inputs['source'] = 'NERC_DDC'
461       
462        return self.savePageAndRender("browse/discovery_search", **self.inputs)
463
464       
465    def __checkBBoxValidity(self, bbox):
466        '''
467        Check the integrity of the bounding box; return any errors found as list
468        @return: list of errors
469        '''
470        errors = []
471       
472        for name, val in [('North', float(bbox[0])), ('South', float(bbox[3]))]:
473            if val > 90.0 or val < -90.:
474                errors.append("%s latitude exceeds valid range - -90 <= x <= 90" %name)
475               
476        for name, val in [('West', float(bbox[1])), ('East', float(bbox[2]))]:
477            if val > 180.0 or val < -180.:
478                errors.append("%s longitude exceeds valid range - -180 <= x <= 180" %name)
479        return errors
480
481           
482    def __checkform(self,expected):
483        '''
484        Simply checks the inputs to make sure the elements in expected are present
485        - NB, this isn't actually checking that a value for these inputs are set, it
486        is just checking the fields are there
487        @return array of missing inputs
488        '''
489        logging.debug("Checking for missing inputs")
490        missingInputs = []
491        for i in expected:
492            if i not in self.inputs:
493                logging.debug(" - found missing input: %s" %i)
494                missingInputs.append(i)
495        logging.debug("Finished checking for missing inputs")
496        return missingInputs
497       
498               
499    def __checkdates(self,dateRange):
500        '''
501        Check input dates for sanity
502        @return: error message, if invalid, None otherwise
503        '''
504        if not ValidDate(dateRange[0])*ValidDate(dateRange[1]):
505            return str(dateRange)
506        elif JulDay(dateRange[0]) >= JulDay(dateRange[1]):
507            return 'second date must be after first date'
508     
509        return None
510
511       
512    def __buildconstraints(self, dateRange, bbox, scope, searchString, geoSearch):
513        '''
514        Build and return a DiscoveryState.constraints object
515        '''
516        return constraints(dateRange=dateRange, bbox=bbox,
517                           scope=scope, searchString=searchString, 
518                           geoSearchType=geoSearch)
519       
520
521    def semantic(self):
522        self.__setup()
523        vs = VS(proxyServer = g.proxyServer)
524        if 'searchString' in self.inputs:
525            try:
526                # NB, the search doesn't work with aggregated info so just
527                # look up both in turn
528                broader = []
529                narrower = []
530                synonyms = []
531                if 'searchString' in self.inputs and self.inputs['searchString']:
532                    [broader, narrower, synonyms] = \
533                        vs.getRelated(self.inputs['searchString'])
534
535                if 'vocabTerm' in self.inputs and self.inputs['vocabTerm']:
536                    [broader1, narrower1, synonyms1] = \
537                        vs.getRelated(self.inputs['vocabTerm'])
538                   
539                    broader = self.__addNewTerms(broader, broader1)
540                    narrower = self.__addNewTerms(narrower, narrower1)
541                    synonyms = self.__addNewTerms(synonyms, synonyms1)
542                   
543                   
544                #get a base string for the links to new searches
545                if 'start' in self.inputs: 
546                    del self.inputs['start']
547                if 'howmany' in self.inputs: 
548                    del self.inputs['howmany']
549                self.inputs['searchString']='###SEARCHSSTRING###'
550                q='%s/discovery?'%g.server
551                for i in self.inputs: q+='%s=%s&'%(i,self.inputs[i])
552                url=q[0:-1]
553                # and now build the links
554                c.narrower=[]
555                c.broader=[]
556                c.synonyms=[]
557                for i in narrower:
558                    c.narrower.append((i,url.replace('###SEARCHSSTRING###',i)))
559                for i in broader:
560                    c.broader.append((i,url.replace('###SEARCHSSTRING###',i)))
561                for i in synonyms:
562                    c.synonyms.append((i,url.replace('###SEARCHSSTRING###',i)))
563                if c.narrower!=[] or c.broader!=[] or c.synonyms!=[]: c.semAvailable=1
564            except IOError,e:
565                c.semAvailable=0
566                c.semError=' (No valid reply from vocabulary service)'
567                #This should go in a log file ...
568                print 'ERROR: Vocabulary Service: %s (for search [%s])'%(str(e),self.inputs['searchString'])
569        else:
570            broader,narrower,synonyms=[],[],[]
571            c.semAvailable=0
572            c.semError='.'
573       
574        return render('browse/semantic',fragment=True)
575
576
577    def __addNewTerms(self, toList, fromList):
578        '''
579        Add any terms in list2 not in list1 to list1 - and then return this list
580        @param toList: list to add terms to
581        @param fromList: list to add terms from
582        '''
583        for data in fromList:
584            if data not in toList:
585                toList.append(data)
586               
587        return toList
588   
589    def __displayBrowseSearchResults(self, searchClient):
590        '''
591        Provides the search results for Browse and NumSim content
592        @param searchClient: search client adhering to the ndg.common.clients.interfacesearchclient
593        interface - which has just ran a search
594        '''
595        c.results = searchClient.results
596        c.searchTarget = self.inputs['searchTarget']
597        textTarget = self.inputs['textTarget']
598
599        # check if we've done a search against atoms - NB, this should be the
600        # default eventually - so we can remove all the alternative options
601        isAtom = False
602        if textTarget == SearchClient.ATOM_TARGET:
603            isAtom = True
604       
605        for r in c.results:
606            id = r.id
607                # cope with atom docs
608            if isAtom:
609                r.link = r.href
610            else:
611                n=ndgObject(id,config=self.cf)
612                r.link={'Browse':n.BURL,'NumSim':n.URL}[c.searchTarget]
613
614        # filter atom docs according to publication state
615        if isAtom:
616            c.searchTerm = " - for search term, '%s'" %self.inputs['searchString']
617            if not g.atomEditorEnabled:
618                c.results = self.__filterAtomResults(c.results)
619
620            if c.results:
621                c.searchTerm += ' [%s results found]' %len(c.results)
622               
623               
624            html = render('genshi', 'browse/short_atom_results')
625            # make sure the edit links point to the editor, not the browse service
626            html = html.replace(VTD.BROWSE_SERVER_URL + '/editAtom', g.server + '/editAtom')
627            return html
628        else:
629            return render('browse/short_results')
630
631
632    def __filterAtomResults(self, results):
633        '''
634        Given a set of atom docs search results, filter these to only return docs in the
635        'published' or 'Published' state
636        @param results: list of results as returned by SearchClient
637        @return filteredResults: list of results with only published data included
638        '''
639        logging.debug("Filtering results to remove non-published data")
640        filteredResults = []
641        for result in results:
642            if result.collection.find('ublished') == -1:
643                logging.debug("- found non-published doc - ignoring")
644                continue
645            filteredResults.append(result)
646        logging.debug("- returning filtered results")
647        return filteredResults
648
649           
650    def clearSession(self):
651        '''
652        Clear out all session variables - to help when these change in development
653        '''
654        session.clear()
655        session.save()           
656     
Note: See TracBrowser for help on using the repository browser.