diff --git a/modules/bibcatalog/lib/bibcatalog_system.py b/modules/bibcatalog/lib/bibcatalog_system.py index 2614e452e..6fff09e29 100644 --- a/modules/bibcatalog/lib/bibcatalog_system.py +++ b/modules/bibcatalog/lib/bibcatalog_system.py @@ -1,155 +1,156 @@ # -*- coding: utf-8 -*- ## ## This file is part of CDS Invenio. ## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008 CERN. ## ## CDS Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## CDS Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with CDS Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. """ Provide a "ticket" interface with a request tracker. Please see the help/hacking/bibcatalog-api page for details. This is a base class that cannot be instantiated. """ from invenio.webuser import get_user_preferences class BibCatalogSystem: """ A template class for ticket support.""" - TICKET_ATTRIBUTES = ['ticketid', 'priority', 'recordid', 'subject', 'text', 'creator', 'owner', 'date', 'status', 'queue', 'url_display', 'url_close'] + TICKET_ATTRIBUTES = ['ticketid', 'priority', 'recordid', 'subject', 'text', 'creator', 'owner', 'date', 'status', 'queue', 'url_display', 'url_modify', 'url_close'] def check_system(self, uid): """Check connectivity. Return a string describing the error or an empty str @param uid: invenio user id @type uid: number @return: empty string on success. Otherwise a string describing error. @rtype: string """ return "this class cannot be instantiated" def ticket_search(self, uid, recordid=-1, subject="", text="", creator="", owner="", \ date_from="", date_until="", status="", priority=""): """Search for tickets based on various criteria. Return an array of ticket numbers @param uid: invenio user id. @type uid: number @param recordid: search criteria - ticket contains this record id. @type recordid: number @param subject: search criteria - ticket has this subject (substr). @type subject: string @param text: search criteria - ticket has this text in body (substr). @type text: string @param creator: search criteria - ticket creator's id. @type creator: number @param owner: search criteria - ticket owner's id. @type owner: number @param date_from: search criteria - ticket created starting from this date. Example: '2009-01-24' @type date_until: date in yyyy-mm-dd format @param date_until: search criteria - ticket created until from this date. Example: '2009-01-24' @type date_from: date in yyyy-mm-dd format @param status: search criteria - ticket has this status. Example: 'resolved'. @type status: string @param priority: search criteria - ticket priority number. @type priority: number. """ pass def ticket_submit(self, uid, subject, recordid, text="", queue="", priority="", owner=""): - """submit a ticket. Return 1 on success + """submit a ticket. Return ticket number on success, otherwise None @param uid: invenio user id @type uid: number @param subject: set this as the ticket's subject. @type subject: string @param recordid: ticket concerns this record. @type recordid: number @param text: ticket body. @type text: string @param queue: the queue for this ticket (if supported). @type queue: string @param priority: ticket priority. @type priority: number @param owner: set ticket owner to this uid. @type owner: number + @return: new ticket id or None """ pass def ticket_assign(self, uid, ticketid, to_user): """assign a ticket to a user. Return 1 on success @param uid: invenio user id @type uid: number @param ticketid: ticket id @type ticketid: number @param to_user: assign ticket to this user @type to_user: number @return: 1 on success, 0 otherwise @rtype: number """ pass def ticket_set_attribute(self, uid, ticketid, attribute, new_value): """set an attribute of a ticket. Return 1 on success @param uid: invenio user id @type uid: number @param ticketid: ticket id @type ticketid: number @param attribute. This is a member of TICKET_ATTRIBUTES. @type attribute: string @param new_value: new value for this attribute. @type new_value: string @return: 1 on success, 0 otherwise @rtype: number """ pass def ticket_get_attribute(self, uid, ticketid, attrname): """return an attribute @param uid: invenio user id @type uid: number @param ticketid: ticket id @type ticketid: number @param attrname: attribute name. @type attrname: string @return: the value of the attribute, or None if the ticket or attribute does not exist @rtype: string """ pass def ticket_get_info(self, uid, ticketid, attrlist = None): """Return the attributes of a ticket as a dictionary whose fields are TICKET_ATTRIBUTES. @param uid: user id @type uid: number @param ticketid: ticket id @type ticketid: number @param attrlist: a list of attributes, each in TICKET_ATTRIBUTES. @type attrlist: list @return: dictionary whose fields are TICKET_ATTRIBUTES @rtype: dictionary """ pass def get_bibcat_from_prefs(uid): """gets username and pw from user prefs as a tuple. if not successfull, returns None @param uid: user id @type uid: number @return: ('bibcatalog_username', 'bibcatalog_password') @rtype: tuple """ user_pref = get_user_preferences(uid) if not user_pref.has_key('bibcatalog_username'): return (None, None) if not user_pref.has_key('bibcatalog_password'): return (None, None) return (user_pref['bibcatalog_username'], user_pref['bibcatalog_password']) diff --git a/modules/bibcatalog/lib/bibcatalog_system_rt.py b/modules/bibcatalog/lib/bibcatalog_system_rt.py index 78ec7bacf..f52ec7a74 100644 --- a/modules/bibcatalog/lib/bibcatalog_system_rt.py +++ b/modules/bibcatalog/lib/bibcatalog_system_rt.py @@ -1,355 +1,364 @@ # -*- coding: utf-8 -*- ## ## This file is part of CDS Invenio. ## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008 CERN. ## ## CDS Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## CDS Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with CDS Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. """ Provide a "ticket" interface with a request tracker. This is a subclass of BibCatalogSystem """ import os import os.path import invenio.webuser from invenio.shellutils import run_shell_command, escape_shell_arg from invenio.bibcatalog_system import BibCatalogSystem, get_bibcat_from_prefs from invenio.config import CFG_BIBCATALOG_SYSTEM, \ CFG_BIBCATALOG_SYSTEM_RT_CLI, \ CFG_BIBCATALOG_SYSTEM_RT_URL, \ CFG_BIBCATALOG_SYSTEM_RT_URL, \ CFG_BIBCATALOG_QUEUES class BibCatalogSystemRT(BibCatalogSystem): BIBCATALOG_RT_SERVER = "" #construct this by http://user:password@RT_URL def check_system(self, uid): """return an error string if there are problems""" user_pref = invenio.webuser.get_user_preferences(uid) if not user_pref.has_key('bibcatalog_username'): return "user " + str(uid) + " has no bibcatalog_username" rtuid = user_pref['bibcatalog_username'] if not user_pref.has_key('bibcatalog_password'): return "user " + str(uid) + " has no bibcatalog_password" rtpw = user_pref['bibcatalog_password'] if not CFG_BIBCATALOG_SYSTEM == 'RT': return "CFG_BIBCATALOG_SYSTEM is not RT though this is an RT module" if not CFG_BIBCATALOG_SYSTEM_RT_CLI: return "CFG_BIBCATALOG_SYSTEM_RT_CLI not defined or empty" if not os.path.exists(CFG_BIBCATALOG_SYSTEM_RT_CLI): return "CFG_BIBCATALOG_SYSTEM_RT_CLI " + CFG_BIBCATALOG_SYSTEM_RT_CLI + " file does not exists" #check that you can execute it.. this is a safe call unless someone can fake CFG_BIBCATALOG_SYSTEM_RT_CLI (unlikely) dummy, myout, myerr = run_shell_command(CFG_BIBCATALOG_SYSTEM_RT_CLI + " help") helpfound = False if myerr.count("help") > 0: helpfound = True if not helpfound: return "Execution of CFG_BIBCATALOG_SYSTEM_RT_CLI " + CFG_BIBCATALOG_SYSTEM_RT_CLI + " help did not produce output 'help'" if not CFG_BIBCATALOG_SYSTEM_RT_URL: return "CFG_BIBCATALOG_SYSTEM_RT_URL not defined or empty" #construct.. split RT_URL at // if CFG_BIBCATALOG_SYSTEM_RT_URL.count("http://") == 0: return "CFG_BIBCATALOG__SYSTEM_RT_URL does not start with 'http://'" httppart, siteandpath = CFG_BIBCATALOG_SYSTEM_RT_URL.split("//") BIBCATALOG_RT_SERVER = httppart + "//" + rtuid + ":" + rtpw + "@" + siteandpath #set as env var os.environ["RTUSER"] = rtuid os.environ["RTSERVER"] = BIBCATALOG_RT_SERVER #try to talk to RT server #this is a safe call since rtpw is the only variable in it, and it is escaped rtpw = escape_shell_arg(rtpw) dummy, myout, myerr = run_shell_command("echo "+rtpw+" | " + CFG_BIBCATALOG_SYSTEM_RT_CLI + " ls \"Subject like 'F00'\"") if len(myerr) > 0: return "could not connect to " + BIBCATALOG_RT_SERVER + " " + myerr #finally, check that there is some sane output like tickets or 'No matching result' saneoutput = (myout.count('matching') > 0) or (myout.count('1') > 0) if not saneoutput: return CFG_BIBCATALOG_SYSTEM_RT_CLI + " returned " + myout + " instead of 'matching' or '1'" if not CFG_BIBCATALOG_QUEUES: return "CFG_BIBCATALOG_QUEUES not defined or empty" (username, dummy) = get_bibcat_from_prefs(uid) if (username is None): return "Cannot find user preference bibcatalog_username for uid "+str(uid) return "" def ticket_search(self, uid, recordid=-1, subject="", text="", creator="", owner="", \ - date_from="", date_until="", status="", priority=""): + date_from="", date_until="", status="", priority="", include_resolved=False): """returns a list of ticket ID's related to this record or by matching the subject, creator or owner of the ticket.""" search_atoms = [] #the search expression will be made by and'ing these + if not include_resolved: + search_atoms.append("Status != 'resolved'") if (recordid > -1): #search by recid search_atoms.append("CF.{RecordID} = " + escape_shell_arg(str(recordid))) if (len(subject) > 0): #search by subject search_atoms.append("Subject like " + escape_shell_arg(str(subject))) if (len(text) > 0): search_atoms.append("Content like " + escape_shell_arg(str(text))) if (len(str(creator)) > 0): #search for this person's bibcatalog_username in preferences creatorprefs = invenio.webuser.get_user_preferences(creator) creator = "Nobody can Have This Kind of Name" if creatorprefs.has_key("bibcatalog_username"): creator = creatorprefs["bibcatalog_username"] search_atoms.append("Creator = " + escape_shell_arg(str(creator))) if (len(str(owner)) > 0): ownerprefs = invenio.webuser.get_user_preferences(owner) owner = "Nobody can Have This Kind of Name" if ownerprefs.has_key("bibcatalog_username"): owner = ownerprefs["bibcatalog_username"] search_atoms.append("Owner = " + escape_shell_arg(str(owner))) if (len(date_from) > 0): search_atoms.append("Created >= " + escape_shell_arg(str(date_from))) if (len(date_until) > 0): search_atoms.append("Created <= " + escape_shell_arg(str(date_until))) if (len(str(status)) > 0): search_atoms.append("Status = " + escape_shell_arg(str(status))) if (len(str(priority)) > 0): #try to convert to int intpri = -1 try: intpri = int(priority) except: pass if (intpri > -1): search_atoms.append("Priority = " + str(intpri)) searchexp = " and ".join(search_atoms) tickets = [] if not CFG_BIBCATALOG_SYSTEM_RT_URL: return tickets httppart, siteandpath = CFG_BIBCATALOG_SYSTEM_RT_URL.split("//") (username, passwd) = get_bibcat_from_prefs(uid) BIBCATALOG_RT_SERVER = httppart + "//" + username + ":" + passwd + "@" + siteandpath #set as env var os.environ["RTUSER"] = username os.environ["RTSERVER"] = BIBCATALOG_RT_SERVER #search.. if len(searchexp) == 0: #just make an expression that is true for all tickets searchexp = "Created > '1900-01-01'" passwd = escape_shell_arg(passwd) #make a call. This is safe since passwd and all variables in searchexp have been escaped. dummy, myout, dummyerr = run_shell_command("echo "+passwd+" | " + CFG_BIBCATALOG_SYSTEM_RT_CLI + " ls \"" + searchexp + "\"") for line in myout.split("\n"): #if there are matching lines they will look like NUM:subj.. so pick num if (line.count(': ') > 0) and (line.count("Invalid") == 0): #the parameters may be insane tnum, dummy = line.split(': ') #get the ticket id and transform it to int try: inum = int(tnum) tickets.append(tnum) except: pass return tickets def ticket_submit(self, uid, subject, recordid, text="", queue="", priority="", owner=""): """creates a ticket. return ticket num on success, otherwise None""" if not CFG_BIBCATALOG_SYSTEM_RT_URL: return None (username, passwd) = get_bibcat_from_prefs(uid) httppart, siteandpath = CFG_BIBCATALOG_SYSTEM_RT_URL.split("//") BIBCATALOG_RT_SERVER = httppart + "//" + username + ":" + passwd + "@" + siteandpath #set as env var os.environ["RTUSER"] = username os.environ["RTSERVER"] = BIBCATALOG_RT_SERVER queueset = "" textset = "" priorityset = "" ownerset = "" - subjectset = " subject=" + escape_shell_arg(subject) + subjectset = "" + if subject: + subjectset = " subject=" + escape_shell_arg(subject) recidset = " CF-RecordID=" + escape_shell_arg(str(recordid)) if text: textset = " text=" + escape_shell_arg(text) if priority: priorityset = " priority=" + escape_shell_arg(str(priority)) if queue: queueset = " queue=" + escape_shell_arg(queue) if owner: #get the owner name from prefs ownerprefs = invenio.webuser.get_user_preferences(owner) if ownerprefs.has_key("bibcatalog_username"): owner = ownerprefs["bibcatalog_username"] ownerset = " owner=" + escape_shell_arg(owner) #make a command.. note that all set 'set' parts have been escaped command = CFG_BIBCATALOG_SYSTEM_RT_CLI + " create -t ticket set " + subjectset + recidset + \ queueset + textset + priorityset + ownerset passwd = escape_shell_arg(passwd) #make a call.. passwd and command have been escaped (see above) dummy, myout, dummyerr = run_shell_command("echo "+passwd+" | " + command) inum = -1 for line in myout.split("\n"): if line.count(' ') > 0: stuff = line.split(' ') - inum = int(stuff[2]) + try: + inum = int(stuff[2]) + except: + pass if inum > 0: return inum return None def ticket_assign(self, uid, ticketid, to_user): """assign a ticket to an RT user. Returns 1 on success, 0 on failure""" return self.ticket_set_attribute(uid, ticketid, 'owner', to_user) def ticket_set_attribute(self, uid, ticketid, attribute, new_value): """change the ticket's attribute. Returns 1 on success, 0 on failure""" #check that the attribute is accepted.. if attribute not in BibCatalogSystem.TICKET_ATTRIBUTES: return 0 #we cannot change read-only values.. including text that is an attachment. pity if attribute in ['creator', 'date', 'ticketid', 'url_close', 'url_display', 'recordid', 'text']: return 0 #check attribute setme = "" if (attribute == 'priority'): try: dummy = int(new_value) except: return 0 setme = "set Priority=" + str(new_value) if (attribute == 'subject'): subject = escape_shell_arg(new_value) setme = "set Subject='" + subject +"'" if (attribute == 'owner'): #convert from invenio to RT ownerprefs = invenio.webuser.get_user_preferences(new_value) if not ownerprefs.has_key("bibcatalog_username"): return 0 else: owner = escape_shell_arg(ownerprefs["bibcatalog_username"]) setme = " set owner='" + owner +"'" if (attribute == 'status'): setme = " set status='" + escape_shell_arg(new_value) +"'" if (attribute == 'queue'): setme = " set queue='" + escape_shell_arg(new_value) +"'" if not CFG_BIBCATALOG_SYSTEM_RT_URL: return 0 #make sure ticketid is numeric try: dummy = int(ticketid) except: return 0 (username, passwd) = get_bibcat_from_prefs(uid) httppart, siteandpath = CFG_BIBCATALOG_SYSTEM_RT_URL.split("//") BIBCATALOG_RT_SERVER = httppart + "//" + username + ":" + passwd + "@" + siteandpath #set as env var os.environ["RTUSER"] = username os.environ["RTSERVER"] = BIBCATALOG_RT_SERVER passwd = escape_shell_arg(passwd) #make a call. safe since passwd and all variables in 'setme' have been escaped dummy, myout, dummyerr = run_shell_command("echo "+passwd+" | " + CFG_BIBCATALOG_SYSTEM_RT_CLI + " edit ticket/" + str(ticketid) + setme) respOK = False mylines = myout.split("\n") for line in mylines: if line.count('updated') > 0: respOK = True if respOK: return 1 #print str(mylines) return 0 def ticket_get_attribute(self, uid, ticketid, attrname): """return an attribute of a ticket""" ticinfo = self.ticket_get_info(uid, ticketid, [attrname]) if ticinfo.has_key(attrname): return ticinfo[attrname] return None def ticket_get_info(self, uid, ticketid, attrlist = None): """return ticket info as a dictionary of pre-defined attribute names. Or just those listed in attrlist. Returns None on failure""" if not CFG_BIBCATALOG_SYSTEM_RT_URL: return 0 #make sure ticketid is numeric try: dummy = int(ticketid) except: return 0 if attrlist is None: attrlist = [] (username, passwd) = get_bibcat_from_prefs(uid) httppart, siteandpath = CFG_BIBCATALOG_SYSTEM_RT_URL.split("//") BIBCATALOG_RT_SERVER = httppart + "//" + username + ":" + passwd + "@" + siteandpath #set as env var os.environ["RTUSER"] = username os.environ["RTSERVER"] = BIBCATALOG_RT_SERVER passwd = escape_shell_arg(passwd) #make a call. This is safe.. passwd escaped, ticketid numeric dummy, myout, dummyerr = run_shell_command("echo "+passwd+" | " + CFG_BIBCATALOG_SYSTEM_RT_CLI + " show ticket/" + str(ticketid)) tdict = {} for line in myout.split("\n"): if line.count(": ") > 0: tattr, tvaluen = line.split(": ") tvalue = tvaluen.rstrip() tdict[tattr] = tvalue #query again to get attachments -> Contents dummy, myout, dummyerr = run_shell_command("echo "+passwd+" | " + CFG_BIBCATALOG_SYSTEM_RT_CLI + " show ticket/" + str(ticketid) + "/attachments/") attachments = [] for line in myout.split("\n"): if line.count(": ") > 1: #there is a line Attachments: 40: xxx aline = line.split(": ") attachments.append(aline[1]) #query again for each attachment for att in attachments: #passwd still escaped.. dummy, myout, dummyerr = run_shell_command("echo "+passwd+" | " + CFG_BIBCATALOG_SYSTEM_RT_CLI + " show ticket/" + str(ticketid) + "/attachments/" + att) #get the contents line for line in myout.split("\n"): if line.count("Content: ") > 0: cstuff = line.split("Content: ") tdict['Text'] = cstuff[1].rstrip() if (len(tdict) > 0): #iterate over TICKET_ATTRIBUTES to make a canonical ticket candict = {} for f in BibCatalogSystem.TICKET_ATTRIBUTES: tcased = f.title() if tdict.has_key(tcased): candict[f] = tdict[tcased] if tdict.has_key('CF.{RecordID}'): candict['recordid'] = tdict['CF.{RecordID}'] if tdict.has_key('id'): candict['ticketid'] = tdict['id'] #make specific URL attributes: url_display = CFG_BIBCATALOG_SYSTEM_RT_URL + "/Ticket/Display.html?id="+str(ticketid) candict['url_display'] = url_display url_close = CFG_BIBCATALOG_SYSTEM_RT_URL + "/Ticket/Update.html?Action=Resolve&id="+str(ticketid) candict['url_close'] = url_close + url_modify = CFG_BIBCATALOG_SYSTEM_RT_URL + "/Ticket/Modify.html?id="+str(ticketid) + candict['url_modify'] = url_modify #change the ticket owner into invenio UID if tdict.has_key('owner'): rt_owner = tdict["owner"] uid = invenio.webuser.get_uid_based_on_pref("bibcatalog_username", rt_owner) candict['owner'] = uid if len(attrlist) == 0: #return all fields return candict else: #return only the fields that were requested tdict = {} for f in attrlist: if candict.has_key(f): tdict[f] = candict[f] return tdict else: return None diff --git a/modules/bibedit/lib/bibedit_engine.py b/modules/bibedit/lib/bibedit_engine.py index 3d6819a96..b1713c77e 100644 --- a/modules/bibedit/lib/bibedit_engine.py +++ b/modules/bibedit/lib/bibedit_engine.py @@ -1,399 +1,424 @@ ## This file is part of CDS Invenio. ## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008 CERN. ## ## CDS Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## CDS Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with CDS Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. # pylint: disable-msg=C0103 """CDS Invenio BibEdit Engine.""" __revision__ = "$Id" from invenio.bibedit_config import CFG_BIBEDIT_JS_HASH_CHECK_INTERVAL, \ CFG_BIBEDIT_JS_CHECK_SCROLL_INTERVAL, CFG_BIBEDIT_JS_STATUS_ERROR_TIME, \ CFG_BIBEDIT_JS_STATUS_INFO_TIME, CFG_BIBEDIT_JS_NEW_ADD_FIELD_FORM_COLOR, \ CFG_BIBEDIT_JS_NEW_ADD_FIELD_FORM_COLOR_FADE_DURATION, \ CFG_BIBEDIT_JS_NEW_FIELDS_COLOR, \ CFG_BIBEDIT_JS_NEW_FIELDS_COLOR_FADE_DURATION, \ CFG_BIBEDIT_AJAX_RESULT_CODES, CFG_BIBEDIT_MAX_SEARCH_RESULTS, \ CFG_BIBEDIT_TAG_FORMAT from invenio.bibedit_dblayer import get_name_tags_all from invenio.bibedit_utils import cache_exists, cache_expired, \ create_cache_file, delete_cache_file, get_cache_file_contents, \ get_cache_mtime, latest_record_revision, record_locked_by_other_user, \ record_locked_by_queue, save_xml_record, touch_cache_file, \ update_cache_file_contents from invenio.bibrecord import record_add_field, record_add_subfield_into, \ record_modify_controlfield, record_modify_subfield, record_move_subfield, \ record_delete_subfield, record_delete_field from invenio.config import CFG_BIBEDIT_PROTECTED_FIELDS, CFG_CERN_SITE, \ CFG_SITE_URL from invenio.search_engine import record_exists, search_pattern from invenio.webuser import getUid, session_param_get, session_param_set from invenio.bibcatalog import bibcatalog_system import invenio.template bibedit_templates = invenio.template.load('bibedit') def perform_request_init(): """Handle the initial request by adding menu and Javascript to the page.""" errors = [] warnings = [] body = '' # Add script data. tag_names = get_name_tags_all() protected_fields = ['001'] protected_fields.extend(CFG_BIBEDIT_PROTECTED_FIELDS.split(',')) history_url = '"' + CFG_SITE_URL + '/admin/bibedit/bibeditadmin.py/history"' cern_site = 'false' if CFG_CERN_SITE: cern_site = 'true' data = {'gTagNames': tag_names, 'gProtectedFields': protected_fields, 'gSiteURL': '"' + CFG_SITE_URL + '"', 'gHistoryURL': history_url, 'gCERNSite': cern_site, 'gHASH_CHECK_INTERVAL': CFG_BIBEDIT_JS_HASH_CHECK_INTERVAL, 'gCHECK_SCROLL_INTERVAL': CFG_BIBEDIT_JS_CHECK_SCROLL_INTERVAL, 'gSTATUS_ERROR_TIME': CFG_BIBEDIT_JS_STATUS_ERROR_TIME, 'gSTATUS_INFO_TIME': CFG_BIBEDIT_JS_STATUS_INFO_TIME, 'gNEW_ADD_FIELD_FORM_COLOR': '"' + CFG_BIBEDIT_JS_NEW_ADD_FIELD_FORM_COLOR + '"', 'gNEW_ADD_FIELD_FORM_COLOR_FADE_DURATION': CFG_BIBEDIT_JS_NEW_ADD_FIELD_FORM_COLOR_FADE_DURATION, 'gNEW_FIELDS_COLOR': '"' + CFG_BIBEDIT_JS_NEW_FIELDS_COLOR + '"', 'gNEW_FIELDS_COLOR_FADE_DURATION': CFG_BIBEDIT_JS_NEW_FIELDS_COLOR_FADE_DURATION, 'gRESULT_CODES': CFG_BIBEDIT_AJAX_RESULT_CODES } body += '\n' # Add scripts (the ordering is NOT irrelevant). scripts = ['jquery.min.js', 'effects.core.min.js', 'effects.highlight.min.js', 'jquery.autogrow.js', 'jquery.jeditable.mini.js', 'jquery.hotkeys.min.js', 'json2.js', 'bibedit_display.js', 'bibedit_engine.js', 'bibedit_keys.js', 'bibedit_menu.js'] for script in scripts: body += ' \n' % (CFG_SITE_URL, script) # Build page structure and menu. body += bibedit_templates.menu() body += '
\n' return body, errors, warnings def perform_request_ajax(req, recid, uid, data): """Handle Ajax requests by redirecting to appropriate function.""" response = {} request_type = data['requestType'] # Call function based on request type. if request_type == 'searchForRecord': # Search request. response.update(perform_request_search(data)) elif request_type in ['changeTagFormat']: # User related requests. response.update(perform_request_user(req, request_type, recid, data)) elif request_type in ('getRecord', 'submit', 'deleteRecord', 'cancel', 'deleteRecordCache', 'prepareRecordMerge'): # 'Major' record related requests. response.update(perform_request_record(req, request_type, recid, uid, data)) else: # Record updates. response.update(perform_request_update_record( request_type, recid, uid, data)) return response def perform_request_search(data): """Handle search requests.""" response = {} searchType = data['searchType'] searchPattern = data['searchPattern'] if searchType == 'anywhere': pattern = searchPattern else: pattern = searchType + ':' + searchPattern result_set = list(search_pattern(p=pattern)) response['resultCode'] = 1 response['resultSet'] = result_set[0:CFG_BIBEDIT_MAX_SEARCH_RESULTS] return response def perform_request_user(req, request_type, recid, data): """Handle user related requests.""" response = {} if request_type == 'changeTagFormat': try: tagformat_settings = session_param_get(req, 'bibedit_tagformat') except KeyError: tagformat_settings = {} tagformat_settings[recid] = data['tagFormat'] session_param_set(req, 'bibedit_tagformat', tagformat_settings) response['resultCode'] = 2 return response def perform_request_record(req, request_type, recid, uid, data): """Handle 'major' record related requests like fetching, submitting or deleting a record, cancel editing or preparing a record for merging. """ response = {} if request_type == 'getRecord': # Fetch the record. Possible error situations: # - Non-existing record # - Deleted record # - Record locked by other user # - Record locked by queue # A cache file will be created if it does not exist. # If the cache is outdated (i.e., not based on the latest DB revision), # cacheOutdated will be set to True in the response. record_status = record_exists(recid) existing_cache = cache_exists(recid, uid) if record_status == 0: response['resultCode'] = 102 elif record_status == -1: response['resultCode'] = 103 elif not existing_cache and record_locked_by_other_user(recid, uid): response['resultCode'] = 104 elif existing_cache and cache_expired(recid, uid) and \ record_locked_by_other_user(recid, uid): response['resultCode'] = 104 elif record_locked_by_queue(recid): response['resultCode'] = 105 else: if data.get('deleteRecordCache'): delete_cache_file(recid, uid) existing_cache = False if not existing_cache: record_revision, record = create_cache_file(recid, uid) mtime = get_cache_mtime(recid, uid) cache_dirty = False else: cache_dirty, record_revision, record = \ get_cache_file_contents(recid, uid) touch_cache_file(recid, uid) mtime = get_cache_mtime(recid, uid) if not latest_record_revision(recid, record_revision): response['cacheOutdated'] = True response['resultCode'], response['cacheDirty'], \ response['record'], response['cacheMTime'] = 3, cache_dirty, \ record, mtime - #insert the ticket data in the result, if possible - if uid and bibcatalog_system.check_system(uid) == "": - tickets_found = bibcatalog_system.ticket_search(uid, recordid=recid) - t_url_str = '' #put ticket urls here, formatted for HTML display - for t_id in tickets_found: - t_url = bibcatalog_system.ticket_get_attribute(uid, t_id, 'url_display') - #format.. - t_url_str += ''+str(t_id)+' ' - #t_url_str += str(t_id)+" " - t_url_str = t_url_str.rstrip() - response['tickets'] = t_url_str + # Insert the ticket data in the response, if possible + if uid: + bibcat_resp = bibcatalog_system.check_system(uid) + if bibcat_resp == "": + tickets_found = bibcatalog_system.ticket_search(uid, recordid=recid) + t_url_str = '' #put ticket urls here, formatted for HTML display + for t_id in tickets_found: + #t_url = bibcatalog_system.ticket_get_attribute(uid, t_id, 'url_display') + ticket_info = bibcatalog_system.ticket_get_info(uid, t_id, ['url_display','url_close']) + t_url = ticket_info['url_display'] + t_close_url = ticket_info['url_close'] + #format.. + t_url_str += "#"+str(t_id)+'[read] [close]
' + #put ticket header and tickets links in the box + t_url_str = "Tickets
"+t_url_str+"
"+'[new ticket]' + response['tickets'] = t_url_str + #add a new ticket link + else: + #put something in the tickets container, for debug + response['tickets'] = "" # Set tag format from user's session settings. try: tagformat_settings = session_param_get(req, 'bibedit_tagformat') tagformat = tagformat_settings[recid] except KeyError: tagformat = CFG_BIBEDIT_TAG_FORMAT response['tagFormat'] = tagformat elif request_type == 'submit': # Submit the record. Possible error situations: # - Missing cache file # - Cache file modified in other editor # - Record locked by other user # - Record locked by queue # If the cache is outdated cacheOutdated will be set to True in the # response. if not cache_exists(recid, uid): response['resultCode'] = 106 elif not get_cache_mtime(recid, uid) == data['cacheMTime']: response['resultCode'] = 107 elif cache_expired(recid, uid) and \ record_locked_by_other_user(recid, uid): response['resultCode'] = 104 elif record_locked_by_queue(recid): response['resultCode'] = 105 else: record_revision, record = get_cache_file_contents(recid, uid)[1:] if not data['force'] and \ not latest_record_revision(recid, record_revision): response['cacheOutdated'] = True else: save_xml_record(recid, uid) response['resultCode'] = 4 elif request_type == 'cancel': # Cancel editing by deleting the cache file. Possible error situations: # - Cache file modified in other editor if cache_exists(recid, uid): if get_cache_mtime(recid, uid) == data['cacheMTime']: delete_cache_file(recid, uid) response['resultCode'] = 5 else: response['resultCode'] = 107 else: response['resultCode'] = 5 elif request_type == 'deleteRecord': # Submit the record. Possible error situations: # - Record locked by other user # - Record locked by queue # As the user is requesting deletion we proceed even if the cache file # is missing and we don't check if the cache is outdated or has # been modified in another editor. existing_cache = cache_exists(recid, uid) if existing_cache and cache_expired(recid, uid) and \ record_locked_by_other_user(recid, uid): response['resultCode'] = 104 elif record_locked_by_queue(recid): response['resultCode'] = 105 else: if not existing_cache: record_revision, record = create_cache_file(recid, uid) else: record_revision, record = get_cache_file_contents( recid, uid)[1:] record_add_field(record, '980', ' ', ' ', '', [('c', 'DELETED')]) update_cache_file_contents(recid, uid, record_revision, record) save_xml_record(recid, uid) response['resultCode'] = 6 elif request_type == 'deleteRecordCache': # Delete the cache file. Ignore the request if the cache has been # modified in another editor. if cache_exists(recid, uid) and get_cache_mtime(recid, uid) == \ data['cacheMTime']: delete_cache_file(recid, uid) response['resultCode'] = 7 elif request_type == 'prepareRecordMerge': # We want to merge the cache with the current DB version of the record, # so prepare an XML file from the file cache, to be used by BibMerge. # Possible error situations: # - Missing cache file # - Record locked by other user # - Record locked by queue # We don't check if cache is outdated (a likely scenario for this # request) or if it has been modified in another editor. if not cache_exists(recid, uid): response['resultCode'] = 106 elif cache_expired(recid, uid) and \ record_locked_by_other_user(recid, uid): response['resultCode'] = 104 elif record_locked_by_queue(recid): response['resultCode'] = 105 else: save_xml_record(recid, uid, to_upload=False, to_merge=True) response['resultCode'] = 8 return response def perform_request_update_record(request_type, recid, uid, data): """Handle record update requests like adding, modifying, moving or deleting of fields or subfields. Possible common error situations: - Missing cache file - Cache file modified in other editor """ response = {} if not cache_exists(recid, uid): response['resultCode'] = 106 elif not get_cache_mtime(recid, uid) == data['cacheMTime']: response['resultCode'] = 107 else: record_revision, record = get_cache_file_contents(recid, uid)[1:] if request_type == 'addField': if data['controlfield']: record_add_field(record, data['tag'], controlfield_value=data['value']) response['resultCode'] = 9 else: field_number = record_add_field(record, data['tag'], data['ind1'], data['ind2']) for subfield in data['subfields']: record_add_subfield_into(record, data['tag'], field_number, subfield[0], subfield[1]) response['resultCode'] = 10 elif request_type == 'addSubfields': subfields = data['subfields'] for subfield in subfields: record_add_subfield_into(record, data['tag'], int(data['fieldNumber']), subfield[0], subfield[1], None) if len(subfields) == 1: response['resultCode'] = 11 else: response['resultCode'] = 12 elif request_type == 'modifyContent': if data['subfieldIndex'] != None: record_modify_subfield(record, data['tag'], int(data['fieldNumber']), data['subfieldCode'], data['value'], int(data['subfieldIndex'])) else: record_modify_controlfield(record, data['tag'], int(data['fieldNumber']), data['value']) response['resultCode'] = 13 elif request_type == 'moveSubfield': record_move_subfield(record, data['tag'], int(data['fieldNumber']), int(data['subfieldIndex']), int(data['newSubfieldIndex'])) response['resultCode'] = 14 elif request_type == 'deleteFields': to_delete = data['toDelete'] deleted_fields = 0 deleted_subfields = 0 for tag in to_delete: for field_number in to_delete[tag]: if not to_delete[tag][field_number]: # No subfields specified - delete entire field. record_delete_field(record, tag, int(field_number)) deleted_fields += 1 else: for subfield_index in \ to_delete[tag][field_number][::-1]: # Delete subfields in reverse order (to keep the # indexing correct). record_delete_subfield(record, tag, int(field_number), int(subfield_index)) deleted_subfields += 1 if deleted_fields == 1 and deleted_subfields == 0: response['resultCode'] = 15 elif deleted_fields and deleted_subfields == 0: response['resultCode'] = 16 elif deleted_subfields == 1 and deleted_fields == 0: response['resultCode'] = 17 elif deleted_subfields and deleted_fields == 0: response['resultCode'] = 18 else: response['resultCode'] = 19 response['cacheMTime'], response['cacheDirty'] = \ update_cache_file_contents(recid, uid, record_revision, record), \ True return response + +def perform_request_newticket(recid, uid): + """create a new ticket with this record's number + @param recid: record id + @param uid: user id + @return: (error_msg, url) + """ + t_id = bibcatalog_system.ticket_submit(uid, "", recid, "") + t_url = "" + errmsg = "" + if t_id: + #get the ticket's URL + t_url = bibcatalog_system.ticket_get_attribute(uid, t_id, 'url_modify') + else: + errmsg = "ticket_submit failed" + return (errmsg, t_url) diff --git a/modules/bibedit/lib/bibedit_templates.py b/modules/bibedit/lib/bibedit_templates.py index b7d3376fa..b4fcbfa01 100644 --- a/modules/bibedit/lib/bibedit_templates.py +++ b/modules/bibedit/lib/bibedit_templates.py @@ -1,223 +1,220 @@ ## This file is part of CDS Invenio. ## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008 CERN. ## ## CDS Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## CDS Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with CDS Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. # pylint: disable-msg=C0103 """BibEdit Templates.""" __revision__ = "$Id$" from invenio.config import CFG_SITE_URL class Template: """BibEdit Templates Class.""" def __init__(self): """Initialize.""" pass def menu(self): """Create the menu.""" imgExpandMenuSection = img('/img/bullet_toggle_plus.png', 'bibEditImgExpandMenuSection') recordmenu = '
\n' \ ' %(imgExpandMenuSection)sRecord\n' \ '
\n' \ ' \n' \ ' \n' \ ' \n' \ ' \n' \ ' \n' \ ' \n' \ ' \n' \ ' \n' \ ' \n' \ ' \n' \ ' \n' \ ' \n' \ ' \n' \ ' \n' \ ' \n' \ ' \n' \ ' \n' \ ' \n' \ ' \n' \ ' \n' \ ' \n' \ - ' \n' \ - ' \n' \ - ' \n' \ + ' \n' \ ' \n' \ ' \n' \ ' \n' \ ' \n' \ ' \n' \ '
\n' \ '
\n' \ ' %(txtSearchPattern)s\n' \ '
\n' \ '
\n' \ ' %(sctSearchType)s\n' \ '
%(btnSearch)s
%(btnSubmit)s%(btnCancel)s
%(tickets)s
%(btnDeleteRecord)s
' % { 'imgExpandMenuSection': imgExpandMenuSection, 'txtSearchPattern': inp('text', id='txtSearchPattern'), 'sctSearchType': '', 'btnSearch': button('button', 'Search', 'bibEditBtnBold', id='btnSearch'), 'btnPrev': button('button', '<', id='btnPrev', disabled='disabled'), 'btnNext': button('button', '>', id='btnNext', disabled='disabled'), 'btnSubmit': button('button', 'Submit', 'bibEditBtnBold', id='btnSubmit', disabled='disabled'), 'btnCancel': button('button', 'Cancel', id='btnCancel', disabled='disabled'), - 'tickets': "Tickets", 'imgDeleteRecord': img('/img/trash.png'), 'btnDeleteRecord': button('button', 'Delete record', id='btnDeleteRecord', disabled='disabled') } viewmenu = '
\n' \ ' %(imgExpandMenuSection)sView\n' \ '
\n' \ ' \n' \ ' \n' \ ' \n' \ ' \n' \ ' \n' \ ' \n' \ ' \n' \ '
%(btnTagMARC)s%(btnTagNames)s
' % { 'imgExpandMenuSection': imgExpandMenuSection, 'btnTagMARC': button('button', 'MARC', id='btnMARCTags', disabled='disabled'), 'btnTagNames': button('button', 'Human', id='btnHumanTags', disabled='disabled') } fieldmenu = '
\n' \ ' %(imgExpandMenuSection)sFields\n' \ '
\n' \ ' \n' \ ' \n' \ ' \n' \ ' \n' \ ' \n' \ ' \n' \ ' \n' \ ' \n' \ ' \n' \ ' \n' \ ' \n' \ ' \n' \ ' \n' \ '
%(imgAddField)s%(btnAddField)s
%(imgSortFields)s%(btnSortFields)s
%(imgDeleteSelected)s%(btnDeleteSelected)s
' % { 'imgExpandMenuSection': imgExpandMenuSection, 'imgAddField': img('/img/table_row_insert.png'), 'btnAddField': button('button', 'Add', id='btnAddField', disabled='disabled'), 'imgSortFields': img('/img/table_sort.png'), 'btnSortFields': button('button', 'Sort', id='btnSortFields', disabled='disabled'), 'imgDeleteSelected': img('/img/table_row_delete.png'), 'btnDeleteSelected': button('button', 'Delete selected', id='btnDeleteSelected', disabled='disabled')} statusarea = '\n' \ ' \n' \ ' \n' \ ' \n' \ '
%(imgIndicator)s%(lblChecking)s
' % { 'imgIndicator': img('/img/indicator.gif'), 'lblChecking': 'Checking status' + '...' } lnkhelp = img('/img/help.png', '', style='vertical-align: bottom') + \ link('Help', href='#', onclick='window.open(' \ '\'%s/help/admin/bibedit-admin-guide#2\', \'\', \'width=640,' \ 'height=600,left=150,top=150,resizable=yes,scrollbars=yes\');' \ 'return false;' % CFG_SITE_URL) return '
\n' \ '
\n' \ ' %(recordmenu)s\n' \ '
\n' \ '
\n' \ ' %(viewmenu)s\n' \ '
\n' \ '
\n' \ ' %(fieldmenu)s\n' \ '
\n' \ '
\n' \ ' %(statusarea)s\n' \ '
\n' \ '
\n' \ ' %(lnkhelp)s\n' \ '
\n' \ '
\n' % { 'recordmenu': recordmenu, 'viewmenu': viewmenu, 'fieldmenu': fieldmenu, 'statusarea': statusarea, 'lnkhelp': lnkhelp } def img(src, _class='', **kargs): """Create an HTML element.""" src = 'src="%s" ' % src if _class: _class = 'class="%s" ' % _class args = '' for karg in kargs: args += '%s="%s" ' % (karg, kargs[karg]) return '' % (src, _class, args) def inp(_type, _class='', **kargs): """Create an HTML element.""" _type = 'type="%s" ' % _type if _class: _class = 'class="%s" ' % _class args = '' for karg in kargs: args += '%s="%s" ' % (karg, kargs[karg]) return '' % (_type, _class, args) def button(_type, value, _class='', **kargs): """Create an HTML ' % (_type, _class, args, value) def link(value, _class='', **kargs): """Create an HTML
(link) element.""" if _class: _class = 'class="%s" ' % _class args = '' for karg in kargs: args += '%s="%s" ' % (karg, kargs[karg]) return '%s' % (_class, args, value) diff --git a/modules/bibedit/lib/bibedit_webinterface.py b/modules/bibedit/lib/bibedit_webinterface.py index f092db3b4..531601162 100644 --- a/modules/bibedit/lib/bibedit_webinterface.py +++ b/modules/bibedit/lib/bibedit_webinterface.py @@ -1,135 +1,165 @@ ## This file is part of CDS Invenio. ## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008 CERN. ## ## CDS Invenio is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## CDS Invenio is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with CDS Invenio; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. # pylint: disable-msg=C0103 """CDS Invenio BibEdit Administrator Interface.""" __revision__ = "$Id" __lastupdated__ = """$Date: 2008/08/12 09:26:46 $""" try: import simplejson as json except ImportError: pass # okay, no Ajax app will be possible, but continue anyway from invenio.access_control_engine import acc_authorize_action -from invenio.bibedit_engine import perform_request_init, perform_request_ajax +from invenio.bibedit_engine import perform_request_init, perform_request_ajax, \ + perform_request_record, perform_request_search, \ + perform_request_update_record, perform_request_user, perform_request_newticket from invenio.bibedit_utils import json_unicode_to_utf8 from invenio.config import CFG_SITE_LANG, CFG_SITE_URL from invenio.search_engine import guess_primary_collection_of_a_record from invenio.urlutils import redirect_to_url -from invenio.webinterface_handler import WebInterfaceDirectory +from invenio.webinterface_handler import WebInterfaceDirectory, wash_urlargd from invenio.webpage import page from invenio.webuser import collect_user_info, getUid, page_not_authorized +from invenio.messages import gettext_set_language navtrail = (' Admin Area ' ) % CFG_SITE_URL + class WebInterfaceEditPages(WebInterfaceDirectory): """Defines the set of /edit pages.""" - _exports = [''] + _exports = ['', 'new_ticket'] def __init__(self, recid=None): """Initialize.""" self.recid = recid def index(self, req, form): """Handle all BibEdit requests. The responsibilities of this functions is: * JSON decoding and encoding. * Redirection, if necessary. * Authorization. * Calling the appropriate function from the engine. """ # If it is an Ajax request, extract any JSON data. ajax_request, recid = False, None if form.has_key('jsondata'): json_data = json.loads(str(form['jsondata'])) # Deunicode all strings (CDS Invenio doesn't have unicode # support). json_data = json_unicode_to_utf8(json_data) ajax_request = True if json_data.has_key('recID'): recid = json_data['recID'] json_response = {'resultCode': 0, 'ID': json_data['ID']} # Authorization. user_info = collect_user_info(req) if user_info['email'] == 'guest': # User is not logged in. if not ajax_request: # Do not display the introductory recID selection box to guest # users (as it used to be with v0.99.0): auth_code, auth_message = acc_authorize_action(req, 'runbibedit') referer = '/edit/' if self.recid: referer = '/record/%s/edit/' % self.recid return page_not_authorized(req=req, referer=referer, text=auth_message, navtrail=navtrail) else: # Session has most likely timed out. json_response.update({'resultCode': 100}) return json.dumps(json_response) elif self.recid: # Handle RESTful calls from logged in users by redirecting to # generic URL. redirect_to_url(req, '%s/record/edit/#state=edit&recid=%s' % ( CFG_SITE_URL, self.recid)) elif recid is not None: json_response.update({'recID': recid}) # Authorize access to record. auth_code, auth_message = acc_authorize_action(req, 'runbibedit', collection=guess_primary_collection_of_a_record(recid)) if auth_code != 0: json_response.update({'resultCode': 101}) return json.dumps(json_response) # Handle request. uid = getUid(req) if not ajax_request: # Show BibEdit start page. body, errors, warnings = perform_request_init() title = 'BibEdit' ln = CFG_SITE_LANG return page(title = title, body = body, errors = errors, warnings = warnings, uid = uid, language = ln, navtrail = navtrail, lastupdated = __lastupdated__, req = req) else: # Handle AJAX request. json_response.update(perform_request_ajax(req, recid, uid, json_data)) return json.dumps(json_response) + def new_ticket(self, req, form): + """handle a edit/new_ticket request""" + argd = wash_urlargd(form, {'ln': (str, CFG_SITE_LANG), 'recid': (int, 0)}) + ln = argd['ln'] + _ = gettext_set_language(ln) + auth_code, auth_message = acc_authorize_action(req, 'runbibedit') + if auth_code != 0: + return page_not_authorized(req=req, referer="/edit", + text=auth_message, navtrail=navtrail) + uid = getUid(req) + if argd['recid']: + (errmsg, url) = perform_request_newticket(argd['recid'], uid) + if errmsg: + return page(title = _("Failed to create a ticket"), + body = _("Error")+": "+errmsg, + errors = [], + warnings = [], + uid = uid, + language = ln, + navtrail = navtrail, + lastupdated = __lastupdated__, + req = req) + else: + #redirect.. + redirect_to_url(req, url) + def __call__(self, req, form): """Redirect calls without final slash.""" if self.recid: redirect_to_url(req, '%s/record/%s/edit/' % (CFG_SITE_URL, self.recid)) else: redirect_to_url(req, '%s/record/edit/' % CFG_SITE_URL)