Merge branch 'master' into wagtail/integration

This commit is contained in:
Artur Barseghyan 2017-05-12 03:12:57 +02:00
commit 7700a5f28e
45 changed files with 930 additions and 135 deletions

2
.gitignore vendored
View file

@ -36,7 +36,6 @@ README_PARTS.rst
/src/django_fobi.egg-info
/src/fobi/contrib/plugins/form_elements/fields/hidden_model_object/
/src/fobi/contrib/apps/drf_integration/form_elements/content/
/src/fobi/contrib/plugins/form_importers/mailchimp_importer/bucket.py
/src/fobi/contrib/apps/wagtail_integration_/
@ -52,6 +51,7 @@ README_PARTS.rst
/examples/media/cache/
/examples/simple/lund_urls.py
/examples/simple/lund/
/examples/saved_forms_in_json_format/all/
/examples/simple/settings/local_settings.py
/examples/simple/settings/local_settings_foundation5.py
/examples/simple/settings/bootstrap3_theme_django_1_9_lund.py

View file

@ -15,8 +15,30 @@ are used for versioning (schema follows below):
0.3.4 to 0.4).
- All backwards incompatible changes are mentioned in this document.
0.11
----
0.11.4
------
2017-05-12
- Minor fixes in integration callbacks of the ``drf_integration`` sub-package.
- Added support for ``content_image``, ``content_text`` and ``content_video``
plugins.
- Fixes in installable demo.
0.11.3
------
2017-05-10
- Concept of integration callbacks introduced and implemented for the
``drf_integration`` sub-package.
0.11.2
------
2017-05-09
- Minor fixes in ``drf_integration`` app.
0.11.1
------
2017-05-08
- Minor fixes in ``decimal`` plugin.

View file

@ -12,9 +12,9 @@ mkdir ../static/
mkdir ../db/
mkdir ../logs/
mkdir ../tmp/
cp local_settings.example local_settings.py
./manage.py syncdb --noinput --traceback -v 3
cp settings/local_settings.example settings/local_settings.py
#./manage.py syncdb --noinput --traceback -v 3
./manage.py migrate --noinput
./manage.py collectstatic --noinput --traceback -v 3
./manage.py fobi_create_test_data --traceback -v 3
./manage.py runserver 0.0.0.0:8001 --traceback -v 3
./manage.py runserver 0.0.0.0:8001 --traceback -v 3

View file

@ -1,13 +1 @@
-r common.txt
-r djangorestframework.txt
Django>=1.8,<1.9
django-admin-tools>=0.7.1
django-autoslug==1.9.3
django-debug-toolbar==0.11
django-formtools==1.0
django-nine>=0.1.10
django-nonefield>=0.1
django-registration-redux>=1.4
easy-thumbnails>=2.3
vishap>=0.1.5
-r django_1_9.txt

View file

@ -0,0 +1,4 @@
Saved forms in JSON format
==========================
A nice collection of saved ``django-fobi`` forms. Importable using the
import functionality.

View file

@ -0,0 +1 @@
{"success_page_message": "", "is_public": false, "success_page_title": "", "slug": "change-payment-method", "form_handlers": [], "action": "", "name": "Change Payment Method", "is_cloneable": false, "form_elements": [{"plugin_data": "{\"required\": false, \"name\": \"customer_id\", \"help_text\": \"\", \"max_length\": 255, \"label\": \"Customer ID\", \"placeholder\": \"\", \"initial\": \"\"}", "plugin_uid": "text", "position": 1}, {"plugin_data": "{\"required\": false, \"name\": \"account_hold_name\", \"help_text\": \"\", \"max_length\": 255, \"label\": \"Account Hold Name\", \"placeholder\": \"\", \"initial\": \"\"}", "plugin_uid": "text", "position": 2}, {"plugin_data": "{\"required\": false, \"name\": \"account_password\", \"help_text\": \"\", \"max_length\": 255, \"label\": \"Account Password\", \"placeholder\": \"\", \"initial\": \"\"}", "plugin_uid": "text", "position": 3}, {"plugin_data": "{\"required\": false, \"name\": \"contact_phone_number\", \"help_text\": \"\", \"max_length\": 255, \"label\": \"Contact Phone Number\", \"placeholder\": \"\", \"initial\": \"\"}", "plugin_uid": "text", "position": 4}, {"plugin_data": "{\"required\": false, \"name\": \"contact_email_address\", \"help_text\": \"\", \"max_length\": 255, \"label\": \"Contact email address\", \"placeholder\": \"\", \"initial\": \"\"}", "plugin_uid": "text", "position": 5}, {"plugin_data": "{\"required\": false, \"name\": \"new_payment_method\", \"help_text\": \"\", \"max_length\": 255, \"label\": \"New Payment Method\", \"placeholder\": \"\", \"initial\": \"\"}", "plugin_uid": "text", "position": 6}, {"plugin_data": "{\"required\": false, \"name\": \"card_holder_or_account_holder\", \"help_text\": \"\", \"max_length\": 255, \"label\": \"Card Holder or Account Holder\", \"placeholder\": \"\", \"initial\": \"\"}", "plugin_uid": "text", "position": 7}, {"plugin_data": "{\"required\": false, \"name\": \"account_number\", \"help_text\": \"\", \"max_length\": 255, \"label\": \"Account Number\", \"placeholder\": \"\", \"initial\": \"\"}", "plugin_uid": "text", "position": 8}, {"plugin_data": "{\"required\": false, \"name\": \"routing_code_if_electronic_check\", \"help_text\": \"\", \"max_length\": 255, \"label\": \"Routing Code (if electronic check)\", \"placeholder\": \"\", \"initial\": \"\"}", "plugin_uid": "text", "position": 9}, {"plugin_data": "{\"required\": false, \"name\": \"expiration_date_if_card\", \"help_text\": \"\", \"label\": \"Expiration date if Card\", \"input_formats\": \"\", \"initial\": null}", "plugin_uid": "date", "position": 10}, {"plugin_data": "{\"required\": false, \"name\": \"cvv_if_card\", \"help_text\": \"\", \"max_length\": 255, \"label\": \"CVV if Card\", \"placeholder\": \"\", \"initial\": \"\"}", "plugin_uid": "text", "position": 11}]}

View file

@ -0,0 +1 @@
{"success_page_message": "", "is_public": false, "success_page_title": "", "slug": "change-responsible-party-on-account", "form_handlers": [], "action": "", "name": "Change Responsible Party on Account", "is_cloneable": false, "form_elements": [{"plugin_data": "{\"required\": true, \"name\": \"customer_id\", \"help_text\": \"\", \"max_length\": 8, \"label\": \"Customer ID\", \"placeholder\": \"\", \"initial\": \"\"}", "plugin_uid": "text", "position": 1}, {"plugin_data": "{\"required\": true, \"name\": \"name\", \"help_text\": \"\", \"max_length\": 50, \"label\": \"Account Hold Name\", \"placeholder\": \"\", \"initial\": \"\"}", "plugin_uid": "text", "position": 2}, {"plugin_data": "{\"required\": false, \"name\": \"new_user_first_name\", \"help_text\": \"\", \"max_length\": 50, \"label\": \"New User First Name\", \"placeholder\": \"\", \"initial\": \"\"}", "plugin_uid": "text", "position": 3}, {"plugin_data": "{\"required\": false, \"name\": \"new_user_last_name\", \"help_text\": \"\", \"max_length\": 50, \"label\": \"New User Last Name\", \"placeholder\": \"\", \"initial\": \"\"}", "plugin_uid": "text", "position": 4}, {"plugin_data": "{\"required\": false, \"name\": \"relationship_to_account_holder_if_any\", \"help_text\": \"\", \"max_length\": 50, \"label\": \"Relationship to Account Holder, if Any\", \"placeholder\": \"\", \"initial\": \"\"}", "plugin_uid": "text", "position": 5}, {"plugin_data": "{\"required\": false, \"name\": \"account_password\", \"help_text\": \"\", \"max_length\": 50, \"label\": \"Account Password\", \"placeholder\": \"\", \"initial\": \"\"}", "plugin_uid": "text", "position": 6}, {"plugin_data": "{\"required\": false, \"name\": \"contact_phone_number\", \"help_text\": \"\", \"max_length\": 50, \"label\": \"Contact Phone Number\", \"placeholder\": \"\", \"initial\": \"\"}", "plugin_uid": "text", "position": 7}, {"plugin_data": "{\"required\": false, \"name\": \"contact_email_address\", \"help_text\": \"\", \"max_length\": 50, \"label\": \"Contact email address\", \"placeholder\": \"\", \"initial\": \"\"}", "plugin_uid": "text", "position": 8}, {"plugin_data": "{\"required\": false, \"choices\": \"1, Electronic Check\\r\\n2, Visa \\r\\n3, Mastercard \\r\\n4, American Express, \\r\\n5, Discover\", \"name\": \"new_payment_method\", \"help_text\": \"\", \"label\": \"New Payment Method\", \"initial\": \"\"}", "plugin_uid": "select", "position": 9}, {"plugin_data": "{\"required\": false, \"name\": \"account_number\", \"help_text\": \"\", \"max_length\": 255, \"label\": \"Account Number\", \"placeholder\": \"\", \"initial\": \"\"}", "plugin_uid": "text", "position": 10}, {"plugin_data": "{\"required\": false, \"name\": \"routing_code_if_electronic_check\", \"help_text\": \"\", \"max_length\": 50, \"label\": \"Routing Code (if electronic check)\", \"placeholder\": \"\", \"initial\": \"\"}", "plugin_uid": "text", "position": 11}, {"plugin_data": "{\"required\": false, \"name\": \"expiration_date_if_card\", \"help_text\": \"\", \"label\": \"Expiration date if Card\", \"input_formats\": \"\", \"initial\": null}", "plugin_uid": "date", "position": 12}, {"plugin_data": "{\"required\": false, \"name\": \"cvv_if_card\", \"help_text\": \"\", \"max_length\": 8, \"label\": \"CVV if Card\", \"placeholder\": \"\", \"initial\": \"\"}", "plugin_uid": "text", "position": 13}]}

View file

@ -0,0 +1 @@
{"name": "Otevreny form (imported on 2016-11-30 21:33:48)", "is_public": false, "form_handlers": [], "form_elements": [{"position": 1, "plugin_uid": "text", "plugin_data": "{\"name\": \"jmeno\", \"max_length\": 80, \"required\": true, \"label\": \"Jm\\u00e9no\", \"help_text\": \"Zadejte sv\\u00e9 jm\\u00e9no\", \"initial\": \"\", \"placeholder\": \"Jm\\u00e9no\"}"}, {"position": 2, "plugin_uid": "text", "plugin_data": "{\"name\": \"prijmeni\", \"max_length\": 255, \"required\": true, \"label\": \"P\\u0159\\u00edjmen\\u00ed\", \"help_text\": \"Zadejte sv\\u00e9 p\\u0159\\u00edjmen\\u00ed\", \"initial\": \"\", \"placeholder\": \"P\\u0159\\u00edjmen\\u00ed\"}"}, {"position": 3, "plugin_uid": "text", "plugin_data": "{\"name\": \"prezdivka\", \"max_length\": 255, \"required\": false, \"label\": \"P\\u0159ezd\\u00edvka\", \"help_text\": \"Zadejte svoji p\\u0159ezd\\u00edvku\", \"initial\": \"\", \"placeholder\": \"P\\u0159ezd\\u00edvka\"}"}, {"position": 4, "plugin_uid": "email", "plugin_data": "{\"name\": \"e_mail\", \"max_length\": 255, \"required\": true, \"label\": \"E-mail\", \"help_text\": \"Zadejte sv\\u016fj e-mail\", \"initial\": \"\", \"placeholder\": \"E-mail\"}"}, {"position": 5, "plugin_uid": "date_drop_down", "plugin_data": "{\"input_formats\": \"\", \"name\": \"datum_narozeni\", \"year_max\": 2050, \"label\": \"Datum narozen\\u00ed\", \"year_min\": 1950, \"help_text\": \"Zadejte sv\\u00e9 datum narozen\\u00ed.\", \"initial\": \"2000\", \"required\": true}"}, {"position": 6, "plugin_uid": "textarea", "plugin_data": "{\"name\": \"dalsi_informace_o_soutezicim\", \"required\": true, \"label\": \"Dal\\u0161\\u00ed informace o sout\\u011b\\u017e\\u00edc\\u00edm\", \"help_text\": \"Z\\u00e1jmy a tak\", \"initial\": \"\", \"placeholder\": \"\"}"}], "slug": "otevreny-form", "success_page_title": "Success page title", "success_page_message": "Success page body", "is_cloneable": false, "action": ""}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
{"form_handlers": [{"plugin_data": null, "plugin_uid": "db_store"}], "name": "Test dynamic values", "action": "", "success_page_message": "", "slug": "test-dynamic-values", "success_page_title": "", "form_elements": [{"plugin_data": "{\"name\": \"username\", \"required\": false, \"help_text\": \"\", \"placeholder\": \"\", \"initial\": \"{{ request.user.get_username }}\", \"max_length\": 255, \"label\": \"Username\"}", "plugin_uid": "text", "position": 136}, {"plugin_data": "{\"name\": \"origin\", \"required\": false, \"help_text\": \"\", \"placeholder\": \"\", \"initial\": \"{{ request.path }}\", \"max_length\": 255, \"label\": \"Origin\"}", "plugin_uid": "text", "position": 137}, {"plugin_data": "{\"name\": \"meta\", \"required\": false, \"help_text\": \"\", \"placeholder\": \"\", \"initial\": \"{{ request.META }}\", \"label\": \"Meta\"}", "plugin_uid": "textarea", "position": 138}, {"plugin_data": "{\"placeholder\": \"\", \"name\": \"test_if_it_breaks\", \"label\": \"Test if it breaks\", \"required\": false, \"help_text\": \"\", \"initial\": \"{% load admin_list %}{% admin_actions %}\\r\\n{{ request.user.get_username }}\\r\\n{{ request.user.email }}\\r\\n{{ request.path }}\\r\\n{{ request.META }}\\r\\n\"}", "plugin_uid": "textarea", "position": 161}], "is_public": false, "is_cloneable": false, "position": null}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
{"form_handlers": [{"plugin_data": null, "plugin_uid": "db_store"}], "name": "Test private form", "action": "", "success_page_message": "", "slug": "test-private-form", "success_page_title": "", "form_elements": [{"plugin_data": "{\"required\": true, \"placeholder\": \"\", \"help_text\": \"\", \"max_length\": 255, \"initial\": \"\", \"label\": \"E-mail\", \"name\": \"e_mail\"}", "plugin_uid": "email", "position": 165}, {"plugin_data": "{\"required\": true, \"placeholder\": \"\", \"help_text\": \"\", \"max_length\": 255, \"initial\": \"\", \"label\": \"Full name\", \"name\": \"full_name\"}", "plugin_uid": "text", "position": 166}, {"plugin_data": "{\"required\": true, \"placeholder\": \"\", \"help_text\": \"\", \"initial\": \"\", \"label\": \"Question\", \"name\": \"question\"}", "plugin_uid": "textarea", "position": 167}, {"plugin_data": "{\"readonly_value\": false, \"name\": \"nombre\", \"help_text\": \"\", \"required\": true, \"min_value\": \"\", \"multiple_value\": false, \"initial\": \"\", \"type_value\": \"text\", \"list_value\": \"5\", \"disabled_value\": false, \"step_value\": null, \"autofocus_value\": true, \"placeholder\": \"nombre\", \"pattern_value\": \"\", \"autocomplete_value\": true, \"label\": \"nombre\", \"max_length\": 255, \"max_value\": \"6\"}", "plugin_uid": "input", "position": 168}, {"plugin_data": "{\"name\": \"apellido\", \"required\": true, \"help_text\": \"\", \"placeholder\": \"apellido\", \"initial\": \"\", \"max_length\": 255, \"label\": \"apellido\"}", "plugin_uid": "text", "position": 169}], "is_public": false, "is_cloneable": false, "position": null}

View file

@ -0,0 +1 @@
{"name": "Test sliders", "is_cloneable": false, "form_wizard_forms": [{"action": "", "name": "Test slider form", "is_cloneable": false, "form_elements": [{"plugin_uid": "slider", "position": 1, "plugin_data": "{\"step\": 10, \"initial\": 50, \"tooltip\": \"show\", \"label\": \"Slider variant 1\", \"name\": \"slider_variant_1\", \"max_value\": 100, \"help_text\": \"\", \"custom_ticks\": \"0, Start\\r\\n50, Middle\\r\\n100, End\", \"min_value\": 0, \"show_endpoints_as\": \"labeled_ticks\", \"label_end\": \"\", \"label_start\": \"\", \"required\": false, \"handle\": \"square\"}"}, {"plugin_uid": "slider", "position": 2, "plugin_data": "{\"step\": 20, \"initial\": 40, \"tooltip\": \"show\", \"label\": \"Slider variant 2\", \"name\": \"slider_variant_2\", \"max_value\": 100, \"help_text\": \"\", \"custom_ticks\": \"0\\r\\n20\\r\\n40\\r\\n60\\r\\n80\\r\\n100\", \"min_value\": 0, \"show_endpoints_as\": \"labeled_ticks\", \"label_end\": \"\", \"label_start\": \"\", \"required\": false, \"handle\": \"round\"}"}, {"plugin_uid": "slider", "position": 3, "plugin_data": "{\"step\": 25, \"initial\": 50, \"tooltip\": \"show\", \"label\": \"Slider variant 3\", \"name\": \"slider_variant_3\", \"max_value\": 100, \"help_text\": \"\", \"custom_ticks\": \"\", \"min_value\": 0, \"show_endpoints_as\": \"labels\", \"label_end\": \"100%\", \"label_start\": \"0%\", \"required\": false, \"handle\": \"triangle\"}"}, {"plugin_uid": "slider", "position": 4, "plugin_data": "{\"step\": 10, \"initial\": 50, \"tooltip\": \"hide\", \"label\": \"Slider variant 4\", \"name\": \"slider_variant_4\", \"max_value\": 100, \"help_text\": \"\", \"custom_ticks\": \"\", \"min_value\": 0, \"show_endpoints_as\": \"labels\", \"label_end\": \"\", \"label_start\": \"\", \"required\": false, \"handle\": \"round\"}"}], "success_page_title": "", "slug": "test-slider-form", "form_handlers": [], "success_page_message": "", "is_public": false}, {"action": "", "name": "Flight form", "is_cloneable": false, "form_elements": [{"plugin_uid": "radio", "position": 1, "plugin_data": "{\"help_text\": \"\", \"required\": true, \"initial\": \"\", \"label\": \"Arrival/Departure\", \"name\": \"arrival_departure\", \"choices\": \"A, Arrival\\r\\nD, Departure\\r\\nO, Overfly\"}"}, {"plugin_uid": "text", "position": 2, "plugin_data": "{\"help_text\": \"\", \"max_length\": 50, \"initial\": \"\", \"label\": \"Callsign\", \"name\": \"callsign\", \"placeholder\": \"\", \"required\": true}"}, {"plugin_uid": "text", "position": 3, "plugin_data": "{\"help_text\": \"\", \"max_length\": 255, \"initial\": \"\", \"label\": \"Airport\", \"name\": \"airport\", \"placeholder\": \"Origin/Destination\", \"required\": true}"}, {"plugin_uid": "datetime", "position": 4, "plugin_data": "{\"help_text\": \"\", \"required\": true, \"initial\": null, \"label\": \"Schedule\", \"name\": \"time\", \"input_formats\": \"\"}"}], "success_page_title": "OK", "slug": "flight-form", "form_handlers": [{"plugin_uid": "db_store", "plugin_data": null}], "success_page_message": "Flight updated", "is_public": false}], "success_page_title": "", "slug": "test-sliders", "form_wizard_handlers": [{"plugin_uid": "db_store", "plugin_data": null}], "success_page_message": "", "is_public": false}

File diff suppressed because one or more lines are too long

View file

@ -2,15 +2,23 @@ from __future__ import print_function
import logging
from fobi.base import FormCallback, form_callback_registry
from fobi.base import (
form_callback_registry,
FormCallback,
integration_form_callback_registry,
IntegrationFormCallback,
)
from fobi.constants import (
CALLBACK_BEFORE_FORM_VALIDATION,
CALLBACK_FORM_VALID_BEFORE_SUBMIT_PLUGIN_FORM_DATA,
CALLBACK_FORM_INVALID,
CALLBACK_FORM_VALID,
CALLBACK_FORM_VALID_AFTER_FORM_HANDLERS,
CALLBACK_FORM_INVALID
CALLBACK_FORM_VALID_BEFORE_SUBMIT_PLUGIN_FORM_DATA,
)
from fobi.contrib.apps.drf_integration import UID as INTEGRATE_WITH
logger = logging.getLogger('fobi')
__all__ = (
@ -18,6 +26,12 @@ __all__ = (
'DummyInvalidCallback',
)
# *************************************************************
# *************************************************************
# ********************** Core callbacks ***********************
# *************************************************************
# *************************************************************
# *************************************************************
# **************** Save as foo callback ***********************
# *************************************************************
@ -51,3 +65,46 @@ class DummyInvalidCallback(FormCallback):
form_callback_registry.register(DummyInvalidCallback)
# *************************************************************
# *************************************************************
# ****************** DRF integration callbacks ****************
# *************************************************************
# *************************************************************
# *************************************************************
# **************** Save as foo callback ***********************
# *************************************************************
class DRFSaveAsFooItem(IntegrationFormCallback):
"""Save the form as a foo item, if certain conditions are met."""
stage = CALLBACK_FORM_VALID
integrate_with = INTEGRATE_WITH
def callback(self, form_entry, request, **kwargs):
"""Custom callback login comes here."""
logger.debug("Great! Your form is valid!")
integration_form_callback_registry.register(DRFSaveAsFooItem)
# *************************************************************
# **************** Save as foo callback ***********************
# *************************************************************
class DRFDummyInvalidCallback(IntegrationFormCallback):
"""Saves the form as a foo item, if certain conditions are met."""
stage = CALLBACK_FORM_INVALID
integrate_with = INTEGRATE_WITH
def callback(self, form_entry, request, **kwargs):
"""Custom callback login comes here."""
logger.debug("Damn! You've made a mistake, boy!")
integration_form_callback_registry.register(DRFDummyInvalidCallback)

View file

@ -397,7 +397,7 @@ INSTALLED_APPS = [
'rest_framework', # Django REST framework
'fobi.contrib.apps.drf_integration', # DRF integration app
# Form elements
# Form fields
'fobi.contrib.apps.drf_integration.form_elements.fields.boolean',
'fobi.contrib.apps.drf_integration.form_elements.fields'
'.checkbox_select_multiple',
@ -427,6 +427,11 @@ INSTALLED_APPS = [
'fobi.contrib.apps.drf_integration.form_elements.fields.time',
'fobi.contrib.apps.drf_integration.form_elements.fields.url',
# Presentational elements
'fobi.contrib.apps.drf_integration.form_elements.content.content_image',
'fobi.contrib.apps.drf_integration.form_elements.content.content_text',
'fobi.contrib.apps.drf_integration.form_elements.content.content_video',
# Form handlers
'fobi.contrib.apps.drf_integration.form_handlers.db_store',
'fobi.contrib.apps.drf_integration.form_handlers.mail',
@ -463,6 +468,12 @@ PACKAGE_NAME_GRAPPELLI = "grappelli_safe" # Just for tests
# }
# SOUTH_MIGRATION_MODULES = 'south_migrations'
# **************************************************************
# ********************* Registration settings ******************
# **************************************************************
ACCOUNT_ACTIVATION_DAYS = 7
# **************************************************************
# ************************ Fobi settings ***********************
# **************************************************************

View file

@ -4,7 +4,7 @@ import sys
from distutils.version import LooseVersion
from setuptools import setup, find_packages
version = '0.11.1'
version = '0.11.4'
# ***************************************************************************
# ************************** Python version *********************************

View file

@ -1,6 +1,6 @@
__title__ = 'django-fobi'
__version__ = '0.11.1'
__build__ = 0x00007c
__version__ = '0.11.4'
__build__ = 0x00007f
__author__ = 'Artur Barseghyan <artur.barseghyan@gmail.com>'
__copyright__ = '2014-2017 Artur Barseghyan'
__license__ = 'GPL 2.0/LGPL 2.1'

View file

@ -146,6 +146,9 @@ __all__ = (
'IntegrationFormHandlerPlugin',
'IntegrationFormHandlerPluginDataStorage',
'IntegrationFormHandlerPluginRegistry',
'IntegrationFormCallbackRegistry',
'IntegrationFormCallback',
'integration_form_callback_registry',
'run_form_handlers',
'run_form_wizard_handlers',
'theme_registry',
@ -2060,7 +2063,7 @@ class IntegrationFormHandlerPlugin(BasePlugin):
is_hidden = False
class FormCallback(object):
class BaseFormCallback(object):
"""Base form callback."""
stage = None
@ -2069,6 +2072,10 @@ class FormCallback(object):
"""Constructor."""
assert self.stage in CALLBACK_STAGES
class FormCallback(BaseFormCallback):
"""Form callback."""
def _callback(self, form_entry, request, form):
"""Callback (internal method).
@ -2098,31 +2105,45 @@ class FormCallback(object):
"subclass.".format(self.__class__.__name__)
)
# def custom_field_instances_callback(self, integrate_with, form_entry,
# request, **kwargs):
# """Custom field instances callback.
#
# :param str integrate_with:
# :param fobi.models.FormEntry form_entry: Instance of
# ``fobi.models.FormEntry``.
# :param django.http.HttpRequest request:
# :param kwargs:
# """
# try:
# custom_callback = self.get_custom_field_instance_callback(
# integrate_with=integrate_with
# )
# return custom_callback.callback(
# form_entry=form_entry,
# request=request,
# **kwargs
# )
# except Exception as err:
# logger.debug(
# "Error in class %s. Details: %s",
# self.__class__.__name__,
# str(err)
# )
class IntegrationFormCallback(object):
"""Integration form callback."""
integrate_with = None
def __init__(self):
"""Constructor."""
assert self.stage in CALLBACK_STAGES
assert self.integrate_with is not None
def _callback(self, form_entry, request, **kwargs):
"""Callback (internal method).
Calling the ``callback`` method in a safe way.
"""
try:
return self.callback(form_entry, request, **kwargs)
except Exception as err:
logger.debug(
"Error in class %s. Details: %s",
self.__class__.__name__,
str(err)
)
def callback(self, form_entry, request, **kwargs):
"""Callback.
Custom callback code should be implemented here.
:param fobi.models.FormEntry form_entry: Instance of
``fobi.models.FormEntry``.
:param django.http.HttpRequest request:
:param django.forms.Form form:
"""
raise NotImplementedError(
"You should implement ``callback`` method in your {0} "
"subclass.".format(self.__class__.__name__)
)
class ClassProperty(property):
@ -2507,6 +2528,61 @@ class FormCallbackRegistry(object):
return callbacks
class IntegrationFormCallbackRegistry(object):
"""Registry of callbacks for integration plugins.
Holds callbacks for stages listed in the
``fobi.constants.CALLBACK_STAGES``.
"""
def __init__(self):
"""Constructor."""
self._registry = defaultdict(lambda: defaultdict(list))
@property
def registry(self):
return self._registry
def uidfy(self, cls):
"""Makes a UID string from the class given.
:param mixed cls:
:return string:
"""
return "{0}.{1}".format(cls.__module__, cls.__name__)
def register(self, cls):
"""Registers the plugin in the registry.
:param mixed cls:
"""
if not issubclass(cls, IntegrationFormCallback):
raise InvalidRegistryItemType(
"Invalid item type `{0}` for registry "
"`{1}`".format(cls, self.__class__)
)
if cls in self._registry[cls.integrate_with][cls.stage]:
return False
else:
self._registry[cls.integrate_with][cls.stage].append(cls)
return True
def get_callbacks(self, integrate_with, stage=None):
"""Get callbacks for the stage given.
:param str integrate_with:
:param string stage:
:return list:
"""
if stage:
return self._registry[integrate_with].get(stage, [])
else:
callbacks = []
for stage_callbacks in self._registry[integrate_with].values():
callbacks += stage_callbacks
return callbacks
class BasePluginWidgetRegistry(object):
"""Registry of plugins widgets (renderers)."""
type = None
@ -2630,6 +2706,9 @@ theme_registry = ThemeRegistry()
# Register action plugins by calling form_action_plugin_registry.register()
form_callback_registry = FormCallbackRegistry()
# Register action plugins by calling form_action_plugin_registry.register()
integration_form_callback_registry = IntegrationFormCallbackRegistry()
# Register plugin widgets by calling
# form_element_plugin_widget_registry.register()
form_element_plugin_widget_registry = FormElementPluginWidgetRegistry()

View file

@ -19,6 +19,14 @@ Supported fields
----------------
The following fields are supported.
Content (presentational form elements)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- content_image
- content_text
- content_video
Fields
~~~~~~
- boolean
- checkbox_select_multiple
- date
@ -52,9 +60,6 @@ The following fields are not supported. Those marked with asterisk are planned
to be supported in the upcoming releases.
- date_drop_down
- content_image
- content_text
- content_video
- select_model_object
- select_mptt_model_object
- select_multiple_model_objects
@ -100,6 +105,9 @@ the core plugins:
- fobi.contrib.plugins.form_elements.fields.textarea
- fobi.contrib.plugins.form_elements.fields.time
- fobi.contrib.plugins.form_elements.fields.url
- fobi.contrib.plugins.form_elements.content.content_image
- fobi.contrib.plugins.form_elements.content.content_text
- fobi.contrib.plugins.form_elements.content.content_video
- fobi.contrib.plugins.form_handlers.db_store
- fobi.contrib.plugins.form_handlers.http_repost
- fobi.contrib.plugins.form_handlers.mail
@ -133,6 +141,9 @@ in the ``INSTALLED_APPS`` as well:
- fobi.contrib.apps.drf_integration.form_elements.fields.textarea
- fobi.contrib.apps.drf_integration.form_elements.fields.time
- fobi.contrib.apps.drf_integration.form_elements.fields.url
- fobi.contrib.apps.drf_integration.form_elements.content.content_image
- fobi.contrib.apps.drf_integration.form_elements.content.content_text
- fobi.contrib.apps.drf_integration.form_elements.content.content_video
- fobi.contrib.apps.drf_integration.form_handlers.db_store
- fobi.contrib.apps.drf_integration.form_handlers.http_repost
- fobi.contrib.apps.drf_integration.form_handlers.mail
@ -165,7 +176,7 @@ See the `example settings file
'rest_framework', # Django REST framework
'fobi.contrib.apps.drf_integration', # DRF integration app
# DRF integration form element plugins
# DRF integration form element plugins - fields
'fobi.contrib.apps.drf_integration.form_elements.fields.boolean',
'fobi.contrib.apps.drf_integration.form_elements.fields.checkbox_select_multiple',
'fobi.contrib.apps.drf_integration.form_elements.fields.date',
@ -193,6 +204,11 @@ See the `example settings file
'fobi.contrib.apps.drf_integration.form_elements.fields.time',
'fobi.contrib.apps.drf_integration.form_elements.fields.url',
# DRF integration form element plugins - presentational
'fobi.contrib.apps.drf_integration.form_elements.content.content_image',
'fobi.contrib.apps.drf_integration.form_elements.content.content_text',
'fobi.contrib.apps.drf_integration.form_elements.content.content_video',
# DRF integration form handler plugins
'fobi.contrib.apps.drf_integration.form_handlers.db_store',
'fobi.contrib.apps.drf_integration.form_handlers.mail',
@ -247,6 +263,51 @@ PUT
{DATA}
Callbacks
---------
Callbacks work just the same way the core callbacks work.
fobi_form_callbacks.py
~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: python
from fobi.base import (
integration_form_callback_registry,
IntegrationFormCallback,
)
from fobi.constants import (
CALLBACK_BEFORE_FORM_VALIDATION,
CALLBACK_FORM_INVALID,
CALLBACK_FORM_VALID,
CALLBACK_FORM_VALID_AFTER_FORM_HANDLERS,
CALLBACK_FORM_VALID_BEFORE_SUBMIT_PLUGIN_FORM_DATA,
)
from fobi.contrib.apps.drf_integration import UID as INTEGRATE_WITH
class DRFSaveAsFooItem(IntegrationFormCallback):
"""Save the form as a foo item, if certain conditions are met."""
stage = CALLBACK_FORM_VALID
integrate_with = INTEGRATE_WITH
def callback(self, form_entry, request, **kwargs):
"""Custom callback login comes here."""
logger.debug("Great! Your form is valid!")
class DRFDummyInvalidCallback(IntegrationFormCallback):
"""Saves the form as a foo item, if certain conditions are met."""
stage = CALLBACK_FORM_INVALID
integrate_with = INTEGRATE_WITH
def callback(self, form_entry, request, **kwargs):
"""Custom callback login comes here."""
logger.debug("Damn! You've made a mistake, boy!")
Testing
-------
To test Django REST framework integration package only, run the following
@ -264,5 +325,4 @@ or use plain Django tests:
Limitations
-----------
Due to limits of the API interface, certain fields are not available
yet (presentational fields).
Certain fields are not available yet (relational fields).

View file

@ -9,8 +9,8 @@ Must haves
instances. Do it this way and not the other way, since things get
complicated when we start to deal with wizards.
+ Find out how to handle further the submitted data? It should be in
accordance with fobi concepts of loosely couple parts. After successful
submission, the fobi form callbacks, handlers and that kind of things
accordance with ``fobi`` concepts of loosely couple parts. After successful
submission, the ``fobi`` form callbacks, handlers and that kind of things
should be fired for the given form entry. Thus, it should likely be the
same in this case. Probably each CustomFieldInstancePlugin should get
a method ``drf_submit_plugin_form_data``, which should mimic the
@ -27,19 +27,19 @@ Must haves
+ Fixed Python3 issues with max_length for text fields.
+ In decimal plugin, if any of the values are None, don't try to cast them
into Decimal.
- Add ``date_drop_down`` plugin.
+ Fix this https://github.com/barseghyanartur/django-fobi/blob/master/src/fobi/contrib/apps/drf_integration/views.py#L151
It should not be form = ... but serializer = ...
Should haves
------------
+ Find why HiddenInput tests fail (in terms of Django REST framework it's
a read-only field).
- Add custom field instance callback for handling data of the custom field
instances. Do it this way and not the other way, since things get
complicated when we start to deal with wizards.
- Add more fields (relation- and presentational- fields).
+ Add Integration form callbacks for handling data of the integration plugins.
- Add ``date_drop_down`` plugin.
- Add more fields (relation fields).
- Think of what to do with presentational fields (perhaps just display?)
- Somehow, the `file` plugin data, when submitted, isn't shown properly in the
posted data (by DRF), although is posted 100% correctly.
- Somehow, the ``file`` plugin data, when submitted, isn't shown properly in
the posted data (by DRF), although is posted 100% correctly.
- In the API form view, use memoize technique or cache the value somehow to
reduce the number of queries.
@ -49,4 +49,4 @@ Could haves
Would haves
-----------
- Remove codebin.py at the end.
+ Remove codebin.py at the end.

View file

@ -2,12 +2,12 @@ import copy
import logging
from ....base import (
IntegrationFormElementPluginProcessor,
# form_callback_registry,
get_ignorable_form_fields,
clean_dict,
get_ignorable_form_fields,
get_ordered_form_handler_plugins,
integration_form_callback_registry,
integration_form_element_plugin_registry,
IntegrationFormElementPluginProcessor,
)
from ....helpers import get_ignorable_form_values
@ -131,27 +131,29 @@ class DRFSubmitPluginFormDataMixin(object):
"""
# def fire_form_callbacks(form_entry, request, serializer, stage=None):
# """Fire form callbacks.
#
# :param fobi.models.FormEntry form_entry:
# :param django.http.HttpRequest request:
# :param rest_framework.serializers.Serializer serializer:
# :param string stage:
# :return rest_framework.serializers.Serializer serializer:
# """
# callbacks = form_callback_registry.get_callbacks(stage=stage)
# for CallbackClass in callbacks:
# callback = CallbackClass()
# updated_serializer = callback.custom_field_instances_callback(
# integrate_with=UID,
# form_entry=form_entry,
# request=request,
# serializer=serializer,
# )
# if updated_serializer:
# serializer = updated_serializer
# return serializer
def fire_form_callbacks(form_entry, request, serializer, stage=None):
"""Fire DRF integration form callbacks.
:param fobi.models.FormEntry form_entry:
:param django.http.HttpRequest request:
:param rest_framework.serializers.Serializer serializer:
:param string stage:
:return rest_framework.serializers.Serializer serializer:
"""
callbacks = integration_form_callback_registry.get_callbacks(
integrate_with=UID,
stage=stage
)
for callback_cls in callbacks:
callback = callback_cls()
updated_serializer = callback.callback(
form_entry=form_entry,
request=request,
serializer=serializer
)
if updated_serializer:
serializer = updated_serializer
return serializer
def run_form_handlers(form_entry,
@ -232,17 +234,17 @@ def submit_plugin_form_data(form_entry,
)
if custom_plugin_cls:
custom_plugin = custom_plugin_cls()
updated_serializer = \
custom_plugin._submit_plugin_form_data(
form_element_plugin=form_element_plugin,
form_entry=form_entry,
request=request,
serializer=serializer,
form_element_entries=form_element_entries,
**kwargs
)
if updated_serializer:
serializer = updated_serializer
updated_serializer = \
custom_plugin._submit_plugin_form_data(
form_element_plugin=form_element_plugin,
form_entry=form_entry,
request=request,
serializer=serializer,
form_element_entries=form_element_entries,
**kwargs
)
if updated_serializer:
serializer = updated_serializer
return serializer

View file

@ -1,13 +1,13 @@
import copy
# import six
#
# from django.utils.safestring import mark_safe
import six
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
from rest_framework.fields import (
# Field,
# empty,
Field,
empty,
MultipleChoiceField,
)
@ -16,7 +16,11 @@ __author__ = 'Artur Barseghyan <artur.barseghyan@gmail.com>'
__copyright__ = '2016-2017 Artur Barseghyan'
__license__ = 'GPL 2.0/LGPL 2.1'
__all__ = (
'ContentImageField',
'ContentTextField',
'ContentVideoField',
'MultipleChoiceWithMaxField',
'NoneField',
)
@ -43,3 +47,49 @@ class MultipleChoiceWithMaxField(MultipleChoiceField):
MultipleChoiceWithMaxField,
self
).to_internal_value(data)
class NoneField(Field):
"""NoneField."""
default_error_messages = {}
initial = ''
default_empty_html = ''
def __init__(self, **kwargs):
self.allow_blank = True
self.trim_whitespace = kwargs.pop('trim_whitespace', True)
self.raw_data = kwargs.pop('raw_data', {})
super(NoneField, self).__init__(**kwargs)
def run_validation(self, data=empty):
return ''
def to_internal_value(self, data):
# We're lenient with allowing basic numerics to be coerced into
# strings, but other types should fail. Eg. unclear if booleans
# should represent as `true` or `True`, and composites such as lists
# are likely user error.
_not_isinstance_str_int_float = not isinstance(
data, six.string_types + six.integer_types + (float,)
)
if isinstance(data, bool) or _not_isinstance_str_int_float:
self.fail('invalid')
value = six.text_type(data)
return value.strip() if self.trim_whitespace else value
def to_representation(self, value):
return mark_safe(six.text_type(value))
class ContentTextField(NoneField):
"""Content text field."""
class ContentImageField(NoneField):
"""Content image field."""
class ContentVideoField(NoneField):
"""Content video field."""

View file

@ -0,0 +1,28 @@
====================================================================
fobi.contrib.apps.drf_integration.form_elements.fields.content_image
====================================================================
A ``django-fobi`` ContentImage plugin for integration with
``Django REST framework``. Makes use of the
``fobi.contrib.apps.drf_integration.fields.ContentImage``.
Installation
============
1. Add ``fobi.contrib.apps.drf_integration.form_elements.fields.content_image``
to the ``INSTALLED_APPS`` in your ``settings.py``.
.. code-block:: python
INSTALLED_APPS = (
# ...
'fobi.contrib.apps.drf_integration.form_elements.fields.content_image',
# ...
)
2. In the terminal type:
.. code-block:: sh
./manage.py fobi_sync_plugins
3. Assign appropriate permissions to the target users/groups to be using
the plugin if ``FOBI_RESTRICT_PLUGIN_ACCESS`` is set to True.

View file

@ -0,0 +1,11 @@
__title__ = 'fobi.contrib.apps.drf_integration.form_elements.content.' \
'content_image'
__author__ = 'Artur Barseghyan <artur.barseghyan@gmail.com>'
__copyright__ = '2014-2017 Artur Barseghyan'
__license__ = 'GPL 2.0/LGPL 2.1'
__all__ = ('default_app_config', 'UID',)
default_app_config = 'fobi.contrib.apps.drf_integration.form_elements.' \
'content.content_image.apps.Config'
UID = 'content_image'

View file

@ -0,0 +1,19 @@
__title__ = 'fobi.contrib.apps.drf_integration.form_elements.content.' \
'content_image.apps'
__author__ = 'Artur Barseghyan <artur.barseghyan@gmail.com>'
__copyright__ = '2014-2017 Artur Barseghyan'
__license__ = 'GPL 2.0/LGPL 2.1'
try:
__all__ = ('Config',)
from django.apps import AppConfig
class Config(AppConfig):
name = 'fobi.contrib.apps.drf_integration.form_elements.content.' \
'content_image'
label = 'fobi_contrib_apps_drf_integration_form_elements_content_' \
'content_image'
except ImportError:
pass

View file

@ -0,0 +1,59 @@
import logging
from django.utils.translation import ugettext_lazy as _
from .......base import IntegrationFormElementPlugin
from .... import UID as INTEGRATE_WITH_UID
from ....base import (
DRFIntegrationFormElementPluginProcessor,
DRFSubmitPluginFormDataMixin,
)
from ....fields import ContentImageField
from . import UID
__title__ = 'fobi.contrib.apps.drf_integration.form_elements.content.' \
'content_image.base'
__author__ = 'Artur Barseghyan <artur.barseghyan@gmail.com>'
__copyright__ = '2014-2017 Artur Barseghyan'
__license__ = 'GPL 2.0/LGPL 2.1'
__all__ = ('ContentImagePlugin',)
LOGGER = logging.getLogger(__name__)
class ContentImagePlugin(IntegrationFormElementPlugin,
DRFSubmitPluginFormDataMixin):
"""CharField plugin."""
uid = UID
integrate_with = INTEGRATE_WITH_UID
name = _("Content image")
group = _("Content")
def get_custom_field_instances(self,
form_element_plugin,
request=None,
form_entry=None,
form_element_entries=None,
has_value=None,
**kwargs):
"""Get form field instances."""
rendered_image = form_element_plugin.get_rendered_image()
field_kwargs = {
'initial': rendered_image,
'default': rendered_image,
'required': False,
'label': '',
'read_only': True,
'raw_data': form_element_plugin.get_raw_data(),
}
return [
DRFIntegrationFormElementPluginProcessor(
field_class=ContentImageField,
field_kwargs=field_kwargs
)
]

View file

@ -0,0 +1,12 @@
from .......base import integration_form_element_plugin_registry
from .base import ContentImagePlugin
__title__ = 'fobi.contrib.apps.drf_integration.form_elements.content.' \
'content_image.fobi_integration_form_elements'
__author__ = 'Artur Barseghyan <artur.barseghyan@gmail.com>'
__copyright__ = '2014-2017 Artur Barseghyan'
__license__ = 'GPL 2.0/LGPL 2.1'
__all__ = ('ContentImagePlugin',)
integration_form_element_plugin_registry.register(ContentImagePlugin)

View file

@ -0,0 +1,28 @@
===================================================================
fobi.contrib.apps.drf_integration.form_elements.fields.content_text
===================================================================
A ``django-fobi`` ContentText plugin for integration with
``Django REST framework``. Makes use of the
``fobi.contrib.apps.drf_integration.fields.ContentText``.
Installation
============
1. Add ``fobi.contrib.apps.drf_integration.form_elements.fields.content_text``
to the ``INSTALLED_APPS`` in your ``settings.py``.
.. code-block:: python
INSTALLED_APPS = (
# ...
'fobi.contrib.apps.drf_integration.form_elements.fields.content_text',
# ...
)
2. In the terminal type:
.. code-block:: sh
./manage.py fobi_sync_plugins
3. Assign appropriate permissions to the target users/groups to be using
the plugin if ``FOBI_RESTRICT_PLUGIN_ACCESS`` is set to True.

View file

@ -0,0 +1,11 @@
__title__ = 'fobi.contrib.apps.drf_integration.form_elements.content.' \
'content_text'
__author__ = 'Artur Barseghyan <artur.barseghyan@gmail.com>'
__copyright__ = '2014-2017 Artur Barseghyan'
__license__ = 'GPL 2.0/LGPL 2.1'
__all__ = ('default_app_config', 'UID',)
default_app_config = 'fobi.contrib.apps.drf_integration.form_elements.' \
'content.content_text.apps.Config'
UID = 'content_text'

View file

@ -0,0 +1,19 @@
__title__ = 'fobi.contrib.apps.drf_integration.form_elements.content.' \
'content_text.apps'
__author__ = 'Artur Barseghyan <artur.barseghyan@gmail.com>'
__copyright__ = '2014-2017 Artur Barseghyan'
__license__ = 'GPL 2.0/LGPL 2.1'
try:
__all__ = ('Config',)
from django.apps import AppConfig
class Config(AppConfig):
name = 'fobi.contrib.apps.drf_integration.form_elements.content.' \
'content_text'
label = 'fobi_contrib_apps_drf_integration_form_elements_content_' \
'content_text'
except ImportError:
pass

View file

@ -0,0 +1,61 @@
import logging
from django.utils.translation import ugettext_lazy as _
from .......base import IntegrationFormElementPlugin
from .... import UID as INTEGRATE_WITH_UID
from ....base import (
DRFIntegrationFormElementPluginProcessor,
DRFSubmitPluginFormDataMixin,
)
from ....fields import ContentTextField
from . import UID
__title__ = 'fobi.contrib.apps.drf_integration.form_elements.content.' \
'content_text.base'
__author__ = 'Artur Barseghyan <artur.barseghyan@gmail.com>'
__copyright__ = '2014-2017 Artur Barseghyan'
__license__ = 'GPL 2.0/LGPL 2.1'
__all__ = ('ContentTextPlugin',)
LOGGER = logging.getLogger(__name__)
class ContentTextPlugin(IntegrationFormElementPlugin,
DRFSubmitPluginFormDataMixin):
"""CharField plugin."""
uid = UID
integrate_with = INTEGRATE_WITH_UID
name = _("Content text")
group = _("Content")
def get_custom_field_instances(self,
form_element_plugin,
request=None,
form_entry=None,
form_element_entries=None,
has_value=None,
**kwargs):
"""Get form field instances."""
rendered_text = form_element_plugin.get_rendered_text()
LOGGER.debug(rendered_text)
field_kwargs = {
'initial': rendered_text,
'default': rendered_text,
'required': False,
'label': '',
'read_only': True,
'raw_data': form_element_plugin.get_raw_data(),
}
return [
DRFIntegrationFormElementPluginProcessor(
field_class=ContentTextField,
field_kwargs=field_kwargs
)
]

View file

@ -0,0 +1,12 @@
from .......base import integration_form_element_plugin_registry
from .base import ContentTextPlugin
__title__ = 'fobi.contrib.apps.drf_integration.form_elements.content.' \
'content_text.fobi_integration_form_elements'
__author__ = 'Artur Barseghyan <artur.barseghyan@gmail.com>'
__copyright__ = '2014-2017 Artur Barseghyan'
__license__ = 'GPL 2.0/LGPL 2.1'
__all__ = ('ContentTextPlugin',)
integration_form_element_plugin_registry.register(ContentTextPlugin)

View file

@ -0,0 +1,28 @@
====================================================================
fobi.contrib.apps.drf_integration.form_elements.fields.content_video
====================================================================
A ``django-fobi`` ContentVideo plugin for integration with
``Django REST framework``. Makes use of the
``fobi.contrib.apps.drf_integration.fields.ContentVideo``.
Installation
============
1. Add ``fobi.contrib.apps.drf_integration.form_elements.fields.content_video``
to the ``INSTALLED_APPS`` in your ``settings.py``.
.. code-block:: python
INSTALLED_APPS = (
# ...
'fobi.contrib.apps.drf_integration.form_elements.fields.content_video',
# ...
)
2. In the terminal type:
.. code-block:: sh
./manage.py fobi_sync_plugins
3. Assign appropriate permissions to the target users/groups to be using
the plugin if ``FOBI_RESTRICT_PLUGIN_ACCESS`` is set to True.

View file

@ -0,0 +1,11 @@
__title__ = 'fobi.contrib.apps.drf_integration.form_elements.content.' \
'content_video'
__author__ = 'Artur Barseghyan <artur.barseghyan@gmail.com>'
__copyright__ = '2014-2017 Artur Barseghyan'
__license__ = 'GPL 2.0/LGPL 2.1'
__all__ = ('default_app_config', 'UID',)
default_app_config = 'fobi.contrib.apps.drf_integration.form_elements.' \
'content.content_video.apps.Config'
UID = 'content_video'

View file

@ -0,0 +1,19 @@
__title__ = 'fobi.contrib.apps.drf_integration.form_elements.content.' \
'content_video.apps'
__author__ = 'Artur Barseghyan <artur.barseghyan@gmail.com>'
__copyright__ = '2014-2017 Artur Barseghyan'
__license__ = 'GPL 2.0/LGPL 2.1'
try:
__all__ = ('Config',)
from django.apps import AppConfig
class Config(AppConfig):
name = 'fobi.contrib.apps.drf_integration.form_elements.content.' \
'content_video'
label = 'fobi_contrib_apps_drf_integration_form_elements_content_' \
'content_video'
except ImportError:
pass

View file

@ -0,0 +1,58 @@
import logging
from django.utils.translation import ugettext_lazy as _
from .......base import IntegrationFormElementPlugin
from .... import UID as INTEGRATE_WITH_UID
from ....base import (
DRFIntegrationFormElementPluginProcessor,
DRFSubmitPluginFormDataMixin,
)
from ....fields import ContentVideoField
from . import UID
__title__ = 'fobi.contrib.apps.drf_integration.form_elements.content.' \
'content_video.base'
__author__ = 'Artur Barseghyan <artur.barseghyan@gmail.com>'
__copyright__ = '2014-2017 Artur Barseghyan'
__license__ = 'GPL 2.0/LGPL 2.1'
__all__ = ('ContentVideoPlugin',)
LOGGER = logging.getLogger(__name__)
class ContentVideoPlugin(IntegrationFormElementPlugin,
DRFSubmitPluginFormDataMixin):
"""CharField plugin."""
uid = UID
integrate_with = INTEGRATE_WITH_UID
name = _("Content image")
group = _("Content")
def get_custom_field_instances(self,
form_element_plugin,
request=None,
form_entry=None,
form_element_entries=None,
has_value=None,
**kwargs):
"""Get form field instances."""
rendered_video = form_element_plugin.get_rendered_video()
field_kwargs = {
'initial': rendered_video,
'default': rendered_video,
'required': False,
'label': '',
'read_only': True,
'raw_data': form_element_plugin.get_raw_data(),
}
return [
DRFIntegrationFormElementPluginProcessor(
field_class=ContentVideoField,
field_kwargs=field_kwargs
)
]

View file

@ -0,0 +1,12 @@
from .......base import integration_form_element_plugin_registry
from .base import ContentVideoPlugin
__title__ = 'fobi.contrib.apps.drf_integration.form_elements.content.' \
'content_video.fobi_integration_form_elements'
__author__ = 'Artur Barseghyan <artur.barseghyan@gmail.com>'
__copyright__ = '2014-2017 Artur Barseghyan'
__license__ = 'GPL 2.0/LGPL 2.1'
__all__ = ('ContentVideoPlugin',)
integration_form_element_plugin_registry.register(ContentVideoPlugin)

View file

@ -1,20 +1,20 @@
===============================================================
fobi.contrib.apps.drf_integration.form_elements.fields.datetime
===============================================================
==============================================================
fobi.contrib.apps.drf_integration.form_elements.fields.decimal
==============================================================
A ``django-fobi`` DecimalField plugin for integration with
``Django REST framework``. Makes use of the
``rest_framework.fields.DateTimeField``.
``rest_framework.fields.DecimalField``.
Installation
============
1. Add ``fobi.contrib.apps.drf_integration.form_elements.fields.datetime`` to
1. Add ``fobi.contrib.apps.drf_integration.form_elements.fields.decimal`` to
the ``INSTALLED_APPS`` in your ``settings.py``.
.. code-block:: python
INSTALLED_APPS = (
# ...
'fobi.contrib.apps.drf_integration.form_elements.fields.datetime',
'fobi.contrib.apps.drf_integration.form_elements.fields.decimal',
# ...
)

View file

@ -0,0 +1,63 @@
import copy
from rest_framework.metadata import SimpleMetadata
from rest_framework.utils.field_mapping import ClassLookupDict
from .fields import (
MultipleChoiceWithMaxField,
ContentImageField,
ContentTextField,
ContentVideoField,
)
__title__ = 'fobi.contrib.apps.drf_integration.metadata'
__author__ = 'Artur Barseghyan <artur.barseghyan@gmail.com>'
__copyright__ = '2016-2017 Artur Barseghyan'
__license__ = 'GPL 2.0/LGPL 2.1'
__all__ = (
'FobiMetaData',
)
class FobiMetaData(SimpleMetadata):
"""Meta data for better representation of the form elements."""
__mapping = copy.copy(SimpleMetadata.label_lookup.mapping)
__mapping.update(
{
MultipleChoiceWithMaxField: 'multiple choice',
ContentImageField: 'content',
ContentTextField: 'content',
ContentVideoField: 'content',
}
)
label_lookup = ClassLookupDict(__mapping)
def get_field_info(self, field):
"""Get field info.
Given an instance of a serializer field, return a dictionary
of metadata about it.
"""
field_info = super(FobiMetaData, self).get_field_info(field)
if isinstance(
field,
(ContentTextField, ContentImageField, ContentVideoField)
):
field_info['type'] = 'content'
if isinstance(field, ContentTextField):
field_info['contenttype'] = 'text'
field_info['content'] = field.initial
field_info['raw'] = field.raw_data
elif isinstance(field, ContentImageField):
field_info['contenttype'] = 'image'
field_info['content'] = field.initial
field_info['raw'] = field.raw_data
else:
field_info['contenttype'] = 'video'
field_info['content'] = field.initial
field_info['raw'] = field.raw_data
return field_info

View file

@ -17,11 +17,12 @@ from ....constants import (
from ....models import FormEntry
from .base import (
# fire_form_callbacks,
fire_form_callbacks,
run_form_handlers,
submit_plugin_form_data,
)
from .dynamic import get_declared_fields
from .metadata import FobiMetaData
from .serializers import FormEntrySerializer
from .utils import get_serializer_class
@ -52,6 +53,7 @@ class FobiFormEntryViewSet(
permission_classes = [permissions.AllowAny]
lookup_field = 'slug'
lookup_url_kwarg = 'slug'
metadata_class = FobiMetaData
def has_value(self):
return None if self.action == 'metadata' else True
@ -147,13 +149,13 @@ class FobiFormEntryViewSet(
# Try to fetch only once.
form_element_entries = form_entry.formelemententry_set.all()
# # Fire form valid before submit plugin data
# form = fire_form_callbacks(
# form_entry=form_entry,
# request=request,
# form=form,
# stage=CALLBACK_FORM_VALID_BEFORE_SUBMIT_PLUGIN_FORM_DATA
# )
# Fire form valid before submit plugin data
serializer = fire_form_callbacks(
form_entry=form_entry,
request=request,
serializer=serializer,
stage=CALLBACK_FORM_VALID_BEFORE_SUBMIT_PLUGIN_FORM_DATA
)
# Fire plugin processors
serializer = submit_plugin_form_data(
@ -162,10 +164,13 @@ class FobiFormEntryViewSet(
serializer=serializer
)
# # Fire form valid callbacks
# form = fire_form_callbacks(form_entry=form_entry,
# request=request, form=form,
# stage=CALLBACK_FORM_VALID)
# Fire form valid callbacks
serializer = fire_form_callbacks(
form_entry=form_entry,
request=request,
serializer=serializer,
stage=CALLBACK_FORM_VALID
)
# Run all handlers
handler_responses, handler_errors = run_form_handlers(
@ -187,9 +192,9 @@ class FobiFormEntryViewSet(
)
# Fire post handler callbacks
# fire_form_callbacks(
# form_entry=form_entry,
# request=request,
# form=form,
# stage=CALLBACK_FORM_VALID_AFTER_FORM_HANDLERS
# )
fire_form_callbacks(
form_entry=form_entry,
request=request,
serializer=serializer,
stage=CALLBACK_FORM_VALID_AFTER_FORM_HANDLERS
)

View file

@ -1,5 +1,6 @@
from __future__ import absolute_import
from collections import OrderedDict
from uuid import uuid4
from django.conf import settings
@ -56,6 +57,20 @@ class ContentImagePlugin(FormElementPlugin):
)
return self.get_cloned_plugin_data(update={'file': cloned_image})
def get_raw_data(self):
"""Get raw data.
Might be used in integration plugins.
"""
return OrderedDict(
(
('file', "{}{}".format(settings.MEDIA_URL, self.data.file)),
('alt', self.data.alt),
('fit_method', self.data.fit_method),
('size', self.data.size),
)
)
def get_rendered_image(self):
"""Get rendered image."""
width, height = self.data.size.split('x')

View file

@ -1,7 +1,9 @@
from __future__ import absolute_import
from collections import OrderedDict
from uuid import uuid4
from django.template.loader import render_to_string
from django.utils.encoding import smart_str
from django.utils.translation import ugettext_lazy as _
@ -34,6 +36,26 @@ class ContentTextPlugin(FormElementPlugin):
"""
self.data.name = "{0}_{1}".format(self.uid, uuid4())
def get_raw_data(self):
"""Get raw data.
Might be used in integration plugins.
"""
return OrderedDict(
(
('text', self.data.text),
)
)
def get_rendered_text(self):
"""Get rendered image."""
context = {
'plugin': self,
}
rendered_text = render_to_string('content_image/render.html', context)
return rendered_text
def get_form_field_instances(self, request=None, form_entry=None,
form_element_entries=None, **kwargs):
"""Get form field instances."""

View file

@ -1,5 +1,6 @@
from __future__ import absolute_import
from collections import OrderedDict
from uuid import uuid4
from django.utils.translation import ugettext_lazy as _
@ -35,14 +36,33 @@ class ContentVideoPlugin(FormElementPlugin):
"""
self.data.name = "{0}_{1}".format(self.uid, uuid4())
def get_raw_data(self):
"""Get raw data.
Might be used in integration plugins.
"""
return OrderedDict(
(
('title', self.data.title),
('url', self.data.url),
('size', self.data.size),
)
)
def get_rendered_video(self):
"""Get rendered video.
Might be used in integration plugins.
"""
width, height = self.data.size.split('x')
return render_video(self.data.url, width, height)
def get_form_field_instances(self, request=None, form_entry=None,
form_element_entries=None, **kwargs):
"""Get form field instances."""
width, height = self.data.size.split('x')
field_kwargs = {
'initial': '<div class="video-wrapper">{0}</div>'.format(
render_video(self.data.url, width, height)
self.get_rendered_video()
),
'required': False,
'label': '',