Commit 1664f3a6 authored by Sylvain Thénault's avatar Sylvain Thénault
Browse files

default is stable

--HG--
branch : stable
......@@ -14,3 +14,7 @@ bb7aa8c654a45f401d7755b003ac69a5603d8f76 oldstable
688fc001f03e6a52674fee6d70abacf17575f5c0 cubicweb-email-debian-version-1.7.0-1
55d7bb281356fa4586068d9e7e04ede431e39b8c cubicweb-email-version-1.7.1
b3e659b9ed1448d584d04a0ec20d25c2647d1e46 cubicweb-email-debian-version-1.7.1-1
c5ef2aca958c4ea042f28cc279f41a2a0fbcc1e3 cubicweb-email-version-1.8.0
41366e43985e9021b2414167bb413b50dd95c9da cubicweb-email-debian-version-1.8.0-1
ab3a1524c0230c80ca5e74e7ba0120978be8004c cubicweb-email-version-1.8.1
ed2720db906a426992fcd8435c0c161abff10f00 cubicweb-email-debian-version-1.8.1-1
This cube models multipart email messages (`Emails` and `EmailPart`) and
provides tools to import your mail box into a cubicweb instance.
Email are automatically stored into`EmailThreads`.
......@@ -4,32 +4,14 @@
modname = 'email'
distname = "cubicweb-%s" % modname
numversion = (1, 7, 1)
numversion = (1, 8, 1)
version = '.'.join(str(num) for num in numversion)
license = 'LGPL'
author = "Logilab"
author_email = "contact@logilab.fr"
web = 'http://www.cubicweb.org/project/%s' % distname
short_desc = "email component for the CubicWeb framework"
long_desc = """\
This cube models multipart email messages (`Emails` and `EmailPart`) and
provides tools to import your mail box into a cubicweb instance.
Email are automatically stored into`EmailThreads`.
"""
# used packages
__depends__ = {'cubicweb': '>= 3.6.0',
'cubicweb-file': '>= 1.6.0'}
__recommends__ = {'cubicweb-comment': None}
# XXX cw < 3.8 bw compat
__use__ = ('file',)
__recommend__ = ('comment',)
description = "email component for the CubicWeb framework"
classifiers = [
'Environment :: Web Environment',
'Framework :: CubicWeb',
......@@ -37,31 +19,33 @@ classifiers = [
'Programming Language :: JavaScript',
]
# used packages
__depends__ = {'cubicweb': '>= 3.9.5',
'cubicweb-file': '>= 1.9.0'}
__recommends__ = {'cubicweb-comment': None}
# packaging ###
from os import listdir as _listdir
from os.path import join, isdir
from glob import glob
THIS_CUBE_DIR = join('share', 'cubicweb', 'cubes', modname)
def listdir(dirpath):
return [join(dirpath, fname) for fname in _listdir(dirpath)
if fname[0] != '.' and not fname.endswith('.pyc')
and not fname.endswith('~')]
CUBES_DIR = join('share', 'cubicweb', 'cubes')
THIS_CUBE_DIR = join(CUBES_DIR, modname)
try:
data_files = [
# common files
[THIS_CUBE_DIR, [fname for fname in glob('*.py') if fname != 'setup.py']],
]
# check for possible extended cube layout
for dirname in ('entities', 'views', 'sobjects', 'hooks', 'schema', 'data', 'i18n', 'migration'):
if isdir(dirname):
data_files.append([join(THIS_CUBE_DIR, dirname), listdir(dirname)])
# Note: here, you'll need to add subdirectories if you want
# them to be included in the debian package
except OSError:
# we are in an installed directory
pass
and not fname.endswith('~')
and not isdir(join(dirpath, fname))]
data_files = [
# common files
[THIS_CUBE_DIR, [fname for fname in glob('*.py') if fname != 'setup.py']],
]
# check for possible extended cube layout
for dirname in ('entities', 'views', 'sobjects', 'hooks', 'schema', 'data', 'i18n', 'migration', 'wdoc'):
if isdir(dirname):
data_files.append([join(THIS_CUBE_DIR, dirname), listdir(dirname)])
# Note: here, you'll need to add subdirectories if you want
# them to be included in the debian package
......@@ -9,9 +9,8 @@ __docformat__ = "restructuredtext en"
import sys
from cStringIO import StringIO
from logilab.common.clcommands import register_commands, pop_arg
from cubicweb.toolsutils import CONNECT_OPTIONS, Command, config_connect
from cubicweb.cwctl import CWCTL
from cubes.email.mboximport import MBOXImporter
......@@ -27,7 +26,8 @@ class MBOXImportCommand(Command):
path to a file using the Unix MBOX format. If "-" is given, stdin is read.
"""
name = 'mboximport'
arguments = '<pyro id> <mbox file>'
arguments = '<pyro id> <mbox file>...'
min_args = 2
options = CONNECT_OPTIONS + (
("interactive",
{'short': 'i', 'action' : 'store_true',
......@@ -38,7 +38,7 @@ class MBOXImportCommand(Command):
def run(self, args):
"""run the command with its specific arguments"""
appid = pop_arg(args, expected_size_after=None)
appid = args.pop()
cnx = config_connect(appid, self.config)
cnx.load_appobjects(cubes=None, subpath=('entities',))
importer = MBOXImporter(cnx, verbose=True,
......@@ -64,4 +64,4 @@ class MBOXImportCommand(Command):
raise
cnx.close()
register_commands((MBOXImportCommand,))
CWCTL.register(MBOXImportCommand)
cubicweb-email (1.8.1-1) unstable; urgency=low
* new upstream release
-- Sylvain Thénault <sylvain.thenault@logilab.fr> Thu, 09 Sep 2010 18:08:24 +0200
cubicweb-email (1.8.0-1) unstable; urgency=low
* new upstream release
-- Sylvain Thénault <sylvain.thenault@logilab.fr> Tue, 27 Jul 2010 16:16:03 +0200
cubicweb-email (1.7.1-1) unstable; urgency=low
* new upstream release
......
......@@ -9,7 +9,7 @@ Homepage: http://www.cubicweb.org/project/cubicweb-email
Package: cubicweb-email
Architecture: all
Depends: cubicweb-common (>= 3.6.0), cubicweb-file (>= 1.6.0)
Depends: cubicweb-common (>= 3.9.5), cubicweb-file (>= 1.9.0)
Description: email component for the CubicWeb framework
This CubicWeb component models email messages.
.
......
......@@ -7,7 +7,7 @@
build: build-stamp
build-stamp:
dh_testdir
python setup.py -q build
NO_SETUPTOOLS=1 python setup.py -q build
touch build-stamp
clean:
......@@ -24,7 +24,7 @@ install: build
dh_testroot
dh_clean -k
dh_installdirs -i
python setup.py -q install --no-compile --prefix=debian/cubicweb-email/usr/
NO_SETUPTOOLS=1 python setup.py -q install --no-compile --prefix=debian/cubicweb-email/usr/
cp doc/*.html doc/*.txt debian/cubicweb-email/usr/share/doc/cubicweb-email/
rm -rf debian/cubicweb-email/usr/lib/python*
......
"""entity classes for entity types provided by the cubicweb email package
:organization: Logilab
:copyright: 2003-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:copyright: 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
......@@ -10,24 +10,15 @@ import re
from logilab.common import umessage
from cubicweb.interfaces import ITree
from cubicweb.mixins import TreeMixIn
from cubicweb.entities import AnyEntity, fetch_config
from cubicweb.entities import AnyEntity, fetch_config, adapters
from cubicweb.selectors import is_instance
from cubes.email.emailcites import parse_body
class Email(TreeMixIn, AnyEntity):
class Email(AnyEntity):
"""customized class for Email entities"""
__regid__ = 'Email'
fetch_attrs, fetch_order = fetch_config(['subject'])
__implements__ = AnyEntity.__implements__ + (ITree,)
tree_attribute = 'reply_to'
def parent(self):
"""for breadcrumbs"""
return self.thread
def dc_title(self):
return self.subject
......@@ -98,6 +89,7 @@ class Email(TreeMixIn, AnyEntity):
return umessage.message_from_string(headers + '\n\n')
class EmailPart(AnyEntity):
"""customized class for EmailPart entities"""
__regid__ = 'EmailPart'
......@@ -131,3 +123,21 @@ class EmailThread(AnyEntity):
def dc_title(self):
return self.title
class EmailITreeAdapter(adapters.ITreeAdapter):
__select__ = is_instance('Email')
tree_relation = 'reply_to'
class EmailPartIFTIAdapter(adapters.IFTIndexableAdapter):
"""customize EmailPart IFTI adapter so we don't index pgp signature"""
__select__ = adapters.IFTIndexableAdapter.__select__ & is_instance('EmailPart')
def get_words(self):
try:
if self.entity.contenttype == 'application/pgp-signature':
return []
except AttributeError:
return super(EmailPartIFTIAdapter, self).get_words()
......@@ -5,7 +5,7 @@
linking information are found
:organization: Logilab
:copyright: 2003-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:copyright: 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
......@@ -14,19 +14,18 @@ from logilab.mtconverter import TransformError
from cubicweb import UnknownEid, typed_eid
from cubicweb.mail import parse_message_id
from cubicweb.selectors import implements
from cubicweb.selectors import is_instance
from cubicweb.server import hook
def fix_ownership(session, eid, email):
sender = email.senderaddr.email_of
if (sender and sender.e_schema == 'CWUser' and
sender.eid != session.actual_session().user.eid):
if sender and sender.e_schema == 'CWUser' and sender.eid != session.user.eid:
# match a user which is not the session's user, set owned_by / created_by
session.unsafe_execute('SET X owned_by U WHERE X eid %(x)s, U eid %(u)s',
{'x': eid, 'u': sender.eid}, 'x')
session.unsafe_execute('SET X created_by U WHERE X eid %(x)s, U eid %(u)s',
{'x': eid, 'u': sender.eid}, 'x')
session.execute('SET X owned_by U WHERE X eid %(x)s, U eid %(u)s',
{'x': eid, 'u': sender.eid})
session.execute('SET X created_by U WHERE X eid %(x)s, U eid %(u)s',
{'x': eid, 'u': sender.eid})
class ExtractEmailInformation(hook.Operation):
......@@ -42,7 +41,7 @@ class ExtractEmailInformation(hook.Operation):
except UnknownEid:
self.error('email %s is referencing an unknown eid %s',
email.messageid, origeid)
return
return
if origetype in self.session.vreg.schema['comments'].objects('Comment'):
try:
part = email.parts_in_order(prefered_mime_type='text/plain')[0]
......@@ -56,12 +55,12 @@ class ExtractEmailInformation(hook.Operation):
origeid, email)
def insert_comment(self, eid, emailpart):
com = self.session.unsafe_execute(
com = self.session.execute(
'INSERT Comment C: C content %(content)s, '
'C content_format %(format)s, C comments X, C generated_by E '
'WHERE X eid %(x)s, E eid %(e)s',
{'x': eid, 'e': self.email.eid, 'format': u'text/plain',
'content': emailpart.actual_content()}, 'x')
'content': emailpart.actual_content()})
fix_ownership(self.session, com[0][0], self.email)
......@@ -89,7 +88,7 @@ class AddEmailHook(hook.Hook):
"""an email has been added, check if associated content should be created
"""
__regid__ = 'extractmailcontent'
__select__ = hook.Hook.__select__ & implements('Email')
__select__ = hook.Hook.__select__ & is_instance('Email')
events = ('after_add_entity',)
def __call__(self):
......
......@@ -317,9 +317,6 @@ msgctxt "Email"
msgid "subject"
msgstr ""
msgid "thread view"
msgstr ""
msgctxt "EmailThread"
msgid "title"
msgstr ""
......
......@@ -317,9 +317,6 @@ msgctxt "Email"
msgid "subject"
msgstr "sujet"
msgid "thread view"
msgstr "vue en enfilade"
msgctxt "EmailThread"
msgid "title"
msgstr "titre"
......
......@@ -22,6 +22,10 @@ def combinaison(list):
[(1, 2), (2, 3), (1, 3)]
>>> list(combinaison([1,2,3,4]))
[(1, 2), (2, 3), (3, 4), (1,3), (1,4), (2,4)]
note: in python 2.6, this can be written:
>>> import itertools
>>> itertools.combinations([1,2,3], 2)
"""
list = list[:]
while len(list) > 1:
......@@ -161,10 +165,17 @@ class MBOXImporter(object):
data = part.get_payload(decode=True)
if main == 'text':
encoding = u'UTF-8'
elif contenttype == 'application/pgp-signature':
encoding = u'ascii'
if isinstance(data, str):
data = unicode(data, encoding)
self.req.set_shared_data('raw_content_%s_%s' %
(emaileid, self._part_index + 1),
str(part.message))
else:
encoding = None
name = part.get_filename()
if name or main != 'text':
if name or main != 'text' and contenttype != 'application/pgp-signature':
# suppose if we have a name, this is an attachement else this is a
# part/alternative
if not name and main != 'text':
......
"""entity/relation schemas to store email in an cubicweb instance
:organization: Logilab
:copyright: 2006-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:copyright: 2006-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
......@@ -10,11 +10,8 @@ _ = unicode
# pylint: disable-msg=E0611,F0401
from yams.buildobjs import (SubjectRelation, RelationType, EntityType,
String, Datetime, Int, RelationDefinition)
try:
from yams.reader import context
defined_types = context.defined
except ImportError:
pass
from yams.reader import context
from cubicweb.schema import ERQLExpression
......@@ -31,7 +28,7 @@ class Email(EntityType):
cc = SubjectRelation('EmailAddress')
parts = SubjectRelation('EmailPart', cardinality='*1', composite='subject')
attachment = SubjectRelation('File') # XXX Image should be there until it inherits from File
attachment = SubjectRelation('File')
reply_to = SubjectRelation('Email', cardinality='?*')
cites = SubjectRelation('Email')
......@@ -82,7 +79,7 @@ class generated_by(RelationType):
object = 'Email'
# if comment is installed
if 'Comment' in defined_types:
if 'Comment' in context.defined:
class comment_generated_by(RelationDefinition):
subject = 'Comment'
name = 'generated_by'
......
#!/usr/bin/env python
# pylint: disable-msg=W0404,W0622,W0704,W0613,W0152
# Copyright (c) 2003-2004 LOGILAB S.A. (Paris, FRANCE).
# http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
""" Generic Setup script, takes package info from __pkginfo__.py file """
from distutils.core import setup
# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
__docformat__ = "restructuredtext en"
import os
import sys
import shutil
from os.path import isdir, exists, join, walk
try:
if os.environ.get('NO_SETUPTOOLS'):
raise ImportError()
from setuptools import setup
from setuptools.command import install_lib
USE_SETUPTOOLS = 1
except ImportError:
from distutils.core import setup
from distutils.command import install_lib
USE_SETUPTOOLS = 0
sys.modules.pop('__pkginfo__', None)
# import required features
from __pkginfo__ import distname, version, license, short_desc, long_desc, \
from __pkginfo__ import modname, version, license, description, \
web, author, author_email
# import optional features
try:
from __pkginfo__ import data_files
except ImportError:
data_files = None
try:
from __pkginfo__ import include_dirs
except ImportError:
include_dirs = []
import __pkginfo__
distname = getattr(__pkginfo__, 'distname', modname)
scripts = getattr(__pkginfo__, 'scripts', [])
data_files = getattr(__pkginfo__, 'data_files', None)
include_dirs = getattr(__pkginfo__, 'include_dirs', [])
ext_modules = getattr(__pkginfo__, 'ext_modules', None)
dependency_links = getattr(__pkginfo__, 'dependency_links', [])
STD_BLACKLIST = ('CVS', '.svn', '.hg', 'debian', 'dist', 'build')
IGNORED_EXTENSIONS = ('.pyc', '.pyo', '.elc', '~')
if exists('README'):
long_description = file('README').read()
else:
long_description = ''
if USE_SETUPTOOLS:
requires = {}
for entry in ("__depends__", "__recommends__"):
requires.update(getattr(__pkginfo__, entry, {}))
install_requires = [("%s %s" % (d, v and v or "")).strip()
for d, v in requires.iteritems()]
else:
install_requires = []
def ensure_scripts(linux_scripts):
"""Creates the proper script names required for each platform
(taken from 4Suite)
"""
from distutils import util
if util.get_platform()[:3] == 'win':
scripts_ = [script + '.bat' for script in linux_scripts]
else:
scripts_ = linux_scripts
return scripts_
def get_packages(directory, prefix):
"""return a list of subpackages for the given directory"""
result = []
for package in os.listdir(directory):
absfile = join(directory, package)
if isdir(absfile):
if exists(join(absfile, '__init__.py')) or \
package in ('test', 'tests'):
if prefix:
result.append('%s.%s' % (prefix, package))
else:
result.append(package)
result += get_packages(absfile, result[-1])
return result
def export(from_dir, to_dir,
blacklist=STD_BLACKLIST,
ignore_ext=IGNORED_EXTENSIONS,
verbose=True):
"""make a mirror of from_dir in to_dir, omitting directories and files
listed in the black list
"""
def make_mirror(arg, directory, fnames):
"""walk handler"""
for norecurs in blacklist:
try:
fnames.remove(norecurs)
except ValueError:
pass
for filename in fnames:
# don't include binary files
if filename[-4:] in ignore_ext:
continue
if filename[-1] == '~':
continue
src = join(directory, filename)
dest = to_dir + src[len(from_dir):]
if verbose:
print >> sys.stderr, src, '->', dest
if os.path.isdir(src):
if not exists(dest):
os.mkdir(dest)
else:
if exists(dest):
os.remove(dest)
shutil.copy2(src, dest)
try:
os.mkdir(to_dir)
except OSError, ex:
# file exists ?
import errno
if ex.errno != errno.EEXIST:
raise
walk(from_dir, make_mirror, None)
class MyInstallLib(install_lib.install_lib):
"""extend install_lib command to handle package __init__.py and
include_dirs variable if necessary
"""
def run(self):
"""overridden from install_lib class"""
install_lib.install_lib.run(self)
# manually install included directories if any
if include_dirs:
base = modname
for directory in include_dirs:
dest = join(self.install_dir, base, directory)
export(directory, dest, verbose=False)
def install(**kwargs):
"""setup entry point"""
#kwargs['distname'] = modname
return setup(name=distname,
version=version,
license=license,
description=short_desc,
long_description=long_desc,
author=author,
author_email=author_email,
url=web,
data_files=data_files,
**kwargs)
if USE_SETUPTOOLS:
if '--force-manifest' in sys.argv:
sys.argv.remove('--force-manifest')
# install-layout option was introduced in 2.5.3-1~exp1
elif sys.version_info < (2, 5, 4) and '--install-layout=deb' in sys.argv:
sys.argv.remove('--install-layout=deb')
if USE_SETUPTOOLS and install_requires:
kwargs['install_requires'] = install_requires
kwargs['dependency_links'] = dependency_links
return setup(name = distname,
version = version,
license = license,
description = description,
long_description = long_description,
author = author,
author_email = author_email,
url = web,
scripts = ensure_scripts(scripts),
data_files = data_files,
ext_modules = ext_modules,
cmdclass = {'install_lib': MyInstallLib},
**kwargs
)
if __name__ == '__main__' :
install()
# -*- coding: utf-8 -*-
from logilab.common.testlib import TestCase, unittest_main
import sys, os.path as osp
sys.path.insert(0, osp.join(osp.dirname(__file__), '..'))
from cubicweb import devtools # make cube importable
from emailcites import parse_body
from cubes.email.emailcites import parse_body
class ParseBodyTC(TestCase):
......
......@@ -59,7 +59,7 @@ class ChangeStateHooksTC(CubicWebTC):
userstate = self.execute('Any SN WHERE X in_state S, S name SN, X eid %(x)s',
{'x': u.eid}, 'x')[0][0]
self.assertEquals(userstate, 'deactivated')
self.assertEquals(u.latest_trinfo().creator.login, 'admin')
self.assertEquals(u.cw_adapt_to('IWorkflowable').latest_trinfo().creator.login, 'admin')