source: TI05-delivery/ows_framework/trunk/ows_server/ows_server/controllers/discovery.py @ 3472

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI05-delivery/ows_framework/trunk/ows_server/ows_server/controllers/discovery.py@3472
Revision 3472, 15.9 KB checked in by cbyrom, 12 years ago (diff)

Add functionality to produce XML files for use with the Con Terra service.

Line 
1from ows_server.lib.base import *
2from paste.request import parse_querystring
3from ows_server.lib.Date import *
4from ows_server.templates import DiscoveryTemplate
5from ows_server.models import ndgSearch as NS
6from ows_server.models.DocumentRetrieve import DocumentRetrieve as DR
7from ows_server.models.DocumentRetrieve import ndgVocabPOX as VS
8from ows_server.models.DIF import DIF
9#from ows_server.templates.renderDiscoverySet import renderDiscoverySet
10from ows_server.models.DiscoveryState import DiscoveryState,constraints
11from ows_server.models.ndgObject import ndgObject
12#from ows_server.models.Utilities import myConfig
13from ows_server.lib.mailer import mailHandler
14from ows_server.models.xmlHandler2 import xmlHandler
15debug=0
16
17class DiscoveryController(BaseController):
18    ''' Provides the pylons controller for NDG discovery '''
19   
20    def __setup(self):
21        ''' Common setup for controller methods '''
22        self.cf=request.environ['ndgConfig']
23        self.exist=(self.cf.get('NDG_EXIST','local'),self.cf.get('NDG_EXIST','passwordFile'))
24        self.inputs=dict(parse_querystring(request.environ))
25        self.message=''
26               
27   
28    def index(self):
29       
30        self.__setup()
31        # parse the query string and hand off to a discovery engine
32       
33        if self.inputs=={} or 'ClearForm' in self.inputs: 
34            return self.__advancedPrompt()
35       
36        # see if this is a discovery search or a more complicated search
37        if 'searchTarget' not in self.inputs: self.inputs['searchTarget']='Discovery'
38       
39        #the following need to be defined
40        continuations={'start':1,'howmany':10}
41        for i in continuations:
42            if i not in self.inputs: self.inputs[i]=continuations[i]
43           
44           
45        # the simplest query we might get is a text search, in which case
46        # the inputs should be start, howmany and searchString (although
47        # maybe not in that order. The next simplest is one with
48        # a specified textTarget, after that we need all the inputs.
49       
50        if 'searchString' in self.inputs and 'textTarget' not in self.inputs:
51            # it's a simple text search
52            self.inputs['textTarget']='All'
53           
54        # the next simplest is one that includes texttarget as well ...
55        expected=['searchString','textTarget','start','howmany','searchTarget']
56        self.__checkform(expected)
57   
58        if self.message!='':
59            c.xml='Simple %s:'%self.message
60            return render_response('content')
61       
62       
63        if 'geoSearchType' not in self.inputs:self.inputs['geoSearchType']='overlaps'
64        if len(self.inputs)==6:
65           
66            # now we add the defaults ...
67            # this is kind of historical ...
68            bbox=None
69            dateRange=None
70            scope=None
71           
72        else:
73       
74            # ------------- Handle scope from radio button on form -------
75            if 'source' in self.inputs:
76                # the WSDL expects a list, we're just providing one ... via a radio ...
77                scope=[self.inputs['source']]
78                if scope==['All']: scope=None
79            else:
80                scope=None
81               
82            expected=['bboxN','bboxE','bboxS','bboxW','geoSearchType']
83            self.__checkform(expected)
84            if self.message!='': 
85                self.message=''
86                bbox=None
87            else:
88                # default form has a global bounding box, NB, internal to this routine we use bbox=[N,W,E,S], not [W,S,E,N]!
89                bbox=[self.inputs['bboxN'],self.inputs['bboxW'],self.inputs['bboxE'],self.inputs['bboxS']]
90               
91                self.__checkbox(bbox)
92                if self.message!='': 
93                    c.xml=self.message
94                    return render_response('content')
95                   
96           
97            expected=['startDateDay','startDateMon','startDateYear',
98                            'endDateDay','endDateMon','endDateYear']
99            self.__checkform(expected)
100            if self.message!='': 
101                self.message=''
102                dateRange=None
103            else:
104                try:
105                    dateRange=[(self.inputs['startDateDay'],self.inputs['startDateMon'],self.inputs['startDateYear']),
106                                (self.inputs['endDateDay'],self.inputs['endDateMon'],self.inputs['endDateYear'])]
107                    #default form has blanks, in which case we don't want to check for date range
108                    if dateRange<>[("","",""),("","","")]:
109                        self.__checkdates(dateRange)
110                    else: dateRange=None           
111                except:
112                    self.message='Invalid date provided'
113                if self.message!='': 
114                    c.xml=self.message
115                    return render_response('content')
116       
117        if 'constrained' in self.inputs: 
118            con=self.__buildconstraints(dateRange,bbox,scope,self.inputs['searchString'],self.inputs['geoSearchType'])
119            return self.__advancedPrompt(searchConstraints=con)
120        else:
121            # ------------- ok, now go do the search -----------
122            response=self.doText(self.inputs['searchString'],self.inputs['textTarget'],
123                self.inputs['start'],self.inputs['howmany'],scope=scope,dateRange=dateRange,bbox=bbox,
124                geoSearch=self.inputs['geoSearchType'])
125            return response
126
127    def doText(self,searchString,textTarget,start,howmany,scope=None,dateRange=None,bbox=None,geoSearch='overlaps'):
128       
129        ''' Carry out a text search for <searchString>
130        in the <textTarget> where the accepted text target values are controlled
131        by the DiscoveryTemplate GUI, and are: All, Authors, Parameters '''
132       
133        start,howmany=int(start),int(howmany)  # url arguments need conversion ...
134       
135        if self.inputs['searchTarget']=='Discovery':
136            ws=NS.ndgSearch(logger=logger)
137        elif self.inputs['searchTarget'] in ['Browse','NumSim']:
138            ws=DR(self.exist[0],pwfile=self.exist[1])
139            #overriding text target which is ignored currently ... yuck ...
140            textTarget=self.inputs['searchTarget']
141            if textTarget=='Browse':textTarget='ndg_B_metadata'
142        else:
143            c.xml='Unknown searchTarget %s'%self.inputs['searchTarget']
144            return render_response('error')
145           
146        documents=ws.search(searchString,start=start,howmany=howmany,target=textTarget,
147                            scope=scope,dateRange=dateRange,bbox=bbox,geoSearchType=geoSearch)
148        if ws.error !=None:
149            m=''
150            for i in ws.error:m+='<p>%s</p>'%i
151            c.xml=m
152            return render_response('content')
153       
154        #build constraints info for report
155        searchConstraints=self.__buildconstraints(dateRange,bbox,scope,searchString,geoSearch)
156       
157        hits=ws.hits
158        id=ws.serverSessionID
159       
160        if hits<howmany:howmany=hits
161       
162        c.state=DiscoveryState(id,searchString,request.environ,hits,searchConstraints,start,howmany)
163        c.querystring=request.environ['QUERY_STRING']
164       
165        if hits==0: 
166            c.xml='<p>No records found [%s]</p>'%c.state.constraints
167            return render_response('content')
168     
169        try:
170            if self.inputs['searchTarget']=='Discovery':
171                results=ws.getLabelledDocs(format='DIF')
172            else:
173                return self.moreSearch(ws)
174            difs=[]
175            errors=[]
176            for result in results: 
177                obj=ndgObject(result[0])
178                obj.setConfig(self.cf)
179                try:
180                    difs.append(DIF(result[1],ndgObj=obj))
181                except ValueError,e:
182                    errors.append((result[0],str(e)))
183            if results==[]:
184                c.xml='<p> No results for "%s"!</p>'%searchString
185                return render_response('content')
186            elif difs==[]:
187                c.xml='<p>No usable results for "%s"!</p>'%searchString
188                return render_response('content')
189            else:
190                if errors<>[]:
191                    c.xml='<p>Search results for "%s"'%searchString
192                    dp=[]
193                    for e in errors:
194                        n=ndgObject(e[0])
195                        if n.repository not in dp: dp.append(n.repository)
196                    if len(dp)<>1: 
197                        dp='[Various Data Providers]'
198                    else:
199                        dp='[%s]'%dp[0] 
200                    c.xml+=' (unfortunately %s hits matched unformattable documents from %s, an internal error has been logged):</p>'%(len(errors),dp)
201                    status,message=mailHandler(['b.n.lawrence@rl.ac.uk'],'DIF errors',str(errors),
202                                    server=self.cf.get('DEFAULT','mailserver'))
203                    if not status:
204                        c.xml+='<p> Actually, not even an internal error has been logged. <br/>'
205                        c.xml+='Internal sending of mail failed with error [%s]</p>'%message
206                    return render_response('content')
207               
208               
209                c.difs=difs
210               
211                session['results']=h.current_url()
212                session.save()
213               
214                # set up the displayed tabs
215                if len(c.pageTabs)==1: 
216                    c.pageTabs.append(('Results',session['results']))
217                    c.pageTabs.append(('Selections',
218                                       h.url_for(controller='selectedItems',
219                                                 action='index')))
220                elif c.pageTabs[1][0]!='Results':
221                        c.pageTabs.insert(1,('Results',session['results']))
222                        selectionsNeeded=1
223                        for tab in c.pageTabs[0]:
224                            if tab == 'Selections':
225                                selectionsNeeded=0
226                        if selectionsNeeded:
227                            c.pageTabs.append(('Selections',
228                                       h.url_for(controller='selectedItems',
229                                                 action='index')))
230                           
231                return render_response('results')
232               
233        except ValueError,e:
234            if debug: 
235                raise ValueError,str(e)
236            else:
237                c.xml='<p> Error retrieving documents for %s hits is [%s]</p>'%(hits,e)
238                return render_response('content')
239        except Exception,e:
240                c.xml='Unknown error %s,%s'%(str(Exception),e)
241                return render_response('error')                       
242       
243    def __advancedPrompt(self,searchConstraints=None):
244        ''' This provides the advanced search input page '''
245        try:
246            discoveryURL=self.cf.get('SEARCH','discoveryURL')
247            advancedURL=self.cf.get('SEARCH','advancedURL')
248        except:
249            return 'Error, invalid configuration for search interface'
250        #defaults
251        c.bbox='90.0','-180.0','180.0','-90.0'
252        c.startDateDay,c.startDateMon,c.startDateYear='','',''
253        c.endDateDay,c.endDateMon,c.endDateYear='','',''
254        c.textTarget='All'
255        c.searchString=''
256        c.source=['All']
257        c.geoSearchType='overlaps'
258        #constraints
259
260        if searchConstraints is not None:
261            if searchConstraints['dateRange'] is not None:
262                c.startDateDay,c.startDateMon,c.startDateYear=searchConstraints['dateRange'][0]
263                c.endDateDay,c.endDateMon,c.endDateYear=searchConstraints['dateRange'][1]
264            if searchConstraints['bbox'] is not None:
265                c.bbox=searchConstraints['bbox']
266            if searchConstraints['textTarget'] is not None:
267                c.textTarget=searchConstraints['textTarget']
268            if searchConstraints['searchString'] is not None:
269                c.searchString=searchConstraints['searchString']
270            if searchConstraints['scope'] is not None:
271                c.source=searchConstraints['scope']
272            c.geoSearchType=(searchConstraints['geoSearchType'] or 'overlaps')
273        return render_response('advanced')
274       
275    def __checkbox(self,bbox):
276        m='Invalid bounding box dimensions entered - limits are '
277        if float(bbox[0])>90.0 or float(bbox[3])<-90.:
278            self.message=m+'+90 (N), -90 (S)!'
279        if float(bbox[1])<-180. or float(bbox[2])>180.:
280            if self.message=='':self.message=m
281            self.message=self.message[:-1]+' -180 (W), 180 (E)!'
282           
283    def __checkform(self,expected):
284        ''' Simply checks the inputs to make sure the elements in expected are present '''
285        message="An incomplete NDG search form was received: "
286        for i in expected:
287            if i not in self.inputs: 
288                self.message=message+i
289        if self.message!='':self.message+='[%s]'%self.inputs
290               
291    def __checkdates(self,dateRange):
292        ''' Check input dates for sanity '''
293       
294        if not ValidDate(dateRange[0])*ValidDate(dateRange[1]):
295            self.message='Input dates are not valid [%s]'%dateRange
296        elif JulDay(dateRange[0])>=JulDay(dateRange[1]):
297            self.message='Second date must be after first date'
298       
299    def __buildconstraints(self,dateRange,bbox,scope,searchString,geoSearch):
300        ''' Just build a constraint string '''
301        return constraints(dateRange=dateRange,bbox=bbox,scope=scope,searchString=searchString,geoSearchType=geoSearch)
302       
303    #def oneLineSearch(self):
304    #    try:
305    #        discoveryURL=self.cf.get('SEARCH','discoveryURL')
306    #        oneLiner=DiscoveryTemplate.oneLiner
307    #    except:
308    #        return 'Error, invalid configuration for search interface'
309    #    return DiscoveryTemplate.searchTextOneLine%locals() 
310   
311    def semantic(self):
312        self.__setup()
313        vs=VS(proxyServer=self.cf.get('DEFAULT','proxyServer'))
314        if 'searchString' in self.inputs:
315            try:
316                [broader,narrower,synonyms]=vs.getRelated(self.inputs['searchString'])
317                #get a base string for the links to new searches
318                if 'start' in self.inputs: del self.inputs['start']
319                if 'howmany' in self.inputs: del self.inputs['howmany']
320                self.inputs['searchString']='###SEARCHSSTRING###'
321                q='%s/discovery?'%g.server
322                for i in self.inputs: q+='%s=%s&'%(i,self.inputs[i])
323                url=q[0:-1]
324                # and now build the links
325                c.narrower=[]
326                c.broader=[]
327                c.synonyms=[]
328                for i in narrower:
329                    c.narrower.append((i,url.replace('###SEARCHSSTRING###',i)))
330                for i in broader:
331                    c.broader.append((i,url.replace('###SEARCHSSTRING###',i)))
332                for i in synonyms:
333                    c.synonyms.append((i,url.replace('###SEARCHSSTRING###',i)))
334                if c.narrower!=[] or c.broader!=[] or c.synonyms!=[]: c.semAvailable=1
335            except IOError,e:
336                c.semAvailable=0
337                c.semError=' (No valid reply from vocabulary service)'
338                #This should go in a log file ...
339                print 'ERROR: Vocabulary Service: %s (for search [%s])'%(str(e),self.inputs['searchString'])
340        else:
341            broader,narrower,synonyms=[],[],[]
342            c.semAvailable=0
343            c.semError='.'
344       
345        return render_response('semantic',fragment=True)
346   
347    def moreSearch(self,ws):
348        ''' Provides the search on Browse and NumSim content '''
349        c.results=ws.results
350        c.searchTarget=self.inputs['searchTarget']
351        for r in c.results:
352            n=ndgObject(r.id,config=self.cf)
353            r.link={'Browse':n.BURL,'NumSim':n.URL}[c.searchTarget]
354       
355        return render_response('short_results')
356           
357
358   
359       
360       
Note: See TracBrowser for help on using the repository browser.