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

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

Fix pointer to error template and ensure initialisation of context error object.

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            if authors:
226                c.atom.addAuthors(authors)
227   
228            onlineRefs = self.extractOnlineReferenceDetails(inputs)
229            c.atom.addOnlineReferences(onlineRefs)
230
231            params = self.extractParameterDetails(inputs)
232            # NB, the atom type and subtype are added to the categories when the
233            # atom is exported to XML - so don't need to worry about overwriting
234            # them now
235            c.atom.parameters = params
236           
237            if inputs.get('subtype'):
238                c.atom.subtype = self.getLatestTermURLFromDropDownInput( \
239                        inputs.get('subtype'))
240                c.atom.subtypeID = c.atom.subtype.split('/')[-1]
241
242        logging.info("Validating input")
243        try:
244            g.validator.setAtom(c.atom)
245            g.validator.validateAtom()
246            logging.info("- input valid")
247
248            self.saveAtomToExist(c.atom)
249        except Exception, e:
250            self.__unpackErrors(e)
251            logging.info("- input invalid")
252            return self.edit(uri)
253                   
254        # now do redirection - NB, this ensures that current atom contents are
255        # reloaded and displayed
256        h.redirect_to(controller = 'atom_editor/editatom', action='edit', \
257                        uri = c.atom.ndgURI)
258
259
260    def prepareDataModel(self, uri):
261        '''
262        Set up the underlying atom data model - loading the bulk from eXist
263        then updating any input fields appropriately
264        '''
265        logging.info("Preparing underlying data model")
266        status=self.__setup(uri=uri)
267        if status:
268            c.xml='<p>%s</p>'%status
269            response.status_code = 400
270            raise SystemError('Problem experienced setting up data model')
271
272        logging.info("Retrieving document to edit")
273        # NB, don't use the cache as docs are likely to change
274        # quite a lot during editing; ensure you've always got
275        # the latest updates loaded
276        status,x = interface.GetXML(uri, useCache=False)
277
278        if not status:
279            code=400
280            if x.startswith('<p> Access Denied'):
281                code=401
282
283            c.xml='%s'%x
284            response.status_code = code
285            raise SystemError('Problem experienced retrieving atom doc from eXist')
286
287        # NB, passing in the inputs will overwrite any original values with the
288        # user input ones
289        inputs = self.__getTidyInputs()
290       
291        c.atom = Atom(xmlString=str(x), ndgObject = self.ndgObject, **dict(inputs))
292       
293        # save the current atom - to avoid this needing be recreated by the
294        # asynch viewAssociatedData call
295        session['currentAtom'] = c.atom
296        session.save()
297        logging.info("Data model set up")
298
299
300    def prepareEditForm(self, uri):
301        '''
302        Get everything set up for displaying the edit form
303        @param uri: ndg url for the atom to load in edit mode
304        '''
305        if not c.errors:
306            c.errors = {}
307
308        try:
309            # NB, can get here directly from saveAtom - if there have been errors
310            # - in this case keep original data
311            if not c.atom:
312                self.prepareDataModel(uri)
313        except SystemError, e:
314            return self.__handleError(e)
315           
316        c.title='Editing [%s]' %c.atom.ndgURI
317        c.uri = c.atom.ndgURI
318       
319        c.saveLink = h.url_for(controller='atom_editor/editatom',action='saveAtom', \
320                               saveLevel='1',  uri = c.atom.ndgURI)
321        c.saveLink2 = h.url_for(controller='atom_editor/editatom',action='saveAtom', saveLevel='2')
322        c.saveAssoc = h.url_for(controller='atom_editor/editatom',action='saveAtom', \
323                                 saveLevel = self.REMOVE_ASSOCIATIONS)
324        c.deploymentsURL = h.url_for(controller='browse/retrieve', \
325                                     action='viewAssociatedData', \
326                                     type = VTD.DEPLOYMENT_TERM, \
327                                     uri = c.atom.ndgURI)
328        c.dataEntitiesURL = h.url_for(controller='browse/retrieve', \
329                                     action='viewAssociatedData', \
330                                     type = VTD.DE_TERM, \
331                                     uri = c.atom.ndgURI)
332       
333        # adjust atom type to cope with activity deployment exception
334        atomType = c.atom.atomTypeID
335        if atomType == g.vtd.ACTIVITY_TERM and \
336            c.atom.subtypeID == g.vtd.DEPLOYMENT_TERM:
337            atomType = g.vtd.DEPLOYMENT_TERM
338       
339        c.addEntityLink = h.url_for(controller='atom_editor/listatom',action='list', searchData = '0', \
340                               associatedAtomID = c.atom.ndgURI, \
341                               associatedAtomType = atomType, 
342                               associationType = utils.ENTITY_ASSOCIATION)
343           
344        c.addGranuleLink = h.url_for(controller='atom_editor/listatom',action='list', searchData = '0', \
345                               associatedAtomID = c.atom.ndgURI, \
346                               associatedAtomType = atomType, 
347                               associationType = utils.GRANULE_ASSOCIATION)
348           
349        c.addDeploymentLink = h.url_for(controller='atom_editor/listatom',action='list', searchData = '0', \
350                               associatedAtomID = c.atom.ndgURI, \
351                               associatedAtomType = atomType, 
352                               associationType = utils.DEPLOYMENT_ASSOCIATION)
353
354        # account for special case where we're dealing with deployments
355        listVals = g.vtd.getValidSubTypes(c.atom.atomTypeID)
356        if c.atom.isDeployment():
357            listVals = [g.vtd.TERM_DATA[g.vtd.DEPLOYMENT_TERM]]
358
359        c.subTypes = utils.getVocabTermDataDropdown(listVals, \
360                                        selected=c.atom.subtype)
361       
362        self.addRelatedLinksDropDowns()
363
364   
365    def edit(self, uri):
366        '''
367        Edit the specified uri
368        '''
369        logging.info("Setting up atom edit template")
370        self.prepareEditForm(uri)
371
372        try:
373            return savePageAndRender(self.pathInfo, "atom_editor/atom_editor")
374       
375        except ExpatError, e:
376            c.xml='XML content is not well formed'
377            c.doc=str(x)
378            logging.error("Error retrieving [%s] - XML content: %s" % (uri, e))
379
380        except Exception, e:
381            errorMessage = traceback.format_exc()
382            c.xml='Unexpected error [%s] viewing [%s]'%(str(e), uri)
383            c.doc=''
384            logging.error(c.xml)
385       
386        response.status_code = 400
387        return render("genshi", 'atom_editor/error')
388
389
390    def addRelatedLinksDropDowns(self):
391        '''
392        Set up the drop down lists required for the selection of online ref links
393        '''
394        # at the very least, we need a simple drop down list with no preselected
395        # values
396        logging.debug("Setting up drop down lists for related links")
397        c.relatedLinkTerms = utils.getVocabTermDataDropdown(\
398                g.vtd.getValidTypes(g.vtd.ONLINE_REF_CATEGORY))
399       
400        c.relatedLinkSelectedLists = {}
401        for link in c.atom.relatedLinks:
402            logging.debug("Adding dropdown for related link, '%s'" %(str(link)))
403            c.relatedLinkSelectedLists[str(link)] = \
404                utils.getVocabTermDataDropdown(g.vtd.getValidTypes(g.vtd.ONLINE_REF_CATEGORY), \
405                                                     selected=link.rel)
406
407        logging.debug("Finished setting up drop down lists")
408
409
410    def extractAuthorDetails(self, inputs):
411        '''
412        Retrieve author data from inputs and set appropriately on Atom, if any
413        found
414        @return: list of Person objects with the author data
415        '''
416        logging.info("Extracting author data from inputs")
417        processedAuthors = []
418        authors = []
419        for key in inputs:
420            keyBits = key.split('.')
421            if len(keyBits) == 3 and keyBits[1] not in processedAuthors:
422               
423                authorType = -1
424                if key.lower().startswith('author'):
425                    authorType = Person.AUTHOR_TYPE
426                elif key.lower().startswith('contributor'):
427                    authorType = Person.CONTRIBUTOR_TYPE
428                elif key.lower().startswith('responsible'):
429                    authorType = Person.RESPONSIBLE_PARTY_TYPE
430                else:
431                    continue
432
433                # NB, adding an empty object here is valid as it will clear out
434                # existing entries, potentially
435                author = Person(personType = authorType)
436                # check if the remove checkbox has been set
437                keyStem = ".".join(keyBits[0:2])
438                if inputs.get(keyStem + ".remove"):
439                    logging.info("Removing author data")
440                else:
441                    author.name = inputs.get(keyStem + '.name') or ""
442                    author.uri = inputs.get(keyStem + '.uri') or ""
443                    author.role = inputs.get(keyStem + '.role') or ""
444                   
445                    logging.info("Adding new author info")
446                    logging.debug("Extracted author (type:'%s', name:'%s', uri:'%s', role:'%s')" \
447                                  %(author.type, author.name, author.uri, author.role))
448                    authors.append(author)
449                processedAuthors.append(keyBits[1])
450
451        logging.info("Finished extracting author data")
452        return authors
453
454
455    def extractOnlineReferenceDetails(self, inputs):
456        '''
457        Retrieve online reference data from inputs and set appropriately on Atom, if any
458        found
459        @return: list of Link objects containing the extracted data
460        '''
461        logging.info("Extracting related links data from inputs")
462        processedLinks = []
463        links = []
464
465        for key in inputs:
466            keyBits = key.split('.')
467            if len(keyBits) == 3 and keyBits[1] not in processedLinks:
468               
469                if key.lower().startswith(Atom.ONLINE_REF_LABEL):
470                    link = Link()
471                    keyStem = ".".join(keyBits[0:2])
472                   
473                    if inputs.get(keyStem + ".remove"):
474                        logging.info("Removing online reference data")
475                    else:
476                        # NB, this is in the format vocabURL--termID, so requires further
477                        # processing
478                        link.rel = self.getLatestTermURLFromDropDownInput(inputs.get(keyStem + '.rel'))
479                        link.href = inputs.get(keyStem + '.href') or ""
480                        link.title = inputs.get(keyStem + '.title') or ""
481                       
482                        if not link.hasValue():
483                            continue
484                           
485                        logging.info("Adding new online reference info")
486                        logging.debug("Extracted online reference (href:'%s', title:'%s', rel:'%s')" \
487                                      %(link.href, link.title, link.rel))
488                        links.append(link)
489
490                    processedLinks.append(keyBits[1])
491                else:
492                    continue
493
494        logging.info("Finished extracting links data")
495        return links
496
497
498    def extractParameterDetails(self, inputs):
499        '''
500        Retrieve parameters data from inputs and set appropriately on Atom, if any
501        found
502        @return: list of Category objects containing the extracted data
503        '''
504        logging.info("Extracting parameters data from inputs")
505        processedParameters = []
506        parameters = []
507
508        for key in inputs:
509            keyBits = key.split('.')
510            if len(keyBits) == 3 and keyBits[1] not in processedParameters:
511               
512                if key.lower().startswith(Atom.PARAMETER_LABEL):
513                    parameter = Category()
514                    keyStem = ".".join(keyBits[0:2])
515                   
516                    if inputs.get(keyStem + ".remove"):
517                        logging.info("Removing parameters data")
518                    else:
519                        parameter.term = inputs.get(keyStem + '.term') or ""
520                        parameter.scheme = inputs.get(keyStem + '.scheme') or ""
521                        parameter.label = inputs.get(keyStem + '.label') or ""
522                           
523                        logging.info("Adding new parameter info")
524                        logging.debug("Extracted parameter (vocabURL:'%s', label:'%s', term:'%s')" \
525                                      %(parameter.scheme, parameter.label, parameter.term))
526                        parameters.append(parameter)
527
528                    processedParameters.append(keyBits[1])
529                else:
530                    continue
531
532        logging.info("Finished extracting parameters data")
533        return parameters
534
535
536    def extractAtomAssociations(self, inputs):
537        '''
538        Retrieve atom data from inputs and create related links pointing to
539        this data
540        @return: list of Links representing the related atoms
541        '''
542        logging.info("Extracting related atom ID data from inputs")
543        atoms = []
544        processedAtoms = []
545
546        for key in inputs:
547            if key.lower().startswith(Atom.ATOM_REF_LABEL):
548                (x, href, title, rel) = key.split(Atom.DELIMITER)
549                # NB, we handle removes by effectively ignoring them later on
550                if href not in processedAtoms:
551                    processedAtoms.append(href)
552
553                    link = Link()
554                    link.href = href or ""
555                    link.title = title or ""
556                    link.rel = rel or ""
557                   
558                    # adjust href to point to the view, not the edit version
559                    link.href = link.href.replace('editAtom', 'view')
560                   
561                    logging.debug("Extracted atom info (href:'%s', title:'%s', rel:'%s')" \
562                                  %(link.href, link.title, link.rel))
563                    atoms.append(link)
564            else:
565                continue
566
567        logging.info("Finished extracting atoms data")
568        return atoms
569               
570
571    def getLatestTermURLFromDropDownInput(self, inputVal):
572        '''
573        Term ID and vocabURL are specified in the drop down menus
574        - using the input from this, return the lastest full href to the
575        term ID
576        '''
577        termData = inputVal.split('--')
578        return g.vtd.getCurrentVocabURI(termData[0]) + \
579                        "/" + termData[1]
580
581
582    def saveAtomToExist(self, atom):
583        '''
584        Save the specified atom in eXist
585        @param atom: atom object to save to eXist
586        @return atom: atom object saved in eXist
587        '''
588        logging.info("Saving changes to eXist")
589        eXist = self.__getExistClient(atom.ME.providerID)
590        createdAtom = eXist.createAtomInExist(atom)
591        logging.info("Changes successfully saved to eXist")
592        return createdAtom
593
594
595    def __getExistClient(self, providerID):
596        '''
597        Use the config data to set up and return an eXist client
598        '''
599        logging.info("Setting up eXist client to provider, '%s'" %providerID)
600        # firstly check if there is a current connection available
601        eXistClient = g.eXistDBCons.get(providerID)
602        if eXistClient:
603            logging.info("- found existing client to provider - returning this")
604            return eXistClient
605       
606        # lookup the eXist DB to use according to the provider ID for the
607        # data - NB, this is specified in the milk.config file
608        existHost = self.cf.get('NDG_EXIST', providerID)
609        configFile = self.cf.get('NDG_EXIST','passwordFile')
610        eXistClient = edc.eXistDBClient(eXistDBHostname = existHost, \
611                                       configFile = configFile)
612        # add the client to the global variables for re-use
613        g.eXistDBCons[providerID] = eXistClient
614        logging.info("Returning eXist client")
615        return eXistClient
616
617   
618    def create(self, saveData = None, **inputs):
619        '''
620        Create a new atom
621        '''
622        if saveData:
623            logging.info("Validating input")
624            try:
625                inputs = request.params
626                validator = CreateAtomFormSchema()
627                validator.to_python(inputs)
628                logging.info("- input valid")
629               
630                logging.info("Creating basic atom")
631                self.__setup()
632                atomTypeID = inputs.get('atomTypeID').split('--')[1]
633                inputs['atomTypeID'] = atomTypeID
634   
635                # activity deployments should have subtype deployment specified automatically
636                if atomTypeID == g.vtd.ACTIVITY_DEPLOYMENT_TERM:
637                    inputs['subtypeID'] = g.vtd.DEPLOYMENT_TERM
638                    inputs['atomTypeID'] = g.vtd.ACTIVITY_TERM
639                   
640                inputs['providerID'] = inputs.get('providerID').split('--')[1]
641                atom = self.saveAtomToExist(Atom(**dict(inputs)))
642               
643                h.redirect_to(controller = 'atom_editor/editatom', action='edit',
644                               uri = atom.ndgURI)
645            except Invalid, e:
646                c.errors = e.unpack_errors()
647               
648           
649        logging.info("Setting up atom create template")
650        c.title='Create new atom'
651       
652        # set up the drop down content - NB, add special case, 'deployment activity'
653        # - this is just a specialised activity - i.e. with subtype preset
654        c.atomTypes = utils.getVocabTermDataDropdown(g.vtd.getValidTypes(g.vtd.ATOM_CATEGORY),
655                                               selected = inputs.get('atomTypeID'))
656        c.providerIDs = utils.getVocabTermDataDropdown(
657                            g.vtd.getValidTypes(g.vtd.PROVIDER_CATEGORY), 
658                            selected = inputs.get('providerID'))
659
660        try:
661            return savePageAndRender(self.pathInfo, 'atom_editor/atom_creator')
662
663        except Exception, e:
664            return self.__handleError(e)
665
666   
667    def createGranule(self, **inputs):
668        '''
669        Create a new atom from a granulite file
670        '''
671        logging.info("Setting up new atom from granulite template")
672        c.title='Create new data granule atom - from a granulite file'
673        c.errors = {}
674        try:
675            return savePageAndRender(self.pathInfo, 'atom_editor/atom_granulator')
676
677        except Exception, e:
678            return self.__handleError(e)
Note: See TracBrowser for help on using the repository browser.