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

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

A few small fixes to support usage of ndgCommon.

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
8from xml.parsers.expat import ExpatError
9from formencode import Invalid
10from genshi.filters import HTMLFormFiller
11from genshi import HTML
12from milk_server.lib.base import *
13from milk_server.models.form import *
14from milk_server.lib import mailer
15import ndg.common.src.lib.htmlUtilities as utils
16from ndg.common.src.models.Atom import Atom, Person, Link, Category
17from ndg.common.src.models import AtomState
18from ndg.common.src.lib.atomvalidator import ValidationError
19import ndg.common.src.clients.xmldb.eXist.existdbclient as edc
20from ndg.common.src.models.MolesEntity import MolesEntity as ME
21from ndg.common.src.lib.utilities import escapeSpecialCharacters
22from ndg.common.src.models.vocabtermdata import VocabTermData as VTD
23from ndg.common.src.lib.granulite import granulite
24from editorconstants import *
25from atomeditorcontroller import AtomEditorController
26from milk_server.lib import Utilities
27
28class EditatomController(AtomEditorController):
29    '''
30    Provides the pylons controller for editing NDG Atom documents.
31    '''
32    def upload(self, uri):
33        '''
34        Upload a CSML, CDML or granulite file and store it in the session variable
35        NB, if the uri is specified, we're already dealing with an atom
36        (which this refers to) - so the file is not a granulite - since
37        this is used to create an atom from scratch
38        '''
39        logging.info("Uploading file...")
40        self._setup(uri=uri)
41       
42        granFile = request.POST.get('upload_granulite')
43        csmlOrCdmlFile = request.POST.get('CSMLOrCDML')
44       
45        # check whether we can replace existing atoms
46        replaceAtom = self.inputs.get('replaceAtom')
47       
48        # NB, need to turn from string to boolean - there doesn't seem a reliable
49        # way of doing this using built in methods - so just do simple check
50        if replaceAtom == 'True':
51            replaceAtom = True
52        else:
53            replaceAtom = False
54       
55        # if this is true, then re-extract the inputs from the session data
56        if replaceAtom:
57            if session.get(GRAN_FILE_VALUE):
58                granFile = cgi.FieldStorage()
59                granFile.value = session.get(GRAN_FILE_VALUE)
60                granFile.filename = session.get(GRAN_FILE_NAME)
61                del session[GRAN_FILE_VALUE]
62                del session[GRAN_FILE_NAME]
63               
64            if session.get(CSML_OR_CDML_FILE_VALUE):
65                csmlOrCdmlFile = cgi.FieldStorage()
66                csmlOrCdmlFile.value = session.get(CSML_OR_CDML_FILE_VALUE)
67                csmlOrCdmlFile.filename = session.get(CSML_OR_CDML_FILE_NAME)
68                del session[CSML_OR_CDML_FILE_VALUE]
69                del session[CSML_OR_CDML_FILE_NAME]
70         
71        c.errors = {}
72        try:
73            logging.info("Validating inputs")
74            validator = LoadGranuliteFormSchema()
75            validator.to_python(self.inputs)
76            logging.info("- inputs valid")
77           
78            useCSMLID = True
79            if uri:
80                useCSMLID = False
81
82            if (granFile == '' or granFile == None) and \
83                (csmlOrCdmlFile == '' or csmlOrCdmlFile == None):
84                errorMessage = "Error: could not load file - please try again"
85                logging.error(errorMessage)
86                raise IOError(errorMessage)
87            else:
88                # Prepare the basic data model
89                # NB, if loading a granulite, this will create the displayed atom
90                # with the ID taken from the CSML file, if specified
91                fileContents = None
92                if (granFile is not None and granFile != ''):
93                    fileContents = granFile.value
94                   
95                # use the granulite helper class to add either the full granulite
96                # data or just the CSML/CDML data
97                # NB, we'll be creating the atom in the default local eXist
98                eXistClient = Utilities.getExistClient('local')
99                gran = granulite(fileContents, granuleAtom = c.atom, \
100                                 eXistClient = eXistClient, \
101                                 csmlOrCdmlFile = csmlOrCdmlFile, \
102                                 timeAxis = self.inputs.get('timeAxis'), \
103                                 datasetID = self.inputs.get('datasetID'), \
104                                 useCSMLID = useCSMLID, \
105                                 replaceAtom = replaceAtom)
106
107                # now process the input file and add any extra required data
108                if uri:
109                    c.atom = gran.processCSMLOrCDMLFile()
110
111                    # save new data - NB, for granulites, this is done as part of the
112                    # processing steps
113                    self.saveAtomToExist(c.atom)
114                else:
115                    try:
116                        c.atom = gran.processGranulite()
117
118                        # Now set up the ndgObject with the created atom's vals
119                        self._setup(uri=c.atom.ndgURI, loadAtom = False)
120                        c.atom.ndgObject = self.ndgObject
121                    except edc.DuplicateError, e:
122                        # we've found an existing atom with the same ID
123                        # - give the users the choice of replacing the contents of this atom
124                        # or just exiting
125                        # - NB, do this via a session variable to act as a flag
126                        # for a javascript command
127                        session[OVERWRITE_GRANULE_FLAG] = e.message
128                       
129                        # store the inputs data for easy retrieval
130                        # - NB, file fields don't behave as text fields - for security
131                        # purposes - so need to store their data as session variables
132                        # for easy retrieval
133                        # - Also, cannot pickle the cgi.FieldStorage object so extract
134                        # picklable data and recreate on the return run
135                        if granFile != '':
136                            session[GRAN_FILE_VALUE] = granFile.value
137                            session[GRAN_FILE_NAME] = granFile.filename
138                        if csmlOrCdmlFile != '':
139                            session[CSML_OR_CDML_FILE_VALUE] = csmlOrCdmlFile.value
140                            session[CSML_OR_CDML_FILE_NAME] = csmlOrCdmlFile.filename
141                       
142                        # need to return to original screen - so clear out variables
143                        c.atom = None
144                        uri = None
145                           
146                # now do redirection - NB, this ensures that current atom contents are
147                # reloaded and displayed
148                logging.info("File data loaded and extracted to atom")
149        except Invalid, e:
150            logging.info(" - inputs invalid")
151            c.errors = e.unpack_errors()
152        except Exception, e:
153            c.errors['WARNING'] = ['Error loading data: the displayed data will not be saved - please fix problem and retry']
154            self._unpackErrors(e)
155        except SystemExit, ee:
156            # NB, some of the CSML libraries just sys.exit on problems - catch errors here
157            c.errors['ERROR'] = ['Problem encountered whilst transforming the CDML data into CSML']
158            self._unpackErrors(ee)
159
160        if c.atom and hasattr(c.atom, 'ndgURI'):
161            self.pathInfo = self.pathInfo.replace('upload', 'editAtom')
162
163            h.redirect_to(h.url_for('edit', uri = c.atom.ndgURI))           
164        elif uri:
165            # something has gone wrong here...
166            return render("genshi", 'atom_editor/error')
167        else:
168            return self.createGranule(**self.inputs)
169
170   
171    def saveAtom(self, uri, saveLevel=0):
172        '''
173        Save the atom contents - NB, validation is done by method decoration
174        - if this fails, the action is reran as a GET with htmlfill auto-populating
175        the fields to keep them as they were before the submission
176        '''
177        logging.info("Saving input atom data")
178        c.errors = {}
179        try:
180            self._setup(uri)
181        except Exception, e:
182            return self._handleError(e)
183       
184        # variable to hold publication state changes
185        newState = None
186       
187        # save atom association changes
188        if int(saveLevel) == self.ADD_ASSOCIATIONS:
189            atomLinks = self.extractAtomAssociations(self.inputs)
190            c.atom.addUniqueRelatedLinks(atomLinks)
191        elif int(saveLevel) == self.REMOVE_ASSOCIATIONS:
192            atomLinks = self.extractAtomAssociations(self.inputs)
193            c.atom.removeRelatedLinks(atomLinks)
194        else:
195            authors = self.extractAuthorDetails(self.inputs)
196            c.atom.addAuthors(authors)
197   
198            onlineRefs = self.extractOnlineReferenceDetails(self.inputs)
199            c.atom.addOnlineReferences(onlineRefs)
200
201            params = self.extractParameterDetails(self.inputs)
202            # NB, the atom type and subtype are added to the categories when the
203            # atom is exported to XML - so don't need to worry about overwriting
204            # them now
205            c.atom.parameters = params
206           
207            if self.inputs.get('subtype'):
208                c.atom.subtype = self.getLatestTermURLFromDropDownInput( \
209                        self.inputs.get('subtype'))
210                c.atom.subtypeID = c.atom.subtype.split('/')[-1]
211           
212            if self.inputs.get('publication_state'):
213                newState = AtomState.getAtomState(self.inputs['publication_state'])
214
215        logging.info("Validating input")
216        try:
217            g.validator.setAtom(c.atom)
218            g.validator.validateAtom()
219            logging.info("- input valid")
220           
221            # if a change of state has been specified,
222
223            self.saveAtomToExist(c.atom, newState = newState)
224        except Exception, e:
225            self._unpackErrors(e)
226            logging.info("- input invalid")
227            return self.edit(uri)
228                   
229        # now do redirection - NB, this ensures that current atom contents are
230        # reloaded and displayed
231        h.redirect_to(h.url_for(controller = 'atom_editor/editatom', action='edit', \
232                        uri = c.atom.ndgURI))
233
234
235    def prepareEditForm(self, uri):
236        '''
237        Get everything set up for displaying the edit form
238        @param uri: ndg url for the atom to load in edit mode
239        '''
240        if not c.errors:
241            c.errors = {}
242
243        # NB, can get here directly from saveAtom - if there have been errors
244        # - in this case keep original data
245        if not c.atom:
246            self._setup(uri)
247           
248        c.title= EDIT_TITLE %c.atom.ndgURI
249        c.uri = c.atom.ndgURI
250       
251        c.saveLink = h.url_for('save', saveLevel = self.STANDARD_SAVE, uri = c.atom.ndgURI)
252        c.saveAssoc = h.url_for('save', saveLevel = self.REMOVE_ASSOCIATIONS, uri = c.atom.ndgURI)
253        c.deploymentsURL = h.url_for('viewAssociatedData', type = VTD.DEPLOYMENT_TERM, \
254                                     uri = c.atom.ndgURI)
255        c.dataEntitiesURL = h.url_for('viewAssociatedData', type = VTD.DE_TERM, \
256                                     uri = c.atom.ndgURI)
257       
258        # adjust atom type to cope with activity deployment exception
259        atomType = c.atom.atomTypeID
260        if atomType == g.vtd.ACTIVITY_TERM and \
261            c.atom.subtypeID == g.vtd.DEPLOYMENT_TERM:
262            atomType = g.vtd.DEPLOYMENT_TERM
263       
264        c.addEntityLink = h.url_for('list', searchData = '0', \
265                               associatedAtomID = c.atom.ndgURI, \
266                               associatedAtomType = atomType, 
267                               associationType = utils.ENTITY_ASSOCIATION)
268           
269        c.addGranuleLink = h.url_for('list', searchData = '0', \
270                               associatedAtomID = c.atom.ndgURI, \
271                               associatedAtomType = atomType, 
272                               associationType = utils.GRANULE_ASSOCIATION)
273           
274        c.addDeploymentLink = h.url_for('list', searchData = '0', \
275                               associatedAtomID = c.atom.ndgURI, \
276                               associatedAtomType = atomType, 
277                               associationType = utils.DEPLOYMENT_ASSOCIATION)
278
279        # account for special case where we're dealing with deployments
280        listVals = g.vtd.getValidSubTypes(c.atom.atomTypeID)
281        if c.atom.isDeployment():
282            listVals = [g.vtd.TERM_DATA[g.vtd.DEPLOYMENT_TERM]]
283
284        c.subTypes = utils.getVocabTermDataDropdown(listVals, \
285                                        selected=c.atom.subtype)
286       
287        c.states = h.options_for_select(AtomState.selectList, c.atom.state.stateFlag)
288        self.inputs['publication_state'] = c.atom.state.stateFlag
289       
290        self.__setDropDownSelectVal('subtype', c.atom.subtype, listVals)
291        self.addRelatedLinksDropDowns()
292
293
294    def __setDropDownSelectVal(self, name, val, vtds):
295        '''
296        Given a list of vocab terms, with the name of a 'select' tag and the selected
297        value, set the proper value in the inputs dict to allow htmlfill to correctly
298        display the list
299        @param name: name of select element to set the select value of
300        @param val: value of the selected item - NB, this need not be the current vocab
301        term url - but should start with the main stem and end with the termID
302        @param vtds: list of vocab term definition objects
303        '''
304        if not val:
305            return
306        for vtd in vtds:
307            if val.endswith(vtd.termID) and \
308                val.startswith(vtd.vocabURL):
309                self.inputs[name] = utils.getVocabTermDataSelectValue(vtd)
310                return
311           
312
313    def delete(self, uri):
314        '''
315        Delete the atom associated with the specified uri - and return
316        user to the atom home page.  NB, only granule atoms can be deleted
317        at the moment.
318        '''
319        if uri:
320            try:
321                logging.info("Deleting atom, '%s'" %uri)
322                self._setup(uri)
323                eXistClient = Utilities.getExistClient(c.atom.ME.providerID)
324                gran = granulite(None, granuleAtom = c.atom, \
325                                 eXistClient = eXistClient, \
326                                 deleteMode = True)
327   
328                gran.deleteGranuleAndDEReferences()
329                c.deleteResult = "Atom deleted successfully."
330                logging.info("- atom deleted")
331            except Exception, e:
332                logging.error("Problem occured whilst deleting atom: '%s'" %e.message)
333                c.deleteResult = "Warning: a problem occured whilst deleting the atom - this " + \
334                    "may have left the system in an unstable state - please check if the atom, or " + \
335                    "references to the atom still exist"
336
337        return render("genshi", "atom_editor/atom_home")
338       
339   
340    def edit(self, uri):
341        '''
342        Edit the atom with the specified ndg uri
343        '''
344        logging.info("Setting up atom edit template")
345        try:
346            self.prepareEditForm(uri)
347           
348            # NB, there appears to be a bug in htmlfill which automagically
349            # clears out content from textarea - so need to set the content
350            # explicitly for htmlfill to use
351            self.inputs['Content'] = c.atom.Content
352            self.inputs['Summary'] = c.atom.Summary
353            return self.savePageAndRender("atom_editor/atom_editor", **self.inputs)
354       
355        except ExpatError, e:
356            c.xml='XML content is not well formed'
357            c.doc=str(x)
358            logging.error("Error retrieving [%s] - XML content: %s" % (uri, e))
359        except SystemError, e:
360            return self._handleError(e)
361        except Exception, e:
362            errorMessage = traceback.format_exc()
363            c.xml='Unexpected error [%s] viewing [%s]'%(str(e), uri)
364            c.doc=''
365            logging.error(c.xml)
366       
367        response.status_code = 400
368        return render("genshi", 'atom_editor/error')
369
370
371    def addRelatedLinksDropDowns(self):
372        '''
373        Set up the drop down lists required for the selection of online ref links
374        '''
375        # at the very least, we need a simple drop down list with no preselected
376        # values
377        logging.debug("Setting up drop down lists for related links")
378        vtds = g.vtd.getValidTypes(g.vtd.ONLINE_REF_CATEGORY)
379        c.relatedLinkTerms = utils.getVocabTermDataDropdown(vtds)
380
381        # ensure we have set up the correct inputs to allow htmlfill to show
382        # the correct selected value
383        for i, link in enumerate(c.atom.relatedLinks):
384            logging.debug("Adding dropdown for related link, '%s'" %(str(link)))
385            refLabel = Atom.ONLINE_REF_LABEL + "." + str(i) + '.rel'
386           
387            # get the value of the selected list
388            self.__setDropDownSelectVal(refLabel, link.rel, vtds)
389
390        logging.debug("Finished setting up drop down lists")
391
392
393    def extractAuthorDetails(self, inputs):
394        '''
395        Retrieve author data from inputs and set appropriately on Atom, if any
396        found
397        @return: list of Person objects with the author data
398        '''
399        logging.info("Extracting author data from inputs")
400        processedAuthors = []
401        authors = []
402        for key in inputs:
403            keyBits = key.split('.')
404            if len(keyBits) == 3 and keyBits[1] not in processedAuthors:
405               
406                authorType = -1
407                if key.lower().startswith('author'):
408                    authorType = Person.AUTHOR_TYPE
409                elif key.lower().startswith('contributor'):
410                    authorType = Person.CONTRIBUTOR_TYPE
411                elif key.lower().startswith('responsible'):
412                    authorType = Person.RESPONSIBLE_PARTY_TYPE
413                else:
414                    continue
415
416                # NB, adding an empty object here is valid as it will clear out
417                # existing entries, potentially
418                author = Person(personType = authorType)
419                # check if the remove checkbox has been set
420                keyStem = ".".join(keyBits[0:2])
421                if inputs.get(keyStem + ".remove"):
422                    logging.info("Removing author data")
423                else:
424                    author.name = inputs.get(keyStem + '.name') or ""
425                    author.uri = inputs.get(keyStem + '.uri') or ""
426                    author.role = inputs.get(keyStem + '.role') or ""
427                   
428                    logging.info("Adding new author info")
429                    logging.debug("Extracted author (type:'%s', name:'%s', uri:'%s', role:'%s')" \
430                                  %(author.type, author.name, author.uri, author.role))
431                authors.append(author)
432                processedAuthors.append(keyBits[1])
433
434        logging.info("Finished extracting author data")
435        return authors
436
437
438    def extractOnlineReferenceDetails(self, inputs):
439        '''
440        Retrieve online reference data from inputs and set appropriately on Atom, if any
441        found
442        @return: list of Link objects containing the extracted data
443        '''
444        logging.info("Extracting related links data from inputs")
445        processedLinks = []
446        links = []
447
448        for key in inputs:
449            keyBits = key.split('.')
450            if len(keyBits) == 3 and keyBits[1] not in processedLinks:
451               
452                if key.lower().startswith(Atom.ONLINE_REF_LABEL):
453                    link = Link()
454                    keyStem = ".".join(keyBits[0:2])
455                   
456                    if inputs.get(keyStem + ".remove"):
457                        logging.info("Removing online reference data")
458                    else:
459                        # NB, this is in the format vocabURL--termID, so requires further
460                        # processing
461                        link.rel = self.getLatestTermURLFromDropDownInput(inputs.get(keyStem + '.rel'))
462                        link.href = inputs.get(keyStem + '.href') or ""
463                        link.title = inputs.get(keyStem + '.title') or ""
464                       
465                        if not link.hasValue():
466                            continue
467                           
468                        logging.info("Adding new online reference info")
469                        logging.debug("Extracted online reference (href:'%s', title:'%s', rel:'%s')" \
470                                      %(link.href, link.title, link.rel))
471                        links.append(link)
472
473                    processedLinks.append(keyBits[1])
474                else:
475                    continue
476
477        logging.info("Finished extracting links data")
478        return links
479
480
481    def extractParameterDetails(self, inputs):
482        '''
483        Retrieve parameters data from inputs and set appropriately on Atom, if any
484        found
485        @return: list of Category objects containing the extracted data
486        '''
487        logging.info("Extracting parameters data from inputs")
488        processedParameters = []
489        parameters = []
490
491        for key in inputs:
492            keyBits = key.split('.')
493            if len(keyBits) == 3 and keyBits[1] not in processedParameters:
494               
495                if key.lower().startswith(Atom.PARAMETER_LABEL):
496                    parameter = Category()
497                    keyStem = ".".join(keyBits[0:2])
498                   
499                    if inputs.get(keyStem + ".remove"):
500                        logging.info("Removing parameters data")
501                    else:
502                        parameter.term = inputs.get(keyStem + '.term') or ""
503                        parameter.scheme = inputs.get(keyStem + '.scheme') or ""
504                        parameter.label = inputs.get(keyStem + '.label') or ""
505                           
506                        logging.info("Adding new parameter info")
507                        logging.debug("Extracted parameter (vocabURL:'%s', label:'%s', term:'%s')" \
508                                      %(parameter.scheme, parameter.label, parameter.term))
509                        parameters.append(parameter)
510
511                    processedParameters.append(keyBits[1])
512                else:
513                    continue
514
515        logging.info("Finished extracting parameters data")
516        return parameters
517
518
519    def extractAtomAssociations(self, inputs):
520        '''
521        Retrieve atom data from inputs and create related links pointing to
522        this data
523        @return: list of Links representing the related atoms
524        '''
525        logging.info("Extracting related atom ID data from inputs")
526        atoms = []
527        processedAtoms = []
528
529        for key in inputs:
530            if key.lower().startswith(Atom.ATOM_REF_LABEL):
531                (x, href, title, rel) = key.split(Atom.DELIMITER)
532                # NB, we handle removes by effectively ignoring them later on
533                if href not in processedAtoms:
534                    processedAtoms.append(href)
535
536                    link = Link()
537                    link.href = href or ""
538                    link.title = title or ""
539                    link.rel = rel or ""
540                   
541                    # adjust href to point to the view, not the edit version
542                    link.href = link.href.replace('editAtom', 'view')
543                   
544                    logging.debug("Extracted atom info (href:'%s', title:'%s', rel:'%s')" \
545                                  %(link.href, link.title, link.rel))
546                    atoms.append(link)
547            else:
548                continue
549
550        logging.info("Finished extracting atoms data")
551        return atoms
552               
553
554    def getLatestTermURLFromDropDownInput(self, inputVal):
555        '''
556        Term ID and vocabURL are specified in the drop down menus
557        - using the input from this, return the lastest full href to the
558        term ID
559        '''
560        termData = inputVal.split('--')
561        return g.vtd.getCurrentVocabURI(termData[0]) + \
562                        "/" + termData[1]
563
564
565    def saveAtomToExist(self, atom, newState = None):
566        '''
567        Save the specified atom in eXist
568        @param atom: atom object to save to eXist
569        @keyword newState:  AtomState publication state to move the atom to, default: None
570        - use the current atom state
571        @return atom: atom object saved in eXist
572        '''
573        logging.info("Saving changes to eXist")
574        eXist = Utilities.getExistClient(atom.ME.providerID)
575       
576        if newState and newState != atom.state:
577            createdAtom = eXist.changeAtomPublicationStateInExist(atom, newState)
578        else:
579            createdAtom = eXist.createAtomInExist(atom)
580        logging.info("Changes successfully saved to eXist")
581        return createdAtom
582
583   
584    def create(self, saveData = None, **inputs):
585        '''
586        Create a new atom
587        '''
588        self._setup()
589        if saveData:
590            logging.info("Validating input")
591            try:
592                validator = CreateAtomFormSchema()
593                validator.to_python(self.inputs)
594                logging.info("- input valid")
595               
596                logging.info("Creating basic atom")
597                atomTypeID = self.inputs.get('atomTypeID').split('--')[1]
598                self.inputs['atomTypeID'] = atomTypeID
599   
600                # activity deployments should have subtype deployment specified automatically
601                if atomTypeID == g.vtd.ACTIVITY_DEPLOYMENT_TERM:
602                    self.inputs['subtypeID'] = g.vtd.DEPLOYMENT_TERM
603                    self.inputs['atomTypeID'] = g.vtd.ACTIVITY_TERM
604                   
605                self.inputs['providerID'] = self.inputs.get('providerID').split('--')[1]
606                atom = self.saveAtomToExist(Atom(**dict(self.inputs)))
607                url = h.url_for('edit', uri = atom.ndgURI, saveData=None)
608               
609                # NB, the redirect throws an exception, so be careful not to catch it
610                h.redirect_to(url)
611            except Invalid, e:
612                c.errors = e.unpack_errors()
613               
614           
615        logging.info("Setting up atom create template")
616        c.title = CREATE_ATOM_TITLE
617       
618        # set up the drop down content - NB, add special case, 'deployment activity'
619        # - this is just a specialised activity - i.e. with subtype preset
620        c.atomTypes = utils.getVocabTermDataDropdown(g.vtd.getValidTypes(g.vtd.ATOM_CATEGORY))
621        c.providerIDs = utils.getVocabTermDataDropdown(g.vtd.getValidTypes(g.vtd.PROVIDER_CATEGORY))
622
623        try:
624            return self.savePageAndRender('atom_editor/atom_creator', **self.inputs)
625
626        except Exception, e:
627            return self._handleError(e)
628
629   
630    def createGranule(self, **inputs):
631        '''
632        Create a new atom from a granulite file
633        '''
634        logging.info("Setting up new atom from granulite template")
635        c.title='Create new data granule atom - from a granulite file'
636        c.errors = {}
637        try:
638            return self.savePageAndRender('atom_editor/atom_granulator', **inputs)
639
640        except Exception, e:
641            return self._handleError(e)
Note: See TracBrowser for help on using the repository browser.