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

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

Add improved error handling when doing document ingests + add
additional help sections for this purpose.

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('granuleDatasetID'), \
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            # NB, if there are errors, don't redirect otherwise these will get lost
164            if not c.errors:
165                h.redirect_to(h.url_for('edit', uri = c.atom.ndgURI))
166            else:
167                c.atom.contentFile = None
168                return self.edit(c.atom.ndgURI)
169                           
170        elif uri:
171            # something has gone wrong here...
172            return render("genshi", 'atom_editor/error')
173        else:
174            return self.createGranule(**self.inputs)
175
176   
177    def saveAtom(self, uri, saveLevel=0):
178        '''
179        Save the atom contents - NB, validation is done by method decoration
180        - if this fails, the action is reran as a GET with htmlfill auto-populating
181        the fields to keep them as they were before the submission
182        '''
183        logging.info("Saving input atom data")
184        c.errors = {}
185        try:
186            self._setup(uri)
187        except Exception, e:
188            return self._handleError(e)
189       
190        # variable to hold publication state changes
191        newState = None
192       
193        # save atom association changes
194        if int(saveLevel) == self.ADD_ASSOCIATIONS:
195            atomLinks = self.extractAtomAssociations(self.inputs)
196            c.atom.addUniqueRelatedLinks(atomLinks)
197        elif int(saveLevel) == self.REMOVE_ASSOCIATIONS:
198            atomLinks = self.extractAtomAssociations(self.inputs)
199            c.atom.removeRelatedLinks(atomLinks)
200        else:
201            authors = self.extractAuthorDetails(self.inputs)
202            c.atom.addAuthors(authors)
203   
204            onlineRefs = self.extractOnlineReferenceDetails(self.inputs)
205            c.atom.addOnlineReferences(onlineRefs)
206
207            params = self.extractParameterDetails(self.inputs)
208            # NB, the atom type and subtype are added to the categories when the
209            # atom is exported to XML - so don't need to worry about overwriting
210            # them now
211            c.atom.parameters = params
212           
213            if self.inputs.get('subtype'):
214                c.atom.subtype = self.getLatestTermURLFromDropDownInput( \
215                        self.inputs.get('subtype'))
216                c.atom.subtypeID = c.atom.subtype.split('/')[-1]
217           
218            if self.inputs.get('publication_state'):
219                newState = AtomState.getAtomState(self.inputs['publication_state'])
220
221        logging.info("Validating input")
222        try:
223            g.validator.setAtom(c.atom)
224            g.validator.validateAtom()
225            logging.info("- input valid")
226           
227            # if a change of state has been specified,
228
229            self.saveAtomToExist(c.atom, newState = newState)
230        except Exception, e:
231            self._unpackErrors(e)
232            logging.info("- input invalid")
233            return self.edit(uri)
234                   
235        # now do redirection - NB, this ensures that current atom contents are
236        # reloaded and displayed
237        h.redirect_to(h.url_for(controller = 'atom_editor/editatom', action='edit', \
238                        uri = c.atom.ndgURI))
239
240
241    def prepareEditForm(self, uri):
242        '''
243        Get everything set up for displaying the edit form
244        @param uri: ndg url for the atom to load in edit mode
245        '''
246        if not c.errors:
247            c.errors = {}
248
249        # NB, can get here directly from saveAtom - if there have been errors
250        # - in this case keep original data
251        if not c.atom:
252            self._setup(uri)
253           
254        c.title= EDIT_TITLE %c.atom.ndgURI
255        c.uri = c.atom.ndgURI
256       
257        c.saveLink = h.url_for('save', saveLevel = self.STANDARD_SAVE, uri = c.atom.ndgURI)
258        c.saveAssoc = h.url_for('save', saveLevel = self.REMOVE_ASSOCIATIONS, uri = c.atom.ndgURI)
259        c.deploymentsURL = h.url_for('viewAssociatedData', type = VTD.DEPLOYMENT_TERM, \
260                                     uri = c.atom.ndgURI)
261        c.dataEntitiesURL = h.url_for('viewAssociatedData', type = VTD.DE_TERM, \
262                                     uri = c.atom.ndgURI)
263       
264        # adjust atom type to cope with activity deployment exception
265        atomType = c.atom.atomTypeID
266        if atomType == g.vtd.ACTIVITY_TERM and \
267            c.atom.subtypeID == g.vtd.DEPLOYMENT_TERM:
268            atomType = g.vtd.DEPLOYMENT_TERM
269       
270        c.addEntityLink = h.url_for('list', searchData = '0', \
271                               associatedAtomID = c.atom.ndgURI, \
272                               associatedAtomType = atomType, 
273                               associationType = utils.ENTITY_ASSOCIATION)
274           
275        c.addGranuleLink = h.url_for('list', searchData = '0', \
276                               associatedAtomID = c.atom.ndgURI, \
277                               associatedAtomType = atomType, 
278                               associationType = utils.GRANULE_ASSOCIATION)
279           
280        c.addDeploymentLink = h.url_for('list', searchData = '0', \
281                               associatedAtomID = c.atom.ndgURI, \
282                               associatedAtomType = atomType, 
283                               associationType = utils.DEPLOYMENT_ASSOCIATION)
284
285        # account for special case where we're dealing with deployments
286        listVals = g.vtd.getValidSubTypes(c.atom.atomTypeID)
287        if c.atom.isDeployment():
288            listVals = [g.vtd.TERM_DATA[g.vtd.DEPLOYMENT_TERM]]
289
290        c.subTypes = utils.getVocabTermDataDropdown(listVals, \
291                                        selected=c.atom.subtype)
292       
293        c.states = h.options_for_select(AtomState.selectList, c.atom.state.stateFlag)
294        self.inputs['publication_state'] = c.atom.state.stateFlag
295       
296        self.__setDropDownSelectVal('subtype', c.atom.subtype, listVals)
297        self.addRelatedLinksDropDowns()
298
299
300    def __setDropDownSelectVal(self, name, val, vtds):
301        '''
302        Given a list of vocab terms, with the name of a 'select' tag and the selected
303        value, set the proper value in the inputs dict to allow htmlfill to correctly
304        display the list
305        @param name: name of select element to set the select value of
306        @param val: value of the selected item - NB, this need not be the current vocab
307        term url - but should start with the main stem and end with the termID
308        @param vtds: list of vocab term definition objects
309        '''
310        if not val:
311            return
312        for vtd in vtds:
313            if val.endswith(vtd.termID) and \
314                val.startswith(vtd.vocabURL):
315                self.inputs[name] = utils.getVocabTermDataSelectValue(vtd)
316                return
317           
318
319    def delete(self, uri):
320        '''
321        Delete the atom associated with the specified uri - and return
322        user to the atom home page.  NB, only granule atoms can be deleted
323        at the moment.
324        '''
325        if uri:
326            try:
327                logging.info("Deleting atom, '%s'" %uri)
328                self._setup(uri)
329                eXistClient = Utilities.getExistClient(c.atom.ME.providerID)
330                gran = granulite(None, granuleAtom = c.atom, \
331                                 eXistClient = eXistClient, \
332                                 deleteMode = True)
333   
334                gran.deleteGranuleAndDEReferences()
335                c.deleteResult = "Atom deleted successfully."
336                logging.info("- atom deleted")
337            except Exception, e:
338                logging.error("Problem occured whilst deleting atom: '%s'" %e.message)
339                c.deleteResult = "Warning: a problem occured whilst deleting the atom - this " + \
340                    "may have left the system in an unstable state - please check if the atom, or " + \
341                    "references to the atom still exist"
342
343        return render("genshi", "atom_editor/atom_home")
344       
345   
346    def edit(self, uri):
347        '''
348        Edit the atom with the specified ndg uri
349        '''
350        logging.info("Setting up atom edit template")
351        try:
352            self.prepareEditForm(uri)
353           
354            # NB, there appears to be a bug in htmlfill which automagically
355            # clears out content from textarea - so need to set the content
356            # explicitly for htmlfill to use
357            self.inputs['Content'] = c.atom.Content
358            self.inputs['Summary'] = c.atom.Summary
359            return self.savePageAndRender("atom_editor/atom_editor", **self.inputs)
360       
361        except ExpatError, e:
362            c.xml='XML content is not well formed'
363            c.doc=str(x)
364            logging.error("Error retrieving [%s] - XML content: %s" % (uri, e))
365        except SystemError, e:
366            return self._handleError(e)
367        except Exception, e:
368            errorMessage = traceback.format_exc()
369            c.xml='Unexpected error [%s] viewing [%s]'%(str(e), uri)
370            c.doc=''
371            logging.error(c.xml)
372       
373        response.status_code = 400
374        return render("genshi", 'atom_editor/error')
375
376
377    def addRelatedLinksDropDowns(self):
378        '''
379        Set up the drop down lists required for the selection of online ref links
380        '''
381        # at the very least, we need a simple drop down list with no preselected
382        # values
383        logging.debug("Setting up drop down lists for related links")
384        vtds = g.vtd.getValidTypes(g.vtd.ONLINE_REF_CATEGORY)
385        c.relatedLinkTerms = utils.getVocabTermDataDropdown(vtds)
386
387        # ensure we have set up the correct inputs to allow htmlfill to show
388        # the correct selected value
389        for i, link in enumerate(c.atom.relatedLinks):
390            logging.debug("Adding dropdown for related link, '%s'" %(str(link)))
391            refLabel = Atom.ONLINE_REF_LABEL + "." + str(i) + '.rel'
392           
393            # get the value of the selected list
394            self.__setDropDownSelectVal(refLabel, link.rel, vtds)
395
396        logging.debug("Finished setting up drop down lists")
397
398
399    def extractAuthorDetails(self, inputs):
400        '''
401        Retrieve author data from inputs and set appropriately on Atom, if any
402        found
403        @return: list of Person objects with the author data
404        '''
405        logging.info("Extracting author data from inputs")
406        processedAuthors = []
407        authors = []
408        for key in inputs:
409            keyBits = key.split('.')
410            if len(keyBits) == 3 and keyBits[1] not in processedAuthors:
411               
412                authorType = -1
413                if key.lower().startswith('author'):
414                    authorType = Person.AUTHOR_TYPE
415                elif key.lower().startswith('contributor'):
416                    authorType = Person.CONTRIBUTOR_TYPE
417                elif key.lower().startswith('responsible'):
418                    authorType = Person.RESPONSIBLE_PARTY_TYPE
419                else:
420                    continue
421
422                # NB, adding an empty object here is valid as it will clear out
423                # existing entries, potentially
424                author = Person(personType = authorType)
425                # check if the remove checkbox has been set
426                keyStem = ".".join(keyBits[0:2])
427                if inputs.get(keyStem + ".remove"):
428                    logging.info("Removing author data")
429                else:
430                    author.name = inputs.get(keyStem + '.name') or ""
431                    author.uri = inputs.get(keyStem + '.uri') or ""
432                    author.role = inputs.get(keyStem + '.role') or ""
433                   
434                    logging.info("Adding new author info")
435                    logging.debug("Extracted author (type:'%s', name:'%s', uri:'%s', role:'%s')" \
436                                  %(author.type, author.name, author.uri, author.role))
437                authors.append(author)
438                processedAuthors.append(keyBits[1])
439
440        logging.info("Finished extracting author data")
441        return authors
442
443
444    def extractOnlineReferenceDetails(self, inputs):
445        '''
446        Retrieve online reference data from inputs and set appropriately on Atom, if any
447        found
448        @return: list of Link objects containing the extracted data
449        '''
450        logging.info("Extracting related links data from inputs")
451        processedLinks = []
452        links = []
453
454        for key in inputs:
455            keyBits = key.split('.')
456            if len(keyBits) == 3 and keyBits[1] not in processedLinks:
457               
458                if key.lower().startswith(Atom.ONLINE_REF_LABEL):
459                    link = Link()
460                    keyStem = ".".join(keyBits[0:2])
461                   
462                    if inputs.get(keyStem + ".remove"):
463                        logging.info("Removing online reference data")
464                    else:
465                        # NB, this is in the format vocabURL--termID, so requires further
466                        # processing
467                        link.rel = self.getLatestTermURLFromDropDownInput(inputs.get(keyStem + '.rel'))
468                        link.href = inputs.get(keyStem + '.href') or ""
469                        link.title = inputs.get(keyStem + '.title') or ""
470                       
471                        if not link.hasValue():
472                            continue
473                           
474                        logging.info("Adding new online reference info")
475                        logging.debug("Extracted online reference (href:'%s', title:'%s', rel:'%s')" \
476                                      %(link.href, link.title, link.rel))
477                        links.append(link)
478
479                    processedLinks.append(keyBits[1])
480                else:
481                    continue
482
483        logging.info("Finished extracting links data")
484        return links
485
486
487    def extractParameterDetails(self, inputs):
488        '''
489        Retrieve parameters data from inputs and set appropriately on Atom, if any
490        found
491        @return: list of Category objects containing the extracted data
492        '''
493        logging.info("Extracting parameters data from inputs")
494        processedParameters = []
495        parameters = []
496
497        for key in inputs:
498            keyBits = key.split('.')
499            if len(keyBits) == 3 and keyBits[1] not in processedParameters:
500               
501                if key.lower().startswith(Atom.PARAMETER_LABEL):
502                    parameter = Category()
503                    keyStem = ".".join(keyBits[0:2])
504                   
505                    if inputs.get(keyStem + ".remove"):
506                        logging.info("Removing parameters data")
507                    else:
508                        parameter.term = inputs.get(keyStem + '.term') or ""
509                        parameter.scheme = inputs.get(keyStem + '.scheme') or ""
510                        parameter.label = inputs.get(keyStem + '.label') or ""
511                           
512                        logging.info("Adding new parameter info")
513                        logging.debug("Extracted parameter (vocabURL:'%s', label:'%s', term:'%s')" \
514                                      %(parameter.scheme, parameter.label, parameter.term))
515                        parameters.append(parameter)
516
517                    processedParameters.append(keyBits[1])
518                else:
519                    continue
520
521        logging.info("Finished extracting parameters data")
522        return parameters
523
524
525    def extractAtomAssociations(self, inputs):
526        '''
527        Retrieve atom data from inputs and create related links pointing to
528        this data
529        @return: list of Links representing the related atoms
530        '''
531        logging.info("Extracting related atom ID data from inputs")
532        atoms = []
533        processedAtoms = []
534
535        for key in inputs:
536            if key.lower().startswith(Atom.ATOM_REF_LABEL):
537                (x, href, title, rel) = key.split(Atom.DELIMITER)
538                # NB, we handle removes by effectively ignoring them later on
539                if href not in processedAtoms:
540                    processedAtoms.append(href)
541
542                    link = Link()
543                    link.href = href or ""
544                    link.title = title or ""
545                    link.rel = rel or ""
546                   
547                    # adjust href to point to the view, not the edit version
548                    link.href = link.href.replace('editAtom', 'view')
549                   
550                    logging.debug("Extracted atom info (href:'%s', title:'%s', rel:'%s')" \
551                                  %(link.href, link.title, link.rel))
552                    atoms.append(link)
553            else:
554                continue
555
556        logging.info("Finished extracting atoms data")
557        return atoms
558               
559
560    def getLatestTermURLFromDropDownInput(self, inputVal):
561        '''
562        Term ID and vocabURL are specified in the drop down menus
563        - using the input from this, return the lastest full href to the
564        term ID
565        '''
566        termData = inputVal.split('--')
567        return g.vtd.getCurrentVocabURI(termData[0]) + \
568                        "/" + termData[1]
569
570
571    def saveAtomToExist(self, atom, newState = None):
572        '''
573        Save the specified atom in eXist
574        @param atom: atom object to save to eXist
575        @keyword newState:  AtomState publication state to move the atom to, default: None
576        - use the current atom state
577        @return atom: atom object saved in eXist
578        '''
579        logging.info("Saving changes to eXist")
580        eXist = Utilities.getExistClient(atom.ME.providerID)
581       
582        if newState and newState != atom.state:
583            createdAtom = eXist.changeAtomPublicationStateInExist(atom, newState)
584        else:
585            createdAtom = eXist.createAtomInExist(atom)
586        logging.info("Changes successfully saved to eXist")
587        return createdAtom
588
589   
590    def create(self, saveData = None, **inputs):
591        '''
592        Create a new atom
593        '''
594        self._setup()
595        if saveData:
596            logging.info("Validating input")
597            try:
598                validator = CreateAtomFormSchema()
599                validator.to_python(self.inputs)
600                logging.info("- input valid")
601               
602                logging.info("Creating basic atom")
603                atomTypeID = self.inputs.get('atomTypeID').split('--')[1]
604                self.inputs['atomTypeID'] = atomTypeID
605   
606                # activity deployments should have subtype deployment specified automatically
607                if atomTypeID == g.vtd.ACTIVITY_DEPLOYMENT_TERM:
608                    self.inputs['subtypeID'] = g.vtd.DEPLOYMENT_TERM
609                    self.inputs['atomTypeID'] = g.vtd.ACTIVITY_TERM
610                   
611                self.inputs['providerID'] = self.inputs.get('providerID').split('--')[1]
612                atom = self.saveAtomToExist(Atom(**dict(self.inputs)))
613                url = h.url_for('edit', uri = atom.ndgURI, saveData=None)
614               
615                # NB, the redirect throws an exception, so be careful not to catch it
616                h.redirect_to(url)
617            except Invalid, e:
618                c.errors = e.unpack_errors()
619               
620           
621        logging.info("Setting up atom create template")
622        c.title = CREATE_ATOM_TITLE
623       
624        # set up the drop down content - NB, add special case, 'deployment activity'
625        # - this is just a specialised activity - i.e. with subtype preset
626        c.atomTypes = utils.getVocabTermDataDropdown(g.vtd.getValidTypes(g.vtd.ATOM_CATEGORY))
627        c.providerIDs = utils.getVocabTermDataDropdown(g.vtd.getValidTypes(g.vtd.PROVIDER_CATEGORY))
628
629        try:
630            return self.savePageAndRender('atom_editor/atom_creator', **self.inputs)
631
632        except Exception, e:
633            return self._handleError(e)
634
635   
636    def createGranule(self, **inputs):
637        '''
638        Create a new atom from a granulite file
639        '''
640        logging.info("Setting up new atom from granulite template")
641        c.title='Create new data granule atom - from a granulite file'
642        c.errors = {}
643        try:
644            return self.savePageAndRender('atom_editor/atom_granulator', **inputs)
645
646        except Exception, e:
647            return self._handleError(e)
Note: See TracBrowser for help on using the repository browser.