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

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

Move vocabdata object into global variable to allow efficient re-use +
simplify some of the edit control logic + remove unused routings.

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