From d5d505b962b010b4036c09bccdc8e1b293bd9c32 Mon Sep 17 00:00:00 2001 From: Artur Barseghyan Date: Fri, 7 Nov 2014 03:09:01 +0100 Subject: [PATCH] prepare 0.2.1; do not wrap media files in tags when saving data of db_store plugin; make sure custom form handler actions are properly listed in the simple theme as well; make it possible to fail silently on missing form element- and form handler- plugins; --- CHANGELOG.rst | 18 ++++- README.rst | 22 ++++++ TODOS.rst | 6 ++ docs/index.rst | 22 ++++++ setup.py | 2 +- src/fobi/__init__.py | 4 +- src/fobi/base.py | 71 ++++++++++++++----- .../fobi_form_elements.py | 2 +- .../db_store/fobi_form_handlers.py | 12 ++-- .../plugins/form_handlers/db_store/models.py | 23 ++++-- .../http_repost/fobi_form_handlers.py | 6 -- .../foundation5/edit_form_entry_ajax.html | 2 + .../simple/edit_form_entry_ajax.html | 16 +++-- src/fobi/defaults.py | 31 +++++--- src/fobi/dynamic.py | 57 +++++++++------ src/fobi/exceptions.py | 21 +++++- src/fobi/models.py | 5 ++ src/fobi/settings.py | 49 +++++++++---- .../fobi/generic/edit_form_entry_ajax.html | 2 + src/fobi/views.py | 3 + 20 files changed, 279 insertions(+), 95 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8fa7165f..8f532892 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,5 +1,19 @@ Release history and notes ===================================== +0.2.1 +------------------------------------- +2014-11-06 + +- Minor improvements of the `db_store` plugin. +- Minor improvements of the `simple` theme. Make sure that custom + form handler actions are properly shown in the form handlers list. +- Make it possible to fail silently on missing form element or form + handler plugins by setting the respected values to False: + + * `FAIL_ON_MISSING_FORM_ELEMENT_PLUGINS`, + * `FAIL_ON_MISSING_FORM_HANDLER_PLUGINS`. Raising an appropriate exception + otherwise. + 0.2 ------------------------------------- 2014-11-05 @@ -12,8 +26,8 @@ Note, that this release contains minor backwards incompatible changes. you have written your own or have changed existing form handler plugins with use of one of the above mentioned methods, append those arguments to the method declarations when upgrading to this version. If you haven't - written your own form or changed existing form handler plugins, you may - just upgrade to this version. + written your own or changed existing form handler plugins, you may just + upgrade to this version. - Added data export features to the ``db_store`` plugin. - Minor fixes in ``db_store`` plugin. - Added missing documentation for the ``feincms_integration`` app. diff --git a/README.rst b/README.rst index 58204d8c..5d96cc13 100644 --- a/README.rst +++ b/README.rst @@ -1244,6 +1244,28 @@ somehow doesn't appear in the list of available plugins, do run the plugins into the database, but also is a great way of checking for possible errors. +If you have forms refering to form element- of form handler- plugins +that are currently missing (not registered, removed, failed to load - thus +there would be a risk that your form would't be rendered properly/fully and +the necessary data handling wouldn't happen either) you will get an +appropriate exception. Although it's fine to get an instant error message about +such failures in development, in production is wouldn't look appropriate. +Thus, there are two settings related to the non-existing (not-found) form +element- and form handler- plugins. + +- FOBI_FAIL_ON_MISSING_FORM_ELEMENT_PLUGINS: If you want no error to be + shown in case of missing form element plugins, set this to False in + your settings module. Default value is True. +- FOBI_FAIL_ON_MISSING_FORM_HANDLER_PLUGINS: If you want no error to be + shown in case of missing form element handlers, set this to False in + your settings module. Default value is True. + +Troubleshooting +=============================================== +If you get a ``FormElementPluginDoesNotExist`` or a +``FormHandlerPluginDoesNotExist`` exception, make sure you have listed your +plugin in the `settings` module of your project. + License =============================================== GPL 2.0/LGPL 2.1 diff --git a/TODOS.rst b/TODOS.rst index 87ea6288..54ea9539 100644 --- a/TODOS.rst +++ b/TODOS.rst @@ -208,6 +208,12 @@ Must haves - Add data export features for the ``db_store`` plugin into the "simpe" theme as well (same way as already done fore "bootstrap 3" and "foundation 5" themes. +- Add a management command to remove broken form elements. +- Think of making putting several actions (repair) into the management + interface (UI). +- Add `PluginThemeAddOn`, which would be a stand-alone plugin for having the + specific theme HTML/JS/CSS added to the appropriate form element or a form + handler plugin. Should haves =============================================== diff --git a/docs/index.rst b/docs/index.rst index 74bd1187..80168dd6 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1244,6 +1244,28 @@ somehow doesn't appear in the list of available plugins, do run the plugins into the database, but also is a great way of checking for possible errors. +If you have forms refering to form element- of form handler- plugins +that are currently missing (not registered, removed, failed to load - thus +there would be a risk that your form would't be rendered properly/fully and +the necessary data handling wouldn't happen either) you will get an +appropriate exception. Although it's fine to get an instant error message about +such failures in development, in production is wouldn't look appropriate. +Thus, there are two settings related to the non-existing (not-found) form +element- and form handler- plugins. + +- FOBI_FAIL_ON_MISSING_FORM_ELEMENT_PLUGINS: If you want no error to be + shown in case of missing form element plugins, set this to False in + your settings module. Default value is True. +- FOBI_FAIL_ON_MISSING_FORM_HANDLER_PLUGINS: If you want no error to be + shown in case of missing form element handlers, set this to False in + your settings module. Default value is True. + +Troubleshooting +=============================================== +If you get a ``FormElementPluginDoesNotExist`` or a +``FormHandlerPluginDoesNotExist`` exception, make sure you have listed your +plugin in the `settings` module of your project. + License =============================================== GPL 2.0/LGPL 2.1 diff --git a/setup.py b/setup.py index f7205705..8388badb 100644 --- a/setup.py +++ b/setup.py @@ -56,7 +56,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.2' +version = '0.2.1' install_requires = [ 'Pillow>=2.0.0', diff --git a/src/fobi/__init__.py b/src/fobi/__init__.py index b94e6a14..13c0839f 100644 --- a/src/fobi/__init__.py +++ b/src/fobi/__init__.py @@ -1,6 +1,6 @@ __title__ = 'django-fobi' -__version__ = '0.2' -__build__ = 0x000008 +__version__ = '0.2.1' +__build__ = 0x000009 __author__ = 'Artur Barseghyan ' __copyright__ = 'Copyright (c) 2014 Artur Barseghyan' __license__ = 'GPL 2.0/LGPL 2.1' diff --git a/src/fobi/base.py b/src/fobi/base.py index fa27f33d..d3630862 100644 --- a/src/fobi/base.py +++ b/src/fobi/base.py @@ -54,9 +54,13 @@ from fobi.discover import autodiscover from fobi.constants import CALLBACK_STAGES from fobi.settings import ( DEFAULT_THEME, FORM_HANDLER_PLUGINS_EXECUTION_ORDER, - CUSTOM_THEME_DATA, THEME_FOOTER_TEXT, DEBUG + CUSTOM_THEME_DATA, THEME_FOOTER_TEXT, FAIL_ON_MISSING_FORM_ELEMENT_PLUGINS, + FAIL_ON_MISSING_FORM_HANDLER_PLUGINS, DEBUG +) +from fobi.exceptions import ( + InvalidRegistryItemType, DoesNotExist, ThemeDoesNotExist, + FormElementPluginDoesNotExist, FormHandlerPluginDoesNotExist ) -from fobi.exceptions import InvalidRegistryItemType, ThemeDoesNotExist from fobi.helpers import ( uniquify_sequence, map_field_name_to_label, clean_dict, map_field_name_to_label, get_ignorable_form_values @@ -308,8 +312,12 @@ class BaseTheme(object): {delete_text} - - + + """.format( container_class = cls.form_list_container_class, edit_option_html = "{edit_option_html}", @@ -460,7 +468,9 @@ class BasePluginForm(object): :param django.http.HttpRequest request: """ if self.plugin_data_fields: - return self._get_plugin_data(self.plugin_data_fields, request=request, json_format=json_format) + return self._get_plugin_data(self.plugin_data_fields, + request=request, + json_format=json_format) def save_plugin_data(self, request=None): """ @@ -1147,8 +1157,7 @@ class FormElementPlugin(BasePlugin): extra={}): """ If ``kwargs_update_func`` is given, is callable and returns results - without failures, - return the result. Otherwise - return None. + without failures, return the result. Otherwise - return None. """ # Check hooks if kwargs_update_func and callable(kwargs_update_func): @@ -1163,7 +1172,6 @@ class FormElementPlugin(BasePlugin): return kwargs_update except Exception as e: logger.debug(str(e)) - #import ipdb; ipdb.set_trace() return {} def _submit_plugin_form_data(self, form_entry, request, form): @@ -1371,8 +1379,21 @@ class BaseRegistry(object): """ Registry of dash plugins. It's essential, that class registered has the ``uid`` property. + + If ``fail_on_missing_plugin`` is set to True, an appropriate exception + (``plugin_not_found_exception_cls``) is raised in cases if plugin cound't + be found in the registry. + + :property mixed type: + :property bool fail_on_missing_plugin: + :property fobi.exceptions.DoesNotExist plugin_not_found_exception_cls: + :property str plugin_not_found_error_message: """ type = None + fail_on_missing_plugin = False + plugin_not_found_exception_cls = DoesNotExist + plugin_not_found_error_message = "Can't find plugin with uid `{0}` in " \ + "`{1}` registry." def __init__(self): assert self.type @@ -1432,11 +1453,17 @@ class BaseRegistry(object): :return mixed. """ item = self._registry.get(uid, default) + if not item: - logger.debug( - "Can't find plugin with uid `{0}` in `{1}` " - "registry".format(uid, self.__class__) + err_msg = self.plugin_not_found_error_message.format( + uid, self.__class__ ) + if self.fail_on_missing_plugin: + logger.error(err_msg) + raise self.plugin_not_found_exception_cls(err_msg) + else: + logger.debug(err_msg) + return item @@ -1445,6 +1472,8 @@ class FormElementPluginRegistry(BaseRegistry): Form element plugins registry. """ type = (FormElementPlugin, FormFieldPlugin) + fail_on_missing_plugin = FAIL_ON_MISSING_FORM_ELEMENT_PLUGINS + plugin_not_found_exception_cls = FormElementPluginDoesNotExist class FormHandlerPluginRegistry(BaseRegistry): @@ -1452,6 +1481,8 @@ class FormHandlerPluginRegistry(BaseRegistry): Form handler plugins registry. """ type = FormHandlerPlugin + fail_on_missing_plugin = FAIL_ON_MISSING_FORM_HANDLER_PLUGINS + plugin_not_found_exception_cls = FormHandlerPluginDoesNotExist class ThemeRegistry(BaseRegistry): @@ -1480,7 +1511,7 @@ class FormCallbackRegistry(object): :return string: """ - name = "{0}.{1}".format(cls.__module__, cls.__name__) + return "{0}.{1}".format(cls.__module__, cls.__name__) def register(self, cls): """ @@ -1494,7 +1525,7 @@ class FormCallbackRegistry(object): "`{1}`".format(cls, self.__class__) ) - uid = self.uidfy(cls) + #uid = self.uidfy(cls) # If item has not been forced yet, add/replace its' value in the # registry. @@ -1659,7 +1690,9 @@ def assemble_form_field_widget_class(base_class, plugin): Wrapped class. """ def __new__(cls, name, bases, attrs): - new_class = super(DeclarativeMetaclass, cls).__new__(cls, name, bases, attrs) + new_class = super(DeclarativeMetaclass, cls).__new__( + cls, name, bases, attrs + ) return new_class def render(self, name, value, attrs=None): @@ -1672,7 +1705,9 @@ def assemble_form_field_widget_class(base_class, plugin): return widget.render(name, value, attrs=attrs) else: #print 'rendered using standard' - super(DeclarativeMetaclass, self).render(name, value, attrs=attrs) + super(DeclarativeMetaclass, self).render( + name, value, attrs=attrs + ) class WrappedWidget(with_metaclass(DeclarativeMetaclass, base_class)): """ @@ -1681,9 +1716,9 @@ def assemble_form_field_widget_class(base_class, plugin): return WrappedWidget -# ******************************************************************************** -# *********************************** Generic ************************************ -# ******************************************************************************** +# ***************************************************************************** +# *********************************** Generic ********************************* +# ***************************************************************************** def get_registered_plugins(registry): """ diff --git a/src/fobi/contrib/plugins/form_elements/fields/select_multiple_model_objects/fobi_form_elements.py b/src/fobi/contrib/plugins/form_elements/fields/select_multiple_model_objects/fobi_form_elements.py index 0abb18af..0643a78e 100644 --- a/src/fobi/contrib/plugins/form_elements/fields/select_multiple_model_objects/fobi_form_elements.py +++ b/src/fobi/contrib/plugins/form_elements/fields/select_multiple_model_objects/fobi_form_elements.py @@ -57,7 +57,7 @@ class SelectMultipleModelObjectsInputPlugin(FormFieldPlugin): """ # Get the object obj = form.cleaned_data.get(self.data.name, None) - #import ipdb; ipdb.set_trace() + if obj: # Handle the upload admin_url = admin_change_url( diff --git a/src/fobi/contrib/plugins/form_handlers/db_store/fobi_form_handlers.py b/src/fobi/contrib/plugins/form_handlers/db_store/fobi_form_handlers.py index d6cc00ea..c11c0c8f 100644 --- a/src/fobi/contrib/plugins/form_handlers/db_store/fobi_form_handlers.py +++ b/src/fobi/contrib/plugins/form_handlers/db_store/fobi_form_handlers.py @@ -7,9 +7,6 @@ __all__ = ('DBStoreHandlerPlugin',) import json import datetime -from six import string_types - -from django.conf import settings from django.utils.translation import ugettext_lazy as _ from django.core.urlresolvers import reverse @@ -32,13 +29,10 @@ class DBStoreHandlerPlugin(FormHandlerPlugin): :param django.http.HttpRequest request: :param django.forms.Form form: """ - #import ipdb; ipdb.set_trace() # Clean up the values, leave our content fields and empty values. field_name_to_label_map, cleaned_data = get_processed_form_data(form) for key, value in cleaned_data.items(): - if isinstance(value, string_types) and value.startswith(settings.MEDIA_URL): - cleaned_data[key] = '{value}'.format(value=value) if isinstance(value, (datetime.datetime, datetime.date)): cleaned_data[key] = value.isoformat() if hasattr(value, 'isoformat') else value @@ -58,12 +52,14 @@ class DBStoreHandlerPlugin(FormHandlerPlugin): """ return ( ( - reverse('fobi.contrib.plugins.form_handlers.db_store.view_saved_form_data_entries', args=[form_entry.pk]), + reverse('fobi.contrib.plugins.form_handlers.db_store.view_saved_form_data_entries', + args=[form_entry.pk]), _("View entries"), 'glyphicon glyphicon-list' ), ( - reverse('fobi.contrib.plugins.form_handlers.db_store.export_saved_form_data_entries', args=[form_entry.pk]), + reverse('fobi.contrib.plugins.form_handlers.db_store.export_saved_form_data_entries', + args=[form_entry.pk]), _("Export entries"), 'glyphicon glyphicon-export' ), diff --git a/src/fobi/contrib/plugins/form_handlers/db_store/models.py b/src/fobi/contrib/plugins/form_handlers/db_store/models.py index e813c15d..ab06003f 100644 --- a/src/fobi/contrib/plugins/form_handlers/db_store/models.py +++ b/src/fobi/contrib/plugins/form_handlers/db_store/models.py @@ -6,9 +6,11 @@ __all__ = ('SavedFormDataEntry',) import json +from six import string_types + from django.db import models from django.utils.translation import ugettext_lazy as _ -#from django.contrib.auth.models import User +from django.conf import settings from django.db import models @@ -38,7 +40,8 @@ except ImportError: raise ImproperlyConfigured("Your custom user model ({0}.{1}) doesn't " "have ``username`` property, while " "``django-fobi`` relies on its' presence" - ".".format(user._meta.app_label, user._meta.object_name)) + ".".format(user._meta.app_label, \ + user._meta.object_name)) # **************************************************************************** # **************************************************************************** @@ -50,9 +53,12 @@ class SavedFormDataEntry(models.Model): """ Saved form data. """ - form_entry = models.ForeignKey('fobi.FormEntry', verbose_name=_("Form"), null=True, blank=True) - user = models.ForeignKey(User, verbose_name=_("User"), null=True, blank=True) - form_data_headers = models.TextField(_("Form data headers"), null=True, blank=True) + form_entry = models.ForeignKey('fobi.FormEntry', verbose_name=_("Form"), + null=True, blank=True) + user = models.ForeignKey(User, verbose_name=_("User"), null=True, + blank=True) + form_data_headers = models.TextField(_("Form data headers"), null=True, + blank=True) saved_data = models.TextField(_("Plugin data"), null=True, blank=True) created = models.DateTimeField(_("Date created"), auto_now_add=True) @@ -72,6 +78,13 @@ class SavedFormDataEntry(models.Model): """ headers = json.loads(self.form_data_headers) data = json.loads(self.saved_data) + for key, value in data.items(): + if isinstance(value, string_types) and \ + (value.startswith(settings.MEDIA_URL) or \ + value.startswith('http://') or value.startswith('https://')): + + data[key] = '{value}'.format(value=value) + return two_dicts_to_string(headers, data) formatted_saved_data.allow_tags = True formatted_saved_data.short_description = _("Saved data") diff --git a/src/fobi/contrib/plugins/form_handlers/http_repost/fobi_form_handlers.py b/src/fobi/contrib/plugins/form_handlers/http_repost/fobi_form_handlers.py index 245bc7a6..f004e5ea 100644 --- a/src/fobi/contrib/plugins/form_handlers/http_repost/fobi_form_handlers.py +++ b/src/fobi/contrib/plugins/form_handlers/http_repost/fobi_form_handlers.py @@ -56,10 +56,7 @@ class HTTPRepostHandlerPlugin(FormHandlerPlugin): file_path = settings.PROJECT_DIR('../{0}'.format(file_path)) files[field_name] = (imf.name, open(file_path, 'rb')) - #logger.debug("ORIGINAL REQUEST FILES: ") - #logger.debug(request.FILES) for field_name, imf in request.FILES.items(): - #import ipdb; ipdb.set_trace() try: file_path = form.cleaned_data.get(field_name, '') process_path(file_path, imf) @@ -67,9 +64,6 @@ class HTTPRepostHandlerPlugin(FormHandlerPlugin): file_path = extract_file_path(imf.name) process_path(file_path, imf) - #logger.debug("FILES: ") - #logger.debug(files) - #import ipdb; ipdb.set_trace() response = requests.post(self.data.endpoint_url, \ data=request.POST, files=files) diff --git a/src/fobi/contrib/themes/foundation5/templates/foundation5/edit_form_entry_ajax.html b/src/fobi/contrib/themes/foundation5/templates/foundation5/edit_form_entry_ajax.html index 0855ca1e..6babb97f 100644 --- a/src/fobi/contrib/themes/foundation5/templates/foundation5/edit_form_entry_ajax.html +++ b/src/fobi/contrib/themes/foundation5/templates/foundation5/edit_form_entry_ajax.html @@ -99,6 +99,7 @@ {% for form_handler in form_handlers %} {% with form_handler.get_plugin as plugin %} + {% if plugin %} {{ form_handler.plugin_name }} {% if form_handler.plugin_data %} @@ -129,6 +130,7 @@ + {% endif %} {% endwith %} {% endfor %} diff --git a/src/fobi/contrib/themes/simple/templates/simple/edit_form_entry_ajax.html b/src/fobi/contrib/themes/simple/templates/simple/edit_form_entry_ajax.html index 5172be0e..e660cf18 100644 --- a/src/fobi/contrib/themes/simple/templates/simple/edit_form_entry_ajax.html +++ b/src/fobi/contrib/themes/simple/templates/simple/edit_form_entry_ajax.html @@ -1,4 +1,4 @@ -{% load i18n %} +{% load i18n fobi_tags %}

{% trans "Edit form" %}

@@ -107,15 +107,15 @@ {% for form_handler in form_handlers %} + {% with form_handler.get_plugin as plugin %} + {% if plugin %} {{ form_handler.plugin_name }} - {% if form_handler.plugin_data %} - {% with form_handler.get_plugin as plugin %} + {% if form_handler.plugin_data %} ? - {% endwith %} - {% endif %} + {% endif %} + {% endif %} + {% endwith %} {% endfor %} diff --git a/src/fobi/defaults.py b/src/fobi/defaults.py index 366b0e57..de8c6339 100644 --- a/src/fobi/defaults.py +++ b/src/fobi/defaults.py @@ -4,11 +4,17 @@ __copyright__ = 'Copyright (c) 2014 Artur Barseghyan' __license__ = 'GPL 2.0/LGPL 2.1' __all__ = ( 'RESTRICT_PLUGIN_ACCESS', 'FORM_ELEMENT_PLUGINS_MODULE_NAME', - 'FORM_HANDLER_PLUGINS_MODULE_NAME', 'FORM_CALLBACKS_MODULE_NAME', - 'THEMES_MODULE_NAME', 'DEFAULT_THEME', 'DISPLAY_AUTH_LINK', - 'WAIT_BETWEEN_TEST_STEPS', 'WAIT_AT_TEST_END', 'THEME_FOOTER_TEXT', - 'FORM_IMPORTER_PLUGINS_MODULE_NAME', 'CUSTOM_THEME_DATA', - 'DEBUG', + 'FORM_HANDLER_PLUGINS_MODULE_NAME', 'FORM_IMPORTER_PLUGINS_MODULE_NAME', + 'FORM_CALLBACKS_MODULE_NAME', 'THEMES_MODULE_NAME', 'DEFAULT_THEME', + 'DISPLAY_AUTH_LINK', 'DEBUG', + + 'CUSTOM_THEME_DATA', 'THEME_FOOTER_TEXT', + + 'DEFAULT_MAX_LENGTH', 'FORM_HANDLER_PLUGINS_EXECUTION_ORDER', + 'FAIL_ON_MISSING_FORM_ELEMENT_PLUGINS', + 'FAIL_ON_MISSING_FORM_HANDLER_PLUGINS' + + 'WAIT_BETWEEN_TEST_STEPS', 'WAIT_AT_TEST_END', ) from django.utils.translation import ugettext @@ -45,9 +51,6 @@ DEFAULT_THEME = 'bootstrap3' DISPLAY_AUTH_LINK = True -WAIT_BETWEEN_TEST_STEPS = 2 -WAIT_AT_TEST_END = 4 - DEBUG = False # ************************************************************** @@ -72,3 +75,15 @@ FORM_HANDLER_PLUGINS_EXECUTION_ORDER = ( # The 'db_store' is left out intentionally, since it should # be the last plugin to be executed. ) + +FAIL_ON_MISSING_FORM_ELEMENT_PLUGINS = True +FAIL_ON_MISSING_FORM_HANDLER_PLUGINS = True + +# ************************************************************** +# ************************************************************** +# ************************ Tests related *********************** +# ************************************************************** +# ************************************************************** + +WAIT_BETWEEN_TEST_STEPS = 2 +WAIT_AT_TEST_END = 4 diff --git a/src/fobi/dynamic.py b/src/fobi/dynamic.py index 9c6a3303..24651b4f 100644 --- a/src/fobi/dynamic.py +++ b/src/fobi/dynamic.py @@ -4,7 +4,7 @@ __copyright__ = 'Copyright (c) 2014 Artur Barseghyan' __license__ = 'GPL 2.0/LGPL 2.1' __all__ = ('assemble_form_class',) -import logging +#import logging from six import with_metaclass @@ -12,17 +12,17 @@ from django.utils.datastructures import SortedDict from django.forms.forms import BaseForm#, get_declared_fields from django.forms.widgets import media_property -logger = logging.getLogger(__file__) +#logger = logging.getLogger(__file__) -# ****************************************************************************** -# ****************************************************************************** -# **************************** Form generator ********************************** -# ****************************************************************************** -# ****************************************************************************** +# **************************************************************************** +# **************************************************************************** +# **************************** Form generator ******************************** +# **************************************************************************** +# **************************************************************************** def assemble_form_class(form_entry, base_class=BaseForm, request=None, \ - origin=None, origin_kwargs_update_func=None, origin_return_func=None, - form_element_entries=None): + origin=None, origin_kwargs_update_func=None, \ + origin_return_func=None, form_element_entries=None): """ Assembles a form class by given entry. @@ -32,8 +32,8 @@ def assemble_form_class(form_entry, base_class=BaseForm, request=None, \ :param string origin: :param callable origin_kwargs_update_func: :param callable origin_return_func: - :param iterable form_element_entries: If given, used instead of ``form_entry.formelemententry_set.all`` (no - additional database hit). + :param iterable form_element_entries: If given, used instead of + ``form_entry.formelemententry_set.all`` (no additional database hit). """ if form_element_entries is None: form_element_entries = form_entry.formelemententry_set.all() @@ -42,26 +42,37 @@ def assemble_form_class(form_entry, base_class=BaseForm, request=None, \ """ Copied from ``django.forms.forms.DeclarativeFieldsMetaclass``. - Metaclass that converts Field attributes to a dictionary called 'base_fields', taking into - account parent class 'base_fields' as well. + Metaclass that converts Field attributes to a dictionary called + `base_fields`, taking into account parent class 'base_fields' as well. """ def __new__(cls, name, bases, attrs): base_fields = [] + for creation_counter, form_element_entry in enumerate(form_element_entries): plugin = form_element_entry.get_plugin(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} - ) - for form_field_name, form_field_instance in plugin_form_field_instances: - base_fields.append((form_field_name, form_field_instance)) + + # We simply make sure the plugin exists. We don't handle + # exceptions relate to the non-existent plugins here. They + # are istead 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} + ) + 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) - new_class = super(DeclarativeFieldsMetaclass, cls).__new__(cls, name, bases, attrs) + new_class = super(DeclarativeFieldsMetaclass, cls).__new__( + cls, name, bases, attrs + ) + if 'media' not in attrs: new_class.media = media_property(new_class) + return new_class class DynamicForm(with_metaclass(DeclarativeFieldsMetaclass, base_class)): diff --git a/src/fobi/exceptions.py b/src/fobi/exceptions.py index 328441f0..0524a144 100644 --- a/src/fobi/exceptions.py +++ b/src/fobi/exceptions.py @@ -4,7 +4,9 @@ __copyright__ = 'Copyright (c) 2014 Artur Barseghyan' __license__ = 'GPL 2.0/LGPL 2.1' __all__ = ( 'BaseException', 'ImproperlyConfigured', 'InvalidRegistryItemType', - 'DoesNotExist', 'ThemeDoesNotExist', 'NoDefaultThemeSet', + 'DoesNotExist', 'ThemeDoesNotExist', 'PluginDoesNotExist', + 'FormElementPluginDoesNotExist', 'FormHandlerPluginDoesNotExist', + 'NoDefaultThemeSet', ) class BaseException(Exception): @@ -37,6 +39,23 @@ class ThemeDoesNotExist(DoesNotExist): """ +class PluginDoesNotExist(DoesNotExist): + """ + Raised when no plugin with given uid can be found. + """ + + +class FormElementPluginDoesNotExist(PluginDoesNotExist): + """ + Raised when no form element plugin with given uid can be found. + """ + + +class FormHandlerPluginDoesNotExist(PluginDoesNotExist): + """ + Raised when no form handler plugin with given uid can be found. + """ + class NoDefaultThemeSet(ImproperlyConfigured): """ Raised when no active theme is chosen. diff --git a/src/fobi/models.py b/src/fobi/models.py index 2ea5bd4d..19c799bb 100644 --- a/src/fobi/models.py +++ b/src/fobi/models.py @@ -395,6 +395,11 @@ class AbstractPluginEntry(models.Model): if not cls: # No need to log here, since already logged in registry. + if registry.fail_on_missing_plugin: + err_msg = registry.plugin_not_found_error_message.format( + self.plugin_uid, registry.__class__ + ) + raise registry.plugin_not_found_exception_cls(err_msg) return None # Creating plugin instance. diff --git a/src/fobi/settings.py b/src/fobi/settings.py index 49ac9dc5..3c644c8a 100644 --- a/src/fobi/settings.py +++ b/src/fobi/settings.py @@ -15,12 +15,18 @@ __author__ = 'Artur Barseghyan ' __copyright__ = 'Copyright (c) 2014 Artur Barseghyan' __license__ = 'GPL 2.0/LGPL 2.1' __all__ = ( - 'RESTRICT_PLUGIN_ACCESS', 'FORM_ELEMENT_PLUGINS_MODULE_NAME', - 'FORM_HANDLER_PLUGINS_MODULE_NAME', 'FORM_CALLBACKS_MODULE_NAME', - 'THEMES_MODULE_NAME', 'DEFAULT_THEME', 'DISPLAY_AUTH_LINK', - 'WAIT_BETWEEN_TEST_STEPS', 'WAIT_AT_TEST_END', 'THEME_FOOTER_TEXT', - 'FORM_HANDLER_PLUGINS_EXECUTION_ORDER', 'CUSTOM_THEME_DATA', - 'FORM_IMPORTER_PLUGINS_MODULE_NAME', 'DEBUG', + 'RESTRICT_PLUGIN_ACCESS', 'FORM_ELEMENT_PLUGINS_MODULE_NAME', + 'FORM_HANDLER_PLUGINS_MODULE_NAME', 'FORM_IMPORTER_PLUGINS_MODULE_NAME', + 'FORM_CALLBACKS_MODULE_NAME', 'THEMES_MODULE_NAME', 'DEFAULT_THEME', + 'DISPLAY_AUTH_LINK', 'DEBUG', + + 'CUSTOM_THEME_DATA', 'THEME_FOOTER_TEXT', + + 'DEFAULT_MAX_LENGTH', 'FORM_HANDLER_PLUGINS_EXECUTION_ORDER', + 'FAIL_ON_MISSING_FORM_ELEMENT_PLUGINS', + 'FAIL_ON_MISSING_FORM_HANDLER_PLUGINS' + + 'WAIT_BETWEEN_TEST_STEPS', 'WAIT_AT_TEST_END', ) from fobi.conf import get_setting @@ -31,14 +37,16 @@ from fobi.exceptions import NoDefaultThemeSet # *************************** Core ***************************** # ************************************************************** # ************************************************************** - RESTRICT_PLUGIN_ACCESS = get_setting('RESTRICT_PLUGIN_ACCESS') -FORM_ELEMENT_PLUGINS_MODULE_NAME = get_setting('FORM_ELEMENT_PLUGINS_MODULE_NAME') +FORM_ELEMENT_PLUGINS_MODULE_NAME = \ + get_setting('FORM_ELEMENT_PLUGINS_MODULE_NAME') -FORM_HANDLER_PLUGINS_MODULE_NAME = get_setting('FORM_HANDLER_PLUGINS_MODULE_NAME') +FORM_HANDLER_PLUGINS_MODULE_NAME = \ + get_setting('FORM_HANDLER_PLUGINS_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') FORM_CALLBACKS_MODULE_NAME = get_setting('FORM_CALLBACKS_MODULE_NAME') @@ -51,9 +59,6 @@ DISPLAY_AUTH_LINK = get_setting('DISPLAY_AUTH_LINK') if not DEFAULT_THEME: raise NoDefaultThemeSet("No default theme set!") -WAIT_BETWEEN_TEST_STEPS = get_setting('WAIT_BETWEEN_TEST_STEPS') -WAIT_AT_TEST_END = get_setting('WAIT_AT_TEST_END') - DEBUG = get_setting('DEBUG') # ************************************************************** @@ -69,7 +74,21 @@ THEME_FOOTER_TEXT = get_setting('THEME_FOOTER_TEXT') # *********************** Plugin related *********************** # ************************************************************** # ************************************************************** - DEFAULT_MAX_LENGTH = get_setting('DEFAULT_MAX_LENGTH') -FORM_HANDLER_PLUGINS_EXECUTION_ORDER = get_setting('FORM_HANDLER_PLUGINS_EXECUTION_ORDER') +FORM_HANDLER_PLUGINS_EXECUTION_ORDER = \ + get_setting('FORM_HANDLER_PLUGINS_EXECUTION_ORDER') + +FAIL_ON_MISSING_FORM_ELEMENT_PLUGINS = \ + get_setting('FAIL_ON_MISSING_FORM_ELEMENT_PLUGINS') + +FAIL_ON_MISSING_FORM_HANDLER_PLUGINS = \ + get_setting('FAIL_ON_MISSING_FORM_HANDLER_PLUGINS') + +# ************************************************************** +# ************************************************************** +# ************************ Tests related *********************** +# ************************************************************** +# ************************************************************** +WAIT_BETWEEN_TEST_STEPS = get_setting('WAIT_BETWEEN_TEST_STEPS') +WAIT_AT_TEST_END = get_setting('WAIT_AT_TEST_END') diff --git a/src/fobi/templates/fobi/generic/edit_form_entry_ajax.html b/src/fobi/templates/fobi/generic/edit_form_entry_ajax.html index c2ceecd4..1620f79a 100644 --- a/src/fobi/templates/fobi/generic/edit_form_entry_ajax.html +++ b/src/fobi/templates/fobi/generic/edit_form_entry_ajax.html @@ -103,6 +103,7 @@ {% for form_handler in form_handlers %} {% with form_handler.get_plugin as plugin %} + {% if plugin %} {{ form_handler.plugin_name }} {% if form_handler.plugin_data or plugin.plugin_data_repr %} @@ -125,6 +126,7 @@ + {% endif %} {% endwith %} {% endfor %} diff --git a/src/fobi/views.py b/src/fobi/views.py index 966e659e..c21bbe18 100644 --- a/src/fobi/views.py +++ b/src/fobi/views.py @@ -10,6 +10,8 @@ __all__ = ( 'dashboard', 'view_form_entry', 'form_entry_submitted', ) +#import logging + from django.template import RequestContext from django.shortcuts import render_to_response, redirect from django.core.exceptions import ObjectDoesNotExist @@ -43,6 +45,7 @@ from fobi.utils import ( append_edit_and_delete_links_to_field ) +#logger = logging.getLogger(__name__) # ***************************************************************************** # *****************************************************************************