diff --git a/.gitignore b/.gitignore index cc9186b3..c08e5b70 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ ghostdriver.log /.cache/ /htmlcov/ *.py,cover +.idea/ MANIFEST.in~ MIND_BUCKET.rst diff --git a/.hgignore b/.hgignore index 19c0346f..82425ef6 100644 --- a/.hgignore +++ b/.hgignore @@ -10,6 +10,7 @@ syntax: regexp \.travis\.yml~ ^htmlcov/ \.py,cover +\.idea/ ^MANIFEST\.in~ ^tmp/ diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3114542b..e1d97aed 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -15,6 +15,19 @@ are used for versioning (schema follows below): 0.3.4 to 0.4). - All backwards incompatible changes are mentioned in this document. +0.10.6 +------ +2017-02-14 + +- Minor Python 3 fixes for integer, float and decimal fields. + +0.10.5 +------ +2017-02-13 + +- Tested against Python 3.6. +- Initial (experimental) Django 1.11 support. + 0.10.4 ------ 2017-01-11 diff --git a/README.rst b/README.rst index 439ac37c..5f3f0b38 100644 --- a/README.rst +++ b/README.rst @@ -12,8 +12,15 @@ Prerequisites ============= Present ------- -- Django 1.8, 1.9, 1.10 -- Python >= 2.7, >= 3.4, PyPy +- Django 1.8, 1.9, 1.10 and 1.11. +- Python 2.7, 3.4, 3.5, 3.6 and PyPy. + +Note, that Django 1.11 is not yet proclaimed to be flawlessly supported. The +core and contrib packages have been tested against the alpha Django 1.11a1 +PyPI release. All tests have successfully passed, although it's yet too early +to claim that Django 1.11 is fully supported. Certain dependencies +(``django-formtools`` and ``easy-thumbnails``) have been installed from source +(since versions supporting Django 1.11 are not yet released on PyPI.) Past ---- diff --git a/ROADMAP.rst b/ROADMAP.rst index 67553cc1..a7623561 100644 --- a/ROADMAP.rst +++ b/ROADMAP.rst @@ -1,5 +1,8 @@ -Roadmap of upcoming releases -============================ +======= +Roadmap +======= +Upcoming releases. + 0.11 ---- yyyy-mm-dd (upcoming). diff --git a/docs/index.rst b/docs/index.rst index e02a9a11..1136d1e2 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -12,8 +12,15 @@ Prerequisites ============= Present ------- -- Django 1.8, 1.9, 1.10 -- Python >= 2.7, >= 3.4, PyPy +- Django 1.8, 1.9, 1.10 and 1.11. +- Python 2.7, 3.4, 3.5, 3.6 and PyPy. + +Note, that Django 1.11 is not yet proclaimed to be flawlessly supported. The +core and contrib packages have been tested against the alpha Django 1.11a1 +PyPI release. All tests have successfully passed, although it's yet too early +to claim that Django 1.11 is fully supported. Certain dependencies +(``django-formtools`` and ``easy-thumbnails``) have been installed from source +(since versions supporting Django 1.11 are not yet released on PyPI.) Past ---- diff --git a/SCREENSHOTS.rst b/docs/screenshots.rst.distrib similarity index 100% rename from SCREENSHOTS.rst rename to docs/screenshots.rst.distrib diff --git a/examples/requirements/common_python2.txt b/examples/requirements/common.txt similarity index 83% rename from examples/requirements/common_python2.txt rename to examples/requirements/common.txt index 8a64c212..a0f838d0 100644 --- a/examples/requirements/common_python2.txt +++ b/examples/requirements/common.txt @@ -1,14 +1,11 @@ --r common_test_requirements.txt --r style_checkers.txt +#-r test.txt +#-r style_checkers.txt alabaster==0.7.6 Babel==2.1.1 decorator==4.0.4 docopt==0.4.0 docutils==0.12 -ipdb==0.8.1 -ipython==4.0.0 -ipython-genutils==0.1.0 Jinja2==2.8 mailchimp==2.0.9 MarkupSafe==0.23 diff --git a/examples/requirements/common_python3.txt b/examples/requirements/common_python3.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/examples/requirements/debug.txt b/examples/requirements/debug.txt new file mode 100644 index 00000000..d811eb9d --- /dev/null +++ b/examples/requirements/debug.txt @@ -0,0 +1,3 @@ +ipdb==0.8.1 +ipython==4.0.0 +ipython-genutils==0.1.0 diff --git a/examples/requirements/demo.txt b/examples/requirements/demo.txt index e173f48a..1a9e7371 100644 --- a/examples/requirements/demo.txt +++ b/examples/requirements/demo.txt @@ -1,11 +1,10 @@ --r common_python2.txt +-r common.txt Django>=1.9,<1.10 django-admin-tools>=0.7.1 django-autoslug==1.9.3 django-debug-toolbar==0.11 django-formtools==1.0 -#django-localeurl==2.0.2 django-nine>=0.1.10 django-nonefield>=0.1 django-registration-redux>=1.4 diff --git a/examples/requirements/django_1_10.txt b/examples/requirements/django_1_10.txt index cb2d975c..0dcf0de8 100644 --- a/examples/requirements/django_1_10.txt +++ b/examples/requirements/django_1_10.txt @@ -1,13 +1,9 @@ --r common_python2.txt +-r common.txt +-r test.txt +-r style_checkers.txt Django>=1.10,<1.11 django-admin-tools>=0.8.0 -#django-autoslug==1.9.3 django-debug-toolbar==1.5 -#django-formtools==1.0 -#django-nine>=0.1.10 -#django-nonefield==0.1 django-registration-redux>=1.4 -#easy-thumbnails==2.3 -#vishap>=0.1.5 sqlparse==0.2.2 diff --git a/examples/requirements/django_1_11.txt b/examples/requirements/django_1_11.txt new file mode 100644 index 00000000..960cf11a --- /dev/null +++ b/examples/requirements/django_1_11.txt @@ -0,0 +1,15 @@ +-r common.txt +-r test.txt +-r style_checkers.txt + +Django==1.11b1 +django-admin-tools>=0.8.0 +django-debug-toolbar==1.5 +django-registration-redux>=1.4 +sqlparse==0.2.2 + +# easy-thumbnails, compatible with Django 1.11 +https://github.com/django/django-formtools/archive/master.tar.gz + +# django-formtools, compatible with Django 1.11 +https://github.com/SmileyChris/easy-thumbnails/archive/master.tar.gz diff --git a/examples/requirements/django_1_5.txt b/examples/requirements/django_1_5.txt index d671e119..ccfa5147 100644 --- a/examples/requirements/django_1_5.txt +++ b/examples/requirements/django_1_5.txt @@ -1,14 +1,11 @@ --r common_python2.txt +-r common.txt +-r test.txt +-r style_checkers.txt Django>=1.5,<1.6 South>=0.8.2 django-admin-tools==0.5.2 -#django-autoslug>=1.7.1 django-debug-toolbar>=0.11.0 -django-localeurl>=2.0.2 -#django-nine>=0.1.10 -#django-nonefield>=0.1 +#django-localeurl>=2.0.2 django-registration-redux==1.2 -#easy-thumbnails==2.3 -#vishap>=0.1.5 sqlparse==0.1.9 diff --git a/examples/requirements/django_1_6.txt b/examples/requirements/django_1_6.txt index 9213495c..24bdabc9 100644 --- a/examples/requirements/django_1_6.txt +++ b/examples/requirements/django_1_6.txt @@ -1,14 +1,11 @@ --r common_python2.txt +-r common.txt +-r test.txt +-r style_checkers.txt Django>=1.6,<1.7 South>=0.8.2 django-admin-tools==0.5.2 -#django-autoslug>=1.7.1 django-debug-toolbar>=0.11.0 -django-localeurl>=2.0.2 -#django-nine>=0.1.10 -#django-nonefield>=0.1 +#django-localeurl>=2.0.2 django-registration-redux==1.2 -#easy-thumbnails==2.3 -#vishap>=0.1.5 sqlparse==0.1.9 diff --git a/examples/requirements/django_1_7.txt b/examples/requirements/django_1_7.txt index 1d7dc808..0305cc16 100644 --- a/examples/requirements/django_1_7.txt +++ b/examples/requirements/django_1_7.txt @@ -1,13 +1,10 @@ --r common_python2.txt +-r common.txt +-r test.txt +-r style_checkers.txt Django>=1.7,<1.8 django-admin-tools>=0.6.0 -#django-autoslug==1.7.1 django-debug-toolbar==0.11.0 -django-localeurl>=2.0.2 -#django-nine>=0.1.10 -#django-nonefield>=0.1 +#django-localeurl>=2.0.2 django-registration-redux==1.3 -#easy-thumbnails==2.3 -#vishap>=0.1.5 sqlparse==0.1.9 \ No newline at end of file diff --git a/examples/requirements/django_1_8.txt b/examples/requirements/django_1_8.txt index 47223f35..c6668b54 100644 --- a/examples/requirements/django_1_8.txt +++ b/examples/requirements/django_1_8.txt @@ -1,14 +1,10 @@ --r common_python2.txt +-r common.txt +-r test.txt +-r style_checkers.txt Django>=1.8,<1.9 django-admin-tools>=0.6.0 -#django-autoslug==1.7.1 django-debug-toolbar==0.11.0 django-formtools -#django-localeurl>=2.0.2 -#django-nine>=0.1.10 -#django-nonefield>=0.1 django-registration-redux>=1.4 -#easy-thumbnails==2.3 -#vishap>=0.1.5 -sqlparse==0.1.9 \ No newline at end of file +sqlparse==0.1.9 diff --git a/examples/requirements/django_1_9.txt b/examples/requirements/django_1_9.txt index 0f7a1757..7dff1ec6 100644 --- a/examples/requirements/django_1_9.txt +++ b/examples/requirements/django_1_9.txt @@ -1,4 +1,6 @@ --r common_python2.txt +-r common.txt +-r test.txt +-r style_checkers.txt Django>=1.9,<1.10 django-admin-tools>=0.7.1 diff --git a/examples/requirements/docs.txt b/examples/requirements/docs.txt index 7872c874..ba95855d 100644 --- a/examples/requirements/docs.txt +++ b/examples/requirements/docs.txt @@ -1,21 +1,6 @@ --r common_python2.txt +-r common.txt +-r django_1_10.txt -Django==1.10.1 django-fobi -#Jinja2 -#MarkupSafe -#MySQL-python -#mailchimp -#Sphinx -django-admin-tools>=0.8.0 -django-debug-toolbar>=1.5 -django-registration-redux>=1.4 -#docutils -#ipdb -#ipython -#ordereddict>=1.1 -# Selenium shall always be upgraded -#selenium -#tox rst2pdf reportlab==3.3.0 diff --git a/examples/requirements/latest.txt b/examples/requirements/latest.txt index 7bd0d8de..fe7439a7 100644 --- a/examples/requirements/latest.txt +++ b/examples/requirements/latest.txt @@ -1,4 +1,4 @@ --r common_python2.txt +-r common.txt Django django-admin-tools>=0.7.1 diff --git a/examples/requirements/python_3.txt b/examples/requirements/python_3.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/examples/requirements/common_test_requirements.txt b/examples/requirements/test.txt similarity index 100% rename from examples/requirements/common_test_requirements.txt rename to examples/requirements/test.txt diff --git a/examples/requirements/testing.txt b/examples/requirements/testing.txt new file mode 100644 index 00000000..a51e40ab --- /dev/null +++ b/examples/requirements/testing.txt @@ -0,0 +1,3 @@ +-r django_1_10.txt +-r test.txt +-r style_checkers.txt diff --git a/examples/simple/bar/forms.py b/examples/simple/bar/forms.py new file mode 100644 index 00000000..456efd2b --- /dev/null +++ b/examples/simple/bar/forms.py @@ -0,0 +1,6 @@ +from django import forms + +class MyForm(forms.Form): + """Test form.""" + + number = forms.IntegerField(max_value=200) diff --git a/examples/simple/bar/templates/bar/form.html b/examples/simple/bar/templates/bar/form.html new file mode 100644 index 00000000..ab70a5ce --- /dev/null +++ b/examples/simple/bar/templates/bar/form.html @@ -0,0 +1,17 @@ + + + + + Title + + + +
+ {% csrf_token %} + {{ form.non_field_errors }} + {{ form }} + +
+ + + diff --git a/examples/simple/bar/urls.py b/examples/simple/bar/urls.py new file mode 100644 index 00000000..34a6f602 --- /dev/null +++ b/examples/simple/bar/urls.py @@ -0,0 +1,8 @@ +from django.conf.urls import include, url +from django.utils.translation import ugettext_lazy as _ + +from .views import my_view + +urlpatterns = [ + url(_(r'^$'), my_view, name='bar.my_view'), +] diff --git a/examples/simple/bar/views.py b/examples/simple/bar/views.py new file mode 100644 index 00000000..7e44ff68 --- /dev/null +++ b/examples/simple/bar/views.py @@ -0,0 +1,13 @@ +from django.shortcuts import render + +from .forms import MyForm + +def my_view(request): + if request.method == 'POST': + form = MyForm(data=request.POST) + else: + form = MyForm() + + context = {'form': form} + + return render(request, 'bar/form.html', context) \ No newline at end of file diff --git a/examples/simple/runserver/bootstrap3-theme-django-1-11.sh b/examples/simple/runserver/bootstrap3-theme-django-1-11.sh new file mode 100755 index 00000000..b2ef79e6 --- /dev/null +++ b/examples/simple/runserver/bootstrap3-theme-django-1-11.sh @@ -0,0 +1 @@ +./manage.py runserver 0.0.0.0:8000 --traceback -v 3 --settings=settings.bootstrap3_theme_django_1_11 --traceback -v 3 diff --git a/examples/simple/settings/bootstrap3_theme_django_1_11.py b/examples/simple/settings/bootstrap3_theme_django_1_11.py new file mode 100644 index 00000000..4a5d0b53 --- /dev/null +++ b/examples/simple/settings/bootstrap3_theme_django_1_11.py @@ -0,0 +1,19 @@ +from .base import * + +INSTALLED_APPS = list(INSTALLED_APPS) + +try: + INSTALLED_APPS.remove('south') if 'south' in INSTALLED_APPS else None + INSTALLED_APPS.remove('tinymce') if 'tinymce' in INSTALLED_APPS else None +except Exception as err: + pass + +try: + INSTALLED_APPS.remove('admin_tools') \ + if 'admin_tools' in INSTALLED_APPS else None + INSTALLED_APPS.remove('admin_tools.menu') \ + if 'admin_tools.menu' in INSTALLED_APPS else None + INSTALLED_APPS.remove('admin_tools.dashboard') \ + if 'admin_tools.dashboard' in INSTALLED_APPS else None +except Exception as err: + pass diff --git a/examples/simple/urls.py b/examples/simple/urls.py index b723034e..52ea6504 100644 --- a/examples/simple/urls.py +++ b/examples/simple/urls.py @@ -57,6 +57,9 @@ url_patterns_args = [ # foo URLs: url(r'^foo/', include('foo.urls')), + # bar URLs: + # url(r'^bar/', include('bar.urls')), + url(r'^$', TemplateView.as_view(template_name=fobi_home_template)), # django-fobi public forms contrib app: diff --git a/scripts/build_docs.sh b/scripts/build_docs.sh index 922a43fc..020b17da 100755 --- a/scripts/build_docs.sh +++ b/scripts/build_docs.sh @@ -1,6 +1,6 @@ #./scripts/uninstall.sh #./scripts/install.sh -cat README.rst SCREENSHOTS.rst docs/documentation.rst.distrib > docs/index.rst +cat README.rst docs/screenshots.rst.distrib docs/documentation.rst.distrib > docs/index.rst cat QUICK_START.rst > docs/quickstart.rst sphinx-build -n -a -b html docs builddocs #sphinx-build -n -a -b pdf docs builddocs diff --git a/scripts/clean_up.sh b/scripts/clean_up.sh index 12943a8a..51cb5474 100755 --- a/scripts/clean_up.sh +++ b/scripts/clean_up.sh @@ -1,5 +1,6 @@ find . -name "*.pyc" -exec rm -rf {} \; find . -name "*.py,cover" -exec rm -rf {} \; +find . -name "*.orig" -exec rm -rf {} \; find . -name "__pycache__" -exec rm -rf {} \; rm -rf build/ rm -rf dist/ diff --git a/scripts/install_django_1_11.sh b/scripts/install_django_1_11.sh new file mode 100755 index 00000000..acef833b --- /dev/null +++ b/scripts/install_django_1_11.sh @@ -0,0 +1,8 @@ +pip uninstall south -y +pip install -r examples/requirements/django_1_11.txt +python setup.py install +mkdir -p examples/logs examples/db examples/media examples/media/static examples/media/fobi_plugins/content_image +mkdir -p examples/media/fobi_plugins/file +python examples/simple/manage.py collectstatic --noinput --settings=settings.bootstrap3_theme_django_1_11 --traceback -v 3 +python examples/simple/manage.py migrate --noinput --settings=settings.bootstrap3_theme_django_1_11 --traceback -v 3 +python examples/simple/manage.py fobi_create_test_data --settings=settings.bootstrap3_theme_django_1_11 --traceback -v 3 diff --git a/scripts/prepare_docs.sh b/scripts/prepare_docs.sh index b5491e4d..70ece38c 100755 --- a/scripts/prepare_docs.sh +++ b/scripts/prepare_docs.sh @@ -1,2 +1,2 @@ -cat README.rst SCREENSHOTS.rst docs/documentation.rst.distrib > docs/index.rst +cat README.rst docs/screenshots.rst.distrib docs/documentation.rst.distrib > docs/index.rst cat QUICK_START.rst > docs/quickstart.rst diff --git a/setup.py b/setup.py index a23b56ca..b9e61e51 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,15 @@ import sys from distutils.version import LooseVersion from setuptools import setup, find_packages -version = '0.10.4' +version = '0.10.6' + +# *************************************************************************** +# ************************** Python version ********************************* +# *************************************************************************** +PY2 = sys.version_info[0] == 2 +PY3 = sys.version_info[0] == 3 +LTE_PY26 = PY2 and (7 > sys.version_info[1]) +PYPY = hasattr(sys, 'pypy_translation_info') # *************************************************************************** # ************************** Django version ********************************* @@ -69,7 +77,6 @@ except Exception as err: try: readme = open(os.path.join(os.path.dirname(__file__), 'README.rst')).read() - readme = readme.replace('.. code-block:: none', '.. code-block::') screenshots = open( os.path.join(os.path.dirname(__file__), 'SCREENSHOTS.rst') ).read() @@ -78,10 +85,6 @@ try: '.. figure:: https://github.com/barseghyanartur/django-fobi/raw/' 'master/docs/_static' ) - screenshots = screenshots.replace( - '.. code-block:: none', - '.. code-block::' - ) except: readme = '' screenshots = '' @@ -206,6 +209,8 @@ for locale_dir in locale_dirs: for f in os.listdir(locale_dir)] +dependency_links = [] + install_requires = [] # If certain version of Django is already installed, choose version agnostic # dependencies. @@ -216,20 +221,21 @@ if DJANGO_INSTALLED: # 'django-formtools>=1.0', 'django-nine>=0.1.10', 'django-nonefield>=0.1', - 'ordereddict>=1.1', + # 'ordereddict>=1.1', 'Pillow>=2.0.0', 'requests>=1.0.0', 'six>=1.9', 'Unidecode>=0.04.1', 'vishap>=0.1.5,<2.0', ] + elif DJANGO_1_8: install_requires = [ 'django-autoslug==1.7.1', 'django-formtools>=1.0', 'django-nine>=0.1.10', 'django-nonefield>=0.1', - 'ordereddict>=1.1', + # 'ordereddict>=1.1', 'Pillow>=2.0.0', 'requests>=1.0.0', 'six>=1.9', @@ -242,7 +248,7 @@ if DJANGO_INSTALLED: 'django-formtools>=1.0', 'django-nine>=0.1.10', 'django-nonefield>=0.1', - 'ordereddict>=1.1', + # 'ordereddict>=1.1', 'Pillow>=2.0.0', 'requests>=1.0.0', 'six>=1.9', @@ -255,13 +261,31 @@ if DJANGO_INSTALLED: 'django-formtools>=1.0', 'django-nine>=0.1.10', 'django-nonefield>=0.1', - 'ordereddict>=1.1', + # 'ordereddict>=1.1', 'Pillow>=2.0.0', 'requests>=1.0.0', 'six>=1.9', 'Unidecode>=0.04.1', 'vishap>=0.1.5,<2.0', ] + elif DJANGO_1_11: + install_requires = [ + 'django-autoslug==1.9.3', + 'django-formtools', + 'django-nine>=0.1.10', + 'django-nonefield>=0.1', + # 'ordereddict>=1.1', + 'Pillow>=2.0.0', + 'requests>=1.0.0', + 'six>=1.9', + 'Unidecode>=0.04.1', + 'vishap>=0.1.5,<2.0', + ] + dependency_links.append( + 'https://github.com/django/django-formtools/archive/master.tar.gz' + '#egg=django-formtools' + ) + # Fall back to the latest dependencies if not install_requires: install_requires = [ @@ -269,7 +293,7 @@ if not install_requires: 'django-formtools>=1.0', 'django-nine>=0.1.10', 'django-nonefield>=0.1', - 'ordereddict>=1.1', + # 'ordereddict>=1.1', 'Pillow>=2.0.0', 'requests>=1.0.0', 'six>=1.9', @@ -290,24 +314,35 @@ tests_require = [ # 'tox', ] -try: - PY2 = sys.version_info[0] == 2 - LTE_PY26 = PY2 and (7 > sys.version_info[1]) - PY3 = sys.version_info[0] == 3 - if PY3: - install_requires.append('simplejson>=3.0.0') # When using Python 3 +if PY3: + install_requires.append('simplejson>=3.0.0') # When using Python 3 + if not DJANGO_1_11: install_requires.append('easy-thumbnails>=2.3') else: - install_requires.append('simplejson>=2.1.0') # When using Python 2.* + install_requires.append('easy-thumbnails') + dependency_links.append( + 'https://github.com/SmileyChris/easy-thumbnails/archive/' + 'master.tar.gz' + '#egg=easy-thumbnails' + ) +else: + install_requires.append('simplejson>=2.1.0') # When using Python 2.* + install_requires.append('ordereddict>=1.1') + if not DJANGO_1_11: install_requires.append('easy-thumbnails>=1.4') -except Exception as err: - pass + else: + install_requires.append('easy-thumbnails') + dependency_links.append( + 'https://github.com/SmileyChris/easy-thumbnails/archive/' + 'master.tar.gz' + '#egg=easy-thumbnails' + ) setup( name='django-fobi', version=version, - description=("Form generator/builder application for Django done right: " - "customisable, modular, user- and developer- friendly."), + description="Form generator/builder application for Django done right: " + "customisable, modular, user- and developer- friendly.", long_description="{0}{1}".format(readme, screenshots), classifiers=[ # "Programming Language :: Python :: 2.6", @@ -316,6 +351,7 @@ setup( # "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", "Environment :: Web Environment", "License :: OSI Approved :: GNU General Public License v2 (GPLv2)", "License :: OSI Approved :: GNU Lesser General Public License v2 or " @@ -335,6 +371,7 @@ setup( license='GPL 2.0/LGPL 2.1', install_requires=install_requires, tests_require=tests_require, + dependency_links=dependency_links, package_data={ 'fobi': templates + static_files + locale_files }, diff --git a/src/fobi/__init__.py b/src/fobi/__init__.py index 790fb4ce..69733cf5 100644 --- a/src/fobi/__init__.py +++ b/src/fobi/__init__.py @@ -1,6 +1,6 @@ __title__ = 'django-fobi' -__version__ = '0.10.4' -__build__ = 0x000077 +__version__ = '0.10.6' +__build__ = 0x000079 __author__ = 'Artur Barseghyan ' __copyright__ = '2014-2017 Artur Barseghyan' __license__ = 'GPL 2.0/LGPL 2.1' diff --git a/src/fobi/contrib/plugins/form_elements/fields/decimal/base.py b/src/fobi/contrib/plugins/form_elements/fields/decimal/base.py index 1a9e2278..42ace84b 100644 --- a/src/fobi/contrib/plugins/form_elements/fields/decimal/base.py +++ b/src/fobi/contrib/plugins/form_elements/fields/decimal/base.py @@ -1,5 +1,7 @@ from __future__ import absolute_import +import decimal + from django.forms.fields import DecimalField from django.utils.translation import ugettext_lazy as _ @@ -41,19 +43,24 @@ class DecimalInputPlugin(FormFieldPlugin): 'initial': self.data.initial, 'required': self.data.required, } + if self.data.max_value: - field_kwargs['max_value'] = self.data.max_value - widget_attrs['max'] = self.data.max_value + data_max_value = decimal.Decimal(self.data.max_value) + field_kwargs['max_value'] = data_max_value + widget_attrs['max'] = data_max_value + if self.data.min_value: - field_kwargs['min_value'] = self.data.min_value - widget_attrs['min'] = self.data.min_value + data_min_value = decimal.Decimal(self.data.min_value) + field_kwargs['min_value'] = data_min_value + widget_attrs['min'] = data_min_value if self.data.max_digits: - field_kwargs['max_digits'] = self.data.max_digits - widget_attrs['max'] = self.data.max_value + data_max_digits = int(self.data.max_digits) + field_kwargs['max_digits'] = data_max_digits + if self.data.decimal_places: - field_kwargs['decimal_places'] = self.data.decimal_places - widget_attrs['min'] = self.data.min_value + data_decimal_places = int(self.data.decimal_places) + field_kwargs['decimal_places'] = data_decimal_places field_kwargs['widget'] = NumberInput(attrs=widget_attrs) diff --git a/src/fobi/contrib/plugins/form_elements/fields/float/base.py b/src/fobi/contrib/plugins/form_elements/fields/float/base.py index 59182a25..c7909c40 100644 --- a/src/fobi/contrib/plugins/form_elements/fields/float/base.py +++ b/src/fobi/contrib/plugins/form_elements/fields/float/base.py @@ -34,18 +34,23 @@ class FloatInputPlugin(FormFieldPlugin): 'type': 'number', 'placeholder': self.data.placeholder, } + field_kwargs = { 'label': self.data.label, 'help_text': self.data.help_text, 'initial': self.data.initial, 'required': self.data.required, } + if self.data.max_value: - field_kwargs['max_value'] = self.data.max_value - widget_attrs['max'] = self.data.max_value + data_max_value = float(data.max_value) + field_kwargs['max_value'] = data_max_value + widget_attrs['max'] = data_max_value + if self.data.min_value: - field_kwargs['min_value'] = self.data.min_value - widget_attrs['min'] = self.data.min_value + data_min_value = float(self.data.min_value) + field_kwargs['min_value'] = data_min_value + widget_attrs['min'] = data_min_value field_kwargs['widget'] = NumberInput(attrs=widget_attrs) diff --git a/src/fobi/contrib/plugins/form_elements/fields/integer/base.py b/src/fobi/contrib/plugins/form_elements/fields/integer/base.py index 0a73c7a1..859c0544 100644 --- a/src/fobi/contrib/plugins/form_elements/fields/integer/base.py +++ b/src/fobi/contrib/plugins/form_elements/fields/integer/base.py @@ -41,11 +41,13 @@ class IntegerInputPlugin(FormFieldPlugin): 'required': self.data.required, } if self.data.max_value: - field_kwargs['max_value'] = self.data.max_value - widget_attrs['max'] = self.data.max_value + data_max_value = int(self.data.max_value) + field_kwargs['max_value'] = data_max_value + widget_attrs['max'] = data_max_value if self.data.min_value: - field_kwargs['min_value'] = self.data.min_value - widget_attrs['min'] = self.data.min_value + data_min_value = int(self.data.min_value) + field_kwargs['min_value'] = data_min_value + widget_attrs['min'] = data_min_value field_kwargs['widget'] = NumberInput(attrs=widget_attrs) diff --git a/src/fobi/tests/helpers.py b/src/fobi/tests/helpers.py index 67b4992f..f966393d 100644 --- a/src/fobi/tests/helpers.py +++ b/src/fobi/tests/helpers.py @@ -1,3 +1,7 @@ +import os +import signal +import subprocess + from django.conf import settings from django.contrib.auth import get_user_model from django.core.exceptions import ObjectDoesNotExist @@ -46,6 +50,7 @@ __all__ = ( 'get_or_create_admin_user', 'create_form_with_entries', 'db_clean_up', + 'phantom_js_clean_up', ) # **************************************************************************** @@ -272,3 +277,17 @@ def db_clean_up(): """ FormElementEntry._default_manager.all().delete() FormHandlerEntry._default_manager.all().delete() + + +def phantom_js_clean_up(): + """Clean up Phantom JS. + + Kills all phantomjs instances, disregard of their origin. + """ + processes = subprocess.Popen(['ps', '-A'], stdout=subprocess.PIPE) + out, err = processes.communicate() + + for line in out.splitlines(): + if 'phantomjs' in line: + pid = int(line.split(None, 1)[0]) + os.kill(pid, signal.SIGKILL) diff --git a/src/fobi/tests/test_browser_build_dynamic_forms.py b/src/fobi/tests/test_browser_build_dynamic_forms.py index 5383d601..57fde797 100644 --- a/src/fobi/tests/test_browser_build_dynamic_forms.py +++ b/src/fobi/tests/test_browser_build_dynamic_forms.py @@ -27,7 +27,8 @@ from .data import ( from .helpers import ( setup_fobi, get_or_create_admin_user, - db_clean_up + db_clean_up, + phantom_js_clean_up ) @@ -75,23 +76,23 @@ class BaseFobiBrowserBuldDynamicFormsTest(LiveServerTestCase): @classmethod def setUpClass(cls): """Set up class.""" - # cls.selenium = WebDriver() + # cls.driver = WebDriver() firefox_bin_path = getattr(settings, 'FIREFOX_BIN_PATH', None) phantom_js_executable_path = getattr( settings, 'PHANTOM_JS_EXECUTABLE_PATH', None ) if phantom_js_executable_path is not None: if phantom_js_executable_path: - cls.selenium = webdriver.PhantomJS( + cls.driver = webdriver.PhantomJS( executable_path=phantom_js_executable_path ) else: - cls.selenium = webdriver.PhantomJS() + cls.driver = webdriver.PhantomJS() elif firefox_bin_path: binary = FirefoxBinary(firefox_bin_path) - cls.selenium = webdriver.Firefox(firefox_binary=binary) + cls.driver = webdriver.Firefox(firefox_binary=binary) else: - cls.selenium = webdriver.Firefox() + cls.driver = webdriver.Firefox() setup_fobi(fobi_sync_plugins=True) # user = get_or_create_admin_user() @@ -103,7 +104,8 @@ class BaseFobiBrowserBuldDynamicFormsTest(LiveServerTestCase): def tearDownClass(cls): """Tear down class.""" try: - cls.selenium.quit() + cls.driver.quit() + phantom_js_clean_up() except Exception as err: print(err) @@ -137,21 +139,21 @@ class BaseFobiBrowserBuldDynamicFormsTest(LiveServerTestCase): # Make sure the user exists user = get_or_create_admin_user() - self.selenium.get( + self.driver.get( '{0}{1}'.format( self._get_live_server_url(), reverse('auth_login') ) ) - self.selenium.maximize_window() - username_input = self.selenium.find_element_by_name("username") + self.driver.maximize_window() + username_input = self.driver.find_element_by_name("username") username_input.send_keys(constants.FOBI_TEST_USER_USERNAME) - password_input = self.selenium.find_element_by_name("password") + password_input = self.driver.find_element_by_name("password") password_input.send_keys(constants.FOBI_TEST_USER_PASSWORD) - self.selenium.find_element_by_xpath('//button[@type="submit"]').click() + self.driver.find_element_by_xpath('//button[@type="submit"]').click() # Wait until the list view opens - WebDriverWait(self.selenium, timeout=TIMEOUT).until( + WebDriverWait(self.driver, timeout=TIMEOUT).until( # lambda driver: driver.find_element_by_id('id_main') lambda driver: driver.find_element_by_xpath( '//body[contains(@class, "theme")]' @@ -161,11 +163,16 @@ class BaseFobiBrowserBuldDynamicFormsTest(LiveServerTestCase): def _sleep(self, wait=WAIT_FOR): """Sleep.""" if WAIT and wait: - self.selenium.implicitly_wait(wait) + self.driver.implicitly_wait(wait) def _click(self, element): """Click on any element.""" - self.selenium.execute_script("$(arguments[0]).click();", element) + self.driver.execute_script("$(arguments[0]).click();", element) + + def _aggressive_click(self, element): + """Aggressive click.""" + link = element.get_attribute('href') + self.driver.get(link) def _go_to_dashboard(self, wait=WAIT_FOR): """Go to dashboard.""" @@ -174,10 +181,10 @@ class BaseFobiBrowserBuldDynamicFormsTest(LiveServerTestCase): # Open the dashboard. url = reverse('fobi.dashboard') - self.selenium.get('{0}{1}'.format(self._get_live_server_url(), url)) + self.driver.get('{0}{1}'.format(self._get_live_server_url(), url)) # Wait until the edit widget form opens - WebDriverWait(self.selenium, timeout=TIMEOUT).until( + WebDriverWait(self.driver, timeout=TIMEOUT).until( lambda driver: driver.find_element_by_xpath( '//body[contains(@class, "theme-bootstrap3")]' ) @@ -193,22 +200,22 @@ class BaseFobiBrowserBuldDynamicFormsTest(LiveServerTestCase): x = coordinates.get('x', 0) y = coordinates.get('y', 0) - self.selenium.execute_script( + self.driver.execute_script( "window.scrollTo({0}, {1});".format(x, y) ) - self.selenium.execute_script( + self.driver.execute_script( "window.scrollBy({0}, {1});".format(0, -100) ) def _scroll_to(self, x, y): """Scroll to.""" - self.selenium.execute_script( + self.driver.execute_script( "window.scrollTo({0}, {1});".format(x, y) ) def _scroll_by(self, x, y): """Scroll by.""" - self.selenium.execute_script( + self.driver.execute_script( "window.scrollBy({0}, {1});".format(x, y) ) @@ -223,12 +230,12 @@ class BaseFobiBrowserBuldDynamicFormsTest(LiveServerTestCase): # Follow the create form link. # Click the button to go to dashboard edit - self.selenium.find_element_by_xpath( + self.driver.find_element_by_xpath( '//a[contains(@class, "list-group-item")]' ).click() # Wait until the dashboard edit view opens - WebDriverWait(self.selenium, timeout=TIMEOUT).until( + WebDriverWait(self.driver, timeout=TIMEOUT).until( # lambda driver: driver.find_element_by_id('id_main') lambda driver: driver.find_element_by_xpath( '//body[contains(@class, "theme-bootstrap3")]' @@ -246,11 +253,11 @@ class BaseFobiBrowserBuldDynamicFormsTest(LiveServerTestCase): if form_data: for field_name, field_value in form_data.items(): - field_input = self.selenium.find_element_by_name(field_name) + field_input = self.driver.find_element_by_name(field_name) field_input.send_keys(field_value) # Click add widget button - self.selenium.find_element_by_xpath('//button[@type="submit"]').click() + self.driver.find_element_by_xpath('//button[@type="submit"]').click() logger.debug( """//div[contains(text(), 'Form {0} was created """ @@ -260,7 +267,7 @@ class BaseFobiBrowserBuldDynamicFormsTest(LiveServerTestCase): ) # Wait until the fobi page opens with the form element in - WebDriverWait(self.selenium, timeout=TIMEOUT).until( + WebDriverWait(self.driver, timeout=TIMEOUT).until( lambda driver: driver.find_element_by_xpath( """//div[contains(text(), 'Form {0} was created """ """successfully.') """ @@ -288,7 +295,7 @@ class BaseFobiBrowserBuldDynamicFormsTest(LiveServerTestCase): """ # Click the add form element button to add a new form element to the # form. - add_form_element_link = self.selenium.find_element_by_xpath( + add_form_element_link = self.driver.find_element_by_xpath( """//a[contains(text(), 'Choose form element to add') and """ """contains(@class, "dropdown-toggle")]""" ) @@ -320,7 +327,7 @@ class BaseFobiBrowserBuldDynamicFormsTest(LiveServerTestCase): # Adding form data if form_element_data: # Wait until the add widget view opens - WebDriverWait(self.selenium, timeout=TIMEOUT).until( + WebDriverWait(self.driver, timeout=TIMEOUT).until( lambda driver: driver.find_element_by_xpath( """//h1[contains(text(), 'Add "{0}" element to """ """the form')]""".format( @@ -330,18 +337,18 @@ class BaseFobiBrowserBuldDynamicFormsTest(LiveServerTestCase): ) for field_name, field_value in form_element_data.items(): - field_input = self.selenium.find_element_by_name(field_name) + field_input = self.driver.find_element_by_name(field_name) field_input.send_keys(field_value) # Click add widget button - self.selenium.find_element_by_xpath( + self.driver.find_element_by_xpath( '//button[@type="submit"]' ).click() logger.debug(form_element_name) # Wait until the fobi page opens with the form element in. - WebDriverWait(self.selenium, timeout=TIMEOUT).until( + WebDriverWait(self.driver, timeout=TIMEOUT).until( lambda driver: driver.find_element_by_xpath( """//div[contains(text(), 'The form element plugin "{0}" """ """was added successfully.') """ @@ -385,7 +392,7 @@ class BaseFobiBrowserBuldDynamicFormsTest(LiveServerTestCase): """ # Get the label of the given form element in order to delete it later # from the form. - delete_form_element_label = self.selenium.find_element_by_xpath( + delete_form_element_label = self.driver.find_element_by_xpath( """//label[contains(text(), '({0})') """ """and contains(@class, "control-label")]""".format( form_element_name @@ -403,13 +410,14 @@ class BaseFobiBrowserBuldDynamicFormsTest(LiveServerTestCase): .find_element_by_partial_link_text( 'Delete' ) - delete_form_element_link.click() + # delete_form_element_link.click() # self._click(delete_form_element_link) + self._aggressive_click(delete_form_element_link) logger.debug(form_element_name) # Wait until the fobi page opens with the form element in. - WebDriverWait(self.selenium, timeout=LONG_TIMEOUT).until( + WebDriverWait(self.driver, timeout=LONG_TIMEOUT).until( lambda driver: driver.find_element_by_xpath( """//div[contains(text(), 'The form element plugin "{0}" """ """was deleted successfully.') """ @@ -442,14 +450,14 @@ class BaseFobiBrowserBuldDynamicFormsTest(LiveServerTestCase): # tab with form handlers. Otherwise Selenium raises # an exception about non-visible element on the page # that we're trying to fetch. - form_handlers_tab_link = self.selenium.find_element_by_xpath( + form_handlers_tab_link = self.driver.find_element_by_xpath( """//a[@href="#tab-form-handlers"]""" ) form_handlers_tab_link.click() # Click the add form element button to add a new form element to the # form. - add_form_handler_link = self.selenium.find_element_by_xpath( + add_form_handler_link = self.driver.find_element_by_xpath( """//a[contains(text(), 'Choose form handler to add') """ """and contains(@class, "dropdown-toggle")]""" ) @@ -476,7 +484,7 @@ class BaseFobiBrowserBuldDynamicFormsTest(LiveServerTestCase): # If has config, there's a need to perform some extra tests. if form_handler_data: # Wait until the add widget view opens - WebDriverWait(self.selenium, timeout=TIMEOUT).until( + WebDriverWait(self.driver, timeout=TIMEOUT).until( lambda driver: driver.find_element_by_xpath( """//h1[contains(text(), 'Add "{0}" handler to """ """the form')]""".format( @@ -487,16 +495,16 @@ class BaseFobiBrowserBuldDynamicFormsTest(LiveServerTestCase): # Config for field_name, field_value in form_handler_data.items(): - field_input = self.selenium.find_element_by_name(field_name) + field_input = self.driver.find_element_by_name(field_name) field_input.send_keys(field_value) # Click add widget button - self.selenium.find_element_by_xpath( + self.driver.find_element_by_xpath( '//button[@type="submit"]' ).click() # Wait until the fobi page opens with the form element in. - WebDriverWait(self.selenium, timeout=TIMEOUT).until( + WebDriverWait(self.driver, timeout=TIMEOUT).until( lambda driver: driver.find_element_by_xpath( """//div[contains(text(), 'The form handler plugin "{0}" """ """was added successfully.') """ @@ -542,14 +550,14 @@ class BaseFobiBrowserBuldDynamicFormsTest(LiveServerTestCase): # tab with form handlers. Otherwise Selenium raises # an exception about non-visible element on the page # that we're trying to fetch. - form_handlers_tab_link = self.selenium.find_element_by_xpath( + form_handlers_tab_link = self.driver.find_element_by_xpath( """//a[@href="#tab-form-handlers"]""" ) form_handlers_tab_link.click() # Get the label of the given form element in order to delete it later # from the form. - delete_form_handler_label = self.selenium.find_element_by_xpath( + delete_form_handler_label = self.driver.find_element_by_xpath( """//td[contains(text(), '{0}')]""".format(form_handler_name) ) @@ -569,7 +577,7 @@ class BaseFobiBrowserBuldDynamicFormsTest(LiveServerTestCase): logger.debug(form_handler_name) # Wait until the fobi page opens with the form element in. - WebDriverWait(self.selenium, timeout=TIMEOUT).until( + WebDriverWait(self.driver, timeout=TIMEOUT).until( lambda driver: driver.find_element_by_xpath( """//div[contains(text(), 'The form handler plugin "{0}" """ """was deleted successfully.') """ @@ -630,7 +638,7 @@ class BaseFobiBrowserBuldDynamicFormsTest(LiveServerTestCase): self._test_add_form(wait=WAIT_FOR) # Make sure the success message is there - # self.selenium.find_element_by_xpath( + # self.driver.find_element_by_xpath( # """//div[text()='Form {0} was created successfully.']""".format( # constants.TEST_FORM_NAME # ) @@ -665,26 +673,26 @@ class BaseFobiBrowserBuldDynamicFormsTest(LiveServerTestCase): # Getting the form URL url = reverse('fobi.view_form_entry', args=[form.slug]) - self.selenium.get('{0}{1}'.format(self._get_live_server_url(), url)) + self.driver.get('{0}{1}'.format(self._get_live_server_url(), url)) # Wait until the edit widget form opens - WebDriverWait(self.selenium, timeout=TIMEOUT).until( + WebDriverWait(self.driver, timeout=TIMEOUT).until( lambda driver: driver.find_element_by_xpath( '//body[contains(@class, "theme-bootstrap3")]' ) ) for field_name, field_value in TEST_FORM_FIELD_DATA.items(): - field_input = self.selenium.find_element_by_name(field_name) + field_input = self.driver.find_element_by_name(field_name) field_input.send_keys(field_value) self._sleep(2) # Click add widget button - self.selenium.find_element_by_xpath('//button[@type="submit"]').click() + self.driver.find_element_by_xpath('//button[@type="submit"]').click() # Wait until the submit success page opens a clear success message. - WebDriverWait(self.selenium, timeout=TIMEOUT).until( + WebDriverWait(self.driver, timeout=TIMEOUT).until( lambda driver: driver.find_element_by_xpath( """//div[contains(text(), 'Form {0} was submitted """ """successfully.') """ diff --git a/tox.ini b/tox.ini index 5a466a0d..d371cf8e 100644 --- a/tox.ini +++ b/tox.ini @@ -3,8 +3,8 @@ envlist = # py{27,33,34}-{django15,django16}, # py{27,33,34}-{django17,django18} # py{27,34}-{django17,django18} - py{27,34,35,py}-{django18,django19} - py{27,35,py}-{django110} + py{27,34,35,36,py}-{django18,django19} + py{27,35,36,py}-{django110,django111} #flake8, #isort @@ -18,6 +18,7 @@ deps = django18: -r{toxinidir}/examples/requirements/django_1_8.txt django19: -r{toxinidir}/examples/requirements/django_1_9.txt django110: -r{toxinidir}/examples/requirements/django_1_10.txt + django111: -r{toxinidir}/examples/requirements/django_1_11.txt commands = # {envpython} examples/simple/manage.py test {posargs:fobi} --settings=settings.test --traceback -v 3 {envpython} runtests.py