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

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

Update MILK stack to use the new ndgCommon clients suite + improve
tests by adding scaffold to do proper tidyups after tests run.

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.abstractxmldbatomclient as ac
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 ac.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(e)
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.changeAtomPublicationState(atom, newState)
584        else:
585            createdAtom = eXist.createAtom(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.