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