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

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

Improve error handling in the atom controllers + refactor ndgInterface
to remove code from constructor to allow dependency injection - to
improve flexibility and testability. Also, change ambiguous naming
references in classes to minimise codeword overaps/confusion.

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 ndgInterface
17import milk_server.lib.htmlUtilities as utils
18from milk_server.lib.atomutilities import savePageAndRender
19from ndgUtils import ndgObject
20from ndgUtils.models.Atom import Atom, Person, Link, Category
21from ndgUtils.eXistConnector import eXistConnector
22from ndgUtils.lib.atomvalidator import ValidationError
23import ndgUtils.lib.existdbclient as edc
24from ndgUtils.models.MolesEntity import MolesEntity as ME
25from ndgUtils.lib.utilities import escapeSpecialCharacters
26from ndgUtils.vocabtermdata import VocabTermData as VTD
27from ndgUtils.lib.granulite import granulite
28
29class EditatomController(BaseController):
30    '''
31    Provides the pylons controller for editing NDG Atom documents.
32    '''
33    ADD_ASSOCIATIONS = 3
34    REMOVE_ASSOCIATIONS = 4
35   
36    def __setup(self,uri=None):
37        ''' Common setup stuff for all the actions on this controller '''
38        logging.info("Setting up EditatomController")
39        self.cf=request.environ['ndgConfig']
40
41        if uri:
42            try:
43                self.ndgObject = ndgObject(uri, config=self.cf)
44            except ValueError,e:
45                return e
46
47        self.inputs=dict(parse_querystring(request.environ))
48
49        logging.info("EditatomController set up")
50        return 0
51
52
53    def __unpackErrors(self, e):
54        '''
55        Add exception errors to the common error structures - for use
56        in templates
57        @param e: Exception to add
58        '''
59        errorMessage = e.message
60        if g.debugAtomEditor:
61            errorMessage = traceback.format_exc()
62
63        c.xml = escapeSpecialCharacters('Unexpected error loading page [%s]' \
64                                        %str(errorMessage))
65        c.doc = ''
66       
67        # unpack errors, if possible - NB, the c.errors value is used by the template
68        # function, displayErrors, which is used by most of the editor templates
69        if isinstance(e, xmlrpclib.Fault):
70            # strip out the exception type - NB, this is usually native library code
71            # and is of no real interest - and will just confuse viewers
72            c.errors['Unexpected error'] = e.faultString.split(':')[-1] 
73        if hasattr(e, 'unpack_errors'):
74            c.errors.update(e.unpack_errors())
75           
76        else:
77            c.errors['Unexpected error'] = [c.xml]
78
79        # tidy up errors - escaping any xml tags, if necessary
80        for key, errors in c.errors.items():
81            newErrors = []
82            for error in errors:
83                for err in error.split('<br/>'):
84                    newErrors.append(escapeSpecialCharacters(err))
85            c.errors[key] = newErrors
86
87
88    def __handleError(self, e, template='atom_editor/error'):
89        '''
90        Handle exceptions thrown; if debug mode on, display full stack trace
91        in output, otherwise just show basic error message in error template
92        @param e: Exception to process
93        @keyword template: template to render - 'error' is the default - NB, if
94        an alternative is specified it should have a div with class set to 'error'
95        containing the variable, c.xml to display properly
96        '''
97        self.__unpackErrors(e)
98        logging.error(c.xml)
99        response.status_code = 400
100        return render("genshi", template)
101
102   
103    def upload(self, uri):
104        '''
105        Upload a CSML, CDML or granulite file and store it in the session variable
106        NB, if the uri is specified, we're already dealing with an atom
107        (which this refers to) - so the file is not a granulite - since
108        this is used to create an atom from scratch
109        '''
110        logging.info("Uploading file...")
111        self.__setup(uri=uri)
112        inputs = self.__getTidyInputs()
113       
114        granFile = request.POST.get('upload_granulite')
115        csmlOrCdmlFile = request.POST.get('CSMLOrCDML')
116         
117        c.errors = {}
118        try:
119            useCSMLID = True
120            c.atom = None
121            if uri:
122                self.prepareDataModel(uri)
123                useCSMLID = False
124
125            if (granFile == '' or granFile == None) and \
126                (csmlOrCdmlFile == '' or csmlOrCdmlFile == None):
127                errorMessage = "Error: could not load file - please try again"
128                logging.error(errorMessage)
129                raise IOError(errorMessage)
130            else:
131                # Prepare the basic data model
132                # NB, if loading a granulite, this will create the displayed atom
133                # with the ID taken from the CSML file, if specified
134                fileContents = None
135                if (granFile is not None and granFile != ''):
136                    fileContents = granFile.value
137                   
138                # use the granulite helper class to add either the full granulite
139                # data or just the CSML/CDML data
140                # NB, we'll be creating the atom in the default local eXist
141                eXistClient = self.__getExistClient('local')
142                gran = granulite(fileContents, granuleAtom = c.atom, \
143                                 eXistClient = eXistClient, \
144                                 csmlOrCdmlFile = csmlOrCdmlFile, \
145                                 timeAxis = inputs.get('timeAxis'), \
146                                 datasetID = inputs.get('datasetID'), \
147                                 useCSMLID = useCSMLID)
148
149                # now process the input file and add any extra required data
150                if uri:
151                    c.atom = gran.processCSMLOrCDMLFile()
152
153                    # save new data - NB, for granulites, this is done as part of the
154                    # processing steps
155                    self.saveAtomToExist(c.atom)
156                else:
157                    c.atom = gran.processGranulite()
158
159                    # Now set up the ndgObject with the created atom's vals
160                    self.__setup(uri=c.atom.ndgURI)
161                    c.atom.ndgObject = self.ndgObject
162                           
163                # now do redirection - NB, this ensures that current atom contents are
164                # reloaded and displayed
165                logging.info("File data loaded and extracted to atom")
166        except Exception, e:
167            c.errors['WARNING'] = ['Error loading data: the displayed data will not be saved - please fix problem and retry']
168            self.__unpackErrors(e)
169        except SystemExit, ee:
170            # NB, some of the CSML libraries just sys.exit on problems - catch errors here
171            c.errors['ERROR'] = ['Problem encountered whilst transforming the CDML data into CSML']
172            self.__unpackErrors(ee)
173
174        if c.atom and hasattr(c.atom, 'ndgURI'):
175            self.pathInfo = self.pathInfo.replace('upload', 'editAtom')
176            return self.edit(c.atom.ndgURI)
177        elif uri:
178            # something has gone wrong here...
179            return render("genshi", 'atom_editor/error')
180        else:
181            return self.createGranule()
182
183
184    def __getTidyInputs(self):
185        '''
186        The inputs can be used generically to specify atom attributes - however
187        some inputs should not be set against the Atom object since they use slots
188        so cannot be pickled - which means saving the atom in the session with fail.
189        This method clears out any unwanted inputs
190        '''
191        logging.debug("Getting pickleable input data")
192        inputs = request.params
193        tidyInputs = {}
194        for key, val in inputs.items():
195            if not isinstance(val, cgi.FieldStorage):
196                tidyInputs[key] = val
197        logging.debug("Pickleable data extracted")
198        return tidyInputs
199
200   
201    def saveAtom(self, uri, saveLevel=0):
202        '''
203        Save the atom contents - NB, validation is done by method decoration
204        - if this fails, the action is reran as a GET with htmlfill auto-populating
205        the fields to keep them as they were before the submission
206        '''
207        logging.info("Saving input atom data")
208        c.errors = {}
209        try:
210            self.prepareDataModel(uri)
211        except SystemError, e:
212            return self.__handleError(e)
213       
214        inputs = request.params
215
216        # save atom association changes
217        if int(saveLevel) == self.ADD_ASSOCIATIONS:
218            atomLinks = self.extractAtomAssociations(inputs)
219            c.atom.addUniqueRelatedLinks(atomLinks)
220        elif int(saveLevel) == self.REMOVE_ASSOCIATIONS:
221            atomLinks = self.extractAtomAssociations(inputs)
222            c.atom.removeRelatedLinks(atomLinks)
223        else:
224            authors = self.extractAuthorDetails(inputs)
225            c.atom.addAuthors(authors)
226   
227            onlineRefs = self.extractOnlineReferenceDetails(inputs)
228            c.atom.addOnlineReferences(onlineRefs)
229
230            params = self.extractParameterDetails(inputs)
231            # NB, the atom type and subtype are added to the categories when the
232            # atom is exported to XML - so don't need to worry about overwriting
233            # them now
234            c.atom.parameters = params
235           
236            if inputs.get('subtype'):
237                c.atom.subtype = self.getLatestTermURLFromDropDownInput( \
238                        inputs.get('subtype'))
239                c.atom.subtypeID = c.atom.subtype.split('/')[-1]
240
241        logging.info("Validating input")
242        try:
243            g.validator.setAtom(c.atom)
244            g.validator.validateAtom()
245            logging.info("- input valid")
246
247            self.saveAtomToExist(c.atom)
248        except Exception, e:
249            self.__unpackErrors(e)
250            logging.info("- input invalid")
251            return self.edit(uri)
252                   
253        # now do redirection - NB, this ensures that current atom contents are
254        # reloaded and displayed
255        h.redirect_to(controller = 'atom_editor/editatom', action='edit', \
256                        uri = c.atom.ndgURI)
257
258
259    def prepareDataModel(self, uri):
260        '''
261        Set up the underlying atom data model - loading the bulk from eXist
262        then updating any input fields appropriately
263        '''
264        logging.info("Preparing underlying data model")
265        status=self.__setup(uri=uri)
266        if status:
267            c.xml='<p>%s</p>'%status
268            response.status_code = 400
269            raise SystemError('Problem experienced setting up data model')
270
271        logging.info("Retrieving document to edit")
272        # NB, don't use the cache as docs are likely to change
273        # quite a lot during editing; ensure you've always got
274        # the latest updates loaded
275        interface = ndgInterface()
276        status,x = interface.GetXML(uri, useCache=False)
277
278        if not status:
279            code=400
280            if x.startswith('<p> Access Denied'):
281                code=401
282
283            c.xml='%s'%x
284            response.status_code = code
285            raise SystemError('Problem experienced retrieving atom doc from eXist')
286
287        # NB, passing in the inputs will overwrite any original values with the
288        # user input ones
289        inputs = self.__getTidyInputs()
290       
291        c.atom = Atom(xmlString=str(x), ndgObject = self.ndgObject, **dict(inputs))
292       
293        # save the current atom - to avoid this needing be recreated by the
294        # asynch viewAssociatedData call
295        session['currentAtom'] = c.atom
296        session.save()
297        logging.info("Data model set up")
298
299
300    def prepareEditForm(self, uri):
301        '''
302        Get everything set up for displaying the edit form
303        @param uri: ndg url for the atom to load in edit mode
304        '''
305        if not c.errors:
306            c.errors = {}
307
308        # NB, can get here directly from saveAtom - if there have been errors
309        # - in this case keep original data
310        if not c.atom:
311            self.prepareDataModel(uri)
312           
313        c.title='Editing [%s]' %c.atom.ndgURI
314        c.uri = c.atom.ndgURI
315       
316        c.saveLink = h.url_for(controller='atom_editor/editatom',action='saveAtom', \
317                               saveLevel='1',  uri = c.atom.ndgURI)
318        c.saveLink2 = h.url_for(controller='atom_editor/editatom',action='saveAtom', saveLevel='2')
319        c.saveAssoc = h.url_for(controller='atom_editor/editatom',action='saveAtom', \
320                                 saveLevel = self.REMOVE_ASSOCIATIONS)
321        c.deploymentsURL = h.url_for(controller='browse/retrieve', \
322                                     action='viewAssociatedData', \
323                                     type = VTD.DEPLOYMENT_TERM, \
324                                     uri = c.atom.ndgURI)
325        c.dataEntitiesURL = h.url_for(controller='browse/retrieve', \
326                                     action='viewAssociatedData', \
327                                     type = VTD.DE_TERM, \
328                                     uri = c.atom.ndgURI)
329       
330        # adjust atom type to cope with activity deployment exception
331        atomType = c.atom.atomTypeID
332        if atomType == g.vtd.ACTIVITY_TERM and \
333            c.atom.subtypeID == g.vtd.DEPLOYMENT_TERM:
334            atomType = g.vtd.DEPLOYMENT_TERM
335       
336        c.addEntityLink = h.url_for(controller='atom_editor/listatom',action='list', searchData = '0', \
337                               associatedAtomID = c.atom.ndgURI, \
338                               associatedAtomType = atomType, 
339                               associationType = utils.ENTITY_ASSOCIATION)
340           
341        c.addGranuleLink = h.url_for(controller='atom_editor/listatom',action='list', searchData = '0', \
342                               associatedAtomID = c.atom.ndgURI, \
343                               associatedAtomType = atomType, 
344                               associationType = utils.GRANULE_ASSOCIATION)
345           
346        c.addDeploymentLink = h.url_for(controller='atom_editor/listatom',action='list', searchData = '0', \
347                               associatedAtomID = c.atom.ndgURI, \
348                               associatedAtomType = atomType, 
349                               associationType = utils.DEPLOYMENT_ASSOCIATION)
350
351        # account for special case where we're dealing with deployments
352        listVals = g.vtd.getValidSubTypes(c.atom.atomTypeID)
353        if c.atom.isDeployment():
354            listVals = [g.vtd.TERM_DATA[g.vtd.DEPLOYMENT_TERM]]
355
356        c.subTypes = utils.getVocabTermDataDropdown(listVals, \
357                                        selected=c.atom.subtype)
358       
359        self.addRelatedLinksDropDowns()
360
361   
362    def edit(self, uri):
363        '''
364        Edit the specified uri
365        '''
366        logging.info("Setting up atom edit template")
367        try:
368            self.prepareEditForm(uri)
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        except SystemError, e:
376            return self.__handleError(e)
377        except Exception, e:
378            errorMessage = traceback.format_exc()
379            c.xml='Unexpected error [%s] viewing [%s]'%(str(e), uri)
380            c.doc=''
381            logging.error(c.xml)
382       
383        response.status_code = 400
384        return render("genshi", 'atom_editor/error')
385
386
387    def addRelatedLinksDropDowns(self):
388        '''
389        Set up the drop down lists required for the selection of online ref links
390        '''
391        # at the very least, we need a simple drop down list with no preselected
392        # values
393        logging.debug("Setting up drop down lists for related links")
394        c.relatedLinkTerms = utils.getVocabTermDataDropdown(\
395                g.vtd.getValidTypes(g.vtd.ONLINE_REF_CATEGORY))
396       
397        c.relatedLinkSelectedLists = {}
398        for link in c.atom.relatedLinks:
399            logging.debug("Adding dropdown for related link, '%s'" %(str(link)))
400            c.relatedLinkSelectedLists[str(link)] = \
401                utils.getVocabTermDataDropdown(g.vtd.getValidTypes(g.vtd.ONLINE_REF_CATEGORY), \
402                                                     selected=link.rel)
403
404        logging.debug("Finished setting up drop down lists")
405
406
407    def extractAuthorDetails(self, inputs):
408        '''
409        Retrieve author data from inputs and set appropriately on Atom, if any
410        found
411        @return: list of Person objects with the author data
412        '''
413        logging.info("Extracting author data from inputs")
414        processedAuthors = []
415        authors = []
416        for key in inputs:
417            keyBits = key.split('.')
418            if len(keyBits) == 3 and keyBits[1] not in processedAuthors:
419               
420                authorType = -1
421                if key.lower().startswith('author'):
422                    authorType = Person.AUTHOR_TYPE
423                elif key.lower().startswith('contributor'):
424                    authorType = Person.CONTRIBUTOR_TYPE
425                elif key.lower().startswith('responsible'):
426                    authorType = Person.RESPONSIBLE_PARTY_TYPE
427                else:
428                    continue
429
430                # NB, adding an empty object here is valid as it will clear out
431                # existing entries, potentially
432                author = Person(personType = authorType)
433                # check if the remove checkbox has been set
434                keyStem = ".".join(keyBits[0:2])
435                if inputs.get(keyStem + ".remove"):
436                    logging.info("Removing author data")
437                else:
438                    author.name = inputs.get(keyStem + '.name') or ""
439                    author.uri = inputs.get(keyStem + '.uri') or ""
440                    author.role = inputs.get(keyStem + '.role') or ""
441                   
442                    logging.info("Adding new author info")
443                    logging.debug("Extracted author (type:'%s', name:'%s', uri:'%s', role:'%s')" \
444                                  %(author.type, author.name, author.uri, author.role))
445                authors.append(author)
446                processedAuthors.append(keyBits[1])
447
448        logging.info("Finished extracting author data")
449        return authors
450
451
452    def extractOnlineReferenceDetails(self, inputs):
453        '''
454        Retrieve online reference data from inputs and set appropriately on Atom, if any
455        found
456        @return: list of Link objects containing the extracted data
457        '''
458        logging.info("Extracting related links data from inputs")
459        processedLinks = []
460        links = []
461
462        for key in inputs:
463            keyBits = key.split('.')
464            if len(keyBits) == 3 and keyBits[1] not in processedLinks:
465               
466                if key.lower().startswith(Atom.ONLINE_REF_LABEL):
467                    link = Link()
468                    keyStem = ".".join(keyBits[0:2])
469                   
470                    if inputs.get(keyStem + ".remove"):
471                        logging.info("Removing online reference data")
472                    else:
473                        # NB, this is in the format vocabURL--termID, so requires further
474                        # processing
475                        link.rel = self.getLatestTermURLFromDropDownInput(inputs.get(keyStem + '.rel'))
476                        link.href = inputs.get(keyStem + '.href') or ""
477                        link.title = inputs.get(keyStem + '.title') or ""
478                       
479                        if not link.hasValue():
480                            continue
481                           
482                        logging.info("Adding new online reference info")
483                        logging.debug("Extracted online reference (href:'%s', title:'%s', rel:'%s')" \
484                                      %(link.href, link.title, link.rel))
485                        links.append(link)
486
487                    processedLinks.append(keyBits[1])
488                else:
489                    continue
490
491        logging.info("Finished extracting links data")
492        return links
493
494
495    def extractParameterDetails(self, inputs):
496        '''
497        Retrieve parameters data from inputs and set appropriately on Atom, if any
498        found
499        @return: list of Category objects containing the extracted data
500        '''
501        logging.info("Extracting parameters data from inputs")
502        processedParameters = []
503        parameters = []
504
505        for key in inputs:
506            keyBits = key.split('.')
507            if len(keyBits) == 3 and keyBits[1] not in processedParameters:
508               
509                if key.lower().startswith(Atom.PARAMETER_LABEL):
510                    parameter = Category()
511                    keyStem = ".".join(keyBits[0:2])
512                   
513                    if inputs.get(keyStem + ".remove"):
514                        logging.info("Removing parameters data")
515                    else:
516                        parameter.term = inputs.get(keyStem + '.term') or ""
517                        parameter.scheme = inputs.get(keyStem + '.scheme') or ""
518                        parameter.label = inputs.get(keyStem + '.label') or ""
519                           
520                        logging.info("Adding new parameter info")
521                        logging.debug("Extracted parameter (vocabURL:'%s', label:'%s', term:'%s')" \
522                                      %(parameter.scheme, parameter.label, parameter.term))
523                        parameters.append(parameter)
524
525                    processedParameters.append(keyBits[1])
526                else:
527                    continue
528
529        logging.info("Finished extracting parameters data")
530        return parameters
531
532
533    def extractAtomAssociations(self, inputs):
534        '''
535        Retrieve atom data from inputs and create related links pointing to
536        this data
537        @return: list of Links representing the related atoms
538        '''
539        logging.info("Extracting related atom ID data from inputs")
540        atoms = []
541        processedAtoms = []
542
543        for key in inputs:
544            if key.lower().startswith(Atom.ATOM_REF_LABEL):
545                (x, href, title, rel) = key.split(Atom.DELIMITER)
546                # NB, we handle removes by effectively ignoring them later on
547                if href not in processedAtoms:
548                    processedAtoms.append(href)
549
550                    link = Link()
551                    link.href = href or ""
552                    link.title = title or ""
553                    link.rel = rel or ""
554                   
555                    # adjust href to point to the view, not the edit version
556                    link.href = link.href.replace('editAtom', 'view')
557                   
558                    logging.debug("Extracted atom info (href:'%s', title:'%s', rel:'%s')" \
559                                  %(link.href, link.title, link.rel))
560                    atoms.append(link)
561            else:
562                continue
563
564        logging.info("Finished extracting atoms data")
565        return atoms
566               
567
568    def getLatestTermURLFromDropDownInput(self, inputVal):
569        '''
570        Term ID and vocabURL are specified in the drop down menus
571        - using the input from this, return the lastest full href to the
572        term ID
573        '''
574        termData = inputVal.split('--')
575        return g.vtd.getCurrentVocabURI(termData[0]) + \
576                        "/" + termData[1]
577
578
579    def saveAtomToExist(self, atom):
580        '''
581        Save the specified atom in eXist
582        @param atom: atom object to save to eXist
583        @return atom: atom object saved in eXist
584        '''
585        logging.info("Saving changes to eXist")
586        eXist = self.__getExistClient(atom.ME.providerID)
587        createdAtom = eXist.createAtomInExist(atom)
588        logging.info("Changes successfully saved to eXist")
589        return createdAtom
590
591
592    def __getExistClient(self, providerID):
593        '''
594        Use the config data to set up and return an eXist client
595        '''
596        logging.info("Setting up eXist client to provider, '%s'" %providerID)
597        # firstly check if there is a current connection available
598        eXistClient = g.eXistDBCons.get(providerID)
599        if eXistClient:
600            logging.info("- found existing client to provider - returning this")
601            return eXistClient
602       
603        # lookup the eXist DB to use according to the provider ID for the
604        # data - NB, this is specified in the milk.config file
605        existHost = self.cf.get('NDG_EXIST', providerID)
606        configFile = self.cf.get('NDG_EXIST','passwordFile')
607        eXistClient = edc.eXistDBClient(eXistDBHostname = existHost, \
608                                       configFile = configFile)
609        # add the client to the global variables for re-use
610        g.eXistDBCons[providerID] = eXistClient
611        logging.info("Returning eXist client")
612        return eXistClient
613
614   
615    def create(self, saveData = None, **inputs):
616        '''
617        Create a new atom
618        '''
619        if saveData:
620            logging.info("Validating input")
621            try:
622                inputs = request.params
623                validator = CreateAtomFormSchema()
624                validator.to_python(inputs)
625                logging.info("- input valid")
626               
627                logging.info("Creating basic atom")
628                self.__setup()
629                atomTypeID = inputs.get('atomTypeID').split('--')[1]
630                inputs['atomTypeID'] = atomTypeID
631   
632                # activity deployments should have subtype deployment specified automatically
633                if atomTypeID == g.vtd.ACTIVITY_DEPLOYMENT_TERM:
634                    inputs['subtypeID'] = g.vtd.DEPLOYMENT_TERM
635                    inputs['atomTypeID'] = g.vtd.ACTIVITY_TERM
636                   
637                inputs['providerID'] = inputs.get('providerID').split('--')[1]
638                atom = self.saveAtomToExist(Atom(**dict(inputs)))
639               
640                h.redirect_to(controller = 'atom_editor/editatom', action='edit',
641                               uri = atom.ndgURI)
642            except Invalid, e:
643                c.errors = e.unpack_errors()
644               
645           
646        logging.info("Setting up atom create template")
647        c.title='Create new atom'
648       
649        # set up the drop down content - NB, add special case, 'deployment activity'
650        # - this is just a specialised activity - i.e. with subtype preset
651        c.atomTypes = utils.getVocabTermDataDropdown(g.vtd.getValidTypes(g.vtd.ATOM_CATEGORY),
652                                               selected = inputs.get('atomTypeID'))
653        c.providerIDs = utils.getVocabTermDataDropdown(
654                            g.vtd.getValidTypes(g.vtd.PROVIDER_CATEGORY), 
655                            selected = inputs.get('providerID'))
656
657        try:
658            return savePageAndRender(self.pathInfo, 'atom_editor/atom_creator')
659
660        except Exception, e:
661            return self.__handleError(e)
662
663   
664    def createGranule(self, **inputs):
665        '''
666        Create a new atom from a granulite file
667        '''
668        logging.info("Setting up new atom from granulite template")
669        c.title='Create new data granule atom - from a granulite file'
670        c.errors = {}
671        try:
672            return savePageAndRender(self.pathInfo, 'atom_editor/atom_granulator')
673
674        except Exception, e:
675            return self.__handleError(e)
Note: See TracBrowser for help on using the repository browser.