prepare 0.6; form importers; better support for django 1.9 and django 1.10; various improvements and fixes;

This commit is contained in:
Artur Barseghyan 2015-12-16 03:16:00 +01:00
parent de2ebae999
commit ad9c4a7a4e
95 changed files with 905 additions and 242 deletions

View file

@ -15,6 +15,19 @@ are used for versioning (schema follows below):
0.3.4 to 0.4).
- All backwards incompatible changes are mentioned in this document.
0.5.19
------
2015-12-15
- New style urls everywhere.
0.5.18
------
2015-12-08
- Minor improvements. Adding request to the `get_form_field_instances` method
of the `FormElementPlugin`.
0.5.17
------
2015-10-22

View file

@ -46,3 +46,5 @@ recursive-include src/fobi/contrib/plugins/form_elements/content/content_image/t
recursive-include src/fobi/contrib/plugins/form_handlers/db_store/templates *
recursive-include src/fobi/contrib/plugins/form_handlers/mail/templates *
recursive-include src/fobi/contrib/plugins/form_handlers/http_repost/templates *
#recursive-include src/fobi/contrib/plugins/form_importers/mailchimp_importer/templates *

View file

@ -180,7 +180,7 @@ Add the following line to ``urlpatterns`` of your `urls` module.
.. code-block:: python
urlpatterns = patterns('',
urlpatterns = [
# ...
# DB Store plugin URLs
@ -195,7 +195,7 @@ Add the following line to ``urlpatterns`` of your `urls` module.
# ...
)
]
Update the database
^^^^^^^^^^^^^^^^^^^

View file

@ -75,19 +75,18 @@ Main features and highlights
<https://github.com/barseghyanartur/django-fobi/tree/stable/src/fobi/contrib/apps/mezzanine_integration>`_
(in a form of a Mezzanine page).
- Reordering of form elements using drag-n-drop.
- Data export (`db_store
- Data export (`DB store
<https://github.com/barseghyanartur/django-fobi/tree/stable/src/fobi/contrib/plugins/form_handlers/db_store>`_
form handler plugin) into XLS/CSV format.
- `Dynamic initial values`_ for form elements.
- Import/export forms to/from JSON format.
- Import forms from MailChimp using `mailchimp importer
<https://github.com/barseghyanartur/django-fobi/tree/stable/src/fobi/contrib/plugins/form_importers/mailchimp_importer>`_.
Roadmap
=======
Some of the upcoming/in-development features/improvements are:
- Form importers (and as a part of it - MailChimp integration,
which would allow to import forms from MailChimp into `django-fobi` using
a user-friendly wizard).
- Fieldsets.
See the `TODOS <https://raw.githubusercontent.com/barseghyanartur/django-fobi/master/TODOS.rst>`_
@ -114,10 +113,10 @@ Credentials:
Run demo locally
----------------
In order to be able to quickly evaluate the `django-fobi`, a demo app (with a quick
installer) has been created (works on Ubuntu/Debian, may work on other Linux
systems as well, although not guaranteed). Follow the instructions below for
having the demo running within a minute.
In order to be able to quickly evaluate the `django-fobi`, a demo app (with a
quick installer) has been created (works on Ubuntu/Debian, may work on other
Linux systems as well, although not guaranteed). Follow the instructions below
for having the demo running within a minute.
Grab the latest `django_fobi_example_app_installer.sh`:
@ -369,7 +368,7 @@ Defining the Sample textarea plugin.
form = SampleTextareaForm
group = "Samples" # Group to which the plugin belongs to
def get_form_field_instances(self):
def get_form_field_instances(self, request=None):
kwargs = {
'required': self.data.required,
'label': self.data.label,

View file

@ -4,21 +4,16 @@ Changelog for upcoming releases
---
yyyy-mm-ddd (upcoming).
This release contains minor backwards incompatible changes, related to the
change of the name of the "simple" theme into "django_admin_style" theme.
- Fieldsets.
- The "simple" theme has been renamed to "django_admin_style".
0.6
---
yyyy-mm-ddd (upcoming).
This release contains minor backwards incompatible changes, related to the
change of the name of the "simple" theme into "django_admin_style" theme.
- Mailchimp support.
- Kube framework integration (theme).
- PureCSS framework integration (theme).
- Skeleton framework integration (theme).
- Baseline framework integration (theme).
- Amazium framework integration (theme).
- The "simple" theme has been renamed to "django_admin_style".
- Internally, make a date when form has been created. Also keep track of when
the form has been last edited.
- Form importers (and as a part of it - MailChimp integration,
which would allow to import forms from MailChimp into `django-fobi` using
a user-friendly wizard).

View file

@ -260,7 +260,7 @@ Must haves
- Nicer styling for the radio button (Simple theme).
- Make it possible to provide an alternative rendering of the form field
in the correspondent form field plugin widget (in such a way, that it
falls back to the defaut rendering when no custom is available and
falls back to the default rendering when no custom is available and
uses the custom rendering if available). This should be done on the
widget level, so that it's not necessary to update the theme in case of
customisations made for one or more form field plugins (the rendering
@ -279,6 +279,8 @@ Must haves
`get_form_hidden_fields_errors` template tags into another template tag
library or product to reuse it in Django-dash as well. Move the permission
code from `decorators` into a separate package.
- Update the `djangocms_admin_style` theme, since it stopped looking nice
with the latest versions of the packages.
Should haves
============
@ -359,6 +361,7 @@ Should haves
- Think of making putting several actions (repair) into the management
interface (UI).
- Make Django's CSRF validation optional.
- Quiz mode (randomize the ordering of the form elements).
Could haves
===========
@ -389,6 +392,11 @@ Could haves
- Add option to redirect to another page.
- Make a Django<->Fobi list of supported fields with proper `referencies
<https://docs.djangoproject.com/en/1.7/ref/forms/fields/>`_.
- Kube framework integration (theme).
- PureCSS framework integration (theme).
- Skeleton framework integration (theme).
- Baseline framework integration (theme).
- Amazium framework integration (theme).
Would haves
===========

View file

@ -13,12 +13,6 @@ Prerequisites
- Django 1.5, 1.6, 1.7, 1.8
- Python >= 2.6.8, >= 2.7, >= 3.3
Note, that Django 1.8 is not yet proclaimed to be flawlessly supported. The
core and contrib packages (with no additional dependencies) have been tested
against the latest stable Django 1.8 release. All tests have successfully
passed, although it's yet too early to claim that Django 1.8 is fully
supported.
Key concepts
============
- Each form consists of elements. Form elements are divided

View file

@ -1,6 +1,6 @@
from __future__ import unicode_literals
from django.conf.urls import patterns, include, url
from django.conf.urls import include, url
from django.conf.urls.i18n import i18n_patterns
from django.contrib import admin
@ -41,17 +41,17 @@ admin.autodiscover()
# You can also change the ``home`` view to add your own functionality
# to the project's homepage.
urlpatterns = i18n_patterns("",
urlpatterns = i18n_patterns([
# Change the admin prefix here to use an alternate URL for the
# admin interface, which would be marginally more secure.
("^admin/", include(admin.site.urls)),
)
])
# ***********
# Fobi patterns
# ***********
urlpatterns += patterns('',
urlpatterns += [
# DB Store plugin URLs
url(r'^fobi/plugins/form-handlers/db-store/',
include('fobi.contrib.plugins.form_handlers.db_store.urls')),
@ -66,13 +66,13 @@ urlpatterns += patterns('',
# django-fobi public forms contrib app:
#url(r'^', include('fobi.contrib.apps.public_forms.urls')),
)
]
# ***********
# End fobi patterns
# ***********
urlpatterns += patterns('',
urlpatterns += [
# We don't want to presume how your homepage works, so here are a
# few patterns you can use to set it up.
@ -122,7 +122,7 @@ urlpatterns += patterns('',
# ``mezzanine.urls``, go right ahead and take the parts you want
# from it, and use them directly below instead of using
# ``mezzanine.urls``.
("^", include("mezzanine.urls")),
url("^", include("mezzanine.urls")),
# MOUNTING MEZZANINE UNDER A PREFIX
# ---------------------------------
@ -140,7 +140,7 @@ urlpatterns += patterns('',
# ("^%s/" % settings.SITE_PREFIX, include("mezzanine.urls"))
)
]
# Adds ``STATIC_URL`` to the context of error pages, so that error
# pages can use JS, CSS and images.

View file

@ -1,7 +1,7 @@
from django.conf.urls import patterns, include, url
from django.conf.urls import include, url
from django.contrib import admin
urlpatterns = patterns('',
urlpatterns = [
# Examples:
# url(r'^$', 'quick_start.views.home', name='home'),
# url(r'^blog/', include('blog.urls')),
@ -20,4 +20,4 @@ urlpatterns = patterns('',
# Edit URLs
url(r'^fobi/', include('fobi.urls.edit')),
#, namespace='fobi'
)
]

View file

@ -1,13 +1,12 @@
Django==1.7.4
Pillow==2.7.0
Unidecode==0.04.17
argparse==1.2.1
django-autoslug==1.7.2
django-fobi==0.4.18
easy-thumbnails==1.5
ipython==3.0.0-b1
Django==1.8.5
Pillow==3.0.0
Unidecode==0.04.18
argparse==1.4.0
django-autoslug==1.9.3
django-fobi==0.5.17
easy-thumbnails==2.2
ipython==4.0.0
ordereddict==1.1
requests==2.5.1
six==1.9.0
vishap==0.1.4
wsgiref==0.1.2
requests==2.8.1
six==1.10.0
vishap==0.1.5

View file

@ -3,6 +3,7 @@ Django
Jinja2
MarkupSafe
MySQL-python
mailchimp
#South
Sphinx
django-admin-tools>=0.5.2

View file

@ -3,11 +3,13 @@ Django>=1.8,<1.9
Jinja2
MarkupSafe
mailchimp
#MySQL-python
Sphinx
django-admin-tools>=0.5.2
django-autoslug==1.7.1
django-debug-toolbar==0.11.0
django-formtools
django-localeurl>=2.0.2
django-registration-redux>=1.1
docutils

View file

@ -0,0 +1,48 @@
alabaster==0.7.6
Babel==2.1.1
decorator==4.0.4
Django==1.9
django-admin-tools==0.7.1
django-autoslug==1.9.3
django-debug-toolbar==0.11
django-fobi==0.5.18
django-formtools==1.0
django-localeurl==2.0.2
django-nine==0.1.6
django-nonefield==0.1
django-registration-redux==1.3a0
docopt==0.4.0
docutils==0.12
easy-thumbnails==2.3
ipdb==0.8.1
ipython==4.0.0
ipython-genutils==0.1.0
Jinja2==2.8
mailchimp==2.0.9
MarkupSafe==0.23
ordereddict==1.1
path.py==8.1.2
pexpect==4.0.1
pickleshare==0.5
Pillow==3.0.0
pluggy==0.3.1
ptyprocess==0.5
py==1.4.30
Pygments==2.0.2
pytz==2015.6
requests==2.8.1
selenium==2.48.0
simple-timer==0.2
simplegeneric==0.8.1
simplejson==3.8.0
six==1.10.0
snowballstemmer==1.2.0
Sphinx==1.3.1
sphinx-rtd-theme==0.1.9
sqlparse==0.1.17
tox==2.1.1
traitlets==4.0.0
Unidecode==0.4.18
virtualenv==13.1.2
vishap==0.1.5
wheel==0.24.0

View file

@ -1,5 +1,7 @@
from django.conf.urls import patterns, url
from django.conf.urls import url
urlpatterns = patterns('foo.views',
url(r'^endpoint/$', view='endpoint', name='foo.endpoint'),
)
from foo.views import endpoint as foo_views_endpoint
urlpatterns = [
url(r'^endpoint/$', view=foo_views_endpoint, name='foo.endpoint'),
]

View file

@ -4,6 +4,7 @@ PROJECT_DIR = lambda base : os.path.abspath(os.path.join(os.path.dirname(__file_
DEBUG = True
DEBUG_TOOLBAR = not True
TEMPLATE_DEBUG = not True
DEV = True
DATABASES = {
'default': {

View file

@ -21,7 +21,7 @@ class RadioInputPlugin(FormFieldPlugin):
group = _("Fields")
form = RadioInputForm
def get_form_field_instances(self):
def get_form_field_instances(self, request=None):
"""
Get form field instances.
"""

View file

@ -20,7 +20,7 @@ class SelectModelObjectInputPlugin(FormFieldPlugin):
group = _("Fields")
form = SelectModelObjectInputForm
def get_form_field_instances(self):
def get_form_field_instances(self, request=None):
"""
Get form field instances.
"""

View file

@ -0,0 +1,2 @@
#workon fobi
./manage.py runserver 0.0.0.0:8000 --traceback -v 3 --settings=settings_bootstrap3_theme_django_1_9 --traceback -v 3

View file

@ -8,7 +8,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(__file__))
DEBUG = False
DEBUG_TOOLBAR = False
TEMPLATE_DEBUG = DEBUG
DEV = False
ADMINS = (
# ('Your Name', 'your_email@example.com'),
@ -101,12 +101,67 @@ STATICFILES_FINDERS = (
# Make this unique, and don't share it with anybody.
SECRET_KEY = '97818c*w97Zi8a-m^1coRRrmurMI6+q5_kyn*)s@(*_Pk6q423'
# List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
'django.template.loaders.eggs.Loader',
)
from nine.versions import DJANGO_GTE_1_7, DJANGO_GTE_1_8
if DJANGO_GTE_1_8:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
#'APP_DIRS': True,
'DIRS': [PROJECT_DIR('templates'),],
'OPTIONS': {
'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.contrib.messages.context_processors.messages",
"django.core.context_processors.request",
"fobi.context_processors.theme", # Important!
"fobi.context_processors.dynamic_values", # Optional
],
'loaders': [
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
'django.template.loaders.eggs.Loader',
'admin_tools.template_loaders.Loader',
],
'debug': DEBUG,
}
},
]
else:
TEMPLATE_DEBUG = DEBUG
# List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
'django.template.loaders.eggs.Loader',
'admin_tools.template_loaders.Loader',
)
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.contrib.messages.context_processors.messages",
"django.core.context_processors.request",
"fobi.context_processors.theme", # Important!
"fobi.context_processors.dynamic_values", # Optional
)
TEMPLATE_DIRS = (
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
PROJECT_DIR('templates'),
)
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
@ -124,26 +179,6 @@ ROOT_URLCONF = 'urls'
# Python dotted path to the WSGI application used by Django's runserver.
WSGI_APPLICATION = 'wsgi.application'
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.contrib.messages.context_processors.messages",
"django.core.context_processors.request",
"fobi.context_processors.theme", # Important!
"fobi.context_processors.dynamic_values", # Optional
)
TEMPLATE_DIRS = (
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
PROJECT_DIR('templates'),
)
#FIXTURE_DIRS = (
# PROJECT_DIR(os.path.join('..', 'fixtures'))
#)
@ -243,6 +278,13 @@ INSTALLED_APPS = (
'fobi.contrib.plugins.form_handlers.http_repost',
'fobi.contrib.plugins.form_handlers.mail',
# ***********************************************************************
# ***********************************************************************
# ************************* Fobi form importers *************************
# ***********************************************************************
# ***********************************************************************
'fobi.contrib.plugins.form_importers.mailchimp_importer',
# ***********************************************************************
# ***********************************************************************
# ************************** Fobi themes ********************************
@ -505,7 +547,6 @@ LOGGING = {
}
# Make settings quite compatible among various Django versions used.
from nine.versions import DJANGO_GTE_1_7, DJANGO_GTE_1_8
if DJANGO_GTE_1_7 or DJANGO_GTE_1_8:
INSTALLED_APPS = list(INSTALLED_APPS)
@ -557,7 +598,7 @@ if DEBUG and DEBUG_TOOLBAR:
except ImportError:
pass
if DEBUG and TEMPLATE_DEBUG:
if DEBUG:
try:
# Make sure the django-template-debug is installed. You can then
# in templates use it as follows:
@ -571,3 +612,7 @@ if DEBUG and TEMPLATE_DEBUG:
except ImportError:
pass
# Make the `django-fobi` package available without installation.
if DEV:
import sys
sys.path.insert(0, os.path.abspath('../../src'))

View file

@ -0,0 +1,19 @@
from settings import *
INSTALLED_APPS = list(INSTALLED_APPS)
try:
INSTALLED_APPS.remove('south') if 'south' in INSTALLED_APPS else None
INSTALLED_APPS.remove('tinymce') if 'tinymce' in INSTALLED_APPS else None
except Exception as e:
pass
try:
INSTALLED_APPS.remove('admin_tools') \
if 'admin_tools' in INSTALLED_APPS else None
INSTALLED_APPS.remove('admin_tools.menu') \
if 'admin_tools.menu' in INSTALLED_APPS else None
INSTALLED_APPS.remove('admin_tools.dashboard') \
if 'admin_tools.dashboard' in INSTALLED_APPS else None
except Exception as e:
pass

View file

@ -12,6 +12,7 @@ INSTALLED_APPS += [
# Some plugins
'djangocms_picture',
'djangocms_snippet',
'treebeard',
'fobi.contrib.apps.djangocms_integration', # Fobi DjangoCMS app
@ -41,12 +42,20 @@ MIDDLEWARE_CLASSES += [
#'django.middleware.cache.FetchFromCacheMiddleware',
]
TEMPLATE_CONTEXT_PROCESSORS = list(TEMPLATE_CONTEXT_PROCESSORS)
TEMPLATE_CONTEXT_PROCESSORS += [
'cms.context_processors.media',
'sekizai.context_processors.sekizai',
'cms.context_processors.cms_settings',
]
from nine.versions import DJANGO_GTE_1_8
if DJANGO_GTE_1_8:
TEMPLATES[0]['OPTIONS']['context_processors'] += [
'cms.context_processors.cms_settings',
'sekizai.context_processors.sekizai',
'cms.context_processors.cms_settings',
]
else:
TEMPLATE_CONTEXT_PROCESSORS = list(TEMPLATE_CONTEXT_PROCESSORS)
TEMPLATE_CONTEXT_PROCESSORS += [
'cms.context_processors.cms_settings',
'sekizai.context_processors.sekizai',
'cms.context_processors.cms_settings',
]
#FOBI_DEFAULT_THEME = 'bootstrap3'
#FOBI_DEFAULT_THEME = 'foundation5'

View file

@ -1,4 +1,4 @@
from django.conf.urls import patterns, include, url
from django.conf.urls import include, url
from django.conf import settings
from django.contrib import admin
@ -27,7 +27,7 @@ FOBI_EDIT_URLS_PREFIX = ''
if DEFAULT_THEME in ('simple', 'djangocms_admin_style_theme'):
FOBI_EDIT_URLS_PREFIX = 'admin/'
urlpatterns = patterns('',
urlpatterns = [
# DB Store plugin URLs
url(r'^fobi/plugins/form-handlers/db-store/',
include('fobi.contrib.plugins.form_handlers.db_store.urls')), #,namespace='fobi'
@ -42,7 +42,7 @@ urlpatterns = patterns('',
url(r'^admin/', include(admin.site.urls)),
# django-registration URLs:
(r'^accounts/', include('registration.backends.default.urls')),
url(r'^accounts/', include('registration.backends.default.urls')),
# foo URLs:
url(r'^foo/', include('foo.urls')),
@ -51,7 +51,7 @@ urlpatterns = patterns('',
# django-fobi public forms contrib app:
#url(r'^', include('fobi.contrib.apps.public_forms.urls')),
)
]
# Serving media and static in debug/developer mode.
if settings.DEBUG:
@ -63,16 +63,16 @@ if settings.DEBUG:
if 'feincms' in settings.INSTALLED_APPS:
from page.models import Page
Page
urlpatterns += patterns('',
urlpatterns += [
url(r'^pages/', include('feincms.urls')),
)
]
# Conditionally including DjangoCMS URls in case if
# DjangoCMS in installed apps.
if 'cms' in settings.INSTALLED_APPS:
urlpatterns += patterns('',
urlpatterns += [
url(r'^cms-pages/', include('cms.urls')),
)
]
# Conditionally including Captcha URls in case if
# Captcha in installed apps.
@ -80,6 +80,6 @@ try:
from captcha.fields import ReCaptchaField
except ImportError as e:
if 'captcha' in settings.INSTALLED_APPS:
urlpatterns += patterns('',
urlpatterns += [
url(r'^captcha/', include('captcha.urls')),
)
]

View file

@ -9,7 +9,7 @@ class SampleTextareaPlugin(FormFieldPlugin):
form = SampleTextareaForm
group = "Samples" # Group to which the plugin belongs to
def get_form_field_instances(self):
def get_form_field_instances(self, request=None):
kwargs = {
'required': self.data.required,
'label': self.data.label,

View file

@ -1,4 +1,4 @@
from django.conf.urls import patterns, include, url
from django.conf.urls import include, url
from django.conf import settings
from django.contrib import admin
@ -22,7 +22,7 @@ fobi_home_template = fobi_theme_home_template_mapping.get(
'home/base.html'
)
urlpatterns = patterns('',
urlpatterns = [
# DB Store plugin URLs
url(r'^fobi/plugins/form-handlers/db-store/',
include('fobi.contrib.plugins.form_handlers.db_store.urls')),
@ -36,7 +36,7 @@ urlpatterns = patterns('',
url(r'^admin/', include(admin.site.urls)),
# django-registration URLs:
(r'^accounts/', include('registration.backends.default.urls')),
url(r'^accounts/', include('registration.backends.default.urls')),
# foo URLs:
url(r'^foo/', include('foo.urls')),
@ -45,7 +45,7 @@ urlpatterns = patterns('',
# django-fobi public forms contrib app:
#url(r'^', include('fobi.contrib.apps.public_forms.urls')),
)
]
# Serving media and static in debug/developer mode.
if settings.DEBUG:
@ -57,6 +57,6 @@ if settings.DEBUG:
if 'feincms' in settings.INSTALLED_APPS:
from page.models import Page
Page
urlpatterns += patterns('',
urlpatterns += [
url(r'', include('feincms.urls')),
)
]

View file

@ -1,5 +1,3 @@
#pip install -r examples/requirements.txt --allow-all-external --allow-unverified django-admin-tools
#cd ..
pip install -r examples/requirements.txt
python setup.py install
mkdir -p examples/logs examples/db examples/media examples/media/static examples/media/fobi_plugins/content_image
@ -8,4 +6,3 @@ python examples/simple/manage.py collectstatic --noinput
python examples/simple/manage.py syncdb --noinput
python examples/simple/manage.py migrate --noinput
python examples/simple/manage.py fobi_create_test_data
#cd scripts

View file

@ -69,7 +69,7 @@ for static_dir in static_dirs:
for locale_dir in locale_dirs:
locale_files += [os.path.join(locale_dir, f) for f in os.listdir(locale_dir)]
version = '0.5.17'
version = '0.6'
install_requires = [
'Pillow>=2.0.0',
@ -78,10 +78,12 @@ install_requires = [
'django-nonefield>=0.1',
'ordereddict>=1.1',
'six>=1.4.1',
'easy-thumbnails>=1.4',
'vishap>=0.1.3,<2.0',
'Unidecode>=0.04.1',
'django-nine>=0.1.6',
]
# There are also conditional PY3/PY2 requirements. Scroll down to see them.
tests_require = [
'selenium',

View file

@ -1,6 +1,6 @@
__title__ = 'django-fobi'
__version__ = '0.5.17'
__build__ = 0x000046
__version__ = '0.6'
__build__ = 0x000049
__author__ = 'Artur Barseghyan <artur.barseghyan@gmail.com>'
__copyright__ = '2014-2015 Artur Barseghyan'
__license__ = 'GPL 2.0/LGPL 2.1'

View file

@ -9,7 +9,7 @@ from django.utils.translation import ugettext_lazy as _
from django.utils.html import strip_tags
from django.contrib.admin.views.decorators import staff_member_required
from django.utils.decorators import method_decorator
from django.conf.urls import patterns, url
from django.conf.urls import url
from django.shortcuts import render_to_response, redirect
from django.template import RequestContext
from django.contrib import messages
@ -389,11 +389,11 @@ class FormElementAdmin(BasePluginModelAdmin):
return 'admin:fobi_formelement_changelist'
def get_urls(self):
my_urls = patterns('',
my_urls = [
# Bulk change plugins
url(r'^bulk-change-form-element-plugins/$', self.bulk_change_plugins,
name='bulk_change_form_element_plugins'),
)
]
return my_urls + super(FormElementAdmin, self).get_urls()
@ -418,11 +418,11 @@ class FormHandlerAdmin(BasePluginModelAdmin):
return 'admin:fobi_formhandler_changelist'
def get_urls(self):
my_urls = patterns('',
my_urls = [
# Bulk change plugins
url(r'^bulk-change-form-handler-plugins/$', self.bulk_change_plugins,
name='bulk_change_form_handler_plugins'),
)
]
return my_urls + super(FormHandlerAdmin, self).get_urls()

View file

@ -36,7 +36,6 @@ import traceback
import logging
import copy
import uuid
#import json
import re
import simplejson as json
@ -52,13 +51,11 @@ from django import forms
from django.forms import ModelForm
from django.http import Http404
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import AnonymousUser
from django.test import RequestFactory
from django.template import RequestContext, Template, Context
from django.template import RequestContext, Template
from nine.versions import DJANGO_GTE_1_8
from nine.versions import DJANGO_GTE_1_7
if DJANGO_GTE_1_8:
if DJANGO_GTE_1_7:
from django.forms.utils import ErrorList
else:
from django.forms.util import ErrorList
@ -77,9 +74,8 @@ from fobi.exceptions import (
FormElementPluginDoesNotExist, FormHandlerPluginDoesNotExist
)
from fobi.helpers import (
uniquify_sequence, map_field_name_to_label, clean_dict,
map_field_name_to_label, get_ignorable_form_values, safe_text,
StrippedRequest,
uniquify_sequence, clean_dict, map_field_name_to_label,
get_ignorable_form_values, safe_text, StrippedRequest,
)
from fobi.data_structures import SortableDict
@ -151,6 +147,7 @@ class BaseTheme(object):
base_view_template = None
base_edit_template = None
form_snippet_template_name = 'fobi/generic/snippets/form_snippet.html'
form_wizard_template = 'fobi/generic/snippets/form_wizard.html'
form_view_snippet_template_name = None
form_edit_snippet_template_name = None
form_properties_snippet_template_name = \
@ -192,6 +189,8 @@ class BaseTheme(object):
import_form_entry_template = 'fobi/generic/import_form_entry.html'
import_form_entry_ajax_template = 'fobi/generic/import_form_entry_ajax.html'
form_importer_template = 'fobi/generic/form_importer.html'
form_importer_ajax_template = 'fobi/generic/form_importer_ajax.html'
# *************************************************************************
# ******************** Extras that make things easy ***********************
@ -1151,7 +1150,9 @@ class FormElementPlugin(BasePlugin):
# methods in plugins). In DEBUG mode raise an exception if something
# goes wrong. Otherwise - skip the element.
try:
form_field_instances = self.get_form_field_instances()
form_field_instances = self.get_form_field_instances(
request=request
)
except AttributeError as e:
if DEBUG:
raise e
@ -1230,7 +1231,7 @@ class FormElementPlugin(BasePlugin):
return processed_field_instances
def get_form_field_instances(self):
def get_form_field_instances(self, request=None):
"""
Gets the instances of form fields, that plugin contains.
@ -1821,7 +1822,9 @@ form_handler_plugin_widget_registry = FormHandlerPluginWidgetRegistry()
def ensure_autodiscover():
"""
Ensures that plugins are autodiscovered.
Ensures that plugins are auto-discovered. The form callbacks registry
is intentionally left out, since they will be auto-discovered in
any case if other modules are discovered.
"""
if not (form_element_plugin_registry._registry
and form_handler_plugin_registry._registry

View file

@ -2,12 +2,13 @@ __title__ = 'fobi.context_processors'
__author__ = 'Artur Barseghyan <artur.barseghyan@gmail.com>'
__copyright__ = 'Copyright (c) 2014 Artur Barseghyan'
__license__ = 'GPL 2.0/LGPL 2.1'
__all__ = ('theme',)
__all__ = ('theme', 'dynamic_values', 'form_importers',)
import datetime
from fobi.base import get_theme
from fobi.helpers import StrippedRequest
from fobi.form_importers import get_form_impoter_plugin_urls
def theme(request):
"""
@ -29,3 +30,11 @@ def dynamic_values(request):
'today': datetime.date.today(),
}
}
def form_importers(request):
"""
Form importers.
"""
return {
'form_importers': get_form_impoter_plugin_urls(),
}

View file

@ -54,7 +54,7 @@ class ContentImagePlugin(FormElementPlugin):
)
return self.get_cloned_plugin_data(update={'file': cloned_image})
def get_form_field_instances(self):
def get_form_field_instances(self, request=None):
"""
Get form field instances.
"""

View file

@ -31,7 +31,7 @@ class ContentTextPlugin(FormElementPlugin):
"""
self.data.name = "{0}_{1}".format(self.uid, uuid4())
def get_form_field_instances(self):
def get_form_field_instances(self, request=None):
"""
Get form field instances.
"""

View file

@ -32,7 +32,7 @@ class ContentVideoPlugin(FormElementPlugin):
"""
self.data.name = "{0}_{1}".format(self.uid, uuid4())
def get_form_field_instances(self):
def get_form_field_instances(self, request=None):
"""
Get form field instances.
"""

View file

@ -21,7 +21,7 @@ class BooleanSelectPlugin(FormFieldPlugin):
group = _("Fields")
form = BooleanSelectForm
def get_form_field_instances(self):
def get_form_field_instances(self, request=None):
"""
Get form field instances.
"""

View file

@ -29,7 +29,7 @@ class CheckboxSelectMultipleInputPlugin(FormFieldPlugin):
group = _("Fields")
form = CheckboxSelectMultipleInputForm
def get_form_field_instances(self):
def get_form_field_instances(self, request=None):
"""
Get form field instances.
"""

View file

@ -26,7 +26,7 @@ class DateInputPlugin(FormFieldPlugin):
group = _("Fields")
form = DateInputForm
def get_form_field_instances(self):
def get_form_field_instances(self, request=None):
"""
Get form field instances.
"""

View file

@ -24,7 +24,7 @@ class DateDropDownInputPlugin(FormFieldPlugin):
group = _("Fields")
form = DateDropDownInputForm
def get_form_field_instances(self):
def get_form_field_instances(self, request=None):
"""
Get form field instances.
"""

View file

@ -26,7 +26,7 @@ class DateTimeInputPlugin(FormFieldPlugin):
group = _("Fields")
form = DateTimeInputForm
def get_form_field_instances(self):
def get_form_field_instances(self, request=None):
"""
Get form field instances.
"""

View file

@ -26,7 +26,7 @@ class DecimalInputPlugin(FormFieldPlugin):
group = _("Fields")
form = DecimalInputForm
def get_form_field_instances(self):
def get_form_field_instances(self, request=None):
"""
Get form field instances.
"""

View file

@ -26,7 +26,7 @@ class EmailInputPlugin(FormFieldPlugin):
group = _("Fields")
form = EmailInputForm
def get_form_field_instances(self):
def get_form_field_instances(self, request=None):
"""
Get form field instances.
"""

View file

@ -27,7 +27,7 @@ class FileInputPlugin(FormFieldPlugin):
group = _("Fields")
form = FileInputForm
def get_form_field_instances(self):
def get_form_field_instances(self, request=None):
"""
Get form field instances.
"""

View file

@ -24,7 +24,7 @@ class FloatInputPlugin(FormFieldPlugin):
group = _("Fields")
form = FloatInputForm
def get_form_field_instances(self):
def get_form_field_instances(self, request=None):
"""
Get form field instances.
"""

View file

@ -25,7 +25,7 @@ class HiddenInputPlugin(FormFieldPlugin):
form = HiddenInputForm
is_hidden = True
def get_form_field_instances(self):
def get_form_field_instances(self, request=None):
"""
Get form field instances.
"""

View file

@ -24,7 +24,7 @@ class InputPlugin(FormFieldPlugin):
group = _("Fields")
form = InputForm
def get_form_field_instances(self):
def get_form_field_instances(self, request=None):
"""
Get form field instances.
"""

View file

@ -24,7 +24,7 @@ class IntegerInputPlugin(FormFieldPlugin):
group = _("Fields")
form = IntegerInputForm
def get_form_field_instances(self):
def get_form_field_instances(self, request=None):
"""
Get form field instances.
"""

View file

@ -24,7 +24,7 @@ class IPAddressInputPlugin(FormFieldPlugin):
group = _("Fields")
form = IPAddressInputForm
def get_form_field_instances(self):
def get_form_field_instances(self, request=None):
"""
Get form field instances.
"""

View file

@ -24,7 +24,7 @@ class NullBooleanSelectPlugin(FormFieldPlugin):
group = _("Fields")
form = NullBooleanSelectForm
def get_form_field_instances(self):
def get_form_field_instances(self, request=None):
"""
Get form field instances.
"""

View file

@ -24,7 +24,7 @@ class PasswordInputPlugin(FormFieldPlugin):
group = _("Fields")
form = PasswordInputForm
def get_form_field_instances(self):
def get_form_field_instances(self, request=None):
"""
Get form field instances.
"""

View file

@ -29,7 +29,7 @@ class RadioInputPlugin(FormFieldPlugin):
group = _("Fields")
form = RadioInputForm
def get_form_field_instances(self):
def get_form_field_instances(self, request=None):
"""
Get form field instances.
"""

View file

@ -26,7 +26,7 @@ class RegexInputPlugin(FormFieldPlugin):
group = _("Fields")
form = RegexInputForm
def get_form_field_instances(self):
def get_form_field_instances(self, request=None):
"""
Get form field instances.
"""

View file

@ -32,42 +32,67 @@ class RegexInputForm(forms.Form, BaseFormFieldPluginForm):
label = forms.CharField(
label = _("Label"),
required = True,
widget = forms.widgets.TextInput(attrs={'class': theme.form_element_html_class})
widget = forms.widgets.TextInput(
attrs={'class': theme.form_element_html_class}
)
)
name = forms.CharField(
label = _("Name"),
required = True,
widget = forms.widgets.TextInput(attrs={'class': theme.form_element_html_class})
widget = forms.widgets.TextInput(
attrs={'class': theme.form_element_html_class}
)
)
help_text = forms.CharField(
label = _("Help text"),
required = False,
widget = forms.widgets.Textarea(attrs={'class': theme.form_element_html_class})
widget = forms.widgets.Textarea(
attrs={'class': theme.form_element_html_class}
)
)
regex = forms.RegexField(
label = _("Regex"),
required = True,
widget = forms.widgets.TextInput(attrs={'class': theme.form_element_html_class}),
regex = ""
)
widget = forms.widgets.TextInput(
attrs={'class': theme.form_element_html_class}
),
regex = "",
help_text = _("Enter a valid regular expression. A couple of common "
"examples are listed below.<br/>"
"- Allow a single digit from 1 to 9 (example value 6): "
"<code>^[1-9]$</code><br/>"
"- Allow any combination of characters from a to z, "
"including capitals (example value abcXYZ):"
"<code>^([a-zA-Z])+$</code><br/>"
"- Allow a hex value (example value #a5c125:"
"<code>^#?([a-f0-9]{6}|[a-f0-9]{3})$</code><br/>")
)
initial = forms.CharField(
label = _("Initial"),
required = False,
widget = forms.widgets.TextInput(attrs={'class': theme.form_element_html_class})
widget = forms.widgets.TextInput(
attrs={'class': theme.form_element_html_class}
)
)
max_length = forms.IntegerField(
label = _("Max length"),
required = True,
widget = forms.widgets.TextInput(attrs={'class': theme.form_element_html_class}),
widget = forms.widgets.TextInput(
attrs={'class': theme.form_element_html_class}
),
initial = DEFAULT_MAX_LENGTH
)
)
required = forms.BooleanField(
label = _("Required"),
required = False,
widget = forms.widgets.CheckboxInput(attrs={'class': theme.form_element_checkbox_html_class})
widget = forms.widgets.CheckboxInput(
attrs={'class': theme.form_element_checkbox_html_class}
)
)
placeholder = forms.CharField(
label = _("Placeholder"),
required = False,
widget = forms.widgets.TextInput(attrs={'class': theme.form_element_html_class})
widget = forms.widgets.TextInput(
attrs={'class': theme.form_element_html_class}
)
)

View file

@ -29,7 +29,7 @@ class SelectInputPlugin(FormFieldPlugin):
group = _("Fields")
form = SelectInputForm
def get_form_field_instances(self):
def get_form_field_instances(self, request=None):
"""
Get form field instances.
"""

View file

@ -15,6 +15,14 @@ from fobi.constants import (
)
from fobi.helpers import safe_text, get_app_label_and_model_name
from nine.versions import DJANGO_GTE_1_7
if DJANGO_GTE_1_7:
from django.apps import apps
get_model = apps.get_model
else:
from django.db.models import get_model
from . import UID
from .forms import SelectModelObjectInputForm
from .settings import SUBMIT_VALUE_AS
@ -30,12 +38,12 @@ class SelectModelObjectInputPlugin(FormFieldPlugin):
group = _("Fields")
form = SelectModelObjectInputForm
def get_form_field_instances(self):
def get_form_field_instances(self, request=None):
"""
Get form field instances.
"""
app_label, model_name = get_app_label_and_model_name(self.data.model)
model = models.get_model(app_label, model_name)
model = get_model(app_label, model_name)
queryset = model._default_manager.all()
kwargs = {

View file

@ -16,6 +16,14 @@ from fobi.constants import (
)
from fobi.helpers import safe_text, get_app_label_and_model_name
from nine.versions import DJANGO_GTE_1_7
if DJANGO_GTE_1_7:
from django.apps import apps
get_model = apps.get_model
else:
from django.db.models import get_model
from . import UID
from .forms import SelectMPTTModelObjectInputForm
from .settings import SUBMIT_VALUE_AS
@ -31,12 +39,12 @@ class SelectMPTTModelObjectInputPlugin(FormFieldPlugin):
group = _("Fields")
form = SelectMPTTModelObjectInputForm
def get_form_field_instances(self):
def get_form_field_instances(self, request=None):
"""
Get form field instances.
"""
app_label, model_name = get_app_label_and_model_name(self.data.model)
model = models.get_model(app_label, model_name)
model = get_model(app_label, model_name)
queryset = model._default_manager.all()
kwargs = {

View file

@ -29,7 +29,7 @@ class SelectMultipleInputPlugin(FormFieldPlugin):
group = _("Fields")
form = SelectMultipleInputForm
def get_form_field_instances(self):
def get_form_field_instances(self, request=None):
"""
Get form field instances.
"""

View file

@ -17,6 +17,14 @@ from fobi.constants import (
)
from fobi.helpers import safe_text, get_app_label_and_model_name
from nine.versions import DJANGO_GTE_1_7
if DJANGO_GTE_1_7:
from django.apps import apps
get_model = apps.get_model
else:
from django.db.models import get_model
from . import UID
from .forms import SelectMultipleModelObjectsInputForm
from .settings import SUBMIT_VALUE_AS
@ -32,12 +40,12 @@ class SelectMultipleModelObjectsInputPlugin(FormFieldPlugin):
group = _("Fields")
form = SelectMultipleModelObjectsInputForm
def get_form_field_instances(self):
def get_form_field_instances(self, request=None):
"""
Get form field instances.
"""
app_label, model_name = get_app_label_and_model_name(self.data.model)
model = models.get_model(app_label, model_name)
model = get_model(app_label, model_name)
queryset = model._default_manager.all()
kwargs = {

View file

@ -18,6 +18,14 @@ from fobi.constants import (
)
from fobi.helpers import safe_text, get_app_label_and_model_name
from nine.versions import DJANGO_GTE_1_7
if DJANGO_GTE_1_7:
from django.apps import apps
get_model = apps.get_model
else:
from django.db.models import get_model
from . import UID
from .forms import SelectMultipleMPTTModelObjectsInputForm
from .settings import SUBMIT_VALUE_AS
@ -33,12 +41,12 @@ class SelectMultipleMPTTModelObjectsInputPlugin(FormFieldPlugin):
group = _("Fields")
form = SelectMultipleMPTTModelObjectsInputForm
def get_form_field_instances(self):
def get_form_field_instances(self, request=None):
"""
Get form field instances.
"""
app_label, model_name = get_app_label_and_model_name(self.data.model)
model = models.get_model(app_label, model_name)
model = get_model(app_label, model_name)
queryset = model._default_manager.all()
kwargs = {

View file

@ -24,7 +24,7 @@ class SlugInputPlugin(FormFieldPlugin):
group = _("Fields")
form = SlugInputForm
def get_form_field_instances(self):
def get_form_field_instances(self, request=None):
"""
Get form field instances.
"""

View file

@ -24,7 +24,7 @@ class TextInputPlugin(FormFieldPlugin):
group = _("Fields")
form = TextInputForm
def get_form_field_instances(self):
def get_form_field_instances(self, request=None):
"""
Get form field instances.
"""

View file

@ -24,7 +24,7 @@ class TextareaPlugin(FormFieldPlugin):
group = _("Fields")
form = TextareaForm
def get_form_field_instances(self):
def get_form_field_instances(self, request=None):
"""
Get form field instances.
"""

View file

@ -26,7 +26,7 @@ class TimeInputPlugin(FormFieldPlugin):
group = _("Fields")
form = TimeInputForm
def get_form_field_instances(self):
def get_form_field_instances(self, request=None):
"""
Get form field instances.
"""

View file

@ -31,7 +31,7 @@ class URLInputPlugin(FormFieldPlugin):
group = _("Fields")
form = URLInputForm
def get_form_field_instances(self):
def get_form_field_instances(self, request=None):
"""
Get form field instances.
"""

View file

@ -28,9 +28,9 @@ Taken from django-simple-captcha `installation instructions
.. code-block:: python
urlpatterns += patterns('',
urlpatterns += [
url(r'^captcha/', include('captcha.urls')),
)
]
Install `fobi` Captcha plugin
-----------------------------

View file

@ -75,7 +75,7 @@ class CaptchaInputPlugin(FormElementPlugin):
group = _("Security")
form = CaptchaInputForm
def get_form_field_instances(self):
def get_form_field_instances(self, request=None):
"""
Get form field instances.
"""

View file

@ -5,7 +5,7 @@ A `Honeypot <http://en.wikipedia.org/wiki/Honeypot_%28computing%29>`_
form field plugin. Just another anti-spam technique.
Installation
===============================================
============
1. Add ``fobi.contrib.plugins.form_elements.security.honeypot`` to the
``INSTALLED_APPS`` in your ``settings.py``.
@ -27,5 +27,4 @@ Installation
the plugin if ``FOBI_RESTRICT_PLUGIN_ACCESS`` is set to True.
Usage
===============================================
=====

View file

@ -26,7 +26,7 @@ class HoneypotInputPlugin(FormElementPlugin):
form = HoneypotInputForm
is_hidden = True
def get_form_field_instances(self):
def get_form_field_instances(self, request=None):
"""
Get form field instances.
"""

View file

@ -6,9 +6,9 @@ Makes use of the `django-recaptcha
<https://github.com/praekelt/django-recaptcha>`_.
Installation
===============================================
============
Install `django-recaptcha`
-----------------------------------------------
--------------------------
1. Download ``django-recaptcha`` using pip by running:
.. code-block:: none
@ -22,7 +22,7 @@ Install `django-recaptcha`
tables.
Install `fobi` ReCAPTCHA plugin
-----------------------------------------------
-------------------------------
1. Add ``fobi.contrib.plugins.form_elements.security.recaptcha`` to the
``INSTALLED_APPS`` in your ``settings.py``.
@ -49,7 +49,7 @@ Install `fobi` ReCAPTCHA plugin
- ``RECAPTCHA_PRIVATE_KEY``
Troubleshooting and usage limitations
===============================================
=====================================
At the moment, you can't use both ``CAPTCHA``
(fobi.contrib.plugins.form_elements.security.captcha) and ``ReCAPTCHA``
(fobi.contrib.plugins.form_elements.security.recaptcha) plugins alongside due
@ -65,7 +65,7 @@ See the `following <https://github.com/praekelt/django-recaptcha/issues/32>`_
thread for more information.
Usage
===============================================
=====
Note, that unlike most of the other form element plugins, default
value for the ``required`` attribute is True, which makes the ReCaptcha
obligatory. Although you could still set it to False, it does not make

View file

@ -77,7 +77,7 @@ class ReCaptchaInputPlugin(FormElementPlugin):
group = _("Security")
form = ReCaptchaInputForm
def get_form_field_instances(self):
def get_form_field_instances(self, request=None):
"""
Get form field instances.
"""

View file

@ -29,7 +29,7 @@ class DummyPlugin(FormElementPlugin):
"""
self.data.name = "{0}_{1}".format(self.uid, uuid4())
def get_form_field_instances(self):
def get_form_field_instances(self, request=None):
"""
Get form field instances.
"""

View file

@ -1,17 +1,17 @@
===============================================
===========================================
fobi.contrib.plugins.form_handlers.db_store
===============================================
===========================================
A ``Fobi`` Database Store form handler plugin. Saves submitted form
data into the ``SavedFormDataEntry`` model.
Dependencies
===============================================
============
The `xlwt <https://pypi.python.org/pypi/xlwt>`_ package is required
(optional) for XLS export. If not present, export format falls back
to CSV.
Installation
===============================================
============
1. Add ``fobi.contrib.plugins.form_handlers.db_store`` to the
``INSTALLED_APPS`` in your ``settings.py``.
@ -38,8 +38,8 @@ Installation
.. code-block:: python
urlpatterns = patterns('',
urlpatterns = [
# DB Store plugin URLs
url(r'^fobi/plugins/form-handlers/db-store/',
include('fobi.contrib.plugins.form_handlers.db_store.urls')),
)
]

View file

@ -1,11 +1,11 @@
===============================================
==============================================
fobi.contrib.plugins.form_handlers.http_repost
===============================================
==============================================
A ``Fobi`` HTTP Repost form handler plugin. Submits the form
data as is to the given endpoint.
Installation
===============================================
============
1. Add ``fobi.contrib.plugins.form_handlers.http_repost`` to the
``INSTALLED_APPS`` in your ``settings.py``.

View file

@ -1,11 +1,11 @@
===============================================
=======================================
fobi.contrib.plugins.form_handlers.mail
===============================================
=======================================
A ``Fobi`` Mail form handler plugin. Submits the form
data by email to the specified email address.
Installation
===============================================
============
1. Add ``fobi.contrib.plugins.form_handlers.mail`` to the
``INSTALLED_APPS`` in your ``settings.py``.

View file

@ -56,6 +56,7 @@ class Bootstrap3Theme(BaseTheme):
base_template = 'bootstrap3/base.html'
form_ajax = 'bootstrap3/snippets/form_ajax.html'
form_wizard_template = 'bootstrap3/snippets/form_wizard.html'
form_snippet_template_name = 'bootstrap3/snippets/form_snippet.html'
form_properties_snippet_template_name = \
'bootstrap3/snippets/form_properties_snippet.html'
@ -98,5 +99,8 @@ class Bootstrap3Theme(BaseTheme):
view_embed_form_entry_ajax_template = \
'bootstrap3/view_embed_form_entry_ajax.html'
form_importer_template = 'bootstrap3/form_importer.html'
form_importer_ajax_template = 'bootstrap3/form_importer_ajax.html'
theme_registry.register(Bootstrap3Theme)

View file

@ -0,0 +1 @@
{% extends "fobi/generic/form_importer.html" %}

View file

@ -0,0 +1 @@
{% extends "fobi/generic/form_importer_ajax.html" %}

View file

@ -0,0 +1 @@
{% extends "fobi/generic/snippets/form_wizard.html" %}

View file

@ -30,6 +30,11 @@
<li>
<a class="addlink" href="{% url 'fobi.import_form_entry' %}">{% trans "Import form"%}</a>
</li>
{% for form_importer_uid,form_importer_name,form_importer_url in form_importers %}
<li>
<a class="addlink addlink-importer-{{ form_importer_uid }}" href="{{ form_importer_url }}">{{ form_importer_name }}</a>
</li>
{% endfor %}
</ul>
<div id="changelist" class="module">

View file

@ -191,7 +191,7 @@
<p>{% trans "Export your form into JSON format and import it again any time!" %}</p>
<p>
<a class="btn btn-primary" href="{% url 'fobi.export_form_entry' form_entry.pk %}" role="button">
<span class="custom-icon custom-icon-export"></span> {% trans "Export form data" %}
<span class="custom-icon custom-icon-export"></span> {% trans "Export form" %}
</a>
</p>
</div>

View file

@ -65,7 +65,11 @@
<div class="list-group">
<a href="{% url 'fobi.import_form_entry' %}"><span class="fi-upload"></span> {% trans "Import form"%}</a>
</div>
{% for form_importer_uid,form_importer_name,form_importer_url in form_importers %}
<div class="list-group">
<a href="{{ form_importer_url }}"><span class="fi-upload ff-importer-{{ form_importer_uid }}"></span> {{ form_importer_name }}</a>
</div>
{% endfor %}
</div>
</div>
{% endblock content-wrapper %}

View file

@ -181,7 +181,7 @@
<p>{% trans "Export your form into JSON format and import it again any time!" %}</p>
<p>
<a class="radius button btn-primary" href="{% url 'fobi.export_form_entry' form_entry.pk %}" role="button">
<span class="fi-download"></span> {% trans "Export form data" %}
<span class="fi-download"></span> {% trans "Export form" %}
</a>
</p>
</div>

View file

@ -29,6 +29,11 @@
<li>
<a class="addlink" href="{% url 'fobi.import_form_entry' %}">{% trans "Import form"%}</a>
</li>
{% for form_importer_uid,form_importer_name,form_importer_url in form_importers %}
<li>
<a class="addlink addlink-importer-{{ form_importer_uid }}" href="{{ form_importer_url }}">{{ form_importer_name }}</a>
</li>
{% endfor %}
</ul>
<div id="changelist" class="module">

View file

@ -191,7 +191,7 @@
<p>{% trans "Export your form into JSON format and import it again any time!" %}</p>
<p>
<a class="btn btn-primary" href="{% url 'fobi.export_form_entry' form_entry.pk %}" role="button">
<span class="custom-icon custom-icon-export"></span> {% trans "Export form data" %}
<span class="custom-icon custom-icon-export"></span> {% trans "Export form" %}
</a>
</p>
</div>

View file

@ -68,9 +68,9 @@ def autodiscover():
'FORM_CALLBACKS_MODULE_NAME'
)
#FORM_IMPORTER_PLUGINS_MODULE_NAME = get_setting(
# 'FORM_IMPORTER_PLUGINS_MODULE_NAME'
# )
FORM_IMPORTER_PLUGINS_MODULE_NAME = get_setting(
'FORM_IMPORTER_PLUGINS_MODULE_NAME'
)
# Discover modules
autodiscover_modules(FORM_ELEMENT_PLUGINS_MODULE_NAME)
@ -79,7 +79,7 @@ def autodiscover():
autodiscover_modules(FORM_CALLBACKS_MODULE_NAME)
# Do not yet discover form importers
#autodiscover_modules(FORM_IMPORTER_PLUGINS_MODULE_NAME)
autodiscover_modules(FORM_IMPORTER_PLUGINS_MODULE_NAME)
if six.PY3 and recursion_limit > default_recursion_limit:
sys.setrecursionlimit(default_recursion_limit)

View file

@ -8,10 +8,17 @@ __all__ = ('assemble_form_class',)
from six import with_metaclass
from django.utils.datastructures import SortedDict
from django.forms.forms import BaseForm#, get_declared_fields
from django.forms.widgets import media_property
from nine.versions import DJANGO_GTE_1_7
if DJANGO_GTE_1_7:
from collections import OrderedDict
else:
from django.utils.datastructures import SortedDict as OrderedDict
#logger = logging.getLogger(__file__)
# ****************************************************************************
@ -48,25 +55,30 @@ def assemble_form_class(form_entry, base_class=BaseForm, request=None,
def __new__(cls, name, bases, attrs):
base_fields = []
for creation_counter, form_element_entry in enumerate(form_element_entries):
for creation_counter, form_element_entry \
in enumerate(form_element_entries):
plugin = form_element_entry.get_plugin(request=request)
# We simply make sure the plugin exists. We don't handle
# exceptions relate to the non-existent plugins here. They
# are instead handled in registry.
if plugin:
plugin_form_field_instances = plugin._get_form_field_instances(
form_element_entry = form_element_entry,
origin = origin,
kwargs_update_func = origin_kwargs_update_func,
return_func = origin_return_func,
extra = {'counter': creation_counter},
request = request
plugin_form_field_instances = \
plugin._get_form_field_instances(
form_element_entry = form_element_entry,
origin = origin,
kwargs_update_func = origin_kwargs_update_func,
return_func = origin_return_func,
extra = {'counter': creation_counter},
request = request
)
for form_field_name, form_field_instance \
in plugin_form_field_instances:
base_fields.append(
(form_field_name, form_field_instance)
)
for form_field_name, form_field_instance in plugin_form_field_instances:
base_fields.append((form_field_name, form_field_instance))
attrs['base_fields'] = SortedDict(base_fields)
attrs['base_fields'] = OrderedDict(base_fields)
new_class = super(DeclarativeFieldsMetaclass, cls).__new__(
cls, name, bases, attrs
)

View file

@ -1,8 +1,21 @@
#import json
__title__ = 'fobi.form_importers'
__author__ = 'Artur Barseghyan <artur.barseghyan@gmail.com>'
__copyright__ = 'Copyright (c) 2014-2015 Artur Barseghyan'
__license__ = 'GPL 2.0/LGPL 2.1'
__all__ = (
'BaseFormImporter', 'FormImporterPluginRegistry',
'form_importer_plugin_registry', 'ensure_autodiscover',
'get_form_importer_plugin_uids', 'get_form_impoter_plugin_urls',
)
from six import text_type
import simplejson as json
from django.core.urlresolvers import reverse
from fobi.base import BaseRegistry
from fobi.discover import autodiscover
class BaseFormImporter(object):
"""
@ -21,8 +34,11 @@ class BaseFormImporter(object):
# Position is stored in a model (field)
field_type_prop_name = None
position_prop_name = None
wizard = None
templates = None
def __init__(self, form_properties, form_data):
def __init__(self, form_entry_cls, form_element_entry_cls,
form_properties=None, form_data=None):
"""
:param django.contrib.auth.models.User user: User importing the form.
:param dict form_properties: Properties of the form, that
@ -32,11 +48,19 @@ class BaseFormImporter(object):
assert self.name
assert self.fields_mapping is not None
assert self.field_properties_mapping is not None
assert self.wizard is not None
assert self.templates is not None
assert form_entry_cls is not None
assert form_element_entry_cls is not None
for prop in ('name', 'label', 'help_text', 'initial', 'required'):
assert prop in self.field_properties_mapping
self.form_data = form_data
self.form_properties = form_properties
self.form_entry_cls = form_entry_cls
self.form_element_entry_cls = form_element_entry_cls
def get_form_data(self):
return self.form_data
@ -48,14 +72,15 @@ class BaseFormImporter(object):
field_properties[prop] = field_data[val]
return field_properties
def import_data(self):
def import_data(self, form_properties, form_data):
"""
Imports data.
"""
# TODO: Move this to top level and ensure it works!
from fobi.models import FormEntry, FormElementEntry
self.form_properties = form_properties
self.form_data = form_data
assert 'name' in self.form_properties
form_entry = FormEntry()
form_entry = self.form_entry_cls()
for prop, val in self.form_properties.items():
setattr(form_entry, prop, val)
@ -67,9 +92,10 @@ class BaseFormImporter(object):
if not field_data[self.field_type_prop_name] in self.fields_mapping:
continue
form_element_entry = FormElementEntry()
form_element_entry = self.form_element_entry_cls()
form_element_entry.form_entry = form_entry
form_element_entry.plugin_uid = self.fields_mapping[field_data[self.field_type_prop_name]]
form_element_entry.plugin_uid = self.fields_mapping[
field_data[self.field_type_prop_name]]
# Assign form data
form_element_entry.plugin_data = json.dumps(
@ -78,10 +104,34 @@ class BaseFormImporter(object):
# Assign position in form
if self.position_prop_name in field_data:
form_element_entry.position = field_data[self.position_prop_name]
form_element_entry.position = field_data[
self.position_prop_name]
form_element_entry.save()
return form_entry
def get_template_names(self):
"""
"""
return {text_type(idx): tpl for idx, tpl in enumerate(self.templates)}
def get_wizard(self, request, *args, **kwargs):
"""
"""
template_names = self.get_template_names()
class FormImporterWizard(self.wizard):
"""
Constructing the importer class dynamically.
"""
_form_importer = self
def get_template_names(self):
return [template_names[self.steps.current]]
wizard = FormImporterWizard.as_view()
return wizard(request, *args, **kwargs)
class FormImporterPluginRegistry(BaseRegistry):
@ -93,3 +143,29 @@ class FormImporterPluginRegistry(BaseRegistry):
# Register form field plugins by calling form_field_plugin_registry.register()
form_importer_plugin_registry = FormImporterPluginRegistry()
def ensure_autodiscover():
"""
Ensures that form importer plugins are auto-discovered.
"""
if not (form_importer_plugin_registry._registry):
autodiscover()
def get_form_importer_plugin_uids():
"""
"""
ensure_autodiscover()
return list(form_importer_plugin_registry._registry.keys())
def get_form_impoter_plugin_urls():
"""
Gets the form importers as a list of tuples.
"""
urls = []
ensure_autodiscover()
for uid, plugin in form_importer_plugin_registry._registry.items():
urls.append(
(uid, plugin.name, reverse('fobi.form_importer', kwargs={'form_importer_plugin_uid': uid}))
)
return urls

View file

@ -79,6 +79,11 @@
<a href="{% url 'fobi.import_form_entry' %}" class="list-group-item">
<span class="glyphicon glyphicon-import"></span> {% trans "Import form" %}
</a>
{% for form_importer_uid,form_importer_name,form_importer_url in form_importers %}
<a href="{{ form_importer_url }}" class="list-group-item">
<span class="glyphicon glyphicon-import glyphicon-import-{{ form_importer_uid }}"></span> {{ form_importer_name }}
</a>
{% endfor %}
</div>
</div>

View file

@ -177,7 +177,7 @@
<p>{% trans "Export your form into JSON format and import it again any time!" %}</p>
<p>
<a class="btn btn-primary" href="{% url 'fobi.export_form_entry' form_entry.pk %}" role="button">
<span class="glyphicon glyphicon-export"></span> {% trans "Export form data" %}
<span class="glyphicon glyphicon-export"></span> {% trans "Export form" %}
</a>
</p>
</div>

View file

@ -0,0 +1,15 @@
{% extends fobi_theme.base_edit_template %}
{% load i18n %}
{% block page-title %}{% trans "Form importer" %}{% endblock page-title %}
{% block navbar-menu-content %}
{% endblock navbar-menu-content %}
{% block content %}
{% include fobi_theme.form_importer_ajax_template %}
{% endblock content %}
{% block sidebar-wrapper %}
{% endblock sidebar-wrapper %}

View file

@ -0,0 +1,9 @@
{% extends fobi_theme.form_wizard_template %}
{% load i18n %}
{% block form_page_title %}
{% trans "Form importer" %}
{% endblock form_page_title %}
{% block form_primary_button_text %}{% trans "Import" %}{% endblock %}

View file

@ -0,0 +1,56 @@
{% load i18n %}
<div class="{% block form_page_header_html_class %}{% endblock %}">
<h1>{% block form_page_title %}{% endblock %}</h1>
</div>
<form method="post" {% block form_enctype %}enctype="multipart/form-data"{% endblock %}
class="{% block form_html_class %}{% endblock %}"
{#action="{% if api_key %}?api_key={{ api_key }}{% endif %}"#}
{% block form_extra_attrs %}{% endblock %}>
{% csrf_token %}
{{ wizard.form.media }}
{{ wizard.management_form }}
{% if wizard.form.forms %}
{{ wizard.form.management_form }}
{% for wizard_form in wizard.form.forms %}
{{ wizard_form.as_p }}
{#% include fobi_theme.form_snippet_template_name %#}
{% endfor %}
{% else %}
{{ wizard.form.as_p }}
{% endif %}
<div class="{% block form_button_outer_wrapper_html_class %}{% endblock %}">
<div class="{% block form_button_wrapper_html_class %}{% endblock %}">
{% block form_buttons %}
{% if wizard.steps.prev %}
<button name="wizard_goto_step"
type="submit"
value="{{ wizard.steps.first }}"
class="{% block form_first_step_button_html_class %}first-step{% endblock %}">
{% trans "First step" %}
</button>
{#% endif %#}
{#% if wizard.steps.prev %#}
<button name="wizard_goto_step"
type="submit"
value="{{ wizard.steps.prev }}"
class="{% block form_prev_step_button_html_class %}prev-step{% endblock %}">
{% trans "Previous step" %}
</button>
{% endif %}
<button name="submit"
type="submit"
class="{% block form_primary_button_html_class %}{% endblock %}">
{% block form_primary_button_text %}{% trans "Submit" %}{% endblock %}
</button>
{% endblock form_buttons %}
</div>
</div>
</form>

View file

@ -12,7 +12,13 @@ from django.template import Library, TemplateSyntaxError, Node
from django.conf import settings
from django import forms
from django.utils.translation import ugettext_lazy as _
from django.forms.util import ErrorDict
from nine.versions import DJANGO_GTE_1_7
if DJANGO_GTE_1_7:
from django.forms.utils import ErrorDict
else:
from django.forms.util import ErrorDict
from fobi.settings import DISPLAY_AUTH_LINK

View file

@ -13,8 +13,9 @@ except ImportError:
import warnings
from django.template.base import (
Node, TemplateSyntaxError, Library
Node, TemplateSyntaxError
)
from django.template import Library
from django.utils.timezone import template_localtime
from django.utils.formats import localize
from django.utils.encoding import force_text

View file

@ -0,0 +1,222 @@
from django.conf import settings
from django.contrib.auth import get_user_model
from fobi.contrib.plugins.form_importers.mailchimp_importer.fobi_form_importers \
import MailChimpFormImporter
test_form_data = [
{
u'default': u'',
u'field_type': u'email',
u'helptext': u'',
u'id': 0,
u'name': u'Email Address',
u'order': u'1',
u'public': True,
u'req': True,
u'show': True,
u'size': u'25',
u'tag': u'EMAIL'
},
{
u'default': u'',
u'field_type': u'text',
u'helptext': u'',
u'id': 1,
u'name': u'First Name',
u'order': u'2',
u'public': True,
u'req': False,
u'show': True,
u'size': u'25',
u'tag': u'FNAME'
},
{
u'default': u'',
u'field_type': u'text',
u'helptext': u'',
u'id': 2,
u'name': u'Last Name',
u'order': u'3',
u'public': True,
u'req': False,
u'show': True,
u'size': u'25',
u'tag': u'LNAME'
},
{
u'default': u'',
u'field_type': u'text',
u'helptext': u'',
u'id': 3,
u'name': u'Organisation',
u'order': u'4',
u'public': True,
u'req': False,
u'show': True,
u'size': u'25',
u'tag': u'ORG'
},
{
u'default': u'Type Text Default Value',
u'field_type': u'text',
u'helptext': u'Type Text Help Text',
u'id': 4,
u'name': u'type_text',
u'order': u'5',
u'public': True,
u'req': True,
u'show': True,
u'size': u'25',
u'tag': u'TYPE_TEXT'
},
{
u'default': u'1',
u'field_type': u'number',
u'helptext': u'Type Number Help Text',
u'id': 5,
u'name': u'type_number',
u'order': u'6',
u'public': True,
u'req': False,
u'show': True,
u'size': u'25',
u'tag': u'TYPE_NUMBE'
},
{
u'choices': [u'First Choice', u'Second Choice', u'Third Choice'],
u'default': u'Second Choice',
u'field_type': u'radio',
u'helptext': u'Type Radio Buttons Help Text',
u'id': 6,
u'name': u'type_radio_buttons',
u'order': u'7',
u'public': True,
u'req': True,
u'show': True,
u'size': u'25',
u'tag': u'TYPE_RADIO'
},
{
u'choices': [u'First Choice', u'Second Choice', u'Third Choice'],
u'default': u'Third Choice',
u'field_type': u'dropdown',
u'helptext': u'Drop Down Help Text',
u'id': 7,
u'name': u'type_drop_down',
u'order': u'9',
u'public': True,
u'req': True,
u'show': True,
u'size': u'25',
u'tag': u'TYPE_DROPD'
},
{
u'dateformat': u'MM/DD/YYYY',
u'default': u'',
u'field_type': u'date',
u'helptext': u'Type Date Help Text',
u'id': 8,
u'name': u'type_date',
u'order': u'10',
u'public': True,
u'req': True,
u'show': True,
u'size': u'25',
u'tag': u'TYPE_DATE'
},
{
u'dateformat': u'MM/DD',
u'default': u'',
u'field_type': u'birthday',
u'helptext': u'Type Birthday Help Text',
u'id': 9,
u'name': u'type_birthday',
u'order': u'11',
u'public': True,
u'req': True,
u'show': True,
u'size': u'25',
u'tag': u'TYPE_BIRTH'
},
{
u'default': u'',
u'defaultcountry': u'109',
u'defaultcountry_cc': u'NL',
u'defaultcountry_name': u'Netherlands',
u'field_type': u'address',
u'helptext': u'Type Address Help Text',
u'id': 10,
u'name': u'type_address',
u'order': u'12',
u'public': True,
u'req': False,
u'show': True,
u'size': u'25',
u'tag': u'TYPE_ADDRE'
},
{
u'default': u'',
u'field_type': u'zip',
u'helptext': u'Type Zip Code Help Text',
u'id': 11,
u'name': u'type_zip_code',
u'order': u'13',
u'public': True,
u'req': False,
u'show': True,
u'size': u'25',
u'tag': u'TYPE_ZIP_C'
},
{
u'default': u'',
u'field_type': u'phone',
u'helptext': u'Type Phone Help Text',
u'id': 12,
u'name': u'type_phone',
u'order': u'14',
u'phoneformat': u'none',
u'public': True,
u'req': False,
u'show': True,
u'size': u'25',
u'tag': u'TYPE_PHONE'
},
{
u'default': u'',
u'field_type': u'url',
u'helptext': u'Type Website Help Text',
u'id': 13,
u'name': u'type_website',
u'order': u'15',
u'public': True,
u'req': True,
u'show': True,
u'size': u'25',
u'tag': u'TYPE_WEBSI'
},
{
u'default': u'',
u'field_type': u'imageurl',
u'helptext': u'Type Image Help Text',
u'id': 14,
u'name': u'type_image',
u'order': u'16',
u'public': True,
u'req': False,
u'show': True,
u'size': u'25',
u'tag': u'TYPE_IMAGE'
}
]
def do():
User = get_user_model()
kwargs = {User.USERNAME_FIELD: 'test_user',}
user = User.objects.get(**kwargs)
form_properties = {'name': 'Test mailchimp form', 'user': user}
importer = MailChimpFormImporter()
importer.import_data(form_properties, test_form_data)

View file

@ -11,7 +11,7 @@ from fobi.views import (
dashboard, create_form_entry, edit_form_entry, delete_form_entry,
add_form_element_entry, edit_form_element_entry, delete_form_element_entry,
add_form_handler_entry, edit_form_handler_entry, delete_form_handler_entry,
export_form_entry, import_form_entry,
export_form_entry, import_form_entry, form_importer
)
#urlpatterns = patterns('fobi.views',
@ -41,6 +41,11 @@ urlpatterns = [
import_form_entry,
name='fobi.import_form_entry'),
# Form importers
url(_(r'^forms/importer/(?P<form_importer_plugin_uid>[\w_\-]+)/$'),
form_importer,
name='fobi.form_importer'),
# Add form element entry
url(_(r'^forms/elements/add/(?P<form_entry_id>\d+)/(?P<form_element_plugin_uid>[\w_\-]+)/$'),
add_form_element_entry,

View file

@ -12,7 +12,7 @@ __all__ = (
'delete_form_element_entry', 'add_form_handler_entry',
'edit_form_handler_entry', 'delete_form_handler_entry',
'dashboard', 'view_form_entry', 'form_entry_submitted',
'export_form_entry', 'import_form_entry',
'export_form_entry', 'import_form_entry', 'form_importer',
)
import datetime
@ -42,6 +42,10 @@ from fobi.base import (
form_handler_plugin_registry, submit_plugin_form_data, get_theme,
#get_registered_form_handler_plugins
)
from fobi.form_importers import (
form_importer_plugin_registry, get_form_impoter_plugin_urls,
ensure_autodiscover as ensure_importers_autodiscover
)
from fobi.constants import (
CALLBACK_BEFORE_FORM_VALIDATION,
CALLBACK_FORM_VALID_BEFORE_SUBMIT_PLUGIN_FORM_DATA,
@ -127,7 +131,10 @@ def dashboard(request, theme=None, template_name=None):
.filter(user__pk=request.user.pk) \
.select_related('user')
context = {'form_entries': form_entries}
context = {
'form_entries': form_entries,
'form_importers': get_form_impoter_plugin_urls(),
}
# If given, pass to the template (and override the value set by
# the context processor.
@ -1232,3 +1239,25 @@ def import_form_entry(request, template_name=None):
return render_to_response(template_name, context,
context_instance=RequestContext(request))
# *****************************************************************************
# *****************************************************************************
# ****************************** Form importers *******************************
# *****************************************************************************
# *****************************************************************************
@login_required
@permissions_required(satisfy=SATISFY_ALL, perms=create_form_entry_permissions)
def form_importer(request, form_importer_plugin_uid, template_name=None,
*args, **kwargs):
"""
"""
ensure_importers_autodiscover()
form_importer_cls = form_importer_plugin_registry._registry.get(
form_importer_plugin_uid
)
form_importer = form_importer_cls(form_entry_cls=FormEntry,
form_element_entry_cls=FormElementEntry)
return form_importer.get_wizard(request, *args, **kwargs)