source: TI05-delivery/ows_framework/trunk/ows_server/ows_server/controllers/editatom.py @ 4315

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI05-delivery/ows_framework/trunk/ows_server/ows_server/controllers/editatom.py@4315
Revision 4315, 19.0 KB checked in by cbyrom, 11 years ago (diff)

Add new template and controller code for adding and editing parameters +
fix data entities so they can properly associate granules and deployments with
them + add new form module to handle all the form validations and implement
basics for the create atom form.

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'''
7from ows_server.lib.base import *
8from ows_server.models.form import *
9from ows_server.models import Utilities
10from ndgUtils import ndgObject
11from paste.request import parse_querystring
12from ows_server.lib import mailer
13from ows_server.lib.ndgInterface import interface
14from xml.parsers.expat import ExpatError
15import logging
16from ndgUtils.models.Atom import Atom, Person, Link, Category
17from formencode import Invalid
18from genshi.filters import HTMLFormFiller
19from genshi import HTML
20import ndgUtils.models.existdbclient as edc
21from ndgUtils.models.MolesEntity import MolesEntity as ME
22from ndgUtils.models.utilities import escapeSpecialCharacters
23from ndgUtils.vocabtermdata import VocabTermData as VTD
24import ows_server.templates.htmlUtilities as utils
25
26class EditatomController(BaseController):
27    '''
28    Provides the pylons controller for editing NDG Atom documents.
29    '''
30    ADD_ASSOCIATIONS = 3
31    REMOVE_ASSOCIATIONS = 4
32   
33    def __setup(self,uri=None):
34        ''' Common setup stuff for all the actions on this controller '''
35        logging.info("Setting up EditatomController")
36        self.cf=request.environ['ndgConfig']
37
38        if not c.vtd:
39            c.vtd = VTD()
40       
41        if uri:
42            try:
43                self.ndgObject = ndgObject(uri, config=self.cf.config)
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    @validate(schema=AtomFormSchema(), form='saveAtom')
54    def saveAtom(self, uri, saveLevel=0):
55        '''
56        Save the atom contents - NB, validation is done by method decoration
57        - if this fails, the action is reran as a GET with htmlfill auto-populating
58        the fields to keep them as they were before the submission
59        '''
60        logging.info("Saving input atom data")
61        c.errors = {}
62        try:
63            self.prepareDataModel(uri)
64        except SystemError, e:
65            logging.error(e.message)
66            return render('error')
67       
68        inputs = request.params
69
70        # save atom association changes
71        if int(saveLevel) == self.ADD_ASSOCIATIONS:
72            atomLinks = self.extractAtomAssociations(inputs)
73            c.atom.addUniqueRelatedLinks(atomLinks)
74        elif int(saveLevel) == self.REMOVE_ASSOCIATIONS:
75            atomLinks = self.extractAtomAssociations(inputs)
76            c.atom.removeRelatedLinks(atomLinks)
77        else:
78            authors = self.extractAuthorDetails(inputs)
79            if authors:
80                c.atom.addAuthors(authors)
81   
82            onlineRefs = self.extractOnlineReferenceDetails(inputs)
83            c.atom.addOnlineReferences(onlineRefs)
84
85            params = self.extractParameterDetails(inputs)
86            # NB, the atom type and subtype are added to the categories when the
87            # atom is exported to XML - so don't need to worry about overwriting
88            # them now
89            c.atom.parameters = params
90           
91            if inputs.get('subtype'):
92                c.atom.subtype = self.getLatestTermURLFromDropDownInput( \
93                        inputs.get('subtype'))
94                c.atom.subtypeID = c.atom.subtype.split('/')[-1]
95
96        self.saveAtomToExist(c.atom)
97
98                   
99        # now do redirection - NB, this ensures that current atom contents are
100        # reloaded and displayed
101        h.redirect_to(controller = 'editatom', action='edit', \
102                        uri = c.atom.ndgURI)
103
104
105    def prepareDataModel(self, uri):
106        '''
107        Set up the underlying atom data model - loading the bulk from eXist
108        then updating any input fields appropriately
109        '''
110        logging.info("Preparing underlying data model")
111        status=self.__setup(uri=uri)
112        if status:
113            c.xml='<p>%s</p>'%status
114            response.status_code = 400
115            raise SystemError('Problem experienced setting up data model')
116
117        logging.info("Retrieving document to edit")
118        # NB, don't use the cache as docs are likely to change
119        # quite a lot during editing; ensure you've always got
120        # the latest updates loaded
121        status,x = interface.GetXML(uri, useCache=False)
122
123        if not status:
124            code=400
125            if x.startswith('<p> Access Denied'):
126                code=401
127
128            c.xml='%s'%x
129            response.status_code = code
130            raise SystemError('Problem experienced retrieving atom doc from eXist')
131
132        # NB, passing in the inputs will overwrite any original values with the
133        # user input ones
134        inputs = request.params
135        c.atom = Atom(xmlString=str(x), ndgObject = self.ndgObject, **dict(inputs))
136        logging.info("Data model set up")
137
138   
139    @validate(schema=AtomFormSchema(), form='edit')
140    def edit(self, uri, saveData=0, **data):
141        '''
142        Edit the specified uri
143        '''
144        logging.info("Setting up atom edit template")
145        c.errors = {}
146        try:
147            self.prepareDataModel(uri)
148        except SystemError, e:
149            logging.error(e.message)
150            return render('error')
151
152        c.title='Editing [%s]'%self.ndgObject
153       
154        c.saveLink = h.url_for(controller='editatom',action='saveAtom', \
155                               saveLevel='1',  uri = c.atom.ndgURI)
156        c.saveLink2 = h.url_for(controller='editatom',action='saveAtom', saveLevel='2')
157        c.saveAssoc = h.url_for(controller='editatom',action='saveAtom', \
158                                 saveLevel = self.REMOVE_ASSOCIATIONS)
159       
160        # adjust atom type to cope with activity deployment exception
161        atomType = c.atom.atomTypeID
162        c.isDE = False
163        c.isGranule = False
164        if atomType == c.vtd.ACTIVITY_TERM and \
165            c.atom.subtypeID == c.vtd.DEPLOYMENT_TERM:
166            atomType = c.vtd.DEPLOYMENT_TERM
167       
168        elif atomType == c.vtd.DE_TERM:
169            c.isDE = True
170        elif atomType == c.vtd.GRANULE_TERM:
171            c.isGranule = True
172           
173        c.addEntityLink = h.url_for(controller='listatom',action='list', searchData = '0', \
174                               associatedAtomID = c.atom.ndgURI, \
175                               associatedAtomType = atomType, 
176                               associationType = utils.ENTITY_ASSOCIATION)
177           
178        c.addGranuleLink = h.url_for(controller='listatom',action='list', searchData = '0', \
179                               associatedAtomID = c.atom.ndgURI, \
180                               associatedAtomType = atomType, 
181                               associationType = utils.GRANULE_ASSOCIATION)
182           
183        c.addDeploymentLink = h.url_for(controller='listatom',action='list', searchData = '0', \
184                               associatedAtomID = c.atom.ndgURI, \
185                               associatedAtomType = atomType, 
186                               associationType = utils.DEPLOYMENT_ASSOCIATION)
187
188        # account for special case where we're dealing with deployments
189        c.notDeployment = True
190        listVals = c.vtd.getValidSubTypes(c.atom.atomTypeID)
191        if c.atom.subtype:
192            subtypeTermID = c.vtd.getTermItemfromFullVocabURI(c.atom.subtype).termID
193            if subtypeTermID == c.vtd.getVTI(c.vtd.DEPLOYMENT_TERM).termID:
194                listVals = [c.vtd.TERM_DATA[c.vtd.DEPLOYMENT_TERM]]
195                c.notDeployment = False
196        c.subTypes = utils.getVocabTermDataDropdown(listVals, \
197                                        selected=c.atom.subtype)
198       
199       
200        self.addRelatedLinksDropDowns()
201
202        try:
203            return render("genshi", 'atom_editor')
204       
205        except ExpatError, e:
206            c.xml='XML content is not well formed'
207            c.doc=str(x)
208            logging.error("Error retrieving [%s] - XML content: %s" % (uri, e))
209
210        except Exception, e:
211            #we may be showing an xml document ... but it could go wrong if
212            #we have crap content ...
213            c.xml='Unexpected error [%s] viewing [%s]'%(str(e), uri)
214            c.doc=''
215            logging.error(c.xml)
216       
217        response.status_code = 400
218        return render('error')
219
220
221    def addRelatedLinksDropDowns(self):
222        '''
223        Set up the drop down lists required for the selection of online ref links
224        '''
225        # at the very least, we need a simple drop down list with no preselected
226        # values
227        logging.debug("Setting up drop down lists for related links")
228        c.relatedLinkTerms = utils.getVocabTermDataDropdown(\
229                c.vtd.getValidTypes(c.vtd.ONLINE_REF_CATEGORY))
230       
231        c.relatedLinkSelectedLists = {}
232        for link in c.atom.relatedLinks:
233            logging.debug("Adding dropdown for related link, '%s'" %(str(link)))
234            c.relatedLinkSelectedLists[str(link)] = \
235                utils.getVocabTermDataDropdown(c.vtd.getValidTypes(c.vtd.ONLINE_REF_CATEGORY), \
236                                                     selected=link.rel)
237
238        logging.debug("Finished setting up drop down lists")
239
240
241    def extractAuthorDetails(self, inputs):
242        '''
243        Retrieve author data from inputs and set appropriately on Atom, if any
244        found
245        @return: list of Person objects with the author data
246        '''
247        logging.info("Extracting author data from inputs")
248        processedAuthors = []
249        authors = []
250        for key in inputs:
251            keyBits = key.split('.')
252            if len(keyBits) == 3 and keyBits[1] not in processedAuthors:
253               
254                authorType = -1
255                if key.lower().startswith('author'):
256                    authorType = Person.AUTHOR_TYPE
257                elif key.lower().startswith('contributor'):
258                    authorType = Person.CONTRIBUTOR_TYPE
259                elif key.lower().startswith('responsible'):
260                    authorType = Person.RESPONSIBLE_PARTY_TYPE
261                else:
262                    continue
263
264                # NB, adding an empty object here is valid as it will clear out
265                # existing entries, potentially
266                author = Person(personType = authorType)
267                # check if the remove checkbox has been set
268                keyStem = ".".join(keyBits[0:2])
269                if inputs.get(keyStem + ".remove"):
270                    logging.info("Removing author data")
271                else:
272                    author.name = inputs.get(keyStem + '.name') or ""
273                    author.uri = inputs.get(keyStem + '.uri') or ""
274                    author.role = inputs.get(keyStem + '.role') or ""
275                   
276                    logging.info("Adding new author info")
277                    logging.debug("Extracted author (type:'%s', name:'%s', uri:'%s', role:'%s')" \
278                                  %(author.type, author.name, author.uri, author.role))
279                    authors.append(author)
280                processedAuthors.append(keyBits[1])
281
282        logging.info("Finished extracting author data")
283        return authors
284
285
286    def extractOnlineReferenceDetails(self, inputs):
287        '''
288        Retrieve online reference data from inputs and set appropriately on Atom, if any
289        found
290        @return: list of Link objects containing the extracted data
291        '''
292        logging.info("Extracting related links data from inputs")
293        processedLinks = []
294        links = []
295
296        for key in inputs:
297            keyBits = key.split('.')
298            if len(keyBits) == 3 and keyBits[1] not in processedLinks:
299               
300                if key.lower().startswith(Atom.ONLINE_REF_LABEL):
301                    link = Link()
302                    keyStem = ".".join(keyBits[0:2])
303                   
304                    if inputs.get(keyStem + ".remove"):
305                        logging.info("Removing online reference data")
306                    else:
307                        # NB, this is in the format vocabURL--termID, so requires further
308                        # processing
309                        link.rel = self.getLatestTermURLFromDropDownInput(inputs.get(keyStem + '.rel'))
310                        link.href = inputs.get(keyStem + '.href') or ""
311                        link.title = inputs.get(keyStem + '.title') or ""
312                           
313                        logging.info("Adding new online reference info")
314                        logging.debug("Extracted online reference (href:'%s', title:'%s', rel:'%s')" \
315                                      %(link.href, link.title, link.rel))
316                        links.append(link)
317
318                    processedLinks.append(keyBits[1])
319                else:
320                    continue
321
322        logging.info("Finished extracting links data")
323        return links
324
325
326    def extractParameterDetails(self, inputs):
327        '''
328        Retrieve parameters data from inputs and set appropriately on Atom, if any
329        found
330        @return: list of Category objects containing the extracted data
331        '''
332        logging.info("Extracting parameters data from inputs")
333        processedParameters = []
334        parameters = []
335
336        for key in inputs:
337            keyBits = key.split('.')
338            if len(keyBits) == 3 and keyBits[1] not in processedParameters:
339               
340                if key.lower().startswith(Atom.PARAMETER_LABEL):
341                    parameter = Category()
342                    keyStem = ".".join(keyBits[0:2])
343                   
344                    if inputs.get(keyStem + ".remove"):
345                        logging.info("Removing parameters data")
346                    else:
347                        parameter.term = inputs.get(keyStem + '.term') or ""
348                        parameter.scheme = inputs.get(keyStem + '.scheme') or ""
349                        parameter.label = inputs.get(keyStem + '.label') or ""
350                           
351                        logging.info("Adding new parameter info")
352                        logging.debug("Extracted parameter (vocabURL:'%s', label:'%s', term:'%s')" \
353                                      %(parameter.scheme, parameter.label, parameter.term))
354                        parameters.append(parameter)
355
356                    processedParameters.append(keyBits[1])
357                else:
358                    continue
359
360        logging.info("Finished extracting parameters data")
361        return parameters
362
363
364    def extractAtomAssociations(self, inputs):
365        '''
366        Retrieve atom data from inputs and create related links pointing to
367        this data
368        @return: list of Links representing the related atoms
369        '''
370        logging.info("Extracting related atom ID data from inputs")
371        atoms = []
372        processedAtoms = []
373
374        for key in inputs:
375            if key.lower().startswith(Atom.ATOM_REF_LABEL):
376                (x, href, title, rel) = key.split(Atom.DELIMITER)
377                # NB, we handle removes by effectively ignoring them later on
378                if href not in processedAtoms:
379                    processedAtoms.append(href)
380
381                    link = Link()
382                    link.href = href or ""
383                    link.title = title or ""
384                    link.rel = rel or ""
385                   
386                    logging.debug("Extracted atom info (href:'%s', title:'%s', rel:'%s')" \
387                                  %(link.href, link.title, link.rel))
388                    atoms.append(link)
389            else:
390                continue
391
392        logging.info("Finished extracting atoms data")
393        return atoms
394               
395
396    def getLatestTermURLFromDropDownInput(self, inputVal):
397        '''
398        Term ID and vocabURL are specified in the drop down menus
399        - using the input from this, return the lastest full href to the
400        term ID
401        '''
402        termData = inputVal.split('--')
403        return c.vtd.getCurrentVocabURI(termData[0]) + \
404                        "/" + termData[1]
405
406
407    def saveAtomToExist(self, atom):
408        '''
409        Save the specified atom in eXist
410        @param atom: atom object to save to eXist
411        @return atom: atom object saved in eXist
412        '''
413        logging.info("Saving changes to eXist")
414        # lookup the eXist DB to use according to the provider ID for the
415        # data - NB, this is specified in the ndgDiscovery.config file
416        self.existHost = self.cf.get('NDG_EXIST', atom.ME.providerID)
417        self.configFile = self.cf.get('NDG_EXIST','passwordFile')
418        self.eXist = edc.eXistDBClient(eXistDBHostname = self.existHost, \
419                                       configFile = self.configFile)
420        createdAtom = self.eXist.createAtomInExist(atom)
421        logging.info("Changes successfully saved to eXist")
422        return createdAtom
423       
424   
425    def create(self, saveData = None, **inputs):
426        '''
427        Create a new atom
428        '''
429        if saveData:
430            logging.info("Validating input")
431            try:
432                inputs = request.params
433                validator = CreateAtomFormSchema()
434                validator.to_python(inputs)
435                logging.info("- input valid")
436               
437                logging.info("Creating basic atom")
438                self.__setup()
439                atomTypeID = inputs.get('atomTypeID').split('--')[1]
440                inputs['atomTypeID'] = atomTypeID
441   
442                # activity deployments should have subtype deployment specified automatically
443                if atomTypeID == c.vtd.ACTIVITY_DEPLOYMENT_TERM:
444                    inputs['subtypeID'] = c.vtd.DEPLOYMENT_TERM
445                    inputs['atomTypeID'] = c.vtd.ACTIVITY_TERM
446                   
447                inputs['providerID'] = inputs.get('providerID').split('--')[1]
448                atom = self.saveAtomToExist(Atom(**dict(inputs)))
449               
450                h.redirect_to (controller = 'editatom', action='edit',
451                               uri = atom.ndgURI)
452            except Invalid, e:
453                c.errors = e.unpack_errors()
454               
455           
456        logging.info("Setting up atom create template")
457        c.title='Create new atom'
458       
459        # set up the drop down content - NB, add special case, 'deployment activity'
460        # - this is just a specialised activity - i.e. with subtype preset
461        if not c.vtd:
462            c.vtd = VTD()
463        deploymentActivity = c.vtd.TERM_DATA[c.vtd.ACTIVITY_DEPLOYMENT_TERM]
464        c.atomTypes = utils.getVocabTermDataDropdown(c.vtd.getValidTypes(c.vtd.ATOM_CATEGORY),
465                                               defaultVal = deploymentActivity, \
466                                               selected = inputs.get('atomTypeID'))
467        c.providerIDs = utils.getVocabTermDataDropdown(
468                            c.vtd.getValidTypes(c.vtd.PROVIDER_CATEGORY), 
469                            selected = inputs.get('providerID'))
470
471        try:
472            return render("genshi", 'atom_creator')
473
474        except Exception, e:
475            c.xml='Unexpected error loading page [%s]' %str(e)
476            c.doc=''
477            logging.error(c.xml)
478       
479        response.status_code = 400
480        return render('error')
Note: See TracBrowser for help on using the repository browser.