From 30589c66626e9c0aebea7503ff36d27b35a177ea Mon Sep 17 00:00:00 2001 From: philomat Date: Tue, 19 Jul 2011 16:07:24 +0200 Subject: [PATCH] Using theirs --- .gitignore | 5 + .gitmodules | 3 - AUTHORS | 2 + MANIFEST.in | 15 + README.md | 16 +- form_designer/__init__.py | 7 + form_designer/admin.py | 185 +++++--- form_designer/admin_urls.py | 7 - form_designer/admin_views.py | 62 --- form_designer/app_settings.py | 8 - form_designer/cms_plugins.py | 13 +- form_designer/defaults.py | 55 --- .../{model_name_field.py => fields.py} | 34 +- form_designer/forms.py | 58 +++ form_designer/locale/de/LC_MESSAGES/django.mo | Bin 5369 -> 5806 bytes form_designer/locale/de/LC_MESSAGES/django.po | 338 ++++++++------- form_designer/locale/en/LC_MESSAGES/django.po | 404 ++++++++++++++++++ form_designer/locale/nl/LC_MESSAGES/django.mo | Bin 0 -> 6310 bytes form_designer/locale/nl/LC_MESSAGES/django.po | 309 ++++++++++++++ .../js/jquery-inline-collapsible.js | 50 +++ .../js/jquery-inline-fieldset-collapsible.js | 5 + .../js/jquery-inline-positioning.js | 45 ++ .../js/jquery-inline-prepopulate-label.js | 29 ++ .../form_designer/js/jquery-inline-rename.js | 44 ++ .../form_designer/js/{lib => }/jquery-ui.js | 0 .../form_designer/js/jquery-url-param.js | 17 + .../form_designer/js/{lib => }/jquery.js | 0 .../js/lib/django-admin-tweaks-js-lib | 1 - .../lib/django-admin-tweaks-js-lib/README.md | 4 + .../js/jquery-inline-collapsible.js | 50 +++ .../js/jquery-inline-fieldset-collapsible.js | 5 + .../js/jquery-inline-positioning.js | 45 ++ .../js/jquery-inline-prepopulate-label.js | 29 ++ .../js/jquery-inline-rename.js | 44 ++ .../js/jquery-url-param.js | 17 + form_designer/migrations/0001_initial.py | 175 ++++++++ ...__chg_field_formdefinitionfield_initial.py | 104 +++++ form_designer/migrations/__init__.py | 0 form_designer/models.py | 100 +++-- form_designer/pickled_object_field.py | 25 -- form_designer/settings.py | 65 +++ form_designer/template_field.py | 34 -- .../form_designer/formlog/change_list.html | 7 + .../html/formdefinition/forms/as_p.html | 12 + .../html/formdefinition/forms/as_table.html | 12 + .../html/formdefinition/forms/as_u.html | 12 + .../html/formdefinition/forms/as_ul.html | 12 + .../html/formdefinition/forms/custom.html | 42 ++ .../txt/formdefinition/data_message.txt | 3 +- form_designer/templatetags/friendly.py | 5 +- form_designer/templatetags/widget_type.py | 6 + form_designer/tests.py | 23 - form_designer/urls.py | 2 +- form_designer/views.py | 84 ++-- setup.py | 44 ++ 55 files changed, 2164 insertions(+), 509 deletions(-) create mode 100644 .gitignore delete mode 100644 .gitmodules create mode 100644 AUTHORS create mode 100644 MANIFEST.in delete mode 100644 form_designer/admin_urls.py delete mode 100644 form_designer/admin_views.py delete mode 100644 form_designer/app_settings.py delete mode 100644 form_designer/defaults.py rename form_designer/{model_name_field.py => fields.py} (51%) create mode 100644 form_designer/forms.py create mode 100644 form_designer/locale/en/LC_MESSAGES/django.po create mode 100644 form_designer/locale/nl/LC_MESSAGES/django.mo create mode 100644 form_designer/locale/nl/LC_MESSAGES/django.po create mode 100644 form_designer/media/form_designer/js/jquery-inline-collapsible.js create mode 100644 form_designer/media/form_designer/js/jquery-inline-fieldset-collapsible.js create mode 100644 form_designer/media/form_designer/js/jquery-inline-positioning.js create mode 100644 form_designer/media/form_designer/js/jquery-inline-prepopulate-label.js create mode 100644 form_designer/media/form_designer/js/jquery-inline-rename.js rename form_designer/media/form_designer/js/{lib => }/jquery-ui.js (100%) create mode 100644 form_designer/media/form_designer/js/jquery-url-param.js rename form_designer/media/form_designer/js/{lib => }/jquery.js (100%) delete mode 160000 form_designer/media/form_designer/js/lib/django-admin-tweaks-js-lib create mode 100644 form_designer/media/form_designer/js/lib/django-admin-tweaks-js-lib/README.md create mode 100644 form_designer/media/form_designer/js/lib/django-admin-tweaks-js-lib/js/jquery-inline-collapsible.js create mode 100644 form_designer/media/form_designer/js/lib/django-admin-tweaks-js-lib/js/jquery-inline-fieldset-collapsible.js create mode 100644 form_designer/media/form_designer/js/lib/django-admin-tweaks-js-lib/js/jquery-inline-positioning.js create mode 100644 form_designer/media/form_designer/js/lib/django-admin-tweaks-js-lib/js/jquery-inline-prepopulate-label.js create mode 100644 form_designer/media/form_designer/js/lib/django-admin-tweaks-js-lib/js/jquery-inline-rename.js create mode 100644 form_designer/media/form_designer/js/lib/django-admin-tweaks-js-lib/js/jquery-url-param.js create mode 100644 form_designer/migrations/0001_initial.py create mode 100644 form_designer/migrations/0002_auto__chg_field_formdefinitionfield_initial.py create mode 100644 form_designer/migrations/__init__.py delete mode 100644 form_designer/pickled_object_field.py create mode 100644 form_designer/settings.py delete mode 100644 form_designer/template_field.py create mode 100644 form_designer/templates/html/formdefinition/forms/as_p.html create mode 100644 form_designer/templates/html/formdefinition/forms/as_table.html create mode 100644 form_designer/templates/html/formdefinition/forms/as_u.html create mode 100644 form_designer/templates/html/formdefinition/forms/as_ul.html create mode 100644 form_designer/templates/html/formdefinition/forms/custom.html create mode 100644 form_designer/templatetags/widget_type.py delete mode 100644 form_designer/tests.py create mode 100644 setup.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..d76d3cfa --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.pyc +.* +dist +MANIFEST + diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index b72179b6..00000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "form_designer/media/form_designer/js/lib/django-admin-tweaks-js-lib"] - path = form_designer/media/form_designer/js/lib/django-admin-tweaks-js-lib - url = git://github.com/philomat/django-admin-tweaks-js-lib.git diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 00000000..98e5b938 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,2 @@ +Samuel Lüscher (philomat) +Jannis Leidel (jezdez) diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 00000000..1d99ea08 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,15 @@ +<<<<<<< HEAD +include LICENSE +include README.md + +recursive-include form_designer/locale * +recursive-include form_designer/media * +recursive-include form_designer/templates * +======= +include AUTHORS +include README.md +include LICENSE +recursive-include form_designer/templates *.html *.txt +recursive-include form_designer/media *.js +recursive-include form_designer/locale *.po *.mo +>>>>>>> 29a8207f09ef53dba714edf377d444702106ad5c diff --git a/README.md b/README.md index 4cee4a27..c9320546 100644 --- a/README.md +++ b/README.md @@ -29,18 +29,19 @@ This document assumes that you are familiar with Python and Django. 2. Make sure `form_designer` is on your `PYTHONPATH`. 3. Make the directory `form_designer/media/form_designer` available under your `MEDIA_ROOT`. -4. Set up the database tables using - - $ manage.py syncdb -5. Add `form_designer` to your `INSTALLED_APPS` setting. +4. Add `form_designer` to your `INSTALLED_APPS` setting. INSTALLED_APPS = ( ... 'form_designer', ) -6. Add the form_designer URLs to your URL conf. For instance, in order to make a form named `example-form` available under `http://domain.com/forms/example-form`, add the following line to `urls.py`. Note: __If you are using the form_designer plugin for Django CMS, step 5 is not necessary__: +5. Set up the database tables using + + $ manage.py syncdb + +6. Add the form_designer URLs to your URL conf. For instance, in order to make a form named `example-form` available under `http://domain.com/forms/example-form`, add the following line to `urls.py`. Note: __If you are using the form_designer plugin for Django CMS, this step is not necessary__: urlpatterns = patterns('', (r'^forms/', include('form_designer.urls')), @@ -62,8 +63,3 @@ Optional requirements * The form_designer admin interface requires jQuery and the jQuery UI Sortable plugin to make building forms a lot more user-friendly. The two Javascript files are bundled with form_designer. Optionally, if Django CMS is installed, the files bundled with that app will be used. If you want to use you own jquery.js instead because you're already including it anyway, define JQUERY_JS in your settings file. For instance: JQUERY_JS = 'jquery/jquery-latest.js' - -Missing features ----------------- - -* File upload fields should be implemented diff --git a/form_designer/__init__.py b/form_designer/__init__.py index e69de29b..0e6c49cb 100644 --- a/form_designer/__init__.py +++ b/form_designer/__init__.py @@ -0,0 +1,7 @@ +from south.modelsinspector import add_introspection_rules + + +add_introspection_rules([], ['^form_designer\.pickled_object_field\.PickledObjectField']) +add_introspection_rules([], ['^form_designer\.model_name_field\.ModelNameField']) +add_introspection_rules([], ['^form_designer\.template_field\.TemplateCharField']) +add_introspection_rules([], ['^form_designer\.template_field\.TemplateTextField']) diff --git a/form_designer/admin.py b/form_designer/admin.py index ff13860b..77b6b34c 100644 --- a/form_designer/admin.py +++ b/form_designer/admin.py @@ -1,21 +1,23 @@ +import csv from django.contrib import admin +from django.utils.translation import ugettext as _, ugettext_lazy +from django.conf.urls.defaults import patterns, url +from django.contrib.admin.views.main import ChangeList +from django.db.models import Count +from django.http import HttpResponse +from django.utils.encoding import smart_unicode, smart_str + +try: + import xlwt +except ImportError: + xlwt_installed = False +else: + xlwt_installed = True + +from form_designer.forms import FormDefinitionForm, FormDefinitionFieldInlineForm from form_designer.models import FormDefinition, FormDefinitionField, FormLog -from django import forms -from django.utils.translation import ugettext as _ -from django.db import models -from django.conf import settings -import os - -MEDIA_SUBDIR = 'form_designer' - -class FormDefinitionFieldInlineForm(forms.ModelForm): - class Meta: - model = FormDefinitionField - - def clean_choice_model(self): - if not self.cleaned_data['choice_model'] and self.cleaned_data.has_key('field_class') and self.cleaned_data['field_class'] in ('forms.ModelChoiceField', 'forms.ModelMultipleChoiceField'): - raise forms.ValidationError(_('This field class requires a model.')) - return self.cleaned_data['choice_model'] +from form_designer import settings +from form_designer.templatetags.friendly import friendly class FormDefinitionFieldInline(admin.StackedInline): form = FormDefinitionFieldInlineForm @@ -31,34 +33,6 @@ class FormDefinitionFieldInline(admin.StackedInline): (_('Model Choices'), {'fields': ['choice_model', 'choice_model_empty_label']}), ] -class FormDefinitionForm(forms.ModelForm): - class Meta: - model = FormDefinition - class Media: - js = ([ - # Use central jQuery - settings.JQUERY_JS, - # and use jQuery UI bundled with this app - os.path.join(MEDIA_SUBDIR, 'lib/jquery/ui.core.js'), - os.path.join(MEDIA_SUBDIR, 'lib/jquery/ui.sortable.js'), - ] if hasattr(settings, 'JQUERY_JS') else [ - # Use jQuery bundled with CMS - os.path.join(settings.CMS_MEDIA_URL, 'js/lib/jquery.js'), - os.path.join(settings.CMS_MEDIA_URL, 'js/lib/ui.core.js'), - os.path.join(settings.CMS_MEDIA_URL, 'js/lib/ui.sortable.js'), - ] if hasattr(settings, 'CMS_MEDIA_URL') else [ - # or use jQuery bundled with this app - os.path.join(MEDIA_SUBDIR, 'lib/jquery/jquery.js'), - os.path.join(MEDIA_SUBDIR, 'lib/jquery/ui.core.js'), - os.path.join(MEDIA_SUBDIR, 'lib/jquery/ui.sortable.js'), - ])+[os.path.join(MEDIA_SUBDIR, 'js/lib/django-admin-tweaks-js-lib/js', basename) for basename in ( - 'jquery-inline-positioning.js', - 'jquery-inline-rename.js', - 'jquery-inline-collapsible.js', - 'jquery-inline-fieldset-collapsible.js', - 'jquery-inline-prepopulate-label.js', - )] - class FormDefinitionAdmin(admin.ModelAdmin): fieldsets = [ (_('Basic'), {'fields': ['name', 'method', 'action', 'title', 'allow_get_initial', 'log_data', 'success_redirect', 'success_clear']}), @@ -76,7 +50,10 @@ class FormLogAdmin(admin.ModelAdmin): list_display = ('form_no_link', 'created', 'id', 'data_html') list_filter = ('form_definition',) list_display_links = () - + actions = ['export_csv'] + if xlwt_installed: + actions.append('export_xls') + # Disabling all edit links: Hack as found at http://stackoverflow.com/questions/1618728/disable-link-to-edit-object-in-djangos-admin-display-list-only def form_no_link(self, obj): return ''+obj.form_definition.__unicode__()+'' @@ -84,25 +61,133 @@ def form_no_link(self, obj): form_no_link.allow_tags = True form_no_link.short_description = _('Form') + def get_urls(self): + urls = patterns('', + url(r'^export/csv/$', self.admin_site.admin_view(self.export_csv), name='form_designer_export_csv'), + ) + if xlwt_installed: + urls += patterns('', + url(r'^export/xls/$', self.admin_site.admin_view(self.export_xls), name='form_designer_export_xls'), + ) + return urls + super(FormLogAdmin, self).get_urls() + def data_html(self, obj): return obj.form_definition.compile_message(obj.data, 'html/formdefinition/data_message.html') data_html.allow_tags = True data_html.short_description = _('Data') + def get_change_list_query_set(self, request): + cl = ChangeList(request, self.model, self.list_display, self.list_display_links, self.list_filter, + self.date_hierarchy, self.search_fields, self.list_select_related, self.list_per_page, self.list_editable, self) + return cl.get_query_set() + + def export_csv(self, request, queryset=None): + response = HttpResponse(mimetype='text/csv') + response['Content-Disposition'] = 'attachment; filename=' + settings.CSV_EXPORT_FILENAME + writer = csv.writer(response, delimiter=settings.CSV_EXPORT_DELIMITER) + if queryset is None: + queryset = self.get_change_list_query_set(request) + + distinct_forms = queryset.aggregate(Count('form_definition', distinct=True))['form_definition__count'] + + include_created = settings.CSV_EXPORT_INCLUDE_CREATED + include_pk = settings.CSV_EXPORT_INCLUDE_PK + include_header = settings.CSV_EXPORT_INCLUDE_HEADER and distinct_forms == 1 + include_form = settings.CSV_EXPORT_INCLUDE_FORM and distinct_forms > 1 + + if include_header: + header = [] + if include_form: + header.append(_('Form')) + if include_created: + header.append(_('Created')) + if include_pk: + header.append(_('ID')) + for field in queryset[0].data: + header.append(field['label'] if field['label'] else field['key']) + writer.writerow([smart_str(cell, encoding=settings.CSV_EXPORT_ENCODING) for cell in header]) + + for entry in queryset: + row = [] + if include_form: + row.append(entry.form_definition) + if include_created: + row.append(entry.created) + if include_pk: + row.append(entry.pk) + for field in entry.data: + value = friendly(field['value']) + row.append(smart_str( + value, encoding=settings.CSV_EXPORT_ENCODING)) + writer.writerow(row) + return response + export_csv.short_description = ugettext_lazy("Export selected %(verbose_name_plural)s as CSV") + + def export_xls(self, request, queryset=None): + import xlwt + + response = HttpResponse(mimetype='application/ms-excel') + response['Content-Disposition'] = 'attachment; filename=%s.xls' % unicode(self.model._meta.verbose_name_plural) + wb = xlwt.Workbook() + ws = wb.add_sheet(unicode(self.model._meta.verbose_name_plural)) + if queryset is None: + queryset = self.get_change_list_query_set(request) + + distinct_forms = queryset.aggregate(Count('form_definition', distinct=True))['form_definition__count'] + + include_created = settings.CSV_EXPORT_INCLUDE_CREATED + include_pk = settings.CSV_EXPORT_INCLUDE_PK + include_header = settings.CSV_EXPORT_INCLUDE_HEADER and distinct_forms == 1 + include_form = settings.CSV_EXPORT_INCLUDE_FORM and distinct_forms > 1 + + if include_header: + header = [] + if include_form: + header.append(_('Form')) + if include_created: + header.append(_('Created')) + if include_pk: + header.append(_('ID')) + for field in queryset[0].data: + header.append(field['label'] if field['label'] else field['key']) + for i, f in enumerate(header): + ws.write(0, i, smart_unicode(f, encoding=settings.CSV_EXPORT_ENCODING)) + + for i, entry in enumerate(queryset): + row = [] + if include_form: + row.append(entry.form_definition) + if include_created: + row.append(entry.created) + if include_pk: + row.append(entry.pk) + for field in entry.data: + value = friendly(field['value']) + row.append(smart_unicode( + value, encoding=settings.CSV_EXPORT_ENCODING)) + for j, cell in enumerate(row): + ws.write(i+1, j, smart_unicode(cell)) + wb.save(response) + return response + export_xls.short_description = ugettext_lazy("Export selected %(verbose_name_plural)s as XLS") + def changelist_view(self, request, extra_context=None): - from django.core.urlresolvers import reverse, NoReverseMatch + from django.core.urlresolvers import reverse, NoReverseMatch extra_context = extra_context or {} try: query_string = '?'+request.META['QUERY_STRING'] - except TypeError, KeyError: + except (TypeError, KeyError): query_string = '' try: - extra_context['export_csv_url'] = reverse('form_designer_export_csv')+query_string + extra_context['export_csv_url'] = reverse('admin:form_designer_export_csv')+query_string except NoReverseMatch: request.user.message_set.create(message=_('CSV export is not enabled.')) - + if xlwt_installed: + try: + extra_context['export_xls_url'] = reverse('admin:form_designer_export_xls')+query_string + except NoReverseMatch: + request.user.message_set.create(message=_('XLS export is not enabled.')) return super(FormLogAdmin, self).changelist_view(request, extra_context) admin.site.register(FormDefinition, FormDefinitionAdmin) admin.site.register(FormLog, FormLogAdmin) - diff --git a/form_designer/admin_urls.py b/form_designer/admin_urls.py deleted file mode 100644 index 59173260..00000000 --- a/form_designer/admin_urls.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.conf.urls.defaults import * - -urlpatterns = patterns('', - - url(r'^formlog/export_csv/$', 'form_designer.admin_views.export_csv', name='form_designer_export_csv'), - -) diff --git a/form_designer/admin_views.py b/form_designer/admin_views.py deleted file mode 100644 index 923ead33..00000000 --- a/form_designer/admin_views.py +++ /dev/null @@ -1,62 +0,0 @@ -# encoding=utf-8 -from django.http import HttpResponse -from form_designer.models import FormLog -from form_designer.admin import FormLogAdmin -from form_designer import app_settings -from django.utils.translation import ugettext as _ -from form_designer.templatetags.friendly import friendly -from django.conf import settings -import csv - -# Returns a QuerySet with the same ordering and filtering like the one that would be visible in Django admin -def get_change_list_query_set(model_admin, model, request): - from django.contrib import admin - from django.contrib.admin.views.main import ChangeList - a = model_admin(model, admin.site) - cl = ChangeList(request, a.model, a.list_display, a.list_display_links, a.list_filter, - a.date_hierarchy, a.search_fields, a.list_select_related, a.list_per_page, a.list_editable, a) - return cl.get_query_set() - -def export_csv(request): - response = HttpResponse(mimetype='text/csv') - response['Content-Disposition'] = 'attachment; filename='+app_settings.get('FORM_DESIGNER_CSV_EXPORT_FILENAME') - writer = csv.writer(response, delimiter=app_settings.get('FORM_DESIGNER_CSV_EXPORT_DELIMITER')) - qs = get_change_list_query_set(FormLogAdmin, FormLog, request) - - from django.db.models import Count - distinct_forms = qs.aggregate(Count('form_definition', distinct=True))['form_definition__count'] - - include_created = app_settings.get('FORM_DESIGNER_CSV_EXPORT_INCLUDE_CREATED') - include_pk = app_settings.get('FORM_DESIGNER_CSV_EXPORT_INCLUDE_PK') - include_header = app_settings.get('FORM_DESIGNER_CSV_EXPORT_INCLUDE_HEADER') and distinct_forms == 1 - include_form = app_settings.get('FORM_DESIGNER_CSV_EXPORT_INCLUDE_FORM') and distinct_forms > 1 - - if include_header: - header = [] - if include_form: - header.append(_('Form')) - if include_created: - header.append(_('Created')) - if include_pk: - header.append(_('ID')) - for field in qs.all()[0].data: - header.append(field['label'] if field['label'] else field['key']) - writer.writerow(header) - - for entry in qs: - row = [] - if include_form: - row.append(entry.form_definition) - if include_created: - row.append(entry.created) - if include_pk: - row.append(entry.pk) - for field in entry.data: - value = friendly(field['value']) - if not isinstance(value, basestring): - value = unicode(value) - value = value.encode(settings.DEFAULT_CHARSET) - row.append(value) - writer.writerow(row) - - return response diff --git a/form_designer/app_settings.py b/form_designer/app_settings.py deleted file mode 100644 index 6e91f414..00000000 --- a/form_designer/app_settings.py +++ /dev/null @@ -1,8 +0,0 @@ -from django.conf import settings -from form_designer import defaults - -def get(key): - if hasattr(settings, key): - return getattr(settings, key) - else: - return getattr(defaults, key) diff --git a/form_designer/cms_plugins.py b/form_designer/cms_plugins.py index 3486bde1..0012a256 100644 --- a/form_designer/cms_plugins.py +++ b/form_designer/cms_plugins.py @@ -1,9 +1,11 @@ +from django.utils.translation import ugettext as _ + from cms.plugin_base import CMSPluginBase from cms.plugin_pool import plugin_pool + from form_designer.models import CMSFormDefinition -from django.utils.translation import ugettext as _ -from views import process_form -from form_designer import app_settings +from form_designer.views import process_form +from form_designer import settings class FormDesignerPlugin(CMSPluginBase): model = CMSFormDefinition @@ -14,8 +16,7 @@ def render(self, context, instance, placeholder): if instance.form_definition.form_template_name: self.render_template = instance.form_definition.form_template_name else: - self.render_template = app_settings.get('FORM_DESIGNER_DEFAULT_FORM_TEMPLATE') - context.update(process_form(context['request'], instance.form_definition, is_cms_plugin=True)) - return context + self.render_template = settings.DEFAULT_FORM_TEMPLATE + return process_form(context['request'], instance.form_definition, context, is_cms_plugin=True) plugin_pool.register_plugin(FormDesignerPlugin) diff --git a/form_designer/defaults.py b/form_designer/defaults.py deleted file mode 100644 index 9b164228..00000000 --- a/form_designer/defaults.py +++ /dev/null @@ -1,55 +0,0 @@ -from django.utils.translation import ugettext_lazy as _ - -FORM_DESIGNER_FIELD_CLASSES = ( - ('forms.CharField', _('Text')), - ('forms.EmailField', _('E-mail address')), - ('forms.URLField', _('Web address')), - ('forms.IntegerField', _('Number')), - ('forms.DecimalField', _('Decimal number')), - ('forms.BooleanField', _('Yes/No')), - ('forms.DateField', _('Date')), - ('forms.DateTimeField', _('Date & time')), - ('forms.TimeField', _('Time')), - ('forms.ChoiceField', _('Choice')), - ('forms.MultipleChoiceField', _('Multiple Choice')), - ('forms.ModelChoiceField', _('Model Choice')), - ('forms.ModelMultipleChoiceField', _('Model Multiple Choice')), - ('forms.RegexField', _('Regex')), -) - -FORM_DESIGNER_WIDGET_CLASSES = ( - ('', _('Default')), - ('widgets.Textarea', _('Text area')), - ('widgets.PasswordInput', _('Password input')), - ('widgets.HiddenInput', _('Hidden input')), -) - -FORM_DESIGNER_FORM_TEMPLATES = ( - ('', _('Default')), - ('html/formdefinition/forms/as_p.html', _('as paragraphs')), - ('html/formdefinition/forms/as_table.html', _('as table')), -) - -# Sequence of two-tuples like (('your_app.models.ModelName', 'My Model'), ...) for limiting the models available to ModelChoiceField and ModelMultipleChoiceField. -# If None, any model can be chosen by entering it as a string -FORM_DESIGNER_CHOICE_MODEL_CHOICES = None - -FORM_DESIGNER_DEFAULT_FORM_TEMPLATE = 'html/formdefinition/forms/default.html' - -# semicolon is Microsoft Excel default -FORM_DESIGNER_CSV_EXPORT_DELIMITER = ';' - -# include log timestamp in export -FORM_DESIGNER_CSV_EXPORT_INCLUDE_CREATED = True - -FORM_DESIGNER_CSV_EXPORT_INCLUDE_PK = True - -# include field labels/names in first row if exporting logs for one form only -FORM_DESIGNER_CSV_EXPORT_INCLUDE_HEADER = True - -# include form title if exporting logs for more than one form -FORM_DESIGNER_CSV_EXPORT_INCLUDE_FORM = True - -FORM_DESIGNER_CSV_EXPORT_FILENAME = 'export.csv' - -FORM_DESIGNER_SUBMIT_FLAG_NAME = 'submit__%s' \ No newline at end of file diff --git a/form_designer/model_name_field.py b/form_designer/fields.py similarity index 51% rename from form_designer/model_name_field.py rename to form_designer/fields.py index 81f32672..e7349772 100644 --- a/form_designer/model_name_field.py +++ b/form_designer/fields.py @@ -28,10 +28,42 @@ class ModelNameField(models.CharField): @staticmethod def get_model_from_string(model_path): return ModelNameFormField.get_model_from_string(model_path) - + def formfield(self, **kwargs): # This is a fairly standard way to set up some defaults # while letting the caller override them. defaults = {'form_class': ModelNameFormField} defaults.update(kwargs) return super(ModelNameField, self).formfield(**defaults) + +class TemplateFormField(forms.CharField): + + def clean(self, value): + """ + Validates that the input can be compiled as a template. + """ + value = super(TemplateFormField, self).clean(value) + from django.template import Template, TemplateSyntaxError + try: + Template(value) + except TemplateSyntaxError as error: + raise forms.ValidationError(error) + return value + +class TemplateCharField(models.CharField): + + def formfield(self, **kwargs): + # This is a fairly standard way to set up some defaults + # while letting the caller override them. + defaults = {'form_class': TemplateFormField} + defaults.update(kwargs) + return super(TemplateCharField, self).formfield(**defaults) + +class TemplateTextField(models.TextField): + + def formfield(self, **kwargs): + # This is a fairly standard way to set up some defaults + # while letting the caller override them. + defaults = {'form_class': TemplateFormField} + defaults.update(kwargs) + return super(TemplateTextField, self).formfield(**defaults) diff --git a/form_designer/forms.py b/form_designer/forms.py new file mode 100644 index 00000000..6029032f --- /dev/null +++ b/form_designer/forms.py @@ -0,0 +1,58 @@ +import os + +from django import forms +from django.forms import widgets +from django.conf import settings as django_settings +from django.utils.translation import ugettext as _ + +from form_designer import settings +from form_designer.models import get_class, FormDefinitionField, FormDefinition + +class DesignedForm(forms.Form): + def __init__(self, form_definition, initial_data=None, *args, **kwargs): + super(DesignedForm, self).__init__(*args, **kwargs) + for def_field in form_definition.formdefinitionfield_set.all(): + self.add_defined_field(def_field, initial_data) + self.fields[form_definition.submit_flag_name] = forms.BooleanField(required=False, initial=1, widget=widgets.HiddenInput) + + def add_defined_field(self, def_field, initial_data=None): + if initial_data and initial_data.has_key(def_field.name): + if not def_field.field_class in ('django.forms.MultipleChoiceField', 'django.forms.ModelMultipleChoiceField'): + def_field.initial = initial_data.get(def_field.name) + else: + def_field.initial = initial_data.getlist(def_field.name) + self.fields[def_field.name] = get_class(def_field.field_class)(**def_field.get_form_field_init_args()) + + +class FormDefinitionFieldInlineForm(forms.ModelForm): + class Meta: + model = FormDefinitionField + + def clean_choice_model(self): + if not self.cleaned_data['choice_model'] and self.cleaned_data.has_key('field_class') and self.cleaned_data['field_class'] in ('django.forms.ModelChoiceField', 'django.forms.ModelMultipleChoiceField'): + raise forms.ValidationError(_('This field class requires a model.')) + return self.cleaned_data['choice_model'] + + +class FormDefinitionForm(forms.ModelForm): + class Meta: + model = FormDefinition + + def _media(self): + js = [] + if hasattr(django_settings, 'CMS_MEDIA_URL'): + # Use jQuery bundled with django_cms if installed + js.append(os.path.join(django_settings.CMS_MEDIA_URL, 'js/lib/jquery.js')) + elif hasattr(django_settings, 'JQUERY_URL'): + js.append(settings.MEDIA_URL + 'js/jquery.js') + js.extend( + ['%s%s' % (settings.MEDIA_URL, path) for path in ( + 'js/jquery-ui.js', + 'js/jquery-inline-positioning.js', + 'js/jquery-inline-rename.js', + 'js/jquery-inline-collapsible.js', + 'js/jquery-inline-fieldset-collapsible.js', + 'js/jquery-inline-prepopulate-label.js', + )]) + return forms.Media(js=js) + media = property(_media) diff --git a/form_designer/locale/de/LC_MESSAGES/django.mo b/form_designer/locale/de/LC_MESSAGES/django.mo index 8828468179b0e497795f75c235d295b546f8e433..8d9bf8b08390d2eab756f00445c197e34f62e68e 100644 GIT binary patch delta 2532 zcmb7_U2IfE6vwBQ@=+*ML_P&?(H3gimO`l&E!fh6l(H16lmbd_+o9dMy<7I)Z6P69 zqXvCZG>woL6HHK}F(Dy+&=+GABZe1<`XE}3n3!OU@~${&;4J)MNHNUwa2tF9UWZ@7JK+~(p@hDL zntv54;P0+~96o@bPxeePl~5b0b8LczDs>x1G2920VG?Sg4CFCKX;{m=2$k3fRNyz^ z-S7;Y13!b>>DN&6FGE%IM>rQ=b-V>By_wBoSp{5*p@qw6=EJp6J8FTN*akJO8+tGa zwZKbIf!=_3!P8KQd;pd3d8nO#36FUn%*MJZeWYoHQn zg^V?OVIAyuJPmWDgj(PVRE2(nO6WRN;6I_(DWnXTW+Bv3lsm3<{n>g9l_U-i!JV*_ zikyQ==oc=40_AbQTDS-*;l*$sEQb%l4Nw6OLybEIHE#qe@F~b+-gEv(kjiGwc?=D_ z=w5sW72s#b>rjd06HPl=0#$)kj@3{VsD(>lJ=CS`gi0g{Rhc7D2|f$8-Yc*`-~YQ9 z#4u-|CVUFZ;6>LiIO0#-S4HaQ=Q+fp6h9_$pihzlR!Ez^7KA z@4rw6EQYGYe5f5Qhq{F2P?vBkRDwI9#ytgD%k)DY^9+p=I|&uw9jK#yA9A8OQ3WKG zsE$SPbD}_6bSJ7w4XAb}sX?1_mNC^(yHF+c>6W7sRZ#ap{~?mKlgMqP)!9P3vz6dx z#b1v>f;oAZRy)Du8(|f?73DtWG?DK zoh+$!0$@ho=Vec!>V}75Ngm*`@Ly@urHJs>Aj3h zkV^O1G-)OiVPv{9VU!y15(9%tJ79yzj}ocC*w}VIk@Uh$S3g@aLCPGlp=nE}W>{kry*9Ak*4wq~EFs%cC#xAeBXW!-JNx2>RI zBPPa@m|z-hBiXqx~~%fJN-461G5%d%1-3R{(qIkb@91)<#_W4ac|Olk^{k z%EVcyom_wlZ~-~qL$q^cvuwJ_@x{b1<(iOXdl$X*Pt%R z+fb>UaXjw&Pe5JPd8nW3X}FdQB$=cDUgXBgQ3h(=*P#M`2bRMb_yo*-Mnw)ULk+wN zHL-vTA;+aqXI|mj)ljKyf*RNA`rDu!bUXGz1u_b?ktwJQ9CbVf$w1C#sockl6OhNw zQYes%P?`A+YN5ZOj;M%e6i^w3##O>cU>%gB7og@1!nJS&DzFb+|3{7|VIltQOV=?E zs~Pw$+y(!EkHS{+rN{%Q0EQh$pfWH4b(V*qGBX9Ag6}~E_yg3q1*kx-Ku#(S_A&fh z5@~@JB-2l7J=%s8h@9_0vS-laaV`IgP*peiR^t?7Yo_c*&mm<+wj1flHzWDCr;#G9 zL2YOcx);g3xHNkZ>igb=wxa}k9w|ees0zv0Tx@le9Y{wf(}zp826dqgXruf$P}zxY z+QU?oCVeQjA})5Ec1|O{@xaz0ow@FfzGO1}dFf9~sjo%)lag&gy6KzLz*_T&%V(AI zMyfhD-FAIEZkoE2HBsX};#^(BDb$xO!UG{Im+^@?&UOacC z{KJBs?S5TH5T>o?Rj)q`vfhDl&)*k?6JanGq-(rvCY6eUTf@o+%aF*M495C{bnSH4 T%2msD#J&?-gJ>)m7_k2V8jakc diff --git a/form_designer/locale/de/LC_MESSAGES/django.po b/form_designer/locale/de/LC_MESSAGES/django.po index 05968871..e9c97734 100644 --- a/form_designer/locale/de/LC_MESSAGES/django.po +++ b/form_designer/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2010-01-13 14:01+0100\n" +"POT-Creation-Date: 2010-06-28 14:41+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -16,156 +16,98 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: admin.py:15 -msgid "This field class requires a model." -msgstr "Diese Feld-Klasse benötigt ein Modell." - -#: admin.py:23 admin.py:69 +#: admin.py:27 admin.py:38 msgid "Basic" msgstr "Grundlegendes" -#: admin.py:24 +#: admin.py:28 msgid "Display" msgstr "Darstellung" -#: admin.py:25 defaults.py:4 +#: admin.py:29 settings.py:7 msgid "Text" msgstr "Text" -#: admin.py:26 +#: admin.py:30 msgid "Numbers" msgstr "Zahlen" -#: admin.py:27 defaults.py:17 +#: admin.py:31 settings.py:20 msgid "Regex" msgstr "Regex" -#: admin.py:28 +#: admin.py:32 msgid "Choices" msgstr "Auswahl" -#: admin.py:29 +#: admin.py:33 msgid "Model Choices" msgstr "Modell-Auswahl" -#: admin.py:70 +#: admin.py:39 msgid "Mail form" msgstr "Mail-Formular" -#: admin.py:71 +#: admin.py:40 msgid "Templates" msgstr "Vorlagen" -#: admin.py:72 +#: admin.py:41 msgid "Messages" msgstr "Meldungen" -#: admin.py:90 admin_views.py:35 cms_plugins.py:10 models.py:31 models.py:107 -#: models.py:243 +#: admin.py:62 admin.py:101 admin.py:146 cms_plugins.py:12 models.py:52 +#: models.py:148 models.py:284 msgid "Form" msgstr "Formular" -#: admin.py:95 models.py:108 +#: admin.py:77 models.py:149 msgid "Data" msgstr "Daten" -#: admin.py:107 -msgid "CSV export is not enabled." -msgstr "CSV-Export ist nicht aktiviert." - -#: admin_views.py:37 models.py:106 +#: admin.py:103 admin.py:148 models.py:147 msgid "Created" msgstr "Erstellt" -#: admin_views.py:39 +#: admin.py:105 admin.py:150 msgid "ID" msgstr "ID" -#: defaults.py:5 -msgid "E-mail address" -msgstr "E-Mail-Adresse" - -#: defaults.py:6 -msgid "Web address" -msgstr "Web-Adresse" - -#: defaults.py:7 -msgid "Number" -msgstr "Zahl" - -#: defaults.py:8 -msgid "Decimal number" -msgstr "Dezimalzahl" - -#: defaults.py:9 -msgid "Yes/No" -msgstr "Ja/Nein" - -#: defaults.py:10 -msgid "Date" -msgstr "Datum" - -#: defaults.py:11 -msgid "Date & time" -msgstr "Datum & Zeit" - -#: defaults.py:12 -msgid "Time" -msgstr "Zeit" - -#: defaults.py:13 -msgid "Choice" -msgstr "Auswahl" - -#: defaults.py:14 -msgid "Multiple Choice" -msgstr "Mehrfach-Auswahl" - -#: defaults.py:15 -msgid "Model Choice" -msgstr "Modell-Auswahl" - -#: defaults.py:16 -msgid "Model Multiple Choice" -msgstr "Modell-Mehrfach-Auswahl" +#: admin.py:124 +#, python-format +msgid "Export selected %(verbose_name_plural)s as CSV" +msgstr "Ausgewählte %(verbose_name_plural)s als CSV exportieren" -#: defaults.py:21 defaults.py:28 -msgid "Default" -msgstr "Voreinstellung" +#: admin.py:172 +#, python-format +msgid "Export selected %(verbose_name_plural)s as XLS" +msgstr "Ausgewählte %(verbose_name_plural)s als XLS exportieren" -#: defaults.py:22 -msgid "Text area" -msgstr "Textfeld" - -#: defaults.py:23 -msgid "Password input" -msgstr "Passwortfeld" +#: admin.py:184 +msgid "CSV export is not enabled." +msgstr "CSV-Export ist nicht aktiviert." -#: defaults.py:24 -msgid "Hidden input" -msgstr "Versteckter Wert" +#: admin.py:189 +msgid "XLS export is not enabled." +msgstr "XLS-Export ist nicht aktiviert." -#: defaults.py:29 -msgid "as paragraphs" -msgstr "als Absätze" - -#: defaults.py:30 -msgid "as table" -msgstr "als Tabelle" +#: forms.py:33 +msgid "This field class requires a model." +msgstr "Diese Feld-Klasse benötigt ein Modell." -#: models.py:13 models.py:121 +#: models.py:34 models.py:162 msgid "Name" msgstr "Name" -#: models.py:14 +#: models.py:35 msgid "Title" msgstr "Titel" -#: models.py:15 +#: models.py:36 msgid "Target URL" msgstr "Ziel-URL" -#: models.py:15 +#: models.py:36 msgid "" "If you leave this empty, the page where the form resides will be requested, " "and you can use the mail form and logging features. You can also send data " @@ -177,188 +119,274 @@ msgstr "" "werden. Es ist auch möglich, Daten an externe Sites zu senden: Beispielweise " "\"http://www.google.ch/search\" eingeben, um ein Suchformular zu erstellen." -#: models.py:16 +#: models.py:37 msgid "Send form data to e-mail address" msgstr "Formulardaten an E-Mail-Adresse senden" -#: models.py:17 +#: models.py:38 msgid "Sender address" msgstr "Absender-Adresse" -#: models.py:18 +#: models.py:39 msgid "e-Mail subject" msgstr "E-Mail-Betreff" -#: models.py:19 +#: models.py:40 msgid "Method" msgstr "Methode" -#: models.py:20 +#: models.py:41 msgid "Success message" msgstr "Erfolgsmeldung" -#: models.py:21 +#: models.py:42 msgid "Error message" msgstr "Fehlermeldung" -#: models.py:22 +#: models.py:43 msgid "Submit button label" msgstr "Beschriftung der Senden-Schaltfläche" -#: models.py:23 +#: models.py:44 msgid "Log form data" msgstr "Formulardaten loggen" -#: models.py:23 +#: models.py:44 msgid "Logs all form submissions to the database." msgstr "Speichert alle Formular-Übermittlungen in der Datenbank." -#: models.py:24 +#: models.py:45 msgid "Redirect after success" msgstr "Bei Erfolg weiterleiten" -#: models.py:24 -msgid "You should install django_notify if you want to enable this." -msgstr "Um dies zu aktivieren, sollte django_notify installiert werden." - -#: models.py:25 +#: models.py:46 msgid "Clear form after success" msgstr "Bei Erfolg Formular zurücksetzen" -#: models.py:26 +#: models.py:47 msgid "Allow initial values via URL" msgstr "Initialwerte via URL erlauben" -#: models.py:26 +#: models.py:47 msgid "" "If enabled, you can fill in form fields by adding them to the query string." msgstr "" "Falls aktiviert, ist es möglich, Formularfelder vorauszufüllen, indem sie " "dem Query-String hinzugefügt werden." -#: models.py:27 +#: models.py:48 msgid "Message template" msgstr "Nachrichten-Vorlage" -#: models.py:27 +#: models.py:48 +#, fuzzy msgid "" -"Available context: \"data\" (a list containing a dictionary for each form " -"field, each containing the elements \"name\", \"label\", \"value\")." +"Your form fields are available as template context. Example: \"{{ message }}" +"\" if you have a field named `message`. To iterate over all fields, use the " +"variable `data` (a list containing a dictionary for each form field, each " +"containing the elements `name`, `label`, `value`)." msgstr "" +"Verfügbarer Kontext: \"data\" (Eine List mit Dictionaries für jedes Formular " +"und Feld, jeweils mit den folgenden Elementen: \"name\", \"label\", \"value" +"\")." -#: models.py:28 +#: models.py:49 msgid "Form template" msgstr "Formular-Vorlage" -#: models.py:32 +#: models.py:53 msgid "Forms" msgstr "Formulare" -#: models.py:67 models.py:147 models.py:233 +#: models.py:95 models.py:188 models.py:274 msgid "Fields" msgstr "Felder" -#: models.py:111 +#: models.py:152 msgid "Form log" msgstr "Formular-Log" -#: models.py:112 +#: models.py:153 msgid "Form logs" msgstr "Formular-Logs" -#: models.py:118 +#: models.py:159 msgid "Field class" msgstr "Feld-Klasse" -#: models.py:119 +#: models.py:160 msgid "Position" msgstr "Position" -#: models.py:122 +#: models.py:163 msgid "Label" msgstr "Beschriftung" -#: models.py:123 +#: models.py:164 msgid "Required" msgstr "Erforderlich" -#: models.py:124 +#: models.py:165 msgid "Include in result" msgstr "In Resultat einbeziehen" -#: models.py:125 +#: models.py:166 msgid "Widget" msgstr "Widget" -#: models.py:126 +#: models.py:167 msgid "Initial value" msgstr "Initialwert" -#: models.py:127 +#: models.py:168 msgid "Help text" msgstr "Hilfetext" -#: models.py:129 +#: models.py:170 msgid "Values" msgstr "Werte" -#: models.py:129 +#: models.py:170 msgid "One value per line" msgstr "Ein Wert pro Zeile" -#: models.py:130 +#: models.py:171 msgid "Labels" msgstr "Beschriftungen" -#: models.py:130 +#: models.py:171 msgid "One label per line" msgstr "Eine Beschriftung pro Zeile" -#: models.py:132 +#: models.py:173 msgid "Max. length" msgstr "Max. Länge" -#: models.py:133 +#: models.py:174 msgid "Min. length" msgstr "Min. Länge" -#: models.py:134 +#: models.py:175 msgid "Max. value" msgstr "Max. Wert" -#: models.py:135 +#: models.py:176 msgid "Min. value" msgstr "Min. Wert" -#: models.py:136 +#: models.py:177 msgid "Max. digits" msgstr "Max. Ziffern" -#: models.py:137 +#: models.py:178 msgid "Decimal places" msgstr "Dezimalstellen" -#: models.py:139 +#: models.py:180 msgid "Regular Expression" msgstr "Regulärer Ausdruck" -#: models.py:142 +#: models.py:183 msgid "Data model" msgstr "Datenmodell" -#: models.py:143 +#: models.py:184 msgid "Empty label" msgstr "Beschriftung falls leer" -#: models.py:146 models.py:232 +#: models.py:187 models.py:273 msgid "Field" msgstr "Feld" -#: views.py:18 +#: settings.py:8 +msgid "E-mail address" +msgstr "E-Mail-Adresse" + +#: settings.py:9 +msgid "Web address" +msgstr "Web-Adresse" + +#: settings.py:10 +msgid "Number" +msgstr "Zahl" + +#: settings.py:11 +msgid "Decimal number" +msgstr "Dezimalzahl" + +#: settings.py:12 +msgid "Yes/No" +msgstr "Ja/Nein" + +#: settings.py:13 +msgid "Date" +msgstr "Datum" + +#: settings.py:14 +msgid "Date & time" +msgstr "Datum & Zeit" + +#: settings.py:15 +msgid "Time" +msgstr "Zeit" + +#: settings.py:16 +msgid "Choice" +msgstr "Auswahl" + +#: settings.py:17 +msgid "Multiple Choice" +msgstr "Mehrfach-Auswahl" + +#: settings.py:18 +msgid "Model Choice" +msgstr "Modell-Auswahl" + +#: settings.py:19 +msgid "Model Multiple Choice" +msgstr "Modell-Mehrfach-Auswahl" + +#: settings.py:25 settings.py:33 +msgid "Default" +msgstr "Voreinstellung" + +#: settings.py:26 +msgid "Text area" +msgstr "Textfeld" + +#: settings.py:27 +msgid "Password input" +msgstr "Passwortfeld" + +#: settings.py:28 +msgid "Hidden input" +msgstr "Versteckter Wert" + +#: settings.py:29 +msgid "Radio button" +msgstr "Radio-Buttons" + +#: settings.py:34 +msgid "as paragraphs" +msgstr "als Absätze" + +#: settings.py:35 +msgid "as table" +msgstr "als Tabelle" + +#: settings.py:36 +msgid "as unordered list" +msgstr "als unsortierte Liste" + +#: settings.py:37 +msgid "custom implementation" +msgstr "eigene Implementierung" + +#: views.py:13 msgid "Thank you, the data was submitted successfully." msgstr "Vielen Dank, die Daten wurden übermittelt." -#: views.py:19 +#: views.py:14 msgid "The data could not be submitted, please try again." msgstr "" "Die Daten konnten nicht übermittelt werden, bitte versuchen Sie es erneut." @@ -367,10 +395,22 @@ msgstr "" msgid "Export CSV" msgstr "CSV exportieren" -#: templates/html/formdefinition/forms/as_p.html:10 -#: templates/html/formdefinition/forms/as_table.html:10 +#: templates/admin/form_designer/formlog/change_list.html:16 +#, fuzzy +msgid "Export XLS" +msgstr "XLS exportieren" + +#: templates/html/formdefinition/forms/as_p.html:11 +#: templates/html/formdefinition/forms/as_table.html:11 +#: templates/html/formdefinition/forms/as_u.html:11 +#: templates/html/formdefinition/forms/custom.html:40 msgid "Submit" msgstr "Absenden" -#~ msgid "Captcha" -#~ msgstr "Captcha" +#: templatetags/friendly.py:16 +msgid "yes" +msgstr "ja" + +#: templatetags/friendly.py:16 +msgid "no" +msgstr "nein" diff --git a/form_designer/locale/en/LC_MESSAGES/django.po b/form_designer/locale/en/LC_MESSAGES/django.po new file mode 100644 index 00000000..b5b625d0 --- /dev/null +++ b/form_designer/locale/en/LC_MESSAGES/django.po @@ -0,0 +1,404 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2010-06-28 14:41+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: admin.py:27 admin.py:38 +msgid "Basic" +msgstr "" + +#: admin.py:28 +msgid "Display" +msgstr "" + +#: admin.py:29 settings.py:7 +msgid "Text" +msgstr "" + +#: admin.py:30 +msgid "Numbers" +msgstr "" + +#: admin.py:31 settings.py:20 +msgid "Regex" +msgstr "" + +#: admin.py:32 +msgid "Choices" +msgstr "" + +#: admin.py:33 +msgid "Model Choices" +msgstr "" + +#: admin.py:39 +msgid "Mail form" +msgstr "" + +#: admin.py:40 +msgid "Templates" +msgstr "" + +#: admin.py:41 +msgid "Messages" +msgstr "" + +#: admin.py:62 admin.py:101 admin.py:146 cms_plugins.py:12 models.py:52 +#: models.py:148 models.py:284 +msgid "Form" +msgstr "" + +#: admin.py:77 models.py:149 +msgid "Data" +msgstr "" + +#: admin.py:103 admin.py:148 models.py:147 +msgid "Created" +msgstr "" + +#: admin.py:105 admin.py:150 +msgid "ID" +msgstr "" + +#: admin.py:124 +#, python-format +msgid "Export selected %(verbose_name_plural)s as CSV" +msgstr "" + +#: admin.py:172 +#, python-format +msgid "Export selected %(verbose_name_plural)s as XLS" +msgstr "" + +#: admin.py:184 +msgid "CSV export is not enabled." +msgstr "" + +#: admin.py:189 +msgid "XLS export is not enabled." +msgstr "" + +#: forms.py:33 +msgid "This field class requires a model." +msgstr "" + +#: models.py:34 models.py:162 +msgid "Name" +msgstr "" + +#: models.py:35 +msgid "Title" +msgstr "" + +#: models.py:36 +msgid "Target URL" +msgstr "" + +#: models.py:36 +msgid "" +"If you leave this empty, the page where the form resides will be requested, " +"and you can use the mail form and logging features. You can also send data " +"to external sites: For instance, enter \"http://www.google.ch/search\" to " +"create a search form." +msgstr "" + +#: models.py:37 +msgid "Send form data to e-mail address" +msgstr "" + +#: models.py:38 +msgid "Sender address" +msgstr "" + +#: models.py:39 +msgid "e-Mail subject" +msgstr "" + +#: models.py:40 +msgid "Method" +msgstr "" + +#: models.py:41 +msgid "Success message" +msgstr "" + +#: models.py:42 +msgid "Error message" +msgstr "" + +#: models.py:43 +msgid "Submit button label" +msgstr "" + +#: models.py:44 +msgid "Log form data" +msgstr "" + +#: models.py:44 +msgid "Logs all form submissions to the database." +msgstr "" + +#: models.py:45 +msgid "Redirect after success" +msgstr "" + +#: models.py:46 +msgid "Clear form after success" +msgstr "" + +#: models.py:47 +msgid "Allow initial values via URL" +msgstr "" + +#: models.py:47 +msgid "" +"If enabled, you can fill in form fields by adding them to the query string." +msgstr "" + +#: models.py:48 +msgid "Message template" +msgstr "" + +#: models.py:48 +msgid "" +"Your form fields are available as template context. Example: \"{{ message }}" +"\" if you have a field named `message`. To iterate over all fields, use the " +"variable `data` (a list containing a dictionary for each form field, each " +"containing the elements `name`, `label`, `value`)." +msgstr "" + +#: models.py:49 +msgid "Form template" +msgstr "" + +#: models.py:53 +msgid "Forms" +msgstr "" + +#: models.py:95 models.py:188 models.py:274 +msgid "Fields" +msgstr "" + +#: models.py:152 +msgid "Form log" +msgstr "" + +#: models.py:153 +msgid "Form logs" +msgstr "" + +#: models.py:159 +msgid "Field class" +msgstr "" + +#: models.py:160 +msgid "Position" +msgstr "" + +#: models.py:163 +msgid "Label" +msgstr "" + +#: models.py:164 +msgid "Required" +msgstr "" + +#: models.py:165 +msgid "Include in result" +msgstr "" + +#: models.py:166 +msgid "Widget" +msgstr "" + +#: models.py:167 +msgid "Initial value" +msgstr "" + +#: models.py:168 +msgid "Help text" +msgstr "" + +#: models.py:170 +msgid "Values" +msgstr "" + +#: models.py:170 +msgid "One value per line" +msgstr "" + +#: models.py:171 +msgid "Labels" +msgstr "" + +#: models.py:171 +msgid "One label per line" +msgstr "" + +#: models.py:173 +msgid "Max. length" +msgstr "" + +#: models.py:174 +msgid "Min. length" +msgstr "" + +#: models.py:175 +msgid "Max. value" +msgstr "" + +#: models.py:176 +msgid "Min. value" +msgstr "" + +#: models.py:177 +msgid "Max. digits" +msgstr "" + +#: models.py:178 +msgid "Decimal places" +msgstr "" + +#: models.py:180 +msgid "Regular Expression" +msgstr "" + +#: models.py:183 +msgid "Data model" +msgstr "" + +#: models.py:184 +msgid "Empty label" +msgstr "" + +#: models.py:187 models.py:273 +msgid "Field" +msgstr "" + +#: settings.py:8 +msgid "E-mail address" +msgstr "" + +#: settings.py:9 +msgid "Web address" +msgstr "" + +#: settings.py:10 +msgid "Number" +msgstr "" + +#: settings.py:11 +msgid "Decimal number" +msgstr "" + +#: settings.py:12 +msgid "Yes/No" +msgstr "" + +#: settings.py:13 +msgid "Date" +msgstr "" + +#: settings.py:14 +msgid "Date & time" +msgstr "" + +#: settings.py:15 +msgid "Time" +msgstr "" + +#: settings.py:16 +msgid "Choice" +msgstr "" + +#: settings.py:17 +msgid "Multiple Choice" +msgstr "" + +#: settings.py:18 +msgid "Model Choice" +msgstr "" + +#: settings.py:19 +msgid "Model Multiple Choice" +msgstr "" + +#: settings.py:25 settings.py:33 +msgid "Default" +msgstr "" + +#: settings.py:26 +msgid "Text area" +msgstr "" + +#: settings.py:27 +msgid "Password input" +msgstr "" + +#: settings.py:28 +msgid "Hidden input" +msgstr "" + +#: settings.py:29 +msgid "Radio button" +msgstr "" + +#: settings.py:34 +msgid "as paragraphs" +msgstr "" + +#: settings.py:35 +msgid "as table" +msgstr "" + +#: settings.py:36 +msgid "as unordered list" +msgstr "" + +#: settings.py:37 +msgid "custom implementation" +msgstr "" + +#: views.py:13 +msgid "Thank you, the data was submitted successfully." +msgstr "" + +#: views.py:14 +msgid "The data could not be submitted, please try again." +msgstr "" + +#: templates/admin/form_designer/formlog/change_list.html:9 +msgid "Export CSV" +msgstr "" + +#: templates/admin/form_designer/formlog/change_list.html:16 +msgid "Export XLS" +msgstr "" + +#: templates/html/formdefinition/forms/as_p.html:11 +#: templates/html/formdefinition/forms/as_table.html:11 +#: templates/html/formdefinition/forms/as_u.html:11 +#: templates/html/formdefinition/forms/custom.html:40 +msgid "Submit" +msgstr "" + +#: templatetags/friendly.py:16 +msgid "yes" +msgstr "" + +#: templatetags/friendly.py:16 +msgid "no" +msgstr "" diff --git a/form_designer/locale/nl/LC_MESSAGES/django.mo b/form_designer/locale/nl/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..f6aea2eac443312a7c88eaa52d43f419dae22b07 GIT binary patch literal 6310 zcmb7{X>24%6~_x$u1&ZS2uFYvOA->+GxjbaSxgA=uD!9dws&KDce7k^%}jaRc2D=v zhrQkqp&*3d3lazvJ|J<5fDl0f386?JK|p*!0wF#jP(bh@5&|J{i5q_Z>hjnG5YV3L zUsrb>uU@_P>iN+t4t&sXy@m2(%H>Zo=I!t=Pv*w;ho?60{|NQ!s;3$ABKTtX6nG2d zlerDP0G^EXd*A`;eRvS2vAzv2qy9GdO!)4|2cX9JROCbP{gwABE{~>re^^e0R!q3A~@C)#_ z@Ne)r@HYsf{!eBy^}iBoz86BhxA2v)4e4SoKz^nhnM1ATUifVIE-3jPfEwqcke_*w zo7#O5O0KU$jsI=!}E>`sHvOUImZBlkgDS zfok_*sQEtzHU5K;C7Fj}{b49O{93I42ww37V}1ttncpLvFF9MxnB*n zuhS5dH+R6B>3<4dNBtdahUWh?ycRwb>)(fx=Lb;x`V**iUdAMn`zomQT@BU$I>^$^ zVaU+tSo}U4xdbKWo1ofxsPPJ@_1*_1?|ULY1aGGPAiNbm3N`Q5Y?As}DESUU$#E1) zAGbn{b0_3yE^yQMDO9_QP~YDHrI+_X2R{H0pqJl6>F>{MGD|kkd2X}5Ls0YG0Qs45 zZY?+sHC_Q_7xzQW_rb`ILFwajQ1gEU%1*x(zkfIKhmns#>E}04{UnU~JsWEMFM^We z8mMuuhcq>9$j_YMCV4JEeP4%?X9)S3w{er4?~nWxRJ(_v_WA2j{eKBH?_b~x;ZtxH zy|+;NI1csw7}WkBkM&vDroIG^!~3D^{Top7KL*wQ7f|#58fu(BL9Oe+^BX(745~f> zW$(8@?eCogU$ws*^2&6e)^iDJzupUF=O2OE-%mighfDo+jZ-wn?qzw9j+Q9uBe}Ik zU9YB`3{Q>!-oW$qlvh!tFI}2{lJXK&xNd3?V%rZPr z(Ruv(_)h0DreJ1c{dPD(xsxJ0*L7WkU`KD{S@!vg`0gBhDdhxZM(?K5G~c#KmQ;yL z?UqYxU)rt2*|SUY=C~`9t~s%M&iaePyr^ta+AOcE&s-<K^6q2l zQ{P28d)*CNzUbtozbA78f6p+j3zyzlT36bAJ^z>H8vSJA(_VA8-P9ppcmvQ+xY;!C zj~<%$w(_|UJ*0!x+jOJ4{fvzpuQ1!g|jQMi|8K1W{JwFFZQt6=HjRatiiNm(X& zR_>}-tvassZL=T)RWqJ0wrwxzCso-zr#|af>&z)zB7j*E|dfqb&N!DhY zntpnY{p3tGI^)8=3LQgjf#oH`)Q{RcuD*!9o5jZG8kFXA<{MwKLzJ8*nGdhRjC-%n zVA$Jv(Tj$8CTFA2gjsUEB)6Tqs#Nj4q`=1>7ZsL#-(TcozfK7o_^7-%bjF*$pNEsT{1snIR;N9%+2F7i&-0b+$oGEfuTXVhEIbX=0C+(T36Q`!` zn6c+(mX_yEFCJX-I!;>)Wk2b)j@SLNwUSTTgJ({!w8BwIr`9xjo3uwKj!d*BZf+et zW{=!7dDD@@6GtZ|XxLivTZsmb?JmqrEwvU-x2C7;?CHscxx;Tby*P7l-j!8rrEpov znUWWicBV)+?NUBSHZpswr*gY)?IgdAwpqU>*0)x?8%)~ysl_|aO6FVV=WkDwc3$*v zJGd~nFtfL+BkhTUCuAxalk5zUR#r34`ITg^wcT}Bl)if1*_D&6V|(o+%$hG+Gg&v+ zu{3Fqb&~3!3BmV~&ti$&&Y}A(WFf?75)mBV>rl)>MnXEVe6H1GBy169B3Vg-FZcZ; zpTtnMyf4U2PWk$hZ|0B_u$7bLuq`@ynuD@V6U8_0`vbSh6jSKI51iX1&rq<} zZIrFl1J&z6$X7N#KrKH4mPHJ*MxIqI9V^D+Q+-& zrjFUoI;(6Qj)X9(+R6g5Eg~=vy`1>a^OuN9+lmcEeR;R;O8|t|UH5=7Kreb<9iWP?*$SN{BZd95AD0KJ5Fg!nuBre8g@tEkhE2 znuCg0=%KY%XDouK-uIoNPO!7KJ=?4%&o`SxBg0x^!o#7Vd}q&6+xB>}vBly#o)wR2 z3I9Iv$p@p%hs1`M<&of0?lt6s1h9R*H!XNP||?elIarp;#j3hi zw4U~RpEuD9787E+ysK#w9MfkcQALgy@ZV@1+%g~1u-?`XxV`3X0+r;KW`YNV0IpPu zm`y6hcAXEWOsQNd>R~UF`tOI!oo;;5d&8neCLBXiSX;-J-SuR%Bac=P{@2WEzdJYS z760$bszZ9EK6MaWqoNu1azJhs+pMy1zeav`dN9hYnx&&QTw7Tef9NWP8|g|r8j>wC zBim$KlVJ)r2`vqXaEcYmwFoatv(n9=&hUxeF{7f*<5DWcy_i&yQz3dxs@z(r;qmBO l?{gwSN#euqF\n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "Allow initial values via URL" +msgstr "Initiële waarden via de URL toelaten" + +msgid "Basic" +msgstr "Basis" + +msgid "CSV export is not enabled." +msgstr "CSV-exporteren is niet geäctiveerd." + +msgid "Choice" +msgstr "Keuze" + +msgid "Choices" +msgstr "Keuzes" + +msgid "Clear form after success" +msgstr "Na succesvol versturen formulier leegmaken" + +msgid "Created" +msgstr "Aangemaakt" + +msgid "Data" +msgstr "Data" + +msgid "Data model" +msgstr "Datamodel" + +msgid "Date" +msgstr "Datum" + +msgid "Date & time" +msgstr "Datum & tijd" + +msgid "Decimal number" +msgstr "Decimaal nummer" + +msgid "Decimal places" +msgstr "Decimalen" + +msgid "Default" +msgstr "Standaard" + +msgid "Display" +msgstr "Weergave" + +msgid "E-mail address" +msgstr "E-mail-adres" + +msgid "Empty label" +msgstr "Leeg label" + +msgid "Error message" +msgstr "Foutmelding" + +msgid "Export CSV" +msgstr "CSV exporteren" + +msgid "Export XLS" +msgstr "XLS exporteren" + +msgid "Export selected %(verbose_name_plural)s as CSV" +msgstr "Exporteer geselecteerde %(verbose_name_plural) als CSV" + +msgid "Export selected %(verbose_name_plural)s as XLS" +msgstr "Exporteer geselecteerde %(verbose_name_plural) als Excel XLS" + +msgid "Field" +msgstr "Veld" + +msgid "Field class" +msgstr "Veldklasse" + +msgid "Fields" +msgstr "Velden" + +msgid "Form" +msgstr "Formulier" + +msgid "Form log" +msgstr "Formulier-log" + +msgid "Form logs" +msgstr "Formulier-logs" + +msgid "Form template" +msgstr "Formulier-sjabloon" + +msgid "Forms" +msgstr "Formulieren" + +msgid "Help text" +msgstr "Hulptekst" + +msgid "Hidden input" +msgstr "Verborgen veld" + +msgid "ID" +msgstr "ID" + +msgid "" +"If enabled, you can fill in form fields by adding them to the query string." +msgstr "" +"Indien geactiveerd, kunt u formuliervelden invullen door deze aan de query-" +"string toe te voegen. " + +msgid "" +"If you leave this empty, the page where the form resides will be requested, " +"and you can use the mail form and logging features. You can also send data " +"to external sites: For instance, enter \"http://www.google.ch/search\" to " +"create a search form." +msgstr "" +"Als u dit leeg laat, wordt de pagina waar het formulier zich bevindt " +"opgevraagd en kan u de e-mail en log-functies gebruiken. U kunt ook data " +"naar externe sites versturen. Bijvoorbeeld, voer \"http://www.google.com/" +"search\" in om een zoekformulier aan te maken. " + +msgid "Include in result" +msgstr "In resultaat plaatsen" + +msgid "Initial value" +msgstr "Initiële waarde" + +msgid "Label" +msgstr "Label" + +msgid "Labels" +msgstr "Labels" + +msgid "Log form data" +msgstr "Formulierdata loggen" + +msgid "Logs all form submissions to the database." +msgstr "Log alle formulierinsturingen in de database." + +msgid "Mail form" +msgstr "Mail-formulier" + +msgid "Max. digits" +msgstr "Max. aantal cijfers" + +msgid "Max. length" +msgstr "Max. lengte" + +msgid "Max. value" +msgstr "Max. waarde" + +msgid "Message template" +msgstr "Berichtsjabloon" + +msgid "Messages" +msgstr "Berichten" + +msgid "Method" +msgstr "Methode" + +msgid "Min. length" +msgstr "Min. lengte" + +msgid "Min. value" +msgstr "Min. waarde" + +msgid "Model Choice" +msgstr "Model-keuze" + +msgid "Model Choices" +msgstr "Model-keuzes" + +msgid "Model Multiple Choice" +msgstr "Model-meerkeuze" + +msgid "Multiple Choice" +msgstr "Meerkeuze" + +msgid "Name" +msgstr "Naam" + +msgid "Number" +msgstr "Nummer" + +msgid "Numbers" +msgstr "Nummers" + +msgid "One label per line" +msgstr "Eén label per regel" + +msgid "One value per line" +msgstr "Eén waarde per regel" + +msgid "Password input" +msgstr "Wachtwoordveld" + +msgid "Position" +msgstr "Positie" + +msgid "Radio button" +msgstr "Keuzerondje" + +msgid "Redirect after success" +msgstr "Doorsturen na success" + +msgid "Regex" +msgstr "Reguliere expressie" + +msgid "Regular Expression" +msgstr "Reguliere expressie" + +msgid "Required" +msgstr "Vereist" + +msgid "Send form data to e-mail address" +msgstr "Formuliergegevens naar e-mail-adres sturen" + +msgid "Sender address" +msgstr "Afzender-adres" + +msgid "Submit" +msgstr "Verzenden" + +msgid "Submit button label" +msgstr "Label voor de verzendknop" + +msgid "Success message" +msgstr "Successmelding" + +msgid "Target URL" +msgstr "Doel-URL" + +msgid "Templates" +msgstr "Sjablonen" + +msgid "Text" +msgstr "Tekst" + +msgid "Text area" +msgstr "Tekstveld" + +msgid "Thank you, the data was submitted successfully." +msgstr "Hartelijk dank. Uw gegevens zijn doorgestuurd." + +msgid "The data could not be submitted, please try again." +msgstr "De gegevens konden niet opgestuurd worden, probeert u het opnieuw." + +msgid "This field class requires a model." +msgstr "Deze veldklasse vereist een model." + +msgid "Time" +msgstr "Tijd" + +msgid "Title" +msgstr "Titel" + +msgid "Values" +msgstr "Waardes" + +msgid "Web address" +msgstr "Web-adres" + +msgid "Widget" +msgstr "Widget" + +msgid "XLS export is not enabled." +msgstr "XLS-exporteren is niet geäctiveerd." + +msgid "Yes/No" +msgstr "Ja/Nee" + +msgid "" +"Your form fields are available as template context. Example: \"{{ message }}" +"\" if you have a field named `message`. To iterate over all fields, use the " +"variable `data` (a list containing a dictionary for each form field, each " +"containing the elements `name`, `label`, `value`)." +msgstr "" +"Uw formuliervelden zijn beschikbaar als template context. Bijvoorbeeld: " +"\"{{ bericht }}\" als u een veld genaamd `bericht` heeft. Om over alle " +"velden te itereren, gebruikt u de variabele `data`: een lijst met een dict " +"voor elk veld met de elementen `name`, `label` en `value`." + +msgid "as paragraphs" +msgstr "als paragraaf" + +msgid "as table" +msgstr "als tabel" + +msgid "as unordered list" +msgstr "als ongesorteerde lijst" + +msgid "custom implementation" +msgstr "eigen implementatie" + +msgid "e-Mail subject" +msgstr "E-mail-onderwerp" + +msgid "no" +msgstr "nee" + +msgid "yes" +msgstr "ja" diff --git a/form_designer/media/form_designer/js/jquery-inline-collapsible.js b/form_designer/media/form_designer/js/jquery-inline-collapsible.js new file mode 100644 index 00000000..b1f863a2 --- /dev/null +++ b/form_designer/media/form_designer/js/jquery-inline-collapsible.js @@ -0,0 +1,50 @@ +function makeCollapsible(target, item, collapsible, triggerTarget, setInitStatus, setFirstStatus) +{ + var triggerExpand = gettext('Show'); + var triggerCollapse = gettext('Hide'); + var triggerLink = ''; + var triggerPrepend = ' ('; + var triggerAppend = ')'; + + $(target).find(item).each(function(i) { + $(this).find(collapsible).hide(); + + var trigger = $(triggerLink) + $(this).find(triggerTarget).append(trigger); + trigger.before(triggerPrepend); + trigger.after(triggerAppend); + var item = this + var toggleCollapse = function(status, speed) + { + if (status == null) { + status = !item.collapseStatus; + } + if (speed == null) { + speed = 1; + } + item.collapseStatus = status; + if (status) { + trigger.html(triggerCollapse); + $(item).find(collapsible).show(); + } else { + trigger.html(triggerExpand); + $(item).find(collapsible).hide(); + } + } + + trigger.click(function(event) { + event.preventDefault(); + toggleCollapse(null, 'normal') + }) + + // Collapse by default unless there are errors + initStatus = setInitStatus != null ? setInitStatus : $(this).find('.errors').length != 0; + firstStatus = setFirstStatus != null ? setFirstStatus : initStatus; + + toggleCollapse(i == 0 ? firstStatus : initStatus) + }); +} + +jQuery(function($) { + makeCollapsible('div.inline-group', 'div.inline-related', 'fieldset', 'h3 b'); +}); diff --git a/form_designer/media/form_designer/js/jquery-inline-fieldset-collapsible.js b/form_designer/media/form_designer/js/jquery-inline-fieldset-collapsible.js new file mode 100644 index 00000000..c4b02044 --- /dev/null +++ b/form_designer/media/form_designer/js/jquery-inline-fieldset-collapsible.js @@ -0,0 +1,5 @@ +jQuery(function($) { + $('div.inline-related').each(function(i) { + makeCollapsible(this, 'fieldset', '.form-row', 'h2', null, true) + }); +}); diff --git a/form_designer/media/form_designer/js/jquery-inline-positioning.js b/form_designer/media/form_designer/js/jquery-inline-positioning.js new file mode 100644 index 00000000..295a8a74 --- /dev/null +++ b/form_designer/media/form_designer/js/jquery-inline-positioning.js @@ -0,0 +1,45 @@ +/* +Enables positioning of the inline elements by drag & drop. + +All the inline model requires is a "position" field that is blank by default. +This value will be set automatically by this code snippet when dragging elements. +The model can then be ordered by "position". +*/ + +jQuery(function($) { + + var positionField = $.scriptUrlParam ? $.scriptUrlParam(/jquery-inline-positioning\.js(\?.*)?$/, 'positionField', 'position') : 'position'; + var target = $('div.inline-group'); + var handle = 'h3 b'; + var item = 'div.inline-related'; + var positionInput = 'input[id$=-'+positionField+']'; + + target.find(item).each(function(i) { + $(this).find(handle).css('cursor', 'move'); + $(this).find(handle).addClass('draggable'); + $(this).find(positionInput).each(function() { + $(this)[0].readOnly = true; + }); + $(this).find('input, select, textarea').change(function() { + $(this).closest(item).find('input[id$='+positionField+']').val('X'); // mark for renumberAll() to fill in + renumberAll($('div.inline-group')); + }); + }); + + var renumberAll = function() { + target.find(item).each(function(i) { + if ($(this).find(positionInput).val() != '') { + $(this).find(positionInput).val(i+1); + } + }); + }; + + target.sortable({ + containment: 'parent', + /*zindex: 10, */ + items: item, + handle: handle, + update: renumberAll, + opacity: .75 + }); +}); diff --git a/form_designer/media/form_designer/js/jquery-inline-prepopulate-label.js b/form_designer/media/form_designer/js/jquery-inline-prepopulate-label.js new file mode 100644 index 00000000..ad9cba5a --- /dev/null +++ b/form_designer/media/form_designer/js/jquery-inline-prepopulate-label.js @@ -0,0 +1,29 @@ +/* +Does exactly what + prepopulated_fields = {"label" : ('name',)} +would do, but does not URLify the value (since in this case, name is a slug field but label is a title field) +*/ + + +jQuery(function($) { + + var target = $('div.inline-group'); + var item = 'div.inline-related'; + + $(target).find(item).each(function(i) { + var item = $(this); + item.find('input[id*=-label]').each(function() { + this._changed = item.find('input[id*=-name]').val() != $(this).val(); + }); + item.find('input[id*=-label]').change(function() { + this._changed = true; + }); + item.find('input[id*=-name]').keyup(function() { + labelInput = item.find('input[id*=-label]'); + if (!labelInput[0]._changed) { + labelInput.val($(this).val()); + } + }); + }); + +}); \ No newline at end of file diff --git a/form_designer/media/form_designer/js/jquery-inline-rename.js b/form_designer/media/form_designer/js/jquery-inline-rename.js new file mode 100644 index 00000000..3c55b14e --- /dev/null +++ b/form_designer/media/form_designer/js/jquery-inline-rename.js @@ -0,0 +1,44 @@ +/* +Replaces the name in an inline element's header while typing it in the input of the "name" field. +This way, the extra inline element's header will be named instead of numbered #4, #5 etc. +*/ + +jQuery(function($) { + + var nameField = $.scriptUrlParam ? $.scriptUrlParam(/jquery-inline-rename\.js(\?.*)?$/, 'nameField', 'name') : 'name'; + var target = $('div.inline-group'); + var item = 'div.inline-related'; + var containsName = 'h3'; + var beforeName = 'h3 > *:first'; + var afterName = 'h3 > *:last'; + var nameInput = 'input[id*=-'+nameField+']'; + + target.find(item).each(function() { + // The following code is due to the fact that the inline + // element's name is (lamentably) not wrapped in a tag. + + // 1. Strip everything before and after name + var stripBefore = $(this).find(beforeName).remove(); + var stripAfter = $(this).find(afterName).remove(); + // 2. Now we can get the name (text node) + var nameParent = $(this).find(containsName); + var name = $(nameParent).html(); + // 3. Strip leading whitespace including   + name = name.replace( /^(\s| )+/g, "" ) + // 4. Wrap name in tag + name = $(''+name+''); + // 5. Re-insert it with a space before + $(nameParent).html(name); + $(name).before(' '); + // 6. Restore everything before and after name + $(nameParent).prepend(stripBefore); + $(nameParent).append(stripAfter); + + // Update name while typing + $(this).find(nameInput).keyup(function(event) { + name.html($(this).val()); + }) + }) + + +}); diff --git a/form_designer/media/form_designer/js/lib/jquery-ui.js b/form_designer/media/form_designer/js/jquery-ui.js similarity index 100% rename from form_designer/media/form_designer/js/lib/jquery-ui.js rename to form_designer/media/form_designer/js/jquery-ui.js diff --git a/form_designer/media/form_designer/js/jquery-url-param.js b/form_designer/media/form_designer/js/jquery-url-param.js new file mode 100644 index 00000000..7572a82b --- /dev/null +++ b/form_designer/media/form_designer/js/jquery-url-param.js @@ -0,0 +1,17 @@ +$.urlParam = function(name, defaultValue, url) { + if (!url) { + url = window.location.href + } + var results = new RegExp('[\\?&]'+name+'=([^&#]*)').exec(url); + return results ? results[1] : defaultValue; +} + +$.scriptUrlParam = function(js, name, defaultValue) { + result = defaultValue; + $('head script[src]').each(function() { + if (this.src.match(js)) { + result = $.urlParam(name, result, this.src); + } + }); + return result; +} diff --git a/form_designer/media/form_designer/js/lib/jquery.js b/form_designer/media/form_designer/js/jquery.js similarity index 100% rename from form_designer/media/form_designer/js/lib/jquery.js rename to form_designer/media/form_designer/js/jquery.js diff --git a/form_designer/media/form_designer/js/lib/django-admin-tweaks-js-lib b/form_designer/media/form_designer/js/lib/django-admin-tweaks-js-lib deleted file mode 160000 index 6fddb31c..00000000 --- a/form_designer/media/form_designer/js/lib/django-admin-tweaks-js-lib +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6fddb31c46f1bd4a153d4814cf5c4c62884602cc diff --git a/form_designer/media/form_designer/js/lib/django-admin-tweaks-js-lib/README.md b/form_designer/media/form_designer/js/lib/django-admin-tweaks-js-lib/README.md new file mode 100644 index 00000000..5f8129fc --- /dev/null +++ b/form_designer/media/form_designer/js/lib/django-admin-tweaks-js-lib/README.md @@ -0,0 +1,4 @@ +django-admin-tweaks-js-lib +========================== + +Javascript Library for the Django admin_tweaks app, for internal use._ diff --git a/form_designer/media/form_designer/js/lib/django-admin-tweaks-js-lib/js/jquery-inline-collapsible.js b/form_designer/media/form_designer/js/lib/django-admin-tweaks-js-lib/js/jquery-inline-collapsible.js new file mode 100644 index 00000000..12d4fcf9 --- /dev/null +++ b/form_designer/media/form_designer/js/lib/django-admin-tweaks-js-lib/js/jquery-inline-collapsible.js @@ -0,0 +1,50 @@ +function makeCollapsible(target, item, collapsible, triggerTarget, setInitStatus, setFirstStatus) +{ + var triggerExpand = gettext('Show'); + var triggerCollapse = gettext('Hide'); + var triggerLink = ''; + var triggerPrepend = ' ( '; + var triggerAppend = ' )'; + + $(target).find(item).each(function(i) { + $(this).find(collapsible).slideUp(1); + + var trigger = $(triggerLink) + $(this).find(triggerTarget).append(trigger); + trigger.before(triggerPrepend); + trigger.after(triggerAppend); + var item = this + var toggleCollapse = function(status, speed) + { + if (status == null) { + status = !item.collapseStatus; + } + if (speed == null) { + speed = 1; + } + item.collapseStatus = status; + if (status) { + trigger.html(triggerCollapse); + $(item).find(collapsible).slideDown(speed); + } else { + trigger.html(triggerExpand); + $(item).find(collapsible).slideUp(speed); + } + } + + trigger.click(function(event) { + event.preventDefault(); + toggleCollapse(null, 'normal') + }) + + // Collapse by default unless there are errors + initStatus = setInitStatus != null ? setInitStatus : $(this).find('.errors').length != 0; + firstStatus = setFirstStatus != null ? setFirstStatus : initStatus; + + toggleCollapse(i == 0 ? firstStatus : initStatus) + }); +} + +jQuery(function($) { + makeCollapsible('div.inline-group', 'div.inline-related', 'fieldset', 'h3 b'); +}); diff --git a/form_designer/media/form_designer/js/lib/django-admin-tweaks-js-lib/js/jquery-inline-fieldset-collapsible.js b/form_designer/media/form_designer/js/lib/django-admin-tweaks-js-lib/js/jquery-inline-fieldset-collapsible.js new file mode 100644 index 00000000..c4b02044 --- /dev/null +++ b/form_designer/media/form_designer/js/lib/django-admin-tweaks-js-lib/js/jquery-inline-fieldset-collapsible.js @@ -0,0 +1,5 @@ +jQuery(function($) { + $('div.inline-related').each(function(i) { + makeCollapsible(this, 'fieldset', '.form-row', 'h2', null, true) + }); +}); diff --git a/form_designer/media/form_designer/js/lib/django-admin-tweaks-js-lib/js/jquery-inline-positioning.js b/form_designer/media/form_designer/js/lib/django-admin-tweaks-js-lib/js/jquery-inline-positioning.js new file mode 100644 index 00000000..295a8a74 --- /dev/null +++ b/form_designer/media/form_designer/js/lib/django-admin-tweaks-js-lib/js/jquery-inline-positioning.js @@ -0,0 +1,45 @@ +/* +Enables positioning of the inline elements by drag & drop. + +All the inline model requires is a "position" field that is blank by default. +This value will be set automatically by this code snippet when dragging elements. +The model can then be ordered by "position". +*/ + +jQuery(function($) { + + var positionField = $.scriptUrlParam ? $.scriptUrlParam(/jquery-inline-positioning\.js(\?.*)?$/, 'positionField', 'position') : 'position'; + var target = $('div.inline-group'); + var handle = 'h3 b'; + var item = 'div.inline-related'; + var positionInput = 'input[id$=-'+positionField+']'; + + target.find(item).each(function(i) { + $(this).find(handle).css('cursor', 'move'); + $(this).find(handle).addClass('draggable'); + $(this).find(positionInput).each(function() { + $(this)[0].readOnly = true; + }); + $(this).find('input, select, textarea').change(function() { + $(this).closest(item).find('input[id$='+positionField+']').val('X'); // mark for renumberAll() to fill in + renumberAll($('div.inline-group')); + }); + }); + + var renumberAll = function() { + target.find(item).each(function(i) { + if ($(this).find(positionInput).val() != '') { + $(this).find(positionInput).val(i+1); + } + }); + }; + + target.sortable({ + containment: 'parent', + /*zindex: 10, */ + items: item, + handle: handle, + update: renumberAll, + opacity: .75 + }); +}); diff --git a/form_designer/media/form_designer/js/lib/django-admin-tweaks-js-lib/js/jquery-inline-prepopulate-label.js b/form_designer/media/form_designer/js/lib/django-admin-tweaks-js-lib/js/jquery-inline-prepopulate-label.js new file mode 100644 index 00000000..ad9cba5a --- /dev/null +++ b/form_designer/media/form_designer/js/lib/django-admin-tweaks-js-lib/js/jquery-inline-prepopulate-label.js @@ -0,0 +1,29 @@ +/* +Does exactly what + prepopulated_fields = {"label" : ('name',)} +would do, but does not URLify the value (since in this case, name is a slug field but label is a title field) +*/ + + +jQuery(function($) { + + var target = $('div.inline-group'); + var item = 'div.inline-related'; + + $(target).find(item).each(function(i) { + var item = $(this); + item.find('input[id*=-label]').each(function() { + this._changed = item.find('input[id*=-name]').val() != $(this).val(); + }); + item.find('input[id*=-label]').change(function() { + this._changed = true; + }); + item.find('input[id*=-name]').keyup(function() { + labelInput = item.find('input[id*=-label]'); + if (!labelInput[0]._changed) { + labelInput.val($(this).val()); + } + }); + }); + +}); \ No newline at end of file diff --git a/form_designer/media/form_designer/js/lib/django-admin-tweaks-js-lib/js/jquery-inline-rename.js b/form_designer/media/form_designer/js/lib/django-admin-tweaks-js-lib/js/jquery-inline-rename.js new file mode 100644 index 00000000..3c55b14e --- /dev/null +++ b/form_designer/media/form_designer/js/lib/django-admin-tweaks-js-lib/js/jquery-inline-rename.js @@ -0,0 +1,44 @@ +/* +Replaces the name in an inline element's header while typing it in the input of the "name" field. +This way, the extra inline element's header will be named instead of numbered #4, #5 etc. +*/ + +jQuery(function($) { + + var nameField = $.scriptUrlParam ? $.scriptUrlParam(/jquery-inline-rename\.js(\?.*)?$/, 'nameField', 'name') : 'name'; + var target = $('div.inline-group'); + var item = 'div.inline-related'; + var containsName = 'h3'; + var beforeName = 'h3 > *:first'; + var afterName = 'h3 > *:last'; + var nameInput = 'input[id*=-'+nameField+']'; + + target.find(item).each(function() { + // The following code is due to the fact that the inline + // element's name is (lamentably) not wrapped in a tag. + + // 1. Strip everything before and after name + var stripBefore = $(this).find(beforeName).remove(); + var stripAfter = $(this).find(afterName).remove(); + // 2. Now we can get the name (text node) + var nameParent = $(this).find(containsName); + var name = $(nameParent).html(); + // 3. Strip leading whitespace including   + name = name.replace( /^(\s| )+/g, "" ) + // 4. Wrap name in tag + name = $(''+name+''); + // 5. Re-insert it with a space before + $(nameParent).html(name); + $(name).before(' '); + // 6. Restore everything before and after name + $(nameParent).prepend(stripBefore); + $(nameParent).append(stripAfter); + + // Update name while typing + $(this).find(nameInput).keyup(function(event) { + name.html($(this).val()); + }) + }) + + +}); diff --git a/form_designer/media/form_designer/js/lib/django-admin-tweaks-js-lib/js/jquery-url-param.js b/form_designer/media/form_designer/js/lib/django-admin-tweaks-js-lib/js/jquery-url-param.js new file mode 100644 index 00000000..7572a82b --- /dev/null +++ b/form_designer/media/form_designer/js/lib/django-admin-tweaks-js-lib/js/jquery-url-param.js @@ -0,0 +1,17 @@ +$.urlParam = function(name, defaultValue, url) { + if (!url) { + url = window.location.href + } + var results = new RegExp('[\\?&]'+name+'=([^&#]*)').exec(url); + return results ? results[1] : defaultValue; +} + +$.scriptUrlParam = function(js, name, defaultValue) { + result = defaultValue; + $('head script[src]').each(function() { + if (this.src.match(js)) { + result = $.urlParam(name, result, this.src); + } + }); + return result; +} diff --git a/form_designer/migrations/0001_initial.py b/form_designer/migrations/0001_initial.py new file mode 100644 index 00000000..59d6b3c6 --- /dev/null +++ b/form_designer/migrations/0001_initial.py @@ -0,0 +1,175 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding model 'FormDefinition' + db.create_table('form_designer_formdefinition', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('name', self.gf('django.db.models.fields.SlugField')(unique=True, max_length=255, db_index=True)), + ('title', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)), + ('action', self.gf('django.db.models.fields.URLField')(max_length=255, null=True, blank=True)), + ('mail_to', self.gf('form_designer.fields.TemplateCharField')(max_length=255, null=True, blank=True)), + ('mail_from', self.gf('form_designer.fields.TemplateCharField')(max_length=255, null=True, blank=True)), + ('mail_subject', self.gf('form_designer.fields.TemplateCharField')(max_length=255, null=True, blank=True)), + ('method', self.gf('django.db.models.fields.CharField')(default='POST', max_length=10)), + ('success_message', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)), + ('error_message', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)), + ('submit_label', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)), + ('log_data', self.gf('django.db.models.fields.BooleanField')(default=True, blank=True)), + ('success_redirect', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True)), + ('success_clear', self.gf('django.db.models.fields.BooleanField')(default=True, blank=True)), + ('allow_get_initial', self.gf('django.db.models.fields.BooleanField')(default=True, blank=True)), + ('message_template', self.gf('form_designer.fields.TemplateTextField')(null=True, blank=True)), + ('form_template_name', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)), + )) + db.send_create_signal('form_designer', ['FormDefinition']) + + # Adding model 'FormLog' + db.create_table('form_designer_formlog', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)), + ('form_definition', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['form_designer.FormDefinition'])), + ('data', self.gf('picklefield.fields.PickledObjectField')(null=True, blank=True)), + )) + db.send_create_signal('form_designer', ['FormLog']) + + # Adding model 'FormDefinitionField' + db.create_table('form_designer_formdefinitionfield', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('form_definition', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['form_designer.FormDefinition'])), + ('field_class', self.gf('django.db.models.fields.CharField')(max_length=32)), + ('position', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)), + ('name', self.gf('django.db.models.fields.SlugField')(max_length=255, db_index=True)), + ('label', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)), + ('required', self.gf('django.db.models.fields.BooleanField')(default=True, blank=True)), + ('include_result', self.gf('django.db.models.fields.BooleanField')(default=True, blank=True)), + ('widget', self.gf('django.db.models.fields.CharField')(default='', max_length=255, null=True, blank=True)), + ('initial', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)), + ('help_text', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)), + ('choice_values', self.gf('django.db.models.fields.TextField')(null=True, blank=True)), + ('choice_labels', self.gf('django.db.models.fields.TextField')(null=True, blank=True)), + ('max_length', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)), + ('min_length', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)), + ('max_value', self.gf('django.db.models.fields.FloatField')(null=True, blank=True)), + ('min_value', self.gf('django.db.models.fields.FloatField')(null=True, blank=True)), + ('max_digits', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)), + ('decimal_places', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)), + ('regex', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)), + ('choice_model', self.gf('form_designer.fields.ModelNameField')(max_length=255, null=True, blank=True)), + ('choice_model_empty_label', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)), + )) + db.send_create_signal('form_designer', ['FormDefinitionField']) + + # Adding model 'CMSFormDefinition' + db.create_table('cmsplugin_cmsformdefinition', ( + ('cmsplugin_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['cms.CMSPlugin'], unique=True, primary_key=True)), + ('form_definition', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['form_designer.FormDefinition'])), + )) + db.send_create_signal('form_designer', ['CMSFormDefinition']) + + + def backwards(self, orm): + + # Deleting model 'FormDefinition' + db.delete_table('form_designer_formdefinition') + + # Deleting model 'FormLog' + db.delete_table('form_designer_formlog') + + # Deleting model 'FormDefinitionField' + db.delete_table('form_designer_formdefinitionfield') + + # Deleting model 'CMSFormDefinition' + db.delete_table('cmsplugin_cmsformdefinition') + + + models = { + 'cms.cmsplugin': { + 'Meta': {'object_name': 'CMSPlugin'}, + 'creation_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'language': ('django.db.models.fields.CharField', [], {'max_length': '5', 'db_index': 'True'}), + 'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), + 'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.CMSPlugin']", 'null': 'True', 'blank': 'True'}), + 'placeholder': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.Placeholder']", 'null': 'True'}), + 'plugin_type': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}), + 'position': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'publisher_is_draft': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True', 'blank': 'True'}), + 'publisher_public': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'publisher_draft'", 'unique': 'True', 'null': 'True', 'to': "orm['cms.CMSPlugin']"}), + 'publisher_state': ('django.db.models.fields.SmallIntegerField', [], {'default': '0', 'db_index': 'True'}), + 'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), + 'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}) + }, + 'cms.placeholder': { + 'Meta': {'object_name': 'Placeholder'}, + 'default_width': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'slot': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}) + }, + 'form_designer.cmsformdefinition': { + 'Meta': {'object_name': 'CMSFormDefinition', 'db_table': "'cmsplugin_cmsformdefinition'", '_ormbases': ['cms.CMSPlugin']}, + 'cmsplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.CMSPlugin']", 'unique': 'True', 'primary_key': 'True'}), + 'form_definition': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['form_designer.FormDefinition']"}) + }, + 'form_designer.formdefinition': { + 'Meta': {'object_name': 'FormDefinition'}, + 'action': ('django.db.models.fields.URLField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'allow_get_initial': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}), + 'error_message': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'form_template_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'log_data': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}), + 'mail_from': ('form_designer.fields.TemplateCharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'mail_subject': ('form_designer.fields.TemplateCharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'mail_to': ('form_designer.fields.TemplateCharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'message_template': ('form_designer.fields.TemplateTextField', [], {'null': 'True', 'blank': 'True'}), + 'method': ('django.db.models.fields.CharField', [], {'default': "'POST'", 'max_length': '10'}), + 'name': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'}), + 'submit_label': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'success_clear': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}), + 'success_message': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'success_redirect': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}) + }, + 'form_designer.formdefinitionfield': { + 'Meta': {'object_name': 'FormDefinitionField'}, + 'choice_labels': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'choice_model': ('form_designer.fields.ModelNameField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'choice_model_empty_label': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'choice_values': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'decimal_places': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'field_class': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'form_definition': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['form_designer.FormDefinition']"}), + 'help_text': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'include_result': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}), + 'initial': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'max_digits': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'max_length': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'max_value': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + 'min_length': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'min_value': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'db_index': 'True'}), + 'position': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'regex': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'required': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}), + 'widget': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255', 'null': 'True', 'blank': 'True'}) + }, + 'form_designer.formlog': { + 'Meta': {'object_name': 'FormLog'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'data': ('picklefield.fields.PickledObjectField', [], {'null': 'True', 'blank': 'True'}), + 'form_definition': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['form_designer.FormDefinition']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + } + } + + complete_apps = ['form_designer'] diff --git a/form_designer/migrations/0002_auto__chg_field_formdefinitionfield_initial.py b/form_designer/migrations/0002_auto__chg_field_formdefinitionfield_initial.py new file mode 100644 index 00000000..5fa38e27 --- /dev/null +++ b/form_designer/migrations/0002_auto__chg_field_formdefinitionfield_initial.py @@ -0,0 +1,104 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Changing field 'FormDefinitionField.initial' + db.alter_column('form_designer_formdefinitionfield', 'initial', self.gf('django.db.models.fields.TextField')(null=True, blank=True)) + + + def backwards(self, orm): + + # Changing field 'FormDefinitionField.initial' + db.alter_column('form_designer_formdefinitionfield', 'initial', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)) + + + models = { + 'cms.cmsplugin': { + 'Meta': {'object_name': 'CMSPlugin'}, + 'creation_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'language': ('django.db.models.fields.CharField', [], {'max_length': '5', 'db_index': 'True'}), + 'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), + 'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.CMSPlugin']", 'null': 'True', 'blank': 'True'}), + 'placeholder': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.Placeholder']", 'null': 'True'}), + 'plugin_type': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}), + 'position': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'publisher_is_draft': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True', 'blank': 'True'}), + 'publisher_public': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'publisher_draft'", 'unique': 'True', 'null': 'True', 'to': "orm['cms.CMSPlugin']"}), + 'publisher_state': ('django.db.models.fields.SmallIntegerField', [], {'default': '0', 'db_index': 'True'}), + 'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), + 'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}) + }, + 'cms.placeholder': { + 'Meta': {'object_name': 'Placeholder'}, + 'default_width': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'slot': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}) + }, + 'form_designer.cmsformdefinition': { + 'Meta': {'object_name': 'CMSFormDefinition', 'db_table': "'cmsplugin_cmsformdefinition'", '_ormbases': ['cms.CMSPlugin']}, + 'cmsplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.CMSPlugin']", 'unique': 'True', 'primary_key': 'True'}), + 'form_definition': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['form_designer.FormDefinition']"}) + }, + 'form_designer.formdefinition': { + 'Meta': {'object_name': 'FormDefinition'}, + 'action': ('django.db.models.fields.URLField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'allow_get_initial': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}), + 'error_message': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'form_template_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'log_data': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}), + 'mail_from': ('form_designer.fields.TemplateCharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'mail_subject': ('form_designer.fields.TemplateCharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'mail_to': ('form_designer.fields.TemplateCharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'message_template': ('form_designer.fields.TemplateTextField', [], {'null': 'True', 'blank': 'True'}), + 'method': ('django.db.models.fields.CharField', [], {'default': "'POST'", 'max_length': '10'}), + 'name': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'}), + 'submit_label': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'success_clear': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}), + 'success_message': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'success_redirect': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}) + }, + 'form_designer.formdefinitionfield': { + 'Meta': {'object_name': 'FormDefinitionField'}, + 'choice_labels': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'choice_model': ('form_designer.fields.ModelNameField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'choice_model_empty_label': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'choice_values': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'decimal_places': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'field_class': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'form_definition': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['form_designer.FormDefinition']"}), + 'help_text': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'include_result': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}), + 'initial': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'max_digits': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'max_length': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'max_value': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + 'min_length': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'min_value': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'db_index': 'True'}), + 'position': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'regex': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'required': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}), + 'widget': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255', 'null': 'True', 'blank': 'True'}) + }, + 'form_designer.formlog': { + 'Meta': {'object_name': 'FormLog'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'data': ('picklefield.fields.PickledObjectField', [], {'null': 'True', 'blank': 'True'}), + 'form_definition': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['form_designer.FormDefinition']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + } + } + + complete_apps = ['form_designer'] diff --git a/form_designer/migrations/__init__.py b/form_designer/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/form_designer/models.py b/form_designer/models.py index 12d78a55..767de563 100644 --- a/form_designer/models.py +++ b/form_designer/models.py @@ -1,13 +1,34 @@ +import re + from django.db import models from django.utils.translation import ugettext, ugettext_lazy as _ from django.forms import widgets from django.core.mail import send_mail -from django.conf import settings -from form_designer import app_settings -import re -from pickled_object_field import PickledObjectField -from model_name_field import ModelNameField -from template_field import TemplateTextField, TemplateCharField +from django.conf import settings as django_settings +from django.core.exceptions import ImproperlyConfigured +from django.utils.importlib import import_module + +from picklefield.fields import PickledObjectField + +from form_designer.fields import TemplateTextField, TemplateCharField, ModelNameField +from form_designer import settings + +def get_class(import_path): + try: + dot = import_path.rindex('.') + except ValueError: + raise ImproperlyConfigured("%s isn't a Python path." % import_path) + module, classname = import_path[:dot], import_path[dot + 1:] + try: + mod = import_module(module) + except ImportError, e: + raise ImproperlyConfigured('Error importing module %s: "%s"' % + (module, e)) + try: + return getattr(mod, classname) + except AttributeError: + raise ImproperlyConfigured('Module "%s" does not define a "%s" ' + 'class.' % (module, classname)) class FormDefinition(models.Model): name = models.SlugField(_('Name'), max_length=255, unique=True) @@ -21,11 +42,11 @@ class FormDefinition(models.Model): error_message = models.CharField(_('Error message'), max_length=255, blank=True, null=True) submit_label = models.CharField(_('Submit button label'), max_length=255, blank=True, null=True) log_data = models.BooleanField(_('Log form data'), help_text=_('Logs all form submissions to the database.'), default=True) - success_redirect = models.BooleanField(_('Redirect after success'), help_text=_('You should install django_notify if you want to enable this.') if not 'django_notify' in settings.INSTALLED_APPS else None, default=False) + success_redirect = models.BooleanField(_('Redirect after success'), default=False) success_clear = models.BooleanField(_('Clear form after success'), default=True) allow_get_initial = models.BooleanField(_('Allow initial values via URL'), help_text=_('If enabled, you can fill in form fields by adding them to the query string.'), default=True) message_template = TemplateTextField(_('Message template'), help_text=_('Your form fields are available as template context. Example: "{{ message }}" if you have a field named `message`. To iterate over all fields, use the variable `data` (a list containing a dictionary for each form field, each containing the elements `name`, `label`, `value`).'), blank=True, null=True) - form_template_name = models.CharField(_('Form template'), max_length=255, choices=app_settings.get('FORM_DESIGNER_FORM_TEMPLATES'), blank=True, null=True) + form_template_name = models.CharField(_('Form template'), max_length=255, choices=settings.FORM_TEMPLATES, blank=True, null=True) class Meta: verbose_name = _('Form') @@ -36,7 +57,7 @@ def get_field_dict(self): for field in self.formdefinitionfield_set.all(): dict[field.name] = field return dict - + def get_form_data(self, form): data = [] field_dict = self.get_field_dict() @@ -49,7 +70,7 @@ def get_form_data(self, form): value = value.__form_data__() data.append({'name': key, 'label': form.fields[key].label, 'value': value}) return data - + def get_form_data_dict(self, form_data): dict = {} for field in form_data: @@ -90,34 +111,39 @@ def string_template_replace(self, text, context_dict): except TemplateSyntaxError: return text - def send_mail(self, form): + def send_mail(self, form, files=[]): form_data = self.get_form_data(form) message = self.compile_message(form_data) context_dict = self.get_form_data_dict(form_data) - import re + import re mail_to = re.compile('\s*[,;]+\s*').split(self.mail_to) for key, email in enumerate(mail_to): mail_to[key] = self.string_template_replace(email, context_dict) - + mail_from = self.mail_from or None if mail_from: mail_from = self.string_template_replace(mail_from, context_dict) - + if self.mail_subject: mail_subject = self.string_template_replace(self.mail_subject, context_dict) else: mail_subject = self.title - + import logging logging.debug('Mail: '+repr(mail_from)+' --> '+repr(mail_to)); - - from django.core.mail import send_mail - send_mail(mail_subject, message, mail_from or None, mail_to, fail_silently=False) + + from django.core.mail import EmailMessage + message = EmailMessage(mail_subject, message, mail_from or None, mail_to) + + for file_path in files: + message.attach_file(file_path) + + message.send(fail_silently=False) @property def submit_flag_name(self): - name = app_settings.get('FORM_DESIGNER_SUBMIT_FLAG_NAME') % self.name + name = settings.SUBMIT_FLAG_NAME % self.name while self.formdefinitionfield_set.filter(name__exact=name).count() > 0: name += '_' return name @@ -135,14 +161,14 @@ class Meta: class FormDefinitionField(models.Model): form_definition = models.ForeignKey(FormDefinition) - field_class = models.CharField(_('Field class'), choices=app_settings.get('FORM_DESIGNER_FIELD_CLASSES'), max_length=32) + field_class = models.CharField(_('Field class'), choices=settings.FIELD_CLASSES, max_length=32) position = models.IntegerField(_('Position'), blank=True, null=True) name = models.SlugField(_('Name'), max_length=255) label = models.CharField(_('Label'), max_length=255, blank=True, null=True) required = models.BooleanField(_('Required'), default=True) include_result = models.BooleanField(_('Include in result'), help_text=('If this is disabled, the field value will not be included in logs and e-mails generated from form data.'), default=True) - widget = models.CharField(_('Widget'), default='', choices=app_settings.get('FORM_DESIGNER_WIDGET_CLASSES'), max_length=255, blank=True, null=True) + widget = models.CharField(_('Widget'), default='', choices=settings.WIDGET_CLASSES, max_length=255, blank=True, null=True) initial = models.TextField(_('Initial value'), blank=True, null=True) help_text = models.CharField(_('Help text'), max_length=255, blank=True, null=True) @@ -158,7 +184,7 @@ class FormDefinitionField(models.Model): regex = models.CharField(_('Regular Expression'), max_length=255, blank=True, null=True) - choice_model_choices = app_settings.get('FORM_DESIGNER_CHOICE_MODEL_CHOICES') + choice_model_choices = settings.CHOICE_MODEL_CHOICES choice_model = ModelNameField(_('Data model'), max_length=255, blank=True, null=True, choices=choice_model_choices, help_text=('your_app.models.ModelName' if not choice_model_choices else None)) choice_model_empty_label = models.CharField(_('Empty label'), max_length=255, blank=True, null=True) @@ -174,7 +200,7 @@ def save(self): def ____init__(self, field_class=None, name=None, required=None, widget=None, label=None, initial=None, help_text=None, *args, **kwargs): super(FormDefinitionField, self).__init__(*args, **kwargs) self.name = name - self.field_class = field_class + self.field_class = field_class self.required = required self.widget = widget self.label = label @@ -188,20 +214,20 @@ def get_form_field_init_args(self): 'initial': self.initial if self.initial else None, 'help_text': self.help_text, } - - if self.field_class in ('forms.CharField', 'forms.EmailField', 'forms.RegexField'): + + if self.field_class in ('django.forms.CharField', 'django.forms.EmailField', 'django.forms.RegexField'): args.update({ 'max_length': self.max_length, 'min_length': self.min_length, }) - if self.field_class in ('forms.IntegerField', 'forms.DecimalField'): + if self.field_class in ('django.forms.IntegerField', 'django.forms.DecimalField'): args.update({ 'max_value': int(self.max_value) if self.max_value != None else None, 'min_value': int(self.min_value) if self.min_value != None else None, }) - if self.field_class == 'forms.DecimalField': + if self.field_class == 'django.forms.DecimalField': args.update({ 'max_value': self.max_value, 'min_value': self.min_value, @@ -209,13 +235,13 @@ def get_form_field_init_args(self): 'decimal_places': self.decimal_places, }) - if self.field_class == 'forms.RegexField': + if self.field_class == 'django.forms.RegexField': if self.regex: args.update({ 'regex': self.regex }) - if self.field_class in ('forms.ChoiceField', 'forms.MultipleChoiceField'): + if self.field_class in ('django.forms.ChoiceField', 'django.forms.MultipleChoiceField'): if self.choice_values: choices = [] regex = re.compile('[\s]*\n[\s]*') @@ -231,21 +257,21 @@ def get_form_field_init_args(self): 'choices': tuple(choices) }) - if self.field_class in ('forms.ModelChoiceField', 'forms.ModelMultipleChoiceField'): + if self.field_class in ('django.forms.ModelChoiceField', 'django.forms.ModelMultipleChoiceField'): args.update({ 'queryset': ModelNameField.get_model_from_string(self.choice_model).objects.all() }) - - if self.field_class == 'forms.ModelChoiceField': + + if self.field_class == 'django.forms.ModelChoiceField': args.update({ 'empty_label': self.choice_model_empty_label }) if self.widget: args.update({ - 'widget': eval(self.widget)() + 'widget': get_class(self.widget)() }) - + return args class Meta: @@ -256,7 +282,7 @@ class Meta: def __unicode__(self): return self.label if self.label else self.name -if 'cms' in settings.INSTALLED_APPS: +if 'cms' in django_settings.INSTALLED_APPS: from cms.models import CMSPlugin class CMSFormDefinition(CMSPlugin): @@ -264,3 +290,7 @@ class CMSFormDefinition(CMSPlugin): def __unicode__(self): return self.form_definition.__unicode__() + +if 'south' in django_settings.INSTALLED_APPS: + from south.modelsinspector import add_introspection_rules + add_introspection_rules([], ["^form_designer\.fields\..*"]) diff --git a/form_designer/pickled_object_field.py b/form_designer/pickled_object_field.py deleted file mode 100644 index 8ccd8ff6..00000000 --- a/form_designer/pickled_object_field.py +++ /dev/null @@ -1,25 +0,0 @@ -# http://www.smipple.net/snippet/IanLewis/Django%20Pickled%20Object%20Field - -try: - import cPickle as pickle -except ImportError: - import pickle - -import base64 - -from django.db import models - -class PickledObjectField(models.TextField): - __metaclass__ = models.SubfieldBase - - def to_python(self, value): - if value is None: return None - if not isinstance(value, basestring): return value - try: - return pickle.loads(base64.b64decode(value)) - except: - return None - - def get_db_prep_save(self, value): - if value is None: return - return base64.b64encode(pickle.dumps(value)) diff --git a/form_designer/settings.py b/form_designer/settings.py new file mode 100644 index 00000000..c2ee38a2 --- /dev/null +++ b/form_designer/settings.py @@ -0,0 +1,65 @@ +from django.conf import settings +from django.utils.translation import ugettext_lazy as _ + +MEDIA_URL = getattr(settings, 'FORM_DESIGNER_MEDIA_URL', '%sform_designer/' % settings.MEDIA_URL) + +FIELD_CLASSES = getattr(settings, 'FORM_DESIGNER_FIELD_CLASSES', ( + ('django.forms.CharField', _('Text')), + ('django.forms.EmailField', _('E-mail address')), + ('django.forms.URLField', _('Web address')), + ('django.forms.IntegerField', _('Number')), + ('django.forms.DecimalField', _('Decimal number')), + ('django.forms.BooleanField', _('Yes/No')), + ('django.forms.DateField', _('Date')), + ('django.forms.DateTimeField', _('Date & time')), + ('django.forms.TimeField', _('Time')), + ('django.forms.ChoiceField', _('Choice')), + ('django.forms.MultipleChoiceField', _('Multiple Choice')), + ('django.forms.ModelChoiceField', _('Model Choice')), + ('django.forms.ModelMultipleChoiceField', _('Model Multiple Choice')), + ('django.forms.RegexField', _('Regex')), + ('django.forms.FileField', _('File')), + # ('captcha.fields.CaptchaField', _('Captcha')), +)) + +WIDGET_CLASSES = getattr(settings, 'FORM_DESIGNER_WIDGET_CLASSES', ( + ('', _('Default')), + ('django.forms.widgets.Textarea', _('Text area')), + ('django.forms.widgets.PasswordInput', _('Password input')), + ('django.forms.widgets.HiddenInput', _('Hidden input')), + ('django.forms.widgets.RadioSelect', _('Radio button')), +)) + +FORM_TEMPLATES = getattr(settings, 'FORM_DESIGNER_FORM_TEMPLATES', ( + ('', _('Default')), + ('html/formdefinition/forms/as_p.html', _('as paragraphs')), + ('html/formdefinition/forms/as_table.html', _('as table')), + ('html/formdefinition/forms/as_ul.html', _('as unordered list')), + ('html/formdefinition/forms/custom.html', _('custom implementation')), +)) + +# Sequence of two-tuples like (('your_app.models.ModelName', 'My Model'), ...) for limiting the models available to ModelChoiceField and ModelMultipleChoiceField. +# If None, any model can be chosen by entering it as a string +CHOICE_MODEL_CHOICES = getattr(settings, 'FORM_DESIGNER_CHOICE_MODEL_CHOICES', None) + +DEFAULT_FORM_TEMPLATE = getattr(settings, 'FORM_DESIGNER_DEFAULT_FORM_TEMPLATE', 'html/formdefinition/forms/as_p.html') + +# semicolon is Microsoft Excel default +CSV_EXPORT_DELIMITER = getattr(settings, 'FORM_DESIGNER_CSV_EXPORT_DELIMITER', ';') + +# include log timestamp in export +CSV_EXPORT_INCLUDE_CREATED = getattr(settings, 'FORM_DESIGNER_CSV_EXPORT_INCLUDE_CREATED', True) + +CSV_EXPORT_INCLUDE_PK = getattr(settings, 'FORM_DESIGNER_CSV_EXPORT_INCLUDE_PK', True) + +# include field labels/names in first row if exporting logs for one form only +CSV_EXPORT_INCLUDE_HEADER = getattr(settings, 'FORM_DESIGNER_CSV_EXPORT_INCLUDE_HEADER', True) + +# include form title if exporting logs for more than one form +CSV_EXPORT_INCLUDE_FORM = getattr(settings, 'FORM_DESIGNER_CSV_EXPORT_INCLUDE_FORM', True) + +CSV_EXPORT_FILENAME = getattr(settings, 'FORM_DESIGNER_CSV_EXPORT_FILENAME', 'export.csv') + +CSV_EXPORT_ENCODING = getattr(settings, 'FORM_DESIGNER_CSV_EXPORT_ENCODING', 'utf-8') + +SUBMIT_FLAG_NAME = getattr(settings, 'FORM_DESIGNER_SUBMIT_FLAG_NAME', 'submit__%s') diff --git a/form_designer/template_field.py b/form_designer/template_field.py deleted file mode 100644 index a812d1ec..00000000 --- a/form_designer/template_field.py +++ /dev/null @@ -1,34 +0,0 @@ -from django.db import models -from django import forms - -class TemplateFormField(forms.CharField): - - def clean(self, value): - """ - Validates that the input can be compiled as a template. - """ - value = super(TemplateFormField, self).clean(value) - from django.template import Template, TemplateSyntaxError - try: - Template(value) - except TemplateSyntaxError as error: - raise forms.ValidationError(error) - return value - -class TemplateCharField(models.CharField): - - def formfield(self, **kwargs): - # This is a fairly standard way to set up some defaults - # while letting the caller override them. - defaults = {'form_class': TemplateFormField} - defaults.update(kwargs) - return super(TemplateCharField, self).formfield(**defaults) - -class TemplateTextField(models.TextField): - - def formfield(self, **kwargs): - # This is a fairly standard way to set up some defaults - # while letting the caller override them. - defaults = {'form_class': TemplateFormField} - defaults.update(kwargs) - return super(TemplateTextField, self).formfield(**defaults) diff --git a/form_designer/templates/admin/form_designer/formlog/change_list.html b/form_designer/templates/admin/form_designer/formlog/change_list.html index 84e0e8a3..6ad372c0 100644 --- a/form_designer/templates/admin/form_designer/formlog/change_list.html +++ b/form_designer/templates/admin/form_designer/formlog/change_list.html @@ -10,6 +10,13 @@ {% endif %} + {% if export_xls_url %} +
  • + + {% trans "Export XLS" %} + +
  • + {% endif %} {% endblock %} diff --git a/form_designer/templates/html/formdefinition/forms/as_p.html b/form_designer/templates/html/formdefinition/forms/as_p.html new file mode 100644 index 00000000..57a046b9 --- /dev/null +++ b/form_designer/templates/html/formdefinition/forms/as_p.html @@ -0,0 +1,12 @@ +{% load i18n %} +

    {{ form_definition.title }}

    +{% if message %} +
      +
    • {{ message }}
    • +
    +{% endif %} +
    +{% csrf_token %} +{{ form.as_p }} + +
    diff --git a/form_designer/templates/html/formdefinition/forms/as_table.html b/form_designer/templates/html/formdefinition/forms/as_table.html new file mode 100644 index 00000000..f9f6623f --- /dev/null +++ b/form_designer/templates/html/formdefinition/forms/as_table.html @@ -0,0 +1,12 @@ +{% load i18n %} +

    {{ form_definition.title }}

    +{% if message %} +
      +
    • {{ message }}
    • +
    +{% endif %} +
    +{% csrf_token %} +{{ form.as_table }} + +
    diff --git a/form_designer/templates/html/formdefinition/forms/as_u.html b/form_designer/templates/html/formdefinition/forms/as_u.html new file mode 100644 index 00000000..fbdaafa3 --- /dev/null +++ b/form_designer/templates/html/formdefinition/forms/as_u.html @@ -0,0 +1,12 @@ +{% load i18n %} +

    {{ form_definition.title }}

    +{% if message %} +
      +
    • {{ message }}
    • +
    +{% endif %} +
    +{% csrf_token %} +{{ form.as_ul }} + +
    \ No newline at end of file diff --git a/form_designer/templates/html/formdefinition/forms/as_ul.html b/form_designer/templates/html/formdefinition/forms/as_ul.html new file mode 100644 index 00000000..418d5d95 --- /dev/null +++ b/form_designer/templates/html/formdefinition/forms/as_ul.html @@ -0,0 +1,12 @@ +{% load i18n %} +

    {{ form_definition.title }}

    +{% if message %} +
      +
    • {{ message }}
    • +
    +{% endif %} +
    +{% csrf_token %} +{{ form.as_ul }} + +
    diff --git a/form_designer/templates/html/formdefinition/forms/custom.html b/form_designer/templates/html/formdefinition/forms/custom.html new file mode 100644 index 00000000..313a357d --- /dev/null +++ b/form_designer/templates/html/formdefinition/forms/custom.html @@ -0,0 +1,42 @@ +{% load i18n %} +{% load widget_type %} + +{% if message %} +
      +
    • {{ message }}
    • +
    +{% endif %} + + +
    +{% csrf_token %} +
    + {% if form_definition.title %} + {{ form_definition.title }} + {% endif %} + + {% for item in form.visible_fields %} + + {% if forloop.first %} + {% for hidden in form.hidden_fields %} + {{ hidden }} + {% endfor %} + {% endif %} + + + + {% endfor %} + +
    +
    \ No newline at end of file diff --git a/form_designer/templates/txt/formdefinition/data_message.txt b/form_designer/templates/txt/formdefinition/data_message.txt index c5cc8fe2..248e62b6 100644 --- a/form_designer/templates/txt/formdefinition/data_message.txt +++ b/form_designer/templates/txt/formdefinition/data_message.txt @@ -1,2 +1,3 @@ -{% load friendly %}{% for item in data %}{% if item.label %}{{ item.label }}{% else %}{{ item.name }}{% endif %}: {{ item.value|friendly }} +{% load friendly %}{% for item in data %}{% if item.label %}{{ item.label }}{% else %}{{ item.name }}{% endif %}: {{ item.value|friendly|safe }} + {% endfor %} \ No newline at end of file diff --git a/form_designer/templatetags/friendly.py b/form_designer/templatetags/friendly.py index 09206a9a..ab65bf76 100644 --- a/form_designer/templatetags/friendly.py +++ b/form_designer/templatetags/friendly.py @@ -3,11 +3,13 @@ from django.utils.translation import ugettext_lazy as _ from django.template.defaultfilters import yesno +register = template.Library() + # Returns a more "human-friendly" representation of value than repr() def friendly(value): if type(value) is QuerySet: qs = value - value = [] + value = [] for object in qs: value.append(object.__unicode__()) if type(value) is list: @@ -18,5 +20,4 @@ def friendly(value): value = unicode(value) return value -register = template.Library() register.filter(friendly) \ No newline at end of file diff --git a/form_designer/templatetags/widget_type.py b/form_designer/templatetags/widget_type.py new file mode 100644 index 00000000..45803cf9 --- /dev/null +++ b/form_designer/templatetags/widget_type.py @@ -0,0 +1,6 @@ +from django import template +register = template.Library() + +@register.filter('field_type') +def field_type(obj): + return obj.__class__.__name__ diff --git a/form_designer/tests.py b/form_designer/tests.py deleted file mode 100644 index 2247054b..00000000 --- a/form_designer/tests.py +++ /dev/null @@ -1,23 +0,0 @@ -""" -This file demonstrates two different styles of tests (one doctest and one -unittest). These will both pass when you run "manage.py test". - -Replace these with more appropriate tests for your application. -""" - -from django.test import TestCase - -class SimpleTest(TestCase): - def test_basic_addition(self): - """ - Tests that 1 + 1 always equals 2. - """ - self.failUnlessEqual(1 + 1, 2) - -__test__ = {"doctest": """ -Another way to test that 1 + 1 is equal to 2. - ->>> 1 + 1 == 2 -True -"""} - diff --git a/form_designer/urls.py b/form_designer/urls.py index e0d46a6e..2e915f2d 100644 --- a/form_designer/urls.py +++ b/form_designer/urls.py @@ -1,4 +1,4 @@ -from django.conf.urls.defaults import * +from django.conf.urls.defaults import patterns, url urlpatterns = patterns('', url(r'^(?P[-\w]+)/$', 'form_designer.views.detail', name='form_designer_detail'), diff --git a/form_designer/views.py b/form_designer/views.py index 09cfded8..336a2e87 100644 --- a/form_designer/views.py +++ b/form_designer/views.py @@ -1,64 +1,74 @@ from django.shortcuts import get_object_or_404, render_to_response from django.template import RequestContext -from django.db import models -from form_designer.models import FormDefinition from django.utils.translation import ugettext as _ -from django import forms -from django.forms import widgets from django.http import HttpResponseRedirect from django.conf import settings -from form_designer import app_settings +from django.contrib import messages +from django.core.context_processors import csrf -class DesignedForm(forms.Form): - def __init__(self, form_definition, initial_data=None, *args, **kwargs): - super(DesignedForm, self).__init__(*args, **kwargs) - for def_field in form_definition.formdefinitionfield_set.all(): - self.add_defined_field(def_field, initial_data) - self.fields[form_definition.submit_flag_name] = forms.BooleanField(required=False, initial=1, widget=widgets.HiddenInput) +import os +import random +from datetime import datetime - def add_defined_field(self, def_field, initial_data=None): - if initial_data and initial_data.has_key(def_field.name): - if not def_field.field_class in ('forms.MultipleChoiceField', 'forms.ModelMultipleChoiceField'): - def_field.initial = initial_data.get(def_field.name) - else: - def_field.initial = initial_data.getlist(def_field.name) - self.fields[def_field.name] = eval(def_field.field_class)(**def_field.get_form_field_init_args()) +from form_designer.forms import DesignedForm +from form_designer.models import FormDefinition def process_form(request, form_definition, context={}, is_cms_plugin=False): success_message = form_definition.success_message or _('Thank you, the data was submitted successfully.') error_message = form_definition.error_message or _('The data could not be submitted, please try again.') message = None - + form_error = False + form_success = False is_submit = False # If the form has been submitted... if request.method == 'POST' and request.POST.get(form_definition.submit_flag_name): - form = DesignedForm(form_definition, None, request.POST) + form = DesignedForm(form_definition, None, request.POST, request.FILES) is_submit = True if request.method == 'GET' and request.GET.get(form_definition.submit_flag_name): form = DesignedForm(form_definition, None, request.GET) is_submit = True - + if is_submit: if form.is_valid(): + # Handle file uploads + files = [] + if hasattr(request, 'FILES'): + for file_key in request.FILES: + file_obj = request.FILES[file_key] + file_name = '%s.%s_%s' % ( + datetime.now().strftime('%Y%m%d'), + random.randrange(0, 10000), + file_obj.name, + ) + + if not os.path.exists(os.path.join(settings.MEDIA_ROOT, 'contact_form')): + os.mkdir(os.path.join(settings.MEDIA_ROOT, 'contact_form')) + + destination = open(os.path.join(settings.MEDIA_ROOT, 'contact_form', file_name), 'wb+') + for chunk in file_obj.chunks(): + destination.write(chunk) + destination.close() + + form.cleaned_data[file_key] = os.path.join(settings.MEDIA_URL, 'contact_form', file_name) + files.append(os.path.join(settings.MEDIA_ROOT, 'contact_form', file_name)) + # Successful submission - if 'django_notify' in settings.INSTALLED_APPS: - request.notifications.success(success_message) - else: - message = success_message + messages.success(request, success_message) + message = success_message + form_success = True if form_definition.log_data: form_definition.log(form) if form_definition.mail_to: - form_definition.send_mail(form) + form_definition.send_mail(form, files) if form_definition.success_redirect and not is_cms_plugin: # TODO Redirection does not work for cms plugin return HttpResponseRedirect(form_definition.action or '?') if form_definition.success_clear: form = DesignedForm(form_definition) # clear form else: - if 'django_notify' in settings.INSTALLED_APPS: - request.notifications.error(error_message) - else: - message = error_message + form_error = True + messages.error(request, error_message) + message = error_message else: if form_definition.allow_get_initial: form = DesignedForm(form_definition, initial_data=request.GET) @@ -67,10 +77,12 @@ def process_form(request, form_definition, context={}, is_cms_plugin=False): context.update({ 'message': message, + 'form_error': form_error, + 'form_success': form_success, 'form': form, 'form_definition': form_definition }) - + context.update(csrf(request)) return context def detail(request, object_name): @@ -78,8 +90,8 @@ def detail(request, object_name): result = process_form(request, form_definition) if isinstance(result, HttpResponseRedirect): return result - else: - result.update({ - 'form_template': form_definition.form_template_name or app_settings.get('FORM_DESIGNER_DEFAULT_FORM_TEMPLATE') - }) - return render_to_response('html/formdefinition/detail.html', result, context_instance=RequestContext(request)) + result.update({ + 'form_template': form_definition.form_template_name or settings.DEFAULT_FORM_TEMPLATE + }) + return render_to_response('html/formdefinition/detail.html', result, + context_instance=RequestContext(request)) diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..1ff6abc1 --- /dev/null +++ b/setup.py @@ -0,0 +1,44 @@ +# encoding=utf8 +import os +from distutils.core import setup + +def read(fname): + return open(os.path.join(os.path.dirname(__file__), fname)).read() + +README = read('README.md') + +setup( + name = "django-form-designer", + version = "0.1a8", + url = 'http://github.com/philomat/django-form-designer', + license = 'BSD', + description = "Design contact forms, search forms etc from the Django admin, without writing any code. Integrates with Django CMS.", + long_description = README, + + author = u'Samuel Lüscher', + author_email = 'philomat@popkultur.net', + packages = [ + 'form_designer', + 'form_designer.migrations', + 'form_designer.templatetags', + ], + package_data = { + 'form_designer': [ + 'media/form_designer/js/*.js', + 'templates/admin/form_designer/formlog/change_list.html', + 'templates/html/formdefinition/*.html', + 'templates/html/formdefinition/forms/*.html', + 'templates/txt/formdefinition/*.txt', + 'locale/*/LC_MESSAGES/*', + ], + }, + classifiers = [ + 'Development Status :: 4 - Beta', + 'Framework :: Django', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: BSD License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Topic :: Internet :: WWW/HTTP', + ] +)