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

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

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

Line 
1'''
2 Class representing base pylons controller for the atom editor shared functions
3 
4 @author: C Byrom, Tessella Dec 2008
5'''
6import logging, xmlrpclib, cgi, traceback
7from ndg.common.src.models.ndgObject import ndgObject
8from ndg.common.src.models.Atom import Atom
9from ndg.common.src.lib.utilities import escapeSpecialCharacters
10import ndg.common.src.clients.xmldb.eXist.existdbclient as edc
11from milk_server.lib.base import *
12from milk_server.lib.ndgInterface import ndgInterface
13from milk_server.lib import Utilities
14from editorconstants import *
15from formencode import htmlfill
16   
17class AtomEditorController(BaseController):
18    '''
19    Provides the pylons controller for shared functions
20    '''
21    STANDARD_SAVE = 1
22    ADD_ASSOCIATIONS = 3
23    REMOVE_ASSOCIATIONS = 4
24
25    def _setup(self, uri=None, loadAtom=True):
26        '''
27        Common setup stuff for all the actions on this controller
28        @keyword uri: uri of the atom to set up
29        @keyword loadAtom: if True, load the atom into c.atom, otherwise don't.
30        Default is True.
31        '''
32        logging.info("Setting up AtomEditorController")
33        self.cf=request.environ['ndgConfig']
34        self.inputs = self.__getTidyInputs()
35
36        if uri:
37            self.ndgObject = ndgObject(uri, config=self.cf)
38            if loadAtom:
39                self.__prepareDataModel(uri)
40
41        logging.info("AtomEditorController set up")
42
43
44    def __prepareDataModel(self, uri):
45        '''
46        Set up the underlying atom data model - loading the bulk from eXist
47        then updating any input fields appropriately
48        '''
49        logging.info("Preparing underlying data model")
50        logging.info("Retrieving document to edit")
51        # NB, don't use the cache as docs are likely to change
52        # quite a lot during editing; ensure you've always got
53        # the latest updates loaded
54        interface = ndgInterface()
55        status,x = interface.GetXML(uri, useCache=False)
56
57        if not status:
58            code=400
59            if x.startswith('<p> Access Denied'):
60                code=401
61
62            c.xml='%s'%x
63            response.status_code = code
64            raise SystemError('Problem experienced retrieving atom doc from eXist')
65
66        # NB, passing in the inputs will overwrite any original values with the
67        # user input ones
68        if not self.inputs:
69            self.inputs = self.__getTidyInputs()
70
71        c.atom = Atom(xmlString=str(x), ndgObject = self.ndgObject, **dict(self.inputs))
72
73        # lookup the atom publication state
74        edc = Utilities.getExistClient(c.atom.ME.providerID)
75        c.atom.state = edc.getAtomPublicationState(c.atom.datasetID)
76       
77        # save the current atom - to avoid this needing be recreated by the
78        # asynch viewAssociatedData call
79        session['currentAtom'] = c.atom
80        session.save()
81        logging.info("Data model set up")
82
83
84    def __getTidyInputs(self):
85        '''
86        The inputs can be used generically to specify atom attributes - however
87        some inputs should not be set against the Atom object since they use slots
88        so cannot be pickled - which means saving the atom in the session with fail.
89        This method clears out any unwanted inputs
90        '''
91        logging.debug("Getting pickleable input data")
92        inputs = request.params
93        tidyInputs = {}
94        for key, val in inputs.items():
95            if not isinstance(val, cgi.FieldStorage):
96                tidyInputs[key] = val
97
98        logging.debug("Pickleable data extracted")
99        return tidyInputs
100
101
102    def _unpackErrors(self, e):
103        '''
104        Add exception errors to the common error structures - for use
105        in templates
106        @param e: Exception to add
107        '''
108        if not c.errors:
109            c.errors = {}
110
111        errorMessage = e.message
112        if g.debugAtomEditor == 'True':
113            errorMessage = traceback.format_exc()
114
115        c.xml = escapeSpecialCharacters('Unexpected error loading page [%s]' \
116                                        %str(errorMessage))
117        c.doc = ''
118       
119        # unpack errors, if possible - NB, the c.errors value is used by the template
120        # function, displayErrors, which is used by most of the editor templates
121        if isinstance(e, xmlrpclib.Fault):
122            # strip out the exception type - NB, this is usually native library code
123            # and is of no real interest - and will just confuse viewers
124            c.errors['Unexpected error'] = e.faultString.split(':')[-1] 
125        if hasattr(e, 'unpack_errors'):
126            c.errors.update(e.unpack_errors())
127           
128        else:
129            c.errors['Unexpected error'] = [c.xml]
130
131        # tidy up errors - escaping any xml tags, if necessary
132        for key, errors in c.errors.items():
133            newErrors = []
134            for error in errors:
135                for err in error.split('<br/>'):
136                    newErrors.append(escapeSpecialCharacters(err))
137            c.errors[key] = newErrors
138
139
140    def _handleError(self, e, template='atom_editor/error'):
141        '''
142        Handle exceptions thrown; if debug mode on, display full stack trace
143        in output, otherwise just show basic error message in error template
144        @param e: Exception to process
145        @keyword template: template to render - 'error' is the default - NB, if
146        an alternative is specified it should have a div with class set to 'error'
147        containing the variable, c.xml to display properly
148        '''
149        self._unpackErrors(e)
150        logging.error(c.xml)
151        response.status_code = 400
152        return render("genshi", template)
153
154
155    def savePageAndRender(self, template, **inputs):
156        '''
157        Save the current path info - to provide a memory function when changing
158        tabs + render the given template with the specified inputs filled in
159        automatically
160        @param template: name of template to render
161        @param inputs: dict of inputs with keynames matching input names in template
162        form 
163        '''
164        logging.debug("Saving current page url (%s) - to keep track of atom editor state" \
165                      %self.pathInfo)
166        session['currentEditor'] = self.pathInfo
167        session.save()
168        logging.debug("Now rendering current template (%s)" %template)
169        html = render("genshi", template)
170        # NB, need html in unicode for for htmlfill.render
171        html = unicode(html, 'utf-8', 'xmlcharrefreplace')
172        for key, val in inputs.items():
173            inputs[key] = str(val)
174
175        return htmlfill.render(html, inputs)
Note: See TracBrowser for help on using the repository browser.