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

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

Support for synonyms in semantic search

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