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