From 536cb51abc401e73ea5d9189c4610d261999130e Mon Sep 17 00:00:00 2001 From: John Vandenberg Date: Thu, 16 Jul 2020 10:41:42 +0700 Subject: [PATCH 01/10] Replace render_to_response render_to_response was removed in Django 3 --- form_designer/views.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/form_designer/views.py b/form_designer/views.py index 90d2e12a..5ddd7e2b 100644 --- a/form_designer/views.py +++ b/form_designer/views.py @@ -1,4 +1,3 @@ - import json from django.contrib import messages try: @@ -9,8 +8,7 @@ from django.utils.six.moves.urllib.parse import urlencode from django.utils.six.moves.urllib.request import Request, urlopen from django.http import HttpResponseRedirect -from django.shortcuts import get_object_or_404, render_to_response -from django.template import RequestContext +from django.shortcuts import get_object_or_404, render from django.utils.module_loading import import_string from django.utils.translation import ugettext_lazy as _ @@ -142,8 +140,7 @@ def _form_detail_view(request, form_definition): result.update({ 'form_template': form_definition.form_template_name or app_settings.DEFAULT_FORM_TEMPLATE }) - return render_to_response('html/formdefinition/detail.html', result, - context_instance=RequestContext(request)) + return render('html/formdefinition/detail.html', result) def detail(request, object_name): From 1fe4aafa386215a9223d30027b964620482d1b1a Mon Sep 17 00:00:00 2001 From: John Vandenberg Date: Thu, 16 Jul 2020 10:43:36 +0700 Subject: [PATCH 02/10] Use django.apps to load model by name --- form_designer/fields.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/form_designer/fields.py b/form_designer/fields.py index fc34ea6c..77f1773a 100644 --- a/form_designer/fields.py +++ b/form_designer/fields.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals from django import forms +from django.apps import apps from django.core import validators from django.core.exceptions import ValidationError from django.db import models @@ -13,8 +14,8 @@ class ModelNameFormField(forms.CharField): def get_model_from_string(model_path): try: app_label, model_name = model_path.rsplit('.models.') - return models.get_model(app_label, model_name) - except: + return apps.get_model(app_label, model_name) + except (ValueError, LookupError): return None def clean(self, value): From c2c9ee4cd7e8b99ea6146fdef0ed56c4f4d59a8f Mon Sep 17 00:00:00 2001 From: John Vandenberg Date: Wed, 28 Oct 2020 17:23:14 +0700 Subject: [PATCH 03/10] Fallback to six directly django.utils.six is removed in Django 3. --- .../contrib/cms_plugins/form_designer_form/models.py | 3 +-- form_designer/contrib/exporters/csv_exporter.py | 6 +++++- form_designer/models.py | 6 +++++- form_designer/uploads.py | 2 +- form_designer/views.py | 9 +++++++-- 5 files changed, 19 insertions(+), 7 deletions(-) diff --git a/form_designer/contrib/cms_plugins/form_designer_form/models.py b/form_designer/contrib/cms_plugins/form_designer_form/models.py index b0dea4fd..ee0bdef2 100644 --- a/form_designer/contrib/cms_plugins/form_designer_form/models.py +++ b/form_designer/contrib/cms_plugins/form_designer_form/models.py @@ -1,10 +1,9 @@ from django.db import models from django.utils.encoding import force_text -from django.utils.six import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ from cms.models import CMSPlugin -from form_designer.models import FormDefinition +from form_designer.models import FormDefinition, python_2_unicode_compatible @python_2_unicode_compatible diff --git a/form_designer/contrib/exporters/csv_exporter.py b/form_designer/contrib/exporters/csv_exporter.py index d9c42126..8348b10f 100644 --- a/form_designer/contrib/exporters/csv_exporter.py +++ b/form_designer/contrib/exporters/csv_exporter.py @@ -3,12 +3,16 @@ import csv from django.http import HttpResponse -from django.utils import six from django.utils.encoding import force_bytes from form_designer import settings from form_designer.contrib.exporters import FormLogExporterBase +try: + from django.utils import six +except ImportError: + import six + class CsvExporter(FormLogExporterBase): diff --git a/form_designer/models.py b/form_designer/models.py index 4ecd057c..46c13944 100644 --- a/form_designer/models.py +++ b/form_designer/models.py @@ -10,7 +10,6 @@ from django.template.loader import get_template from django.utils.deprecation import warn_about_renamed_method from django.utils.module_loading import import_string -from django.utils.six import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ from form_designer import settings @@ -23,6 +22,11 @@ except ImportError: from django.core.urlresolvers import reverse +try: + from django.utils.six import python_2_unicode_compatible +except ImportError: + from six import python_2_unicode_compatible + MAIL_TEMPLATE_CONTEXT_HELP_TEXT = _( 'Your form fields are available as template context. ' 'Example: "{{ first_name }} {{ last_name }} <{{ from_email }}>" ' diff --git a/form_designer/uploads.py b/form_designer/uploads.py index 25c17235..fb073553 100644 --- a/form_designer/uploads.py +++ b/form_designer/uploads.py @@ -8,10 +8,10 @@ from django.db.models.fields.files import FieldFile from django.forms.forms import NON_FIELD_ERRORS from django.template.defaultfilters import filesizeformat -from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ from form_designer import settings as app_settings +from form_designer.models import python_2_unicode_compatible from form_designer.utils import get_random_hash diff --git a/form_designer/views.py b/form_designer/views.py index 5ddd7e2b..8d2be81f 100644 --- a/form_designer/views.py +++ b/form_designer/views.py @@ -5,8 +5,6 @@ except ImportError: # older Django from django.core.context_processors import csrf -from django.utils.six.moves.urllib.parse import urlencode -from django.utils.six.moves.urllib.request import Request, urlopen from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404, render from django.utils.module_loading import import_string @@ -17,6 +15,13 @@ from form_designer.signals import designedform_error, designedform_render, designedform_submit, designedform_success from form_designer.uploads import handle_uploaded_files +try: + from django.utils.six.moves.urllib.parse import urlencode + from django.utils.six.moves.urllib.request import Request, urlopen +except ImportError: + from six.moves.urllib.parse import urlencode + from six.moves.urllib.request import Request, urlopen + def get_designed_form_class(): return import_string(app_settings.DESIGNED_FORM_CLASS) From 57617eba74ce8eafc4ef575fc15435637c3e58ec Mon Sep 17 00:00:00 2001 From: John Vandenberg Date: Wed, 28 Oct 2020 17:24:25 +0700 Subject: [PATCH 04/10] Add Django 3 to Django 2 check --- dfd_tests/settings.py | 2 +- dfd_tests/urls.py | 2 +- form_designer/models.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dfd_tests/settings.py b/dfd_tests/settings.py index 8d13646f..db989d42 100644 --- a/dfd_tests/settings.py +++ b/dfd_tests/settings.py @@ -48,7 +48,7 @@ ('page.html', 'page'), ] -if django.VERSION[0] == 2: +if django.VERSION[0] >= 2: MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', diff --git a/dfd_tests/urls.py b/dfd_tests/urls.py index e30a59b7..16d0272f 100644 --- a/dfd_tests/urls.py +++ b/dfd_tests/urls.py @@ -2,7 +2,7 @@ from django.contrib import admin import django -admin_urls = (admin.site.urls if (django.VERSION[0] == 2) else include(admin.site.urls)) +admin_urls = (admin.site.urls if (django.VERSION[0] >= 2) else include(admin.site.urls)) urlpatterns = [ url(r'^admin/', admin_urls), diff --git a/form_designer/models.py b/form_designer/models.py index 46c13944..6df95ef1 100644 --- a/form_designer/models.py +++ b/form_designer/models.py @@ -166,7 +166,7 @@ def log(self, form, user=None): # For old versions, not calling it will result in false positives, # so we have to be pretty explicit about these checks here. - if django.VERSION[0] == 2: + if django.VERSION[0] >= 2: if user and user.is_authenticated: created_by = user else: # TODO: Remove when Django <1.10 compat is dropped From 619857c993d6e9e22c8e38ed7d5c54f0469634eb Mon Sep 17 00:00:00 2001 From: John Vandenberg Date: Wed, 28 Oct 2020 17:24:52 +0700 Subject: [PATCH 05/10] setup.py: Disable picklefield version pin --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 78d0e721..8a6b5303 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ 'Topic :: Internet :: WWW/HTTP', ], install_requires=[ - 'django-picklefield>=0.3.2,<0.4', + 'django-picklefield', ], zip_safe=False, ) From 16e4b16b252555df4cc5fccbe76804dab675fd69 Mon Sep 17 00:00:00 2001 From: John Vandenberg Date: Wed, 28 Oct 2020 17:26:31 +0700 Subject: [PATCH 06/10] tox.ini: Add Django 3 and Python 3.8 --- tox.ini | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/tox.ini b/tox.ini index 179b92fe..e1e9c111 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,8 @@ envlist = py{27,34}-django{17}-djcms{32} py{27,34,35,36}-django{18,19}-djcms{32,33,34} py{27,34,35,36}-django{110,111}-djcms{34} - py{35,36}-django{20,21} + py{35,36,37,38}-django{20,21,22} + py{36,37,38}-django{30} skip_missing_interpreters = true @@ -14,9 +15,14 @@ python = 3.4: py34 3.5: py35 3.6: py36 + 3.7: py37 + 3.8: py38 [testenv] deps = + djcms{32,33,34}: django-formtools<2.2 + djcms{32,33,34}: django-classy-tags<2 + djcms{32,33,34}: django-treebeard djcms32: django-cms~=3.2.0 djcms33: django-cms~=3.3.0 djcms34: django-cms~=3.4.5 @@ -28,12 +34,18 @@ deps = django111: Django>=1.11,<1.12 django20: Django>=2.0,<2.1 django21: Django>=2.1,<2.2 + django22: Django>=2.2,<2.3 + django30: Django>=3.0,<3.1 django17: pytest-django>=3.1,<3.2 - django{18,19,110,111,20,21}: pytest-django + django{18,19,110,111}: pytest-django<4 + django{20,21,22,30}: pytest-django django17: pytest<4 - django{18,19,110,111,20,21}: pytest + django{18,19,110,111,20,21,22,30}: pytest + + django{17,18,19,110,111,20,21,22}: django-picklefield<0.4.0 + django{30}: django-picklefield py34: mysqlclient<1.4 py{27,35,36}: mysqlclient From 4bed23129c092bdc9b8777c2e3dca5bff7594584 Mon Sep 17 00:00:00 2001 From: John Vandenberg Date: Wed, 28 Oct 2020 17:26:47 +0700 Subject: [PATCH 07/10] .travis.yml: Add Python 3.8 --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index f5b6b99d..b6f9fc69 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,8 @@ python: - "3.4" - "3.5" - "3.6" + - "3.7" + - "3.8" install: - pip install -U pip - pip install 'tox>=3.4.0' tox-travis codecov From 13ba1d6adada6f79045a898d33be37a110826d85 Mon Sep 17 00:00:00 2001 From: William Lindholm Date: Wed, 15 Jun 2022 17:33:44 +0300 Subject: [PATCH 08/10] Update to python3 and django3 while dropping python- and django-2 - Drop support for python 2 and django < 3 - Add support for python 3.10 and django 3.2 --- .travis.yml | 6 +-- dfd_tests/urls.py | 5 +- form_designer/__init__.py | 1 - form_designer/admin.py | 14 ++---- form_designer/apps.py | 2 +- .../form_designer_form/cms_plugins.py | 2 +- .../migrations/0001_initial.py | 2 - .../cms_plugins/form_designer_form/models.py | 5 +- form_designer/contrib/exporters/__init__.py | 2 +- .../contrib/exporters/csv_exporter.py | 9 ---- .../contrib/exporters/xls_exporter.py | 2 - form_designer/fields.py | 8 ++-- form_designer/forms.py | 2 +- form_designer/migrations/0001_initial.py | 2 - form_designer/migrations/0002_reply_to.py | 2 - .../migrations/0003_mail_cover_text.py | 2 - form_designer/models.py | 22 ++------- form_designer/settings.py | 2 +- form_designer/signals.py | 8 ++-- form_designer/tests/test_basics.py | 4 +- form_designer/uploads.py | 12 +++-- form_designer/urls.py | 6 +-- form_designer/views.py | 18 ++------ setup.cfg | 4 +- tox.ini | 46 ++++--------------- 25 files changed, 55 insertions(+), 133 deletions(-) diff --git a/.travis.yml b/.travis.yml index b6f9fc69..486a3490 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,12 +7,10 @@ env: - DATABASE=mysql DATABASE_USER=travis DATABASE_PASSWORD= - DATABASE=sqlite python: - - "2.7" - - "3.4" - - "3.5" - - "3.6" - "3.7" - "3.8" + - "3.9" + - "3.10" install: - pip install -U pip - pip install 'tox>=3.4.0' tox-travis codecov diff --git a/dfd_tests/urls.py b/dfd_tests/urls.py index 16d0272f..932673b3 100644 --- a/dfd_tests/urls.py +++ b/dfd_tests/urls.py @@ -1,9 +1,10 @@ -from django.conf.urls import include, url +from django.conf.urls import include +from django.urls import path from django.contrib import admin import django admin_urls = (admin.site.urls if (django.VERSION[0] >= 2) else include(admin.site.urls)) urlpatterns = [ - url(r'^admin/', admin_urls), + path('admin/', admin_urls), ] diff --git a/form_designer/__init__.py b/form_designer/__init__.py index c3b809a1..e69de29b 100644 --- a/form_designer/__init__.py +++ b/form_designer/__init__.py @@ -1 +0,0 @@ -default_app_config = 'form_designer.apps.FormDesignerConfig' diff --git a/form_designer/admin.py b/form_designer/admin.py index 6ad78332..167e5b4f 100644 --- a/form_designer/admin.py +++ b/form_designer/admin.py @@ -1,19 +1,13 @@ -from __future__ import unicode_literals - -from django.conf.urls import url +from django.urls import re_path from django.contrib import admin from django.http import Http404 from django.utils.module_loading import import_string -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from form_designer import settings from form_designer.forms import FormDefinitionFieldInlineForm, FormDefinitionForm from form_designer.models import FormDefinition, FormDefinitionField, FormLog - -try: - from django.urls import reverse -except ImportError: - from django.core.urlresolvers import reverse +from django.urls import reverse class FormDefinitionFieldInline(admin.StackedInline): @@ -76,7 +70,7 @@ def get_actions(self, request): def get_urls(self): urls = [ - url( + re_path( r'^export/(?P[a-zA-Z0-9_-]+)/$', self.admin_site.admin_view(self.export_view), name='form_designer_export' diff --git a/form_designer/apps.py b/form_designer/apps.py index 42403a9c..1d49ab53 100644 --- a/form_designer/apps.py +++ b/form_designer/apps.py @@ -1,5 +1,5 @@ from django.apps import AppConfig -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ class FormDesignerConfig(AppConfig): diff --git a/form_designer/contrib/cms_plugins/form_designer_form/cms_plugins.py b/form_designer/contrib/cms_plugins/form_designer_form/cms_plugins.py index 48b82469..f665edce 100644 --- a/form_designer/contrib/cms_plugins/form_designer_form/cms_plugins.py +++ b/form_designer/contrib/cms_plugins/form_designer_form/cms_plugins.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from cms.plugin_base import CMSPluginBase from cms.plugin_pool import plugin_pool diff --git a/form_designer/contrib/cms_plugins/form_designer_form/migrations/0001_initial.py b/form_designer/contrib/cms_plugins/form_designer_form/migrations/0001_initial.py index 19ca7adf..b13048aa 100644 --- a/form_designer/contrib/cms_plugins/form_designer_form/migrations/0001_initial.py +++ b/form_designer/contrib/cms_plugins/form_designer_form/migrations/0001_initial.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - import cms from django.db import migrations, models from pkg_resources import parse_version as V diff --git a/form_designer/contrib/cms_plugins/form_designer_form/models.py b/form_designer/contrib/cms_plugins/form_designer_form/models.py index ee0bdef2..27e9ef11 100644 --- a/form_designer/contrib/cms_plugins/form_designer_form/models.py +++ b/form_designer/contrib/cms_plugins/form_designer_form/models.py @@ -1,12 +1,11 @@ from django.db import models from django.utils.encoding import force_text -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from cms.models import CMSPlugin -from form_designer.models import FormDefinition, python_2_unicode_compatible +from form_designer.models import FormDefinition -@python_2_unicode_compatible class CMSFormDefinition(CMSPlugin): form_definition = models.ForeignKey(FormDefinition, verbose_name=_('form'), on_delete=models.CASCADE) diff --git a/form_designer/contrib/exporters/__init__.py b/form_designer/contrib/exporters/__init__.py index 67868315..63230173 100644 --- a/form_designer/contrib/exporters/__init__.py +++ b/form_designer/contrib/exporters/__init__.py @@ -1,6 +1,6 @@ from django.db.models import Count from django.utils.encoding import force_text -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from form_designer import settings from form_designer.templatetags.friendly import friendly diff --git a/form_designer/contrib/exporters/csv_exporter.py b/form_designer/contrib/exporters/csv_exporter.py index 8348b10f..14f98ae4 100644 --- a/form_designer/contrib/exporters/csv_exporter.py +++ b/form_designer/contrib/exporters/csv_exporter.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import csv from django.http import HttpResponse @@ -8,11 +6,6 @@ from form_designer import settings from form_designer.contrib.exporters import FormLogExporterBase -try: - from django.utils import six -except ImportError: - import six - class CsvExporter(FormLogExporterBase): @@ -30,8 +23,6 @@ def init_response(self): ) def writerow(self, row): - if six.PY2: - row = [force_bytes(value, encoding=settings.CSV_EXPORT_ENCODING) for value in row] self.writer.writerow(row) def export(self, request, queryset=None): diff --git a/form_designer/contrib/exporters/xls_exporter.py b/form_designer/contrib/exporters/xls_exporter.py index 2c6d85b4..ecf8340b 100644 --- a/form_designer/contrib/exporters/xls_exporter.py +++ b/form_designer/contrib/exporters/xls_exporter.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.http import HttpResponse from django.utils.encoding import force_text diff --git a/form_designer/fields.py b/form_designer/fields.py index 77f1773a..327d3221 100644 --- a/form_designer/fields.py +++ b/form_designer/fields.py @@ -1,11 +1,9 @@ -from __future__ import unicode_literals - from django import forms from django.apps import apps from django.core import validators from django.core.exceptions import ValidationError from django.db import models -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ class ModelNameFormField(forms.CharField): @@ -25,7 +23,7 @@ def clean(self, value): """ value = super(ModelNameFormField, self).clean(value) if value in validators.EMPTY_VALUES: - return u'' + return'' if not ModelNameFormField.get_model_from_string(value): raise ValidationError( _('Model could not be imported: %(value)s. Please use a valid model path.'), @@ -93,7 +91,7 @@ def clean(self, value): """ value = super(RegexpExpressionFormField, self).clean(value) if value in validators.EMPTY_VALUES: - value = u'' + value ='' import re try: re.compile(value) diff --git a/form_designer/forms.py b/form_designer/forms.py index e56e78b4..1eadb54f 100644 --- a/form_designer/forms.py +++ b/form_designer/forms.py @@ -5,7 +5,7 @@ from django.forms import widgets from django.forms.widgets import Select from django.utils.module_loading import import_string -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from form_designer import settings from form_designer.models import FormDefinition, FormDefinitionField diff --git a/form_designer/migrations/0001_initial.py b/form_designer/migrations/0001_initial.py index 05c79a96..7a5d7c0d 100644 --- a/form_designer/migrations/0001_initial.py +++ b/form_designer/migrations/0001_initial.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.6 on 2016-05-19 07:05 -from __future__ import unicode_literals - from django.conf import settings from django.db import migrations, models import django.db.models.deletion diff --git a/form_designer/migrations/0002_reply_to.py b/form_designer/migrations/0002_reply_to.py index d28a0828..c1291e6d 100644 --- a/form_designer/migrations/0002_reply_to.py +++ b/form_designer/migrations/0002_reply_to.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2016-06-06 06:50 -from __future__ import unicode_literals - from django.db import migrations import form_designer.fields diff --git a/form_designer/migrations/0003_mail_cover_text.py b/form_designer/migrations/0003_mail_cover_text.py index 788f69e9..d11d30c0 100644 --- a/form_designer/migrations/0003_mail_cover_text.py +++ b/form_designer/migrations/0003_mail_cover_text.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models import form_designer.fields diff --git a/form_designer/models.py b/form_designer/models.py index 6df95ef1..da22b9a7 100644 --- a/form_designer/models.py +++ b/form_designer/models.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import re from collections import OrderedDict from decimal import Decimal @@ -10,22 +8,14 @@ from django.template.loader import get_template from django.utils.deprecation import warn_about_renamed_method from django.utils.module_loading import import_string -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from form_designer import settings from form_designer.fields import ModelNameField, RegexpExpressionField, TemplateCharField, TemplateTextField from form_designer.utils import get_random_hash, string_template_replace from picklefield.fields import PickledObjectField +from django.urls import reverse -try: - from django.urls import reverse -except ImportError: - from django.core.urlresolvers import reverse - -try: - from django.utils.six import python_2_unicode_compatible -except ImportError: - from six import python_2_unicode_compatible MAIL_TEMPLATE_CONTEXT_HELP_TEXT = _( 'Your form fields are available as template context. ' @@ -61,7 +51,6 @@ def get_django_template_from_string(template_string): return Template(template_string) -@python_2_unicode_compatible class FormDefinition(models.Model): name = models.SlugField(_('name'), max_length=255, unique=True) require_hash = models.BooleanField(_('obfuscate URL to this form'), default=False, help_text=_('If enabled, the form can only be reached via a secret URL.')) @@ -198,7 +187,7 @@ def is_template_html(self): if template: # We have a custom inline template string? # Assume the template string is HTML-ish if it has at least one opening # and closing HTML tag: - return (re.search(u"<[^>]+>", template) and re.search(u"]+>", template)) + return (re.search("<[^>]+>", template) and re.search("]+>", template)) # If there is no custom inline template, see if the `EMAIL_TEMPLATE` # setting points to a `.html` file: @@ -213,7 +202,6 @@ def submit_flag_name(self): return name -@python_2_unicode_compatible class FormDefinitionField(models.Model): form_definition = models.ForeignKey(FormDefinition, on_delete=models.CASCADE) field_class = models.CharField(_('field class'), max_length=100) @@ -323,7 +311,6 @@ def __str__(self): return (self.label or self.name) -@python_2_unicode_compatible class FormLog(models.Model): form_definition = models.ForeignKey(FormDefinition, related_name='logs', on_delete=models.CASCADE) created = models.DateTimeField(_('Created'), auto_now=True) @@ -397,11 +384,10 @@ def save(self, *args, **kwargs): self._data = None -@python_2_unicode_compatible class FormValue(models.Model): form_log = models.ForeignKey(FormLog, related_name='values', on_delete=models.CASCADE) field_name = models.SlugField(_('field name'), max_length=255) value = PickledObjectField(_('value'), null=True, blank=True) def __str__(self): - return u'%s = %s' % (self.field_name, self.value) + return'%s = %s' % (self.field_name, self.value) diff --git a/form_designer/settings.py b/form_designer/settings.py index db59e8ed..32717e28 100644 --- a/form_designer/settings.py +++ b/form_designer/settings.py @@ -2,7 +2,7 @@ from django.conf import settings from django.core.files.storage import get_storage_class -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ STATIC_URL = os.path.join(getattr(settings, 'STATIC_URL', settings.MEDIA_URL), 'form_designer') diff --git a/form_designer/signals.py b/form_designer/signals.py index 43527375..3b1433e8 100644 --- a/form_designer/signals.py +++ b/form_designer/signals.py @@ -1,6 +1,6 @@ from django import dispatch -designedform_submit = dispatch.Signal(providing_args=["designed_form"]) -designedform_success = dispatch.Signal(providing_args=["designed_form"]) -designedform_error = dispatch.Signal(providing_args=["designed_form"]) -designedform_render = dispatch.Signal(providing_args=["designed_form"]) +designedform_submit = dispatch.Signal() +designedform_success = dispatch.Signal() +designedform_error = dispatch.Signal() +designedform_render = dispatch.Signal() diff --git a/form_designer/tests/test_basics.py b/form_designer/tests/test_basics.py index 9448df1f..cedc4bb8 100644 --- a/form_designer/tests/test_basics.py +++ b/form_designer/tests/test_basics.py @@ -1,6 +1,4 @@ # -- encoding: UTF-8 -- -from __future__ import unicode_literals - from base64 import b64decode from django.contrib.auth.models import AnonymousUser @@ -110,7 +108,7 @@ def test_simple_form( ]) @pytest.mark.parametrize('n_logs', range(5)) def test_export(rf, greeting_form, exporter, n_logs): - message = u'Térve' + message ='Térve' for n in range(n_logs): fl = FormLog.objects.create( form_definition=greeting_form diff --git a/form_designer/uploads.py b/form_designer/uploads.py index fb073553..94015340 100644 --- a/form_designer/uploads.py +++ b/form_designer/uploads.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import hashlib import os import uuid @@ -8,10 +6,9 @@ from django.db.models.fields.files import FieldFile from django.forms.forms import NON_FIELD_ERRORS from django.template.defaultfilters import filesizeformat -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from form_designer import settings as app_settings -from form_designer.models import python_2_unicode_compatible from form_designer.utils import get_random_hash @@ -75,7 +72,6 @@ def handle_uploaded_files(form_definition, form): return files -@python_2_unicode_compatible class StoredUploadedFile(FieldFile): """ A wrapper for uploaded files that is compatible to the FieldFile class, i.e. @@ -87,6 +83,12 @@ def __init__(self, name): File.__init__(self, None, name) self.field = self + def __getstate__(self): + return {'name': self.name, 'closed': False, '_committed': True, '_file': None} + + def __setstate__(self, state): + self.__dict__.update(state) + @property def storage(self): return get_storage() diff --git a/form_designer/urls.py b/form_designer/urls.py index 319174f2..347d81a7 100644 --- a/form_designer/urls.py +++ b/form_designer/urls.py @@ -1,6 +1,6 @@ -from django.conf.urls import url +from django.urls import re_path urlpatterns = [ - url(r'^(?P[-\w]+)/$', 'form_designer.views.detail', name='form_designer_detail'), - url(r'^h/(?P[-\w]+)/$', 'form_designer.views.detail_by_hash', name='form_designer_detail_by_hash'), + re_path(r'^(?P[-\w]+)/$', 'form_designer.views.detail', name='form_designer_detail'), + re_path(r'^h/(?P[-\w]+)/$', 'form_designer.views.detail_by_hash', name='form_designer_detail_by_hash'), ] diff --git a/form_designer/views.py b/form_designer/views.py index 8d2be81f..b3f1fe73 100644 --- a/form_designer/views.py +++ b/form_designer/views.py @@ -1,26 +1,19 @@ import json from django.contrib import messages -try: - from django.template.context_processors import csrf -except ImportError: # older Django - from django.core.context_processors import csrf +from django.template.context_processors import csrf from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404, render from django.utils.module_loading import import_string -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ from form_designer import settings as app_settings from form_designer.models import FormDefinition from form_designer.signals import designedform_error, designedform_render, designedform_submit, designedform_success from form_designer.uploads import handle_uploaded_files -try: - from django.utils.six.moves.urllib.parse import urlencode - from django.utils.six.moves.urllib.request import Request, urlopen -except ImportError: - from six.moves.urllib.parse import urlencode - from six.moves.urllib.request import Request, urlopen +from django.utils.http import urlencode +from urllib.request import urlopen def get_designed_form_class(): @@ -39,8 +32,7 @@ def check_recaptcha(request, context, push_messages): 'response': recaptcha_response } data = urlencode(values).encode('utf-8') - req = Request(url, data) - response = urlopen(req) + response = urlopen(url, data=data) result = json.load(response) ''' End reCAPTCHA validation ''' if not result['success']: diff --git a/setup.cfg b/setup.cfg index 40e06ca5..7c1ad07f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -17,7 +17,7 @@ atomic=true combine_as_imports=false indent=4 known_standard_library=token,tokenize,enum,importlib -known_third_party=django,six +known_third_party=django length_sort=false line_length=120 multi_line_output=5 @@ -34,4 +34,4 @@ doctest_optionflags = NORMALIZE_WHITESPACE IGNORE_EXCEPTION_DETAIL ALLOW_UNICODE filterwarnings = once::DeprecationWarning once::PendingDeprecationWarning - ignore:.+PickledObjectField.from_db_value.+ \ No newline at end of file + ignore:.+PickledObjectField.from_db_value.+ diff --git a/tox.ini b/tox.ini index e1e9c111..07207b78 100644 --- a/tox.ini +++ b/tox.ini @@ -1,58 +1,32 @@ [tox] # We can't use "full product", since not all combinations are compatible envlist = - py{27,34}-django{17}-djcms{32} - py{27,34,35,36}-django{18,19}-djcms{32,33,34} - py{27,34,35,36}-django{110,111}-djcms{34} - py{35,36,37,38}-django{20,21,22} - py{36,37,38}-django{30} + py{37,38,39,310}-django{30,32} skip_missing_interpreters = true [travis] python = - 2.7: py27 - 3.4: py34 - 3.5: py35 - 3.6: py36 3.7: py37 3.8: py38 + 3.9: py39 + 3.10: py310 [testenv] deps = - djcms{32,33,34}: django-formtools<2.2 - djcms{32,33,34}: django-classy-tags<2 - djcms{32,33,34}: django-treebeard - djcms32: django-cms~=3.2.0 - djcms33: django-cms~=3.3.0 - djcms34: django-cms~=3.4.5 - - django17: Django>=1.7,<1.8 - django18: Django>=1.8,<1.9 - django19: Django>=1.9,<1.10 - django110: Django>=1.10,<1.11 - django111: Django>=1.11,<1.12 - django20: Django>=2.0,<2.1 - django21: Django>=2.1,<2.2 - django22: Django>=2.2,<2.3 django30: Django>=3.0,<3.1 + django32: Django>=3.2,<3.3 - django17: pytest-django>=3.1,<3.2 - django{18,19,110,111}: pytest-django<4 - django{20,21,22,30}: pytest-django - - django17: pytest<4 - django{18,19,110,111,20,21,22,30}: pytest - - django{17,18,19,110,111,20,21,22}: django-picklefield<0.4.0 - django{30}: django-picklefield - - py34: mysqlclient<1.4 - py{27,35,36}: mysqlclient + django{20,21,22,30,32}: pytest-django + django{30,32}: pytest + django{30,32}: django-picklefield + py{37,38,39,310}: mysqlclient pytest-cov pytz xlwt + pysqlite3 + django-cms commands = pip install pytest-django From 71b49c66afa4de017ca3cbe4885d2c57eb8bc43c Mon Sep 17 00:00:00 2001 From: William Lindholm Date: Wed, 15 Jun 2022 18:18:05 +0300 Subject: [PATCH 09/10] Fix code style with isort and flake8 --- .travis.yml | 7 +- README.rst | 2 +- dfd_tests/settings.py | 147 +++--- dfd_tests/urls.py | 8 +- dfd_tests/wsgi.py | 1 - form_designer/admin.py | 131 +++-- form_designer/apps.py | 2 +- .../form_designer_form/cms_plugins.py | 17 +- .../migrations/0001_initial.py | 50 +- .../cms_plugins/form_designer_form/models.py | 6 +- form_designer/contrib/exporters/__init__.py | 28 +- .../contrib/exporters/csv_exporter.py | 11 +- .../contrib/exporters/xls_exporter.py | 7 +- form_designer/email.py | 24 +- form_designer/fields.py | 31 +- form_designer/forms.py | 77 ++- form_designer/migrations/0001_initial.py | 469 ++++++++++++++--- form_designer/migrations/0002_reply_to.py | 14 +- .../migrations/0003_mail_cover_text.py | 25 +- form_designer/models.py | 471 +++++++++++------- form_designer/settings.py | 256 +++++++--- form_designer/templatetags/friendly.py | 3 +- form_designer/templatetags/widget_type.py | 2 +- form_designer/tests/conftest.py | 19 +- form_designer/tests/test_admin.py | 92 ++-- form_designer/tests/test_basics.py | 117 +++-- form_designer/tests/test_cms_plugin.py | 23 +- form_designer/tests/test_messages.py | 14 +- form_designer/uploads.py | 45 +- form_designer/urls.py | 12 +- form_designer/utils.py | 3 +- form_designer/views.py | 139 ++++-- setup.cfg | 9 +- setup.py | 41 +- 34 files changed, 1493 insertions(+), 810 deletions(-) diff --git a/.travis.yml b/.travis.yml index 486a3490..7fba961f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,9 @@ python: - "3.10" install: - pip install -U pip - - pip install 'tox>=3.4.0' tox-travis codecov -script: tox -vv -r + - pip install 'tox>=3.4.0' tox-travis codecov isort flake8 +script: + - tox -vv -r + - isort . + - flake8 . after_success: codecov -e TRAVIS_PYTHON_VERSION -e DATABASE diff --git a/README.rst b/README.rst index 0c4cceed..3d4afe20 100644 --- a/README.rst +++ b/README.rst @@ -7,7 +7,7 @@ Acknowledgements This project is a fork of https://github.com/samluescher/django-form-designer . Thanks, @samluescher! -This fork is compatible with Django 1.7+ and Python 2.7+. +This fork is compatible with Django 3+ and Python 3.7+. General ======= diff --git a/dfd_tests/settings.py b/dfd_tests/settings.py index db989d42..58c94ac9 100644 --- a/dfd_tests/settings.py +++ b/dfd_tests/settings.py @@ -1,4 +1,3 @@ -import django import os from tempfile import gettempdir @@ -8,118 +7,88 @@ cms = None BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -SECRET_KEY = '&nsa)3w(oz6^a1e-dj+iw9=jqps6az(&l2khgqtr)%%sj8ky@(' +SECRET_KEY = "&nsa)3w(oz6^a1e-dj+iw9=jqps6az(&l2khgqtr)%%sj8ky@(" DEBUG = True ALLOWED_HOSTS = [] -LANGUAGE_CODE = 'en' -LANGUAGES = [('en', 'en')] -TIME_ZONE = 'UTC' +LANGUAGE_CODE = "en" +LANGUAGES = [("en", "en")] +TIME_ZONE = "UTC" USE_I18N = True USE_L10N = True USE_TZ = True -STATIC_URL = '/static/' +STATIC_URL = "/static/" MEDIA_ROOT = gettempdir() -WSGI_APPLICATION = 'dfd_tests.wsgi.application' -ROOT_URLCONF = 'dfd_tests.urls' +WSGI_APPLICATION = "dfd_tests.wsgi.application" +ROOT_URLCONF = "dfd_tests.urls" SITE_ID = 1 INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'django.contrib.sites', - 'dfd_tests', - 'form_designer', + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "django.contrib.sites", + "dfd_tests", + "form_designer", ] if cms: - INSTALLED_APPS.extend([ - 'menus', - 'cms', - 'treebeard', - 'form_designer.contrib.cms_plugins.form_designer_form', - ]) + INSTALLED_APPS.extend( + [ + "menus", + "cms", + "treebeard", + "form_designer.contrib.cms_plugins.form_designer_form", + ] + ) CMS_TEMPLATES = [ - ('page.html', 'page'), + ("page.html", "page"), +] + +MIDDLEWARE = [ + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", ] -if django.VERSION[0] >= 2: - MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', - ] -elif django.VERSION[:2] < (1, 8): - MIDDLEWARE_CLASSES = ( - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', - ) - TEMPLATE_CONTEXT_PROCESSORS = ( - 'django.contrib.auth.context_processors.auth', - 'django.core.context_processors.debug', - 'django.core.context_processors.i18n', - 'django.core.context_processors.media', - 'django.core.context_processors.static', - 'django.core.context_processors.tz', - 'django.core.context_processors.request', - 'django.contrib.messages.context_processors.messages', - ) -else: - MIDDLEWARE_CLASSES = ( - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', - 'django.middleware.security.SecurityMiddleware', - ) -if django.VERSION[:2] >= (1, 8): - TEMPLATES = [ - { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', - ], - }, +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], }, - ] + }, +] DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": os.path.join(BASE_DIR, "db.sqlite3"), } } -if os.environ.get('DATABASE') == 'mysql': +if os.environ.get("DATABASE") == "mysql": DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.mysql', - 'NAME': 'dfd_tests', - 'USER': os.environ['DATABASE_USER'], - 'PASSWORD': os.environ.get('DATABASE_PASSWORD', ''), + "default": { + "ENGINE": "django.db.backends.mysql", + "NAME": "dfd_tests", + "USER": os.environ["DATABASE_USER"], + "PASSWORD": os.environ.get("DATABASE_PASSWORD", ""), } } diff --git a/dfd_tests/urls.py b/dfd_tests/urls.py index 932673b3..083932c6 100644 --- a/dfd_tests/urls.py +++ b/dfd_tests/urls.py @@ -1,10 +1,6 @@ -from django.conf.urls import include -from django.urls import path from django.contrib import admin -import django - -admin_urls = (admin.site.urls if (django.VERSION[0] >= 2) else include(admin.site.urls)) +from django.urls import path urlpatterns = [ - path('admin/', admin_urls), + path("admin/", admin.site.urls), ] diff --git a/dfd_tests/wsgi.py b/dfd_tests/wsgi.py index 221f1d42..ca185e10 100644 --- a/dfd_tests/wsgi.py +++ b/dfd_tests/wsgi.py @@ -8,7 +8,6 @@ """ import os - from django.core.wsgi import get_wsgi_application os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dfd_tests.settings") diff --git a/form_designer/admin.py b/form_designer/admin.py index 167e5b4f..eaeceaa4 100644 --- a/form_designer/admin.py +++ b/form_designer/admin.py @@ -1,13 +1,12 @@ -from django.urls import re_path from django.contrib import admin from django.http import Http404 +from django.urls import re_path, reverse from django.utils.module_loading import import_string from django.utils.translation import gettext_lazy as _ from form_designer import settings from form_designer.forms import FormDefinitionFieldInlineForm, FormDefinitionForm from form_designer.models import FormDefinition, FormDefinitionField, FormLog -from django.urls import reverse class FormDefinitionFieldInline(admin.StackedInline): @@ -15,38 +14,84 @@ class FormDefinitionFieldInline(admin.StackedInline): model = FormDefinitionField extra = 1 fieldsets = [ - (_('Basic'), {'fields': ['name', 'field_class', 'required', 'initial']}), - (_('Display'), {'fields': ['label', 'widget', 'help_text', 'position', 'include_result']}), - (_('Text'), {'fields': ['max_length', 'min_length']}), - (_('Numbers'), {'fields': ['max_value', 'min_value', 'max_digits', 'decimal_places']}), - (_('Regex'), {'fields': ['regex']}), - (_('Choices'), {'fields': ['choice_values', 'choice_labels']}), - (_('Model Choices'), {'fields': ['choice_model', 'choice_model_empty_label']}), + (_("Basic"), {"fields": ["name", "field_class", "required", "initial"]}), + ( + _("Display"), + {"fields": ["label", "widget", "help_text", "position", "include_result"]}, + ), + (_("Text"), {"fields": ["max_length", "min_length"]}), + ( + _("Numbers"), + {"fields": ["max_value", "min_value", "max_digits", "decimal_places"]}, + ), + (_("Regex"), {"fields": ["regex"]}), + (_("Choices"), {"fields": ["choice_values", "choice_labels"]}), + (_("Model Choices"), {"fields": ["choice_model", "choice_model_empty_label"]}), ] class FormDefinitionAdmin(admin.ModelAdmin): save_as = True fieldsets = [ - (_('Basic'), {'fields': ['name', 'require_hash', 'method', 'action', 'title', 'body']}), - (_('Settings'), {'fields': ['allow_get_initial', 'log_data', 'success_redirect', 'success_clear', 'display_logged', 'save_uploaded_files'], 'classes': ['collapse']}), - (_('Mail form'), {'fields': ['mail_to', 'mail_from', 'mail_subject', 'mail_uploaded_files', 'mail_cover_text'], 'classes': ['collapse']}), - (_('Templates'), {'fields': ['message_template', 'form_template_name'], 'classes': ['collapse']}), - (_('Messages'), {'fields': ['success_message', 'error_message', 'submit_label'], 'classes': ['collapse']}), + ( + _("Basic"), + {"fields": ["name", "require_hash", "method", "action", "title", "body"]}, + ), + ( + _("Settings"), + { + "fields": [ + "allow_get_initial", + "log_data", + "success_redirect", + "success_clear", + "display_logged", + "save_uploaded_files", + ], + "classes": ["collapse"], + }, + ), + ( + _("Mail form"), + { + "fields": [ + "mail_to", + "mail_from", + "mail_subject", + "mail_uploaded_files", + "mail_cover_text", + ], + "classes": ["collapse"], + }, + ), + ( + _("Templates"), + { + "fields": ["message_template", "form_template_name"], + "classes": ["collapse"], + }, + ), + ( + _("Messages"), + { + "fields": ["success_message", "error_message", "submit_label"], + "classes": ["collapse"], + }, + ), ] - list_display = ('name', 'title', 'method', 'count_fields') + list_display = ("name", "title", "method", "count_fields") form = FormDefinitionForm inlines = [ FormDefinitionFieldInline, ] - search_fields = ('name', 'title') + search_fields = ("name", "title") class FormLogAdmin(admin.ModelAdmin): - list_display = ('form_definition', 'created', 'id', 'created_by', 'data_html') - list_filter = ('form_definition',) + list_display = ("form_definition", "created", "id", "created_by", "data_html") + list_filter = ("form_definition",) list_display_links = None - date_hierarchy = 'created' + date_hierarchy = "created" exporter_classes = {} exporter_classes_ordered = [] @@ -63,7 +108,10 @@ def get_actions(self, request): actions = super(FormLogAdmin, self).get_actions(request) for cls in self.get_exporter_classes(): - desc = _("Export selected %%(verbose_name_plural)s as %s") % cls.export_format() + desc = ( + _("Export selected %%(verbose_name_plural)s as %s") + % cls.export_format() + ) actions[cls.export_format()] = (cls.export_view, cls.export_format(), desc) return actions @@ -71,38 +119,26 @@ def get_actions(self, request): def get_urls(self): urls = [ re_path( - r'^export/(?P[a-zA-Z0-9_-]+)/$', + r"^export/(?P[a-zA-Z0-9_-]+)/$", self.admin_site.admin_view(self.export_view), - name='form_designer_export' + name="form_designer_export", ), ] return urls + super(FormLogAdmin, self).get_urls() def data_html(self, obj): - return obj.form_definition.compile_message(obj.data, 'html/formdefinition/data_message.html') + return obj.form_definition.compile_message( + obj.data, "html/formdefinition/data_message.html" + ) + data_html.allow_tags = True - data_html.short_description = _('Data') + data_html.short_description = _("Data") def get_change_list_query_set(self, request, extra_context=None): """ The 'change list' admin view for this model. """ - if hasattr(self, 'get_changelist_instance'): # Available on Django 2.0+ - cl = self.get_changelist_instance(request) - else: - list_display = self.get_list_display(request) - list_display_links = self.get_list_display_links(request, list_display) - list_filter = self.get_list_filter(request) - ChangeList = self.get_changelist(request) - - cl = ChangeList(request, self.model, list_display, - list_display_links, list_filter, self.date_hierarchy, - self.search_fields, self.list_select_related, - self.list_per_page, self.list_max_show_all, self.list_editable, - self) - - if hasattr(cl, "get_query_set"): # Old Django versions - return cl.get_query_set(request) + cl = self.get_changelist_instance(request) return cl.get_queryset(request) def export_view(self, request, format): @@ -113,13 +149,18 @@ def export_view(self, request, format): def changelist_view(self, request, extra_context=None): extra_context = extra_context or {} - query_string = '?' + request.META.get('QUERY_STRING', '') + query_string = "?" + request.META.get("QUERY_STRING", "") exporter_links = [] for cls in self.get_exporter_classes(): - url = reverse('admin:form_designer_export', args=(cls.export_format(),)) + query_string - exporter_links.append({'url': url, 'label': _('Export view as %s') % cls.export_format()}) - - extra_context['exporters'] = exporter_links + url = ( + reverse("admin:form_designer_export", args=(cls.export_format(),)) + + query_string + ) + exporter_links.append( + {"url": url, "label": _("Export view as %s") % cls.export_format()} + ) + + extra_context["exporters"] = exporter_links return super(FormLogAdmin, self).changelist_view(request, extra_context) diff --git a/form_designer/apps.py b/form_designer/apps.py index 1d49ab53..8f5f9d06 100644 --- a/form_designer/apps.py +++ b/form_designer/apps.py @@ -3,5 +3,5 @@ class FormDesignerConfig(AppConfig): - name = 'form_designer' + name = "form_designer" verbose_name = _("Form Designer") diff --git a/form_designer/contrib/cms_plugins/form_designer_form/cms_plugins.py b/form_designer/contrib/cms_plugins/form_designer_form/cms_plugins.py index f665edce..7a955b0f 100644 --- a/form_designer/contrib/cms_plugins/form_designer_form/cms_plugins.py +++ b/form_designer/contrib/cms_plugins/form_designer_form/cms_plugins.py @@ -1,7 +1,7 @@ -from django.utils.translation import gettext_lazy as _ - from cms.plugin_base import CMSPluginBase from cms.plugin_pool import plugin_pool +from django.utils.translation import gettext_lazy as _ + from form_designer import settings from form_designer.contrib.cms_plugins.form_designer_form.models import CMSFormDefinition from form_designer.views import process_form @@ -9,8 +9,8 @@ class FormDesignerPlugin(CMSPluginBase): model = CMSFormDefinition - module = _('Form Designer') - name = _('Form') + module = _("Form Designer") + name = _("Form") admin_preview = False render_template = False cache = False # New in version 3.0. see http://django-cms.readthedocs.org/en/latest/advanced/caching.html @@ -22,8 +22,13 @@ def render(self, context, instance, placeholder): self.render_template = settings.DEFAULT_FORM_TEMPLATE # Redirection does not work with CMS plugin, hence disable: - return process_form(context['request'], instance.form_definition, context, disable_redirection=True, - push_messages=settings.PUSH_MESSAGES) + return process_form( + context["request"], + instance.form_definition, + context, + disable_redirection=True, + push_messages=settings.PUSH_MESSAGES, + ) plugin_pool.register_plugin(FormDesignerPlugin) diff --git a/form_designer/contrib/cms_plugins/form_designer_form/migrations/0001_initial.py b/form_designer/contrib/cms_plugins/form_designer_form/migrations/0001_initial.py index b13048aa..f921d799 100644 --- a/form_designer/contrib/cms_plugins/form_designer_form/migrations/0001_initial.py +++ b/form_designer/contrib/cms_plugins/form_designer_form/migrations/0001_initial.py @@ -5,42 +5,46 @@ # Django CMS 3.3.1 is oldest release where the change affects. # Refs https://github.com/divio/django-cms/commit/871a164 -if V(cms.__version__) >= V('3.3.1'): - field_kwargs = {'related_name': 'form_designer_form_cmsformdefinition'} +if V(cms.__version__) >= V("3.3.1"): + field_kwargs = {"related_name": "form_designer_form_cmsformdefinition"} else: field_kwargs = {} class Migration(migrations.Migration): dependencies = [ - ('cms', '0001_initial'), - ('form_designer', '0001_initial'), + ("cms", "0001_initial"), + ("form_designer", "0001_initial"), ] operations = [ migrations.CreateModel( - name='CMSFormDefinition', + name="CMSFormDefinition", fields=[ - ('cmsplugin_ptr', - models.OneToOneField( - serialize=False, - auto_created=True, - primary_key=True, - to='cms.CMSPlugin', - parent_link=True, - on_delete=models.CASCADE, - **field_kwargs)), - ('form_definition', - models.ForeignKey( - verbose_name='form', - to='form_designer.FormDefinition', - on_delete=models.CASCADE)), + ( + "cmsplugin_ptr", + models.OneToOneField( + serialize=False, + auto_created=True, + primary_key=True, + to="cms.CMSPlugin", + parent_link=True, + on_delete=models.CASCADE, + **field_kwargs + ), + ), + ( + "form_definition", + models.ForeignKey( + verbose_name="form", + to="form_designer.FormDefinition", + on_delete=models.CASCADE, + ), + ), ], options={ - 'abstract': False, + "abstract": False, }, - bases=( - 'cms.cmsplugin', - ), + bases=("cms.cmsplugin",), ), ] diff --git a/form_designer/contrib/cms_plugins/form_designer_form/models.py b/form_designer/contrib/cms_plugins/form_designer_form/models.py index 27e9ef11..a3788b6a 100644 --- a/form_designer/contrib/cms_plugins/form_designer_form/models.py +++ b/form_designer/contrib/cms_plugins/form_designer_form/models.py @@ -1,13 +1,15 @@ +from cms.models import CMSPlugin from django.db import models from django.utils.encoding import force_text from django.utils.translation import gettext_lazy as _ -from cms.models import CMSPlugin from form_designer.models import FormDefinition class CMSFormDefinition(CMSPlugin): - form_definition = models.ForeignKey(FormDefinition, verbose_name=_('form'), on_delete=models.CASCADE) + form_definition = models.ForeignKey( + FormDefinition, verbose_name=_("form"), on_delete=models.CASCADE + ) def __str__(self): return force_text(self.form_definition) diff --git a/form_designer/contrib/exporters/__init__.py b/form_designer/contrib/exporters/__init__.py index 63230173..95ceba18 100644 --- a/form_designer/contrib/exporters/__init__.py +++ b/form_designer/contrib/exporters/__init__.py @@ -7,7 +7,6 @@ class ExporterBase(object): - def __init__(self, model): self.model = model @@ -40,11 +39,12 @@ def export(self, request, queryset=None): class FormLogExporterBase(ExporterBase): - - def export(self, request, queryset=None): + def export(self, request, queryset=None): # noqa: C901 self.init_response() self.init_writer() - distinct_forms = queryset.aggregate(Count('form_definition', distinct=True))['form_definition__count'] + distinct_forms = queryset.aggregate(Count("form_definition", distinct=True))[ + "form_definition__count" + ] include_created = settings.CSV_EXPORT_INCLUDE_CREATED include_pk = settings.CSV_EXPORT_INCLUDE_PK @@ -57,11 +57,11 @@ def export(self, request, queryset=None): if include_header: header = [] if include_form: - header.append(_('Form')) + header.append(_("Form")) if include_created: - header.append(_('Created')) + header.append(_("Created")) if include_pk: - header.append(_('ID')) + header.append(_("ID")) # Form fields might have been changed and not match # existing form logs anymore. # Hence, use current form definition for header. @@ -70,7 +70,12 @@ def export(self, request, queryset=None): for field_name, field in fields.items(): header.append(field.label or field.name) - self.writerow([force_text(cell, encoding=settings.CSV_EXPORT_ENCODING) for cell in header]) + self.writerow( + [ + force_text(cell, encoding=settings.CSV_EXPORT_ENCODING) + for cell in header + ] + ) for entry in queryset: row = [] @@ -80,9 +85,12 @@ def export(self, request, queryset=None): row.append(entry.created) if include_pk: row.append(entry.pk) - name_to_value = {d['name']: d['value'] for d in entry.data} + name_to_value = {d["name"]: d["value"] for d in entry.data} for field in field_order: - value = friendly(name_to_value.get(field), null_value=settings.CSV_EXPORT_NULL_VALUE) + value = friendly( + name_to_value.get(field), + null_value=settings.CSV_EXPORT_NULL_VALUE, + ) value = force_text(value, encoding=settings.CSV_EXPORT_ENCODING) row.append(value) diff --git a/form_designer/contrib/exporters/csv_exporter.py b/form_designer/contrib/exporters/csv_exporter.py index 14f98ae4..8f789e7c 100644 --- a/form_designer/contrib/exporters/csv_exporter.py +++ b/form_designer/contrib/exporters/csv_exporter.py @@ -1,25 +1,22 @@ import csv - from django.http import HttpResponse -from django.utils.encoding import force_bytes from form_designer import settings from form_designer.contrib.exporters import FormLogExporterBase class CsvExporter(FormLogExporterBase): - @staticmethod def export_format(): - return 'CSV' + return "CSV" def init_writer(self): self.writer = csv.writer(self.response, delimiter=settings.CSV_EXPORT_DELIMITER) def init_response(self): - self.response = HttpResponse(content_type='text/csv') - self.response['Content-Disposition'] = ( - 'attachment; filename=%s.csv' % self.model._meta.verbose_name_plural + self.response = HttpResponse(content_type="text/csv") + self.response["Content-Disposition"] = ( + "attachment; filename=%s.csv" % self.model._meta.verbose_name_plural ) def writerow(self, row): diff --git a/form_designer/contrib/exporters/xls_exporter.py b/form_designer/contrib/exporters/xls_exporter.py index ecf8340b..e151b4e9 100644 --- a/form_designer/contrib/exporters/xls_exporter.py +++ b/form_designer/contrib/exporters/xls_exporter.py @@ -12,10 +12,9 @@ class XlsExporter(FormLogExporterBase): - @staticmethod def export_format(): - return 'XLS' + return "XLS" @staticmethod def is_enabled(): @@ -27,8 +26,8 @@ def init_writer(self): self.rownum = 0 def init_response(self): - self.response = HttpResponse(content_type='application/ms-excel') - self.response['Content-Disposition'] = 'attachment; filename=%s.xls' % ( + self.response = HttpResponse(content_type="application/ms-excel") + self.response["Content-Disposition"] = "attachment; filename=%s.xls" % ( self.model._meta.verbose_name_plural ) diff --git a/form_designer/email.py b/form_designer/email.py index 0022862c..1347580d 100644 --- a/form_designer/email.py +++ b/form_designer/email.py @@ -1,13 +1,9 @@ import re - -import django from django.core.mail import EmailMessage from django.utils.encoding import force_text from form_designer.utils import string_template_replace -DJANGO_18 = django.VERSION[:2] >= (1, 8) - def _template_replace_list(input_str, context_dict): """ @@ -25,8 +21,7 @@ def _template_replace_list(input_str, context_dict): return [] return [ string_template_replace(email, context_dict) - for email - in re.compile(r'\s*[,;]+\s*').split(force_text(input_str)) + for email in re.compile(r"\s*[,;]+\s*").split(force_text(input_str)) ] @@ -55,25 +50,20 @@ def build_form_mail(form_definition, form, files=None): reply_to = _template_replace_list(form_definition.mail_reply_to, context_dict) mail_subject = string_template_replace( - (form_definition.mail_subject or form_definition.title), - context_dict + (form_definition.mail_subject or form_definition.title), context_dict ) kwargs = { - 'subject': mail_subject, - 'body': message, - 'from_email': from_email, - 'to': mail_to, + "subject": mail_subject, + "body": message, + "from_email": from_email, + "to": mail_to, } - if DJANGO_18: # the reply_to kwarg is only supported in Django 1.8+ . . . - kwargs['reply_to'] = reply_to + kwargs["reply_to"] = reply_to message = EmailMessage(**kwargs) - if not DJANGO_18: # so do it manually when not on Django 1.8 - message.extra_headers['Reply-To'] = ', '.join(map(force_text, reply_to)) - if form_definition.is_template_html: message.content_subtype = "html" diff --git a/form_designer/fields.py b/form_designer/fields.py index 327d3221..7aa1cd4d 100644 --- a/form_designer/fields.py +++ b/form_designer/fields.py @@ -7,11 +7,10 @@ class ModelNameFormField(forms.CharField): - @staticmethod def get_model_from_string(model_path): try: - app_label, model_name = model_path.rsplit('.models.') + app_label, model_name = model_path.rsplit(".models.") return apps.get_model(app_label, model_name) except (ValueError, LookupError): return None @@ -23,18 +22,19 @@ def clean(self, value): """ value = super(ModelNameFormField, self).clean(value) if value in validators.EMPTY_VALUES: - return'' + return "" if not ModelNameFormField.get_model_from_string(value): raise ValidationError( - _('Model could not be imported: %(value)s. Please use a valid model path.'), - code='invalid', - params={'value': value}, + _( + "Model could not be imported: %(value)s. Please use a valid model path." + ), + code="invalid", + params={"value": value}, ) return value class ModelNameField(models.CharField): - @staticmethod def get_model_from_string(model_path): return ModelNameFormField.get_model_from_string(model_path) @@ -42,19 +42,19 @@ def get_model_from_string(model_path): def formfield(self, **kwargs): # This is a fairly standard way to set up some defaults # while letting the caller override them. - defaults = {'form_class': ModelNameFormField} + defaults = {"form_class": ModelNameFormField} defaults.update(kwargs) return super(ModelNameField, self).formfield(**defaults) class TemplateFormField(forms.CharField): - def clean(self, value): """ Validates that the input can be compiled as a template. """ value = super(TemplateFormField, self).clean(value) from django.template import Template, TemplateSyntaxError + if value: try: Template(value) @@ -64,35 +64,33 @@ def clean(self, value): class TemplateCharField(models.CharField): - def formfield(self, **kwargs): # This is a fairly standard way to set up some defaults # while letting the caller override them. - defaults = {'form_class': TemplateFormField} + defaults = {"form_class": TemplateFormField} defaults.update(kwargs) return super(TemplateCharField, self).formfield(**defaults) class TemplateTextField(models.TextField): - def formfield(self, **kwargs): # This is a fairly standard way to set up some defaults # while letting the caller override them. - defaults = {'form_class': TemplateFormField} + defaults = {"form_class": TemplateFormField} defaults.update(kwargs) return super(TemplateTextField, self).formfield(**defaults) class RegexpExpressionFormField(forms.CharField): - def clean(self, value): """ Validates that the input can be compiled as a Regular Expression. """ value = super(RegexpExpressionFormField, self).clean(value) if value in validators.EMPTY_VALUES: - value ='' + value = "" import re + try: re.compile(value) except Exception as error: @@ -101,10 +99,9 @@ def clean(self, value): class RegexpExpressionField(models.CharField): - def formfield(self, **kwargs): # This is a fairly standard way to set up some defaults # while letting the caller override them. - defaults = {'form_class': RegexpExpressionFormField} + defaults = {"form_class": RegexpExpressionFormField} defaults.update(kwargs) return super(RegexpExpressionField, self).formfield(**defaults) diff --git a/form_designer/forms.py b/form_designer/forms.py index 1eadb54f..1d39b092 100644 --- a/form_designer/forms.py +++ b/form_designer/forms.py @@ -1,5 +1,4 @@ import os - from django import forms from django.conf import settings as django_settings from django.forms import widgets @@ -13,21 +12,27 @@ class DesignedForm(forms.Form): - def __init__(self, form_definition, initial_data=None, *args, **kwargs): super(DesignedForm, self).__init__(*args, **kwargs) self.file_fields = [] for def_field in form_definition.formdefinitionfield_set.all(): self.add_defined_field(def_field, initial_data) - self.fields[form_definition.submit_flag_name] = forms.BooleanField(required=False, initial=1, widget=widgets.HiddenInput) + self.fields[form_definition.submit_flag_name] = forms.BooleanField( + required=False, initial=1, widget=widgets.HiddenInput + ) def add_defined_field(self, def_field, initial_data=None): if initial_data and def_field.name in initial_data: - if not def_field.field_class in ('django.forms.MultipleChoiceField', 'django.forms.ModelMultipleChoiceField'): + if def_field.field_class not in ( + "django.forms.MultipleChoiceField", + "django.forms.ModelMultipleChoiceField", + ): def_field.initial = initial_data.get(def_field.name) else: def_field.initial = initial_data.getlist(def_field.name) - field = import_string(def_field.field_class)(**def_field.get_form_field_init_args()) + field = import_string(def_field.field_class)( + **def_field.get_form_field_init_args() + ) self.fields[def_field.name] = field if isinstance(field, forms.FileField): self.file_fields.append(def_field) @@ -42,21 +47,37 @@ class Meta: exclude = () def clean_regex(self): - if not self.cleaned_data['regex'] and 'field_class' in self.cleaned_data and self.cleaned_data['field_class'] in ('django.forms.RegexField',): - raise forms.ValidationError(_('This field class requires a regular expression.')) - return self.cleaned_data['regex'] + if ( + not self.cleaned_data["regex"] + and "field_class" in self.cleaned_data + and self.cleaned_data["field_class"] in ("django.forms.RegexField",) + ): + raise forms.ValidationError( + _("This field class requires a regular expression.") + ) + return self.cleaned_data["regex"] def clean_choice_model(self): - if not self.cleaned_data['choice_model'] and 'field_class' in self.cleaned_data and self.cleaned_data['field_class'] in ('django.forms.ModelChoiceField', 'django.forms.ModelMultipleChoiceField'): - raise forms.ValidationError(_('This field class requires a model.')) - return self.cleaned_data['choice_model'] + if ( + not self.cleaned_data["choice_model"] + and "field_class" in self.cleaned_data + and self.cleaned_data["field_class"] + in ( + "django.forms.ModelChoiceField", + "django.forms.ModelMultipleChoiceField", + ) + ): + raise forms.ValidationError(_("This field class requires a model.")) + return self.cleaned_data["choice_model"] def __init__(self, data=None, files=None, **kwargs): - super(FormDefinitionFieldInlineForm, self).__init__(data=data, files=files, **kwargs) + super(FormDefinitionFieldInlineForm, self).__init__( + data=data, files=files, **kwargs + ) for field_name, choices in ( - ('field_class', settings.FIELD_CLASSES), - ('widget', settings.WIDGET_CLASSES), - ('choice_model', settings.CHOICE_MODEL_CHOICES), + ("field_class", settings.FIELD_CLASSES), + ("widget", settings.WIDGET_CLASSES), + ("choice_model", settings.CHOICE_MODEL_CHOICES), ): if choices is None: continue @@ -74,23 +95,25 @@ class Meta: def _media(self): js = [] plugins = [ - 'js/jquery-ui.js', - 'js/jquery-inline-positioning.js', - 'js/jquery-inline-rename.js', - 'js/jquery-inline-collapsible.js', - 'js/jquery-inline-fieldset-collapsible.js', - 'js/jquery-inline-prepopulate-label.js', + "js/jquery-ui.js", + "js/jquery-inline-positioning.js", + "js/jquery-inline-rename.js", + "js/jquery-inline-collapsible.js", + "js/jquery-inline-fieldset-collapsible.js", + "js/jquery-inline-prepopulate-label.js", ] - if hasattr(django_settings, 'JQUERY_URL'): + if hasattr(django_settings, "JQUERY_URL"): js.append(django_settings.JQUERY_URL) else: - plugins = ['js/jquery.js'] + plugins - js.extend( - [os.path.join(settings.STATIC_URL, path) for path in plugins]) + plugins = ["js/jquery.js"] + plugins + js.extend([os.path.join(settings.STATIC_URL, path) for path in plugins]) return forms.Media(js=js) + media = property(_media) def __init__(self, data=None, files=None, **kwargs): super(FormDefinitionForm, self).__init__(data=data, files=files, **kwargs) - if 'form_template_name' in self.fields: - self.fields['form_template_name'].widget = Select(choices=settings.FORM_TEMPLATES) + if "form_template_name" in self.fields: + self.fields["form_template_name"].widget = Select( + choices=settings.FORM_TEMPLATES + ) diff --git a/form_designer/migrations/0001_initial.py b/form_designer/migrations/0001_initial.py index 7a5d7c0d..068b445c 100644 --- a/form_designer/migrations/0001_initial.py +++ b/form_designer/migrations/0001_initial.py @@ -1,10 +1,11 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.6 on 2016-05-19 07:05 +import django.db.models.deletion +import picklefield.fields from django.conf import settings from django.db import migrations, models -import django.db.models.deletion + import form_designer.fields -import picklefield.fields class Migration(migrations.Migration): @@ -17,90 +18,422 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='FormDefinition', + name="FormDefinition", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.SlugField(max_length=255, unique=True, verbose_name='name')), - ('require_hash', models.BooleanField(default=False, help_text='If enabled, the form can only be reached via a secret URL.', verbose_name='obfuscate URL to this form')), - ('private_hash', models.CharField(default='', editable=False, max_length=40)), - ('public_hash', models.CharField(default='', editable=False, max_length=40)), - ('title', models.CharField(blank=True, max_length=255, null=True, verbose_name='title')), - ('body', models.TextField(blank=True, null=True, verbose_name='body', help_text='Form description. Display on form after title.')), - ('action', models.URLField(blank=True, help_text='If you leave this empty, the page where the form resides will be requested, and you can use the mail form and logging features. You can also send data to external sites: For instance, enter "http://www.google.ch/search" to create a search form.', max_length=255, null=True, verbose_name='target URL')), - ('mail_to', form_designer.fields.TemplateCharField(blank=True, help_text='Separate several addresses with a comma. Your form fields are available as template context. Example: "admin@domain.com, {{ from_email }}" if you have a field named `from_email`.', max_length=255, null=True, verbose_name='send form data to e-mail address')), - ('mail_from', form_designer.fields.TemplateCharField(blank=True, help_text='Your form fields are available as template context. Example: "{{ first_name }} {{ last_name }} <{{ from_email }}>" if you have fields named `first_name`, `last_name`, `from_email`.', max_length=255, null=True, verbose_name='sender address')), - ('mail_subject', form_designer.fields.TemplateCharField(blank=True, help_text='Your form fields are available as template context. Example: "Contact form {{ subject }}" if you have a field named `subject`.', max_length=255, null=True, verbose_name='email subject')), - ('mail_uploaded_files', models.BooleanField(default=True, verbose_name='Send uploaded files as email attachments')), - ('method', models.CharField(choices=[('POST', 'POST'), ('GET', 'GET')], default='POST', max_length=10, verbose_name='method')), - ('success_message', models.CharField(blank=True, max_length=255, null=True, verbose_name='success message')), - ('error_message', models.CharField(blank=True, max_length=255, null=True, verbose_name='error message')), - ('submit_label', models.CharField(blank=True, max_length=255, null=True, verbose_name='submit button label')), - ('log_data', models.BooleanField(default=True, help_text='Logs all form submissions to the database.', verbose_name='log form data')), - ('save_uploaded_files', models.BooleanField(default=True, help_text='Saves all uploaded files using server storage.', verbose_name='save uploaded files')), - ('success_redirect', models.BooleanField(default=True, verbose_name='HTTP redirect after successful submission')), - ('success_clear', models.BooleanField(default=True, verbose_name='clear form after successful submission')), - ('allow_get_initial', models.BooleanField(default=True, help_text='If enabled, you can fill in form fields by adding them to the query string.', verbose_name='allow initial values via URL')), - ('message_template', form_designer.fields.TemplateTextField(blank=True, help_text='Your form fields are available as template context. Example: "{{ message }}" if you have a field named `message`. To iterate over all fields, use the variable `data` (a list containing a dictionary for each form field, each containing the elements `name`, `label`, `value`).', null=True, verbose_name='message template')), - ('form_template_name', models.CharField(blank=True, max_length=255, null=True, verbose_name='form template')), - ('display_logged', models.BooleanField(default=False, verbose_name='display logged submissions with form')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "name", + models.SlugField(max_length=255, unique=True, verbose_name="name"), + ), + ( + "require_hash", + models.BooleanField( + default=False, + help_text="If enabled, the form can only be reached via a secret URL.", + verbose_name="obfuscate URL to this form", + ), + ), + ( + "private_hash", + models.CharField(default="", editable=False, max_length=40), + ), + ( + "public_hash", + models.CharField(default="", editable=False, max_length=40), + ), + ( + "title", + models.CharField( + blank=True, max_length=255, null=True, verbose_name="title" + ), + ), + ( + "body", + models.TextField( + blank=True, + null=True, + verbose_name="body", + help_text="Form description. Display on form after title.", + ), + ), + ( + "action", + models.URLField( + blank=True, + help_text='If you leave this empty, the page where the form resides will be requested, and you can use the mail form and logging features. You can also send data to external sites: For instance, enter "http://www.google.ch/search" to create a search form.', + max_length=255, + null=True, + verbose_name="target URL", + ), + ), + ( + "mail_to", + form_designer.fields.TemplateCharField( + blank=True, + help_text='Separate several addresses with a comma. Your form fields are available as template context. Example: "admin@domain.com, {{ from_email }}" if you have a field named `from_email`.', + max_length=255, + null=True, + verbose_name="send form data to e-mail address", + ), + ), + ( + "mail_from", + form_designer.fields.TemplateCharField( + blank=True, + help_text='Your form fields are available as template context. Example: "{{ first_name }} {{ last_name }} <{{ from_email }}>" if you have fields named `first_name`, `last_name`, `from_email`.', + max_length=255, + null=True, + verbose_name="sender address", + ), + ), + ( + "mail_subject", + form_designer.fields.TemplateCharField( + blank=True, + help_text='Your form fields are available as template context. Example: "Contact form {{ subject }}" if you have a field named `subject`.', + max_length=255, + null=True, + verbose_name="email subject", + ), + ), + ( + "mail_uploaded_files", + models.BooleanField( + default=True, + verbose_name="Send uploaded files as email attachments", + ), + ), + ( + "method", + models.CharField( + choices=[("POST", "POST"), ("GET", "GET")], + default="POST", + max_length=10, + verbose_name="method", + ), + ), + ( + "success_message", + models.CharField( + blank=True, + max_length=255, + null=True, + verbose_name="success message", + ), + ), + ( + "error_message", + models.CharField( + blank=True, + max_length=255, + null=True, + verbose_name="error message", + ), + ), + ( + "submit_label", + models.CharField( + blank=True, + max_length=255, + null=True, + verbose_name="submit button label", + ), + ), + ( + "log_data", + models.BooleanField( + default=True, + help_text="Logs all form submissions to the database.", + verbose_name="log form data", + ), + ), + ( + "save_uploaded_files", + models.BooleanField( + default=True, + help_text="Saves all uploaded files using server storage.", + verbose_name="save uploaded files", + ), + ), + ( + "success_redirect", + models.BooleanField( + default=True, + verbose_name="HTTP redirect after successful submission", + ), + ), + ( + "success_clear", + models.BooleanField( + default=True, + verbose_name="clear form after successful submission", + ), + ), + ( + "allow_get_initial", + models.BooleanField( + default=True, + help_text="If enabled, you can fill in form fields by adding them to the query string.", + verbose_name="allow initial values via URL", + ), + ), + ( + "message_template", + form_designer.fields.TemplateTextField( + blank=True, + help_text='Your form fields are available as template context. Example: "{{ message }}" if you have a field named `message`. To iterate over all fields, use the variable `data` (a list containing a dictionary for each form field, each containing the elements `name`, `label`, `value`).', + null=True, + verbose_name="message template", + ), + ), + ( + "form_template_name", + models.CharField( + blank=True, + max_length=255, + null=True, + verbose_name="form template", + ), + ), + ( + "display_logged", + models.BooleanField( + default=False, + verbose_name="display logged submissions with form", + ), + ), ], options={ - 'verbose_name': 'Form', - 'verbose_name_plural': 'Forms', + "verbose_name": "Form", + "verbose_name_plural": "Forms", }, ), migrations.CreateModel( - name='FormDefinitionField', + name="FormDefinitionField", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('field_class', models.CharField(max_length=100, verbose_name='field class')), - ('position', models.IntegerField(blank=True, null=True, verbose_name='position')), - ('name', models.SlugField(max_length=255, verbose_name='name')), - ('label', models.CharField(blank=True, max_length=255, null=True, verbose_name='label')), - ('required', models.BooleanField(default=True, verbose_name='required')), - ('include_result', models.BooleanField(default=True, help_text='If this is disabled, the field value will not be included in logs and e-mails generated from form data.', verbose_name='include in result')), - ('widget', models.CharField(blank=True, default='', max_length=255, null=True, verbose_name='widget')), - ('initial', models.TextField(blank=True, null=True, verbose_name='initial value')), - ('help_text', models.CharField(blank=True, max_length=255, null=True, verbose_name='help text')), - ('choice_values', models.TextField(blank=True, help_text='One value per line', null=True, verbose_name='values')), - ('choice_labels', models.TextField(blank=True, help_text='One label per line', null=True, verbose_name='labels')), - ('max_length', models.IntegerField(blank=True, null=True, verbose_name='max. length')), - ('min_length', models.IntegerField(blank=True, null=True, verbose_name='min. length')), - ('max_value', models.FloatField(blank=True, null=True, verbose_name='max. value')), - ('min_value', models.FloatField(blank=True, null=True, verbose_name='min. value')), - ('max_digits', models.IntegerField(blank=True, null=True, verbose_name='max. digits')), - ('decimal_places', models.IntegerField(blank=True, null=True, verbose_name='decimal places')), - ('regex', form_designer.fields.RegexpExpressionField(blank=True, max_length=255, null=True, verbose_name='regular Expression')), - ('choice_model', form_designer.fields.ModelNameField(blank=True, max_length=255, null=True, verbose_name='data model')), - ('choice_model_empty_label', models.CharField(blank=True, max_length=255, null=True, verbose_name='empty label')), - ('form_definition', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='form_designer.FormDefinition')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "field_class", + models.CharField(max_length=100, verbose_name="field class"), + ), + ( + "position", + models.IntegerField(blank=True, null=True, verbose_name="position"), + ), + ("name", models.SlugField(max_length=255, verbose_name="name")), + ( + "label", + models.CharField( + blank=True, max_length=255, null=True, verbose_name="label" + ), + ), + ( + "required", + models.BooleanField(default=True, verbose_name="required"), + ), + ( + "include_result", + models.BooleanField( + default=True, + help_text="If this is disabled, the field value will not be included in logs and e-mails generated from form data.", + verbose_name="include in result", + ), + ), + ( + "widget", + models.CharField( + blank=True, + default="", + max_length=255, + null=True, + verbose_name="widget", + ), + ), + ( + "initial", + models.TextField( + blank=True, null=True, verbose_name="initial value" + ), + ), + ( + "help_text", + models.CharField( + blank=True, max_length=255, null=True, verbose_name="help text" + ), + ), + ( + "choice_values", + models.TextField( + blank=True, + help_text="One value per line", + null=True, + verbose_name="values", + ), + ), + ( + "choice_labels", + models.TextField( + blank=True, + help_text="One label per line", + null=True, + verbose_name="labels", + ), + ), + ( + "max_length", + models.IntegerField( + blank=True, null=True, verbose_name="max. length" + ), + ), + ( + "min_length", + models.IntegerField( + blank=True, null=True, verbose_name="min. length" + ), + ), + ( + "max_value", + models.FloatField(blank=True, null=True, verbose_name="max. value"), + ), + ( + "min_value", + models.FloatField(blank=True, null=True, verbose_name="min. value"), + ), + ( + "max_digits", + models.IntegerField( + blank=True, null=True, verbose_name="max. digits" + ), + ), + ( + "decimal_places", + models.IntegerField( + blank=True, null=True, verbose_name="decimal places" + ), + ), + ( + "regex", + form_designer.fields.RegexpExpressionField( + blank=True, + max_length=255, + null=True, + verbose_name="regular Expression", + ), + ), + ( + "choice_model", + form_designer.fields.ModelNameField( + blank=True, max_length=255, null=True, verbose_name="data model" + ), + ), + ( + "choice_model_empty_label", + models.CharField( + blank=True, + max_length=255, + null=True, + verbose_name="empty label", + ), + ), + ( + "form_definition", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="form_designer.FormDefinition", + ), + ), ], options={ - 'verbose_name': 'field', - 'verbose_name_plural': 'fields', - 'ordering': ['position'], + "verbose_name": "field", + "verbose_name_plural": "fields", + "ordering": ["position"], }, ), migrations.CreateModel( - name='FormLog', + name="FormLog", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', models.DateTimeField(auto_now=True, verbose_name='Created')), - ('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ('form_definition', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='logs', to='form_designer.FormDefinition')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "created", + models.DateTimeField(auto_now=True, verbose_name="Created"), + ), + ( + "created_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "form_definition", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="logs", + to="form_designer.FormDefinition", + ), + ), ], options={ - 'verbose_name': 'form log', - 'verbose_name_plural': 'form logs', - } + "verbose_name": "form log", + "verbose_name_plural": "form logs", + }, ), migrations.CreateModel( - name='FormValue', + name="FormValue", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('field_name', models.SlugField(max_length=255, verbose_name='field name')), - ('value', picklefield.fields.PickledObjectField(blank=True, editable=False, null=True, verbose_name='value')), - ('form_log', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='values', to='form_designer.FormLog')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "field_name", + models.SlugField(max_length=255, verbose_name="field name"), + ), + ( + "value", + picklefield.fields.PickledObjectField( + blank=True, editable=False, null=True, verbose_name="value" + ), + ), + ( + "form_log", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="values", + to="form_designer.FormLog", + ), + ), ], ), ] diff --git a/form_designer/migrations/0002_reply_to.py b/form_designer/migrations/0002_reply_to.py index c1291e6d..a0e3cb0f 100644 --- a/form_designer/migrations/0002_reply_to.py +++ b/form_designer/migrations/0002_reply_to.py @@ -1,19 +1,25 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2016-06-06 06:50 from django.db import migrations + import form_designer.fields class Migration(migrations.Migration): dependencies = [ - ('form_designer', '0001_initial'), + ("form_designer", "0001_initial"), ] operations = [ migrations.AddField( - model_name='formdefinition', - name='mail_reply_to', - field=form_designer.fields.TemplateCharField(blank=True, help_text='Your form fields are available as template context. Example: "{{ first_name }} {{ last_name }} <{{ from_email }}>" if you have fields named `first_name`, `last_name`, `from_email`.', max_length=255, verbose_name='reply-to address'), + model_name="formdefinition", + name="mail_reply_to", + field=form_designer.fields.TemplateCharField( + blank=True, + help_text='Your form fields are available as template context. Example: "{{ first_name }} {{ last_name }} <{{ from_email }}>" if you have fields named `first_name`, `last_name`, `from_email`.', + max_length=255, + verbose_name="reply-to address", + ), ), ] diff --git a/form_designer/migrations/0003_mail_cover_text.py b/form_designer/migrations/0003_mail_cover_text.py index d11d30c0..7653dca2 100644 --- a/form_designer/migrations/0003_mail_cover_text.py +++ b/form_designer/migrations/0003_mail_cover_text.py @@ -1,23 +1,34 @@ # -*- coding: utf-8 -*- from django.db import migrations, models + import form_designer.fields class Migration(migrations.Migration): dependencies = [ - ('form_designer', '0002_reply_to'), + ("form_designer", "0002_reply_to"), ] operations = [ migrations.AddField( - model_name='formdefinition', - name='mail_cover_text', - field=models.TextField(help_text='Email cover text which can be included in the default email template and in the message template.', null=True, verbose_name='email cover text', blank=True), + model_name="formdefinition", + name="mail_cover_text", + field=models.TextField( + help_text="Email cover text which can be included in the default email template and in the message template.", + null=True, + verbose_name="email cover text", + blank=True, + ), ), migrations.AlterField( - model_name='formdefinition', - name='message_template', - field=form_designer.fields.TemplateTextField(help_text='Your form fields are available as template context. Example: "{{ message }}" if you have a field named `message`. To iterate over all fields, use the variable `data` (a list containing a dictionary for each form field, each containing the elements `name`, `label`, `value`). If you have set up email cover text, you can use {{ mail_cover_text }} to access it.', null=True, verbose_name='message template', blank=True), + model_name="formdefinition", + name="message_template", + field=form_designer.fields.TemplateTextField( + help_text='Your form fields are available as template context. Example: "{{ message }}" if you have a field named `message`. To iterate over all fields, use the variable `data` (a list containing a dictionary for each form field, each containing the elements `name`, `label`, `value`). If you have set up email cover text, you can use {{ mail_cover_text }} to access it.', + null=True, + verbose_name="message template", + blank=True, + ), ), ] diff --git a/form_designer/models.py b/form_designer/models.py index da22b9a7..73aede1c 100644 --- a/form_designer/models.py +++ b/form_designer/models.py @@ -1,86 +1,182 @@ import re from collections import OrderedDict from decimal import Decimal - -import django from django.conf import settings as django_settings from django.db import models +from django.template.backends.django import DjangoTemplates from django.template.loader import get_template +from django.urls import reverse from django.utils.deprecation import warn_about_renamed_method from django.utils.module_loading import import_string from django.utils.translation import gettext_lazy as _ +from picklefield.fields import PickledObjectField from form_designer import settings from form_designer.fields import ModelNameField, RegexpExpressionField, TemplateCharField, TemplateTextField from form_designer.utils import get_random_hash, string_template_replace -from picklefield.fields import PickledObjectField -from django.urls import reverse - MAIL_TEMPLATE_CONTEXT_HELP_TEXT = _( - 'Your form fields are available as template context. ' + "Your form fields are available as template context. " 'Example: "{{ first_name }} {{ last_name }} <{{ from_email }}>" ' - 'if you have fields named `first_name`, `last_name`, `from_email`.' + "if you have fields named `first_name`, `last_name`, `from_email`." ) class FormValueDict(dict): def __init__(self, name, value, label): - self['name'] = name - self['value'] = value - self['label'] = label + self["name"] = name + self["value"] = value + self["label"] = label super(FormValueDict, self).__init__() def get_django_template_from_string(template_string): - if django.VERSION >= (1, 9): - # We need to create an ad-hoc django templates backend - # since we can't trust that the user's configuration - # even includes one. Using the "raw" `django.template.Template` - # object does not work for reasons unknown (well, semi-unknown; - # it just seems to have a slightly different API). - from django.template.backends.django import DjangoTemplates - return DjangoTemplates({ - 'NAME': 'django-form-designer-renderer', - 'DIRS': [], - 'APP_DIRS': False, - 'OPTIONS': {}, - }).from_string(template_string) - else: - from django.template import Template - return Template(template_string) + # We need to create an ad-hoc django templates backend + # since we can't trust that the user's configuration + # even includes one. Using the "raw" `django.template.Template` + # object does not work for reasons unknown (well, semi-unknown; + # it just seems to have a slightly different API). + return DjangoTemplates( + { + "NAME": "django-form-designer-renderer", + "DIRS": [], + "APP_DIRS": False, + "OPTIONS": {}, + } + ).from_string(template_string) class FormDefinition(models.Model): - name = models.SlugField(_('name'), max_length=255, unique=True) - require_hash = models.BooleanField(_('obfuscate URL to this form'), default=False, help_text=_('If enabled, the form can only be reached via a secret URL.')) - private_hash = models.CharField(editable=False, max_length=40, default='') - public_hash = models.CharField(editable=False, max_length=40, default='') - title = models.CharField(_('title'), max_length=255, blank=True, null=True) - body = models.TextField(_('body'), help_text=_('Form description. Display on form after title.'), blank=True, null=True) - action = models.URLField(_('target URL'), help_text=_('If you leave this empty, the page where the form resides will be requested, and you can use the mail form and logging features. You can also send data to external sites: For instance, enter "http://www.google.ch/search" to create a search form.'), max_length=255, blank=True, null=True) - mail_cover_text = models.TextField(_('email cover text'), help_text=_('Email cover text which can be included in the default email template and in the message template.'), blank=True, null=True) - mail_to = TemplateCharField(_('send form data to e-mail address'), help_text=_('Separate several addresses with a comma. Your form fields are available as template context. Example: "admin@domain.com, {{ from_email }}" if you have a field named `from_email`.'), max_length=255, blank=True, null=True) - mail_from = TemplateCharField(_('sender address'), max_length=255, help_text=MAIL_TEMPLATE_CONTEXT_HELP_TEXT, blank=True, null=True) - mail_reply_to = TemplateCharField(_('reply-to address'), max_length=255, help_text=MAIL_TEMPLATE_CONTEXT_HELP_TEXT, blank=True) - mail_subject = TemplateCharField(_('email subject'), max_length=255, help_text=_('Your form fields are available as template context. Example: "Contact form {{ subject }}" if you have a field named `subject`.'), blank=True, null=True) - mail_uploaded_files = models.BooleanField(_('Send uploaded files as email attachments'), default=True) - method = models.CharField(_('method'), max_length=10, default="POST", choices=(('POST', 'POST'), ('GET', 'GET'))) - success_message = models.CharField(_('success message'), max_length=255, blank=True, null=True) - error_message = models.CharField(_('error message'), max_length=255, blank=True, null=True) - submit_label = models.CharField(_('submit button label'), max_length=255, blank=True, null=True) - log_data = models.BooleanField(_('log form data'), help_text=_('Logs all form submissions to the database.'), default=True) - save_uploaded_files = models.BooleanField(_('save uploaded files'), help_text=_('Saves all uploaded files using server storage.'), default=True) - success_redirect = models.BooleanField(_('HTTP redirect after successful submission'), default=True) - success_clear = models.BooleanField(_('clear form after successful submission'), default=True) - allow_get_initial = models.BooleanField(_('allow initial values via URL'), help_text=_('If enabled, you can fill in form fields by adding them to the query string.'), default=True) - message_template = TemplateTextField(_('message template'), help_text=_('Your form fields are available as template context. Example: "{{ message }}" if you have a field named `message`. To iterate over all fields, use the variable `data` (a list containing a dictionary for each form field, each containing the elements `name`, `label`, `value`). If you have set up email cover text, you can use {{ mail_cover_text }} to access it.'), blank=True, null=True) - form_template_name = models.CharField(_('form template'), max_length=255, blank=True, null=True) - display_logged = models.BooleanField(_('display logged submissions with form'), default=False) + name = models.SlugField(_("name"), max_length=255, unique=True) + require_hash = models.BooleanField( + _("obfuscate URL to this form"), + default=False, + help_text=_("If enabled, the form can only be reached via a secret URL."), + ) + private_hash = models.CharField(editable=False, max_length=40, default="") + public_hash = models.CharField(editable=False, max_length=40, default="") + title = models.CharField(_("title"), max_length=255, blank=True, null=True) + body = models.TextField( + _("body"), + help_text=_("Form description. Display on form after title."), + blank=True, + null=True, + ) + action = models.URLField( + _("target URL"), + help_text=_( + "If you leave this empty, the page where the form resides will be requested, " + "and you can use the mail form and logging features. You can also send data to " + 'external sites: For instance, enter "http://www.google.ch/search" to create a search form.' + ), + max_length=255, + blank=True, + null=True, + ) + mail_cover_text = models.TextField( + _("email cover text"), + help_text=_( + "Email cover text which can be included in the default email template and in the message template." + ), + blank=True, + null=True, + ) + mail_to = TemplateCharField( + _("send form data to e-mail address"), + help_text=_( + "Separate several addresses with a comma. Your form fields are available as " + 'template context. Example: "admin@domain.com, {{ from_email }}" if you have a field named `from_email`.' + ), + max_length=255, + blank=True, + null=True, + ) + mail_from = TemplateCharField( + _("sender address"), + max_length=255, + help_text=MAIL_TEMPLATE_CONTEXT_HELP_TEXT, + blank=True, + null=True, + ) + mail_reply_to = TemplateCharField( + _("reply-to address"), + max_length=255, + help_text=MAIL_TEMPLATE_CONTEXT_HELP_TEXT, + blank=True, + ) + mail_subject = TemplateCharField( + _("email subject"), + max_length=255, + help_text=_( + "Your form fields are available as template context. Example: " + '"Contact form {{ subject }}" if you have a field named `subject`.' + ), + blank=True, + null=True, + ) + mail_uploaded_files = models.BooleanField( + _("Send uploaded files as email attachments"), default=True + ) + method = models.CharField( + _("method"), + max_length=10, + default="POST", + choices=(("POST", "POST"), ("GET", "GET")), + ) + success_message = models.CharField( + _("success message"), max_length=255, blank=True, null=True + ) + error_message = models.CharField( + _("error message"), max_length=255, blank=True, null=True + ) + submit_label = models.CharField( + _("submit button label"), max_length=255, blank=True, null=True + ) + log_data = models.BooleanField( + _("log form data"), + help_text=_("Logs all form submissions to the database."), + default=True, + ) + save_uploaded_files = models.BooleanField( + _("save uploaded files"), + help_text=_("Saves all uploaded files using server storage."), + default=True, + ) + success_redirect = models.BooleanField( + _("HTTP redirect after successful submission"), default=True + ) + success_clear = models.BooleanField( + _("clear form after successful submission"), default=True + ) + allow_get_initial = models.BooleanField( + _("allow initial values via URL"), + help_text=_( + "If enabled, you can fill in form fields by adding them to the query string." + ), + default=True, + ) + message_template = TemplateTextField( + _("message template"), + help_text=_( + 'Your form fields are available as template context. Example: "{{ message }}" if ' + "you have a field named `message`. To iterate over all fields, use the variable `data` " + "(a list containing a dictionary for each form field, each containing the elements " + "`name`, `label`, `value`). If you have set up email cover text, " + "you can use {{ mail_cover_text }} to access it." + ), + blank=True, + null=True, + ) + form_template_name = models.CharField( + _("form template"), max_length=255, blank=True, null=True + ) + display_logged = models.BooleanField( + _("display logged submissions with form"), default=False + ) class Meta: - verbose_name = _('Form') - verbose_name_plural = _('Forms') + verbose_name = _("Form") + verbose_name_plural = _("Forms") def save(self, *args, **kwargs): if not self.private_hash: @@ -97,8 +193,10 @@ def get_field_dict(self): def get_absolute_url(self): if self.require_hash: - return reverse('form_designer.views.detail_by_hash', [str(self.public_hash)]) - return reverse('form_designer.views.detail', [str(self.name)]) + return reverse( + "form_designer.views.detail_by_hash", [str(self.public_hash)] + ) + return reverse("form_designer.views.detail", [str(self.name)]) def get_form_data(self, form): # TODO: refactor, move to utils or views @@ -109,7 +207,7 @@ def get_form_data(self, form): for key in form_keys: if key in def_keys and field_dict[key].include_result: value = form.cleaned_data[key] - if getattr(value, '__form_data__', False): + if getattr(value, "__form_data__", False): value = value.__form_data__() data.append(FormValueDict(key, value, form.fields[key].label)) return data @@ -118,7 +216,7 @@ def get_form_data_context(self, form_data): # TODO: refactor, move to utils dict = {} for field in form_data or (): - dict[field['name']] = field['value'] + dict[field["name"]] = field["value"] return dict def compile_message(self, form_data, template=None): @@ -130,18 +228,14 @@ def compile_message(self, form_data, template=None): else: t = get_django_template_from_string(self.message_template) context = self.get_form_data_context(form_data) - context['data'] = form_data - context['mail_cover_text'] = self.mail_cover_text or '' - if django.VERSION < (1, 9): - # For old Djangoes, we need to wrap these contexts - from django.template import Context - context = Context(context) + context["data"] = form_data + context["mail_cover_text"] = self.mail_cover_text or "" return t.render(context) def count_fields(self): return self.formdefinitionfield_set.count() - count_fields.short_description = _('Fields') + count_fields.short_description = _("Fields") def __str__(self): return self.title or self.name @@ -150,25 +244,18 @@ def log(self, form, user=None): form_data = self.get_form_data(form) created_by = None - # User.is_authenticated became a hybrid bool/callable property in Django 1.10, - # and an actual non-callable property in Django 2.0. - # For old versions, not calling it will result in false positives, - # so we have to be pretty explicit about these checks here. - - if django.VERSION[0] >= 2: - if user and user.is_authenticated: - created_by = user - else: # TODO: Remove when Django <1.10 compat is dropped - if user and user.is_authenticated(): - created_by = user + if user and user.is_authenticated: + created_by = user flog = FormLog(form_definition=self, data=form_data, created_by=created_by) flog.save() return flog @warn_about_renamed_method( - 'FormDefinition', 'string_template_replace', 'form_designer.utils.string_template_replace', - DeprecationWarning + "FormDefinition", + "string_template_replace", + "form_designer.utils.string_template_replace", + DeprecationWarning, ) def string_template_replace(self, text, context_dict): return string_template_replace(text, context_dict) @@ -177,6 +264,7 @@ def send_mail(self, form, files=None): if not self.mail_to: return from form_designer.email import build_form_mail + message = build_form_mail(form_definition=self, form=form, files=files) message.send(fail_silently=False) return message @@ -187,144 +275,192 @@ def is_template_html(self): if template: # We have a custom inline template string? # Assume the template string is HTML-ish if it has at least one opening # and closing HTML tag: - return (re.search("<[^>]+>", template) and re.search("]+>", template)) + return re.search("<[^>]+>", template) and re.search("]+>", template) # If there is no custom inline template, see if the `EMAIL_TEMPLATE` # setting points to a `.html` file: - return settings.EMAIL_TEMPLATE.lower().endswith('.html') + return settings.EMAIL_TEMPLATE.lower().endswith(".html") @property def submit_flag_name(self): name = settings.SUBMIT_FLAG_NAME % self.name # make sure we are not overriding one of the actual form fields while self.formdefinitionfield_set.filter(name__exact=name).count() > 0: - name += '_' + name += "_" return name class FormDefinitionField(models.Model): form_definition = models.ForeignKey(FormDefinition, on_delete=models.CASCADE) - field_class = models.CharField(_('field class'), max_length=100) - position = models.IntegerField(_('position'), blank=True, null=True) - - name = models.SlugField(_('name'), max_length=255) - label = models.CharField(_('label'), max_length=255, blank=True, null=True) - required = models.BooleanField(_('required'), default=True) - include_result = models.BooleanField(_('include in result'), help_text=_('If this is disabled, the field value will not be included in logs and e-mails generated from form data.'), default=True) - widget = models.CharField(_('widget'), default='', max_length=255, blank=True, null=True) - initial = models.TextField(_('initial value'), blank=True, null=True) - help_text = models.CharField(_('help text'), max_length=255, blank=True, null=True) + field_class = models.CharField(_("field class"), max_length=100) + position = models.IntegerField(_("position"), blank=True, null=True) + + name = models.SlugField(_("name"), max_length=255) + label = models.CharField(_("label"), max_length=255, blank=True, null=True) + required = models.BooleanField(_("required"), default=True) + include_result = models.BooleanField( + _("include in result"), + help_text=_( + "If this is disabled, the field value will not be included in logs and e-mails generated from form data." + ), + default=True, + ) + widget = models.CharField( + _("widget"), default="", max_length=255, blank=True, null=True + ) + initial = models.TextField(_("initial value"), blank=True, null=True) + help_text = models.CharField(_("help text"), max_length=255, blank=True, null=True) - choice_values = models.TextField(_('values'), help_text=_('One value per line'), blank=True, null=True) - choice_labels = models.TextField(_('labels'), help_text=_('One label per line'), blank=True, null=True) + choice_values = models.TextField( + _("values"), help_text=_("One value per line"), blank=True, null=True + ) + choice_labels = models.TextField( + _("labels"), help_text=_("One label per line"), blank=True, null=True + ) - max_length = models.IntegerField(_('max. length'), blank=True, null=True) - min_length = models.IntegerField(_('min. length'), blank=True, null=True) - max_value = models.FloatField(_('max. value'), blank=True, null=True) - min_value = models.FloatField(_('min. value'), blank=True, null=True) - max_digits = models.IntegerField(_('max. digits'), blank=True, null=True) - decimal_places = models.IntegerField(_('decimal places'), blank=True, null=True) + max_length = models.IntegerField(_("max. length"), blank=True, null=True) + min_length = models.IntegerField(_("min. length"), blank=True, null=True) + max_value = models.FloatField(_("max. value"), blank=True, null=True) + min_value = models.FloatField(_("min. value"), blank=True, null=True) + max_digits = models.IntegerField(_("max. digits"), blank=True, null=True) + decimal_places = models.IntegerField(_("decimal places"), blank=True, null=True) - regex = RegexpExpressionField(_('regular Expression'), max_length=255, blank=True, null=True) + regex = RegexpExpressionField( + _("regular Expression"), max_length=255, blank=True, null=True + ) - choice_model = ModelNameField(_('data model'), max_length=255, blank=True, null=True) - choice_model_empty_label = models.CharField(_('empty label'), max_length=255, blank=True, null=True) + choice_model = ModelNameField( + _("data model"), max_length=255, blank=True, null=True + ) + choice_model_empty_label = models.CharField( + _("empty label"), max_length=255, blank=True, null=True + ) class Meta: - verbose_name = _('field') - verbose_name_plural = _('fields') - ordering = ['position'] + verbose_name = _("field") + verbose_name_plural = _("fields") + ordering = ["position"] def save(self, *args, **kwargs): if self.position is None: self.position = 0 super(FormDefinitionField, self).save(*args, **kwargs) - def get_form_field_init_args(self): + def get_form_field_init_args(self): # noqa: C901 args = { - 'required': self.required, - 'label': self.label if self.label else '', - 'initial': self.initial if self.initial else None, - 'help_text': self.help_text, + "required": self.required, + "label": self.label if self.label else "", + "initial": self.initial if self.initial else None, + "help_text": self.help_text, } - if self.field_class in ('django.forms.CharField', 'django.forms.EmailField', 'django.forms.RegexField'): - args.update({ - 'max_length': self.max_length, - 'min_length': self.min_length, - }) - - if self.field_class in ('django.forms.IntegerField', 'django.forms.DecimalField'): - args.update({ - 'max_value': int(self.max_value) if self.max_value is not None else None, - 'min_value': int(self.min_value) if self.min_value is not None else None, - }) - - if self.field_class == 'django.forms.DecimalField': - args.update({ - 'max_value': Decimal(str(self.max_value)) if self.max_value is not None else None, - 'min_value': Decimal(str(self.min_value)) if self.max_value is not None else None, - 'max_digits': self.max_digits, - 'decimal_places': self.decimal_places, - }) - - if self.field_class == 'django.forms.RegexField': + if self.field_class in ( + "django.forms.CharField", + "django.forms.EmailField", + "django.forms.RegexField", + ): + args.update( + { + "max_length": self.max_length, + "min_length": self.min_length, + } + ) + + if self.field_class in ( + "django.forms.IntegerField", + "django.forms.DecimalField", + ): + args.update( + { + "max_value": int(self.max_value) + if self.max_value is not None + else None, + "min_value": int(self.min_value) + if self.min_value is not None + else None, + } + ) + + if self.field_class == "django.forms.DecimalField": + args.update( + { + "max_value": Decimal(str(self.max_value)) + if self.max_value is not None + else None, + "min_value": Decimal(str(self.min_value)) + if self.max_value is not None + else None, + "max_digits": self.max_digits, + "decimal_places": self.decimal_places, + } + ) + + if self.field_class == "django.forms.RegexField": if self.regex: - args.update({ - 'regex': self.regex - }) + args.update({"regex": self.regex}) - if self.field_class in ('django.forms.ChoiceField', 'django.forms.MultipleChoiceField'): + if self.field_class in ( + "django.forms.ChoiceField", + "django.forms.MultipleChoiceField", + ): if self.choice_values: choices = [] - regex = re.compile('[\s]*\n[\s]*') + regex = re.compile(r"[\s]*\n[\s]*") values = regex.split(self.choice_values) labels = regex.split(self.choice_labels) if self.choice_labels else [] for index, value in enumerate(values): try: label = labels[index] - except: + except Exception: label = value choices.append((value, label)) - args.update({ - 'choices': tuple(choices) - }) - - if self.field_class in ('django.forms.ModelChoiceField', 'django.forms.ModelMultipleChoiceField'): - args.update({ - 'queryset': ModelNameField.get_model_from_string(self.choice_model).objects.all() - }) - - if self.field_class == 'django.forms.ModelChoiceField': - args.update({ - 'empty_label': self.choice_model_empty_label - }) + args.update({"choices": tuple(choices)}) + + if self.field_class in ( + "django.forms.ModelChoiceField", + "django.forms.ModelMultipleChoiceField", + ): + args.update( + { + "queryset": ModelNameField.get_model_from_string( + self.choice_model + ).objects.all() + } + ) + + if self.field_class == "django.forms.ModelChoiceField": + args.update({"empty_label": self.choice_model_empty_label}) if self.widget: - args.update({ - 'widget': import_string(self.widget)() - }) + args.update({"widget": import_string(self.widget)()}) return args def __str__(self): - return (self.label or self.name) + return self.label or self.name class FormLog(models.Model): - form_definition = models.ForeignKey(FormDefinition, related_name='logs', on_delete=models.CASCADE) - created = models.DateTimeField(_('Created'), auto_now=True) - created_by = models.ForeignKey(getattr(django_settings, "AUTH_USER_MODEL", "auth.User"), null=True, blank=True, on_delete=models.CASCADE) + form_definition = models.ForeignKey( + FormDefinition, related_name="logs", on_delete=models.CASCADE + ) + created = models.DateTimeField(_("Created"), auto_now=True) + created_by = models.ForeignKey( + getattr(django_settings, "AUTH_USER_MODEL", "auth.User"), + null=True, + blank=True, + on_delete=models.CASCADE, + ) _data = None class Meta: - verbose_name = _('form log') - verbose_name_plural = _('form logs') + verbose_name = _("form log") + verbose_name_plural = _("form logs") def __str__(self): return "%s (%s)" % ( self.form_definition.title or self.form_definition.name, - self.created + self.created, ) def get_data(self): @@ -344,8 +480,7 @@ def get_data(self): # field may have been removed label = None - value_dict = FormValueDict(item.field_name, item.value, - label) + value_dict = FormValueDict(item.field_name, item.value, label) if item.field_name in fields: values_with_header[item.field_name] = value_dict @@ -378,16 +513,18 @@ def save(self, *args, **kwargs): for item in self._data: FormValue.objects.create( form_log=self, - field_name=item['name'], - value=item['value'], + field_name=item["name"], + value=item["value"], ) self._data = None class FormValue(models.Model): - form_log = models.ForeignKey(FormLog, related_name='values', on_delete=models.CASCADE) - field_name = models.SlugField(_('field name'), max_length=255) - value = PickledObjectField(_('value'), null=True, blank=True) + form_log = models.ForeignKey( + FormLog, related_name="values", on_delete=models.CASCADE + ) + field_name = models.SlugField(_("field name"), max_length=255) + value = PickledObjectField(_("value"), null=True, blank=True) def __str__(self): - return'%s = %s' % (self.field_name, self.value) + return "%s = %s" % (self.field_name, self.value) diff --git a/form_designer/settings.py b/form_designer/settings.py index 32717e28..2b800226 100644 --- a/form_designer/settings.py +++ b/form_designer/settings.py @@ -1,104 +1,198 @@ import os.path - from django.conf import settings from django.core.files.storage import get_storage_class from django.utils.translation import gettext_lazy as _ -STATIC_URL = os.path.join(getattr(settings, 'STATIC_URL', settings.MEDIA_URL), 'form_designer') - -FIELD_CLASSES = getattr(settings, 'FORM_DESIGNER_FIELD_CLASSES', ( - ('django.forms.CharField', _('Text')), - ('django.forms.EmailField', _('E-mail address')), - ('django.forms.URLField', _('Web address')), - ('django.forms.IntegerField', _('Number')), - ('django.forms.DecimalField', _('Decimal number')), - ('django.forms.BooleanField', _('Yes/No')), - ('django.forms.DateField', _('Date')), - ('django.forms.DateTimeField', _('Date & time')), - ('django.forms.TimeField', _('Time')), - ('django.forms.ChoiceField', _('Choice')), - ('django.forms.MultipleChoiceField', _('Multiple Choice')), - ('django.forms.ModelChoiceField', _('Model Choice')), - ('django.forms.ModelMultipleChoiceField', _('Model Multiple Choice')), - ('django.forms.RegexField', _('Regex')), - ('django.forms.FileField', _('File')), - # ('captcha.fields.CaptchaField', _('Captcha')), -)) - -WIDGET_CLASSES = getattr(settings, 'FORM_DESIGNER_WIDGET_CLASSES', ( - ('', _('Default')), - ('django.forms.widgets.Textarea', _('Text area')), - ('django.forms.widgets.PasswordInput', _('Password input')), - ('django.forms.widgets.HiddenInput', _('Hidden input')), - ('django.forms.widgets.RadioSelect', _('Radio button')), - ('django.forms.widgets.CheckboxSelectMultiple', _('Checkbox')), -)) - -EXPORTER_CLASSES = getattr(settings, 'FORM_DESIGNER_EXPORTER_CLASSES', ( - 'form_designer.contrib.exporters.csv_exporter.CsvExporter', - 'form_designer.contrib.exporters.xls_exporter.XlsExporter', -)) - -FORM_TEMPLATES = getattr(settings, 'FORM_DESIGNER_FORM_TEMPLATES', ( - ('', _('Default')), - ('html/formdefinition/forms/as_p.html', _('as paragraphs')), - ('html/formdefinition/forms/as_p_cms.html', _('as paragraphs for cms')), - ('html/formdefinition/forms/as_table.html', _('as table')), - ('html/formdefinition/forms/as_table_h.html', _('as table (horizontal)')), - ('html/formdefinition/forms/as_ul.html', _('as unordered list')), - ('html/formdefinition/forms/custom.html', _('custom implementation')), -)) - -# Sequence of two-tuples like (('your_app.models.ModelName', 'My Model'), ...) for limiting the models available to ModelChoiceField and ModelMultipleChoiceField. +STATIC_URL = os.path.join( + getattr(settings, "STATIC_URL", settings.MEDIA_URL), "form_designer" +) + +FIELD_CLASSES = getattr( + settings, + "FORM_DESIGNER_FIELD_CLASSES", + ( + ("django.forms.CharField", _("Text")), + ("django.forms.EmailField", _("E-mail address")), + ("django.forms.URLField", _("Web address")), + ("django.forms.IntegerField", _("Number")), + ("django.forms.DecimalField", _("Decimal number")), + ("django.forms.BooleanField", _("Yes/No")), + ("django.forms.DateField", _("Date")), + ("django.forms.DateTimeField", _("Date & time")), + ("django.forms.TimeField", _("Time")), + ("django.forms.ChoiceField", _("Choice")), + ("django.forms.MultipleChoiceField", _("Multiple Choice")), + ("django.forms.ModelChoiceField", _("Model Choice")), + ("django.forms.ModelMultipleChoiceField", _("Model Multiple Choice")), + ("django.forms.RegexField", _("Regex")), + ("django.forms.FileField", _("File")), + # ('captcha.fields.CaptchaField', _('Captcha')), + ), +) + +WIDGET_CLASSES = getattr( + settings, + "FORM_DESIGNER_WIDGET_CLASSES", + ( + ("", _("Default")), + ("django.forms.widgets.Textarea", _("Text area")), + ("django.forms.widgets.PasswordInput", _("Password input")), + ("django.forms.widgets.HiddenInput", _("Hidden input")), + ("django.forms.widgets.RadioSelect", _("Radio button")), + ("django.forms.widgets.CheckboxSelectMultiple", _("Checkbox")), + ), +) + +EXPORTER_CLASSES = getattr( + settings, + "FORM_DESIGNER_EXPORTER_CLASSES", + ( + "form_designer.contrib.exporters.csv_exporter.CsvExporter", + "form_designer.contrib.exporters.xls_exporter.XlsExporter", + ), +) + +FORM_TEMPLATES = getattr( + settings, + "FORM_DESIGNER_FORM_TEMPLATES", + ( + ("", _("Default")), + ("html/formdefinition/forms/as_p.html", _("as paragraphs")), + ("html/formdefinition/forms/as_p_cms.html", _("as paragraphs for cms")), + ("html/formdefinition/forms/as_table.html", _("as table")), + ("html/formdefinition/forms/as_table_h.html", _("as table (horizontal)")), + ("html/formdefinition/forms/as_ul.html", _("as unordered list")), + ("html/formdefinition/forms/custom.html", _("custom implementation")), + ), +) + +# Sequence of two-tuples like (('your_app.models.ModelName', 'My Model'), ...) for +# limiting the models available to ModelChoiceField and ModelMultipleChoiceField. # If None, any model can be chosen by entering it as a string -CHOICE_MODEL_CHOICES = getattr(settings, 'FORM_DESIGNER_CHOICE_MODEL_CHOICES', None) +CHOICE_MODEL_CHOICES = getattr(settings, "FORM_DESIGNER_CHOICE_MODEL_CHOICES", None) -DEFAULT_FORM_TEMPLATE = getattr(settings, 'FORM_DESIGNER_DEFAULT_FORM_TEMPLATE', 'html/formdefinition/forms/as_p.html') +DEFAULT_FORM_TEMPLATE = getattr( + settings, + "FORM_DESIGNER_DEFAULT_FORM_TEMPLATE", + "html/formdefinition/forms/as_p.html", +) # semicolon is Microsoft Excel default -CSV_EXPORT_DELIMITER = getattr(settings, 'FORM_DESIGNER_CSV_EXPORT_DELIMITER', ';') +CSV_EXPORT_DELIMITER = getattr(settings, "FORM_DESIGNER_CSV_EXPORT_DELIMITER", ";") # include log timestamp in export -CSV_EXPORT_INCLUDE_CREATED = getattr(settings, 'FORM_DESIGNER_CSV_EXPORT_INCLUDE_CREATED', True) +CSV_EXPORT_INCLUDE_CREATED = getattr( + settings, "FORM_DESIGNER_CSV_EXPORT_INCLUDE_CREATED", True +) -CSV_EXPORT_INCLUDE_PK = getattr(settings, 'FORM_DESIGNER_CSV_EXPORT_INCLUDE_PK', True) +CSV_EXPORT_INCLUDE_PK = getattr(settings, "FORM_DESIGNER_CSV_EXPORT_INCLUDE_PK", True) # include field labels/names in first row if exporting logs for one form only -CSV_EXPORT_INCLUDE_HEADER = getattr(settings, 'FORM_DESIGNER_CSV_EXPORT_INCLUDE_HEADER', True) +CSV_EXPORT_INCLUDE_HEADER = getattr( + settings, "FORM_DESIGNER_CSV_EXPORT_INCLUDE_HEADER", True +) # include form title if exporting logs for more than one form -CSV_EXPORT_INCLUDE_FORM = getattr(settings, 'FORM_DESIGNER_CSV_EXPORT_INCLUDE_FORM', True) - -CSV_EXPORT_ENCODING = getattr(settings, 'FORM_DESIGNER_CSV_EXPORT_ENCODING', 'utf-8') - -CSV_EXPORT_NULL_VALUE = getattr(settings, 'FORM_DESIGNER_CSV_EXPORT_NULL_VALUE', '') - -SUBMIT_FLAG_NAME = getattr(settings, 'FORM_DESIGNER_SUBMIT_FLAG_NAME', 'submit__%s') - -FILE_STORAGE_CLASS = getattr(settings, 'FORM_DESIGNER_FILE_STORAGE_CLASS', get_storage_class()) - -FILE_STORAGE_DIR = 'form_uploads' - -ALLOWED_FILE_TYPES = getattr(settings, 'FORM_DESIGNER_ALLOWED_FILE_TYPES', ( - 'aac', 'ace', 'ai', 'aiff', 'avi', 'bmp', 'dir', 'doc', 'docx', 'dmg', 'eps', 'fla', 'flv', - 'gif', 'gz', 'hqx', 'ico', 'indd', 'inx', 'jpg', 'jar', 'jpeg', 'md', 'mov', - 'mp3', 'mp4', 'mpc', 'mkv', 'mpg', 'mpeg', 'ogg', 'odg', 'odf', 'odp', 'ods', 'odt', 'otf', - 'pdf', 'png', 'pps', 'ppsx', 'ps', 'psd', 'rar', 'rm', 'rtf', 'sit', 'swf', 'tar', 'tga', - 'tif', 'tiff', 'ttf', 'txt', 'wav', 'wma', 'wmv', 'xls', 'xlsx', 'xml', 'zip' -)) - -MAX_UPLOAD_SIZE = getattr(settings, 'MAX_UPLOAD_SIZE', 5242880) # 5M -MAX_UPLOAD_TOTAL_SIZE = getattr(settings, 'MAX_UPLOAD_TOTAL_SIZE', 10485760) # 10M +CSV_EXPORT_INCLUDE_FORM = getattr( + settings, "FORM_DESIGNER_CSV_EXPORT_INCLUDE_FORM", True +) + +CSV_EXPORT_ENCODING = getattr(settings, "FORM_DESIGNER_CSV_EXPORT_ENCODING", "utf-8") + +CSV_EXPORT_NULL_VALUE = getattr(settings, "FORM_DESIGNER_CSV_EXPORT_NULL_VALUE", "") + +SUBMIT_FLAG_NAME = getattr(settings, "FORM_DESIGNER_SUBMIT_FLAG_NAME", "submit__%s") + +FILE_STORAGE_CLASS = getattr( + settings, "FORM_DESIGNER_FILE_STORAGE_CLASS", get_storage_class() +) + +FILE_STORAGE_DIR = "form_uploads" + +ALLOWED_FILE_TYPES = getattr( + settings, + "FORM_DESIGNER_ALLOWED_FILE_TYPES", + ( + "aac", + "ace", + "ai", + "aiff", + "avi", + "bmp", + "dir", + "doc", + "docx", + "dmg", + "eps", + "fla", + "flv", + "gif", + "gz", + "hqx", + "ico", + "indd", + "inx", + "jpg", + "jar", + "jpeg", + "md", + "mov", + "mp3", + "mp4", + "mpc", + "mkv", + "mpg", + "mpeg", + "ogg", + "odg", + "odf", + "odp", + "ods", + "odt", + "otf", + "pdf", + "png", + "pps", + "ppsx", + "ps", + "psd", + "rar", + "rm", + "rtf", + "sit", + "swf", + "tar", + "tga", + "tif", + "tiff", + "ttf", + "txt", + "wav", + "wma", + "wmv", + "xls", + "xlsx", + "xml", + "zip", + ), +) + +MAX_UPLOAD_SIZE = getattr(settings, "MAX_UPLOAD_SIZE", 5242880) # 5M +MAX_UPLOAD_TOTAL_SIZE = getattr(settings, "MAX_UPLOAD_TOTAL_SIZE", 10485760) # 10M VALUE_PICKLEFIELD = True -DESIGNED_FORM_CLASS = getattr(settings, 'FORM_DESIGNER_DESIGNED_FORM_CLASS', 'form_designer.forms.DesignedForm') +DESIGNED_FORM_CLASS = getattr( + settings, "FORM_DESIGNER_DESIGNED_FORM_CLASS", "form_designer.forms.DesignedForm" +) -EMAIL_TEMPLATE = getattr(settings, 'FORM_DESIGNER_EMAIL_TEMPLATE', 'txt/formdefinition/data_message.txt') +EMAIL_TEMPLATE = getattr( + settings, "FORM_DESIGNER_EMAIL_TEMPLATE", "txt/formdefinition/data_message.txt" +) -PUSH_MESSAGES = getattr(settings, 'FORM_DESIGNER_CMS_PLUGIN_PUSH_MESSAGES', False) +PUSH_MESSAGES = getattr(settings, "FORM_DESIGNER_CMS_PLUGIN_PUSH_MESSAGES", False) # reCAPTCHA settings -USE_GOOGLE_RECAPTCHA = getattr(settings, 'FORM_DESIGNER_USE_GOOGLE_RECAPTCHA', False) -GOOGLE_RECAPTCHA_SITE_KEY = getattr(settings, 'GOOGLE_RECAPTCHA_SITE_KEY', None) -GOOGLE_RECAPTCHA_SECRET_KEY = getattr(settings, 'GOOGLE_RECAPTCHA_SECRET_KEY', None) +USE_GOOGLE_RECAPTCHA = getattr(settings, "FORM_DESIGNER_USE_GOOGLE_RECAPTCHA", False) +GOOGLE_RECAPTCHA_SITE_KEY = getattr(settings, "GOOGLE_RECAPTCHA_SITE_KEY", None) +GOOGLE_RECAPTCHA_SECRET_KEY = getattr(settings, "GOOGLE_RECAPTCHA_SECRET_KEY", None) diff --git a/form_designer/templatetags/friendly.py b/form_designer/templatetags/friendly.py index a011b393..a1fab600 100644 --- a/form_designer/templatetags/friendly.py +++ b/form_designer/templatetags/friendly.py @@ -15,8 +15,9 @@ def friendly(value, null_value=None): value = ", ".join(force_text(object) for object in value) if isinstance(value, bool): value = yesno(value) - if hasattr(value, 'url'): + if hasattr(value, "url"): value = value.url return force_text(value) + register.filter(friendly) diff --git a/form_designer/templatetags/widget_type.py b/form_designer/templatetags/widget_type.py index 02300b30..7a2dcc74 100644 --- a/form_designer/templatetags/widget_type.py +++ b/form_designer/templatetags/widget_type.py @@ -3,6 +3,6 @@ register = template.Library() -@register.filter('field_type') +@register.filter("field_type") def field_type(obj): return obj.__class__.__name__ diff --git a/form_designer/tests/conftest.py b/form_designer/tests/conftest.py index 417ea1da..b113b4ad 100644 --- a/form_designer/tests/conftest.py +++ b/form_designer/tests/conftest.py @@ -7,23 +7,24 @@ @pytest.fixture() def greeting_form(): from form_designer.models import FormDefinition, FormDefinitionField + fd = FormDefinition.objects.create( name=get_random_string(), - mail_to='test@example.com', - mail_subject='Someone sent you a greeting: {{ greeting }}', - mail_reply_to='Greeting Bot ', + mail_to="test@example.com", + mail_subject="Someone sent you a greeting: {{ greeting }}", + mail_reply_to="Greeting Bot ", ) FormDefinitionField.objects.create( form_definition=fd, - name='greeting', - label='Greeting', - field_class='django.forms.CharField', + name="greeting", + label="Greeting", + field_class="django.forms.CharField", required=True, ) FormDefinitionField.objects.create( form_definition=fd, - name='upload', - field_class='django.forms.FileField', + name="upload", + field_class="django.forms.FileField", required=False, ) return fd @@ -35,5 +36,5 @@ def greeting_form_with_log(admin_user, greeting_form): form_definition=greeting_form, created_by=admin_user, ) - log.values.create(field_name='greeting', value=get_random_string()) + log.values.create(field_name="greeting", value=get_random_string()) return greeting_form diff --git a/form_designer/tests/test_admin.py b/form_designer/tests/test_admin.py index bd936d67..517ebfe8 100644 --- a/form_designer/tests/test_admin.py +++ b/form_designer/tests/test_admin.py @@ -1,17 +1,20 @@ +import pytest from django.forms.models import model_to_dict from django.utils.crypto import get_random_string -import pytest from form_designer.models import FormDefinition @pytest.mark.django_db def test_admin_list_view_renders(admin_client, greeting_form): - assert greeting_form.name in admin_client.get("/admin/form_designer/formdefinition/").content.decode() + assert ( + greeting_form.name + in admin_client.get("/admin/form_designer/formdefinition/").content.decode() + ) @pytest.mark.django_db -def test_admin_create_view_renders(admin_client): +def test_admin_create_view_renders_add(admin_client): assert admin_client.get("/admin/form_designer/formdefinition/add/").content @@ -20,52 +23,48 @@ def test_admin_create_view_renders(admin_client): def test_admin_create_view_creates_form(admin_client, n_fields): name = get_random_string() data = { - '_save': 'Save', - 'action': '', - 'allow_get_initial': 'on', - 'body': '', - 'error_message': '', - 'form_template_name': '', - 'formdefinitionfield_set-INITIAL_FORMS': '0', - 'formdefinitionfield_set-MAX_NUM_FORMS': '1000', - 'formdefinitionfield_set-MIN_NUM_FORMS': '0', - 'formdefinitionfield_set-TOTAL_FORMS': n_fields, - 'log_data': 'on', - 'mail_from': '', - 'mail_subject': '', - 'mail_to': '', - 'mail_uploaded_files': 'on', - 'message_template': '', - 'method': 'POST', - 'name': name, - 'save_uploaded_files': 'on', - 'submit_label': '', - 'success_clear': 'on', - 'success_message': '', - 'success_redirect': 'on', - 'title': '', + "_save": "Save", + "action": "", + "allow_get_initial": "on", + "body": "", + "error_message": "", + "form_template_name": "", + "formdefinitionfield_set-INITIAL_FORMS": "0", + "formdefinitionfield_set-MAX_NUM_FORMS": "1000", + "formdefinitionfield_set-MIN_NUM_FORMS": "0", + "formdefinitionfield_set-TOTAL_FORMS": n_fields, + "log_data": "on", + "mail_from": "", + "mail_subject": "", + "mail_to": "", + "mail_uploaded_files": "on", + "message_template": "", + "method": "POST", + "name": name, + "save_uploaded_files": "on", + "submit_label": "", + "success_clear": "on", + "success_message": "", + "success_redirect": "on", + "title": "", } for i in range(n_fields): data.update( { key.replace("NUM", str(i)): value - for (key, value) - in { - 'formdefinitionfield_set-NUM-field_class': 'django.forms.CharField', - 'formdefinitionfield_set-NUM-include_result': 'on', - 'formdefinitionfield_set-NUM-label': 'test %d' % i, - 'formdefinitionfield_set-NUM-name': 'test%d' % i, - 'formdefinitionfield_set-NUM-position': i, - 'formdefinitionfield_set-NUM-required': 'on', + for (key, value) in { + "formdefinitionfield_set-NUM-field_class": "django.forms.CharField", + "formdefinitionfield_set-NUM-include_result": "on", + "formdefinitionfield_set-NUM-label": "test %d" % i, + "formdefinitionfield_set-NUM-name": "test%d" % i, + "formdefinitionfield_set-NUM-position": i, + "formdefinitionfield_set-NUM-required": "on", }.items() } ) - admin_client.post( - "/admin/form_designer/formdefinition/add/", - data=data - ) + admin_client.post("/admin/form_designer/formdefinition/add/", data=data) fd = FormDefinition.objects.get(name=name) assert fd.formdefinitionfield_set.count() == n_fields @@ -73,19 +72,22 @@ def test_admin_create_view_creates_form(admin_client, n_fields): if key not in data: continue if value is True: - assert data[key] == 'on' + assert data[key] == "on" else: - if data[key] == '': + if data[key] == "": assert data[key] == value or value is None else: assert data[key] == value @pytest.mark.django_db -def test_admin_list_view_renders(admin_client, greeting_form_with_log): +def test_admin_list_view_renders_formlog(admin_client, greeting_form_with_log): log = greeting_form_with_log.logs.first() - greeting_value = log.data[0]['value'] - assert greeting_value in admin_client.get("/admin/form_designer/formlog/").content.decode() + greeting_value = log.data[0]["value"] + assert ( + greeting_value + in admin_client.get("/admin/form_designer/formlog/").content.decode() + ) @pytest.mark.django_db @@ -94,6 +96,6 @@ def test_admin_create_view_renders(admin_client, greeting_form_with_log): @pytest.mark.django_db -@pytest.mark.parametrize('format', ('CSV', 'XLS')) +@pytest.mark.parametrize("format", ("CSV", "XLS")) def test_admin_export_view(admin_client, greeting_form_with_log, format): assert admin_client.get("/admin/form_designer/formlog/export/%s/" % format).content diff --git a/form_designer/tests/test_basics.py b/form_designer/tests/test_basics.py index cedc4bb8..476258af 100644 --- a/form_designer/tests/test_basics.py +++ b/form_designer/tests/test_basics.py @@ -1,129 +1,140 @@ # -- encoding: UTF-8 -- +import pytest from base64 import b64decode - from django.contrib.auth.models import AnonymousUser from django.contrib.messages.storage.base import BaseStorage from django.core import mail from django.core.files.base import ContentFile, File from django.utils.crypto import get_random_string -import pytest - from form_designer import settings as fd_settings from form_designer.contrib.exporters.csv_exporter import CsvExporter from form_designer.contrib.exporters.xls_exporter import XlsExporter from form_designer.forms import DesignedForm -from form_designer.models import FormDefinition, FormDefinitionField, FormLog, FormValue +from form_designer.models import FormLog, FormValue from form_designer.views import process_form # https://raw.githubusercontent.com/mathiasbynens/small/master/jpeg.jpg VERY_SMALL_JPEG = b64decode( - '/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgK' - 'CgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/' - 'yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=' + "/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgK" + "CgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/" + "yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=" ) class OverriddenDesignedForm(DesignedForm): def clean_greeting(self): - return self.cleaned_data['greeting'].upper() + return self.cleaned_data["greeting"].upper() @pytest.mark.django_db -@pytest.mark.parametrize('push_messages', (False, True)) -@pytest.mark.parametrize('valid_data', (False, True)) -@pytest.mark.parametrize('method', ('GET', 'POST')) -@pytest.mark.parametrize('anon', (False, True)) -@pytest.mark.parametrize('override_form', (False, 'settings', 'kwarg')) +@pytest.mark.parametrize("push_messages", (False, True)) +@pytest.mark.parametrize("valid_data", (False, True)) +@pytest.mark.parametrize("method", ("GET", "POST")) +@pytest.mark.parametrize("anon", (False, True)) +@pytest.mark.parametrize("override_form", (False, "settings", "kwarg")) def test_simple_form( - monkeypatch, rf, admin_user, - greeting_form, push_messages, valid_data, method, anon, override_form + monkeypatch, + rf, + admin_user, + greeting_form, + push_messages, + valid_data, + method, + anon, + override_form, ): fd = greeting_form - message = 'zzz-å%sÖ' % get_random_string() + message = "zzz-å%sÖ" % get_random_string() data = { - 'greeting': message, - 'upload': ContentFile(VERY_SMALL_JPEG, name='hello.jpg'), - fd.submit_flag_name: 'true', + "greeting": message, + "upload": ContentFile(VERY_SMALL_JPEG, name="hello.jpg"), + fd.submit_flag_name: "true", } if not valid_data: - data.pop('greeting') + data.pop("greeting") - if method == 'POST': - request = rf.post('/', data) - elif method == 'GET': - data.pop('upload') # can't upload via GET - request = rf.get('/', data) + if method == "POST": + request = rf.post("/", data) + elif method == "GET": + data.pop("upload") # can't upload via GET + request = rf.get("/", data) - request.user = (AnonymousUser() if anon else admin_user) + request.user = AnonymousUser() if anon else admin_user request._messages = BaseStorage(request) kwargs = dict( push_messages=push_messages, disable_redirection=True, ) - if override_form == 'kwarg': - kwargs['form_class'] = OverriddenDesignedForm - elif override_form == 'settings': + if override_form == "kwarg": + kwargs["form_class"] = OverriddenDesignedForm + elif override_form == "settings": # Can't use the pytest-django settings fixture, since `form_designer.settings` # has non-lazy copies of Django settings taken at that module's import time. monkeypatch.setattr( fd_settings, - 'DESIGNED_FORM_CLASS', - 'form_designer.tests.test_basics.OverriddenDesignedForm' + "DESIGNED_FORM_CLASS", + "form_designer.tests.test_basics.OverriddenDesignedForm", ) context = process_form(request, fd, **kwargs) - assert context['form_success'] == valid_data + assert context["form_success"] == valid_data # Test that a message was (or was not) pushed assert len(request._messages._queued_messages) == int(push_messages) if valid_data: - if override_form: # If we've overridden the form, we expect an uppercase message + if ( + override_form + ): # If we've overridden the form, we expect an uppercase message message = message.upper() # Test that the form log was saved: flog = FormLog.objects.get(form_definition=fd) - assert flog == context['form_log'] # (and it's the same object in the context) - name_to_value = {d['name']: d['value'] for d in flog.data} - assert name_to_value['greeting'] == message - if name_to_value.get('upload'): - assert isinstance(name_to_value['upload'], File) + assert flog == context["form_log"] # (and it's the same object in the context) + name_to_value = {d["name"]: d["value"] for d in flog.data} + assert name_to_value["greeting"] == message + if name_to_value.get("upload"): + assert isinstance(name_to_value["upload"], File) if not anon: assert flog.created_by == admin_user # Test that the email was sent: sent_email = mail.outbox[-1] - assert message in sent_email.subject # (since we customized the subject with a template) - assert 'greetingbot' in sent_email.message().get("Reply-To") # (since we customized the reply-to address) - + assert ( + message in sent_email.subject + ) # (since we customized the subject with a template) + assert "greetingbot" in sent_email.message().get( + "Reply-To" + ) # (since we customized the reply-to address) @pytest.mark.django_db -@pytest.mark.parametrize('exporter', [ - CsvExporter, - XlsExporter, -]) -@pytest.mark.parametrize('n_logs', range(5)) +@pytest.mark.parametrize( + "exporter", + [ + CsvExporter, + XlsExporter, + ], +) +@pytest.mark.parametrize("n_logs", range(5)) def test_export(rf, greeting_form, exporter, n_logs): - message ='Térve' + message = "Térve" for n in range(n_logs): - fl = FormLog.objects.create( - form_definition=greeting_form - ) + fl = FormLog.objects.create(form_definition=greeting_form) FormValue.objects.create( form_log=fl, - field_name='greeting', + field_name="greeting", value="%s %d" % (message, n + 1), ) resp = exporter(greeting_form).export( request=rf.get("/"), - queryset=FormLog.objects.filter(form_definition=greeting_form) + queryset=FormLog.objects.filter(form_definition=greeting_form), ) - if 'csv' in resp['content-type']: + if "csv" in resp["content-type"]: # TODO: Improve CSV test? csv_data = resp.content.decode("utf8").splitlines() if n_logs > 0: # The file will be empty if no logs exist diff --git a/form_designer/tests/test_cms_plugin.py b/form_designer/tests/test_cms_plugin.py index d4231c95..c846dd38 100644 --- a/form_designer/tests/test_cms_plugin.py +++ b/form_designer/tests/test_cms_plugin.py @@ -1,36 +1,35 @@ +import pytest from distutils.version import StrictVersion as Ver - from django.contrib.auth.models import AnonymousUser from django.template import RequestContext from django.utils.crypto import get_random_string -import pytest - try: import cms from cms import api from cms.models import Page, Placeholder + from form_designer.contrib.cms_plugins.form_designer_form.cms_plugins import FormDesignerPlugin from form_designer.contrib.cms_plugins.form_designer_form.models import CMSFormDefinition from form_designer.models import FormDefinition, FormDefinitionField + CMS_VERSION = Ver(cms.__version__) except ImportError: - pytestmark = pytest.mark.skip('django-cms not importable') - + pytestmark = pytest.mark.skip("django-cms not importable") @pytest.mark.django_db def test_cms_plugin_renders_in_cms_page(rf): fd = FormDefinition.objects.create( - mail_to='test@example.com', - mail_subject='Someone sent you a greeting: {{ test }}' + mail_to="test@example.com", + mail_subject="Someone sent you a greeting: {{ test }}", ) field = FormDefinitionField.objects.create( form_definition=fd, - name='test', + name="test", label=get_random_string(), - field_class='django.forms.CharField', + field_class="django.forms.CharField", ) page = api.create_page("test", "page.html", "en") assert isinstance(page, Page) @@ -42,14 +41,16 @@ def test_cms_plugin_renders_in_cms_page(rf): request = rf.get("/") request.user = AnonymousUser() request.current_page = page - if CMS_VERSION >= Ver('3.4'): + if CMS_VERSION >= Ver("3.4"): from cms.plugin_rendering import ContentRenderer + renderer = ContentRenderer(request) context = RequestContext(request) - context['request'] = request + context["request"] = request content = renderer.render_plugin(plugin, context) else: from cms.page_rendering import render_page + response = render_page(request, page, "fi", "test") response.render() content = response.content.decode("utf8") diff --git a/form_designer/tests/test_messages.py b/form_designer/tests/test_messages.py index d1bc6bb4..c3301b7b 100644 --- a/form_designer/tests/test_messages.py +++ b/form_designer/tests/test_messages.py @@ -1,4 +1,5 @@ import pytest + from form_designer.forms import DesignedForm @@ -7,11 +8,14 @@ def test_custom_message_template(greeting_form): """ Test that custom message templates work as expected. """ - greeting_form.message_template = '{{ greeting }}, friend!' - form = DesignedForm(greeting_form, data={ - 'greeting': 'Hello', - }) + greeting_form.message_template = "{{ greeting }}, friend!" + form = DesignedForm( + greeting_form, + data={ + "greeting": "Hello", + }, + ) assert form.is_valid() greeting_form.log(form) mail = greeting_form.send_mail(form) - assert mail.body == 'Hello, friend!' + assert mail.body == "Hello, friend!" diff --git a/form_designer/uploads.py b/form_designer/uploads.py index 94015340..1110876c 100644 --- a/form_designer/uploads.py +++ b/form_designer/uploads.py @@ -1,7 +1,4 @@ -import hashlib import os -import uuid - from django.core.files.base import File from django.db.models.fields.files import FieldFile from django.forms.forms import NON_FIELD_ERRORS @@ -23,25 +20,34 @@ def clean_files(form): msg = None if uploaded_file is None: if field.required: - msg = _('This field is required.') + msg = _("This field is required.") else: continue else: file_size = uploaded_file.size total_upload_size += file_size - if not os.path.splitext(uploaded_file.name)[1].lstrip('.').lower() in \ - app_settings.ALLOWED_FILE_TYPES: - msg = _('This file type is not allowed.') + if ( + not os.path.splitext(uploaded_file.name)[1].lstrip(".").lower() + in app_settings.ALLOWED_FILE_TYPES + ): + msg = _("This file type is not allowed.") elif file_size > app_settings.MAX_UPLOAD_SIZE: - msg = _('Please keep file size under %(max_size)s. Current size is %(size)s.') % \ - {'max_size': filesizeformat(app_settings.MAX_UPLOAD_SIZE), - 'size': filesizeformat(file_size)} + msg = _( + "Please keep file size under %(max_size)s. Current size is %(size)s." + ) % { + "max_size": filesizeformat(app_settings.MAX_UPLOAD_SIZE), + "size": filesizeformat(file_size), + } if msg: form._errors[field.name] = form.error_class([msg]) if total_upload_size > app_settings.MAX_UPLOAD_TOTAL_SIZE: - msg = _('Please keep total file size under %(max)s. Current total size is %(current)s.') % \ - {"max": filesizeformat(app_settings.MAX_UPLOAD_TOTAL_SIZE), "current": filesizeformat(total_upload_size)} + msg = _( + "Please keep total file size under %(max)s. Current total size is %(current)s." + ) % { + "max": filesizeformat(app_settings.MAX_UPLOAD_TOTAL_SIZE), + "current": filesizeformat(total_upload_size), + } if NON_FIELD_ERRORS in form._errors: form._errors[NON_FIELD_ERRORS].append(msg) @@ -63,9 +69,12 @@ def handle_uploaded_files(form_definition, form): valid_file_name = storage.get_valid_name(uploaded_file.name) root, ext = os.path.splitext(valid_file_name) filename = storage.get_available_name( - os.path.join(app_settings.FILE_STORAGE_DIR, - form_definition.name, - '%s_%s%s' % (root, secret_hash, ext))) + os.path.join( + app_settings.FILE_STORAGE_DIR, + form_definition.name, + "%s_%s%s" % (root, secret_hash, ext), + ) + ) storage.save(filename, uploaded_file) form.cleaned_data[field.name] = StoredUploadedFile(filename) files.append(storage.path(filename)) @@ -84,7 +93,7 @@ def __init__(self, name): self.field = self def __getstate__(self): - return {'name': self.name, 'closed': False, '_committed': True, '_file': None} + return {"name": self.name, "closed": False, "_committed": True, "_file": None} def __setstate__(self, state): self.__dict__.update(state) @@ -94,10 +103,10 @@ def storage(self): return get_storage() def save(self, *args, **kwargs): - raise NotImplementedError('Static files are read-only') # pragma: no cover + raise NotImplementedError("Static files are read-only") # pragma: no cover def delete(self, *args, **kwargs): - raise NotImplementedError('Static files are read-only') # pragma: no cover + raise NotImplementedError("Static files are read-only") # pragma: no cover def __str__(self): return self.name diff --git a/form_designer/urls.py b/form_designer/urls.py index 347d81a7..2cd2adcc 100644 --- a/form_designer/urls.py +++ b/form_designer/urls.py @@ -1,6 +1,14 @@ from django.urls import re_path urlpatterns = [ - re_path(r'^(?P[-\w]+)/$', 'form_designer.views.detail', name='form_designer_detail'), - re_path(r'^h/(?P[-\w]+)/$', 'form_designer.views.detail_by_hash', name='form_designer_detail_by_hash'), + re_path( + r"^(?P[-\w]+)/$", + "form_designer.views.detail", + name="form_designer_detail", + ), + re_path( + r"^h/(?P[-\w]+)/$", + "form_designer.views.detail_by_hash", + name="form_designer_detail_by_hash", + ), ] diff --git a/form_designer/utils.py b/form_designer/utils.py index 01b589de..6c836e8f 100644 --- a/form_designer/utils.py +++ b/form_designer/utils.py @@ -1,7 +1,6 @@ import hashlib - -from django.utils.crypto import get_random_string from django.template import Context, Template, TemplateSyntaxError +from django.utils.crypto import get_random_string def get_random_hash(length=32): diff --git a/form_designer/views.py b/form_designer/views.py index b3f1fe73..724e49f4 100644 --- a/form_designer/views.py +++ b/form_designer/views.py @@ -1,20 +1,18 @@ import json from django.contrib import messages -from django.template.context_processors import csrf - from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404, render +from django.template.context_processors import csrf +from django.utils.http import urlencode from django.utils.module_loading import import_string from django.utils.translation import gettext_lazy as _ +from urllib.request import urlopen from form_designer import settings as app_settings from form_designer.models import FormDefinition from form_designer.signals import designedform_error, designedform_render, designedform_submit, designedform_success from form_designer.uploads import handle_uploaded_files -from django.utils.http import urlencode -from urllib.request import urlopen - def get_designed_form_class(): return import_string(app_settings.DESIGNED_FORM_CLASS) @@ -24,20 +22,20 @@ def check_recaptcha(request, context, push_messages): is_valid = True if not app_settings.USE_GOOGLE_RECAPTCHA: return is_valid - ''' Begin reCAPTCHA validation ''' - recaptcha_response = request.POST.get('g-recaptcha-response') - url = 'https://www.google.com/recaptcha/api/siteverify' + """ Begin reCAPTCHA validation """ + recaptcha_response = request.POST.get("g-recaptcha-response") + url = "https://www.google.com/recaptcha/api/siteverify" values = { - 'secret': app_settings.GOOGLE_RECAPTCHA_SECRET_KEY, - 'response': recaptcha_response + "secret": app_settings.GOOGLE_RECAPTCHA_SECRET_KEY, + "response": recaptcha_response, } - data = urlencode(values).encode('utf-8') + data = urlencode(values).encode("utf-8") response = urlopen(url, data=data) result = json.load(response) - ''' End reCAPTCHA validation ''' - if not result['success']: + """ End reCAPTCHA validation """ + if not result["success"]: is_valid = False - error_message = _('Invalid reCAPTCHA.') + error_message = _("Invalid reCAPTCHA.") if push_messages: messages.error(request, error_message) return is_valid @@ -46,37 +44,51 @@ def check_recaptcha(request, context, push_messages): def update_recaptcha_context(context): if not app_settings.USE_GOOGLE_RECAPTCHA: return - context.update({ - 'use_google_recaptcha': True, - 'google_recaptcha_site_key': app_settings.GOOGLE_RECAPTCHA_SITE_KEY, - }) - - -def process_form( - request, form_definition, extra_context=None, disable_redirection=False, push_messages=True, - form_class=None + context.update( + { + "use_google_recaptcha": True, + "google_recaptcha_site_key": app_settings.GOOGLE_RECAPTCHA_SITE_KEY, + } + ) + + +def process_form( # noqa: C901 + request, + form_definition, + extra_context=None, + disable_redirection=False, + push_messages=True, + form_class=None, ): if extra_context is None: extra_context = {} if form_class is None: form_class = get_designed_form_class() context = extra_context - success_message = form_definition.success_message or _('Thank you, the data was submitted successfully.') - error_message = form_definition.error_message or _('The data could not be submitted, please try again.') + success_message = form_definition.success_message or _( + "Thank you, the data was submitted successfully." + ) + error_message = form_definition.error_message or _( + "The data could not be submitted, please try again." + ) form_error = False form_success = False is_submit = False # If the form has been submitted... - if request.method == 'POST' and request.POST.get(form_definition.submit_flag_name): + if request.method == "POST" and request.POST.get(form_definition.submit_flag_name): form = form_class(form_definition, None, request.POST, request.FILES) is_submit = True - if request.method == 'GET' and request.GET.get(form_definition.submit_flag_name): + if request.method == "GET" and request.GET.get(form_definition.submit_flag_name): form = form_class(form_definition, None, request.GET) is_submit = True if is_submit: - designedform_submit.send(sender=process_form, context=context, - form_definition=form_definition, request=request) + designedform_submit.send( + sender=process_form, + context=context, + form_definition=form_definition, + request=request, + ) recaptcha_is_valid = check_recaptcha(request, context, push_messages) if form.is_valid() and recaptcha_is_valid: # Handle file uploads using storage object @@ -87,21 +99,29 @@ def process_form( messages.success(request, success_message) form_success = True - designedform_success.send(sender=process_form, context=context, - form_definition=form_definition, request=request) + designedform_success.send( + sender=process_form, + context=context, + form_definition=form_definition, + request=request, + ) if form_definition.log_data: - context['form_log'] = form_definition.log(form, request.user) + context["form_log"] = form_definition.log(form, request.user) if form_definition.mail_to: - context['form_mail_message'] = form_definition.send_mail(form, files) + context["form_mail_message"] = form_definition.send_mail(form, files) if form_definition.success_redirect and not disable_redirection: - return HttpResponseRedirect(form_definition.action or '?') + return HttpResponseRedirect(form_definition.action or "?") if form_definition.success_clear: form = form_class(form_definition) # clear form else: form_error = True - designedform_error.send(sender=process_form, context=context, - form_definition=form_definition, request=request) + designedform_error.send( + sender=process_form, + context=context, + form_definition=form_definition, + request=request, + ) if push_messages: messages.error(request, error_message) else: @@ -109,23 +129,29 @@ def process_form( form = form_class(form_definition, initial_data=request.GET) else: form = form_class(form_definition) - designedform_render.send(sender=process_form, context=context, - form_definition=form_definition, request=request) - - context.update({ - 'form_error': form_error, - 'form_success': form_success, - 'form_success_message': success_message, - 'form_error_message': error_message, - 'form': form, - 'form_definition': form_definition - }) + designedform_render.send( + sender=process_form, + context=context, + form_definition=form_definition, + request=request, + ) + + context.update( + { + "form_error": form_error, + "form_success": form_success, + "form_success_message": success_message, + "form_error_message": error_message, + "form": form, + "form_definition": form_definition, + } + ) context.update(csrf(request)) update_recaptcha_context(context) if form_definition.display_logged: - logs = form_definition.logs.all().order_by('created') - context.update({'logs': logs}) + logs = form_definition.logs.all().order_by("created") + context.update({"logs": logs}) return context @@ -134,14 +160,19 @@ def _form_detail_view(request, form_definition): result = process_form(request, form_definition) if isinstance(result, HttpResponseRedirect): return result - result.update({ - 'form_template': form_definition.form_template_name or app_settings.DEFAULT_FORM_TEMPLATE - }) - return render('html/formdefinition/detail.html', result) + result.update( + { + "form_template": form_definition.form_template_name + or app_settings.DEFAULT_FORM_TEMPLATE + } + ) + return render("html/formdefinition/detail.html", result) def detail(request, object_name): - form_definition = get_object_or_404(FormDefinition, name=object_name, require_hash=False) + form_definition = get_object_or_404( + FormDefinition, name=object_name, require_hash=False + ) return _form_detail_view(request, form_definition) diff --git a/setup.cfg b/setup.cfg index 7c1ad07f..a526bb35 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,12 +7,12 @@ exclude = *migrations* ignore = E309 [flake8] -exclude = - migrations +exclude = migrations,node_modules,.tox max-line-length = 120 max-complexity = 10 [isort] +profile=black atomic=true combine_as_imports=false indent=4 @@ -20,11 +20,10 @@ known_standard_library=token,tokenize,enum,importlib known_third_party=django length_sort=false line_length=120 -multi_line_output=5 +multi_line_output=3 not_skip=__init__.py order_by_type=false -skip= - migrations +skip=migrations,.tox wrap_length=120 [tool:pytest] diff --git a/setup.py b/setup.py index 8a6b5303..21babe85 100644 --- a/setup.py +++ b/setup.py @@ -1,30 +1,33 @@ # encoding=utf8 -from setuptools import setup, find_packages +from setuptools import find_packages, setup setup( - name='django-form-designer-ai', - version='1.0.1', - url='http://github.com/andersinno/django-form-designer-ai', - license='BSD', - maintainer='Anders Innovations Ltd', - maintainer_email='info@anders.fi', - packages=find_packages('.', include=[ - 'form_designer', - 'form_designer.*', - ]), + name="django-form-designer-ai", + version="1.0.1", + url="http://github.com/andersinno/django-form-designer-ai", + license="BSD", + maintainer="Anders Innovations Ltd", + maintainer_email="info@anders.fi", + packages=find_packages( + ".", + include=[ + "form_designer", + "form_designer.*", + ], + ), include_package_data=True, classifiers=[ - 'Development Status :: 4 - Beta', - 'Framework :: Django', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: BSD License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Topic :: Internet :: WWW/HTTP', + "Development Status :: 4 - Beta", + "Framework :: Django", + "Intended Audience :: Developers", + "License :: OSI Approved :: BSD License", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Topic :: Internet :: WWW/HTTP", ], install_requires=[ - 'django-picklefield', + "django-picklefield", ], zip_safe=False, ) From 3172d5601d803896ef79df69a682efd9ea5c27bf Mon Sep 17 00:00:00 2001 From: William Lindholm Date: Wed, 22 Jun 2022 09:43:29 +0300 Subject: [PATCH 10/10] Update version to 2.0.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 21babe85..6e2bea8a 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ setup( name="django-form-designer-ai", - version="1.0.1", + version="2.0.0", url="http://github.com/andersinno/django-form-designer-ai", license="BSD", maintainer="Anders Innovations Ltd",