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

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

Update ows_server code to use new ndgUtils module - and remove files that have
been migrated to this module.

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