From: <ke...@us...> - 2006-03-31 13:53:54
|
Revision: 2876 Author: kevca Date: 2006-03-31 05:52:03 -0800 (Fri, 31 Mar 2006) ViewCVS: http://svn.sourceforge.net/mailmanager/?rev=2876&view=rev Log Message: ----------- Reinstating lost data from CVS from migration from sourceforge. The corresponding changes correspond to the following entries in the changelog - State (or Status) of Ticket not display (#1455795) - Issues with New state (#1456222) - Deleting users via the Settings tab fails (#1456837) Modified Paths: -------------- MailManager/branches/RELENG_2_1/CHANGES.txt MailManager/branches/RELENG_2_1/CREDITS.txt MailManager/branches/RELENG_2_1/Extensions/AccountPluggableBrain.py MailManager/branches/RELENG_2_1/Extensions/TicketPluggableBrain.py MailManager/branches/RELENG_2_1/MailManager.py MailManager/branches/RELENG_2_1/migrations/v2_1/__init__.py MailManager/branches/RELENG_2_1/ruleset/zope.py MailManager/branches/RELENG_2_1/sql/v2_1/addRulesetState.zsql MailManager/branches/RELENG_2_1/sql/v2_1/editTicket.zsql MailManager/branches/RELENG_2_1/sql/v2_1/listTickets.zsql MailManager/branches/RELENG_2_1/testdata/acmestatic/dataset.py MailManager/branches/RELENG_2_1/testdata/acmewidgets/dataset.py MailManager/branches/RELENG_2_1/testdata/mailtests/dataset.py MailManager/branches/RELENG_2_1/testdata/queuetests/dataset.py MailManager/branches/RELENG_2_1/testdata/rulesettests/dataset.py MailManager/branches/RELENG_2_1/testdata/sendingtests/dataset.py MailManager/branches/RELENG_2_1/testdata/sendingtests/tickets/000017.py MailManager/branches/RELENG_2_1/tests/testAPI.py MailManager/branches/RELENG_2_1/tests/testSending.py MailManager/branches/RELENG_2_1/version.txt MailManager/branches/RELENG_2_1/www/AccountSettings.zpt MailManager/branches/RELENG_2_1/www/Create.zpt MailManager/branches/RELENG_2_1/www/FilterSettings.zpt MailManager/branches/RELENG_2_1/www/GroupSettings.zpt MailManager/branches/RELENG_2_1/www/OptionSettings.zpt MailManager/branches/RELENG_2_1/www/QueueSettings.zpt MailManager/branches/RELENG_2_1/www/Queues.zpt MailManager/branches/RELENG_2_1/www/Search.zpt MailManager/branches/RELENG_2_1/www/Tickets.zpt MailManager/branches/RELENG_2_1/www/UserSettings.zpt MailManager/branches/RELENG_2_1/www/macros.zpt MailManager/branches/RELENG_2_1/www/master.zpt MailManager/branches/RELENG_2_1/www/test.zpt MailManager/branches/RELENG_2_1/www/ticket_index_html.zpt Modified: MailManager/branches/RELENG_2_1/CHANGES.txt =================================================================== --- MailManager/branches/RELENG_2_1/CHANGES.txt 2006-03-31 13:39:04 UTC (rev 2875) +++ MailManager/branches/RELENG_2_1/CHANGES.txt 2006-03-31 13:52:03 UTC (rev 2876) @@ -6,6 +6,9 @@ - TID in subject lines is now optional, based on database settings although no UI pages allow this to be configured * BUG FIXES +- State (or Status) of Ticket not display (#1455795) +- Issues with New state (#1456222) +- Deleting users via the Settings tab fails (#1456837) - Deleting an Account with tickets fails (#1459386) - RNG using too much entropy (#1459353) - Direct cycles of support_of are not allowed now. (#1353210) Modified: MailManager/branches/RELENG_2_1/CREDITS.txt =================================================================== --- MailManager/branches/RELENG_2_1/CREDITS.txt 2006-03-31 13:39:04 UTC (rev 2875) +++ MailManager/branches/RELENG_2_1/CREDITS.txt 2006-03-31 13:52:03 UTC (rev 2876) @@ -1,6 +1,7 @@ MailManager is created by Andrew Veitch, Clare Scrivener, James Henderson, -Graeme Mathieson, Kevin Campbell, Peter George and Eugene Antimirov at -Logicalware Ltd. The user interface was designed by Richard Cross. +Graeme Mathieson, Kevin Campbell, Peter George, Eugene Antimirov and +James Gray at Logicalware Ltd. The user interface was designed by +Richard Cross. Thanks to Zettai.net for sponorship of the development of some major new features. Modified: MailManager/branches/RELENG_2_1/Extensions/AccountPluggableBrain.py =================================================================== --- MailManager/branches/RELENG_2_1/Extensions/AccountPluggableBrain.py 2006-03-31 13:39:04 UTC (rev 2875) +++ MailManager/branches/RELENG_2_1/Extensions/AccountPluggableBrain.py 2006-03-31 13:52:03 UTC (rev 2876) @@ -474,8 +474,6 @@ else: msg_text = self.getHeader(msg, fil.header_name).lower() fil_text = fil.header_value.lower() - if type(msg_text) == unicode: - fil_text = fil_text.decode('utf-8') if fil.header_operator == 'exactly matches': if type(msg_text) is type([]): if msg_text.count(fil_text) == 0: @@ -544,7 +542,8 @@ return self.index_html(self, REQUEST) return ret - security.declareProtected('MailManager Manage Tickets', 'getTicketReplySubject') + security.declareProtected('MailManager Manage Tickets', + 'getTicketReplySubject') def getTicketReplySubject(self, ticket_id, subject): """ Return a subject for replying to the given ticket_id @@ -553,9 +552,9 @@ mainly for convenience for use in ZPTs. """ if self.tid_subject: - return 'TID %06d Re: %s' % (ticket_id, subject) + return u'TID %06d Re: %s' % (ticket_id, subject) else: - return 'Re: %s' % (subject) + return u'Re: %s' % (subject) security.declarePrivate('_getPOP3') def _getPOP3(self, ssl=False): Modified: MailManager/branches/RELENG_2_1/Extensions/TicketPluggableBrain.py =================================================================== --- MailManager/branches/RELENG_2_1/Extensions/TicketPluggableBrain.py 2006-03-31 13:39:04 UTC (rev 2875) +++ MailManager/branches/RELENG_2_1/Extensions/TicketPluggableBrain.py 2006-03-31 13:52:03 UTC (rev 2876) @@ -49,18 +49,6 @@ sig_remover = re.compile('^-- $.*\Z', re.DOTALL | re.MULTILINE) -#class SendmailFailure(Exception): -# """ Exception raised on mail sending failures -# -# """ -# -# def __init__ -# REQUEST.set('error', 'The To field may not be empty') -# REQUEST.set('flag_mail_to', 1) -# return self.ticket_index_html(self, REQUEST) - - - class TicketPluggableBrain(MailMixin): security = ClassSecurityInfo() @@ -144,6 +132,16 @@ FIXME: RULESETMODS """ + # Sort out encodings first + subject = unicode(subject, 'utf-8') + assigned = unicode(assigned, 'utf-8') + if category0 is not None: + category0 = unicode(category0, 'utf-8') + if category1 is not None: + category1 = unicode(category1, 'utf-8') + if category2 is not None: + category2 = unicode(category2, 'utf-8') + if REQUEST is not None: # Check for errors (if called through the web). error = None @@ -283,6 +281,34 @@ return REQUEST.RESPONSE.redirect('%s/ticket/%06d' % (self.getBaseURL(), self.id)) + security.declareProtected('MailManager Manage Tickets', 'viewTicket') + def viewTicket(self): + """ User interface hook to note the ticket has been viewed + + We are currently tracking unread status in two places, once in + the ticket itself in sql (2.0 functionality) and once as an + attribute in the ruleset via hooks. The latter will be used in + future revisions to allow for more complex rulesets + """ + self.sql.markRead(sqv_id = self.id) + + # Generate and process the event + user = getSecurityManager().getUser().getUserName() + try: + self.engine.processEvent('ViewTicket', user, self) + except NoTransitionError: + pass + + transitions = True + while transitions: + try: + self.engine.processEvent('Epsilon', None, self) + print "Transition made" + except NoTransitionError: + print "No transition made" + transitions = False + + security.declareProtected('MailManager Manage Tickets', 'export') def export(self, RESPONSE): """Export all of a ticket's messages in mbox format. TODO: export @@ -464,6 +490,15 @@ transition=None, event=None, sendmail=1, REQUEST=None, RESPONSE=None): + # Parameters are in utf-8, convert to unicode to avoid problems + mail_to = unicode(mail_to, 'utf-8') + cc = unicode(cc, 'utf-8') + bcc = unicode(bcc, 'utf-8') + subject = unicode(subject, 'utf-8') + body = unicode(body, 'utf-8') + if user_signature is not None: + user_signature = unicode(user_signature, 'utf-8') + # First, check for any modifications to the ticket if last_modified: mdate = self.sql.getTicketLastModified( @@ -625,7 +660,7 @@ if body_is_html: log('%sSanitising HTML body' % (self.getLogName()), logging.DEBUG, 'ticket.addnote') - body, html_body = '', html2safehtml(body) + body, html_body = '', body body_with_sig = body html_body_with_sig = self._addSignature(html_body, user_signature, account_signature, html=1) @@ -743,8 +778,86 @@ return RESPONSE.redirect('%s/ticket/%06d' % (self.getBaseURL(), self.id)) + def http_setTemplate(self, REQUEST): + """ Handler for user selecting a new template to use in a message + This method is called when the end user clicks the arrow button + to the right of the template. This method then replaces the + content of the current message body with the given template, + setting the editor to HTML/plain text as appropriate. If + template_name is None then do nothing. + Request variables: + + * template_name : the template name we are to replace with + * body_is_html : the format to encode the resulting body in + * body : altered to be the content of the given tempalte + """ + template_name = REQUEST.get('template_name', None).decode('utf-8') + body_is_html = REQUEST.get('body_is_html', False) + body = REQUEST.get('body', None).decode('utf-8') + + if template_name: + + # Obtain the template details, deailing with the cite_last + # method appropriately + if template_name == 'cite_last': + # Find the last actual message + msgs = self.sql.listMessages(sqv_ticket_id=self.id) + # Skip over any notes + for position in range(1,len(msgs)+1): + zmsg = msgs[-position] + if not zmsg.msg_to == '': break + # Remove the signature and split the plain text body per + # line, start with > for quote chars. Note that we don't + # currently quote HTML, but that would be a nice addition + # in the future. + cite = sig_remover.sub('', zmsg.body) + body = ''.join(['> %s' % line for line + in cite.splitlines(1)]) + template_is_html = 0 + else: + template = self._getTemplate(template_name) + body = template['body'] + template_is_html = template['html'] + + # Is this an HTML template? Check the settings flag + if template['html']: + # Set the body_is_html flag so that the correct editor is + # used for the reply + REQUEST.set('body_is_html', True) + + REQUEST.set('body', template['body']) + + return self.index_html(REQUEST) + + def http_setHTML(self, REQUEST): + """ Handler for user changing the format of a message body + + This method is called when the end user clicks the arrow button + to the right of the HTML/Plain text option. This method then + replaces the content of the current message body based on + given template, converting to HTML/plain text as appropriate. + + Request variables: + + * new_body_format: the format to encode the resulting body in + * body_is_html : gets set to the format the body is now in + * body : altered so that it is in the correct format + """ + body_is_html = REQUEST.get('body_is_html', False) + new_body_html = REQUEST.get('new_body_format', False) + body = REQUEST.get('body', '') + + if body_is_html and new_body_format + # Determine format of message body and convert if required. + body_is_html = body_is_html or template_is_html or self.HTMLRequired() + if body_is_html and not body_was_html: + body = self._makeHTML(body) + if not body_is_html and body_was_html: + body = html2text(body) + + security.declareProtected('MailManager Manage Tickets', 'setBody') def setBody(self, template_name='_plain', body_is_html=0, body=''): """Set the text of the message body and whether it is HTML. @@ -756,6 +869,7 @@ also be in HTML if the account signature is in HTML, but in this case there is no format select box. """ + # No change of body, possibly a change of format. if template_name.endswith('_plain'): template_name = template_name[:-6] @@ -768,28 +882,14 @@ except KeyError: template_is_html = body_is_html body_was_html = 1 + # A new choice of template. else: - if template_name == 'cite_last': - msgs = self.sql.listMessages(sqv_ticket_id=self.id) - for position in range(1,len(msgs)+1): - zmsg = msgs[-position] - if not zmsg.msg_to == '': break - cite = sig_remover.sub('', zmsg.body) - body = ''.join(['> %s' % line for line - in cite.splitlines(1)]) - template_is_html = 0 else: template = self._getTemplate(template_name) body = template['body'] template_is_html = template['html'] body_was_html = template_is_html - # Determine format of message body and convert if required. - body_is_html = body_is_html or template_is_html or self.HTMLRequired() - if body_is_html and not body_was_html: - body = self._makeHTML(body) - if not body_is_html and body_was_html: - body = html2text(body) # Reset template_name. template_name = '%s_%s' % (template_name, body_is_html and 'html' or 'plain') @@ -983,9 +1083,6 @@ if (idx + 1) < len(history): nextitem = history[idx + 1] - print "Item ", item.event - print "NextItem ", nextitem - # Combine epsilon transitons, so that only the end result # is shown. We don't reset the initial histitem for Epsilon # transitions, so we preseve the last modification of any Modified: MailManager/branches/RELENG_2_1/MailManager.py =================================================================== --- MailManager/branches/RELENG_2_1/MailManager.py 2006-03-31 13:39:04 UTC (rev 2875) +++ MailManager/branches/RELENG_2_1/MailManager.py 2006-03-31 13:52:03 UTC (rev 2876) @@ -86,7 +86,7 @@ # This should be updated each time the dataset changes. Read the developer # documentation for details about doing this. initialmversion = 'v2_1_0' -mversion = 'v2_1_1' +mversion = 'v2_1_2' url_pat = re.compile(r'(https?|ftp)://\S*[^,;:.!?)\]\'"\s]') @@ -1441,7 +1441,8 @@ return self.sql.homePageStats(sqv_assigned=user.getUserName()) security.declareProtected('MailManager View Tickets', 'listTickets') - def listTickets(self, REQUEST, count=False, offset=None, limit=None): + def listTickets(self, REQUEST, count=False, offset=None, limit=None, + queue_name=None): """Return a list of tickets for the main Tickets screen. Filter based on state or spam flag, handle results of @@ -1487,22 +1488,29 @@ # Customers, who have no tickets assigned to them, see tickets # assigned to anyone. Their view is restricted instead by the # from email field. - user = getSecurityManager().getUser() - if user.has_role('Customer'): - from_email = [x.access_email for x in self.sql.getCustomerAddresses(sqv_username=user.getUserName())] - if view == 'search': - search_email = svd.get('from_email_lower', '') - if search_email != '': - if search_email in from_email: - from_email = search_email - else: - from_email = None -# else: -# from_email = from_email[0] - assigned = svd.get('assigned', '') + + if queue_name is None: + user = getSecurityManager().getUser() + if user.has_role('Customer'): + from_email = [x.access_email for x in self.sql.getCustomerAddresses(sqv_username=user.getUserName())] + if view == 'search': + search_email = svd.get('from_email_lower', '') + if search_email != '': + if search_email in from_email: + from_email = search_email + else: + from_email = None + assigned = svd.get('assigned', '') + else: + from_email = svd.get('from_email_lower', '') + assigned = svd.get('assigned', user.getUserName()) else: + # Only list tickets assigned to the current queue manager + # role user + print "Listing tickets from ", queue_name from_email = svd.get('from_email_lower', '') - assigned = svd.get('assigned', user.getUserName()) + assigned = '_Q%s' % queue_name + print assigned # The LIKE operator needs % as wildcard if svd.get('from_name'): @@ -1542,13 +1550,8 @@ via the user interface. It is used through the ZPTs to give a more sensible user list. """ - users = [] - rawusers = self.sql.listUsers(sqv_username='') - for r in rawusers: - if not r.username.startswith('_'): - users.append(r.username) - return users - + return [user for user in self.sql.listUsers(sqv_username='') + if not user.username.startswith(u'_')] security.declareProtected('MailManager View Tickets', 'getQueueStats') def getQueueStats(self, queue_name): @@ -1753,11 +1756,12 @@ # User Settings - security.declareProtected('MailManager Settings', 'addOrEditMMUser') - def addOrEditMMUser(self, username, real_name, email, signature='', - password=None, confirm=None, tickets=False, reports=False, - abilities0=None, abilities1=None, abilities2=None, - settings=False, queues=False, REQUEST=None): + security.declareProtected('MailManager Settings', 'http_addOrEditMMUser') + def http_addOrEditMMUser(self, username, real_name, email, signature='', + password=None, confirm=None, tickets=False, + reports=False, settings=False, queues=False, + abilities0=[], abilities1=[], abilities2=[], + section='', REQUEST=None): """Add a user to the system.""" if REQUEST is not None: # Only check errors if called through the web @@ -1781,7 +1785,7 @@ error = 'Password and Confirm Password must match.' flag = 'password' REQUEST.set('flag_confirm', True) - if REQUEST['section'] == 'add': # Additional checks for new accounts + if section == 'add': # Additional checks for new accounts if not password: error = 'You must enter a Password.' flag = 'password' @@ -1795,11 +1799,50 @@ if error is not None: REQUEST.set('error', error) REQUEST.set('flag_%s' % flag, True) - return self.UserSettings(self, REQUEST) + return self.UserSettings(self, REQUEST, + real_name=unicode(real_name, 'utf-8'), + username=unicode(username, 'utf-8'), + email=unicode(email, 'utf-8'), + tickets=bool(tickets), + reports=bool(reports), + settings=bool(settings), + abilities0=[unicode(i, 'utf-8') for i in abilities0], + abilities1=[unicode(i, 'utf-8') for i in abilities1], + abilities2=[unicode(i, 'utf-8') for i in abilities2], + signature=unicode(signature, 'utf-8')) + + self.addOrEditMMUser(username=username, real_name=real_name, + email=email, signature=signature, password=password, + confirm=confirm, tickets=tickets, reports=reports, + settings=settings, queues=queues, abilities0=abilities0, + abilities1=abilities1, abilities2=abilities2, section=section) + + # User abilities + + if REQUEST is not None: + REQUEST.set('section', 'list') + return self.UserSettings(self, REQUEST) + + + security.declareProtected('MailManager Settings', 'addOrEditMMUser') + def addOrEditMMUser(self, username, real_name, email, signature='', + password=None, confirm=None, tickets=False, + reports=False, settings=False, queues=False, + abilities0=[], abilities1=[], abilities2=[], + section=''): + """ Add a user to the system. + + This method also takes care of encrypting the given password + before sending it to the database, and setting initial abilities + for the given user they can work with all tickets. + """ + if password: password = pw_encrypt(password, 'SSHA') + if not self.sql.listUsers(sqv_username=username): self.sql.addUser(sqv_username=username) + self.sql.editUser(sqv_username=username, sqv_real_name=real_name, sqv_email=email, sqv_signature=signature, sqv_password=password, sqv_tickets=self._sql_bool(tickets), @@ -1807,13 +1850,7 @@ sqv_settings=self._sql_bool(settings), sqv_queues=self._sql_bool(queues)) - # User abilities - - print abilities0 - print abilities1 - print abilities2 - - if abilities0 is not None: + if abilities0: if type(abilities0) is not list: abilities0 = [abilities0] @@ -1830,7 +1867,7 @@ sqv_ability = ability ) - if abilities1 is not None: + if abilities1: if type(abilities1) is not list: abilities1 = [abilities1] @@ -1847,7 +1884,7 @@ sqv_ability = ability ) - if abilities2 is not None: + if abilities2: if type(abilities2) is not list: abilities2 = [abilities2] @@ -1864,10 +1901,15 @@ sqv_ability = ability ) - if REQUEST is not None: - REQUEST.set('section', 'list') - return self.UserSettings(self, REQUEST) + + security.declareProtected('MailManager Settings', 'http_delUserForm') + def http_delUserForm(self, username, REQUEST): + """Display the Delete User form.""" + return self.UserSettings(self, REQUEST, + section='delete', + username=unicode(username, 'utf-8')) + security.declareProtected('MailManager Settings', 'http_delMMUser') def http_delMMUser(self, username, reassign, REQUEST): """ Delete a user and re-assign any accounts/tickets allocated to them. @@ -1885,6 +1927,15 @@ condition and will only lead to an SQL error being displayed to the user. + Some consistency need to be maintained in the database. + Specifically, users need to have all of their abilities removed + as these reference the given username. + + @type username: unicode + @param username: User to delete + @type reassign: unicode + @param reassign: User to reassign tickets to + @affectstickets: yes """ @@ -1900,20 +1951,34 @@ sqv_support_of = ticket.support_of, sqv_assigned = reassign) + # Remove abilities + self.sql.deleteUserAbility(sqv_username=username, sqv_catno=0) + self.sql.deleteUserAbility(sqv_username=username, sqv_catno=1) + self.sql.deleteUserAbility(sqv_username=username, sqv_catno=2) + # Delete the user. self.sql.deleteUser(sqv_username=username) - security.declareProtected('MailManager Settings', 'getUserDetails') - def getUserDetails(self, username): - """Get the details for a given user or None if user doesn't exist. + security.declareProtected('MailManager Settings', 'http_editUserForm') + def http_editUserForm(self, username, REQUEST): + """Display the form for editing a user. + The form will be populated with the settings for the given user. @affectstickets: no """ - results = self.sql.listUsers(sqv_username=username) - if results: - return results[0] - else: - return None + usr = self.sql.listUsers(sqv_username=username).dictionaries()[0] + abilities0 = [r.ability + for r in self.sql.listAbilities(sqv_username=username, + sqv_category_id=0)] + abilities1 = [r.ability + for r in self.sql.listAbilities(sqv_username=username, + sqv_category_id=1)] + abilities2 = [r.ability + for r in self.sql.listAbilities(sqv_username=username, + sqv_category_id=2)] + return self.UserSettings(self, REQUEST, section='edit', + abilities0=abilities0, abilities1=abilities1, + abilities2=abilities2, **usr) ############################################################################### @@ -2639,6 +2704,7 @@ security.declareProtected('MailManager Settings', 'http_addChoice') def http_addChoice(self, cat, choice, REQUEST): """Adds a choice to a category.""" + choice = unicode(choice, 'utf-8') if not choice: REQUEST.set('error', 'You must give your new choice a name.') elif choice in [res['choice'] for res in self.sql.listCategoryChoices(sqv_category_id=cat)]: Modified: MailManager/branches/RELENG_2_1/migrations/v2_1/__init__.py =================================================================== --- MailManager/branches/RELENG_2_1/migrations/v2_1/__init__.py 2006-03-31 13:39:04 UTC (rev 2875) +++ MailManager/branches/RELENG_2_1/migrations/v2_1/__init__.py 2006-03-31 13:52:03 UTC (rev 2876) @@ -19,11 +19,50 @@ # Update the mversion to show we are complete self.mversion = 'v2_1_1' +def migrate_v2_1_1_v2_1_2(self, migrationParams): + """ Move to multiple ruleset descriptions in the database + The previous release only had a single ruleset, entitled queuesystem. + We now need to move to using multiple rulesets and migrate existing + tickets between rulesets. Normally end users are only going to have + a single ruleset in use, which should be the latest revision of + queuesystem. + + Multiple rulesets in the same system would require some fixes still. + The account table would need a rsname attribute, and the ruleset + engine would need to load the ruleset from a ticket each time. Also, + difficulties arise when dealing with supporters over multiple + rulesets. + """ + + schema = migrationParams.get('schema', '') + create_tables = migrationParams.get('create_tables', False) + dbplatform = migrationParams.get('dbplatform', 'postgres') + + self.sql.migrateRulesetStates() + + # Load in additional ruleset and update engine + self.engine.setup(rsname = 'queuesystem_2_1_1') + + # Convert any tickets + for id in [t.id for t in self.sql.listTickets(sqv_state = 'New')]: + self.sql.editTicket(sqv_id = id, sqv_state = 'Open') + for id in [t.id for t in self.sql.listTickets(sqv_rsname = 'queuesystem')]: + self.sql.editTicket(sqv_id = id, sqv_rsname = 'queuesystem_2_1_1') + + # Update the mversion to show we are complete + self.mversion = 'v2_1_2' + + migrations = { 'v2_1_0' : { 'targetstate' : 'v2_1_1', 'overview' : 'Add on TID flag to accounts', 'method' : migrate_v2_1_0_v2_1_1, - } + }, + 'v2_1_1' : { + 'targetstate' : 'v2_1_2', + 'overview' : 'Move to multiple ruleset descriptions in the database', + 'method' : migrate_v2_1_1_v2_1_2, + }, } Modified: MailManager/branches/RELENG_2_1/ruleset/zope.py =================================================================== --- MailManager/branches/RELENG_2_1/ruleset/zope.py 2006-03-31 13:39:04 UTC (rev 2875) +++ MailManager/branches/RELENG_2_1/ruleset/zope.py 2006-03-31 13:52:03 UTC (rev 2876) @@ -222,7 +222,7 @@ def generateDefaults(self, rsname): - for attribute in ['Overdue', 'Responded', 'Spam', 'OpenChildren', '_AssignQueue', '_AssignUser', '_AssignGroup']: + for attribute in ['Overdue', 'Responded', 'Spam', 'Viewed', 'OpenChildren', '_AssignQueue', '_AssignUser', '_AssignGroup']: self.sql.addRulesetAttribute( sqv_rsname = rsname, sqv_attribute = attribute Modified: MailManager/branches/RELENG_2_1/sql/v2_1/addRulesetState.zsql =================================================================== --- MailManager/branches/RELENG_2_1/sql/v2_1/addRulesetState.zsql 2006-03-31 13:39:04 UTC (rev 2875) +++ MailManager/branches/RELENG_2_1/sql/v2_1/addRulesetState.zsql 2006-03-31 13:52:03 UTC (rev 2876) @@ -1,12 +1,14 @@ <dtml-comment> title:Add a state to a ruleset connection_id:mailmanager_db -arguments:sqv_name sqv_description +arguments:sqv_name sqv_rsname sqv_description </dtml-comment> INSERT INTO <dtml-var schema>mm_ruleset_states ( - name + name, + rsname <dtml-if sqv_description>, description </dtml-if> ) VALUES ( - <dtml-sqlvar sqv_name type="nb"> + <dtml-sqlvar sqv_name type="nb">, + <dtml-sqlvar sqv_rsname type="nb"> <dtml-if sqv_description> , <dtml-sqlvar sqv_description type="nb"> </dtml-if> ) Modified: MailManager/branches/RELENG_2_1/sql/v2_1/editTicket.zsql =================================================================== --- MailManager/branches/RELENG_2_1/sql/v2_1/editTicket.zsql 2006-03-31 13:39:04 UTC (rev 2875) +++ MailManager/branches/RELENG_2_1/sql/v2_1/editTicket.zsql 2006-03-31 13:52:03 UTC (rev 2876) @@ -2,7 +2,7 @@ title:Edit a Ticket connection_id: mailmanager_db max_rows:0 -arguments:sqv_id sqv_subject sqv_assigned sqv_state sqv_priority sqv_category0 sqv_category1 sqv_category2 sqv_support_of sqv_set_date_closed sqv_clear_date_closed sqv_debug_date_opened sqv_debug_respond_by sqv_debug_date_responded sqv_debug_date_closed sqv_set_support_of sqv_clear_support_of +arguments:sqv_id sqv_subject sqv_assigned sqv_state sqv_priority sqv_category0 sqv_category1 sqv_category2 sqv_support_of sqv_set_date_closed sqv_clear_date_closed sqv_debug_date_opened sqv_debug_respond_by sqv_debug_date_responded sqv_debug_date_closed sqv_set_support_of sqv_clear_support_of sqv_rsname The initial assignment of id = id purely exists so that the commas separating each sql variable can be added in a clean fashion. It is purely a dummy @@ -42,6 +42,9 @@ <dtml-if sqv_state> , state=<dtml-sqlvar sqv_state type="nb"> </dtml-if> + <dtml-if sqv_rsname> + , rsname=<dtml-sqlvar sqv_rsname type="nb"> + </dtml-if> <dtml-if sqv_priority> , priority=<dtml-sqlvar sqv_priority type="int"> </dtml-if> Modified: MailManager/branches/RELENG_2_1/sql/v2_1/listTickets.zsql =================================================================== --- MailManager/branches/RELENG_2_1/sql/v2_1/listTickets.zsql 2006-03-31 13:39:04 UTC (rev 2875) +++ MailManager/branches/RELENG_2_1/sql/v2_1/listTickets.zsql 2006-03-31 13:52:03 UTC (rev 2876) @@ -2,7 +2,7 @@ title:List tickets connection_id: mailmanager_db max_rows: 0 -arguments:sqv_count sqv_sort_on sqv_sort_order sqv_limit sqv_offset sqv_unread sqv_state sqv_account_id sqv_assigned sqv_from_date sqv_to_date sqv_priority sqv_category0 sqv_category1 sqv_category2 sqv_from_name sqv_from_email sqv_support_of sqv_ticket_id sqv_searchText sqv_is_overdue sqv_subject +arguments:sqv_count sqv_sort_on sqv_sort_order sqv_limit sqv_offset sqv_unread sqv_state sqv_account_id sqv_assigned sqv_from_date sqv_to_date sqv_priority sqv_category0 sqv_category1 sqv_category2 sqv_from_name sqv_from_email sqv_support_of sqv_ticket_id sqv_searchText sqv_is_overdue sqv_subject sqv_rsname This method does quite a lot of varying operations, and should probably at some point be split into separate methods for clarity. Note that the dtml @@ -127,6 +127,8 @@ <dtml-and> <dtml-sqltest sqv_subject column="subject" type="nb" optional> <dtml-and> + <dtml-sqltest sqv_rsname column="rsname" type="nb" optional> + <dtml-and> <dtml-sqltest sqv_assigned column="assigned" type="nb" optional> <dtml-and> <dtml-if sqv_from_date> Modified: MailManager/branches/RELENG_2_1/testdata/acmestatic/dataset.py =================================================================== --- MailManager/branches/RELENG_2_1/testdata/acmestatic/dataset.py 2006-03-31 13:39:04 UTC (rev 2875) +++ MailManager/branches/RELENG_2_1/testdata/acmestatic/dataset.py 2006-03-31 13:52:03 UTC (rev 2876) @@ -6,7 +6,7 @@ # # Description file for the acmewidgets dataset -# $Header$ +# $Header: /cvsroot/mailmanager/MailManager/testdata/acmestatic/dataset.py,v 1.6 2006/01/09 17:37:47 mathie Exp $ # { Modified: MailManager/branches/RELENG_2_1/testdata/acmewidgets/dataset.py =================================================================== --- MailManager/branches/RELENG_2_1/testdata/acmewidgets/dataset.py 2006-03-31 13:39:04 UTC (rev 2875) +++ MailManager/branches/RELENG_2_1/testdata/acmewidgets/dataset.py 2006-03-31 13:52:03 UTC (rev 2876) @@ -6,7 +6,7 @@ # # Description file for the acmewidgets dataset -# $Header$ +# $Header: /cvsroot/mailmanager/MailManager/testdata/acmewidgets/dataset.py,v 1.7 2006/01/09 17:37:47 mathie Exp $ # { Modified: MailManager/branches/RELENG_2_1/testdata/mailtests/dataset.py =================================================================== --- MailManager/branches/RELENG_2_1/testdata/mailtests/dataset.py 2006-03-31 13:39:04 UTC (rev 2875) +++ MailManager/branches/RELENG_2_1/testdata/mailtests/dataset.py 2006-03-31 13:52:03 UTC (rev 2876) @@ -6,7 +6,7 @@ # # Description file for the mailtests dataset -# $Header$ +# $Header: /cvsroot/mailmanager/MailManager/testdata/mailtests/dataset.py,v 1.4.2.1 2006/02/27 14:27:47 kevca Exp $ # { Modified: MailManager/branches/RELENG_2_1/testdata/queuetests/dataset.py =================================================================== --- MailManager/branches/RELENG_2_1/testdata/queuetests/dataset.py 2006-03-31 13:39:04 UTC (rev 2875) +++ MailManager/branches/RELENG_2_1/testdata/queuetests/dataset.py 2006-03-31 13:52:03 UTC (rev 2876) @@ -6,7 +6,7 @@ # # Description file for the mailtests dataset -# $Header$ +# $Header: /cvsroot/mailmanager/MailManager/testdata/queuetests/dataset.py,v 1.5.2.3 2006/03/15 10:43:37 kevca Exp $ # # Modified: MailManager/branches/RELENG_2_1/testdata/rulesettests/dataset.py =================================================================== --- MailManager/branches/RELENG_2_1/testdata/rulesettests/dataset.py 2006-03-31 13:39:04 UTC (rev 2875) +++ MailManager/branches/RELENG_2_1/testdata/rulesettests/dataset.py 2006-03-31 13:52:03 UTC (rev 2876) @@ -6,7 +6,7 @@ # # Description file for the mailtests dataset -# $Header$ +# $Header: /cvsroot/mailmanager/MailManager/testdata/rulesettests/dataset.py,v 1.1.4.2 2006/02/22 16:27:30 kevca Exp $ # Modified: MailManager/branches/RELENG_2_1/testdata/sendingtests/dataset.py =================================================================== --- MailManager/branches/RELENG_2_1/testdata/sendingtests/dataset.py 2006-03-31 13:39:04 UTC (rev 2875) +++ MailManager/branches/RELENG_2_1/testdata/sendingtests/dataset.py 2006-03-31 13:52:03 UTC (rev 2876) @@ -6,7 +6,7 @@ # # Description file for the acmewidgets dataset -# $Header$ +# $Header: /cvsroot/mailmanager/MailManager/testdata/sendingtests/dataset.py,v 1.2.2.2 2006/02/22 16:27:31 kevca Exp $ # { Modified: MailManager/branches/RELENG_2_1/testdata/sendingtests/tickets/000017.py =================================================================== --- MailManager/branches/RELENG_2_1/testdata/sendingtests/tickets/000017.py 2006-03-31 13:39:04 UTC (rev 2875) +++ MailManager/branches/RELENG_2_1/testdata/sendingtests/tickets/000017.py 2006-03-31 13:52:03 UTC (rev 2876) @@ -5,7 +5,7 @@ Content-Transfer-Encoding: 7bit Date: Mon, 18 Jul 2005 15:44:05 +0100 From: Dougal MacDougal <dougal@widgetworld.example> -To: enquiries@acmewidgets.example +To: sales@acmewidgets.example Subject: Widgetworld Exhibition 2005 User-Agent: logicalware mail manager 2.0.0-rc2 Message-ID: <200...@da...> Modified: MailManager/branches/RELENG_2_1/tests/testAPI.py =================================================================== --- MailManager/branches/RELENG_2_1/tests/testAPI.py 2006-03-31 13:39:04 UTC (rev 2875) +++ MailManager/branches/RELENG_2_1/tests/testAPI.py 2006-03-31 13:52:03 UTC (rev 2876) @@ -269,8 +269,6 @@ self.mmobj.addOrEditMMUser('kev', 'Kevin Campbell', 'kev@test.example') # editMMUser self.mmobj.addOrEditMMUser('kev', 'Alan Campbell', 'kev@test.example') - # getUserDetails - self.mmobj.getUserDetails('kev') # getUserConstraints self.mmobj.addGroup(group_name='sales', usernames=['kev']) Modified: MailManager/branches/RELENG_2_1/tests/testSending.py =================================================================== --- MailManager/branches/RELENG_2_1/tests/testSending.py 2006-03-31 13:39:04 UTC (rev 2875) +++ MailManager/branches/RELENG_2_1/tests/testSending.py 2006-03-31 13:52:03 UTC (rev 2876) @@ -38,6 +38,7 @@ import shutil import DocumentTemplate import DateTime +import email import testDatabase @@ -95,8 +96,11 @@ self.addNoteTest() self.attachmentTest() self.loopTest() + self.viewTicketTest() self.TIDTest() + self.HTMLTest() + def sendTest(self): """ Calls the web interface method to send a reply and close a ticket @@ -469,6 +473,12 @@ msg_count = self.getMsgCount(ticket.id) + # Get the last modified date of the ticket + ticket = self.mmobj.ticket(id=4)[0] + last_modified = self.mmobj.sql.getTicketLastModified( + sqv_ticket_id = ticket.id + )[0].last_modified.strftime('%s') + result = ticket.http_addNote( mail_to = '', subject = 'Test', @@ -589,6 +599,28 @@ #_logtrace = True + def viewTicketTest(self): + """ Not the best place for this test, but check that viewing a ticket + updates the value for last_modified """ + + # Get the last modified date of the ticket + ticket = self.mmobj.ticket(id=7)[0] + last_modified = self.mmobj.sql.getTicketLastModified( + sqv_ticket_id = ticket.id + )[0].last_modified.strftime('%s') + + # View the ticket + html = ticket.index_html(REQUEST=self.request) + + # Get the new last modified date of the ticket + ticket = self.mmobj.ticket(id=7)[0] + new_last_modified = self.mmobj.sql.getTicketLastModified( + sqv_ticket_id = ticket.id + )[0].last_modified.strftime('%s') + + self.failUnless(new_last_modified > last_modified) + + def TIDTest(self): """ Check that the account flag tid_subject alters the subject line of responses. @@ -598,16 +630,11 @@ behaviour. """ - # Get the last modified date of the ticket - ticket = self.mmobj.ticket(id=7)[0] - last_modified = self.mmobj.sql.getTicketLastModified( - sqv_ticket_id = ticket.id - )[0].last_modified.strftime('%s') - self.mmobj.sql.editAccount( sqv_email = 'sales@acmewidgets.example', sqv_tid_subject = self.mmobj.sql_falsevar ) + ticket = self.mmobj.ticket(id=7)[0] html = ticket.index_html(REQUEST=self.request) self.failIf('TID 000007 Re: Purple Widgets' in html) self.failUnless('Purple Widgets' in html) @@ -617,14 +644,21 @@ sqv_email = 'sales@acmewidgets.example', sqv_tid_subject = self.mmobj.sql_truevar ) + ticket = self.mmobj.ticket(id=7)[0] html = ticket.index_html(REQUEST=self.request) self.failUnless('TID 000007 Re: Purple Widgets' in html) + # Get the last modified date of the ticket + ticket = self.mmobj.ticket(id=7)[0] + last_modified = self.mmobj.sql.getTicketLastModified( + sqv_ticket_id = ticket.id + )[0].last_modified.strftime('%s') # Send a reply, check that it we can explicitly remove the TID # from the subject by hand msg_count = self.getMsgCount(ticket.id) + print "Sending reply", msg_count result = ticket.http_sendReply( mail_to = 'kev@logicalware.example', subject = 'Test', @@ -633,8 +667,10 @@ last_modified = last_modified, REQUEST = self.request, RESPONSE = self.request.response) + print result new_msg_count = self.getMsgCount(ticket.id) + print "Sent reply", new_msg_count self.failUnless(new_msg_count == (msg_count + 1)) msginfo = self.mmobj.MailHost.messages[-1:][0] @@ -648,7 +684,49 @@ self.failUnless(headerfound) + def HTMLTest(self): + """ Check that HTML messages are delivered as HTML """ + # Get the last modified date of the ticket + ticket = self.mmobj.ticket(id=7)[0] + last_modified = self.mmobj.sql.getTicketLastModified( + sqv_ticket_id = ticket.id + )[0].last_modified.strftime('%s') + + # Send a reply, check that it we can explicitly remove the TID + # from the subject by hand + msg_count = self.getMsgCount(ticket.id) + + print "Sending reply", msg_count + result = ticket.http_sendReply( + mail_to = 'kev@logicalware.example', + subject = 'Test', + body = '<h1>Test</h1>', + body_is_html = 1, + last_modified = last_modified, + REQUEST = self.request, + RESPONSE = self.request.response) + print result + + new_msg_count = self.getMsgCount(ticket.id) + print "Sent reply", new_msg_count + self.failUnless(new_msg_count == (msg_count + 1)) + + msginfo = self.mmobj.MailHost.messages[-1:][0] + msg = email.message_from_string(msginfo['text']) + payload = msg.get_payload(i=0) + print payload.get_payload(decode=True) + self.fail() + self.failUnless(msginfo['from'] == 'sales@acmewidgets.example') + self.failUnless(msginfo['to'][0] == 'kev@logicalware.example') + headerfound = False + for line in msginfo['text'].split('\n'): + if line.startswith('Subject'): + self.failUnless(line.replace('Subject: ','') == 'Test') + headerfound = True + self.failUnless(headerfound) + + def getMsgCount(self, ticket_id): return len(self.mmobj.sql.listMessages( sqv_ticket_id = ticket_id Modified: MailManager/branches/RELENG_2_1/version.txt =================================================================== --- MailManager/branches/RELENG_2_1/version.txt 2006-03-31 13:39:04 UTC (rev 2875) +++ MailManager/branches/RELENG_2_1/version.txt 2006-03-31 13:52:03 UTC (rev 2876) @@ -1 +1 @@ -2.1-rc1 +2.1-rc2 Modified: MailManager/branches/RELENG_2_1/www/AccountSettings.zpt =================================================================== --- MailManager/branches/RELENG_2_1/www/AccountSettings.zpt 2006-03-31 13:39:04 UTC (rev 2875) +++ MailManager/branches/RELENG_2_1/www/AccountSettings.zpt 2006-03-31 13:52:03 UTC (rev 2876) @@ -113,7 +113,7 @@ <label for="assign_user" tal:attributes="class python:test(options.has_key('flag_assign'), 'error', None)" i18n:translate="assign_to">Assign to User</label> <select tal:attributes="tabindex tabindex/next" id="assign_user" name="assign_user"> <option value="" i18n:translate="none">None</option> - <option tal:repeat="user here/listUsers" tal:attributes="value user; selected python:test(assign_user == user, 'selected', None)" tal:content="user">Administrator</option> + <option tal:repeat="user here/listUsers" tal:attributes="value user/username; selected python:test(assign_user == user.username, 'selected', None)" tal:content="user/username">Administrator</option> </select> </p> <p> Modified: MailManager/branches/RELENG_2_1/www/Create.zpt =================================================================== --- MailManager/branches/RELENG_2_1/www/Create.zpt 2006-03-31 13:39:04 UTC (rev 2875) +++ MailManager/branches/RELENG_2_1/www/Create.zpt 2006-03-31 13:52:03 UTC (rev 2876) @@ -42,7 +42,7 @@ <p> <label for="assigned" tal:attributes="class python:test(request.has_key('flag_assigned'), 'error', None)" i18n:translate="assigned_label">Assigned</label> <select tal:attributes="tabindex tabindex/next" id="assigned" name="assigned"> - <option tal:repeat="user here/listUsers" tal:attributes="selected python:test(user == here.sql.listUsers(sqv_username=user)[0].real_name, 1, None)" tal:content="user">Username</option> + <option tal:repeat="u here/listUsers" tal:attributes="selected python:test(u.username == user.getUserName(), 'selected', None)" tal:content="u/username">Username</option> </select> </p> <p metal:use-macro="container/macros/macros/priority"> Modified: MailManager/branches/RELENG_2_1/www/FilterSettings.zpt =================================================================== --- MailManager/branches/RELENG_2_1/www/FilterSettings.zpt 2006-03-31 13:39:04 UTC (rev 2875) +++ MailManager/branches/RELENG_2_1/www/FilterSettings.zpt 2006-03-31 13:52:03 UTC (rev 2876) @@ -120,7 +120,7 @@ <label for="assign_user" tal:attributes="class python:test(request.has_key('flag_assign_user'), 'error', None)" i18n:translate="assign_to">Assign to User</label> <select tal:attributes="tabindex tabindex/next" id="assign_user" name="assign_user"> <option value="" i18n:translate="default">Default</option> - <option tal:repeat="user here/listUsers" tal:attributes="value user" tal:content="user">Administrator</option> + <option tal:repeat="user here/listUsers" tal:attributes="value user/username" tal:content="user/username">Administrator</option> </select> </p> <p> Modified: MailManager/branches/RELENG_2_1/www/GroupSettings.zpt =================================================================== --- MailManager/branches/RELENG_2_1/www/GroupSettings.zpt 2006-03-31 13:39:04 UTC (rev 2875) +++ MailManager/branches/RELENG_2_1/www/GroupSettings.zpt 2006-03-31 13:52:03 UTC (rev 2876) @@ -76,8 +76,8 @@ <p> <label i18n:translate="users_field">Users</label> <span class="indentedonform" tal:repeat="user here/listUsers"> - <input tal:attributes="tabindex tabindex/next; value user; id user" type="checkbox" name="usernames:list" /> - <label tal:attributes="for user" tal:content="user">Administrator</label><br /> + <input tal:attributes="tabindex tabindex/next; value user/username; id user/username" type="checkbox" name="usernames:list" /> + <label tal:attributes="for user/username" tal:content="user/username">Administrator</label><br /> </span> </p> <p> @@ -98,8 +98,8 @@ <p> <label i18n:translate="users_field">Users</label> <span class="indentedonform" tal:repeat="user here/listUsers"> - <input tal:define="members python:[m.username for m in here.sql.listGroupMembers(sqv_group_name=group_name)]" tal:attributes="tabindex tabindex/next; id user; value user; checked python:test(user in members, '1', None)" type="checkbox" name="usernames:list" /> - <label tal:attributes="for user" tal:replace="user">Administrator</label><br /> + <input tal:define="members python:[m.username for m in here.sql.listGroupMembers(sqv_group_name=group_name)]" tal:attributes="tabindex tabindex/next; id user; value user; checked python:test(user.username in members, 'checked', None)" type="checkbox" name="usernames:list" /> + <label tal:attributes="for user" tal:content="user/username">Administrator</label><br /> </span> </p> <p> Modified: MailManager/branches/RELENG_2_1/www/OptionSettings.zpt =================================================================== --- MailManager/branches/RELENG_2_1/www/OptionSettings.zpt 2006-03-31 13:39:04 UTC (rev 2875) +++ MailManager/branches/RELENG_2_1/www/OptionSettings.zpt 2006-03-31 13:52:03 UTC (rev 2876) @@ -98,10 +98,10 @@ Body </td> <td> - <a tal:define="name python:pss.url_quote(template['name'])" tal:attributes="href string:OptionSettings?section=edit&temp_name=$name" i18n:translate="edit" class="state edit">Edit</a> + <a tal:attributes="href string:OptionSettings?section=edit&temp_name=${template/name}" i18n:translate="edit" class="state edit">Edit</a> </td> <td> - <a tal:define="name python:pss.url_quote(template['name'])" tal:attributes="href string:deleteTemplate?section=templates&del_temp=$name" i18n:translate="delete" class="state delete">Delete</a> + <a tal:attributes="href string:deleteTemplate?section=templates&del_temp=${template/name}" i18n:translate="delete" class="state delete">Delete</a> </td> </tr> </tbody> @@ -120,8 +120,8 @@ <p tal:define="cat python:here.sql.listCategories(sqv_category_id=number)[0]"> <input name="cat:int" type="hidden" tal:attributes="tabindex tabindex/next; value number" /> <input name="label" size="30" tal:attributes="tabindex tabindex/next; value cat/label" /> - <input type="radio" id="cat_on" name="on:int" value="1" tal:attributes="tabindex tabindex/next; checked cat/enabled | nothing" /> <label for="cat_on" tal:attributes="class python:test(request.has_key('flag_cat_on'), 'error', None)" i18n:translate="on">On</label> - <input type="radio" id="cat_off" name="on:int" value="0" tal:attributes="tabindex tabindex/next; checked not:cat/enabled | nothing" /> <label for="cat_off" tal:attributes="class python:test(request.has_key('flag_cat_off'), 'error', None)" i18n:translate="off">Off</label> + <input type="radio" id="cat_on" name="on:int" value="1" tal:attributes="tabindex tabindex/next; checked python:test(cat.enabled, 'checked', None)" /> <label for="cat_on" tal:attributes="class python:test(request.has_key('flag_cat_on'), 'error', None)" i18n:translate="on">On</label> + <input type="radio" id="cat_off" name="on:int" value="0" tal:attributes="tabindex tabindex/next; checked python:test(not cat.enabled, 'checked', None)" /> <label for="cat_off" tal:attributes="class python:test(request.has_key('flag_cat_off'), 'error', None)" i18n:translate="off">Off</label> <input tal:attributes="tabindex tabindex/next" type="submit" value="Change" name="setCategory:method" /> </p> <table class="settings" summary="Category choices" i18n:attributes="summary category_choices;"> Modified: MailManager/branches/RELENG_2_1/www/QueueSettings.zpt =================================================================== --- MailManager/branches/RELENG_2_1/www/QueueSettings.zpt 2006-03-31 13:39:04 UTC (rev 2875) +++ MailManager/branches/RELENG_2_1/www/QueueSettings.zpt 2006-03-31 13:52:03 UTC (rev 2876) @@ -10,7 +10,8 @@ view string:queues; help string:HelpQueues; section python:request.get('section', 'list'); - dummy python:request.RESPONSE.setHeader('content-type', 'text/html;;charset=UTF-8')"> + dummy python:request.RESPONSE.setHeader('content-type', 'text/html;;charset=UTF-8'); + queue_name python:unicode(request.get('queue_name', ''), 'utf-8')"> <head metal:use-macro="container/master/macros/head"> <title metal:fill-slot="title">Logicalware MailManager : Queue Settings</title> </head> @@ -38,19 +39,17 @@ </thead> <tbody> <tr tal:repeat="q python:here.sql.listQueues(sqv_queue_name='')" tal:attributes="class python:test(repeat['q'].odd(),'odd','even')"> - <span tal:define="queue_name python:q.queue_name.decode('utf-8')"> <td> - <strong tal:content="queue_name">Sales</strong> + <strong tal:content="q/queue_name">Sales</strong> </td> <td tal:content="q/assign_group"> </td> <td tal:condition="python:section == 'list'"> - <a tal:attributes="href string:QueueSettings?section=edit&queue_name=${queue_name}" i18n:translate="edit" class="state edit">Edit</a> + <a tal:attributes="href string:QueueSettings?section=edit&queue_name=${q/queue_name}" i18n:translate="edit" class="state edit">Edit</a> </td> <td tal:condition="python:section == 'remove'"> - <a tal:attributes="href string:QueueSettings?section=delete&queue_name=${queue_name}" i18n:translate="delete" class="state delete">Delete</a> + <a tal:attributes="href string:QueueSettings?section=delete&queue_name=${q/queue_name}" i18n:translate="delete" class="state delete">Delete</a> </td> - </span> </tr> <tr tal:condition="python:not here.sql.listQueues(sqv_queue_name='')"> <td colspan="3" i18n:translate="no_queues"> Modified: MailManager/branches/RELENG_2_1/www/Queues.zpt =================================================================== --- MailManager/branches/RELENG_2_1/www/Queues.zpt 2006-03-31 13:39:04 UTC (rev 2875) +++ MailManager/branches/RELENG_2_1/www/Queues.zpt 2006-03-31 13:52:03 UTC (rev 2876) @@ -14,8 +14,9 @@ sort_on svd/sort_on | string:id; sort_order svd/sort_order | string:desc; dummy python:request.SESSION.set('view', 'Queued'); - results python:here.listTickets(request); - num_tickets python:here.listTickets(request, count=True)[0].count; + queue_name python:unicode(request.SESSION.get('queue_name', ''), 'utf-8'); + results python:here.listTickets(request, queue_name=queue_name); + num_tickets python:here.listTickets(request, queue_name=queue_name, count=True)[0].count; offset python:request.SESSION.get('offset', 0); dummy python:request.RESPONSE.setHeader('content-type', 'text/html;;charset=UTF-8')"> <head metal:use-macro="container/master/macros/head"> @@ -49,7 +50,7 @@ <label for="assigned" i18n:translate="assigned">Assigned</label> <select tal:attributes="tabindex tabindex/next" id="assigned" name="assigned"> <option value="" i18n:translate="no_change">No Change</option> - <option tal:repeat="item python:here/listUsers" tal:attributes="value user" tal:content="user">me</option> + <option tal:repeat="user here/listUsers" tal:attributes="value user/username" tal:content="user/username">me</option> </select> <label for="state" i18n:translate="state_label">State</label> <select tal:attributes="tabindex tabindex/next" id="state" name="state"> @@ -78,44 +79,21 @@ Error details </p> - <p tal:condition="python:not num_tickets"> + <p tal:condition="python:not num_tickets" + tal:define="mode python:'get'"> No tickets exist in this queue, please select a queue to work with - <table > - <thead> - <tr> - <th> - Queue Name - </th> - <th> - Ticket Count - </th> - <th> - Queue Details - </th> - <th> - </th> - </tr> - </thead> - - <tbody> - <tr tal:repeat="queueinfo python:here.sql.listQueues() "> - <td> - <a tal:attributes="href python:'setQueuesSession?key=assigned&value=%s' % queueinfo.queue_user" - tal:content="queueinfo/queue_name">incoming</a> - </td> - <td tal:content="python:here.sql.listTickets(sqv_assigned='_Q' + queueinfo.queue_name, sqv_count=here.sql_true)[0].count"> - 1 - </td> - <td> - Next Overdue at 12:23 - </td> - </tr> - </tbody> - </table> + <p metal:use-macro="container/macros/macros/queue_list"/> </p> + <p tal:condition="python:num_tickets" + tal:define="mode python:'get'"> + + Select a different queue to work with + + <p metal:use-macro="container/macros/macros/queue_list"/> + </p> </div> </body> </html> Modified: MailManager/branches/RELENG_2_1/www/Search.zpt =================================================================== --- MailManager/branches/RELENG_2_1/www/Search.zpt 2006-03-31 13:39:04 UTC (rev 2875) +++ MailManager/branches/RELENG_2_1/www/Search.zpt 2006-03-31 13:52:03 UTC (rev 2876) @@ -73,7 +73,7 @@ <label for="assigned" tal:attributes="class python:test(request.has_key('flag_assigned'), 'error', None)" i18n:translate="assigned_label">Assigned</label> <select tal:attributes="tabindex tabindex/next" id="assigned" name="assigned"> <option value="" i18n:tran... [truncated message content] |
From: <ke...@us...> - 2006-04-03 11:53:10
|
Revision: 2885 Author: kevca Date: 2006-04-03 04:52:56 -0700 (Mon, 03 Apr 2006) ViewCVS: http://svn.sourceforge.net/mailmanager/?rev=2885&view=rev Log Message: ----------- Merged in most of the changes from the 2.0.7 fix for - Changing format away from HTML generates a Zope error (#1460216) Not fully tested yet Modified Paths: -------------- MailManager/branches/RELENG_2_1/CHANGES.txt MailManager/branches/RELENG_2_1/Extensions/TicketPluggableBrain.py MailManager/branches/RELENG_2_1/www/ticket_index_html.zpt Modified: MailManager/branches/RELENG_2_1/CHANGES.txt =================================================================== --- MailManager/branches/RELENG_2_1/CHANGES.txt 2006-04-03 10:44:16 UTC (rev 2884) +++ MailManager/branches/RELENG_2_1/CHANGES.txt 2006-04-03 11:52:56 UTC (rev 2885) @@ -77,6 +77,7 @@ Version 2.0.8 (Unreleased) * BUG FIXES +- Changing format away from HTML generates a Zope error (#1460216) - Tickets without a subject no longer result in a permissions error (#1436863) - Test dataset has broken messages (#1422365) - Account and User drop downs now work with Internet Explorer (#1410232) Modified: MailManager/branches/RELENG_2_1/Extensions/TicketPluggableBrain.py =================================================================== --- MailManager/branches/RELENG_2_1/Extensions/TicketPluggableBrain.py 2006-04-03 10:44:16 UTC (rev 2884) +++ MailManager/branches/RELENG_2_1/Extensions/TicketPluggableBrain.py 2006-04-03 11:52:56 UTC (rev 2885) @@ -778,6 +778,7 @@ return RESPONSE.redirect('%s/ticket/%06d' % (self.getBaseURL(), self.id)) + def http_setTemplate(self, REQUEST): """ Handler for user selecting a new template to use in a message @@ -846,55 +847,21 @@ * body : altered so that it is in the correct format """ body_is_html = REQUEST.get('body_is_html', False) - new_body_html = REQUEST.get('new_body_format', False) - body = REQUEST.get('body', '') - - if body_is_html and new_body_format - # Determine format of message body and convert if required. - body_is_html = body_is_html or template_is_html or self.HTMLRequired() - if body_is_html and not body_was_html: - body = self._makeHTML(body) - if not body_is_html and body_was_html: + new_body_html = REQUEST.get('new_body_html', False) + body = REQUEST.get('body', '').decode('utf-8') + + if body_is_html and not new_body_html: + # Convert from html to text body = html2text(body) + if not body_is_html and new_body_html: + # Convert from text to html + body = self._makeHTML(body) - security.declareProtected('MailManager Manage Tickets', 'setBody') - def setBody(self, template_name='_plain', body_is_html=0, body=''): - """Set the text of the message body and whether it is HTML. + REQUEST.set('body', body.encode('utf-8')) + REQUEST.set('body_is_html', new_body_html) + return self.index_html(REQUEST) - Note that if an HTML template is chosen it may not be - converted to plain text even if 'plain' is chosen from the - format select box (the 'plain' option is still useful if - another template is chosen at the same time). The body must - also be in HTML if the account signature is in HTML, but in - this case there is no format select box. - """ - - # No change of body, possibly a change of format. - if template_name.endswith('_plain'): - template_name = template_name[:-6] - template_is_html = 0 - body_was_html = 0 - elif template_name.endswith('_html'): - template_name = template_name[:-5] - try: - template_is_html = self._getTemplate(template_name)['html'] - except KeyError: - template_is_html = body_is_html - body_was_html = 1 - - # A new choice of template. - else: - else: - template = self._getTemplate(template_name) - body = template['body'] - template_is_html = template['html'] - body_was_html = template_is_html - # Reset template_name. - template_name = '%s_%s' % (template_name, - body_is_html and 'html' or 'plain') - return template_name, body_is_html, body - security.declarePrivate('_getTemplate') def _getTemplate(self, template_name): templates = self.sql.getTemplate(sqv_name = template_name) @@ -1497,6 +1464,23 @@ return None + security.declarePublic('__bobo_traverse__') + def __bobo_traverse__(self, REQUEST=None, name=None): + """ Traversal hook to do http_ name mangling """ + + # First of all, try and mangle http_ in front of requests + ret = getattr(self, 'http_%s' % name, None) + if ret: return ret + + # Then try and obtain without mangling + ret = getattr(self, name, None) + if ret: return ret + else: + ret = self[name] + if ret: return ret + + return name + ######################################################################### # # Utility Functions Modified: MailManager/branches/RELENG_2_1/www/ticket_index_html.zpt =================================================================== --- MailManager/branches/RELENG_2_1/www/ticket_index_html.zpt 2006-04-03 10:44:16 UTC (rev 2884) +++ MailManager/branches/RELENG_2_1/www/ticket_index_html.zpt 2006-04-03 11:52:56 UTC (rev 2885) @@ -259,12 +259,9 @@ tal:define="template_name python:unicode(request.get('template_name', ''), 'utf-8'); template_html request/template_html | python:0; body_is_html request/body_is_html | python:0; - body request/body | string:; - tpl python:here.setBody(template_name, template_html, body_is_html, body); - body_is_html python:tpl[1]; - body python:tpl[2]" - method="post" - id="response" + body request/body | string:" + method="post" + id="response" enctype="multipart/form-data"> <h2 i18n:translate="response">Response</h2> @@ -284,15 +281,20 @@ <input type="checkbox" id="cc_all" tal:condition="all" tal:attributes="tabindex tabindex/next; value all; onclick string:document.response.cc.value='${all}'" /> <label for="cc_all" tal:attributes="class python:test(request.has_key('flag_cc_all'), 'error', None)" class="checkbox" tal:condition="all" i18n:translate="all">All</label> </p> + + <!-- Bcc --> <p> <label for="bcc" tal:attributes="class python:test(request.has_key('flag_bcc'), 'error', None)" i18n:translate="bcc_label">Bcc</label> <input size="40" name="bcc" id="bcc" tal:attributes="tabindex tabindex/next; value request/bcc | nothing" /> </p> + + <!-- Subject --> <p> <label for="subject" tal:attributes="class python:test(request.has_key('flag_subject'), 'error', None)" i18n:translate="subject_label">Subject</label> <input size="40" id="subject" name="subject" tal:attributes="tabindex tabindex/next; value request/subject | python:here.account(email=here.account_id)[0].getTicketReplySubject(here.id, here.subject)" /> </p> + <!-- Choose template --> <p tal:condition="agent"> <label for="template_name" tal:attributes="class python:test(request.has_key('flag_template_name'), 'error', None)" i18n:translate="template_label">Template</label> <select tal:attributes="tabindex tabindex/next" name="template_name" id="template_name"> @@ -300,18 +302,25 @@ <option value="cite_last" i18n:translate="cite_last">Cite last message</option> <option tal:repeat="tplt python:here.sql.listTemplates()" tal:content="tplt/name">Some other template.</option> </select> - <input tal:attributes="tabindex tabindex/next" type="image" class="gobutton" src="images/button_go_3.gif" alt="Go" /> + <input tal:attributes="tabindex tabindex/next" type="image" + class="gobutton" + name="setTemplate:method" + src="images/button_go_3.gif" alt="Go" /> </p> + <!-- Format --> <p> <label for="body_is_html:int" tal:attributes="class python:test(request.has_key('flag_body_is_html:int'), 'error', None)" i18n:translate="format_label">Format</label> - <select tal:define="html_required here/HTMLRequired" tal:attributes="tabindex tabindex/next" id="body_is_html:int" name="body_is_html:int"> + <select tal:define="html_required here/HTMLRequired" tal:attributes="tabindex tabindex/next" id="new_body_html:int" name="new_body_html:int"> <option value="0" tal:condition="not:html_required" tal:attributes="selected python:test(body_is_html, None, 1)" i18n:translate="plain_text">Plain text</option> <option value="1" tal:attributes="selected python:test(body_is_html, 1, None)" i18n:translate="html">HTML</option> </select> - <input tal:attributes="tabindex tabindex/next" type="image" class="gobutton" src="images/button_go_3.gif" alt="Go" /> + <input tal:attributes="tabindex tabindex/next" type="image" + name="setHTML:method" class="gobutton" + src="images/button_go_3.gif" alt="Go" /> </p> + <!-- Attachments --> <p> <label for="file_attach" tal:attributes="class python:test(request.has_key('flag_file_attach'), 'error', None)" i18n:translate="attach_label">Attach</label> <input tal:attributes="tabindex tabindex/next" id="file_attach" name="file_attach" type="file" /> This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ke...@us...> - 2006-04-10 15:41:28
|
Revision: 2905 Author: kevca Date: 2006-04-10 08:41:14 -0700 (Mon, 10 Apr 2006) ViewCVS: http://svn.sourceforge.net/mailmanager/?rev=2905&view=rev Log Message: ----------- - Non existant refresh value leads to security error (#1466274) Modified Paths: -------------- MailManager/branches/RELENG_2_1/CHANGES.txt MailManager/branches/RELENG_2_1/www/Tickets.zpt Modified: MailManager/branches/RELENG_2_1/CHANGES.txt =================================================================== --- MailManager/branches/RELENG_2_1/CHANGES.txt 2006-04-07 15:43:48 UTC (rev 2904) +++ MailManager/branches/RELENG_2_1/CHANGES.txt 2006-04-10 15:41:14 UTC (rev 2905) @@ -76,7 +76,20 @@ - Initial modularisation of the reporting engine. Version 2.0.8 (Unreleased) +* Unfactored from 2.0.8 +- Cannot create HTML tickets (#1466223) +- Archive and Restore functionality broken (#1452514) +- Unicode from addresses break incoming mail (#1440861) +- Email sent from Macs/Linux to Windows aren't formatted (#1440850) +- Valid tags are stripped from HTML on sending (#1460262) +- Encoding Error with signatures (#1440862) +- Installing test suite data fails (#1463368) +- Fixed issue with non-ascii filter condition (#1438720) +- Cite last includes only replies now (#1369571) +- Direct cycles of support_of are not allowed now. (#1353210) +- Test suite no longer generates testsuite.log unless set in config * BUG FIXES +- Non existant refresh value leads to security error (#1466274) - Changing format away from HTML generates a Zope error (#1460216) - Tickets without a subject no longer result in a permissions error (#1436863) - Test dataset has broken messages (#1422365) Modified: MailManager/branches/RELENG_2_1/www/Tickets.zpt =================================================================== --- MailManager/branches/RELENG_2_1/www/Tickets.zpt 2006-04-07 15:43:48 UTC (rev 2904) +++ MailManager/branches/RELENG_2_1/www/Tickets.zpt 2006-04-10 15:41:14 UTC (rev 2905) @@ -13,6 +13,7 @@ request.SESSION.get('search', {}), request.SESSION); sort_on svd/sort_on | string:id; sort_order svd/sort_order | string:desc; + refresh here/refresh | python:0; results python:here.listTickets(request); mycount python:here.sql.listTickets(sqv_assigned=user, sqv_state='Open'); num_tickets python:here.listTickets(request, count=True)[0].count; @@ -22,7 +23,7 @@ <title metal:fill-slot="title">Logicalware MailManager : Ticket List</title> <span metal:fill-slot="head"> <meta http-equiv="Refresh" - tal:condition="here/refresh | nothing" + tal:condition="refresh" tal:attributes="content python:'%d;; url=%s/Tickets' % (here.refresh * 60, here.absolute_url())" /> <script type="text/javascript" src="javascript_js"> </script> This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ke...@us...> - 2006-04-10 15:56:39
|
Revision: 2907 Author: kevca Date: 2006-04-10 08:56:22 -0700 (Mon, 10 Apr 2006) ViewCVS: http://svn.sourceforge.net/mailmanager/?rev=2907&view=rev Log Message: ----------- Adding in some files lost since the move to SF Modified Paths: -------------- MailManager/branches/RELENG_2_1/CHANGES.txt Added Paths: ----------- MailManager/branches/RELENG_2_1/ruleset/data/queuesystem_2_1_1.py MailManager/branches/RELENG_2_1/sql/v2_1/finalTables.zsql MailManager/branches/RELENG_2_1/sql/v2_1/migrateRulesetStates.zsql MailManager/branches/RELENG_2_1/tests/testUser.py Modified: MailManager/branches/RELENG_2_1/CHANGES.txt =================================================================== --- MailManager/branches/RELENG_2_1/CHANGES.txt 2006-04-10 15:51:19 UTC (rev 2906) +++ MailManager/branches/RELENG_2_1/CHANGES.txt 2006-04-10 15:56:22 UTC (rev 2907) @@ -89,6 +89,7 @@ - Direct cycles of support_of are not allowed now. (#1353210) - Test suite no longer generates testsuite.log unless set in config * BUG FIXES + - Non existant refresh value leads to security error (#1466274) - Changing format away from HTML generates a Zope error (#1460216) - Tickets without a subject no longer result in a permissions error (#1436863) Added: MailManager/branches/RELENG_2_1/ruleset/data/queuesystem_2_1_1.py =================================================================== --- MailManager/branches/RELENG_2_1/ruleset/data/queuesystem_2_1_1.py (rev 0) +++ MailManager/branches/RELENG_2_1/ruleset/data/queuesystem_2_1_1.py 2006-04-10 15:56:22 UTC (rev 2907) @@ -0,0 +1,418 @@ +# +# Ruleset description for mailmanager2.1 with the queueing support added. +# This ruleset is basically a clone of the ruleset adding a new state +# (Queued), and dealing with some new events +# + +try: + import ruleset +except ImportError: + from Products.MailManager import ruleset + +events = [] +states = [ + 'Received', + 'Open', + 'Closed', + 'Hold', + 'Spam', + 'Overdue', + 'Queued' +] + +############################################################################# +# +# User Interface Methods +# + +# +# Viewing a ticket +# + +event = ruleset.events.UserInterfaceEvent('ViewTicket') + +for state in states: + event.addTransition( + state = state, + attrs = ['!Viewed'], + actions = [ruleset.actions.SetAttribute('Viewed')] + ) +events.append(event) + +# +# Sending a reply +# + +event = ruleset.events.UserInterfaceEvent('Send') +event.addTransition( + state = 'Open', + perms = 'User', + actions = [ruleset.actions.SetAttribute('Responded')] +) +event.addTransition( + state = 'Closed', + perms = 'User', + actions = [ruleset.actions.SetAttribute('Responded')] +) +events.append(event) + +# +# Adding a note +# + +event = ruleset.events.UserInterfaceEvent('AddNote') +event.addTransition( + state = 'Open', + perms = 'User' +) +event.addTransition( + state = 'Closed', + perms = 'User' +) +events.append(event) + +# +# Sending a reply +# + +event = ruleset.events.UserInterfaceEvent('SendReply') +event.addTransition( + state = 'Open', + attrs = [], + newstate = 'Hold', + perms = 'User', + actions = [ruleset.actions.SetAttribute('Responded')] +) +event.addTransition( + state = 'Closed', + attrs = [], + newstate = 'Closed', + perms = 'User', + actions = [ruleset.actions.SetAttribute('Responded')] +) +event.addTransition( + state = 'Overdue', + attrs = [], + newstate = 'Open', + perms = 'User', + actions = [ruleset.actions.SetAttribute('Responded')] +) +event.addTransition( + state = 'Hold', + attrs = [], + newstate = 'Hold', + perms = 'User', + actions = [ruleset.actions.SetAttribute('Responded')] +) +events.append(event) + +# +# Send and close +# + +event = ruleset.events.UserInterfaceEvent('SendAndClose') +event.addTransition( + state = 'Open', + attrs = ['!OpenChildren'], + newstate = 'Closed', + perms = 'User', + actions = [ruleset.actions.SetAttribute('Responded')] +) +event.addTransition( + state = 'Closed', + attrs = ['!OpenChildren'], + newstate = 'Closed', + perms = 'User', + actions = [ruleset.actions.SetAttribute('Responded')] +) +event.addTransition( + state = 'Overdue', + attrs = ['!OpenChildren'], + newstate = 'Closed', + perms = 'User', + actions = [ruleset.actions.SetAttribute('Responded')] +) +events.append(event) + +# +# Send and hold +# + +event = ruleset.events.UserInterfaceEvent('SendAndHold') +event.addTransition( + state = 'Open', + attrs = [], + newstate = 'Hold', + perms = 'User', + actions = [ruleset.actions.SetAttribute('Responded')] +) +event.addTransition( + state = 'Closed', + attrs = [], + newstate = 'Hold', + perms = 'User', + actions = [ruleset.actions.SetAttribute('Responded')] +) +event.addTransition( + state = 'Overdue', + attrs = [], + newstate = 'Hold', + perms = 'User', + actions = [ruleset.actions.SetAttribute('Responded')] +) +events.append(event) + + +# +# Get next ticket from queue +# + +event = ruleset.events.UserInterfaceEvent('GetNextTicketFromQueue') +event.addTransition( + state = 'Queued', + attrs = [], + newstate = 'Open', + perms = 'Other', + actions = [ruleset.actions.AssignUser('_$Current')] +) +events.append(event) + + +# +# Return ticket to queue +# + +event = ruleset.events.UserInterfaceEvent('ReturnToQueue') +event.addTransition( + state = 'Open', + attrs = ['_AssignQueue'], + newstate = 'Queued', + perms = 'User', + actions = [ruleset.actions.AssignUser('$QueueUser')] +) +event.addTransition( + state = 'Hold', + attrs = ['_AssignQueue'], + newstate = 'Queued', + perms = 'User', + actions = [ruleset.actions.AssignUser('$QueueUser')] +) +event.addTransition( + state = 'Closed', + attrs = ['_AssignQueue'], + newstate = 'Queued', + perms = 'User', + actions = [ruleset.actions.AssignUser('$QueueUser')] +) +event.addTransition( + state = 'Overdue', + attrs = ['_AssignQueue'], + newstate = 'Queued', + perms = 'User', + actions = [ruleset.actions.AssignUser('$QueueUser')] +) +events.append(event) + + +# # +# # Update Ticket Details +# # +# +# event = ruleset.events.UserInterfaceModify('UpdateTicketDetails') +# event.addTransition( +# state = 'Any', +# newstate = 'Any', +# perms = 'User', +# actions = 'Any' +# ) +# +# event = ruleset.events.UserInterfaceModify('UpdateTicketBatch') +# event.addTransition( +# state = 'Any', +# newstate = 'Any', +# perms = 'User', +# actions = 'Any' +# ) +# +# # +# # Delete User And Reassign +# # +# +# event = ruleset.events.UserInterfaceEvent('DeleteUserAndReassign') +# event.addTransition( +# state = 'Any', +# perms = 'User', +# actions = [ReassignTicket()] +# ) +# +# # +# # Restore Tickets +# # + + +############################################################################# +# +# getMail methods +# + +event = ruleset.events.GetMailEvent('AppendToTicket') +event.addTransition( + state = 'Closed', + attrs = ['Responded'], + newstate = 'Open', + actions = [ruleset.actions.AlertOwner('Opened'), ruleset.actions.AlertGroup('Opened')] +) +event.addTransition( + state = 'Closed', + newstate = 'Open', + attrs = ['!Responded'], + actions = [ruleset.actions.AlertOwner('Opened'), ruleset.actions.AlertGroup('Opened')] +) +event.addTransition( + state = 'Open', + newstate = 'Open', +) +event.addTransition( + state = 'Spam', + newstate = 'Spam', +) +event.addTransition( + state = 'Overdue', + newstate = 'Overdue', +) +events.append(event) + + + +############################################################################# +# +# Time based methods +# + +event = ruleset.events.TimeEvent('Overdue') +event.addTransition( + state = 'Open', + attrs = ['!Responded'], + newstate = 'Overdue', + actions = [ruleset.actions.AlertOwner('Overdue'), ruleset.actions.AlertGroup('Overdue')] +) +events.append(event) + + +event = ruleset.events.TimeEvent('AutoLogout') +event.addTransition( + state = 'Open', + attrs = ['_AssignQueue'], + newstate = 'Queued', + perms = 'User', + actions = [ruleset.actions.AssignUser('$QueueUser')] +) +event.addTransition( + state = 'Hold', + attrs = ['_AssignQueue'], + newstate = 'Queued', + perms = 'User', + actions = [ruleset.actions.AssignUser('$QueueUser')] +) +event.addTransition( + state = 'Closed', + attrs = ['_AssignQueue'], + newstate = 'Queued', + perms = 'User', + actions = [ruleset.actions.AssignUser('$QueueUser')] +) +event.addTransition( + state = 'Overdue', + attrs = ['_AssignQueue'], + newstate = 'Queued', + perms = 'User', + actions = [ruleset.actions.AssignUser('$QueueUser')] +) +events.append(event) + + +############################################################################# +# +# Epsilon Transitions +# + +event = ruleset.events.EpsilonEvent('Epsilon') + +# Spam goes direct to the spam state +event.addTransition( + state = 'Received', + attrs = ['Spam'], + newstate = 'Spam', + actions = [] +) + +# Queue any tickets which are assigned to a queue +event.addTransition( + state = 'Received', + attrs = ['_AssignQueue'], + newstate = 'Queued', + actions = [ruleset.actions.AssignUser('$QueueUser')] +) + +# Tickets assigned to a user or a group should go straight to the Open state +# Don't reassign the ticket to any user, as the AccountPluggableBrain will +# currently do that from the assignment engine (this will be replaced at +# a later stage, merging the assignment engine with the queueing rules) +event.addTransition( + state = 'Received', + attrs = ['!_AssignQueue'], + newstate = 'Open', +) +events.append(event) + +# +# +############################################################################# + + +ruleset = { + 'eventnames' : [ + 'Epsilon', # Internal + 'Overdue', # Overdue Event + 'ViewTicket', # User Interface + 'Send', # User Interface + 'AddNote', # User Interface + 'SendReply', # User Interface + 'SendAndClose', # User Interface + 'SendAndHold', # User Interface + 'UpdateTicketDetails', # User Interface + 'UpdateTicketBatch', # User Interface + 'DeleteUserAndReassign', # User Interface + 'RestoreTickets', # User Interface + 'ReturnToQueue', # User Interface + 'GetNextTicketFromQueue', # User Interface + 'AppendToTicket', # Get Mail + 'AutoLogout', # Timed Event + ], + + 'actions' : [ + 'AlertOwner', + 'AlertGroup', + 'AlertSender', + 'SetAttribute', + 'UnsetAttribute', + 'ReassignTicket', + ], + + 'states' : states, + + 'attributes' : [ + 'OpenChildren', + 'Responded', + 'Viewed', + 'Spam' + ], + + 'categories' : [ + 'sales', + 'support' + ], + + 'events' : events +} + Added: MailManager/branches/RELENG_2_1/sql/v2_1/finalTables.zsql =================================================================== --- MailManager/branches/RELENG_2_1/sql/v2_1/finalTables.zsql (rev 0) +++ MailManager/branches/RELENG_2_1/sql/v2_1/finalTables.zsql 2006-04-10 15:56:22 UTC (rev 2907) @@ -0,0 +1,889 @@ +<dtml-comment> +title:Create Tables v2_1 +connection_id: mailmanager_db +max_rows:0 +arguments: + + This file decribes the initial database schema for MailManager version + v2_1_0. For the latest schema, please see latestTables.zsql, which is + the contents of this file with all of the migration changes applied. + + The migration scripts from v2.0 should result in this database schema + being reached. The migration scripts from v2.1 are then applied to bring + the schema up to date with the latest version. + +</dtml-comment> + + +<dtml-comment> + + Ruleset Table + + The following table lists and describes all of the known rulesets in + the system. This allows multiple rulesets to be loaded in the database. + +</dtml-comment> + + CREATE TABLE <dtml-var schema>mm_ruleset ( + name VARCHAR(20) PRIMARY KEY, + description <dtml-var sql_largetext> + ) <dtml-var sql_tabletype> <dtml-var sql_charset> <dtml-var sql_delimiter> + + +<dtml-comment> + + Ruleset States + + In mailmanager-2.0, states were fixed. In 2.1, arbitrary states can be + created for a ruleset. The following table allows these to be + specifically enumerated + +</dtml-comment> + + CREATE TABLE <dtml-var schema>mm_ruleset_states ( + rsname VARCHAR(20), + FOREIGN KEY (rsname) REFERENCES <dtml-var schema>mm_ruleset(name), + name VARCHAR(64), + description TEXT, + PRIMARY KEY (rsname, name) + ) <dtml-var sql_tabletype> <dtml-var sql_charset> <dtml-var sql_delimiter> + + + + + <dtml-comment> + + Users table + + This table stores all of the users who exist in MailManager. It ties into + Zope's security framework using the acl_users folder. We are also doing + some namespace mangling in here. Usernames which begin with _ are retricted + from end user modification. Users with these usernames will not normally be + displayed in the normal fashion. Current namespace allocations include the + following + + _Q... - Role users for the queueing system + + The virtual flag should be set for said users, although at present it is + not used in the code. This should be fixed on a later release as name + mangling is not ideal. + + </dtml-comment> + + CREATE TABLE <dtml-var schema>mm_user ( + username <dtml-var sql_varchar>(12) PRIMARY KEY, + real_name <dtml-var sql_smalltext>, + email <dtml-var sql_smalltext>, + signature <dtml-var sql_largetext>, + tickets <dtml-var sql_boolean> DEFAULT <dtml-var sql_false>, + reports <dtml-var sql_boolean> DEFAULT <dtml-var sql_false>, + queues <dtml-var sql_boolean> DEFAULT <dtml-var sql_false>, + virtual <dtml-var sql_boolean> DEFAULT <dtml-var sql_false>, + settings <dtml-var sql_boolean> DEFAULT <dtml-var sql_false>, + password <dtml-var sql_varchar>(42) + ) <dtml-var sql_tabletype> <dtml-var sql_charset> <dtml-var sql_delimiter> + + CREATE TABLE <dtml-var schema>mm_customer ( + username <dtml-var sql_varchar>(12) PRIMARY KEY, + real_name <dtml-var sql_smalltext>, + email <dtml-var sql_smalltext>, + signature <dtml-var sql_largetext>, + password <dtml-var sql_varchar>(42) + ) <dtml-var sql_tabletype> <dtml-var sql_charset> <dtml-var sql_delimiter> + + CREATE TABLE <dtml-var schema>mm_group ( + group_name <dtml-var sql_varchar>(128) PRIMARY KEY, + group_turn <dtml-var sql_integer> DEFAULT 0 + ) <dtml-var sql_tabletype> <dtml-var sql_charset> <dtml-var sql_delimiter> + + CREATE TABLE <dtml-var schema>mm_group_members ( + group_name <dtml-var sql_varchar>(128), + FOREIGN KEY (group_name) REFERENCES <dtml-var schema>mm_group(group_name) ON DELETE CASCADE <dtml-var sql_deferrable>, + username <dtml-var sql_varchar>(12), + FOREIGN KEY (username) REFERENCES <dtml-var schema>mm_user(username) <dtml-var sql_deferrable> + ) <dtml-var sql_tabletype> <dtml-var sql_charset> <dtml-var sql_delimiter> + + + + <dtml-comment> + + Queue Table + + Note that queue names can only be 10 chars as we are going to prefix the + usernames with _Q. The limit of the mm_user table is currently 12 characters. + Eventually this class will allow setting rules for queues by choosing a + queue class. For now, just the basics will suffice. + + </dtml-comment> + + CREATE TABLE <dtml-var schema>mm_queue ( + queue_name VARCHAR(10) PRIMARY KEY, + queue_user VARCHAR(12), + FOREIGN KEY (queue_user) REFERENCES <dtml-var schema>mm_user(username), + assign_group VARCHAR(12), + FOREIGN KEY (assign_group) REFERENCES <dtml-var schema>mm_group(group_name) + ) <dtml-var sql_tabletype> <dtml-var sql_charset> <dtml-var sql_delimiter> + + + + <dtml-comment> + + Accounts Table + + </dtml-comment> + + CREATE TABLE <dtml-var schema>mm_account ( + email <dtml-var sql_varchar>(255) PRIMARY KEY, + assign_user <dtml-var sql_varchar>(12), + FOREIGN KEY (assign_user) REFERENCES <dtml-var schema>mm_user(username) <dtml-var sql_deferrable>, + assign_group <dtml-var sql_varchar>(128), + FOREIGN KEY (assign_group) REFERENCES <dtml-var schema>mm_group(group_name) <dtml-var sql_deferrable>, + assign_queue <dtml-var sql_varchar>(10), + FOREIGN KEY (assign_queue) REFERENCES <dtml-var schema>mm_queue(queue_name) <dtml-var sql_deferrable>, + notify_user <dtml-var sql_boolean> DEFAULT <dtml-var sql_false>, + notify_group <dtml-var sql_boolean> DEFAULT <dtml-var sql_false>, + routing <dtml-var sql_varchar>(9) + <dtml-if sql_constraints> + CHECK (routing IN ('forwarded', 'get')) + </dtml-if sql_constraints> + , + mail_server <dtml-var sql_smalltext>, + mail_username <dtml-var sql_smalltext>, + mail_password <dtml-var sql_smalltext>, + server_type <dtml-var sql_varchar>(8) + <dtml-if sql_constraints> + CHECK (server_type IN ('POP3', 'IMAP', 'POP3 SSL', 'IMAP SSL')) + </dtml-if sql_constraints> + , + default_priority <dtml-var sql_integer>, + default_category0 <dtml-var sql_smalltext> NOT NULL DEFAULT '', + default_category1 <dtml-var sql_smalltext> NOT NULL DEFAULT '', + default_category2 <dtml-var sql_smalltext> NOT NULL DEFAULT '', + response_target <dtml-var sql_interval>, + auto_reply <dtml-var sql_boolean> DEFAULT <dtml-var sql_false>, + tid_subject <dtml-var sql_boolean> DEFAULT <dtml-var sql_true>, + reply_text <dtml-var sql_largetext>, + html_reply <dtml-var sql_boolean> DEFAULT <dtml-var sql_false>, + signature <dtml-var sql_boolean> DEFAULT <dtml-var sql_false>, + signature_text <dtml-var sql_largetext>, + html_signature <dtml-var sql_boolean> DEFAULT <dtml-var sql_false> + ) <dtml-var sql_tabletype> <dtml-var sql_charset> <dtml-var sql_delimiter> + + + <dtml-comment> + + Tickets table + + date_opened will normally be set to the time that the ticket is created in + the SQL database. It is not the time that the ticket was opened in the sense + that it was viewed and changed from the 'new' state. + +</dtml-comment> + + CREATE TABLE <dtml-var schema>mm_ticket ( + id <dtml-var sql_serialpkey>, + rsname VARCHAR(20), + FOREIGN KEY (rsname) REFERENCES <dtml-var schema>mm_ruleset(name), + subject <dtml-var sql_smalltext>, + from_name <dtml-var sql_smalltext>, + from_email <dtml-var sql_smalltext>, + account_id <dtml-var sql_varchar>(255), + FOREIGN KEY (account_id) REFERENCES <dtml-var schema>mm_account(email) <dtml-var sql_deferrable>, + assign_queue VARCHAR(10), + FOREIGN KEY (assign_queue) REFERENCES <dtml-var schema>mm_queue(queue_name) <dtml-var sql_deferrable>, + assigned <dtml-var sql_varchar>(12), + FOREIGN KEY (assigned) REFERENCES <dtml-var schema>mm_user(username) <dtml-var sql_deferrable>, + state <dtml-var sql_varchar>(64) + DEFAULT 'Open' + <dtml-if sql_constraints> + CHECK (state IN ('Open', 'Closed', 'Hold', 'Spam', 'Received', 'New', 'Overdue', 'Queued')) + </dtml-if sql_constraints> + , + priority <dtml-var sql_integer> + DEFAULT 3 + <dtml-if sql_constraints> + CHECK (priority >=1 AND priority <=5) + </dtml-if sql_constraints> + , + category0 <dtml-var sql_smalltext> NOT NULL DEFAULT '', + category1 <dtml-var sql_smalltext> NOT NULL DEFAULT '', + category2 <dtml-var sql_smalltext> NOT NULL DEFAULT '', + unread <dtml-var sql_boolean> DEFAULT <dtml-var sql_true>, + support_of <dtml-var sql_integer>, + FOREIGN KEY (support_of) + REFERENCES <dtml-var schema>mm_ticket(id) + <dtml-var sql_deferrable> + <dtml-var sql_initdefer>, + respond_by <dtml-var sql_datetimestamp>, + close_by <dtml-var sql_datetimestamp>, + date_opened <dtml-var sql_datetimestamp>, + date_responded <dtml-var sql_datetimestamp>, + date_closed <dtml-var sql_datetimestamp> + ) <dtml-var sql_tabletype> <dtml-var sql_charset> <dtml-var sql_delimiter> + + CREATE INDEX mm_ticket_state_idx + ON <dtml-var schema>mm_ticket(state) <dtml-var sql_delimiter> + + CREATE INDEX mm_ticket_account_id_idx + ON <dtml-var schema>mm_ticket(account_id) <dtml-var sql_delimiter> + + CREATE INDEX mm_ticket_assigned_idx + ON <dtml-var schema>mm_ticket(assigned) <dtml-var sql_delimiter> + + CREATE INDEX mm_ticket_priority_idx + ON <dtml-var schema>mm_ticket(priority) <dtml-var sql_delimiter> + + CREATE INDEX mm_ticket_from_email_idx + ON <dtml-var schema>mm_ticket(from_email <dtml-var sql_indexlimit>) <dtml-var sql_delimiter> + + CREATE INDEX mm_ticket_respond_by_idx + ON <dtml-var schema>mm_ticket(respond_by) <dtml-var sql_delimiter> + + CREATE INDEX mm_ticket_date_responded_idx + ON <dtml-var schema>mm_ticket(date_responded) <dtml-var sql_delimiter> + + <dtml-if expr="sql_database == 'oracle'"> + CREATE SEQUENCE <dtml-var schema>mm_ticket_id_seq <dtml-var sql_delimiter> + </dtml-if> + + +<dtml-comment> + + Messages table + +</dtml-comment> + + CREATE TABLE <dtml-var schema>mm_message ( + id <dtml-var sql_serialpkey>, + ticket_id <dtml-var sql_integer>, + FOREIGN KEY (ticket_id) REFERENCES <dtml-var schema>mm_ticket(id) + ON DELETE CASCADE <dtml-var sql_deferrable>, + message_id <dtml-var sql_smalltext>, + from_name <dtml-var sql_smalltext>, + from_email <dtml-var sql_smalltext>, + subject <dtml-var sql_smalltext>, + msg_date <dtml-var sql_datetimestamp>, + msg_to <dtml-var sql_smalltext>, + cc <dtml-var sql_smalltext>, + bcc <dtml-var sql_smalltext>, + reply_to <dtml-var sql_smalltext>, + raw_headers <dtml-var sql_largetext>, + body <dtml-var sql_largetext>, + html_body <dtml-var sql_largetext> + ) <dtml-var sql_tabletype> <dtml-var sql_charset> <dtml-var sql_delimiter> + + CREATE INDEX mm_message_ticket_id_idx + ON <dtml-var schema>mm_message(ticket_id) <dtml-var sql_delimiter> + + CREATE INDEX mm_message_message_id_idx + ON <dtml-var schema>mm_message(message_id <dtml-var sql_indexlimit>) <dtml-var sql_delimiter> + + <dtml-if expr="sql_database == 'oracle'"> + CREATE SEQUENCE <dtml-var schema>mm_message_id_seq <dtml-var sql_delimiter> + </dtml-if> + +<dtml-comment> + + Attachments Table + +</dtml-comment> + + CREATE TABLE <dtml-var schema>mm_attachment ( + id <dtml-var sql_serialpkey>, + message_id <dtml-var sql_integer>, + FOREIGN KEY (message_id) REFERENCES <dtml-var schema>mm_message(id) + ON DELETE CASCADE + <dtml-var sql_deferrable> + <dtml-var sql_initdefer>, + title <dtml-var sql_largetext>, + content_type <dtml-var sql_smalltext>, + is_file <dtml-var sql_boolean> DEFAULT <dtml-var sql_false>, + body <dtml-var sql_binary>, + dangerous <dtml-var sql_boolean> DEFAULT <dtml-var sql_false> + ) <dtml-var sql_tabletype> <dtml-var sql_charset> <dtml-var sql_delimiter> + + CREATE INDEX mm_attachment_message_id_idx + ON <dtml-var schema>mm_attachment(message_id) <dtml-var sql_delimiter> + + <dtml-if expr="sql_database == 'oracle'"> + CREATE SEQUENCE <dtml-var schema>mm_attachment_id_seq <dtml-var sql_delimiter> + </dtml-if> + +<dtml-comment> + + Ruleset Attributes + + Attributes encompass categories and priorities from the 2.0 MailManager + design, as well as adding in dynamically calculated attributes giving + additional information about tickets. Currently only the 'openSiblings' + attribute is dynamically calculated. All attributes which can be used + in the ruleset must be enumerated in the mm_ruleset_attributes table as + R.I will be used by anything referring to attributes. + + Dynamic attributes also need to be included in this table. + + mm_ticket_attributes maps attributes to tickets in a n-1 relationship. + +</dtml-comment> + + CREATE TABLE <dtml-var schema>mm_ruleset_attributes ( + rsname VARCHAR(20), + FOREIGN KEY (rsname) REFERENCES <dtml-var schema>mm_ruleset(name), + attribute VARCHAR(64), + PRIMARY KEY (rsname, attribute) + ) <dtml-var sql_tabletype> <dtml-var sql_charset> <dtml-var sql_delimiter> + + CREATE TABLE <dtml-var schema>mm_ticket_attributes ( + rsname VARCHAR(20), + FOREIGN KEY (rsname) REFERENCES <dtml-var schema>mm_ruleset(name), + ticket_id INT, + FOREIGN KEY (ticket_id) REFERENCES <dtml-var schema>mm_ticket(id), + attribute VARCHAR(64), + FOREIGN KEY (rsname, attribute) REFERENCES <dtml-var schema>mm_ruleset_attributes(rsname, attribute) + ) <dtml-var sql_tabletype> <dtml-var sql_charset> <dtml-var sql_delimiter> + + +<dtml-comment> + + Ruleset Events + + This table enumerates all of the events defined in the ruleset + +</dtml-comment> + + CREATE TABLE <dtml-var schema>mm_ruleset_events ( + rsname VARCHAR(20), + FOREIGN KEY (rsname) REFERENCES <dtml-var schema>mm_ruleset(name), + name VARCHAR(64), + PRIMARY KEY (rsname, name) + ) <dtml-var sql_tabletype> <dtml-var sql_charset> <dtml-var sql_delimiter> + + + +<dtml-comment> + + Ruleset Event Queue + + The event queue schedules TimeEvents which will be called in the future. + These are given a timestamp and a ticket id, so the queue can be quickly + scanned for events which are due and the ticket which they apply to can + be found + + The following constraint is ommitted, as rsname would need to be obtained + from the ticket table. For performance reasons, this is omitted just now + and it shall just be taken that the event queue management code is + correct. + + FOREIGN KEY (rsname, event) + REFERENCES <dtml-var schema>mm_ruleset_events(rsname, name), + +</dtml-comment> + + CREATE TABLE <dtml-var schema>mm_ruleset_event_queue ( + ticket_id INT, + FOREIGN KEY (ticket_id) REFERENCES <dtml-var schema>mm_ticket(id), + event VARCHAR(64), + triggertime <dtml-var sql_datetimestamp> + ) <dtml-var sql_tabletype> <dtml-var sql_charset> <dtml-var sql_delimiter> + + CREATE INDEX mm_ruleset_event_queue_ticket_id_idx + ON <dtml-var schema>mm_ruleset_event_queue(ticket_id) <dtml-var sql_delimiter> + + +<dtml-comment> + + Ruleset Transitions + + The following tables allow the definition on an event in the database + An event represents a user action, an action which occurs at a specific + due to a change in overdue status, or external activity operating on the + the system - incoming mail. + + name : an identifier for the event + perms : who is allowed to trigger this event. Can be restricted to the + standard UGO permissions model: user, group, other + newstate : the state the ticket will take once the event has been + processed. If NULL, then any state may be possible, depending + on some other factors + + N-1 relationships + + actions : actions which are taken whenever the event takes place. These + can perform operations such as alerting end users of updates + or modifying attributes on a ticket + attributes : attributes a ticket must have/must not have in order for the + event to be allowed to take place + + + Note: For MySQL support, the following table has its pkey replaced by a + unique constraint. This is non-ideal, and may need some improvements + for performance later. MySQL does not support sequences/auto_increment + on a row unless that row is the primary key (composite keys are not + allowed when using InnoDB tables). + +</dtml-comment> + + CREATE TABLE <dtml-var schema>mm_ruleset_transitions ( + rsname VARCHAR(20), + FOREIGN KEY (rsname) REFERENCES <dtml-var schema>mm_ruleset(name), + event_name VARCHAR(255), + FOREIGN KEY (rsname, event_name) REFERENCES <dtml-var schema>mm_ruleset_events(rsname, name), + transition_id <dtml-var sql_serialpkey>, + state VARCHAR(255), + perms VARCHAR(255) CHECK (perms IN ('User', 'Group', 'Other')), + newstate VARCHAR(255), + UNIQUE(rsname, transition_id) + ) <dtml-var sql_tabletype> <dtml-var sql_charset> <dtml-var sql_delimiter> + + <dtml-comment> Attributes which are required for the event to be possible </dtml-comment> + + CREATE TABLE <dtml-var schema>mm_ruleset_transition_attributes ( + rsname VARCHAR(20), + FOREIGN KEY (rsname) REFERENCES <dtml-var schema>mm_ruleset(name), + transition_id INT, + FOREIGN KEY (rsname, transition_id) REFERENCES <dtml-var schema>mm_ruleset_transitions(rsname, transition_id), + attribute VARCHAR(64), + FOREIGN KEY (rsname, attribute) REFERENCES <dtml-var schema>mm_ruleset_attributes(rsname, attribute), + status <dtml-var sql_boolean> + ) <dtml-var sql_tabletype> <dtml-var sql_charset> <dtml-var sql_delimiter> + + CREATE TABLE <dtml-var schema>mm_ruleset_transition_actions ( + rsname VARCHAR(20), + FOREIGN KEY (rsname) REFERENCES <dtml-var schema>mm_ruleset(name), + transition_id INT, + FOREIGN KEY (rsname, transition_id) REFERENCES <dtml-var schema>mm_ruleset_transitions(rsname, transition_id), + action VARCHAR(255), + params VARCHAR(255) + ) <dtml-var sql_tabletype> <dtml-var sql_charset> <dtml-var sql_delimiter> + + +<dtml-comment> + + History Table + + We are now adding in some activity logging into the event queue. For now + this is purely used for development purposes and we are not treating any + of this as authoritative. At some point, however, it will be used to + better present the history information at the side of the screen, and + improve the information present in reports. + + We also avoid the constraints in this table, as again rsname is normally + obtained form the ticket table + + ALTER TABLE <dtml-var schema>mm_history + ADD FOREIGN KEY (event) + REFERENCES <dtml-var schema>mm_ruleset_events(name) <dtml-var sql_deferrable> + ALTER TABLE <dtml-var schema>mm_history + ADD FOREIGN KEY (transition) + REFERENCES <dtml-var schema>mm_ruleset_transitions(transition_id) <dtml-var sql_deferrable> + +</dtml-comment> + + CREATE TABLE <dtml-var schema>mm_history ( + id <dtml-var sql_serialpkey>, + event VARCHAR(64), + transition <dtml-var sql_integer>, + ticket_id <dtml-var sql_integer>, + FOREIGN KEY (ticket_id) REFERENCES <dtml-var schema>mm_ticket(id) + ON DELETE CASCADE <dtml-var sql_deferrable>, + subject <dtml-var sql_smalltext>, + assigned <dtml-var sql_varchar>(12), + state <dtml-var sql_varchar>(64) + <dtml-if sql_constraints> + CHECK (state IN ('Open', 'Closed', 'Hold', 'Spam', 'Received', 'New', 'Overdue', 'Queued')) + </dtml-if sql_constraints> + , + priority <dtml-var sql_integer>, + support_of <dtml-var sql_integer>, + category0 <dtml-var sql_smalltext>, + category1 <dtml-var sql_smalltext>, + category2 <dtml-var sql_smalltext>, + changed_by <dtml-var sql_varchar>(12), + change_date <dtml-var sql_datetimestamp> + ) <dtml-var sql_tabletype> <dtml-var sql_charset> <dtml-var sql_delimiter> + + CREATE INDEX mm_history_ticket_id_idx + ON <dtml-var schema>mm_history(ticket_id) <dtml-var sql_delimiter> + + CREATE INDEX mm_history_state_idx + ON <dtml-var schema>mm_history(state) <dtml-var sql_delimiter> + + <dtml-if expr="sql_database == 'oracle'"> + CREATE SEQUENCE <dtml-var schema>mm_history_id_seq <dtml-var sql_delimiter> + </dtml-if> + + +<dtml-comment> + + Filter Table + + The filters table describes a list of filters composed of match + conditions and actions, which are applied to incoming messages. Each + incoming ticket is compared to the match conditions, and should it + match the described action is applied. + +</dtml-comment> + + CREATE TABLE <dtml-var schema>mm_filter ( + id <dtml-var sql_serialpkey>, + header_name <dtml-var sql_smalltext>, + header_operator <dtml-var sql_varchar>(15) + <dtml-if sql_constraints> + CHECK (header_operator IN ('exactly matches', 'contains')) + </dtml-if sql_constraints> , + header_value <dtml-var sql_smalltext>, + state <dtml-var sql_varchar>(64) + <dtml-if sql_constraints> + CHECK (state IN ('Open', 'Closed', 'Hold', 'Spam', 'Received', 'New', 'Overdue', 'Queued')) + </dtml-if sql_constraints> , + assign_user <dtml-var sql_varchar>(12), + FOREIGN KEY (assign_user) + REFERENCES <dtml-var schema>mm_user(username) <dtml-var sql_deferrable>, + assign_group <dtml-var sql_varchar>(128), + FOREIGN KEY (assign_group) + REFERENCES <dtml-var schema>mm_group(group_name) <dtml-var sql_deferrable>, + assign_queue <dtml-var sql_varchar>(10), + FOREIGN KEY (assign_queue) + REFERENCES <dtml-var schema>mm_queue(queue_name) <dtml-var sql_deferrable>, + priority <dtml-var sql_integer>, + category0 <dtml-var sql_smalltext> NOT NULL DEFAULT '', + category1 <dtml-var sql_smalltext> NOT NULL DEFAULT '', + category2 <dtml-var sql_smalltext> NOT NULL DEFAULT '', + response_target <dtml-var sql_interval> + ) <dtml-var sql_tabletype> <dtml-var sql_charset> <dtml-var sql_delimiter> + + CREATE INDEX mm_filter_state_idx ON + <dtml-var schema>mm_filter(state) + <dtml-var sql_delimiter> + + <dtml-if expr="sql_database == 'oracle'"> + CREATE SEQUENCE <dtml-var schema>mm_filter_id_seq <dtml-var sql_delimiter> + </dtml-if> + + +<dtml-comment> + + Customer Email + + This table stores the email addresses which a given customer is allowed to + view tickets from. This provides some form of an ACL to restrict customers + to viewing their own tickets. + +</dtml-comment> + + CREATE TABLE <dtml-var schema>mm_customer_email ( + username <dtml-var sql_varchar>(12), + FOREIGN KEY (username) + REFERENCES <dtml-var schema>mm_customer(username) ON DELETE CASCADE <dtml-var sql_deferrable>, + access_email <dtml-var sql_smalltext> + ) <dtml-var sql_tabletype> <dtml-var sql_charset> <dtml-var sql_delimiter> + + + +<dtml-comment> + + The following 2 tables are there in order to replace the functionality of + MailManager 2.0's categories. Eventually these will be restructured somewhat + in order to allow an arbitrary number of categories. This should involve + little more than removing the category id, and replacing the primary key + with the category label, although the UI pages will need altered throughout. + + mm_category_choices + + replaces self.category_choices in MailManager 2.0 + + mm_category_labels + + replaces self.category_labels and self.category_on in MailManager 2.0 + +</dtml-comment> + + CREATE TABLE <dtml-var schema>mm_categories ( + id <dtml-var sql_integer> PRIMARY KEY + <dtml-if sql_constraints> + CHECK (id IN (0,1,2)) + </dtml-if sql_constraints>, + label <dtml-var sql_smalltext>, + enabled <dtml-var sql_boolean> + ) <dtml-var sql_tabletype> <dtml-var sql_charset> <dtml-var sql_delimiter> + + CREATE TABLE <dtml-var schema>mm_category_choices ( + category_id <dtml-var sql_integer>, + FOREIGN KEY(category_id) REFERENCES <dtml-var schema>mm_categories(id), + choice <dtml-var sql_smalltext> NOT NULL, + <dtml-if expr="sql_database == 'postgres'"> + PRIMARY KEY(category_id, choice) + <dtml-elif expr="sql_database == 'oracle'"> + PRIMARY KEY(category_id, choice) + <dtml-elif expr="sql_database == 'mysql'"> + PRIMARY KEY(category_id, choice(255)) + </dtml-if> + ) <dtml-var sql_tabletype> <dtml-var sql_charset> <dtml-var sql_delimiter> + + +<dtml-comment> + + Templates + + This table stores reply templates, with the primary key being the + name given to the template. Either one of body or html_body should be + set to NULL in order to select what type of reply to generate. + + replaces self.reply_templates in MailManager 2.0 + +</dtml-comment> + + CREATE TABLE <dtml-var schema>mm_templates ( + name <dtml-var sql_smalltext>, + body <dtml-var sql_largetext> NOT NULL, + html <dtml-var sql_boolean> DEFAULT <dtml-var sql_false>, + <dtml-if expr="sql_database == 'postgres'"> + PRIMARY KEY(name) + <dtml-elif expr="sql_database == 'oracle'"> + PRIMARY KEY(name) + <dtml-elif expr="sql_database == 'mysql'"> + PRIMARY KEY(name(255)) + </dtml-if> + ) <dtml-var sql_tabletype> <dtml-var sql_charset> <dtml-var sql_delimiter> + + + + +<dtml-comment> + + User Abilities + +</dtml-comment> + + CREATE TABLE <dtml-var schema>mm_user_abilities_category0( + username TEXT REFERENCES <dtml-var schema>mm_user, + category0 TEXT + ) + <dtml-var sql_tabletype> <dtml-var sql_charset> <dtml-var sql_delimiter> + + CREATE TABLE <dtml-var schema>mm_user_abilities_category1( + username TEXT REFERENCES <dtml-var schema>mm_user, + category1 TEXT + ) + <dtml-var sql_tabletype> <dtml-var sql_charset> <dtml-var sql_delimiter> + + CREATE TABLE <dtml-var schema>mm_user_abilities_category2( + username TEXT REFERENCES <dtml-var schema>mm_user, + category2 TEXT + ) + <dtml-var sql_tabletype> <dtml-var sql_charset> <dtml-var sql_delimiter> + + +<dtml-comment> + + Navigation Tabs + +</dtml-comment> + + CREATE TABLE <dtml-var schema>mm_nav_tabs ( + id <dtml-var sql_varchar>(20) PRIMARY KEY, + title <dtml-var sql_varchar>(20), -- Arbitrary value, but much more will make the UI look ugly! + tooltip <dtml-var sql_smalltext>, + url <dtml-var sql_smalltext>, + accesskey <dtml-var sql_varchar>(2), + roles <dtml-var sql_smalltext> + ) <dtml-var sql_tabletype> <dtml-var sql_charset> <dtml-var sql_delimiter> + + CREATE TABLE <dtml-var schema>mm_nav_subtabs ( + id <dtml-var sql_varchar>(20), + title <dtml-var sql_varchar>(20), -- Arbitrary, for the same reasons. + tab_id <dtml-var sql_varchar>(20), + tooltip <dtml-var sql_smalltext>, + url <dtml-var sql_smalltext>, + accesskey <dtml-var sql_varchar>(2), + roles <dtml-var sql_smalltext> + ) <dtml-var sql_tabletype> <dtml-var sql_charset> <dtml-var sql_delimiter> + + +<dtml-comment> + + User Sessions + + The following tables are used to store session information used by + support/session.py in order to persist user login information. + +</dtml-comment> + + CREATE TABLE <dtml-var schema>mm_session ( + session_id VARCHAR(64) PRIMARY KEY, + accessed <dtml-var sql_datetimestamp>, + authinfo <dtml-var sql_smalltext> + ) <dtml-var sql_tabletype> <dtml-var sql_charset> <dtml-var sql_delimiter> + + +<dtml-comment> + + Text Search + + The following adds on text search, which is highly specific to the target + database platform. For some databases, this will involve adding functions + and triggers in order to automatically maintain the consistency of search + indexes. + +</dtml-comment> + + <dtml-if sql_textsearch> + + <dtml-if expr="sql_database == 'postgres'"> + + <dtml-comment> + + Create Functionality + + Aggregation function (used internally) + + This function combines the tsvector values of the messages in a ticket, + gives back a tsvector which can be used to search all of the messages at + once. The resulting tsvector preserves the releative distances between + search terms, so the the scoring of results is not changed. + + Alternatively, we could use postgresql's CREATE AGGREGATE here, which may + improve both the performance and the semantics of the database. For now, + this shall suffice. + + Function: ts_aggregate(ticket_id integer) + Description: Returns the aggregated message indicies from all of the + messages under the ticket with the given id + + </dtml-comment> + + CREATE OR REPLACE FUNCTION <dtml-var schema>ts_aggregate(integer) RETURNS tsvector AS ' + DECLARE + ticket_id ALIAS FOR $1; + summation TSVECTOR := ''''::tsvector; + msg RECORD; + BEGIN + FOR msg IN SELECT * FROM <dtml-var schema>mm_message WHERE <dtml-var schema>mm_message.ticket_id = ticket_id LOOP + summation = summation || msg.idxFTI; + END LOOP; + + RETURN summation; + END; + ' LANGUAGE plpgsql <dtml-var sql_delimiter> + + <dtml-comment> + + Trigger function up maintaining the search indicies on mm_ticket + + This trigger gets called whenever a new mm_message inserted, updated or + deleted. It will update the text index of the ticket which holds the + given message. Each update involves aggregating the indicies of all of + the child messages of a ticket, which is not a particularly fast operation. + Some improvements could be made to this if really required, but in practice + if the message count is small these are not worth doing. For tickets with + large numbers of messages, it may be possible to do some kind of incremental + update on the ticket index, and incremental updates of the index in the + case of removal of messages would be problematic. + + The trigger function is mainly a wrapper to the standard version of the + function, to avoid code duplication. + + Function: mm_ticket_update_idxFTI(ticket_id integer) + Description: Updates the text index on the ticket with the given id by + combining the text indices of all the messages under the ticket. + + </dtml-comment> + + CREATE OR REPLACE FUNCTION <dtml-var schema>mm_ticket_update_idxFTI(integer) RETURNS void AS ' + DECLARE + ticket_id ALIAS FOR $1; + BEGIN + UPDATE <dtml-var schema>mm_ticket SET idxFTI = <dtml-var schema>ts_aggregate(ticket_id) WHERE + mm_ticket.id = ticket_id; + RETURN; + END + ' LANGUAGE plpgsql <dtml-var sql_delimiter> + + CREATE OR REPLACE FUNCTION <dtml-var schema>mm_ticket_update_idxFTI() RETURNS trigger AS ' + BEGIN + IF (TG_OP = \'DELETE\') THEN + PERFORM <dtml-var schema>mm_ticket_update_idxFTI(OLD.ticket_id); + RETURN NULL; + ELSIF (TG_OP = \'UPDATE\') THEN + PERFORM <dtml-var schema>mm_ticket_update_idxFTI(NEW.ticket_id); + RETURN NULL; + ELSIF (TG_OP = \'INSERT\') THEN + PERFORM <dtml-var schema>mm_ticket_update_idxFTI(NEW.ticket_id); + RETURN NULL; + END IF; + END + ' LANGUAGE plpgsql <dtml-var sql_delimiter> + + <dtml-comment> + + Update Existing Data + + This should be altered so that we are only defining the schema + here. This script should only be creating the initial tables + which are not populated with any data. + + </dtml-comment> + + ALTER TABLE <dtml-var schema>mm_message ADD COLUMN idxFTI tsvector <dtml-var sql_delimiter> + ALTER TABLE <dtml-var schema>mm_ticket ADD COLUMN idxFTI tsvector <dtml-var sql_delimiter> + + <dtml-comment> Update the indices on the messages </dtml-comment> + UPDATE <dtml-var schema>mm_message SET idxFTI=to_tsvector( + 'default', + coalesce(subject,'') ||' '|| coalesce(body,'') ||' '|| coalesce(html_body,'') + ) <dtml-var sql_delimiter> + + <dtml-comment> Update the indices on the tickets </dtml-comment> + SELECT COUNT(<dtml-var schema>mm_ticket_update_idxFTI(id)) FROM <dtml-var schema>mm_ticket <dtml-var sql_delimiter> + + CREATE INDEX mm_message_idxFTI_idx + ON <dtml-var schema>mm_message USING gist(idxFTI) + <dtml-var sql_delimiter> + + CREATE INDEX mm_ticket_idxFTI_idx + ON <dtml-var schema>mm_ticket USING gist(idxFTI) + <dtml-var sql_delimiter> + + <dtml-comment> + + Maintain Future Data + + These two triggers must be ordered, which is done alphabetically + in postgresql. + + </dtml-comment> + + CREATE TRIGGER mm_message_trigA_message_searchupdate + BEFORE UPDATE OR INSERT ON <dtml-var schema>mm_message + FOR EACH ROW EXECUTE PROCEDURE tsearch2(idxFTI, subject, body, html_body) + <dtml-var sql_delimiter> + + CREATE TRIGGER mm_message_trigB_ticket_searchupdate + AFTER UPDATE OR INSERT OR DELETE ON <dtml-var schema>mm_message + FOR EACH ROW EXECUTE PROCEDURE <dtml-var schema>mm_ticket_update_idxFTI() + <dtml-var sql_delimiter> + + <dtml-elif expr="sql_database == 'mysql'"> + + CREATE TABLE mm_message_index ( + id INT, + FOREIGN KEY (id) REFERENCES <dtml-var schema>mm_message(id), + ticket_id INT, + FOREIGN KEY (ticket_id) REFERENCES <dtml-var schema>mm_ticket(id), + body TEXT, + html_body TEXT, + FULLTEXT (body, html_body) + ) <dtml-var sql_charset> <dtml-var sql_delimiter> + + CREATE TABLE mm_ticket_index ( + id INT, + FOREIGN KEY (id) REFERENCES <dtml-var schema>mm_ticket(id), + body TEXT, + html_body TEXT, + FULLTEXT (body, html_body) + ) <dtml-var sql_charset> <dtml-var sql_delimiter> + + </dtml-if> + + </dtml-if> + Added: MailManager/branches/RELENG_2_1/sql/v2_1/migrateRulesetStates.zsql =================================================================== --- MailManager/branches/RELENG_2_1/sql/v2_1/migrateRulesetStates.zsql (rev 0) +++ MailManager/branches/RELENG_2_1/sql/v2_1/migrateRulesetStates.zsql 2006-04-10 15:56:22 UTC (rev 2907) @@ -0,0 +1,36 @@ +<dtml-comment> +title:Migrate ruleset states (v2_1_1 -> v2_1_2) +connection_id:mailmanager_db +arguments: + +This is a migration script from 2_1_1 to 2_1_2 which alters ruleset_states +to have a composite key. Previously this key has been only on the ruleset +state itself, not combined with the ruleset name, so multiple ruleset +could not share state names. + +</dtml-comment> + + +<dtml-if expr="sql_database == 'mysql'"> + + ALTER TABLE <dtml-var schema>mm_ruleset_states + DROP PRIMARY KEY + <dtml-var sql_delimiter> + +<dtml-else> + + ALTER TABLE <dtml-var schema>mm_ruleset_states + DROP CONSTRAINT mm_ruleset_states_pkey + <dtml-var sql_delimiter> + +</dtml-if> + + +UPDATE <dtml-var schema>mm_ruleset_states + SET rsname = 'queuesystem' +<dtml-var sql_delimiter> + +ALTER TABLE <dtml-var schema>mm_ruleset_states + ADD PRIMARY KEY (rsname, name) +<dtml-var sql_delimiter> + Added: MailManager/branches/RELENG_2_1/tests/testUser.py =================================================================== --- MailManager/branches/RELENG_2_1/tests/testUser.py (rev 0) +++ MailManager/branches/RELENG_2_1/tests/testUser.py 2006-04-10 15:56:22 UTC (rev 2907) @@ -0,0 +1,127 @@ + +# (c) Copyright Logicalware Ltd 2002-2006 +# Logicalware MailManager comes with ABSOLUTELY NO WARRANTY. Further details +# including conditions of redistribution are contained in LICENSE.txt +# http://www.logicalware.org/ +# $Id: testUser.py,v 1.1.2.1 2006/03/28 13:27:14 kevca Exp $ +# +# Test Cross Site Scripting +# +# This method attempts to send embedded HTML to the display through a variety +# of inputs. None of these should result in code making it through to affect +# the end user. We test directly from data held in the database, as we should +# not be relying on sanitation of incoming input. +# + +import os, sys +if __name__ == '__main__': + execfile(os.path.join(sys.path[0], 'framework.py')) + +from Testing import ZopeTestCase +ZopeTestCase.installProduct('MailManager') + +import random +import time +import re +import shutil +import DocumentTemplate +import DateTime + +import testDatabase + +from Shared.DC.ZRDB import sqlvar, sqltest, sqlgroup, Results +from Products.MailManager.tests.classes import testStructures +from Products.MailManager.tests.classes import registry +from Products.MailManager.support.i18n import convertString + +try: + import _mysql + import MySQLdb + from Products import ZMySQLDA +except ImportError, e: + pass + +try: + import psycopg + import pg + from Products import ZPsycopgDA +except ImportError, e: + pass + +import Globals + +from Testing import makerequest +from Testing.ZopeTestCase import utils +from Products import PlacelessTranslationService + +import ZODB, ZODB.FileStorage + +from Products.MailManager.tests.classes.testStructures import pop3control, imapcontrol + +import pprint + + +class userTest(testStructures.mailManagerTestCase): + """ + Test cross site scripting + """ + + def userTests(self): + self.abilitiesTest() + + def abilitiesTest(self): + """ Checks the user abilities are consistently handled when + creating/deleting users """ + + self.mmobj.addOrEditMMUser(username='jacques', + real_name='Jacques Quint', + email='jacques@test.example', + password='xyz', + abilities0=['Customer query', 'Sales lead', 'Technical Issue', 'Other'], + tickets=self.mmobj._sql_bool(True), + reports=self.mmobj._sql_bool(True), + settings=self.mmobj._sql_bool(True)) + + + + # Check the user has abilities set + res = self.mmobj.sql.listUserAbilities(sqv_username='jacques') + actual = [r.category0 for r in res] + expected = ['Customer query', 'Sales lead', 'Technical Issue', 'Other'] + + # Compare lists irrespective of ordering + for c in actual: + self.failUnless(c in expected) + for c in expected: + self.failUnless(c in actual) + + # Remove the user, check that the abilities are automatically + # removed, and that we aren't failing due to constraints + self.mmobj.delMMUser(username='jacques', reassign='sally') + + +class TestUser(userTest): + + def testUser(self): + self.dbplatform = self.config['databases']['primary_database'] + + self.setupMailManager(dbplatform = self.dbplatform, debugmode=True) + + self.mmobj.populateAccounts('acmestatic') + self.mmobj.populateTickets('acmestatic') + + self.userTests() + + + ########################################################################### + +def test_suite(): + from unittest import TestSuite, makeSuite + suite = TestSuite() + suite.addTest(makeSuite(TestUser)) + return suite + +if __name__ == '__main__': + framework() + + This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ke...@us...> - 2006-04-10 16:09:09
|
Revision: 2908 Author: kevca Date: 2006-04-10 09:08:58 -0700 (Mon, 10 Apr 2006) ViewCVS: http://svn.sourceforge.net/mailmanager/?rev=2908&view=rev Log Message: ----------- Part of the following in Extensions/TicketPluggableBrain.py - Archive and Restore functionality broken (#1452514) Rest is related to closing - Encoding Error with signatures (#1440862) Modified Paths: -------------- MailManager/branches/RELENG_2_1/CHANGES.txt MailManager/branches/RELENG_2_1/Extensions/TicketPluggableBrain.py MailManager/branches/RELENG_2_1/MailMixin.py Modified: MailManager/branches/RELENG_2_1/CHANGES.txt =================================================================== --- MailManager/branches/RELENG_2_1/CHANGES.txt 2006-04-10 15:56:22 UTC (rev 2907) +++ MailManager/branches/RELENG_2_1/CHANGES.txt 2006-04-10 16:08:58 UTC (rev 2908) @@ -82,20 +82,20 @@ - Unicode from addresses break incoming mail (#1440861) - Email sent from Macs/Linux to Windows aren't formatted (#1440850) - Valid tags are stripped from HTML on sending (#1460262) -- Encoding Error with signatures (#1440862) - Installing test suite data fails (#1463368) - Fixed issue with non-ascii filter condition (#1438720) - Cite last includes only replies now (#1369571) - Direct cycles of support_of are not allowed now. (#1353210) - Test suite no longer generates testsuite.log unless set in config * BUG FIXES - +- Encoding Error with signatures (#1440862) - Non existant refresh value leads to security error (#1466274) - Changing format away from HTML generates a Zope error (#1460216) - Tickets without a subject no longer result in a permissions error (#1436863) - Test dataset has broken messages (#1422365) - Account and User drop downs now work with Internet Explorer (#1410232) - Ticket date is set to application server date by default (#1444200) +- Test suite no longer generates testsuite.log unless set in config Version 2.0.7 * BUG FIXES Modified: MailManager/branches/RELENG_2_1/Extensions/TicketPluggableBrain.py =================================================================== --- MailManager/branches/RELENG_2_1/Extensions/TicketPluggableBrain.py 2006-04-10 15:56:22 UTC (rev 2907) +++ MailManager/branches/RELENG_2_1/Extensions/TicketPluggableBrain.py 2006-04-10 16:08:58 UTC (rev 2908) @@ -322,13 +322,13 @@ for msg in self.sql.listMessages(sqv_ticket_id = self.id): date = DateTime(msg.msg_date).rfc822() from_email = msg.from_email or self.from_email - write("From %s %s\n" % (from_email, date)) - write(msg.raw_headers) + write("From %s %s\n" % (from_email.encode('utf-8'), date)) + write(msg.raw_headers.encode('utf-8')) write('\n\n') for line in msg.body.splitlines(): if from_escape.match(line): - line = ">" + line - write(line) + line = u">" + line + write(line.encode('utf-8')) write('\n') write ('\n') return @@ -471,25 +471,64 @@ security.declareProtected('MailManager Manage Tickets', 'http_addNote') def http_addNote(self, mail_to, cc='', bcc='', subject='', body='', - body_is_html=0, user_signature=None, + body_is_html=False, user_signature=None, last_modified=None, REQUEST=None, RESPONSE=None): """ Add a note to a ticket. No mail is generated """ - + return self.sendMethod(mail_to=mail_to, cc=cc, bcc=bcc, subject=subject, body=body, body_is_html=body_is_html, - user_signature=user_signature, sendmail=0, + user_signature=user_signature, sendmail=False, last_modified=last_modified, - change_state = None, + change_status = None, event = 'AddNote', REQUEST=REQUEST, RESPONSE=RESPONSE) def sendMethod(self, mail_to, cc='', bcc='', subject='', body='', - body_is_html=0, user_signature=None, next_id=None, - last_modified=None, offset=None, change_state=None, - transition=None, event=None, sendmail=1, + body_is_html=False, user_signature=None, next_id=None, + last_modified=None, offset=None, change_status=None, + transition=None, event=None, sendmail=True, REQUEST=None, RESPONSE=None): + """ Common underlying method for sending replies/adding notes + This method handles all of the various http_ request method which + handle a user request to send a reply, or add a note to a ticket. + In order to avoid code duplication, this method takes care of + generating error messages, and sanitising user input, before + passing it to the addNote method to act on the request. + + + @param mail_to: The intended recipient - should be blank for notes + @param cc: Carbon Copy recipients - should be blank for notes + @param bcc: Blind Carbon Copy recipients - should be blank for notes + @type mail_to string (utf-8) + @type cc: string (utf-8) + @type bcc: string (utf-8) + @type subject: string (utf-8) + @type body_is_html: boolean + @type body: string (utf-8) + @type user_signature: string (utf-8) + @type next_id: int + @type last_modified: string (ascii, iso date format) + @type offset: int + @type change_status: string (utf-8) + @type transition: string (utf-8) + @type event: string (utf-8) + @type sendmail: boolean + """ + + # Sanitize the string parameters + if mail_to is not None: mail_to = mail_to.decode('utf-8') + if cc is not None: cc = cc.decode('utf-8') + if bcc is not None: bcc = bcc.decode('utf-8') + if subject is not None: subject = subject.decode('utf-8') + if body is not None: body = body.decode('utf-8') + if user_signature is not None: user_signature = user_signature.decode('utf-8') + if change_status is not None: change_status = change_status.decode('utf-8') + if transition is not None: transition = transition.decode('utf-8') + if event is not None: event = event.decode('utf-8') + body_is_html = int(body_is_html) + # Parameters are in utf-8, convert to unicode to avoid problems mail_to = unicode(mail_to, 'utf-8') cc = unicode(cc, 'utf-8') @@ -566,6 +605,8 @@ raw_headers = '' remote_addr = None + + try: self.addNote(mail_to = mail_to, cc = cc, bcc = bcc, subject = subject, body = body, body_is_html = body_is_html, Modified: MailManager/branches/RELENG_2_1/MailMixin.py =================================================================== --- MailManager/branches/RELENG_2_1/MailMixin.py 2006-04-10 15:56:22 UTC (rev 2907) +++ MailManager/branches/RELENG_2_1/MailMixin.py 2006-04-10 16:08:58 UTC (rev 2908) @@ -294,6 +294,8 @@ def _addSignature(self, body, user_signature, account_signature, html=0): """Add the signature to the body of the e-mail. Note that for the time being the user signature cannot contain HTML. + body, user_signature and account_signature should all be supplied in + unicode. """ if not (user_signature or account_signature): return body @@ -311,11 +313,11 @@ account_signature = self._makeHTML(account_signature) if not html: body = self._makeHTML(body) - sep1 = '<br />\n-- \n<br />' - sep2 = '<hr />' + sep1 = u'<br />\n-- \n<br />' + sep2 = u'<hr />' else: - sep1 = '-- ' - sep2 = '=' * 40 + sep1 = u'-- ' + sep2 = u'=' * 40 if user_signature and account_signature: return '\n'.join([body, sep1, user_signature, sep2, account_signature]) @@ -324,7 +326,7 @@ (user_signature or account_signature)]) def _makeHTML(self, text): - return cgi.escape(text).replace('\n', '<br />\n') + return cgi.escape(text).replace(u'\n', u'<br />\n') # Utility Functions @@ -336,7 +338,7 @@ try: ss = [self.toUnicode(s, charset) for s, charset in email.Header.decode_header(text)] - return ' '.join(ss) + return u' '.join(ss) except email.Header.HeaderParseError: return text This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ke...@us...> - 2006-04-10 16:17:15
|
Revision: 2909 Author: kevca Date: 2006-04-10 09:16:57 -0700 (Mon, 10 Apr 2006) ViewCVS: http://svn.sourceforge.net/mailmanager/?rev=2909&view=rev Log Message: ----------- Completing fix for - Archive and Restore functionality broken (#1452514) Including part of - Cannot create HTML tickets (#1466223) Modified Paths: -------------- MailManager/branches/RELENG_2_1/CHANGES.txt MailManager/branches/RELENG_2_1/Extensions/AccountPluggableBrain.py MailManager/branches/RELENG_2_1/MailManager.py MailManager/branches/RELENG_2_1/Makefile Added Paths: ----------- MailManager/branches/RELENG_2_1/tests/testExporting.py Modified: MailManager/branches/RELENG_2_1/CHANGES.txt =================================================================== --- MailManager/branches/RELENG_2_1/CHANGES.txt 2006-04-10 16:08:58 UTC (rev 2908) +++ MailManager/branches/RELENG_2_1/CHANGES.txt 2006-04-10 16:16:57 UTC (rev 2909) @@ -78,7 +78,6 @@ Version 2.0.8 (Unreleased) * Unfactored from 2.0.8 - Cannot create HTML tickets (#1466223) -- Archive and Restore functionality broken (#1452514) - Unicode from addresses break incoming mail (#1440861) - Email sent from Macs/Linux to Windows aren't formatted (#1440850) - Valid tags are stripped from HTML on sending (#1460262) @@ -88,6 +87,7 @@ - Direct cycles of support_of are not allowed now. (#1353210) - Test suite no longer generates testsuite.log unless set in config * BUG FIXES +- Archive and Restore functionality broken (#1452514) - Encoding Error with signatures (#1440862) - Non existant refresh value leads to security error (#1466274) - Changing format away from HTML generates a Zope error (#1460216) Modified: MailManager/branches/RELENG_2_1/Extensions/AccountPluggableBrain.py =================================================================== --- MailManager/branches/RELENG_2_1/Extensions/AccountPluggableBrain.py 2006-04-10 16:08:58 UTC (rev 2908) +++ MailManager/branches/RELENG_2_1/Extensions/AccountPluggableBrain.py 2006-04-10 16:16:57 UTC (rev 2909) @@ -515,14 +515,6 @@ if self.assign_queue: return "%s (Queue)" % self.assign_queue - security.declareProtected('MailManager Manage Tickets', 'export') - def export(self, RESPONSE): - """ Export all the messages for a particular account.""" - RESPONSE.setHeader('content-type', 'text/plain') - RESPONSE.setHeader('Content-Disposition', - 'attachment; filename=mbox.txt') - [self.ticket(id = t.id)[0].export(RESPONSE) for - t in self.sql.listTickets(sqv_account_id = self.email)] # Methods to fetch mail. security.declarePublic('getMail') Modified: MailManager/branches/RELENG_2_1/MailManager.py =================================================================== --- MailManager/branches/RELENG_2_1/MailManager.py 2006-04-10 16:08:58 UTC (rev 2908) +++ MailManager/branches/RELENG_2_1/MailManager.py 2006-04-10 16:16:57 UTC (rev 2909) @@ -1057,15 +1057,6 @@ account.process(msg.as_string(), REQUEST) return self.index_html(REQUEST) - security.declareProtected('MailManager Manage Tickets', 'export') - def export(self, RESPONSE): - """Export all messages in self in a Unix mailbox format.""" - RESPONSE.setHeader('content-type', 'text/plain') - RESPONSE.setHeader('Content-Disposition', - 'attachment; filename=mbox.txt') - [self.account(email=a.email)[0].export(RESPONSE) - for a in self.sql.listAccounts(sqv_email='')] - ############################################################################### ############################################################################### @@ -2735,44 +2726,24 @@ # System Settings - security.declareProtected('MailManager Settings', 'deleteTickets') - def deleteTickets(self, to_date, account_id, section, REQUEST, RESPONSE, - state=None, category0=None, category1=None, - category2=None, archive=False): + security.declareProtected('MailManager Settings', 'http_deleteTickets') + def http_deleteTickets(self, to_date, account_id, section, REQUEST, RESPONSE, + status='', category0='', category1='', + category2='', archive=False): """Delete selected tickets.""" if section == 'delete': - if state is None: + if status is None: REQUEST.set('error', 'You must select at least one state.') REQUEST.set('section', 'delete') else: REQUEST.set('section', 'confirm') return self.SystemSettings(self, REQUEST) + if archive: - f = TemporaryFile(suffix='.xml') - exp = XMLGenerator(f, 'utf-8') - exp.startDocument() - exp.startElement('mailmanager', {}) - tickets=self.sql.listTickets(sqv_to_date=to_date, - sqv_account_id=account_id, - sqv_state=state, - sqv_category0=category0, - sqv_category1=category1, - sqv_category2=category2) - for ticket in tickets: - self.row2xml(exp, 'ticket', tickets.names(), ticket) - messages = self.sql.listMessages(sqv_ticket_id=ticket.id) - for message in messages: - self.row2xml(exp, 'message', messages.names(), message) - attach = self.sql.listAttachments(sqv_message_id=message.id, - sqv_include_body=True) - for att in attach: - self.row2xml(exp, 'attachment', attach.names(), att) - history = self.sql.listHistory(sqv_ticket_id=ticket.id) - for hist in history: - self.row2xml(exp, 'history', history.names(), hist) - exp.endElement('mailmanager') - exp.endDocument() - f.seek(0) + f = self.archiveTickets(to_date = to_date, + account_id = account_id, status = status, + category0 = category0, category1 = category1, + category2 = category2) # In all cases we need to delete the tickets self.sql.deleteTickets(sqv_to_date=to_date, @@ -2790,10 +2761,68 @@ # This should be replaced with a filestream iterator - unfortunately # the stock iterator requires a filename, not a file object. RESPONSE.write(f.read()) - else: + + # In all cases we need to delete the tickets + self.deleteTickets(to_date=to_date, + account_id=account_id, + status=status, + category0=category0, + category1=category1, + category2=category2) + + if not archive: REQUEST.set('section', 'delete') return self.SystemSettings(self, REQUEST) + + security.declareProtected('MailManager Settings', 'deleteTickets') + def deleteTickets(self, to_date, account_id, status='', + category0='', category1='', category2=''): + """ Delete selected tickets. """ + # In all cases we need to delete the tickets + self.sql.deleteTickets(to_date=to_date.ISO8601(), + account_id=account_id, + status=status, + category0=category0, + category1=category1, + category2=category2) + + + security.declareProtected('MailManager Settings', 'archiveTickets') + def archiveTickets(self, to_date, account_id, status='', + category0='', category1='', category2=''): + """ Archive selected tickets. + + Method generates an .xml file and returns it to the caller + """ + f = TemporaryFile(suffix='.xml') + exp = XMLGenerator(f, 'utf-8') + exp.startDocument() + exp.startElement('mailmanager', {}) + tickets=self.sql.listTickets(to_date=to_date.ISO8601(), + account_id=account_id, + status=status, + category0=category0, + category1=category1, + category2=category2) + for ticket in tickets: + self.row2xml(exp, 'ticket', tickets.names(), ticket) + messages = self.sql.listMessages(ticket_id=ticket.id) + for message in messages: + self.row2xml(exp, 'message', messages.names(), message) + attach = self.sql.listAttachments(message_id=message.id, + include_body=True) + for att in attach: + self.row2xml(exp, 'attachment', attach.names(), att) + history = self.sql.listHistory(ticket_id=ticket.id) + for hist in history: + self.row2xml(exp, 'history', history.names(), hist) + exp.endElement('mailmanager') + exp.endDocument() + f.seek(0) + + return f + security.declarePrivate('row2xml') def row2xml(self, exp, name, columns, row): """Convert a database row into XML.""" @@ -3059,7 +3088,76 @@ ############################################################################### ############################################################################### + + # Misc methods + security.declareProtected('MailManager Create Tickets', 'http_setHTML') + def http_setHTML(self, REQUEST): + """ Handler for user changing the format of a message body + + This method is called when the end user clicks the arrow button + to the right of the HTML/Plain text option. This method then + replaces the content of the current message body based on + given template, converting to HTML/plain text as appropriate. + + Request variables: + + * new_body_format: the format to encode the resulting body in + * body_is_html : gets set to the format the body is now in + * body : altered so that it is in the correct format + + This is a duplication of the code in + Extensions/TicketPluggableBrain.py . A refactoring should be done + in order to remove this duplication. + """ + body_is_html = REQUEST.get('body_is_html', False) + new_body_html = REQUEST.get('new_body_html', False) + body = REQUEST.get('body', '').decode('utf-8') + + if body_is_html and not new_body_html: + # Convert from html to text + body = html2text(body) + + if not body_is_html and new_body_html: + # Convert from text to html + body = self._makeHTML(body) + + REQUEST.set('body', body.encode('utf-8')) + REQUEST.set('body_is_html', new_body_html) + return self.Create(REQUEST) + + + security.declarePublic('__bobo_traverse__') + def __bobo_traverse__(self, REQUEST=None, name=None): + """ Traversal hook to add HTTP realm and do method mangling + + There are some docs at the following page, although they don't + match the code of the base class which is what this method is + taken from + http://www.tchezope.org/traducoes/gdz_python/ObjectPublishing.html + + Eventually we will split this to use bobo_realm + """ + + # First of all, try and mangle http_ in front of requests + ret = getattr(self, 'http_%s' % name, None) + if ret: return ret + + # Then try and obtain without mangling + ret = getattr(self, name, None) + if ret: return ret + else: + ret = self[name] + if ret: return ret + + return name + + + + +############################################################################### +############################################################################### + security.declarePublic('master') master = PageTemplateFile('www/master', globals()) Modified: MailManager/branches/RELENG_2_1/Makefile =================================================================== --- MailManager/branches/RELENG_2_1/Makefile 2006-04-10 16:08:58 UTC (rev 2908) +++ MailManager/branches/RELENG_2_1/Makefile 2006-04-10 16:16:57 UTC (rev 2909) @@ -36,6 +36,7 @@ testAPI.py \ testAutoLogout.py \ testQueueing.py \ + testExporting.py \ testXSS.py ) # testSending.py ) Added: MailManager/branches/RELENG_2_1/tests/testExporting.py =================================================================== --- MailManager/branches/RELENG_2_1/tests/testExporting.py (rev 0) +++ MailManager/branches/RELENG_2_1/tests/testExporting.py 2006-04-10 16:16:57 UTC (rev 2909) @@ -0,0 +1,153 @@ +# (c) Copyright Logicalware Ltd 2002-2006 +# Logicalware MailManager comes with ABSOLUTELY NO WARRANTY. Further details +# including conditions of redistribution are contained in LICENSE.txt +# http://www.logicalware.org/ +# $Id: testSending.py 2872 2006-03-31 12:41:56Z kevca $ + +import os, sys +if __name__ == '__main__': + execfile(os.path.join(sys.path[0], 'framework.py')) + +from Testing import ZopeTestCase +ZopeTestCase.installProduct('MailManager') + +import random +import time +import re +import shutil +import DocumentTemplate +import DateTime + +import testDatabase + +from Shared.DC.ZRDB import sqlvar, sqltest, sqlgroup, Results +from Products.MailManager.tests.classes import testStructures +from Products.MailManager.tests.classes import registry + +try: + import _mysql + import MySQLdb + from Products import ZMySQLDA +except ImportError, e: + pass + +try: + import psycopg + import pg + from Products import ZPsycopgDA +except ImportError, e: + pass + +import Globals + +from Testing import makerequest +from Testing.ZopeTestCase import utils +from Products import PlacelessTranslationService + +import ZODB, ZODB.FileStorage + +from Products.MailManager.tests.classes.testStructures import pop3control, imapcontrol + +import pprint + +import mx.DateTime +import StringIO + + +class exportingTest(testStructures.mailManagerTestCase): + """ + Exports the tickets held in the system to a dumpfile, and then + attempts to restore from that dumpfile + """ + + #_logtrace = True + + def exportingTests(self): + self.mmobj.setStartDate(mx.DateTime.strptime("20010101 00:00", '%Y%m%d %H:%M')) + if self.dbplatform == 'postgres': + self.mmobj.sql_now = "CAST ('2001-01-01 12:00' AS TIMESTAMP)" + elif self.dbplatform == 'mysql': + self.mmobj.sql_now = "(SELECT CAST('2001-01-01 12:00' AS DATETIME))" + else: + raise NotImplementedError("testSending isn't implemented for platform %s yet" % self.dbplatform) + + self.mmobj.populateTickets('acmestatic') + + self.exportTest() + + self.mmobj.wipeDataset('acmestatic') + self.mmobj.populateAccounts('acmestatic') + + print self.mmobj.sql.listTickets(count=1)[0].count + + self.importTest() + + def exportTicket(self): + """ Export ticket with id = 1 """ + self.exportData = StringIO.StringIO() + self.request.response.stdout = self.exportData + + ticket = self.mmobj.ticket(id=1)[0] + ticket.export( + RESPONSE = self.request.response + ) + + def exportTest(self): + """ Runs export and saves the output in self.exportData """ + + self.exportData = StringIO.StringIO() + self.request.response.stdout = self.exportData + + self.mmobj.http_deleteTickets(section = 'confirm', + archive = True, + account_id = '', + to_date = DateTime.DateTime("2010-01-01 12:00:00 +0000"), + REQUEST=self.request, RESPONSE=self.request.response) + + def importTest(self): + """ Loads in the dumped data from self.exportData """ + self.exportData.seek(0) + datalines = self.exportData.readlines() + header = True + exportData = StringIO.StringIO() + for line in datalines: + if header: + if line.strip() == '': + header = False + else: + exportData.write(line) + + ofile = open('/tmp/ticketdata.dat', 'wb') + ofile.write(exportData.getvalue()) + + exportData.seek(0) + self.mmobj.restoreTickets( + archive_file = exportData, + REQUEST = self.request + ) + + +class TestExporting(exportingTest): + + def testExporting(self): + self.dbplatform = self.config['databases']['primary_database'] + + self.setupMailManager(dbplatform = self.dbplatform, debugmode=True) + + self.mmobj.populateAccounts('acmestatic') + + self.exportingTests() + + + ########################################################################### + +def test_suite(): + from unittest import TestSuite, makeSuite + suite = TestSuite() + suite.addTest(makeSuite(TestExporting)) + return suite + +if __name__ == '__main__': + framework() + + This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ke...@us...> - 2006-04-17 12:05:56
|
Revision: 2913 Author: kevca Date: 2006-04-17 05:05:46 -0700 (Mon, 17 Apr 2006) ViewCVS: http://svn.sourceforge.net/mailmanager/?rev=2913&view=rev Log Message: ----------- - Cannot create HTML tickets (#1466223) Modified Paths: -------------- MailManager/branches/RELENG_2_1/MailManager.py MailManager/branches/RELENG_2_1/www/Create.zpt Modified: MailManager/branches/RELENG_2_1/MailManager.py =================================================================== --- MailManager/branches/RELENG_2_1/MailManager.py 2006-04-17 09:57:41 UTC (rev 2912) +++ MailManager/branches/RELENG_2_1/MailManager.py 2006-04-17 12:05:46 UTC (rev 2913) @@ -74,6 +74,14 @@ import mx.DateTime.ISO from mx.DateTime.Parser import DateTimeFromString +# HTML conversion for the Create method +try: + from stripogram import html2safehtml +except ImportError: + from Products.stripogram import html2safehtml + +from Products.MailManager.support.html2text import html2text + # Test data to be used under the ZMI and test suite from tests.classes.mmtestdata import TestDataMixin Modified: MailManager/branches/RELENG_2_1/www/Create.zpt =================================================================== --- MailManager/branches/RELENG_2_1/www/Create.zpt 2006-04-17 09:57:41 UTC (rev 2912) +++ MailManager/branches/RELENG_2_1/www/Create.zpt 2006-04-17 12:05:46 UTC (rev 2913) @@ -9,7 +9,10 @@ tal:define="page string:Tickets; help string:HelpCreateTicket; view string:create; - date_query request/SESSION/date_query | string:; + body_is_html python:int(request.get('body_is_html', 0)); + body request/body | string:; + ticket_type request/ticket_type | string:incoming; + account_name request/account_name | string:; attachments request/SESSION/attachments | python:{}; ob_or_req request; dummy python:request.RESPONSE.setHeader('content-type', 'text/html;;charset=UTF-8')"> @@ -35,79 +38,103 @@ </div> - <div metal:fill-slot="main_content"> - <form accept-charset="utf-8" class="blockform" method="post" action="." enctype="multipart/form-data"> - <div tal:condition="python:user.has_role('Tickets')"> - <h2>Ticket Details</h2> - <p> - <label for="assigned" tal:attributes="class python:test(request.has_key('flag_assigned'), 'error', None)" i18n:translate="assigned_label">Assigned</label> - <select tal:attributes="tabindex tabindex/next" id="assigned" name="assigned"> - <option tal:repeat="u here/listUsers" tal:attributes="selected python:test(u.username == user.getUserName(), 'selected', None)" tal:content="u/username">Username</option> - </select> - </p> - <p metal:use-macro="container/macros/macros/priority"> - Priority - </p> - <p metal:use-macro="container/macros/macros/categories_form"> - Categories - </p> - <p metal:use-macro="container/macros/macros/response_target"> - response targets - </p> - - <p> - <span tal:condition="exists:request/parent_ticket" tal:omit-tag=""> - <label for="support_of" tal:attributes="class python:test(request.has_key('flag_support_of'), 'error', None)" i18n:translate="support_of_label">Support of</label> - <input id="support_of" type="checkbox" name="support_of" checked="checked" - tal:attributes="tabindex tabindex/next; value request/parent_ticket" /> - <span i18n:translate="ticket_x" tal:omit-tag="">Ticket - <span i18n:name="x" tal:content="python:request.get('parent_ticket').zfill(6)">1</span> - </span> - </span> - </p> - </div> - <h2 i18n:translate="new_ticket">New Ticket</h2> + <div metal:fill-slot="main_content"> + <form accept-charset="utf-8" class="blockform" method="post" action="." enctype="multipart/form-data"> + <div tal:condition="python:user.has_role('Tickets')"> + <h2>Ticket Details</h2> <p> - <label for="account_name" tal:attributes="class python:test(request.has_key('flag_account_name'), 'error', None)" i18n:translate="to">To</label> - <select tal:attributes="tabindex tabindex/next" id="account_name" name="account_name"> - <option tal:repeat="acc python:here.sql.listAccounts(sqv_email='')" tal:content="acc/email">te...@ex...</option> + <label for="assigned" tal:attributes="class python:test(request.has_key('flag_assigned'), 'error', None)" i18n:translate="assigned_label">Assigned</label> + <select tal:attributes="tabindex tabindex/next" id="assigned" name="assigned"> + <option tal:repeat="u here/listUsers" tal:attributes="selected python:test(u.username == user.getUserName(), 'selected', None)" tal:content="u/username">Username</option> </select> </p> - <p> - <label for="from_name" tal:attributes="class python:test(request.has_key('flag_from_name'), 'error', None)" i18n:translate="from_name">From (name)</label> - <input id="from_name" name="from_name" size="40" tal:attributes="tabindex tabindex/next; value request/from_name | nothing" /> - (full name) - </p> - <p> - <label for="from_email" tal:attributes="class python:test(request.has_key('flag_from_email'), 'error', None)" i18n:translate="from_address">From (address)</label> - <input id="from_email" name="from_email" size="40" tal:attributes="tabindex tabindex/next; value request/from_email | nothing" /> (email address) - </p> - <p> - <label for="title" tal:attributes="class python:test(request.has_key('flag_title'), 'error', None)" i18n:translate="subject">Subject</label> - <input id="title" name="title" size="40" tal:attributes="tabindex tabindex/next; value request/title | nothing" /> - </p> - <p> - <label for="file_attach" tal:attributes="class python:test(request.has_key('flag_file_attach'), 'error', None)" i18n:translate="attach">Attach</label> - <input tal:attributes="tabindex tabindex/next" id="file_attach" size="30" name="file_attach" type="file" /> - <input tal:attributes="tabindex tabindex/next" type="image" class="gobutton" src="images/button_go.gif" name="addAttachment:method" alt="Go" /> + <p metal:use-macro="container/macros/macros/priority"> + Priority </p> - <p tal:condition="here/attachments/objectIds"> - <select tal:attributes="tabindex tabindex/next" name="standard_attach"> - <option tal:repeat="sa here/attachments/objectIds" tal:attributes="value sa" tal:content="sa">test.pdf</option> - </select> - <input tal:attributes="tabindex tabindex/next" type="image" class="gobutton" src="images/button_go.gif" name="addAttachment:method" alt="Go" /> + <p metal:use-macro="container/macros/macros/categories_form"> + Categories </p> - <p tal:repeat="attached attachments/keys"> - <input id="ids:list" type="checkbox" name="ids:list" tal:attributes="tabindex tabindex/next; value attached" /> - <label for="ids:list" tal:attributes="class python:test(request.has_key('flag_ids:list'), 'error', None)" tal:content="attached">test.pdf</label> + <p metal:use-macro="container/macros/macros/response_target"> + response targets </p> - <p tal:condition="attachments"> - <a i18n:translate="delete" class="state delete">Delete</a> + + <p> + <span tal:condition="exists:request/parent_ticket" tal:omit-tag=""> + <label for="support_of" tal:attributes="class python:test(request.has_key('flag_support_of'), 'error', None)" i18n:translate="support_of_label">Support of</label> + <input id="support_of" type="checkbox" name="support_of" checked="checked" + tal:attributes="tabindex tabindex/next; value request/parent_ticket" /> + <span i18n:translate="ticket_x" tal:omit-tag="">Ticket + <span i18n:name="x" tal:content="python:request.get('parent_ticket').zfill(6)">1</span> + </span> + </span> </p> - <p><textarea tal:attributes="tabindex tabindex/next" name="body" rows="16" cols="65" tal:content="request/body | nothing"></textarea></p> + </div> + <h2 i18n:translate="new_ticket">New Ticket</h2> + <p> + <label for="account_name" tal:attributes="class python:test(request.has_key('flag_account_name'), 'error', None)" i18n:translate="to">To</label> + <select tal:attributes="tabindex tabindex/next" id="account_name" name="account_name"> + <option tal:repeat="acc python:here.sql.listAccounts(sqv_email='')" tal:content="acc/email">te...@ex...</option> + </select> + </p> + <p> + <label for="from_name" tal:attributes="class python:test(request.has_key('flag_from_name'), 'error', None)" i18n:translate="from_name">From (name)</label> + <input id="from_name" name="from_name" size="40" tal:attributes="tabindex tabindex/next; value request/from_name | nothing" /> + (full name) + </p> + <p> + <label for="from_email" tal:attributes="class python:test(request.has_key('flag_from_email'), 'error', None)" i18n:translate="from_address">From (address)</label> + <input id="from_email" name="from_email" size="40" tal:attributes="tabindex tabindex/next; value request/from_email | nothing" /> (email address) + </p> + <p> + <label for="title" tal:attributes="class python:test(request.has_key('flag_title'), 'error', None)" i18n:translate="subject">Subject</label> + <input id="title" name="title" size="40" tal:attributes="tabindex tabindex/next; value request/title | nothing" /> + </p> + <p> + <label for="format" tal:attributes="class python:test(request.has_key('flag_format'), 'error', None)" i18n:translate="format_label">Format</label> + <input type="hidden" name="body_is_html" tal:attributes="value python:body_is_html"/> + <select name="new_body_html:int"> + <option value="0" + tal:attributes="selected not:body_is_html" + i18n:translate="plain_text"> + Plain text + </option> + <option value="1" + tal:attributes="selected body_is_html" + i18n:translate="html"> + HTML + </option> + </select> + <input tal:attributes="tabindex tabindex/next" type="image" class="gobutton" src="images/button_go.gif" name="setHTML:method" alt="Go" /> + </p> + <p> + <label for="file_attach" tal:attributes="class python:test(request.has_key('flag_file_attach'), 'error', None)" i18n:translate="attach">Attach</label> + <input tal:attributes="tabindex tabindex/next" id="file_attach" size="30" name="file_attach" type="file" /> + <input tal:attributes="tabindex tabindex/next" type="image" class="gobutton" src="images/button_go.gif" name="addAttachment:method" alt="Go" /> + </p> + <p tal:condition="here/attachments/objectIds"> + <select tal:attributes="tabindex tabindex/next" name="standard_attach"> + <option tal:repeat="sa here/attachments/objectIds" tal:attributes="value sa" tal:content="sa">test.pdf</option> + </select> + <input tal:attributes="tabindex tabindex/next" type="image" class="gobutton" src="images/button_go.gif" name="addAttachment:method" alt="Go" /> + </p> + <p tal:repeat="attached attachments/keys"> + <input id="ids:list" type="checkbox" name="ids:list" tal:attributes="tabindex tabindex/next; value attached" /> + <label for="ids:list" tal:attributes="class python:test(request.has_key('flag_ids:list'), 'error', None)" tal:content="attached">test.pdf</label> + </p> + <p tal:condition="attachments"> + <a i18n:translate="delete" class="state delete">Delete</a> + </p> - <p><input tal:attributes="tabindex tabindex/next" type="submit" value="Send" name="createTicket:method" /></p> - </form> - </div> + <!-- Body --> + <p> + <textarea tal:attributes="tabindex tabindex/next" tal:condition="not:body_is_html" name="body" rows="16" cols="65" tal:content="body">Non-HTML template.</textarea> + <span tal:condition="body_is_html" tal:replace="structure python:here.Epoz(lang='en', name='body', data=body, css='master_style_css', style='width: 550px; height: 225px;')"> + An (EPOZ) text area containing HTML template. + </span> + </p> + + <p><input tal:attributes="tabindex tabindex/next" type="submit" value="Send" name="createTicket:method" /></p> + </form> + </div> </body> </html> This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ke...@us...> - 2006-04-17 14:04:18
|
Revision: 2914 Author: kevca Date: 2006-04-17 07:04:03 -0700 (Mon, 17 Apr 2006) ViewCVS: http://svn.sourceforge.net/mailmanager/?rev=2914&view=rev Log Message: ----------- Fix for - Default template on accounts (#1464963) Requires a migration to add a new database field. Updated UI screens to allow default template to be set on accounts. Modified Paths: -------------- MailManager/branches/RELENG_2_1/CHANGES.txt MailManager/branches/RELENG_2_1/Extensions/TicketPluggableBrain.py MailManager/branches/RELENG_2_1/MailManager.py MailManager/branches/RELENG_2_1/migrations/v2_1/__init__.py MailManager/branches/RELENG_2_1/sql/v2_1/addAccount.zsql MailManager/branches/RELENG_2_1/sql/v2_1/editAccount.zsql MailManager/branches/RELENG_2_1/sql/v2_1/finalTables.zsql MailManager/branches/RELENG_2_1/www/AccountSettings.zpt MailManager/branches/RELENG_2_1/www/macros.zpt MailManager/branches/RELENG_2_1/www/ticket_index_html.zpt Modified: MailManager/branches/RELENG_2_1/CHANGES.txt =================================================================== --- MailManager/branches/RELENG_2_1/CHANGES.txt 2006-04-17 12:05:46 UTC (rev 2913) +++ MailManager/branches/RELENG_2_1/CHANGES.txt 2006-04-17 14:04:03 UTC (rev 2914) @@ -1,3 +1,24 @@ +Version 2.1-RC3 +- Default template on accounts (#1464963) +* BUG FIXES From 2.0.7 to 2.0.8 +- Non existant refresh value leads to security error (#1466274) +- Cannot create HTML tickets (#1466223) +- Archive and Restore functionality broken (#1452514) +- Unicode from addresses break incoming mail (#1440861) +- Changing format away from HTML generates a Zope error (#1460216) +- Email sent from Macs/Linux to Windows aren't formatted (#1440850) +- Valid tags are stripped from HTML on sending (#1460262) +- Encoding Error with signatures (#1440862) +- Installing test suite data fails (#1463368) +- Fixed issue with non-ascii filter condition (#1438720) +- Cite last includes only replies now (#1369571) +- Direct cycles of support_of are not allowed now. (#1353210) +- Tickets without a subject no longer result in a permissions error (#1436863) +- Test dataset has broken messages (#1422365) +- Account and User drop downs now work with Internet Explorer (#1410232) +- Ticket date is set to application server date by default (#1444200) +- Test suite no longer generates testsuite.log unless set in config + Version 2.1-RC2 * FEATURES - Message loops are now caught based on presence of X-MailManager header @@ -11,9 +32,7 @@ - Deleting users via the Settings tab fails (#1456837) - Deleting an Account with tickets fails (#1459386) - RNG using too much entropy (#1459353) -- Direct cycles of support_of are not allowed now. (#1353210) - Fixed issue with non-ascii filter condition (#1438720) -- Cite last includes only replies now (#1369571) - Tabs do not display on MySQL (#1451140) - addUser page breaks on MySQL (#1451131) - CookieCrumblerDisabled exception with http auth (#1451109) @@ -21,6 +40,9 @@ user has successfully supplied a correct username and password combination. In addition, the amount of entropy used per login has been reduced but can still be considered secure. Tests have also been updated to reflect this. +* BUG FIXES From 2.0.7 to 2.0.8 +- Direct cycles of support_of are not allowed now. (#1353210) +- Cite last includes only replies now (#1369571) Version 2.1-RC1 * BUG FIXES @@ -77,26 +99,45 @@ Version 2.0.8 (Unreleased) * Unfactored from 2.0.8 -- Cannot create HTML tickets (#1466223) +- Create ticket method is highly broken + - Cannot create HTML tickets (#1466223) - Unicode from addresses break incoming mail (#1440861) -- Email sent from Macs/Linux to Windows aren't formatted (#1440850) - Valid tags are stripped from HTML on sending (#1460262) - Installing test suite data fails (#1463368) - Fixed issue with non-ascii filter condition (#1438720) -- Cite last includes only replies now (#1369571) -- Direct cycles of support_of are not allowed now. (#1353210) -- Test suite no longer generates testsuite.log unless set in config * BUG FIXES +- Email sent from Macs/Linux to Windows aren't formatted (#1440850) - Archive and Restore functionality broken (#1452514) - Encoding Error with signatures (#1440862) - Non existant refresh value leads to security error (#1466274) - Changing format away from HTML generates a Zope error (#1460216) +- Direct cycles of support_of are not allowed now. (#1353210) - Tickets without a subject no longer result in a permissions error (#1436863) - Test dataset has broken messages (#1422365) - Account and User drop downs now work with Internet Explorer (#1410232) - Ticket date is set to application server date by default (#1444200) - Test suite no longer generates testsuite.log unless set in config +Version 2.0.8 +* BUG FIXES +- Non existant refresh value leads to security error (#1466274) +- Cannot create HTML tickets (#1466223) +- Archive and Restore functionality broken (#1452514) +- Unicode from addresses break incoming mail (#1440861) +- Changing format away from HTML generates a Zope error (#1460216) +- Email sent from Macs/Linux to Windows aren't formatted (#1440850) +- Valid tags are stripped from HTML on sending (#1460262) +- Encoding Error with signatures (#1440862) +- Installing test suite data fails (#1463368) +- Fixed issue with non-ascii filter condition (#1438720) +- Cite last includes only replies now (#1369571) +- Direct cycles of support_of are not allowed now. (#1353210) +- Tickets without a subject no longer result in a permissions error (#1436863) +- Test dataset has broken messages (#1422365) +- Account and User drop downs now work with Internet Explorer (#1410232) +- Ticket date is set to application server date by default (#1444200) +- Test suite no longer generates testsuite.log unless set in config + Version 2.0.7 * BUG FIXES - Fixed the setting of next ticket id for MySQL, no longer silently ignores Modified: MailManager/branches/RELENG_2_1/Extensions/TicketPluggableBrain.py =================================================================== --- MailManager/branches/RELENG_2_1/Extensions/TicketPluggableBrain.py 2006-04-17 12:05:46 UTC (rev 2913) +++ MailManager/branches/RELENG_2_1/Extensions/TicketPluggableBrain.py 2006-04-17 14:04:03 UTC (rev 2914) @@ -820,6 +820,72 @@ self.id)) + def getDefaultTemplate(self, REQUEST): + """ Method to set the default template in the ticket response + + This method is called from the ticket index page. It will set the + default template for replies, providing that no template has been + set already. This method will translate the template_name + and possibly update the body and body_is_html request variables + """ + + # Check to see if a template name is already set + if REQUEST.get('template_name', None) is not None: + return unicode(REQUEST['template_name'], 'utf-8') + + # Otherwise, this is a new reponse, set the template automatically + else: + + # Template has not already been set, get the default + account = self.sql.listAccounts(sqv_email=self.account_id)[0] + template_name = account.default_template + + # Obtain the template details, deailing with the cite_last + # method appropriately + if template_name == 'cite_last': + + # Find the last actual message + msgs = self.sql.listMessages(sqv_ticket_id=self.id) + # Skip over any notes + for position in range(1,len(msgs)+1): + zmsg = msgs[-position] + if not zmsg.msg_to == '': break + # Remove the signature and split the plain text body per + # line, start with > for quote chars. Note that we don't + # currently quote HTML, but that would be a nice addition + # in the future. + cite = sig_remover.sub('', zmsg.body) + body = u''.join(['> %s' % line for line + in cite.splitlines(1)]) + template_is_html = 0 + + elif template_name is not None: + template = self._getTemplate(template_name) + body = template['body'] + template_is_html = template['html'] + + else: + template_is_html = False + template_name = u'' + body = u'' + + # Is this an HTML template? Check the settings flag + if template_is_html: + # Set the body_is_html flag so that the correct editor is + # used for the reply + REQUEST.set('body_is_html', True) + else: + REQUEST.set('body_is_html', False) + + REQUEST.set('body', body) + + # Return value of 'No Change' to UI + return '' + + + + + def http_setTemplate(self, REQUEST): """ Handler for user selecting a new template to use in a message @@ -836,8 +902,6 @@ * body : altered to be the content of the given tempalte """ template_name = REQUEST.get('template_name', None).decode('utf-8') - body_is_html = REQUEST.get('body_is_html', False) - body = REQUEST.get('body', None).decode('utf-8') if template_name: @@ -864,12 +928,14 @@ template_is_html = template['html'] # Is this an HTML template? Check the settings flag - if template['html']: + if template_is_html: # Set the body_is_html flag so that the correct editor is # used for the reply REQUEST.set('body_is_html', True) + else: + REQUEST.set('body_is_html', False) - REQUEST.set('body', template['body']) + REQUEST.set('body', body) return self.index_html(REQUEST) @@ -887,8 +953,8 @@ * body_is_html : gets set to the format the body is now in * body : altered so that it is in the correct format """ - body_is_html = REQUEST.get('body_is_html', False) - new_body_html = REQUEST.get('new_body_html', False) + body_is_html = int(REQUEST.get('body_is_html', False)) + new_body_html = int(REQUEST.get('new_body_html', False)) body = REQUEST.get('body', '').decode('utf-8') if body_is_html and not new_body_html: @@ -896,6 +962,7 @@ body = html2text(body) if not body_is_html and new_body_html: + print "Converting to html" # Convert from text to html body = self._makeHTML(body) @@ -1505,23 +1572,6 @@ return None - security.declarePublic('__bobo_traverse__') - def __bobo_traverse__(self, REQUEST=None, name=None): - """ Traversal hook to do http_ name mangling """ - - # First of all, try and mangle http_ in front of requests - ret = getattr(self, 'http_%s' % name, None) - if ret: return ret - - # Then try and obtain without mangling - ret = getattr(self, name, None) - if ret: return ret - else: - ret = self[name] - if ret: return ret - - return name - ######################################################################### # # Utility Functions Modified: MailManager/branches/RELENG_2_1/MailManager.py =================================================================== --- MailManager/branches/RELENG_2_1/MailManager.py 2006-04-17 12:05:46 UTC (rev 2913) +++ MailManager/branches/RELENG_2_1/MailManager.py 2006-04-17 14:04:03 UTC (rev 2914) @@ -94,7 +94,7 @@ # This should be updated each time the dataset changes. Read the developer # documentation for details about doing this. initialmversion = 'v2_1_0' -mversion = 'v2_1_2' +mversion = 'v2_1_3' url_pat = re.compile(r'(https?|ftp)://\S*[^,;:.!?)\]\'"\s]') @@ -2084,7 +2084,7 @@ notify_user=None, notify_group=None, mail_server='', mail_username='', mail_password='', default_category0='', default_category1='', default_category2='', - auto_reply=None, reply_text='', html_reply=None, + default_template=None, auto_reply=None, reply_text='', html_reply=None, signature=None, signature_text='', html_signature=None): """Adds or edits an email Account. @@ -2158,6 +2158,8 @@ sqv_default_category0=default_category0, sqv_default_category1=default_category1, sqv_default_category2=default_category2, + sqv_default_template=default_template, + sqv_set_default_template=self.sql_true, sqv_response_target=response_target, sqv_auto_reply= self._sql_bool(auto_reply), sqv_reply_text=reply_text, @@ -3227,6 +3229,11 @@ REQUEST.set('body_is_html', new_body_html) return self.Create(REQUEST) + security.declareProtected('View MailManager', 'getEditorForm') + def getEditorForm(self, data): + return self.Epoz(lang='en', name='body', data=data, + css='master_style_css', + style='width: 550px; height: 225px;') security.declarePublic('__bobo_traverse__') def __bobo_traverse__(self, REQUEST=None, name=None): Modified: MailManager/branches/RELENG_2_1/migrations/v2_1/__init__.py =================================================================== --- MailManager/branches/RELENG_2_1/migrations/v2_1/__init__.py 2006-04-17 12:05:46 UTC (rev 2913) +++ MailManager/branches/RELENG_2_1/migrations/v2_1/__init__.py 2006-04-17 14:04:03 UTC (rev 2914) @@ -53,7 +53,32 @@ # Update the mversion to show we are complete self.mversion = 'v2_1_2' +def migrate_v2_1_2_v2_1_3(self, migrationParams): + """ Move to multiple ruleset descriptions in the database + The previous release only had a single ruleset, entitled queuesystem. + We now need to move to using multiple rulesets and migrate existing + tickets between rulesets. Normally end users are only going to have + a single ruleset in use, which should be the latest revision of + queuesystem. + + Multiple rulesets in the same system would require some fixes still. + The account table would need a rsname attribute, and the ruleset + engine would need to load the ruleset from a ticket each time. Also, + difficulties arise when dealing with supporters over multiple + rulesets. + """ + + schema = migrationParams.get('schema', '') + create_tables = migrationParams.get('create_tables', False) + dbplatform = migrationParams.get('dbplatform', 'postgres') + + self.sql.migrateDefaultTemplate() + + # Update the mversion to show we are complete + self.mversion = 'v2_1_3' + + migrations = { 'v2_1_0' : { 'targetstate' : 'v2_1_1', @@ -65,4 +90,9 @@ 'overview' : 'Move to multiple ruleset descriptions in the database', 'method' : migrate_v2_1_1_v2_1_2, }, + 'v2_1_2' : { + 'targetstate' : 'v2_1_3', + 'overview' : 'Add on default template for accounts (#1464963)', + 'method' : migrate_v2_1_2_v2_1_3, + }, } Modified: MailManager/branches/RELENG_2_1/sql/v2_1/addAccount.zsql =================================================================== --- MailManager/branches/RELENG_2_1/sql/v2_1/addAccount.zsql 2006-04-17 12:05:46 UTC (rev 2913) +++ MailManager/branches/RELENG_2_1/sql/v2_1/addAccount.zsql 2006-04-17 14:04:03 UTC (rev 2914) @@ -2,7 +2,7 @@ title:Add a new account connection_id: mailmanager_db max_rows:0 -arguments:sqv_email sqv_assign_user sqv_assign_group sqv_assign_queue sqv_notify_user sqv_notify_group sqv_routing sqv_mail_server sqv_mail_username sqv_mail_password sqv_server_type sqv_default_priority sqv_default_category0 sqv_default_category1 sqv_default_category2 sqv_response_target sqv_auto_reply sqv_reply_text sqv_html_reply sqv_signature sqv_signature_text sqv_html_signature +arguments:sqv_email sqv_assign_user sqv_assign_group sqv_assign_queue sqv_notify_user sqv_notify_group sqv_routing sqv_mail_server sqv_mail_username sqv_mail_password sqv_server_type sqv_default_priority sqv_default_category0 sqv_default_category1 sqv_default_category2 sqv_response_target sqv_auto_reply sqv_reply_text sqv_html_reply sqv_signature sqv_signature_text sqv_html_signature sqv_default_template </dtml-comment> INSERT INTO <dtml-var schema>mm_account (email <dtml-if sqv_assign_user>, assign_user </dtml-if> @@ -26,6 +26,7 @@ <dtml-if sqv_signature>, signature </dtml-if> <dtml-if sqv_signature_text>, signature_text </dtml-if> <dtml-if sqv_html_signature>, html_signature </dtml-if> + <dtml-if sqv_default_template>, default_template </dtml-if> ) VALUES (<dtml-sqlvar sqv_email type="nb"> <dtml-if sqv_assign_user> @@ -91,4 +92,7 @@ <dtml-if sqv_html_signature> , <dtml-sqlvar sqv_html_signature type="string"> </dtml-if> + <dtml-if sqv_default_template> + , <dtml-sqlvar sqv_default_template type="string"> + </dtml-if> ) Modified: MailManager/branches/RELENG_2_1/sql/v2_1/editAccount.zsql =================================================================== --- MailManager/branches/RELENG_2_1/sql/v2_1/editAccount.zsql 2006-04-17 12:05:46 UTC (rev 2913) +++ MailManager/branches/RELENG_2_1/sql/v2_1/editAccount.zsql 2006-04-17 14:04:03 UTC (rev 2914) @@ -2,7 +2,7 @@ title:Edit an account connection_id: mailmanager_db max_rows:0 -arguments:sqv_email sqv_assign_user sqv_assign_group sqv_assign_queue sqv_notify_user sqv_notify_group sqv_routing sqv_mail_server sqv_mail_username sqv_mail_password sqv_server_type sqv_default_priority sqv_default_category0 sqv_default_category1 sqv_default_category2 sqv_response_target sqv_auto_reply sqv_reply_text sqv_html_reply sqv_signature sqv_signature_text sqv_html_signature sqv_tid_subject +arguments:sqv_email sqv_assign_user sqv_assign_group sqv_assign_queue sqv_notify_user sqv_notify_group sqv_routing sqv_mail_server sqv_mail_username sqv_mail_password sqv_server_type sqv_default_priority sqv_default_category0 sqv_default_category1 sqv_default_category2 sqv_response_target sqv_auto_reply sqv_reply_text sqv_html_reply sqv_signature sqv_signature_text sqv_html_signature sqv_tid_subject sqv_set_default_template sqv_default_template General method used to edit an account's details @@ -48,4 +48,5 @@ <dtml-if sqv_signature>, signature=<dtml-sqlvar sqv_signature type="string" optional> </dtml-if> <dtml-if sqv_signature_text>, signature_text=<dtml-sqlvar sqv_signature_text type="string" optional> </dtml-if> <dtml-if sqv_html_signature>, html_signature=<dtml-sqlvar sqv_html_signature type="string" optional> </dtml-if> + <dtml-if sqv_set_default_template>, default_template=<dtml-sqlvar sqv_default_template type="string"> </dtml-if> WHERE <dtml-sqltest sqv_email column="email" type="nb"> Modified: MailManager/branches/RELENG_2_1/sql/v2_1/finalTables.zsql =================================================================== --- MailManager/branches/RELENG_2_1/sql/v2_1/finalTables.zsql 2006-04-17 12:05:46 UTC (rev 2913) +++ MailManager/branches/RELENG_2_1/sql/v2_1/finalTables.zsql 2006-04-17 14:04:03 UTC (rev 2914) @@ -159,6 +159,7 @@ default_category0 <dtml-var sql_smalltext> NOT NULL DEFAULT '', default_category1 <dtml-var sql_smalltext> NOT NULL DEFAULT '', default_category2 <dtml-var sql_smalltext> NOT NULL DEFAULT '', + default_template <dtml-var sql_smalltext> NOT NULL DEFAULT '', response_target <dtml-var sql_interval>, auto_reply <dtml-var sql_boolean> DEFAULT <dtml-var sql_false>, tid_subject <dtml-var sql_boolean> DEFAULT <dtml-var sql_true>, Modified: MailManager/branches/RELENG_2_1/www/AccountSettings.zpt =================================================================== --- MailManager/branches/RELENG_2_1/www/AccountSettings.zpt 2006-04-17 12:05:46 UTC (rev 2913) +++ MailManager/branches/RELENG_2_1/www/AccountSettings.zpt 2006-04-17 14:04:03 UTC (rev 2914) @@ -97,166 +97,173 @@ <!-- if section in ('add', 'edit') --> <form accept-charset="utf-8" class="blockform" action="addOrEditAccount" method="post" tal:condition="python:section in ('add', 'edit')"> - <h2 i18n:translate="add_new_account" tal:condition="python:section == 'add'">Add New Account</h2> - <h2 i18n:translate="edit_account" tal:condition="python:section == 'edit'">Edit Account</h2> - <p> - <label for="email" tal:attributes="class python:test(options.has_key('flag_account_id'), 'error', None)" i18n:translate="email_address">Email Address</label> - <span tal:condition="python:section == 'add'" tal:omit-tag=""> - <input id="email" name="email" size="30" tal:attributes="tabindex tabindex/next; value email" /> - </span> - <span tal:condition="python:section == 'edit'" tal:omit-tag=""> - <input name="email" size="30" tal:attributes="tabindex tabindex/next; value email" type="hidden" /> - <span tal:replace="email">te...@ex...</span> - </span> - </p> - <p> - <label for="assign_user" tal:attributes="class python:test(options.has_key('flag_assign'), 'error', None)" i18n:translate="assign_to">Assign to User</label> - <select tal:attributes="tabindex tabindex/next" id="assign_user" name="assign_user"> - <option value="" i18n:translate="none">None</option> - <option tal:repeat="user here/listUsers" tal:attributes="value user/username; selected python:test(assign_user == user.username, 'selected', None)" tal:content="user/username">Administrator</option> - </select> - </p> - <p> - <label for="assign_group" tal:attributes="class python:test(options.has_key('flag_assign_group'), 'error', None)" i18n:translate="or_group">or Group</label> - <select tal:attributes="tabindex tabindex/next" id="assign_group" name="assign_group"> - <option value="" i18n:translate="none">None</option> - <option tal:repeat="grp here/sql/listGroups" tal:attributes="value grp/group_name; selected python:test(assign_group == grp.group_name, 1, None)" tal:content="grp/group_name">Sales team</option> - </select> - </p> - <p> - <label for="assign_queue" tal:attributes="class python:test(options.has_key('flag_assign_queue'), 'error', None)" i18n:translate="or_queue">or Queue</label> - <select tal:attributes="tabindex tabindex/next" id="assign_queue" name="assign_queue"> - <option value="" i18n:translate="none">None</option> - <option tal:repeat="que here/sql/listQueues" tal:attributes="value que/queue_name; selected python:test(assign_queue == que.queue_name, 'selected', None)" tal:content="que/queue_name">Support Queue</option> - </select> - </p> + <span tal:define="account python:here.account(email=email)[0]"> + <h2 i18n:translate="add_new_account" tal:condition="python:section == 'add'">Add New Account</h2> + <h2 i18n:translate="edit_account" tal:condition="python:section == 'edit'">Edit Account</h2> + <p> + <label for="email" tal:attributes="class python:test(options.has_key('flag_account_id'), 'error', None)" i18n:translate="email_address">Email Address</label> + <span tal:condition="python:section == 'add'" tal:omit-tag=""> + <input id="email" name="email" size="30" tal:attributes="tabindex tabindex/next; value email" /> + </span> + <span tal:condition="python:section == 'edit'" tal:omit-tag=""> + <input name="email" size="30" tal:attributes="tabindex tabindex/next; value email" type="hidden" /> + <span tal:replace="email">te...@ex...</span> + </span> + </p> + <p> + <label for="assign_user" tal:attributes="class python:test(options.has_key('flag_assign'), 'error', None)" i18n:translate="assign_to">Assign to User</label> + <select tal:attributes="tabindex tabindex/next" id="assign_user" name="assign_user"> + <option value="" i18n:translate="none">None</option> + <option tal:repeat="user here/listUsers" tal:attributes="value user/username; selected python:test(assign_user == user.username, 'selected', None)" tal:content="user/username">Administrator</option> + </select> + </p> + <p> + <label for="assign_group" tal:attributes="class python:test(options.has_key('flag_assign_group'), 'error', None)" i18n:translate="or_group">or Group</label> + <select tal:attributes="tabindex tabindex/next" id="assign_group" name="assign_group"> + <option value="" i18n:translate="none">None</option> + <option tal:repeat="grp here/sql/listGroups" tal:attributes="value grp/group_name; selected python:test(assign_group == grp.group_name, 1, None)" tal:content="grp/group_name">Sales team</option> + </select> + </p> + <p> + <label for="assign_queue" tal:attributes="class python:test(options.has_key('flag_assign_queue'), 'error', None)" i18n:translate="or_queue">or Queue</label> + <select tal:attributes="tabindex tabindex/next" id="assign_queue" name="assign_queue"> + <option value="" i18n:translate="none">None</option> + <option tal:repeat="que here/sql/listQueues" tal:attributes="value que/queue_name; selected python:test(assign_queue == que.queue_name, 'selected', None)" tal:content="que/queue_name">Support Queue</option> + </select> + </p> - <!-- Give notification option if assigned to either a user or a queue --> - <p> - <label i18n:translate="notification">Notification</label> - <span class="indentedonform"> - <input tal:attributes="tabindex tabindex/next; - checked python:test(notify_user, 'checked', None)" - id="notify_user" name="notify_user" type="checkbox" /> - <label for="notify_user" tal:attributes="class python:test(options.has_key('flag_notify_user'), 'error', None)" i18n:translate="notify_assigned">Notify assigned user by email when new messages arrive</label><br /> + <!-- Give notification option if assigned to either a user or a queue --> + <p> + <label i18n:translate="notification">Notification</label> + <span class="indentedonform"> + <input tal:attributes="tabindex tabindex/next; + checked python:test(notify_user, 'checked', None)" + id="notify_user" name="notify_user" type="checkbox" /> + <label for="notify_user" tal:attributes="class python:test(options.has_key('flag_notify_user'), 'error', None)" i18n:translate="notify_assigned">Notify assigned user by email when new messages arrive</label><br /> - <input tal:attributes="tabindex tabindex/next; - checked python:test(notify_group, 'checked', None)" - id="notify_group" name="notify_group" type="checkbox" /> - <label for="notify_group" tal:attributes="class python:test(options.has_key('flag_notify_group'), 'error', None)" i18n:translate="notify_group">Notify other group members when new messages arrive</label><br /> - </span> - </p> + <input tal:attributes="tabindex tabindex/next; + checked python:test(notify_group, 'checked', None)" + id="notify_group" name="notify_group" type="checkbox" /> + <label for="notify_group" tal:attributes="class python:test(options.has_key('flag_notify_group'), 'error', None)" i18n:translate="notify_group">Notify other group members when new messages arrive</label><br /> + </span> + </p> - <h2 i18n:translate="mail_source">Mail Source</h2> + <h2 i18n:translate="mail_source">Mail Source</h2> - <p><label for="routing#1" i18n:translate="routing_1" - tal:attributes="class python:test(options.has_key('flag_get_from_server'), 'error', None)"> - Routing #1 - </label> - <input type="radio" name="routing" id="mail_forwarded" value="forwarded" - tal:attributes="tabindex tabindex/next; checked python:test(routing == 'forwarded', 'checked', None)" /> - <strong i18n:translate="mail_forwarded"> - Incoming mail forwarded to Account - </strong> - </p> + <p><label for="routing#1" i18n:translate="routing_1" + tal:attributes="class python:test(options.has_key('flag_get_from_server'), 'error', None)"> + Routing #1 + </label> + <input type="radio" name="routing" id="mail_forwarded" value="forwarded" + tal:attributes="tabindex tabindex/next; checked python:test(routing == 'forwarded', 'checked', None)" /> + <strong i18n:translate="mail_forwarded"> + Incoming mail forwarded to Account + </strong> + </p> - <p><label for="routing#2" i18n:translate="routing_2" - tal:attributes="class python:test(options.has_key('flag_get_from_server'), 'error', None)"> - Routing #2 - </label> - <input type="radio" name="routing" id="get_from_server" value="get" - tal:attributes="tabindex tabindex/next; checked python:test(routing == 'get', 'checked', None)" /> - <strong i18n:translate="get_from_server"> - Get from server (fill in details below) - </strong> - </p> - <p> - <label for="mail_server" tal:attributes="class python:test(options.has_key('flag_mail_server'), 'error', None)" i18n:translate="mail_server">Mail Server</label> - <input id="mail_server" name="mail_server" size="30" tal:attributes="tabindex tabindex/next; value mail_server" /> - </p> - <p> - <label for="mail_username" tal:attributes="class python:test(options.has_key('flag_mail_username'), 'error', None)" i18n:translate="username">Username</label> - <input id="mail_username" name="mail_username" size="30" tal:attributes="tabindex tabindex/next; value mail_username" /> - </p> - <p> - <label for="mail_password" tal:attributes="class python:test(options.has_key('flag_mail_password'), 'error', None)" i18n:translate="password">Password</label> - <input id="mail_password" name="mail_password" size="30" type="password" tal:attributes="tabindex tabindex/next; value mail_password" /> - </p> - <p> - <label for="server_type" tal:attributes="class python:test(options.has_key('flag_server_type'), 'error', None)" i18n:translate="server_type">Server Type</label> - <select tal:attributes="tabindex tabindex/next" id="server_type" name="server_type"> - <option tal:attributes="selected python:test(server_type == 'POP3', 'selected', None)" value="POP3">POP3</option> - <option tal:attributes="selected python:test(server_type == 'IMAP', 'selected', None)" value="IMAP">IMAP</option> - <option tal:attributes="selected python:test(server_type == 'POP3 SSL', 'selected', None)" tal:condition="here/checkPOP3SSL" value="POP3 SSL">POP3 SSL</option> - <option tal:attributes="selected python:test(server_type == 'IMAP SSL', 'selected', None)" value="IMAP SSL">IMAP SSL</option> - </select> - </p> + <p><label for="routing#2" i18n:translate="routing_2" + tal:attributes="class python:test(options.has_key('flag_get_from_server'), 'error', None)"> + Routing #2 + </label> + <input type="radio" name="routing" id="get_from_server" value="get" + tal:attributes="tabindex tabindex/next; checked python:test(routing == 'get', 'checked', None)" /> + <strong i18n:translate="get_from_server"> + Get from server (fill in details below) + </strong> + </p> + <p> + <label for="mail_server" tal:attributes="class python:test(options.has_key('flag_mail_server'), 'error', None)" i18n:translate="mail_server">Mail Server</label> + <input id="mail_server" name="mail_server" size="30" tal:attributes="tabindex tabindex/next; value mail_server" /> + </p> + <p> + <label for="mail_username" tal:attributes="class python:test(options.has_key('flag_mail_username'), 'error', None)" i18n:translate="username">Username</label> + <input id="mail_username" name="mail_username" size="30" tal:attributes="tabindex tabindex/next; value mail_username" /> + </p> + <p> + <label for="mail_password" tal:attributes="class python:test(options.has_key('flag_mail_password'), 'error', None)" i18n:translate="password">Password</label> + <input id="mail_password" name="mail_password" size="30" type="password" tal:attributes="tabindex tabindex/next; value mail_password" /> + </p> + <p> + <label for="server_type" tal:attributes="class python:test(options.has_key('flag_server_type'), 'error', None)" i18n:translate="server_type">Server Type</label> + <select tal:attributes="tabindex tabindex/next" id="server_type" name="server_type"> + <option tal:attributes="selected python:test(server_type == 'POP3', 'selected', None)" value="POP3">POP3</option> + <option tal:attributes="selected python:test(server_type == 'IMAP', 'selected', None)" value="IMAP">IMAP</option> + <option tal:attributes="selected python:test(server_type == 'POP3 SSL', 'selected', None)" tal:condition="here/checkPOP3SSL" value="POP3 SSL">POP3 SSL</option> + <option tal:attributes="selected python:test(server_type == 'IMAP SSL', 'selected', None)" value="IMAP SSL">IMAP SSL</option> + </select> + </p> - <h2 i18n:translate="options">Options</h2> - <p> - <span metal:use-macro="container/macros/macros/default_priority"> - Default Priority - </span> - </p> + <h2 i18n:translate="options">Options</h2> + <p> + <span metal:use-macro="container/macros/macros/default_priority"> + Default Priority + </span> + </p> + <p> + <span metal:use-macro="container/macros/macros/default_template"> + Default Template + </span> + </p> - <p tal:define="current_categories python:(default_category0, default_category1, default_category2)" - tal:repeat="cat python:here.sql.listCategories(sqv_enabled=here.sql_truevar)"> - <label tal:attributes="for string:default_category${cat/id}" - tal:content="cat/label">Category</label> - <select tal:attributes="tabindex tabindex/next; - name string:default_category${cat/id}; - id string:default_category${cat/id}"> - <option tal:repeat="item python:here.sql.listCategoryChoices(sqv_category_id=cat.id)" - tal:attributes="selected python:test(item.choice == current_categories[cat.id], 'selected', None)" - tal:content="item/choice">Sales lead</option> - </select> - </p> + <p tal:define="current_categories python:(default_category0, default_category1, default_category2)" + tal:repeat="cat python:here.sql.listCategories(sqv_enabled=here.sql_truevar)"> + <label tal:attributes="for string:default_category${cat/id}" + tal:content="cat/label">Category</label> + <select tal:attributes="tabindex tabindex/next; + name string:default_category${cat/id}; + id string:default_category${cat/id}"> + <option tal:repeat="item python:here.sql.listCategoryChoices(sqv_category_id=cat.id)" + tal:attributes="selected python:test(item.choice == current_categories[cat.id], 'selected', None)" + tal:content="item/choice">Sales lead</option> + </select> + </p> - <p> - <label for="response_target" i18n:translate="response_target">Response Target</label> - <select tal:attributes="tabindex tabindex/next" id="respose_target" name="response_target"> - <option tal:repeat="seconds here/listResponseTimes" - tal:attributes="value seconds; - selected python:test(seconds == response_target, 'selected', None)" - tal:content="python:here.formatTargetEn(seconds)">3 days</option> - </select> - </p> + <p> + <label for="response_target" i18n:translate="response_target">Response Target</label> + <select tal:attributes="tabindex tabindex/next" id="respose_target" name="response_target"> + <option tal:repeat="seconds here/listResponseTimes" + tal:attributes="value seconds; + selected python:test(seconds == response_target, 'selected', None)" + tal:content="python:here.formatTargetEn(seconds)">3 days</option> + </select> + </p> - <p> - <label for="auto_reply" tal:attributes="class python:test(options.has_key('flag_auto_reply'), 'error', None)" i18n:translate="auto_reply">Auto Reply</label> - <input type="checkbox" id="auto_reply" name="auto_reply" value="checked" tal:attributes="tabindex tabindex/next; checked python:test(auto_reply, 'checked', None)" /> - </p> - <p> - <label for="reply_text" tal:attributes="class python:test(options.has_key('flag_reply_text'), 'error', None)" i18n:translate="auto_reply_text">Text for Auto Reply</label> - <textarea tal:attributes="tabindex tabindex/next" id="reply_text" name="reply_text" rows="6" cols="60" tal:content="reply_text | default">Thank you for your mail. It has been allocated ticket number %T. Please quote this number if you get in touch with us. + <p> + <label for="auto_reply" tal:attributes="class python:test(options.has_key('flag_auto_reply'), 'error', None)" i18n:translate="auto_reply">Auto Reply</label> + <input type="checkbox" id="auto_reply" name="auto_reply" value="checked" tal:attributes="tabindex tabindex/next; checked python:test(auto_reply, 'checked', None)" /> + </p> + <p> + <label for="reply_text" tal:attributes="class python:test(options.has_key('flag_reply_text'), 'error', None)" i18n:translate="auto_reply_text">Text for Auto Reply</label> + <textarea tal:attributes="tabindex tabindex/next" id="reply_text" name="reply_text" rows="6" cols="60" tal:content="reply_text | default">Thank you for your mail. It has been allocated ticket number %T. Please quote this number if you get in touch with us. -Best regards + Best regards -Customer Service Team</textarea> - </p> - <p class="indentedonform" i18n:translate="auto_reply_info"> - (%T will be replaced by the ticket number) - </p> - <p class="indentedonform"> - <input type="checkbox" id="html_reply" name="html_reply" value="checked" tal:attributes="tabindex tabindex/next; checked python:test(html_reply, 'checked', None)" /> - <strong tal:attributes="class python:test(options.has_key('flag_html_reply'), 'error', None)" i18n:translate="allow_html_tags_in_auto_reply">Allow HTML tags in auto reply</strong> - </p> - <p class="indentedonform"> - <input type="checkbox" id="use_signature" name="signature" value="checked" tal:attributes="tabindex tabindex/next; checked python:test(signature, 'checked', None)" /> - <strong tal:attributes="class python:test(options.has_key('flag_use_signature'), 'error', None)" i18n:translate="use_signature_label">Use Signature</strong> - </p> - <p> - <label for="signature_text" tal:attributes="class python:test(options.has_key('flag_signature_text'), 'error', None)" i18n:translate="signature_text">Signature Text</label> - <textarea tal:attributes="tabindex tabindex/next" tal:content="signature_text" id="signature_text" name="signature_text" rows="6" cols="60">Some signature text.</textarea> - </p> - <p class="indentedonform"> - <input type="checkbox" id="html_signature" name="html_signature" value="checked" tal:attributes="tabindex tabindex/next; checked python:test(html_signature, 'checked', None)" /> - <strong tal:attributes="class python:test(options.has_key('flag_html_signature'), 'error', None)" i18n:translate="allow_html">Allow HTML tags in signature</strong> - </p> - <p style="text-align:center;"> - <input type="hidden" name="section" tal:attributes="tabindex tabindex/next; value section" /> - <input tal:attributes="tabindex tabindex/next" value="Save" i18n:attributes="value" type="submit" /> - </p> + Customer Service Team</textarea> + </p> + <p class="indentedonform" i18n:translate="auto_reply_info"> + (%T will be replaced by the ticket number) + </p> + <p class="indentedonform"> + <input type="checkbox" id="html_reply" name="html_reply" value="checked" tal:attributes="tabindex tabindex/next; checked python:test(html_reply, 'checked', None)" /> + <strong tal:attributes="class python:test(options.has_key('flag_html_reply'), 'error', None)" i18n:translate="allow_html_tags_in_auto_reply">Allow HTML tags in auto reply</strong> + </p> + <p class="indentedonform"> + <input type="checkbox" id="use_signature" name="signature" value="checked" tal:attributes="tabindex tabindex/next; checked python:test(signature, 'checked', None)" /> + <strong tal:attributes="class python:test(options.has_key('flag_use_signature'), 'error', None)" i18n:translate="use_signature_label">Use Signature</strong> + </p> + <p> + <label for="signature_text" tal:attributes="class python:test(options.has_key('flag_signature_text'), 'error', None)" i18n:translate="signature_text">Signature Text</label> + <textarea tal:attributes="tabindex tabindex/next" tal:content="signature_text" id="signature_text" name="signature_text" rows="6" cols="60">Some signature text.</textarea> + </p> + <p class="indentedonform"> + <input type="checkbox" id="html_signature" name="html_signature" value="checked" tal:attributes="tabindex tabindex/next; checked python:test(html_signature, 'checked', None)" /> + <strong tal:attributes="class python:test(options.has_key('flag_html_signature'), 'error', None)" i18n:translate="allow_html">Allow HTML tags in signature</strong> + </p> + <p style="text-align:center;"> + <input type="hidden" name="section" tal:attributes="tabindex tabindex/next; value section" /> + <input tal:attributes="tabindex tabindex/next" value="Save" i18n:attributes="value" type="submit" /> + </p> + </span> </form> <!-- end if section in ('add', 'edit') --> Modified: MailManager/branches/RELENG_2_1/www/macros.zpt =================================================================== --- MailManager/branches/RELENG_2_1/www/macros.zpt 2006-04-17 12:05:46 UTC (rev 2913) +++ MailManager/branches/RELENG_2_1/www/macros.zpt 2006-04-17 14:04:03 UTC (rev 2914) @@ -31,6 +31,15 @@ <option tal:attributes="selected python:test(pri == 1, 'selected', None)" value="1" i18n:translate="junk">Junk</option> </select> </p> + <!-- Call this with the account name set in the param email --> + <p metal:define-macro="default_template"> + <label for="default_template:string" tal:attributes="class python:test(request.has_key('flag_default_template:int'), 'error', None)" i18n:translate="default_template">Default Template</label> + <select tal:attributes="tabindex tabindex/next" id="default_template:string" name="default_template:string"> + <option value="" tal:attributes="selected python:test(account.default_template is None, 1, None)">None</option> + <option value="cite_last" tal:attributes="selected python:test('cite_last' == account.default_template, 1, None)">Cite Last Message</option> + <option tal:repeat="template here/sql/listTemplates" tal:attributes="value template/name; selected python:test(template.name == account.default_template, 1, None)" tal:content="template/name">Acknowledgement</option> + </select> + </p> <p metal:define-macro="response_target"> <label for="response_target" tal:attributes="class python:test(request.has_key('flag_response_target'), 'error', None)" i18n:translate="response_target">Response Target</label> <select tal:define="rt response_target | nothing" tal:attributes="tabindex tabindex/next" id="respose_target" name="response_target"> @@ -45,7 +54,7 @@ <label tal:attributes="for string:default_category$catid" tal:content="cat/label">Category</label> <span tal:define="cat_name string:default_category$catid; curr_cat python:getattr(ob_or_req, cat_name, None)" tal:omit-tag=""> <select tal:attributes="tabindex tabindex/next; name cat_name; id cat_name"> - <option tal:repeat="item python:here.sql.listCategoryChoices(sqv_category_id=catid)" tal:attributes="selected python:test(item['choice'] == curr_cat, 1, None)" tal:content="item/choice">Sales lead</option> + <option tal:repeat="item python:filter(lambda x: not x['choice'] == '', here.sql.listCategoryChoices(sqv_category_id=catid))" tal:attributes="selected python:test(item['choice'] == curr_cat, 1, None)" tal:content="item/choice">Sales lead</option> </select> </span> </span> Modified: MailManager/branches/RELENG_2_1/www/ticket_index_html.zpt =================================================================== --- MailManager/branches/RELENG_2_1/www/ticket_index_html.zpt 2006-04-17 12:05:46 UTC (rev 2913) +++ MailManager/branches/RELENG_2_1/www/ticket_index_html.zpt 2006-04-17 14:04:03 UTC (rev 2914) @@ -15,9 +15,15 @@ prevnext python:here.getPrevNext(request); prev_id python:prevnext[0]; next_id python:prevnext[1]; + template_name python:here.getDefaultTemplate(request); + body_is_html request/body_is_html | python:0; + body request/body | string:; global next_url string:; attachments request/SESSION/attachments | python:{}; dummy python:request.RESPONSE.setHeader('content-type', 'text/html;;charset=UTF-8')"> + <!-- Definition above double lists body_is_html, etc as they are mutated by + the getDefaultTemplate method, which updates request + --> <head metal:use-macro="container/master/macros/head"> <title metal:fill-slot="title"> Logicalware MailManager : Ticket <span tal:replace="python:'%06d' % here.id" /> @@ -256,10 +262,6 @@ tal:condition="python:here.state != 'Closed' and user.has_role('Tickets')" tal:attributes="action python:'ticket/%06d' % here.id" action="specifiedByTal" - tal:define="template_name python:unicode(request.get('template_name', ''), 'utf-8'); - template_html request/template_html | python:0; - body_is_html request/body_is_html | python:0; - body request/body | string:" method="post" id="response" enctype="multipart/form-data"> @@ -298,7 +300,6 @@ <p tal:condition="agent"> <label for="template_name" tal:attributes="class python:test(request.has_key('flag_template_name'), 'error', None)" i18n:translate="template_label">Template</label> <select tal:attributes="tabindex tabindex/next" name="template_name" id="template_name"> - <option tal:attributes="value template_name" i18n:translate="no_change">No Change</option> <option value="cite_last" i18n:translate="cite_last">Cite last message</option> <option tal:repeat="tplt python:here.sql.listTemplates()" tal:content="tplt/name">Some other template.</option> </select> @@ -311,6 +312,7 @@ <!-- Format --> <p> <label for="body_is_html:int" tal:attributes="class python:test(request.has_key('flag_body_is_html:int'), 'error', None)" i18n:translate="format_label">Format</label> + <input type="hidden" name="body_is_html" tal:attributes="value python:int(body_is_html)"/> <select tal:define="html_required here/HTMLRequired" tal:attributes="tabindex tabindex/next" id="new_body_html:int" name="new_body_html:int"> <option value="0" tal:condition="not:html_required" tal:attributes="selected python:test(body_is_html, None, 1)" i18n:translate="plain_text">Plain text</option> <option value="1" tal:attributes="selected python:test(body_is_html, 1, None)" i18n:translate="html">HTML</option> @@ -351,11 +353,13 @@ <!-- Body --> <p> <textarea tal:attributes="tabindex tabindex/next" tal:condition="not:body_is_html" name="body" rows="16" cols="65" tal:content="body">Non-HTML template.</textarea> - <span tal:condition="body_is_html" tal:replace="structure python:here.Epoz(lang='en', name='body', data=body, css='master_style_css', style='width: 550px; height: 225px;')"> - An (EPOZ) text area containing HTML template. + <span tal:condition="body_is_html" + tal:replace="structure python:here.getEditorForm(data=body)"> + An EPOZ text area containing HTML template. </span> </p> + <p> <input type="hidden" name="next_id:int" tal:condition="next_id" tal:attributes="tabindex tabindex/next; value next_id" /> <input type="hidden" name="offset:int" tal:condition="exists:request/offset" tal:attributes="tabindex tabindex/next; value request/offset" /> This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ke...@us...> - 2006-04-18 10:54:12
|
Revision: 2915 Author: kevca Date: 2006-04-18 03:53:52 -0700 (Tue, 18 Apr 2006) ViewCVS: http://svn.sourceforge.net/mailmanager/?rev=2915&view=rev Log Message: ----------- Fixes for - Setting template should return to template (#1464961) - Close button not listed (#1464954) Modified Paths: -------------- MailManager/branches/RELENG_2_1/CHANGES.txt MailManager/branches/RELENG_2_1/Extensions/TicketPluggableBrain.py MailManager/branches/RELENG_2_1/migrations/v2_1/__init__.py MailManager/branches/RELENG_2_1/ruleset/engine.py MailManager/branches/RELENG_2_1/www/ticket_index_html.zpt Added Paths: ----------- MailManager/branches/RELENG_2_1/ruleset/data/queuesystem_2_1_2.py Modified: MailManager/branches/RELENG_2_1/CHANGES.txt =================================================================== --- MailManager/branches/RELENG_2_1/CHANGES.txt 2006-04-17 14:04:03 UTC (rev 2914) +++ MailManager/branches/RELENG_2_1/CHANGES.txt 2006-04-18 10:53:52 UTC (rev 2915) @@ -1,5 +1,7 @@ Version 2.1-RC3 - Default template on accounts (#1464963) +- Setting template should return to template (#1464961) +- Close button not listed (#1464954) * BUG FIXES From 2.0.7 to 2.0.8 - Non existant refresh value leads to security error (#1466274) - Cannot create HTML tickets (#1466223) Modified: MailManager/branches/RELENG_2_1/Extensions/TicketPluggableBrain.py =================================================================== --- MailManager/branches/RELENG_2_1/Extensions/TicketPluggableBrain.py 2006-04-17 14:04:03 UTC (rev 2914) +++ MailManager/branches/RELENG_2_1/Extensions/TicketPluggableBrain.py 2006-04-18 10:53:52 UTC (rev 2915) @@ -483,7 +483,37 @@ event = 'AddNote', REQUEST=REQUEST, RESPONSE=RESPONSE) + security.declareProtected('MailManager Manage Tickets', 'http_Close') + def http_Close(self, mail_to, cc='', bcc='', subject='', body='', + body_is_html=0, user_signature=None, next_id=None, + offset=None, last_modified=None, + REQUEST=None, RESPONSE=None): + """ Close the ticket. + This method is a little larger than the previous wrappers as it + does not actually send any message, so the combined sendMethod + cannot be used. + """ + # First, check for any modifications to the ticket + if last_modified: + mdate = self.sql.getTicketLastModified( + sqv_ticket_id = self.id)[0].last_modified.strftime('%s') + if not last_modified == mdate: + REQUEST.set('error', 'This ticket has been modified. Please check it, and then retry your request') + return self.ticket_index_html(self, REQUEST) + + self.save(state='Closed', event='Close') + + # Redirect to the next ticket in the list, or just to the main + # tickets screen if no next ticket exists. + if RESPONSE is not None: + if next_id and offset: + RESPONSE.redirect('%s/ticket/%06d?offset:int=%d' % ( + self.getBaseURL(), next_id, offset)) + else: + RESPONSE.redirect('%s/Tickets' % self.getBaseURL()) + + def sendMethod(self, mail_to, cc='', bcc='', subject='', body='', body_is_html=False, user_signature=None, next_id=None, last_modified=None, offset=None, change_status=None, @@ -936,6 +966,7 @@ REQUEST.set('body_is_html', False) REQUEST.set('body', body) + REQUEST.set('autojump', True) return self.index_html(REQUEST) @@ -968,6 +999,8 @@ REQUEST.set('body', body.encode('utf-8')) REQUEST.set('body_is_html', new_body_html) + REQUEST.set('autojump', True) + return self.index_html(REQUEST) security.declarePrivate('_getTemplate') @@ -1053,6 +1086,7 @@ 'data': file_attach.read()} attachments[file_attach.filename] = file REQUEST.SESSION.set('attachments', attachments) + REQUEST.set('autojump', True) return self.ticket_index_html(self, REQUEST) security.declareProtected('MailManager Manage Tickets', 'delAttachment') Modified: MailManager/branches/RELENG_2_1/migrations/v2_1/__init__.py =================================================================== --- MailManager/branches/RELENG_2_1/migrations/v2_1/__init__.py 2006-04-17 14:04:03 UTC (rev 2914) +++ MailManager/branches/RELENG_2_1/migrations/v2_1/__init__.py 2006-04-18 10:53:52 UTC (rev 2915) @@ -54,19 +54,11 @@ self.mversion = 'v2_1_2' def migrate_v2_1_2_v2_1_3(self, migrationParams): - """ Move to multiple ruleset descriptions in the database + """ Add on default templates and close action - The previous release only had a single ruleset, entitled queuesystem. - We now need to move to using multiple rulesets and migrate existing - tickets between rulesets. Normally end users are only going to have - a single ruleset in use, which should be the latest revision of - queuesystem. - - Multiple rulesets in the same system would require some fixes still. - The account table would need a rsname attribute, and the ruleset - engine would need to load the ruleset from a ticket each time. Also, - difficulties arise when dealing with supporters over multiple - rulesets. + We add on an attribute to an account entitled default_template, which + automatically adds a template on ticket replies. We also update the + ruleset to add on a new Close action. """ schema = migrationParams.get('schema', '') @@ -75,6 +67,13 @@ self.sql.migrateDefaultTemplate() + # Load in additional ruleset and update engine + self.engine.setup(rsname = 'queuesystem_2_1_2') + + # All tickets should now be using this ruleset + for id in [t.id for t in self.sql.listTickets(sqv_rsname = 'queuesystem_2_1_1')]: + self.sql.editTicket(sqv_id = id, sqv_rsname = 'queuesystem_2_1_2') + # Update the mversion to show we are complete self.mversion = 'v2_1_3' Copied: MailManager/branches/RELENG_2_1/ruleset/data/queuesystem_2_1_2.py (from rev 2913, MailManager/branches/RELENG_2_1/ruleset/data/queuesystem_2_1_1.py) =================================================================== --- MailManager/branches/RELENG_2_1/ruleset/data/queuesystem_2_1_2.py (rev 0) +++ MailManager/branches/RELENG_2_1/ruleset/data/queuesystem_2_1_2.py 2006-04-18 10:53:52 UTC (rev 2915) @@ -0,0 +1,444 @@ +# +# Ruleset description for mailmanager2.1 with the queueing support added. +# This ruleset is basically a clone of the ruleset adding a new state +# (Queued), and dealing with some new events +# + +try: + import ruleset +except ImportError: + from Products.MailManager import ruleset + +events = [] +states = [ + 'Received', + 'Open', + 'Closed', + 'Hold', + 'Spam', + 'Overdue', + 'Queued' +] + +############################################################################# +# +# User Interface Methods +# + +# +# Viewing a ticket +# + +event = ruleset.events.UserInterfaceEvent('ViewTicket') + +for state in states: + event.addTransition( + state = state, + attrs = ['!Viewed'], + actions = [ruleset.actions.SetAttribute('Viewed')] + ) +events.append(event) + +# +# Sending a reply +# + +event = ruleset.events.UserInterfaceEvent('Send') +event.addTransition( + state = 'Open', + perms = 'User', + actions = [ruleset.actions.SetAttribute('Responded')] +) +event.addTransition( + state = 'Closed', + perms = 'User', + actions = [ruleset.actions.SetAttribute('Responded')] +) +events.append(event) + +# +# Adding a note +# + +event = ruleset.events.UserInterfaceEvent('AddNote') +event.addTransition( + state = 'Open', + perms = 'User' +) +event.addTransition( + state = 'Closed', + perms = 'User' +) +events.append(event) + +# +# Sending a reply +# + +event = ruleset.events.UserInterfaceEvent('SendReply') +event.addTransition( + state = 'Open', + attrs = [], + newstate = 'Hold', + perms = 'User', + actions = [ruleset.actions.SetAttribute('Responded')] +) +event.addTransition( + state = 'Closed', + attrs = [], + newstate = 'Closed', + perms = 'User', + actions = [ruleset.actions.SetAttribute('Responded')] +) +event.addTransition( + state = 'Overdue', + attrs = [], + newstate = 'Open', + perms = 'User', + actions = [ruleset.actions.SetAttribute('Responded')] +) +event.addTransition( + state = 'Hold', + attrs = [], + newstate = 'Hold', + perms = 'User', + actions = [ruleset.actions.SetAttribute('Responded')] +) +events.append(event) + +# +# Send and close +# + +event = ruleset.events.UserInterfaceEvent('SendAndClose') +event.addTransition( + state = 'Open', + attrs = ['!OpenChildren'], + newstate = 'Closed', + perms = 'User', + actions = [ruleset.actions.SetAttribute('Responded')] +) +event.addTransition( + state = 'Closed', + attrs = ['!OpenChildren'], + newstate = 'Closed', + perms = 'User', + actions = [ruleset.actions.SetAttribute('Responded')] +) +event.addTransition( + state = 'Overdue', + attrs = ['!OpenChildren'], + newstate = 'Closed', + perms = 'User', + actions = [ruleset.actions.SetAttribute('Responded')] +) +events.append(event) + +# +# Send and hold +# + +event = ruleset.events.UserInterfaceEvent('SendAndHold') +event.addTransition( + state = 'Open', + attrs = [], + newstate = 'Hold', + perms = 'User', + actions = [ruleset.actions.SetAttribute('Responded')] +) +event.addTransition( + state = 'Closed', + attrs = [], + newstate = 'Hold', + perms = 'User', + actions = [ruleset.actions.SetAttribute('Responded')] +) +event.addTransition( + state = 'Overdue', + attrs = [], + newstate = 'Hold', + perms = 'User', + actions = [ruleset.actions.SetAttribute('Responded')] +) +events.append(event) + +# +# Close +# + +event = ruleset.events.UserInterfaceEvent('Close') +event.addTransition( + state = 'Open', + attrs = ['!OpenChildren'], + newstate = 'Closed', + perms = 'User', +) +event.addTransition( + state = 'Closed', + attrs = ['!OpenChildren'], + newstate = 'Closed', + perms = 'User', +) +event.addTransition( + state = 'Hold', + attrs = ['!OpenChildren'], + newstate = 'Closed', + perms = 'User', +) +events.append(event) + + +# +# Get next ticket from queue +# + +event = ruleset.events.UserInterfaceEvent('GetNextTicketFromQueue') +event.addTransition( + state = 'Queued', + attrs = [], + newstate = 'Open', + perms = 'Other', + actions = [ruleset.actions.AssignUser('_$Current')] +) +events.append(event) + + +# +# Return ticket to queue +# + +event = ruleset.events.UserInterfaceEvent('ReturnToQueue') +event.addTransition( + state = 'Open', + attrs = ['_AssignQueue'], + newstate = 'Queued', + perms = 'User', + actions = [ruleset.actions.AssignUser('$QueueUser')] +) +event.addTransition( + state = 'Hold', + attrs = ['_AssignQueue'], + newstate = 'Queued', + perms = 'User', + actions = [ruleset.actions.AssignUser('$QueueUser')] +) +event.addTransition( + state = 'Closed', + attrs = ['_AssignQueue'], + newstate = 'Queued', + perms = 'User', + actions = [ruleset.actions.AssignUser('$QueueUser')] +) +event.addTransition( + state = 'Overdue', + attrs = ['_AssignQueue'], + newstate = 'Queued', + perms = 'User', + actions = [ruleset.actions.AssignUser('$QueueUser')] +) +events.append(event) + + +# # +# # Update Ticket Details +# # +# +# event = ruleset.events.UserInterfaceModify('UpdateTicketDetails') +# event.addTransition( +# state = 'Any', +# newstate = 'Any', +# perms = 'User', +# actions = 'Any' +# ) +# +# event = ruleset.events.UserInterfaceModify('UpdateTicketBatch') +# event.addTransition( +# state = 'Any', +# newstate = 'Any', +# perms = 'User', +# actions = 'Any' +# ) +# +# # +# # Delete User And Reassign +# # +# +# event = ruleset.events.UserInterfaceEvent('DeleteUserAndReassign') +# event.addTransition( +# state = 'Any', +# perms = 'User', +# actions = [ReassignTicket()] +# ) +# +# # +# # Restore Tickets +# # + + +############################################################################# +# +# getMail methods +# + +event = ruleset.events.GetMailEvent('AppendToTicket') +event.addTransition( + state = 'Closed', + attrs = ['Responded'], + newstate = 'Open', + actions = [ruleset.actions.AlertOwner('Opened'), ruleset.actions.AlertGroup('Opened')] +) +event.addTransition( + state = 'Closed', + newstate = 'Open', + attrs = ['!Responded'], + actions = [ruleset.actions.AlertOwner('Opened'), ruleset.actions.AlertGroup('Opened')] +) +event.addTransition( + state = 'Open', + newstate = 'Open', +) +event.addTransition( + state = 'Spam', + newstate = 'Spam', +) +event.addTransition( + state = 'Overdue', + newstate = 'Overdue', +) +events.append(event) + + + +############################################################################# +# +# Time based methods +# + +event = ruleset.events.TimeEvent('Overdue') +event.addTransition( + state = 'Open', + attrs = ['!Responded'], + newstate = 'Overdue', + actions = [ruleset.actions.AlertOwner('Overdue'), ruleset.actions.AlertGroup('Overdue')] +) +events.append(event) + + +event = ruleset.events.TimeEvent('AutoLogout') +event.addTransition( + state = 'Open', + attrs = ['_AssignQueue'], + newstate = 'Queued', + perms = 'User', + actions = [ruleset.actions.AssignUser('$QueueUser')] +) +event.addTransition( + state = 'Hold', + attrs = ['_AssignQueue'], + newstate = 'Queued', + perms = 'User', + actions = [ruleset.actions.AssignUser('$QueueUser')] +) +event.addTransition( + state = 'Closed', + attrs = ['_AssignQueue'], + newstate = 'Queued', + perms = 'User', + actions = [ruleset.actions.AssignUser('$QueueUser')] +) +event.addTransition( + state = 'Overdue', + attrs = ['_AssignQueue'], + newstate = 'Queued', + perms = 'User', + actions = [ruleset.actions.AssignUser('$QueueUser')] +) +events.append(event) + + +############################################################################# +# +# Epsilon Transitions +# + +event = ruleset.events.EpsilonEvent('Epsilon') + +# Spam goes direct to the spam state +event.addTransition( + state = 'Received', + attrs = ['Spam'], + newstate = 'Spam', + actions = [] +) + +# Queue any tickets which are assigned to a queue +event.addTransition( + state = 'Received', + attrs = ['_AssignQueue'], + newstate = 'Queued', + actions = [ruleset.actions.AssignUser('$QueueUser')] +) + +# Tickets assigned to a user or a group should go straight to the Open state +# Don't reassign the ticket to any user, as the AccountPluggableBrain will +# currently do that from the assignment engine (this will be replaced at +# a later stage, merging the assignment engine with the queueing rules) +event.addTransition( + state = 'Received', + attrs = ['!_AssignQueue'], + newstate = 'Open', +) +events.append(event) + +# +# +############################################################################# + + +ruleset = { + 'eventnames' : [ + 'Epsilon', # Internal + 'Overdue', # Overdue Event + 'ViewTicket', # User Interface + 'Send', # User Interface + 'Close', # User Interface + 'AddNote', # User Interface + 'SendReply', # User Interface + 'SendAndClose', # User Interface + 'SendAndHold', # User Interface + 'UpdateTicketDetails', # User Interface + 'UpdateTicketBatch', # User Interface + 'DeleteUserAndReassign', # User Interface + 'RestoreTickets', # User Interface + 'ReturnToQueue', # User Interface + 'GetNextTicketFromQueue', # User Interface + 'AppendToTicket', # Get Mail + 'AutoLogout', # Timed Event + ], + + 'actions' : [ + 'AlertOwner', + 'AlertGroup', + 'AlertSender', + 'SetAttribute', + 'UnsetAttribute', + 'ReassignTicket', + ], + + 'states' : states, + + 'attributes' : [ + 'OpenChildren', + 'Responded', + 'Viewed', + 'Spam' + ], + + 'categories' : [ + 'sales', + 'support' + ], + + 'events' : events +} + Modified: MailManager/branches/RELENG_2_1/ruleset/engine.py =================================================================== --- MailManager/branches/RELENG_2_1/ruleset/engine.py 2006-04-17 14:04:03 UTC (rev 2914) +++ MailManager/branches/RELENG_2_1/ruleset/engine.py 2006-04-18 10:53:52 UTC (rev 2915) @@ -131,9 +131,8 @@ self.setAttribute(*params, **kw) def _checkTransitions(self, transitions, ticket): - """ Check if an of the given transitions appliy to a ticket - - Return the transition if it does + """ Check the given list of transitions, and see which apply to + the given ticket. Returns a list of transitions which apply. """ acttrans = None for transition in transitions: Modified: MailManager/branches/RELENG_2_1/www/ticket_index_html.zpt =================================================================== --- MailManager/branches/RELENG_2_1/www/ticket_index_html.zpt 2006-04-17 14:04:03 UTC (rev 2914) +++ MailManager/branches/RELENG_2_1/www/ticket_index_html.zpt 2006-04-18 10:53:52 UTC (rev 2915) @@ -15,6 +15,7 @@ prevnext python:here.getPrevNext(request); prev_id python:prevnext[0]; next_id python:prevnext[1]; + autojump python:int(request.get('autojump', False)); template_name python:here.getDefaultTemplate(request); body_is_html request/body_is_html | python:0; body request/body | string:; @@ -368,11 +369,18 @@ <input tal:attributes="tabindex tabindex/next" tal:condition="python:user.has_role('Tickets') and here.engine.hasTransition(here, 'SendAndClose')" type="submit" value="Send & Close" name="sendAndClose:method" i18n:attributes="value" /> <input tal:attributes="tabindex tabindex/next" tal:condition="python:user.has_role('Tickets') and here.engine.hasTransition(here, 'SendAndHold')" type="submit" value="Send & Hold" name="sendAndHold:method" i18n:attributes="value" /> <input tal:attributes="tabindex tabindex/next" tal:condition="python:user.has_role('Tickets') and here.engine.hasTransition(here, 'ReturnToQueue')" type="submit" value="Return to Queue" name="returnToQueue:method" i18n:attributes="value" /> + <input tal:attributes="tabindex tabindex/next" tal:condition="python:user.has_role('Tickets') and here.engine.hasTransition(here, 'Close')" type="submit" value="Close" name="Close:method" i18n:attributes="value" /> </p> </form> <!-- End form to add message. --> + <script tal:condition="autojump" language='JavaScript'> + <!-- + window.location.href=window.location.href + '#template_name'; + //--> + </script> + </div> <!-- End main_content slot. --> This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ke...@us...> - 2006-04-18 13:19:34
|
Revision: 2919 Author: kevca Date: 2006-04-18 06:19:16 -0700 (Tue, 18 Apr 2006) ViewCVS: http://svn.sourceforge.net/mailmanager/?rev=2919&view=rev Log Message: ----------- - Restrict incoming emails with cc's to other accts to 1 tckt (#1464959) Modified Paths: -------------- MailManager/branches/RELENG_2_1/CHANGES.txt MailManager/branches/RELENG_2_1/Extensions/AccountPluggableBrain.py Modified: MailManager/branches/RELENG_2_1/CHANGES.txt =================================================================== --- MailManager/branches/RELENG_2_1/CHANGES.txt 2006-04-18 12:20:27 UTC (rev 2918) +++ MailManager/branches/RELENG_2_1/CHANGES.txt 2006-04-18 13:19:16 UTC (rev 2919) @@ -2,6 +2,7 @@ - Default template on accounts (#1464963) - Setting template should return to template (#1464961) - Close button not listed (#1464954) +- Restrict incoming emails with cc's to other accts to 1 tckt (#1464959) * BUG FIXES From 2.0.7 to 2.0.8 - Non existant refresh value leads to security error (#1466274) - Cannot create HTML tickets (#1466223) Modified: MailManager/branches/RELENG_2_1/Extensions/AccountPluggableBrain.py =================================================================== --- MailManager/branches/RELENG_2_1/Extensions/AccountPluggableBrain.py 2006-04-18 12:20:27 UTC (rev 2918) +++ MailManager/branches/RELENG_2_1/Extensions/AccountPluggableBrain.py 2006-04-18 13:19:16 UTC (rev 2919) @@ -90,6 +90,15 @@ # logicalware community site. # + self.logFlowPointEntry('process', 'find duplicate message_id') + have_duplicate = self._findDuplicateMessageId(msg) + self.logFlowPointExit('process', 'find duplicate message_id') + if have_duplicate: + # Discard duplicate message + zLOG.LOG('MailManager:', zLOG.INFO, + '%sprocess: Duplicate copy of message with id: %s' % (self.getLogName(), msg.get('message-id','Undefined'))) + return + # check if message relates to an existing ticket ###################### self.logFlowPointEntry('process', 'find referenced ticket') ticket_id = self._findReferencedTicket(msg) @@ -360,6 +369,23 @@ return result[0].ticket_id return None + security.declarePrivate('_findDuplicateMessageId') + def _findDuplicateMessageId(self, msg): + """ Checks to see if a message already exists with the given Mesage Id + + Returns a boolean value. + If the message has no message-id, return False + """ + message_id = msg.get('message-id', None) + if message_id: + result = self.sql.listMessages(sqv_message_id=message_id) + if len(result) > 0: + return True + else: + return False + else: + return False + security.declarePrivate('_assignmentEngine') def _assignmentEngine(self, filter_user, filter_group, filter_queue): """Work our which user a new ticket should be assigned to.""" This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ke...@us...> - 2006-04-18 15:27:43
|
Revision: 2922 Author: kevca Date: 2006-04-18 08:27:33 -0700 (Tue, 18 Apr 2006) ViewCVS: http://svn.sourceforge.net/mailmanager/?rev=2922&view=rev Log Message: ----------- Fixed - Return to Queue does not return to queues list (#1464947) Modified Paths: -------------- MailManager/branches/RELENG_2_1/CHANGES.txt MailManager/branches/RELENG_2_1/Extensions/TicketPluggableBrain.py Modified: MailManager/branches/RELENG_2_1/CHANGES.txt =================================================================== --- MailManager/branches/RELENG_2_1/CHANGES.txt 2006-04-18 14:23:23 UTC (rev 2921) +++ MailManager/branches/RELENG_2_1/CHANGES.txt 2006-04-18 15:27:33 UTC (rev 2922) @@ -3,6 +3,7 @@ - Setting template should return to template (#1464961) - Close button not listed (#1464954) - Restrict incoming emails with cc's to other accts to 1 tckt (#1464959) +- Return to Queue does not return to queues list (#1464947) * BUG FIXES From 2.0.7 to 2.0.8 - Non existant refresh value leads to security error (#1466274) - Cannot create HTML tickets (#1466223) Modified: MailManager/branches/RELENG_2_1/Extensions/TicketPluggableBrain.py =================================================================== --- MailManager/branches/RELENG_2_1/Extensions/TicketPluggableBrain.py 2006-04-18 14:23:23 UTC (rev 2921) +++ MailManager/branches/RELENG_2_1/Extensions/TicketPluggableBrain.py 2006-04-18 15:27:33 UTC (rev 2922) @@ -814,15 +814,16 @@ msg) - security.declareProtected('MailManager Manage Tickets', 'returnToQueue') - def returnToQueue(self, mail_to, cc='', bcc='', subject='', body='', - body_is_html=0, user_signature=None, sendmail=0, - last_modified=None, REQUEST=None, RESPONSE=None): - """Add a copy of a message to a ticket. + security.declareProtected('MailManager Manage Tickets', 'http_returnToQueue') + def http_returnToQueue(self, mail_to, cc='', bcc='', subject='', + body='', body_is_html=0, user_signature=None, + sendmail=0, next_id=None, last_modified=None, + offset=None, REQUEST=None, RESPONSE=None): + """ Returns the current ticket to the queue - FIXME: RULESETMODS - This needs replaced higher up so that it ties in with the ruleset - engine instead. + This method returns the current ticket to the queue. It then + redirects to the next ticket, or to the tickets list should + no next ticket exist. """ # First, check for any modifications to the ticket if last_modified: @@ -845,9 +846,13 @@ print "No transition made" transitions = False + # Redirect to next ticket or to the tickets list if RESPONSE is not None: - return RESPONSE.redirect('%s/ticket/%06d' % (self.getBaseURL(), - self.id)) + if next_id is not None and offset is not None: + RESPONSE.redirect('%s/ticket/%06d?offset:int=%d' % ( + self.getBaseURL(), next_id, offset)) + else: + RESPONSE.redirect('%s/Tickets' % self.getBaseURL()) def getDefaultTemplate(self, REQUEST): This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ke...@us...> - 2006-04-18 15:28:55
|
Revision: 2923 Author: kevca Date: 2006-04-18 08:28:43 -0700 (Tue, 18 Apr 2006) ViewCVS: http://svn.sourceforge.net/mailmanager/?rev=2923&view=rev Log Message: ----------- Changes for deviations between 2.0 and 2.1 for the fix for - Archive and Restore functionality broken (#1452514) Modified Paths: -------------- MailManager/branches/RELENG_2_1/MMImportHandler.py MailManager/branches/RELENG_2_1/MailManager.py MailManager/branches/RELENG_2_1/tests/testAPI.py MailManager/branches/RELENG_2_1/tests/testExporting.py Modified: MailManager/branches/RELENG_2_1/MMImportHandler.py =================================================================== --- MailManager/branches/RELENG_2_1/MMImportHandler.py 2006-04-18 15:27:33 UTC (rev 2922) +++ MailManager/branches/RELENG_2_1/MMImportHandler.py 2006-04-18 15:28:43 UTC (rev 2923) @@ -51,6 +51,11 @@ raise NotImplementedError("Error - No current support for attachments with MS SQL") else: raise NotImplementedError("Error - Support for attachments does not exist on this database platform") + + # Name mangle the row, before it gets passed to the sql methods + for key in self.row.keys(): + self.row['sqv_%s' % key] = self.row[key] + if name == 'ticket': self.sql.addTicket(self.row) zLOG.LOG(subsystem='MailManager', Modified: MailManager/branches/RELENG_2_1/MailManager.py =================================================================== --- MailManager/branches/RELENG_2_1/MailManager.py 2006-04-18 15:27:33 UTC (rev 2922) +++ MailManager/branches/RELENG_2_1/MailManager.py 2006-04-18 15:28:43 UTC (rev 2923) @@ -2831,11 +2831,11 @@ security.declareProtected('MailManager Settings', 'http_deleteTickets') def http_deleteTickets(self, to_date, account_id, section, REQUEST, RESPONSE, - status='', category0='', category1='', + state='', category0='', category1='', category2='', archive=False): """Delete selected tickets.""" if section == 'delete': - if status is None: + if state is None: REQUEST.set('error', 'You must select at least one state.') REQUEST.set('section', 'delete') else: @@ -2844,19 +2844,10 @@ if archive: f = self.archiveTickets(to_date = to_date, - account_id = account_id, status = status, + account_id = account_id, state = state, category0 = category0, category1 = category1, category2 = category2) - # In all cases we need to delete the tickets - self.sql.deleteTickets(sqv_to_date=to_date, - sqv_account_id=account_id, - sqv_state=state, - sqv_category0=category0, - sqv_category1=category1, - sqv_category2=category2) - - if archive: # Return the archive file RESPONSE.setHeader('Content-Type', 'text/xml') RESPONSE.setHeader('Content-Disposition', @@ -2868,7 +2859,7 @@ # In all cases we need to delete the tickets self.deleteTickets(to_date=to_date, account_id=account_id, - status=status, + state=state, category0=category0, category1=category1, category2=category2) @@ -2879,20 +2870,35 @@ security.declareProtected('MailManager Settings', 'deleteTickets') - def deleteTickets(self, to_date, account_id, status='', + def deleteTickets(self, to_date, account_id, state='', category0='', category1='', category2=''): """ Delete selected tickets. """ - # In all cases we need to delete the tickets - self.sql.deleteTickets(to_date=to_date.ISO8601(), - account_id=account_id, - status=status, - category0=category0, - category1=category1, - category2=category2) + # First find the affected tickets + res = self.sql.listTickets(sqv_to_date=to_date.ISO8601(), + sqv_account_id=account_id, + sqv_state=state, + sqv_category0=category0, + sqv_category1=category1, + sqv_category2=category2) + + # Remove the queued time events + for ticket in res: + self.sql.deleteRulesetTimeEvents( + sqv_tid = ticket.id + ) + + # Now remove the tickets + self.sql.deleteTickets(sqv_to_date=to_date.ISO8601(), + sqv_account_id=account_id, + sqv_state=state, + sqv_category0=category0, + sqv_category1=category1, + sqv_category2=category2) + security.declareProtected('MailManager Settings', 'archiveTickets') - def archiveTickets(self, to_date, account_id, status='', + def archiveTickets(self, to_date, account_id, state='', category0='', category1='', category2=''): """ Archive selected tickets. @@ -2902,22 +2908,22 @@ exp = XMLGenerator(f, 'utf-8') exp.startDocument() exp.startElement('mailmanager', {}) - tickets=self.sql.listTickets(to_date=to_date.ISO8601(), - account_id=account_id, - status=status, - category0=category0, - category1=category1, - category2=category2) + tickets=self.sql.listTickets(sqv_to_date=to_date.ISO8601(), + sqv_account_id=account_id, + sqv_state=state, + sqv_category0=category0, + sqv_category1=category1, + sqv_category2=category2) for ticket in tickets: self.row2xml(exp, 'ticket', tickets.names(), ticket) - messages = self.sql.listMessages(ticket_id=ticket.id) + messages = self.sql.listMessages(sqv_ticket_id=ticket.id) for message in messages: self.row2xml(exp, 'message', messages.names(), message) - attach = self.sql.listAttachments(message_id=message.id, - include_body=True) + attach = self.sql.listAttachments(sqv_message_id=message.id, + sqv_include_body=True) for att in attach: self.row2xml(exp, 'attachment', attach.names(), att) - history = self.sql.listHistory(ticket_id=ticket.id) + history = self.sql.listHistory(sqv_ticket_id=ticket.id) for hist in history: self.row2xml(exp, 'history', history.names(), hist) exp.endElement('mailmanager') Modified: MailManager/branches/RELENG_2_1/tests/testAPI.py =================================================================== --- MailManager/branches/RELENG_2_1/tests/testAPI.py 2006-04-18 15:27:33 UTC (rev 2922) +++ MailManager/branches/RELENG_2_1/tests/testAPI.py 2006-04-18 15:28:43 UTC (rev 2923) @@ -181,18 +181,21 @@ # method: createTicket self.mmobj.createTicket( + account_name = 'enquiries@acmewidgets.example', + title = 'This is a test', + body = 'This is a test', + body_is_html = False, + from_name = 'Test Person', + from_email = 'test@test.example', assigned = 'kev', priority = 1, response_target = 3600, - account_name = 'enquiries@acmewidgets.example', - from_name = 'Test Person', - from_email = 'test@test.example', - title = 'This is a test', - body = 'This is a test', - category0=None, - category1=None, - category2=None, - support_of=None, + category0 = None, + category1 = None, + category2 = None, + support_of = None, + ticket_type = 'incoming', + user_signature = None, attach = {}, remote_addr = None ) Modified: MailManager/branches/RELENG_2_1/tests/testExporting.py =================================================================== --- MailManager/branches/RELENG_2_1/tests/testExporting.py 2006-04-18 15:27:33 UTC (rev 2922) +++ MailManager/branches/RELENG_2_1/tests/testExporting.py 2006-04-18 15:28:43 UTC (rev 2923) @@ -78,7 +78,7 @@ self.mmobj.wipeDataset('acmestatic') self.mmobj.populateAccounts('acmestatic') - print self.mmobj.sql.listTickets(count=1)[0].count + print self.mmobj.sql.listTickets(sqv_count=1)[0].count self.importTest() This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ke...@us...> - 2006-04-18 17:46:46
|
Revision: 2924 Author: kevca Date: 2006-04-18 10:46:35 -0700 (Tue, 18 Apr 2006) ViewCVS: http://svn.sourceforge.net/mailmanager/?rev=2924&view=rev Log Message: ----------- Adds in mass updates. Not a particularly great implementation, but anything deeper will require quite a lot of consideration. For now, this works alongside existing 2.0 functionality. Mass ticket updates not implemented (#1464953) Modified Paths: -------------- MailManager/branches/RELENG_2_1/CHANGES.txt MailManager/branches/RELENG_2_1/MailManager.py MailManager/branches/RELENG_2_1/ruleset/data/queuesystem_2_1_2.py MailManager/branches/RELENG_2_1/ruleset/engine.py MailManager/branches/RELENG_2_1/ruleset/events.py MailManager/branches/RELENG_2_1/www/Queues.zpt Modified: MailManager/branches/RELENG_2_1/CHANGES.txt =================================================================== --- MailManager/branches/RELENG_2_1/CHANGES.txt 2006-04-18 15:28:43 UTC (rev 2923) +++ MailManager/branches/RELENG_2_1/CHANGES.txt 2006-04-18 17:46:35 UTC (rev 2924) @@ -1,9 +1,11 @@ Version 2.1-RC3 +* BUG FIXES - Default template on accounts (#1464963) - Setting template should return to template (#1464961) - Close button not listed (#1464954) - Restrict incoming emails with cc's to other accts to 1 tckt (#1464959) - Return to Queue does not return to queues list (#1464947) +- Mass ticket updates not implemented (#1464953) * BUG FIXES From 2.0.7 to 2.0.8 - Non existant refresh value leads to security error (#1466274) - Cannot create HTML tickets (#1466223) Modified: MailManager/branches/RELENG_2_1/MailManager.py =================================================================== --- MailManager/branches/RELENG_2_1/MailManager.py 2006-04-18 15:28:43 UTC (rev 2923) +++ MailManager/branches/RELENG_2_1/MailManager.py 2006-04-18 17:46:35 UTC (rev 2924) @@ -90,6 +90,13 @@ from Reporting import ReportingDataMixin, manage_addQueueReportingEngine from ruleset.zope import manage_addRulesetEngine +# Ruleset engine +from Products.MailManager.ruleset.common import NoTransitionError + +# Logging support +import logging +from Products.MailManager.support.logger import log + # Internal dataset revision # This should be updated each time the dataset changes. Read the developer # documentation for details about doing this. @@ -1809,22 +1816,89 @@ REQUEST.set('error', e) return self.index_html(REQUEST) + security.declareProtected('MailManager Manage Tickets', 'changeQueued') + def changeQueued(self, ticket_ids=[], assigned='', REQUEST=None): + """ Change all selected tickets in a batch. + This should be done solely through an event in the ruleset, but + for now this code is bypassing that slightly. + + @rulesetmethod: ChangeTicket + """ + for tid in ticket_ids: + + ticket = self.ticket(id=tid)[0] + user = getSecurityManager().getUser().getUserName() + try: + self.engine.processArbitraryEvent( + 'ChangeSelected', user, ticket, + assigned=assigned, state='Open', priority=None) + except NoTransitionError: + pass + + # Check for any time events and replace those which already exist + time = ticket.calculateOverdue() + if time: + log('%sQueueing (changeQueued) overdue event for ticket %i' % (self.getLogName(), ticket.id), + logging.INFO, 'ruleset.engine') + self.engine.clearTimeEvents('Overdue', ticket) + self.engine.queueTimeEvent('Overdue', ticket, time) + else: + self.engine.clearTimeEvents('Overdue', ticket) + + transitions = True + while transitions: + try: + self.engine.processEvent('Epsilon', None, ticket) + print "Transition made" + except NoTransitionError: + print "No transition made" + transitions = False + + if REQUEST is not None: + REQUEST.RESPONSE.redirect('%s/Queues' % self.getBaseURL()) + security.declareProtected('MailManager Manage Tickets', 'changeSelected') def changeSelected(self, ticket_ids=[], assigned='', state='', priority=0, REQUEST=None): - """Change all selected tickets in a batch. + """ Change all selected tickets in a batch. + + This should be done solely through an event in the ruleset, but + for now this code is bypassing that slightly. - @rulesetmethod: ChangeTicket + @rulesetmethod: ChangeTicket """ - raise NotImplementedError("RULESET 2.1 DEV") for tid in ticket_ids: - tick = self.ticket(id=tid)[0] - tick.save(subject=tick.subject, - assigned=assigned or tick.assigned, - state=state or tick.state, - priority=priority or tick.priority) + + ticket = self.ticket(id=tid)[0] + user = getSecurityManager().getUser().getUserName() + try: + self.engine.processArbitraryEvent( + 'ChangeSelected', user, ticket, + assigned=assigned, state=state, priority=priority) + except NoTransitionError: + pass + + # Check for any time events and replace those which already exist + time = ticket.calculateOverdue() + if time: + log('%sQueueing (changeQueued) overdue event for ticket %i' % (self.getLogName(), ticket.id), + logging.INFO, 'ruleset.engine') + self.engine.clearTimeEvents('Overdue', ticket) + self.engine.queueTimeEvent('Overdue', ticket, time) + else: + self.engine.clearTimeEvents('Overdue', ticket) + + transitions = True + while transitions: + try: + self.engine.processEvent('Epsilon', None, ticket) + print "Transition made" + except NoTransitionError: + print "No transition made" + transitions = False + if REQUEST is not None: REQUEST.RESPONSE.redirect('%s/Tickets' % self.getBaseURL()) Modified: MailManager/branches/RELENG_2_1/ruleset/data/queuesystem_2_1_2.py =================================================================== --- MailManager/branches/RELENG_2_1/ruleset/data/queuesystem_2_1_2.py 2006-04-18 15:28:43 UTC (rev 2923) +++ MailManager/branches/RELENG_2_1/ruleset/data/queuesystem_2_1_2.py 2006-04-18 17:46:35 UTC (rev 2924) @@ -26,6 +26,15 @@ # # +# Mass Updates +# This is a special case event. At present, we cannot configure or restrict +# it in any way. +# + +event = ruleset.events.MassChangeUIEvent('ChangeSelected') +events.append(event) + +# # Viewing a ticket # Modified: MailManager/branches/RELENG_2_1/ruleset/engine.py =================================================================== --- MailManager/branches/RELENG_2_1/ruleset/engine.py 2006-04-18 15:28:43 UTC (rev 2923) +++ MailManager/branches/RELENG_2_1/ruleset/engine.py 2006-04-18 17:46:35 UTC (rev 2924) @@ -189,6 +189,27 @@ return DateTime.DateTime(offsetdate.strftime('%Y-%m-%d %H:%M:%S')) + def processArbitraryEvent(self, eventname, user, ticket, + assigned=None, state=None, priority=None): + """ Process an event that allows an arbitrary modification to a ticket + + At present this is really just a stub. This needs to be able to + deal with transitions better. The arbitrary transition choices + mean that there would be a massive number of states in the + database. + """ + + # Update the sibling attribute: FIXME, this doesn't need calculated + # every time, and will be invariant over most sections of the code + ticket.updateChildAttribute() + + ticket.save(subject=ticket.subject, + assigned=assigned, + state=state, + priority=priority, + event=eventname) + + def processEvent(self, eventname, user, ticket): """ Find the action to take for the given event and apply it Modified: MailManager/branches/RELENG_2_1/ruleset/events.py =================================================================== --- MailManager/branches/RELENG_2_1/ruleset/events.py 2006-04-18 15:28:43 UTC (rev 2923) +++ MailManager/branches/RELENG_2_1/ruleset/events.py 2006-04-18 17:46:35 UTC (rev 2924) @@ -56,6 +56,10 @@ newstate=newstate, actions=actions) self.transitions.append(transition) +class MassChangeUIEvent(Event): + """ Special case event which includes all possible transitions """ + pass + class UserInterfaceEvent(Event): pass Modified: MailManager/branches/RELENG_2_1/www/Queues.zpt =================================================================== --- MailManager/branches/RELENG_2_1/www/Queues.zpt 2006-04-18 15:28:43 UTC (rev 2923) +++ MailManager/branches/RELENG_2_1/www/Queues.zpt 2006-04-18 17:46:35 UTC (rev 2924) @@ -38,7 +38,7 @@ </div> <div metal:fill-slot="main_content"> - <form accept-charset="utf-8" action="changeSelected" + <form accept-charset="utf-8" action="changeQueued" method="post" tal:omit-tag="not:results" tal:define="display_column here/display_column; categories python:here.sql.listCategories()"> @@ -49,27 +49,8 @@ <p class="noprint" tal:condition="python:results and user.has_role('Tickets')"> <label for="assigned" i18n:translate="assigned">Assigned</label> <select tal:attributes="tabindex tabindex/next" id="assigned" name="assigned"> - <option value="" i18n:translate="no_change">No Change</option> <option tal:repeat="user here/listUsers" tal:attributes="value user/username" tal:content="user/username">me</option> </select> - <label for="state" i18n:translate="status_label">Status</label> - <select tal:attributes="tabindex tabindex/next" id="state" name="state"> - <option value="" i18n:translate="no_change">No Change</option> - <option value="open" i18n:translate="open">Open</option> - <option value="hold" i18n:translate="hold">Hold</option> - <option value="closed" i18n:translate="closed">Closed</option> - <option value="spam" i18n:translate="spam">Spam</option> - </select> - - <label for="priority:int">Priority</label> - <select tal:attributes="tabindex tabindex/next" id="priority:int" name="priority:int"> - <option value="0" i18n:translate="no_change">No Change</option> - <option value="5" i18n:translate="critical">Critical</option> - <option value="4" i18n:translate="high">High</option> - <option value="3" i18n:translate="normal">Normal</option> - <option value="2" i18n:translate="low">Low</option> - <option value="1" i18n:translate="junk">Junk</option> - </select> <input tal:attributes="tabindex tabindex/next" type="submit" value="Change Selected" /> </p> </form> This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ke...@us...> - 2006-04-18 18:22:06
|
Revision: 2926 Author: kevca Date: 2006-04-18 11:21:57 -0700 (Tue, 18 Apr 2006) ViewCVS: http://svn.sourceforge.net/mailmanager/?rev=2926&view=rev Log Message: ----------- Fix for - Adding a customer fails (#1460922) Modified Paths: -------------- MailManager/branches/RELENG_2_1/CHANGES.txt MailManager/branches/RELENG_2_1/MailManager.py MailManager/branches/RELENG_2_1/version.txt Modified: MailManager/branches/RELENG_2_1/CHANGES.txt =================================================================== --- MailManager/branches/RELENG_2_1/CHANGES.txt 2006-04-18 18:06:13 UTC (rev 2925) +++ MailManager/branches/RELENG_2_1/CHANGES.txt 2006-04-18 18:21:57 UTC (rev 2926) @@ -6,6 +6,7 @@ - Restrict incoming emails with cc's to other accts to 1 tckt (#1464959) - Return to Queue does not return to queues list (#1464947) - Mass ticket updates not implemented (#1464953) +- Adding a customer fails (#1460922) * BUG FIXES From 2.0.7 to 2.0.8 - Non existant refresh value leads to security error (#1466274) - Cannot create HTML tickets (#1466223) Modified: MailManager/branches/RELENG_2_1/MailManager.py =================================================================== --- MailManager/branches/RELENG_2_1/MailManager.py 2006-04-18 18:06:13 UTC (rev 2925) +++ MailManager/branches/RELENG_2_1/MailManager.py 2006-04-18 18:21:57 UTC (rev 2926) @@ -2732,7 +2732,7 @@ error += 'For more details, please see the documentation or help pages' flag = 'access' else: - self.addOrEditCustomer(real_name, username, password, confirm, access, action, REQUEST) + self.addOrEditCustomer(real_name, username, password, confirm, access) else: # XXX edit user # Check the supplied email addresses aren't account email addresses @@ -2746,7 +2746,7 @@ error += 'For more details, please see the documentation or help pages' flag = 'access' else: - self.addOrEditCustomer(real_name, username, password, confirm, access, action, REQUEST) + self.addOrEditCustomer(real_name, username, password, confirm, access) if error is not None: REQUEST.set('error', error) @@ -2757,8 +2757,7 @@ return self.CustomerSettings(self, REQUEST) security.declareProtected('MailManager Settings', 'addOrEditCustomer') - def addOrEditCustomer(self, real_name, username, password, confirm, access, - section, REQUEST): + def addOrEditCustomer(self, real_name, username, password, confirm, access): """Add a customer.""" self.sql.deleteCustomer(sqv_username=username) self.sql.addCustomer(sqv_username=username, Modified: MailManager/branches/RELENG_2_1/version.txt =================================================================== --- MailManager/branches/RELENG_2_1/version.txt 2006-04-18 18:06:13 UTC (rev 2925) +++ MailManager/branches/RELENG_2_1/version.txt 2006-04-18 18:21:57 UTC (rev 2926) @@ -1 +1 @@ -2.1-rc2 +2.1-rc3 This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ke...@us...> - 2006-04-20 18:48:34
|
Revision: 2930 Author: kevca Date: 2006-04-20 11:48:20 -0700 (Thu, 20 Apr 2006) ViewCVS: http://svn.sourceforge.net/mailmanager/?rev=2930&view=rev Log Message: ----------- Fixes for +- Reports page is broken (#1472976) Modified Paths: -------------- MailManager/branches/RELENG_2_1/CHANGES.txt MailManager/branches/RELENG_2_1/Reporting.py MailManager/branches/RELENG_2_1/tests/testAPI.py MailManager/branches/RELENG_2_1/tests/testDatabase.py MailManager/branches/RELENG_2_1/tests/testQueueing.py MailManager/branches/RELENG_2_1/www/QueueReport.zpt Modified: MailManager/branches/RELENG_2_1/CHANGES.txt =================================================================== --- MailManager/branches/RELENG_2_1/CHANGES.txt 2006-04-19 14:23:56 UTC (rev 2929) +++ MailManager/branches/RELENG_2_1/CHANGES.txt 2006-04-20 18:48:20 UTC (rev 2930) @@ -7,6 +7,7 @@ - Return to Queue does not return to queues list (#1464947) - Mass ticket updates not implemented (#1464953) - Adding a customer fails (#1460922) +- Reports page is broken (#1472976) * BUG FIXES From 2.0.7 to 2.0.8 - Non existant refresh value leads to security error (#1466274) - Cannot create HTML tickets (#1466223) Modified: MailManager/branches/RELENG_2_1/Reporting.py =================================================================== --- MailManager/branches/RELENG_2_1/Reporting.py 2006-04-19 14:23:56 UTC (rev 2929) +++ MailManager/branches/RELENG_2_1/Reporting.py 2006-04-20 18:48:20 UTC (rev 2930) @@ -185,30 +185,30 @@ priorityVaccounts - accounts vs priority priorityVusers - users vs priority """ - if subsection == 'account': + if subsection == 'account': print "Getting account data" return self.sql.accountStatus( - sqv_report_from = report_from, - sqv_report_to = report_to + sqv_report_from = report_from.HTML4(), + sqv_report_to = report_to.HTML4() ) elif subsection == 'user': - return self.sql.userStatus( - sqv_report_from = report_from, - sqv_report_to = report_to - ) - return self.sql.userStatus() - elif subsection == 'priorityVusers': - return self.sql.userPriority( - sqv_report_from = report_from, - sqv_report_to = report_to - ) - elif subsection == 'priorityVaccounts': - return self.sql.accountPriority( - sqv_report_from = report_from, - sqv_report_to = report_to - ) + return self.sql.userStatus( + sqv_report_from = report_from.HTML4(), + sqv_report_to = report_to.HTML4() + ) return self.sql.userStatus() - + elif subsection == 'priorityVusers': + return self.sql.userPriority( + sqv_report_from = report_from.HTML4(), + sqv_report_to = report_to.HTML4() + ) + elif subsection == 'priorityVaccounts': + return self.sql.accountPriority( + sqv_report_from = report_from.HTML4(), + sqv_report_to = report_to.HTML4() + ) + return self.sql.userStatus() + security.declareProtected('MailManager Reports', 'getReceivedData') def getReceivedData(self, subsection, report_from, report_to): if subsection == 'account': @@ -221,10 +221,10 @@ security.declareProtected('MailManager Reports', 'getPerformanceData') def getPerformanceData(self, subsection, report_from, report_to): if subsection == 'account': - data = self.getGraphDetails(report_from = report_from, - report_to = report_to, account_id='', assigned='', + data = self.getGraphDetails(report_from = report_from, + report_to = report_to, account_id='', assigned='', category0='', category1='', category2='' ) - + results = [] for i in range(len(data['values'])): results.append((i, data['values'][i])) @@ -247,7 +247,7 @@ First return the maximum value to be displayed on the y-axis, then return a list of values for the bars. Values are scaled such that they are between 0 and 200 (for direct display). The report - dates aren't used exactly, and instead a month is used if the + dates aren't used exactly, and instead a month is used if the difference is 31 days or under, otherwise, years are used. What month/year to use is dependant only on the value of report_to. @@ -300,8 +300,8 @@ 'range': 'minmax'} val = self.sql.listTickets( sqv_count=1, - sqv_from_date=month_start, - sqv_to_date=month_end, + sqv_from_date=month_start.HTML4(), + sqv_to_date=month_end.HTML4(), sqv_account_id=account_id, sqv_assigned=assigned, sqv_category0=category0, @@ -322,8 +322,8 @@ print day_start, day_end val = self.sql.listTickets( sqv_count=1, - sqv_from_date=day_start, - sqv_to_date=day_end, + sqv_from_date=day_start.HTML4(), + sqv_to_date=day_end.HTML4(), sqv_account_id=account_id, sqv_assigned=assigned, sqv_category0=category0, @@ -348,17 +348,17 @@ class BaseReportingEngine(OFS.SimpleItem.Item, Persistent, OFS.PropertyManager.PropertyManager, - Acquisition.Implicit, + Acquisition.Implicit, AccessControl.Role.RoleManager): """ The main reporting engine processor - + This base reporting engine provides a HTML page with the sections - performance, snapshot, email received and other on it. It will + performance, snapshot, email received and other on it. It will then call the method generateReport to display the report information """ #zope.interface.implements(IMailManagerReportingData,) - + meta_type = 'Base Reporting Engine' security = ClassSecurityInfo() @@ -366,7 +366,7 @@ security.declareProtected('MailManager Reports', 'index_html') index_html = PageTemplateFile('www/Reports', globals()) - + __ac_roles__ = ('Tickets', 'Reports', 'Settings', 'Customer') security = ClassSecurityInfo() security.setPermissionDefault('View MailManager', @@ -379,8 +379,8 @@ ['Tickets', 'Customer']) security.setPermissionDefault('MailManager Manage Tickets', ['Tickets', 'Customer']) - + # Just for testing manage_options = OFS.PropertyManager.PropertyManager.manage_options + ( {'label': 'Security', 'action': 'manage_access'}, @@ -431,12 +431,14 @@ to_date = date_opened['query'][1] if account_id is not None: - results = self.sql.listTickets(from_date=from_date, - to_date=to_date, + results = self.sql.listTickets( + from_date=from_date.HTML4(), + to_date=to_date.HTML4(), account_id=account_id) if assigned is not None: - results = self.sql.listTickets(from_date=from_date, - to_date=to_date, + results = self.sql.listTickets( + from_date=from_date.HTML4(), + to_date=to_date.HTML4(), assigned=assigned) number_responses = 0 number_closes = 0 @@ -563,11 +565,11 @@ ('account', 'user', 'priorityVaccounts', 'priorityVusers'), 'received': ('account', 'user', 'category0', 'category1', 'category2'), - 'performance': + 'performance': ('account', 'user', 'targets'), - 'other': + 'other': ('email',), - 'queues': + 'queues': ('account', 'user', 'priorityVaccounts', 'priorityVusers'), } if subsection not in d[section]: @@ -577,9 +579,9 @@ security.declareProtected('MailManager Reports', 'generateReport') def generateReport(self, REQUEST=None, RESPONSE=None, section='', subsection='', report_from=None, report_to=None): - """ Generate a message saying this is the base reporting engine - - The base reporting engine only implements the structure around + """ Generate a message saying this is the base reporting engine + + The base reporting engine only implements the structure around navigating reports. It does not do any reporting of it's own. """ return "<b>This reporting engine does not generate any output</b>" @@ -615,7 +617,7 @@ ['Tickets', 'Customer']) security.setPermissionDefault('MailManager Manage Tickets', ['Tickets', 'Customer']) - + # Just for testing manage_options = OFS.PropertyManager.PropertyManager.manage_options + ( {'label': 'Security', 'action': 'manage_access'}, @@ -630,10 +632,10 @@ return [] security.declareProtected('MailManager Reports', 'getChart') - def getChart(self, section, subsection, report_from=None, report_to=None, + def getChart(self, section, subsection, report_from=None, report_to=None, REQUEST=None, RESPONSE=None): """ Generates a chart object using the methods in ZChart2d - + What chart is generated and the chart parameters are altered by the variables held in the user's session. """ @@ -652,7 +654,7 @@ elif section == 'other': pass - + security.declareProtected('MailManager Reports', 'report_page') report_page = PageTemplateFile('www/HTMLReport', globals()) @@ -661,10 +663,10 @@ """ Generate the report information This report engine generates a report in HTML using the report_page - page template. + page template. """ - return self.report_page(REQUEST=REQUEST, RESPONSE=RESPONSE, - section=section, subsection=subsection, + return self.report_page(REQUEST=REQUEST, RESPONSE=RESPONSE, + section=section, subsection=subsection, report_from=report_from, report_to=report_to) @@ -680,10 +682,10 @@ """ A Reporting Engine with added support for Queueing - This is based on the HTML reporting engine, which duplicated + This is based on the HTML reporting engine, which duplicated MailManager 2.0 functionality. It moves towards the intended design for the new reporting architecture, and adds in the ability to view - details about tickets held in the Queued state. + details about tickets held in the Queued state. The following statistics shall be calculated by this class and made available to the presentation logic. @@ -695,10 +697,10 @@ Per queue (per day/per month): * Number of tickets in the queued state in this interval (Avg/Max) * Throughput (tickets put into queue, tickets removed from queue) - * Time in queue for all tickets closed in this interval (Avg/Max) + * Time in queue for all tickets closed in this interval (Avg/Max) Each of the above stats will be split so that there is a statistic - for each number of times a ticket has been in the queue. + for each number of times a ticket has been in the queue. """ @@ -718,7 +720,7 @@ ['Tickets', 'Customer']) security.setPermissionDefault('MailManager Manage Tickets', ['Tickets', 'Customer']) - + # Just for testing manage_options = OFS.PropertyManager.PropertyManager.manage_options + ( {'label': 'Security', 'action': 'manage_access'}, @@ -731,24 +733,24 @@ def __init__(self, id, title): HTMLReportingEngine.__init__(self, id=id, title=title) - def getQueueStatus(self, type, queue, report_from=None, + def getQueueStatus(self, type, queue, report_from=None, report_to=None, period='daily'): """ Get statistics on a single queue - - - """ + @param from_date: DateTime.DateTime + @param to_date: DateTime.DateTime + """ if type.startswith('number_'): # Find the number of tickets assigned to this queue user at the - # start of the window. Then find all of the history items + # start of the window. Then find all of the history items # throughout the window, in order. Traversing this history, we # can calculate the number of queued tickets at any given point # of time tickets = self.sql.getHistoricalTickets( - sqv_date_from = report_from, - sqv_date_to = report_to, + sqv_date_from = report_from.HTML4(), + sqv_date_to = report_to.HTML4(), sqv_assigned = '_Qincoming' ) @@ -756,7 +758,7 @@ if type == 'number_avg': pass - + elif type == 'number_max': pass @@ -765,91 +767,93 @@ elif type == 'throughput_out': pass - + elif type == 'time_avg': pass - + elif type == 'time_max': pass def performanceByQueue(self, report_from, report_to, period): - """ + """ A quick method to obtain the following information on a per queue basis: * Number of tickets removed from the queue in the time period * Average time those tickets has been queued for in the period * Maximum time those tickets had been queued for in the period - + The results are all split by their queue count, ie: if a ticket had previously been in the queue, removed and requeued, then it will have a count of 1. + @param from_date: DateTime.DateTime + @param to_date: DateTime.DateTime """ - + ret = {} report_from = mx.DateTime.gmtime(report_from.timeTime()) report_to = mx.DateTime.gmtime(report_to.timeTime()) + mx.DateTime.RelativeDateTime(days=1) - print "Report from ", report_from - print "Report to ", report_to - for queue in self.sql.listQueues(): queuename = queue.queue_name - + ret[queuename] = {} + # Find all tickets which were in the given queue at some point over + # the given time window. + res = self.sql.getTicketWindow( sqv_from_date = report_from, sqv_to_date = report_to, sqv_assigned = '_Q' + queuename ) - + for ritem in res: - pprint(("Ticket ", ritem.ticket_id, ritem.change_date)) - + + pprint(("Ticket ", ritem.ticket_id, ritem.change_date, ritem.change_date.HTML4())) + subres = self.sql.listHistory( sqv_ticket_id = ritem.ticket_id, sqv_assigned = '_Q' + queuename, - sqv_to_date = ritem.change_date + sqv_to_date = ritem.change_date.HTML4() ) count = len(subres) - + subres = self.sql.listHistory( sqv_ticket_id = ritem.ticket_id, - sqv_to_date = ritem.change_date, + sqv_to_date = ritem.change_date.HTML4(), sqv_limit = 1, sqv_reverse_history = True ) last_date = subres[0].change_date - - + if not ret[queuename].has_key(count): ret[queuename][count] = [] ret[queuename][count].append({ - 'ticket_id' : ritem.ticket_id, - 'assigned' : ritem.assigned, - 'state' : ritem.state, + 'ticket_id' : ritem.ticket_id, + 'assigned' : ritem.assigned, + 'state' : ritem.state, 'change_date' : mx.DateTime.gmtime(ritem.change_date.timeTime()), - 'count' : count, + 'count' : count, 'last_date' : last_date, 'duration' : mx.DateTime.gmtime(ritem.change_date.timeTime()) - mx.DateTime.gmtime(last_date.timeTime()) } ) pprint(ret) - - results = {} + results = {} + for queuename in ret.keys(): results[queuename] = {} - + for count in ret[queuename]: results[queuename][count] = {} - + tickets = ret[queuename][count] total_time = 0 @@ -868,24 +872,26 @@ def performanceByQueueTmp(self, report_from, report_to, period): - """ + """ A quick method to obtain the following information on a per queue basis: * Average Held Time - * Maximum Held Time - * Average Count - * Maximum Count + * Maximum Held Time + * Average Count + * Maximum Count period = ('daily' | 'monthly') - We use the getTicketWindow SQL method to find all tickets with + We use the getTicketWindow SQL method to find all tickets with a given assignment. We then look at the history over the given window, and use this to calculate the time in the given queue for each ticket. + @param from_date: DateTime.DateTime + @param to_date: DateTime.DateTime """ - + ret = [] for queue in self.sql.listQueues(): @@ -896,8 +902,8 @@ # window. res = self.sql.getTicketWindow( - sqv_from_date = report_from, - sqv_to_date = report_to, + sqv_from_date = report_from.HTML4(), + sqv_to_date = report_to.HTML4(), sqv_assigned = '_Q' + queuename ) @@ -911,20 +917,20 @@ ticket_duration[t.id] = mx.DateTime.DateTimeDelta(0) # Now look at the history of all tickets over the given period, - # where assignment is affected in some way. - - + # where assignment is affected in some way. + + history = self.sql.listHistory( - sqv_from_date = report_from, - sqv_to_date = report_to, + sqv_from_date = report_from.HTML4(), + sqv_to_date = report_to.HTML4(), sqv_reverse_history = True, sqv_active_field = 'assigned' ) - # The following list and variable track the total count in the + # The following list and variable track the total count in the # queue at any given time. The latter is used to calculate the - # average count in the queue. We could just obtain this + # average count in the queue. We could just obtain this # information from the total count vs time, but I'd like to get # the standard deviation from this at some point. @@ -942,7 +948,7 @@ else: # We have moved to an assignment other than the queue - # If the timer is set, then the previous assignement + # If the timer is set, then the previous assignement # was to the relevant queue master. Add the time for # the interval to the total # print "Moved ticket %i to assignment %s " % (hitem.ticket_id, hitem.assigned) @@ -962,7 +968,7 @@ logging.DEBUG, 'reports.queuereport') if self.sql.listHistory(sqv_ticket_id = tid, - sqv_to_date = ticket_state[tid] ): + sqv_to_date = ticket_state[tid].HTML4() ): log('%sFound prior history' % (self.getLogName()), logging.DEBUG, 'reports.queuereport') remaining_period = ticket_state[tid] - ParseDateTime(report_from) @@ -973,7 +979,7 @@ # Now with the data we have collected, calculate the results - # Find the max count + # Find the max count max_count = 0 for (count, ts) in ticket_counts: if count > max_count: @@ -988,7 +994,7 @@ # Find the average duration tot_time = mx.DateTime.DateTimeDelta(0) for tid in ticket_duration.keys(): - tot_time = tot_time + ticket_duration[tid] + tot_time = tot_time + ticket_duration[tid] avg_time = tot_time / len(ticket_duration) res = {} @@ -1006,11 +1012,11 @@ def performanceByQueueGraph(self): - """ - Not yet implemented """ + Not yet implemented + """ - + security.declareProtected('MailManager Reports', 'report_page') report_page = PageTemplateFile('www/QueueReport', globals()) @@ -1019,10 +1025,10 @@ """ Generate the report information This report engine generates a report in HTML using the report_page - page template. + page template. """ - return self.report_page(REQUEST=REQUEST, RESPONSE=RESPONSE, - section=section, subsection=subsection, + return self.report_page(REQUEST=REQUEST, RESPONSE=RESPONSE, + section=section, subsection=subsection, report_from=report_from, report_to=report_to) Modified: MailManager/branches/RELENG_2_1/tests/testAPI.py =================================================================== --- MailManager/branches/RELENG_2_1/tests/testAPI.py 2006-04-19 14:23:56 UTC (rev 2929) +++ MailManager/branches/RELENG_2_1/tests/testAPI.py 2006-04-20 18:48:20 UTC (rev 2930) @@ -289,7 +289,8 @@ self.mmobj.addOrEditCustomer( real_name = 'Alan Partridge', username = 'alan', password = 'letmein', confirm = 'letmein', - access = ['al...@bb...'], section='add', REQUEST=self.request) + access = ['al...@bb...'] + ) # getCustomer self.mmobj.getCustomer('alan') # delCustomer Modified: MailManager/branches/RELENG_2_1/tests/testDatabase.py =================================================================== --- MailManager/branches/RELENG_2_1/tests/testDatabase.py 2006-04-19 14:23:56 UTC (rev 2929) +++ MailManager/branches/RELENG_2_1/tests/testDatabase.py 2006-04-20 18:48:20 UTC (rev 2930) @@ -354,6 +354,8 @@ * Timezone information is silently dropped by database from DateTime objects for all databases * Setting time_zone in MySQL does not affect DATETIME columns + * Obtaining strings from the database returns DateTime objects + in UTC. """ # Clear the test table query = self.convertZSQL("DELETE FROM <dtml-var schema>timestamp_test;") @@ -409,6 +411,32 @@ newtime = results[0].ts self.failUnlessEqual(newtime,origtime) + # Check the UTC response + sql = """ + DELETE FROM <dtml-var schema>timestamp_test + """ + zsql = self.ZSQLMethod(sql) + query = zsql.getQuery(sqv_time = dt) + sql = """ + INSERT INTO <dtml-var schema>timestamp_test ( + ts + ) VALUES ( + <dtml-sqlvar sqv_time type="nb" optional> + ) + """ + zsql = self.ZSQLMethod(sql) + query = zsql.getQuery(sqv_time = "2001-01-01 12:00:00") + sql = """ + SELECT * FROM <dtml-var schema>timestamp_test + """ + zsql = self.ZSQLMethod(sql) + query = zsql.getQuery(sqv_time = dt) + results = Results.Results(self.dbconn.query(query)) + newtime = results[0].ts + print "UTC time is ", newtime + print "HTML4 time is ", newtime.HTML4() + + def _testIntervals(self): """ Check for the following properties Modified: MailManager/branches/RELENG_2_1/tests/testQueueing.py =================================================================== --- MailManager/branches/RELENG_2_1/tests/testQueueing.py 2006-04-19 14:23:56 UTC (rev 2929) +++ MailManager/branches/RELENG_2_1/tests/testQueueing.py 2006-04-20 18:48:20 UTC (rev 2930) @@ -173,12 +173,14 @@ # Should redirect to open tickets page res = self.mmobj.index_html(REQUEST=self.request) # Should possibly be a redirect in here - self.failUnless('You currently have no tickets. To obtain a ticket, select from one' in res) + print res + self.failUnless('You currently have no open tickets. To obtain a ticket' in res) # Check the open tickets page, this should display "get ticket from # queue" as an option res = self.mmobj.Tickets(REQUEST=self.request) - self.failUnless('You currently have no tickets. To obtain a ticket, select from one' in res) + self.failUnless('You currently have no open tickets. To obtain a ticket' in res) + print res # Obtain a ticket from the queue # Check that the open tickets page now redirects to the one open ticket Modified: MailManager/branches/RELENG_2_1/www/QueueReport.zpt =================================================================== --- MailManager/branches/RELENG_2_1/www/QueueReport.zpt 2006-04-19 14:23:56 UTC (rev 2929) +++ MailManager/branches/RELENG_2_1/www/QueueReport.zpt 2006-04-20 18:48:20 UTC (rev 2930) @@ -241,8 +241,8 @@ </thead> <tbody> <tr tal:repeat="acc python:here.sql.emailReceivedByAccount( - sqv_from_date = report_from, - sqv_to_date = report_to + sqv_from_date = report_from.HTML4(), + sqv_to_date = report_to.HTML4() )" tal:attributes="bgcolor python:test(repeat['acc'].odd(), '#d0cfe5', None)"> @@ -270,8 +270,8 @@ </thead> <tbody> <tr tal:repeat="key python:here.sql.emailReceivedByUser( - sqv_from_date = report_from, - sqv_to_date = report_to + sqv_from_date = report_from.HTML4(), + sqv_to_date = report_to.HTML4() )" tal:attributes="bgcolor python:test(repeat['key'].odd(), '#d0cfe5', None)"> @@ -307,8 +307,8 @@ '#d0cfe5', None)"> <td tal:content="key/choice">te...@ex...</td> <td tal:content="python:here.sql.listTickets(**{ - 'sqv_from_date' : report_from, - 'sqv_to_date' : report_to, + 'sqv_from_date' : report_from.HTML4(), + 'sqv_to_date' : report_to.HTML4(), 'sqv_count' : 1, 'sqv_category%d' % cat : key['choice'] })[0][0]" @@ -384,8 +384,8 @@ </thead> <tbody> <tr tal:repeat="stat python:here.sql.performanceByAccount( - sqv_from_date = report_from, - sqv_to_date = report_to + sqv_from_date = report_from.HTML4(), + sqv_to_date = report_to.HTML4() )" tal:attributes="bgcolor python:test(repeat['stat'].odd(), '#d0cfe5', None)"> @@ -418,8 +418,8 @@ </thead> <tbody> <tr tal:repeat="stat python:here.sql.performanceByUser( - from_date = report_from, - to_date = report_to + from_date = report_from.HTML4(), + to_date = report_to.HTML4() )" tal:attributes="bgcolor python:test(repeat['stat'].odd(), '#d0cfe5', None)"> @@ -453,8 +453,8 @@ '#d0cfe5', None)"> <td tal:content="acc/email">te...@ex...</td> <tal:stats define="stats python:here.sql.accountStats( - sqv_from_date = report_from, - sqv_to_date = report_to, + sqv_from_date = report_from.HTML4(), + sqv_to_date = report_to.HTML4(), sqv_account_id=acc.email )[0]"> <td tal:content="python:here.formatTargetEn(acc.response_target)"> This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ke...@us...> - 2006-04-24 12:47:47
|
Revision: 2931 Author: kevca Date: 2006-04-24 05:47:34 -0700 (Mon, 24 Apr 2006) ViewCVS: http://svn.sourceforge.net/mailmanager/?rev=2931&view=rev Log Message: ----------- Fix for +- getMail fails with unicode error (#1475487) Now splits off save into a http method and a API call Modified Paths: -------------- MailManager/branches/RELENG_2_1/CHANGES.txt MailManager/branches/RELENG_2_1/Extensions/TicketPluggableBrain.py MailManager/branches/RELENG_2_1/ruleset/engine.py Modified: MailManager/branches/RELENG_2_1/CHANGES.txt =================================================================== --- MailManager/branches/RELENG_2_1/CHANGES.txt 2006-04-20 18:48:20 UTC (rev 2930) +++ MailManager/branches/RELENG_2_1/CHANGES.txt 2006-04-24 12:47:34 UTC (rev 2931) @@ -1,5 +1,6 @@ Version 2.1-RC3 * BUG FIXES +- getMail fails with unicode error (#1475487) - Default template on accounts (#1464963) - Setting template should return to template (#1464961) - Close button not listed (#1464954) Modified: MailManager/branches/RELENG_2_1/Extensions/TicketPluggableBrain.py =================================================================== --- MailManager/branches/RELENG_2_1/Extensions/TicketPluggableBrain.py 2006-04-20 18:48:20 UTC (rev 2930) +++ MailManager/branches/RELENG_2_1/Extensions/TicketPluggableBrain.py 2006-04-24 12:47:34 UTC (rev 2931) @@ -114,8 +114,8 @@ return self.ticket_index_html(self, REQUEST) - security.declareProtected('MailManager Manage Tickets', 'save') - def save(self, subject='', assigned='', state='', priority=0, + security.declareProtected('MailManager Manage Tickets', 'http_save') + def http_save(self, subject='', assigned='', state='', priority=0, category0=None, category1=None, category2=None, support_of=None, changed_by='', event='', transition=None, REQUEST=None, RESPONSE=None): @@ -166,8 +166,46 @@ REQUEST.set('error', error) REQUEST.set('details', 'y') return self.ticket_index_html(self, REQUEST) + changed_by = changed_by or getSecurityManager().getUser().getUserName() if changed_by == 'Anonymous User': changed_by = '' + + self.save( + changed_by = changed_by, + subject = subject, + assigned = assigned, + state = state, + priority = priority, + category0 = category0, + category1 = category1, + category2 = category2, + support_of = support_of, + event = event, + transition = transition + ) + + if RESPONSE is not None: + return RESPONSE.redirect('%s/ticket/%06d' % (self.getBaseURL(), + self.id)) + + security.declareProtected('MailManager Manage Tickets', 'save') + def save(self, changed_by, subject='', assigned='', state='', priority=0, + category0=None, category1=None, category2=None, support_of=None, + event='', transition=None): + """ Save changes to a ticket. + + Check what has been changed, record changes in history and then make + the changes to the ticket. + + transition is the id of transition from the ruleset engine which is + being used to make this change. + + event is the name of the event which is causing the transition to + occur. + + changed_by : + + """ changes = {} set_date_Closed = False clear_date_Closed = False @@ -175,7 +213,7 @@ # Changes need to be done to the local object as well as set in the # database. Some changes need special SQL parameters. - if state and state != self.state: + if state and state != self.state: if state == 'Closed': # Record the date & time the ticket was Closed. set_date_Closed = True @@ -249,9 +287,6 @@ elif support_of: self.sql.editTicket(sqv_id = self.id, sqv_set_support_of = True, sqv_support_of = support_of) - if RESPONSE is not None: - return RESPONSE.redirect('%s/ticket/%06d' % (self.getBaseURL(), - self.id)) security.declareProtected('MailManager Manage Tickets', 'headers') def headers(self, REQUEST): Modified: MailManager/branches/RELENG_2_1/ruleset/engine.py =================================================================== --- MailManager/branches/RELENG_2_1/ruleset/engine.py 2006-04-20 18:48:20 UTC (rev 2930) +++ MailManager/branches/RELENG_2_1/ruleset/engine.py 2006-04-24 12:47:34 UTC (rev 2931) @@ -38,7 +38,22 @@ from Products.MailManager.support.logger import log +try: + from AccessControl import getSecurityManager +except ImportError: + # Mockup so we can run outside Zope. Security Manager needs refactoring + class mockuser: + def getUserName(self): + return "Anonymous User" + class mocksm: + def getUser(self): + return mockuser() + + def getSecurityManager(): + return mocksm() + + class ActionHandlersMixin: """ Call python code to do an action based on a ticket/action pair @@ -78,15 +93,18 @@ if action.username == '$QueueUser': username = '_Q%s' % self.sql.listAccounts(sqv_email=ticket.account_id)[0].assign_queue elif action.username == '_$Current': - from AccessControl import getSecurityManager user = getSecurityManager().getUser() username = user.getUserName() else: username = action.username log('%sAssigning ticket %i to user %s' % (self.getLogName(), ticket.id, username), logging.INFO, 'ruleset.actions') - ticket.save(assigned = username) + changed_by = getSecurityManager().getUser().getUserName() + if changed_by == 'Anonymous User': changed_by = '' + + ticket.save(changed_by = changed_by, assigned = username) + def alertCustomer(self, *params): pass @@ -203,7 +221,12 @@ # every time, and will be invariant over most sections of the code ticket.updateChildAttribute() - ticket.save(subject=ticket.subject, + changed_by = getSecurityManager().getUser().getUserName() + if changed_by == 'Anonymous User': changed_by = '' + + ticket.save( + changed_by=changed_by, + subject=ticket.subject, assigned=assigned, state=state, priority=priority, @@ -280,7 +303,12 @@ #print "Processing supplied ticket" log('%sTransition from state %s to state %s' % (self.getLogName(), ticket.state, transition.newstate), logging.INFO, 'ruleset.engine') - ticket.save(state = transition.newstate, + + changed_by = getSecurityManager().getUser().getUserName() + if changed_by == 'Anonymous User': changed_by = '' + + ticket.save(changed_by = changed_by, + state = transition.newstate, event = eventname, transition = transition.transition_id) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ke...@us...> - 2006-04-24 13:52:06
|
Revision: 2932 Author: kevca Date: 2006-04-24 06:51:54 -0700 (Mon, 24 Apr 2006) ViewCVS: http://svn.sourceforge.net/mailmanager/?rev=2932&view=rev Log Message: ----------- Fixes for the UI issues in - Performance Reports still contain queue masters (#1475530) Modified Paths: -------------- MailManager/branches/RELENG_2_1/CHANGES.txt MailManager/branches/RELENG_2_1/MailManager.py MailManager/branches/RELENG_2_1/www/QueueReport.zpt Modified: MailManager/branches/RELENG_2_1/CHANGES.txt =================================================================== --- MailManager/branches/RELENG_2_1/CHANGES.txt 2006-04-24 12:47:34 UTC (rev 2931) +++ MailManager/branches/RELENG_2_1/CHANGES.txt 2006-04-24 13:51:54 UTC (rev 2932) @@ -1,5 +1,6 @@ Version 2.1-RC3 * BUG FIXES +- Performance Reports still contain queue masters (#1475530) - getMail fails with unicode error (#1475487) - Default template on accounts (#1464963) - Setting template should return to template (#1464961) Modified: MailManager/branches/RELENG_2_1/MailManager.py =================================================================== --- MailManager/branches/RELENG_2_1/MailManager.py 2006-04-24 12:47:34 UTC (rev 2931) +++ MailManager/branches/RELENG_2_1/MailManager.py 2006-04-24 13:51:54 UTC (rev 2932) @@ -1651,6 +1651,16 @@ """ return [user for user in self.sql.listUsers(sqv_username='') if not user.username.startswith(u'_')] + + security.declareProtected('View MailManager', 'listCategoryChoices') + def listCategoryChoices(self, cat_id): + """ Return a list of choices for the given category id + + This method will strip out the empty choice, which must exist + in the database for SQL operations to operate correctly. + """ + return [cat for cat in self.sql.listCategoryChoices(sqv_category_id=cat_id) + if not cat.choice == ''] security.declareProtected('MailManager View Tickets', 'getQueueStats') def getQueueStats(self, queue_name): Modified: MailManager/branches/RELENG_2_1/www/QueueReport.zpt =================================================================== --- MailManager/branches/RELENG_2_1/www/QueueReport.zpt 2006-04-24 12:47:34 UTC (rev 2931) +++ MailManager/branches/RELENG_2_1/www/QueueReport.zpt 2006-04-24 13:51:54 UTC (rev 2932) @@ -275,12 +275,14 @@ )" tal:attributes="bgcolor python:test(repeat['key'].odd(), '#d0cfe5', None)"> - <td tal:content="key/assigned">username</td> - <td tal:content="key/tickets">42</td> - <td style="text-align:center" - tal:condition="not:exists:request/excel"> - <a tal:attributes="href string:Reports?graph=y&assigned=${key/assigned}" class="state graph">Graph</a> - </td> + <span tal:condition="python:key.assigned in [u.username for u in here.listUsers()]"> + <td tal:content="key/assigned">username</td> + <td tal:content="key/tickets">42</td> + <td style="text-align:center" + tal:condition="not:exists:request/excel"> + <a tal:attributes="href string:Reports?graph=y&assigned=${key/assigned}" class="state graph">Graph</a> + </td> + </span> </tr> </tbody> </table> @@ -302,7 +304,7 @@ </thead> <tr tal:define="cat python:int(subsection[8:])" - tal:repeat="key python:here.sql.listCategoryChoices(sqv_category_id = cat)" + tal:repeat="key python:here.listCategoryChoices(cat_id = cat)" tal:attributes="bgcolor python:test(path('repeat/key/odd'), '#d0cfe5', None)"> <td tal:content="key/choice">te...@ex...</td> @@ -423,11 +425,13 @@ )" tal:attributes="bgcolor python:test(repeat['stat'].odd(), '#d0cfe5', None)"> - <td tal:content="stat/username">User Name</td> - <td align="center" tal:content="python:here.formatInterval(stat.max_respond_time)"> </td> - <td align="center" tal:content="python:here.formatInterval(stat.avg_respond_time)"> </td> - <td align="center" tal:content="python:here.formatInterval(stat.max_close_time)"> </td> - <td align="center" tal:content="python:here.formatInterval(stat.avg_close_time)"> </td> + <span tal:condition="python:stat.username in [u.username for u in here.listUsers()]"> + <td tal:content="stat/username">User Name</td> + <td align="center" tal:content="python:here.formatInterval(stat.max_respond_time)"> </td> + <td align="center" tal:content="python:here.formatInterval(stat.avg_respond_time)"> </td> + <td align="center" tal:content="python:here.formatInterval(stat.max_close_time)"> </td> + <td align="center" tal:content="python:here.formatInterval(stat.avg_close_time)"> </td> + </span> </tr> </tbody> </table> This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ke...@us...> - 2006-04-24 16:13:46
|
Revision: 2936 Author: kevca Date: 2006-04-24 09:13:39 -0700 (Mon, 24 Apr 2006) ViewCVS: http://svn.sourceforge.net/mailmanager/?rev=2936&view=rev Log Message: ----------- Fixes for - History Display Issues (#1460955) - getMail fails with unicode error (#1475487) Modified Paths: -------------- MailManager/branches/RELENG_2_1/CHANGES.txt MailManager/branches/RELENG_2_1/Extensions/TicketPluggableBrain.py Modified: MailManager/branches/RELENG_2_1/CHANGES.txt =================================================================== --- MailManager/branches/RELENG_2_1/CHANGES.txt 2006-04-24 16:12:04 UTC (rev 2935) +++ MailManager/branches/RELENG_2_1/CHANGES.txt 2006-04-24 16:13:39 UTC (rev 2936) @@ -1,5 +1,9 @@ Version 2.1-RC3 * BUG FIXES +- History Display Issues (#1460955) +- Adding/Deleting Attachments problems (#1460943) +- Support of: text invisible (#1460915) +- Queue button doesn't highlight (#1460913) - Performance Reports still contain queue masters (#1475530) - getMail fails with unicode error (#1475487) - Default template on accounts (#1464963) Modified: MailManager/branches/RELENG_2_1/Extensions/TicketPluggableBrain.py =================================================================== --- MailManager/branches/RELENG_2_1/Extensions/TicketPluggableBrain.py 2006-04-24 16:12:04 UTC (rev 2935) +++ MailManager/branches/RELENG_2_1/Extensions/TicketPluggableBrain.py 2006-04-24 16:13:39 UTC (rev 2936) @@ -44,7 +44,9 @@ import pprint +from AccessControl import getSecurityManager + from_escape = re.compile('^>*From ') sig_remover = re.compile('^-- $.*\Z', re.DOTALL | re.MULTILINE) @@ -117,8 +119,7 @@ security.declareProtected('MailManager Manage Tickets', 'http_save') def http_save(self, subject='', assigned='', state='', priority=0, category0=None, category1=None, category2=None, support_of=None, - changed_by='', event='', transition=None, - REQUEST=None, RESPONSE=None): + event='', transition=None, REQUEST=None, RESPONSE=None): """ Save changes to a ticket. Check what has been changed, record changes in history and then make @@ -167,7 +168,7 @@ REQUEST.set('details', 'y') return self.ticket_index_html(self, REQUEST) - changed_by = changed_by or getSecurityManager().getUser().getUserName() + changed_by = getSecurityManager().getUser().getUserName() if changed_by == 'Anonymous User': changed_by = '' self.save( @@ -514,7 +515,7 @@ subject=subject, body=body, body_is_html=body_is_html, user_signature=user_signature, sendmail=False, last_modified=last_modified, - change_status = None, + change_state = None, event = 'AddNote', REQUEST=REQUEST, RESPONSE=RESPONSE) @@ -537,7 +538,9 @@ REQUEST.set('error', 'This ticket has been modified. Please check it, and then retry your request') return self.ticket_index_html(self, REQUEST) - self.save(state='Closed', event='Close') + changed_by = getSecurityManager().getUser().getUserName() + if changed_by == 'Anonymous User': changed_by = '' + self.save(changed_by=changed_by, state='Closed', event='Close') # Redirect to the next ticket in the list, or just to the main # tickets screen if no next ticket exists. @@ -551,7 +554,7 @@ def sendMethod(self, mail_to, cc='', bcc='', subject='', body='', body_is_html=False, user_signature=None, next_id=None, - last_modified=None, offset=None, change_status=None, + last_modified=None, offset=None, change_state=None, transition=None, event=None, sendmail=True, REQUEST=None, RESPONSE=None): """ Common underlying method for sending replies/adding notes @@ -576,33 +579,25 @@ @type next_id: int @type last_modified: string (ascii, iso date format) @type offset: int - @type change_status: string (utf-8) + @type change_state: string (utf-8) @type transition: string (utf-8) @type event: string (utf-8) @type sendmail: boolean """ # Sanitize the string parameters + # Parameters are in utf-8, convert to unicode to avoid problems if mail_to is not None: mail_to = mail_to.decode('utf-8') if cc is not None: cc = cc.decode('utf-8') if bcc is not None: bcc = bcc.decode('utf-8') if subject is not None: subject = subject.decode('utf-8') if body is not None: body = body.decode('utf-8') if user_signature is not None: user_signature = user_signature.decode('utf-8') - if change_status is not None: change_status = change_status.decode('utf-8') + if change_state is not None: change_state= change_state.decode('utf-8') if transition is not None: transition = transition.decode('utf-8') if event is not None: event = event.decode('utf-8') body_is_html = int(body_is_html) - # Parameters are in utf-8, convert to unicode to avoid problems - mail_to = unicode(mail_to, 'utf-8') - cc = unicode(cc, 'utf-8') - bcc = unicode(bcc, 'utf-8') - subject = unicode(subject, 'utf-8') - body = unicode(body, 'utf-8') - if user_signature is not None: - user_signature = unicode(user_signature, 'utf-8') - # First, check for any modifications to the ticket if last_modified: mdate = self.sql.getTicketLastModified( @@ -747,7 +742,9 @@ if change_state: # Set the new state. - self.save(state=change_state, event=event) + changed_by = getSecurityManager().getUser().getUserName() + if changed_by == 'Anonymous User': changed_by = '' + self.save(changed_by=changed_by, state=change_state, event=event) # The first time we reply check promptness. if not self.date_responded: @@ -1109,6 +1106,7 @@ uploaded file. Attachments are stored in the SESSION until the message is sent. """ + print "Add attachment" attachments = REQUEST.SESSION.get('attachments', {}) if standard_attach: file = self.attachments[standard_attach] @@ -1124,6 +1122,7 @@ attachments[file_attach.filename] = file REQUEST.SESSION.set('attachments', attachments) REQUEST.set('autojump', True) + print "Back to the ticket" return self.ticket_index_html(self, REQUEST) security.declareProtected('MailManager Manage Tickets', 'delAttachment') @@ -1238,6 +1237,14 @@ if item.event != 'Epsilon': histitem = {} + # Skip over events that we don't really care about + if item.event == 'ViewTicket': + continue + + # Received state should always use an epsilon to get a usable state + if item.state == 'Received': + continue + if item.state: histitem['State'] = item.state if item.assigned: @@ -1273,7 +1280,9 @@ aobj = ruleset.actions.getActionClass(action.action, action.params) if not histitem.has_key('Actions'): histitem['Actions'] = [] - histitem['Actions'].append(str(aobj)) + # Don't display the action of assignment to the queue masters + if not (isinstance(aobj, ruleset.actions.AssignUser) and aobj.username == '$QueueUser'): + histitem['Actions'].append(str(aobj)) if nextitem: if nextitem.event == 'Epsilon': @@ -1295,12 +1304,17 @@ ret = [] + if histitem.has_key('Event'): + if histitem['Event'] == 'GetNextTicketFromQueue': + ret.append(('Event', 'GetQueuedTicket')) + else: + ret.append(('Event', histitem['Event'])) if histitem.has_key('State'): - ret.append(('State', histitem['State'])) + ret.append(('Status', histitem['State'])) if histitem.has_key('Assigned'): ret.append(('Assigned', histitem['Assigned'])) if histitem.has_key('Queue Name'): - ret.append(('Queue Name', histitem['Queue Name'])) + ret.append(('Queue-Name', histitem['Queue Name'])) if histitem.has_key('Subject'): ret.append(('Subject', histitem['Subject'])) if histitem.has_key('Priority'): @@ -1317,8 +1331,6 @@ ret.append(('Changed', histitem['Changed'])) if histitem.has_key('By'): ret.append(('By', histitem['By'])) - if histitem.has_key('Event'): - ret.append(('Event', histitem['Event'])) if histitem.has_key('Actions'): for action in histitem['Actions']: ret.append(('Action', action)) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ke...@us...> - 2006-04-24 16:54:06
|
Revision: 2937 Author: kevca Date: 2006-04-24 09:53:53 -0700 (Mon, 24 Apr 2006) ViewCVS: http://svn.sourceforge.net/mailmanager/?rev=2937&view=rev Log Message: ----------- - Empty abilities are not being handled correctly (#1475604) Modified Paths: -------------- MailManager/branches/RELENG_2_1/CHANGES.txt MailManager/branches/RELENG_2_1/MailManager.py MailManager/branches/RELENG_2_1/www/UserSettings.zpt Modified: MailManager/branches/RELENG_2_1/CHANGES.txt =================================================================== --- MailManager/branches/RELENG_2_1/CHANGES.txt 2006-04-24 16:13:39 UTC (rev 2936) +++ MailManager/branches/RELENG_2_1/CHANGES.txt 2006-04-24 16:53:53 UTC (rev 2937) @@ -1,5 +1,6 @@ Version 2.1-RC3 * BUG FIXES +- Empty abilities are not being handled correctly (#1475604) - History Display Issues (#1460955) - Adding/Deleting Attachments problems (#1460943) - Support of: text invisible (#1460915) Modified: MailManager/branches/RELENG_2_1/MailManager.py =================================================================== --- MailManager/branches/RELENG_2_1/MailManager.py 2006-04-24 16:13:39 UTC (rev 2936) +++ MailManager/branches/RELENG_2_1/MailManager.py 2006-04-24 16:53:53 UTC (rev 2937) @@ -2049,6 +2049,13 @@ sqv_ability = ability ) + # Add on the empty ability to allow searching to work ok + self.sql.addUserAbility( + sqv_catno = 0, + sqv_username = username, + sqv_ability = '' + ) + if abilities1: if type(abilities1) is not list: abilities1 = [abilities1] @@ -2066,6 +2073,13 @@ sqv_ability = ability ) + # Add on the empty ability to allow searching to work ok + self.sql.addUserAbility( + sqv_catno = 1, + sqv_username = username, + sqv_ability = '' + ) + if abilities2: if type(abilities2) is not list: abilities2 = [abilities2] @@ -2083,8 +2097,15 @@ sqv_ability = ability ) + # Add on the empty ability to allow searching to work ok + self.sql.addUserAbility( + sqv_catno = 2, + sqv_username = username, + sqv_ability = '' + ) + security.declareProtected('MailManager Settings', 'http_delUserForm') def http_delUserForm(self, username, REQUEST): """Display the Delete User form.""" Modified: MailManager/branches/RELENG_2_1/www/UserSettings.zpt =================================================================== --- MailManager/branches/RELENG_2_1/www/UserSettings.zpt 2006-04-24 16:13:39 UTC (rev 2936) +++ MailManager/branches/RELENG_2_1/www/UserSettings.zpt 2006-04-24 16:53:53 UTC (rev 2937) @@ -176,7 +176,7 @@ <td> <!-- Multi select with options for all values --> <select tal:attributes="tabindex tabindex/next; name string:abilities${cat/id}; id string:abilities${cat/id}" multiple="true"> - <option tal:repeat="item python:here.sql.listCategoryChoices(sqv_category_id=cat.id)" + <option tal:repeat="item python:here.listCategoryChoices(cat_id=cat.id)" tal:attributes="value item/choice; selected python:test(item.choice in options.get('abilities%d' % cat.id, []), 'selected', None)" tal:content="item/choice">Sales lead</option> </select> This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ke...@us...> - 2006-04-25 09:08:04
|
Revision: 2938 Author: kevca Date: 2006-04-25 02:07:43 -0700 (Tue, 25 Apr 2006) ViewCVS: http://svn.sourceforge.net/mailmanager/?rev=2938&view=rev Log Message: ----------- Adding an initial debian packaging. This is not complete and will not work correctly at present. It is being included only for other developers to work with at present. Added Paths: ----------- MailManager/branches/RELENG_2_1/debian/ MailManager/branches/RELENG_2_1/debian/NOTES MailManager/branches/RELENG_2_1/debian/changelog MailManager/branches/RELENG_2_1/debian/control MailManager/branches/RELENG_2_1/debian/copyright MailManager/branches/RELENG_2_1/debian/mailmanager.README.Debian MailManager/branches/RELENG_2_1/debian/mailmanager.config MailManager/branches/RELENG_2_1/debian/mailmanager.docs MailManager/branches/RELENG_2_1/debian/mailmanager.postinst MailManager/branches/RELENG_2_1/debian/mailmanager.postrm.debhelper MailManager/branches/RELENG_2_1/debian/mailmanager.substvars MailManager/branches/RELENG_2_1/debian/mailmanager.templates MailManager/branches/RELENG_2_1/debian/rules MailManager/branches/RELENG_2_1/debian/zope-mailmanager.postinst MailManager/branches/RELENG_2_1/debian/zope2.7-mailmanager.postinst MailManager/branches/RELENG_2_1/debian/zope2.7-mailmanager.prerm Added: MailManager/branches/RELENG_2_1/debian/NOTES =================================================================== --- MailManager/branches/RELENG_2_1/debian/NOTES (rev 0) +++ MailManager/branches/RELENG_2_1/debian/NOTES 2006-04-25 09:07:43 UTC (rev 2938) @@ -0,0 +1,18 @@ +Notes taken in development of this package: + + build-stamp stops rebuilding package from same source directory + + Need to fix security for passwords in templates. + + Makefile doesn't rebuild docs correctly + + +Changes to make to svn: + +Remove MMMailIn-QMail.py + +Update Makefile so that subversion directories are ignored + + + + Added: MailManager/branches/RELENG_2_1/debian/changelog =================================================================== --- MailManager/branches/RELENG_2_1/debian/changelog (rev 0) +++ MailManager/branches/RELENG_2_1/debian/changelog 2006-04-25 09:07:43 UTC (rev 2938) @@ -0,0 +1,5 @@ +mailmanager (2.1.rc1-1) unstable; urgency=low + + * Initial debian package for testing + + -- Kevin Campbell <ke...@lo...> Sun, 26 Mar 2006 13:36:00 +0000 Added: MailManager/branches/RELENG_2_1/debian/control =================================================================== --- MailManager/branches/RELENG_2_1/debian/control (rev 0) +++ MailManager/branches/RELENG_2_1/debian/control 2006-04-25 09:07:43 UTC (rev 2938) @@ -0,0 +1,32 @@ +Source: mailmanager +Section: python +Priority: optional +Build-depends: debhelper (>> 3), python, libxml2-utils, docbook-utils +Maintainer: Kevin Campbell <ke...@lo...> +Uploaders: Kevin Campbell <ke...@lo...> +Standards-Version: 3.6.1 + +Package: zope-mailmanager-common +Architecture: all +Section: python +Recommends: zope2.7-mailmanager +Description: MailManager Product for Zope (common files) + The package contains the common files for the MailManager application + which runs under Zope. Install the specific package for the version of + Zope you are running to use this application. + +Package: zope2.7-mailmanager +Architecture: all +Section: python +Depends: zope-mailmanager-common, zope2.7 +Description: MailManager Product for Zope 2.7 + This package will install MailManager for use with Zope 2.7 + +Package: mailmanager +Architecture: all +Section: python +Depends: zope2.7-mailmanager, zope2.7 +Description: MailManager email management system + This package will install of the required software to run MailManager and + create and manage a zope instance running nothing but MailManager + Added: MailManager/branches/RELENG_2_1/debian/copyright =================================================================== --- MailManager/branches/RELENG_2_1/debian/copyright (rev 0) +++ MailManager/branches/RELENG_2_1/debian/copyright 2006-04-25 09:07:43 UTC (rev 2938) @@ -0,0 +1,6 @@ + + (c) Copyright Logicalware Ltd 2002-2006 logicalware mail + manager comes with ABSOLUTELY NO WARRANTY. Further details including + conditions of redistribution are contained in LICENSE.txt + http://www.logicalware.org/ + Added: MailManager/branches/RELENG_2_1/debian/mailmanager.README.Debian =================================================================== --- MailManager/branches/RELENG_2_1/debian/mailmanager.README.Debian (rev 0) +++ MailManager/branches/RELENG_2_1/debian/mailmanager.README.Debian 2006-04-25 09:07:43 UTC (rev 2938) @@ -0,0 +1,6 @@ +This is WIP debian package for MailManager. It should be completed for the +2.2 release. At present, it is being included in the 2.1 branch for those +who wish to test it. + +The mailmanager package will install and configure mailmanager automatically +on the host it is installed on, optionally performing the required Added: MailManager/branches/RELENG_2_1/debian/mailmanager.config =================================================================== --- MailManager/branches/RELENG_2_1/debian/mailmanager.config (rev 0) +++ MailManager/branches/RELENG_2_1/debian/mailmanager.config 2006-04-25 09:07:43 UTC (rev 2938) @@ -0,0 +1,140 @@ +#!/bin/sh +# +# Configuration for MailManager +# +# This file will obtain the configuration settings for MailManager from the +# end user, storing these in the debconf database. These parameters are +# later used by the postinst script in order to set up a MailManager instance. +# Please see the development documentation regarding installers for a more +# in depth guide to what is done here +# +# Note that this script doesn't use -e at present due to the use of grep. This +# must be corrected later, as otherwise all return codes need to be explicitly +# tested. +# + +# Source debconf library. +. /usr/share/debconf/confmodule +# Enable debugging +set -x + + + +# Pre config check, see if MailManager is already configured. If so, then +# don't ask for any setup questions. The end user will be asked to remove +# their existing MailManager installation if they want to reconfigure. + +if [ -d /var/lib/zope2.7/mailmanager ] ; then + db_input medium mailmanager/instance_alreadyexists || true + db_go + exit 0 +fi + + + +# Find out what database we want to use +db_input medium mailmanager/database_platform || true +db_go + +# Now find out the hostname we wish to connect to +db_input medium mailmanager/database_hostname || true +db_go + +# Ask the user if they want us to do the setup automatically +db_input medium mailmanager/database_admin || true +db_go + +# Are we configuring postgres on localhost automatically? If so, offer to +# use the postgres account to configure the database without a password. + +# "Installing postgres automatically (remote/local) +# Check for postgresql-contrib package existing on the local machine + +# First check that we can connect to postgres in this fashion before +# offering. + +dpkg -s postgresql-contrib | grep ' installed' +STATUS=$? + +if [ $STATUS -eq 0 ] ; then + su - postgres -c "psql < /dev/null" + if [ $? -eq 0 ] ; then + + db_input medium mailmanager/database_haveadminaccess + db_go + + db_input medium mailmanager/database_dbname + db_go + + db_input medium mailmanager/database_schema + db_go + + else + + # Postgres database cannot be connected to using the admin user + db_input medium mailmanager/database_noadminaccess + db_go + + db_input medium mailmanager/database_adminuser + db_go + + db_input medium mailmanager/database_adminpassword + db_go + + # Offer to test the given connection string now + db_input medium mailmanager/database_testconn + db_go + + if [ "$RET" == "true" ] ; then + echo "Testing..." + + db_get mailmanager/database_connstring + CONNSTRING=$RET + /usr/share/zope/lib/python/Products/MailManager/scripts/testconn.py $CONNSTRING + + if [ $? -ne 0 ] ; then + db_input medium mailmanager/database_connfailed + exit 0 + fi + fi + + db_input medium mailmanager/database_dbname + db_go + + db_input medium mailmanager/database_schema + db_go + fi +else + db_input medium mailmanager/database_nopostgrescontrib + db_go + + db_input medium mailmanager/database_connstring + db_go + + # Offer to test the given connection string now + db_input medium mailmanager/database_testconn + db_go + + db_get mailmanager/database_testconn + if [ "$RET" == "true" ] ; then + echo "Testing..." + + db_get mailmanager/database_connstring + CONNSTRING=$RET + /usr/share/zope/lib/python/Products/MailManager/scripts/testconn.py $CONNSTRING + + if [ $? -ne 0 ] ; then + db_input medium mailmanager/database_connfailed + exit 0 + fi + fi + + db_input medium mailmanager/database_schema + db_go + + # Setup complete, we now rely on the postinst setup to do the rest of + # the configuration work. +fi + + +exit 0 Property changes on: MailManager/branches/RELENG_2_1/debian/mailmanager.config ___________________________________________________________________ Name: svn:executable + * Added: MailManager/branches/RELENG_2_1/debian/mailmanager.docs =================================================================== --- MailManager/branches/RELENG_2_1/debian/mailmanager.docs (rev 0) +++ MailManager/branches/RELENG_2_1/debian/mailmanager.docs 2006-04-25 09:07:43 UTC (rev 2938) @@ -0,0 +1,10 @@ +CREDITS.txt +CHANGES.txt +LICENSE.txt +README.txt +docs/development/mailmanager-dev/ +docs/development/mailmanager-dev.pdf +docs/userguide/mailmanager-userguide/ +docs/userguide/mailmanager-userguide.pdf +docs/adminguide/mailmanager-adminguide/ +docs/adminguide/mailmanager-adminguide.pdf Added: MailManager/branches/RELENG_2_1/debian/mailmanager.postinst =================================================================== --- MailManager/branches/RELENG_2_1/debian/mailmanager.postinst (rev 0) +++ MailManager/branches/RELENG_2_1/debian/mailmanager.postinst 2006-04-25 09:07:43 UTC (rev 2938) @@ -0,0 +1,28 @@ +#!/bin/sh -e +# +# Configure a zope instance running MailManager +# + +PRODUCT=MailManager +DESTDIR=/usr/lib/zope2.7/lib/python/Products +PYTHON=/usr/bin/python2.3 +PYLIB=$(echo $PYTHON | sed -e 's,/bin/,/lib/,') + +PKG=mailmanager +#CONFIGFILE=/etc/default/tardis-packagetools +#set -e +set -x +. /usr/share/debconf/confmodule + +case "$1" in + configure) + db_get $PKG/database_platform + DBPLATFORM="$RET" + echo $DBPLATFORM > /tmp/dbplatform +# mkdir -p $DESTDIR +# cp -rs /usr/share/zope/lib/python/Products/MailManager $DESTDIR/$PRODUCT +# $PYTHON -O $PYLIB/compileall.py -q $DESTDIR/$PRODUCT +# $PYTHON $PYLIB/compileall.py -q $DESTDIR/$PRODUCT +esac + + Property changes on: MailManager/branches/RELENG_2_1/debian/mailmanager.postinst ___________________________________________________________________ Name: svn:executable + * Added: MailManager/branches/RELENG_2_1/debian/mailmanager.postrm.debhelper =================================================================== --- MailManager/branches/RELENG_2_1/debian/mailmanager.postrm.debhelper (rev 0) +++ MailManager/branches/RELENG_2_1/debian/mailmanager.postrm.debhelper 2006-04-25 09:07:43 UTC (rev 2938) @@ -0,0 +1,6 @@ +# Automatically added by dh_installdebconf +if [ "$1" = purge ] && [ -e /usr/share/debconf/confmodule ]; then + . /usr/share/debconf/confmodule + db_purge +fi +# End automatically added section Added: MailManager/branches/RELENG_2_1/debian/mailmanager.substvars =================================================================== --- MailManager/branches/RELENG_2_1/debian/mailmanager.substvars (rev 0) +++ MailManager/branches/RELENG_2_1/debian/mailmanager.substvars 2006-04-25 09:07:43 UTC (rev 2938) @@ -0,0 +1 @@ +misc:Depends=debconf (>= 0.5) | debconf-2.0 Added: MailManager/branches/RELENG_2_1/debian/mailmanager.templates =================================================================== --- MailManager/branches/RELENG_2_1/debian/mailmanager.templates (rev 0) +++ MailManager/branches/RELENG_2_1/debian/mailmanager.templates 2006-04-25 09:07:43 UTC (rev 2938) @@ -0,0 +1,100 @@ +Template: mailmanager/database_platform +Type: string +Default: postgres +Description: Select database + Choose the database platform you wish to use to run mailmanager. + +Template: mailmanager/database_admin +Type: boolean +Default: true +Description: Configure database automatically + Do you wish to configure the database automatically? Note that you will + need the admin username and password for the database in order to do + this. + +Template: mailmanager/database_hostname +Type: string +Default: localhost +Description: Database hostname + Enter the database hostname. Note that if you are configuring a postgres + database automatically, you will need the database to be on the local + machine. Otherwise, you will need to manually configure postgres. + +Template: mailmanager/database_dbname +Type: string +Default: mailmanager +Description: Supply the database name to be created + Please supply the name to use when creating a new database on the database + server. All of the MailManager tables will be created under this database. + +Template: mailmanager/database_dbschema +Type: string +Default: +Description: Choose an optional schema name + The database platform you have choosen supports schemas. To create all of + the MailManager tables under a specific schema, please give a name here. + This is not required, and leaving this field blank will mean that no schema + is used. + +Template: mailmanager/database_adminuser +Type: string +Default: postgres +Description: Supply the admin username + Please provide an admin username with which to connect to the database + server. This user must have administrative access to create new users and + databases. + +Template: mailmanager/database_adminpassword +Type: string +Default: password +Description: Supply the admin password + Please provide a password for the admin user. + +Template: mailmanager/database_noautoconfig +Type: note +Description: Unable to automatically configure database. + The database could not be configured automatically. You will need to set up + the database manually, using the instructions in the supplied documentation. + Please reconfigure this package without using autoconfigure. + +Template: mailmanager/instance_alreadyexists +Type: note +Description: MailManager is already configured + There is already a MailManager instance configured on this system. If you + would like to erase this installation, and reconfigure from scratch, please + remove the instance from /var/lib/zope2.7/instance/mailmanager and + reconfigure. + +Template: mailmanager/database_nopostgrescontrib +Type: note +Description: postgresql-contrib required for automatic setup + The database could not be configured automatically as the postgresql-contrib + package is not installed. You will need to either set up the database + manually, using the instructions in the supplied documentation, or install + the postgresql-contrib package. Please either reconfigure this package + without using autoconfigure, or install the postgresql-contrib package + before reconfiguring this package. + +Template: mailmanager/database_noadminaccess +Type: note +Description: Unable to connect to the database. + It has not been possible to connect to the database automatically on the + local system. You will need to supply a login and password for the database + admin user in order to proceed. + +Template: mailmanager/database_testconn +Type: boolean +Default: true +Description: Test the database connection + Do you wish to test the database connection string you just supplied? + Choosing yes will connect to the database using the connection string + to see that it is functioning correctly. + +Template: mailmanager/database_connstring +Type: string +Default: dbname@hostname user password socket +Description: Database connection details + Please supply the details required to connect to the remote database. Note + that only the dbname and user paramaters are required, and you may remove + the other paramaters. The database documentation supplied with MailManager + will describe the format of this string in more detail if required. Added: MailManager/branches/RELENG_2_1/debian/rules =================================================================== --- MailManager/branches/RELENG_2_1/debian/rules (rev 0) +++ MailManager/branches/RELENG_2_1/debian/rules 2006-04-25 09:07:43 UTC (rev 2938) @@ -0,0 +1,49 @@ +#!/usr/bin/make -f +# Sample debian/rules that uses debhelper. +# GNU copyright 1997 to 1999 by Joey Hess. + +# This is the debhelper compatability version to use. +export DH_COMPAT=3 +export version="`cat version.txt`" + +build: build-stamp +build-stamp: + dh_testdir + $(MAKE) dist + #$(MAKE) docs + cd docs && $(MAKE) all + touch build-stamp + +clean: + dh_testdir + dh_testroot + $(MAKE) clean + dh_clean + +install: build + dh_testdir + dh_testroot + dh_clean -k + dh_installdirs + mkdir debian/zope-mailmanager-common/usr/share/zope/lib/python/Products -p + tar xzf MailManager-$(version).tar.gz \ + -C debian/zope-mailmanager-common/usr/share/zope/lib/python/Products + +# Build architecture-independent files here. +binary-indep: build install + dh_testdir + dh_testroot + dh_installdocs -i + dh_installmenu -i + dh_installdebconf -i + dh_installchangelogs -i + dh_compress -i + dh_fixperms -i + dh_installdeb -i + dh_gencontrol -i -- -Vversion=$(version) + dh_md5sums -i + dh_builddeb -i + +binary: binary-indep +.PHONY: build clean binary-indep binary install + Property changes on: MailManager/branches/RELENG_2_1/debian/rules ___________________________________________________________________ Name: svn:executable + * Added: MailManager/branches/RELENG_2_1/debian/zope-mailmanager.postinst =================================================================== --- MailManager/branches/RELENG_2_1/debian/zope-mailmanager.postinst (rev 0) +++ MailManager/branches/RELENG_2_1/debian/zope-mailmanager.postinst 2006-04-25 09:07:43 UTC (rev 2938) @@ -0,0 +1,18 @@ +#!/bin/sh -e +# +# Build and install the MailManager product for Zope (pre 2.7) +# + +PRODUCT=MailManager +DESTDIR=/usr/lib/zope/lib/python/Products +PYTHON=/usr/bin/python2.2 +PYLIB=$(echo $PYTHON | sed -e 's,/bin/,/lib/,') + +case "$1" in + configure) + mkdir -p $DESTDIR + cp -rs /usr/share/zope/lib/python/Products/MailManager $DESTDIR/$PRODUCT + $PYTHON -O $PYLIB/compileall.py -q $DESTDIR/$PRODUCT + $PYTHON $PYLIB/compileall.py -q $DESTDIR/$PRODUCT +esac + Added: MailManager/branches/RELENG_2_1/debian/zope2.7-mailmanager.postinst =================================================================== --- MailManager/branches/RELENG_2_1/debian/zope2.7-mailmanager.postinst (rev 0) +++ MailManager/branches/RELENG_2_1/debian/zope2.7-mailmanager.postinst 2006-04-25 09:07:43 UTC (rev 2938) @@ -0,0 +1,15 @@ +#!/bin/sh -e + +PRODUCT=MailManager +DESTDIR=/usr/lib/zope2.7/lib/python/Products +PYTHON=/usr/bin/python2.3 +PYLIB=$(echo $PYTHON | sed -e 's,/bin/,/lib/,') + +case "$1" in + configure) + mkdir -p $DESTDIR + cp -rs /usr/share/zope/lib/python/Products/MailManager $DESTDIR/$PRODUCT + $PYTHON -O $PYLIB/compileall.py -q $DESTDIR/$PRODUCT + $PYTHON $PYLIB/compileall.py -q $DESTDIR/$PRODUCT +esac + Added: MailManager/branches/RELENG_2_1/debian/zope2.7-mailmanager.prerm =================================================================== --- MailManager/branches/RELENG_2_1/debian/zope2.7-mailmanager.prerm (rev 0) +++ MailManager/branches/RELENG_2_1/debian/zope2.7-mailmanager.prerm 2006-04-25 09:07:43 UTC (rev 2938) @@ -0,0 +1,10 @@ +#!/bin/sh -e + +PRODUCTS_DIR=/usr/lib/zope2.7/lib/python/Products + +case "$1" in + remove|upgrade|deconfigure) + rm -rf $PRODUCTS_DIR/MailManager + rmdir --ignore-fail-on-non-empty $PRODUCTS_DIR + +esac This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ke...@us...> - 2006-04-25 16:37:15
|
Revision: 2940 Author: kevca Date: 2006-04-25 09:37:04 -0700 (Tue, 25 Apr 2006) ViewCVS: http://svn.sourceforge.net/mailmanager/?rev=2940&view=rev Log Message: ----------- Partial fix wrt - Export to Excel on Reports (#1460909) Still needs some work for excel output and unicode support Modified Paths: -------------- MailManager/branches/RELENG_2_1/CHANGES.txt MailManager/branches/RELENG_2_1/MailManager.py MailManager/branches/RELENG_2_1/Reporting.py MailManager/branches/RELENG_2_1/www/QueueReport.zpt MailManager/branches/RELENG_2_1/www/QueueReports.zpt MailManager/branches/RELENG_2_1/www/Reports.zpt Modified: MailManager/branches/RELENG_2_1/CHANGES.txt =================================================================== --- MailManager/branches/RELENG_2_1/CHANGES.txt 2006-04-25 16:30:22 UTC (rev 2939) +++ MailManager/branches/RELENG_2_1/CHANGES.txt 2006-04-25 16:37:04 UTC (rev 2940) @@ -1,5 +1,6 @@ Version 2.1-RC3 * BUG FIXES +- Export to Excel on Reports (#1460909) - Empty abilities are not being handled correctly (#1475604) - History Display Issues (#1460955) - Adding/Deleting Attachments problems (#1460943) Modified: MailManager/branches/RELENG_2_1/MailManager.py =================================================================== --- MailManager/branches/RELENG_2_1/MailManager.py 2006-04-25 16:30:22 UTC (rev 2939) +++ MailManager/branches/RELENG_2_1/MailManager.py 2006-04-25 16:37:04 UTC (rev 2940) @@ -3345,6 +3345,26 @@ REQUEST.set('body_is_html', new_body_html) return self.Create(REQUEST) + security.declarePublic('checkType') + def checkType(self, item, typelist): + """ Check that the item is of a type contained in typelist + + This method is necessary as the global method type is not + available inside Zope Page Templates. + """ + if type(item) is str: + return 'str' in typelist + if type(item) is unicode: + return 'unicode' in typelist + if type(item) is float: + return 'float' in typelist + if type(item) is long: + return 'long' in typelist + if type(item) is int: + return 'int' in typelist + return False + + security.declareProtected('View MailManager', 'getEditorForm') def getEditorForm(self, data): return self.Epoz(lang='en', name='body', data=data, Modified: MailManager/branches/RELENG_2_1/Reporting.py =================================================================== --- MailManager/branches/RELENG_2_1/Reporting.py 2006-04-25 16:30:22 UTC (rev 2939) +++ MailManager/branches/RELENG_2_1/Reporting.py 2006-04-25 16:37:04 UTC (rev 2940) @@ -54,7 +54,9 @@ from pprint import pprint +from support.i18n import convertString + class BadRequest(Exception): pass def container_filter(container): @@ -674,9 +676,23 @@ InitializeClass(HTMLReportingEngine) +class GraphLink: + __ac_roles__ = ('Tickets', 'Reports', 'Settings', 'Customer') + security = ClassSecurityInfo() + security.setPermissionDefault('View MailManager', + ['Tickets', 'Reports', 'Settings', 'Customer']) + def __init__(self, ref): + self.ref = ref + security.declarePublic('getLink') + def getLink(self): + """ Return an HTML link """ + return "<a href='Reports?graph=y&graphkey=%s' class='state graph'>Graph</a>" % self.ref + def __str__(self): + return self.getLink() +InitializeClass(GraphLink) class QueueReportingEngine(HTMLReportingEngine): """ @@ -702,6 +718,12 @@ Each of the above stats will be split so that there is a statistic for each number of times a ticket has been in the queue. + TODO: + + This class is now in need of a refactor. In particular, the graph + generation should be done from the data obtained from the + generateReportData method, and some of the view code is inside the + data model. """ meta_type = 'MailManager HTML Reporting Engine' @@ -728,52 +750,28 @@ ) security.declareProtected('MailManager Reports', 'index_html') - index_html = PageTemplateFile('www/QueueReports', globals()) + index_html_zpt = PageTemplateFile('www/QueueReports', globals()) def __init__(self, id, title): HTMLReportingEngine.__init__(self, id=id, title=title) - def getQueueStatus(self, type, queue, report_from=None, - report_to=None, period='daily'): - """ Get statistics on a single queue + security.declareProtected('View MailManager', 'index_html') + def index_html(self, REQUEST): + """ Wrapper for the index_html page to handle generation of excel data - @param from_date: DateTime.DateTime - @param to_date: DateTime.DateTime + This wrapper is necessary so that the generation of the outer + skeleton of the reports page is skipped, and only the tabular + data is generated and returned to the end user in an .xls format """ + if REQUEST.has_key('excel'): + print "Excel Report" + REQUEST.RESPONSE.setHeader('Content-Type', 'application/vnd.ms-excel'); + REQUEST.RESPONSE.setHeader('Content-Disposition','attachment;;filename=report.xls') + return self.excelReport(REQUEST) + else: + return self.index_html_zpt(REQUEST) - if type.startswith('number_'): - # Find the number of tickets assigned to this queue user at the - # start of the window. Then find all of the history items - # throughout the window, in order. Traversing this history, we - # can calculate the number of queued tickets at any given point - # of time - tickets = self.sql.getHistoricalTickets( - sqv_date_from = report_from.HTML4(), - sqv_date_to = report_to.HTML4(), - sqv_assigned = '_Qincoming' - ) - - queuedByTime = [] - - if type == 'number_avg': - pass - - elif type == 'number_max': - pass - - elif type == 'throughput_in': - pass - - elif type == 'throughput_out': - pass - - elif type == 'time_avg': - pass - - elif type == 'time_max': - pass - def performanceByQueue(self, report_from, report_to, period): """ A quick method to obtain the following information on a per @@ -813,8 +811,6 @@ for ritem in res: - pprint(("Ticket ", ritem.ticket_id, ritem.change_date, ritem.change_date.HTML4())) - subres = self.sql.listHistory( sqv_ticket_id = ritem.ticket_id, sqv_assigned = '_Q' + queuename, @@ -844,8 +840,6 @@ } ) - pprint(ret) - results = {} for queuename in ret.keys(): @@ -867,171 +861,399 @@ results[queuename][count]['avg_time'] = total_time / len(tickets) results[queuename][count]['max_time'] = max_time - pprint(results) return results - def performanceByQueueTmp(self, report_from, report_to, period): + security.declareProtected('MailManager Reports', 'report_page') + report_page = PageTemplateFile('www/QueueReport', globals()) + + security.declareProtected('MailManager Reports', 'generateReport') + def generateReport(self, REQUEST=None, RESPONSE=None, section='', subsection='', report_from=None, report_to=None): + """ Generate the report information + + This report engine generates a report in HTML using the report_page + page template. + + section + subsection + report_from + report_to + + REQUEST['excel'] + + Need i18n in here! """ - A quick method to obtain the following information on a per - queue basis: - * Average Held Time - * Maximum Held Time - * Average Count - * Maximum Count + # Sanitize input (periods are already as DateTime objects) + #section = section.decode('utf-8') + #subsection = subsection.decode('utf-8') - period = ('daily' | 'monthly') + (header, results) = self.generateReportData( + section=section, + subsection=subsection, + report_from=report_from, + report_to=report_to + ) - We use the getTicketWindow SQL method to find all tickets with - a given assignment. We then look at the history over the given - window, and use this to calculate the time in the given queue - for each ticket. + print "RESULTS" + pprint(header) + pprint(results) - @param from_date: DateTime.DateTime - @param to_date: DateTime.DateTime + if REQUEST.has_key('graph'): + period = REQUEST.get('period', 'daily') + return self.generateGraph(section=section, subsection=subsection, + report_from=report_from, report_to=report_to, period=period, graphkey=REQUEST.get('graphkey')) + else: + REQUEST.set('header', header) + REQUEST.set('results', results) + return self.report_page(REQUEST=REQUEST, RESPONSE=RESPONSE, + section=section, subsection=subsection, + report_from=report_from, report_to=report_to) + + + def excelReport(self, REQUEST): + """ Genearte a .xls output of the report + + This method bypasses the enclosing reports page and solely + outputs the tabular data. At present this is just outputing + CSV data, which may not be correct. Also, some encoding to + utf-8 should be done here. """ + # Sanitize variables from request and session - ret = [] + section = REQUEST.SESSION.get('section','snapshot') + subsection = REQUEST.SESSION.get('subsection','account') + subsection = self.fixSubsection(section, subsection, REQUEST.SESSION) + period = REQUEST.SESSION.get('period', 'daily') + report_dates = REQUEST.SESSION.get('report_dates', self.getReportDate(REQUEST, period)) + report_from = report_dates[0] + report_to = report_dates[1] - for queue in self.sql.listQueues(): + (header, results) = self.generateReportData( + section=section, subsection=subsection, + report_from=report_from, report_to=report_to) - queuename = queue.queue_name + output = "" + output += ','.join(header) + output += '\n' + for res in results: + render_items = [] + for item in res: + if not isinstance(item, GraphLink): + render_items.append(str(item)) + output += ','.join(render_items) + output += '\n' + return output - # Find all tickets which are in the given state at the start of the - # window. - res = self.sql.getTicketWindow( - sqv_from_date = report_from.HTML4(), - sqv_to_date = report_to.HTML4(), - sqv_assigned = '_Q' + queuename - ) + def generateGraph(self, section='', subsection='', report_from=None, report_to=None, period='daily', graphkey = ''): + """ Generate HTML structure to display a graph """ - ticket_state = {} - ticket_duration = {} + assigned = '' + account_id = '' + category0 = '' + category1 = '' + category2 = '' - # Note the state of all the given tickets + if section == 'received': + if subsection == 'account': + account_id = graphkey + if subsection == 'user': + assigned = graphkey + if subsection == 'category0': + category0 = graphkey + if subsection == 'category1': + category0 = graphkey + if subsection == 'category2': + category0 = graphkey - for t in res: - ticket_state[t.id] = (ParseDateTime(report_to)) - ticket_duration[t.id] = mx.DateTime.DateTimeDelta(0) + x = self.generateReportData(section=section, subsection=subsection, + report_from=report_from, report_to=report_to) + print "Data" + pprint(x) - # Now look at the history of all tickets over the given period, - # where assignment is affected in some way. + # getGraphDetails returns {'max': max, 'scale': scale, 'label': label, 'values': results} + results = self.getGraphDetails( + report_from, report_to, + account_id = account_id, + assigned = assigned, + category0 = category0, + category1 = category1, + category2 = category2 + ) + vals = results['values'] + scale = results['scale'] - history = self.sql.listHistory( - sqv_from_date = report_from.HTML4(), - sqv_to_date = report_to.HTML4(), - sqv_reverse_history = True, - sqv_active_field = 'assigned' - ) + output = "" + output += "<table cellpadding='2' cellspacing='0' border='0' class='content'>\n" + output += "<tr class='table_head'>\n" + output += " <td class='white' colspan='32' i18n:translate='Graph'>Graph</td>\n" + output += "</tr>\n\n" + output += "<tr>\n" + output += " <td> </td>\n" + output += "</tr>\n\n" - # The following list and variable track the total count in the - # queue at any given time. The latter is used to calculate the - # average count in the queue. We could just obtain this - # information from the total count vs time, but I'd like to get - # the standard deviation from this at some point. + output += "<tr>\n" + output += " <td></td>\n" + for row in vals: + output += " <td valign='bottom' width='20'>" + output += " <img height='%i' alt='%i'" % ((row * scale), row) + output += " src='images/bar.jpg' width='9' />" + output += " </td>\n" + output += "</tr>\n\n" + + output += "<tr>\n" + output += " <th>Tickets</th>\n" + for row in vals: + output += " <td>%s</td>\n" % (row and row or ' ') + output += "</tr>\n\n" + + output += "<tr>\n" + output += " <th>%s</th>\n" % (period == 'daily' and 'Day' or 'Month') + index = 1 + for row in vals: + output += " <td>%s</td>\n" % (index) #['number']) + index += 1 + output += "</tr>\n\n" - ticket_counts = [(len(ticket_state), ParseDateTime(report_to))] + output += "</table>\n\n" - for hitem in history: + return output - if hitem.assigned == '_Q' + queuename: - # We are now back in the queue again, set the timer - # convert the DateTime result from the SQL query into - # an mx.DateTime object - # print "Moved ticket %i into queue " % hitem.ticket_id - ticket_state[t.id] = mx.DateTime.gmtime(hitem.change_date.timeTime()) - ticket_counts.append(ticket_counts[-1][0] + 1, mx.DateTime.gmtime(hitem.change_date.timeTime())) - else: - # We have moved to an assignment other than the queue - # If the timer is set, then the previous assignement - # was to the relevant queue master. Add the time for - # the interval to the total - # print "Moved ticket %i to assignment %s " % (hitem.ticket_id, hitem.assigned) - if ticket_state.has_key(t.id): - ticket_counts.append(ticket_counts[-1][0] - 1, mx.DateTime.gmtime(hitem.change_date.timeTime())) - if not ticket_duration.has_key(t.id): - ticket_duration[t.id] = mx.DateTime.DateTimeDelta(0) - ticket_duration[t.id] += (ticket_state[t.id] - mx.DateTime.gmtime(hitem.change_date.timeTime())) - del ticket_state[t.id] + security.declareProtected('MailManager Reports', 'generateReportData') + def generateReportData(self, section, subsection, report_from=None, report_to=None): + """ Return a tuple comtaining + (header, results) - # Remaining period for the ticket - # We only add this if there are additional history items for the given - # ticket, prior to the last change in assignment. It is possible that - # the last history item visited was the creation of the ticket! - for tid in ticket_state: - log('%sChecking for prior history for ticket %i before %s' % (self.getLogName(), tid, ticket_state[tid]), - logging.DEBUG, 'reports.queuereport') + Header is a tuple of headings for the results, results is a list + of result entries to be displayed under the given headings. The + (header, results) pairs should be translated to a results table + on the display. - if self.sql.listHistory(sqv_ticket_id = tid, - sqv_to_date = ticket_state[tid].HTML4() ): - log('%sFound prior history' % (self.getLogName()), - logging.DEBUG, 'reports.queuereport') - remaining_period = ticket_state[tid] - ParseDateTime(report_from) - ticket_duration[tid] += remaining_period + At present, GraphLink objects are put into this data to allow + the user to choose to display a graph. This isn't the place to + do this, as this method should be returning a model, not a view. - # for tid in ticket_duration: - # print "Total time for ticket %i is %s" % (tid, ticket_duration[tid]) + For results which can be graphed, the first entry in the results + will be the key, and the last entry will be the graphlink (not + displayed) + """ - # Now with the data we have collected, calculate the results + ## Snapshot ######################################################### - # Find the max count - max_count = 0 - for (count, ts) in ticket_counts: - if count > max_count: - max_count = count + if section == 'snapshot': + if subsection == 'account': + header = [convertString("Account", "account"), + convertString("Open", "open"), + convertString("Hold", "hold"), + convertString("Closed", "closed"), + convertString("Overdue", "overdue"), + convertString("Spam", "spam") ] + res = self.getSnapshotData( + subsection = 'account', + report_from = report_from, + report_to = report_to + ) + results = [(acc.email, acc.open, acc.hold, acc.closed, acc.overdue, acc.spam) for acc in res ] + results.append((convertString('Total', 'total'), + sum([r.open for r in res]), + sum([r.hold for r in res]), + sum([r.closed for r in res]), + sum([r.overdue for r in res]), + sum([r.spam for r in res]))) - # Find the max duration - max_time = mx.DateTime.DateTimeDelta(0) - for tid in ticket_duration.keys(): - if ticket_duration[tid] > max_time: - max_time = ticket_duration[tid] + if subsection == 'user': + header = [convertString("User", "user"), + convertString("Open", "open"), + convertString("Hold", "hold"), + convertString("Closed", "closed"), + convertString("Overdue", "overdue"), + convertString("Spam", "spam") ] + res = self.sql.userStatus( + ) + results = [(stat.username, stat.open, stat.hold, stat.closed, stat.overdue, stat.spam) for stat in res + if not stat.username.startswith('_Q')] + results.append((convertString('Total', 'total'), + sum([r.open for r in res]), + sum([r.hold for r in res]), + sum([r.closed for r in res]), + sum([r.overdue for r in res]), + sum([r.spam for r in res]))) - # Find the average duration - tot_time = mx.DateTime.DateTimeDelta(0) - for tid in ticket_duration.keys(): - tot_time = tot_time + ticket_duration[tid] - avg_time = tot_time / len(ticket_duration) + if subsection == 'priorityVaccounts': + header = [convertString("Account", "account"), + convertString("Junk", "junk"), + convertString("Low", "low"), + convertString("Normal", "normal"), + convertString("High", "high"), + convertString("Critical", "critical") ] + res = self.sql.accountPriority( + ) + results = [(acc.email, acc.junk, acc.low, acc.normal, acc.high, acc.critical) for acc in res ] + results.append((convertString('Total', 'total'), + sum([r.junk for r in res]), + sum([r.low for r in res]), + sum([r.normal for r in res]), + sum([r.high for r in res]), + sum([r.critical for r in res]))) - res = {} - ret.append(res) - res['queue_name'] = queuename - res['max_queue_time'] = max_time - res['avg_queue_time'] = avg_time - res['max_count'] = max_time - res['avg_count'] = avg_time + if subsection == 'priorityVusers': + header = [convertString("User", "user"), + convertString("Junk", "junk"), + convertString("Low", "low"), + convertString("Normal", "normal"), + convertString("High", "high"), + convertString("Critical", "critical") ] + res = self.sql.userPriority( + ) + results = [(stat.username, stat.junk, stat.low, stat.normal, stat.high, stat.critical) for stat in res + if not stat.username.startswith('_Q')] + results.append((convertString('Total', 'total'), + sum([r.junk for r in res]), + sum([r.low for r in res]), + sum([r.normal for r in res]), + sum([r.high for r in res]), + sum([r.critical for r in res]))) - return ret + ## Received ######################################################### + + if section == 'received': + if subsection == 'account': + header = [convertString("Account", "account"), + convertString("New Tickets Opened", "new_tickets_opened"), + convertString("Action", "action") ] + res = self.sql.emailReceivedByAccount( + sqv_report_from = report_from.HTML4(), + sqv_report_to = report_to.HTML4() + ) + results = [(acc.account_id, acc.tickets, GraphLink(acc.account_id)) for acc in res ] + results.append((convertString('Total', 'total'), + sum([r.tickets for r in res]), + GraphLink(''))) + if subsection == 'user': + header = [convertString("User", "user"), + convertString("New Tickets Opened", "new_tickets_opened"), + convertString("Action", "action") ] + res = self.sql.emailReceivedByUser( + sqv_report_from = report_from.HTML4(), + sqv_report_to = report_to.HTML4() + ) + results = [(stat.assigned, stat.tickets, GraphLink(stat.assigned)) for stat in res + if not stat.assigned.startswith('_Q')] + results.append((convertString('Total', 'total'), + sum([r.tickets for r in res]), + GraphLink(''))) + if subsection.startswith('category'): + cat_id = int(subsection[8:]) + category = self.sql.listCategories(sqv_category_id=cat_id)[0]['label'].upper() + header = [convertString("User", "user"), + convertString("New Tickets Opened", "new_tickets_opened"), + convertString("Action", "action") ] + results = [] + for choice in [c.choice for c in self.listCategoryChoices(cat_id = cat_id)]: + res = self.sql.listTickets(**{ + 'sqv_from_date' : report_from.HTML4(), + 'sqv_to_date' : report_to.HTML4(), + 'sqv_count' : 1, + 'sqv_category%d' % cat_id : choice + })[0][0] + results.append((choice, res, GraphLink(choice))) + ## Performance ###################################################### + if section == 'performance': + if subsection == 'account': + header = [convertString("Account", "account"), + convertString("Max Response Time", "max_response"), + convertString("Average Response Time", "max_response"), + convertString("Max Close Time", "max_close"), + convertString("Average Close Time", "avg_close") ] + res = self.sql.performanceByAccount( + sqv_report_from = report_from.HTML4(), + sqv_report_to = report_to.HTML4() + ) + + results = [(stat.email, + self.formatInterval(stat.max_respond_time), + self.formatInterval(stat.avg_respond_time), + self.formatInterval(stat.max_close_time), + self.formatInterval(stat.avg_close_time)) for stat in res ] + + if subsection == 'user': + header = [convertString("User", "user"), + convertString("Max Response Time", "max_response"), + convertString("Average Response Time", "max_response"), + convertString("Max Close Time", "max_close"), + convertString("Average Close Time", "avg_close") ] + res = self.sql.performanceByUser( + sqv_report_from = report_from.HTML4(), + sqv_report_to = report_to.HTML4() + ) + + results = [(stat.username, + self.formatInterval(stat.max_respond_time), + self.formatInterval(stat.avg_respond_time), + self.formatInterval(stat.max_close_time), + self.formatInterval(stat.avg_close_time)) for stat in res + if not stat.username.startswith('_Q')] - def performanceByQueueGraph(self): - """ - Not yet implemented - """ + if subsection == 'targets': + header = [convertString("Account", "account"), + convertString("Target Time", "target_time"), + convertString("Responses On Target", "responses_on_target"), + convertString("Responses Off Target", "responses_off_target"), + convertString("Currently Overdue", "currently_overdue") ] + results = [] + for account in self.sql.listAccounts(): + stats = self.sql.accountStats( + sqv_from_date = report_from.HTML4(), + sqv_to_date = report_to.HTML4(), + sqv_account_id = account.email + )[0] + results.append((account.email, self.formatTargetEn(account.response_target), + stats[0], stats[1], stats[2])) + ## Other ############################################################ - security.declareProtected('MailManager Reports', 'report_page') - report_page = PageTemplateFile('www/QueueReport', globals()) + if section == 'other': + header = [convertString("Email Addresses", "email_addresses")] + results = [[r.from_email] for r in self.sql.listSenders()] + + ## Queues ########################################################### + + if section == 'queues': + header = [convertString("Queue Name", "queue"), + convertString("Times Queued", "times_queued"), + convertString("Tickets DeQueued", "tickets_dequeued"), + convertString("Average Queue Time", "avg_queue_time"), + convertString("Max Queue Time", "max_queue_time")] - security.declareProtected('MailManager Reports', 'generateReport') - def generateReport(self, REQUEST=None, RESPONSE=None, section='', subsection='', report_from=None, report_to=None): - """ Generate the report information + queuedata = self.performanceByQueue( + report_from = report_from, + report_to = report_to, + period = None + ) - This report engine generates a report in HTML using the report_page - page template. - """ - return self.report_page(REQUEST=REQUEST, RESPONSE=RESPONSE, - section=section, subsection=subsection, - report_from=report_from, report_to=report_to) + results = [] + for queue in queuedata.keys(): + for count in queuedata[queue].keys(): + results.append(( + queue, + '%ist time in queue' % (count+1), + queuedata[queue][count]['num_removed'], + self.formatInterval(queuedata[queue][count]['avg_time']), + self.formatInterval(queuedata[queue][count]['max_time']) + )) + return (header, results) + + # Initialize security. InitializeClass(QueueReportingEngine) Modified: MailManager/branches/RELENG_2_1/www/QueueReport.zpt =================================================================== --- MailManager/branches/RELENG_2_1/www/QueueReport.zpt 2006-04-25 16:30:22 UTC (rev 2939) +++ MailManager/branches/RELENG_2_1/www/QueueReport.zpt 2006-04-25 16:37:04 UTC (rev 2940) @@ -1,547 +1,41 @@ +<div i18n:domain="mailmanager" + tal:define="page string:Reports; + help string:HelpReports; + section options/section | string:snapshot; + subsection options/subsection | string:account; + subsection python:here.fixSubsection(section, subsection, + request.SESSION); + period options/period | string:daily; + report_dates options/report_dates | + python:here.getReportDate(request, period); + report_date_settings python:here.getReportDateSettings(request); + report_from python:report_dates[0]; + report_to python:report_dates[1]; + date_query python:{'query': (report_from, report_to), + 'range': 'minmax'}; + header request/header; + results request/results;"> - <div i18n:domain="mailmanager" - tal:define="page string:Reports; - help string:HelpReports; - section options/section | string:snapshot; - subsection options/subsection | string:account; - subsection python:here.fixSubsection(section, subsection, - request.SESSION); - period options/period | string:daily; - report_dates options/report_dates | - python:here.getReportDate(request, period); - report_date_settings python:here.getReportDateSettings(request); - report_from python:report_dates[0]; - report_to python:report_dates[1]; - date_query python:{'query': (report_from, report_to), - 'range': 'minmax'}" - > + <table cellpadding="5" cellspacing="0" border="0" class="content"> + <thead> + <tr class="table_head"> + <td class="white" tal:repeat="hitem header" tal:content="hitem">Header Item</td> + </tr> + </thead> - - <!-- - ===================== SNAPSHOT ====================== - --> + <tbody> + <tr tal:repeat="res results" + tal:attributes="bgcolor python:test(repeat['res'].odd(), '#d0cfe5', None)"> + <td tal:repeat="item res" align="center" tal:attributes="style python:test(here.checkType(item, ['str','float','long','int', 'unicode']), None, 'text-align: center')"> + <tal:item_text tal:condition="python:here.checkType(item, ['str','float','long','int', 'unicode'])" tal:content="item"> + Body Item + </tal:item_text> + <tal:item_text tal:condition="python:not here.checkType(item, ['str','float','long','int', 'unicode'])" tal:content="structure item"> + Graph Link + </tal:item_text> + </td> + </tr> + </tbody> + </table> - <!-- if section == 'snapshot' and subsection == 'account' --> - <table tal:condition="python:section == 'snapshot' and subsection == 'account'" - cellpadding="5" cellspacing="0" border="0" class="content"> - - <thead> - <tr class="table_head"> - <td class="white" i18n:translate="account">Account</td> - <td class="white" align="center" width="60" - i18n:translate="open">Open</td> - <td class="white" align="center" width="60" - i18n:translate="hold">Hold</td> - <td class="white" align="center" width="60" - i18n:translate="closed">Closed</td> - <td class="white" align="center" width="60" - i18n:translate="overdue">Overdue</td> - <td class="white" align="center" width="60" - i18n:translate="spam">Spam</td> - </tr> - </thead> - - <tbody tal:define="results python:here.getSnapshotData( - subsection='account', - report_from=report_from, - report_to=report_to - )"> - <tr tal:repeat="acc results" - tal:attributes="bgcolor python:test(repeat['acc'].odd(), - '#d0cfe5', None)"> - <td tal:content="acc/email">account</td> - <td tal:content="acc/open" align="center">32</td> - <td tal:content="acc/hold" align="center">33</td> - <td tal:content="acc/closed" align="center">33</td> - <td tal:content="acc/overdue" class="red" align="center">1</td> - <td tal:content="acc/spam" align="center">33</td> - </tr> - <tr> - <td><b i18n:translate="total">Total</b></td> - <td align="center"> - <b tal:content="python:sum([r.open for r in results])">9</b> - </td> - <td align="center"> - <b tal:content="python:sum([r.hold for r in results])">9</b> - </td> - <td align="center"> - <b tal:content="python:sum([r.closed for r in results])">9</b> - </td> - <td align="center"> - <b tal:content="python:sum([r.overdue for r in results])" - class="red">9</b> - </td> - <td align="center"> - <b tal:content="python:sum([r.spam for r in results])">9</b> - </td> - </tr> - </tbody> - </table> - <!-- end if section == 'snapshot' and subsection == 'account' --> - - <!-- if section == 'snapshot' and subsection == 'user' --> - <table tal:condition="python:section == 'snapshot' and subsection == 'user'" - cellpadding="5" cellspacing="0" border="0" class="content"> - <thead> - <tr class="table_head"> - <td class="white">USER</td> - <td class="white" align="center" width="60">OPEN</td> - <td class="white" align="center" width="60">HOLD</td> - <td class="white" align="center" width="60">CLOSED</td> - <td class="white" align="center" width="60">OVERDUE</td> - <td class="white" align="center" width="60">SPAM</td> - </tr> - </thead> - <tbody tal:define="results here/sql/userStatus"> - <tr tal:repeat="key results" - tal:attributes="bgcolor python:test(repeat['key'].odd(), - '#d0cfe5', None)"> - <td tal:content="key/username">username</td> - <td tal:content="key/open" align="center">32</td> - <td tal:content="key/hold" align="center">33</td> - <td tal:content="key/closed" align="center">33</td> - <td tal:content="key/overdue" align="center" class="red">3</td> - <td tal:content="key/spam" align="center">32</td> - </tr> - <tr> - <td><b>Total</b></td> - <td align="center"> - <b tal:content="python:sum([r.open for r in results])">x</b> - </td> - <td align="center"> - <b tal:content="python:sum([r.hold for r in results])">x</b> - </td> - <td align="center"> - <b tal:content="python:sum([r.closed for r in results])">x</b> - </td> - <td align="center"> - <b tal:content="python:sum([r.overdue for r in results])" - class="red">x</b> - </td> - <td align="center"> - <b tal:content="python:sum([r.spam for r in results])">x</b> - </td> - </tr> - </tbody> - </table> - <!-- end if section == 'snapshot' and subsection == 'user' --> - - <!-- if section == 'snapshot' and subsection == 'priorityVaccounts' --> - <table tal:condition="python:section == 'snapshot' and subsection == 'priorityVaccounts'" - cellpadding="5" cellspacing="0" border="0" class="content"> - <thead> - <tr class="table_head"> - <td class="white" i18n:translate="account">Account</td> - <td class="white" align="center" i18n:translate="junk">Junk</td> - <td class="white" align="center" i18n:translate="low">Low</td> - <td class="white" align="center" - i18n:translate="normal">Normal</td> - <td class="white" align="center" i18n:translate="high">High</td> - <td class="white" align="center" - i18n:translate="critical">Critical</td> - </tr> - </thead> - <tbody tal:define="results here/sql/accountPriority"> - <tr tal:repeat="acc results" - tal:attributes="bgcolor python:test(repeat['acc'].odd(), - '#d0cfe5', None)"> - <td tal:content="acc/email">account</td> - <td tal:content="acc/junk" align="center">32</td> - <td tal:content="acc/low" align="center">32</td> - <td tal:content="acc/normal" align="center">32</td> - <td tal:content="acc/high" align="center">32</td> - <td tal:content="acc/critical" align="center">32</td> - </tr> - <tr> - <td><b i18n:translate="total">Total</b></td> - <td align="center"> - <b tal:content="python:sum([r.junk for r in results])">x</b> - </td> - <td align="center"> - <b tal:content="python:sum([r.low for r in results])">x</b> - </td> - <td align="center"> - <b tal:content="python:sum([r.normal for r in results])">x</b> - </td> - <td align="center"> - <b tal:content="python:sum([r.high for r in results])">x</b> - </td> - <td align="center"> - <b tal:content="python:sum([r.critical for r in results])">x</b> - </td> - </tr> - </tbody> - </table> - <!--endif section == 'snapshot' and subsection =='priorityVaccounts'--> - - <!-- if section == 'snapshot' and subsection == 'priorityVusers' --> - <table tal:condition="python:section == 'snapshot' and subsection == 'priorityVusers'" - cellpadding="5" cellspacing="0" border="0" class="content"> - <thead> - <tr class="table_head"> - <td class="white" i18n:translate="user">User</td> - <td class="white" align="center" i18n:translate="junk">Junk</td> - <td class="white" align="center" i18n:translate="low">Low</td> - <td class="white" align="center" - i18n:translate="normal">Normal</td> - <td class="white" align="center" i18n:translate="high">High</td> - <td class="white" align="center" - i18n:translate="critical">Critical</td> - </tr> - </thead> - <tbody tal:define="results here/sql/userPriority"> - <tr tal:repeat="key results" - tal:attributes="bgcolor python:test(repeat['key'].odd(), - '#d0cfe5', None)"> - <td tal:content="key/username">username</td> - <td tal:content="key/junk" align="center">32</td> - <td tal:content="key/low" align="center">32</td> - <td tal:content="key/normal" align="center">32</td> - <td tal:content="key/high" align="center">32</td> - <td tal:content="key/critical" align="center">32</td> - </tr> - <tr> - <td><b i18n:translate="total">Total</b></td> - <td align="center"> - <b tal:content="python:sum([r.junk for r in results])">x</b> - </td> - <td align="center"> - <b tal:content="python:sum([r.low for r in results])">x</b> - </td> - <td align="center"> - <b tal:content="python:sum([r.normal for r in results])">x</b> - </td> - <td align="center"> - <b tal:content="python:sum([r.high for r in results])">x</b> - </td> - <td align="center"> - <b tal:content="python:sum([r.critical for r in results])">x</b> - </td> - </tr> - </tbody> - </table> - <!-- end if section == 'snapshot' and subsection == 'priorityVusers'--> - - - <!-- - ===================== RECEIVED ====================== - --> - - - <!--section:received,subsection:account,not request.has_key('graph')--> - <table tal:condition="python:section == 'received' and subsection == 'account' and not request.has_key('graph')" class="settings"> - <thead> - <tr class="table_head"> - <th i18n:translate="account">Account</th> - <th i18n:translate="new_tickets_opened">New Tickets Opened</th> - <th tal:condition="not:exists:request/excel" - i18n:translate="action" - style="text-align:center">Action</th> - </tr> - </thead> - <tbody> - <tr tal:repeat="acc python:here.sql.emailReceivedByAccount( - sqv_from_date = report_from.HTML4(), - sqv_to_date = report_to.HTML4() - )" - tal:attributes="bgcolor python:test(repeat['acc'].odd(), - '#d0cfe5', None)"> - <td tal:content="acc/account_id">te...@ex...</td> - <td tal:content="acc/tickets" align="center">42</td> - <td style="text-align:center" - tal:condition="not:exists:request/excel"> - <a tal:attributes="href string:Reports?graph=y&account_id=${acc/account_id}" class="state graph">Graph</a> - </td> - </tr> - </tbody> - </table> - <!--end subsection:account,not request.has_key('graph')--> - - <!--section:received,subsection:user,not request.has_key('graph')--> - <table tal:condition="python:section == 'received' and subsection == 'user' and not request.has_key('graph')" class="settings"> - <thead> - <tr class="table_head"> - <th i18n:translate="user">User</th> - <th i18n:translate="new_tickets_opened">New Tickets Opened</th> - <th tal:condition="not:exists:request/excel" - i18n:translate="action" - style="text-align:center">Action</th> - </tr> - </thead> - <tbody> - <tr tal:repeat="key python:here.sql.emailReceivedByUser( - sqv_from_date = report_from.HTML4(), - sqv_to_date = report_to.HTML4() - )" - tal:attributes="bgcolor python:test(repeat['key'].odd(), - '#d0cfe5', None)"> - <span tal:condition="python:key.assigned in [u.username for u in here.listUsers()]"> - <td tal:content="key/assigned">username</td> - <td tal:content="key/tickets">42</td> - <td style="text-align:center" - tal:condition="not:exists:request/excel"> - <a tal:attributes="href string:Reports?graph=y&assigned=${key/assigned}" class="state graph">Graph</a> - </td> - </span> - </tr> - </tbody> - </table> - <!--end subsection:user,not request.has_key('graph')--> - - <!--section:received,subsection:category,not request.has_key(graph)--> - <table tal:condition="python: - section == 'received' and subsection[:8] == 'category' and not request.has_key('graph')" - class="settings"> - <thead> - <tr class="table_head"> - <th tal:define="cat python:int(subsection[8:])" - tal:content="python:here.sql.listCategories(sqv_category_id=int(cat))[0]['label'].upper()"> - Category - </th> - <th i18n:translate="new_tickets_opened">New Tickets Opened</th> - <th tal:condition="not:exists:request/excel" i18n:translate="action">Action</th> - </tr> - </thead> - - <tr tal:define="cat python:int(subsection[8:])" - tal:repeat="key python:here.listCategoryChoices(cat_id = cat)" - tal:attributes="bgcolor python:test(path('repeat/key/odd'), - '#d0cfe5', None)"> - <td tal:content="key/choice">te...@ex...</td> - <td tal:content="python:here.sql.listTickets(**{ - 'sqv_from_date' : report_from.HTML4(), - 'sqv_to_date' : report_to.HTML4(), - 'sqv_count' : 1, - 'sqv_category%d' % cat : key['choice'] - })[0][0]" - align="center">42</td> - <td style="text-align:center" - tal:condition="not:exists:request/excel"> - <a tal:define="catname key/choice" tal:attributes="href string:Reports?graph=y&category$cat=$catname" class="state graph">Graph</a> - </td> - </tr> - </table> - <!--end subsection:category,not request.has_key('graph')--> - - <!-- if section == 'received' and request.has_key('graph') --> - <!-- getGraphDetails returns {'max': max, 'scale': scale, 'label': label, 'values': results} --> - <table tal:condition="python: - section == 'received' and request.has_key('graph')" - tal:define="results python:here.getGraphDetails(report_from,report_to,request.get('account_id', ''),request.get('assigned', ''),request.get('category0', ''),request.get('category1', ''),request.get('category2', '')); - vals python:results['values']; - scale python:results['scale']" - cellpadding="2" cellspacing="0" border="0" class="content"> - - <tr class="table_head"> - <td class="white" colspan="32" i18n:translate="Graph">Graph</td> - </tr> - <tr> - <td> </td> - </tr> - <tr> - <td></td> - - <td tal:repeat="row vals" valign="bottom" width="20"> - <img tal:attributes="height python:int(row * scale); alt row" - src="images/bar.jpg" width="9" /> - </td> - </tr> - <tr> - <th>Tickets</th> - <td tal:repeat="row vals" - tal:content="python:test(row, row, ' ')">12</td> - </tr> - <tr> - <th tal:content="python:test(period == 'daily', 'Day', 'Month')"> - Day - </th> - <td tal:repeat="row vals" - tal:content="repeat/row/number">12</td> - </tr> - </table> - <!-- end if section == 'received' and request.has_key('graph') --> - - - <!-- - ===================== PERFORMANCE ====================== - --> - - - <!-- if section == 'performance' and subsection == 'account' --> - <table tal:condition="python: - section == 'performance' and subsection == 'account'" - cellpadding="5" cellspacing="0" border="0" class="content"> - <thead> - <tr class="table_head"> - <td class="white" i18n:translate="account">Account</td> - <td class="white" align="center" - i18n:translate="max_response">Max<br />Response Time</td> - <td class="white" align="center" - i18n:translate="avg_response">Average<br />Response Time</td> - <td class="white" align="center" - i18n:translate="max_close">Max<br />Close Time</td> - <td class="white" align="center" - i18n:translate="avg_close">Average<br />Close Time</td> - </tr> - </thead> - <tbody> - <tr tal:repeat="stat python:here.sql.performanceByAccount( - sqv_from_date = report_from.HTML4(), - sqv_to_date = report_to.HTML4() - )" - tal:attributes="bgcolor python:test(repeat['stat'].odd(), - '#d0cfe5', None)"> - <td tal:content="stat/email">te...@ex...</td> - <td align="center" tal:content="python:here.formatInterval(stat.max_respond_time)"> </td> - <td align="center" tal:content="python:here.formatInterval(stat.avg_respond_time)"> </td> - <td align="center" tal:content="python:here.formatInterval(stat.max_close_time)"> </td> - <td align="center" tal:content="python:here.formatInterval(stat.avg_close_time)"> </td> - </tr> - </tbody> - </table> - <!-- end if section == 'performance' and subsection == 'account' --> - - <!-- if section == 'performance' and subsection == 'user' --> - <table tal:condition="python: - section == 'performance' and subsection == 'user'" - cellpadding="5" cellspacing="0" border="0" class="content"> - <thead> - <tr class="table_head"> - <td class="white" i18n:translate="user">User</td> - <td class="white" align="center" - i18n:translate="max_response">Max<br />Response Time</td> - <td class="white" align="center" - i18n:translate="avg_response">Average<br />Response Time</td> - <td class="white" align="center" - i18n:translate="max_close">Max<br />Close Time</td> - <td class="white" align="center" - i18n:translate="avg_close">Average<br />Close Time</td> - </tr> - </thead> - <tbody> - <tr tal:repeat="stat python:here.sql.performanceByUser( - from_date = report_from.HTML4(), - to_date = report_to.HTML4() - )" - tal:attributes="bgcolor python:test(repeat['stat'].odd(), - '#d0cfe5', None)"> - <span tal:condition="python:stat.username in [u.username for u in here.listUsers()]"> - <td tal:content="stat/username">User Name</td> - <td align="center" tal:content="python:here.formatInterval(stat.max_respond_time)"> </td> - <td align="center" tal:content="python:here.formatInterval(stat.avg_respond_time)"> </td> - <td align="center" tal:content="python:here.formatInterval(stat.max_close_time)"> </td> - <td align="center" tal:content="python:here.formatInterval(stat.avg_close_time)"> </td> - </span> - </tr> - </tbody> - </table> - <!-- end if section == 'performance' and subsection == 'user' --> - - <!-- if section == 'performance' and subsection == 'targets' --> - <table tal:condition="python: - section == 'performance' and subsection == 'targets'" - cellpadding="5" cellspacing="0" border="0" class="content"> - <tr class="table_head"> - <td class="white" i18n:translate="account">Account</td> - <td class="white" i18n:translate="target_time">Target Time</td> - <td class="white" - i18n:translate="responses_on_target" >Responses<br />On Target</td> - <td class="white" - i18n:translate="responses_off_target">Responses<br />Off Target</td> - <td class="white - " i18n:translate="currently_overdue">Currently<br />Overdue</td> - </tr> - - <tr tal:repeat="acc python:here.sql.listAccounts(sqv_email='')" - tal:attributes="bgcolor python:test(path('repeat/acc/odd'), - '#d0cfe5', None)"> - <td tal:content="acc/email">te...@ex...</td> - <tal:stats define="stats python:here.sql.accountStats( - sqv_from_date = report_from.HTML4(), - sqv_to_date = report_to.HTML4(), - sqv_account_id=acc.email - )[0]"> - <td tal:content="python:here.formatTargetEn(acc.response_target)"> - 1 day - </td> - <td tal:content="python:stats[0]"> - 23 - </td> - <td tal:content="python:stats[1]"> - 3 - </td> - <td tal:content="python:stats[2]"> - 3 - </td> - </tal:stats> - </tr> - - </table> - <!-- end if section == 'performance' and subsection == 'targets' --> - - - <!-- - ===================== OTHER ====================== - --> - - - <!-- if section == 'other' --> - <table tal:condition="python:section == 'other'" - cellpadding="5" cellspacing="0" border="0" class="content"> - <tr class="table_head"> - <td class="white" i18n:translate="email_addresse">Email Addresses</td> - </tr> - <tr tal:repeat="address here/sql/listSenders"> - <td tal:content="address/from_email">te...@ex...</td> - </tr> - </table> - <!-- end if section == 'other' --> - - - <!-- - ===================== QUEUES ====================== - --> - - <!-- if section == 'queues' and subsection == 'account' --> - <table tal:condition="python: - section == 'queues' and subsection == 'account'" - tal:define="queuedata python:here.performanceByQueue( - report_from = report_from, - report_to = report_to, - period = period - )" - tal:repeat="queuename queuedata" - cellpadding="5" cellspacing="0" border="0" class="content"> - <thead class="table_head"> - <tr> - <td class="white" i18n:translate="queue" tal:content="python:'Queue %s' % queuename">Queue Incoming</td> - <td class="white" i18n:translate="queue">Times <br />Queued</td> - <td class="white" align="center" - i18n:translate="max_response">Tickets Dequeued</td> - <td class="white" align="center" - i18n:translate="avg_response">Average<br />Queue Time</td> - <td class="white" align="center" - i18n:translate="max_close">Max<br />Queue Time</td> - </tr> - </thead> - <tbody> - <tr tal:repeat="count python:queuedata[queuename]" - tal:attributes="bgcolor python:test(repeat['count'].odd(), - '#d0cfe5', None)"> - <td></td> - <td tal:content="python:'%ist time in queue' % (count+1)">0</td> - <td align="center" tal:content="python:queuedata[queuename][count]['num_removed']"> </td> - <td align="center" tal:content="python:here.formatInterval(queuedata[queuename][count]['avg_time'])"> </td> - <td align="center" tal:content="python:here.formatInterval(queuedata[queuename][count]['max_time'])"> </td> - </tr> - </tbody> - - </table> - <!-- end if section == 'performance' and subsection == 'account' --> - - - <!-- - ===================== END ====================== - --> - - </div> +</div> Modified: MailManager/branches/RELENG_2_1/www/QueueReports.zpt =================================================================== --- MailManager/branches/RELENG_2_1/www/QueueReports.zpt 2006-04-25 16:30:... [truncated message content] |
From: <ke...@us...> - 2006-04-26 13:00:53
|
Revision: 2941 Author: kevca Date: 2006-04-26 06:00:43 -0700 (Wed, 26 Apr 2006) ViewCVS: http://svn.sourceforge.net/mailmanager/?rev=2941&view=rev Log Message: ----------- Fixes for - getMail fails with TypeError (#1476878) A large number of calls are still calling a ZPT and passing self as the first parameter, even though that is implicit by the preceeding self. prefix. Modified Paths: -------------- MailManager/branches/RELENG_2_1/CHANGES.txt MailManager/branches/RELENG_2_1/Extensions/AccountPluggableBrain.py MailManager/branches/RELENG_2_1/Extensions/TicketPluggableBrain.py MailManager/branches/RELENG_2_1/MailManager.py MailManager/branches/RELENG_2_1/Reporting.py Modified: MailManager/branches/RELENG_2_1/CHANGES.txt =================================================================== --- MailManager/branches/RELENG_2_1/CHANGES.txt 2006-04-25 16:37:04 UTC (rev 2940) +++ MailManager/branches/RELENG_2_1/CHANGES.txt 2006-04-26 13:00:43 UTC (rev 2941) @@ -1,5 +1,6 @@ Version 2.1-RC3 * BUG FIXES +- getMail fails with TypeError (#1476878) - Export to Excel on Reports (#1460909) - Empty abilities are not being handled correctly (#1475604) - History Display Issues (#1460955) Modified: MailManager/branches/RELENG_2_1/Extensions/AccountPluggableBrain.py =================================================================== --- MailManager/branches/RELENG_2_1/Extensions/AccountPluggableBrain.py 2006-04-25 16:37:04 UTC (rev 2940) +++ MailManager/branches/RELENG_2_1/Extensions/AccountPluggableBrain.py 2006-04-26 13:00:43 UTC (rev 2941) @@ -564,7 +564,7 @@ elif self.server_type == 'IMAP SSL': ret = self._getIMAP(ssl=True) if REQUEST is not None and not noreturn: - return self.index_html(self, REQUEST) + return self.index_html(REQUEST) return ret security.declareProtected('MailManager Manage Tickets', Modified: MailManager/branches/RELENG_2_1/Extensions/TicketPluggableBrain.py =================================================================== --- MailManager/branches/RELENG_2_1/Extensions/TicketPluggableBrain.py 2006-04-25 16:37:04 UTC (rev 2940) +++ MailManager/branches/RELENG_2_1/Extensions/TicketPluggableBrain.py 2006-04-26 13:00:43 UTC (rev 2941) @@ -93,7 +93,7 @@ security.declareProtected('MailManager Manage Tickets', 'index_html') def index_html(self, REQUEST): """Provide a view for the simple direct traversal.""" - return self.ticket_index_html(self, REQUEST) + return self.ticket_index_html(REQUEST) security.declareProtected('MailManager Manage Tickets', 'findSupporter') def findSupporter(self, REQUEST, SESSION): @@ -107,13 +107,13 @@ handled in the zpts. """ SESSION.set('find_supporter', self.id) - return self.ticket_index_html(self, REQUEST) + return self.ticket_index_html(REQUEST) security.declareProtected('MailManager Manage Tickets', 'change') def change(self, REQUEST): """Return a view allowing the details to be changed.""" REQUEST.set('details', 'y') - return self.ticket_index_html(self, REQUEST) + return self.ticket_index_html(REQUEST) security.declareProtected('MailManager Manage Tickets', 'http_save') @@ -166,7 +166,7 @@ if error is not None: REQUEST.set('error', error) REQUEST.set('details', 'y') - return self.ticket_index_html(self, REQUEST) + return self.ticket_index_html(REQUEST) changed_by = getSecurityManager().getUser().getUserName() if changed_by == 'Anonymous User': changed_by = '' @@ -296,7 +296,7 @@ del REQUEST.SESSION['show_headers'] else: REQUEST.SESSION.set('show_headers', True) - return self.ticket_index_html(self, REQUEST) + return self.ticket_index_html(REQUEST) security.declareProtected('MailManager Manage Tickets', 'showHTML') def showHTML(self, REQUEST): @@ -536,7 +536,7 @@ sqv_ticket_id = self.id)[0].last_modified.strftime('%s') if not last_modified == mdate: REQUEST.set('error', 'This ticket has been modified. Please check it, and then retry your request') - return self.ticket_index_html(self, REQUEST) + return self.ticket_index_html(REQUEST) changed_by = getSecurityManager().getUser().getUserName() if changed_by == 'Anonymous User': changed_by = '' @@ -604,14 +604,14 @@ sqv_ticket_id = self.id)[0].last_modified.strftime('%s') if not last_modified == mdate: REQUEST.set('error', 'This ticket has been modified. Please check it, and then retry your request') - return self.ticket_index_html(self, REQUEST) + return self.ticket_index_html(REQUEST) if change_state: if change_state == 'Closed' and not self._okToClose(): if REQUEST is not None: REQUEST.set('error', 'All supporting tickets must be Closed.') REQUEST.set('flag_supporters', 1) - return self.ticket_index_html(self, REQUEST) + return self.ticket_index_html(REQUEST) raise BadRequest, 'All supporting tickes must be Closed.' if sendmail: @@ -619,14 +619,14 @@ if REQUEST is not None: REQUEST.set('error', 'The To field may not be empty') REQUEST.set('flag_mail_to', 1) - return self.ticket_index_html(self, REQUEST) + return self.ticket_index_html(REQUEST) raise BadRequest, 'The To field may not be empty' if not self.validEmail(mail_to): if REQUEST is not None: REQUEST.set('error', '%s is not a valid email address.' % mail_to) REQUEST.set('flag_mail_to', 1) - return self.ticket_index_html(self, REQUEST) + return self.ticket_index_html(REQUEST) raise BadRequest, 'The To field may not be empty' else: @@ -643,7 +643,7 @@ REQUEST.set('flag_cc', True) if bcc: REQUEST.set('flag_bcc', True) - return self.ticket_index_html(self, REQUEST) + return self.ticket_index_html(REQUEST) raise BadRequest, 'Private notes may not be sent to anyone.' # from_name will be the username of the logged in user. This will be @@ -688,7 +688,7 @@ REQUEST.set('flag_mail_to', True) REQUEST.set('flag_cc', True) REQUEST.set('flag_bcc', True) - return self.ticket_index_html(self, REQUEST) + return self.ticket_index_html(REQUEST) else: raise BadRequest, error_msg @@ -863,7 +863,7 @@ sqv_ticket_id = self.id)[0].last_modified.strftime('%s') if not last_modified == mdate: REQUEST.set('error', 'This ticket has been modified. Please check it, and then retry your request') - return self.ticket_index_html(self, REQUEST) + return self.ticket_index_html(REQUEST) # Generate and process the event user = getSecurityManager().getUser().getUserName() @@ -1123,14 +1123,14 @@ REQUEST.SESSION.set('attachments', attachments) REQUEST.set('autojump', True) print "Back to the ticket" - return self.ticket_index_html(self, REQUEST) + return self.ticket_index_html(REQUEST) security.declareProtected('MailManager Manage Tickets', 'delAttachment') def delAttachment(self, REQUEST, RESPONSE, ids=[]): """Delete an attachment.""" for id in ids: del REQUEST.SESSION['attachments'][id] - return self.ticket_index_html(self, REQUEST) + return self.ticket_index_html(REQUEST) security.declareProtected('MailManager Manage Tickets', 'getPrevNext') def getPrevNext(self, request): @@ -1646,8 +1646,7 @@ eventTime = mx.DateTime.strptime( self.date_opened.strftime('%Y-%m-%d %H:%M:%S'), '%Y-%m-%d %H:%M:%S') + \ - mx.DateTime.DateTimeDelta(0,0,0,resptarget - ) + mx.DateTime.DateTimeDelta(0,0,0,resptarget) print "Queuing overdue status for ticket %i for time %s" % (self.id, eventTime) return DateTime(eventTime.strftime('%Y-%m-%d %H:%M:%S')) Modified: MailManager/branches/RELENG_2_1/MailManager.py =================================================================== --- MailManager/branches/RELENG_2_1/MailManager.py 2006-04-25 16:37:04 UTC (rev 2940) +++ MailManager/branches/RELENG_2_1/MailManager.py 2006-04-26 13:00:43 UTC (rev 2941) @@ -1033,7 +1033,7 @@ for accountname in [acct['email'] for acct in self.sql.listAccounts(sqv_email='')]] if REQUEST is not None and not noreturn: - return self.index_html(self, REQUEST) + return self.index_html(REQUEST) return '\n'.join(filter(None, rets)) security.declareProtected('MailManager Settings', 'testMail') Modified: MailManager/branches/RELENG_2_1/Reporting.py =================================================================== --- MailManager/branches/RELENG_2_1/Reporting.py 2006-04-25 16:37:04 UTC (rev 2940) +++ MailManager/branches/RELENG_2_1/Reporting.py 2006-04-26 13:00:43 UTC (rev 2941) @@ -524,7 +524,7 @@ REQUEST.SESSION.set('report_date_settings', (month, year)) REQUEST.SESSION.set('report_dates', self.getReportDate(REQUEST, period)) - return self.index_html(self, REQUEST) + return self.index_html(REQUEST) security.declareProtected('MailManager Reports', 'getReportDateSettings') def getReportDateSettings(self, REQUEST): This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ke...@us...> - 2006-04-26 13:18:14
|
Revision: 2943 Author: kevca Date: 2006-04-26 06:17:57 -0700 (Wed, 26 Apr 2006) ViewCVS: http://svn.sourceforge.net/mailmanager/?rev=2943&view=rev Log Message: ----------- Fix for - No strptime on windows (#1476818) Now includes a python based strptime module Modified Paths: -------------- MailManager/branches/RELENG_2_1/CHANGES.txt MailManager/branches/RELENG_2_1/Extensions/TicketPluggableBrain.py MailManager/branches/RELENG_2_1/ruleset/common.py MailManager/branches/RELENG_2_1/ruleset/engine.py Added Paths: ----------- MailManager/branches/RELENG_2_1/support/strptime.py Modified: MailManager/branches/RELENG_2_1/CHANGES.txt =================================================================== --- MailManager/branches/RELENG_2_1/CHANGES.txt 2006-04-26 13:17:26 UTC (rev 2942) +++ MailManager/branches/RELENG_2_1/CHANGES.txt 2006-04-26 13:17:57 UTC (rev 2943) @@ -1,5 +1,6 @@ Version 2.1-RC3 * BUG FIXES +- No strptime on windows (#1476818) - getMail fails with TypeError (#1476878) - Export to Excel on Reports (#1460909) - Empty abilities are not being handled correctly (#1475604) Modified: MailManager/branches/RELENG_2_1/Extensions/TicketPluggableBrain.py =================================================================== --- MailManager/branches/RELENG_2_1/Extensions/TicketPluggableBrain.py 2006-04-26 13:17:26 UTC (rev 2942) +++ MailManager/branches/RELENG_2_1/Extensions/TicketPluggableBrain.py 2006-04-26 13:17:57 UTC (rev 2943) @@ -46,7 +46,9 @@ from AccessControl import getSecurityManager +from Products.MailManager.support.strptime import strptime + from_escape = re.compile('^>*From ') sig_remover = re.compile('^-- $.*\Z', re.DOTALL | re.MULTILINE) @@ -1644,8 +1646,8 @@ else: resptarget = 0 - eventTime = mx.DateTime.strptime( - self.date_opened.strftime('%Y-%m-%d %H:%M:%S'), '%Y-%m-%d %H:%M:%S') + \ + eventTime = mx.DateTime.mktime( + strptime(self.date_opened.strftime('%Y-%m-%d %H:%M:%S'), '%Y-%m-%d %H:%M:%S')) + \ mx.DateTime.DateTimeDelta(0,0,0,resptarget) print "Queuing overdue status for ticket %i for time %s" % (self.id, eventTime) Modified: MailManager/branches/RELENG_2_1/ruleset/common.py =================================================================== --- MailManager/branches/RELENG_2_1/ruleset/common.py 2006-04-26 13:17:26 UTC (rev 2942) +++ MailManager/branches/RELENG_2_1/ruleset/common.py 2006-04-26 13:17:57 UTC (rev 2943) @@ -29,7 +29,10 @@ from mx.DateTime.ISO import ParseDateTime import time +from Products.MailManager.support.strptime import strptime + + class SecurityError(Exception): """ An attempt to handle an event generated by a user who does not have the correct permissions. @@ -125,7 +128,9 @@ def calculateOverdue(self): if 'responded' not in self.attributes and not self.state == 'overdue': - return mx.DateTime.strptime('2001-01-01', '%Y-%m-%d') + mx.DateTime.DateTimeDelta(0,1) + return mx.DateTime.mktime( + strptime('2001-01-01', '%Y-%m-%d') + ) + mx.DateTime.DateTimeDelta(0,1) else: return None Modified: MailManager/branches/RELENG_2_1/ruleset/engine.py =================================================================== --- MailManager/branches/RELENG_2_1/ruleset/engine.py 2006-04-26 13:17:26 UTC (rev 2942) +++ MailManager/branches/RELENG_2_1/ruleset/engine.py 2006-04-26 13:17:57 UTC (rev 2943) @@ -38,6 +38,8 @@ from Products.MailManager.support.logger import log +from Products.MailManager.support.strptime import strptime + try: from AccessControl import getSecurityManager except ImportError: @@ -203,7 +205,8 @@ will work correctly with the database backend. """ - offsetdate = mx.DateTime.strptime('2001-01-01', '%Y-%m-%d') + mx.DateTime.DateTimeDelta(0,self.houroffset) + offsetdate = mx.DateTime.mktime(strptime('2001-01-01', '%Y-%m-%d')) + \ + mx.DateTime.DateTimeDelta(0,self.houroffset) return DateTime.DateTime(offsetdate.strftime('%Y-%m-%d %H:%M:%S')) Added: MailManager/branches/RELENG_2_1/support/strptime.py =================================================================== --- MailManager/branches/RELENG_2_1/support/strptime.py (rev 0) +++ MailManager/branches/RELENG_2_1/support/strptime.py 2006-04-26 13:17:57 UTC (rev 2943) @@ -0,0 +1,311 @@ +""" strptime version 1.7, Time-stamp: <01/05/31 13:07:02 flognat> + The reverse of strftime. + +BTW This functionality seems to be in Python 2.1! + + Copyright (C) 2001 Andrew Markebo, fl...@fu... + + This is free software; unrestricted redistribution is allowed under the + terms of the LGPL. For full details of the license conditions of this + software, see the GNU LESSER GENERAL PUBLIC LICENSE + http://www.gnu.org/copyleft/lesser.txt + + And here comes the documentation: + + Throw a string and a format specification at strptime and if everything + is ok you will get a tuple containing 9 items that are compatible with + pythons time-module. + + interface: + strptime(inputstring, formatstring) + + Little errorchecking... so you'd better now what you are doing. + + example: + from strptime import * + mktime(strptime("26/6 1973", "%d/%m %Y")) + + And voila you have the second when the author of this function was born. + + The supported format identifiers are: + %a weekday in short text-form, e.g. Mon + %A weekday in long text-form, e.g. Monday + %b month in short text-form, e.g. Jul + %B month in long text-form e.g. July + %c the format specified by DateAndTimeRepresentation + %d the day in month in numeric form, e.g. 24 + %H hour in 24 hour form + %j julian day (day of year) + %m month in numeric format + %M minute + %S second + %T Time in '%H:%M:%S'-format + %w weekday, 0=monday + %x date in format represented by DateRepresentation + %X time in format represented by TimeRepresentation + %y year in short form + %Y year in long form + %% %-sign + + I have done some thinking here (*REALLY*) and it is possible to configure + this module so it uses other languages by adding their names to the + dictionaries first in the file, and setting the variable LANGUAGE. + + For your exercise I have inserted the swedish names ;-) + + The lfind, name, complex, numbers and parse functions are for internal + use, called by strptime. + + Uh.. oh yeah.. if you want to get in touch with me.. I am reachable + at fl...@fu..., the newest version of this file can probably + be found somewhere close to http://www.fukt.hk-r.se/~flognat + + Story: + 9th of October 2000, upgraded 1.3 to 1.4, + changed license from GPL to LGPL, and updated + my addresses. + 23rd of May 2001, updated 1.4 to 1.5, + Additions by BroytMann, ph...@ph.... + "I've added Russian language (koi8-r and windows-1251 encodings)" + 31st of May 2001, 1.5-1.6 OOps not Y2K compatible.. + long years became 1900, not 200 :-) + 25 Jul 2001 1.7 Some minor problems with the fix above + """ + +import string + +LongDayNames={ 'English' : [ 'Monday', 'Tuesday', 'Wednesday', + 'Thursday', 'Friday', 'Saturday', 'Sunday'], + 'Swedish' : [ 'M\xE5ndag', 'Tisdag', 'Onsdag', 'Torsdag', + 'Fredag', 'L\xF6rdag', 'S\xF6ndag'], + 'Russian' : [ '\xF0\xCF\xCE\xC5\xC4\xC5\xCC\xD8\xCE\xC9\xCB', '\xF7\xD4\xCF\xD2\xCE\xC9\xCB', '\xF3\xD2\xC5\xC4\xC1', '\xFE\xC5\xD4\xD7\xC5\xD2\xC7', + '\xF0\xD1\xD4\xCE\xC9\xC3\xC1', '\xF3\xD5\xC2\xC2\xCF\xD4\xC1', '\xF7\xCF\xD3\xCB\xD2\xC5\xD3\xC5\xCE\xD8\xC5'], + 'Windows-1251' : [ '\xCF\xEE\xED\xE5\xE4\xE5\xEB\xFC\xED\xE8\xEA', '\xC2\xF2\xEE\xF0\xED\xE8\xEA', '\xD1\xF0\xE5\xE4\xE0', '\xD7\xE5\xF2\xE2\xE5\xF0\xE3', + '\xCF\xFF\xF2\xED\xE8\xF6\xE0', '\xD1\xF3\xE1\xE1\xEE\xF2\xE0', '\xC2\xEE\xF1\xEA\xF0\xE5\xF1\xE5\xED\xFC\xE5'] +} + +ShortDayNames={ 'English' : [ 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], + 'Swedish' : [ 'M\xE5n', 'Tis', 'Ons', 'Tor', 'Fre', 'L\xF6r', 'S\xF6n'], + 'Russian' : [ '\xF0\xCE', '\xF7\xD4', '\xF3\xD2', '\xFE\xD4', '\xF0\xD4', '\xF3\xC2', '\xF7\xD3\xCB'], + 'Windows-1251' : [ '\xCF\xED', '\xC2\xF2', '\xD1\xF0', '\xD7\xF2', '\xCF\xF2', '\xD1\xE1', '\xC2\xF1\xEA'] +} + +LongMonthNames={ 'English' : ['none', 'January', 'February', 'March', 'April', + 'May', 'June', 'July', 'August', 'September', + 'October', 'November', 'December'], + 'Swedish' : ['none', 'Januari', 'Februari', 'Mars', 'April', + 'Maj', 'Juni', 'Juli', 'Augusti','September', + 'Oktober', 'November', 'December'], + 'Russian' : ['\xCE\xC5\xD4', '\xF1\xCE\xD7\xC1\xD2\xD8', '\xE6\xC5\xD7\xD2\xC1\xCC\xD8', '\xED\xC1\xD2\xD4', '\xE1\xD0\xD2\xC5\xCC\xD8', + '\xED\xC1\xCA', '\xE9\xC0\xCE\xD8', '\xE9\xC0\xCC\xD8', '\xE1\xD7\xC7\xD5\xD3\xD4', '\xF3\xC5\xCE\xD4\xD1\xC2\xD2\xD8', + '\xEF\xCB\xD4\xD1\xC2\xD2\xD8', '\xEE\xCF\xD1\xC2\xD2\xD8', '\xE4\xC5\xCB\xC1\xC2\xD2\xD8'], + 'Windows-1251' : ['\xED\xE5\xF2', '\xDF\xED\xE2\xE0\xF0\xFC', '\xD4\xE5\xE2\xF0\xE0\xEB\xFC', '\xCC\xE0\xF0\xF2', '\xC0\xEF\xF0\xE5\xEB\xFC', + '\xCC\xE0\xE9', '\xC8\xFE\xED\xFC', '\xC8\xFE\xEB\xFC', '\xC0\xE2\xE3\xF3\xF1\xF2', '\xD1\xE5\xED\xF2\xFF\xE1\xF0\xFC', + '\xCE\xEA\xF2\xFF\xE1\xF0\xFC', '\xCD\xEE\xFF\xE1\xF0\xFC', '\xC4\xE5\xEA\xE0\xE1\xF0\xFC'] +} + +ShortMonthNames={ 'English' : ['none', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + 'Swedish' : ['none', 'Jan', 'Feb', 'Mar', 'Apr', 'Maj', 'Jun', + 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dec'], + 'Russian' : ['\xCE\xC5\xD4', '\xF1\xCE\xD7', '\xE6\xD7\xD2', '\xED\xC1\xD2', '\xE1\xD0\xD2', '\xED\xC1\xCA', + '\xE9\xC0\xCE', '\xE9\xC0\xCC', '\xE1\xD7\xC7', '\xF3\xCE\xD4', '\xEF\xCB\xD4', '\xEE\xCF\xD1', '\xE4\xC5\xCB'], + 'Windows-1251' : ['\xED\xE5\xF2', '\xDF\xED\xE2', '\xD4\xE2\xF0', '\xCC\xE0\xF0', '\xC0\xEF\xF0', '\xCC\xE0\xE9', + '\xC8\xFE\xED', '\xC8\xFE\xEB', '\xC0\xE2\xE3', '\xD1\xED\xF2', '\xCE\xEA\xF2', '\xCD\xEE\xFF', '\xC4\xE5\xEA'] +} + +DateAndTimeRepresentation={ 'English' : '%a %b %d %H:%m:%S %Y', + 'Swedish' : '%a %d %b %Y %H:%m:%S', + 'Russian' : '%a %d %b %Y %H:%M:%S', + 'Windows-1251' : '%a %d %b %Y %H:%M:%S' +} + +DateRepresentation = { 'English' : '%m/%d/%y', + 'Swedish' : '%d/%m/%y', + 'Russian' : '%d-%m-%y', + 'Windows-1251' : '%d-%m-%y' +} + +TimeRepresentation = { 'English' : '%H:%M:%S', + 'Swedish' : '%H:%M:%S', + 'Russian' : '%H:%M:%S', + 'Windows-1251' : '%H:%M:%S' +} + +LANGUAGE='English' + +BadFormatter='An illegal formatter was given' + +#Check if string begins with substr +def lfind(str, substr): + return string.lower(str[:len(substr)])==string.lower(substr) + +#atoms consisting of other atoms +def complex(str, format, base): + code=format[:1] + if code=='c': + string=DateAndTimeRepresentation[LANGUAGE] + elif code=='T': + string='%H:%M:%S' + elif code=='x': + string=DateRepresentation[LANGUAGE] + elif code=='X': + string=TimeRepresentation[LANGUAGE] + + return parse(str, string, base) + +#string based names +def names(str, format, base): + code=format[:1] + if code=='a': + selection=ShortDayNames[LANGUAGE] + result='weekd' + elif code=='A': + selection=LongDayNames[LANGUAGE] + result='weekd' + elif code=='b': + selection=ShortMonthNames[LANGUAGE] + result='month' + elif code=='B': + selection=LongMonthNames[LANGUAGE] + result='month' + + match=None + for i in selection: + if lfind(str, i): + match=i + break + + base[result]=selection.index(match) + return len(match) + +#numeric stuff +def numeric(str, format, base): + code=format[:1] + if code=='d': result='day' + elif code=='H': result='hour' + elif code=='j': result='juliand' + elif code=='m': result='month' + elif code=='M': result='min' + elif code=='S': result='sec' + elif code=='w': result='weekd' + elif code=='y': result='shortYear' + elif code=='Y': result='year' + + i=0 + while str[i] in string.whitespace: i=i+1 + j=i + if len(format)>1: + while not str[j] in string.whitespace and str[j]!=format[1]: j=j+1 + else: + try: + while not str[j] in string.whitespace: j=j+1 + except IndexError: + pass + + # hmm could check exception here, but what could I add? + base[result]=string.atoi(str[i:j]) + + return j + +parseFuns={ 'a':names, 'A':names, 'b':names, 'B':names, 'c':complex, 'd':numeric, + 'H':numeric, 'j':numeric, 'm':numeric, 'M':numeric, 'S':numeric, + 'T':complex, 'w':numeric, 'x':complex, 'y':numeric, 'Y':numeric} + +# Well split up in atoms, reason to why this is separated from atrptime +# is to be able to reparse complex atoms +def parse(str, format, base): + atoms=string.split(format, '%') + charCounter=0 + atomCounter=0 + + # Hey I am laazy and think that the format is exactly what the string is! + charCounter=charCounter+len(atoms[atomCounter]) + atomCounter=atomCounter+1 + + while atomCounter < len(atoms) and charCounter < len(str): + atom=atoms[atomCounter] + + if atom=='': # escaped + charCounter=charCounter+1 + atomCounter=atomCounter+1 + charCounter=charCounter+len(atoms[atomCounter]) + else: + try: + parsefunction=parseFuns[atom[:1]] + except KeyError: + raise BadFormatter, atom[:1] + grabbed=apply(parsefunction, (str[charCounter:], atom, base)) + charCounter=charCounter+grabbed+len(atom)-1 + + atomCounter=atomCounter+1 + + return charCounter + +# Ok here we go, tadaaa --> STRPTIME <-- at last.. +def strptime(str, format): + """Converts str specified by format to tuple useable by the time module""" + returnTime={} + returnTime['year']=0 + returnTime['shortYear']=None + returnTime['month']=0 + returnTime['day']=0 + returnTime['hour']=0 + returnTime['min']=0 + returnTime['sec']=0 + returnTime['weekd']=0 + returnTime['juliand']=0 + returnTime['dst']=0 + + parse(str, format, returnTime) + + ######################## + # Preparation for more configurability + # (Oops it wasn't Y2k compatible.. :-) LongYear was 1900 prefixed, not 2000 + # Now we decide on YEARSPLIT if it is 2000 or 1900 + # Will have to dig on this some, in the future + + # CENTURY=2000 + CENTURY=None + YEARSPLIT=50 + + if returnTime['shortYear']!=None: + if CENTURY!=None: + returnTime['year']=returnTime['shortYear']+CENTURY + else: + if returnTime['shortYear'] <= YEARSPLIT: + returnTime['year']=returnTime['shortYear']+2000 + else: + returnTime['year']=returnTime['shortYear']+1900 + + + return (returnTime['year'], returnTime['month'], returnTime['day'], + returnTime['hour'], returnTime['min'], returnTime['sec'], + returnTime['weekd'], returnTime['juliand'], returnTime['dst']) + +# just for my convenience +def strpdebug(): + import pdb + pdb.run('strptime("% Tue 3 Feb", "%% %a %d %b")') + +def test(): + from time import * + a=asctime(localtime(time())) + print a + b=strptime(a, '%a %b %d %H:%M:%S %Y') + print b + print asctime(b) + + print strptime("%% % Tue 3 Feb", "%%%% %% %a %d %b") + print strptime('Thu, 12 Sep 1996 19:42:06 GMT', '%a, %d %b %Y %T GMT') + try: + print strptime('Thu, 12 Sep 1996 19:42:06 GMT', '%a, %d %b %Y %T') + except ValueError: + print "Error as expected.. unconverted data remains" + + print strptime('Thu, 12 Sep 1996 19:42:06', '%a, %d %b %Y %T') + +if __name__ == '__main__': + test() + + This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ke...@us...> - 2006-04-26 16:52:58
|
Revision: 2946 Author: kevca Date: 2006-04-26 09:52:43 -0700 (Wed, 26 Apr 2006) ViewCVS: http://svn.sourceforge.net/mailmanager/?rev=2946&view=rev Log Message: ----------- Fixes for - AutoLogout not working correctly (#1477081) Modified Paths: -------------- MailManager/branches/RELENG_2_1/CHANGES.txt MailManager/branches/RELENG_2_1/MailManager.py MailManager/branches/RELENG_2_1/debian/mailmanager.postinst MailManager/branches/RELENG_2_1/ruleset/engine.py MailManager/branches/RELENG_2_1/ruleset/zope.py MailManager/branches/RELENG_2_1/sql/v2_1/getHistoricalTickets.zsql MailManager/branches/RELENG_2_1/support/Database.py MailManager/branches/RELENG_2_1/support/login.py MailManager/branches/RELENG_2_1/tests/testAutoLogout.py Modified: MailManager/branches/RELENG_2_1/CHANGES.txt =================================================================== --- MailManager/branches/RELENG_2_1/CHANGES.txt 2006-04-26 15:07:28 UTC (rev 2945) +++ MailManager/branches/RELENG_2_1/CHANGES.txt 2006-04-26 16:52:43 UTC (rev 2946) @@ -1,5 +1,6 @@ Version 2.1-RC3 * BUG FIXES +- AutoLogout not working correctly (#1477081) - No strptime on windows (#1476818) - getMail fails with TypeError (#1476878) - Export to Excel on Reports (#1460909) Modified: MailManager/branches/RELENG_2_1/MailManager.py =================================================================== --- MailManager/branches/RELENG_2_1/MailManager.py 2006-04-26 15:07:28 UTC (rev 2945) +++ MailManager/branches/RELENG_2_1/MailManager.py 2006-04-26 16:52:43 UTC (rev 2946) @@ -1929,7 +1929,7 @@ formatted as appropriate for Atom feeds (the date-time production from RFC3339). """ - return self.sql.lastModified(sqv_ticket_id = ticket_id or "")[0][0].ISO8601() + return self.sql.lastModified(sqv_ticket_id = ticket_id or "")[0][0].HTML4() ############################################################################### ############################################################################### @@ -2985,7 +2985,7 @@ """ Delete selected tickets. """ # First find the affected tickets - res = self.sql.listTickets(sqv_to_date=to_date.ISO8601(), + res = self.sql.listTickets(sqv_to_date=to_date.HTML4(), sqv_account_id=account_id, sqv_state=state, sqv_category0=category0, @@ -2999,7 +2999,7 @@ ) # Now remove the tickets - self.sql.deleteTickets(sqv_to_date=to_date.ISO8601(), + self.sql.deleteTickets(sqv_to_date=to_date.HTML4(), sqv_account_id=account_id, sqv_state=state, sqv_category0=category0, @@ -3018,7 +3018,7 @@ exp = XMLGenerator(f, 'utf-8') exp.startDocument() exp.startElement('mailmanager', {}) - tickets=self.sql.listTickets(sqv_to_date=to_date.ISO8601(), + tickets=self.sql.listTickets(sqv_to_date=to_date.HTML4(), sqv_account_id=account_id, sqv_state=state, sqv_category0=category0, @@ -3057,7 +3057,12 @@ exp.characters(base64.encodestring(row[column])) else: exp.startElement(column, {}) - exp.characters(str(row[column])) + if isinstance(row[column], DateTime): + exp.characters(str(row[column].HTML4())) + elif type(row[column]) is unicode: + exp.characters(row[column].encode('utf-8')) + else: + exp.characters(str(row[column])) exp.endElement(column) exp.endElement(name) @@ -3226,8 +3231,8 @@ search.clear() # Now put the search requirements into it. search['account_id'] = account_id - search['from_date'] = from_date.ISO8601() - search['to_date'] = to_date.ISO8601() + search['from_date'] = from_date.HTML4() + search['to_date'] = to_date.HTML4() search['assigned'] = assigned search['state'] = state search['priority'] = priority or '' Modified: MailManager/branches/RELENG_2_1/debian/mailmanager.postinst =================================================================== --- MailManager/branches/RELENG_2_1/debian/mailmanager.postinst 2006-04-26 15:07:28 UTC (rev 2945) +++ MailManager/branches/RELENG_2_1/debian/mailmanager.postinst 2006-04-26 16:52:43 UTC (rev 2946) @@ -9,8 +9,7 @@ PYLIB=$(echo $PYTHON | sed -e 's,/bin/,/lib/,') PKG=mailmanager -#CONFIGFILE=/etc/default/tardis-packagetools -#set -e + set -x . /usr/share/debconf/confmodule @@ -18,11 +17,16 @@ configure) db_get $PKG/database_platform DBPLATFORM="$RET" - echo $DBPLATFORM > /tmp/dbplatform -# mkdir -p $DESTDIR -# cp -rs /usr/share/zope/lib/python/Products/MailManager $DESTDIR/$PRODUCT -# $PYTHON -O $PYLIB/compileall.py -q $DESTDIR/$PRODUCT -# $PYTHON $PYLIB/compileall.py -q $DESTDIR/$PRODUCT -esac + + # Output initial setup config file + cat > /tmp/setupconfig <<EndOfConfig +[database] +dbplatform=$DBPLATFORM +EndOfConfig + + # Run installation script + /usr/share/zope/lib/python/Products/MailManager/scripts/debian-postinstall.py + +esac Modified: MailManager/branches/RELENG_2_1/ruleset/engine.py =================================================================== --- MailManager/branches/RELENG_2_1/ruleset/engine.py 2006-04-26 15:07:28 UTC (rev 2945) +++ MailManager/branches/RELENG_2_1/ruleset/engine.py 2006-04-26 16:52:43 UTC (rev 2946) @@ -203,6 +203,8 @@ We are making use of mx.DateTime here just now to calculate offsets, but then converting to Zope's DateTime so that it will work correctly with the database backend. + + @return: DateTime.DateTime """ offsetdate = mx.DateTime.mktime(strptime('2001-01-01', '%Y-%m-%d')) + \ Modified: MailManager/branches/RELENG_2_1/ruleset/zope.py =================================================================== --- MailManager/branches/RELENG_2_1/ruleset/zope.py 2006-04-26 15:07:28 UTC (rev 2945) +++ MailManager/branches/RELENG_2_1/ruleset/zope.py 2006-04-26 16:52:43 UTC (rev 2946) @@ -353,10 +353,10 @@ def getQueuedEvents(self): current_time = self.getCurrentTime() events = self.sql.getRulesetTimeEvents( - sqv_time = current_time + sqv_time = current_time.HTML4() ) self.sql.deleteRulesetTimeEvents( - sqv_time = current_time + sqv_time = current_time.HTML4() ) return [(e[0],e[1],e[2]) for e in events] @@ -365,7 +365,7 @@ in the process """ current_time = self.getCurrentTime() events = self.sql.getRulesetTimeEvents( - sqv_time = current_time + sqv_time = current_time.HTML4() ) return events Modified: MailManager/branches/RELENG_2_1/sql/v2_1/getHistoricalTickets.zsql =================================================================== --- MailManager/branches/RELENG_2_1/sql/v2_1/getHistoricalTickets.zsql 2006-04-26 15:07:28 UTC (rev 2945) +++ MailManager/branches/RELENG_2_1/sql/v2_1/getHistoricalTickets.zsql 2006-04-26 16:52:43 UTC (rev 2946) @@ -17,11 +17,12 @@ SELECT FROM <dtml-var schema>mm_ticket WHERE <dtml-sqltest sqv_message_id column="message_id" type="int"> -; +<dtml-var sql_delimiter> -SELECT id AS ticket_id, assigned, CURRENT_TIMESTAMP AS itemdate + +SELECT id AS ticket_id, assigned, <dtml-var sql_now> AS itemdate FROM lwtestkev10.mm_ticket - WHERE CURRENT_TIMESTAMP < '2006-02-05' + WHERE <dtml-var sql_now> < '2006-02-05' UNION ( SELECT @@ -30,7 +31,7 @@ WHERE change_date < '2006-02-07' ) ORDER BY ticket_id -; +<dtml-var sql_delimiter> <dtml-comment> Now obtain @@ -40,10 +41,11 @@ SELECT FROM <dtml-var schema>mm_ticket WHERE <dtml-sqltest sqv_message_id column="message_id" type="int"> +<dtml-var sql_delimiter> -SELECT id AS ticket_id, assigned, CURRENT_TIMESTAMP AS itemdate +SELECT id AS ticket_id, assigned, <dtml-var sql_now> AS itemdate FROM lwtestkev10.mm_ticket - WHERE CURRENT_TIMESTAMP < '2006-02-05' + WHERE <dtml-var sql_now> < '2006-02-05' UNION ( SELECT @@ -52,7 +54,7 @@ WHERE change_date < '2006-02-07' ) ORDER BY ticket_id -; +<dtml-var sql_delimiter> Modified: MailManager/branches/RELENG_2_1/support/Database.py =================================================================== --- MailManager/branches/RELENG_2_1/support/Database.py 2006-04-26 15:07:28 UTC (rev 2945) +++ MailManager/branches/RELENG_2_1/support/Database.py 2006-04-26 16:52:43 UTC (rev 2946) @@ -18,7 +18,7 @@ sql_mapping = { 'v2_1' : { 'postgres': { - 'sql_now' : 'CURRENT_TIMESTAMP', + 'sql_now' : 'CURRENT_TIMESTAMP AT TIME ZONE \'utc\'', 'sql_delimiter': ';', 'sql_boolean': 'BOOL', 'sql_binary': 'BYTEA', @@ -44,7 +44,7 @@ 'suboptimise': True, }, 'mysql': { - 'sql_now' : 'CURRENT_TIMESTAMP', + 'sql_now' : 'UTC_TIMESTAMP', 'sql_delimiter': '\0', 'sql_boolean': 'BOOL', 'sql_binary': 'LONGBLOB', Modified: MailManager/branches/RELENG_2_1/support/login.py =================================================================== --- MailManager/branches/RELENG_2_1/support/login.py 2006-04-26 15:07:28 UTC (rev 2945) +++ MailManager/branches/RELENG_2_1/support/login.py 2006-04-26 16:52:43 UTC (rev 2946) @@ -289,9 +289,11 @@ # on the application server for caching if accessed: sqlaccessed = DateTime(accessed).HTML4() + if debug: print "Accessed passed :", accessed else: sqlaccessed = None accessed = time.time() + if debug: print "Accessed inferred (time.time):", accessed update_sql = False if self._v_logon_cache.has_key(logon_id): @@ -299,18 +301,22 @@ if (accessed - last_accessed) > self.accessed_resolution: update_sql = True + if debug: print "Update SQL" + else: + if debug: print "No update, cache clean", (last_accessed, old_authinfo) else: update_sql = True + if debug: print "Update SQL (no cache)" - if update_sql: + if debug: print "Setting session to : ", sqlaccessed self.sql.setSession( sqv_session_id = logon_id, sqv_accessed = sqlaccessed, sqv_authinfo = authinfo ) - self._v_logon_cache[logon_id] = (accessed, authinfo) + self._v_logon_cache[logon_id] = (accessed, authinfo) security.declarePrivate('modifyRequest') Modified: MailManager/branches/RELENG_2_1/tests/testAutoLogout.py =================================================================== --- MailManager/branches/RELENG_2_1/tests/testAutoLogout.py 2006-04-26 15:07:28 UTC (rev 2945) +++ MailManager/branches/RELENG_2_1/tests/testAutoLogout.py 2006-04-26 16:52:43 UTC (rev 2946) @@ -53,7 +53,12 @@ from Products.MailManager.tests.classes.testStructures import manage_addDebugZPsycopgConnection +# Change this to enable debugging output +# Note that this might not work on win32 +debug = 0 + + def makerequest(root, stdout, stdin=None): resp = HTTPResponse(stdout=stdout) environ = {} @@ -68,7 +73,6 @@ - class loginHandlerTest(testStructures.mailManagerTestCase): """ This test case checks out the results of the the queueing code @@ -110,11 +114,12 @@ self.mmobj.Cookie.REQUEST = self.request self.mmobj.Cookie.REQUEST['RESPONSE'] = self.request.response - + # Login as an anonymouse user ##################################### self.request.traverse('/index_test') self.assertEqual(self.request['AUTHENTICATED_USER'].getUserName(), 'Anonymous User') + # Login with a valid username/password ############################ self.request.cookies['__ac_name'] = 'jacques' self.request.cookies['__ac_password'] = 'pass-w' resp = self.request.response @@ -122,11 +127,14 @@ self.request.traverse('/index_test') session = self.mmobj.sql.listSessions()[0] + pprint((session.session_id, session.accessed, session.authinfo)) - pprint(session) + # Check that the user is correctly identified internally self.assert_(self.request.has_key('AUTHENTICATED_USER')) self.assertEqual(self.request['AUTHENTICATED_USER'].getUserName(), 'jacques') + + # Check that the response has the correct cookies set resp = self.request.response self.assert_(resp.cookies.has_key('__ac')) self.assertEqual(resp.cookies['__ac']['value'], @@ -181,14 +189,12 @@ self.credentials = urllib.quote( base64.encodestring('jacques:pass-w').rstrip()) - - def loginHandlerTests(self): self.setupTest() self.loginTests() self.autoLogoutTest() # this is disabled because it doesn't work on windows - # self.entropyDisplay() + if debug: self.entropyDisplay() def entropyDisplay(self): """ Try out the various RNGs and display the amount of entropy they @@ -202,7 +208,6 @@ except: print "unable to find entropy_avail" - print "results for SecureRandom" print "========================" @@ -218,9 +223,6 @@ print "entropy after: " + after print "consumed: " + str(int(before) - int(after)) - - - print "results for NewSecureRandom" print "===========================" @@ -238,6 +240,7 @@ file.close() + def autoLogoutTest(self): self.mmobj.addOrEditMMUser(username='authuser', real_name='Auth Test User', @@ -269,9 +272,11 @@ session = self.mmobj.sql.listSessions()[0] self.mmobj.Cookie.cleanupSessions() + # Check that the given ticket has been returned to the queue ticket = self.mmobj.ticket(id=ticket_id)[0] self.failUnless(ticket.state == 'Queued') + class TestLoginHandler(loginHandlerTest): def testAutoLogout(self): self.dbplatform = self.config['databases']['primary_database'] @@ -279,6 +284,7 @@ self.mmobj.populateDataset('queuetests') self.loginHandlerTests() + def test_suite(): from unittest import TestSuite, makeSuite suite = TestSuite() This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <ke...@us...> - 2006-04-27 14:02:51
|
Revision: 2948 Author: kevca Date: 2006-04-27 07:02:11 -0700 (Thu, 27 Apr 2006) ViewCVS: http://svn.sourceforge.net/mailmanager/?rev=2948&view=rev Log Message: ----------- Fix for - Queues page does not have status buttons (#1477689) Modified Paths: -------------- MailManager/branches/RELENG_2_1/CHANGES.txt MailManager/branches/RELENG_2_1/www/master_style_css.dtml Added Paths: ----------- MailManager/branches/RELENG_2_1/www/images/status_queued.gif Modified: MailManager/branches/RELENG_2_1/CHANGES.txt =================================================================== --- MailManager/branches/RELENG_2_1/CHANGES.txt 2006-04-27 13:20:56 UTC (rev 2947) +++ MailManager/branches/RELENG_2_1/CHANGES.txt 2006-04-27 14:02:11 UTC (rev 2948) @@ -1,5 +1,6 @@ Version 2.1-RC3 * BUG FIXES +- Queues page does not have status buttons (#1477689) - AutoLogout not working correctly (#1477081) - No strptime on windows (#1476818) - getMail fails with TypeError (#1476878) Copied: MailManager/branches/RELENG_2_1/www/images/status_queued.gif (from rev 2946, MailManager/branches/RELENG_2_1/www/images/status_overdue.gif) =================================================================== (Binary files differ) Modified: MailManager/branches/RELENG_2_1/www/master_style_css.dtml =================================================================== --- MailManager/branches/RELENG_2_1/www/master_style_css.dtml 2006-04-27 13:20:56 UTC (rev 2947) +++ MailManager/branches/RELENG_2_1/www/master_style_css.dtml 2006-04-27 14:02:11 UTC (rev 2948) @@ -411,6 +411,12 @@ width: 70px; } +.queued { + background: url("images/status_overdue.gif") no-repeat center; + display: block; + width: 70px; +} + .spam { background: url("images/status_spam.gif") no-repeat center; display: block; This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |