New style: convert cube to new layout
authorNsukami Patrick <ndkpatt at gmail dot com>
Tue, 19 Mar 2019 08:02:48 +0000
changeset 78 1e437181114f
parent 77 6ea8e8c763d8
child 79 a263c1438750
New style: convert cube to new layout - move files to cube folder - add basic README - update __pkginfo__.py to use cubicweb 3.24 - update MANIFEST.in, setup.py - update how cubes are imported, use new style
MANIFEST.in
README
__init__.py
__pkginfo__.py
cubicweb_fresh/__init__.py
cubicweb_fresh/__pkginfo__.py
cubicweb_fresh/data/accounting-entries.xsl
cubicweb_fresh/entities.py
cubicweb_fresh/hooks.py
cubicweb_fresh/i18n/en.po
cubicweb_fresh/i18n/fr.po
cubicweb_fresh/migration/0.5.0_Any.py
cubicweb_fresh/migration/0.5.1_Any.py
cubicweb_fresh/migration/0.7.0_Any.py
cubicweb_fresh/migration/0.7.1_Any.py
cubicweb_fresh/migration/postcreate.py
cubicweb_fresh/schema.py
cubicweb_fresh/sobjects.py
cubicweb_fresh/views/__init__.py
cubicweb_fresh/views/accounting.py
cubicweb_fresh/views/actions.py
data/accounting-entries.xsl
entities.py
hooks.py
i18n/en.po
i18n/fr.po
migration/0.5.0_Any.py
migration/0.5.1_Any.py
migration/0.7.0_Any.py
migration/0.7.1_Any.py
migration/postcreate.py
schema.py
setup.py
sobjects.py
views/__init__.py
views/accounting.py
views/actions.py
--- a/MANIFEST.in	Thu Mar 02 14:21:15 2017 +0100
+++ b/MANIFEST.in	Tue Mar 19 08:02:48 2019 +0000
@@ -1,5 +1,9 @@
+include README
 include *.py
-recursive-include data *.xsl
-recursive-include views *.py
-recursive-include i18n *.pot *.po
-recursive-include migration *.sql *.py depends.map
+include */*.py
+recursive-include cubicweb_fresh *.py
+recursive-include cubicweb_fresh/data *.gif *.png *.ico *.css *.js *.xsl
+recursive-include cubicweb_fresh/i18n *.po
+recursive-include test/data bootstrap_cubes *.py
+include *.ini
+recursive-include debian changelog compat control copyright rules
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/README	Tue Mar 19 08:02:48 2019 +0000
@@ -0,0 +1,1 @@
+Expense tracking application built on the CubicWeb framework.
\ No newline at end of file
--- a/__init__.py	Thu Mar 02 14:21:15 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-"""cubicweb-fresh"""
--- a/__pkginfo__.py	Thu Mar 02 14:21:15 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-# pylint: disable-msg=W0622
-"""cubicweb-fresh application packaging information"""
-
-modname = 'fresh'
-distname = 'cubicweb-fresh'
-
-numversion = (0, 7, 1)
-version = '.'.join(str(num) for num in numversion)
-
-license = 'LGPL'
-author = 'Logilab'
-author_email = 'contact@logilab.fr'
-description = 'expense tracking application built on the CubicWeb framework'
-web = 'http://www.cubicweb.org/project/%s' % distname
-classifiers = [
-    'Environment :: Web Environment',
-    'Framework :: CubicWeb',
-    'Programming Language :: Python',
-    'Programming Language :: JavaScript',
-    ]
-
-__depends__ = {'cubicweb': '>= 3.10.0',
-               'cubicweb-expense': '>= 0.7.0',
-               'cubicweb-workcase': 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('~')
-            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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_fresh/__init__.py	Tue Mar 19 08:02:48 2019 +0000
@@ -0,0 +1,1 @@
+"""cubicweb-fresh"""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_fresh/__pkginfo__.py	Tue Mar 19 08:02:48 2019 +0000
@@ -0,0 +1,26 @@
+# pylint: disable-msg=W0622
+"""cubicweb-fresh application packaging information"""
+
+modname = 'fresh'
+distname = 'cubicweb-fresh'
+
+numversion = (0, 7, 1)
+version = '.'.join(str(num) for num in numversion)
+
+license = 'LGPL'
+author = 'Logilab'
+author_email = 'contact@logilab.fr'
+description = 'expense tracking application built on the CubicWeb framework'
+web = 'http://www.cubicweb.org/project/%s' % distname
+classifiers = [
+    'Environment :: Web Environment',
+    'Framework :: CubicWeb',
+    'Programming Language :: Python',
+    'Programming Language :: JavaScript',
+    ]
+
+__depends__ = {'cubicweb': '>= 3.24.0',
+               'cubicweb-expense': '>= 0.7.0',
+               'cubicweb-workcase': None,
+               }
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_fresh/data/accounting-entries.xsl	Tue Mar 19 08:02:48 2019 +0000
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+                version="1.0">
+  <xsl:output method="text" encoding="UTF-8"/>
+
+  <xsl:template match="*">
+    <xsl:param name="indent" select="''"/>
+
+    <xsl:call-template name="write-element">
+      <xsl:with-param name="indent" select="$indent"/>
+    </xsl:call-template>
+  </xsl:template>
+
+  <xsl:template match="ecriture">
+    <xsl:param name="indent" select="''"/>
+
+    <xsl:call-template name="write-element">
+      <xsl:with-param name="indent" select="$indent"/>
+    </xsl:call-template>
+    <xsl:if test="following-sibling::ecriture">
+      <xsl:text>
+</xsl:text>
+    </xsl:if>
+  </xsl:template>
+
+  <xsl:template match="@*">
+    <xsl:text> </xsl:text>
+    <xsl:value-of select="name()"/>
+    <xsl:text>="</xsl:text>
+    <xsl:value-of select="."/>
+    <xsl:text>"</xsl:text>
+  </xsl:template>
+
+  <xsl:template match="text()">
+    <xsl:if test="normalize-space(.)">
+      <xsl:value-of select="."/>
+    </xsl:if>
+  </xsl:template>
+
+  <xsl:template name="write-element">
+    <xsl:param name="indent" select="''"/>
+
+    <xsl:text>
+</xsl:text>
+    <xsl:value-of select="$indent"/>
+    <xsl:text>&lt;</xsl:text>
+    <xsl:value-of select="name()"/>
+    <xsl:apply-templates select="@*">
+      <xsl:sort select="name()"/>
+    </xsl:apply-templates>
+    <xsl:if test="not(*|text())">
+      <xsl:text>/</xsl:text>
+    </xsl:if>
+    <xsl:text>></xsl:text>
+
+    <xsl:if test="*|text()">
+      <xsl:apply-templates select="*|text()">
+	<xsl:with-param name="indent" select="concat($indent,'  ')"/>
+      </xsl:apply-templates>
+
+      <xsl:if test="*">
+	<xsl:text>
+</xsl:text>
+        <xsl:value-of select="$indent"/>
+      </xsl:if>
+      <xsl:text>&lt;/</xsl:text>
+      <xsl:value-of select="name()"/>
+      <xsl:text>></xsl:text>
+    </xsl:if>
+  </xsl:template>
+
+</xsl:stylesheet>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_fresh/entities.py	Tue Mar 19 08:02:48 2019 +0000
@@ -0,0 +1,13 @@
+"""this contains the template-specific entities' classes"""
+
+from cubicweb_expense.entities import ExpenseLine as BaseExpenseLine
+
+class ExpenseLine(BaseExpenseLine):
+
+    @property
+    def workcase(self):
+        rql = 'Any R WHERE E has_lines EL, EL eid %(el)s, E spent_for W, W ref R'
+        rset = self._cw.execute(rql, {'el': self.eid})
+        if rset:
+            return rset[0][0]
+        return None
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_fresh/hooks.py	Tue Mar 19 08:02:48 2019 +0000
@@ -0,0 +1,17 @@
+from os import makedirs
+from os.path import join, exists
+
+from cubicweb.server import hook
+from cubicweb.server.sources import storages
+
+class ServerStartupHook(hook.Hook):
+    __regid__ = 'drh.serverstartup'
+    events = ('server_startup', 'server_maintenance')
+
+    def __call__(self):
+        bfssdir = join(self.repo.config.appdatahome, 'bfss')
+        if not exists(bfssdir):
+            makedirs(bfssdir)
+            print 'created', bfssdir
+        storage = storages.BytesFileSystemStorage(bfssdir)
+        storages.set_attribute_storage(self.repo, 'File', 'data', storage)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_fresh/i18n/en.po	Tue Mar 19 08:02:48 2019 +0000
@@ -0,0 +1,38 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: 2.0\n"
+"POT-Creation-Date: 2006-01-12 17:35+CET\n"
+"PO-Revision-Date: 2008-06-09 17:56+0200\n"
+"Last-Translator: Logilab\n"
+"Language-Team: English <devel@logilab.fr.org>\n"
+"Language: en\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: pygettext.py 1.5\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+msgid "accounting entry view"
+msgstr "accounting entry view"
+
+msgid "generate accounting entries"
+msgstr "generate accounting entries"
+
+# schema pot file, generated on 2008-04-14 11:36:25
+#
+# singular and plural forms for each entity type
+# subject and object forms for each relation type
+# (no object form for final relation types)
+msgid "spent_for"
+msgstr "spent for"
+
+msgctxt "Expense"
+msgid "spent_for"
+msgstr "spent for"
+
+msgid "spent_for_object"
+msgstr "was paid"
+
+msgctxt "Workcase"
+msgid "spent_for_object"
+msgstr "was paid"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_fresh/i18n/fr.po	Tue Mar 19 08:02:48 2019 +0000
@@ -0,0 +1,38 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: 2.0\n"
+"POT-Creation-Date: 2006-01-12 17:35+CET\n"
+"PO-Revision-Date: 2008-06-09 17:56+0200\n"
+"Last-Translator: Logilab\n"
+"Language-Team: French <devel@logilab.fr.org>\n"
+"Language: fr\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: pygettext.py 1.5\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+msgid "accounting entry view"
+msgstr "écriture comptabilité"
+
+msgid "generate accounting entries"
+msgstr "générer les écritures comptables"
+
+# schema pot file, generated on 2008-04-14 11:36:25
+#
+# singular and plural forms for each entity type
+# subject and object forms for each relation type
+# (no object form for final relation types)
+msgid "spent_for"
+msgstr "dépensé pour"
+
+msgctxt "Expense"
+msgid "spent_for"
+msgstr "dépensé pour"
+
+msgid "spent_for_object"
+msgstr "s'est fait rembourser"
+
+msgctxt "Workcase"
+msgid "spent_for_object"
+msgstr "s'est fait rembourser"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_fresh/migration/0.5.0_Any.py	Tue Mar 19 08:02:48 2019 +0000
@@ -0,0 +1,1 @@
+sync_schema_props_perms('spent_for')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_fresh/migration/0.5.1_Any.py	Tue Mar 19 08:02:48 2019 +0000
@@ -0,0 +1,1 @@
+sync_schema_props_perms('spent_for', syncprops=False)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_fresh/migration/0.7.0_Any.py	Tue Mar 19 08:02:48 2019 +0000
@@ -0,0 +1,1 @@
+add_entity_type('Training')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_fresh/migration/0.7.1_Any.py	Tue Mar 19 08:02:48 2019 +0000
@@ -0,0 +1,7 @@
+if 'Training' not in schema:
+    add_entity_type('Training')
+
+# workflows doen't understand yams inheritance
+rql('SET WF workflow_of ET, ET default_workflow WF '
+    'WHERE WF workflow_of WC, WC name "Workcase", ET name "Training"')
+commit()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_fresh/migration/postcreate.py	Tue Mar 19 08:02:48 2019 +0000
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+# postcreate script. You could setup a workflow here for example
+
+for login in (u'alf', u'syt', u'nico', u'jphc', u'ocy', u'auc', u'katia',
+              u'graz', u'dede', u'juj', u'ludal', u'steph', u'arthur',
+              u'david', u'joel', u'gaston', u'adim'):
+    rql('INSERT CWUser E: E login %(login)s, E upassword %(login)s, E in_group G '
+        'WHERE G name "users"', {'login' : login})
+    rql('INSERT PaidByAccount P: P label %(label)s, P associated_to U WHERE U login %(login)s',
+        {'label' : u"refund account - %s" % login, 'login': login})
+    rql('INSERT PaidForAccount P: P label %(label)s', {'label' : u"charge account - %s" % login})
+
+
+for label in (u'Logilab - CB Nicolas', u'Logilab - CB Alexandre', u'Logilab - CB Olivier',
+              u'Logilab - Espèces'):
+    rql('INSERT PaidByAccount P: P label %(label)s', {'label' : label})
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_fresh/schema.py	Tue Mar 19 08:02:48 2019 +0000
@@ -0,0 +1,17 @@
+# template's specific schema
+from yams.buildobjs import RelationDefinition
+from cubicweb.schema import RRQLExpression
+from cubicweb_workcase.schema import Workcase
+
+class Training(Workcase):
+    __specializes_schema__ = True
+
+class spent_for(RelationDefinition):
+    subject = 'Expense'
+    object = 'Workcase'
+    cardinality = '?*'
+    __permissions__ = {
+        'read' : ('managers', 'users'),
+        'add': ('managers', RRQLExpression('NOT (S in_state ST, ST name "accepted")')),
+        'delete': ('managers', RRQLExpression('NOT (S in_state ST, ST name "accepted")')),
+        }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_fresh/sobjects.py	Tue Mar 19 08:02:48 2019 +0000
@@ -0,0 +1,115 @@
+import os
+from six import text_type
+import cwclientlib
+from cubicweb.server.sources import datafeed
+from cubicweb.dataimport import stores, importer
+
+
+# XXX class copied in crm/fresh, should be kepts sync
+class CWClientLibDataFeedParser(datafeed.DataFeedParser):
+    """Base class for parsers that search for distant entities using cwclientlib.
+    """
+
+    def process_urls(self, *args, **kwargs):
+        """IDataFeedParser main entry point."""
+        os.environ['CWCLCONF'] = os.path.join(self._cw.vreg.config.apphome, 'cwclientlibrc')
+        self._source_uris = {}
+        error = super(CWClientLibDataFeedParser, self).process_urls(*args, **kwargs)
+        if not error:
+            self.handle_deletion()
+        return error
+
+    def process(self, url, raise_on_error=False):
+        """Called once by process_urls (several URL are not expected with this parser)."""
+        # ensure url ends with a single slash for proper extid generation
+        url = url.rstrip('/') + '/'
+        eeimporter = self.build_importer(raise_on_error)
+        entities = self.extentities_generator(url)
+        set_cwuri = importer.use_extid_as_cwuri(eeimporter.extid2eid)
+        eeimporter.import_entities(set_cwuri(entities))
+        self.stats['created'] = eeimporter.created
+        self.stats['updated'] = eeimporter.updated
+
+    def build_importer(self, raise_on_error):
+        """Instantiate and configure an importer"""
+        etypes, extid2eid = self.init_extid2eid()
+        existing_relations = self.init_existing_relations()
+        store = stores.NoHookRQLObjectStore(self._cw, metagen=stores.MetaGenerator(
+            self._cw, source=self.source))
+        return importer.ExtEntitiesImporter(self._cw.vreg.schema, store,
+                                            extid2eid=extid2eid,
+                                            existing_relations=existing_relations,
+                                            etypes_order_hint=etypes,
+                                            import_log=self.import_log,
+                                            raise_on_error=raise_on_error)
+
+    def handle_deletion(self, *args, **kwargs):
+        for extid, (eid, etype) in self._source_uris.items():
+            self._cw.entity_from_eid(eid, etype).cw_delete()
+
+    def existing_entities(self, etype):
+        rset = self._cw.execute('Any XURI, X WHERE X cwuri XURI, X is {0},'
+                                ' X cw_source S, S eid %(s)s'.format(etype),
+                                {'s': self.source.eid})
+        for extid, eid in rset:
+            extid = extid.encode('ascii')
+            self._source_uris[extid] = (eid, etype)
+            yield extid, eid
+
+    def states_map(self, etype):
+        return dict(self._cw.execute(
+            'Any SN,S WHERE S name SN, S state_of WF, '
+            'ET default_workflow WF, ET name %(etype)s',
+            {'etype': etype}))
+
+    def ext_entity(self, url, etype, eid, values):
+        extid = url + text_type(eid)
+        self._source_uris.pop(extid, None)
+        return importer.ExtEntity(etype, extid, values)
+
+
+class DataFeedFreshActivity(CWClientLibDataFeedParser):
+    """Parser to import workcases from crm."""
+    __regid__ = 'fresh.workcases'
+
+    def init_extid2eid(self):
+        # put state eids in extid2eid as we'll want to link to them
+        extid2eid = dict(self._cw.execute('Any X,X WHERE X is State'))
+        # map existing orders and workorders from our souce
+        etypes = ('Workcase',)
+        for etype in etypes:
+            for extid, eid in self.existing_entities(etype):
+                extid2eid[extid] = eid
+        return etypes, extid2eid
+
+    def init_existing_relations(self):
+        existing_relations = {}
+        rset = self._cw.execute(
+            'Any O,OS WHERE O in_state OS, O is Workcase, O cw_source S, S eid %(s)s',
+            {'s': self.source.eid})
+        existing_relations['in_state'] = set(tuple(x) for x in rset)
+        return existing_relations
+
+    def extentities_generator(self, url):
+        # connect to the instance using cwclientlib
+        proxy = cwclientlib.cwproxy_for(url)
+        # XXX check modification_date > last_update
+        # information necessary to relate order to a state
+        states_map = self.states_map('Workcase')
+
+        for args in proxy.execute(
+                'Any W,SN,WR,WS WHERE W is_instance_of Workcase, '
+                'W ref WR, W subject WS, W in_state S, S name SN'):
+            eid = args.pop(0)
+            state = args.pop(0)
+            values = values_dict(args, ['ref', 'subject'])
+            values['in_state'] = set([states_map[state]])
+            yield self.ext_entity(url, 'Workcase', eid, values)
+
+
+def values_dict(values_list, attributes):
+    values = {}
+    for k, v in zip(attributes, values_list):
+        if v is not None:
+            values[k] = set([v])
+    return values
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_fresh/views/__init__.py	Tue Mar 19 08:02:48 2019 +0000
@@ -0,0 +1,27 @@
+"""template-specific forms/views/actions/components"""
+from logilab.common.decorators import monkeypatch
+
+from cubicweb.web import uicfg, formwidgets as fw
+from cubicweb.web.views import basecontrollers
+
+_afs = uicfg.autoform_section
+_affk = uicfg.autoform_field_kwargs
+_afs.tag_subject_of(('Expense', 'spent_for', '*'), 'main', 'attributes')
+_afs.tag_subject_of(('Expense', 'spent_for', '*'), 'muledit', 'attributes')
+_affk.tag_subject_of(('Expense', 'spent_for', '*'),
+                     {'widget': fw.LazyRestrictedAutoCompletionWidget(
+            autocomplete_initfunc='get_concerned_by',
+            autocomplete_settings={'limit': 100,
+                                   'delay': 300}),
+                      })
+
+
+@monkeypatch(basecontrollers.JSonController)
+@basecontrollers.jsonize
+def js_get_concerned_by(self):
+    term = self._cw.form['q']
+    limit = self._cw.form.get('limit', 50)
+    return [{'value': eid, 'label': ref}
+            for eid, ref in self._cw.execute('DISTINCT Any W,R ORDERBY R LIMIT %s WHERE W ref R,'
+                                             'W ref ILIKE %%(term)s' % limit,
+                                             {'term': u'%%%s%%' % term})]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_fresh/views/accounting.py	Tue Mar 19 08:02:48 2019 +0000
@@ -0,0 +1,89 @@
+"""accounting views for fresh template
+
+:organization: Logilab
+:copyright: 2008-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+
+from logilab.mtconverter import xml_escape
+
+from cubicweb.selectors import is_instance
+from cubicweb.view import EntityView
+
+
+class ExpenseAccountingXmlView(EntityView):
+    __regid__ = 'accexpense'
+    __select__ = is_instance('Expense')
+
+    title = _('accounting entry view')
+    templatable = False
+    content_type = 'text/xml'
+
+    def call(self):
+        """display a list of entities by calling their <item_vid> view
+        """
+        self.w(u'<?xml version="1.0" encoding="%s"?>\n' % self._cw.encoding)
+        self.w(u'<?xml-stylesheet href="%saccounting-entries.xsl" '
+               u'rel="stylesheet" type="text/xsl"?>\n' % self._cw.datadir_url)
+        self.w(u'<ecritures>\n')
+        for i in xrange(self.cw_rset.rowcount):
+            self.cell_call(i, 0)
+        self.w(u'</ecritures>\n')
+
+    def cell_call(self, row, col):
+        entity = self.cw_rset.get_entity(row, col)
+        rset = entity.related('has_lines')
+        for i in xrange(len(rset)):
+            self.wview('accentry', rset, row=i, col=0)
+
+
+class ExpenseLineAccountingEntryXmlView(EntityView):
+    __regid__ = 'accentry'
+    __select__ = is_instance('ExpenseLine',)
+
+    title = _('accounting entry view')
+    templatable = False
+    content_type = 'text/xml'
+
+    def call(self):
+        """display a list of entities by calling their <item_vid> view
+        """
+        self.w(u'<?xml version="1.0" encoding="%s"?>\n' % self._cw.encoding)
+        self.w(u'<?xml-stylesheet href="%saccounting-entries.xsl" '
+               u'rel="stylesheet" type="text/xsl"?>\n' % self._cw.datadir_url)
+        self.w(u'<ecritures>\n')
+        for i in xrange(self.cw_rset.rowcount):
+            self.cell_call(i, 0)
+        self.w(u'</ecritures>\n')
+
+    def cell_call(self, row, col):
+        entity = self.cw_rset.complete_entity(row, col)
+        self.w(u'  <ecriture date="%s">\n' % entity.diem.strftime('%Y-%m-%d'))
+        self.w(u'    <libelle>%s</libelle>\n' % xml_escape(entity.dc_long_title()))
+        amount = round(entity.euro_amount(), 2)
+        taxes = round(entity.taxes, 2)
+        account = entity.paid_by[0].account and xml_escape(entity.paid_by[0].account) or u''
+        self.w(u'    <credit compte="%s" montant="%.2f" />\n'
+               % ( account, amount))
+        if entity.taxes:
+            # XXX hardcoded account for VAT
+            self.w(u'    <debit compte="44566" montant="%.2f" />\n' % entity.taxes)
+        taxfree = int(round((amount - taxes) * 100))
+        accounts = list(entity.paid_for)
+        debit_quotient = taxfree / len(accounts)
+        debit_remainder = taxfree % len(accounts)
+        for account in accounts:
+            if debit_remainder > 0:
+                debit = (debit_quotient + 1) / 100.0
+                debit_remainder -= 1
+            else:
+                debit = debit_quotient / 100.0
+            account = account.account and xml_escape(account.account) or u''
+            self.w(u'    <debit compte="%s" montant="%.2f" />\n'
+                   % (account, debit))
+        if entity.workcase:
+            self.w(u'    <groupe>%s</groupe>\n' % xml_escape(entity.workcase))
+        self.w(u'  </ecriture>\n')
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb_fresh/views/actions.py	Tue Mar 19 08:02:48 2019 +0000
@@ -0,0 +1,20 @@
+"""specific actions for fresh template
+
+:organization: Logilab
+:copyright: 2008-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from cubicweb.selectors import is_instance
+from cubicweb.web.action import Action
+
+class AccountingAction(Action):
+    __regid__ = 'accaction'
+    __select__ = is_instance('Expense')
+    title = _('generate accounting entries')
+
+    def url(self):
+        entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
+        return entity.absolute_url(vid='accexpense')
+
--- a/data/accounting-entries.xsl	Thu Mar 02 14:21:15 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,73 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-
-<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
-                version="1.0">
-  <xsl:output method="text" encoding="UTF-8"/>
-
-  <xsl:template match="*">
-    <xsl:param name="indent" select="''"/>
-
-    <xsl:call-template name="write-element">
-      <xsl:with-param name="indent" select="$indent"/>
-    </xsl:call-template>
-  </xsl:template>
-
-  <xsl:template match="ecriture">
-    <xsl:param name="indent" select="''"/>
-
-    <xsl:call-template name="write-element">
-      <xsl:with-param name="indent" select="$indent"/>
-    </xsl:call-template>
-    <xsl:if test="following-sibling::ecriture">
-      <xsl:text>
-</xsl:text>
-    </xsl:if>
-  </xsl:template>
-
-  <xsl:template match="@*">
-    <xsl:text> </xsl:text>
-    <xsl:value-of select="name()"/>
-    <xsl:text>="</xsl:text>
-    <xsl:value-of select="."/>
-    <xsl:text>"</xsl:text>
-  </xsl:template>
-
-  <xsl:template match="text()">
-    <xsl:if test="normalize-space(.)">
-      <xsl:value-of select="."/>
-    </xsl:if>
-  </xsl:template>
-
-  <xsl:template name="write-element">
-    <xsl:param name="indent" select="''"/>
-
-    <xsl:text>
-</xsl:text>
-    <xsl:value-of select="$indent"/>
-    <xsl:text>&lt;</xsl:text>
-    <xsl:value-of select="name()"/>
-    <xsl:apply-templates select="@*">
-      <xsl:sort select="name()"/>
-    </xsl:apply-templates>
-    <xsl:if test="not(*|text())">
-      <xsl:text>/</xsl:text>
-    </xsl:if>
-    <xsl:text>></xsl:text>
-
-    <xsl:if test="*|text()">
-      <xsl:apply-templates select="*|text()">
-	<xsl:with-param name="indent" select="concat($indent,'  ')"/>
-      </xsl:apply-templates>
-
-      <xsl:if test="*">
-	<xsl:text>
-</xsl:text>
-        <xsl:value-of select="$indent"/>
-      </xsl:if>
-      <xsl:text>&lt;/</xsl:text>
-      <xsl:value-of select="name()"/>
-      <xsl:text>></xsl:text>
-    </xsl:if>
-  </xsl:template>
-
-</xsl:stylesheet>
--- a/entities.py	Thu Mar 02 14:21:15 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-"""this contains the template-specific entities' classes"""
-
-from cubes.expense.entities import ExpenseLine as BaseExpenseLine
-
-class ExpenseLine(BaseExpenseLine):
-
-    @property
-    def workcase(self):
-        rql = 'Any R WHERE E has_lines EL, EL eid %(el)s, E spent_for W, W ref R'
-        rset = self._cw.execute(rql, {'el': self.eid})
-        if rset:
-            return rset[0][0]
-        return None
--- a/hooks.py	Thu Mar 02 14:21:15 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,17 +0,0 @@
-from os import makedirs
-from os.path import join, exists
-
-from cubicweb.server import hook
-from cubicweb.server.sources import storages
-
-class ServerStartupHook(hook.Hook):
-    __regid__ = 'drh.serverstartup'
-    events = ('server_startup', 'server_maintenance')
-
-    def __call__(self):
-        bfssdir = join(self.repo.config.appdatahome, 'bfss')
-        if not exists(bfssdir):
-            makedirs(bfssdir)
-            print 'created', bfssdir
-        storage = storages.BytesFileSystemStorage(bfssdir)
-        storages.set_attribute_storage(self.repo, 'File', 'data', storage)
--- a/i18n/en.po	Thu Mar 02 14:21:15 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,38 +0,0 @@
-msgid ""
-msgstr ""
-"Project-Id-Version: 2.0\n"
-"POT-Creation-Date: 2006-01-12 17:35+CET\n"
-"PO-Revision-Date: 2008-06-09 17:56+0200\n"
-"Last-Translator: Logilab\n"
-"Language-Team: English <devel@logilab.fr.org>\n"
-"Language: en\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: pygettext.py 1.5\n"
-"Plural-Forms: nplurals=2; plural=(n > 1);\n"
-
-msgid "accounting entry view"
-msgstr "accounting entry view"
-
-msgid "generate accounting entries"
-msgstr "generate accounting entries"
-
-# schema pot file, generated on 2008-04-14 11:36:25
-#
-# singular and plural forms for each entity type
-# subject and object forms for each relation type
-# (no object form for final relation types)
-msgid "spent_for"
-msgstr "spent for"
-
-msgctxt "Expense"
-msgid "spent_for"
-msgstr "spent for"
-
-msgid "spent_for_object"
-msgstr "was paid"
-
-msgctxt "Workcase"
-msgid "spent_for_object"
-msgstr "was paid"
--- a/i18n/fr.po	Thu Mar 02 14:21:15 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,38 +0,0 @@
-msgid ""
-msgstr ""
-"Project-Id-Version: 2.0\n"
-"POT-Creation-Date: 2006-01-12 17:35+CET\n"
-"PO-Revision-Date: 2008-06-09 17:56+0200\n"
-"Last-Translator: Logilab\n"
-"Language-Team: French <devel@logilab.fr.org>\n"
-"Language: fr\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: pygettext.py 1.5\n"
-"Plural-Forms: nplurals=2; plural=(n > 1);\n"
-
-msgid "accounting entry view"
-msgstr "écriture comptabilité"
-
-msgid "generate accounting entries"
-msgstr "générer les écritures comptables"
-
-# schema pot file, generated on 2008-04-14 11:36:25
-#
-# singular and plural forms for each entity type
-# subject and object forms for each relation type
-# (no object form for final relation types)
-msgid "spent_for"
-msgstr "dépensé pour"
-
-msgctxt "Expense"
-msgid "spent_for"
-msgstr "dépensé pour"
-
-msgid "spent_for_object"
-msgstr "s'est fait rembourser"
-
-msgctxt "Workcase"
-msgid "spent_for_object"
-msgstr "s'est fait rembourser"
--- a/migration/0.5.0_Any.py	Thu Mar 02 14:21:15 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-sync_schema_props_perms('spent_for')
--- a/migration/0.5.1_Any.py	Thu Mar 02 14:21:15 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-sync_schema_props_perms('spent_for', syncprops=False)
--- a/migration/0.7.0_Any.py	Thu Mar 02 14:21:15 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-add_entity_type('Training')
--- a/migration/0.7.1_Any.py	Thu Mar 02 14:21:15 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-if 'Training' not in schema:
-    add_entity_type('Training')
-
-# workflows doen't understand yams inheritance
-rql('SET WF workflow_of ET, ET default_workflow WF '
-    'WHERE WF workflow_of WC, WC name "Workcase", ET name "Training"')
-commit()
--- a/migration/postcreate.py	Thu Mar 02 14:21:15 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-# postcreate script. You could setup a workflow here for example
-
-for login in (u'alf', u'syt', u'nico', u'jphc', u'ocy', u'auc', u'katia',
-              u'graz', u'dede', u'juj', u'ludal', u'steph', u'arthur',
-              u'david', u'joel', u'gaston', u'adim'):
-    rql('INSERT CWUser E: E login %(login)s, E upassword %(login)s, E in_group G '
-        'WHERE G name "users"', {'login' : login})
-    rql('INSERT PaidByAccount P: P label %(label)s, P associated_to U WHERE U login %(login)s',
-        {'label' : u"refund account - %s" % login, 'login': login})
-    rql('INSERT PaidForAccount P: P label %(label)s', {'label' : u"charge account - %s" % login})
-
-
-for label in (u'Logilab - CB Nicolas', u'Logilab - CB Alexandre', u'Logilab - CB Olivier',
-              u'Logilab - Espèces'):
-    rql('INSERT PaidByAccount P: P label %(label)s', {'label' : label})
--- a/schema.py	Thu Mar 02 14:21:15 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,17 +0,0 @@
-# template's specific schema
-from yams.buildobjs import RelationDefinition
-from cubicweb.schema import RRQLExpression
-from cubes.workcase.schema import Workcase
-
-class Training(Workcase):
-    __specializes_schema__ = True
-
-class spent_for(RelationDefinition):
-    subject = 'Expense'
-    object = 'Workcase'
-    cardinality = '?*'
-    __permissions__ = {
-        'read' : ('managers', 'users'),
-        'add': ('managers', RRQLExpression('NOT (S in_state ST, ST name "accepted")')),
-        'delete': ('managers', RRQLExpression('NOT (S in_state ST, ST name "accepted")')),
-        }
--- a/setup.py	Thu Mar 02 14:21:15 2017 +0100
+++ b/setup.py	Tue Mar 19 08:02:48 2019 +0000
@@ -1,10 +1,10 @@
 #!/usr/bin/env python
 # pylint: disable=W0142,W0403,W0404,W0613,W0622,W0622,W0704,R0904,C0103,E0611
 #
-# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2018 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
-# This file is part of CubicWeb tag cube.
+# This file is part of cubiweb-fresh.
 #
 # CubicWeb is free software: you can redistribute it and/or modify it under the
 # terms of the GNU Lesser General Public License as published by the Free
@@ -14,188 +14,71 @@
 # CubicWeb 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 Lesser General Public License for more
+
 # details.
 #
-# You should have received a copy of the GNU Lesser General Public License along
-# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""Generic Setup script, takes package info from __pkginfo__.py file
+# You should have received a copy of the GNU Lesser General Public License
+# along with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
+"""cubicweb_fresh setup module using data from
+cubicweb_fresh/__pkginfo__.py file
 """
-__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() # do as there is no setuptools
-    from setuptools import setup
-    from setuptools.command import install_lib
-    USE_SETUPTOOLS = True
-except ImportError:
-    from distutils.core import setup
-    from distutils.command import install_lib
-    USE_SETUPTOOLS = False
-from distutils.command import install_data
 
-# import required features
-from __pkginfo__ import modname, version, license, description, web, \
-     author, author_email, classifiers
-
-if exists('README'):
-    long_description = file('README').read()
-else:
-    long_description = ''
+from os.path import join, dirname
 
-# import optional features
-import __pkginfo__
-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 = []
-
-distname = getattr(__pkginfo__, 'distname', modname)
-scripts = getattr(__pkginfo__, 'scripts', ())
-include_dirs = getattr(__pkginfo__, 'include_dirs', ())
-data_files = getattr(__pkginfo__, 'data_files', None)
-ext_modules = getattr(__pkginfo__, 'ext_modules', None)
-dependency_links = getattr(__pkginfo__, 'dependency_links', ())
-
-BASE_BLACKLIST = ('CVS', '.svn', '.hg', 'debian', 'dist', 'build')
-IGNORED_EXTENSIONS = ('.pyc', '.pyo', '.elc', '~')
+from setuptools import find_packages, setup
 
 
-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_
+here = dirname(__file__)
+
+# load metadata from the __pkginfo__.py file so there is no risk of conflict
+# see https://packaging.python.org/en/latest/single_source_version.html
+pkginfo = join(here, 'cubicweb_fresh', '__pkginfo__.py')
+__pkginfo__ = {}
+with open(pkginfo) as f:
+    exec(f.read(), __pkginfo__)
 
-def export(from_dir, to_dir,
-           blacklist=BASE_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)
+# get required metadatas
+distname = __pkginfo__['distname']
+version = __pkginfo__['version']
+license = __pkginfo__['license']
+description = __pkginfo__['description']
+web = __pkginfo__['web']
+author = __pkginfo__['author']
+author_email = __pkginfo__['author_email']
+classifiers = __pkginfo__['classifiers']
+
+with open(join(here, 'README')) as f:
+    long_description = f.read()
+
+# get optional metadatas
+data_files = __pkginfo__.get('data_files', None)
+dependency_links = __pkginfo__.get('dependency_links', ())
+
+requires = {}
+for entry in ("__depends__",):  # "__recommends__"):
+    requires.update(__pkginfo__.get(entry, {}))
+install_requires = ["{0} {1}".format(d, v and v or "").strip()
+
+                    for d, v in requires.items()]
 
 
-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)
-
-# re-enable copying data files in sys.prefix
-old_install_data = install_data.install_data
-if USE_SETUPTOOLS:
-    # overwrite InstallData to use sys.prefix instead of the egg directory
-    class MyInstallData(old_install_data):
-        """A class that manages data files installation"""
-        def run(self):
-            _old_install_dir = self.install_dir
-            if self.install_dir.endswith('egg'):
-                self.install_dir = sys.prefix
-            old_install_data.run(self)
-            self.install_dir = _old_install_dir
-    try:
-        import setuptools.command.easy_install # only if easy_install avaible
-        # monkey patch: Crack SandboxViolation verification
-        from setuptools.sandbox import DirectorySandbox as DS
-        old_ok = DS._ok
-        def _ok(self, path):
-            """Return True if ``path`` can be written during installation."""
-            out = old_ok(self, path) # here for side effect from setuptools
-            realpath = os.path.normcase(os.path.realpath(path))
-            allowed_path = os.path.normcase(sys.prefix)
-            if realpath.startswith(allowed_path):
-                out = True
-            return out
-        DS._ok = _ok
-    except ImportError:
-        pass
-
-def install(**kwargs):
-    """setup entry point"""
-    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')
-    cmdclass = {'install_lib': MyInstallLib}
-    if USE_SETUPTOOLS:
-        kwargs['install_requires'] = install_requires
-        kwargs['dependency_links'] = dependency_links
-        kwargs['zip_safe'] = False
-        cmdclass['install_data'] = MyInstallData
-
-    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 = cmdclass,
-                 classifiers = classifiers,
-                 **kwargs
-                 )
-
-if __name__ == '__main__' :
-    install()
+setup(
+    name=distname,
+    version=version,
+    license=license,
+    description=description,
+    long_description=long_description,
+    author=author,
+    author_email=author_email,
+    url=web,
+    classifiers=classifiers,
+    packages=find_packages(exclude=['test']),
+    install_requires=install_requires,
+    include_package_data=True,
+    entry_points={
+        'cubicweb.cubes': [
+            'fresh=cubicweb_fresh',
+        ],
+    },
+    zip_safe=False,
+)
--- a/sobjects.py	Thu Mar 02 14:21:15 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,115 +0,0 @@
-import os
-from six import text_type
-import cwclientlib
-from cubicweb.server.sources import datafeed
-from cubicweb.dataimport import stores, importer
-
-
-# XXX class copied in crm/fresh, should be kepts sync
-class CWClientLibDataFeedParser(datafeed.DataFeedParser):
-    """Base class for parsers that search for distant entities using cwclientlib.
-    """
-
-    def process_urls(self, *args, **kwargs):
-        """IDataFeedParser main entry point."""
-        os.environ['CWCLCONF'] = os.path.join(self._cw.vreg.config.apphome, 'cwclientlibrc')
-        self._source_uris = {}
-        error = super(CWClientLibDataFeedParser, self).process_urls(*args, **kwargs)
-        if not error:
-            self.handle_deletion()
-        return error
-
-    def process(self, url, raise_on_error=False):
-        """Called once by process_urls (several URL are not expected with this parser)."""
-        # ensure url ends with a single slash for proper extid generation
-        url = url.rstrip('/') + '/'
-        eeimporter = self.build_importer(raise_on_error)
-        entities = self.extentities_generator(url)
-        set_cwuri = importer.use_extid_as_cwuri(eeimporter.extid2eid)
-        eeimporter.import_entities(set_cwuri(entities))
-        self.stats['created'] = eeimporter.created
-        self.stats['updated'] = eeimporter.updated
-
-    def build_importer(self, raise_on_error):
-        """Instantiate and configure an importer"""
-        etypes, extid2eid = self.init_extid2eid()
-        existing_relations = self.init_existing_relations()
-        store = stores.NoHookRQLObjectStore(self._cw, metagen=stores.MetaGenerator(
-            self._cw, source=self.source))
-        return importer.ExtEntitiesImporter(self._cw.vreg.schema, store,
-                                            extid2eid=extid2eid,
-                                            existing_relations=existing_relations,
-                                            etypes_order_hint=etypes,
-                                            import_log=self.import_log,
-                                            raise_on_error=raise_on_error)
-
-    def handle_deletion(self, *args, **kwargs):
-        for extid, (eid, etype) in self._source_uris.items():
-            self._cw.entity_from_eid(eid, etype).cw_delete()
-
-    def existing_entities(self, etype):
-        rset = self._cw.execute('Any XURI, X WHERE X cwuri XURI, X is {0},'
-                                ' X cw_source S, S eid %(s)s'.format(etype),
-                                {'s': self.source.eid})
-        for extid, eid in rset:
-            extid = extid.encode('ascii')
-            self._source_uris[extid] = (eid, etype)
-            yield extid, eid
-
-    def states_map(self, etype):
-        return dict(self._cw.execute(
-            'Any SN,S WHERE S name SN, S state_of WF, '
-            'ET default_workflow WF, ET name %(etype)s',
-            {'etype': etype}))
-
-    def ext_entity(self, url, etype, eid, values):
-        extid = url + text_type(eid)
-        self._source_uris.pop(extid, None)
-        return importer.ExtEntity(etype, extid, values)
-
-
-class DataFeedFreshActivity(CWClientLibDataFeedParser):
-    """Parser to import workcases from crm."""
-    __regid__ = 'fresh.workcases'
-
-    def init_extid2eid(self):
-        # put state eids in extid2eid as we'll want to link to them
-        extid2eid = dict(self._cw.execute('Any X,X WHERE X is State'))
-        # map existing orders and workorders from our souce
-        etypes = ('Workcase',)
-        for etype in etypes:
-            for extid, eid in self.existing_entities(etype):
-                extid2eid[extid] = eid
-        return etypes, extid2eid
-
-    def init_existing_relations(self):
-        existing_relations = {}
-        rset = self._cw.execute(
-            'Any O,OS WHERE O in_state OS, O is Workcase, O cw_source S, S eid %(s)s',
-            {'s': self.source.eid})
-        existing_relations['in_state'] = set(tuple(x) for x in rset)
-        return existing_relations
-
-    def extentities_generator(self, url):
-        # connect to the instance using cwclientlib
-        proxy = cwclientlib.cwproxy_for(url)
-        # XXX check modification_date > last_update
-        # information necessary to relate order to a state
-        states_map = self.states_map('Workcase')
-
-        for args in proxy.execute(
-                'Any W,SN,WR,WS WHERE W is_instance_of Workcase, '
-                'W ref WR, W subject WS, W in_state S, S name SN'):
-            eid = args.pop(0)
-            state = args.pop(0)
-            values = values_dict(args, ['ref', 'subject'])
-            values['in_state'] = set([states_map[state]])
-            yield self.ext_entity(url, 'Workcase', eid, values)
-
-
-def values_dict(values_list, attributes):
-    values = {}
-    for k, v in zip(attributes, values_list):
-        if v is not None:
-            values[k] = set([v])
-    return values
--- a/views/__init__.py	Thu Mar 02 14:21:15 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,27 +0,0 @@
-"""template-specific forms/views/actions/components"""
-from logilab.common.decorators import monkeypatch
-
-from cubicweb.web import uicfg, formwidgets as fw
-from cubicweb.web.views import basecontrollers
-
-_afs = uicfg.autoform_section
-_affk = uicfg.autoform_field_kwargs
-_afs.tag_subject_of(('Expense', 'spent_for', '*'), 'main', 'attributes')
-_afs.tag_subject_of(('Expense', 'spent_for', '*'), 'muledit', 'attributes')
-_affk.tag_subject_of(('Expense', 'spent_for', '*'),
-                     {'widget': fw.LazyRestrictedAutoCompletionWidget(
-            autocomplete_initfunc='get_concerned_by',
-            autocomplete_settings={'limit': 100,
-                                   'delay': 300}),
-                      })
-
-
-@monkeypatch(basecontrollers.JSonController)
-@basecontrollers.jsonize
-def js_get_concerned_by(self):
-    term = self._cw.form['q']
-    limit = self._cw.form.get('limit', 50)
-    return [{'value': eid, 'label': ref}
-            for eid, ref in self._cw.execute('DISTINCT Any W,R ORDERBY R LIMIT %s WHERE W ref R,'
-                                             'W ref ILIKE %%(term)s' % limit,
-                                             {'term': u'%%%s%%' % term})]
--- a/views/accounting.py	Thu Mar 02 14:21:15 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,89 +0,0 @@
-"""accounting views for fresh template
-
-:organization: Logilab
-:copyright: 2008-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-"""
-__docformat__ = "restructuredtext en"
-
-
-from logilab.mtconverter import xml_escape
-
-from cubicweb.selectors import is_instance
-from cubicweb.view import EntityView
-
-
-class ExpenseAccountingXmlView(EntityView):
-    __regid__ = 'accexpense'
-    __select__ = is_instance('Expense')
-
-    title = _('accounting entry view')
-    templatable = False
-    content_type = 'text/xml'
-
-    def call(self):
-        """display a list of entities by calling their <item_vid> view
-        """
-        self.w(u'<?xml version="1.0" encoding="%s"?>\n' % self._cw.encoding)
-        self.w(u'<?xml-stylesheet href="%saccounting-entries.xsl" '
-               u'rel="stylesheet" type="text/xsl"?>\n' % self._cw.datadir_url)
-        self.w(u'<ecritures>\n')
-        for i in xrange(self.cw_rset.rowcount):
-            self.cell_call(i, 0)
-        self.w(u'</ecritures>\n')
-
-    def cell_call(self, row, col):
-        entity = self.cw_rset.get_entity(row, col)
-        rset = entity.related('has_lines')
-        for i in xrange(len(rset)):
-            self.wview('accentry', rset, row=i, col=0)
-
-
-class ExpenseLineAccountingEntryXmlView(EntityView):
-    __regid__ = 'accentry'
-    __select__ = is_instance('ExpenseLine',)
-
-    title = _('accounting entry view')
-    templatable = False
-    content_type = 'text/xml'
-
-    def call(self):
-        """display a list of entities by calling their <item_vid> view
-        """
-        self.w(u'<?xml version="1.0" encoding="%s"?>\n' % self._cw.encoding)
-        self.w(u'<?xml-stylesheet href="%saccounting-entries.xsl" '
-               u'rel="stylesheet" type="text/xsl"?>\n' % self._cw.datadir_url)
-        self.w(u'<ecritures>\n')
-        for i in xrange(self.cw_rset.rowcount):
-            self.cell_call(i, 0)
-        self.w(u'</ecritures>\n')
-
-    def cell_call(self, row, col):
-        entity = self.cw_rset.complete_entity(row, col)
-        self.w(u'  <ecriture date="%s">\n' % entity.diem.strftime('%Y-%m-%d'))
-        self.w(u'    <libelle>%s</libelle>\n' % xml_escape(entity.dc_long_title()))
-        amount = round(entity.euro_amount(), 2)
-        taxes = round(entity.taxes, 2)
-        account = entity.paid_by[0].account and xml_escape(entity.paid_by[0].account) or u''
-        self.w(u'    <credit compte="%s" montant="%.2f" />\n'
-               % ( account, amount))
-        if entity.taxes:
-            # XXX hardcoded account for VAT
-            self.w(u'    <debit compte="44566" montant="%.2f" />\n' % entity.taxes)
-        taxfree = int(round((amount - taxes) * 100))
-        accounts = list(entity.paid_for)
-        debit_quotient = taxfree / len(accounts)
-        debit_remainder = taxfree % len(accounts)
-        for account in accounts:
-            if debit_remainder > 0:
-                debit = (debit_quotient + 1) / 100.0
-                debit_remainder -= 1
-            else:
-                debit = debit_quotient / 100.0
-            account = account.account and xml_escape(account.account) or u''
-            self.w(u'    <debit compte="%s" montant="%.2f" />\n'
-                   % (account, debit))
-        if entity.workcase:
-            self.w(u'    <groupe>%s</groupe>\n' % xml_escape(entity.workcase))
-        self.w(u'  </ecriture>\n')
-
--- a/views/actions.py	Thu Mar 02 14:21:15 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,20 +0,0 @@
-"""specific actions for fresh template
-
-:organization: Logilab
-:copyright: 2008-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-"""
-__docformat__ = "restructuredtext en"
-
-from cubicweb.selectors import is_instance
-from cubicweb.web.action import Action
-
-class AccountingAction(Action):
-    __regid__ = 'accaction'
-    __select__ = is_instance('Expense')
-    title = _('generate accounting entries')
-
-    def url(self):
-        entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
-        return entity.absolute_url(vid='accexpense')
-