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

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

Simplify upload of CSML/CDML/granulite data by using the newly extended
granulite class for all these ops - remove unecessary code in editatom.
Add new template function to combine the upload of CSML/CDML files into
a single field - with additional inputs for time axis and dataset ID data.
Fix editor to display deployments data for DEs once again.

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