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

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

Implement 'Selections' tab - a shopping cart type function to
allow the storing of different DIF selections together. Functionality
implemented to allow users to select/unselect DIFs from the results
screen and also to remove them, and remove everything, from the
Selections tab. Also, if KML is available for the DIF, icons are
provided to allow the user to open the file - currently valid for
the GoogleEarth? option but not the ConTerra?. Lastly, checkboxes
are provided - together with a 'select all' one - to allow selection
of multiple DIFs when viewing the associated KML. NB, this latter
functionality is not yet implemented.

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
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                if len(c.pageTabs)==1: 
214                    c.pageTabs.append(('Results',session['results']))
215                elif c.pageTabs[1][0]!='Results':
216                        c.pageTabs.insert(1,('Results',session['results']))
217                return render_response('results')
218               
219        except ValueError,e:
220            if debug: 
221                raise ValueError,str(e)
222            else:
223                c.xml='<p> Error retrieving documents for %s hits is [%s]</p>'%(hits,e)
224                return render_response('content')
225        except Exception,e:
226                c.xml='Unknown error %s,%s'%(str(Exception),e)
227                return render_response('error')                       
228       
229    def __advancedPrompt(self,searchConstraints=None):
230        ''' This provides the advanced search input page '''
231        try:
232            discoveryURL=self.cf.get('SEARCH','discoveryURL')
233            advancedURL=self.cf.get('SEARCH','advancedURL')
234        except:
235            return 'Error, invalid configuration for search interface'
236        #defaults
237        c.bbox='90.0','-180.0','180.0','-90.0'
238        c.startDateDay,c.startDateMon,c.startDateYear='','',''
239        c.endDateDay,c.endDateMon,c.endDateYear='','',''
240        c.textTarget='All'
241        c.searchString=''
242        c.source=['All']
243        c.geoSearchType='overlaps'
244        #constraints
245
246        if searchConstraints is not None:
247            if searchConstraints['dateRange'] is not None:
248                c.startDateDay,c.startDateMon,c.startDateYear=searchConstraints['dateRange'][0]
249                c.endDateDay,c.endDateMon,c.endDateYear=searchConstraints['dateRange'][1]
250            if searchConstraints['bbox'] is not None:
251                c.bbox=searchConstraints['bbox']
252            if searchConstraints['textTarget'] is not None:
253                c.textTarget=searchConstraints['textTarget']
254            if searchConstraints['searchString'] is not None:
255                c.searchString=searchConstraints['searchString']
256            if searchConstraints['scope'] is not None:
257                c.source=searchConstraints['scope']
258            c.geoSearchType=(searchConstraints['geoSearchType'] or 'overlaps')
259        return render_response('advanced')
260       
261    def __checkbox(self,bbox):
262        m='Invalid bounding box dimensions entered - limits are '
263        if float(bbox[0])>90.0 or float(bbox[3])<-90.:
264            self.message=m+'+90 (N), -90 (S)!'
265        if float(bbox[1])<-180. or float(bbox[2])>180.:
266            if self.message=='':self.message=m
267            self.message=self.message[:-1]+' -180 (W), 180 (E)!'
268           
269    def __checkform(self,expected):
270        ''' Simply checks the inputs to make sure the elements in expected are present '''
271        message="An incomplete NDG search form was received: "
272        for i in expected:
273            if i not in self.inputs: 
274                self.message=message+i
275        if self.message!='':self.message+='[%s]'%self.inputs
276               
277    def __checkdates(self,dateRange):
278        ''' Check input dates for sanity '''
279       
280        if not ValidDate(dateRange[0])*ValidDate(dateRange[1]):
281            self.message='Input dates are not valid [%s]'%dateRange
282        elif JulDay(dateRange[0])>=JulDay(dateRange[1]):
283            self.message='Second date must be after first date'
284       
285    def __buildconstraints(self,dateRange,bbox,scope,searchString,geoSearch):
286        ''' Just build a constraint string '''
287        return constraints(dateRange=dateRange,bbox=bbox,scope=scope,searchString=searchString,geoSearchType=geoSearch)
288       
289    #def oneLineSearch(self):
290    #    try:
291    #        discoveryURL=self.cf.get('SEARCH','discoveryURL')
292    #        oneLiner=DiscoveryTemplate.oneLiner
293    #    except:
294    #        return 'Error, invalid configuration for search interface'
295    #    return DiscoveryTemplate.searchTextOneLine%locals() 
296   
297    def semantic(self):
298        self.__setup()
299        vs=VS(proxyServer=self.cf.get('DEFAULT','proxyServer'))
300        if 'searchString' in self.inputs:
301            try:
302                [broader,narrower,synonyms]=vs.getRelated(self.inputs['searchString'])
303                #get a base string for the links to new searches
304                if 'start' in self.inputs: del self.inputs['start']
305                if 'howmany' in self.inputs: del self.inputs['howmany']
306                self.inputs['searchString']='###SEARCHSSTRING###'
307                q='%s/discovery?'%g.server
308                for i in self.inputs: q+='%s=%s&'%(i,self.inputs[i])
309                url=q[0:-1]
310                # and now build the links
311                c.narrower=[]
312                c.broader=[]
313                c.synonyms=[]
314                for i in narrower:
315                    c.narrower.append((i,url.replace('###SEARCHSSTRING###',i)))
316                for i in broader:
317                    c.broader.append((i,url.replace('###SEARCHSSTRING###',i)))
318                for i in synonyms:
319                    c.synonyms.append((i,url.replace('###SEARCHSSTRING###',i)))
320                if c.narrower!=[] or c.broader!=[] or c.synonyms!=[]: c.semAvailable=1
321            except IOError,e:
322                c.semAvailable=0
323                c.semError=' (No valid reply from vocabulary service)'
324                #This should go in a log file ...
325                print 'ERROR: Vocabulary Service: %s (for search [%s])'%(str(e),self.inputs['searchString'])
326        else:
327            broader,narrower,synonyms=[],[],[]
328            c.semAvailable=0
329            c.semError='.'
330       
331        return render_response('semantic',fragment=True)
332   
333    def moreSearch(self,ws):
334        ''' Provides the search on Browse and NumSim content '''
335        c.results=ws.results
336        c.searchTarget=self.inputs['searchTarget']
337        for r in c.results:
338            n=ndgObject(r.id,config=self.cf)
339            r.link={'Browse':n.BURL,'NumSim':n.URL}[c.searchTarget]
340       
341        return render_response('short_results')
342           
343
344   
345       
346       
Note: See TracBrowser for help on using the repository browser.