Skip to content

/ Zope / gocept svn checkins / Archive / 2008 / 2008-12 / SVN: r7190 - in gocept.devtools/trunk: . gocept gocept/devtools

[ << ] [ >> ]

[ SVN: r7172 - in gocept.infrastructure/feature_syst... ] [ SVN: r7193 - in gocept.infrastructure/feature_syst... ]

SVN: r7190 - in gocept.devtools/trunk: . gocept gocept/devtools
Christian Theune <ct(at)gocept.com>
2008-12-07 14:15:55 [ FULL ]
Author: ctheune
Date: Sun Dec  7 14:15:53 2008
New Revision: 7190

Log:
Import code.

This package currently features a helper script to update our copyright
headers.



Added:
   gocept.devtools/trunk/README.txt   (contents, props changed)
   gocept.devtools/trunk/bootstrap.py   (contents, props changed)
   gocept.devtools/trunk/buildout.cfg
   gocept.devtools/trunk/gocept/
   gocept.devtools/trunk/gocept/__init__.py   (contents, props changed)
   gocept.devtools/trunk/gocept/devtools/
   gocept.devtools/trunk/gocept/devtools/__init__.py   (contents, props
changed)
   gocept.devtools/trunk/gocept/devtools/copyright.py   (contents, props
changed)
   gocept.devtools/trunk/setup.py   (contents, props changed)
Modified:
   gocept.devtools/trunk/   (props changed)

Added: gocept.devtools/trunk/README.txt
==============================================================================
--- (empty file)
+++ gocept.devtools/trunk/README.txt	Sun Dec  7 14:15:53 2008
(at)(at) -0,0 +1,16 (at)(at)
+======================
+gocept developer tools
+======================
+
+This is a small collection of utilities to perform various minor code
+management tasks.
+
+Copyright fixing
+================
+
+The `fix-copyright` script can be used to update copyright headers of Python
+files for a given directory tree::
+
+  $ fix-copyright <pathname>
+
+It currently only applies to the gocept copyright headers.

Added: gocept.devtools/trunk/bootstrap.py
==============================================================================
--- (empty file)
+++ gocept.devtools/trunk/bootstrap.py	Sun Dec  7 14:15:53 2008
(at)(at) -0,0 +1,77 (at)(at)
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Bootstrap a buildout-based project
+
+Simply run this script in a directory containing a buildout.cfg.
+The script accepts buildout command-line options, so you can
+use the -c option to specify an alternate configuration file.
+
+$Id: bootstrap.py 90478 2008-08-27 22:44:46Z georgyberdyshev $
+"""
+
+import os, shutil, sys, tempfile, urllib2
+
+tmpeggs = tempfile.mkdtemp()
+
+is_jython = sys.platform.startswith('java')
+
+try:
+    import pkg_resources
+except ImportError:
+    ez = {}
+    exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py'
+                         ).read() in ez
+    ez['use_setuptools'](to_dir=tmpeggs, download_delay=0)
+
+    import pkg_resources
+
+if sys.platform == 'win32':
+    def quote(c):
+        if ' ' in c:
+            return '"%s"' % c # work around spawn lamosity on windows
+        else:
+            return c
+else:
+    def quote (c):
+        return c
+
+cmd = 'from setuptools.command.easy_install import main; main()'
+ws  = pkg_resources.working_set
+
+if is_jython:
+    import subprocess
+    
+    assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd', 
+           quote(tmpeggs), 'zc.buildout'], 
+           env=dict(os.environ,
+               PYTHONPATH=
+               ws.find(pkg_resources.Requirement.parse('setuptools')).location
+               ),
+           ).wait() == 0
+
+else:
+    assert os.spawnle(
+        os.P_WAIT, sys.executable, quote (sys.executable),
+        '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout',
+        dict(os.environ,
+            PYTHONPATH=
+            ws.find(pkg_resources.Requirement.parse('setuptools')).location
+            ),
+        ) == 0
+
+ws.add_entry(tmpeggs)
+ws.require('zc.buildout')
+import zc.buildout.buildout
+zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap'])
+shutil.rmtree(tmpeggs)

Added: gocept.devtools/trunk/buildout.cfg
==============================================================================
--- (empty file)
+++ gocept.devtools/trunk/buildout.cfg	Sun Dec  7 14:15:53 2008
(at)(at) -0,0 +1,6 (at)(at)
+[buildout]
+develop = .
+parts = gocept.devtools
+
+[gocept.devtools]
+recipe = zc.recipe.egg

Added: gocept.devtools/trunk/gocept/__init__.py
==============================================================================
--- (empty file)
+++ gocept.devtools/trunk/gocept/__init__.py	Sun Dec  7 14:15:53 2008
(at)(at) -0,0 +1,6 (at)(at)
+# namespace package boilerplate
+try:
+    __import__('pkg_resources').declare_namespace(__name__)
+except ImportError, e:
+    from pkgutil import extend_path
+    __path__ = extend_path(__path__, __name__)

Added: gocept.devtools/trunk/gocept/devtools/__init__.py
==============================================================================
--- (empty file)
+++ gocept.devtools/trunk/gocept/devtools/__init__.py	Sun Dec  7 14:15:53 2008
(at)(at) -0,0 +1 (at)(at)
+# Make this a Python package

Added: gocept.devtools/trunk/gocept/devtools/copyright.py
==============================================================================
--- (empty file)
+++ gocept.devtools/trunk/gocept/devtools/copyright.py	Sun Dec  7 14:15:53 2008
(at)(at) -0,0 +1,65 (at)(at)
+#!/usr/bin/env python2.5
+# vim:fileencoding=utf-8
+# Copyright (c) 2008 gocept gmbh & co. kg
+# See also LICENSE.txt
+
+# TODO: support comma syntax (split 2003,2005-2006 correctly and re-append
+# without including 2004, don't always -CURRENTYEAR but eventually use , to
+# append the new year)
+
+import StringIO
+import datetime
+import os.path
+import re
+import sys
+
+TEMPLATE = '# Copyright (c) %s gocept gmbh & co. kg'
+TEMPLATE_PATTERN = re.compile('^# Copyright \(c\) ([0-9\-]+) gocept gmbh &
co. kg$')
+YEAR_PATTERN = re.compile('(19[0-9]{2}|20[0-9]{2})')
+
+
+def fix_file(filename):
+    file = open(filename, 'r').readlines()
+    output = StringIO.StringIO()
+    for line in file:
+        if not line.startswith('# Copyright'):
+            output.write(line)
+            continue
+        if not 'gocept' in line:
+            output.write(line)
+            continue
+        if line.endswith('\n'):
+            line = line[:-1]
+            trail = '\n'
+        else:
+            trail = ''
+        # This smells a lot like our copyright line. If this doesn't match our
+        # expression, bail out.
+        m = TEMPLATE_PATTERN.match(line)
+        if m is None:
+            raise ValueError('Weird copyright line', line)
+        # Find smallest year number
+        years = m.groups()[0].split('-')
+        years = [int(y) for y in years]
+        low = min(years)
+        max = datetime.date.today().year
+
+        if low == max:
+            signature = str(max)
+        else:
+            signature = '%s-%s' % (low, max)
+        line = TEMPLATE % signature
+        line = line + trail
+        output.write(line)
+    open(filename, 'w').write(output.getvalue())
+
+
+def visit(arg, dirname, names):
+    for name in names:
+        if name.endswith('.py'):
+            path = os.path.join(dirname, name)
+            print "Fixing", path
+            fix_file(path)
+
+def main():
+    os.path.walk(sys.argv[1], visit, None)

Added: gocept.devtools/trunk/setup.py
==============================================================================
--- (empty file)
+++ gocept.devtools/trunk/setup.py	Sun Dec  7 14:15:53 2008
(at)(at) -0,0 +1,23 (at)(at)
+# vim:fileencoding=utf-8
+# Copyright (c) 2008 gocept gmbh & co. kg
+# See also LICENSE.txt
+
+
+from setuptools import setup, find_packages
+
+setup(
+    name='gocept.devtools',
+    version='0.1',
+    author='Christian Theune',
+    author_email='ct(at)gocept.com',
+    description='Small utilities for managing code.',
+    packages=find_packages('.'),
+    package_dir = {'': '.'},
+    include_package_data = True,
+    zip_safe=False,
+    license='ZPL',
+    namespace_packages=['gocept'],
+    entry_points="""
+        [console_scripts]
+        fix-copyright = gocept.devtools.copyright:main
+    """)

SVN: r7192 - gocept.restmail/trunk/gocept/restmail/browser
Sebastian Wehrmann <sw(at)gocept.com>
2008-12-08 11:05:37 [ FULL ]
Author: sweh
Date: Mon Dec  8 11:05:36 2008
New Revision: 7192

Log:
refactor deleting of messages to use new imapapi api
add copying and moving of messages



Modified:
   gocept.restmail/trunk/gocept/restmail/browser/configure.zcml
   gocept.restmail/trunk/gocept/restmail/browser/message.py

Modified: gocept.restmail/trunk/gocept/restmail/browser/configure.zcml
==============================================================================
--- gocept.restmail/trunk/gocept/restmail/browser/configure.zcml	(original)
+++ gocept.restmail/trunk/gocept/restmail/browser/configure.zcml	Mon Dec  8
11:05:36 2008
(at)(at) -204,6 +204,16 (at)(at)
       name="delete"
       attribute="delete"
       />
+
+    <browser:page
+      name="copy"
+      attribute="copy"
+      />
+
+    <browser:page
+      name="move"
+      attribute="move"
+      />
   </browser:pages>
 
   <browser:page

Modified: gocept.restmail/trunk/gocept/restmail/browser/message.py
==============================================================================
--- gocept.restmail/trunk/gocept/restmail/browser/message.py	(original)
+++ gocept.restmail/trunk/gocept/restmail/browser/message.py	Mon Dec  8
11:05:36 2008
(at)(at) -69,9 +69,19 (at)(at)
 
     (at)gocept.restmail.browser.json.view
     def delete(self):
-        message = self.context.message
-        folder = message.parent
-        folder.delete(message)
+        self.context.message.delete()
+        return {}
+
+    (at)gocept.restmail.browser.json.view
+    def copy(self, target):
+        """Copy message to target folder"""
+        self.context.message.copy(target)
+        return {}
+
+    (at)gocept.restmail.browser.json.view
+    def move(self, target):
+        """Move message to target folder"""
+        self.context.message.move(target)
         return {}

SVN: r7194 - gocept.restmail/trunk/gocept/restmail/browser
Sebastian Wehrmann <sw(at)gocept.com>
2008-12-08 15:19:46 [ FULL ]
Author: sweh
Date: Mon Dec  8 15:19:45 2008
New Revision: 7194

Log:
traverse for a folder with a given path
add copy and move view for messages



Modified:
   gocept.restmail/trunk/gocept/restmail/browser/README.txt
   gocept.restmail/trunk/gocept/restmail/browser/message.py
   gocept.restmail/trunk/gocept/restmail/browser/traversal.py

Modified: gocept.restmail/trunk/gocept/restmail/browser/README.txt
==============================================================================
--- gocept.restmail/trunk/gocept/restmail/browser/README.txt	(original)
+++ gocept.restmail/trunk/gocept/restmail/browser/README.txt	Mon Dec  8
15:19:45 2008
(at)(at) -429,9 +429,18 (at)(at)
 </body>
 </html>
 
+
 Deleting messages
 -----------------
 
 Messages can be deleted using the `(at)(at)delete` view.
 
 XXX Test it when tests work again
+
+
+Copy and move messages
+----------------------
+
+Messages can be copied and moved using the `(at)(at)copy` and `(at)(at)move`
view.
+
+XXX Test it when tests work again

Modified: gocept.restmail/trunk/gocept/restmail/browser/message.py
==============================================================================
--- gocept.restmail/trunk/gocept/restmail/browser/message.py	(original)
+++ gocept.restmail/trunk/gocept/restmail/browser/message.py	Mon Dec  8
15:19:45 2008
(at)(at) -13,6 +13,8 (at)(at)
 import gocept.imapapi.interfaces
 import gocept.restmail.interfaces
 import gocept.restmail.browser.json
+import gocept.restmail.browser.traversal
+import gocept.restmail.interfaces
 
 
 class MessageContainerWebAPI(object):
(at)(at) -73,15 +75,15 (at)(at)
         return {}
 
     (at)gocept.restmail.browser.json.view
-    def copy(self, target):
+    def copy(self, folder_url):
         """Copy message to target folder"""
-        self.context.message.copy(target)
+        self.context.message.copy(traverse(folder_url, self.context))
         return {}
 
     (at)gocept.restmail.browser.json.view
-    def move(self, target):
+    def move(self, folder_url):
         """Move message to target folder"""
-        self.context.message.move(target)
+        self.context.message.move(traverse(folder_url, self.context))
         return {}
 
 
(at)(at) -194,3 +196,9 (at)(at)
 def footer_pgp_signature(part):
     """Provide a footer representation for application/pgp-signature."""
     return footer_pgp_signature_template()
+
+
+def traverse(url, context):
+    account = gocept.restmail.interfaces.IProfile(context)
+    return gocept.restmail.browser.traversal.traverse(
+        url, account, context.request)

Modified: gocept.restmail/trunk/gocept/restmail/browser/traversal.py
==============================================================================
--- gocept.restmail/trunk/gocept/restmail/browser/traversal.py	(original)
+++ gocept.restmail/trunk/gocept/restmail/browser/traversal.py	Mon Dec  8
15:19:45 2008
(at)(at) -125,3 +125,15 (at)(at)
 def account_url(context, request):
     return zope.component.getMultiAdapter(
             (context.aq_inner.aq_parent, request), name='absolute_url')
+
+
+def traverse(url, container, request):
+    """Traverse url and return the result.
+
+    Make sure the the object is inside the given container."""
+    container_url = zope.component.getMultiAdapter(
+            (container.aq_inner, request), name='absolute_url')() + '/'
+    if not url.startswith(container_url):
+        raise ValueError("The url doesn't point inside the given container.")
+    path = url[len(container_url):]
+    return container.restrictedTraverse(path)

SVN: r7197 - in gocept.restmail/trunk/gocept/restmail: . testmessages
Sebastian Wehrmann <sw(at)gocept.com>
2008-12-08 15:53:26 [ FULL ]
Author: sweh
Date: Mon Dec  8 15:53:24 2008
New Revision: 7197

Log:
refactor broken tests, still more work to do



Added:
   gocept.restmail/trunk/gocept/restmail/render.txt   (contents, props changed)
      - copied, changed from r7190,
gocept.restmail/trunk/gocept/restmail/draft.txt
   gocept.restmail/trunk/gocept/restmail/testmessages/23-invalid-html  
(contents, props changed)
      - copied, changed from r7190,
gocept.restmail/trunk/gocept/restmail/testmessages/04-html
Modified:
   gocept.restmail/trunk/gocept/restmail/draft.txt
   gocept.restmail/trunk/gocept/restmail/tests.py

Modified: gocept.restmail/trunk/gocept/restmail/draft.txt
==============================================================================
--- gocept.restmail/trunk/gocept/restmail/draft.txt	(original)
+++ gocept.restmail/trunk/gocept/restmail/draft.txt	Mon Dec  8 15:53:24 2008
(at)(at) -75,21 +75,14 (at)(at)
 <07(at)example.org>
 
 
-
-# XXX From here on, part of the tests should be moved to a render.txt after
-#  the refactoring.
-
-
 Quoting the message body
 ========================
 
 Converting the message parts
 ----------------------------
 
-In order to quote and display the complete message body, all parts must be
-converted into a displayable manner. That means, that all non-text parts
-(attachments, images, etc...) are substituted by a placeholder. All parts are
-then quoted with html.
+In order to quote and display the complete message body, displayable parts
must
+quoted using html. Attachments and images etc. are ignored.
 
 For plain text and html messages, html is returned:
 
(at)(at) -127,7 +120,10 (at)(at)
 </html>
 
 Of multipart/alternative parts, only the most faithful one is chosen and
-quoted. Attachments are skipped and HTML is sanitized:
+quoted. Attachments are skipped and HTML is sanitized. Also, we drop anything
+that was contained in the HTML head. We think this is the right thing to do
+since normal mail clients should - in our opinion - write nothing semantically
+meaningful into the head, anyway.
 
 >>> print get_html_text(messages[4])
 <html>
(at)(at) -145,196 +141,19 (at)(at)
 </html>
 
 
-Encoding
---------
-
-We have a number of messages in a mail folder named testmessages:
-
->>> print get_html_text(messages[0])
-<html>
-...I'm a message with no funny characters.
-...
-
-Correctly specified encoding works:
-
->>> print get_html_text(messages[1])
-<html>
-...I'm a message with some funny characters:[...]

SVN: r7224 - gocept.restmail/trunk/gocept/restmail/browser
Sebastian Wehrmann <sw(at)gocept.com>
2008-12-09 14:23:04 [ FULL ]
Author: sweh
Date: Tue Dec  9 14:23:02 2008
New Revision: 7224

Log:
add a status flag to folder dict to differ between accounts and folders in YUI



Modified:
   gocept.restmail/trunk/gocept/restmail/browser/folder.py
   gocept.restmail/trunk/gocept/restmail/browser/profile.py

Modified: gocept.restmail/trunk/gocept/restmail/browser/folder.py
==============================================================================
--- gocept.restmail/trunk/gocept/restmail/browser/folder.py	(original)
+++ gocept.restmail/trunk/gocept/restmail/browser/folder.py	Tue Dec  9 14:23:02
2008
(at)(at) -17,6 +17,7 (at)(at)
             url=zope.app.zapi.absoluteURL(folder, self.request),
             children=len(folder.folders()),
             name=folder.name,
+            type='inbox'
             )
 
     (at)gocept.restmail.browser.json.view

Modified: gocept.restmail/trunk/gocept/restmail/browser/profile.py
==============================================================================
--- gocept.restmail/trunk/gocept/restmail/browser/profile.py	(original)
+++ gocept.restmail/trunk/gocept/restmail/browser/profile.py	Tue Dec  9
14:23:02 2008
(at)(at) -95,5 +95,6 (at)(at)
             data.append(
                 {'url': zope.app.zapi.absoluteURL(account, self.request),
                  'children': children,
-                 'name': identity.address})
+                 'name': identity.address,
+                 'type': 'account'})
         return data

SVN: r7247 - gocept.collmex/trunk/src/gocept/collmex
Wolfgang Schnerring <ws(at)gocept.com>
2008-12-11 09:41:54 [ FULL ]
Author: wosc
Date: Thu Dec 11 09:41:52 2008
New Revision: 7247

Log:
implemented get_products and create_product



Modified:
   gocept.collmex/trunk/src/gocept/collmex/README.txt
   gocept.collmex/trunk/src/gocept/collmex/collmex.py
   gocept.collmex/trunk/src/gocept/collmex/interfaces.py
   gocept.collmex/trunk/src/gocept/collmex/model.py
   gocept.collmex/trunk/src/gocept/collmex/testing.py

Modified: gocept.collmex/trunk/src/gocept/collmex/README.txt
==============================================================================
--- gocept.collmex/trunk/src/gocept/collmex/README.txt	(original)
+++ gocept.collmex/trunk/src/gocept/collmex/README.txt	Thu Dec 11 09:41:52 2008
(at)(at) -63,6 +63,22 (at)(at)
 >>> customer['Firma']
 'Testkunden'
 
+Products: ``create_product`` and ``get_products``
+-------------------------------------------------
+
+Products are created using the ``create_product`` method:
+
+>>> product = gocept.collmex.model.Product()
+>>> product['Produktnummer'] = 'TEST'
+>>> product['Bezeichnung'] = 'Testprodukt'
+>>> product['Produktart'] = 0 # Ware
+>>> product['Verkaufs-Preis'] = 5
+>>> collmex.create_product(product)
+>>> import transaction
+>>> transaction.commit()
+>>> collmex.get_products()[0]['Bezeichnung']
+'Testprodukt'
+
 Invoices: ``create_invoice`` and ``get_invoices``
 -------------------------------------------------
 
(at)(at) -74,13 +90,11 (at)(at)
 >>> item['Kunden-Nr'] = '10000'
 >>> item['Rechnungsnummer'] = 100000
 >>> item['Menge'] = 3
->>> item['Produktnummer'] = 'TRAFFIC'
+>>> item['Produktnummer'] = 'TEST'
 >>> item['Rechnungstext'] = 'item text'
 >>> item['Positionstyp'] = 0
 >>> collmex.create_invoice([item])
 
-
-
 Invoices can be looked up again, using the ``get_invoices`` method. However,
as
 discussed above the invoice was only registered for addition. Querying right
 now does *not* return the invoice:
(at)(at) -90,14 +104,11 (at)(at)
 
 After committing, the invoice is found:
 
->>> import transaction
 >>> transaction.commit()
 >>> collmex.get_invoices(customer_id='10000',
 ...                      start_date=start_date)[0]['Rechnungstext']
 'item text'
 
-
-
 .. [#pre-flight-cleanup] First we need to clean up the Collmex environment:
 
     >>> import gocept.collmex.testing

Modified: gocept.collmex/trunk/src/gocept/collmex/collmex.py
==============================================================================
--- gocept.collmex/trunk/src/gocept/collmex/collmex.py	(original)
+++ gocept.collmex/trunk/src/gocept/collmex/collmex.py	Thu Dec 11 09:41:52 2008
(at)(at) -97,6 +97,7 (at)(at)
     model_factory = {
         'CMXINV': gocept.collmex.model.InvoiceItem,
         'CMXKND': gocept.collmex.model.Customer,
+        'CMXPRD': gocept.collmex.model.Product,
     }
 
     def __init__(self, customer_id, company_id, username, password):
(at)(at) -123,6 +124,13 (at)(at)
             writer.writerow(list(item))
         self.connection.register_data(data.getvalue())
 
+    def create_product(self, product):
+        data = StringIO.StringIO()
+        writer = csv.writer(data, dialect=CollmexDialect)
+        product['Firma'] = self.company_id
+        writer.writerow(list(product))
+        self.connection.register_data(data.getvalue())
+
     def get_invoices(self, invoice_id=NULL, customer_id=NULL,
                      start_date=NULL, end_date=NULL):
         return self._query_objects(
(at)(at) -144,6 +152,16 (at)(at)
             0, self.NULL, self.NULL, self.NULL, self.NULL, self.NULL,
             0, self.system_identifier)
 
+    def get_products(self, product_id=NULL,
+                     product_group=NULL, price_group=NULL):
+        return self._query_objects(
+            'PRODUCT_GET',
+            self.company_id,
+            product_id,
+            product_group,
+            price_group,
+            0, self.system_identifier)
+
     def _query_objects(self, function, *args):
         data = StringIO.StringIO()
         writer = csv.writer(data, dialect=CollmexDialect)
(at)(at) -158,8 +176,6 (at)(at)
             result.append(factory(line))
         return result
 
-
-
     def _post(self, data):
         data = 'LOGIN;%s;%s\n' % (self.username, self.password) + data
         log.debug(data)

Modified: gocept.collmex/trunk/src/gocept/collmex/interfaces.py
==============================================================================
--- gocept.collmex/trunk/src/gocept/collmex/interfaces.py	(original)
+++ gocept.collmex/trunk/src/gocept/collmex/interfaces.py	Thu Dec 11 09:41:52
2008
(at)(at) -14,6 +14,9 (at)(at)
     def create_invoice(items):
         """Creates an invoice consisting of the given IInvoiceItems."""
 
+    def create_product(product):
+        """Creates a product for the given IProduct."""
+
     def get_invoices(invoice_id=NULL, customer_id=NULL,
                      start_date=NULL, end_date=NULL):
         """Returns a list of IInvoiceItems maching given criteria."""
(at)(at) -21,6 +24,8 (at)(at)
     def get_customers(customer_id=NULL, text=NULL):
         """Returns a list of ICustomers matching given criteria."""
 
+    def get_products(product_id=NULL, product_group=NULL, price_group=NULL):
+        """Returns a list of IProducts matching given criteria."""
 
 
 class IModel(zope.interface.common.mapping.IFullMapping):
(at)(at) -38,8 +43,12 (at)(at)
 
 
 class IInvoiceItem(IModel):
-    """An invoice item from Collmex CMXINV"""
+    """An invoice item (CMXINV)"""
 
 
 class ICustomer(IModel):
-    """A customer CMXKND."""
+    """A customer (CMXKND)."""
+
+
+class IProduct(IModel):
+    """A product (CMXPRD)."""

Modified: gocept.collmex/trunk/src/gocept/collmex/model.py
==============================================================================
--- gocept.collmex/trunk/src/gocept/collmex/model.py	(original)
+++ gocept.collmex/trunk/src/gocept/collmex/model.py	Thu Dec 11 09:41:52 2008
(at)(at) -23,7 +23,6 (at)(at)
             self[self.fields[i]] = row[i]
 
     def __iter__(self):
-        result = []
         for field in self.fields:
             if field in self:
                 value = self[field]
(at)(at) -169,3 +168,38 (at)(at)
         'Vermittler',
         'Kostenstelle',
     )
+
+
+class Product(Model):
+
+    zope.interface.implements(gocept.collmex.interfaces.IProduct)
+
+    satzart = 'CMXPRD'
+    fields = (
+        'Satzart',
+        'Produktnummer',
+        'Bezeichnung',
+        'Bezeichnung Eng',
+        'Basismengeneinheit',
+        'Produktgruppe',
+        'Firma',
+        'Steuerklassifikation',
+        'Gewicht',
+        'Gewicht Mengeneinheit',
+        'Preismenge',
+        'Produktart',
+        'Inaktiv',
+        'Preisgruppe',
+        'Verkaufs-Preis',
+        'EAN',
+        'Hersteller',
+        'Versandgruppe',
+        'Mindestbestand',
+        'Bestellmenge',
+        'Chargenpflicht',
+        'Beschaffungsart',
+        'Produktionsdauer',
+        'Lohnkosten',
+        'Lohnkosten-Bezugsmenge',
+        'Reserviert',
+    )

Modified: gocept.collmex/trunk/src/gocept/collmex/testing.py
==============================================================================
--- gocept.collmex/trunk/src/gocept/collmex/testing.py	(original)
+++ gocept.collmex/trunk/src/gocept/collmex/testing.py	Thu Dec 11 09:41:52 2008
(at)(at) -40,18 +40,5 (at)(at)
     b.getControl('Firma').value = 'Testkunden'
     b.getControl('Speichern').click()
 
-    # Beispielprodukt anlegen
-    b.getLink('Warenwirtschaft').click()
-    add_link = b.getLink(u'Anlegen', index=4)
-    assert add_link.url.endswith('prcr')
-    add_link.click() # XXX Magic number
-    b.getControl('Produkt', index=1).value = 'TRAFFIC'
-    b.getControl('Produkt anlegen').click()
-    b.getControl('Bezeichnung').value = 'Testprodukt'
-    b.getControl('Speichern').click()
-    b.getLink('Verkauf', index=1).click()
-    b.getControl(name='preis_1_preis').value = '5,00'
-    b.getControl('Speichern').click()
-
     # Explicitly close response to not leave open http objects.
     b.mech_browser._response.close()

SVN: r7248 - gocept.collmex/trunk/src/gocept/collmex
Wolfgang Schnerring <ws(at)gocept.com>
2008-12-11 09:45:44 [ FULL ]
Author: wosc
Date: Thu Dec 11 09:45:42 2008
New Revision: 7248

Log:
implemented create_customer



Modified:
   gocept.collmex/trunk/src/gocept/collmex/README.txt
   gocept.collmex/trunk/src/gocept/collmex/collmex.py
   gocept.collmex/trunk/src/gocept/collmex/testing.py

Modified: gocept.collmex/trunk/src/gocept/collmex/README.txt
==============================================================================
--- gocept.collmex/trunk/src/gocept/collmex/README.txt	(original)
+++ gocept.collmex/trunk/src/gocept/collmex/README.txt	Thu Dec 11 09:45:42 2008
(at)(at) -28,10 +28,17 (at)(at)
 
 [#pre-flight-cleanup]_ [#invalid-login]_
 
+>>> import transaction
+
 
+Customers: ``create_customer`` and ``get_customers``
+----------------------------------------------------
 
-Customers: ``get_customers``
-----------------------------
+>>> customer = gocept.collmex.model.Customer()
+>>> customer['Kundennummer'] = 10000
+>>> customer['Firma'] = 'Testkunden'
+>>> collmex.create_customer(customer)
+>>> transaction.commit()
 
 Customers can be listed using the get_customers method:
 
(at)(at) -74,7 +81,6 (at)(at)
 >>> product['Produktart'] = 0 # Ware
 >>> product['Verkaufs-Preis'] = 5
 >>> collmex.create_product(product)
->>> import transaction
 >>> transaction.commit()
 >>> collmex.get_products()[0]['Bezeichnung']
 'Testprodukt'

Modified: gocept.collmex/trunk/src/gocept/collmex/collmex.py
==============================================================================
--- gocept.collmex/trunk/src/gocept/collmex/collmex.py	(original)
+++ gocept.collmex/trunk/src/gocept/collmex/collmex.py	Thu Dec 11 09:45:42 2008
(at)(at) -131,6 +131,13 (at)(at)
         writer.writerow(list(product))
         self.connection.register_data(data.getvalue())
 
+    def create_customer(self, customer):
+        data = StringIO.StringIO()
+        writer = csv.writer(data, dialect=CollmexDialect)
+        customer['Firma Nr'] = self.company_id
+        writer.writerow(list(customer))
+        self.connection.register_data(data.getvalue())
+
     def get_invoices(self, invoice_id=NULL, customer_id=NULL,
                      start_date=NULL, end_date=NULL):
         return self._query_objects(

Modified: gocept.collmex/trunk/src/gocept/collmex/testing.py
==============================================================================
--- gocept.collmex/trunk/src/gocept/collmex/testing.py	(original)
+++ gocept.collmex/trunk/src/gocept/collmex/testing.py	Thu Dec 11 09:45:42 2008
(at)(at) -29,16 +29,5 (at)(at)
 
     assert 'Daten erfolgreich gel' in b.contents
 
-    # Beispielkunden anlegen
-    b.getLink('Warenwirtschaft').click()
-    add_link = b.getLink(u'Anzeigen und ändern', index=2)
-    assert add_link.url.endswith('cu')
-    add_link.click() # XXX Magic number
-    b.getLink('Anlegen').click()
-    b.getControl('Kunde Nr', index=0).value = '10000'
-    b.getControl('Kunde anlegen').click()
-    b.getControl('Firma').value = 'Testkunden'
-    b.getControl('Speichern').click()
-
     # Explicitly close response to not leave open http objects.
     b.mech_browser._response.close()

SVN: r7253 - gocept.restmail/trunk/gocept/restmail/browser
Sebastian Wehrmann <sw(at)gocept.com>
2008-12-11 10:49:11 [ FULL ]
Author: sweh
Date: Thu Dec 11 10:49:10 2008
New Revision: 7253

Log:
list accounts even if one account is failing (bug #4569)



Modified:
   gocept.restmail/trunk/gocept/restmail/browser/profile.py

Modified: gocept.restmail/trunk/gocept/restmail/browser/profile.py
==============================================================================
--- gocept.restmail/trunk/gocept/restmail/browser/profile.py	(original)
+++ gocept.restmail/trunk/gocept/restmail/browser/profile.py	Thu Dec 11
10:49:10 2008
(at)(at) -88,7 +88,7 (at)(at)
                 # (e.g. because of a wrong configuration). Available accounts
                 # should be listet although.
                 children = len(account.folders())
-            except socket.gaierror:
+            except:
                 # XXX Log an error for the user
                 children = 0
             identity = gocept.restmail.interfaces.IIdentity(account)

SVN: r7257 - in gocept.restmail/trunk/gocept/restmail: . browser
Sebastian Wehrmann <sw(at)gocept.com>
2008-12-11 12:48:28 [ FULL ]
Author: sweh
Date: Thu Dec 11 12:48:27 2008
New Revision: 7257

Log:
deletion of draft messages (bug #4608)



Modified:
   gocept.restmail/trunk/gocept/restmail/browser/configure.zcml
   gocept.restmail/trunk/gocept/restmail/browser/draft.py
   gocept.restmail/trunk/gocept/restmail/draft.py
   gocept.restmail/trunk/gocept/restmail/draft.txt

Modified: gocept.restmail/trunk/gocept/restmail/browser/configure.zcml
==============================================================================
--- gocept.restmail/trunk/gocept/restmail/browser/configure.zcml	(original)
+++ gocept.restmail/trunk/gocept/restmail/browser/configure.zcml	Thu Dec 11
12:48:27 2008
(at)(at) -244,6 +244,11 (at)(at)
       attribute="send"
       />
 
+    <browser:page
+      name="delete"
+      attribute="delete"
+      />
+
   </browser:pages>
 
   <browser:page

Modified: gocept.restmail/trunk/gocept/restmail/browser/draft.py
==============================================================================
--- gocept.restmail/trunk/gocept/restmail/browser/draft.py	(original)
+++ gocept.restmail/trunk/gocept/restmail/browser/draft.py	Thu Dec 11 12:48:27
2008
(at)(at) -12,7 +12,7 (at)(at)
 
 
 class WebAPI(object):
-    """Web API for accounts."""
+    """Web API for drafts."""
 
     (at)gocept.restmail.browser.json.view
     def data(self):
(at)(at) -44,6 +44,10 (at)(at)
         """Update draft and send this message."""
         self.context.send()
 
+    def delete(self):
+        """Delete draft."""
+        self.context.delete() 
+
 
 draft_header_template = gocept.restmail.browser.message.template(
     'draft_snippet_header.pt')

Modified: gocept.restmail/trunk/gocept/restmail/draft.py
==============================================================================
--- gocept.restmail/trunk/gocept/restmail/draft.py	(original)
+++ gocept.restmail/trunk/gocept/restmail/draft.py	Thu Dec 11 12:48:27 2008
(at)(at) -94,9 +94,13 (at)(at)
         sent.append(message)
 
         # 4. Delete draft
+        self.delete()
+
+    def delete(self):
         self.aq_inner.getParentNode().manage_delObjects([self.getId()])
 
 
+
 def new_draft(container, message=None):
     id = str(uuid.uuid1())
     while id in container.objectIds():

Modified: gocept.restmail/trunk/gocept/restmail/draft.txt
==============================================================================
--- gocept.restmail/trunk/gocept/restmail/draft.txt	(original)
+++ gocept.restmail/trunk/gocept/restmail/draft.txt	Thu Dec 11 12:48:27 2008
(at)(at) -74,6 +74,14 (at)(at)
 >>> print reply.references
 <07(at)example.org>
 
+To delete a draft, simply call its delete method:
+
+>>> len(reply.aq_inner.getParentNode().objectValues())
+5
+>>> reply.delete()
+>>> len(reply.aq_inner.getParentNode().objectValues())
+4
+
 
 Quoting the message body
 ========================

SVN: r7263 - gocept.restmail/trunk/gocept/restmail/browser
Sebastian Wehrmann <sw(at)gocept.com>
2008-12-12 09:11:18 [ FULL ]
Author: sweh
Date: Fri Dec 12 09:11:16 2008
New Revision: 7263

Log:
fix tests



Modified:
   gocept.restmail/trunk/gocept/restmail/browser/README.txt

Modified: gocept.restmail/trunk/gocept/restmail/browser/README.txt
==============================================================================
--- gocept.restmail/trunk/gocept/restmail/browser/README.txt	(original)
+++ gocept.restmail/trunk/gocept/restmail/browser/README.txt	Fri Dec 12
09:11:16 2008
(at)(at) -283,6 +283,7 (at)(at)
 >>> browser.open(message['structure']['url'])
 >>> print browser.headers
 Status: 200 OK
+Content-Disposition: attachment
 Content-Length: 17
 Content-Type: text/plain; charset=utf-8
 >>> print browser.contents

SVN: r7264 - gocept.restmail/trunk/gocept/restmail/browser
Sebastian Wehrmann <sw(at)gocept.com>
2008-12-12 09:40:05 [ FULL ]
Author: sweh
Date: Fri Dec 12 09:40:04 2008
New Revision: 7264

Log:
readd ZMI view for messages



Modified:
   gocept.restmail/trunk/gocept/restmail/browser/configure.zcml
   gocept.restmail/trunk/gocept/restmail/browser/message.py
   gocept.restmail/trunk/gocept/restmail/browser/zmi.txt

Modified: gocept.restmail/trunk/gocept/restmail/browser/configure.zcml
==============================================================================
--- gocept.restmail/trunk/gocept/restmail/browser/configure.zcml	(original)
+++ gocept.restmail/trunk/gocept/restmail/browser/configure.zcml	Fri Dec 12
09:40:04 2008
(at)(at) -51,7 +51,6 (at)(at)
     permission="zope2.View"
     />
 
-  <!--
   <browser:pages
     for="gocept.imapapi.interfaces.IMessage"
     class=".message.Message"
(at)(at) -76,7 +75,7 (at)(at)
       name="manage_showParts"
       template="show_parts.pt"
       />
-  </browser:pages> -->
+  </browser:pages>
 
   <!-- Publisher support for non-Zope objects -->
   <adapter

Modified: gocept.restmail/trunk/gocept/restmail/browser/message.py
==============================================================================
--- gocept.restmail/trunk/gocept/restmail/browser/message.py	(original)
+++ gocept.restmail/trunk/gocept/restmail/browser/message.py	Fri Dec 12
09:40:04 2008
(at)(at) -97,6 +97,25 (at)(at)
             body=self.context.fetch(),
             )
 
+class Message(object):
+    """View helper for messages."""
+
+    def list_parts(self):
+        """Create a flat list of all parts."""
+        stack = [self.context.aq_inner.body()]
+        while stack:
+            part = stack.pop(0)
+            yield part
+            if part['content_type'].startswith('multipart/'):
+                stack = part.parts() + stack
+
+    def body(self):
+        """Select a body part, decode it and prepare for embedding."""
+        rendered_message = gocept.restmail.interfaces.IRenderedMessage(
+            self.context.aq_inner)
+        rendered_message.annotate()
+        return IBody(rendered_message)
+
 
 class IHeader(zope.interface.Interface):
     """A representation of a message's headers."""

Modified: gocept.restmail/trunk/gocept/restmail/browser/zmi.txt
==============================================================================
--- gocept.restmail/trunk/gocept/restmail/browser/zmi.txt	(original)
+++ gocept.restmail/trunk/gocept/restmail/browser/zmi.txt	Fri Dec 12 09:40:04
2008
(at)(at) -147,9 +147,10 (at)(at)
 </dl>
 <BLANKLINE>
 <div class="location-bar"
-     style="border:1px solid grey; padding:0.5em;"><div
class="messagebody">
-     <pre>Everything is ok!</pre>
-     </div>
+     style="border:1px solid grey; padding:0.5em;"><div
class="messagepart">
+  <div><pre>Everything is ok!
+</pre></div>
+  <div><div class="footer">
 </div>
 ...

SVN: r7265 - in gocept.restmail/trunk: . gocept/restmail/browser
Sebastian Wehrmann <sw(at)gocept.com>
2008-12-12 10:29:48 [ FULL ]
Author: sweh
Date: Fri Dec 12 10:29:45 2008
New Revision: 7265

Log:
fix restmail inline tests (now all tests of the restmail package are fixed)
pin lxml to 2.1 because of a bug in the newest version 2.2alpha2



Modified:
   gocept.restmail/trunk/gocept/restmail/browser/inline.txt
   gocept.restmail/trunk/setup.py

Modified: gocept.restmail/trunk/gocept/restmail/browser/inline.txt
==============================================================================
--- gocept.restmail/trunk/gocept/restmail/browser/inline.txt	(original)
+++ gocept.restmail/trunk/gocept/restmail/browser/inline.txt	Fri Dec 12
10:29:45 2008
(at)(at) -27,7 +27,6 (at)(at)
 
 >>> folder = 'http://localhost/profile/ben-example.com/+Testmessages'
 
-
 Various combined messages
 =========================
 
(at)(at) -36,7 +35,8 (at)(at)
 
 >>> r = render_message(folder, '09 - AppleMail, Simple HTML with
image')
 >>> print r['header']
-<dl>
+<div class="header">
+  <dl>
       <dt>Subject:</dt>
       <dd>09 - AppleMail, Simple HTML with image</dd>
       <dt>From:</dt>
(at)(at) -45,20 +45,27 (at)(at)
       <dd>Christian Theune &lt;ct(at)gocept.com&gt;</dd>
       <dt>Date:</dt>
       <dd>Fri, 21 Nov 2008 10:42:12 +0100</dd>
-</dl>
+  </dl>
+</div>
 
 >>> print r['body']
-<div class="messagebody"><div>Daa <font
class="Apple-style-span" color="#FF3728">dudel</font>
d&#246;<img height="240" width="324" src="http://localhost/profile/ben-example.com/+Testmessages/*.../*body/2/2"><div><br></div><div><br></div><div><font
class="Apple-style-span">blasdjf&#160;</font><br><div>
<div><div><div>--&#160;</div><div>Christian
Zagrodnick &#183;
<a>cz(at)gocept.com</a></div><div>gocept gmbh &amp;
co. kg &#183; forsterstra&#223;e 29 &#183; 06112 halle (saale)
&#183; germany</div><div><a>http://gocept.com</a> &#183;
tel +49 345 1229889 0 &#183; fax +49 345 1229889
1</div><div>Zope and Plone consulting and
development</div><br></div></div><br></div><br></div></div></div>
+<div class="messagepart">
+  <div><div>Daa <font class="Apple-style-span"
color="#FF3728">dudel</font> d&#246;<img height="240"
width="324"
src="cid:50CDC34E-4963-4E84-9CD2-69F2BE4E4BA6"><div><br></div><div><br></div><div><font
class="Apple-style-span">blasdjf&#160;</font><br><div>
<div><div><div>--&#160;</div><div>Christian
Zagrodnick &#183; <a
href="mailto:cz(at)gocept.com">cz(at)gocept.com</a></div><div>gocept
gmbh &amp; co. kg &#183; forsterstra&#223;e 29 &#183; 06112
halle (saale) &#183; germany</div><div><a href="http://gocept.com">http://gocept.com</a> &#183;
tel +49 345 1229889 0 &#183; fax +49 345 1229889
1</div><div>Zope and Plone consulting and
development</div><br></div></div><br></div><br></div></div></div>
+  <div><div class="footer">
+</div>
+</div>
+</div>
 
->>> print r['attachments']
-  <div class="messagebody">
-    <h3>Attachments</h3>
-    <ul>
-      <li>
-        <a href="http://localhost/profile/ben-example.com/+Testmessages/*.../*body/2/2">DSC03090.jpg</a>
(<span>image/jpeg</span> <span>213680</span>)
-      </li>
-    </ul>
-  </div>
+>>> print r['footer']
+<div class="footer">
+  <div class="footercomponent"><h6>Attachments</h6>
+<ul>
+  <li>
+    <a href="#">DSC03090.jpg</a>
(<span>image/jpeg</span> <span>213680</span>)
+  </li>
+</ul>
+</div>
+</div>
 
 
 Claws, Signed, Text attachment
(at)(at) -66,7 +73,8 (at)(at)
 
 >>> r = render_message(folder, '10 - Claws, Signed, Text attachment')
 >>> print r['header']
-<dl>
+<div class="header">
+  <dl>
       <dt>Subject:</dt>
       <dd>10 - Claws, Signed, Text attachment</dd>
       <dt>From:</dt>
(at)(at) -75,36 +83,40 (at)(at)
       <dd>ct(at)gocept.com</dd>
       <dt>Date:</dt>
       <dd>Fri, 21 Nov 2008 10:43:38 +0100</dd>
-</dl>
+  </dl>
+</div>
 
 >>> print r['body'].encode('utf-8')
-<div class="messagebody">
-  <pre>Moin.
+<div class="messagepart">
+  <div><pre>Moin.
 Zagy sagt, ich soll Dir eine Mail mit Anhang schicken.
-Viele Grüße,
+Viele Gr&#252;&#223;e,
 Thomas
 --
-Thomas Lotze · tl(at)gocept.com
-gocept gmbh &amp; co. kg · forsterstraße 29 · 06112 halle (saale) ·
germany
-http://gocept.com · tel +49 345 1229889 0 ·
fax +49 345 1229889 1
+Thomas Lotze &#183; tl(at)gocept.com
+gocept gmbh &amp; co. kg &#183; forsterstra&#223;e 29 &#183;
06112 halle (saale) &#183; germany
+http://gocept.com &#183; tel +49 345
1229889 0 &#183; fax +49 345 1229889 1
 Zope and Plone consulting and development
-</pre>
+</pre></div>
+  <div><div class="footer">
+</div>
 </div>
-<div class="messagebody signature-untrusted">
-  <p>This message was digitally signed. The signature has <b>not
been
-      verified</b>.
-  </p>
 </div>
 
->>> print r['attachments']
-  <div class="messagebody">
-    <h3>Attachments</h3>
-    <ul>
-      <li>
-        <a href="http://localhost/profile/ben-example.com/+Testmessages/*.../*body/1/2">ez_setup.py</a>
(<span>text/x-python</span> <span>8509</span>)
-      </li>
-    </ul>
-  </div>
+>>> print r['footer']
+<div class="footer">
+  <div class="footercomponent"><h6>Attachments</h6>
+<BLANKLINE>
+<ul>
+  <li>
+    <a href="#">ez_setup.py</a>
(<span>text/x-python</span> <span>8509</span>)
+  </li>
+  <li>
+    <a href="#">signature.asc</a>
(<span>application/pgp-signature</span>
<span>196</span>)
+  </li>
+</ul>
+</div>
+</div>
 
 
 AppleMail, Complex HTML with many images
(at)(at) -112,7 +124,8 (at)(at)
 
 >>> r = render_message(folder, '11 - AppleMail, Complex HTML with
many images')
 >>> print r['header']
-<dl>
+<div class="header">
+  <dl>
       <dt>Subject:</dt>
       <dd>11 - AppleMail, Complex HTML with many images</dd>
       <dt>From:</dt>
(at)(at) -121,52 +134,59 (at)(at)
       <dd>Christian Theune &lt;ct(at)gocept.com&gt;</dd>
       <dt>Date:</dt>
       <dd>Fri, 21 Nov 2008 10:44:57 +0100</dd>
-</dl>
+  </dl>
+</div>
 
 >>> print r['body']
-<div class="messagebody"><div> 
+<div class="messagepart">
+  <div><div>
 <div>
 <table width="753" class="email-body-wrap" id="email-body" align="center"
cellspacing="0" cellpadding="0" border="0"><tbody><tr><td
width="45" rowspan="2">&#160;</td>
-    <td width="663"><img width="663" height="148" alt="" src="http://localhost/profile/ben-example.com/+Testmessages/*.../*body/2/2"></td>
-    <td width="45" rowspan="2">&#160;</td>
-  </tr><tr><td width="663"><table width="663"
height="289" border="0" cellpadding="0"
cellspacing="0"><tbody><tr><td
width="105">&#160;</td>
-        <td width="548"><font color="#4a4533">
-          <div class="AppleResizingDiv"><div>
<div><div><div>das ist bestimmt der totale hass f&#252;r
jeden mailer
*GG</div><div><br></div><div>--&#160;</div><div>Christian
Zagrodnick &#183;
<a>cz(at)gocept.com</a></div><div>gocept gmbh &amp;
co. kg &#183; forsterstra&#223;e 29 &#183; 06112 halle (saale)
&#183; germany</div><div><a>http://gocept.com</a> &#183;
tel +49 345 1229889 0 &#183; fax +49 345 1229889
1</div><div>Zope and Plone consulting and
development</div><br></div></div><br></div></div>
-        </font></td>
-        <td width="100">&#160;</td>
-    </tr></tbody></table></td>
-  </tr><tr><td width="753" colspan="3"><img width="753"
height="262" src="http://localhost/profile/ben-example.com/+Testmessages/*.../*body/2/3"></td>
-  </tr><tr><td width="753" colspan="3"><img width="753"
height="57" alt="" src="http://localhost/profile/ben-example.com/+Testmessages/*.../*body/2/4"></td>
-  </tr></tbody></table></div>
+		<td width="663"><img width="663" height="148" alt=""
src="cid:919A8163-52C0-4CE3-B45C-F05A1AE2FC3D/top.jpg"></td>
+		<td width="45" rowspan="2">&#160;</td>
+	</tr><tr><td width="663"><table width="663" height="289"
border="0" cellpadding="0" cellspacing="0"><tbody><tr><td
width="105">&#160;</td>
+				<td width="548"><font color="#4a4533">
+					<div class="AppleResizingDiv"><div>
<div><div><div>das ist bestimmt der totale hass f&#252;r
jeden mailer
*GG</div><div><br></div><div>--&#160;</div><div>Christian
Zagrodnick &#183; <a
href="mailto:cz(at)gocept.com">cz(at)gocept.com</a></div><div>gocept
gmbh &amp; co. kg &#183; forsterstra&#223;e 29 &#183; 06112
halle (saale) &#183; germany</div><div><a href="http://gocept.com">http://gocept.com</a> &#183;
tel +49 345 1229889 0 &#183; fax +49 345 1229889
1</div><div>Zope and Plone consulting and
development</div><br></div></div><br></div></div>
+				</font></td>
+				<td width="100">&#160;</td>
+		</tr></tbody></table></td>
+	</tr><tr><td width="753" colspan="3"><img width="753"
height="262"
src="cid:919A8163-52C0-4CE3-B45C-F05A1AE2FC3D/2/Photos"></td>
+	</tr><tr><td width="753" colspan="3"><img width="753"
height="57" alt=""
src="cid:919A8163-52C0-4CE3-B45C-F05A1AE2FC3D/bottom.jpg"></td>
+	</tr></tbody></table></div>
 </div></div>
+  <div><div class="footer">
+</div>
+</div>
+</div>
 
->>> print r['attachments']
- <div class="messagebody"> 
-   <h3>Attachments</h3>
-   <ul>
-     <li>
-       <a href="http://localhost/profile/ben-example.com/+Testmessages/*.../*body/2/2">top.jpg</a>
(<span>image/jpeg</span> <span>27412</span>)
-     </li>
-     <li>
-       <a href="http://localhost/profile/ben-example.com/+Testmessages/*.../*body/2/3">Photos.png</a>
(<span>image/png</span> <span>457106</span>)
-     </li>
-     <li>
-       <a href="http://localhost/profile/ben-example.com/+Testmessages/*.../*body/2/4">bottom.jpg</a>
(<span>image/jpeg</span> <span>19046</span>)
-     </li>
-     <li>
-       <a href="http://localhost/profile/ben-example.com/+Testmessages/*.../*body/2/5">bg_pattern.jpg</a>
(<span>image/jpeg</span> <span>103782</span>)
-     </li>
-     <li>
-       <a href="http://localhost/profile/ben-example.com/+Testmessages/*.../*body/2/6">lbg.jpg</a>
(<span>image/jpeg</span> <span>9756</span>)
-     </li>
-     <li>
-       <a href="http://localhost/profile/ben-example.com/+Testmessages/*.../*body/2/7">rbg.jpg</a>
(<span>image/jpeg</span> <span>9816</span>)
-     </li>
-     <li>
-       <a href="http://localhost/profile/ben-example.com/+Testmessages/*.../*body/2/8">bg_letter.jpg</a>
(<span>image/jpeg</span> <span>36484</span>)
-     </li>
-   </ul>
- </div>
+>>> print r['footer']
+<div class="footer">
+  <div class="footercomponent"><h6>Attachments</h6>
+<ul>
+  <li>
+    <a href="#">top.jpg</a> (<span>image/jpeg</span>
<span>27412</span>)
+  </li>
+  <li>
+    <a href="#">Photos.png</a> (<span>image/png</span>
<span>457106</span>)
+  </li>
+  <li>
+    <a href="#">bottom.jpg</a>
(<span>image/jpeg</span> <span>19046</span>)
+  </li>
+  <li>
+    <a href="#">bg_pattern.jpg</a>
(<span>image/jpeg</span> <span>103782</span>)
+  </li>
+  <li>
+    <a href="#">lbg.jpg</a> (<span>image/jpeg</span>
<span>9756</span>)
+  </li>
+  <li>
+    <a href="#">rbg.jpg</a> (<span>image/jpeg</span>
<span>9816</span>)
+  </li>
+  <li>
+    <a href="#">bg_letter.jpg</a>
(<span>image/jpeg</span> <span>36484</span>)
+  </li>
+</ul>
+</div>
+</div>
 
 
 Evolution, multipart/mixed, inline image, gzip attachment
(at)(at) -177,7 +197,8 (at)(at)
 
 >>> r = render_message(folder, '12 - Evolution, multipart/mixed,
inline image, gzip attachment')
 >>> print r['header']
-<dl>
+<div class="header">
+  <dl>
       <dt>Subject:</dt>
       <dd>12 - Evolution, multipart/mixed, inline image, gzip
attachment</dd>
       <dt>From:</dt>
(at)(at) -186,34 +207,42 (at)(at)
       <dd>ct(at)gocept.com</dd>
       <dt>Date:</dt>
       <dd>Fri, 21 Nov 2008 12:10:38 +0100</dd>
-</dl>
+  </dl>
+</div>
 
 >>> print r['body']
-<div class="messagebody"><div>
-Ich habe hier noch ein Bild beigefuegt.<br><img src="http://localhost/profile/ben-example.com/+Testmessages/*.../*body/1/2"
align="bottom" border="0"><br><br>
+<div class="messagepart">
+  <div><div>
+Ich habe hier noch ein Bild beigefuegt.<br><img
src="cid:1227265836.18594.12.camel(at)mindy" align="bottom"
border="0"><br><br>
 Viel Spass<br><br><table cellspacing="0" cellpadding="0"
width="100%"><tr><td>
 <pre>
 --
-Christian Theune &#183; <a>ct(at)gocept.com</a>
+Christian Theune &#183; <a
href="mailto:ct(at)gocept.com">ct(at)gocept.com</a>
 gocept gmbh &amp; co. kg &#183; forsterstra&#223;e 29 &#183;
06112 halle (saale) &#183; germany
-<a>http://gocept.com</a>&#160;&#183;
tel +49 345 1229889 7 &#183; fax +49 345 1229889 1
+<a href="http://gocept.com">http://gocept.com</a>&#160;&#183;
tel +49 345 1229889 7 &#183; fax +49 345 1229889 1
 Zope and Plone consulting and development
 </pre>
 </td>
 </tr></table></div></div>
+  <div><div class="footer">
+</div>
+</div>
+</div>
 
->>> print r['attachments']
-  <div class="messagebody">
-    <h3>Attachments</h3>
-    <ul>
-      <li>
-        <a href="http://localhost/profile/ben-example.com/+Testmessages/*.../*body/1/2">drawing.png</a>
(<span>image/png</span> <span>110992</span>)
-      </li>
-      <li>
-        <a href="http://localhost/profile/ben-example.com/+Testmessages/*.../*body/2">random.zip</a>
(<span>application/zip</span> <span>104276</span>)
-      </li>
-    </ul>
-  </div>
+>>> print r['footer']
+<div class="footer">
+  <div class="footercomponent"><h6>Attachments</h6>
+<BLANKLINE>
+<ul>
+  <li>
+    <a href="#">drawing.png</a>
(<span>image/png</span> <span>110992</span>)
+  </li>
+  <li>
+    <a href="#">random.zip</a>
(<span>application/zip</span> <span>104276</span>)
+  </li>
+</ul>
+</div>
+</div>
 
 
 Claws, text/plain, UTF-8 (Japanese & German)
(at)(at) -223,9 +252,14 (at)(at)
 Japanese characters and umlauts, encoded in UTF-8:
 
 >>> r = render_message(folder, '13 - Claws, text/plain, UTF-8
(Japanese, German)')
->>> r['body']
-u'<div class="messagebody">\n  <pre>Altjapanisch
(\u4e0a\u53e4\u65e5\u672c\u8a9e, j\u014dko nihongo) sp\xe4testens seit der
Nara-Zeit\r\n</pre>\n</div>\n'
-
+>>> print r['body']
+<div class="messagepart">
+  <div><pre>Altjapanisch
(&#19978;&#21476;&#26085;&#26412;&#35486;, j&#333;ko
nihongo) sp&#228;testens seit der Nara-Zeit
+</pre></div>
+  <div><div class="footer">
+</div>
+</div>
+</div>
 
 Webmail, text/html, UTF-8 (Japanese & German)
 ---------------------------------------------
(at)(at) -235,7 +269,12 (at)(at)
 
 >>> r = render_message(folder, '14 - Webmail, text/html, UTF-8
(Japanese, German)')
 >>> print r['body']
-<div ... (<span class="lang"
lang="ja-Hani">&#19978;&#21476;&#26085;&#26412;&#35486;</span>,
<em>j&#333;ko nihongo</em>) sp&#228;testens seit ...
+<div class="messagepart">
+  <div><div><a href="http://de.wikipedia.org/wiki/Altjapanisch"
title="Altjapanisch" class="mw-redirect">Altjapanisch</a> (<span
class="lang"
lang="ja-Hani">&#19978;&#21476;&#26085;&#26412;&#35486;</span>,
<em>j&#333;ko nihongo</em>) sp&#228;testens seit der <a
href="http://de.wikipedia.org/wiki/Nara-Zeit"
title="Nara-Zeit">Nara-Zeit</a></div></div>
+  <div><div class="footer">
+</div>
+</div>
+</div>
 
 
 AppleMail, text/html, UTF-8 (many languages)
(at)(at) -246,15 +285,12 (at)(at)
 
 >>> r = render_message(folder, '15 - AppleMail, text/html, UTF-8
(many languages)')
 >>> print r['body']
-<div
-...<a>Aragon&#233;s</a>...
-...<a>&#1575;&#1604;&#1593;&#1585;&#1576;&#1610;&#1577;</a>...
-...<a>Az&#601;rbaycan</a>...
-...<a>&#1041;&#1098;&#1083;&#1075;&#1072;&#1088;&#1089;&#1082;&#1080;</a>...
-...<a>Bosanski</a>...
-...<a>&#6661;&#6676;
&#6677;&#6680;&#6657;&#6679;</a>...
-...<a>Catal&#224;</a>...
-...<a>&#268;esky</a>...
+<div class="messagepart">
+  <div><div><font class="Apple-style-span"
color="#FF3F45">pjlk</font>j<div><br></div><div>wie
w&#228;rs
damit</div><div><br></div><div><span
class="Apple-style-span"><ul><li class="interwiki-af"><a
href="http://af.wikipedia.org/wiki/Python">Afrikaans</a></li><li
class="interwiki-an"><a href="http://an.wikipedia.org/wiki/Python">Aragon&#233;s</a></li><li
class="interwiki-ar"><a href="http://ar.wikipedia.org/wiki/%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86">&#1575;&#1604;&#1593;&#1585;&#1576;&#1610;&#1577;</a></li><li
class="interwiki-az"><a href="http://az.wikipedia.org/wiki/Python">Az&#601;rbaycan</a></li><li
class="interwiki-bg"><a href="http://bg.wikipedia.org/wiki/Python">&#1041;&#1098;&#1083;&#1075;&#1072;&#1088;&#1089;&#1082;&#1080;</a></li><li
class="interwiki-bs"><a href="http://bs.wikipedia.org/wiki/Python_programski_jezik">Bosanski</a></li><li
class="interwiki-bug"><a href="http://bug.wikipedia.org/wiki/Python">&#6661;&#6676;
&#6677;&#6680;&#6657;&#6679;</a></li><li class
 ="interwiki-ca"><a href="http://ca.wikipedia.org/wiki/Python">Catal&#224;</a></li><li
class="interwiki-cs"><a href="http://cs.wikipedia.org/wiki/Python">&#268;esky</a></li><li
class="interwiki-da"><a href="http://da.wikipedia.org/wiki/Python_(programmeringssprog)">Dansk</a></li><li
class="interwiki-el"><a href="http://el.wikipedia.org/wiki/Python">&#917;&#955;&#955;&#951;&#957;&#953;&#954;&#940;</a></li><li
class="interwiki-en"><a href="http://en.wikipedia.org/wiki/Python_(programming_language)">English</a></li><li
class="interwiki-eo"><a href="http://eo.wikipedia.org/wiki/Python_(programlingvo)">Esperanto</a></li><li
class="interwiki-es"><a href="http://es.wikipedia.org/wiki/Python">Espa&#241;ol</a></li><li
class="interwiki-et"><a href="http://et.wikipedia.org/wiki/Python_(programmeerimiskeel)">Eesti</a></li><li
class="interwiki-eu"><a href="http://eu.wikipedia.org/wiki/Python">Euskara</a></li><li
class="interwiki-fa"><a href="http://fa.wikipedia.org/wiki/%D8%B2%D8%A8%D8%A7

%D9%86_%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D9%87%E2%80%8C%D9%86%D9%88%DB%8C%D8%B3%DB%8C_%D9%BE%D8%A7%DB%8C%D8%AA%D9%88%D9%86">&#1601;&#1575;&#1585;&#1587;&#1740;</a></li><li
class="interwiki-fi"><a href="http://fi.wikipedia.org/wiki/Python">Suomi</a></li><li
class="interwiki-fr"><a href="http://fr.wikipedia.org/wiki/Python_(langage)">Fran&#231;ais</a></li><li
class="interwiki-gl"><a href="http://gl.wikipedia.org/wiki/Python">Galego</a></li><li
class="interwiki-he"><a href="http://he.wikipedia.org/wiki/Python">&#1506;&#1489;&#1512;&#1497;&#1514;</a></li><li
class="interwiki-hu"><a href="http://hu.wikipedia.org/wiki/Python_(programoz%C3%A1si_nyelv)">Magyar</a></li><li
class="interwiki-is"><a href="http://is.wikipedia.org/wiki/Python_(forritunarm%C3%A1l)">&#205;slenska</a></li><li
class="interwiki-it"><a href="http://it.wikipedia.org/wiki/Python">Italiano</a></li><li
class="interwiki-ja"><a href="http://ja.wikipedia.org/wiki/Python">&#26085;&#26412;&#35486;</a></li><li
class="interw
 iki-ka"><a href="http://ka.wikipedia.org/wiki/%E1%83%9E%E1%83%98%E1%83%97%E1%83%9D%E1%83%9C%E1%83%98_(%E1%83%9E%E1%83%A0%E1%83%9D%E1%83%92%E1%83%A0%E1%83%90%E1%83%9B%E1%83%98%E1%83%A0%E1%83%94%E1%83%91%E1%83%98%E1%83%A1_%E1%83%94%E1%83%9C%E1%83%90)">&#4325;&#4304;&#4320;&#4311;&#4323;&#4314;&#4312;</a></li><li
class="interwiki-ko"><a href="http://ko.wikipedia.org/wiki/%ED%8C%8C%EC%9D%B4%EC%8D%AC">&#54620;&#44397;&#50612;</a></li><li
class="interwiki-la"><a href="http://la.wikipedia.org/wiki/Python">Latina</a></li><li
class="interwiki-lt"><a href="http://lt.wikipedia.org/wiki/Python">Lietuvi&#371;</a></li><li
class="interwiki-ml"><a href="http://ml.wikipedia.org/wiki/%E0%B4%AA%E0%B5%88%E0%B4%A4%E0%B5%8D%E0%B4%A4%E0%B4%A3%E0%B5%8D%E2%80%8D">&#3374;&#3378;&#3375;&#3390;&#3379;&#3330;</a></li><li
class="interwiki-ms"><a href="http://ms.wikipedia.org/wiki/Python">Bahasa
Melayu</a></li><li class="interwiki-nl"><a href="http://nl.wikipedia.org/wiki/Python_(programmeertaal)">Nederla
 nds</a></li><li class="interwiki-no"><a href="http://no.wikipedia.org/wiki/Python">Norsk
(bokm&#229;l)</a></li><li class="interwiki-pl"><a
href="http://pl.wikipedia.org/wiki/Python">Polski</a></li><li
class="interwiki-pt"><a href="http://pt.wikipedia.org/wiki/Python">Portugu&#234;s</a></li><li
class="interwiki-ro"><a href="http://ro.wikipedia.org/wiki/Python">Rom&#226;n&#259;</a></li><li
class="interwiki-ru FA" title="Dieser Artikel wurde als exzellent
bewertet."><a href="http://ru.wikipedia.org/wiki/Python">&#1056;&#1091;&#1089;&#1089;&#1082;&#1080;&#1081;</a></li><li
class="interwiki-simple"><a href="http://simple.wikipedia.org/wiki/Python_(programming_language)">Simple
English</a></li><li class="interwiki-sk"><a href="http://sk.wikipedia.org/wiki/Python_(programovac%C3%AD_jazyk)">Sloven&#269;ina</a></li><li
class="interwiki-sl"><a href="http://sl.wikipedia.org/wiki/Python_(programski_jezik)">Sloven&#353;&#269;ina</a></li><li
class="interwiki-sq"><a href="http://sq.wikiped
 ia.org/wiki/Python">Shqip</a></li><li
class="interwiki-sr"><a href="http://sr.wikipedia.org/wiki/%D0%9F%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D1%81%D0%BA%D0%B8_%D1%98%D0%B5%D0%B7%D0%B8%D0%BA_%D0%9F%D0%B0%D1%98%D1%82%D0%BE%D0%BD">&#1057;&#1088;&#1087;&#1089;&#1082;&#1080;
/ Srpski</a></li><li class="interwiki-sv"><a href="http://sv.wikipedia.org/wiki/Python_(programspr%C3%A5k)">Svenska</a></li><li
class="interwiki-ta"><a href="http://ta.wikipedia.org/wiki/%E0%AE%AA%E0%AF%88%E0%AE%A4%E0%AF%8D%E0%AE%A4%E0%AF%8B%E0%AE%A9%E0%AF%8D">&#2980;&#2990;&#3007;&#2996;&#3021;</a></li><li
class="interwiki-te"><a href="http://te.wikipedia.org/wiki/%E0%B0%AA%E0%B1%88%E0%B0%A5%E0%B0%BE%E0%B0%A8%E0%B1%8D_(%E0%B0%95%E0%B0%82%E0%B0%AA%E0%B1%8D%E0%B0%AF%E0%B1%82%E0%B0%9F%E0%B0%B0%E0%B1%8D_%E0%B0%AD%E0%B0%BE%E0%B0%B7)">&#3108;&#3142;&#3122;&#3137;&#3095;&#3137;</a></li><li
class="interwiki-th"><a href="http://th.wikipedia.org/wiki/%E0%B8%A0%E0%B8%B2%E0%B8%A9%E0%B8%B2%E0%B9%84%E0%B8%9E%E0%B8%97%E0%B8
 %AD%E0%B8%99">&#3652;&#3607;&#3618;</a></li><li
class="interwiki-tr"><a href="http://tr.wikipedia.org/wiki/Python_(programlama_dili)">T&#252;rk&#231;e</a></li><li
class="interwiki-uk"><a href="http://uk.wikipedia.org/wiki/Python">&#1059;&#1082;&#1088;&#1072;&#1111;&#1085;&#1089;&#1100;&#1082;&#1072;</a></li><li
class="interwiki-vi"><a href="http://vi.wikipedia.org/wiki/Python_(ng%C3%B4n_ng%E1%BB%AF_l%E1%BA%ADp_tr%C3%ACnh)">Ti&#7871;ng
Vi&#7879;t</a></li><li class="interwiki-zh"><a
href="http://zh.wikipedia.org/wiki/Python">&#20013;&#25991;</a></li></ul><div><font
class="Apple-style-span" size="3"><span
class="Apple-style-span"><br></span></font></div></span></div><div><br><div>
<div><div><div>--&#160;</div><div>Christian
Zagrodnick &#183; <a
href="mailto:cz(at)gocept.com">cz(at)gocept.com</a></div><div>gocept
gmbh &amp; co. kg &#183; forsterstra&#223;e 29 &#183; 06112
halle (saale) &#183; germany</div><div><a href="http://gocept.com">http://gocept.com</a> &#183;
tel +49 345 122988
 9 0 &#183; fax +49 345 1229889 1</div><div>Zope and Plone
consulting and
development</div><br></div></div><br></div><br></div></div></div>
+  <div><div class="footer">
+</div>
+</div>
+</div>
 
 
 Artificial message, text/html, undeclared UTF-8 (Japanese & German)
(at)(at) -266,7 +302,12 (at)(at)
 
 >>> r = render_message(folder, '16 - Artificial, text/html,
undeclared UTF-8 (Japanese, German)')
 >>> print r['body']
-<div
class="messagebody"><div><p>&#19978;&#21476;&#26085;&#26412;&#35486;
<em>j&#333;ko nihongo</em></p></div></div>
+<div class="messagepart">
+ 
<div><div><p>&#19978;&#21476;&#26085;&#26412;&#35486;
<em>j&#333;ko nihongo</em></p></div></div>
+  <div><div class="footer">
+</div>
+</div>
+</div>
 
 
 Artificial message, text/html, undeclared Latin-1
(at)(at) -278,7 +319,12 (at)(at)
 
 >>> r = render_message(folder, '17 - Artificial, text/html,
undeclared Latin-1 (German)')
 >>> print r['body']
-<div
class="messagebody"><div>&#246;&#228;&#252;</div></div>
+<div class="messagepart">
+  <div><div>&#246;&#228;&#252;</div></div>
+  <div><div class="footer">
+</div>
+</div>
+</div>
 
 
 Artificial message, text/html, undeclared EUC-JP
(at)(at) -290,4 +336,9 (at)(at)
 
 >>> r = render_message(folder, '18 - Artificial, text/html,
undeclared EUC-JP (Japanese)')
 >>> print r['body']
-<div
class="messagebody"><div><p>&#190;&#229;&#184;&#197;&#198;&#252;&#203;&#220;&#184;&#236;
<em>j&#143;&#171;&#215;ko
nihongo</em></p></div></div>
+<div class="messagepart">
+ 
<div><div><p>&#190;&#229;&#184;&#197;&#198;&#252;&#203;&#220;&#184;&#236;
<em>j&#143;&#171;&#215;ko
nihongo</em></p></div></div>
+  <div><div class="footer">
+</div>
+</div>
+</div>

Modified: gocept.restmail/trunk/setup.py
==============================================================================
--- gocept.restmail/trunk/setup.py	(original)
+++ gocept.restmail/trunk/setup.py	Fri Dec 12 10:29:45 2008
(at)(at) -17,7 +17,7 (at)(at)
     namespace_packages=['gocept'],
     install_requires=[
         'gocept.imapapi>0.1',
-        'lxml',
+        'lxml==2.1',
         'python-cjson',
         'setuptools',
         'uuid',

SVN: r7266 - gocept.restmail/trunk/gocept/restmail/browser
Sebastian Wehrmann <sw(at)gocept.com>
2008-12-12 11:27:55 [ FULL ]
Author: sweh
Date: Fri Dec 12 11:27:54 2008
New Revision: 7266

Log:
add tests for copy, move and delete of messages in the MessageWebAPI



Modified:
   gocept.restmail/trunk/gocept/restmail/browser/README.txt

Modified: gocept.restmail/trunk/gocept/restmail/browser/README.txt
==============================================================================
--- gocept.restmail/trunk/gocept/restmail/browser/README.txt	(original)
+++ gocept.restmail/trunk/gocept/restmail/browser/README.txt	Fri Dec 12
11:27:54 2008
(at)(at) -442,12 +442,46 (at)(at)
 
 Messages can be deleted using the `(at)(at)delete` view.
 
-XXX Test it when tests work again
-
+>>> len(messages)
+2
+>>> json_request(messages[0]['url']+'/(at)(at)delete', post=True)
+{}
+>>> INBOX_url = 'http://localhost/profile/ben-example.com/+INBOX'
+>>> messages = json_request(INBOX_url + '/(at)(at)messages')
+>>> len(messages)
+1
 
 Copy and move messages
 ----------------------
 
-Messages can be copied and moved using the `(at)(at)copy` and `(at)(at)move`
view.
+Messages can be copied and moved using the `(at)(at)copy` and `(at)(at)move`
view. You must
+pass the url of the target folder.
+
+When copying, the source message will not be deleted:
 
-XXX Test it when tests work again
+>>> folders = json_request(INBOX_url + '/(at)(at)folders')
+>>> folders
+[{'url': 'http://localhost/profile/ben-example.com/+INBOX/+Baz',
'type': 'inbox', 'name': 'Baz', 'children': 0}]
+>>> BAZ_url = folders[0]['url']
+>>> baz_messages = json_request(BAZ_url + '/(at)(at)messages')
+>>> len(baz_messages)
+0
+>>> json_request(messages[0]['url']+'/(at)(at)copy',
folder_url=BAZ_url, post=True)
+{}
+>>> baz_messages = json_request(BAZ_url + '/(at)(at)messages')
+>>> len(baz_messages)
+1
+>>> messages = json_request(INBOX_url + '/(at)(at)messages')
+>>> len(messages)
+1
+
+Moving messages implies the deletion of the source message:
+
+>>> json_request(baz_messages[0]['url']+'/(at)(at)move',
folder_url=INBOX_url, post=True)
+{}
+>>> baz_messages = json_request(BAZ_url + '/(at)(at)messages')
+>>> len(baz_messages)
+0
+>>> messages = json_request(INBOX_url + '/(at)(at)messages')
+>>> len(messages)
+2

SVN: r7269 - in gocept.restmail/trunk/gocept/restmail: . browser
Sebastian Wehrmann <sw(at)gocept.com>
2008-12-12 13:10:13 [ FULL ]
Author: sweh
Date: Fri Dec 12 13:10:12 2008
New Revision: 7269

Log:
add a check_connection method to account
remove bare excepts
(bug #4569)



Modified:
   gocept.restmail/trunk/gocept/restmail/browser/profile.py
   gocept.restmail/trunk/gocept/restmail/connection.txt
   gocept.restmail/trunk/gocept/restmail/imapaccount.py

Modified: gocept.restmail/trunk/gocept/restmail/browser/profile.py
==============================================================================
--- gocept.restmail/trunk/gocept/restmail/browser/profile.py	(original)
+++ gocept.restmail/trunk/gocept/restmail/browser/profile.py	Fri Dec 12
13:10:12 2008
(at)(at) -6,7 +6,6 (at)(at)
 import gocept.restmail.identity
 import gocept.restmail.imapaccount
 import gocept.restmail.browser.json
-import socket
 import xml.sax.saxutils
 import zope.app.zapi
 
(at)(at) -22,10 +21,7 (at)(at)
         for account in self.context.objectValues('IMAP Account'):
             identity = gocept.restmail.interfaces.IIdentity(account)
             status = 'ok'
-            try:
-                # XXX refactor into account.check_connection()
-                account.folders()
-            except:
+            if not account.check_connection():
                 status = 'failure'
             data.append(
                 {'status': status,
(at)(at) -83,14 +79,9 (at)(at)
         """Child listing to support tree views."""
         data = []
         for account in self.context.objectValues('IMAP Account'):
-            try:
-                # Prevent raising an exception if the server is not available
-                # (e.g. because of a wrong configuration). Available accounts
-                # should be listet although.
+            children = 0
+            if account.check_connection():
                 children = len(account.folders())
-            except:
-                # XXX Log an error for the user
-                children = 0
             identity = gocept.restmail.interfaces.IIdentity(account)
             data.append(
                 {'url': zope.app.zapi.absoluteURL(account, self.request),

Modified: gocept.restmail/trunk/gocept/restmail/connection.txt
==============================================================================
--- gocept.restmail/trunk/gocept/restmail/connection.txt	(original)
+++ gocept.restmail/trunk/gocept/restmail/connection.txt	Fri Dec 12 13:10:12
2008
(at)(at) -40,7 +40,7 (at)(at)
 
 >>> create_connections(1)
 Traceback (most recent call last):
-error: Maximum number of connections from user+IP exceeded
+IMAPConnectionError: Maximum number of connections from user+IP exceeded
 
 After a request, Zope crosses a transaction boundary which causes all IMAP
 connections to be closed so we can open new ones again.
(at)(at) -55,7 +55,7 (at)(at)
 
 >>> create_connections(10)
 Traceback (most recent call last):
-error: Maximum number of connections from user+IP exceeded
+IMAPConnectionError: Maximum number of connections from user+IP exceeded
 >>> import transaction
 >>> transaction.abort()
 >>> create_connections(1)

Modified: gocept.restmail/trunk/gocept/restmail/imapaccount.py
==============================================================================
--- gocept.restmail/trunk/gocept/restmail/imapaccount.py	(original)
+++ gocept.restmail/trunk/gocept/restmail/imapaccount.py	Fri Dec 12 13:10:12
2008
(at)(at) -10,11 +10,14 (at)(at)
 from OFS.ObjectManager import ObjectManager
 
 import UserDict
+import gocept.imapapi
 import gocept.imapapi.account
 import gocept.restmail.interfaces
 import transaction
 import zope.component
 import zope.interface
+import socket
+import imaplib
 
 
 class IMAPAccount(ObjectManager, PropertyManager, Item):
(at)(at) -82,6 +85,15 (at)(at)
         connection.folders[name] = new_folder
         return self.folders(name)[0]
 
+    def check_connection(self):
+        try:
+            self.folders()
+        except gocept.imapapi.IMAPServerError:
+            return False
+        except gocept.imapapi.IMAPConnectionError:
+            return False
+        return True
+
     # ZMI support
 
     isPrincipiaFolderish = True

SVN: r7272 - gocept.restmail/trunk/gocept/restmail/browser
Sebastian Wehrmann <sw(at)gocept.com>
2008-12-12 15:53:50 [ FULL ]
Author: sweh
Date: Fri Dec 12 15:53:49 2008
New Revision: 7272

Log:
add a json view to MessageWebApi returning the messages source



Modified:
   gocept.restmail/trunk/gocept/restmail/browser/configure.zcml
   gocept.restmail/trunk/gocept/restmail/browser/message.py

Modified: gocept.restmail/trunk/gocept/restmail/browser/configure.zcml
==============================================================================
--- gocept.restmail/trunk/gocept/restmail/browser/configure.zcml	(original)
+++ gocept.restmail/trunk/gocept/restmail/browser/configure.zcml	Fri Dec 12
15:53:49 2008
(at)(at) -195,6 +195,11 (at)(at)
       />
 
     <browser:page
+      name="raw"
+      attribute="raw"
+      />
+
+    <browser:page
       name="reply"
       attribute="reply"
       />

Modified: gocept.restmail/trunk/gocept/restmail/browser/message.py
==============================================================================
--- gocept.restmail/trunk/gocept/restmail/browser/message.py	(original)
+++ gocept.restmail/trunk/gocept/restmail/browser/message.py	Fri Dec 12
15:53:49 2008
(at)(at) -63,6 +63,10 (at)(at)
                     footer=IFooter(rendered_message))
 
     (at)gocept.restmail.browser.json.view
+    def raw(self):
+        return {'raw': '<pre>%s</pre>' % self.context.message.raw}
+
+    (at)gocept.restmail.browser.json.view
     def reply(self):
         """Create a new draft message."""
         profile = gocept.restmail.interfaces.IProfile(self.context)

SVN: r7273 - gocept.collmex/trunk/src/gocept/collmex
Wolfgang Schnerring <ws(at)gocept.com>
2008-12-15 11:12:56 [ FULL ]
Author: wosc
Date: Mon Dec 15 11:12:55 2008
New Revision: 7273

Log:
convert values coming from Collmex to unicode



Modified:
   gocept.collmex/trunk/src/gocept/collmex/model.py

Modified: gocept.collmex/trunk/src/gocept/collmex/model.py
==============================================================================
--- gocept.collmex/trunk/src/gocept/collmex/model.py	(original)
+++ gocept.collmex/trunk/src/gocept/collmex/model.py	Mon Dec 15 11:12:55 2008
(at)(at) -18,9 +18,10 (at)(at)
         self['Rechnungsart'] = 0 # type invoice
 
         for i in range(len(row)):
-            if row[i] == '':
-                row[i] = None
-            self[self.fields[i]] = row[i]
+            if row[i] == '' or row[i] is None:
+                self[self.fields[i]] = None
+            else:
+                self[self.fields[i]] = unicode(row[i], 'Windows-1252')
 
     def __iter__(self):
         for field in self.fields:

SVN: r7282 - in gocept.restmail/trunk/gocept/restmail: . browser
Sebastian Wehrmann <sw(at)gocept.com>
2008-12-15 14:33:50 [ FULL ]
Author: sweh
Date: Mon Dec 15 14:33:49 2008
New Revision: 7282

Log:
set a message to \Answered when replying to it
show message flags in the YUI API



Modified:
   gocept.restmail/trunk/gocept/restmail/browser/README.txt
   gocept.restmail/trunk/gocept/restmail/browser/message.py
   gocept.restmail/trunk/gocept/restmail/draft.py
   gocept.restmail/trunk/gocept/restmail/imapaccount.py

Modified: gocept.restmail/trunk/gocept/restmail/browser/README.txt
==============================================================================
--- gocept.restmail/trunk/gocept/restmail/browser/README.txt	(original)
+++ gocept.restmail/trunk/gocept/restmail/browser/README.txt	Mon Dec 15
14:33:49 2008
(at)(at) -182,10 +182,12 (at)(at)
 >>> messages = json_request('http://localhost/profile/ben-example.com/+INBOX/(at)(at)messages')
 >>> pprint.pprint(messages)
 [{'date': '02-Jul-2008 03:05:00 +0200',
+  'flags': [],
   'from': 'test(at)localhost',
   'subject': 'Mail 1',
   'url': 'http://localhost/profile/ben-example.com/+INBOX/*...'},
  {'date': '02-Jul-2008 03:06:00 +0200',
+  'flags': [],
   'from': 'test(at)localhost',
   'subject': 'Mail 2',
   'url': 'http://localhost/profile/ben-example.com/+INBOX/*...'}]
(at)(at) -395,6 +397,7 (at)(at)
 
 >>> pprint.pprint(json_request('http://localhost/profile/ben-example.com/+Sent/(at)(at)messages'))
 [{'date': None,
+  'flags': [],
   'from': 'Ben Utzer <ben(at)example.com>',
   'subject': 'asdf',
   'url': 'http://localhost/profile/ben-example.com/+Sent/*...'}]
(at)(at) -436,6 +439,20 (at)(at)
 </body>
 </html>
 
+The original message is now flaged as ``\Seen`` and ``\Answered``:
+
+>>> pprint.pprint(json_request('http://localhost/profile/ben-example.com/+INBOX/(at)(at)messages'))
+[{'date': '02-Jul-2008 03:05:00 +0200',
+  'flags': ['\\Answered', '\\Seen'],
+  'from': 'test(at)localhost',
+  'subject': 'Mail 1',
+  'url': 'http://localhost/profile/ben-example.com/+INBOX/*...'},
+ {'date': '02-Jul-2008 03:06:00 +0200',
+  'flags': [],
+  'from': 'test(at)localhost',
+  'subject': 'Mail 2',
+  'url': 'http://localhost/profile/ben-example.com/+INBOX/*...'}]
+
 
 Deleting messages
 -----------------

Modified: gocept.restmail/trunk/gocept/restmail/browser/message.py
==============================================================================
--- gocept.restmail/trunk/gocept/restmail/browser/message.py	(original)
+++ gocept.restmail/trunk/gocept/restmail/browser/message.py	Mon Dec 15
14:33:49 2008
(at)(at) -28,6 +28,7 (at)(at)
             'from': message.headers.get('From'),
             'subject': message.headers.get('Subject'),
             'date': message.headers.get('Date'),
+            'flags': message.flags
             } for message in self.context.messages()]
         return data
 

Modified: gocept.restmail/trunk/gocept/restmail/draft.py
==============================================================================
--- gocept.restmail/trunk/gocept/restmail/draft.py	(original)
+++ gocept.restmail/trunk/gocept/restmail/draft.py	Mon Dec 15 14:33:49 2008
(at)(at) -59,6 +59,7 (at)(at)
         if msgid:
             self.in_reply_to = msgid
             references += ' ' + msgid
+        message.message.answered() #XXX: do it when sending the draft
         if references:
             self.references = references
 

Modified: gocept.restmail/trunk/gocept/restmail/imapaccount.py
==============================================================================
--- gocept.restmail/trunk/gocept/restmail/imapaccount.py	(original)
+++ gocept.restmail/trunk/gocept/restmail/imapaccount.py	Mon Dec 15 14:33:49
2008
(at)(at) -215,6 +215,10 (at)(at)
         return self.message.headers
 
     (at)property
+    def flags(self):
+        return self.message.flags
+
+    (at)property
     def text(self):
         return self.message.text

SVN: r7295 - gocept.restmail/trunk/gocept/restmail
Sebastian Wehrmann <sw(at)gocept.com>
2008-12-16 11:39:29 [ FULL ]
Author: sweh
Date: Tue Dec 16 11:39:28 2008
New Revision: 7295

Log:
bugfix with specially quoted mails



Modified:
   gocept.restmail/trunk/gocept/restmail/render.py

Modified: gocept.restmail/trunk/gocept/restmail/render.py
==============================================================================
--- gocept.restmail/trunk/gocept/restmail/render.py	(original)
+++ gocept.restmail/trunk/gocept/restmail/render.py	Tue Dec 16 11:39:28 2008
(at)(at) -247,8 +247,11 (at)(at)
             elif c != ' ':
                 break
         if level:
-            if line[last_quote+1] == ' ':
+            try:
+                if line[last_quote+1] == ' ':
+                    line = line[last_quote+2:]
+                else:
+                    line = line[last_quote+1:]
+            except IndexError:
                 line = line[last_quote+2:]
-            else:
-                line = line[last_quote+1:]
         return level, line

SVN: r7297 - in gocept.restmail/trunk/gocept/restmail: . testmessages
Sebastian Wehrmann <sw(at)gocept.com>
2008-12-17 08:33:57 [ FULL ]
Author: sweh
Date: Wed Dec 17 08:33:56 2008
New Revision: 7297

Log:
testcase for bugfix, if a quote does not end with a whitespace



Modified:
   gocept.restmail/trunk/gocept/restmail/render.py
   gocept.restmail/trunk/gocept/restmail/render.txt
  
gocept.restmail/trunk/gocept/restmail/testmessages/26-ascii-quoting-whitespaces

Modified: gocept.restmail/trunk/gocept/restmail/render.py
==============================================================================
--- gocept.restmail/trunk/gocept/restmail/render.py	(original)
+++ gocept.restmail/trunk/gocept/restmail/render.py	Wed Dec 17 08:33:56 2008
(at)(at) -253,5 +253,5 (at)(at)
                 else:
                     line = line[last_quote+1:]
             except IndexError:
-                line = line[last_quote+2:]
+                line = '' 
         return level, line

Modified: gocept.restmail/trunk/gocept/restmail/render.txt
==============================================================================
--- gocept.restmail/trunk/gocept/restmail/render.txt	(original)
+++ gocept.restmail/trunk/gocept/restmail/render.txt	Wed Dec 17 08:33:56 2008
(at)(at) -57,6 +57,12 (at)(at)
 one leading space after quote
  two leading spaces after quote
 </pre></blockquote>
+<pre>
+no text after quote:
+</pre>
+<blockquote><pre>
+</pre></blockquote>
+
 
 Encoding
 --------

Modified:
gocept.restmail/trunk/gocept/restmail/testmessages/26-ascii-quoting-whitespaces
==============================================================================
---
gocept.restmail/trunk/gocept/restmail/testmessages/26-ascii-quoting-whitespaces	(original)
+++
gocept.restmail/trunk/gocept/restmail/testmessages/26-ascii-quoting-whitespaces	Wed
Dec 17 08:33:56 2008
(at)(at) -6,3 +6,6 (at)(at)
 >no leading space after quote
 > one leading space after quote
 >  two leading spaces after quote
+
+no text after quote:
+>

SVN: r7306 - in gocept.lockd/trunk: . gocept gocept/lockd
Christian Theune <ct(at)gocept.com>
2008-12-17 14:32:42 [ FULL ]
Author: ctheune
Date: Wed Dec 17 14:32:40 2008
New Revision: 7306

Log:
First shot at a *very simple* lock server.



Added:
   gocept.lockd/trunk/bootstrap.py   (contents, props changed)
   gocept.lockd/trunk/buildout.cfg
   gocept.lockd/trunk/gocept/
   gocept.lockd/trunk/gocept/__init__.py   (contents, props changed)
   gocept.lockd/trunk/gocept/lockd/
   gocept.lockd/trunk/gocept/lockd/__init__.py   (contents, props changed)
   gocept.lockd/trunk/gocept/lockd/client.py   (contents, props changed)
   gocept.lockd/trunk/gocept/lockd/lockd.py   (contents, props changed)
   gocept.lockd/trunk/setup.py   (contents, props changed)
Modified:
   gocept.lockd/trunk/   (props changed)

Added: gocept.lockd/trunk/bootstrap.py
==============================================================================
--- (empty file)
+++ gocept.lockd/trunk/bootstrap.py	Wed Dec 17 14:32:40 2008
(at)(at) -0,0 +1,55 (at)(at)
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Bootstrap a buildout-based project
+
+Simply run this script in a directory containing a buildout.cfg.
+The script accepts buildout command-line options, so you can
+use the -c option to specify an alternate configuration file.
+
+$Id$
+"""
+
+import os, shutil, sys, tempfile, urllib2
+
+tmpeggs = tempfile.mkdtemp()
+
+try:
+    import pkg_resources
+except ImportError:
+    ez = {}
+    exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py'
+                         ).read() in ez
+    ez['use_setuptools'](to_dir=tmpeggs, download_delay=0)
+
+    import pkg_resources
+
+cmd = 'from setuptools.command.easy_install import main; main()'
+if sys.platform == 'win32':
+    cmd = '"%s"' % cmd # work around spawn lamosity on windows
+
+ws = pkg_resources.working_set
+assert os.spawnle(
+    os.P_WAIT, sys.executable, sys.executable,
+    '-c', cmd, '-mqNxd', tmpeggs, 'zc.buildout',
+    dict(os.environ,
+         PYTHONPATH=
+         ws.find(pkg_resources.Requirement.parse('setuptools')).location
+         ),
+    ) == 0
+
+ws.add_entry(tmpeggs)
+ws.require('zc.buildout')
+import zc.buildout.buildout
+zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap'])
+shutil.rmtree(tmpeggs)

Added: gocept.lockd/trunk/buildout.cfg
==============================================================================
--- (empty file)
+++ gocept.lockd/trunk/buildout.cfg	Wed Dec 17 14:32:40 2008
(at)(at) -0,0 +1,22 (at)(at)
+[buildout]
+develop = .
+parts = lockd lockcmd
+
+[lockd]
+recipe = zc.recipe.egg
+eggs = gocept.lockd
+scripts = lockd=lockd
+arguments = "${lockd:host}", ${lockd:port}
+
+host = 127.0.0.1
+port = 8000
+
+[lockcmd]
+recipe = zc.recipe.egg
+eggs = gocept.lockd
+scripts = lockcmd=lockcmd
+arguments = "${lockcmd:server}"
+
+server = http://${lockd:host}:${lockd:port}/
+
+

Added: gocept.lockd/trunk/gocept/__init__.py
==============================================================================
--- (empty file)
+++ gocept.lockd/trunk/gocept/__init__.py	Wed Dec 17 14:32:40 2008
(at)(at) -0,0 +1,10 (at)(at)
+# Copyright (c) 2008 gocept gmbh & co. kg
+# See also LICENSE.txt
+# $Id: __init__.py 5769 2008-05-20 08:05:02Z sweh $
+
+#namespace package boilerplate
+try:
+    __import__('pkg_resources').declare_namespace(__name__)
+except ImportError, e:
+    from pkgutil import extend_path
+    __path__ = extend_path(__path__, __name__)

Added: gocept.lockd/trunk/gocept/lockd/__init__.py
==============================================================================
--- (empty file)
+++ gocept.lockd/trunk/gocept/lockd/__init__.py	Wed Dec 17 14:32:40 2008
(at)(at) -0,0 +1 (at)(at)
+# Make this a Python package

Added: gocept.lockd/trunk/gocept/lockd/client.py
==============================================================================
--- (empty file)
+++ gocept.lockd/trunk/gocept/lockd/client.py	Wed Dec 17 14:32:40 2008
(at)(at) -0,0 +1,15 (at)(at)
+# vim:fileencoding=utf-8
+# Copyright (c) 2008 gocept gmbh & co. kg
+# See also LICENSE.txt
+
+import sys
+import xmlrpclib
+
+
+def main(server):
+    _, command, resource, client = sys.argv
+    assert command in ['lock', 'unlock']
+
+    s = xmlrpclib.Server(server)
+    function = getattr(s, command)
+    function(resource, client)

Added: gocept.lockd/trunk/gocept/lockd/lockd.py
==============================================================================
--- (empty file)
+++ gocept.lockd/trunk/gocept/lockd/lockd.py	Wed Dec 17 14:32:40 2008
(at)(at) -0,0 +1,66 (at)(at)
+# vim:fileencoding=utf-8
+# Copyright (c) 2008 gocept gmbh & co. kg
+# See also LICENSE.txt
+
+import SimpleXMLRPCServer
+import os.path
+import time
+import zc.lockfile
+
+
+def run_locked(function):
+    def wrapped(self, resource, client):
+        lock = self._acquire()
+        try:
+            return function(self, resource, client)
+        finally:
+            lock.close()
+    wrapped.__name__ = function.__name__
+    return wrapped
+
+
+class Lockd(object):
+
+    def __init__(self, lockdir):
+        self.lockdir = lockdir
+        self.server_lock = os.path.join(self.lockdir, 'lockd.lock')
+
+    def _acquire(self):
+        while True:
+            try:
+                lock = zc.lockfile.LockFile('lockd.lock')
+            except zc.lockfile.LockError:
+                time.sleep(0.1)
+                continue
+            return lock
+
+    def _resource_lock_name(self, resource):
+        return os.path.join(self.lockdir, resource + '.resource.lock')
+
+    (at)run_locked
+    def lock(self, resource, client):
+        resource_lock = self._resource_lock_name(resource)
+        if os.path.exists(resource_lock):
+            client = open(resource_lock, 'r').read().strip()
+            raise Exception('Resource already locked by %r' % client)
+        open(resource_lock, 'w').write(client)
+
+    (at)run_locked
+    def unlock(self, resource, client):
+        resource_lock = self._resource_lock_name(resource)
+        if not os.path.exists(resource_lock):
+            raise Exception('Resource not locked')
+        locked_by = open(resource_lock, 'r').read().strip()
+        if client != locked_by:
+            raise Exception(
+                'Resource locked by %r cannot be unlocked by %r' % 
+                (locked_by, client))
+        os.unlink(resource_lock)
+
+
+def main(host, port):
+    server = SimpleXMLRPCServer.SimpleXMLRPCServer(
+        (host, port), allow_none=True)
+    server.register_introspection_functions()
+    server.register_instance(Lockd('/tmp/locks'))
+    server.serve_forever()

Added: gocept.lockd/trunk/setup.py
==============================================================================
--- (empty file)
+++ gocept.lockd/trunk/setup.py	Wed Dec 17 14:32:40 2008
(at)(at) -0,0 +1,27 (at)(at)
+# vim:fileencoding=utf-8
+# Copyright (c) 2008 gocept gmbh & co. kg
+# See also LICENSE.txt
+
+from setuptools import setup, find_packages
+
+setup(name='gocept.lockd',
+      version='0.1dev',
+      description="A simple XML-RPC-based lock daemon to support fencing.",
+      author="Christian Theune",
+      author_email="ct(at)gocept.com",
+      license="ZPL 2.1",
+      package_dir={'': '.'},
+      packages=find_packages('.'),
+      include_package_data=True,
+      namespace_packages=['gocept'],
+      zip_safe=False,
+      install_requires=[
+          'setuptools',
+          'zc.lockfile',
+      ],
+      entry_points="""
+        [console_scripts]
+        lockd = gocept.lockd.lockd:main
+        lockcmd = gocept.lockd.client:main
+      """,
+      )

SVN: r7307 - in gocept.lockd/trunk: . gocept/lockd
Christian Theune <ct(at)gocept.com>
2008-12-17 14:52:29 [ FULL ]
Author: ctheune
Date: Wed Dec 17 14:52:28 2008
New Revision: 7307

Log:
Added simple test coverage. The actual race condition of the server
isn't tested for.



Added:
   gocept.lockd/trunk/gocept/lockd/README.txt   (contents, props changed)
   gocept.lockd/trunk/gocept/lockd/tests.py   (contents, props changed)
Modified:
   gocept.lockd/trunk/buildout.cfg

Modified: gocept.lockd/trunk/buildout.cfg
==============================================================================
--- gocept.lockd/trunk/buildout.cfg	(original)
+++ gocept.lockd/trunk/buildout.cfg	Wed Dec 17 14:52:28 2008
(at)(at) -1,6 +1,6 (at)(at)
 [buildout]
 develop = .
-parts = lockd lockcmd
+parts = lockd lockcmd test
 
 [lockd]
 recipe = zc.recipe.egg
(at)(at) -19,4 +19,6 (at)(at)
 
 server = http://${lockd:host}:${lockd:port}/
 
-
+[test]
+recipe = zc.recipe.testrunner
+eggs = gocept.lockd

Added: gocept.lockd/trunk/gocept/lockd/README.txt
==============================================================================
--- (empty file)
+++ gocept.lockd/trunk/gocept/lockd/README.txt	Wed Dec 17 14:52:28 2008
(at)(at) -0,0 +1,54 (at)(at)
+Using the lockd API
+===================
+
+lockd provides a light-weight implementation of a locking mechanism for
+long-running resources by maintaining lock files in a given directory.
+
+>>> import tempfile
+>>> lockdir = tempfile.mkdtemp()
+>>> from gocept.lockd.lockd import Lockd
+>>> daemon = Lockd(lockdir)
+
+Resources are identified by a string and clients performing the lock are
+identified by another string:
+
+>>> daemon.lock('resource1', 'client1')
+
+Once a resource is locked, it can not be locked again:
+
+>>> daemon.lock('resource1', 'client2')
+Traceback (most recent call last):
+Exception: Resource already locked by 'client1'
+
+However, other resources can be locked in parallel:
+
+>>> daemon.lock('resource2', 'client2')
+
+Clients, other than the client who locked a resource, can not unlock it:
+
+>>> daemon.unlock('resource1', 'client2')
+Traceback (most recent call last):
+Exception: Resource locked by 'client1' cannot be unlocked by 'client2'
+
+The client, who locked the resource, can unlock it again:
+
+>>> daemon.unlock('resource1', 'client1')
+
+Once unlocked, it can not be unlocked a second time:
+
+>>> daemon.unlock('resource1', 'client1')
+Traceback (most recent call last):
+Exception: Resource not locked
+
+
+IMPORTANT security note
+=======================
+
+Resource identification and client authorization are out of scope for
+lockd. It should only be used and exposed within a trusted environment.
+
+
+Cleanup:
+
+>>> import shutil
+>>> shutil.rmtree(lockdir)

Added: gocept.lockd/trunk/gocept/lockd/tests.py
==============================================================================
--- (empty file)
+++ gocept.lockd/trunk/gocept/lockd/tests.py	Wed Dec 17 14:52:28 2008
(at)(at) -0,0 +1,12 (at)(at)
+# vim:fileencoding=utf-8
+# Copyright (c) 2008 gocept gmbh & co. kg
+# See also LICENSE.txt
+
+import unittest
+import doctest
+
+
+def test_suite():
+    suite = unittest.TestSuite()
+    suite.addTest(doctest.DocFileSuite('README.txt'))
+    return suite

SVN: r7315 - in gocept.restmail/trunk/gocept/restmail: . browser
Sebastian Wehrmann <sw(at)gocept.com>
2008-12-18 09:23:57 [ FULL ]
Author: sweh
Date: Thu Dec 18 09:23:56 2008
New Revision: 7315

Log:
add a method to view the raw content of a draft message



Modified:
   gocept.restmail/trunk/gocept/restmail/browser/configure.zcml
   gocept.restmail/trunk/gocept/restmail/browser/draft.py
   gocept.restmail/trunk/gocept/restmail/draft.py

Modified: gocept.restmail/trunk/gocept/restmail/browser/configure.zcml
==============================================================================
--- gocept.restmail/trunk/gocept/restmail/browser/configure.zcml	(original)
+++ gocept.restmail/trunk/gocept/restmail/browser/configure.zcml	Thu Dec 18
09:23:56 2008
(at)(at) -244,6 +244,11 (at)(at)
       />
 
     <browser:page
+      name="raw"
+      attribute="raw"
+      />
+
+    <browser:page
       name="send"
       attribute="send"
       />

Modified: gocept.restmail/trunk/gocept/restmail/browser/draft.py
==============================================================================
--- gocept.restmail/trunk/gocept/restmail/browser/draft.py	(original)
+++ gocept.restmail/trunk/gocept/restmail/browser/draft.py	Thu Dec 18 09:23:56
2008
(at)(at) -40,6 +40,10 (at)(at)
         self.context.body = body
 
     (at)gocept.restmail.browser.json.view
+    def raw(self):
+        return {'raw': '<pre>%s</pre>' % self.context.raw()}
+
+    (at)gocept.restmail.browser.json.view
     def send(self):
         """Update draft and send this message."""
         self.context.send()

Modified: gocept.restmail/trunk/gocept/restmail/draft.py
==============================================================================
--- gocept.restmail/trunk/gocept/restmail/draft.py	(original)
+++ gocept.restmail/trunk/gocept/restmail/draft.py	Thu Dec 18 09:23:56 2008
(at)(at) -63,7 +63,7 (at)(at)
         if references:
             self.references = references
 
-    def send(self):
+    def raw(self):
         profile = gocept.restmail.interfaces.IProfile(self)
         account = profile[self.account]
         identity = gocept.restmail.interfaces.IIdentity(account)
(at)(at) -84,7 +84,13 (at)(at)
             message['In-reply-to'] = self.in_reply_to
         if self.references:
             message['References'] = self.references
-        message = message.as_string() # XXX Memory usage
+        return message.as_string() # XXX Memory usage
+
+    def send(self):
+        profile = gocept.restmail.interfaces.IProfile(self)
+        account = profile[self.account]
+        identity = gocept.restmail.interfaces.IIdentity(account)
+        message = self.raw()
 
         # 2. Send via MailHost
         mh = profile[identity.smtp_server]

SVN: r7318 - gocept.collmex/trunk/src/gocept/collmex
Wolfgang Schnerring <ws(at)gocept.com>
2008-12-18 09:39:34 [ FULL ]
Author: wosc
Date: Thu Dec 18 09:39:33 2008
New Revision: 7318

Log:
updated tests for r7273: values from Collmex are converted to unicode



Modified:
   gocept.collmex/trunk/src/gocept/collmex/README.txt

Modified: gocept.collmex/trunk/src/gocept/collmex/README.txt
==============================================================================
--- gocept.collmex/trunk/src/gocept/collmex/README.txt	(original)
+++ gocept.collmex/trunk/src/gocept/collmex/README.txt	Thu Dec 18 09:39:33 2008
(at)(at) -1,3 +1,5 (at)(at)
+coding: utf-8
+
 Collmex API
 ===========
 
(at)(at) -53,22 +55,22 (at)(at)
 
 >>> customer = customers[0]
 >>> customer['Satzart']
-'CMXKND'
+u'CMXKND'
 >>> customer['Kundennummer']
-'9999'
+u'9999'
 >>> customer['Firma']
-'Allgemeiner Gesch\xe4ftspartner'
+u'Allgemeiner Gesch\xe4ftspartner'
 
 
 The second customer is one created during test setup:
 
 >>> customer = customers[1]
 >>> customer['Satzart']
-'CMXKND'
+u'CMXKND'
 >>> customer['Kundennummer']
-'10000'
+u'10000'
 >>> customer['Firma']
-'Testkunden'
+u'Testkunden'
 
 Products: ``create_product`` and ``get_products``
 -------------------------------------------------
(at)(at) -83,7 +85,7 (at)(at)
 >>> collmex.create_product(product)
 >>> transaction.commit()
 >>> collmex.get_products()[0]['Bezeichnung']
-'Testprodukt'
+u'Testprodukt'
 
 Invoices: ``create_invoice`` and ``get_invoices``
 -------------------------------------------------
(at)(at) -97,7 +99,7 (at)(at)
 >>> item['Rechnungsnummer'] = 100000
 >>> item['Menge'] = 3
 >>> item['Produktnummer'] = 'TEST'
->>> item['Rechnungstext'] = 'item text'
+>>> item['Rechnungstext'] = u'item text – with non-ascii
characters'
 >>> item['Positionstyp'] = 0
 >>> collmex.create_invoice([item])
 
(at)(at) -113,7 +115,7 (at)(at)
 >>> transaction.commit()
 >>> collmex.get_invoices(customer_id='10000',
 ...                      start_date=start_date)[0]['Rechnungstext']
-'item text'
+u'item text \u2013 with non-ascii characters'
 
 .. [#pre-flight-cleanup] First we need to clean up the Collmex environment:

SVN: r7319 - gocept.restmail/trunk/gocept/restmail/browser
Sebastian Wehrmann <sw(at)gocept.com>
2008-12-18 10:02:09 [ FULL ]
Author: sweh
Date: Thu Dec 18 10:02:08 2008
New Revision: 7319

Log:
added test for r7315



Modified:
   gocept.restmail/trunk/gocept/restmail/browser/README.txt

Modified: gocept.restmail/trunk/gocept/restmail/browser/README.txt
==============================================================================
--- gocept.restmail/trunk/gocept/restmail/browser/README.txt	(original)
+++ gocept.restmail/trunk/gocept/restmail/browser/README.txt	Thu Dec 18
10:02:08 2008
(at)(at) -372,6 +372,15 (at)(at)
  'to': 'ct(at)gocept.com'}
 
 
+Retrieving the raw data of draft messages
+-----------------------------------------
+
+The draft's raw content can be retrieved:
+
+>>> pprint.pprint(json_request(draft['url']+'/(at)(at)raw'))
+{'raw': '<pre>Content-Type: text/html; charset="us-ascii"\nMIME-Version:
1.0\nContent-Transfer-Encoding: 7bit\nFrom: Ben Utzer
<ben(at)example.com>\nTo: ct(at)gocept.com\nSubject:
asdf\n\nHelloHello</pre>'}
+
+
 Sending draft messages
 ----------------------

SVN: r7321 - gocept.lockd/trunk/gocept/lockd
Christian Theune <ct(at)gocept.com>
2008-12-18 12:57:49 [ FULL ]
Author: ctheune
Date: Thu Dec 18 12:57:48 2008
New Revision: 7321

Log:
make lock directory configurable



Modified:
   gocept.lockd/trunk/gocept/lockd/lockd.py

Modified: gocept.lockd/trunk/gocept/lockd/lockd.py
==============================================================================
--- gocept.lockd/trunk/gocept/lockd/lockd.py	(original)
+++ gocept.lockd/trunk/gocept/lockd/lockd.py	Thu Dec 18 12:57:48 2008
(at)(at) -58,9 +58,9 (at)(at)
         os.unlink(resource_lock)
 
 
-def main(host, port):
+def main(host, port, directory):
     server = SimpleXMLRPCServer.SimpleXMLRPCServer(
         (host, port), allow_none=True)
     server.register_introspection_functions()
-    server.register_instance(Lockd('/tmp/locks'))
+    server.register_instance(Lockd(directory))
     server.serve_forever()

SVN: r7335 - in gocept.collmex/trunk: . src/gocept/collmex
Wolfgang Schnerring <ws(at)gocept.com>
2008-12-19 09:16:28 [ FULL ]
Author: wosc
Date: Fri Dec 19 09:16:26 2008
New Revision: 7335

Log:
implemented caching of results



Modified:
   gocept.collmex/trunk/setup.py
   gocept.collmex/trunk/src/gocept/collmex/README.txt
   gocept.collmex/trunk/src/gocept/collmex/collmex.py

Modified: gocept.collmex/trunk/setup.py
==============================================================================
--- gocept.collmex/trunk/setup.py	(original)
+++ gocept.collmex/trunk/setup.py	Fri Dec 19 09:16:26 2008
(at)(at) -24,6 +24,7 (at)(at)
     license='ZPL 2.1',
     namespace_packages=['gocept'],
     install_requires=[
+        'gocept.cache>=0.3',
         'setuptools',
         'transaction',
         'zope.deprecation',

Modified: gocept.collmex/trunk/src/gocept/collmex/README.txt
==============================================================================
--- gocept.collmex/trunk/src/gocept/collmex/README.txt	(original)
+++ gocept.collmex/trunk/src/gocept/collmex/README.txt	Fri Dec 19 09:16:26 2008
(at)(at) -117,6 +117,47 (at)(at)
 ...                      start_date=start_date)[0]['Rechnungstext']
 u'item text \u2013 with non-ascii characters'
 
+
+Caching
+-------
+
+Results queried from Collmex are cached for the duration of the transaction.
+
+To demonstrate this, we instrument the _post() method that performs the actual
+HTTP communication to show when it is called:
+
+    >>> original_post = collmex._post
+    >>> def tracing_post(self, *args, **kw):
+    ...     print 'cache miss'
+    ...     return original_post(*args, **kw)
+    >>> collmex._post = tracing_post.__get__(collmex, type(collmex))
+
+The first time in an transaction is retrieved from Collmex, of course:
+
+    >>> transaction.abort()
+    >>> collmex.get_products()[0]['Bezeichnung']
+    cache miss
+    u'Testprodukt'
+
+But after that, values are cached:
+
+    >>> collmex.get_products()[0]['Bezeichnung']
+    u'Testprodukt'
+
+When the transaction ends, the cache is invalidated:
+
+    >>> transaction.commit()
+    >>> collmex.get_products()[0]['Bezeichnung']
+    cache miss
+    u'Testprodukt'
+    >>> collmex.get_products()[0]['Bezeichnung']
+    u'Testprodukt'
+ 
+Remove tracing instrumentation:
+
+    >>> collmex._post = original_post
+
+
 .. [#pre-flight-cleanup] First we need to clean up the Collmex environment:
 
     >>> import gocept.collmex.testing

Modified: gocept.collmex/trunk/src/gocept/collmex/collmex.py
==============================================================================
--- gocept.collmex/trunk/src/gocept/collmex/collmex.py	(original)
+++ gocept.collmex/trunk/src/gocept/collmex/collmex.py	Fri Dec 19 09:16:26 2008
(at)(at) -4,6 +4,8 (at)(at)
 
 import StringIO
 import csv
+import gocept.cache.method
+import gocept.cache.property
 import gocept.collmex.interfaces
 import gocept.collmex.model
 import gocept.collmex.utils
(at)(at) -62,7 +64,7 (at)(at)
 
     def commit(self, transaction):
         assert transaction is self._transaction
-        # We need to do our work in tpc_vote as we're a single-phase-only data

+        # We need to do our work in tpc_vote as we're a single-phase-only data
         # manager.
         pass
 
(at)(at) -108,13 +110,14 (at)(at)
 
         # Store thread-local (actually: transaction-local) information
         self._local = threading.local()
+        self._local.connection = None
+        self._local.cache = None
 
     (at)property
     def connection(self):
-        connection = getattr(self._local, 'connection', None)
-        if connection is None:
-            connection = self._local.connection = CollmexDataManager(self)
-        return connection
+        if self._local.connection is None:
+            self._local.connection = CollmexDataManager(self)
+        return self._local.connection
 
     def create_invoice(self, items):
         data = StringIO.StringIO()
(at)(at) -169,6 +172,24 (at)(at)
             price_group,
             0, self.system_identifier)
 
+    def _get_cache(self):
+        if self._local.cache is None:
+            self._local.cache = {}
+            dm = gocept.cache.property.CacheDataManager(
+                self, None, transaction.get())
+            transaction.get().join(dm)
+        return self._local.cache
+
+    def _set_cache(self, cache):
+        self._local.cache = cache
+
+    _cache = property(_get_cache, _set_cache)
+
+    # hook for gocept.cache.property.CacheDataManager
+    def invalidate(self, dummy):
+        self._cache = None
+
+    (at)gocept.cache.method.memoize_on_attribute('_cache', timeout=5*60)
     def _query_objects(self, function, *args):
         data = StringIO.StringIO()
         writer = csv.writer(data, dialect=CollmexDialect)

SVN: r7336 - gocept.collmex/trunk/src/gocept/collmex
Wolfgang Schnerring <ws(at)gocept.com>
2008-12-19 09:17:29 [ FULL ]
Author: wosc
Date: Fri Dec 19 09:17:28 2008
New Revision: 7336

Log:
replaced static record-type-->model dictionary with lookup



Modified:
   gocept.collmex/trunk/src/gocept/collmex/collmex.py
   gocept.collmex/trunk/src/gocept/collmex/model.py

Modified: gocept.collmex/trunk/src/gocept/collmex/collmex.py
==============================================================================
--- gocept.collmex/trunk/src/gocept/collmex/collmex.py	(original)
+++ gocept.collmex/trunk/src/gocept/collmex/collmex.py	Fri Dec 19 09:17:28 2008
(at)(at) -96,12 +96,6 (at)(at)
 
     system_identifier = 'gocept.collmex'
 
-    model_factory = {
-        'CMXINV': gocept.collmex.model.InvoiceItem,
-        'CMXKND': gocept.collmex.model.Customer,
-        'CMXPRD': gocept.collmex.model.Product,
-    }
-
     def __init__(self, customer_id, company_id, username, password):
         self.customer_id = customer_id
         self.company_id = company_id
(at)(at) -198,7 +192,7 (at)(at)
         result = []
         for line in lines:
             record_type = line[0]
-            factory = self.model_factory.get(record_type)
+            factory = gocept.collmex.model.factory(record_type)
             if factory is None:
                 continue
             result.append(factory(line))

Modified: gocept.collmex/trunk/src/gocept/collmex/model.py
==============================================================================
--- gocept.collmex/trunk/src/gocept/collmex/model.py	(original)
+++ gocept.collmex/trunk/src/gocept/collmex/model.py	Fri Dec 19 09:17:28 2008
(at)(at) -34,6 +34,16 (at)(at)
                 yield gocept.collmex.interfaces.NULL
 
 
+def factory(record_type):
+    """looks up Model class representing the given `record_type`."""
+
+    for class_ in globals().values():
+        if isinstance(class_, type) and issubclass(class_, Model):
+            if class_.satzart == record_type:
+                return class_
+    return None
+
+
 class InvoiceItem(Model):
 
     zope.interface.implements(gocept.collmex.interfaces.IInvoiceItem)

SVN: r7339 - gocept.collmex/trunk/src/gocept/collmex
Wolfgang Schnerring <ws(at)gocept.com>
2008-12-19 09:47:28 [ FULL ]
Author: wosc
Date: Fri Dec 19 09:47:27 2008
New Revision: 7339

Log:
setuptools can't deal with non-ascii descriptions


Modified:
   gocept.collmex/trunk/src/gocept/collmex/README.txt

Modified: gocept.collmex/trunk/src/gocept/collmex/README.txt
==============================================================================
--- gocept.collmex/trunk/src/gocept/collmex/README.txt	(original)
+++ gocept.collmex/trunk/src/gocept/collmex/README.txt	Fri Dec 19 09:47:27 2008
(at)(at) -1,5 +1,3 (at)(at)
-coding: utf-8
-
 Collmex API
 ===========
 
(at)(at) -99,7 +97,7 (at)(at)
 >>> item['Rechnungsnummer'] = 100000
 >>> item['Menge'] = 3
 >>> item['Produktnummer'] = 'TEST'
->>> item['Rechnungstext'] = u'item text – with non-ascii
characters'
+>>> item['Rechnungstext'] = u'item text \u2013 with non-ascii
characters'
 >>> item['Positionstyp'] = 0
 >>> collmex.create_invoice([item])

SVN: r7345 - in gocept.collmex/tags/0.5: . src/gocept/collmex
Wolfgang Schnerring <ws(at)gocept.com>
2008-12-19 12:12:09 [ FULL ]
Author: wosc
Date: Fri Dec 19 12:12:08 2008
New Revision: 7345

Log:
released 0.5


Modified:
   gocept.collmex/tags/0.5/CHANGES.txt
   gocept.collmex/tags/0.5/setup.py
   gocept.collmex/tags/0.5/src/gocept/collmex/README.txt

Modified: gocept.collmex/tags/0.5/CHANGES.txt
==============================================================================
--- gocept.collmex/tags/0.5/CHANGES.txt	(original)
+++ gocept.collmex/tags/0.5/CHANGES.txt	Fri Dec 19 12:12:08 2008
(at)(at) -1,7 +1,7 (at)(at)
 Changes
 =======
 
-0.5 (unreleased)
+0.5 (2008-12-19)
 ----------------
 
 - Values returned from Collmex are converted to unicode.

Modified: gocept.collmex/tags/0.5/setup.py
==============================================================================
--- gocept.collmex/tags/0.5/setup.py	(original)
+++ gocept.collmex/tags/0.5/setup.py	Fri Dec 19 12:12:08 2008
(at)(at) -7,7 +7,7 (at)(at)
 
 setup(
     name='gocept.collmex',
-    version='0.5dev',
+    version='0.5',
     author='gocept',
     author_email='mail(at)gocept.com',
     description='Python-bindings for the Collmex import/export API',

Modified: gocept.collmex/tags/0.5/src/gocept/collmex/README.txt
==============================================================================
--- gocept.collmex/tags/0.5/src/gocept/collmex/README.txt	(original)
+++ gocept.collmex/tags/0.5/src/gocept/collmex/README.txt	Fri Dec 19 12:12:08
2008
(at)(at) -1,5 +1,3 (at)(at)
-coding: utf-8
-
 Collmex API
 ===========
 
(at)(at) -99,7 +97,7 (at)(at)
 >>> item['Rechnungsnummer'] = 100000
 >>> item['Menge'] = 3
 >>> item['Produktnummer'] = 'TEST'
->>> item['Rechnungstext'] = u'item text – with non-ascii
characters'
+>>> item['Rechnungstext'] = u'item text \u2013 with non-ascii
characters'
 >>> item['Positionstyp'] = 0
 >>> collmex.create_invoice([item])

SVN: r7361 - gocept.webmail/trunk/gocept/webmail
Christian Theune <ct(at)gocept.com>
2008-12-22 14:30:18 [ FULL ]
Author: ctheune
Date: Mon Dec 22 14:30:17 2008
New Revision: 7361

Log:
Remove experimental XML comparison code. See LXML for better solutions.



Removed:
   gocept.webmail/trunk/gocept/webmail/xmlcompare.py
   gocept.webmail/trunk/gocept/webmail/xmlcompare.txt
Modified:
   gocept.webmail/trunk/gocept/webmail/tests.py

Modified: gocept.webmail/trunk/gocept/webmail/tests.py
==============================================================================
--- gocept.webmail/trunk/gocept/webmail/tests.py	(original)
+++ gocept.webmail/trunk/gocept/webmail/tests.py	Mon Dec 22 14:30:17 2008
(at)(at) -56,9 +56,6 (at)(at)
 def test_suite():
     suite = unittest.TestSuite()
     return suite # XXX No tests. :(
-    suite.addTest(doctest.DocFileSuite(
-        'xmlcompare.txt',
-        optionflags=flags))
     suite.addTest(gocept.restmail.tests.FunctionalDocFileSuite(
         'README.txt',
         globs=dict(Browser=zc.testbrowser.real.Browser),

MailBoxer