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 xml.parsers.expat import ExpatError |
---|
9 | from formencode import Invalid |
---|
10 | from genshi.filters import HTMLFormFiller |
---|
11 | from genshi import HTML |
---|
12 | from milk_server.lib.base import * |
---|
13 | from milk_server.models.form import * |
---|
14 | import ndg.common.src.lib.htmlUtilities as utils |
---|
15 | from ndg.common.src.models.Atom import Atom, Person, Link, Category |
---|
16 | from ndg.common.src.models import AtomState |
---|
17 | from ndg.common.src.lib.atomvalidator import ValidationError |
---|
18 | import ndg.common.src.clients.xmldb.abstractxmldbatomclient as ac |
---|
19 | import ndg.common.src.clients.xmldb.eXist.dbconstants as dc |
---|
20 | from ndg.common.src.models.MolesEntity import MolesEntity as ME |
---|
21 | from ndg.common.src.models.vocabtermdata import VocabTermData as VTD |
---|
22 | from ndg.common.src.lib.granulite import granulite |
---|
23 | from editorconstants import * |
---|
24 | from atomeditorcontroller import AtomEditorController |
---|
25 | from milk_server.lib import Utilities |
---|
26 | |
---|
27 | class EditatomController(AtomEditorController): |
---|
28 | ''' |
---|
29 | Provides the pylons controller for editing NDG Atom documents. |
---|
30 | ''' |
---|
31 | def upload(self, uri): |
---|
32 | ''' |
---|
33 | Upload a CSML, CDML or granulite file and store it in the session variable |
---|
34 | NB, if the uri is specified, we're already dealing with an atom |
---|
35 | (which this refers to) - so the file is not a granulite - since |
---|
36 | this is used to create an atom from scratch |
---|
37 | ''' |
---|
38 | logging.info("Uploading file...") |
---|
39 | self._setup(uri=uri) |
---|
40 | |
---|
41 | granFile = request.POST.get('upload_granulite') |
---|
42 | csmlOrCdmlFile = request.POST.get('CSMLOrCDML') |
---|
43 | |
---|
44 | # check whether we can replace existing atoms |
---|
45 | replaceAtom = self.inputs.get('replaceAtom') |
---|
46 | |
---|
47 | # NB, need to turn from string to boolean - there doesn't seem a reliable |
---|
48 | # way of doing this using built in methods - so just do simple check |
---|
49 | if replaceAtom == 'True': |
---|
50 | replaceAtom = True |
---|
51 | else: |
---|
52 | replaceAtom = False |
---|
53 | |
---|
54 | # if this is true, then re-extract the inputs from the session data |
---|
55 | if replaceAtom: |
---|
56 | if session.get(GRAN_FILE_VALUE): |
---|
57 | granFile = cgi.FieldStorage() |
---|
58 | granFile.value = session.get(GRAN_FILE_VALUE) |
---|
59 | granFile.filename = session.get(GRAN_FILE_NAME) |
---|
60 | del session[GRAN_FILE_VALUE] |
---|
61 | del session[GRAN_FILE_NAME] |
---|
62 | |
---|
63 | if session.get(CSML_OR_CDML_FILE_VALUE): |
---|
64 | csmlOrCdmlFile = cgi.FieldStorage() |
---|
65 | csmlOrCdmlFile.value = session.get(CSML_OR_CDML_FILE_VALUE) |
---|
66 | csmlOrCdmlFile.filename = session.get(CSML_OR_CDML_FILE_NAME) |
---|
67 | del session[CSML_OR_CDML_FILE_VALUE] |
---|
68 | del session[CSML_OR_CDML_FILE_NAME] |
---|
69 | |
---|
70 | c.errors = {} |
---|
71 | try: |
---|
72 | logging.info("Validating inputs") |
---|
73 | validator = LoadGranuliteFormSchema() |
---|
74 | validator.to_python(self.inputs) |
---|
75 | logging.info("- inputs valid") |
---|
76 | |
---|
77 | useCSMLID = True |
---|
78 | if uri: |
---|
79 | useCSMLID = False |
---|
80 | |
---|
81 | if (granFile == '' or granFile == None) and \ |
---|
82 | (csmlOrCdmlFile == '' or csmlOrCdmlFile == None): |
---|
83 | errorMessage = "Error: could not load file - please try again" |
---|
84 | logging.error(errorMessage) |
---|
85 | raise IOError(errorMessage) |
---|
86 | else: |
---|
87 | # Prepare the basic data model |
---|
88 | # NB, if loading a granulite, this will create the displayed atom |
---|
89 | # with the ID taken from the CSML file, if specified |
---|
90 | fileContents = None |
---|
91 | if (granFile is not None and granFile != ''): |
---|
92 | fileContents = granFile.value |
---|
93 | |
---|
94 | # use the granulite helper class to add either the full granulite |
---|
95 | # data or just the CSML/CDML data |
---|
96 | # NB, we'll be creating the atom in the default local eXist |
---|
97 | eXistClient = Utilities.getExistClient('local') |
---|
98 | gran = granulite(fileContents, granuleAtom = c.atom, \ |
---|
99 | eXistClient = eXistClient, \ |
---|
100 | csmlOrCdmlFile = csmlOrCdmlFile, \ |
---|
101 | timeAxis = self.inputs.get('timeAxis'), \ |
---|
102 | datasetID = self.inputs.get('granuleDatasetID'), \ |
---|
103 | useCSMLID = useCSMLID, \ |
---|
104 | replaceAtom = replaceAtom) |
---|
105 | |
---|
106 | # now process the input file and add any extra required data |
---|
107 | if uri: |
---|
108 | c.atom = gran.processCSMLOrCDMLFile() |
---|
109 | |
---|
110 | # save new data - NB, for granulites, this is done as part of the |
---|
111 | # processing steps |
---|
112 | self.saveAtomToExist(c.atom) |
---|
113 | else: |
---|
114 | try: |
---|
115 | c.atom = gran.processGranulite() |
---|
116 | |
---|
117 | # Now set up the ndgObject with the created atom's vals |
---|
118 | self._setup(uri=c.atom.ndgURI, loadAtom = False) |
---|
119 | c.atom.ndgObject = self.ndgObject |
---|
120 | except ac.DuplicateError, e: |
---|
121 | # we've found an existing atom with the same ID |
---|
122 | # - give the users the choice of replacing the contents of this atom |
---|
123 | # or just exiting |
---|
124 | # - NB, do this via a session variable to act as a flag |
---|
125 | # for a javascript command |
---|
126 | session[OVERWRITE_GRANULE_FLAG] = e.message |
---|
127 | |
---|
128 | # store the inputs data for easy retrieval |
---|
129 | # - NB, file fields don't behave as text fields - for security |
---|
130 | # purposes - so need to store their data as session variables |
---|
131 | # for easy retrieval |
---|
132 | # - Also, cannot pickle the cgi.FieldStorage object so extract |
---|
133 | # picklable data and recreate on the return run |
---|
134 | if granFile != '': |
---|
135 | session[GRAN_FILE_VALUE] = granFile.value |
---|
136 | session[GRAN_FILE_NAME] = granFile.filename |
---|
137 | if csmlOrCdmlFile != '': |
---|
138 | session[CSML_OR_CDML_FILE_VALUE] = csmlOrCdmlFile.value |
---|
139 | session[CSML_OR_CDML_FILE_NAME] = csmlOrCdmlFile.filename |
---|
140 | |
---|
141 | # need to return to original screen - so clear out variables |
---|
142 | c.atom = None |
---|
143 | uri = None |
---|
144 | |
---|
145 | # now do redirection - NB, this ensures that current atom contents are |
---|
146 | # reloaded and displayed |
---|
147 | logging.info("File data loaded and extracted to atom") |
---|
148 | except Invalid, e: |
---|
149 | logging.info(" - inputs invalid") |
---|
150 | c.errors = e.unpack_errors() |
---|
151 | except ValidationError, e: |
---|
152 | logging.info(e) |
---|
153 | self._unpackErrors(e) |
---|
154 | |
---|
155 | except Exception, e: |
---|
156 | c.errors['WARNING'] = ['Error loading data: the displayed data will not be saved - please fix problem and retry'] |
---|
157 | self._unpackErrors(e) |
---|
158 | except SystemExit, ee: |
---|
159 | # NB, some of the CSML libraries just sys.exit on problems - catch errors here |
---|
160 | c.errors['ERROR'] = ['Problem encountered whilst transforming the CDML data into CSML'] |
---|
161 | self._unpackErrors(ee) |
---|
162 | |
---|
163 | if c.atom and hasattr(c.atom, 'ndgURI'): |
---|
164 | |
---|
165 | if hasattr(self, 'pathInfo'): |
---|
166 | self.pathInfo = self.pathInfo.replace('upload', 'editAtom') |
---|
167 | |
---|
168 | # NB, if there are errors, don't redirect otherwise these will get lost |
---|
169 | if not c.errors: |
---|
170 | h.redirect_to(h.url_for('edit', uri = c.atom.ndgURI)) |
---|
171 | else: |
---|
172 | c.atom.contentFile = None |
---|
173 | return self.edit(c.atom.ndgURI) |
---|
174 | |
---|
175 | elif uri: |
---|
176 | # something has gone wrong here... |
---|
177 | return render("genshi", 'error') |
---|
178 | else: |
---|
179 | return self.createGranule(**self.inputs) |
---|
180 | |
---|
181 | |
---|
182 | def saveAtom(self, uri, saveLevel=0): |
---|
183 | ''' |
---|
184 | Save the atom contents - NB, validation is done by method decoration |
---|
185 | - if this fails, the action is reran as a GET with htmlfill auto-populating |
---|
186 | the fields to keep them as they were before the submission |
---|
187 | ''' |
---|
188 | logging.info("Saving input atom data") |
---|
189 | c.errors = {} |
---|
190 | try: |
---|
191 | self._setup(uri) |
---|
192 | except Exception, e: |
---|
193 | return self._handleError(e) |
---|
194 | |
---|
195 | # variable to hold publication state changes |
---|
196 | newState = None |
---|
197 | |
---|
198 | # save atom association changes |
---|
199 | if int(saveLevel) == self.ADD_ASSOCIATIONS: |
---|
200 | atomLinks = self.extractAtomAssociations(self.inputs) |
---|
201 | c.atom.addUniqueRelatedLinks(atomLinks) |
---|
202 | elif int(saveLevel) == self.REMOVE_ASSOCIATIONS: |
---|
203 | atomLinks = self.extractAtomAssociations(self.inputs) |
---|
204 | c.atom.removeRelatedLinks(atomLinks) |
---|
205 | else: |
---|
206 | authors = self.extractAuthorDetails(self.inputs) |
---|
207 | c.atom.addAuthors(authors) |
---|
208 | |
---|
209 | onlineRefs = self.extractOnlineReferenceDetails(self.inputs) |
---|
210 | c.atom.addOnlineReferences(onlineRefs) |
---|
211 | |
---|
212 | # NB, if params have been specified, or removed, a list will be returned |
---|
213 | params = self.extractParameterDetails(self.inputs) |
---|
214 | # NB, the atom type and subtype are added to the categories when the |
---|
215 | # atom is exported to XML - so don't need to worry about overwriting |
---|
216 | # them now |
---|
217 | if params == []: |
---|
218 | c.atom.parameters = [] |
---|
219 | elif params: |
---|
220 | c.atom.parameters = [] |
---|
221 | c.atom.addParameters(params) |
---|
222 | |
---|
223 | if self.inputs.get('subtype'): |
---|
224 | c.atom.subtype = self.getLatestTermURLFromDropDownInput( \ |
---|
225 | self.inputs.get('subtype')) |
---|
226 | c.atom.subtypeID = c.atom.subtype.split('/')[-1] |
---|
227 | |
---|
228 | if self.inputs.get('publication_state'): |
---|
229 | newState = AtomState.getAtomState(self.inputs['publication_state']) |
---|
230 | |
---|
231 | logging.info("Validating input") |
---|
232 | try: |
---|
233 | g.validator.setAtom(c.atom) |
---|
234 | g.validator.validateAtom() |
---|
235 | logging.info("- input valid") |
---|
236 | |
---|
237 | # if a change of state has been specified, |
---|
238 | self.saveAtomToExist(c.atom, newState = newState) |
---|
239 | except Exception, e: |
---|
240 | self._unpackErrors(e) |
---|
241 | logging.info("- input invalid") |
---|
242 | return self.edit(uri) |
---|
243 | |
---|
244 | # now do redirection - NB, this ensures that current atom contents are |
---|
245 | # reloaded and displayed |
---|
246 | h.redirect_to(h.url_for(controller = 'atom_editor/editatom', action='edit', \ |
---|
247 | uri = c.atom.ndgURI)) |
---|
248 | |
---|
249 | |
---|
250 | def prepareEditForm(self, uri): |
---|
251 | ''' |
---|
252 | Get everything set up for displaying the edit form |
---|
253 | @param uri: ndg url for the atom to load in edit mode |
---|
254 | ''' |
---|
255 | if not c.errors: |
---|
256 | c.errors = {} |
---|
257 | |
---|
258 | # NB, can get here directly from saveAtom - if there have been errors |
---|
259 | # - in this case keep original data |
---|
260 | if not c.atom: |
---|
261 | self._setup(uri) |
---|
262 | |
---|
263 | c.title= EDIT_TITLE %c.atom.ndgURI |
---|
264 | c.uri = c.atom.ndgURI |
---|
265 | |
---|
266 | c.saveLink = h.url_for('save', saveLevel = self.STANDARD_SAVE, |
---|
267 | uri = c.atom.ndgURI) |
---|
268 | c.saveAssoc = h.url_for('save', saveLevel = self.REMOVE_ASSOCIATIONS, |
---|
269 | uri = c.atom.ndgURI) |
---|
270 | c.atom.deploymentsURL = h.url_for('viewAssociatedData', |
---|
271 | type = VTD.DEPLOYMENT_TERM, |
---|
272 | uri = c.atom.ndgURI) |
---|
273 | c.atom.dataEntitiesURL = h.url_for('viewAssociatedData', |
---|
274 | type = VTD.DE_TERM, |
---|
275 | uri = c.atom.ndgURI) |
---|
276 | |
---|
277 | # adjust atom type to cope with activity deployment exception |
---|
278 | atomType = c.atom.atomTypeID |
---|
279 | if atomType == g.vtd.ACTIVITY_TERM and \ |
---|
280 | c.atom.subtypeID == g.vtd.DEPLOYMENT_TERM: |
---|
281 | atomType = g.vtd.DEPLOYMENT_TERM |
---|
282 | |
---|
283 | c.addEntityLink = h.url_for('list', searchData = '0', \ |
---|
284 | associatedAtomID = c.atom.ndgURI, \ |
---|
285 | associatedAtomType = atomType, |
---|
286 | associationType = utils.ENTITY_ASSOCIATION) |
---|
287 | |
---|
288 | c.addGranuleLink = h.url_for('list', searchData = '0', \ |
---|
289 | associatedAtomID = c.atom.ndgURI, \ |
---|
290 | associatedAtomType = atomType, |
---|
291 | associationType = utils.GRANULE_ASSOCIATION) |
---|
292 | |
---|
293 | c.addDeploymentLink = h.url_for('list', searchData = '0', \ |
---|
294 | associatedAtomID = c.atom.ndgURI, \ |
---|
295 | associatedAtomType = atomType, |
---|
296 | associationType = utils.DEPLOYMENT_ASSOCIATION) |
---|
297 | |
---|
298 | # account for special case where we're dealing with deployments |
---|
299 | listVals = g.vtd.getValidSubTypes(c.atom.atomTypeID) |
---|
300 | if c.atom.isDeployment(): |
---|
301 | listVals = [g.vtd.TERM_DATA[g.vtd.DEPLOYMENT_TERM]] |
---|
302 | |
---|
303 | c.subTypes = utils.getVocabTermDataDropdown(listVals, \ |
---|
304 | selected=c.atom.subtype) |
---|
305 | |
---|
306 | c.states = h.options_for_select(AtomState.selectList, c.atom.state.stateFlag) |
---|
307 | self.inputs['publication_state'] = c.atom.state.stateFlag |
---|
308 | |
---|
309 | self.__setDropDownSelectVal('subtype', c.atom.subtype, listVals) |
---|
310 | self.addRelatedLinksDropDowns() |
---|
311 | |
---|
312 | |
---|
313 | def __setDropDownSelectVal(self, name, val, vtds): |
---|
314 | ''' |
---|
315 | Given a list of vocab terms, with the name of a 'select' tag and the selected |
---|
316 | value, set the proper value in the inputs dict to allow htmlfill to correctly |
---|
317 | display the list |
---|
318 | @param name: name of select element to set the select value of |
---|
319 | @param val: value of the selected item - NB, this need not be the current vocab |
---|
320 | term url - but should start with the main stem and end with the termID |
---|
321 | @param vtds: list of vocab term definition objects |
---|
322 | ''' |
---|
323 | if not val: |
---|
324 | return |
---|
325 | for vtd in vtds: |
---|
326 | if val.endswith(vtd.termID) and \ |
---|
327 | val.startswith(vtd.vocabURL): |
---|
328 | self.inputs[name] = utils.getVocabTermDataSelectValue(vtd) |
---|
329 | return |
---|
330 | |
---|
331 | |
---|
332 | def delete(self, uri): |
---|
333 | ''' |
---|
334 | Delete the atom associated with the specified uri - and return |
---|
335 | user to the atom home page. NB, only granule atoms can be deleted |
---|
336 | at the moment. |
---|
337 | ''' |
---|
338 | if uri: |
---|
339 | try: |
---|
340 | logging.info("Deleting atom, '%s'" %uri) |
---|
341 | self._setup(uri) |
---|
342 | eXistClient = Utilities.getExistClient(c.atom.ME.providerID) |
---|
343 | gran = granulite(None, granuleAtom = c.atom, \ |
---|
344 | eXistClient = eXistClient, \ |
---|
345 | deleteMode = True) |
---|
346 | |
---|
347 | gran.deleteGranuleAndDEReferences() |
---|
348 | c.deleteResult = "Atom deleted successfully." |
---|
349 | logging.info("- atom deleted") |
---|
350 | except Exception, e: |
---|
351 | logging.error("Problem occured whilst deleting atom: '%s'" %e.message) |
---|
352 | c.deleteResult = "Warning: a problem occured whilst deleting the atom - this " + \ |
---|
353 | "may have left the system in an unstable state - please check if the atom, or " + \ |
---|
354 | "references to the atom still exist" |
---|
355 | |
---|
356 | return render("genshi", "atom_editor/atom_home") |
---|
357 | |
---|
358 | |
---|
359 | def edit(self, uri): |
---|
360 | ''' |
---|
361 | Edit the atom with the specified ndg uri |
---|
362 | ''' |
---|
363 | logging.info("Setting up atom edit template") |
---|
364 | try: |
---|
365 | self.prepareEditForm(uri) |
---|
366 | |
---|
367 | # NB, there appears to be a bug in htmlfill which automagically |
---|
368 | # clears out content from textarea - so need to set the content |
---|
369 | # explicitly for htmlfill to use |
---|
370 | self.inputs['Content'] = c.atom.Content |
---|
371 | self.inputs['summary'] = c.atom.summary |
---|
372 | self.inputs['lineage'] = c.atom.ME.lineage |
---|
373 | self.inputs['quality'] = c.atom.ME.quality |
---|
374 | return self.savePageAndRender("atom_editor/atom_editor", **self.inputs) |
---|
375 | |
---|
376 | except ExpatError, e: |
---|
377 | c.xml='XML content is not well formed' |
---|
378 | c.doc=str(e) |
---|
379 | logging.error("Error retrieving [%s] - XML content: %s" % (uri, e)) |
---|
380 | except SystemError, e: |
---|
381 | return self._handleError(e) |
---|
382 | except Exception, e: |
---|
383 | errorMessage = traceback.format_exc() |
---|
384 | c.xml='Unexpected error [%s] viewing [%s]'%(str(e), uri) |
---|
385 | c.doc='' |
---|
386 | logging.error(c.xml) |
---|
387 | |
---|
388 | response.status_code = 400 |
---|
389 | return render("genshi", 'error') |
---|
390 | |
---|
391 | |
---|
392 | def addRelatedLinksDropDowns(self): |
---|
393 | ''' |
---|
394 | Set up the drop down lists required for the selection of online ref links |
---|
395 | ''' |
---|
396 | # at the very least, we need a simple drop down list with no preselected |
---|
397 | # values |
---|
398 | logging.debug("Setting up drop down lists for related links") |
---|
399 | vtds = g.vtd.getValidTypes(g.vtd.ONLINE_REF_CATEGORY) |
---|
400 | c.relatedLinkTerms = utils.getVocabTermDataDropdown(vtds) |
---|
401 | |
---|
402 | # ensure we have set up the correct inputs to allow htmlfill to show |
---|
403 | # the correct selected value |
---|
404 | for i, link in enumerate(c.atom.relatedLinks): |
---|
405 | logging.debug("Adding dropdown for related link, '%s'" %(str(link))) |
---|
406 | refLabel = Atom.ONLINE_REF_LABEL + "." + str(i) + '.rel' |
---|
407 | |
---|
408 | # get the value of the selected list |
---|
409 | self.__setDropDownSelectVal(refLabel, link.rel, vtds) |
---|
410 | |
---|
411 | logging.debug("Finished setting up drop down lists") |
---|
412 | |
---|
413 | |
---|
414 | def extractAuthorDetails(self, inputs): |
---|
415 | ''' |
---|
416 | Retrieve author data from inputs and set appropriately on Atom, if any |
---|
417 | found |
---|
418 | @return: list of Person objects with the author data |
---|
419 | ''' |
---|
420 | logging.info("Extracting author data from inputs") |
---|
421 | processedAuthors = [] |
---|
422 | authors = [] |
---|
423 | for key in inputs: |
---|
424 | keyBits = key.split('.') |
---|
425 | if len(keyBits) == 3 and keyBits[1] not in processedAuthors: |
---|
426 | |
---|
427 | authorType = -1 |
---|
428 | if key.lower().startswith('author'): |
---|
429 | authorType = Person.AUTHOR_TYPE |
---|
430 | elif key.lower().startswith('contributor'): |
---|
431 | authorType = Person.CONTRIBUTOR_TYPE |
---|
432 | elif key.lower().startswith('responsible'): |
---|
433 | authorType = Person.RESPONSIBLE_PARTY_TYPE |
---|
434 | else: |
---|
435 | continue |
---|
436 | |
---|
437 | # NB, adding an empty object here is valid as it will clear out |
---|
438 | # existing entries, potentially |
---|
439 | author = Person(personType = authorType) |
---|
440 | # check if the remove checkbox has been set |
---|
441 | keyStem = ".".join(keyBits[0:2]) |
---|
442 | if inputs.get(keyStem + ".remove"): |
---|
443 | logging.info("Removing author data") |
---|
444 | else: |
---|
445 | author.name = inputs.get(keyStem + '.name') or "" |
---|
446 | author.uri = inputs.get(keyStem + '.uri') or "" |
---|
447 | author.role = inputs.get(keyStem + '.role') or "" |
---|
448 | |
---|
449 | logging.info("Adding new author info") |
---|
450 | logging.debug("Extracted author (type:'%s', name:'%s', uri:'%s', role:'%s')" \ |
---|
451 | %(author.type, author.name, author.uri, author.role)) |
---|
452 | authors.append(author) |
---|
453 | processedAuthors.append(keyBits[1]) |
---|
454 | |
---|
455 | logging.info("Finished extracting author data") |
---|
456 | return authors |
---|
457 | |
---|
458 | |
---|
459 | def extractOnlineReferenceDetails(self, inputs): |
---|
460 | ''' |
---|
461 | Retrieve online reference data from inputs and set appropriately on Atom, if any |
---|
462 | found |
---|
463 | @return: list of Link objects containing the extracted data |
---|
464 | ''' |
---|
465 | logging.info("Extracting related links data from inputs") |
---|
466 | processedLinks = [] |
---|
467 | links = [] |
---|
468 | |
---|
469 | for key in inputs: |
---|
470 | keyBits = key.split('.') |
---|
471 | if len(keyBits) == 3 and keyBits[1] not in processedLinks: |
---|
472 | |
---|
473 | if key.lower().startswith(Atom.ONLINE_REF_LABEL): |
---|
474 | link = Link() |
---|
475 | keyStem = ".".join(keyBits[0:2]) |
---|
476 | |
---|
477 | if inputs.get(keyStem + ".remove"): |
---|
478 | logging.info("Removing online reference data") |
---|
479 | else: |
---|
480 | # NB, this is in the format vocabURL--termID, so requires further |
---|
481 | # processing |
---|
482 | link.rel = self.getLatestTermURLFromDropDownInput(inputs.get(keyStem + '.rel')) |
---|
483 | link.href = inputs.get(keyStem + '.href') or "" |
---|
484 | link.title = inputs.get(keyStem + '.title') or "" |
---|
485 | |
---|
486 | if not link.hasValue(): |
---|
487 | continue |
---|
488 | |
---|
489 | logging.info("Adding new online reference info") |
---|
490 | logging.debug("Extracted online reference (href:'%s', title:'%s', rel:'%s')" \ |
---|
491 | %(link.href, link.title, link.rel)) |
---|
492 | links.append(link) |
---|
493 | |
---|
494 | processedLinks.append(keyBits[1]) |
---|
495 | else: |
---|
496 | continue |
---|
497 | |
---|
498 | logging.info("Finished extracting links data") |
---|
499 | return links |
---|
500 | |
---|
501 | |
---|
502 | def extractParameterDetails(self, inputs): |
---|
503 | ''' |
---|
504 | Retrieve parameters data from inputs and set appropriately on Atom, if any |
---|
505 | found |
---|
506 | @return: list of parameter data in triple string format - i.e. |
---|
507 | of format, 'label | scheme | term' |
---|
508 | If no parameters are found, return None. NB, if all params have been |
---|
509 | removed, return [] |
---|
510 | ''' |
---|
511 | logging.info("Extracting parameters data from inputs") |
---|
512 | processedParameters = [] |
---|
513 | parameters = [] |
---|
514 | |
---|
515 | emptyParam = " | | " |
---|
516 | for key in inputs: |
---|
517 | keyBits = key.split('.') |
---|
518 | if len(keyBits) == 3 and keyBits[1] not in processedParameters: |
---|
519 | |
---|
520 | if key.lower().startswith(Atom.PARAMETER_LABEL): |
---|
521 | keyStem = ".".join(keyBits[0:2]) |
---|
522 | |
---|
523 | if inputs.get(keyStem + ".remove"): |
---|
524 | logging.info("Removing parameters data") |
---|
525 | else: |
---|
526 | parameter = inputs.get(keyStem + '.label') or "" |
---|
527 | parameter += " | " + inputs.get(keyStem + '.scheme') or "" |
---|
528 | parameter += " | " + inputs.get(keyStem + '.term') or "" |
---|
529 | |
---|
530 | if parameter == emptyParam: |
---|
531 | continue |
---|
532 | |
---|
533 | logging.info("Adding new parameter info: %s" %parameter) |
---|
534 | parameters.append(parameter) |
---|
535 | |
---|
536 | processedParameters.append(keyBits[1]) |
---|
537 | else: |
---|
538 | continue |
---|
539 | |
---|
540 | if not processedParameters: |
---|
541 | return None |
---|
542 | |
---|
543 | logging.info("Finished extracting parameters data") |
---|
544 | return parameters |
---|
545 | |
---|
546 | |
---|
547 | def extractAtomAssociations(self, inputs): |
---|
548 | ''' |
---|
549 | Retrieve atom data from inputs and create related links pointing to |
---|
550 | this data |
---|
551 | @return: list of Links representing the related atoms |
---|
552 | ''' |
---|
553 | logging.info("Extracting related atom ID data from inputs") |
---|
554 | atoms = [] |
---|
555 | processedAtoms = [] |
---|
556 | |
---|
557 | for key in inputs: |
---|
558 | if key.lower().startswith(Atom.ATOM_REF_LABEL): |
---|
559 | (x, href, title, rel) = key.split(Atom.DELIMITER) |
---|
560 | # NB, we handle removes by effectively ignoring them later on |
---|
561 | if href not in processedAtoms: |
---|
562 | processedAtoms.append(href) |
---|
563 | |
---|
564 | link = Link() |
---|
565 | link.href = href or "" |
---|
566 | link.title = title or "" |
---|
567 | link.rel = rel or "" |
---|
568 | |
---|
569 | # adjust href to point to the browse, not the edit version |
---|
570 | link.href = link.href.replace('editAtom', 'view') |
---|
571 | link.href = link.href.replace(g.server, VTD.BROWSE_SERVER_URL) |
---|
572 | |
---|
573 | logging.debug("Extracted atom info (href:'%s', title:'%s', rel:'%s')" \ |
---|
574 | %(link.href, link.title, link.rel)) |
---|
575 | atoms.append(link) |
---|
576 | else: |
---|
577 | continue |
---|
578 | |
---|
579 | logging.info("Finished extracting atoms data") |
---|
580 | return atoms |
---|
581 | |
---|
582 | |
---|
583 | def getLatestTermURLFromDropDownInput(self, inputVal): |
---|
584 | ''' |
---|
585 | Term ID and vocabURL are specified in the drop down menus |
---|
586 | - using the input from this, return the lastest full href to the |
---|
587 | term ID |
---|
588 | ''' |
---|
589 | termData = inputVal.split('--') |
---|
590 | return g.vtd.getCurrentVocabURI(termData[0]) + \ |
---|
591 | "/" + termData[1] |
---|
592 | |
---|
593 | |
---|
594 | def saveAtomToExist(self, atom, newState = None): |
---|
595 | ''' |
---|
596 | Save the specified atom in eXist |
---|
597 | @param atom: atom object to save to eXist |
---|
598 | @keyword newState: AtomState publication state to move the atom to, default: None |
---|
599 | - use the current atom state |
---|
600 | @return atom: atom object saved in eXist |
---|
601 | ''' |
---|
602 | logging.info("Saving changes to eXist") |
---|
603 | eXist = Utilities.getExistClient(atom.ME.providerID) |
---|
604 | |
---|
605 | if newState and newState != atom.state: |
---|
606 | createdAtom = eXist.changeAtomPublicationState(atom, newState) |
---|
607 | else: |
---|
608 | createdAtom = eXist.createAtom(atom) |
---|
609 | |
---|
610 | # update references to the atom, if its title has changed |
---|
611 | if atom.oldTitle != atom.title: |
---|
612 | self.__updateAtomReferences(atom, eXist) |
---|
613 | |
---|
614 | logging.info("Changes successfully saved to eXist") |
---|
615 | return createdAtom |
---|
616 | |
---|
617 | |
---|
618 | def __updateAtomReferences(self, atom, eXist): |
---|
619 | ''' |
---|
620 | If an atom title changes, update any references to it elsewhere - since |
---|
621 | this title is used in the link 'title' attribute |
---|
622 | @param atom: Atom whose references need to be updated |
---|
623 | @param eXist: AtomClient for eXist DB |
---|
624 | ''' |
---|
625 | logging.info("- updating references to atom with changed title") |
---|
626 | atoms = [] |
---|
627 | if atom.atomTypeID == VTD.GRANULE_TERM or \ |
---|
628 | (atom.atomTypeID == VTD.ACTIVITY_TERM and \ |
---|
629 | atom.subtypeID == VTD.DEPLOYMENT_TERM): |
---|
630 | logging.debug("- retrieving references in data entities") |
---|
631 | atom.lookupAssociatedData(VTD.DE_TERM, eXist, |
---|
632 | lookupIndirectReferences = True) |
---|
633 | for de in atom.dataEntities: |
---|
634 | doc = eXist.buildAndRunQuery('atom', |
---|
635 | dc.ATOM_COLLECTION_PATH, |
---|
636 | "", |
---|
637 | de.rel) |
---|
638 | atoms.append(Atom(xmlString=str(doc[0]))) |
---|
639 | |
---|
640 | elif atom.atomTypeID == VTD.DE_TERM: |
---|
641 | # DEs are never referenced by other atoms - just keep this check |
---|
642 | # to make an easier catch all 'else' for the deployments data |
---|
643 | logging.debug("Atom is a data entity - so has no references elsewhere to update") |
---|
644 | else: |
---|
645 | logging.debug("- retrieving references in deployments") |
---|
646 | atom.lookupAssociatedData(VTD.DEPLOYMENT_TERM, eXist, |
---|
647 | lookupIndirectReferences = True) |
---|
648 | |
---|
649 | for dep in atom.deployments: |
---|
650 | doc = eXist.buildAndRunQuery('atom', |
---|
651 | dc.ATOM_COLLECTION_PATH, |
---|
652 | dep.providerID, |
---|
653 | dep.id) |
---|
654 | atoms.append(Atom(xmlString=str(doc[0]))) |
---|
655 | |
---|
656 | logging.debug("- references retrieved - now updating") |
---|
657 | for refAtom in atoms: |
---|
658 | for rel in refAtom.relatedLinks: |
---|
659 | if rel.title == atom.oldTitle: |
---|
660 | rel.title = atom.title |
---|
661 | |
---|
662 | eXist.createAtom(refAtom) |
---|
663 | logging.info("- references updated") |
---|
664 | |
---|
665 | |
---|
666 | def create(self, saveData = None, **inputs): |
---|
667 | ''' |
---|
668 | Create a new atom |
---|
669 | ''' |
---|
670 | self._setup() |
---|
671 | if saveData: |
---|
672 | logging.info("Validating input") |
---|
673 | try: |
---|
674 | validator = CreateAtomFormSchema() |
---|
675 | validator.to_python(self.inputs) |
---|
676 | logging.info("- input valid") |
---|
677 | |
---|
678 | logging.info("Creating basic atom") |
---|
679 | atomTypeID = self.inputs.get('atomTypeID').split('--')[1] |
---|
680 | self.inputs['atomTypeID'] = atomTypeID |
---|
681 | |
---|
682 | # activity deployments should have subtype deployment specified automatically |
---|
683 | if atomTypeID == g.vtd.ACTIVITY_DEPLOYMENT_TERM: |
---|
684 | self.inputs['subtypeID'] = g.vtd.DEPLOYMENT_TERM |
---|
685 | self.inputs['atomTypeID'] = g.vtd.ACTIVITY_TERM |
---|
686 | |
---|
687 | self.inputs['providerID'] = self.inputs.get('providerID').split('--')[1] |
---|
688 | atom = self.saveAtomToExist(Atom(**dict(self.inputs))) |
---|
689 | url = h.url_for('edit', uri = atom.ndgURI, saveData=None) |
---|
690 | |
---|
691 | except Invalid, e: |
---|
692 | c.errors = e.unpack_errors() |
---|
693 | except Exception, e: |
---|
694 | c.xml = 'System Error: %s' %str(e) |
---|
695 | return render("genshi", 'error') |
---|
696 | |
---|
697 | # NB, the redirect throws an exception, so be careful not to catch it |
---|
698 | h.redirect_to(url) |
---|
699 | |
---|
700 | logging.info("Setting up atom create template") |
---|
701 | c.title = CREATE_ATOM_TITLE |
---|
702 | |
---|
703 | # set up the drop down content - NB, add special case, 'deployment activity' |
---|
704 | # - this is just a specialised activity - i.e. with subtype preset |
---|
705 | c.atomTypes = utils.getVocabTermDataDropdown(g.vtd.getValidTypes(g.vtd.ATOM_CATEGORY)) |
---|
706 | c.providerIDs = utils.getVocabTermDataDropdown(g.vtd.getValidTypes(g.vtd.PROVIDER_CATEGORY)) |
---|
707 | |
---|
708 | try: |
---|
709 | return self.savePageAndRender('atom_editor/atom_creator', **self.inputs) |
---|
710 | |
---|
711 | except Exception, e: |
---|
712 | return self._handleError(e) |
---|
713 | |
---|
714 | |
---|
715 | def createGranule(self, **inputs): |
---|
716 | ''' |
---|
717 | Create a new atom from a granulite file |
---|
718 | ''' |
---|
719 | self._setup() |
---|
720 | logging.info("Setting up new atom from granulite template") |
---|
721 | c.title='Create new data granule atom - from a granulite file' |
---|
722 | |
---|
723 | if not hasattr(c, 'errors'): |
---|
724 | c.errors = {} |
---|
725 | try: |
---|
726 | return self.savePageAndRender('atom_editor/atom_granulator', **inputs) |
---|
727 | |
---|
728 | except Exception, e: |
---|
729 | return self._handleError(e) |
---|