diff --git a/.travis.yml b/.travis.yml index a7bbd4d4..dcdb8d02 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,55 +8,93 @@ matrix: env: TOX_ENV='flake8' - python: 3.5 env: TOX_ENV='isort' - # Django 1.11 + + # python 3.5 + Django 1.11 + CMS 34, 35, 36, 37, 40 - python: 3.5 env: DJANGO='dj111' CMS='cms34' - python: 3.5 env: DJANGO='dj111' CMS='cms35' - python: 3.5 env: DJANGO='dj111' CMS='cms36' + - python: 3.5 + env: DJANGO='dj111' CMS='cms37' - python: 3.5 env: DJANGO='dj111' CMS='cms40' - - - python: 3.4 + # python 3.6 + Django 1.11 + CMS 34, 35, 36, 37, 40 + - python: 3.6 env: DJANGO='dj111' CMS='cms34' - - python: 3.4 + - python: 3.6 env: DJANGO='dj111' CMS='cms35' - - python: 3.4 + - python: 3.6 env: DJANGO='dj111' CMS='cms36' - - python: 3.4 + - python: 3.6 + env: DJANGO='dj111' CMS='cms37' + - python: 3.6 env: DJANGO='dj111' CMS='cms40' - - - python: 2.7 + # python 3.7 + Django 1.11 + CMS 34, 35, 36, 37, 40 + - python: 3.7 env: DJANGO='dj111' CMS='cms34' - - python: 2.7 + - python: 3.7 env: DJANGO='dj111' CMS='cms35' - - python: 2.7 + - python: 3.7 env: DJANGO='dj111' CMS='cms36' - - python: 2.7 + - python: 3.7 + env: DJANGO='dj111' CMS='cms37' + - python: 3.7 env: DJANGO='dj111' CMS='cms40' - # Django 2.0 - - python: 3.4 - env: DJANGO='dj20' CMS='cms36' + # python 3.8 + Django 1.11 + CMS 34, 35, 36, 37, 40 + - python: 3.8 + env: DJANGO='dj111' CMS='cms34' + - python: 3.8 + env: DJANGO='dj111' CMS='cms35' + - python: 3.8 + env: DJANGO='dj111' CMS='cms36' + - python: 3.8 + env: DJANGO='dj111' CMS='cms37' + - python: 3.8 + env: DJANGO='dj111' CMS='cms40' + + # python 3.5 + Django 2.0 + CMS 36, 37 - python: 3.5 env: DJANGO='dj20' CMS='cms36' + - python: 3.5 + env: DJANGO='dj20' CMS='cms37' + # python 3.6 + Django 2.0 + CMS 36, 37 - python: 3.6 env: DJANGO='dj20' CMS='cms36' - - python: 3.4 - env: DJANGO='dj20' CMS='cms40' - - python: 3.5 - env: DJANGO='dj20' CMS='cms40' - python: 3.6 - env: DJANGO='dj20' CMS='cms40' - # Django 2.1 + env: DJANGO='dj20' CMS='cms37' + # python 3.7 + Django 2.0 + CMS 36, 37 + - python: 3.7 + env: DJANGO='dj20' CMS='cms36' + - python: 3.7 + env: DJANGO='dj20' CMS='cms37' + # python 3.8 + Django 2.0 + CMS 36, 37 + - python: 3.8 + env: DJANGO='dj20' CMS='cms36' + - python: 3.8 + env: DJANGO='dj20' CMS='cms37' + + # python 3.5 + Django 2.1 + CMS 36, 37 - python: 3.5 env: DJANGO='dj21' CMS='cms36' + - python: 3.5 + env: DJANGO='dj21' CMS='cms37' + # python 3.6 + Django 2.1 + CMS 36, 37 - python: 3.6 env: DJANGO='dj21' CMS='cms36' - - python: 3.5 - env: DJANGO='dj21' CMS='cms40' - python: 3.6 - env: DJANGO='dj21' CMS='cms40' + env: DJANGO='dj21' CMS='cms37' + # python 3.7 + Django 2.1 + CMS 36, 37 + - python: 3.7 + env: DJANGO='dj21' CMS='cms36' + - python: 3.7 + env: DJANGO='dj21' CMS='cms37' + # python 3.8 + Django 2.1 + CMS 36, 37 + - python: 3.8 + env: DJANGO='dj21' CMS='cms36' + - python: 3.8 + env: DJANGO='dj21' CMS='cms37' cache: directories: @@ -64,10 +102,10 @@ cache: install: - pip install coverage isort tox - - "if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then export PY_VER=py27; fi" - - "if [[ $TRAVIS_PYTHON_VERSION == '3.4' ]]; then export PY_VER=py34; fi" - "if [[ $TRAVIS_PYTHON_VERSION == '3.5' ]]; then export PY_VER=py35; fi" - "if [[ $TRAVIS_PYTHON_VERSION == '3.6' ]]; then export PY_VER=py36; fi" + - "if [[ $TRAVIS_PYTHON_VERSION == '3.7' ]]; then export PY_VER=py37; fi" + - "if [[ $TRAVIS_PYTHON_VERSION == '3.8' ]]; then export PY_VER=py38; fi" - "if [[ ${DJANGO}z != 'z' ]]; then export TOX_ENV=$PY_VER-$DJANGO-$CMS; fi" script: diff --git a/README.rst b/README.rst index 67b7b1c5..2acb3782 100644 --- a/README.rst +++ b/README.rst @@ -1,3 +1,6 @@ +.. image:: https://secure.travis-ci.org/zbohm/aldryn-forms.png?branch=alias_and_unique_field_names + :target: http://travis-ci.com/zbohm/aldryn-forms + ================ Aldryn Forms App ================ diff --git a/aldryn_forms/helpers.py b/aldryn_forms/helpers.py index 4bbe0fa0..ec1ab374 100644 --- a/aldryn_forms/helpers.py +++ b/aldryn_forms/helpers.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from cms.cms_plugins import AliasPlugin def get_user_name(user): @@ -17,5 +18,8 @@ def is_form_element(plugin): # cms_plugins.CMSPlugin subclass cms_plugin = plugin.get_plugin_class_instance(None) is_orphan_plugin = cms_plugin.model != plugin.__class__ - is_element_subclass = issubclass(plugin.get_plugin_class(), FormElement) + plugin_class = plugin.get_plugin_class() + if plugin.get_plugin_class() == AliasPlugin: + plugin_class = plugin.plugin.get_plugin_class() + is_element_subclass = issubclass(plugin_class, FormElement) return (not is_orphan_plugin) and is_element_subclass diff --git a/aldryn_forms/models.py b/aldryn_forms/models.py index 96dd1faf..cda2568a 100644 --- a/aldryn_forms/models.py +++ b/aldryn_forms/models.py @@ -2,6 +2,7 @@ import json import warnings from collections import OrderedDict, defaultdict, namedtuple +from distutils.version import LooseVersion from functools import partial from django.conf import settings @@ -12,6 +13,8 @@ from django.utils.six import text_type from django.utils.translation import ugettext_lazy as _ +import cms +from cms.cms_plugins import AliasPlugin from cms.models.fields import PageField from cms.models.pluginmodel import CMSPlugin from cms.utils.plugins import downcast_plugins @@ -27,6 +30,7 @@ ) +CMS_LESS_THAN_4_0 = LooseVersion(cms.__version__) < LooseVersion('4.0') AUTH_USER_MODEL = getattr(settings, 'AUTH_USER_MODEL', 'auth.User') @@ -244,11 +248,15 @@ def get_form_fields(self): field_type_occurrences = defaultdict(lambda: 1) form_elements = self.get_form_elements() - field_plugins = [ - plugin for plugin in form_elements - if issubclass(plugin.get_plugin_class(), Field) - ] - + field_plugins = [] + for plugin in form_elements: + if issubclass(plugin.get_plugin_class(), Field): + field_plugins.append(plugin) + elif issubclass(plugin.get_plugin_class(), AliasPlugin) and \ + issubclass(plugin.plugin.get_plugin_class(), Field): + field_plugins.append(plugin.plugin.get_plugin_instance()[0]) + + unique_field_names = [] for field_plugin in field_plugins: field_type = field_plugin.field_type @@ -271,6 +279,11 @@ def get_form_fields(self): if field_id in field_occurrences: field_occurrences[field_id] += 1 + # Make filed names unique. + while field_name in unique_field_names: + field_name += "_" + unique_field_names.append(field_name) + field = FormField( name=field_name, label=field_label, @@ -307,7 +320,10 @@ def get_form_elements(self): from .utils import get_nested_plugins if self.child_plugin_instances is None: - descendants = self.get_descendants().order_by('path') + if CMS_LESS_THAN_4_0: + descendants = self.get_descendants().order_by('path') + else: + descendants = self.get_descendants().order_by('position') # Set parent_id to None in order to # fool the build_plugin_tree function. # This is sadly necessary to avoid getting all nodes diff --git a/setup.py b/setup.py index aa10a311..abec3552 100644 --- a/setup.py +++ b/setup.py @@ -5,9 +5,9 @@ REQUIREMENTS = [ - 'django>=2.0', + 'django>=1.11,<2.2', 'aldryn-boilerplates>=0.7.5', - 'django-cms>=3.5', + 'django-cms>=3.5,<4.0', 'django-emailit', 'djangocms-text-ckeditor', 'djangocms-attributes-field>=1.0.0', @@ -34,6 +34,8 @@ 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 'Topic :: Software Development', diff --git a/tests/requirements.txt b/tests/requirements.txt index 586311d9..39ecac65 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -3,5 +3,6 @@ tox>=2.9.1 coverage>=4.4.2 django-polymorphic>=1.3,<2.0 djangocms-text-ckeditor>=3.7.0 +django-simple-captcha flake8 isort diff --git a/tests/test_helpers.py b/tests/test_helpers.py new file mode 100644 index 00000000..dd6078e8 --- /dev/null +++ b/tests/test_helpers.py @@ -0,0 +1,38 @@ +from distutils.version import LooseVersion + +import cms +from cms.api import add_plugin, create_page +from cms.test_utils.testcases import CMSTestCase + +from aldryn_forms.helpers import is_form_element + + +CMS_3_6 = LooseVersion(cms.__version__) < LooseVersion('4.0') + + +class HelpersTest(CMSTestCase): + + def setUp(self): + page = create_page('test page', 'test_page.html', 'en') + if CMS_3_6: + self.placeholder = page.placeholders.get(slot='content') + else: + self.placeholder = page.get_placeholders('en').get(slot='content') + + def test_is_form_element_is_field(self): + plugin = add_plugin(self.placeholder, 'EmailField', 'en', name="email") + self.assertTrue(is_form_element(plugin)) + + def test_is_form_element_is_not_field(self): + plugin = add_plugin(self.placeholder, 'TextPlugin', 'en') + self.assertFalse(is_form_element(plugin)) + + def test_is_form_element_alias_with_field(self): + field = add_plugin(self.placeholder, 'EmailField', 'en', name="email") + plugin = add_plugin(self.placeholder, 'AliasPlugin', 'en', plugin=field) + self.assertTrue(is_form_element(plugin)) + + def test_is_form_element_alias_without_field(self): + field = add_plugin(self.placeholder, 'TextPlugin', 'en') + plugin = add_plugin(self.placeholder, 'AliasPlugin', 'en', plugin=field) + self.assertFalse(is_form_element(plugin)) diff --git a/tests/test_models.py b/tests/test_models.py index 7a660db9..5119ba43 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,16 +1,21 @@ -# -*- coding: utf-8 -*- -from __future__ import division, print_function, unicode_literals +from distutils.version import LooseVersion from django.db import IntegrityError from django.test import TestCase -from cms.api import add_plugin +import cms +from cms.api import add_plugin, create_page from cms.models import Placeholder +from cms.test_utils.testcases import CMSTestCase from aldryn_forms.models import Option +CMS_3_6 = LooseVersion(cms.__version__) < LooseVersion('4.0') + + class OptionTestCase(TestCase): + def setUp(self): super(TestCase, self).setUp() self.placeholder = Placeholder.objects.create(slot='test') @@ -90,3 +95,40 @@ def test_position_is_not_nullable(self): self.assertEquals(option1.position, 960) # We force a value for it on Option.save self.assertRaises(IntegrityError, Option.objects.update, position=None) # See? Not nullable + + +class FormPluginTestGetFormFields(CMSTestCase): + + def setUp(self): + page = create_page('test page', 'test_page.html', 'en') + if CMS_3_6: + self.placeholder = page.placeholders.get(slot='content') + else: + self.placeholder = page.get_placeholders('en').get(slot='content') + self.form = add_plugin(self.placeholder, 'FormPlugin', 'en') + + def test_field_with_different_names(self): + add_plugin(self.form.placeholder, 'EmailField', 'en', target=self.form, name="email-1") + add_plugin(self.form.placeholder, 'EmailField', 'en', target=self.form, name="email-2") + self.assertEquals([fld.name for fld in self.form.get_form_fields()], ['email-1', 'email-2']) + + def test_field_with_duplicate_names(self): + add_plugin(self.form.placeholder, 'EmailField', 'en', target=self.form, name="email") + add_plugin(self.form.placeholder, 'EmailField', 'en', target=self.form, name="email") + self.assertEquals([fld.name for fld in self.form.get_form_fields()], ['email', 'email_']) + + def test_alias_with_field(self): + field = add_plugin(self.placeholder, 'EmailField', 'en', name="email") + add_plugin(self.form.placeholder, 'AliasPlugin', 'en', plugin=field, target=self.form) + self.assertEquals([fld.name for fld in self.form.get_form_fields()], ['email']) + + def test_alias_without_field(self): + field = add_plugin(self.placeholder, 'TextPlugin', 'en') + add_plugin(self.form.placeholder, 'AliasPlugin', 'en', plugin=field, target=self.form) + self.assertEquals([fld.name for fld in self.form.get_form_fields()], []) + + def test_alias_with_field_and_duplicate_name(self): + add_plugin(self.form.placeholder, 'EmailField', 'en', target=self.form, name="email") + field = add_plugin(self.placeholder, 'EmailField', 'en', name="email") + add_plugin(self.form.placeholder, 'AliasPlugin', 'en', plugin=field, target=self.form) + self.assertEquals([fld.name for fld in self.form.get_form_fields()], ['email', 'email_']) diff --git a/tests/test_views.py b/tests/test_views.py index a554c06e..7254d905 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -33,9 +33,9 @@ def setUp(self): published=True, apphook='FormsApp', ) - try: + if CMS_3_6: self.placeholder = self.page.placeholders.get(slot='content') - except AttributeError: + else: self.placeholder = self.page.get_placeholders('en').get(slot='content') self.redirect_url = 'http://www.google.com' @@ -91,11 +91,9 @@ def reload_urls(self): def test_form_view_and_submission_with_apphook_django_gte_111(self): if CMS_3_6: public_page = self.page.publisher_public + public_placeholder = public_page.placeholders.first() else: public_page = self.page - try: - public_placeholder = public_page.placeholders.first() - except AttributeError: public_placeholder = public_page.get_placeholders('en').first() public_page_form_plugin = ( @@ -148,7 +146,10 @@ def test_view_submit_one_form_instead_multiple(self): published=True, apphook='FormsApp', ) - placeholder = page.placeholders.get(slot='content') + if CMS_3_6: + placeholder = page.placeholders.get(slot='content') + else: + placeholder = page.get_placeholders('en').get(slot='content') form_plugin = add_plugin( placeholder, @@ -200,7 +201,8 @@ def test_view_submit_one_form_instead_multiple(self): form_plugin2.action_backend = 'default' form_plugin2.save() - page.publish('en') + if CMS_3_6: + page.publish('en') self.reload_urls() self.apphook_clear() @@ -219,7 +221,10 @@ def test_view_submit_one_valid_form_instead_multiple(self): published=True, apphook='FormsApp', ) - placeholder = page.placeholders.get(slot='content') + if CMS_3_6: + placeholder = page.placeholders.get(slot='content') + else: + placeholder = page.get_placeholders('en').get(slot='content') form_plugin = add_plugin( placeholder, @@ -273,7 +278,8 @@ def test_view_submit_one_valid_form_instead_multiple(self): form_plugin2.action_backend = 'default' form_plugin2.save() - page.publish('en') + if CMS_3_6: + page.publish('en') self.reload_urls() self.apphook_clear() diff --git a/tox.ini b/tox.ini index 54abe8f5..dc9a179e 100644 --- a/tox.ini +++ b/tox.ini @@ -2,9 +2,9 @@ envlist = flake8 isort - py{27,34,35,36}-dj111-cms{34,35,36,40} - py{34,35,36}-dj20-cms{36,40} - py{35,36}-dj21-cms{36,40} + py{35,36,37,38}-dj111-cms{34,35,36,37,40} + py{35,36,37,38}-dj20-cms{36,37} + py{35,36,37,38}-dj21-cms{36,37} skip_missing_interpreters=True @@ -48,7 +48,8 @@ deps = cms34: django-cms>=3.4,<3.5 cms35: django-cms>=3.5,<3.6 cms36: django-cms>=3.6,<3.7 - cms40: https://github.com/divio/django-cms/archive/release/4.0.x.zip + cms37: django-cms>=3.7,<3.8 + cms40: git+https://github.com/divio/django-cms.git@release/4.0.x#egg=cms commands = {envpython} --version {env:COMMAND:coverage} erase