diff --git a/MANIFEST.in b/MANIFEST.in index 99a038a30378968490107a86648000e259486a8d..9140cd5d644a1aaec6fc1f3c104f5e48a096885a 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,11 +1,11 @@ include *.py include */*.py -include tox.ini -include test/data/bootstrap_cubes -recursive-include data *.gif *.png *.ico *.css *.js -recursive-include i18n *.po -recursive-include test *.py -recursive-include wdoc * +recursive-include cubicweb_i18nfield *.py +recursive-include cubicweb_i18nfield/data *.gif *.png *.ico *.css *.js +recursive-include cubicweb_i18nfield/i18n *.po +recursive-include cubicweb_i18nfield/wdoc * +recursive-include test/data bootstrap_cubes *.py +include *.ini prune debian -include README.rst +exclude cubicweb-i18nfield.spec exclude .gitlab-ci.yml diff --git a/__pkginfo__.py b/__pkginfo__.py deleted file mode 100644 index 7c10a16298c520a11aa54de378a3588f864b0fa2..0000000000000000000000000000000000000000 --- a/__pkginfo__.py +++ /dev/null @@ -1,51 +0,0 @@ -# -*- coding: utf-8 -*- -# pylint: disable=W0622 -"""cubicweb-i18nfield application packaging information""" - -from os import listdir as _listdir -from os.path import join, isdir -from glob import glob - -modname = 'i18nfield' -distname = 'cubicweb-i18nfield' - -numversion = (0, 1, 0) -version = '.'.join(str(num) for num in numversion) - -license = 'LGPL' -author = 'Florent Cayré (Villejuif, FRANCE)' -author_email = 'Florent Cayré ' -description = 'Provides a way to translate entity fields individually.' -web = 'http://www.cubicweb.org/project/%s' % distname - -__depends__ = { - 'cubicweb': '>= 3.24', - 'cubicweb-card': '>= 0.5', -} - -__recommends__ = {} - - -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 dname in ('entities', 'views', 'sobjects', 'hooks', 'schema', 'data', - 'wdoc', 'i18n', 'migration'): - if isdir(dname): - data_files.append([join(THIS_CUBE_DIR, dname), listdir(dname)]) - -# Note: here, you'll need to add subdirectories if you want -# them to be included in the debian package diff --git a/.gitlab-ci.yml b/cubicweb_i18nfield/.gitlab-ci.yml similarity index 100% rename from .gitlab-ci.yml rename to cubicweb_i18nfield/.gitlab-ci.yml diff --git a/.hgtags b/cubicweb_i18nfield/.hgtags similarity index 100% rename from .hgtags rename to cubicweb_i18nfield/.hgtags diff --git a/__init__.py b/cubicweb_i18nfield/__init__.py similarity index 100% rename from __init__.py rename to cubicweb_i18nfield/__init__.py diff --git a/cubicweb_i18nfield/__pkginfo__.py b/cubicweb_i18nfield/__pkginfo__.py new file mode 100644 index 0000000000000000000000000000000000000000..24a54059596ee09c0dbf0efce2f58af18c380892 --- /dev/null +++ b/cubicweb_i18nfield/__pkginfo__.py @@ -0,0 +1,26 @@ +# pylint: disable=W0622 +"""cubicweb-/home/ethieblin/src/cubes/i18nfield application packaging information""" + + +modname = "cubicweb_/home/ethieblin/src/cubes/i18nfield" +distname = "i18nfield" + +numversion = (0, 1, 0) +version = ".".join(str(num) for num in numversion) + +license = "LGPL" +author = "Florent Cayré (Villejuif, FRANCE)" +author_email = "Florent Cayré " +description = "Provides a way to translate entity fields individually." +web = "http://www.cubicweb.org/project/%s" % distname + +__depends__ = {'cubicweb': '>= 3.24', 'cubicweb-card': '>= 0.5'} +__recommends__ = {} + +classifiers = [ + "Environment :: Web Environment", + "Framework :: CubicWeb", + "Programming Language :: Python :: 3", + "Programming Language :: JavaScript", +] + diff --git a/entities.py b/cubicweb_i18nfield/entities.py similarity index 98% rename from entities.py rename to cubicweb_i18nfield/entities.py index f595604643bda117ab00f76c874f7e77d0267df8..ca6b934aecb4210c481a646331d0df3155447eeb 100644 --- a/entities.py +++ b/cubicweb_i18nfield/entities.py @@ -18,7 +18,7 @@ """cubicweb-i18nfield entity's classes""" -from six import text_type as unicode +from six import text_type as str import pytz from logilab.common.decorators import cached @@ -153,7 +153,7 @@ class TranslatableEntityMixin(object): def printable_value(self, attr, value=_marker, attrtype=None, format='text/html', displaytime=True): - uattr = unicode(attr) # attr could be a rschema object + uattr = str(attr) # attr could be a rschema object if uattr in self.i18nfields and value == _marker: adapted = self.cw_adapt_to('translatable_entity') tlang_code = target_lang_code(self._cw) diff --git a/hooks.py b/cubicweb_i18nfield/hooks.py similarity index 99% rename from hooks.py rename to cubicweb_i18nfield/hooks.py index 89657c99b3916a5ead0ab8090b028a30b363e950..4a46d637e04b06e5f0c133fc92667d462fd3d051 100644 --- a/hooks.py +++ b/cubicweb_i18nfield/hooks.py @@ -34,7 +34,7 @@ class TranslatableEntityAddHook(Hook): def __call__(self): langs = self._cw.transaction_data.get('i18nfield_lang', {}) for field in self.entity.i18nfields: - lang = langs.get(field, u'en') + lang = langs.get(field, 'en') ctx = {'e': self.entity.eid, 'c': lang, 'f': field} rql = 'Any L WHERE L is I18nLang, L code %(c)s' rset = self._cw.execute(rql, ctx) diff --git a/i18n/de.po b/cubicweb_i18nfield/i18n/de.po similarity index 100% rename from i18n/de.po rename to cubicweb_i18nfield/i18n/de.po diff --git a/i18n/en.po b/cubicweb_i18nfield/i18n/en.po similarity index 100% rename from i18n/en.po rename to cubicweb_i18nfield/i18n/en.po diff --git a/i18n/es.po b/cubicweb_i18nfield/i18n/es.po similarity index 100% rename from i18n/es.po rename to cubicweb_i18nfield/i18n/es.po diff --git a/i18n/fr.po b/cubicweb_i18nfield/i18n/fr.po similarity index 100% rename from i18n/fr.po rename to cubicweb_i18nfield/i18n/fr.po diff --git a/migration/postcreate.py b/cubicweb_i18nfield/migration/postcreate.py similarity index 100% rename from migration/postcreate.py rename to cubicweb_i18nfield/migration/postcreate.py diff --git a/schema.py b/cubicweb_i18nfield/schema.py similarity index 98% rename from schema.py rename to cubicweb_i18nfield/schema.py index baae4e94ded774f42eade2a5d4861160bcede88c..2929664e986813e94da462f53ad75a3dde33a8d0 100644 --- a/schema.py +++ b/cubicweb_i18nfield/schema.py @@ -18,13 +18,13 @@ """cubicweb-i18nfield schema""" -from six import text_type as unicode +from six import text_type as str from yams.buildobjs import (EntityType, RelationType, String, Datetime, SubjectRelation) from cubicweb.schema import RQLConstraint -_ = unicode +_ = str class I18nLang(EntityType): diff --git a/uiprops.py b/cubicweb_i18nfield/uiprops.py similarity index 100% rename from uiprops.py rename to cubicweb_i18nfield/uiprops.py diff --git a/utils.py b/cubicweb_i18nfield/utils.py similarity index 100% rename from utils.py rename to cubicweb_i18nfield/utils.py diff --git a/views.py b/cubicweb_i18nfield/views.py similarity index 95% rename from views.py rename to cubicweb_i18nfield/views.py index cef903a89229229bdb9b81863327576326a623d0..db0d8742d40cec8d924727306d192701f3155f14 100644 --- a/views.py +++ b/cubicweb_i18nfield/views.py @@ -18,7 +18,7 @@ """cubicweb-i18nfield views/forms/actions/components for web ui""" -from six import text_type as unicode +from six import text_type as str from logilab.mtconverter import xml_escape from logilab.common.decorators import iclassmethod @@ -48,7 +48,7 @@ from cubicweb.view import StartupView from cubicweb.web.httpcache import NoHTTPCacheManager from cubicweb.web.views.urlrewrite import SimpleReqRewriter, rgx -_ = unicode # make pylint happier +_ = str # make pylint happier _AFF = uicfg.autoform_field _AFF_KWARGS = uicfg.autoform_field_kwargs @@ -90,8 +90,8 @@ class TranslateEntityAction(Action): action.append(self._cw._('outdated')) if len(infos) < len(entity.i18nfields): action.append(self._cw._('incomplete')) - action = u', '.join(action) or self._cw._('edition') - title = u'%s (%s)' % (self._cw._(lang.name), action) + action = ', '.join(action) or self._cw._('edition') + title = '%s (%s)' % (self._cw._(lang.name), action) yield self.build_action(title, url) yield self.build_action(self._cw._('manage translations'), self._cw.build_url('i18n')) @@ -124,7 +124,7 @@ class ManageTranslationsView(StartupView): infos = LANGS_BY_EID[typed_eid(eid)] href = entity.absolute_url(vid='translate_entity', lang_code=infos['code']) - html.append(u'%s (%s)' % (xml_escape(infos['name']), + html.append('%s (%s)' % (xml_escape(infos['name']), tags.a(self._cw._('edit'), href=href))) return ', '.join(html) @@ -134,10 +134,10 @@ class ManageTranslationsView(StartupView): for title, rql_method in sections: rset = self._cw.execute(rql_method()) if rset.rowcount: - self.w(u'
' - u'

%s

' % self._cw._(title)) + self.w('
' + '

%s

' % self._cw._(title)) self.wview('table.translations', rset) - self.w(u'
') + self.w('
') class ManageTranslationTableView(cw_tableview.RsetTableView): @@ -156,7 +156,7 @@ class TableLayout(cw_tableview.TableLayout): view = self.view divid = view.domid if divid is not None: - w(u'
' % divid) + w('
' % divid) else: assert not (actions or paginate) nav_html = UStringIO() @@ -169,15 +169,15 @@ class TableLayout(cw_tableview.TableLayout): self.render_actions(w, actions) colrenderers = view.build_column_renderers() attrs = self.table_attributes() - w(u'' % sgml_attributes(attrs)) + w('
' % sgml_attributes(attrs)) if self.view.has_headers: self.render_table_headers(w, colrenderers) self.render_table_body(w, colrenderers) - w(u'
') + w('') if actions and self.display_actions == 'bottom': self.render_actions(w, actions) if divid is not None: - w(u'
') + w('
') class TranslateEntityView(EditionFormView): @@ -268,7 +268,7 @@ class I18nTranslationInlineViewMixin(object): title = super(I18nTranslationInlineViewMixin, self).form_title( entity, i18nctx) if entity.has_eid() and entity.is_outdated(): - title += u' (%s)' % self._cw._('outdated') + title += ' (%s)' % self._cw._('outdated') return title @@ -283,7 +283,7 @@ class I18nTranslationInlinedFormRenderer(EntityInlinedFormRenderer): class I18nFieldInlinedFormRenderer(EntityInlinedFormRenderer): __select__ = (EntityInlinedFormRenderer.__select__ & is_instance('I18nField')) - title_template = u"""\ + title_template = """\
{orig_val}
{label} : {orig_lang}
""" @@ -291,7 +291,7 @@ class I18nFieldInlinedFormRenderer(EntityInlinedFormRenderer): def render_title(self, w, form, values): ent = form.edited_entity template_parameters = { - 'orig_val': xml_escape(ent.original_value() or u''), + 'orig_val': xml_escape(ent.original_value() or ''), 'orig_lang': self._cw._(ent.i18nfield_of[0].ref_lang[0].name), 'label': self._cw._('Original version'), 'css_lang': 'cw_i18nfield_orig_lang', diff --git a/setup.py b/setup.py index ee2218c6c4e52d4318c0a9c68251cd10ceaa4ab0..5c65650914f16f083575312441e76e0b9fbb437e 100644 --- a/setup.py +++ b/setup.py @@ -1,10 +1,10 @@ #!/usr/bin/env python # pylint: disable=W0142,W0403,W0404,W0613,W0622,W0622,W0704,R0904,C0103,E0611 # -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# copyright 2020 Logilab, all rights reserved. +# contact https://www.logilab.fr -- mailto:contact@logilab.fr # -# This file is part of CubicWeb tag cube. +# This file is part of a cubicweb-i18nfield. # # 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 @@ -18,195 +18,67 @@ # # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . -import __pkginfo__ -"""Generic Setup script, takes package info from __pkginfo__.py file -""" -__docformat__ = "restructuredtext en" - -import os -import sys -import shutil -from os.path import isdir, exists, join - -try: - from os.path import walk -except ImportError: - from os import 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 - -if exists('README.rst'): - long_description = open('README.rst', 'r').read() -else: - long_description = '' - -# import optional features -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.items()] -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', '~') - - -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 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 as 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) - - -# 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, - **kwargs - ) +"""cubicweb_i18nfield setup module using data from +cubicweb_i18nfield/__pkginfo__.py file +""" -if __name__ == '__main__': - install() +from os.path import join, dirname + +from setuptools import find_packages, setup + + +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_i18nfield", "__pkginfo__.py") +__pkginfo__ = {} +with open(pkginfo) as f: + exec(f.read(), __pkginfo__) + +# 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.rst")) 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() +] + + +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": [ + "i18nfield=cubicweb_i18nfield", + ], + }, + zip_safe=False, +) diff --git a/test/data/entities.py b/test/data/entities.py index 1ac4e05d560a813782bc3f6409b0a3c29bd26486..d577a39b45f763041ec875d72f42fc056e850d55 100644 --- a/test/data/entities.py +++ b/test/data/entities.py @@ -8,7 +8,7 @@ from cubes.i18nfield.entities import (TranslatableEntityMixin, class Card(TranslatableEntityMixin, OrigCard): __select__ = OrigCard.__select__ & yes() - i18nfields = (u'title',) + i18nfields = ('title',) def dc_title(self): return self.printable_value('title', format='text/plain') diff --git a/test/test_i18nfield.py b/test/test_i18nfield.py index b83154e77ba88eadf9bcc72b3858ad038f6be656..8a1cbf4cd024fbe6e4ec620f5242a21d1464100d 100644 --- a/test/test_i18nfield.py +++ b/test/test_i18nfield.py @@ -28,7 +28,7 @@ class AutomaticWebTest(AutomaticWebTest): def setup_database(self): with self.admin_access.repo_cnx() as cnx: - cnx.create_entity('I18nLang', code=u'en', name=u'English') + cnx.create_entity('I18nLang', code='en', name='English') cnx.commit() def test_ten_each_config(self): diff --git a/test/unittest_i18nfield.py b/test/unittest_i18nfield.py index 02f1b2b29116dfc313f35359513a6bed064c8951..3557a452f7b514535cd86a0400b7093203a70110 100644 --- a/test/unittest_i18nfield.py +++ b/test/unittest_i18nfield.py @@ -31,11 +31,11 @@ class I18nFieldTC(CubicWebTC): super(I18nFieldTC, self).setup_database() with self.admin_access.repo_cnx() as cnx: self.en = cnx.create_entity('I18nLang', - code=u'en', name=u'English') - self.fr = cnx.create_entity('I18nLang', code=u'fr', name=u'French') - self.de = cnx.create_entity('I18nLang', code=u'de', name=u'German') - cnx.transaction_data['i18nfield_lang'] = {u'title': u'fr'} - self.card = cnx.create_entity('Card', title=u'salut', + code='en', name='English') + self.fr = cnx.create_entity('I18nLang', code='fr', name='French') + self.de = cnx.create_entity('I18nLang', code='de', name='German') + cnx.transaction_data['i18nfield_lang'] = {'title': 'fr'} + self.card = cnx.create_entity('Card', title='salut', ref_lang=self.fr) cnx.commit() @@ -47,20 +47,20 @@ class I18nFieldTC(CubicWebTC): with self.admin_access.repo_cnx() as cnx: card = cnx.find('Card', eid=self.card.eid).one() tr_en = cnx.create_entity( - 'Translation', value=u'hello', lang=self.en, + 'Translation', value='hello', lang=self.en, of_field=card.reverse_i18nfield_of[0]) cnx.commit() - cnx.data['i18nfield_target_lang'] = u'fr' - self.assertEqual(card.dc_title(), u'salut') - cnx.data['i18nfield_target_lang'] = u'en' - self.assertEqual(card.dc_title(), u'hello') + cnx.data['i18nfield_target_lang'] = 'fr' + self.assertEqual(card.dc_title(), 'salut') + cnx.data['i18nfield_target_lang'] = 'en' + self.assertEqual(card.dc_title(), 'hello') # Check I18nField methods field = card.reverse_i18nfield_of[0] - self.assertEqual(field.translation(u'en').value, u'hello') - self.assertEqual(field.original_value(), u'salut') + self.assertEqual(field.translation('en').value, 'hello') + self.assertEqual(field.original_value(), 'salut') # Check Translation methods self._set_field_last_edited(field, dt.now()) - self.failUnless(tr_en.is_outdated()) + self.assertTrue(tr_en.is_outdated()) # Check adapter's translation_infos method adapted = card.cw_adapt_to('translatable_entity') tr_infos = adapted.translation_infos() @@ -77,7 +77,7 @@ class I18nFieldTC(CubicWebTC): def _assert_is_fresh(self, field): field.cw_clear_all_caches() - self.failUnless(dt.now() - field.last_edited < td(seconds=10)) + self.assertTrue(dt.now() - field.last_edited < td(seconds=10)) def _set_field_last_edited(self, field, date): with self.repo.internal_cnx() as cnx: @@ -91,39 +91,39 @@ class I18nFieldTC(CubicWebTC): # check creation hook: I18nField creation with supplied lang... with self.admin_access.repo_cnx() as cnx: card = cnx.find('Card', eid=self.card).one() - self.failUnless(card.reverse_i18nfield_of) + self.assertTrue(card.reverse_i18nfield_of) field = card.reverse_i18nfield_of[0] # ... and correct last_edited date self._assert_is_fresh(field) # edition hook: last_edited must be updated self._set_field_last_edited(field, dt.now() - td(days=10)) - card.cw_set(title=u'bonjour') + card.cw_set(title='bonjour') cnx.commit() field.cw_clear_all_caches() - self.failUnless(dt.now() - field.last_edited < td(seconds=1)) + self.assertTrue(dt.now() - field.last_edited < td(seconds=1)) # deletion hook cnx.execute('DELETE Card C WHERE C eid %(c)s', {'c': card.eid}) cnx.commit() - self.failIf(cnx.execute('Any F WHERE F eid %(x)s', + self.assertFalse(cnx.execute('Any F WHERE F eid %(x)s', {'x': field.eid}).rowcount) def test_lang_cache_dicts_hooks(self): with self.admin_access.repo_cnx() as cnx: - init_codes = [u'de', u'en', u'fr'] + init_codes = ['de', 'en', 'fr'] init_eids = sorted([self.en.eid, self.fr.eid, self.de.eid]) self.assertEqual(sorted(LANGS_BY_CODE.keys()), init_codes) self.assertEqual(sorted(LANGS_BY_EID.keys()), init_eids) # test creation - sp = cnx.create_entity('I18nLang', code=u'sp', name=u'Spanish') + sp = cnx.create_entity('I18nLang', code='sp', name='Spanish') cnx.commit() self.assertEqual(sorted(LANGS_BY_CODE.keys()), - init_codes + [u'sp']) + init_codes + ['sp']) self.assertEqual(sorted(LANGS_BY_EID.keys()), init_eids + [sp.eid]) # test update - sp.cw_set(code=u'sq') + sp.cw_set(code='sq') cnx.commit() self.assertEqual(sorted(LANGS_BY_CODE.keys()), - init_codes + [u'sq']) + init_codes + ['sq']) self.assertEqual(sorted(LANGS_BY_EID.keys()), init_eids + [sp.eid]) # test remove cnx.execute('DELETE I18nLang L WHERE L code "sq"') @@ -135,7 +135,7 @@ class I18nFieldTC(CubicWebTC): with self.admin_access.repo_cnx() as cnx: card = cnx.find('Card', eid=self.card.eid).one() cnx.create_entity( - 'Translation', value=u'salut', lang=self.fr.eid, + 'Translation', value='salut', lang=self.fr.eid, of_field=card.reverse_i18nfield_of[0]) with self.assertRaises(ValidationError): cnx.commit() @@ -143,7 +143,7 @@ class I18nFieldTC(CubicWebTC): def test_permission_admin_cannot_add_i18nfield(self): with self.admin_access.repo_cnx() as cnx: with self.assertRaises(Unauthorized) as wraperr: - cnx.create_entity('I18nField', field_name=u'synopsis', + cnx.create_entity('I18nField', field_name='synopsis', i18nfield_of=self.card.eid) self.assertEqual( str(wraperr.exception), @@ -153,15 +153,15 @@ class I18nFieldTC(CubicWebTC): def test_unique_together(self): with self.repo.internal_cnx() as cnx: with self.assertRaises(ValidationError) as wraperr: - cnx.create_entity('I18nField', field_name=u'title', + cnx.create_entity('I18nField', field_name='title', i18nfield_of=self.card.eid) self.assertDictEqual( {'i18nfiaeld_of': - u'i18nfield_of is part of violated unicity constraint', + 'i18nfield_of is part of violated unicity constraint', 'field_name': - u'field_name is part of violated unicity constraint', + 'field_name is part of violated unicity constraint', 'unicity constraint': - u'some relations violate a unicity constraint'}, + 'some relations violate a unicity constraint'}, wraperr.exception.args[1]) def _first_inlined_form(self, form): @@ -176,7 +176,7 @@ class I18nFieldTC(CubicWebTC): '''translation value field and widget classes must be the same as the translated field of the original entity''' with self.admin_access.web_request() as req: - req.form['lang_code'] = u'fr' + req.form['lang_code'] = 'fr' # get card translation value field tr_card_form = self._card_form(req, 'translate_entity') title_form = self._first_inlined_form(tr_card_form) @@ -195,13 +195,13 @@ class I18nFieldTC(CubicWebTC): with self.admin_access.repo_cnx() as cnx: card = cnx.find('Card', eid=self.card.eid).one() adapted = card.cw_adapt_to('translatable_entity') - self.assertEqual(adapted.i18nfield('title').field_name, u'title') + self.assertEqual(adapted.i18nfield('title').field_name, 'title') def test_translatable_entity_udpate(self): '''test TranslatableEntityUpdateHook''' with self.admin_access.repo_cnx() as cnx: - card1 = self.create_card(cnx, u'title') - card2 = self.create_card(cnx, u'card2') + card1 = self.create_card(cnx, 'title') + card2 = self.create_card(cnx, 'card2') cnx.commit() i18ntitle1 = card1.cw_adapt_to( 'translatable_entity').i18nfield('title') @@ -211,14 +211,14 @@ class I18nFieldTC(CubicWebTC): initial_date2 = i18ntitle2.last_edited # check title i18nfield last_edited date is not changed when # another card1 attribute is edited - card1.cw_set(synopsis=u'synopsis1') + card1.cw_set(synopsis='synopsis1') cnx.commit() i18ntitle1.cw_clear_all_caches() self.assertEqual(initial_date1, i18ntitle1.last_edited) # check title1 i18nfield last_edited date is changed when card1's # title is edited initial_date1 = i18ntitle1.last_edited - card1.cw_set(title=u'card1') + card1.cw_set(title='card1') cnx.commit() i18ntitle1.cw_clear_all_caches() self.assertTrue(initial_date1 < i18ntitle1.last_edited) diff --git a/tox.ini b/tox.ini index 4a5400ecac6653f6fde864dfa7311ddd10ec4ee2..782d610527d6e1a6c40d5a9dd45c59dabcf65ada 100644 --- a/tox.ini +++ b/tox.ini @@ -1,19 +1,18 @@ [tox] -envlist = py27,flake8,check-manifest +envlist = py3,flake8,check-manifest,black [testenv] deps = pytest commands = - {envpython} -m pytest test {posargs} + {envpython} -m pytest {posargs:test} [testenv:flake8] +basepython = python3 skip_install = true -whitelist_externals = - flake8 deps = - flake8 -commands = flake8 {toxinidir} + flake8 >= 3.6 +commands = flake8 [testenv:check-manifest] skip_install = true @@ -22,12 +21,24 @@ deps = commands = {envpython} -m check_manifest {toxinidir} -[flake8] -format = pylint -ignore = W503, E203, E731, E231 -exclude = migration/*,test,setup.py,.tox/*,scripts,doc -max-line-length = 100 -max-complexity = 12 +[testenv:mypy] +deps = + mypy >= 0.761 +commands = mypy --ignore-missing-imports cubicweb_i18nfield + +[testenv:black] +basepython = python3 +skip_install = true +deps = + black >= 19.10b0 +commands = black --check . + +[testenv:black-run] +basepython = python3 +skip_install = true +deps = + black >= 19.10b0 +commands = black . [testenv:pypi-publish] basepython = python3 @@ -58,6 +69,19 @@ commands = hg clean --all --dirs --files rm -rf build dist .egg .egg-info python3 setup.py sdist - sh -c "PACKAGE_NAME=$(python3 setup.py --name) && VERSION=$(python3 setup.py --version) && cd dist && tar xf $PACKAGE_NAME-$VERSION.tar.gz && cd $PACKAGE_NAME-$VERSION && cp -a {toxinidir}/debian . && mk-origtargz --rename ../$PACKAGE_NAME-$VERSION.tar.gz && dpkg-buildpackage -us -uc --no-check-builddeps --build=source " + sh -c "PACKAGE_NAME=$(python3 setup.py --name) && VERSION=$(python3 setup.py --version) && \ + cd dist && \ + tar xf $PACKAGE_NAME-$VERSION.tar.gz && \ + cd $PACKAGE_NAME-$VERSION && \ + cp -a {toxinidir}/debian . && \ + mk-origtargz --rename ../$PACKAGE_NAME-$VERSION.tar.gz && \ + dpkg-buildpackage -us -uc --no-check-builddeps --build=source " sh -c "cd dist && dcmd zip latest.zip *.changes" http -f POST https://{env:JENKINS_USER}:{env:JENKINS_TOKEN}@jenkins.intra.logilab.fr/job/pkg-from-dsc/buildWithParameters DIST=buster source.zip@dist/latest.zip REPO=buster PUBLISH=true + +[flake8] +basepython = python3 +format = pylint +ignore = W503, E203, E731, E231 +max-line-length = 100 +exclude = cubicweb_i18nfield/migration/*,test/data/*,.tox/*