Changeset 5261


Ignore:
Timestamp:
07/05/09 17:19:38 (10 years ago)
Author:
cbyrom
Message:

Lots of tidy ups to MILK codebase:

Implement new input search filters - with javascript datapickers to
pick the date ranges + add the vocab search ahead text input and
combine this with the text input.

Refactor discovery controller to tidy it up significantly - making more
structured and improving error handling and logging. Improve
templates for the search filter, splitting into multiple files to
organise better.

Various tidying up and tweaks of other codebase - e.g. standardising
use of global variables across app.

Location:
MILK/trunk/milk_server
Files:
29 added
1 deleted
13 edited

Legend:

Unmodified
Added
Removed
  • MILK/trunk/milk_server/README.txt

    r5201 r5261  
    2828[DEFAULT] 
    2929- server - NB, this should match the host and port settings in the development.ini file 
    30 - mailserver - SMTP server to sending mails 
     30- mailServer - SMTP server to sending mails 
    3131- metadataMaintainer - email address to which correction comments will be mailed 
    3232- tbrecipient mail address to which trackback comments are sent 
  • MILK/trunk/milk_server/milk_server/config/milkMiddleware.py

    r5208 r5261  
    6565 
    6666        self.globals.proxyServer = cf.get('DEFAULT','proxyServer') 
     67 
     68        self.globals.mailServer = cf.get('DEFAULT','mailServer') 
     69        self.globals.tbRecipient = cf.get('DEFAULT', 'tbrecipient') 
     70        self.globals.metadataMaintainer = cf.get('DEFAULT','metadataMaintainer') 
    6771                 
    6872        # for standalone discovery 
    69         self.globals.standalone=cf.config.getboolean('DISCOVERY','standalone') 
     73        self.globals.standalone = cf.config.getboolean('DISCOVERY','standalone') 
    7074 
    7175        # NB, without this set, the default host on ndg.badc.rl.ac.uk will be  
    7276        # used by discoveryServiceClient 
    73         if cf.config.has_option('DISCOVERY', 'discoveryServiceURL'): 
    74             self.globals.discoveryServiceURL = \ 
    75                 cf.config.get('DISCOVERY', 'discoveryServiceURL') 
     77        self.globals.discoveryServiceURL = cf.get('DISCOVERY', 'discoveryServiceURL') 
    7678                 
    7779        self.globals.pwFile = cf.get('NDG_EXIST','passwordFile') 
    78         localDbHostName = cf.get('NDG_EXIST','local') 
     80        self.globals.localEXist = cf.get('NDG_EXIST','local') 
    7981        # set up the validator for atoms - to allow re-use across the app 
    8082        self.globals.validator = AtomValidator(None, 
    8183                                               localMILKServer = self.globals.server,  
    8284                                               dbConfigFile = self.globals.pwFile,  
    83                                                dbHostName = localDbHostName, 
     85                                               dbHostName = self.globals.localEXist, 
    8486                                               newLineChar="<br/>", 
    8587                                               isDebug = self.globals.debugModeOn, 
  • MILK/trunk/milk_server/milk_server/config/routing.py

    r5201 r5261  
    6464    map.connect('doVocabSearch', controller = 'vocabsearch/vocabsearch', action='doVocabSearch') 
    6565 
    66  
    6766    #do this last or else you'll get bizarre behaviour 
    6867    map.connect('default', '',controller='home',action='index') 
  • MILK/trunk/milk_server/milk_server/controllers/atom_editor/editatom.py

    r5215 r5261  
    162162 
    163163        if c.atom and hasattr(c.atom, 'ndgURI'): 
    164             self.pathInfo = self.pathInfo.replace('upload', 'editAtom') 
     164             
     165            if hasattr(self, 'pathInfo'): 
     166                self.pathInfo = self.pathInfo.replace('upload', 'editAtom') 
    165167 
    166168            # NB, if there are errors, don't redirect otherwise these will get lost 
     
    691693            except Invalid, e: 
    692694                c.errors = e.unpack_errors() 
     695            except Exception, e: 
     696                c.xml = 'System Error: %s' %str(e) 
     697                return render("genshi", 'error') 
    693698                 
    694699             
  • MILK/trunk/milk_server/milk_server/controllers/browse/discovery.py

    r5198 r5261  
    11''' 
    22Controller for the discovery search functionality 
    3 TODO: this is a complete mess and really needs tidying up! 
    43''' 
    54import socket, logging 
     
    1514from milk_server.lib.Date import * 
    1615from milk_server.models.DiscoveryState import DiscoveryState,constraints 
    17  
    18 class DiscoveryController(BaseController): 
     16from milk_server.controllers.home import HomeController 
     17import browserconstants as bc 
     18 
     19class DiscoveryController(HomeController): 
    1920    '''  
    2021    Provides the pylons controller for NDG discovery  
     
    2223 
    2324    def __setup(self): 
    24         ''' Common setup for controller methods ''' 
     25        '''  
     26        Common setup for controller methods  
     27        ''' 
    2528        self.cf=request.environ['ndgConfig'] 
    26         self.exist=(self.cf.get('NDG_EXIST','local'), g.pwFile) 
    2729        self.inputs=dict(parse_querystring(request.environ)) 
    2830        self.message='' 
     31        c.errors = {}    # dict to store error messages 
    2932                
    3033     
    3134    def index(self): 
    32          
     35        ''' 
     36        Main entry point for doing discovery searches 
     37        ''' 
    3338        self.__setup() 
    3439         
    35         # parse the query string and hand off to a discovery engine 
    36         if self.inputs=={} or 'ClearForm' in self.inputs: 
     40        # if inputs are not set: if the discovery mode is enabled, display 
     41        # the search screen, otherwise redirect to the default home page according 
     42        # to what milk mode is set (i.e. editor/browse) 
     43        if not self.inputs or 'ClearForm' in self.inputs: 
    3744            if g.discoveryEnabled: 
    3845                return self.__advancedPrompt() 
     
    4148                return h.redirect_to(h.url_for('default')) 
    4249        
     50        self.__getInputs() 
     51 
     52        # if any errors are found, return user to search page 
     53        if c.errors: 
     54            return self.__advancedPrompt() 
     55 
     56        searchString = self.inputs['searchString'] 
     57        if 'vocabTerm' in self.inputs: 
     58            searchString += " %s" %self.inputs['vocabTerm'] 
     59             
     60        # users can return to search page to refine the search inputs; in this case 
     61        # they will have a 'constrained' input 
     62        self.constraints = self.__buildconstraints(self.dateRange, self.bbox, self.scope, 
     63                                                   searchString, self.inputs['geoSearchType']) 
     64        if 'constrained' in self.inputs:  
     65            return self.__advancedPrompt(searchConstraints = self.constraints) 
     66 
     67        # ok, now go do the search 
     68        try: 
     69            return self.__runSearch(searchString, self.inputs['textTarget'], 
     70                                 self.inputs['start'], self.inputs['howmany'],  
     71                                 scope = self.scope, dateRange = self.dateRange,  
     72                                 bbox = self.bbox, geoSearch=self.inputs['geoSearchType']) 
     73        except Exception, e: 
     74            if g.debugModeOn == 'True': 
     75                raise e 
     76            else: 
     77                c.xml='Unexpected error: %s'%(str(e)) 
     78                return render('error') 
     79 
     80 
     81    def __getInputs(self): 
     82        ''' 
     83        Retrieve the user inputs and set defaults.  Values are stored in the 
     84        self.inputs dict 
     85        ''' 
     86        logging.debug("Getting user inputs") 
    4387        # see if this is a discovery search or a more complicated search 
    4488        if 'searchTarget' not in self.inputs:  
    4589            self.inputs['searchTarget']='Discovery' 
    4690         
    47         #the following need to be defined 
    48         continuations={'start':1,'howmany':10} 
    49         for i in continuations: 
    50             if i not in self.inputs:  
    51                 self.inputs[i]=continuations[i] 
     91        # set default for table paging, if not already set 
     92        # NB, url arguments need converting back to ints 
     93        if 'start' not in self.inputs: 
     94            self.inputs['start'] = 1 
     95        else: 
     96            self.inputs['start'] = int(self.inputs['start']) 
     97             
     98        if 'howmany' not in self.inputs: 
     99            self.inputs['howmany'] = 10 
     100        else: 
     101            self.inputs['howmany'] = int(self.inputs['howmany']) 
    52102             
    53103        # the simplest query we might get is a text search, in which case 
     
    58108            # it's a simple text search 
    59109            self.inputs['textTarget']='All' 
    60              
     110 
    61111        # the next simplest is one that includes texttarget as well ... 
    62         expected=['searchString','textTarget','start','howmany','searchTarget'] 
    63         self.__checkform(expected) 
    64      
    65         if self.message!='': 
    66             c.xml='Simple %s:'%self.message  
    67             return render('content') 
    68          
     112        expected=['searchString','textTarget'] 
     113        missingInputs = self.__checkform(expected) 
     114        if missingInputs: 
     115            if bc.INCOMPLETE_SEARCH_INPUT_MESSAGE not in c.errors: 
     116                c.errors[bc.INCOMPLETE_SEARCH_INPUT_MESSAGE] = [] 
     117            c.errors[bc.INCOMPLETE_SEARCH_INPUT_MESSAGE].extend(missingInputs) 
     118 
     119        self.__getSpatioTemporalInputs() 
     120        logging.debug("User inputs retrieved") 
     121 
     122 
     123    def __getSpatioTemporalInputs(self): 
     124        ''' 
     125        Get spatiotemporal input data - and set up any defaults, if required 
     126        ''' 
     127        logging.debug("Getting spatiotemporal inputs") 
    69128        if 'geoSearchType' not in self.inputs: 
    70129            self.inputs['geoSearchType']='overlaps' 
    71              
     130 
     131        # now we add the defaults... this is kind of historical - NOT SURE THIS IS STILL NEEDED 
    72132        if len(self.inputs)==6: 
    73             # now we add the defaults ... 
    74             # this is kind of historical ... 
    75             bbox=None 
    76             dateRange=None 
    77             scope=None 
    78              
    79         else: 
    80          
    81             # ------------- Handle scope from radio button on form ------- 
    82             if 'source' in self.inputs: 
    83                 # the WSDL expects a list, we're just providing one ... via a radio ... 
    84                 scope=[self.inputs['source']] 
    85                 if scope==['All']: scope=None 
     133            self.bbox=None 
     134            self.dateRange=None 
     135            self.scope=None 
     136            return 
     137         
     138        if 'source' in self.inputs and self.inputs['source'] != 'All': 
     139            # NB, the WSDL expects a list 
     140            self.scope = [self.inputs['source']] 
     141        else: 
     142            self.scope = None 
     143             
     144        missingInputs = self.__checkform(['bboxN','bboxE','bboxS','bboxW','geoSearchType']) 
     145        if missingInputs:  
     146            self.bbox = None 
     147        else: 
     148            # default form has a global bounding box, NB, internal to this routine we use bbox=[N,W,E,S], not [W,S,E,N]! 
     149            self.bbox = [self.inputs['bboxN'], self.inputs['bboxW'], 
     150                         self.inputs['bboxE'], self.inputs['bboxS']] 
     151            
     152            errors = self.__checkBBoxValidity(self.bbox) 
     153            if errors: 
     154                if bc.INVALID_BBOX_MESSAGE not in c.errors: 
     155                    c.errors[bc.INVALID_BBOX_MESSAGE] = [] 
     156                c.errors[bc.INVALID_BBOX_MESSAGE].extend(missingInputs) 
     157 
     158        missingInputs = self.__checkform(['startDate', 'endDate']) 
     159        if missingInputs or not(self.inputs['startDate'] and self.inputs['endDate']): 
     160            self.dateRange = None 
     161        else: 
     162            dateError = None 
     163            try: 
     164                year, month, day = self.inputs['startDate'].split('/') 
     165                self.dateRange = [(day, month, year)] 
     166                year, month, day = self.inputs['endDate'].split('/') 
     167                self.dateRange.append((day, month, year)) 
     168 
     169                if self.dateRange <> [("","",""),("","","")]: 
     170                    dateError = self.__checkdates(self.dateRange) 
     171                     
     172                else:  
     173                    self.dateRange = None 
     174                                
     175            except: 
     176                dateError = 'Invalid date provided' 
     177 
     178            if dateError: 
     179                if bc.INVALID_DATERANGE_MESSAGE not in c.errors: 
     180                    c.errors[bc.INVALID_DATERANGE_MESSAGE] = [] 
     181                c.errors[bc.INVALID_DATERANGE_MESSAGE].append(dateError) 
     182         
     183        logging.debug("Spatiotemporal inputs retrieved") 
     184 
     185 
     186    def __getSearchClient(self, clientType): 
     187        ''' 
     188        Retrieve the appropriate client to complete the search 
     189        - currently supported are browse and discovery clients 
     190        @param clientType: type of search client to use.  Currently accepts, 
     191        'Discovery', 'Browse' and 'NumSim' 
     192        @raise ValueError if unrecognised search client entered 
     193        @return search client adhering to the ndg.common.clients.interfacesearchclient 
     194        interface 
     195        ''' 
     196        logging.debug("Getting %s type search client" %clientType) 
     197        searchClient = None 
     198        if clientType =='Discovery': 
     199            logging.info(" - use Discovery service to complete search") 
     200            if g.discoveryServiceURL: 
     201                searchClient = DiscoveryServiceClient(HostAndPort = g.discoveryServiceURL) 
    86202            else: 
    87                 scope=None 
    88                  
    89             expected=['bboxN','bboxE','bboxS','bboxW','geoSearchType'] 
    90             self.__checkform(expected) 
    91             if self.message!='':  
    92                 self.message='' 
    93                 bbox=None 
    94             else: 
    95                 # default form has a global bounding box, NB, internal to this routine we use bbox=[N,W,E,S], not [W,S,E,N]! 
    96                 bbox=[self.inputs['bboxN'],self.inputs['bboxW'],self.inputs['bboxE'],self.inputs['bboxS']] 
    97                 
    98                 self.__checkbox(bbox) 
    99                 if self.message!='':  
    100                     c.xml=self.message 
    101                     return render('content') 
    102                      
    103             expected=['startDateDay','startDateMon','startDateYear', 
    104                       'endDateDay','endDateMon','endDateYear'] 
    105             self.__checkform(expected) 
    106             if self.message!='':  
    107                 self.message='' 
    108                 dateRange=None 
    109             else: 
    110                 try: 
    111                     dateRange=[(self.inputs['startDateDay'],self.inputs['startDateMon'],self.inputs['startDateYear']), 
    112                                 (self.inputs['endDateDay'],self.inputs['endDateMon'],self.inputs['endDateYear'])] 
    113                     #default form has blanks, in which case we don't want to check for date range 
    114                     if dateRange<>[("","",""),("","","")]: 
    115                         self.__checkdates(dateRange) 
    116                     else: dateRange=None            
    117                 except: 
    118                     self.message='Invalid date provided' 
    119                 if self.message!='':  
    120                     c.xml=self.message 
    121                     return render('content') 
    122          
    123         if 'constrained' in self.inputs:  
    124             con=self.__buildconstraints(dateRange,bbox,scope,self.inputs['searchString'],self.inputs['geoSearchType']) 
    125             return self.__advancedPrompt(searchConstraints=con) 
    126         else: 
    127             # ------------- ok, now go do the search ----------- 
    128             response=self.doText(self.inputs['searchString'],self.inputs['textTarget'], 
    129                 self.inputs['start'],self.inputs['howmany'],scope=scope,dateRange=dateRange,bbox=bbox, 
    130                 geoSearch=self.inputs['geoSearchType']) 
    131             return response 
    132  
    133  
    134     def doText(self,searchString,textTarget,start, \ 
    135                howmany,scope=None,dateRange=None,bbox=None,geoSearch='overlaps'): 
    136          
    137         ''' Carry out a text search for <searchString>  
     203                searchClient = DiscoveryServiceClient() 
     204                 
     205        elif clientType in ['Browse','NumSim']: 
     206            logging.info(" - use Browse service to complete search") 
     207            searchClient = SearchClient(dbHostName = g.localEXist, 
     208                                        configFileName = g.pwFile) 
     209        else: 
     210            raise ValueError("Unrecognised search type, '%s'" %clientType) 
     211         
     212        logging.debug("- returning search client") 
     213        return searchClient 
     214         
     215 
     216    def __runSearch(self, searchString, textTarget, start, 
     217                 howmany, scope = None, dateRange = None, bbox = None, 
     218                 geoSearch = 'overlaps'): 
     219        '''  
     220        Carry out a text search for <searchString>  
    138221        in the <textTarget> where the accepted text target values are controlled 
    139         by the DiscoveryTemplate GUI, and are: All, Authors, Parameters ''' 
    140         logging.info("'doText' invoke with string, '%s'" %searchString) 
    141         start,howmany=int(start),int(howmany)  # url arguments need conversion ... 
    142          
    143         if self.inputs['searchTarget']=='Discovery': 
    144             logging.info(" - use Discovery service to complete search") 
    145             url = None 
    146             if hasattr(g, 'discoveryServiceURL'): 
    147                 url = g.discoveryServiceURL 
    148             ws = DiscoveryServiceClient(HostAndPort=url) 
    149         elif self.inputs['searchTarget'] in ['Browse','NumSim']: 
    150             logging.info(" - use Browse service to complete search") 
    151             ws = SearchClient(dbHostName = self.exist[0], 
    152                               configFileName = self.exist[1]) 
    153             #overriding text target which is ignored currently ... yuck ... 
    154             textTarget=self.inputs['searchTarget'] 
     222        by the DiscoveryTemplate GUI, and are: All, Authors, Parameters 
     223        @param searchString: string to search for 
     224        @param textTarget: target of the search - either, 'All', 'Authors' or 'Parameters' 
     225        @param start: starting record to return 
     226        @param howmany: number of records to return 
     227        @keyword scope: scope of search - either NERC, NERC_DDC, MDIP or DPPP. Default = None 
     228        @keyword dateRange: date range in format [startDate, endDate] where the 
     229        date objects are tuples with format (day, month, year). Default = None 
     230        @keyword bbox: Bounding box in format [N,W,E,S]. Default = None 
     231        @keyword geoSearch: type of spatial search. Defaults to 'overlaps'.  
     232        ''' 
     233        logging.debug("Running text search with string, '%s'" %searchString) 
     234         
     235        searchClient = self.__getSearchClient(self.inputs['searchTarget']) 
     236         
     237        if self.inputs['searchTarget'] in ['Browse','NumSim']: 
     238            textTarget = self.inputs['searchTarget'] 
    155239            if textTarget == 'Browse': 
     240                # NB, this switches the searching to be done on atom format 
     241                # rather than moles1.3 format docs 
    156242                textTarget = SearchClient.ATOM_TARGET#'ndg_B_metadata' 
    157         else: 
    158             logging.error("Unrecognised search type, '%s'" \ 
    159                           %self.inputs['searchTarget']) 
    160             c.xml='Unknown searchTarget %s'%self.inputs['searchTarget'] 
    161             return render('error') 
    162243             
    163244        # PJK 04/09/08 Handle errors more gracefully 
     
    165246        # http://proj.badc.rl.ac.uk/ndg/ticket/984 
    166247        try: 
    167             documents=ws.search(searchString, 
    168                                 start=start, 
    169                                 howmany=howmany, 
    170                                 target=textTarget, 
    171                                 scope=scope, 
    172                                 dateRange=dateRange, 
    173                                 bbox=bbox, 
    174                                 geoSearchType=geoSearch) 
     248            searchClient.search(searchString, 
     249                                start = start, 
     250                                howmany = howmany, 
     251                                target = textTarget, 
     252                                scope = scope, 
     253                                dateRange = dateRange, 
     254                                bbox = bbox, 
     255                                geoSearchType = geoSearch) 
    175256        except socket.error, e: 
    176257            logging.error("Socket error for discovery service search: %s" % e) 
     
    184265            return render('error') 
    185266             
    186         logging.info("'doText()' returned - now processing results") 
    187         if ws.error !=None: 
    188             logging.error("Error encountered whilst running search: %s" %ws.error) 
     267        logging.debug("Search returned - now processing results") 
     268        # DiscoveryState object is a wrapper to the various search config 
     269        # variables 
     270        c.state = DiscoveryState(searchClient.serverSessionID, searchString, 
     271                                 request.environ, searchClient.hits, self.constraints, 
     272                                 start, howmany) 
     273 
     274        return self.__processSearchResults(searchClient, c.state) 
     275 
     276 
     277    def __processSearchResults(self, searchClient, ds): 
     278        ''' 
     279        Process the results from a search - as ran by the input search client object 
     280        @param searchClient: search client adhering to the ndg.common.clients.interfacesearchclient 
     281        interface - which has just ran a search 
     282        @param ds: DiscoveryState object with info on the search 
     283        ''' 
     284        if searchClient.error: 
     285            logging.error("Error encountered whilst running search: %s" %searchClient.error) 
    189286            m='' 
    190             for i in ws.error:m+='<p>%s</p>'%i 
    191             c.xml=m 
    192             return render('content') 
    193          
    194         #build constraints info for report 
    195         searchConstraints=self.__buildconstraints(dateRange,bbox,scope,\ 
    196                                                   searchString,geoSearch) 
    197         hits=ws.hits 
    198         if hits==0 and textTarget != SearchClient.ATOM_TARGET: 
    199             outMessage = 'No records found [contraints: %s]' %searchConstraints 
     287            for i in searchClient.error: 
     288                m+='<p>%s</p>'%i 
     289            c.xml = m 
     290            return render('error') 
     291         
     292        hits = searchClient.hits 
     293        if hits == 0 and ds.constraintsInstance['textTarget'] != SearchClient.ATOM_TARGET: 
     294            outMessage = 'No records found [constraints: %s]' %ds.constraints 
    200295            logging.info(outMessage)  
    201296            c.xml='<p>' + outMessage + '</p>' 
    202297            return render('content') 
    203298         
    204         id=ws.serverSessionID 
    205          
    206         if hits < howmany: 
    207             howmany = hits 
    208          
    209         # DiscoveryState object is a wrapper to the various search config 
    210         # variables 
    211         c.state=DiscoveryState(id,searchString,request.environ,\ 
    212                                hits,searchConstraints,start,howmany) 
    213         c.querystring=request.environ['QUERY_STRING'] 
     299        # NB, this is used in the semantic search function of results.kid and short_results.kid 
     300        c.querystring = request.environ['QUERY_STRING'] 
    214301      
    215302        try: 
    216             if self.inputs['searchTarget']=='Discovery': 
    217                 results=ws.getLabelledDocs(format='DIF') 
    218             else: 
    219                 return self.moreSearch(ws) 
    220  
    221             if results==[]: 
    222                 c.xml='<p> No results for "%s"!</p>'%searchString 
     303            # display browse search results differently 
     304            if self.inputs['searchTarget'] != 'Discovery': 
     305                return self.__displayBrowseSearchResults(searchClient) 
     306 
     307            # now actually retrieve the search records 
     308            results = searchClient.getLabelledDocs(format='DIF') 
     309 
     310            if not results: 
     311                c.xml='<p> No results for "%s"!</p>'%ds.searchString 
    223312                return render('content') 
    224313 
    225             difs=[] 
    226             errors=[] 
     314            difs = [] 
     315            errors = [] 
    227316            for result in results:  
    228317                obj=ndgObject(result[0], config = self.cf) 
     
    230319                    difs.append(DIF(result[1],ndgObj=obj)) 
    231320                except ValueError,e: 
    232                     errors.append((result[0],str(e))) 
    233  
    234             if difs==[]: 
    235                 c.xml='<p>No usable results for "%s"!</p>'%searchString 
     321                    errors.append((result[0], str(e))) 
     322 
     323            if not difs: 
     324                c.xml='<p>No usable results for "%s"!</p>'%ds.searchString 
    236325                return render('content') 
     326             
    237327            elif errors: 
    238                 c.xml='<p>Search results for "%s"'%searchString 
     328                c.xml='<p>Search results for "%s"'%ds.searchString 
    239329                dp=[] 
    240330                for e in errors: 
    241331                    n=ndgObject(e[0]) 
    242                     if n.repository not in dp: dp.append(n.repository) 
     332                    if n.repository not in dp:  
     333                        dp.append(n.repository) 
    243334                if len(dp)<>1:  
    244335                    dp='[Various Data Providers]' 
    245336                else: 
    246337                    dp='[%s]'%dp[0]  
     338                     
    247339                c.xml+=' (unfortunately %s hits matched unformattable documents from %s, an internal error has been logged):</p>'%(len(errors),dp) 
    248                 status,message=mailHandler(['b.n.lawrence@rl.ac.uk'],'DIF errors',str(errors), 
    249                                 server=self.cf.get('DEFAULT','mailserver')) 
     340                status, message=mailHandler([g.metadataMaintainer],'DIF errors', 
     341                                            str(errors), server = g.mailServer) 
    250342                if not status: 
    251343                    c.xml+='<p> Actually, not even an internal error has been logged. <br/>' 
    252344                    c.xml+='Internal sending of mail failed with error [%s]</p>'%message 
    253345                return render('content') 
    254             else: 
    255                 c.difs=difs 
    256                 session['results']=h.current_url() 
    257                 session.save() 
    258                  
    259                 # set up the displayed tabs 
    260                 if len(c.pageTabs)==1:  
    261                     c.pageTabs.append(('Results',session['results'])) 
    262                     c.pageTabs.append(('Selections', 
    263                                        h.url_for(controller='browse/selectedItems', 
    264                                                  action='index'))) 
    265                 elif c.pageTabs[1][0]!='Results': 
    266                         c.pageTabs.insert(1,('Results',session['results'])) 
    267                         selectionsNeeded=1 
    268                         for tab in c.pageTabs[0]: 
    269                             if tab == 'Selections': 
    270                                 selectionsNeeded=0 
    271                         if selectionsNeeded: 
    272                             c.pageTabs.append(('Selections', 
    273                                        h.url_for(controller='browse/selectedItems', 
    274                                                  action='index'))) 
    275                              
    276                 return render('browse/results') 
     346                 
     347            # if we're here, we're ready to display the dif records 
     348            c.difs = difs 
     349            session['results'] = h.current_url() 
     350            session.save() 
     351             
     352            # set up the displayed tabs 
     353            if len(c.pageTabs)==1:  
     354                c.pageTabs.append(('Results', session['results'])) 
     355                c.pageTabs.append(('Selections', 
     356                                   h.url_for(controller='browse/selectedItems', 
     357                                             action='index'))) 
     358            elif c.pageTabs[1][0]!='Results': 
     359                    c.pageTabs.insert(1,('Results',session['results'])) 
     360                    selectionsNeeded=1 
     361                    for tab in c.pageTabs[0]: 
     362                        if tab == 'Selections': 
     363                            selectionsNeeded=0 
     364                    if selectionsNeeded: 
     365                        c.pageTabs.append(('Selections', 
     366                                   h.url_for(controller='browse/selectedItems', 
     367                                             action='index'))) 
     368                         
     369            return render('browse/results') 
    277370                 
    278371        except ValueError,e: 
     
    282375                c.xml='<p> Error retrieving documents for %s hits is [%s]</p>'%(hits,e) 
    283376                return render('content') 
    284         except Exception,e: 
    285                 c.xml='Unknown error %s,%s'%(str(Exception),e) 
    286                 return render('error')                        
    287          
    288     def __advancedPrompt(self,searchConstraints=None): 
    289         ''' This provides the advanced search input page ''' 
     377 
     378         
     379    def __advancedPrompt(self, searchConstraints = None): 
     380        '''  
     381        This provides the advanced search input page  
     382        @keyword searchConstraints: a DiscoveryState.constraints object with the 
     383        search filter details in 
     384        ''' 
    290385        #defaults 
     386        c.title = bc.DISCOVERY_HOME_TITLE 
    291387        c.bbox='90.0','-180.0','180.0','-90.0' 
    292         c.startDateDay,c.startDateMon,c.startDateYear='','','' 
    293         c.endDateDay,c.endDateMon,c.endDateYear='','','' 
     388        c.startDate = '' 
     389        c.endDate = '' 
    294390        c.textTarget='All' 
    295391        c.searchString='' 
    296392        c.source=['All'] 
    297393        c.geoSearchType='overlaps' 
    298         #constraints 
    299  
    300         if searchConstraints is not None: 
    301             if searchConstraints['dateRange'] is not None: 
    302                 c.startDateDay,c.startDateMon,c.startDateYear=searchConstraints['dateRange'][0] 
    303                 c.endDateDay,c.endDateMon,c.endDateYear=searchConstraints['dateRange'][1] 
    304             if searchConstraints['bbox'] is not None: 
     394 
     395        # apply any available constraints 
     396        if searchConstraints: 
     397            if searchConstraints['dateRange']: 
     398                c.startDate = '%s/%s/%s' %searchConstraints['dateRange'][0] 
     399                c.endDate = '%s/%s/%s' %searchConstraints['dateRange'][1] 
     400            if searchConstraints['bbox']: 
    305401                c.bbox=searchConstraints['bbox'] 
    306             if searchConstraints['textTarget'] is not None: 
     402            if searchConstraints['textTarget']: 
    307403                c.textTarget=searchConstraints['textTarget'] 
    308             if searchConstraints['searchString'] is not None: 
     404            if searchConstraints['searchString']: 
    309405                c.searchString=searchConstraints['searchString'] 
    310             if searchConstraints['scope'] is not None: 
     406            if searchConstraints['scope']: 
    311407                c.source=searchConstraints['scope'] 
    312             c.geoSearchType=(searchConstraints['geoSearchType'] or 'overlaps') 
    313         return render('browse/advanced') 
    314          
    315     def __checkbox(self,bbox): 
    316         m='Invalid bounding box dimensions entered - limits are ' 
    317         if float(bbox[0])>90.0 or float(bbox[3])<-90.: 
    318             self.message=m+'+90 (N), -90 (S)!' 
    319         if float(bbox[1])<-180. or float(bbox[2])>180.: 
    320             if self.message=='':self.message=m 
    321             self.message=self.message[:-1]+' -180 (W), 180 (E)!' 
     408            if searchConstraints['geoSearchType']: 
     409                c.geoSearchType = searchConstraints['geoSearchType'] 
     410         
     411        return self.savePageAndRender("browse/discovery_search", **self.inputs) 
     412 
     413         
     414    def __checkBBoxValidity(self, bbox): 
     415        ''' 
     416        Check the integrity of the bounding box; return any errors found as list 
     417        @return: list of errors 
     418        ''' 
     419        errors = [] 
     420         
     421        for name, val in [('North', float(bbox[0])), ('South', float(bbox[3]))]: 
     422            if val > 90.0 or val < -90.: 
     423                errors.append("%s latitude exceeds valid range - -90 <= x <= 90" %name) 
     424                 
     425        for name, val in [('West', float(bbox[1])), ('East', float(bbox[2]))]: 
     426            if val > 180.0 or val < -180.: 
     427                errors.append("%s longitude exceeds valid range - -180 <= x <= 180" %name) 
     428        return errors 
     429 
    322430             
    323431    def __checkform(self,expected): 
    324         ''' Simply checks the inputs to make sure the elements in expected are present ''' 
    325         message="An incomplete NDG search form was received: " 
     432        '''  
     433        Simply checks the inputs to make sure the elements in expected are present 
     434        - NB, this isn't actually checking that a value for these inputs are set, it 
     435        is just checking the fields are there 
     436        @return array of missing inputs 
     437        ''' 
     438        logging.debug("Checking for missing inputs") 
     439        missingInputs = [] 
    326440        for i in expected: 
    327             if i not in self.inputs:  
    328                 self.message=message+i 
    329         if self.message!='':self.message+='[%s]'%self.inputs 
     441            if i not in self.inputs: 
     442                logging.debug(" - found missing input: %s" %i) 
     443                missingInputs.append(i) 
     444        logging.debug("Finished checking for missing inputs") 
     445        return missingInputs 
     446         
    330447                 
    331448    def __checkdates(self,dateRange): 
    332         ''' Check input dates for sanity ''' 
    333         
     449        '''  
     450        Check input dates for sanity  
     451        @return: error message, if invalid, None otherwise 
     452        ''' 
    334453        if not ValidDate(dateRange[0])*ValidDate(dateRange[1]): 
    335             self.message='Input dates are not valid [%s]'%dateRange 
    336         elif JulDay(dateRange[0])>=JulDay(dateRange[1]): 
    337             self.message='Second date must be after first date' 
    338          
    339     def __buildconstraints(self,dateRange,bbox,scope,searchString,geoSearch): 
    340         ''' Just build a constraint string ''' 
    341         return constraints(dateRange=dateRange,bbox=bbox,scope=scope,searchString=searchString,geoSearchType=geoSearch) 
     454            return str(dateRange) 
     455        elif JulDay(dateRange[0]) >= JulDay(dateRange[1]): 
     456            return 'second date must be after first date' 
     457      
     458        return None 
     459 
     460         
     461    def __buildconstraints(self, dateRange, bbox, scope, searchString, geoSearch): 
     462        '''  
     463        Build and return a DiscoveryState.constraints object  
     464        ''' 
     465        return constraints(dateRange=dateRange, bbox=bbox, 
     466                           scope=scope, searchString=searchString,  
     467                           geoSearchType=geoSearch) 
    342468         
    343469 
     
    379505 
    380506     
    381     def moreSearch(self,ws): 
    382         ''' Provides the search on Browse and NumSim content ''' 
    383         c.results=ws.results 
    384         c.searchTarget=self.inputs['searchTarget'] 
     507    def __displayBrowseSearchResults(self, searchClient): 
     508        '''  
     509        Provides the search results for Browse and NumSim content 
     510        @param searchClient: search client adhering to the ndg.common.clients.interfacesearchclient 
     511        interface - which has just ran a search 
     512        ''' 
     513        c.results = searchClient.results 
     514        c.searchTarget = self.inputs['searchTarget'] 
    385515        textTarget = self.inputs['textTarget'] 
    386516 
    387         # check if we're doing a search against atoms - NB, this should be the 
     517        # check if we've done a search against atoms - NB, this should be the 
    388518        # default eventually - so we can remove all the alternative options 
    389519        isAtom = False 
     
    400530                r.link={'Browse':n.BURL,'NumSim':n.URL}[c.searchTarget] 
    401531 
     532        # filter atom docs according to publication state 
    402533        if isAtom: 
    403534            c.searchTerm = " - for search term, '%s'" %self.inputs['searchString'] 
     
    436567             
    437568    def clearSession(self): 
    438         ''' Clear out all session variables - to help when these change in development ''' 
     569        '''  
     570        Clear out all session variables - to help when these change in development  
     571        ''' 
    439572        session.clear() 
    440573        session.save()             
  • MILK/trunk/milk_server/milk_server/controllers/home.py

    r5234 r5261  
    4242        form   
    4343        ''' 
    44         logging.debug("Saving current page url (%s) - to keep track of atom editor state" \ 
    45                       %self.pathInfo) 
    46         session['currentEditor'] = self.pathInfo 
    47         session.save() 
     44        logging.debug("Saving current page url - to keep track of atom editor state") 
     45        if hasattr(self, 'pathInfo'): 
     46            logging.debug(" - page: %s" %self.pathInfo) 
     47            session['currentEditor'] = self.pathInfo 
     48            session.save() 
     49             
    4850        logging.debug("Now rendering current template (%s)" %template) 
    4951        try: 
  • MILK/trunk/milk_server/milk_server/controllers/trackback/trackback.py

    r5076 r5261  
    6464            err = "Could not find document, '%s', to reference in trackback" %uri 
    6565         
    66         server = self.cf.get('DEFAULT', 'mailserver') 
    67         if server is None: 
     66        if not g.mailServer: 
    6867            err = 'Server not configured for trackback (no mail server)' 
    6968         
    70         recipient=self.cf.get('DEFAULT', 'tbrecipient') 
    71         if recipient is None: 
     69        if not g.tbRecipient: 
    7270            err = 'Server not configured for trackback (no recipient)' 
    7371         
     
    7977            c.err = 1 
    8078        else:  
    81             logging.info("Sending notification mail to '%s'" %recipient) 
    82             status, message = mailHandler([recipient,], body, body, 
     79            logging.info("Sending notification mail to '%s'" %g.tbRecipient) 
     80            status, message = mailHandler([g.tbRecipient,], body, body, 
    8381                                          xmlAttachments = [(payload, name),], 
    84                                           server = server) 
     82                                          server = g.mailServer) 
    8583            if status: 
    8684                c.err = 0 
     
    159157            %(self.inputs['Identifier'],self.inputs['Submitter'],self.inputs['Comment']) 
    160158        name = 'ChangeRequestFor_%s.xml' %uri 
    161         maintainer = self.cf.get('DEFAULT','metadataMaintainer') 
    162159         
    163         logging.info("Mailing corrections info to %s" %maintainer) 
    164         status,message = mailHandler([maintainer,], 
     160        logging.info("Mailing corrections info to %s" %g.metadataMaintainer) 
     161        status,message = mailHandler([g.metadataMaintainer], 
    165162                                     body,body,xmlAttachments=[(payload,name),], 
    166                                      server=self.cf.get('DEFAULT','mailserver')) 
     163                                     server = g.mailServer) 
    167164        if status: 
    168165            result = 'Your comments have been sent to the metadata maintainer and added to corrections feed for the metatadata database.' 
     
    173170        logging.info("- now add these to the corrections feed") 
    174171        # add the maintainer info 
    175         body += " - for metadata maintainer, '%s'" %maintainer  
     172        body += " - for metadata maintainer, '%s'" %g.metadataMaintainer  
    176173        g.validator._atomClient.feedClient.createGenericFeedEntry(body, payload, 
    177174                                                                  dc.CORRECTIONS_COLLECTION_PATH) 
  • MILK/trunk/milk_server/milk_server/models/DiscoveryState.py

    r4469 r5261  
    22import cgi,urllib 
    33class constraints: 
    4     ''' A container object for constraints on a search ''' 
     4    '''  
     5    A container object for constraints on a search  
     6    ''' 
    57    def __init__(self, **kw): 
    68        self.values={} 
    79        keys=['dateRange','bbox','scope','textTarget','searchString','geoSearchType'] 
    8         for key in keys:self.values[key]=None 
    9         print kw 
    10         for k in kw: self[k]=kw[k] 
     10        for key in keys: 
     11            self.values[key]=None 
     12        for k in kw:  
     13            self[k]=kw[k] 
    1114             
    12     def __setitem__(self,key,value): 
     15             
     16    def __setitem__(self, key, value): 
    1317        if key in self.values: 
    1418            self.values[key]=value 
    1519        else: 
    1620            raise ValueError('Unknown key [%s] in constraints') 
     21 
    1722     
    1823    def __getitem__(self,key): 
     
    2126        else: 
    2227            raise ValueError('Unknown key [%s] in constraints'%key) 
     28 
    2329     
    2430    def __str__(self): 
    2531        c='' 
    2632        if self.values['dateRange'] is not None: 
    27             dr=self.values['dateRange']  
    28             c+='Including&nbsp;%s&nbsp;to&nbsp;%s; '%('%s,%s,%s'%dr[0],'%s,%s,%s'%dr[1]) 
     33            dr = self.values['dateRange']  
     34            c += 'Date range: %s to %s'%('%s/%s/%s'%dr[0],'%s/%s/%s'%dr[1]) 
    2935        bbox=self.values['bbox'] 
     36        if c: 
     37            c += ', ' 
     38        c += 'Bounding box: ' 
    3039        if bbox is None or bbox==['90.0','-180.0','180.0','-90.0']: 
    31             c+='Global; ' 
     40            c+='Global' 
    3241        else: 
    33             overlaps=self.values['geoSearchType'] or 'overlaps' 
    34             c+='which %s latitude %s to %sN and longitude %s to %sE; '%( 
    35                 overlaps,bbox[3],bbox[0],bbox[1],bbox[2]) 
    36             #c+='Bounding&nbsp;Box:&nbsp;%sN,%sW,%sE,%sS; '%tuple(bbox) 
    37         if self.values['scope'] is not None or self.values['textTarget'] is not None: 
    38             c+='Restricted to ' 
    39         if self.values['scope'] is not None: c+='%s; '%self.values['scope'] 
    40         if self.values['textTarget'] is not None: c+='%s; '%self.values['textTarget'] 
    41         return c[:-2] 
     42            overlaps = self.values['geoSearchType'] or 'overlaps' 
     43            c+='%s latitude %s to %sN and longitude %s to %sE' \ 
     44                %(overlaps,bbox[3],bbox[0],bbox[1],bbox[2]) 
     45        if self.values['scope']:  
     46            c += 'Scope: %s'%self.values['scope'] 
     47        if self.values['textTarget']: 
     48            c += 'Target: %s'%self.values['textTarget'] 
     49        return c 
     50 
    4251 
    4352class DiscoveryState: 
     
    5968        self.searchString=searchString 
    6069        self.alternatives=None 
     70 
     71     
    6172    def geturl(self,**kw): 
    62         ''' Get a url from the wsgi environment, modified by the keyword arguments offset and stride  
    63         which are to be part of the querystring ''' 
     73        '''  
     74        Get a url from the wsgi environment, modified by the keyword arguments  
     75        offset and stride which are to be part of the querystring  
     76        ''' 
    6477        args=dict(parse_querystring(self.environ)) 
    6578        offset,stride=kw.get('offset'),kw.get('stride') 
     
    7790        #url=cgi.escape(url) 
    7891        return url 
     92 
     93     
    7994    def getNext(self): 
    8095        ''' Get the next slice ''' 
  • MILK/trunk/milk_server/milk_server/templates/layout.html

    r5201 r5261  
    66<xi:include href="vocabsearch/vocabsearch.html" /> 
    77--> 
    8 <?python from milk_server.controllers.atom_editor.editorconstants import *?> 
     8<?python  
     9from milk_server.controllers.atom_editor.editorconstants import * 
     10from milk_server.controllers.browse.browserconstants import * 
     11?> 
    912 
    1013  <py:match path="head" once="true"> 
     
    3033      <div py:replace="header()"/> 
    3134      ${Markup(c.tbinfo)} 
    32       <div py:if="g.discoveryEnabled"> 
     35      <div py:if="g.discoveryEnabled and c.title != DISCOVERY_HOME_TITLE"> 
    3336                  <span py:with="panelTab = select('panelTab/text()')" py:strip=""> 
    3437                    <py:if test="panelTab"> 
     
    5154                            </td></tr> 
    5255                    </table> 
     56                <div id="loading" class='hidden'><img src="$g.loadingIcon" alt="Loading" />Please wait...</div> 
     57                    ${select('*[local-name()!="panelTab" and local-name()!="includeCorrectionLink"]')} 
    5358                        <div class="javascriptWarning"><script type="text/javascript"><!-- 
    5459                        document.write("<div style='display:none' id='moreinfo'>"); 
     
    5762                        document.write("</div>"); 
    5863                        //--></script></div> 
    59                 <div id="loading" class='hidden'><img src="$g.loadingIcon" alt="Loading" />Please wait...</div> 
    60                     ${select('*[local-name()!="panelTab" and local-name()!="includeCorrectionLink"]')} 
    6164      </div> 
    6265          <span py:with="corrLink = list(select('includeCorrectionLink/text()'))" py:strip=""> 
  • MILK/trunk/milk_server/milk_server/templates/ndgPage.html

    r5171 r5261  
    6363        <table width="100%"><tr><td align="left"> 
    6464        <form action="$g.discoveryURL"> 
    65             Browse text search<span py:replace="helpIcon('nts_help', 'div')"/>&nbsp; 
     65            Browse text search<span py:replace="helpIcon('browse_help', 'div')"/>&nbsp; 
    6666            <input type="text" size="25" name="searchString"/> 
    6767            <input type="submit" value="Search"/> 
     
    7171         </td> 
    7272                </tr></table> 
    73           <div id="nts_help" class="hidden"> 
     73          <div id="browse_help" class="hidden"> 
    7474            <div class="helptxt"><p>  
    7575            This will do a completely new text search against the local browse documents.  
     
    171171    </span> 
    172172     
     173 
     174    <div py:if="c.errors" class="error" py:def="displayErrors()"> 
     175        <h3>Input error</h3> 
     176        <table align="center"> 
     177        <span py:for="key, errors in c.errors.items()" py:strip=""> 
     178                <tr py:for="i, error in enumerate(errors)"> 
     179                        <td py:if="i == 0">${Markup(key)}</td> 
     180                        <td py:if="i != 0" /> 
     181                        <td align="left">${Markup(error)}</td> 
     182                </tr> 
     183        </span> 
     184        </table> 
     185    </div> 
    173186     
    174187     
  • MILK/trunk/milk_server/milk_server/templates/utils.html

    r5234 r5261  
    109109     
    110110    <!--! Help Icons --> 
    111     <span py:def="helpIcon(value, elementType)"> 
     111    <span py:def="helpIcon(value, elementType = 'div')"> 
    112112        <span> 
    113113            <a href="javascript:;" title="Toggle help" onclick="toggleDiv(1,'$value','shown','hidden','$elementType'); return false;"> 
  • MILK/trunk/milk_server/milk_server/templates/vocabsearch/vocabsearch.html

    r5201 r5261  
    44        <xi:include href="../utils.html" /> 
    55 
    6         <div py:def="vocabSearchAutoComplete()"> 
     6        <div py:def="vocabSearchAutoComplete(showInfo = True)"> 
    77 
    88                <link rel="stylesheet" type="text/css" href="$g.server/js/yui/autocomplete/assets/skins/sam/autocomplete.css" /> 
     
    1616                <table class="yui-skin-sam"> 
    1717                <tr> 
    18                         <th>Vocab Term:<span py:replace="helpIcon('vts_help', 'div')"/>&nbsp;</th> 
     18                        <th py:if="showInfo">Vocab Term:<span py:replace="helpIcon('vts_help', 'div')"/>&nbsp;</th> 
    1919                        <td> 
    2020                                <div id="myAutoComplete"> 
    21                                 <input id="myInput" type="text" >type in some text here to search for vocab terms</input> 
    22                                 <div id="myContainer" /> 
     21                                <input id="myInput" type="text" name="vocabTerm">type in some text here to search for vocab terms</input> 
     22                                <div id="myContainer" name="vocabTerm2" /> 
    2323                                </div> 
    2424                        </td> 
  • MILK/trunk/milk_server/setup.py

    r4837 r5261  
    1010    #author_email="", 
    1111    url="http://ndg.nerc.ac.uk/", 
    12     install_requires=["Pylons>=0.9.6",  
     12    install_requires=["Pylons>=0.9.6", 
     13                                                "genshi", 
    1314                      "TurboKid",  
    1415                      "ndgCommon" 
Note: See TracChangeset for help on using the changeset viewer.