source: TI03-DataExtractor/trunk/cgi/dxui @ 1109

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI03-DataExtractor/trunk/cgi/dxui@1109
Revision 1109, 19.7 KB checked in by astephen, 13 years ago (diff)

Stable-ish version with fully-ish working dxc client.

  • Property svn:executable set to *
Line 
1#!<YOUR_PYTHON_HERE>
2#   Copyright (C) 2004 CCLRC & NERC( Natural Environment Research Council ).
3#   This software may be distributed under the terms of the
4#   Q Public License, version 1.0 or later. http://ndg.nerc.ac.uk/public_docs/QPublic_license.txt
5
6"""
7dxui
8====
9
10Holds the CGIClient class the users interact with if they
11are calling the DX web service via a CGI interface through a
12web browser. Typically this script will be called "dxui".
13
14"""
15
16# Import standard library modules
17import cgi, random, time, re
18
19# Import SOAP library
20from ZSI.client import Binding
21       
22# Import package modules
23from pydxc import *
24
25# Set up CGI error reporting if DEBUG is set to 1 in clientConfig.py module
26if DEBUG==1:
27    import cgitb
28    cgitb.enable()
29
30
31class DXCGIClient:
32    """
33    The controlling class for interacting with the dx via the web.
34    """
35   
36    def __init__(self):
37        """
38        Initiates the instance setting up the appropriate
39        internal objects and calls to the relevant classes depending
40        on the configuration and arguments provided.
41        """
42        self.username="undefined"
43        self.password="undefined"
44        self.secureToken="undefined"
45        self.userRoles=[]
46        self.loginStatus="out"
47        self.loginMessage=""
48        self.sessionObject={}
49       
50        self.displayer=DisplayManager()
51
52        # Parse the arguments
53        try:
54            self._parseArgs()
55        except:
56            if DEBUG==1:
57                raise
58            else:
59                CGIErrorHandler("Error parsing arguments: "+str(sys.exc_type), noheader=0)
60
61        # Destroy any arguments not intended to pass any further
62        try:
63            self._destroyUnnecessaryArgs()
64        except:
65            if DEBUG==1:
66                raise
67            else:
68                CGIErrorHandler("Error destroying arguments: "+str(sys.exc_type), noheader=0)
69
70        # If switched on check security       
71        if RESTRICTED_DATA==1:
72            try:
73                self._checkSecurity()
74            except:
75                if DEBUG==1:
76                    raise
77                else:
78                    CGIErrorHandler("Security error: "+str(sys.exc_type), noheader=0)
79        else:
80            self.secureToken="undefined"
81           
82        # Call the dx Web Service with the arguments provided       
83        try:
84            self._callDXWebService(self.args)   
85        except SystemExit:
86            pass         
87        except:
88            if DEBUG==1:
89                raise
90            else:
91                CGIErrorHandler("Error calling dx Web Service: "+str(sys.exc_type), noheader=0)       
92
93        try:   
94            self.displayer._displayHTMLFooter()
95        except:
96            if DEBUG==1:
97                raise
98            else:
99                CGIErrorHandler("Error generating HTML footer: "+str(sys.exc_type), noheader=0)
100       
101       
102    def _parseArgs(self):
103        """
104        Parses the arguments sent, if any, ready for sending to the
105        web service.
106        """
107        # Get arguments
108        self.args={}                           
109        args=cgi.FieldStorage()
110       
111        # Get upload file link
112        if args.has_key("uploadedFile"):
113            self.tempFileLink=args["uploadedFile"].file
114            self.args["fileName"]=args["uploadedFile"].filename
115
116        # Populate the argument dictionary
117        for key in args.keys():
118            if key=="uploadedFile": continue
119            self.args[key]=args.getvalue(key)
120                   
121        if self.args.has_key("sessionID") and self.args["sessionID"]=="undefined":
122            self.args["sessionID"]="undefined"     
123         
124        # Check for secure items, destroy if necessary and assign as instance variables   
125        if self.args.has_key("yousirnaim"):
126            self.username=self.args["yousirnaim"]
127            self.args["username"]=self.username
128            del self.args["yousirnaim"]
129           
130        if self.args.has_key("parcewerd"):
131            self.password=self.args["parcewerd"]
132            del self.args["parcewerd"] 
133               
134        if self.args.has_key("secureToken"):
135            self.secureToken=self.args["secureToken"]
136            del self.args["secureToken"]   
137       
138        if not self.args.has_key("username"):
139            self.args["username"]="undefined"
140           
141        #self._determineNumberOfDatasets()
142        #self._parsePartialDimensionArgs()
143       
144        self._compileAxisSelections()
145       
146
147    def _compileAxisSelections(self):
148        """
149        Compiles partial selections from the last form selections
150        so that axis start and end components are linked into a
151        list of arguments and string components become whole date/time
152        strings.
153        """
154        #print "Content-Type: text/html\n\n"
155        #print "<P>".join(["%s:%s" % (n,v) for (n,v) in self.args.items()])     
156        dateTimeTemplate="%.4d-%.2d-%.2dT%.2d:%.2d:%f"
157        timeAxisItems=getDictSubsetMatching(self.args, "axis_.*_low\.time\.year", "regex")
158       
159        # Must have come from dxui so if year is there all others will be
160        for name, value in timeAxisItems.items():
161            axisIndex=re.match(r"axis_(.*)_low\.time\.year", name).groups()[0]
162            tcList=[]
163            for lh in ("low", "high"):
164                for t in "year month day hour minute second".split():
165                    argName="axis_%s_%s.time.%s" % (axisIndex, lh, t)
166                    tcList.append(self.args[argName])
167                    del self.args[argName]
168       
169            # Now compile into start and end date time
170            floatList=[float(i) for i in tcList]
171            self.args["axis_%s" % axisIndex]=[dateTimeTemplate % tuple(floatList[:6]),
172                                              dateTimeTemplate % tuple(floatList[6:])]
173
174        otherAxisItems=getDictSubsetMatching(self.args, "axis_.*_low$", "regex")
175        for name, value in otherAxisItems.items():
176            axisIndex=re.match(r"axis_(.*)_low$", name).groups()[0]
177            iList=[]
178            for lh in ("low", "high"):
179                argName="axis_%s_%s" % (axisIndex, lh)
180                iList.append(self.args[argName])
181                del self.args[argName]
182       
183            try:
184                iList=[float(i) for i in iList]
185            except:
186                try:
187                    iList=[int(i) for i in iList]
188                except:
189                    pass
190           
191            self.args["axis_%s" % axisIndex]=iList
192       
193        #print "Content-Type: text/html\n\n"
194        #print "<P>".join(["%s:%s" % (n,v) for (n,v) in self.args.items()])
195       
196                   
197    def DEPRECATED_determineNumberOfDatasets(self):
198        """
199        Returns the number of datasets the user is requesting.
200        This affects how the output is produced.
201        """
202        ndsetPattern=re.compile(r"^\w+_(\d+)$")
203        self.numberOfDatasets=1  # Dummy value for now
204       
205        for key in self.args.keys():
206            match=ndsetPattern.match(key)
207            if match:
208                dsetNumber=int(match.groups()[0])
209                if dsetNumber>self.numberOfDatasets:
210                    self.numberOfDatasets=dsetNumber
211
212        if self.args.has_key("numberOfDatasets"):
213            try:
214                self.numberOfDatasets=int(self.args["numberOfDatasets"])
215            except:
216                self.numberOfDatasets=int(self.args.getvalue("numberOfDatasets"))       
217
218
219    def DEPRECATED_parsePartialDimensionArgs(self):
220        """
221        Parses dimension arguments so that they are of the
222        correct types to send to the server.
223        """                 
224        # Make a list of all the possible time keys to parse in time args
225        allTimeKeys=createTimeKeyList()
226                       
227        timeKeyCount=0   
228        for key in self.args.keys():
229            if key in allTimeKeys:
230                # Make sure it is an integer
231                try:
232                    self.args[key]=int(self.args[key])
233                    timeKeyCount=timeKeyCount+1
234                except:
235                    pass                   
236            elif key in HORIZ_KEYS:
237                try:
238                    self.args[key]=float(self.args[key])
239                except:
240                    pass
241               
242        if timeKeyCount>0:
243            self._compileDateTimeArgs()             
244                   
245
246    def DEPRECATED_compileDateTimeArgs(self):
247        """
248        Converts a list of arguments for each date and time component
249        to a start and end date/time following the xsd:dateTime description.
250        """
251        dateTimeString="%.4d-%.2d-%.2dT%.2d:%.2d:%.2d"
252        # Build item lists of year, month, day...second for each dataset
253        # both start and end date times.
254        # Also delete the original component items for clarity.
255        for n in range(1, self.numberOfDatasets+1):
256            startList=[]
257            endList=[]
258            for tk in TIME_KEYS:
259                startList.append(self.args["start%s_%s" % (tk, n)])
260                del self.args["start%s_%s" % (tk, n)]
261                endList.append(self.args["end%s_%s" % (tk, n)])
262                del self.args["end%s_%s" % (tk, n)]             
263           
264            self.args["startDateTime_%s" % n]=dateTimeString % tuple(startList)
265            self.args["endDateTime_%s" % n]=dateTimeString % tuple(endList)
266           
267
268    def _destroyUnnecessaryArgs(self):
269        """
270        Destroy the arguments that should not be passed to the main dx Web Service.
271        """
272        if self.args.has_key("password"):
273            del self.args["password"]   
274       
275       
276    def _checkSecurity(self):
277        """
278        If security is switched on with RESTRICTED_DATA=1 then this will
279        call the local implementation of the security.
280        """
281        secChecker=SecurityViaCGI(self.username, self.password, self.secureToken)
282       
283        # Deal with logout
284        ############# NOTE - doesn't destroy session server side (yet)
285        if self.args.has_key("logout") and self.args["logout"]=="Logout":
286            secChecker.logout()
287            secCheck="You have been logged out."
288        else:
289            secCheck=secChecker.validate()         
290           
291        if type(secCheck)==type(""):
292            # Returned string means error in log in or logged out
293            self.loginMessage=secCheck
294        else:
295            self.loginStatus="in"
296            self.loginMessage=""
297            (self.secureToken, self.username, self.userRoles)=secCheck   
298
299               
300    def _callDXWebService(self, args):
301        """
302        According to the arguments given this binds to an appropriate
303        Web Service and calls it with the relevant arguments.
304        """
305        # Just print the login page if not logged in and login required
306        if RESTRICTED_DATA==1 and self.loginStatus=="out":
307            self.displayer._displayHTTPHeader()
308            self.displayer._displayHTMLHeader()
309            self.displayer._displayIntroduction()           
310            self.displayer._displayLoginBar(self.username, self.loginStatus, self.loginMessage)
311            return     
312             
313        # Set up SOAP bindings 
314        if CALL_METHOD.upper()=="WS":
315            self.server=WSCaller()
316        elif CALL_METHOD.upper()=="LOCAL":
317            serverLocation=LOCAL_SERVER_PACKAGE
318            sys.path.append(serverLocation)
319            print sys.path
320            self.server=LocalCaller()
321
322        # Get session ID
323        self._getSession()
324       
325        # Check if complete
326        isCompleteStatus=self._checkIfComplete()
327       
328
329        # Perform actions as requested by user
330        if self.args.has_key("action"):
331            actionReturn=self._performActions()
332            if actionReturn=="Display footer then complete":
333                return
334       
335        # Clear request if required
336        if self.args.has_key("clearRequest") or self.args.has_key("newRequest"):
337            if self.args.has_key("clearRequest"):
338                del self.args["clearRequest"]
339            else:
340                del self.args["newRequest"] 
341            self.sessionID, self.secureToken=self.server.callServerMethod("newRequest",
342                                                  [self.sessionID, self.secureToken])                                           
343
344        # Make sure instance and args sessionID are the same
345        self.args["sessionID"]=self.sessionID
346       
347        # Ensure args contains secureToken object
348        if not self.args.has_key("secureToken"):
349            self.args["secureToken"]=self.secureToken
350
351        # Get the options (and make selections if appropriate)
352        (optionCategories, options, optionStrings, summaryString, secureToken)=self._getLatestOptions()
353       
354        # Get summary of request and determine number of datasets   
355        #self.summary=self.server.callServerMethod("summariseRequest", [self.sessionID, self.secureToken])[0]
356        """numDatasetsMatch=re.search(r"numberOfDatasets:\t(\d+)", self.summary)
357        if numDatasetsMatch:
358            self.numberOfDatasets=int(numDatasetsMatch.groups()[0])"""
359           
360        # Analyse the request
361        self._respondToOptionCategories(optionCategories, options, optionStrings, summaryString)       
362   
363       
364    def _getSession(self):
365        """
366        Checks if a session is already underway and starts one if not.
367        """           
368        # If no session then start a session and get a sessionID
369        if not self.args.has_key("sessionID") or self.args["sessionID"] in (None, "None", "undefined"):
370            # Start session if not known about
371            self.sessionID, self.secureToken=self.server.callServerMethod("startSession",
372                                                 [self.username, self.password, self.secureToken])[0]
373        else:
374            self.sessionID=self.args["sessionID"]
375           
376           
377    def _checkIfComplete(self):
378        """
379        Checks if complete, returns 1 (yes) or 0 (no).
380        """
381        isCompleteStatus=0       
382        if self.args.has_key("isComplete"):     
383            isCompleteStatus, self.secureToken=self.server.callServerMethod("isComplete",
384                                                      [self.sessionID, self.secureToken])     
385        return isCompleteStatus
386       
387       
388    def _performActions(self):
389        """
390        If "action" argument received then do the appropriate action.
391        """     
392        if 1:  # Move all back <-- one tab and delete this line
393            action=self.args["action"]
394            if action=="viewRequestSummary":
395                summary=self.server.callServerMethod("summariseRequest", [self.sessionID, self.secureToken])[0]
396                self.displayer._displayHTTPHeader()
397                self.displayer._displayHTMLHeader()
398                self.displayer._displayRequestSummaryTable(summary)
399                self.displayer._displayReturnLine(self.sessionID)
400                return "Display footer then complete"
401               
402            elif action=="saveRequest":
403                dataSubsetSpecifier, self.secureToken=self.server.callServerMethod("getDataSubsetSpecifier",
404                                                           [self.sessionID, self.secureToken])
405                self.displayer._displayHTTPHeader()
406                self.displayer._displayHTMLHeader()             
407                self.displayer._displaySaveRequestOptions(dataSubsetSpecifier, self.sessionID)
408                self.displayer._displayReturnLine(self.sessionID)
409                return "Display footer then complete"
410               
411            elif action=="uploadRequest":
412                self.displayer._displayHTTPHeader()
413                self.displayer._displayHTMLHeader()
414                self.displayer._displayUploadRequestOptions(self.sessionID)
415                self.displayer._displayReturnLine(self.sessionID)               
416                return "Display footer then complete"           
417               
418            elif action=="parseUploadedFile":
419                xmlFileString=self.tempFileLink.read()
420                status, self.secureToken=self.server.callServerMethod("uploadRequest",
421                                              [self.sessionID, xmlFileString, self.secureToken])
422       
423         
424    def _getLatestOptions(self):
425        """
426        If target page provided then get the options for that page,
427        otherwise get whatever options are presented by the dx and
428        send latest selections to update request.
429        """
430        if self.args.has_key("targetPage"):
431            tp=self.args["targetPage"]
432            tp2catMap={"DatasetGroupPage":"datasetGroup", "DatasetPage":"dataset",
433                       "VariablesPage":"variable", "DomainPage":"horizontalDomain"}
434            optionCategory=tp2catMap[tp]
435            response=self.server.callServerMethod("getOptions", [self.sessionID, self.secureToken, optionCategory])
436        else: 
437            response=self.server.callServerMethod("selectOptions", [self.sessionID, self._packArgsAsList(self.args)])
438       
439        #self.displayer._displayHTTPHeader()
440        (optionCategories, options, optionStrings, summaryString, secureToken)=self._translateResponse(response)       
441        #print (optionCategories, options, optionStrings, summaryString, secureToken)   
442        return (optionCategories, options, optionStrings, summaryString, secureToken)
443
444   
445    def _translateResponse(self, response):
446        """
447        Takes an object received from the WS and translates to:
448        (optionCategories, options, optionStrings, secureToken)
449        """
450        response=response[0][0][0]
451        optionCategories=[item[0] for item in response[:-2]]
452        options=[item[1] for item in response[:-2]]
453        optionStrings=options
454        summaryString=response[-2]
455        secureToken=response[-1]               
456        return (optionCategories, options, optionStrings, summaryString, secureToken)
457       
458
459    def _respondToOptionCategories(self, optionCategories, options, optionStrings, summaryString):
460        """
461        Work out what the option category is and respond by displaying
462        the appropriate user interface.
463        """     
464        summaryDict=createSummaryDict(summaryString)
465        # Analyse the option categories now
466        if optionCategories=="No category" or optionCategories==[]:
467            optcat=None
468        else:
469            optcat=optionCategories[0].split("_")[0]
470       
471        # Can display simple form for these categories 
472        if optcat in ("datasetGroup", "dataset", "variable"):           
473            self.displayer._displayHTTPHeader()
474            self.displayer._displayHTMLHeader()     
475            if RESTRICTED_DATA==1: self.displayer._displayLoginBar(self.username, loginStatus="in", loginMessage="with roles: "+str(self.userRoles))
476            self.displayer._displayDatasetSummaryLine(summaryString, optionCategories[0], self.sessionID)
477            self.displayer._displayMainTableHeader(self.sessionID)
478            self.displayer._displayOptionsTable(summaryDict, optionCategories, options, optionStrings, self.sessionID, self.sessionObject)
479            self.displayer._displayMainTableFooter()
480       
481        # Need to make a number of web service calls to get information
482        # for each domain as well as output format which are all
483        # displayed on the same page.   
484        elif optcat=="axis":
485            self.displayer._displayHTTPHeader()
486            self.displayer._displayHTMLHeader()
487            self.displayer._displayDatasetSummaryLine(summaryString, optionCategories[0], self.sessionID)
488            self.displayer._displayMainTableHeader(self.sessionID)
489            self.displayer._displayDomainOptions(summaryDict, optionCategories, options, optionStrings, self.sessionID)     
490            self.displayer._displayMainTableFooter()
491       
492        elif optcat=="horizontalDomain":
493            self.displayer._displayHTTPHeader()
494            self.displayer._displayHTMLHeader()
495            self.displayer._displayDatasetSummaryLine(summaryString, optionCategories[0], self.sessionID)
496            self.displayer._displayMainTableHeader(self.sessionID)         
497            print "<P><B>SPATIAL AND TEMPORAL SELECTION</B><P>"     
498            self.displayer._displayHorizontalDomainOptions(options)
499           
500            # Now get the vertical domain options
501            self.args["optionCategoryRequested"]="verticalDomain"       
502            (optionCategories, options, optionStrings, secureToken)=self.server.callServerMethod("selectOptions",
503                                                                         [self.sessionID, self._packArgsAsList(self.args)])
504            self.displayer._displayVerticalSpatialDomainOptions(options)
505
506            # Now get the temporal domain options
507            self.args["optionCategoryRequested"]="temporalDomain"           
508            (optionCategories, options, optionStrings, secureToken)=self.server.callServerMethod("selectOptions",
509                                                                         [self.sessionID, self._packArgsAsList(self.args)])
510            self.displayer._displayTemporalDomainOptions(options)
511
512            # Now get the output format options
513            self.args["optionCategoryRequested"]="outputFormat"             
514            (optionCategories, options, optionStrings, secureToken)=self.server.callServerMethod("selectOptions",
515                                                                         [self.sessionID, self._packArgsAsList(self.args)])         
516            self.displayer._displayOutputFormatOptions(options, self.sessionID)   
517       
518        # If there are no option categories then the request is likely to be complete
519        elif optcat==None:
520       
521            # Display confirmation page if needed
522            if CONFIRMATION_PAGE==1 and not self.args.has_key("confirm"):
523                self.displayer._displayHTTPHeader()
524                self.displayer._displayHTMLHeader()
525                self.displayer._displayDatasetSummaryLine(summaryString, optionCategories, self.sessionID)         
526                self.displayer._displayMainTableHeader(self.sessionID)   
527                print '<INPUT NAME="action" TYPE="hidden" VALUE="requestCosts">'
528                self.displayer._displayConfirmationSection(summaryDict)
529               
530            # Or display final job processing page     
531            elif self.args.has_key("action") and self.args["action"]=="requestCosts":   
532                (estimatedDuration, estimatedVolume)=self.server.callServerMethod("getExtractionCosts",
533                                                          [self.sessionID, self.secureToken])[0][0:2]
534                self.displayer._displayHTTPHeader()
535                self.displayer._displayHTMLHeader()
536                self.displayer._displayProcessingSection(estimatedDuration, estimatedVolume, self.sessionID)
537                pathList, self.secureToken=self.server.callServerMethod("createOutput", [self.sessionID, self.secureToken])[0]         
538                self.displayer._displayOutputFileList(pathList[0][0])   
539
540
541    def _packArgsAsList(self, args):
542        """
543        In order to work with ZSI SOAP library need to pack up arguments as a list
544        of [keyword, value] pairs rather than a dictionary.
545        """
546        newList=[]
547        for key, value in args.items():
548            newList.append([key, value])
549        return newList
550       
551
552if __name__=="__main__":
553
554    DXCGIClient()       
Note: See TracBrowser for help on using the repository browser.