source: MILK/trunk/milk_server/milk_server/controllers/atom_editor/editatom.py @ 4594

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/MILK/trunk/milk_server/milk_server/controllers/atom_editor/editatom.py@4594
Revision 4594, 27.4 KB checked in by cbyrom, 11 years ago (diff)

Improve look + feel - esp. on windows IE + pass debug flag into
validator + properly handle empty author content.

Line 
1'''
2 Class representing pylons controller for the creation and editing of atom
3 data
4 
5 @author: C Byrom, Tessella Sep 2008
6'''
7import logging, traceback, sys, cgi, xmlrpclib
8from paste.request import parse_querystring
9from xml.parsers.expat import ExpatError
10from formencode import Invalid
11from genshi.filters import HTMLFormFiller
12from genshi import HTML
13from milk_server.lib.base import *
14from milk_server.models.form import *
15from milk_server.lib import mailer
16from milk_server.lib.ndgInterface import interface
17import milk_server.lib.htmlUtilities as utils
18from milk_server.lib.atomutilities import savePageAndRender
19from ndgUtils import ndgObject
20from ndgUtils.models.Atom import Atom, Person, Link, Category
21from ndgUtils.eXistConnector import eXistConnector
22from ndgUtils.lib.atomvalidator import ValidationError
23import ndgUtils.lib.existdbclient as edc
24from ndgUtils.models.MolesEntity import MolesEntity as ME
25from ndgUtils.lib.utilities import escapeSpecialCharacters
26from ndgUtils.vocabtermdata import VocabTermData as VTD
27from ndgUtils.lib.granulite import granulite
28
29class EditatomController(BaseController):
30    '''
31    Provides the pylons controller for editing NDG Atom documents.
32    '''
33    ADD_ASSOCIATIONS = 3
34    REMOVE_ASSOCIATIONS = 4
35   
36    def __setup(self,uri=None):
37        ''' Common setup stuff for all the actions on this controller '''
38        logging.info("Setting up EditatomController")
39        self.cf=request.environ['ndgConfig']
40
41        if uri:
42            try:
43                self.ndgObject = ndgObject(uri, config=self.cf)
44            except ValueError,e:
45                return e
46
47        self.inputs=dict(parse_querystring(request.environ))
48
49        logging.info("EditatomController set up")
50        return 0
51
52
53    def __unpackErrors(self, e):
54        '''
55        Add exception errors to the common error structures - for use
56        in templates
57        @param e: Exception to add
58        '''
59        errorMessage = e.message
60        if g.debugAtomEditor:
61            errorMessage = traceback.format_exc()
62
63        c.xml = escapeSpecialCharacters('Unexpected error loading page [%s]' \
64                                        %str(errorMessage))
65        c.doc = ''
66       
67        # unpack errors, if possible - NB, the c.errors value is used by the template
68        # function, displayErrors, which is used by most of the editor templates
69        if isinstance(e, xmlrpclib.Fault):
70            # strip out the exception type - NB, this is usually native library code
71            # and is of no real interest - and will just confuse viewers
72            c.errors['Unexpected error'] = e.faultString.split(':')[-1] 
73        if hasattr(e, 'unpack_errors'):
74            c.errors.update(e.unpack_errors())
75           
76        else:
77            c.errors['Unexpected error'] = [c.xml]
78
79        # tidy up errors - escaping any xml tags, if necessary
80        for key, errors in c.errors.items():
81            newErrors = []
82            for error in errors:
83                for err in error.split('<br/>'):
84                    newErrors.append(escapeSpecialCharacters(err))
85            c.errors[key] = newErrors
86
87
88    def __handleError(self, e, template='atom_editor/error'):
89        '''
90        Handle exceptions thrown; if debug mode on, display full stack trace
91        in output, otherwise just show basic error message in error template
92        @param e: Exception to process
93        @keyword template: template to render - 'error' is the default - NB, if
94        an alternative is specified it should have a div with class set to 'error'
95        containing the variable, c.xml to display properly
96        '''
97        self.__unpackErrors(e)
98        logging.error(c.xml)
99        response.status_code = 400
100        return render("genshi", template)
101
102   
103    def upload(self, uri):
104        '''
105        Upload a CSML, CDML or granulite file and store it in the session variable
106        NB, if the uri is specified, we're already dealing with an atom
107        (which this refers to) - so the file is not a granulite - since
108        this is used to create an atom from scratch
109        '''
110        logging.info("Uploading file...")
111        self.__setup(uri=uri)
112        inputs = self.__getTidyInputs()
113       
114        granFile = request.POST.get('upload_granulite')
115        csmlOrCdmlFile = request.POST.get('CSMLOrCDML')
116         
117        c.errors = {}
118        try:
119            useCSMLID = True
120            c.atom = None
121            if uri:
122                self.prepareDataModel(uri)
123                useCSMLID = False
124
125            if (granFile == '' or granFile == None) and \
126                (csmlOrCdmlFile == '' or csmlOrCdmlFile == None):
127                errorMessage = "Error: could not load file - please try again"
128                logging.error(errorMessage)
129                raise IOError(errorMessage)
130            else:
131                # Prepare the basic data model
132                # NB, if loading a granulite, this will create the displayed atom
133                # with the ID taken from the CSML file, if specified
134                fileContents = None
135                if (granFile is not None and granFile != ''):
136                    fileContents = granFile.value
137                   
138                # use the granulite helper class to add either the full granulite
139                # data or just the CSML/CDML data
140                # NB, we'll be creating the atom in the default local eXist
141                eXistClient = self.__getExistClient('local')
142                gran = granulite(fileContents, granuleAtom = c.atom, \
143                                 eXistClient = eXistClient, \
144                                 csmlOrCdmlFile = csmlOrCdmlFile, \
145                                 timeAxis = inputs.get('timeAxis'), \
146                                 datasetID = inputs.get('datasetID'), \
147                                 useCSMLID = useCSMLID)
148
149                # now process the input file and add any extra required data
150                if uri:
151                    c.atom = gran.processCSMLOrCDMLFile()
152
153                    # save new data - NB, for granulites, this is done as part of the
154                    # processing steps
155                    self.saveAtomToExist(c.atom)
156                else:
157                    c.atom = gran.processGranulite()
158
159                    # Now set up the ndgObject with the created atom's vals
160                    self.__setup(uri=c.atom.ndgURI)
161                    c.atom.ndgObject = self.ndgObject
162                           
163                # now do redirection - NB, this ensures that current atom contents are
164                # reloaded and displayed
165                logging.info("File data loaded and extracted to atom")
166        except Exception, e:
167            c.errors['WARNING'] = ['Error loading data: the displayed data will not be saved - please fix problem and retry']
168            self.__unpackErrors(e)
169        except SystemExit, ee:
170            # NB, some of the CSML libraries just sys.exit on problems - catch errors here
171            c.errors['ERROR'] = ['Problem encountered whilst transforming the CDML data into CSML']
172            self.__unpackErrors(ee)
173
174        if c.atom and hasattr(c.atom, 'ndgURI'):
175            self.pathInfo = self.pathInfo.replace('upload', 'editAtom')
176            return self.edit(c.atom.ndgURI)
177        elif uri:
178            # something has gone wrong here...
179            return render("genshi", 'atom_editor/error')
180        else:
181            return self.createGranule()
182
183
184    def __getTidyInputs(self):
185        '''
186        The inputs can be used generically to specify atom attributes - however
187        some inputs should not be set against the Atom object since they use slots
188        so cannot be pickled - which means saving the atom in the session with fail.
189        This method clears out any unwanted inputs
190        '''
191        logging.debug("Getting pickleable input data")
192        inputs = request.params
193        tidyInputs = {}
194        for key, val in inputs.items():
195            if not isinstance(val, cgi.FieldStorage):
196                tidyInputs[key] = val
197        logging.debug("Pickleable data extracted")
198        return tidyInputs
199
200   
201    def saveAtom(self, uri, saveLevel=0):
202        '''
203        Save the atom contents - NB, validation is done by method decoration
204        - if this fails, the action is reran as a GET with htmlfill auto-populating
205        the fields to keep them as they were before the submission
206        '''
207        logging.info("Saving input atom data")
208        c.errors = {}
209        try:
210            self.prepareDataModel(uri)
211        except SystemError, e:
212            return self.__handleError(e)
213       
214        inputs = request.params
215
216        # save atom association changes
217        if int(saveLevel) == self.ADD_ASSOCIATIONS:
218            atomLinks = self.extractAtomAssociations(inputs)
219            c.atom.addUniqueRelatedLinks(atomLinks)
220        elif int(saveLevel) == self.REMOVE_ASSOCIATIONS:
221            atomLinks = self.extractAtomAssociations(inputs)
222            c.atom.removeRelatedLinks(atomLinks)
223        else:
224            authors = self.extractAuthorDetails(inputs)
225            c.atom.addAuthors(authors)
226   
227            onlineRefs = self.extractOnlineReferenceDetails(inputs)
228            c.atom.addOnlineReferences(onlineRefs)
229
230            params = self.extractParameterDetails(inputs)
231            # NB, the atom type and subtype are added to the categories when the
232            # atom is exported to XML - so don't need to worry about overwriting
233            # them now
234            c.atom.parameters = params
235           
236            if inputs.get('subtype'):
237                c.atom.subtype = self.getLatestTermURLFromDropDownInput( \
238                        inputs.get('subtype'))
239                c.atom.subtypeID = c.atom.subtype.split('/')[-1]
240
241        logging.info("Validating input")
242        try:
243            g.validator.setAtom(c.atom)
244            g.validator.validateAtom()
245            logging.info("- input valid")
246
247            self.saveAtomToExist(c.atom)
248        except Exception, e:
249            self.__unpackErrors(e)
250            logging.info("- input invalid")
251            return self.edit(uri)
252                   
253        # now do redirection - NB, this ensures that current atom contents are
254        # reloaded and displayed
255        h.redirect_to(controller = 'atom_editor/editatom', action='edit', \
256                        uri = c.atom.ndgURI)
257
258
259    def prepareDataModel(self, uri):
260        '''
261        Set up the underlying atom data model - loading the bulk from eXist
262        then updating any input fields appropriately
263        '''
264        logging.info("Preparing underlying data model")
265        status=self.__setup(uri=uri)
266        if status:
267            c.xml='<p>%s</p>'%status
268            response.status_code = 400
269            raise SystemError('Problem experienced setting up data model')
270
271        logging.info("Retrieving document to edit")
272        # NB, don't use the cache as docs are likely to change
273        # quite a lot during editing; ensure you've always got
274        # the latest updates loaded
275        status,x = interface.GetXML(uri, useCache=False)
276
277        if not status:
278            code=400
279            if x.startswith('<p> Access Denied'):
280                code=401
281
282            c.xml='%s'%x
283            response.status_code = code
284            raise SystemError('Problem experienced retrieving atom doc from eXist')
285
286        # NB, passing in the inputs will overwrite any original values with the
287        # user input ones
288        inputs = self.__getTidyInputs()
289       
290        c.atom = Atom(xmlString=str(x), ndgObject = self.ndgObject, **dict(inputs))
291       
292        # save the current atom - to avoid this needing be recreated by the
293        # asynch viewAssociatedData call
294        session['currentAtom'] = c.atom
295        session.save()
296        logging.info("Data model set up")
297
298
299    def prepareEditForm(self, uri):
300        '''
301        Get everything set up for displaying the edit form
302        @param uri: ndg url for the atom to load in edit mode
303        '''
304        if not c.errors:
305            c.errors = {}
306
307        try:
308            # NB, can get here directly from saveAtom - if there have been errors
309            # - in this case keep original data
310            if not c.atom:
311                self.prepareDataModel(uri)
312        except SystemError, e:
313            return self.__handleError(e)
314           
315        c.title='Editing [%s]' %c.atom.ndgURI
316        c.uri = c.atom.ndgURI
317       
318        c.saveLink = h.url_for(controller='atom_editor/editatom',action='saveAtom', \
319                               saveLevel='1',  uri = c.atom.ndgURI)
320        c.saveLink2 = h.url_for(controller='atom_editor/editatom',action='saveAtom', saveLevel='2')
321        c.saveAssoc = h.url_for(controller='atom_editor/editatom',action='saveAtom', \
322                                 saveLevel = self.REMOVE_ASSOCIATIONS)
323        c.deploymentsURL = h.url_for(controller='browse/retrieve', \
324                                     action='viewAssociatedData', \
325                                     type = VTD.DEPLOYMENT_TERM, \
326                                     uri = c.atom.ndgURI)
327        c.dataEntitiesURL = h.url_for(controller='browse/retrieve', \
328                                     action='viewAssociatedData', \
329                                     type = VTD.DE_TERM, \
330                                     uri = c.atom.ndgURI)
331       
332        # adjust atom type to cope with activity deployment exception
333        atomType = c.atom.atomTypeID
334        if atomType == g.vtd.ACTIVITY_TERM and \
335            c.atom.subtypeID == g.vtd.DEPLOYMENT_TERM:
336            atomType = g.vtd.DEPLOYMENT_TERM
337       
338        c.addEntityLink = h.url_for(controller='atom_editor/listatom',action='list', searchData = '0', \
339                               associatedAtomID = c.atom.ndgURI, \
340                               associatedAtomType = atomType, 
341                               associationType = utils.ENTITY_ASSOCIATION)
342           
343        c.addGranuleLink = h.url_for(controller='atom_editor/listatom',action='list', searchData = '0', \
344                               associatedAtomID = c.atom.ndgURI, \
345                               associatedAtomType = atomType, 
346                               associationType = utils.GRANULE_ASSOCIATION)
347           
348        c.addDeploymentLink = h.url_for(controller='atom_editor/listatom',action='list', searchData = '0', \
349                               associatedAtomID = c.atom.ndgURI, \
350                               associatedAtomType = atomType, 
351                               associationType = utils.DEPLOYMENT_ASSOCIATION)
352
353        # account for special case where we're dealing with deployments
354        listVals = g.vtd.getValidSubTypes(c.atom.atomTypeID)
355        if c.atom.isDeployment():
356            listVals = [g.vtd.TERM_DATA[g.vtd.DEPLOYMENT_TERM]]
357
358        c.subTypes = utils.getVocabTermDataDropdown(listVals, \
359                                        selected=c.atom.subtype)
360       
361        self.addRelatedLinksDropDowns()
362
363   
364    def edit(self, uri):
365        '''
366        Edit the specified uri
367        '''
368        logging.info("Setting up atom edit template")
369        self.prepareEditForm(uri)
370
371        try:
372            return savePageAndRender(self.pathInfo, "atom_editor/atom_editor")
373       
374        except ExpatError, e:
375            c.xml='XML content is not well formed'
376            c.doc=str(x)
377            logging.error("Error retrieving [%s] - XML content: %s" % (uri, e))
378
379        except Exception, e:
380            errorMessage = traceback.format_exc()
381            c.xml='Unexpected error [%s] viewing [%s]'%(str(e), uri)
382            c.doc=''
383            logging.error(c.xml)
384       
385        response.status_code = 400
386        return render("genshi", 'atom_editor/error')
387
388
389    def addRelatedLinksDropDowns(self):
390        '''
391        Set up the drop down lists required for the selection of online ref links
392        '''
393        # at the very least, we need a simple drop down list with no preselected
394        # values
395        logging.debug("Setting up drop down lists for related links")
396        c.relatedLinkTerms = utils.getVocabTermDataDropdown(\
397                g.vtd.getValidTypes(g.vtd.ONLINE_REF_CATEGORY))
398       
399        c.relatedLinkSelectedLists = {}
400        for link in c.atom.relatedLinks:
401            logging.debug("Adding dropdown for related link, '%s'" %(str(link)))
402            c.relatedLinkSelectedLists[str(link)] = \
403                utils.getVocabTermDataDropdown(g.vtd.getValidTypes(g.vtd.ONLINE_REF_CATEGORY), \
404                                                     selected=link.rel)
405
406        logging.debug("Finished setting up drop down lists")
407
408
409    def extractAuthorDetails(self, inputs):
410        '''
411        Retrieve author data from inputs and set appropriately on Atom, if any
412        found
413        @return: list of Person objects with the author data
414        '''
415        logging.info("Extracting author data from inputs")
416        processedAuthors = []
417        authors = []
418        for key in inputs:
419            keyBits = key.split('.')
420            if len(keyBits) == 3 and keyBits[1] not in processedAuthors:
421               
422                authorType = -1
423                if key.lower().startswith('author'):
424                    authorType = Person.AUTHOR_TYPE
425                elif key.lower().startswith('contributor'):
426                    authorType = Person.CONTRIBUTOR_TYPE
427                elif key.lower().startswith('responsible'):
428                    authorType = Person.RESPONSIBLE_PARTY_TYPE
429                else:
430                    continue
431
432                # NB, adding an empty object here is valid as it will clear out
433                # existing entries, potentially
434                author = Person(personType = authorType)
435                # check if the remove checkbox has been set
436                keyStem = ".".join(keyBits[0:2])
437                if inputs.get(keyStem + ".remove"):
438                    logging.info("Removing author data")
439                else:
440                    author.name = inputs.get(keyStem + '.name') or ""
441                    author.uri = inputs.get(keyStem + '.uri') or ""
442                    author.role = inputs.get(keyStem + '.role') or ""
443                   
444                    logging.info("Adding new author info")
445                    logging.debug("Extracted author (type:'%s', name:'%s', uri:'%s', role:'%s')" \
446                                  %(author.type, author.name, author.uri, author.role))
447                authors.append(author)
448                processedAuthors.append(keyBits[1])
449
450        logging.info("Finished extracting author data")
451        return authors
452
453
454    def extractOnlineReferenceDetails(self, inputs):
455        '''
456        Retrieve online reference data from inputs and set appropriately on Atom, if any
457        found
458        @return: list of Link objects containing the extracted data
459        '''
460        logging.info("Extracting related links data from inputs")
461        processedLinks = []
462        links = []
463
464        for key in inputs:
465            keyBits = key.split('.')
466            if len(keyBits) == 3 and keyBits[1] not in processedLinks:
467               
468                if key.lower().startswith(Atom.ONLINE_REF_LABEL):
469                    link = Link()
470                    keyStem = ".".join(keyBits[0:2])
471                   
472                    if inputs.get(keyStem + ".remove"):
473                        logging.info("Removing online reference data")
474                    else:
475                        # NB, this is in the format vocabURL--termID, so requires further
476                        # processing
477                        link.rel = self.getLatestTermURLFromDropDownInput(inputs.get(keyStem + '.rel'))
478                        link.href = inputs.get(keyStem + '.href') or ""
479                        link.title = inputs.get(keyStem + '.title') or ""
480                       
481                        if not link.hasValue():
482                            continue
483                           
484                        logging.info("Adding new online reference info")
485                        logging.debug("Extracted online reference (href:'%s', title:'%s', rel:'%s')" \
486                                      %(link.href, link.title, link.rel))
487                        links.append(link)
488
489                    processedLinks.append(keyBits[1])
490                else:
491                    continue
492
493        logging.info("Finished extracting links data")
494        return links
495
496
497    def extractParameterDetails(self, inputs):
498        '''
499        Retrieve parameters data from inputs and set appropriately on Atom, if any
500        found
501        @return: list of Category objects containing the extracted data
502        '''
503        logging.info("Extracting parameters data from inputs")
504        processedParameters = []
505        parameters = []
506
507        for key in inputs:
508            keyBits = key.split('.')
509            if len(keyBits) == 3 and keyBits[1] not in processedParameters:
510               
511                if key.lower().startswith(Atom.PARAMETER_LABEL):
512                    parameter = Category()
513                    keyStem = ".".join(keyBits[0:2])
514                   
515                    if inputs.get(keyStem + ".remove"):
516                        logging.info("Removing parameters data")
517                    else:
518                        parameter.term = inputs.get(keyStem + '.term') or ""
519                        parameter.scheme = inputs.get(keyStem + '.scheme') or ""
520                        parameter.label = inputs.get(keyStem + '.label') or ""
521                           
522                        logging.info("Adding new parameter info")
523                        logging.debug("Extracted parameter (vocabURL:'%s', label:'%s', term:'%s')" \
524                                      %(parameter.scheme, parameter.label, parameter.term))
525                        parameters.append(parameter)
526
527                    processedParameters.append(keyBits[1])
528                else:
529                    continue
530
531        logging.info("Finished extracting parameters data")
532        return parameters
533
534
535    def extractAtomAssociations(self, inputs):
536        '''
537        Retrieve atom data from inputs and create related links pointing to
538        this data
539        @return: list of Links representing the related atoms
540        '''
541        logging.info("Extracting related atom ID data from inputs")
542        atoms = []
543        processedAtoms = []
544
545        for key in inputs:
546            if key.lower().startswith(Atom.ATOM_REF_LABEL):
547                (x, href, title, rel) = key.split(Atom.DELIMITER)
548                # NB, we handle removes by effectively ignoring them later on
549                if href not in processedAtoms:
550                    processedAtoms.append(href)
551
552                    link = Link()
553                    link.href = href or ""
554                    link.title = title or ""
555                    link.rel = rel or ""
556                   
557                    # adjust href to point to the view, not the edit version
558                    link.href = link.href.replace('editAtom', 'view')
559                   
560                    logging.debug("Extracted atom info (href:'%s', title:'%s', rel:'%s')" \
561                                  %(link.href, link.title, link.rel))
562                    atoms.append(link)
563            else:
564                continue
565
566        logging.info("Finished extracting atoms data")
567        return atoms
568               
569
570    def getLatestTermURLFromDropDownInput(self, inputVal):
571        '''
572        Term ID and vocabURL are specified in the drop down menus
573        - using the input from this, return the lastest full href to the
574        term ID
575        '''
576        termData = inputVal.split('--')
577        return g.vtd.getCurrentVocabURI(termData[0]) + \
578                        "/" + termData[1]
579
580
581    def saveAtomToExist(self, atom):
582        '''
583        Save the specified atom in eXist
584        @param atom: atom object to save to eXist
585        @return atom: atom object saved in eXist
586        '''
587        logging.info("Saving changes to eXist")
588        eXist = self.__getExistClient(atom.ME.providerID)
589        createdAtom = eXist.createAtomInExist(atom)
590        logging.info("Changes successfully saved to eXist")
591        return createdAtom
592
593
594    def __getExistClient(self, providerID):
595        '''
596        Use the config data to set up and return an eXist client
597        '''
598        logging.info("Setting up eXist client to provider, '%s'" %providerID)
599        # firstly check if there is a current connection available
600        eXistClient = g.eXistDBCons.get(providerID)
601        if eXistClient:
602            logging.info("- found existing client to provider - returning this")
603            return eXistClient
604       
605        # lookup the eXist DB to use according to the provider ID for the
606        # data - NB, this is specified in the milk.config file
607        existHost = self.cf.get('NDG_EXIST', providerID)
608        configFile = self.cf.get('NDG_EXIST','passwordFile')
609        eXistClient = edc.eXistDBClient(eXistDBHostname = existHost, \
610                                       configFile = configFile)
611        # add the client to the global variables for re-use
612        g.eXistDBCons[providerID] = eXistClient
613        logging.info("Returning eXist client")
614        return eXistClient
615
616   
617    def create(self, saveData = None, **inputs):
618        '''
619        Create a new atom
620        '''
621        if saveData:
622            logging.info("Validating input")
623            try:
624                inputs = request.params
625                validator = CreateAtomFormSchema()
626                validator.to_python(inputs)
627                logging.info("- input valid")
628               
629                logging.info("Creating basic atom")
630                self.__setup()
631                atomTypeID = inputs.get('atomTypeID').split('--')[1]
632                inputs['atomTypeID'] = atomTypeID
633   
634                # activity deployments should have subtype deployment specified automatically
635                if atomTypeID == g.vtd.ACTIVITY_DEPLOYMENT_TERM:
636                    inputs['subtypeID'] = g.vtd.DEPLOYMENT_TERM
637                    inputs['atomTypeID'] = g.vtd.ACTIVITY_TERM
638                   
639                inputs['providerID'] = inputs.get('providerID').split('--')[1]
640                atom = self.saveAtomToExist(Atom(**dict(inputs)))
641               
642                h.redirect_to(controller = 'atom_editor/editatom', action='edit',
643                               uri = atom.ndgURI)
644            except Invalid, e:
645                c.errors = e.unpack_errors()
646               
647           
648        logging.info("Setting up atom create template")
649        c.title='Create new atom'
650       
651        # set up the drop down content - NB, add special case, 'deployment activity'
652        # - this is just a specialised activity - i.e. with subtype preset
653        c.atomTypes = utils.getVocabTermDataDropdown(g.vtd.getValidTypes(g.vtd.ATOM_CATEGORY),
654                                               selected = inputs.get('atomTypeID'))
655        c.providerIDs = utils.getVocabTermDataDropdown(
656                            g.vtd.getValidTypes(g.vtd.PROVIDER_CATEGORY), 
657                            selected = inputs.get('providerID'))
658
659        try:
660            return savePageAndRender(self.pathInfo, 'atom_editor/atom_creator')
661
662        except Exception, e:
663            return self.__handleError(e)
664
665   
666    def createGranule(self, **inputs):
667        '''
668        Create a new atom from a granulite file
669        '''
670        logging.info("Setting up new atom from granulite template")
671        c.title='Create new data granule atom - from a granulite file'
672        c.errors = {}
673        try:
674            return savePageAndRender(self.pathInfo, 'atom_editor/atom_granulator')
675
676        except Exception, e:
677            return self.__handleError(e)
Note: See TracBrowser for help on using the repository browser.